agent-economy 0.0.0-stage → 0.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/AGENTS.md +36 -0
- package/LICENSE +21 -0
- package/README.md +59 -2
- package/package.json +33 -4
- package/packs/registry.json +39 -0
- package/src/cli.js +292 -0
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,60 @@
|
|
|
1
|
-
#
|
|
1
|
+
# agent-economy
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
## Why requests, not tokens
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
## Levels
|
|
20
|
+
|
|
21
|
+
| Level | Rules |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `standard` | parallel independent calls · batch related shell commands (max ~5) · never re-run for known info · filter large output at the source |
|
|
24
|
+
| `strict` | standard **+** assume-and-proceed instead of clarifying questions · same-turn completion · no subagents for small lookups |
|
|
25
|
+
| `off` | removes the block (clean uninstall) |
|
|
26
|
+
|
|
27
|
+
## Mechanics
|
|
28
|
+
|
|
29
|
+
- **Idempotent** — blocks are wrapped in `<!-- agent-economy:start level=… -->` … `<!-- agent-economy:end -->`; re-running updates in place, never duplicates
|
|
30
|
+
- **Backup first** — `AGENTS.md.bak-<timestamp>` before any change
|
|
31
|
+
- **Zero dependencies** — Node stdlib only; runs anywhere Node ≥ 18 runs
|
|
32
|
+
- **Zero runtime cost** — the CLI runs once at setup; the artifact is the file. No plugin, no MCP, no per-session overhead
|
|
33
|
+
|
|
34
|
+
## Enforce it in CI
|
|
35
|
+
|
|
36
|
+
You can't police teammates' machines — you can police the repo:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
# .github/workflows/agents-md.yml
|
|
40
|
+
- run: npx agent-economy verify --min-level strict
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Packs (v0.2 roadmap)
|
|
44
|
+
|
|
45
|
+
Curated, attributed, pinned fetch-on-demand integrations — never vendored:
|
|
46
|
+
|
|
47
|
+
| Pack | Source | Kind |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `karpathy-rules` | multica-ai/andrej-karpathy-skills | behavior rules |
|
|
50
|
+
| `matt-skills` | mattpocock/skills | engineering skills |
|
|
51
|
+
| `ecc-harness` | affaan-m/ECC | harness performance patterns |
|
|
52
|
+
| `anthropic-skills` | anthropics/skills | official skills |
|
|
53
|
+
|
|
54
|
+
## Positioning
|
|
55
|
+
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-economy",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"src",
|
|
6
|
+
"packs",
|
|
7
|
+
"AGENTS.md",
|
|
8
|
+
"README.md",
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
11
|
+
"description": "Request economy for coding agents — treat every model round-trip as the billable unit. Idempotent AGENTS.md presets, tool detection, CI verify.",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"ae": "src/cli.js",
|
|
15
|
+
"economy": "src/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"agents-md",
|
|
23
|
+
"opencode",
|
|
24
|
+
"codex",
|
|
25
|
+
"cursor",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"request-economy",
|
|
28
|
+
"token-savings",
|
|
29
|
+
"ai-agents"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/markymakes-dev/agent-economy.git"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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": "planned",
|
|
20
|
+
"note": "Matt Pocock's .agents directory — engineering skills for real codebases (install spec TBD)"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "ecc-harness",
|
|
24
|
+
"source": "affaan-m/ECC",
|
|
25
|
+
"ref": "main",
|
|
26
|
+
"kind": "harness",
|
|
27
|
+
"status": "planned",
|
|
28
|
+
"note": "Agent-harness performance patterns: skills, instincts, memory, security (install spec TBD)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "anthropic-skills",
|
|
32
|
+
"source": "anthropics/skills",
|
|
33
|
+
"ref": "main",
|
|
34
|
+
"kind": "skills",
|
|
35
|
+
"status": "planned",
|
|
36
|
+
"note": "Official Anthropic agent skills (install spec TBD)"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
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 upsert(file, level) {
|
|
42
|
+
const exists = fs.existsSync(file);
|
|
43
|
+
const prev = exists ? fs.readFileSync(file, "utf8") : "";
|
|
44
|
+
let next;
|
|
45
|
+
if (level === "off") {
|
|
46
|
+
if (!RE_BLOCK.test(prev)) { console.log(` = no agent-economy block in ${file}`); return; }
|
|
47
|
+
next = prev.replace(RE_BLOCK, "").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
48
|
+
} else {
|
|
49
|
+
next = RE_BLOCK.test(prev)
|
|
50
|
+
? prev.replace(RE_BLOCK, blockFor(level))
|
|
51
|
+
: (prev.trimEnd() ? prev.trimEnd() + "\n\n" + blockFor(level) + "\n" : blockFor(level) + "\n");
|
|
52
|
+
}
|
|
53
|
+
if (next === prev) { console.log(` = unchanged ${file}`); return; }
|
|
54
|
+
if (exists) {
|
|
55
|
+
const bak = `${file}.bak-${Date.now()}`;
|
|
56
|
+
fs.copyFileSync(file, bak);
|
|
57
|
+
console.log(` + backup ${bak}`);
|
|
58
|
+
}
|
|
59
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
60
|
+
fs.writeFileSync(file, next);
|
|
61
|
+
console.log(` ✔ wrote ${file} (level=${level})`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function verify(file, minLevel) {
|
|
65
|
+
if (!fs.existsSync(file)) return { code: 1, msg: `✘ ${file} not found` };
|
|
66
|
+
const m = fs.readFileSync(file, "utf8").match(MARKER_START);
|
|
67
|
+
if (!m) return { code: 1, msg: `✘ ${file} has no agent-economy block` };
|
|
68
|
+
const lvl = m[1];
|
|
69
|
+
if (RANK[lvl] === undefined) return { code: 2, msg: `✘ ${file} has unknown level "${lvl}"` };
|
|
70
|
+
if (RANK[lvl] < RANK[minLevel]) return { code: 2, msg: `✘ ${file} level=${lvl} is below required ${minLevel}` };
|
|
71
|
+
return { code: 0, msg: `✔ ${file} level=${lvl} (required ≥ ${minLevel})` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const hasBin = (cmd) => {
|
|
75
|
+
try { execSync(`${process.platform === "win32" ? "where" : "which"} ${cmd}`, { stdio: "pipe" }); return true; }
|
|
76
|
+
catch { return false; }
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function doctor() {
|
|
80
|
+
const opencodeDir = path.join(os.homedir(), ".config", "opencode");
|
|
81
|
+
const opencodeCfg = (() => { try { return fs.readFileSync(path.join(opencodeDir, "opencode.json"), "utf8"); } catch { return ""; } })();
|
|
82
|
+
const goalInstalled = /goal/i.test(opencodeCfg) || ["plugin", "plugins"].some((d) => {
|
|
83
|
+
try {
|
|
84
|
+
return fs.readdirSync(path.join(opencodeDir, d)).some((f) =>
|
|
85
|
+
/goal/i.test(f) || /goal/i.test(fs.readFileSync(path.join(opencodeDir, d, f), "utf8")));
|
|
86
|
+
} catch { return false; }
|
|
87
|
+
});
|
|
88
|
+
const rows = [
|
|
89
|
+
["rtk (output filter)", hasBin("rtk"), "winget install rtk-ai.rtk"],
|
|
90
|
+
["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"],
|
|
91
|
+
["opencode CLI", hasBin("opencode"), "opencode.ai"],
|
|
92
|
+
["/goal (opencode command)", goalInstalled || hasBin("opencode"), "built into opencode 2.x — else install prevalentWare/opencode-goal-plugin"],
|
|
93
|
+
["project AGENTS.md", fs.existsSync(path.resolve("AGENTS.md")), "economy init"],
|
|
94
|
+
["global AGENTS.md", fs.existsSync(globalFile()), "economy init --scope global"]
|
|
95
|
+
];
|
|
96
|
+
for (const [name, ok, hint] of rows) console.log(` ${ok ? "✔" : "✘"} ${name}${ok ? "" : ` → ${hint}`}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function packs() {
|
|
100
|
+
const reg = JSON.parse(fs.readFileSync(path.join(ROOT, "packs", "registry.json"), "utf8"));
|
|
101
|
+
console.log(" Curated packs (fetch-on-demand in v0.2; attribution + pinned refs):\n");
|
|
102
|
+
for (const p of reg.packs) console.log(` ${p.id.padEnd(18)} ${p.source.padEnd(38)} [${p.status}] ${p.note}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function installPack(id, dir) {
|
|
106
|
+
const reg = JSON.parse(fs.readFileSync(path.join(ROOT, "packs", "registry.json"), "utf8"));
|
|
107
|
+
const p = reg.packs.find((x) => x.id === id);
|
|
108
|
+
if (!p) { console.error(` ✘ unknown pack "${id}"`); process.exit(2); }
|
|
109
|
+
if (!Array.isArray(p.install) || !p.install.length) {
|
|
110
|
+
console.error(` ✘ pack "${id}" has no curated install spec yet (status: ${p.status})`);
|
|
111
|
+
process.exit(2);
|
|
112
|
+
}
|
|
113
|
+
const target = path.resolve(dir || path.join(os.homedir(), ".claude", "skills"));
|
|
114
|
+
const work = fs.mkdtempSync(path.join(os.tmpdir(), "ae-pack-"));
|
|
115
|
+
try {
|
|
116
|
+
const url = `https://codeload.github.com/${p.source}/tar.gz/refs/heads/${p.ref || "main"}`;
|
|
117
|
+
console.log(` ↓ ${url}`);
|
|
118
|
+
const res = await fetch(url);
|
|
119
|
+
if (!res.ok) { console.error(` ✘ download failed: HTTP ${res.status}`); process.exit(2); }
|
|
120
|
+
const tgz = path.join(work, "pack.tgz");
|
|
121
|
+
fs.writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
|
|
122
|
+
const root = path.join(work, "x");
|
|
123
|
+
fs.mkdirSync(root, { recursive: true });
|
|
124
|
+
const r = spawnSync("tar", ["-xzf", tgz, "-C", root]);
|
|
125
|
+
if (r.status !== 0) { console.error(" ✘ untar failed (is tar available?)"); process.exit(2); }
|
|
126
|
+
const base = path.join(root, fs.readdirSync(root)[0]);
|
|
127
|
+
fs.mkdirSync(target, { recursive: true });
|
|
128
|
+
for (const spec of p.install) {
|
|
129
|
+
const from = path.join(base, spec.from);
|
|
130
|
+
const to = path.join(target, spec.to);
|
|
131
|
+
if (!fs.existsSync(from)) { console.error(` ✘ missing in pack: ${spec.from}`); process.exit(2); }
|
|
132
|
+
fs.cpSync(from, to, { recursive: true });
|
|
133
|
+
console.log(` ✔ ${spec.from} → ${to}`);
|
|
134
|
+
}
|
|
135
|
+
const attr = path.join(target, `${p.id}.ATTRIBUTION.md`);
|
|
136
|
+
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`);
|
|
137
|
+
console.log(` ✔ attribution → ${attr}`);
|
|
138
|
+
console.log(` = done: ${p.id} installed to ${target}`);
|
|
139
|
+
} finally {
|
|
140
|
+
fs.rmSync(work, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function listDir(p, filter) {
|
|
145
|
+
try { return fs.readdirSync(p, { withFileTypes: true }).filter((d) => filter(d, p)).map((d) => d.name); }
|
|
146
|
+
catch { return []; }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const pkgName = (spec) => spec.replace(/@[^/@]+$/, "");
|
|
150
|
+
|
|
151
|
+
function rtkStatus() {
|
|
152
|
+
const r = spawnSync("rtk", ["--version"], { encoding: "utf8", timeout: 5000 });
|
|
153
|
+
const v = (r.stdout || "").trim().split(/\r?\n/)[0] || "";
|
|
154
|
+
if (r.error && !v) return "rtk — not found (install: winget install rtk-ai.rtk · then: rtk init -g --opencode --hook-only --no-patch)";
|
|
155
|
+
if (!v) return "rtk — installed (version unknown)";
|
|
156
|
+
return v.startsWith("rtk") ? v : `rtk ${v}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function biliStatus() {
|
|
160
|
+
const dir = path.join(os.homedir(), ".local", "state", "billion-context", "instances");
|
|
161
|
+
const files = listDir(dir, (d) => d.isFile() && d.name.endsWith(".json"));
|
|
162
|
+
for (const f of files) {
|
|
163
|
+
try {
|
|
164
|
+
const inst = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
|
|
165
|
+
if (!inst.origin) continue;
|
|
166
|
+
const res = await fetch(`${inst.origin}/__bili/health`, { signal: AbortSignal.timeout(3000) });
|
|
167
|
+
const h = await res.json();
|
|
168
|
+
return h.ok
|
|
169
|
+
? `billion-context v${h.version} — healthy (${inst.origin})`
|
|
170
|
+
: `billion-context — responding, not healthy (${inst.origin})`;
|
|
171
|
+
} catch { /* dead instance file, try next */ }
|
|
172
|
+
}
|
|
173
|
+
if (files.length) return "billion-context — proxy not responding (restart opencode to relaunch it)";
|
|
174
|
+
return "billion-context — not detected (install: npm i -g billion-context · then: bili plugin install opencode)";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function inventory() {
|
|
178
|
+
const home = os.homedir();
|
|
179
|
+
const oc = path.join(home, ".config", "opencode");
|
|
180
|
+
const rows = [];
|
|
181
|
+
let active = new Set();
|
|
182
|
+
try {
|
|
183
|
+
const j = JSON.parse(fs.readFileSync(path.join(oc, "opencode.json"), "utf8"));
|
|
184
|
+
for (const p of j.plugin || []) { rows.push(["opencode plugin (config)", p]); active.add(pkgName(p)); }
|
|
185
|
+
for (const m of Object.keys(j.mcp || {})) rows.push(["opencode mcp", m]);
|
|
186
|
+
} catch {}
|
|
187
|
+
for (const f of listDir(path.join(oc, "plugins"), (d) => d.isFile())) rows.push(["opencode plugin (file)", f]);
|
|
188
|
+
for (const f of listDir(path.join(oc, "command"), (d) => d.isFile())) rows.push(["opencode command", f]);
|
|
189
|
+
for (const dir of [
|
|
190
|
+
path.join(oc, "skill"), path.join(oc, "skills"),
|
|
191
|
+
path.resolve(".opencode", "skill"), path.resolve(".opencode", "skills"),
|
|
192
|
+
path.join(home, ".claude", "skills")
|
|
193
|
+
]) for (const d of listDir(dir, (e) => e.isDirectory())) rows.push([`skills ${dir.replace(home, "~")}`, d]);
|
|
194
|
+
for (const d of listDir(path.join(home, ".claude", "plugins"), (e) => e.isDirectory())) rows.push(["claude plugin", d]);
|
|
195
|
+
const cache = path.join(home, ".cache", "opencode", "npm");
|
|
196
|
+
for (const d of listDir(cache, (e) => e.isDirectory())) {
|
|
197
|
+
if (d.startsWith("@")) for (const c of listDir(path.join(cache, d), (e) => e.isDirectory())) rows.push(["opencode npm cache", `${d}/${c}`]);
|
|
198
|
+
else rows.push(["opencode npm cache", d]);
|
|
199
|
+
}
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
console.log(" Token-economy stack:\n");
|
|
202
|
+
for (const line of await Promise.all([rtkStatus(), biliStatus()])) console.log(` · ${line}`);
|
|
203
|
+
console.log("\n Plugins / skills / MCP loaded across agents on this machine:\n");
|
|
204
|
+
let last = "";
|
|
205
|
+
let n = 0;
|
|
206
|
+
for (const [src, name] of rows) {
|
|
207
|
+
const k = `${src}|${name}`;
|
|
208
|
+
if (seen.has(k)) continue;
|
|
209
|
+
seen.add(k); n++;
|
|
210
|
+
if (src !== last) { console.log(` ${src}`); last = src; }
|
|
211
|
+
const orphan = src === "opencode npm cache" && !active.has(pkgName(name));
|
|
212
|
+
console.log(` · ${name}${orphan ? " (not in config — orphaned)" : ""}`);
|
|
213
|
+
}
|
|
214
|
+
console.log(`\n ${n} entries · health: ae doctor · curated extras: ae packs`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const HELP = `
|
|
218
|
+
agent-economy — request economy for coding agents
|
|
219
|
+
|
|
220
|
+
Usage: ae <command> [options] (alias: economy)
|
|
221
|
+
|
|
222
|
+
One-time install: npm i -g agent-economy (or: npx agent-economy <cmd>)
|
|
223
|
+
|
|
224
|
+
Commands:
|
|
225
|
+
init Install/update the request-economy block
|
|
226
|
+
--level off|standard|strict (default: interactive, standard)
|
|
227
|
+
--scope project|global|both (default: interactive, project)
|
|
228
|
+
--yes non-interactive defaults
|
|
229
|
+
verify Check a file carries the block; exit 1 missing, 2 below level
|
|
230
|
+
[file=AGENTS.md] --min-level standard|strict
|
|
231
|
+
doctor Detect agent tooling (rtk, billion-context, goal plugin, ...)
|
|
232
|
+
inventory Show the token-economy stack (rtk, billion-context) with health,
|
|
233
|
+
then every plugin/skill/MCP actually loaded on this machine
|
|
234
|
+
packs List curated third-party rule/skill packs
|
|
235
|
+
--install <id> [--dir <target>] fetch + install a curated pack
|
|
236
|
+
(default target: ~/.claude/skills)
|
|
237
|
+
help This text
|
|
238
|
+
|
|
239
|
+
Every model round-trip bills one request. Batch the independent ones.
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
async function ask(q, def) {
|
|
243
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
244
|
+
const a = (await rl.question(`${q} [${def}]: `)).trim() || def;
|
|
245
|
+
rl.close();
|
|
246
|
+
return a;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function main() {
|
|
250
|
+
const args = process.argv.slice(2);
|
|
251
|
+
const cmd = args[0] || "help";
|
|
252
|
+
const get = (f) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : undefined; };
|
|
253
|
+
const hasFlag = (f) => args.includes(f);
|
|
254
|
+
|
|
255
|
+
if (cmd === "init") {
|
|
256
|
+
const yes = hasFlag("--yes");
|
|
257
|
+
let level = get("--level");
|
|
258
|
+
let scope = get("--scope");
|
|
259
|
+
if (!level) level = yes ? "standard" : await ask("Economy level off/standard/strict?", "standard");
|
|
260
|
+
if (!scope) scope = yes ? "project" : await ask("Scope project/global/both?", "project");
|
|
261
|
+
if (!(level in RANK)) { console.error(`✘ unknown level "${level}"`); process.exit(2); }
|
|
262
|
+
const targets = { project: [path.resolve("AGENTS.md")], global: [globalFile()], both: [path.resolve("AGENTS.md"), globalFile()] }[scope];
|
|
263
|
+
if (!targets) { console.error(`✘ unknown scope "${scope}"`); process.exit(2); }
|
|
264
|
+
for (const f of targets) upsert(f, level);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (cmd === "verify") {
|
|
269
|
+
const VALUE_FLAGS = ["--level", "--scope", "--min-level"];
|
|
270
|
+
const positional = [];
|
|
271
|
+
for (let i = 1; i < args.length; i++) {
|
|
272
|
+
if (VALUE_FLAGS.includes(args[i])) { i++; continue; }
|
|
273
|
+
if (!args[i].startsWith("--")) positional.push(args[i]);
|
|
274
|
+
}
|
|
275
|
+
const file = positional[0] || path.resolve("AGENTS.md");
|
|
276
|
+
const min = get("--min-level") || "standard";
|
|
277
|
+
if (!(min in RANK)) { console.error(`✘ unknown --min-level "${min}"`); process.exit(2); }
|
|
278
|
+
const r = verify(file, min);
|
|
279
|
+
console.log(" " + r.msg);
|
|
280
|
+
process.exit(r.code);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (cmd === "doctor") return doctor();
|
|
284
|
+
if (cmd === "inventory") return inventory();
|
|
285
|
+
if (cmd === "packs") {
|
|
286
|
+
if (get("--install")) return installPack(get("--install"), get("--dir"));
|
|
287
|
+
return packs();
|
|
288
|
+
}
|
|
289
|
+
console.log(HELP);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
main().catch((e) => { console.error(e); process.exit(1); });
|