@alia-codea/cli 1.1.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.
- package/dist/index.js +1263 -487
- package/package.json +5 -5
- package/src/app.tsx +281 -0
- package/src/commands/auth.ts +11 -1
- package/src/commands/repl.ts +11 -299
- package/src/commands/run.ts +103 -126
- package/src/commands/sessions.ts +5 -6
- package/src/components/ApprovalPrompt.tsx +60 -0
- package/src/components/Header.tsx +39 -0
- package/src/components/InputBar.tsx +36 -0
- package/src/components/MessageList.tsx +81 -0
- package/src/components/ThinkingIndicator.tsx +28 -0
- package/src/components/ToolCallCard.tsx +68 -0
- package/src/index.ts +20 -6
- package/src/tools/executor.ts +140 -14
- package/src/tools/patch.ts +167 -0
- package/src/utils/api.ts +22 -3
- package/src/utils/approval.ts +31 -0
- package/src/utils/context.ts +65 -4
- package/src/utils/conversation.ts +141 -0
- package/dist/api-X2G5QROW.js +0 -10
- package/dist/chunk-SVPL4GNV.js +0 -230
- package/dist/index.d.ts +0 -1
- package/src/utils/ui.ts +0 -153
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alia-codea/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Codea CLI - AI coding assistant for your terminal by Alia",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "tsup src/index.ts --format esm --
|
|
11
|
+
"build": "tsup src/index.ts --format esm --clean",
|
|
12
12
|
"dev": "tsx src/index.ts",
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
14
|
"lint": "eslint src",
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
"chalk": "^5.3.0",
|
|
30
30
|
"commander": "^12.1.0",
|
|
31
31
|
"conf": "^13.0.1",
|
|
32
|
-
"ink": "^
|
|
32
|
+
"ink": "^6.7.0",
|
|
33
33
|
"ink-spinner": "^5.0.0",
|
|
34
34
|
"ink-text-input": "^6.0.0",
|
|
35
35
|
"marked": "^15.0.4",
|
|
36
36
|
"marked-terminal": "^7.2.1",
|
|
37
37
|
"openai": "^6.16.0",
|
|
38
38
|
"ora": "^8.1.1",
|
|
39
|
-
"react": "
|
|
39
|
+
"react": "19.2.0",
|
|
40
40
|
"simple-git": "^3.27.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^22.10.5",
|
|
44
|
-
"@types/react": "^
|
|
44
|
+
"@types/react": "^19.2.0",
|
|
45
45
|
"tsup": "^8.3.5",
|
|
46
46
|
"tsx": "^4.19.2",
|
|
47
47
|
"typescript": "^5.7.2"
|
package/src/app.tsx
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
+
import { Header } from './components/Header.js';
|
|
4
|
+
import { MessageList, DisplayMessage } from './components/MessageList.js';
|
|
5
|
+
import { InputBar } from './components/InputBar.js';
|
|
6
|
+
import { ApprovalPrompt } from './components/ApprovalPrompt.js';
|
|
7
|
+
import { processConversation, Message, ToolExecution } from './utils/conversation.js';
|
|
8
|
+
import { buildSystemMessage, getCodebaseContext, loadProjectInstructions } from './utils/context.js';
|
|
9
|
+
import { createSession, saveSession } from './utils/config.js';
|
|
10
|
+
import { ApprovalMode } from './utils/approval.js';
|
|
11
|
+
|
|
12
|
+
export interface AppOptions {
|
|
13
|
+
model: string;
|
|
14
|
+
approvalMode: ApprovalMode;
|
|
15
|
+
context: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let msgCounter = 0;
|
|
19
|
+
function nextId(): string {
|
|
20
|
+
return `msg-${++msgCounter}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function App({ options }: { options: AppOptions }) {
|
|
24
|
+
const { exit } = useApp();
|
|
25
|
+
const [displayMessages, setDisplayMessages] = useState<DisplayMessage[]>([]);
|
|
26
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
27
|
+
const [thinkingLabel, setThinkingLabel] = useState('Thinking');
|
|
28
|
+
const [approvalMode, setApprovalMode] = useState<ApprovalMode>(options.approvalMode);
|
|
29
|
+
const [contextPercent, setContextPercent] = useState(100);
|
|
30
|
+
const [pendingApproval, setPendingApproval] = useState<{
|
|
31
|
+
execution: ToolExecution;
|
|
32
|
+
resolve: (approved: boolean) => void;
|
|
33
|
+
} | null>(null);
|
|
34
|
+
const [ready, setReady] = useState(false);
|
|
35
|
+
const [codebaseContext, setCodebaseContext] = useState('');
|
|
36
|
+
const [instructions, setInstructions] = useState('');
|
|
37
|
+
|
|
38
|
+
const messagesRef = useRef<Message[]>([]);
|
|
39
|
+
const sessionRef = useRef(createSession());
|
|
40
|
+
const activeRef = useRef(true);
|
|
41
|
+
const streamingIdRef = useRef<string | null>(null);
|
|
42
|
+
|
|
43
|
+
// Initialize on mount
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
let cancelled = false;
|
|
46
|
+
(async () => {
|
|
47
|
+
let ctx = '';
|
|
48
|
+
let instr = '';
|
|
49
|
+
if (options.context !== false) {
|
|
50
|
+
ctx = await getCodebaseContext();
|
|
51
|
+
if (ctx && !cancelled) {
|
|
52
|
+
setDisplayMessages((prev) => [
|
|
53
|
+
...prev,
|
|
54
|
+
{ id: nextId(), type: 'info', content: `Loaded context from ${ctx.split('\n').length} lines` },
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
instr = await loadProjectInstructions();
|
|
59
|
+
if (instr && !cancelled) {
|
|
60
|
+
const count = instr.split('\n---\n').length;
|
|
61
|
+
setDisplayMessages((prev) => [
|
|
62
|
+
...prev,
|
|
63
|
+
{ id: nextId(), type: 'info', content: `Loaded ${count} CODEA.md instruction file(s)` },
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
if (!cancelled) {
|
|
67
|
+
setCodebaseContext(ctx);
|
|
68
|
+
setInstructions(instr);
|
|
69
|
+
setReady(true);
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
return () => { cancelled = true; };
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
// Handle Ctrl+C
|
|
76
|
+
useInput((_input, key) => {
|
|
77
|
+
if (key.ctrl && (_input === 'c' || _input === 'C')) {
|
|
78
|
+
if (isProcessing) {
|
|
79
|
+
activeRef.current = false;
|
|
80
|
+
setIsProcessing(false);
|
|
81
|
+
setPendingApproval(null);
|
|
82
|
+
setDisplayMessages((prev) => [
|
|
83
|
+
...prev,
|
|
84
|
+
{ id: nextId(), type: 'info', content: 'Cancelled.' },
|
|
85
|
+
]);
|
|
86
|
+
} else {
|
|
87
|
+
exit();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const addMessage = useCallback((msg: DisplayMessage) => {
|
|
93
|
+
setDisplayMessages((prev) => [...prev, msg]);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const updateLastAssistant = useCallback((text: string) => {
|
|
97
|
+
setDisplayMessages((prev) => {
|
|
98
|
+
const last = prev[prev.length - 1];
|
|
99
|
+
if (last && last.type === 'assistant' && last.streaming) {
|
|
100
|
+
return [...prev.slice(0, -1), { ...last, content: last.content + text }];
|
|
101
|
+
}
|
|
102
|
+
return prev;
|
|
103
|
+
});
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
const finalizeAssistant = useCallback(() => {
|
|
107
|
+
setDisplayMessages((prev) => {
|
|
108
|
+
const last = prev[prev.length - 1];
|
|
109
|
+
if (last && last.type === 'assistant' && last.streaming) {
|
|
110
|
+
return [...prev.slice(0, -1), { ...last, streaming: false }];
|
|
111
|
+
}
|
|
112
|
+
return prev;
|
|
113
|
+
});
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
const handleSubmit = useCallback(async (input: string) => {
|
|
117
|
+
// Handle slash commands
|
|
118
|
+
if (input.startsWith('/')) {
|
|
119
|
+
const [cmd, ...args] = input.slice(1).split(' ');
|
|
120
|
+
switch (cmd.toLowerCase()) {
|
|
121
|
+
case 'help':
|
|
122
|
+
addMessage({
|
|
123
|
+
id: nextId(),
|
|
124
|
+
type: 'info',
|
|
125
|
+
content: 'Commands: /help, /clear, /mode <suggest|auto-edit|full-auto>, /model <name>, /exit',
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
case 'clear':
|
|
129
|
+
messagesRef.current = [];
|
|
130
|
+
setDisplayMessages([]);
|
|
131
|
+
setContextPercent(100);
|
|
132
|
+
return;
|
|
133
|
+
case 'mode':
|
|
134
|
+
if (args[0] && ['suggest', 'auto-edit', 'full-auto'].includes(args[0])) {
|
|
135
|
+
setApprovalMode(args[0] as ApprovalMode);
|
|
136
|
+
addMessage({ id: nextId(), type: 'info', content: `Approval mode: ${args[0]}` });
|
|
137
|
+
} else {
|
|
138
|
+
addMessage({ id: nextId(), type: 'info', content: `Current mode: ${approvalMode}. Options: suggest, auto-edit, full-auto` });
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
case 'model':
|
|
142
|
+
if (args[0]) {
|
|
143
|
+
options.model = args[0].startsWith('alia-') ? args[0] : `alia-v1-${args[0]}`;
|
|
144
|
+
addMessage({ id: nextId(), type: 'info', content: `Model: ${options.model}` });
|
|
145
|
+
} else {
|
|
146
|
+
addMessage({ id: nextId(), type: 'info', content: `Current model: ${options.model}` });
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
case 'exit':
|
|
150
|
+
case 'quit':
|
|
151
|
+
exit();
|
|
152
|
+
return;
|
|
153
|
+
default:
|
|
154
|
+
addMessage({ id: nextId(), type: 'info', content: `Unknown command: /${cmd}` });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add user message
|
|
160
|
+
addMessage({ id: nextId(), type: 'user', content: input });
|
|
161
|
+
messagesRef.current.push({ role: 'user', content: input });
|
|
162
|
+
|
|
163
|
+
setIsProcessing(true);
|
|
164
|
+
activeRef.current = true;
|
|
165
|
+
streamingIdRef.current = null;
|
|
166
|
+
|
|
167
|
+
const systemMessage = buildSystemMessage(options.model, codebaseContext, instructions);
|
|
168
|
+
|
|
169
|
+
await processConversation({
|
|
170
|
+
messages: messagesRef.current,
|
|
171
|
+
systemMessage,
|
|
172
|
+
model: options.model,
|
|
173
|
+
approvalMode,
|
|
174
|
+
isActive: () => activeRef.current,
|
|
175
|
+
requestApproval: (execution) => {
|
|
176
|
+
return new Promise<boolean>((resolve) => {
|
|
177
|
+
setPendingApproval({ execution, resolve });
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
onEvent: (event) => {
|
|
181
|
+
switch (event.type) {
|
|
182
|
+
case 'thinking':
|
|
183
|
+
setThinkingLabel('Thinking');
|
|
184
|
+
// Start a new streaming assistant message
|
|
185
|
+
streamingIdRef.current = nextId();
|
|
186
|
+
setDisplayMessages((prev) => [
|
|
187
|
+
...prev,
|
|
188
|
+
{ id: streamingIdRef.current!, type: 'assistant', content: '', streaming: true },
|
|
189
|
+
]);
|
|
190
|
+
break;
|
|
191
|
+
case 'content':
|
|
192
|
+
updateLastAssistant(event.text);
|
|
193
|
+
break;
|
|
194
|
+
case 'tool_start':
|
|
195
|
+
// Finalize any streaming text before showing tool
|
|
196
|
+
finalizeAssistant();
|
|
197
|
+
setThinkingLabel(`Running ${event.execution.tool}`);
|
|
198
|
+
addMessage({
|
|
199
|
+
id: nextId(),
|
|
200
|
+
type: 'tool',
|
|
201
|
+
content: '',
|
|
202
|
+
toolExecution: { ...event.execution },
|
|
203
|
+
});
|
|
204
|
+
break;
|
|
205
|
+
case 'tool_done':
|
|
206
|
+
// Update the tool message with result
|
|
207
|
+
setDisplayMessages((prev) => {
|
|
208
|
+
const idx = prev.findLastIndex(
|
|
209
|
+
(m) => m.type === 'tool' && m.toolExecution?.id === event.execution.id
|
|
210
|
+
);
|
|
211
|
+
if (idx >= 0) {
|
|
212
|
+
const updated = [...prev];
|
|
213
|
+
updated[idx] = {
|
|
214
|
+
...updated[idx],
|
|
215
|
+
toolExecution: { ...event.execution },
|
|
216
|
+
};
|
|
217
|
+
return updated;
|
|
218
|
+
}
|
|
219
|
+
return prev;
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
case 'done':
|
|
223
|
+
finalizeAssistant();
|
|
224
|
+
break;
|
|
225
|
+
case 'error':
|
|
226
|
+
finalizeAssistant();
|
|
227
|
+
addMessage({ id: nextId(), type: 'info', content: `Error: ${event.message}` });
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
setIsProcessing(false);
|
|
234
|
+
setPendingApproval(null);
|
|
235
|
+
|
|
236
|
+
// Save session
|
|
237
|
+
const session = sessionRef.current;
|
|
238
|
+
session.messages = messagesRef.current.map((m) => ({ role: m.role, content: m.content }));
|
|
239
|
+
session.title = messagesRef.current[0]?.content.slice(0, 50) || 'New conversation';
|
|
240
|
+
session.updatedAt = Date.now();
|
|
241
|
+
saveSession(session);
|
|
242
|
+
|
|
243
|
+
// Update context estimate
|
|
244
|
+
const totalChars = messagesRef.current.reduce((acc, m) => acc + m.content.length, 0);
|
|
245
|
+
const maxContext = 128000;
|
|
246
|
+
setContextPercent(Math.max(5, 100 - Math.floor((totalChars / maxContext) * 100)));
|
|
247
|
+
}, [approvalMode, codebaseContext, instructions, options]);
|
|
248
|
+
|
|
249
|
+
const handleApprovalResolve = useCallback((approved: boolean) => {
|
|
250
|
+
if (pendingApproval) {
|
|
251
|
+
pendingApproval.resolve(approved);
|
|
252
|
+
setPendingApproval(null);
|
|
253
|
+
}
|
|
254
|
+
}, [pendingApproval]);
|
|
255
|
+
|
|
256
|
+
const modelDisplay = options.model.replace('alia-v1-', '');
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<Box flexDirection="column">
|
|
260
|
+
<Header
|
|
261
|
+
cwd={process.cwd()}
|
|
262
|
+
model={modelDisplay}
|
|
263
|
+
approvalMode={approvalMode}
|
|
264
|
+
contextPercent={contextPercent}
|
|
265
|
+
/>
|
|
266
|
+
<MessageList messages={displayMessages} />
|
|
267
|
+
{pendingApproval ? (
|
|
268
|
+
<ApprovalPrompt
|
|
269
|
+
execution={pendingApproval.execution}
|
|
270
|
+
onResolve={handleApprovalResolve}
|
|
271
|
+
/>
|
|
272
|
+
) : (
|
|
273
|
+
<InputBar
|
|
274
|
+
onSubmit={handleSubmit}
|
|
275
|
+
isProcessing={isProcessing}
|
|
276
|
+
thinkingLabel={thinkingLabel}
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
</Box>
|
|
280
|
+
);
|
|
281
|
+
}
|
package/src/commands/auth.ts
CHANGED
|
@@ -4,7 +4,17 @@ import * as http from 'http';
|
|
|
4
4
|
import { exec } from 'child_process';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { config } from '../utils/config.js';
|
|
7
|
-
|
|
7
|
+
function printSuccess(message: string): void {
|
|
8
|
+
console.log(chalk.green('✓ ') + message);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function printError(message: string): void {
|
|
12
|
+
console.log(chalk.red('✗ Error: ') + message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function printInfo(message: string): void {
|
|
16
|
+
console.log(chalk.blue('ℹ ') + message);
|
|
17
|
+
}
|
|
8
18
|
|
|
9
19
|
function openBrowser(url: string): void {
|
|
10
20
|
const cmd =
|
package/src/commands/repl.ts
CHANGED
|
@@ -1,309 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
}
|