@bahulam/code 0.1.6 → 0.1.8
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/LICENSE +1 -1
- package/NOTICE +6 -6
- package/README.md +11 -18
- package/package.json +11 -11
- package/src/agents/loader.mjs +1 -1
- package/src/agents/workflow_loader.mjs +2 -2
- package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +4 -3
- package/src/commands/agent.mjs +3 -3
- package/src/commands/device.mjs +2 -2
- package/src/commands/pair.mjs +2 -2
- package/src/commands/remote.mjs +3 -3
- package/src/commands/workflow.mjs +5 -5
- package/src/config/env.mjs +9 -2
- package/src/config/memory-loader.mjs +1 -1
- package/src/config/model-catalog-default.json +4 -3
- package/src/config/model-catalog.mjs +2 -0
- package/src/config/settings.mjs +2 -2
- package/src/context/prose-chunker.mjs +162 -5
- package/src/core/agent-loop.mjs +17 -0
- package/src/core/attachments.mjs +66 -4
- package/src/core/backend-url.mjs +11 -12
- package/src/core/bundled-runtime.mjs +1 -1
- package/src/core/callback-client.mjs +1 -1
- package/src/core/headless.mjs +18 -4
- package/src/core/jsonl-writer.mjs +18 -18
- package/src/core/lint-resolver.mjs +472 -0
- package/src/core/local-agent.mjs +40 -8
- package/src/core/local-store.mjs +5 -1
- package/src/core/mode-selector.mjs +1 -1
- package/src/core/output-filter.mjs +22 -26
- package/src/core/pricing.mjs +10 -3
- package/src/core/project-artifacts.mjs +3 -3
- package/src/core/project-context-loader.mjs +9 -9
- package/src/core/settings-sync.mjs +1 -1
- package/src/core/stagnation.mjs +61 -1
- package/src/core/stream-client.mjs +13 -8
- package/src/core/system-prompt.mjs +2 -2
- package/src/core/tool-executor.mjs +178 -84
- package/src/daemon/http-dashboard.mjs +400 -0
- package/src/daemon/session-core.mjs +1 -0
- package/src/local-service/agent-relay.mjs +868 -0
- package/src/local-service/approval-bridge.mjs +222 -0
- package/src/local-service/browser.mjs +24 -0
- package/src/local-service/command.mjs +155 -0
- package/src/local-service/file-access.mjs +393 -0
- package/src/local-service/machine.mjs +86 -0
- package/src/local-service/paths.mjs +29 -0
- package/src/local-service/preview-converters.mjs +260 -0
- package/src/local-service/server.mjs +2644 -0
- package/src/local-service/session-store.mjs +221 -0
- package/src/mcp/client.mjs +4 -4
- package/src/onboarding/preflight.mjs +2 -2
- package/src/terminal/agents.mjs +2 -2
- package/src/terminal/analytics.mjs +2 -6
- package/src/terminal/ansi.mjs +11 -3
- package/src/terminal/main.mjs +43 -17
- package/src/terminal/model-catalog-display.mjs +111 -0
- package/src/terminal/repl-format.mjs +4 -1
- package/src/terminal/repl-model-form.mjs +81 -14
- package/src/terminal/repl-render.mjs +37 -7
- package/src/terminal/repl-resume.mjs +2 -2
- package/src/terminal/repl-state.mjs +12 -0
- package/src/terminal/repl.mjs +248 -70
- package/src/terminal/tool-display.mjs +32 -0
- package/src/terminal/watch-state.mjs +118 -0
- package/src/tools/agent.mjs +2 -2
- package/src/tools/analyze-image.mjs +158 -0
- package/src/tools/bash.mjs +11 -9
- package/src/tools/generate-image.mjs +411 -0
- package/src/tools/lint.mjs +24 -43
- package/src/tools/project-overview.mjs +12 -12
- package/src/tools/registry.mjs +4 -0
- package/src/ui/formatter.mjs +2 -2
- package/src/ui/icons.mjs +2 -0
- package/src/ui/input-dock.mjs +7 -7
- package/src/ui/palette.mjs +1 -1
- package/src/ui/slash-commands.mjs +4 -6
- package/src/ui/spinner.mjs +1 -1
- package/src/ui/term.mjs +2 -6
- package/src/ui/tool-card.mjs +81 -6
- package/pulse/app/activity/page.tsx +0 -190
- package/pulse/app/api/activity/route.ts +0 -138
- package/pulse/app/api/benchmark/route.ts +0 -113
- package/pulse/app/api/benchmarks/route.ts +0 -195
- package/pulse/app/api/costs/route.ts +0 -88
- package/pulse/app/api/export/route.ts +0 -77
- package/pulse/app/api/history/route.ts +0 -11
- package/pulse/app/api/import/route.ts +0 -31
- package/pulse/app/api/memory/route.ts +0 -50
- package/pulse/app/api/plans/route.ts +0 -9
- package/pulse/app/api/projects/[slug]/route.ts +0 -96
- package/pulse/app/api/projects/route.ts +0 -121
- package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
- package/pulse/app/api/sessions/[id]/route.ts +0 -31
- package/pulse/app/api/sessions/route.ts +0 -112
- package/pulse/app/api/settings/route.ts +0 -14
- package/pulse/app/api/stats/route.ts +0 -143
- package/pulse/app/api/todos/route.ts +0 -9
- package/pulse/app/api/tools/route.ts +0 -160
- package/pulse/app/benchmarks/page.tsx +0 -224
- package/pulse/app/costs/page.tsx +0 -179
- package/pulse/app/export/page.tsx +0 -465
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +0 -263
- package/pulse/app/help/page.tsx +0 -143
- package/pulse/app/history/page.tsx +0 -157
- package/pulse/app/layout.tsx +0 -46
- package/pulse/app/memory/page.tsx +0 -365
- package/pulse/app/overview-client.tsx +0 -393
- package/pulse/app/page.tsx +0 -14
- package/pulse/app/plans/page.tsx +0 -308
- package/pulse/app/projects/[slug]/page.tsx +0 -390
- package/pulse/app/projects/page.tsx +0 -110
- package/pulse/app/sessions/[id]/page.tsx +0 -243
- package/pulse/app/sessions/page.tsx +0 -39
- package/pulse/app/settings/page.tsx +0 -188
- package/pulse/app/todos/page.tsx +0 -211
- package/pulse/app/tools/page.tsx +0 -249
- package/pulse/cli.js +0 -164
- package/pulse/components/activity/day-of-week-chart.tsx +0 -35
- package/pulse/components/activity/streak-card.tsx +0 -36
- package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
- package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
- package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
- package/pulse/components/costs/model-token-table.tsx +0 -60
- package/pulse/components/global-search.tsx +0 -193
- package/pulse/components/keyboard-nav-provider.tsx +0 -23
- package/pulse/components/layout/bottom-nav.tsx +0 -53
- package/pulse/components/layout/client-layout.tsx +0 -31
- package/pulse/components/layout/sidebar-context.tsx +0 -50
- package/pulse/components/layout/sidebar.tsx +0 -183
- package/pulse/components/layout/top-bar.tsx +0 -121
- package/pulse/components/overview/activity-heatmap.tsx +0 -107
- package/pulse/components/overview/conversation-table.tsx +0 -148
- package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
- package/pulse/components/overview/peak-hours-chart.tsx +0 -87
- package/pulse/components/overview/project-activity-donut.tsx +0 -96
- package/pulse/components/overview/stat-card.tsx +0 -102
- package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
- package/pulse/components/projects/project-card.tsx +0 -175
- package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
- package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
- package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
- package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
- package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
- package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
- package/pulse/components/sessions/session-badges.tsx +0 -49
- package/pulse/components/sessions/session-table.tsx +0 -299
- package/pulse/components/theme-provider.tsx +0 -44
- package/pulse/components/tools/feature-adoption-table.tsx +0 -58
- package/pulse/components/tools/mcp-server-panel.tsx +0 -45
- package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
- package/pulse/components/tools/version-history-table.tsx +0 -32
- package/pulse/components/ui/alert.tsx +0 -66
- package/pulse/components/ui/badge.tsx +0 -48
- package/pulse/components/ui/breadcrumb.tsx +0 -109
- package/pulse/components/ui/button.tsx +0 -64
- package/pulse/components/ui/calendar.tsx +0 -220
- package/pulse/components/ui/card.tsx +0 -92
- package/pulse/components/ui/command.tsx +0 -158
- package/pulse/components/ui/dialog.tsx +0 -158
- package/pulse/components/ui/input.tsx +0 -21
- package/pulse/components/ui/popover.tsx +0 -89
- package/pulse/components/ui/progress.tsx +0 -31
- package/pulse/components/ui/select.tsx +0 -190
- package/pulse/components/ui/separator.tsx +0 -28
- package/pulse/components/ui/sheet.tsx +0 -143
- package/pulse/components/ui/skeleton.tsx +0 -13
- package/pulse/components/ui/table.tsx +0 -116
- package/pulse/components/ui/tabs.tsx +0 -91
- package/pulse/components/ui/tooltip.tsx +0 -57
- package/pulse/components/use-global-keyboard-nav.ts +0 -79
- package/pulse/components.json +0 -23
- package/pulse/eslint.config.mjs +0 -18
- package/pulse/lib/bahulam-paths.ts +0 -23
- package/pulse/lib/claude-reader.ts +0 -592
- package/pulse/lib/decode.ts +0 -129
- package/pulse/lib/pricing.ts +0 -102
- package/pulse/lib/replay-parser.ts +0 -165
- package/pulse/lib/tool-categories.ts +0 -140
- package/pulse/lib/utils.ts +0 -6
- package/pulse/next-env.d.ts +0 -6
- package/pulse/next.config.ts +0 -16
- package/pulse/package.json +0 -45
- package/pulse/postcss.config.mjs +0 -7
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +0 -1
- package/pulse/public/globe.svg +0 -1
- package/pulse/public/next.svg +0 -1
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +0 -1
- package/pulse/public/window.svg +0 -1
- package/pulse/tsconfig.json +0 -34
- package/pulse/types/claude.ts +0 -294
|
@@ -52,12 +52,12 @@ export const COMMANDS = {
|
|
|
52
52
|
'/review': 'Code review agent',
|
|
53
53
|
'/architect': 'Feature architect agent',
|
|
54
54
|
'/safety': 'Show safety guardrail status',
|
|
55
|
-
'/auto': 'Session autopilot: auto
|
|
56
|
-
'/approvals': 'List or edit session approval grants',
|
|
57
|
-
'/revoke': 'Revoke auto-approvals',
|
|
55
|
+
'/auto': 'Session autopilot: `/auto` routine-only · `/auto full` everything · `/auto off`',
|
|
56
|
+
'/approvals': 'List or edit session approval grants (`/approvals clear` revokes all)',
|
|
58
57
|
'/resume': 'Resume a previous session',
|
|
59
58
|
'/sessions': 'List resumable sessions',
|
|
60
59
|
'/logout': 'Sign out and clear credentials',
|
|
60
|
+
'/watch': 'Toggle live agent activity tracking panel',
|
|
61
61
|
'/exit': 'Exit CLI',
|
|
62
62
|
};
|
|
63
63
|
|
|
@@ -118,7 +118,6 @@ export const HELP_GROUPS = [
|
|
|
118
118
|
['/settings logout', 'Sign out'],
|
|
119
119
|
['/settings whoami', 'Current user'],
|
|
120
120
|
['/settings quiet|verbose|surgical', 'Verbosity'],
|
|
121
|
-
['/settings revoke', 'Revoke auto-approvals'],
|
|
122
121
|
],
|
|
123
122
|
},
|
|
124
123
|
{
|
|
@@ -138,6 +137,7 @@ export const HELP_GROUPS = [
|
|
|
138
137
|
title: 'Agents',
|
|
139
138
|
summary: 'specialist modes',
|
|
140
139
|
commands: [
|
|
140
|
+
['/watch', 'Toggle live agent tracking panel'],
|
|
141
141
|
['/agents', 'List built-in and local agents'],
|
|
142
142
|
['/agents create <name>', 'Create .bahulam/agents/<name>.yaml'],
|
|
143
143
|
['/agents edit <name>', 'Open local agent YAML'],
|
|
@@ -207,7 +207,6 @@ export const LEGACY_COMMAND_HINTS = {
|
|
|
207
207
|
'/quiet': '/settings quiet',
|
|
208
208
|
'/verbose': '/settings verbose',
|
|
209
209
|
'/surgical': '/settings surgical',
|
|
210
|
-
'/revoke': '/settings revoke',
|
|
211
210
|
};
|
|
212
211
|
|
|
213
212
|
// Namespaced subcommands. `/status metrics` -> `/stats`, etc.
|
|
@@ -235,7 +234,6 @@ export const NAMESPACED_COMMANDS = {
|
|
|
235
234
|
quiet: '/quiet',
|
|
236
235
|
verbose: '/verbose',
|
|
237
236
|
surgical: '/surgical',
|
|
238
|
-
revoke: '/revoke',
|
|
239
237
|
},
|
|
240
238
|
};
|
|
241
239
|
|
package/src/ui/spinner.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Behavior:
|
|
18
18
|
* - 120ms per frame.
|
|
19
|
-
* - Suppressed entirely when stdout is not a TTY or when
|
|
19
|
+
* - Suppressed entirely when stdout is not a TTY or when BAHULAM_PLAIN=1.
|
|
20
20
|
* The `start`/`stop` API is still safe to call (no-op).
|
|
21
21
|
* - Color follows the current orbit (orchestrated by the caller via the
|
|
22
22
|
* palette token). Default: brand.primary.
|
package/src/ui/term.mjs
CHANGED
|
@@ -34,9 +34,7 @@ function readEnv() {
|
|
|
34
34
|
NO_COLOR: env.NO_COLOR,
|
|
35
35
|
FORCE_COLOR: env.FORCE_COLOR,
|
|
36
36
|
BAHULAM_PLAIN: env.BAHULAM_PLAIN,
|
|
37
|
-
KEPLER_PLAIN: env.KEPLER_PLAIN,
|
|
38
37
|
BAHULAM_TTY_MODE: env.BAHULAM_TTY_MODE,
|
|
39
|
-
KEPLER_TTY_MODE: env.KEPLER_TTY_MODE,
|
|
40
38
|
COLORTERM: (env.COLORTERM || '').toLowerCase(),
|
|
41
39
|
TERM: (env.TERM || '').toLowerCase(),
|
|
42
40
|
TERM_PROGRAM: env.TERM_PROGRAM || '',
|
|
@@ -48,7 +46,6 @@ function detectColorLevel(env, isTTY) {
|
|
|
48
46
|
// Hard opt-out (https://no-color.org). Honored even on TTYs.
|
|
49
47
|
if (env.NO_COLOR !== undefined && env.NO_COLOR !== '') return 'none';
|
|
50
48
|
if (env.BAHULAM_PLAIN === '1') return 'none';
|
|
51
|
-
if (env.KEPLER_PLAIN === '1') return 'none';
|
|
52
49
|
|
|
53
50
|
// Hard opt-in. FORCE_COLOR=1|2|3 maps to ansi16|ansi256|truecolor.
|
|
54
51
|
// FORCE_COLOR with no value or =true falls through to detection.
|
|
@@ -84,7 +81,6 @@ function detectColorLevel(env, isTTY) {
|
|
|
84
81
|
|
|
85
82
|
function detectUnicode(env) {
|
|
86
83
|
if (env.BAHULAM_PLAIN === '1') return false;
|
|
87
|
-
if (env.KEPLER_PLAIN === '1') return false;
|
|
88
84
|
// Most modern terminals on macOS/Linux handle UTF-8.
|
|
89
85
|
// Windows ConEmu / older terminals are the main holdouts; conservative
|
|
90
86
|
// fallback when LANG and LC_* are missing or explicitly POSIX.
|
|
@@ -95,8 +91,8 @@ function detectUnicode(env) {
|
|
|
95
91
|
}
|
|
96
92
|
|
|
97
93
|
function detectTtyMode(env) {
|
|
98
|
-
const raw = String(env.BAHULAM_TTY_MODE ||
|
|
99
|
-
if (env.BAHULAM_PLAIN === '1'
|
|
94
|
+
const raw = String(env.BAHULAM_TTY_MODE || '').trim().toLowerCase();
|
|
95
|
+
if (env.BAHULAM_PLAIN === '1') return 'plain';
|
|
100
96
|
if (raw === 'stable' || raw === 'simple' || raw === 'static' || raw === 'transcript') return 'stable';
|
|
101
97
|
return 'rich';
|
|
102
98
|
}
|
package/src/ui/tool-card.mjs
CHANGED
|
@@ -78,12 +78,15 @@ const paintShellAdapter = {
|
|
|
78
78
|
*
|
|
79
79
|
* @returns {{ text: string, tone: 'success'|'warn'|'danger'|'dim' }}
|
|
80
80
|
*/
|
|
81
|
-
export function summarizeResult(tool, data) {
|
|
81
|
+
export function summarizeResult(tool, data, args = {}) {
|
|
82
82
|
if (!data) return { text: '', tone: 'dim' };
|
|
83
83
|
|
|
84
84
|
if (data._blocked) {
|
|
85
85
|
return { text: firstOutputLine(data) || 'blocked', tone: 'danger' };
|
|
86
86
|
}
|
|
87
|
+
if (isNoChangeEditResult(tool, data)) {
|
|
88
|
+
return { text: 'no changes', tone: 'warn' };
|
|
89
|
+
}
|
|
87
90
|
if (data._observation_timeout) {
|
|
88
91
|
const ms = data._observation_timeout_ms;
|
|
89
92
|
const duration = typeof ms === 'number' ? formatDuration(ms) : '';
|
|
@@ -111,7 +114,8 @@ export function summarizeResult(tool, data) {
|
|
|
111
114
|
|
|
112
115
|
switch (tool) {
|
|
113
116
|
case 'read_file': {
|
|
114
|
-
const lines =
|
|
117
|
+
const lines = readFileLineCount(data, args);
|
|
118
|
+
if (lines == null) return { text: 'read', tone: 'success' };
|
|
115
119
|
return { text: `${lines} line${lines === 1 ? '' : 's'}`, tone: 'success' };
|
|
116
120
|
}
|
|
117
121
|
case 'read_files':
|
|
@@ -136,6 +140,9 @@ export function summarizeResult(tool, data) {
|
|
|
136
140
|
case 'write_file':
|
|
137
141
|
case 'write_project': {
|
|
138
142
|
const delta = diffDelta(data);
|
|
143
|
+
if (tool === 'edit_file' && delta === '+0 −0') {
|
|
144
|
+
return { text: 'no changes', tone: 'warn' };
|
|
145
|
+
}
|
|
139
146
|
if (delta) return { text: delta, tone: 'success' };
|
|
140
147
|
return { text: 'updated', tone: 'success' };
|
|
141
148
|
}
|
|
@@ -159,7 +166,7 @@ export function summarizeResult(tool, data) {
|
|
|
159
166
|
// Multi-row preview: first N rows + a "+ M more" tail so long
|
|
160
167
|
// outputs (e.g. `ls`) surface their scale instead of collapsing
|
|
161
168
|
// to a single first line with no hint that more exists.
|
|
162
|
-
const { preview, remaining } = outputPreviewRows(data, shellPreviewRows());
|
|
169
|
+
const { preview, remaining } = outputPreviewRows(data, shellPreviewRows(data));
|
|
163
170
|
if (!preview) return { text: 'ok', tone: 'success' };
|
|
164
171
|
if (remaining === 0) return { text: preview, tone: 'success' };
|
|
165
172
|
const tail = paint.text.dim(`+ ${remaining} more row${remaining === 1 ? '' : 's'}`);
|
|
@@ -434,9 +441,13 @@ function outputPreviewRows(data, n, perRow = 200) {
|
|
|
434
441
|
// Default rows shown in the shell result preview. Overridable via env for
|
|
435
442
|
// power users; keep it small — the result line rides above every command
|
|
436
443
|
// and eats vertical space in a long session.
|
|
437
|
-
function shellPreviewRows() {
|
|
444
|
+
function shellPreviewRows(data = {}) {
|
|
438
445
|
const raw = parseInt(process.env.BAHULAM_SHELL_PREVIEW_ROWS ?? '', 10);
|
|
439
|
-
|
|
446
|
+
if (Number.isFinite(raw) && raw >= 1) return raw;
|
|
447
|
+
const command = String(data?.args?.command || data?.command || '');
|
|
448
|
+
if (/^\s*git\s+(diff|show)\b/i.test(command)) return 8;
|
|
449
|
+
if (/^\s*git\s+status\b/i.test(command)) return 8;
|
|
450
|
+
return 2;
|
|
440
451
|
}
|
|
441
452
|
|
|
442
453
|
function lineCount(s) {
|
|
@@ -444,6 +455,64 @@ function lineCount(s) {
|
|
|
444
455
|
return String(s).split('\n').filter(Boolean).length;
|
|
445
456
|
}
|
|
446
457
|
|
|
458
|
+
function physicalLineCount(text) {
|
|
459
|
+
const value = String(text ?? '').replace(/\r\n?/g, '\n');
|
|
460
|
+
if (!value) return 0;
|
|
461
|
+
const lines = value.split('\n');
|
|
462
|
+
if (lines[lines.length - 1] === '') lines.pop();
|
|
463
|
+
return lines.length;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function readFileLineCount(data = {}, args = {}) {
|
|
467
|
+
const explicit = explicitReadLineCount(data);
|
|
468
|
+
if (explicit != null) return explicit;
|
|
469
|
+
|
|
470
|
+
const text = firstReadTextPayload(data);
|
|
471
|
+
if (text != null) return physicalLineCount(text);
|
|
472
|
+
|
|
473
|
+
const range = requestedRangeLineCount(args || data?.args || {});
|
|
474
|
+
if (range != null) return range;
|
|
475
|
+
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function explicitReadLineCount(data = {}) {
|
|
480
|
+
if (!data || typeof data !== 'object') return null;
|
|
481
|
+
for (const key of ['_total_lines', 'line_count', 'lines_count', 'total_lines', 'lineCount', 'totalLines']) {
|
|
482
|
+
const value = data[key];
|
|
483
|
+
if (value == null || value === '') continue;
|
|
484
|
+
const number = Number(value);
|
|
485
|
+
if (Number.isFinite(number) && number >= 0) return Math.floor(number);
|
|
486
|
+
}
|
|
487
|
+
if (Array.isArray(data.lines)) return data.lines.length;
|
|
488
|
+
if (data.result && typeof data.result === 'object' && data.result !== data) {
|
|
489
|
+
return explicitReadLineCount(data.result);
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function firstReadTextPayload(data = {}) {
|
|
495
|
+
if (!data || typeof data !== 'object') return null;
|
|
496
|
+
for (const key of ['content', 'text', 'output', 'output_preview', 'message']) {
|
|
497
|
+
if (typeof data[key] === 'string') return data[key];
|
|
498
|
+
}
|
|
499
|
+
if (data.result && typeof data.result === 'object' && data.result !== data) {
|
|
500
|
+
return firstReadTextPayload(data.result);
|
|
501
|
+
}
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function requestedRangeLineCount(args = {}) {
|
|
506
|
+
const start = Number(args.start_line ?? args.startLine);
|
|
507
|
+
const end = Number(args.end_line ?? args.endLine);
|
|
508
|
+
if (Number.isFinite(start) && Number.isFinite(end) && start > 0 && end >= start) {
|
|
509
|
+
return Math.floor(end - start + 1);
|
|
510
|
+
}
|
|
511
|
+
const limit = Number(args.limit ?? args.max_lines ?? args.maxLines);
|
|
512
|
+
if (Number.isFinite(limit) && limit >= 0) return Math.floor(limit);
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
447
516
|
function countMatches(data) {
|
|
448
517
|
if (typeof data?.match_count === 'number') return data.match_count;
|
|
449
518
|
return lineCount(data?.output);
|
|
@@ -470,6 +539,12 @@ function diffDelta(data) {
|
|
|
470
539
|
return `+${a} −${r}`;
|
|
471
540
|
}
|
|
472
541
|
|
|
542
|
+
function isNoChangeEditResult(tool, data = {}) {
|
|
543
|
+
if (tool !== 'edit_file') return false;
|
|
544
|
+
if (data._no_change || data.no_change) return true;
|
|
545
|
+
return data.success !== false && data.lines_added === 0 && data.lines_removed === 0;
|
|
546
|
+
}
|
|
547
|
+
|
|
473
548
|
function tone(text, t) {
|
|
474
549
|
switch (t) {
|
|
475
550
|
case 'success': return paint.state.success(text);
|
|
@@ -573,7 +648,7 @@ export function formatCard({ tool, args, result, durationMs, indent, columns, cw
|
|
|
573
648
|
const cols = columns || term().columns || 120;
|
|
574
649
|
const head = formatCardHead(tool, args, { indent, columns: cols, cwd });
|
|
575
650
|
|
|
576
|
-
const summary = summarizeResult(tool, result);
|
|
651
|
+
const summary = summarizeResult(tool, result, args);
|
|
577
652
|
const duration = formatDuration(durationMs ?? result?.duration_ms ?? (result?.duration_s != null ? result.duration_s * 1000 : null));
|
|
578
653
|
|
|
579
654
|
if (!summary.text && !duration) return head;
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import useSWR from 'swr'
|
|
4
|
-
import { TopBar } from '@/components/layout/top-bar'
|
|
5
|
-
import { ActivityHeatmap } from '@/components/overview/activity-heatmap'
|
|
6
|
-
import { PeakHoursChart } from '@/components/overview/peak-hours-chart'
|
|
7
|
-
import { DayOfWeekChart } from '@/components/activity/day-of-week-chart'
|
|
8
|
-
import { UsageOverTimeChart } from '@/components/overview/usage-over-time-chart'
|
|
9
|
-
import type { DailyActivity } from '@/types/claude'
|
|
10
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
11
|
-
import { Skeleton } from '@/components/ui/skeleton'
|
|
12
|
-
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
13
|
-
import { AlertTriangle, Flame, CalendarDays, BarChart3, Clock, Zap, TrendingUp, Star } from 'lucide-react'
|
|
14
|
-
|
|
15
|
-
const fetcher = (url: string) =>
|
|
16
|
-
fetch(url).then(r => { if (!r.ok) throw new Error(`API error ${r.status}`); return r.json() })
|
|
17
|
-
|
|
18
|
-
interface ActivityData {
|
|
19
|
-
daily_activity: DailyActivity[]
|
|
20
|
-
hour_counts: Array<{ hour: number; count: number }>
|
|
21
|
-
dow_counts: Array<{ day: string; count: number }>
|
|
22
|
-
streaks: { current: number; longest: number }
|
|
23
|
-
most_active_day: string
|
|
24
|
-
most_active_day_msgs: number
|
|
25
|
-
total_active_days: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function StatTile({
|
|
29
|
-
label,
|
|
30
|
-
value,
|
|
31
|
-
sub,
|
|
32
|
-
icon: Icon,
|
|
33
|
-
color,
|
|
34
|
-
}: {
|
|
35
|
-
label: string
|
|
36
|
-
value: string | number
|
|
37
|
-
sub: string
|
|
38
|
-
icon: React.ElementType
|
|
39
|
-
color: string
|
|
40
|
-
}) {
|
|
41
|
-
return (
|
|
42
|
-
<Card className="gap-0">
|
|
43
|
-
<CardHeader className="pb-2">
|
|
44
|
-
<CardDescription className="flex items-center gap-2 text-sm font-medium">
|
|
45
|
-
<Icon className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
46
|
-
{label}
|
|
47
|
-
</CardDescription>
|
|
48
|
-
<CardTitle className="text-3xl font-bold tabular-nums leading-none" style={{ color }}>
|
|
49
|
-
{value}
|
|
50
|
-
</CardTitle>
|
|
51
|
-
</CardHeader>
|
|
52
|
-
<CardContent className="pt-0">
|
|
53
|
-
<p className="text-xs text-muted-foreground">{sub}</p>
|
|
54
|
-
</CardContent>
|
|
55
|
-
</Card>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export default function ActivityPage() {
|
|
60
|
-
const { data, error, isLoading } = useSWR<ActivityData>('/api/activity', fetcher, { refreshInterval: 5_000 })
|
|
61
|
-
|
|
62
|
-
const hourCounts = data
|
|
63
|
-
? Object.fromEntries(data.hour_counts.map(h => [String(h.hour), h.count]))
|
|
64
|
-
: {}
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className="flex flex-col min-h-screen">
|
|
68
|
-
<TopBar title="Activity" subtitle="Patterns, streaks, and peak hours" />
|
|
69
|
-
<div className="w-full space-y-4 p-4 md:p-6">
|
|
70
|
-
|
|
71
|
-
{error && (
|
|
72
|
-
<Alert variant="destructive">
|
|
73
|
-
<AlertTriangle className="h-4 w-4" />
|
|
74
|
-
<AlertDescription>Error loading data: {String(error)}</AlertDescription>
|
|
75
|
-
</Alert>
|
|
76
|
-
)}
|
|
77
|
-
|
|
78
|
-
{isLoading && (
|
|
79
|
-
<>
|
|
80
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
81
|
-
{Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-28 rounded-xl" />)}
|
|
82
|
-
</div>
|
|
83
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
84
|
-
<Skeleton className="h-72 min-h-72 rounded-xl md:min-h-80" />
|
|
85
|
-
<Skeleton className="h-56 min-h-56 rounded-xl md:min-h-80" />
|
|
86
|
-
</div>
|
|
87
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
88
|
-
<Skeleton className="h-64 rounded-xl md:min-h-72" />
|
|
89
|
-
<Skeleton className="h-56 rounded-xl md:min-h-72" />
|
|
90
|
-
</div>
|
|
91
|
-
</>
|
|
92
|
-
)}
|
|
93
|
-
|
|
94
|
-
{data && (
|
|
95
|
-
<>
|
|
96
|
-
{/* Stat tiles */}
|
|
97
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
98
|
-
<StatTile
|
|
99
|
-
label="Current Streak"
|
|
100
|
-
value={data.streaks.current}
|
|
101
|
-
sub="consecutive days"
|
|
102
|
-
icon={Flame}
|
|
103
|
-
color="#f97316"
|
|
104
|
-
/>
|
|
105
|
-
<StatTile
|
|
106
|
-
label="Longest Streak"
|
|
107
|
-
value={data.streaks.longest}
|
|
108
|
-
sub="personal best"
|
|
109
|
-
icon={Zap}
|
|
110
|
-
color="var(--viz-sky)"
|
|
111
|
-
/>
|
|
112
|
-
<StatTile
|
|
113
|
-
label="Active Days"
|
|
114
|
-
value={data.total_active_days}
|
|
115
|
-
sub="total days with activity"
|
|
116
|
-
icon={TrendingUp}
|
|
117
|
-
color="#a78bfa"
|
|
118
|
-
/>
|
|
119
|
-
<StatTile
|
|
120
|
-
label="Most Active Day"
|
|
121
|
-
value={data.most_active_day ? data.most_active_day.slice(5) : '—'}
|
|
122
|
-
sub={data.most_active_day_msgs ? `${data.most_active_day_msgs.toLocaleString()} messages` : 'no data'}
|
|
123
|
-
icon={Star}
|
|
124
|
-
color="#34d399"
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* Row 1: Activity calendar | Peak hours */}
|
|
129
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
130
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
131
|
-
<CardHeader className="space-y-1 pb-0">
|
|
132
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
133
|
-
<CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
134
|
-
Activity Calendar
|
|
135
|
-
</CardTitle>
|
|
136
|
-
<CardDescription>GitHub-style contribution heatmap</CardDescription>
|
|
137
|
-
</CardHeader>
|
|
138
|
-
<CardContent className="flex flex-1 flex-col pt-3 pb-0">
|
|
139
|
-
<ActivityHeatmap data={data.daily_activity} />
|
|
140
|
-
</CardContent>
|
|
141
|
-
</Card>
|
|
142
|
-
|
|
143
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
144
|
-
<CardHeader className="space-y-1 pb-0">
|
|
145
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
146
|
-
<Clock className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
147
|
-
Peak Hours
|
|
148
|
-
</CardTitle>
|
|
149
|
-
<CardDescription>Activity by hour of day</CardDescription>
|
|
150
|
-
</CardHeader>
|
|
151
|
-
<CardContent className="flex flex-1 flex-col justify-between pt-3 pb-0">
|
|
152
|
-
<PeakHoursChart hourCounts={hourCounts} />
|
|
153
|
-
</CardContent>
|
|
154
|
-
</Card>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
{/* Row 2: Usage over time | Day of week */}
|
|
158
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:items-stretch">
|
|
159
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
160
|
-
<CardHeader className="space-y-1 pb-0">
|
|
161
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
162
|
-
<BarChart3 className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
163
|
-
Usage Over Time
|
|
164
|
-
</CardTitle>
|
|
165
|
-
<CardDescription>Messages and sessions over the last 90 days</CardDescription>
|
|
166
|
-
</CardHeader>
|
|
167
|
-
<CardContent className="flex flex-1 flex-col pt-3 pb-0">
|
|
168
|
-
<UsageOverTimeChart data={data.daily_activity} days={90} />
|
|
169
|
-
</CardContent>
|
|
170
|
-
</Card>
|
|
171
|
-
|
|
172
|
-
<Card className="flex h-full min-h-0 min-w-0 flex-col gap-2 py-4">
|
|
173
|
-
<CardHeader className="space-y-1 pb-0">
|
|
174
|
-
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
|
175
|
-
<CalendarDays className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
176
|
-
Day of Week
|
|
177
|
-
</CardTitle>
|
|
178
|
-
<CardDescription>Which days you use Bahulam Code most</CardDescription>
|
|
179
|
-
</CardHeader>
|
|
180
|
-
<CardContent className="flex flex-1 flex-col justify-between pt-3 pb-0">
|
|
181
|
-
<DayOfWeekChart data={data.dow_counts} />
|
|
182
|
-
</CardContent>
|
|
183
|
-
</Card>
|
|
184
|
-
</div>
|
|
185
|
-
</>
|
|
186
|
-
)}
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
)
|
|
190
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { getSessions, readStatsCache } from '@/lib/claude-reader'
|
|
3
|
-
import type { DailyActivity, SessionMeta } from '@/types/claude'
|
|
4
|
-
|
|
5
|
-
export const dynamic = 'force-dynamic'
|
|
6
|
-
|
|
7
|
-
function computeStreaks(dates: Set<string>): { current: number; longest: number } {
|
|
8
|
-
const sorted = [...dates].sort()
|
|
9
|
-
if (sorted.length === 0) return { current: 0, longest: 0 }
|
|
10
|
-
|
|
11
|
-
let longest = 1
|
|
12
|
-
let streak = 1
|
|
13
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
14
|
-
const prev = new Date(sorted[i - 1])
|
|
15
|
-
const curr = new Date(sorted[i])
|
|
16
|
-
const diff = (curr.getTime() - prev.getTime()) / 86_400_000
|
|
17
|
-
if (diff === 1) {
|
|
18
|
-
streak++
|
|
19
|
-
if (streak > longest) longest = streak
|
|
20
|
-
} else {
|
|
21
|
-
streak = 1
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Current streak (from today backwards)
|
|
26
|
-
const today = new Date().toISOString().slice(0, 10)
|
|
27
|
-
let current = 0
|
|
28
|
-
const d = new Date(today)
|
|
29
|
-
while (dates.has(d.toISOString().slice(0, 10))) {
|
|
30
|
-
current++
|
|
31
|
-
d.setDate(d.getDate() - 1)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return { current, longest }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function computeDailyActivityFromSessions(sessions: SessionMeta[]): DailyActivity[] {
|
|
38
|
-
const byDate = new Map<string, { messages: number; sessions: number; tools: number }>()
|
|
39
|
-
|
|
40
|
-
for (const s of sessions) {
|
|
41
|
-
if (!s.start_time) continue
|
|
42
|
-
const date = s.start_time.slice(0, 10)
|
|
43
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue
|
|
44
|
-
|
|
45
|
-
const existing = byDate.get(date) ?? { messages: 0, sessions: 0, tools: 0 }
|
|
46
|
-
existing.messages += (s.user_message_count ?? 0) + (s.assistant_message_count ?? 0)
|
|
47
|
-
existing.sessions += 1
|
|
48
|
-
existing.tools += Object.values(s.tool_counts ?? {}).reduce((sum, count) => sum + count, 0)
|
|
49
|
-
byDate.set(date, existing)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return Array.from(byDate.entries())
|
|
53
|
-
.map(([date, { messages, sessions: sessionCount, tools }]) => ({
|
|
54
|
-
date,
|
|
55
|
-
messageCount: messages,
|
|
56
|
-
sessionCount,
|
|
57
|
-
toolCallCount: tools,
|
|
58
|
-
}))
|
|
59
|
-
.sort((a, b) => a.date.localeCompare(b.date))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function mergeDailyActivity(fromStats: DailyActivity[], fromSessions: DailyActivity[]): DailyActivity[] {
|
|
63
|
-
const byDate = new Map<string, DailyActivity>()
|
|
64
|
-
for (const d of fromStats) byDate.set(d.date, d)
|
|
65
|
-
for (const d of fromSessions) byDate.set(d.date, d)
|
|
66
|
-
return Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date))
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function computeHourCounts(sessions: SessionMeta[]): Array<{ hour: number; count: number }> {
|
|
70
|
-
const hourCounts = Array.from({ length: 24 }, () => 0)
|
|
71
|
-
|
|
72
|
-
for (const s of sessions) {
|
|
73
|
-
if (s.user_message_timestamps?.length) {
|
|
74
|
-
for (const ts of s.user_message_timestamps) {
|
|
75
|
-
const d = new Date(ts)
|
|
76
|
-
if (!isNaN(d.getTime())) hourCounts[d.getHours()]++
|
|
77
|
-
}
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (s.message_hours?.length) {
|
|
82
|
-
for (const hour of s.message_hours) {
|
|
83
|
-
if (Number.isInteger(hour) && hour >= 0 && hour < 24) hourCounts[hour]++
|
|
84
|
-
}
|
|
85
|
-
continue
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const d = new Date(s.start_time)
|
|
89
|
-
if (!isNaN(d.getTime())) hourCounts[d.getHours()]++
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return hourCounts.map((count, hour) => ({ hour, count }))
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function GET() {
|
|
96
|
-
const [stats, sessions] = await Promise.all([readStatsCache(), getSessions()])
|
|
97
|
-
const dailyFromSessions = computeDailyActivityFromSessions(sessions)
|
|
98
|
-
const dailyActivity = stats
|
|
99
|
-
? mergeDailyActivity(stats.dailyActivity ?? [], dailyFromSessions)
|
|
100
|
-
: dailyFromSessions
|
|
101
|
-
|
|
102
|
-
// Day-of-week counts from session timestamps
|
|
103
|
-
const dowCounts: number[] = [0, 0, 0, 0, 0, 0, 0] // Sun=0..Sat=6
|
|
104
|
-
const activeDates = new Set<string>()
|
|
105
|
-
|
|
106
|
-
for (const s of sessions) {
|
|
107
|
-
if (!s.start_time) continue
|
|
108
|
-
const d = new Date(s.start_time)
|
|
109
|
-
if (isNaN(d.getTime())) continue
|
|
110
|
-
dowCounts[d.getDay()]++
|
|
111
|
-
activeDates.add(s.start_time.slice(0, 10))
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const streaks = computeStreaks(activeDates)
|
|
115
|
-
|
|
116
|
-
// Most active day
|
|
117
|
-
let mostActiveDay = ''
|
|
118
|
-
let mostActiveMsgs = 0
|
|
119
|
-
for (const da of dailyActivity) {
|
|
120
|
-
if (da.messageCount > mostActiveMsgs) {
|
|
121
|
-
mostActiveMsgs = da.messageCount
|
|
122
|
-
mostActiveDay = da.date
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return NextResponse.json({
|
|
127
|
-
daily_activity: dailyActivity,
|
|
128
|
-
hour_counts: computeHourCounts(sessions),
|
|
129
|
-
dow_counts: dowCounts.map((count, i) => ({
|
|
130
|
-
day: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][i],
|
|
131
|
-
count,
|
|
132
|
-
})),
|
|
133
|
-
streaks,
|
|
134
|
-
most_active_day: mostActiveDay,
|
|
135
|
-
most_active_day_msgs: mostActiveMsgs,
|
|
136
|
-
total_active_days: activeDates.size,
|
|
137
|
-
})
|
|
138
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server'
|
|
2
|
-
import { readFileSync } from 'fs'
|
|
3
|
-
import { join } from 'path'
|
|
4
|
-
|
|
5
|
-
export const dynamic = 'force-dynamic'
|
|
6
|
-
|
|
7
|
-
interface BenchmarkResult {
|
|
8
|
-
instance_id: string
|
|
9
|
-
repo: string
|
|
10
|
-
base_commit: string
|
|
11
|
-
test_patch: string
|
|
12
|
-
resolved: boolean
|
|
13
|
-
test_result: {
|
|
14
|
-
result: string[]
|
|
15
|
-
exit_code: number
|
|
16
|
-
}
|
|
17
|
-
metadata: {
|
|
18
|
-
agent_class: string
|
|
19
|
-
model_name: string
|
|
20
|
-
max_iterations: number
|
|
21
|
-
eval_history: Array<{
|
|
22
|
-
timestamp: string
|
|
23
|
-
action: string
|
|
24
|
-
observation: string
|
|
25
|
-
}>
|
|
26
|
-
submission: string
|
|
27
|
-
instance_id: string
|
|
28
|
-
predict_output: string
|
|
29
|
-
model_patch: string
|
|
30
|
-
test_result: {
|
|
31
|
-
result: string[]
|
|
32
|
-
exit_code: number
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface BenchmarkData {
|
|
38
|
-
results: BenchmarkResult[]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function GET(request: Request) {
|
|
42
|
-
try {
|
|
43
|
-
const { searchParams } = new URL(request.url)
|
|
44
|
-
const run = searchParams.get('run') || 'swebench-v4-flash-300'
|
|
45
|
-
const limit = parseInt(searchParams.get('limit') || '50', 10)
|
|
46
|
-
const offset = parseInt(searchParams.get('offset') || '0', 10)
|
|
47
|
-
|
|
48
|
-
// Load benchmark results from file
|
|
49
|
-
const resultsPath = join(
|
|
50
|
-
process.cwd(),
|
|
51
|
-
'..',
|
|
52
|
-
'benchmark',
|
|
53
|
-
'results',
|
|
54
|
-
'runs',
|
|
55
|
-
run,
|
|
56
|
-
'harness-results.json'
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
let data: BenchmarkData
|
|
60
|
-
try {
|
|
61
|
-
const fileContent = readFileSync(resultsPath, 'utf-8')
|
|
62
|
-
data = JSON.parse(fileContent)
|
|
63
|
-
} catch (error) {
|
|
64
|
-
return NextResponse.json(
|
|
65
|
-
{ error: `Benchmark run "${run}" not found` },
|
|
66
|
-
{ status: 404 }
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Calculate statistics
|
|
71
|
-
const results = data.results || []
|
|
72
|
-
const totalTests = results.length
|
|
73
|
-
const resolvedTests = results.filter((r) => r.resolved).length
|
|
74
|
-
const passRate = totalTests > 0 ? (resolvedTests / totalTests) * 100 : 0
|
|
75
|
-
|
|
76
|
-
// Group by repo
|
|
77
|
-
const byRepo = new Map<string, number>()
|
|
78
|
-
const byRepoResolved = new Map<string, number>()
|
|
79
|
-
for (const result of results) {
|
|
80
|
-
const repo = result.repo || 'unknown'
|
|
81
|
-
byRepo.set(repo, (byRepo.get(repo) || 0) + 1)
|
|
82
|
-
if (result.resolved) {
|
|
83
|
-
byRepoResolved.set(repo, (byRepoResolved.get(repo) || 0) + 1)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Paginate results
|
|
88
|
-
const paginatedResults = results.slice(offset, offset + limit)
|
|
89
|
-
|
|
90
|
-
return NextResponse.json({
|
|
91
|
-
run,
|
|
92
|
-
stats: {
|
|
93
|
-
totalTests,
|
|
94
|
-
resolvedTests,
|
|
95
|
-
passRate: parseFloat(passRate.toFixed(2)),
|
|
96
|
-
byRepo: Object.fromEntries(byRepo),
|
|
97
|
-
byRepoResolved: Object.fromEntries(byRepoResolved),
|
|
98
|
-
},
|
|
99
|
-
pagination: {
|
|
100
|
-
limit,
|
|
101
|
-
offset,
|
|
102
|
-
total: totalTests,
|
|
103
|
-
},
|
|
104
|
-
results: paginatedResults,
|
|
105
|
-
})
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error('Benchmark API error:', error)
|
|
108
|
-
return NextResponse.json(
|
|
109
|
-
{ error: 'Failed to load benchmark data' },
|
|
110
|
-
{ status: 500 }
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
}
|