diff --git a/src/app/utils/yaml-operations.ts b/src/app/utils/yaml-operations.ts index 82b59b8..ae11be4 100644 --- a/src/app/utils/yaml-operations.ts +++ b/src/app/utils/yaml-operations.ts @@ -1,6 +1,6 @@ import * as yaml from 'js-yaml'; -import { PromptMetadata, Variable } from '../../shared/types'; +import { PromptMetadata, PromptVariable } from '../../shared/types'; import logger from '../../shared/utils/logger'; import { appConfig } from '../config/app-config'; @@ -73,8 +73,8 @@ export function isValidMetadata(obj: unknown): obj is PromptMetadata { return true; } -function isValidVariable(obj: unknown): obj is Variable { - const variable = obj as Partial; +function isValidVariable(obj: unknown): obj is PromptVariable { + const variable = obj as Partial; return ( typeof variable === 'object' && variable !== null && diff --git a/src/cli/commands/config-command.ts b/src/cli/commands/config-command.ts index 174f6f9..e7e7ffa 100644 --- a/src/cli/commands/config-command.ts +++ b/src/cli/commands/config-command.ts @@ -42,7 +42,7 @@ class ConfigCommand extends BaseCommand { console.log(chalk.yellow('The configuration is empty.')); } else { Object.entries(currentConfig).forEach(([key, value]) => { - console.log(chalk.green(`${key}:`), chalk.yellow(key === 'ANTHROPIC_API_KEY' ? '********' : value)); + console.log(`${key} -->`, chalk.green(key === 'ANTHROPIC_API_KEY' ? '********' : value)); }); } } diff --git a/src/cli/commands/env-command.ts b/src/cli/commands/env-command.ts index 961c1a4..f218879 100644 --- a/src/cli/commands/env-command.ts +++ b/src/cli/commands/env-command.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import { BaseCommand } from './base-command'; -import { EnvVar, Fragment } from '../../shared/types'; +import { EnvVariable, PromptFragment } from '../../shared/types'; import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string-formatter'; import { FRAGMENT_PREFIX } from '../constants'; import { createEnvVar, readEnvVars, updateEnvVar, deleteEnvVar } from '../utils/env-vars'; @@ -39,7 +39,7 @@ class EnvCommand extends BaseCommand { private formatVariableChoices( allVariables: Array<{ name: string; role: string }>, - envVars: EnvVar[] + envVars: EnvVariable[] ): Array<{ name: string; value: { name: string; role: string } }> { const maxNameLength = Math.max(...allVariables.map((v) => formatSnakeCase(v.name).length)); return allVariables.map((variable) => { @@ -48,17 +48,19 @@ class EnvCommand extends BaseCommand { const envVar = envVars.find((v) => formatSnakeCase(v.name) === formattedName); const status = this.getVariableStatus(envVar); return { - name: `${chalk.cyan(paddedName)}: ${status}`, + name: `${paddedName} --> ${status}`, value: variable }; }); } - private getVariableStatus(envVar: EnvVar | undefined): string { + private getVariableStatus(envVar: EnvVariable | undefined): string { if (!envVar) return chalk.yellow('Not Set'); - if (envVar.value.startsWith('Fragment:')) return chalk.blue(envVar.value); - return chalk.green(`Set: ${envVar.value.substring(0, 20)}${envVar.value.length > 20 ? '...' : ''}`); + const trimmedValue = envVar.value.trim(); + return trimmedValue.startsWith(FRAGMENT_PREFIX) + ? chalk.blue(trimmedValue) + : chalk.green(`Set: ${trimmedValue.substring(0, 20)}${trimmedValue.length > 20 ? '...' : ''}`); } private async manageEnvVar(variable: { name: string; role: string }): Promise { @@ -94,7 +96,7 @@ class EnvCommand extends BaseCommand { private async enterValueForVariable( variable: { name: string; role: string }, - envVar: EnvVar | undefined + envVar: EnvVariable | undefined ): Promise { try { const currentValue = envVar?.value || ''; @@ -128,10 +130,10 @@ class EnvCommand extends BaseCommand { if (!fragments) return; - const selectedFragment = await this.showMenu( + const selectedFragment = await this.showMenu( 'Select a fragment: ', fragments.map((f) => ({ - name: `${formatTitleCase(f.category)} / ${chalk.blue(f.name)}`, + name: `${formatTitleCase(f.category)} > ${chalk.blue(f.name)}`, value: f })) ); @@ -182,7 +184,10 @@ class EnvCommand extends BaseCommand { } } - private async unsetVariable(variable: { name: string; role: string }, envVar: EnvVar | undefined): Promise { + private async unsetVariable( + variable: { name: string; role: string }, + envVar: EnvVariable | undefined + ): Promise { try { if (envVar) { const deleteResult = await deleteEnvVar(envVar.id); diff --git a/src/cli/commands/execute-command.ts b/src/cli/commands/execute-command.ts index 2679658..d973743 100644 --- a/src/cli/commands/execute-command.ts +++ b/src/cli/commands/execute-command.ts @@ -3,8 +3,9 @@ import fs from 'fs-extra'; import yaml from 'js-yaml'; import { BaseCommand } from './base-command'; -import { PromptMetadata, Variable } from '../../shared/types'; +import { PromptMetadata } from '../../shared/types'; import { processPromptContent, updatePromptWithVariables } from '../../shared/utils/prompt-processing'; +import { formatSnakeCase } from '../../shared/utils/string-formatter'; import { getPromptFiles, viewPromptDetails } from '../utils/prompts'; class ExecuteCommand extends BaseCommand { @@ -129,7 +130,10 @@ Note: fileInputs: Record ): Promise { try { - const promptFiles = await this.handleApiResult(await getPromptFiles(promptId), 'Fetched prompt files'); + const promptFiles = await this.handleApiResult( + await getPromptFiles(promptId, { cleanVariables: true }), + 'Fetched prompt files' + ); if (!promptFiles) return; @@ -177,7 +181,7 @@ Note: description: metadata.description, tags: metadata.tags, variables: metadata.variables - } as PromptMetadata & { variables: Variable[] }, + } as PromptMetadata, true ); } catch (error) { @@ -190,53 +194,74 @@ Note: metadata: PromptMetadata, dynamicOptions: Record, fileInputs: Record - ): Promise { - try { - const userInputs: Record = {}; - - for (const variable of metadata.variables) { - if (!variable.optional_for_user && !variable.value) { - const snakeCaseName = variable.name.replace(/[{}]/g, '').toLowerCase(); - const hasValue = - (dynamicOptions && snakeCaseName in dynamicOptions) || - (fileInputs && snakeCaseName in fileInputs); - - if (!hasValue) { - throw new Error(`Required variable ${snakeCaseName} is not set`); - } - } + ): Promise { + const userInputs: Record = {}; + const missingVariables: string[] = []; + + for (const variable of metadata.variables) { + const variableName = variable.name.replace(/[{}]/g, ''); + const snakeCaseName = variableName.toLowerCase(); + + if (variable.value) { + userInputs[variable.name] = variable.value; + continue; } - for (const variable of metadata.variables) { - const snakeCaseName = variable.name.replace(/[{}]/g, '').toLowerCase(); - let value = dynamicOptions[snakeCaseName]; - - if (fileInputs[snakeCaseName]) { - try { - value = await fs.readFile(fileInputs[snakeCaseName], 'utf-8'); - console.log(chalk.green(`Loaded file content for ${snakeCaseName}`)); - } catch (error) { - console.error(chalk.red(`Error reading file for ${snakeCaseName}:`, error)); - throw new Error(`Failed to read file for ${snakeCaseName}`); - } - } + if (!variable.optional_for_user) { + const hasValue = + (dynamicOptions && snakeCaseName in dynamicOptions) || (fileInputs && snakeCaseName in fileInputs); - if (value) { - userInputs[variable.name] = value; + if (!hasValue) { + missingVariables.push(snakeCaseName); } } + } + + if (missingVariables.length > 0) { + throw new Error(`Missing required variables: ${missingVariables.join(', ')}`); + } - const updatedPromptContent = updatePromptWithVariables(promptContent, userInputs); - const result = await processPromptContent([{ role: 'user', content: updatedPromptContent }], false, false); + for (const variable of metadata.variables) { + const variableName = variable.name.replace(/[{}]/g, ''); + const snakeCaseName = variableName.toLowerCase(); - if (typeof result === 'string') { - console.log(result); - } else { - console.error(chalk.red('Unexpected result format from prompt processing')); + if (variable.name in userInputs) { + continue; + } + + let value = dynamicOptions[snakeCaseName]; + + if (fileInputs[snakeCaseName]) { + try { + value = await fs.readFile(fileInputs[snakeCaseName], 'utf-8'); + // console.log(chalk.green(`Loaded file content for ${snakeCaseName}`)); + } catch (error) { + console.error(chalk.red(`Error reading file for ${snakeCaseName}:`, error)); + throw new Error(`Failed to read file for ${snakeCaseName}`); + } + } + + if (value !== undefined) { + userInputs[variable.name] = value; + } else if (!variable.optional_for_user) { + throw new Error(`Required variable ${snakeCaseName} is not set`); } - } catch (error) { - this.handleError(error, 'executing prompt with metadata'); } + + console.log(chalk.cyan('\nUsing variables:')); + Object.entries(userInputs).forEach(([key, value]) => { + console.log(` ${formatSnakeCase(key)}: ${value.length > 50 ? value.substring(0, 50) + '...' : value}`); + }); + + const updatedPromptContent = updatePromptWithVariables(promptContent, userInputs); + const result = await processPromptContent([{ role: 'user', content: updatedPromptContent }], false, false); + + if (typeof result !== 'string') { + throw new Error('Unexpected result format from prompt processing'); + } + + console.log(result); + return result; } } diff --git a/src/cli/commands/fragments-command.ts b/src/cli/commands/fragments-command.ts index 0f36130..b126442 100644 --- a/src/cli/commands/fragments-command.ts +++ b/src/cli/commands/fragments-command.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import { BaseCommand } from './base-command'; -import { Fragment } from '../../shared/types'; +import { PromptFragment } from '../../shared/types'; import { formatTitleCase } from '../../shared/utils/string-formatter'; import { listFragments, viewFragmentContent } from '../utils/fragments'; @@ -41,14 +41,14 @@ class FragmentsCommand extends BaseCommand { } } - private async viewAllFragments(fragments: Fragment[]): Promise { + private async viewAllFragments(fragments: PromptFragment[]): Promise { const sortedFragments = fragments.sort((a, b) => `${a.category}/${a.name}`.localeCompare(`${b.category}/${b.name}`) ); await this.viewFragmentMenu(sortedFragments); } - private async viewFragmentsByCategory(fragments: Fragment[]): Promise { + private async viewFragmentsByCategory(fragments: PromptFragment[]): Promise { const categories = [...new Set(fragments.map((f) => f.category))].sort(); while (true) { const category = await this.showMenu( @@ -65,12 +65,12 @@ class FragmentsCommand extends BaseCommand { } } - private async viewFragmentMenu(fragments: Fragment[]): Promise { + private async viewFragmentMenu(fragments: PromptFragment[]): Promise { while (true) { - const selectedFragment = await this.showMenu( + const selectedFragment = await this.showMenu( 'Select a fragment to view:', fragments.map((f) => ({ - name: `${formatTitleCase(f.category)} / ${chalk.green(f.name)}`, + name: `${formatTitleCase(f.category)} > ${chalk.green(f.name)}`, value: f })) ); @@ -83,7 +83,7 @@ class FragmentsCommand extends BaseCommand { } } - private async displayFragmentContent(fragment: Fragment): Promise { + private async displayFragmentContent(fragment: PromptFragment): Promise { try { const content = await this.handleApiResult( await viewFragmentContent(fragment.category, fragment.name), diff --git a/src/cli/commands/menu-command.ts b/src/cli/commands/menu-command.ts index e6ebc1e..a2d797a 100644 --- a/src/cli/commands/menu-command.ts +++ b/src/cli/commands/menu-command.ts @@ -34,11 +34,10 @@ class MenuCommand extends BaseCommand { { name: 'Settings', value: 'settings' } ); - // console.clear(); + console.clear(); const action = await this.showMenu( - `${chalk.reset(chalk.italic(chalk.cyan('Want to manage AI prompts with ease ?')))} -${chalk.bold(`${chalk.yellow('Welcome to the Prompt Library !')} + `${chalk.bold(`${chalk.cyan('Welcome to the Prompt Library !')} Select an action:`)}`, choices, { goBackLabel: 'Exit' } diff --git a/src/cli/commands/prompts-command.ts b/src/cli/commands/prompts-command.ts index d4ee6f6..41157dd 100644 --- a/src/cli/commands/prompts-command.ts +++ b/src/cli/commands/prompts-command.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import { BaseCommand } from './base-command'; -import { CategoryItem, EnvVar, Fragment, PromptMetadata, Variable } from '../../shared/types'; +import { CategoryItem, EnvVariable, PromptFragment, PromptMetadata, PromptVariable } from '../../shared/types'; import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string-formatter'; import { ENV_PREFIX, FRAGMENT_PREFIX } from '../constants'; import { ConversationManager } from '../utils/conversation-manager'; @@ -11,7 +11,7 @@ import { listFragments, viewFragmentContent } from '../utils/fragments'; import { viewPromptDetails } from '../utils/prompts'; type PromptMenuAction = 'all' | 'category' | 'id' | 'back'; -type SelectPromptMenuAction = Variable | 'execute' | 'unset_all' | 'back'; +type SelectPromptMenuAction = PromptVariable | 'execute' | 'unset_all' | 'back'; class PromptCommand extends BaseCommand { constructor() { @@ -80,7 +80,7 @@ class PromptCommand extends BaseCommand { } else { console.log(chalk.bold('All prompts:')); allPrompts.forEach((prompt) => { - console.log(`${chalk.green(prompt.id)} - ${chalk.cyan(prompt.category)} / ${prompt.title}`); + console.log(`${chalk.green(prompt.id)} - ${chalk.cyan(prompt.category)} > ${prompt.title}`); }); } } @@ -145,7 +145,7 @@ class PromptCommand extends BaseCommand { const selectedPrompt = await this.showMenu( 'Select a prompt or action:', prompts.map((p) => ({ - name: `${formatTitleCase(p.category)} / ${chalk.green(p.title)} (ID: ${p.id})`, + name: `${formatTitleCase(p.category)} > ${chalk.green(p.title)} (ID: ${p.id})`, value: p })) ); @@ -203,7 +203,10 @@ class PromptCommand extends BaseCommand { ); } - private formatVariableChoices(variables: Variable[], envVars: EnvVar[]): Array<{ name: string; value: Variable }> { + private formatVariableChoices( + variables: PromptVariable[], + envVars: EnvVariable[] + ): Array<{ name: string; value: PromptVariable }> { return variables.map((v) => { const snakeCaseName = formatSnakeCase(v.name); const nameColor = this.getVariableNameColor(v); @@ -215,7 +218,7 @@ class PromptCommand extends BaseCommand { }); } - private getVariableNameColor(v: Variable): (text: string) => string { + private getVariableNameColor(v: PromptVariable): (text: string) => string { if (v.value) { if (v.value.startsWith(FRAGMENT_PREFIX)) return chalk.blue; @@ -225,7 +228,7 @@ class PromptCommand extends BaseCommand { return v.optional_for_user ? chalk.yellow : chalk.red; } - private getVariableHint(v: Variable, envVars: EnvVar[]): string { + private getVariableHint(v: PromptVariable, envVars: EnvVariable[]): string { if (!v.value) { const matchingEnvVar = envVars.find((env) => env.name === v.name); @@ -236,7 +239,7 @@ class PromptCommand extends BaseCommand { return ''; } - private async assignVariable(promptId: string, variable: Variable): Promise { + private async assignVariable(promptId: string, variable: PromptVariable): Promise { const envVarsResult = await readEnvVars(); const envVars = envVarsResult.success ? envVarsResult.data || [] : []; const matchingEnvVar = envVars.find((env) => env.name === variable.name); @@ -275,7 +278,7 @@ class PromptCommand extends BaseCommand { } } - private async assignValueToVariable(promptId: string, variable: Variable): Promise { + private async assignValueToVariable(promptId: string, variable: PromptVariable): Promise { console.log(chalk.cyan(`Enter or edit value for ${formatSnakeCase(variable.name)}:`)); console.log( chalk.yellow('(An editor will open with the current value. Edit, save, and close the file when done.)') @@ -292,12 +295,12 @@ class PromptCommand extends BaseCommand { } } - private async assignFragmentToVariable(promptId: string, variable: Variable): Promise { + private async assignFragmentToVariable(promptId: string, variable: PromptVariable): Promise { const fragmentsResult = await this.handleApiResult(await listFragments(), 'Fetched fragments'); if (!fragmentsResult) return; - const selectedFragment = await this.showMenu( + const selectedFragment = await this.showMenu( 'Select a fragment: ', fragmentsResult.map((f) => ({ name: `${f.category}/${f.name}`, @@ -331,7 +334,7 @@ class PromptCommand extends BaseCommand { } } - private async assignEnvVarToVariable(promptId: string, variable: Variable): Promise { + private async assignEnvVarToVariable(promptId: string, variable: PromptVariable): Promise { const envVarsResult = await readEnvVars(); if (!envVarsResult.success) { @@ -344,7 +347,7 @@ class PromptCommand extends BaseCommand { ev.name.toLowerCase().includes(variable.name.toLowerCase()) || variable.name.toLowerCase().includes(ev.name.toLowerCase()) ); - const selectedEnvVar = await this.showMenu('Select an Environment Variable:', [ + const selectedEnvVar = await this.showMenu('Select an Environment Variable:', [ ...matchingEnvVars.map((v) => ({ name: chalk.green(chalk.bold(`${formatSnakeCase(v.name)} (${v.scope}) - Suggested Match`)), value: v @@ -373,7 +376,7 @@ class PromptCommand extends BaseCommand { console.log(chalk.cyan(`Current value: ${selectedEnvVar.value}`)); } - private async unsetVariable(promptId: string, variable: Variable): Promise { + private async unsetVariable(promptId: string, variable: PromptVariable): Promise { const unsetResult = await updatePromptVariable(promptId, variable.name, ''); if (unsetResult.success) { @@ -382,6 +385,7 @@ class PromptCommand extends BaseCommand { throw new Error(`Failed to unset value for ${formatSnakeCase(variable.name)}: ${unsetResult.error}`); } } + private async unsetAllVariables(promptId: string): Promise { const details = await this.handleApiResult(await getPromptDetails(promptId), 'Fetched prompt details'); diff --git a/src/cli/config/cli-config.ts b/src/cli/config/cli-config.ts index 4427ee2..5d1af97 100644 --- a/src/cli/config/cli-config.ts +++ b/src/cli/config/cli-config.ts @@ -11,8 +11,8 @@ export interface CliConfig { } export const cliConfig: CliConfig = { - PROMPTS_DIR: 'prompts', - FRAGMENTS_DIR: 'fragments', + PROMPTS_DIR: path.join(CONFIG_DIR, 'prompts'), + FRAGMENTS_DIR: path.join(CONFIG_DIR, 'fragments'), DB_PATH: path.join(CONFIG_DIR, 'prompts.sqlite'), TEMP_DIR: path.join(CONFIG_DIR, 'temp'), MENU_PAGE_SIZE: process.env.MENU_PAGE_SIZE ? parseInt(process.env.MENU_PAGE_SIZE, 10) : 20 diff --git a/src/cli/utils/__tests__/__snapshots__/prompts.test.ts.snap b/src/cli/utils/__tests__/__snapshots__/prompts.test.ts.snap index c636f79..6132939 100644 --- a/src/cli/utils/__tests__/__snapshots__/prompts.test.ts.snap +++ b/src/cli/utils/__tests__/__snapshots__/prompts.test.ts.snap @@ -12,8 +12,7 @@ Tags: tag1, tag2 Options: ([*] Required [ ] Optional) --var1 [*] test role - Env: env_var_name (env-value) -" + Env: env_var_name (env-value)" `; exports[`PromptsUtils viewPromptDetails should display fragment variable correctly 1`] = ` @@ -43,8 +42,7 @@ Options: ([*] Required [ ] Optional) Not Set (Required) --var2 [ ] test role 2 - Not Set -" + Not Set" `; exports[`PromptsUtils viewPromptDetails should display regular variable value correctly 1`] = ` @@ -74,6 +72,5 @@ Options: ([*] Required [ ] Optional) Not Set (Required) --var2 [ ] test role 2 - Not Set -" + Not Set" `; diff --git a/src/cli/utils/__tests__/env-vars.test.ts b/src/cli/utils/__tests__/env-vars.test.ts index 9d2ef18..fb2702b 100644 --- a/src/cli/utils/__tests__/env-vars.test.ts +++ b/src/cli/utils/__tests__/env-vars.test.ts @@ -1,4 +1,4 @@ -import { EnvVar } from '../../../shared/types'; +import { EnvVariable } from '../../../shared/types'; import { runAsync, allAsync } from '../database'; import { createEnvVar, readEnvVars, updateEnvVar, deleteEnvVar } from '../env-vars'; @@ -16,7 +16,7 @@ describe('EnvVarsUtils', () => { describe('createEnvVar', () => { it('should successfully create an environment variable', async () => { - const mockEnvVar: Omit = { + const mockEnvVar: Omit = { name: 'TEST_VAR', value: 'test-value', scope: 'global', @@ -39,7 +39,7 @@ describe('EnvVarsUtils', () => { }); it('should handle database errors during creation', async () => { - const mockEnvVar: Omit = { + const mockEnvVar: Omit = { name: 'TEST_VAR', value: 'test-value', scope: 'global', diff --git a/src/cli/utils/__tests__/fragments.test.ts b/src/cli/utils/__tests__/fragments.test.ts index bf151c5..337ccb7 100644 --- a/src/cli/utils/__tests__/fragments.test.ts +++ b/src/cli/utils/__tests__/fragments.test.ts @@ -2,7 +2,7 @@ import path from 'path'; import { jest } from '@jest/globals'; -import { Fragment } from '../../../shared/types'; +import { PromptFragment } from '../../../shared/types'; import { readDirectory, readFileContent } from '../../../shared/utils/file-system'; import { cliConfig } from '../../config/cli-config'; import { listFragments, viewFragmentContent } from '../fragments'; @@ -26,7 +26,7 @@ describe('FragmentsUtils', () => { .mockResolvedValueOnce(['fragment1.md', 'fragment2.md']) .mockResolvedValueOnce(['fragment3.md']); - const expectedFragments: Fragment[] = [ + const expectedFragments: PromptFragment[] = [ { category: 'category1', name: 'fragment1', variable: '' }, { category: 'category1', name: 'fragment2', variable: '' }, { category: 'category2', name: 'fragment3', variable: '' } @@ -50,7 +50,7 @@ describe('FragmentsUtils', () => { .mockResolvedValueOnce(['category1']) .mockResolvedValueOnce(['fragment1.md', 'fragment2.txt', 'fragment3.md']); - const expectedFragments: Fragment[] = [ + const expectedFragments: PromptFragment[] = [ { category: 'category1', name: 'fragment1', variable: '' }, { category: 'category1', name: 'fragment3', variable: '' } ]; diff --git a/src/cli/utils/__tests__/input-resolver.test.ts b/src/cli/utils/__tests__/input-resolver.test.ts index bc26d12..8d12cca 100644 --- a/src/cli/utils/__tests__/input-resolver.test.ts +++ b/src/cli/utils/__tests__/input-resolver.test.ts @@ -1,6 +1,6 @@ import { jest } from '@jest/globals'; -import { EnvVar } from '../../../shared/types'; +import { EnvVariable } from '../../../shared/types'; import { FRAGMENT_PREFIX, ENV_PREFIX } from '../../constants'; import { readEnvVars } from '../env-vars'; import { viewFragmentContent } from '../fragments'; @@ -20,7 +20,7 @@ describe('InputResolverUtils', () => { }); describe('resolveValue', () => { - const mockEnvVars: EnvVar[] = [ + const mockEnvVars: EnvVariable[] = [ { id: 1, name: 'TEST_VAR', value: 'test-value', scope: 'global' }, { id: 2, name: 'NESTED_VAR', value: '$env:TEST_VAR', scope: 'global' } ]; diff --git a/src/cli/utils/__tests__/prompts.test.ts b/src/cli/utils/__tests__/prompts.test.ts index 26a2ef6..2f3dde3 100644 --- a/src/cli/utils/__tests__/prompts.test.ts +++ b/src/cli/utils/__tests__/prompts.test.ts @@ -1,6 +1,6 @@ import { RunResult } from 'sqlite3'; -import { PromptMetadata, Variable } from '../../../shared/types'; +import { PromptMetadata, PromptVariable } from '../../../shared/types'; import { ENV_PREFIX, FRAGMENT_PREFIX } from '../../constants'; import { allAsync, getAsync, runAsync } from '../database'; import { readEnvVars } from '../env-vars'; @@ -385,7 +385,7 @@ describe('PromptsUtils', () => { }); it('should display fragment variable correctly', async () => { - const mockPromptWithFragment: PromptMetadata & { variables: Variable[] } = { + const mockPromptWithFragment: PromptMetadata & { variables: PromptVariable[] } = { ...mockPrompt, variables: [ { @@ -402,7 +402,7 @@ describe('PromptsUtils', () => { }); it('should display env variable correctly', async () => { - const mockPromptWithEnvVar: PromptMetadata & { variables: Variable[] } = { + const mockPromptWithEnvVar: PromptMetadata & { variables: PromptVariable[] } = { ...mockPrompt, variables: [ { @@ -425,7 +425,7 @@ describe('PromptsUtils', () => { }); it('should display regular variable value correctly', async () => { - const mockPromptWithValue: PromptMetadata & { variables: Variable[] } = { + const mockPromptWithValue: PromptMetadata & { variables: PromptVariable[] } = { ...mockPrompt, variables: [ { diff --git a/src/cli/utils/database.ts b/src/cli/utils/database.ts index 3c93a40..10e6363 100644 --- a/src/cli/utils/database.ts +++ b/src/cli/utils/database.ts @@ -8,7 +8,7 @@ import sqlite3, { RunResult } from 'sqlite3'; import { AppError, handleError } from './errors'; import { createPrompt } from './prompts'; import { commonConfig } from '../../shared/config/common-config'; -import { ApiResult, CategoryItem, PromptMetadata, Variable } from '../../shared/types'; +import { ApiResult, CategoryItem, PromptMetadata, PromptVariable } from '../../shared/types'; import { fileExists, readDirectory, readFileContent } from '../../shared/utils/file-system'; import logger from '../../shared/utils/logger'; import { cliConfig } from '../config/cli-config'; @@ -123,7 +123,7 @@ export async function initDatabase(): Promise> { prompt_id INTEGER, category TEXT NOT NULL, name TEXT NOT NULL, - variable TEXT NOT NULL, + variable TEXT NOT NULL DEFAULT '', FOREIGN KEY (prompt_id) REFERENCES prompts (id) )`, `CREATE TABLE IF NOT EXISTS env_vars ( @@ -184,9 +184,9 @@ export async function fetchCategories(): Promise> { +): Promise> { const promptResult = await getAsync('SELECT * FROM prompts WHERE id = ?', [promptId]); - const variablesResult = await allAsync( + const variablesResult = await allAsync( 'SELECT name, role, value, optional_for_user FROM variables WHERE prompt_id = ?', [promptId] ); diff --git a/src/cli/utils/env-vars.ts b/src/cli/utils/env-vars.ts index 529f2c3..308e7bc 100644 --- a/src/cli/utils/env-vars.ts +++ b/src/cli/utils/env-vars.ts @@ -1,8 +1,8 @@ import { runAsync, allAsync } from './database'; import { handleError } from './errors'; -import { EnvVar, ApiResult } from '../../shared/types'; +import { EnvVariable, ApiResult } from '../../shared/types'; -export async function createEnvVar(envVar: Omit): Promise> { +export async function createEnvVar(envVar: Omit): Promise> { try { const result = await runAsync('INSERT INTO env_vars (name, value, scope, prompt_id) VALUES (?, ?, ?, ?)', [ envVar.name, @@ -20,7 +20,7 @@ export async function createEnvVar(envVar: Omit): Promise> { +export async function readEnvVars(promptId?: number): Promise> { try { let query = 'SELECT * FROM env_vars WHERE scope = "global"'; const params: any[] = []; @@ -30,7 +30,7 @@ export async function readEnvVars(promptId?: number): Promise(query, params); + const result = await allAsync(query, params); if (!result.success) { return { success: false, error: result.error || 'Failed to fetch environment variables' }; diff --git a/src/cli/utils/fragments.ts b/src/cli/utils/fragments.ts index c991278..cad1b7b 100644 --- a/src/cli/utils/fragments.ts +++ b/src/cli/utils/fragments.ts @@ -1,13 +1,13 @@ import path from 'path'; import { handleError } from './errors'; -import { ApiResult, Fragment } from '../../shared/types'; +import { ApiResult, PromptFragment } from '../../shared/types'; import { readDirectory, readFileContent } from '../../shared/utils/file-system'; import { cliConfig } from '../config/cli-config'; -export async function listFragments(): Promise> { +export async function listFragments(): Promise> { try { - const fragments: Fragment[] = []; + const fragments: PromptFragment[] = []; const categories = await readDirectory(cliConfig.FRAGMENTS_DIR); for (const category of categories) { diff --git a/src/cli/utils/input-resolver.ts b/src/cli/utils/input-resolver.ts index cd881a7..18bbe7c 100644 --- a/src/cli/utils/input-resolver.ts +++ b/src/cli/utils/input-resolver.ts @@ -1,11 +1,11 @@ -import { EnvVar } from '../../shared/types'; +import { EnvVariable } from '../../shared/types'; import logger from '../../shared/utils/logger'; import { FRAGMENT_PREFIX, ENV_PREFIX } from '../constants'; import { readEnvVars } from './env-vars'; import { handleError } from './errors'; import { viewFragmentContent } from './fragments'; -export async function resolveValue(value: string, envVars: EnvVar[]): Promise { +export async function resolveValue(value: string, envVars: EnvVariable[]): Promise { if (value.startsWith(FRAGMENT_PREFIX)) { const [category, name] = value.split(FRAGMENT_PREFIX)[1].split('/'); const fragmentResult = await viewFragmentContent(category, name); diff --git a/src/cli/utils/prompts.ts b/src/cli/utils/prompts.ts index 5c187d5..76043c9 100644 --- a/src/cli/utils/prompts.ts +++ b/src/cli/utils/prompts.ts @@ -2,11 +2,15 @@ import chalk from 'chalk'; import { allAsync, getAsync, runAsync } from './database'; import { handleError } from './errors'; -import { ApiResult, Fragment, PromptMetadata, Variable } from '../../shared/types'; +import { ApiResult, PromptFragment, PromptMetadata, PromptVariable } from '../../shared/types'; import { formatSnakeCase, formatTitleCase } from '../../shared/utils/string-formatter'; import { FRAGMENT_PREFIX, ENV_PREFIX } from '../constants'; import { readEnvVars } from './env-vars'; +interface GetPromptFilesOptions { + cleanVariables?: boolean; +} + export async function createPrompt(promptMetadata: PromptMetadata, content: string): Promise> { try { let tagsString: string; @@ -78,7 +82,8 @@ export async function listPrompts(): Promise> { } export async function getPromptFiles( - promptId: string + promptId: string, + options: GetPromptFilesOptions = { cleanVariables: false } ): Promise> { try { const promptContentResult = await getAsync<{ content: string }>('SELECT content FROM prompts WHERE id = ?', [ @@ -89,7 +94,7 @@ export async function getPromptFiles( return { success: false, error: 'Prompt not found' }; } - const metadataResult = await getPromptMetadata(promptId); + const metadataResult = await getPromptMetadata(promptId, options); if (!metadataResult.success || !metadataResult.data) { return { success: false, error: 'Failed to get prompt metadata' }; @@ -107,7 +112,10 @@ export async function getPromptFiles( } } -export async function getPromptMetadata(promptId: string): Promise> { +export async function getPromptMetadata( + promptId: string, + options: GetPromptFilesOptions = { cleanVariables: false } +): Promise> { try { const promptResult = await getAsync('SELECT * FROM prompts WHERE id = ?', [promptId]); @@ -124,16 +132,16 @@ export async function getPromptMetadata(promptId: string): Promise( - 'SELECT name, role, optional_for_user, value FROM variables WHERE prompt_id = ?', - [promptId] - ); + const variablesQuery = options.cleanVariables + ? 'SELECT name, role, optional_for_user FROM variables WHERE prompt_id = ?' + : 'SELECT name, role, optional_for_user, value FROM variables WHERE prompt_id = ?'; + const variablesResult = await allAsync(variablesQuery, [promptId]); if (!variablesResult.success || !variablesResult.data) { return { success: false, error: 'Failed to get variables' }; } - const fragmentsResult = await allAsync( + const fragmentsResult = await allAsync( 'SELECT category, name, variable FROM fragments WHERE prompt_id = ?', [promptId] ); @@ -142,6 +150,10 @@ export async function getPromptMetadata(promptId: string): Promise ({ + ...variable, + value: options.cleanVariables ? '' : variable.value || '' + })); const promptMetadata: PromptMetadata = { id: promptResult.data.id, title: promptResult.data.title, @@ -151,7 +163,7 @@ export async function getPromptMetadata(promptId: string): Promise { +export async function viewPromptDetails(details: PromptMetadata, isExecute = false): Promise { console.log(chalk.cyan('Prompt:'), details.title); console.log(`\n${details.description || ''}`); console.log(chalk.cyan('\nCategory:'), formatTitleCase(details.primary_category)); @@ -192,18 +201,20 @@ export async function viewPromptDetails( let status; if (variable.value) { - if (variable.value.startsWith(FRAGMENT_PREFIX)) { - status = chalk.blue(variable.value); - } else if (variable.value.startsWith(ENV_PREFIX)) { - const envVarName = variable.value.split(ENV_PREFIX)[1]; + const trimmedValue = variable.value.trim(); + + if (trimmedValue.startsWith(FRAGMENT_PREFIX)) { + status = chalk.blue(trimmedValue); + } else if (trimmedValue.startsWith(ENV_PREFIX)) { + const envVarName = trimmedValue.split(ENV_PREFIX)[1]; const envVar = envVars.find((v: { name: string }) => v.name === envVarName); - const envValue = envVar ? envVar.value : 'Not found'; + const envValue = envVar ? envVar.value.trim() : 'Not found'; status = chalk.magenta( `${ENV_PREFIX}${formatSnakeCase(envVarName)} (${envValue.substring(0, 30)}${envValue.length > 30 ? '...' : ''})` ); } else { status = chalk.green( - `Set: ${variable.value.substring(0, 30)}${variable.value.length > 30 ? '...' : ''}` + `Set: ${trimmedValue.substring(0, 30)}${trimmedValue.length > 30 ? '...' : ''}` ); } } else { @@ -223,10 +234,6 @@ export async function viewPromptDetails( console.log(` ${status}`); } } - - if (!isExecute) { - console.log(); - } } catch (error) { handleError(error, 'viewing prompt details'); } diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index 58ed1bd..991b5ed 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -1,4 +1,4 @@ -export interface EnvVar { +export interface EnvVariable { id: number; name: string; value: string; @@ -15,7 +15,7 @@ export interface CategoryItem { subcategories: string[]; } -export interface Variable { +export interface PromptVariable { name: string; role: string; optional_for_user: boolean; @@ -37,13 +37,13 @@ export interface PromptMetadata { tags: string | string[]; one_line_description: string; description: string; - variables: Variable[]; + variables: PromptVariable[]; content_hash?: string; - fragments?: Fragment[]; + fragments?: PromptFragment[]; } -export interface Fragment { +export interface PromptFragment { name: string; category: string; - variable: string; + variable?: string; } diff --git a/src/shared/utils/prompt-processing.ts b/src/shared/utils/prompt-processing.ts index b93ab2d..4602620 100644 --- a/src/shared/utils/prompt-processing.ts +++ b/src/shared/utils/prompt-processing.ts @@ -56,9 +56,9 @@ export async function processPromptContent( try { if (logging) { - console.log(chalk.blue(chalk.bold('\nYou:'))); + console.log(chalk.blue(chalk.bold('You:'))); console.log(messages[messages.length - 1]?.content); - console.log(chalk.green(chalk.bold('\nAI:'))); + console.log(chalk.green(chalk.bold('AI:'))); } if (useStreaming) {