@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.
- package/.claude/hooks/sessionend-recall.mjs +3 -3
- package/.claude/hooks/worker-suggest.mjs +110 -0
- package/.claude/settings.json +9 -0
- package/README.md +125 -47
- package/bizar-dash/dist/assets/{EnvVarsSection-B58aiJiE.js → EnvVarsSection-weVnLz3Z.js} +3 -3
- package/bizar-dash/dist/assets/EnvVarsSection-weVnLz3Z.js.map +1 -0
- package/bizar-dash/dist/assets/MobileChat-BRFjer8p.js +1 -0
- package/bizar-dash/dist/assets/{MobileChat-BJrqwVDd.js.map → MobileChat-BRFjer8p.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js +1 -0
- package/bizar-dash/dist/assets/MobileSettings-Dw5jyJWi.js.map +1 -0
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js → Toast-WTxc3m2g.js} +1 -1
- package/bizar-dash/dist/assets/{Toast-Cpl6-E63.js.map → Toast-WTxc3m2g.js.map} +1 -1
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js +1 -0
- package/bizar-dash/dist/assets/flow-BpU0-gK2.js.map +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js +1 -0
- package/bizar-dash/dist/assets/fuzzy-l0sNRNKZ.js.map +1 -0
- package/bizar-dash/dist/assets/{icons-Do5N2jmX.js → icons-B0AKnYI3.js} +98 -103
- package/bizar-dash/dist/assets/icons-B0AKnYI3.js.map +1 -0
- package/bizar-dash/dist/assets/main-BE4Uh9im.css +1 -0
- package/bizar-dash/dist/assets/main-DqC58FEZ.js +18 -0
- package/bizar-dash/dist/assets/main-DqC58FEZ.js.map +1 -0
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js +1 -0
- package/bizar-dash/dist/assets/markdown-w2bDDkAH.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-DRZsK3ic.js +1 -0
- package/bizar-dash/dist/assets/{mobile-DYCHcUpq.js.map → mobile-DRZsK3ic.js.map} +1 -1
- package/bizar-dash/dist/assets/mobile-layout-BWLtwtfQ.css +1 -0
- package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js +2 -0
- package/bizar-dash/dist/assets/mobile-layout-Ckru3HKJ.js.map +1 -0
- package/bizar-dash/dist/assets/react-vendor-BnuhLJ6X.css +1 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js +46 -0
- package/bizar-dash/dist/assets/react-vendor-D3pra6w-.js.map +1 -0
- package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js +2 -0
- package/bizar-dash/dist/assets/useSlashCommands-BAxVOU_a.js.map +1 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js +45 -0
- package/bizar-dash/dist/assets/vendor-Di7Cp-rw.js.map +1 -0
- package/bizar-dash/dist/index.html +13 -11
- package/bizar-dash/dist/mobile.html +8 -5
- package/bizar-dash/src/server/api.mjs +11 -0
- package/bizar-dash/src/server/memory-consolidator.mjs +225 -0
- package/bizar-dash/src/server/memory-distillation.mjs +296 -0
- package/bizar-dash/src/server/routes/distill.mjs +129 -0
- package/bizar-dash/src/server/routes/goal-planner.mjs +236 -0
- package/bizar-dash/src/web/App.tsx +4 -0
- package/bizar-dash/src/web/components/Tag.tsx +18 -5
- package/bizar-dash/src/web/components/Topbar.tsx +3 -0
- package/bizar-dash/src/web/components/agents/CommunicationLog.tsx +126 -0
- package/bizar-dash/src/web/components/agents/DependencyGraph.tsx +163 -0
- package/bizar-dash/src/web/components/agents/QualityGates.tsx +123 -0
- package/bizar-dash/src/web/components/agents/RealTimeEventLog.tsx +127 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.test.tsx +148 -0
- package/bizar-dash/src/web/components/agents/RoutingDecisions.tsx +160 -0
- package/bizar-dash/src/web/components/goals/GoalInput.tsx +86 -0
- package/bizar-dash/src/web/components/goals/PlanVisualization.tsx +111 -0
- package/bizar-dash/src/web/lib/goapPlanner.ts +614 -0
- package/bizar-dash/src/web/lib/types.ts +25 -0
- package/bizar-dash/src/web/mobile/MobileApp.tsx +33 -2
- package/bizar-dash/src/web/mobile/MobileBottomNav.tsx +2 -2
- package/bizar-dash/src/web/mobile/MobileEval.tsx +4 -4
- package/bizar-dash/src/web/mobile/MobileMemory.tsx +2 -2
- package/bizar-dash/src/web/mobile/MobileSettings.tsx +3 -3
- package/bizar-dash/src/web/mobile/MobileTasks.tsx +2 -2
- package/bizar-dash/src/web/mobile/components/MobileListItem.tsx +3 -3
- package/bizar-dash/src/web/styles/agents-redesign.css +311 -0
- package/bizar-dash/src/web/styles/main.css +251 -0
- package/bizar-dash/src/web/styles/mobile.css +41 -0
- package/bizar-dash/src/web/styles/tasks-redesign.css +253 -0
- package/bizar-dash/src/web/ui/controls/Button.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/Checkbox.tsx +95 -0
- package/bizar-dash/src/web/ui/controls/IconButton.tsx +66 -0
- package/bizar-dash/src/web/ui/controls/Kbd.tsx +24 -0
- package/bizar-dash/src/web/ui/controls/NumberInput.tsx +77 -0
- package/bizar-dash/src/web/ui/controls/RadioGroup.tsx +109 -0
- package/bizar-dash/src/web/ui/controls/SearchInput.tsx +91 -0
- package/bizar-dash/src/web/ui/controls/Select.tsx +92 -0
- package/bizar-dash/src/web/ui/controls/Slider.tsx +70 -0
- package/bizar-dash/src/web/ui/controls/TextInput.tsx +67 -0
- package/bizar-dash/src/web/ui/controls/Textarea.tsx +56 -0
- package/bizar-dash/src/web/ui/controls/Toggle.tsx +79 -0
- package/bizar-dash/src/web/ui/controls/controls.css +476 -0
- package/bizar-dash/src/web/ui/controls/index.ts +35 -0
- package/bizar-dash/src/web/ui/data/BarChart.tsx +60 -0
- package/bizar-dash/src/web/ui/data/DataTable.tsx +216 -0
- package/bizar-dash/src/web/ui/data/EmptyState.tsx +60 -0
- package/bizar-dash/src/web/ui/data/ErrorState.tsx +56 -0
- package/bizar-dash/src/web/ui/data/KeyValueList.tsx +106 -0
- package/bizar-dash/src/web/ui/data/LoadingState.tsx +65 -0
- package/bizar-dash/src/web/ui/data/Sparkline.tsx +87 -0
- package/bizar-dash/src/web/ui/data/StatTile.tsx +87 -0
- package/bizar-dash/src/web/ui/data/data.css +535 -0
- package/bizar-dash/src/web/ui/data/index.ts +31 -0
- package/bizar-dash/src/web/ui/feedback/Badge.tsx +49 -0
- package/bizar-dash/src/web/ui/feedback/Dialog.tsx +209 -0
- package/bizar-dash/src/web/ui/feedback/ProgressBar.tsx +69 -0
- package/bizar-dash/src/web/ui/feedback/StatusDot.tsx +50 -0
- package/bizar-dash/src/web/ui/feedback/Toast.tsx +200 -0
- package/bizar-dash/src/web/ui/feedback/Tooltip.tsx +126 -0
- package/bizar-dash/src/web/ui/feedback/feedback.css +314 -0
- package/bizar-dash/src/web/ui/feedback/index.ts +43 -0
- package/bizar-dash/src/web/ui/index.ts +56 -0
- package/bizar-dash/src/web/ui/layout/AppShell.tsx +51 -0
- package/bizar-dash/src/web/ui/layout/Breadcrumbs.tsx +97 -0
- package/bizar-dash/src/web/ui/layout/Panel.tsx +73 -0
- package/bizar-dash/src/web/ui/layout/PanelHeader.tsx +39 -0
- package/bizar-dash/src/web/ui/layout/Sidebar.tsx +131 -0
- package/bizar-dash/src/web/ui/layout/Tabs.tsx +133 -0
- package/bizar-dash/src/web/ui/layout/Topbar.tsx +42 -0
- package/bizar-dash/src/web/ui/layout/ViewHeader.tsx +48 -0
- package/bizar-dash/src/web/ui/layout/index.ts +34 -0
- package/bizar-dash/src/web/ui/layout/layout.css +464 -0
- package/bizar-dash/src/web/ui/navigation/CommandPalette.tsx +199 -0
- package/bizar-dash/src/web/ui/navigation/NavGroup.tsx +95 -0
- package/bizar-dash/src/web/ui/navigation/NavLink.tsx +93 -0
- package/bizar-dash/src/web/ui/navigation/index.ts +17 -0
- package/bizar-dash/src/web/ui/navigation/navigation.css +343 -0
- package/bizar-dash/src/web/ui/primitives/Box.tsx +91 -0
- package/bizar-dash/src/web/ui/primitives/Grid.tsx +49 -0
- package/bizar-dash/src/web/ui/primitives/Inline.tsx +55 -0
- package/bizar-dash/src/web/ui/primitives/Separator.tsx +46 -0
- package/bizar-dash/src/web/ui/primitives/Stack.tsx +54 -0
- package/bizar-dash/src/web/ui/primitives/VisuallyHidden.tsx +26 -0
- package/bizar-dash/src/web/ui/primitives/index.ts +23 -0
- package/bizar-dash/src/web/ui/primitives/primitives.css +183 -0
- package/bizar-dash/src/web/ui/styles/globals.css +86 -0
- package/bizar-dash/src/web/ui/styles/reset.css +125 -0
- package/bizar-dash/src/web/ui/styles/tokens.css +208 -0
- package/bizar-dash/src/web/ui/theme/ThemeProvider.tsx +142 -0
- package/bizar-dash/src/web/ui/theme/tokens.ts +131 -0
- package/bizar-dash/src/web/ui/theme/useTheme.ts +22 -0
- package/bizar-dash/src/web/ui/utils/cx.ts +42 -0
- package/bizar-dash/src/web/views/Agents.tsx +516 -268
- package/bizar-dash/src/web/views/GoalPlanner.tsx +158 -0
- package/bizar-dash/src/web/views/Overview.tsx +985 -448
- package/bizar-dash/src/web/views/Tasks.tsx +271 -273
- package/bizar-dash/tests/communication-log.test.tsx +67 -0
- package/bizar-dash/tests/dep-graph.test.tsx +40 -0
- package/bizar-dash/tests/event-log.test.tsx +61 -0
- package/bizar-dash/tests/goal-input.test.tsx +72 -0
- package/bizar-dash/tests/goap-planner.test.ts +68 -0
- package/bizar-dash/tests/quality-gates.test.tsx +59 -0
- package/bizar-dash/tests/setup.ts +31 -1
- package/bizar-dash/tests/ui/controls/Button.test.tsx +50 -0
- package/bizar-dash/tests/ui/controls/Checkbox.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/IconButton.test.tsx +37 -0
- package/bizar-dash/tests/ui/controls/Kbd.test.tsx +39 -0
- package/bizar-dash/tests/ui/controls/NumberInput.test.tsx +56 -0
- package/bizar-dash/tests/ui/controls/RadioGroup.test.tsx +58 -0
- package/bizar-dash/tests/ui/controls/SearchInput.test.tsx +52 -0
- package/bizar-dash/tests/ui/controls/Select.test.tsx +66 -0
- package/bizar-dash/tests/ui/controls/Slider.test.tsx +41 -0
- package/bizar-dash/tests/ui/controls/TextInput.test.tsx +44 -0
- package/bizar-dash/tests/ui/controls/Textarea.test.tsx +56 -0
- package/bizar-dash/tests/ui/controls/Toggle.test.tsx +39 -0
- package/bizar-dash/tests/ui/data/BarChart.test.tsx +55 -0
- package/bizar-dash/tests/ui/data/DataTable.test.tsx +112 -0
- package/bizar-dash/tests/ui/data/EmptyState.test.tsx +57 -0
- package/bizar-dash/tests/ui/data/ErrorState.test.tsx +47 -0
- package/bizar-dash/tests/ui/data/KeyValueList.test.tsx +59 -0
- package/bizar-dash/tests/ui/data/LoadingState.test.tsx +37 -0
- package/bizar-dash/tests/ui/data/Sparkline.test.tsx +56 -0
- package/bizar-dash/tests/ui/data/StatTile.test.tsx +69 -0
- package/bizar-dash/tests/ui/feedback/Badge.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Dialog.test.tsx +99 -0
- package/bizar-dash/tests/ui/feedback/ProgressBar.test.tsx +66 -0
- package/bizar-dash/tests/ui/feedback/StatusDot.test.tsx +41 -0
- package/bizar-dash/tests/ui/feedback/Toast.test.tsx +124 -0
- package/bizar-dash/tests/ui/feedback/Tooltip.test.tsx +102 -0
- package/bizar-dash/tests/ui/layout/AppShell.test.tsx +50 -0
- package/bizar-dash/tests/ui/layout/Breadcrumbs.test.tsx +85 -0
- package/bizar-dash/tests/ui/layout/Panel.test.tsx +52 -0
- package/bizar-dash/tests/ui/layout/PanelHeader.test.tsx +32 -0
- package/bizar-dash/tests/ui/layout/Sidebar.test.tsx +95 -0
- package/bizar-dash/tests/ui/layout/Tabs.test.tsx +87 -0
- package/bizar-dash/tests/ui/layout/Topbar.test.tsx +41 -0
- package/bizar-dash/tests/ui/layout/ViewHeader.test.tsx +47 -0
- package/bizar-dash/tests/ui/navigation/CommandPalette.test.tsx +105 -0
- package/bizar-dash/tests/ui/navigation/NavGroup.test.tsx +55 -0
- package/bizar-dash/tests/ui/navigation/NavLink.test.tsx +61 -0
- package/bizar-dash/tests/ui/primitives/Box.test.tsx +49 -0
- package/bizar-dash/tests/ui/primitives/Grid.test.tsx +37 -0
- package/bizar-dash/tests/ui/primitives/Inline.test.tsx +33 -0
- package/bizar-dash/tests/ui/primitives/Separator.test.tsx +34 -0
- package/bizar-dash/tests/ui/primitives/Stack.test.tsx +45 -0
- package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +27 -0
- package/bizar-dash/tests/views/Agents.test.tsx +359 -0
- package/bizar-dash/tests/views/Overview.test.tsx +413 -0
- package/bizar-dash/tests/views/Tasks.test.tsx +250 -0
- package/bizar-dash/vitest.config.ts +3 -1
- package/cli/__tests__/cost-gate.test.mjs +301 -0
- package/cli/__tests__/feature-list-bridge.test.mjs +371 -0
- package/cli/bin.mjs +38 -0
- package/cli/commands/claim.mjs +282 -0
- package/cli/commands/cost.mjs +279 -0
- package/cli/cost-gate.mjs +501 -0
- package/cli/feature-list-bridge.mjs +400 -0
- package/cli/memory-constants.mjs +17 -0
- package/cli/provision-claude.mjs +94 -0
- package/cli/worker-dispatcher.mjs +259 -0
- package/cli/worker-dispatcher.test.mjs +204 -0
- package/config/trigger-patterns.json +186 -0
- package/package.json +2 -2
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/agent-registry.ts +304 -0
- package/packages/sdk/src/consensus/byzantine.ts +587 -0
- package/packages/sdk/src/consensus/index.ts +147 -0
- package/packages/sdk/src/consensus/queen.ts +173 -0
- package/packages/sdk/src/consensus/types.ts +199 -0
- package/packages/sdk/src/federation/audit.ts +156 -0
- package/packages/sdk/src/federation/budget.ts +269 -0
- package/packages/sdk/src/federation/envelope.ts +144 -0
- package/packages/sdk/src/federation/hmac.ts +160 -0
- package/packages/sdk/src/federation/index.ts +443 -0
- package/packages/sdk/src/federation/pii.ts +241 -0
- package/packages/sdk/src/federation/policy.ts +112 -0
- package/packages/sdk/src/federation/trust.ts +146 -0
- package/packages/sdk/src/index.ts +67 -0
- package/packages/sdk/src/mcp/server.ts +351 -1
- package/packages/sdk/src/router/codemod-intent.ts +125 -0
- package/packages/sdk/src/router/index.ts +247 -0
- package/packages/sdk/src/router/memory-distillation-shim.mjs +18 -0
- package/packages/sdk/src/router/memory-distillation.ts +249 -0
- package/packages/sdk/src/router/model-router.ts +235 -0
- package/packages/sdk/src/router/q-learning-router.ts +304 -0
- package/packages/sdk/src/swarm-topology.ts +279 -0
- package/packages/sdk/tests/agent-registry.test.ts +285 -0
- package/packages/sdk/tests/consensus.test.ts +376 -0
- package/packages/sdk/tests/federation/audit.test.ts +164 -0
- package/packages/sdk/tests/federation/budget.test.ts +189 -0
- package/packages/sdk/tests/federation/envelope.test.ts +120 -0
- package/packages/sdk/tests/federation/hmac.test.ts +194 -0
- package/packages/sdk/tests/federation/orchestrator.test.ts +176 -0
- package/packages/sdk/tests/federation/pii.test.ts +106 -0
- package/packages/sdk/tests/federation/policy.test.ts +140 -0
- package/packages/sdk/tests/federation/trust.test.ts +124 -0
- package/packages/sdk/tests/mcp-tools.test.ts +204 -0
- package/packages/sdk/tests/memory-distillation.test.mjs +186 -0
- package/packages/sdk/tests/model-router.test.mjs +108 -0
- package/packages/sdk/tests/q-learning-router.test.mjs +100 -0
- package/packages/sdk/tests/router-orchestrator.test.mjs +172 -0
- package/packages/sdk/tests/router.test.mjs +91 -0
- package/packages/sdk/tests/sdk.test.mjs +26 -2
- package/packages/sdk/tests/swarm-topology.test.ts +258 -0
- package/scripts/bh-full-e2e.mjs +11 -15
- package/bizar-dash/dist/assets/EnvVarsSection-B58aiJiE.js.map +0 -1
- package/bizar-dash/dist/assets/MobileChat-BJrqwVDd.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js +0 -1
- package/bizar-dash/dist/assets/MobileSettings-CEQNJNLJ.js.map +0 -1
- package/bizar-dash/dist/assets/icons-Do5N2jmX.js.map +0 -1
- package/bizar-dash/dist/assets/main-IvfQAOfy.js +0 -18
- package/bizar-dash/dist/assets/main-IvfQAOfy.js.map +0 -1
- package/bizar-dash/dist/assets/main-ietCEg_R.css +0 -1
- package/bizar-dash/dist/assets/markdown-tOLaD6nm.js +0 -1
- package/bizar-dash/dist/assets/markdown-tOLaD6nm.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-DYCHcUpq.js +0 -1
- package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js +0 -2
- package/bizar-dash/dist/assets/mobile-layout-CBHjpwsb.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-layout-CJnZNLy3.css +0 -1
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js +0 -40
- package/bizar-dash/dist/assets/react-vendor-Dn4wqh4Z.js.map +0 -1
- package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js +0 -2
- package/bizar-dash/dist/assets/useSlashCommands-Bd7_FA6U.js.map +0 -1
- package/bizar-dash/dist/assets/vendor-C843201K.js +0 -29
- package/bizar-dash/dist/assets/vendor-C843201K.js.map +0 -1
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- 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
|
+
}
|