apex-dev 1.0.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/src/ui.js ADDED
@@ -0,0 +1,269 @@
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
+ };