@viberaven/cli 0.1.0-beta.0 → 0.1.0-beta.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/AGENTS.md +46 -37
- package/README.md +6 -5
- package/SECURITY.md +36 -0
- package/assets/report/assets/provider-authjs.svg +5 -0
- package/assets/report/assets/provider-aws.svg +5 -0
- package/assets/report/assets/provider-logrocket.svg +4 -0
- package/assets/report/assets/raven-mark.svg +7 -0
- package/assets/report/assets/viberaven-logo.png +0 -0
- package/assets/report/report-cli.css +1192 -0
- package/assets/report/station.css +8236 -0
- package/dist/cli.js +3000 -474
- package/dist/cli.js.map +4 -4
- package/dist/report/assets/provider-authjs.svg +5 -0
- package/dist/report/assets/provider-aws.svg +5 -0
- package/dist/report/assets/provider-logrocket.svg +4 -0
- package/dist/report/assets/raven-mark.svg +7 -0
- package/dist/report/assets/viberaven-logo.png +0 -0
- package/dist/report/report-cli.css +1192 -0
- package/dist/report/station.css +8236 -0
- package/package.json +9 -2
- package/templates/AGENTS.snippet.md +17 -10
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,161 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// node_modules/picocolors/picocolors.js
|
|
30
|
+
var require_picocolors = __commonJS({
|
|
31
|
+
"node_modules/picocolors/picocolors.js"(exports2, module2) {
|
|
32
|
+
var p2 = process || {};
|
|
33
|
+
var argv = p2.argv || [];
|
|
34
|
+
var env = p2.env || {};
|
|
35
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p2.platform === "win32" || (p2.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
36
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
37
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
38
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
39
|
+
};
|
|
40
|
+
var replaceClose = (string, close, replace, index) => {
|
|
41
|
+
let result = "", cursor = 0;
|
|
42
|
+
do {
|
|
43
|
+
result += string.substring(cursor, index) + replace;
|
|
44
|
+
cursor = index + close.length;
|
|
45
|
+
index = string.indexOf(close, cursor);
|
|
46
|
+
} while (~index);
|
|
47
|
+
return result + string.substring(cursor);
|
|
48
|
+
};
|
|
49
|
+
var createColors = (enabled = isColorSupported) => {
|
|
50
|
+
let f = enabled ? formatter : () => String;
|
|
51
|
+
return {
|
|
52
|
+
isColorSupported: enabled,
|
|
53
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
54
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
55
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
56
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
57
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
58
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
59
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
60
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
61
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
62
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
63
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
64
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
65
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
66
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
67
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
68
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
69
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
70
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
71
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
72
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
73
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
74
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
75
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
76
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
77
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
78
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
79
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
80
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
81
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
82
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
83
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
84
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
85
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
86
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
87
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
88
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
89
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
90
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
91
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
92
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
93
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
module2.exports = createColors();
|
|
97
|
+
module2.exports.createColors = createColors;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// node_modules/sisteransi/src/index.js
|
|
102
|
+
var require_src = __commonJS({
|
|
103
|
+
"node_modules/sisteransi/src/index.js"(exports2, module2) {
|
|
104
|
+
"use strict";
|
|
105
|
+
var ESC = "\x1B";
|
|
106
|
+
var CSI = `${ESC}[`;
|
|
107
|
+
var beep = "\x07";
|
|
108
|
+
var cursor = {
|
|
109
|
+
to(x2, y3) {
|
|
110
|
+
if (!y3) return `${CSI}${x2 + 1}G`;
|
|
111
|
+
return `${CSI}${y3 + 1};${x2 + 1}H`;
|
|
112
|
+
},
|
|
113
|
+
move(x2, y3) {
|
|
114
|
+
let ret = "";
|
|
115
|
+
if (x2 < 0) ret += `${CSI}${-x2}D`;
|
|
116
|
+
else if (x2 > 0) ret += `${CSI}${x2}C`;
|
|
117
|
+
if (y3 < 0) ret += `${CSI}${-y3}A`;
|
|
118
|
+
else if (y3 > 0) ret += `${CSI}${y3}B`;
|
|
119
|
+
return ret;
|
|
120
|
+
},
|
|
121
|
+
up: (count = 1) => `${CSI}${count}A`,
|
|
122
|
+
down: (count = 1) => `${CSI}${count}B`,
|
|
123
|
+
forward: (count = 1) => `${CSI}${count}C`,
|
|
124
|
+
backward: (count = 1) => `${CSI}${count}D`,
|
|
125
|
+
nextLine: (count = 1) => `${CSI}E`.repeat(count),
|
|
126
|
+
prevLine: (count = 1) => `${CSI}F`.repeat(count),
|
|
127
|
+
left: `${CSI}G`,
|
|
128
|
+
hide: `${CSI}?25l`,
|
|
129
|
+
show: `${CSI}?25h`,
|
|
130
|
+
save: `${ESC}7`,
|
|
131
|
+
restore: `${ESC}8`
|
|
132
|
+
};
|
|
133
|
+
var scroll = {
|
|
134
|
+
up: (count = 1) => `${CSI}S`.repeat(count),
|
|
135
|
+
down: (count = 1) => `${CSI}T`.repeat(count)
|
|
136
|
+
};
|
|
137
|
+
var erase = {
|
|
138
|
+
screen: `${CSI}2J`,
|
|
139
|
+
up: (count = 1) => `${CSI}1J`.repeat(count),
|
|
140
|
+
down: (count = 1) => `${CSI}J`.repeat(count),
|
|
141
|
+
line: `${CSI}2K`,
|
|
142
|
+
lineEnd: `${CSI}K`,
|
|
143
|
+
lineStart: `${CSI}1K`,
|
|
144
|
+
lines(count) {
|
|
145
|
+
let clear = "";
|
|
146
|
+
for (let i = 0; i < count; i++)
|
|
147
|
+
clear += this.line + (i < count - 1 ? cursor.up() : "");
|
|
148
|
+
if (count)
|
|
149
|
+
clear += cursor.left;
|
|
150
|
+
return clear;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
module2.exports = { cursor, scroll, erase, beep };
|
|
154
|
+
}
|
|
155
|
+
});
|
|
3
156
|
|
|
4
157
|
// src/cli.ts
|
|
5
|
-
var
|
|
6
|
-
var import_node_path5 = require("node:path");
|
|
158
|
+
var import_node_path8 = require("node:path");
|
|
7
159
|
|
|
8
160
|
// src/config.ts
|
|
9
161
|
var import_node_os = require("node:os");
|
|
@@ -34,6 +186,43 @@ function getCredentialsPath() {
|
|
|
34
186
|
function getProjectArtifactsDir(cwd = process.cwd()) {
|
|
35
187
|
return (0, import_node_path.join)(cwd, ".viberaven");
|
|
36
188
|
}
|
|
189
|
+
var LAST_SCAN_FILE = "last-scan.json";
|
|
190
|
+
async function findArtifactsWorkspace(startDir = process.cwd()) {
|
|
191
|
+
let dir = (0, import_node_path.resolve)(startDir);
|
|
192
|
+
const fsRoot = (0, import_node_path.parse)(dir).root;
|
|
193
|
+
while (true) {
|
|
194
|
+
try {
|
|
195
|
+
await (0, import_promises2.access)((0, import_node_path.join)(dir, ".viberaven", LAST_SCAN_FILE), import_node_fs.constants.F_OK);
|
|
196
|
+
return dir;
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
200
|
+
if (parent === dir || dir === fsRoot) {
|
|
201
|
+
return void 0;
|
|
202
|
+
}
|
|
203
|
+
dir = parent;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function resolveWorkspaceRoot(startDir = process.cwd()) {
|
|
207
|
+
const withArtifacts = await findArtifactsWorkspace(startDir);
|
|
208
|
+
if (withArtifacts) {
|
|
209
|
+
return withArtifacts;
|
|
210
|
+
}
|
|
211
|
+
let dir = (0, import_node_path.resolve)(startDir);
|
|
212
|
+
const fsRoot = (0, import_node_path.parse)(dir).root;
|
|
213
|
+
while (true) {
|
|
214
|
+
try {
|
|
215
|
+
await (0, import_promises2.access)((0, import_node_path.join)(dir, ".git"), import_node_fs.constants.F_OK);
|
|
216
|
+
return dir;
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
220
|
+
if (parent === dir || dir === fsRoot) {
|
|
221
|
+
return (0, import_node_path.resolve)(startDir);
|
|
222
|
+
}
|
|
223
|
+
dir = parent;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
37
226
|
async function ensureConfigDir() {
|
|
38
227
|
const dir = getConfigDir();
|
|
39
228
|
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
@@ -104,21 +293,21 @@ function isNetworkFetchFailure(error) {
|
|
|
104
293
|
return false;
|
|
105
294
|
}
|
|
106
295
|
if (error instanceof TypeError) {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
296
|
+
const m2 = error.message;
|
|
297
|
+
if (m2 === "fetch failed" || m2.toLowerCase().includes("fetch failed") || m2.includes("Failed to fetch")) {
|
|
109
298
|
return true;
|
|
110
299
|
}
|
|
111
300
|
}
|
|
112
301
|
if (error instanceof AggregateError) {
|
|
113
|
-
return error.errors.some((
|
|
302
|
+
return error.errors.some((e3) => isNetworkFetchFailure(e3));
|
|
114
303
|
}
|
|
115
304
|
if (error instanceof Error && "cause" in error && error.cause != null) {
|
|
116
305
|
return isNetworkFetchFailure(error.cause);
|
|
117
306
|
}
|
|
118
|
-
const
|
|
119
|
-
if (typeof
|
|
307
|
+
const e2 = error;
|
|
308
|
+
if (typeof e2.code === "string") {
|
|
120
309
|
if (["ECONNREFUSED", "ECONNRESET", "ENOTFOUND", "EAI_AGAIN", "ETIMEDOUT", "ENETUNREACH", "EHOSTUNREACH"].includes(
|
|
121
|
-
|
|
310
|
+
e2.code
|
|
122
311
|
)) {
|
|
123
312
|
return true;
|
|
124
313
|
}
|
|
@@ -249,7 +438,7 @@ function isManagedUsage(value) {
|
|
|
249
438
|
if (!Array.isArray(value.unlockedMapCategoryKeys)) {
|
|
250
439
|
return false;
|
|
251
440
|
}
|
|
252
|
-
return value.unlockedMapCategoryKeys.every((
|
|
441
|
+
return value.unlockedMapCategoryKeys.every((k3) => typeof k3 === "string");
|
|
253
442
|
}
|
|
254
443
|
var BackendHttpError = class extends Error {
|
|
255
444
|
constructor(message, status, upgradeUrl) {
|
|
@@ -300,6 +489,20 @@ async function syncCredentialsFromAccount(credentials) {
|
|
|
300
489
|
await saveCredentials(updated);
|
|
301
490
|
return { ...updated, account };
|
|
302
491
|
}
|
|
492
|
+
async function enrichArtifactWithAccount(artifact, apiBaseUrl, accessToken) {
|
|
493
|
+
try {
|
|
494
|
+
const account = await fetchAccountMe(apiBaseUrl, accessToken);
|
|
495
|
+
return {
|
|
496
|
+
...artifact,
|
|
497
|
+
accountEmail: account.email,
|
|
498
|
+
plan: account.plan,
|
|
499
|
+
usage: account.usage,
|
|
500
|
+
usageLine: formatUsageLine(account.usage)
|
|
501
|
+
};
|
|
502
|
+
} catch {
|
|
503
|
+
return artifact;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
303
506
|
function formatUsageLine(usage) {
|
|
304
507
|
const periodLabel = usage.period === "monthly" ? "this month" : "lifetime";
|
|
305
508
|
return `Scans: ${usage.used}/${usage.limit} (${periodLabel}, ${usage.plan}) \xB7 ${usage.remainingPrompts} remaining`;
|
|
@@ -317,7 +520,7 @@ function formatScanLimitMessage(upgradeUrl) {
|
|
|
317
520
|
|
|
318
521
|
// src/auth.ts
|
|
319
522
|
function sleep(ms) {
|
|
320
|
-
return new Promise((
|
|
523
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
321
524
|
}
|
|
322
525
|
async function runDeviceLogin(apiBaseUrl) {
|
|
323
526
|
const signIn = await startManagedSignIn(apiBaseUrl);
|
|
@@ -479,6 +682,7 @@ var STACK_SIGNAL_PATHS = {
|
|
|
479
682
|
hasDocker: ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"],
|
|
480
683
|
hasCI: [".github/workflows", ".gitlab-ci.yml", "circle.yml"],
|
|
481
684
|
hasStripe: [],
|
|
685
|
+
hasPolar: [],
|
|
482
686
|
hasClerk: [],
|
|
483
687
|
hasAuthJs: [],
|
|
484
688
|
hasPaddle: [],
|
|
@@ -524,7 +728,7 @@ async function deepScanWorkspace(workspaceRoot, options = {}) {
|
|
|
524
728
|
} catch {
|
|
525
729
|
return;
|
|
526
730
|
}
|
|
527
|
-
entries.sort((a,
|
|
731
|
+
entries.sort((a, b3) => a.name.localeCompare(b3.name));
|
|
528
732
|
for (const entry of entries) {
|
|
529
733
|
const fullPath = (0, import_node_path2.join)(dir, entry.name);
|
|
530
734
|
const relPath = (0, import_node_path2.relative)(workspaceRoot, fullPath);
|
|
@@ -555,7 +759,7 @@ async function deepScanWorkspace(workspaceRoot, options = {}) {
|
|
|
555
759
|
} catch {
|
|
556
760
|
return;
|
|
557
761
|
}
|
|
558
|
-
entries.sort((a,
|
|
762
|
+
entries.sort((a, b3) => a.name.localeCompare(b3.name));
|
|
559
763
|
for (const entry of entries) {
|
|
560
764
|
const fullPath = (0, import_node_path2.join)(dir, entry.name);
|
|
561
765
|
const relPath = (0, import_node_path2.join)(relDir, entry.name);
|
|
@@ -569,7 +773,7 @@ async function deepScanWorkspace(workspaceRoot, options = {}) {
|
|
|
569
773
|
}
|
|
570
774
|
}
|
|
571
775
|
await walk(workspaceRoot, 0);
|
|
572
|
-
const scored = allFiles.map((f) => ({ path: f, score: scoreFile(f) })).sort((a,
|
|
776
|
+
const scored = allFiles.map((f) => ({ path: f, score: scoreFile(f) })).sort((a, b3) => b3.score - a.score || a.path.localeCompare(b3.path)).slice(0, opts.maxFiles);
|
|
573
777
|
let totalBytes = 0;
|
|
574
778
|
const scannedFiles = [];
|
|
575
779
|
for (const { path: relPath, score } of scored) {
|
|
@@ -598,7 +802,7 @@ async function deepScanWorkspace(workspaceRoot, options = {}) {
|
|
|
598
802
|
for (const [signal, paths] of Object.entries(STACK_SIGNAL_PATHS)) {
|
|
599
803
|
if (paths.length > 0) {
|
|
600
804
|
stackSignals[signal] = paths.some(
|
|
601
|
-
(
|
|
805
|
+
(p2) => allPathsSet.has(p2) || [...allPathsSet].some((ap) => ap.replace(/\\/g, "/").includes(p2))
|
|
602
806
|
);
|
|
603
807
|
}
|
|
604
808
|
}
|
|
@@ -611,6 +815,7 @@ ${pathBlob}
|
|
|
611
815
|
${contentBlob}`;
|
|
612
816
|
stackSignals.hasSupabase = Boolean(stackSignals.hasSupabase) || hasPackage(packageDepsLower, ["@supabase/"]) || /\bsupabase\b/.test(repoBlob);
|
|
613
817
|
stackSignals.hasStripe = hasPackage(packageDepsLower, ["stripe"]) || /\bstripe_(secret_key|webhook_secret)\b/.test(repoBlob) || /stripe/.test(pathBlob);
|
|
818
|
+
stackSignals.hasPolar = hasPackage(packageDepsLower, ["@polar-sh/"]) || /\bpolar_(access_token|webhook_secret|pro_product_id|sandbox)\b/.test(repoBlob) || /\bpolar\b/.test(pathBlob);
|
|
614
819
|
stackSignals.hasClerk = hasPackage(packageDepsLower, ["@clerk/"]) || /\bclerk_(secret_key|publishable_key)\b|\bnext_public_clerk_/.test(repoBlob);
|
|
615
820
|
stackSignals.hasAuthJs = hasPackage(packageDepsLower, ["next-auth", "@auth/"]) || /\b(auth_secret|nextauth_secret)\b|\bauth\.js\b/.test(repoBlob);
|
|
616
821
|
stackSignals.hasAuth = Boolean(stackSignals.hasAuth) || Boolean(stackSignals.hasClerk) || Boolean(stackSignals.hasAuthJs) || /\bauthmiddleware\b|\bclerkmiddleware\b/.test(repoBlob);
|
|
@@ -628,7 +833,7 @@ ${contentBlob}`;
|
|
|
628
833
|
stackSignals.hasMongoose = packageDepsLower.includes("mongoose");
|
|
629
834
|
stackSignals.hasTests = Boolean(stackSignals.hasTests) || hasPackage(packageDepsLower, ["vitest", "jest", "@playwright/test", "cypress"]) || /\.test\.[jt]sx?\b|\.spec\.[jt]sx?\b/.test(pathBlob);
|
|
630
835
|
stackSignals.hasRateLimit = packageDeps.some(
|
|
631
|
-
(
|
|
836
|
+
(d3) => ["express-rate-limit", "rate-limiter-flexible", "@fastify/rate-limit", "upstash"].some((r) => d3.includes(r))
|
|
632
837
|
) || scanContainsRateLimit(scannedFiles);
|
|
633
838
|
stackSignals.hasCI = Boolean(stackSignals.hasCI) || /(^|\n)\.github\/workflows\/[^/\n]+\.ya?ml(\n|$)|(^|\n)(\.gitlab-ci\.yml|circle\.yml)(\n|$)/i.test(pathBlob);
|
|
634
839
|
stackSignals.hasRobots = Boolean(stackSignals.hasRobots) || /(^|\n|\/)robots\.txt(\n|$)/i.test(pathBlob);
|
|
@@ -675,7 +880,7 @@ function scoreFile(relPath) {
|
|
|
675
880
|
"app.ts"
|
|
676
881
|
].includes(name)) score += 5;
|
|
677
882
|
if (/types?|\.d\.ts/.test(name)) score += 3;
|
|
678
|
-
if (parts.some((
|
|
883
|
+
if (parts.some((p2) => ["hooks", "store", "stores", "context"].includes(p2))) score += 4;
|
|
679
884
|
if (/db|database/i.test(relPath)) score += 5;
|
|
680
885
|
score -= Math.max(0, parts.length - 3) * 0.5;
|
|
681
886
|
return score;
|
|
@@ -931,10 +1136,10 @@ function rootGapKey(gap) {
|
|
|
931
1136
|
}
|
|
932
1137
|
return slugify([gap.category, gap.copyPrompt].join(" ")) || gap.id;
|
|
933
1138
|
}
|
|
934
|
-
function mergeToolSuggestions(a,
|
|
1139
|
+
function mergeToolSuggestions(a, b3) {
|
|
935
1140
|
const seen = /* @__PURE__ */ new Set();
|
|
936
1141
|
const out = [];
|
|
937
|
-
for (const tool of [...a, ...
|
|
1142
|
+
for (const tool of [...a, ...b3]) {
|
|
938
1143
|
const key = `${tool.name.trim().toLowerCase()}|${tool.url.trim().toLowerCase()}`;
|
|
939
1144
|
if (!seen.has(key)) {
|
|
940
1145
|
seen.add(key);
|
|
@@ -943,15 +1148,15 @@ function mergeToolSuggestions(a, b) {
|
|
|
943
1148
|
}
|
|
944
1149
|
return out;
|
|
945
1150
|
}
|
|
946
|
-
function mergeRootGaps(a,
|
|
947
|
-
const severity = SEVERITY_RANK[
|
|
1151
|
+
function mergeRootGaps(a, b3) {
|
|
1152
|
+
const severity = SEVERITY_RANK[b3.severity] > SEVERITY_RANK[a.severity] ? b3.severity : a.severity;
|
|
948
1153
|
return {
|
|
949
1154
|
...a,
|
|
950
1155
|
severity,
|
|
951
|
-
detail:
|
|
952
|
-
copyPrompt:
|
|
953
|
-
toolSuggestions: mergeToolSuggestions(a.toolSuggestions,
|
|
954
|
-
mcpSuggestion: a.mcpSuggestion ??
|
|
1156
|
+
detail: b3.detail.length > a.detail.length ? b3.detail : a.detail,
|
|
1157
|
+
copyPrompt: b3.copyPrompt.length > a.copyPrompt.length ? b3.copyPrompt : a.copyPrompt,
|
|
1158
|
+
toolSuggestions: mergeToolSuggestions(a.toolSuggestions, b3.toolSuggestions),
|
|
1159
|
+
mcpSuggestion: a.mcpSuggestion ?? b3.mcpSuggestion,
|
|
955
1160
|
primaryMapCategory: a.primaryMapCategory,
|
|
956
1161
|
affectedMapCategories: a.affectedMapCategories
|
|
957
1162
|
};
|
|
@@ -995,7 +1200,7 @@ function normalizeChecklist(v) {
|
|
|
995
1200
|
};
|
|
996
1201
|
}
|
|
997
1202
|
function normalizeModelOutput(raw) {
|
|
998
|
-
const gaps = Array.isArray(raw.gaps) ? raw.gaps.map(normalizeGap).filter((
|
|
1203
|
+
const gaps = Array.isArray(raw.gaps) ? raw.gaps.map(normalizeGap).filter((g2) => g2 !== null) : [];
|
|
999
1204
|
const uniqueGaps = dedupeRootGaps(gaps);
|
|
1000
1205
|
return {
|
|
1001
1206
|
score: stableVisibleScore(raw.score),
|
|
@@ -1301,6 +1506,11 @@ var PROVIDERS = [
|
|
|
1301
1506
|
},
|
|
1302
1507
|
verification: { supportsReadOnly: false }
|
|
1303
1508
|
}),
|
|
1509
|
+
provider("polar", "Polar", ["polar", "polarsh"], ["payments"], ["payments"], "polar", {
|
|
1510
|
+
docsUrl: "https://polar.sh/docs",
|
|
1511
|
+
dashboardUrl: "https://polar.sh/dashboard",
|
|
1512
|
+
verification: { supportsReadOnly: false }
|
|
1513
|
+
}),
|
|
1304
1514
|
provider("vercel", "Vercel", ["vercel"], ["deployment"], ["deployment"], "vercel", {
|
|
1305
1515
|
docsUrl: "https://vercel.com/docs",
|
|
1306
1516
|
dashboardUrl: "https://vercel.com/dashboard",
|
|
@@ -1768,7 +1978,7 @@ function buildAreas(providerMissions) {
|
|
|
1768
1978
|
// ../../src/station/promptBuilder.ts
|
|
1769
1979
|
function buildAnalysisPrompt(scan, specContent, productionConnectionContext, scannerEvidenceContext, stackWiringContext, stackAutomationContext) {
|
|
1770
1980
|
const sections = [];
|
|
1771
|
-
const detectedStack = Object.entries(scan.stackSignals).filter(([, v]) => v === true).map(([
|
|
1981
|
+
const detectedStack = Object.entries(scan.stackSignals).filter(([, v]) => v === true).map(([k3]) => k3).join(", ");
|
|
1772
1982
|
sections.push(`## PROJECT CONTEXT
|
|
1773
1983
|
Total files in repo: ${scan.totalFilesScanned}
|
|
1774
1984
|
Files analyzed: ${scan.files.length}
|
|
@@ -2285,6 +2495,8 @@ function envPatternFor(provider2) {
|
|
|
2285
2495
|
return /\bstripe_(secret_key|webhook_secret)\b/i;
|
|
2286
2496
|
case "paddle":
|
|
2287
2497
|
return /\bpaddle_(api_key|webhook_secret|client_token)\b/i;
|
|
2498
|
+
case "polar":
|
|
2499
|
+
return /\bpolar_(access_token|webhook_secret|pro_product_id|sandbox)\b/i;
|
|
2288
2500
|
case "sentry":
|
|
2289
2501
|
return /\bsentry_dsn\b/i;
|
|
2290
2502
|
case "posthog":
|
|
@@ -2305,6 +2517,8 @@ function webhookPatternFor(provider2) {
|
|
|
2305
2517
|
return /\bstripe\b.*\bwebhook\b|\bwebhook\b.*\bstripe\b/i;
|
|
2306
2518
|
case "paddle":
|
|
2307
2519
|
return /\bpaddle\b.*\bwebhook\b|\bwebhook\b.*\bpaddle\b/i;
|
|
2520
|
+
case "polar":
|
|
2521
|
+
return /\bpolar\b.*\bwebhook\b|\bwebhook\b.*\bpolar\b/i;
|
|
2308
2522
|
default:
|
|
2309
2523
|
return /\bwebhook\b/i;
|
|
2310
2524
|
}
|
|
@@ -3688,6 +3902,7 @@ function analyzeStackWiring(scan) {
|
|
|
3688
3902
|
analyzePlanetScaleDatabase(ctx),
|
|
3689
3903
|
analyzeStripePayments(ctx),
|
|
3690
3904
|
analyzePaddlePayments(ctx),
|
|
3905
|
+
analyzePolarPayments(ctx),
|
|
3691
3906
|
analyzeVercelDeployment(ctx),
|
|
3692
3907
|
analyzeNetlifyDeployment(ctx),
|
|
3693
3908
|
analyzeAwsDeployment(ctx),
|
|
@@ -4017,6 +4232,26 @@ function analyzePaddlePayments(ctx) {
|
|
|
4017
4232
|
]
|
|
4018
4233
|
});
|
|
4019
4234
|
}
|
|
4235
|
+
function analyzePolarPayments(ctx) {
|
|
4236
|
+
return summarize({
|
|
4237
|
+
key: "polar-payments",
|
|
4238
|
+
provider: "polar",
|
|
4239
|
+
providerLabel: "Polar",
|
|
4240
|
+
area: "payments",
|
|
4241
|
+
areaLabel: "Payments",
|
|
4242
|
+
promptSubject: "Polar payments",
|
|
4243
|
+
items: [
|
|
4244
|
+
item2("api-client-found", "Polar SDK or API client found", hasPackage2(ctx, [/@polar-sh\//]) || Boolean(ctx.scan.stackSignals.hasPolar) || /api\.polar\.sh|sandbox-api\.polar\.sh|polarbillingservice/i.test(ctx.contentBlob), packageEvidence2(ctx, [/@polar-sh\//]).concat(fileEvidence(ctx, /api\.polar\.sh|sandbox-api\.polar\.sh|polarbillingservice/i)), "Add a Polar SDK or server-side API client for checkout and customer state."),
|
|
4245
|
+
item2("env-names-documented", "Polar env names documented", /polar_access_token|polar_webhook_secret|polar_pro_product_id/i.test(ctx.contentBlob), envEvidence2(ctx, [/polar_access_token/i, /polar_webhook_secret/i, /polar_pro_product_id/i, /polar_sandbox/i]), "Document POLAR_ACCESS_TOKEN, POLAR_WEBHOOK_SECRET, and product ID env names in safe examples."),
|
|
4246
|
+
item2("checkout-found", "Polar checkout flow found", /polar.*checkout|checkout.*polar|\/checkouts\b|createcheckoutsession/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /polar.*checkout|checkout.*polar|\/checkouts\b|createcheckoutsession/i), "Add a server-side Polar checkout flow for paid plans."),
|
|
4247
|
+
item2("webhook-route-found", "Polar webhook route found", /polar.*webhook|webhook.*polar/i.test(ctx.pathBlob + "\n" + ctx.contentBlob), pathEvidence2(ctx, /polar.*webhook|webhook.*polar/i).concat(fileEvidence(ctx, /polar.*webhook|webhook.*polar/i)), "Add a Polar webhook route for subscription and customer lifecycle events."),
|
|
4248
|
+
item2("webhook-signature-found", "Polar webhook verification found", /polar_webhook_secret|verify.*polar|polar.*signature|webhook.*signature/i.test(ctx.contentBlob), fileEvidence(ctx, /polar_webhook_secret|verify.*polar|polar.*signature|webhook.*signature/i), "Verify Polar webhook signatures before processing billing events."),
|
|
4249
|
+
item2("customer-state-found", "Polar customer or subscription state found", /external_customer_id|customer_portal|customer[_-]?state|polar_subscription_id|polar_customer_id/i.test(ctx.contentBlob), fileEvidence(ctx, /external_customer_id|customer_portal|customer[_-]?state|polar_subscription_id|polar_customer_id/i), "Persist Polar customer/subscription state and expose a customer portal path for paid users."),
|
|
4250
|
+
secretSafetyItem(ctx, "secret-not-exposed", "Polar secret not exposed to frontend", /polar_access_token|polar_webhook_secret/i, "Move Polar access tokens and webhook secrets to server-only code."),
|
|
4251
|
+
manualItem("production-dashboard-checked", "Production Polar products and webhooks checked", "Confirm products, prices, customer portal, webhook URL, and event list in Polar Dashboard.")
|
|
4252
|
+
]
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4020
4255
|
function analyzeNeonDatabase(ctx) {
|
|
4021
4256
|
return databaseSummary(ctx, {
|
|
4022
4257
|
key: "neon-database",
|
|
@@ -4950,7 +5185,7 @@ function collectProductionConnectionSummaries(productionConnections) {
|
|
|
4950
5185
|
function providerLabel2(provider2) {
|
|
4951
5186
|
return PROVIDER_LABELS[provider2] ?? provider2.replace(
|
|
4952
5187
|
/(^|-)([a-z])/g,
|
|
4953
|
-
(
|
|
5188
|
+
(_3, prefix, letter) => `${prefix === "-" ? " " : ""}${letter.toUpperCase()}`
|
|
4954
5189
|
);
|
|
4955
5190
|
}
|
|
4956
5191
|
|
|
@@ -5100,6 +5335,111 @@ async function loadProductionConnectionChoices(deps) {
|
|
|
5100
5335
|
}
|
|
5101
5336
|
}
|
|
5102
5337
|
|
|
5338
|
+
// ../../src/station/stackMapProviderOptions.ts
|
|
5339
|
+
var STACK_MAP_PROVIDER_OPTIONS = {
|
|
5340
|
+
appFlow: [
|
|
5341
|
+
{ name: "Figma", toolName: "Figma", description: "Map flows, screens, and onboarding states" },
|
|
5342
|
+
{ name: "Storybook", toolName: "Storybook", description: "Component states and UI variants" },
|
|
5343
|
+
{ name: "Product Spec", toolName: "product-spec", description: "PRD, acceptance criteria, and success goals" },
|
|
5344
|
+
{ name: "Route Map", toolName: "route-map", description: "Routes, navigation, and protected paths" }
|
|
5345
|
+
],
|
|
5346
|
+
frontend: [
|
|
5347
|
+
{ name: "React", toolName: "React", description: "Component UI and client state" },
|
|
5348
|
+
{ name: "Vue", toolName: "Vue", description: "Vue or Nuxt product UI" },
|
|
5349
|
+
{ name: "Svelte", toolName: "Svelte", description: "SvelteKit routes and components" },
|
|
5350
|
+
{ name: "Angular", toolName: "Angular", description: "Angular components and router" }
|
|
5351
|
+
],
|
|
5352
|
+
backend: [
|
|
5353
|
+
{ name: "Node.js", toolName: "Node.js", description: "API routes and server behavior" },
|
|
5354
|
+
{ name: "Python / FastAPI", toolName: "Python", description: "Python API services and validation" },
|
|
5355
|
+
{ name: "Rails", toolName: "Rails", description: "Rails routes, controllers, and models" },
|
|
5356
|
+
{ name: "Go", toolName: "Go", description: "Go HTTP handlers and services" }
|
|
5357
|
+
],
|
|
5358
|
+
security: [
|
|
5359
|
+
{ name: "Rate limiting", toolName: "rate-limit", description: "Protect API routes from abuse and retry loops" },
|
|
5360
|
+
{ name: "Bot protection", toolName: "bot protection", description: "Screen bots before expensive flows" },
|
|
5361
|
+
{ name: "Secrets hygiene", toolName: "secrets hygiene", description: "Keep env examples and secret files safe" }
|
|
5362
|
+
],
|
|
5363
|
+
auth: [
|
|
5364
|
+
{ name: "Clerk", toolName: "Clerk", description: "Managed auth" },
|
|
5365
|
+
{ name: "Auth.js", toolName: "Auth.js", description: "Framework auth" },
|
|
5366
|
+
{ name: "Supabase Auth", toolName: "Supabase Auth", description: "Auth with Supabase" },
|
|
5367
|
+
{ name: "Auth0", toolName: "Auth0", description: "Enterprise identity and social login" },
|
|
5368
|
+
{ name: "Better Auth", toolName: "better-auth", description: "Type-safe auth in your codebase" }
|
|
5369
|
+
],
|
|
5370
|
+
database: [
|
|
5371
|
+
{ name: "Supabase", toolName: "Supabase", description: "Postgres + auth + storage" },
|
|
5372
|
+
{ name: "Neon", toolName: "Neon", description: "Serverless Postgres" },
|
|
5373
|
+
{ name: "Turso", toolName: "Turso", description: "Edge SQLite" },
|
|
5374
|
+
{ name: "MongoDB", toolName: "MongoDB", description: "Document database" },
|
|
5375
|
+
{ name: "PlanetScale", toolName: "PlanetScale", description: "Serverless MySQL" }
|
|
5376
|
+
],
|
|
5377
|
+
payments: [
|
|
5378
|
+
{ name: "Stripe", toolName: "Stripe", description: "Global standard" },
|
|
5379
|
+
{ name: "Paddle", toolName: "Paddle", description: "MoR subscriptions" },
|
|
5380
|
+
{ name: "Polar", toolName: "Polar", description: "Developer-native MoR billing" },
|
|
5381
|
+
{ name: "Lemon Squeezy", toolName: "Lemon Squeezy", description: "Digital products and SaaS billing" }
|
|
5382
|
+
],
|
|
5383
|
+
deployment: [
|
|
5384
|
+
{ name: "Vercel", toolName: "Vercel", description: "Preview, env, domains" },
|
|
5385
|
+
{ name: "Netlify", toolName: "Netlify", description: "Static and serverless deploy" },
|
|
5386
|
+
{ name: "Render", toolName: "Render", description: "Simple web services and background jobs" },
|
|
5387
|
+
{ name: "Railway", toolName: "Railway", description: "Fast deploy loops and managed infra" },
|
|
5388
|
+
{ name: "Cloudflare", toolName: "Cloudflare", description: "Pages, Workers, DNS, and edge stack" },
|
|
5389
|
+
{ name: "AWS", toolName: "AWS", description: "Cloud infrastructure" }
|
|
5390
|
+
],
|
|
5391
|
+
landing: [
|
|
5392
|
+
{ name: "Supabase", toolName: "Supabase", description: "Waitlist and onboarding data" },
|
|
5393
|
+
{ name: "PostHog", toolName: "PostHog", description: "Activation tracking" }
|
|
5394
|
+
],
|
|
5395
|
+
monitoring: [
|
|
5396
|
+
{ name: "Sentry", toolName: "Sentry", description: "Errors and replay" },
|
|
5397
|
+
{ name: "PostHog", toolName: "PostHog", description: "Analytics and events" },
|
|
5398
|
+
{ name: "LogRocket", toolName: "LogRocket", description: "Session replay" }
|
|
5399
|
+
],
|
|
5400
|
+
testing: [
|
|
5401
|
+
{ name: "Vitest", toolName: "Vitest", description: "Unit coverage" },
|
|
5402
|
+
{ name: "Playwright", toolName: "Playwright", description: "Browser flows" },
|
|
5403
|
+
{ name: "GitHub", toolName: "GitHub", description: "Actions CI and PR checks" },
|
|
5404
|
+
{ name: "GitLab", toolName: "GitLab", description: "Pipelines and merge checks" }
|
|
5405
|
+
],
|
|
5406
|
+
errorHandling: [
|
|
5407
|
+
{ name: "Sentry", toolName: "Sentry", description: "Exception capture" },
|
|
5408
|
+
{ name: "PostHog", toolName: "PostHog", description: "Error events" }
|
|
5409
|
+
]
|
|
5410
|
+
};
|
|
5411
|
+
function stackMapProviderOptionsForArea(areaKey) {
|
|
5412
|
+
const rows = STACK_MAP_PROVIDER_OPTIONS[areaKey] ?? [];
|
|
5413
|
+
return rows.slice(0, 6).map((row) => ({
|
|
5414
|
+
provider: row.toolName,
|
|
5415
|
+
label: row.name,
|
|
5416
|
+
description: row.description
|
|
5417
|
+
}));
|
|
5418
|
+
}
|
|
5419
|
+
|
|
5420
|
+
// src/report/hydrateArtifact.ts
|
|
5421
|
+
var STACK_MAP_AREAS = [
|
|
5422
|
+
"appFlow",
|
|
5423
|
+
"frontend",
|
|
5424
|
+
"backend",
|
|
5425
|
+
"auth",
|
|
5426
|
+
"database",
|
|
5427
|
+
"payments",
|
|
5428
|
+
"deployment",
|
|
5429
|
+
"monitoring",
|
|
5430
|
+
"security",
|
|
5431
|
+
"testing",
|
|
5432
|
+
"landing",
|
|
5433
|
+
"errorHandling"
|
|
5434
|
+
];
|
|
5435
|
+
function hydrateArtifactForReport(artifact) {
|
|
5436
|
+
const providerOptions = { ...artifact.providerOptions ?? {} };
|
|
5437
|
+
for (const area of STACK_MAP_AREAS) {
|
|
5438
|
+
providerOptions[area] = stackMapProviderOptionsForArea(area);
|
|
5439
|
+
}
|
|
5440
|
+
return { ...artifact, providerOptions };
|
|
5441
|
+
}
|
|
5442
|
+
|
|
5103
5443
|
// src/runScan.ts
|
|
5104
5444
|
function computeProductionCorePercent(missionGraph) {
|
|
5105
5445
|
const areas = missionGraph.areas ?? [];
|
|
@@ -5109,14 +5449,6 @@ function computeProductionCorePercent(missionGraph) {
|
|
|
5109
5449
|
const sum = areas.reduce((acc, area) => acc + (area.readinessPercent ?? 0), 0);
|
|
5110
5450
|
return Math.round(sum / areas.length);
|
|
5111
5451
|
}
|
|
5112
|
-
function buildProviderOptions() {
|
|
5113
|
-
const byArea = productionProvidersByArea();
|
|
5114
|
-
const options = {};
|
|
5115
|
-
for (const [area, providers] of Object.entries(byArea)) {
|
|
5116
|
-
options[area] = providers.map((provider2) => ({ provider: provider2, label: providerLabel(provider2) }));
|
|
5117
|
-
}
|
|
5118
|
-
return options;
|
|
5119
|
-
}
|
|
5120
5452
|
function toArtifact(workspacePath, result, selectedProviders) {
|
|
5121
5453
|
return {
|
|
5122
5454
|
version: 1,
|
|
@@ -5129,10 +5461,10 @@ function toArtifact(workspacePath, result, selectedProviders) {
|
|
|
5129
5461
|
gaps: result.gaps,
|
|
5130
5462
|
missionGraph: result.missionGraph,
|
|
5131
5463
|
stackWiring: result.stackWiring,
|
|
5464
|
+
stackAutomation: result.stackAutomation,
|
|
5132
5465
|
providerRegistry: result.providerRegistry,
|
|
5133
5466
|
verificationSummary: result.verificationSummary,
|
|
5134
5467
|
productionCorePercent: computeProductionCorePercent(result.missionGraph),
|
|
5135
|
-
providerOptions: buildProviderOptions(),
|
|
5136
5468
|
selectedProviders,
|
|
5137
5469
|
usage: result.usage
|
|
5138
5470
|
};
|
|
@@ -5178,7 +5510,9 @@ async function runProjectScan(options) {
|
|
|
5178
5510
|
selectedProviders[area] = choice.provider;
|
|
5179
5511
|
}
|
|
5180
5512
|
}
|
|
5181
|
-
const artifact =
|
|
5513
|
+
const artifact = hydrateArtifactForReport(
|
|
5514
|
+
toArtifact(workspacePath, result, selectedProviders)
|
|
5515
|
+
);
|
|
5182
5516
|
return { ok: true, artifact };
|
|
5183
5517
|
} catch (error) {
|
|
5184
5518
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -5188,7 +5522,7 @@ async function runProjectScan(options) {
|
|
|
5188
5522
|
|
|
5189
5523
|
// src/artifacts.ts
|
|
5190
5524
|
var import_promises3 = require("node:fs/promises");
|
|
5191
|
-
var
|
|
5525
|
+
var import_node_path5 = require("node:path");
|
|
5192
5526
|
|
|
5193
5527
|
// src/report/agentSummary.ts
|
|
5194
5528
|
var SEVERITY_ORDER = {
|
|
@@ -5197,12 +5531,12 @@ var SEVERITY_ORDER = {
|
|
|
5197
5531
|
info: 2
|
|
5198
5532
|
};
|
|
5199
5533
|
function sortGaps(gaps) {
|
|
5200
|
-
return [...gaps].sort((a,
|
|
5201
|
-
const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[
|
|
5534
|
+
return [...gaps].sort((a, b3) => {
|
|
5535
|
+
const sev = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b3.severity];
|
|
5202
5536
|
if (sev !== 0) {
|
|
5203
5537
|
return sev;
|
|
5204
5538
|
}
|
|
5205
|
-
return a.title.localeCompare(
|
|
5539
|
+
return a.title.localeCompare(b3.title);
|
|
5206
5540
|
});
|
|
5207
5541
|
}
|
|
5208
5542
|
function generateAgentSummary(artifact) {
|
|
@@ -5265,449 +5599,2625 @@ function generateAgentSummary(artifact) {
|
|
|
5265
5599
|
`;
|
|
5266
5600
|
}
|
|
5267
5601
|
|
|
5268
|
-
// src/report/
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
}
|
|
5294
|
-
.report-header h1 { margin: 0; font-size: 1.05rem; font-weight: 800; letter-spacing: 0.04em; }
|
|
5295
|
-
.report-header p { margin: 4px 0 0; color: var(--vr-muted); font-size: 0.8rem; }
|
|
5296
|
-
.report-score {
|
|
5297
|
-
padding: 6px 14px; border-radius: 999px; border: 1px solid rgba(232, 189, 98, 0.4);
|
|
5298
|
-
color: var(--vr-gold); font-size: 0.82rem; font-weight: 800; letter-spacing: 0.04em;
|
|
5299
|
-
}
|
|
5300
|
-
.report-workspace {
|
|
5301
|
-
display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 420px);
|
|
5302
|
-
gap: 18px; padding: 22px; align-items: start;
|
|
5303
|
-
}
|
|
5304
|
-
@media (max-width: 920px) { .report-workspace { grid-template-columns: 1fr; } }
|
|
5305
|
-
|
|
5306
|
-
.studio-system-map {
|
|
5307
|
-
position: relative; min-height: 600px; display: grid;
|
|
5308
|
-
grid-template-rows: minmax(560px, 1fr); gap: 14px; padding: 14px;
|
|
5309
|
-
border-radius: 22px; border: 1px solid rgba(232, 189, 98, 0.2);
|
|
5310
|
-
background:
|
|
5311
|
-
radial-gradient(circle at 50% 48%, rgba(232, 189, 98, 0.14), transparent 18%),
|
|
5312
|
-
radial-gradient(circle at 20% 20%, rgba(91, 205, 194, 0.08), transparent 25%),
|
|
5313
|
-
radial-gradient(circle at 80% 76%, rgba(248, 113, 113, 0.06), transparent 24%),
|
|
5314
|
-
linear-gradient(160deg, rgba(16, 16, 18, 0.98), rgba(5, 6, 8, 0.99));
|
|
5315
|
-
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07), 0 18px 48px rgba(0, 0, 0, 0.42);
|
|
5316
|
-
overflow: hidden;
|
|
5317
|
-
}
|
|
5318
|
-
.studio-node-layer {
|
|
5319
|
-
position: relative; min-height: 560px; max-width: 680px;
|
|
5320
|
-
width: min(100%, 680px); justify-self: center; overflow: hidden; border-radius: 16px;
|
|
5321
|
-
}
|
|
5322
|
-
.studio-connector-layer {
|
|
5323
|
-
position: absolute; inset: 32px; pointer-events: none; opacity: 0.5;
|
|
5324
|
-
background:
|
|
5325
|
-
radial-gradient(circle at 50% 50%, transparent 0 88px, rgba(232, 189, 98, 0.26) 89px 90px, transparent 91px),
|
|
5326
|
-
linear-gradient(90deg, transparent 8%, rgba(232, 189, 98, 0.18) 49%, rgba(232, 189, 98, 0.18) 51%, transparent 92%),
|
|
5327
|
-
linear-gradient(180deg, transparent 10%, rgba(232, 189, 98, 0.14) 49%, rgba(232, 189, 98, 0.14) 51%, transparent 90%);
|
|
5328
|
-
mask-image: radial-gradient(circle at center, black, transparent 76%);
|
|
5329
|
-
}
|
|
5330
|
-
.studio-core-node {
|
|
5331
|
-
position: absolute; left: 50%; top: 50%; z-index: 3; width: 132px; height: 132px;
|
|
5332
|
-
transform: translate(-50%, -50%); display: grid; place-items: center; align-content: center;
|
|
5333
|
-
gap: 5px; border-radius: 999px; border: 1px solid rgba(232, 189, 98, 0.48);
|
|
5334
|
-
background:
|
|
5335
|
-
radial-gradient(circle at 50% 22%, rgba(255, 233, 166, 0.24), transparent 42%),
|
|
5336
|
-
linear-gradient(180deg, rgba(42, 32, 13, 0.98), rgba(10, 10, 12, 0.99));
|
|
5337
|
-
color: rgba(255, 248, 226, 0.98); text-align: center;
|
|
5338
|
-
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 18px 44px rgba(0, 0, 0, 0.48), 0 0 0 10px rgba(232, 189, 98, 0.05);
|
|
5339
|
-
}
|
|
5340
|
-
.studio-core-node span { color: var(--vr-gold); font-size: 11px; font-weight: 900; letter-spacing: 0.12em; line-height: 1; }
|
|
5341
|
-
.studio-core-node strong { font-size: 30px; font-weight: 900; line-height: 1; }
|
|
5342
|
-
.studio-core-node small { color: rgba(245, 242, 235, 0.66); font-size: 10px; font-weight: 760; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
5343
|
-
|
|
5344
|
-
.studio-node {
|
|
5345
|
-
position: absolute; z-index: 2; width: 96px; height: 96px; display: grid;
|
|
5346
|
-
grid-template-columns: 10px minmax(0, 1fr); align-items: center; align-content: center;
|
|
5347
|
-
gap: 2px 6px; padding: 12px 10px; border-radius: 999px;
|
|
5348
|
-
border: 1px solid rgba(255, 255, 255, 0.13);
|
|
5349
|
-
background: linear-gradient(160deg, rgba(22, 23, 26, 0.96), rgba(8, 9, 11, 0.99));
|
|
5350
|
-
color: var(--vr-text); font: inherit; text-align: left; cursor: pointer;
|
|
5351
|
-
transform: translate(-50%, -50%);
|
|
5352
|
-
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07), 0 12px 28px rgba(0, 0, 0, 0.34);
|
|
5353
|
-
transition: transform 170ms var(--vr-ease-out), border-color 150ms ease, box-shadow 150ms ease;
|
|
5354
|
-
}
|
|
5355
|
-
.studio-node:hover { transform: translate(-50%, calc(-50% - 2px)); border-color: rgba(232, 189, 98, 0.34); }
|
|
5356
|
-
.studio-node:focus-visible { outline: 2px solid rgba(232, 189, 98, 0.78); outline-offset: 3px; }
|
|
5357
|
-
.studio-node--selected {
|
|
5358
|
-
border-color: rgba(232, 189, 98, 0.66);
|
|
5359
|
-
background:
|
|
5360
|
-
radial-gradient(circle at 20% 10%, rgba(232, 189, 98, 0.18), transparent 42%),
|
|
5361
|
-
linear-gradient(160deg, rgba(30, 25, 16, 0.98), rgba(9, 9, 11, 0.99));
|
|
5362
|
-
box-shadow: inset 0 1px 0 rgba(255, 248, 226, 0.1), 0 0 0 1px rgba(232, 189, 98, 0.16), 0 16px 36px rgba(0, 0, 0, 0.4);
|
|
5363
|
-
}
|
|
5364
|
-
.studio-node--critical { border-color: rgba(248, 113, 113, 0.58); }
|
|
5365
|
-
.studio-node--warning { border-color: rgba(245, 158, 11, 0.5); }
|
|
5366
|
-
.studio-node--in-project {
|
|
5367
|
-
border-color: rgba(93, 240, 175, 0.66);
|
|
5368
|
-
background:
|
|
5369
|
-
radial-gradient(circle at 50% 0%, rgba(93, 240, 175, 0.18), transparent 48%),
|
|
5370
|
-
linear-gradient(160deg, rgba(7, 34, 28, 0.92), rgba(4, 10, 14, 0.98));
|
|
5371
|
-
}
|
|
5372
|
-
.studio-node__dot { grid-row: 1 / 3; width: 9px; height: 9px; border-radius: 999px; background: #d4af37; box-shadow: 0 0 0 4px rgba(212, 175, 55, 0.12); }
|
|
5373
|
-
.studio-node--critical .studio-node__dot { background: #ef6f6f; box-shadow: 0 0 0 4px rgba(239, 111, 111, 0.13); }
|
|
5374
|
-
.studio-node--warning .studio-node__dot { background: #f0b94e; box-shadow: 0 0 0 4px rgba(240, 185, 78, 0.13); }
|
|
5375
|
-
.studio-node__title, .studio-node__provider, .studio-node__meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
5376
|
-
.studio-node__title { color: rgba(255, 253, 247, 0.98); font-size: 10.5px; font-weight: 860; line-height: 1.05; }
|
|
5377
|
-
.studio-node__provider { color: rgba(245, 242, 235, 0.64); font-size: 9px; font-weight: 700; line-height: 1.05; }
|
|
5378
|
-
.studio-node__meta { grid-column: 2; color: var(--vr-gold); font-size: 9px; font-weight: 850; line-height: 1.05; }
|
|
5379
|
-
.studio-node--critical .studio-node__meta { color: #ef9a9a; }
|
|
5380
|
-
|
|
5381
|
-
.studio-node--appFlow { left: 22%; top: 18%; }
|
|
5382
|
-
.studio-node--frontend { left: 50%; top: 12%; }
|
|
5383
|
-
.studio-node--backend { left: 78%; top: 18%; }
|
|
5384
|
-
.studio-node--auth { left: 14%; top: 38%; }
|
|
5385
|
-
.studio-node--database { left: 50%; top: 32%; }
|
|
5386
|
-
.studio-node--payments { left: 86%; top: 40%; }
|
|
5387
|
-
.studio-node--deployment { left: 18%; top: 64%; }
|
|
5388
|
-
.studio-node--monitoring { left: 50%; top: 68%; }
|
|
5389
|
-
.studio-node--security { left: 82%; top: 64%; }
|
|
5390
|
-
.studio-node--testing { left: 28%; top: 84%; }
|
|
5391
|
-
.studio-node--landing { left: 50%; top: 88%; }
|
|
5392
|
-
.studio-node--errorHandling { left: 72%; top: 84%; }
|
|
5393
|
-
|
|
5394
|
-
.studio-setup-panel {
|
|
5395
|
-
border: 1px solid var(--vr-line); border-radius: 18px; background: var(--vr-panel);
|
|
5396
|
-
padding: 20px 20px 26px; position: sticky; top: 22px; max-height: calc(100vh - 44px); overflow: auto;
|
|
5397
|
-
}
|
|
5398
|
-
.studio-setup-panel__eyebrow { margin: 0; font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--vr-muted); }
|
|
5399
|
-
.studio-setup-panel__title { margin: 6px 0 0; font-size: 1.05rem; font-weight: 800; }
|
|
5400
|
-
.studio-setup-panel__hint { margin: 6px 0 0; color: var(--vr-muted); font-size: 0.82rem; line-height: 1.5; }
|
|
5401
|
-
.panel-section { margin-top: 18px; }
|
|
5402
|
-
.panel-section h3 { margin: 0 0 10px; font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--vr-muted); }
|
|
5403
|
-
.provider-switch { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
5404
|
-
.provider-chip {
|
|
5405
|
-
border: 1px solid var(--vr-line); background: rgba(255,255,255,0.02); color: var(--vr-text);
|
|
5406
|
-
border-radius: 999px; padding: 5px 11px; font-size: 0.76rem; cursor: pointer; transition: border-color 0.15s;
|
|
5407
|
-
}
|
|
5408
|
-
.provider-chip:hover { border-color: rgba(232, 189, 98, 0.45); }
|
|
5409
|
-
.provider-chip--active { border-color: rgba(93, 240, 175, 0.66); color: rgba(111, 255, 190, 0.96); background: rgba(93, 240, 175, 0.08); }
|
|
5410
|
-
.switch-hint { margin-top: 8px; font-size: 0.75rem; color: var(--vr-muted); }
|
|
5411
|
-
.switch-hint code { color: var(--vr-gold); }
|
|
5412
|
-
.check, .gap-card { border: 1px solid var(--vr-line); border-radius: 12px; padding: 10px 12px; margin-bottom: 8px; background: rgba(0,0,0,0.22); }
|
|
5413
|
-
.check strong, .gap-card strong { display: block; font-size: 0.86rem; }
|
|
5414
|
-
.check .status, .gap-card p { margin: 4px 0 0; font-size: 0.78rem; color: var(--vr-muted); line-height: 1.45; }
|
|
5415
|
-
.status-passed { color: rgba(111, 255, 190, 0.96); }
|
|
5416
|
-
.status-missing, .status-failed { color: #ef9a9a; }
|
|
5417
|
-
.status-needs-connection { color: #f0b94e; }
|
|
5418
|
-
.copy-btn { margin-top: 8px; border: 1px solid var(--vr-line); background: transparent; color: var(--vr-text); border-radius: 8px; padding: 6px 10px; font-size: 0.76rem; cursor: pointer; }
|
|
5419
|
-
.copy-btn:hover { border-color: rgba(232, 189, 98, 0.45); }
|
|
5420
|
-
`;
|
|
5421
|
-
|
|
5422
|
-
// src/report/reportHtml.ts
|
|
5423
|
-
function escapeHtml(value) {
|
|
5424
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5425
|
-
}
|
|
5426
|
-
var CATEGORY_META = [
|
|
5427
|
-
{ key: "appFlow", label: "App Flow" },
|
|
5428
|
-
{ key: "frontend", label: "Frontend" },
|
|
5429
|
-
{ key: "backend", label: "Backend / API" },
|
|
5430
|
-
{ key: "auth", label: "Auth" },
|
|
5431
|
-
{ key: "database", label: "Database" },
|
|
5432
|
-
{ key: "payments", label: "Payments" },
|
|
5433
|
-
{ key: "deployment", label: "Deployment" },
|
|
5434
|
-
{ key: "monitoring", label: "Monitoring" },
|
|
5435
|
-
{ key: "security", label: "Security" },
|
|
5436
|
-
{ key: "testing", label: "Testing" },
|
|
5437
|
-
{ key: "landing", label: "Landing / Onboarding" },
|
|
5438
|
-
{ key: "errorHandling", label: "Error Handling" }
|
|
5439
|
-
];
|
|
5440
|
-
function gapCountForArea(artifact, areaKey) {
|
|
5441
|
-
return artifact.gaps.filter((g) => g.primaryMapCategory === areaKey).length;
|
|
5602
|
+
// src/report/missionSelection.ts
|
|
5603
|
+
function normalizeProviderToken2(value) {
|
|
5604
|
+
return value.toLowerCase().replace(/&/g, "and").replace(/[^a-z0-9]+/g, "");
|
|
5605
|
+
}
|
|
5606
|
+
function missionMatchesProvider(mission, provider2) {
|
|
5607
|
+
const current = normalizeProviderToken2(provider2);
|
|
5608
|
+
return normalizeProviderToken2(mission.provider || mission.providerLabel || mission.key) === current || normalizeProviderToken2(mission.providerLabel || mission.provider || mission.key) === current;
|
|
5609
|
+
}
|
|
5610
|
+
function missionEvidenceScore(mission) {
|
|
5611
|
+
const repoVerified = mission.checks.filter((check) => check.evidenceClass === "repo-verified" || check.status === "passed").length;
|
|
5612
|
+
const missing = mission.checks.filter(
|
|
5613
|
+
(check) => check.evidenceClass === "missing-repo-fix" || check.status === "missing" || check.status === "failed"
|
|
5614
|
+
).length;
|
|
5615
|
+
return repoVerified * 100 + (mission.readinessPercent ?? 0) - missing;
|
|
5616
|
+
}
|
|
5617
|
+
function preferredMissionForArea(area, selectedProvider) {
|
|
5618
|
+
const missions = area?.providerMissions ?? [];
|
|
5619
|
+
if (missions.length === 0) {
|
|
5620
|
+
return void 0;
|
|
5621
|
+
}
|
|
5622
|
+
const selected = selectedProvider ? missions.find((mission) => missionMatchesProvider(mission, selectedProvider)) : void 0;
|
|
5623
|
+
if (selected) {
|
|
5624
|
+
return selected;
|
|
5625
|
+
}
|
|
5626
|
+
return [...missions].sort((a, b3) => missionEvidenceScore(b3) - missionEvidenceScore(a))[0] ?? missions[0];
|
|
5442
5627
|
}
|
|
5443
|
-
function
|
|
5444
|
-
if (!
|
|
5628
|
+
function openChecksForMission(mission) {
|
|
5629
|
+
if (!mission) {
|
|
5445
5630
|
return 0;
|
|
5446
5631
|
}
|
|
5447
|
-
return
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
const area = (artifact.missionGraph.areas ?? []).find((a) => a.key === meta.key);
|
|
5451
|
-
const mission = area?.providerMissions[0];
|
|
5452
|
-
const provider2 = mission?.providerLabel ?? "\u2014";
|
|
5453
|
-
const readiness = mission?.readinessPercent ?? area?.readinessPercent ?? 0;
|
|
5454
|
-
const openChecks = openChecksForArea(area);
|
|
5455
|
-
const modelGaps = gapCountForArea(artifact, meta.key);
|
|
5456
|
-
const stateClass = modelGaps > 0 ? " studio-node--critical" : openChecks > 0 ? " studio-node--warning" : area ? " studio-node--in-project" : "";
|
|
5457
|
-
const meta1 = modelGaps > 0 ? `GAP ${modelGaps}` : openChecks > 0 ? `${openChecks} stack fix${openChecks === 1 ? "" : "es"}` : `${readiness}% health`;
|
|
5458
|
-
return `<button type="button" class="studio-node studio-node--${escapeHtml(meta.key)}${stateClass}" data-area-key="${escapeHtml(meta.key)}" aria-label="${escapeHtml(meta.label)}">
|
|
5459
|
-
<span class="studio-node__dot" aria-hidden="true"></span>
|
|
5460
|
-
<span class="studio-node__title">${escapeHtml(meta.label)}</span>
|
|
5461
|
-
<span class="studio-node__provider">${escapeHtml(provider2)}</span>
|
|
5462
|
-
<span class="studio-node__meta">${escapeHtml(meta1)}</span>
|
|
5463
|
-
</button>`;
|
|
5464
|
-
}
|
|
5465
|
-
function generateReportHtml(artifact) {
|
|
5466
|
-
const dataJson = JSON.stringify(artifact).replace(/</g, "\\u003c");
|
|
5467
|
-
const nodesHtml = CATEGORY_META.map((meta) => buildNodeHtml(artifact, meta)).join("\n");
|
|
5468
|
-
return `<!DOCTYPE html>
|
|
5469
|
-
<html lang="en">
|
|
5470
|
-
<head>
|
|
5471
|
-
<meta charset="utf-8" />
|
|
5472
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5473
|
-
<title>VibeRaven Launch Report</title>
|
|
5474
|
-
<style>${REPORT_STYLES}</style>
|
|
5475
|
-
</head>
|
|
5476
|
-
<body>
|
|
5477
|
-
<header class="report-header">
|
|
5478
|
-
<div>
|
|
5479
|
-
<h1>VIBERAVEN \xB7 LAUNCH REPORT</h1>
|
|
5480
|
-
<p>${escapeHtml(artifact.workspacePath)} \xB7 ${escapeHtml(artifact.scannedAt)}</p>
|
|
5481
|
-
</div>
|
|
5482
|
-
<div class="report-score">Model ${artifact.score} \xB7 ${escapeHtml(artifact.scoreLabel)}</div>
|
|
5483
|
-
</header>
|
|
5484
|
-
<div class="report-workspace">
|
|
5485
|
-
<section class="studio-system-map" aria-label="Production system map">
|
|
5486
|
-
<div class="studio-node-layer">
|
|
5487
|
-
<div class="studio-connector-layer" aria-hidden="true"></div>
|
|
5488
|
-
<div class="studio-core-node" aria-label="Production core">
|
|
5489
|
-
<span>VIBERAVEN</span>
|
|
5490
|
-
<strong>${artifact.productionCorePercent}%</strong>
|
|
5491
|
-
<small>Production core</small>
|
|
5492
|
-
</div>
|
|
5493
|
-
${nodesHtml}
|
|
5494
|
-
</div>
|
|
5495
|
-
</section>
|
|
5496
|
-
<aside class="studio-setup-panel" id="detail-panel" aria-live="polite">
|
|
5497
|
-
<p class="studio-setup-panel__eyebrow">No section selected</p>
|
|
5498
|
-
<p class="studio-setup-panel__title">Choose a node on the map</p>
|
|
5499
|
-
<p class="studio-setup-panel__hint">${escapeHtml(artifact.summary || "Click any provider node to see stack checks, launch gaps, and switch providers.")}</p>
|
|
5500
|
-
</aside>
|
|
5501
|
-
</div>
|
|
5502
|
-
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
5503
|
-
<script>${CLIENT_SCRIPT}</script>
|
|
5504
|
-
</body>
|
|
5505
|
-
</html>`;
|
|
5632
|
+
return mission.checks.filter(
|
|
5633
|
+
(check) => check.status === "missing" || check.status === "failed" || check.status === "needs-connection"
|
|
5634
|
+
).length;
|
|
5506
5635
|
}
|
|
5507
|
-
|
|
5636
|
+
|
|
5637
|
+
// src/report/panelClientScript.ts
|
|
5638
|
+
var PANEL_CLIENT_SCRIPT = `
|
|
5639
|
+
|
|
5508
5640
|
(function () {
|
|
5641
|
+
|
|
5509
5642
|
var artifact = JSON.parse(document.getElementById('scan-data').textContent);
|
|
5643
|
+
|
|
5644
|
+
var defaultAreaKey = JSON.parse(document.getElementById('default-area-key').textContent);
|
|
5645
|
+
|
|
5646
|
+
var logoPayload = JSON.parse(document.getElementById('provider-logos').textContent);
|
|
5647
|
+
|
|
5510
5648
|
var panel = document.getElementById('detail-panel');
|
|
5649
|
+
|
|
5511
5650
|
var areas = (artifact.missionGraph && artifact.missionGraph.areas) || [];
|
|
5651
|
+
|
|
5512
5652
|
var gaps = artifact.gaps || [];
|
|
5653
|
+
|
|
5513
5654
|
var providerOptions = artifact.providerOptions || {};
|
|
5514
|
-
|
|
5655
|
+
|
|
5656
|
+
var selectedProviders = Object.assign({}, artifact.selectedProviders || {});
|
|
5657
|
+
|
|
5658
|
+
var projectProviders = Object.assign({}, artifact.selectedProviders || {});
|
|
5659
|
+
|
|
5660
|
+
var stackAutomation = artifact.stackAutomation || {};
|
|
5661
|
+
|
|
5662
|
+
var STACK_AREAS = { database: 1, auth: 1, payments: 1, deployment: 1, monitoring: 1, security: 1 };
|
|
5663
|
+
|
|
5664
|
+
var CHOICE_HINTS = {
|
|
5665
|
+
|
|
5666
|
+
appFlow: 'Choose flow focus',
|
|
5667
|
+
|
|
5668
|
+
frontend: 'Choose frontend focus',
|
|
5669
|
+
|
|
5670
|
+
backend: 'Choose backend focus',
|
|
5671
|
+
|
|
5672
|
+
auth: 'Choose auth stack',
|
|
5673
|
+
|
|
5674
|
+
database: 'Choose database stack',
|
|
5675
|
+
|
|
5676
|
+
payments: 'Choose payment stack',
|
|
5677
|
+
|
|
5678
|
+
deployment: 'Choose deployment stack',
|
|
5679
|
+
|
|
5680
|
+
monitoring: 'Choose monitoring stack',
|
|
5681
|
+
|
|
5682
|
+
security: 'Choose security control',
|
|
5683
|
+
|
|
5684
|
+
testing: 'Choose coverage target',
|
|
5685
|
+
|
|
5686
|
+
landing: 'Choose launch item',
|
|
5687
|
+
|
|
5688
|
+
errorHandling: 'Choose reliability control'
|
|
5689
|
+
|
|
5690
|
+
};
|
|
5691
|
+
|
|
5515
5692
|
var LABELS = {
|
|
5693
|
+
|
|
5516
5694
|
appFlow: 'App Flow', frontend: 'Frontend', backend: 'Backend / API', auth: 'Auth',
|
|
5517
|
-
|
|
5695
|
+
|
|
5696
|
+
database: 'Database', payments: 'Payments', deployment: 'Deployment', monitoring: 'Monitoring / Analytics',
|
|
5697
|
+
|
|
5518
5698
|
security: 'Security', testing: 'Testing', landing: 'Landing / Onboarding', errorHandling: 'Error Handling'
|
|
5699
|
+
|
|
5519
5700
|
};
|
|
5520
5701
|
|
|
5702
|
+
var MAX_GROUP_ITEMS = 6;
|
|
5703
|
+
|
|
5704
|
+
|
|
5705
|
+
|
|
5521
5706
|
function esc(s) {
|
|
5707
|
+
|
|
5522
5708
|
return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
5523
|
-
}
|
|
5524
|
-
function statusClass(status) {
|
|
5525
|
-
if (status === 'passed') return 'status-passed';
|
|
5526
|
-
if (status === 'needs-connection') return 'status-needs-connection';
|
|
5527
|
-
return 'status-missing';
|
|
5528
|
-
}
|
|
5529
5709
|
|
|
5530
|
-
function providerSwitchHtml(areaKey, mission) {
|
|
5531
|
-
var options = providerOptions[areaKey];
|
|
5532
|
-
if (!options || !options.length) return '';
|
|
5533
|
-
var current = selectedProviders[areaKey] || (mission ? (mission.provider || '') : '');
|
|
5534
|
-
var chips = options.map(function (opt) {
|
|
5535
|
-
var active = String(opt.provider).toLowerCase() === String(current).toLowerCase();
|
|
5536
|
-
return '<button type="button" class="provider-chip' + (active ? ' provider-chip--active' : '') +
|
|
5537
|
-
'" data-switch-area="' + esc(areaKey) + '" data-switch-provider="' + esc(opt.provider) + '">' + esc(opt.label) + '</button>';
|
|
5538
|
-
}).join('');
|
|
5539
|
-
return '<div class="panel-section"><h3>Switch provider</h3><div class="provider-switch">' + chips +
|
|
5540
|
-
'</div><p class="switch-hint" id="switch-hint">Pick a provider, then rescan to re-map this area.</p></div>';
|
|
5541
5710
|
}
|
|
5542
5711
|
|
|
5543
|
-
function render(areaKey) {
|
|
5544
|
-
var area = areas.find(function (a) { return a.key === areaKey; });
|
|
5545
|
-
document.querySelectorAll('.studio-node').forEach(function (n) {
|
|
5546
|
-
n.classList.toggle('studio-node--selected', n.getAttribute('data-area-key') === areaKey);
|
|
5547
|
-
});
|
|
5548
|
-
var label = (area && area.label) || LABELS[areaKey] || areaKey;
|
|
5549
|
-
var missions = (area && area.providerMissions) || [];
|
|
5550
|
-
var mission = missions[0];
|
|
5551
|
-
var areaGaps = gaps.filter(function (g) { return g.primaryMapCategory === areaKey; });
|
|
5552
|
-
|
|
5553
|
-
var checksHtml = missions.flatMap(function (m) {
|
|
5554
|
-
return (m.checks || []).map(function (c) {
|
|
5555
|
-
var ev = (c.evidence && c.evidence[0]) ? c.evidence[0] : (c.promptHint || '');
|
|
5556
|
-
return '<div class="check"><strong>' + esc(c.label) + '</strong>' +
|
|
5557
|
-
'<div class="status ' + statusClass(c.status) + '">' + esc(c.status) + '</div>' +
|
|
5558
|
-
(ev ? '<div class="status">' + esc(ev) + '</div>' : '') + '</div>';
|
|
5559
|
-
});
|
|
5560
|
-
}).join('');
|
|
5561
|
-
|
|
5562
|
-
var gapsHtml = areaGaps.map(function (g) {
|
|
5563
|
-
return '<div class="gap-card"><strong>' + esc(g.title) + '</strong><p>' + esc(g.detail) +
|
|
5564
|
-
'</p><button type="button" class="copy-btn" data-copy-gap="' + esc(g.id) + '">Copy agent prompt</button></div>';
|
|
5565
|
-
}).join('');
|
|
5566
5712
|
|
|
5567
|
-
var head =
|
|
5568
|
-
'<p class="studio-setup-panel__eyebrow">' + esc(areaKey) + '</p>' +
|
|
5569
|
-
'<p class="studio-setup-panel__title">' + esc(label) + '</p>' +
|
|
5570
|
-
'<p class="studio-setup-panel__hint">' +
|
|
5571
|
-
(missions.map(function (m) { return esc(m.providerLabel) + ' \xB7 ' + m.readinessPercent + '% repo readiness'; }).join(' \xB7 ') || 'No provider detected for this area.') +
|
|
5572
|
-
'</p>';
|
|
5573
5713
|
|
|
5574
|
-
|
|
5575
|
-
providerSwitchHtml(areaKey, mission) +
|
|
5576
|
-
'<div class="panel-section"><h3>Stack checks</h3>' + (checksHtml || '<p class="switch-hint">No checks for this area.</p>') + '</div>' +
|
|
5577
|
-
'<div class="panel-section"><h3>Launch gaps</h3>' + (gapsHtml || '<p class="switch-hint">No model gaps tagged here.</p>') + '</div>';
|
|
5714
|
+
function attrEsc(s) {
|
|
5578
5715
|
|
|
5579
|
-
|
|
5580
|
-
btn.addEventListener('click', function () {
|
|
5581
|
-
var gap = gaps.find(function (g) { return g.id === btn.getAttribute('data-copy-gap'); });
|
|
5582
|
-
if (!gap) return;
|
|
5583
|
-
navigator.clipboard.writeText(gap.copyPrompt).then(function () {
|
|
5584
|
-
btn.textContent = 'Copied';
|
|
5585
|
-
setTimeout(function () { btn.textContent = 'Copy agent prompt'; }, 1200);
|
|
5586
|
-
});
|
|
5587
|
-
});
|
|
5588
|
-
});
|
|
5716
|
+
return esc(s).replace(/"/g, '"').replace(/'/g, ''');
|
|
5589
5717
|
|
|
5590
|
-
panel.querySelectorAll('[data-switch-provider]').forEach(function (btn) {
|
|
5591
|
-
btn.addEventListener('click', function () {
|
|
5592
|
-
var a = btn.getAttribute('data-switch-area');
|
|
5593
|
-
var p = btn.getAttribute('data-switch-provider');
|
|
5594
|
-
var cmd = 'viberaven stack set ' + a + ' ' + p + ' && viberaven scan --open';
|
|
5595
|
-
navigator.clipboard.writeText(cmd);
|
|
5596
|
-
var hint = document.getElementById('switch-hint');
|
|
5597
|
-
if (hint) hint.innerHTML = 'Copied: <code>' + esc(cmd) + '</code> \u2014 run it (or ask your agent) to re-map this area.';
|
|
5598
|
-
panel.querySelectorAll('[data-switch-area="' + a + '"]').forEach(function (c) {
|
|
5599
|
-
c.classList.toggle('provider-chip--active', c === btn);
|
|
5600
|
-
});
|
|
5601
|
-
});
|
|
5602
|
-
});
|
|
5603
5718
|
}
|
|
5604
5719
|
|
|
5605
|
-
document.querySelectorAll('.studio-node').forEach(function (n) {
|
|
5606
|
-
n.addEventListener('click', function () { render(n.getAttribute('data-area-key')); });
|
|
5607
|
-
});
|
|
5608
|
-
if (areas[0]) render(areas[0].key);
|
|
5609
|
-
})();
|
|
5610
|
-
`;
|
|
5611
5720
|
|
|
5612
|
-
// src/artifacts.ts
|
|
5613
|
-
async function writeScanArtifacts(options) {
|
|
5614
|
-
const cwd = options.cwd ?? options.artifact.workspacePath;
|
|
5615
|
-
const dir = getProjectArtifactsDir(cwd);
|
|
5616
|
-
await (0, import_promises3.mkdir)(dir, { recursive: true });
|
|
5617
|
-
const jsonPath = (0, import_node_path4.join)(dir, "last-scan.json");
|
|
5618
|
-
const summaryPath = (0, import_node_path4.join)(dir, "agent-summary.md");
|
|
5619
|
-
const reportPath = (0, import_node_path4.join)(dir, "report.html");
|
|
5620
|
-
const json = `${JSON.stringify(options.artifact, null, 2)}
|
|
5621
|
-
`;
|
|
5622
|
-
const summary = generateAgentSummary(options.artifact);
|
|
5623
|
-
const html = generateReportHtml(options.artifact);
|
|
5624
|
-
await (0, import_promises3.writeFile)(jsonPath, json, "utf-8");
|
|
5625
|
-
await (0, import_promises3.writeFile)(summaryPath, summary, "utf-8");
|
|
5626
|
-
await (0, import_promises3.writeFile)(reportPath, html, "utf-8");
|
|
5627
|
-
return { dir, jsonPath, summaryPath, reportPath };
|
|
5628
|
-
}
|
|
5629
5721
|
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5722
|
+
function normKey(p) {
|
|
5723
|
+
|
|
5724
|
+
if (!p) return '';
|
|
5725
|
+
|
|
5726
|
+
var raw = String(p).trim().toLowerCase();
|
|
5727
|
+
|
|
5728
|
+
var compact = raw.replace(/[^a-z0-9]+/g, '');
|
|
5729
|
+
|
|
5730
|
+
if (logoPayload.aliases[raw]) return logoPayload.aliases[raw];
|
|
5731
|
+
|
|
5732
|
+
if (logoPayload.aliases[compact]) return logoPayload.aliases[compact];
|
|
5733
|
+
|
|
5734
|
+
if (logoPayload.logos[compact]) return compact;
|
|
5735
|
+
|
|
5736
|
+
var parts = raw.split('-').filter(Boolean);
|
|
5737
|
+
|
|
5738
|
+
if (parts.length >= 2) {
|
|
5739
|
+
|
|
5740
|
+
if (logoPayload.logos[parts[0]] || logoPayload.aliases[parts[0]]) return logoPayload.aliases[parts[0]] || parts[0];
|
|
5741
|
+
|
|
5742
|
+
var two = parts.slice(0, 2).join('');
|
|
5743
|
+
|
|
5744
|
+
if (logoPayload.logos[two] || logoPayload.aliases[two]) return logoPayload.aliases[two] || two;
|
|
5745
|
+
|
|
5746
|
+
var twoHyphen = parts.slice(0, 2).join('-');
|
|
5747
|
+
|
|
5748
|
+
if (logoPayload.aliases[twoHyphen]) return logoPayload.aliases[twoHyphen];
|
|
5749
|
+
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5752
|
+
return compact;
|
|
5753
|
+
|
|
5754
|
+
}
|
|
5755
|
+
|
|
5756
|
+
|
|
5757
|
+
|
|
5758
|
+
function logoClass(p) {
|
|
5759
|
+
|
|
5760
|
+
var key = normKey(p);
|
|
5761
|
+
|
|
5762
|
+
if (!key) return '';
|
|
5763
|
+
|
|
5764
|
+
var cls = ' provider-logo--' + key;
|
|
5765
|
+
|
|
5766
|
+
if (logoPayload.brandKeys && logoPayload.brandKeys.indexOf(key) >= 0) {
|
|
5767
|
+
|
|
5768
|
+
cls += ' provider-logo--brand';
|
|
5769
|
+
|
|
5770
|
+
}
|
|
5771
|
+
|
|
5772
|
+
return cls;
|
|
5773
|
+
|
|
5774
|
+
}
|
|
5775
|
+
|
|
5776
|
+
|
|
5777
|
+
|
|
5778
|
+
function logoHtml(p, label) {
|
|
5779
|
+
|
|
5780
|
+
var key = normKey(p);
|
|
5781
|
+
|
|
5782
|
+
if (key && logoPayload.inlineOnly && logoPayload.inlineOnly.indexOf(key) >= 0 && logoPayload.logos[key]) {
|
|
5783
|
+
|
|
5784
|
+
return logoPayload.logos[key];
|
|
5785
|
+
|
|
5786
|
+
}
|
|
5787
|
+
|
|
5788
|
+
if (key && logoPayload.assetUrls && logoPayload.assetUrls[key]) {
|
|
5789
|
+
|
|
5790
|
+
return '<img class="provider-logo__img" src="' + esc(logoPayload.assetUrls[key]) + '" alt="" decoding="async" data-provider-logo-key="' + esc(key) + '" />';
|
|
5791
|
+
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5794
|
+
if (key && logoPayload.iconUrls && logoPayload.iconUrls[key]) {
|
|
5795
|
+
|
|
5796
|
+
return '<img class="provider-logo__img" src="' + esc(logoPayload.iconUrls[key]) + '" alt="" decoding="async" data-provider-logo-key="' + esc(key) + '" />';
|
|
5797
|
+
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
if (key && logoPayload.logos[key]) return logoPayload.logos[key];
|
|
5801
|
+
|
|
5802
|
+
var t = (label || p || '?').trim();
|
|
5803
|
+
|
|
5804
|
+
return '<span aria-hidden="true">' + esc(t.slice(0, 2).toUpperCase()) + '</span>';
|
|
5805
|
+
|
|
5806
|
+
}
|
|
5807
|
+
|
|
5808
|
+
|
|
5809
|
+
|
|
5810
|
+
function benefitText(p, desc) {
|
|
5811
|
+
|
|
5812
|
+
var key = normKey(p);
|
|
5813
|
+
|
|
5814
|
+
return (key && logoPayload.benefits[key]) || desc || 'Useful path for this section.';
|
|
5815
|
+
|
|
5816
|
+
}
|
|
5817
|
+
|
|
5818
|
+
|
|
5819
|
+
|
|
5820
|
+
function panelTitle(areaKey, label) {
|
|
5821
|
+
|
|
5822
|
+
return esc(String(label || LABELS[areaKey] || areaKey).toUpperCase()) +
|
|
5823
|
+
|
|
5824
|
+
(STACK_AREAS[areaKey] ? ' STACK' : ' CONTROLS');
|
|
5825
|
+
|
|
5826
|
+
}
|
|
5827
|
+
|
|
5828
|
+
|
|
5829
|
+
|
|
5830
|
+
function evidenceBadgeHtml(missions) {
|
|
5831
|
+
|
|
5832
|
+
var open = missions.flatMap(function (m) {
|
|
5833
|
+
|
|
5834
|
+
return (m.checks || []).filter(function (c) {
|
|
5835
|
+
|
|
5836
|
+
return c.status === 'missing' || c.status === 'failed' || c.status === 'needs-connection';
|
|
5837
|
+
|
|
5838
|
+
});
|
|
5839
|
+
|
|
5840
|
+
});
|
|
5841
|
+
|
|
5842
|
+
var tone = open.length > 0 ? 'missing' : 'repo';
|
|
5843
|
+
|
|
5844
|
+
var label = open.length > 0 ? 'Missing repo fixes' : 'Repo evidence found';
|
|
5845
|
+
|
|
5846
|
+
return '<span class="studio-evidence-badge studio-evidence-badge--' + tone + '">' + esc(label) + '</span>';
|
|
5847
|
+
|
|
5848
|
+
}
|
|
5849
|
+
|
|
5850
|
+
|
|
5851
|
+
|
|
5852
|
+
function automationFor(areaKey, mission, currentProvider) {
|
|
5853
|
+
|
|
5854
|
+
var byKey = stackAutomation.byKey || {};
|
|
5855
|
+
|
|
5856
|
+
if (mission && mission.key && byKey[mission.key]) return byKey[mission.key];
|
|
5857
|
+
|
|
5858
|
+
var items = stackAutomation.items || [];
|
|
5859
|
+
|
|
5860
|
+
var current = normKey(currentProvider || (mission && (mission.provider || mission.providerLabel)));
|
|
5861
|
+
|
|
5862
|
+
return items.find(function (item) {
|
|
5863
|
+
|
|
5864
|
+
return item.area === areaKey && (!current || normKey(item.provider || item.providerLabel) === current);
|
|
5865
|
+
|
|
5866
|
+
}) || null;
|
|
5867
|
+
|
|
5868
|
+
}
|
|
5869
|
+
|
|
5870
|
+
|
|
5871
|
+
|
|
5872
|
+
function optionFor(areaKey, currentProvider) {
|
|
5873
|
+
|
|
5874
|
+
var options = providerOptions[areaKey] || [];
|
|
5875
|
+
|
|
5876
|
+
var current = normKey(currentProvider);
|
|
5877
|
+
|
|
5878
|
+
return options.find(function (opt) {
|
|
5879
|
+
|
|
5880
|
+
var p = opt.provider || opt.label;
|
|
5881
|
+
|
|
5882
|
+
return p === currentProvider || normKey(p) === current;
|
|
5883
|
+
|
|
5884
|
+
}) || null;
|
|
5885
|
+
|
|
5886
|
+
}
|
|
5887
|
+
|
|
5888
|
+
|
|
5889
|
+
|
|
5890
|
+
function sameProvider(a, b) {
|
|
5891
|
+
|
|
5892
|
+
if (!a || !b) return false;
|
|
5893
|
+
|
|
5894
|
+
return String(a).toLowerCase() === String(b).toLowerCase() || normKey(a) === normKey(b);
|
|
5895
|
+
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
|
|
5899
|
+
|
|
5900
|
+
function projectProviderFor(areaKey, missions) {
|
|
5901
|
+
|
|
5902
|
+
if (projectProviders[areaKey]) return projectProviders[areaKey];
|
|
5903
|
+
|
|
5904
|
+
var mission = preferredMission(missions, '');
|
|
5905
|
+
|
|
5906
|
+
return (mission && (mission.provider || mission.providerLabel)) || '';
|
|
5907
|
+
|
|
5908
|
+
}
|
|
5909
|
+
|
|
5910
|
+
|
|
5911
|
+
|
|
5912
|
+
function missionMatchesProvider(mission, currentProvider) {
|
|
5913
|
+
|
|
5914
|
+
if (!mission || !currentProvider) return false;
|
|
5915
|
+
|
|
5916
|
+
var current = normKey(currentProvider);
|
|
5917
|
+
|
|
5918
|
+
return normKey(mission.provider || mission.providerLabel || mission.key) === current ||
|
|
5919
|
+
|
|
5920
|
+
normKey(mission.providerLabel || mission.provider || mission.key) === current;
|
|
5921
|
+
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
|
|
5925
|
+
|
|
5926
|
+
function missionForProvider(missions, currentProvider) {
|
|
5927
|
+
|
|
5928
|
+
if (!missions || !missions.length) return null;
|
|
5929
|
+
|
|
5930
|
+
if (!currentProvider) return missions[0];
|
|
5931
|
+
|
|
5932
|
+
return missions.find(function (mission) {
|
|
5933
|
+
|
|
5934
|
+
return missionMatchesProvider(mission, currentProvider);
|
|
5935
|
+
|
|
5936
|
+
}) || null;
|
|
5937
|
+
|
|
5938
|
+
}
|
|
5939
|
+
|
|
5940
|
+
function missionEvidenceScore(mission) {
|
|
5941
|
+
|
|
5942
|
+
if (!mission || !mission.checks) return 0;
|
|
5943
|
+
|
|
5944
|
+
var repoVerified = mission.checks.filter(function (check) {
|
|
5945
|
+
|
|
5946
|
+
return check.evidenceClass === 'repo-verified' || check.status === 'passed';
|
|
5947
|
+
|
|
5948
|
+
}).length;
|
|
5949
|
+
|
|
5950
|
+
var missing = mission.checks.filter(function (check) {
|
|
5951
|
+
|
|
5952
|
+
return check.evidenceClass === 'missing-repo-fix' || check.status === 'missing' || check.status === 'failed';
|
|
5953
|
+
|
|
5954
|
+
}).length;
|
|
5955
|
+
|
|
5956
|
+
return repoVerified * 100 + (mission.readinessPercent || 0) - missing;
|
|
5957
|
+
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5960
|
+
|
|
5961
|
+
|
|
5962
|
+
function preferredMission(missions, selectedProvider) {
|
|
5963
|
+
|
|
5964
|
+
if (!missions || !missions.length) return null;
|
|
5965
|
+
|
|
5966
|
+
var selected = selectedProvider ? missionForProvider(missions, selectedProvider) : null;
|
|
5967
|
+
|
|
5968
|
+
if (selected) return selected;
|
|
5969
|
+
|
|
5970
|
+
return missions.slice().sort(function (a, b) {
|
|
5971
|
+
|
|
5972
|
+
return missionEvidenceScore(b) - missionEvidenceScore(a);
|
|
5973
|
+
|
|
5974
|
+
})[0] || missions[0];
|
|
5975
|
+
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
|
|
5979
|
+
|
|
5980
|
+
function providerLabelFor(areaKey, currentProvider, mission, areaLabel) {
|
|
5981
|
+
|
|
5982
|
+
var opt = optionFor(areaKey, currentProvider);
|
|
5983
|
+
|
|
5984
|
+
return (opt && opt.label) || (mission && mission.providerLabel) || currentProvider || areaLabel;
|
|
5985
|
+
|
|
5986
|
+
}
|
|
5987
|
+
|
|
5988
|
+
|
|
5989
|
+
|
|
5990
|
+
function itemLines(items, fallback, includeEvidence) {
|
|
5991
|
+
|
|
5992
|
+
if (!items || !items.length) return fallback;
|
|
5993
|
+
|
|
5994
|
+
return items.map(function (item) {
|
|
5995
|
+
|
|
5996
|
+
var evidence = includeEvidence && item.evidence && item.evidence.length
|
|
5997
|
+
|
|
5998
|
+
? ' (' + item.evidence.slice(0, 3).join('; ') + ')'
|
|
5999
|
+
|
|
6000
|
+
: '';
|
|
6001
|
+
|
|
6002
|
+
return '- ' + item.label + evidence + (item.promptHint && !includeEvidence ? ': ' + item.promptHint : '');
|
|
6003
|
+
|
|
6004
|
+
}).join('\\n');
|
|
6005
|
+
|
|
6006
|
+
}
|
|
6007
|
+
|
|
6008
|
+
|
|
6009
|
+
|
|
6010
|
+
function stackPromptFromMission(mission, providerLabel) {
|
|
6011
|
+
|
|
6012
|
+
if (!mission || !(mission.checks && mission.checks.length)) return '';
|
|
6013
|
+
|
|
6014
|
+
var subject = mission.promptSubject || providerLabel || mission.providerLabel || 'this stack';
|
|
6015
|
+
|
|
6016
|
+
var passed = [];
|
|
6017
|
+
|
|
6018
|
+
var missing = [];
|
|
6019
|
+
|
|
6020
|
+
var manual = [];
|
|
6021
|
+
|
|
6022
|
+
mission.checks.forEach(function (check) {
|
|
6023
|
+
|
|
6024
|
+
var item = { label: check.label, promptHint: check.promptHint, evidence: check.evidence || [] };
|
|
6025
|
+
|
|
6026
|
+
if (check.evidenceClass === 'repo-verified' || check.status === 'passed') passed.push(item);
|
|
6027
|
+
|
|
6028
|
+
else if (check.evidenceClass === 'manual-dashboard') manual.push(item);
|
|
6029
|
+
|
|
6030
|
+
else if (check.evidenceClass === 'mcp-verifier') manual.push(item);
|
|
6031
|
+
|
|
6032
|
+
else if (check.evidenceClass === 'missing-repo-fix' || check.status === 'missing' || check.status === 'failed') missing.push(item);
|
|
6033
|
+
|
|
6034
|
+
});
|
|
6035
|
+
|
|
6036
|
+
var total = passed.length + missing.length;
|
|
6037
|
+
|
|
6038
|
+
return [
|
|
6039
|
+
|
|
6040
|
+
'Wire ' + subject + ' for this app safely.',
|
|
6041
|
+
|
|
6042
|
+
'',
|
|
6043
|
+
|
|
6044
|
+
'Current ' + subject + ' readiness: ' + passed.length + '/' + Math.max(total, 1) + ' repo checks passed (' + (mission.readinessPercent || 0) + '%).',
|
|
6045
|
+
|
|
6046
|
+
'',
|
|
6047
|
+
|
|
6048
|
+
'Repo evidence already found:',
|
|
6049
|
+
|
|
6050
|
+
itemLines(passed, '- No ' + subject + ' checks passed yet.', true),
|
|
6051
|
+
|
|
6052
|
+
'',
|
|
6053
|
+
|
|
6054
|
+
'Missing ' + subject + ' checks:',
|
|
6055
|
+
|
|
6056
|
+
itemLines(missing, '- No missing ' + subject + ' checks were found by VibeRaven.', false),
|
|
6057
|
+
|
|
6058
|
+
'',
|
|
6059
|
+
|
|
6060
|
+
'Manual checks that repo evidence cannot prove:',
|
|
6061
|
+
|
|
6062
|
+
itemLines(manual, '- No manual dashboard checks were listed.', false),
|
|
6063
|
+
|
|
6064
|
+
'',
|
|
6065
|
+
|
|
6066
|
+
'First inspect the existing package.json files, env examples, framework routes, provider helpers, and server/client boundaries before editing.',
|
|
6067
|
+
|
|
6068
|
+
'',
|
|
6069
|
+
|
|
6070
|
+
'Implement:',
|
|
6071
|
+
|
|
6072
|
+
'1. Close only the missing ' + subject + ' checks listed above.',
|
|
6073
|
+
|
|
6074
|
+
'2. Follow the existing file structure and naming patterns.',
|
|
6075
|
+
|
|
6076
|
+
'3. Keep provider secrets in server-only code and documented env templates.',
|
|
6077
|
+
|
|
6078
|
+
'4. Keep external dashboard work explicit instead of claiming it from repo evidence.',
|
|
6079
|
+
|
|
6080
|
+
'',
|
|
6081
|
+
|
|
6082
|
+
'Constraints:',
|
|
6083
|
+
|
|
6084
|
+
'- Do not rewrite unrelated auth, payments, UI, billing, deployment, or analytics code.',
|
|
6085
|
+
|
|
6086
|
+
'- Do not expose secret keys to browser code, public env variables, or client-executed files.',
|
|
6087
|
+
|
|
6088
|
+
'- Do not claim external provider dashboard setup is complete from repo evidence alone.',
|
|
6089
|
+
|
|
6090
|
+
'',
|
|
6091
|
+
|
|
6092
|
+
'Verification:',
|
|
6093
|
+
|
|
6094
|
+
'- Run the relevant TypeScript/build/test command for this repo.',
|
|
6095
|
+
|
|
6096
|
+
'- Confirm VibeRaven can rescan and move the missing checks to passed where repo evidence exists.',
|
|
6097
|
+
|
|
6098
|
+
'- Summarize what changed and what still requires manual provider dashboard verification.'
|
|
6099
|
+
|
|
6100
|
+
].join('\\n');
|
|
6101
|
+
|
|
6102
|
+
}
|
|
6103
|
+
|
|
6104
|
+
|
|
6105
|
+
|
|
6106
|
+
function choiceTilesHtml(areaKey, currentProvider, missions, evidenceMissions) {
|
|
6107
|
+
|
|
6108
|
+
var options = providerOptions[areaKey];
|
|
6109
|
+
|
|
6110
|
+
if (!options || !options.length) return '';
|
|
6111
|
+
|
|
6112
|
+
var projectProvider = projectProviderFor(areaKey, missions);
|
|
6113
|
+
|
|
6114
|
+
var tiles = options.map(function (opt) {
|
|
6115
|
+
|
|
6116
|
+
var p = opt.provider || opt.label;
|
|
6117
|
+
|
|
6118
|
+
var active = sameProvider(p, currentProvider);
|
|
6119
|
+
|
|
6120
|
+
var inProject = sameProvider(p, projectProvider);
|
|
6121
|
+
|
|
6122
|
+
var desc = opt.description || benefitText(p);
|
|
6123
|
+
|
|
6124
|
+
var status = inProject ? 'Using now' : active ? 'Added to setup' : 'Use this path';
|
|
6125
|
+
|
|
6126
|
+
return '<button type="button" class="studio-choice-tile' + (active ? ' studio-choice-tile--selected' : '') + (inProject ? ' studio-choice-tile--in-project' : '') +
|
|
6127
|
+
|
|
6128
|
+
'" data-switch-area="' + esc(areaKey) + '" data-switch-provider="' + esc(opt.provider) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
|
|
6129
|
+
|
|
6130
|
+
'<span class="studio-choice-tile__icon provider-logo' + logoClass(p) + '" aria-hidden="true">' + logoHtml(p, opt.label) + '</span>' +
|
|
6131
|
+
|
|
6132
|
+
'<span class="studio-choice-tile__name">' + esc(opt.label) + '</span>' +
|
|
6133
|
+
|
|
6134
|
+
'<span class="studio-choice-tile__desc">' + esc(desc) + '</span>' +
|
|
6135
|
+
|
|
6136
|
+
'<span class="studio-choice-tile__status">' + status + '</span>' +
|
|
6137
|
+
|
|
6138
|
+
'</button>';
|
|
6139
|
+
|
|
6140
|
+
}).join('');
|
|
6141
|
+
|
|
6142
|
+
var hintLabel = CHOICE_HINTS[areaKey] || ('Choose ' + (LABELS[areaKey] || areaKey).toLowerCase());
|
|
6143
|
+
|
|
6144
|
+
return '<div class="studio-setup-panel__hint"><span>' + esc(hintLabel) + '</span>' + evidenceBadgeHtml(evidenceMissions || missions) + '</div>' +
|
|
6145
|
+
|
|
6146
|
+
'<div class="studio-choice-list" role="group" aria-label="' + esc(hintLabel) + '">' + tiles + '</div>';
|
|
6147
|
+
|
|
6148
|
+
}
|
|
6149
|
+
|
|
6150
|
+
|
|
6151
|
+
|
|
6152
|
+
function buildPrompt(areaKey, missions, providerLabel, automation) {
|
|
6153
|
+
|
|
6154
|
+
if (automation) {
|
|
6155
|
+
|
|
6156
|
+
if (automation.automationLevel === 'manual-only' && automation.verificationPrompt) return automation.verificationPrompt;
|
|
6157
|
+
|
|
6158
|
+
if (automation.repoPrompt) return automation.repoPrompt;
|
|
6159
|
+
|
|
6160
|
+
if (automation.promptRoutes && automation.promptRoutes['repo-fix'] && automation.promptRoutes['repo-fix'].body) {
|
|
6161
|
+
|
|
6162
|
+
return automation.promptRoutes['repo-fix'].body;
|
|
6163
|
+
|
|
6164
|
+
}
|
|
6165
|
+
|
|
6166
|
+
}
|
|
6167
|
+
|
|
6168
|
+
var areaGaps = missions[0] ? gaps.filter(function (g) { return g.primaryMapCategory === areaKey; }) : [];
|
|
6169
|
+
|
|
6170
|
+
if (areaGaps[0] && areaGaps[0].copyPrompt) return areaGaps[0].copyPrompt;
|
|
6171
|
+
|
|
6172
|
+
var missionPrompt = stackPromptFromMission(missions[0], providerLabel);
|
|
6173
|
+
|
|
6174
|
+
if (missionPrompt) return missionPrompt;
|
|
6175
|
+
|
|
6176
|
+
var missing = (missions[0] && missions[0].checks || []).filter(function (c) {
|
|
6177
|
+
|
|
6178
|
+
return c.evidenceClass === 'missing-repo-fix' || c.status === 'missing' || c.status === 'failed';
|
|
6179
|
+
|
|
6180
|
+
});
|
|
6181
|
+
|
|
6182
|
+
if (missing[0] && missing[0].promptHint) return missing[0].promptHint;
|
|
6183
|
+
|
|
6184
|
+
return setupPromptForProvider(areaKey, providerLabel);
|
|
6185
|
+
|
|
6186
|
+
}
|
|
6187
|
+
|
|
6188
|
+
|
|
6189
|
+
|
|
6190
|
+
function setupPromptForProvider(areaKey, providerLabel) {
|
|
6191
|
+
|
|
6192
|
+
var areaLabel = LABELS[areaKey] || areaKey;
|
|
6193
|
+
|
|
6194
|
+
var provider = providerLabel || areaLabel;
|
|
6195
|
+
|
|
6196
|
+
return [
|
|
6197
|
+
|
|
6198
|
+
'Set up ' + provider + ' for the ' + areaLabel + ' area of this app safely.',
|
|
6199
|
+
|
|
6200
|
+
'',
|
|
6201
|
+
|
|
6202
|
+
'First inspect package.json files, env examples, framework routes, provider helpers, server/client boundaries, and existing billing/auth/deployment patterns before editing.',
|
|
6203
|
+
|
|
6204
|
+
'',
|
|
6205
|
+
|
|
6206
|
+
'Implement the smallest useful setup:',
|
|
6207
|
+
|
|
6208
|
+
'1. Add the right ' + provider + ' package or SDK only if it is missing.',
|
|
6209
|
+
|
|
6210
|
+
'2. Document required environment variables in safe examples, without reading or exposing real secrets.',
|
|
6211
|
+
|
|
6212
|
+
'3. Add server-side integration points, route handlers, webhooks, or helpers that match this repo structure.',
|
|
6213
|
+
|
|
6214
|
+
'4. Keep external provider dashboard setup explicit as a manual step.',
|
|
6215
|
+
|
|
6216
|
+
'',
|
|
6217
|
+
|
|
6218
|
+
'Constraints:',
|
|
6219
|
+
|
|
6220
|
+
'- Do not rewrite unrelated product, auth, payment, database, deployment, or analytics code.',
|
|
6221
|
+
|
|
6222
|
+
'- Do not put secret keys in client code, public env variables, or browser-executed files.',
|
|
6223
|
+
|
|
6224
|
+
'- Do not claim live provider configuration is complete from repo changes alone.',
|
|
6225
|
+
|
|
6226
|
+
'',
|
|
6227
|
+
|
|
6228
|
+
'Verification:',
|
|
6229
|
+
|
|
6230
|
+
'- Run the relevant TypeScript/build/test command for this repo.',
|
|
6231
|
+
|
|
6232
|
+
'- Summarize repo changes and list dashboard/provider checks still needed.',
|
|
6233
|
+
|
|
6234
|
+
'- Re-run VibeRaven so repo evidence can move from setup needed to verified.'
|
|
6235
|
+
|
|
6236
|
+
].join('\\n');
|
|
6237
|
+
|
|
6238
|
+
|
|
6239
|
+
}
|
|
6240
|
+
|
|
6241
|
+
|
|
6242
|
+
|
|
6243
|
+
function setupActionsHtml(areaKey, missions, providerLabel, automation) {
|
|
6244
|
+
|
|
6245
|
+
var prompt = buildPrompt(areaKey, missions, providerLabel, automation);
|
|
6246
|
+
|
|
6247
|
+
var hasFixes = (missions[0] && missions[0].checks || []).some(function (c) {
|
|
6248
|
+
|
|
6249
|
+
return c.evidenceClass === 'missing-repo-fix' || c.status === 'missing' || c.status === 'failed';
|
|
6250
|
+
|
|
6251
|
+
});
|
|
6252
|
+
|
|
6253
|
+
var isManualOnly = automation && automation.automationLevel === 'manual-only';
|
|
6254
|
+
|
|
6255
|
+
var mcpCheck = (missions[0] && missions[0].checks || []).find(function (c) {
|
|
6256
|
+
|
|
6257
|
+
return c.evidenceClass === 'mcp-verifier';
|
|
6258
|
+
|
|
6259
|
+
});
|
|
6260
|
+
|
|
6261
|
+
var mcpProvider = (automation && automation.mcpProvider) ||
|
|
6262
|
+
|
|
6263
|
+
(mcpCheck && (mcpCheck.providerKey || mcpCheck.provider || normKey(providerLabel)));
|
|
6264
|
+
|
|
6265
|
+
var supportsMcp = Boolean(mcpProvider);
|
|
6266
|
+
|
|
6267
|
+
var title = esc(providerLabel || LABELS[areaKey] || areaKey) + (isManualOnly ? ' manual check' : hasFixes ? ' fix prompt' : ' setup');
|
|
6268
|
+
|
|
6269
|
+
var meta = supportsMcp ? 'MCP verification available' : 'Prompt only';
|
|
6270
|
+
|
|
6271
|
+
var copy = isManualOnly
|
|
6272
|
+
|
|
6273
|
+
? 'Repo fixes are already clear. Use the manual checklist for provider dashboard work, then rescan.'
|
|
6274
|
+
|
|
6275
|
+
: hasFixes
|
|
6276
|
+
|
|
6277
|
+
? 'One prompt for the missing repo fixes above. Manual dashboard checks stay separate.'
|
|
6278
|
+
|
|
6279
|
+
: supportsMcp
|
|
6280
|
+
|
|
6281
|
+
? 'Use this setup prompt when starting with this provider. The MCP helper is optional.'
|
|
6282
|
+
|
|
6283
|
+
: 'Use this setup prompt when starting with this provider. No trusted MCP helper is available yet.';
|
|
6284
|
+
|
|
6285
|
+
var label = isManualOnly ? 'Copy Checklist' : hasFixes ? 'Copy Fix Prompt' : 'Copy Setup Prompt';
|
|
6286
|
+
|
|
6287
|
+
return '<section class="studio-setup-actions" aria-label="Setup actions">' +
|
|
6288
|
+
|
|
6289
|
+
'<div class="studio-setup-actions__head"><strong>' + title + '</strong><span>' + meta + '</span></div>' +
|
|
6290
|
+
|
|
6291
|
+
'<p class="studio-setup-actions__copy">' + esc(copy) + '</p>' +
|
|
6292
|
+
|
|
6293
|
+
'<div class="studio-setup-actions__buttons">' +
|
|
6294
|
+
|
|
6295
|
+
'<button type="button" class="studio-action-button studio-action-button--primary" data-copy-prompt="' + attrEsc(prompt) + '">' +
|
|
6296
|
+
|
|
6297
|
+
label + '</button>' +
|
|
6298
|
+
|
|
6299
|
+
'</div>' +
|
|
6300
|
+
|
|
6301
|
+
(supportsMcp
|
|
6302
|
+
|
|
6303
|
+
? '<div class="studio-setup-actions__mcp-row"><p class="studio-setup-actions__auth-note"><strong>MCP verifier not configured</strong><br />Optional MCP verification stays read-only and must use credentials already configured by the IDE. VibeRaven does not read real .env files or store provider API keys.</p><button type="button" class="studio-action-button" data-copy-prompt="' + attrEsc('Use read-only ' + mcpProvider + ' MCP verification for ' + (providerLabel || LABELS[areaKey] || areaKey) + '. Report evidence only; do not mutate provider settings or claim dashboard setup from repo edits.') + '">MCP Verify</button></div>'
|
|
6304
|
+
|
|
6305
|
+
: '') +
|
|
6306
|
+
|
|
6307
|
+
'</section>';
|
|
6308
|
+
|
|
6309
|
+
}
|
|
6310
|
+
|
|
6311
|
+
|
|
6312
|
+
|
|
6313
|
+
function addedSetupPathHtml(areaKey, currentProvider, providerLabel, missions) {
|
|
6314
|
+
|
|
6315
|
+
var projectProvider = projectProviderFor(areaKey, missions);
|
|
6316
|
+
|
|
6317
|
+
if (!currentProvider || sameProvider(currentProvider, projectProvider)) return '';
|
|
6318
|
+
|
|
6319
|
+
return '<section class="studio-added-path" aria-label="Added setup path">' +
|
|
6320
|
+
|
|
6321
|
+
'<div class="studio-added-path__title">Added setup path</div>' +
|
|
6322
|
+
|
|
6323
|
+
'<div class="studio-added-path__pill">' +
|
|
6324
|
+
|
|
6325
|
+
'<span class="studio-added-path__icon provider-logo' + logoClass(currentProvider) + '" aria-hidden="true">' + logoHtml(currentProvider, providerLabel) + '</span>' +
|
|
6326
|
+
|
|
6327
|
+
'<strong>' + esc(providerLabel) + '</strong>' +
|
|
6328
|
+
|
|
6329
|
+
'</div>' +
|
|
6330
|
+
|
|
6331
|
+
'</section>';
|
|
6332
|
+
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6335
|
+
|
|
6336
|
+
|
|
6337
|
+
function setupReadinessHtml(areaKey, currentProvider, providerLabel, missions, automation) {
|
|
6338
|
+
|
|
6339
|
+
if (missions[0]) return '';
|
|
6340
|
+
|
|
6341
|
+
var provider = providerLabel || currentProvider || (LABELS[areaKey] || areaKey);
|
|
6342
|
+
|
|
6343
|
+
var repoItems = [
|
|
6344
|
+
|
|
6345
|
+
{ label: provider + ' package or SDK installed', detail: 'VibeRaven has not found repo evidence for this setup path yet.' },
|
|
6346
|
+
|
|
6347
|
+
{ label: provider + ' env names documented', detail: 'Use safe examples only. Do not expose real provider secrets.' },
|
|
6348
|
+
|
|
6349
|
+
{ label: provider + ' server integration or route found', detail: 'Add provider code on the server side before rescanning.' }
|
|
6350
|
+
|
|
6351
|
+
];
|
|
6352
|
+
|
|
6353
|
+
var manualItems = [
|
|
6354
|
+
|
|
6355
|
+
{ label: 'Production ' + provider + ' account and product checked', detail: 'Dashboard confirmation stays manual unless a trusted MCP verifier is configured.' },
|
|
6356
|
+
|
|
6357
|
+
{ label: 'Production webhook or provider credentials checked', detail: 'Repo evidence cannot prove live provider settings by itself.' }
|
|
6358
|
+
|
|
6359
|
+
];
|
|
6360
|
+
|
|
6361
|
+
var external = automation && automation.mcpProvider
|
|
6362
|
+
|
|
6363
|
+
? groupHtml('MCP verifier', [{ label: provider + ' MCP verifier available', detail: 'Use read-only MCP verification when already configured by the IDE.' }], 'external')
|
|
6364
|
+
|
|
6365
|
+
: '';
|
|
6366
|
+
|
|
6367
|
+
return '<section class="studio-verification studio-provider-readiness" aria-label="' + esc(provider) + ' setup readiness">' +
|
|
6368
|
+
|
|
6369
|
+
'<h3 class="studio-verification__title">' + esc(provider) + '</h3>' +
|
|
6370
|
+
|
|
6371
|
+
readinessMetersHtml(0, 0, 'Not checked') +
|
|
6372
|
+
|
|
6373
|
+
'<p class="studio-provider-readiness__note">Provider live moves after MCP verification or manual dashboard confirmation. Repo scans cannot prove live provider state.</p>' +
|
|
6374
|
+
|
|
6375
|
+
'<p class="studio-wiring__summary">' + esc(provider) + ' is added as a setup path, but VibeRaven has not found repo evidence for it yet.</p>' +
|
|
6376
|
+
|
|
6377
|
+
groupHtml('Repo setup needed', repoItems, 'missing') +
|
|
6378
|
+
|
|
6379
|
+
external +
|
|
6380
|
+
|
|
6381
|
+
groupHtml('Provider live check', manualItems, 'manual') +
|
|
6382
|
+
|
|
6383
|
+
'</section>';
|
|
6384
|
+
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6387
|
+
|
|
6388
|
+
|
|
6389
|
+
function readinessMetersHtml(repoPercent, providerPercent, providerStatus) {
|
|
6390
|
+
|
|
6391
|
+
var repo = Math.max(0, Math.min(100, Math.round(repoPercent || 0)));
|
|
6392
|
+
|
|
6393
|
+
var provider = Math.max(0, Math.min(100, Math.round(providerPercent || 0)));
|
|
6394
|
+
var providerValue = providerStatus || (provider + '%');
|
|
6395
|
+
|
|
6396
|
+
return '<div class="studio-provider-readiness__meters" aria-label="Provider readiness meters">' +
|
|
6397
|
+
|
|
6398
|
+
'<div class="studio-provider-readiness__meter-row"><span>Repo · files</span><span class="studio-provider-readiness__bar" aria-hidden="true"><span class="studio-provider-readiness__bar-fill" style="width:' + repo + '%"></span></span><strong>' + repo + '%</strong></div>' +
|
|
6399
|
+
|
|
6400
|
+
'<div class="studio-provider-readiness__meter-row"><span>Provider · live</span><span class="studio-provider-readiness__bar" aria-hidden="true"><span class="studio-provider-readiness__bar-fill" style="width:' + provider + '%"></span></span><strong>' + esc(providerValue) + '</strong></div>' +
|
|
6401
|
+
|
|
6402
|
+
'</div>';
|
|
6403
|
+
|
|
6404
|
+
}
|
|
6405
|
+
|
|
6406
|
+
|
|
6407
|
+
|
|
6408
|
+
function checkToItem(check) {
|
|
6409
|
+
|
|
6410
|
+
var ev = (check.evidence && check.evidence[0]) ? check.evidence[0] : (check.promptHint || '');
|
|
6411
|
+
|
|
6412
|
+
return { label: check.label, detail: ev };
|
|
6413
|
+
|
|
6414
|
+
}
|
|
6415
|
+
|
|
6416
|
+
|
|
6417
|
+
|
|
6418
|
+
function groupHtml(label, items, tone) {
|
|
6419
|
+
|
|
6420
|
+
if (!items.length) return '';
|
|
6421
|
+
|
|
6422
|
+
var slice = items.slice(0, MAX_GROUP_ITEMS);
|
|
6423
|
+
|
|
6424
|
+
var rows = slice.map(function (item) {
|
|
6425
|
+
|
|
6426
|
+
var title = item.detail ? ' title="' + esc(item.detail) + '"' : '';
|
|
6427
|
+
|
|
6428
|
+
return '<li class="studio-verification__item' + (tone === 'manual' ? ' studio-verification__item--manual' : '') + '"' + title + '><span class="studio-verification__item-label">' + esc(item.label) + '</span></li>';
|
|
6429
|
+
|
|
6430
|
+
}).join('');
|
|
6431
|
+
|
|
6432
|
+
var more = items.length > MAX_GROUP_ITEMS
|
|
6433
|
+
|
|
6434
|
+
? '<li class="studio-verification__item studio-verification__item--more">+' + (items.length - MAX_GROUP_ITEMS) + ' more</li>'
|
|
6435
|
+
|
|
6436
|
+
: '';
|
|
6437
|
+
|
|
6438
|
+
return '<div class="studio-verification__group studio-verification__group--' + tone + '">' +
|
|
6439
|
+
|
|
6440
|
+
'<div class="studio-verification__group-title"><strong>' + esc(label) + '</strong>' +
|
|
6441
|
+
|
|
6442
|
+
'<span class="studio-verification__count">' + items.length + '</span></div>' +
|
|
6443
|
+
|
|
6444
|
+
'<ul class="studio-verification__list">' + rows + more + '</ul></div>';
|
|
6445
|
+
|
|
6446
|
+
}
|
|
6447
|
+
|
|
6448
|
+
|
|
6449
|
+
|
|
6450
|
+
function missionBlockHtml(missions, providerLabel) {
|
|
6451
|
+
|
|
6452
|
+
var mission = missions[0];
|
|
6453
|
+
|
|
6454
|
+
if (!mission || !(mission.checks && mission.checks.length)) return '';
|
|
6455
|
+
|
|
6456
|
+
var groups = {
|
|
6457
|
+
|
|
6458
|
+
'repo-verified': [],
|
|
6459
|
+
|
|
6460
|
+
'missing-repo-fix': [],
|
|
6461
|
+
|
|
6462
|
+
'mcp-verifier': [],
|
|
6463
|
+
|
|
6464
|
+
'manual-dashboard': []
|
|
6465
|
+
|
|
6466
|
+
};
|
|
6467
|
+
|
|
6468
|
+
mission.checks.forEach(function (check) {
|
|
6469
|
+
|
|
6470
|
+
var bucket = groups[check.evidenceClass] ? check.evidenceClass : 'manual-dashboard';
|
|
6471
|
+
|
|
6472
|
+
groups[bucket].push(checkToItem(check));
|
|
6473
|
+
|
|
6474
|
+
});
|
|
6475
|
+
|
|
6476
|
+
var actionable = groups['repo-verified'].length + groups['missing-repo-fix'].length;
|
|
6477
|
+
var manualTotal = groups['manual-dashboard'].length;
|
|
6478
|
+
var manualDone = mission.checks.filter(function (check) {
|
|
6479
|
+
|
|
6480
|
+
return check.evidenceClass === 'manual-dashboard' && (check.status === 'passed' || check.status === 'user-confirmed');
|
|
6481
|
+
|
|
6482
|
+
}).length;
|
|
6483
|
+
var providerPercent = manualTotal ? Math.round((manualDone / manualTotal) * 100) : 0;
|
|
6484
|
+
var providerValue = manualTotal ? (manualDone > 0 ? providerPercent + '%' : 'Not checked') : 'No live checks';
|
|
6485
|
+
|
|
6486
|
+
var summary =
|
|
6487
|
+
|
|
6488
|
+
'Stack scanner: ' + groups['repo-verified'].length + ' / ' + Math.max(actionable, 1) +
|
|
6489
|
+
|
|
6490
|
+
' repo checks verified (' + (mission.readinessPercent || 0) + '%).';
|
|
6491
|
+
|
|
6492
|
+
if (groups['missing-repo-fix'].length > 0) {
|
|
6493
|
+
|
|
6494
|
+
summary += ' Fix ' + groups['missing-repo-fix'].length + ' repo item' +
|
|
6495
|
+
|
|
6496
|
+
(groups['missing-repo-fix'].length === 1 ? '' : 's') + ', then rescan.';
|
|
6497
|
+
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6500
|
+
var body =
|
|
6501
|
+
|
|
6502
|
+
groupHtml('Repo verified', groups['repo-verified'], 'found') +
|
|
6503
|
+
|
|
6504
|
+
groupHtml('Stack fixes needed', groups['missing-repo-fix'], 'missing') +
|
|
6505
|
+
|
|
6506
|
+
groupHtml('MCP verifier', groups['mcp-verifier'], 'external') +
|
|
6507
|
+
|
|
6508
|
+
groupHtml('Manual dashboard check', groups['manual-dashboard'], 'manual');
|
|
6509
|
+
|
|
6510
|
+
if (!body) return '';
|
|
6511
|
+
|
|
6512
|
+
return '<section class="studio-verification studio-wiring studio-mission-graph" aria-label="Mission evidence">' +
|
|
6513
|
+
|
|
6514
|
+
'<h3 class="studio-verification__title">' + esc(providerLabel || mission.providerLabel || 'Detected evidence') + '</h3>' +
|
|
6515
|
+
|
|
6516
|
+
readinessMetersHtml(mission.readinessPercent || 0, providerPercent, providerValue) +
|
|
6517
|
+
|
|
6518
|
+
'<p class="studio-provider-readiness__note">Provider live moves after MCP verification or manual dashboard confirmation. Repo scans cannot prove live provider state.</p>' +
|
|
6519
|
+
|
|
6520
|
+
'<p class="studio-wiring__summary">' + esc(summary) + '</p>' + body + '</section>';
|
|
6521
|
+
|
|
6522
|
+
}
|
|
6523
|
+
|
|
6524
|
+
|
|
6525
|
+
|
|
6526
|
+
function render(areaKey, providerOverride) {
|
|
6527
|
+
|
|
6528
|
+
if (providerOverride) selectedProviders[areaKey] = providerOverride;
|
|
6529
|
+
|
|
6530
|
+
var area = areas.find(function (a) { return a.key === areaKey; });
|
|
6531
|
+
|
|
6532
|
+
document.querySelectorAll('.studio-node').forEach(function (n) {
|
|
6533
|
+
|
|
6534
|
+
var selected = n.getAttribute('data-area-key') === areaKey;
|
|
6535
|
+
|
|
6536
|
+
n.classList.toggle('studio-node--selected', selected);
|
|
6537
|
+
|
|
6538
|
+
n.setAttribute('aria-pressed', selected ? 'true' : 'false');
|
|
6539
|
+
|
|
6540
|
+
});
|
|
6541
|
+
|
|
6542
|
+
var label = (area && area.label) || LABELS[areaKey] || areaKey;
|
|
6543
|
+
|
|
6544
|
+
var missions = (area && area.providerMissions) || [];
|
|
6545
|
+
|
|
6546
|
+
var selectedBeforeRender = selectedProviders[areaKey] || '';
|
|
6547
|
+
|
|
6548
|
+
var defaultMission = preferredMission(missions, selectedBeforeRender);
|
|
6549
|
+
|
|
6550
|
+
var current = selectedProviders[areaKey] ||
|
|
6551
|
+
|
|
6552
|
+
(defaultMission && (defaultMission.provider || defaultMission.providerLabel)) || '';
|
|
6553
|
+
|
|
6554
|
+
var mission = missionForProvider(missions, current) || (!selectedBeforeRender ? defaultMission : null);
|
|
6555
|
+
|
|
6556
|
+
var panelMissions = mission ? [mission] : [];
|
|
6557
|
+
|
|
6558
|
+
var providerLabel = providerLabelFor(areaKey, current, mission, label);
|
|
6559
|
+
|
|
6560
|
+
var automation = automationFor(areaKey, mission, current);
|
|
6561
|
+
|
|
6562
|
+
|
|
6563
|
+
|
|
6564
|
+
panel.innerHTML =
|
|
6565
|
+
|
|
6566
|
+
'<div class="studio-setup-panel__inner">' +
|
|
6567
|
+
|
|
6568
|
+
'<div class="studio-setup-panel__head">' +
|
|
6569
|
+
|
|
6570
|
+
'<div class="studio-setup-panel__title">' + panelTitle(areaKey, label) + '</div>' +
|
|
6571
|
+
|
|
6572
|
+
'</div>' +
|
|
6573
|
+
|
|
6574
|
+
choiceTilesHtml(areaKey, current, missions, panelMissions) +
|
|
6575
|
+
|
|
6576
|
+
addedSetupPathHtml(areaKey, current, providerLabel, missions) +
|
|
6577
|
+
|
|
6578
|
+
setupActionsHtml(areaKey, panelMissions, providerLabel, automation) +
|
|
6579
|
+
|
|
6580
|
+
setupReadinessHtml(areaKey, current, providerLabel, panelMissions, automation) +
|
|
6581
|
+
|
|
6582
|
+
missionBlockHtml(panelMissions, providerLabel) +
|
|
6583
|
+
|
|
6584
|
+
'</div>';
|
|
6585
|
+
|
|
6586
|
+
|
|
6587
|
+
|
|
6588
|
+
panel.querySelectorAll('[data-copy-prompt]').forEach(function (btn) {
|
|
6589
|
+
|
|
6590
|
+
btn.addEventListener('click', function () {
|
|
6591
|
+
|
|
6592
|
+
var text = btn.getAttribute('data-copy-prompt') || '';
|
|
6593
|
+
|
|
6594
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
6595
|
+
|
|
6596
|
+
var label = btn.textContent;
|
|
6597
|
+
|
|
6598
|
+
btn.textContent = 'Copied';
|
|
6599
|
+
|
|
6600
|
+
setTimeout(function () { btn.textContent = label; }, 1200);
|
|
6601
|
+
|
|
6602
|
+
});
|
|
6603
|
+
|
|
6604
|
+
});
|
|
6605
|
+
|
|
6606
|
+
});
|
|
6607
|
+
|
|
6608
|
+
|
|
6609
|
+
|
|
6610
|
+
panel.querySelectorAll('[data-switch-provider]').forEach(function (btn) {
|
|
6611
|
+
|
|
6612
|
+
btn.addEventListener('click', function () {
|
|
6613
|
+
|
|
6614
|
+
var a = btn.getAttribute('data-switch-area');
|
|
6615
|
+
|
|
6616
|
+
var p = btn.getAttribute('data-switch-provider');
|
|
6617
|
+
|
|
6618
|
+
render(a, p);
|
|
6619
|
+
|
|
6620
|
+
});
|
|
6621
|
+
|
|
6622
|
+
});
|
|
6623
|
+
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
|
|
6627
|
+
|
|
6628
|
+
document.querySelectorAll('.studio-node').forEach(function (n) {
|
|
6629
|
+
|
|
6630
|
+
n.addEventListener('click', function () { render(n.getAttribute('data-area-key')); });
|
|
6631
|
+
|
|
6632
|
+
});
|
|
6633
|
+
|
|
6634
|
+
|
|
6635
|
+
|
|
6636
|
+
document.addEventListener(
|
|
6637
|
+
|
|
6638
|
+
'error',
|
|
6639
|
+
|
|
6640
|
+
function (ev) {
|
|
6641
|
+
|
|
6642
|
+
var img = ev.target;
|
|
6643
|
+
|
|
6644
|
+
if (!img || !img.classList || !img.classList.contains('provider-logo__img')) return;
|
|
6645
|
+
|
|
6646
|
+
var key = img.getAttribute('data-provider-logo-key');
|
|
6647
|
+
|
|
6648
|
+
if (!key || !logoPayload.logos[key]) return;
|
|
6649
|
+
|
|
6650
|
+
var wrap = document.createElement('span');
|
|
6651
|
+
|
|
6652
|
+
wrap.innerHTML = logoPayload.logos[key];
|
|
6653
|
+
|
|
6654
|
+
var svg = wrap.firstElementChild;
|
|
6655
|
+
|
|
6656
|
+
if (svg) img.replaceWith(svg);
|
|
6657
|
+
|
|
6658
|
+
},
|
|
6659
|
+
|
|
6660
|
+
true
|
|
6661
|
+
|
|
6662
|
+
);
|
|
6663
|
+
|
|
6664
|
+
|
|
6665
|
+
|
|
6666
|
+
render(defaultAreaKey);
|
|
6667
|
+
|
|
6668
|
+
})();
|
|
6669
|
+
|
|
6670
|
+
`;
|
|
6671
|
+
|
|
6672
|
+
// src/report/providerLogos.ts
|
|
6673
|
+
var PROVIDER_LOGOS = {
|
|
6674
|
+
supabase: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13.8 2.4 5.6 13.1c-.5.7 0 1.7.9 1.7h4.7l-1 6.1c-.2 1 .9 1.6 1.5.8l8.1-10.8c.5-.7 0-1.7-.9-1.7h-4.7l1-6.1c.2-1-.8-1.6-1.4-.7Z"/></svg>',
|
|
6675
|
+
clerk: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#6C47FF" d="M19.2 12c0 3.9-3.1 7.1-7.1 7.1-3.2 0-5.9-2.1-6.8-5h2.9c.7 1.8 2.5 3.1 4.5 3.1 2.7 0 4.9-2.2 4.9-4.9S13.2 7.4 10.5 7.4c-2 0-3.8 1.2-4.5 3H3.3c.9-2.9 3.6-5 6.8-5 4 0 7.1 3.2 7.1 7.1Z"/></svg>',
|
|
6676
|
+
authjs: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#412991" d="M12 2 4 6.5v5.2c0 4.6 3.4 8.9 8 10.3 4.6-1.4 8-5.7 8-10.3V6.5L12 2Z"/><path fill="#EB5424" d="M12 2v18.5c-1.2-.4-2.3-1-3.3-1.7L12 2Z"/><path fill="#FBC22C" d="M12 2 15.3 18.8c-1-.7-2.1-1.3-3.3-1.7V2Z"/></svg>',
|
|
6677
|
+
auth0: '<svg viewBox="0 0 24 24" aria-hidden="true"><rect fill="#EB5424" x="3" y="3" width="18" height="18" rx="3.2"/><path fill="#ffffff" d="M12 7.4c2.5 0 4.6 2.1 4.6 4.6S14.5 16.6 12 16.6 7.4 14.5 7.4 12 9.5 7.4 12 7.4Zm0 2.2a2.4 2.4 0 1 0 0 4.8 2.4 2.4 0 0 0 0-4.8Z"/></svg>',
|
|
6678
|
+
"better-auth": '<svg viewBox="0 0 24 24" aria-hidden="true"><rect fill="#171717" x="3" y="3" width="18" height="18" rx="4"/><path fill="#ffffff" d="M8 8h3v3H8V8Zm5 0h3v3h-3V8ZM8 13h3v3H8v-3Zm5 0h3v3h-3v-3Z"/></svg>',
|
|
6679
|
+
polar: '<svg viewBox="0 0 29 29" aria-hidden="true"><path fill="#0062FF" fill-rule="evenodd" clip-rule="evenodd" d="M9.077 23.057c4.801 3.25 11.328 1.992 14.577-2.808 3.25-4.801 1.993-11.328-2.808-14.578C16.045 2.422 9.519 3.679 6.269 8.48c-3.25 4.801-1.993 11.327 2.808 14.577Zm1.393.086c4.392 2.247 9.963.138 12.444-4.711 2.48-4.848.93-10.6-3.461-12.847-4.392-2.247-9.963-.138-12.443 4.711-2.481 4.848-.932 10.6 3.46 12.847Z"/><path fill="#0062FF" fill-rule="evenodd" clip-rule="evenodd" d="M11.722 24.29c3.965 1.29 8.628-2.118 10.417-7.613 1.788-5.495.024-10.996-3.94-12.286-3.964-1.29-8.628 2.118-10.416 7.613-1.789 5.495-.025 10.995 3.939 12.286Zm1.213-.418c3.355.716 6.982-2.961 8.102-8.212 1.12-5.252-.691-10.089-4.046-10.804-3.356-.716-6.983 2.96-8.103 8.212-1.12 5.251.692 10.088 4.047 10.804Z"/><path fill="#0062FF" fill-rule="evenodd" clip-rule="evenodd" d="M13.854 24.738c2.652.284 5.3-4.14 5.912-9.882.613-5.74-1.04-10.624-3.692-10.907-2.653-.283-5.3 4.141-5.913 9.882-.613 5.741 1.04 10.624 3.693 10.907Zm1.241-1.747c1.92-.031 3.415-3.917 3.34-8.68-.075-4.764-1.693-8.6-3.612-8.57-1.92.03-3.415 3.916-3.34 8.68.076 4.763 1.693 8.6 3.612 8.57Z"/></svg>',
|
|
6680
|
+
"lemon-squeezy": '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 3c-3.2 2.8-5 5.8-5 8.6a5 5 0 1 0 10 0c0-2.8-1.8-5.8-5-8.6Zm0 14.2a1.6 1.6 0 1 1 0-3.2 1.6 1.6 0 0 1 0 3.2Z"/></svg>',
|
|
6681
|
+
github: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 2C6.48 2 2 6.58 2 12.26c0 4.52 2.87 8.35 6.86 9.71.5.1.69-.22.69-.49 0-.24-.01-.87-.01-1.7-2.78.62-3.37-1.36-3.37-1.36-.45-1.17-1.11-1.48-1.11-1.48-.91-.64.07-.63.07-.63 1 .07 1.53 1.05 1.53 1.05.9 1.56 2.36 1.11 2.94.85.09-.67.35-1.11.63-1.37-2.22-.26-4.56-1.14-4.56-5.07 0-1.12.39-2.03 1.03-2.75-.1-.26-.45-1.3.1-2.7 0 0 .84-.28 2.75 1.05A9.2 9.2 0 0 1 12 6.84c.85 0 1.71.12 2.51.34 1.91-1.33 2.75-1.05 2.75-1.05.55 1.4.2 2.44.1 2.7.64.72 1.03 1.63 1.03 2.75 0 3.94-2.34 4.8-4.57 5.06.36.32.68.94.68 1.9 0 1.37-.01 2.47-.01 2.8 0 .27.18.6.7.49A10.03 10.03 0 0 0 22 12.26C22 6.58 17.52 2 12 2Z"/></svg>',
|
|
6682
|
+
gitlab: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#FC6D26" d="M23.2 9.4 12 2.2.8 9.4l1.4 8.2L12 21.8l9.8-4.2 1.4-8.2Z"/><path fill="#E24329" d="M12 2.2v7.1l3.8 1.9 1.8-5.5L12 2.2Z"/><path fill="#FC6D26" d="M12 10.2 7.7 12l1.8-5.5L12 2.2Z"/><path fill="#FCA326" d="M.8 9.4 12 21.8 7.7 12 12 10.2Z"/><path fill="#FC6D26" d="M12 10.2l4.3 1.8 6.1-2.6L12 2.2Z"/></svg>',
|
|
6683
|
+
neon: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4.5c0-1 .8-1.7 1.7-1.3l9.5 4.3c1.1.5 1.8 1.6 1.8 2.8v8.6c0 1-.8 1.7-1.7 1.3L6.8 15.9A3 3 0 0 1 5 13.1V4.5Zm4.1 4.1v5l4.9 2.2v-5L9.1 8.6Z"/></svg>',
|
|
6684
|
+
planetscale: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M19.1 4.9A10 10 0 0 0 4.9 19.1L19.1 4.9Zm-12 16A10 10 0 0 0 20.9 7.1L7.1 20.9Z"/></svg>',
|
|
6685
|
+
mongodb: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2c3 3 4.8 6.2 4.8 9.6 0 4.2-2.2 7.1-4.8 9.9-2.6-2.8-4.8-5.7-4.8-9.9C7.2 8.2 9 5 12 2Zm0 5.2v10.2"/></svg>',
|
|
6686
|
+
turso: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7.5h16v3H4v-3Zm0 6h16v3H4v-3ZM7.5 4h9v3h-9V4Zm0 13h9v3h-9v-3Z"/></svg>',
|
|
6687
|
+
stripe: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#635BFF" d="M4.5 15.4c1.5.9 3.5 1.4 5.4 1.4 1.6 0 2.5-.4 2.5-1.2 0-.7-.7-1-3-1.5-3-.7-4.7-1.8-4.7-4.1 0-2.6 2.1-4.4 5.8-4.4 2.1 0 3.9.4 5.3 1.1v3.4a10 10 0 0 0-5.1-1.4c-1.5 0-2.2.4-2.2 1.1 0 .7.8 1 3 1.5 3.1.7 4.8 1.8 4.8 4.1 0 2.7-2.2 4.5-6.2 4.5-2.2 0-4.3-.5-5.6-1.3v-3.2Z"/></svg>',
|
|
6688
|
+
paddle: '<svg viewBox="0 0 90 90" aria-hidden="true"><rect x="11" y="11" width="68" height="68" rx="17" fill="#101318"/><rect x="11.5" y="11.5" width="67" height="67" rx="16.5" fill="none" stroke="#343942"/><path fill="#FFD21E" d="M8.49991 17C8.51217 21.6945 12.3128 25.5001 17 25.5001C12.3128 25.5001 8.51217 29.3055 8.49991 34C8.48783 29.3055 4.68717 25.5001 0 25.5001C4.68717 25.5001 8.48783 21.6945 8.49991 17Z" transform="translate(19.5 -31.5) scale(3)"/></svg>',
|
|
6689
|
+
vercel: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4 22 20H2L12 4Z"/></svg>',
|
|
6690
|
+
netlify: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m12 2 3.1 5.4 6.2 1.2-4.2 4.7.8 6.3-5.9-2.6-5.9 2.6.8-6.3-4.2-4.7 6.2-1.2L12 2Z"/></svg>',
|
|
6691
|
+
aws: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#FF9900" d="M6.763 10.582c4.95-2.283 10.563-2.283 15.475 0 .532.243.848.741.848 1.256 0 .532-.323 1.026-.85 1.256-4.912 2.283-10.525 2.283-15.474 0-.528-.23-.851-.724-.851-.279 0-.544.098-.754.243-4.95 2.283-10.563 2.283-15.475 0-.532-.243-.848-.741-.848-1.256 0-.532.323-1.026.85-1.256.228-.098.492-.196.754-.196.279 0 .544.098.754.243Z"/><path fill="#FF9900" d="M12 3.65c-2.772 0-5.027 1.147-5.027 2.553S9.228 8.756 12 8.756s5.027-1.147 5.027-2.553S14.772 3.65 12 3.65Z"/></svg>',
|
|
6692
|
+
sentry: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 22 20H2L12 3Zm0 5.1L6.8 17h2.1l3.1-5.3 3.1 5.3h2.1L12 8.1Z"/></svg>',
|
|
6693
|
+
posthog: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#1D4AFF" d="M5 6h8.06c3.19 0 5.44 2.06 5.44 5.31S16.25 16.62 13.06 16.62H5V6Z"/><path fill="#F54E00" d="M4.13 3.94v4.5h3.94a2.25 2.25 0 0 0 0-4.5H4.13Z"/><circle cx="13.5" cy="11.81" r="1.01" fill="#F9BD2B"/><path fill="#F54E00" d="m16.39 9.49 1.88-1.13.79 1.2-2.03 1.28-.64-1.35Zm.34 4.61 2.33.83-.74 1.54-2.33-.98.74-1.39Z"/></svg>',
|
|
6694
|
+
playwright: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#2EAD33" d="M8.4 8.2 5.4 17.2h2.3l.8-2.8h2.7l.8 2.8h2.3L11.6 8.2H8.4Zm1.1 4.8.8-2.4.8 2.4H9.5Z"/><path fill="#E2574C" d="M15.6 8.2 12.6 17.2h2.3l.8-2.8h2.7l.8 2.8h2.3L18.8 8.2h-3.2Zm1.1 4.8.8-2.4.8 2.4h-1.6Z"/><ellipse fill="#2EAD33" cx="9.5" cy="13.1" rx="1.1" ry="1.4"/><ellipse fill="#E2574C" cx="16.5" cy="13.1" rx="1.1" ry="1.4"/></svg>',
|
|
6695
|
+
logrocket: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><rect x="2" y="2" width="20" height="20" rx="5" fill="#764ABC"/><path fill="#FFFFFF" d="M12 7.4c2.7 0 4.9 2.2 4.9 4.9s-2.2 4.9-4.9 4.9-4.9-2.2-4.9-4.9 4.9-4.9 4.9-4.9Zm0 1.9a3 3 0 1 0 0 6 3 3 0 0 0 0-6Zm-3.2 7.4h6.4v1.5H8.8v-1.5Z"/></svg>',
|
|
6696
|
+
figma: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 3h3.5v6H8.5a3 3 0 1 1 0-6Zm3.5 6h3.5a3 3 0 1 0 0-6H12v6Zm0 0h3.5a3 3 0 1 1 0 6H12V9Zm-3.5 0H12v6H8.5a3 3 0 1 1 0-6ZM8.5 15H12v2.5A3.5 3.5 0 1 1 8.5 15Z"/></svg>',
|
|
6697
|
+
storybook: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.2 3.4 17.9 2l.7 18.1-12.4.7V3.4Zm8.7 3.2.3 2.4 1.7-1.3 1.7 1.1.3-3.4-4 .5v.7Zm-5.2 5.1c0 2 1.6 3.5 4.2 3.5 2.4 0 3.8-1.2 3.8-3 0-1.7-1.1-2.6-3.4-3l-1.2-.2c-.7-.1-1-.4-1-.8 0-.5.5-.8 1.3-.8.9 0 1.5.4 1.8 1.1l2.1-.8c-.5-1.4-1.8-2.2-3.8-2.2-2.3 0-3.8 1.2-3.8 2.9 0 1.6 1.1 2.5 3.3 2.9l1.2.2c.8.1 1.1.4 1.1.9 0 .6-.5.9-1.4.9-1.1 0-1.8-.5-2.1-1.3l-2.1.7Z"/></svg>',
|
|
6698
|
+
"product-spec": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3.2h9.2L19 7v13.8H6V3.2Zm8.2 1.9v3h3l-3-3ZM8.4 10h7.2v1.7H8.4V10Zm0 3.2h7.2v1.7H8.4v-1.7Zm0 3.2h4.9v1.7H8.4v-1.7Z"/></svg>',
|
|
6699
|
+
"route-map": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.5 4.2a3 3 0 0 1 3 3c0 2-3 5.1-3 5.1s-3-3.1-3-5.1a3 3 0 0 1 3-3Zm0 1.8a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4Zm11 5.8a3 3 0 0 1 3 3c0 2-3 5.1-3 5.1s-3-3.1-3-5.1a3 3 0 0 1 3-3Zm0 1.8a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4ZM9.5 7h2.1c2.8 0 4.9 2 4.9 4.8h-2c0-1.7-1.2-2.8-2.9-2.8H9.5V7Zm4.9 10h-2.1c-2.8 0-4.9-2-4.9-4.8h2c0 1.7 1.2 2.8 2.9 2.8h2.1v2Z"/></svg>',
|
|
6700
|
+
react: '<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="2.2"/><ellipse cx="12" cy="12" rx="9" ry="3.6" fill="none" stroke="currentColor" stroke-width="1.6"/><ellipse cx="12" cy="12" rx="9" ry="3.6" fill="none" stroke="currentColor" stroke-width="1.6" transform="rotate(60 12 12)"/><ellipse cx="12" cy="12" rx="9" ry="3.6" fill="none" stroke="currentColor" stroke-width="1.6" transform="rotate(120 12 12)"/></svg>',
|
|
6701
|
+
vue: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.6 4h5.1L12 11.4 16.3 4h5.1L12 20 2.6 4Zm5.6 0L12 10.5 15.8 4h-2.7L12 5.9 10.9 4H8.2Z"/></svg>',
|
|
6702
|
+
svelte: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M16.9 3.4a5.2 5.2 0 0 0-6.9 1.5L6.3 9.7a4.6 4.6 0 0 0 .8 6.4 5.1 5.1 0 0 0 6.9-1.2l.6-.8a1.6 1.6 0 0 0-.3-2.2 1.8 1.8 0 0 0-2.4.4l-.6.8a1.3 1.3 0 0 1-1.8.3 1.2 1.2 0 0 1-.2-1.6L13 7a1.3 1.3 0 0 1 1.8-.4c.5.4.7 1.1.3 1.6l-.3.4 2.8 2 .3-.4a4.7 4.7 0 0 0-1-6.8Zm-9.8 17.2a5.2 5.2 0 0 0 6.9-1.5l3.7-4.8a4.6 4.6 0 0 0-.8-6.4A5.1 5.1 0 0 0 10 9.1l-.6.8a1.6 1.6 0 0 0 .3 2.2 1.8 1.8 0 0 0 2.4-.4l.6-.8a1.3 1.3 0 0 1 1.8-.3 1.2 1.2 0 0 1 .2 1.6L11 17a1.3 1.3 0 0 1-1.8.4 1.2 1.2 0 0 1-.3-1.6l.3-.4-2.8-2-.3.4a4.7 4.7 0 0 0 1 6.8Z"/></svg>',
|
|
6703
|
+
angular: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2.5 20.5 5.6 19.2 17 12 21.5 4.8 17 3.5 5.6 12 2.5Zm0 4.2-5 11h2.4l1-2.4h3.2l1 2.4H17l-5-11Zm0 3.8 1.1 2.9h-2.2l1.1-2.9Z"/></svg>',
|
|
6704
|
+
nodejs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2.5 20.2 7v10L12 21.5 3.8 17V7L12 2.5Zm-3.8 6.7v5.6h1.9v-3.1l3.8 3.1h1.9V9.2h-1.9v3.2l-3.8-3.2H8.2Z"/></svg>',
|
|
6705
|
+
python: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11.8 3c2.7 0 4.2.8 4.2 2.5V9H9.6A2.6 2.6 0 0 0 7 11.6V13H4.5C3.5 13 3 12.2 3 11c0-3.2 1.9-4.9 5.6-4.9h3.8V5H8.8V3.6A10 10 0 0 1 11.8 3Zm-1.4 1.5a.8.8 0 1 0 0 1.6.8.8 0 0 0 0-1.6ZM12.2 21c-2.7 0-4.2-.8-4.2-2.5V15h6.4a2.6 2.6 0 0 0 2.6-2.6V11h2.5c1 0 1.5.8 1.5 2 0 3.2-1.9 4.9-5.6 4.9h-3.8V19h3.6v1.4a10 10 0 0 1-3 .6Zm1.4-3.1a.8.8 0 1 0 0 1.6.8.8 0 0 0 0-1.6Z"/></svg>',
|
|
6706
|
+
rails: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 17.7C5.8 9.5 11.7 5.2 21 4.8v3.1C13.5 8.3 8.7 11.6 6 18.5L3 17.7Zm4.6.7c2.1-5 5.9-7.5 11.4-7.9v2.4c-4.4.4-7.4 2.5-9 6.2l-2.4-.7Zm4.7.7c1.3-2.4 3.4-3.7 6.7-4.1v2.2c-2.2.4-3.6 1.3-4.5 2.6l-2.2-.7Z"/></svg>',
|
|
6707
|
+
go: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 8.2h7.4v1.5H3V8.2Zm-1 3h7.4v1.5H2v-1.5Zm2 3h5.4v1.5H4v-1.5Zm10.5-6.1c3.1 0 5.5 2 5.5 4.6s-2.4 4.6-5.5 4.6-5.5-2-5.5-4.6 2.4-4.6 5.5-4.6Zm0 2.1c-1.7 0-3 1.1-3 2.5s1.3 2.5 3 2.5 3-1.1 3-2.5-1.3-2.5-3-2.5Zm5.1-2h2.4l-1.2 9h-2.4l1.2-9Z"/></svg>',
|
|
6708
|
+
"testing-library": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3h10v4.2l-3.4 4.5 5.4 7.9c.5.8 0 1.9-1 1.9H6c-1 0-1.6-1.1-1-1.9l5.4-7.9L7 7.2V3Zm3 3.5 2 2.7 2-2.7H10Zm1.9 8.4-2.6 3.8h5.4l-2.8-3.8Z"/></svg>',
|
|
6709
|
+
vitest: '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" aria-hidden="true"><path fill="#FCC72B" d="M13.4 3 21 7.4 12 21 3 7.4 10.6 3l1.4 4.5L13.4 3Zm-1.4 7.3-2.1 4h4.2l-2.1-4Z"/><path fill="#729B1B" d="M12 10.3 9.9 14.3h4.2L12 10.3Z"/></svg>',
|
|
6710
|
+
"rate-limit": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3a9 9 0 1 0 9 9h-3a6 6 0 1 1-1.8-4.3L13 11h8V3l-2.7 2.7A9 9 0 0 0 12 3Z"/></svg>',
|
|
6711
|
+
"bot-protection": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2 20 6v6c0 5-3.5 8.6-8 10-4.5-1.4-8-5-8-10V6l8-4Zm-3 8h6v4H9v-4Zm1.5-3.2h3V10h-3V6.8Z"/></svg>',
|
|
6712
|
+
"secrets-hygiene": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 10V7a5 5 0 0 1 10 0v3h1.5v11h-13V10H7Zm3 0h4V7a2 2 0 0 0-4 0v3Z"/></svg>'
|
|
6713
|
+
};
|
|
6714
|
+
var ALIASES = {
|
|
6715
|
+
authjs: "authjs",
|
|
6716
|
+
"auth.js": "authjs",
|
|
6717
|
+
nextauth: "authjs",
|
|
6718
|
+
posthog: "posthog",
|
|
6719
|
+
node: "nodejs",
|
|
6720
|
+
nodejs: "nodejs",
|
|
6721
|
+
"node.js": "nodejs",
|
|
6722
|
+
express: "nodejs",
|
|
6723
|
+
expressjs: "nodejs",
|
|
6724
|
+
supabase: "supabase",
|
|
6725
|
+
clerk: "clerk",
|
|
6726
|
+
stripe: "stripe",
|
|
6727
|
+
vercel: "vercel",
|
|
6728
|
+
"supabase auth": "supabase",
|
|
6729
|
+
supabaseauth: "supabase",
|
|
6730
|
+
"bot protection": "bot-protection",
|
|
6731
|
+
botprotection: "bot-protection",
|
|
6732
|
+
"secrets hygiene": "secrets-hygiene",
|
|
6733
|
+
secretshygiene: "secrets-hygiene",
|
|
6734
|
+
"rate limiting": "rate-limit",
|
|
6735
|
+
ratelimiting: "rate-limit",
|
|
6736
|
+
polar: "polar",
|
|
6737
|
+
lemonsqueezy: "lemon-squeezy",
|
|
6738
|
+
"lemon squeezy": "lemon-squeezy",
|
|
6739
|
+
"lemon-squeezy": "lemon-squeezy",
|
|
6740
|
+
github: "github",
|
|
6741
|
+
gitlab: "gitlab",
|
|
6742
|
+
auth0: "auth0",
|
|
6743
|
+
"better-auth": "better-auth",
|
|
6744
|
+
betterauth: "better-auth",
|
|
6745
|
+
"mongodb atlas": "mongodb",
|
|
6746
|
+
logrocket: "logrocket",
|
|
6747
|
+
sentry: "sentry",
|
|
6748
|
+
figma: "figma",
|
|
6749
|
+
react: "react",
|
|
6750
|
+
vitest: "vitest",
|
|
6751
|
+
playwright: "playwright",
|
|
6752
|
+
neon: "neon",
|
|
6753
|
+
planetscale: "planetscale",
|
|
6754
|
+
"planet scale": "planetscale",
|
|
6755
|
+
mongodb: "mongodb",
|
|
6756
|
+
netlify: "netlify",
|
|
6757
|
+
render: "render",
|
|
6758
|
+
rendercom: "render",
|
|
6759
|
+
railway: "railway",
|
|
6760
|
+
railwayapp: "railway",
|
|
6761
|
+
cloudflare: "cloudflare",
|
|
6762
|
+
cloudflarepages: "cloudflare",
|
|
6763
|
+
workers: "cloudflare",
|
|
6764
|
+
aws: "aws",
|
|
6765
|
+
paddle: "paddle",
|
|
6766
|
+
turso: "turso",
|
|
6767
|
+
vue: "vue",
|
|
6768
|
+
svelte: "svelte",
|
|
6769
|
+
angular: "angular",
|
|
6770
|
+
python: "python",
|
|
6771
|
+
rails: "rails",
|
|
6772
|
+
go: "go",
|
|
6773
|
+
storybook: "storybook",
|
|
6774
|
+
productspec: "product-spec",
|
|
6775
|
+
prd: "product-spec",
|
|
6776
|
+
routemap: "route-map",
|
|
6777
|
+
routes: "route-map",
|
|
6778
|
+
ratelimit: "rate-limit"
|
|
6779
|
+
};
|
|
6780
|
+
var INLINE_ONLY_LOGO_KEYS = /* @__PURE__ */ new Set(["better-auth", "paddle", "polar"]);
|
|
6781
|
+
var PROVIDER_ASSET_FILES = {
|
|
6782
|
+
authjs: "provider-authjs.svg",
|
|
6783
|
+
logrocket: "provider-logrocket.svg",
|
|
6784
|
+
aws: "provider-aws.svg"
|
|
6785
|
+
};
|
|
6786
|
+
var REPORT_ASSET_PREFIX = "report/assets";
|
|
6787
|
+
function providerAssetUrl(key) {
|
|
6788
|
+
const file = PROVIDER_ASSET_FILES[key];
|
|
6789
|
+
return file ? `${REPORT_ASSET_PREFIX}/${file}` : void 0;
|
|
6790
|
+
}
|
|
6791
|
+
function providerAssetUrls() {
|
|
6792
|
+
return Object.fromEntries(
|
|
6793
|
+
Object.keys(PROVIDER_ASSET_FILES).map((key) => [key, providerAssetUrl(key)]).filter((entry) => Boolean(entry[1]))
|
|
6794
|
+
);
|
|
6795
|
+
}
|
|
6796
|
+
var PROVIDER_ICON_URLS = {
|
|
6797
|
+
supabase: "https://cdn.simpleicons.org/supabase/3ECF8E",
|
|
6798
|
+
clerk: "https://cdn.simpleicons.org/clerk/6C47FF",
|
|
6799
|
+
auth0: "https://cdn.simpleicons.org/auth0/EB5424",
|
|
6800
|
+
neon: "https://cdn.simpleicons.org/neon/00E599",
|
|
6801
|
+
planetscale: "https://cdn.simpleicons.org/planetscale/000000",
|
|
6802
|
+
mongodb: "https://cdn.simpleicons.org/mongodb/47A248",
|
|
6803
|
+
turso: "https://cdn.simpleicons.org/turso/4FF8D2",
|
|
6804
|
+
"lemon-squeezy": "https://cdn.simpleicons.org/lemonsqueezy/FFC233",
|
|
6805
|
+
stripe: "https://cdn.simpleicons.org/stripe/635BFF",
|
|
6806
|
+
github: "https://cdn.simpleicons.org/github/181717",
|
|
6807
|
+
gitlab: "https://cdn.simpleicons.org/gitlab/FC6D26",
|
|
6808
|
+
vercel: "https://cdn.simpleicons.org/vercel/000000",
|
|
6809
|
+
netlify: "https://cdn.simpleicons.org/netlify/00C7B7",
|
|
6810
|
+
render: "https://cdn.simpleicons.org/render/46E3B7",
|
|
6811
|
+
railway: "https://cdn.simpleicons.org/railway/0B0D0E",
|
|
6812
|
+
cloudflare: "https://cdn.simpleicons.org/cloudflare/F38020",
|
|
6813
|
+
sentry: "https://cdn.simpleicons.org/sentry/362D59",
|
|
6814
|
+
posthog: "https://cdn.simpleicons.org/posthog/F54E00",
|
|
6815
|
+
playwright: "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/playwright/playwright-original.svg",
|
|
6816
|
+
figma: "https://cdn.simpleicons.org/figma/F24E1E",
|
|
6817
|
+
storybook: "https://cdn.simpleicons.org/storybook/FF4785",
|
|
6818
|
+
react: "https://cdn.simpleicons.org/react/61DAFB",
|
|
6819
|
+
vue: "https://cdn.simpleicons.org/vuedotjs/4FC08D",
|
|
6820
|
+
svelte: "https://cdn.simpleicons.org/svelte/FF3E00",
|
|
6821
|
+
angular: "https://cdn.simpleicons.org/angular/DD0031",
|
|
6822
|
+
nodejs: "https://cdn.simpleicons.org/nodedotjs/5FA04E",
|
|
6823
|
+
python: "https://cdn.simpleicons.org/python/3776AB",
|
|
6824
|
+
rails: "https://cdn.simpleicons.org/rubyonrails/D30001",
|
|
6825
|
+
go: "https://cdn.simpleicons.org/go/00ADD8",
|
|
6826
|
+
"testing-library": "https://cdn.simpleicons.org/testinglibrary/E33332",
|
|
6827
|
+
vitest: "https://cdn.simpleicons.org/vitest/FCC72B"
|
|
6828
|
+
};
|
|
6829
|
+
var BRAND_LOGO_KEYS = /* @__PURE__ */ new Set([
|
|
6830
|
+
"auth0",
|
|
6831
|
+
"authjs",
|
|
6832
|
+
"aws",
|
|
6833
|
+
"better-auth",
|
|
6834
|
+
"clerk",
|
|
6835
|
+
"gitlab",
|
|
6836
|
+
"logrocket",
|
|
6837
|
+
"auth0",
|
|
6838
|
+
"paddle",
|
|
6839
|
+
"playwright",
|
|
6840
|
+
"polar",
|
|
6841
|
+
"posthog",
|
|
6842
|
+
"stripe",
|
|
6843
|
+
"vitest"
|
|
6844
|
+
]);
|
|
6845
|
+
function resolveLogoKey(raw, compact) {
|
|
6846
|
+
if (ALIASES[raw]) {
|
|
6847
|
+
return ALIASES[raw];
|
|
6848
|
+
}
|
|
6849
|
+
if (ALIASES[compact]) {
|
|
6850
|
+
return ALIASES[compact];
|
|
6851
|
+
}
|
|
6852
|
+
if (PROVIDER_LOGOS[compact]) {
|
|
6853
|
+
return compact;
|
|
6854
|
+
}
|
|
6855
|
+
const parts = raw.split("-").filter(Boolean);
|
|
6856
|
+
if (parts.length >= 2) {
|
|
6857
|
+
const first = parts[0];
|
|
6858
|
+
if (PROVIDER_LOGOS[first] || ALIASES[first]) {
|
|
6859
|
+
return ALIASES[first] ?? first;
|
|
6860
|
+
}
|
|
6861
|
+
const twoPart = parts.slice(0, 2).join("");
|
|
6862
|
+
if (PROVIDER_LOGOS[twoPart] || ALIASES[twoPart]) {
|
|
6863
|
+
return ALIASES[twoPart] ?? twoPart;
|
|
6864
|
+
}
|
|
6865
|
+
const twoHyphen = parts.slice(0, 2).join("-");
|
|
6866
|
+
if (ALIASES[twoHyphen]) {
|
|
6867
|
+
return ALIASES[twoHyphen];
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
return compact;
|
|
6871
|
+
}
|
|
6872
|
+
function normalizeProviderKey2(providerOrLabel) {
|
|
6873
|
+
if (!providerOrLabel) {
|
|
6874
|
+
return "";
|
|
6875
|
+
}
|
|
6876
|
+
const raw = String(providerOrLabel).trim().toLowerCase();
|
|
6877
|
+
const compact = raw.replace(/[^a-z0-9]+/g, "");
|
|
6878
|
+
return resolveLogoKey(raw, compact);
|
|
6879
|
+
}
|
|
6880
|
+
function resolveProviderLogoKey(...candidates) {
|
|
6881
|
+
for (const candidate of candidates) {
|
|
6882
|
+
const key = normalizeProviderKey2(candidate);
|
|
6883
|
+
if (key && PROVIDER_LOGOS[key]) {
|
|
6884
|
+
return key;
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6887
|
+
for (const candidate of candidates) {
|
|
6888
|
+
const key = normalizeProviderKey2(candidate);
|
|
6889
|
+
if (key) {
|
|
6890
|
+
return key;
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
return "";
|
|
6894
|
+
}
|
|
6895
|
+
function providerLogoClass(providerOrLabel, ...moreCandidates) {
|
|
6896
|
+
const key = resolveProviderLogoKey(providerOrLabel, ...moreCandidates) || normalizeProviderKey2(providerOrLabel);
|
|
6897
|
+
if (!key) {
|
|
6898
|
+
return "";
|
|
6899
|
+
}
|
|
6900
|
+
return ` provider-logo--${key}${BRAND_LOGO_KEYS.has(key) ? " provider-logo--brand" : ""}`;
|
|
6901
|
+
}
|
|
6902
|
+
function providerIconImgHtml(iconUrl, logoKey) {
|
|
6903
|
+
return `<img class="provider-logo__img" src="${iconUrl}" alt="" decoding="async" data-provider-logo-key="${logoKey}" />`;
|
|
6904
|
+
}
|
|
6905
|
+
function providerLogoHtml(providerOrLabel, labelFallback, ...moreCandidates) {
|
|
6906
|
+
const key = resolveProviderLogoKey(providerOrLabel, labelFallback, ...moreCandidates) || normalizeProviderKey2(providerOrLabel);
|
|
6907
|
+
const svg = key && PROVIDER_LOGOS[key];
|
|
6908
|
+
if (key && INLINE_ONLY_LOGO_KEYS.has(key) && svg) {
|
|
6909
|
+
return svg;
|
|
6910
|
+
}
|
|
6911
|
+
const assetUrl = key ? providerAssetUrl(key) : void 0;
|
|
6912
|
+
if (assetUrl) {
|
|
6913
|
+
return providerIconImgHtml(assetUrl, key);
|
|
6914
|
+
}
|
|
6915
|
+
const iconUrl = key && PROVIDER_ICON_URLS[key];
|
|
6916
|
+
if (iconUrl) {
|
|
6917
|
+
return providerIconImgHtml(iconUrl, key);
|
|
6918
|
+
}
|
|
6919
|
+
if (svg) {
|
|
6920
|
+
return svg;
|
|
6921
|
+
}
|
|
6922
|
+
const label = (labelFallback ?? providerOrLabel ?? "?").trim();
|
|
6923
|
+
const initials = label ? label.slice(0, 2).toUpperCase() : "?";
|
|
6924
|
+
return `<span aria-hidden="true">${initials}</span>`;
|
|
6925
|
+
}
|
|
6926
|
+
var PROVIDER_BENEFITS = {
|
|
6927
|
+
supabase: "Best when you want auth, data, and storage in one stack.",
|
|
6928
|
+
clerk: "Fastest managed auth path with clean session primitives.",
|
|
6929
|
+
authjs: "Best when you want framework-owned auth and full control.",
|
|
6930
|
+
neon: "Good serverless Postgres fit for Vercel-style apps.",
|
|
6931
|
+
planetscale: "Good fit for branching MySQL workflows.",
|
|
6932
|
+
mongodb: "Good when the app already models document data.",
|
|
6933
|
+
turso: "Good edge SQLite option for lightweight apps.",
|
|
6934
|
+
stripe: "Best supported global payment ecosystem.",
|
|
6935
|
+
paddle: "Handles merchant-of-record subscription operations.",
|
|
6936
|
+
polar: "MoR billing built for developers shipping SaaS.",
|
|
6937
|
+
"lemon-squeezy": "Sell digital products and subscriptions with less setup.",
|
|
6938
|
+
github: "Best for GitHub Actions CI and required PR checks.",
|
|
6939
|
+
gitlab: "Best for GitLab CI pipelines and merge gates.",
|
|
6940
|
+
vercel: "Best fit for preview deploys, envs, and domains.",
|
|
6941
|
+
netlify: "Good fit for static and serverless deploy workflows.",
|
|
6942
|
+
render: "Good fit for simple web services and background jobs.",
|
|
6943
|
+
railway: "Good fit for fast deploy loops and managed infra.",
|
|
6944
|
+
cloudflare: "Good fit for Pages, Workers, DNS, and edge stack.",
|
|
6945
|
+
aws: "Best when the app needs direct cloud infrastructure control.",
|
|
6946
|
+
sentry: "Best first choice for production errors and traces.",
|
|
6947
|
+
posthog: "Best for product analytics and funnel verification.",
|
|
6948
|
+
auth0: "Enterprise identity with rules, MFA, and social login.",
|
|
6949
|
+
"better-auth": "Type-safe auth you own in your codebase.",
|
|
6950
|
+
playwright: "Best for browser-flow verification and UI regression checks.",
|
|
6951
|
+
logrocket: "Best when session replay is the missing evidence.",
|
|
6952
|
+
"rate-limit": "Protects API routes from abuse and retry loops.",
|
|
6953
|
+
"bot-protection": "Adds bot screening before expensive flows.",
|
|
6954
|
+
"secrets-hygiene": "Hardens env handling and secret exposure risk.",
|
|
6955
|
+
figma: "Best for screen flows, handoff, and UX alignment.",
|
|
6956
|
+
storybook: "Best for proving component states before production.",
|
|
6957
|
+
react: "Best fit for common component UI and app-router projects.",
|
|
6958
|
+
vue: "Good fit for Vue or Nuxt product interfaces.",
|
|
6959
|
+
svelte: "Good fit for SvelteKit route-first apps.",
|
|
6960
|
+
angular: "Good fit for structured enterprise frontend workflows.",
|
|
6961
|
+
nodejs: "Best fit for JavaScript API routes and server behavior.",
|
|
6962
|
+
python: "Good fit for FastAPI-style APIs and typed validation.",
|
|
6963
|
+
rails: "Good fit for full-stack CRUD and convention-heavy APIs.",
|
|
6964
|
+
go: "Good fit for lean, fast HTTP services.",
|
|
6965
|
+
vitest: "Fast unit and integration tests for modern JS apps.",
|
|
6966
|
+
"testing-library": "User-centric component testing primitives."
|
|
6967
|
+
};
|
|
6968
|
+
function providerLogosPayloadJson() {
|
|
6969
|
+
return JSON.stringify({
|
|
6970
|
+
logos: PROVIDER_LOGOS,
|
|
6971
|
+
iconUrls: PROVIDER_ICON_URLS,
|
|
6972
|
+
assetUrls: providerAssetUrls(),
|
|
6973
|
+
inlineOnly: [...INLINE_ONLY_LOGO_KEYS],
|
|
6974
|
+
aliases: ALIASES,
|
|
6975
|
+
benefits: PROVIDER_BENEFITS,
|
|
6976
|
+
brandKeys: [...BRAND_LOGO_KEYS]
|
|
6977
|
+
}).replace(/</g, "\\u003c");
|
|
6978
|
+
}
|
|
6979
|
+
|
|
6980
|
+
// src/report/reportHtml.ts
|
|
6981
|
+
var GEIST_FONTS = "https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap";
|
|
6982
|
+
function escapeHtml(value) {
|
|
6983
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6984
|
+
}
|
|
6985
|
+
var CATEGORY_META = [
|
|
6986
|
+
{ key: "appFlow", label: "App Flow" },
|
|
6987
|
+
{ key: "frontend", label: "Frontend" },
|
|
6988
|
+
{ key: "backend", label: "Backend / API" },
|
|
6989
|
+
{ key: "auth", label: "Auth" },
|
|
6990
|
+
{ key: "database", label: "Database" },
|
|
6991
|
+
{ key: "payments", label: "Payments" },
|
|
6992
|
+
{ key: "deployment", label: "Deployment" },
|
|
6993
|
+
{ key: "monitoring", label: "Monitoring / Analytics" },
|
|
6994
|
+
{ key: "security", label: "Security" },
|
|
6995
|
+
{ key: "testing", label: "Testing" },
|
|
6996
|
+
{ key: "landing", label: "Landing / Onboarding" },
|
|
6997
|
+
{ key: "errorHandling", label: "Error Handling" }
|
|
6998
|
+
];
|
|
6999
|
+
function gapCountForArea(artifact, areaKey) {
|
|
7000
|
+
return artifact.gaps.filter((g2) => g2.primaryMapCategory === areaKey).length;
|
|
7001
|
+
}
|
|
7002
|
+
function defaultAreaKey(artifact) {
|
|
7003
|
+
const fromGap = artifact.gaps[0]?.primaryMapCategory;
|
|
7004
|
+
if (fromGap) {
|
|
7005
|
+
return fromGap;
|
|
7006
|
+
}
|
|
7007
|
+
const areas = artifact.missionGraph.areas ?? [];
|
|
7008
|
+
if (areas.some((a) => a.key === "frontend")) {
|
|
7009
|
+
return "frontend";
|
|
7010
|
+
}
|
|
7011
|
+
return areas[0]?.key ?? "frontend";
|
|
7012
|
+
}
|
|
7013
|
+
function buildAccountStripHtml(artifact) {
|
|
7014
|
+
if (!artifact.accountEmail) {
|
|
7015
|
+
return "";
|
|
7016
|
+
}
|
|
7017
|
+
const planLabel = artifact.plan === "pro" ? "Pro" : "Free";
|
|
7018
|
+
const usage = artifact.usageLine ? `<span class="account-bar__meta">${escapeHtml(artifact.usageLine)}</span>` : "";
|
|
7019
|
+
return `<div class="station-account-strip mc-session" aria-label="Account">
|
|
7020
|
+
<div class="account-bar">
|
|
7021
|
+
<div class="account-bar__main">
|
|
7022
|
+
<div class="account-bar__identity">
|
|
7023
|
+
<div class="account-bar__id-text">
|
|
7024
|
+
<span class="account-bar__email">${escapeHtml(artifact.accountEmail)}</span>
|
|
7025
|
+
${usage}
|
|
7026
|
+
</div>
|
|
7027
|
+
</div>
|
|
7028
|
+
<div class="account-bar__actions">
|
|
7029
|
+
<span class="account-bar__plan">${escapeHtml(planLabel)}</span>
|
|
7030
|
+
</div>
|
|
7031
|
+
</div>
|
|
7032
|
+
</div>
|
|
7033
|
+
</div>`;
|
|
7034
|
+
}
|
|
7035
|
+
function buildNodeHtml(artifact, meta, selectedAreaKey) {
|
|
7036
|
+
const area = (artifact.missionGraph.areas ?? []).find((a) => a.key === meta.key);
|
|
7037
|
+
const selectedOverride = artifact.selectedProviders?.[meta.key] ?? "";
|
|
7038
|
+
const mission = preferredMissionForArea(area, selectedOverride);
|
|
7039
|
+
const selectedProvider = selectedOverride || mission?.provider || mission?.providerLabel || "";
|
|
7040
|
+
const providerLabel3 = mission?.providerLabel ?? "Not selected";
|
|
7041
|
+
const readiness = mission?.readinessPercent ?? area?.readinessPercent ?? 0;
|
|
7042
|
+
const openChecks = openChecksForMission(mission);
|
|
7043
|
+
const modelGaps = gapCountForArea(artifact, meta.key);
|
|
7044
|
+
const stateClass = modelGaps > 0 ? " studio-node--critical" : openChecks > 0 ? " studio-node--warning" : area ? " studio-node--in-project" : "";
|
|
7045
|
+
const selectedClass = selectedAreaKey === meta.key ? " studio-node--selected" : "";
|
|
7046
|
+
const metaText = modelGaps > 0 ? `${modelGaps} stack fix${modelGaps === 1 ? "" : "es"}` : openChecks > 0 ? `${openChecks} stack fix${openChecks === 1 ? "" : "es"}` : `${readiness}% health`;
|
|
7047
|
+
const logoClass = providerLogoClass(mission?.provider, mission?.key, String(selectedProvider), providerLabel3);
|
|
7048
|
+
const logoInner = providerLogoHtml(
|
|
7049
|
+
mission?.provider,
|
|
7050
|
+
providerLabel3,
|
|
7051
|
+
mission?.key,
|
|
7052
|
+
String(selectedProvider)
|
|
7053
|
+
);
|
|
7054
|
+
const gapBadge = modelGaps > 0 ? `<span class="studio-node__raven-badge" role="presentation">Gap ${modelGaps}</span>` : "";
|
|
7055
|
+
return `<button type="button" class="studio-node studio-node--${escapeHtml(meta.key)} provider-logo${logoClass}${stateClass}${selectedClass}" data-area-key="${escapeHtml(meta.key)}" aria-label="${escapeHtml(meta.label)}, ${escapeHtml(providerLabel3)}, ${escapeHtml(metaText)}">
|
|
7056
|
+
<span class="studio-node__logo provider-logo${logoClass}" aria-hidden="true">${logoInner}</span>
|
|
7057
|
+
<span class="studio-node__title">${escapeHtml(meta.label)}</span>
|
|
7058
|
+
<span class="studio-node__provider">${escapeHtml(providerLabel3)}</span>
|
|
7059
|
+
<span class="studio-node__meta">${escapeHtml(metaText)}</span>
|
|
7060
|
+
${gapBadge}
|
|
7061
|
+
</button>`;
|
|
7062
|
+
}
|
|
7063
|
+
function generateReportHtml(artifact) {
|
|
7064
|
+
const hydrated = hydrateArtifactForReport(artifact);
|
|
7065
|
+
const dataJson = JSON.stringify(hydrated).replace(/</g, "\\u003c");
|
|
7066
|
+
const defaultKey = defaultAreaKey(hydrated);
|
|
7067
|
+
const nodesHtml = CATEGORY_META.map((meta) => buildNodeHtml(hydrated, meta, defaultKey)).join("\n");
|
|
7068
|
+
const accountStrip = buildAccountStripHtml(hydrated);
|
|
7069
|
+
const logosJson = providerLogosPayloadJson();
|
|
7070
|
+
const scannedLabel = escapeHtml(hydrated.scannedAt);
|
|
7071
|
+
return `<!DOCTYPE html>
|
|
7072
|
+
<html lang="en" class="cli-mission-report" data-surface="panel" data-skin="editorial">
|
|
7073
|
+
<head>
|
|
7074
|
+
<meta charset="utf-8" />
|
|
7075
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7076
|
+
<title>VibeRaven Mission Map</title>
|
|
7077
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
7078
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
7079
|
+
<link rel="stylesheet" href="${GEIST_FONTS}" />
|
|
7080
|
+
<link rel="stylesheet" href="report/station.css" />
|
|
7081
|
+
<link rel="stylesheet" href="report/report-cli.css" />
|
|
7082
|
+
</head>
|
|
7083
|
+
<body class="station-results-active">
|
|
7084
|
+
<div class="station-app">
|
|
7085
|
+
${accountStrip}
|
|
7086
|
+
<div class="mc-state mc-state--results" role="region" aria-label="Mission Map results">
|
|
7087
|
+
<div class="studio-shell" aria-label="VibeRaven Studio cockpit">
|
|
7088
|
+
<header class="studio-top-rail" aria-label="Studio status">
|
|
7089
|
+
<img class="studio-top-rail__logo" src="report/assets/viberaven-logo.png" alt="" width="32" height="32" />
|
|
7090
|
+
<span class="studio-top-rail__brand"><span>VIBERAVEN / MISSION MAP</span></span>
|
|
7091
|
+
<div class="studio-top-rail__build">Last scan \xB7 ${scannedLabel}</div>
|
|
7092
|
+
</header>
|
|
7093
|
+
<div class="studio-workspace">
|
|
7094
|
+
<main class="studio-map-canvas" aria-label="Interactive full-stack system map">
|
|
7095
|
+
<section class="studio-system-map" aria-label="Production system map">
|
|
7096
|
+
<div class="studio-node-layer">
|
|
7097
|
+
<div class="studio-connector-layer" aria-hidden="true"></div>
|
|
7098
|
+
<div class="studio-core-group">
|
|
7099
|
+
<div class="studio-core-node" aria-label="Production core score">
|
|
7100
|
+
<img class="studio-core-node__mark" src="report/assets/viberaven-logo.png" alt="VibeRaven" width="92" height="92" />
|
|
7101
|
+
<strong>${hydrated.productionCorePercent}%</strong>
|
|
7102
|
+
<small>Production core</small>
|
|
7103
|
+
</div>
|
|
7104
|
+
</div>
|
|
7105
|
+
${nodesHtml}
|
|
7106
|
+
</div>
|
|
7107
|
+
</section>
|
|
7108
|
+
</main>
|
|
7109
|
+
<aside class="studio-setup-panel" id="detail-panel" aria-live="polite"></aside>
|
|
7110
|
+
</div>
|
|
7111
|
+
</div>
|
|
7112
|
+
</div>
|
|
7113
|
+
</div>
|
|
7114
|
+
<script type="application/json" id="scan-data">${dataJson}</script>
|
|
7115
|
+
<script type="application/json" id="default-area-key">${JSON.stringify(defaultKey)}</script>
|
|
7116
|
+
<script type="application/json" id="provider-logos">${logosJson}</script>
|
|
7117
|
+
<script>${PANEL_CLIENT_SCRIPT}</script>
|
|
7118
|
+
</body>
|
|
7119
|
+
</html>`;
|
|
7120
|
+
}
|
|
7121
|
+
|
|
7122
|
+
// src/report/reportAssets.ts
|
|
7123
|
+
var import_node_fs4 = require("node:fs");
|
|
7124
|
+
var import_node_path4 = require("node:path");
|
|
7125
|
+
function getBundledReportAssetsDir() {
|
|
7126
|
+
const base = __dirname;
|
|
7127
|
+
const candidates = [
|
|
7128
|
+
(0, import_node_path4.join)(base, "report"),
|
|
7129
|
+
(0, import_node_path4.join)(base, "..", "assets", "report"),
|
|
7130
|
+
(0, import_node_path4.join)(base, "..", "..", "assets", "report")
|
|
7131
|
+
];
|
|
7132
|
+
for (const dir of candidates) {
|
|
7133
|
+
if ((0, import_node_fs4.existsSync)((0, import_node_path4.join)(dir, "station.css"))) {
|
|
7134
|
+
return dir;
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7137
|
+
throw new Error("Report assets missing. Run `npm run sync-report-assets` and `npm run build` in packages/cli.");
|
|
7138
|
+
}
|
|
7139
|
+
var REPORT_ASSET_FILES = [
|
|
7140
|
+
"station.css",
|
|
7141
|
+
"report-cli.css",
|
|
7142
|
+
"assets/viberaven-logo.png",
|
|
7143
|
+
"assets/provider-authjs.svg",
|
|
7144
|
+
"assets/provider-aws.svg",
|
|
7145
|
+
"assets/provider-logrocket.svg"
|
|
7146
|
+
];
|
|
7147
|
+
|
|
7148
|
+
// src/sanitizeArtifact.ts
|
|
7149
|
+
var INLINE_SECRET_PATTERNS = [
|
|
7150
|
+
/\b(sk_(?:live|test)_[A-Za-z0-9]{12,}|sk-proj-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{20,})\b/g,
|
|
7151
|
+
/\b(whsec_[A-Za-z0-9]{12,}|rk_(?:live|test)_[A-Za-z0-9]{12,})\b/g,
|
|
7152
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g
|
|
7153
|
+
];
|
|
7154
|
+
var SENSITIVE_ENV_KEYS = /^(?:OPENAI_API_KEY|OPENAI_KEY|ANTHROPIC_API_KEY|VIBERAVEN_ACCESS_TOKEN|VRAVEN_.*|SUPABASE_SERVICE_ROLE_KEY|.*_SECRET_KEY)$/i;
|
|
7155
|
+
function redactString(value) {
|
|
7156
|
+
let out = value;
|
|
7157
|
+
for (const pattern of INLINE_SECRET_PATTERNS) {
|
|
7158
|
+
out = out.replace(pattern, "[REDACTED_SECRET]");
|
|
7159
|
+
}
|
|
7160
|
+
return out.replace(
|
|
7161
|
+
/\b([A-Za-z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD|PRIVATE_KEY)[A-Za-z0-9_]*)\s*=\s*["']?[^"'\s;,]+["']?/gi,
|
|
7162
|
+
"$1=[REDACTED]"
|
|
7163
|
+
);
|
|
7164
|
+
}
|
|
7165
|
+
function redactUnknown(value) {
|
|
7166
|
+
if (typeof value === "string") {
|
|
7167
|
+
return redactString(value);
|
|
7168
|
+
}
|
|
7169
|
+
if (Array.isArray(value)) {
|
|
7170
|
+
return value.map(redactUnknown);
|
|
7171
|
+
}
|
|
7172
|
+
if (value && typeof value === "object") {
|
|
7173
|
+
const record = value;
|
|
7174
|
+
const next = {};
|
|
7175
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
7176
|
+
if (SENSITIVE_ENV_KEYS.test(key)) {
|
|
7177
|
+
next[key] = "[REDACTED]";
|
|
7178
|
+
} else {
|
|
7179
|
+
next[key] = redactUnknown(entry);
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
return next;
|
|
7183
|
+
}
|
|
7184
|
+
return value;
|
|
7185
|
+
}
|
|
7186
|
+
function sanitizeArtifactForDisk(artifact) {
|
|
7187
|
+
return redactUnknown(artifact);
|
|
7188
|
+
}
|
|
7189
|
+
|
|
7190
|
+
// src/artifacts.ts
|
|
7191
|
+
async function copyReportAssets(reportAssetsDir) {
|
|
7192
|
+
const sourceDir = getBundledReportAssetsDir();
|
|
7193
|
+
await (0, import_promises3.mkdir)((0, import_node_path5.join)(reportAssetsDir, "assets"), { recursive: true });
|
|
7194
|
+
for (const rel of REPORT_ASSET_FILES) {
|
|
7195
|
+
await (0, import_promises3.copyFile)((0, import_node_path5.join)(sourceDir, rel), (0, import_node_path5.join)(reportAssetsDir, rel));
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
7198
|
+
async function writeScanArtifacts(options) {
|
|
7199
|
+
const cwd = options.cwd ?? options.artifact.workspacePath;
|
|
7200
|
+
const dir = getProjectArtifactsDir(cwd);
|
|
7201
|
+
await (0, import_promises3.mkdir)(dir, { recursive: true });
|
|
7202
|
+
const jsonPath = (0, import_node_path5.join)(dir, "last-scan.json");
|
|
7203
|
+
const summaryPath = (0, import_node_path5.join)(dir, "agent-summary.md");
|
|
7204
|
+
const reportPath = (0, import_node_path5.join)(dir, "report.html");
|
|
7205
|
+
const reportAssetsDir = (0, import_node_path5.join)(dir, "report");
|
|
7206
|
+
const safe = sanitizeArtifactForDisk(options.artifact);
|
|
7207
|
+
const json = `${JSON.stringify(safe, null, 2)}
|
|
7208
|
+
`;
|
|
7209
|
+
const summary = generateAgentSummary(safe);
|
|
7210
|
+
const html = generateReportHtml(safe);
|
|
7211
|
+
await copyReportAssets(reportAssetsDir);
|
|
7212
|
+
await (0, import_promises3.writeFile)(jsonPath, json, "utf-8");
|
|
7213
|
+
await (0, import_promises3.writeFile)(summaryPath, summary, "utf-8");
|
|
7214
|
+
await (0, import_promises3.writeFile)(reportPath, html, "utf-8");
|
|
7215
|
+
return { dir, jsonPath, summaryPath, reportPath, reportAssetsDir };
|
|
7216
|
+
}
|
|
7217
|
+
|
|
7218
|
+
// src/tui/menu.ts
|
|
7219
|
+
var import_promises4 = require("node:fs/promises");
|
|
7220
|
+
var import_node_path6 = require("node:path");
|
|
7221
|
+
var SEVERITY_RANK2 = {
|
|
7222
|
+
critical: 0,
|
|
7223
|
+
warning: 1,
|
|
7224
|
+
info: 2
|
|
7225
|
+
};
|
|
7226
|
+
var ScanNotFoundError = class extends Error {
|
|
7227
|
+
constructor(message = "No scan found. Run a scan first.") {
|
|
7228
|
+
super(message);
|
|
7229
|
+
this.name = "ScanNotFoundError";
|
|
7230
|
+
}
|
|
7231
|
+
};
|
|
7232
|
+
function isScanNotFoundError(error) {
|
|
7233
|
+
return error instanceof ScanNotFoundError;
|
|
7234
|
+
}
|
|
7235
|
+
function needsScanMessage(startDir) {
|
|
7236
|
+
const cwd = startDir ?? process.cwd();
|
|
7237
|
+
return [
|
|
7238
|
+
"No CLI scan found for this folder.",
|
|
7239
|
+
`Looking from: ${cwd}`,
|
|
7240
|
+
'Choose "Scan project", or run the menu from your repo root (where .viberaven/ lives).',
|
|
7241
|
+
"VS Code extension scans stay inside the editor \u2014 run a CLI scan once to create .viberaven/ on disk."
|
|
7242
|
+
].join("\n");
|
|
7243
|
+
}
|
|
7244
|
+
function sortGapsByPriority(gaps) {
|
|
7245
|
+
return [...gaps].sort(
|
|
7246
|
+
(a, b3) => SEVERITY_RANK2[a.severity] - SEVERITY_RANK2[b3.severity] || a.title.localeCompare(b3.title)
|
|
7247
|
+
);
|
|
7248
|
+
}
|
|
7249
|
+
function pickGap(artifact, options = {}) {
|
|
7250
|
+
if (options.gapId) {
|
|
7251
|
+
return artifact.gaps.find((g2) => g2.id === options.gapId);
|
|
7252
|
+
}
|
|
7253
|
+
if (options.provider) {
|
|
7254
|
+
const key = options.provider.toLowerCase();
|
|
7255
|
+
return artifact.gaps.find(
|
|
7256
|
+
(g2) => g2.primaryMapCategory === key || g2.title.toLowerCase().includes(key) || g2.id.toLowerCase().includes(key)
|
|
7257
|
+
);
|
|
7258
|
+
}
|
|
7259
|
+
if (options.area) {
|
|
7260
|
+
return artifact.gaps.find((g2) => g2.primaryMapCategory === options.area);
|
|
7261
|
+
}
|
|
7262
|
+
return sortGapsByPriority(artifact.gaps)[0];
|
|
7263
|
+
}
|
|
7264
|
+
function formatTopGapsList(artifact, limit = 10) {
|
|
7265
|
+
const sorted = sortGapsByPriority(artifact.gaps);
|
|
7266
|
+
if (sorted.length === 0) {
|
|
7267
|
+
return "No gaps found \u2014 production core looks solid.";
|
|
7268
|
+
}
|
|
7269
|
+
return sorted.slice(0, limit).map((gap, index) => {
|
|
7270
|
+
const severity = gap.severity.toUpperCase().padEnd(8);
|
|
7271
|
+
const area = gap.primaryMapCategory.padEnd(12);
|
|
7272
|
+
return `${index + 1}. [${severity}] ${area} ${gap.title}`;
|
|
7273
|
+
}).join("\n");
|
|
7274
|
+
}
|
|
7275
|
+
async function loadLastArtifact(startDir) {
|
|
7276
|
+
const workspace = await findArtifactsWorkspace(startDir);
|
|
7277
|
+
if (!workspace) {
|
|
7278
|
+
throw new ScanNotFoundError(needsScanMessage(startDir));
|
|
7279
|
+
}
|
|
7280
|
+
const path = (0, import_node_path6.join)(getProjectArtifactsDir(workspace), "last-scan.json");
|
|
7281
|
+
try {
|
|
7282
|
+
const raw = await (0, import_promises4.readFile)(path, "utf-8");
|
|
7283
|
+
return JSON.parse(raw);
|
|
7284
|
+
} catch {
|
|
7285
|
+
throw new ScanNotFoundError(needsScanMessage(startDir));
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
7288
|
+
|
|
7289
|
+
// src/report/refreshReport.ts
|
|
7290
|
+
async function refreshReportFromDisk(startDir) {
|
|
7291
|
+
const workspace = await findArtifactsWorkspace(startDir);
|
|
7292
|
+
if (!workspace) {
|
|
7293
|
+
throw new ScanNotFoundError();
|
|
7294
|
+
}
|
|
7295
|
+
const artifact = await loadLastArtifact(startDir);
|
|
7296
|
+
return writeScanArtifacts({ artifact, cwd: workspace });
|
|
7297
|
+
}
|
|
7298
|
+
|
|
7299
|
+
// src/openBrowser.ts
|
|
7300
|
+
var import_node_child_process = require("node:child_process");
|
|
7301
|
+
async function openPathInBrowser(filePath) {
|
|
7302
|
+
const absolute = filePath;
|
|
7303
|
+
let command;
|
|
7304
|
+
let args;
|
|
7305
|
+
if (process.platform === "win32") {
|
|
7306
|
+
command = "cmd";
|
|
7307
|
+
args = ["/c", "start", "", absolute];
|
|
7308
|
+
} else if (process.platform === "darwin") {
|
|
7309
|
+
command = "open";
|
|
7310
|
+
args = [absolute];
|
|
7311
|
+
} else {
|
|
7312
|
+
command = "xdg-open";
|
|
7313
|
+
args = [absolute];
|
|
7314
|
+
}
|
|
7315
|
+
await new Promise((resolve3, reject) => {
|
|
7316
|
+
const child = (0, import_node_child_process.spawn)(command, args, { stdio: "ignore", shell: process.platform === "win32" });
|
|
7317
|
+
child.on("error", reject);
|
|
7318
|
+
child.on("exit", (code) => {
|
|
7319
|
+
if (code === 0) {
|
|
7320
|
+
resolve3();
|
|
7321
|
+
} else {
|
|
7322
|
+
reject(new Error(`Could not open browser (exit ${code ?? "unknown"}). Open manually: ${absolute}`));
|
|
7323
|
+
}
|
|
7324
|
+
});
|
|
7325
|
+
});
|
|
7326
|
+
}
|
|
7327
|
+
|
|
7328
|
+
// src/clipboard.ts
|
|
7329
|
+
var import_node_child_process2 = require("node:child_process");
|
|
7330
|
+
async function copyToClipboard(text) {
|
|
7331
|
+
if (!text) {
|
|
7332
|
+
throw new Error("Nothing to copy.");
|
|
7333
|
+
}
|
|
7334
|
+
if (process.platform === "win32") {
|
|
7335
|
+
await pipeToCommand("clip", text);
|
|
7336
|
+
return;
|
|
7337
|
+
}
|
|
7338
|
+
if (process.platform === "darwin") {
|
|
7339
|
+
await pipeToCommand("pbcopy", text);
|
|
7340
|
+
return;
|
|
7341
|
+
}
|
|
7342
|
+
try {
|
|
7343
|
+
await pipeToCommand("wl-copy", text);
|
|
7344
|
+
return;
|
|
7345
|
+
} catch {
|
|
7346
|
+
}
|
|
7347
|
+
try {
|
|
7348
|
+
await pipeToCommand("xclip", text, ["-selection", "clipboard"]);
|
|
7349
|
+
return;
|
|
7350
|
+
} catch {
|
|
7351
|
+
throw new Error("Clipboard unavailable. Install wl-clipboard or xclip, or copy from the terminal output.");
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
function pipeToCommand(command, text, extraArgs = []) {
|
|
7355
|
+
return new Promise((resolve3, reject) => {
|
|
7356
|
+
const child = (0, import_node_child_process2.spawn)(command, extraArgs, { stdio: ["pipe", "ignore", "ignore"] });
|
|
7357
|
+
child.on("error", reject);
|
|
7358
|
+
child.on("close", (code) => {
|
|
7359
|
+
if (code === 0) {
|
|
7360
|
+
resolve3();
|
|
7361
|
+
} else {
|
|
7362
|
+
reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
|
|
7363
|
+
}
|
|
7364
|
+
});
|
|
7365
|
+
child.stdin?.write(text, "utf8", (error) => {
|
|
7366
|
+
if (error) {
|
|
7367
|
+
reject(error);
|
|
7368
|
+
return;
|
|
7369
|
+
}
|
|
7370
|
+
child.stdin?.end();
|
|
7371
|
+
});
|
|
7372
|
+
});
|
|
7373
|
+
}
|
|
7374
|
+
|
|
7375
|
+
// src/terminalSummary.ts
|
|
7376
|
+
var import_picocolors = __toESM(require_picocolors());
|
|
7377
|
+
function boxLine(content, width) {
|
|
7378
|
+
const inner = content.length > width - 4 ? content.slice(0, width - 7) + "\u2026" : content;
|
|
7379
|
+
const padding = " ".repeat(Math.max(0, width - inner.length - 4));
|
|
7380
|
+
return `${import_picocolors.default.dim("\u2502")} ${inner}${padding} ${import_picocolors.default.dim("\u2502")}`;
|
|
7381
|
+
}
|
|
7382
|
+
function readinessColor(percent) {
|
|
7383
|
+
if (percent >= 70) {
|
|
7384
|
+
return import_picocolors.default.green;
|
|
7385
|
+
}
|
|
7386
|
+
if (percent >= 40) {
|
|
7387
|
+
return import_picocolors.default.yellow;
|
|
7388
|
+
}
|
|
7389
|
+
return import_picocolors.default.red;
|
|
7390
|
+
}
|
|
7391
|
+
function gapTagColor(modelGaps, open) {
|
|
7392
|
+
if (modelGaps > 0) {
|
|
7393
|
+
return import_picocolors.default.red;
|
|
7394
|
+
}
|
|
7395
|
+
if (open > 0) {
|
|
7396
|
+
return import_picocolors.default.yellow;
|
|
7397
|
+
}
|
|
7398
|
+
return import_picocolors.default.dim;
|
|
7399
|
+
}
|
|
7400
|
+
function printScanSummary(artifact, paths) {
|
|
7401
|
+
const pct = artifact.productionCorePercent;
|
|
7402
|
+
const gapCount = artifact.gaps.length;
|
|
7403
|
+
const pctColor = readinessColor(pct);
|
|
7404
|
+
const headline = `${import_picocolors.default.bold("VibeRaven")} \xB7 ${pctColor(`Production core ${pct}%`)} \xB7 ${gapCount} gap(s)`;
|
|
7405
|
+
const subline = `Score ${artifact.score} \xB7 ${artifact.scoreLabel}`;
|
|
7406
|
+
const width = Math.max(headline.length, subline.length, 44) + 4;
|
|
7407
|
+
console.log("");
|
|
7408
|
+
console.log(import_picocolors.default.dim("\u250C") + import_picocolors.default.dim("\u2500".repeat(width - 2)) + import_picocolors.default.dim("\u2510"));
|
|
7409
|
+
console.log(boxLine(headline, width));
|
|
7410
|
+
console.log(boxLine(subline, width));
|
|
7411
|
+
console.log(import_picocolors.default.dim("\u2514") + import_picocolors.default.dim("\u2500".repeat(width - 2)) + import_picocolors.default.dim("\u2518"));
|
|
7412
|
+
console.log("");
|
|
7413
|
+
for (const area of artifact.missionGraph.areas ?? []) {
|
|
7414
|
+
const mission = preferredMissionForArea(area, artifact.selectedProviders?.[area.key] ?? "");
|
|
7415
|
+
if (!mission) {
|
|
7416
|
+
continue;
|
|
7417
|
+
}
|
|
7418
|
+
const open = openChecksForMission(mission);
|
|
7419
|
+
const modelGaps = artifact.gaps.filter((g2) => g2.primaryMapCategory === area.key).length;
|
|
7420
|
+
const tag = modelGaps > 0 ? ` GAP ${modelGaps}` : open > 0 ? ` ${open} fix` : "";
|
|
7421
|
+
const tagColored = tag ? gapTagColor(modelGaps, open)(tag) : "";
|
|
7422
|
+
const label = area.label.padEnd(18);
|
|
7423
|
+
const provider2 = mission.providerLabel.padEnd(14);
|
|
7424
|
+
const readiness = readinessColor(mission.readinessPercent)(`${mission.readinessPercent}%`);
|
|
7425
|
+
console.log(` ${import_picocolors.default.dim(label)} ${provider2} ${readiness}${tagColored}`);
|
|
7426
|
+
}
|
|
7427
|
+
console.log("");
|
|
7428
|
+
console.log(import_picocolors.default.bold("Artifacts:"));
|
|
7429
|
+
console.log(import_picocolors.default.dim(` ${paths.reportPath}`));
|
|
7430
|
+
console.log(import_picocolors.default.dim(` ${paths.jsonPath}`));
|
|
7431
|
+
console.log(import_picocolors.default.dim(` ${paths.summaryPath}`));
|
|
7432
|
+
console.log("");
|
|
7433
|
+
console.log(import_picocolors.default.dim("Press Enter in `viberaven` menu to rescan \xB7 `viberaven prompt` for top gap"));
|
|
7434
|
+
console.log(import_picocolors.default.dim("Agents: read .viberaven/agent-summary.md"));
|
|
7435
|
+
console.log("");
|
|
7436
|
+
}
|
|
7437
|
+
|
|
7438
|
+
// src/tui/runInteractive.ts
|
|
7439
|
+
var import_node_path7 = require("node:path");
|
|
7440
|
+
|
|
7441
|
+
// node_modules/@clack/core/dist/index.mjs
|
|
7442
|
+
var import_sisteransi = __toESM(require_src(), 1);
|
|
7443
|
+
var import_node_process = require("node:process");
|
|
7444
|
+
var g = __toESM(require("node:readline"), 1);
|
|
7445
|
+
var import_node_readline = __toESM(require("node:readline"), 1);
|
|
7446
|
+
var import_node_stream = require("node:stream");
|
|
7447
|
+
function DD({ onlyFirst: e2 = false } = {}) {
|
|
7448
|
+
const t = ["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))", "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"].join("|");
|
|
7449
|
+
return new RegExp(t, e2 ? void 0 : "g");
|
|
7450
|
+
}
|
|
7451
|
+
var uD = DD();
|
|
7452
|
+
function P(e2) {
|
|
7453
|
+
if (typeof e2 != "string") throw new TypeError(`Expected a \`string\`, got \`${typeof e2}\``);
|
|
7454
|
+
return e2.replace(uD, "");
|
|
7455
|
+
}
|
|
7456
|
+
function L(e2) {
|
|
7457
|
+
return e2 && e2.__esModule && Object.prototype.hasOwnProperty.call(e2, "default") ? e2.default : e2;
|
|
7458
|
+
}
|
|
7459
|
+
var W = { exports: {} };
|
|
7460
|
+
(function(e2) {
|
|
7461
|
+
var u2 = {};
|
|
7462
|
+
e2.exports = u2, u2.eastAsianWidth = function(F2) {
|
|
7463
|
+
var s = F2.charCodeAt(0), i = F2.length == 2 ? F2.charCodeAt(1) : 0, D2 = s;
|
|
7464
|
+
return 55296 <= s && s <= 56319 && 56320 <= i && i <= 57343 && (s &= 1023, i &= 1023, D2 = s << 10 | i, D2 += 65536), D2 == 12288 || 65281 <= D2 && D2 <= 65376 || 65504 <= D2 && D2 <= 65510 ? "F" : D2 == 8361 || 65377 <= D2 && D2 <= 65470 || 65474 <= D2 && D2 <= 65479 || 65482 <= D2 && D2 <= 65487 || 65490 <= D2 && D2 <= 65495 || 65498 <= D2 && D2 <= 65500 || 65512 <= D2 && D2 <= 65518 ? "H" : 4352 <= D2 && D2 <= 4447 || 4515 <= D2 && D2 <= 4519 || 4602 <= D2 && D2 <= 4607 || 9001 <= D2 && D2 <= 9002 || 11904 <= D2 && D2 <= 11929 || 11931 <= D2 && D2 <= 12019 || 12032 <= D2 && D2 <= 12245 || 12272 <= D2 && D2 <= 12283 || 12289 <= D2 && D2 <= 12350 || 12353 <= D2 && D2 <= 12438 || 12441 <= D2 && D2 <= 12543 || 12549 <= D2 && D2 <= 12589 || 12593 <= D2 && D2 <= 12686 || 12688 <= D2 && D2 <= 12730 || 12736 <= D2 && D2 <= 12771 || 12784 <= D2 && D2 <= 12830 || 12832 <= D2 && D2 <= 12871 || 12880 <= D2 && D2 <= 13054 || 13056 <= D2 && D2 <= 19903 || 19968 <= D2 && D2 <= 42124 || 42128 <= D2 && D2 <= 42182 || 43360 <= D2 && D2 <= 43388 || 44032 <= D2 && D2 <= 55203 || 55216 <= D2 && D2 <= 55238 || 55243 <= D2 && D2 <= 55291 || 63744 <= D2 && D2 <= 64255 || 65040 <= D2 && D2 <= 65049 || 65072 <= D2 && D2 <= 65106 || 65108 <= D2 && D2 <= 65126 || 65128 <= D2 && D2 <= 65131 || 110592 <= D2 && D2 <= 110593 || 127488 <= D2 && D2 <= 127490 || 127504 <= D2 && D2 <= 127546 || 127552 <= D2 && D2 <= 127560 || 127568 <= D2 && D2 <= 127569 || 131072 <= D2 && D2 <= 194367 || 177984 <= D2 && D2 <= 196605 || 196608 <= D2 && D2 <= 262141 ? "W" : 32 <= D2 && D2 <= 126 || 162 <= D2 && D2 <= 163 || 165 <= D2 && D2 <= 166 || D2 == 172 || D2 == 175 || 10214 <= D2 && D2 <= 10221 || 10629 <= D2 && D2 <= 10630 ? "Na" : D2 == 161 || D2 == 164 || 167 <= D2 && D2 <= 168 || D2 == 170 || 173 <= D2 && D2 <= 174 || 176 <= D2 && D2 <= 180 || 182 <= D2 && D2 <= 186 || 188 <= D2 && D2 <= 191 || D2 == 198 || D2 == 208 || 215 <= D2 && D2 <= 216 || 222 <= D2 && D2 <= 225 || D2 == 230 || 232 <= D2 && D2 <= 234 || 236 <= D2 && D2 <= 237 || D2 == 240 || 242 <= D2 && D2 <= 243 || 247 <= D2 && D2 <= 250 || D2 == 252 || D2 == 254 || D2 == 257 || D2 == 273 || D2 == 275 || D2 == 283 || 294 <= D2 && D2 <= 295 || D2 == 299 || 305 <= D2 && D2 <= 307 || D2 == 312 || 319 <= D2 && D2 <= 322 || D2 == 324 || 328 <= D2 && D2 <= 331 || D2 == 333 || 338 <= D2 && D2 <= 339 || 358 <= D2 && D2 <= 359 || D2 == 363 || D2 == 462 || D2 == 464 || D2 == 466 || D2 == 468 || D2 == 470 || D2 == 472 || D2 == 474 || D2 == 476 || D2 == 593 || D2 == 609 || D2 == 708 || D2 == 711 || 713 <= D2 && D2 <= 715 || D2 == 717 || D2 == 720 || 728 <= D2 && D2 <= 731 || D2 == 733 || D2 == 735 || 768 <= D2 && D2 <= 879 || 913 <= D2 && D2 <= 929 || 931 <= D2 && D2 <= 937 || 945 <= D2 && D2 <= 961 || 963 <= D2 && D2 <= 969 || D2 == 1025 || 1040 <= D2 && D2 <= 1103 || D2 == 1105 || D2 == 8208 || 8211 <= D2 && D2 <= 8214 || 8216 <= D2 && D2 <= 8217 || 8220 <= D2 && D2 <= 8221 || 8224 <= D2 && D2 <= 8226 || 8228 <= D2 && D2 <= 8231 || D2 == 8240 || 8242 <= D2 && D2 <= 8243 || D2 == 8245 || D2 == 8251 || D2 == 8254 || D2 == 8308 || D2 == 8319 || 8321 <= D2 && D2 <= 8324 || D2 == 8364 || D2 == 8451 || D2 == 8453 || D2 == 8457 || D2 == 8467 || D2 == 8470 || 8481 <= D2 && D2 <= 8482 || D2 == 8486 || D2 == 8491 || 8531 <= D2 && D2 <= 8532 || 8539 <= D2 && D2 <= 8542 || 8544 <= D2 && D2 <= 8555 || 8560 <= D2 && D2 <= 8569 || D2 == 8585 || 8592 <= D2 && D2 <= 8601 || 8632 <= D2 && D2 <= 8633 || D2 == 8658 || D2 == 8660 || D2 == 8679 || D2 == 8704 || 8706 <= D2 && D2 <= 8707 || 8711 <= D2 && D2 <= 8712 || D2 == 8715 || D2 == 8719 || D2 == 8721 || D2 == 8725 || D2 == 8730 || 8733 <= D2 && D2 <= 8736 || D2 == 8739 || D2 == 8741 || 8743 <= D2 && D2 <= 8748 || D2 == 8750 || 8756 <= D2 && D2 <= 8759 || 8764 <= D2 && D2 <= 8765 || D2 == 8776 || D2 == 8780 || D2 == 8786 || 8800 <= D2 && D2 <= 8801 || 8804 <= D2 && D2 <= 8807 || 8810 <= D2 && D2 <= 8811 || 8814 <= D2 && D2 <= 8815 || 8834 <= D2 && D2 <= 8835 || 8838 <= D2 && D2 <= 8839 || D2 == 8853 || D2 == 8857 || D2 == 8869 || D2 == 8895 || D2 == 8978 || 9312 <= D2 && D2 <= 9449 || 9451 <= D2 && D2 <= 9547 || 9552 <= D2 && D2 <= 9587 || 9600 <= D2 && D2 <= 9615 || 9618 <= D2 && D2 <= 9621 || 9632 <= D2 && D2 <= 9633 || 9635 <= D2 && D2 <= 9641 || 9650 <= D2 && D2 <= 9651 || 9654 <= D2 && D2 <= 9655 || 9660 <= D2 && D2 <= 9661 || 9664 <= D2 && D2 <= 9665 || 9670 <= D2 && D2 <= 9672 || D2 == 9675 || 9678 <= D2 && D2 <= 9681 || 9698 <= D2 && D2 <= 9701 || D2 == 9711 || 9733 <= D2 && D2 <= 9734 || D2 == 9737 || 9742 <= D2 && D2 <= 9743 || 9748 <= D2 && D2 <= 9749 || D2 == 9756 || D2 == 9758 || D2 == 9792 || D2 == 9794 || 9824 <= D2 && D2 <= 9825 || 9827 <= D2 && D2 <= 9829 || 9831 <= D2 && D2 <= 9834 || 9836 <= D2 && D2 <= 9837 || D2 == 9839 || 9886 <= D2 && D2 <= 9887 || 9918 <= D2 && D2 <= 9919 || 9924 <= D2 && D2 <= 9933 || 9935 <= D2 && D2 <= 9953 || D2 == 9955 || 9960 <= D2 && D2 <= 9983 || D2 == 10045 || D2 == 10071 || 10102 <= D2 && D2 <= 10111 || 11093 <= D2 && D2 <= 11097 || 12872 <= D2 && D2 <= 12879 || 57344 <= D2 && D2 <= 63743 || 65024 <= D2 && D2 <= 65039 || D2 == 65533 || 127232 <= D2 && D2 <= 127242 || 127248 <= D2 && D2 <= 127277 || 127280 <= D2 && D2 <= 127337 || 127344 <= D2 && D2 <= 127386 || 917760 <= D2 && D2 <= 917999 || 983040 <= D2 && D2 <= 1048573 || 1048576 <= D2 && D2 <= 1114109 ? "A" : "N";
|
|
7465
|
+
}, u2.characterLength = function(F2) {
|
|
7466
|
+
var s = this.eastAsianWidth(F2);
|
|
7467
|
+
return s == "F" || s == "W" || s == "A" ? 2 : 1;
|
|
7468
|
+
};
|
|
7469
|
+
function t(F2) {
|
|
7470
|
+
return F2.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
|
|
7471
|
+
}
|
|
7472
|
+
u2.length = function(F2) {
|
|
7473
|
+
for (var s = t(F2), i = 0, D2 = 0; D2 < s.length; D2++) i = i + this.characterLength(s[D2]);
|
|
7474
|
+
return i;
|
|
7475
|
+
}, u2.slice = function(F2, s, i) {
|
|
7476
|
+
textLen = u2.length(F2), s = s || 0, i = i || 1, s < 0 && (s = textLen + s), i < 0 && (i = textLen + i);
|
|
7477
|
+
for (var D2 = "", r = 0, n = t(F2), E = 0; E < n.length; E++) {
|
|
7478
|
+
var a = n[E], o2 = u2.length(a);
|
|
7479
|
+
if (r >= s - (o2 == 2 ? 1 : 0)) if (r + o2 <= i) D2 += a;
|
|
7480
|
+
else break;
|
|
7481
|
+
r += o2;
|
|
7482
|
+
}
|
|
7483
|
+
return D2;
|
|
7484
|
+
};
|
|
7485
|
+
})(W);
|
|
7486
|
+
var tD = W.exports;
|
|
7487
|
+
var eD = L(tD);
|
|
7488
|
+
var FD = function() {
|
|
7489
|
+
return /\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|(?:\uD83E\uDDD1\uD83C\uDFFF\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFC-\uDFFF])|\uD83D\uDC68(?:\uD83C\uDFFB(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|[\u2695\u2696\u2708]\uFE0F|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))?|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])\uFE0F|\u200D(?:(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D[\uDC66\uDC67])|\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC)?|(?:\uD83D\uDC69(?:\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC69(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83E\uDDD1(?:\u200D(?:\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDE36\u200D\uD83C\uDF2B|\uD83C\uDFF3\uFE0F\u200D\u26A7|\uD83D\uDC3B\u200D\u2744|(?:(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\uD83C\uDFF4\u200D\u2620|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])\u200D[\u2640\u2642]|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u2600-\u2604\u260E\u2611\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26B0\u26B1\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0\u26F1\u26F4\u26F7\u26F8\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u3030\u303D\u3297\u3299]|\uD83C[\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]|\uD83D[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3])\uFE0F|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDE35\u200D\uD83D\uDCAB|\uD83D\uDE2E\u200D\uD83D\uDCA8|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83E\uDDD1(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83D\uDC69(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83D\uDC08\u200D\u2B1B|\u2764\uFE0F\u200D(?:\uD83D\uDD25|\uD83E\uDE79)|\uD83D\uDC41\uFE0F|\uD83C\uDFF3\uFE0F|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|[#\*0-9]\uFE0F\u20E3|\u2764\uFE0F|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|\uD83C\uDFF4|(?:[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270C\u270D]|\uD83D[\uDD74\uDD90])(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC08\uDC15\uDC3B\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE2E\uDE35\uDE36\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5]|\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD]|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF]|[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0D\uDD0E\uDD10-\uDD17\uDD1D\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78\uDD7A-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCB\uDDD0\uDDE0-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6]|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDD77\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g;
|
|
7490
|
+
};
|
|
7491
|
+
var sD = L(FD);
|
|
7492
|
+
function p(e2, u2 = {}) {
|
|
7493
|
+
if (typeof e2 != "string" || e2.length === 0 || (u2 = { ambiguousIsNarrow: true, ...u2 }, e2 = P(e2), e2.length === 0)) return 0;
|
|
7494
|
+
e2 = e2.replace(sD(), " ");
|
|
7495
|
+
const t = u2.ambiguousIsNarrow ? 1 : 2;
|
|
7496
|
+
let F2 = 0;
|
|
7497
|
+
for (const s of e2) {
|
|
7498
|
+
const i = s.codePointAt(0);
|
|
7499
|
+
if (i <= 31 || i >= 127 && i <= 159 || i >= 768 && i <= 879) continue;
|
|
7500
|
+
switch (eD.eastAsianWidth(s)) {
|
|
7501
|
+
case "F":
|
|
7502
|
+
case "W":
|
|
7503
|
+
F2 += 2;
|
|
7504
|
+
break;
|
|
7505
|
+
case "A":
|
|
7506
|
+
F2 += t;
|
|
7507
|
+
break;
|
|
7508
|
+
default:
|
|
7509
|
+
F2 += 1;
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
return F2;
|
|
7513
|
+
}
|
|
7514
|
+
var w = 10;
|
|
7515
|
+
var N = (e2 = 0) => (u2) => `\x1B[${u2 + e2}m`;
|
|
7516
|
+
var I = (e2 = 0) => (u2) => `\x1B[${38 + e2};5;${u2}m`;
|
|
7517
|
+
var R = (e2 = 0) => (u2, t, F2) => `\x1B[${38 + e2};2;${u2};${t};${F2}m`;
|
|
7518
|
+
var C = { modifier: { reset: [0, 0], bold: [1, 22], dim: [2, 22], italic: [3, 23], underline: [4, 24], overline: [53, 55], inverse: [7, 27], hidden: [8, 28], strikethrough: [9, 29] }, color: { black: [30, 39], red: [31, 39], green: [32, 39], yellow: [33, 39], blue: [34, 39], magenta: [35, 39], cyan: [36, 39], white: [37, 39], blackBright: [90, 39], gray: [90, 39], grey: [90, 39], redBright: [91, 39], greenBright: [92, 39], yellowBright: [93, 39], blueBright: [94, 39], magentaBright: [95, 39], cyanBright: [96, 39], whiteBright: [97, 39] }, bgColor: { bgBlack: [40, 49], bgRed: [41, 49], bgGreen: [42, 49], bgYellow: [43, 49], bgBlue: [44, 49], bgMagenta: [45, 49], bgCyan: [46, 49], bgWhite: [47, 49], bgBlackBright: [100, 49], bgGray: [100, 49], bgGrey: [100, 49], bgRedBright: [101, 49], bgGreenBright: [102, 49], bgYellowBright: [103, 49], bgBlueBright: [104, 49], bgMagentaBright: [105, 49], bgCyanBright: [106, 49], bgWhiteBright: [107, 49] } };
|
|
7519
|
+
Object.keys(C.modifier);
|
|
7520
|
+
var iD = Object.keys(C.color);
|
|
7521
|
+
var rD = Object.keys(C.bgColor);
|
|
7522
|
+
[...iD, ...rD];
|
|
7523
|
+
function CD() {
|
|
7524
|
+
const e2 = /* @__PURE__ */ new Map();
|
|
7525
|
+
for (const [u2, t] of Object.entries(C)) {
|
|
7526
|
+
for (const [F2, s] of Object.entries(t)) C[F2] = { open: `\x1B[${s[0]}m`, close: `\x1B[${s[1]}m` }, t[F2] = C[F2], e2.set(s[0], s[1]);
|
|
7527
|
+
Object.defineProperty(C, u2, { value: t, enumerable: false });
|
|
7528
|
+
}
|
|
7529
|
+
return Object.defineProperty(C, "codes", { value: e2, enumerable: false }), C.color.close = "\x1B[39m", C.bgColor.close = "\x1B[49m", C.color.ansi = N(), C.color.ansi256 = I(), C.color.ansi16m = R(), C.bgColor.ansi = N(w), C.bgColor.ansi256 = I(w), C.bgColor.ansi16m = R(w), Object.defineProperties(C, { rgbToAnsi256: { value: (u2, t, F2) => u2 === t && t === F2 ? u2 < 8 ? 16 : u2 > 248 ? 231 : Math.round((u2 - 8) / 247 * 24) + 232 : 16 + 36 * Math.round(u2 / 255 * 5) + 6 * Math.round(t / 255 * 5) + Math.round(F2 / 255 * 5), enumerable: false }, hexToRgb: { value: (u2) => {
|
|
7530
|
+
const t = /[a-f\d]{6}|[a-f\d]{3}/i.exec(u2.toString(16));
|
|
7531
|
+
if (!t) return [0, 0, 0];
|
|
7532
|
+
let [F2] = t;
|
|
7533
|
+
F2.length === 3 && (F2 = [...F2].map((i) => i + i).join(""));
|
|
7534
|
+
const s = Number.parseInt(F2, 16);
|
|
7535
|
+
return [s >> 16 & 255, s >> 8 & 255, s & 255];
|
|
7536
|
+
}, enumerable: false }, hexToAnsi256: { value: (u2) => C.rgbToAnsi256(...C.hexToRgb(u2)), enumerable: false }, ansi256ToAnsi: { value: (u2) => {
|
|
7537
|
+
if (u2 < 8) return 30 + u2;
|
|
7538
|
+
if (u2 < 16) return 90 + (u2 - 8);
|
|
7539
|
+
let t, F2, s;
|
|
7540
|
+
if (u2 >= 232) t = ((u2 - 232) * 10 + 8) / 255, F2 = t, s = t;
|
|
7541
|
+
else {
|
|
7542
|
+
u2 -= 16;
|
|
7543
|
+
const r = u2 % 36;
|
|
7544
|
+
t = Math.floor(u2 / 36) / 5, F2 = Math.floor(r / 6) / 5, s = r % 6 / 5;
|
|
7545
|
+
}
|
|
7546
|
+
const i = Math.max(t, F2, s) * 2;
|
|
7547
|
+
if (i === 0) return 30;
|
|
7548
|
+
let D2 = 30 + (Math.round(s) << 2 | Math.round(F2) << 1 | Math.round(t));
|
|
7549
|
+
return i === 2 && (D2 += 60), D2;
|
|
7550
|
+
}, enumerable: false }, rgbToAnsi: { value: (u2, t, F2) => C.ansi256ToAnsi(C.rgbToAnsi256(u2, t, F2)), enumerable: false }, hexToAnsi: { value: (u2) => C.ansi256ToAnsi(C.hexToAnsi256(u2)), enumerable: false } }), C;
|
|
7551
|
+
}
|
|
7552
|
+
var ED = CD();
|
|
7553
|
+
var d = /* @__PURE__ */ new Set(["\x1B", "\x9B"]);
|
|
7554
|
+
var oD = 39;
|
|
7555
|
+
var y = "\x07";
|
|
7556
|
+
var V = "[";
|
|
7557
|
+
var nD = "]";
|
|
7558
|
+
var G = "m";
|
|
7559
|
+
var _ = `${nD}8;;`;
|
|
7560
|
+
var z = (e2) => `${d.values().next().value}${V}${e2}${G}`;
|
|
7561
|
+
var K = (e2) => `${d.values().next().value}${_}${e2}${y}`;
|
|
7562
|
+
var aD = (e2) => e2.split(" ").map((u2) => p(u2));
|
|
7563
|
+
var k = (e2, u2, t) => {
|
|
7564
|
+
const F2 = [...u2];
|
|
7565
|
+
let s = false, i = false, D2 = p(P(e2[e2.length - 1]));
|
|
7566
|
+
for (const [r, n] of F2.entries()) {
|
|
7567
|
+
const E = p(n);
|
|
7568
|
+
if (D2 + E <= t ? e2[e2.length - 1] += n : (e2.push(n), D2 = 0), d.has(n) && (s = true, i = F2.slice(r + 1).join("").startsWith(_)), s) {
|
|
7569
|
+
i ? n === y && (s = false, i = false) : n === G && (s = false);
|
|
7570
|
+
continue;
|
|
7571
|
+
}
|
|
7572
|
+
D2 += E, D2 === t && r < F2.length - 1 && (e2.push(""), D2 = 0);
|
|
7573
|
+
}
|
|
7574
|
+
!D2 && e2[e2.length - 1].length > 0 && e2.length > 1 && (e2[e2.length - 2] += e2.pop());
|
|
7575
|
+
};
|
|
7576
|
+
var hD = (e2) => {
|
|
7577
|
+
const u2 = e2.split(" ");
|
|
7578
|
+
let t = u2.length;
|
|
7579
|
+
for (; t > 0 && !(p(u2[t - 1]) > 0); ) t--;
|
|
7580
|
+
return t === u2.length ? e2 : u2.slice(0, t).join(" ") + u2.slice(t).join("");
|
|
7581
|
+
};
|
|
7582
|
+
var lD = (e2, u2, t = {}) => {
|
|
7583
|
+
if (t.trim !== false && e2.trim() === "") return "";
|
|
7584
|
+
let F2 = "", s, i;
|
|
7585
|
+
const D2 = aD(e2);
|
|
7586
|
+
let r = [""];
|
|
7587
|
+
for (const [E, a] of e2.split(" ").entries()) {
|
|
7588
|
+
t.trim !== false && (r[r.length - 1] = r[r.length - 1].trimStart());
|
|
7589
|
+
let o2 = p(r[r.length - 1]);
|
|
7590
|
+
if (E !== 0 && (o2 >= u2 && (t.wordWrap === false || t.trim === false) && (r.push(""), o2 = 0), (o2 > 0 || t.trim === false) && (r[r.length - 1] += " ", o2++)), t.hard && D2[E] > u2) {
|
|
7591
|
+
const c = u2 - o2, f = 1 + Math.floor((D2[E] - c - 1) / u2);
|
|
7592
|
+
Math.floor((D2[E] - 1) / u2) < f && r.push(""), k(r, a, u2);
|
|
7593
|
+
continue;
|
|
7594
|
+
}
|
|
7595
|
+
if (o2 + D2[E] > u2 && o2 > 0 && D2[E] > 0) {
|
|
7596
|
+
if (t.wordWrap === false && o2 < u2) {
|
|
7597
|
+
k(r, a, u2);
|
|
7598
|
+
continue;
|
|
7599
|
+
}
|
|
7600
|
+
r.push("");
|
|
7601
|
+
}
|
|
7602
|
+
if (o2 + D2[E] > u2 && t.wordWrap === false) {
|
|
7603
|
+
k(r, a, u2);
|
|
7604
|
+
continue;
|
|
7605
|
+
}
|
|
7606
|
+
r[r.length - 1] += a;
|
|
7607
|
+
}
|
|
7608
|
+
t.trim !== false && (r = r.map((E) => hD(E)));
|
|
7609
|
+
const n = [...r.join(`
|
|
7610
|
+
`)];
|
|
7611
|
+
for (const [E, a] of n.entries()) {
|
|
7612
|
+
if (F2 += a, d.has(a)) {
|
|
7613
|
+
const { groups: c } = new RegExp(`(?:\\${V}(?<code>\\d+)m|\\${_}(?<uri>.*)${y})`).exec(n.slice(E).join("")) || { groups: {} };
|
|
7614
|
+
if (c.code !== void 0) {
|
|
7615
|
+
const f = Number.parseFloat(c.code);
|
|
7616
|
+
s = f === oD ? void 0 : f;
|
|
7617
|
+
} else c.uri !== void 0 && (i = c.uri.length === 0 ? void 0 : c.uri);
|
|
7618
|
+
}
|
|
7619
|
+
const o2 = ED.codes.get(Number(s));
|
|
7620
|
+
n[E + 1] === `
|
|
7621
|
+
` ? (i && (F2 += K("")), s && o2 && (F2 += z(o2))) : a === `
|
|
7622
|
+
` && (s && o2 && (F2 += z(s)), i && (F2 += K(i)));
|
|
7623
|
+
}
|
|
7624
|
+
return F2;
|
|
7625
|
+
};
|
|
7626
|
+
function Y(e2, u2, t) {
|
|
7627
|
+
return String(e2).normalize().replace(/\r\n/g, `
|
|
7628
|
+
`).split(`
|
|
7629
|
+
`).map((F2) => lD(F2, u2, t)).join(`
|
|
7630
|
+
`);
|
|
7631
|
+
}
|
|
7632
|
+
var xD = ["up", "down", "left", "right", "space", "enter", "cancel"];
|
|
7633
|
+
var B = { actions: new Set(xD), aliases: /* @__PURE__ */ new Map([["k", "up"], ["j", "down"], ["h", "left"], ["l", "right"], ["", "cancel"], ["escape", "cancel"]]) };
|
|
7634
|
+
function $(e2, u2) {
|
|
7635
|
+
if (typeof e2 == "string") return B.aliases.get(e2) === u2;
|
|
7636
|
+
for (const t of e2) if (t !== void 0 && $(t, u2)) return true;
|
|
7637
|
+
return false;
|
|
7638
|
+
}
|
|
7639
|
+
function BD(e2, u2) {
|
|
7640
|
+
if (e2 === u2) return;
|
|
7641
|
+
const t = e2.split(`
|
|
7642
|
+
`), F2 = u2.split(`
|
|
7643
|
+
`), s = [];
|
|
7644
|
+
for (let i = 0; i < Math.max(t.length, F2.length); i++) t[i] !== F2[i] && s.push(i);
|
|
7645
|
+
return s;
|
|
7646
|
+
}
|
|
7647
|
+
var AD = globalThis.process.platform.startsWith("win");
|
|
7648
|
+
var S = Symbol("clack:cancel");
|
|
7649
|
+
function pD(e2) {
|
|
7650
|
+
return e2 === S;
|
|
7651
|
+
}
|
|
7652
|
+
function m(e2, u2) {
|
|
7653
|
+
const t = e2;
|
|
7654
|
+
t.isTTY && t.setRawMode(u2);
|
|
7655
|
+
}
|
|
7656
|
+
function fD({ input: e2 = import_node_process.stdin, output: u2 = import_node_process.stdout, overwrite: t = true, hideCursor: F2 = true } = {}) {
|
|
7657
|
+
const s = g.createInterface({ input: e2, output: u2, prompt: "", tabSize: 1 });
|
|
7658
|
+
g.emitKeypressEvents(e2, s), e2.isTTY && e2.setRawMode(true);
|
|
7659
|
+
const i = (D2, { name: r, sequence: n }) => {
|
|
7660
|
+
const E = String(D2);
|
|
7661
|
+
if ($([E, r, n], "cancel")) {
|
|
7662
|
+
F2 && u2.write(import_sisteransi.cursor.show), process.exit(0);
|
|
7663
|
+
return;
|
|
7664
|
+
}
|
|
7665
|
+
if (!t) return;
|
|
7666
|
+
const a = r === "return" ? 0 : -1, o2 = r === "return" ? -1 : 0;
|
|
7667
|
+
g.moveCursor(u2, a, o2, () => {
|
|
7668
|
+
g.clearLine(u2, 1, () => {
|
|
7669
|
+
e2.once("keypress", i);
|
|
7670
|
+
});
|
|
7671
|
+
});
|
|
7672
|
+
};
|
|
7673
|
+
return F2 && u2.write(import_sisteransi.cursor.hide), e2.once("keypress", i), () => {
|
|
7674
|
+
e2.off("keypress", i), F2 && u2.write(import_sisteransi.cursor.show), e2.isTTY && !AD && e2.setRawMode(false), s.terminal = false, s.close();
|
|
7675
|
+
};
|
|
7676
|
+
}
|
|
7677
|
+
var gD = Object.defineProperty;
|
|
7678
|
+
var vD = (e2, u2, t) => u2 in e2 ? gD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
7679
|
+
var h = (e2, u2, t) => (vD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
7680
|
+
var x = class {
|
|
7681
|
+
constructor(u2, t = true) {
|
|
7682
|
+
h(this, "input"), h(this, "output"), h(this, "_abortSignal"), h(this, "rl"), h(this, "opts"), h(this, "_render"), h(this, "_track", false), h(this, "_prevFrame", ""), h(this, "_subscribers", /* @__PURE__ */ new Map()), h(this, "_cursor", 0), h(this, "state", "initial"), h(this, "error", ""), h(this, "value");
|
|
7683
|
+
const { input: F2 = import_node_process.stdin, output: s = import_node_process.stdout, render: i, signal: D2, ...r } = u2;
|
|
7684
|
+
this.opts = r, this.onKeypress = this.onKeypress.bind(this), this.close = this.close.bind(this), this.render = this.render.bind(this), this._render = i.bind(this), this._track = t, this._abortSignal = D2, this.input = F2, this.output = s;
|
|
7685
|
+
}
|
|
7686
|
+
unsubscribe() {
|
|
7687
|
+
this._subscribers.clear();
|
|
7688
|
+
}
|
|
7689
|
+
setSubscriber(u2, t) {
|
|
7690
|
+
const F2 = this._subscribers.get(u2) ?? [];
|
|
7691
|
+
F2.push(t), this._subscribers.set(u2, F2);
|
|
7692
|
+
}
|
|
7693
|
+
on(u2, t) {
|
|
7694
|
+
this.setSubscriber(u2, { cb: t });
|
|
7695
|
+
}
|
|
7696
|
+
once(u2, t) {
|
|
7697
|
+
this.setSubscriber(u2, { cb: t, once: true });
|
|
7698
|
+
}
|
|
7699
|
+
emit(u2, ...t) {
|
|
7700
|
+
const F2 = this._subscribers.get(u2) ?? [], s = [];
|
|
7701
|
+
for (const i of F2) i.cb(...t), i.once && s.push(() => F2.splice(F2.indexOf(i), 1));
|
|
7702
|
+
for (const i of s) i();
|
|
7703
|
+
}
|
|
7704
|
+
prompt() {
|
|
7705
|
+
return new Promise((u2, t) => {
|
|
7706
|
+
if (this._abortSignal) {
|
|
7707
|
+
if (this._abortSignal.aborted) return this.state = "cancel", this.close(), u2(S);
|
|
7708
|
+
this._abortSignal.addEventListener("abort", () => {
|
|
7709
|
+
this.state = "cancel", this.close();
|
|
7710
|
+
}, { once: true });
|
|
7711
|
+
}
|
|
7712
|
+
const F2 = new import_node_stream.Writable();
|
|
7713
|
+
F2._write = (s, i, D2) => {
|
|
7714
|
+
this._track && (this.value = this.rl?.line.replace(/\t/g, ""), this._cursor = this.rl?.cursor ?? 0, this.emit("value", this.value)), D2();
|
|
7715
|
+
}, this.input.pipe(F2), this.rl = import_node_readline.default.createInterface({ input: this.input, output: F2, tabSize: 2, prompt: "", escapeCodeTimeout: 50, terminal: true }), import_node_readline.default.emitKeypressEvents(this.input, this.rl), this.rl.prompt(), this.opts.initialValue !== void 0 && this._track && this.rl.write(this.opts.initialValue), this.input.on("keypress", this.onKeypress), m(this.input, true), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
|
|
7716
|
+
this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), m(this.input, false), u2(this.value);
|
|
7717
|
+
}), this.once("cancel", () => {
|
|
7718
|
+
this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), m(this.input, false), u2(S);
|
|
7719
|
+
});
|
|
7720
|
+
});
|
|
7721
|
+
}
|
|
7722
|
+
onKeypress(u2, t) {
|
|
7723
|
+
if (this.state === "error" && (this.state = "active"), t?.name && (!this._track && B.aliases.has(t.name) && this.emit("cursor", B.aliases.get(t.name)), B.actions.has(t.name) && this.emit("cursor", t.name)), u2 && (u2.toLowerCase() === "y" || u2.toLowerCase() === "n") && this.emit("confirm", u2.toLowerCase() === "y"), u2 === " " && this.opts.placeholder && (this.value || (this.rl?.write(this.opts.placeholder), this.emit("value", this.opts.placeholder))), u2 && this.emit("key", u2.toLowerCase()), t?.name === "return") {
|
|
7724
|
+
if (!this.value && this.opts.placeholder && (this.rl?.write(this.opts.placeholder), this.emit("value", this.opts.placeholder)), this.opts.validate) {
|
|
7725
|
+
const F2 = this.opts.validate(this.value);
|
|
7726
|
+
F2 && (this.error = F2 instanceof Error ? F2.message : F2, this.state = "error", this.rl?.write(this.value));
|
|
7727
|
+
}
|
|
7728
|
+
this.state !== "error" && (this.state = "submit");
|
|
7729
|
+
}
|
|
7730
|
+
$([u2, t?.name, t?.sequence], "cancel") && (this.state = "cancel"), (this.state === "submit" || this.state === "cancel") && this.emit("finalize"), this.render(), (this.state === "submit" || this.state === "cancel") && this.close();
|
|
7731
|
+
}
|
|
7732
|
+
close() {
|
|
7733
|
+
this.input.unpipe(), this.input.removeListener("keypress", this.onKeypress), this.output.write(`
|
|
7734
|
+
`), m(this.input, false), this.rl?.close(), this.rl = void 0, this.emit(`${this.state}`, this.value), this.unsubscribe();
|
|
7735
|
+
}
|
|
7736
|
+
restoreCursor() {
|
|
7737
|
+
const u2 = Y(this._prevFrame, process.stdout.columns, { hard: true }).split(`
|
|
7738
|
+
`).length - 1;
|
|
7739
|
+
this.output.write(import_sisteransi.cursor.move(-999, u2 * -1));
|
|
7740
|
+
}
|
|
7741
|
+
render() {
|
|
7742
|
+
const u2 = Y(this._render(this) ?? "", process.stdout.columns, { hard: true });
|
|
7743
|
+
if (u2 !== this._prevFrame) {
|
|
7744
|
+
if (this.state === "initial") this.output.write(import_sisteransi.cursor.hide);
|
|
7745
|
+
else {
|
|
7746
|
+
const t = BD(this._prevFrame, u2);
|
|
7747
|
+
if (this.restoreCursor(), t && t?.length === 1) {
|
|
7748
|
+
const F2 = t[0];
|
|
7749
|
+
this.output.write(import_sisteransi.cursor.move(0, F2)), this.output.write(import_sisteransi.erase.lines(1));
|
|
7750
|
+
const s = u2.split(`
|
|
7751
|
+
`);
|
|
7752
|
+
this.output.write(s[F2]), this._prevFrame = u2, this.output.write(import_sisteransi.cursor.move(0, s.length - F2 - 1));
|
|
7753
|
+
return;
|
|
7754
|
+
}
|
|
7755
|
+
if (t && t?.length > 1) {
|
|
7756
|
+
const F2 = t[0];
|
|
7757
|
+
this.output.write(import_sisteransi.cursor.move(0, F2)), this.output.write(import_sisteransi.erase.down());
|
|
7758
|
+
const s = u2.split(`
|
|
7759
|
+
`).slice(F2);
|
|
7760
|
+
this.output.write(s.join(`
|
|
7761
|
+
`)), this._prevFrame = u2;
|
|
7762
|
+
return;
|
|
7763
|
+
}
|
|
7764
|
+
this.output.write(import_sisteransi.erase.down());
|
|
7765
|
+
}
|
|
7766
|
+
this.output.write(u2), this.state === "initial" && (this.state = "active"), this._prevFrame = u2;
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
};
|
|
7770
|
+
var A;
|
|
7771
|
+
A = /* @__PURE__ */ new WeakMap();
|
|
7772
|
+
var OD = Object.defineProperty;
|
|
7773
|
+
var PD = (e2, u2, t) => u2 in e2 ? OD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
7774
|
+
var J = (e2, u2, t) => (PD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
7775
|
+
var LD = class extends x {
|
|
7776
|
+
constructor(u2) {
|
|
7777
|
+
super(u2, false), J(this, "options"), J(this, "cursor", 0), this.options = u2.options, this.cursor = this.options.findIndex(({ value: t }) => t === u2.initialValue), this.cursor === -1 && (this.cursor = 0), this.changeValue(), this.on("cursor", (t) => {
|
|
7778
|
+
switch (t) {
|
|
7779
|
+
case "left":
|
|
7780
|
+
case "up":
|
|
7781
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
7782
|
+
break;
|
|
7783
|
+
case "down":
|
|
7784
|
+
case "right":
|
|
7785
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
7786
|
+
break;
|
|
7787
|
+
}
|
|
7788
|
+
this.changeValue();
|
|
7789
|
+
});
|
|
7790
|
+
}
|
|
7791
|
+
get _value() {
|
|
7792
|
+
return this.options[this.cursor];
|
|
7793
|
+
}
|
|
7794
|
+
changeValue() {
|
|
7795
|
+
this.value = this._value.value;
|
|
7796
|
+
}
|
|
7797
|
+
};
|
|
7798
|
+
|
|
7799
|
+
// node_modules/@clack/prompts/dist/index.mjs
|
|
7800
|
+
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
7801
|
+
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
7802
|
+
var import_sisteransi2 = __toESM(require_src(), 1);
|
|
7803
|
+
function ce() {
|
|
7804
|
+
return import_node_process2.default.platform !== "win32" ? import_node_process2.default.env.TERM !== "linux" : !!import_node_process2.default.env.CI || !!import_node_process2.default.env.WT_SESSION || !!import_node_process2.default.env.TERMINUS_SUBLIME || import_node_process2.default.env.ConEmuTask === "{cmd::Cmder}" || import_node_process2.default.env.TERM_PROGRAM === "Terminus-Sublime" || import_node_process2.default.env.TERM_PROGRAM === "vscode" || import_node_process2.default.env.TERM === "xterm-256color" || import_node_process2.default.env.TERM === "alacritty" || import_node_process2.default.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
7805
|
+
}
|
|
7806
|
+
var V2 = ce();
|
|
7807
|
+
var u = (t, n) => V2 ? t : n;
|
|
7808
|
+
var le = u("\u25C6", "*");
|
|
7809
|
+
var L2 = u("\u25A0", "x");
|
|
7810
|
+
var W2 = u("\u25B2", "x");
|
|
7811
|
+
var C2 = u("\u25C7", "o");
|
|
7812
|
+
var ue = u("\u250C", "T");
|
|
7813
|
+
var o = u("\u2502", "|");
|
|
7814
|
+
var d2 = u("\u2514", "\u2014");
|
|
7815
|
+
var k2 = u("\u25CF", ">");
|
|
7816
|
+
var P2 = u("\u25CB", " ");
|
|
7817
|
+
var A2 = u("\u25FB", "[\u2022]");
|
|
7818
|
+
var T = u("\u25FC", "[+]");
|
|
7819
|
+
var F = u("\u25FB", "[ ]");
|
|
7820
|
+
var $e = u("\u25AA", "\u2022");
|
|
7821
|
+
var _2 = u("\u2500", "-");
|
|
7822
|
+
var me = u("\u256E", "+");
|
|
7823
|
+
var de = u("\u251C", "+");
|
|
7824
|
+
var pe = u("\u256F", "+");
|
|
7825
|
+
var q = u("\u25CF", "\u2022");
|
|
7826
|
+
var D = u("\u25C6", "*");
|
|
7827
|
+
var U = u("\u25B2", "!");
|
|
7828
|
+
var K2 = u("\u25A0", "x");
|
|
7829
|
+
var b2 = (t) => {
|
|
7830
|
+
switch (t) {
|
|
7831
|
+
case "initial":
|
|
7832
|
+
case "active":
|
|
7833
|
+
return import_picocolors2.default.cyan(le);
|
|
7834
|
+
case "cancel":
|
|
7835
|
+
return import_picocolors2.default.red(L2);
|
|
7836
|
+
case "error":
|
|
7837
|
+
return import_picocolors2.default.yellow(W2);
|
|
7838
|
+
case "submit":
|
|
7839
|
+
return import_picocolors2.default.green(C2);
|
|
7840
|
+
}
|
|
7841
|
+
};
|
|
7842
|
+
var G2 = (t) => {
|
|
7843
|
+
const { cursor: n, options: r, style: i } = t, s = t.maxItems ?? Number.POSITIVE_INFINITY, c = Math.max(process.stdout.rows - 4, 0), a = Math.min(c, Math.max(s, 5));
|
|
7844
|
+
let l2 = 0;
|
|
7845
|
+
n >= l2 + a - 3 ? l2 = Math.max(Math.min(n - a + 3, r.length - a), 0) : n < l2 + 2 && (l2 = Math.max(n - 2, 0));
|
|
7846
|
+
const $2 = a < r.length && l2 > 0, g2 = a < r.length && l2 + a < r.length;
|
|
7847
|
+
return r.slice(l2, l2 + a).map((p2, v, f) => {
|
|
7848
|
+
const j2 = v === 0 && $2, E = v === f.length - 1 && g2;
|
|
7849
|
+
return j2 || E ? import_picocolors2.default.dim("...") : i(p2, v + l2 === n);
|
|
7850
|
+
});
|
|
7851
|
+
};
|
|
7852
|
+
var ve = (t) => {
|
|
7853
|
+
const n = (r, i) => {
|
|
7854
|
+
const s = r.label ?? String(r.value);
|
|
7855
|
+
switch (i) {
|
|
7856
|
+
case "selected":
|
|
7857
|
+
return `${import_picocolors2.default.dim(s)}`;
|
|
7858
|
+
case "active":
|
|
7859
|
+
return `${import_picocolors2.default.green(k2)} ${s} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}`;
|
|
7860
|
+
case "cancelled":
|
|
7861
|
+
return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}`;
|
|
7862
|
+
default:
|
|
7863
|
+
return `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(s)}`;
|
|
7864
|
+
}
|
|
7865
|
+
};
|
|
7866
|
+
return new LD({ options: t.options, initialValue: t.initialValue, render() {
|
|
7867
|
+
const r = `${import_picocolors2.default.gray(o)}
|
|
7868
|
+
${b2(this.state)} ${t.message}
|
|
7869
|
+
`;
|
|
7870
|
+
switch (this.state) {
|
|
7871
|
+
case "submit":
|
|
7872
|
+
return `${r}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
|
|
7873
|
+
case "cancel":
|
|
7874
|
+
return `${r}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
|
|
7875
|
+
${import_picocolors2.default.gray(o)}`;
|
|
7876
|
+
default:
|
|
7877
|
+
return `${r}${import_picocolors2.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
|
|
7878
|
+
${import_picocolors2.default.cyan(o)} `)}
|
|
7879
|
+
${import_picocolors2.default.cyan(d2)}
|
|
7880
|
+
`;
|
|
7881
|
+
}
|
|
7882
|
+
} }).prompt();
|
|
7883
|
+
};
|
|
7884
|
+
var xe = (t = "") => {
|
|
7885
|
+
process.stdout.write(`${import_picocolors2.default.gray(d2)} ${import_picocolors2.default.red(t)}
|
|
7886
|
+
|
|
7887
|
+
`);
|
|
7888
|
+
};
|
|
7889
|
+
var Ie = (t = "") => {
|
|
7890
|
+
process.stdout.write(`${import_picocolors2.default.gray(ue)} ${t}
|
|
7891
|
+
`);
|
|
7892
|
+
};
|
|
7893
|
+
var Se = (t = "") => {
|
|
7894
|
+
process.stdout.write(`${import_picocolors2.default.gray(o)}
|
|
7895
|
+
${import_picocolors2.default.gray(d2)} ${t}
|
|
7896
|
+
|
|
7897
|
+
`);
|
|
7898
|
+
};
|
|
7899
|
+
var M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
|
|
7900
|
+
const r = [`${import_picocolors2.default.gray(o)}`];
|
|
7901
|
+
if (t) {
|
|
7902
|
+
const [i, ...s] = t.split(`
|
|
7903
|
+
`);
|
|
7904
|
+
r.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
|
|
7905
|
+
}
|
|
7906
|
+
process.stdout.write(`${r.join(`
|
|
7907
|
+
`)}
|
|
7908
|
+
`);
|
|
7909
|
+
}, info: (t) => {
|
|
7910
|
+
M2.message(t, { symbol: import_picocolors2.default.blue(q) });
|
|
7911
|
+
}, success: (t) => {
|
|
7912
|
+
M2.message(t, { symbol: import_picocolors2.default.green(D) });
|
|
7913
|
+
}, step: (t) => {
|
|
7914
|
+
M2.message(t, { symbol: import_picocolors2.default.green(C2) });
|
|
7915
|
+
}, warn: (t) => {
|
|
7916
|
+
M2.message(t, { symbol: import_picocolors2.default.yellow(U) });
|
|
7917
|
+
}, warning: (t) => {
|
|
7918
|
+
M2.warn(t);
|
|
7919
|
+
}, error: (t) => {
|
|
7920
|
+
M2.message(t, { symbol: import_picocolors2.default.red(K2) });
|
|
7921
|
+
} };
|
|
7922
|
+
var J2 = `${import_picocolors2.default.gray(o)} `;
|
|
7923
|
+
var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
7924
|
+
const n = V2 ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], r = V2 ? 80 : 120, i = process.env.CI === "true";
|
|
7925
|
+
let s, c, a = false, l2 = "", $2, g2 = performance.now();
|
|
7926
|
+
const p2 = (m2) => {
|
|
7927
|
+
const h2 = m2 > 1 ? "Something went wrong" : "Canceled";
|
|
7928
|
+
a && N2(h2, m2);
|
|
7929
|
+
}, v = () => p2(2), f = () => p2(1), j2 = () => {
|
|
7930
|
+
process.on("uncaughtExceptionMonitor", v), process.on("unhandledRejection", v), process.on("SIGINT", f), process.on("SIGTERM", f), process.on("exit", p2);
|
|
7931
|
+
}, E = () => {
|
|
7932
|
+
process.removeListener("uncaughtExceptionMonitor", v), process.removeListener("unhandledRejection", v), process.removeListener("SIGINT", f), process.removeListener("SIGTERM", f), process.removeListener("exit", p2);
|
|
7933
|
+
}, B2 = () => {
|
|
7934
|
+
if ($2 === void 0) return;
|
|
7935
|
+
i && process.stdout.write(`
|
|
7936
|
+
`);
|
|
7937
|
+
const m2 = $2.split(`
|
|
7938
|
+
`);
|
|
7939
|
+
process.stdout.write(import_sisteransi2.cursor.move(-999, m2.length - 1)), process.stdout.write(import_sisteransi2.erase.down(m2.length));
|
|
7940
|
+
}, R2 = (m2) => m2.replace(/\.+$/, ""), O2 = (m2) => {
|
|
7941
|
+
const h2 = (performance.now() - m2) / 1e3, w2 = Math.floor(h2 / 60), I2 = Math.floor(h2 % 60);
|
|
7942
|
+
return w2 > 0 ? `[${w2}m ${I2}s]` : `[${I2}s]`;
|
|
7943
|
+
}, H = (m2 = "") => {
|
|
7944
|
+
a = true, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
|
|
7945
|
+
`);
|
|
7946
|
+
let h2 = 0, w2 = 0;
|
|
7947
|
+
j2(), c = setInterval(() => {
|
|
7948
|
+
if (i && l2 === $2) return;
|
|
7949
|
+
B2(), $2 = l2;
|
|
7950
|
+
const I2 = import_picocolors2.default.magenta(n[h2]);
|
|
7951
|
+
if (i) process.stdout.write(`${I2} ${l2}...`);
|
|
7952
|
+
else if (t === "timer") process.stdout.write(`${I2} ${l2} ${O2(g2)}`);
|
|
7953
|
+
else {
|
|
7954
|
+
const z2 = ".".repeat(Math.floor(w2)).slice(0, 3);
|
|
7955
|
+
process.stdout.write(`${I2} ${l2}${z2}`);
|
|
7956
|
+
}
|
|
7957
|
+
h2 = h2 + 1 < n.length ? h2 + 1 : 0, w2 = w2 < n.length ? w2 + 0.125 : 0;
|
|
7958
|
+
}, r);
|
|
7959
|
+
}, N2 = (m2 = "", h2 = 0) => {
|
|
7960
|
+
a = false, clearInterval(c), B2();
|
|
7961
|
+
const w2 = h2 === 0 ? import_picocolors2.default.green(C2) : h2 === 1 ? import_picocolors2.default.red(L2) : import_picocolors2.default.red(W2);
|
|
7962
|
+
l2 = R2(m2 ?? l2), t === "timer" ? process.stdout.write(`${w2} ${l2} ${O2(g2)}
|
|
7963
|
+
`) : process.stdout.write(`${w2} ${l2}
|
|
7964
|
+
`), E(), s();
|
|
7965
|
+
};
|
|
7966
|
+
return { start: H, stop: N2, message: (m2 = "") => {
|
|
7967
|
+
l2 = R2(m2 ?? l2);
|
|
7968
|
+
} };
|
|
7969
|
+
};
|
|
7970
|
+
|
|
7971
|
+
// src/tui/runInteractive.ts
|
|
7972
|
+
var import_picocolors3 = __toESM(require_picocolors());
|
|
7973
|
+
|
|
7974
|
+
// src/version.ts
|
|
7975
|
+
var VERSION = "0.1.0-beta.1";
|
|
7976
|
+
|
|
7977
|
+
// src/tui/runInteractive.ts
|
|
7978
|
+
async function formatStatusLine() {
|
|
7979
|
+
const creds = await loadCredentials();
|
|
7980
|
+
if (!creds?.accessToken) {
|
|
7981
|
+
return import_picocolors3.default.dim("Not signed in");
|
|
7982
|
+
}
|
|
7983
|
+
try {
|
|
7984
|
+
const synced = await syncCredentialsFromAccount(creds);
|
|
7985
|
+
const plan = synced.plan ?? "unknown";
|
|
7986
|
+
return import_picocolors3.default.green(`Signed in: ${synced.email ?? "(email unknown)"} \xB7 ${plan}`);
|
|
7987
|
+
} catch {
|
|
7988
|
+
return import_picocolors3.default.yellow(`Signed in: ${creds.email ?? "(email unknown)"} (offline)`);
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
async function handleScan(cwd) {
|
|
7992
|
+
const apiBaseUrl = resolveApiBaseUrl();
|
|
7993
|
+
const spinner = Y2();
|
|
7994
|
+
spinner.start("Checking credentials\u2026");
|
|
7995
|
+
let accessToken;
|
|
7996
|
+
try {
|
|
7997
|
+
({ accessToken } = await requireCredentials(apiBaseUrl));
|
|
7998
|
+
} catch (error) {
|
|
7999
|
+
spinner.stop("Sign in required");
|
|
8000
|
+
M2.error(error instanceof Error ? error.message : String(error));
|
|
8001
|
+
M2.message(import_picocolors3.default.dim('Choose "Sign in / Sign out" from the menu.'));
|
|
8002
|
+
return;
|
|
8003
|
+
}
|
|
8004
|
+
spinner.message(`Scanning ${cwd}\u2026`);
|
|
8005
|
+
const result = await runProjectScan({ workspacePath: cwd, accessToken, apiBaseUrl });
|
|
8006
|
+
if (!result.ok) {
|
|
8007
|
+
spinner.stop("Scan failed");
|
|
8008
|
+
if (result.kind === "scan_limit") {
|
|
8009
|
+
M2.error(formatScanLimitMessage(result.upgradeUrl));
|
|
8010
|
+
try {
|
|
8011
|
+
const account = await fetchAccountMe(apiBaseUrl, accessToken);
|
|
8012
|
+
M2.message(formatUsageLine(account.usage));
|
|
8013
|
+
} catch {
|
|
8014
|
+
}
|
|
8015
|
+
return;
|
|
8016
|
+
}
|
|
8017
|
+
M2.error(result.message);
|
|
8018
|
+
return;
|
|
8019
|
+
}
|
|
8020
|
+
const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
|
|
8021
|
+
const paths = await writeScanArtifacts({ artifact, cwd });
|
|
8022
|
+
spinner.stop("Scan complete");
|
|
8023
|
+
printScanSummary(artifact, paths);
|
|
8024
|
+
if (artifact.usage) {
|
|
8025
|
+
M2.message(formatUsageLine(artifact.usage));
|
|
8026
|
+
}
|
|
8027
|
+
}
|
|
8028
|
+
async function handleViewGaps(cwd) {
|
|
8029
|
+
try {
|
|
8030
|
+
const artifact = await loadLastArtifact(cwd);
|
|
8031
|
+
M2.message(formatTopGapsList(artifact));
|
|
8032
|
+
} catch (error) {
|
|
8033
|
+
if (isScanNotFoundError(error)) {
|
|
8034
|
+
M2.warn(error.message);
|
|
8035
|
+
return;
|
|
8036
|
+
}
|
|
8037
|
+
throw error;
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
8040
|
+
async function handlePrompt(cwd) {
|
|
8041
|
+
try {
|
|
8042
|
+
const artifact = await loadLastArtifact(cwd);
|
|
8043
|
+
const gap = pickGap(artifact);
|
|
8044
|
+
if (!gap) {
|
|
8045
|
+
M2.warn("No gaps to fix. Run a scan or pick a different project.");
|
|
8046
|
+
return;
|
|
8047
|
+
}
|
|
8048
|
+
try {
|
|
8049
|
+
await copyToClipboard(gap.copyPrompt);
|
|
8050
|
+
M2.success(import_picocolors3.default.green(`Copied top prompt to clipboard \u2014 ${gap.title}`));
|
|
8051
|
+
} catch (error) {
|
|
8052
|
+
M2.warn(error instanceof Error ? error.message : String(error));
|
|
8053
|
+
console.log("");
|
|
8054
|
+
console.log(gap.copyPrompt);
|
|
8055
|
+
console.log("");
|
|
8056
|
+
M2.message(import_picocolors3.default.dim("Copy the text above manually."));
|
|
8057
|
+
}
|
|
8058
|
+
} catch (error) {
|
|
8059
|
+
if (isScanNotFoundError(error)) {
|
|
8060
|
+
M2.warn(error.message);
|
|
8061
|
+
return;
|
|
8062
|
+
}
|
|
8063
|
+
throw error;
|
|
8064
|
+
}
|
|
8065
|
+
}
|
|
8066
|
+
async function handleOpenReport(cwd) {
|
|
8067
|
+
const spinner = Y2();
|
|
8068
|
+
spinner.start("Refreshing report from last scan\u2026");
|
|
8069
|
+
try {
|
|
8070
|
+
const paths = await refreshReportFromDisk(cwd);
|
|
8071
|
+
spinner.stop("Report ready");
|
|
8072
|
+
await openPathInBrowser(paths.reportPath);
|
|
8073
|
+
M2.success(`Opened ${paths.reportPath}`);
|
|
8074
|
+
} catch (error) {
|
|
8075
|
+
spinner.stop("Could not open report");
|
|
8076
|
+
if (isScanNotFoundError(error)) {
|
|
8077
|
+
M2.warn(error.message);
|
|
8078
|
+
return;
|
|
8079
|
+
}
|
|
8080
|
+
M2.warn(error instanceof Error ? error.message : String(error));
|
|
8081
|
+
}
|
|
8082
|
+
}
|
|
8083
|
+
async function handleAuth() {
|
|
8084
|
+
const creds = await loadCredentials();
|
|
8085
|
+
if (creds?.accessToken) {
|
|
8086
|
+
await clearCredentials();
|
|
8087
|
+
M2.success("Signed out.");
|
|
8088
|
+
return;
|
|
8089
|
+
}
|
|
8090
|
+
const apiBaseUrl = resolveApiBaseUrl();
|
|
8091
|
+
await runDeviceLogin(apiBaseUrl);
|
|
8092
|
+
}
|
|
8093
|
+
function buildMenuOptions(isSignedIn) {
|
|
8094
|
+
return [
|
|
8095
|
+
{ value: "scan", label: "Scan project", hint: "Map launch readiness" },
|
|
8096
|
+
{ value: "gaps", label: "View top gaps", hint: "From last scan" },
|
|
8097
|
+
{ value: "prompt", label: "Copy top prompt", hint: "Agent-ready fix prompt" },
|
|
8098
|
+
{
|
|
8099
|
+
value: "open-report",
|
|
8100
|
+
label: "Open report in browser",
|
|
8101
|
+
hint: "Rebuilds UI from last scan (no new scan)"
|
|
8102
|
+
},
|
|
8103
|
+
{
|
|
8104
|
+
value: "auth",
|
|
8105
|
+
label: isSignedIn ? "Sign out" : "Sign in",
|
|
8106
|
+
hint: isSignedIn ? "Clear local credentials" : "Device login flow"
|
|
8107
|
+
},
|
|
8108
|
+
{ value: "exit", label: "Exit" }
|
|
8109
|
+
];
|
|
8110
|
+
}
|
|
8111
|
+
async function runInteractiveSession(startDir = process.cwd()) {
|
|
8112
|
+
Ie(`${import_picocolors3.default.bold("VibeRaven")} ${import_picocolors3.default.dim(VERSION)}`);
|
|
8113
|
+
const cwd = await resolveWorkspaceRoot(startDir);
|
|
8114
|
+
const artifactsAt = await findArtifactsWorkspace(startDir);
|
|
8115
|
+
if (artifactsAt && (0, import_node_path7.resolve)(artifactsAt) !== (0, import_node_path7.resolve)(startDir)) {
|
|
8116
|
+
M2.message(import_picocolors3.default.dim(`Using scan from: ${artifactsAt}`));
|
|
8117
|
+
} else {
|
|
8118
|
+
M2.message(import_picocolors3.default.dim(`Project folder: ${cwd}`));
|
|
8119
|
+
if (!artifactsAt) {
|
|
8120
|
+
M2.message(
|
|
8121
|
+
import_picocolors3.default.dim(
|
|
8122
|
+
"No .viberaven/ here yet. Extension scans are separate \u2014 choose Scan project to write CLI artifacts."
|
|
8123
|
+
)
|
|
8124
|
+
);
|
|
8125
|
+
}
|
|
8126
|
+
}
|
|
8127
|
+
let running = true;
|
|
8128
|
+
while (running) {
|
|
8129
|
+
const statusLine = await formatStatusLine();
|
|
8130
|
+
M2.message(statusLine);
|
|
8131
|
+
const creds = await loadCredentials();
|
|
8132
|
+
const action = await ve({
|
|
8133
|
+
message: "What would you like to do?",
|
|
8134
|
+
options: buildMenuOptions(Boolean(creds?.accessToken))
|
|
8135
|
+
});
|
|
8136
|
+
if (pD(action)) {
|
|
8137
|
+
xe("Goodbye.");
|
|
8138
|
+
return;
|
|
8139
|
+
}
|
|
8140
|
+
switch (action) {
|
|
8141
|
+
case "scan":
|
|
8142
|
+
await handleScan(cwd);
|
|
8143
|
+
break;
|
|
8144
|
+
case "gaps":
|
|
8145
|
+
await handleViewGaps(cwd);
|
|
8146
|
+
break;
|
|
8147
|
+
case "prompt":
|
|
8148
|
+
await handlePrompt(cwd);
|
|
8149
|
+
break;
|
|
8150
|
+
case "open-report":
|
|
8151
|
+
await handleOpenReport(cwd);
|
|
8152
|
+
break;
|
|
8153
|
+
case "auth":
|
|
8154
|
+
await handleAuth();
|
|
8155
|
+
break;
|
|
8156
|
+
case "exit":
|
|
8157
|
+
running = false;
|
|
8158
|
+
break;
|
|
8159
|
+
default:
|
|
8160
|
+
break;
|
|
8161
|
+
}
|
|
8162
|
+
}
|
|
8163
|
+
Se(import_picocolors3.default.dim("Run viberaven anytime for the interactive menu."));
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
// src/cli.ts
|
|
8167
|
+
function printHelp() {
|
|
8168
|
+
console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
|
|
5658
8169
|
|
|
5659
|
-
// src/terminalSummary.ts
|
|
5660
|
-
function printScanSummary(artifact, paths) {
|
|
5661
|
-
console.log("");
|
|
5662
|
-
console.log(`VibeRaven \xB7 Production core ${artifact.productionCorePercent}% \xB7 ${artifact.gaps.length} gap(s)`);
|
|
5663
|
-
console.log(`Score ${artifact.score} \xB7 ${artifact.scoreLabel}`);
|
|
5664
|
-
console.log("");
|
|
5665
|
-
for (const area of artifact.missionGraph.areas ?? []) {
|
|
5666
|
-
const mission = area.providerMissions[0];
|
|
5667
|
-
if (!mission) {
|
|
5668
|
-
continue;
|
|
5669
|
-
}
|
|
5670
|
-
const open = mission.checks.filter(
|
|
5671
|
-
(c) => c.status === "missing" || c.status === "failed" || c.status === "needs-connection"
|
|
5672
|
-
).length;
|
|
5673
|
-
const modelGaps = artifact.gaps.filter((g) => g.primaryMapCategory === area.key).length;
|
|
5674
|
-
const gapTag = modelGaps > 0 ? ` GAP ${modelGaps}` : open > 0 ? ` ${open} fix` : "";
|
|
5675
|
-
const label = area.label.padEnd(18);
|
|
5676
|
-
const provider2 = mission.providerLabel.padEnd(14);
|
|
5677
|
-
console.log(` ${label} ${provider2} ${mission.readinessPercent}%${gapTag}`);
|
|
5678
|
-
}
|
|
5679
|
-
console.log("");
|
|
5680
|
-
console.log("Artifacts:");
|
|
5681
|
-
console.log(` ${paths.reportPath}`);
|
|
5682
|
-
console.log(` ${paths.jsonPath}`);
|
|
5683
|
-
console.log(` ${paths.summaryPath}`);
|
|
5684
|
-
console.log("");
|
|
5685
|
-
console.log("Next: viberaven prompt (top gap for your coding agent)");
|
|
5686
|
-
console.log("Agents: read .viberaven/agent-summary.md");
|
|
5687
|
-
console.log("");
|
|
5688
|
-
}
|
|
5689
8170
|
|
|
5690
|
-
// src/cli.ts
|
|
5691
|
-
var VERSION = "0.1.0-beta.0";
|
|
5692
|
-
function printHelp() {
|
|
5693
|
-
console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
|
|
5694
8171
|
|
|
5695
8172
|
Usage:
|
|
8173
|
+
|
|
8174
|
+
viberaven Interactive menu (default for vibe coders)
|
|
8175
|
+
|
|
8176
|
+
viberaven tui Same interactive menu
|
|
8177
|
+
|
|
5696
8178
|
viberaven login [--api-url <url>]
|
|
8179
|
+
|
|
5697
8180
|
viberaven logout
|
|
8181
|
+
|
|
5698
8182
|
viberaven status
|
|
8183
|
+
|
|
5699
8184
|
viberaven scan [--open] [--json] [--api-url <url>] [path]
|
|
5700
|
-
|
|
8185
|
+
|
|
8186
|
+
viberaven report [--open] [path]
|
|
8187
|
+
Rebuild report.html from last scan (no new API scan)
|
|
8188
|
+
|
|
8189
|
+
viberaven prompt [--gap <id>] [--provider <key>] [--area <key>] [--no-copy]
|
|
8190
|
+
|
|
5701
8191
|
viberaven stack set <area> <provider>
|
|
8192
|
+
|
|
5702
8193
|
viberaven stack clear <area>
|
|
8194
|
+
|
|
5703
8195
|
viberaven stack list
|
|
5704
8196
|
|
|
8197
|
+
|
|
8198
|
+
|
|
5705
8199
|
Agent workflow (Claude Code / Codex):
|
|
5706
|
-
|
|
8200
|
+
|
|
8201
|
+
npx -y @viberaven/cli@beta scan
|
|
8202
|
+
|
|
5707
8203
|
Read .viberaven/agent-summary.md and fix the top gap, then scan again.
|
|
5708
8204
|
|
|
8205
|
+
|
|
8206
|
+
|
|
8207
|
+
Humans: run \`viberaven\` or \`viberaven tui\` for the interactive menu.
|
|
8208
|
+
|
|
8209
|
+
Agents: use \`npx -y @viberaven/cli@beta scan\` directly (no --open required).
|
|
8210
|
+
|
|
8211
|
+
|
|
8212
|
+
|
|
5709
8213
|
Environment:
|
|
8214
|
+
|
|
5710
8215
|
VIBERAVEN_API_URL Managed API base URL (same server as the VS Code extension)
|
|
8216
|
+
|
|
8217
|
+
Security:
|
|
8218
|
+
|
|
8219
|
+
CLI scans use VibeRaven login \u2014 not OPENAI_API_KEY. See packages/cli/SECURITY.md.
|
|
8220
|
+
|
|
5711
8221
|
`);
|
|
5712
8222
|
}
|
|
5713
8223
|
function parseArgs(argv) {
|
|
@@ -5739,29 +8249,6 @@ function parseArgs(argv) {
|
|
|
5739
8249
|
}
|
|
5740
8250
|
return { command, flags, positional };
|
|
5741
8251
|
}
|
|
5742
|
-
async function loadLastArtifact(cwd) {
|
|
5743
|
-
const path = (0, import_node_path5.join)(getProjectArtifactsDir(cwd), "last-scan.json");
|
|
5744
|
-
const raw = await (0, import_promises4.readFile)(path, "utf-8");
|
|
5745
|
-
return JSON.parse(raw);
|
|
5746
|
-
}
|
|
5747
|
-
function pickGap(artifact, options) {
|
|
5748
|
-
if (options.gapId) {
|
|
5749
|
-
return artifact.gaps.find((g) => g.id === options.gapId);
|
|
5750
|
-
}
|
|
5751
|
-
if (options.provider) {
|
|
5752
|
-
const key = options.provider.toLowerCase();
|
|
5753
|
-
return artifact.gaps.find(
|
|
5754
|
-
(g) => g.primaryMapCategory === key || g.title.toLowerCase().includes(key) || g.id.toLowerCase().includes(key)
|
|
5755
|
-
);
|
|
5756
|
-
}
|
|
5757
|
-
if (options.area) {
|
|
5758
|
-
return artifact.gaps.find((g) => g.primaryMapCategory === options.area);
|
|
5759
|
-
}
|
|
5760
|
-
const rank = { critical: 0, warning: 1, info: 2 };
|
|
5761
|
-
return [...artifact.gaps].sort(
|
|
5762
|
-
(a, b) => rank[a.severity] - rank[b.severity] || a.title.localeCompare(b.title)
|
|
5763
|
-
)[0];
|
|
5764
|
-
}
|
|
5765
8252
|
async function cmdLogin(flags) {
|
|
5766
8253
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
5767
8254
|
await runDeviceLogin(apiBaseUrl);
|
|
@@ -5792,7 +8279,7 @@ async function cmdStatus() {
|
|
|
5792
8279
|
}
|
|
5793
8280
|
}
|
|
5794
8281
|
async function cmdScan(flags, positional) {
|
|
5795
|
-
const workspacePath = positional[0] ? (0,
|
|
8282
|
+
const workspacePath = positional[0] ? (0, import_node_path8.join)(process.cwd(), positional[0]) : await resolveWorkspaceRoot(process.cwd());
|
|
5796
8283
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
5797
8284
|
let accessToken;
|
|
5798
8285
|
try {
|
|
@@ -5816,14 +8303,15 @@ async function cmdScan(flags, positional) {
|
|
|
5816
8303
|
console.error(result.message);
|
|
5817
8304
|
return 1;
|
|
5818
8305
|
}
|
|
5819
|
-
const
|
|
8306
|
+
const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
|
|
8307
|
+
const paths = await writeScanArtifacts({ artifact, cwd: workspacePath });
|
|
5820
8308
|
if (flags.json) {
|
|
5821
|
-
console.log(JSON.stringify(
|
|
8309
|
+
console.log(JSON.stringify(artifact, null, 2));
|
|
5822
8310
|
return 0;
|
|
5823
8311
|
}
|
|
5824
|
-
printScanSummary(
|
|
5825
|
-
if (
|
|
5826
|
-
console.log(formatUsageLine(
|
|
8312
|
+
printScanSummary(artifact, paths);
|
|
8313
|
+
if (artifact.usage) {
|
|
8314
|
+
console.log(formatUsageLine(artifact.usage));
|
|
5827
8315
|
}
|
|
5828
8316
|
if (flags.open) {
|
|
5829
8317
|
try {
|
|
@@ -5834,13 +8322,35 @@ async function cmdScan(flags, positional) {
|
|
|
5834
8322
|
}
|
|
5835
8323
|
return 0;
|
|
5836
8324
|
}
|
|
8325
|
+
async function cmdReport(flags, positional) {
|
|
8326
|
+
const startDir = positional[0] ? (0, import_node_path8.join)(process.cwd(), positional[0]) : process.cwd();
|
|
8327
|
+
try {
|
|
8328
|
+
const paths = await refreshReportFromDisk(startDir);
|
|
8329
|
+
console.log(`Report refreshed: ${paths.reportPath}`);
|
|
8330
|
+
if (flags.open) {
|
|
8331
|
+
try {
|
|
8332
|
+
await openPathInBrowser(paths.reportPath);
|
|
8333
|
+
} catch (error) {
|
|
8334
|
+
console.warn(error instanceof Error ? error.message : String(error));
|
|
8335
|
+
}
|
|
8336
|
+
}
|
|
8337
|
+
return 0;
|
|
8338
|
+
} catch (error) {
|
|
8339
|
+
if (isScanNotFoundError(error)) {
|
|
8340
|
+
console.error(error.message);
|
|
8341
|
+
return 1;
|
|
8342
|
+
}
|
|
8343
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
8344
|
+
return 1;
|
|
8345
|
+
}
|
|
8346
|
+
}
|
|
5837
8347
|
async function cmdPrompt(flags, positional) {
|
|
5838
|
-
const
|
|
8348
|
+
const startDir = positional[0] ? (0, import_node_path8.join)(process.cwd(), positional[0]) : process.cwd();
|
|
5839
8349
|
let artifact;
|
|
5840
8350
|
try {
|
|
5841
|
-
artifact = await loadLastArtifact(
|
|
5842
|
-
} catch {
|
|
5843
|
-
console.error("No scan found. Run: viberaven scan");
|
|
8351
|
+
artifact = await loadLastArtifact(startDir);
|
|
8352
|
+
} catch (error) {
|
|
8353
|
+
console.error(error instanceof Error ? error.message : "No scan found. Run: viberaven scan");
|
|
5844
8354
|
return 1;
|
|
5845
8355
|
}
|
|
5846
8356
|
const gap = pickGap(artifact, {
|
|
@@ -5852,6 +8362,16 @@ async function cmdPrompt(flags, positional) {
|
|
|
5852
8362
|
console.error("No matching gap. Run `viberaven scan` or pass --gap <id>.");
|
|
5853
8363
|
return 1;
|
|
5854
8364
|
}
|
|
8365
|
+
const skipCopy = flags["no-copy"] === true;
|
|
8366
|
+
if (!skipCopy) {
|
|
8367
|
+
try {
|
|
8368
|
+
await copyToClipboard(gap.copyPrompt);
|
|
8369
|
+
console.log(`Copied to clipboard: ${gap.title}`);
|
|
8370
|
+
return 0;
|
|
8371
|
+
} catch (error) {
|
|
8372
|
+
console.warn(error instanceof Error ? error.message : String(error));
|
|
8373
|
+
}
|
|
8374
|
+
}
|
|
5855
8375
|
console.log(gap.copyPrompt);
|
|
5856
8376
|
return 0;
|
|
5857
8377
|
}
|
|
@@ -5910,10 +8430,14 @@ async function main() {
|
|
|
5910
8430
|
return 0;
|
|
5911
8431
|
}
|
|
5912
8432
|
if (!command) {
|
|
5913
|
-
|
|
5914
|
-
return
|
|
8433
|
+
await runInteractiveSession();
|
|
8434
|
+
return 0;
|
|
5915
8435
|
}
|
|
5916
8436
|
switch (command) {
|
|
8437
|
+
case "tui":
|
|
8438
|
+
case "interactive":
|
|
8439
|
+
await runInteractiveSession();
|
|
8440
|
+
return 0;
|
|
5917
8441
|
case "login":
|
|
5918
8442
|
await cmdLogin(flags);
|
|
5919
8443
|
return 0;
|
|
@@ -5924,6 +8448,8 @@ async function main() {
|
|
|
5924
8448
|
return cmdStatus();
|
|
5925
8449
|
case "scan":
|
|
5926
8450
|
return cmdScan(flags, positional);
|
|
8451
|
+
case "report":
|
|
8452
|
+
return cmdReport(flags, positional);
|
|
5927
8453
|
case "prompt":
|
|
5928
8454
|
return cmdPrompt(flags, positional);
|
|
5929
8455
|
case "stack":
|