@axplusb/kepler 1.0.10 → 2.0.2
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/package.json +5 -2
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/components/layout/bottom-nav.tsx +2 -1
- package/pulse/components/layout/sidebar.tsx +2 -1
- package/src/context/retriever.mjs +42 -4
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/approval.mjs +154 -95
- package/src/core/backend-url.mjs +2 -2
- package/src/core/headless.mjs +5 -0
- package/src/core/risk-tier.mjs +245 -0
- package/src/core/stream-client.mjs +24 -1
- package/src/core/tool-executor.mjs +58 -5
- package/src/onboarding/preflight.mjs +292 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/terminal/ansi.mjs +44 -22
- package/src/terminal/repl.mjs +487 -133
- package/src/tools/project-overview.mjs +109 -16
- package/src/ui/approval.mjs +167 -0
- package/src/ui/banner.mjs +133 -122
- package/src/ui/dock.mjs +88 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/mission-report.mjs +264 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/status-bar.mjs +275 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/tool-card.mjs +322 -0
- package/src/ui/tool-details.mjs +277 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool detail formatters — Mission Control (PRD-055 §6.3).
|
|
3
|
+
*
|
|
4
|
+
* One function per high-value tool, all sharing the same signature so the
|
|
5
|
+
* expand handler can dispatch by `tool`:
|
|
6
|
+
*
|
|
7
|
+
* detailFor(card) → string // multi-line, ANSI-styled, no trailing \n
|
|
8
|
+
*
|
|
9
|
+
* Falls back to a generic dump when there's no dedicated formatter.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no I/O. The REPL writes the returned string to stderr.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { paint } from './palette.mjs';
|
|
15
|
+
import { icon, toolFamily } from './icons.mjs';
|
|
16
|
+
import { toolDisplayLabel } from '../terminal/tool-display.mjs';
|
|
17
|
+
|
|
18
|
+
const MAX_DETAIL_LINES = 60;
|
|
19
|
+
const MAX_LINE_WIDTH = 220;
|
|
20
|
+
|
|
21
|
+
// ── Dispatch ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export function detailFor(card) {
|
|
24
|
+
if (!card) return paint.text.dim(' (no card to expand)');
|
|
25
|
+
const { tool } = card;
|
|
26
|
+
|
|
27
|
+
const header = renderHeader(card);
|
|
28
|
+
const body = renderBody(card);
|
|
29
|
+
return body ? `${header}\n${body}` : header;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderBody(card) {
|
|
33
|
+
const { tool } = card;
|
|
34
|
+
switch (tool) {
|
|
35
|
+
case 'read_file': return detailReadFile(card);
|
|
36
|
+
case 'read_files': return detailReadFiles(card);
|
|
37
|
+
case 'search_code':
|
|
38
|
+
case 'search_files':
|
|
39
|
+
case 'grep': return detailSearch(card);
|
|
40
|
+
case 'list_files': return detailListFiles(card);
|
|
41
|
+
case 'edit_file': return detailEditFile(card);
|
|
42
|
+
case 'write_file': return detailWriteFile(card);
|
|
43
|
+
case 'write_project': return detailWriteProject(card);
|
|
44
|
+
case 'delete_file': return detailDeleteFile(card);
|
|
45
|
+
case 'shell': return detailShell(card);
|
|
46
|
+
case 'run_tests':
|
|
47
|
+
case 'validate_build':
|
|
48
|
+
case 'lint_check':
|
|
49
|
+
case 'validate_file':
|
|
50
|
+
case 'validate_structure': return detailValidator(card);
|
|
51
|
+
case 'plan': return detailPlan(card);
|
|
52
|
+
case 'explore':
|
|
53
|
+
case 'verify':
|
|
54
|
+
case 'debug':
|
|
55
|
+
case 'refactor':
|
|
56
|
+
case 'analyze_code': return detailGenericOutput(card);
|
|
57
|
+
default: return detailGenericOutput(card);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Header / framing ─────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
function renderHeader(card) {
|
|
64
|
+
const { tool, args, durationMs, result } = card;
|
|
65
|
+
const label = toolDisplayLabel(tool);
|
|
66
|
+
const fam = toolFamily(tool);
|
|
67
|
+
const accent = fam === 'write' ? paint.brand.primary
|
|
68
|
+
: fam === 'shell' ? paint.state.warn
|
|
69
|
+
: fam === 'subAgent' ? paint.brand.data
|
|
70
|
+
: paint.text.primary;
|
|
71
|
+
|
|
72
|
+
const lines = [
|
|
73
|
+
` ${paint.text.dim('━━')} ${icon(tool)} ${accent(label)} ${paint.text.dim('━━')}`,
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const args1 = oneLineArgs(tool, args);
|
|
77
|
+
if (args1) lines.push(` ${paint.text.dim('args ')} ${args1}`);
|
|
78
|
+
if (durationMs != null) {
|
|
79
|
+
lines.push(` ${paint.text.dim('time ')} ${paint.text.muted(formatDuration(durationMs))}`);
|
|
80
|
+
}
|
|
81
|
+
if (result?.success === false) {
|
|
82
|
+
lines.push(` ${paint.state.danger('error ')} ${result?.error || 'failed'}`);
|
|
83
|
+
}
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function oneLineArgs(tool, args) {
|
|
88
|
+
if (!args) return '';
|
|
89
|
+
try {
|
|
90
|
+
const compact = JSON.stringify(args);
|
|
91
|
+
if (compact.length <= 140) return paint.text.muted(compact);
|
|
92
|
+
return paint.text.muted(compact.slice(0, 137) + '…');
|
|
93
|
+
} catch {
|
|
94
|
+
return paint.text.muted(String(args));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Read ────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
function detailReadFile(card) {
|
|
101
|
+
const output = String(card.result?.output ?? card.result?.output_preview ?? '');
|
|
102
|
+
if (!output) return paint.text.dim(' (empty file)');
|
|
103
|
+
const startLine = Number(card.args?.start_line) || 1;
|
|
104
|
+
return numbered(output, startLine);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function detailReadFiles(card) {
|
|
108
|
+
const output = String(card.result?.output ?? '');
|
|
109
|
+
return clip(output);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Search / list ───────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
function detailSearch(card) {
|
|
115
|
+
const output = String(card.result?.output ?? '');
|
|
116
|
+
if (!output.trim()) return paint.text.dim(' (no matches)');
|
|
117
|
+
|
|
118
|
+
const grouped = groupSearchByFile(output);
|
|
119
|
+
if (!grouped) return clip(output);
|
|
120
|
+
|
|
121
|
+
const out = [];
|
|
122
|
+
let totalLines = 0;
|
|
123
|
+
for (const [file, hits] of grouped) {
|
|
124
|
+
if (totalLines > MAX_DETAIL_LINES) {
|
|
125
|
+
out.push(paint.text.dim(` … ${grouped.size - out.length / 2} more file(s)`));
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
out.push(` ${paint.brand.data(file)}`);
|
|
129
|
+
for (const hit of hits.slice(0, 8)) {
|
|
130
|
+
out.push(` ${paint.text.dim(hit.line + ':')} ${paint.text.primary(hit.text)}`);
|
|
131
|
+
totalLines++;
|
|
132
|
+
}
|
|
133
|
+
if (hits.length > 8) {
|
|
134
|
+
out.push(paint.text.dim(` … ${hits.length - 8} more match(es)`));
|
|
135
|
+
}
|
|
136
|
+
totalLines += 1;
|
|
137
|
+
}
|
|
138
|
+
return out.join('\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function groupSearchByFile(output) {
|
|
142
|
+
const groups = new Map();
|
|
143
|
+
let any = false;
|
|
144
|
+
for (const line of output.split('\n')) {
|
|
145
|
+
const m = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
146
|
+
if (!m) continue;
|
|
147
|
+
any = true;
|
|
148
|
+
const [, file, ln, text] = m;
|
|
149
|
+
if (!groups.has(file)) groups.set(file, []);
|
|
150
|
+
groups.get(file).push({ line: ln, text: text.trim().slice(0, MAX_LINE_WIDTH) });
|
|
151
|
+
}
|
|
152
|
+
return any ? groups : null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function detailListFiles(card) {
|
|
156
|
+
const output = String(card.result?.output ?? '');
|
|
157
|
+
if (!output.trim()) return paint.text.dim(' (empty)');
|
|
158
|
+
const lines = output.split('\n').filter(Boolean).slice(0, MAX_DETAIL_LINES);
|
|
159
|
+
return lines.map(l => ` ${paint.text.primary(l)}`).join('\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Write / edit ────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
function detailEditFile(card) {
|
|
165
|
+
const diff = card.result?.diff || card.result?.patch || card.result?.output;
|
|
166
|
+
if (diff) return renderDiff(String(diff));
|
|
167
|
+
|
|
168
|
+
const before = card.args?.search;
|
|
169
|
+
const after = card.args?.replace;
|
|
170
|
+
if (before != null && after != null) {
|
|
171
|
+
return [
|
|
172
|
+
` ${paint.state.danger('- ' + String(before).split('\n')[0].slice(0, 160))}`,
|
|
173
|
+
` ${paint.state.success('+ ' + String(after).split('\n')[0].slice(0, 160))}`,
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
return paint.text.dim(' (edit applied, no diff returned)');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function detailWriteFile(card) {
|
|
180
|
+
const content = card.args?.content;
|
|
181
|
+
if (!content) return paint.text.dim(' (no content)');
|
|
182
|
+
return numbered(String(content), 1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function detailWriteProject(card) {
|
|
186
|
+
const files = card.args?.files || [];
|
|
187
|
+
if (!files.length) return paint.text.dim(' (no files)');
|
|
188
|
+
return files.slice(0, 30).map(f => {
|
|
189
|
+
const p = f.path || f.file_path || '';
|
|
190
|
+
const lines = typeof f.content === 'string' ? f.content.split('\n').length : '?';
|
|
191
|
+
return ` ${paint.brand.primary(p)} ${paint.text.dim(`(${lines} lines)`)}`;
|
|
192
|
+
}).join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function detailDeleteFile(card) {
|
|
196
|
+
const p = card.args?.file_path || card.args?.path || '';
|
|
197
|
+
return ` ${paint.state.danger('✗')} ${paint.text.primary(p)}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Shell / validators ──────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
function detailShell(card) {
|
|
203
|
+
const stdout = String(card.result?.stdout ?? card.result?.output ?? '');
|
|
204
|
+
const stderr = String(card.result?.stderr ?? '');
|
|
205
|
+
const out = [];
|
|
206
|
+
if (stdout) {
|
|
207
|
+
out.push(paint.text.dim(' stdout'));
|
|
208
|
+
out.push(clip(stdout));
|
|
209
|
+
}
|
|
210
|
+
if (stderr) {
|
|
211
|
+
if (out.length) out.push('');
|
|
212
|
+
out.push(paint.state.warn(' stderr'));
|
|
213
|
+
out.push(clip(stderr, paint.state.danger));
|
|
214
|
+
}
|
|
215
|
+
return out.length ? out.join('\n') : paint.text.dim(' (no output)');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function detailValidator(card) {
|
|
219
|
+
return detailShell(card);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Sub-agent output ────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
function detailPlan(card) {
|
|
225
|
+
const text = String(card.result?.output ?? card.result?.plan ?? '');
|
|
226
|
+
if (!text.trim()) return paint.text.dim(' (no plan)');
|
|
227
|
+
// Number list items, highlight headers.
|
|
228
|
+
return text.split('\n').slice(0, MAX_DETAIL_LINES).map(line => {
|
|
229
|
+
if (/^\s*\d+\./.test(line)) return ` ${paint.brand.accent(line.trim())}`;
|
|
230
|
+
if (/^#+\s/.test(line)) return ` ${paint.bold(paint.brand.primary(line.trim()))}`;
|
|
231
|
+
return ` ${paint.text.primary(line)}`;
|
|
232
|
+
}).join('\n');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function detailGenericOutput(card) {
|
|
236
|
+
const out = String(card.result?.output ?? card.result?.output_preview ?? '');
|
|
237
|
+
return clip(out);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
function numbered(text, start) {
|
|
243
|
+
const lines = String(text).split('\n');
|
|
244
|
+
const total = lines.length;
|
|
245
|
+
const width = String(start + total - 1).length;
|
|
246
|
+
return lines.slice(0, MAX_DETAIL_LINES).map((line, i) => {
|
|
247
|
+
const n = String(start + i).padStart(width);
|
|
248
|
+
return ` ${paint.text.dim(n)} ${paint.text.primary(line.slice(0, MAX_LINE_WIDTH))}`;
|
|
249
|
+
}).join('\n') + (total > MAX_DETAIL_LINES
|
|
250
|
+
? `\n ${paint.text.dim(`… ${total - MAX_DETAIL_LINES} more line(s)`)}`
|
|
251
|
+
: '');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function clip(text, painter = paint.text.primary) {
|
|
255
|
+
if (!text) return paint.text.dim(' (empty)');
|
|
256
|
+
const lines = String(text).split('\n');
|
|
257
|
+
const head = lines.slice(0, MAX_DETAIL_LINES).map(l => ` ${painter(l.slice(0, MAX_LINE_WIDTH))}`);
|
|
258
|
+
if (lines.length > MAX_DETAIL_LINES) {
|
|
259
|
+
head.push(` ${paint.text.dim(`… ${lines.length - MAX_DETAIL_LINES} more line(s)`)}`);
|
|
260
|
+
}
|
|
261
|
+
return head.join('\n');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function renderDiff(text) {
|
|
265
|
+
return text.split('\n').slice(0, MAX_DETAIL_LINES).map(line => {
|
|
266
|
+
if (line.startsWith('+++') || line.startsWith('---')) return ` ${paint.bold(paint.text.muted(line))}`;
|
|
267
|
+
if (line.startsWith('@@')) return ` ${paint.brand.data(line)}`;
|
|
268
|
+
if (line.startsWith('+')) return ` ${paint.state.success(line)}`;
|
|
269
|
+
if (line.startsWith('-')) return ` ${paint.state.danger(line)}`;
|
|
270
|
+
return ` ${paint.text.dim(line)}`;
|
|
271
|
+
}).join('\n');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function formatDuration(ms) {
|
|
275
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
276
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
277
|
+
}
|