@pretense/cli 0.6.11 → 0.6.13

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.11" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
20
+ var PRETENSE_VERSION = (true ? "0.6.13" : 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-5GO2CY5W.js";
4
+ } from "./chunk-HWSWJ4EC.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"],
@@ -12171,6 +12171,19 @@ var SECRET_PATTERNS = [
12171
12171
  defaultAction: "block",
12172
12172
  pattern: /\bgithub_pat_[A-Za-z0-9_]{22,}\b/g
12173
12173
  },
12174
+ // FIX 1 (v0.6.13) — HuggingFace access tokens. `hf_` user and fine-grained
12175
+ // access tokens are `hf_` + 34+ base62 chars. Confirmed leaking on 0.6.12:
12176
+ // an `hf_` token in bare prose scanned clean (a real credential egressed to
12177
+ // the LLM) because no `hf_`/`huggingface` pattern existed. The `hf_` prefix
12178
+ // plus a 34+ base62 body is a near-zero-false-positive signal. Critical/block
12179
+ // because a live model-registry token grants push access to private models.
12180
+ {
12181
+ name: "huggingface-token",
12182
+ category: "secret",
12183
+ severity: "critical",
12184
+ defaultAction: "block",
12185
+ pattern: /\bhf_[A-Za-z0-9]{34,}\b/g
12186
+ },
12174
12187
  {
12175
12188
  name: "gitlab-token",
12176
12189
  category: "secret",
@@ -12745,8 +12758,9 @@ var ENV_PATTERNS = [
12745
12758
  // variable references (`$VAR`, `${VAR}`, `process.env.X`, `os.environ…`),
12746
12759
  // connection URLs (handled by `database-url`), and values that begin with a
12747
12760
  // recognised structured-secret prefix (Stripe `sk_`/`pk_`/`rk_`, OpenAI
12748
- // `sk-`, Slack `xox*`, GitHub `gh*_`/`glpat-`, AWS `AKIA`, Google `AIza`,
12749
- // Stripe webhook `whsec_`) are deliberately skipped so a generic
12761
+ // `sk-`, Slack `xox*`, GitHub `gh*_`/`glpat-`, HuggingFace `hf_`, AWS
12762
+ // `AKIA`, Google `AIza`, Stripe webhook `whsec_`) are deliberately skipped
12763
+ // so a generic
12750
12764
  // `KEY=variable` reference is not flagged and the dedicated, higher-fidelity
12751
12765
  // patterns above own those matches. Placed last so more-specific patterns
12752
12766
  // win overlap deduplication on ties.
@@ -12760,7 +12774,7 @@ var ENV_PATTERNS = [
12760
12774
  // specific env-var names rather than generic secret-label tails, so they do
12761
12775
  // not belong in the shared vocabulary.
12762
12776
  pattern: new RegExp(
12763
- `(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
12777
+ `(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|hf_|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
12764
12778
  "gi"
12765
12779
  ),
12766
12780
  // PRECISION: reject `PASSWORD=changeme`, `PASSWORD=<your-password>` and
@@ -13827,6 +13841,8 @@ function overlapsAny(m, sorted) {
13827
13841
  }
13828
13842
  async function scanLevel2(text, language, opts = {}) {
13829
13843
  const lang = language ?? opts.language ?? "typescript";
13844
+ const t0 = performance.now();
13845
+ const elapsed = () => Math.round((performance.now() - t0) * 100) / 100;
13830
13846
  const base = opts.presidioUrl ? await scanWithPresidio(text, opts.presidioUrl, opts) : scan(text, opts);
13831
13847
  const l1Matches = base.matches.map((m) => ({ ...m, level: 1 }));
13832
13848
  const l1Findings = base.findings.map((f) => ({ ...f, level: 1 }));
@@ -13834,6 +13850,8 @@ async function scanLevel2(text, language, opts = {}) {
13834
13850
  ...base,
13835
13851
  matches: l1Matches,
13836
13852
  findings: l1Findings,
13853
+ // Real wall time up to this early return, not the L1 sub-phase alone.
13854
+ durationMs: elapsed(),
13837
13855
  l2: status
13838
13856
  });
13839
13857
  const native = loadNative();
@@ -13911,7 +13929,9 @@ async function scanLevel2(text, language, opts = {}) {
13911
13929
  matches: merged,
13912
13930
  findings: mergedFindings,
13913
13931
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
13914
- durationMs: base.durationMs,
13932
+ // Real elapsed wall time for the full L1+L2 scan (was base.durationMs, the
13933
+ // L1-only time, which undercounted the AST work ~25x — see FIX 3/TASK-16).
13934
+ durationMs: elapsed(),
13915
13935
  patternCount: base.patternCount,
13916
13936
  // Bounded-work state is a property of the underlying scan and MUST survive
13917
13937
  // this merge — dropping it here would re-create the silent-incomplete hole
@@ -16425,11 +16445,11 @@ function versionFromCliPackageJson() {
16425
16445
  return void 0;
16426
16446
  }
16427
16447
  function productVersion() {
16428
- return override ?? (true ? "0.6.11" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16448
+ return override ?? (true ? "0.6.13" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16429
16449
  }
16430
16450
  var UNKNOWN_COMMIT = "unknown";
16431
16451
  function bakedCommitSha() {
16432
- return "b21055a9648ee90cddf55cbe5f9ad921f2363665".length > 0 ? "b21055a9648ee90cddf55cbe5f9ad921f2363665" : UNKNOWN_COMMIT;
16452
+ return "a36bf5d80a91cfd8fbf57e6c5a6a9b9114826309".length > 0 ? "a36bf5d80a91cfd8fbf57e6c5a6a9b9114826309" : UNKNOWN_COMMIT;
16433
16453
  }
16434
16454
  var FEATURE_TIERS = {
16435
16455
  compliance_export: "pro",
@@ -16517,10 +16537,15 @@ var PlanEnforcer = class {
16517
16537
  }
16518
16538
  const QUOTA_PERIOD_DAYS3 = 7;
16519
16539
  const resetDate = new Date(Date.now() + QUOTA_PERIOD_DAYS3 * 864e5).toISOString().split("T")[0];
16520
- if (worstBlocked || overallPercent >= 100) {
16540
+ const atOrOverLimit = (key) => {
16541
+ const check = limits[key];
16542
+ return check.limit !== void 0 && check.limit !== null && (check.current ?? 0) >= check.limit;
16543
+ };
16544
+ const anyAtOrOverLimit = metricKeys.some(atOrOverLimit);
16545
+ if (worstBlocked || anyAtOrOverLimit) {
16521
16546
  for (const key of metricKeys) {
16522
16547
  const check = limits[key];
16523
- if ((check.usagePercent ?? 0) >= 100 && check.limit !== void 0 && check.limit !== null) {
16548
+ if (atOrOverLimit(key)) {
16524
16549
  worstMetric = key;
16525
16550
  worstCurrent = check.current ?? 0;
16526
16551
  worstLimit = check.limit;
@@ -16839,7 +16864,10 @@ async function streamWithRollingBuffer(upstreamResp, map, provider, requestId) {
16839
16864
  (async () => {
16840
16865
  const reader = upstreamResp.body?.getReader();
16841
16866
  if (!reader) {
16842
- await writer.close();
16867
+ try {
16868
+ await writer.close();
16869
+ } catch {
16870
+ }
16843
16871
  return;
16844
16872
  }
16845
16873
  let lineBuffer = "";
@@ -16897,9 +16925,17 @@ async function streamWithRollingBuffer(upstreamResp, map, provider, requestId) {
16897
16925
  if (tail) await writer.write(encoder.encode(tail));
16898
16926
  }
16899
16927
  } finally {
16900
- await writer.close();
16928
+ try {
16929
+ await reader.cancel();
16930
+ } catch {
16931
+ }
16932
+ try {
16933
+ await writer.close();
16934
+ } catch {
16935
+ }
16901
16936
  }
16902
- })();
16937
+ })().catch(() => {
16938
+ });
16903
16939
  return new Response(readable, {
16904
16940
  status: upstreamResp.status,
16905
16941
  headers: {
@@ -17025,7 +17061,6 @@ function getTelemetryEmitter(dashboardUrl, pushToken) {
17025
17061
  _emitter = new TelemetryEmitter(url, token);
17026
17062
  return _emitter;
17027
17063
  }
17028
- var TIER_ORDER2 = { free: 0, pro: 1, enterprise: 2 };
17029
17064
  var riskScorer = new RiskScorer();
17030
17065
  function detectUpstream(path2, overrideHeader) {
17031
17066
  if (overrideHeader) return overrideHeader;
@@ -17034,12 +17069,66 @@ function detectUpstream(path2, overrideHeader) {
17034
17069
  if (path2.includes("/v1beta/") || path2.includes("/v1/models")) return "https://generativelanguage.googleapis.com";
17035
17070
  return "https://api.openai.com";
17036
17071
  }
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";
17072
+ function detectProviderFromPath(path2) {
17073
+ if (path2.includes("/v1/messages")) return "anthropic";
17074
+ if (path2.includes("/v1beta/") || path2.includes("/v1/models")) return "google";
17075
+ if (path2.includes("/v1/chat/completions") || path2.includes("/v1/completions")) return "openai";
17041
17076
  return "openai-compatible";
17042
17077
  }
17078
+ function modelFromPath(path2) {
17079
+ const m = path2.match(/\/models\/([^:/?]+)/);
17080
+ return m?.[1];
17081
+ }
17082
+ var _tierCache = /* @__PURE__ */ new Map();
17083
+ var TIER_CACHE_OK_MS = 6e4;
17084
+ var TIER_CACHE_FAIL_MS = 1e4;
17085
+ var TIER_VALIDATE_TIMEOUT_MS = 3e3;
17086
+ function tierApiRoot() {
17087
+ const raw2 = (process.env["PRETENSE_API_URL"] ?? "https://api.pretense.ai").trim();
17088
+ const normalized = /^https?:\/\//i.test(raw2) ? raw2 : `https://${raw2}`;
17089
+ return normalized.replace(/\/$/, "");
17090
+ }
17091
+ async function validateDashboardKeyTier(apiKey) {
17092
+ const url = `${tierApiRoot()}/api/cli/auth/validate`;
17093
+ const controller = new AbortController();
17094
+ const timer = setTimeout(() => controller.abort(), TIER_VALIDATE_TIMEOUT_MS);
17095
+ try {
17096
+ const resp = await fetch(url, {
17097
+ method: "POST",
17098
+ headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
17099
+ body: "{}",
17100
+ signal: controller.signal
17101
+ });
17102
+ if (!resp.ok) return { tier: "free", ok: false };
17103
+ const body = await resp.json();
17104
+ if (body["valid"] === false) return { tier: "free", ok: true };
17105
+ switch (String(body["plan"]).toUpperCase()) {
17106
+ case "PRO":
17107
+ return { tier: "pro", ok: true };
17108
+ case "ENTERPRISE":
17109
+ return { tier: "enterprise", ok: true };
17110
+ case "FREE":
17111
+ return { tier: "free", ok: true };
17112
+ default:
17113
+ return { tier: "free", ok: false };
17114
+ }
17115
+ } catch {
17116
+ return { tier: "free", ok: false };
17117
+ } finally {
17118
+ clearTimeout(timer);
17119
+ }
17120
+ }
17121
+ async function resolveKeyTier(apiKey) {
17122
+ if (!apiKey) return "free";
17123
+ const fromPrefix = tierFromApiKey(apiKey);
17124
+ if (fromPrefix) return fromPrefix;
17125
+ if (!apiKey.startsWith("prtns_")) return "free";
17126
+ const cached = _tierCache.get(apiKey);
17127
+ if (cached && cached.expiresAt > Date.now()) return cached.tier;
17128
+ const { tier, ok } = await validateDashboardKeyTier(apiKey);
17129
+ _tierCache.set(apiKey, { tier, expiresAt: Date.now() + (ok ? TIER_CACHE_OK_MS : TIER_CACHE_FAIL_MS) });
17130
+ return tier;
17131
+ }
17043
17132
  function extractAllText(body) {
17044
17133
  const parts = [];
17045
17134
  if (typeof body["system"] === "string") parts.push(body["system"]);
@@ -17054,6 +17143,19 @@ function extractAllText(body) {
17054
17143
  }
17055
17144
  }
17056
17145
  if (typeof body["prompt"] === "string") parts.push(body["prompt"]);
17146
+ const gWalkParts = (v) => {
17147
+ if (!Array.isArray(v)) return;
17148
+ for (const part of v) {
17149
+ if (part && typeof part["text"] === "string") parts.push(part["text"]);
17150
+ }
17151
+ };
17152
+ if (Array.isArray(body["contents"])) {
17153
+ for (const c of body["contents"]) {
17154
+ if (c) gWalkParts(c["parts"]);
17155
+ }
17156
+ }
17157
+ const sysInstr = body["systemInstruction"] ?? body["system_instruction"];
17158
+ if (sysInstr && typeof sysInstr === "object") gWalkParts(sysInstr["parts"]);
17057
17159
  return parts.join("\n");
17058
17160
  }
17059
17161
  var TRANSFORM_MAX_DEPTH = 256;
@@ -17377,11 +17479,7 @@ function createProxy(opts = {}) {
17377
17479
  }
17378
17480
  {
17379
17481
  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
- })();
17482
+ const _effectiveTier = await resolveKeyTier(_apiKey);
17385
17483
  const _requestEnforcer = new PlanEnforcer(_effectiveTier, planCfg.tenantId);
17386
17484
  const QUOTA_PERIOD_DAYS3 = 7;
17387
17485
  const _periodMs = QUOTA_PERIOD_DAYS3 * 864e5;
@@ -17450,8 +17548,8 @@ ${_upgradeTier === "enterprise" ? "Enterprise offers unlimited mutations \u2014
17450
17548
  }
17451
17549
  }
17452
17550
  const upstream = detectUpstream(c.req.path, c.req.header("x-pretense-upstream"));
17453
- const provider = detectProvider(upstream);
17454
- const model = body["model"] ?? "unknown";
17551
+ const provider = detectProviderFromPath(c.req.path);
17552
+ const model = body["model"] ?? (provider === "google" ? modelFromPath(c.req.path) : void 0) ?? "unknown";
17455
17553
  const fullText = extractAllText(body);
17456
17554
  const scanT0 = performance.now();
17457
17555
  const deep = tokenizeBodyDeep(
@@ -17648,12 +17746,26 @@ ${_upgradeTier === "enterprise" ? "Enterprise offers unlimited mutations \u2014
17648
17746
  });
17649
17747
  delete headers["host"];
17650
17748
  delete headers["content-length"];
17749
+ delete headers["expect"];
17750
+ delete headers["connection"];
17751
+ delete headers["keep-alive"];
17752
+ delete headers["transfer-encoding"];
17753
+ delete headers["te"];
17754
+ delete headers["upgrade"];
17651
17755
  headers["host"] = new URL(upstream).host;
17652
17756
  headers["x-pretense-request-id"] = requestId;
17653
17757
  if (riskResult.recommendation === "warn") {
17654
17758
  headers["x-pretense-risk-score"] = String(riskResult.score);
17655
17759
  }
17656
- const forwardBody = JSON.stringify(mutatedBody);
17760
+ let forwardBody;
17761
+ try {
17762
+ forwardBody = JSON.stringify(mutatedBody);
17763
+ } catch {
17764
+ return c.json(
17765
+ { error: { type: "invalid_request", message: "Request body is too deeply nested to process" } },
17766
+ 400
17767
+ );
17768
+ }
17657
17769
  const originalSize = fullText.length;
17658
17770
  const processedSize = forwardBody.length;
17659
17771
  let upstreamResp;
@@ -17786,6 +17898,15 @@ function startProxy(opts = {}) {
17786
17898
  });
17787
17899
  }
17788
17900
  }
17901
+ function renderClientEnvLines(port, protocol) {
17902
+ const base = `${protocol}://localhost:${port}`;
17903
+ return [
17904
+ " Point your AI client at Pretense:",
17905
+ ` ANTHROPIC_BASE_URL=${base}`,
17906
+ ` OPENAI_BASE_URL=${base}/v1`,
17907
+ ` GOOGLE_GEMINI_BASE_URL=${base}`
17908
+ ].join("\n") + "\n";
17909
+ }
17789
17910
  function printBanner(port, protocol, tls) {
17790
17911
  const tlsLabel = tls ? " [TLS]" : "";
17791
17912
  const version = productVersion();
@@ -17800,10 +17921,9 @@ function printBanner(port, protocol, tls) {
17800
17921
  \u2502 Proxy: ${protocol}://localhost:${port}${tls ? " " : " "}\u2502
17801
17922
  \u2502 Health: ${protocol}://localhost:${port}/health \u2502
17802
17923
  \u2502 \u2502
17803
- \u2502 ANTHROPIC_BASE_URL=${protocol}://localhost:${port} \u2502
17804
- \u2502 \u2502
17805
17924
  \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
- `
17925
+
17926
+ ` + renderClientEnvLines(port, protocol) + "\n"
17807
17927
  );
17808
17928
  }
17809
17929
  var isMain = process.argv[1]?.endsWith("server.js") || process.argv[1]?.endsWith("server.ts");
@@ -18223,9 +18343,24 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
18223
18343
  ".idea",
18224
18344
  ".vscode"
18225
18345
  ]);
18346
+ function isSkippableWalkError(err) {
18347
+ const code = err?.code;
18348
+ return code === "EACCES" || code === "EPERM" || code === "ENOENT" || code === "ELOOP" || code === "ENOTDIR";
18349
+ }
18226
18350
  function walkDir(dir) {
18227
18351
  const files = [];
18228
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
18352
+ let entries;
18353
+ try {
18354
+ entries = readdirSync2(dir, { withFileTypes: true });
18355
+ } catch (err) {
18356
+ if (isSkippableWalkError(err)) {
18357
+ const code = err.code;
18358
+ console.error(chalk4.yellow(` Skipping unreadable path (${code}): ${dir}`));
18359
+ return files;
18360
+ }
18361
+ throw err;
18362
+ }
18363
+ for (const entry of entries) {
18229
18364
  if (entry.isDirectory()) {
18230
18365
  if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
18231
18366
  files.push(...walkDir(join7(dir, entry.name)));
@@ -18278,6 +18413,18 @@ function l2LanguageFor(filePath) {
18278
18413
  if (!filePath) return void 0;
18279
18414
  return L2_LANGUAGE_BY_EXT[extname2(filePath).toLowerCase()];
18280
18415
  }
18416
+ var L2_LARGE_FILE_WARN_BYTES = 5 * 1024 * 1024;
18417
+ function warnIfLargeForL2(byteLength, filePath) {
18418
+ if (byteLength <= L2_LARGE_FILE_WARN_BYTES) return false;
18419
+ const mb = (byteLength / (1024 * 1024)).toFixed(1);
18420
+ const where = filePath ? ` \u2014 ${basename(filePath)}` : "";
18421
+ console.error(
18422
+ chalk4.yellow(
18423
+ ` Note: L2 AST scanning is slow on very large files (${mb}MB${where}); this may take a while.`
18424
+ )
18425
+ );
18426
+ return true;
18427
+ }
18281
18428
  async function scanContent(content, opts, filePath) {
18282
18429
  return withLocations(await scanContentRaw(content, opts, filePath), content);
18283
18430
  }
@@ -18321,6 +18468,7 @@ async function scanContentRaw(content, opts, filePath) {
18321
18468
  }
18322
18469
  };
18323
18470
  }
18471
+ warnIfLargeForL2(Buffer.byteLength(content, "utf-8"), filePath);
18324
18472
  const result = await scanLevel2(content, language, scanOpts);
18325
18473
  if (opts.level === 2) {
18326
18474
  const only2 = result.matches.filter((m) => m.level === 2);
@@ -18806,9 +18954,21 @@ function scanCommand() {
18806
18954
  process.exit(1);
18807
18955
  }
18808
18956
  } else {
18957
+ if (process.stdin.isTTY) {
18958
+ console.error(
18959
+ 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"
18960
+ );
18961
+ process.exit(USAGE_EXIT);
18962
+ }
18809
18963
  const chunks = [];
18810
18964
  for await (const chunk of process.stdin) chunks.push(chunk);
18811
18965
  content = Buffer.concat(chunks).toString("utf-8");
18966
+ if (content.trim() === "") {
18967
+ console.error(
18968
+ 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"
18969
+ );
18970
+ process.exit(USAGE_EXIT);
18971
+ }
18812
18972
  }
18813
18973
  const rawResult = await scanContent(content, opts, file);
18814
18974
  const filteredMatches = filterBySeverity(rawResult.matches, opts?.severity);
@@ -18881,18 +19041,25 @@ function statusCommand() {
18881
19041
  const fingerprintPath = join8(dir, ".pretense", "fingerprint.json");
18882
19042
  const configPath = join8(dir, "pretense.yaml");
18883
19043
  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) {
19044
+ const record = readProxyRecord(pidFilePath());
19045
+ if (record && !isProcessAlive(record.pid)) {
19046
+ console.log(
19047
+ chalk5.yellow(
19048
+ ` \u25CB Proxy not running ${chalk5.dim(`(stale record for pid ${record.pid}; run: pretense start)`)}`
19049
+ )
19050
+ );
19051
+ } else {
19052
+ const port = record && record.port > 0 ? record.port : 9339;
19053
+ try {
19054
+ const resp = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(1e3) });
19055
+ if (!resp.ok) throw new Error(`health returned ${resp.status}`);
18888
19056
  const health = await resp.json();
18889
- proxyRunning = true;
18890
- console.log(chalk5.green(` \u2713 Proxy running on :9339 (uptime: ${health["uptime"]}s)`));
19057
+ console.log(chalk5.green(` \u2713 Proxy running on :${port} (uptime: ${health["uptime"]}s)`));
18891
19058
  const stats = health["stats"];
18892
19059
  console.log(chalk5.dim(` Requests: ${stats["requestsProcessed"] ?? 0} | Mutations: ${stats["mutationsApplied"] ?? 0} | Blocked: ${stats["secretsBlocked"] ?? 0}`));
19060
+ } catch {
19061
+ console.log(chalk5.yellow(` \u25CB Proxy not running ${chalk5.dim("(run: pretense start)")}`));
18893
19062
  }
18894
- } catch {
18895
- console.log(chalk5.yellow(` \u25CB Proxy not running ${chalk5.dim("(run: pretense start)")}`));
18896
19063
  }
18897
19064
  if (existsSync8(configPath)) {
18898
19065
  console.log(chalk5.green(` \u2713 Config: pretense.yaml`));
@@ -20407,7 +20574,8 @@ function quickstartCommand() {
20407
20574
  console.log(` ${chalk13.green("2.")} ${chalk13.cyan("pretense install")} Install pre-commit + pre-push git hooks`);
20408
20575
  console.log(` ${chalk13.green("3.")} Set your LLM client to use the proxy:`);
20409
20576
  console.log(` ${chalk13.dim("ANTHROPIC_BASE_URL=http://localhost:9339 claude")}`);
20410
- console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/openai openai ...")}
20577
+ console.log(` ${chalk13.dim("OPENAI_BASE_URL=http://localhost:9339/v1 openai ...")}`);
20578
+ console.log(` ${chalk13.dim("GOOGLE_GEMINI_BASE_URL=http://localhost:9339 gemini ...")}
20411
20579
  `);
20412
20580
  console.log(chalk13.bold.green(" Protected. Happy coding.\n"));
20413
20581
  });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-5GO2CY5W.js";
4
+ } from "./chunk-HWSWJ4EC.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.11",
3
+ "version": "0.6.13",
4
4
  "description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,13 +24,13 @@
24
24
  "typescript": "^5.4.0",
25
25
  "vitest": "^1.0.0",
26
26
  "@pretense/protocol": "0.1.0",
27
- "@pretense/compliance-engine": "0.1.0",
28
27
  "@pretense/learner": "0.2.0",
29
- "@pretense/billing": "0.1.0",
28
+ "@pretense/compliance-engine": "0.1.0",
30
29
  "@pretense/mutator": "0.2.0",
30
+ "@pretense/billing": "0.1.0",
31
31
  "@pretense/proxy": "0.1.0",
32
- "@pretense/scanner-rs": "0.2.0",
33
32
  "@pretense/scanner": "0.2.0",
33
+ "@pretense/scanner-rs": "0.2.0",
34
34
  "@pretense/store": "0.2.0"
35
35
  },
36
36
  "publishConfig": {