@pretense/cli 0.6.19 → 0.6.21

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.
@@ -17,7 +17,7 @@ function versionFromPackageJson() {
17
17
  return void 0;
18
18
  }
19
19
  }
20
- var PRETENSE_VERSION = (true ? "0.6.19" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
20
+ var PRETENSE_VERSION = (true ? "0.6.21" : 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-XY4NRTOT.js";
4
+ } from "./chunk-2BMR5XBU.js";
5
5
  import {
6
6
  __commonJS,
7
7
  __toESM,
@@ -14472,14 +14472,16 @@ async function scanWithPresidio(text, presidioUrl, opts = {}) {
14472
14472
 
14473
14473
  // ../packages/mutator/dist/index.js
14474
14474
  init_esm_shims();
14475
+ import { existsSync as existsSync4, readFileSync as readFileSync32 } from "fs";
14476
+ import { join as join4 } from "path";
14475
14477
  import { createHmac, hkdfSync, randomBytes as randomBytes2 } from "crypto";
14476
14478
  import { createHash, randomBytes } from "crypto";
14477
14479
  import { readFileSync as readFileSync4, existsSync as existsSync22 } from "fs";
14478
14480
  import { homedir as homedir2 } from "os";
14479
14481
  import { join as join22, dirname, parse, resolve } from "path";
14480
- import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
14482
+ import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
14481
14483
  import { homedir } from "os";
14482
- import { join as join4 } from "path";
14484
+ import { join as join5 } from "path";
14483
14485
  var DIR_MODE = 448;
14484
14486
  var FILE_MODE = 384;
14485
14487
  var POSIX_MODES_ENFORCED = process.platform !== "win32";
@@ -14501,10 +14503,10 @@ var PretenseHomeError = class extends Error {
14501
14503
  }
14502
14504
  };
14503
14505
  function pretenseHome() {
14504
- return join4(homedir(), ".pretense");
14506
+ return join5(homedir(), ".pretense");
14505
14507
  }
14506
14508
  function ensureDirOwnerOnly(dir) {
14507
- const preExisting = existsSync4(dir);
14509
+ const preExisting = existsSync5(dir);
14508
14510
  if (!preExisting) {
14509
14511
  try {
14510
14512
  mkdirSync2(dir, { recursive: true, mode: DIR_MODE });
@@ -15634,6 +15636,79 @@ function mutate(code, language, seed = "pretense", opts = {}) {
15634
15636
  }
15635
15637
  };
15636
15638
  }
15639
+ function mutateNamedIdentifiers(code, language, identifiers, seed = "pretense", opts = {}) {
15640
+ const t0 = performance.now();
15641
+ const lang = language ?? detectLanguage(code);
15642
+ const map = /* @__PURE__ */ new Map();
15643
+ if (!code?.trim() || identifiers.length === 0) {
15644
+ return { mutatedCode: code ?? "", map, stats: { tokensScanned: identifiers.length, tokensMutated: 0, durationMs: 0, language: lang } };
15645
+ }
15646
+ const signingKey = resolveSigningKey(opts);
15647
+ const usedSynthetics = /* @__PURE__ */ new Set();
15648
+ for (const { name, category } of identifiers) {
15649
+ if (!name || name.length <= 1 || SKIP_IDENTIFIERS2.has(name)) continue;
15650
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) continue;
15651
+ if (map.has(name)) continue;
15652
+ let synthetic = generateSyntheticName(name, seed, category, signingKey);
15653
+ let attempt = 0;
15654
+ while ((usedSynthetics.has(synthetic) || synthetic === name || code.includes(synthetic)) && attempt < 100) {
15655
+ attempt++;
15656
+ synthetic = generateSyntheticName(name, `${seed}:${attempt}`, category, signingKey);
15657
+ }
15658
+ if (synthetic !== name && !usedSynthetics.has(synthetic) && !code.includes(synthetic)) {
15659
+ map.set(name, synthetic);
15660
+ usedSynthetics.add(synthetic);
15661
+ }
15662
+ }
15663
+ const entries = [...map.entries()].sort((a, b) => b[0].length - a[0].length);
15664
+ let mutatedCode = code;
15665
+ const applied = /* @__PURE__ */ new Map();
15666
+ for (const [original, synthetic] of entries) {
15667
+ const next = replaceAll(mutatedCode, [[original, synthetic]], lang, true);
15668
+ if (next !== mutatedCode) {
15669
+ applied.set(original, synthetic);
15670
+ mutatedCode = next;
15671
+ }
15672
+ }
15673
+ return {
15674
+ mutatedCode,
15675
+ map: applied,
15676
+ stats: {
15677
+ tokensScanned: identifiers.length,
15678
+ tokensMutated: applied.size,
15679
+ durationMs: Math.round((performance.now() - t0) * 100) / 100,
15680
+ language: lang
15681
+ }
15682
+ };
15683
+ }
15684
+ var FINGERPRINT_CATEGORIES = [
15685
+ ["functions", "function"],
15686
+ ["classes", "class"],
15687
+ ["constants", "constant"],
15688
+ ["envVars", "variable"]
15689
+ ];
15690
+ function loadFingerprintIdentifiers(projectRoot) {
15691
+ const path2 = join4(projectRoot, ".pretense", "fingerprint.json");
15692
+ if (!existsSync4(path2)) return [];
15693
+ let parsed;
15694
+ try {
15695
+ parsed = JSON.parse(readFileSync32(path2, "utf-8"));
15696
+ } catch {
15697
+ return [];
15698
+ }
15699
+ const out = [];
15700
+ const seen = /* @__PURE__ */ new Set();
15701
+ for (const [key, category] of FINGERPRINT_CATEGORIES) {
15702
+ const arr = parsed[key];
15703
+ if (!Array.isArray(arr)) continue;
15704
+ for (const name of arr) {
15705
+ if (typeof name !== "string" || seen.has(name)) continue;
15706
+ seen.add(name);
15707
+ out.push({ name, category });
15708
+ }
15709
+ }
15710
+ return out;
15711
+ }
15637
15712
  function reverse(mutatedCode, map) {
15638
15713
  if (!mutatedCode || map.size === 0) return mutatedCode;
15639
15714
  assertNoReplacementCollision(map.entries());
@@ -15940,7 +16015,7 @@ var RiskScorer = class {
15940
16015
  // ../packages/store/dist/index.js
15941
16016
  init_esm_shims();
15942
16017
  import { createRequire as createRequire2 } from "module";
15943
- import { mkdirSync as mkdirSync22, existsSync as existsSync5, chmodSync as chmodSync2, statSync as statSync2 } from "fs";
16018
+ import { mkdirSync as mkdirSync22, existsSync as existsSync6, chmodSync as chmodSync2, statSync as statSync2 } from "fs";
15944
16019
  import { dirname as dirname2 } from "path";
15945
16020
  import { homedir as homedir22 } from "os";
15946
16021
  var nodeRequire = createRequire2(import.meta.url);
@@ -16001,7 +16076,7 @@ function openDatabase(path2) {
16001
16076
  return sqliteBackend === "bun:sqlite" ? openBun(path2) : openBetterSqlite3(path2);
16002
16077
  }
16003
16078
  function ensureDbDirOwnerOnly(dir) {
16004
- const preExisting = existsSync5(dir);
16079
+ const preExisting = existsSync6(dir);
16005
16080
  if (!preExisting) mkdirSync22(dir, { recursive: true, mode: 448 });
16006
16081
  if (process.platform === "win32") return;
16007
16082
  try {
@@ -16051,7 +16126,7 @@ var AuditStore = class {
16051
16126
  if (!isMemory) {
16052
16127
  for (const p of [resolvedPath, `${resolvedPath}-wal`, `${resolvedPath}-shm`]) {
16053
16128
  try {
16054
- if (existsSync5(p)) chmodSync2(p, 384);
16129
+ if (existsSync6(p)) chmodSync2(p, 384);
16055
16130
  } catch {
16056
16131
  }
16057
16132
  }
@@ -16432,8 +16507,8 @@ var PERIOD_DAYS = 7;
16432
16507
  var PERIOD_MS = PERIOD_DAYS * 24 * 60 * 60 * 1e3;
16433
16508
 
16434
16509
  // ../apps/proxy/dist/server.js
16435
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
16436
- import { join as join5 } from "path";
16510
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
16511
+ import { join as join6 } from "path";
16437
16512
  import { parse as parseYaml } from "yaml";
16438
16513
  import { readFileSync as readFileSync23 } from "fs";
16439
16514
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -16531,12 +16606,12 @@ var DEFAULTS = {
16531
16606
  };
16532
16607
  function loadConfig(configPath) {
16533
16608
  const paths = configPath ? [configPath] : [
16534
- join5(process.cwd(), "pretense.yaml"),
16535
- join5(process.cwd(), "pretense.yml"),
16536
- join5(process.env["HOME"] ?? "/tmp", ".pretense", "config.yaml")
16609
+ join6(process.cwd(), "pretense.yaml"),
16610
+ join6(process.cwd(), "pretense.yml"),
16611
+ join6(process.env["HOME"] ?? "/tmp", ".pretense", "config.yaml")
16537
16612
  ];
16538
16613
  for (const p of paths) {
16539
- if (existsSync6(p)) {
16614
+ if (existsSync7(p)) {
16540
16615
  try {
16541
16616
  const raw2 = parseYaml(readFileSync5(p, "utf-8"));
16542
16617
  return deepMerge(DEFAULTS, raw2);
@@ -16582,11 +16657,11 @@ function versionFromCliPackageJson() {
16582
16657
  return void 0;
16583
16658
  }
16584
16659
  function productVersion() {
16585
- return override ?? (true ? "0.6.19" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16660
+ return override ?? (true ? "0.6.21" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16586
16661
  }
16587
16662
  var UNKNOWN_COMMIT = "unknown";
16588
16663
  function bakedCommitSha() {
16589
- return "79bc3b0f2c6a2ff139ae392d2de37679a06308a6".length > 0 ? "79bc3b0f2c6a2ff139ae392d2de37679a06308a6" : UNKNOWN_COMMIT;
16664
+ return "7e80103647b573f455a73aecebeae1a0e2fa3ac9".length > 0 ? "7e80103647b573f455a73aecebeae1a0e2fa3ac9" : UNKNOWN_COMMIT;
16590
16665
  }
16591
16666
  var FEATURE_TIERS = {
16592
16667
  compliance_export: "pro",
@@ -17201,6 +17276,65 @@ function getTelemetryEmitter(dashboardUrl, pushToken) {
17201
17276
  _emitter = new TelemetryEmitter(url, token);
17202
17277
  return _emitter;
17203
17278
  }
17279
+ var DEFAULT_UPLOAD_URL = "https://api.pretense.ai";
17280
+ var UPLOAD_TIMEOUT_MS = 5e3;
17281
+ var MAX_IN_FLIGHT2 = 64;
17282
+ var _inFlight = 0;
17283
+ var _dropped = 0;
17284
+ function telemetryDisabled() {
17285
+ const v = process.env["PRETENSE_NO_TELEMETRY"]?.trim().toLowerCase();
17286
+ return v === "1" || v === "true" || v === "yes" || v === "on";
17287
+ }
17288
+ function resolveUploadBaseUrl() {
17289
+ const raw2 = (process.env["PRETENSE_DASHBOARD_URL"] ?? DEFAULT_UPLOAD_URL).trim();
17290
+ if (!raw2) return DEFAULT_UPLOAD_URL;
17291
+ const normalized = /^https?:\/\//i.test(raw2) ? raw2 : `https://${raw2}`;
17292
+ try {
17293
+ const u = new URL(normalized);
17294
+ const path2 = u.pathname === "/" ? "" : u.pathname.replace(/\/$/, "");
17295
+ return `${u.origin}${path2}`;
17296
+ } catch {
17297
+ return DEFAULT_UPLOAD_URL;
17298
+ }
17299
+ }
17300
+ function resolveUploadKey(requestKey) {
17301
+ const dashboardKey = process.env["PRETENSE_DASHBOARD_API_KEY"]?.trim();
17302
+ if (dashboardKey) return dashboardKey;
17303
+ const rk = requestKey?.trim();
17304
+ return rk && rk.length > 0 ? rk : null;
17305
+ }
17306
+ function clampCount(n) {
17307
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
17308
+ }
17309
+ function uploadProxyUsage(event) {
17310
+ if (telemetryDisabled()) return;
17311
+ const key = resolveUploadKey(event.apiKey);
17312
+ if (!key) return;
17313
+ if (_inFlight >= MAX_IN_FLIGHT2) {
17314
+ _dropped++;
17315
+ return;
17316
+ }
17317
+ const url = `${resolveUploadBaseUrl()}/api/cli/log`;
17318
+ const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(UPLOAD_TIMEOUT_MS) : void 0;
17319
+ _inFlight++;
17320
+ void Promise.resolve().then(
17321
+ () => fetch(url, {
17322
+ method: "POST",
17323
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
17324
+ // COUNTS + METADATA ONLY — never a secret value or request body.
17325
+ body: JSON.stringify({
17326
+ file_count: 0,
17327
+ identifiers_mutated: clampCount(event.identifiersMutated),
17328
+ secrets_blocked: clampCount(event.secretsBlocked),
17329
+ llm_provider: event.provider
17330
+ }),
17331
+ ...signal ? { signal } : {}
17332
+ })
17333
+ ).catch(() => {
17334
+ }).finally(() => {
17335
+ _inFlight--;
17336
+ });
17337
+ }
17204
17338
  var riskScorer = new RiskScorer();
17205
17339
  function detectUpstream(path2, overrideHeader) {
17206
17340
  if (overrideHeader) return overrideHeader;
@@ -17532,6 +17666,7 @@ function createProxy(opts = {}) {
17532
17666
  }
17533
17667
  const app = new Hono2();
17534
17668
  const derivation = { projectRoot: resolveProxyProjectRoot(opts.projectRoot) };
17669
+ const fingerprintIdentifiers = loadFingerprintIdentifiers(derivation.projectRoot);
17535
17670
  let auditWriteFailures = 0;
17536
17671
  let auditWriteFailureLogged = false;
17537
17672
  const safeLog = (entry) => {
@@ -17899,9 +18034,16 @@ ${_upgradeTier === "enterprise" ? "Enterprise offers unlimited mutations \u2014
17899
18034
  continue;
17900
18035
  }
17901
18036
  const result = mutate(block.code, block.language, "pretense", derivation);
18037
+ let blockCode = result.mutatedCode;
17902
18038
  for (const [k, v] of result.map.entries()) globalMap.set(k, v);
17903
18039
  tokensMutated += result.stats.tokensMutated;
17904
- mutatedBlocks.push(result.mutatedCode);
18040
+ if (fingerprintIdentifiers.length > 0) {
18041
+ const fp = mutateNamedIdentifiers(blockCode, block.language, fingerprintIdentifiers, "pretense", derivation);
18042
+ for (const [k, v] of fp.map.entries()) globalMap.set(k, v);
18043
+ tokensMutated += fp.stats.tokensMutated;
18044
+ blockCode = fp.mutatedCode;
18045
+ }
18046
+ mutatedBlocks.push(blockCode);
17905
18047
  }
17906
18048
  return replaceCodeBlocks(text, blocks, mutatedBlocks);
17907
18049
  });
@@ -18076,6 +18218,15 @@ ${_upgradeTier === "enterprise" ? "Enterprise offers unlimited mutations \u2014
18076
18218
  ...skippedUnscannable > 0 ? { findings: ["skipped_unscannable_field"] } : {},
18077
18219
  sourceIp: c.req.header("x-forwarded-for") ?? c.req.header("x-real-ip")
18078
18220
  }));
18221
+ if (anyTokenized) {
18222
+ const usageKey = c.req.header("x-pretense-api-key") ?? c.req.header("authorization")?.replace(/^Bearer\s+/i, "");
18223
+ uploadProxyUsage({
18224
+ apiKey: usageKey,
18225
+ identifiersMutated: mutationsApplied,
18226
+ secretsBlocked: secretsTokenized,
18227
+ provider
18228
+ });
18229
+ }
18079
18230
  const reverseMap = /* @__PURE__ */ new Map();
18080
18231
  for (const [orig, syn] of globalMap.entries()) {
18081
18232
  if (!secretMap.has(syn)) reverseMap.set(orig, syn);
@@ -18195,11 +18346,11 @@ async function findFreePort(start, opts = {}) {
18195
18346
 
18196
18347
  // src/proxy-pidfile.ts
18197
18348
  init_esm_shims();
18198
- import { existsSync as existsSync7, readFileSync as readFileSync6, unlinkSync } from "fs";
18199
- import { join as join6 } from "path";
18349
+ import { existsSync as existsSync8, readFileSync as readFileSync6, unlinkSync } from "fs";
18350
+ import { join as join7 } from "path";
18200
18351
  import { homedir as homedir3 } from "os";
18201
18352
  function pidFilePath(home = homedir3()) {
18202
- return join6(process.env.PRETENSE_PID_DIR ?? join6(home, ".pretense"), "proxy.pid");
18353
+ return join7(process.env.PRETENSE_PID_DIR ?? join7(home, ".pretense"), "proxy.pid");
18203
18354
  }
18204
18355
  function isProcessAlive(pid) {
18205
18356
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -18211,7 +18362,7 @@ function isProcessAlive(pid) {
18211
18362
  }
18212
18363
  }
18213
18364
  function readProxyRecord(path2 = pidFilePath()) {
18214
- if (!existsSync7(path2)) return null;
18365
+ if (!existsSync8(path2)) return null;
18215
18366
  try {
18216
18367
  const parsed = JSON.parse(readFileSync6(path2, "utf-8"));
18217
18368
  if (!Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
@@ -18238,7 +18389,7 @@ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
18238
18389
  async function stopProxy(path2 = pidFilePath(), graceMs = 2e3, pollMs = 50) {
18239
18390
  const rec = readProxyRecord(path2);
18240
18391
  if (!rec) {
18241
- if (existsSync7(path2)) {
18392
+ if (existsSync8(path2)) {
18242
18393
  removeProxyRecord(path2);
18243
18394
  return { kind: "stale", pid: 0, path: path2 };
18244
18395
  }
@@ -18411,17 +18562,17 @@ import chalk3 from "chalk";
18411
18562
 
18412
18563
  // src/plan-usage.ts
18413
18564
  init_esm_shims();
18414
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
18415
- import { join as join7 } from "path";
18565
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
18566
+ import { join as join8 } from "path";
18416
18567
  import { homedir as homedir4 } from "os";
18417
18568
  function credentialsPath() {
18418
- return join7(homedir4(), ".pretense", "credentials.json");
18569
+ return join8(homedir4(), ".pretense", "credentials.json");
18419
18570
  }
18420
18571
  function readApiKey() {
18421
18572
  const fromEnv = process.env["PRETENSE_API_KEY"]?.trim();
18422
18573
  if (fromEnv) return fromEnv;
18423
18574
  const path2 = credentialsPath();
18424
- if (!existsSync8(path2)) return null;
18575
+ if (!existsSync9(path2)) return null;
18425
18576
  try {
18426
18577
  const creds = JSON.parse(readFileSync7(path2, "utf-8"));
18427
18578
  const key = creds["apiKey"] ?? creds["api_key"];
@@ -18843,7 +18994,7 @@ import { Command as Command5, Option, InvalidArgumentError as InvalidArgumentErr
18843
18994
  import chalk5 from "chalk";
18844
18995
  import { readFileSync as readFileSync8, statSync as statSync3, readdirSync as readdirSync2, openSync, readSync, closeSync } from "fs";
18845
18996
  import { execSync as execSync3 } from "child_process";
18846
- import { resolve as resolve3, join as join8, extname as extname2, basename } from "path";
18997
+ import { resolve as resolve3, join as join9, extname as extname2, basename } from "path";
18847
18998
 
18848
18999
  // src/safe-output.ts
18849
19000
  init_esm_shims();
@@ -19033,9 +19184,9 @@ function walkDir(dir) {
19033
19184
  for (const entry of entries) {
19034
19185
  if (entry.isDirectory()) {
19035
19186
  if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
19036
- files.push(...walkDir(join8(dir, entry.name)));
19187
+ files.push(...walkDir(join9(dir, entry.name)));
19037
19188
  } else if (entry.isFile()) {
19038
- files.push(join8(dir, entry.name));
19189
+ files.push(join9(dir, entry.name));
19039
19190
  }
19040
19191
  }
19041
19192
  return files;
@@ -19759,13 +19910,13 @@ function scanCommand() {
19759
19910
  init_esm_shims();
19760
19911
  import { Command as Command6 } from "commander";
19761
19912
  import chalk6 from "chalk";
19762
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
19763
- import { join as join9 } from "path";
19913
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
19914
+ import { join as join10 } from "path";
19764
19915
  function statusCommand() {
19765
19916
  return new Command6("status").description("Show Pretense protection status").action(async () => {
19766
19917
  const dir = process.cwd();
19767
- const fingerprintPath = join9(dir, ".pretense", "fingerprint.json");
19768
- const configPath = join9(dir, "pretense.yaml");
19918
+ const fingerprintPath = join10(dir, ".pretense", "fingerprint.json");
19919
+ const configPath = join10(dir, "pretense.yaml");
19769
19920
  console.log(chalk6.cyan("\n Pretense Status\n"));
19770
19921
  const record = readProxyRecord(pidFilePath());
19771
19922
  if (record && !isProcessAlive(record.pid)) {
@@ -19787,12 +19938,12 @@ function statusCommand() {
19787
19938
  console.log(chalk6.yellow(` \u25CB Proxy not running ${chalk6.dim("(run: pretense start)")}`));
19788
19939
  }
19789
19940
  }
19790
- if (existsSync9(configPath)) {
19941
+ if (existsSync10(configPath)) {
19791
19942
  console.log(chalk6.green(` \u2713 Config: pretense.yaml`));
19792
19943
  } else {
19793
19944
  console.log(chalk6.yellow(` \u25CB No config ${chalk6.dim("(run: pretense config)")}`));
19794
19945
  }
19795
- if (existsSync9(fingerprintPath)) {
19946
+ if (existsSync10(fingerprintPath)) {
19796
19947
  try {
19797
19948
  const fp = JSON.parse(readFileSync9(fingerprintPath, "utf-8"));
19798
19949
  const age = Math.round((Date.now() - fp.scannedAt) / (60 * 1e3));
@@ -19814,8 +19965,8 @@ function statusCommand() {
19814
19965
  init_esm_shims();
19815
19966
  import { Command as Command7 } from "commander";
19816
19967
  import chalk7 from "chalk";
19817
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
19818
- import { join as join10 } from "path";
19968
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
19969
+ import { join as join11 } from "path";
19819
19970
  import YAML2 from "yaml";
19820
19971
  var DEFAULT_CONFIG = {
19821
19972
  version: 1,
@@ -19828,8 +19979,8 @@ function findConfigPath() {
19828
19979
  const cwd = process.cwd();
19829
19980
  const candidates = [".pretense.yaml", "pretense.yaml"];
19830
19981
  for (const name of candidates) {
19831
- const full = join10(cwd, name);
19832
- if (existsSync10(full)) return full;
19982
+ const full = join11(cwd, name);
19983
+ if (existsSync11(full)) return full;
19833
19984
  }
19834
19985
  return null;
19835
19986
  }
@@ -19844,7 +19995,7 @@ function loadConfig2() {
19844
19995
  }
19845
19996
  }
19846
19997
  function configFilePath() {
19847
- return findConfigPath() ?? join10(process.cwd(), "pretense.yaml");
19998
+ return findConfigPath() ?? join11(process.cwd(), "pretense.yaml");
19848
19999
  }
19849
20000
  function getNestedValue(obj, key) {
19850
20001
  const parts = key.split(".");
@@ -19915,7 +20066,7 @@ function configCommand() {
19915
20066
  });
19916
20067
  cmd.option("--force", "Overwrite existing config with defaults", false).action((opts) => {
19917
20068
  if (opts.force) {
19918
- const outPath = join10(process.cwd(), "pretense.yaml");
20069
+ const outPath = join11(process.cwd(), "pretense.yaml");
19919
20070
  writeFileSync4(outPath, YAML2.stringify(DEFAULT_CONFIG));
19920
20071
  console.log(chalk7.green(`
19921
20072
  \u2713 Created pretense.yaml
@@ -19932,14 +20083,14 @@ init_esm_shims();
19932
20083
  import { Command as Command8 } from "commander";
19933
20084
  import chalk8 from "chalk";
19934
20085
  import { homedir as homedir5 } from "os";
19935
- import { join as join11 } from "path";
20086
+ import { join as join12 } from "path";
19936
20087
  function logsCommand() {
19937
20088
  return new Command8("logs").description("Show recent audit log entries").option("-l, --limit <n>", "Number of entries to show", "50").option("--json", "Output as JSON", false).action((opts) => {
19938
20089
  const limit = parseInt(opts.limit, 10);
19939
20090
  let store;
19940
20091
  let entries;
19941
20092
  try {
19942
- store = new AuditStore(join11(homedir5(), ".pretense", "audit.db"));
20093
+ store = new AuditStore(join12(homedir5(), ".pretense", "audit.db"));
19943
20094
  entries = store.getRecentEntries(limit);
19944
20095
  store.close();
19945
20096
  } catch {
@@ -19979,7 +20130,7 @@ function logsCommand() {
19979
20130
  init_esm_shims();
19980
20131
  import { Command as Command9 } from "commander";
19981
20132
  import chalk9 from "chalk";
19982
- import { join as join12 } from "path";
20133
+ import { join as join13 } from "path";
19983
20134
  import { homedir as homedir6 } from "os";
19984
20135
  function renderBar(percent, width = 30) {
19985
20136
  const filled = Math.min(Math.round(percent / 100 * width), width);
@@ -20019,7 +20170,7 @@ function emptyUsagePayload(resolution) {
20019
20170
  };
20020
20171
  }
20021
20172
  function usageCommand() {
20022
- return new Command9("usage").description("Show plan usage vs limits for the current billing period").option("--db <path>", "Path to audit database", join12(homedir6(), ".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) => {
20173
+ return new Command9("usage").description("Show plan usage vs limits for the current billing period").option("--db <path>", "Path to audit database", join13(homedir6(), ".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) => {
20023
20174
  const resolution = await resolvePlanTier({ offline: opts.offline });
20024
20175
  const tier = resolution.tier;
20025
20176
  const collected = collectUsage(opts.db);
@@ -20170,12 +20321,12 @@ function usageCommand() {
20170
20321
  init_esm_shims();
20171
20322
  import { Command as Command10 } from "commander";
20172
20323
  import chalk10 from "chalk";
20173
- import { existsSync as existsSync11, readFileSync as readFileSync11, unlinkSync as unlinkSync2 } from "fs";
20174
- import { join as join13 } from "path";
20324
+ import { existsSync as existsSync12, readFileSync as readFileSync11, unlinkSync as unlinkSync2 } from "fs";
20325
+ import { join as join14 } from "path";
20175
20326
  import { homedir as homedir7 } from "os";
20176
20327
  import { execSync as execSync4 } from "child_process";
20177
- var CREDENTIALS_DIR = join13(homedir7(), ".pretense");
20178
- var CREDENTIALS_PATH = join13(CREDENTIALS_DIR, "credentials.json");
20328
+ var CREDENTIALS_DIR = join14(homedir7(), ".pretense");
20329
+ var CREDENTIALS_PATH = join14(CREDENTIALS_DIR, "credentials.json");
20179
20330
  async function verifyApiKey(apiKey) {
20180
20331
  const url = `${getConfiguredApiRoot()}/api/cli/auth/validate`;
20181
20332
  const timeoutMs = tierRequestTimeoutMs();
@@ -20222,7 +20373,7 @@ function storageNotice() {
20222
20373
  `);
20223
20374
  }
20224
20375
  function readCredentials() {
20225
- if (!existsSync11(CREDENTIALS_PATH)) return null;
20376
+ if (!existsSync12(CREDENTIALS_PATH)) return null;
20226
20377
  try {
20227
20378
  return JSON.parse(readFileSync11(CREDENTIALS_PATH, "utf-8"));
20228
20379
  } catch {
@@ -20356,7 +20507,7 @@ function authCommand() {
20356
20507
  openUrl(authUrl);
20357
20508
  });
20358
20509
  auth.command("logout").description("Remove stored credentials").action(() => {
20359
- if (!existsSync11(CREDENTIALS_PATH)) {
20510
+ if (!existsSync12(CREDENTIALS_PATH)) {
20360
20511
  console.log(chalk10.yellow("\n No credentials found. Already logged out.\n"));
20361
20512
  return;
20362
20513
  }
@@ -20411,7 +20562,7 @@ init_esm_shims();
20411
20562
  import { Command as Command11 } from "commander";
20412
20563
  import chalk11 from "chalk";
20413
20564
  import { readdirSync as readdirSync3, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
20414
- import { join as join14, extname as extname3, relative, isAbsolute } from "path";
20565
+ import { join as join15, extname as extname3, relative, isAbsolute } from "path";
20415
20566
  var SUPPORTED_EXTENSIONS2 = /* @__PURE__ */ new Set([
20416
20567
  ".ts",
20417
20568
  ".tsx",
@@ -20446,7 +20597,7 @@ function discoverFiles(dir) {
20446
20597
  }
20447
20598
  for (const entry of entries) {
20448
20599
  if (entry.startsWith(".")) continue;
20449
- const full = join14(current, entry);
20600
+ const full = join15(current, entry);
20450
20601
  let stat;
20451
20602
  try {
20452
20603
  stat = statSync4(full);
@@ -20537,7 +20688,7 @@ function formatAgent(results, basePath) {
20537
20688
  const agentResults = [];
20538
20689
  for (const r of results) {
20539
20690
  if (r.mutations.length === 0 && r.secrets.length === 0) continue;
20540
- const filePath = join14(basePath, r.file);
20691
+ const filePath = join15(basePath, r.file);
20541
20692
  let lines;
20542
20693
  try {
20543
20694
  lines = readFileSync12(filePath, "utf-8").split("\n");
@@ -20608,7 +20759,7 @@ function formatInteractive(results) {
20608
20759
  }
20609
20760
  function reviewCommand() {
20610
20761
  return new Command11("review").description("Preview mutations and secrets that would be applied").argument("[path]", "Directory to review (defaults to current directory)").option("--plain", "Plain text output", false).option("--json", "JSON array output with metadata", false).option("--agent", "Structured JSON optimized for AI agent consumption", false).option("--prompt-only", "Minimal output suitable for piping", false).option("--interactive", "Interactive formatted output (default)", false).action(async (path2, opts) => {
20611
- const targetDir = path2 ? isAbsolute(path2) ? path2 : join14(process.cwd(), path2) : process.cwd();
20762
+ const targetDir = path2 ? isAbsolute(path2) ? path2 : join15(process.cwd(), path2) : process.cwd();
20612
20763
  const files = discoverFiles(targetDir);
20613
20764
  if (files.length === 0) {
20614
20765
  if (opts?.json || opts?.agent) {
@@ -20644,13 +20795,13 @@ init_esm_shims();
20644
20795
  import { Command as Command12 } from "commander";
20645
20796
  import chalk12 from "chalk";
20646
20797
  import {
20647
- existsSync as existsSync12,
20798
+ existsSync as existsSync13,
20648
20799
  writeFileSync as writeFileSync5,
20649
20800
  chmodSync as chmodSync3,
20650
20801
  readFileSync as readFileSync13,
20651
20802
  mkdirSync as mkdirSync3
20652
20803
  } from "fs";
20653
- import { join as join15, resolve as resolve4 } from "path";
20804
+ import { join as join16, resolve as resolve4 } from "path";
20654
20805
  var HOOK_SCRIPTS = {
20655
20806
  "pre-commit": `#!/bin/sh
20656
20807
  # Pretense AI Firewall \u2014 pre-commit hook
@@ -20669,14 +20820,14 @@ exit $?
20669
20820
  };
20670
20821
  var VALID_MODES = ["pre-commit", "pre-push"];
20671
20822
  function findGitDir(cwd) {
20672
- const gitDir = join15(cwd, ".git");
20673
- if (existsSync12(gitDir)) return gitDir;
20823
+ const gitDir = join16(cwd, ".git");
20824
+ if (existsSync13(gitDir)) return gitDir;
20674
20825
  return null;
20675
20826
  }
20676
20827
  function installHook(hooksDir, mode, force) {
20677
- const hookPath = join15(hooksDir, mode);
20828
+ const hookPath = join16(hooksDir, mode);
20678
20829
  const script = HOOK_SCRIPTS[mode];
20679
- if (existsSync12(hookPath) && !force) {
20830
+ if (existsSync13(hookPath) && !force) {
20680
20831
  const existing = readFileSync13(hookPath, "utf-8");
20681
20832
  if (existing.includes("Pretense AI Firewall")) {
20682
20833
  return { installed: false, path: hookPath, skipped: true };
@@ -20718,7 +20869,7 @@ function installCommand() {
20718
20869
  );
20719
20870
  process.exit(1);
20720
20871
  }
20721
- const hooksDir = join15(gitDir, "hooks");
20872
+ const hooksDir = join16(gitDir, "hooks");
20722
20873
  mkdirSync3(hooksDir, { recursive: true });
20723
20874
  console.log(chalk12.cyan("\n Pretense hook installer\n"));
20724
20875
  let hasError = false;
@@ -20755,22 +20906,22 @@ function installCommand() {
20755
20906
  init_esm_shims();
20756
20907
  import { Command as Command13 } from "commander";
20757
20908
  import chalk13 from "chalk";
20758
- import { appendFileSync as appendFileSync3, readFileSync as readFileSync14, existsSync as existsSync13 } from "fs";
20759
- import { join as join16 } from "path";
20909
+ import { appendFileSync as appendFileSync3, readFileSync as readFileSync14, existsSync as existsSync14 } from "fs";
20910
+ import { join as join17 } from "path";
20760
20911
  import { homedir as homedir8 } from "os";
20761
20912
  var IGNORE_FILE = ".pretenseignore";
20762
- var LAST_SCAN_PATH = join16(homedir8(), ".pretense", "last-scan.json");
20913
+ var LAST_SCAN_PATH = join17(homedir8(), ".pretense", "last-scan.json");
20763
20914
  function ignorePath() {
20764
- return join16(process.cwd(), IGNORE_FILE);
20915
+ return join17(process.cwd(), IGNORE_FILE);
20765
20916
  }
20766
20917
  function readIgnoreFile() {
20767
20918
  const p = ignorePath();
20768
- if (!existsSync13(p)) return [];
20919
+ if (!existsSync14(p)) return [];
20769
20920
  return readFileSync14(p, "utf-8").split("\n").filter((l) => l.length > 0);
20770
20921
  }
20771
20922
  function appendPattern(pattern) {
20772
20923
  const p = ignorePath();
20773
- const content = existsSync13(p) ? readFileSync14(p, "utf-8") : "";
20924
+ const content = existsSync14(p) ? readFileSync14(p, "utf-8") : "";
20774
20925
  const lines = content.split("\n").filter((l) => l.length > 0);
20775
20926
  if (lines.includes(pattern)) {
20776
20927
  console.log(chalk13.yellow(`
@@ -20809,7 +20960,7 @@ function ignoreCommand() {
20809
20960
  return;
20810
20961
  }
20811
20962
  if (opts.lastFound) {
20812
- if (!existsSync13(LAST_SCAN_PATH)) {
20963
+ if (!existsSync14(LAST_SCAN_PATH)) {
20813
20964
  console.error(chalk13.red(`
20814
20965
  \u2717 No last scan results found at ${LAST_SCAN_PATH}
20815
20966
  `));
@@ -21067,8 +21218,8 @@ function completionCommand() {
21067
21218
  init_esm_shims();
21068
21219
  import { Command as Command15 } from "commander";
21069
21220
  import chalk14 from "chalk";
21070
- import { existsSync as existsSync14, writeFileSync as writeFileSync6 } from "fs";
21071
- import { join as join17, resolve as resolve5 } from "path";
21221
+ import { existsSync as existsSync15, writeFileSync as writeFileSync6 } from "fs";
21222
+ import { join as join18, resolve as resolve5 } from "path";
21072
21223
  var DEFAULT_CONFIG_YAML = `scan:
21073
21224
  secrets: block
21074
21225
  pii: redact
@@ -21108,9 +21259,9 @@ function quickstartCommand() {
21108
21259
  process.exit(1);
21109
21260
  }
21110
21261
  console.log(chalk14.dim(" [2/4] Writing config..."));
21111
- const configPath = join17(dir, "pretense.yaml");
21262
+ const configPath = join18(dir, "pretense.yaml");
21112
21263
  if (!opts.dryRun) {
21113
- if (!existsSync14(configPath)) {
21264
+ if (!existsSync15(configPath)) {
21114
21265
  writeFileSync6(configPath, DEFAULT_CONFIG_YAML);
21115
21266
  console.log(chalk14.green(" Created pretense.yaml"));
21116
21267
  } else {
@@ -21119,8 +21270,8 @@ function quickstartCommand() {
21119
21270
  }
21120
21271
  console.log(chalk14.dim(" [3/4] Updating .gitignore..."));
21121
21272
  if (!opts.dryRun) {
21122
- const gitignorePath = join17(dir, ".gitignore");
21123
- let content = existsSync14(gitignorePath) ? (await import("fs")).default.readFileSync(gitignorePath, "utf-8") : "";
21273
+ const gitignorePath = join18(dir, ".gitignore");
21274
+ let content = existsSync15(gitignorePath) ? (await import("fs")).default.readFileSync(gitignorePath, "utf-8") : "";
21124
21275
  if (!content.includes(".pretense/")) {
21125
21276
  content = content + (content.endsWith("\n") ? "" : "\n") + ".pretense/\n";
21126
21277
  writeFileSync6(gitignorePath, content);
@@ -21150,7 +21301,7 @@ function quickstartCommand() {
21150
21301
  init_esm_shims();
21151
21302
  import { Command as Command16, Option as Option2 } from "commander";
21152
21303
  import chalk16 from "chalk";
21153
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, chmodSync as chmodSync4, existsSync as existsSync15 } from "fs";
21304
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, chmodSync as chmodSync4, existsSync as existsSync16 } from "fs";
21154
21305
  import { createHash as createHash3 } from "crypto";
21155
21306
  import { resolve as resolve6, extname as extname4 } from "path";
21156
21307
 
@@ -21158,8 +21309,8 @@ import { resolve as resolve6, extname as extname4 } from "path";
21158
21309
  init_esm_shims();
21159
21310
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, hkdfSync as hkdfSync2 } from "crypto";
21160
21311
  import { homedir as homedir9 } from "os";
21161
- import { join as join18 } from "path";
21162
- var KEYS_DIR = join18(homedir9(), ".pretense", "keys");
21312
+ import { join as join19 } from "path";
21313
+ var KEYS_DIR = join19(homedir9(), ".pretense", "keys");
21163
21314
  var HKDF_SALT2 = Buffer.from("pretense-maps-v1", "utf-8");
21164
21315
  var HKDF_INFO2 = Buffer.from("secret-mapping-aes-256-gcm", "utf-8");
21165
21316
  var IV_LEN = 12;
@@ -21204,13 +21355,13 @@ import {
21204
21355
  fchmodSync
21205
21356
  } from "fs";
21206
21357
  import { randomBytes as randomBytes4 } from "crypto";
21207
- import { dirname as dirname4, basename as basename2, join as join19 } from "path";
21358
+ import { dirname as dirname4, basename as basename2, join as join20 } from "path";
21208
21359
  function writeFileAtomicSync(targetPath, data, opts = {}) {
21209
21360
  let resolved;
21210
21361
  try {
21211
21362
  resolved = realpathSync2(targetPath);
21212
21363
  } catch {
21213
- resolved = join19(realpathSync2(dirname4(targetPath)), basename2(targetPath));
21364
+ resolved = join20(realpathSync2(dirname4(targetPath)), basename2(targetPath));
21214
21365
  }
21215
21366
  let mode = opts.mode;
21216
21367
  if (mode === void 0) {
@@ -21221,7 +21372,7 @@ function writeFileAtomicSync(targetPath, data, opts = {}) {
21221
21372
  }
21222
21373
  }
21223
21374
  const dir = dirname4(resolved);
21224
- const tmp = join19(dir, `.${basename2(resolved)}.pretense-${randomBytes4(6).toString("hex")}.tmp`);
21375
+ const tmp = join20(dir, `.${basename2(resolved)}.pretense-${randomBytes4(6).toString("hex")}.tmp`);
21225
21376
  const buf = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf-8");
21226
21377
  let fd = openSync2(tmp, "wx", 384);
21227
21378
  try {
@@ -21262,13 +21413,13 @@ function writeFileAtomicSync(targetPath, data, opts = {}) {
21262
21413
  // src/default-map.ts
21263
21414
  init_esm_shims();
21264
21415
  import { createHash as createHash2 } from "crypto";
21265
- import { join as join20 } from "path";
21416
+ import { join as join21 } from "path";
21266
21417
  function mapsDir(override2) {
21267
- return override2 ?? join20(pretenseHome(), "maps");
21418
+ return override2 ?? join21(pretenseHome(), "maps");
21268
21419
  }
21269
21420
  function defaultMapPath(absFilePath, override2) {
21270
21421
  const digest = createHash2("sha256").update(absFilePath, "utf8").digest("hex");
21271
- return join20(mapsDir(override2), `${digest}.json`);
21422
+ return join21(mapsDir(override2), `${digest}.json`);
21272
21423
  }
21273
21424
  function ensureDefaultMapPath(absFilePath, override2) {
21274
21425
  ensureDirOwnerOnly(mapsDir(override2));
@@ -21278,11 +21429,11 @@ function ensureDefaultMapPath(absFilePath, override2) {
21278
21429
  // src/local-audit.ts
21279
21430
  init_esm_shims();
21280
21431
  import { homedir as homedir10 } from "os";
21281
- import { join as join21 } from "path";
21432
+ import { join as join24 } from "path";
21282
21433
  import { randomUUID } from "crypto";
21283
21434
  import chalk15 from "chalk";
21284
21435
  function localAuditDbPath() {
21285
- return join21(homedir10(), ".pretense", "audit.db");
21436
+ return join24(homedir10(), ".pretense", "audit.db");
21286
21437
  }
21287
21438
  function recordLocalAudit(rec, dbPath = localAuditDbPath()) {
21288
21439
  let store = null;
@@ -21317,10 +21468,71 @@ function recordLocalAudit(rec, dbPath = localAuditDbPath()) {
21317
21468
  }
21318
21469
  }
21319
21470
 
21471
+ // src/usage-upload.ts
21472
+ init_esm_shims();
21473
+ var UPLOAD_TIMEOUT_MS2 = 3e3;
21474
+ function telemetryDisabled2() {
21475
+ const v = process.env["PRETENSE_NO_TELEMETRY"]?.trim().toLowerCase();
21476
+ return v === "1" || v === "true" || v === "yes" || v === "on";
21477
+ }
21478
+ function resolveUploadBaseUrl2() {
21479
+ const raw2 = process.env["PRETENSE_DASHBOARD_URL"]?.trim();
21480
+ if (!raw2) return getConfiguredApiRoot();
21481
+ const normalized = /^https?:\/\//i.test(raw2) ? raw2 : `https://${raw2}`;
21482
+ try {
21483
+ const u = new URL(normalized);
21484
+ const path2 = u.pathname === "/" ? "" : u.pathname.replace(/\/$/, "");
21485
+ return `${u.origin}${path2}`;
21486
+ } catch {
21487
+ return getConfiguredApiRoot();
21488
+ }
21489
+ }
21490
+ function resolveUploadKey2() {
21491
+ const dashboardKey = process.env["PRETENSE_DASHBOARD_API_KEY"]?.trim();
21492
+ if (dashboardKey) return dashboardKey;
21493
+ return readApiKey();
21494
+ }
21495
+ function clampCount2(n) {
21496
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
21497
+ }
21498
+ async function uploadUsage(event) {
21499
+ try {
21500
+ if (telemetryDisabled2()) return "skipped";
21501
+ const key = resolveUploadKey2();
21502
+ if (!key) return "skipped";
21503
+ const url = `${resolveUploadBaseUrl2()}/api/cli/log`;
21504
+ const controller = new AbortController();
21505
+ const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS2);
21506
+ try {
21507
+ const resp = await fetch(url, {
21508
+ method: "POST",
21509
+ headers: {
21510
+ "content-type": "application/json",
21511
+ authorization: `Bearer ${key}`
21512
+ },
21513
+ // COUNTS + METADATA ONLY — never a secret value, an identifier name, or
21514
+ // file content. Field names match the `/api/cli/log` contract.
21515
+ body: JSON.stringify({
21516
+ file_count: clampCount2(event.fileCount),
21517
+ identifiers_mutated: clampCount2(event.identifiersMutated),
21518
+ secrets_blocked: clampCount2(event.secretsBlocked),
21519
+ llm_provider: event.llmProvider
21520
+ }),
21521
+ signal: controller.signal
21522
+ });
21523
+ return resp.ok ? "sent" : "failed";
21524
+ } finally {
21525
+ clearTimeout(timer);
21526
+ }
21527
+ } catch {
21528
+ return "failed";
21529
+ }
21530
+ }
21531
+
21320
21532
  // src/commands/mutate.ts
21321
21533
  var REVERSAL_MAP_VERSION = 3;
21322
21534
  function preflightReversalMap(mapPath, absFile, content, contentSha, force) {
21323
- if (!existsSync15(mapPath)) return { action: "write", generation: 1 };
21535
+ if (!existsSync16(mapPath)) return { action: "write", generation: 1 };
21324
21536
  let existing;
21325
21537
  try {
21326
21538
  const parsed = JSON.parse(readFileSync15(mapPath, "utf-8"));
@@ -21497,7 +21709,25 @@ async function runMutate(file, opts = {}) {
21497
21709
  opts.level,
21498
21710
  opts.presetName
21499
21711
  );
21500
- if (findings.length === 0) {
21712
+ const absFile = resolve6(file);
21713
+ const projectRoot = resolveProjectRoot(absFile);
21714
+ const secretResult = mutateSecrets(content, findings, { projectRoot, keysDir: opts.keysDir });
21715
+ const fingerprintIds = loadFingerprintIdentifiers(projectRoot);
21716
+ const idMut = fingerprintIds.length > 0 ? mutateNamedIdentifiers(
21717
+ secretResult.content,
21718
+ L2_LANGUAGE_BY_EXT2[extname4(file).toLowerCase()],
21719
+ fingerprintIds,
21720
+ "pretense",
21721
+ { projectRoot, keysDir: opts.keysDir }
21722
+ ) : { mutatedCode: secretResult.content, map: /* @__PURE__ */ new Map() };
21723
+ const finalContent = idMut.mutatedCode;
21724
+ const identifierMutations = [...idMut.map.entries()].map(([original, replacement]) => ({
21725
+ original,
21726
+ replacement,
21727
+ type: "proprietary"
21728
+ }));
21729
+ const totalMutations = secretResult.mutations.length + identifierMutations.length;
21730
+ if (totalMutations === 0) {
21501
21731
  if (unmutatable.size === 0) {
21502
21732
  console.error(chalk16.dim(` No secrets found in ${file} \u2014 nothing to mutate.`));
21503
21733
  } else {
@@ -21507,9 +21737,10 @@ async function runMutate(file, opts = {}) {
21507
21737
  return 0;
21508
21738
  }
21509
21739
  reportUnmutatable(unmutatable);
21510
- const absFile = resolve6(file);
21511
- const projectRoot = resolveProjectRoot(absFile);
21512
- const result = mutateSecrets(content, findings, { projectRoot, keysDir: opts.keysDir });
21740
+ const result = {
21741
+ content: finalContent,
21742
+ mutations: [...secretResult.mutations, ...identifierMutations]
21743
+ };
21513
21744
  let effectiveMap;
21514
21745
  if (preview) {
21515
21746
  effectiveMap = void 0;
@@ -21581,13 +21812,21 @@ async function runMutate(file, opts = {}) {
21581
21812
  );
21582
21813
  return 1;
21583
21814
  }
21815
+ const secretCount = secretResult.mutations.length;
21816
+ const idCount = identifierMutations.length;
21817
+ const summary = () => {
21818
+ const parts = [];
21819
+ if (secretCount > 0) parts.push(`${secretCount} secret${secretCount === 1 ? "" : "s"}`);
21820
+ if (idCount > 0) parts.push(`${idCount} registered identifier${idCount === 1 ? "" : "s"}`);
21821
+ return parts.join(" + ");
21822
+ };
21584
21823
  try {
21585
21824
  if (inPlace) {
21586
21825
  writeFileAtomicSync(file, result.content);
21587
- console.error(chalk16.green(` \u2713 Mutated ${result.mutations.length} secrets in ${file} (in-place)`));
21826
+ console.error(chalk16.green(` \u2713 Mutated ${summary()} in ${file} (in-place)`));
21588
21827
  } else if (opts.output) {
21589
21828
  writeFileAtomicSync(opts.output, result.content);
21590
- console.error(chalk16.green(` \u2713 Mutated ${result.mutations.length} secrets \u2192 ${opts.output}`));
21829
+ console.error(chalk16.green(` \u2713 Mutated ${summary()} \u2192 ${opts.output}`));
21591
21830
  } else {
21592
21831
  process.stdout.write(result.content);
21593
21832
  }
@@ -21604,7 +21843,9 @@ async function runMutate(file, opts = {}) {
21604
21843
  let secretsBlocked = 0;
21605
21844
  let piiRedacted = 0;
21606
21845
  for (const m of result.mutations) {
21607
- if (categoryByType.get(m.type) === "pii") piiRedacted += 1;
21846
+ const cat = categoryByType.get(m.type);
21847
+ if (cat === "pii") piiRedacted += 1;
21848
+ else if (m.type === "proprietary") continue;
21608
21849
  else secretsBlocked += 1;
21609
21850
  }
21610
21851
  recordLocalAudit({
@@ -21618,6 +21859,12 @@ async function runMutate(file, opts = {}) {
21618
21859
  detectorKinds: [...new Set(result.mutations.map((m) => m.type))],
21619
21860
  file: absFile
21620
21861
  });
21862
+ await uploadUsage({
21863
+ fileCount: 1,
21864
+ identifiersMutated: result.mutations.length,
21865
+ secretsBlocked,
21866
+ llmProvider: "cli-mutate"
21867
+ });
21621
21868
  }
21622
21869
  if (opts.showMap) {
21623
21870
  console.error(JSON.stringify(result.mutations, null, 2));
@@ -21660,7 +21907,7 @@ function mutateCommand() {
21660
21907
  init_esm_shims();
21661
21908
  import { Command as Command17 } from "commander";
21662
21909
  import chalk17 from "chalk";
21663
- import { readFileSync as readFileSync16, existsSync as existsSync16, statSync as statSync6 } from "fs";
21910
+ import { readFileSync as readFileSync16, existsSync as existsSync17, statSync as statSync6 } from "fs";
21664
21911
  import { resolve as resolve7 } from "path";
21665
21912
  import { createHash as createHash4 } from "crypto";
21666
21913
  function sha256Hex3(s) {
@@ -21721,14 +21968,14 @@ function loadReversalMap(mapPath) {
21721
21968
  function runReverse(file, opts = {}) {
21722
21969
  const reverseStartMs = Date.now();
21723
21970
  const absPath = resolve7(file);
21724
- if (!existsSync16(absPath) || !statSync6(absPath).isFile()) {
21971
+ if (!existsSync17(absPath) || !statSync6(absPath).isFile()) {
21725
21972
  console.error(chalk17.red(`
21726
21973
  x File not found: ${file}
21727
21974
  `));
21728
21975
  return 1;
21729
21976
  }
21730
21977
  const mapPath = opts.map ?? defaultMapPath(absPath, opts.mapsDir);
21731
- if (!opts.map && !existsSync16(mapPath)) {
21978
+ if (!opts.map && !existsSync17(mapPath)) {
21732
21979
  console.error(
21733
21980
  chalk17.red(`
21734
21981
  x No reversal map found for ${absPath}`) + chalk17.dim(`
@@ -22185,7 +22432,7 @@ init_esm_shims();
22185
22432
  import { Command as Command20 } from "commander";
22186
22433
  import chalk20 from "chalk";
22187
22434
  import { homedir as homedir11 } from "os";
22188
- import { join as join24 } from "path";
22435
+ import { join as join25 } from "path";
22189
22436
  var VALID_FORMATS2 = ["table", "csv", "json", "jsonl"];
22190
22437
  function toSafeAuditRow(e) {
22191
22438
  return {
@@ -22309,7 +22556,7 @@ function auditCommand() {
22309
22556
  `));
22310
22557
  process.exit(1);
22311
22558
  }
22312
- const dbPath = opts.db ?? join24(homedir11(), ".pretense", "audit.db");
22559
+ const dbPath = opts.db ?? join25(homedir11(), ".pretense", "audit.db");
22313
22560
  let rows;
22314
22561
  try {
22315
22562
  const store = new AuditStore(dbPath);
@@ -22369,7 +22616,7 @@ function auditCommand() {
22369
22616
  init_esm_shims();
22370
22617
  import { Command as Command21 } from "commander";
22371
22618
  import chalk21 from "chalk";
22372
- import { join as join25 } from "path";
22619
+ import { join as join26 } from "path";
22373
22620
  import { homedir as homedir12 } from "os";
22374
22621
  function remaining(used, limit) {
22375
22622
  if (limit === null) return null;
@@ -22388,7 +22635,7 @@ function bar(used, limit, width = 24) {
22388
22635
  return chalk21.green(s);
22389
22636
  }
22390
22637
  function creditsCommand() {
22391
- return new Command21("credits").alias("tokens").description("Show remaining mutation budget for the current period").option("--db <path>", "Path to audit database", join25(homedir12(), ".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) => {
22638
+ return new Command21("credits").alias("tokens").description("Show remaining mutation budget for the current period").option("--db <path>", "Path to audit database", join26(homedir12(), ".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) => {
22392
22639
  const resolution = await resolvePlanTier({ offline: opts.offline });
22393
22640
  const tier = resolution.tier;
22394
22641
  const collected = collectUsage(opts.db);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-XY4NRTOT.js";
4
+ } from "./chunk-2BMR5XBU.js";
5
5
  import {
6
6
  init_esm_shims
7
7
  } from "./chunk-TWMRHZGM.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pretense/cli",
3
- "version": "0.6.19",
3
+ "version": "0.6.21",
4
4
  "description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
5
5
  "type": "module",
6
6
  "bin": {