@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.
- package/README.md +80 -0
- package/package.json +49 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- 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 +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- 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 +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +222 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +306 -0
- package/src/commands/agent.mjs +220 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +200 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +308 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +104 -0
- package/src/core/approval.mjs +476 -0
- package/src/core/attachments.mjs +380 -0
- package/src/core/backend-url.mjs +55 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +311 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +448 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +537 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +337 -0
- package/src/core/safety.mjs +203 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +829 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +1950 -0
- package/src/core/trust.mjs +158 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +426 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +360 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +652 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +269 -0
- package/src/terminal/repl-explore.mjs +35 -0
- package/src/terminal/repl-format.mjs +257 -0
- package/src/terminal/repl-render.mjs +561 -0
- package/src/terminal/repl-resume.mjs +625 -0
- package/src/terminal/repl-state.mjs +103 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +3832 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +240 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +641 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +263 -0
- package/src/ui/banner.mjs +235 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/input-dock.mjs +444 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/slash-commands.mjs +245 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +463 -0
- package/src/ui/tool-details.mjs +312 -0
- 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
|
+
}
|