@vibedrift/cli 0.1.4 → 0.2.0
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 +53 -17
- package/dist/index.js +955 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -230,12 +230,12 @@ function normalizeBody(body, language) {
|
|
|
230
230
|
}
|
|
231
231
|
const sortedVars = [...varNames.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
232
232
|
for (const [name, placeholder] of sortedVars) {
|
|
233
|
-
normalized = normalized.replace(new RegExp(`\\b${
|
|
233
|
+
normalized = normalized.replace(new RegExp(`\\b${escapeRegex3(name)}\\b`, "g"), placeholder);
|
|
234
234
|
}
|
|
235
235
|
normalized = normalized.replace(/\s+/g, " ").trim();
|
|
236
236
|
return normalized;
|
|
237
237
|
}
|
|
238
|
-
function
|
|
238
|
+
function escapeRegex3(s) {
|
|
239
239
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
240
240
|
}
|
|
241
241
|
function fnv1aHash(str) {
|
|
@@ -1012,13 +1012,13 @@ var init_sampler = __esm({
|
|
|
1012
1012
|
});
|
|
1013
1013
|
|
|
1014
1014
|
// src/ml-client/client.ts
|
|
1015
|
-
async function callMlApi(request,
|
|
1015
|
+
async function callMlApi(request, token, apiUrl) {
|
|
1016
1016
|
const url = `${apiUrl ?? DEFAULT_API_URL}/v1/analyze`;
|
|
1017
1017
|
const headers = {
|
|
1018
1018
|
"Content-Type": "application/json"
|
|
1019
1019
|
};
|
|
1020
|
-
if (
|
|
1021
|
-
headers["
|
|
1020
|
+
if (token) {
|
|
1021
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
1022
1022
|
}
|
|
1023
1023
|
const controller = new AbortController();
|
|
1024
1024
|
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
@@ -1031,7 +1031,7 @@ async function callMlApi(request, apiKey, apiUrl) {
|
|
|
1031
1031
|
});
|
|
1032
1032
|
if (!response.ok) {
|
|
1033
1033
|
const errorBody = await response.text().catch(() => "");
|
|
1034
|
-
throw new Error(`
|
|
1034
|
+
throw new Error(`Deep-analysis API error ${response.status}: ${errorBody.slice(0, 200)}`);
|
|
1035
1035
|
}
|
|
1036
1036
|
return await response.json();
|
|
1037
1037
|
} finally {
|
|
@@ -1266,8 +1266,8 @@ async function runMlAnalysis(ctx, codeDnaResult, findings, options) {
|
|
|
1266
1266
|
const deviations = buildDeviationPayloads(codeDnaResult, options.driftFindings ?? []);
|
|
1267
1267
|
const payloadSize = JSON.stringify(sampled).length + JSON.stringify(deviations).length;
|
|
1268
1268
|
if (options.verbose) {
|
|
1269
|
-
console.error(`[
|
|
1270
|
-
console.error(`[
|
|
1269
|
+
console.error(`[deep] Sending ${sampled.length} functions + ${deviations.length} deviations (${Math.round(payloadSize / 1024)}KB) to VibeDrift API...`);
|
|
1270
|
+
console.error(`[deep] No full files transmitted \u2014 only function snippets and structural metadata.`);
|
|
1271
1271
|
}
|
|
1272
1272
|
const request = {
|
|
1273
1273
|
language: ctx.dominantLanguage ?? "unknown",
|
|
@@ -1276,10 +1276,10 @@ async function runMlAnalysis(ctx, codeDnaResult, findings, options) {
|
|
|
1276
1276
|
deviations,
|
|
1277
1277
|
llm_validations: []
|
|
1278
1278
|
};
|
|
1279
|
-
const response = await callMlApi(request, options.
|
|
1279
|
+
const response = await callMlApi(request, options.token, options.apiUrl);
|
|
1280
1280
|
if (options.verbose) {
|
|
1281
1281
|
console.error(
|
|
1282
|
-
`[
|
|
1282
|
+
`[deep] API returned: ${response.duplicates.length} duplicates, ${response.intent_mismatches.length} intent mismatches, ${response.anomalies.length} anomalies, ${response.deviations?.length ?? 0} deviation verdicts \u2014 ${response.processing_time_ms}ms`
|
|
1283
1283
|
);
|
|
1284
1284
|
}
|
|
1285
1285
|
const filtered = filterByConfidence(response);
|
|
@@ -1372,7 +1372,7 @@ var summarize_exports = {};
|
|
|
1372
1372
|
__export(summarize_exports, {
|
|
1373
1373
|
fetchAiSummary: () => fetchAiSummary
|
|
1374
1374
|
});
|
|
1375
|
-
async function fetchAiSummary(result, apiUrl,
|
|
1375
|
+
async function fetchAiSummary(result, apiUrl, token) {
|
|
1376
1376
|
const dna = result.codeDnaResult;
|
|
1377
1377
|
const mlFindings = result.findings.filter((f) => f.tags?.includes("ml"));
|
|
1378
1378
|
const body = {
|
|
@@ -1409,7 +1409,7 @@ async function fetchAiSummary(result, apiUrl, mlKey) {
|
|
|
1409
1409
|
}).slice(0, 5).map((d) => d.recommendation)
|
|
1410
1410
|
};
|
|
1411
1411
|
const headers = { "Content-Type": "application/json" };
|
|
1412
|
-
if (
|
|
1412
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1413
1413
|
const controller = new AbortController();
|
|
1414
1414
|
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS2);
|
|
1415
1415
|
try {
|
|
@@ -1969,8 +1969,8 @@ import { Command, Option } from "commander";
|
|
|
1969
1969
|
// src/cli/commands/scan.ts
|
|
1970
1970
|
init_esm_shims();
|
|
1971
1971
|
import { resolve } from "path";
|
|
1972
|
-
import { writeFile as
|
|
1973
|
-
import { stat as
|
|
1972
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
1973
|
+
import { stat as stat3 } from "fs/promises";
|
|
1974
1974
|
import chalk2 from "chalk";
|
|
1975
1975
|
import ora from "ora";
|
|
1976
1976
|
|
|
@@ -2086,8 +2086,8 @@ async function discoverFiles(rootDir) {
|
|
|
2086
2086
|
const language = detectLanguage(entry.name);
|
|
2087
2087
|
if (!language) continue;
|
|
2088
2088
|
try {
|
|
2089
|
-
const
|
|
2090
|
-
if (
|
|
2089
|
+
const info2 = await stat(fullPath);
|
|
2090
|
+
if (info2.size > MAX_FILE_SIZE) continue;
|
|
2091
2091
|
const content = await readFile2(fullPath, "utf-8");
|
|
2092
2092
|
const lineCount = content.split("\n").length;
|
|
2093
2093
|
files.push({ path: fullPath, relativePath: relPath, language, content, lineCount });
|
|
@@ -5962,33 +5962,36 @@ function computePerFileScores(findings, ctx) {
|
|
|
5962
5962
|
|
|
5963
5963
|
// src/output/tease.ts
|
|
5964
5964
|
init_esm_shims();
|
|
5965
|
-
function generateTeaseMessages(ctx, findings,
|
|
5966
|
-
if (
|
|
5965
|
+
function generateTeaseMessages(ctx, findings, deepUsed) {
|
|
5966
|
+
if (deepUsed) return [];
|
|
5967
5967
|
const messages = [];
|
|
5968
5968
|
const securityFindings = findings.filter((f) => f.analyzerId === "security");
|
|
5969
5969
|
if (securityFindings.length > 0) {
|
|
5970
5970
|
const errorCount = securityFindings.filter((f) => f.severity === "error").length;
|
|
5971
5971
|
messages.push(
|
|
5972
|
-
`${securityFindings.length} security issues found${errorCount > 0 ? ` (${errorCount} critical)` : ""} \u2014 run --
|
|
5972
|
+
`${securityFindings.length} security issues found${errorCount > 0 ? ` (${errorCount} critical)` : ""} \u2014 run \`vibedrift --deep\` for AI-powered context analysis`
|
|
5973
5973
|
);
|
|
5974
5974
|
}
|
|
5975
5975
|
const complexityFindings = findings.filter((f) => f.analyzerId === "complexity");
|
|
5976
5976
|
if (complexityFindings.length > 0) {
|
|
5977
5977
|
messages.push(
|
|
5978
|
-
`${complexityFindings.length} complexity concerns detected \u2014 run --
|
|
5978
|
+
`${complexityFindings.length} complexity concerns detected \u2014 run \`vibedrift --deep\` for AI-powered architectural recommendations`
|
|
5979
5979
|
);
|
|
5980
5980
|
}
|
|
5981
5981
|
const deadCodeFindings = findings.filter((f) => f.analyzerId === "dead-code");
|
|
5982
5982
|
if (deadCodeFindings.length > 0) {
|
|
5983
5983
|
messages.push(
|
|
5984
|
-
`Potential dead code detected \u2014 run --
|
|
5984
|
+
`Potential dead code detected \u2014 run \`vibedrift --deep\` for AI-powered semantic analysis`
|
|
5985
5985
|
);
|
|
5986
5986
|
}
|
|
5987
5987
|
if (ctx.files.length > 10 && messages.length === 0) {
|
|
5988
5988
|
messages.push(
|
|
5989
|
-
`Run --
|
|
5989
|
+
`Run \`vibedrift --deep\` for AI-powered architectural pattern detection and intent analysis`
|
|
5990
5990
|
);
|
|
5991
5991
|
}
|
|
5992
|
+
if (messages.length > 0) {
|
|
5993
|
+
messages.push(`Sign in first with \`vibedrift login\` \u2014 it's free.`);
|
|
5994
|
+
}
|
|
5992
5995
|
return messages;
|
|
5993
5996
|
}
|
|
5994
5997
|
|
|
@@ -6870,9 +6873,9 @@ function buildMlInsights(result) {
|
|
|
6870
6873
|
</details>`;
|
|
6871
6874
|
}).join("");
|
|
6872
6875
|
return `<section class="section">
|
|
6873
|
-
<div class="label">
|
|
6876
|
+
<div class="label">AI ANALYSIS <span style="font-size:11px;font-weight:400;letter-spacing:0;text-transform:none;color:var(--info-blue);margin-left:8px">Deep Layer · Code Embeddings · ${mlFindings.length} findings</span></div>
|
|
6874
6877
|
<p style="font-size:13px;color:var(--text-secondary);margin-bottom:16px;line-height:1.6">
|
|
6875
|
-
These findings were detected by
|
|
6878
|
+
These findings were detected by VibeDrift’s AI inference API. Only function snippets (not full files) were sent for analysis — snippets are processed in memory and not stored.
|
|
6876
6879
|
Functions were embedded as 768-dimensional vectors and compared for semantic similarity, name-body alignment, and clustering anomalies.
|
|
6877
6880
|
</p>
|
|
6878
6881
|
${sections}
|
|
@@ -6982,11 +6985,11 @@ function buildCodeDnaSummary(result) {
|
|
|
6982
6985
|
</section>`;
|
|
6983
6986
|
}
|
|
6984
6987
|
function buildFooter(result) {
|
|
6985
|
-
const
|
|
6986
|
-
const premiumUpsell = !
|
|
6987
|
-
Want
|
|
6988
|
-
<code class="mono" style="color:var(--text-primary);background:var(--bg-code);padding:2px 6px;border-radius:0;margin-top:4px;display:inline-block">vibedrift . --
|
|
6989
|
-
<span data-copy="vibedrift . --
|
|
6988
|
+
const hasDeep = result.findings.some((f) => f.tags?.includes("ml")) || !!result.aiSummary;
|
|
6989
|
+
const premiumUpsell = !hasDeep ? `<div style="margin-top:16px;padding:14px 18px;background:var(--bg-surface);border-radius:0;border-left:3px solid var(--border);text-align:left;font-size:13px;color:var(--text-secondary);max-width:520px;margin-left:auto;margin-right:auto">
|
|
6990
|
+
Want AI-powered deep analysis? Sign in once with <code class="mono" style="color:var(--text-primary);background:var(--bg-code);padding:2px 6px;border-radius:0">vibedrift login</code> then run:<br>
|
|
6991
|
+
<code class="mono" style="color:var(--text-primary);background:var(--bg-code);padding:2px 6px;border-radius:0;margin-top:4px;display:inline-block">vibedrift . --deep</code>
|
|
6992
|
+
<span data-copy="vibedrift . --deep" style="cursor:pointer;margin-left:6px;font-size:11px;color:var(--text-tertiary)">[copy]</span>
|
|
6990
6993
|
</div>` : "";
|
|
6991
6994
|
return `<footer style="border-top:1px solid var(--border);padding-top:28px;margin-top:48px;text-align:center;color:var(--text-tertiary);font-size:13px">
|
|
6992
6995
|
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:20px;flex-wrap:wrap">
|
|
@@ -6996,7 +6999,7 @@ function buildFooter(result) {
|
|
|
6996
6999
|
</div>
|
|
6997
7000
|
<p>Generated by <span style="color:var(--text-primary);font-weight:600">VibeDrift v${getVersion()}</span></p>
|
|
6998
7001
|
<p style="margin:4px 0">${result.context.files.length} files · ${result.context.totalLines.toLocaleString()} lines · ${(result.scanTimeMs / 1e3).toFixed(1)}s</p>
|
|
6999
|
-
<p style="margin:4px 0;font-size:12px">${
|
|
7002
|
+
<p style="margin:4px 0;font-size:12px">${hasDeep ? "Function snippets were sent to VibeDrift’s AI API for analysis. No full files transmitted. Snippets processed in memory and not stored." : "No data sent externally."}</p>
|
|
7000
7003
|
<p style="margin-top:10px">Fix the top issues and re-scan: <code class="mono" style="background:var(--bg-code);padding:2px 6px;border-radius:0;color:var(--text-primary)">vibedrift .</code> <span data-copy="vibedrift ." style="cursor:pointer;font-size:11px;color:var(--text-tertiary)">[copy]</span></p>
|
|
7001
7004
|
${premiumUpsell}
|
|
7002
7005
|
<div style="margin-top:16px;padding:12px 18px;background:var(--bg-surface);border-radius:0;display:inline-block;text-align:left">
|
|
@@ -7404,16 +7407,25 @@ function esc2(s) { return String(s || '').replace(/&/g,'&').replace(/</g,'&l
|
|
|
7404
7407
|
// src/core/history.ts
|
|
7405
7408
|
init_esm_shims();
|
|
7406
7409
|
import { readdir as readdir2, readFile as readFile3, writeFile, mkdir } from "fs/promises";
|
|
7410
|
+
import { homedir } from "os";
|
|
7411
|
+
import { createHash } from "crypto";
|
|
7407
7412
|
import { join as join4 } from "path";
|
|
7408
|
-
var
|
|
7413
|
+
var ROOT_DIR = join4(homedir(), ".vibedrift", "scans");
|
|
7414
|
+
function projectHash(rootDir) {
|
|
7415
|
+
return createHash("sha256").update(rootDir).digest("hex").slice(0, 16);
|
|
7416
|
+
}
|
|
7417
|
+
function projectDir(rootDir) {
|
|
7418
|
+
return join4(ROOT_DIR, projectHash(rootDir));
|
|
7419
|
+
}
|
|
7409
7420
|
async function saveScanResult(rootDir, scores, compositeScore) {
|
|
7410
|
-
const dir =
|
|
7421
|
+
const dir = projectDir(rootDir);
|
|
7411
7422
|
try {
|
|
7412
|
-
await mkdir(dir, { recursive: true });
|
|
7423
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
7413
7424
|
} catch {
|
|
7414
7425
|
}
|
|
7415
7426
|
const data = {
|
|
7416
7427
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7428
|
+
rootDir,
|
|
7417
7429
|
scores,
|
|
7418
7430
|
compositeScore
|
|
7419
7431
|
};
|
|
@@ -7421,7 +7433,7 @@ async function saveScanResult(rootDir, scores, compositeScore) {
|
|
|
7421
7433
|
await writeFile(join4(dir, filename), JSON.stringify(data, null, 2));
|
|
7422
7434
|
}
|
|
7423
7435
|
async function loadPreviousScores(rootDir) {
|
|
7424
|
-
const dir =
|
|
7436
|
+
const dir = projectDir(rootDir);
|
|
7425
7437
|
try {
|
|
7426
7438
|
const files = await readdir2(dir);
|
|
7427
7439
|
const scanFiles = files.filter((f) => f.startsWith("scan-") && f.endsWith(".json")).sort().reverse();
|
|
@@ -7434,12 +7446,196 @@ async function loadPreviousScores(rootDir) {
|
|
|
7434
7446
|
}
|
|
7435
7447
|
}
|
|
7436
7448
|
|
|
7449
|
+
// src/core/file-filter.ts
|
|
7450
|
+
init_esm_shims();
|
|
7451
|
+
function applyIncludeExclude(files, includes, excludes) {
|
|
7452
|
+
const includeRegexes = includes.map(globToRegex);
|
|
7453
|
+
const excludeRegexes = excludes.map(globToRegex);
|
|
7454
|
+
const useInclude = includeRegexes.length > 0;
|
|
7455
|
+
return files.filter((file) => {
|
|
7456
|
+
const path2 = file.relativePath;
|
|
7457
|
+
if (useInclude && !includeRegexes.some((re) => re.test(path2))) {
|
|
7458
|
+
return false;
|
|
7459
|
+
}
|
|
7460
|
+
if (excludeRegexes.some((re) => re.test(path2))) {
|
|
7461
|
+
return false;
|
|
7462
|
+
}
|
|
7463
|
+
return true;
|
|
7464
|
+
});
|
|
7465
|
+
}
|
|
7466
|
+
function globToRegex(glob) {
|
|
7467
|
+
let pattern = glob.replace(/^\.\//, "");
|
|
7468
|
+
if (pattern.endsWith("/")) {
|
|
7469
|
+
pattern = pattern + "**";
|
|
7470
|
+
}
|
|
7471
|
+
let result = "";
|
|
7472
|
+
let i = 0;
|
|
7473
|
+
let inClass = false;
|
|
7474
|
+
while (i < pattern.length) {
|
|
7475
|
+
const ch = pattern[i];
|
|
7476
|
+
if (inClass) {
|
|
7477
|
+
if (ch === "]") {
|
|
7478
|
+
inClass = false;
|
|
7479
|
+
result += "]";
|
|
7480
|
+
} else {
|
|
7481
|
+
result += escapeInClass(ch);
|
|
7482
|
+
}
|
|
7483
|
+
i++;
|
|
7484
|
+
continue;
|
|
7485
|
+
}
|
|
7486
|
+
switch (ch) {
|
|
7487
|
+
case "*": {
|
|
7488
|
+
if (pattern[i + 1] === "*") {
|
|
7489
|
+
if (pattern[i + 2] === "/") {
|
|
7490
|
+
result += "(?:.*/)?";
|
|
7491
|
+
i += 3;
|
|
7492
|
+
} else {
|
|
7493
|
+
result += ".*";
|
|
7494
|
+
i += 2;
|
|
7495
|
+
}
|
|
7496
|
+
} else {
|
|
7497
|
+
result += "[^/]*";
|
|
7498
|
+
i++;
|
|
7499
|
+
}
|
|
7500
|
+
break;
|
|
7501
|
+
}
|
|
7502
|
+
case "?": {
|
|
7503
|
+
result += "[^/]";
|
|
7504
|
+
i++;
|
|
7505
|
+
break;
|
|
7506
|
+
}
|
|
7507
|
+
case "[": {
|
|
7508
|
+
inClass = true;
|
|
7509
|
+
result += "[";
|
|
7510
|
+
i++;
|
|
7511
|
+
break;
|
|
7512
|
+
}
|
|
7513
|
+
case "{": {
|
|
7514
|
+
const close = pattern.indexOf("}", i);
|
|
7515
|
+
if (close === -1) {
|
|
7516
|
+
result += "\\{";
|
|
7517
|
+
i++;
|
|
7518
|
+
break;
|
|
7519
|
+
}
|
|
7520
|
+
const inner = pattern.slice(i + 1, close);
|
|
7521
|
+
const parts = inner.split(",").map((p) => p.trim()).filter(Boolean);
|
|
7522
|
+
if (parts.length > 0) {
|
|
7523
|
+
result += "(?:" + parts.map(escapeRegex2).join("|") + ")";
|
|
7524
|
+
} else {
|
|
7525
|
+
result += "\\{\\}";
|
|
7526
|
+
}
|
|
7527
|
+
i = close + 1;
|
|
7528
|
+
break;
|
|
7529
|
+
}
|
|
7530
|
+
default: {
|
|
7531
|
+
result += escapeRegex2(ch);
|
|
7532
|
+
i++;
|
|
7533
|
+
break;
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
return new RegExp("^" + result + "$");
|
|
7538
|
+
}
|
|
7539
|
+
function escapeRegex2(ch) {
|
|
7540
|
+
if (/[.+^${}()|\\]/.test(ch)) return "\\" + ch;
|
|
7541
|
+
return ch;
|
|
7542
|
+
}
|
|
7543
|
+
function escapeInClass(ch) {
|
|
7544
|
+
if (ch === "\\") return "\\\\";
|
|
7545
|
+
return ch;
|
|
7546
|
+
}
|
|
7547
|
+
|
|
7548
|
+
// src/auth/resolver.ts
|
|
7549
|
+
init_esm_shims();
|
|
7550
|
+
|
|
7551
|
+
// src/auth/config.ts
|
|
7552
|
+
init_esm_shims();
|
|
7553
|
+
import { homedir as homedir2 } from "os";
|
|
7554
|
+
import { join as join5 } from "path";
|
|
7555
|
+
import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2, chmod, unlink, stat as stat2 } from "fs/promises";
|
|
7556
|
+
var DEFAULT_DIR = join5(homedir2(), ".vibedrift");
|
|
7557
|
+
var DEFAULT_FILE = join5(DEFAULT_DIR, "config.json");
|
|
7558
|
+
function getConfigDir() {
|
|
7559
|
+
return DEFAULT_DIR;
|
|
7560
|
+
}
|
|
7561
|
+
function getConfigPath() {
|
|
7562
|
+
return DEFAULT_FILE;
|
|
7563
|
+
}
|
|
7564
|
+
async function ensureConfigDir() {
|
|
7565
|
+
await mkdir2(DEFAULT_DIR, { recursive: true, mode: 448 });
|
|
7566
|
+
}
|
|
7567
|
+
async function readConfig() {
|
|
7568
|
+
try {
|
|
7569
|
+
const raw = await readFile4(DEFAULT_FILE, "utf-8");
|
|
7570
|
+
const parsed = JSON.parse(raw);
|
|
7571
|
+
if (typeof parsed !== "object" || parsed === null) return {};
|
|
7572
|
+
return parsed;
|
|
7573
|
+
} catch (err) {
|
|
7574
|
+
if (err?.code === "ENOENT") return {};
|
|
7575
|
+
process.stderr.write(`vibedrift: warning \u2014 config at ${DEFAULT_FILE} is unreadable (${err?.message ?? err}). Treating as empty.
|
|
7576
|
+
`);
|
|
7577
|
+
return {};
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
async function writeConfig(config) {
|
|
7581
|
+
await ensureConfigDir();
|
|
7582
|
+
const json = JSON.stringify(config, null, 2);
|
|
7583
|
+
await writeFile2(DEFAULT_FILE, json, { mode: 384 });
|
|
7584
|
+
try {
|
|
7585
|
+
await chmod(DEFAULT_FILE, 384);
|
|
7586
|
+
} catch {
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
async function clearConfig() {
|
|
7590
|
+
try {
|
|
7591
|
+
await unlink(DEFAULT_FILE);
|
|
7592
|
+
} catch (err) {
|
|
7593
|
+
if (err?.code !== "ENOENT") throw err;
|
|
7594
|
+
}
|
|
7595
|
+
}
|
|
7596
|
+
async function patchConfig(patch) {
|
|
7597
|
+
const current = await readConfig();
|
|
7598
|
+
const next = { ...current, ...patch };
|
|
7599
|
+
await writeConfig(next);
|
|
7600
|
+
return next;
|
|
7601
|
+
}
|
|
7602
|
+
|
|
7603
|
+
// src/auth/resolver.ts
|
|
7604
|
+
async function resolveToken(input = {}) {
|
|
7605
|
+
if (input.explicitToken && input.explicitToken.trim().length > 0) {
|
|
7606
|
+
return { token: input.explicitToken.trim(), source: "flag" };
|
|
7607
|
+
}
|
|
7608
|
+
const fromEnv = process.env.VIBEDRIFT_TOKEN;
|
|
7609
|
+
if (fromEnv && fromEnv.trim().length > 0) {
|
|
7610
|
+
return { token: fromEnv.trim(), source: "env" };
|
|
7611
|
+
}
|
|
7612
|
+
const config = await readConfig();
|
|
7613
|
+
if (config.token && config.token.trim().length > 0) {
|
|
7614
|
+
return { token: config.token.trim(), source: "config" };
|
|
7615
|
+
}
|
|
7616
|
+
return null;
|
|
7617
|
+
}
|
|
7618
|
+
async function resolveApiUrl(explicitUrl) {
|
|
7619
|
+
if (explicitUrl && explicitUrl.trim().length > 0) return explicitUrl.trim();
|
|
7620
|
+
if (process.env.VIBEDRIFT_API_URL && process.env.VIBEDRIFT_API_URL.trim().length > 0) {
|
|
7621
|
+
return process.env.VIBEDRIFT_API_URL.trim();
|
|
7622
|
+
}
|
|
7623
|
+
const config = await readConfig();
|
|
7624
|
+
if (config.apiUrl && config.apiUrl.trim().length > 0) return config.apiUrl.trim();
|
|
7625
|
+
return "https://vibedrift-api.fly.dev";
|
|
7626
|
+
}
|
|
7627
|
+
function previewToken(token) {
|
|
7628
|
+
if (!token) return "(none)";
|
|
7629
|
+
if (token.length <= 12) return token.slice(0, 4) + "\u2026";
|
|
7630
|
+
return token.slice(0, 12) + "\u2026";
|
|
7631
|
+
}
|
|
7632
|
+
|
|
7437
7633
|
// src/cli/commands/scan.ts
|
|
7438
7634
|
async function runScan(targetPath, options) {
|
|
7439
7635
|
const rootDir = resolve(targetPath);
|
|
7440
7636
|
try {
|
|
7441
|
-
const
|
|
7442
|
-
if (!
|
|
7637
|
+
const info2 = await stat3(rootDir);
|
|
7638
|
+
if (!info2.isDirectory()) {
|
|
7443
7639
|
console.error(`Error: ${rootDir} is not a directory`);
|
|
7444
7640
|
process.exit(1);
|
|
7445
7641
|
}
|
|
@@ -7447,12 +7643,39 @@ async function runScan(targetPath, options) {
|
|
|
7447
7643
|
console.error(`Error: ${rootDir} does not exist`);
|
|
7448
7644
|
process.exit(1);
|
|
7449
7645
|
}
|
|
7646
|
+
let bearerToken = null;
|
|
7647
|
+
let apiUrl = options.apiUrl;
|
|
7648
|
+
if (options.deep) {
|
|
7649
|
+
const resolved = await resolveToken();
|
|
7650
|
+
if (!resolved) {
|
|
7651
|
+
console.error("");
|
|
7652
|
+
console.error(chalk2.red(" \u2717 Deep scans require a VibeDrift account."));
|
|
7653
|
+
console.error("");
|
|
7654
|
+
console.error(" Run " + chalk2.bold("vibedrift login") + " to sign in.");
|
|
7655
|
+
console.error(" Or set " + chalk2.bold("VIBEDRIFT_TOKEN") + " in your environment for CI.");
|
|
7656
|
+
console.error("");
|
|
7657
|
+
process.exit(1);
|
|
7658
|
+
}
|
|
7659
|
+
bearerToken = resolved.token;
|
|
7660
|
+
apiUrl = await resolveApiUrl(options.apiUrl);
|
|
7661
|
+
}
|
|
7450
7662
|
const startTime = Date.now();
|
|
7451
7663
|
const timings = {};
|
|
7452
7664
|
const isTerminal = options.format === "terminal" && !options.json;
|
|
7453
7665
|
const spinner = isTerminal ? ora("Discovering files...").start() : null;
|
|
7454
7666
|
const t0 = Date.now();
|
|
7455
7667
|
const { ctx, warnings } = await buildAnalysisContext(rootDir);
|
|
7668
|
+
const includes = options.include ?? [];
|
|
7669
|
+
const excludes = options.exclude ?? [];
|
|
7670
|
+
if (includes.length > 0 || excludes.length > 0) {
|
|
7671
|
+
const before = ctx.files.length;
|
|
7672
|
+
const filtered = applyIncludeExclude(ctx.files, includes, excludes);
|
|
7673
|
+
ctx.files = filtered;
|
|
7674
|
+
ctx.totalLines = filtered.reduce((sum, f) => sum + f.lineCount, 0);
|
|
7675
|
+
if (options.verbose) {
|
|
7676
|
+
console.error(chalk2.dim(`[filter] ${before} \u2192 ${filtered.length} files after include/exclude`));
|
|
7677
|
+
}
|
|
7678
|
+
}
|
|
7456
7679
|
timings.discovery = Date.now() - t0;
|
|
7457
7680
|
if (isTerminal) {
|
|
7458
7681
|
if (warnings.truncated) {
|
|
@@ -7503,26 +7726,27 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7503
7726
|
}
|
|
7504
7727
|
}
|
|
7505
7728
|
let mlMediumConfidence = [];
|
|
7506
|
-
if (options.
|
|
7729
|
+
if (options.deep && bearerToken) {
|
|
7507
7730
|
const t5 = Date.now();
|
|
7508
|
-
if (spinner) spinner.text = "
|
|
7731
|
+
if (spinner) spinner.text = "Running AI deep analysis (may take ~30s on cold start)...";
|
|
7509
7732
|
try {
|
|
7510
7733
|
const { runMlAnalysis: runMlAnalysis2 } = await Promise.resolve().then(() => (init_ml_client(), ml_client_exports));
|
|
7511
7734
|
const mlResult = await runMlAnalysis2(ctx, codeDnaResult, allFindings, {
|
|
7512
|
-
|
|
7513
|
-
|
|
7735
|
+
token: bearerToken,
|
|
7736
|
+
apiUrl,
|
|
7514
7737
|
verbose: options.verbose,
|
|
7515
7738
|
driftFindings: driftResult.driftFindings
|
|
7516
7739
|
});
|
|
7517
7740
|
allFindings.push(...mlResult.highConfidence);
|
|
7518
7741
|
mlMediumConfidence = mlResult.mediumConfidence;
|
|
7519
7742
|
if (options.verbose) {
|
|
7520
|
-
console.error(`[
|
|
7743
|
+
console.error(`[deep] ${mlResult.highConfidence.length} high-confidence findings shipped, ${mlResult.mediumConfidence.length} sent to LLM, ${mlResult.droppedCount} dropped`);
|
|
7521
7744
|
}
|
|
7522
7745
|
} catch (err) {
|
|
7523
|
-
console.error(`[
|
|
7746
|
+
console.error(chalk2.red(`[deep] AI analysis failed: ${err.message}`));
|
|
7747
|
+
console.error(chalk2.dim(" The local scan will continue. Run `vibedrift doctor` if this persists."));
|
|
7524
7748
|
}
|
|
7525
|
-
timings.
|
|
7749
|
+
timings.deep = Date.now() - t5;
|
|
7526
7750
|
}
|
|
7527
7751
|
const { deduplicateFindingsAcrossLayers: deduplicateFindingsAcrossLayers2 } = await Promise.resolve().then(() => (init_dedup(), dedup_exports));
|
|
7528
7752
|
const dedupedCount = allFindings.length;
|
|
@@ -7541,13 +7765,13 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7541
7765
|
previousScores ?? void 0
|
|
7542
7766
|
);
|
|
7543
7767
|
const deepInsights = [];
|
|
7544
|
-
const teaseMessages = generateTeaseMessages(ctx, allFindings,
|
|
7768
|
+
const teaseMessages = generateTeaseMessages(ctx, allFindings, options.deep === true);
|
|
7545
7769
|
const scanTimeMs = Date.now() - startTime;
|
|
7546
7770
|
if (spinner) spinner.stop();
|
|
7547
7771
|
const layer1Ms = (timings.discovery ?? 0) + (timings.parsing ?? 0) + (timings.analyzers ?? 0) + (timings.drift ?? 0);
|
|
7548
7772
|
const parts = [`Layer 1: ${(layer1Ms / 1e3).toFixed(1)}s`];
|
|
7549
7773
|
if (timings.codedna) parts.push(`Code DNA: ${(timings.codedna / 1e3).toFixed(1)}s`);
|
|
7550
|
-
if (timings.
|
|
7774
|
+
if (timings.deep) parts.push(`AI: ${(timings.deep / 1e3).toFixed(1)}s`);
|
|
7551
7775
|
parts.push(`Total: ${(scanTimeMs / 1e3).toFixed(1)}s`);
|
|
7552
7776
|
console.error(chalk2.dim(` ${parts.join(" \xB7 ")}`));
|
|
7553
7777
|
const result = {
|
|
@@ -7564,13 +7788,13 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7564
7788
|
perFileScores,
|
|
7565
7789
|
codeDnaResult
|
|
7566
7790
|
};
|
|
7567
|
-
if (options.
|
|
7791
|
+
if (options.deep && bearerToken) {
|
|
7568
7792
|
if (spinner) spinner.text = "Generating AI summary...";
|
|
7569
7793
|
try {
|
|
7570
7794
|
const { fetchAiSummary: fetchAiSummary2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
|
|
7571
|
-
const
|
|
7572
|
-
if (options.verbose) console.error(`[summary] Calling ${
|
|
7573
|
-
const summary = await fetchAiSummary2(result,
|
|
7795
|
+
const targetUrl = apiUrl ?? "https://vibedrift-api.fly.dev";
|
|
7796
|
+
if (options.verbose) console.error(`[summary] Calling ${targetUrl}/v1/summarize...`);
|
|
7797
|
+
const summary = await fetchAiSummary2(result, targetUrl, bearerToken);
|
|
7574
7798
|
if (summary) {
|
|
7575
7799
|
result.aiSummary = summary;
|
|
7576
7800
|
if (options.verbose) console.error(`[summary] AI summary generated (${summary.highlights.length} highlights)`);
|
|
@@ -7586,7 +7810,7 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7586
7810
|
if (format === "html") {
|
|
7587
7811
|
const html = renderHtmlReport(result);
|
|
7588
7812
|
const outputPath = options.output ?? "vibedrift-report.html";
|
|
7589
|
-
await
|
|
7813
|
+
await writeFile3(outputPath, html);
|
|
7590
7814
|
const { createServer } = await import("http");
|
|
7591
7815
|
const server = createServer((req, res) => {
|
|
7592
7816
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -7612,18 +7836,18 @@ Warning: File limit reached (${warnings.truncatedAt}). Only partial coverage \u2
|
|
|
7612
7836
|
const { renderCsvReport: renderCsvReport2 } = await Promise.resolve().then(() => (init_csv(), csv_exports));
|
|
7613
7837
|
const csv = renderCsvReport2(result);
|
|
7614
7838
|
const outputPath = options.output ?? "vibedrift-report.csv";
|
|
7615
|
-
await
|
|
7839
|
+
await writeFile3(outputPath, csv);
|
|
7616
7840
|
console.log(`CSV report written to ${outputPath}`);
|
|
7617
7841
|
} else if (format === "docx") {
|
|
7618
7842
|
const { renderDocxReport: renderDocxReport2 } = await Promise.resolve().then(() => (init_docx(), docx_exports));
|
|
7619
7843
|
const docx = renderDocxReport2(result);
|
|
7620
7844
|
const outputPath = options.output ?? "vibedrift-report.docx";
|
|
7621
|
-
await
|
|
7845
|
+
await writeFile3(outputPath, docx);
|
|
7622
7846
|
console.log(`DOCX report written to ${outputPath}`);
|
|
7623
7847
|
} else if (format === "json") {
|
|
7624
7848
|
const json = renderJsonOutput(result);
|
|
7625
7849
|
if (options.output) {
|
|
7626
|
-
await
|
|
7850
|
+
await writeFile3(options.output, json);
|
|
7627
7851
|
console.log(`JSON report written to ${options.output}`);
|
|
7628
7852
|
} else {
|
|
7629
7853
|
console.log(json);
|
|
@@ -7728,6 +7952,614 @@ Update failed: ${message}`));
|
|
|
7728
7952
|
});
|
|
7729
7953
|
}
|
|
7730
7954
|
|
|
7955
|
+
// src/cli/commands/login.ts
|
|
7956
|
+
init_esm_shims();
|
|
7957
|
+
import chalk4 from "chalk";
|
|
7958
|
+
|
|
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
|
+
// src/auth/browser.ts
|
|
8051
|
+
init_esm_shims();
|
|
8052
|
+
import { spawn as spawn2 } from "child_process";
|
|
8053
|
+
function openInBrowser(url) {
|
|
8054
|
+
if (!isInteractive()) return false;
|
|
8055
|
+
const env = process.env.BROWSER;
|
|
8056
|
+
if (env && env.length > 0 && env !== "none") {
|
|
8057
|
+
return spawnDetached(env, [url]);
|
|
8058
|
+
}
|
|
8059
|
+
switch (process.platform) {
|
|
8060
|
+
case "darwin":
|
|
8061
|
+
return spawnDetached("open", [url]);
|
|
8062
|
+
case "win32":
|
|
8063
|
+
return spawnDetached("cmd", ["/c", "start", "", url]);
|
|
8064
|
+
default:
|
|
8065
|
+
return spawnDetached("xdg-open", [url]);
|
|
8066
|
+
}
|
|
8067
|
+
}
|
|
8068
|
+
function isInteractive() {
|
|
8069
|
+
if (process.env.CI === "true" || process.env.CI === "1") return false;
|
|
8070
|
+
if (process.env.VIBEDRIFT_NO_BROWSER === "1") return false;
|
|
8071
|
+
return process.stdout.isTTY ?? false;
|
|
8072
|
+
}
|
|
8073
|
+
function spawnDetached(cmd, args) {
|
|
8074
|
+
try {
|
|
8075
|
+
const child = spawn2(cmd, args, {
|
|
8076
|
+
detached: true,
|
|
8077
|
+
stdio: "ignore",
|
|
8078
|
+
shell: false
|
|
8079
|
+
});
|
|
8080
|
+
child.on("error", () => {
|
|
8081
|
+
});
|
|
8082
|
+
child.unref();
|
|
8083
|
+
return true;
|
|
8084
|
+
} catch {
|
|
8085
|
+
return false;
|
|
8086
|
+
}
|
|
8087
|
+
}
|
|
8088
|
+
|
|
8089
|
+
// src/cli/commands/login.ts
|
|
8090
|
+
async function runLogin(options = {}) {
|
|
8091
|
+
const existing = await readConfig();
|
|
8092
|
+
if (existing.token) {
|
|
8093
|
+
console.log(
|
|
8094
|
+
chalk4.yellow(
|
|
8095
|
+
`
|
|
8096
|
+
You're already logged in as ${chalk4.bold(existing.email ?? "unknown")} (${existing.plan ?? "free"}).`
|
|
8097
|
+
)
|
|
8098
|
+
);
|
|
8099
|
+
console.log(chalk4.dim(` Token: ${previewToken(existing.token)}`));
|
|
8100
|
+
console.log(chalk4.dim(" Continuing will replace this token.\n"));
|
|
8101
|
+
}
|
|
8102
|
+
let device;
|
|
8103
|
+
try {
|
|
8104
|
+
device = await startDeviceAuth({ apiUrl: options.apiUrl });
|
|
8105
|
+
} catch (err) {
|
|
8106
|
+
fail("Could not start the login flow", err);
|
|
8107
|
+
return;
|
|
8108
|
+
}
|
|
8109
|
+
console.log("");
|
|
8110
|
+
console.log(chalk4.bold(" First, copy your one-time code:"));
|
|
8111
|
+
console.log("");
|
|
8112
|
+
console.log(` ${chalk4.bgYellow.black.bold(` ${device.user_code} `)}`);
|
|
8113
|
+
console.log("");
|
|
8114
|
+
console.log(chalk4.dim(` This code expires in ${formatDuration(device.expires_in)}.`));
|
|
8115
|
+
console.log("");
|
|
8116
|
+
const opened = !options.noBrowser && openInBrowser(device.verification_uri_complete);
|
|
8117
|
+
if (opened) {
|
|
8118
|
+
console.log(chalk4.bold(" Opened your browser to:"));
|
|
8119
|
+
} else {
|
|
8120
|
+
console.log(chalk4.bold(" Open this URL in your browser:"));
|
|
8121
|
+
}
|
|
8122
|
+
console.log(` ${chalk4.cyan(device.verification_uri_complete)}`);
|
|
8123
|
+
console.log("");
|
|
8124
|
+
console.log(chalk4.dim(" Waiting for you to authorize the CLI..."));
|
|
8125
|
+
console.log("");
|
|
8126
|
+
let interval = Math.max(1, device.interval);
|
|
8127
|
+
const deadline = Date.now() + device.expires_in * 1e3;
|
|
8128
|
+
while (Date.now() < deadline) {
|
|
8129
|
+
await sleep(interval * 1e3);
|
|
8130
|
+
let result;
|
|
8131
|
+
try {
|
|
8132
|
+
result = await pollDeviceAuth(device.device_code, { apiUrl: options.apiUrl });
|
|
8133
|
+
} catch (err) {
|
|
8134
|
+
if (err instanceof VibeDriftApiError && err.status === 429) {
|
|
8135
|
+
interval = Math.min(interval * 2, 30);
|
|
8136
|
+
continue;
|
|
8137
|
+
}
|
|
8138
|
+
fail("Polling for authorization failed", err);
|
|
8139
|
+
return;
|
|
8140
|
+
}
|
|
8141
|
+
if (result.status === "pending") continue;
|
|
8142
|
+
if (result.status === "authorized") {
|
|
8143
|
+
await patchConfig({
|
|
8144
|
+
token: result.access_token,
|
|
8145
|
+
email: result.email,
|
|
8146
|
+
plan: result.plan,
|
|
8147
|
+
expiresAt: result.expires_at,
|
|
8148
|
+
loggedInAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8149
|
+
apiUrl: options.apiUrl
|
|
8150
|
+
});
|
|
8151
|
+
console.log(chalk4.green(" \u2713 Logged in successfully."));
|
|
8152
|
+
console.log("");
|
|
8153
|
+
console.log(` Account: ${chalk4.bold(result.email)}`);
|
|
8154
|
+
console.log(` Plan: ${chalk4.bold(result.plan)}`);
|
|
8155
|
+
console.log("");
|
|
8156
|
+
if (result.plan === "free") {
|
|
8157
|
+
console.log(chalk4.dim(" Run `vibedrift upgrade` to enable deep AI scans."));
|
|
8158
|
+
console.log("");
|
|
8159
|
+
} else {
|
|
8160
|
+
console.log(chalk4.dim(" Run `vibedrift . --deep` to use AI-powered analysis."));
|
|
8161
|
+
console.log("");
|
|
8162
|
+
}
|
|
8163
|
+
return;
|
|
8164
|
+
}
|
|
8165
|
+
if (result.status === "denied") {
|
|
8166
|
+
console.error(chalk4.red("\n \u2717 Authorization was denied in the browser."));
|
|
8167
|
+
console.error(chalk4.dim(" Run `vibedrift login` again to retry.\n"));
|
|
8168
|
+
process.exit(1);
|
|
8169
|
+
}
|
|
8170
|
+
if (result.status === "expired") {
|
|
8171
|
+
console.error(chalk4.red("\n \u2717 The login code expired before you authorized it."));
|
|
8172
|
+
console.error(chalk4.dim(" Run `vibedrift login` again to retry.\n"));
|
|
8173
|
+
process.exit(1);
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
console.error(chalk4.red("\n \u2717 Login timed out before authorization completed."));
|
|
8177
|
+
console.error(chalk4.dim(" Run `vibedrift login` again to retry.\n"));
|
|
8178
|
+
process.exit(1);
|
|
8179
|
+
}
|
|
8180
|
+
function fail(intro, err) {
|
|
8181
|
+
const msg = err instanceof VibeDriftApiError ? `${err.status ? `HTTP ${err.status}: ` : ""}${err.message}` : err instanceof Error ? err.message : String(err);
|
|
8182
|
+
console.error(chalk4.red(`
|
|
8183
|
+
\u2717 ${intro}: ${msg}
|
|
8184
|
+
`));
|
|
8185
|
+
process.exit(1);
|
|
8186
|
+
}
|
|
8187
|
+
function sleep(ms) {
|
|
8188
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
8189
|
+
}
|
|
8190
|
+
function formatDuration(seconds) {
|
|
8191
|
+
if (seconds < 60) return `${seconds}s`;
|
|
8192
|
+
const m = Math.round(seconds / 60);
|
|
8193
|
+
return `${m} minute${m === 1 ? "" : "s"}`;
|
|
8194
|
+
}
|
|
8195
|
+
|
|
8196
|
+
// src/cli/commands/logout.ts
|
|
8197
|
+
init_esm_shims();
|
|
8198
|
+
import chalk5 from "chalk";
|
|
8199
|
+
async function runLogout() {
|
|
8200
|
+
const config = await readConfig();
|
|
8201
|
+
if (!config.token) {
|
|
8202
|
+
console.log(chalk5.dim(" Not logged in. Nothing to do."));
|
|
8203
|
+
return;
|
|
8204
|
+
}
|
|
8205
|
+
try {
|
|
8206
|
+
await revokeToken(config.token, { apiUrl: config.apiUrl });
|
|
8207
|
+
} catch (err) {
|
|
8208
|
+
if (err instanceof VibeDriftApiError && (err.status === 401 || err.status === 404)) {
|
|
8209
|
+
} else {
|
|
8210
|
+
console.warn(
|
|
8211
|
+
chalk5.yellow(
|
|
8212
|
+
` \u26A0 Could not revoke token on the server: ${err instanceof Error ? err.message : String(err)}`
|
|
8213
|
+
)
|
|
8214
|
+
);
|
|
8215
|
+
console.warn(chalk5.dim(" Local token will still be removed."));
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
await clearConfig();
|
|
8219
|
+
console.log(chalk5.green(" \u2713 Logged out."));
|
|
8220
|
+
}
|
|
8221
|
+
|
|
8222
|
+
// src/cli/commands/status.ts
|
|
8223
|
+
init_esm_shims();
|
|
8224
|
+
import chalk6 from "chalk";
|
|
8225
|
+
init_version();
|
|
8226
|
+
async function runStatus() {
|
|
8227
|
+
const version = getVersion();
|
|
8228
|
+
const config = await readConfig();
|
|
8229
|
+
const resolved = await resolveToken();
|
|
8230
|
+
console.log("");
|
|
8231
|
+
console.log(chalk6.bold(` VibeDrift CLI v${version}`));
|
|
8232
|
+
console.log("");
|
|
8233
|
+
if (!resolved) {
|
|
8234
|
+
console.log(` Status: ${chalk6.dim("not logged in")}`);
|
|
8235
|
+
console.log(` Config: ${chalk6.dim(getConfigPath())}`);
|
|
8236
|
+
console.log("");
|
|
8237
|
+
console.log(chalk6.dim(" Run `vibedrift login` to authenticate."));
|
|
8238
|
+
console.log("");
|
|
8239
|
+
return;
|
|
8240
|
+
}
|
|
8241
|
+
console.log(` Status: ${chalk6.green("authenticated")}`);
|
|
8242
|
+
console.log(` Source: ${chalk6.dim(describeSource(resolved.source))}`);
|
|
8243
|
+
console.log(` Token: ${chalk6.dim(previewToken(resolved.token))}`);
|
|
8244
|
+
if (resolved.source === "config") {
|
|
8245
|
+
if (config.email) console.log(` Account: ${chalk6.bold(config.email)}`);
|
|
8246
|
+
if (config.plan) console.log(` Plan: ${chalk6.bold(config.plan)}`);
|
|
8247
|
+
if (config.expiresAt) console.log(` Expires: ${chalk6.dim(config.expiresAt)}`);
|
|
8248
|
+
console.log(` Config: ${chalk6.dim(getConfigPath())}`);
|
|
8249
|
+
}
|
|
8250
|
+
console.log("");
|
|
8251
|
+
process.stdout.write(chalk6.dim(" Validating token with server... "));
|
|
8252
|
+
try {
|
|
8253
|
+
const result = await validateToken(resolved.token, { apiUrl: config.apiUrl });
|
|
8254
|
+
if (result.valid) {
|
|
8255
|
+
console.log(chalk6.green("ok"));
|
|
8256
|
+
if (result.email && result.email !== config.email) {
|
|
8257
|
+
console.log(chalk6.dim(` Server account: ${result.email} (config out of sync \u2014 run \`vibedrift login\` to refresh)`));
|
|
8258
|
+
}
|
|
8259
|
+
if (result.plan && result.plan !== config.plan) {
|
|
8260
|
+
console.log(chalk6.dim(` Server plan: ${result.plan} (config out of sync \u2014 run \`vibedrift login\` to refresh)`));
|
|
8261
|
+
}
|
|
8262
|
+
} else {
|
|
8263
|
+
console.log(chalk6.red("invalid"));
|
|
8264
|
+
console.log(chalk6.dim(" Run `vibedrift login` to re-authenticate."));
|
|
8265
|
+
}
|
|
8266
|
+
} catch (err) {
|
|
8267
|
+
console.log(chalk6.yellow("offline"));
|
|
8268
|
+
if (err instanceof VibeDriftApiError) {
|
|
8269
|
+
console.log(chalk6.dim(` ${err.message}`));
|
|
8270
|
+
}
|
|
8271
|
+
}
|
|
8272
|
+
console.log("");
|
|
8273
|
+
}
|
|
8274
|
+
function describeSource(source) {
|
|
8275
|
+
switch (source) {
|
|
8276
|
+
case "flag":
|
|
8277
|
+
return "command-line flag";
|
|
8278
|
+
case "env":
|
|
8279
|
+
return "VIBEDRIFT_TOKEN environment variable";
|
|
8280
|
+
case "config":
|
|
8281
|
+
return "~/.vibedrift/config.json";
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
|
|
8285
|
+
// src/cli/commands/usage.ts
|
|
8286
|
+
init_esm_shims();
|
|
8287
|
+
import chalk7 from "chalk";
|
|
8288
|
+
async function runUsage() {
|
|
8289
|
+
const resolved = await resolveToken();
|
|
8290
|
+
if (!resolved) {
|
|
8291
|
+
console.error(chalk7.red("\n \u2717 Not logged in. Run `vibedrift login` first.\n"));
|
|
8292
|
+
process.exit(1);
|
|
8293
|
+
}
|
|
8294
|
+
const config = await readConfig();
|
|
8295
|
+
let data;
|
|
8296
|
+
try {
|
|
8297
|
+
data = await fetchUsage(resolved.token, { apiUrl: config.apiUrl });
|
|
8298
|
+
} catch (err) {
|
|
8299
|
+
if (err instanceof VibeDriftApiError && err.status === 401) {
|
|
8300
|
+
console.error(chalk7.red("\n \u2717 Your token is invalid or expired. Run `vibedrift login` to re-authenticate.\n"));
|
|
8301
|
+
process.exit(1);
|
|
8302
|
+
}
|
|
8303
|
+
console.error(chalk7.red(`
|
|
8304
|
+
\u2717 Could not fetch usage: ${err instanceof Error ? err.message : String(err)}
|
|
8305
|
+
`));
|
|
8306
|
+
process.exit(1);
|
|
8307
|
+
}
|
|
8308
|
+
console.log("");
|
|
8309
|
+
console.log(chalk7.bold(" Account"));
|
|
8310
|
+
console.log(` Email: ${chalk7.bold(data.user.email)}`);
|
|
8311
|
+
console.log(` Plan: ${chalk7.bold(data.user.plan)}`);
|
|
8312
|
+
console.log("");
|
|
8313
|
+
console.log(chalk7.bold(" Current period"));
|
|
8314
|
+
console.log(` From: ${chalk7.dim(formatDate(data.current_period.start))}`);
|
|
8315
|
+
console.log(` To: ${chalk7.dim(formatDate(data.current_period.end))}`);
|
|
8316
|
+
console.log(` Scans: ${chalk7.bold(data.current_period.scans.toString())}`);
|
|
8317
|
+
console.log(` Deep: ${chalk7.bold(data.current_period.deep_scans.toString())}`);
|
|
8318
|
+
console.log("");
|
|
8319
|
+
console.log(chalk7.bold(" Limits"));
|
|
8320
|
+
const deepLimit = data.limits.deep_scans_per_month;
|
|
8321
|
+
console.log(` Deep: ${deepLimit === null ? chalk7.green("unlimited") : `${deepLimit}/month`}`);
|
|
8322
|
+
console.log(` Rate: ${data.limits.rate_limit_per_min} requests/minute`);
|
|
8323
|
+
console.log("");
|
|
8324
|
+
if (data.recent_scans.length > 0) {
|
|
8325
|
+
console.log(chalk7.bold(` Recent scans (${data.recent_scans.length})`));
|
|
8326
|
+
for (const scan of data.recent_scans.slice(0, 10)) {
|
|
8327
|
+
const flag = scan.is_deep ? chalk7.cyan("deep") : chalk7.dim("std ");
|
|
8328
|
+
const score = scan.score === null ? chalk7.dim("\u2014") : chalk7.bold(String(Math.round(scan.score))).padEnd(3);
|
|
8329
|
+
console.log(` ${flag} ${score} ${chalk7.dim(formatDateTime(scan.created_at))} ${chalk7.dim(scan.project_hash.slice(0, 12))}`);
|
|
8330
|
+
}
|
|
8331
|
+
console.log("");
|
|
8332
|
+
}
|
|
8333
|
+
}
|
|
8334
|
+
function formatDate(iso) {
|
|
8335
|
+
try {
|
|
8336
|
+
return new Date(iso).toISOString().slice(0, 10);
|
|
8337
|
+
} catch {
|
|
8338
|
+
return iso;
|
|
8339
|
+
}
|
|
8340
|
+
}
|
|
8341
|
+
function formatDateTime(iso) {
|
|
8342
|
+
try {
|
|
8343
|
+
const d = new Date(iso);
|
|
8344
|
+
return d.toISOString().slice(0, 16).replace("T", " ");
|
|
8345
|
+
} catch {
|
|
8346
|
+
return iso;
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8349
|
+
|
|
8350
|
+
// src/cli/commands/upgrade.ts
|
|
8351
|
+
init_esm_shims();
|
|
8352
|
+
import chalk8 from "chalk";
|
|
8353
|
+
var PRICING_URL = "https://vibedrift.ai/pricing";
|
|
8354
|
+
async function runUpgrade() {
|
|
8355
|
+
console.log("");
|
|
8356
|
+
console.log(chalk8.bold(" Upgrade your VibeDrift plan"));
|
|
8357
|
+
console.log("");
|
|
8358
|
+
console.log(` ${chalk8.cyan(PRICING_URL)}`);
|
|
8359
|
+
console.log("");
|
|
8360
|
+
const opened = openInBrowser(PRICING_URL);
|
|
8361
|
+
if (opened) {
|
|
8362
|
+
console.log(chalk8.dim(" Opened in your browser."));
|
|
8363
|
+
} else {
|
|
8364
|
+
console.log(chalk8.dim(" Open the link above in your browser."));
|
|
8365
|
+
}
|
|
8366
|
+
console.log("");
|
|
8367
|
+
console.log(chalk8.dim(" After upgrading, run `vibedrift login` to refresh your plan locally."));
|
|
8368
|
+
console.log("");
|
|
8369
|
+
}
|
|
8370
|
+
|
|
8371
|
+
// src/cli/commands/billing.ts
|
|
8372
|
+
init_esm_shims();
|
|
8373
|
+
import chalk9 from "chalk";
|
|
8374
|
+
async function runBilling() {
|
|
8375
|
+
const resolved = await resolveToken();
|
|
8376
|
+
if (!resolved) {
|
|
8377
|
+
console.error(chalk9.red("\n \u2717 Not logged in. Run `vibedrift login` first.\n"));
|
|
8378
|
+
process.exit(1);
|
|
8379
|
+
}
|
|
8380
|
+
const config = await readConfig();
|
|
8381
|
+
let portal;
|
|
8382
|
+
try {
|
|
8383
|
+
portal = await createPortalSession(resolved.token, { apiUrl: config.apiUrl });
|
|
8384
|
+
} catch (err) {
|
|
8385
|
+
if (err instanceof VibeDriftApiError) {
|
|
8386
|
+
if (err.status === 401) {
|
|
8387
|
+
console.error(chalk9.red("\n \u2717 Your token is invalid or expired. Run `vibedrift login`.\n"));
|
|
8388
|
+
process.exit(1);
|
|
8389
|
+
}
|
|
8390
|
+
if (err.status === 402 || err.status === 404) {
|
|
8391
|
+
console.error(chalk9.yellow("\n \u26A0 No billing account found for this user."));
|
|
8392
|
+
console.error(chalk9.dim(" Run `vibedrift upgrade` to start a paid plan first.\n"));
|
|
8393
|
+
process.exit(1);
|
|
8394
|
+
}
|
|
8395
|
+
}
|
|
8396
|
+
console.error(chalk9.red(`
|
|
8397
|
+
\u2717 Could not open billing portal: ${err instanceof Error ? err.message : String(err)}
|
|
8398
|
+
`));
|
|
8399
|
+
process.exit(1);
|
|
8400
|
+
}
|
|
8401
|
+
console.log("");
|
|
8402
|
+
console.log(chalk9.bold(" Stripe Customer Portal"));
|
|
8403
|
+
console.log("");
|
|
8404
|
+
console.log(` ${chalk9.cyan(portal.url)}`);
|
|
8405
|
+
console.log("");
|
|
8406
|
+
const opened = openInBrowser(portal.url);
|
|
8407
|
+
if (opened) {
|
|
8408
|
+
console.log(chalk9.dim(" Opened in your browser. The link is single-use and expires shortly."));
|
|
8409
|
+
} else {
|
|
8410
|
+
console.log(chalk9.dim(" Open the link above in your browser. It's single-use and expires shortly."));
|
|
8411
|
+
}
|
|
8412
|
+
console.log("");
|
|
8413
|
+
}
|
|
8414
|
+
|
|
8415
|
+
// src/cli/commands/doctor.ts
|
|
8416
|
+
init_esm_shims();
|
|
8417
|
+
import chalk10 from "chalk";
|
|
8418
|
+
import { homedir as homedir3, platform, arch } from "os";
|
|
8419
|
+
import { join as join6 } from "path";
|
|
8420
|
+
import { stat as stat4, access, constants } from "fs/promises";
|
|
8421
|
+
init_version();
|
|
8422
|
+
async function runDoctor() {
|
|
8423
|
+
let failures = 0;
|
|
8424
|
+
console.log("");
|
|
8425
|
+
console.log(chalk10.bold(" VibeDrift Doctor"));
|
|
8426
|
+
console.log("");
|
|
8427
|
+
console.log(chalk10.bold(" Environment"));
|
|
8428
|
+
ok("CLI version", getVersion());
|
|
8429
|
+
ok("Node", process.version);
|
|
8430
|
+
ok("Platform", `${platform()} ${arch()}`);
|
|
8431
|
+
ok("HOME", homedir3());
|
|
8432
|
+
console.log("");
|
|
8433
|
+
console.log(chalk10.bold(" Config"));
|
|
8434
|
+
const configDir = getConfigDir();
|
|
8435
|
+
const configPath = getConfigPath();
|
|
8436
|
+
let configDirOk = false;
|
|
8437
|
+
try {
|
|
8438
|
+
const info2 = await stat4(configDir);
|
|
8439
|
+
if (info2.isDirectory()) {
|
|
8440
|
+
configDirOk = true;
|
|
8441
|
+
const mode = (info2.mode & 511).toString(8);
|
|
8442
|
+
ok("Config dir", `${configDir} (mode ${mode})`);
|
|
8443
|
+
} else {
|
|
8444
|
+
bad(`Config dir exists but is not a directory: ${configDir}`);
|
|
8445
|
+
failures++;
|
|
8446
|
+
}
|
|
8447
|
+
} catch {
|
|
8448
|
+
info("Config dir", `${configDir} (will be created on first login)`);
|
|
8449
|
+
configDirOk = true;
|
|
8450
|
+
}
|
|
8451
|
+
if (configDirOk) {
|
|
8452
|
+
try {
|
|
8453
|
+
await access(configPath, constants.R_OK);
|
|
8454
|
+
const info2 = await stat4(configPath);
|
|
8455
|
+
const mode = (info2.mode & 511).toString(8);
|
|
8456
|
+
if ((info2.mode & 63) !== 0) {
|
|
8457
|
+
warn("Config file", `${configPath} (mode ${mode}, world/group readable \u2014 should be 600)`);
|
|
8458
|
+
} else {
|
|
8459
|
+
ok("Config file", `${configPath} (mode ${mode})`);
|
|
8460
|
+
}
|
|
8461
|
+
} catch {
|
|
8462
|
+
info("Config file", "absent (not logged in)");
|
|
8463
|
+
}
|
|
8464
|
+
}
|
|
8465
|
+
const historyDir = join6(homedir3(), ".vibedrift", "scans");
|
|
8466
|
+
try {
|
|
8467
|
+
const info2 = await stat4(historyDir);
|
|
8468
|
+
if (info2.isDirectory()) ok("Scan history", historyDir);
|
|
8469
|
+
else warn("Scan history", `${historyDir} exists but is not a directory`);
|
|
8470
|
+
} catch {
|
|
8471
|
+
info("Scan history", "empty (no scans run yet)");
|
|
8472
|
+
}
|
|
8473
|
+
console.log("");
|
|
8474
|
+
console.log(chalk10.bold(" Authentication"));
|
|
8475
|
+
const config = await readConfig();
|
|
8476
|
+
const resolved = await resolveToken();
|
|
8477
|
+
if (!resolved) {
|
|
8478
|
+
info("Login state", "not logged in");
|
|
8479
|
+
} else {
|
|
8480
|
+
ok("Token source", describeSource2(resolved.source));
|
|
8481
|
+
ok("Token preview", previewToken(resolved.token));
|
|
8482
|
+
if (resolved.source === "config") {
|
|
8483
|
+
if (config.email) ok("Email", config.email);
|
|
8484
|
+
if (config.plan) ok("Plan", config.plan);
|
|
8485
|
+
if (config.expiresAt) {
|
|
8486
|
+
const expires = new Date(config.expiresAt).getTime();
|
|
8487
|
+
const now = Date.now();
|
|
8488
|
+
if (expires < now) {
|
|
8489
|
+
bad(`Token expired ${Math.floor((now - expires) / 864e5)} days ago`);
|
|
8490
|
+
failures++;
|
|
8491
|
+
} else {
|
|
8492
|
+
ok("Token expires", `${config.expiresAt} (${Math.ceil((expires - now) / 864e5)} days)`);
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
}
|
|
8497
|
+
console.log("");
|
|
8498
|
+
console.log(chalk10.bold(" API"));
|
|
8499
|
+
const apiUrl = await resolveApiUrl();
|
|
8500
|
+
ok("API URL", apiUrl);
|
|
8501
|
+
if (resolved) {
|
|
8502
|
+
process.stdout.write(` ${chalk10.dim("\u2192 Validating token... ")}`);
|
|
8503
|
+
try {
|
|
8504
|
+
const result = await validateToken(resolved.token, { apiUrl });
|
|
8505
|
+
if (result.valid) {
|
|
8506
|
+
console.log(chalk10.green("ok"));
|
|
8507
|
+
} else {
|
|
8508
|
+
console.log(chalk10.red("invalid token"));
|
|
8509
|
+
failures++;
|
|
8510
|
+
}
|
|
8511
|
+
} catch (err) {
|
|
8512
|
+
console.log(chalk10.red("unreachable"));
|
|
8513
|
+
console.log(` ${chalk10.dim(" ")}${err instanceof VibeDriftApiError ? err.message : String(err)}`);
|
|
8514
|
+
failures++;
|
|
8515
|
+
}
|
|
8516
|
+
} else {
|
|
8517
|
+
process.stdout.write(` ${chalk10.dim("\u2192 Pinging API... ")}`);
|
|
8518
|
+
try {
|
|
8519
|
+
const res = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(1e4) });
|
|
8520
|
+
if (res.ok) console.log(chalk10.green("ok"));
|
|
8521
|
+
else {
|
|
8522
|
+
console.log(chalk10.yellow(`HTTP ${res.status}`));
|
|
8523
|
+
failures++;
|
|
8524
|
+
}
|
|
8525
|
+
} catch (err) {
|
|
8526
|
+
console.log(chalk10.red("unreachable"));
|
|
8527
|
+
console.log(` ${chalk10.dim(" ")}${err instanceof Error ? err.message : String(err)}`);
|
|
8528
|
+
failures++;
|
|
8529
|
+
}
|
|
8530
|
+
}
|
|
8531
|
+
console.log("");
|
|
8532
|
+
if (failures === 0) {
|
|
8533
|
+
console.log(chalk10.green(" \u2713 All checks passed."));
|
|
8534
|
+
} else {
|
|
8535
|
+
console.log(chalk10.red(` \u2717 ${failures} check${failures === 1 ? "" : "s"} failed.`));
|
|
8536
|
+
}
|
|
8537
|
+
console.log("");
|
|
8538
|
+
process.exit(failures === 0 ? 0 : 1);
|
|
8539
|
+
}
|
|
8540
|
+
function ok(label, value) {
|
|
8541
|
+
console.log(` ${chalk10.green("\u2713")} ${label.padEnd(14)} ${chalk10.dim(value)}`);
|
|
8542
|
+
}
|
|
8543
|
+
function warn(label, value) {
|
|
8544
|
+
console.log(` ${chalk10.yellow("\u26A0")} ${label.padEnd(14)} ${chalk10.dim(value)}`);
|
|
8545
|
+
}
|
|
8546
|
+
function bad(value) {
|
|
8547
|
+
console.log(` ${chalk10.red("\u2717")} ${chalk10.red(value)}`);
|
|
8548
|
+
}
|
|
8549
|
+
function info(label, value) {
|
|
8550
|
+
console.log(` ${chalk10.dim("\xB7")} ${label.padEnd(14)} ${chalk10.dim(value)}`);
|
|
8551
|
+
}
|
|
8552
|
+
function describeSource2(source) {
|
|
8553
|
+
switch (source) {
|
|
8554
|
+
case "flag":
|
|
8555
|
+
return "command-line flag";
|
|
8556
|
+
case "env":
|
|
8557
|
+
return "VIBEDRIFT_TOKEN environment variable";
|
|
8558
|
+
case "config":
|
|
8559
|
+
return "~/.vibedrift/config.json";
|
|
8560
|
+
}
|
|
8561
|
+
}
|
|
8562
|
+
|
|
7731
8563
|
// src/cli/index.ts
|
|
7732
8564
|
init_version();
|
|
7733
8565
|
var VERSION = getVersion();
|
|
@@ -7741,10 +8573,14 @@ function parseScoreThreshold(value) {
|
|
|
7741
8573
|
}
|
|
7742
8574
|
return n;
|
|
7743
8575
|
}
|
|
8576
|
+
function collect(value, previous) {
|
|
8577
|
+
return previous.concat([value]);
|
|
8578
|
+
}
|
|
7744
8579
|
var program = new Command();
|
|
7745
8580
|
program.name("vibedrift").description(
|
|
7746
8581
|
"Detect drift, contradictions, and security gaps in AI-generated codebases."
|
|
7747
|
-
).version(VERSION, "-V, --version", "show the installed version").helpOption("-h, --help", "show this help")
|
|
8582
|
+
).version(VERSION, "-V, --version", "show the installed version").helpOption("-h, --help", "show this help");
|
|
8583
|
+
program.command("scan", { isDefault: true }).description("Scan a project for vibe drift (default command)").argument("[path]", "path to project directory", ".").option(
|
|
7748
8584
|
"--format <type>",
|
|
7749
8585
|
"output format: html, terminal, json, csv, docx",
|
|
7750
8586
|
"html"
|
|
@@ -7752,8 +8588,24 @@ program.name("vibedrift").description(
|
|
|
7752
8588
|
"--fail-on-score <n>",
|
|
7753
8589
|
"exit with code 1 if composite score is below this threshold",
|
|
7754
8590
|
parseScoreThreshold
|
|
7755
|
-
).option("--no-codedna", "skip Code DNA semantic analysis").option(
|
|
7756
|
-
|
|
8591
|
+
).option("--no-codedna", "skip Code DNA semantic analysis").option(
|
|
8592
|
+
"--deep",
|
|
8593
|
+
"enable AI-powered deep analysis (requires `vibedrift login`)"
|
|
8594
|
+
).option(
|
|
8595
|
+
"--include <pattern>",
|
|
8596
|
+
"only scan files matching this glob (repeatable)",
|
|
8597
|
+
collect,
|
|
8598
|
+
[]
|
|
8599
|
+
).option(
|
|
8600
|
+
"--exclude <pattern>",
|
|
8601
|
+
"exclude files matching this glob (repeatable)",
|
|
8602
|
+
collect,
|
|
8603
|
+
[]
|
|
8604
|
+
).option(
|
|
8605
|
+
"--update",
|
|
8606
|
+
"update the VibeDrift CLI to the latest version (alias for `vibedrift update`)"
|
|
8607
|
+
).option("--verbose", "show timing breakdown and analyzer details").addOption(
|
|
8608
|
+
new Option("--api-url <url>", "override the VibeDrift API base URL").hideHelp()
|
|
7757
8609
|
).action(async (path2, options) => {
|
|
7758
8610
|
if (options.update) {
|
|
7759
8611
|
await runUpdate(VERSION);
|
|
@@ -7765,12 +8617,42 @@ program.name("vibedrift").description(
|
|
|
7765
8617
|
output: options.output,
|
|
7766
8618
|
failOnScore: options.failOnScore,
|
|
7767
8619
|
codedna: options.codedna,
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
8620
|
+
deep: options.deep,
|
|
8621
|
+
apiUrl: options.apiUrl,
|
|
8622
|
+
include: options.include,
|
|
8623
|
+
exclude: options.exclude,
|
|
7771
8624
|
verbose: options.verbose
|
|
7772
8625
|
});
|
|
7773
8626
|
});
|
|
8627
|
+
program.command("login").description("Log in to your VibeDrift account").option("--no-browser", "don't open the browser automatically").addOption(
|
|
8628
|
+
new Option("--api-url <url>", "override the API base URL").hideHelp()
|
|
8629
|
+
).action(async (options) => {
|
|
8630
|
+
await runLogin({
|
|
8631
|
+
apiUrl: options.apiUrl,
|
|
8632
|
+
noBrowser: options.browser === false
|
|
8633
|
+
});
|
|
8634
|
+
});
|
|
8635
|
+
program.command("logout").description("Log out and revoke the current token").action(async () => {
|
|
8636
|
+
await runLogout();
|
|
8637
|
+
});
|
|
8638
|
+
program.command("status").description("Show the current account, plan, and token").action(async () => {
|
|
8639
|
+
await runStatus();
|
|
8640
|
+
});
|
|
8641
|
+
program.command("usage").description("Show your current billing period's scan usage").action(async () => {
|
|
8642
|
+
await runUsage();
|
|
8643
|
+
});
|
|
8644
|
+
program.command("upgrade").description("Open the VibeDrift pricing page").action(async () => {
|
|
8645
|
+
await runUpgrade();
|
|
8646
|
+
});
|
|
8647
|
+
program.command("billing").description("Open the Stripe Customer Portal to manage your subscription").action(async () => {
|
|
8648
|
+
await runBilling();
|
|
8649
|
+
});
|
|
8650
|
+
program.command("doctor").description("Diagnose CLI installation, auth, and API connectivity").action(async () => {
|
|
8651
|
+
await runDoctor();
|
|
8652
|
+
});
|
|
8653
|
+
program.command("update").description("Update the VibeDrift CLI to the latest version").action(async () => {
|
|
8654
|
+
await runUpdate(VERSION);
|
|
8655
|
+
});
|
|
7774
8656
|
program.addHelpText(
|
|
7775
8657
|
"after",
|
|
7776
8658
|
`
|
|
@@ -7780,8 +8662,20 @@ Examples:
|
|
|
7780
8662
|
$ vibedrift --format terminal print results to the terminal
|
|
7781
8663
|
$ vibedrift --json > report.json pipe JSON output to a file
|
|
7782
8664
|
$ vibedrift --fail-on-score 70 fail CI if score drops below 70
|
|
7783
|
-
$ vibedrift --
|
|
7784
|
-
$ vibedrift --
|
|
8665
|
+
$ vibedrift --include "src/**" only scan files under src/
|
|
8666
|
+
$ vibedrift --exclude "**/*.spec.*" skip test files
|
|
8667
|
+
$ vibedrift --deep run premium AI-powered deep analysis
|
|
8668
|
+
$ vibedrift login sign in to enable --deep
|
|
8669
|
+
$ vibedrift status check current auth state
|
|
8670
|
+
$ vibedrift usage view this month's scan usage
|
|
8671
|
+
$ vibedrift upgrade open the pricing page
|
|
8672
|
+
$ vibedrift billing manage your Stripe subscription
|
|
8673
|
+
$ vibedrift update update to the latest CLI version
|
|
8674
|
+
|
|
8675
|
+
Environment:
|
|
8676
|
+
VIBEDRIFT_TOKEN bearer token (overrides ~/.vibedrift/config.json)
|
|
8677
|
+
VIBEDRIFT_API_URL override the API base URL
|
|
8678
|
+
VIBEDRIFT_NO_BROWSER if "1", never auto-open the browser
|
|
7785
8679
|
|
|
7786
8680
|
Learn more: https://vibedrift.ai`
|
|
7787
8681
|
);
|