@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { streamChat } from './api.js';
|
|
2
|
+
import { executeTool } from '../tools/executor.js';
|
|
3
|
+
import { ApprovalMode, needsApproval } from './approval.js';
|
|
4
|
+
|
|
5
|
+
export interface Message {
|
|
6
|
+
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
7
|
+
content: string;
|
|
8
|
+
tool_calls?: ToolCall[];
|
|
9
|
+
tool_call_id?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToolCall {
|
|
13
|
+
id: string;
|
|
14
|
+
type: 'function';
|
|
15
|
+
function: {
|
|
16
|
+
name: string;
|
|
17
|
+
arguments: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ToolExecution {
|
|
22
|
+
id: string;
|
|
23
|
+
tool: string;
|
|
24
|
+
args: Record<string, any>;
|
|
25
|
+
result?: string;
|
|
26
|
+
success?: boolean;
|
|
27
|
+
approved?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ConversationEvent =
|
|
31
|
+
| { type: 'thinking' }
|
|
32
|
+
| { type: 'content'; text: string }
|
|
33
|
+
| { type: 'tool_start'; execution: ToolExecution }
|
|
34
|
+
| { type: 'approval_needed'; execution: ToolExecution; resolve: (approved: boolean) => void }
|
|
35
|
+
| { type: 'tool_done'; execution: ToolExecution }
|
|
36
|
+
| { type: 'done'; content: string }
|
|
37
|
+
| { type: 'error'; message: string };
|
|
38
|
+
|
|
39
|
+
export interface ConversationOptions {
|
|
40
|
+
messages: Message[];
|
|
41
|
+
systemMessage: string;
|
|
42
|
+
model: string;
|
|
43
|
+
approvalMode: ApprovalMode;
|
|
44
|
+
onEvent: (event: ConversationEvent) => void;
|
|
45
|
+
requestApproval: (execution: ToolExecution) => Promise<boolean>;
|
|
46
|
+
isActive: () => boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function processConversation(opts: ConversationOptions): Promise<void> {
|
|
50
|
+
const { messages, systemMessage, model, approvalMode, onEvent, requestApproval, isActive } = opts;
|
|
51
|
+
|
|
52
|
+
while (isActive()) {
|
|
53
|
+
let fullContent = '';
|
|
54
|
+
let toolCalls: ToolCall[] | undefined;
|
|
55
|
+
|
|
56
|
+
onEvent({ type: 'thinking' });
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await streamChat(messages, systemMessage, model, {
|
|
60
|
+
onContent: (content) => {
|
|
61
|
+
if (!isActive()) return;
|
|
62
|
+
fullContent += content;
|
|
63
|
+
onEvent({ type: 'content', text: content });
|
|
64
|
+
},
|
|
65
|
+
onToolCall: () => {},
|
|
66
|
+
onDone: (_content, tcs) => {
|
|
67
|
+
toolCalls = tcs;
|
|
68
|
+
},
|
|
69
|
+
onError: (error) => {
|
|
70
|
+
onEvent({ type: 'error', message: error.message });
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
} catch (error: any) {
|
|
74
|
+
onEvent({ type: 'error', message: error.message });
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!isActive()) break;
|
|
79
|
+
|
|
80
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
81
|
+
messages.push({
|
|
82
|
+
role: 'assistant',
|
|
83
|
+
content: fullContent,
|
|
84
|
+
tool_calls: toolCalls,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
for (const tc of toolCalls) {
|
|
88
|
+
if (!isActive()) break;
|
|
89
|
+
|
|
90
|
+
const args = JSON.parse(tc.function.arguments);
|
|
91
|
+
const execution: ToolExecution = {
|
|
92
|
+
id: tc.id,
|
|
93
|
+
tool: tc.function.name,
|
|
94
|
+
args,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
onEvent({ type: 'tool_start', execution });
|
|
98
|
+
|
|
99
|
+
// Check approval
|
|
100
|
+
if (needsApproval(tc.function.name, approvalMode)) {
|
|
101
|
+
const approved = await requestApproval(execution);
|
|
102
|
+
if (!approved) {
|
|
103
|
+
execution.approved = false;
|
|
104
|
+
execution.success = false;
|
|
105
|
+
execution.result = 'User declined this action.';
|
|
106
|
+
messages.push({
|
|
107
|
+
role: 'tool',
|
|
108
|
+
tool_call_id: tc.id,
|
|
109
|
+
content: 'User declined this action.',
|
|
110
|
+
});
|
|
111
|
+
onEvent({ type: 'tool_done', execution });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
execution.approved = true;
|
|
115
|
+
} else {
|
|
116
|
+
execution.approved = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = await executeTool(tc.function.name, args);
|
|
120
|
+
execution.result = result.result;
|
|
121
|
+
execution.success = result.success;
|
|
122
|
+
|
|
123
|
+
messages.push({
|
|
124
|
+
role: 'tool',
|
|
125
|
+
tool_call_id: tc.id,
|
|
126
|
+
content: result.result,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
onEvent({ type: 'tool_done', execution });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
continue;
|
|
133
|
+
} else {
|
|
134
|
+
if (fullContent) {
|
|
135
|
+
messages.push({ role: 'assistant', content: fullContent });
|
|
136
|
+
}
|
|
137
|
+
onEvent({ type: 'done', content: fullContent });
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
package/dist/api-X2G5QROW.js
DELETED
package/dist/chunk-SVPL4GNV.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// src/utils/api.ts
|
|
2
|
-
import OpenAI from "openai";
|
|
3
|
-
|
|
4
|
-
// src/utils/config.ts
|
|
5
|
-
import Conf from "conf";
|
|
6
|
-
var config = new Conf({
|
|
7
|
-
projectName: "alia-codea-cli",
|
|
8
|
-
defaults: {
|
|
9
|
-
apiKey: "",
|
|
10
|
-
apiBaseUrl: "https://api.alia.onl",
|
|
11
|
-
defaultModel: "alia-v1-codea",
|
|
12
|
-
sessions: [],
|
|
13
|
-
currentSessionId: null
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
function saveSession(session) {
|
|
17
|
-
const sessions = config.get("sessions") || [];
|
|
18
|
-
const existingIndex = sessions.findIndex((s) => s.id === session.id);
|
|
19
|
-
if (existingIndex >= 0) {
|
|
20
|
-
sessions[existingIndex] = session;
|
|
21
|
-
} else {
|
|
22
|
-
sessions.unshift(session);
|
|
23
|
-
}
|
|
24
|
-
if (sessions.length > 50) {
|
|
25
|
-
sessions.splice(50);
|
|
26
|
-
}
|
|
27
|
-
config.set("sessions", sessions);
|
|
28
|
-
}
|
|
29
|
-
function getSession(id) {
|
|
30
|
-
const sessions = config.get("sessions") || [];
|
|
31
|
-
return sessions.find((s) => s.id === id);
|
|
32
|
-
}
|
|
33
|
-
function getSessions() {
|
|
34
|
-
return config.get("sessions") || [];
|
|
35
|
-
}
|
|
36
|
-
function createSession() {
|
|
37
|
-
const session = {
|
|
38
|
-
id: Date.now().toString(),
|
|
39
|
-
title: "New conversation",
|
|
40
|
-
messages: [],
|
|
41
|
-
createdAt: Date.now(),
|
|
42
|
-
updatedAt: Date.now(),
|
|
43
|
-
cwd: process.cwd()
|
|
44
|
-
};
|
|
45
|
-
saveSession(session);
|
|
46
|
-
config.set("currentSessionId", session.id);
|
|
47
|
-
return session;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/utils/api.ts
|
|
51
|
-
var fileTools = [
|
|
52
|
-
{
|
|
53
|
-
type: "function",
|
|
54
|
-
function: {
|
|
55
|
-
name: "read_file",
|
|
56
|
-
description: "Read the contents of a file",
|
|
57
|
-
parameters: {
|
|
58
|
-
type: "object",
|
|
59
|
-
properties: {
|
|
60
|
-
path: { type: "string", description: "The file path to read" }
|
|
61
|
-
},
|
|
62
|
-
required: ["path"]
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
type: "function",
|
|
68
|
-
function: {
|
|
69
|
-
name: "write_file",
|
|
70
|
-
description: "Write content to a file (creates or overwrites)",
|
|
71
|
-
parameters: {
|
|
72
|
-
type: "object",
|
|
73
|
-
properties: {
|
|
74
|
-
path: { type: "string", description: "The file path to write to" },
|
|
75
|
-
content: { type: "string", description: "The content to write" }
|
|
76
|
-
},
|
|
77
|
-
required: ["path", "content"]
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
type: "function",
|
|
83
|
-
function: {
|
|
84
|
-
name: "edit_file",
|
|
85
|
-
description: "Make targeted edits to a file by replacing specific text",
|
|
86
|
-
parameters: {
|
|
87
|
-
type: "object",
|
|
88
|
-
properties: {
|
|
89
|
-
path: { type: "string", description: "The file path to edit" },
|
|
90
|
-
old_text: { type: "string", description: "The text to find and replace" },
|
|
91
|
-
new_text: { type: "string", description: "The replacement text" }
|
|
92
|
-
},
|
|
93
|
-
required: ["path", "old_text", "new_text"]
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
type: "function",
|
|
99
|
-
function: {
|
|
100
|
-
name: "list_files",
|
|
101
|
-
description: "List files in a directory",
|
|
102
|
-
parameters: {
|
|
103
|
-
type: "object",
|
|
104
|
-
properties: {
|
|
105
|
-
path: { type: "string", description: "The directory path (default: current directory)" },
|
|
106
|
-
recursive: { type: "boolean", description: "Whether to list recursively" }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
type: "function",
|
|
113
|
-
function: {
|
|
114
|
-
name: "search_files",
|
|
115
|
-
description: "Search for text patterns across files",
|
|
116
|
-
parameters: {
|
|
117
|
-
type: "object",
|
|
118
|
-
properties: {
|
|
119
|
-
pattern: { type: "string", description: "The search pattern (regex supported)" },
|
|
120
|
-
path: { type: "string", description: "Directory to search in (default: current)" },
|
|
121
|
-
file_pattern: { type: "string", description: 'File glob pattern (e.g., "*.ts")' }
|
|
122
|
-
},
|
|
123
|
-
required: ["pattern"]
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
type: "function",
|
|
129
|
-
function: {
|
|
130
|
-
name: "run_command",
|
|
131
|
-
description: "Execute a shell command",
|
|
132
|
-
parameters: {
|
|
133
|
-
type: "object",
|
|
134
|
-
properties: {
|
|
135
|
-
command: { type: "string", description: "The command to execute" },
|
|
136
|
-
cwd: { type: "string", description: "Working directory (default: current)" }
|
|
137
|
-
},
|
|
138
|
-
required: ["command"]
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
];
|
|
143
|
-
async function streamChat(messages, systemMessage, model, callbacks) {
|
|
144
|
-
const apiKey = config.get("apiKey");
|
|
145
|
-
const baseUrl = config.get("apiBaseUrl") || "https://api.alia.onl";
|
|
146
|
-
const openai = new OpenAI({
|
|
147
|
-
apiKey,
|
|
148
|
-
baseURL: `${baseUrl}/v1`
|
|
149
|
-
});
|
|
150
|
-
const allMessages = [
|
|
151
|
-
{ role: "system", content: systemMessage },
|
|
152
|
-
...messages.map((m) => {
|
|
153
|
-
if (m.role === "tool") {
|
|
154
|
-
return { role: "tool", tool_call_id: m.tool_call_id, content: m.content };
|
|
155
|
-
} else if (m.tool_calls) {
|
|
156
|
-
return { role: "assistant", content: m.content || "", tool_calls: m.tool_calls };
|
|
157
|
-
}
|
|
158
|
-
return { role: m.role, content: m.content };
|
|
159
|
-
})
|
|
160
|
-
];
|
|
161
|
-
try {
|
|
162
|
-
const stream = await openai.chat.completions.create({
|
|
163
|
-
model,
|
|
164
|
-
messages: allMessages,
|
|
165
|
-
tools: fileTools,
|
|
166
|
-
stream: true
|
|
167
|
-
});
|
|
168
|
-
let fullContent = "";
|
|
169
|
-
const toolCalls = [];
|
|
170
|
-
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
171
|
-
for await (const chunk of stream) {
|
|
172
|
-
const delta = chunk.choices?.[0]?.delta;
|
|
173
|
-
if (!delta) continue;
|
|
174
|
-
if (delta.content) {
|
|
175
|
-
fullContent += delta.content;
|
|
176
|
-
callbacks.onContent(delta.content);
|
|
177
|
-
}
|
|
178
|
-
if (delta.tool_calls) {
|
|
179
|
-
for (const tc of delta.tool_calls) {
|
|
180
|
-
const index = tc.index ?? 0;
|
|
181
|
-
if (!toolCallsMap.has(index)) {
|
|
182
|
-
const newToolCall = {
|
|
183
|
-
id: tc.id || "",
|
|
184
|
-
type: "function",
|
|
185
|
-
function: {
|
|
186
|
-
name: tc.function?.name || "",
|
|
187
|
-
arguments: tc.function?.arguments || ""
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
toolCallsMap.set(index, newToolCall);
|
|
191
|
-
toolCalls.push(newToolCall);
|
|
192
|
-
} else {
|
|
193
|
-
const existingToolCall = toolCallsMap.get(index);
|
|
194
|
-
if (tc.function?.name) {
|
|
195
|
-
existingToolCall.function.name = tc.function.name;
|
|
196
|
-
}
|
|
197
|
-
if (tc.function?.arguments) {
|
|
198
|
-
existingToolCall.function.arguments += tc.function.arguments;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
callbacks.onDone(fullContent, toolCalls.length > 0 ? toolCalls : void 0);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
callbacks.onError(error);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
async function fetchModels() {
|
|
210
|
-
const baseUrl = config.get("apiBaseUrl") || "https://api.alia.onl";
|
|
211
|
-
try {
|
|
212
|
-
const response = await fetch(`${baseUrl}/v1/models?category=coding`);
|
|
213
|
-
if (!response.ok) return [];
|
|
214
|
-
const data = await response.json();
|
|
215
|
-
return data.data || [];
|
|
216
|
-
} catch {
|
|
217
|
-
return [];
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export {
|
|
222
|
-
config,
|
|
223
|
-
saveSession,
|
|
224
|
-
getSession,
|
|
225
|
-
getSessions,
|
|
226
|
-
createSession,
|
|
227
|
-
fileTools,
|
|
228
|
-
streamChat,
|
|
229
|
-
fetchModels
|
|
230
|
-
};
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
package/src/utils/ui.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import * as readline from 'readline';
|
|
3
|
-
|
|
4
|
-
// Gradient colors for the banner (cyan to magenta)
|
|
5
|
-
const gradientColors = [
|
|
6
|
-
'#00d4ff', '#00c4ff', '#00b4ff', '#00a4ff',
|
|
7
|
-
'#4094ff', '#8084ff', '#c074ff', '#ff64ff'
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
function hexToRgb(hex: string): [number, number, number] {
|
|
11
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
12
|
-
return result
|
|
13
|
-
? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
|
|
14
|
-
: [255, 255, 255];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function colorize(text: string, colorIndex: number, totalColors: number): string {
|
|
18
|
-
const ratio = colorIndex / totalColors;
|
|
19
|
-
const startColor = hexToRgb(gradientColors[0]);
|
|
20
|
-
const endColor = hexToRgb(gradientColors[gradientColors.length - 1]);
|
|
21
|
-
|
|
22
|
-
const r = Math.round(startColor[0] + ratio * (endColor[0] - startColor[0]));
|
|
23
|
-
const g = Math.round(startColor[1] + ratio * (endColor[1] - startColor[1]));
|
|
24
|
-
const b = Math.round(startColor[2] + ratio * (endColor[2] - startColor[2]));
|
|
25
|
-
|
|
26
|
-
return chalk.rgb(r, g, b)(text);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ASCII art banner with gradient
|
|
30
|
-
export function printBanner(): void {
|
|
31
|
-
const banner = [
|
|
32
|
-
' ██████╗ ██████╗ ██████╗ ███████╗ █████╗ ',
|
|
33
|
-
' ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗',
|
|
34
|
-
' ██║ ██║ ██║██║ ██║█████╗ ███████║',
|
|
35
|
-
' ██║ ██║ ██║██║ ██║██╔══╝ ██╔══██║',
|
|
36
|
-
' ╚██████╗╚██████╔╝██████╔╝███████╗██║ ██║',
|
|
37
|
-
' ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
console.log();
|
|
41
|
-
banner.forEach((line, i) => {
|
|
42
|
-
let coloredLine = '';
|
|
43
|
-
for (let j = 0; j < line.length; j++) {
|
|
44
|
-
coloredLine += colorize(line[j], j, line.length);
|
|
45
|
-
}
|
|
46
|
-
console.log(coloredLine);
|
|
47
|
-
});
|
|
48
|
-
console.log(chalk.gray(' by Alia'));
|
|
49
|
-
console.log();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function printTips(): void {
|
|
53
|
-
console.log(chalk.white('Tips for getting started:'));
|
|
54
|
-
console.log(chalk.gray('1. Ask questions, edit files, or run commands.'));
|
|
55
|
-
console.log(chalk.gray('2. Be specific for the best results.'));
|
|
56
|
-
console.log(chalk.gray('3. ') + chalk.cyan('/help') + chalk.gray(' for more information.'));
|
|
57
|
-
console.log();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function printPrompt(): void {
|
|
61
|
-
process.stdout.write(chalk.cyan('❯ '));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function printToolExecution(tool: string, description: string): void {
|
|
65
|
-
const boxWidth = Math.min(process.stdout.columns || 80, 80);
|
|
66
|
-
const content = `${chalk.bold(tool)} ${description}`;
|
|
67
|
-
const paddedContent = ` ← ${content} `.padEnd(boxWidth - 4);
|
|
68
|
-
|
|
69
|
-
console.log();
|
|
70
|
-
console.log(chalk.gray('┌' + '─'.repeat(boxWidth - 2) + '┐'));
|
|
71
|
-
console.log(chalk.gray('│') + paddedContent + chalk.gray('│'));
|
|
72
|
-
console.log(chalk.gray('└' + '─'.repeat(boxWidth - 2) + '┘'));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function printToolResult(success: boolean, result: string): void {
|
|
76
|
-
const status = success ? chalk.green('✓') : chalk.red('✗');
|
|
77
|
-
const preview = result.slice(0, 100).replace(/\n/g, ' ');
|
|
78
|
-
console.log(` ${status} ${chalk.gray(preview)}${result.length > 100 ? '...' : ''}`);
|
|
79
|
-
console.log();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let statusInterval: NodeJS.Timeout | null = null;
|
|
83
|
-
let startTime: number = 0;
|
|
84
|
-
|
|
85
|
-
export function showThinkingStatus(message: string): void {
|
|
86
|
-
startTime = Date.now();
|
|
87
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
88
|
-
let frameIndex = 0;
|
|
89
|
-
|
|
90
|
-
statusInterval = setInterval(() => {
|
|
91
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
92
|
-
const frame = frames[frameIndex % frames.length];
|
|
93
|
-
frameIndex++;
|
|
94
|
-
|
|
95
|
-
readline.clearLine(process.stdout, 0);
|
|
96
|
-
readline.cursorTo(process.stdout, 0);
|
|
97
|
-
process.stdout.write(
|
|
98
|
-
chalk.cyan(frame) + ' ' +
|
|
99
|
-
chalk.bold(message) +
|
|
100
|
-
chalk.gray(` (esc to cancel, ${elapsed}s)`)
|
|
101
|
-
);
|
|
102
|
-
}, 80);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function hideThinkingStatus(): void {
|
|
106
|
-
if (statusInterval) {
|
|
107
|
-
clearInterval(statusInterval);
|
|
108
|
-
statusInterval = null;
|
|
109
|
-
readline.clearLine(process.stdout, 0);
|
|
110
|
-
readline.cursorTo(process.stdout, 0);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function printStatusBar(cwd: string, model: string, contextPercent: number): void {
|
|
115
|
-
const width = process.stdout.columns || 80;
|
|
116
|
-
const cwdPart = chalk.cyan(shortenPath(cwd));
|
|
117
|
-
const modelPart = chalk.magenta(`${model} (${contextPercent}% context left)`);
|
|
118
|
-
|
|
119
|
-
const padding = width - stripAnsi(cwdPart).length - stripAnsi(modelPart).length - 4;
|
|
120
|
-
const spacer = ' '.repeat(Math.max(padding, 2));
|
|
121
|
-
|
|
122
|
-
console.log();
|
|
123
|
-
console.log(chalk.gray('─'.repeat(width)));
|
|
124
|
-
console.log(`${cwdPart}${spacer}${modelPart}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function shortenPath(p: string): string {
|
|
128
|
-
const home = process.env.HOME || '';
|
|
129
|
-
if (p.startsWith(home)) {
|
|
130
|
-
return '~' + p.slice(home.length);
|
|
131
|
-
}
|
|
132
|
-
return p;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function stripAnsi(str: string): string {
|
|
136
|
-
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function printAssistantPrefix(): void {
|
|
140
|
-
process.stdout.write(chalk.magenta('✦ '));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function printError(message: string): void {
|
|
144
|
-
console.log(chalk.red('✗ Error: ') + message);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function printSuccess(message: string): void {
|
|
148
|
-
console.log(chalk.green('✓ ') + message);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function printInfo(message: string): void {
|
|
152
|
-
console.log(chalk.blue('ℹ ') + message);
|
|
153
|
-
}
|