@vibedrift/cli 0.2.0 → 0.3.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/dist/index.js +509 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1199,6 +1199,95 @@ var init_confidence = __esm({
|
|
|
1199
1199
|
}
|
|
1200
1200
|
});
|
|
1201
1201
|
|
|
1202
|
+
// src/ml-client/project-name.ts
|
|
1203
|
+
var project_name_exports = {};
|
|
1204
|
+
__export(project_name_exports, {
|
|
1205
|
+
detectProjectIdentity: () => detectProjectIdentity
|
|
1206
|
+
});
|
|
1207
|
+
import { basename, join as join6 } from "path";
|
|
1208
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1209
|
+
import { createHash as createHash2 } from "crypto";
|
|
1210
|
+
async function detectProjectIdentity(rootDir, override) {
|
|
1211
|
+
const hash = createHash2("sha256").update(rootDir).digest("hex");
|
|
1212
|
+
if (override && override.trim()) {
|
|
1213
|
+
return { name: override.trim(), hash };
|
|
1214
|
+
}
|
|
1215
|
+
const fromPackageJson = await readJsonField(
|
|
1216
|
+
join6(rootDir, "package.json"),
|
|
1217
|
+
"name"
|
|
1218
|
+
);
|
|
1219
|
+
if (fromPackageJson) return { name: fromPackageJson, hash };
|
|
1220
|
+
const fromCargo = await readTomlFieldInSection(
|
|
1221
|
+
join6(rootDir, "Cargo.toml"),
|
|
1222
|
+
"package",
|
|
1223
|
+
"name"
|
|
1224
|
+
);
|
|
1225
|
+
if (fromCargo) return { name: fromCargo, hash };
|
|
1226
|
+
const fromGoMod = await readGoModule(join6(rootDir, "go.mod"));
|
|
1227
|
+
if (fromGoMod) return { name: fromGoMod, hash };
|
|
1228
|
+
const fromPyProject = await readTomlFieldInSection(
|
|
1229
|
+
join6(rootDir, "pyproject.toml"),
|
|
1230
|
+
"project",
|
|
1231
|
+
"name"
|
|
1232
|
+
) ?? await readTomlFieldInSection(
|
|
1233
|
+
join6(rootDir, "pyproject.toml"),
|
|
1234
|
+
"tool.poetry",
|
|
1235
|
+
"name"
|
|
1236
|
+
);
|
|
1237
|
+
if (fromPyProject) return { name: fromPyProject, hash };
|
|
1238
|
+
return { name: basename(rootDir) || "untitled", hash };
|
|
1239
|
+
}
|
|
1240
|
+
async function readJsonField(path2, field) {
|
|
1241
|
+
try {
|
|
1242
|
+
const raw = await readFile5(path2, "utf-8");
|
|
1243
|
+
const parsed = JSON.parse(raw);
|
|
1244
|
+
const value = parsed[field];
|
|
1245
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
1246
|
+
} catch {
|
|
1247
|
+
}
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
async function readTomlFieldInSection(path2, section, key) {
|
|
1251
|
+
try {
|
|
1252
|
+
const raw = await readFile5(path2, "utf-8");
|
|
1253
|
+
const lines = raw.split("\n");
|
|
1254
|
+
let inSection = false;
|
|
1255
|
+
for (const line of lines) {
|
|
1256
|
+
const trimmed = line.trim();
|
|
1257
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
1258
|
+
inSection = trimmed === `[${section}]`;
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
if (!inSection) continue;
|
|
1262
|
+
const match = trimmed.match(/^([\w-]+)\s*=\s*"([^"]+)"/);
|
|
1263
|
+
if (match && match[1] === key && match[2]) {
|
|
1264
|
+
return match[2];
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
async function readGoModule(path2) {
|
|
1272
|
+
try {
|
|
1273
|
+
const raw = await readFile5(path2, "utf-8");
|
|
1274
|
+
const match = raw.match(/^\s*module\s+(\S+)/m);
|
|
1275
|
+
if (match && match[1]) {
|
|
1276
|
+
const segs = match[1].split("/");
|
|
1277
|
+
const last = segs[segs.length - 1];
|
|
1278
|
+
if (last) return last;
|
|
1279
|
+
}
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
var init_project_name = __esm({
|
|
1285
|
+
"src/ml-client/project-name.ts"() {
|
|
1286
|
+
"use strict";
|
|
1287
|
+
init_esm_shims();
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1202
1291
|
// src/ml-client/index.ts
|
|
1203
1292
|
var ml_client_exports = {};
|
|
1204
1293
|
__export(ml_client_exports, {
|
|
@@ -1269,9 +1358,22 @@ async function runMlAnalysis(ctx, codeDnaResult, findings, options) {
|
|
|
1269
1358
|
console.error(`[deep] Sending ${sampled.length} functions + ${deviations.length} deviations (${Math.round(payloadSize / 1024)}KB) to VibeDrift API...`);
|
|
1270
1359
|
console.error(`[deep] No full files transmitted \u2014 only function snippets and structural metadata.`);
|
|
1271
1360
|
}
|
|
1361
|
+
const { detectProjectIdentity: detectProjectIdentity2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
1362
|
+
const projectIdentity = await detectProjectIdentity2(
|
|
1363
|
+
ctx.rootDir,
|
|
1364
|
+
options.projectName
|
|
1365
|
+
);
|
|
1272
1366
|
const request = {
|
|
1273
1367
|
language: ctx.dominantLanguage ?? "unknown",
|
|
1274
1368
|
file_count: ctx.files.length,
|
|
1369
|
+
project_hash: projectIdentity.hash,
|
|
1370
|
+
project_name: projectIdentity.name,
|
|
1371
|
+
score_hint: options.scoreHint,
|
|
1372
|
+
grade_hint: options.gradeHint,
|
|
1373
|
+
// Tell the server NOT to persist a row from the analyze call —
|
|
1374
|
+
// the CLI logs the full scan summary via /v1/scans/log AFTER the
|
|
1375
|
+
// pipeline finishes, which has accurate metadata.
|
|
1376
|
+
defer_persist: true,
|
|
1275
1377
|
functions: sampled,
|
|
1276
1378
|
deviations,
|
|
1277
1379
|
llm_validations: []
|
|
@@ -1283,6 +1385,9 @@ async function runMlAnalysis(ctx, codeDnaResult, findings, options) {
|
|
|
1283
1385
|
);
|
|
1284
1386
|
}
|
|
1285
1387
|
const filtered = filterByConfidence(response);
|
|
1388
|
+
if (response.scan_id) {
|
|
1389
|
+
filtered.scanId = response.scan_id;
|
|
1390
|
+
}
|
|
1286
1391
|
if (response.deviations?.length > 0 && codeDnaResult?.deviationJustifications) {
|
|
1287
1392
|
for (const mlDev of response.deviations) {
|
|
1288
1393
|
const local = codeDnaResult.deviationJustifications.find(
|
|
@@ -1445,6 +1550,160 @@ var init_summarize = __esm({
|
|
|
1445
1550
|
}
|
|
1446
1551
|
});
|
|
1447
1552
|
|
|
1553
|
+
// src/ml-client/log-scan.ts
|
|
1554
|
+
var log_scan_exports = {};
|
|
1555
|
+
__export(log_scan_exports, {
|
|
1556
|
+
logScan: () => logScan
|
|
1557
|
+
});
|
|
1558
|
+
async function logScan(opts) {
|
|
1559
|
+
const { payload, token, apiUrl, verbose } = opts;
|
|
1560
|
+
const base = apiUrl ?? DEFAULT_API_URL2;
|
|
1561
|
+
const trimmed = { ...payload };
|
|
1562
|
+
if (trimmed.report_html) {
|
|
1563
|
+
const size = Buffer.byteLength(trimmed.report_html, "utf-8");
|
|
1564
|
+
if (size > MAX_HTML_BYTES) {
|
|
1565
|
+
if (verbose) {
|
|
1566
|
+
console.error(
|
|
1567
|
+
`[scan-log] HTML too large (${Math.round(size / 1024)}KB), dropping the blob`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
delete trimmed.report_html;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
const controller = new AbortController();
|
|
1574
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS3);
|
|
1575
|
+
try {
|
|
1576
|
+
const res = await fetch(`${base}/v1/scans/log`, {
|
|
1577
|
+
method: "POST",
|
|
1578
|
+
headers: {
|
|
1579
|
+
Authorization: `Bearer ${token}`,
|
|
1580
|
+
"Content-Type": "application/json"
|
|
1581
|
+
},
|
|
1582
|
+
body: JSON.stringify(trimmed),
|
|
1583
|
+
signal: controller.signal
|
|
1584
|
+
});
|
|
1585
|
+
if (!res.ok) {
|
|
1586
|
+
const text = await res.text().catch(() => "");
|
|
1587
|
+
if (verbose) {
|
|
1588
|
+
console.error(`[scan-log] HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
1589
|
+
}
|
|
1590
|
+
return { ok: false, error: `HTTP ${res.status}` };
|
|
1591
|
+
}
|
|
1592
|
+
const data = await res.json().catch(() => ({}));
|
|
1593
|
+
if (verbose) {
|
|
1594
|
+
console.error(
|
|
1595
|
+
`[scan-log] Logged scan ${data.scan_id?.slice(0, 8) ?? "?"} (project ${data.project_id?.slice(0, 8) ?? "?"}, ${data.bytes_stored ?? 0} HTML bytes)`
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
return {
|
|
1599
|
+
ok: true,
|
|
1600
|
+
scanId: data.scan_id,
|
|
1601
|
+
projectId: data.project_id,
|
|
1602
|
+
bytesStored: data.bytes_stored
|
|
1603
|
+
};
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1606
|
+
if (verbose) console.error(`[scan-log] Failed: ${msg}`);
|
|
1607
|
+
return { ok: false, error: msg };
|
|
1608
|
+
} finally {
|
|
1609
|
+
clearTimeout(timer);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
var DEFAULT_API_URL2, TIMEOUT_MS3, MAX_HTML_BYTES;
|
|
1613
|
+
var init_log_scan = __esm({
|
|
1614
|
+
"src/ml-client/log-scan.ts"() {
|
|
1615
|
+
"use strict";
|
|
1616
|
+
init_esm_shims();
|
|
1617
|
+
DEFAULT_API_URL2 = "https://vibedrift-api.fly.dev";
|
|
1618
|
+
TIMEOUT_MS3 = 2e4;
|
|
1619
|
+
MAX_HTML_BYTES = 15e5;
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
// src/ml-client/sanitize-result.ts
|
|
1624
|
+
var sanitize_result_exports = {};
|
|
1625
|
+
__export(sanitize_result_exports, {
|
|
1626
|
+
sanitizeResultForUpload: () => sanitizeResultForUpload
|
|
1627
|
+
});
|
|
1628
|
+
function sanitizeResultForUpload(result) {
|
|
1629
|
+
const ctx = result.context;
|
|
1630
|
+
const rootDir = ctx?.rootDir ?? "";
|
|
1631
|
+
const stripPath = (p) => {
|
|
1632
|
+
if (!p) return null;
|
|
1633
|
+
if (rootDir && p.startsWith(rootDir)) {
|
|
1634
|
+
const rel = p.slice(rootDir.length).replace(/^\/+/, "");
|
|
1635
|
+
return rel || ".";
|
|
1636
|
+
}
|
|
1637
|
+
return p;
|
|
1638
|
+
};
|
|
1639
|
+
const sanitizeNode = (node) => {
|
|
1640
|
+
if (typeof node === "string") return stripPath(node) ?? node;
|
|
1641
|
+
if (Array.isArray(node)) return node.map(sanitizeNode);
|
|
1642
|
+
if (node && typeof node === "object") {
|
|
1643
|
+
const out = {};
|
|
1644
|
+
for (const [k, v] of Object.entries(node)) {
|
|
1645
|
+
if (k === "rootDir") continue;
|
|
1646
|
+
if (k === "files" && Array.isArray(v)) {
|
|
1647
|
+
out[k] = v.map((f) => ({
|
|
1648
|
+
relativePath: stripPath(f.relativePath) ?? f.relativePath,
|
|
1649
|
+
lineCount: f.lineCount,
|
|
1650
|
+
language: f.language
|
|
1651
|
+
}));
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
if (k === "ast" || k === "treeSitterNode") continue;
|
|
1655
|
+
out[k] = sanitizeNode(v);
|
|
1656
|
+
}
|
|
1657
|
+
return out;
|
|
1658
|
+
}
|
|
1659
|
+
if (node instanceof Map) {
|
|
1660
|
+
const obj = {};
|
|
1661
|
+
for (const [k, v] of node.entries()) {
|
|
1662
|
+
const safeKey = typeof k === "string" ? stripPath(k) ?? k : String(k);
|
|
1663
|
+
obj[safeKey] = sanitizeNode(v);
|
|
1664
|
+
}
|
|
1665
|
+
return obj;
|
|
1666
|
+
}
|
|
1667
|
+
if (node instanceof Set) {
|
|
1668
|
+
return [...node].map(sanitizeNode);
|
|
1669
|
+
}
|
|
1670
|
+
return node;
|
|
1671
|
+
};
|
|
1672
|
+
const envelope = {
|
|
1673
|
+
schema: "vibedrift-scan-result/v1",
|
|
1674
|
+
project: {
|
|
1675
|
+
// populated by the caller, since the CLI knows project_name + hash
|
|
1676
|
+
},
|
|
1677
|
+
language: {
|
|
1678
|
+
dominant: ctx?.dominantLanguage ?? null,
|
|
1679
|
+
breakdown: sanitizeNode(ctx?.languageBreakdown),
|
|
1680
|
+
totalLines: ctx?.totalLines ?? 0
|
|
1681
|
+
},
|
|
1682
|
+
fileCount: (ctx?.files ?? []).length,
|
|
1683
|
+
files: sanitizeNode(ctx?.files),
|
|
1684
|
+
score: {
|
|
1685
|
+
composite: result.compositeScore,
|
|
1686
|
+
max: result.maxCompositeScore,
|
|
1687
|
+
categories: sanitizeNode(result.scores)
|
|
1688
|
+
},
|
|
1689
|
+
findings: sanitizeNode(result.findings),
|
|
1690
|
+
driftFindings: sanitizeNode(result.driftFindings),
|
|
1691
|
+
driftScores: sanitizeNode(result.driftScores),
|
|
1692
|
+
codeDnaResult: sanitizeNode(result.codeDnaResult),
|
|
1693
|
+
perFileScores: sanitizeNode(result.perFileScores),
|
|
1694
|
+
teaseMessages: result.teaseMessages,
|
|
1695
|
+
aiSummary: result.aiSummary ?? null,
|
|
1696
|
+
scanTimeMs: result.scanTimeMs
|
|
1697
|
+
};
|
|
1698
|
+
return envelope;
|
|
1699
|
+
}
|
|
1700
|
+
var init_sanitize_result = __esm({
|
|
1701
|
+
"src/ml-client/sanitize-result.ts"() {
|
|
1702
|
+
"use strict";
|
|
1703
|
+
init_esm_shims();
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1448
1707
|
// src/output/csv.ts
|
|
1449
1708
|
var csv_exports = {};
|
|
1450
1709
|
__export(csv_exports, {
|
|
@@ -4917,10 +5176,10 @@ function extractSymbols(file) {
|
|
|
4917
5176
|
function analyzeFileNaming(files) {
|
|
4918
5177
|
const fileNameConventions = /* @__PURE__ */ new Map();
|
|
4919
5178
|
for (const file of files) {
|
|
4920
|
-
const
|
|
4921
|
-
if (
|
|
4922
|
-
if (/(?:test|spec|config|setup|__)/i.test(
|
|
4923
|
-
const conv = classifyName(
|
|
5179
|
+
const basename2 = file.path.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "";
|
|
5180
|
+
if (basename2.length <= 1) continue;
|
|
5181
|
+
if (/(?:test|spec|config|setup|__)/i.test(basename2)) continue;
|
|
5182
|
+
const conv = classifyName(basename2);
|
|
4924
5183
|
if (!conv) continue;
|
|
4925
5184
|
if (!fileNameConventions.has(conv)) fileNameConventions.set(conv, []);
|
|
4926
5185
|
fileNameConventions.get(conv).push(file.path);
|
|
@@ -7630,6 +7889,104 @@ function previewToken(token) {
|
|
|
7630
7889
|
return token.slice(0, 12) + "\u2026";
|
|
7631
7890
|
}
|
|
7632
7891
|
|
|
7892
|
+
// src/auth/api.ts
|
|
7893
|
+
init_esm_shims();
|
|
7894
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
7895
|
+
var VibeDriftApiError = class extends Error {
|
|
7896
|
+
constructor(status, message) {
|
|
7897
|
+
super(message);
|
|
7898
|
+
this.status = status;
|
|
7899
|
+
this.name = "VibeDriftApiError";
|
|
7900
|
+
}
|
|
7901
|
+
status;
|
|
7902
|
+
};
|
|
7903
|
+
async function jsonFetch(url, init = {}) {
|
|
7904
|
+
const controller = new AbortController();
|
|
7905
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
7906
|
+
try {
|
|
7907
|
+
const res = await fetch(url, {
|
|
7908
|
+
...init,
|
|
7909
|
+
signal: controller.signal,
|
|
7910
|
+
headers: {
|
|
7911
|
+
Accept: "application/json",
|
|
7912
|
+
...init.body ? { "Content-Type": "application/json" } : {},
|
|
7913
|
+
...init.headers ?? {}
|
|
7914
|
+
}
|
|
7915
|
+
});
|
|
7916
|
+
if (!res.ok) {
|
|
7917
|
+
let detail = "";
|
|
7918
|
+
try {
|
|
7919
|
+
const body = await res.json();
|
|
7920
|
+
detail = body.detail ?? body.message ?? "";
|
|
7921
|
+
} catch {
|
|
7922
|
+
try {
|
|
7923
|
+
detail = await res.text();
|
|
7924
|
+
} catch {
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
throw new VibeDriftApiError(res.status, detail || `HTTP ${res.status}`);
|
|
7928
|
+
}
|
|
7929
|
+
return await res.json();
|
|
7930
|
+
} catch (err) {
|
|
7931
|
+
if (err?.name === "AbortError") {
|
|
7932
|
+
throw new VibeDriftApiError(0, `Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`);
|
|
7933
|
+
}
|
|
7934
|
+
if (err instanceof VibeDriftApiError) throw err;
|
|
7935
|
+
throw new VibeDriftApiError(0, err?.message ?? String(err));
|
|
7936
|
+
} finally {
|
|
7937
|
+
clearTimeout(timer);
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
async function startDeviceAuth(opts) {
|
|
7941
|
+
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7942
|
+
return jsonFetch(`${base}/auth/device`, {
|
|
7943
|
+
method: "POST",
|
|
7944
|
+
body: JSON.stringify({ client_id: "vibedrift-cli" })
|
|
7945
|
+
});
|
|
7946
|
+
}
|
|
7947
|
+
async function pollDeviceAuth(deviceCode, opts) {
|
|
7948
|
+
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7949
|
+
return jsonFetch(`${base}/auth/poll`, {
|
|
7950
|
+
method: "POST",
|
|
7951
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
7952
|
+
});
|
|
7953
|
+
}
|
|
7954
|
+
async function validateToken(token, opts) {
|
|
7955
|
+
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7956
|
+
return jsonFetch(`${base}/auth/validate`, {
|
|
7957
|
+
method: "GET",
|
|
7958
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
7959
|
+
});
|
|
7960
|
+
}
|
|
7961
|
+
async function revokeToken(token, opts) {
|
|
7962
|
+
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7963
|
+
await jsonFetch(`${base}/auth/revoke`, {
|
|
7964
|
+
method: "POST",
|
|
7965
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
7966
|
+
});
|
|
7967
|
+
}
|
|
7968
|
+
async function fetchUsage(token, opts) {
|
|
7969
|
+
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7970
|
+
return jsonFetch(`${base}/account/usage`, {
|
|
7971
|
+
method: "GET",
|
|
7972
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
7973
|
+
});
|
|
7974
|
+
}
|
|
7975
|
+
async function fetchCredits(token, opts) {
|
|
7976
|
+
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7977
|
+
return jsonFetch(`${base}/account/credits`, {
|
|
7978
|
+
method: "GET",
|
|
7979
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
7980
|
+
});
|
|
7981
|
+
}
|
|
7982
|
+
async function createPortalSession(token, opts) {
|
|
7983
|
+
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7984
|
+
return jsonFetch(`${base}/account/portal`, {
|
|
7985
|
+
method: "POST",
|
|
7986
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
7987
|
+
});
|
|
7988
|
+
}
|
|
7989
|
+
|
|
7633
7990
|
// src/cli/commands/scan.ts
|
|
7634
7991
|
async function runScan(targetPath, options) {
|
|
7635
7992
|
const rootDir = resolve(targetPath);
|
|
@@ -7651,13 +8008,43 @@ async function runScan(targetPath, options) {
|
|
|
7651
8008
|
console.error("");
|
|
7652
8009
|
console.error(chalk2.red(" \u2717 Deep scans require a VibeDrift account."));
|
|
7653
8010
|
console.error("");
|
|
7654
|
-
console.error(
|
|
8011
|
+
console.error(chalk2.bgYellow.black.bold(" \u{1F381} But your first deep scan is FREE. "));
|
|
8012
|
+
console.error("");
|
|
8013
|
+
console.error(" Run " + chalk2.bold("vibedrift login") + " to sign in and claim it.");
|
|
7655
8014
|
console.error(" Or set " + chalk2.bold("VIBEDRIFT_TOKEN") + " in your environment for CI.");
|
|
7656
8015
|
console.error("");
|
|
7657
8016
|
process.exit(1);
|
|
7658
8017
|
}
|
|
7659
8018
|
bearerToken = resolved.token;
|
|
7660
8019
|
apiUrl = await resolveApiUrl(options.apiUrl);
|
|
8020
|
+
} else {
|
|
8021
|
+
try {
|
|
8022
|
+
const resolved = await resolveToken();
|
|
8023
|
+
if (resolved) {
|
|
8024
|
+
bearerToken = resolved.token;
|
|
8025
|
+
apiUrl = await resolveApiUrl(options.apiUrl);
|
|
8026
|
+
}
|
|
8027
|
+
} catch {
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
if (!options.json && options.format !== "json" && !options.deep) {
|
|
8031
|
+
if (bearerToken) {
|
|
8032
|
+
try {
|
|
8033
|
+
const credits = await fetchCredits(bearerToken, { apiUrl });
|
|
8034
|
+
if (credits.has_free_deep_scan && !credits.unlimited) {
|
|
8035
|
+
console.log("");
|
|
8036
|
+
console.log(chalk2.bgYellow.black.bold(" \u{1F381} 1 FREE DEEP SCAN AVAILABLE "));
|
|
8037
|
+
console.log(chalk2.yellow(" Run with ") + chalk2.bold.cyan("--deep") + chalk2.yellow(" to use Claude-powered analysis (no card required)."));
|
|
8038
|
+
console.log("");
|
|
8039
|
+
}
|
|
8040
|
+
} catch {
|
|
8041
|
+
}
|
|
8042
|
+
} else {
|
|
8043
|
+
console.log("");
|
|
8044
|
+
console.log(chalk2.dim(" Tip: ") + chalk2.yellow("sign up free to get 1 deep scan included"));
|
|
8045
|
+
console.log(chalk2.dim(" Run ") + chalk2.bold("vibedrift login") + chalk2.dim(" to claim it (no card required)."));
|
|
8046
|
+
console.log("");
|
|
8047
|
+
}
|
|
7661
8048
|
}
|
|
7662
8049
|
const startTime = Date.now();
|
|
7663
8050
|
const timings = {};
|
|
@@ -7735,7 +8122,8 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7735
8122
|
token: bearerToken,
|
|
7736
8123
|
apiUrl,
|
|
7737
8124
|
verbose: options.verbose,
|
|
7738
|
-
driftFindings: driftResult.driftFindings
|
|
8125
|
+
driftFindings: driftResult.driftFindings,
|
|
8126
|
+
projectName: options.projectName
|
|
7739
8127
|
});
|
|
7740
8128
|
allFindings.push(...mlResult.highConfidence);
|
|
7741
8129
|
mlMediumConfidence = mlResult.mediumConfidence;
|
|
@@ -7806,6 +8194,62 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7806
8194
|
}
|
|
7807
8195
|
}
|
|
7808
8196
|
await saveScanResult(rootDir, scores, compositeScore);
|
|
8197
|
+
if (bearerToken) {
|
|
8198
|
+
try {
|
|
8199
|
+
const { logScan: logScan2 } = await Promise.resolve().then(() => (init_log_scan(), log_scan_exports));
|
|
8200
|
+
const { detectProjectIdentity: detectProjectIdentity2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
|
|
8201
|
+
const { sanitizeResultForUpload: sanitizeResultForUpload2 } = await Promise.resolve().then(() => (init_sanitize_result(), sanitize_result_exports));
|
|
8202
|
+
const projectIdentity = await detectProjectIdentity2(
|
|
8203
|
+
rootDir,
|
|
8204
|
+
options.projectName
|
|
8205
|
+
);
|
|
8206
|
+
const mlDuplicates = allFindings.filter((f) => f.analyzerId === "ml-duplicate").length;
|
|
8207
|
+
const mlIntent = allFindings.filter((f) => f.analyzerId === "ml-intent").length;
|
|
8208
|
+
const mlAnomaly = allFindings.filter((f) => f.analyzerId === "ml-anomaly").length;
|
|
8209
|
+
let html;
|
|
8210
|
+
try {
|
|
8211
|
+
html = renderHtmlReport(result);
|
|
8212
|
+
} catch {
|
|
8213
|
+
html = void 0;
|
|
8214
|
+
}
|
|
8215
|
+
const pct = maxCompositeScore > 0 ? compositeScore / maxCompositeScore * 100 : 0;
|
|
8216
|
+
const grade = pct >= 90 ? "A" : pct >= 75 ? "B" : pct >= 50 ? "C" : pct >= 25 ? "D" : "F";
|
|
8217
|
+
const sanitizedResult = sanitizeResultForUpload2(result);
|
|
8218
|
+
sanitizedResult.project = {
|
|
8219
|
+
name: projectIdentity.name,
|
|
8220
|
+
hash: projectIdentity.hash
|
|
8221
|
+
};
|
|
8222
|
+
sanitizedResult.grade = grade;
|
|
8223
|
+
sanitizedResult.scanType = options.deep ? "deep" : "free";
|
|
8224
|
+
sanitizedResult.scannedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8225
|
+
await logScan2({
|
|
8226
|
+
token: bearerToken,
|
|
8227
|
+
apiUrl,
|
|
8228
|
+
verbose: options.verbose,
|
|
8229
|
+
payload: {
|
|
8230
|
+
project_hash: projectIdentity.hash,
|
|
8231
|
+
project_name: projectIdentity.name,
|
|
8232
|
+
language: ctx.dominantLanguage ?? "unknown",
|
|
8233
|
+
file_count: ctx.files.length,
|
|
8234
|
+
function_count: codeDnaResult?.functions?.length ?? 0,
|
|
8235
|
+
finding_count: allFindings.length,
|
|
8236
|
+
score: compositeScore,
|
|
8237
|
+
grade,
|
|
8238
|
+
duplicates_found: mlDuplicates,
|
|
8239
|
+
intent_mismatches: mlIntent,
|
|
8240
|
+
anomalies_found: mlAnomaly,
|
|
8241
|
+
is_deep: !!options.deep,
|
|
8242
|
+
processing_time_ms: scanTimeMs,
|
|
8243
|
+
report_html: html,
|
|
8244
|
+
result_json: sanitizedResult
|
|
8245
|
+
}
|
|
8246
|
+
});
|
|
8247
|
+
} catch (err) {
|
|
8248
|
+
if (options.verbose) {
|
|
8249
|
+
console.error(chalk2.dim(`[scan-log] Failed: ${err.message}`));
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
}
|
|
7809
8253
|
const format = options.format ?? (options.json ? "json" : "html");
|
|
7810
8254
|
if (format === "html") {
|
|
7811
8255
|
const html = renderHtmlReport(result);
|
|
@@ -7956,97 +8400,6 @@ Update failed: ${message}`));
|
|
|
7956
8400
|
init_esm_shims();
|
|
7957
8401
|
import chalk4 from "chalk";
|
|
7958
8402
|
|
|
7959
|
-
// src/auth/api.ts
|
|
7960
|
-
init_esm_shims();
|
|
7961
|
-
var REQUEST_TIMEOUT_MS = 3e4;
|
|
7962
|
-
var VibeDriftApiError = class extends Error {
|
|
7963
|
-
constructor(status, message) {
|
|
7964
|
-
super(message);
|
|
7965
|
-
this.status = status;
|
|
7966
|
-
this.name = "VibeDriftApiError";
|
|
7967
|
-
}
|
|
7968
|
-
status;
|
|
7969
|
-
};
|
|
7970
|
-
async function jsonFetch(url, init = {}) {
|
|
7971
|
-
const controller = new AbortController();
|
|
7972
|
-
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
7973
|
-
try {
|
|
7974
|
-
const res = await fetch(url, {
|
|
7975
|
-
...init,
|
|
7976
|
-
signal: controller.signal,
|
|
7977
|
-
headers: {
|
|
7978
|
-
Accept: "application/json",
|
|
7979
|
-
...init.body ? { "Content-Type": "application/json" } : {},
|
|
7980
|
-
...init.headers ?? {}
|
|
7981
|
-
}
|
|
7982
|
-
});
|
|
7983
|
-
if (!res.ok) {
|
|
7984
|
-
let detail = "";
|
|
7985
|
-
try {
|
|
7986
|
-
const body = await res.json();
|
|
7987
|
-
detail = body.detail ?? body.message ?? "";
|
|
7988
|
-
} catch {
|
|
7989
|
-
try {
|
|
7990
|
-
detail = await res.text();
|
|
7991
|
-
} catch {
|
|
7992
|
-
}
|
|
7993
|
-
}
|
|
7994
|
-
throw new VibeDriftApiError(res.status, detail || `HTTP ${res.status}`);
|
|
7995
|
-
}
|
|
7996
|
-
return await res.json();
|
|
7997
|
-
} catch (err) {
|
|
7998
|
-
if (err?.name === "AbortError") {
|
|
7999
|
-
throw new VibeDriftApiError(0, `Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`);
|
|
8000
|
-
}
|
|
8001
|
-
if (err instanceof VibeDriftApiError) throw err;
|
|
8002
|
-
throw new VibeDriftApiError(0, err?.message ?? String(err));
|
|
8003
|
-
} finally {
|
|
8004
|
-
clearTimeout(timer);
|
|
8005
|
-
}
|
|
8006
|
-
}
|
|
8007
|
-
async function startDeviceAuth(opts) {
|
|
8008
|
-
const base = await resolveApiUrl(opts?.apiUrl);
|
|
8009
|
-
return jsonFetch(`${base}/auth/device`, {
|
|
8010
|
-
method: "POST",
|
|
8011
|
-
body: JSON.stringify({ client_id: "vibedrift-cli" })
|
|
8012
|
-
});
|
|
8013
|
-
}
|
|
8014
|
-
async function pollDeviceAuth(deviceCode, opts) {
|
|
8015
|
-
const base = await resolveApiUrl(opts?.apiUrl);
|
|
8016
|
-
return jsonFetch(`${base}/auth/poll`, {
|
|
8017
|
-
method: "POST",
|
|
8018
|
-
body: JSON.stringify({ device_code: deviceCode })
|
|
8019
|
-
});
|
|
8020
|
-
}
|
|
8021
|
-
async function validateToken(token, opts) {
|
|
8022
|
-
const base = await resolveApiUrl(opts?.apiUrl);
|
|
8023
|
-
return jsonFetch(`${base}/auth/validate`, {
|
|
8024
|
-
method: "GET",
|
|
8025
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
8026
|
-
});
|
|
8027
|
-
}
|
|
8028
|
-
async function revokeToken(token, opts) {
|
|
8029
|
-
const base = await resolveApiUrl(opts?.apiUrl);
|
|
8030
|
-
await jsonFetch(`${base}/auth/revoke`, {
|
|
8031
|
-
method: "POST",
|
|
8032
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
8033
|
-
});
|
|
8034
|
-
}
|
|
8035
|
-
async function fetchUsage(token, opts) {
|
|
8036
|
-
const base = await resolveApiUrl(opts?.apiUrl);
|
|
8037
|
-
return jsonFetch(`${base}/account/usage`, {
|
|
8038
|
-
method: "GET",
|
|
8039
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
8040
|
-
});
|
|
8041
|
-
}
|
|
8042
|
-
async function createPortalSession(token, opts) {
|
|
8043
|
-
const base = await resolveApiUrl(opts?.apiUrl);
|
|
8044
|
-
return jsonFetch(`${base}/account/portal`, {
|
|
8045
|
-
method: "POST",
|
|
8046
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
8047
|
-
});
|
|
8048
|
-
}
|
|
8049
|
-
|
|
8050
8403
|
// src/auth/browser.ts
|
|
8051
8404
|
init_esm_shims();
|
|
8052
8405
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -8153,11 +8506,43 @@ async function runLogin(options = {}) {
|
|
|
8153
8506
|
console.log(` Account: ${chalk4.bold(result.email)}`);
|
|
8154
8507
|
console.log(` Plan: ${chalk4.bold(result.plan)}`);
|
|
8155
8508
|
console.log("");
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8509
|
+
try {
|
|
8510
|
+
const credits = await fetchCredits(result.access_token, {
|
|
8511
|
+
apiUrl: options.apiUrl
|
|
8512
|
+
});
|
|
8513
|
+
if (credits.has_free_deep_scan && !credits.unlimited) {
|
|
8514
|
+
console.log(
|
|
8515
|
+
chalk4.bgYellow.black.bold(" \u{1F381} 1 FREE deep scan included with your account ")
|
|
8516
|
+
);
|
|
8517
|
+
console.log("");
|
|
8518
|
+
console.log(
|
|
8519
|
+
chalk4.yellow(" Try the full pipeline (Claude analysis, security review,")
|
|
8520
|
+
);
|
|
8521
|
+
console.log(
|
|
8522
|
+
chalk4.yellow(" AI-powered drift detection) on any project \u2014 no card needed.")
|
|
8523
|
+
);
|
|
8524
|
+
console.log("");
|
|
8525
|
+
console.log(` ${chalk4.cyan("vibedrift . --deep")}`);
|
|
8526
|
+
console.log("");
|
|
8527
|
+
} else if (credits.unlimited) {
|
|
8528
|
+
console.log(chalk4.dim(" Run `vibedrift . --deep` to use AI-powered analysis."));
|
|
8529
|
+
console.log("");
|
|
8530
|
+
} else if (credits.available_total > 0) {
|
|
8531
|
+
console.log(
|
|
8532
|
+
chalk4.dim(` You have ${credits.available_total} deep scan credit${credits.available_total === 1 ? "" : "s"} available.`)
|
|
8533
|
+
);
|
|
8534
|
+
console.log(chalk4.dim(" Run `vibedrift . --deep` to use one."));
|
|
8535
|
+
console.log("");
|
|
8536
|
+
} else {
|
|
8537
|
+
console.log(chalk4.dim(" Run `vibedrift upgrade` to enable deep AI scans."));
|
|
8538
|
+
console.log("");
|
|
8539
|
+
}
|
|
8540
|
+
} catch {
|
|
8541
|
+
if (result.plan === "free") {
|
|
8542
|
+
console.log(chalk4.dim(" Run `vibedrift upgrade` to enable deep AI scans."));
|
|
8543
|
+
} else {
|
|
8544
|
+
console.log(chalk4.dim(" Run `vibedrift . --deep` to use AI-powered analysis."));
|
|
8545
|
+
}
|
|
8161
8546
|
console.log("");
|
|
8162
8547
|
}
|
|
8163
8548
|
return;
|
|
@@ -8269,6 +8654,21 @@ async function runStatus() {
|
|
|
8269
8654
|
console.log(chalk6.dim(` ${err.message}`));
|
|
8270
8655
|
}
|
|
8271
8656
|
}
|
|
8657
|
+
try {
|
|
8658
|
+
const credits = await fetchCredits(resolved.token, { apiUrl: config.apiUrl });
|
|
8659
|
+
console.log("");
|
|
8660
|
+
if (credits.unlimited) {
|
|
8661
|
+
console.log(` Deep scans: ${chalk6.bold.green("unlimited")} (${credits.plan})`);
|
|
8662
|
+
} else if (credits.has_free_deep_scan) {
|
|
8663
|
+
console.log(` Deep scans: ${chalk6.bold.yellow("1 free")} + ${credits.available_purchased} purchased`);
|
|
8664
|
+
console.log(chalk6.dim(" Run `vibedrift . --deep` to use your free credit."));
|
|
8665
|
+
} else if (credits.available_total > 0) {
|
|
8666
|
+
console.log(` Deep scans: ${chalk6.bold(credits.available_total)} credit${credits.available_total === 1 ? "" : "s"} available`);
|
|
8667
|
+
} else {
|
|
8668
|
+
console.log(` Deep scans: ${chalk6.dim("0 credits")} \u2014 run \`vibedrift upgrade\` for more`);
|
|
8669
|
+
}
|
|
8670
|
+
} catch {
|
|
8671
|
+
}
|
|
8272
8672
|
console.log("");
|
|
8273
8673
|
}
|
|
8274
8674
|
function describeSource(source) {
|
|
@@ -8416,7 +8816,7 @@ async function runBilling() {
|
|
|
8416
8816
|
init_esm_shims();
|
|
8417
8817
|
import chalk10 from "chalk";
|
|
8418
8818
|
import { homedir as homedir3, platform, arch } from "os";
|
|
8419
|
-
import { join as
|
|
8819
|
+
import { join as join7 } from "path";
|
|
8420
8820
|
import { stat as stat4, access, constants } from "fs/promises";
|
|
8421
8821
|
init_version();
|
|
8422
8822
|
async function runDoctor() {
|
|
@@ -8462,7 +8862,7 @@ async function runDoctor() {
|
|
|
8462
8862
|
info("Config file", "absent (not logged in)");
|
|
8463
8863
|
}
|
|
8464
8864
|
}
|
|
8465
|
-
const historyDir =
|
|
8865
|
+
const historyDir = join7(homedir3(), ".vibedrift", "scans");
|
|
8466
8866
|
try {
|
|
8467
8867
|
const info2 = await stat4(historyDir);
|
|
8468
8868
|
if (info2.isDirectory()) ok("Scan history", historyDir);
|
|
@@ -8591,6 +8991,9 @@ program.command("scan", { isDefault: true }).description("Scan a project for vib
|
|
|
8591
8991
|
).option("--no-codedna", "skip Code DNA semantic analysis").option(
|
|
8592
8992
|
"--deep",
|
|
8593
8993
|
"enable AI-powered deep analysis (requires `vibedrift login`)"
|
|
8994
|
+
).option(
|
|
8995
|
+
"--project-name <name>",
|
|
8996
|
+
"override the auto-detected project name shown in the dashboard"
|
|
8594
8997
|
).option(
|
|
8595
8998
|
"--include <pattern>",
|
|
8596
8999
|
"only scan files matching this glob (repeatable)",
|
|
@@ -8621,7 +9024,8 @@ program.command("scan", { isDefault: true }).description("Scan a project for vib
|
|
|
8621
9024
|
apiUrl: options.apiUrl,
|
|
8622
9025
|
include: options.include,
|
|
8623
9026
|
exclude: options.exclude,
|
|
8624
|
-
verbose: options.verbose
|
|
9027
|
+
verbose: options.verbose,
|
|
9028
|
+
projectName: options.projectName
|
|
8625
9029
|
});
|
|
8626
9030
|
});
|
|
8627
9031
|
program.command("login").description("Log in to your VibeDrift account").option("--no-browser", "don't open the browser automatically").addOption(
|