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.
Files changed (75) hide show
  1. package/.local/share/amp/history.jsonl +32 -0
  2. package/.local/share/amp/session.json +2 -2
  3. package/.local/share/amp/threads/T-019c9769-4a8a-77b8-beab-f48973276f9a.json +1541 -0
  4. package/.local/share/amp/threads/T-019c9772-edac-7075-b26e-0ada1f8697d2.json +7 -0
  5. package/.local/share/amp/threads/T-019c97e8-a9ab-71a1-a8f9-109c540c98bf.json +111 -0
  6. package/.local/share/amp/threads/T-019c97e9-2277-753c-8c5d-df745fa6cfff.json +7 -0
  7. package/.local/share/amp/threads/T-019c97e9-f28e-758d-9663-e37047a8ed95.json +111 -0
  8. package/.local/share/amp/threads/T-019c97ea-17c7-77b8-92b2-f641c069bcc9.json +71 -0
  9. package/.local/share/amp/threads/T-019c97ea-44c6-75b8-88bc-d88113194f6a.json +1611 -0
  10. package/.local/share/amp/threads/T-019c97ec-abae-7251-a5f6-693adf496a1c.json +7 -0
  11. package/.local/share/amp/threads/T-019c97f5-8e61-73ad-8c5d-2637abedcde6.json +1341 -0
  12. package/.local/share/amp/threads/T-019c989d-4f4e-7249-bde0-21d19455ccae.json +163 -0
  13. package/.local/share/amp/threads/T-019c989d-9024-73c4-bee8-e2ae45028a39.json +124 -0
  14. package/.local/share/amp/threads/T-019c989e-1394-74ad-8234-ac573fcdb4c7.json +1260 -0
  15. package/.local/share/amp/threads/T-019c989f-e3dd-772e-85ac-525d0fc88fda.json +403 -0
  16. package/.local/share/amp/threads/T-019c98a1-7b0c-778a-b311-2e1cff85d710.json +3422 -0
  17. package/.local/share/amp/threads/T-019c98c5-4b7f-7284-99e9-88aa8c18ba66.json +1830 -0
  18. package/.local/share/amp/threads/T-019c98d0-f27f-76ec-be10-6df96f22be99.json +4061 -0
  19. package/.local/share/amp/threads/T-019c98f9-d031-704d-a0c2-f2f395f68f2b.json +509 -0
  20. package/.local/share/amp/threads/T-019c9919-f9ee-766c-90be-af7a07f6a4c6.json +2075 -0
  21. package/.local/share/amp/threads/T-019c991c-b98b-7158-9083-cc52408beb13.json +7 -0
  22. package/.local/share/amp/threads/T-019c991d-66d6-72aa-a9a1-105f7df0ea06.json +7 -0
  23. package/.local/share/amp/threads/T-019c9c2e-71a4-77ff-bd7f-b053da7f9000.json +1637 -0
  24. package/.local/share/amp/threads/T-019c9c45-27ca-728b-ba77-835115dfa9b2.json +3893 -0
  25. package/.local/share/amp/threads/T-019c9c48-45dc-736a-9752-e4119fe698f9.json +7 -0
  26. package/.local/share/amp/threads/T-019c9c4d-266b-72d0-b56e-74a5777e6583.json +7 -0
  27. package/.local/share/amp/threads/T-019c9c52-ab89-758f-9178-bda99c39d10b.json +7 -0
  28. package/.local/share/amp/threads/T-019c9c56-5715-72e2-b8b4-87711a842dd1.json +1799 -0
  29. package/.local/share/opencode/opencode.db +0 -0
  30. package/.local/share/opencode/opencode.db-shm +0 -0
  31. package/.local/share/opencode/opencode.db-wal +0 -0
  32. package/.local/share/opencode/storage/agent-usage-reminder/ses_36870ea98ffe8S5ZOCE4F11yFh.json +6 -0
  33. package/.local/share/opencode/storage/agent-usage-reminder/ses_3687a3e9affewUnHBzvpiPR6df.json +6 -0
  34. package/.local/share/opencode/storage/agent-usage-reminder/ses_36886e68dffeKVgUWf6lzXdEEt.json +6 -0
  35. package/.local/share/opencode/storage/session_diff/ses_36870ea98ffe8S5ZOCE4F11yFh.json +1 -0
  36. package/.local/share/opencode/storage/session_diff/ses_3687a3e9affewUnHBzvpiPR6df.json +1 -0
  37. package/.local/share/opencode/storage/session_diff/ses_36886e68dffeKVgUWf6lzXdEEt.json +1 -0
  38. package/.local/state/replit/log-query.db +0 -0
  39. package/.local/state/replit/log-query.db-shm +0 -0
  40. package/.local/state/replit/log-query.db-wal +0 -0
  41. package/.upm/store.json +1 -1
  42. package/AGENTS.md +32 -0
  43. package/bun.lock +137 -103
  44. package/index.jsx +24 -0
  45. package/package.json +12 -9
  46. package/src/agent.js +252 -169
  47. package/src/app.jsx +96 -0
  48. package/src/commands.js +66 -38
  49. package/src/components/AssistantMessage.jsx +83 -0
  50. package/src/components/ChatArea.jsx +84 -0
  51. package/src/components/DiffView.jsx +26 -0
  52. package/src/components/Divider.jsx +8 -0
  53. package/src/components/Header.jsx +44 -0
  54. package/src/components/HelpModal.jsx +81 -0
  55. package/src/components/InputBar.jsx +32 -0
  56. package/src/components/Spinner.jsx +23 -0
  57. package/src/components/StatusBar.jsx +44 -0
  58. package/src/components/SystemMessage.jsx +31 -0
  59. package/src/components/ThinkBlock.jsx +29 -0
  60. package/src/components/ToolCallItem.jsx +43 -0
  61. package/src/components/UserMessage.jsx +11 -0
  62. package/src/components/Welcome.jsx +14 -0
  63. package/src/config.js +118 -2
  64. package/src/hooks/useLayout.js +15 -0
  65. package/src/hooks/useStore.js +6 -0
  66. package/src/prompt.js +67 -48
  67. package/src/store.js +99 -0
  68. package/src/theme.js +19 -0
  69. package/src/thinking.js +0 -24
  70. package/src/toolExecutors.js +521 -23
  71. package/src/tools.js +124 -4
  72. package/src/utils.js +32 -0
  73. package/tsconfig.json +10 -0
  74. package/index.js +0 -92
  75. 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: 'CodeReview',
198
- 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.',
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: '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.' },
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: '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"].',
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
@@ -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
- };