@blockyfy/stg-cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -57
- package/dist/index.js +204 -292
- package/dist/index.js.map +1 -1
- package/package.json +56 -56
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import chalk9 from "chalk";
|
|
6
6
|
import {
|
|
7
7
|
readFileSync as readFileSync7,
|
|
8
|
-
existsSync as
|
|
8
|
+
existsSync as existsSync7,
|
|
9
9
|
readdirSync,
|
|
10
10
|
statSync,
|
|
11
11
|
openSync,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
closeSync
|
|
14
14
|
} from "fs";
|
|
15
15
|
import { readFile, stat as fsStat, open as fsOpen } from "fs/promises";
|
|
16
|
-
import { resolve as resolve2, extname, join as
|
|
16
|
+
import { resolve as resolve2, extname, join as join5, basename } from "path";
|
|
17
17
|
import os from "os";
|
|
18
18
|
|
|
19
19
|
// src/types.ts
|
|
@@ -31,7 +31,7 @@ var DEFAULT_CONFIG = {
|
|
|
31
31
|
{ tokenType: "Property" /* Property */, prefix: "_prop" },
|
|
32
32
|
{ tokenType: "Method" /* Method */, prefix: "_fn" }
|
|
33
33
|
],
|
|
34
|
-
storePath: `${process.env["HOME"] ?? "/tmp"}/.
|
|
34
|
+
storePath: `${process.env["HOME"] ?? "/tmp"}/.pretense`
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
// src/scanner.ts
|
|
@@ -866,50 +866,20 @@ function scan(code, language) {
|
|
|
866
866
|
}
|
|
867
867
|
|
|
868
868
|
// src/salt.ts
|
|
869
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync
|
|
870
|
-
import { join
|
|
869
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
870
|
+
import { join } from "path";
|
|
871
871
|
import { homedir } from "os";
|
|
872
872
|
import { randomBytes } from "crypto";
|
|
873
|
-
|
|
874
|
-
// src/config-dir.ts
|
|
875
|
-
import { existsSync, renameSync } from "fs";
|
|
876
|
-
import { join } from "path";
|
|
877
|
-
var PROJECT_CONFIG_DIR_NAME = ".pretest";
|
|
878
|
-
var LEGACY_PROJECT_CONFIG_DIR_NAME = ".pretense";
|
|
879
|
-
function migrateLegacyProjectConfigDir(parentDir) {
|
|
880
|
-
const next = join(parentDir, PROJECT_CONFIG_DIR_NAME);
|
|
881
|
-
const prev = join(parentDir, LEGACY_PROJECT_CONFIG_DIR_NAME);
|
|
882
|
-
if (existsSync(next)) return;
|
|
883
|
-
if (!existsSync(prev)) return;
|
|
884
|
-
try {
|
|
885
|
-
renameSync(prev, next);
|
|
886
|
-
} catch {
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// src/salt.ts
|
|
891
873
|
var SALT_BYTES = 32;
|
|
892
874
|
function getSaltPath() {
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
const dir = join2(home, PROJECT_CONFIG_DIR_NAME);
|
|
896
|
-
const legacyDir = join2(home, LEGACY_PROJECT_CONFIG_DIR_NAME);
|
|
897
|
-
const path = join2(dir, "mutation.salt");
|
|
898
|
-
const legacySalt = join2(legacyDir, "mutation.salt");
|
|
899
|
-
if (!existsSync2(path) && existsSync2(legacySalt)) {
|
|
900
|
-
try {
|
|
901
|
-
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
902
|
-
renameSync2(legacySalt, path);
|
|
903
|
-
} catch {
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
return { dir, path };
|
|
875
|
+
const dir = join(homedir(), ".pretense");
|
|
876
|
+
return { dir, path: join(dir, "mutation.salt") };
|
|
907
877
|
}
|
|
908
878
|
var _cachedSalt = null;
|
|
909
879
|
function getMutationSalt() {
|
|
910
880
|
if (_cachedSalt) return _cachedSalt;
|
|
911
881
|
const { dir, path } = getSaltPath();
|
|
912
|
-
if (
|
|
882
|
+
if (existsSync(path)) {
|
|
913
883
|
try {
|
|
914
884
|
const raw = readFileSync(path, "utf-8").trim();
|
|
915
885
|
if (/^[0-9a-f]{64}$/i.test(raw)) {
|
|
@@ -931,12 +901,6 @@ function getMutationSalt() {
|
|
|
931
901
|
function buildSaltedSeed(baseSeed) {
|
|
932
902
|
return `${baseSeed}:${getMutationSalt()}`;
|
|
933
903
|
}
|
|
934
|
-
function effectiveSeedForMutation(userSeed) {
|
|
935
|
-
if (userSeed === "pretense" || userSeed === "pretest") {
|
|
936
|
-
return buildSaltedSeed("pretense");
|
|
937
|
-
}
|
|
938
|
-
return userSeed;
|
|
939
|
-
}
|
|
940
904
|
|
|
941
905
|
// src/deterministic-id.ts
|
|
942
906
|
function hash32(str) {
|
|
@@ -980,7 +944,7 @@ function prefixForType(type) {
|
|
|
980
944
|
return "_tok";
|
|
981
945
|
}
|
|
982
946
|
}
|
|
983
|
-
function mutate(code, language, seed = "
|
|
947
|
+
function mutate(code, language, seed = "pretense") {
|
|
984
948
|
const startMs = performance.now();
|
|
985
949
|
const lang = language ?? detectLanguage(code);
|
|
986
950
|
if (!code || !code.trim()) {
|
|
@@ -991,7 +955,7 @@ function mutate(code, language, seed = "pretest") {
|
|
|
991
955
|
stats: { tokensScanned: 0, tokensMutated: 0, durationMs: 0, language: lang }
|
|
992
956
|
};
|
|
993
957
|
}
|
|
994
|
-
const effectiveSeed =
|
|
958
|
+
const effectiveSeed = seed === "pretense" ? buildSaltedSeed(seed) : seed;
|
|
995
959
|
const { tokens } = scan(code, lang);
|
|
996
960
|
const map = /* @__PURE__ */ new Map();
|
|
997
961
|
for (const token of tokens) {
|
|
@@ -1080,7 +1044,7 @@ function reverse(mutatedCode, map, secretsMap) {
|
|
|
1080
1044
|
// src/secrets.ts
|
|
1081
1045
|
var SECRET_PATTERNS = [
|
|
1082
1046
|
{ name: "anthropic-api-key", category: "secret", severity: "critical", defaultAction: "block", pattern: /sk-ant-api\d{2}-[A-Za-z0-9_-]{40,}/g },
|
|
1083
|
-
/** Legacy `sk-…` and modern `sk-proj-…` (hyphenated body); aligned with
|
|
1047
|
+
/** Legacy `sk-…` and modern `sk-proj-…` (hyphenated body); aligned with @pretense/scanner */
|
|
1084
1048
|
{ name: "openai-api-key", category: "secret", severity: "critical", defaultAction: "block", pattern: /sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g },
|
|
1085
1049
|
{ name: "aws-access-key", category: "secret", severity: "critical", defaultAction: "block", pattern: /AKIA[0-9A-Z]{16}/g },
|
|
1086
1050
|
/** `AWS_SECRET = '…40 chars…'` (not `AWS_SECRET_ACCESS_KEY`, which is covered below). */
|
|
@@ -1343,7 +1307,7 @@ function applyRedactionsTracked(text, matches, mutationSeed = buildSaltedSeed("p
|
|
|
1343
1307
|
}
|
|
1344
1308
|
|
|
1345
1309
|
// src/store.ts
|
|
1346
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as
|
|
1310
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
1347
1311
|
import { dirname } from "path";
|
|
1348
1312
|
var MutationStore = class {
|
|
1349
1313
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -1408,7 +1372,7 @@ var MutationStore = class {
|
|
|
1408
1372
|
*/
|
|
1409
1373
|
persist() {
|
|
1410
1374
|
const dir = dirname(this.filePath);
|
|
1411
|
-
if (!
|
|
1375
|
+
if (!existsSync2(dir)) {
|
|
1412
1376
|
mkdirSync2(dir, { recursive: true });
|
|
1413
1377
|
}
|
|
1414
1378
|
const data = JSON.stringify([...this.entries.values()], null, 2);
|
|
@@ -1419,7 +1383,7 @@ var MutationStore = class {
|
|
|
1419
1383
|
* No-ops if the file doesn't exist.
|
|
1420
1384
|
*/
|
|
1421
1385
|
load() {
|
|
1422
|
-
if (!
|
|
1386
|
+
if (!existsSync2(this.filePath)) return;
|
|
1423
1387
|
try {
|
|
1424
1388
|
const raw = readFileSync2(this.filePath, "utf-8");
|
|
1425
1389
|
const parsed = JSON.parse(raw);
|
|
@@ -1454,42 +1418,41 @@ var MutationStore = class {
|
|
|
1454
1418
|
};
|
|
1455
1419
|
|
|
1456
1420
|
// src/config.ts
|
|
1457
|
-
import { existsSync as
|
|
1458
|
-
import { join as
|
|
1421
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1422
|
+
import { join as join2, resolve } from "path";
|
|
1459
1423
|
import { createHmac } from "crypto";
|
|
1460
|
-
var CONFIG_DIR_NAME =
|
|
1424
|
+
var CONFIG_DIR_NAME = ".pretense";
|
|
1461
1425
|
var CONFIG_FILE = "config.json";
|
|
1462
1426
|
var MUTATION_MAP_FILE = "mutation-map.json";
|
|
1463
1427
|
var AUDIT_LOG_FILE = "audit.log";
|
|
1464
1428
|
var USAGE_FILE = "usage.json";
|
|
1465
1429
|
function getConfigDir(baseDir) {
|
|
1466
1430
|
const base = baseDir ?? process.cwd();
|
|
1467
|
-
migrateLegacyProjectConfigDir(base);
|
|
1468
1431
|
return resolve(base, CONFIG_DIR_NAME);
|
|
1469
1432
|
}
|
|
1470
1433
|
function initConfig(dir) {
|
|
1471
1434
|
const configDir = getConfigDir(dir);
|
|
1472
|
-
if (!
|
|
1435
|
+
if (!existsSync3(configDir)) {
|
|
1473
1436
|
mkdirSync3(configDir, { recursive: true });
|
|
1474
1437
|
}
|
|
1475
|
-
const configPath =
|
|
1476
|
-
if (!
|
|
1438
|
+
const configPath = join2(configDir, CONFIG_FILE);
|
|
1439
|
+
if (!existsSync3(configPath)) {
|
|
1477
1440
|
const config = {
|
|
1478
1441
|
...DEFAULT_CONFIG,
|
|
1479
1442
|
storePath: configDir
|
|
1480
1443
|
};
|
|
1481
1444
|
writeFileSync3(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1482
1445
|
}
|
|
1483
|
-
const mapPath =
|
|
1484
|
-
if (!
|
|
1446
|
+
const mapPath = join2(configDir, MUTATION_MAP_FILE);
|
|
1447
|
+
if (!existsSync3(mapPath)) {
|
|
1485
1448
|
writeFileSync3(mapPath, "[]", "utf-8");
|
|
1486
1449
|
}
|
|
1487
|
-
const auditPath =
|
|
1488
|
-
if (!
|
|
1450
|
+
const auditPath = join2(configDir, AUDIT_LOG_FILE);
|
|
1451
|
+
if (!existsSync3(auditPath)) {
|
|
1489
1452
|
writeFileSync3(auditPath, "", "utf-8");
|
|
1490
1453
|
}
|
|
1491
|
-
const usagePath =
|
|
1492
|
-
if (!
|
|
1454
|
+
const usagePath = join2(configDir, USAGE_FILE);
|
|
1455
|
+
if (!existsSync3(usagePath)) {
|
|
1493
1456
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1494
1457
|
const month = today.slice(0, 7);
|
|
1495
1458
|
const usageData = { month, mutations: 0, firstUseDate: today };
|
|
@@ -1503,8 +1466,8 @@ function initConfig(dir) {
|
|
|
1503
1466
|
}
|
|
1504
1467
|
function loadConfig(dir) {
|
|
1505
1468
|
const configDir = getConfigDir(dir);
|
|
1506
|
-
const configPath =
|
|
1507
|
-
if (!
|
|
1469
|
+
const configPath = join2(configDir, CONFIG_FILE);
|
|
1470
|
+
if (!existsSync3(configPath)) {
|
|
1508
1471
|
return { ...DEFAULT_CONFIG };
|
|
1509
1472
|
}
|
|
1510
1473
|
try {
|
|
@@ -1517,7 +1480,7 @@ function loadConfig(dir) {
|
|
|
1517
1480
|
}
|
|
1518
1481
|
var USAGE_HMAC_SECRET = "pretense_usage_integrity_v1";
|
|
1519
1482
|
function getUsageSecret() {
|
|
1520
|
-
return process.env["
|
|
1483
|
+
return process.env["PRETENSE_USAGE_SECRET"] ?? USAGE_HMAC_SECRET;
|
|
1521
1484
|
}
|
|
1522
1485
|
function computeUsageChecksum(data) {
|
|
1523
1486
|
const payload = JSON.stringify({ month: data.month, mutations: data.mutations, firstUseDate: data.firstUseDate });
|
|
@@ -1529,10 +1492,10 @@ function verifyUsageChecksum(data) {
|
|
|
1529
1492
|
}
|
|
1530
1493
|
function loadUsage(dir) {
|
|
1531
1494
|
const configDir = getConfigDir(dir);
|
|
1532
|
-
const usagePath =
|
|
1495
|
+
const usagePath = join2(configDir, USAGE_FILE);
|
|
1533
1496
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1534
1497
|
const currentMonth = today.slice(0, 7);
|
|
1535
|
-
if (!
|
|
1498
|
+
if (!existsSync3(usagePath)) {
|
|
1536
1499
|
return { month: currentMonth, mutations: 0, firstUseDate: today };
|
|
1537
1500
|
}
|
|
1538
1501
|
try {
|
|
@@ -1543,7 +1506,7 @@ function loadUsage(dir) {
|
|
|
1543
1506
|
return { month: currentMonth, mutations: 0, firstUseDate: data.firstUseDate ?? legacyDate };
|
|
1544
1507
|
}
|
|
1545
1508
|
if (!verifyUsageChecksum({ month: data.month, mutations: data.mutations, firstUseDate: data.firstUseDate, checksum: data.checksum })) {
|
|
1546
|
-
process.stderr.write("[
|
|
1509
|
+
process.stderr.write("[PRETENSE] Warning: usage.json integrity check failed. Resetting usage counter.\n");
|
|
1547
1510
|
return { month: currentMonth, mutations: 0, firstUseDate: data.firstUseDate ?? today };
|
|
1548
1511
|
}
|
|
1549
1512
|
if (data.month !== currentMonth) {
|
|
@@ -1556,10 +1519,10 @@ function loadUsage(dir) {
|
|
|
1556
1519
|
}
|
|
1557
1520
|
function saveUsage(usage, dir) {
|
|
1558
1521
|
const configDir = getConfigDir(dir);
|
|
1559
|
-
if (!
|
|
1522
|
+
if (!existsSync3(configDir)) {
|
|
1560
1523
|
mkdirSync3(configDir, { recursive: true });
|
|
1561
1524
|
}
|
|
1562
|
-
const usagePath =
|
|
1525
|
+
const usagePath = join2(configDir, USAGE_FILE);
|
|
1563
1526
|
const checksum = computeUsageChecksum(usage);
|
|
1564
1527
|
writeFileSync3(usagePath, JSON.stringify({ ...usage, checksum }, null, 2), "utf-8");
|
|
1565
1528
|
}
|
|
@@ -1568,7 +1531,7 @@ var LICENSE_PAYLOAD_REGEX = /^[a-zA-Z0-9]+$/;
|
|
|
1568
1531
|
function isValidLicenseKey(key, prefix) {
|
|
1569
1532
|
if (key.length < MIN_LICENSE_KEY_LENGTH) return false;
|
|
1570
1533
|
const afterPrefix = key.slice(prefix.length);
|
|
1571
|
-
const hmacSecret = process.env["
|
|
1534
|
+
const hmacSecret = process.env["PRETENSE_LICENSE_SECRET"];
|
|
1572
1535
|
if (hmacSecret) {
|
|
1573
1536
|
const lastUnderscore = afterPrefix.lastIndexOf("_");
|
|
1574
1537
|
if (lastUnderscore === -1) return false;
|
|
@@ -1582,7 +1545,7 @@ function isValidLicenseKey(key, prefix) {
|
|
|
1582
1545
|
return LICENSE_PAYLOAD_REGEX.test(afterPrefix);
|
|
1583
1546
|
}
|
|
1584
1547
|
function detectTier() {
|
|
1585
|
-
const key = process.env["
|
|
1548
|
+
const key = process.env["PRETENSE_LICENSE_KEY"] ?? "";
|
|
1586
1549
|
if (key.startsWith("pre_ent_") && isValidLicenseKey(key, "pre_ent_")) return "enterprise";
|
|
1587
1550
|
if (key.startsWith("pre_pro_") && isValidLicenseKey(key, "pre_pro_")) return "pro";
|
|
1588
1551
|
return "free";
|
|
@@ -1605,13 +1568,13 @@ function getTierLimits(tier) {
|
|
|
1605
1568
|
}
|
|
1606
1569
|
|
|
1607
1570
|
// src/audit.ts
|
|
1608
|
-
import { appendFileSync, existsSync as
|
|
1609
|
-
import { join as
|
|
1571
|
+
import { appendFileSync, existsSync as existsSync4, readFileSync as readFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
1572
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
1610
1573
|
function writeAuditEntry(entry, dir) {
|
|
1611
1574
|
const configDir = getConfigDir(dir);
|
|
1612
|
-
const auditPath =
|
|
1575
|
+
const auditPath = join3(configDir, "audit.log");
|
|
1613
1576
|
const logDir = dirname2(auditPath);
|
|
1614
|
-
if (!
|
|
1577
|
+
if (!existsSync4(logDir)) {
|
|
1615
1578
|
mkdirSync4(logDir, { recursive: true });
|
|
1616
1579
|
}
|
|
1617
1580
|
const line = JSON.stringify(entry) + "\n";
|
|
@@ -1631,8 +1594,8 @@ function createAuditEntry(sessionId, file, identifiersMutated, secretsBlocked, l
|
|
|
1631
1594
|
function readAuditLog(options = {}) {
|
|
1632
1595
|
const { limit, dir } = options;
|
|
1633
1596
|
const configDir = getConfigDir(dir);
|
|
1634
|
-
const auditPath =
|
|
1635
|
-
if (!
|
|
1597
|
+
const auditPath = join3(configDir, "audit.log");
|
|
1598
|
+
if (!existsSync4(auditPath)) {
|
|
1636
1599
|
return [];
|
|
1637
1600
|
}
|
|
1638
1601
|
const raw = readFileSync4(auditPath, "utf-8");
|
|
@@ -1699,7 +1662,7 @@ import { Hono } from "hono";
|
|
|
1699
1662
|
import { serve } from "@hono/node-server";
|
|
1700
1663
|
import { nanoid } from "nanoid";
|
|
1701
1664
|
import { createServer } from "net";
|
|
1702
|
-
import { existsSync as
|
|
1665
|
+
import { existsSync as existsSync6, watch } from "fs";
|
|
1703
1666
|
|
|
1704
1667
|
// src/auth.ts
|
|
1705
1668
|
import { createHash } from "crypto";
|
|
@@ -1819,55 +1782,18 @@ async function collectGitContextCached(cwd = process.cwd(), now = Date.now()) {
|
|
|
1819
1782
|
}
|
|
1820
1783
|
|
|
1821
1784
|
// src/commands/login.ts
|
|
1822
|
-
import { existsSync as
|
|
1823
|
-
import { join as
|
|
1785
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, readFileSync as readFileSync5, chmodSync, readSync } from "fs";
|
|
1786
|
+
import { join as join4 } from "path";
|
|
1824
1787
|
import { homedir as homedir2 } from "os";
|
|
1825
1788
|
import readline from "readline/promises";
|
|
1826
1789
|
import chalk from "chalk";
|
|
1827
|
-
|
|
1828
|
-
// src/dashboard-env.ts
|
|
1829
|
-
var DASHBOARD_API_KEY_ENVS = ["PRETEST_API_KEY", "PRETENSE_API_KEY"];
|
|
1830
|
-
var DASHBOARD_API_URL_ENVS = ["PRETEST_API_URL", "PRETENSE_API_URL"];
|
|
1831
|
-
var DASHBOARD_NO_BANNER_ENVS = ["PRETEST_NO_BANNER", "PRETENSE_NO_BANNER"];
|
|
1832
|
-
function firstNonEmptyEnv(names) {
|
|
1833
|
-
for (const name of names) {
|
|
1834
|
-
const v = process.env[name]?.trim();
|
|
1835
|
-
if (v) return v;
|
|
1836
|
-
}
|
|
1837
|
-
return "";
|
|
1838
|
-
}
|
|
1839
|
-
function activeDashboardApiKeyEnvName() {
|
|
1840
|
-
for (const name of DASHBOARD_API_KEY_ENVS) {
|
|
1841
|
-
const v = process.env[name]?.trim();
|
|
1842
|
-
if (v) return name;
|
|
1843
|
-
}
|
|
1844
|
-
return null;
|
|
1845
|
-
}
|
|
1846
|
-
function assignDashboardApiKeyToProcess(key) {
|
|
1847
|
-
process.env["PRETEST_API_KEY"] = key;
|
|
1848
|
-
process.env["PRETENSE_API_KEY"] = key;
|
|
1849
|
-
}
|
|
1850
|
-
function clearDashboardApiKeyFromProcess() {
|
|
1851
|
-
delete process.env["PRETEST_API_KEY"];
|
|
1852
|
-
delete process.env["PRETENSE_API_KEY"];
|
|
1853
|
-
}
|
|
1854
|
-
function hasDashboardApiKeyInEnv() {
|
|
1855
|
-
return DASHBOARD_API_KEY_ENVS.some((n) => !!process.env[n]?.trim());
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
// src/publish-info.ts
|
|
1859
|
-
var PUBLISH_PACKAGE_URL = "https://www.npmjs.com/package/@blockyfy/stg-cli";
|
|
1860
|
-
|
|
1861
|
-
// src/commands/login.ts
|
|
1862
|
-
var CONFIG_DIR_NAME2 = PROJECT_CONFIG_DIR_NAME;
|
|
1790
|
+
var CONFIG_DIR_NAME2 = ".pretense";
|
|
1863
1791
|
var CONFIG_FILE2 = "config.json";
|
|
1864
1792
|
function getUserDashboardConfigDir() {
|
|
1865
|
-
|
|
1866
|
-
migrateLegacyProjectConfigDir(home);
|
|
1867
|
-
return join5(home, CONFIG_DIR_NAME2);
|
|
1793
|
+
return join4(homedir2(), CONFIG_DIR_NAME2);
|
|
1868
1794
|
}
|
|
1869
1795
|
function getUserDashboardConfigPath() {
|
|
1870
|
-
return
|
|
1796
|
+
return join4(getUserDashboardConfigDir(), CONFIG_FILE2);
|
|
1871
1797
|
}
|
|
1872
1798
|
function isValidKeyShape(key) {
|
|
1873
1799
|
const trimmed = key.trim();
|
|
@@ -1900,30 +1826,29 @@ function maskKey(key) {
|
|
|
1900
1826
|
return `${trimmed.slice(0, 12)}${"\u2022".repeat(8)}${trimmed.slice(-4)}`;
|
|
1901
1827
|
}
|
|
1902
1828
|
function logDashboardCredentialHint() {
|
|
1903
|
-
const
|
|
1904
|
-
const env = envName ? process.env[envName]?.trim() ?? "" : "";
|
|
1829
|
+
const env = process.env["PRETENSE_API_KEY"]?.trim() ?? "";
|
|
1905
1830
|
const envOk = env.length > 0 && isValidKeyShape(env);
|
|
1906
1831
|
const abs = getUserDashboardConfigPath();
|
|
1907
1832
|
const fk = loadApiKey();
|
|
1908
|
-
if (envOk
|
|
1833
|
+
if (envOk) {
|
|
1909
1834
|
console.error(
|
|
1910
1835
|
chalk.gray(
|
|
1911
|
-
`[
|
|
1836
|
+
`[pretense] Dashboard key source: PRETENSE_API_KEY in environment (${maskKey(env)}) \u2014 unset this variable after logout if you expect no key.`
|
|
1912
1837
|
)
|
|
1913
1838
|
);
|
|
1914
1839
|
} else if (fk && isValidKeyShape(fk)) {
|
|
1915
|
-
console.error(chalk.gray(`[
|
|
1840
|
+
console.error(chalk.gray(`[pretense] Dashboard key source: ${abs} (${maskKey(fk)})`));
|
|
1916
1841
|
} else {
|
|
1917
|
-
console.error(chalk.gray("[
|
|
1842
|
+
console.error(chalk.gray("[pretense] Dashboard key source: none configured."));
|
|
1918
1843
|
}
|
|
1919
1844
|
}
|
|
1920
1845
|
function saveApiKey(key, configDir = getUserDashboardConfigDir()) {
|
|
1921
|
-
if (!
|
|
1846
|
+
if (!existsSync5(configDir)) {
|
|
1922
1847
|
mkdirSync5(configDir, { recursive: true });
|
|
1923
1848
|
}
|
|
1924
|
-
const path =
|
|
1849
|
+
const path = join4(configDir, CONFIG_FILE2);
|
|
1925
1850
|
let existing = {};
|
|
1926
|
-
if (
|
|
1851
|
+
if (existsSync5(path)) {
|
|
1927
1852
|
try {
|
|
1928
1853
|
existing = JSON.parse(readFileSync5(path, "utf-8"));
|
|
1929
1854
|
} catch {
|
|
@@ -1943,8 +1868,8 @@ function saveApiKey(key, configDir = getUserDashboardConfigDir()) {
|
|
|
1943
1868
|
return path;
|
|
1944
1869
|
}
|
|
1945
1870
|
function removeSavedDashboardCredentials(configDir = getUserDashboardConfigDir()) {
|
|
1946
|
-
const path =
|
|
1947
|
-
if (!
|
|
1871
|
+
const path = join4(configDir, CONFIG_FILE2);
|
|
1872
|
+
if (!existsSync5(path)) return { removed: false, path };
|
|
1948
1873
|
let existing = {};
|
|
1949
1874
|
try {
|
|
1950
1875
|
existing = JSON.parse(readFileSync5(path, "utf-8"));
|
|
@@ -1964,8 +1889,8 @@ function removeSavedDashboardCredentials(configDir = getUserDashboardConfigDir()
|
|
|
1964
1889
|
return { removed: true, path };
|
|
1965
1890
|
}
|
|
1966
1891
|
function loadApiKey(configDir = getUserDashboardConfigDir()) {
|
|
1967
|
-
const path =
|
|
1968
|
-
if (!
|
|
1892
|
+
const path = join4(configDir, CONFIG_FILE2);
|
|
1893
|
+
if (!existsSync5(path)) return null;
|
|
1969
1894
|
try {
|
|
1970
1895
|
const raw = readFileSync5(path, "utf-8");
|
|
1971
1896
|
const parsed = JSON.parse(raw);
|
|
@@ -1978,7 +1903,7 @@ function loadApiKey(configDir = getUserDashboardConfigDir()) {
|
|
|
1978
1903
|
return null;
|
|
1979
1904
|
}
|
|
1980
1905
|
function resolveDashboardKeyCandidate() {
|
|
1981
|
-
const env =
|
|
1906
|
+
const env = (process.env["PRETENSE_API_KEY"] ?? "").trim();
|
|
1982
1907
|
if (env.length > 0) {
|
|
1983
1908
|
if (!isValidKeyShape(env)) {
|
|
1984
1909
|
return { key: null, fromFile: false, badEnv: true };
|
|
@@ -1994,14 +1919,14 @@ function resolveDashboardKeyCandidate() {
|
|
|
1994
1919
|
function installDashboardKeyForCurrentProcess(key) {
|
|
1995
1920
|
const trimmed = key.trim();
|
|
1996
1921
|
const path = saveApiKey(trimmed);
|
|
1997
|
-
|
|
1922
|
+
process.env["PRETENSE_API_KEY"] = trimmed;
|
|
1998
1923
|
return path;
|
|
1999
1924
|
}
|
|
2000
1925
|
async function promptDashboardApiKeyAfterFailure() {
|
|
2001
1926
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
2002
1927
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2003
1928
|
try {
|
|
2004
|
-
const line = (await rl.question(chalk.bold("
|
|
1929
|
+
const line = (await rl.question(chalk.bold("Pretense dashboard API key: "))).trim();
|
|
2005
1930
|
return line.length > 0 ? line : null;
|
|
2006
1931
|
} finally {
|
|
2007
1932
|
rl.close();
|
|
@@ -2010,15 +1935,9 @@ async function promptDashboardApiKeyAfterFailure() {
|
|
|
2010
1935
|
async function ensureDashboardKeyForStart() {
|
|
2011
1936
|
const { key, fromFile, badEnv } = resolveDashboardKeyCandidate();
|
|
2012
1937
|
if (badEnv) {
|
|
1938
|
+
console.error(chalk.red("Error: PRETENSE_API_KEY is set but is not a valid Pretense dashboard API key shape."));
|
|
2013
1939
|
console.error(
|
|
2014
|
-
chalk.
|
|
2015
|
-
"Error: A dashboard API key is set in the environment but does not match the expected key shape."
|
|
2016
|
-
)
|
|
2017
|
-
);
|
|
2018
|
-
console.error(
|
|
2019
|
-
chalk.gray(
|
|
2020
|
-
"Keys look like prtns_live_\u2026 or pk_live_\u2026. Fix the variable or unset it to use ~/.pretest/config.json."
|
|
2021
|
-
)
|
|
1940
|
+
chalk.gray("Keys look like prtns_live_\u2026 or pk_live_\u2026. Fix the variable or unset it to use ~/.pretense/config.json.")
|
|
2022
1941
|
);
|
|
2023
1942
|
process.exit(1);
|
|
2024
1943
|
}
|
|
@@ -2028,34 +1947,32 @@ async function ensureDashboardKeyForStart() {
|
|
|
2028
1947
|
if (fromFile) {
|
|
2029
1948
|
console.warn(
|
|
2030
1949
|
chalk.yellow(
|
|
2031
|
-
"Ignoring invalid `api_key` in ~/.
|
|
1950
|
+
"Ignoring invalid `api_key` in ~/.pretense/config.json (wrong shape). Enter a new key or run `pretense login --key ...`."
|
|
2032
1951
|
)
|
|
2033
1952
|
);
|
|
2034
1953
|
}
|
|
2035
1954
|
const stdinOk = process.stdin.isTTY;
|
|
2036
1955
|
const stdoutOk = process.stdout.isTTY;
|
|
2037
1956
|
if (!stdinOk || !stdoutOk) {
|
|
2038
|
-
console.error(chalk.red("Error:
|
|
1957
|
+
console.error(chalk.red("Error: Pretense API key required to start the proxy."));
|
|
2039
1958
|
console.error(
|
|
2040
|
-
chalk.gray(
|
|
1959
|
+
chalk.gray(
|
|
1960
|
+
"Save one with `pretense login --key prtns_live_...` or set PRETENSE_API_KEY."
|
|
1961
|
+
)
|
|
2041
1962
|
);
|
|
2042
1963
|
process.exit(1);
|
|
2043
1964
|
}
|
|
2044
1965
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2045
1966
|
try {
|
|
2046
|
-
console.log(
|
|
2047
|
-
|
|
2048
|
-
"No dashboard API key found in ~/.pretest/config.json. Set PRETEST_API_KEY or run pretest login."
|
|
2049
|
-
)
|
|
2050
|
-
);
|
|
2051
|
-
const entered = (await rl.question(chalk.bold("Enter your dashboard API key: "))).trim();
|
|
1967
|
+
console.log(chalk.cyan("No Pretense API key found in ~/.pretense/config.json or $PRETENSE_API_KEY."));
|
|
1968
|
+
const entered = (await rl.question(chalk.bold("Enter your Pretense API key: "))).trim();
|
|
2052
1969
|
if (!entered) {
|
|
2053
1970
|
console.error(chalk.red("No key entered; exiting."));
|
|
2054
1971
|
process.exit(1);
|
|
2055
1972
|
}
|
|
2056
1973
|
if (!isValidKeyShape(entered)) {
|
|
2057
|
-
console.error(chalk.red("That does not look like a
|
|
2058
|
-
console.error(chalk.gray(
|
|
1974
|
+
console.error(chalk.red("That does not look like a Pretense API key (expect prtns_live_\u2026 or pk_live_\u2026)."));
|
|
1975
|
+
console.error(chalk.gray("Get one from your Pretense dashboard, then run `pretense login --key ...`."));
|
|
2059
1976
|
process.exit(1);
|
|
2060
1977
|
}
|
|
2061
1978
|
const path = installDashboardKeyForCurrentProcess(entered);
|
|
@@ -2066,29 +1983,29 @@ async function ensureDashboardKeyForStart() {
|
|
|
2066
1983
|
}
|
|
2067
1984
|
const persisted = resolveDashboardKeyCandidate();
|
|
2068
1985
|
if (!persisted.key || persisted.badEnv) {
|
|
2069
|
-
console.error(chalk.red("Error:
|
|
1986
|
+
console.error(chalk.red("Error: Pretense dashboard API key could not be confirmed after login prompt."));
|
|
2070
1987
|
process.exit(1);
|
|
2071
1988
|
}
|
|
2072
1989
|
}
|
|
2073
1990
|
function runLogin(opts = {}) {
|
|
2074
|
-
const candidate = opts.key?.trim() ||
|
|
1991
|
+
const candidate = opts.key?.trim() || (process.env["PRETENSE_API_KEY"] ?? "").trim() || readStdinIfPiped();
|
|
2075
1992
|
if (!candidate) {
|
|
2076
1993
|
console.error(chalk.red("Error: no API key provided."));
|
|
2077
1994
|
console.error(
|
|
2078
|
-
chalk.gray("Try:
|
|
1995
|
+
chalk.gray("Try: pretense login --key pk_live_xxxxx")
|
|
2079
1996
|
);
|
|
2080
1997
|
console.error(
|
|
2081
|
-
chalk.gray(" or: echo $
|
|
1998
|
+
chalk.gray(" or: echo $PRETENSE_API_KEY | pretense login")
|
|
2082
1999
|
);
|
|
2083
2000
|
return 1;
|
|
2084
2001
|
}
|
|
2085
2002
|
if (!isValidKeyShape(candidate)) {
|
|
2086
|
-
console.error(chalk.red("Error: that does not look like a
|
|
2003
|
+
console.error(chalk.red("Error: that does not look like a Pretense API key."));
|
|
2087
2004
|
console.error(
|
|
2088
2005
|
chalk.gray("Keys start with prtns_live_ (or pk_live_) and are at least 24 characters long.")
|
|
2089
2006
|
);
|
|
2090
2007
|
console.error(
|
|
2091
|
-
chalk.gray(
|
|
2008
|
+
chalk.gray("Get one at https://pretense.ai/dashboard/settings/api-keys")
|
|
2092
2009
|
);
|
|
2093
2010
|
return 1;
|
|
2094
2011
|
}
|
|
@@ -2113,10 +2030,10 @@ function usageSummaryFromValidate(v) {
|
|
|
2113
2030
|
keyStatus: "active"
|
|
2114
2031
|
};
|
|
2115
2032
|
}
|
|
2116
|
-
var DEFAULT_PRETENSE_API_URL = "https://
|
|
2033
|
+
var DEFAULT_PRETENSE_API_URL = "https://api.pretense.ai";
|
|
2117
2034
|
var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
|
|
2118
2035
|
function getConfiguredApiRoot(rootOverride) {
|
|
2119
|
-
const raw = (rootOverride
|
|
2036
|
+
const raw = (rootOverride ?? process.env["PRETENSE_API_URL"] ?? DEFAULT_PRETENSE_API_URL).trim();
|
|
2120
2037
|
if (!raw) {
|
|
2121
2038
|
return DEFAULT_PRETENSE_API_URL.replace(/\/$/, "");
|
|
2122
2039
|
}
|
|
@@ -2126,7 +2043,7 @@ function getConfiguredApiRoot(rootOverride) {
|
|
|
2126
2043
|
}
|
|
2127
2044
|
try {
|
|
2128
2045
|
const u = new URL(normalized);
|
|
2129
|
-
const origin = /^pretense\.ai$/i.test(u.hostname) ?
|
|
2046
|
+
const origin = /^pretense\.ai$/i.test(u.hostname) ? "https://api.pretense.ai" : u.origin;
|
|
2130
2047
|
const path = u.pathname === "/" ? "" : u.pathname.replace(/\/$/, "");
|
|
2131
2048
|
return `${origin}${path}`;
|
|
2132
2049
|
} catch {
|
|
@@ -2139,20 +2056,20 @@ function getCliApiUrl(pathWithinCli, rootOverride) {
|
|
|
2139
2056
|
return `${root}/api/cli/${tail}`;
|
|
2140
2057
|
}
|
|
2141
2058
|
function getApiRequestTimeoutMs() {
|
|
2142
|
-
const raw = process.env["
|
|
2059
|
+
const raw = process.env["PRETENSE_API_TIMEOUT_MS"]?.trim();
|
|
2143
2060
|
if (!raw || !/^\d+$/.test(raw)) return DEFAULT_REQUEST_TIMEOUT_MS;
|
|
2144
2061
|
const n = parseInt(raw, 10);
|
|
2145
2062
|
return Math.min(Math.max(n, 3e3), 12e4);
|
|
2146
2063
|
}
|
|
2147
2064
|
function getApiKey() {
|
|
2148
|
-
const fromEnv =
|
|
2065
|
+
const fromEnv = process.env["PRETENSE_API_KEY"]?.trim();
|
|
2149
2066
|
if (fromEnv) return fromEnv;
|
|
2150
2067
|
const fromFile = loadApiKey();
|
|
2151
2068
|
if (!fromFile) return null;
|
|
2152
2069
|
const t = fromFile.trim();
|
|
2153
2070
|
return t.length > 0 ? t : null;
|
|
2154
2071
|
}
|
|
2155
|
-
function
|
|
2072
|
+
function getPretenseApiKey() {
|
|
2156
2073
|
return getApiKey();
|
|
2157
2074
|
}
|
|
2158
2075
|
var cachedValidation = null;
|
|
@@ -2192,7 +2109,7 @@ async function validateKey(opts = {}) {
|
|
|
2192
2109
|
const apiKey = getApiKey();
|
|
2193
2110
|
if (!apiKey) {
|
|
2194
2111
|
if (enforceReachability) {
|
|
2195
|
-
const err = new Error("Missing dashboard API key");
|
|
2112
|
+
const err = new Error("Missing Pretense dashboard API key");
|
|
2196
2113
|
err.code = "MISSING_API_KEY";
|
|
2197
2114
|
cachedValidation = null;
|
|
2198
2115
|
lastValidationSuccessAt = 0;
|
|
@@ -2222,7 +2139,7 @@ async function validateKey(opts = {}) {
|
|
|
2222
2139
|
throw err;
|
|
2223
2140
|
}
|
|
2224
2141
|
if (!resp.ok) {
|
|
2225
|
-
const msg = `
|
|
2142
|
+
const msg = `Pretense API returned HTTP ${resp.status} from ${validateUrl}`;
|
|
2226
2143
|
if (enforceReachability) {
|
|
2227
2144
|
cachedValidation = null;
|
|
2228
2145
|
lastValidationSuccessAt = 0;
|
|
@@ -2259,14 +2176,14 @@ async function validateKey(opts = {}) {
|
|
|
2259
2176
|
throw err;
|
|
2260
2177
|
}
|
|
2261
2178
|
if (enforceReachability) {
|
|
2262
|
-
const msg = err.name === "AbortError" ? `Timed out after ${timeoutMs}ms reaching ${validateUrl} (raise
|
|
2179
|
+
const msg = err.name === "AbortError" ? `Timed out after ${timeoutMs}ms reaching ${validateUrl} (raise PRETENSE_API_TIMEOUT_MS)` : `Cannot reach Pretense API at ${validateUrl}: ${err.message}`;
|
|
2263
2180
|
cachedValidation = null;
|
|
2264
2181
|
lastValidationSuccessAt = 0;
|
|
2265
2182
|
throw makeBackendReachabilityError("BACKEND_UNAVAILABLE", msg);
|
|
2266
2183
|
}
|
|
2267
2184
|
if (opts.verbose) {
|
|
2268
2185
|
process.stderr.write(
|
|
2269
|
-
`[
|
|
2186
|
+
`[PRETENSE] Backend validation failed: ${err.message}
|
|
2270
2187
|
`
|
|
2271
2188
|
);
|
|
2272
2189
|
}
|
|
@@ -2292,7 +2209,7 @@ async function fetchUsageSummary(opts = {}) {
|
|
|
2292
2209
|
} catch (err) {
|
|
2293
2210
|
if (opts.verbose) {
|
|
2294
2211
|
process.stderr.write(
|
|
2295
|
-
`[
|
|
2212
|
+
`[PRETENSE] Usage summary fetch failed: ${err.message}
|
|
2296
2213
|
`
|
|
2297
2214
|
);
|
|
2298
2215
|
}
|
|
@@ -2305,10 +2222,15 @@ async function isWithinLimits() {
|
|
|
2305
2222
|
const v = getCachedValidation();
|
|
2306
2223
|
if (!v) return { allowed: true };
|
|
2307
2224
|
if (v.mutationsRemaining !== -1 && v.mutationsRemaining <= 0) {
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2225
|
+
const fresh = await validateKey({ force: true }).catch(() => null);
|
|
2226
|
+
const check = fresh ?? v;
|
|
2227
|
+
if (check.mutationsRemaining !== -1 && check.mutationsRemaining <= 0) {
|
|
2228
|
+
return {
|
|
2229
|
+
allowed: false,
|
|
2230
|
+
reason: `Monthly mutation limit reached (${check.mutationsUsed}/${check.monthlyMutationLimit}). Upgrade at https://pretense.ai/pricing`
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
return { allowed: true };
|
|
2312
2234
|
}
|
|
2313
2235
|
return { allowed: true };
|
|
2314
2236
|
}
|
|
@@ -2340,13 +2262,13 @@ async function uploadLog(event, options) {
|
|
|
2340
2262
|
signal: controller.signal
|
|
2341
2263
|
});
|
|
2342
2264
|
if (!resp.ok && verbose) {
|
|
2343
|
-
process.stderr.write(`[
|
|
2265
|
+
process.stderr.write(`[PRETENSE] log upload HTTP ${resp.status}
|
|
2344
2266
|
`);
|
|
2345
2267
|
}
|
|
2346
2268
|
} catch (err) {
|
|
2347
2269
|
if (verbose) {
|
|
2348
2270
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2349
|
-
process.stderr.write(`[
|
|
2271
|
+
process.stderr.write(`[PRETENSE] log upload failed: ${msg}
|
|
2350
2272
|
`);
|
|
2351
2273
|
}
|
|
2352
2274
|
} finally {
|
|
@@ -2360,18 +2282,13 @@ function printStartClientInstructions(port) {
|
|
|
2360
2282
|
const host = `http://localhost:${port}`;
|
|
2361
2283
|
console.log();
|
|
2362
2284
|
console.log(chalk2.cyan.bold("\u2500\u2500 Client setup (environment variables) \u2500\u2500"));
|
|
2363
|
-
console.log(chalk2.gray("Point the CLI at the backend API (optional if using default):"));
|
|
2285
|
+
console.log(chalk2.gray("Point the CLI at the Pretense backend API (optional if using default):"));
|
|
2364
2286
|
console.log(
|
|
2365
|
-
chalk2.white(`export
|
|
2287
|
+
chalk2.white(`export PRETENSE_API_URL='https://api.pretense.ai'`)
|
|
2366
2288
|
);
|
|
2367
2289
|
console.log();
|
|
2368
|
-
console.log(chalk2.gray("Log in with your dashboard API key:"));
|
|
2369
|
-
console.log(chalk2.white("
|
|
2370
|
-
console.log();
|
|
2371
|
-
console.log(
|
|
2372
|
-
chalk2.gray("Documentation:"),
|
|
2373
|
-
chalk2.cyan.underline(PUBLISH_PACKAGE_URL)
|
|
2374
|
-
);
|
|
2290
|
+
console.log(chalk2.gray("Log in with your Pretense dashboard API key:"));
|
|
2291
|
+
console.log(chalk2.white("pretense login --key your-pretense-api-key"));
|
|
2375
2292
|
console.log();
|
|
2376
2293
|
console.log(
|
|
2377
2294
|
chalk2.gray("Set the LLM base URL for your provider (proxy below):")
|
|
@@ -2388,15 +2305,15 @@ function printStartClientInstructions(port) {
|
|
|
2388
2305
|
console.log(chalk2.cyan.bold("\u2500\u2500 Stop & log out \u2500\u2500"));
|
|
2389
2306
|
console.log(
|
|
2390
2307
|
chalk2.gray(
|
|
2391
|
-
"Stop the proxy: press Ctrl+C in the terminal where
|
|
2308
|
+
"Stop the proxy: press Ctrl+C in the terminal where pretense start is running."
|
|
2392
2309
|
)
|
|
2393
2310
|
);
|
|
2394
2311
|
console.log(
|
|
2395
2312
|
chalk2.gray(
|
|
2396
|
-
"Remove the saved dashboard key from ~/.
|
|
2313
|
+
"Remove the saved Pretense dashboard key from ~/.pretense/config.json:"
|
|
2397
2314
|
)
|
|
2398
2315
|
);
|
|
2399
|
-
console.log(chalk2.white("
|
|
2316
|
+
console.log(chalk2.white("pretense logout"));
|
|
2400
2317
|
console.log(
|
|
2401
2318
|
chalk2.gray(
|
|
2402
2319
|
"(confirm with y / cancel with n.) Mutations stop immediately unless this proxy was started with"
|
|
@@ -2404,7 +2321,7 @@ function printStartClientInstructions(port) {
|
|
|
2404
2321
|
);
|
|
2405
2322
|
console.log(
|
|
2406
2323
|
chalk2.gray(
|
|
2407
|
-
"
|
|
2324
|
+
"PRETENSE_API_KEY set in that same terminal \u2014 then press Ctrl+C and run pretense start again."
|
|
2408
2325
|
)
|
|
2409
2326
|
);
|
|
2410
2327
|
console.log();
|
|
@@ -2437,11 +2354,11 @@ function attachDashboardLogoutWatcher(enabled) {
|
|
|
2437
2354
|
if (!fk) {
|
|
2438
2355
|
if (dashboardSavedCredentialsRevoked) return;
|
|
2439
2356
|
dashboardSavedCredentialsRevoked = true;
|
|
2440
|
-
|
|
2357
|
+
delete process.env["PRETENSE_API_KEY"];
|
|
2441
2358
|
clearValidationCache();
|
|
2442
2359
|
try {
|
|
2443
2360
|
process.stderr.write(
|
|
2444
|
-
`\x1B[33m[
|
|
2361
|
+
`\x1B[33m[PRETENSE] Saved dashboard API key removed (~/.pretense/config.json, e.g. pretense logout). LLM proxy POST requests are disabled until you run pretense login and restart pretense start.\x1B[0m
|
|
2445
2362
|
`
|
|
2446
2363
|
);
|
|
2447
2364
|
} catch {
|
|
@@ -2458,7 +2375,7 @@ function attachDashboardLogoutWatcher(enabled) {
|
|
|
2458
2375
|
debounceTimer = setTimeout(applyFilesystemCredentialState, 120);
|
|
2459
2376
|
};
|
|
2460
2377
|
try {
|
|
2461
|
-
if (!
|
|
2378
|
+
if (!existsSync6(dir)) return;
|
|
2462
2379
|
watch(dir, () => {
|
|
2463
2380
|
schedule();
|
|
2464
2381
|
});
|
|
@@ -2567,8 +2484,8 @@ function daysSinceFirstUse() {
|
|
|
2567
2484
|
function printUpgradeNudge(reason) {
|
|
2568
2485
|
process.stdout.write(
|
|
2569
2486
|
`
|
|
2570
|
-
\x1B[33m[
|
|
2571
|
-
\x1B[33m[
|
|
2487
|
+
\x1B[33m[PRETENSE] ${reason}\x1B[0m
|
|
2488
|
+
\x1B[33m[PRETENSE] Upgrade to Pro: https://pretense.ai/pricing\x1B[0m
|
|
2572
2489
|
|
|
2573
2490
|
`
|
|
2574
2491
|
);
|
|
@@ -2582,7 +2499,7 @@ function createProxy(opts = {}) {
|
|
|
2582
2499
|
app.get(
|
|
2583
2500
|
"/",
|
|
2584
2501
|
(c) => c.json({
|
|
2585
|
-
name: "
|
|
2502
|
+
name: "pretense",
|
|
2586
2503
|
version: getCliSemver(),
|
|
2587
2504
|
status: "running",
|
|
2588
2505
|
routes: {
|
|
@@ -2593,7 +2510,7 @@ function createProxy(opts = {}) {
|
|
|
2593
2510
|
"POST /*": "Proxy to upstream LLM API (Anthropic, OpenAI, Google auto-detected)"
|
|
2594
2511
|
},
|
|
2595
2512
|
languages: ["TypeScript", "JavaScript", "Python", "Go", "Java", "C#", "Ruby", "Rust"],
|
|
2596
|
-
docs:
|
|
2513
|
+
docs: "https://pretense.ai/docs"
|
|
2597
2514
|
})
|
|
2598
2515
|
);
|
|
2599
2516
|
app.get(
|
|
@@ -2652,19 +2569,19 @@ function createProxy(opts = {}) {
|
|
|
2652
2569
|
{
|
|
2653
2570
|
error: {
|
|
2654
2571
|
type: "pretense_session_logged_out",
|
|
2655
|
-
message: "Saved dashboard credentials were removed (for example
|
|
2572
|
+
message: "Saved Pretense dashboard credentials were removed (for example pretense logout). Run pretense login, then restart pretense start. If you started the proxy with PRETENSE_API_KEY in this shell, stop it (Ctrl+C) and start again after logout."
|
|
2656
2573
|
}
|
|
2657
2574
|
},
|
|
2658
2575
|
401
|
|
2659
2576
|
);
|
|
2660
2577
|
}
|
|
2661
|
-
const
|
|
2662
|
-
if (!
|
|
2578
|
+
const pretenseDashboardKey = getPretenseApiKey();
|
|
2579
|
+
if (!pretenseDashboardKey) {
|
|
2663
2580
|
return c.json(
|
|
2664
2581
|
{
|
|
2665
2582
|
error: {
|
|
2666
2583
|
type: "pretense_dashboard_key_required",
|
|
2667
|
-
message: "Configure your dashboard API key: run `
|
|
2584
|
+
message: "Configure your Pretense dashboard API key: run `pretense login --key prtns_live_...` or set PRETENSE_API_KEY."
|
|
2668
2585
|
}
|
|
2669
2586
|
},
|
|
2670
2587
|
401
|
|
@@ -2717,7 +2634,7 @@ function createProxy(opts = {}) {
|
|
|
2717
2634
|
{
|
|
2718
2635
|
error: {
|
|
2719
2636
|
type: "pretense_rate_limit",
|
|
2720
|
-
message: `Monthly mutation limit reached (${monthlyMutations}/month). Upgrade to Pro for unlimited:
|
|
2637
|
+
message: `Monthly mutation limit reached (${monthlyMutations}/month). Upgrade to Pro for unlimited: https://pretense.ai/pricing`
|
|
2721
2638
|
}
|
|
2722
2639
|
},
|
|
2723
2640
|
429
|
|
@@ -2741,7 +2658,7 @@ function createProxy(opts = {}) {
|
|
|
2741
2658
|
{
|
|
2742
2659
|
error: {
|
|
2743
2660
|
type: "pretense_forbidden",
|
|
2744
|
-
message: "This API key is read-only. Create a read_write key in the dashboard to use the
|
|
2661
|
+
message: "This API key is read-only. Create a read_write key in the dashboard to use the Pretense proxy."
|
|
2745
2662
|
}
|
|
2746
2663
|
},
|
|
2747
2664
|
403
|
|
@@ -2797,7 +2714,7 @@ function createProxy(opts = {}) {
|
|
|
2797
2714
|
usage.mutations += tokensMutated;
|
|
2798
2715
|
saveUsage(usage);
|
|
2799
2716
|
const remoteLogDisabled = process.env["PRETENSE_DISABLE_REMOTE_LOG"] === "1";
|
|
2800
|
-
if (
|
|
2717
|
+
if (pretenseDashboardKey && !remoteLogDisabled) {
|
|
2801
2718
|
void uploadLog(
|
|
2802
2719
|
{
|
|
2803
2720
|
file_count: 0,
|
|
@@ -2806,7 +2723,7 @@ function createProxy(opts = {}) {
|
|
|
2806
2723
|
llm_provider: provider,
|
|
2807
2724
|
cli_version: getCliSemver()
|
|
2808
2725
|
},
|
|
2809
|
-
{ apiKey:
|
|
2726
|
+
{ apiKey: pretenseDashboardKey, verbose }
|
|
2810
2727
|
);
|
|
2811
2728
|
bumpCachedUsageAfterLog(tokensMutated);
|
|
2812
2729
|
}
|
|
@@ -2815,7 +2732,7 @@ function createProxy(opts = {}) {
|
|
|
2815
2732
|
printUpgradeNudge(`${usage.mutations}/${monthlyMutations} monthly mutations used. Running low!`);
|
|
2816
2733
|
}
|
|
2817
2734
|
if (tier === "free" && daysSinceFirstUse() >= 7) {
|
|
2818
|
-
printUpgradeNudge("You've been using
|
|
2735
|
+
printUpgradeNudge("You've been using Pretense for 7+ days. Unlock Pro features: unlimited mutations, 90-day audit, Slack alerts.");
|
|
2819
2736
|
}
|
|
2820
2737
|
const entry = {
|
|
2821
2738
|
id: requestId,
|
|
@@ -2832,7 +2749,7 @@ function createProxy(opts = {}) {
|
|
|
2832
2749
|
createAuditEntry(requestId, "proxy-request", tokensMutated, blockedSecrets.length, provider, latencyMs)
|
|
2833
2750
|
);
|
|
2834
2751
|
if (verbose && tokensMutated > 0) {
|
|
2835
|
-
process.stdout.write(`[
|
|
2752
|
+
process.stdout.write(`[PRETENSE] ${tokensMutated} tokens mutated for request ${requestId}
|
|
2836
2753
|
`);
|
|
2837
2754
|
}
|
|
2838
2755
|
const headers = {};
|
|
@@ -2959,12 +2876,12 @@ function startProxy(opts = {}) {
|
|
|
2959
2876
|
const requestedPort = opts.port ?? opts.config?.port ?? DEFAULT_CONFIG.port;
|
|
2960
2877
|
const app = createProxy(opts);
|
|
2961
2878
|
void (async () => {
|
|
2962
|
-
const
|
|
2963
|
-
const dashboardKey =
|
|
2879
|
+
const pretenseDashboardKeyFromEnvAtLaunch = !!process.env["PRETENSE_API_KEY"]?.trim();
|
|
2880
|
+
const dashboardKey = getPretenseApiKey();
|
|
2964
2881
|
if (!dashboardKey || !isValidKeyShape(dashboardKey)) {
|
|
2965
2882
|
process.stderr.write(
|
|
2966
|
-
`\x1B[31m[
|
|
2967
|
-
\x1B[31m[
|
|
2883
|
+
`\x1B[31m[PRETENSE] API key required or invalid shape. Run \`pretense login --key prtns_live_...\`\x1B[0m
|
|
2884
|
+
\x1B[31m[PRETENSE] or set PRETENSE_API_KEY. Get a key at https://pretense.ai/dashboard/settings/api-keys\x1B[0m
|
|
2968
2885
|
`
|
|
2969
2886
|
);
|
|
2970
2887
|
process.exit(1);
|
|
@@ -2979,7 +2896,7 @@ function startProxy(opts = {}) {
|
|
|
2979
2896
|
});
|
|
2980
2897
|
if (!validation) {
|
|
2981
2898
|
process.stderr.write(
|
|
2982
|
-
`\x1B[31m[
|
|
2899
|
+
`\x1B[31m[PRETENSE] Startup validation failed (no API key or unreachable backend). Cannot start proxy.\x1B[0m
|
|
2983
2900
|
`
|
|
2984
2901
|
);
|
|
2985
2902
|
process.exit(1);
|
|
@@ -2987,7 +2904,7 @@ function startProxy(opts = {}) {
|
|
|
2987
2904
|
const plan = validation.plan ?? "FREE";
|
|
2988
2905
|
const remaining = validation.mutationsRemaining === -1 ? "unlimited" : String(validation.mutationsRemaining);
|
|
2989
2906
|
process.stdout.write(
|
|
2990
|
-
`\x1B[32m[
|
|
2907
|
+
`\x1B[32m[PRETENSE] Key validated: ${plan} plan` + (validation.organizationName ? ` (${validation.organizationName})` : "") + ` \u2014 ${remaining} mutations remaining\x1B[0m
|
|
2991
2908
|
`
|
|
2992
2909
|
);
|
|
2993
2910
|
break;
|
|
@@ -2995,62 +2912,62 @@ function startProxy(opts = {}) {
|
|
|
2995
2912
|
const code = err.code;
|
|
2996
2913
|
const msg = err.message;
|
|
2997
2914
|
if (code === "BACKEND_UNAVAILABLE" || code === "AUTH_BACKEND_REJECTED") {
|
|
2998
|
-
process.stderr.write(`\x1B[31m[
|
|
2915
|
+
process.stderr.write(`\x1B[31m[PRETENSE] ${msg}\x1B[0m
|
|
2999
2916
|
`);
|
|
3000
|
-
process.stderr.write(`\x1B[31m[
|
|
2917
|
+
process.stderr.write(`\x1B[31m[PRETENSE] Fix network or set PRETENSE_API_URL if using a custom API.\x1B[0m
|
|
3001
2918
|
`);
|
|
3002
2919
|
process.exit(1);
|
|
3003
2920
|
}
|
|
3004
2921
|
if (code === "ORG_INACTIVE") {
|
|
3005
|
-
process.stderr.write(`\x1B[31m[
|
|
2922
|
+
process.stderr.write(`\x1B[31m[PRETENSE] ${msg}\x1B[0m
|
|
3006
2923
|
`);
|
|
3007
|
-
process.stderr.write(`\x1B[31m[
|
|
2924
|
+
process.stderr.write(`\x1B[31m[PRETENSE] Your organization is inactive; contact support.\x1B[0m
|
|
3008
2925
|
`);
|
|
3009
2926
|
process.exit(1);
|
|
3010
2927
|
}
|
|
3011
2928
|
const authRetryable = code === "INVALID_API_KEY" || code === "KEY_REVOKED" || code === "KEY_EXPIRED" || code === "AUTH_ERROR" || code === "MISSING_API_KEY";
|
|
3012
2929
|
if (authRetryable && interactive) {
|
|
3013
|
-
process.stderr.write(`\x1B[31m[
|
|
2930
|
+
process.stderr.write(`\x1B[31m[PRETENSE] ${msg}\x1B[0m
|
|
3014
2931
|
`);
|
|
3015
2932
|
process.stderr.write(
|
|
3016
|
-
`\x1B[33m[
|
|
2933
|
+
`\x1B[33m[PRETENSE] Enter your Pretense dashboard API key and press Enter (Ctrl+C to quit).\x1B[0m
|
|
3017
2934
|
`
|
|
3018
2935
|
);
|
|
3019
2936
|
let entered = null;
|
|
3020
2937
|
for (; ; ) {
|
|
3021
2938
|
entered = await promptDashboardApiKeyAfterFailure();
|
|
3022
2939
|
if (!entered) {
|
|
3023
|
-
process.stderr.write(`\x1B[31m[
|
|
2940
|
+
process.stderr.write(`\x1B[31m[PRETENSE] No key entered; exiting.\x1B[0m
|
|
3024
2941
|
`);
|
|
3025
2942
|
process.exit(1);
|
|
3026
2943
|
}
|
|
3027
2944
|
if (isValidKeyShape(entered)) break;
|
|
3028
2945
|
process.stderr.write(
|
|
3029
|
-
`\x1B[31m[
|
|
2946
|
+
`\x1B[31m[PRETENSE] That does not look like a Pretense dashboard key (expect prtns_live_\u2026 or pk_live_\u2026).\x1B[0m
|
|
3030
2947
|
`
|
|
3031
2948
|
);
|
|
3032
2949
|
}
|
|
3033
2950
|
const savedPath = installDashboardKeyForCurrentProcess(entered);
|
|
3034
|
-
process.stdout.write(`\x1B[32m[
|
|
2951
|
+
process.stdout.write(`\x1B[32m[PRETENSE] Saved API key to ${savedPath}\x1B[0m
|
|
3035
2952
|
`);
|
|
3036
2953
|
clearValidationCache();
|
|
3037
2954
|
continue;
|
|
3038
2955
|
}
|
|
3039
2956
|
if (code === "KEY_REVOKED" || code === "KEY_EXPIRED" || code === "INVALID_API_KEY" || code === "AUTH_ERROR" || code === "MISSING_API_KEY") {
|
|
3040
|
-
process.stderr.write(`\x1B[31m[
|
|
2957
|
+
process.stderr.write(`\x1B[31m[PRETENSE] ${msg}\x1B[0m
|
|
3041
2958
|
`);
|
|
3042
|
-
process.stderr.write(`\x1B[31m[
|
|
2959
|
+
process.stderr.write(`\x1B[31m[PRETENSE] Run 'pretense login --key <new-key>' to fix.\x1B[0m
|
|
3043
2960
|
`);
|
|
3044
2961
|
process.exit(1);
|
|
3045
2962
|
}
|
|
3046
|
-
process.stderr.write(`\x1B[31m[
|
|
2963
|
+
process.stderr.write(`\x1B[31m[PRETENSE] ${msg}\x1B[0m
|
|
3047
2964
|
`);
|
|
3048
|
-
process.stderr.write(`\x1B[31m[
|
|
2965
|
+
process.stderr.write(`\x1B[31m[PRETENSE] Run 'pretense login --key <new-key>' or check connectivity.\x1B[0m
|
|
3049
2966
|
`);
|
|
3050
2967
|
process.exit(1);
|
|
3051
2968
|
}
|
|
3052
2969
|
}
|
|
3053
|
-
attachDashboardLogoutWatcher(!
|
|
2970
|
+
attachDashboardLogoutWatcher(!pretenseDashboardKeyFromEnvAtLaunch);
|
|
3054
2971
|
const bannerTier = tierFromDashboardPlan(
|
|
3055
2972
|
getCachedValidation()?.plan,
|
|
3056
2973
|
detectTier()
|
|
@@ -3059,13 +2976,13 @@ function startProxy(opts = {}) {
|
|
|
3059
2976
|
try {
|
|
3060
2977
|
port = await findAvailablePort(requestedPort, 10);
|
|
3061
2978
|
} catch (err) {
|
|
3062
|
-
process.stderr.write(`\x1B[31m[
|
|
2979
|
+
process.stderr.write(`\x1B[31m[PRETENSE] ${err.message}\x1B[0m
|
|
3063
2980
|
`);
|
|
3064
2981
|
process.exit(1);
|
|
3065
2982
|
}
|
|
3066
2983
|
if (port !== requestedPort) {
|
|
3067
2984
|
process.stderr.write(
|
|
3068
|
-
`\x1B[33m[
|
|
2985
|
+
`\x1B[33m[PRETENSE] Port ${requestedPort} in use, falling back to ${port}.\x1B[0m
|
|
3069
2986
|
`
|
|
3070
2987
|
);
|
|
3071
2988
|
}
|
|
@@ -3073,7 +2990,7 @@ function startProxy(opts = {}) {
|
|
|
3073
2990
|
serve({ fetch: app.fetch, port }, () => {
|
|
3074
2991
|
const portStr = String(port);
|
|
3075
2992
|
process.stdout.write(`
|
|
3076
|
-
\x1B[
|
|
2993
|
+
\x1B[36mPretense v${getCliSemver()} [${bannerTier.toUpperCase()}]\x1B[0m
|
|
3077
2994
|
Your code hits AI naked. We fix that.
|
|
3078
2995
|
|
|
3079
2996
|
Proxy: http://localhost:${portStr}
|
|
@@ -3095,7 +3012,7 @@ function startProxy(opts = {}) {
|
|
|
3095
3012
|
`);
|
|
3096
3013
|
setInterval(() => {
|
|
3097
3014
|
if (dashboardSavedCredentialsRevoked) return;
|
|
3098
|
-
if (!
|
|
3015
|
+
if (!getPretenseApiKey()) return;
|
|
3099
3016
|
void validateKey({
|
|
3100
3017
|
force: true,
|
|
3101
3018
|
verbose: opts.verbose,
|
|
@@ -3151,13 +3068,13 @@ function printTokenFooter(filesScanned, mutationsMade) {
|
|
|
3151
3068
|
if (tier === "free") {
|
|
3152
3069
|
if (pct >= 80) {
|
|
3153
3070
|
process.stderr.write(
|
|
3154
|
-
chalk3.yellow(` \u26A0 Approaching free-tier limit. Upgrade: ${chalk3.bold("
|
|
3071
|
+
chalk3.yellow(` \u26A0 Approaching free-tier limit. Upgrade: ${chalk3.bold("pretense upgrade")}
|
|
3155
3072
|
|
|
3156
3073
|
`)
|
|
3157
3074
|
);
|
|
3158
3075
|
} else {
|
|
3159
3076
|
process.stderr.write(
|
|
3160
|
-
chalk3.gray(` Run ${chalk3.bold("
|
|
3077
|
+
chalk3.gray(` Run ${chalk3.bold("pretense status")} for details, ${chalk3.bold("pretense upgrade")} to remove limits.
|
|
3161
3078
|
|
|
3162
3079
|
`)
|
|
3163
3080
|
);
|
|
@@ -3169,19 +3086,19 @@ function printTokenFooter(filesScanned, mutationsMade) {
|
|
|
3169
3086
|
|
|
3170
3087
|
// src/banner.ts
|
|
3171
3088
|
import chalk4 from "chalk";
|
|
3172
|
-
var BANNER_RAW = `\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3089
|
+
var BANNER_RAW = `\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 pretense
|
|
3173
3090
|
\u2588\u2588 \u2588\u2588
|
|
3174
3091
|
\u2588\u2588 \u2588\u2588 Stop your code from
|
|
3175
3092
|
\u2588\u2588 \u2588\u2588 leaking to any LLM.
|
|
3176
3093
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3177
|
-
\u2588\u2588
|
|
3094
|
+
\u2588\u2588 pretense.ai
|
|
3178
3095
|
\u2588\u2588
|
|
3179
3096
|
\u2588\u2588
|
|
3180
3097
|
\u2588\u2588
|
|
3181
3098
|
\u2588\u2588`;
|
|
3182
3099
|
var ICE_BLUE = "#7DD3FC";
|
|
3183
3100
|
function shouldPrint() {
|
|
3184
|
-
if (
|
|
3101
|
+
if (process.env.PRETENSE_NO_BANNER === "1") return false;
|
|
3185
3102
|
if (!process.stdout.isTTY) return false;
|
|
3186
3103
|
if (process.env.CI) return false;
|
|
3187
3104
|
return true;
|
|
@@ -3226,7 +3143,7 @@ async function runStatus() {
|
|
|
3226
3143
|
const tierBadge2 = plan === "ENTERPRISE" ? chalk5.bgMagenta.white.bold(" ENTERPRISE ") : plan === "PRO" ? chalk5.bgBlue.white.bold(" PRO ") : chalk5.bgGray.white.bold(" FREE ");
|
|
3227
3144
|
const lines2 = [];
|
|
3228
3145
|
lines2.push("");
|
|
3229
|
-
lines2.push(`${chalk5.cyan("
|
|
3146
|
+
lines2.push(`${chalk5.cyan("Pretense")} ${chalk5.gray(`v${getCliSemver()}`)} ${tierBadge2} ${chalk5.green("(synced)")}`);
|
|
3230
3147
|
lines2.push("");
|
|
3231
3148
|
lines2.push(` Plan: ${chalk5.bold(plan)}`);
|
|
3232
3149
|
lines2.push(` Mutations: ${fmt2(remote.mutationsUsed)} / ${fmt2(remote.monthlyMutationLimit)} this month`);
|
|
@@ -3240,7 +3157,7 @@ async function runStatus() {
|
|
|
3240
3157
|
}
|
|
3241
3158
|
lines2.push("");
|
|
3242
3159
|
if (plan === "FREE") {
|
|
3243
|
-
lines2.push(` ${chalk5.gray("Upgrade:")} ${chalk5.bold("
|
|
3160
|
+
lines2.push(` ${chalk5.gray("Upgrade:")} ${chalk5.bold("pretense upgrade")}`);
|
|
3244
3161
|
lines2.push("");
|
|
3245
3162
|
}
|
|
3246
3163
|
process.stdout.write(lines2.join("\n") + "\n");
|
|
@@ -3253,7 +3170,7 @@ async function runStatus() {
|
|
|
3253
3170
|
const tierBadge = tier === "enterprise" ? chalk5.bgMagenta.white.bold(" ENTERPRISE ") : tier === "pro" ? chalk5.bgBlue.white.bold(" PRO ") : chalk5.bgGray.white.bold(" FREE ");
|
|
3254
3171
|
const lines = [];
|
|
3255
3172
|
lines.push("");
|
|
3256
|
-
lines.push(`${chalk5.cyan("
|
|
3173
|
+
lines.push(`${chalk5.cyan("Pretense")} ${chalk5.gray(`v${getCliSemver()}`)} ${tierBadge} ${chalk5.yellow("(offline)")}`);
|
|
3257
3174
|
lines.push("");
|
|
3258
3175
|
lines.push(` Plan: ${chalk5.bold(PLAN_LABEL[tier])} ${chalk5.gray("(" + planLimits.pricePerMonth + ")")}`);
|
|
3259
3176
|
lines.push(` Mutations: ${fmt2(usage.mutations)} / ${fmt2(planLimits.monthlyMutations)} this month`);
|
|
@@ -3262,8 +3179,8 @@ async function runStatus() {
|
|
|
3262
3179
|
lines.push(` Audit log: ${Number.isFinite(tierLimits.auditRetentionDays) ? `${tierLimits.auditRetentionDays} days` : "unlimited"}`);
|
|
3263
3180
|
lines.push("");
|
|
3264
3181
|
if (tier === "free") {
|
|
3265
|
-
lines.push(` ${chalk5.gray("Upgrade:")} ${chalk5.bold("
|
|
3266
|
-
lines.push(` ${chalk5.gray("Credits:")} ${chalk5.bold("
|
|
3182
|
+
lines.push(` ${chalk5.gray("Upgrade:")} ${chalk5.bold("pretense upgrade")}`);
|
|
3183
|
+
lines.push(` ${chalk5.gray("Credits:")} ${chalk5.bold("pretense credits")}`);
|
|
3267
3184
|
lines.push("");
|
|
3268
3185
|
}
|
|
3269
3186
|
process.stdout.write(lines.join("\n") + "\n");
|
|
@@ -3271,12 +3188,12 @@ async function runStatus() {
|
|
|
3271
3188
|
|
|
3272
3189
|
// src/commands/upgrade.ts
|
|
3273
3190
|
import chalk6 from "chalk";
|
|
3274
|
-
var PRICING_URL =
|
|
3191
|
+
var PRICING_URL = "https://pretense.ai/pricing";
|
|
3275
3192
|
function runUpgrade() {
|
|
3276
3193
|
const tier = detectTier();
|
|
3277
3194
|
const lines = [];
|
|
3278
3195
|
lines.push("");
|
|
3279
|
-
lines.push(chalk6.cyan.bold("
|
|
3196
|
+
lines.push(chalk6.cyan.bold(" Pretense Plans"));
|
|
3280
3197
|
lines.push("");
|
|
3281
3198
|
lines.push(chalk6.gray(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
3282
3199
|
lines.push(chalk6.gray(" \u2502 \u2502 Free \u2502 Pro \u2502 Enterprise \u2502"));
|
|
@@ -3295,7 +3212,7 @@ function runUpgrade() {
|
|
|
3295
3212
|
lines.push(` ${chalk6.gray("Upgrade at:")} ${chalk6.cyan.underline(PRICING_URL)}`);
|
|
3296
3213
|
lines.push("");
|
|
3297
3214
|
lines.push(chalk6.gray(" After purchase, set:"));
|
|
3298
|
-
lines.push(chalk6.gray(" export
|
|
3215
|
+
lines.push(chalk6.gray(" export PRETENSE_LICENSE_KEY=pre_pro_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
|
|
3299
3216
|
lines.push("");
|
|
3300
3217
|
process.stdout.write(lines.join("\n") + "\n");
|
|
3301
3218
|
}
|
|
@@ -3334,7 +3251,7 @@ async function runCredits() {
|
|
|
3334
3251
|
const pct2 = cap2 > 0 && cap2 !== -1 ? Math.round(used2 / cap2 * 100) : 0;
|
|
3335
3252
|
const lines2 = [];
|
|
3336
3253
|
lines2.push("");
|
|
3337
|
-
lines2.push(chalk7.cyan.bold("
|
|
3254
|
+
lines2.push(chalk7.cyan.bold(" Pretense Credits") + " " + chalk7.green("(synced)"));
|
|
3338
3255
|
lines2.push("");
|
|
3339
3256
|
lines2.push(` Plan: ${chalk7.bold(remote.plan)}`);
|
|
3340
3257
|
lines2.push(` Resets: ${new Date(remote.periodResetsAt).toLocaleDateString()}`);
|
|
@@ -3344,10 +3261,10 @@ async function runCredits() {
|
|
|
3344
3261
|
lines2.push(` Remaining: ${fmt3(remaining2)}`);
|
|
3345
3262
|
lines2.push("");
|
|
3346
3263
|
if (remote.plan === "FREE" && cap2 !== -1 && used2 >= cap2 * 0.8) {
|
|
3347
|
-
lines2.push(chalk7.yellow(" Warning: Approaching free-tier limit. Run '
|
|
3264
|
+
lines2.push(chalk7.yellow(" Warning: Approaching free-tier limit. Run 'pretense upgrade' for unlimited."));
|
|
3348
3265
|
lines2.push("");
|
|
3349
3266
|
} else if (remote.plan === "FREE") {
|
|
3350
|
-
lines2.push(chalk7.gray(" Tip: Run '
|
|
3267
|
+
lines2.push(chalk7.gray(" Tip: Run 'pretense upgrade' to compare plans."));
|
|
3351
3268
|
lines2.push("");
|
|
3352
3269
|
}
|
|
3353
3270
|
process.stdout.write(lines2.join("\n") + "\n");
|
|
@@ -3362,7 +3279,7 @@ async function runCredits() {
|
|
|
3362
3279
|
const pct = Number.isFinite(cap) && cap > 0 ? Math.round(used / cap * 100) : 0;
|
|
3363
3280
|
const lines = [];
|
|
3364
3281
|
lines.push("");
|
|
3365
|
-
lines.push(chalk7.cyan.bold("
|
|
3282
|
+
lines.push(chalk7.cyan.bold(" Pretense Credits") + " " + chalk7.yellow("(offline)"));
|
|
3366
3283
|
lines.push("");
|
|
3367
3284
|
lines.push(` Plan: ${chalk7.bold(PLAN_LABEL2[tier])}`);
|
|
3368
3285
|
lines.push(` Month: ${usage.month}`);
|
|
@@ -3372,10 +3289,10 @@ async function runCredits() {
|
|
|
3372
3289
|
lines.push(` Remaining: ${fmt3(remaining)}`);
|
|
3373
3290
|
lines.push("");
|
|
3374
3291
|
if (tier === "free" && Number.isFinite(cap) && used >= cap * 0.8) {
|
|
3375
|
-
lines.push(chalk7.yellow(" Warning: Approaching free-tier limit. Run '
|
|
3292
|
+
lines.push(chalk7.yellow(" Warning: Approaching free-tier limit. Run 'pretense upgrade' for unlimited."));
|
|
3376
3293
|
lines.push("");
|
|
3377
3294
|
} else if (tier === "free") {
|
|
3378
|
-
lines.push(chalk7.gray(" Tip: Run '
|
|
3295
|
+
lines.push(chalk7.gray(" Tip: Run 'pretense upgrade' to compare plans."));
|
|
3379
3296
|
lines.push("");
|
|
3380
3297
|
}
|
|
3381
3298
|
process.stdout.write(lines.join("\n") + "\n");
|
|
@@ -3386,16 +3303,14 @@ import readline2 from "readline/promises";
|
|
|
3386
3303
|
import chalk8 from "chalk";
|
|
3387
3304
|
async function runLogout(opts = {}) {
|
|
3388
3305
|
if (!loadApiKey()) {
|
|
3389
|
-
console.log(chalk8.gray("No saved dashboard API key in ~/.
|
|
3390
|
-
console.log(
|
|
3391
|
-
chalk8.gray("If you use PRETEST_API_KEY in your shell, run unset PRETEST_API_KEY.")
|
|
3392
|
-
);
|
|
3306
|
+
console.log(chalk8.gray("No saved Pretense dashboard API key in ~/.pretense/config.json."));
|
|
3307
|
+
console.log(chalk8.gray("If you use PRETENSE_API_KEY in your shell, run unset PRETENSE_API_KEY."));
|
|
3393
3308
|
return 0;
|
|
3394
3309
|
}
|
|
3395
3310
|
if (!opts.assumeYes) {
|
|
3396
3311
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3397
3312
|
console.error(chalk8.red("Error: confirmation requires an interactive terminal."));
|
|
3398
|
-
console.error(chalk8.gray("Run `
|
|
3313
|
+
console.error(chalk8.gray("Run `pretense logout --yes` to skip the prompt."));
|
|
3399
3314
|
return 1;
|
|
3400
3315
|
}
|
|
3401
3316
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -3412,19 +3327,17 @@ async function runLogout(opts = {}) {
|
|
|
3412
3327
|
rl.close();
|
|
3413
3328
|
}
|
|
3414
3329
|
}
|
|
3415
|
-
const hadEnvDashboardKey =
|
|
3330
|
+
const hadEnvDashboardKey = !!process.env["PRETENSE_API_KEY"]?.trim();
|
|
3416
3331
|
const { removed, path } = removeSavedDashboardCredentials();
|
|
3417
3332
|
clearValidationCache();
|
|
3418
|
-
|
|
3333
|
+
delete process.env["PRETENSE_API_KEY"];
|
|
3419
3334
|
if (removed) {
|
|
3420
3335
|
console.log(chalk8.green("Logged out. Removed saved API key from"), chalk8.bold(path));
|
|
3421
|
-
console.log(
|
|
3422
|
-
chalk8.gray("Other terminals: run unset PRETEST_API_KEY if that variable is still exported (e.g. in ~/.zshrc).")
|
|
3423
|
-
);
|
|
3336
|
+
console.log(chalk8.gray("Other terminals: run unset PRETENSE_API_KEY if that variable is still exported (e.g. in ~/.zshrc)."));
|
|
3424
3337
|
if (hadEnvDashboardKey) {
|
|
3425
3338
|
console.log(
|
|
3426
3339
|
chalk8.yellow(
|
|
3427
|
-
"This shell had
|
|
3340
|
+
"This shell had PRETENSE_API_KEY set; it was cleared for this process only. Restart any running pretense proxy (Ctrl+C, then pretense start)."
|
|
3428
3341
|
)
|
|
3429
3342
|
);
|
|
3430
3343
|
}
|
|
@@ -3437,7 +3350,7 @@ async function runLogout(opts = {}) {
|
|
|
3437
3350
|
const major = parseInt(process.versions.node.split(".")[0], 10);
|
|
3438
3351
|
if (Number.isNaN(major) || major < 18) {
|
|
3439
3352
|
process.stderr.write(
|
|
3440
|
-
`
|
|
3353
|
+
`pretense requires Node.js 18 or newer (you have v${process.versions.node}).
|
|
3441
3354
|
`
|
|
3442
3355
|
);
|
|
3443
3356
|
process.stderr.write(`Upgrade via nvm: nvm install 20 && nvm use 20
|
|
@@ -3569,7 +3482,7 @@ function validateLanguage(lang) {
|
|
|
3569
3482
|
}
|
|
3570
3483
|
function collectFiles(target) {
|
|
3571
3484
|
const abs = resolve2(target);
|
|
3572
|
-
if (!
|
|
3485
|
+
if (!existsSync7(abs)) {
|
|
3573
3486
|
return [];
|
|
3574
3487
|
}
|
|
3575
3488
|
const stat = statSync(abs);
|
|
@@ -3587,14 +3500,13 @@ function collectFiles(target) {
|
|
|
3587
3500
|
".git",
|
|
3588
3501
|
"dist",
|
|
3589
3502
|
"build",
|
|
3590
|
-
".pretest",
|
|
3591
3503
|
".pretense",
|
|
3592
3504
|
".next",
|
|
3593
3505
|
"__pycache__",
|
|
3594
3506
|
".Trash"
|
|
3595
3507
|
]);
|
|
3596
3508
|
for (const entry of entries) {
|
|
3597
|
-
const fullPath =
|
|
3509
|
+
const fullPath = join5(dir, entry.name);
|
|
3598
3510
|
if (entry.isDirectory()) {
|
|
3599
3511
|
if (skipDirs.has(entry.name)) continue;
|
|
3600
3512
|
walk(fullPath);
|
|
@@ -3607,12 +3519,12 @@ function collectFiles(target) {
|
|
|
3607
3519
|
return files;
|
|
3608
3520
|
}
|
|
3609
3521
|
var program = new Command();
|
|
3610
|
-
program.name("
|
|
3522
|
+
program.name("pretense").description("AI firewall CLI \u2014 mutates proprietary code before LLM API calls").version(VERSION).hook("preAction", () => {
|
|
3611
3523
|
printBanner();
|
|
3612
3524
|
});
|
|
3613
|
-
program.command("init").description("Initialize .
|
|
3525
|
+
program.command("init").description("Initialize .pretense/ config in current directory").option("-d, --dir <path>", "Target directory", process.cwd()).action((opts) => {
|
|
3614
3526
|
const dir = resolve2(opts.dir);
|
|
3615
|
-
console.log(chalk9.cyan("Initializing
|
|
3527
|
+
console.log(chalk9.cyan("Initializing Pretense in"), chalk9.bold(dir));
|
|
3616
3528
|
const configDir = initConfig(dir);
|
|
3617
3529
|
const files = collectFiles(dir);
|
|
3618
3530
|
const langCounts = {};
|
|
@@ -3620,16 +3532,16 @@ program.command("init").description("Initialize .pretest/ config in current dire
|
|
|
3620
3532
|
const lang = langFromExt(f);
|
|
3621
3533
|
langCounts[lang] = (langCounts[lang] ?? 0) + 1;
|
|
3622
3534
|
}
|
|
3623
|
-
console.log(chalk9.green("\n .
|
|
3535
|
+
console.log(chalk9.green("\n .pretense/ created at"), chalk9.bold(configDir));
|
|
3624
3536
|
console.log(chalk9.gray(`
|
|
3625
3537
|
Scanned ${files.length} source files:`));
|
|
3626
3538
|
for (const [lang, count] of Object.entries(langCounts)) {
|
|
3627
3539
|
console.log(chalk9.gray(` ${lang}: ${count} files`));
|
|
3628
3540
|
}
|
|
3629
|
-
console.log(chalk9.cyan("\n Next: run"), chalk9.bold("
|
|
3541
|
+
console.log(chalk9.cyan("\n Next: run"), chalk9.bold("pretense start"), chalk9.cyan("to launch the proxy"));
|
|
3630
3542
|
console.log();
|
|
3631
3543
|
});
|
|
3632
|
-
program.command("start").description("Start the
|
|
3544
|
+
program.command("start").description("Start the Pretense proxy server").option("-p, --port <number>", "Port number", "9339").option("-v, --verbose", "Enable verbose logging", false).action(async (opts) => {
|
|
3633
3545
|
const config = loadConfig();
|
|
3634
3546
|
const port = validatePort(opts.port);
|
|
3635
3547
|
if (port === null) {
|
|
@@ -3748,7 +3660,7 @@ function parseSizeOption(value) {
|
|
|
3748
3660
|
}
|
|
3749
3661
|
program.command("scan <target>").description("Scan file or directory for identifiers and secrets (no mutation)").option("--secrets-only", "Only scan for secrets/credentials", false).option("--json", "Output as JSON", false).option("--max-file-size <size>", "Skip files larger than this (e.g. 10mb, 500k)", "10mb").option("--concurrency <n>", "Max files scanned in parallel", String(SCAN_CONCURRENCY)).option("--quiet", "Suppress progress output", false).action(async (target, opts) => {
|
|
3750
3662
|
const absTarget = resolve2(target);
|
|
3751
|
-
if (!
|
|
3663
|
+
if (!existsSync7(absTarget)) {
|
|
3752
3664
|
console.error(chalk9.red("Error: Path not found:"), chalk9.bold(absTarget));
|
|
3753
3665
|
process.exit(1);
|
|
3754
3666
|
}
|
|
@@ -3796,7 +3708,7 @@ program.command("scan <target>").description("Scan file or directory for identif
|
|
|
3796
3708
|
interrupted
|
|
3797
3709
|
}, null, 2));
|
|
3798
3710
|
} else {
|
|
3799
|
-
console.log(chalk9.cyan("\
|
|
3711
|
+
console.log(chalk9.cyan("\nPretense Scan Results\n"));
|
|
3800
3712
|
for (const r of allResults) {
|
|
3801
3713
|
if (r.skipped) {
|
|
3802
3714
|
if (r.skipped === "too-large") {
|
|
@@ -3837,9 +3749,9 @@ program.command("scan <target>").description("Scan file or directory for identif
|
|
|
3837
3749
|
process.exit(2);
|
|
3838
3750
|
}
|
|
3839
3751
|
});
|
|
3840
|
-
program.command("mutate <file>").description("Mutate identifiers for stdout (scanner redacts secrets/PII in the source first)").option("-s, --seed <seed>", "Mutation seed", "
|
|
3752
|
+
program.command("mutate <file>").description("Mutate identifiers for stdout (scanner redacts secrets/PII in the source first)").option("-s, --seed <seed>", "Mutation seed", "pretense").option("-l, --language <lang>", "Source language (typescript, javascript, python, go, java, csharp, ruby, rust)").option("--save", "Save mutation map to .pretense/", false).option("--json", "Output as JSON (includes map + stats)", false).action((file, opts) => {
|
|
3841
3753
|
const absPath = resolve2(file);
|
|
3842
|
-
if (!
|
|
3754
|
+
if (!existsSync7(absPath)) {
|
|
3843
3755
|
console.error(chalk9.red("Error: File not found:"), chalk9.bold(absPath));
|
|
3844
3756
|
process.exit(1);
|
|
3845
3757
|
}
|
|
@@ -3859,13 +3771,13 @@ program.command("mutate <file>").description("Mutate identifiers for stdout (sca
|
|
|
3859
3771
|
const code = readFileSync7(absPath, "utf-8");
|
|
3860
3772
|
const lang = opts.language ? validateLanguage(opts.language) : langFromExt(absPath);
|
|
3861
3773
|
const secretScan = scan2(code);
|
|
3862
|
-
const effectiveSeed =
|
|
3774
|
+
const effectiveSeed = opts.seed === "pretense" ? buildSaltedSeed(opts.seed) : opts.seed;
|
|
3863
3775
|
const { redactedCode: redacted, secretsMap } = applyRedactionsTracked(code, secretScan.matches, effectiveSeed);
|
|
3864
3776
|
const secretsRedacted = secretsMap.size;
|
|
3865
3777
|
const result = mutate(redacted, lang, opts.seed);
|
|
3866
3778
|
if (opts.save) {
|
|
3867
3779
|
const configDir = getConfigDir();
|
|
3868
|
-
const store = new MutationStore(
|
|
3780
|
+
const store = new MutationStore(join5(configDir, "mutation-map.json"));
|
|
3869
3781
|
store.load();
|
|
3870
3782
|
store.save({
|
|
3871
3783
|
id: absPath,
|
|
@@ -3903,15 +3815,15 @@ program.command("mutate <file>").description("Mutate identifiers for stdout (sca
|
|
|
3903
3815
|
});
|
|
3904
3816
|
program.command("reverse <file>").description("Reverse mutations using stored map").option("-m, --map <path>", "Path to mutation map JSON file").action((file, opts) => {
|
|
3905
3817
|
const absPath = resolve2(file);
|
|
3906
|
-
if (!
|
|
3818
|
+
if (!existsSync7(absPath)) {
|
|
3907
3819
|
console.error(chalk9.red("Error: File not found:"), chalk9.bold(absPath));
|
|
3908
3820
|
process.exit(1);
|
|
3909
3821
|
}
|
|
3910
3822
|
const mutatedCode = readFileSync7(absPath, "utf-8");
|
|
3911
|
-
const mapPath = opts.map ? resolve2(opts.map) :
|
|
3912
|
-
if (!
|
|
3823
|
+
const mapPath = opts.map ? resolve2(opts.map) : join5(getConfigDir(), "mutation-map.json");
|
|
3824
|
+
if (!existsSync7(mapPath)) {
|
|
3913
3825
|
console.error(chalk9.red("Error: Mutation map not found:"), chalk9.bold(mapPath));
|
|
3914
|
-
console.error(chalk9.gray("Run '
|
|
3826
|
+
console.error(chalk9.gray("Run 'pretense mutate --save' first, or specify --map <path>"));
|
|
3915
3827
|
process.exit(1);
|
|
3916
3828
|
}
|
|
3917
3829
|
let store;
|
|
@@ -3920,13 +3832,13 @@ program.command("reverse <file>").description("Reverse mutations using stored ma
|
|
|
3920
3832
|
store.load();
|
|
3921
3833
|
} catch {
|
|
3922
3834
|
console.error(chalk9.red("Error: Failed to load mutation map:"), chalk9.bold(mapPath));
|
|
3923
|
-
console.error(chalk9.gray("The file may be corrupted. Run '
|
|
3835
|
+
console.error(chalk9.gray("The file may be corrupted. Run 'pretense mutate --save' to regenerate."));
|
|
3924
3836
|
process.exit(1);
|
|
3925
3837
|
}
|
|
3926
3838
|
const entry = store.get(absPath) ?? store.getByHash(absPath) ?? store.list(1)[0];
|
|
3927
3839
|
if (!entry) {
|
|
3928
3840
|
console.error(chalk9.red("Error: No mutation map entries found."));
|
|
3929
|
-
console.error(chalk9.gray("Run '
|
|
3841
|
+
console.error(chalk9.gray("Run 'pretense mutate --save <file>' first to generate a map."));
|
|
3930
3842
|
process.exit(1);
|
|
3931
3843
|
}
|
|
3932
3844
|
const map = MutationStore.toMap(entry);
|
|
@@ -3946,16 +3858,16 @@ program.command("audit").description("View the audit log").option("--json", "Out
|
|
|
3946
3858
|
const output = formatAudit(entries, format);
|
|
3947
3859
|
console.log(output);
|
|
3948
3860
|
});
|
|
3949
|
-
program.command("version").description("Print
|
|
3950
|
-
console.log(`@
|
|
3861
|
+
program.command("version").description("Print Pretense CLI version").action(() => {
|
|
3862
|
+
console.log(`@pretense/cli v${VERSION}`);
|
|
3951
3863
|
});
|
|
3952
3864
|
program.command("status").description("Show current plan, monthly quota, seat usage").action(async () => {
|
|
3953
3865
|
await runStatus();
|
|
3954
3866
|
});
|
|
3955
|
-
program.command("usage").description("Alias for `
|
|
3867
|
+
program.command("usage").description("Alias for `pretense status` \u2014 show monthly quota usage").action(async () => {
|
|
3956
3868
|
await runStatus();
|
|
3957
3869
|
});
|
|
3958
|
-
program.command("tokens").description("Alias for `
|
|
3870
|
+
program.command("tokens").description("Alias for `pretense credits` \u2014 show remaining mutation budget").action(async () => {
|
|
3959
3871
|
await runCredits();
|
|
3960
3872
|
});
|
|
3961
3873
|
program.command("upgrade").description("Compare plans and upgrade to Pro or Enterprise").action(() => {
|
|
@@ -3964,11 +3876,11 @@ program.command("upgrade").description("Compare plans and upgrade to Pro or Ente
|
|
|
3964
3876
|
program.command("credits").description("Show remaining mutation/scan budget for the current month").action(async () => {
|
|
3965
3877
|
await runCredits();
|
|
3966
3878
|
});
|
|
3967
|
-
program.command("login").description("Save a
|
|
3879
|
+
program.command("login").description("Save a Pretense API key to ~/.pretense/config.json for future CLI runs").option("-k, --key <key>", "API key (prtns_live_... or pk_live_...). If omitted, read from $PRETENSE_API_KEY or stdin.").action((opts) => {
|
|
3968
3880
|
const code = runLogin({ key: opts.key });
|
|
3969
3881
|
if (code !== 0) process.exit(code);
|
|
3970
3882
|
});
|
|
3971
|
-
program.command("logout").description("Remove the saved dashboard API key from ~/.
|
|
3883
|
+
program.command("logout").description("Remove the saved Pretense dashboard API key from ~/.pretense/config.json").option("-y, --yes", "Skip confirmation prompt", false).action(async (opts) => {
|
|
3972
3884
|
const code = await runLogout({ assumeYes: opts.yes === true });
|
|
3973
3885
|
if (code !== 0) process.exit(code);
|
|
3974
3886
|
});
|