@pretense/cli 0.6.10 → 0.6.12

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.10" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
20
+ var PRETENSE_VERSION = (true ? "0.6.12" : 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-4IG2OEJZ.js";
4
+ } from "./chunk-K3L4E6HN.js";
5
5
  import {
6
6
  __commonJS,
7
7
  __toESM,
@@ -7565,8 +7565,8 @@ var DETECTOR_FRAMEWORKS = {
7565
7565
  "swift-bic": ["FADP", "GDPR", "UK_GDPR"],
7566
7566
  "twilio-account-sid": ["APRA_CPS234", "DORA", "FISMA", "FedRAMP", "HITRUST", "ISO_27001", "ISO_27701", "NIS2", "NIST_800_171", "NIST_800_53", "NYDFS_500", "SOC2", "SOX"],
7567
7567
  "twilio-auth-token": ["APRA_CPS234", "DORA", "FISMA", "FedRAMP", "HITRUST", "ISO_27001", "ISO_27701", "NIS2", "NIST_800_171", "NIST_800_53", "NYDFS_500", "SOC2", "SOX"],
7568
- "uk-nhs-number-labeled": ["GDPR", "UK_GDPR"],
7569
- "uk-nino-labeled": ["APPI", "AU_PRIVACY", "CCPA_CPRA", "CJIS", "DPDP", "FADP", "FISMA", "GDPR", "IRS_1075", "ISO_27001", "ISO_27701", "LGPD", "PDPA_SG", "PDPA_TH", "PIPA_KR", "PIPEDA", "PIPL", "POPIA", "UK_GDPR"],
7568
+ "uk-nhs-number-labeled": ["UK_GDPR"],
7569
+ "uk-nino-labeled": ["UK_GDPR"],
7570
7570
  "us-ein": ["APRA_CPS234", "DORA", "GLBA", "IRS_1075", "NYDFS_500", "SOX"],
7571
7571
  "vat-number": ["FADP", "GDPR", "UK_GDPR"],
7572
7572
  "vault-token": ["APRA_CPS234", "DORA", "FISMA", "FedRAMP", "HITRUST", "ISO_27001", "ISO_27701", "NIS2", "NIST_800_171", "NIST_800_53", "NYDFS_500", "SOC2", "SOX"],
@@ -16425,11 +16425,11 @@ function versionFromCliPackageJson() {
16425
16425
  return void 0;
16426
16426
  }
16427
16427
  function productVersion() {
16428
- return override ?? (true ? "0.6.10" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16428
+ return override ?? (true ? "0.6.12" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16429
16429
  }
16430
16430
  var UNKNOWN_COMMIT = "unknown";
16431
16431
  function bakedCommitSha() {
16432
- return "22e24b5ffd48acef2a8be52b9fa50ad38960cd2f".length > 0 ? "22e24b5ffd48acef2a8be52b9fa50ad38960cd2f" : UNKNOWN_COMMIT;
16432
+ return "cd2bfc0670d3c8aadaa5a163493ba152391489a1".length > 0 ? "cd2bfc0670d3c8aadaa5a163493ba152391489a1" : UNKNOWN_COMMIT;
16433
16433
  }
16434
16434
  var FEATURE_TIERS = {
16435
16435
  compliance_export: "pro",
@@ -16517,10 +16517,15 @@ var PlanEnforcer = class {
16517
16517
  }
16518
16518
  const QUOTA_PERIOD_DAYS3 = 7;
16519
16519
  const resetDate = new Date(Date.now() + QUOTA_PERIOD_DAYS3 * 864e5).toISOString().split("T")[0];
16520
- if (worstBlocked || overallPercent >= 100) {
16520
+ const atOrOverLimit = (key) => {
16521
+ const check = limits[key];
16522
+ return check.limit !== void 0 && check.limit !== null && (check.current ?? 0) >= check.limit;
16523
+ };
16524
+ const anyAtOrOverLimit = metricKeys.some(atOrOverLimit);
16525
+ if (worstBlocked || anyAtOrOverLimit) {
16521
16526
  for (const key of metricKeys) {
16522
16527
  const check = limits[key];
16523
- if ((check.usagePercent ?? 0) >= 100 && check.limit !== void 0 && check.limit !== null) {
16528
+ if (atOrOverLimit(key)) {
16524
16529
  worstMetric = key;
16525
16530
  worstCurrent = check.current ?? 0;
16526
16531
  worstLimit = check.limit;
@@ -16839,7 +16844,10 @@ async function streamWithRollingBuffer(upstreamResp, map, provider, requestId) {
16839
16844
  (async () => {
16840
16845
  const reader = upstreamResp.body?.getReader();
16841
16846
  if (!reader) {
16842
- await writer.close();
16847
+ try {
16848
+ await writer.close();
16849
+ } catch {
16850
+ }
16843
16851
  return;
16844
16852
  }
16845
16853
  let lineBuffer = "";
@@ -16897,9 +16905,17 @@ async function streamWithRollingBuffer(upstreamResp, map, provider, requestId) {
16897
16905
  if (tail) await writer.write(encoder.encode(tail));
16898
16906
  }
16899
16907
  } finally {
16900
- await writer.close();
16908
+ try {
16909
+ await reader.cancel();
16910
+ } catch {
16911
+ }
16912
+ try {
16913
+ await writer.close();
16914
+ } catch {
16915
+ }
16901
16916
  }
16902
- })();
16917
+ })().catch(() => {
16918
+ });
16903
16919
  return new Response(readable, {
16904
16920
  status: upstreamResp.status,
16905
16921
  headers: {
@@ -17025,7 +17041,6 @@ function getTelemetryEmitter(dashboardUrl, pushToken) {
17025
17041
  _emitter = new TelemetryEmitter(url, token);
17026
17042
  return _emitter;
17027
17043
  }
17028
- var TIER_ORDER2 = { free: 0, pro: 1, enterprise: 2 };
17029
17044
  var riskScorer = new RiskScorer();
17030
17045
  function detectUpstream(path2, overrideHeader) {
17031
17046
  if (overrideHeader) return overrideHeader;
@@ -17034,12 +17049,66 @@ function detectUpstream(path2, overrideHeader) {
17034
17049
  if (path2.includes("/v1beta/") || path2.includes("/v1/models")) return "https://generativelanguage.googleapis.com";
17035
17050
  return "https://api.openai.com";
17036
17051
  }
17037
- function detectProvider(upstream) {
17038
- if (upstream.includes("anthropic")) return "anthropic";
17039
- if (upstream.includes("openai")) return "openai";
17040
- if (upstream.includes("google")) return "google";
17052
+ function detectProviderFromPath(path2) {
17053
+ if (path2.includes("/v1/messages")) return "anthropic";
17054
+ if (path2.includes("/v1beta/") || path2.includes("/v1/models")) return "google";
17055
+ if (path2.includes("/v1/chat/completions") || path2.includes("/v1/completions")) return "openai";
17041
17056
  return "openai-compatible";
17042
17057
  }
17058
+ function modelFromPath(path2) {
17059
+ const m = path2.match(/\/models\/([^:/?]+)/);
17060
+ return m?.[1];
17061
+ }
17062
+ var _tierCache = /* @__PURE__ */ new Map();
17063
+ var TIER_CACHE_OK_MS = 6e4;
17064
+ var TIER_CACHE_FAIL_MS = 1e4;
17065
+ var TIER_VALIDATE_TIMEOUT_MS = 3e3;
17066
+ function tierApiRoot() {
17067
+ const raw2 = (process.env["PRETENSE_API_URL"] ?? "https://api.pretense.ai").trim();
17068
+ const normalized = /^https?:\/\//i.test(raw2) ? raw2 : `https://${raw2}`;
17069
+ return normalized.replace(/\/$/, "");
17070
+ }
17071
+ async function validateDashboardKeyTier(apiKey) {
17072
+ const url = `${tierApiRoot()}/api/cli/auth/validate`;
17073
+ const controller = new AbortController();
17074
+ const timer = setTimeout(() => controller.abort(), TIER_VALIDATE_TIMEOUT_MS);
17075
+ try {
17076
+ const resp = await fetch(url, {
17077
+ method: "POST",
17078
+ headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
17079
+ body: "{}",
17080
+ signal: controller.signal
17081
+ });
17082
+ if (!resp.ok) return { tier: "free", ok: false };
17083
+ const body = await resp.json();
17084
+ if (body["valid"] === false) return { tier: "free", ok: true };
17085
+ switch (String(body["plan"]).toUpperCase()) {
17086
+ case "PRO":
17087
+ return { tier: "pro", ok: true };
17088
+ case "ENTERPRISE":
17089
+ return { tier: "enterprise", ok: true };
17090
+ case "FREE":
17091
+ return { tier: "free", ok: true };
17092
+ default:
17093
+ return { tier: "free", ok: false };
17094
+ }
17095
+ } catch {
17096
+ return { tier: "free", ok: false };
17097
+ } finally {
17098
+ clearTimeout(timer);
17099
+ }
17100
+ }
17101
+ async function resolveKeyTier(apiKey) {
17102
+ if (!apiKey) return "free";
17103
+ const fromPrefix = tierFromApiKey(apiKey);
17104
+ if (fromPrefix) return fromPrefix;
17105
+ if (!apiKey.startsWith("prtns_")) return "free";
17106
+ const cached = _tierCache.get(apiKey);
17107
+ if (cached && cached.expiresAt > Date.now()) return cached.tier;
17108
+ const { tier, ok } = await validateDashboardKeyTier(apiKey);
17109
+ _tierCache.set(apiKey, { tier, expiresAt: Date.now() + (ok ? TIER_CACHE_OK_MS : TIER_CACHE_FAIL_MS) });
17110
+ return tier;
17111
+ }
17043
17112
  function extractAllText(body) {
17044
17113
  const parts = [];
17045
17114
  if (typeof body["system"] === "string") parts.push(body["system"]);
@@ -17054,6 +17123,19 @@ function extractAllText(body) {
17054
17123
  }
17055
17124
  }
17056
17125
  if (typeof body["prompt"] === "string") parts.push(body["prompt"]);
17126
+ const gWalkParts = (v) => {
17127
+ if (!Array.isArray(v)) return;
17128
+ for (const part of v) {
17129
+ if (part && typeof part["text"] === "string") parts.push(part["text"]);
17130
+ }
17131
+ };
17132
+ if (Array.isArray(body["contents"])) {
17133
+ for (const c of body["contents"]) {
17134
+ if (c) gWalkParts(c["parts"]);
17135
+ }
17136
+ }
17137
+ const sysInstr = body["systemInstruction"] ?? body["system_instruction"];
17138
+ if (sysInstr && typeof sysInstr === "object") gWalkParts(sysInstr["parts"]);
17057
17139
  return parts.join("\n");
17058
17140
  }
17059
17141
  var TRANSFORM_MAX_DEPTH = 256;
@@ -17377,11 +17459,7 @@ function createProxy(opts = {}) {
17377
17459
  }
17378
17460
  {
17379
17461
  const _apiKey = c.req.header("x-pretense-api-key") ?? c.req.header("authorization")?.replace(/^Bearer\s+/i, "");
17380
- const _keyTier = tierFromApiKey(_apiKey);
17381
- const _effectiveTier = (() => {
17382
- if (!_keyTier) return "free";
17383
- return TIER_ORDER2[_keyTier] <= TIER_ORDER2[planCfg.tier] ? _keyTier : planCfg.tier;
17384
- })();
17462
+ const _effectiveTier = await resolveKeyTier(_apiKey);
17385
17463
  const _requestEnforcer = new PlanEnforcer(_effectiveTier, planCfg.tenantId);
17386
17464
  const QUOTA_PERIOD_DAYS3 = 7;
17387
17465
  const _periodMs = QUOTA_PERIOD_DAYS3 * 864e5;
@@ -17450,8 +17528,8 @@ ${_upgradeTier === "enterprise" ? "Enterprise offers unlimited mutations \u2014
17450
17528
  }
17451
17529
  }
17452
17530
  const upstream = detectUpstream(c.req.path, c.req.header("x-pretense-upstream"));
17453
- const provider = detectProvider(upstream);
17454
- const model = body["model"] ?? "unknown";
17531
+ const provider = detectProviderFromPath(c.req.path);
17532
+ const model = body["model"] ?? (provider === "google" ? modelFromPath(c.req.path) : void 0) ?? "unknown";
17455
17533
  const fullText = extractAllText(body);
17456
17534
  const scanT0 = performance.now();
17457
17535
  const deep = tokenizeBodyDeep(
@@ -17648,12 +17726,26 @@ ${_upgradeTier === "enterprise" ? "Enterprise offers unlimited mutations \u2014
17648
17726
  });
17649
17727
  delete headers["host"];
17650
17728
  delete headers["content-length"];
17729
+ delete headers["expect"];
17730
+ delete headers["connection"];
17731
+ delete headers["keep-alive"];
17732
+ delete headers["transfer-encoding"];
17733
+ delete headers["te"];
17734
+ delete headers["upgrade"];
17651
17735
  headers["host"] = new URL(upstream).host;
17652
17736
  headers["x-pretense-request-id"] = requestId;
17653
17737
  if (riskResult.recommendation === "warn") {
17654
17738
  headers["x-pretense-risk-score"] = String(riskResult.score);
17655
17739
  }
17656
- const forwardBody = JSON.stringify(mutatedBody);
17740
+ let forwardBody;
17741
+ try {
17742
+ forwardBody = JSON.stringify(mutatedBody);
17743
+ } catch {
17744
+ return c.json(
17745
+ { error: { type: "invalid_request", message: "Request body is too deeply nested to process" } },
17746
+ 400
17747
+ );
17748
+ }
17657
17749
  const originalSize = fullText.length;
17658
17750
  const processedSize = forwardBody.length;
17659
17751
  let upstreamResp;
@@ -17786,6 +17878,15 @@ function startProxy(opts = {}) {
17786
17878
  });
17787
17879
  }
17788
17880
  }
17881
+ function renderClientEnvLines(port, protocol) {
17882
+ const base = `${protocol}://localhost:${port}`;
17883
+ return [
17884
+ " Point your AI client at Pretense:",
17885
+ ` ANTHROPIC_BASE_URL=${base}`,
17886
+ ` OPENAI_BASE_URL=${base}/v1`,
17887
+ ` GOOGLE_GEMINI_BASE_URL=${base}`
17888
+ ].join("\n") + "\n";
17889
+ }
17789
17890
  function printBanner(port, protocol, tls) {
17790
17891
  const tlsLabel = tls ? " [TLS]" : "";
17791
17892
  const version = productVersion();
@@ -17800,10 +17901,9 @@ function printBanner(port, protocol, tls) {
17800
17901
  \u2502 Proxy: ${protocol}://localhost:${port}${tls ? " " : " "}\u2502
17801
17902
  \u2502 Health: ${protocol}://localhost:${port}/health \u2502
17802
17903
  \u2502 \u2502
17803
- \u2502 ANTHROPIC_BASE_URL=${protocol}://localhost:${port} \u2502
17804
- \u2502 \u2502
17805
17904
  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m
17806
- `
17905
+
17906
+ ` + renderClientEnvLines(port, protocol) + "\n"
17807
17907
  );
17808
17908
  }
17809
17909
  var isMain = process.argv[1]?.endsWith("server.js") || process.argv[1]?.endsWith("server.ts");
@@ -18806,9 +18906,21 @@ function scanCommand() {
18806
18906
  process.exit(1);
18807
18907
  }
18808
18908
  } else {
18909
+ if (process.stdin.isTTY) {
18910
+ console.error(
18911
+ chalk4.yellow("\n x Nothing to scan.\n") + chalk4.dim(" Scan this project: ") + chalk4.cyan("pretense scan .") + "\n" + chalk4.dim(" Scan a path: ") + chalk4.cyan("pretense scan <file-or-dir>") + "\n" + chalk4.dim(" Or pipe content: ") + chalk4.cyan("cat file | pretense scan") + "\n"
18912
+ );
18913
+ process.exit(USAGE_EXIT);
18914
+ }
18809
18915
  const chunks = [];
18810
18916
  for await (const chunk of process.stdin) chunks.push(chunk);
18811
18917
  content = Buffer.concat(chunks).toString("utf-8");
18918
+ if (content.trim() === "") {
18919
+ console.error(
18920
+ chalk4.yellow("\n x Nothing to scan \u2014 no file argument and empty input.\n") + chalk4.dim(" Scan this project: ") + chalk4.cyan("pretense scan .") + "\n" + chalk4.dim(" Scan a path: ") + chalk4.cyan("pretense scan <file-or-dir>") + "\n" + chalk4.dim(" Or pipe content: ") + chalk4.cyan("cat file | pretense scan") + "\n"
18921
+ );
18922
+ process.exit(USAGE_EXIT);
18923
+ }
18812
18924
  }
18813
18925
  const rawResult = await scanContent(content, opts, file);
18814
18926
  const filteredMatches = filterBySeverity(rawResult.matches, opts?.severity);
@@ -18881,18 +18993,25 @@ function statusCommand() {
18881
18993
  const fingerprintPath = join8(dir, ".pretense", "fingerprint.json");
18882
18994
  const configPath = join8(dir, "pretense.yaml");
18883
18995
  console.log(chalk5.cyan("\n Pretense Status\n"));
18884
- let proxyRunning = false;
18885
- try {
18886
- const resp = await fetch("http://localhost:9339/health", { signal: AbortSignal.timeout(1e3) });
18887
- if (resp.ok) {
18996
+ const record = readProxyRecord(pidFilePath());
18997
+ if (record && !isProcessAlive(record.pid)) {
18998
+ console.log(
18999
+ chalk5.yellow(
19000
+ ` \u25CB Proxy not running ${chalk5.dim(`(stale record for pid ${record.pid}; run: pretense start)`)}`
19001
+ )
19002
+ );
19003
+ } else {
19004
+ const port = record && record.port > 0 ? record.port : 9339;
19005
+ try {
19006
+ const resp = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(1e3) });
19007
+ if (!resp.ok) throw new Error(`health returned ${resp.status}`);
18888
19008
  const health = await resp.json();
18889
- proxyRunning = true;
18890
- console.log(chalk5.green(` \u2713 Proxy running on :9339 (uptime: ${health["uptime"]}s)`));
19009
+ console.log(chalk5.green(` \u2713 Proxy running on :${port} (uptime: ${health["uptime"]}s)`));
18891
19010
  const stats = health["stats"];
18892
19011
  console.log(chalk5.dim(` Requests: ${stats["requestsProcessed"] ?? 0} | Mutations: ${stats["mutationsApplied"] ?? 0} | Blocked: ${stats["secretsBlocked"] ?? 0}`));
19012
+ } catch {
19013
+ console.log(chalk5.yellow(` \u25CB Proxy not running ${chalk5.dim("(run: pretense start)")}`));
18893
19014
  }
18894
- } catch {
18895
- console.log(chalk5.yellow(` \u25CB Proxy not running ${chalk5.dim("(run: pretense start)")}`));
18896
19015
  }
18897
19016
  if (existsSync8(configPath)) {
18898
19017
  console.log(chalk5.green(` \u2713 Config: pretense.yaml`));
@@ -20407,7 +20526,8 @@ function quickstartCommand() {
20407
20526
  console.log(` ${chalk13.green("2.")} ${chalk13.cyan("pretense install")} Install pre-commit + pre-push git hooks`);
20408
20527
  console.log(` ${chalk13.green("3.")} Set your LLM client to use the proxy:`);
20409
20528
  console.log(` ${chalk13.dim("ANTHROPIC_BASE_URL=http://localhost:9339 claude")}`);
20410
- console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/openai openai ...")}
20529
+ console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/v1 openai ...")}`);
20530
+ console.log(` ${chalk13.dim("GOOGLE_GEMINI_BASE_URL=http://localhost:9339 gemini ...")}
20411
20531
  `);
20412
20532
  console.log(chalk13.bold.green(" Protected. Happy coding.\n"));
20413
20533
  });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-4IG2OEJZ.js";
4
+ } from "./chunk-K3L4E6HN.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.10",
3
+ "version": "0.6.12",
4
4
  "description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,15 +23,15 @@
23
23
  "tsx": "^4.0.0",
24
24
  "typescript": "^5.4.0",
25
25
  "vitest": "^1.0.0",
26
- "@pretense/billing": "0.1.0",
27
26
  "@pretense/compliance-engine": "0.1.0",
27
+ "@pretense/billing": "0.1.0",
28
28
  "@pretense/protocol": "0.1.0",
29
29
  "@pretense/learner": "0.2.0",
30
30
  "@pretense/mutator": "0.2.0",
31
31
  "@pretense/proxy": "0.1.0",
32
32
  "@pretense/scanner": "0.2.0",
33
- "@pretense/store": "0.2.0",
34
- "@pretense/scanner-rs": "0.2.0"
33
+ "@pretense/scanner-rs": "0.2.0",
34
+ "@pretense/store": "0.2.0"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public",