@polderlabs/bizar 6.2.4 → 6.3.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/.claude/CLAUDE.md +15 -0
- package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
- package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
- package/.claude/agents/_shared/SKILLS.md +109 -0
- package/.claude/agents/agent-browser.md +80 -0
- package/.claude/agents/baldr.md +49 -0
- package/.claude/agents/forseti.md +51 -0
- package/.claude/agents/frigg.md +42 -0
- package/.claude/agents/heimdall.md +33 -0
- package/.claude/agents/hermod.md +53 -0
- package/.claude/agents/mimir.md +49 -0
- package/.claude/agents/odin.md +287 -0
- package/.claude/agents/quick.md +34 -0
- package/.claude/agents/semble-search.md +50 -0
- package/.claude/agents/thor.md +53 -0
- package/.claude/agents/tyr.md +56 -0
- package/.claude/agents/vidarr.md +54 -0
- package/.claude/agents/vor.md +53 -0
- package/.claude/commands/audit.md +25 -0
- package/.claude/commands/bizar.md +22 -0
- package/.claude/commands/explain.md +17 -0
- package/.claude/commands/init.md +29 -0
- package/.claude/commands/learn.md +44 -0
- package/.claude/commands/plan.md +35 -0
- package/.claude/commands/plow-through.md +50 -0
- package/.claude/commands/pr-review.md +49 -0
- package/.claude/commands/setup-provider.md +96 -0
- package/.claude/commands/tailscale-serve.md +100 -0
- package/.claude/commands/team.md +132 -0
- package/.claude/commands/test.md +62 -0
- package/.claude/commands/validate.md +68 -0
- package/.claude/commands/visual-plan.md +24 -0
- package/.claude/hooks/README.md +92 -0
- package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
- package/.claude/hooks/pretooluse-bash.mjs +81 -0
- package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
- package/.claude/hooks/sessionend-recall.mjs +74 -0
- package/.claude/hooks/sessionstart-prime.mjs +80 -0
- package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
- package/.claude/settings.json +116 -0
- package/.claude/skills/9router/SKILL.md +80 -0
- package/.claude/skills/9router-chat/SKILL.md +73 -0
- package/.claude/skills/9router-embeddings/SKILL.md +69 -0
- package/.claude/skills/9router-image/SKILL.md +86 -0
- package/.claude/skills/9router-stt/SKILL.md +79 -0
- package/.claude/skills/9router-tts/SKILL.md +80 -0
- package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
- package/.claude/skills/9router-web-search/SKILL.md +91 -0
- package/.claude/skills/bizar/README.md +9 -0
- package/.claude/skills/bizar/SKILL.md +450 -0
- package/.claude/skills/cpp-coding-standards/README.md +28 -0
- package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
- package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
- package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
- package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
- package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
- package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
- package/.claude/skills/cpp-testing/README.md +28 -0
- package/.claude/skills/cpp-testing/SKILL.md +304 -0
- package/.claude/skills/cpp-testing/references/coverage.md +370 -0
- package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
- package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
- package/.claude/skills/cpp-testing/references/mocking.md +364 -0
- package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
- package/.claude/skills/cubesandbox/SKILL.md +148 -0
- package/.claude/skills/embedded-esp-idf/README.md +41 -0
- package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
- package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
- package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
- package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
- package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
- package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
- package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
- package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
- package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
- package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
- package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
- package/.claude/skills/glyph/SKILL.md +163 -0
- package/.claude/skills/harness-engineering/SKILL.md +143 -0
- package/.claude/skills/lightrag/SKILL.md +81 -0
- package/.claude/skills/memory-protocol/SKILL.md +105 -0
- package/.claude/skills/obsidian/SKILL.md +306 -0
- package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
- package/.claude/skills/self-improvement/SKILL.md +64 -0
- package/README.md +87 -59
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
- package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
- package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
- package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
- package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
- package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
- package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
- package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
- package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
- package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
- package/bizar-dash/dist/index.html +6 -6
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/api.mjs +4 -4
- package/bizar-dash/src/server/background-store.mjs +15 -16
- package/bizar-dash/src/server/bg-poller.mjs +25 -35
- package/bizar-dash/src/server/bg-retry.mjs +66 -194
- package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
- package/bizar-dash/src/server/claude-info.mjs +412 -0
- package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
- package/bizar-dash/src/server/claude-sdk.mjs +191 -0
- package/bizar-dash/src/server/providers-store.mjs +22 -49
- package/bizar-dash/src/server/routes/background.mjs +24 -20
- package/bizar-dash/src/server/routes/chat.mjs +223 -326
- package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
- package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
- package/bizar-dash/src/server/routes/tasks.mjs +20 -28
- package/bizar-dash/src/server/task-delegator.mjs +76 -77
- package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
- package/bizar-dash/src/web/views/Chat.tsx +8 -8
- package/cli/bin.mjs +31 -18
- package/cli/commands/claude-cmd.mjs +348 -0
- package/cli/commands/install.mjs +12 -11
- package/cli/commands/sandbox.mjs +220 -0
- package/cli/commands/validate.mjs +252 -288
- package/cli/dev-link.test.mjs +11 -11
- package/cli/doctor.mjs +131 -213
- package/cli/install.mjs +33 -37
- package/cli/install.test.mjs +9 -9
- package/cli/provision-claude.mjs +893 -0
- package/cli/provision.mjs +330 -226
- package/cli/provision.test.mjs +102 -0
- package/cli/utils.mjs +72 -58
- package/config/AGENTS.md +28 -28
- package/config/agents/_shared/AGENT_BASELINE.md +104 -684
- package/config/skills/bizar/SKILL.md +197 -0
- package/config/skills/cubesandbox/SKILL.md +148 -0
- package/config/skills/harness-engineering/SKILL.md +142 -0
- package/install.sh +66 -44
- package/package.json +19 -11
- package/packages/sdk/ARCHITECTURE.md +40 -36
- package/packages/sdk/package-lock.json +32 -0
- package/packages/sdk/package.json +15 -5
- package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
- package/packages/sdk/src/fingerprint.ts +0 -0
- package/packages/sdk/src/index.ts +38 -73
- package/packages/sdk/src/mcp/bin.ts +51 -0
- package/packages/sdk/src/mcp/server.ts +498 -0
- package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
- package/packages/sdk/tests/sdk.test.mjs +148 -0
- package/packages/sdk/vitest.config.ts +1 -1
- package/scripts/bh-full-e2e.mjs +166 -363
- package/scripts/check-deps.mjs +28 -46
- package/scripts/mirror-claude-md.sh +78 -0
- package/scripts/test-in-container.sh +157 -0
- package/templates/clean-state-checklist.md +3 -3
- package/templates/sprint-contract.md +2 -2
- package/bizar-dash/.bizar/activity.log +0 -3
- package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
- package/bizar-dash/.omx/state/session.json +0 -10
- package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
- package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
- package/bizar-dash/bizar-design/canvas.html +0 -281
- package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/components.css +0 -1665
- package/bizar-dash/bizar-design/components.html +0 -557
- package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/agents.html +0 -74
- package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/memory.html +0 -75
- package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
- package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/overview.html +0 -95
- package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/desktop/settings.html +0 -70
- package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/icons.svg +0 -44
- package/bizar-dash/bizar-design/index.html +0 -192
- package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/agents.html +0 -29
- package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/memory.html +0 -28
- package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
- package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/overview.html +0 -33
- package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/mobile/settings.html +0 -28
- package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
- package/bizar-dash/bizar-design/prototype.js +0 -114
- package/bizar-dash/bizar-design/tokens.css +0 -106
- package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
- package/bizar-dash/node_modules/.package-lock.json +0 -6
- package/bizar-dash/package-lock.json +0 -6
- package/bizar-dash/src/server/cline-sdk.mjs +0 -132
- package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
- package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
- package/cli/commands/cline-cmd.mjs +0 -289
- package/cli/commands/validate.test.mjs +0 -344
- package/cli/doctor.test.mjs +0 -350
- package/config/cline.json.template +0 -342
- package/packages/sdk/src/client.ts +0 -188
- package/packages/sdk/src/cline-events.ts +0 -134
- package/packages/sdk/src/cline-types.ts +0 -66
- package/packages/sdk/src/cline.ts +0 -339
- package/packages/sdk/src/errors.ts +0 -129
- package/packages/sdk/src/events.ts +0 -153
- package/packages/sdk/src/types.ts +0 -176
- package/packages/sdk/tests/client.test.ts +0 -217
- package/packages/sdk/tests/errors.test.ts +0 -108
- package/packages/sdk/tests/events.test.ts +0 -139
- package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
- package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
- package/plugins/bizar/ARCHITECTURE.md +0 -142
- package/plugins/bizar/CONSTRAINTS.md +0 -67
- package/plugins/bizar/LICENSE +0 -21
- package/plugins/bizar/README.md +0 -448
- package/plugins/bizar/index.ts +0 -872
- package/plugins/bizar/package.json +0 -39
- package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
- package/plugins/bizar/src/background-state.ts +0 -641
- package/plugins/bizar/src/background.ts +0 -1806
- package/plugins/bizar/src/cline-runner.ts +0 -203
- package/plugins/bizar/src/clineruntime.ts +0 -227
- package/plugins/bizar/src/commands-impl.ts +0 -151
- package/plugins/bizar/src/commands.ts +0 -1799
- package/plugins/bizar/src/compaction.d.mts +0 -48
- package/plugins/bizar/src/compaction.mjs +0 -192
- package/plugins/bizar/src/dashboard-client.ts +0 -233
- package/plugins/bizar/src/event-stream.ts +0 -606
- package/plugins/bizar/src/fingerprint.ts +0 -120
- package/plugins/bizar/src/handoff.ts +0 -79
- package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
- package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
- package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
- package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
- package/plugins/bizar/src/http-client.ts +0 -467
- package/plugins/bizar/src/key-rotation.ts +0 -218
- package/plugins/bizar/src/logger.ts +0 -144
- package/plugins/bizar/src/loop-engineering.ts +0 -241
- package/plugins/bizar/src/loop.ts +0 -176
- package/plugins/bizar/src/mistake-recovery.ts +0 -98
- package/plugins/bizar/src/odin.ts +0 -227
- package/plugins/bizar/src/options.ts +0 -470
- package/plugins/bizar/src/plan-fs.ts +0 -323
- package/plugins/bizar/src/reasoning-clean.ts +0 -454
- package/plugins/bizar/src/report.ts +0 -178
- package/plugins/bizar/src/research-prompt.ts +0 -35
- package/plugins/bizar/src/serve-info.ts +0 -228
- package/plugins/bizar/src/serve.ts +0 -496
- package/plugins/bizar/src/settings.ts +0 -349
- package/plugins/bizar/src/state.ts +0 -298
- package/plugins/bizar/src/tool-discipline.ts +0 -105
- package/plugins/bizar/src/tools/agent-browser.ts +0 -315
- package/plugins/bizar/src/tools/bg-collect.ts +0 -131
- package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
- package/plugins/bizar/src/tools/bg-kill.ts +0 -116
- package/plugins/bizar/src/tools/bg-pause.ts +0 -99
- package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
- package/plugins/bizar/src/tools/bg-resume.ts +0 -94
- package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
- package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
- package/plugins/bizar/src/tools/bg-status.ts +0 -130
- package/plugins/bizar/src/tools/graph-query.ts +0 -278
- package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
- package/plugins/bizar/src/tools/memory-list.ts +0 -43
- package/plugins/bizar/src/tools/memory-read.ts +0 -69
- package/plugins/bizar/src/tools/memory-search.ts +0 -47
- package/plugins/bizar/src/tools/memory-write.ts +0 -54
- package/plugins/bizar/src/tools/open-kb.ts +0 -198
- package/plugins/bizar/src/tools/plan-action.ts +0 -785
- package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
- package/plugins/bizar/src/tools/team-spawn.ts +0 -73
- package/plugins/bizar/src/tools/team-status.ts +0 -76
- package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
- package/plugins/bizar/src/trajectory.ts +0 -104
- package/plugins/bizar/tests/README.md +0 -99
- package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
- package/plugins/bizar/tests/background-state.test.ts +0 -277
- package/plugins/bizar/tests/background.test.ts +0 -402
- package/plugins/bizar/tests/block.test.ts +0 -195
- package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
- package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
- package/plugins/bizar/tests/commands-impl.test.ts +0 -316
- package/plugins/bizar/tests/commands.test.ts +0 -584
- package/plugins/bizar/tests/compaction.test.ts +0 -264
- package/plugins/bizar/tests/config.test.ts +0 -128
- package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
- package/plugins/bizar/tests/dispose.test.ts +0 -336
- package/plugins/bizar/tests/event-stream.test.ts +0 -409
- package/plugins/bizar/tests/event.test.ts +0 -262
- package/plugins/bizar/tests/fingerprint.test.ts +0 -162
- package/plugins/bizar/tests/http-client.test.ts +0 -404
- package/plugins/bizar/tests/init-helpers.test.ts +0 -203
- package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
- package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
- package/plugins/bizar/tests/key-rotation.test.ts +0 -396
- package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
- package/plugins/bizar/tests/loop.test.ts +0 -397
- package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
- package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
- package/plugins/bizar/tests/odin.test.ts +0 -125
- package/plugins/bizar/tests/options.test.ts +0 -329
- package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
- package/plugins/bizar/tests/safety.test.ts +0 -256
- package/plugins/bizar/tests/serve.test.ts +0 -339
- package/plugins/bizar/tests/settings.test.ts +0 -351
- package/plugins/bizar/tests/stall-think.test.ts +0 -750
- package/plugins/bizar/tests/state.test.ts +0 -276
- package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
- package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
- package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
- package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
- package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
- package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
- package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
- package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
- package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
- package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
- package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
- package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
- package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
- package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
- package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
- package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
- package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
- package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
- package/plugins/bizar/tsconfig.json +0 -29
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/claude-info.mjs
|
|
3
|
+
*
|
|
4
|
+
* v6.3.0 — Dashboard-side documentation + discovery for the Claude
|
|
5
|
+
* Code runtime. Replaces serve-info.mjs (which documented the Cline
|
|
6
|
+
* HTTP serve subprocess contract).
|
|
7
|
+
*
|
|
8
|
+
* Why this module exists:
|
|
9
|
+
* Claude Code does NOT have an out-of-process serve child like
|
|
10
|
+
* Cline's `cline serve`. Instead, the dashboard talks to the
|
|
11
|
+
* `claude` CLI directly:
|
|
12
|
+
*
|
|
13
|
+
* - `claude -p "<prompt>"` — one-shot, runs to completion
|
|
14
|
+
* - `claude --bg` — background daemon session
|
|
15
|
+
* - `claude --resume <id> -p …` — resume an existing session
|
|
16
|
+
*
|
|
17
|
+
* And the SDK (`@anthropic-ai/claude-agent-sdk`) gives us in-process
|
|
18
|
+
* `query()` + `ClaudeSDKClient` for typed access to streaming events.
|
|
19
|
+
*
|
|
20
|
+
* This module:
|
|
21
|
+
* 1. Documents the CLI flag contract (so other modules don't have
|
|
22
|
+
* to hardcode flag names).
|
|
23
|
+
* 2. Documents the on-disk session storage at `~/.claude/sessions/`.
|
|
24
|
+
* 3. Resolves the `claude` binary path (PATH first, then `npx
|
|
25
|
+
* @anthropic-ai/claude-agent-sdk/cli`).
|
|
26
|
+
* 4. Provides small JSONL helpers for reading session message
|
|
27
|
+
* history that the rest of the dashboard can use.
|
|
28
|
+
*
|
|
29
|
+
* Reference:
|
|
30
|
+
* https://code.claude.com/docs/en/cli-reference
|
|
31
|
+
*
|
|
32
|
+
* No HTTP serve handshake, no Basic auth header, no SQLite DB. Claude
|
|
33
|
+
* Code reads API keys from the environment itself.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { existsSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
37
|
+
import { homedir } from 'node:os';
|
|
38
|
+
import { join, resolve } from 'node:path';
|
|
39
|
+
import { warn as loggerWarn } from './logger.mjs';
|
|
40
|
+
|
|
41
|
+
const HOME = homedir();
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Canonical on-disk paths for Claude Code.
|
|
45
|
+
*
|
|
46
|
+
* ~/.claude/ — root config + data dir (per Claude docs)
|
|
47
|
+
* ~/.claude/sessions/ — one subdir per session
|
|
48
|
+
* <sessionId>/
|
|
49
|
+
* messages.jsonl — conversation history (one JSON per line)
|
|
50
|
+
*
|
|
51
|
+
* Each messages.jsonl line is one of:
|
|
52
|
+
* - {type:"user", message:{role:"user", content:[...]}}
|
|
53
|
+
* - {type:"assistant", message:{role:"assistant", content:[...]}}
|
|
54
|
+
* - {type:"system", subtype:"init", session_id, model, ...}
|
|
55
|
+
* - {type:"result", subtype:"success"|"error_during_execution", ...}
|
|
56
|
+
*/
|
|
57
|
+
export const CLAUDE_HOME = join(HOME, '.claude');
|
|
58
|
+
export const CLAUDE_SESSIONS_DIR = join(CLAUDE_HOME, 'sessions');
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the `claude` binary path. Tries (in order):
|
|
62
|
+
* 1. `process.env.CLAUDE_BIN` (override)
|
|
63
|
+
* 2. `claude` on PATH (the user-installed case)
|
|
64
|
+
* 3. `npx -y @anthropic-ai/claude-agent-sdk/cli` as a fallback
|
|
65
|
+
*
|
|
66
|
+
* Returns `{ bin, args }` so the caller can construct the right
|
|
67
|
+
* subprocess argv. The fallback uses `npx -y` so a fresh checkout
|
|
68
|
+
* can spawn Claude Code even when the CLI isn't installed globally.
|
|
69
|
+
*
|
|
70
|
+
* @returns {{ bin: string, args: string[] }}
|
|
71
|
+
*/
|
|
72
|
+
export function resolveClaudeCommand() {
|
|
73
|
+
if (process.env.CLAUDE_BIN && process.env.CLAUDE_BIN.trim()) {
|
|
74
|
+
return { bin: process.env.CLAUDE_BIN.trim(), args: [] };
|
|
75
|
+
}
|
|
76
|
+
return { bin: 'claude', args: [] };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Claude Code CLI flag contract (verified against
|
|
81
|
+
* https://code.claude.com/docs/en/cli-reference). Single source of
|
|
82
|
+
* truth — every other module reads flags from here so a Claude
|
|
83
|
+
* update only changes one file.
|
|
84
|
+
*/
|
|
85
|
+
export const CLAUDE_CLI_FLAGS = Object.freeze({
|
|
86
|
+
// Print / run modes
|
|
87
|
+
PRINT: '-p',
|
|
88
|
+
BACKGROUND: '--bg',
|
|
89
|
+
RESUME: '--resume',
|
|
90
|
+
SESSION_ID: '--session-id',
|
|
91
|
+
SESSION_TITLE: '--session-title',
|
|
92
|
+
|
|
93
|
+
// Model + agent
|
|
94
|
+
MODEL: '--model',
|
|
95
|
+
AGENT: '--agent',
|
|
96
|
+
|
|
97
|
+
// Working directory
|
|
98
|
+
ADD_DIR: '--add-dir',
|
|
99
|
+
|
|
100
|
+
// Output / wire format
|
|
101
|
+
OUTPUT_FORMAT: '--output-format',
|
|
102
|
+
VERBOSE: '--verbose',
|
|
103
|
+
JSON_FORMAT: 'json',
|
|
104
|
+
|
|
105
|
+
// Permission / autonomy
|
|
106
|
+
PERMISSION_MODE: '--permission-mode',
|
|
107
|
+
PERMISSION_BYPASS: 'bypassPermissions',
|
|
108
|
+
PERMISSION_PLAN: 'plan',
|
|
109
|
+
PERMISSION_DEFAULT: 'default',
|
|
110
|
+
|
|
111
|
+
// MCP + skills
|
|
112
|
+
MCP_CONFIG: '--mcp-config',
|
|
113
|
+
|
|
114
|
+
// Limits
|
|
115
|
+
MAX_TURNS: '--max-turns',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Claude Code stores sessions as JSONL. Normalize one parsed line
|
|
120
|
+
* into the dashboard's chat shape:
|
|
121
|
+
*
|
|
122
|
+
* { id, role, content, ts, kind? }
|
|
123
|
+
*
|
|
124
|
+
* Accepts both v1 shapes (text directly on the message) and v2
|
|
125
|
+
* shapes (parts / blocks under `content`).
|
|
126
|
+
*
|
|
127
|
+
* @param {object} line one entry from messages.jsonl
|
|
128
|
+
* @returns {{ id: string, role: string, content: string, ts: number, kind?: string }}
|
|
129
|
+
*/
|
|
130
|
+
export function normalizeClaudeMessage(line) {
|
|
131
|
+
if (!line || typeof line !== 'object') {
|
|
132
|
+
return { id: '', role: 'assistant', content: '', ts: Date.now() };
|
|
133
|
+
}
|
|
134
|
+
const t = typeof line.type === 'string' ? line.type : null;
|
|
135
|
+
const message = line.message || line;
|
|
136
|
+
const id =
|
|
137
|
+
(typeof message?.id === 'string' && message.id) ||
|
|
138
|
+
(typeof line.id === 'string' && line.id) ||
|
|
139
|
+
(typeof line.uuid === 'string' && line.uuid) ||
|
|
140
|
+
(typeof line.timestamp === 'string' && line.timestamp) ||
|
|
141
|
+
'';
|
|
142
|
+
const role = typeof message?.role === 'string' ? message.role : (t === 'user' ? 'user' : 'assistant');
|
|
143
|
+
const ts =
|
|
144
|
+
(typeof line.timestamp === 'string' ? Date.parse(line.timestamp) : NaN) ||
|
|
145
|
+
(typeof line.ts === 'number' ? line.ts : NaN) ||
|
|
146
|
+
(typeof message?.ts === 'number' ? message.ts : NaN) ||
|
|
147
|
+
Date.now();
|
|
148
|
+
const content = extractClaudeContent(message);
|
|
149
|
+
const out = { id: String(id), role: String(role), content, ts: typeof ts === 'number' && Number.isFinite(ts) ? ts : Date.now() };
|
|
150
|
+
if (t) out.kind = t;
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Flatten a Claude Code message's `content` field into a single
|
|
156
|
+
* string. Handles:
|
|
157
|
+
* - string content ("hello")
|
|
158
|
+
* - array of blocks (text, tool_use, tool_result, image, …)
|
|
159
|
+
* - legacy { text } wrapper
|
|
160
|
+
*
|
|
161
|
+
* Non-text blocks are skipped — they don't contribute to the chat
|
|
162
|
+
* surface but still appear in the JSONL for tooling.
|
|
163
|
+
*/
|
|
164
|
+
function extractClaudeContent(message) {
|
|
165
|
+
if (!message) return '';
|
|
166
|
+
if (typeof message.text === 'string') return stripThinkingTags(message.text);
|
|
167
|
+
const c = message.content;
|
|
168
|
+
if (typeof c === 'string') return stripThinkingTags(c);
|
|
169
|
+
if (!Array.isArray(c)) return '';
|
|
170
|
+
const text = c
|
|
171
|
+
.filter((p) => p && (p.type === 'text' || typeof p.text === 'string'))
|
|
172
|
+
.map((p) => (typeof p.text === 'string' ? p.text : ''))
|
|
173
|
+
.filter(Boolean)
|
|
174
|
+
.join('\n\n');
|
|
175
|
+
return stripThinkingTags(text);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Strip `<thinking>...</thinking>` blocks from model output so the
|
|
180
|
+
* chat UI doesn't render them as visible escaped HTML.
|
|
181
|
+
*
|
|
182
|
+
* Same shape as the v5.0.0 cline helper — kept here so chat
|
|
183
|
+
* rendering stays consistent across the migration.
|
|
184
|
+
*/
|
|
185
|
+
function stripThinkingTags(text) {
|
|
186
|
+
if (typeof text !== 'string') return '';
|
|
187
|
+
const cleaned = text
|
|
188
|
+
.replace(/<thinking\b[^>]*>[\s\S]*?<\/thinking>/gi, '')
|
|
189
|
+
.replace(/<thinking\b[^>]*\/?>/gi, '')
|
|
190
|
+
.replace(/<\/thinking>/gi, '');
|
|
191
|
+
return cleaned.replace(/\n{3,}/g, '\n\n').trim();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* List every Claude Code session currently on disk. Reads
|
|
196
|
+
* `~/.claude/sessions/` and returns metadata derived from each
|
|
197
|
+
* `messages.jsonl` (first system/init line carries the model +
|
|
198
|
+
* session id; first user message carries the title).
|
|
199
|
+
*
|
|
200
|
+
* Returns `[]` when the directory doesn't exist (a fresh user has
|
|
201
|
+
* never run `claude`). Never throws.
|
|
202
|
+
*
|
|
203
|
+
* @returns {Array<{ id: string, title: string, createdAt: number, updatedAt: number, model?: string }>}
|
|
204
|
+
*/
|
|
205
|
+
export function listClaudeSessions() {
|
|
206
|
+
if (!existsSync(CLAUDE_SESSIONS_DIR)) return [];
|
|
207
|
+
let dirs;
|
|
208
|
+
try {
|
|
209
|
+
dirs = readdirSync(CLAUDE_SESSIONS_DIR, { withFileTypes: true })
|
|
210
|
+
.filter((d) => d.isDirectory())
|
|
211
|
+
.map((d) => d.name);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
loggerWarn('claude-info: listClaudeSessions readdir failed', { err: err instanceof Error ? err.message : String(err) });
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
const out = [];
|
|
217
|
+
for (const id of dirs) {
|
|
218
|
+
const file = join(CLAUDE_SESSIONS_DIR, id, 'messages.jsonl');
|
|
219
|
+
if (!existsSync(file)) continue;
|
|
220
|
+
const meta = readSessionMeta(file, id);
|
|
221
|
+
if (meta) out.push(meta);
|
|
222
|
+
}
|
|
223
|
+
out.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Read the metadata header for a single session (first init line +
|
|
229
|
+
* first user message + last mtime). Returns `null` on any failure.
|
|
230
|
+
*
|
|
231
|
+
* @param {string} file absolute path to messages.jsonl
|
|
232
|
+
* @param {string} id session id (the directory name)
|
|
233
|
+
*/
|
|
234
|
+
function readSessionMeta(file, id) {
|
|
235
|
+
let firstUserText = '';
|
|
236
|
+
let model = '';
|
|
237
|
+
let createdAt = 0;
|
|
238
|
+
let lastTs = 0;
|
|
239
|
+
let size = 0;
|
|
240
|
+
try {
|
|
241
|
+
const stat = statSync(file);
|
|
242
|
+
size = stat.size;
|
|
243
|
+
lastTs = stat.mtimeMs || 0;
|
|
244
|
+
} catch {
|
|
245
|
+
/* fall through — stat is best-effort */
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const text = readFileSync(file, 'utf8');
|
|
249
|
+
const lines = text.split('\n');
|
|
250
|
+
for (const raw of lines) {
|
|
251
|
+
const line = raw.trim();
|
|
252
|
+
if (!line) continue;
|
|
253
|
+
let parsed;
|
|
254
|
+
try { parsed = JSON.parse(line); } catch { continue; }
|
|
255
|
+
if (parsed?.type === 'system' && parsed?.subtype === 'init') {
|
|
256
|
+
if (typeof parsed.session_id === 'string') id = parsed.session_id;
|
|
257
|
+
if (typeof parsed.model === 'string') model = parsed.model;
|
|
258
|
+
if (typeof parsed.timestamp === 'string') {
|
|
259
|
+
const t = Date.parse(parsed.timestamp);
|
|
260
|
+
if (Number.isFinite(t)) createdAt = createdAt || t;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (parsed?.type === 'user' && !firstUserText) {
|
|
264
|
+
const content = parsed?.message?.content;
|
|
265
|
+
if (typeof content === 'string') firstUserText = content;
|
|
266
|
+
else if (Array.isArray(content)) {
|
|
267
|
+
const t = content.find((b) => b && (b.type === 'text' || typeof b.text === 'string'));
|
|
268
|
+
if (t && typeof t.text === 'string') firstUserText = t.text;
|
|
269
|
+
}
|
|
270
|
+
if (typeof parsed.timestamp === 'string') {
|
|
271
|
+
const t = Date.parse(parsed.timestamp);
|
|
272
|
+
if (Number.isFinite(t)) createdAt = createdAt || t;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (typeof parsed?.timestamp === 'string') {
|
|
276
|
+
const t = Date.parse(parsed.timestamp);
|
|
277
|
+
if (Number.isFinite(t)) lastTs = Math.max(lastTs, t);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const title = firstUserText
|
|
284
|
+
? firstUserText.replace(/\s+/g, ' ').trim().slice(0, 80) || `Session ${id}`
|
|
285
|
+
: `Session ${id}`;
|
|
286
|
+
return {
|
|
287
|
+
id,
|
|
288
|
+
title,
|
|
289
|
+
createdAt: createdAt || lastTs || 0,
|
|
290
|
+
updatedAt: lastTs || createdAt || 0,
|
|
291
|
+
model: model || undefined,
|
|
292
|
+
size,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* List every message in a session. Reads `messages.jsonl` from
|
|
298
|
+
* `~/.claude/sessions/<id>/` and normalizes each line via
|
|
299
|
+
* {@link normalizeClaudeMessage}.
|
|
300
|
+
*
|
|
301
|
+
* Returns `{ ok, messages }` on success; `{ ok: false, error }`
|
|
302
|
+
* when the file is missing or unreadable. Never throws.
|
|
303
|
+
*
|
|
304
|
+
* @param {string} sessionId
|
|
305
|
+
* @returns {{ ok: true, messages: ReturnType<typeof normalizeClaudeMessage>[] } | { ok: false, error: string }}
|
|
306
|
+
*/
|
|
307
|
+
export function listClaudeMessages(sessionId) {
|
|
308
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
309
|
+
return { ok: false, error: 'sessionId is required' };
|
|
310
|
+
}
|
|
311
|
+
const file = join(CLAUDE_SESSIONS_DIR, sessionId, 'messages.jsonl');
|
|
312
|
+
if (!existsSync(file)) {
|
|
313
|
+
return { ok: false, error: `session ${sessionId} not found at ${file}` };
|
|
314
|
+
}
|
|
315
|
+
let text;
|
|
316
|
+
try {
|
|
317
|
+
text = readFileSync(file, 'utf8');
|
|
318
|
+
} catch (err) {
|
|
319
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
320
|
+
}
|
|
321
|
+
const out = [];
|
|
322
|
+
for (const raw of text.split('\n')) {
|
|
323
|
+
const line = raw.trim();
|
|
324
|
+
if (!line) continue;
|
|
325
|
+
let parsed;
|
|
326
|
+
try { parsed = JSON.parse(line); } catch { continue; }
|
|
327
|
+
out.push(normalizeClaudeMessage(parsed));
|
|
328
|
+
}
|
|
329
|
+
return { ok: true, messages: out };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Read the most recent assistant text from a session — used by the
|
|
334
|
+
* bg-poller's html-artifact scanner (replaces the old
|
|
335
|
+
* `extractContentFromClineMessage` + `listClineMessages` flow).
|
|
336
|
+
*
|
|
337
|
+
* @param {string} sessionId
|
|
338
|
+
* @returns {string}
|
|
339
|
+
*/
|
|
340
|
+
export function readLastAssistantText(sessionId) {
|
|
341
|
+
const res = listClaudeMessages(sessionId);
|
|
342
|
+
if (!res.ok) return '';
|
|
343
|
+
for (let i = res.messages.length - 1; i >= 0; i -= 1) {
|
|
344
|
+
const m = res.messages[i];
|
|
345
|
+
if (m && m.role === 'assistant' && m.content) return m.content;
|
|
346
|
+
}
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Resolve the on-disk worktree (directory) for a session by looking
|
|
352
|
+
* at the JSONL init line. Claude Code stores `cwd` and `addDir` on
|
|
353
|
+
* the system init event.
|
|
354
|
+
*
|
|
355
|
+
* Returns `null` when the session can't be found or has no cwd field.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} sessionId
|
|
358
|
+
* @returns {string|null}
|
|
359
|
+
*/
|
|
360
|
+
export function resolveSessionWorktree(sessionId) {
|
|
361
|
+
if (!sessionId) return null;
|
|
362
|
+
const file = join(CLAUDE_SESSIONS_DIR, sessionId, 'messages.jsonl');
|
|
363
|
+
if (!existsSync(file)) return null;
|
|
364
|
+
try {
|
|
365
|
+
const text = readFileSync(file, 'utf8');
|
|
366
|
+
for (const raw of text.split('\n')) {
|
|
367
|
+
const line = raw.trim();
|
|
368
|
+
if (!line) continue;
|
|
369
|
+
let parsed;
|
|
370
|
+
try { parsed = JSON.parse(line); } catch { continue; }
|
|
371
|
+
if (parsed?.type === 'system' && parsed?.subtype === 'init') {
|
|
372
|
+
if (typeof parsed.cwd === 'string' && parsed.cwd) return resolve(parsed.cwd);
|
|
373
|
+
if (Array.isArray(parsed.addDir) && parsed.addDir.length > 0) {
|
|
374
|
+
const first = parsed.addDir.find((d) => typeof d === 'string' && d);
|
|
375
|
+
if (first) return resolve(first);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch { /* ignore */ }
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Best-effort deletion of a session's on-disk directory. Idempotent.
|
|
385
|
+
*
|
|
386
|
+
* @param {string} sessionId
|
|
387
|
+
* @returns {{ ok: boolean, error?: string }}
|
|
388
|
+
*/
|
|
389
|
+
export function deleteClaudeSession(sessionId) {
|
|
390
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
391
|
+
return { ok: false, error: 'sessionId is required' };
|
|
392
|
+
}
|
|
393
|
+
const dir = join(CLAUDE_SESSIONS_DIR, sessionId);
|
|
394
|
+
if (!existsSync(dir)) return { ok: true };
|
|
395
|
+
try {
|
|
396
|
+
rmSync(dir, { recursive: true, force: true });
|
|
397
|
+
return { ok: true };
|
|
398
|
+
} catch (err) {
|
|
399
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Exposed for tests / introspection.
|
|
404
|
+
export const _claudeInfoInternals = {
|
|
405
|
+
CLAUDE_HOME,
|
|
406
|
+
CLAUDE_SESSIONS_DIR,
|
|
407
|
+
CLAUDE_CLI_FLAGS,
|
|
408
|
+
resolveClaudeCommand,
|
|
409
|
+
normalizeClaudeMessage,
|
|
410
|
+
listClaudeSessions,
|
|
411
|
+
listClaudeMessages,
|
|
412
|
+
};
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* bizar-dash/src/server/
|
|
2
|
+
* bizar-dash/src/server/claude-runner.mjs
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* v6.3.0 — Claude Code-native agent runner. Replaces the legacy
|
|
5
|
+
* cline-runner.mjs that spawned `cline run` subprocesses. This module
|
|
6
|
+
* spawns `claude -p` (print mode, blocking) or `claude --bg`
|
|
7
|
+
* (background, returns session id immediately).
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Claude Code's CLI flags (from https://code.claude.com/docs/en/cli-reference):
|
|
10
|
+
* -p "<prompt>" : print mode (run, output, exit)
|
|
11
|
+
* --output-format json : JSON-lines output (one message per line)
|
|
12
|
+
* --model <model> : sonnet | opus | haiku (or full ID)
|
|
13
|
+
* --agent <name> : pre-defined subagent name from .claude/agents/
|
|
14
|
+
* --add-dir <path> : add a working directory
|
|
15
|
+
* --mcp-config <path> : load MCP config from JSON
|
|
16
|
+
* --session-id <id> : resume a specific session
|
|
17
|
+
* --verbose : verbose logging to stderr
|
|
10
18
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
19
|
+
* Background mode (--bg) keeps the agent alive in a daemon process.
|
|
20
|
+
* The output is monitored via the SDK (query() streams events) — for
|
|
21
|
+
* the dashboard's HTTP layer we just need: spawn, list, kill, status.
|
|
13
22
|
*/
|
|
23
|
+
|
|
14
24
|
import { spawn } from 'node:child_process';
|
|
15
25
|
import { createWriteStream, mkdirSync, existsSync } from 'node:fs';
|
|
16
26
|
import { dirname } from 'node:path';
|
|
@@ -29,12 +39,13 @@ import { dirname } from 'node:path';
|
|
|
29
39
|
*
|
|
30
40
|
* @typedef {Object} SpawnAgentOptions
|
|
31
41
|
* @property {string} prompt
|
|
32
|
-
* @property {string} agent
|
|
33
|
-
* @property {
|
|
42
|
+
* @property {string} [agent] subagent name from .claude/agents/
|
|
43
|
+
* @property {string} [model] sonnet | opus | haiku | provider/model
|
|
34
44
|
* @property {string} worktree
|
|
35
45
|
* @property {string} logPath
|
|
36
46
|
* @property {string} [title]
|
|
37
47
|
* @property {Object<string,string>} [env]
|
|
48
|
+
* @property {boolean} [background] if true, run via `claude --bg` (persistent)
|
|
38
49
|
* @property {number} [sessionIdTimeoutMs]
|
|
39
50
|
*
|
|
40
51
|
* @typedef {Object} SpawnAgentResult
|
|
@@ -48,8 +59,8 @@ import { dirname } from 'node:path';
|
|
|
48
59
|
const agents = new Map();
|
|
49
60
|
|
|
50
61
|
/**
|
|
51
|
-
* Spawn a single `
|
|
52
|
-
* child has reported its session id in the
|
|
62
|
+
* Spawn a single `claude` (or `claude --bg`) process. Resolves once
|
|
63
|
+
* the child has reported its session id in the JSON output stream
|
|
53
64
|
* (or once the process exits before that).
|
|
54
65
|
*
|
|
55
66
|
* @param {SpawnAgentOptions} opts
|
|
@@ -69,30 +80,32 @@ export function spawnAgent(opts) {
|
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
// 2. Build argv.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
args.push('--', opts.
|
|
83
|
+
// 2. Build argv. `claude --bg` keeps the session alive as a daemon;
|
|
84
|
+
// `claude -p` runs to completion and exits.
|
|
85
|
+
const args = [];
|
|
86
|
+
if (opts.background) args.push('--bg');
|
|
87
|
+
else args.push('-p', opts.prompt);
|
|
88
|
+
|
|
89
|
+
args.push('--output-format', 'json');
|
|
90
|
+
args.push('--verbose');
|
|
91
|
+
args.push('--add-dir', opts.worktree);
|
|
92
|
+
if (opts.title) args.push('--session-title', opts.title);
|
|
93
|
+
if (opts.agent) args.push('--agent', opts.agent);
|
|
94
|
+
if (opts.model) args.push('--model', opts.model);
|
|
84
95
|
|
|
85
|
-
// 3. Spawn the process.
|
|
96
|
+
// 3. Spawn the process. Prefer the `claude` binary on PATH; fall
|
|
97
|
+
// back to `npx @anthropic-ai/claude-agent-sdk/cli` if not found.
|
|
86
98
|
let proc;
|
|
87
99
|
try {
|
|
88
|
-
proc = spawn('
|
|
100
|
+
proc = spawn('claude', args, {
|
|
101
|
+
cwd: opts.worktree,
|
|
89
102
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
90
103
|
env: { ...process.env, ...(opts.env || {}) },
|
|
91
104
|
});
|
|
92
105
|
} catch (err) {
|
|
93
106
|
return Promise.resolve({
|
|
94
107
|
ok: false,
|
|
95
|
-
error: `failed to spawn
|
|
108
|
+
error: `failed to spawn claude: ${err.message}`,
|
|
96
109
|
});
|
|
97
110
|
}
|
|
98
111
|
|
|
@@ -107,7 +120,10 @@ export function spawnAgent(opts) {
|
|
|
107
120
|
|
|
108
121
|
// 5. Pipe stdout+stderr to the log file.
|
|
109
122
|
const logStream = createWriteStream(opts.logPath, { flags: 'a' });
|
|
110
|
-
|
|
123
|
+
|
|
124
|
+
// Claude Code prints session-id in the JSON-line stream as soon as
|
|
125
|
+
// the run starts. Pattern: {"type":"system","subtype":"init","session_id":"..."}
|
|
126
|
+
const sessionIdRegex = /"session_id"\s*:\s*"([A-Za-z0-9_-]{6,})"/;
|
|
111
127
|
let sessionId;
|
|
112
128
|
|
|
113
129
|
const resolveSpawnIfReady = (forceError) => {
|
|
@@ -123,15 +139,14 @@ export function spawnAgent(opts) {
|
|
|
123
139
|
}
|
|
124
140
|
};
|
|
125
141
|
|
|
126
|
-
// 6. Stream readers — line-buffered by splitting on newlines.
|
|
127
|
-
// append a [stdout]/[stderr] prefix for human readability.
|
|
142
|
+
// 6. Stream readers — line-buffered by splitting on newlines.
|
|
128
143
|
const attachLineReader = (stream, label) => {
|
|
129
144
|
let buf = '';
|
|
130
145
|
stream.setEncoding('utf8');
|
|
131
146
|
stream.on('data', (chunk) => {
|
|
132
147
|
const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
133
148
|
logStream.write(`[${label}] ${text}`);
|
|
134
|
-
if (
|
|
149
|
+
if (!sessionId) {
|
|
135
150
|
buf += text;
|
|
136
151
|
const m = buf.match(sessionIdRegex);
|
|
137
152
|
if (m && m[1]) {
|
|
@@ -142,8 +157,7 @@ export function spawnAgent(opts) {
|
|
|
142
157
|
}
|
|
143
158
|
});
|
|
144
159
|
stream.on('end', () => {
|
|
145
|
-
|
|
146
|
-
if (label === 'stderr' && !sessionId && buf) {
|
|
160
|
+
if (!sessionId && buf) {
|
|
147
161
|
const m = buf.match(sessionIdRegex);
|
|
148
162
|
if (m && m[1]) {
|
|
149
163
|
sessionId = m[1];
|
|
@@ -171,11 +185,11 @@ export function spawnAgent(opts) {
|
|
|
171
185
|
rec.status.state = 'done';
|
|
172
186
|
} else {
|
|
173
187
|
rec.status.state = 'failed';
|
|
174
|
-
rec.status.error = rec.status.error || `
|
|
188
|
+
rec.status.error = rec.status.error || `claude exited with code ${exitCode}`;
|
|
175
189
|
}
|
|
176
190
|
}
|
|
177
191
|
if (!sessionId) {
|
|
178
|
-
resolveSpawnIfReady(rec.status.error || '
|
|
192
|
+
resolveSpawnIfReady(rec.status.error || 'claude exited before reporting session id');
|
|
179
193
|
}
|
|
180
194
|
const cbs = rec.onExit.splice(0);
|
|
181
195
|
for (const cb of cbs) {
|
|
@@ -199,7 +213,7 @@ export function spawnAgent(opts) {
|
|
|
199
213
|
setTimeout(() => {
|
|
200
214
|
if (!sessionId) {
|
|
201
215
|
resolveSpawnIfReady(
|
|
202
|
-
`
|
|
216
|
+
`claude did not report a session id within ${sessionIdTimeoutMs}ms`,
|
|
203
217
|
);
|
|
204
218
|
}
|
|
205
219
|
}, sessionIdTimeoutMs);
|
|
@@ -208,8 +222,6 @@ export function spawnAgent(opts) {
|
|
|
208
222
|
|
|
209
223
|
/**
|
|
210
224
|
* Snapshot the current status of an agent.
|
|
211
|
-
* @param {number} processId
|
|
212
|
-
* @returns {AgentStatus | null}
|
|
213
225
|
*/
|
|
214
226
|
export function getStatus(processId) {
|
|
215
227
|
const rec = agents.get(processId);
|
|
@@ -218,11 +230,8 @@ export function getStatus(processId) {
|
|
|
218
230
|
}
|
|
219
231
|
|
|
220
232
|
/**
|
|
221
|
-
* Subscribe to the exit event. Callback fires exactly once when
|
|
222
|
-
*
|
|
223
|
-
* @param {number} processId
|
|
224
|
-
* @param {(status: AgentStatus) => void} cb
|
|
225
|
-
* @returns {() => void} unsubscribe
|
|
233
|
+
* Subscribe to the exit event. Callback fires exactly once when the
|
|
234
|
+
* process exits.
|
|
226
235
|
*/
|
|
227
236
|
export function onExit(processId, cb) {
|
|
228
237
|
const rec = agents.get(processId);
|
|
@@ -241,9 +250,6 @@ export function onExit(processId, cb) {
|
|
|
241
250
|
|
|
242
251
|
/**
|
|
243
252
|
* Kill the agent. Sends SIGTERM, waits up to 5s, then SIGKILL.
|
|
244
|
-
* @param {number} processId
|
|
245
|
-
* @param {'SIGTERM'|'SIGKILL'} [signal]
|
|
246
|
-
* @returns {{ ok: boolean, error?: string }}
|
|
247
253
|
*/
|
|
248
254
|
export function killAgent(processId, signal = 'SIGTERM') {
|
|
249
255
|
const rec = agents.get(processId);
|
|
@@ -267,7 +273,6 @@ export function killAgent(processId, signal = 'SIGTERM') {
|
|
|
267
273
|
|
|
268
274
|
/**
|
|
269
275
|
* List every tracked agent.
|
|
270
|
-
* @returns {AgentStatus[]}
|
|
271
276
|
*/
|
|
272
277
|
export function list() {
|
|
273
278
|
return Array.from(agents.values()).map((r) => ({ ...r.status }));
|
|
@@ -276,4 +281,4 @@ export function list() {
|
|
|
276
281
|
/** For tests: clear the in-memory registry. */
|
|
277
282
|
export function _resetForTests() {
|
|
278
283
|
agents.clear();
|
|
279
|
-
}
|
|
284
|
+
}
|