prae-cli 0.10.0 → 0.10.2

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.
@@ -26,23 +26,21 @@ guide in a temporary project:
26
26
  `claude -p` refused them. Nothing protected changed on disk.
27
27
  - **Record.** `prae verify` passed the chain, with 25 of 25 signatures.
28
28
 
29
- ## 1. Install (one command)
29
+ ## 1. Install
30
30
 
31
- You need Node 22.19 or later and pnpm 10 (`corepack enable`). Until the
32
- release that carries this hook is on npm (`prae-cli` 0.9.2 does not have it),
33
- run it from a checkout of this repository, built once:
31
+ You need Node 22.19 or later. Install the CLI globally, then preview the
32
+ change and apply it:
34
33
 
35
34
  ```bash
36
- git clone https://github.com/taltara/prae-harness && cd prae-harness && pnpm install && pnpm --filter prae-cli build
37
- PRAE="node $PWD/packages/cli/lib/bin.js" # used below; after the npm release this is just `prae`
35
+ npm install -g prae-cli # prae-cli 0.10.0 or later carries the hook
36
+ prae init claude-code --scope user # prints the settings diff, writes nothing
37
+ prae init claude-code --scope user --apply # installs, shadow mode
38
38
  ```
39
39
 
40
- Then preview the change, and apply it:
41
-
42
- ```bash
43
- $PRAE init claude-code --scope user # prints the settings diff, writes nothing
44
- $PRAE init claude-code --scope user --apply # installs, shadow mode
45
- ```
40
+ Install globally rather than with `npx prae-cli init …`: the settings entry
41
+ runs the `prae` it was installed from, and an `npx` copy lives in npm's
42
+ temporary cache, which npm may clear — the hook would then stop running, and
43
+ Claude Code 2.1.233 lets every call through when a hook cannot start.
46
44
 
47
45
  You must name a target. `--scope user` is `~/.claude/settings.json`, which
48
46
  covers every project. `--scope project` is `./.claude/settings.json`.
@@ -55,19 +53,22 @@ hand-formatted file shows layout changes in the diff. Its values do not change.
55
53
 
56
54
  ```json
57
55
  "PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "timeout": 10,
58
- "command": "'/opt/homebrew/bin/node' '…/packages/cli/lib/bin.js' hook claude-code --shadow --pack '~/.prae/claude-code/prae-baseline.prae.json' --ledger '~/.prae/claude-code/ledger.jsonl' --key '~/.prae/claude-code/prae-signing.pem' --settings '~/.claude/settings.json'" }] }]
56
+ "command": "'/opt/homebrew/bin/node' '…/lib/node_modules/prae-cli/lib/bin.js' hook claude-code --shadow --pack '~/.prae/claude-code/prae-baseline.prae.json' --ledger '~/.prae/claude-code/ledger.jsonl' --key '~/.prae/claude-code/prae-signing.pem' --settings '~/.claude/settings.json'" }] }]
59
57
  ```
60
58
 
61
59
  The real file holds absolute paths. The command names the node binary and
62
60
  PRAE's own script by absolute path, so it does not depend on `PATH`. If you
63
- move the checkout, run `init` again. Claude Code reloads settings on its own,
61
+ upgrade or reinstall prae-cli, or switch Node versions, run `init` again. Claude Code reloads settings on its own,
64
62
  so new tool calls go through PRAE without a restart.
65
63
 
66
64
  Options:
67
65
 
68
66
  - `--pack <file>`: use your own pack. The default copies the built-in baseline
69
- (8 rules: download-and-execute, `rm -rf`, privilege escalation, block devices,
70
- credential files, writes outside the workspace, force-push, publishes).
67
+ (13 rules: download-and-execute, `rm -rf`, privilege escalation, block devices,
68
+ credential files, writes outside the workspace, force-push, publishes, and
69
+ credential stores — the macOS Keychain through `security` and its files,
70
+ `.netrc`, `.git-credentials`, `.npmrc`, Docker, kube, GnuPG, gh and gcloud
71
+ config — read directly or named in a shell command).
71
72
  `prae check <file>` summarises a pack.
72
73
  - `--ledger <file>`: write the ledger somewhere else.
73
74
  - `--dir <dir>`: keep PRAE's state somewhere other than `~/.prae/claude-code/`.
@@ -95,9 +96,9 @@ Read the record:
95
96
 
96
97
  ```bash
97
98
  P=~/.prae/claude-code
98
- $PRAE verify $P/ledger.jsonl --key $P/prae-signing.pub.pem
99
- $PRAE report $P/ledger.jsonl --pack $P/prae-baseline.prae.json --out report.html
100
- $PRAE console $P/ledger.jsonl --pack $P/prae-baseline.prae.json --out console/
99
+ prae verify $P/ledger.jsonl --key $P/prae-signing.pub.pem
100
+ prae report $P/ledger.jsonl --pack $P/prae-baseline.prae.json --out report.html
101
+ prae console $P/ledger.jsonl --pack $P/prae-baseline.prae.json --out console/
101
102
  ```
102
103
 
103
104
  In the report and the console, a shadow verdict is marked `· SHADOW`. It is
@@ -106,7 +107,7 @@ never counted as vetoed or held.
106
107
  ## 3. From shadow to enforce
107
108
 
108
109
  ```bash
109
- $PRAE init claude-code --scope user --enforce --apply
110
+ prae init claude-code --scope user --enforce --apply
110
111
  ```
111
112
 
112
113
  This replaces PRAE's entry. It does not add a second one. In enforce mode:
@@ -124,7 +125,7 @@ warning and records a `PRAE-ERROR` row marked `shadow`.
124
125
  ## 4. Uninstall
125
126
 
126
127
  ```bash
127
- $PRAE init claude-code --scope user --remove --apply
128
+ prae init claude-code --scope user --remove --apply
128
129
  ```
129
130
 
130
131
  This removes PRAE's hook entry and nothing else. The ledger and the key stay
@@ -0,0 +1,89 @@
1
+ import { a as PATH_ARGS, s as relativeTo } from "./src-CZYvvEn5.js";
2
+ import { capabilityForTool } from "capmark";
3
+
4
+ //#region ../dsh/src/facts.ts
5
+ function resolveArgs(args, resolvePath, cwd) {
6
+ if (resolvePath === void 0 && cwd === void 0) return args;
7
+ const out = { ...args };
8
+ for (const key of PATH_ARGS) {
9
+ const value = out[key];
10
+ if (typeof value !== "string") continue;
11
+ let path = value;
12
+ if (resolvePath !== void 0) try {
13
+ path = resolvePath(path);
14
+ } catch {}
15
+ out[key] = cwd === void 0 ? path : relativeTo(path, cwd);
16
+ }
17
+ return out;
18
+ }
19
+ /**
20
+ * Claude Code names its built-in tools in PascalCase (`Read`, `Bash`, …),
21
+ * which capmark's catalogue does not list. Each takes the capability of its
22
+ * lowercase counterpart; without this a Claude Code call carries no
23
+ * capability, and the rules scoped by one (paths, egress, the shell) never
24
+ * see it. An MCP tool, `mcp__<server>__<tool>`, takes its tool's.
25
+ */
26
+ const CLAUDE_CODE = {
27
+ Read: "read",
28
+ Write: "write",
29
+ Edit: "edit",
30
+ MultiEdit: "edit",
31
+ NotebookEdit: "edit",
32
+ Bash: "bash",
33
+ PowerShell: "bash",
34
+ Grep: "grep",
35
+ Glob: "glob",
36
+ LS: "glob",
37
+ WebFetch: "web_fetch",
38
+ WebSearch: "web_search"
39
+ };
40
+ /**
41
+ * MCP tools whose names capmark's catalogue does not list, from the
42
+ * reference servers an agent most often mounts (modelcontextprotocol
43
+ * `filesystem` and `fetch`) and `prae mcp`'s own. Applied only after an
44
+ * `mcp__<server>__` prefix: without it, a Claude Code session reading
45
+ * `.env` through `mcp__filesystem__read_file` carries no capability, and
46
+ * no `fs:read` rule sees it.
47
+ */
48
+ const MCP_TOOLS = {
49
+ read_file: "read",
50
+ read_text_file: "read",
51
+ read_media_file: "read",
52
+ list_directory: "glob",
53
+ list_dir: "glob",
54
+ directory_tree: "glob",
55
+ search_files: "glob",
56
+ get_file_info: "glob",
57
+ write_file: "write",
58
+ edit_file: "edit",
59
+ create_directory: "write",
60
+ fetch: "web_fetch"
61
+ };
62
+ function capabilityOf(tool) {
63
+ const direct = capabilityForTool(tool)?.id;
64
+ if (direct !== void 0) return direct;
65
+ const alias = CLAUDE_CODE[tool];
66
+ if (alias !== void 0) return capabilityForTool(alias)?.id;
67
+ const mcp = tool.startsWith("mcp__") ? tool.lastIndexOf("__") : -1;
68
+ if (mcp <= 5) return void 0;
69
+ const name = tool.slice(mcp + 2);
70
+ return capabilityOf(MCP_TOOLS[name] ?? name);
71
+ }
72
+ function factsFor(exec, options = {}) {
73
+ const args = exec.arguments ?? {};
74
+ const capability = capabilityOf(exec.name);
75
+ const plugin = options.owners?.get(exec.name);
76
+ const id = exec.agent?.id;
77
+ const agent = typeof id === "string" ? id : void 0;
78
+ return {
79
+ tool: exec.name,
80
+ ...capability === void 0 ? {} : { capability },
81
+ ...plugin === void 0 ? {} : { plugin },
82
+ ...agent === void 0 ? {} : { agent },
83
+ args: resolveArgs(args, options.resolvePath, options.cwd),
84
+ ...options.counts === void 0 ? {} : { counts: options.counts }
85
+ };
86
+ }
87
+
88
+ //#endregion
89
+ export { factsFor as n, capabilityOf as t };
@@ -1,98 +1,11 @@
1
- import { i as relativeTo, n as PATH_ARGS, o as segments, t as evaluate } from "./evaluate-DhoGQw0Z.js";
2
- import "./frameworks-BgZHGKiH.js";
3
- import { t as parsePack } from "./pack-h5n43S0F.js";
4
- import { n as Ledger } from "./src-Cy-Acrgj.js";
5
- import { a as scan, n as MAX_SCAN_BYTES } from "./src-BuaOiL2v.js";
1
+ import { a as PATH_ARGS, i as evaluate, l as segments, s as relativeTo, t as parsePack } from "./src-CZYvvEn5.js";
2
+ import { n as Ledger } from "./src-CuiWC80o.js";
3
+ import { n as factsFor, t as capabilityOf } from "./facts-DkTRO2p0.js";
4
+ import { a as scan, n as MAX_SCAN_BYTES } from "./src-9phPLfAL.js";
6
5
  import { lstatSync, readFileSync, readlinkSync, realpathSync, statSync } from "node:fs";
7
6
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
8
7
  import { createHash } from "node:crypto";
9
- import { capabilityForTool } from "capmark";
10
8
 
11
- //#region ../dsh/src/facts.ts
12
- function resolveArgs(args, resolvePath, cwd) {
13
- if (resolvePath === void 0 && cwd === void 0) return args;
14
- const out = { ...args };
15
- for (const key of PATH_ARGS) {
16
- const value = out[key];
17
- if (typeof value !== "string") continue;
18
- let path = value;
19
- if (resolvePath !== void 0) try {
20
- path = resolvePath(path);
21
- } catch {}
22
- out[key] = cwd === void 0 ? path : relativeTo(path, cwd);
23
- }
24
- return out;
25
- }
26
- /**
27
- * Claude Code names its built-in tools in PascalCase (`Read`, `Bash`, …),
28
- * which capmark's catalogue does not list. Each takes the capability of its
29
- * lowercase counterpart; without this a Claude Code call carries no
30
- * capability, and the rules scoped by one (paths, egress, the shell) never
31
- * see it. An MCP tool, `mcp__<server>__<tool>`, takes its tool's.
32
- */
33
- const CLAUDE_CODE = {
34
- Read: "read",
35
- Write: "write",
36
- Edit: "edit",
37
- MultiEdit: "edit",
38
- NotebookEdit: "edit",
39
- Bash: "bash",
40
- PowerShell: "bash",
41
- Grep: "grep",
42
- Glob: "glob",
43
- LS: "glob",
44
- WebFetch: "web_fetch",
45
- WebSearch: "web_search"
46
- };
47
- /**
48
- * MCP tools whose names capmark's catalogue does not list, from the
49
- * reference servers an agent most often mounts (modelcontextprotocol
50
- * `filesystem` and `fetch`) and `prae mcp`'s own. Applied only after an
51
- * `mcp__<server>__` prefix: without it, a Claude Code session reading
52
- * `.env` through `mcp__filesystem__read_file` carries no capability, and
53
- * no `fs:read` rule sees it.
54
- */
55
- const MCP_TOOLS = {
56
- read_file: "read",
57
- read_text_file: "read",
58
- read_media_file: "read",
59
- list_directory: "glob",
60
- list_dir: "glob",
61
- directory_tree: "glob",
62
- search_files: "glob",
63
- get_file_info: "glob",
64
- write_file: "write",
65
- edit_file: "edit",
66
- create_directory: "write",
67
- fetch: "web_fetch"
68
- };
69
- function capabilityOf(tool) {
70
- const direct = capabilityForTool(tool)?.id;
71
- if (direct !== void 0) return direct;
72
- const alias = CLAUDE_CODE[tool];
73
- if (alias !== void 0) return capabilityForTool(alias)?.id;
74
- const mcp = tool.startsWith("mcp__") ? tool.lastIndexOf("__") : -1;
75
- if (mcp <= 5) return void 0;
76
- const name = tool.slice(mcp + 2);
77
- return capabilityOf(MCP_TOOLS[name] ?? name);
78
- }
79
- function factsFor(exec, options = {}) {
80
- const args = exec.arguments ?? {};
81
- const capability = capabilityOf(exec.name);
82
- const plugin = options.owners?.get(exec.name);
83
- const id = exec.agent?.id;
84
- const agent = typeof id === "string" ? id : void 0;
85
- return {
86
- tool: exec.name,
87
- ...capability === void 0 ? {} : { capability },
88
- ...plugin === void 0 ? {} : { plugin },
89
- ...agent === void 0 ? {} : { agent },
90
- args: resolveArgs(args, options.resolvePath, options.cwd),
91
- ...options.counts === void 0 ? {} : { counts: options.counts }
92
- };
93
- }
94
-
95
- //#endregion
96
9
  //#region ../dsh/src/judge.ts
97
10
  /**
98
11
  * Judge one call against a pack, without a harness anywhere near it.
package/lib/index.js CHANGED
@@ -1,8 +1,7 @@
1
- import "./evaluate-DhoGQw0Z.js";
2
- import "./frameworks-BgZHGKiH.js";
3
- import "./pack-h5n43S0F.js";
4
- import "./src-Cy-Acrgj.js";
5
- import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-DPldZ2ND.js";
6
- import "./views-CUcixte0.js";
1
+ import "./src-CZYvvEn5.js";
2
+ import "./src-CuiWC80o.js";
3
+ import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-BTVEk0Fh.js";
4
+ import "./facts-DkTRO2p0.js";
5
+ import "./views-endiZu1Z.js";
7
6
 
8
7
  export { applyWrite, defaultProfile, dshHome, main, planWrite, praeRow, profileAt };
@@ -1,6 +1,5 @@
1
- import "./frameworks-BgZHGKiH.js";
2
- import { t as parsePack } from "./pack-h5n43S0F.js";
3
- import { d as generateKeypair, u as fingerprint } from "./src-Cy-Acrgj.js";
1
+ import { t as parsePack } from "./src-CZYvvEn5.js";
2
+ import { d as generateKeypair, u as fingerprint } from "./src-CuiWC80o.js";
4
3
  import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
5
4
  import { delimiter, dirname, join, resolve } from "node:path";
6
5
  import { homedir } from "node:os";
@@ -228,10 +227,17 @@ function applyInit(plan) {
228
227
  }
229
228
  return print === void 0 ? {} : { fingerprint: print };
230
229
  }
230
+ /** Whether a script path is inside npm's temporary npx cache (`…/_npx/<hash>/…`). */
231
+ function fromNpxCache(bin) {
232
+ return /[\\/]_npx[\\/]/.test(bin);
233
+ }
231
234
  /** `prae init claude-code …`: print the diff; with --apply, write it. */
232
235
  function initClaudeCode(argv, context) {
233
236
  const plan = planInit(argv, context);
234
237
  const removing = argv.includes("--remove");
238
+ if (!removing && fromNpxCache(context.bin)) console.error(`prae: warning: this prae runs from npm's temporary npx cache (${context.bin}).\n The hook entry would point there, and npm may clear that cache: the hook would
239
+ stop running, and Claude Code lets a call through when its hook cannot start.
240
+ Install it to stay: npm install -g prae-cli, then run prae init claude-code again.`);
235
241
  console.log(`settings ${plan.settings}`);
236
242
  if (!removing) {
237
243
  console.log(`mode ${argv.includes("--enforce") ? "enforce · deny and ask are returned to Claude Code; a gate error refuses the call" : "shadow · every verdict sealed, every call proceeds"}`);
@@ -263,4 +269,4 @@ function initClaudeCode(argv, context) {
263
269
  }
264
270
 
265
271
  //#endregion
266
- export { HOOK_TIMEOUT, MARKER, applyInit, initClaudeCode, lineDiff, planInit, quote, settingsTarget, stableNode, withPrae, withoutPrae };
272
+ export { HOOK_TIMEOUT, MARKER, applyInit, fromNpxCache, initClaudeCode, lineDiff, planInit, quote, settingsTarget, stableNode, withPrae, withoutPrae };
@@ -1,5 +1,5 @@
1
- import "./frameworks-BgZHGKiH.js";
2
- import { c as renderNav, l as renderShell, s as esc, t as navFor } from "./views-CUcixte0.js";
1
+ import "./src-CZYvvEn5.js";
2
+ import { c as renderNav, l as renderShell, s as esc, t as navFor } from "./views-endiZu1Z.js";
3
3
 
4
4
  //#region src/console/instructions.ts
5
5
  const STYLE = `
@@ -1,5 +1,5 @@
1
- import { t as evaluate } from "./evaluate-DhoGQw0Z.js";
2
- import { a as scan, n as MAX_SCAN_BYTES } from "./src-BuaOiL2v.js";
1
+ import { i as evaluate } from "./src-CZYvvEn5.js";
2
+ import { a as scan, n as MAX_SCAN_BYTES } from "./src-9phPLfAL.js";
3
3
  import { readFile, readdir, stat } from "node:fs/promises";
4
4
  import { resolve, sep } from "node:path";
5
5
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";