Skip to content

Commit 2b7626a

Browse files
committed
feat: add simulate CLI subcommand + dispatcher router (v0.1.2)
1 parent 8e9bd50 commit 2b7626a

4 files changed

Lines changed: 145 additions & 4 deletions

File tree

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ npx authority-layer doctor
3535
```
3636

3737
```
38-
AuthorityLayer Doctor authority-layer@0.1.1
38+
AuthorityLayer Doctor authority-layer@0.1.2
3939
4040
✔ Node.js version >= 18 pass
4141
✔ crypto module (sha256) pass
@@ -46,6 +46,14 @@ AuthorityLayer Doctor authority-layer@0.1.1
4646
All checks passed. AuthorityLayer is ready.
4747
```
4848

49+
Try a simulated enforcement run:
50+
51+
```bash
52+
npx authority-layer simulate
53+
```
54+
55+
Runs a canned agent loop that intentionally exceeds the budget cap — so you can see AuthorityLayer halt execution and verify the event chain without writing any code.
56+
4957
---
5058

5159
## Minimal Integration

packages/core/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
{
22
"name": "authority-layer",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"description": "Hard execution and budget limits for autonomous agents — enforced locally.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"files": [
88
"dist"
99
],
1010
"bin": {
11-
"authority-layer": "dist/doctor.js"
11+
"authority-layer": "dist/cli.js"
1212
},
1313
"scripts": {
1414
"build": "tsc -p tsconfig.json",
1515
"dev": "tsc -p tsconfig.json --watch",
1616
"typecheck": "tsc -p tsconfig.json --noEmit",
1717
"validate": "tsc -p tsconfig.json && node dist/index.js",
1818
"example": "node ../../examples/run.js",
19-
"doctor": "node dist/doctor.js",
19+
"doctor": "node dist/cli.js doctor",
20+
"simulate": "node dist/cli.js simulate",
2021
"prepublishOnly": "npm run build && npm run typecheck"
2122
},
2223
"keywords": [

packages/core/src/cli.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env node
2+
// ─────────────────────────────────────────────────────────────────────────────
3+
// authority-layer CLI dispatcher
4+
//
5+
// Routes subcommands to their implementations.
6+
// All new subcommands should be added here.
7+
//
8+
// Usage:
9+
// npx authority-layer doctor
10+
// npx authority-layer simulate
11+
// ─────────────────────────────────────────────────────────────────────────────
12+
13+
const RESET = "\x1b[0m";
14+
const BOLD = "\x1b[1m";
15+
const DIM = "\x1b[2m";
16+
const CYAN = "\x1b[1;36m";
17+
const YELLOW = "\x1b[1;33m";
18+
19+
const subcommand = process.argv[2];
20+
21+
switch (subcommand) {
22+
case "doctor": {
23+
// Doctor runs its own top-level logic — require the compiled file directly
24+
require("./doctor");
25+
break;
26+
}
27+
28+
case "simulate": {
29+
const { runSimulate } = require("./cli/simulate") as { runSimulate: () => Promise<void> };
30+
runSimulate().catch((err: unknown) => {
31+
console.error(err);
32+
process.exit(1);
33+
});
34+
break;
35+
}
36+
37+
default: {
38+
const pkg = require("../package.json") as { name: string; version: string };
39+
console.log(`\n${BOLD}${pkg.name}${RESET} ${DIM}v${pkg.version}${RESET}\n`);
40+
console.log("Usage:\n");
41+
console.log(` ${CYAN}authority-layer doctor${RESET} ${DIM}Run environment checks${RESET}`);
42+
console.log(` ${CYAN}authority-layer simulate${RESET} ${DIM}Run a live enforcement simulation${RESET}`);
43+
console.log();
44+
if (subcommand) {
45+
console.log(`${YELLOW}Unknown command: ${subcommand}${RESET}\n`);
46+
process.exit(1);
47+
}
48+
process.exit(0);
49+
}
50+
}

packages/core/src/cli/simulate.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// ─────────────────────────────────────────────────────────────────────────────
2+
// authority-layer simulate
3+
//
4+
// Runs a short simulated agent execution that intentionally exceeds the
5+
// budget cap so developers can see enforcement halt output without writing
6+
// any code.
7+
//
8+
// Usage:
9+
// npx authority-layer simulate
10+
// ─────────────────────────────────────────────────────────────────────────────
11+
12+
import { AuthorityLayer } from "../AuthorityLayer";
13+
import { EnforcementHalt } from "../EnforcementHalt";
14+
15+
const RESET = "\x1b[0m";
16+
const GREEN = "\x1b[1;32m";
17+
const RED = "\x1b[1;31m";
18+
const YELLOW = "\x1b[1;33m";
19+
const BOLD = "\x1b[1m";
20+
const DIM = "\x1b[2m";
21+
22+
export async function runSimulate(): Promise<void> {
23+
console.log(`\n${BOLD}AuthorityLayer Simulation${RESET}`);
24+
console.log(`${DIM}Demonstrating runtime enforcement.${RESET}\n`);
25+
26+
const authority = new AuthorityLayer({
27+
budget: { dailyUSD: 0.05 },
28+
loopGuard: { maxToolCallsPerRun: 10 },
29+
toolThrottle: { maxCallsPerMinute: 100 },
30+
});
31+
32+
console.log(`${DIM}Config: $0.05 budget cap · 10 calls/run max · 100 calls/min${RESET}\n`);
33+
console.log("Starting simulated agent run...\n");
34+
35+
let callCount = 0;
36+
37+
try {
38+
await authority.wrap(async () => {
39+
// 7 calls × $0.01 = $0.07 — exceeds the $0.05 cap after call 6
40+
for (let i = 1; i <= 7; i++) {
41+
callCount++;
42+
await authority.tool("llm.chat", async () => "LLM response");
43+
console.log(` Tool call #${i} ${DIM}llm.chat${RESET}`);
44+
authority.recordSpend(0.01);
45+
}
46+
});
47+
} catch (err) {
48+
if (err instanceof EnforcementHalt) {
49+
console.log(`\n${RED}${BOLD}⛔ Execution halted by AuthorityLayer${RESET}\n`);
50+
const h = err.enforcement;
51+
const display = {
52+
status: h.status,
53+
reason: h.reason,
54+
limit: Math.round(h.limit * 100) / 100,
55+
spent: Math.round(h.spent * 100) / 100,
56+
event_id: h.event_id,
57+
};
58+
console.log(YELLOW + JSON.stringify(display, null, 2) + RESET);
59+
} else {
60+
throw err;
61+
}
62+
}
63+
64+
// ── Integrity check ───────────────────────────────────────────────────────
65+
66+
const valid = authority.verifyChain();
67+
const chain = authority.getChain();
68+
69+
console.log(`\n${DIM}─── Event chain ${"─".repeat(44)}${RESET}`);
70+
for (const event of chain) {
71+
const ts = new Date(event.timestamp).toISOString().slice(11, 23);
72+
console.log(` ${DIM}[${ts}]${RESET} ${event.type.padEnd(20)} ${DIM}${event.event_id}${RESET}`);
73+
}
74+
75+
console.log();
76+
if (valid) {
77+
console.log(`Event chain integrity: ${GREEN}${BOLD}VERIFIED${RESET}`);
78+
} else {
79+
console.log(`Event chain integrity: ${RED}${BOLD}TAMPERED${RESET}`);
80+
}
81+
console.log(`Total events: ${chain.length}\n`);
82+
}

0 commit comments

Comments
 (0)