@ashlr/hub 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1158 -0
- package/LICENSE +21 -0
- package/README.md +833 -0
- package/bin/ashlr +8 -0
- package/dist/api/core.d.ts +28 -0
- package/dist/api/core.js +42 -0
- package/dist/api/core.js.map +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.js +9 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/plugin.d.ts +12 -0
- package/dist/api/plugin.js +13 -0
- package/dist/api/plugin.js.map +1 -0
- package/dist/api/types.d.ts +9 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.js +23 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/ask.d.ts +22 -0
- package/dist/cli/ask.js +240 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/audit.d.ts +26 -0
- package/dist/cli/audit.js +199 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/backlog.d.ts +26 -0
- package/dist/cli/backlog.js +322 -0
- package/dist/cli/backlog.js.map +1 -0
- package/dist/cli/completions.d.ts +18 -0
- package/dist/cli/completions.js +156 -0
- package/dist/cli/completions.js.map +1 -0
- package/dist/cli/daemon.d.ts +31 -0
- package/dist/cli/daemon.js +334 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/demo-sandbox.d.ts +78 -0
- package/dist/cli/demo-sandbox.js +227 -0
- package/dist/cli/demo-sandbox.js.map +1 -0
- package/dist/cli/demo.d.ts +76 -0
- package/dist/cli/demo.js +285 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/digest.d.ts +46 -0
- package/dist/cli/digest.js +352 -0
- package/dist/cli/digest.js.map +1 -0
- package/dist/cli/doctor-init.d.ts +28 -0
- package/dist/cli/doctor-init.js +335 -0
- package/dist/cli/doctor-init.js.map +1 -0
- package/dist/cli/genome.d.ts +62 -0
- package/dist/cli/genome.js +1061 -0
- package/dist/cli/genome.js.map +1 -0
- package/dist/cli/gh.d.ts +27 -0
- package/dist/cli/gh.js +322 -0
- package/dist/cli/gh.js.map +1 -0
- package/dist/cli/goals.d.ts +55 -0
- package/dist/cli/goals.js +667 -0
- package/dist/cli/goals.js.map +1 -0
- package/dist/cli/health.d.ts +56 -0
- package/dist/cli/health.js +407 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/help.d.ts +51 -0
- package/dist/cli/help.js +436 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/inbox.d.ts +28 -0
- package/dist/cli/inbox.js +589 -0
- package/dist/cli/inbox.js.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.js +1208 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/knowledge.d.ts +33 -0
- package/dist/cli/knowledge.js +575 -0
- package/dist/cli/knowledge.js.map +1 -0
- package/dist/cli/mcp.d.ts +26 -0
- package/dist/cli/mcp.js +487 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/models.d.ts +26 -0
- package/dist/cli/models.js +435 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/new.d.ts +32 -0
- package/dist/cli/new.js +565 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/notify.d.ts +20 -0
- package/dist/cli/notify.js +188 -0
- package/dist/cli/notify.js.map +1 -0
- package/dist/cli/onboard.d.ts +85 -0
- package/dist/cli/onboard.js +305 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/open.d.ts +38 -0
- package/dist/cli/open.js +86 -0
- package/dist/cli/open.js.map +1 -0
- package/dist/cli/orient.d.ts +14 -0
- package/dist/cli/orient.js +110 -0
- package/dist/cli/orient.js.map +1 -0
- package/dist/cli/picker.d.ts +21 -0
- package/dist/cli/picker.js +177 -0
- package/dist/cli/picker.js.map +1 -0
- package/dist/cli/plugins.d.ts +21 -0
- package/dist/cli/plugins.js +394 -0
- package/dist/cli/plugins.js.map +1 -0
- package/dist/cli/preflight.d.ts +29 -0
- package/dist/cli/preflight.js +131 -0
- package/dist/cli/preflight.js.map +1 -0
- package/dist/cli/pulse.d.ts +26 -0
- package/dist/cli/pulse.js +482 -0
- package/dist/cli/pulse.js.map +1 -0
- package/dist/cli/reflect.d.ts +42 -0
- package/dist/cli/reflect.js +381 -0
- package/dist/cli/reflect.js.map +1 -0
- package/dist/cli/run.d.ts +29 -0
- package/dist/cli/run.js +687 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/sandbox.d.ts +20 -0
- package/dist/cli/sandbox.js +345 -0
- package/dist/cli/sandbox.js.map +1 -0
- package/dist/cli/seams.d.ts +41 -0
- package/dist/cli/seams.js +167 -0
- package/dist/cli/seams.js.map +1 -0
- package/dist/cli/serve.d.ts +25 -0
- package/dist/cli/serve.js +249 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/ship.d.ts +33 -0
- package/dist/cli/ship.js +433 -0
- package/dist/cli/ship.js.map +1 -0
- package/dist/cli/spec.d.ts +19 -0
- package/dist/cli/spec.js +478 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/cli/swarm.d.ts +42 -0
- package/dist/cli/swarm.js +1365 -0
- package/dist/cli/swarm.js.map +1 -0
- package/dist/cli/telemetry.d.ts +26 -0
- package/dist/cli/telemetry.js +364 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/cli/tui.d.ts +17 -0
- package/dist/cli/tui.js +52 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +53 -0
- package/dist/cli/ui.js +66 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli/update.d.ts +38 -0
- package/dist/cli/update.js +682 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/vercel.d.ts +20 -0
- package/dist/cli/vercel.js +193 -0
- package/dist/cli/vercel.js.map +1 -0
- package/dist/cli/verify-safety.d.ts +96 -0
- package/dist/cli/verify-safety.js +509 -0
- package/dist/cli/verify-safety.js.map +1 -0
- package/dist/cli/wire.d.ts +22 -0
- package/dist/cli/wire.js +205 -0
- package/dist/cli/wire.js.map +1 -0
- package/dist/core/classify.d.ts +51 -0
- package/dist/core/classify.js +450 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/config.d.ts +63 -0
- package/dist/core/config.js +474 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/daemon/loop.d.ts +74 -0
- package/dist/core/daemon/loop.js +618 -0
- package/dist/core/daemon/loop.js.map +1 -0
- package/dist/core/daemon/state.d.ts +66 -0
- package/dist/core/daemon/state.js +197 -0
- package/dist/core/daemon/state.js.map +1 -0
- package/dist/core/dashboard.d.ts +40 -0
- package/dist/core/dashboard.js +463 -0
- package/dist/core/dashboard.js.map +1 -0
- package/dist/core/digest/build.d.ts +51 -0
- package/dist/core/digest/build.js +230 -0
- package/dist/core/digest/build.js.map +1 -0
- package/dist/core/digest/deliver.d.ts +47 -0
- package/dist/core/digest/deliver.js +230 -0
- package/dist/core/digest/deliver.js.map +1 -0
- package/dist/core/digest/store.d.ts +57 -0
- package/dist/core/digest/store.js +223 -0
- package/dist/core/digest/store.js.map +1 -0
- package/dist/core/doctor-fix.d.ts +21 -0
- package/dist/core/doctor-fix.js +440 -0
- package/dist/core/doctor-fix.js.map +1 -0
- package/dist/core/doctor.d.ts +18 -0
- package/dist/core/doctor.js +806 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/env-bridge.d.ts +64 -0
- package/dist/core/env-bridge.js +103 -0
- package/dist/core/env-bridge.js.map +1 -0
- package/dist/core/genome/capture.d.ts +49 -0
- package/dist/core/genome/capture.js +352 -0
- package/dist/core/genome/capture.js.map +1 -0
- package/dist/core/genome/consolidate.d.ts +38 -0
- package/dist/core/genome/consolidate.js +426 -0
- package/dist/core/genome/consolidate.js.map +1 -0
- package/dist/core/genome/export.d.ts +29 -0
- package/dist/core/genome/export.js +102 -0
- package/dist/core/genome/export.js.map +1 -0
- package/dist/core/genome/playbook.d.ts +33 -0
- package/dist/core/genome/playbook.js +320 -0
- package/dist/core/genome/playbook.js.map +1 -0
- package/dist/core/genome/recall.d.ts +45 -0
- package/dist/core/genome/recall.js +293 -0
- package/dist/core/genome/recall.js.map +1 -0
- package/dist/core/genome/store.d.ts +62 -0
- package/dist/core/genome/store.js +711 -0
- package/dist/core/genome/store.js.map +1 -0
- package/dist/core/git.d.ts +34 -0
- package/dist/core/git.js +115 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/goals/advance.d.ts +82 -0
- package/dist/core/goals/advance.js +263 -0
- package/dist/core/goals/advance.js.map +1 -0
- package/dist/core/goals/planner.d.ts +58 -0
- package/dist/core/goals/planner.js +233 -0
- package/dist/core/goals/planner.js.map +1 -0
- package/dist/core/goals/store.d.ts +128 -0
- package/dist/core/goals/store.js +442 -0
- package/dist/core/goals/store.js.map +1 -0
- package/dist/core/inbox/apply.d.ts +36 -0
- package/dist/core/inbox/apply.js +407 -0
- package/dist/core/inbox/apply.js.map +1 -0
- package/dist/core/inbox/notify-proposal.d.ts +13 -0
- package/dist/core/inbox/notify-proposal.js +21 -0
- package/dist/core/inbox/notify-proposal.js.map +1 -0
- package/dist/core/inbox/store.d.ts +67 -0
- package/dist/core/inbox/store.js +258 -0
- package/dist/core/inbox/store.js.map +1 -0
- package/dist/core/index-engine.d.ts +62 -0
- package/dist/core/index-engine.js +486 -0
- package/dist/core/index-engine.js.map +1 -0
- package/dist/core/integrations/desktop-notify.d.ts +18 -0
- package/dist/core/integrations/desktop-notify.js +42 -0
- package/dist/core/integrations/desktop-notify.js.map +1 -0
- package/dist/core/integrations/editors.d.ts +50 -0
- package/dist/core/integrations/editors.js +216 -0
- package/dist/core/integrations/editors.js.map +1 -0
- package/dist/core/integrations/github.d.ts +66 -0
- package/dist/core/integrations/github.js +332 -0
- package/dist/core/integrations/github.js.map +1 -0
- package/dist/core/integrations/identity.d.ts +29 -0
- package/dist/core/integrations/identity.js +359 -0
- package/dist/core/integrations/identity.js.map +1 -0
- package/dist/core/integrations/notify.d.ts +23 -0
- package/dist/core/integrations/notify.js +96 -0
- package/dist/core/integrations/notify.js.map +1 -0
- package/dist/core/integrations/vercel.d.ts +37 -0
- package/dist/core/integrations/vercel.js +192 -0
- package/dist/core/integrations/vercel.js.map +1 -0
- package/dist/core/knowledge/ask.d.ts +34 -0
- package/dist/core/knowledge/ask.js +325 -0
- package/dist/core/knowledge/ask.js.map +1 -0
- package/dist/core/knowledge/graph.d.ts +51 -0
- package/dist/core/knowledge/graph.js +564 -0
- package/dist/core/knowledge/graph.js.map +1 -0
- package/dist/core/knowledge/index.d.ts +74 -0
- package/dist/core/knowledge/index.js +586 -0
- package/dist/core/knowledge/index.js.map +1 -0
- package/dist/core/learn/playbooks.d.ts +84 -0
- package/dist/core/learn/playbooks.js +241 -0
- package/dist/core/learn/playbooks.js.map +1 -0
- package/dist/core/learn/reflect.d.ts +87 -0
- package/dist/core/learn/reflect.js +435 -0
- package/dist/core/learn/reflect.js.map +1 -0
- package/dist/core/learn/store.d.ts +51 -0
- package/dist/core/learn/store.js +165 -0
- package/dist/core/learn/store.js.map +1 -0
- package/dist/core/learn/tuning.d.ts +48 -0
- package/dist/core/learn/tuning.js +201 -0
- package/dist/core/learn/tuning.js.map +1 -0
- package/dist/core/lifecycle/scaffold.d.ts +43 -0
- package/dist/core/lifecycle/scaffold.js +260 -0
- package/dist/core/lifecycle/scaffold.js.map +1 -0
- package/dist/core/lifecycle/ship.d.ts +48 -0
- package/dist/core/lifecycle/ship.js +513 -0
- package/dist/core/lifecycle/ship.js.map +1 -0
- package/dist/core/lifecycle/templates.d.ts +20 -0
- package/dist/core/lifecycle/templates.js +605 -0
- package/dist/core/lifecycle/templates.js.map +1 -0
- package/dist/core/mcp-gateway.d.ts +59 -0
- package/dist/core/mcp-gateway.js +385 -0
- package/dist/core/mcp-gateway.js.map +1 -0
- package/dist/core/mcp-native.d.ts +53 -0
- package/dist/core/mcp-native.js +507 -0
- package/dist/core/mcp-native.js.map +1 -0
- package/dist/core/mcp-registry.d.ts +36 -0
- package/dist/core/mcp-registry.js +180 -0
- package/dist/core/mcp-registry.js.map +1 -0
- package/dist/core/observability/budget-alert.d.ts +18 -0
- package/dist/core/observability/budget-alert.js +90 -0
- package/dist/core/observability/budget-alert.js.map +1 -0
- package/dist/core/observability/estimate.d.ts +27 -0
- package/dist/core/observability/estimate.js +188 -0
- package/dist/core/observability/estimate.js.map +1 -0
- package/dist/core/observability/forecast.d.ts +19 -0
- package/dist/core/observability/forecast.js +101 -0
- package/dist/core/observability/forecast.js.map +1 -0
- package/dist/core/observability/governance.d.ts +28 -0
- package/dist/core/observability/governance.js +101 -0
- package/dist/core/observability/governance.js.map +1 -0
- package/dist/core/observability/otlp.d.ts +88 -0
- package/dist/core/observability/otlp.js +217 -0
- package/dist/core/observability/otlp.js.map +1 -0
- package/dist/core/observability/rollup.d.ts +35 -0
- package/dist/core/observability/rollup.js +311 -0
- package/dist/core/observability/rollup.js.map +1 -0
- package/dist/core/observability/telemetry-sink.d.ts +63 -0
- package/dist/core/observability/telemetry-sink.js +350 -0
- package/dist/core/observability/telemetry-sink.js.map +1 -0
- package/dist/core/observability/usage-source.d.ts +60 -0
- package/dist/core/observability/usage-source.js +347 -0
- package/dist/core/observability/usage-source.js.map +1 -0
- package/dist/core/onboard.d.ts +27 -0
- package/dist/core/onboard.js +288 -0
- package/dist/core/onboard.js.map +1 -0
- package/dist/core/orient.d.ts +23 -0
- package/dist/core/orient.js +135 -0
- package/dist/core/orient.js.map +1 -0
- package/dist/core/phantom.d.ts +23 -0
- package/dist/core/phantom.js +279 -0
- package/dist/core/phantom.js.map +1 -0
- package/dist/core/plugins/host-api.d.ts +29 -0
- package/dist/core/plugins/host-api.js +113 -0
- package/dist/core/plugins/host-api.js.map +1 -0
- package/dist/core/plugins/integrity.d.ts +36 -0
- package/dist/core/plugins/integrity.js +73 -0
- package/dist/core/plugins/integrity.js.map +1 -0
- package/dist/core/plugins/manifest.d.ts +31 -0
- package/dist/core/plugins/manifest.js +316 -0
- package/dist/core/plugins/manifest.js.map +1 -0
- package/dist/core/plugins/registry.d.ts +87 -0
- package/dist/core/plugins/registry.js +415 -0
- package/dist/core/plugins/registry.js.map +1 -0
- package/dist/core/plugins/types.d.ts +182 -0
- package/dist/core/plugins/types.js +40 -0
- package/dist/core/plugins/types.js.map +1 -0
- package/dist/core/plugins/wrappers.d.ts +40 -0
- package/dist/core/plugins/wrappers.js +229 -0
- package/dist/core/plugins/wrappers.js.map +1 -0
- package/dist/core/portfolio/backlog.d.ts +40 -0
- package/dist/core/portfolio/backlog.js +177 -0
- package/dist/core/portfolio/backlog.js.map +1 -0
- package/dist/core/portfolio/scanners.d.ts +21 -0
- package/dist/core/portfolio/scanners.js +600 -0
- package/dist/core/portfolio/scanners.js.map +1 -0
- package/dist/core/providers.d.ts +34 -0
- package/dist/core/providers.js +250 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/quality/conventions.d.ts +35 -0
- package/dist/core/quality/conventions.js +267 -0
- package/dist/core/quality/conventions.js.map +1 -0
- package/dist/core/quality/fixes.d.ts +56 -0
- package/dist/core/quality/fixes.js +209 -0
- package/dist/core/quality/fixes.js.map +1 -0
- package/dist/core/quality/health.d.ts +69 -0
- package/dist/core/quality/health.js +350 -0
- package/dist/core/quality/health.js.map +1 -0
- package/dist/core/quality/store.d.ts +56 -0
- package/dist/core/quality/store.js +195 -0
- package/dist/core/quality/store.js.map +1 -0
- package/dist/core/readiness.d.ts +112 -0
- package/dist/core/readiness.js +431 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core/run/agent-loop.d.ts +40 -0
- package/dist/core/run/agent-loop.js +291 -0
- package/dist/core/run/agent-loop.js.map +1 -0
- package/dist/core/run/budget.d.ts +47 -0
- package/dist/core/run/budget.js +113 -0
- package/dist/core/run/budget.js.map +1 -0
- package/dist/core/run/engines.d.ts +75 -0
- package/dist/core/run/engines.js +199 -0
- package/dist/core/run/engines.js.map +1 -0
- package/dist/core/run/model-manager.d.ts +64 -0
- package/dist/core/run/model-manager.js +339 -0
- package/dist/core/run/model-manager.js.map +1 -0
- package/dist/core/run/orchestrator.d.ts +100 -0
- package/dist/core/run/orchestrator.js +1515 -0
- package/dist/core/run/orchestrator.js.map +1 -0
- package/dist/core/run/provider-client.d.ts +46 -0
- package/dist/core/run/provider-client.js +796 -0
- package/dist/core/run/provider-client.js.map +1 -0
- package/dist/core/run/retry.d.ts +19 -0
- package/dist/core/run/retry.js +68 -0
- package/dist/core/run/retry.js.map +1 -0
- package/dist/core/run/router.d.ts +50 -0
- package/dist/core/run/router.js +257 -0
- package/dist/core/run/router.js.map +1 -0
- package/dist/core/run/self-heal.d.ts +52 -0
- package/dist/core/run/self-heal.js +181 -0
- package/dist/core/run/self-heal.js.map +1 -0
- package/dist/core/run/streaming.d.ts +31 -0
- package/dist/core/run/streaming.js +122 -0
- package/dist/core/run/streaming.js.map +1 -0
- package/dist/core/run/verify.d.ts +30 -0
- package/dist/core/run/verify.js +204 -0
- package/dist/core/run/verify.js.map +1 -0
- package/dist/core/sandbox/audit.d.ts +31 -0
- package/dist/core/sandbox/audit.js +169 -0
- package/dist/core/sandbox/audit.js.map +1 -0
- package/dist/core/sandbox/policy.d.ts +61 -0
- package/dist/core/sandbox/policy.js +211 -0
- package/dist/core/sandbox/policy.js.map +1 -0
- package/dist/core/sandbox/worktree.d.ts +175 -0
- package/dist/core/sandbox/worktree.js +673 -0
- package/dist/core/sandbox/worktree.js.map +1 -0
- package/dist/core/seams/backlog.d.ts +47 -0
- package/dist/core/seams/backlog.js +47 -0
- package/dist/core/seams/backlog.js.map +1 -0
- package/dist/core/seams/daemon-coordinator.d.ts +72 -0
- package/dist/core/seams/daemon-coordinator.js +76 -0
- package/dist/core/seams/daemon-coordinator.js.map +1 -0
- package/dist/core/seams/genome.d.ts +45 -0
- package/dist/core/seams/genome.js +53 -0
- package/dist/core/seams/genome.js.map +1 -0
- package/dist/core/seams/identity.d.ts +40 -0
- package/dist/core/seams/identity.js +44 -0
- package/dist/core/seams/identity.js.map +1 -0
- package/dist/core/seams/inbox.d.ts +60 -0
- package/dist/core/seams/inbox.js +66 -0
- package/dist/core/seams/inbox.js.map +1 -0
- package/dist/core/seams/index.d.ts +20 -0
- package/dist/core/seams/index.js +21 -0
- package/dist/core/seams/index.js.map +1 -0
- package/dist/core/seams/portfolio.d.ts +50 -0
- package/dist/core/seams/portfolio.js +61 -0
- package/dist/core/seams/portfolio.js.map +1 -0
- package/dist/core/seams/registry.d.ts +42 -0
- package/dist/core/seams/registry.js +128 -0
- package/dist/core/seams/registry.js.map +1 -0
- package/dist/core/seams/run-swarm.d.ts +66 -0
- package/dist/core/seams/run-swarm.js +74 -0
- package/dist/core/seams/run-swarm.js.map +1 -0
- package/dist/core/seams/types.d.ts +123 -0
- package/dist/core/seams/types.js +35 -0
- package/dist/core/seams/types.js.map +1 -0
- package/dist/core/spec/spec-store.d.ts +62 -0
- package/dist/core/spec/spec-store.js +359 -0
- package/dist/core/spec/spec-store.js.map +1 -0
- package/dist/core/swarm/gate.d.ts +45 -0
- package/dist/core/swarm/gate.js +114 -0
- package/dist/core/swarm/gate.js.map +1 -0
- package/dist/core/swarm/planner.d.ts +32 -0
- package/dist/core/swarm/planner.js +293 -0
- package/dist/core/swarm/planner.js.map +1 -0
- package/dist/core/swarm/rollback.d.ts +56 -0
- package/dist/core/swarm/rollback.js +266 -0
- package/dist/core/swarm/rollback.js.map +1 -0
- package/dist/core/swarm/runner.d.ts +62 -0
- package/dist/core/swarm/runner.js +1263 -0
- package/dist/core/swarm/runner.js.map +1 -0
- package/dist/core/swarm/sign.d.ts +71 -0
- package/dist/core/swarm/sign.js +362 -0
- package/dist/core/swarm/sign.js.map +1 -0
- package/dist/core/swarm/store.d.ts +52 -0
- package/dist/core/swarm/store.js +195 -0
- package/dist/core/swarm/store.js.map +1 -0
- package/dist/core/tidy.d.ts +32 -0
- package/dist/core/tidy.js +354 -0
- package/dist/core/tidy.js.map +1 -0
- package/dist/core/tools-registry.d.ts +16 -0
- package/dist/core/tools-registry.js +308 -0
- package/dist/core/tools-registry.js.map +1 -0
- package/dist/core/types.d.ts +2545 -0
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/web/api.d.ts +53 -0
- package/dist/core/web/api.js +698 -0
- package/dist/core/web/api.js.map +1 -0
- package/dist/core/web/public/app.js +1906 -0
- package/dist/core/web/public/index.html +721 -0
- package/dist/core/web/public/styles.css +2007 -0
- package/dist/core/web/server.d.ts +18 -0
- package/dist/core/web/server.js +122 -0
- package/dist/core/web/server.js.map +1 -0
- package/dist/core/web/static.d.ts +18 -0
- package/dist/core/web/static.js +122 -0
- package/dist/core/web/static.js.map +1 -0
- package/dist/tui/app.d.ts +33 -0
- package/dist/tui/app.js +350 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/render.d.ts +20 -0
- package/dist/tui/render.js +558 -0
- package/dist/tui/render.js.map +1 -0
- package/package.json +80 -0
- package/schema/config.schema.json +223 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/mcp-registry.ts — MCP server discovery registry.
|
|
3
|
+
*
|
|
4
|
+
* Scans known config file locations on this machine, extracts mcpServers
|
|
5
|
+
* entries, deduplicates by name (first occurrence wins), and returns a
|
|
6
|
+
* typed McpRegistry. Never throws — unreadable or malformed files are
|
|
7
|
+
* silently skipped. Never prints or returns real env values; use
|
|
8
|
+
* redactEnv() before displaying any spec.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Public API
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* The known config file paths discovery scans, in scan order. Absolute paths:
|
|
18
|
+
* ~/.claude.json, ~/.claude/settings.json, ~/.mcp.json,
|
|
19
|
+
* ~/.ashlrcode/settings.json, and ashlr-workbench agent settings.
|
|
20
|
+
*/
|
|
21
|
+
export function knownConfigPaths() {
|
|
22
|
+
const home = homedir();
|
|
23
|
+
return [
|
|
24
|
+
join(home, '.claude.json'),
|
|
25
|
+
join(home, '.claude', 'settings.json'),
|
|
26
|
+
join(home, '.mcp.json'),
|
|
27
|
+
join(home, '.ashlrcode', 'settings.json'),
|
|
28
|
+
// ashlr-workbench agent settings (two candidate locations)
|
|
29
|
+
join(home, '.ashlr-workbench', 'settings.json'),
|
|
30
|
+
join(home, '.aw', 'settings.json'),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Discover MCP servers already configured on this machine.
|
|
35
|
+
*
|
|
36
|
+
* Reads the known config paths (see knownConfigPaths), parses each
|
|
37
|
+
* `mcpServers` object ({ <name>: { command, args, env? } }), and returns
|
|
38
|
+
* deduped specs (dedupe by `name`; first occurrence wins, stable order).
|
|
39
|
+
* Recognises the ashlr-plugin server (name "ashlr") and phantom
|
|
40
|
+
* (name "phantom-secrets") when present.
|
|
41
|
+
*
|
|
42
|
+
* Never throws: unreadable/malformed configs are skipped silently.
|
|
43
|
+
* When producing ANY printed/displayed form, redact every env value to '<set>'
|
|
44
|
+
* via redactEnv().
|
|
45
|
+
*/
|
|
46
|
+
export function discoverMcpServers(paths) {
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
const servers = [];
|
|
49
|
+
// Paths may be injected (tests / callers that want a hermetic scan); default
|
|
50
|
+
// to the machine's known config locations.
|
|
51
|
+
const configPaths = paths ?? knownConfigPaths();
|
|
52
|
+
for (const configPath of configPaths) {
|
|
53
|
+
if (!existsSync(configPath))
|
|
54
|
+
continue;
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
58
|
+
parsed = JSON.parse(raw);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Unreadable or malformed — skip silently per contract.
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Extract specs from all mcpServers objects in this file.
|
|
65
|
+
const specsFromFile = extractSpecsFromConfig(parsed, configPath);
|
|
66
|
+
for (const spec of specsFromFile) {
|
|
67
|
+
if (!seen.has(spec.name)) {
|
|
68
|
+
seen.add(spec.name);
|
|
69
|
+
servers.push(spec);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { servers };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Return a copy of the spec with all env values replaced by the literal
|
|
77
|
+
* string '<set>'. Use this before printing or serialising for display.
|
|
78
|
+
* Non-env fields are returned unchanged.
|
|
79
|
+
*/
|
|
80
|
+
export function redactEnv(spec) {
|
|
81
|
+
if (!spec.env || Object.keys(spec.env).length === 0) {
|
|
82
|
+
return spec;
|
|
83
|
+
}
|
|
84
|
+
const redacted = {};
|
|
85
|
+
for (const key of Object.keys(spec.env)) {
|
|
86
|
+
redacted[key] = '<set>';
|
|
87
|
+
}
|
|
88
|
+
return { ...spec, env: redacted };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Extract McpServerSpec[] from a parsed config object.
|
|
92
|
+
*
|
|
93
|
+
* Handles two shapes Claude Code uses:
|
|
94
|
+
* 1. Top-level `mcpServers` object.
|
|
95
|
+
* 2. `projects[*].mcpServers` per-project objects.
|
|
96
|
+
*
|
|
97
|
+
* Also handles a flat top-level object that IS itself a server map
|
|
98
|
+
* (some minimal configs omit the `mcpServers` wrapper).
|
|
99
|
+
*/
|
|
100
|
+
function extractSpecsFromConfig(parsed, source) {
|
|
101
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const obj = parsed;
|
|
105
|
+
const specs = [];
|
|
106
|
+
// Shape 1: top-level mcpServers
|
|
107
|
+
if (obj['mcpServers'] !== undefined) {
|
|
108
|
+
const fromTop = specsFromMcpServersMap(obj['mcpServers'], source);
|
|
109
|
+
specs.push(...fromTop);
|
|
110
|
+
}
|
|
111
|
+
// Shape 2: projects[*].mcpServers
|
|
112
|
+
const projects = obj['projects'];
|
|
113
|
+
if (projects !== null && typeof projects === 'object' && !Array.isArray(projects)) {
|
|
114
|
+
const projectsMap = projects;
|
|
115
|
+
for (const projectKey of Object.keys(projectsMap)) {
|
|
116
|
+
const project = projectsMap[projectKey];
|
|
117
|
+
if (project !== null && typeof project === 'object' && !Array.isArray(project)) {
|
|
118
|
+
const proj = project;
|
|
119
|
+
if (proj['mcpServers'] !== undefined) {
|
|
120
|
+
const fromProject = specsFromMcpServersMap(proj['mcpServers'], source);
|
|
121
|
+
specs.push(...fromProject);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return specs;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Convert a raw `mcpServers` value (expected: Record<string, RawServerEntry>)
|
|
130
|
+
* into McpServerSpec[]. Skips any entry that lacks a usable `command` string.
|
|
131
|
+
*/
|
|
132
|
+
function specsFromMcpServersMap(raw, source) {
|
|
133
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const map = raw;
|
|
137
|
+
const specs = [];
|
|
138
|
+
for (const name of Object.keys(map)) {
|
|
139
|
+
const entry = map[name];
|
|
140
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const e = entry;
|
|
144
|
+
// command is required and must be a string.
|
|
145
|
+
if (typeof e.command !== 'string' || e.command.trim() === '') {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// args: must be string[], default [].
|
|
149
|
+
let args = [];
|
|
150
|
+
if (Array.isArray(e.args)) {
|
|
151
|
+
args = e.args.filter((a) => typeof a === 'string');
|
|
152
|
+
}
|
|
153
|
+
// env: must be Record<string, string>, default undefined.
|
|
154
|
+
let env;
|
|
155
|
+
if (e.env !== null && typeof e.env === 'object' && !Array.isArray(e.env)) {
|
|
156
|
+
const rawEnv = e.env;
|
|
157
|
+
const safeEnv = {};
|
|
158
|
+
for (const k of Object.keys(rawEnv)) {
|
|
159
|
+
if (typeof rawEnv[k] === 'string') {
|
|
160
|
+
safeEnv[k] = rawEnv[k];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (Object.keys(safeEnv).length > 0) {
|
|
164
|
+
env = safeEnv;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const spec = {
|
|
168
|
+
name,
|
|
169
|
+
command: e.command.trim(),
|
|
170
|
+
args,
|
|
171
|
+
source,
|
|
172
|
+
};
|
|
173
|
+
if (env !== undefined) {
|
|
174
|
+
spec.env = env;
|
|
175
|
+
}
|
|
176
|
+
specs.push(spec);
|
|
177
|
+
}
|
|
178
|
+
return specs;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=mcp-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-registry.js","sourceRoot":"","sources":["../../src/core/mcp-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* budget-alert.ts — evaluate a budget cap against spent totals.
|
|
3
|
+
*
|
|
4
|
+
* Pure function, no I/O, never throws.
|
|
5
|
+
* Privacy: reads only numeric caps from cfg.telemetry — no message content.
|
|
6
|
+
*/
|
|
7
|
+
import type { AshlrConfig, BudgetAlert } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Evaluate the budget cap for a window against spent totals.
|
|
10
|
+
*
|
|
11
|
+
* Caps are read from cfg.telemetry.budgetUsd / budgetTokens (either may be unset).
|
|
12
|
+
* level: 'over' when any cap is exceeded; 'warn' when >= 80% of any cap; else 'ok'.
|
|
13
|
+
* message is a concise human-readable status. Never throws.
|
|
14
|
+
*/
|
|
15
|
+
export declare function evalBudget(totals: {
|
|
16
|
+
spentUsd: number;
|
|
17
|
+
spentTokens: number;
|
|
18
|
+
}, cfg: AshlrConfig, window: string): BudgetAlert;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* budget-alert.ts — evaluate a budget cap against spent totals.
|
|
3
|
+
*
|
|
4
|
+
* Pure function, no I/O, never throws.
|
|
5
|
+
* Privacy: reads only numeric caps from cfg.telemetry — no message content.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Evaluate the budget cap for a window against spent totals.
|
|
9
|
+
*
|
|
10
|
+
* Caps are read from cfg.telemetry.budgetUsd / budgetTokens (either may be unset).
|
|
11
|
+
* level: 'over' when any cap is exceeded; 'warn' when >= 80% of any cap; else 'ok'.
|
|
12
|
+
* message is a concise human-readable status. Never throws.
|
|
13
|
+
*/
|
|
14
|
+
export function evalBudget(totals, cfg, window) {
|
|
15
|
+
try {
|
|
16
|
+
const capUsd = cfg.telemetry?.budgetUsd ?? null;
|
|
17
|
+
const capTokens = cfg.telemetry?.budgetTokens ?? null;
|
|
18
|
+
const { spentUsd, spentTokens } = totals;
|
|
19
|
+
// No caps configured — always ok
|
|
20
|
+
if (capUsd === null && capTokens === null) {
|
|
21
|
+
return {
|
|
22
|
+
level: "ok",
|
|
23
|
+
window,
|
|
24
|
+
spentUsd,
|
|
25
|
+
capUsd: null,
|
|
26
|
+
spentTokens,
|
|
27
|
+
capTokens: null,
|
|
28
|
+
message: "No budget cap configured.",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Determine level: 'over' if any cap exceeded, 'warn' if >= 80%, else 'ok'
|
|
32
|
+
const usdOver = capUsd !== null && spentUsd >= capUsd;
|
|
33
|
+
const tokensOver = capTokens !== null && spentTokens >= capTokens;
|
|
34
|
+
const usdWarn = capUsd !== null && !usdOver && spentUsd >= capUsd * 0.8;
|
|
35
|
+
const tokensWarn = capTokens !== null && !tokensOver && spentTokens >= capTokens * 0.8;
|
|
36
|
+
let level;
|
|
37
|
+
if (usdOver || tokensOver) {
|
|
38
|
+
level = "over";
|
|
39
|
+
}
|
|
40
|
+
else if (usdWarn || tokensWarn) {
|
|
41
|
+
level = "warn";
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
level = "ok";
|
|
45
|
+
}
|
|
46
|
+
// Build message focusing on whichever cap is most relevant (tightest %)
|
|
47
|
+
const parts = [];
|
|
48
|
+
if (capUsd !== null) {
|
|
49
|
+
const pct = capUsd > 0 ? Math.round((spentUsd / capUsd) * 100) : 0;
|
|
50
|
+
parts.push(`$${spentUsd.toFixed(2)} of $${capUsd.toFixed(2)} (${pct}%) this ${window}`);
|
|
51
|
+
}
|
|
52
|
+
if (capTokens !== null) {
|
|
53
|
+
const pct = capTokens > 0 ? Math.round((spentTokens / capTokens) * 100) : 0;
|
|
54
|
+
const spentK = formatTokens(spentTokens);
|
|
55
|
+
const capK = formatTokens(capTokens);
|
|
56
|
+
parts.push(`${spentK} of ${capK} tokens (${pct}%) this ${window}`);
|
|
57
|
+
}
|
|
58
|
+
const prefix = level === "over" ? "Over budget: " : level === "warn" ? "Warning: " : "";
|
|
59
|
+
return {
|
|
60
|
+
level,
|
|
61
|
+
window,
|
|
62
|
+
spentUsd,
|
|
63
|
+
capUsd,
|
|
64
|
+
spentTokens,
|
|
65
|
+
capTokens,
|
|
66
|
+
message: prefix + parts.join("; "),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Never throws — return safe fallback
|
|
71
|
+
return {
|
|
72
|
+
level: "ok",
|
|
73
|
+
window,
|
|
74
|
+
spentUsd: totals.spentUsd,
|
|
75
|
+
capUsd: null,
|
|
76
|
+
spentTokens: totals.spentTokens,
|
|
77
|
+
capTokens: null,
|
|
78
|
+
message: "Budget evaluation unavailable.",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Format a token count as a compact string (e.g. 1500000 -> '1.5M', 42000 -> '42K'). */
|
|
83
|
+
function formatTokens(n) {
|
|
84
|
+
if (n >= 1_000_000)
|
|
85
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
86
|
+
if (n >= 1_000)
|
|
87
|
+
return `${Math.round(n / 1_000)}K`;
|
|
88
|
+
return String(n);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=budget-alert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget-alert.js","sourceRoot":"","sources":["../../../src/core/observability/budget-alert.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M32: pre-flight cost estimation — predict tokens / cost / duration for a
|
|
3
|
+
* `run` or `swarm` BEFORE committing, from persisted history.
|
|
4
|
+
*
|
|
5
|
+
* Method: percentile statistics (p25 / median / p75) over completed history
|
|
6
|
+
* (~/.ashlr/runs/, ~/.ashlr/swarms/), lightly weighted toward goals that share
|
|
7
|
+
* keywords with the new goal. The upper bound is clamped to the requested
|
|
8
|
+
* budget. Cloud cost uses the same reference pricing as forecast.ts; local
|
|
9
|
+
* runs cost $0 (the would-be-cloud figure is reported for context).
|
|
10
|
+
*
|
|
11
|
+
* Contract: PURE READ-ONLY (never writes, never scans repos, never calls a
|
|
12
|
+
* model); NEVER throws — empty/corrupt history yields a zeroed estimate with
|
|
13
|
+
* confidence 'low'.
|
|
14
|
+
*/
|
|
15
|
+
import type { AshlrConfig, RunEstimate } from '../types.js';
|
|
16
|
+
/** Estimate a `run` from run history. Never throws. */
|
|
17
|
+
export declare function estimateRun(goal: string, opts: {
|
|
18
|
+
maxTokens?: number;
|
|
19
|
+
allowCloud?: boolean;
|
|
20
|
+
}, _cfg: AshlrConfig): Promise<RunEstimate>;
|
|
21
|
+
/** Estimate a `swarm` from swarm history. Never throws. */
|
|
22
|
+
export declare function estimateSwarm(goal: string, opts: {
|
|
23
|
+
maxTokens?: number;
|
|
24
|
+
allowCloud?: boolean;
|
|
25
|
+
}, _cfg: AshlrConfig): Promise<RunEstimate>;
|
|
26
|
+
/** Render an estimate as a compact human block (shared by run/swarm CLIs). */
|
|
27
|
+
export declare function renderEstimate(e: RunEstimate): string;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M32: pre-flight cost estimation — predict tokens / cost / duration for a
|
|
3
|
+
* `run` or `swarm` BEFORE committing, from persisted history.
|
|
4
|
+
*
|
|
5
|
+
* Method: percentile statistics (p25 / median / p75) over completed history
|
|
6
|
+
* (~/.ashlr/runs/, ~/.ashlr/swarms/), lightly weighted toward goals that share
|
|
7
|
+
* keywords with the new goal. The upper bound is clamped to the requested
|
|
8
|
+
* budget. Cloud cost uses the same reference pricing as forecast.ts; local
|
|
9
|
+
* runs cost $0 (the would-be-cloud figure is reported for context).
|
|
10
|
+
*
|
|
11
|
+
* Contract: PURE READ-ONLY (never writes, never scans repos, never calls a
|
|
12
|
+
* model); NEVER throws — empty/corrupt history yields a zeroed estimate with
|
|
13
|
+
* confidence 'low'.
|
|
14
|
+
*/
|
|
15
|
+
/** Reference cloud pricing ($/M tokens) — mirrors forecast.ts. */
|
|
16
|
+
const CLOUD_REF_PRICE_IN = 3.0;
|
|
17
|
+
const CLOUD_REF_PRICE_OUT = 15.0;
|
|
18
|
+
/** Minimum samples for medium/high confidence. */
|
|
19
|
+
const MEDIUM_SAMPLES = 3;
|
|
20
|
+
const HIGH_SAMPLES = 10;
|
|
21
|
+
function keywordSet(text) {
|
|
22
|
+
return new Set(text
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.split(/[^a-z0-9]+/)
|
|
25
|
+
.filter((w) => w.length > 3));
|
|
26
|
+
}
|
|
27
|
+
/** Jaccard-ish goal similarity in [0,1]. */
|
|
28
|
+
function similarity(a, b) {
|
|
29
|
+
if (a.size === 0 || b.size === 0)
|
|
30
|
+
return 0;
|
|
31
|
+
let shared = 0;
|
|
32
|
+
for (const w of a)
|
|
33
|
+
if (b.has(w))
|
|
34
|
+
shared++;
|
|
35
|
+
return shared / Math.max(a.size, b.size);
|
|
36
|
+
}
|
|
37
|
+
function percentile(sorted, p) {
|
|
38
|
+
if (sorted.length === 0)
|
|
39
|
+
return 0;
|
|
40
|
+
const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor((p / 100) * sorted.length)));
|
|
41
|
+
return sorted[idx] ?? 0;
|
|
42
|
+
}
|
|
43
|
+
function cloudCost(tokensIn, tokensOut) {
|
|
44
|
+
return ((Math.max(0, tokensIn) / 1_000_000) * CLOUD_REF_PRICE_IN +
|
|
45
|
+
(Math.max(0, tokensOut) / 1_000_000) * CLOUD_REF_PRICE_OUT);
|
|
46
|
+
}
|
|
47
|
+
function buildEstimate(kind, goal, samples, opts) {
|
|
48
|
+
const zeroed = {
|
|
49
|
+
kind,
|
|
50
|
+
goal,
|
|
51
|
+
sampleSize: 0,
|
|
52
|
+
confidence: 'low',
|
|
53
|
+
tokens: { p25: 0, median: 0, p75: 0 },
|
|
54
|
+
steps: { p25: 0, median: 0, p75: 0 },
|
|
55
|
+
estCostUsd: { p25: 0, median: 0, p75: 0 },
|
|
56
|
+
wouldBeCloudUsd: 0,
|
|
57
|
+
durationMs: { p25: 0, median: 0, p75: 0 },
|
|
58
|
+
budgetClamped: false,
|
|
59
|
+
generatedAt: new Date().toISOString(),
|
|
60
|
+
};
|
|
61
|
+
if (samples.length === 0)
|
|
62
|
+
return zeroed;
|
|
63
|
+
// Similarity weighting: when ≥3 samples share keywords with the goal, use
|
|
64
|
+
// ONLY those (a focused prior beats a diffuse one); otherwise use everything.
|
|
65
|
+
const goalWords = keywordSet(goal);
|
|
66
|
+
const similar = samples.filter((s) => similarity(goalWords, keywordSet(s.goal)) > 0.15);
|
|
67
|
+
const used = similar.length >= MEDIUM_SAMPLES ? similar : samples;
|
|
68
|
+
const tokens = used.map((s) => s.tokens).sort((a, b) => a - b);
|
|
69
|
+
const steps = used.map((s) => s.steps).sort((a, b) => a - b);
|
|
70
|
+
const costs = used.map((s) => s.estCostUsd).sort((a, b) => a - b);
|
|
71
|
+
const durations = used.map((s) => s.durationMs).filter((d) => d > 0).sort((a, b) => a - b);
|
|
72
|
+
let p25t = percentile(tokens, 25);
|
|
73
|
+
let p50t = percentile(tokens, 50);
|
|
74
|
+
let p75t = percentile(tokens, 75);
|
|
75
|
+
// Clamp to the requested budget (a run can never exceed its hard cap).
|
|
76
|
+
let budgetClamped = false;
|
|
77
|
+
const cap = opts.maxTokens;
|
|
78
|
+
if (cap !== undefined && cap > 0) {
|
|
79
|
+
if (p75t > cap) {
|
|
80
|
+
p75t = cap;
|
|
81
|
+
budgetClamped = true;
|
|
82
|
+
}
|
|
83
|
+
if (p50t > cap) {
|
|
84
|
+
p50t = cap;
|
|
85
|
+
budgetClamped = true;
|
|
86
|
+
}
|
|
87
|
+
if (p25t > cap) {
|
|
88
|
+
p25t = cap;
|
|
89
|
+
budgetClamped = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Median in/out split from history (for the would-be-cloud figure).
|
|
93
|
+
const ratioSum = used.reduce((acc, s) => {
|
|
94
|
+
acc.in += s.tokensIn;
|
|
95
|
+
acc.out += s.tokensOut;
|
|
96
|
+
return acc;
|
|
97
|
+
}, { in: 0, out: 0 });
|
|
98
|
+
const total = ratioSum.in + ratioSum.out;
|
|
99
|
+
const inShare = total > 0 ? ratioSum.in / total : 0.7;
|
|
100
|
+
const confidence = used.length >= HIGH_SAMPLES ? 'high' : used.length >= MEDIUM_SAMPLES ? 'medium' : 'low';
|
|
101
|
+
return {
|
|
102
|
+
kind,
|
|
103
|
+
goal,
|
|
104
|
+
sampleSize: used.length,
|
|
105
|
+
confidence,
|
|
106
|
+
tokens: { p25: p25t, median: p50t, p75: p75t },
|
|
107
|
+
steps: { p25: percentile(steps, 25), median: percentile(steps, 50), p75: percentile(steps, 75) },
|
|
108
|
+
estCostUsd: {
|
|
109
|
+
p25: percentile(costs, 25),
|
|
110
|
+
median: percentile(costs, 50),
|
|
111
|
+
p75: percentile(costs, 75),
|
|
112
|
+
},
|
|
113
|
+
wouldBeCloudUsd: cloudCost(p50t * inShare, p50t * (1 - inShare)),
|
|
114
|
+
durationMs: {
|
|
115
|
+
p25: percentile(durations, 25),
|
|
116
|
+
median: percentile(durations, 50),
|
|
117
|
+
p75: percentile(durations, 75),
|
|
118
|
+
},
|
|
119
|
+
budgetClamped,
|
|
120
|
+
generatedAt: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Estimate a `run` from run history. Never throws. */
|
|
124
|
+
export async function estimateRun(goal, opts, _cfg) {
|
|
125
|
+
let samples = [];
|
|
126
|
+
try {
|
|
127
|
+
const { listRuns } = await import('../run/orchestrator.js');
|
|
128
|
+
samples = listRuns()
|
|
129
|
+
.filter((r) => r.status === 'done' || r.status === 'failed')
|
|
130
|
+
.map((r) => ({
|
|
131
|
+
goal: r.goal,
|
|
132
|
+
tokens: r.usage.tokensIn + r.usage.tokensOut,
|
|
133
|
+
tokensIn: r.usage.tokensIn,
|
|
134
|
+
tokensOut: r.usage.tokensOut,
|
|
135
|
+
steps: r.usage.steps,
|
|
136
|
+
estCostUsd: r.usage.estCostUsd,
|
|
137
|
+
durationMs: Date.parse(r.updatedAt) - Date.parse(r.createdAt) || 0,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
samples = [];
|
|
142
|
+
}
|
|
143
|
+
return buildEstimate('run', goal, samples, opts);
|
|
144
|
+
}
|
|
145
|
+
/** Estimate a `swarm` from swarm history. Never throws. */
|
|
146
|
+
export async function estimateSwarm(goal, opts, _cfg) {
|
|
147
|
+
let samples = [];
|
|
148
|
+
try {
|
|
149
|
+
const { listSwarms } = await import('../swarm/store.js');
|
|
150
|
+
samples = listSwarms()
|
|
151
|
+
.filter((s) => s.status === 'done' || s.status === 'failed' || s.status === 'aborted')
|
|
152
|
+
.map((s) => ({
|
|
153
|
+
goal: s.goal,
|
|
154
|
+
tokens: s.usage.tokensIn + s.usage.tokensOut,
|
|
155
|
+
tokensIn: s.usage.tokensIn,
|
|
156
|
+
tokensOut: s.usage.tokensOut,
|
|
157
|
+
steps: s.usage.steps,
|
|
158
|
+
estCostUsd: s.usage.estCostUsd,
|
|
159
|
+
durationMs: Date.parse(s.updatedAt) - Date.parse(s.createdAt) || 0,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
samples = [];
|
|
164
|
+
}
|
|
165
|
+
return buildEstimate('swarm', goal, samples, opts);
|
|
166
|
+
}
|
|
167
|
+
/** Render an estimate as a compact human block (shared by run/swarm CLIs). */
|
|
168
|
+
export function renderEstimate(e) {
|
|
169
|
+
const fmtTok = (n) => n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(Math.round(n));
|
|
170
|
+
const fmtDur = (ms) => {
|
|
171
|
+
if (ms <= 0)
|
|
172
|
+
return '—';
|
|
173
|
+
const s = Math.round(ms / 1000);
|
|
174
|
+
return s >= 60 ? `${Math.floor(s / 60)}m${s % 60}s` : `${s}s`;
|
|
175
|
+
};
|
|
176
|
+
const lines = [
|
|
177
|
+
`estimate (${e.kind}) — confidence: ${e.confidence} (${e.sampleSize} similar ${e.kind}${e.sampleSize === 1 ? '' : 's'})`,
|
|
178
|
+
` tokens p25 ${fmtTok(e.tokens.p25)} · median ${fmtTok(e.tokens.median)} · p75 ${fmtTok(e.tokens.p75)}${e.budgetClamped ? ' (clamped to budget)' : ''}`,
|
|
179
|
+
` steps p25 ${Math.round(e.steps.p25)} · median ${Math.round(e.steps.median)} · p75 ${Math.round(e.steps.p75)}`,
|
|
180
|
+
` cost median $${e.estCostUsd.median.toFixed(4)} (local $0; would-be-cloud ≈ $${e.wouldBeCloudUsd.toFixed(4)})`,
|
|
181
|
+
` duration p25 ${fmtDur(e.durationMs.p25)} · median ${fmtDur(e.durationMs.median)} · p75 ${fmtDur(e.durationMs.p75)}`,
|
|
182
|
+
];
|
|
183
|
+
if (e.sampleSize === 0) {
|
|
184
|
+
lines.push(' (no history yet — run a few goals first for meaningful estimates)');
|
|
185
|
+
}
|
|
186
|
+
return lines.join('\n');
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=estimate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate.js","sourceRoot":"","sources":["../../../src/core/observability/estimate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forecast.ts — cost attribution + forward forecast for a recent usage window.
|
|
3
|
+
*
|
|
4
|
+
* M15: buildForecast reuses buildRollup for actual spend and wouldBeCloudCost
|
|
5
|
+
* (from router.ts) to compute local savings. All numbers are ESTIMATES, clearly
|
|
6
|
+
* labeled. Never throws — falls back to zeroed CostForecast on any error.
|
|
7
|
+
*/
|
|
8
|
+
import type { AshlrConfig, CostForecast } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Build a CostForecast for the given window.
|
|
11
|
+
*
|
|
12
|
+
* - `spentUsd`: actual cost from the rollup (local providers contribute $0).
|
|
13
|
+
* - `localSavingsUsd`: estimated cloud cost for tokens served by LOCAL models —
|
|
14
|
+
* what you WOULD have paid if those same tokens ran on cloud.
|
|
15
|
+
* - `projectedMonthlyUsd`: the window's daily average spend × 30 days.
|
|
16
|
+
*
|
|
17
|
+
* All figures are ESTIMATES. Never throws — returns a zeroed forecast on error.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildForecast(window: '7d' | '30d', cfg: AshlrConfig): CostForecast;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forecast.ts — cost attribution + forward forecast for a recent usage window.
|
|
3
|
+
*
|
|
4
|
+
* M15: buildForecast reuses buildRollup for actual spend and wouldBeCloudCost
|
|
5
|
+
* (from router.ts) to compute local savings. All numbers are ESTIMATES, clearly
|
|
6
|
+
* labeled. Never throws — falls back to zeroed CostForecast on any error.
|
|
7
|
+
*/
|
|
8
|
+
import { buildRollup, windowToMs, isLocalProviderModel } from './rollup.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Local provider detection
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when the model id belongs to a local (zero-cost) provider.
|
|
14
|
+
*
|
|
15
|
+
* Reuses rollup.ts `isLocalProviderModel` (built on the SAME `modelToProviderKey`
|
|
16
|
+
* derivation rollup uses to compute cost) so the local/cloud split is identical
|
|
17
|
+
* across rollup, savings, and forecast — instead of a fragile raw substring test
|
|
18
|
+
* that only matched the run-source label format by coincidence.
|
|
19
|
+
*/
|
|
20
|
+
const isLocalModel = isLocalProviderModel;
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Cloud cost reference: representative cloud pricing used for savings estimate.
|
|
23
|
+
// Uses claude/anthropic as the reference cloud tier (conservative mid estimate).
|
|
24
|
+
// These are ESTIMATES only, never billed — reuse the same constants from budget.ts
|
|
25
|
+
// rather than re-importing to avoid circular deps (budget.ts is a pure util).
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/** $/M input tokens — conservative mid-tier cloud reference (anthropic claude). */
|
|
28
|
+
const CLOUD_REF_PRICE_IN = 3.0;
|
|
29
|
+
/** $/M output tokens — conservative mid-tier cloud reference (anthropic claude). */
|
|
30
|
+
const CLOUD_REF_PRICE_OUT = 15.0;
|
|
31
|
+
/**
|
|
32
|
+
* Estimate what `tokensIn`/`tokensOut` would have cost on a representative
|
|
33
|
+
* cloud model (conservative anthropic mid-tier).
|
|
34
|
+
*
|
|
35
|
+
* This is the same calculation as `wouldBeCloudCost` in router.ts — duplicated
|
|
36
|
+
* here to avoid a circular dependency (forecast -> router -> budget -> forecast).
|
|
37
|
+
* The function intentionally stays private; external callers use router.ts.
|
|
38
|
+
*
|
|
39
|
+
* Returns 0 when token counts are non-positive. Always >= 0. Clearly an estimate.
|
|
40
|
+
*/
|
|
41
|
+
function cloudRefCost(tokensIn, tokensOut) {
|
|
42
|
+
if (tokensIn <= 0 && tokensOut <= 0)
|
|
43
|
+
return 0;
|
|
44
|
+
const safeIn = Math.max(0, tokensIn);
|
|
45
|
+
const safeOut = Math.max(0, tokensOut);
|
|
46
|
+
return (safeIn / 1_000_000) * CLOUD_REF_PRICE_IN +
|
|
47
|
+
(safeOut / 1_000_000) * CLOUD_REF_PRICE_OUT;
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// buildForecast
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* Build a CostForecast for the given window.
|
|
54
|
+
*
|
|
55
|
+
* - `spentUsd`: actual cost from the rollup (local providers contribute $0).
|
|
56
|
+
* - `localSavingsUsd`: estimated cloud cost for tokens served by LOCAL models —
|
|
57
|
+
* what you WOULD have paid if those same tokens ran on cloud.
|
|
58
|
+
* - `projectedMonthlyUsd`: the window's daily average spend × 30 days.
|
|
59
|
+
*
|
|
60
|
+
* All figures are ESTIMATES. Never throws — returns a zeroed forecast on error.
|
|
61
|
+
*/
|
|
62
|
+
export function buildForecast(window, cfg) {
|
|
63
|
+
try {
|
|
64
|
+
const rollup = buildRollup(window, cfg);
|
|
65
|
+
// Actual spend is already computed in the rollup totals.
|
|
66
|
+
const spentUsd = Math.max(0, rollup.totals.estCostUsd);
|
|
67
|
+
// ── Local savings ───────────────────────────────────────────────────────
|
|
68
|
+
// Sum up the cloud-equivalent cost for every local model's token usage.
|
|
69
|
+
// rollup.byModel lists per-model aggregates; we identify local models and
|
|
70
|
+
// compute what cloud would have charged for the same token volume.
|
|
71
|
+
let localSavingsUsd = 0;
|
|
72
|
+
for (const mu of rollup.byModel) {
|
|
73
|
+
if (isLocalModel(mu.model)) {
|
|
74
|
+
localSavingsUsd += cloudRefCost(mu.tokensIn, mu.tokensOut);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
localSavingsUsd = Math.max(0, localSavingsUsd);
|
|
78
|
+
// ── Monthly projection ──────────────────────────────────────────────────
|
|
79
|
+
// Scale the window's spend to a 30-day equivalent.
|
|
80
|
+
// windowToMs is already exported from rollup.ts.
|
|
81
|
+
const windowDays = windowToMs(window) / 86_400_000; // exact days in window
|
|
82
|
+
const dailyRate = windowDays > 0 ? spentUsd / windowDays : 0;
|
|
83
|
+
const projectedMonthlyUsd = Math.max(0, dailyRate * 30);
|
|
84
|
+
return {
|
|
85
|
+
window,
|
|
86
|
+
spentUsd,
|
|
87
|
+
localSavingsUsd,
|
|
88
|
+
projectedMonthlyUsd,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Never throw — return a safe zeroed forecast on any error.
|
|
93
|
+
return {
|
|
94
|
+
window,
|
|
95
|
+
spentUsd: 0,
|
|
96
|
+
localSavingsUsd: 0,
|
|
97
|
+
projectedMonthlyUsd: 0,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=forecast.js.map
|