@vibedrift/cli 0.3.1 → 0.4.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/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/index.js +268 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -149,11 +149,11 @@ In short:
|
|
|
149
149
|
- No use to build competing products
|
|
150
150
|
- All rights reserved
|
|
151
151
|
|
|
152
|
-
For commercial licensing, partnerships, or enterprise terms: **sami@
|
|
152
|
+
For commercial licensing, partnerships, or enterprise terms: **sami.ahmadkhan12@gmail.com**
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
156
156
|
## Links
|
|
157
157
|
|
|
158
158
|
- **Website:** https://vibedrift.ai
|
|
159
|
-
- **Issues / Support:** sami@
|
|
159
|
+
- **Issues / Support:** sami.ahmadkhan12@gmail.com
|
package/dist/index.js
CHANGED
|
@@ -195,6 +195,7 @@ var init_version = __esm({
|
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
// src/codedna/semantic-fingerprint.ts
|
|
198
|
+
import { createHash as createHash2 } from "crypto";
|
|
198
199
|
function normalizeBody(body, language) {
|
|
199
200
|
let normalized = body;
|
|
200
201
|
normalized = normalized.replace(/\/\/.*$/gm, "");
|
|
@@ -262,7 +263,11 @@ function computeSemanticFingerprints(functions) {
|
|
|
262
263
|
normalizedHash: fnv1aHash(normalizeBody(fn.rawBody, fn.language))
|
|
263
264
|
}));
|
|
264
265
|
}
|
|
265
|
-
function findDuplicateGroups(fingerprints) {
|
|
266
|
+
function findDuplicateGroups(fingerprints, functions) {
|
|
267
|
+
const fnLookup = /* @__PURE__ */ new Map();
|
|
268
|
+
for (const fn of functions) {
|
|
269
|
+
fnLookup.set(`${fn.name}:${fn.file}`, fn);
|
|
270
|
+
}
|
|
266
271
|
const byHash = /* @__PURE__ */ new Map();
|
|
267
272
|
for (const fp of fingerprints) {
|
|
268
273
|
if (!byHash.has(fp.normalizedHash)) byHash.set(fp.normalizedHash, []);
|
|
@@ -272,13 +277,25 @@ function findDuplicateGroups(fingerprints) {
|
|
|
272
277
|
let groupCounter = 0;
|
|
273
278
|
for (const [hash, fps] of byHash) {
|
|
274
279
|
if (fps.length < 2) continue;
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
const bySha = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const fp of fps) {
|
|
282
|
+
const key = `${fp.functionRef.name}:${fp.functionRef.file}`;
|
|
283
|
+
const fn = fnLookup.get(key);
|
|
284
|
+
if (!fn) continue;
|
|
285
|
+
const sha = createHash2("sha256").update(normalizeBody(fn.rawBody, fn.language)).digest("hex");
|
|
286
|
+
if (!bySha.has(sha)) bySha.set(sha, []);
|
|
287
|
+
bySha.get(sha).push(fp);
|
|
288
|
+
}
|
|
289
|
+
for (const [, shaGroup] of bySha) {
|
|
290
|
+
if (shaGroup.length < 2) continue;
|
|
291
|
+
const uniqueFiles = new Set(shaGroup.map((fp) => fp.functionRef.file));
|
|
292
|
+
if (uniqueFiles.size < 2) continue;
|
|
293
|
+
groups.push({
|
|
294
|
+
groupId: `fingerprint-${groupCounter++}`,
|
|
295
|
+
hash,
|
|
296
|
+
functions: shaGroup.map((fp) => fp.functionRef)
|
|
297
|
+
});
|
|
298
|
+
}
|
|
282
299
|
}
|
|
283
300
|
return groups;
|
|
284
301
|
}
|
|
@@ -923,7 +940,7 @@ function runCodeDnaAnalysis(ctx) {
|
|
|
923
940
|
timings.extractionMs = Date.now() - t;
|
|
924
941
|
t = Date.now();
|
|
925
942
|
const fingerprints = computeSemanticFingerprints(functions);
|
|
926
|
-
const duplicateGroups = findDuplicateGroups(fingerprints);
|
|
943
|
+
const duplicateGroups = findDuplicateGroups(fingerprints, functions);
|
|
927
944
|
timings.fingerprintMs = Date.now() - t;
|
|
928
945
|
t = Date.now();
|
|
929
946
|
const sequences = extractOperationSequences(functions);
|
|
@@ -1206,9 +1223,9 @@ __export(project_name_exports, {
|
|
|
1206
1223
|
});
|
|
1207
1224
|
import { basename, join as join6 } from "path";
|
|
1208
1225
|
import { readFile as readFile5 } from "fs/promises";
|
|
1209
|
-
import { createHash as
|
|
1226
|
+
import { createHash as createHash3 } from "crypto";
|
|
1210
1227
|
async function detectProjectIdentity(rootDir, override) {
|
|
1211
|
-
const hash =
|
|
1228
|
+
const hash = createHash3("sha256").update(rootDir).digest("hex");
|
|
1212
1229
|
if (override && override.trim()) {
|
|
1213
1230
|
return { name: override.trim(), hash };
|
|
1214
1231
|
}
|
|
@@ -2682,7 +2699,7 @@ var namingAnalyzer = {
|
|
|
2682
2699
|
init_esm_shims();
|
|
2683
2700
|
var ESM_PATTERN = /\b(?:import\s+|export\s+(?:default\s+)?(?:function|class|const|let|var|{))/;
|
|
2684
2701
|
var CJS_PATTERN = /\b(?:require\s*\(|module\.exports|exports\.)/;
|
|
2685
|
-
var CONFIG_FILE_PATTERN = /(?:\.config\.|\.setup\.|\.rc\.|jest\.|babel\.|webpack\.|rollup\.|vite\.)/;
|
|
2702
|
+
var CONFIG_FILE_PATTERN = /(?:\.config\.|\.setup\.|\.rc\.|jest\.|babel\.|webpack\.|rollup\.|vite\.|next\.config|tailwind\.config|postcss\.config|tsconfig|eslint\.config|prettier\.config|svelte\.config|nuxt\.config|astro\.config|vitest\.config|tsup\.config|esbuild\.config|turbo\.json|\.cjs$|\.mjs$)/;
|
|
2686
2703
|
var importsAnalyzer = {
|
|
2687
2704
|
id: "imports",
|
|
2688
2705
|
name: "Import Patterns",
|
|
@@ -2743,8 +2760,6 @@ function densityPer1K(count, totalLines) {
|
|
|
2743
2760
|
|
|
2744
2761
|
// src/analyzers/error-handling.ts
|
|
2745
2762
|
var EMPTY_CATCH_PATTERN = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
2746
|
-
var TRY_CATCH_PATTERN = /\btry\s*\{/g;
|
|
2747
|
-
var ASYNC_PATTERN = /\basync\s+(?:function|\(|[a-zA-Z])/g;
|
|
2748
2763
|
var errorHandlingAnalyzer = {
|
|
2749
2764
|
id: "error-handling",
|
|
2750
2765
|
name: "Error Handling",
|
|
@@ -2781,22 +2796,43 @@ var errorHandlingAnalyzer = {
|
|
|
2781
2796
|
tags: ["error-handling", "empty-catch"]
|
|
2782
2797
|
});
|
|
2783
2798
|
}
|
|
2784
|
-
const
|
|
2799
|
+
const ASYNC_FN_PATTERN = /async\s+(?:function\s+\w+|\(\w*\)|\w+)\s*\([^)]*\)\s*(?::\s*[^{]*)?\s*\{/g;
|
|
2800
|
+
const ERROR_HANDLING_PATTERNS = [
|
|
2801
|
+
/\btry\s*\{/,
|
|
2802
|
+
/\.catch\s*\(/,
|
|
2803
|
+
/\bcatch\s*\(/,
|
|
2804
|
+
/\b(?:Result|Either)\s*[<(]/
|
|
2805
|
+
];
|
|
2806
|
+
const dirUnhandled = /* @__PURE__ */ new Map();
|
|
2785
2807
|
for (const file of jsFiles) {
|
|
2786
2808
|
const dir = file.relativePath.includes("/") ? file.relativePath.slice(0, file.relativePath.lastIndexOf("/")) : ".";
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2809
|
+
const asyncRegex = new RegExp(ASYNC_FN_PATTERN.source, "g");
|
|
2810
|
+
let fnMatch;
|
|
2811
|
+
while ((fnMatch = asyncRegex.exec(file.content)) !== null) {
|
|
2812
|
+
const openBrace = file.content.indexOf("{", fnMatch.index + fnMatch[0].length - 1);
|
|
2813
|
+
if (openBrace === -1) continue;
|
|
2814
|
+
let depth = 1;
|
|
2815
|
+
let pos = openBrace + 1;
|
|
2816
|
+
while (pos < file.content.length && depth > 0) {
|
|
2817
|
+
if (file.content[pos] === "{") depth++;
|
|
2818
|
+
else if (file.content[pos] === "}") depth--;
|
|
2819
|
+
pos++;
|
|
2820
|
+
}
|
|
2821
|
+
const body = file.content.slice(openBrace + 1, pos - 1);
|
|
2822
|
+
if (!/\bawait\b/.test(body)) continue;
|
|
2823
|
+
const hasHandling = ERROR_HANDLING_PATTERNS.some((p) => p.test(body));
|
|
2824
|
+
if (!hasHandling) {
|
|
2825
|
+
dirUnhandled.set(dir, (dirUnhandled.get(dir) ?? 0) + 1);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
for (const [dir, count] of dirUnhandled) {
|
|
2830
|
+
if (count > 3) {
|
|
2795
2831
|
findings.push({
|
|
2796
2832
|
analyzerId: "error-handling",
|
|
2797
2833
|
severity: "info",
|
|
2798
2834
|
confidence: 0.6,
|
|
2799
|
-
message: `${
|
|
2835
|
+
message: `${count} async functions without error handling in ${dir}/`,
|
|
2800
2836
|
locations: [{ file: dir }],
|
|
2801
2837
|
tags: ["error-handling", "unhandled-async"]
|
|
2802
2838
|
});
|
|
@@ -3418,6 +3454,16 @@ var duplicatesAnalyzer = {
|
|
|
3418
3454
|
}
|
|
3419
3455
|
}
|
|
3420
3456
|
}
|
|
3457
|
+
if (allSequences.length > 200) {
|
|
3458
|
+
findings.push({
|
|
3459
|
+
analyzerId: "duplicates",
|
|
3460
|
+
severity: "info",
|
|
3461
|
+
confidence: 0.3,
|
|
3462
|
+
message: `Cross-function duplicate detection limited to hash-based matching (${allSequences.length} functions exceeds the 200-function threshold for full comparison). Use --include to narrow scope for deeper analysis.`,
|
|
3463
|
+
locations: [],
|
|
3464
|
+
tags: ["duplicates", "scalability"]
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3421
3467
|
if (allSequences.length <= 200) {
|
|
3422
3468
|
for (let i = 0; i < allSequences.length; i++) {
|
|
3423
3469
|
for (let j = i + 1; j < allSequences.length; j++) {
|
|
@@ -3768,7 +3814,9 @@ var SECURITY_PATTERNS = [
|
|
|
3768
3814
|
message: "Math.random() is not cryptographically secure \u2014 use crypto.randomUUID()",
|
|
3769
3815
|
languages: ["javascript", "typescript"],
|
|
3770
3816
|
tags: ["security", "crypto"],
|
|
3771
|
-
negativeFilter: /(?:test|mock|seed|shuffle|animation|color|position|offset|delay|jitter)/i
|
|
3817
|
+
negativeFilter: /(?:test|mock|seed|shuffle|animation|color|position|offset|delay|jitter)/i,
|
|
3818
|
+
// Only flag near security-relevant code — UI shuffles/animations are not a risk
|
|
3819
|
+
contextRequired: /(?:token|secret|password|key|nonce|salt|hash|crypto|auth|session|jwt|api.?key|credential)/i
|
|
3772
3820
|
},
|
|
3773
3821
|
// === Path Traversal ===
|
|
3774
3822
|
{
|
|
@@ -3860,6 +3908,14 @@ var securityAnalyzer = {
|
|
|
3860
3908
|
const line = file.content.slice(lineStart2, lineEnd2 === -1 ? void 0 : lineEnd2);
|
|
3861
3909
|
if (pattern.negativeFilter.test(line)) continue;
|
|
3862
3910
|
}
|
|
3911
|
+
if (pattern.contextRequired) {
|
|
3912
|
+
const lines = file.content.split("\n");
|
|
3913
|
+
const matchLine = file.content.slice(0, match.index).split("\n").length - 1;
|
|
3914
|
+
const start = Math.max(0, matchLine - 5);
|
|
3915
|
+
const end = Math.min(lines.length, matchLine + 6);
|
|
3916
|
+
const context = lines.slice(start, end).join("\n");
|
|
3917
|
+
if (!pattern.contextRequired.test(context)) continue;
|
|
3918
|
+
}
|
|
3863
3919
|
const lineNum = getLineNumber(file.content, match.index);
|
|
3864
3920
|
const lineStart = file.content.lastIndexOf("\n", match.index) + 1;
|
|
3865
3921
|
const lineEnd = file.content.indexOf("\n", match.index);
|
|
@@ -3974,7 +4030,11 @@ function computeComplexityAST(node, language) {
|
|
|
3974
4030
|
walk(node);
|
|
3975
4031
|
return complexity;
|
|
3976
4032
|
}
|
|
4033
|
+
function stripComments(code) {
|
|
4034
|
+
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "");
|
|
4035
|
+
}
|
|
3977
4036
|
function computeComplexityRegex(content) {
|
|
4037
|
+
const stripped = stripComments(content);
|
|
3978
4038
|
let complexity = 1;
|
|
3979
4039
|
const patterns = [
|
|
3980
4040
|
/\bif\s*\(/g,
|
|
@@ -3997,7 +4057,7 @@ function computeComplexityRegex(content) {
|
|
|
3997
4057
|
];
|
|
3998
4058
|
for (const p of patterns) {
|
|
3999
4059
|
const regex = new RegExp(p.source, p.flags);
|
|
4000
|
-
const matches =
|
|
4060
|
+
const matches = stripped.match(regex);
|
|
4001
4061
|
if (matches) complexity += matches.length;
|
|
4002
4062
|
}
|
|
4003
4063
|
return complexity;
|
|
@@ -5397,7 +5457,7 @@ function analyzeSecurityProperty(routes, propertyName, getter, excludePaths) {
|
|
|
5397
5457
|
const withProperty = applicableRoutes.filter(getter);
|
|
5398
5458
|
const withoutProperty = applicableRoutes.filter((r) => !getter(r));
|
|
5399
5459
|
const ratio = withProperty.length / applicableRoutes.length;
|
|
5400
|
-
if (ratio <= 0.
|
|
5460
|
+
if (ratio <= 0.75 || withoutProperty.length === 0) return null;
|
|
5401
5461
|
return {
|
|
5402
5462
|
detector: "security_posture",
|
|
5403
5463
|
subCategory: propertyName,
|
|
@@ -6118,10 +6178,10 @@ function computeCategoryScore(findings, maxScore, totalLines, applicable, correl
|
|
|
6118
6178
|
}
|
|
6119
6179
|
rawWeight += base * confidence * fileWeight * corrWeight;
|
|
6120
6180
|
}
|
|
6121
|
-
const sizeFactor = totalLines >
|
|
6181
|
+
const sizeFactor = totalLines > 500 ? Math.sqrt(totalLines / 1e3) : 1;
|
|
6122
6182
|
const clampedSizeFactor = Math.max(0.5, Math.min(3, sizeFactor));
|
|
6123
6183
|
const adjustedWeight = rawWeight / clampedSizeFactor;
|
|
6124
|
-
const k = Math.LN2 /
|
|
6184
|
+
const k = Math.LN2 / 15;
|
|
6125
6185
|
const factor = Math.exp(-k * adjustedWeight);
|
|
6126
6186
|
const score = Math.round(maxScore * factor * 10) / 10;
|
|
6127
6187
|
return {
|
|
@@ -6172,7 +6232,7 @@ function computeScores(findings, totalLines, ctx, previousScores) {
|
|
|
6172
6232
|
}
|
|
6173
6233
|
const DRAG_CATEGORIES = ["architecturalConsistency", "redundancy"];
|
|
6174
6234
|
const DRAG_THRESHOLD = 0.5;
|
|
6175
|
-
const DRAG_MAX_PENALTY = 0.
|
|
6235
|
+
const DRAG_MAX_PENALTY = 0.1;
|
|
6176
6236
|
for (const cat of DRAG_CATEGORIES) {
|
|
6177
6237
|
const s = scores[cat];
|
|
6178
6238
|
if (!s.applicable || s.maxScore === 0) continue;
|
|
@@ -7972,6 +8032,21 @@ async function fetchUsage(token, opts) {
|
|
|
7972
8032
|
headers: { Authorization: `Bearer ${token}` }
|
|
7973
8033
|
});
|
|
7974
8034
|
}
|
|
8035
|
+
async function sendFeedback(args) {
|
|
8036
|
+
const base = await resolveApiUrl(args.apiUrl);
|
|
8037
|
+
const headers = {};
|
|
8038
|
+
if (args.token) headers.Authorization = `Bearer ${args.token}`;
|
|
8039
|
+
return jsonFetch(`${base}/v1/feedback/general`, {
|
|
8040
|
+
method: "POST",
|
|
8041
|
+
headers,
|
|
8042
|
+
body: JSON.stringify({
|
|
8043
|
+
source: args.source,
|
|
8044
|
+
message: args.message,
|
|
8045
|
+
email: args.email,
|
|
8046
|
+
metadata: args.metadata
|
|
8047
|
+
})
|
|
8048
|
+
});
|
|
8049
|
+
}
|
|
7975
8050
|
async function fetchCredits(token, opts) {
|
|
7976
8051
|
const base = await resolveApiUrl(opts?.apiUrl);
|
|
7977
8052
|
return jsonFetch(`${base}/account/credits`, {
|
|
@@ -8960,6 +9035,145 @@ function describeSource2(source) {
|
|
|
8960
9035
|
}
|
|
8961
9036
|
}
|
|
8962
9037
|
|
|
9038
|
+
// src/cli/commands/feedback.ts
|
|
9039
|
+
init_esm_shims();
|
|
9040
|
+
import os from "os";
|
|
9041
|
+
import readline from "readline";
|
|
9042
|
+
import chalk11 from "chalk";
|
|
9043
|
+
init_version();
|
|
9044
|
+
var MAX_MESSAGE_BYTES = 4096;
|
|
9045
|
+
async function runFeedback(opts = {}) {
|
|
9046
|
+
console.log("");
|
|
9047
|
+
console.log(chalk11.bold(" VibeDrift feedback"));
|
|
9048
|
+
console.log(
|
|
9049
|
+
chalk11.dim(" Tell us what's broken, what's confusing, or what you wish existed.")
|
|
9050
|
+
);
|
|
9051
|
+
console.log(
|
|
9052
|
+
chalk11.dim(" Goes straight to the maintainer \u2014 anonymous unless you're logged in.")
|
|
9053
|
+
);
|
|
9054
|
+
console.log("");
|
|
9055
|
+
let message = (opts.message ?? "").trim();
|
|
9056
|
+
if (!message) {
|
|
9057
|
+
message = await promptForMultilineMessage();
|
|
9058
|
+
}
|
|
9059
|
+
if (!message) {
|
|
9060
|
+
console.error(chalk11.red(" \u2717 No feedback provided. Aborting."));
|
|
9061
|
+
process.exit(1);
|
|
9062
|
+
}
|
|
9063
|
+
if (Buffer.byteLength(message, "utf8") > MAX_MESSAGE_BYTES) {
|
|
9064
|
+
console.error(
|
|
9065
|
+
chalk11.red(
|
|
9066
|
+
` \u2717 Message too long (max ${MAX_MESSAGE_BYTES} bytes). Please trim it or open an issue on GitHub for longer reports.`
|
|
9067
|
+
)
|
|
9068
|
+
);
|
|
9069
|
+
process.exit(1);
|
|
9070
|
+
}
|
|
9071
|
+
let token = null;
|
|
9072
|
+
try {
|
|
9073
|
+
const resolved = await resolveToken();
|
|
9074
|
+
if (resolved) token = resolved.token;
|
|
9075
|
+
} catch {
|
|
9076
|
+
}
|
|
9077
|
+
const metadata = {
|
|
9078
|
+
cli_version: getVersion(),
|
|
9079
|
+
node_version: process.version,
|
|
9080
|
+
platform: process.platform,
|
|
9081
|
+
arch: process.arch,
|
|
9082
|
+
os_release: os.release(),
|
|
9083
|
+
locale: process.env.LANG ?? null,
|
|
9084
|
+
tty: process.stdin.isTTY === true
|
|
9085
|
+
};
|
|
9086
|
+
process.stdout.write(chalk11.dim(" Sending... "));
|
|
9087
|
+
try {
|
|
9088
|
+
const result = await sendFeedback({
|
|
9089
|
+
source: "cli",
|
|
9090
|
+
message,
|
|
9091
|
+
token: token ?? void 0,
|
|
9092
|
+
metadata,
|
|
9093
|
+
apiUrl: await resolveApiUrl(opts.apiUrl)
|
|
9094
|
+
});
|
|
9095
|
+
console.log(chalk11.green("ok"));
|
|
9096
|
+
console.log("");
|
|
9097
|
+
console.log(` ${chalk11.dim("Reference:")} ${chalk11.dim(result.id)}`);
|
|
9098
|
+
if (token) {
|
|
9099
|
+
console.log(
|
|
9100
|
+
chalk11.dim(
|
|
9101
|
+
" Submitted under your account \u2014 we'll reply to your email if needed."
|
|
9102
|
+
)
|
|
9103
|
+
);
|
|
9104
|
+
} else {
|
|
9105
|
+
console.log(
|
|
9106
|
+
chalk11.dim(
|
|
9107
|
+
" Submitted anonymously. If you'd like a reply, run `vibedrift login` first."
|
|
9108
|
+
)
|
|
9109
|
+
);
|
|
9110
|
+
}
|
|
9111
|
+
console.log("");
|
|
9112
|
+
console.log(chalk11.green(" \u2713 Thanks for helping us improve VibeDrift."));
|
|
9113
|
+
console.log("");
|
|
9114
|
+
} catch (err) {
|
|
9115
|
+
console.log(chalk11.red("failed"));
|
|
9116
|
+
console.log("");
|
|
9117
|
+
if (err instanceof VibeDriftApiError) {
|
|
9118
|
+
console.error(chalk11.red(` \u2717 ${err.message}`));
|
|
9119
|
+
} else {
|
|
9120
|
+
console.error(
|
|
9121
|
+
chalk11.red(` \u2717 ${err instanceof Error ? err.message : String(err)}`)
|
|
9122
|
+
);
|
|
9123
|
+
}
|
|
9124
|
+
console.error(
|
|
9125
|
+
chalk11.dim(
|
|
9126
|
+
" You can also email sami.ahmadkhan12@gmail.com directly."
|
|
9127
|
+
)
|
|
9128
|
+
);
|
|
9129
|
+
console.log("");
|
|
9130
|
+
process.exit(1);
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
function promptForMultilineMessage() {
|
|
9134
|
+
return new Promise((resolve2) => {
|
|
9135
|
+
if (!process.stdin.isTTY) {
|
|
9136
|
+
let buf = "";
|
|
9137
|
+
process.stdin.setEncoding("utf8");
|
|
9138
|
+
process.stdin.on("data", (chunk) => {
|
|
9139
|
+
buf += chunk;
|
|
9140
|
+
});
|
|
9141
|
+
process.stdin.on("end", () => resolve2(buf.trim()));
|
|
9142
|
+
return;
|
|
9143
|
+
}
|
|
9144
|
+
console.log(
|
|
9145
|
+
chalk11.dim(
|
|
9146
|
+
" Type your feedback below. Submit with a single line containing 'EOF',"
|
|
9147
|
+
)
|
|
9148
|
+
);
|
|
9149
|
+
console.log(chalk11.dim(" or press Ctrl-D when done. Press Ctrl-C to abort."));
|
|
9150
|
+
console.log("");
|
|
9151
|
+
const rl = readline.createInterface({
|
|
9152
|
+
input: process.stdin,
|
|
9153
|
+
output: process.stdout,
|
|
9154
|
+
prompt: chalk11.yellow(" > ")
|
|
9155
|
+
});
|
|
9156
|
+
const lines = [];
|
|
9157
|
+
rl.prompt();
|
|
9158
|
+
rl.on("line", (line) => {
|
|
9159
|
+
if (line.trim() === "EOF") {
|
|
9160
|
+
rl.close();
|
|
9161
|
+
return;
|
|
9162
|
+
}
|
|
9163
|
+
lines.push(line);
|
|
9164
|
+
rl.prompt();
|
|
9165
|
+
});
|
|
9166
|
+
rl.on("close", () => {
|
|
9167
|
+
console.log("");
|
|
9168
|
+
resolve2(lines.join("\n").trim());
|
|
9169
|
+
});
|
|
9170
|
+
rl.on("SIGINT", () => {
|
|
9171
|
+
console.log(chalk11.red("\n \u2717 Aborted."));
|
|
9172
|
+
process.exit(1);
|
|
9173
|
+
});
|
|
9174
|
+
});
|
|
9175
|
+
}
|
|
9176
|
+
|
|
8963
9177
|
// src/cli/index.ts
|
|
8964
9178
|
init_version();
|
|
8965
9179
|
var VERSION = getVersion();
|
|
@@ -9007,6 +9221,9 @@ program.command("scan", { isDefault: true }).description("Scan a project for vib
|
|
|
9007
9221
|
).option(
|
|
9008
9222
|
"--update",
|
|
9009
9223
|
"update the VibeDrift CLI to the latest version (alias for `vibedrift update`)"
|
|
9224
|
+
).option(
|
|
9225
|
+
"--feedback [message...]",
|
|
9226
|
+
"send feedback to the maintainer (alias for `vibedrift feedback`)"
|
|
9010
9227
|
).option("--verbose", "show timing breakdown and analyzer details").addOption(
|
|
9011
9228
|
new Option("--api-url <url>", "override the VibeDrift API base URL").hideHelp()
|
|
9012
9229
|
).action(async (path2, options) => {
|
|
@@ -9014,6 +9231,14 @@ program.command("scan", { isDefault: true }).description("Scan a project for vib
|
|
|
9014
9231
|
await runUpdate(VERSION);
|
|
9015
9232
|
return;
|
|
9016
9233
|
}
|
|
9234
|
+
if (options.feedback) {
|
|
9235
|
+
const inline = Array.isArray(options.feedback) ? options.feedback.join(" ").trim() : "";
|
|
9236
|
+
await runFeedback({
|
|
9237
|
+
message: inline || void 0,
|
|
9238
|
+
apiUrl: options.apiUrl
|
|
9239
|
+
});
|
|
9240
|
+
return;
|
|
9241
|
+
}
|
|
9017
9242
|
await runScan(path2, {
|
|
9018
9243
|
json: options.json,
|
|
9019
9244
|
format: options.json ? "json" : options.format,
|
|
@@ -9057,6 +9282,15 @@ program.command("doctor").description("Diagnose CLI installation, auth, and API
|
|
|
9057
9282
|
program.command("update").description("Update the VibeDrift CLI to the latest version").action(async () => {
|
|
9058
9283
|
await runUpdate(VERSION);
|
|
9059
9284
|
});
|
|
9285
|
+
program.command("feedback").description("Send feedback, bug reports, or feature requests directly to the maintainer").argument(
|
|
9286
|
+
"[message...]",
|
|
9287
|
+
"feedback text (omit to be prompted interactively)"
|
|
9288
|
+
).addOption(
|
|
9289
|
+
new Option("--api-url <url>", "override the API base URL").hideHelp()
|
|
9290
|
+
).action(async (messageWords, options) => {
|
|
9291
|
+
const message = (messageWords ?? []).join(" ").trim() || void 0;
|
|
9292
|
+
await runFeedback({ message, apiUrl: options.apiUrl });
|
|
9293
|
+
});
|
|
9060
9294
|
program.addHelpText(
|
|
9061
9295
|
"after",
|
|
9062
9296
|
`
|
|
@@ -9075,6 +9309,10 @@ Examples:
|
|
|
9075
9309
|
$ vibedrift upgrade open the pricing page
|
|
9076
9310
|
$ vibedrift billing manage your Stripe subscription
|
|
9077
9311
|
$ vibedrift update update to the latest CLI version
|
|
9312
|
+
$ vibedrift feedback open an interactive feedback prompt
|
|
9313
|
+
$ vibedrift feedback "the install is broken on Windows"
|
|
9314
|
+
send inline feedback in one shot
|
|
9315
|
+
$ vibedrift --feedback "..." same as above, top-level shortcut
|
|
9078
9316
|
|
|
9079
9317
|
Environment:
|
|
9080
9318
|
VIBEDRIFT_TOKEN bearer token (overrides ~/.vibedrift/config.json)
|