@pretense/cli 0.6.12 → 0.6.14

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/README.md CHANGED
@@ -35,6 +35,10 @@ pretending.
35
35
  docs-exec:skip "curl -fsSL https://www.pretense.ai/install.sh | sh" — downloads and installs a binary from the network
36
36
  docs-exec:skip "pretense start" — long-running server; it would outlive the suite holding port 9339
37
37
  docs-exec:skip "pretense start --port 9500" — long-running server; it would outlive the suite holding port 9500
38
+ docs-exec:skip `pretense run claude "explain this repo"` — starts a proxy and a third-party tool that would outlive the suite
39
+ docs-exec:skip "pretense run cursor" — starts a proxy and a third-party tool that would outlive the suite
40
+ docs-exec:skip `pretense run gemini "summarise the changes"` — starts a proxy and a third-party tool that would outlive the suite
41
+ docs-exec:skip "pretense run --provider openai mytool" — starts a proxy and a third-party tool that would outlive the suite
38
42
  docs-exec:skip "curl -s http://localhost:9339/health" — needs the proxy from the preceding line, which cannot be started here
39
43
  docs-exec:skip "claude" — third-party CLI that is not a dependency of this repo
40
44
  docs-exec:skip `curl -fsSLO "${BASE}/SHA256SUMS"` — network fetch from the release host
@@ -103,20 +107,30 @@ binary has no Node requirement.
103
107
 
104
108
  ```bash
105
109
  pretense scan <file|dir> # what would leak
106
- pretense mutate <file> --map m.json -i # swap it before the AI sees it
107
- pretense reverse <file> --map m.json -i # put the real values back
110
+ pretense mutate <file> # swap secrets in place, keep a reversal map (auto)
111
+ pretense reverse <file> # put the real values back (finds the map itself)
108
112
  pretense start # proxy mode (npm install only, see below)
109
113
  pretense scan <file> --policy hipaa # the flag most people type
110
114
  ```
111
115
 
116
+ `mutate <file>` then `reverse <file>` is a byte-exact round trip with **no
117
+ flags**: `mutate` writes an encrypted reversal map to
118
+ `~/.pretense/maps/<hash>.json` (derived from the file's absolute path, mode
119
+ 0600), and `reverse` recomputes that same location and finds it. To run any AI
120
+ tool through the mutating proxy with zero setup, use `pretense run` (see
121
+ [Zero-export proxy runner](#zero-export-proxy-runner)).
122
+
112
123
  Two honest caveats about the verbs:
113
124
 
114
125
  - `mutate` and `reverse` take **one file at a time**. `pretense mutate .` and
115
126
  `pretense mutate src` both fail with `Cannot read file: .` and exit 1.
116
127
  Directory mutation is not available yet. `scan` does accept a directory.
117
- - `mutate` **previews by default** it prints to stdout and leaves the file
118
- untouched. `-i` writes in place. `-i` without `--map` is irreversible; there
119
- is no global map store to recover from.
128
+ - `mutate` **mutates in place by default** and auto-writes the reversal map, so
129
+ the round trip is reversible with no `--map` on either side. Use `--stdout` to
130
+ preview to stdout without touching the file, `-o <path>` to write a copy, and
131
+ `--map <path>` to override where the map is written. The map lives under
132
+ `~/.pretense/maps`, deliberately out of the working tree so it is never
133
+ committed by accident.
120
134
 
121
135
  ## The 30-second walkthrough
122
136
 
@@ -273,6 +287,28 @@ claude
273
287
 
274
288
  `OPENAI_BASE_URL` works the same way, as does any client that accepts a base URL.
275
289
 
290
+ ## Zero-export proxy runner
291
+
292
+ The manual dance above — start a proxy, remember the port, export the *right*
293
+ base-URL variable for your tool, remember to stop it — is what `pretense run`
294
+ does for you. It ensures a healthy proxy exists (reusing a running one, else
295
+ starting one on an auto-found free port), sets the correct base-URL env var, runs
296
+ your tool, and stops the proxy it started when the tool exits.
297
+
298
+ ```bash
299
+ pretense run claude "explain this repo" # Claude → ANTHROPIC_BASE_URL
300
+ pretense run cursor # Cursor / OpenAI-compatible → OPENAI_BASE_URL
301
+ pretense run gemini "summarise the changes" # Gemini → GEMINI_BASE_URL
302
+ pretense run --provider openai mytool # force the provider mapping
303
+ ```
304
+
305
+ `pretense with` is an alias for `pretense run`. The tool name selects the
306
+ provider automatically (`claude` → Anthropic, `cursor`/`code` → OpenAI-compatible,
307
+ `gemini` → Google); pass `--provider anthropic|openai|google` to override it, and
308
+ `--port <n>` to prefer a port when a proxy has to be started. If the tool is not
309
+ on your `PATH`, `run` prints an install hint and exits non-zero instead of
310
+ crashing — and it never tears down a proxy it did not start.
311
+
276
312
  Read this before you rely on the proxy:
277
313
 
278
314
  - **npm install only.** `start` crashes on the `curl | sh` binary
@@ -383,8 +419,10 @@ pretense init # 0 — writes pretense.yaml + .prete
383
419
  pretense scan <file|dir> # 0 clean / 1 findings
384
420
  pretense scan <file> --policy <preset> # 0 clean / 1 findings
385
421
  pretense scan <file> --severity high # 0 clean / 1 findings
386
- pretense mutate <file> # 0 — preview to stdout, file untouched
387
- pretense mutate <file> --map m.json -i # 0 — in place, reversible
422
+ pretense mutate <file> --stdout # 0 — preview to stdout, file untouched
423
+ pretense mutate <file> # 0 — in place + auto-map (the default)
424
+ pretense reverse <file> # 0 — in place, auto-finds the map
425
+ pretense mutate <file> --map m.json -i # 0 — explicit map-path override
388
426
  pretense reverse <file> --map m.json -i # 0 — byte-exact restore
389
427
  pretense status # 0
390
428
  pretense audit --limit 20 # 0
@@ -451,9 +489,13 @@ has edited will not reverse today. Token-substitution reversal is on the roadmap
451
489
 
452
490
  ## Where the reversal map lives
453
491
 
454
- In the CLI path, a map exists only where you ask for one, via
455
- `pretense mutate --map <path>`. It is AES-256-GCM encrypted and written mode
456
- `0600`.
492
+ In the CLI path, `pretense mutate <file>` auto-writes the map to a deterministic
493
+ per-file location `~/.pretense/maps/<sha256(absolute-path)>.json` and
494
+ `pretense reverse <file>` recomputes that same path to find it, so neither verb
495
+ needs `--map`. Pass `pretense mutate --map <path>` to override the location. The
496
+ map is AES-256-GCM encrypted and written mode `0600`, and it lives under
497
+ `~/.pretense/maps` (not in the working tree) so it is never committed by
498
+ accident.
457
499
 
458
500
  In the proxy path the reverse map is held in memory for the life of the request
459
501
  and is never written to disk — deliberately, so Pretense never becomes an
@@ -17,7 +17,7 @@ function versionFromPackageJson() {
17
17
  return void 0;
18
18
  }
19
19
  }
20
- var PRETENSE_VERSION = (true ? "0.6.12" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
20
+ var PRETENSE_VERSION = (true ? "0.6.14" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
21
21
 
22
22
  export {
23
23
  PRETENSE_VERSION
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-K3L4E6HN.js";
4
+ } from "./chunk-2WW3XY6A.js";
5
5
  import {
6
6
  __commonJS,
7
7
  __toESM,
@@ -221,8 +221,8 @@ var require_brace_expansion = __commonJS({
221
221
 
222
222
  // src/index.ts
223
223
  init_esm_shims();
224
- import { Command as Command21 } from "commander";
225
- import chalk20 from "chalk";
224
+ import { Command as Command22 } from "commander";
225
+ import chalk21 from "chalk";
226
226
 
227
227
  // src/commands/init.ts
228
228
  init_esm_shims();
@@ -12171,6 +12171,19 @@ var SECRET_PATTERNS = [
12171
12171
  defaultAction: "block",
12172
12172
  pattern: /\bgithub_pat_[A-Za-z0-9_]{22,}\b/g
12173
12173
  },
12174
+ // FIX 1 (v0.6.13) — HuggingFace access tokens. `hf_` user and fine-grained
12175
+ // access tokens are `hf_` + 34+ base62 chars. Confirmed leaking on 0.6.12:
12176
+ // an `hf_` token in bare prose scanned clean (a real credential egressed to
12177
+ // the LLM) because no `hf_`/`huggingface` pattern existed. The `hf_` prefix
12178
+ // plus a 34+ base62 body is a near-zero-false-positive signal. Critical/block
12179
+ // because a live model-registry token grants push access to private models.
12180
+ {
12181
+ name: "huggingface-token",
12182
+ category: "secret",
12183
+ severity: "critical",
12184
+ defaultAction: "block",
12185
+ pattern: /\bhf_[A-Za-z0-9]{34,}\b/g
12186
+ },
12174
12187
  {
12175
12188
  name: "gitlab-token",
12176
12189
  category: "secret",
@@ -12745,8 +12758,9 @@ var ENV_PATTERNS = [
12745
12758
  // variable references (`$VAR`, `${VAR}`, `process.env.X`, `os.environ…`),
12746
12759
  // connection URLs (handled by `database-url`), and values that begin with a
12747
12760
  // recognised structured-secret prefix (Stripe `sk_`/`pk_`/`rk_`, OpenAI
12748
- // `sk-`, Slack `xox*`, GitHub `gh*_`/`glpat-`, AWS `AKIA`, Google `AIza`,
12749
- // Stripe webhook `whsec_`) are deliberately skipped so a generic
12761
+ // `sk-`, Slack `xox*`, GitHub `gh*_`/`glpat-`, HuggingFace `hf_`, AWS
12762
+ // `AKIA`, Google `AIza`, Stripe webhook `whsec_`) are deliberately skipped
12763
+ // so a generic
12750
12764
  // `KEY=variable` reference is not flagged and the dedicated, higher-fidelity
12751
12765
  // patterns above own those matches. Placed last so more-specific patterns
12752
12766
  // win overlap deduplication on ties.
@@ -12760,7 +12774,7 @@ var ENV_PATTERNS = [
12760
12774
  // specific env-var names rather than generic secret-label tails, so they do
12761
12775
  // not belong in the shared vocabulary.
12762
12776
  pattern: new RegExp(
12763
- `(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
12777
+ `(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|hf_|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
12764
12778
  "gi"
12765
12779
  ),
12766
12780
  // PRECISION: reject `PASSWORD=changeme`, `PASSWORD=<your-password>` and
@@ -13827,6 +13841,8 @@ function overlapsAny(m, sorted) {
13827
13841
  }
13828
13842
  async function scanLevel2(text, language, opts = {}) {
13829
13843
  const lang = language ?? opts.language ?? "typescript";
13844
+ const t0 = performance.now();
13845
+ const elapsed = () => Math.round((performance.now() - t0) * 100) / 100;
13830
13846
  const base = opts.presidioUrl ? await scanWithPresidio(text, opts.presidioUrl, opts) : scan(text, opts);
13831
13847
  const l1Matches = base.matches.map((m) => ({ ...m, level: 1 }));
13832
13848
  const l1Findings = base.findings.map((f) => ({ ...f, level: 1 }));
@@ -13834,6 +13850,8 @@ async function scanLevel2(text, language, opts = {}) {
13834
13850
  ...base,
13835
13851
  matches: l1Matches,
13836
13852
  findings: l1Findings,
13853
+ // Real wall time up to this early return, not the L1 sub-phase alone.
13854
+ durationMs: elapsed(),
13837
13855
  l2: status
13838
13856
  });
13839
13857
  const native = loadNative();
@@ -13911,7 +13929,9 @@ async function scanLevel2(text, language, opts = {}) {
13911
13929
  matches: merged,
13912
13930
  findings: mergedFindings,
13913
13931
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
13914
- durationMs: base.durationMs,
13932
+ // Real elapsed wall time for the full L1+L2 scan (was base.durationMs, the
13933
+ // L1-only time, which undercounted the AST work ~25x — see FIX 3/TASK-16).
13934
+ durationMs: elapsed(),
13915
13935
  patternCount: base.patternCount,
13916
13936
  // Bounded-work state is a property of the underlying scan and MUST survive
13917
13937
  // this merge — dropping it here would re-create the silent-incomplete hole
@@ -16425,11 +16445,11 @@ function versionFromCliPackageJson() {
16425
16445
  return void 0;
16426
16446
  }
16427
16447
  function productVersion() {
16428
- return override ?? (true ? "0.6.12" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16448
+ return override ?? (true ? "0.6.14" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16429
16449
  }
16430
16450
  var UNKNOWN_COMMIT = "unknown";
16431
16451
  function bakedCommitSha() {
16432
- return "cd2bfc0670d3c8aadaa5a163493ba152391489a1".length > 0 ? "cd2bfc0670d3c8aadaa5a163493ba152391489a1" : UNKNOWN_COMMIT;
16452
+ return "13f5fb3bd88295258e3733458b63315e9382678e".length > 0 ? "13f5fb3bd88295258e3733458b63315e9382678e" : UNKNOWN_COMMIT;
16433
16453
  }
16434
16454
  var FEATURE_TIERS = {
16435
16455
  compliance_export: "pro",
@@ -17881,7 +17901,10 @@ function startProxy(opts = {}) {
17881
17901
  function renderClientEnvLines(port, protocol) {
17882
17902
  const base = `${protocol}://localhost:${port}`;
17883
17903
  return [
17884
- " Point your AI client at Pretense:",
17904
+ " Easiest \u2014 let Pretense wire the env var and run the tool for you:",
17905
+ ' pretense run claude "build me X" # or: cursor, gemini, any tool',
17906
+ "",
17907
+ " Or point your AI client at Pretense yourself:",
17885
17908
  ` ANTHROPIC_BASE_URL=${base}`,
17886
17909
  ` OPENAI_BASE_URL=${base}/v1`,
17887
17910
  ` GOOGLE_GEMINI_BASE_URL=${base}`
@@ -17911,6 +17934,33 @@ if (isMain) {
17911
17934
  startProxy();
17912
17935
  }
17913
17936
 
17937
+ // src/free-port.ts
17938
+ init_esm_shims();
17939
+ import net from "net";
17940
+ function isPortFree(port) {
17941
+ return new Promise((resolve7) => {
17942
+ const srv = net.createServer();
17943
+ srv.once("error", () => {
17944
+ srv.close(() => resolve7(false));
17945
+ resolve7(false);
17946
+ });
17947
+ srv.once("listening", () => {
17948
+ srv.close(() => resolve7(true));
17949
+ });
17950
+ srv.listen(port);
17951
+ });
17952
+ }
17953
+ async function findFreePort(start, opts = {}) {
17954
+ const maxTries = opts.maxTries ?? 100;
17955
+ const end = Math.min(start + maxTries - 1, 65535);
17956
+ for (let p = start; p <= end; p += 1) {
17957
+ if (await isPortFree(p)) return p;
17958
+ }
17959
+ throw new Error(
17960
+ `No free port found in [${start}, ${end}] after ${maxTries} attempts.`
17961
+ );
17962
+ }
17963
+
17914
17964
  // src/proxy-pidfile.ts
17915
17965
  init_esm_shims();
17916
17966
  import { existsSync as existsSync7, readFileSync as readFileSync6, unlinkSync } from "fs";
@@ -18034,15 +18084,15 @@ function explainAddrInUse(port) {
18034
18084
  );
18035
18085
  }
18036
18086
  function startCommand() {
18037
- return new Command2("start").description("Start the Pretense proxy server").option("-p, --port <port>", "Port to listen on", "9339").option("--verbose", "Enable verbose logging", false).option("-c, --config <path>", "Path to pretense.yaml").action((opts) => {
18038
- const port = parseInt(opts.port, 10);
18087
+ return new Command2("start").description("Start the Pretense proxy server").option("-p, --port <port>", "Port to listen on", "9339").option("--verbose", "Enable verbose logging", false).option("-c, --config <path>", "Path to pretense.yaml").option("--strict-port", "Fail if the requested port is busy instead of auto-advancing", false).action(async (opts) => {
18088
+ const requested = parseInt(opts.port, 10);
18039
18089
  const existing = readProxyRecord();
18040
18090
  if (existing) {
18041
18091
  if (isProcessAlive(existing.pid)) {
18042
- if (existing.port === port) {
18092
+ if (existing.port === requested) {
18043
18093
  console.error(
18044
18094
  chalk2.red(`
18045
- x Pretense proxy is already running (pid ${existing.pid}, port ${existing.port}).`) + chalk2.dim("\n Stop it with ") + chalk2.cyan("pretense stop") + chalk2.dim(", or start this one on another port with ") + chalk2.cyan(`--port ${port + 1}`) + "\n"
18095
+ x Pretense proxy is already running (pid ${existing.pid}, port ${existing.port}).`) + chalk2.dim("\n Stop it with ") + chalk2.cyan("pretense stop") + chalk2.dim(", or start this one on another port with ") + chalk2.cyan(`--port ${requested + 1}`) + "\n"
18046
18096
  );
18047
18097
  process.exit(1);
18048
18098
  }
@@ -18050,6 +18100,25 @@ function startCommand() {
18050
18100
  removeProxyRecord();
18051
18101
  }
18052
18102
  }
18103
+ let port = requested;
18104
+ if (!opts.strictPort) {
18105
+ try {
18106
+ port = await findFreePort(requested);
18107
+ } catch (err) {
18108
+ console.error(chalk2.red(`
18109
+ x ${err.message}
18110
+ `));
18111
+ process.exit(1);
18112
+ }
18113
+ if (port !== requested) {
18114
+ console.error(
18115
+ chalk2.yellow(`
18116
+ ! Port ${requested} is busy \u2014 starting Pretense on port ${port} instead.`) + chalk2.dim(`
18117
+ Pass --strict-port to fail instead of auto-advancing.
18118
+ `)
18119
+ );
18120
+ }
18121
+ }
18053
18122
  process.on("uncaughtException", (err) => {
18054
18123
  if (err?.code === "EADDRINUSE") {
18055
18124
  const rec = readProxyRecord();
@@ -18323,9 +18392,24 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
18323
18392
  ".idea",
18324
18393
  ".vscode"
18325
18394
  ]);
18395
+ function isSkippableWalkError(err) {
18396
+ const code = err?.code;
18397
+ return code === "EACCES" || code === "EPERM" || code === "ENOENT" || code === "ELOOP" || code === "ENOTDIR";
18398
+ }
18326
18399
  function walkDir(dir) {
18327
18400
  const files = [];
18328
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
18401
+ let entries;
18402
+ try {
18403
+ entries = readdirSync2(dir, { withFileTypes: true });
18404
+ } catch (err) {
18405
+ if (isSkippableWalkError(err)) {
18406
+ const code = err.code;
18407
+ console.error(chalk4.yellow(` Skipping unreadable path (${code}): ${dir}`));
18408
+ return files;
18409
+ }
18410
+ throw err;
18411
+ }
18412
+ for (const entry of entries) {
18329
18413
  if (entry.isDirectory()) {
18330
18414
  if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
18331
18415
  files.push(...walkDir(join7(dir, entry.name)));
@@ -18378,6 +18462,18 @@ function l2LanguageFor(filePath) {
18378
18462
  if (!filePath) return void 0;
18379
18463
  return L2_LANGUAGE_BY_EXT[extname2(filePath).toLowerCase()];
18380
18464
  }
18465
+ var L2_LARGE_FILE_WARN_BYTES = 5 * 1024 * 1024;
18466
+ function warnIfLargeForL2(byteLength, filePath) {
18467
+ if (byteLength <= L2_LARGE_FILE_WARN_BYTES) return false;
18468
+ const mb = (byteLength / (1024 * 1024)).toFixed(1);
18469
+ const where = filePath ? ` \u2014 ${basename(filePath)}` : "";
18470
+ console.error(
18471
+ chalk4.yellow(
18472
+ ` Note: L2 AST scanning is slow on very large files (${mb}MB${where}); this may take a while.`
18473
+ )
18474
+ );
18475
+ return true;
18476
+ }
18381
18477
  async function scanContent(content, opts, filePath) {
18382
18478
  return withLocations(await scanContentRaw(content, opts, filePath), content);
18383
18479
  }
@@ -18421,6 +18517,7 @@ async function scanContentRaw(content, opts, filePath) {
18421
18517
  }
18422
18518
  };
18423
18519
  }
18520
+ warnIfLargeForL2(Buffer.byteLength(content, "utf-8"), filePath);
18424
18521
  const result = await scanLevel2(content, language, scanOpts);
18425
18522
  if (opts.level === 2) {
18426
18523
  const only2 = result.matches.filter((m) => m.level === 2);
@@ -20522,11 +20619,13 @@ function quickstartCommand() {
20522
20619
  }
20523
20620
  console.log(chalk13.dim(" [4/4] Done.\n"));
20524
20621
  console.log(chalk13.bold(" Next steps:\n"));
20525
- console.log(` ${chalk13.green("1.")} ${chalk13.cyan("pretense start")} Start proxy on localhost:9339`);
20526
- console.log(` ${chalk13.green("2.")} ${chalk13.cyan("pretense install")} Install pre-commit + pre-push git hooks`);
20527
- console.log(` ${chalk13.green("3.")} Set your LLM client to use the proxy:`);
20622
+ console.log(` ${chalk13.green("1.")} ${chalk13.cyan('pretense run claude "build me X"')} Run any AI tool through the proxy \u2014 zero setup`);
20623
+ console.log(` ${chalk13.dim("(auto-starts the proxy on a free port and sets the base-URL env var for you)")}`);
20624
+ console.log(` ${chalk13.green("2.")} ${chalk13.cyan("pretense install")} Install pre-commit + pre-push git hooks`);
20625
+ console.log(` ${chalk13.green("3.")} ${chalk13.dim("Prefer to wire it by hand? Start the proxy and set the base URL yourself:")}`);
20626
+ console.log(` ${chalk13.dim("pretense start")}`);
20528
20627
  console.log(` ${chalk13.dim("ANTHROPIC_BASE_URL=http://localhost:9339 claude")}`);
20529
- console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/v1 openai ...")}`);
20628
+ console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/v1 cursor ...")}`);
20530
20629
  console.log(` ${chalk13.dim("GOOGLE_GEMINI_BASE_URL=http://localhost:9339 gemini ...")}
20531
20630
  `);
20532
20631
  console.log(chalk13.bold.green(" Protected. Happy coding.\n"));
@@ -20538,7 +20637,7 @@ init_esm_shims();
20538
20637
  import { Command as Command15, Option as Option2 } from "commander";
20539
20638
  import chalk14 from "chalk";
20540
20639
  import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, chmodSync as chmodSync4, existsSync as existsSync15 } from "fs";
20541
- import { createHash as createHash2 } from "crypto";
20640
+ import { createHash as createHash3 } from "crypto";
20542
20641
  import { resolve as resolve5, extname as extname4 } from "path";
20543
20642
 
20544
20643
  // src/secret-crypto.ts
@@ -20646,6 +20745,22 @@ function writeFileAtomicSync(targetPath, data, opts = {}) {
20646
20745
  }
20647
20746
  }
20648
20747
 
20748
+ // src/default-map.ts
20749
+ init_esm_shims();
20750
+ import { createHash as createHash2 } from "crypto";
20751
+ import { join as join20 } from "path";
20752
+ function mapsDir(override2) {
20753
+ return override2 ?? join20(pretenseHome(), "maps");
20754
+ }
20755
+ function defaultMapPath(absFilePath, override2) {
20756
+ const digest = createHash2("sha256").update(absFilePath, "utf8").digest("hex");
20757
+ return join20(mapsDir(override2), `${digest}.json`);
20758
+ }
20759
+ function ensureDefaultMapPath(absFilePath, override2) {
20760
+ ensureDirOwnerOnly(mapsDir(override2));
20761
+ return defaultMapPath(absFilePath, override2);
20762
+ }
20763
+
20649
20764
  // src/commands/mutate.ts
20650
20765
  var REVERSAL_MAP_VERSION = 3;
20651
20766
  function preflightReversalMap(mapPath, absFile, content, contentSha, force) {
@@ -20710,7 +20825,7 @@ function preflightReversalMap(mapPath, absFile, content, contentSha, force) {
20710
20825
  };
20711
20826
  }
20712
20827
  function sha256Hex2(s) {
20713
- return createHash2("sha256").update(s, "utf8").digest("hex");
20828
+ return createHash3("sha256").update(s, "utf8").digest("hex");
20714
20829
  }
20715
20830
  var L2_LANGUAGE_BY_EXT2 = {
20716
20831
  ".ts": "typescript",
@@ -20815,6 +20930,8 @@ async function runMutate(file, opts = {}) {
20815
20930
  );
20816
20931
  return 1;
20817
20932
  }
20933
+ const preview = opts.stdout === true;
20934
+ const inPlace = !preview && !opts.output;
20818
20935
  const { findings, unmutatable } = await collectMutationFindings(
20819
20936
  content,
20820
20937
  file,
@@ -20827,16 +20944,24 @@ async function runMutate(file, opts = {}) {
20827
20944
  } else {
20828
20945
  reportUnmutatable(unmutatable);
20829
20946
  }
20830
- if (!opts.inPlace && !opts.output) process.stdout.write(content);
20947
+ if (preview) process.stdout.write(content);
20831
20948
  return 0;
20832
20949
  }
20833
20950
  reportUnmutatable(unmutatable);
20834
20951
  const absFile = resolve5(file);
20835
20952
  const projectRoot = findProjectRoot(absFile);
20836
20953
  const result = mutateSecrets(content, findings, { projectRoot, keysDir: opts.keysDir });
20837
- if (opts.map) {
20954
+ let effectiveMap;
20955
+ if (preview) {
20956
+ effectiveMap = void 0;
20957
+ } else if (inPlace) {
20958
+ effectiveMap = opts.map ?? ensureDefaultMapPath(absFile, opts.mapsDir);
20959
+ } else {
20960
+ effectiveMap = opts.map;
20961
+ }
20962
+ if (effectiveMap) {
20838
20963
  const contentSha = sha256Hex2(content);
20839
- const pre = preflightReversalMap(opts.map, absFile, content, contentSha, opts.forceMap === true);
20964
+ const pre = preflightReversalMap(effectiveMap, absFile, content, contentSha, opts.forceMap === true);
20840
20965
  if (pre.action === "refuse") {
20841
20966
  console.error(
20842
20967
  chalk14.red(`
@@ -20851,7 +20976,7 @@ async function runMutate(file, opts = {}) {
20851
20976
  const isNoop = pre.action === "keep" && pre.expectedMutatedSha === mutatedSha;
20852
20977
  if (isNoop) {
20853
20978
  console.error(
20854
- chalk14.dim(` = Reversal map ${opts.map} already describes ${file} at this exact content`) + chalk14.dim(`
20979
+ chalk14.dim(` = Reversal map ${effectiveMap} already describes ${file} at this exact content`) + chalk14.dim(`
20855
20980
  hash \u2014 left unchanged (mutation is deterministic, so a rewrite would be`) + chalk14.dim(`
20856
20981
  byte-identical).`)
20857
20982
  );
@@ -20874,7 +20999,7 @@ async function runMutate(file, opts = {}) {
20874
20999
  type: m.type
20875
21000
  }))
20876
21001
  };
20877
- const written = writeReversalMap(opts.map, mapFile);
21002
+ const written = writeReversalMap(effectiveMap, mapFile);
20878
21003
  if (written !== 0) return written;
20879
21004
  if (pre.action === "write" && pre.generation > 1) {
20880
21005
  console.error(
@@ -20888,18 +21013,17 @@ async function runMutate(file, opts = {}) {
20888
21013
  }
20889
21014
  }
20890
21015
  }
20891
- if (opts.inPlace && !opts.map) {
21016
+ if (inPlace && !effectiveMap) {
20892
21017
  console.error(
20893
- chalk14.yellow(`
20894
- ! IRREVERSIBLE: overwriting ${file} in place without a reversal map.`) + chalk14.dim(`
20895
- Synthetic values are one-way \u2014 this cannot be undone and no copy of`) + chalk14.dim(`
20896
- the original secrets is kept. Re-run with --map <path> to be able to`) + chalk14.dim(`
20897
- restore them later with \`pretense reverse\`.
21018
+ chalk14.red(`
21019
+ x Refusing to mutate ${file} in place with no reversal map \u2014 this would be irreversible.`) + chalk14.dim(`
21020
+ Pass --map <path>, or report this as a bug.
20898
21021
  `)
20899
21022
  );
21023
+ return 1;
20900
21024
  }
20901
21025
  try {
20902
- if (opts.inPlace) {
21026
+ if (inPlace) {
20903
21027
  writeFileAtomicSync(file, result.content);
20904
21028
  console.error(chalk14.green(` \u2713 Mutated ${result.mutations.length} secrets in ${file} (in-place)`));
20905
21029
  } else if (opts.output) {
@@ -20924,8 +21048,11 @@ async function runMutate(file, opts = {}) {
20924
21048
  }
20925
21049
  function mutateCommand() {
20926
21050
  return new Command15("mutate").description(
20927
- "Preview mutated secrets on stdout (add -i to write back in place; pair with --map to keep it reversible)"
20928
- ).argument("<file>", "File to mutate").option("-i, --in-place", "Write back to the input file (irreversible unless --map is also given)").option("-o, --output <path>", "Write mutated content to a specific file").option("--map <path>", "Write the encrypted reversal map (required to ever run `pretense reverse`)").option(
21051
+ "Mutate secrets IN PLACE and auto-write an encrypted reversal map (reverse with `pretense reverse <file>`; --stdout to preview instead)"
21052
+ ).argument("<file>", "File to mutate").option("-i, --in-place", "Write back to the input file (this is the default; kept for compatibility)").option("-o, --output <path>", "Write mutated content to a specific file instead of in place").option("--stdout", "Preview mutated content on stdout and write NOTHING (no file, no map)").option(
21053
+ "--map <path>",
21054
+ "Override where the encrypted reversal map is written (default: ~/.pretense/maps/<hash>.json)"
21055
+ ).option(
20929
21056
  "--force-map",
20930
21057
  "Overwrite an existing reversal map that still covers recoverable data (DESTRUCTIVE: the map it replaces may be the only way back to the real secrets)"
20931
21058
  ).option("--show-map", "Print mutation map JSON to stderr").addOption(
@@ -20957,9 +21084,9 @@ import { Command as Command16 } from "commander";
20957
21084
  import chalk15 from "chalk";
20958
21085
  import { readFileSync as readFileSync16, existsSync as existsSync16, statSync as statSync6 } from "fs";
20959
21086
  import { resolve as resolve6 } from "path";
20960
- import { createHash as createHash3 } from "crypto";
21087
+ import { createHash as createHash4 } from "crypto";
20961
21088
  function sha256Hex3(s) {
20962
- return createHash3("sha256").update(s, "utf8").digest("hex");
21089
+ return createHash4("sha256").update(s, "utf8").digest("hex");
20963
21090
  }
20964
21091
  var SYNTHETIC_MARKERS = [
20965
21092
  /pretense_mut_u[0-9a-z]{6,}:pretense_mut_p[0-9a-z]{6,}@/,
@@ -21015,22 +21142,25 @@ function loadReversalMap(mapPath) {
21015
21142
  }
21016
21143
  function runReverse(file, opts = {}) {
21017
21144
  const absPath = resolve6(file);
21018
- if (!opts.map) {
21019
- console.error(
21020
- chalk15.red(`
21021
- x reverse requires a reversal map: --map <file>`) + chalk15.dim(`
21022
- Produce one with: ${chalk15.cyan(`pretense mutate ${file} -i --map <file>`)}
21023
- `)
21024
- );
21025
- return 1;
21026
- }
21027
21145
  if (!existsSync16(absPath) || !statSync6(absPath).isFile()) {
21028
21146
  console.error(chalk15.red(`
21029
21147
  x File not found: ${file}
21030
21148
  `));
21031
21149
  return 1;
21032
21150
  }
21033
- const mapFile = loadReversalMap(opts.map);
21151
+ const mapPath = opts.map ?? defaultMapPath(absPath, opts.mapsDir);
21152
+ if (!opts.map && !existsSync16(mapPath)) {
21153
+ console.error(
21154
+ chalk15.red(`
21155
+ x No reversal map found for ${absPath}`) + chalk15.dim(`
21156
+ Looked in the default location: ${mapPath}`) + chalk15.dim(`
21157
+ Mutate the file first with ${chalk15.cyan(`pretense mutate ${file}`)}, or pass`) + chalk15.dim(`
21158
+ an explicit ${chalk15.cyan("--map <path>")} if you wrote the map somewhere else.
21159
+ `)
21160
+ );
21161
+ return 1;
21162
+ }
21163
+ const mapFile = loadReversalMap(mapPath);
21034
21164
  let plainMutations;
21035
21165
  if (mapFile.encrypted) {
21036
21166
  const projectRoot = findProjectRoot(mapFile.file ?? absPath);
@@ -21136,7 +21266,9 @@ function runReverse(file, opts = {}) {
21136
21266
  }
21137
21267
  const mark = lineage.ok ? chalk15.green(" \u2713 Restored") : chalk15.yellow(" ! Partially restored");
21138
21268
  const detail = `${appliedCount} of ${plainMutations.length} mapped tokens` + (lineage.ok ? "" : " \u2014 to an earlier MUTATED generation, not the original");
21139
- if (opts.inPlace) {
21269
+ const preview = opts.stdout === true;
21270
+ const inPlace = !preview && !opts.output;
21271
+ if (inPlace) {
21140
21272
  writeFileAtomicSync(absPath, restored);
21141
21273
  console.error(`${mark} ${absPath} (${detail}, in-place)`);
21142
21274
  } else if (opts.output) {
@@ -21163,8 +21295,11 @@ function runReverse(file, opts = {}) {
21163
21295
  }
21164
21296
  function reverseCommand() {
21165
21297
  return new Command16("reverse").description(
21166
- "Restore a mutated file to its EXACT original bytes using an encrypted reversal map (fails if the file was edited after mutation)"
21167
- ).argument("<file>", "Mutated file to restore (must be byte-identical to the mutate output)").requiredOption("--map <path>", "Reversal map produced by `pretense mutate --map <path>`").option("-i, --in-place", "Write the restored content back to the input file").option("-o, --output <path>", "Write restored content to a specific file").option("--json", "Emit a JSON summary to stderr").option(
21298
+ "Restore a mutated file to its EXACT original bytes IN PLACE, auto-finding its reversal map (fails if the file was edited after mutation)"
21299
+ ).argument("<file>", "Mutated file to restore (must be byte-identical to the mutate output)").option(
21300
+ "--map <path>",
21301
+ "Override the reversal map to use (default: the one `pretense mutate <file>` auto-wrote)"
21302
+ ).option("-i, --in-place", "Write the restored content back to the input file (this is the default)").option("-o, --output <path>", "Write restored content to a specific file instead of in place").option("--stdout", "Preview restored content on stdout instead of writing the file").option("--json", "Emit a JSON summary to stderr").option(
21168
21303
  "--allow-partial",
21169
21304
  "Apply a map whose originals are themselves synthetics (writes the PREVIOUS MUTATED generation, not the original)"
21170
21305
  ).action((file, opts) => {
@@ -21172,10 +21307,191 @@ function reverseCommand() {
21172
21307
  });
21173
21308
  }
21174
21309
 
21175
- // src/commands/policy.ts
21310
+ // src/commands/run.ts
21176
21311
  init_esm_shims();
21177
21312
  import { Command as Command17 } from "commander";
21178
21313
  import chalk16 from "chalk";
21314
+ import { spawn } from "child_process";
21315
+ import { basename as basename3 } from "path";
21316
+ import { fileURLToPath as fileURLToPath4 } from "url";
21317
+ var TOOL_PROVIDER = {
21318
+ claude: "anthropic",
21319
+ "claude-code": "anthropic",
21320
+ anthropic: "anthropic",
21321
+ cursor: "openai",
21322
+ code: "openai",
21323
+ codex: "openai",
21324
+ openai: "openai",
21325
+ aider: "openai",
21326
+ gemini: "google",
21327
+ google: "google"
21328
+ };
21329
+ var PROVIDER_ALIASES = {
21330
+ anthropic: "anthropic",
21331
+ claude: "anthropic",
21332
+ openai: "openai",
21333
+ "openai-compatible": "openai",
21334
+ cursor: "openai",
21335
+ google: "google",
21336
+ gemini: "google"
21337
+ };
21338
+ function resolveProvider(tool, override2) {
21339
+ if (override2 !== void 0) {
21340
+ const p = PROVIDER_ALIASES[override2.toLowerCase()];
21341
+ if (!p) {
21342
+ throw new Error(
21343
+ `Unknown --provider "${override2}". Expected one of: anthropic, openai, google.`
21344
+ );
21345
+ }
21346
+ return p;
21347
+ }
21348
+ return TOOL_PROVIDER[basename3(tool).toLowerCase()] ?? "anthropic";
21349
+ }
21350
+ function injectedEnvFor(provider, port) {
21351
+ const base = `http://localhost:${port}`;
21352
+ switch (provider) {
21353
+ case "anthropic":
21354
+ return { ANTHROPIC_BASE_URL: base };
21355
+ case "openai":
21356
+ return { OPENAI_BASE_URL: `${base}/v1` };
21357
+ case "google":
21358
+ return { GEMINI_BASE_URL: base, GOOGLE_GEMINI_BASE_URL: base };
21359
+ }
21360
+ }
21361
+ function primaryEnvVar(provider) {
21362
+ return provider === "anthropic" ? "ANTHROPIC_BASE_URL" : provider === "openai" ? "OPENAI_BASE_URL" : "GEMINI_BASE_URL";
21363
+ }
21364
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
21365
+ async function isProxyHealthy(port, timeoutMs = 800) {
21366
+ const ctrl = new AbortController();
21367
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
21368
+ try {
21369
+ const res = await fetch(`http://localhost:${port}/health`, { signal: ctrl.signal });
21370
+ return res.ok;
21371
+ } catch {
21372
+ return false;
21373
+ } finally {
21374
+ clearTimeout(t);
21375
+ }
21376
+ }
21377
+ async function waitHealthy(port, deadlineMs, pollMs = 150) {
21378
+ const deadline = Date.now() + deadlineMs;
21379
+ while (Date.now() < deadline) {
21380
+ if (await isProxyHealthy(port)) return true;
21381
+ await sleep2(pollMs);
21382
+ }
21383
+ return false;
21384
+ }
21385
+ function spawnProxy(port) {
21386
+ const cliEntry = process.argv[1] ?? fileURLToPath4(import.meta.url);
21387
+ return spawn(process.execPath, [cliEntry, "start", "--port", String(port), "--strict-port"], {
21388
+ stdio: ["ignore", "ignore", "pipe"],
21389
+ env: process.env
21390
+ });
21391
+ }
21392
+ function runCommand() {
21393
+ return new Command17("run").alias("with").description(
21394
+ "Run an AI tool THROUGH the Pretense proxy with zero setup \u2014 auto-starts the proxy, sets the right base-URL env var, and stops the proxy it started when the tool exits"
21395
+ ).argument("<tool>", "The AI CLI to run (claude, cursor, gemini, \u2026)").argument("[args...]", "Arguments passed through to <tool>").option("--provider <name>", "Force the provider mapping: anthropic | openai | google").option("-p, --port <port>", "Preferred proxy port when starting one (default 9339)").option("--strict-port", "Fail instead of auto-advancing when starting a proxy on a busy port", false).passThroughOptions().allowUnknownOption().action(async (tool, args, opts) => {
21396
+ let provider;
21397
+ try {
21398
+ provider = resolveProvider(tool, opts.provider);
21399
+ } catch (err) {
21400
+ console.error(chalk16.red(`
21401
+ x ${err.message}
21402
+ `));
21403
+ process.exit(1);
21404
+ }
21405
+ let port;
21406
+ let proxyChild;
21407
+ let startedByUs = false;
21408
+ const rec = readProxyRecord();
21409
+ if (rec && isProcessAlive(rec.pid) && await isProxyHealthy(rec.port)) {
21410
+ port = rec.port;
21411
+ console.error(chalk16.dim(` \u21BA Reusing the Pretense proxy already running on port ${port}.`));
21412
+ } else {
21413
+ const preferred = opts.port ? parseInt(opts.port, 10) : 9339;
21414
+ try {
21415
+ port = await findFreePort(preferred);
21416
+ } catch (err) {
21417
+ console.error(chalk16.red(`
21418
+ x ${err.message}
21419
+ `));
21420
+ process.exit(1);
21421
+ }
21422
+ proxyChild = spawnProxy(port);
21423
+ startedByUs = true;
21424
+ let proxyStderr = "";
21425
+ proxyChild.stderr?.on("data", (d) => {
21426
+ proxyStderr += d.toString();
21427
+ });
21428
+ const ready = await waitHealthy(port, 15e3);
21429
+ if (!ready) {
21430
+ console.error(
21431
+ chalk16.red(`
21432
+ x Pretense proxy did not become healthy on port ${port} in time.`) + (proxyStderr ? chalk16.dim(`
21433
+ ${proxyStderr.split("\n").slice(-6).join("\n")}`) : "") + "\n"
21434
+ );
21435
+ proxyChild.kill("SIGTERM");
21436
+ process.exit(1);
21437
+ }
21438
+ console.error(chalk16.green(` \u2713 Started Pretense proxy on port ${port}.`));
21439
+ }
21440
+ const stopStartedProxy = () => {
21441
+ if (startedByUs && proxyChild && !proxyChild.killed) {
21442
+ proxyChild.kill("SIGTERM");
21443
+ }
21444
+ };
21445
+ const inject = injectedEnvFor(provider, port);
21446
+ console.error(
21447
+ chalk16.dim(
21448
+ ` \u2192 Routing ${basename3(tool)} through the proxy (${primaryEnvVar(provider)}=http://localhost:${port}).
21449
+ `
21450
+ )
21451
+ );
21452
+ const child = spawn(tool, args, {
21453
+ stdio: "inherit",
21454
+ env: { ...process.env, ...inject }
21455
+ });
21456
+ const forward = (sig) => () => {
21457
+ if (!child.killed) child.kill(sig);
21458
+ };
21459
+ const onInt = forward("SIGINT");
21460
+ const onTerm = forward("SIGTERM");
21461
+ process.on("SIGINT", onInt);
21462
+ process.on("SIGTERM", onTerm);
21463
+ child.on("error", (err) => {
21464
+ if (err.code === "ENOENT") {
21465
+ console.error(
21466
+ chalk16.red(`
21467
+ x "${tool}" is not on your PATH.`) + chalk16.dim(`
21468
+ Install it (e.g. the ${basename3(tool)} CLI) and try again, or check the name.`) + chalk16.dim(`
21469
+ You can still point a client at the proxy yourself:`) + chalk16.dim(`
21470
+ ${primaryEnvVar(provider)}=http://localhost:${port}
21471
+ `)
21472
+ );
21473
+ } else {
21474
+ console.error(chalk16.red(`
21475
+ x Could not run "${tool}": ${err.message}
21476
+ `));
21477
+ }
21478
+ stopStartedProxy();
21479
+ process.exit(err.code === "ENOENT" ? 127 : 1);
21480
+ });
21481
+ child.on("exit", (code, signal) => {
21482
+ stopStartedProxy();
21483
+ process.removeListener("SIGINT", onInt);
21484
+ process.removeListener("SIGTERM", onTerm);
21485
+ if (typeof code === "number") process.exit(code);
21486
+ process.exit(signal ? 1 : 0);
21487
+ });
21488
+ });
21489
+ }
21490
+
21491
+ // src/commands/policy.ts
21492
+ init_esm_shims();
21493
+ import { Command as Command18 } from "commander";
21494
+ import chalk17 from "chalk";
21179
21495
  function listAction(opts) {
21180
21496
  const ids = listPresetIds();
21181
21497
  if (opts.json) {
@@ -21198,21 +21514,21 @@ function listAction(opts) {
21198
21514
  );
21199
21515
  return;
21200
21516
  }
21201
- console.log(chalk16.cyan("\n Pretense Compliance Frameworks\n"));
21517
+ console.log(chalk17.cyan("\n Pretense Compliance Frameworks\n"));
21202
21518
  console.log(
21203
- chalk16.dim(
21519
+ chalk17.dim(
21204
21520
  " " + "ID".padEnd(8) + "NAME".padEnd(14) + "TIER".padEnd(12) + "AUDIT".padEnd(8) + "DETECTORS"
21205
21521
  )
21206
21522
  );
21207
- console.log(chalk16.dim(" " + "\u2500".repeat(64)));
21523
+ console.log(chalk17.dim(" " + "\u2500".repeat(64)));
21208
21524
  for (const id of ids) {
21209
21525
  const p = COMPLIANCE_PRESETS[id];
21210
21526
  console.log(
21211
- " " + chalk16.bold(p.id.padEnd(8)) + p.name.padEnd(14) + chalk16.dim(p.minTier.padEnd(12)) + (p.auditRequired ? chalk16.green("yes".padEnd(8)) : chalk16.dim("no".padEnd(8))) + chalk16.dim(`${Object.keys(p.actionOverrides).length} overrides`)
21527
+ " " + chalk17.bold(p.id.padEnd(8)) + p.name.padEnd(14) + chalk17.dim(p.minTier.padEnd(12)) + (p.auditRequired ? chalk17.green("yes".padEnd(8)) : chalk17.dim("no".padEnd(8))) + chalk17.dim(`${Object.keys(p.actionOverrides).length} overrides`)
21212
21528
  );
21213
21529
  }
21214
- console.log(chalk16.dim(`
21215
- Run ${chalk16.cyan("pretense policy show <id>")} for detector-level detail.
21530
+ console.log(chalk17.dim(`
21531
+ Run ${chalk17.cyan("pretense policy show <id>")} for detector-level detail.
21216
21532
  `));
21217
21533
  }
21218
21534
  function showAction(id, opts) {
@@ -21221,8 +21537,8 @@ function showAction(id, opts) {
21221
21537
  preset = loadPreset(id);
21222
21538
  } catch (err) {
21223
21539
  console.error(
21224
- chalk16.red(`
21225
- x ${err.message}`) + chalk16.dim(`
21540
+ chalk17.red(`
21541
+ x ${err.message}`) + chalk17.dim(`
21226
21542
  Valid frameworks: ${listPresetIds().join(", ")}
21227
21543
  `)
21228
21544
  );
@@ -21232,24 +21548,24 @@ function showAction(id, opts) {
21232
21548
  console.log(JSON.stringify(preset, null, 2));
21233
21549
  return;
21234
21550
  }
21235
- console.log(chalk16.cyan(`
21236
- ${preset.name} `) + chalk16.dim(`(${preset.id})`));
21237
- console.log(chalk16.dim(` ${preset.description}
21551
+ console.log(chalk17.cyan(`
21552
+ ${preset.name} `) + chalk17.dim(`(${preset.id})`));
21553
+ console.log(chalk17.dim(` ${preset.description}
21238
21554
  `));
21239
- console.log(` Minimum tier: ${chalk16.bold(preset.minTier)}`);
21240
- console.log(` Audit required: ${preset.auditRequired ? chalk16.green("yes") : chalk16.dim("no")}`);
21241
- console.log(` Detector sets: ${chalk16.dim(preset.detectorSets.join(", "))}
21555
+ console.log(` Minimum tier: ${chalk17.bold(preset.minTier)}`);
21556
+ console.log(` Audit required: ${preset.auditRequired ? chalk17.green("yes") : chalk17.dim("no")}`);
21557
+ console.log(` Detector sets: ${chalk17.dim(preset.detectorSets.join(", "))}
21242
21558
  `);
21243
21559
  const overrides = Object.entries(preset.actionOverrides);
21244
- console.log(chalk16.dim(` Detector actions (${overrides.length}):`));
21560
+ console.log(chalk17.dim(` Detector actions (${overrides.length}):`));
21245
21561
  for (const [detector, action] of overrides) {
21246
- const color = action === "block" ? chalk16.red : action === "redact" ? chalk16.yellow : chalk16.dim;
21562
+ const color = action === "block" ? chalk17.red : action === "redact" ? chalk17.yellow : chalk17.dim;
21247
21563
  console.log(` ${detector.padEnd(24)} ${color(action)}`);
21248
21564
  }
21249
21565
  console.log();
21250
21566
  }
21251
21567
  function policyCommand() {
21252
- const cmd = new Command17("policy").description(
21568
+ const cmd = new Command18("policy").description(
21253
21569
  "List and inspect the built-in compliance presets (5: hipaa, gdpr, soc2, nist, pci)"
21254
21570
  );
21255
21571
  cmd.command("list").description("List the 5 built-in compliance presets").option("--json", "Output as JSON").action((opts) => listAction(opts));
@@ -21260,10 +21576,10 @@ function policyCommand() {
21260
21576
 
21261
21577
  // src/commands/audit.ts
21262
21578
  init_esm_shims();
21263
- import { Command as Command18 } from "commander";
21264
- import chalk17 from "chalk";
21579
+ import { Command as Command19 } from "commander";
21580
+ import chalk18 from "chalk";
21265
21581
  import { homedir as homedir10 } from "os";
21266
- import { join as join20 } from "path";
21582
+ import { join as join21 } from "path";
21267
21583
  var VALID_FORMATS2 = ["table", "csv", "json", "jsonl"];
21268
21584
  function toSafeAuditRow(e) {
21269
21585
  return {
@@ -21334,18 +21650,18 @@ function resolveFormat2(opts) {
21334
21650
  const raw2 = opts.format?.trim().toLowerCase();
21335
21651
  if (raw2 !== void 0 && !VALID_FORMATS2.includes(raw2)) {
21336
21652
  console.error(
21337
- chalk17.red(`
21653
+ chalk18.red(`
21338
21654
  x Unknown --format: "${opts.format}"
21339
- `) + chalk17.dim(" Valid formats: table, csv, json (alias: jsonl).\n")
21655
+ `) + chalk18.dim(" Valid formats: table, csv, json (alias: jsonl).\n")
21340
21656
  );
21341
21657
  return null;
21342
21658
  }
21343
21659
  const fromFormat = raw2 === void 0 ? void 0 : raw2 === "jsonl" ? "json" : raw2;
21344
21660
  if (opts.json && fromFormat !== void 0 && fromFormat !== "json") {
21345
21661
  console.error(
21346
- chalk17.red(`
21662
+ chalk18.red(`
21347
21663
  x --json and --format ${fromFormat} request different outputs.
21348
- `) + chalk17.dim(" Pick one.\n")
21664
+ `) + chalk18.dim(" Pick one.\n")
21349
21665
  );
21350
21666
  return null;
21351
21667
  }
@@ -21353,7 +21669,7 @@ function resolveFormat2(opts) {
21353
21669
  return opts.json ? "json" : "table";
21354
21670
  }
21355
21671
  function auditCommand() {
21356
- return new Command18("audit").description(
21672
+ return new Command19("audit").description(
21357
21673
  "Show recent mutation/tokenization audit entries (leak-proof; JSONL with --json, RFC 4180 CSV with --format csv)"
21358
21674
  ).option("-l, --limit <n>", "Number of entries to show", "20").option("--json", "Output as JSONL (one JSON object per line, jq-pipeable)", false).option("--db <path>", "Path to the audit database (default: ~/.pretense/audit.db)").option(
21359
21675
  "--framework <name>",
@@ -21373,21 +21689,21 @@ function auditCommand() {
21373
21689
  framework = resolveFrameworkName(opts.framework);
21374
21690
  if (!framework) {
21375
21691
  console.error(
21376
- chalk17.red(`
21692
+ chalk18.red(`
21377
21693
  x Unknown framework: "${opts.framework}"
21378
- `) + chalk17.dim(" Run ") + chalk17.cyan("pretense audit --list-frameworks") + chalk17.dim(" to see all 36.\n")
21694
+ `) + chalk18.dim(" Run ") + chalk18.cyan("pretense audit --list-frameworks") + chalk18.dim(" to see all 36.\n")
21379
21695
  );
21380
21696
  process.exit(1);
21381
21697
  }
21382
21698
  }
21383
21699
  const limit = parseInt(opts.limit ?? "20", 10);
21384
21700
  if (!Number.isFinite(limit) || limit <= 0) {
21385
- console.error(chalk17.red(`
21701
+ console.error(chalk18.red(`
21386
21702
  x Invalid --limit: "${opts.limit}". Provide a positive integer.
21387
21703
  `));
21388
21704
  process.exit(1);
21389
21705
  }
21390
- const dbPath = opts.db ?? join20(homedir10(), ".pretense", "audit.db");
21706
+ const dbPath = opts.db ?? join21(homedir10(), ".pretense", "audit.db");
21391
21707
  let rows;
21392
21708
  try {
21393
21709
  const store = new AuditStore(dbPath);
@@ -21407,8 +21723,8 @@ function auditCommand() {
21407
21723
  console.error("pretense: no audit data \u2014 the audit database has not been initialized yet.");
21408
21724
  return;
21409
21725
  }
21410
- console.log(chalk17.dim("\n No audit data available. The audit database has not been initialized yet."));
21411
- console.log(chalk17.dim(" Run " + chalk17.cyan("pretense start") + " and make some requests first.\n"));
21726
+ console.log(chalk18.dim("\n No audit data available. The audit database has not been initialized yet."));
21727
+ console.log(chalk18.dim(" Run " + chalk18.cyan("pretense start") + " and make some requests first.\n"));
21412
21728
  return;
21413
21729
  }
21414
21730
  if (format === "csv") {
@@ -21420,23 +21736,23 @@ function auditCommand() {
21420
21736
  return;
21421
21737
  }
21422
21738
  if (rows.length === 0) {
21423
- console.log(chalk17.dim("\n No audit entries yet. Run pretense start and make some requests.\n"));
21739
+ console.log(chalk18.dim("\n No audit entries yet. Run pretense start and make some requests.\n"));
21424
21740
  return;
21425
21741
  }
21426
- console.log(chalk17.cyan(`
21742
+ console.log(chalk18.cyan(`
21427
21743
  Pretense Mutation Audit \u2014 last ${rows.length} entries
21428
21744
  `));
21429
21745
  console.log(
21430
- chalk17.dim(
21746
+ chalk18.dim(
21431
21747
  " " + "TIME".padEnd(10) + "ACTION".padEnd(12) + "PROVIDER".padEnd(14) + "MUTATED".padEnd(10) + "BLOCKED".padEnd(10) + "PII".padEnd(8) + "MS"
21432
21748
  )
21433
21749
  );
21434
- console.log(chalk17.dim(" " + "\u2500".repeat(72)));
21750
+ console.log(chalk18.dim(" " + "\u2500".repeat(72)));
21435
21751
  for (const row of rows) {
21436
21752
  const time = new Date(row.timestamp).toTimeString().slice(0, 8);
21437
- const actionColor = row.action === "blocked" ? chalk17.red : row.action === "mutated" ? chalk17.green : row.action === "redacted" ? chalk17.yellow : row.action === "error" ? chalk17.red : chalk17.dim;
21753
+ const actionColor = row.action === "blocked" ? chalk18.red : row.action === "mutated" ? chalk18.green : row.action === "redacted" ? chalk18.yellow : row.action === "error" ? chalk18.red : chalk18.dim;
21438
21754
  console.log(
21439
- " " + chalk17.dim(time.padEnd(10)) + actionColor(row.action.padEnd(12)) + chalk17.dim(row.provider.padEnd(14)) + chalk17.green(String(row.mutationsApplied).padEnd(10)) + (row.secretsBlocked > 0 ? chalk17.red(String(row.secretsBlocked).padEnd(10)) : chalk17.dim("0".padEnd(10))) + chalk17.dim(String(row.piiRedacted).padEnd(8)) + chalk17.dim(String(row.scanDurationMs).slice(0, 5))
21755
+ " " + chalk18.dim(time.padEnd(10)) + actionColor(row.action.padEnd(12)) + chalk18.dim(row.provider.padEnd(14)) + chalk18.green(String(row.mutationsApplied).padEnd(10)) + (row.secretsBlocked > 0 ? chalk18.red(String(row.secretsBlocked).padEnd(10)) : chalk18.dim("0".padEnd(10))) + chalk18.dim(String(row.piiRedacted).padEnd(8)) + chalk18.dim(String(row.scanDurationMs).slice(0, 5))
21440
21756
  );
21441
21757
  }
21442
21758
  console.log();
@@ -21445,9 +21761,9 @@ function auditCommand() {
21445
21761
 
21446
21762
  // src/commands/credits.ts
21447
21763
  init_esm_shims();
21448
- import { Command as Command19 } from "commander";
21449
- import chalk18 from "chalk";
21450
- import { join as join21 } from "path";
21764
+ import { Command as Command20 } from "commander";
21765
+ import chalk19 from "chalk";
21766
+ import { join as join24 } from "path";
21451
21767
  import { homedir as homedir11 } from "os";
21452
21768
  function remaining(used, limit) {
21453
21769
  if (limit === null) return null;
@@ -21457,16 +21773,16 @@ function fmt(n) {
21457
21773
  return n === null ? "unlimited" : n.toLocaleString("en-US");
21458
21774
  }
21459
21775
  function bar(used, limit, width = 24) {
21460
- if (limit === null) return chalk18.green("\u2588".repeat(width));
21776
+ if (limit === null) return chalk19.green("\u2588".repeat(width));
21461
21777
  const pct = limit <= 0 ? 1 : Math.min(1, used / limit);
21462
21778
  const filled = Math.min(width, Math.round(pct * width));
21463
21779
  const s = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
21464
- if (pct >= 1) return chalk18.red(s);
21465
- if (pct >= 0.8) return chalk18.yellow(s);
21466
- return chalk18.green(s);
21780
+ if (pct >= 1) return chalk19.red(s);
21781
+ if (pct >= 0.8) return chalk19.yellow(s);
21782
+ return chalk19.green(s);
21467
21783
  }
21468
21784
  function creditsCommand() {
21469
- return new Command19("credits").alias("tokens").description("Show remaining mutation budget for the current period").option("--db <path>", "Path to audit database", join21(homedir11(), ".pretense", "audit.db")).option("--json", "Output as JSON", false).option("--offline", "Do not contact the plan service to resolve the tier", false).action(async (opts) => {
21785
+ return new Command20("credits").alias("tokens").description("Show remaining mutation budget for the current period").option("--db <path>", "Path to audit database", join24(homedir11(), ".pretense", "audit.db")).option("--json", "Output as JSON", false).option("--offline", "Do not contact the plan service to resolve the tier", false).action(async (opts) => {
21470
21786
  const resolution = await resolvePlanTier({ offline: opts.offline });
21471
21787
  const tier = resolution.tier;
21472
21788
  const collected = collectUsage(opts.db);
@@ -21479,7 +21795,7 @@ function creditsCommand() {
21479
21795
  periodEnd: null
21480
21796
  };
21481
21797
  if (!collected && !opts.json) {
21482
- console.log(chalk18.dim("\n No usage recorded yet \u2014 showing your full allowance.\n"));
21798
+ console.log(chalk19.dim("\n No usage recorded yet \u2014 showing your full allowance.\n"));
21483
21799
  }
21484
21800
  const mutationLimit = tier ? PLAN_CONFIGS[tier].mutationsPerPeriod : null;
21485
21801
  const scanLimit = tier ? PLAN_CONFIGS[tier].scansPerPeriod : null;
@@ -21511,12 +21827,12 @@ function creditsCommand() {
21511
21827
  );
21512
21828
  return;
21513
21829
  }
21514
- console.log(chalk18.cyan("\n Pretense Credits"));
21515
- console.log(chalk18.dim(` Plan: ${tierLabel(tier)} | Resets: ${resetCadenceLabel()}
21830
+ console.log(chalk19.cyan("\n Pretense Credits"));
21831
+ console.log(chalk19.dim(` Plan: ${tierLabel(tier)} | Resets: ${resetCadenceLabel()}
21516
21832
  `));
21517
21833
  if (!tier) {
21518
21834
  for (const line of unverifiedNote(resolution.detail)) {
21519
- console.log(chalk18.yellow(` ${line}`));
21835
+ console.log(chalk19.yellow(` ${line}`));
21520
21836
  }
21521
21837
  console.log();
21522
21838
  console.log(" " + "Mutations used".padEnd(20) + m.mutationsApplied.toLocaleString("en-US"));
@@ -21525,18 +21841,18 @@ function creditsCommand() {
21525
21841
  return;
21526
21842
  }
21527
21843
  console.log(
21528
- " " + "Mutations".padEnd(14) + bar(m.mutationsApplied, mutationLimit) + " " + chalk18.bold(fmt(mutationsLeft)) + chalk18.dim(` left of ${fmt(mutationLimit)}`)
21844
+ " " + "Mutations".padEnd(14) + bar(m.mutationsApplied, mutationLimit) + " " + chalk19.bold(fmt(mutationsLeft)) + chalk19.dim(` left of ${fmt(mutationLimit)}`)
21529
21845
  );
21530
21846
  console.log(
21531
- " " + "Requests".padEnd(14) + bar(m.totalScans, scanLimit) + " " + chalk18.bold(fmt(scansLeft)) + chalk18.dim(` left of ${fmt(scanLimit)}`)
21847
+ " " + "Requests".padEnd(14) + bar(m.totalScans, scanLimit) + " " + chalk19.bold(fmt(scansLeft)) + chalk19.dim(` left of ${fmt(scanLimit)}`)
21532
21848
  );
21533
21849
  if (tier !== "enterprise" && mutationLimit !== null && mutationsLeft === 0) {
21534
21850
  console.log(
21535
- chalk18.red("\n Mutation budget exhausted for this period.") + chalk18.dim(" Run ") + chalk18.cyan("pretense upgrade") + chalk18.dim(" to compare plans.")
21851
+ chalk19.red("\n Mutation budget exhausted for this period.") + chalk19.dim(" Run ") + chalk19.cyan("pretense upgrade") + chalk19.dim(" to compare plans.")
21536
21852
  );
21537
21853
  } else if (tier !== "enterprise" && mutationLimit !== null && m.mutationsApplied >= mutationLimit * 0.8) {
21538
21854
  console.log(
21539
- chalk18.yellow("\n Running low.") + chalk18.dim(" Run ") + chalk18.cyan("pretense upgrade") + chalk18.dim(" to compare plans.")
21855
+ chalk19.yellow("\n Running low.") + chalk19.dim(" Run ") + chalk19.cyan("pretense upgrade") + chalk19.dim(" to compare plans.")
21540
21856
  );
21541
21857
  }
21542
21858
  console.log();
@@ -21545,8 +21861,8 @@ function creditsCommand() {
21545
21861
 
21546
21862
  // src/commands/upgrade.ts
21547
21863
  init_esm_shims();
21548
- import { Command as Command20 } from "commander";
21549
- import chalk19 from "chalk";
21864
+ import { Command as Command21 } from "commander";
21865
+ import chalk20 from "chalk";
21550
21866
  var PRICING_URL = "https://pretense.ai/pricing";
21551
21867
  var TIERS = ["free", "pro", "enterprise"];
21552
21868
  function quota(n) {
@@ -21559,7 +21875,7 @@ function yesNo(b) {
21559
21875
  return b ? "yes" : "\u2014";
21560
21876
  }
21561
21877
  function upgradeCommand() {
21562
- return new Command20("upgrade").description("Compare Pretense plans and upgrade").option("--json", "Output as JSON", false).option("--offline", "Do not contact the plan service to resolve the tier", false).action(async (opts) => {
21878
+ return new Command21("upgrade").description("Compare Pretense plans and upgrade").option("--json", "Output as JSON", false).option("--offline", "Do not contact the plan service to resolve the tier", false).action(async (opts) => {
21563
21879
  const resolution = await resolvePlanTier({ offline: opts.offline });
21564
21880
  const current = resolution.tier;
21565
21881
  if (opts.json) {
@@ -21587,11 +21903,11 @@ function upgradeCommand() {
21587
21903
  );
21588
21904
  return;
21589
21905
  }
21590
- console.log(chalk19.cyan("\n Pretense Plans\n"));
21906
+ console.log(chalk20.cyan("\n Pretense Plans\n"));
21591
21907
  const col = 16;
21592
21908
  const header = " " + "".padEnd(18) + TIERS.map((t) => tierName(t).padEnd(col)).join("");
21593
- console.log(chalk19.bold(header));
21594
- console.log(" " + chalk19.dim("\u2500".repeat(18 + col * TIERS.length)));
21909
+ console.log(chalk20.bold(header));
21910
+ console.log(" " + chalk20.dim("\u2500".repeat(18 + col * TIERS.length)));
21595
21911
  const rows = [
21596
21912
  ["Price", (t) => priceLabel(t)],
21597
21913
  ["Mutations/7d", (t) => quota(PLAN_CONFIGS[t].mutationsPerPeriod)],
@@ -21603,24 +21919,24 @@ function upgradeCommand() {
21603
21919
  for (const [label, cell] of rows) {
21604
21920
  console.log(" " + label.padEnd(18) + TIERS.map((t) => cell(t).padEnd(col)).join(""));
21605
21921
  }
21606
- console.log(" " + chalk19.dim("\u2500".repeat(18 + col * TIERS.length)));
21922
+ console.log(" " + chalk20.dim("\u2500".repeat(18 + col * TIERS.length)));
21607
21923
  if (current) {
21608
- console.log(chalk19.dim(`
21924
+ console.log(chalk20.dim(`
21609
21925
  Current plan: ${tierName(current)}`));
21610
21926
  if (current === "enterprise") {
21611
- console.log(chalk19.green(" You are on the highest tier \u2014 nothing to upgrade.\n"));
21927
+ console.log(chalk20.green(" You are on the highest tier \u2014 nothing to upgrade.\n"));
21612
21928
  return;
21613
21929
  }
21614
21930
  } else {
21615
21931
  console.log(
21616
- chalk19.yellow(
21932
+ chalk20.yellow(
21617
21933
  `
21618
21934
  Could not confirm your current plan: ${resolution.detail ?? "unknown reason"}.`
21619
21935
  )
21620
21936
  );
21621
21937
  }
21622
21938
  const from = current ?? "unknown";
21623
- console.log(chalk19.bold(`
21939
+ console.log(chalk20.bold(`
21624
21940
  \u2192 ${PRICING_URL}?ref=cli_upgrade&from=${from}
21625
21941
  `));
21626
21942
  });
@@ -21653,34 +21969,35 @@ function findClosestCommand(input, knownCommands) {
21653
21969
  }
21654
21970
  return bestDist <= 3 ? best : null;
21655
21971
  }
21656
- var program = new Command21();
21657
- program.name("pretense").description(chalk20.bold("AI firewall") + " \u2014 mutates proprietary code before LLM API calls").version(PRETENSE_VERSION, "-v, --version").addHelpText("after", `
21658
- ${chalk20.bold("Quick start:")}
21659
- ${chalk20.cyan("pretense scan")} ${chalk20.dim("<file|dir>")} Scan for secrets and PII
21660
- ${chalk20.cyan("pretense mutate")} ${chalk20.dim("<file>")} Preview code mutations
21661
- ${chalk20.cyan("pretense init")} Scan codebase and build protection profile
21972
+ var program = new Command22();
21973
+ program.name("pretense").enablePositionalOptions().description(chalk21.bold("AI firewall") + " \u2014 mutates proprietary code before LLM API calls").version(PRETENSE_VERSION, "-v, --version").addHelpText("after", `
21974
+ ${chalk21.bold("Quick start:")}
21975
+ ${chalk21.cyan("pretense run claude")} ${chalk21.dim('"build X"')} Run an AI tool through the proxy (zero setup)
21976
+ ${chalk21.cyan("pretense mutate")} ${chalk21.dim("<file>")} Mutate secrets in place (reverse with the same path)
21977
+ ${chalk21.cyan("pretense scan")} ${chalk21.dim("<file|dir>")} Scan for secrets and PII
21662
21978
 
21663
- ${chalk20.dim("All commands:")}
21664
- ${chalk20.cyan("pretense init")} Scan codebase and build protection profile
21665
- ${chalk20.cyan("pretense start")} Start proxy on localhost:9339
21666
- ${chalk20.cyan("pretense stop")} Stop the running proxy
21667
- ${chalk20.cyan("pretense scan src/api.ts")} Scan file for secrets
21668
- ${chalk20.cyan("pretense scan ci")} CI-optimized scan (exit 0=clean, 2=findings)
21669
- ${chalk20.cyan("pretense status")} Show protection status
21670
- ${chalk20.cyan("pretense review")} Preview mutations that would be applied
21671
- ${chalk20.cyan("pretense review --json")} Output review as JSON
21672
- ${chalk20.cyan("pretense mutate f --map m.json")} Mutate secrets + write reversal map
21673
- ${chalk20.cyan("pretense reverse f --map m.json")} Restore a mutated file
21674
- ${chalk20.cyan("pretense policy list")} List the 5 compliance presets
21675
- ${chalk20.cyan("pretense audit")} Show mutation audit log
21676
- ${chalk20.cyan("pretense usage")} Plan usage vs limits
21677
- ${chalk20.cyan("pretense credits")} Remaining mutation budget (alias: tokens)
21678
- ${chalk20.cyan("pretense upgrade")} Compare plans / upgrade
21679
- ${chalk20.cyan("pretense auth login")} Authenticate with Pretense
21680
- ${chalk20.cyan("pretense install")} Install git hooks
21681
- ${chalk20.cyan("pretense logs --limit 20")} Show recent audit entries
21979
+ ${chalk21.dim("All commands:")}
21980
+ ${chalk21.cyan("pretense init")} Scan codebase and build protection profile
21981
+ ${chalk21.cyan("pretense run <tool>")} Run an AI tool through the proxy, no manual export
21982
+ ${chalk21.cyan("pretense start")} Start proxy on localhost:9339
21983
+ ${chalk21.cyan("pretense stop")} Stop the running proxy
21984
+ ${chalk21.cyan("pretense scan src/api.ts")} Scan file for secrets
21985
+ ${chalk21.cyan("pretense scan ci")} CI-optimized scan (exit 0=clean, 2=findings)
21986
+ ${chalk21.cyan("pretense status")} Show protection status
21987
+ ${chalk21.cyan("pretense review")} Preview mutations that would be applied
21988
+ ${chalk21.cyan("pretense review --json")} Output review as JSON
21989
+ ${chalk21.cyan("pretense mutate <file>")} Mutate secrets in place + auto-write reversal map
21990
+ ${chalk21.cyan("pretense reverse <file>")} Restore a mutated file (auto-finds its map)
21991
+ ${chalk21.cyan("pretense policy list")} List the 5 compliance presets
21992
+ ${chalk21.cyan("pretense audit")} Show mutation audit log
21993
+ ${chalk21.cyan("pretense usage")} Plan usage vs limits
21994
+ ${chalk21.cyan("pretense credits")} Remaining mutation budget (alias: tokens)
21995
+ ${chalk21.cyan("pretense upgrade")} Compare plans / upgrade
21996
+ ${chalk21.cyan("pretense auth login")} Authenticate with Pretense
21997
+ ${chalk21.cyan("pretense install")} Install git hooks
21998
+ ${chalk21.cyan("pretense logs --limit 20")} Show recent audit entries
21682
21999
 
21683
- ${chalk20.dim("Run")} ${chalk20.cyan("pretense <command> --help")} ${chalk20.dim("for command-specific help.")}
22000
+ ${chalk21.dim("Run")} ${chalk21.cyan("pretense <command> --help")} ${chalk21.dim("for command-specific help.")}
21684
22001
  `);
21685
22002
  var KNOWN_COMMANDS = [
21686
22003
  "init",
@@ -21699,6 +22016,8 @@ var KNOWN_COMMANDS = [
21699
22016
  "quickstart",
21700
22017
  "mutate",
21701
22018
  "reverse",
22019
+ "run",
22020
+ "with",
21702
22021
  "policy",
21703
22022
  "audit",
21704
22023
  "version",
@@ -21734,6 +22053,7 @@ program.addCommand(completionCommand());
21734
22053
  program.addCommand(quickstartCommand());
21735
22054
  program.addCommand(mutateCommand());
21736
22055
  program.addCommand(reverseCommand());
22056
+ program.addCommand(runCommand());
21737
22057
  program.addCommand(policyCommand());
21738
22058
  program.addCommand(auditCommand());
21739
22059
  program.addCommand(creditsCommand());
@@ -21741,13 +22061,13 @@ program.addCommand(upgradeCommand());
21741
22061
  program.on("command:*", (operands) => {
21742
22062
  const unknown = operands[0] ?? "";
21743
22063
  const suggestion = findClosestCommand(unknown, KNOWN_COMMANDS);
21744
- console.error(chalk20.red(`
22064
+ console.error(chalk21.red(`
21745
22065
  x Unknown command: "${unknown}"`));
21746
22066
  if (suggestion) {
21747
- console.error(chalk20.yellow(` Did you mean: ${chalk20.bold(`pretense ${suggestion}`)}`));
22067
+ console.error(chalk21.yellow(` Did you mean: ${chalk21.bold(`pretense ${suggestion}`)}`));
21748
22068
  }
21749
- console.error(chalk20.dim(`
21750
- Run ${chalk20.cyan("pretense --help")} to see all available commands.
22069
+ console.error(chalk21.dim(`
22070
+ Run ${chalk21.cyan("pretense --help")} to see all available commands.
21751
22071
  `));
21752
22072
  process.exit(1);
21753
22073
  });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-K3L4E6HN.js";
4
+ } from "./chunk-2WW3XY6A.js";
5
5
  import {
6
6
  init_esm_shims
7
7
  } from "./chunk-TWMRHZGM.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pretense/cli",
3
- "version": "0.6.12",
3
+ "version": "0.6.14",
4
4
  "description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,8 +23,8 @@
23
23
  "tsx": "^4.0.0",
24
24
  "typescript": "^5.4.0",
25
25
  "vitest": "^1.0.0",
26
- "@pretense/compliance-engine": "0.1.0",
27
26
  "@pretense/billing": "0.1.0",
27
+ "@pretense/compliance-engine": "0.1.0",
28
28
  "@pretense/protocol": "0.1.0",
29
29
  "@pretense/learner": "0.2.0",
30
30
  "@pretense/mutator": "0.2.0",