@polderlabs/bizar 6.2.5 → 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 +17 -18
- package/cli/commands/claude-cmd.mjs +348 -0
- package/cli/commands/install.mjs +12 -11
- package/cli/commands/validate.mjs +252 -325
- 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 +224 -227
- package/cli/utils.mjs +72 -58
- package/config/AGENTS.md +28 -28
- 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 +31 -9
- 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 -379
- 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 -925
- 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/sandbox.ts +0 -232
- 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 -190
- 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 -281
- 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/sandbox.test.ts +0 -117
- 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
- package/scripts/mirror-agents-md.sh +0 -69
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli/provision-claude.mjs
|
|
4
|
+
*
|
|
5
|
+
* v6.3.0 — Claude Code-native installer + updater.
|
|
6
|
+
*
|
|
7
|
+
* Replaces `cli/provision.mjs` (the Cline-era provisioner). The install
|
|
8
|
+
* / update flow for Claude Code is different from Cline:
|
|
9
|
+
*
|
|
10
|
+
* - Config root is `~/.claude/` (NOT `~/.cline/`).
|
|
11
|
+
* - The configuration file is `~/.claude/settings.json` (NOT
|
|
12
|
+
* `~/.cline/cline.json`).
|
|
13
|
+
* - MCP servers are registered via `claude mcp add` (NOT by
|
|
14
|
+
* patching a `plugin[]` array).
|
|
15
|
+
* - Plugins are managed via `claude plugin install ...` (NOT
|
|
16
|
+
* copied into `~/.cline/plugins/bizar/`).
|
|
17
|
+
* - Skills live under `~/.claude/skills/<name>/SKILL.md`.
|
|
18
|
+
* - Commands live under `~/.claude/commands/<name>.md`.
|
|
19
|
+
* - Rules live under `~/.claude/rules/<name>.md`.
|
|
20
|
+
* - Hooks are wired into `~/.claude/settings.json` (NOT installed
|
|
21
|
+
* as executable files in `~/.cline/hooks/`).
|
|
22
|
+
* - `~/.claude/CLAUDE.md` mirrors the repo's `AGENTS.md`.
|
|
23
|
+
*
|
|
24
|
+
* Public API:
|
|
25
|
+
* runProvision({ mode, dryRun, force, yes, restart, ... })
|
|
26
|
+
* Runs the full provision flow for `mode` ('install' | 'update').
|
|
27
|
+
* detectState()
|
|
28
|
+
* Probe what's installed without modifying anything.
|
|
29
|
+
* Returns a structured state object.
|
|
30
|
+
*
|
|
31
|
+
* Both modes are idempotent and safe to re-run.
|
|
32
|
+
*
|
|
33
|
+
* Flags (parsed by the CLI shim at the bottom):
|
|
34
|
+
* --mode=install|update|install-only-system
|
|
35
|
+
* --dry-run Print actions, make no changes.
|
|
36
|
+
* --force Overwrite existing files.
|
|
37
|
+
* --yes / -y Same as --force.
|
|
38
|
+
* --non-interactive Same as --yes.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import chalk from 'chalk';
|
|
42
|
+
import { execFileSync, execSync, spawn, spawnSync } from 'node:child_process';
|
|
43
|
+
import {
|
|
44
|
+
chmodSync,
|
|
45
|
+
copyFileSync,
|
|
46
|
+
existsSync,
|
|
47
|
+
mkdirSync,
|
|
48
|
+
readdirSync,
|
|
49
|
+
readFileSync,
|
|
50
|
+
rmSync,
|
|
51
|
+
statSync,
|
|
52
|
+
writeFileSync,
|
|
53
|
+
} from 'node:fs';
|
|
54
|
+
import { homedir } from 'node:os';
|
|
55
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
56
|
+
import { fileURLToPath } from 'node:url';
|
|
57
|
+
|
|
58
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
59
|
+
const __dirname = dirname(__filename);
|
|
60
|
+
const HOME = homedir();
|
|
61
|
+
|
|
62
|
+
// Repo root = `<pkg>/cli/provision-claude.mjs` → one level up.
|
|
63
|
+
// This works for source checkouts AND global npm installs.
|
|
64
|
+
export const REPO_ROOT = resolve(__dirname, '..');
|
|
65
|
+
|
|
66
|
+
// ─── Paths ────────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the Claude Code config directory.
|
|
70
|
+
*
|
|
71
|
+
* Resolution order (matches Claude Code's own resolver, see
|
|
72
|
+
* https://code.claude.com/docs/en/cli-reference):
|
|
73
|
+
* 1. `process.env.CLAUDE_CONFIG_DIR` (explicit override)
|
|
74
|
+
* 2. `$HOME/.claude` (default)
|
|
75
|
+
* 3. (Windows only) `%APPDATA%\Claude`
|
|
76
|
+
*/
|
|
77
|
+
export function resolveClaudeDir() {
|
|
78
|
+
if (process.env.CLAUDE_CONFIG_DIR && process.env.CLAUDE_CONFIG_DIR.trim()) {
|
|
79
|
+
return process.env.CLAUDE_CONFIG_DIR.trim();
|
|
80
|
+
}
|
|
81
|
+
if (process.platform === 'win32') {
|
|
82
|
+
return process.env.APPDATA
|
|
83
|
+
? join(process.env.APPDATA, 'Claude')
|
|
84
|
+
: join(HOME, '.claude');
|
|
85
|
+
}
|
|
86
|
+
return join(HOME, '.claude');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const CLAUDE_DIR = resolveClaudeDir();
|
|
90
|
+
|
|
91
|
+
/** `~/.bizar_home/` — Bizar-specific runtime state (NOT under ~/.claude/). */
|
|
92
|
+
export const BIZAR_HOME = process.env.BIZAR_HOME
|
|
93
|
+
|| join(process.env.XDG_CONFIG_HOME || join(HOME, '.config'), 'bizar');
|
|
94
|
+
|
|
95
|
+
// Standard Claude Code subdirectories (v6.3.0 — Claude Code layout)
|
|
96
|
+
export const CLAUDE_AGENTS_DIR = join(CLAUDE_DIR, 'agents');
|
|
97
|
+
export const CLAUDE_SKILLS_DIR = join(CLAUDE_DIR, 'skills');
|
|
98
|
+
export const CLAUDE_COMMANDS_DIR = join(CLAUDE_DIR, 'commands');
|
|
99
|
+
export const CLAUDE_HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
|
|
100
|
+
export const CLAUDE_RULES_DIR = join(CLAUDE_DIR, 'rules');
|
|
101
|
+
export const CLAUDE_PLUGINS_DIR = join(CLAUDE_DIR, 'plugins');
|
|
102
|
+
|
|
103
|
+
// Service / dashboard pid files
|
|
104
|
+
const SERVICE_PID_FILE = join(BIZAR_HOME, 'service.pid');
|
|
105
|
+
const DASHBOARD_PID_FILE = join(BIZAR_HOME, 'dashboard.pid');
|
|
106
|
+
const DASHBOARD_PORT_FILE = join(BIZAR_HOME, 'dashboard.port');
|
|
107
|
+
|
|
108
|
+
// ─── Tiny utilities ──────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
function haveCmd(cmd) {
|
|
111
|
+
try {
|
|
112
|
+
execSync(`command -v ${cmd}`, { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
113
|
+
return true;
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readTextSafe(file, fallback = '') {
|
|
120
|
+
try {
|
|
121
|
+
if (!existsSync(file)) return fallback;
|
|
122
|
+
return readFileSync(file, 'utf8');
|
|
123
|
+
} catch {
|
|
124
|
+
return fallback;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readJsonSafe(file, fallback = null) {
|
|
129
|
+
try {
|
|
130
|
+
if (!existsSync(file)) return fallback;
|
|
131
|
+
return JSON.parse(readFileSync(file, 'utf8'));
|
|
132
|
+
} catch {
|
|
133
|
+
return fallback;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function ensureDir(dir) {
|
|
138
|
+
mkdirSync(dir, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function copyDirContents(src, dest) {
|
|
142
|
+
ensureDir(dest);
|
|
143
|
+
for (const name of readdirSync(src)) {
|
|
144
|
+
const sp = join(src, name);
|
|
145
|
+
const dp = join(dest, name);
|
|
146
|
+
const st = statSync(sp);
|
|
147
|
+
if (st.isDirectory()) copyDirContents(sp, dp);
|
|
148
|
+
else copyFileSync(sp, dp);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function rimrafSafe(p) {
|
|
153
|
+
try { rmSync(p, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function logOk(msg) { console.log(chalk.green(' ✓') + ' ' + msg); }
|
|
157
|
+
function logInfo(msg) { console.log(chalk.cyan(' →') + ' ' + msg); }
|
|
158
|
+
function logWarn(msg) { console.log(chalk.yellow(' ⚠') + ' ' + msg); }
|
|
159
|
+
function logErr(msg) { console.log(chalk.red(' ✗') + ' ' + msg); }
|
|
160
|
+
function section(title) { console.log('\n' + chalk.bold.cyan(`── ${title} ──`)); }
|
|
161
|
+
|
|
162
|
+
// ─── Bizar HOME bootstrap ────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Ensure `~/.bizar_home/` (or `$BIZAR_HOME`) exists with the runtime
|
|
166
|
+
* subdirectories Bizar needs (memory vault, loops dir, dashboard port
|
|
167
|
+
* file). Idempotent.
|
|
168
|
+
*/
|
|
169
|
+
export function ensureBizarHome({ dryRun = false } = {}) {
|
|
170
|
+
if (dryRun) {
|
|
171
|
+
return { ok: true, message: `[dry-run] would ensure ${BIZAR_HOME}` };
|
|
172
|
+
}
|
|
173
|
+
mkdirSync(BIZAR_HOME, { recursive: true });
|
|
174
|
+
mkdirSync(join(BIZAR_HOME, 'memory-vault'), { recursive: true });
|
|
175
|
+
mkdirSync(join(BIZAR_HOME, 'loops'), { recursive: true });
|
|
176
|
+
// Touch the dashboard port file so downstream readers can probe it.
|
|
177
|
+
if (!existsSync(DASHBOARD_PORT_FILE)) {
|
|
178
|
+
writeFileSync(DASHBOARD_PORT_FILE, '4321\n');
|
|
179
|
+
}
|
|
180
|
+
return { ok: true, message: `${BIZAR_HOME} ready`, path: BIZAR_HOME };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ─── Toolchain ───────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
function checkToolchain() {
|
|
186
|
+
section('Toolchain');
|
|
187
|
+
if (haveCmd('node')) logOk(`node ${execSync('node --version').toString().trim()}`);
|
|
188
|
+
else { logErr('node not on PATH — install Node.js 18+ from https://nodejs.org'); process.exit(1); }
|
|
189
|
+
|
|
190
|
+
if (haveCmd('npm')) logOk(`npm ${execSync('npm --version').toString().trim()}`);
|
|
191
|
+
else logWarn('npm not on PATH — Claude Code install will need npm');
|
|
192
|
+
|
|
193
|
+
if (haveCmd('git')) logOk(`git ${execSync('git --version').toString().trim().split(' ')[2]}`);
|
|
194
|
+
else logWarn('git not on PATH — optional for many tasks');
|
|
195
|
+
|
|
196
|
+
if (haveCmd('claude')) logOk(`claude ${execSync('claude --version').toString().trim().split('\n')[0]}`);
|
|
197
|
+
else logWarn('claude not on PATH — will install via npm');
|
|
198
|
+
|
|
199
|
+
if (haveCmd('agent-browser')) logOk(`agent-browser ${execSync('agent-browser --version').toString().trim()}`);
|
|
200
|
+
else logInfo('agent-browser not installed (optional; npm install -g agent-browser)');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Install Claude Code CLI + SDK globally via npm. Idempotent. */
|
|
204
|
+
function installClaudeCli({ force = false, dryRun = false } = {}) {
|
|
205
|
+
if (haveCmd('claude') && !force) return;
|
|
206
|
+
section('Installing Claude Code CLI');
|
|
207
|
+
logInfo('npm install -g @anthropic-ai/claude-agent-sdk @anthropic-ai/claude-code');
|
|
208
|
+
if (dryRun) return;
|
|
209
|
+
try {
|
|
210
|
+
execSync(
|
|
211
|
+
'npm install -g @anthropic-ai/claude-agent-sdk @anthropic-ai/claude-code',
|
|
212
|
+
{ stdio: 'inherit' },
|
|
213
|
+
);
|
|
214
|
+
logOk('Claude Code CLI installed');
|
|
215
|
+
} catch (err) {
|
|
216
|
+
logErr(`npm install failed: ${err.message}`);
|
|
217
|
+
logWarn('retry manually: npm install -g @anthropic-ai/claude-code');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── File sync helpers ──────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Mirror a source directory into a destination directory, copying
|
|
225
|
+
* files and preserving subdirectory structure. Skips dotfiles by default.
|
|
226
|
+
*/
|
|
227
|
+
function syncDir(srcDir, destDir, opts = {}) {
|
|
228
|
+
if (!existsSync(srcDir)) return { copied: 0, skipped: 0 };
|
|
229
|
+
ensureDir(destDir);
|
|
230
|
+
let copied = 0;
|
|
231
|
+
let skipped = 0;
|
|
232
|
+
for (const name of readdirSync(srcDir)) {
|
|
233
|
+
if (name.startsWith('.')) continue;
|
|
234
|
+
const sp = join(srcDir, name);
|
|
235
|
+
const dp = join(destDir, name);
|
|
236
|
+
const st = statSync(sp);
|
|
237
|
+
if (opts.filter && !opts.filter(name, sp)) { skipped++; continue; }
|
|
238
|
+
if (st.isDirectory()) {
|
|
239
|
+
const r = syncDir(sp, dp, opts);
|
|
240
|
+
copied += r.copied;
|
|
241
|
+
skipped += r.skipped;
|
|
242
|
+
} else {
|
|
243
|
+
copyFileSync(sp, dp);
|
|
244
|
+
copied++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return { copied, skipped };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Copy `config/agents/*.md` → `~/.claude/agents/*.md`.
|
|
252
|
+
*
|
|
253
|
+
* v6.3.0 — Agent files use Claude Code frontmatter (name / description
|
|
254
|
+
* / tools / model at the top). The source files in `config/agents/`
|
|
255
|
+
* have already been migrated by a parallel task; this helper is a
|
|
256
|
+
* straight copy with no YAML regeneration (Claude Code reads `.md`
|
|
257
|
+
* directly — no `.yaml` shadow needed).
|
|
258
|
+
*/
|
|
259
|
+
export async function syncAgentFiles({ dryRun = false, force = false } = {}) {
|
|
260
|
+
const src = join(REPO_ROOT, 'config', 'agents');
|
|
261
|
+
const dest = CLAUDE_AGENTS_DIR;
|
|
262
|
+
if (!existsSync(src)) {
|
|
263
|
+
return { ok: true, message: `no agents source at ${src}`, copied: 0, skipped: 0 };
|
|
264
|
+
}
|
|
265
|
+
if (dryRun) {
|
|
266
|
+
return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
|
|
267
|
+
}
|
|
268
|
+
ensureDir(dest);
|
|
269
|
+
ensureDir(join(dest, '_shared'));
|
|
270
|
+
// Top-level .md files
|
|
271
|
+
let { copied, skipped } = syncDir(src, dest, { filter: (n) => n.endsWith('.md') });
|
|
272
|
+
// _shared/ — always overwrite (it's tiny + ships agent defaults).
|
|
273
|
+
const sharedSrc = join(src, '_shared');
|
|
274
|
+
if (existsSync(sharedSrc)) {
|
|
275
|
+
for (const entry of readdirSync(sharedSrc, { withFileTypes: true })) {
|
|
276
|
+
if (!entry.isFile()) continue;
|
|
277
|
+
copyFileSync(join(sharedSrc, entry.name), join(dest, '_shared', entry.name));
|
|
278
|
+
copied++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { ok: true, message: `${copied} agent(s) synced (${skipped} kept)`, copied, skipped };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Copy `config/skills/<name>/` → `~/.claude/skills/<name>/`.
|
|
286
|
+
*
|
|
287
|
+
* Each skill is mirrored as its own subdirectory under
|
|
288
|
+
* `~/.claude/skills/<name>/`. Honors `force` to overwrite.
|
|
289
|
+
*/
|
|
290
|
+
export async function syncSkillFiles({ dryRun = false, force = false } = {}) {
|
|
291
|
+
const src = join(REPO_ROOT, 'config', 'skills');
|
|
292
|
+
const dest = CLAUDE_SKILLS_DIR;
|
|
293
|
+
if (!existsSync(src)) {
|
|
294
|
+
return { ok: true, message: `no skills source at ${src}`, copied: 0, skipped: 0 };
|
|
295
|
+
}
|
|
296
|
+
if (dryRun) {
|
|
297
|
+
return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
|
|
298
|
+
}
|
|
299
|
+
ensureDir(dest);
|
|
300
|
+
copyDirContents(src, dest);
|
|
301
|
+
|
|
302
|
+
// Mirror AGENT_BASELINE.md → ~/.claude/skills/agent-baseline/SKILL.md
|
|
303
|
+
// (the Claude Code skill loader resolves the `name: agent-baseline`
|
|
304
|
+
// reference via this path).
|
|
305
|
+
const sharedBaseline = join(REPO_ROOT, 'config', 'agents', '_shared', 'AGENT_BASELINE.md');
|
|
306
|
+
if (existsSync(sharedBaseline)) {
|
|
307
|
+
const baselineDir = join(dest, 'agent-baseline');
|
|
308
|
+
ensureDir(baselineDir);
|
|
309
|
+
copyFileSync(sharedBaseline, join(baselineDir, 'SKILL.md'));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const count = readdirSync(dest).filter((n) => {
|
|
313
|
+
try { return statSync(join(dest, n)).isDirectory(); } catch { return false; }
|
|
314
|
+
}).length;
|
|
315
|
+
return { ok: true, message: `${count} skill(s) synced`, copied: count, skipped: 0 };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Copy `.claude/commands/*.md` → `~/.claude/commands/*.md`.
|
|
320
|
+
*
|
|
321
|
+
* Slash commands live in the project's `.claude/commands/` directory
|
|
322
|
+
* (project-scoped). The global mirror in `~/.claude/commands/` makes
|
|
323
|
+
* them available across every Claude Code session for the user.
|
|
324
|
+
*/
|
|
325
|
+
export async function syncCommandFiles({ dryRun = false, force = false } = {}) {
|
|
326
|
+
// Project-scoped commands first, then legacy `config/commands/` as
|
|
327
|
+
// a fallback for older Bizar installs.
|
|
328
|
+
const candidates = [
|
|
329
|
+
join(REPO_ROOT, '.claude', 'commands'),
|
|
330
|
+
join(REPO_ROOT, 'config', 'commands'),
|
|
331
|
+
];
|
|
332
|
+
let src = null;
|
|
333
|
+
for (const c of candidates) if (existsSync(c)) { src = c; break; }
|
|
334
|
+
if (!src) {
|
|
335
|
+
return { ok: true, message: 'no commands source found', copied: 0, skipped: 0 };
|
|
336
|
+
}
|
|
337
|
+
const dest = CLAUDE_COMMANDS_DIR;
|
|
338
|
+
if (dryRun) {
|
|
339
|
+
return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
|
|
340
|
+
}
|
|
341
|
+
ensureDir(dest);
|
|
342
|
+
const { copied, skipped } = syncDir(src, dest, { filter: (n) => n.endsWith('.md') });
|
|
343
|
+
return { ok: true, message: `${copied} command(s) synced (${skipped} kept)`, copied, skipped };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Copy `config/rules/*.md` → `~/.claude/rules/*.md`.
|
|
348
|
+
*
|
|
349
|
+
* Claude Code auto-loads every `.md` file under `~/.claude/rules/` as
|
|
350
|
+
* an always-on rule (https://code.claude.com/docs/en/rules).
|
|
351
|
+
*/
|
|
352
|
+
export async function syncRulesFiles({ dryRun = false, force = false } = {}) {
|
|
353
|
+
const src = join(REPO_ROOT, 'config', 'rules');
|
|
354
|
+
const dest = CLAUDE_RULES_DIR;
|
|
355
|
+
if (!existsSync(src)) {
|
|
356
|
+
return { ok: true, message: `no rules source at ${src}`, copied: 0, skipped: 0 };
|
|
357
|
+
}
|
|
358
|
+
if (dryRun) {
|
|
359
|
+
return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
|
|
360
|
+
}
|
|
361
|
+
ensureDir(dest);
|
|
362
|
+
const { copied, skipped } = syncDir(src, dest, { filter: (n) => n.endsWith('.md') || n.endsWith('.txt') });
|
|
363
|
+
return { ok: true, message: `${copied} rule(s) synced (${skipped} kept)`, copied, skipped };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Copy `.claude/hooks/*.mjs` → `~/.claude/hooks/*.mjs`.
|
|
368
|
+
*
|
|
369
|
+
* Hook adapter scripts are real Node modules — Claude Code spawns them
|
|
370
|
+
* and pipes JSON on stdin / stdout. They must be `chmod +x` so Claude
|
|
371
|
+
* Code can `execve` them directly.
|
|
372
|
+
*/
|
|
373
|
+
export async function syncHookFiles({ dryRun = false, force = false } = {}) {
|
|
374
|
+
const src = join(REPO_ROOT, '.claude', 'hooks');
|
|
375
|
+
const dest = CLAUDE_HOOKS_DIR;
|
|
376
|
+
ensureDir(dest);
|
|
377
|
+
if (!existsSync(src)) {
|
|
378
|
+
return { ok: true, message: `no hooks source at ${src}`, copied: 0, skipped: 0 };
|
|
379
|
+
}
|
|
380
|
+
if (dryRun) {
|
|
381
|
+
return { ok: true, message: `[dry-run] would sync ${src} → ${dest}` };
|
|
382
|
+
}
|
|
383
|
+
copyDirContents(src, dest);
|
|
384
|
+
// Make all scripts executable.
|
|
385
|
+
for (const name of readdirSync(dest)) {
|
|
386
|
+
const fp = join(dest, name);
|
|
387
|
+
try {
|
|
388
|
+
const st = statSync(fp);
|
|
389
|
+
if (st.isFile() && (name.endsWith('.sh') || name.endsWith('.mjs'))) {
|
|
390
|
+
chmodSync(fp, 0o755);
|
|
391
|
+
}
|
|
392
|
+
} catch { /* ignore */ }
|
|
393
|
+
}
|
|
394
|
+
return { ok: true, message: 'hook scripts installed', copied: readdirSync(dest).length, skipped: 0 };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ─── settings.json ───────────────────────────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Generate `~/.claude/settings.json` with the Bizar harness surface:
|
|
401
|
+
* - $schema pointing to https://json.schemastore.org/claude-code-settings.json
|
|
402
|
+
* - mcpServers block registering the `bizar` MCP server (stdio)
|
|
403
|
+
* - permissions.allow for mcp__bizar__*
|
|
404
|
+
* - permissions.deny for dangerous patterns
|
|
405
|
+
* - hooks.PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd
|
|
406
|
+
* - env block with BIZAR_HOME, BIZAR_MEMORY_VAULT
|
|
407
|
+
*
|
|
408
|
+
* Idempotent — we merge with any existing settings.json (preserve
|
|
409
|
+
* user-added keys, never overwrite).
|
|
410
|
+
*/
|
|
411
|
+
export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
412
|
+
const fp = join(CLAUDE_DIR, 'settings.json');
|
|
413
|
+
const existing = readJsonSafe(fp, {}) || {};
|
|
414
|
+
|
|
415
|
+
const bizarSettings = {
|
|
416
|
+
$schema: 'https://json.schemastore.org/claude-code-settings.json',
|
|
417
|
+
mcpServers: {
|
|
418
|
+
bizar: {
|
|
419
|
+
type: 'stdio',
|
|
420
|
+
command: 'npx',
|
|
421
|
+
args: ['-y', '@polderlabs/bizar-sdk', 'mcp'],
|
|
422
|
+
env: {
|
|
423
|
+
BIZAR_HOME,
|
|
424
|
+
BIZAR_MEMORY_VAULT: process.env.BIZAR_MEMORY_VAULT || join(BIZAR_HOME, 'memory-vault'),
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
permissions: {
|
|
429
|
+
allow: ['mcp__bizar__*'],
|
|
430
|
+
deny: [
|
|
431
|
+
'Read(./.env)',
|
|
432
|
+
'Read(./.env.*)',
|
|
433
|
+
'Bash(rm -rf /)',
|
|
434
|
+
'Bash(sudo *)',
|
|
435
|
+
'Write(./node_modules/**)',
|
|
436
|
+
'Write(./package-lock.json)',
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
env: {
|
|
440
|
+
BIZAR_HOME,
|
|
441
|
+
BIZAR_MEMORY_VAULT: process.env.BIZAR_MEMORY_VAULT || join(BIZAR_HOME, 'memory-vault'),
|
|
442
|
+
},
|
|
443
|
+
hooks: {
|
|
444
|
+
PreToolUse: [
|
|
445
|
+
{
|
|
446
|
+
matcher: 'Write|Edit|MultiEdit|Bash',
|
|
447
|
+
hooks: [
|
|
448
|
+
{
|
|
449
|
+
type: 'command',
|
|
450
|
+
command: join(CLAUDE_HOOKS_DIR, 'pretooluse-editwrite.mjs'),
|
|
451
|
+
timeout: 10,
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
PostToolUse: [
|
|
457
|
+
{
|
|
458
|
+
matcher: 'Edit|Write|MultiEdit',
|
|
459
|
+
hooks: [
|
|
460
|
+
{
|
|
461
|
+
type: 'command',
|
|
462
|
+
command: join(CLAUDE_HOOKS_DIR, 'posttooluse-editwrite.mjs'),
|
|
463
|
+
timeout: 10,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
UserPromptSubmit: [
|
|
469
|
+
{
|
|
470
|
+
hooks: [
|
|
471
|
+
{
|
|
472
|
+
type: 'command',
|
|
473
|
+
command: join(CLAUDE_HOOKS_DIR, 'userpromptsubmit-tag.mjs'),
|
|
474
|
+
timeout: 10,
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
SessionStart: [
|
|
480
|
+
{
|
|
481
|
+
hooks: [
|
|
482
|
+
{
|
|
483
|
+
type: 'command',
|
|
484
|
+
command: join(CLAUDE_HOOKS_DIR, 'sessionstart-prime.mjs'),
|
|
485
|
+
timeout: 10,
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
SessionEnd: [
|
|
491
|
+
{
|
|
492
|
+
hooks: [
|
|
493
|
+
{
|
|
494
|
+
type: 'command',
|
|
495
|
+
command: join(CLAUDE_HOOKS_DIR, 'sessionend-record.mjs'),
|
|
496
|
+
timeout: 10,
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// Merge bizarre-specific keys into existing settings, preserve user's
|
|
505
|
+
// unrelated keys (model, attribution, theme, etc.).
|
|
506
|
+
const merged = { ...existing };
|
|
507
|
+
if (force) {
|
|
508
|
+
Object.assign(merged, bizarSettings);
|
|
509
|
+
} else {
|
|
510
|
+
merged.$schema = merged.$schema || bizarSettings.$schema;
|
|
511
|
+
merged.mcpServers = { ...(existing.mcpServers || {}), ...bizarSettings.mcpServers };
|
|
512
|
+
merged.permissions = {
|
|
513
|
+
allow: [...new Set([...(existing.permissions?.allow || []), ...bizarSettings.permissions.allow])],
|
|
514
|
+
deny: [...new Set([...(existing.permissions?.deny || []), ...bizarSettings.permissions.deny])],
|
|
515
|
+
ask: [...new Set([...(existing.permissions?.ask || [])])],
|
|
516
|
+
};
|
|
517
|
+
merged.env = { ...(existing.env || {}), ...bizarSettings.env };
|
|
518
|
+
merged.hooks = { ...(existing.hooks || {}), ...bizarSettings.hooks };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (dryRun) {
|
|
522
|
+
return { ok: true, message: `[dry-run] would write ${fp}` };
|
|
523
|
+
}
|
|
524
|
+
ensureDir(CLAUDE_DIR);
|
|
525
|
+
writeFileSync(fp, JSON.stringify(merged, null, 2) + '\n');
|
|
526
|
+
return { ok: true, message: `wrote ${fp}`, path: fp };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ─── CLAUDE.md mirror ──────────────────────────────────────────────────────
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Mirror the repo's `AGENTS.md` into `~/.claude/CLAUDE.md`.
|
|
533
|
+
*
|
|
534
|
+
* Claude Code reads `~/.claude/CLAUDE.md` at session start as a global
|
|
535
|
+
* memory file. Mirroring `AGENTS.md` (the canonical harness entry
|
|
536
|
+
* point) makes the project's always-on rules available to every
|
|
537
|
+
* Claude Code session for the user, not just project-scoped sessions.
|
|
538
|
+
*/
|
|
539
|
+
export function writeClaudeMdMirror({ dryRun = false, force = false } = {}) {
|
|
540
|
+
const srcCandidates = [
|
|
541
|
+
join(REPO_ROOT, 'AGENTS.md'),
|
|
542
|
+
join(REPO_ROOT, 'CLAUDE.md'),
|
|
543
|
+
];
|
|
544
|
+
const src = srcCandidates.find((p) => existsSync(p));
|
|
545
|
+
const dest = join(CLAUDE_DIR, 'CLAUDE.md');
|
|
546
|
+
if (!src) {
|
|
547
|
+
return { ok: false, message: 'no CLAUDE.md / AGENTS.md found in repo root' };
|
|
548
|
+
}
|
|
549
|
+
if (existsSync(dest) && !force) {
|
|
550
|
+
return { ok: true, message: `${dest} already exists — pass --force to overwrite` };
|
|
551
|
+
}
|
|
552
|
+
if (dryRun) {
|
|
553
|
+
return { ok: true, message: `[dry-run] would mirror ${src} → ${dest}` };
|
|
554
|
+
}
|
|
555
|
+
ensureDir(CLAUDE_DIR);
|
|
556
|
+
// Add a banner so users know this is auto-generated.
|
|
557
|
+
const body = readFileSync(src, 'utf8');
|
|
558
|
+
const banner =
|
|
559
|
+
`# CLAUDE.md — Mirror of AGENTS.md for Claude Code compatibility\n\n` +
|
|
560
|
+
`> Auto-generated by \`cli/provision-claude.mjs:writeClaudeMdMirror\`.\n` +
|
|
561
|
+
`> DO NOT EDIT THIS FILE DIRECTLY — edit the project root's \`AGENTS.md\`\n` +
|
|
562
|
+
`> and re-run \`bizar update\` (or \`make mirror-claude-md\`) to refresh.\n\n` +
|
|
563
|
+
`---\n\n`;
|
|
564
|
+
writeFileSync(dest, banner + body);
|
|
565
|
+
return { ok: true, message: `mirrored ${src} → ${dest}`, path: dest };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ─── MCP server registration ─────────────────────────────────────────────────
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Register the `bizar` MCP server via `claude mcp add`. Idempotent —
|
|
572
|
+
* if the server is already registered in settings.json, we skip the
|
|
573
|
+
* `claude mcp add` call.
|
|
574
|
+
*
|
|
575
|
+
* Per https://code.claude.com/docs/en/mcp, the canonical way to add a
|
|
576
|
+
* stdio MCP server is:
|
|
577
|
+
*
|
|
578
|
+
* claude mcp add <name> -- <command> [args...]
|
|
579
|
+
*/
|
|
580
|
+
export function setupMcpServer({ dryRun = false } = {}) {
|
|
581
|
+
// Idempotency probe: read existing settings.json and look for a
|
|
582
|
+
// bizar MCP server entry. If present, skip.
|
|
583
|
+
const settingsPath = join(CLAUDE_DIR, 'settings.json');
|
|
584
|
+
const existing = readJsonSafe(settingsPath, null);
|
|
585
|
+
if (existing && existing.mcpServers && existing.mcpServers.bizar) {
|
|
586
|
+
return { ok: true, message: `bizar MCP server already registered in ${settingsPath}` };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (!haveCmd('claude')) {
|
|
590
|
+
return { ok: false, message: 'claude CLI not on PATH — install @anthropic-ai/claude-code first' };
|
|
591
|
+
}
|
|
592
|
+
if (dryRun) {
|
|
593
|
+
return { ok: true, message: '[dry-run] would run: claude mcp add bizar -- npx -y @polderlabs/bizar-sdk mcp' };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const r = spawnSync('claude', [
|
|
597
|
+
'mcp', 'add', '-f', '-s', 'user', 'bizar', '--',
|
|
598
|
+
'npx', '-y', '@polderlabs/bizar-sdk', 'mcp',
|
|
599
|
+
], { stdio: 'inherit', timeout: 60_000 });
|
|
600
|
+
|
|
601
|
+
if (r.status !== 0) {
|
|
602
|
+
return { ok: false, message: `claude mcp add exited with code ${r.status}` };
|
|
603
|
+
}
|
|
604
|
+
return { ok: true, message: 'bizar MCP server registered via `claude mcp add`' };
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* If `./.claude-plugin/plugin.json` exists, install the Bizar plugin
|
|
609
|
+
* via `claude plugin install .`. Per
|
|
610
|
+
* https://code.claude.com/docs/en/plugins, a Claude Code plugin is a
|
|
611
|
+
* directory containing a `.claude-plugin/plugin.json` manifest.
|
|
612
|
+
*/
|
|
613
|
+
export function setupClaudePlugin({ dryRun = false } = {}) {
|
|
614
|
+
const manifestPath = join(REPO_ROOT, '.claude-plugin', 'plugin.json');
|
|
615
|
+
if (!existsSync(manifestPath)) {
|
|
616
|
+
return { ok: true, message: `no plugin manifest at ${manifestPath} — skipping plugin install` };
|
|
617
|
+
}
|
|
618
|
+
if (!haveCmd('claude')) {
|
|
619
|
+
return { ok: false, message: 'claude CLI not on PATH — install @anthropic-ai/claude-code first' };
|
|
620
|
+
}
|
|
621
|
+
if (dryRun) {
|
|
622
|
+
return { ok: true, message: `[dry-run] would run: claude plugin install ${REPO_ROOT}` };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const r = spawnSync('claude', ['plugin', 'install', '.'], {
|
|
626
|
+
stdio: 'inherit',
|
|
627
|
+
timeout: 120_000,
|
|
628
|
+
cwd: REPO_ROOT,
|
|
629
|
+
});
|
|
630
|
+
if (r.status !== 0) {
|
|
631
|
+
return { ok: false, message: `claude plugin install exited with code ${r.status}` };
|
|
632
|
+
}
|
|
633
|
+
return { ok: true, message: `claude plugin installed from ${REPO_ROOT}` };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ─── Dashboard service ───────────────────────────────────────────────────────
|
|
637
|
+
|
|
638
|
+
function installDashboardService({ dryRun = false, start = true } = {}) {
|
|
639
|
+
const bin = join(REPO_ROOT, 'cli', 'bin.mjs');
|
|
640
|
+
if (!existsSync(bin)) {
|
|
641
|
+
return { ok: false, message: `cli/bin.mjs not found at ${bin} — service registration deferred` };
|
|
642
|
+
}
|
|
643
|
+
if (dryRun) {
|
|
644
|
+
return { ok: true, message: `[dry-run] would run: node ${bin} service install` };
|
|
645
|
+
}
|
|
646
|
+
const r = spawnSync('node', [bin, 'service', 'install'], { stdio: 'inherit' });
|
|
647
|
+
if (r.status !== 0) {
|
|
648
|
+
return { ok: false, message: `service install returned ${r.status}` };
|
|
649
|
+
}
|
|
650
|
+
return { ok: true, message: 'dashboard service registered' };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ─── Detection ───────────────────────────────────────────────────────────────
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Probe the current state without modifying anything. Both install and
|
|
657
|
+
* update flows start here so they can decide what to skip.
|
|
658
|
+
*
|
|
659
|
+
* Returned shape:
|
|
660
|
+
* {
|
|
661
|
+
* claudeDir: string,
|
|
662
|
+
* claudeCli: { available, version },
|
|
663
|
+
* agentsDir: { exists, count },
|
|
664
|
+
* skillsDir: { exists, count },
|
|
665
|
+
* commandsDir: { exists, count },
|
|
666
|
+
* hooksDir: { exists, count },
|
|
667
|
+
* rulesDir: { exists, count },
|
|
668
|
+
* settingsFile: { exists, parses },
|
|
669
|
+
* mcpServer: { registered, type, command },
|
|
670
|
+
* pluginManifest: { exists },
|
|
671
|
+
* bizarHome: { exists },
|
|
672
|
+
* gitRepo: boolean,
|
|
673
|
+
* }
|
|
674
|
+
*/
|
|
675
|
+
export function detectState() {
|
|
676
|
+
const settingsPath = join(CLAUDE_DIR, 'settings.json');
|
|
677
|
+
const settings = readJsonSafe(settingsPath, null);
|
|
678
|
+
|
|
679
|
+
let claudeVersion = null;
|
|
680
|
+
try {
|
|
681
|
+
claudeVersion = execFileSync('claude', ['--version'], { encoding: 'utf8', timeout: 5000 })
|
|
682
|
+
.toString().trim().split('\n')[0] || null;
|
|
683
|
+
} catch { /* not installed */ }
|
|
684
|
+
|
|
685
|
+
const countMd = (dir) => {
|
|
686
|
+
if (!existsSync(dir)) return 0;
|
|
687
|
+
try {
|
|
688
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
689
|
+
.filter((e) => e.isFile() && (e.name.endsWith('.md') || e.name.endsWith('.mjs'))).length;
|
|
690
|
+
} catch { return 0; }
|
|
691
|
+
};
|
|
692
|
+
const countSkillDirs = (dir) => {
|
|
693
|
+
if (!existsSync(dir)) return 0;
|
|
694
|
+
try {
|
|
695
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
|
|
696
|
+
} catch { return 0; }
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
const mcpServer = settings && settings.mcpServers && settings.mcpServers.bizar
|
|
700
|
+
? {
|
|
701
|
+
registered: true,
|
|
702
|
+
type: settings.mcpServers.bizar.type || 'stdio',
|
|
703
|
+
command: settings.mcpServers.bizar.command || 'npx',
|
|
704
|
+
}
|
|
705
|
+
: { registered: false };
|
|
706
|
+
|
|
707
|
+
return {
|
|
708
|
+
claudeDir: CLAUDE_DIR,
|
|
709
|
+
claudeCli: { available: !!claudeVersion, version: claudeVersion },
|
|
710
|
+
agentsDir: { exists: existsSync(CLAUDE_AGENTS_DIR), count: countMd(CLAUDE_AGENTS_DIR) },
|
|
711
|
+
skillsDir: { exists: existsSync(CLAUDE_SKILLS_DIR), count: countSkillDirs(CLAUDE_SKILLS_DIR) },
|
|
712
|
+
commandsDir: { exists: existsSync(CLAUDE_COMMANDS_DIR), count: countMd(CLAUDE_COMMANDS_DIR) },
|
|
713
|
+
hooksDir: { exists: existsSync(CLAUDE_HOOKS_DIR), count: countMd(CLAUDE_HOOKS_DIR) },
|
|
714
|
+
rulesDir: { exists: existsSync(CLAUDE_RULES_DIR), count: countMd(CLAUDE_RULES_DIR) },
|
|
715
|
+
settingsFile: {
|
|
716
|
+
exists: existsSync(settingsPath),
|
|
717
|
+
parses: settings !== null && typeof settings === 'object',
|
|
718
|
+
path: settingsPath,
|
|
719
|
+
},
|
|
720
|
+
mcpServer,
|
|
721
|
+
pluginManifest: { exists: existsSync(join(REPO_ROOT, '.claude-plugin', 'plugin.json')) },
|
|
722
|
+
bizarHome: { exists: existsSync(BIZAR_HOME), path: BIZAR_HOME },
|
|
723
|
+
gitRepo: existsSync(join(REPO_ROOT, '.git')),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** Read-only JSON snapshot of detectState() — for diagnostics + tests. */
|
|
728
|
+
export function detectStateJson() {
|
|
729
|
+
return JSON.stringify(detectState(), null, 2);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// ─── Top-level orchestration ─────────────────────────────────────────────────
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* The unified provision flow. `mode` is 'install' or 'update'.
|
|
736
|
+
*
|
|
737
|
+
* Steps performed:
|
|
738
|
+
* 1. Detect state (no side effects).
|
|
739
|
+
* 2. Ensure ~/.bizar_home/ exists (memory-vault, loops, port file).
|
|
740
|
+
* 3. Check toolchain + install Claude Code CLI if missing.
|
|
741
|
+
* 4. Sync agent files to ~/.claude/agents/.
|
|
742
|
+
* 5. Sync skills to ~/.claude/skills/.
|
|
743
|
+
* 6. Sync commands to ~/.claude/commands/.
|
|
744
|
+
* 7. Sync rules to ~/.claude/rules/.
|
|
745
|
+
* 8. Sync hook adapter scripts to ~/.claude/hooks/.
|
|
746
|
+
* 9. Write ~/.claude/settings.json with MCP + hooks + permissions + env.
|
|
747
|
+
* 10. Register the Bizar MCP server via `claude mcp add`.
|
|
748
|
+
* 11. Install the Bizar plugin via `claude plugin install .` (optional).
|
|
749
|
+
* 12. Mirror AGENTS.md → ~/.claude/CLAUDE.md.
|
|
750
|
+
* 13. (install) Register dashboard service.
|
|
751
|
+
* 14. Print summary.
|
|
752
|
+
*
|
|
753
|
+
* Every step is idempotent — running this twice is safe.
|
|
754
|
+
*/
|
|
755
|
+
export async function runProvision(opts = {}) {
|
|
756
|
+
const {
|
|
757
|
+
mode = 'install',
|
|
758
|
+
dryRun = false,
|
|
759
|
+
force = false,
|
|
760
|
+
yes = false,
|
|
761
|
+
start = true,
|
|
762
|
+
} = opts;
|
|
763
|
+
|
|
764
|
+
const effectiveMode = mode === 'update' ? 'update' : 'install';
|
|
765
|
+
|
|
766
|
+
console.log('');
|
|
767
|
+
console.log(chalk.bold.cyan(' ⚡ BizarHarness Provisioner v6.3.0 (Claude Code)'));
|
|
768
|
+
console.log(chalk.dim(` Mode: ${effectiveMode}${force ? ' (force)' : ''}${dryRun ? ' (dry-run)' : ''}`));
|
|
769
|
+
console.log('');
|
|
770
|
+
|
|
771
|
+
// ── 1. Detect state ──────────────────────────────────────────────────
|
|
772
|
+
const state = detectState();
|
|
773
|
+
|
|
774
|
+
// ── 2. Bizar HOME bootstrap ──────────────────────────────────────────
|
|
775
|
+
section('Bizar HOME');
|
|
776
|
+
const bizarHomeStep = ensureBizarHome({ dryRun });
|
|
777
|
+
if (bizarHomeStep.ok) logOk(bizarHomeStep.message);
|
|
778
|
+
else logErr(bizarHomeStep.message);
|
|
779
|
+
|
|
780
|
+
// ── 3. Toolchain ────────────────────────────────────────────────────
|
|
781
|
+
checkToolchain();
|
|
782
|
+
installClaudeCli({ force, dryRun });
|
|
783
|
+
|
|
784
|
+
// ── 4-8. File sync ──────────────────────────────────────────────────
|
|
785
|
+
const stepResults = [];
|
|
786
|
+
const runStep = async (label, fn) => {
|
|
787
|
+
section(label);
|
|
788
|
+
const r = await fn();
|
|
789
|
+
const marker = r.ok ? '✓' : '✗';
|
|
790
|
+
console.log(` ${marker} ${r.message}`);
|
|
791
|
+
stepResults.push({ label, ...r });
|
|
792
|
+
return r;
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
await runStep('Syncing agents', () => syncAgentFiles({ dryRun, force }));
|
|
796
|
+
await runStep('Syncing skills', () => syncSkillFiles({ dryRun, force }));
|
|
797
|
+
await runStep('Syncing commands', () => syncCommandFiles({ dryRun, force }));
|
|
798
|
+
await runStep('Syncing rules', () => syncRulesFiles({ dryRun, force }));
|
|
799
|
+
await runStep('Syncing hooks', () => syncHookFiles({ dryRun, force }));
|
|
800
|
+
|
|
801
|
+
// ── 9. settings.json (MCP + hooks + permissions + env) ──────────────
|
|
802
|
+
section('Writing settings.json');
|
|
803
|
+
const settingsStep = writeClaudeSettings({ dryRun, force });
|
|
804
|
+
if (settingsStep.ok) logOk(settingsStep.message); else logErr(settingsStep.message);
|
|
805
|
+
stepResults.push({ label: 'settings.json', ...settingsStep });
|
|
806
|
+
|
|
807
|
+
// ── 10. MCP server registration ──────────────────────────────────────
|
|
808
|
+
section('MCP server (bizar)');
|
|
809
|
+
const mcpStep = setupMcpServer({ dryRun });
|
|
810
|
+
if (mcpStep.ok) logOk(mcpStep.message); else logWarn(mcpStep.message);
|
|
811
|
+
stepResults.push({ label: 'mcp-server', ...mcpStep });
|
|
812
|
+
|
|
813
|
+
// ── 11. Plugin install (optional) ───────────────────────────────────
|
|
814
|
+
section('Claude plugin');
|
|
815
|
+
const pluginStep = setupClaudePlugin({ dryRun });
|
|
816
|
+
if (pluginStep.ok) logOk(pluginStep.message); else logWarn(pluginStep.message);
|
|
817
|
+
stepResults.push({ label: 'claude-plugin', ...pluginStep });
|
|
818
|
+
|
|
819
|
+
// ── 12. CLAUDE.md mirror ────────────────────────────────────────────
|
|
820
|
+
section('CLAUDE.md mirror');
|
|
821
|
+
const claudeMdStep = writeClaudeMdMirror({ dryRun, force });
|
|
822
|
+
if (claudeMdStep.ok) logOk(claudeMdStep.message); else logWarn(claudeMdStep.message);
|
|
823
|
+
stepResults.push({ label: 'claude-md', ...claudeMdStep });
|
|
824
|
+
|
|
825
|
+
// ── 13. Dashboard service ───────────────────────────────────────────
|
|
826
|
+
if (start && effectiveMode === 'install') {
|
|
827
|
+
section('Dashboard service');
|
|
828
|
+
const svcStep = installDashboardService({ dryRun });
|
|
829
|
+
if (svcStep.ok) logOk(svcStep.message); else logWarn(svcStep.message);
|
|
830
|
+
stepResults.push({ label: 'service', ...svcStep });
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// ── 14. Summary ─────────────────────────────────────────────────────
|
|
834
|
+
section('Provision complete');
|
|
835
|
+
const finalState = detectState();
|
|
836
|
+
console.log(chalk.dim(JSON.stringify(finalState, null, 2)));
|
|
837
|
+
console.log('');
|
|
838
|
+
const anyFail = stepResults.some((r) => !r.ok);
|
|
839
|
+
if (anyFail) {
|
|
840
|
+
console.log(chalk.yellow(' ⚠ Some steps had issues. See messages above.'));
|
|
841
|
+
} else {
|
|
842
|
+
console.log(chalk.bold.green(' ✓ Bizar is ready.'));
|
|
843
|
+
console.log(chalk.dim(' Next: restart your Claude Code session to pick up the new config.'));
|
|
844
|
+
}
|
|
845
|
+
console.log('');
|
|
846
|
+
return { ok: !anyFail, mode: effectiveMode, state: finalState, stepResults };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// ─── CLI entry ───────────────────────────────────────────────────────────────
|
|
850
|
+
|
|
851
|
+
function parseFlags(argv) {
|
|
852
|
+
const opts = {
|
|
853
|
+
mode: 'install',
|
|
854
|
+
dryRun: false,
|
|
855
|
+
force: false,
|
|
856
|
+
yes: false,
|
|
857
|
+
start: true,
|
|
858
|
+
update: false,
|
|
859
|
+
};
|
|
860
|
+
for (let i = 0; i < argv.length; i++) {
|
|
861
|
+
const a = argv[i];
|
|
862
|
+
if (a === '--mode=install') opts.mode = 'install';
|
|
863
|
+
else if (a === '--mode=update') opts.mode = 'update';
|
|
864
|
+
else if (a === '--mode=install-only-system') opts.mode = 'install-only-system';
|
|
865
|
+
else if (a === '--mode') {
|
|
866
|
+
const v = argv[++i];
|
|
867
|
+
if (v === 'install' || v === 'update' || v === 'install-only-system') opts.mode = v;
|
|
868
|
+
}
|
|
869
|
+
else if (a === '--dry-run') opts.dryRun = true;
|
|
870
|
+
else if (a === '--force') opts.force = true;
|
|
871
|
+
else if (a === '--yes' || a === '-y') opts.yes = true;
|
|
872
|
+
else if (a === '--non-interactive') opts.yes = true;
|
|
873
|
+
else if (a === '--no-service') opts.start = false;
|
|
874
|
+
else if (a === '--update') opts.mode = 'update';
|
|
875
|
+
else if (a === '--help' || a === '-h') {
|
|
876
|
+
console.log(
|
|
877
|
+
'Usage: node cli/provision-claude.mjs [--mode=install|update|install-only-system]\n' +
|
|
878
|
+
' [--dry-run] [--force] [--yes] [--non-interactive] [--no-service]',
|
|
879
|
+
);
|
|
880
|
+
process.exit(0);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (opts.mode === 'update') opts.update = true;
|
|
884
|
+
return opts;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
888
|
+
const opts = parseFlags(process.argv.slice(2));
|
|
889
|
+
runProvision(opts).catch((err) => {
|
|
890
|
+
logErr(`provision failed: ${err?.message || err}`);
|
|
891
|
+
process.exit(1);
|
|
892
|
+
});
|
|
893
|
+
}
|