@pretense/cli 0.6.13 → 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 +52 -10
- package/dist/{chunk-HWSWJ4EC.js → chunk-2WW3XY6A.js} +1 -1
- package/dist/index.js +412 -140
- package/dist/postinstall.js +1 -1
- package/package.json +3 -3
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>
|
|
107
|
-
pretense reverse <file>
|
|
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` **
|
|
118
|
-
|
|
119
|
-
|
|
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>
|
|
387
|
-
pretense mutate <file>
|
|
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,
|
|
455
|
-
|
|
456
|
-
`
|
|
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.
|
|
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-
|
|
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
|
|
225
|
-
import
|
|
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();
|
|
@@ -16445,11 +16445,11 @@ function versionFromCliPackageJson() {
|
|
|
16445
16445
|
return void 0;
|
|
16446
16446
|
}
|
|
16447
16447
|
function productVersion() {
|
|
16448
|
-
return override ?? (true ? "0.6.
|
|
16448
|
+
return override ?? (true ? "0.6.14" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
|
|
16449
16449
|
}
|
|
16450
16450
|
var UNKNOWN_COMMIT = "unknown";
|
|
16451
16451
|
function bakedCommitSha() {
|
|
16452
|
-
return "
|
|
16452
|
+
return "13f5fb3bd88295258e3733458b63315e9382678e".length > 0 ? "13f5fb3bd88295258e3733458b63315e9382678e" : UNKNOWN_COMMIT;
|
|
16453
16453
|
}
|
|
16454
16454
|
var FEATURE_TIERS = {
|
|
16455
16455
|
compliance_export: "pro",
|
|
@@ -17901,7 +17901,10 @@ function startProxy(opts = {}) {
|
|
|
17901
17901
|
function renderClientEnvLines(port, protocol) {
|
|
17902
17902
|
const base = `${protocol}://localhost:${port}`;
|
|
17903
17903
|
return [
|
|
17904
|
-
"
|
|
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:",
|
|
17905
17908
|
` ANTHROPIC_BASE_URL=${base}`,
|
|
17906
17909
|
` OPENAI_BASE_URL=${base}/v1`,
|
|
17907
17910
|
` GOOGLE_GEMINI_BASE_URL=${base}`
|
|
@@ -17931,6 +17934,33 @@ if (isMain) {
|
|
|
17931
17934
|
startProxy();
|
|
17932
17935
|
}
|
|
17933
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
|
+
|
|
17934
17964
|
// src/proxy-pidfile.ts
|
|
17935
17965
|
init_esm_shims();
|
|
17936
17966
|
import { existsSync as existsSync7, readFileSync as readFileSync6, unlinkSync } from "fs";
|
|
@@ -18054,15 +18084,15 @@ function explainAddrInUse(port) {
|
|
|
18054
18084
|
);
|
|
18055
18085
|
}
|
|
18056
18086
|
function startCommand() {
|
|
18057
|
-
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) => {
|
|
18058
|
-
const
|
|
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);
|
|
18059
18089
|
const existing = readProxyRecord();
|
|
18060
18090
|
if (existing) {
|
|
18061
18091
|
if (isProcessAlive(existing.pid)) {
|
|
18062
|
-
if (existing.port ===
|
|
18092
|
+
if (existing.port === requested) {
|
|
18063
18093
|
console.error(
|
|
18064
18094
|
chalk2.red(`
|
|
18065
|
-
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 ${
|
|
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"
|
|
18066
18096
|
);
|
|
18067
18097
|
process.exit(1);
|
|
18068
18098
|
}
|
|
@@ -18070,6 +18100,25 @@ function startCommand() {
|
|
|
18070
18100
|
removeProxyRecord();
|
|
18071
18101
|
}
|
|
18072
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
|
+
}
|
|
18073
18122
|
process.on("uncaughtException", (err) => {
|
|
18074
18123
|
if (err?.code === "EADDRINUSE") {
|
|
18075
18124
|
const rec = readProxyRecord();
|
|
@@ -20570,11 +20619,13 @@ function quickstartCommand() {
|
|
|
20570
20619
|
}
|
|
20571
20620
|
console.log(chalk13.dim(" [4/4] Done.\n"));
|
|
20572
20621
|
console.log(chalk13.bold(" Next steps:\n"));
|
|
20573
|
-
console.log(` ${chalk13.green("1.")} ${chalk13.cyan(
|
|
20574
|
-
console.log(`
|
|
20575
|
-
console.log(` ${chalk13.green("
|
|
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")}`);
|
|
20576
20627
|
console.log(` ${chalk13.dim("ANTHROPIC_BASE_URL=http://localhost:9339 claude")}`);
|
|
20577
|
-
console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/v1
|
|
20628
|
+
console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/v1 cursor ...")}`);
|
|
20578
20629
|
console.log(` ${chalk13.dim("GOOGLE_GEMINI_BASE_URL=http://localhost:9339 gemini ...")}
|
|
20579
20630
|
`);
|
|
20580
20631
|
console.log(chalk13.bold.green(" Protected. Happy coding.\n"));
|
|
@@ -20586,7 +20637,7 @@ init_esm_shims();
|
|
|
20586
20637
|
import { Command as Command15, Option as Option2 } from "commander";
|
|
20587
20638
|
import chalk14 from "chalk";
|
|
20588
20639
|
import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, chmodSync as chmodSync4, existsSync as existsSync15 } from "fs";
|
|
20589
|
-
import { createHash as
|
|
20640
|
+
import { createHash as createHash3 } from "crypto";
|
|
20590
20641
|
import { resolve as resolve5, extname as extname4 } from "path";
|
|
20591
20642
|
|
|
20592
20643
|
// src/secret-crypto.ts
|
|
@@ -20694,6 +20745,22 @@ function writeFileAtomicSync(targetPath, data, opts = {}) {
|
|
|
20694
20745
|
}
|
|
20695
20746
|
}
|
|
20696
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
|
+
|
|
20697
20764
|
// src/commands/mutate.ts
|
|
20698
20765
|
var REVERSAL_MAP_VERSION = 3;
|
|
20699
20766
|
function preflightReversalMap(mapPath, absFile, content, contentSha, force) {
|
|
@@ -20758,7 +20825,7 @@ function preflightReversalMap(mapPath, absFile, content, contentSha, force) {
|
|
|
20758
20825
|
};
|
|
20759
20826
|
}
|
|
20760
20827
|
function sha256Hex2(s) {
|
|
20761
|
-
return
|
|
20828
|
+
return createHash3("sha256").update(s, "utf8").digest("hex");
|
|
20762
20829
|
}
|
|
20763
20830
|
var L2_LANGUAGE_BY_EXT2 = {
|
|
20764
20831
|
".ts": "typescript",
|
|
@@ -20863,6 +20930,8 @@ async function runMutate(file, opts = {}) {
|
|
|
20863
20930
|
);
|
|
20864
20931
|
return 1;
|
|
20865
20932
|
}
|
|
20933
|
+
const preview = opts.stdout === true;
|
|
20934
|
+
const inPlace = !preview && !opts.output;
|
|
20866
20935
|
const { findings, unmutatable } = await collectMutationFindings(
|
|
20867
20936
|
content,
|
|
20868
20937
|
file,
|
|
@@ -20875,16 +20944,24 @@ async function runMutate(file, opts = {}) {
|
|
|
20875
20944
|
} else {
|
|
20876
20945
|
reportUnmutatable(unmutatable);
|
|
20877
20946
|
}
|
|
20878
|
-
if (
|
|
20947
|
+
if (preview) process.stdout.write(content);
|
|
20879
20948
|
return 0;
|
|
20880
20949
|
}
|
|
20881
20950
|
reportUnmutatable(unmutatable);
|
|
20882
20951
|
const absFile = resolve5(file);
|
|
20883
20952
|
const projectRoot = findProjectRoot(absFile);
|
|
20884
20953
|
const result = mutateSecrets(content, findings, { projectRoot, keysDir: opts.keysDir });
|
|
20885
|
-
|
|
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) {
|
|
20886
20963
|
const contentSha = sha256Hex2(content);
|
|
20887
|
-
const pre = preflightReversalMap(
|
|
20964
|
+
const pre = preflightReversalMap(effectiveMap, absFile, content, contentSha, opts.forceMap === true);
|
|
20888
20965
|
if (pre.action === "refuse") {
|
|
20889
20966
|
console.error(
|
|
20890
20967
|
chalk14.red(`
|
|
@@ -20899,7 +20976,7 @@ async function runMutate(file, opts = {}) {
|
|
|
20899
20976
|
const isNoop = pre.action === "keep" && pre.expectedMutatedSha === mutatedSha;
|
|
20900
20977
|
if (isNoop) {
|
|
20901
20978
|
console.error(
|
|
20902
|
-
chalk14.dim(` = Reversal map ${
|
|
20979
|
+
chalk14.dim(` = Reversal map ${effectiveMap} already describes ${file} at this exact content`) + chalk14.dim(`
|
|
20903
20980
|
hash \u2014 left unchanged (mutation is deterministic, so a rewrite would be`) + chalk14.dim(`
|
|
20904
20981
|
byte-identical).`)
|
|
20905
20982
|
);
|
|
@@ -20922,7 +20999,7 @@ async function runMutate(file, opts = {}) {
|
|
|
20922
20999
|
type: m.type
|
|
20923
21000
|
}))
|
|
20924
21001
|
};
|
|
20925
|
-
const written = writeReversalMap(
|
|
21002
|
+
const written = writeReversalMap(effectiveMap, mapFile);
|
|
20926
21003
|
if (written !== 0) return written;
|
|
20927
21004
|
if (pre.action === "write" && pre.generation > 1) {
|
|
20928
21005
|
console.error(
|
|
@@ -20936,18 +21013,17 @@ async function runMutate(file, opts = {}) {
|
|
|
20936
21013
|
}
|
|
20937
21014
|
}
|
|
20938
21015
|
}
|
|
20939
|
-
if (
|
|
21016
|
+
if (inPlace && !effectiveMap) {
|
|
20940
21017
|
console.error(
|
|
20941
|
-
chalk14.
|
|
20942
|
-
|
|
20943
|
-
|
|
20944
|
-
the original secrets is kept. Re-run with --map <path> to be able to`) + chalk14.dim(`
|
|
20945
|
-
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.
|
|
20946
21021
|
`)
|
|
20947
21022
|
);
|
|
21023
|
+
return 1;
|
|
20948
21024
|
}
|
|
20949
21025
|
try {
|
|
20950
|
-
if (
|
|
21026
|
+
if (inPlace) {
|
|
20951
21027
|
writeFileAtomicSync(file, result.content);
|
|
20952
21028
|
console.error(chalk14.green(` \u2713 Mutated ${result.mutations.length} secrets in ${file} (in-place)`));
|
|
20953
21029
|
} else if (opts.output) {
|
|
@@ -20972,8 +21048,11 @@ async function runMutate(file, opts = {}) {
|
|
|
20972
21048
|
}
|
|
20973
21049
|
function mutateCommand() {
|
|
20974
21050
|
return new Command15("mutate").description(
|
|
20975
|
-
"
|
|
20976
|
-
).argument("<file>", "File to mutate").option("-i, --in-place", "Write back to the input file (
|
|
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(
|
|
20977
21056
|
"--force-map",
|
|
20978
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)"
|
|
20979
21058
|
).option("--show-map", "Print mutation map JSON to stderr").addOption(
|
|
@@ -21005,9 +21084,9 @@ import { Command as Command16 } from "commander";
|
|
|
21005
21084
|
import chalk15 from "chalk";
|
|
21006
21085
|
import { readFileSync as readFileSync16, existsSync as existsSync16, statSync as statSync6 } from "fs";
|
|
21007
21086
|
import { resolve as resolve6 } from "path";
|
|
21008
|
-
import { createHash as
|
|
21087
|
+
import { createHash as createHash4 } from "crypto";
|
|
21009
21088
|
function sha256Hex3(s) {
|
|
21010
|
-
return
|
|
21089
|
+
return createHash4("sha256").update(s, "utf8").digest("hex");
|
|
21011
21090
|
}
|
|
21012
21091
|
var SYNTHETIC_MARKERS = [
|
|
21013
21092
|
/pretense_mut_u[0-9a-z]{6,}:pretense_mut_p[0-9a-z]{6,}@/,
|
|
@@ -21063,22 +21142,25 @@ function loadReversalMap(mapPath) {
|
|
|
21063
21142
|
}
|
|
21064
21143
|
function runReverse(file, opts = {}) {
|
|
21065
21144
|
const absPath = resolve6(file);
|
|
21066
|
-
if (!opts.map) {
|
|
21067
|
-
console.error(
|
|
21068
|
-
chalk15.red(`
|
|
21069
|
-
x reverse requires a reversal map: --map <file>`) + chalk15.dim(`
|
|
21070
|
-
Produce one with: ${chalk15.cyan(`pretense mutate ${file} -i --map <file>`)}
|
|
21071
|
-
`)
|
|
21072
|
-
);
|
|
21073
|
-
return 1;
|
|
21074
|
-
}
|
|
21075
21145
|
if (!existsSync16(absPath) || !statSync6(absPath).isFile()) {
|
|
21076
21146
|
console.error(chalk15.red(`
|
|
21077
21147
|
x File not found: ${file}
|
|
21078
21148
|
`));
|
|
21079
21149
|
return 1;
|
|
21080
21150
|
}
|
|
21081
|
-
const
|
|
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);
|
|
21082
21164
|
let plainMutations;
|
|
21083
21165
|
if (mapFile.encrypted) {
|
|
21084
21166
|
const projectRoot = findProjectRoot(mapFile.file ?? absPath);
|
|
@@ -21184,7 +21266,9 @@ function runReverse(file, opts = {}) {
|
|
|
21184
21266
|
}
|
|
21185
21267
|
const mark = lineage.ok ? chalk15.green(" \u2713 Restored") : chalk15.yellow(" ! Partially restored");
|
|
21186
21268
|
const detail = `${appliedCount} of ${plainMutations.length} mapped tokens` + (lineage.ok ? "" : " \u2014 to an earlier MUTATED generation, not the original");
|
|
21187
|
-
|
|
21269
|
+
const preview = opts.stdout === true;
|
|
21270
|
+
const inPlace = !preview && !opts.output;
|
|
21271
|
+
if (inPlace) {
|
|
21188
21272
|
writeFileAtomicSync(absPath, restored);
|
|
21189
21273
|
console.error(`${mark} ${absPath} (${detail}, in-place)`);
|
|
21190
21274
|
} else if (opts.output) {
|
|
@@ -21211,8 +21295,11 @@ function runReverse(file, opts = {}) {
|
|
|
21211
21295
|
}
|
|
21212
21296
|
function reverseCommand() {
|
|
21213
21297
|
return new Command16("reverse").description(
|
|
21214
|
-
"Restore a mutated file to its EXACT original bytes
|
|
21215
|
-
).argument("<file>", "Mutated file to restore (must be byte-identical to the mutate output)").
|
|
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(
|
|
21216
21303
|
"--allow-partial",
|
|
21217
21304
|
"Apply a map whose originals are themselves synthetics (writes the PREVIOUS MUTATED generation, not the original)"
|
|
21218
21305
|
).action((file, opts) => {
|
|
@@ -21220,10 +21307,191 @@ function reverseCommand() {
|
|
|
21220
21307
|
});
|
|
21221
21308
|
}
|
|
21222
21309
|
|
|
21223
|
-
// src/commands/
|
|
21310
|
+
// src/commands/run.ts
|
|
21224
21311
|
init_esm_shims();
|
|
21225
21312
|
import { Command as Command17 } from "commander";
|
|
21226
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";
|
|
21227
21495
|
function listAction(opts) {
|
|
21228
21496
|
const ids = listPresetIds();
|
|
21229
21497
|
if (opts.json) {
|
|
@@ -21246,21 +21514,21 @@ function listAction(opts) {
|
|
|
21246
21514
|
);
|
|
21247
21515
|
return;
|
|
21248
21516
|
}
|
|
21249
|
-
console.log(
|
|
21517
|
+
console.log(chalk17.cyan("\n Pretense Compliance Frameworks\n"));
|
|
21250
21518
|
console.log(
|
|
21251
|
-
|
|
21519
|
+
chalk17.dim(
|
|
21252
21520
|
" " + "ID".padEnd(8) + "NAME".padEnd(14) + "TIER".padEnd(12) + "AUDIT".padEnd(8) + "DETECTORS"
|
|
21253
21521
|
)
|
|
21254
21522
|
);
|
|
21255
|
-
console.log(
|
|
21523
|
+
console.log(chalk17.dim(" " + "\u2500".repeat(64)));
|
|
21256
21524
|
for (const id of ids) {
|
|
21257
21525
|
const p = COMPLIANCE_PRESETS[id];
|
|
21258
21526
|
console.log(
|
|
21259
|
-
" " +
|
|
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`)
|
|
21260
21528
|
);
|
|
21261
21529
|
}
|
|
21262
|
-
console.log(
|
|
21263
|
-
Run ${
|
|
21530
|
+
console.log(chalk17.dim(`
|
|
21531
|
+
Run ${chalk17.cyan("pretense policy show <id>")} for detector-level detail.
|
|
21264
21532
|
`));
|
|
21265
21533
|
}
|
|
21266
21534
|
function showAction(id, opts) {
|
|
@@ -21269,8 +21537,8 @@ function showAction(id, opts) {
|
|
|
21269
21537
|
preset = loadPreset(id);
|
|
21270
21538
|
} catch (err) {
|
|
21271
21539
|
console.error(
|
|
21272
|
-
|
|
21273
|
-
x ${err.message}`) +
|
|
21540
|
+
chalk17.red(`
|
|
21541
|
+
x ${err.message}`) + chalk17.dim(`
|
|
21274
21542
|
Valid frameworks: ${listPresetIds().join(", ")}
|
|
21275
21543
|
`)
|
|
21276
21544
|
);
|
|
@@ -21280,24 +21548,24 @@ function showAction(id, opts) {
|
|
|
21280
21548
|
console.log(JSON.stringify(preset, null, 2));
|
|
21281
21549
|
return;
|
|
21282
21550
|
}
|
|
21283
|
-
console.log(
|
|
21284
|
-
${preset.name} `) +
|
|
21285
|
-
console.log(
|
|
21551
|
+
console.log(chalk17.cyan(`
|
|
21552
|
+
${preset.name} `) + chalk17.dim(`(${preset.id})`));
|
|
21553
|
+
console.log(chalk17.dim(` ${preset.description}
|
|
21286
21554
|
`));
|
|
21287
|
-
console.log(` Minimum tier: ${
|
|
21288
|
-
console.log(` Audit required: ${preset.auditRequired ?
|
|
21289
|
-
console.log(` Detector sets: ${
|
|
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(", "))}
|
|
21290
21558
|
`);
|
|
21291
21559
|
const overrides = Object.entries(preset.actionOverrides);
|
|
21292
|
-
console.log(
|
|
21560
|
+
console.log(chalk17.dim(` Detector actions (${overrides.length}):`));
|
|
21293
21561
|
for (const [detector, action] of overrides) {
|
|
21294
|
-
const color = action === "block" ?
|
|
21562
|
+
const color = action === "block" ? chalk17.red : action === "redact" ? chalk17.yellow : chalk17.dim;
|
|
21295
21563
|
console.log(` ${detector.padEnd(24)} ${color(action)}`);
|
|
21296
21564
|
}
|
|
21297
21565
|
console.log();
|
|
21298
21566
|
}
|
|
21299
21567
|
function policyCommand() {
|
|
21300
|
-
const cmd = new
|
|
21568
|
+
const cmd = new Command18("policy").description(
|
|
21301
21569
|
"List and inspect the built-in compliance presets (5: hipaa, gdpr, soc2, nist, pci)"
|
|
21302
21570
|
);
|
|
21303
21571
|
cmd.command("list").description("List the 5 built-in compliance presets").option("--json", "Output as JSON").action((opts) => listAction(opts));
|
|
@@ -21308,10 +21576,10 @@ function policyCommand() {
|
|
|
21308
21576
|
|
|
21309
21577
|
// src/commands/audit.ts
|
|
21310
21578
|
init_esm_shims();
|
|
21311
|
-
import { Command as
|
|
21312
|
-
import
|
|
21579
|
+
import { Command as Command19 } from "commander";
|
|
21580
|
+
import chalk18 from "chalk";
|
|
21313
21581
|
import { homedir as homedir10 } from "os";
|
|
21314
|
-
import { join as
|
|
21582
|
+
import { join as join21 } from "path";
|
|
21315
21583
|
var VALID_FORMATS2 = ["table", "csv", "json", "jsonl"];
|
|
21316
21584
|
function toSafeAuditRow(e) {
|
|
21317
21585
|
return {
|
|
@@ -21382,18 +21650,18 @@ function resolveFormat2(opts) {
|
|
|
21382
21650
|
const raw2 = opts.format?.trim().toLowerCase();
|
|
21383
21651
|
if (raw2 !== void 0 && !VALID_FORMATS2.includes(raw2)) {
|
|
21384
21652
|
console.error(
|
|
21385
|
-
|
|
21653
|
+
chalk18.red(`
|
|
21386
21654
|
x Unknown --format: "${opts.format}"
|
|
21387
|
-
`) +
|
|
21655
|
+
`) + chalk18.dim(" Valid formats: table, csv, json (alias: jsonl).\n")
|
|
21388
21656
|
);
|
|
21389
21657
|
return null;
|
|
21390
21658
|
}
|
|
21391
21659
|
const fromFormat = raw2 === void 0 ? void 0 : raw2 === "jsonl" ? "json" : raw2;
|
|
21392
21660
|
if (opts.json && fromFormat !== void 0 && fromFormat !== "json") {
|
|
21393
21661
|
console.error(
|
|
21394
|
-
|
|
21662
|
+
chalk18.red(`
|
|
21395
21663
|
x --json and --format ${fromFormat} request different outputs.
|
|
21396
|
-
`) +
|
|
21664
|
+
`) + chalk18.dim(" Pick one.\n")
|
|
21397
21665
|
);
|
|
21398
21666
|
return null;
|
|
21399
21667
|
}
|
|
@@ -21401,7 +21669,7 @@ function resolveFormat2(opts) {
|
|
|
21401
21669
|
return opts.json ? "json" : "table";
|
|
21402
21670
|
}
|
|
21403
21671
|
function auditCommand() {
|
|
21404
|
-
return new
|
|
21672
|
+
return new Command19("audit").description(
|
|
21405
21673
|
"Show recent mutation/tokenization audit entries (leak-proof; JSONL with --json, RFC 4180 CSV with --format csv)"
|
|
21406
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(
|
|
21407
21675
|
"--framework <name>",
|
|
@@ -21421,21 +21689,21 @@ function auditCommand() {
|
|
|
21421
21689
|
framework = resolveFrameworkName(opts.framework);
|
|
21422
21690
|
if (!framework) {
|
|
21423
21691
|
console.error(
|
|
21424
|
-
|
|
21692
|
+
chalk18.red(`
|
|
21425
21693
|
x Unknown framework: "${opts.framework}"
|
|
21426
|
-
`) +
|
|
21694
|
+
`) + chalk18.dim(" Run ") + chalk18.cyan("pretense audit --list-frameworks") + chalk18.dim(" to see all 36.\n")
|
|
21427
21695
|
);
|
|
21428
21696
|
process.exit(1);
|
|
21429
21697
|
}
|
|
21430
21698
|
}
|
|
21431
21699
|
const limit = parseInt(opts.limit ?? "20", 10);
|
|
21432
21700
|
if (!Number.isFinite(limit) || limit <= 0) {
|
|
21433
|
-
console.error(
|
|
21701
|
+
console.error(chalk18.red(`
|
|
21434
21702
|
x Invalid --limit: "${opts.limit}". Provide a positive integer.
|
|
21435
21703
|
`));
|
|
21436
21704
|
process.exit(1);
|
|
21437
21705
|
}
|
|
21438
|
-
const dbPath = opts.db ??
|
|
21706
|
+
const dbPath = opts.db ?? join21(homedir10(), ".pretense", "audit.db");
|
|
21439
21707
|
let rows;
|
|
21440
21708
|
try {
|
|
21441
21709
|
const store = new AuditStore(dbPath);
|
|
@@ -21455,8 +21723,8 @@ function auditCommand() {
|
|
|
21455
21723
|
console.error("pretense: no audit data \u2014 the audit database has not been initialized yet.");
|
|
21456
21724
|
return;
|
|
21457
21725
|
}
|
|
21458
|
-
console.log(
|
|
21459
|
-
console.log(
|
|
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"));
|
|
21460
21728
|
return;
|
|
21461
21729
|
}
|
|
21462
21730
|
if (format === "csv") {
|
|
@@ -21468,23 +21736,23 @@ function auditCommand() {
|
|
|
21468
21736
|
return;
|
|
21469
21737
|
}
|
|
21470
21738
|
if (rows.length === 0) {
|
|
21471
|
-
console.log(
|
|
21739
|
+
console.log(chalk18.dim("\n No audit entries yet. Run pretense start and make some requests.\n"));
|
|
21472
21740
|
return;
|
|
21473
21741
|
}
|
|
21474
|
-
console.log(
|
|
21742
|
+
console.log(chalk18.cyan(`
|
|
21475
21743
|
Pretense Mutation Audit \u2014 last ${rows.length} entries
|
|
21476
21744
|
`));
|
|
21477
21745
|
console.log(
|
|
21478
|
-
|
|
21746
|
+
chalk18.dim(
|
|
21479
21747
|
" " + "TIME".padEnd(10) + "ACTION".padEnd(12) + "PROVIDER".padEnd(14) + "MUTATED".padEnd(10) + "BLOCKED".padEnd(10) + "PII".padEnd(8) + "MS"
|
|
21480
21748
|
)
|
|
21481
21749
|
);
|
|
21482
|
-
console.log(
|
|
21750
|
+
console.log(chalk18.dim(" " + "\u2500".repeat(72)));
|
|
21483
21751
|
for (const row of rows) {
|
|
21484
21752
|
const time = new Date(row.timestamp).toTimeString().slice(0, 8);
|
|
21485
|
-
const actionColor = row.action === "blocked" ?
|
|
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;
|
|
21486
21754
|
console.log(
|
|
21487
|
-
" " +
|
|
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))
|
|
21488
21756
|
);
|
|
21489
21757
|
}
|
|
21490
21758
|
console.log();
|
|
@@ -21493,9 +21761,9 @@ function auditCommand() {
|
|
|
21493
21761
|
|
|
21494
21762
|
// src/commands/credits.ts
|
|
21495
21763
|
init_esm_shims();
|
|
21496
|
-
import { Command as
|
|
21497
|
-
import
|
|
21498
|
-
import { join as
|
|
21764
|
+
import { Command as Command20 } from "commander";
|
|
21765
|
+
import chalk19 from "chalk";
|
|
21766
|
+
import { join as join24 } from "path";
|
|
21499
21767
|
import { homedir as homedir11 } from "os";
|
|
21500
21768
|
function remaining(used, limit) {
|
|
21501
21769
|
if (limit === null) return null;
|
|
@@ -21505,16 +21773,16 @@ function fmt(n) {
|
|
|
21505
21773
|
return n === null ? "unlimited" : n.toLocaleString("en-US");
|
|
21506
21774
|
}
|
|
21507
21775
|
function bar(used, limit, width = 24) {
|
|
21508
|
-
if (limit === null) return
|
|
21776
|
+
if (limit === null) return chalk19.green("\u2588".repeat(width));
|
|
21509
21777
|
const pct = limit <= 0 ? 1 : Math.min(1, used / limit);
|
|
21510
21778
|
const filled = Math.min(width, Math.round(pct * width));
|
|
21511
21779
|
const s = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
21512
|
-
if (pct >= 1) return
|
|
21513
|
-
if (pct >= 0.8) return
|
|
21514
|
-
return
|
|
21780
|
+
if (pct >= 1) return chalk19.red(s);
|
|
21781
|
+
if (pct >= 0.8) return chalk19.yellow(s);
|
|
21782
|
+
return chalk19.green(s);
|
|
21515
21783
|
}
|
|
21516
21784
|
function creditsCommand() {
|
|
21517
|
-
return new
|
|
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) => {
|
|
21518
21786
|
const resolution = await resolvePlanTier({ offline: opts.offline });
|
|
21519
21787
|
const tier = resolution.tier;
|
|
21520
21788
|
const collected = collectUsage(opts.db);
|
|
@@ -21527,7 +21795,7 @@ function creditsCommand() {
|
|
|
21527
21795
|
periodEnd: null
|
|
21528
21796
|
};
|
|
21529
21797
|
if (!collected && !opts.json) {
|
|
21530
|
-
console.log(
|
|
21798
|
+
console.log(chalk19.dim("\n No usage recorded yet \u2014 showing your full allowance.\n"));
|
|
21531
21799
|
}
|
|
21532
21800
|
const mutationLimit = tier ? PLAN_CONFIGS[tier].mutationsPerPeriod : null;
|
|
21533
21801
|
const scanLimit = tier ? PLAN_CONFIGS[tier].scansPerPeriod : null;
|
|
@@ -21559,12 +21827,12 @@ function creditsCommand() {
|
|
|
21559
21827
|
);
|
|
21560
21828
|
return;
|
|
21561
21829
|
}
|
|
21562
|
-
console.log(
|
|
21563
|
-
console.log(
|
|
21830
|
+
console.log(chalk19.cyan("\n Pretense Credits"));
|
|
21831
|
+
console.log(chalk19.dim(` Plan: ${tierLabel(tier)} | Resets: ${resetCadenceLabel()}
|
|
21564
21832
|
`));
|
|
21565
21833
|
if (!tier) {
|
|
21566
21834
|
for (const line of unverifiedNote(resolution.detail)) {
|
|
21567
|
-
console.log(
|
|
21835
|
+
console.log(chalk19.yellow(` ${line}`));
|
|
21568
21836
|
}
|
|
21569
21837
|
console.log();
|
|
21570
21838
|
console.log(" " + "Mutations used".padEnd(20) + m.mutationsApplied.toLocaleString("en-US"));
|
|
@@ -21573,18 +21841,18 @@ function creditsCommand() {
|
|
|
21573
21841
|
return;
|
|
21574
21842
|
}
|
|
21575
21843
|
console.log(
|
|
21576
|
-
" " + "Mutations".padEnd(14) + bar(m.mutationsApplied, mutationLimit) + " " +
|
|
21844
|
+
" " + "Mutations".padEnd(14) + bar(m.mutationsApplied, mutationLimit) + " " + chalk19.bold(fmt(mutationsLeft)) + chalk19.dim(` left of ${fmt(mutationLimit)}`)
|
|
21577
21845
|
);
|
|
21578
21846
|
console.log(
|
|
21579
|
-
" " + "Requests".padEnd(14) + bar(m.totalScans, scanLimit) + " " +
|
|
21847
|
+
" " + "Requests".padEnd(14) + bar(m.totalScans, scanLimit) + " " + chalk19.bold(fmt(scansLeft)) + chalk19.dim(` left of ${fmt(scanLimit)}`)
|
|
21580
21848
|
);
|
|
21581
21849
|
if (tier !== "enterprise" && mutationLimit !== null && mutationsLeft === 0) {
|
|
21582
21850
|
console.log(
|
|
21583
|
-
|
|
21851
|
+
chalk19.red("\n Mutation budget exhausted for this period.") + chalk19.dim(" Run ") + chalk19.cyan("pretense upgrade") + chalk19.dim(" to compare plans.")
|
|
21584
21852
|
);
|
|
21585
21853
|
} else if (tier !== "enterprise" && mutationLimit !== null && m.mutationsApplied >= mutationLimit * 0.8) {
|
|
21586
21854
|
console.log(
|
|
21587
|
-
|
|
21855
|
+
chalk19.yellow("\n Running low.") + chalk19.dim(" Run ") + chalk19.cyan("pretense upgrade") + chalk19.dim(" to compare plans.")
|
|
21588
21856
|
);
|
|
21589
21857
|
}
|
|
21590
21858
|
console.log();
|
|
@@ -21593,8 +21861,8 @@ function creditsCommand() {
|
|
|
21593
21861
|
|
|
21594
21862
|
// src/commands/upgrade.ts
|
|
21595
21863
|
init_esm_shims();
|
|
21596
|
-
import { Command as
|
|
21597
|
-
import
|
|
21864
|
+
import { Command as Command21 } from "commander";
|
|
21865
|
+
import chalk20 from "chalk";
|
|
21598
21866
|
var PRICING_URL = "https://pretense.ai/pricing";
|
|
21599
21867
|
var TIERS = ["free", "pro", "enterprise"];
|
|
21600
21868
|
function quota(n) {
|
|
@@ -21607,7 +21875,7 @@ function yesNo(b) {
|
|
|
21607
21875
|
return b ? "yes" : "\u2014";
|
|
21608
21876
|
}
|
|
21609
21877
|
function upgradeCommand() {
|
|
21610
|
-
return new
|
|
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) => {
|
|
21611
21879
|
const resolution = await resolvePlanTier({ offline: opts.offline });
|
|
21612
21880
|
const current = resolution.tier;
|
|
21613
21881
|
if (opts.json) {
|
|
@@ -21635,11 +21903,11 @@ function upgradeCommand() {
|
|
|
21635
21903
|
);
|
|
21636
21904
|
return;
|
|
21637
21905
|
}
|
|
21638
|
-
console.log(
|
|
21906
|
+
console.log(chalk20.cyan("\n Pretense Plans\n"));
|
|
21639
21907
|
const col = 16;
|
|
21640
21908
|
const header = " " + "".padEnd(18) + TIERS.map((t) => tierName(t).padEnd(col)).join("");
|
|
21641
|
-
console.log(
|
|
21642
|
-
console.log(" " +
|
|
21909
|
+
console.log(chalk20.bold(header));
|
|
21910
|
+
console.log(" " + chalk20.dim("\u2500".repeat(18 + col * TIERS.length)));
|
|
21643
21911
|
const rows = [
|
|
21644
21912
|
["Price", (t) => priceLabel(t)],
|
|
21645
21913
|
["Mutations/7d", (t) => quota(PLAN_CONFIGS[t].mutationsPerPeriod)],
|
|
@@ -21651,24 +21919,24 @@ function upgradeCommand() {
|
|
|
21651
21919
|
for (const [label, cell] of rows) {
|
|
21652
21920
|
console.log(" " + label.padEnd(18) + TIERS.map((t) => cell(t).padEnd(col)).join(""));
|
|
21653
21921
|
}
|
|
21654
|
-
console.log(" " +
|
|
21922
|
+
console.log(" " + chalk20.dim("\u2500".repeat(18 + col * TIERS.length)));
|
|
21655
21923
|
if (current) {
|
|
21656
|
-
console.log(
|
|
21924
|
+
console.log(chalk20.dim(`
|
|
21657
21925
|
Current plan: ${tierName(current)}`));
|
|
21658
21926
|
if (current === "enterprise") {
|
|
21659
|
-
console.log(
|
|
21927
|
+
console.log(chalk20.green(" You are on the highest tier \u2014 nothing to upgrade.\n"));
|
|
21660
21928
|
return;
|
|
21661
21929
|
}
|
|
21662
21930
|
} else {
|
|
21663
21931
|
console.log(
|
|
21664
|
-
|
|
21932
|
+
chalk20.yellow(
|
|
21665
21933
|
`
|
|
21666
21934
|
Could not confirm your current plan: ${resolution.detail ?? "unknown reason"}.`
|
|
21667
21935
|
)
|
|
21668
21936
|
);
|
|
21669
21937
|
}
|
|
21670
21938
|
const from = current ?? "unknown";
|
|
21671
|
-
console.log(
|
|
21939
|
+
console.log(chalk20.bold(`
|
|
21672
21940
|
\u2192 ${PRICING_URL}?ref=cli_upgrade&from=${from}
|
|
21673
21941
|
`));
|
|
21674
21942
|
});
|
|
@@ -21701,34 +21969,35 @@ function findClosestCommand(input, knownCommands) {
|
|
|
21701
21969
|
}
|
|
21702
21970
|
return bestDist <= 3 ? best : null;
|
|
21703
21971
|
}
|
|
21704
|
-
var program = new
|
|
21705
|
-
program.name("pretense").description(
|
|
21706
|
-
${
|
|
21707
|
-
${
|
|
21708
|
-
${
|
|
21709
|
-
${
|
|
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
|
|
21710
21978
|
|
|
21711
|
-
${
|
|
21712
|
-
${
|
|
21713
|
-
${
|
|
21714
|
-
${
|
|
21715
|
-
${
|
|
21716
|
-
${
|
|
21717
|
-
${
|
|
21718
|
-
${
|
|
21719
|
-
${
|
|
21720
|
-
${
|
|
21721
|
-
${
|
|
21722
|
-
${
|
|
21723
|
-
${
|
|
21724
|
-
${
|
|
21725
|
-
${
|
|
21726
|
-
${
|
|
21727
|
-
${
|
|
21728
|
-
${
|
|
21729
|
-
${
|
|
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
|
|
21730
21999
|
|
|
21731
|
-
${
|
|
22000
|
+
${chalk21.dim("Run")} ${chalk21.cyan("pretense <command> --help")} ${chalk21.dim("for command-specific help.")}
|
|
21732
22001
|
`);
|
|
21733
22002
|
var KNOWN_COMMANDS = [
|
|
21734
22003
|
"init",
|
|
@@ -21747,6 +22016,8 @@ var KNOWN_COMMANDS = [
|
|
|
21747
22016
|
"quickstart",
|
|
21748
22017
|
"mutate",
|
|
21749
22018
|
"reverse",
|
|
22019
|
+
"run",
|
|
22020
|
+
"with",
|
|
21750
22021
|
"policy",
|
|
21751
22022
|
"audit",
|
|
21752
22023
|
"version",
|
|
@@ -21782,6 +22053,7 @@ program.addCommand(completionCommand());
|
|
|
21782
22053
|
program.addCommand(quickstartCommand());
|
|
21783
22054
|
program.addCommand(mutateCommand());
|
|
21784
22055
|
program.addCommand(reverseCommand());
|
|
22056
|
+
program.addCommand(runCommand());
|
|
21785
22057
|
program.addCommand(policyCommand());
|
|
21786
22058
|
program.addCommand(auditCommand());
|
|
21787
22059
|
program.addCommand(creditsCommand());
|
|
@@ -21789,13 +22061,13 @@ program.addCommand(upgradeCommand());
|
|
|
21789
22061
|
program.on("command:*", (operands) => {
|
|
21790
22062
|
const unknown = operands[0] ?? "";
|
|
21791
22063
|
const suggestion = findClosestCommand(unknown, KNOWN_COMMANDS);
|
|
21792
|
-
console.error(
|
|
22064
|
+
console.error(chalk21.red(`
|
|
21793
22065
|
x Unknown command: "${unknown}"`));
|
|
21794
22066
|
if (suggestion) {
|
|
21795
|
-
console.error(
|
|
22067
|
+
console.error(chalk21.yellow(` Did you mean: ${chalk21.bold(`pretense ${suggestion}`)}`));
|
|
21796
22068
|
}
|
|
21797
|
-
console.error(
|
|
21798
|
-
Run ${
|
|
22069
|
+
console.error(chalk21.dim(`
|
|
22070
|
+
Run ${chalk21.cyan("pretense --help")} to see all available commands.
|
|
21799
22071
|
`));
|
|
21800
22072
|
process.exit(1);
|
|
21801
22073
|
});
|
package/dist/postinstall.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pretense/cli",
|
|
3
|
-
"version": "0.6.
|
|
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,11 +23,11 @@
|
|
|
23
23
|
"tsx": "^4.0.0",
|
|
24
24
|
"typescript": "^5.4.0",
|
|
25
25
|
"vitest": "^1.0.0",
|
|
26
|
+
"@pretense/billing": "0.1.0",
|
|
27
|
+
"@pretense/compliance-engine": "0.1.0",
|
|
26
28
|
"@pretense/protocol": "0.1.0",
|
|
27
29
|
"@pretense/learner": "0.2.0",
|
|
28
|
-
"@pretense/compliance-engine": "0.1.0",
|
|
29
30
|
"@pretense/mutator": "0.2.0",
|
|
30
|
-
"@pretense/billing": "0.1.0",
|
|
31
31
|
"@pretense/proxy": "0.1.0",
|
|
32
32
|
"@pretense/scanner": "0.2.0",
|
|
33
33
|
"@pretense/scanner-rs": "0.2.0",
|