apex-dev 2.0.0 → 3.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.
Files changed (77) hide show
  1. package/.local/share/amp/history.jsonl +33 -0
  2. package/.local/share/amp/session.json +3 -3
  3. package/.local/share/amp/threads/T-019c9761-858c-719b-911f-bc2e4c8cbdde.json +188 -0
  4. package/.local/share/amp/threads/T-019c9761-f5f3-7606-a900-ebe7f10d6e37.json +121 -0
  5. package/.local/share/amp/threads/T-019c9763-b1ae-729d-90aa-f59938ce912e.json +799 -0
  6. package/.local/share/amp/threads/T-019c9769-4a8a-77b8-beab-f48973276f9a.json +1541 -0
  7. package/.local/share/amp/threads/T-019c9772-edac-7075-b26e-0ada1f8697d2.json +7 -0
  8. package/.local/share/amp/threads/T-019c97e8-a9ab-71a1-a8f9-109c540c98bf.json +111 -0
  9. package/.local/share/amp/threads/T-019c97e9-2277-753c-8c5d-df745fa6cfff.json +7 -0
  10. package/.local/share/amp/threads/T-019c97e9-f28e-758d-9663-e37047a8ed95.json +111 -0
  11. package/.local/share/amp/threads/T-019c97ea-17c7-77b8-92b2-f641c069bcc9.json +71 -0
  12. package/.local/share/amp/threads/T-019c97ea-44c6-75b8-88bc-d88113194f6a.json +1611 -0
  13. package/.local/share/amp/threads/T-019c97ec-abae-7251-a5f6-693adf496a1c.json +7 -0
  14. package/.local/share/amp/threads/T-019c97f5-8e61-73ad-8c5d-2637abedcde6.json +1341 -0
  15. package/.local/share/amp/threads/T-019c989d-4f4e-7249-bde0-21d19455ccae.json +163 -0
  16. package/.local/share/amp/threads/T-019c989d-9024-73c4-bee8-e2ae45028a39.json +124 -0
  17. package/.local/share/amp/threads/T-019c989e-1394-74ad-8234-ac573fcdb4c7.json +1260 -0
  18. package/.local/share/amp/threads/T-019c989f-e3dd-772e-85ac-525d0fc88fda.json +403 -0
  19. package/.local/share/amp/threads/T-019c98a1-7b0c-778a-b311-2e1cff85d710.json +3422 -0
  20. package/.local/share/amp/threads/T-019c98c5-4b7f-7284-99e9-88aa8c18ba66.json +1830 -0
  21. package/.local/share/amp/threads/T-019c98d0-f27f-76ec-be10-6df96f22be99.json +4061 -0
  22. package/.local/share/amp/threads/T-019c98f9-d031-704d-a0c2-f2f395f68f2b.json +509 -0
  23. package/.local/share/amp/threads/T-019c9919-f9ee-766c-90be-af7a07f6a4c6.json +2075 -0
  24. package/.local/share/amp/threads/T-019c991c-b98b-7158-9083-cc52408beb13.json +7 -0
  25. package/.local/share/amp/threads/T-019c991d-66d6-72aa-a9a1-105f7df0ea06.json +7 -0
  26. package/.local/share/amp/threads/T-019c9c2e-71a4-77ff-bd7f-b053da7f9000.json +1637 -0
  27. package/.local/share/amp/threads/T-019c9c45-27ca-728b-ba77-835115dfa9b2.json +3893 -0
  28. package/.local/share/amp/threads/T-019c9c48-45dc-736a-9752-e4119fe698f9.json +7 -0
  29. package/.local/share/amp/threads/T-019c9c4d-266b-72d0-b56e-74a5777e6583.json +7 -0
  30. package/.local/share/amp/threads/T-019c9c52-ab89-758f-9178-bda99c39d10b.json +7 -0
  31. package/.local/share/opencode/opencode.db +0 -0
  32. package/.local/share/opencode/opencode.db-shm +0 -0
  33. package/.local/share/opencode/opencode.db-wal +0 -0
  34. package/.local/share/opencode/storage/agent-usage-reminder/ses_36870ea98ffe8S5ZOCE4F11yFh.json +6 -0
  35. package/.local/share/opencode/storage/agent-usage-reminder/ses_3687a3e9affewUnHBzvpiPR6df.json +6 -0
  36. package/.local/share/opencode/storage/agent-usage-reminder/ses_36886e68dffeKVgUWf6lzXdEEt.json +6 -0
  37. package/.local/share/opencode/storage/session_diff/ses_36870ea98ffe8S5ZOCE4F11yFh.json +1 -0
  38. package/.local/share/opencode/storage/session_diff/ses_3687a3e9affewUnHBzvpiPR6df.json +1 -0
  39. package/.local/share/opencode/storage/session_diff/ses_36886e68dffeKVgUWf6lzXdEEt.json +1 -0
  40. package/.local/state/replit/log-query.db +0 -0
  41. package/.local/state/replit/log-query.db-shm +0 -0
  42. package/.local/state/replit/log-query.db-wal +0 -0
  43. package/.upm/store.json +1 -1
  44. package/AGENTS.md +32 -0
  45. package/bun.lock +137 -103
  46. package/index.jsx +24 -0
  47. package/package.json +9 -9
  48. package/src/agent.js +252 -169
  49. package/src/app.jsx +96 -0
  50. package/src/commands.js +66 -38
  51. package/src/components/AssistantMessage.jsx +83 -0
  52. package/src/components/ChatArea.jsx +84 -0
  53. package/src/components/DiffView.jsx +26 -0
  54. package/src/components/Divider.jsx +8 -0
  55. package/src/components/Header.jsx +44 -0
  56. package/src/components/HelpModal.jsx +81 -0
  57. package/src/components/InputBar.jsx +32 -0
  58. package/src/components/Spinner.jsx +23 -0
  59. package/src/components/StatusBar.jsx +44 -0
  60. package/src/components/SystemMessage.jsx +31 -0
  61. package/src/components/ThinkBlock.jsx +29 -0
  62. package/src/components/ToolCallItem.jsx +43 -0
  63. package/src/components/UserMessage.jsx +11 -0
  64. package/src/components/Welcome.jsx +14 -0
  65. package/src/config.js +118 -2
  66. package/src/hooks/useLayout.js +15 -0
  67. package/src/hooks/useStore.js +6 -0
  68. package/src/prompt.js +67 -48
  69. package/src/store.js +99 -0
  70. package/src/theme.js +19 -0
  71. package/src/thinking.js +0 -24
  72. package/src/toolExecutors.js +580 -23
  73. package/src/tools.js +146 -4
  74. package/src/utils.js +32 -0
  75. package/tsconfig.json +10 -0
  76. package/index.js +0 -92
  77. package/src/ui.js +0 -269
package/src/tools.js CHANGED
@@ -172,15 +172,131 @@ const toolDefs = [
172
172
  {
173
173
  type: 'function',
174
174
  function: {
175
- name: 'CodeReview',
176
- description: 'MANDATORY — call this as the FINAL step after completing any code-writing or code-editing task. Spawns a code-review sub-agent that reviews ALL files you modified during this turn. Provide a summary of what you built/changed so the reviewer has context. The reviewer will analyse your changes for bugs, logic errors, style issues, and missed edge cases, then return its findings. If the reviewer finds problems, fix them before responding to the user.',
175
+ name: 'WebSearch',
176
+ description: 'Search the web using Exa AI. Returns relevant results with titles, URLs, and text snippets. Use this to find up-to-date information, documentation, or answers from the internet.',
177
177
  parameters: {
178
178
  type: 'object',
179
179
  properties: {
180
- prompt: { type: 'string', description: 'Summary of what you built or changed, so the reviewer understands the intent behind the code. Include the original user request and any key design decisions.' },
180
+ query: { type: 'string', description: 'The search query to execute.' },
181
+ num_results: { type: 'number', description: 'Number of results to return (default: 5, max: 10).' },
182
+ type: { type: 'string', description: 'Search type: "auto" (default), "neural", or "keyword".' },
183
+ include_domains: {
184
+ type: 'array',
185
+ description: 'Only return results from these domains, e.g. ["github.com", "stackoverflow.com"].',
186
+ items: { type: 'string' },
187
+ },
188
+ category: { type: 'string', description: 'Filter by category: "news", "research paper", "tweet", "company", "personal site", etc.' },
189
+ },
190
+ required: ['query'],
191
+ },
192
+ },
193
+ },
194
+ {
195
+ type: 'function',
196
+ function: {
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
+ parameters: {
200
+ type: 'object',
201
+ properties: {
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
+ },
181
271
  files: {
182
272
  type: 'array',
183
- description: 'Additional file paths to include beyond the auto-detected modified files (e.g. related files for context). Modified files are always included automatically.',
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"].',
184
300
  items: { type: 'string' },
185
301
  },
186
302
  },
@@ -188,6 +304,32 @@ const toolDefs = [
188
304
  },
189
305
  },
190
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
+ },
191
333
  ];
192
334
 
193
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
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react-jsx",
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "skipLibCheck": true,
8
+ "strict": false
9
+ }
10
+ }
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
- };