UNPKG

rawi

Version:

Rawi (راوي) is the developer-friendly AI CLI that brings the power of 12 major AI providers directly to your terminal. With seamless shell integration, persistent conversations, and 200+ specialized prompt templates, Rawi transforms your command line into

1 lines 21.6 kB
{"version":3,"sources":["/home/mkabumattar/withrawi/rawi/dist/chunk-65B2KITR.cjs","../src/cli/commands/ask.command.ts"],"names":["readStdin","resolve","data","chunk","createAskCommand","askCommand","Command","chalk","DEFAULT_PROFILE","query","options","dbManager","stdinContent","fileContent","forceFileType","fileTypeKey","SupportedFileType"],"mappings":"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","file":"/home/mkabumattar/withrawi/rawi/dist/chunk-65B2KITR.cjs","sourcesContent":[null,"import chalk from 'chalk';\nimport {Command} from 'commander';\nimport {getCredentials, isConfigured} from '../../core/configs/utils.js';\nimport {DatabaseManager} from '../../core/database/manager.js';\nimport {SupportedFileType} from '../../core/file-readers/interfaces/types.js';\nimport {FileReaderManager} from '../../core/file-readers/managers/file-reader.manager.js';\nimport {processQuery} from '../../core/providers/utils.js';\nimport {DEFAULT_PROFILE} from '../../core/shared/constants.js';\nimport {spinnerManager} from '../../core/shared/spinner.js';\nimport {applyActTemplate} from '../../core/templates/utils.js';\n\nconst readStdin = (): Promise<string> => {\n return new Promise((resolve) => {\n let data = '';\n\n if (process.stdin.isTTY) {\n resolve('');\n return;\n }\n\n process.stdin.setEncoding('utf8');\n\n process.stdin.on('data', (chunk) => {\n data += chunk;\n });\n\n process.stdin.on('end', () => {\n resolve(data.trim());\n });\n });\n};\n\nexport const createAskCommand = (): Command => {\n const askCommand = new Command('ask');\n\n askCommand\n .description(\n [\n chalk.bold('Ask AI a question and get a response.'),\n '',\n chalk.gray('Supports piped input from stdin and file processing.'),\n chalk.gray('Use --act <template> for expert prompt templates.'),\n chalk.gray('See all templates with \"rawi act --list\".'),\n ].join('\\n'),\n )\n .argument(\n '[query]',\n chalk.white(\n 'The question or prompt to send to the AI (can be combined with piped input or file content)',\n ),\n )\n .option(\n '-p, --profile <profile>',\n chalk.white('Profile to use for AI configuration'),\n DEFAULT_PROFILE,\n )\n .option(\n '-s, --session <sessionId>',\n chalk.white('Continue an existing chat session'),\n )\n .option('-n, --new-session', chalk.white('Start a new chat session'))\n .option(\n '--act <template>',\n chalk.white('Use an act template (e.g., ethereum-developer)'),\n )\n .option(\n '-f, --file <path>',\n chalk.white(\n 'Process content from a file (PDF, DOCX, PPTX, XLSX, ODT, ODP, ODS, TXT, etc.)',\n ),\n )\n .option(\n '-F, --files <paths...>',\n chalk.white('Process content from multiple files'),\n )\n .option(\n '-b, --batch <patterns...>',\n chalk.white(\n 'Process files matching glob patterns (e.g., \"src/**/*.js\" \"docs/*.md\")',\n ),\n )\n .option(\n '--parallel',\n chalk.white('Process multiple files in parallel (faster for many files)'),\n )\n .option(\n '--max-concurrency <number>',\n chalk.white(\n 'Maximum number of files to process concurrently (default: 5)',\n ),\n '5',\n )\n .option(\n '--continue-on-error',\n chalk.white('Continue processing other files if one fails'),\n )\n .option(\n '--file-type <type>',\n chalk.white(\n 'Override automatic file type detection (pdf, docx, pptx, xlsx, odt, odp, ods, txt, etc.)',\n ),\n )\n .option(\n '--verbose',\n chalk.white('Show detailed status and debug information'),\n )\n .addHelpText(\n 'after',\n [\n chalk.bold.cyan('\\nExamples:'),\n chalk.gray(\n ' rawi ask \"What is this document about?\" --file report.pdf',\n ),\n chalk.gray(' rawi ask \"Analyze this data\" --file data.xlsx'),\n chalk.gray(' rawi ask \"Review this code\" --file src/app.js'),\n chalk.gray(\n ' rawi ask \"Summarize\" --file document.docx --act technical-writer',\n ),\n chalk.gray(\n ' echo \"Additional context\" | rawi ask \"Analyze\" --file data.json',\n ),\n '',\n chalk.bold.cyan('Batch Processing:'),\n chalk.gray(\n ' rawi ask \"Review these files\" --files file1.js file2.py file3.md',\n ),\n chalk.gray(\n ' rawi ask \"Analyze source code\" --batch \"src/**/*.{js,ts}\"',\n ),\n chalk.gray(\n ' rawi ask \"Review docs\" --batch \"docs/**/*.md\" --parallel',\n ),\n chalk.gray(\n ' rawi ask \"Check configs\" --batch \"**/*.{json,yml,yaml}\" --continue-on-error',\n ),\n '',\n chalk.bold.cyan('See also:'),\n chalk.gray(' rawi act --list'),\n chalk.gray(' rawi provider --list'),\n chalk.gray(' rawi configure --show'),\n chalk.gray(' rawi ask --help'),\n ].join('\\n'),\n )\n .action(async (query: string, options: any) => {\n let dbManager: DatabaseManager | null = null;\n\n try {\n const stdinContent = await readStdin();\n let fileContent = '';\n\n if (options.file || options.files || options.batch) {\n if (options.verbose) {\n console.log(chalk.dim('📄 Processing files...'));\n }\n\n try {\n let forceFileType: SupportedFileType | undefined;\n if (options.fileType) {\n const fileTypeKey = options.fileType.toUpperCase();\n if (fileTypeKey in SupportedFileType) {\n forceFileType =\n SupportedFileType[\n fileTypeKey as keyof typeof SupportedFileType\n ];\n } else {\n console.error(\n chalk.red(`❌ Unsupported file type: ${options.fileType}`),\n );\n console.log(\n chalk.yellow(\n 'Supported types: pdf, docx, pptx, xlsx, odt, odp, ods, txt, js, jsx, ts, tsx, py, java, cpp, c, go, rs, rb, kt, swift, dart, scala, php, json, md, yml, yaml, xml, html, css, sql, sh, bash, env, ini, conf, toml, log, and more',\n ),\n );\n process.exit(1);\n }\n }\n\n const fileReaderManager = new FileReaderManager({\n verbose: options.verbose,\n forceFileType,\n });\n\n let result: any;\n let totalFiles = 0;\n\n if (options.file) {\n result = await fileReaderManager.readFile(options.file);\n if (result.success && result.content) {\n fileContent = result.content.text;\n totalFiles = 1;\n } else {\n console.error(\n chalk.red(`❌ Failed to process file: ${result.error}`),\n );\n process.exit(1);\n }\n } else {\n const maxConcurrency =\n Number.parseInt(options.maxConcurrency) || 5;\n const batchOptions = {\n parallel: options.parallel,\n maxConcurrency,\n continueOnError: options.continueOnError,\n fileType: options.fileType,\n onProgress: options.verbose\n ? (processed: number, total: number, currentFile: string) => {\n console.log(\n chalk.dim(\n `📝 [${processed}/${total}] Processing: ${currentFile}`,\n ),\n );\n }\n : undefined,\n };\n\n let summary: any;\n if (options.files) {\n summary = await fileReaderManager.processMultipleFiles(\n options.files,\n batchOptions,\n );\n } else if (options.batch) {\n summary = await fileReaderManager.processGlobPatterns(\n options.batch,\n batchOptions,\n );\n }\n\n if (summary) {\n const contentParts: string[] = [];\n let successCount = 0;\n\n for (const fileResult of summary.results) {\n if (fileResult.result.success && fileResult.result.content) {\n contentParts.push(\n `=== ${fileResult.filePath} ===\\n${fileResult.result.content.text}\\n`,\n );\n successCount++;\n }\n }\n\n fileContent = contentParts.join('\\n');\n totalFiles = summary.totalFiles;\n\n if (options.verbose) {\n console.log(\n chalk.dim(\n `✅ Successfully processed ${successCount}/${summary.totalFiles} files`,\n ),\n );\n console.log(\n chalk.dim(\n `⏱️ Total time: ${(summary.totalProcessingTime / 1000).toFixed(2)}s`,\n ),\n );\n console.log(\n chalk.dim(\n `📊 Total content length: ${fileContent.length} characters`,\n ),\n );\n\n if (summary.failedFiles > 0) {\n console.log(\n chalk.yellow(\n `⚠️ ${summary.failedFiles} files failed to process:`,\n ),\n );\n for (const error of summary.errors) {\n console.log(\n chalk.yellow(` • ${error.filePath}: ${error.error}`),\n );\n }\n }\n }\n\n if (successCount === 0) {\n console.error(\n chalk.red('❌ No files were processed successfully'),\n );\n process.exit(1);\n }\n }\n }\n\n if (options.verbose && totalFiles === 1) {\n console.log(chalk.dim('✅ Successfully processed file'));\n console.log(\n chalk.dim(\n `📊 Content length: ${fileContent.length} characters`,\n ),\n );\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error occurred';\n console.error(\n chalk.red(`❌ Error processing file: ${errorMessage}`),\n );\n process.exit(1);\n }\n }\n\n let finalQuery: string;\n const contentParts = [query, stdinContent, fileContent].filter(Boolean);\n\n if (contentParts.length === 0) {\n askCommand.help();\n return;\n }\n\n finalQuery = contentParts.join('\\n\\n');\n\n if (options.act) {\n try {\n finalQuery = applyActTemplate(options.act, finalQuery);\n if (options.verbose) {\n console.log(chalk.dim(`🎭 Using act template: ${options.act}`));\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error occurred';\n if (options.verbose) {\n console.error(chalk.red(`❌ ${errorMessage}`));\n console.log(\n chalk.yellow(\n '💡 Use \"rawi act --list\" to see available templates.',\n ),\n );\n }\n return;\n }\n }\n\n const profile = options.profile || DEFAULT_PROFILE;\n\n if (!isConfigured(profile)) {\n if (options.verbose) {\n console.error(\n chalk.red(\n `❌ Profile '${profile}' does not exist or is not properly configured.`,\n ),\n );\n console.error(\n chalk.yellow(\n `Run 'rawi configure -p ${profile}' to set up this profile.`,\n ),\n );\n }\n process.exit(1);\n }\n\n dbManager = new DatabaseManager();\n\n let sessionId: string;\n if (options.newSession) {\n sessionId = await dbManager.createSession(profile);\n if (options.verbose) {\n console.log(chalk.dim(`🆕 Started new session: ${sessionId}`));\n }\n } else if (options.session) {\n sessionId = options.session;\n const session = await dbManager.getSession(sessionId);\n if (!session) {\n if (options.verbose) {\n console.error(chalk.red(`❌ Session '${sessionId}' not found.`));\n }\n process.exit(1);\n }\n if (session.profile !== profile) {\n if (options.verbose) {\n console.error(\n chalk.red(\n `❌ Session '${sessionId}' belongs to profile '${session.profile}', not '${profile}'.`,\n ),\n );\n }\n process.exit(1);\n }\n if (options.verbose) {\n console.log(chalk.dim(`🔄 Continuing session: ${sessionId}`));\n }\n } else {\n sessionId = await dbManager.getCurrentSession(profile);\n if (options.verbose) {\n console.log(chalk.dim(`📝 Using current session: ${sessionId}`));\n }\n }\n\n if (options.verbose) {\n console.log(`Using profile: ${profile}`);\n console.log(\n `Processing query: ${finalQuery.length > 100 ? `${finalQuery.substring(0, 100)}...` : finalQuery}`,\n );\n }\n\n if (options.verbose) {\n spinnerManager.start('validation', 'Validating configuration...');\n }\n\n const credentials = getCredentials(profile);\n\n if (!credentials) {\n if (options.verbose) {\n spinnerManager.fail(\n 'validation',\n 'Configuration validation failed',\n );\n console.error(\n chalk.red(\n `❌ Unable to load credentials for profile '${profile}'.`,\n ),\n );\n }\n process.exit(1);\n }\n\n if (options.verbose) {\n spinnerManager.succeed(\n 'validation',\n 'Configuration validated successfully',\n );\n }\n\n await dbManager.addMessage(\n sessionId,\n 'user',\n finalQuery,\n credentials.provider,\n credentials.model,\n credentials.temperature,\n credentials.maxTokens,\n );\n\n if (options.verbose) {\n spinnerManager.start(\n 'generation',\n `Streaming response using ${credentials.provider} (${credentials.model})...`,\n {color: 'cyan'},\n );\n }\n\n console.log(`${chalk.cyan('Result:')}`);\n\n let response: string;\n let hasStartedStreaming = false;\n\n try {\n response = await processQuery(credentials, finalQuery, {\n streaming: true,\n onChunk: (chunk: string) => {\n if (!hasStartedStreaming) {\n if (options.verbose) {\n spinnerManager.succeed(\n 'generation',\n 'Starting to stream response...',\n );\n }\n hasStartedStreaming = true;\n }\n process.stdout.write(chunk);\n },\n onComplete: () => {\n console.log();\n if (options.verbose) {\n console.log(chalk.dim('✅ Streaming completed'));\n }\n },\n onError: (error: Error) => {\n if (options.verbose) {\n spinnerManager.fail(\n 'generation',\n 'Failed to generate response',\n );\n console.error(chalk.red(`❌ ${error.message}`));\n }\n },\n });\n\n await dbManager.addMessage(\n sessionId,\n 'assistant',\n response,\n credentials.provider,\n credentials.model,\n credentials.temperature,\n credentials.maxTokens,\n );\n\n if (options.verbose) {\n spinnerManager.succeed(\n 'generation',\n 'Response streamed successfully!',\n );\n }\n } catch (error) {\n if (options.verbose) {\n spinnerManager.fail('generation', 'Failed to generate response');\n console.error(\n chalk.red(\n `❌ ${error instanceof Error ? error.message : String(error)}`,\n ),\n );\n }\n response = `Unable to generate response using ${credentials.provider} (${credentials.model}). Error: ${error instanceof Error ? error.message : String(error)}`;\n\n await dbManager.addMessage(\n sessionId,\n 'assistant',\n response,\n credentials.provider,\n credentials.model,\n credentials.temperature,\n credentials.maxTokens,\n {error: true},\n );\n }\n\n if (options.verbose) {\n const session = await dbManager.getSession(sessionId);\n if (session) {\n console.log(\n chalk.dim(\n `\\n💬 Session: ${sessionId} (${session.messageCount} messages)`,\n ),\n );\n }\n }\n } catch (error) {\n if (options.verbose) {\n console.error('Error processing query:', error);\n }\n process.exit(1);\n } finally {\n if (dbManager) {\n dbManager.close();\n }\n }\n });\n\n return askCommand;\n};\n"]}