@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.
- package/dist/{chunk-5GO2CY5W.js → chunk-HWSWJ4EC.js} +1 -1
- package/dist/index.js +208 -40
- package/dist/postinstall.js +1 -1
- package/package.json +4 -4
|
@@ -17,7 +17,7 @@ function versionFromPackageJson() {
|
|
|
17
17
|
return void 0;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
var PRETENSE_VERSION = (true ? "0.6.
|
|
20
|
+
var PRETENSE_VERSION = (true ? "0.6.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-
|
|
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": ["
|
|
7569
|
-
"uk-nino-labeled": ["
|
|
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-`,
|
|
12749
|
-
// Stripe webhook `whsec_`) are deliberately skipped
|
|
12761
|
+
// `sk-`, Slack `xox*`, GitHub `gh*_`/`glpat-`, HuggingFace `hf_`, AWS
|
|
12762
|
+
// `AKIA`, Google `AIza`, Stripe webhook `whsec_`) are deliberately skipped
|
|
12763
|
+
// so a generic
|
|
12750
12764
|
// `KEY=variable` reference is not flagged and the dedicated, higher-fidelity
|
|
12751
12765
|
// patterns above own those matches. Placed last so more-specific patterns
|
|
12752
12766
|
// win overlap deduplication on ties.
|
|
@@ -12760,7 +12774,7 @@ var ENV_PATTERNS = [
|
|
|
12760
12774
|
// specific env-var names rather than generic secret-label tails, so they do
|
|
12761
12775
|
// not belong in the shared vocabulary.
|
|
12762
12776
|
pattern: new RegExp(
|
|
12763
|
-
`(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
|
|
12777
|
+
`(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|hf_|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
|
|
12764
12778
|
"gi"
|
|
12765
12779
|
),
|
|
12766
12780
|
// PRECISION: reject `PASSWORD=changeme`, `PASSWORD=<your-password>` and
|
|
@@ -13827,6 +13841,8 @@ function overlapsAny(m, sorted) {
|
|
|
13827
13841
|
}
|
|
13828
13842
|
async function scanLevel2(text, language, opts = {}) {
|
|
13829
13843
|
const lang = language ?? opts.language ?? "typescript";
|
|
13844
|
+
const t0 = performance.now();
|
|
13845
|
+
const elapsed = () => Math.round((performance.now() - t0) * 100) / 100;
|
|
13830
13846
|
const base = opts.presidioUrl ? await scanWithPresidio(text, opts.presidioUrl, opts) : scan(text, opts);
|
|
13831
13847
|
const l1Matches = base.matches.map((m) => ({ ...m, level: 1 }));
|
|
13832
13848
|
const l1Findings = base.findings.map((f) => ({ ...f, level: 1 }));
|
|
@@ -13834,6 +13850,8 @@ async function scanLevel2(text, language, opts = {}) {
|
|
|
13834
13850
|
...base,
|
|
13835
13851
|
matches: l1Matches,
|
|
13836
13852
|
findings: l1Findings,
|
|
13853
|
+
// Real wall time up to this early return, not the L1 sub-phase alone.
|
|
13854
|
+
durationMs: elapsed(),
|
|
13837
13855
|
l2: status
|
|
13838
13856
|
});
|
|
13839
13857
|
const native = loadNative();
|
|
@@ -13911,7 +13929,9 @@ async function scanLevel2(text, language, opts = {}) {
|
|
|
13911
13929
|
matches: merged,
|
|
13912
13930
|
findings: mergedFindings,
|
|
13913
13931
|
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13914
|
-
|
|
13932
|
+
// Real elapsed wall time for the full L1+L2 scan (was base.durationMs, the
|
|
13933
|
+
// L1-only time, which undercounted the AST work ~25x — see FIX 3/TASK-16).
|
|
13934
|
+
durationMs: elapsed(),
|
|
13915
13935
|
patternCount: base.patternCount,
|
|
13916
13936
|
// Bounded-work state is a property of the underlying scan and MUST survive
|
|
13917
13937
|
// this merge — dropping it here would re-create the silent-incomplete hole
|
|
@@ -16425,11 +16445,11 @@ function versionFromCliPackageJson() {
|
|
|
16425
16445
|
return void 0;
|
|
16426
16446
|
}
|
|
16427
16447
|
function productVersion() {
|
|
16428
|
-
return override ?? (true ? "0.6.
|
|
16448
|
+
return override ?? (true ? "0.6.13" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
|
|
16429
16449
|
}
|
|
16430
16450
|
var UNKNOWN_COMMIT = "unknown";
|
|
16431
16451
|
function bakedCommitSha() {
|
|
16432
|
-
return "
|
|
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
|
-
|
|
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 ((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
17038
|
-
if (
|
|
17039
|
-
if (
|
|
17040
|
-
if (
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
});
|
package/dist/postinstall.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pretense/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.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/
|
|
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": {
|