From ef06f96c92f7ef8a24b2494fe8d407998329cd05 Mon Sep 17 00:00:00 2001 From: Thibault YOU Date: Tue, 22 Oct 2024 00:30:39 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=9A=20refactor:=20Reorganize=20file=20?= =?UTF-8?q?structure=20and=20rename=20utils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/core/update_metadata.ts | 8 +- src/app/core/update_views.ts | 8 +- src/app/utils/fragment_manager.util.ts | 31 +++ ..._operations.ts => prompt_analyzer.util.ts} | 37 +--- src/app/utils/prompt_operations.ts | 56 ------ ..._operations.ts => yaml_operations.util.ts} | 2 +- src/cli/{config => }/cli.config.ts | 2 +- src/cli/cli.constants.ts | 3 + src/cli/commands/base.command.ts | 10 +- src/cli/commands/env.command.ts | 14 +- src/cli/commands/execute.command.ts | 5 +- src/cli/commands/fragments.command.ts | 4 +- src/cli/commands/menu.command.ts | 2 +- src/cli/commands/prompts.command.ts | 19 +- src/cli/commands/sync.command.ts | 4 +- ...n.util.ts => conversation_manager.util.ts} | 7 +- src/cli/utils/database.util.ts | 8 +- src/cli/utils/error.util.ts | 2 +- .../{content.util.ts => file_system.util.ts} | 4 +- ...nt.util.ts => fragment_operations.util.ts} | 4 +- ...t.cli.util.ts => input_processing.util.ts} | 41 +--- src/cli/utils/input_resolution.util.ts | 24 +++ src/cli/utils/prompt.util.ts | 188 ------------------ src/cli/utils/prompt_crud.util.ts | 93 +++++++++ src/cli/utils/prompt_display.util.ts | 76 +++++++ src/cli/utils/prompt_execution.util.ts | 25 +++ src/shared/config/index.ts | 2 +- ...pic_client.ts => anthropic_client.util.ts} | 0 ...file_operations.ts => file_system.util.ts} | 0 .../utils/{logger.ts => logger.util.ts} | 0 ...perations.ts => prompt_processing.util.ts} | 2 +- ..._formatter.ts => string_formatter.util.ts} | 0 32 files changed, 326 insertions(+), 355 deletions(-) create mode 100644 src/app/utils/fragment_manager.util.ts rename src/app/utils/{analyzer_operations.ts => prompt_analyzer.util.ts} (75%) delete mode 100644 src/app/utils/prompt_operations.ts rename src/app/utils/{yaml_operations.ts => yaml_operations.util.ts} (99%) rename src/cli/{config => }/cli.config.ts (88%) create mode 100644 src/cli/cli.constants.ts rename src/cli/utils/{conversation.util.ts => conversation_manager.util.ts} (92%) rename src/cli/utils/{content.util.ts => file_system.util.ts} (86%) rename src/cli/utils/{fragment.util.ts => fragment_operations.util.ts} (95%) rename src/cli/utils/{prompt.cli.util.ts => input_processing.util.ts} (53%) create mode 100644 src/cli/utils/input_resolution.util.ts delete mode 100644 src/cli/utils/prompt.util.ts create mode 100644 src/cli/utils/prompt_crud.util.ts create mode 100644 src/cli/utils/prompt_display.util.ts create mode 100644 src/cli/utils/prompt_execution.util.ts rename src/shared/utils/{anthropic_client.ts => anthropic_client.util.ts} (100%) rename src/shared/utils/{file_operations.ts => file_system.util.ts} (100%) rename src/shared/utils/{logger.ts => logger.util.ts} (100%) rename src/shared/utils/{prompt_operations.ts => prompt_processing.util.ts} (99%) rename src/shared/utils/{string_formatter.ts => string_formatter.util.ts} (100%) diff --git a/src/app/core/update_metadata.ts b/src/app/core/update_metadata.ts index bf3a122..e19dc3b 100644 --- a/src/app/core/update_metadata.ts +++ b/src/app/core/update_metadata.ts @@ -14,11 +14,11 @@ import { removeDirectory, renameFile, writeFileContent -} from '../../shared/utils/file_operations'; -import logger from '../../shared/utils/logger'; +} from '../../shared/utils/file_system.util'; +import logger from '../../shared/utils/logger.util'; import { appConfig } from '../config/app.config'; -import { processMetadataGeneration } from '../utils/analyzer_operations'; -import { dumpYamlContent, sanitizeYamlContent } from '../utils/yaml_operations'; +import { processMetadataGeneration } from '../utils/prompt_analyzer.util'; +import { dumpYamlContent, sanitizeYamlContent } from '../utils/yaml_operations.util'; export async function generateMetadata(promptContent: string): Promise { logger.info('Starting metadata generation'); diff --git a/src/app/core/update_views.ts b/src/app/core/update_views.ts index e97c7ac..c17de1b 100644 --- a/src/app/core/update_views.ts +++ b/src/app/core/update_views.ts @@ -4,11 +4,11 @@ import * as nunjucks from 'nunjucks'; import { commonConfig } from '../../shared/config/common.config'; import { CategoryItem, Metadata } from '../../shared/types'; -import { isDirectory, readDirectory, readFileContent, writeFileContent } from '../../shared/utils/file_operations'; -import logger from '../../shared/utils/logger'; -import { formatTitleCase } from '../../shared/utils/string_formatter'; +import { isDirectory, readDirectory, readFileContent, writeFileContent } from '../../shared/utils/file_system.util'; +import logger from '../../shared/utils/logger.util'; +import { formatTitleCase } from '../../shared/utils/string_formatter.util'; import { appConfig } from '../config/app.config'; -import { parseYamlContent } from '../utils/yaml_operations'; +import { parseYamlContent } from '../utils/yaml_operations.util'; async function processPromptDirectory(promptDir: string, categories: Record): Promise { const promptPath = path.join(appConfig.PROMPTS_DIR, promptDir); diff --git a/src/app/utils/fragment_manager.util.ts b/src/app/utils/fragment_manager.util.ts new file mode 100644 index 0000000..fbda61a --- /dev/null +++ b/src/app/utils/fragment_manager.util.ts @@ -0,0 +1,31 @@ +import path from 'path'; + +import { isDirectory, readDirectory } from '../../shared/utils/file_system.util'; +import logger from '../../shared/utils/logger.util'; +import { appConfig } from '../config/app.config'; + +export async function listAvailableFragments(): Promise { + try { + logger.info('Listing available fragments'); + const fragmentsDir = path.join(appConfig.FRAGMENTS_DIR); + const categories = await readDirectory(fragmentsDir); + const fragments: Record = {}; + await Promise.all( + categories.map(async (category) => { + const categoryPath = path.join(fragmentsDir, category); + + if (await isDirectory(categoryPath)) { + const categoryFragments = await readDirectory(categoryPath); + fragments[category] = categoryFragments.map((f) => path.parse(f).name); + logger.debug(`Found ${fragments[category].length} fragments in category ${category}`); + } + }) + ); + + logger.info(`Listed fragments from ${Object.keys(fragments).length} categories`); + return JSON.stringify(fragments, null, 2); + } catch (error) { + logger.error('Error listing available fragments:', error); + throw error; + } +} diff --git a/src/app/utils/analyzer_operations.ts b/src/app/utils/prompt_analyzer.util.ts similarity index 75% rename from src/app/utils/analyzer_operations.ts rename to src/app/utils/prompt_analyzer.util.ts index 3608569..52e9fe1 100644 --- a/src/app/utils/analyzer_operations.ts +++ b/src/app/utils/prompt_analyzer.util.ts @@ -1,11 +1,10 @@ -import * as path from 'path'; - +import { listAvailableFragments } from './fragment_manager.util'; +import { parseYamlContent } from './yaml_operations.util'; import { Metadata } from '../../shared/types'; -import { readFileContent, readDirectory, isDirectory } from '../../shared/utils/file_operations'; -import logger from '../../shared/utils/logger'; -import { processPromptContent } from '../../shared/utils/prompt_operations'; +import { readFileContent } from '../../shared/utils/file_system.util'; +import logger from '../../shared/utils/logger.util'; +import { processPromptContent } from '../../shared/utils/prompt_processing.util'; import { appConfig } from '../config/app.config'; -import { parseYamlContent } from '../utils/yaml_operations'; export async function loadAnalyzerPrompt(): Promise { try { @@ -68,32 +67,6 @@ function extractOutputContent(content: string): string { return content.slice(outputStart + 8, outputEnd).trim(); } -export async function listAvailableFragments(): Promise { - try { - logger.info('Listing available fragments'); - const fragmentsDir = path.join(appConfig.FRAGMENTS_DIR); - const categories = await readDirectory(fragmentsDir); - const fragments: Record = {}; - await Promise.all( - categories.map(async (category) => { - const categoryPath = path.join(fragmentsDir, category); - - if (await isDirectory(categoryPath)) { - const categoryFragments = await readDirectory(categoryPath); - fragments[category] = categoryFragments.map((f) => path.parse(f).name); - logger.debug(`Found ${fragments[category].length} fragments in category ${category}`); - } - }) - ); - - logger.info(`Listed fragments from ${Object.keys(fragments).length} categories`); - return JSON.stringify(fragments, null, 2); - } catch (error) { - logger.error('Error listing available fragments:', error); - throw error; - } -} - function isValidMetadata(metadata: Metadata): boolean { if (!metadata.title || !metadata.description || !metadata.primary_category) { logger.warn('Missing one or more required fields in metadata: title, description, or primary_category'); diff --git a/src/app/utils/prompt_operations.ts b/src/app/utils/prompt_operations.ts deleted file mode 100644 index cbce937..0000000 --- a/src/app/utils/prompt_operations.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as path from 'path'; - -import { Metadata } from '../../shared/types'; -import { isDirectory, readDirectory, readFileContent } from '../../shared/utils/file_operations'; -import logger from '../../shared/utils/logger'; -import { processPromptContent } from '../../shared/utils/prompt_operations'; -import { appConfig } from '../config/app.config'; -import { parseYamlContent } from '../utils/yaml_operations'; - -export async function loadAnalyzerPrompt(): Promise { - logger.info(`Loading analyzer prompt from ${appConfig.ANALYZER_PROMPT_PATH}`); - const content = await readFileContent(appConfig.ANALYZER_PROMPT_PATH); - logger.info(`Analyzer prompt loaded, length: ${content.length} characters`); - return content; -} - -export async function processMetadataGeneration(promptContent: string): Promise { - logger.info('Processing prompt for metadata generation'); - - try { - const analyzerPrompt = await loadAnalyzerPrompt(); - const availableFragments = await listAvailableFragments(); - const variables = { - PROMPT_TO_ANALYZE: promptContent, - AVAILABLE_PROMPT_FRAGMENTS: availableFragments - }; - const content = await processPromptContent([{ role: 'user', content: analyzerPrompt }], variables, false); - const yamlContent = extractOutputContent(content); - return parseYamlContent(yamlContent); - } catch (error) { - logger.error('Error in processMetadataGeneration:', error); - throw error; - } -} - -export function extractOutputContent(content: string): string { - const outputStart = content.indexOf(''); - const outputEnd = content.indexOf(''); - return outputStart !== -1 && outputEnd !== -1 ? content.slice(outputStart + 8, outputEnd).trim() : content.trim(); -} - -export async function listAvailableFragments(): Promise { - const fragmentsDir = path.join(appConfig.FRAGMENTS_DIR); - const categories = await readDirectory(fragmentsDir); - const fragments: Record = {}; - - for (const category of categories) { - const categoryPath = path.join(fragmentsDir, category); - - if (await isDirectory(categoryPath)) { - const categoryFragments = await readDirectory(categoryPath); - fragments[category] = categoryFragments.map((f) => path.parse(f).name); - } - } - return JSON.stringify(fragments, null, 2); -} diff --git a/src/app/utils/yaml_operations.ts b/src/app/utils/yaml_operations.util.ts similarity index 99% rename from src/app/utils/yaml_operations.ts rename to src/app/utils/yaml_operations.util.ts index 3563c86..1fbec8e 100644 --- a/src/app/utils/yaml_operations.ts +++ b/src/app/utils/yaml_operations.util.ts @@ -1,7 +1,7 @@ import * as yaml from 'js-yaml'; import { Metadata, Variable } from '../../shared/types'; -import logger from '../../shared/utils/logger'; +import logger from '../../shared/utils/logger.util'; import { appConfig } from '../config/app.config'; /** diff --git a/src/cli/config/cli.config.ts b/src/cli/cli.config.ts similarity index 88% rename from src/cli/config/cli.config.ts rename to src/cli/cli.config.ts index edb07f5..080fe06 100644 --- a/src/cli/config/cli.config.ts +++ b/src/cli/cli.config.ts @@ -1,6 +1,6 @@ import * as path from 'path'; -import { CONFIG_DIR } from '../../shared/config/config.constants'; +import { CONFIG_DIR } from '../shared/config/config.constants'; export interface CliConfig { PROMPTS_DIR: string; diff --git a/src/cli/cli.constants.ts b/src/cli/cli.constants.ts new file mode 100644 index 0000000..7629ceb --- /dev/null +++ b/src/cli/cli.constants.ts @@ -0,0 +1,3 @@ +export const FRAGMENT_PREFIX = 'Fragment: '; + +export const ENV_PREFIX = 'Env: '; diff --git a/src/cli/commands/base.command.ts b/src/cli/commands/base.command.ts index d7583fd..cf41e3b 100644 --- a/src/cli/commands/base.command.ts +++ b/src/cli/commands/base.command.ts @@ -7,7 +7,8 @@ import { Command } from 'commander'; import fs from 'fs-extra'; import { ApiResult } from '../../shared/types'; -import { cliConfig } from '../config/cli.config'; +import { cliConfig } from '../cli.config'; +import { ENV_PREFIX, FRAGMENT_PREFIX } from '../cli.constants'; import { handleApiResult } from '../utils/database.util'; import { handleError } from '../utils/error.util'; @@ -75,10 +76,13 @@ export class BaseCommand extends Command { const tempFilePath = path.join(tempDir, 'input.txt'); try { - await fs.writeFile(tempFilePath, initialValue); + const cleanedInitialValue = + initialValue.startsWith(FRAGMENT_PREFIX) || initialValue.startsWith(ENV_PREFIX) ? '' : initialValue; + await fs.writeFile(tempFilePath, cleanedInitialValue); const input = await editor({ message: 'Edit your input', - default: initialValue, + default: cleanedInitialValue, + waitForUseInput: false, postfix: '.txt' }); return input; diff --git a/src/cli/commands/env.command.ts b/src/cli/commands/env.command.ts index f793f90..104ec0f 100644 --- a/src/cli/commands/env.command.ts +++ b/src/cli/commands/env.command.ts @@ -2,10 +2,11 @@ import chalk from 'chalk'; import { BaseCommand } from './base.command'; import { EnvVar, Fragment } from '../../shared/types'; -import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string_formatter'; +import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string_formatter.util'; +import { FRAGMENT_PREFIX } from '../cli.constants'; import { createEnvVar, readEnvVars, updateEnvVar, deleteEnvVar } from '../utils/env.util'; -import { listFragments, viewFragmentContent } from '../utils/fragment.util'; -import { listPrompts, getPromptFiles } from '../utils/prompt.util'; +import { listFragments, viewFragmentContent } from '../utils/fragment_operations.util'; +import { listPrompts, getPromptFiles } from '../utils/prompt_crud.util'; class EnvCommand extends BaseCommand { constructor() { @@ -36,7 +37,10 @@ class EnvCommand extends BaseCommand { } } - private formatVariableChoices(allVariables: Array<{ name: string; role: string }>, envVars: EnvVar[]): Array<{ name: string; value: { name: string; role: string } }> { + private formatVariableChoices( + allVariables: Array<{ name: string; role: string }>, + envVars: EnvVar[] + ): Array<{ name: string; value: { name: string; role: string } }> { const maxNameLength = Math.max(...allVariables.map((v) => formatSnakeCase(v.name).length)); return allVariables.map((variable) => { const formattedName = formatSnakeCase(variable.name); @@ -137,7 +141,7 @@ class EnvCommand extends BaseCommand { return; } - const fragmentRef = `Fragment: ${selectedFragment.category}/${selectedFragment.name}`; + const fragmentRef = `${FRAGMENT_PREFIX}${selectedFragment.category}/${selectedFragment.name}`; const envVars = await this.handleApiResult(await readEnvVars(), 'Fetched environment variables'); if (!envVars) return; diff --git a/src/cli/commands/execute.command.ts b/src/cli/commands/execute.command.ts index 5890a08..9b9e43a 100644 --- a/src/cli/commands/execute.command.ts +++ b/src/cli/commands/execute.command.ts @@ -4,8 +4,9 @@ import yaml from 'js-yaml'; import { BaseCommand } from './base.command'; import { Metadata, Prompt, Variable } from '../../shared/types'; -import { processPromptContent } from '../../shared/utils/prompt_operations'; -import { getPromptFiles, viewPromptDetails } from '../utils/prompt.util'; +import { processPromptContent } from '../../shared/utils/prompt_processing.util'; +import { getPromptFiles } from '../utils/prompt_crud.util'; +import { viewPromptDetails } from '../utils/prompt_display.util'; class ExecuteCommand extends BaseCommand { constructor() { diff --git a/src/cli/commands/fragments.command.ts b/src/cli/commands/fragments.command.ts index 9eb66e7..a47869c 100644 --- a/src/cli/commands/fragments.command.ts +++ b/src/cli/commands/fragments.command.ts @@ -2,8 +2,8 @@ import chalk from 'chalk'; import { BaseCommand } from './base.command'; import { Fragment } from '../../shared/types'; -import { formatTitleCase } from '../../shared/utils/string_formatter'; -import { listFragments, viewFragmentContent } from '../utils/fragment.util'; +import { formatTitleCase } from '../../shared/utils/string_formatter.util'; +import { listFragments, viewFragmentContent } from '../utils/fragment_operations.util'; type FragmentMenuAction = 'all' | 'category' | 'back'; diff --git a/src/cli/commands/menu.command.ts b/src/cli/commands/menu.command.ts index cce4b40..b938d1d 100644 --- a/src/cli/commands/menu.command.ts +++ b/src/cli/commands/menu.command.ts @@ -3,8 +3,8 @@ import { Command } from 'commander'; import { BaseCommand } from './base.command'; import { getConfig } from '../../shared/config'; -import { hasFragments, hasPrompts } from '../utils/content.util'; import { handleError } from '../utils/error.util'; +import { hasFragments, hasPrompts } from '../utils/file_system.util'; type MenuAction = 'sync' | 'prompts' | 'fragments' | 'settings' | 'env' | 'back'; diff --git a/src/cli/commands/prompts.command.ts b/src/cli/commands/prompts.command.ts index d0b54b2..83119c5 100644 --- a/src/cli/commands/prompts.command.ts +++ b/src/cli/commands/prompts.command.ts @@ -2,12 +2,13 @@ import chalk from 'chalk'; import { BaseCommand } from './base.command'; import { CategoryItem, EnvVar, Fragment, Prompt, Variable } from '../../shared/types'; -import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string_formatter'; -import { ConversationManager } from '../utils/conversation.util'; +import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string_formatter.util'; +import { ENV_PREFIX, FRAGMENT_PREFIX } from '../cli.constants'; +import { ConversationManager } from '../utils/conversation_manager.util'; import { fetchCategories, getPromptDetails, updatePromptVariable } from '../utils/database.util'; import { readEnvVars } from '../utils/env.util'; -import { listFragments, viewFragmentContent } from '../utils/fragment.util'; -import { viewPromptDetails } from '../utils/prompt.util'; +import { listFragments, viewFragmentContent } from '../utils/fragment_operations.util'; +import { viewPromptDetails } from '../utils/prompt_display.util'; type PromptMenuAction = 'all' | 'category' | 'id' | 'back'; type SelectPromptMenuAction = Variable | 'execute' | 'unset_all' | 'back'; @@ -216,9 +217,9 @@ class PromptCommand extends BaseCommand { private getVariableNameColor(v: Variable): (text: string) => string { if (v.value) { - if (v.value.startsWith('Fragment: ')) return chalk.blue; + if (v.value.startsWith(FRAGMENT_PREFIX)) return chalk.blue; - if (v.value.startsWith('Env: ')) return chalk.magenta; + if (v.value.startsWith(ENV_PREFIX)) return chalk.magenta; return chalk.green; } return v.optional_for_user ? chalk.yellow : chalk.red; @@ -297,7 +298,7 @@ class PromptCommand extends BaseCommand { if (!fragmentsResult) return; const selectedFragment = await this.showMenu( - 'Select a Fragment: ', + 'Select a fragment: ', fragmentsResult.map((f) => ({ name: `${f.category}/${f.name}`, value: f @@ -309,7 +310,7 @@ class PromptCommand extends BaseCommand { return; } - const fragmentRef = `Fragment: ${selectedFragment.category}/${selectedFragment.name}`; + const fragmentRef = `${FRAGMENT_PREFIX}${selectedFragment.category}/${selectedFragment.name}`; const updateResult = await updatePromptVariable(promptId, variable.name, fragmentRef); if (!updateResult.success) { @@ -361,7 +362,7 @@ class PromptCommand extends BaseCommand { return; } - const envVarRef = `Env: ${selectedEnvVar.name}`; + const envVarRef = `${ENV_PREFIX}${selectedEnvVar.name}`; const updateResult = await updatePromptVariable(promptId, variable.name, envVarRef); if (!updateResult.success) { diff --git a/src/cli/commands/sync.command.ts b/src/cli/commands/sync.command.ts index 159b054..49b55b1 100644 --- a/src/cli/commands/sync.command.ts +++ b/src/cli/commands/sync.command.ts @@ -6,8 +6,8 @@ import simpleGit, { SimpleGit } from 'simple-git'; import { BaseCommand } from './base.command'; import { getConfig, setConfig } from '../../shared/config'; -import logger from '../../shared/utils/logger'; -import { cliConfig } from '../config/cli.config'; +import logger from '../../shared/utils/logger.util'; +import { cliConfig } from '../cli.config'; import { syncPromptsWithDatabase, cleanupOrphanedData } from '../utils/database.util'; class SyncCommand extends BaseCommand { diff --git a/src/cli/utils/conversation.util.ts b/src/cli/utils/conversation_manager.util.ts similarity index 92% rename from src/cli/utils/conversation.util.ts rename to src/cli/utils/conversation_manager.util.ts index 87aea72..cc46a9f 100644 --- a/src/cli/utils/conversation.util.ts +++ b/src/cli/utils/conversation_manager.util.ts @@ -1,8 +1,9 @@ import { handleError } from './error.util'; -import { processCliPromptContent, resolveCliInputs } from './prompt.cli.util'; -import { getPromptFiles } from './prompt.util'; +import { resolveCliInputs } from './input_processing.util'; +import { processCliPromptContent } from './input_resolution.util'; +import { getPromptFiles } from './prompt_crud.util'; import { ApiResult } from '../../shared/types'; -import { processPromptContent, processPromptWithVariables } from '../../shared/utils/prompt_operations'; +import { processPromptContent, processPromptWithVariables } from '../../shared/utils/prompt_processing.util'; interface ConversationMessage { role: 'human' | 'assistant'; diff --git a/src/cli/utils/database.util.ts b/src/cli/utils/database.util.ts index 3a2e345..45e2fbf 100644 --- a/src/cli/utils/database.util.ts +++ b/src/cli/utils/database.util.ts @@ -6,12 +6,12 @@ import NodeCache from 'node-cache'; import sqlite3, { RunResult } from 'sqlite3'; import { AppError, handleError } from './error.util'; -import { createPrompt } from './prompt.util'; +import { createPrompt } from './prompt_crud.util'; import { commonConfig } from '../../shared/config/common.config'; import { ApiResult, CategoryItem, Metadata, Prompt, Variable } from '../../shared/types'; -import { fileExists, readDirectory, readFileContent } from '../../shared/utils/file_operations'; -import logger from '../../shared/utils/logger'; -import { cliConfig } from '../config/cli.config'; +import { fileExists, readDirectory, readFileContent } from '../../shared/utils/file_system.util'; +import logger from '../../shared/utils/logger.util'; +import { cliConfig } from '../cli.config'; const db = new sqlite3.Database(cliConfig.DB_PATH); const cache = new NodeCache({ stdTTL: 600 }); diff --git a/src/cli/utils/error.util.ts b/src/cli/utils/error.util.ts index f535ad3..c3240d4 100644 --- a/src/cli/utils/error.util.ts +++ b/src/cli/utils/error.util.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; -import logger from '../../shared/utils/logger'; +import logger from '../../shared/utils/logger.util'; export class AppError extends Error { constructor( diff --git a/src/cli/utils/content.util.ts b/src/cli/utils/file_system.util.ts similarity index 86% rename from src/cli/utils/content.util.ts rename to src/cli/utils/file_system.util.ts index 84bed01..0513eda 100644 --- a/src/cli/utils/content.util.ts +++ b/src/cli/utils/file_system.util.ts @@ -1,8 +1,8 @@ import fs from 'fs-extra'; import { handleError } from './error.util'; -import { readDirectory } from '../../shared/utils/file_operations'; -import { cliConfig } from '../config/cli.config'; +import { readDirectory } from '../../shared/utils/file_system.util'; +import { cliConfig } from '../cli.config'; export async function hasPrompts(): Promise { try { diff --git a/src/cli/utils/fragment.util.ts b/src/cli/utils/fragment_operations.util.ts similarity index 95% rename from src/cli/utils/fragment.util.ts rename to src/cli/utils/fragment_operations.util.ts index bf3a262..5b6bdc2 100644 --- a/src/cli/utils/fragment.util.ts +++ b/src/cli/utils/fragment_operations.util.ts @@ -2,8 +2,8 @@ import path from 'path'; import { handleError } from './error.util'; import { ApiResult, Fragment } from '../../shared/types'; -import { readDirectory, readFileContent } from '../../shared/utils/file_operations'; -import { cliConfig } from '../config/cli.config'; +import { readDirectory, readFileContent } from '../../shared/utils/file_system.util'; +import { cliConfig } from '../cli.config'; export async function listFragments(): Promise> { try { diff --git a/src/cli/utils/prompt.cli.util.ts b/src/cli/utils/input_processing.util.ts similarity index 53% rename from src/cli/utils/prompt.cli.util.ts rename to src/cli/utils/input_processing.util.ts index 29e63ab..1b877b5 100644 --- a/src/cli/utils/prompt.cli.util.ts +++ b/src/cli/utils/input_processing.util.ts @@ -1,13 +1,13 @@ +import { EnvVar } from '../../shared/types'; +import logger from '../../shared/utils/logger.util'; +import { FRAGMENT_PREFIX, ENV_PREFIX } from '../cli.constants'; import { readEnvVars } from './env.util'; import { handleError } from './error.util'; -import { viewFragmentContent } from './fragment.util'; -import { EnvVar } from '../../shared/types'; -import logger from '../../shared/utils/logger'; -import { processPromptContent } from '../../shared/utils/prompt_operations'; +import { viewFragmentContent } from './fragment_operations.util'; export async function resolveValue(value: string, envVars: EnvVar[]): Promise { - if (value.startsWith('Fragment: ')) { - const [category, name] = value.split('Fragment: ')[1].split('/'); + if (value.startsWith(FRAGMENT_PREFIX)) { + const [category, name] = value.split(FRAGMENT_PREFIX)[1].split('/'); const fragmentResult = await viewFragmentContent(category, name); if (fragmentResult.success && fragmentResult.data) { @@ -16,12 +16,12 @@ export async function resolveValue(value: string, envVars: EnvVar[]): Promise v.name === envVarName); if (actualEnvVar) { - return await resolveValue(actualEnvVar.value, envVars); // Recursive call to handle nested Env: references + return await resolveValue(actualEnvVar.value, envVars); // Recursive call to handle nested env references } else { logger.warn(`Env var not found: ${envVarName}`); return value; @@ -37,7 +37,7 @@ export async function resolveCliInputs(inputs: Record): Promise< const resolvedInputs: Record = {}; for (const [key, value] of Object.entries(inputs)) { - if (value.startsWith('Fragment: ') || value.startsWith('Env: ')) { + if (value.startsWith(FRAGMENT_PREFIX) || value.startsWith(ENV_PREFIX)) { resolvedInputs[key] = await resolveValue(value, envVars); } else { resolvedInputs[key] = value; @@ -49,24 +49,3 @@ export async function resolveCliInputs(inputs: Record): Promise< throw error; } } - -export async function processCliPromptContent( - messages: { role: string; content: string }[], - inputs: Record = {}, - useStreaming: boolean = true -): Promise { - try { - return processPromptContent(messages, inputs, useStreaming, resolveCliInputs, (event) => { - if (event.type === 'content_block_delta' && event.delta) { - if ('text' in event.delta) { - process.stdout.write(event.delta.text); - } else if ('partial_json' in event.delta) { - process.stdout.write(event.delta.partial_json); - } - } - }); - } catch (error) { - handleError(error, 'processing CLI prompt content'); - throw error; - } -} diff --git a/src/cli/utils/input_resolution.util.ts b/src/cli/utils/input_resolution.util.ts new file mode 100644 index 0000000..bee7b3c --- /dev/null +++ b/src/cli/utils/input_resolution.util.ts @@ -0,0 +1,24 @@ +import { handleError } from './error.util'; +import { resolveCliInputs } from './input_processing.util'; +import { processPromptContent } from '../../shared/utils/prompt_processing.util'; + +export async function processCliPromptContent( + messages: { role: string; content: string }[], + inputs: Record = {}, + useStreaming: boolean = true +): Promise { + try { + return processPromptContent(messages, inputs, useStreaming, resolveCliInputs, (event) => { + if (event.type === 'content_block_delta' && event.delta) { + if ('text' in event.delta) { + process.stdout.write(event.delta.text); + } else if ('partial_json' in event.delta) { + process.stdout.write(event.delta.partial_json); + } + } + }); + } catch (error) { + handleError(error, 'processing CLI prompt content'); + throw error; + } +} diff --git a/src/cli/utils/prompt.util.ts b/src/cli/utils/prompt.util.ts deleted file mode 100644 index aad784e..0000000 --- a/src/cli/utils/prompt.util.ts +++ /dev/null @@ -1,188 +0,0 @@ -import chalk from 'chalk'; - -import { allAsync, getAsync, runAsync } from './database.util'; -import { readEnvVars } from './env.util'; -import { handleError } from './error.util'; -import { getPromptMetadata } from './metadata.util'; -import { ApiResult, Metadata, Prompt, Variable } from '../../shared/types'; -import { processPromptContent } from '../../shared/utils/prompt_operations'; -import { formatSnakeCase, formatTitleCase } from '../../shared/utils/string_formatter'; - -export async function createPrompt(metadata: Metadata, content: string): Promise> { - try { - const result = await runAsync( - 'INSERT INTO prompts (title, content, primary_category, directory, one_line_description, description, content_hash, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', - [ - metadata.title, - content, - metadata.primary_category, - metadata.directory, - metadata.one_line_description, - metadata.description, - metadata.content_hash, - metadata.tags.join(',') - ] - ); - const promptId = result.data?.lastID; - - if (!promptId) { - return { success: false, error: 'Failed to insert prompt' }; - } - - for (const subcategory of metadata.subcategories) { - await runAsync('INSERT INTO subcategories (prompt_id, name) VALUES (?, ?)', [promptId, subcategory]); - } - - for (const variable of metadata.variables) { - await runAsync('INSERT INTO variables (prompt_id, name, role, optional_for_user) VALUES (?, ?, ?, ?)', [ - promptId, - variable.name, - variable.role, - variable.optional_for_user - ]); - } - - for (const fragment of metadata.fragments || []) { - await runAsync('INSERT INTO fragments (prompt_id, category, name, variable) VALUES (?, ?, ?, ?)', [ - promptId, - fragment.category, - fragment.name, - fragment.variable - ]); - } - return { success: true }; - } catch (error) { - handleError(error, 'creating prompt'); - return { success: false, error: 'Failed to create prompt' }; - } -} - -export async function executePrompt( - promptId: string, - userInputs: Record, - useStreaming: boolean -): Promise> { - try { - const promptFilesResult = await getPromptFiles(promptId); - - if (!promptFilesResult.success || !promptFilesResult.data) { - return { success: false, error: promptFilesResult.error || 'Failed to get prompt files' }; - } - - const { promptContent } = promptFilesResult.data; - const result = await processPromptContent([{ role: 'user', content: promptContent }], userInputs, useStreaming); - return { success: true, data: result }; - } catch (error) { - handleError(error, 'executing prompt'); - return { success: false, error: 'Failed to execute prompt' }; - } -} - -export async function listPrompts(): Promise> { - try { - const prompts = await allAsync('SELECT id, title, primary_category FROM prompts'); - return { success: true, data: prompts.data ?? [] }; - } catch (error) { - handleError(error, 'listing prompts'); - return { success: false, error: 'Failed to list prompts' }; - } -} - -export async function getPromptFiles( - promptId: string -): Promise> { - try { - const promptContentResult = await getAsync<{ content: string }>('SELECT content FROM prompts WHERE id = ?', [ - promptId - ]); - - if (!promptContentResult.success || !promptContentResult.data) { - return { success: false, error: 'Prompt not found' }; - } - - const metadataResult = await getPromptMetadata(promptId); - - if (!metadataResult.success || !metadataResult.data) { - return { success: false, error: 'Failed to get prompt metadata' }; - } - return { - success: true, - data: { - promptContent: promptContentResult.data.content, - metadata: metadataResult.data - } - }; - } catch (error) { - handleError(error, 'getting prompt files'); - return { success: false, error: 'Failed to get prompt files' }; - } -} - -export async function viewPromptDetails(details: Prompt & { variables: Variable[] }, isExecute = false): Promise { - // console.clear(); - console.log(chalk.cyan('Prompt:'), details.title); - console.log(`\n${details.description || ''}`); - console.log(chalk.cyan('\nCategory:'), formatTitleCase(details.primary_category)); - - let tags: string[] = []; - - if (typeof details.tags === 'string') { - tags = details.tags.split(',').map((tag) => tag.trim()); - } else if (Array.isArray(details.tags)) { - tags = details.tags; - } - - console.log(chalk.cyan('\nTags:'), tags.length > 0 ? tags.join(', ') : 'No tags'); - console.log(chalk.cyan('\nOptions:'), '([*] Required [ ] Optional)'); - const maxNameLength = Math.max(...details.variables.map((v) => formatSnakeCase(v.name).length)); - - try { - const envVarsResult = await readEnvVars(); - const envVars = envVarsResult.success ? envVarsResult.data || [] : []; - - for (const variable of details.variables) { - const paddedName = formatSnakeCase(variable.name).padEnd(maxNameLength); - const requiredFlag = variable.optional_for_user ? '[ ]' : '[*]'; - const matchingEnvVar = envVars.find((v) => v.name === variable.name); - let status; - - if (variable.value) { - if (variable.value.startsWith('Fragment: ')) { - status = chalk.blue(variable.value); - } else if (variable.value.startsWith('Env: ')) { - const envVarName = variable.value.split('Env: ')[1]; - const envVar = envVars.find((v: { name: string }) => v.name === envVarName); - const envValue = envVar ? envVar.value : 'Not found'; - status = chalk.magenta( - `Env: ${formatSnakeCase(envVarName)} (${envValue.substring(0, 30)}${envValue.length > 30 ? '...' : ''})` - ); - } else { - status = chalk.green( - `Set: ${variable.value.substring(0, 30)}${variable.value.length > 30 ? '...' : ''}` - ); - } - } else { - status = variable.optional_for_user ? chalk.yellow('Not Set') : chalk.red('Not Set (Required)'); - } - - const hint = - !isExecute && - matchingEnvVar && - (!variable.value || (variable.value && !variable.value.startsWith('Env: '))) - ? chalk.magenta('(Env variable available)') - : ''; - console.log(` ${chalk.green(`--${paddedName}`)} ${requiredFlag} ${hint}`); - console.log(` ${variable.role}`); - - if (!isExecute) { - console.log(` ${status}`); - } - } - - if (!isExecute) { - console.log(); - } - } catch (error) { - handleError(error, 'viewing prompt details'); - } -} diff --git a/src/cli/utils/prompt_crud.util.ts b/src/cli/utils/prompt_crud.util.ts new file mode 100644 index 0000000..7450745 --- /dev/null +++ b/src/cli/utils/prompt_crud.util.ts @@ -0,0 +1,93 @@ +import { allAsync, getAsync, runAsync } from './database.util'; +import { handleError } from './error.util'; +import { getPromptMetadata } from './metadata.util'; +import { ApiResult, Metadata, Prompt } from '../../shared/types'; + +export async function createPrompt(metadata: Metadata, content: string): Promise> { + try { + const result = await runAsync( + 'INSERT INTO prompts (title, content, primary_category, directory, one_line_description, description, content_hash, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', + [ + metadata.title, + content, + metadata.primary_category, + metadata.directory, + metadata.one_line_description, + metadata.description, + metadata.content_hash, + metadata.tags.join(',') + ] + ); + const promptId = result.data?.lastID; + + if (!promptId) { + return { success: false, error: 'Failed to insert prompt' }; + } + + for (const subcategory of metadata.subcategories) { + await runAsync('INSERT INTO subcategories (prompt_id, name) VALUES (?, ?)', [promptId, subcategory]); + } + + for (const variable of metadata.variables) { + await runAsync('INSERT INTO variables (prompt_id, name, role, optional_for_user) VALUES (?, ?, ?, ?)', [ + promptId, + variable.name, + variable.role, + variable.optional_for_user + ]); + } + + for (const fragment of metadata.fragments || []) { + await runAsync('INSERT INTO fragments (prompt_id, category, name, variable) VALUES (?, ?, ?, ?)', [ + promptId, + fragment.category, + fragment.name, + fragment.variable + ]); + } + return { success: true }; + } catch (error) { + handleError(error, 'creating prompt'); + return { success: false, error: 'Failed to create prompt' }; + } +} + +export async function listPrompts(): Promise> { + try { + const prompts = await allAsync('SELECT id, title, primary_category FROM prompts'); + return { success: true, data: prompts.data ?? [] }; + } catch (error) { + handleError(error, 'listing prompts'); + return { success: false, error: 'Failed to list prompts' }; + } +} + +export async function getPromptFiles( + promptId: string +): Promise> { + try { + const promptContentResult = await getAsync<{ content: string }>('SELECT content FROM prompts WHERE id = ?', [ + promptId + ]); + + if (!promptContentResult.success || !promptContentResult.data) { + return { success: false, error: 'Prompt not found' }; + } + + const metadataResult = await getPromptMetadata(promptId); + + if (!metadataResult.success || !metadataResult.data) { + return { success: false, error: 'Failed to get prompt metadata' }; + } + return { + success: true, + data: { + promptContent: promptContentResult.data.content, + metadata: metadataResult.data + } + }; + } catch (error) { + handleError(error, 'getting prompt files'); + return { success: false, error: 'Failed to get prompt files' }; + } +} diff --git a/src/cli/utils/prompt_display.util.ts b/src/cli/utils/prompt_display.util.ts new file mode 100644 index 0000000..b2a8f77 --- /dev/null +++ b/src/cli/utils/prompt_display.util.ts @@ -0,0 +1,76 @@ +import chalk from 'chalk'; + +import { Prompt, Variable } from '../../shared/types'; +import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string_formatter.util'; +import { FRAGMENT_PREFIX, ENV_PREFIX } from '../cli.constants'; +import { readEnvVars } from './env.util'; +import { handleError } from './error.util'; + +export async function viewPromptDetails(details: Prompt & { variables: Variable[] }, isExecute = false): Promise { + // console.clear(); + console.log(chalk.cyan('Prompt:'), details.title); + console.log(`\n${details.description || ''}`); + console.log(chalk.cyan('\nCategory:'), formatTitleCase(details.primary_category)); + + let tags: string[] = []; + + if (typeof details.tags === 'string') { + tags = details.tags.split(',').map((tag) => tag.trim()); + } else if (Array.isArray(details.tags)) { + tags = details.tags; + } + + console.log(chalk.cyan('\nTags:'), tags.length > 0 ? tags.join(', ') : 'No tags'); + console.log(chalk.cyan('\nOptions:'), '([*] Required [ ] Optional)'); + const maxNameLength = Math.max(...details.variables.map((v) => formatSnakeCase(v.name).length)); + + try { + const envVarsResult = await readEnvVars(); + const envVars = envVarsResult.success ? envVarsResult.data || [] : []; + + for (const variable of details.variables) { + const paddedName = formatSnakeCase(variable.name).padEnd(maxNameLength); + const requiredFlag = variable.optional_for_user ? '[ ]' : '[*]'; + const matchingEnvVar = envVars.find((v) => v.name === variable.name); + 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 envVar = envVars.find((v: { name: string }) => v.name === envVarName); + const envValue = envVar ? envVar.value : '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 ? '...' : ''}` + ); + } + } else { + status = variable.optional_for_user ? chalk.yellow('Not Set') : chalk.red('Not Set (Required)'); + } + + const hint = + !isExecute && + matchingEnvVar && + (!variable.value || (variable.value && !variable.value.startsWith(ENV_PREFIX))) + ? chalk.magenta('(Env variable available)') + : ''; + console.log(` ${chalk.green(`--${paddedName}`)} ${requiredFlag} ${hint}`); + console.log(` ${variable.role}`); + + if (!isExecute) { + console.log(` ${status}`); + } + } + + if (!isExecute) { + console.log(); + } + } catch (error) { + handleError(error, 'viewing prompt details'); + } +} diff --git a/src/cli/utils/prompt_execution.util.ts b/src/cli/utils/prompt_execution.util.ts new file mode 100644 index 0000000..0cedc75 --- /dev/null +++ b/src/cli/utils/prompt_execution.util.ts @@ -0,0 +1,25 @@ +import { handleError } from './error.util'; +import { getPromptFiles } from './prompt_crud.util'; +import { ApiResult } from '../../shared/types'; +import { processPromptContent } from '../../shared/utils/prompt_processing.util'; + +export async function executePrompt( + promptId: string, + userInputs: Record, + useStreaming: boolean +): Promise> { + try { + const promptFilesResult = await getPromptFiles(promptId); + + if (!promptFilesResult.success || !promptFilesResult.data) { + return { success: false, error: promptFilesResult.error || 'Failed to get prompt files' }; + } + + const { promptContent } = promptFilesResult.data; + const result = await processPromptContent([{ role: 'user', content: promptContent }], userInputs, useStreaming); + return { success: true, data: result }; + } catch (error) { + handleError(error, 'executing prompt'); + return { success: false, error: 'Failed to execute prompt' }; + } +} diff --git a/src/shared/config/index.ts b/src/shared/config/index.ts index cf2518d..516bcd1 100644 --- a/src/shared/config/index.ts +++ b/src/shared/config/index.ts @@ -3,7 +3,7 @@ import * as fs from 'fs'; import { CommonConfig, commonConfig } from './common.config'; import { CONFIG_DIR, CONFIG_FILE, isCliEnvironment } from './config.constants'; import { AppConfig, appConfig } from '../../app/config/app.config'; -import { CliConfig, cliConfig } from '../../cli/config/cli.config'; +import { CliConfig, cliConfig } from '../../cli/cli.config'; export type Config = CommonConfig & (CliConfig | AppConfig); diff --git a/src/shared/utils/anthropic_client.ts b/src/shared/utils/anthropic_client.util.ts similarity index 100% rename from src/shared/utils/anthropic_client.ts rename to src/shared/utils/anthropic_client.util.ts diff --git a/src/shared/utils/file_operations.ts b/src/shared/utils/file_system.util.ts similarity index 100% rename from src/shared/utils/file_operations.ts rename to src/shared/utils/file_system.util.ts diff --git a/src/shared/utils/logger.ts b/src/shared/utils/logger.util.ts similarity index 100% rename from src/shared/utils/logger.ts rename to src/shared/utils/logger.util.ts diff --git a/src/shared/utils/prompt_operations.ts b/src/shared/utils/prompt_processing.util.ts similarity index 99% rename from src/shared/utils/prompt_operations.ts rename to src/shared/utils/prompt_processing.util.ts index 6078626..ae4defa 100644 --- a/src/shared/utils/prompt_operations.ts +++ b/src/shared/utils/prompt_processing.util.ts @@ -1,6 +1,6 @@ import { Message } from '@anthropic-ai/sdk/resources'; -import { sendAnthropicRequestClassic, sendAnthropicRequestStream } from './anthropic_client'; +import { sendAnthropicRequestClassic, sendAnthropicRequestStream } from './anthropic_client.util'; import { handleError } from '../../cli/utils/error.util'; export function replaceVariables(content: string, variables: Record): string { diff --git a/src/shared/utils/string_formatter.ts b/src/shared/utils/string_formatter.util.ts similarity index 100% rename from src/shared/utils/string_formatter.ts rename to src/shared/utils/string_formatter.util.ts