runcap 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kirill D.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # Runcap
2
+
3
+ [![CI](https://github.com/kirder24-code/ai-agent-manager/actions/workflows/ci.yml/badge.svg)](https://github.com/kirder24-code/ai-agent-manager/actions/workflows/ci.yml)
4
+
5
+ **Know what your coding agent will cost before you build it — and set a hard ceiling so it never surprises you.**
6
+
7
+ Runcap estimates the cost of an agent run as a range, enforces a hard spend ceiling that physically stops the run, and when the agent gets stuck it hands you the exact rescue prompt. Free, MIT, 100% local. Your code and tokens never touch a server.
8
+
9
+ > Every other tool here is a rear-view mirror — it shows you the bill *after* you paid it. Runcap estimates the bill *before* you start and caps it. It is a circuit breaker, not a dashboard.
10
+
11
+ ## Why
12
+
13
+ Multi-agent coding runs burn roughly **15x more tokens** than a single chat ([Anthropic engineering](https://www.anthropic.com/engineering/built-multi-agent-research-system)). Agents loop on the same error, rewrite plans, and hand you a confident summary while the task is not actually done. You find out what it cost when the invoice — or the subscription limit — arrives.
14
+
15
+ Observability tools (Langfuse, Helicone, LangSmith, AgentOps) measure the past. Gateways (LiteLLM, Portkey, OpenRouter) route the present. None of them stop the spend *before* it happens. Runcap does the one thing the rear-view mirror can't:
16
+
17
+ ```text
18
+ estimate before build → cap during run → rescue when stuck → verify it finished
19
+ ```
20
+
21
+ ## The honest claim
22
+
23
+ Runcap does **not** promise an exact cost oracle. Agent trajectories are stochastic — nobody, including the model labs, can predict the exact token count of a run. So Runcap gives you a **range plus a hard cap**:
24
+
25
+ > "This build is roughly $3–7. Cap it at $10." — then it kills the run the second it hits the ceiling.
26
+
27
+ The range is the headline. The hard cap is the product.
28
+
29
+ ## 60-second demo
30
+
31
+ No API key required.
32
+
33
+ ```bash
34
+ git clone https://github.com/kirder24-code/ai-agent-manager.git
35
+ cd ai-agent-manager
36
+ npm run setup
37
+ npm run demo
38
+ ```
39
+
40
+ **1. Catch a too-broad request before it spends anything:**
41
+
42
+ ```text
43
+ $ runcap preflight -- claude "build the full mobile app with auth payments and production deploy"
44
+
45
+ Preflight: claude build the full mobile app with auth payments and production deploy
46
+ Scope risk: high
47
+ Fuel: 24% (medium confidence)
48
+ Recommendation: Do not launch as one broad mission. Split into one vertical slice with a verification command.
49
+ ```
50
+
51
+ **2. Wrap a run — and get a rescue prompt the moment it gets stuck:**
52
+
53
+ ```text
54
+ $ runcap run --label demo -- npm run build
55
+
56
+ Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/components' ...
57
+
58
+ Runcap mission: 20260601T221531-demo-ff42c0a
59
+ Status: stuck (medium confidence)
60
+ Exit code: 1
61
+ Changed files: 0
62
+ Parsed errors: 1
63
+ Primary recommendation: Resolve missing import before continuing feature work
64
+ ```
65
+
66
+ The rescue report hands back a copyable prompt:
67
+
68
+ ```text
69
+ Do not continue broad implementation. Diagnose this missing module first:
70
+ Cannot find package '@/components'. Check package.json, tsconfig paths, and
71
+ the latest git diff. Make the smallest change that resolves the import,
72
+ then run the failing command again.
73
+ ```
74
+
75
+ ![Runcap dashboard rescue notice](docs/assets/dashboard-rescue-notice.png)
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ npm install -g runcap # exposes `runcap` (and `aim` as a legacy alias)
81
+ ```
82
+
83
+ Or run from source with `node ./bin/runcap.mjs <command>`.
84
+
85
+ ## Core commands
86
+
87
+ ```bash
88
+ runcap plan --fuel 24 -- "build a small auth feature and verify it" # range + recommended cap, before you spend
89
+ runcap preflight -- claude "build a full SaaS app" # is this prompt too broad?
90
+ runcap run --label fix -- claude "fix one failing check. stop if blocked." # wrap any agent/command
91
+ runcap report # human-readable rescue report
92
+ runcap export # evidence JSON with truth labels
93
+ runcap dashboard # local cockpit at :8791
94
+ runcap gateway # cost-tracking proxy with hard budget cap
95
+ runcap fuel set 24 # calibrate a %-only subscription
96
+ ```
97
+
98
+ ## The hard cap (gateway)
99
+
100
+ Point any OpenAI- or Anthropic-compatible tool at the local gateway. It records real token usage, prices it from a sourced table, and **blocks calls the moment your daily ceiling is hit**.
101
+
102
+ ```bash
103
+ # OpenAI-compatible agents
104
+ OPENAI_API_KEY=sk-... AIM_DAILY_BUDGET_USD=5 runcap gateway
105
+ # then: OPENAI_BASE_URL=http://127.0.0.1:8792/v1
106
+
107
+ # Anthropic-native (Claude Code, /v1/messages)
108
+ ANTHROPIC_API_KEY=sk-ant-... AIM_DAILY_BUDGET_USD=5 runcap gateway
109
+ # then: ANTHROPIC_BASE_URL=http://127.0.0.1:8792/v1
110
+ ```
111
+
112
+ When spend crosses the ceiling, the next call returns `429 budget_guard` instead of money leaving your account. Try it with no key: `runcap gateway --mock`.
113
+
114
+ ## Pricing table
115
+
116
+ Costs are calculated from a sourced multi-provider table — Anthropic (Opus / Sonnet / Haiku) and OpenAI (GPT-5 family + legacy GPT-4), with cache-read and batch discounts handled — labeled with source and verification date. When a model is unknown, Runcap says `unknown_price` rather than guessing.
117
+
118
+ ## Trust model
119
+
120
+ Runcap is built not to fake certainty. Every important output carries a truth label:
121
+
122
+ - `observed` — git diff, exit code, file changes, terminal output;
123
+ - `calculated` — parsed errors, diff hashes, stuck score, cost from the sourced price table;
124
+ - `provider_usage` — token usage returned by the upstream provider;
125
+ - `manual_calibration` — subscription % you entered before/after a run;
126
+ - `unknown` — Runcap cannot honestly know.
127
+
128
+ If it cannot prove something, it says so.
129
+
130
+ ## Pricing (the product, not the tokens)
131
+
132
+ | Tier | Price | What you get |
133
+ |---|---|---|
134
+ | **OSS** (MIT, local) | $0 forever | All local runs, cost estimation, hard cap, run wrapping, stuck detection, rescue prompts, local dashboard. Never crippleware. |
135
+ | **Pro** | $19/mo | Cloud sync across machines, hosted dashboard, estimate-vs-actual trends, shareable reports, alerts on cap breach |
136
+ | **Team** | $49/seat/mo | Shared budget pools, org-wide ceilings, per-project rollups, role-based caps |
137
+
138
+ The local core is free forever. Only persistence, collaboration, and aggregation are paid — the things that only matter once data leaves your laptop.
139
+
140
+ ## Current stage
141
+
142
+ A working local tool, not a hosted SaaS. Ready for: wrapping real Codex / Claude / Cursor sessions, catching stuck agents, and proving rescue prompts save time. Not yet: a hosted cloud platform or a universal observability standard. It is not trying to replace Langfuse or LiteLLM — it does the thing they don't.
143
+
144
+ ## Documentation
145
+
146
+ - [Product status](PRODUCT.md)
147
+ - [Quickstart](docs/quickstart.md)
148
+ - [Roadmap](docs/ROADMAP.md)
149
+ - [Business plan](docs/BUSINESS-PLAN.md)
150
+ - [Integrations](docs/integrations.md)
151
+ - [Trust model](docs/trust-model.md)
152
+
153
+ ---
154
+
155
+ The thesis: **AI agents need managers.**
package/bin/runcap.mjs ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ calibrateFuel,
5
+ doctor,
6
+ exportSnapshot,
7
+ latestMissionId,
8
+ listMissions,
9
+ listPlans,
10
+ planMission,
11
+ preflightMission,
12
+ recordFuel,
13
+ renderReport,
14
+ runMission,
15
+ setupProject,
16
+ startDashboard,
17
+ startGateway,
18
+ showStatus,
19
+ templates
20
+ } from "../src/mission-control.mjs";
21
+
22
+ const args = process.argv.slice(2);
23
+ const command = args[0] ?? "help";
24
+
25
+ function usage() {
26
+ console.log(`Runcap — cap every agent run before it starts
27
+
28
+ Usage:
29
+ runcap run [--label name] [--fuel-before 24] -- <command...>
30
+ runcap plan [--fuel 24] [--quality high|balanced|cheap] -- <goal...>
31
+ runcap plans
32
+ runcap preflight -- <command or prompt...>
33
+ runcap status
34
+ runcap list
35
+ runcap report [mission-id]
36
+ runcap rescue [mission-id]
37
+ runcap export [mission-id]
38
+ runcap templates
39
+ runcap dashboard [--port 8791]
40
+ runcap gateway [--port 8792] [--mock]
41
+ runcap setup
42
+ runcap doctor
43
+ runcap fuel set <percent>
44
+ runcap fuel calibrate <mission-id> <after-percent>
45
+
46
+ Examples:
47
+ runcap run --label auth-fix -- claude "fix the auth bug"
48
+ runcap plan --fuel 24 -- "build a mobile app MVP with auth and deployment"
49
+ runcap run -- npm test
50
+ runcap report
51
+ runcap fuel set 24
52
+
53
+ (\`aim\` works as a legacy alias for every command.)
54
+ `);
55
+ }
56
+
57
+ function takeOption(input, name) {
58
+ const index = input.indexOf(name);
59
+ if (index === -1) return undefined;
60
+ const value = input[index + 1];
61
+ input.splice(index, 2);
62
+ return value;
63
+ }
64
+
65
+ try {
66
+ if (command === "help" || command === "--help" || command === "-h") {
67
+ usage();
68
+ } else if (command === "run") {
69
+ const runArgs = args.slice(1);
70
+ const label = takeOption(runArgs, "--label");
71
+ const fuelBefore = takeOption(runArgs, "--fuel-before");
72
+ const separator = runArgs.indexOf("--");
73
+ const childArgs = separator === -1 ? runArgs : runArgs.slice(separator + 1);
74
+ if (childArgs.length === 0) {
75
+ throw new Error("Missing command after `aim run --`.");
76
+ }
77
+ const result = await runMission({
78
+ command: childArgs,
79
+ label,
80
+ fuelBefore: fuelBefore === undefined ? undefined : Number(fuelBefore)
81
+ });
82
+ console.log(result.summary);
83
+ } else if (command === "preflight") {
84
+ const runArgs = args.slice(1);
85
+ const separator = runArgs.indexOf("--");
86
+ const childArgs = separator === -1 ? runArgs : runArgs.slice(separator + 1);
87
+ if (childArgs.length === 0) {
88
+ throw new Error("Missing command or prompt after `aim preflight --`.");
89
+ }
90
+ console.log(await preflightMission(childArgs));
91
+ } else if (command === "plan") {
92
+ const planArgs = args.slice(1);
93
+ const fuelPercent = takeOption(planArgs, "--fuel");
94
+ const quality = takeOption(planArgs, "--quality") ?? "high";
95
+ const separator = planArgs.indexOf("--");
96
+ const goalArgs = separator === -1 ? planArgs : planArgs.slice(separator + 1);
97
+ const goal = goalArgs.join(" ").trim();
98
+ if (!goal) {
99
+ throw new Error("Missing goal after `aim plan --`.");
100
+ }
101
+ const plan = await planMission(goal, {
102
+ quality,
103
+ fuelPercent: fuelPercent === undefined ? undefined : Number(fuelPercent)
104
+ });
105
+ console.log([
106
+ "",
107
+ `Runcap plan: ${plan.id}`,
108
+ `Goal: ${plan.goal}`,
109
+ `Budget risk: ${plan.budget.risk}`,
110
+ `Expected waste reduction: ${plan.budget.expectedWasteReduction}`,
111
+ `Planning model: ${plan.routing.planningTier}`,
112
+ `Execution model: ${plan.routing.executionTier}`,
113
+ `Proof: ${plan.quality.proof}`,
114
+ `Stop rule: ${plan.stopRule}`,
115
+ `Report: .aim-control/plans/${plan.id}/plan.md`,
116
+ ""
117
+ ].join("\n"));
118
+ } else if (command === "plans") {
119
+ console.log(await listPlans());
120
+ } else if (command === "status") {
121
+ console.log(await showStatus());
122
+ } else if (command === "list") {
123
+ console.log(await listMissions());
124
+ } else if (command === "report" || command === "rescue") {
125
+ const id = args[1] ?? (await latestMissionId());
126
+ if (!id) throw new Error("No mission found.");
127
+ console.log(await renderReport(id));
128
+ } else if (command === "export") {
129
+ const id = args[1] ?? (await latestMissionId());
130
+ if (!id) throw new Error("No mission found.");
131
+ console.log(await exportSnapshot(id));
132
+ } else if (command === "templates") {
133
+ console.log(templates());
134
+ } else if (command === "dashboard") {
135
+ const port = Number(takeOption(args, "--port") ?? 8791);
136
+ await startDashboard({ port });
137
+ } else if (command === "gateway") {
138
+ const port = Number(takeOption(args, "--port") ?? 8792);
139
+ const gatewayArgs = args.slice(1);
140
+ const mock = gatewayArgs.includes("--mock");
141
+ await startGateway({ port, mock });
142
+ } else if (command === "setup") {
143
+ console.log(await setupProject());
144
+ } else if (command === "doctor") {
145
+ console.log(await doctor());
146
+ } else if (command === "fuel") {
147
+ const subcommand = args[1] ?? "show";
148
+ if (subcommand === "set") {
149
+ const value = Number(args[2]);
150
+ if (!Number.isFinite(value)) throw new Error("Usage: aim fuel set <percent>");
151
+ console.log(await recordFuel(value));
152
+ } else if (subcommand === "calibrate") {
153
+ const id = args[2];
154
+ const after = Number(args[3]);
155
+ if (!id || !Number.isFinite(after)) {
156
+ throw new Error("Usage: aim fuel calibrate <mission-id> <after-percent>");
157
+ }
158
+ console.log(await calibrateFuel(id, after));
159
+ } else {
160
+ console.log(await showStatus({ includeFuelOnly: true }));
161
+ }
162
+ } else {
163
+ usage();
164
+ process.exitCode = 1;
165
+ }
166
+ } catch (error) {
167
+ console.error(`aim: ${error.message}`);
168
+ process.exitCode = 1;
169
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "broken-ts-app",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "node src/index.ts"
8
+ },
9
+ "devDependencies": {
10
+ "typescript": "^5.9.3"
11
+ }
12
+ }
@@ -0,0 +1,5 @@
1
+ import { Button } from "@/components/Button";
2
+
3
+ export function renderLogin() {
4
+ return Button({ label: "Sign in" });
5
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true
8
+ },
9
+ "include": ["src"]
10
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "runcap",
3
+ "version": "0.1.0",
4
+ "description": "Cap every agent run before it starts: estimate cost, set a hard ceiling that stops the run, rescue stuck agents. Local, MIT, nothing uploaded.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "author": "Kirill D. <kirill@launchsoloai.com> (https://launchsoloai.com)",
8
+ "homepage": "https://launchsoloai.com/runcap",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/kirder24-code/ai-agent-manager.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/kirder24-code/ai-agent-manager/issues"
15
+ },
16
+ "keywords": [
17
+ "ai",
18
+ "agent",
19
+ "coding-agent",
20
+ "cost",
21
+ "budget",
22
+ "claude",
23
+ "openai",
24
+ "gateway",
25
+ "cli",
26
+ "llm",
27
+ "token-cost"
28
+ ],
29
+ "files": [
30
+ "bin/",
31
+ "src/",
32
+ "examples/",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "bin": {
37
+ "runcap": "./bin/runcap.mjs",
38
+ "aim": "./bin/runcap.mjs"
39
+ },
40
+ "scripts": {
41
+ "setup": "node ./bin/runcap.mjs setup",
42
+ "doctor": "node ./bin/runcap.mjs doctor",
43
+ "demo": "node ./scripts/demo-flow.mjs",
44
+ "acceptance": "node ./scripts/acceptance.mjs",
45
+ "smoke": "node ./bin/runcap.mjs run --label smoke -- npm --prefix examples/broken-ts-app run build",
46
+ "demo:broken": "node ./bin/runcap.mjs run --label broken-ts-demo -- npm --prefix examples/broken-ts-app run build",
47
+ "test": "node ./scripts/validate-demo.mjs",
48
+ "status": "node ./bin/runcap.mjs status",
49
+ "report": "node ./bin/runcap.mjs report",
50
+ "export": "node ./bin/runcap.mjs export",
51
+ "templates": "node ./bin/runcap.mjs templates",
52
+ "dashboard": "node ./bin/runcap.mjs dashboard",
53
+ "gateway": "node ./bin/runcap.mjs gateway",
54
+ "fuel": "node ./bin/runcap.mjs fuel",
55
+ "check": "node --check ./bin/runcap.mjs && node --check ./src/mission-control.mjs"
56
+ },
57
+ "engines": {
58
+ "node": ">=20"
59
+ }
60
+ }