@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.
Files changed (204) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +6 -6
  3. package/README.md +11 -18
  4. package/package.json +11 -11
  5. package/src/agents/loader.mjs +1 -1
  6. package/src/agents/workflow_loader.mjs +2 -2
  7. package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +4 -3
  8. package/src/commands/agent.mjs +3 -3
  9. package/src/commands/device.mjs +2 -2
  10. package/src/commands/pair.mjs +2 -2
  11. package/src/commands/remote.mjs +3 -3
  12. package/src/commands/workflow.mjs +5 -5
  13. package/src/config/env.mjs +9 -2
  14. package/src/config/memory-loader.mjs +1 -1
  15. package/src/config/model-catalog-default.json +4 -3
  16. package/src/config/model-catalog.mjs +2 -0
  17. package/src/config/settings.mjs +2 -2
  18. package/src/context/prose-chunker.mjs +162 -5
  19. package/src/core/agent-loop.mjs +17 -0
  20. package/src/core/attachments.mjs +66 -4
  21. package/src/core/backend-url.mjs +11 -12
  22. package/src/core/bundled-runtime.mjs +1 -1
  23. package/src/core/callback-client.mjs +1 -1
  24. package/src/core/headless.mjs +18 -4
  25. package/src/core/jsonl-writer.mjs +18 -18
  26. package/src/core/lint-resolver.mjs +472 -0
  27. package/src/core/local-agent.mjs +40 -8
  28. package/src/core/local-store.mjs +5 -1
  29. package/src/core/mode-selector.mjs +1 -1
  30. package/src/core/output-filter.mjs +22 -26
  31. package/src/core/pricing.mjs +10 -3
  32. package/src/core/project-artifacts.mjs +3 -3
  33. package/src/core/project-context-loader.mjs +9 -9
  34. package/src/core/settings-sync.mjs +1 -1
  35. package/src/core/stagnation.mjs +61 -1
  36. package/src/core/stream-client.mjs +13 -8
  37. package/src/core/system-prompt.mjs +2 -2
  38. package/src/core/tool-executor.mjs +178 -84
  39. package/src/daemon/http-dashboard.mjs +400 -0
  40. package/src/daemon/session-core.mjs +1 -0
  41. package/src/local-service/agent-relay.mjs +868 -0
  42. package/src/local-service/approval-bridge.mjs +222 -0
  43. package/src/local-service/browser.mjs +24 -0
  44. package/src/local-service/command.mjs +155 -0
  45. package/src/local-service/file-access.mjs +393 -0
  46. package/src/local-service/machine.mjs +86 -0
  47. package/src/local-service/paths.mjs +29 -0
  48. package/src/local-service/preview-converters.mjs +260 -0
  49. package/src/local-service/server.mjs +2644 -0
  50. package/src/local-service/session-store.mjs +221 -0
  51. package/src/mcp/client.mjs +4 -4
  52. package/src/onboarding/preflight.mjs +2 -2
  53. package/src/terminal/agents.mjs +2 -2
  54. package/src/terminal/analytics.mjs +2 -6
  55. package/src/terminal/ansi.mjs +11 -3
  56. package/src/terminal/main.mjs +43 -17
  57. package/src/terminal/model-catalog-display.mjs +111 -0
  58. package/src/terminal/repl-format.mjs +4 -1
  59. package/src/terminal/repl-model-form.mjs +81 -14
  60. package/src/terminal/repl-render.mjs +37 -7
  61. package/src/terminal/repl-resume.mjs +2 -2
  62. package/src/terminal/repl-state.mjs +12 -0
  63. package/src/terminal/repl.mjs +248 -70
  64. package/src/terminal/tool-display.mjs +32 -0
  65. package/src/terminal/watch-state.mjs +118 -0
  66. package/src/tools/agent.mjs +2 -2
  67. package/src/tools/analyze-image.mjs +158 -0
  68. package/src/tools/bash.mjs +11 -9
  69. package/src/tools/generate-image.mjs +411 -0
  70. package/src/tools/lint.mjs +24 -43
  71. package/src/tools/project-overview.mjs +12 -12
  72. package/src/tools/registry.mjs +4 -0
  73. package/src/ui/formatter.mjs +2 -2
  74. package/src/ui/icons.mjs +2 -0
  75. package/src/ui/input-dock.mjs +7 -7
  76. package/src/ui/palette.mjs +1 -1
  77. package/src/ui/slash-commands.mjs +4 -6
  78. package/src/ui/spinner.mjs +1 -1
  79. package/src/ui/term.mjs +2 -6
  80. package/src/ui/tool-card.mjs +81 -6
  81. package/pulse/app/activity/page.tsx +0 -190
  82. package/pulse/app/api/activity/route.ts +0 -138
  83. package/pulse/app/api/benchmark/route.ts +0 -113
  84. package/pulse/app/api/benchmarks/route.ts +0 -195
  85. package/pulse/app/api/costs/route.ts +0 -88
  86. package/pulse/app/api/export/route.ts +0 -77
  87. package/pulse/app/api/history/route.ts +0 -11
  88. package/pulse/app/api/import/route.ts +0 -31
  89. package/pulse/app/api/memory/route.ts +0 -50
  90. package/pulse/app/api/plans/route.ts +0 -9
  91. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  92. package/pulse/app/api/projects/route.ts +0 -121
  93. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  94. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  95. package/pulse/app/api/sessions/route.ts +0 -112
  96. package/pulse/app/api/settings/route.ts +0 -14
  97. package/pulse/app/api/stats/route.ts +0 -143
  98. package/pulse/app/api/todos/route.ts +0 -9
  99. package/pulse/app/api/tools/route.ts +0 -160
  100. package/pulse/app/benchmarks/page.tsx +0 -224
  101. package/pulse/app/costs/page.tsx +0 -179
  102. package/pulse/app/export/page.tsx +0 -465
  103. package/pulse/app/favicon.ico +0 -0
  104. package/pulse/app/globals.css +0 -263
  105. package/pulse/app/help/page.tsx +0 -143
  106. package/pulse/app/history/page.tsx +0 -157
  107. package/pulse/app/layout.tsx +0 -46
  108. package/pulse/app/memory/page.tsx +0 -365
  109. package/pulse/app/overview-client.tsx +0 -393
  110. package/pulse/app/page.tsx +0 -14
  111. package/pulse/app/plans/page.tsx +0 -308
  112. package/pulse/app/projects/[slug]/page.tsx +0 -390
  113. package/pulse/app/projects/page.tsx +0 -110
  114. package/pulse/app/sessions/[id]/page.tsx +0 -243
  115. package/pulse/app/sessions/page.tsx +0 -39
  116. package/pulse/app/settings/page.tsx +0 -188
  117. package/pulse/app/todos/page.tsx +0 -211
  118. package/pulse/app/tools/page.tsx +0 -249
  119. package/pulse/cli.js +0 -164
  120. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  121. package/pulse/components/activity/streak-card.tsx +0 -36
  122. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  123. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  124. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  125. package/pulse/components/costs/model-token-table.tsx +0 -60
  126. package/pulse/components/global-search.tsx +0 -193
  127. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  128. package/pulse/components/layout/bottom-nav.tsx +0 -53
  129. package/pulse/components/layout/client-layout.tsx +0 -31
  130. package/pulse/components/layout/sidebar-context.tsx +0 -50
  131. package/pulse/components/layout/sidebar.tsx +0 -183
  132. package/pulse/components/layout/top-bar.tsx +0 -121
  133. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  134. package/pulse/components/overview/conversation-table.tsx +0 -148
  135. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  136. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  137. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  138. package/pulse/components/overview/stat-card.tsx +0 -102
  139. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  140. package/pulse/components/projects/project-card.tsx +0 -175
  141. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  142. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  143. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  144. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  145. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  146. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  147. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  148. package/pulse/components/sessions/session-badges.tsx +0 -49
  149. package/pulse/components/sessions/session-table.tsx +0 -299
  150. package/pulse/components/theme-provider.tsx +0 -44
  151. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  152. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  153. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  154. package/pulse/components/tools/version-history-table.tsx +0 -32
  155. package/pulse/components/ui/alert.tsx +0 -66
  156. package/pulse/components/ui/badge.tsx +0 -48
  157. package/pulse/components/ui/breadcrumb.tsx +0 -109
  158. package/pulse/components/ui/button.tsx +0 -64
  159. package/pulse/components/ui/calendar.tsx +0 -220
  160. package/pulse/components/ui/card.tsx +0 -92
  161. package/pulse/components/ui/command.tsx +0 -158
  162. package/pulse/components/ui/dialog.tsx +0 -158
  163. package/pulse/components/ui/input.tsx +0 -21
  164. package/pulse/components/ui/popover.tsx +0 -89
  165. package/pulse/components/ui/progress.tsx +0 -31
  166. package/pulse/components/ui/select.tsx +0 -190
  167. package/pulse/components/ui/separator.tsx +0 -28
  168. package/pulse/components/ui/sheet.tsx +0 -143
  169. package/pulse/components/ui/skeleton.tsx +0 -13
  170. package/pulse/components/ui/table.tsx +0 -116
  171. package/pulse/components/ui/tabs.tsx +0 -91
  172. package/pulse/components/ui/tooltip.tsx +0 -57
  173. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  174. package/pulse/components.json +0 -23
  175. package/pulse/eslint.config.mjs +0 -18
  176. package/pulse/lib/bahulam-paths.ts +0 -23
  177. package/pulse/lib/claude-reader.ts +0 -592
  178. package/pulse/lib/decode.ts +0 -129
  179. package/pulse/lib/pricing.ts +0 -102
  180. package/pulse/lib/replay-parser.ts +0 -165
  181. package/pulse/lib/tool-categories.ts +0 -140
  182. package/pulse/lib/utils.ts +0 -6
  183. package/pulse/next-env.d.ts +0 -6
  184. package/pulse/next.config.ts +0 -16
  185. package/pulse/package.json +0 -45
  186. package/pulse/postcss.config.mjs +0 -7
  187. package/pulse/public/activity.png +0 -0
  188. package/pulse/public/cc-lens.png +0 -0
  189. package/pulse/public/command-k.png +0 -0
  190. package/pulse/public/costs.png +0 -0
  191. package/pulse/public/dashboard-dark.png +0 -0
  192. package/pulse/public/dashboard-white.png +0 -0
  193. package/pulse/public/export.png +0 -0
  194. package/pulse/public/file.svg +0 -1
  195. package/pulse/public/globe.svg +0 -1
  196. package/pulse/public/next.svg +0 -1
  197. package/pulse/public/projects.png +0 -0
  198. package/pulse/public/session-chat.png +0 -0
  199. package/pulse/public/todos.png +0 -0
  200. package/pulse/public/tools.png +0 -0
  201. package/pulse/public/vercel.svg +0 -1
  202. package/pulse/public/window.svg +0 -1
  203. package/pulse/tsconfig.json +0 -34
  204. 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-approve routine tools (dangerous still prompts)',
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
 
@@ -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 KEPLER_PLAIN=1.
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 || env.KEPLER_TTY_MODE || '').trim().toLowerCase();
99
- if (env.BAHULAM_PLAIN === '1' || env.KEPLER_PLAIN === '1') return 'plain';
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
  }
@@ -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 = data._total_lines || lineCount(data.output || data.output_preview);
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
- return Number.isFinite(raw) && raw >= 1 ? raw : 2;
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
- }