@polderlabs/bizar 6.3.0 → 7.0.1

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 (264) hide show
  1. package/.claude/hooks/sessionend-recall.mjs +3 -3
  2. package/.claude/hooks/worker-suggest.mjs +110 -0
  3. package/.claude/settings.json +9 -0
  4. package/README.md +125 -47
  5. package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-weVnLz3Z.js} +3 -3
  6. package/bizar-dash/dist/assets/EnvVarsSection-weVnLz3Z.js.map +1 -0
  7. package/bizar-dash/dist/assets/MobileChat-BRFjer8p.js +1 -0
  8. package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-BRFjer8p.js.map} +1 -1
  9. package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js +1 -0
  10. package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js.map +1 -0
  11. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
  12. package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
  13. package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
  14. package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
  15. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
  16. package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
  17. package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
  18. package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
  19. package/bizar-dash/dist/assets/main-BE4Uh9im.css +1 -0
  20. package/bizar-dash/dist/assets/main-DqC58FEZ.js +18 -0
  21. package/bizar-dash/dist/assets/main-DqC58FEZ.js.map +1 -0
  22. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
  23. package/bizar-dash/dist/assets/markdown-w2bDDkAH.js.map +1 -0
  24. package/bizar-dash/dist/assets/mobile-DRZsK3ic.js +1 -0
  25. package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DRZsK3ic.js.map} +1 -1
  26. package/bizar-dash/dist/assets/mobile-layout-BWLtwtfQ.css +1 -0
  27. package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js +2 -0
  28. package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js.map +1 -0
  29. package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
  30. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
  31. package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
  32. package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js +2 -0
  33. package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js.map +1 -0
  34. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
  35. package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
  36. package/bizar-dash/dist/index.html +13 -11
  37. package/bizar-dash/dist/mobile.html +8 -5
  38. package/bizar-dash/src/server/api.mjs +11 -0
  39. package/bizar-dash/src/server/memory-consolidator.mjs +225 -0
  40. package/bizar-dash/src/server/memory-distillation.mjs +296 -0
  41. package/bizar-dash/src/server/routes/distill.mjs +129 -0
  42. package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
  43. package/bizar-dash/src/web/App.tsx +4 -0
  44. package/bizar-dash/src/web/components/Tag.tsx +18 -5
  45. package/bizar-dash/src/web/components/Topbar.tsx +3 -0
  46. package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
  47. package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
  48. package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
  49. package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
  50. package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
  51. package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
  52. package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
  53. package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
  54. package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
  55. package/bizar-dash/src/web/lib/types.ts +25 -0
  56. package/bizar-dash/src/web/mobile/MobileApp.tsx +33 -2
  57. package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +2 -2
  58. package/bizar-dash/src/web/mobile/MobileEval.tsx +4 -4
  59. package/bizar-dash/src/web/mobile/MobileMemory.tsx +2 -2
  60. package/bizar-dash/src/web/mobile/MobileSettings.tsx +3 -3
  61. package/bizar-dash/src/web/mobile/MobileTasks.tsx +2 -2
  62. package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +3 -3
  63. package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
  64. package/bizar-dash/src/web/styles/main.css +251 -0
  65. package/bizar-dash/src/web/styles/mobile.css +41 -0
  66. package/bizar-dash/src/web/styles/tasks-redesign.css +253 -0
  67. package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
  68. package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
  69. package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
  70. package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
  71. package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
  72. package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
  73. package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
  74. package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
  75. package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
  76. package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
  77. package/bizar-dash/src/web/ui/controls/Textarea.tsx +56 -0
  78. package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
  79. package/bizar-dash/src/web/ui/controls/controls.css +476 -0
  80. package/bizar-dash/src/web/ui/controls/index.ts +35 -0
  81. package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
  82. package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
  83. package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
  84. package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
  85. package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
  86. package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
  87. package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
  88. package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
  89. package/bizar-dash/src/web/ui/data/data.css +535 -0
  90. package/bizar-dash/src/web/ui/data/index.ts +31 -0
  91. package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
  92. package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
  93. package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
  94. package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
  95. package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
  96. package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
  97. package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
  98. package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
  99. package/bizar-dash/src/web/ui/index.ts +56 -0
  100. package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
  101. package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
  102. package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
  103. package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
  104. package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
  105. package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
  106. package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
  107. package/bizar-dash/src/web/ui/layout/ViewHeader.tsx +48 -0
  108. package/bizar-dash/src/web/ui/layout/index.ts +34 -0
  109. package/bizar-dash/src/web/ui/layout/layout.css +464 -0
  110. package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
  111. package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
  112. package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
  113. package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
  114. package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
  115. package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
  116. package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
  117. package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
  118. package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
  119. package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
  120. package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
  121. package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
  122. package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
  123. package/bizar-dash/src/web/ui/styles/globals.css +86 -0
  124. package/bizar-dash/src/web/ui/styles/reset.css +125 -0
  125. package/bizar-dash/src/web/ui/styles/tokens.css +208 -0
  126. package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
  127. package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
  128. package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
  129. package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
  130. package/bizar-dash/src/web/views/Agents.tsx +516 -268
  131. package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
  132. package/bizar-dash/src/web/views/Overview.tsx +985 -448
  133. package/bizar-dash/src/web/views/Tasks.tsx +271 -273
  134. package/bizar-dash/tests/communication-log.test.tsx +67 -0
  135. package/bizar-dash/tests/dep-graph.test.tsx +40 -0
  136. package/bizar-dash/tests/event-log.test.tsx +61 -0
  137. package/bizar-dash/tests/goal-input.test.tsx +72 -0
  138. package/bizar-dash/tests/goap-planner.test.ts +68 -0
  139. package/bizar-dash/tests/quality-gates.test.tsx +59 -0
  140. package/bizar-dash/tests/setup.ts +31 -1
  141. package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
  142. package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
  143. package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
  144. package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
  145. package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
  146. package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
  147. package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
  148. package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
  149. package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
  150. package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
  151. package/bizar-dash/tests/ui/controls/Textarea.test.tsx +56 -0
  152. package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
  153. package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
  154. package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
  155. package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
  156. package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
  157. package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
  158. package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
  159. package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
  160. package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
  161. package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
  162. package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
  163. package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
  164. package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
  165. package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
  166. package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
  167. package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
  168. package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
  169. package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
  170. package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
  171. package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
  172. package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
  173. package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
  174. package/bizar-dash/tests/ui/layout/ViewHeader.test.tsx +47 -0
  175. package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
  176. package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
  177. package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
  178. package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
  179. package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
  180. package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
  181. package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
  182. package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
  183. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
  184. package/bizar-dash/tests/views/Agents.test.tsx +359 -0
  185. package/bizar-dash/tests/views/Overview.test.tsx +413 -0
  186. package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
  187. package/bizar-dash/vitest.config.ts +3 -1
  188. package/cli/__tests__/cost-gate.test.mjs +301 -0
  189. package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
  190. package/cli/bin.mjs +38 -0
  191. package/cli/commands/claim.mjs +282 -0
  192. package/cli/commands/cost.mjs +279 -0
  193. package/cli/cost-gate.mjs +501 -0
  194. package/cli/feature-list-bridge.mjs +400 -0
  195. package/cli/memory-constants.mjs +17 -0
  196. package/cli/provision-claude.mjs +94 -0
  197. package/cli/worker-dispatcher.mjs +259 -0
  198. package/cli/worker-dispatcher.test.mjs +204 -0
  199. package/config/trigger-patterns.json +186 -0
  200. package/package.json +2 -2
  201. package/packages/sdk/package.json +1 -1
  202. package/packages/sdk/src/agent-registry.ts +304 -0
  203. package/packages/sdk/src/consensus/byzantine.ts +587 -0
  204. package/packages/sdk/src/consensus/index.ts +147 -0
  205. package/packages/sdk/src/consensus/queen.ts +173 -0
  206. package/packages/sdk/src/consensus/types.ts +199 -0
  207. package/packages/sdk/src/federation/audit.ts +156 -0
  208. package/packages/sdk/src/federation/budget.ts +269 -0
  209. package/packages/sdk/src/federation/envelope.ts +144 -0
  210. package/packages/sdk/src/federation/hmac.ts +160 -0
  211. package/packages/sdk/src/federation/index.ts +443 -0
  212. package/packages/sdk/src/federation/pii.ts +241 -0
  213. package/packages/sdk/src/federation/policy.ts +112 -0
  214. package/packages/sdk/src/federation/trust.ts +146 -0
  215. package/packages/sdk/src/index.ts +67 -0
  216. package/packages/sdk/src/mcp/server.ts +351 -1
  217. package/packages/sdk/src/router/codemod-intent.ts +125 -0
  218. package/packages/sdk/src/router/index.ts +247 -0
  219. package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
  220. package/packages/sdk/src/router/memory-distillation.ts +249 -0
  221. package/packages/sdk/src/router/model-router.ts +235 -0
  222. package/packages/sdk/src/router/q-learning-router.ts +304 -0
  223. package/packages/sdk/src/swarm-topology.ts +279 -0
  224. package/packages/sdk/tests/agent-registry.test.ts +285 -0
  225. package/packages/sdk/tests/consensus.test.ts +376 -0
  226. package/packages/sdk/tests/federation/audit.test.ts +164 -0
  227. package/packages/sdk/tests/federation/budget.test.ts +189 -0
  228. package/packages/sdk/tests/federation/envelope.test.ts +120 -0
  229. package/packages/sdk/tests/federation/hmac.test.ts +194 -0
  230. package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
  231. package/packages/sdk/tests/federation/pii.test.ts +106 -0
  232. package/packages/sdk/tests/federation/policy.test.ts +140 -0
  233. package/packages/sdk/tests/federation/trust.test.ts +124 -0
  234. package/packages/sdk/tests/mcp-tools.test.ts +204 -0
  235. package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
  236. package/packages/sdk/tests/model-router.test.mjs +108 -0
  237. package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
  238. package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
  239. package/packages/sdk/tests/router.test.mjs +91 -0
  240. package/packages/sdk/tests/sdk.test.mjs +26 -2
  241. package/packages/sdk/tests/swarm-topology.test.ts +258 -0
  242. package/scripts/bh-full-e2e.mjs +11 -15
  243. package/bizar-dash/dist/assets/EnvVarsSection-B58aiJiE.js.map +0 -1
  244. package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
  245. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
  246. package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js.map +0 -1
  247. package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
  248. package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
  249. package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
  250. package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
  251. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
  252. package/bizar-dash/dist/assets/markdown-tOLaD6nm.js.map +0 -1
  253. package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
  254. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
  255. package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
  256. package/bizar-dash/dist/assets/mobile-layout-CJnZNLy3.css +0 -1
  257. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
  258. package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
  259. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
  260. package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
  261. package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
  262. package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
  263. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  264. package/packages/sdk/package-lock.json +0 -32
package/cli/bin.mjs CHANGED
@@ -137,6 +137,8 @@ function showHelp() {
137
137
  team Spawn an agent team from CLI (wraps claude --team-name)
138
138
  subagent Spawn a read-only research subagent from CLI
139
139
  rca Analyze a GitHub issue (Claude Code CLI sample)
140
+ cost <subcommand> Atomic cost gate (SQLite-backed room budget tracker)
141
+ claim <subcommand> GitHub-style claim protocol over feature_list.json
140
142
 
141
143
  Examples:
142
144
  bizar install
@@ -597,6 +599,42 @@ async function main() {
597
599
  break;
598
600
  }
599
601
 
602
+ case 'cost': {
603
+ // F-035 MetaHarness — atomic SQLite-backed cost gate.
604
+ // Subcommands: register | status | reserve | commit | release | sweep | list
605
+ const mod = await importCommand('cost');
606
+ if (!mod) {
607
+ console.error(chalk.red(` ✗ Could not load cost command module`));
608
+ process.exit(EXIT_ERROR);
609
+ return;
610
+ }
611
+ dbg('loaded command module:', 'cost');
612
+ const found = await mod.run(cmd, cmdArgs, isHelpRequest);
613
+ if (found === false) {
614
+ console.error(chalk.red(` ✗ Usage: bizar cost <subcommand> — run 'bizar cost --help'`));
615
+ process.exit(EXIT_USAGE);
616
+ }
617
+ break;
618
+ }
619
+
620
+ case 'claim': {
621
+ // F-035 MetaHarness — GitHub-style claim protocol over feature_list.json.
622
+ // Subcommands: <featureId> | release | handoff | steal | status | list | transition
623
+ const mod = await importCommand('claim');
624
+ if (!mod) {
625
+ console.error(chalk.red(` ✗ Could not load claim command module`));
626
+ process.exit(EXIT_ERROR);
627
+ return;
628
+ }
629
+ dbg('loaded command module:', 'claim');
630
+ const found = await mod.run(cmd, cmdArgs, isHelpRequest);
631
+ if (found === false) {
632
+ console.error(chalk.red(` ✗ Usage: bizar claim <subcommand> — run 'bizar claim --help'`));
633
+ process.exit(EXIT_USAGE);
634
+ }
635
+ break;
636
+ }
637
+
600
638
  default: {
601
639
  console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
602
640
  showHelp();
@@ -0,0 +1,282 @@
1
+ /**
2
+ * cli/commands/claim.mjs
3
+ *
4
+ * F-035 (MetaHarness) — `bizar claim` subcommand surface.
5
+ *
6
+ * Wraps cli/feature-list-bridge.mjs (over feature_list.json).
7
+ *
8
+ * Subcommands:
9
+ * bizar claim <featureId> # claim with --who (or $USER)
10
+ * --who <id> (default: $USER)
11
+ * --type <human|agent> (default: human)
12
+ * --reason <text> Free-form claim reason
13
+ * bizar claim release <featureId> # release
14
+ * --who <id> Optional claimant check
15
+ * --reason <text>
16
+ * bizar claim handoff <featureId> --to <id> # handoff
17
+ * --from <id>
18
+ * --reason <text>
19
+ * bizar claim steal <featureId> --by <id> # steal
20
+ * --type <human|agent>
21
+ * --reason <steal-reason> Must be one of STEAL_REASONS
22
+ * bizar claim status <featureId> # Show current claim + history
23
+ * bizar claim list # list all features with status
24
+ * --status <s> One of CLAIM_STATUSES (repeatable)
25
+ * --by-claimant <id> Filter by claimant id
26
+ * --human-only / --agent-only
27
+ * bizar claim transition <featureId> <newStatus> # active|paused|blocked|completed
28
+ * --who <id>
29
+ * --reason <text>
30
+ *
31
+ * --json flag for machine output.
32
+ */
33
+
34
+ import chalk from 'chalk';
35
+
36
+ import { FeatureListBridge, ClaimError, CLAIM_STATUSES, STEAL_REASONS, defaultFeatureListPath } from '../feature-list-bridge.mjs';
37
+
38
+ function parseFlags(args) {
39
+ const flags = { _: [], statuses: [] };
40
+ for (let i = 0; i < args.length; i++) {
41
+ const a = args[i];
42
+ if (a === '--json') flags.json = true;
43
+ else if (a === '--who' && i + 1 < args.length) { flags.who = args[++i]; }
44
+ else if (a.startsWith('--who=')) flags.who = a.slice('--who='.length);
45
+ else if (a === '--type' && i + 1 < args.length) { flags.type = args[++i]; }
46
+ else if (a.startsWith('--type=')) flags.type = a.slice('--type='.length);
47
+ else if (a === '--reason' && i + 1 < args.length) { flags.reason = args[++i]; }
48
+ else if (a.startsWith('--reason=')) flags.reason = a.slice('--reason='.length);
49
+ else if (a === '--to' && i + 1 < args.length) { flags.to = args[++i]; }
50
+ else if (a.startsWith('--to=')) flags.to = a.slice('--to='.length);
51
+ else if (a === '--from' && i + 1 < args.length) { flags.from = args[++i]; }
52
+ else if (a.startsWith('--from=')) flags.from = a.slice('--from='.length);
53
+ else if (a === '--by' && i + 1 < args.length) { flags.by = args[++i]; }
54
+ else if (a.startsWith('--by=')) flags.by = a.slice('--by='.length);
55
+ else if (a === '--status' && i + 1 < args.length) { flags.statuses.push(args[++i]); }
56
+ else if (a.startsWith('--status=')) flags.statuses.push(a.slice('--status='.length));
57
+ else if (a === '--by-claimant' && i + 1 < args.length) { flags.byClaimant = args[++i]; }
58
+ else if (a.startsWith('--by-claimant=')) flags.byClaimant = a.slice('--by-claimant='.length);
59
+ else if (a === '--human-only') flags.humanOnly = true;
60
+ else if (a === '--agent-only') flags.agentOnly = true;
61
+ else if (a === '--file' && i + 1 < args.length) { flags.file = args[++i]; }
62
+ else if (a.startsWith('--file=')) flags.file = a.slice('--file='.length);
63
+ else if (a === '--help' || a === '-h') flags.help = true;
64
+ else flags._.push(a);
65
+ }
66
+ return flags;
67
+ }
68
+
69
+ function makeClaimant(flags, fallbackId) {
70
+ const id = flags.who || flags.by || fallbackId;
71
+ const type = flags.type || 'human';
72
+ if (!id) return null;
73
+ return { type, id, name: id };
74
+ }
75
+
76
+ function bridge(flags) {
77
+ return new FeatureListBridge({
78
+ filePath: flags.file || defaultFeatureListPath(),
79
+ });
80
+ }
81
+
82
+ function showHelp() {
83
+ console.log(`
84
+ bizar claim — GitHub-ish claim protocol over feature_list.json
85
+
86
+ Usage:
87
+ bizar claim <featureId> [--who <id>] [--type human|agent] [--reason <text>]
88
+ bizar claim release <featureId> [--who <id>] [--reason <text>]
89
+ bizar claim handoff <featureId> --to <id> [--from <id>] [--reason <text>]
90
+ bizar claim steal <featureId> --by <id> [--type human|agent] --reason <reason>
91
+ bizar claim status <featureId>
92
+ bizar claim list [--status <s>] [--by-claimant <id>] [--human-only|--agent-only]
93
+ bizar claim transition <featureId> <active|paused|blocked|completed> [--who <id>]
94
+
95
+ Steal reasons (must match exactly):
96
+ ${STEAL_REASONS.join(' | ')}
97
+
98
+ Status states (must match exactly):
99
+ ${CLAIM_STATUSES.join(' | ')}
100
+
101
+ Flags:
102
+ --json Machine-readable JSON output
103
+ --file <path> Override feature_list.json path
104
+ --who <id> / --by <id> Claimant identifier (default: $USER)
105
+ --type human|agent Claimant type (default: human)
106
+ --reason <text> Free-form reason (mandatory for steal)
107
+ --to / --from <id> Recipient / sender for handoff
108
+ --human-only / --agent-only Restrict list to human or agent claims
109
+ --status <s> Filter list (repeat for multiple)
110
+ --by-claimant <id> Filter list by claimant id
111
+ --help, -h Show this help
112
+
113
+ Examples:
114
+ bizar claim F-035 --who odin --reason "porting MetaHarness from ruflo"
115
+ bizar claim steal F-014 --by hermod --reason stale
116
+ bizar claim handoff F-035 --to tyr --from odin
117
+ bizar claim list --status claimed --agent-only
118
+ `);
119
+ }
120
+
121
+ function out(result, flags, formatter) {
122
+ if (flags.json) {
123
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
124
+ } else if (formatter) {
125
+ formatter(result);
126
+ } else {
127
+ console.log(JSON.stringify(result, null, 2));
128
+ }
129
+ }
130
+
131
+ export async function run(name, args, isHelpRequest) {
132
+ if (name !== 'claim') return false;
133
+ const flags = parseFlags(args);
134
+ if (flags.help || isHelpRequest) {
135
+ showHelp();
136
+ return true;
137
+ }
138
+
139
+ const [sub, ...rest] = flags._;
140
+ const fallback = process.env.USER || 'unknown';
141
+ const who = flags.who || fallback;
142
+
143
+ try {
144
+ const br = bridge(flags);
145
+
146
+ if (!sub || sub === 'help') {
147
+ showHelp();
148
+ return true;
149
+ }
150
+
151
+ if (sub === 'list' || sub === 'ls') {
152
+ const filters = {};
153
+ if (flags.statuses.length) filters.status = flags.statuses;
154
+ if (flags.byClaimant) filters.claimantId = flags.byClaimant;
155
+ if (flags.humanOnly) filters.claimedByHuman = true;
156
+ if (flags.agentOnly) filters.claimedByAgent = true;
157
+ const rows = br.list(filters);
158
+ out({ count: rows.length, features: rows }, flags, (r) => {
159
+ console.log(chalk.bold(` Claims (${r.count}):`));
160
+ for (const f of r.features) {
161
+ const who = f.claimant ? `${f.claimant.name || f.claimant.id} (${f.claimant.type})` : '—';
162
+ console.log(` ${chalk.cyan(f.id)} [${f.claimStatus || 'unclaimed'}] by ${who}`);
163
+ }
164
+ });
165
+ return true;
166
+ }
167
+
168
+ if (sub === 'status' || sub === 'show' || sub === 'get') {
169
+ const featureId = rest[0];
170
+ if (!featureId) {
171
+ console.error(chalk.red(' ✗ Usage: bizar claim status <featureId>'));
172
+ return false;
173
+ }
174
+ const feature = br.get(featureId);
175
+ out(feature, flags, (f) => {
176
+ console.log(chalk.bold(` ${chalk.cyan(f.id)} — ${f.behavior || ''}`));
177
+ const who = f.claimant ? `${f.claimant.name || f.claimant.id} (${f.claimant.type})` : 'unclaimed';
178
+ console.log(` state = ${f.state}`);
179
+ console.log(` claim = ${f.claimStatus || 'unclaimed'} (by ${who})`);
180
+ console.log(` claimedAt = ${f.claimedAt || '—'}`);
181
+ console.log(` claimReason = ${f.claimReason || '—'}`);
182
+ const hist = Array.isArray(f.claimHistory) ? f.claimHistory : [];
183
+ console.log(` history = ${hist.length} event(s)`);
184
+ for (const e of hist.slice(-8)) {
185
+ console.log(` ${e.ts} ${chalk.dim(e.event)}${e.reason ? ' — ' + e.reason : ''}`);
186
+ }
187
+ });
188
+ return true;
189
+ }
190
+
191
+ if (sub === 'release') {
192
+ const featureId = rest[0];
193
+ if (!featureId) {
194
+ console.error(chalk.red(' ✗ Usage: bizar claim release <featureId> [--who <id>] [--reason <text>]'));
195
+ return false;
196
+ }
197
+ const claimant = makeClaimant({ who, type: flags.type }, fallback);
198
+ const result = br.release(featureId, claimant, flags.reason || '');
199
+ out(result, flags, (r) =>
200
+ console.log(chalk.green(` ✓ Released ${featureId}`) + (r.previousClaimant ? chalk.dim(` (was held by ${r.previousClaimant.name || r.previousClaimant.id})`) : '')),
201
+ );
202
+ return true;
203
+ }
204
+
205
+ if (sub === 'handoff') {
206
+ const featureId = rest[0];
207
+ if (!featureId || !flags.to) {
208
+ console.error(chalk.red(' ✗ Usage: bizar claim handoff <featureId> --to <id> [--from <id>] [--reason <text>]'));
209
+ return false;
210
+ }
211
+ const fromClaimant = flags.from ? { type: flags.type || 'human', id: flags.from, name: flags.from } : null;
212
+ const toClaimant = { type: flags.type || 'human', id: flags.to, name: flags.to };
213
+ const result = br.handoff(featureId, fromClaimant, toClaimant, flags.reason || '');
214
+ out(result, flags, (r) =>
215
+ console.log(chalk.green(` ✓ Handed off ${featureId} → ${flags.to}`)),
216
+ );
217
+ return true;
218
+ }
219
+
220
+ if (sub === 'steal') {
221
+ const featureId = rest[0];
222
+ if (!featureId || !flags.by) {
223
+ console.error(chalk.red(' ✗ Usage: bizar claim steal <featureId> --by <id> --reason <reason>'));
224
+ return false;
225
+ }
226
+ if (!flags.reason) {
227
+ console.error(chalk.red(' ✗ steal requires --reason (one of: ' + STEAL_REASONS.join(', ') + ')'));
228
+ return false;
229
+ }
230
+ const newClaimant = { type: flags.type || 'human', id: flags.by, name: flags.by };
231
+ const result = br.steal(featureId, newClaimant, flags.reason);
232
+ out(result, flags, (r) =>
233
+ console.log(chalk.green(` ✓ Stole ${featureId} (reason: ${r.stealReason}); previous owner: ${r.previousClaimant?.name || r.previousClaimant?.id || '—'}`)),
234
+ );
235
+ return true;
236
+ }
237
+
238
+ if (sub === 'transition') {
239
+ const [featureId, newStatus] = rest;
240
+ if (!featureId || !newStatus) {
241
+ console.error(chalk.red(' ✗ Usage: bizar claim transition <featureId> <active|paused|blocked|completed> [--who <id>]'));
242
+ return false;
243
+ }
244
+ if (!CLAIM_STATUSES.includes(newStatus)) {
245
+ console.error(chalk.red(` ✗ Unknown status '${newStatus}'. Must be one of: ${CLAIM_STATUSES.join(', ')}`));
246
+ return false;
247
+ }
248
+ const claimant = makeClaimant({ who: flags.who, type: flags.type }, fallback);
249
+ const result = br.transition(featureId, newStatus, claimant, flags.reason || '');
250
+ out(result, flags, (r) =>
251
+ console.log(chalk.green(` ✓ ${featureId} → ${newStatus}` + (r.unchanged ? ' (unchanged)' : ''))),
252
+ );
253
+ return true;
254
+ }
255
+
256
+ // Default: `bizar claim <featureId>` — claim it.
257
+ if (!sub.startsWith('-') && sub) {
258
+ const featureId = sub;
259
+ const claimant = makeClaimant({ who: flags.who, type: flags.type }, fallback);
260
+ if (!claimant) {
261
+ console.error(chalk.red(' ✗ No claimant id (set $USER or pass --who <id>)'));
262
+ return false;
263
+ }
264
+ const result = br.claim(featureId, claimant, flags.reason || '');
265
+ out(result, flags, (r) =>
266
+ console.log(chalk.green(` ✓ Claimed ${featureId} by ${r.feature.claimant.name || r.feature.claimant.id} (${r.feature.claimStatus})`)),
267
+ );
268
+ return true;
269
+ }
270
+
271
+ console.error(chalk.red(` ✗ Unknown subcommand: ${sub}`));
272
+ showHelp();
273
+ return false;
274
+ } catch (err) {
275
+ if (err instanceof ClaimError) {
276
+ console.error(chalk.red(` ✗ ${err.code}: ${err.message}`));
277
+ } else {
278
+ console.error(chalk.red(` ✗ ${err && err.message ? err.message : String(err)}`));
279
+ }
280
+ return false;
281
+ }
282
+ }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * cli/commands/cost.mjs
3
+ *
4
+ * F-035 (MetaHarness) — `bizar cost` subcommand surface.
5
+ *
6
+ * Subcommands:
7
+ * bizar cost register <room> <cap> # registerRoom(roomId, capUsd)
8
+ * bizar cost status <room> # status(roomId) → JSON
9
+ * bizar cost status [--all] # listRooms()
10
+ * bizar cost reserve <room> <amount> # reserve(roomId, callerId, amount)
11
+ * --caller <id> (default: process.env.USER)
12
+ * --expiry <ms> (default: 60_000, clamped to [5s, 300s])
13
+ * bizar cost commit <txId> <amount> # commit(txId, actualUsd)
14
+ * bizar cost release <txId> # release(txId)
15
+ * bizar cost sweep # sweepExpired() (manual)
16
+ *
17
+ * Wraps cli/cost-gate.mjs (better-sqlite3-backed, atomic).
18
+ *
19
+ * --json flag for machine-readable output.
20
+ */
21
+
22
+ import chalk from 'chalk';
23
+ import { existsSync } from 'node:fs';
24
+
25
+ import { CostGate, defaultDbPath } from '../cost-gate.mjs';
26
+
27
+ const EXIT_OK = 0;
28
+ const EXIT_ERROR = 1;
29
+ const EXIT_USAGE = 2;
30
+
31
+ function parseFlags(args) {
32
+ const flags = { _: [] };
33
+ for (let i = 0; i < args.length; i++) {
34
+ const a = args[i];
35
+ if (a === '--json') flags.json = true;
36
+ else if (a === '--all') flags.all = true;
37
+ else if (a === '--caller' && i + 1 < args.length) { flags.caller = args[++i]; }
38
+ else if (a.startsWith('--caller=')) flags.caller = a.slice('--caller='.length);
39
+ else if (a === '--expiry' && i + 1 < args.length) { flags.expiry = Number(args[++i]); }
40
+ else if (a.startsWith('--expiry=')) flags.expiry = Number(a.slice('--expiry='.length));
41
+ else if (a === '--db' && i + 1 < args.length) { flags.db = args[++i]; }
42
+ else if (a.startsWith('--db=')) flags.db = a.slice('--db='.length);
43
+ else if (a === '--help' || a === '-h') flags.help = true;
44
+ else flags._.push(a);
45
+ }
46
+ return flags;
47
+ }
48
+
49
+ function showHelp() {
50
+ console.log(`
51
+ bizar cost — atomic cost gate (SQLite-backed room budget tracker)
52
+
53
+ Usage:
54
+ bizar cost register <room> <cap> Register a room with a monthly cap (USD)
55
+ bizar cost status [room] [--all] Show one room's status, or list all rooms
56
+ bizar cost reserve <room> <amount> Reserve budget (atomic; returns txId)
57
+ [--caller <id>] Caller id (default: $USER)
58
+ [--expiry <ms>] Reservation window (default 60000, range [5s,300s])
59
+ bizar cost commit <txId> <amount> Commit the actual cost
60
+ bizar cost release <txId> Release a still-reserved reservation
61
+ bizar cost sweep Sweep expired reservations (--expired)
62
+ bizar cost list List all transactions in a room (requires <room>)
63
+
64
+ Flags:
65
+ --json Machine-readable JSON output
66
+ --db <path> Override DB path (default: ~/.bizar/cost-gate.db)
67
+ --all For \`status\`: list every room
68
+ --caller <id> Reservation caller id
69
+ --expiry <ms> Reservation window before expiry
70
+ --help, -h Show this help
71
+
72
+ Description:
73
+ The cost gate prevents overspend on multi-model orchestration by
74
+ atomically reserving / committing / releasing micro-budget lines
75
+ per request. Uses better-sqlite3 with WAL + BEGIN IMMEDIATE so
76
+ concurrent reserves never double-spend.
77
+
78
+ Examples:
79
+ bizar cost register titan-cluster 50
80
+ bizar cost reserve titan-cluster 1.50 --caller sonnet --expiry 30000
81
+ bizar cost commit tx-7a4b... 0.42
82
+ `);
83
+ }
84
+
85
+ function withGate(flags, fn) {
86
+ const gate = new CostGate({ dbPath: flags.db || defaultDbPath() });
87
+ try {
88
+ return fn(gate);
89
+ } finally {
90
+ gate.close();
91
+ }
92
+ }
93
+
94
+ function out(result, flags, formatter) {
95
+ if (flags.json) {
96
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
97
+ } else if (formatter) {
98
+ formatter(result);
99
+ } else if (typeof result === 'string') {
100
+ console.log(result);
101
+ } else {
102
+ console.log(JSON.stringify(result, null, 2));
103
+ }
104
+ }
105
+
106
+ export async function run(name, args, isHelpRequest) {
107
+ if (name !== 'cost') return false;
108
+ const flags = parseFlags(args);
109
+ if (flags.help || isHelpRequest) {
110
+ showHelp();
111
+ return true;
112
+ }
113
+
114
+ const [sub, ...rest] = flags._;
115
+ if (!sub) {
116
+ showHelp();
117
+ return true;
118
+ }
119
+
120
+ try {
121
+ if (sub === 'register') {
122
+ const [room, capStr] = rest;
123
+ if (!room || capStr == null) {
124
+ console.error(chalk.red(' ✗ Usage: bizar cost register <room> <cap>'));
125
+ return false;
126
+ }
127
+ const cap = Number(capStr);
128
+ if (!Number.isFinite(cap) || cap < 0) {
129
+ console.error(chalk.red(' ✗ cap must be a non-negative number'));
130
+ return false;
131
+ }
132
+ const result = withGate(flags, (gate) => gate.registerRoom(room, cap));
133
+ out(result, flags, (r) =>
134
+ console.log(chalk.green(` ✓ Registered room '${r.roomId}' with cap $${r.capUsd.toFixed(2)}`)),
135
+ );
136
+ return true;
137
+ }
138
+
139
+ if (sub === 'status') {
140
+ const [room] = rest;
141
+ const result = withGate(flags, (gate) => {
142
+ if (flags.all || !room) return { rooms: gate.listRooms() };
143
+ return gate.status(room);
144
+ });
145
+ out(result, flags, (r) => {
146
+ if (r.rooms) {
147
+ console.log(chalk.bold(` Rooms (${r.rooms.length}):`));
148
+ for (const rm of r.rooms) {
149
+ console.log(` ${chalk.cyan(rm.roomId)} cap=$${rm.capUsd.toFixed(2)} spent=$${rm.spentUsd.toFixed(2)} reserved=$${rm.reservedUsd.toFixed(2)} remaining=$${rm.remainingUsd.toFixed(2)}`);
150
+ }
151
+ return;
152
+ }
153
+ if (r === null) {
154
+ console.log(chalk.yellow(` ! Room '${room}' not found`));
155
+ return;
156
+ }
157
+ console.log(chalk.bold(` Room ${chalk.cyan(r.roomId)}:`));
158
+ console.log(` cap = $${r.capUsd.toFixed(2)}`);
159
+ console.log(` spent = $${r.spentUsd.toFixed(2)}`);
160
+ console.log(` reserved = $${r.reservedUsd.toFixed(2)}`);
161
+ console.log(` remaining= $${r.remainingUsd.toFixed(2)}`);
162
+ console.log(` txCount = ${r.txCount}`);
163
+ });
164
+ return true;
165
+ }
166
+
167
+ if (sub === 'reserve') {
168
+ const [room, amountStr] = rest;
169
+ if (!room || amountStr == null) {
170
+ console.error(chalk.red(' ✗ Usage: bizar cost reserve <room> <amount> [--caller <id>] [--expiry <ms>]'));
171
+ return false;
172
+ }
173
+ const amount = Number(amountStr);
174
+ if (!Number.isFinite(amount) || amount < 0) {
175
+ console.error(chalk.red(' ✗ amount must be a non-negative number'));
176
+ return false;
177
+ }
178
+ const caller = flags.caller || process.env.USER || 'unknown';
179
+ const opts = {};
180
+ if (flags.expiry != null) opts.expiryMs = flags.expiry;
181
+ const result = withGate(flags, (gate) => gate.reserve(room, caller, amount, opts));
182
+ if (!result.ok) {
183
+ if (flags.json) {
184
+ process.stdout.write(JSON.stringify(result) + '\n');
185
+ } else {
186
+ console.log(chalk.red(` ✗ Reserve failed: ${result.error}`));
187
+ if (result.error === 'BUDGET_EXCEEDED') {
188
+ console.log(chalk.dim(` cap=$${result.capUsd.toFixed(2)} committed=$${result.committed.toFixed(2)} reserved=$${result.reserved.toFixed(2)}`));
189
+ }
190
+ }
191
+ return true; // graceful failure with status to caller
192
+ }
193
+ out(result, flags, (r) =>
194
+ console.log(
195
+ chalk.green(` ✓ Reserved $${amount.toFixed(2)} in '${room}' → ${chalk.bold(r.txId)} (expires in ${Math.round((r.expiresAt - Date.now()) / 1000)}s, remaining=$${r.remainingAfterReserve.toFixed(2)})`),
196
+ ),
197
+ );
198
+ return true;
199
+ }
200
+
201
+ if (sub === 'commit') {
202
+ const [txId, amountStr] = rest;
203
+ if (!txId || amountStr == null) {
204
+ console.error(chalk.red(' ✗ Usage: bizar cost commit <txId> <amount>'));
205
+ return false;
206
+ }
207
+ const amount = Number(amountStr);
208
+ if (!Number.isFinite(amount) || amount < 0) {
209
+ console.error(chalk.red(' ✗ amount must be a non-negative number'));
210
+ return false;
211
+ }
212
+ const result = withGate(flags, (gate) => gate.commit(txId, amount));
213
+ out(result, flags, (r) => {
214
+ if (!r.ok) {
215
+ console.log(chalk.red(` ✗ Commit failed: ${r.error}`));
216
+ return;
217
+ }
218
+ if (r.warned === 'COMMIT_AFTER_EXPIRY') {
219
+ console.log(chalk.yellow(` ⚠ Committed ${txId} after expiry ($${amount.toFixed(2)}); remaining=$${r.finalRemaining.toFixed(2)}`));
220
+ } else {
221
+ console.log(chalk.green(` ✓ Committed ${txId} ($${amount.toFixed(2)}); remaining=$${r.finalRemaining.toFixed(2)}`));
222
+ }
223
+ });
224
+ return true;
225
+ }
226
+
227
+ if (sub === 'release') {
228
+ const [txId] = rest;
229
+ if (!txId) {
230
+ console.error(chalk.red(' ✗ Usage: bizar cost release <txId>'));
231
+ return false;
232
+ }
233
+ const result = withGate(flags, (gate) => gate.release(txId));
234
+ out(result, flags, (r) => {
235
+ if (!r.ok) {
236
+ console.log(chalk.red(` ✗ Release failed: ${r.error}`));
237
+ return;
238
+ }
239
+ console.log(chalk.green(` ✓ Released ${txId}`));
240
+ });
241
+ return true;
242
+ }
243
+
244
+ if (sub === 'sweep') {
245
+ const result = withGate(flags, (gate) => ({ swept: gate.sweepExpired() }));
246
+ out(result, flags, (r) =>
247
+ console.log(chalk.green(` ✓ Swept ${r.swept} expired reservation(s)`)),
248
+ );
249
+ return true;
250
+ }
251
+
252
+ if (sub === 'list') {
253
+ const [room] = rest;
254
+ if (!room) {
255
+ console.error(chalk.red(' ✗ Usage: bizar cost list <room>'));
256
+ return false;
257
+ }
258
+ const result = withGate(flags, (gate) => ({ room, txs: gate.listTransactions(room) }));
259
+ out(result, flags, (r) => {
260
+ if (r.txs.length === 0) {
261
+ console.log(chalk.dim(` (no transactions in ${r.room})`));
262
+ return;
263
+ }
264
+ console.log(chalk.bold(` Transactions in ${r.room}:`));
265
+ for (const t of r.txs) {
266
+ console.log(` ${chalk.cyan(t.tx_id.slice(0, 12))}… kind=${t.kind} amount=$${t.amount_usd.toFixed(4)} ts=${new Date(t.ts).toISOString()}`);
267
+ }
268
+ });
269
+ return true;
270
+ }
271
+
272
+ console.error(chalk.red(` ✗ Unknown subcommand: ${sub}`));
273
+ showHelp();
274
+ return false;
275
+ } catch (err) {
276
+ console.error(chalk.red(` ✗ ${err && err.message ? err.message : String(err)}`));
277
+ return false;
278
+ }
279
+ }