@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 +52 -10
- package/dist/{chunk-K3L4E6HN.js → chunk-2WW3XY6A.js} +1 -1
- package/dist/index.js +465 -145
- package/dist/postinstall.js +1 -1
- package/package.json +2 -2
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();
|
|
@@ -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-`,
|
|
12749
|
-
// Stripe webhook `whsec_`) are deliberately skipped
|
|
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
|
-
|
|
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.
|
|
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 "
|
|
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
|
-
"
|
|
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
|
|
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 ===
|
|
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 ${
|
|
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
|
-
|
|
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(
|
|
20526
|
-
console.log(`
|
|
20527
|
-
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")}`);
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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 ${
|
|
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(
|
|
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 (
|
|
21016
|
+
if (inPlace && !effectiveMap) {
|
|
20892
21017
|
console.error(
|
|
20893
|
-
chalk14.
|
|
20894
|
-
|
|
20895
|
-
|
|
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 (
|
|
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
|
-
"
|
|
20928
|
-
).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(
|
|
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
|
|
21087
|
+
import { createHash as createHash4 } from "crypto";
|
|
20961
21088
|
function sha256Hex3(s) {
|
|
20962
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
21167
|
-
).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(
|
|
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/
|
|
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(
|
|
21517
|
+
console.log(chalk17.cyan("\n Pretense Compliance Frameworks\n"));
|
|
21202
21518
|
console.log(
|
|
21203
|
-
|
|
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(
|
|
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
|
-
" " +
|
|
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(
|
|
21215
|
-
Run ${
|
|
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
|
-
|
|
21225
|
-
x ${err.message}`) +
|
|
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(
|
|
21236
|
-
${preset.name} `) +
|
|
21237
|
-
console.log(
|
|
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: ${
|
|
21240
|
-
console.log(` Audit required: ${preset.auditRequired ?
|
|
21241
|
-
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(", "))}
|
|
21242
21558
|
`);
|
|
21243
21559
|
const overrides = Object.entries(preset.actionOverrides);
|
|
21244
|
-
console.log(
|
|
21560
|
+
console.log(chalk17.dim(` Detector actions (${overrides.length}):`));
|
|
21245
21561
|
for (const [detector, action] of overrides) {
|
|
21246
|
-
const color = action === "block" ?
|
|
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
|
|
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
|
|
21264
|
-
import
|
|
21579
|
+
import { Command as Command19 } from "commander";
|
|
21580
|
+
import chalk18 from "chalk";
|
|
21265
21581
|
import { homedir as homedir10 } from "os";
|
|
21266
|
-
import { join as
|
|
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
|
-
|
|
21653
|
+
chalk18.red(`
|
|
21338
21654
|
x Unknown --format: "${opts.format}"
|
|
21339
|
-
`) +
|
|
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
|
-
|
|
21662
|
+
chalk18.red(`
|
|
21347
21663
|
x --json and --format ${fromFormat} request different outputs.
|
|
21348
|
-
`) +
|
|
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
|
|
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
|
-
|
|
21692
|
+
chalk18.red(`
|
|
21377
21693
|
x Unknown framework: "${opts.framework}"
|
|
21378
|
-
`) +
|
|
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(
|
|
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 ??
|
|
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(
|
|
21411
|
-
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"));
|
|
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(
|
|
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(
|
|
21742
|
+
console.log(chalk18.cyan(`
|
|
21427
21743
|
Pretense Mutation Audit \u2014 last ${rows.length} entries
|
|
21428
21744
|
`));
|
|
21429
21745
|
console.log(
|
|
21430
|
-
|
|
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(
|
|
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" ?
|
|
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
|
-
" " +
|
|
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
|
|
21449
|
-
import
|
|
21450
|
-
import { join as
|
|
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
|
|
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
|
|
21465
|
-
if (pct >= 0.8) return
|
|
21466
|
-
return
|
|
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
|
|
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(
|
|
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(
|
|
21515
|
-
console.log(
|
|
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(
|
|
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) + " " +
|
|
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) + " " +
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
21549
|
-
import
|
|
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
|
|
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(
|
|
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(
|
|
21594
|
-
console.log(" " +
|
|
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(" " +
|
|
21922
|
+
console.log(" " + chalk20.dim("\u2500".repeat(18 + col * TIERS.length)));
|
|
21607
21923
|
if (current) {
|
|
21608
|
-
console.log(
|
|
21924
|
+
console.log(chalk20.dim(`
|
|
21609
21925
|
Current plan: ${tierName(current)}`));
|
|
21610
21926
|
if (current === "enterprise") {
|
|
21611
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
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
|
|
21657
|
-
program.name("pretense").description(
|
|
21658
|
-
${
|
|
21659
|
-
${
|
|
21660
|
-
${
|
|
21661
|
-
${
|
|
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
|
-
${
|
|
21664
|
-
${
|
|
21665
|
-
${
|
|
21666
|
-
${
|
|
21667
|
-
${
|
|
21668
|
-
${
|
|
21669
|
-
${
|
|
21670
|
-
${
|
|
21671
|
-
${
|
|
21672
|
-
${
|
|
21673
|
-
${
|
|
21674
|
-
${
|
|
21675
|
-
${
|
|
21676
|
-
${
|
|
21677
|
-
${
|
|
21678
|
-
${
|
|
21679
|
-
${
|
|
21680
|
-
${
|
|
21681
|
-
${
|
|
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
|
-
${
|
|
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(
|
|
22064
|
+
console.error(chalk21.red(`
|
|
21745
22065
|
x Unknown command: "${unknown}"`));
|
|
21746
22066
|
if (suggestion) {
|
|
21747
|
-
console.error(
|
|
22067
|
+
console.error(chalk21.yellow(` Did you mean: ${chalk21.bold(`pretense ${suggestion}`)}`));
|
|
21748
22068
|
}
|
|
21749
|
-
console.error(
|
|
21750
|
-
Run ${
|
|
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
|
});
|
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,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",
|