@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.
Files changed (332) hide show
  1. package/.claude/CLAUDE.md +15 -0
  2. package/.claude/agents/_shared/AGENT_BASELINE.md +168 -0
  3. package/.claude/agents/_shared/CLAUDE_TOOLS.md +412 -0
  4. package/.claude/agents/_shared/SKILLS.md +109 -0
  5. package/.claude/agents/agent-browser.md +80 -0
  6. package/.claude/agents/baldr.md +49 -0
  7. package/.claude/agents/forseti.md +51 -0
  8. package/.claude/agents/frigg.md +42 -0
  9. package/.claude/agents/heimdall.md +33 -0
  10. package/.claude/agents/hermod.md +53 -0
  11. package/.claude/agents/mimir.md +49 -0
  12. package/.claude/agents/odin.md +287 -0
  13. package/.claude/agents/quick.md +34 -0
  14. package/.claude/agents/semble-search.md +50 -0
  15. package/.claude/agents/thor.md +53 -0
  16. package/.claude/agents/tyr.md +56 -0
  17. package/.claude/agents/vidarr.md +54 -0
  18. package/.claude/agents/vor.md +53 -0
  19. package/.claude/commands/audit.md +25 -0
  20. package/.claude/commands/bizar.md +22 -0
  21. package/.claude/commands/explain.md +17 -0
  22. package/.claude/commands/init.md +29 -0
  23. package/.claude/commands/learn.md +44 -0
  24. package/.claude/commands/plan.md +35 -0
  25. package/.claude/commands/plow-through.md +50 -0
  26. package/.claude/commands/pr-review.md +49 -0
  27. package/.claude/commands/setup-provider.md +96 -0
  28. package/.claude/commands/tailscale-serve.md +100 -0
  29. package/.claude/commands/team.md +132 -0
  30. package/.claude/commands/test.md +62 -0
  31. package/.claude/commands/validate.md +68 -0
  32. package/.claude/commands/visual-plan.md +24 -0
  33. package/.claude/hooks/README.md +92 -0
  34. package/.claude/hooks/posttooluse-editwrite.mjs +91 -0
  35. package/.claude/hooks/pretooluse-bash.mjs +81 -0
  36. package/.claude/hooks/pretooluse-editwrite.mjs +139 -0
  37. package/.claude/hooks/sessionend-recall.mjs +74 -0
  38. package/.claude/hooks/sessionstart-prime.mjs +80 -0
  39. package/.claude/hooks/userpromptsubmit-tag.mjs +80 -0
  40. package/.claude/settings.json +116 -0
  41. package/.claude/skills/9router/SKILL.md +80 -0
  42. package/.claude/skills/9router-chat/SKILL.md +73 -0
  43. package/.claude/skills/9router-embeddings/SKILL.md +69 -0
  44. package/.claude/skills/9router-image/SKILL.md +86 -0
  45. package/.claude/skills/9router-stt/SKILL.md +79 -0
  46. package/.claude/skills/9router-tts/SKILL.md +80 -0
  47. package/.claude/skills/9router-web-fetch/SKILL.md +99 -0
  48. package/.claude/skills/9router-web-search/SKILL.md +91 -0
  49. package/.claude/skills/bizar/README.md +9 -0
  50. package/.claude/skills/bizar/SKILL.md +450 -0
  51. package/.claude/skills/cpp-coding-standards/README.md +28 -0
  52. package/.claude/skills/cpp-coding-standards/SKILL.md +634 -0
  53. package/.claude/skills/cpp-coding-standards/references/concurrency.md +320 -0
  54. package/.claude/skills/cpp-coding-standards/references/error-handling.md +229 -0
  55. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +216 -0
  56. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +282 -0
  57. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +96 -0
  58. package/.claude/skills/cpp-testing/README.md +28 -0
  59. package/.claude/skills/cpp-testing/SKILL.md +304 -0
  60. package/.claude/skills/cpp-testing/references/coverage.md +370 -0
  61. package/.claude/skills/cpp-testing/references/framework-compare.md +175 -0
  62. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +499 -0
  63. package/.claude/skills/cpp-testing/references/mocking.md +364 -0
  64. package/.claude/skills/cpp-testing/references/tdd-workflow.md +308 -0
  65. package/.claude/skills/cubesandbox/SKILL.md +148 -0
  66. package/.claude/skills/embedded-esp-idf/README.md +41 -0
  67. package/.claude/skills/embedded-esp-idf/SKILL.md +439 -0
  68. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +214 -0
  69. package/.claude/skills/embedded-esp-idf/references/host-tests.md +164 -0
  70. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +157 -0
  71. package/.claude/skills/embedded-esp-idf/references/kconfig.md +159 -0
  72. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +118 -0
  73. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +137 -0
  74. package/.claude/skills/embedded-esp-idf/references/nvs.md +121 -0
  75. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +192 -0
  76. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +47 -0
  77. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +77 -0
  78. package/.claude/skills/glyph/SKILL.md +163 -0
  79. package/.claude/skills/harness-engineering/SKILL.md +143 -0
  80. package/.claude/skills/lightrag/SKILL.md +81 -0
  81. package/.claude/skills/memory-protocol/SKILL.md +105 -0
  82. package/.claude/skills/obsidian/SKILL.md +306 -0
  83. package/.claude/skills/read-the-damn-docs/SKILL.md +113 -0
  84. package/.claude/skills/self-improvement/SKILL.md +64 -0
  85. package/README.md +87 -59
  86. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js → EnvVarsSection-B58aiJiE.js} +1 -1
  87. package/bizar-dash/dist/assets/{EnvVarsSection-DXM8gRm_.js.map → EnvVarsSection-B58aiJiE.js.map} +1 -1
  88. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js → MobileChat-BJrqwVDd.js} +1 -1
  89. package/bizar-dash/dist/assets/{MobileChat-BnKN_Ks_.js.map → MobileChat-BJrqwVDd.js.map} +1 -1
  90. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js → MobileSettings-CEQNJNLJ.js} +1 -1
  91. package/bizar-dash/dist/assets/{MobileSettings-DjCPxC-Q.js.map → MobileSettings-CEQNJNLJ.js.map} +1 -1
  92. package/bizar-dash/dist/assets/{main-DYiZqMrn.js → main-IvfQAOfy.js} +1 -1
  93. package/bizar-dash/dist/assets/{main-DYiZqMrn.js.map → main-IvfQAOfy.js.map} +1 -1
  94. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js → markdown-tOLaD6nm.js} +1 -1
  95. package/bizar-dash/dist/assets/{markdown-C6mXtQxD.js.map → markdown-tOLaD6nm.js.map} +1 -1
  96. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js → mobile-DYCHcUpq.js} +1 -1
  97. package/bizar-dash/dist/assets/{mobile-CMHqtLV2.js.map → mobile-DYCHcUpq.js.map} +1 -1
  98. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js → mobile-layout-CBHjpwsb.js} +2 -2
  99. package/bizar-dash/dist/assets/{mobile-layout-3jIhHX_p.js.map → mobile-layout-CBHjpwsb.js.map} +1 -1
  100. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js → useSlashCommands-Bd7_FA6U.js} +2 -2
  101. package/bizar-dash/dist/assets/{useSlashCommands-DtITw8Xv.js.map → useSlashCommands-Bd7_FA6U.js.map} +1 -1
  102. package/bizar-dash/dist/assets/{vendor-CeHGtduv.js → vendor-C843201K.js} +12 -12
  103. package/bizar-dash/dist/assets/vendor-C843201K.js.map +1 -0
  104. package/bizar-dash/dist/index.html +6 -6
  105. package/bizar-dash/dist/mobile.html +2 -2
  106. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  107. package/bizar-dash/src/server/api.mjs +4 -4
  108. package/bizar-dash/src/server/background-store.mjs +15 -16
  109. package/bizar-dash/src/server/bg-poller.mjs +25 -35
  110. package/bizar-dash/src/server/bg-retry.mjs +66 -194
  111. package/bizar-dash/src/server/claude-bg-spawner.mjs +390 -0
  112. package/bizar-dash/src/server/claude-info.mjs +412 -0
  113. package/bizar-dash/src/server/{cline-runner.mjs → claude-runner.mjs} +53 -48
  114. package/bizar-dash/src/server/claude-sdk.mjs +191 -0
  115. package/bizar-dash/src/server/providers-store.mjs +22 -49
  116. package/bizar-dash/src/server/routes/background.mjs +24 -20
  117. package/bizar-dash/src/server/routes/chat.mjs +223 -326
  118. package/bizar-dash/src/server/routes/claude-session-detail.mjs +329 -0
  119. package/bizar-dash/src/server/routes/claude-sessions.mjs +259 -0
  120. package/bizar-dash/src/server/routes/tasks.mjs +20 -28
  121. package/bizar-dash/src/server/task-delegator.mjs +76 -77
  122. package/bizar-dash/src/web/components/chat/useChat.ts +23 -27
  123. package/bizar-dash/src/web/views/Chat.tsx +8 -8
  124. package/cli/bin.mjs +31 -18
  125. package/cli/commands/claude-cmd.mjs +348 -0
  126. package/cli/commands/install.mjs +12 -11
  127. package/cli/commands/sandbox.mjs +220 -0
  128. package/cli/commands/validate.mjs +252 -288
  129. package/cli/dev-link.test.mjs +11 -11
  130. package/cli/doctor.mjs +131 -213
  131. package/cli/install.mjs +33 -37
  132. package/cli/install.test.mjs +9 -9
  133. package/cli/provision-claude.mjs +893 -0
  134. package/cli/provision.mjs +330 -226
  135. package/cli/provision.test.mjs +102 -0
  136. package/cli/utils.mjs +72 -58
  137. package/config/AGENTS.md +28 -28
  138. package/config/agents/_shared/AGENT_BASELINE.md +104 -684
  139. package/config/skills/bizar/SKILL.md +197 -0
  140. package/config/skills/cubesandbox/SKILL.md +148 -0
  141. package/config/skills/harness-engineering/SKILL.md +142 -0
  142. package/install.sh +66 -44
  143. package/package.json +19 -11
  144. package/packages/sdk/ARCHITECTURE.md +40 -36
  145. package/packages/sdk/package-lock.json +32 -0
  146. package/packages/sdk/package.json +15 -5
  147. package/{plugins/bizar → packages/sdk}/src/dangerous-patterns.ts +6 -28
  148. package/packages/sdk/src/fingerprint.ts +0 -0
  149. package/packages/sdk/src/index.ts +38 -73
  150. package/packages/sdk/src/mcp/bin.ts +51 -0
  151. package/packages/sdk/src/mcp/server.ts +498 -0
  152. package/{plugins/bizar/src/memory-vault.ts → packages/sdk/src/memory/index.ts} +18 -21
  153. package/packages/sdk/tests/sdk.test.mjs +148 -0
  154. package/packages/sdk/vitest.config.ts +1 -1
  155. package/scripts/bh-full-e2e.mjs +166 -363
  156. package/scripts/check-deps.mjs +28 -46
  157. package/scripts/mirror-claude-md.sh +78 -0
  158. package/scripts/test-in-container.sh +157 -0
  159. package/templates/clean-state-checklist.md +3 -3
  160. package/templates/sprint-contract.md +2 -2
  161. package/bizar-dash/.bizar/activity.log +0 -3
  162. package/bizar-dash/.omx/logs/omx-2026-07-07.jsonl +0 -1
  163. package/bizar-dash/.omx/state/session.json +0 -10
  164. package/bizar-dash/bizar-design/EXPLANATION.md +0 -307
  165. package/bizar-dash/bizar-design/INSTRUCTIONS.md +0 -149
  166. package/bizar-dash/bizar-design/canvas.html +0 -281
  167. package/bizar-dash/bizar-design/canvas.html.artifact.json +0 -19
  168. package/bizar-dash/bizar-design/components.css +0 -1665
  169. package/bizar-dash/bizar-design/components.html +0 -557
  170. package/bizar-dash/bizar-design/components.html.artifact.json +0 -19
  171. package/bizar-dash/bizar-design/desktop/agents.html +0 -74
  172. package/bizar-dash/bizar-design/desktop/agents.html.artifact.json +0 -19
  173. package/bizar-dash/bizar-design/desktop/memory.html +0 -75
  174. package/bizar-dash/bizar-design/desktop/memory.html.artifact.json +0 -19
  175. package/bizar-dash/bizar-design/desktop/metrics.html +0 -63
  176. package/bizar-dash/bizar-design/desktop/metrics.html.artifact.json +0 -19
  177. package/bizar-dash/bizar-design/desktop/overview.html +0 -95
  178. package/bizar-dash/bizar-design/desktop/overview.html.artifact.json +0 -19
  179. package/bizar-dash/bizar-design/desktop/settings.html +0 -70
  180. package/bizar-dash/bizar-design/desktop/settings.html.artifact.json +0 -19
  181. package/bizar-dash/bizar-design/icons.svg +0 -44
  182. package/bizar-dash/bizar-design/index.html +0 -192
  183. package/bizar-dash/bizar-design/index.html.artifact.json +0 -19
  184. package/bizar-dash/bizar-design/mobile/agents.html +0 -29
  185. package/bizar-dash/bizar-design/mobile/agents.html.artifact.json +0 -19
  186. package/bizar-dash/bizar-design/mobile/memory.html +0 -28
  187. package/bizar-dash/bizar-design/mobile/memory.html.artifact.json +0 -19
  188. package/bizar-dash/bizar-design/mobile/metrics.html +0 -29
  189. package/bizar-dash/bizar-design/mobile/metrics.html.artifact.json +0 -19
  190. package/bizar-dash/bizar-design/mobile/overview.html +0 -33
  191. package/bizar-dash/bizar-design/mobile/overview.html.artifact.json +0 -19
  192. package/bizar-dash/bizar-design/mobile/settings.html +0 -28
  193. package/bizar-dash/bizar-design/mobile/settings.html.artifact.json +0 -19
  194. package/bizar-dash/bizar-design/prototype.js +0 -114
  195. package/bizar-dash/bizar-design/tokens.css +0 -106
  196. package/bizar-dash/dist/assets/vendor-CeHGtduv.js.map +0 -1
  197. package/bizar-dash/node_modules/.package-lock.json +0 -6
  198. package/bizar-dash/package-lock.json +0 -6
  199. package/bizar-dash/src/server/cline-sdk.mjs +0 -132
  200. package/bizar-dash/src/server/routes/cline-session-detail.mjs +0 -559
  201. package/bizar-dash/src/server/routes/cline-sessions.mjs +0 -291
  202. package/cli/commands/cline-cmd.mjs +0 -289
  203. package/cli/commands/validate.test.mjs +0 -344
  204. package/cli/doctor.test.mjs +0 -350
  205. package/config/cline.json.template +0 -342
  206. package/packages/sdk/src/client.ts +0 -188
  207. package/packages/sdk/src/cline-events.ts +0 -134
  208. package/packages/sdk/src/cline-types.ts +0 -66
  209. package/packages/sdk/src/cline.ts +0 -339
  210. package/packages/sdk/src/errors.ts +0 -129
  211. package/packages/sdk/src/events.ts +0 -153
  212. package/packages/sdk/src/types.ts +0 -176
  213. package/packages/sdk/tests/client.test.ts +0 -217
  214. package/packages/sdk/tests/errors.test.ts +0 -108
  215. package/packages/sdk/tests/events.test.ts +0 -139
  216. package/packages/sdk/tests/fixtures/fetch-mock.ts +0 -152
  217. package/packages/sdk/tests/fixtures/sse-mock.ts +0 -30
  218. package/plugins/bizar/ARCHITECTURE.md +0 -142
  219. package/plugins/bizar/CONSTRAINTS.md +0 -67
  220. package/plugins/bizar/LICENSE +0 -21
  221. package/plugins/bizar/README.md +0 -448
  222. package/plugins/bizar/index.ts +0 -872
  223. package/plugins/bizar/package.json +0 -39
  224. package/plugins/bizar/scripts/check-forbidden-imports.sh +0 -33
  225. package/plugins/bizar/src/background-state.ts +0 -641
  226. package/plugins/bizar/src/background.ts +0 -1806
  227. package/plugins/bizar/src/cline-runner.ts +0 -203
  228. package/plugins/bizar/src/clineruntime.ts +0 -227
  229. package/plugins/bizar/src/commands-impl.ts +0 -151
  230. package/plugins/bizar/src/commands.ts +0 -1799
  231. package/plugins/bizar/src/compaction.d.mts +0 -48
  232. package/plugins/bizar/src/compaction.mjs +0 -192
  233. package/plugins/bizar/src/dashboard-client.ts +0 -233
  234. package/plugins/bizar/src/event-stream.ts +0 -606
  235. package/plugins/bizar/src/fingerprint.ts +0 -120
  236. package/plugins/bizar/src/handoff.ts +0 -79
  237. package/plugins/bizar/src/hooks/memory-flush-on-compact.ts +0 -123
  238. package/plugins/bizar/src/hooks/memory-inject.ts +0 -247
  239. package/plugins/bizar/src/hooks/memory-write-on-end.ts +0 -188
  240. package/plugins/bizar/src/hooks/skill-curator.ts +0 -180
  241. package/plugins/bizar/src/http-client.ts +0 -467
  242. package/plugins/bizar/src/key-rotation.ts +0 -218
  243. package/plugins/bizar/src/logger.ts +0 -144
  244. package/plugins/bizar/src/loop-engineering.ts +0 -241
  245. package/plugins/bizar/src/loop.ts +0 -176
  246. package/plugins/bizar/src/mistake-recovery.ts +0 -98
  247. package/plugins/bizar/src/odin.ts +0 -227
  248. package/plugins/bizar/src/options.ts +0 -470
  249. package/plugins/bizar/src/plan-fs.ts +0 -323
  250. package/plugins/bizar/src/reasoning-clean.ts +0 -454
  251. package/plugins/bizar/src/report.ts +0 -178
  252. package/plugins/bizar/src/research-prompt.ts +0 -35
  253. package/plugins/bizar/src/serve-info.ts +0 -228
  254. package/plugins/bizar/src/serve.ts +0 -496
  255. package/plugins/bizar/src/settings.ts +0 -349
  256. package/plugins/bizar/src/state.ts +0 -298
  257. package/plugins/bizar/src/tool-discipline.ts +0 -105
  258. package/plugins/bizar/src/tools/agent-browser.ts +0 -315
  259. package/plugins/bizar/src/tools/bg-collect.ts +0 -131
  260. package/plugins/bizar/src/tools/bg-get-comments.ts +0 -266
  261. package/plugins/bizar/src/tools/bg-kill.ts +0 -116
  262. package/plugins/bizar/src/tools/bg-pause.ts +0 -99
  263. package/plugins/bizar/src/tools/bg-report-progress.ts +0 -115
  264. package/plugins/bizar/src/tools/bg-resume.ts +0 -94
  265. package/plugins/bizar/src/tools/bg-send-message.ts +0 -223
  266. package/plugins/bizar/src/tools/bg-spawn.ts +0 -502
  267. package/plugins/bizar/src/tools/bg-status.ts +0 -130
  268. package/plugins/bizar/src/tools/graph-query.ts +0 -278
  269. package/plugins/bizar/src/tools/loop-engineering.ts +0 -193
  270. package/plugins/bizar/src/tools/memory-list.ts +0 -43
  271. package/plugins/bizar/src/tools/memory-read.ts +0 -69
  272. package/plugins/bizar/src/tools/memory-search.ts +0 -47
  273. package/plugins/bizar/src/tools/memory-write.ts +0 -54
  274. package/plugins/bizar/src/tools/open-kb.ts +0 -198
  275. package/plugins/bizar/src/tools/plan-action.ts +0 -785
  276. package/plugins/bizar/src/tools/read-glyph-feedback.ts +0 -191
  277. package/plugins/bizar/src/tools/team-spawn.ts +0 -73
  278. package/plugins/bizar/src/tools/team-status.ts +0 -76
  279. package/plugins/bizar/src/tools/wait-for-feedback.ts +0 -415
  280. package/plugins/bizar/src/trajectory.ts +0 -104
  281. package/plugins/bizar/tests/README.md +0 -99
  282. package/plugins/bizar/tests/attach-handler-bug.test.ts +0 -169
  283. package/plugins/bizar/tests/background-state.test.ts +0 -277
  284. package/plugins/bizar/tests/background.test.ts +0 -402
  285. package/plugins/bizar/tests/block.test.ts +0 -195
  286. package/plugins/bizar/tests/canonical-key-order.test.ts +0 -75
  287. package/plugins/bizar/tests/clineruntime-config.test.ts +0 -283
  288. package/plugins/bizar/tests/commands-impl.test.ts +0 -316
  289. package/plugins/bizar/tests/commands.test.ts +0 -584
  290. package/plugins/bizar/tests/compaction.test.ts +0 -264
  291. package/plugins/bizar/tests/config.test.ts +0 -128
  292. package/plugins/bizar/tests/dashboard-client.test.ts +0 -159
  293. package/plugins/bizar/tests/dispose.test.ts +0 -336
  294. package/plugins/bizar/tests/event-stream.test.ts +0 -409
  295. package/plugins/bizar/tests/event.test.ts +0 -262
  296. package/plugins/bizar/tests/fingerprint.test.ts +0 -162
  297. package/plugins/bizar/tests/http-client.test.ts +0 -404
  298. package/plugins/bizar/tests/init-helpers.test.ts +0 -203
  299. package/plugins/bizar/tests/integration/slash-command.test.ts +0 -349
  300. package/plugins/bizar/tests/integration/tool-routing.test.ts +0 -98
  301. package/plugins/bizar/tests/key-rotation.test.ts +0 -396
  302. package/plugins/bizar/tests/loop-engineering.test.ts +0 -168
  303. package/plugins/bizar/tests/loop.test.ts +0 -397
  304. package/plugins/bizar/tests/memory-write-on-end.test.ts +0 -92
  305. package/plugins/bizar/tests/mistake-recovery.test.ts +0 -116
  306. package/plugins/bizar/tests/odin.test.ts +0 -125
  307. package/plugins/bizar/tests/options.test.ts +0 -329
  308. package/plugins/bizar/tests/reasoning-clean.test.ts +0 -422
  309. package/plugins/bizar/tests/safety.test.ts +0 -256
  310. package/plugins/bizar/tests/serve.test.ts +0 -339
  311. package/plugins/bizar/tests/settings.test.ts +0 -351
  312. package/plugins/bizar/tests/stall-think.test.ts +0 -750
  313. package/plugins/bizar/tests/state.test.ts +0 -276
  314. package/plugins/bizar/tests/tool-discipline.test.ts +0 -77
  315. package/plugins/bizar/tests/tools/agent-browser.test.ts +0 -98
  316. package/plugins/bizar/tests/tools/bg-collect.test.ts +0 -337
  317. package/plugins/bizar/tests/tools/bg-get-comments.test.ts +0 -485
  318. package/plugins/bizar/tests/tools/bg-kill.test.ts +0 -235
  319. package/plugins/bizar/tests/tools/bg-pause.test.ts +0 -61
  320. package/plugins/bizar/tests/tools/bg-report-progress.test.ts +0 -79
  321. package/plugins/bizar/tests/tools/bg-resume.test.ts +0 -40
  322. package/plugins/bizar/tests/tools/bg-send-message.test.ts +0 -116
  323. package/plugins/bizar/tests/tools/bg-spawn-delegation.test.ts +0 -147
  324. package/plugins/bizar/tests/tools/bg-spawn-http.test.ts +0 -233
  325. package/plugins/bizar/tests/tools/bg-spawn.test.ts +0 -311
  326. package/plugins/bizar/tests/tools/bg-status.test.ts +0 -217
  327. package/plugins/bizar/tests/tools/cline-runner.test.ts +0 -115
  328. package/plugins/bizar/tests/tools/plan-action.test.ts +0 -599
  329. package/plugins/bizar/tests/tools/read-glyph-feedback.test.ts +0 -253
  330. package/plugins/bizar/tests/tools/wait-for-feedback.test.ts +0 -390
  331. package/plugins/bizar/tests/update-deadlock.test.ts +0 -151
  332. package/plugins/bizar/tsconfig.json +0 -29
@@ -1,1799 +0,0 @@
1
- /**
2
- * commands.ts
3
- *
4
- * v0.5.0 — Pure slash-command parser.
5
- *
6
- * The plugin's `chat.message` hook calls `parseSlashCommand(text, ctx)`
7
- * on every user message. If the message starts with `/`, this function
8
- * classifies it as a known command or returns `null` (so the message
9
- * passes through to the LLM normally).
10
- *
11
- * Design constraints:
12
- * - Pure function. No I/O, no environment reads, no Date.now() calls
13
- * inside the parser itself.
14
- * - The function is the **only** place slash commands are parsed.
15
- * The hook gathers context (current settings, available plan slugs)
16
- * and feeds it to the parser, then dispatches the returned
17
- * `sideEffect` via `executeSideEffect()` in commands-impl.ts.
18
- * - Returns `null` for non-slash messages so the LLM still sees them.
19
- *
20
- * Output shape (per slash command):
21
- * `{ handled: true, response, settingsPatch?, sideEffect? }`
22
- *
23
- * - `response`: text surfaced to the user / LLM.
24
- * - `settingsPatch`: optional partial `PlanSettings` to apply.
25
- * - `sideEffect`: optional descriptor for the hook to execute. The
26
- * `tool_invocation` kind routes through `executeToolInvocation`,
27
- * which builds a synthetic `ToolContext` and invokes the
28
- * registered tool directly. The other kinds (`create_plan`,
29
- * `list_plans`, `open_plan_url`) are handled by
30
- * `executeSideEffect` in commands-impl.ts.
31
- *
32
- * The parser is unit-tested directly (tests/commands.test.ts).
33
- * Side-effect execution is tested in tests/commands-impl.test.ts.
34
- */
35
-
36
- import type { DefaultTemplate, PlanSettings } from "./settings.js";
37
- import { KNOWN_TEMPLATES } from "./settings.js";
38
-
39
- // --- Public types --------------------------------------------------------
40
-
41
- /**
42
- * What the parser asks the chat.message hook to do on the host side.
43
- *
44
- * v0.5.0 — extended with the `tool_invocation` kind (per C3). Subcommand
45
- * parsers translate the user's typed subcommand into a `tool_invocation`
46
- * side-effect that calls a real `bizar_*` tool with a synthetic
47
- * `ToolContext`. The chat hook does not need to know about the tool
48
- * layer; it just hands the descriptor to `executeToolInvocation`.
49
- */
50
- export type SideEffect =
51
- | {
52
- kind: "create_plan";
53
- slug: string;
54
- template: DefaultTemplate | null;
55
- }
56
- | {
57
- kind: "open_plan_url";
58
- slug: string;
59
- }
60
- | {
61
- kind: "list_plans";
62
- }
63
- | {
64
- kind: "launch_dashboard";
65
- defaultPort: number;
66
- }
67
- | {
68
- kind: "tool_invocation";
69
- toolName: string;
70
- args: unknown;
71
- };
72
-
73
- /** Dialog component types that can be rendered in the dashboard. */
74
- export type DialogComponent =
75
- | "visual-plan"
76
- | "plan-create"
77
- | "plan-list"
78
- | "help"
79
- | "audit"
80
- | "generic";
81
-
82
- export interface DialogDescriptor {
83
- id: string;
84
- title: string;
85
- command: string;
86
- component: DialogComponent;
87
- data?: Record<string, unknown>;
88
- }
89
-
90
- function generateId(): string {
91
- return `dlg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
92
- }
93
-
94
- export interface SlashCommandResult {
95
- handled: true;
96
- /** Text shown to the user / LLM. */
97
- response: string;
98
- /** Optional settings mutation. */
99
- settingsPatch?: Partial<PlanSettings>;
100
- /** Optional side-effect to perform (file I/O lives in the hook, not here). */
101
- sideEffect?: SideEffect;
102
- /** Optional dialog to open in the dashboard instead of showing text. */
103
- dialog?: DialogDescriptor;
104
- }
105
-
106
- /**
107
- * Context passed to the parser. The parser does NOT call `getCurrentSettings`
108
- * itself — instead the caller injects the live values. This keeps the parser
109
- * pure and lets tests stub the context.
110
- */
111
- export interface ParseContext {
112
- /** Current plan settings (already loaded by the caller). */
113
- currentSettings: PlanSettings;
114
- /** Slugs of plans that exist in the worktree's plans/ directory. */
115
- availablePlanSlugs?: readonly string[];
116
- /**
117
- * The default port for `/plan open <slug>` URLs. The viewer runs in-process
118
- * on 127.0.0.1:4321-4330. v0.5.0 defers actually starting the server; the
119
- * URL is informational only.
120
- */
121
- defaultPort?: number;
122
- }
123
-
124
- // --- Internals -----------------------------------------------------------
125
-
126
- /** Same slug rule used by `cli/plan.mjs`. */
127
- const SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,63}$/;
128
-
129
- function isValidSlug(s: string): boolean {
130
- return SLUG_REGEX.test(s);
131
- }
132
-
133
- function isKnownTemplate(t: string): t is DefaultTemplate {
134
- return (KNOWN_TEMPLATES as readonly string[]).includes(t);
135
- }
136
-
137
- function titleCase(slug: string): string {
138
- return slug
139
- .split(/[-_]/)
140
- .map((w) => (w.length === 0 ? w : w[0]!.toUpperCase() + w.slice(1)))
141
- .join(" ");
142
- }
143
-
144
- /** Valid status values for `/plan status`. Mirrors plan-action.ts. */
145
- const PLAN_STATUSES = [
146
- "draft",
147
- "approved",
148
- "rejected",
149
- "in-progress",
150
- "done",
151
- ] as const;
152
- type PlanStatus = (typeof PLAN_STATUSES)[number];
153
-
154
- function isPlanStatus(s: string): s is PlanStatus {
155
- return (PLAN_STATUSES as readonly string[]).includes(s);
156
- }
157
-
158
- // --- Tokenization --------------------------------------------------------
159
-
160
- /**
161
- * Tokenize a subcommand argument string into argv-like tokens, with
162
- * double-quoted strings preserved as a single token. This is just
163
- * enough for our flag parser; it does not support escape sequences
164
- * beyond `\"`.
165
- *
166
- * Examples:
167
- * `add foo --title "Hello world" --type task` →
168
- * ["add", "foo", "--title", "Hello world", "--type", "task"]
169
- * `comment foo "Make this bigger"` →
170
- * ["comment", "foo", "Make this bigger"]
171
- */
172
- function tokenize(input: string): string[] {
173
- const tokens: string[] = [];
174
- let cur = "";
175
- let inQuotes = false;
176
- for (let i = 0; i < input.length; i++) {
177
- const ch = input[i]!;
178
- if (inQuotes) {
179
- if (ch === "\\" && i + 1 < input.length && input[i + 1] === '"') {
180
- cur += '"';
181
- i++;
182
- } else if (ch === '"') {
183
- inQuotes = false;
184
- } else {
185
- cur += ch;
186
- }
187
- continue;
188
- }
189
- if (ch === '"') {
190
- inQuotes = true;
191
- continue;
192
- }
193
- if (ch === " " || ch === "\t") {
194
- if (cur !== "") {
195
- tokens.push(cur);
196
- cur = "";
197
- }
198
- continue;
199
- }
200
- cur += ch;
201
- }
202
- if (cur !== "") tokens.push(cur);
203
- return tokens;
204
- }
205
-
206
- interface ParsedFlags {
207
- positional: string[];
208
- flags: Record<string, string>;
209
- }
210
-
211
- /**
212
- * Parse a flag list of the form `--key value` (or `--key=value`).
213
- * Positional arguments (no leading `--`) come back in order. Flags
214
- * without a value (e.g. `--verbose`) are stored as `""`.
215
- */
216
- function parseFlags(tokens: string[]): ParsedFlags {
217
- const positional: string[] = [];
218
- const flags: Record<string, string> = {};
219
- for (let i = 0; i < tokens.length; i++) {
220
- const t = tokens[i]!;
221
- if (t.startsWith("--")) {
222
- const eq = t.indexOf("=");
223
- if (eq !== -1) {
224
- flags[t.slice(2, eq)] = t.slice(eq + 1);
225
- continue;
226
- }
227
- const key = t.slice(2);
228
- const next = tokens[i + 1];
229
- if (next !== undefined && !next.startsWith("--")) {
230
- flags[key] = next;
231
- i++;
232
- } else {
233
- flags[key] = "";
234
- }
235
- } else {
236
- positional.push(t);
237
- }
238
- }
239
- return { positional, flags };
240
- }
241
-
242
- // --- The main parser -----------------------------------------------------
243
-
244
- /**
245
- * Parse a user message and decide whether it's a slash command.
246
- *
247
- * Returns:
248
- * - `null` — not a slash command; pass through.
249
- * - `SlashCommandResult` — slash command was recognized.
250
- *
251
- * Recognized commands:
252
- * - `/visual-plan on|off|status` — settings patch only
253
- * - `/plan new <slug> [template]` — create_plan
254
- * - `/plan list` — list_plans
255
- * - `/plan open <slug>` — open_plan_url
256
- * - `/plan get <slug>` — tool_invocation: bizar_plan_action (get_canvas)
257
- * - `/plan add <slug> [--title … --type … --x N --y N …]`
258
- * — tool_invocation: bizar_plan_action (add_element)
259
- * - `/plan update <slug> <id> [--x N --y N --title T --content C]`
260
- * — tool_invocation: bizar_plan_action (update_element)
261
- * - `/plan delete <slug> <id>` — tool_invocation: bizar_plan_action (delete_element)
262
- * - `/plan comment <slug> [id] "text"`
263
- * — tool_invocation: bizar_plan_action (add_comment)
264
- * - `/plan status <slug> <status>` — tool_invocation: bizar_plan_action (set_status)
265
- * - `/plan comments <slug> [id]` — tool_invocation: bizar_get_plan_comments
266
- * - `/plan wait <slug> [--timeout N]`
267
- * — deferred (returns response; no tool call)
268
- * - `/kb` — open the Bizar Memory vault in Obsidian
269
- * - `/help` / `/commands` — help text
270
- */
271
- export function parseSlashCommand(
272
- text: string,
273
- ctx: ParseContext,
274
- ): SlashCommandResult | null {
275
- if (typeof text !== "string") return null;
276
- const trimmed = text.trim();
277
- if (trimmed === "") return null;
278
- if (!trimmed.startsWith("/")) return null;
279
-
280
- // Just a bare slash — not a command
281
- if (trimmed === "/") return null;
282
-
283
- // Match `/<command>` optionally followed by whitespace and args
284
- const match = /^\/([a-zA-Z][\w-]*)(?:\s+(.*))?$/.exec(trimmed);
285
- if (match === null) return null;
286
-
287
- const command = match[1]!.toLowerCase();
288
- const rest = (match[2] ?? "").trim();
289
-
290
- switch (command) {
291
- case "visual-plan":
292
- return handleVisualPlan(rest, ctx);
293
- case "plan":
294
- return handlePlan(rest, ctx);
295
- case "bizar":
296
- return handleBizar(rest, ctx);
297
- case "kb":
298
- return handleKb(rest, ctx);
299
- case "odin":
300
- return handleOdin(rest, ctx);
301
- case "loop":
302
- return handleLoop(rest, ctx);
303
- case "kanban":
304
- return handleKanban(rest, ctx);
305
- case "review":
306
- return handleReview(rest, ctx);
307
- case "handoff":
308
- return handleHandoff(rest, ctx);
309
- case "grill":
310
- return handleGrill(rest, ctx);
311
- case "glyph":
312
- return handleGlyph(rest, ctx);
313
- case "decision":
314
- return handleDecision(rest, ctx);
315
- case "issue":
316
- return handleIssue(rest, ctx);
317
- case "digest":
318
- return handleDigest(rest, ctx);
319
- case "usage":
320
- return handleUsage(rest, ctx);
321
- case "memory":
322
- return handleMemory(rest, ctx);
323
- case "spawn":
324
- return handleSpawn(rest, ctx);
325
- case "team":
326
- return handleTeam(rest, ctx);
327
- case "audit":
328
- return handleAudit(rest, ctx);
329
- case "deploy":
330
- return handleDeploy(rest, ctx);
331
- case "status":
332
- return handleStatus(rest, ctx);
333
- case "lightrag":
334
- return handleLightrag(rest, ctx);
335
- case "headroom":
336
- return handleHeadroom(rest, ctx);
337
- case "minimax":
338
- return handleMiniMax(rest, ctx);
339
- case "service":
340
- return handleService(rest, ctx);
341
- case "dash":
342
- return handleDash(rest, ctx);
343
- case "mod":
344
- return handleMod(rest, ctx);
345
- case "init":
346
- return handleInit(rest, ctx);
347
- case "dev":
348
- return handleDev(rest, ctx);
349
- case "test":
350
- return handleTest(rest, ctx);
351
- case "agent-browser":
352
- return handleAgentBrowser(rest, ctx);
353
- case "plow-through":
354
- return handlePlowThrough(rest, ctx);
355
- case "tailscale":
356
- return handleTailscale(rest, ctx);
357
- case "providers":
358
- return handleProviders(rest, ctx);
359
- case "clip":
360
- return handleClip(rest, ctx);
361
- case "ocr":
362
- return handleOcr(rest, ctx);
363
- case "workspace":
364
- return handleWorkspace(rest, ctx);
365
- case "voice":
366
- return handleVoice(rest, ctx);
367
- case "help":
368
- case "commands":
369
- return helpResult();
370
- default:
371
- return {
372
- handled: true,
373
- response: `Unknown command: /${command}. Try /help for a list of available commands.`,
374
- };
375
- }
376
- }
377
-
378
- // --- Command handlers ----------------------------------------------------
379
-
380
- function handleVisualPlan(arg: string, ctx: ParseContext): SlashCommandResult {
381
- const lc = arg.toLowerCase();
382
- const currentEnabled = ctx.currentSettings.visualPlanEnabled;
383
-
384
- if (lc === "") {
385
- // No argument — return current state as a dialog
386
- return {
387
- handled: true,
388
- response: `Visual plan mode is currently ${currentEnabled ? "on" : "off"}.`,
389
- dialog: {
390
- id: generateId(),
391
- title: "Visual Plan",
392
- command: "/visual-plan",
393
- component: "visual-plan",
394
- data: {
395
- enabled: currentEnabled,
396
- previousEnabled: currentEnabled,
397
- defaultTemplate: ctx.currentSettings.defaultTemplate,
398
- lastUsedSlug: ctx.currentSettings.lastUsedSlug ?? null,
399
- mode: "status",
400
- },
401
- },
402
- };
403
- }
404
-
405
- if (lc === "on" || lc === "true" || lc === "1" || lc === "enable") {
406
- return {
407
- handled: true,
408
- response: `Visual plan mode is now on.`,
409
- settingsPatch: { visualPlanEnabled: true },
410
- dialog: {
411
- id: generateId(),
412
- title: "Visual Plan",
413
- command: "/visual-plan on",
414
- component: "visual-plan",
415
- data: {
416
- enabled: true,
417
- previousEnabled: currentEnabled,
418
- mode: "toggle",
419
- },
420
- },
421
- };
422
- }
423
-
424
- if (lc === "off" || lc === "false" || lc === "0" || lc === "disable") {
425
- return {
426
- handled: true,
427
- response: `Visual plan mode is now off.`,
428
- settingsPatch: { visualPlanEnabled: false },
429
- dialog: {
430
- id: generateId(),
431
- title: "Visual Plan",
432
- command: "/visual-plan off",
433
- component: "visual-plan",
434
- data: {
435
- enabled: false,
436
- previousEnabled: currentEnabled,
437
- mode: "toggle",
438
- },
439
- },
440
- };
441
- }
442
-
443
- if (lc === "status" || lc === "state" || lc === "?") {
444
- return {
445
- handled: true,
446
- response: `Visual plan mode is currently ${currentEnabled ? "on" : "off"}.`,
447
- dialog: {
448
- id: generateId(),
449
- title: "Visual Plan",
450
- command: "/visual-plan status",
451
- component: "visual-plan",
452
- data: {
453
- enabled: currentEnabled,
454
- previousEnabled: currentEnabled,
455
- mode: "status",
456
- },
457
- },
458
- };
459
- }
460
-
461
- return {
462
- handled: true,
463
- response: `Unknown argument to /visual-plan: "${arg}". Use "on" or "off".`,
464
- };
465
- }
466
-
467
- function handlePlan(arg: string, ctx: ParseContext): SlashCommandResult {
468
- if (arg === "") {
469
- return helpPlan();
470
- }
471
-
472
- // Split subcommand from its args
473
- const tokens = tokenize(arg);
474
- const sub = (tokens[0] ?? "").toLowerCase();
475
- const subTokens = tokens.slice(1);
476
-
477
- switch (sub) {
478
- case "new":
479
- return handlePlanNew(subTokens, ctx);
480
- case "list":
481
- case "ls":
482
- return handlePlanList(ctx);
483
- case "open":
484
- return handlePlanOpen(subTokens, ctx);
485
- case "get":
486
- return handlePlanGet(subTokens);
487
- case "add":
488
- return handlePlanAdd(subTokens);
489
- case "update":
490
- return handlePlanUpdate(subTokens);
491
- case "delete":
492
- case "del":
493
- case "rm":
494
- return handlePlanDelete(subTokens);
495
- case "comment":
496
- case "comments":
497
- // `/plan comment` is the add-comment verb; `/plan comments` is the
498
- // list-comments verb. Disambiguate by looking at the first token.
499
- if (sub === "comments") return handlePlanComments(subTokens);
500
- return handlePlanComment(subTokens);
501
- case "status":
502
- return handlePlanStatus(subTokens);
503
- case "comments-list":
504
- return handlePlanComments(subTokens);
505
- case "wait":
506
- return handlePlanWait(subTokens);
507
- default:
508
- return {
509
- handled: true,
510
- response: `Unknown /plan subcommand: "${sub}". Try /plan for usage.`,
511
- };
512
- }
513
- }
514
-
515
- function helpPlan(): SlashCommandResult {
516
- return {
517
- handled: true,
518
- response: "Plan commands: /plan new <slug> [template] | /plan list | /plan open <slug> | /plan get <slug> | /plan add <slug> | /plan update <slug> <id> | /plan delete <slug> <id> | /plan comment <slug> [id] \"text\" | /plan comments <slug> [id] | /plan status <slug> <status> | /plan wait <slug> [--timeout N]. Run /plan <subcommand> for details.",
519
- dialog: {
520
- id: generateId(),
521
- title: "Plan Commands",
522
- command: "/plan",
523
- component: "help",
524
- data: {
525
- commands: [
526
- { cmd: "/plan new <slug> [template]", desc: "Create a new plan" },
527
- { cmd: "/plan list", desc: "List all plans in the worktree" },
528
- { cmd: "/plan open <slug>", desc: "Open a plan in the viewer" },
529
- { cmd: "/plan get <slug>", desc: "Fetch the full canvas" },
530
- { cmd: "/plan add <slug> --title T --type kind", desc: "Add an element to a plan" },
531
- { cmd: "/plan update <slug> <id> [--x N --y N …]", desc: "Patch an existing element" },
532
- { cmd: "/plan delete <slug> <id>", desc: "Remove an element" },
533
- { cmd: "/plan comment <slug> [id] \"text\"", desc: "Add a comment" },
534
- { cmd: "/plan comments <slug> [id]", desc: "Read comments on a plan" },
535
- { cmd: "/plan status <slug> <status>", desc: "Set the plan's status" },
536
- { cmd: "/plan wait <slug> [--timeout N]", desc: "Wait for feedback (deferred)" },
537
- ],
538
- templates: KNOWN_TEMPLATES,
539
- statuses: PLAN_STATUSES,
540
- },
541
- },
542
- };
543
- }
544
-
545
- // --- /plan new ------------------------------------------------------------
546
-
547
- function handlePlanNew(args: string[], ctx: ParseContext): SlashCommandResult {
548
- if (args.length === 0 || args[0] === "") {
549
- return {
550
- handled: true,
551
- response: "Usage: /plan new <slug> [template]. Available templates: " + KNOWN_TEMPLATES.join(", ") + ".",
552
- dialog: {
553
- id: generateId(),
554
- title: "Create New Plan",
555
- command: "/plan new",
556
- component: "plan-create",
557
- data: {
558
- templates: [...KNOWN_TEMPLATES],
559
- defaultTemplate: ctx.currentSettings.defaultTemplate,
560
- suggestedSlug: "",
561
- },
562
- },
563
- };
564
- }
565
-
566
- const slug = args[0]!;
567
- if (!isValidSlug(slug)) {
568
- return {
569
- handled: true,
570
- response:
571
- `Invalid slug "${slug}". Slug must be lowercase, may contain hyphens, ` +
572
- `must start with an alphanumeric character, and be 1–64 characters.`,
573
- };
574
- }
575
-
576
- // Optional second arg is the template name
577
- let template: DefaultTemplate | null = null;
578
- if (args.length >= 2 && args[1] !== "") {
579
- const candidate = args[1]!.toLowerCase();
580
- if (!isKnownTemplate(candidate)) {
581
- return {
582
- handled: true,
583
- response:
584
- `Unknown template "${args[1]}". Available: ${KNOWN_TEMPLATES.join(", ")}.`,
585
- };
586
- }
587
- template = candidate;
588
- }
589
-
590
- const resolvedTemplate = template ?? ctx.currentSettings.defaultTemplate;
591
-
592
- return {
593
- handled: true,
594
- response: `Created plan "${titleCase(slug)}" with the "${resolvedTemplate}" template. Use /plan open ${slug} to open it.`,
595
- sideEffect: {
596
- kind: "create_plan",
597
- slug,
598
- template,
599
- },
600
- settingsPatch: { lastUsedSlug: slug },
601
- dialog: {
602
- id: generateId(),
603
- title: "Plan Created",
604
- command: `/plan new ${slug}`,
605
- component: "generic",
606
- data: {
607
- message: `Plan "${titleCase(slug)}" created with the "${resolvedTemplate}" template.`,
608
- detail: `Use /plan open ${slug} to open it.`,
609
- },
610
- },
611
- };
612
- }
613
-
614
- // --- /plan list ----------------------------------------------------------
615
-
616
- function handlePlanList(ctx: ParseContext): SlashCommandResult {
617
- const slugs = ctx.availablePlanSlugs ?? [];
618
- if (slugs.length === 0) {
619
- return {
620
- handled: true,
621
- response: "No plans found in the current worktree. Use /plan new <slug> to create one.",
622
- sideEffect: { kind: "list_plans" },
623
- dialog: {
624
- id: generateId(),
625
- title: "Plans",
626
- command: "/plan list",
627
- component: "plan-list",
628
- data: {
629
- plans: slugs,
630
- count: slugs.length,
631
- },
632
- },
633
- };
634
- }
635
- return {
636
- handled: true,
637
- response: `Found ${slugs.length} plan(s) (${slugs.length}): ${slugs.join(", ")}.`,
638
- sideEffect: { kind: "list_plans" },
639
- dialog: {
640
- id: generateId(),
641
- title: "Plans",
642
- command: "/plan list",
643
- component: "plan-list",
644
- data: {
645
- plans: slugs,
646
- count: slugs.length,
647
- },
648
- },
649
- };
650
- }
651
-
652
- // --- /plan open ----------------------------------------------------------
653
-
654
- function handlePlanOpen(args: string[], ctx: ParseContext): SlashCommandResult {
655
- if (args.length === 0 || args[0] === "") {
656
- return {
657
- handled: true,
658
- response: "Usage: /plan open <slug>",
659
- };
660
- }
661
-
662
- const slug = args[0]!;
663
- if (!isValidSlug(slug)) {
664
- return {
665
- handled: true,
666
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
667
- };
668
- }
669
-
670
- const port = ctx.defaultPort ?? 4321;
671
- const url = `http://localhost:${port}/${slug}/`;
672
-
673
- return {
674
- handled: true,
675
- response: `Opening plan "${slug}" at ${url}.`,
676
- settingsPatch: { lastUsedSlug: slug },
677
- sideEffect: {
678
- kind: "open_plan_url",
679
- slug,
680
- },
681
- dialog: {
682
- id: generateId(),
683
- title: "Opening Plan",
684
- command: `/plan open ${slug}`,
685
- component: "generic",
686
- data: {
687
- message: `Opening plan: ${slug}`,
688
- url,
689
- },
690
- },
691
- };
692
- }
693
-
694
- // --- /plan get -----------------------------------------------------------
695
-
696
- function handlePlanGet(args: string[]): SlashCommandResult {
697
- if (args.length === 0 || args[0] === "") {
698
- return { handled: true, response: "Usage: /plan get <slug>" };
699
- }
700
- const slug = args[0]!;
701
- if (!isValidSlug(slug)) {
702
- return {
703
- handled: true,
704
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
705
- };
706
- }
707
- return {
708
- handled: true,
709
- response: `Fetching canvas for plan "${slug}"…`,
710
- sideEffect: {
711
- kind: "tool_invocation",
712
- toolName: "bizar_plan_action",
713
- args: { action: "get_canvas", planSlug: slug },
714
- },
715
- settingsPatch: { lastUsedSlug: slug },
716
- dialog: {
717
- id: generateId(),
718
- title: "Plan Canvas",
719
- command: `/plan get ${slug}`,
720
- component: "generic",
721
- data: {
722
- message: `Fetching canvas for plan "${slug}"…`,
723
- },
724
- },
725
- };
726
- }
727
-
728
- // --- /plan add -----------------------------------------------------------
729
-
730
- const ELEMENT_FLAG_KEYS = [
731
- "title",
732
- "content",
733
- "type",
734
- "id",
735
- ] as const;
736
- type ElementFlagKey = (typeof ELEMENT_FLAG_KEYS)[number];
737
-
738
- const ELEMENT_NUMERIC_FLAG_KEYS = ["x", "y", "width", "height"] as const;
739
- type ElementNumericFlagKey = (typeof ELEMENT_NUMERIC_FLAG_KEYS)[number];
740
-
741
- function readElementFlags(flags: Record<string, string>): Record<string, unknown> {
742
- const out: Record<string, unknown> = {};
743
- for (const k of ELEMENT_FLAG_KEYS) {
744
- const v = flags[k];
745
- if (v !== undefined && v !== "") out[k] = v;
746
- }
747
- for (const k of ELEMENT_NUMERIC_FLAG_KEYS) {
748
- const v = flags[k];
749
- if (v !== undefined && v !== "") {
750
- const n = Number(v);
751
- if (Number.isFinite(n)) out[k] = n;
752
- }
753
- }
754
- return out;
755
- }
756
-
757
- function handlePlanAdd(args: string[]): SlashCommandResult {
758
- if (args.length === 0 || args[0] === "") {
759
- return {
760
- handled: true,
761
- response: 'Usage: /plan add <slug> --title "Title" --type task [--x N --y N --width N --height N --content "…"]',
762
- };
763
- }
764
- const slug = args[0]!;
765
- if (!isValidSlug(slug)) {
766
- return {
767
- handled: true,
768
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
769
- };
770
- }
771
- const { flags } = parseFlags(args.slice(1));
772
- const element = readElementFlags(flags);
773
- if (Object.keys(element).length === 0) {
774
- return {
775
- handled: true,
776
- response:
777
- `Usage: /plan add ${slug} --title "Title" --type task [--x N --y N --width N --height N --content "…"]\n` +
778
- `At least one of --title / --type / --content / --x / --y is required.`,
779
- };
780
- }
781
- return {
782
- handled: true,
783
- response: "",
784
- sideEffect: {
785
- kind: "tool_invocation",
786
- toolName: "bizar_plan_action",
787
- args: { action: "add_element", planSlug: slug, element },
788
- },
789
- settingsPatch: { lastUsedSlug: slug },
790
- dialog: {
791
- id: generateId(),
792
- title: "Element Added",
793
- command: `/plan add ${slug}`,
794
- component: "generic",
795
- data: {
796
- message: `Adding element to plan "${slug}"…`,
797
- },
798
- },
799
- };
800
- }
801
-
802
- // --- /plan update --------------------------------------------------------
803
-
804
- function handlePlanUpdate(args: string[]): SlashCommandResult {
805
- if (args.length < 2 || args[0] === "" || args[1] === "") {
806
- return {
807
- handled: true,
808
- response:
809
- 'Usage: /plan update <slug> <elementId> [--x N --y N --title T --content C --width N --height N --type kind]',
810
- };
811
- }
812
- const slug = args[0]!;
813
- const elementId = args[1]!;
814
- if (!isValidSlug(slug)) {
815
- return {
816
- handled: true,
817
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
818
- };
819
- }
820
- if (elementId === "") {
821
- return {
822
- handled: true,
823
- response: "Element id cannot be empty.",
824
- };
825
- }
826
- const { flags } = parseFlags(args.slice(2));
827
- const element = readElementFlags(flags);
828
- if (Object.keys(element).length === 0) {
829
- return {
830
- handled: true,
831
- response:
832
- `Usage: /plan update ${slug} ${elementId} [--x N --y N --title T --content C --width N --height N --type kind]\n` +
833
- `At least one update flag is required.`,
834
- };
835
- }
836
- return {
837
- handled: true,
838
- response: "",
839
- sideEffect: {
840
- kind: "tool_invocation",
841
- toolName: "bizar_plan_action",
842
- args: { action: "update_element", planSlug: slug, elementId, element },
843
- },
844
- settingsPatch: { lastUsedSlug: slug },
845
- dialog: {
846
- id: generateId(),
847
- title: "Element Updated",
848
- command: `/plan update ${slug}`,
849
- component: "generic",
850
- data: {
851
- message: `Updating element ${elementId} in plan "${slug}"…`,
852
- },
853
- },
854
- };
855
- }
856
-
857
- // --- /plan delete --------------------------------------------------------
858
-
859
- function handlePlanDelete(args: string[]): SlashCommandResult {
860
- if (args.length < 2 || args[0] === "" || args[1] === "") {
861
- return {
862
- handled: true,
863
- response: "Usage: /plan delete <slug> <elementId>",
864
- };
865
- }
866
- const slug = args[0]!;
867
- const elementId = args[1]!;
868
- if (!isValidSlug(slug)) {
869
- return {
870
- handled: true,
871
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
872
- };
873
- }
874
- return {
875
- handled: true,
876
- response: "",
877
- sideEffect: {
878
- kind: "tool_invocation",
879
- toolName: "bizar_plan_action",
880
- args: { action: "delete_element", planSlug: slug, elementId },
881
- },
882
- settingsPatch: { lastUsedSlug: slug },
883
- dialog: {
884
- id: generateId(),
885
- title: "Element Deleted",
886
- command: `/plan delete ${slug}`,
887
- component: "generic",
888
- data: {
889
- message: `Deleting element ${elementId} from plan "${slug}"…`,
890
- },
891
- },
892
- };
893
- }
894
-
895
- // --- /plan comment (add) -------------------------------------------------
896
-
897
- function handlePlanComment(args: string[]): SlashCommandResult {
898
- // /plan comment <slug> [elementId] "text"
899
- // The text is the LAST positional arg. The slug is first. The middle
900
- // arg (if any) is the elementId. We re-tokenize the raw arg string
901
- // (NOT the tokenized form) so that quoted text is preserved as a
902
- // single token. We've already tokenized at the parent level, so
903
- // we re-tokenize the raw form here by joining back together.
904
- // The parent passes a tokenized array, so we look at the trailing
905
- // token and assume the text is the last non-flag argument. The
906
- // parent already did quote-preserving tokenization, so trailing
907
- // tokens after the slug/elementId are the text.
908
- if (args.length < 2 || args[0] === "") {
909
- return {
910
- handled: true,
911
- response: 'Usage: /plan comment <slug> [elementId] "text"',
912
- };
913
- }
914
- const slug = args[0]!;
915
- if (!isValidSlug(slug)) {
916
- return {
917
- handled: true,
918
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
919
- };
920
- }
921
- // Disambiguate: if the second token is a flag, then there's no
922
- // elementId and the rest is the text.
923
- if (args[1]!.startsWith("--")) {
924
- // No elementId; everything after slug is text.
925
- const text = args.slice(1).join(" ").trim();
926
- if (text === "") {
927
- return { handled: true, response: "Comment text is required." };
928
- }
929
- return commentSideEffect(slug, null, text);
930
- }
931
- // If exactly 2 args, treat second as text (no elementId).
932
- if (args.length === 2) {
933
- return commentSideEffect(slug, null, args[1]!);
934
- }
935
- // 3+ args: slug, elementId, text. The text may itself be quoted
936
- // already (the parent tokenizer preserves quoted strings), so we
937
- // join the rest with spaces.
938
- const elementId = args[1]!;
939
- const text = args.slice(2).join(" ").trim();
940
- if (text === "") {
941
- return { handled: true, response: "Comment text is required." };
942
- }
943
- return commentSideEffect(slug, elementId, text);
944
- }
945
-
946
- function commentSideEffect(
947
- slug: string,
948
- elementId: string | null,
949
- text: string,
950
- ): SlashCommandResult {
951
- return {
952
- handled: true,
953
- response: "",
954
- sideEffect: {
955
- kind: "tool_invocation",
956
- toolName: "bizar_plan_action",
957
- args: {
958
- action: "add_comment",
959
- planSlug: slug,
960
- comment: {
961
- elementId,
962
- author: "user",
963
- text,
964
- },
965
- },
966
- },
967
- settingsPatch: { lastUsedSlug: slug },
968
- dialog: {
969
- id: generateId(),
970
- title: "Comment Added",
971
- command: `/plan comment ${slug}`,
972
- component: "generic",
973
- data: {
974
- message:
975
- elementId === null
976
- ? `Adding canvas-pinned comment to plan "${slug}"…`
977
- : `Adding comment to element ${elementId} on plan "${slug}"…`,
978
- },
979
- },
980
- };
981
- }
982
-
983
- // --- /plan comments (list) -----------------------------------------------
984
-
985
- function handlePlanComments(args: string[]): SlashCommandResult {
986
- if (args.length === 0 || args[0] === "") {
987
- return { handled: true, response: "Usage: /plan comments <slug> [elementId]" };
988
- }
989
- const slug = args[0]!;
990
- if (!isValidSlug(slug)) {
991
- return {
992
- handled: true,
993
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
994
- };
995
- }
996
- const elementId = args.length >= 2 ? args[1]! : undefined;
997
- return {
998
- handled: true,
999
- response: "",
1000
- sideEffect: {
1001
- kind: "tool_invocation",
1002
- toolName: "bizar_get_plan_comments",
1003
- args: elementId === undefined ? { planSlug: slug } : { planSlug: slug, elementId },
1004
- },
1005
- settingsPatch: { lastUsedSlug: slug },
1006
- dialog: {
1007
- id: generateId(),
1008
- title: "Comments",
1009
- command: `/plan comments ${slug}`,
1010
- component: "generic",
1011
- data: {
1012
- message:
1013
- elementId === undefined
1014
- ? `Reading comments on plan "${slug}"…`
1015
- : `Reading comments on element ${elementId} of plan "${slug}"…`,
1016
- },
1017
- },
1018
- };
1019
- }
1020
-
1021
- // --- /plan status --------------------------------------------------------
1022
-
1023
- function handlePlanStatus(args: string[]): SlashCommandResult {
1024
- if (args.length < 2 || args[0] === "" || args[1] === "") {
1025
- return {
1026
- handled: true,
1027
- response: `Usage: /plan status <slug> <status>\nAvailable: ${PLAN_STATUSES.join(", ")}`,
1028
- };
1029
- }
1030
- const slug = args[0]!;
1031
- if (!isValidSlug(slug)) {
1032
- return {
1033
- handled: true,
1034
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
1035
- };
1036
- }
1037
- const status = args[1]!;
1038
- if (!isPlanStatus(status)) {
1039
- return {
1040
- handled: true,
1041
- response: `Invalid status "${status}". Available: ${PLAN_STATUSES.join(", ")}.`,
1042
- };
1043
- }
1044
- return {
1045
- handled: true,
1046
- response: "",
1047
- sideEffect: {
1048
- kind: "tool_invocation",
1049
- toolName: "bizar_plan_action",
1050
- args: { action: "set_status", planSlug: slug, status },
1051
- },
1052
- settingsPatch: { lastUsedSlug: slug },
1053
- dialog: {
1054
- id: generateId(),
1055
- title: "Status Updated",
1056
- command: `/plan status ${slug}`,
1057
- component: "generic",
1058
- data: {
1059
- message: `Setting plan "${slug}" status to "${status}"…`,
1060
- },
1061
- },
1062
- };
1063
- }
1064
-
1065
- // --- /plan wait (deferred) -----------------------------------------------
1066
-
1067
- function handlePlanWait(args: string[]): SlashCommandResult {
1068
- if (args.length === 0 || args[0] === "") {
1069
- return { handled: true, response: "Usage: /plan wait <slug> [--timeout N]" };
1070
- }
1071
- const slug = args[0]!;
1072
- if (!isValidSlug(slug)) {
1073
- return {
1074
- handled: true,
1075
- response: `Invalid slug "${slug}". Slug must match ^[a-z0-9][a-z0-9-]{0,63}$.`,
1076
- };
1077
- }
1078
- // Per the audit: /plan wait is deferred from MVP. We acknowledge the
1079
- // command and point the user at the real tool. The slash command
1080
- // does NOT block — that's the hard-rule requirement.
1081
- return {
1082
- handled: true,
1083
- response:
1084
- `"/plan wait" is deferred from the v0.5.0 MVP.\n` +
1085
- `Until the SSE-based command ships, the agent should call ` +
1086
- `\`bizar_wait_for_feedback\` directly as a tool call. It polls every 2s ` +
1087
- `and returns when the user adds a comment, approves/rejects the plan, ` +
1088
- `or the timeout fires (default 10 min, max 30 min).\n` +
1089
- `\n` +
1090
- `For now, the LLM can call the tool itself — this is the path the ` +
1091
- `visual-plan flow uses. A future release will wire /plan wait to an ` +
1092
- `SSE push from the local viewer so the user does not see a polling ` +
1093
- `loop.`,
1094
- };
1095
- }
1096
-
1097
- // --- /bizar --------------------------------------------------------------
1098
-
1099
- /**
1100
- * v2.5.0 — `/bizar [args]` launches the dashboard or routes a sub-request.
1101
- *
1102
- * Behavior:
1103
- * - `/bizar` (no args) — emits a `launch_dashboard` side-effect. The
1104
- * executor spawns `bizar dash start` as a detached child
1105
- * process, then the host surfaces the URL in the response.
1106
- * - `/bizar <args>` — passes the args to the menu command file. Today
1107
- * the menu routes intent (`/explain`, `/plan`, `/audit`, etc.); the
1108
- * response is the menu's natural-language routing advice.
1109
- *
1110
- * Note: the menu text lives in `config/commands/bizar.md` and is shipped
1111
- * via the CLI package. The plugin only handles the no-arg case for the
1112
- * side-effect; with args we return a short pointer so the user knows
1113
- * where the routing table lives.
1114
- */
1115
- function handleBizar(arg: string, ctx: ParseContext): SlashCommandResult {
1116
- const trimmed = arg.trim();
1117
-
1118
- if (trimmed === "") {
1119
- const port = ctx.defaultPort ?? 4321;
1120
- return {
1121
- handled: true,
1122
- response: "",
1123
- sideEffect: {
1124
- kind: "launch_dashboard",
1125
- defaultPort: port,
1126
- },
1127
- dialog: {
1128
- id: generateId(),
1129
- title: "Dashboard",
1130
- command: "/bizar",
1131
- component: "generic",
1132
- data: {
1133
- message: "Dashboard launching in the background…",
1134
- url: `http://localhost:${port}/`,
1135
- },
1136
- },
1137
- };
1138
- }
1139
-
1140
- // With args, defer to the menu command file shipped with the CLI.
1141
- return {
1142
- handled: true,
1143
- response: "",
1144
- dialog: {
1145
- id: generateId(),
1146
- title: "Bizar Commands",
1147
- command: `/bizar ${trimmed}`,
1148
- component: "help",
1149
- data: {
1150
- commands: [
1151
- { cmd: "/bizar", desc: "Launch the Bizar dashboard" },
1152
- { cmd: "/bizar explain <question>", desc: "Read-only code Q&A" },
1153
- { cmd: "/bizar plan <args>", desc: "Manage plans" },
1154
- { cmd: "/bizar audit", desc: "Run security audit" },
1155
- { cmd: "/bizar learn", desc: "Extract patterns from session" },
1156
- { cmd: "/bizar init", desc: "Initialize .bizar/ in this project" },
1157
- { cmd: "/bizar pr-review", desc: "PR review" },
1158
- ],
1159
- },
1160
- },
1161
- };
1162
- }
1163
-
1164
- // --- /kb -----------------------------------------------------------------
1165
-
1166
- /**
1167
- * v4.0.0 — `/kb` opens the Bizar Memory vault in Obsidian.
1168
- *
1169
- * Behavior:
1170
- * - Routes through the `bizar_open_kb` tool via tool_invocation.
1171
- * - The tool resolves the vault path from .bizar/memory.json and
1172
- * spawns Obsidian (or xdg-open as fallback).
1173
- * - If memory isn't initialized, the tool returns a helpful message.
1174
- *
1175
- * No subcommands. With args, returns a help pointer.
1176
- */
1177
- function handleKb(arg: string, _ctx: ParseContext): SlashCommandResult {
1178
- const trimmed = arg.trim();
1179
-
1180
- if (trimmed !== "") {
1181
- return {
1182
- handled: true,
1183
- response:
1184
- `Unknown argument: \`${trimmed}\`. \`/kb\` takes no arguments.\n\n` +
1185
- `Use \`/kb\` to open the Bizar Memory vault in Obsidian.\n` +
1186
- `Use \`bizar memory status\` to see vault details.`,
1187
- };
1188
- }
1189
-
1190
- return {
1191
- handled: true,
1192
- response: "Opening Bizar Memory vault in Obsidian…",
1193
- sideEffect: {
1194
- kind: "tool_invocation",
1195
- toolName: "bizar_open_kb",
1196
- args: {},
1197
- },
1198
- };
1199
- }
1200
-
1201
- // --- /help ----------------------------------------------------------------
1202
-
1203
- /**
1204
- * Helper: build a SlashCommandResult that invokes a Bizar tool.
1205
- * Most slash commands are thin shims over an existing tool.
1206
- */
1207
- function toolInvocation(toolName: string, args: unknown, response: string): SlashCommandResult {
1208
- return {
1209
- handled: true,
1210
- response,
1211
- sideEffect: { kind: "tool_invocation", toolName, args },
1212
- };
1213
- }
1214
-
1215
- /**
1216
- * Helper: parse `--key=value` and `--key value` flag pairs from an arg
1217
- * string. Returns `{ positional: string[], flags: Record<string,string> }`.
1218
- *
1219
- * NOTE: Named `parseSlashFlags` to avoid colliding with the existing
1220
- * `parseFlags(tokens: string[])` helper used by the legacy `/plan *`
1221
- * handlers.
1222
- */
1223
- function parseSlashFlags(arg: string): { positional: string[]; flags: Record<string, string> } {
1224
- const tokens = arg.split(/\s+/).filter((t) => t.length > 0);
1225
- const positional: string[] = [];
1226
- const flags: Record<string, string> = {};
1227
- for (let i = 0; i < tokens.length; i++) {
1228
- const t = tokens[i]!;
1229
- if (t.startsWith("--")) {
1230
- const eq = t.indexOf("=");
1231
- if (eq !== -1) {
1232
- flags[t.slice(2, eq)] = t.slice(eq + 1);
1233
- continue;
1234
- }
1235
- const key = t.slice(2);
1236
- const next = tokens[i + 1];
1237
- if (next !== undefined && !next.startsWith("--")) {
1238
- flags[key] = next;
1239
- i++;
1240
- } else {
1241
- flags[key] = "";
1242
- }
1243
- } else {
1244
- positional.push(t);
1245
- }
1246
- }
1247
- return { positional, flags };
1248
- }
1249
-
1250
- /**
1251
- * `/odin <task>` — Spawn a Cline agent team orchestrated by Odin.
1252
- *
1253
- * v6.0.0 — Decomposes the task via `odin.decomposeTask`, builds an Odin
1254
- * system prompt with subtasks + roles, and dispatches via
1255
- * `bizar_spawn_team`. The Odin lead coordinates teammates via Cline's
1256
- * TeamSendMessage.
1257
- */
1258
- function handleOdin(arg: string, _ctx: ParseContext): SlashCommandResult {
1259
- const { positional, flags } = parseSlashFlags(arg);
1260
- const task = positional.join(" ").trim();
1261
- if (!task) {
1262
- return {
1263
- handled: true,
1264
- response:
1265
- "Usage: /odin <task>\n\nSpawn a Cline agent team orchestrated by Odin. Odin decomposes the task and dispatches subtasks to teammates.",
1266
- };
1267
- }
1268
- const maxAgents = flags["max"] ? parseInt(flags["max"], 10) : 3;
1269
-
1270
- // Lazy import to avoid a circular load at module init.
1271
- // The odin module is small and side-effect free.
1272
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1273
- const { decomposeTask, buildOdinPrompt } = require("./odin.js") as typeof import("./odin.js");
1274
- const decomposition = decomposeTask(task, Math.max(2, Math.min(6, maxAgents)));
1275
- const mission = buildOdinPrompt(task, decomposition);
1276
-
1277
- return toolInvocation(
1278
- "bizar_spawn_team",
1279
- {
1280
- teamName: "odin-squad",
1281
- mission,
1282
- maxAgents: decomposition.estimatedAgents,
1283
- // Pass the decomposition to the team so individual teammates can
1284
- // pick up their assigned subtasks.
1285
- metadata: {
1286
- odinDecomposition: decomposition,
1287
- originalTask: task,
1288
- },
1289
- },
1290
- `Spawning Odin team for: ${task} (${decomposition.subtasks.length} subtasks, ${decomposition.estimatedAgents} agents)`,
1291
- );
1292
- }
1293
-
1294
- /**
1295
- * `/loop <pattern> <task>` — Start a continuous loop.
1296
- * Patterns: ralph | repl | cron | plan-execute
1297
- */
1298
- function handleLoop(arg: string, _ctx: ParseContext): SlashCommandResult {
1299
- const { positional, flags } = parseSlashFlags(arg);
1300
- const [pattern, ...rest] = positional;
1301
- const task = rest.join(" ").trim();
1302
- if (!pattern) {
1303
- return {
1304
- handled: true,
1305
- response:
1306
- "Usage: /loop <pattern> <task> [options]\n\nPatterns: ralph | repl | cron | plan-execute\nOptions: --interval N (cron) | --max N (default 10)",
1307
- };
1308
- }
1309
- const valid = new Set(["ralph", "repl", "cron", "plan-execute"]);
1310
- if (!valid.has(pattern)) {
1311
- return { handled: true, response: `Unknown loop pattern: ${pattern}. Valid: ${[...valid].join(", ")}` };
1312
- }
1313
- if (!task) return { handled: true, response: `Usage: /loop ${pattern} <task>` };
1314
- return toolInvocation(
1315
- "bizar_loop_start",
1316
- {
1317
- pattern,
1318
- task,
1319
- interval: flags["interval"] ? parseInt(flags["interval"], 10) : undefined,
1320
- maxIterations: flags["max"] ? parseInt(flags["max"], 10) : 10,
1321
- },
1322
- `Starting ${pattern} loop: ${task}`,
1323
- );
1324
- }
1325
-
1326
- /**
1327
- * `/kanban [list|add|move]` — Kanban board operations.
1328
- */
1329
- function handleKanban(arg: string, _ctx: ParseContext): SlashCommandResult {
1330
- const { positional, flags } = parseSlashFlags(arg);
1331
- const sub = positional[0] || "list";
1332
- const args = positional.slice(1);
1333
- if (sub === "list") return toolInvocation("bizar_kanban_list", {}, "Listing kanban board…");
1334
- if (sub === "add") {
1335
- return toolInvocation(
1336
- "bizar_kanban_add",
1337
- { title: args.join(" "), column: flags["column"] || "todo" },
1338
- `Adding kanban card: ${args.join(" ")}`,
1339
- );
1340
- }
1341
- if (sub === "move") {
1342
- return toolInvocation(
1343
- "bizar_kanban_move",
1344
- { cardId: args[0], column: args[1] },
1345
- `Moving card ${args[0]} → ${args[1]}`,
1346
- );
1347
- }
1348
- return { handled: true, response: `Unknown /kanban subcommand: ${sub}. Try list|add|move.` };
1349
- }
1350
-
1351
- /**
1352
- * `/review [scope]` — Run a code review.
1353
- */
1354
- function handleReview(arg: string, _ctx: ParseContext): SlashCommandResult {
1355
- const scope = arg.trim() || ".";
1356
- return toolInvocation("bizar_review", { scope }, `Running review on ${scope}…`);
1357
- }
1358
-
1359
- /**
1360
- * `/handoff <text>` — Compact current session into a handoff document.
1361
- */
1362
- function handleHandoff(arg: string, _ctx: ParseContext): SlashCommandResult {
1363
- const text = arg.trim();
1364
- if (!text) return { handled: true, response: "Usage: /handoff <summary text>" };
1365
- return toolInvocation("bizar_handoff_write", { text }, "Writing handoff document…");
1366
- }
1367
-
1368
- /**
1369
- * `/grill <topic>` — Run a relentless interview on a topic.
1370
- */
1371
- function handleGrill(arg: string, _ctx: ParseContext): SlashCommandResult {
1372
- const topic = arg.trim();
1373
- if (!topic) return { handled: true, response: "Usage: /grill <topic to be grilled on>" };
1374
- return toolInvocation("bizar_grill", { topic, mode: "grill" }, `Grilling on: ${topic}`);
1375
- }
1376
-
1377
- /**
1378
- * `/glyph <type> <slug>` — Create a visual glyph.
1379
- */
1380
- function handleGlyph(arg: string, _ctx: ParseContext): SlashCommandResult {
1381
- const { positional } = parseSlashFlags(arg);
1382
- const [type, ...rest] = positional;
1383
- const slug = rest.join(" ").trim();
1384
- if (!type || !slug) {
1385
- return {
1386
- handled: true,
1387
- response: "Usage: /glyph <type> <slug>\n\nTypes: plan, recap, design, postmortem, handoff",
1388
- };
1389
- }
1390
- return toolInvocation("bizar_glyph_create", { type, slug }, `Creating glyph: ${slug}`);
1391
- }
1392
-
1393
- /**
1394
- * `/decision <title>` — Document an architectural decision (ADR).
1395
- */
1396
- function handleDecision(arg: string, _ctx: ParseContext): SlashCommandResult {
1397
- const title = arg.trim();
1398
- if (!title) return { handled: true, response: "Usage: /decision <ADR title>" };
1399
- return toolInvocation("bizar_decision_write", { title }, `Documenting decision: ${title}`);
1400
- }
1401
-
1402
- /**
1403
- * `/issue <title>` — File an issue.
1404
- */
1405
- function handleIssue(arg: string, _ctx: ParseContext): SlashCommandResult {
1406
- const title = arg.trim();
1407
- if (!title) return { handled: true, response: "Usage: /issue <issue title>" };
1408
- return toolInvocation("bizar_issue_create", { title }, `Filing issue: ${title}`);
1409
- }
1410
-
1411
- /**
1412
- * `/digest [period]` — Show or generate a digest.
1413
- */
1414
- function handleDigest(arg: string, _ctx: ParseContext): SlashCommandResult {
1415
- const period = arg.trim() || "weekly";
1416
- return toolInvocation("bizar_digest", { period, action: "show" }, `Showing ${period} digest…`);
1417
- }
1418
-
1419
- /**
1420
- * `/usage [period]` — Show usage analytics.
1421
- */
1422
- function handleUsage(arg: string, _ctx: ParseContext): SlashCommandResult {
1423
- const period = arg.trim() || "24h";
1424
- return toolInvocation("bizar_usage", { period }, `Showing ${period} usage…`);
1425
- }
1426
-
1427
- /**
1428
- * `/memory <subcommand>` — Memory vault operations.
1429
- */
1430
- function handleMemory(arg: string, _ctx: ParseContext): SlashCommandResult {
1431
- const { positional } = parseSlashFlags(arg);
1432
- const sub = positional[0] || "status";
1433
- const rest = positional.slice(1);
1434
- switch (sub) {
1435
- case "status": return toolInvocation("bizar_memory_status", {}, "Memory status…");
1436
- case "search": return toolInvocation(
1437
- "bizar_memory_search", { query: rest.join(" "), mode: "semantic" },
1438
- `Searching memory: ${rest.join(" ")}`,
1439
- );
1440
- case "read": return toolInvocation(
1441
- "bizar_memory_read", { path: rest.join(" ") },
1442
- `Reading memory note ${rest.join(" ")}`,
1443
- );
1444
- case "write": return toolInvocation(
1445
- "bizar_memory_write", { path: rest.join(" ") },
1446
- `Writing memory note ${rest.join(" ")}`,
1447
- );
1448
- case "list": return toolInvocation("bizar_memory_list", {}, "Listing memory notes…");
1449
- case "link": return toolInvocation(
1450
- "bizar_memory_link", { url: rest[0] },
1451
- `Linking memory vault to ${rest[0]}`,
1452
- );
1453
- }
1454
- return {
1455
- handled: true,
1456
- response: `Unknown /memory subcommand: ${sub}. Try: status|search|read|write|list|link`,
1457
- };
1458
- }
1459
-
1460
- /**
1461
- * `/spawn <agent> <prompt>` — Spawn a background agent.
1462
- */
1463
- function handleSpawn(arg: string, _ctx: ParseContext): SlashCommandResult {
1464
- const { positional, flags } = parseSlashFlags(arg);
1465
- const agent = positional[0];
1466
- const prompt = positional.slice(1).join(" ");
1467
- if (!agent || !prompt) {
1468
- return {
1469
- handled: true,
1470
- response: "Usage: /spawn <agent> <prompt>\n\nAgents: odin, heimdall, thor, tyr, vidarr, forseti, frigg, mimir, hermod",
1471
- };
1472
- }
1473
- return toolInvocation(
1474
- "bizar_spawn_background",
1475
- {
1476
- agent,
1477
- prompt,
1478
- timeoutMs: flags["timeout"] ? parseInt(flags["timeout"], 10) : 300000,
1479
- },
1480
- `Spawning ${agent}: ${prompt.slice(0, 80)}…`,
1481
- );
1482
- }
1483
-
1484
- /**
1485
- * `/team [status|stop|list]` — Team operations.
1486
- */
1487
- function handleTeam(arg: string, _ctx: ParseContext): SlashCommandResult {
1488
- const { positional } = parseSlashFlags(arg);
1489
- const sub = positional[0] || "status";
1490
- switch (sub) {
1491
- case "status":
1492
- return toolInvocation("bizar_team_status", { sessionId: positional[1] }, "Team status…");
1493
- case "stop":
1494
- return toolInvocation(
1495
- "bizar_team_stop", { sessionId: positional[1] },
1496
- `Stopping team ${positional[1] || "(active)"}`,
1497
- );
1498
- case "list":
1499
- return toolInvocation("bizar_team_list", {}, "Listing teams…");
1500
- }
1501
- return { handled: true, response: `Unknown /team subcommand: ${sub}. Try: status|stop|list` };
1502
- }
1503
-
1504
- /**
1505
- * `/audit` — Run security audit.
1506
- */
1507
- function handleAudit(_arg: string, _ctx: ParseContext): SlashCommandResult {
1508
- return toolInvocation("bizar_audit", {}, "Running security audit…");
1509
- }
1510
-
1511
- /**
1512
- * `/deploy <target>` — One-click deploy.
1513
- */
1514
- function handleDeploy(arg: string, _ctx: ParseContext): SlashCommandResult {
1515
- const target = arg.trim() || "vercel";
1516
- return toolInvocation("bizar_deploy", { target }, `Deploying to ${target}…`);
1517
- }
1518
-
1519
- /**
1520
- * `/status` — Show current session status.
1521
- */
1522
- function handleStatus(_arg: string, _ctx: ParseContext): SlashCommandResult {
1523
- return toolInvocation("bizar_status", {}, "Session status…");
1524
- }
1525
-
1526
- /**
1527
- * `/lightrag <subcommand>` — LightRAG server management.
1528
- */
1529
- function handleLightrag(arg: string, _ctx: ParseContext): SlashCommandResult {
1530
- const { positional } = parseSlashFlags(arg);
1531
- const sub = positional[0] || "status";
1532
- return toolInvocation(
1533
- "bizar_lightrag", { subcommand: sub, args: positional.slice(1) },
1534
- `LightRAG ${sub}…`,
1535
- );
1536
- }
1537
-
1538
- /**
1539
- * `/headroom <subcommand>` — Headroom context compression.
1540
- */
1541
- function handleHeadroom(arg: string, _ctx: ParseContext): SlashCommandResult {
1542
- const { positional } = parseSlashFlags(arg);
1543
- const sub = positional[0] || "status";
1544
- return toolInvocation(
1545
- "bizar_headroom", { subcommand: sub, args: positional.slice(1) },
1546
- `Headroom ${sub}…`,
1547
- );
1548
- }
1549
-
1550
- /**
1551
- * `/minimax <subcommand>` — MiniMax Token Plan integration.
1552
- */
1553
- function handleMiniMax(arg: string, _ctx: ParseContext): SlashCommandResult {
1554
- const { positional } = parseSlashFlags(arg);
1555
- const sub = positional[0] || "status";
1556
- return toolInvocation(
1557
- "bizar_minimax", { subcommand: sub, args: positional.slice(1) },
1558
- `MiniMax ${sub}…`,
1559
- );
1560
- }
1561
-
1562
- /**
1563
- * `/service <subcommand>` — Background service management.
1564
- */
1565
- function handleService(arg: string, _ctx: ParseContext): SlashCommandResult {
1566
- const { positional } = parseSlashFlags(arg);
1567
- const sub = positional[0] || "status";
1568
- return toolInvocation(
1569
- "bizar_service", { subcommand: sub, args: positional.slice(1) },
1570
- `Service ${sub}…`,
1571
- );
1572
- }
1573
-
1574
- /**
1575
- * `/dash <subcommand>` — Dashboard control.
1576
- */
1577
- function handleDash(arg: string, _ctx: ParseContext): SlashCommandResult {
1578
- const { positional } = parseSlashFlags(arg);
1579
- const sub = positional[0] || "status";
1580
- return toolInvocation(
1581
- "bizar_dash", { subcommand: sub, args: positional.slice(1) },
1582
- `Dashboard ${sub}…`,
1583
- );
1584
- }
1585
-
1586
- /**
1587
- * `/mod <subcommand>` — Mod management.
1588
- */
1589
- function handleMod(arg: string, _ctx: ParseContext): SlashCommandResult {
1590
- const { positional } = parseSlashFlags(arg);
1591
- const sub = positional[0] || "list";
1592
- return toolInvocation(
1593
- "bizar_mod", { subcommand: sub, args: positional.slice(1) },
1594
- `Mod ${sub}…`,
1595
- );
1596
- }
1597
-
1598
- /**
1599
- * `/init` — Initialize Bizar in the current project.
1600
- */
1601
- function handleInit(_arg: string, _ctx: ParseContext): SlashCommandResult {
1602
- return toolInvocation(
1603
- "bizar_init", { projectRoot: process.cwd() },
1604
- "Initializing Bizar in project…",
1605
- );
1606
- }
1607
-
1608
- /**
1609
- * `/dev <subcommand>` — Developer commands.
1610
- */
1611
- function handleDev(arg: string, _ctx: ParseContext): SlashCommandResult {
1612
- const { positional } = parseSlashFlags(arg);
1613
- const sub = positional[0] || "status";
1614
- return toolInvocation(
1615
- "bizar_dev", { subcommand: sub, args: positional.slice(1) },
1616
- `Dev ${sub}…`,
1617
- );
1618
- }
1619
-
1620
- /**
1621
- * `/test` — Run the project's test suite.
1622
- */
1623
- function handleTest(_arg: string, _ctx: ParseContext): SlashCommandResult {
1624
- return toolInvocation("bizar_test_gate", {}, "Running tests…");
1625
- }
1626
-
1627
- /**
1628
- * `/agent-browser <subcommand>` — Browser automation.
1629
- */
1630
- function handleAgentBrowser(arg: string, _ctx: ParseContext): SlashCommandResult {
1631
- const { positional } = parseSlashFlags(arg);
1632
- const sub = positional[0] || "status";
1633
- return toolInvocation(
1634
- "bizar_agent_browser", { subcommand: sub, args: positional.slice(1) },
1635
- `Agent-browser ${sub}…`,
1636
- );
1637
- }
1638
-
1639
- /**
1640
- * `/plow-through <task>` — Run the plow-through sprint workflow.
1641
- */
1642
- function handlePlowThrough(arg: string, _ctx: ParseContext): SlashCommandResult {
1643
- const task = arg.trim();
1644
- if (!task) return { handled: true, response: "Usage: /plow-through <task>" };
1645
- return toolInvocation("bizar_plow_through", { task }, `Plowing through: ${task}`);
1646
- }
1647
-
1648
- /**
1649
- * `/tailscale <subcommand>` — Tailscale integration.
1650
- */
1651
- function handleTailscale(arg: string, _ctx: ParseContext): SlashCommandResult {
1652
- const { positional } = parseSlashFlags(arg);
1653
- const sub = positional[0] || "status";
1654
- return toolInvocation(
1655
- "bizar_tailscale", { subcommand: sub, args: positional.slice(1) },
1656
- `Tailscale ${sub}…`,
1657
- );
1658
- }
1659
-
1660
- /**
1661
- * `/providers <subcommand>` — Provider detection.
1662
- */
1663
- function handleProviders(arg: string, _ctx: ParseContext): SlashCommandResult {
1664
- const { positional } = parseSlashFlags(arg);
1665
- const sub = positional[0] || "detect";
1666
- return toolInvocation(
1667
- "bizar_providers", { subcommand: sub, args: positional.slice(1) },
1668
- `Providers ${sub}…`,
1669
- );
1670
- }
1671
-
1672
- /**
1673
- * `/clip <subcommand>` — Web clipper.
1674
- */
1675
- function handleClip(arg: string, _ctx: ParseContext): SlashCommandResult {
1676
- const { positional } = parseSlashFlags(arg);
1677
- const sub = positional[0] || "list";
1678
- return toolInvocation(
1679
- "bizar_clip", { subcommand: sub, args: positional.slice(1) },
1680
- `Clip ${sub}…`,
1681
- );
1682
- }
1683
-
1684
- /**
1685
- * `/ocr <subcommand>` — OCR operations.
1686
- */
1687
- function handleOcr(arg: string, _ctx: ParseContext): SlashCommandResult {
1688
- const { positional } = parseSlashFlags(arg);
1689
- const sub = positional[0] || "list";
1690
- return toolInvocation(
1691
- "bizar_ocr", { subcommand: sub, args: positional.slice(1) },
1692
- `OCR ${sub}…`,
1693
- );
1694
- }
1695
-
1696
- /**
1697
- * `/workspace <subcommand>` — Workspace management.
1698
- */
1699
- function handleWorkspace(arg: string, _ctx: ParseContext): SlashCommandResult {
1700
- const { positional } = parseSlashFlags(arg);
1701
- const sub = positional[0] || "status";
1702
- return toolInvocation(
1703
- "bizar_workspace", { subcommand: sub, args: positional.slice(1) },
1704
- `Workspace ${sub}…`,
1705
- );
1706
- }
1707
-
1708
- /**
1709
- * `/voice <subcommand>` — Voice notes.
1710
- */
1711
- function handleVoice(arg: string, _ctx: ParseContext): SlashCommandResult {
1712
- const { positional } = parseSlashFlags(arg);
1713
- const sub = positional[0] || "status";
1714
- return toolInvocation(
1715
- "bizar_voice", { subcommand: sub, args: positional.slice(1) },
1716
- `Voice ${sub}…`,
1717
- );
1718
- }
1719
-
1720
- function helpResult(): SlashCommandResult {
1721
- return {
1722
- handled: true,
1723
- response:
1724
- "Available commands — Bizar commands: /odin <task>, /loop <pattern> <task>, /kanban [list|add|move], " +
1725
- "/review [scope], /handoff <text>, /grill <topic>, /glyph <type> <slug>, " +
1726
- "/decision <title>, /issue <title>, /digest [period], /usage [period], " +
1727
- "/memory <sub>, /spawn <agent> <prompt>, /team <sub>, /audit, /deploy [target], " +
1728
- "/status, /lightrag <sub>, /headroom <sub>, /minimax <sub>, /service <sub>, " +
1729
- "/dash <sub>, /mod <sub>, /init, /dev <sub>, /test, /agent-browser <sub>, " +
1730
- "/plow-through <task>, /tailscale <sub>, /providers <sub>, /clip <sub>, " +
1731
- "/ocr <sub>, /workspace <sub>, /voice <sub>, " +
1732
- "/visual-plan [on|off|status], /plan new <slug> [template], /plan list, /plan open <slug>, " +
1733
- "/plan get <slug>, /plan add <slug>, /plan update <slug> <id>, /plan delete <slug> <id>, " +
1734
- "/plan comment <slug> [id] \"text\", /plan comments <slug> [id], /plan status <slug> <status>, " +
1735
- "/plan wait <slug> [--timeout N], /bizar <args>, /kb, /help.",
1736
- dialog: {
1737
- id: generateId(),
1738
- title: "Bizar Commands",
1739
- command: "/help",
1740
- component: "help",
1741
- data: {
1742
- commands: [
1743
- { cmd: "/odin <task>", desc: "Spawn a Cline agent team orchestrated by Odin" },
1744
- { cmd: "/loop <pattern> <task>", desc: "Start a loop (ralph/repl/cron/plan-execute)" },
1745
- { cmd: "/kanban [list|add|move]", desc: "Kanban board operations" },
1746
- { cmd: "/review [scope]", desc: "Run a code review" },
1747
- { cmd: "/handoff <text>", desc: "Compact current session into a handoff document" },
1748
- { cmd: "/grill <topic>", desc: "Run a relentless interview" },
1749
- { cmd: "/glyph <type> <slug>", desc: "Create a visual glyph (plan/recap/design)" },
1750
- { cmd: "/decision <title>", desc: "Document an architectural decision (ADR)" },
1751
- { cmd: "/issue <title>", desc: "File an issue" },
1752
- { cmd: "/digest [period]", desc: "Show or generate a digest" },
1753
- { cmd: "/usage [period]", desc: "Show usage analytics" },
1754
- { cmd: "/memory <subcommand>", desc: "Memory vault operations (status/search/read/write/list/link)" },
1755
- { cmd: "/spawn <agent> <prompt>", desc: "Spawn a background agent" },
1756
- { cmd: "/team [status|stop|list]", desc: "Team operations" },
1757
- { cmd: "/audit", desc: "Run security audit" },
1758
- { cmd: "/deploy <target>", desc: "One-click deploy (vercel/cloudflare/fly/docker)" },
1759
- { cmd: "/status", desc: "Show current session status" },
1760
- { cmd: "/lightrag <subcommand>", desc: "LightRAG server management" },
1761
- { cmd: "/headroom <subcommand>", desc: "Headroom context compression" },
1762
- { cmd: "/minimax <subcommand>", desc: "MiniMax Token Plan integration" },
1763
- { cmd: "/service <subcommand>", desc: "Background service management" },
1764
- { cmd: "/dash <subcommand>", desc: "Dashboard control" },
1765
- { cmd: "/mod <subcommand>", desc: "Mod management" },
1766
- { cmd: "/init", desc: "Initialize Bizar in current project" },
1767
- { cmd: "/dev <subcommand>", desc: "Developer commands (link/unlink/build)" },
1768
- { cmd: "/test", desc: "Run the project's test suite" },
1769
- { cmd: "/agent-browser <subcommand>", desc: "Browser automation" },
1770
- { cmd: "/plow-through <task>", desc: "Run the plow-through sprint workflow" },
1771
- { cmd: "/tailscale <subcommand>", desc: "Tailscale integration" },
1772
- { cmd: "/providers <subcommand>", desc: "Provider detection" },
1773
- { cmd: "/clip <subcommand>", desc: "Web clipper" },
1774
- { cmd: "/ocr <subcommand>", desc: "OCR operations" },
1775
- { cmd: "/workspace <subcommand>", desc: "Workspace management" },
1776
- { cmd: "/voice <subcommand>", desc: "Voice notes" },
1777
- { cmd: "/visual-plan [on|off|status]", desc: "Toggle or view visual plan mode" },
1778
- { cmd: "/plan new <slug> [template]", desc: "Create a new plan" },
1779
- { cmd: "/plan list", desc: "List all plans in the worktree" },
1780
- { cmd: "/plan open <slug>", desc: "Open a plan in the viewer" },
1781
- { cmd: "/plan get <slug>", desc: "Fetch the full canvas" },
1782
- { cmd: "/plan add <slug> --title T --type kind", desc: "Add an element to a plan" },
1783
- { cmd: "/plan update <slug> <id> [flags]", desc: "Patch an existing element" },
1784
- { cmd: "/plan delete <slug> <id>", desc: "Remove an element" },
1785
- { cmd: "/plan comment <slug> [id] \"text\"", desc: "Add a comment" },
1786
- { cmd: "/plan comments <slug> [id]", desc: "Read comments on a plan" },
1787
- { cmd: "/plan status <slug> <status>", desc: "Set the plan's status" },
1788
- { cmd: "/plan wait <slug> [--timeout N]", desc: "Wait for feedback (deferred)" },
1789
- { cmd: "/bizar", desc: "Launch the Bizar dashboard" },
1790
- { cmd: "/bizar <args>", desc: "Route a request via the menu" },
1791
- { cmd: "/kb", desc: "Open the Bizar Memory vault in Obsidian" },
1792
- { cmd: "/help | /commands", desc: "Show this help" },
1793
- ],
1794
- templates: [...KNOWN_TEMPLATES],
1795
- statuses: [...PLAN_STATUSES],
1796
- },
1797
- },
1798
- };
1799
- }