@alia-codea/cli 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,309 +1,21 @@
1
- import * as readline from 'readline';
2
- import chalk from 'chalk';
3
- import { config, createSession, saveSession } from '../utils/config.js';
4
- import { streamChat } from '../utils/api.js';
5
- import { executeTool, formatToolCall } from '../tools/executor.js';
6
- import {
7
- printBanner,
8
- printTips,
9
- printPrompt,
10
- printToolExecution,
11
- printToolResult,
12
- showThinkingStatus,
13
- hideThinkingStatus,
14
- printStatusBar,
15
- printAssistantPrefix,
16
- printError,
17
- printInfo
18
- } from '../utils/ui.js';
19
- import { buildSystemMessage, getCodebaseContext } from '../utils/context.js';
20
-
21
- interface Message {
22
- role: 'user' | 'assistant' | 'system' | 'tool';
23
- content: string;
24
- tool_calls?: any[];
25
- tool_call_id?: string;
26
- }
1
+ import React from 'react';
2
+ import { render } from 'ink';
3
+ import { App, AppOptions } from '../app.js';
4
+ import { ApprovalMode } from '../utils/approval.js';
27
5
 
28
6
  interface ReplOptions {
29
7
  model: string;
30
8
  context: boolean;
9
+ approvalMode?: string;
31
10
  }
32
11
 
33
12
  export async function startRepl(options: ReplOptions): Promise<void> {
34
- const session = createSession();
35
- const messages: Message[] = [];
36
- let isProcessing = false;
37
- let contextUsed = 0;
38
- const maxContext = 128000;
39
-
40
- // Print welcome UI
41
- printTips();
42
-
43
- // Get initial codebase context
44
- let codebaseContext = '';
45
- if (options.context !== false) {
46
- printInfo('Analyzing codebase...');
47
- codebaseContext = await getCodebaseContext();
48
- if (codebaseContext) {
49
- printInfo(`Loaded context from ${codebaseContext.split('\n').length} files`);
50
- }
51
- }
52
-
53
- // Setup readline
54
- const rl = readline.createInterface({
55
- input: process.stdin,
56
- output: process.stdout,
57
- terminal: true
58
- });
59
-
60
- // Handle Ctrl+C
61
- rl.on('SIGINT', () => {
62
- if (isProcessing) {
63
- isProcessing = false;
64
- hideThinkingStatus();
65
- console.log(chalk.yellow('\nCancelled.'));
66
- printPrompt();
67
- } else {
68
- console.log(chalk.gray('\nGoodbye!'));
69
- process.exit(0);
70
- }
71
- });
72
-
73
- const askQuestion = (): void => {
74
- printPrompt();
75
- rl.question('', async (input) => {
76
- const trimmed = input.trim();
77
-
78
- if (!trimmed) {
79
- askQuestion();
80
- return;
81
- }
82
-
83
- // Handle slash commands
84
- if (trimmed.startsWith('/')) {
85
- await handleSlashCommand(trimmed, messages, session, options);
86
- askQuestion();
87
- return;
88
- }
89
-
90
- // Add user message
91
- messages.push({ role: 'user', content: trimmed });
92
- isProcessing = true;
93
-
94
- // Build system message
95
- const systemMessage = buildSystemMessage(options.model, codebaseContext);
96
-
97
- // Process conversation with tool loop
98
- await processConversation(messages, systemMessage, options.model, () => isProcessing);
99
-
100
- isProcessing = false;
101
-
102
- // Update session
103
- session.messages = messages.map(m => ({ role: m.role, content: m.content }));
104
- session.title = messages[0]?.content.slice(0, 50) || 'New conversation';
105
- session.updatedAt = Date.now();
106
- saveSession(session);
107
-
108
- // Update context usage estimate
109
- contextUsed = Math.min(95, Math.floor(messages.reduce((acc, m) => acc + m.content.length, 0) / maxContext * 100));
110
-
111
- // Print status bar
112
- printStatusBar(process.cwd(), getModelDisplayName(options.model), 100 - contextUsed);
113
-
114
- askQuestion();
115
- });
13
+ const appOptions: AppOptions = {
14
+ model: options.model,
15
+ approvalMode: (options.approvalMode as ApprovalMode) || 'suggest',
16
+ context: options.context,
116
17
  };
117
18
 
118
- askQuestion();
119
- }
120
-
121
- async function processConversation(
122
- messages: Message[],
123
- systemMessage: string,
124
- model: string,
125
- isActive: () => boolean
126
- ): Promise<void> {
127
- while (isActive()) {
128
- console.log();
129
- printAssistantPrefix();
130
-
131
- let fullContent = '';
132
- let toolCalls: any[] | undefined;
133
-
134
- showThinkingStatus('Thinking');
135
-
136
- try {
137
- await streamChat(messages, systemMessage, model, {
138
- onContent: (content) => {
139
- if (!isActive()) return;
140
- hideThinkingStatus();
141
- process.stdout.write(content);
142
- fullContent += content;
143
- },
144
- onToolCall: (tc) => {
145
- // Tool calls are accumulated
146
- },
147
- onDone: (content, tcs) => {
148
- hideThinkingStatus();
149
- toolCalls = tcs;
150
- },
151
- onError: (error) => {
152
- hideThinkingStatus();
153
- printError(error.message);
154
- }
155
- });
156
- } catch (error: any) {
157
- hideThinkingStatus();
158
- printError(error.message);
159
- break;
160
- }
161
-
162
- if (!isActive()) break;
163
-
164
- // Handle tool calls
165
- if (toolCalls && toolCalls.length > 0) {
166
- // Add assistant message with tool calls
167
- messages.push({
168
- role: 'assistant',
169
- content: fullContent,
170
- tool_calls: toolCalls
171
- });
172
-
173
- if (fullContent) {
174
- console.log(); // New line after content
175
- }
176
-
177
- // Execute each tool
178
- for (const tc of toolCalls) {
179
- if (!isActive()) break;
180
-
181
- const args = JSON.parse(tc.function.arguments);
182
- printToolExecution(tc.function.name, formatToolArgs(tc.function.name, args));
183
-
184
- showThinkingStatus(`Executing ${tc.function.name}`);
185
- const result = await executeTool(tc.function.name, args);
186
- hideThinkingStatus();
187
-
188
- printToolResult(result.success, result.result);
189
-
190
- // Add tool result
191
- messages.push({
192
- role: 'tool',
193
- tool_call_id: tc.id,
194
- content: result.result
195
- });
196
- }
197
-
198
- // Continue loop for next response
199
- continue;
200
- } else {
201
- // No tool calls, conversation turn complete
202
- if (fullContent) {
203
- messages.push({ role: 'assistant', content: fullContent });
204
- console.log(); // New line after response
205
- }
206
- break;
207
- }
208
- }
209
- }
210
-
211
- async function handleSlashCommand(
212
- command: string,
213
- messages: Message[],
214
- session: any,
215
- options: ReplOptions
216
- ): Promise<void> {
217
- const [cmd, ...args] = command.slice(1).split(' ');
218
-
219
- switch (cmd.toLowerCase()) {
220
- case 'help':
221
- console.log();
222
- console.log(chalk.bold('Available commands:'));
223
- console.log(chalk.cyan(' /help') + chalk.gray(' - Show this help'));
224
- console.log(chalk.cyan(' /clear') + chalk.gray(' - Clear conversation'));
225
- console.log(chalk.cyan(' /model') + chalk.gray(' - Switch model'));
226
- console.log(chalk.cyan(' /context') + chalk.gray(' - Show current context'));
227
- console.log(chalk.cyan(' /save') + chalk.gray(' - Save conversation'));
228
- console.log(chalk.cyan(' /exit') + chalk.gray(' - Exit Codea'));
229
- console.log();
230
- break;
231
-
232
- case 'clear':
233
- messages.length = 0;
234
- console.log(chalk.green('Conversation cleared.'));
235
- break;
236
-
237
- case 'model':
238
- const modelArg = args[0];
239
- if (modelArg) {
240
- options.model = modelArg.startsWith('alia-') ? modelArg : `alia-v1-${modelArg}`;
241
- console.log(chalk.green(`Model switched to ${options.model}`));
242
- } else {
243
- console.log(chalk.gray('Current model: ') + chalk.cyan(options.model));
244
- try {
245
- const { fetchModels } = await import('../utils/api.js');
246
- const apiModels = await fetchModels();
247
- if (apiModels.length > 0) {
248
- console.log(chalk.gray('Available models:'));
249
- for (const m of apiModels) {
250
- console.log(chalk.gray(' ') + chalk.cyan(m.id) + chalk.gray(` - ${m.name}`));
251
- }
252
- } else {
253
- console.log(chalk.gray('Available: codea, codea-pro, codea-thinking'));
254
- }
255
- } catch {
256
- console.log(chalk.gray('Available: codea, codea-pro, codea-thinking'));
257
- }
258
- }
259
- break;
260
-
261
- case 'context':
262
- console.log(chalk.gray(`Messages in context: ${messages.length}`));
263
- console.log(chalk.gray(`Working directory: ${process.cwd()}`));
264
- break;
265
-
266
- case 'save':
267
- session.messages = messages.map(m => ({ role: m.role, content: m.content }));
268
- session.updatedAt = Date.now();
269
- saveSession(session);
270
- console.log(chalk.green('Conversation saved.'));
271
- break;
272
-
273
- case 'exit':
274
- case 'quit':
275
- console.log(chalk.gray('Goodbye!'));
276
- process.exit(0);
277
- break;
278
-
279
- default:
280
- console.log(chalk.yellow(`Unknown command: /${cmd}`));
281
- console.log(chalk.gray('Type /help for available commands.'));
282
- }
283
- }
284
-
285
- function formatToolArgs(name: string, args: Record<string, any>): string {
286
- switch (name) {
287
- case 'read_file':
288
- case 'write_file':
289
- case 'edit_file':
290
- return args.path || '';
291
- case 'list_files':
292
- return args.path || '.';
293
- case 'search_files':
294
- return `"${args.pattern}" in ${args.path || '.'}`;
295
- case 'run_command':
296
- return args.command || '';
297
- default:
298
- return JSON.stringify(args).slice(0, 50);
299
- }
300
- }
301
-
302
- function getModelDisplayName(model: string): string {
303
- const names: Record<string, string> = {
304
- 'alia-v1-codea': 'codea',
305
- 'alia-v1-pro': 'codea-pro',
306
- 'alia-v1-thinking': 'codea-thinking'
307
- };
308
- return names[model] || model;
19
+ const { waitUntilExit } = render(React.createElement(App, { options: appOptions }));
20
+ await waitUntilExit();
309
21
  }
@@ -1,33 +1,28 @@
1
1
  import chalk from 'chalk';
2
- import { config } from '../utils/config.js';
3
- import { streamChat } from '../utils/api.js';
4
- import { executeTool, formatToolCall } from '../tools/executor.js';
5
- import { buildSystemMessage, getCodebaseContext } from '../utils/context.js';
6
- import {
7
- printToolExecution,
8
- printToolResult,
9
- showThinkingStatus,
10
- hideThinkingStatus,
11
- printAssistantPrefix,
12
- printError,
13
- printInfo
14
- } from '../utils/ui.js';
15
-
16
- interface Message {
17
- role: 'user' | 'assistant' | 'system' | 'tool';
18
- content: string;
19
- tool_calls?: any[];
20
- tool_call_id?: string;
21
- }
2
+ import { buildSystemMessage, getCodebaseContext, loadProjectInstructions } from '../utils/context.js';
3
+ import { processConversation, Message, ToolExecution } from '../utils/conversation.js';
4
+ import { ApprovalMode } from '../utils/approval.js';
5
+ import * as readline from 'readline';
22
6
 
23
7
  interface RunOptions {
24
8
  model: string;
25
9
  yes: boolean;
26
10
  context: boolean;
11
+ approvalMode?: string;
12
+ quiet?: boolean;
13
+ json?: boolean;
14
+ }
15
+
16
+ interface JsonOutput {
17
+ model: string;
18
+ prompt: string;
19
+ response: string;
20
+ tool_calls: Array<{ tool: string; args: Record<string, any>; result: string; success: boolean }>;
27
21
  }
28
22
 
29
23
  export async function runPrompt(prompt: string, options: RunOptions): Promise<void> {
30
24
  const messages: Message[] = [];
25
+ const toolResults: JsonOutput['tool_calls'] = [];
31
26
 
32
27
  // Get codebase context
33
28
  let codebaseContext = '';
@@ -35,136 +30,118 @@ export async function runPrompt(prompt: string, options: RunOptions): Promise<vo
35
30
  codebaseContext = await getCodebaseContext();
36
31
  }
37
32
 
38
- // Add user message
39
- messages.push({ role: 'user', content: prompt });
40
-
41
- // Build system message
42
- const systemMessage = buildSystemMessage(options.model, codebaseContext);
43
-
44
- // Process with tool loop
45
- await processConversation(messages, systemMessage, options.model, options.yes);
46
- }
47
-
48
- async function processConversation(
49
- messages: Message[],
50
- systemMessage: string,
51
- model: string,
52
- autoApprove: boolean
53
- ): Promise<void> {
54
- let continueProcessing = true;
55
-
56
- while (continueProcessing) {
57
- printAssistantPrefix();
58
-
59
- let fullContent = '';
60
- let toolCalls: any[] | undefined;
61
-
62
- showThinkingStatus('Thinking');
63
-
64
- try {
65
- await streamChat(messages, systemMessage, model, {
66
- onContent: (content) => {
67
- hideThinkingStatus();
68
- process.stdout.write(content);
69
- fullContent += content;
70
- },
71
- onToolCall: () => {},
72
- onDone: (content, tcs) => {
73
- hideThinkingStatus();
74
- toolCalls = tcs;
75
- },
76
- onError: (error) => {
77
- hideThinkingStatus();
78
- printError(error.message);
79
- continueProcessing = false;
80
- }
81
- });
82
- } catch (error: any) {
83
- hideThinkingStatus();
84
- printError(error.message);
85
- break;
86
- }
87
-
88
- // Handle tool calls
89
- if (toolCalls && toolCalls.length > 0) {
90
- messages.push({
91
- role: 'assistant',
92
- content: fullContent,
93
- tool_calls: toolCalls
94
- });
95
-
96
- if (fullContent) console.log();
33
+ const instructions = await loadProjectInstructions();
97
34
 
98
- for (const tc of toolCalls) {
99
- const args = JSON.parse(tc.function.arguments);
100
-
101
- // Check if we need approval for file writes
102
- const isDestructive = ['write_file', 'edit_file', 'run_command'].includes(tc.function.name);
103
-
104
- if (isDestructive && !autoApprove) {
105
- console.log();
106
- console.log(chalk.yellow('⚠ ') + chalk.bold('Approval required:'));
107
- console.log(formatToolCall(tc.function.name, args));
108
- console.log();
35
+ messages.push({ role: 'user', content: prompt });
109
36
 
110
- const approved = await askApproval();
111
- if (!approved) {
112
- messages.push({
113
- role: 'tool',
114
- tool_call_id: tc.id,
115
- content: 'User declined this action.'
37
+ const systemMessage = buildSystemMessage(options.model, codebaseContext, instructions);
38
+
39
+ const approvalMode: ApprovalMode = options.yes
40
+ ? 'full-auto'
41
+ : (options.approvalMode as ApprovalMode) || 'suggest';
42
+
43
+ let fullResponse = '';
44
+
45
+ await processConversation({
46
+ messages,
47
+ systemMessage,
48
+ model: options.model,
49
+ approvalMode,
50
+ isActive: () => true,
51
+ requestApproval: async (execution) => {
52
+ if (options.quiet || options.json) return false;
53
+ return askApproval(execution);
54
+ },
55
+ onEvent: (event) => {
56
+ switch (event.type) {
57
+ case 'thinking':
58
+ if (!options.quiet && !options.json) {
59
+ process.stdout.write(chalk.magenta('✦ '));
60
+ }
61
+ break;
62
+ case 'content':
63
+ fullResponse += event.text;
64
+ if (!options.quiet && !options.json) {
65
+ process.stdout.write(event.text);
66
+ }
67
+ break;
68
+ case 'tool_start':
69
+ if (!options.quiet && !options.json) {
70
+ console.log();
71
+ console.log(chalk.cyan(' → ') + chalk.bold(event.execution.tool) + ' ' + chalk.gray(formatArgs(event.execution)));
72
+ }
73
+ break;
74
+ case 'tool_done':
75
+ if (event.execution.result !== undefined) {
76
+ toolResults.push({
77
+ tool: event.execution.tool,
78
+ args: event.execution.args,
79
+ result: event.execution.result,
80
+ success: event.execution.success ?? false,
116
81
  });
117
- continue;
118
82
  }
119
- }
120
-
121
- printToolExecution(tc.function.name, formatToolArgs(tc.function.name, args));
122
-
123
- showThinkingStatus(`Executing ${tc.function.name}`);
124
- const result = await executeTool(tc.function.name, args);
125
- hideThinkingStatus();
126
-
127
- printToolResult(result.success, result.result);
128
-
129
- messages.push({
130
- role: 'tool',
131
- tool_call_id: tc.id,
132
- content: result.result
133
- });
83
+ if (!options.quiet && !options.json) {
84
+ const icon = event.execution.success ? chalk.green(' ✓') : chalk.red(' ✗');
85
+ const preview = (event.execution.result || '').slice(0, 100).replace(/\n/g, ' ');
86
+ console.log(`${icon} ${chalk.gray(preview)}`);
87
+ }
88
+ break;
89
+ case 'done':
90
+ if (!options.quiet && !options.json) {
91
+ console.log();
92
+ }
93
+ break;
94
+ case 'error':
95
+ if (!options.json) {
96
+ console.error(chalk.red('Error: ') + event.message);
97
+ }
98
+ break;
134
99
  }
100
+ },
101
+ });
135
102
 
136
- continue;
137
- } else {
138
- if (fullContent) {
139
- messages.push({ role: 'assistant', content: fullContent });
140
- console.log();
141
- }
142
- break;
103
+ if (options.json) {
104
+ const output: JsonOutput = {
105
+ model: options.model,
106
+ prompt,
107
+ response: fullResponse,
108
+ tool_calls: toolResults,
109
+ };
110
+ console.log(JSON.stringify(output, null, 2));
111
+ } else if (options.quiet) {
112
+ if (fullResponse) {
113
+ console.log(fullResponse);
143
114
  }
144
115
  }
145
116
  }
146
117
 
147
- async function askApproval(): Promise<boolean> {
148
- const readline = await import('readline');
118
+ async function askApproval(execution: ToolExecution): Promise<boolean> {
149
119
  const rl = readline.createInterface({
150
120
  input: process.stdin,
151
- output: process.stdout
121
+ output: process.stdout,
152
122
  });
153
123
 
124
+ const desc = formatArgs(execution);
125
+ console.log();
126
+ console.log(chalk.yellow('⚠ ') + chalk.bold(execution.tool) + ' ' + desc);
127
+
154
128
  return new Promise((resolve) => {
155
- rl.question(chalk.cyan('Allow? [y/N] '), (answer) => {
129
+ rl.question(chalk.cyan(' Allow? [y/N] '), (answer) => {
156
130
  rl.close();
157
131
  resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
158
132
  });
159
133
  });
160
134
  }
161
135
 
162
- function formatToolArgs(name: string, args: Record<string, any>): string {
163
- switch (name) {
136
+ function formatArgs(execution: ToolExecution): string {
137
+ const { tool, args } = execution;
138
+ switch (tool) {
164
139
  case 'read_file':
165
140
  case 'write_file':
166
141
  case 'edit_file':
167
142
  return args.path || '';
143
+ case 'apply_patch':
144
+ return 'applying patch...';
168
145
  case 'list_files':
169
146
  return args.path || '.';
170
147
  case 'search_files':
@@ -172,6 +149,6 @@ function formatToolArgs(name: string, args: Record<string, any>): string {
172
149
  case 'run_command':
173
150
  return args.command || '';
174
151
  default:
175
- return JSON.stringify(args).slice(0, 50);
152
+ return JSON.stringify(args).slice(0, 60);
176
153
  }
177
154
  }
@@ -2,13 +2,12 @@ import chalk from 'chalk';
2
2
  import * as readline from 'readline';
3
3
  import { getSessions, getSession, config } from '../utils/config.js';
4
4
  import { startRepl } from './repl.js';
5
- import { printBanner, printError, printInfo } from '../utils/ui.js';
6
5
 
7
6
  export async function listSessions(): Promise<void> {
8
7
  const sessions = getSessions();
9
8
 
10
9
  if (sessions.length === 0) {
11
- printInfo('No saved sessions found.');
10
+ console.log(chalk.blue('ℹ ') + 'No saved sessions found.');
12
11
  console.log(chalk.gray('Start a new session with: ') + chalk.cyan('codea'));
13
12
  return;
14
13
  }
@@ -38,7 +37,7 @@ export async function resumeSession(sessionId?: string): Promise<void> {
38
37
  const sessions = getSessions();
39
38
 
40
39
  if (sessions.length === 0) {
41
- printInfo('No saved sessions found.');
40
+ console.log(chalk.blue('ℹ ') + 'No saved sessions found.');
42
41
  return;
43
42
  }
44
43
 
@@ -54,7 +53,7 @@ export async function resumeSession(sessionId?: string): Promise<void> {
54
53
  }
55
54
 
56
55
  if (!selectedSession) {
57
- printError(`Session not found: ${sessionId}`);
56
+ console.log(chalk.red('✗ Error: ') + `Session not found: ${sessionId}`);
58
57
  return;
59
58
  }
60
59
  } else {
@@ -82,7 +81,7 @@ export async function resumeSession(sessionId?: string): Promise<void> {
82
81
 
83
82
  const index = parseInt(answer) - 1;
84
83
  if (isNaN(index) || index < 0 || index >= sessions.length) {
85
- printError('Invalid selection.');
84
+ console.log(chalk.red('✗ Error: ') + 'Invalid selection.');
86
85
  resolve();
87
86
  return;
88
87
  }
@@ -99,7 +98,7 @@ export async function resumeSession(sessionId?: string): Promise<void> {
99
98
  }
100
99
 
101
100
  async function startRestoredSession(session: any): Promise<void> {
102
- printInfo(`Resuming: ${session.title}`);
101
+ console.log(chalk.blue('ℹ ') + `Resuming: ${session.title}`);
103
102
  console.log();
104
103
 
105
104
  // Display previous messages