apex-dev 2.1.3 → 3.0.1
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/.local/share/amp/history.jsonl +32 -0
- package/.local/share/amp/session.json +2 -2
- package/.local/share/amp/threads/T-019c9769-4a8a-77b8-beab-f48973276f9a.json +1541 -0
- package/.local/share/amp/threads/T-019c9772-edac-7075-b26e-0ada1f8697d2.json +7 -0
- package/.local/share/amp/threads/T-019c97e8-a9ab-71a1-a8f9-109c540c98bf.json +111 -0
- package/.local/share/amp/threads/T-019c97e9-2277-753c-8c5d-df745fa6cfff.json +7 -0
- package/.local/share/amp/threads/T-019c97e9-f28e-758d-9663-e37047a8ed95.json +111 -0
- package/.local/share/amp/threads/T-019c97ea-17c7-77b8-92b2-f641c069bcc9.json +71 -0
- package/.local/share/amp/threads/T-019c97ea-44c6-75b8-88bc-d88113194f6a.json +1611 -0
- package/.local/share/amp/threads/T-019c97ec-abae-7251-a5f6-693adf496a1c.json +7 -0
- package/.local/share/amp/threads/T-019c97f5-8e61-73ad-8c5d-2637abedcde6.json +1341 -0
- package/.local/share/amp/threads/T-019c989d-4f4e-7249-bde0-21d19455ccae.json +163 -0
- package/.local/share/amp/threads/T-019c989d-9024-73c4-bee8-e2ae45028a39.json +124 -0
- package/.local/share/amp/threads/T-019c989e-1394-74ad-8234-ac573fcdb4c7.json +1260 -0
- package/.local/share/amp/threads/T-019c989f-e3dd-772e-85ac-525d0fc88fda.json +403 -0
- package/.local/share/amp/threads/T-019c98a1-7b0c-778a-b311-2e1cff85d710.json +3422 -0
- package/.local/share/amp/threads/T-019c98c5-4b7f-7284-99e9-88aa8c18ba66.json +1830 -0
- package/.local/share/amp/threads/T-019c98d0-f27f-76ec-be10-6df96f22be99.json +4061 -0
- package/.local/share/amp/threads/T-019c98f9-d031-704d-a0c2-f2f395f68f2b.json +509 -0
- package/.local/share/amp/threads/T-019c9919-f9ee-766c-90be-af7a07f6a4c6.json +2075 -0
- package/.local/share/amp/threads/T-019c991c-b98b-7158-9083-cc52408beb13.json +7 -0
- package/.local/share/amp/threads/T-019c991d-66d6-72aa-a9a1-105f7df0ea06.json +7 -0
- package/.local/share/amp/threads/T-019c9c2e-71a4-77ff-bd7f-b053da7f9000.json +1637 -0
- package/.local/share/amp/threads/T-019c9c45-27ca-728b-ba77-835115dfa9b2.json +3893 -0
- package/.local/share/amp/threads/T-019c9c48-45dc-736a-9752-e4119fe698f9.json +7 -0
- package/.local/share/amp/threads/T-019c9c4d-266b-72d0-b56e-74a5777e6583.json +7 -0
- package/.local/share/amp/threads/T-019c9c52-ab89-758f-9178-bda99c39d10b.json +7 -0
- package/.local/share/amp/threads/T-019c9c56-5715-72e2-b8b4-87711a842dd1.json +1799 -0
- package/.local/share/opencode/opencode.db +0 -0
- package/.local/share/opencode/opencode.db-shm +0 -0
- package/.local/share/opencode/opencode.db-wal +0 -0
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36870ea98ffe8S5ZOCE4F11yFh.json +6 -0
- package/.local/share/opencode/storage/agent-usage-reminder/ses_3687a3e9affewUnHBzvpiPR6df.json +6 -0
- package/.local/share/opencode/storage/agent-usage-reminder/ses_36886e68dffeKVgUWf6lzXdEEt.json +6 -0
- package/.local/share/opencode/storage/session_diff/ses_36870ea98ffe8S5ZOCE4F11yFh.json +1 -0
- package/.local/share/opencode/storage/session_diff/ses_3687a3e9affewUnHBzvpiPR6df.json +1 -0
- package/.local/share/opencode/storage/session_diff/ses_36886e68dffeKVgUWf6lzXdEEt.json +1 -0
- package/.local/state/replit/log-query.db +0 -0
- package/.local/state/replit/log-query.db-shm +0 -0
- package/.local/state/replit/log-query.db-wal +0 -0
- package/.upm/store.json +1 -1
- package/AGENTS.md +32 -0
- package/bun.lock +137 -103
- package/index.jsx +24 -0
- package/package.json +12 -9
- package/src/agent.js +252 -169
- package/src/app.jsx +96 -0
- package/src/commands.js +66 -38
- package/src/components/AssistantMessage.jsx +83 -0
- package/src/components/ChatArea.jsx +84 -0
- package/src/components/DiffView.jsx +26 -0
- package/src/components/Divider.jsx +8 -0
- package/src/components/Header.jsx +44 -0
- package/src/components/HelpModal.jsx +81 -0
- package/src/components/InputBar.jsx +32 -0
- package/src/components/Spinner.jsx +23 -0
- package/src/components/StatusBar.jsx +44 -0
- package/src/components/SystemMessage.jsx +31 -0
- package/src/components/ThinkBlock.jsx +29 -0
- package/src/components/ToolCallItem.jsx +43 -0
- package/src/components/UserMessage.jsx +11 -0
- package/src/components/Welcome.jsx +14 -0
- package/src/config.js +118 -2
- package/src/hooks/useLayout.js +15 -0
- package/src/hooks/useStore.js +6 -0
- package/src/prompt.js +67 -48
- package/src/store.js +99 -0
- package/src/theme.js +19 -0
- package/src/thinking.js +0 -24
- package/src/toolExecutors.js +521 -23
- package/src/tools.js +124 -4
- package/src/utils.js +32 -0
- package/tsconfig.json +10 -0
- package/index.js +0 -92
- package/src/ui.js +0 -269
package/src/tools.js
CHANGED
|
@@ -194,15 +194,109 @@ const toolDefs = [
|
|
|
194
194
|
{
|
|
195
195
|
type: 'function',
|
|
196
196
|
function: {
|
|
197
|
-
name: '
|
|
198
|
-
description: '
|
|
197
|
+
name: 'FilePickerMax',
|
|
198
|
+
description: 'Spawn a file-picker sub-agent that deeply explores the codebase to find files relevant to a prompt. It scans the full directory tree and previews every source file, then uses the most capable model to identify and rank the relevant files. Use this when you need to locate files related to a concept, feature, bug, or pattern. NEVER send generic prompts like "give me an overview of the codebase" — always specify the exact type of files you want.',
|
|
199
199
|
parameters: {
|
|
200
200
|
type: 'object',
|
|
201
201
|
properties: {
|
|
202
|
-
prompt: { type: 'string', description: '
|
|
202
|
+
prompt: { type: 'string', description: 'Specify the exact type of files you need. NEVER ask for a generic overview. Be specific — e.g. "show me the main entry point and routing files", "files that handle user authentication", "all React components related to the dashboard", "where database migrations are defined".' },
|
|
203
|
+
},
|
|
204
|
+
required: ['prompt'],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: 'function',
|
|
210
|
+
function: {
|
|
211
|
+
name: 'TodoList',
|
|
212
|
+
description: 'Manage a persistent todo list for tracking tasks. Supports adding, listing, completing, and removing items. The list is saved to .apex-todos.json in the project root.',
|
|
213
|
+
parameters: {
|
|
214
|
+
type: 'object',
|
|
215
|
+
properties: {
|
|
216
|
+
action: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
enum: ['add', 'list', 'done', 'remove', 'clear'],
|
|
219
|
+
description: 'Action to perform: "add" a new item, "list" all items, "done" to mark complete, "remove" to delete, "clear" to remove all completed.',
|
|
220
|
+
},
|
|
221
|
+
text: { type: 'string', description: 'Text for the todo item (required for "add").' },
|
|
222
|
+
index: { type: 'number', description: 'Item index (1-based, required for "done" and "remove").' },
|
|
223
|
+
},
|
|
224
|
+
required: ['action'],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: 'function',
|
|
230
|
+
function: {
|
|
231
|
+
name: 'Thinker',
|
|
232
|
+
description: 'Spawn a deep reasoning/planning sub-agent. It analyzes the problem, considers multiple approaches, and returns a structured plan. Use for complex tasks that benefit from careful planning before implementation.',
|
|
233
|
+
parameters: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: {
|
|
236
|
+
prompt: { type: 'string', description: 'The question or task to reason about deeply.' },
|
|
237
|
+
},
|
|
238
|
+
required: ['prompt'],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
type: 'function',
|
|
244
|
+
function: {
|
|
245
|
+
name: 'ThinkerBestOfN',
|
|
246
|
+
description: 'Spawn N parallel thinking agents that each independently reason about the same problem, then a selector picks the best response. Use for critical decisions that benefit from multiple perspectives. Only available in MAX mode.',
|
|
247
|
+
parameters: {
|
|
248
|
+
type: 'object',
|
|
249
|
+
properties: {
|
|
250
|
+
prompt: { type: 'string', description: 'The question or task to reason about from multiple angles.' },
|
|
251
|
+
n: { type: 'number', description: 'Number of parallel thinking passes (default: 3, max: 5).' },
|
|
252
|
+
},
|
|
253
|
+
required: ['prompt'],
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
type: 'function',
|
|
259
|
+
function: {
|
|
260
|
+
name: 'EditorMultiPrompt',
|
|
261
|
+
description: 'Spawn multiple editor agents in parallel, each with a different implementation strategy, then a selector picks the best result and applies it. Only available in MAX mode. Use for important code changes where you want to explore multiple approaches.',
|
|
262
|
+
parameters: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
prompt: { type: 'string', description: 'The coding task to implement.' },
|
|
266
|
+
strategies: {
|
|
267
|
+
type: 'array',
|
|
268
|
+
description: 'Array of 2-3 different implementation strategies to try in parallel.',
|
|
269
|
+
items: { type: 'string' },
|
|
270
|
+
},
|
|
203
271
|
files: {
|
|
204
272
|
type: 'array',
|
|
205
|
-
description: '
|
|
273
|
+
description: 'File paths and their contents that each editor will work with.',
|
|
274
|
+
items: {
|
|
275
|
+
type: 'object',
|
|
276
|
+
properties: {
|
|
277
|
+
path: { type: 'string', description: 'File path.' },
|
|
278
|
+
content: { type: 'string', description: 'Current file content.' },
|
|
279
|
+
},
|
|
280
|
+
required: ['path', 'content'],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
required: ['prompt', 'strategies', 'files'],
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
type: 'function',
|
|
290
|
+
function: {
|
|
291
|
+
name: 'CodeReviewMulti',
|
|
292
|
+
description: 'Spawn multiple code reviewers in parallel, each analyzing from a different perspective (correctness, security, performance, etc.). Only available in MAX mode.',
|
|
293
|
+
parameters: {
|
|
294
|
+
type: 'object',
|
|
295
|
+
properties: {
|
|
296
|
+
prompt: { type: 'string', description: 'Description of the changes to review.' },
|
|
297
|
+
perspectives: {
|
|
298
|
+
type: 'array',
|
|
299
|
+
description: 'Review perspectives, e.g. ["correctness and logic", "security vulnerabilities", "performance and efficiency"].',
|
|
206
300
|
items: { type: 'string' },
|
|
207
301
|
},
|
|
208
302
|
},
|
|
@@ -210,6 +304,32 @@ const toolDefs = [
|
|
|
210
304
|
},
|
|
211
305
|
},
|
|
212
306
|
},
|
|
307
|
+
{
|
|
308
|
+
type: 'function',
|
|
309
|
+
function: {
|
|
310
|
+
name: 'Commander',
|
|
311
|
+
description: 'Spawn a terminal command specialist agent that determines and executes the right shell commands for a task. It plans the commands, explains them, then executes them in sequence.',
|
|
312
|
+
parameters: {
|
|
313
|
+
type: 'object',
|
|
314
|
+
properties: {
|
|
315
|
+
prompt: { type: 'string', description: 'Description of what needs to be accomplished via terminal commands.' },
|
|
316
|
+
},
|
|
317
|
+
required: ['prompt'],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
type: 'function',
|
|
323
|
+
function: {
|
|
324
|
+
name: 'ContextPruner',
|
|
325
|
+
description: 'Summarize the current conversation history to free up context space. Automatically invoked in MAX mode but can be called manually. Replaces verbose conversation history with a concise summary preserving all critical information.',
|
|
326
|
+
parameters: {
|
|
327
|
+
type: 'object',
|
|
328
|
+
properties: {},
|
|
329
|
+
required: [],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
213
333
|
];
|
|
214
334
|
|
|
215
335
|
module.exports = { toolDefs };
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function toolDetailStr(name, args) {
|
|
4
|
+
if (!args) return '';
|
|
5
|
+
switch (name) {
|
|
6
|
+
case 'Bash': return args.command || '';
|
|
7
|
+
case 'Grep': return `"${args.pattern}"${args.path ? ` in ${args.path}` : ''}`;
|
|
8
|
+
case 'Glob': return args.pattern || '';
|
|
9
|
+
case 'ListDir': return args.path || '.';
|
|
10
|
+
case 'Read': {
|
|
11
|
+
let d = args.path || '';
|
|
12
|
+
if (args.start_line) d += `:${args.start_line}-${args.end_line || ''}`;
|
|
13
|
+
return d;
|
|
14
|
+
}
|
|
15
|
+
case 'Write': return args.path || '';
|
|
16
|
+
case 'Edit': return args.path || '';
|
|
17
|
+
case 'Patch': return `${args.path} (${(args.edits || []).length} edits)`;
|
|
18
|
+
case 'UndoEdit': return args.path || '';
|
|
19
|
+
case 'Task': return args.description || '';
|
|
20
|
+
case 'CodeReview': return 'reviewing changes';
|
|
21
|
+
case 'CodeReviewMulti': return `multi-review (${(args.perspectives || []).length} perspectives)`;
|
|
22
|
+
case 'FilePickerMax': return args.prompt ? args.prompt.slice(0, 40) : '';
|
|
23
|
+
case 'Thinker': return args.prompt ? args.prompt.slice(0, 40) : 'reasoning';
|
|
24
|
+
case 'ThinkerBestOfN': return `best-of-${args.n || 3}: ${(args.prompt || '').slice(0, 30)}`;
|
|
25
|
+
case 'EditorMultiPrompt': return `${(args.strategies || []).length} strategies`;
|
|
26
|
+
case 'Commander': return args.prompt ? args.prompt.slice(0, 40) : 'running commands';
|
|
27
|
+
case 'ContextPruner': return 'pruning context';
|
|
28
|
+
default: return JSON.stringify(args).slice(0, 60);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { toolDetailStr };
|
package/tsconfig.json
ADDED
package/index.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const { session } = require('./src/config');
|
|
5
|
-
const {
|
|
6
|
-
t,
|
|
7
|
-
indent,
|
|
8
|
-
showHeader,
|
|
9
|
-
showWelcome,
|
|
10
|
-
showPrompt,
|
|
11
|
-
showSessionSummary,
|
|
12
|
-
createPrompt,
|
|
13
|
-
} = require('./src/ui');
|
|
14
|
-
const { handleSlashCommand } = require('./src/commands');
|
|
15
|
-
const { handleUserInput, setRlClosed, getRlClosed, getIsProcessing } = require('./src/agent');
|
|
16
|
-
|
|
17
|
-
// ===== State =====
|
|
18
|
-
let rl;
|
|
19
|
-
let askQuestion;
|
|
20
|
-
|
|
21
|
-
// ===== Input Loop =====
|
|
22
|
-
function setupInputLoop() {
|
|
23
|
-
setRlClosed(false);
|
|
24
|
-
rl = createPrompt();
|
|
25
|
-
|
|
26
|
-
rl.on('close', () => {
|
|
27
|
-
setRlClosed(true);
|
|
28
|
-
if (getIsProcessing()) return;
|
|
29
|
-
// If stdin is still usable, re-create readline (e.g. terminal glitch)
|
|
30
|
-
if (!process.stdin.destroyed && process.stdin.readable) {
|
|
31
|
-
setupInputLoop();
|
|
32
|
-
askQuestion();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
console.log();
|
|
36
|
-
showSessionSummary();
|
|
37
|
-
console.log(indent(t.dim('Goodbye! ') + t.primary('✦')));
|
|
38
|
-
console.log();
|
|
39
|
-
process.exit(0);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ===== Main Loop =====
|
|
44
|
-
async function main() {
|
|
45
|
-
showHeader();
|
|
46
|
-
showWelcome();
|
|
47
|
-
|
|
48
|
-
setupInputLoop();
|
|
49
|
-
|
|
50
|
-
askQuestion = () => {
|
|
51
|
-
if (getRlClosed()) return;
|
|
52
|
-
process.stdin.resume();
|
|
53
|
-
const promptStr = showPrompt();
|
|
54
|
-
|
|
55
|
-
rl.question(promptStr, async (answer) => {
|
|
56
|
-
if (answer === null || answer === undefined) {
|
|
57
|
-
process.exit(0);
|
|
58
|
-
}
|
|
59
|
-
const input = answer.trim();
|
|
60
|
-
|
|
61
|
-
if (!input) { askQuestion(); return; }
|
|
62
|
-
|
|
63
|
-
// Slash commands
|
|
64
|
-
if (input.startsWith('/')) {
|
|
65
|
-
await handleSlashCommand(input, rl);
|
|
66
|
-
askQuestion();
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (input === 'exit' || input === 'quit') {
|
|
71
|
-
console.log();
|
|
72
|
-
showSessionSummary();
|
|
73
|
-
console.log(indent(t.dim('Goodbye! ') + t.primary('✦')));
|
|
74
|
-
console.log();
|
|
75
|
-
process.exit(0);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await handleUserInput(input, { setupInputLoop, askQuestion });
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.log(indent(t.red('✗ Unexpected error: ') + t.text(err.message)));
|
|
82
|
-
console.log();
|
|
83
|
-
}
|
|
84
|
-
askQuestion();
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
askQuestion();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ===== Run =====
|
|
92
|
-
main().catch(console.error);
|
package/src/ui.js
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const ora = require('ora');
|
|
5
|
-
const boxen = require('boxen');
|
|
6
|
-
const figures = require('figures');
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
const { highlight } = require('cli-highlight');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const { execSync } = require('child_process');
|
|
11
|
-
const { MAX_TOOL_ITERATIONS, PROJECT_ROOT, session, timestamp } = require('./config');
|
|
12
|
-
|
|
13
|
-
// ===== Theme (Amp-inspired) =====
|
|
14
|
-
const t = {
|
|
15
|
-
primary: chalk.hex('#6366f1'),
|
|
16
|
-
accent: chalk.hex('#818cf8'),
|
|
17
|
-
dim: chalk.dim,
|
|
18
|
-
muted: chalk.gray,
|
|
19
|
-
text: chalk.white,
|
|
20
|
-
bold: chalk.bold,
|
|
21
|
-
green: chalk.green,
|
|
22
|
-
yellow: chalk.yellow,
|
|
23
|
-
red: chalk.red,
|
|
24
|
-
blue: chalk.blue,
|
|
25
|
-
cyan: chalk.cyan,
|
|
26
|
-
italic: chalk.italic,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// ===== Layout =====
|
|
30
|
-
const COLS = Math.min(process.stdout.columns || 80, 100);
|
|
31
|
-
|
|
32
|
-
function hr() {
|
|
33
|
-
return t.dim('─'.repeat(COLS));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function stripAnsi(str) {
|
|
37
|
-
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function indent(str, n = 0) {
|
|
41
|
-
if (n === 0) return str;
|
|
42
|
-
const prefix = ' '.repeat(n);
|
|
43
|
-
return str.split('\n').map(l => prefix + l).join('\n');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ===== Header =====
|
|
47
|
-
function showHeader() {
|
|
48
|
-
console.clear();
|
|
49
|
-
let branch = '';
|
|
50
|
-
try { branch = execSync('git rev-parse --abbrev-ref HEAD 2>/dev/null', { encoding: 'utf-8', cwd: PROJECT_ROOT }).trim(); } catch {}
|
|
51
|
-
|
|
52
|
-
const name = t.primary.bold('⚡ Apex');
|
|
53
|
-
const branchStr = branch ? t.dim('on ') + t.text(branch) : '';
|
|
54
|
-
const cwd = t.dim(path.basename(PROJECT_ROOT));
|
|
55
|
-
|
|
56
|
-
console.log();
|
|
57
|
-
console.log(`${name} ${cwd} ${branchStr}`);
|
|
58
|
-
console.log(hr());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ===== Welcome =====
|
|
62
|
-
function showWelcome() {
|
|
63
|
-
console.log();
|
|
64
|
-
console.log(t.bold('How can I help?'));
|
|
65
|
-
console.log(t.dim(`Tools available · Max ${MAX_TOOL_ITERATIONS} iterations per turn`));
|
|
66
|
-
console.log();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ===== Messages =====
|
|
70
|
-
function showUserMessage(text) {
|
|
71
|
-
console.log();
|
|
72
|
-
console.log(t.blue.bold('You'));
|
|
73
|
-
console.log(text);
|
|
74
|
-
console.log();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function showAssistantHeader() {
|
|
78
|
-
console.log(t.primary.bold('Apex'));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function showAssistantMessage(text) {
|
|
82
|
-
const lines = text.split('\n');
|
|
83
|
-
const formatted = [];
|
|
84
|
-
let inCodeBlock = false;
|
|
85
|
-
let codeLines = [];
|
|
86
|
-
let codeLang = '';
|
|
87
|
-
|
|
88
|
-
for (const line of lines) {
|
|
89
|
-
if (line.startsWith('```') && !inCodeBlock) {
|
|
90
|
-
inCodeBlock = true;
|
|
91
|
-
codeLang = line.slice(3).trim() || 'code';
|
|
92
|
-
codeLines = [];
|
|
93
|
-
} else if (line.startsWith('```') && inCodeBlock) {
|
|
94
|
-
inCodeBlock = false;
|
|
95
|
-
formatted.push(renderCodeBlock(codeLines.join('\n'), codeLang));
|
|
96
|
-
} else if (inCodeBlock) {
|
|
97
|
-
codeLines.push(line);
|
|
98
|
-
} else {
|
|
99
|
-
const processed = line.replace(/`([^`]+)`/g, (_, code) => t.cyan(code));
|
|
100
|
-
formatted.push(processed);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
console.log(formatted.join('\n'));
|
|
105
|
-
console.log();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ===== Code Block =====
|
|
109
|
-
function renderCodeBlock(code, lang) {
|
|
110
|
-
const label = t.dim(`── ${lang} ──`);
|
|
111
|
-
let highlighted;
|
|
112
|
-
try {
|
|
113
|
-
highlighted = highlight(code, { language: lang, ignoreIllegals: true });
|
|
114
|
-
} catch {
|
|
115
|
-
highlighted = code;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const cLines = highlighted.split('\n').map((line, i) => {
|
|
119
|
-
return t.dim(String(i + 1).padStart(3) + ' │ ') + line;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
return `${label}\n${cLines.join('\n')}\n${t.dim('─'.repeat(Math.min(COLS, 60)))}`;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ===== Tool Display =====
|
|
126
|
-
function toolBadge(name) {
|
|
127
|
-
return t.dim('[') + t.accent(name) + t.dim(']');
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function toolDetail(name, args) {
|
|
131
|
-
switch (name) {
|
|
132
|
-
case 'Bash': return args.command || '';
|
|
133
|
-
case 'Grep': return `"${args.pattern}"${args.path ? ` in ${args.path}` : ''}`;
|
|
134
|
-
case 'Glob': return args.pattern || '';
|
|
135
|
-
case 'ListDir': return args.path || '.';
|
|
136
|
-
case 'Read': {
|
|
137
|
-
let d = args.path || '';
|
|
138
|
-
if (args.start_line) d += `:${args.start_line}-${args.end_line || ''}`;
|
|
139
|
-
return d;
|
|
140
|
-
}
|
|
141
|
-
case 'Write': return args.path || '';
|
|
142
|
-
case 'Edit': return args.path || '';
|
|
143
|
-
case 'Patch': return `${args.path} (${(args.edits || []).length} edits)`;
|
|
144
|
-
case 'UndoEdit': return args.path || '';
|
|
145
|
-
case 'Task': return args.description || '';
|
|
146
|
-
case 'CodeReview': {
|
|
147
|
-
const extra = (args.files || []).length;
|
|
148
|
-
const modCount = session.filesModified.size;
|
|
149
|
-
return `${modCount} modified${extra ? ` + ${extra} extra` : ''}`;
|
|
150
|
-
}
|
|
151
|
-
default: return JSON.stringify(args).slice(0, 60);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function showToolCall(name, detail, startTime) {
|
|
156
|
-
const truncDetail = detail.length > 60 ? detail.slice(0, 57) + '...' : detail;
|
|
157
|
-
const spinner = ora({
|
|
158
|
-
text: `${toolBadge(name)} ${t.dim(truncDetail)}`,
|
|
159
|
-
prefixText: ' ',
|
|
160
|
-
spinner: 'dots',
|
|
161
|
-
color: 'white',
|
|
162
|
-
}).start();
|
|
163
|
-
return spinner;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function finishToolCall(spinner, name, detail, startTime, success = true) {
|
|
167
|
-
const elapsed = Date.now() - startTime;
|
|
168
|
-
const icon = success ? t.green('✓') : t.red('✗');
|
|
169
|
-
const timeStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
|
|
170
|
-
const truncDetail = detail.length > 60 ? detail.slice(0, 57) + '...' : detail;
|
|
171
|
-
|
|
172
|
-
spinner.stopAndPersist({
|
|
173
|
-
symbol: icon,
|
|
174
|
-
text: `${toolBadge(name)} ${t.dim(truncDetail)} ${t.dim(timeStr)}`,
|
|
175
|
-
prefixText: ' ',
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ===== Diff =====
|
|
180
|
-
function showDiff(filename, diffText) {
|
|
181
|
-
console.log(indent(t.bold(path.basename(filename)), 4));
|
|
182
|
-
const lines = diffText.split('\n');
|
|
183
|
-
for (const line of lines) {
|
|
184
|
-
if (line.startsWith('+')) {
|
|
185
|
-
console.log(indent(t.green(line), 4));
|
|
186
|
-
} else if (line.startsWith('-')) {
|
|
187
|
-
console.log(indent(t.red(line), 4));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
console.log();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// ===== Prompt =====
|
|
195
|
-
function createPrompt() {
|
|
196
|
-
return readline.createInterface({
|
|
197
|
-
input: process.stdin,
|
|
198
|
-
output: process.stdout,
|
|
199
|
-
prompt: '',
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function showPrompt() {
|
|
204
|
-
console.log(t.dim(' Ctrl+C to cancel · /help for commands'));
|
|
205
|
-
return t.primary('❯ ');
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// ===== Session Summary =====
|
|
209
|
-
function showSessionSummary() {
|
|
210
|
-
const elapsed = ((Date.now() - session.startTime) / 1000 / 60).toFixed(1);
|
|
211
|
-
const parts = [
|
|
212
|
-
`${elapsed} min`,
|
|
213
|
-
`${session.turnCount} turns`,
|
|
214
|
-
`${session.toolCallCount} tool calls`,
|
|
215
|
-
`${session.totalTokens.toLocaleString()} tokens`,
|
|
216
|
-
`$${session.totalCost.toFixed(4)}`,
|
|
217
|
-
];
|
|
218
|
-
if (session.filesModified.size > 0) {
|
|
219
|
-
parts.push(`${session.filesModified.size} files modified`);
|
|
220
|
-
}
|
|
221
|
-
if (session.commandsRun.length > 0) {
|
|
222
|
-
parts.push(`${session.commandsRun.length} commands`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
console.log();
|
|
226
|
-
console.log(t.dim(' Session: ') + t.text(parts.join(' · ')));
|
|
227
|
-
console.log();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ===== Help =====
|
|
231
|
-
function showHelpMenu() {
|
|
232
|
-
console.log();
|
|
233
|
-
console.log(t.bold('Commands'));
|
|
234
|
-
console.log(t.dim(' /help ') + 'Show this menu');
|
|
235
|
-
console.log(t.dim(' /files ') + 'Show project file tree');
|
|
236
|
-
console.log(t.dim(' /clear ') + 'Clear conversation');
|
|
237
|
-
console.log(t.dim(' /cost ') + 'Show session stats');
|
|
238
|
-
console.log(t.dim(' /undo ') + 'Undo last edit');
|
|
239
|
-
console.log(t.dim(' /diff ') + 'Show git diff');
|
|
240
|
-
console.log(t.dim(' /git <cmd> ') + 'Run a git command');
|
|
241
|
-
console.log(t.dim(' /quit ') + 'Exit');
|
|
242
|
-
console.log();
|
|
243
|
-
console.log(t.bold('Tools'));
|
|
244
|
-
console.log(t.dim(' Read, Write, Edit, Patch, Bash, Grep, Glob, ListDir, UndoEdit, Task, CodeReview'));
|
|
245
|
-
console.log();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
module.exports = {
|
|
249
|
-
t,
|
|
250
|
-
COLS,
|
|
251
|
-
hr,
|
|
252
|
-
stripAnsi,
|
|
253
|
-
indent,
|
|
254
|
-
showHeader,
|
|
255
|
-
showWelcome,
|
|
256
|
-
showUserMessage,
|
|
257
|
-
showAssistantHeader,
|
|
258
|
-
showAssistantMessage,
|
|
259
|
-
renderCodeBlock,
|
|
260
|
-
toolBadge,
|
|
261
|
-
toolDetail,
|
|
262
|
-
showToolCall,
|
|
263
|
-
finishToolCall,
|
|
264
|
-
showDiff,
|
|
265
|
-
createPrompt,
|
|
266
|
-
showPrompt,
|
|
267
|
-
showSessionSummary,
|
|
268
|
-
showHelpMenu,
|
|
269
|
-
};
|