agent-economy 0.0.0-stage → 0.2.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/AGENTS.md ADDED
@@ -0,0 +1,36 @@
1
+ # agent-economy
2
+
3
+ This repo follows its own dogma — the request-economy block below is the product.
4
+
5
+ <!-- agent-economy:start level=strict -->
6
+
7
+ ## Request economy (metering is per request — every model round-trip bills one)
8
+
9
+ - Emit independent tool calls in PARALLEL within one round; never spread them one-per-round.
10
+ - Combine closely-related shell commands into a single call (max ~5, `;`-joined); batch related file reads into one round.
11
+ - Before running anything, check whether a result already in this session answers it; never re-run for information you already have.
12
+ - When an answer is inferable, state the assumption and proceed — do not spend a round asking a clarifying question.
13
+ - Do a task's follow-up work in the same turn; do not stop after each micro-step.
14
+ - Filter expected-large output at the source (`rtk <cmd>`, `--oneline`, `| head -50`) so rounds stay cheap to fold.
15
+ - Avoid subagents for small lookups — each runs its own multi-request loop; use direct tools instead.
16
+
17
+ <!-- agent-economy:end -->
18
+
19
+ ## Project rules
20
+
21
+ - Run `node src/cli.js verify` before committing — a release that fails its own check is not a release.
22
+ - The CLI must stay dependency-free (Node stdlib only); packs are fetched on demand, never vendored.
23
+
24
+ ## Agent skills
25
+
26
+ ### Issue tracker
27
+
28
+ GitHub Issues on `markymakes-dev/agent-economy` (via the `gh` CLI). See `docs/agents/issue-tracker.md`.
29
+
30
+ ### Triage labels
31
+
32
+ Default five-role vocabulary (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`.
33
+
34
+ ### Domain docs
35
+
36
+ Single-context: `GLOSSARY.md` at the root + `docs/adr/` (created lazily by `/domain-modeling`). See `docs/agents/domain.md`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agent-economy contributors
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 CHANGED
@@ -1,3 +1,74 @@
1
- # Temporary Holding Version
1
+ # agent-economy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Request economy for coding agents.** Free and metered agent tiers bill **per request** — every model round-trip costs one, no matter how small. agent-economy installs a battle-tested rule set that makes agents batch, dedupe, and finish in fewer round-trips — as an idempotent, marker-bounded block in `AGENTS.md`, the open standard every major agent already reads (opencode, Codex, Cursor, Gemini CLI, Jules, Amp, Zed).
4
+
5
+ ```
6
+ npm i -g agent-economy # once per machine (or use npx agent-economy <cmd>)
7
+
8
+ ae init # per project: interactive level + scope
9
+ ae init --level strict --yes # scripted onboarding
10
+ ae verify --min-level strict # CI gate: exit 1 missing, 2 below level
11
+ ae doctor # detect rtk / billion-context / goal plugin
12
+ ae packs # curated third-party rule/skill packs
13
+ ```
14
+
15
+ ### Install with your agent (no terminal needed)
16
+
17
+ Paste this into opencode / Claude Code / Codex / Cursor — the agent does the rest:
18
+
19
+ > Install agent-economy: run `npx -y agent-economy@latest init --level standard --yes`, then `npx -y agent-economy@latest skill`, then `npx -y agent-economy@latest doctor`, and report the results. Don't ask clarifying questions; project scope.
20
+
21
+ After that, every future session discovers the `agent-economy-setup` skill and knows how to verify, re-configure, or extend the setup on its own.
22
+
23
+ ## Why requests, not tokens
24
+
25
+ Token-compression tools (billion-context, RTK, headroom) are plentiful. **Request-economy tooling does not exist** — yet per-request metering is what actually caps free-tier sessions (measured baseline: ~7 model requests per user message in a real opencode session). Batching 3 independent calls into 1 round saves 2 requests *and* 2 round-trips of latency, every time. No plugin can force batching — call count is decided at generation time — so the prompt is the only injection point. This project owns that injection point.
26
+
27
+ ## Levels
28
+
29
+ | Level | Rules |
30
+ |---|---|
31
+ | `standard` | parallel independent calls · batch related shell commands (max ~5) · never re-run for known info · filter large output at the source |
32
+ | `strict` | standard **+** assume-and-proceed instead of clarifying questions · same-turn completion · no subagents for small lookups |
33
+ | `off` | removes the block (clean uninstall) |
34
+
35
+ ## Mechanics
36
+
37
+ - **Idempotent** — blocks are wrapped in `<!-- agent-economy:start level=… -->` … `<!-- agent-economy:end -->`; re-running updates in place, never duplicates
38
+ - **Backup first** — `AGENTS.md.bak-<timestamp>` before any change
39
+ - **Zero dependencies** — Node stdlib only; runs anywhere Node ≥ 18 runs
40
+ - **Zero runtime cost** — the CLI runs once at setup; the artifact is the file. No plugin, no MCP, no per-session overhead
41
+
42
+ ## Enforce it in CI
43
+
44
+ You can't police teammates' machines — you can police the repo:
45
+
46
+ ```yaml
47
+ # .github/workflows/agents-md.yml
48
+ - run: npx agent-economy verify --min-level strict
49
+ ```
50
+
51
+ ## Packs
52
+
53
+ Curated, attributed, pinned fetch-on-demand integrations - never vendored:
54
+
55
+ | Pack | Source | Kind |
56
+ |---|---|---|
57
+ | `karpathy-rules` | multica-ai/andrej-karpathy-skills | behavior rules |
58
+ | `matt-skills` | mattpocock/skills | engineering skills |
59
+ | `ecc-harness` | affaan-m/ECC | harness performance patterns |
60
+ | `anthropic-skills` | anthropics/skills | official skills |
61
+
62
+ ```bash
63
+ ae packs --install ecc-harness # → ~/.claude/skills (override with --dir)
64
+ ```
65
+
66
+ RTK and billion-context are **runtime tools**, not packs — `ae packs` prints their setup commands, `ae inventory` shows whether they're alive. `ae` never installs them for you.
67
+
68
+ ## Positioning
69
+
70
+ Token compressors optimize **bytes**; this optimizes **round-trips**. Different unit, different layer, complementary stack. The dedicated AGENTS.md-template niche is empty (existing repos: 6–41★) while the adjacent skills ecosystem measures in six figures — the lane is open.
71
+
72
+ ## License
73
+
74
+ MIT
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "agent-economy",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.1",
4
+ "files": [
5
+ "src",
6
+ "packs",
7
+ "skills",
8
+ "AGENTS.md",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "description": "Request economy for coding agents — treat every model round-trip as the billable unit. Idempotent AGENTS.md presets, tool detection, CI verify.",
13
+ "type": "module",
14
+ "bin": {
15
+ "ae": "src/cli.js",
16
+ "economy": "src/cli.js"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "license": "MIT",
22
+ "keywords": [
23
+ "agents-md",
24
+ "opencode",
25
+ "codex",
26
+ "cursor",
27
+ "claude-code",
28
+ "request-economy",
29
+ "token-savings",
30
+ "ai-agents"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/markymakes-dev/agent-economy.git"
35
+ }
36
+ }
@@ -0,0 +1,75 @@
1
+ {
2
+ "packs": [
3
+ {
4
+ "id": "karpathy-rules",
5
+ "source": "multica-ai/andrej-karpathy-skills",
6
+ "ref": "main",
7
+ "kind": "rules",
8
+ "status": "curated",
9
+ "install": [
10
+ { "from": "skills/karpathy-guidelines", "to": "karpathy-guidelines" }
11
+ ],
12
+ "note": "Behavior rules distilled from Karpathy's LLM coding-pitfall observations"
13
+ },
14
+ {
15
+ "id": "matt-skills",
16
+ "source": "mattpocock/skills",
17
+ "ref": "main",
18
+ "kind": "skills",
19
+ "status": "curated",
20
+ "install": [
21
+ { "from": "skills/engineering/tdd", "to": "tdd" },
22
+ { "from": "skills/engineering/code-review", "to": "code-review" },
23
+ { "from": "skills/engineering/diagnosing-bugs", "to": "diagnosing-bugs" },
24
+ { "from": "skills/engineering/codebase-design", "to": "codebase-design" },
25
+ { "from": "skills/engineering/pr", "to": "pr" },
26
+ { "from": "skills/productivity/writing-for-agents", "to": "writing-for-agents" }
27
+ ],
28
+ "note": "Curated subset of Matt Pocock's engineering skills (the full 66-skill collection is worth browsing; these are the evergreen ones)"
29
+ },
30
+ {
31
+ "id": "ecc-harness",
32
+ "source": "affaan-m/ECC",
33
+ "ref": "main",
34
+ "kind": "harness",
35
+ "status": "curated",
36
+ "install": [
37
+ { "from": ".agents/skills/strategic-compact", "to": "strategic-compact" },
38
+ { "from": ".agents/skills/verification-loop", "to": "verification-loop" },
39
+ { "from": ".agents/skills/change-impact", "to": "change-impact" },
40
+ { "from": ".agents/skills/coding-standards", "to": "coding-standards" },
41
+ { "from": ".agents/skills/security-review", "to": "security-review" }
42
+ ],
43
+ "note": "Request-economy-relevant subset of ECC's harness skills (strategic-compact alone earns the install); ECC-specific skills like ecc-conventions deliberately excluded"
44
+ },
45
+ {
46
+ "id": "anthropic-skills",
47
+ "source": "anthropics/skills",
48
+ "ref": "main",
49
+ "kind": "skills",
50
+ "status": "curated",
51
+ "install": [
52
+ { "from": "skills/skill-creator", "to": "skill-creator" },
53
+ { "from": "skills/mcp-builder", "to": "mcp-builder" },
54
+ { "from": "skills/webapp-testing", "to": "webapp-testing" }
55
+ ],
56
+ "note": "Official Anthropic skills, agent-tooling subset (document skills docx/pdf/pptx/xlsx live in the same repo if you want them manually)"
57
+ },
58
+ {
59
+ "id": "rtk",
60
+ "source": "rtk-ai/rtk",
61
+ "kind": "tool",
62
+ "status": "stack",
63
+ "setup": "winget install rtk-ai.rtk · then: rtk init -g --opencode --hook-only --no-patch",
64
+ "note": "Command rewriter — cuts agent tool output at the source (100+ per-command filters, fail-open)"
65
+ },
66
+ {
67
+ "id": "billion-context",
68
+ "source": "ranxianglei/billion-context",
69
+ "kind": "tool",
70
+ "status": "stack",
71
+ "setup": "npm i -g billion-context · then: bili plugin install opencode",
72
+ "note": "Context-compression proxy for agent sessions — folds stale context out of every request"
73
+ }
74
+ ]
75
+ }
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: agent-economy-setup
3
+ description: Install, verify, or configure agent-economy request-economy rules. Use when the user asks to install/set up agent-economy, wants the agent to save requests/quota/credits, mentions ae commands, or a CI check on agent-economy fails.
4
+ license: MIT
5
+ ---
6
+
7
+ # agent-economy setup
8
+
9
+ Free and metered agent tiers bill **per request** — every model round-trip costs one. agent-economy manages a battle-tested rule set as an idempotent, marker-bounded block in `AGENTS.md`, so agents batch, dedupe, and finish in fewer round-trips.
10
+
11
+ ## Commands
12
+
13
+ - `ae init [--level off|standard|strict] [--scope project|global|both] [--yes]` — install/update the rules block (interactive unless `--yes`)
14
+ - `ae verify [file] [--min-level standard|strict]` — check the block; exit 0 ok, 1 missing, 2 below level (this is the CI gate)
15
+ - `ae doctor` — detect companion tooling (rtk, billion-context, goal plugin)
16
+ - `ae inventory` — list the token-economy stack (with live health) + all loaded plugins/skills/MCP
17
+ - `ae packs` / `ae packs --install <id>` — curated third-party rule/skill packs (fetch-on-demand, attributed)
18
+ - `ae skill` — (re)install this skill
19
+
20
+ ## Installing for a user
21
+
22
+ 1. Run `npx -y agent-economy@latest init --level standard --yes` (project scope). Use `--level strict` when the user wants maximum savings; `--scope global` writes `~/.config/opencode/AGENTS.md`.
23
+ 2. Run `npx -y agent-economy@latest skill` so future sessions can manage the setup themselves.
24
+ 3. Run `npx -y agent-economy@latest doctor` and report the results.
25
+ 4. Optional: `npx -y agent-economy@latest packs --install karpathy-rules` (behavior rules). NEVER install stack tools (`rtk`, `billion-context`) without asking — they change runtime behavior, not content.
26
+
27
+ ## Guardrails
28
+
29
+ - The block between `<!-- agent-economy:start level=… -->` and `<!-- agent-economy:end -->` is MANAGED — never hand-edit it; change the level with `ae init --level <lvl>`.
30
+ - `ae init` is idempotent and backs up to `AGENTS.md.bak-<timestamp>` first.
31
+ - If a repo contains the block, run `ae verify --min-level strict` before committing.
32
+ - Levels: `standard` = batch + dedupe + filter output at source; `strict` = + assume-and-proceed, same-turn completion, no subagents for small lookups.
package/src/cli.js ADDED
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+ // agent-economy — request economy for coding agents.
3
+ // Metering is per request: every model round-trip bills one. This CLI manages
4
+ // the request-economy rules as an idempotent, marker-bounded AGENTS.md block.
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { execSync, spawnSync } from "node:child_process";
9
+ import readline from "node:readline/promises";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
+ const MARKER_START = /<!-- agent-economy:start level=(\w+) -->/;
14
+ const MARKER_END = "<!-- agent-economy:end -->";
15
+ const RE_BLOCK = /<!-- agent-economy:start level=\w+ -->[\s\S]*?<!-- agent-economy:end -->/;
16
+ const HEADER = "## Request economy (metering is per request — every model round-trip bills one)";
17
+
18
+ const RULES = {
19
+ standard: [
20
+ "- Emit independent tool calls in PARALLEL within one round; never spread them one-per-round.",
21
+ "- Combine closely-related shell commands into a single call (max ~5, `;`-joined); batch related file reads into one round.",
22
+ "- Before running anything, check whether a result already in this session answers it; never re-run for information you already have.",
23
+ "- Filter expected-large output at the source (`rtk <cmd>`, `--oneline`, `| head -50`) so rounds stay cheap to fold."
24
+ ],
25
+ strict: [
26
+ "- Emit independent tool calls in PARALLEL within one round; never spread them one-per-round.",
27
+ "- Combine closely-related shell commands into a single call (max ~5, `;`-joined); batch related file reads into one round.",
28
+ "- Before running anything, check whether a result already in this session answers it; never re-run for information you already have.",
29
+ "- When an answer is inferable, state the assumption and proceed — do not spend a round asking a clarifying question.",
30
+ "- Do a task's follow-up work in the same turn; do not stop after each micro-step.",
31
+ "- Filter expected-large output at the source (`rtk <cmd>`, `--oneline`, `| head -50`) so rounds stay cheap to fold.",
32
+ "- Avoid subagents for small lookups — each runs its own multi-request loop; use direct tools instead."
33
+ ]
34
+ };
35
+
36
+ const RANK = { off: 0, standard: 1, strict: 2 };
37
+ const startMarker = (lvl) => `<!-- agent-economy:start level=${lvl} -->`;
38
+ const blockFor = (lvl) => [startMarker(lvl), "", HEADER, "", ...RULES[lvl], "", MARKER_END].join("\n");
39
+ const globalFile = () => path.join(os.homedir(), ".config", "opencode", "AGENTS.md");
40
+
41
+ function skill(dir) {
42
+ const src = path.join(ROOT, "skills", "agent-economy-setup");
43
+ const target = path.resolve(dir || path.join(os.homedir(), ".claude", "skills", "agent-economy-setup"));
44
+ fs.rmSync(target, { recursive: true, force: true });
45
+ fs.mkdirSync(path.dirname(target), { recursive: true });
46
+ fs.cpSync(src, target, { recursive: true });
47
+ console.log(` ✔ agent-economy-setup skill → ${target}`);
48
+ console.log(" = agents discover it automatically (opencode picks it up without restart)");
49
+ }
50
+
51
+ function upsert(file, level) {
52
+ const exists = fs.existsSync(file);
53
+ const prev = exists ? fs.readFileSync(file, "utf8") : "";
54
+ let next;
55
+ if (level === "off") {
56
+ if (!RE_BLOCK.test(prev)) { console.log(` = no agent-economy block in ${file}`); return; }
57
+ next = prev.replace(RE_BLOCK, "").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
58
+ } else {
59
+ next = RE_BLOCK.test(prev)
60
+ ? prev.replace(RE_BLOCK, blockFor(level))
61
+ : (prev.trimEnd() ? prev.trimEnd() + "\n\n" + blockFor(level) + "\n" : blockFor(level) + "\n");
62
+ }
63
+ if (next === prev) { console.log(` = unchanged ${file}`); return; }
64
+ if (exists) {
65
+ const bak = `${file}.bak-${Date.now()}`;
66
+ fs.copyFileSync(file, bak);
67
+ console.log(` + backup ${bak}`);
68
+ }
69
+ fs.mkdirSync(path.dirname(file), { recursive: true });
70
+ fs.writeFileSync(file, next);
71
+ console.log(` ✔ wrote ${file} (level=${level})`);
72
+ }
73
+
74
+ function verify(file, minLevel) {
75
+ if (!fs.existsSync(file)) return { code: 1, msg: `✘ ${file} not found` };
76
+ const m = fs.readFileSync(file, "utf8").match(MARKER_START);
77
+ if (!m) return { code: 1, msg: `✘ ${file} has no agent-economy block` };
78
+ const lvl = m[1];
79
+ if (RANK[lvl] === undefined) return { code: 2, msg: `✘ ${file} has unknown level "${lvl}"` };
80
+ if (RANK[lvl] < RANK[minLevel]) return { code: 2, msg: `✘ ${file} level=${lvl} is below required ${minLevel}` };
81
+ return { code: 0, msg: `✔ ${file} level=${lvl} (required ≥ ${minLevel})` };
82
+ }
83
+
84
+ const hasBin = (cmd) => {
85
+ try { execSync(`${process.platform === "win32" ? "where" : "which"} ${cmd}`, { stdio: "pipe" }); return true; }
86
+ catch { return false; }
87
+ };
88
+
89
+ function doctor() {
90
+ const opencodeDir = path.join(os.homedir(), ".config", "opencode");
91
+ const opencodeCfg = (() => { try { return fs.readFileSync(path.join(opencodeDir, "opencode.json"), "utf8"); } catch { return ""; } })();
92
+ const goalInstalled = /goal/i.test(opencodeCfg) || ["plugin", "plugins"].some((d) => {
93
+ try {
94
+ return fs.readdirSync(path.join(opencodeDir, d)).some((f) =>
95
+ /goal/i.test(f) || /goal/i.test(fs.readFileSync(path.join(opencodeDir, d, f), "utf8")));
96
+ } catch { return false; }
97
+ });
98
+ const rows = [
99
+ ["rtk (output filter)", hasBin("rtk"), "winget install rtk-ai.rtk"],
100
+ ["billion-context (context folds)", hasBin("bili") || fs.existsSync(path.join(os.homedir(), ".local", "bili.cmd")), "npm install -g billion-context --prefix=~/.local && bili plugin install opencode"],
101
+ ["opencode CLI", hasBin("opencode"), "opencode.ai"],
102
+ ["/goal (opencode command)", goalInstalled || hasBin("opencode"), "built into opencode 2.x — else install prevalentWare/opencode-goal-plugin"],
103
+ ["project AGENTS.md", fs.existsSync(path.resolve("AGENTS.md")), "economy init"],
104
+ ["global AGENTS.md", fs.existsSync(globalFile()), "economy init --scope global"]
105
+ ];
106
+ for (const [name, ok, hint] of rows) console.log(` ${ok ? "✔" : "✘"} ${name}${ok ? "" : ` → ${hint}`}`);
107
+ }
108
+
109
+ function packs() {
110
+ const reg = JSON.parse(fs.readFileSync(path.join(ROOT, "packs", "registry.json"), "utf8"));
111
+ console.log(" Curated packs (fetch-on-demand; attribution + pinned refs):\n");
112
+ for (const p of reg.packs.filter((p) => p.status !== "stack"))
113
+ console.log(` ${p.id.padEnd(18)} ${p.source.padEnd(38)} [${p.status}] ${p.note}`);
114
+ const tools = reg.packs.filter((p) => p.status === "stack");
115
+ if (tools.length) {
116
+ console.log("\n Stack tools (runtime — detected by `ae inventory`; ae never installs these):\n");
117
+ for (const p of tools) console.log(` ${p.id.padEnd(18)} ${p.source.padEnd(38)} setup: ${p.setup}`);
118
+ }
119
+ }
120
+
121
+ async function installPack(id, dir) {
122
+ const reg = JSON.parse(fs.readFileSync(path.join(ROOT, "packs", "registry.json"), "utf8"));
123
+ const p = reg.packs.find((x) => x.id === id);
124
+ if (!p) { console.error(` ✘ unknown pack "${id}"`); process.exit(2); }
125
+ if (p.status === "stack") {
126
+ console.error(` ✘ "${id}" is a runtime tool, not a fetchable pack — ae never installs those.`);
127
+ console.error(` set it up directly: ${p.setup}`);
128
+ process.exit(2);
129
+ }
130
+ if (!Array.isArray(p.install) || !p.install.length) {
131
+ console.error(` ✘ pack "${id}" has no curated install spec yet (status: ${p.status})`);
132
+ process.exit(2);
133
+ }
134
+ const target = path.resolve(dir || path.join(os.homedir(), ".claude", "skills"));
135
+ const work = fs.mkdtempSync(path.join(os.tmpdir(), "ae-pack-"));
136
+ try {
137
+ const url = `https://codeload.github.com/${p.source}/tar.gz/refs/heads/${p.ref || "main"}`;
138
+ console.log(` ↓ ${url}`);
139
+ const res = await fetch(url);
140
+ if (!res.ok) { console.error(` ✘ download failed: HTTP ${res.status}`); process.exit(2); }
141
+ const tgz = path.join(work, "pack.tgz");
142
+ fs.writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
143
+ const root = path.join(work, "x");
144
+ fs.mkdirSync(root, { recursive: true });
145
+ const r = spawnSync("tar", ["-xzf", tgz, "-C", root]);
146
+ if (r.status !== 0) { console.error(" ✘ untar failed (is tar available?)"); process.exit(2); }
147
+ const base = path.join(root, fs.readdirSync(root)[0]);
148
+ fs.mkdirSync(target, { recursive: true });
149
+ for (const spec of p.install) {
150
+ const from = path.join(base, spec.from);
151
+ const to = path.join(target, spec.to);
152
+ if (!fs.existsSync(from)) { console.error(` ✘ missing in pack: ${spec.from}`); process.exit(2); }
153
+ fs.cpSync(from, to, { recursive: true });
154
+ console.log(` ✔ ${spec.from} → ${to}`);
155
+ }
156
+ const attr = path.join(target, `${p.id}.ATTRIBUTION.md`);
157
+ fs.writeFileSync(attr, `# ${p.id}\n\nSource: https://github.com/${p.source} (ref ${p.ref || "main"})\nInstalled by agent-economy on ${new Date().toISOString()}\nLicense: see the pack repository.\n`);
158
+ console.log(` ✔ attribution → ${attr}`);
159
+ console.log(` = done: ${p.id} installed to ${target}`);
160
+ } finally {
161
+ fs.rmSync(work, { recursive: true, force: true });
162
+ }
163
+ }
164
+
165
+ function listDir(p, filter) {
166
+ try { return fs.readdirSync(p, { withFileTypes: true }).filter((d) => filter(d, p)).map((d) => d.name); }
167
+ catch { return []; }
168
+ }
169
+
170
+ const pkgName = (spec) => spec.replace(/@[^/@]+$/, "");
171
+
172
+ function rtkStatus() {
173
+ const r = spawnSync("rtk", ["--version"], { encoding: "utf8", timeout: 5000 });
174
+ const v = (r.stdout || "").trim().split(/\r?\n/)[0] || "";
175
+ if (r.error && !v) return "rtk — not found (install: winget install rtk-ai.rtk · then: rtk init -g --opencode --hook-only --no-patch)";
176
+ if (!v) return "rtk — installed (version unknown)";
177
+ return v.startsWith("rtk") ? v : `rtk ${v}`;
178
+ }
179
+
180
+ async function biliStatus() {
181
+ const dir = path.join(os.homedir(), ".local", "state", "billion-context", "instances");
182
+ const files = listDir(dir, (d) => d.isFile() && d.name.endsWith(".json"));
183
+ for (const f of files) {
184
+ try {
185
+ const inst = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
186
+ if (!inst.origin) continue;
187
+ const res = await fetch(`${inst.origin}/__bili/health`, { signal: AbortSignal.timeout(3000) });
188
+ const h = await res.json();
189
+ return h.ok
190
+ ? `billion-context v${h.version} — healthy (${inst.origin})`
191
+ : `billion-context — responding, not healthy (${inst.origin})`;
192
+ } catch { /* dead instance file, try next */ }
193
+ }
194
+ if (files.length) return "billion-context — proxy not responding (restart opencode to relaunch it)";
195
+ return "billion-context — not detected (install: npm i -g billion-context · then: bili plugin install opencode)";
196
+ }
197
+
198
+ async function inventory() {
199
+ const home = os.homedir();
200
+ const oc = path.join(home, ".config", "opencode");
201
+ const rows = [];
202
+ let active = new Set();
203
+ try {
204
+ const j = JSON.parse(fs.readFileSync(path.join(oc, "opencode.json"), "utf8"));
205
+ for (const p of j.plugin || []) { rows.push(["opencode plugin (config)", p]); active.add(pkgName(p)); }
206
+ for (const m of Object.keys(j.mcp || {})) rows.push(["opencode mcp", m]);
207
+ } catch {}
208
+ for (const f of listDir(path.join(oc, "plugins"), (d) => d.isFile())) rows.push(["opencode plugin (file)", f]);
209
+ for (const f of listDir(path.join(oc, "command"), (d) => d.isFile())) rows.push(["opencode command", f]);
210
+ for (const dir of [
211
+ path.join(oc, "skill"), path.join(oc, "skills"),
212
+ path.resolve(".opencode", "skill"), path.resolve(".opencode", "skills"),
213
+ path.join(home, ".claude", "skills")
214
+ ]) for (const d of listDir(dir, (e) => e.isDirectory())) rows.push([`skills ${dir.replace(home, "~")}`, d]);
215
+ for (const d of listDir(path.join(home, ".claude", "plugins"), (e) => e.isDirectory())) rows.push(["claude plugin", d]);
216
+ const cache = path.join(home, ".cache", "opencode", "npm");
217
+ for (const d of listDir(cache, (e) => e.isDirectory())) {
218
+ if (d.startsWith("@")) for (const c of listDir(path.join(cache, d), (e) => e.isDirectory())) rows.push(["opencode npm cache", `${d}/${c}`]);
219
+ else rows.push(["opencode npm cache", d]);
220
+ }
221
+ const seen = new Set();
222
+ console.log(" Token-economy stack:\n");
223
+ for (const line of await Promise.all([rtkStatus(), biliStatus()])) console.log(` · ${line}`);
224
+ console.log("\n Plugins / skills / MCP loaded across agents on this machine:\n");
225
+ let last = "";
226
+ let n = 0;
227
+ for (const [src, name] of rows) {
228
+ const k = `${src}|${name}`;
229
+ if (seen.has(k)) continue;
230
+ seen.add(k); n++;
231
+ if (src !== last) { console.log(` ${src}`); last = src; }
232
+ const orphan = src === "opencode npm cache" && !active.has(pkgName(name));
233
+ console.log(` · ${name}${orphan ? " (not in config — orphaned)" : ""}`);
234
+ }
235
+ console.log(`\n ${n} entries · health: ae doctor · curated extras: ae packs`);
236
+ }
237
+
238
+ const HELP = `
239
+ agent-economy — request economy for coding agents
240
+
241
+ Usage: ae <command> [options] (alias: economy)
242
+
243
+ One-time install: npm i -g agent-economy (or: npx agent-economy <cmd>)
244
+
245
+ Commands:
246
+ init Install/update the request-economy block
247
+ --level off|standard|strict (default: interactive, standard)
248
+ --scope project|global|both (default: interactive, project)
249
+ --yes non-interactive defaults
250
+ verify Check a file carries the block; exit 1 missing, 2 below level
251
+ [file=AGENTS.md] --min-level standard|strict
252
+ doctor Detect agent tooling (rtk, billion-context, goal plugin, ...)
253
+ inventory Show the token-economy stack (rtk, billion-context) with health,
254
+ then every plugin/skill/MCP actually loaded on this machine
255
+ packs List curated third-party rule/skill packs
256
+ --install <id> [--dir <target>] fetch + install a curated pack
257
+ (default target: ~/.claude/skills)
258
+ skill Install the agent-economy-setup skill (teaches any agent to run ae)
259
+ help This text
260
+
261
+ Every model round-trip bills one request. Batch the independent ones.
262
+ `;
263
+
264
+ async function ask(q, def) {
265
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
266
+ const a = (await rl.question(`${q} [${def}]: `)).trim() || def;
267
+ rl.close();
268
+ return a;
269
+ }
270
+
271
+ async function main() {
272
+ const args = process.argv.slice(2);
273
+ const cmd = args[0] || "help";
274
+ const get = (f) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : undefined; };
275
+ const hasFlag = (f) => args.includes(f);
276
+
277
+ if (cmd === "init") {
278
+ const yes = hasFlag("--yes");
279
+ let level = get("--level");
280
+ let scope = get("--scope");
281
+ if (!level) level = yes ? "standard" : await ask("Economy level off/standard/strict?", "standard");
282
+ if (!scope) scope = yes ? "project" : await ask("Scope project/global/both?", "project");
283
+ if (!(level in RANK)) { console.error(`✘ unknown level "${level}"`); process.exit(2); }
284
+ const targets = { project: [path.resolve("AGENTS.md")], global: [globalFile()], both: [path.resolve("AGENTS.md"), globalFile()] }[scope];
285
+ if (!targets) { console.error(`✘ unknown scope "${scope}"`); process.exit(2); }
286
+ for (const f of targets) upsert(f, level);
287
+ return;
288
+ }
289
+
290
+ if (cmd === "verify") {
291
+ const VALUE_FLAGS = ["--level", "--scope", "--min-level"];
292
+ const positional = [];
293
+ for (let i = 1; i < args.length; i++) {
294
+ if (VALUE_FLAGS.includes(args[i])) { i++; continue; }
295
+ if (!args[i].startsWith("--")) positional.push(args[i]);
296
+ }
297
+ const file = positional[0] || path.resolve("AGENTS.md");
298
+ const min = get("--min-level") || "standard";
299
+ if (!(min in RANK)) { console.error(`✘ unknown --min-level "${min}"`); process.exit(2); }
300
+ const r = verify(file, min);
301
+ console.log(" " + r.msg);
302
+ process.exit(r.code);
303
+ }
304
+
305
+ if (cmd === "doctor") return doctor();
306
+ if (cmd === "skill") return skill(get("--dir"));
307
+ if (cmd === "inventory") return inventory();
308
+ if (cmd === "packs") {
309
+ if (get("--install")) return installPack(get("--install"), get("--dir"));
310
+ return packs();
311
+ }
312
+ console.log(HELP);
313
+ }
314
+
315
+ main().catch((e) => { console.error(e); process.exit(1); });