diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f9a2a9a..1e46467 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "WebSearch", - "WebFetch(domain:ai-sdk.dev)" + "WebFetch(domain:ai-sdk.dev)", + "Bash(cat:*)" ], "deny": [], "ask": [] diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..05dbe02 --- /dev/null +++ b/biome.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.4/schema.json", + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100, + "lineEnding": "lf" + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "always" + } + }, + "files": { + "ignoreUnknown": false, + "includes": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.json", "**/*.jsonc"] + } +} diff --git a/bun.lock b/bun.lock index c263328..b7e4711 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,7 @@ "zod": "^4.1.12", }, "devDependencies": { + "@biomejs/biome": "^2.3.4", "@types/bun": "latest", }, "peerDependencies": { @@ -33,6 +34,24 @@ "@aws-sdk/types": ["@aws-sdk/types@3.921.0", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-mqEG8+vFh5w0ZZC+R8VCOdSk998Hy93pIDuwYpfMAWgYwVhFaIMOLn1fZw0w2DhTs5+ONHHwMJ6uVXtuuqOLQQ=="], + "@biomejs/biome": ["@biomejs/biome@2.3.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.4", "@biomejs/cli-darwin-x64": "2.3.4", "@biomejs/cli-linux-arm64": "2.3.4", "@biomejs/cli-linux-arm64-musl": "2.3.4", "@biomejs/cli-linux-x64": "2.3.4", "@biomejs/cli-linux-x64-musl": "2.3.4", "@biomejs/cli-win32-arm64": "2.3.4", "@biomejs/cli-win32-x64": "2.3.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TU08LXjBHdy0mEY9APtEtZdNQQijXUDSXR7IK1i45wgoPD5R0muK7s61QcFir6FpOj/RP1+YkPx5QJlycXUU3w=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w40GvlNzLaqmuWYiDU6Ys9FNhJiclngKqcGld3iJIiy2bpJ0Q+8n3haiaC81uTPY/NA0d8Q/I3Z9+ajc14102Q=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-3s7TLVtjJ7ni1xADXsS7x7GMUrLBZXg8SemXc3T0XLslzvqKj/dq1xGeBQ+pOWQzng9MaozfacIHdK2UlJ3jGA=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-y7efHyyM2gYmHy/AdWEip+VgTMe9973aP7XYKPzu/j8JxnPHuSUXftzmPhkVw0lfm4ECGbdBdGD6+rLmTgNZaA=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-IruVGQRwMURivWazchiq7gKAqZSFs5so6gi0hJyxk7x6HR+iwZbO2IxNOqyLURBvL06qkIHs7Wffl6Bw30vCbQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gKfjWR/6/dfIxPJCw8REdEowiXCkIpl9jycpNVHux8aX2yhWPLjydOshkDL6Y/82PcQJHn95VCj7J+BRcE5o1Q=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-mzKFFv/w66e4/jCobFmD3kymCqG+FuWE7sVa4Yjqd9v7qt2UhXo67MSZKY9Ih18V2IwPzRKQPCw6KwdZs6AXSA=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-5TJ6JfVez+yyupJ/iGUici2wzKf0RrSAxJhghQXtAEsc67OIpdwSKAQboemILrwKfHDi5s6mu7mX+VTCTUydkw=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-FGCijXecmC4IedQ0esdYNlMpx0Jxgf4zceCaMu6fkjWyjgn50ZQtMiqZZQ0Q/77yqPxvtkgZAvt5uGw0gAAjig=="], + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.4", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.8.1", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ=="], diff --git a/bundle.ts b/bundle.ts new file mode 100644 index 0000000..957be9d --- /dev/null +++ b/bundle.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env bun + +import { $ } from "bun"; +import { version } from "./package.json"; + +// Build binary for current platform (chai-cli) +console.log(`→ Building chai-cli v${version} binary...`); + +try { + await $`bun build ./index.ts --compile --outfile=./dist/chai-cli-v${version}`; + console.log(`✓ Binary created at ./dist/chai-cli-v${version}`); +} catch (error) { + console.error("✗ Build failed:", error); + process.exit(1); +} \ No newline at end of file diff --git a/index.ts b/index.ts index 59288b5..530b67f 100644 --- a/index.ts +++ b/index.ts @@ -1,12 +1,12 @@ -import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"; -import { streamText, tool, stepCountIs } from "ai"; -import type { ModelMessage } from "ai"; -import { z } from "zod"; -import * as readline from "node:readline"; +import * as readline from 'node:readline'; +import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; +import type { ModelMessage } from 'ai'; +import { stepCountIs, streamText, tool } from 'ai'; +import { z } from 'zod'; // Initialize Bedrock with Vercel AI SDK const bedrock = createAmazonBedrock({ - region: Bun.env.AWS_REGION || "us-east-1", + region: Bun.env.AWS_REGION || 'us-east-1', accessKeyId: Bun.env.AWS_ACCESS_KEY_ID, secretAccessKey: Bun.env.AWS_SECRET_ACCESS_KEY, }); @@ -16,6 +16,159 @@ const rl = readline.createInterface({ output: process.stdout, }); +// ANSI color codes +const COLORS = { + red: '\x1b[31m', + green: '\x1b[32m', + reset: '\x1b[0m', +} as const; + +// Visual separators and markers +const UI = { + divider: '─'.repeat(50), + dot: '•', + arrow: '→', + check: '✓', + cross: '✗', + spinner: '⣷⣯⣟⡿⢿⣻⣽⣾', + box: { + top: '┌─', + mid: '├─', + bot: '└─', + vert: '│', + }, + diff: { + removed: '-', + added: '+', + }, +} as const; + +// Logger configuration +const LOG_CONFIG = { + indent: ' ', + prefixes: { + tool: (action: string) => `${UI.box.mid} ${action.toUpperCase()}`, + detail: `${UI.box.vert} `, + block: `${UI.box.vert} `, + output: `${UI.box.bot}`, + success: `${UI.check}`, + error: `${UI.cross}`, + }, + app: { + name: 'CHAI CLI', + version: 'v0.3', + }, +} as const; + +// Helper to output with optional formatting +const output = (message: string, method: 'log' | 'error' = 'log') => console[method](message); + +// Helper to format indented block +const formatBlock = (body: string, indent = LOG_CONFIG.prefixes.block) => + (body.trim() || '(no output)') + .split('\n') + .map((line) => `${indent}${line}`) + .join('\n'); + +// Track execution times +const executionTimes = new Map(); + +// Loading indicator state +let loadingInterval: Timer | undefined; +let loadingMessage = ''; + +const logger = { + startLoading: (message: string) => { + loadingMessage = message; + let spinnerIndex = 0; + const spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + + loadingInterval = setInterval(() => { + process.stdout.write(`\r${spinnerChars[spinnerIndex]} ${loadingMessage}`); + spinnerIndex = (spinnerIndex + 1) % spinnerChars.length; + }, 80); + }, + + stopLoading: () => { + if (loadingInterval) { + clearInterval(loadingInterval); + loadingInterval = undefined; + // Clear the loading line + process.stdout.write('\r' + ' '.repeat(loadingMessage.length + 3) + '\r'); + } + }, + banner: () => + output( + [ + '', + `${UI.box.top} ${LOG_CONFIG.app.name} ${LOG_CONFIG.app.version}`, + UI.divider, + `${UI.dot} Type to chat, Ctrl+C to exit`, + '', + ].join('\n'), + ), + + tool: (action: string, target: string, id?: string) => { + logger.stopLoading(); // Stop any existing loading + output(`\n${LOG_CONFIG.prefixes.tool(action)} ${target}`); + if (id) { + executionTimes.set(id, Date.now()); + logger.startLoading(`${action.toLowerCase()}ing ${target}`); + } + }, + + detail: (message: string) => output(`${LOG_CONFIG.prefixes.detail} ${message}`), + + block: (body: string, collapsed = false) => { + if (collapsed && body.split('\n').length > 10) { + const lines = body.split('\n'); + const preview = lines.slice(0, 3).join('\n'); + const remaining = lines.length - 3; + output( + [ + `${LOG_CONFIG.prefixes.block}${preview}`, + `${LOG_CONFIG.prefixes.detail} ... ${remaining} more lines ...`, + ].join('\n'), + ); + } else { + output(formatBlock(body, LOG_CONFIG.prefixes.block)); + } + }, + + output: (body: string, id?: string) => { + logger.stopLoading(); // Stop loading indicator + let timeStr = ''; + if (id && executionTimes.has(id)) { + const elapsed = Date.now() - executionTimes.get(id)!; + timeStr = ` (${elapsed}ms)`; + executionTimes.delete(id); + } + output(`${LOG_CONFIG.prefixes.output} output${timeStr}:`); + logger.block(body); + }, + + success: (message: string) => output(` ${LOG_CONFIG.prefixes.success} ${message}`), + + error: (message: string) => output(` ${LOG_CONFIG.prefixes.error} ${message}`, 'error'), + + diff: (oldText: string, newText: string) => { + const oldLines = oldText.split('\n'); + const newLines = newText.split('\n'); + + // Show removed lines in red + oldLines.forEach(line => { + output(`${LOG_CONFIG.prefixes.detail} ${COLORS.red}${UI.diff.removed} ${line}${COLORS.reset}`); + }); + + // Show added lines in green + newLines.forEach(line => { + output(`${LOG_CONFIG.prefixes.detail} ${COLORS.green}${UI.diff.added} ${line}${COLORS.reset}`); + }); + }, + + lineBreak: () => output(''), +}; + // Helper function to get user input function getUserInput(prompt: string): Promise { return new Promise((resolve) => { @@ -26,81 +179,241 @@ function getUserInput(prompt: string): Promise { } const handleExit = () => { - console.log("Goodbye! Have a nice day!"); + logger.success('Goodbye! Have a nice day!'); rl.close(); process.exit(0); }; -process.on("SIGINT", handleExit); +process.on('SIGINT', handleExit); + +// Tool descriptions as constants for better readability +const TOOL_DESCRIPTIONS = { + READ: 'Read the contents of a file from the filesystem', + + WRITE: [ + 'Write content to a file on the filesystem.', + 'Use this for creating new files or completely replacing file contents.', + 'For editing existing files, prefer using apply_diff instead.', + ].join(' '), + + APPLY_DIFF: [ + 'Apply a diff to an existing file by replacing specific content.', + 'This is more efficient than rewriting entire files.', + 'Provide the exact text to find (old_string) and what to replace it with (new_string).', + ].join(' '), + + BASH: 'Execute shell commands on the system', +} as const; + +const SYSTEM_PROMPT = ` + You are CHAI: CloudHedge's Agentic Intelligence. You are help users with their coding tasks. + Send output which is easy to render in terminal. + + Communication Style: + - Avoid grammar for concision. + + Context: + - You are a coding assistant that can help with coding tasks. + - You are currently in the following directory: ${process.cwd()} + - You are using the following tools: ${Object.keys(TOOL_DESCRIPTIONS).join(', ')} + - Start by exploring the file system and understanding the context of the user's request. + + TOOL USAGE GUIDELINES: + + 1. **File Reading (read tool)**: + - Always read a file before editing it to understand its current contents + - Use this to gather context before making changes + + 2. **File Editing (apply_diff tool)** - PREFERRED for modifications: + - Use this for editing existing files - it's much more efficient than rewriting + - Provide the exact old_string to find (including whitespace and indentation) + - Provide the new_string to replace it with + - Set replace_all: true to replace all occurrences, or false for just the first + - This is faster and uses fewer tokens than the write tool + + 3. **File Creation (write tool)**: + - Only use this for creating NEW files + - Or when you need to completely rewrite an entire file + - For edits, prefer apply_diff instead + + 4. **Shell Commands (bash tool)**: + - Use for running tests, builds, git commands, package managers, etc. + - Provide clear descriptions of what each command does + - Set appropriate timeouts for long-running commands + + BEST PRACTICES: + - Always read before you edit + - Use apply_diff for targeted changes to save time and tokens + - Be precise with old_string in apply_diff - match whitespace exactly + - Explain your reasoning and steps clearly + - Handle errors gracefully and inform the user +`.trim(); // Define tools const tools = { read: tool({ - description: "Read the contents of a file from the filesystem", + description: TOOL_DESCRIPTIONS.READ, inputSchema: z.object({ - filePath: z.string().describe("The path to the file to read"), + filePath: z.string().describe('The path to the file to read'), }), execute: async ({ filePath }) => { + const toolId = `read-${Date.now()}`; try { - console.log(`\n\n [-] READ: ${filePath}\n`); + logger.tool('READ', filePath, toolId); const file = Bun.file(filePath); const exists = await file.exists(); if (!exists) { - return `Error: File not found: ${filePath}`; + const error = `Error: File not found: ${filePath}`; + logger.output(error, toolId); + return error; } const content = await file.text(); + const lines = content.split('\n').length; + logger.output(`${lines} lines read`, toolId); return content; } catch (error) { - if (error instanceof Error) { - return `Error reading file: ${error.message}`; - } - return "Unknown error reading file"; + const errorMsg = + error instanceof Error + ? `Error reading file: ${error.message}` + : 'Unknown error reading file'; + logger.output(errorMsg, toolId); + return errorMsg; } }, }), write: tool({ - description: "Write content to a file on the filesystem", + description: TOOL_DESCRIPTIONS.WRITE, inputSchema: z.object({ - filePath: z.string().describe("The path to the file to write"), - content: z.string().describe("The content to write to the file"), + filePath: z.string().describe('The path to the file to write'), + content: z.string().describe('The content to write to the file'), }), execute: async ({ filePath, content }) => { + const toolId = `write-${Date.now()}`; try { - console.log(`\n\n [-] WRITE: ${filePath}\n`); + logger.tool('WRITE', filePath, toolId); const file = Bun.file(filePath); const exists = await file.exists(); if (exists) { - console.log(` └─ Overwriting existing file\n`); + logger.detail('Overwriting existing file'); } await Bun.write(filePath, content); const bytesWritten = new TextEncoder().encode(content).length; - return `File written successfully to: ${filePath} (${bytesWritten} bytes)`; + const result = `File written successfully (${bytesWritten} bytes)`; + logger.output(result, toolId); + return `${result} to: ${filePath}`; } catch (error) { - if (error instanceof Error) { - return `Error writing file: ${error.message}`; + const errorMsg = + error instanceof Error + ? `Error writing file: ${error.message}` + : 'Unknown error writing file'; + logger.output(errorMsg, toolId); + return errorMsg; + } + }, + }), + + apply_diff: tool({ + description: TOOL_DESCRIPTIONS.APPLY_DIFF, + inputSchema: z.object({ + filePath: z.string().describe('The path to the file to edit'), + old_string: z + .string() + .describe('The exact text to find and replace (must match exactly including whitespace)'), + new_string: z.string().describe('The new text to replace the old text with'), + replace_all: z + .boolean() + .optional() + .default(false) + .describe( + 'If true, replace all occurrences. If false, only replace the first occurrence (default: false)', + ), + }), + execute: async ({ filePath, old_string, new_string, replace_all }) => { + const toolId = `diff-${Date.now()}`; + try { + logger.tool('APPLY_DIFF', filePath, toolId); + + const file = Bun.file(filePath); + const exists = await file.exists(); + + if (!exists) { + const error = `Error: File not found: ${filePath}. Use the write tool to create new files.`; + logger.output(error, toolId); + return error; } - return "Unknown error writing file"; + + // Read current content + const content = await file.text(); + + // Check if old_string exists in the file + if (!content.includes(old_string)) { + const error = `Error: Could not find the specified text in ${filePath}`; + logger.output(error, toolId); + return `${error}. Make sure old_string matches exactly (including whitespace and indentation).`; + } + + // Show the diff before applying changes + logger.diff(old_string, new_string); + + // Apply the replacement + let newContent: string; + let occurrences = 1; + if (replace_all) { + newContent = content.replaceAll(old_string, new_string); + occurrences = content.split(old_string).length - 1; + logger.detail(`Replacing all ${occurrences} occurrence(s)`); + } else { + newContent = content.replace(old_string, new_string); + logger.detail('Replacing first occurrence'); + } + + // Write the updated content + await Bun.write(filePath, newContent); + + const oldSize = new TextEncoder().encode(content).length; + const newSize = new TextEncoder().encode(newContent).length; + const diff = newSize - oldSize; + const diffStr = diff >= 0 ? `+${diff}` : `${diff}`; + + const result = `Diff applied (${occurrences} changes, ${diffStr} bytes)`; + logger.output(result, toolId); + return `${result} to: ${filePath}`; + } catch (error) { + const errorMsg = + error instanceof Error + ? `Error applying diff: ${error.message}` + : 'Unknown error applying diff'; + logger.output(errorMsg, toolId); + return errorMsg; } }, }), bash: tool({ - description: "Execute shell commands on the system", + description: TOOL_DESCRIPTIONS.BASH, inputSchema: z.object({ - command: z.string().describe("The shell command to execute"), - description: z.string().optional().describe("Optional description of what this command does"), - workingDirectory: z.string().optional().describe("Working directory for command execution (defaults to current directory)"), - timeout: z.number().optional().default(30000).describe("Timeout in milliseconds (default: 30000)"), + command: z.string().describe('The shell command to execute'), + description: z.string().optional().describe('Optional description of what this command does'), + workingDirectory: z + .string() + .optional() + .describe('Working directory for command execution (defaults to current directory)'), + timeout: z + .number() + .optional() + .default(30000) + .describe('Timeout in milliseconds (default: 30000)'), }), execute: async ({ command, description, workingDirectory, timeout }) => { + const toolId = `bash-${Date.now()}`; try { // Validate working directory if provided const cwd = workingDirectory || process.cwd(); @@ -108,6 +421,8 @@ const tools = { const dirExists = await Bun.file(workingDirectory).exists(); if (!dirExists) { const errorMsg = `Error: Working directory does not exist: ${workingDirectory}`; + logger.tool('BASH', command, toolId); + logger.output(errorMsg, toolId); return { title: command, metadata: { @@ -121,19 +436,19 @@ const tools = { } // Log execution details - console.log(`\n\n [-] BASH: ${command}\n`); + logger.tool('BASH', command, toolId); if (description) { - console.log(` ├─ Description: ${description}`); + logger.detail(`${description}`); } if (workingDirectory) { - console.log(` ├─ Working dir: ${workingDirectory}`); + logger.detail(`working dir: ${workingDirectory}`); } // Execute command using Bun.spawn - const proc = Bun.spawn(["/bin/sh", "-c", command], { + const proc = Bun.spawn(['/bin/sh', '-c', command], { cwd: cwd, - stdout: "pipe", - stderr: "pipe", + stdout: 'pipe', + stderr: 'pipe', env: process.env, }); @@ -161,25 +476,26 @@ const tools = { if (timeoutId) clearTimeout(timeoutId); // Combine stdout and stderr - const output = stdout + (stderr ? `\nstderr: ${stderr}` : ""); + const output = stdout + (stderr ? `\nstderr: ${stderr}` : ''); - console.log(`\n\n [+] ${output}\n`); + logger.output(output || '(no output)', toolId); return { title: command, metadata: { - output: output || "(no output)", + output: output || '(no output)', exit: exitCode, description: description, }, - output: output || "(no output)", + output: output || '(no output)', }; } catch (timeoutError) { // Clear timeout on error if (timeoutId) clearTimeout(timeoutId); - if (timeoutError instanceof Error && timeoutError.message.includes("timed out")) { + if (timeoutError instanceof Error && timeoutError.message.includes('timed out')) { const errorMsg = timeoutError.message; + logger.output(errorMsg, toolId); return { title: command, metadata: { @@ -193,10 +509,12 @@ const tools = { throw timeoutError; } } catch (error) { - const errorMsg = error instanceof Error - ? `Error executing command: ${error.message}` - : "Unknown error executing command"; + const errorMsg = + error instanceof Error + ? `Error executing command: ${error.message}` + : 'Unknown error executing command'; + logger.output(errorMsg, toolId); return { title: command, metadata: { @@ -212,36 +530,32 @@ const tools = { }; async function main() { - console.log("\n"+"Claude CLI v0.1"); - console.log("------------------------------------------------"); - console.log("Type your questions and press Enter to chat."); - console.log("Press Ctrl+C to leave the chat."); - console.log(""); + logger.banner(); const conversationHistory: ModelMessage[] = []; while (true) { try { - const userMessage = await getUserInput("> "); + const userMessage = await getUserInput('> '); // Skip when user presses Enter - if (userMessage.trim() === "") { + if (userMessage.trim() === '') { continue; } // Store user message in history conversationHistory.push({ - role: "user", + role: 'user', content: userMessage, }); // Stream response from Claude via Bedrock with multi-step tool calling - console.log(""); - process.stdout.write("✶ "); + process.stdout.write('\n* '); - let fullResponse = ""; + let _fullResponse = ''; const result = streamText({ - model: bedrock("anthropic.claude-3-5-sonnet-20240620-v1:0"), + model: bedrock('anthropic.claude-3-5-sonnet-20240620-v1:0'), + system: SYSTEM_PROMPT, messages: conversationHistory, tools, stopWhen: stepCountIs(25), // Allow up to 25 steps (tool calls + responses) @@ -250,12 +564,16 @@ async function main() { // Stream ALL text from all steps (including after tool execution) for await (const textPart of result.fullStream) { if (textPart.type === 'text-delta') { - process.stdout.write(textPart.text); - fullResponse += textPart.text; + // Add asterisk prefix for new lines in CHAI's response + const text = textPart.text; + + process.stdout.write(text); + _fullResponse += textPart.text; } } - console.log("\n"); + // need to have a line break after the response + logger.lineBreak(); // Wait for the complete response with all steps const response = await result.response; @@ -264,12 +582,12 @@ async function main() { conversationHistory.push(...response.messages); } catch (error) { if (error instanceof Error) { - console.error("[Error]:", error.message); + logger.error(error.message); } else { - console.error("[Error]: An unknown error occurred"); + logger.error('An unknown error occurred'); } } } } -main(); \ No newline at end of file +main(); diff --git a/package.json b/package.json index fd49afa..e95cb0b 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,26 @@ { - "name": "coding-agent", - "scripts": { - "dev": "bun run index.ts" - }, - "type": "module", - "private": true, - "dependencies": { - "@ai-sdk/amazon-bedrock": "^3.0.49", - "ai": "^5.0.83", - "zod": "^4.1.12" - }, - "devDependencies": { - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5" - } -} \ No newline at end of file + "name": "chai-cli", + "version": "0.3.0", + "scripts": { + "dev": "bun run index.ts", + "lint": "biome lint .", + "format": "biome format --write .", + "check": "biome check .", + "check:fix": "biome check --write .", + "bundle": "bun run bundle.ts" + }, + "type": "module", + "private": true, + "dependencies": { + "@ai-sdk/amazon-bedrock": "^3.0.49", + "ai": "^5.0.83", + "zod": "^4.1.12" + }, + "devDependencies": { + "@biomejs/biome": "^2.3.4", + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +}