@bahulam/code 2.6.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.
Files changed (273) hide show
  1. package/README.md +80 -0
  2. package/package.json +49 -0
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/benchmark/route.ts +113 -0
  6. package/pulse/app/api/benchmarks/route.ts +195 -0
  7. package/pulse/app/api/costs/route.ts +88 -0
  8. package/pulse/app/api/export/route.ts +77 -0
  9. package/pulse/app/api/history/route.ts +11 -0
  10. package/pulse/app/api/import/route.ts +31 -0
  11. package/pulse/app/api/memory/route.ts +50 -0
  12. package/pulse/app/api/plans/route.ts +9 -0
  13. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  14. package/pulse/app/api/projects/route.ts +121 -0
  15. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  16. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  17. package/pulse/app/api/sessions/route.ts +112 -0
  18. package/pulse/app/api/settings/route.ts +14 -0
  19. package/pulse/app/api/stats/route.ts +143 -0
  20. package/pulse/app/api/todos/route.ts +9 -0
  21. package/pulse/app/api/tools/route.ts +160 -0
  22. package/pulse/app/benchmarks/page.tsx +224 -0
  23. package/pulse/app/costs/page.tsx +179 -0
  24. package/pulse/app/export/page.tsx +465 -0
  25. package/pulse/app/favicon.ico +0 -0
  26. package/pulse/app/globals.css +263 -0
  27. package/pulse/app/help/page.tsx +143 -0
  28. package/pulse/app/history/page.tsx +157 -0
  29. package/pulse/app/layout.tsx +46 -0
  30. package/pulse/app/memory/page.tsx +365 -0
  31. package/pulse/app/overview-client.tsx +393 -0
  32. package/pulse/app/page.tsx +14 -0
  33. package/pulse/app/plans/page.tsx +308 -0
  34. package/pulse/app/projects/[slug]/page.tsx +390 -0
  35. package/pulse/app/projects/page.tsx +110 -0
  36. package/pulse/app/sessions/[id]/page.tsx +243 -0
  37. package/pulse/app/sessions/page.tsx +39 -0
  38. package/pulse/app/settings/page.tsx +188 -0
  39. package/pulse/app/todos/page.tsx +211 -0
  40. package/pulse/app/tools/page.tsx +249 -0
  41. package/pulse/cli.js +164 -0
  42. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  43. package/pulse/components/activity/streak-card.tsx +36 -0
  44. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  45. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  46. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  47. package/pulse/components/costs/model-token-table.tsx +60 -0
  48. package/pulse/components/global-search.tsx +193 -0
  49. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  50. package/pulse/components/layout/bottom-nav.tsx +53 -0
  51. package/pulse/components/layout/client-layout.tsx +31 -0
  52. package/pulse/components/layout/sidebar-context.tsx +50 -0
  53. package/pulse/components/layout/sidebar.tsx +183 -0
  54. package/pulse/components/layout/top-bar.tsx +121 -0
  55. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  56. package/pulse/components/overview/conversation-table.tsx +148 -0
  57. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  58. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  59. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  60. package/pulse/components/overview/stat-card.tsx +102 -0
  61. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  62. package/pulse/components/projects/project-card.tsx +175 -0
  63. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  64. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  65. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  66. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  67. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  68. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  69. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  70. package/pulse/components/sessions/session-badges.tsx +49 -0
  71. package/pulse/components/sessions/session-table.tsx +299 -0
  72. package/pulse/components/theme-provider.tsx +44 -0
  73. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  74. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  75. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  76. package/pulse/components/tools/version-history-table.tsx +32 -0
  77. package/pulse/components/ui/alert.tsx +66 -0
  78. package/pulse/components/ui/badge.tsx +48 -0
  79. package/pulse/components/ui/breadcrumb.tsx +109 -0
  80. package/pulse/components/ui/button.tsx +64 -0
  81. package/pulse/components/ui/calendar.tsx +220 -0
  82. package/pulse/components/ui/card.tsx +92 -0
  83. package/pulse/components/ui/command.tsx +158 -0
  84. package/pulse/components/ui/dialog.tsx +158 -0
  85. package/pulse/components/ui/input.tsx +21 -0
  86. package/pulse/components/ui/popover.tsx +89 -0
  87. package/pulse/components/ui/progress.tsx +31 -0
  88. package/pulse/components/ui/select.tsx +190 -0
  89. package/pulse/components/ui/separator.tsx +28 -0
  90. package/pulse/components/ui/sheet.tsx +143 -0
  91. package/pulse/components/ui/skeleton.tsx +13 -0
  92. package/pulse/components/ui/table.tsx +116 -0
  93. package/pulse/components/ui/tabs.tsx +91 -0
  94. package/pulse/components/ui/tooltip.tsx +57 -0
  95. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  96. package/pulse/components.json +23 -0
  97. package/pulse/eslint.config.mjs +18 -0
  98. package/pulse/lib/bahulam-paths.ts +23 -0
  99. package/pulse/lib/claude-reader.ts +592 -0
  100. package/pulse/lib/decode.ts +129 -0
  101. package/pulse/lib/pricing.ts +102 -0
  102. package/pulse/lib/replay-parser.ts +165 -0
  103. package/pulse/lib/tool-categories.ts +127 -0
  104. package/pulse/lib/utils.ts +6 -0
  105. package/pulse/next-env.d.ts +6 -0
  106. package/pulse/next.config.ts +16 -0
  107. package/pulse/package.json +45 -0
  108. package/pulse/postcss.config.mjs +7 -0
  109. package/pulse/public/activity.png +0 -0
  110. package/pulse/public/cc-lens.png +0 -0
  111. package/pulse/public/command-k.png +0 -0
  112. package/pulse/public/costs.png +0 -0
  113. package/pulse/public/dashboard-dark.png +0 -0
  114. package/pulse/public/dashboard-white.png +0 -0
  115. package/pulse/public/export.png +0 -0
  116. package/pulse/public/file.svg +1 -0
  117. package/pulse/public/globe.svg +1 -0
  118. package/pulse/public/next.svg +1 -0
  119. package/pulse/public/projects.png +0 -0
  120. package/pulse/public/session-chat.png +0 -0
  121. package/pulse/public/todos.png +0 -0
  122. package/pulse/public/tools.png +0 -0
  123. package/pulse/public/vercel.svg +1 -0
  124. package/pulse/public/window.svg +1 -0
  125. package/pulse/tsconfig.json +34 -0
  126. package/pulse/types/claude.ts +294 -0
  127. package/src/agents/loader.mjs +94 -0
  128. package/src/agents/multi_workflow_loader.mjs +330 -0
  129. package/src/agents/parser.mjs +205 -0
  130. package/src/agents/scaffold.mjs +222 -0
  131. package/src/agents/teams.mjs +123 -0
  132. package/src/agents/workflow_loader.mjs +122 -0
  133. package/src/agents/workflow_scaffold.mjs +249 -0
  134. package/src/auth/oauth.mjs +220 -0
  135. package/src/auth/tarang-auth.mjs +306 -0
  136. package/src/commands/agent.mjs +220 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +200 -0
  139. package/src/config/env.mjs +263 -0
  140. package/src/config/hook-runner.mjs +100 -0
  141. package/src/config/memory-loader.mjs +32 -0
  142. package/src/config/settings-loader.mjs +45 -0
  143. package/src/config/settings.mjs +132 -0
  144. package/src/context/ast-parser.mjs +298 -0
  145. package/src/context/bm25.mjs +85 -0
  146. package/src/context/retriever.mjs +308 -0
  147. package/src/context/skeleton.mjs +134 -0
  148. package/src/context/symbol-indexer.mjs +375 -0
  149. package/src/core/agent-history.mjs +111 -0
  150. package/src/core/agent-loop.mjs +486 -0
  151. package/src/core/approval-log.mjs +104 -0
  152. package/src/core/approval.mjs +476 -0
  153. package/src/core/attachments.mjs +380 -0
  154. package/src/core/backend-url.mjs +55 -0
  155. package/src/core/cache-control.mjs +92 -0
  156. package/src/core/cache.mjs +105 -0
  157. package/src/core/callback-client.mjs +180 -0
  158. package/src/core/checkpoints.mjs +142 -0
  159. package/src/core/compact-history.mjs +127 -0
  160. package/src/core/context-envelope.mjs +54 -0
  161. package/src/core/context-manager.mjs +198 -0
  162. package/src/core/error-guidance.mjs +311 -0
  163. package/src/core/file-diff.mjs +217 -0
  164. package/src/core/headless.mjs +448 -0
  165. package/src/core/hooks-manager.mjs +87 -0
  166. package/src/core/jsonl-writer.mjs +449 -0
  167. package/src/core/local-agent.mjs +537 -0
  168. package/src/core/local-store.mjs +836 -0
  169. package/src/core/mode-selector.mjs +51 -0
  170. package/src/core/output-filter.mjs +177 -0
  171. package/src/core/paths.mjs +190 -0
  172. package/src/core/policy-resolver.mjs +156 -0
  173. package/src/core/pricing.mjs +336 -0
  174. package/src/core/project-artifacts.mjs +39 -0
  175. package/src/core/project-context-loader.mjs +139 -0
  176. package/src/core/providers.mjs +219 -0
  177. package/src/core/rate-limit-display.mjs +121 -0
  178. package/src/core/rate-limiter.mjs +119 -0
  179. package/src/core/resume-mode.mjs +192 -0
  180. package/src/core/risk-tier.mjs +337 -0
  181. package/src/core/safety.mjs +203 -0
  182. package/src/core/scheduler.mjs +173 -0
  183. package/src/core/session-manager.mjs +360 -0
  184. package/src/core/session.mjs +143 -0
  185. package/src/core/settings-sync.mjs +85 -0
  186. package/src/core/stagnation.mjs +57 -0
  187. package/src/core/stream-client.mjs +829 -0
  188. package/src/core/streaming.mjs +182 -0
  189. package/src/core/system-prompt.mjs +140 -0
  190. package/src/core/tasks.mjs +196 -0
  191. package/src/core/tool-executor.mjs +1950 -0
  192. package/src/core/trust.mjs +158 -0
  193. package/src/core/work-scope.mjs +248 -0
  194. package/src/hooks/engine.mjs +162 -0
  195. package/src/index.mjs +426 -0
  196. package/src/mcp/client.mjs +253 -0
  197. package/src/mcp/transport-shttp.mjs +130 -0
  198. package/src/mcp/transport-sse.mjs +131 -0
  199. package/src/mcp/transport-ws.mjs +134 -0
  200. package/src/onboarding/preflight.mjs +360 -0
  201. package/src/permissions/checker.mjs +57 -0
  202. package/src/permissions/command-classifier.mjs +652 -0
  203. package/src/permissions/injection-check.mjs +60 -0
  204. package/src/permissions/path-check.mjs +102 -0
  205. package/src/permissions/prompt.mjs +73 -0
  206. package/src/permissions/sandbox.mjs +112 -0
  207. package/src/plugins/loader.mjs +138 -0
  208. package/src/skills/installer.mjs +188 -0
  209. package/src/skills/loader.mjs +252 -0
  210. package/src/skills/runner.mjs +55 -0
  211. package/src/state/orbit.mjs +263 -0
  212. package/src/state/verbosity.mjs +99 -0
  213. package/src/telemetry/index.mjs +96 -0
  214. package/src/terminal/agents.mjs +177 -0
  215. package/src/terminal/analytics.mjs +292 -0
  216. package/src/terminal/ansi.mjs +695 -0
  217. package/src/terminal/init.mjs +145 -0
  218. package/src/terminal/main.mjs +269 -0
  219. package/src/terminal/repl-explore.mjs +35 -0
  220. package/src/terminal/repl-format.mjs +257 -0
  221. package/src/terminal/repl-render.mjs +561 -0
  222. package/src/terminal/repl-resume.mjs +625 -0
  223. package/src/terminal/repl-state.mjs +103 -0
  224. package/src/terminal/repl-utils.mjs +34 -0
  225. package/src/terminal/repl.mjs +3832 -0
  226. package/src/terminal/skills.mjs +54 -0
  227. package/src/terminal/tool-display.mjs +240 -0
  228. package/src/tools/agent.mjs +137 -0
  229. package/src/tools/ask-user.mjs +61 -0
  230. package/src/tools/bash.mjs +231 -0
  231. package/src/tools/cron-create.mjs +120 -0
  232. package/src/tools/cron-delete.mjs +49 -0
  233. package/src/tools/cron-list.mjs +37 -0
  234. package/src/tools/edit.mjs +82 -0
  235. package/src/tools/enter-worktree.mjs +69 -0
  236. package/src/tools/exit-worktree.mjs +57 -0
  237. package/src/tools/glob.mjs +117 -0
  238. package/src/tools/grep.mjs +129 -0
  239. package/src/tools/lint.mjs +71 -0
  240. package/src/tools/ls.mjs +58 -0
  241. package/src/tools/lsp.mjs +115 -0
  242. package/src/tools/multi-edit.mjs +94 -0
  243. package/src/tools/notebook-edit.mjs +96 -0
  244. package/src/tools/project-overview.mjs +641 -0
  245. package/src/tools/read-mcp-resource.mjs +57 -0
  246. package/src/tools/read.mjs +138 -0
  247. package/src/tools/registry.mjs +116 -0
  248. package/src/tools/remote-trigger.mjs +84 -0
  249. package/src/tools/send-message.mjs +64 -0
  250. package/src/tools/skill.mjs +52 -0
  251. package/src/tools/test-runner.mjs +49 -0
  252. package/src/tools/todo-write.mjs +68 -0
  253. package/src/tools/tool-search.mjs +77 -0
  254. package/src/tools/web-fetch.mjs +65 -0
  255. package/src/tools/web-search.mjs +89 -0
  256. package/src/tools/write.mjs +55 -0
  257. package/src/ui/approval.mjs +263 -0
  258. package/src/ui/banner.mjs +235 -0
  259. package/src/ui/commands.mjs +537 -0
  260. package/src/ui/formatter.mjs +409 -0
  261. package/src/ui/icons.mjs +164 -0
  262. package/src/ui/input-dock.mjs +444 -0
  263. package/src/ui/markdown.mjs +278 -0
  264. package/src/ui/mission-report.mjs +296 -0
  265. package/src/ui/palette.mjs +189 -0
  266. package/src/ui/slash-commands.mjs +245 -0
  267. package/src/ui/spinner.mjs +116 -0
  268. package/src/ui/sub-agent.mjs +152 -0
  269. package/src/ui/term.mjs +159 -0
  270. package/src/ui/text-layout.mjs +127 -0
  271. package/src/ui/tool-card.mjs +463 -0
  272. package/src/ui/tool-details.mjs +312 -0
  273. package/src/ui/transcript-block.mjs +21 -0
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @bahulamai/code
2
+
3
+ **Bahulam Code** is Bahulam's AI coding agent for terminal-first software work.
4
+ It inspects a repo, plans changes, runs tools, asks for human approval, resumes
5
+ prior sessions, and keeps local project context in `~/.bahulam/`.
6
+
7
+ - 65.6% SWE-bench Verified
8
+ - CLI-first, reliability-first
9
+ - Sub-agents, skills, workflows, and MCP support
10
+ - Bring your own model (40+ supported)
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g @bahulamai/code@latest
16
+ ```
17
+
18
+ Run without global install:
19
+
20
+ ```bash
21
+ npx @bahulamai/code@latest
22
+ ```
23
+
24
+ ## Start the CLI
25
+
26
+ After install, sign in and launch:
27
+
28
+ ```bash
29
+ bahulam-code login # one-time: sign in through the browser
30
+ bahulam-code # start the interactive REPL
31
+ ```
32
+
33
+ Or run a single instruction and exit:
34
+
35
+ ```bash
36
+ bahulam-code "fix the failing auth test"
37
+ ```
38
+
39
+ ## Common Commands
40
+
41
+ ```text
42
+ bahulam-code Start interactive REPL
43
+ bahulam-code "instruction" Run a single instruction and exit
44
+ bahulam-code login Sign in through the browser
45
+ bahulam-code configure Open settings in your browser
46
+ bahulam-code config --show Display local configuration
47
+ bahulam-code resume Resume a paused or previous session
48
+ bahulam-code --version Show installed version
49
+ bahulam-code --help Full command reference
50
+ ```
51
+
52
+ Inside the REPL, type `/help` for slash commands (models, cache, approvals,
53
+ skills, workflows, session tools).
54
+
55
+ ## 2.6.0 Highlights
56
+
57
+ - Rebrand: `@bahulamai/b0` → `@bahulamai/code`; binary `b0` → `bahulam-code`.
58
+ - Redesigned fixed input dock — labeled "Bahulam Code" identity, colored
59
+ border, horizontal and vertical margins, meta row showing project, git
60
+ branch, turn count, and token usage.
61
+ - Extended session-context meta line under the input dock.
62
+
63
+ ## Development
64
+
65
+ ```bash
66
+ npm test
67
+ npm pack --dry-run
68
+ ```
69
+
70
+ See [RELEASE.md](./RELEASE.md) for the merge, PR, and npm publish checklist.
71
+
72
+ ## Links
73
+
74
+ - Website: https://bahulam.ai
75
+ - Company: https://axplusb.tech
76
+ - Repository: https://github.com/raviakasapu/codekepler-npm
77
+
78
+ ## License
79
+
80
+ MIT
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@bahulam/code",
3
+ "version": "2.6.0",
4
+ "description": "Bahulam Code — Bahulam's coding agent. CLI-first, reliability-first, sub-agents, 65.6% SWE-bench Verified.",
5
+ "type": "module",
6
+ "bin": {
7
+ "bahulam-code": "src/terminal/main.mjs"
8
+ },
9
+ "files": [
10
+ "src/",
11
+ "pulse/",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "start": "node src/terminal/main.mjs",
16
+ "test": "BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-backend-url.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-agent-history.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-sse-client.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-tool-executor.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-project-artifacts.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-work-scope.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-skills.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-callback.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-rate-limit-display.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-preflight.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-formatter.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-terminal-rendering.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-input-dock.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-slash-commands.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-approval.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-approval-log.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-kepler-contract.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-session-manager.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-safety.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-jsonl-writer.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-analytics.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-stagnation.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-attachments.mjs"
17
+ },
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "keywords": [
25
+ "bahulam",
26
+ "bahulam-code",
27
+ "ai",
28
+ "coding-agent",
29
+ "swe-bench",
30
+ "cli",
31
+ "code-generation",
32
+ "deepseek",
33
+ "claude",
34
+ "gpt",
35
+ "gemini",
36
+ "axplusb"
37
+ ],
38
+ "license": "MIT",
39
+ "author": "axplusb.tech <hello@axplusb.tech>",
40
+ "homepage": "https://bahulam.ai/code",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/raviakasapu/codekepler-npm.git"
44
+ },
45
+ "dependencies": {
46
+ "tree-sitter-wasms": "^0.1.13",
47
+ "web-tree-sitter": "^0.26.9"
48
+ }
49
+ }
@@ -0,0 +1,190 @@
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 b0 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
+ }
@@ -0,0 +1,138 @@
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
+ }
@@ -0,0 +1,113 @@
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
+ }