@viberaven/cli 0.1.0-beta.1 → 0.1.0-beta.3
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 +114 -46
- package/README.md +32 -14
- package/assets/report/report-cli.css +602 -348
- package/assets/report/station.css +8008 -4625
- package/dist/cli.js +2537 -251
- package/dist/cli.js.map +4 -4
- package/dist/report/report-cli.css +602 -348
- package/dist/report/station.css +8008 -4625
- package/package.json +1 -1
- package/templates/AGENTS.snippet.md +24 -0
- package/assets/report/assets/raven-mark.svg +0 -7
- package/dist/report/assets/raven-mark.svg +0 -7
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,10 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
9
9
|
var __commonJS = (cb, mod) => function __require() {
|
|
10
10
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
11
|
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
12
16
|
var __copyProps = (to, from, except, desc) => {
|
|
13
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -25,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
29
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
30
|
mod
|
|
27
31
|
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
33
|
|
|
29
34
|
// node_modules/picocolors/picocolors.js
|
|
30
35
|
var require_picocolors = __commonJS({
|
|
@@ -155,6 +160,13 @@ var require_src = __commonJS({
|
|
|
155
160
|
});
|
|
156
161
|
|
|
157
162
|
// src/cli.ts
|
|
163
|
+
var cli_exports = {};
|
|
164
|
+
__export(cli_exports, {
|
|
165
|
+
buildHelpText: () => buildHelpText,
|
|
166
|
+
formatScanJsonStdout: () => formatScanJsonStdout,
|
|
167
|
+
parseArgs: () => parseArgs
|
|
168
|
+
});
|
|
169
|
+
module.exports = __toCommonJS(cli_exports);
|
|
158
170
|
var import_node_path8 = require("node:path");
|
|
159
171
|
|
|
160
172
|
// src/config.ts
|
|
@@ -241,8 +253,18 @@ async function loadCredentials() {
|
|
|
241
253
|
email: typeof parsed.email === "string" ? parsed.email : void 0,
|
|
242
254
|
plan: typeof parsed.plan === "string" ? parsed.plan : void 0
|
|
243
255
|
};
|
|
244
|
-
} catch {
|
|
245
|
-
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (error.code !== "ENOENT") {
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
const accessToken = process.env.VIBERAVEN_ACCESS_TOKEN?.trim();
|
|
261
|
+
if (!accessToken) {
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
accessToken,
|
|
266
|
+
apiBaseUrl: resolveApiBaseUrl()
|
|
267
|
+
};
|
|
246
268
|
}
|
|
247
269
|
}
|
|
248
270
|
async function saveCredentials(credentials) {
|
|
@@ -454,6 +476,16 @@ function isRecord(value) {
|
|
|
454
476
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
455
477
|
}
|
|
456
478
|
|
|
479
|
+
// src/statusLabels.ts
|
|
480
|
+
var READY = "READY";
|
|
481
|
+
var LOGIN_REQUIRED = "LOGIN_REQUIRED";
|
|
482
|
+
var UPGRADE_REQUIRED = "UPGRADE_REQUIRED";
|
|
483
|
+
var MANUAL_ACTION_REQUIRED = "MANUAL_ACTION_REQUIRED";
|
|
484
|
+
var ERROR = "ERROR";
|
|
485
|
+
function formatAgentStatus(label, message) {
|
|
486
|
+
return `${label}: ${message}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
457
489
|
// src/account.ts
|
|
458
490
|
async function fetchAccountMe(apiBaseUrl, accessToken) {
|
|
459
491
|
const url = `${normalizeBaseUrl(apiBaseUrl)}/v1/account/me`;
|
|
@@ -510,8 +542,9 @@ function formatUsageLine(usage) {
|
|
|
510
542
|
function formatScanLimitMessage(upgradeUrl) {
|
|
511
543
|
return [
|
|
512
544
|
"",
|
|
513
|
-
"Free scan limit reached. Upgrade to Pro to continue.",
|
|
545
|
+
formatAgentStatus(UPGRADE_REQUIRED, "Free scan limit reached. Upgrade to Pro to continue."),
|
|
514
546
|
"Your last scan artifacts are unchanged if you already ran a scan in this repo.",
|
|
547
|
+
"Do not keep retrying this scan until the user upgrades or quota resets.",
|
|
515
548
|
"",
|
|
516
549
|
`Upgrade & account: ${upgradeUrl}`,
|
|
517
550
|
""
|
|
@@ -539,7 +572,7 @@ async function runDeviceLogin(apiBaseUrl) {
|
|
|
539
572
|
continue;
|
|
540
573
|
}
|
|
541
574
|
if (result.status === "expired") {
|
|
542
|
-
throw new Error("Sign-in expired.
|
|
575
|
+
throw new Error(formatAgentStatus(LOGIN_REQUIRED, "Sign-in expired. Ask the user to run `npx -y @viberaven/cli@beta login`, complete browser/device approval, then rerun `npx -y @viberaven/cli@beta scan`."));
|
|
543
576
|
}
|
|
544
577
|
if (result.status === "denied") {
|
|
545
578
|
throw new Error("Sign-in was denied.");
|
|
@@ -570,7 +603,7 @@ async function runDeviceLogin(apiBaseUrl) {
|
|
|
570
603
|
return;
|
|
571
604
|
}
|
|
572
605
|
}
|
|
573
|
-
throw new Error("Sign-in timed out.
|
|
606
|
+
throw new Error(formatAgentStatus(LOGIN_REQUIRED, "Sign-in timed out. Ask the user to run `npx -y @viberaven/cli@beta login`, complete browser/device approval, then rerun `npx -y @viberaven/cli@beta scan`."));
|
|
574
607
|
}
|
|
575
608
|
function buildVerificationUrl(verificationUrl, deviceCode) {
|
|
576
609
|
try {
|
|
@@ -586,7 +619,7 @@ async function requireCredentials(apiBaseUrl) {
|
|
|
586
619
|
const creds = await loadCredentials();
|
|
587
620
|
const base = apiBaseUrl ?? creds?.apiBaseUrl ?? resolveApiBaseUrl();
|
|
588
621
|
if (!creds?.accessToken) {
|
|
589
|
-
throw new Error("Not signed in.
|
|
622
|
+
throw new Error(formatAgentStatus(LOGIN_REQUIRED, "Not signed in. Ask the user to run `npx -y @viberaven/cli@beta login`, complete browser/device approval, then rerun `npx -y @viberaven/cli@beta scan`."));
|
|
590
623
|
}
|
|
591
624
|
return { accessToken: creds.accessToken, apiBaseUrl: base };
|
|
592
625
|
}
|
|
@@ -1442,6 +1475,17 @@ var PROVIDERS = [
|
|
|
1442
1475
|
provider("authjs", "Auth.js", ["authjs", "auth", "nextauth"], ["auth"], ["auth"], "authjs", {
|
|
1443
1476
|
docsUrl: "https://authjs.dev"
|
|
1444
1477
|
}),
|
|
1478
|
+
provider("auth0", "Auth0", ["auth0"], ["auth"], ["auth"], "auth0", {
|
|
1479
|
+
docsUrl: "https://auth0.com/docs",
|
|
1480
|
+
dashboardUrl: "https://manage.auth0.com"
|
|
1481
|
+
}),
|
|
1482
|
+
provider("better-auth", "Better Auth", ["betterauth", "better-auth"], ["auth"], ["auth"], "better-auth", {
|
|
1483
|
+
docsUrl: "https://www.better-auth.com/docs"
|
|
1484
|
+
}),
|
|
1485
|
+
provider("firebase", "Firebase", ["firebase", "firestore"], ["database"], ["database"], "firebase", {
|
|
1486
|
+
docsUrl: "https://firebase.google.com/docs",
|
|
1487
|
+
dashboardUrl: "https://console.firebase.google.com"
|
|
1488
|
+
}),
|
|
1445
1489
|
provider("neon", "Neon", ["neon"], ["database"], ["database"], "neon", {
|
|
1446
1490
|
docsUrl: "https://neon.tech/docs",
|
|
1447
1491
|
dashboardUrl: "https://console.neon.tech",
|
|
@@ -1511,6 +1555,10 @@ var PROVIDERS = [
|
|
|
1511
1555
|
dashboardUrl: "https://polar.sh/dashboard",
|
|
1512
1556
|
verification: { supportsReadOnly: false }
|
|
1513
1557
|
}),
|
|
1558
|
+
provider("lemon-squeezy", "Lemon Squeezy", ["lemonsqueezy", "lemon-squeezy", "lemon squeezy"], ["payments"], ["payments"], "lemon-squeezy", {
|
|
1559
|
+
docsUrl: "https://docs.lemonsqueezy.com",
|
|
1560
|
+
dashboardUrl: "https://app.lemonsqueezy.com"
|
|
1561
|
+
}),
|
|
1514
1562
|
provider("vercel", "Vercel", ["vercel"], ["deployment"], ["deployment"], "vercel", {
|
|
1515
1563
|
docsUrl: "https://vercel.com/docs",
|
|
1516
1564
|
dashboardUrl: "https://vercel.com/dashboard",
|
|
@@ -1523,7 +1571,7 @@ var PROVIDERS = [
|
|
|
1523
1571
|
},
|
|
1524
1572
|
verification: { supportsReadOnly: true }
|
|
1525
1573
|
}),
|
|
1526
|
-
provider("netlify", "Netlify", ["netlify"], ["deployment"], [], "netlify", {
|
|
1574
|
+
provider("netlify", "Netlify", ["netlify"], ["deployment"], ["deployment"], "netlify", {
|
|
1527
1575
|
docsUrl: "https://docs.netlify.com",
|
|
1528
1576
|
dashboardUrl: "https://app.netlify.com",
|
|
1529
1577
|
mcp: {
|
|
@@ -1535,7 +1583,27 @@ var PROVIDERS = [
|
|
|
1535
1583
|
},
|
|
1536
1584
|
verification: { supportsReadOnly: false }
|
|
1537
1585
|
}),
|
|
1538
|
-
provider("
|
|
1586
|
+
provider("render", "Render", ["render", "rendercom"], ["deployment"], ["deployment"], "render", {
|
|
1587
|
+
docsUrl: "https://render.com/docs",
|
|
1588
|
+
dashboardUrl: "https://dashboard.render.com"
|
|
1589
|
+
}),
|
|
1590
|
+
provider("railway", "Railway", ["railway", "railwayapp"], ["deployment"], ["deployment"], "railway", {
|
|
1591
|
+
docsUrl: "https://docs.railway.com",
|
|
1592
|
+
dashboardUrl: "https://railway.com"
|
|
1593
|
+
}),
|
|
1594
|
+
provider("cloudflare", "Cloudflare", ["cloudflare", "cloudflarepages", "workers"], ["deployment"], ["deployment"], "cloudflare", {
|
|
1595
|
+
docsUrl: "https://developers.cloudflare.com",
|
|
1596
|
+
dashboardUrl: "https://dash.cloudflare.com",
|
|
1597
|
+
mcp: {
|
|
1598
|
+
label: "Cloudflare",
|
|
1599
|
+
serverName: "cloudflare-api",
|
|
1600
|
+
vscodeServer: { type: "http", url: "https://mcp.cloudflare.com/mcp" },
|
|
1601
|
+
cursorServer: { url: "https://mcp.cloudflare.com/mcp" },
|
|
1602
|
+
keyInstructions: "Cloudflare MCP uses Cloudflare account authentication. Finish browser sign-in or provide an API token only when prompted."
|
|
1603
|
+
},
|
|
1604
|
+
verification: { supportsReadOnly: true }
|
|
1605
|
+
}),
|
|
1606
|
+
provider("aws", "AWS", ["aws"], ["deployment"], ["deployment"], "aws", {
|
|
1539
1607
|
docsUrl: "https://docs.aws.amazon.com",
|
|
1540
1608
|
dashboardUrl: "https://console.aws.amazon.com"
|
|
1541
1609
|
}),
|
|
@@ -1567,6 +1635,18 @@ var PROVIDERS = [
|
|
|
1567
1635
|
docsUrl: "https://docs.logrocket.com",
|
|
1568
1636
|
dashboardUrl: "https://app.logrocket.com"
|
|
1569
1637
|
}),
|
|
1638
|
+
provider("github", "GitHub Actions", ["github", "githubactions"], ["testing"], [], "github", {
|
|
1639
|
+
docsUrl: "https://docs.github.com/actions",
|
|
1640
|
+
dashboardUrl: "https://github.com",
|
|
1641
|
+
mcp: {
|
|
1642
|
+
label: "GitHub",
|
|
1643
|
+
serverName: "github",
|
|
1644
|
+
vscodeServer: { type: "http", url: "https://api.githubcopilot.com/mcp/" },
|
|
1645
|
+
cursorServer: { url: "https://api.githubcopilot.com/mcp/" },
|
|
1646
|
+
keyInstructions: "GitHub MCP should use IDE/GitHub authentication. Use read-only repository, Actions, and branch-protection queries for verification."
|
|
1647
|
+
},
|
|
1648
|
+
verification: { supportsReadOnly: true }
|
|
1649
|
+
}),
|
|
1570
1650
|
provider("playwright", "Playwright", ["playwright", "playwrighttest"], ["testing"], [], "playwright", {
|
|
1571
1651
|
docsUrl: "https://playwright.dev/docs/intro",
|
|
1572
1652
|
mcp: {
|
|
@@ -1590,7 +1670,7 @@ var PROVIDERS = [
|
|
|
1590
1670
|
},
|
|
1591
1671
|
verification: { supportsReadOnly: false }
|
|
1592
1672
|
}),
|
|
1593
|
-
provider("bot-protection", "Bot protection", ["botprotection", "cloudflareturnstile", "turnstile"
|
|
1673
|
+
provider("bot-protection", "Bot protection", ["botprotection", "cloudflareturnstile", "turnstile"], ["security"], ["security"], "bot-protection", {
|
|
1594
1674
|
docsUrl: "https://developers.cloudflare.com/turnstile",
|
|
1595
1675
|
dashboardUrl: "https://dash.cloudflare.com",
|
|
1596
1676
|
mcp: {
|
|
@@ -1773,6 +1853,269 @@ function titleizeProvider(provider2) {
|
|
|
1773
1853
|
);
|
|
1774
1854
|
}
|
|
1775
1855
|
|
|
1856
|
+
// ../../src/station/verificationLayer/types.ts
|
|
1857
|
+
function providerCheckId(provider2, checkKey) {
|
|
1858
|
+
return `${provider2}:${checkKey}`;
|
|
1859
|
+
}
|
|
1860
|
+
var PROVIDER_CONNECTION_BLOCKS_VERIFY = [
|
|
1861
|
+
"not_configured",
|
|
1862
|
+
"configured",
|
|
1863
|
+
"unknown_runtime",
|
|
1864
|
+
"unsupported"
|
|
1865
|
+
];
|
|
1866
|
+
function coerceProviderVerificationStatus(status, connectionState) {
|
|
1867
|
+
if (status !== "verified") {
|
|
1868
|
+
return status;
|
|
1869
|
+
}
|
|
1870
|
+
if (PROVIDER_CONNECTION_BLOCKS_VERIFY.includes(connectionState)) {
|
|
1871
|
+
return connectionState === "not_configured" || connectionState === "configured" ? "needs_mcp" : "unknown";
|
|
1872
|
+
}
|
|
1873
|
+
return status;
|
|
1874
|
+
}
|
|
1875
|
+
function mapVerificationStatusToMissionStatus(status) {
|
|
1876
|
+
switch (status) {
|
|
1877
|
+
case "verified":
|
|
1878
|
+
return "passed";
|
|
1879
|
+
case "missing":
|
|
1880
|
+
return "missing";
|
|
1881
|
+
case "unknown":
|
|
1882
|
+
return "unknown";
|
|
1883
|
+
case "needs_mcp":
|
|
1884
|
+
return "needs-connection";
|
|
1885
|
+
case "manual":
|
|
1886
|
+
return "manual-required";
|
|
1887
|
+
default:
|
|
1888
|
+
return "failed";
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
function mapEvidenceSourceToLegacyEvidenceClass(source, status) {
|
|
1892
|
+
if (source === "repo") {
|
|
1893
|
+
return status === "verified" ? "repo-verified" : "missing-repo-fix";
|
|
1894
|
+
}
|
|
1895
|
+
if (source === "manual") {
|
|
1896
|
+
return "manual-dashboard";
|
|
1897
|
+
}
|
|
1898
|
+
if (source === "mcp") {
|
|
1899
|
+
return "mcp-verifier";
|
|
1900
|
+
}
|
|
1901
|
+
return "mcp-verifier";
|
|
1902
|
+
}
|
|
1903
|
+
function isProviderLayerCheck(check) {
|
|
1904
|
+
return check.evidenceSource === "provider" || check.evidenceSource === "mcp";
|
|
1905
|
+
}
|
|
1906
|
+
function isRepoLayerCheck(check) {
|
|
1907
|
+
return check.evidenceSource === "repo";
|
|
1908
|
+
}
|
|
1909
|
+
function computeLayerReadinessPercent(checks, layer) {
|
|
1910
|
+
const filtered = checks.filter(
|
|
1911
|
+
(check) => layer === "repo" ? isRepoLayerCheck(check) : isProviderLayerCheck(check)
|
|
1912
|
+
);
|
|
1913
|
+
if (filtered.length === 0) {
|
|
1914
|
+
return 100;
|
|
1915
|
+
}
|
|
1916
|
+
const verified = filtered.filter((check) => check.status === "verified").length;
|
|
1917
|
+
return Math.round(verified / filtered.length * 100);
|
|
1918
|
+
}
|
|
1919
|
+
function aggregateReadinessPercents(providerResults) {
|
|
1920
|
+
if (providerResults.length === 0) {
|
|
1921
|
+
return { repoReadinessPercent: 100, providerReadinessPercent: 100 };
|
|
1922
|
+
}
|
|
1923
|
+
const repoSum = providerResults.reduce((sum, r) => sum + r.repoReadinessPercent, 0);
|
|
1924
|
+
const providerSum = providerResults.reduce((sum, r) => sum + r.providerReadinessPercent, 0);
|
|
1925
|
+
return {
|
|
1926
|
+
repoReadinessPercent: Math.round(repoSum / providerResults.length),
|
|
1927
|
+
providerReadinessPercent: Math.round(providerSum / providerResults.length)
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// ../../src/station/verificationLayer/mergeIntoMissionGraph.ts
|
|
1932
|
+
var PROVIDER_WIRING_KEY = {
|
|
1933
|
+
vercel: "vercel-deployment",
|
|
1934
|
+
supabase: "supabase-database",
|
|
1935
|
+
stripe: "stripe-payments"
|
|
1936
|
+
};
|
|
1937
|
+
function mergeVerificationIntoMissionGraph(graph, layer) {
|
|
1938
|
+
const providerMissions = graph.areas.flatMap((area) => area.providerMissions);
|
|
1939
|
+
const missionByKey = new Map(providerMissions.map((mission) => [mission.key, mission]));
|
|
1940
|
+
for (const layerCheck of layer.checks) {
|
|
1941
|
+
if (layerCheck.evidenceSource === "repo") {
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
const mission = resolveTargetMission(graph, missionByKey, layerCheck);
|
|
1945
|
+
if (!mission) {
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
if (mission.checks.some((check) => check.verificationCheckId === layerCheck.id || check.id === missionRowId(layerCheck.id))) {
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
mission.checks.push(verificationCheckToMissionCheck(layerCheck, mission));
|
|
1952
|
+
}
|
|
1953
|
+
recomputeMissionReadiness(providerMissions);
|
|
1954
|
+
const areas = rebuildAreas(providerMissions);
|
|
1955
|
+
return {
|
|
1956
|
+
...graph,
|
|
1957
|
+
areas,
|
|
1958
|
+
byArea: areas.reduce((acc, area) => {
|
|
1959
|
+
acc[area.key] = area;
|
|
1960
|
+
return acc;
|
|
1961
|
+
}, {}),
|
|
1962
|
+
byProvider: providerMissions.reduce((acc, mission) => {
|
|
1963
|
+
acc[mission.key] = mission;
|
|
1964
|
+
return acc;
|
|
1965
|
+
}, {}),
|
|
1966
|
+
verificationLayer: layer
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
function resolveTargetMission(graph, missionByKey, layerCheck) {
|
|
1970
|
+
const wiringKey = PROVIDER_WIRING_KEY[layerCheck.provider];
|
|
1971
|
+
if (wiringKey) {
|
|
1972
|
+
return missionByKey.get(wiringKey) ?? graph.byProvider[wiringKey];
|
|
1973
|
+
}
|
|
1974
|
+
if (layerCheck.provider === "github") {
|
|
1975
|
+
return missionByKey.get("vitest-testing") ?? missionByKey.get("playwright-testing") ?? ensureGitHubMission(graph, missionByKey, layerCheck.area);
|
|
1976
|
+
}
|
|
1977
|
+
return void 0;
|
|
1978
|
+
}
|
|
1979
|
+
function ensureGitHubMission(graph, missionByKey, area) {
|
|
1980
|
+
const key = "vitest-testing";
|
|
1981
|
+
const existing = missionByKey.get(key);
|
|
1982
|
+
if (existing) {
|
|
1983
|
+
return existing;
|
|
1984
|
+
}
|
|
1985
|
+
const mission = {
|
|
1986
|
+
key,
|
|
1987
|
+
provider: "vitest",
|
|
1988
|
+
providerLabel: "GitHub Actions",
|
|
1989
|
+
area,
|
|
1990
|
+
promptSubject: "GitHub Actions CI",
|
|
1991
|
+
readinessPercent: 0,
|
|
1992
|
+
repoReadinessPercent: 100,
|
|
1993
|
+
providerReadinessPercent: 0,
|
|
1994
|
+
checks: []
|
|
1995
|
+
};
|
|
1996
|
+
missionByKey.set(key, mission);
|
|
1997
|
+
const areaEntry = graph.areas.find((entry) => entry.key === area);
|
|
1998
|
+
if (areaEntry) {
|
|
1999
|
+
areaEntry.providerMissions.push(mission);
|
|
2000
|
+
}
|
|
2001
|
+
return mission;
|
|
2002
|
+
}
|
|
2003
|
+
function missionRowId(verificationCheckId) {
|
|
2004
|
+
return `vl-${verificationCheckId}`;
|
|
2005
|
+
}
|
|
2006
|
+
function verificationCheckToMissionCheck(layerCheck, mission) {
|
|
2007
|
+
const evidenceClass = legacyEvidenceClassFor(layerCheck);
|
|
2008
|
+
const status = mapVerificationStatusToMissionStatus(layerCheck.status);
|
|
2009
|
+
return {
|
|
2010
|
+
id: missionRowId(layerCheck.id),
|
|
2011
|
+
label: layerCheck.title,
|
|
2012
|
+
providerKey: mission.key,
|
|
2013
|
+
providerLabel: mission.providerLabel,
|
|
2014
|
+
area: layerCheck.area,
|
|
2015
|
+
evidenceClass,
|
|
2016
|
+
status,
|
|
2017
|
+
evidence: [...layerCheck.repoSignals, ...layerCheck.providerSignals, ...layerCheck.evidenceRefs],
|
|
2018
|
+
promptHint: layerCheck.manualAction ?? layerCheck.description,
|
|
2019
|
+
evidenceSource: layerCheck.evidenceSource,
|
|
2020
|
+
verificationStatus: layerCheck.status,
|
|
2021
|
+
verificationCheckId: layerCheck.id
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
function legacyEvidenceClassFor(layerCheck) {
|
|
2025
|
+
return mapEvidenceSourceToLegacyEvidenceClass(layerCheck.evidenceSource, layerCheck.status);
|
|
2026
|
+
}
|
|
2027
|
+
function recomputeMissionReadiness(missions) {
|
|
2028
|
+
for (const mission of missions) {
|
|
2029
|
+
mission.repoReadinessPercent = readinessPercentForRepoChecks(mission.checks);
|
|
2030
|
+
mission.providerReadinessPercent = readinessPercentForProviderChecks(mission.checks);
|
|
2031
|
+
mission.readinessPercent = mission.repoReadinessPercent;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
function readinessPercentForRepoChecks(checks) {
|
|
2035
|
+
const repoChecks = checks.filter(isRepoLayerMissionCheck);
|
|
2036
|
+
if (repoChecks.length === 0) {
|
|
2037
|
+
return 100;
|
|
2038
|
+
}
|
|
2039
|
+
const verified = repoChecks.filter((check) => isVerifiedMissionCheck(check)).length;
|
|
2040
|
+
return Math.round(verified / repoChecks.length * 100);
|
|
2041
|
+
}
|
|
2042
|
+
function readinessPercentForProviderChecks(checks) {
|
|
2043
|
+
const providerChecks = checks.filter(isProviderLayerMissionCheck);
|
|
2044
|
+
if (providerChecks.length === 0) {
|
|
2045
|
+
return 100;
|
|
2046
|
+
}
|
|
2047
|
+
const verified = providerChecks.filter((check) => isVerifiedMissionCheck(check)).length;
|
|
2048
|
+
return Math.round(verified / providerChecks.length * 100);
|
|
2049
|
+
}
|
|
2050
|
+
function isRepoLayerMissionCheck(check) {
|
|
2051
|
+
if (check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.evidenceSource === "manual") {
|
|
2052
|
+
return false;
|
|
2053
|
+
}
|
|
2054
|
+
if (check.evidenceSource === "repo") {
|
|
2055
|
+
return true;
|
|
2056
|
+
}
|
|
2057
|
+
return check.evidenceClass === "repo-verified" || check.evidenceClass === "missing-repo-fix";
|
|
2058
|
+
}
|
|
2059
|
+
function isProviderLayerMissionCheck(check) {
|
|
2060
|
+
if (check.evidenceSource === "provider" || check.evidenceSource === "mcp") {
|
|
2061
|
+
return true;
|
|
2062
|
+
}
|
|
2063
|
+
return check.evidenceClass === "mcp-verifier";
|
|
2064
|
+
}
|
|
2065
|
+
function isVerifiedMissionCheck(check) {
|
|
2066
|
+
if (check.verificationStatus) {
|
|
2067
|
+
return check.verificationStatus === "verified";
|
|
2068
|
+
}
|
|
2069
|
+
return check.status === "passed" || check.status === "user-confirmed";
|
|
2070
|
+
}
|
|
2071
|
+
function rebuildAreas(providerMissions) {
|
|
2072
|
+
const byArea = /* @__PURE__ */ new Map();
|
|
2073
|
+
for (const mission of providerMissions) {
|
|
2074
|
+
const list = byArea.get(mission.area) ?? [];
|
|
2075
|
+
list.push(mission);
|
|
2076
|
+
byArea.set(mission.area, list);
|
|
2077
|
+
}
|
|
2078
|
+
const AREA_LABELS2 = {
|
|
2079
|
+
appFlow: "App Flow",
|
|
2080
|
+
frontend: "Frontend",
|
|
2081
|
+
backend: "Backend / API",
|
|
2082
|
+
database: "Database",
|
|
2083
|
+
auth: "Auth",
|
|
2084
|
+
payments: "Payments",
|
|
2085
|
+
deployment: "Deployment",
|
|
2086
|
+
monitoring: "Monitoring",
|
|
2087
|
+
security: "Security",
|
|
2088
|
+
testing: "Testing",
|
|
2089
|
+
landing: "Landing / Onboarding",
|
|
2090
|
+
errorHandling: "Error Handling"
|
|
2091
|
+
};
|
|
2092
|
+
return [...byArea.entries()].map(([key, missions]) => {
|
|
2093
|
+
const repoChecks = missions.flatMap((m2) => m2.checks).filter(isRepoLayerMissionCheck);
|
|
2094
|
+
const providerChecks = missions.flatMap((m2) => m2.checks).filter(isProviderLayerMissionCheck);
|
|
2095
|
+
const repoReadinessPercent = percentVerified(repoChecks);
|
|
2096
|
+
const providerReadinessPercent = percentVerified(providerChecks);
|
|
2097
|
+
const criticalCount = missions.flatMap((m2) => m2.checks).filter(
|
|
2098
|
+
(check) => isProviderLayerMissionCheck(check) && (check.verificationStatus === "missing" || check.status === "missing" || check.status === "failed")
|
|
2099
|
+
).length;
|
|
2100
|
+
return {
|
|
2101
|
+
key,
|
|
2102
|
+
label: AREA_LABELS2[key],
|
|
2103
|
+
readinessPercent: repoReadinessPercent,
|
|
2104
|
+
repoReadinessPercent,
|
|
2105
|
+
providerReadinessPercent,
|
|
2106
|
+
criticalCount,
|
|
2107
|
+
providerMissions: missions
|
|
2108
|
+
};
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
function percentVerified(checks) {
|
|
2112
|
+
if (checks.length === 0) {
|
|
2113
|
+
return 100;
|
|
2114
|
+
}
|
|
2115
|
+
const verified = checks.filter((check) => isVerifiedMissionCheck(check)).length;
|
|
2116
|
+
return Math.round(verified / checks.length * 100);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
1776
2119
|
// ../../src/station/missionGraph.ts
|
|
1777
2120
|
var AREA_LABELS = {
|
|
1778
2121
|
appFlow: "App Flow",
|
|
@@ -1802,13 +2145,14 @@ function buildMissionGraph(input) {
|
|
|
1802
2145
|
acc[area.key] = area;
|
|
1803
2146
|
return acc;
|
|
1804
2147
|
}, {});
|
|
1805
|
-
|
|
2148
|
+
const graph = {
|
|
1806
2149
|
areas,
|
|
1807
2150
|
byArea,
|
|
1808
2151
|
byProvider,
|
|
1809
2152
|
repositoryEvidence: input.repositoryEvidence,
|
|
1810
2153
|
...input.staticInfrastructureFlowGraph ? { staticInfrastructureFlowGraph: input.staticInfrastructureFlowGraph } : {}
|
|
1811
2154
|
};
|
|
2155
|
+
return input.verificationLayer ? mergeVerificationIntoMissionGraph(graph, input.verificationLayer) : graph;
|
|
1812
2156
|
}
|
|
1813
2157
|
function toProviderMission(summary) {
|
|
1814
2158
|
const checks = summary.items.map((item3) => toMissionCheck(summary, item3));
|
|
@@ -1832,6 +2176,8 @@ function toProviderMission(summary) {
|
|
|
1832
2176
|
area: summary.area,
|
|
1833
2177
|
promptSubject: summary.promptSubject,
|
|
1834
2178
|
readinessPercent: summary.readinessPercent,
|
|
2179
|
+
repoReadinessPercent: summary.readinessPercent,
|
|
2180
|
+
providerReadinessPercent: checks.some((check) => check.evidenceClass === "mcp-verifier") ? 0 : 100,
|
|
1835
2181
|
checks
|
|
1836
2182
|
};
|
|
1837
2183
|
}
|
|
@@ -1969,6 +2315,10 @@ function buildAreas(providerMissions) {
|
|
|
1969
2315
|
key,
|
|
1970
2316
|
label: AREA_LABELS[key],
|
|
1971
2317
|
readinessPercent: Math.round(passed / Math.max(actionableChecks.length, 1) * 100),
|
|
2318
|
+
repoReadinessPercent: Math.round(passed / Math.max(actionableChecks.length, 1) * 100),
|
|
2319
|
+
providerReadinessPercent: missions.some(
|
|
2320
|
+
(mission) => mission.checks.some((check) => check.evidenceClass === "mcp-verifier")
|
|
2321
|
+
) ? 0 : 100,
|
|
1972
2322
|
criticalCount: missing,
|
|
1973
2323
|
providerMissions: missions
|
|
1974
2324
|
};
|
|
@@ -2130,6 +2480,17 @@ var PROVIDER_RULES = [
|
|
|
2130
2480
|
imports: ["@supabase/supabase-js", "@supabase/ssr"],
|
|
2131
2481
|
docs: ["supabase"]
|
|
2132
2482
|
},
|
|
2483
|
+
{
|
|
2484
|
+
area: "database",
|
|
2485
|
+
provider: "firebase",
|
|
2486
|
+
label: "Firebase",
|
|
2487
|
+
packages: [/^firebase$/, /^firebase-admin$/, /@firebase\//],
|
|
2488
|
+
env: ["FIREBASE_PROJECT_ID", "NEXT_PUBLIC_FIREBASE_PROJECT_ID", "VITE_FIREBASE_PROJECT_ID", "GOOGLE_APPLICATION_CREDENTIALS"],
|
|
2489
|
+
paths: [/firebase/, /firestore/],
|
|
2490
|
+
imports: ["firebase/app", "firebase/firestore", "firebase-admin"],
|
|
2491
|
+
content: [{ pattern: /getFirestore\s*\(|collection\s*\(|firebase-admin/i, signal: "code: Firebase/Firestore usage" }],
|
|
2492
|
+
docs: ["firebase", "firestore"]
|
|
2493
|
+
},
|
|
2133
2494
|
{
|
|
2134
2495
|
area: "database",
|
|
2135
2496
|
provider: "neon",
|
|
@@ -2182,10 +2543,33 @@ var PROVIDER_RULES = [
|
|
|
2182
2543
|
label: "Auth.js",
|
|
2183
2544
|
packages: [/^next-auth$/, /^@auth\//],
|
|
2184
2545
|
env: ["AUTH_SECRET", "NEXTAUTH_SECRET", "NEXTAUTH_URL"],
|
|
2185
|
-
paths: [/
|
|
2546
|
+
paths: [/api\/auth\//],
|
|
2186
2547
|
imports: ["next-auth", "@auth/core", "@auth/nextjs"],
|
|
2548
|
+
content: [{ pattern: /NextAuth\s*\(|getServerSession|useSession\s*\(/i, signal: "code: Auth.js session or route handler" }],
|
|
2187
2549
|
docs: ["auth.js", "nextauth", "next-auth"]
|
|
2188
2550
|
},
|
|
2551
|
+
{
|
|
2552
|
+
area: "auth",
|
|
2553
|
+
provider: "auth0",
|
|
2554
|
+
label: "Auth0",
|
|
2555
|
+
packages: [/@auth0\//],
|
|
2556
|
+
env: ["AUTH0_SECRET", "AUTH0_ISSUER_BASE_URL", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET", "AUTH0_DOMAIN"],
|
|
2557
|
+
paths: [/api\/auth/, /auth0/],
|
|
2558
|
+
imports: ["@auth0/nextjs-auth0", "@auth0/auth0-react", "express-openid-connect"],
|
|
2559
|
+
content: [{ pattern: /handleAuth|withPageAuthRequired|withApiAuthRequired|getSession/i, signal: "code: Auth0 session or route protection" }],
|
|
2560
|
+
docs: ["auth0"]
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
area: "auth",
|
|
2564
|
+
provider: "better-auth",
|
|
2565
|
+
label: "Better Auth",
|
|
2566
|
+
packages: [/^better-auth$/],
|
|
2567
|
+
env: ["BETTER_AUTH_SECRET", "BETTER_AUTH_URL", "AUTH_SECRET", "DATABASE_URL"],
|
|
2568
|
+
paths: [/better-auth/, /auth\.[jt]s$/],
|
|
2569
|
+
imports: ["better-auth"],
|
|
2570
|
+
content: [{ pattern: /betterAuth\s*\(|auth\.api|auth\.handler/i, signal: "code: Better Auth config" }],
|
|
2571
|
+
docs: ["better auth", "better-auth"]
|
|
2572
|
+
},
|
|
2189
2573
|
{
|
|
2190
2574
|
area: "payments",
|
|
2191
2575
|
provider: "stripe",
|
|
@@ -2206,6 +2590,28 @@ var PROVIDER_RULES = [
|
|
|
2206
2590
|
imports: ["@paddle/paddle-js", "@paddle/paddle-node-sdk"],
|
|
2207
2591
|
docs: ["paddle"]
|
|
2208
2592
|
},
|
|
2593
|
+
{
|
|
2594
|
+
area: "payments",
|
|
2595
|
+
provider: "polar",
|
|
2596
|
+
label: "Polar",
|
|
2597
|
+
packages: [/@polar-sh\//],
|
|
2598
|
+
env: ["POLAR_ACCESS_TOKEN", "POLAR_WEBHOOK_SECRET", "POLAR_PRODUCT_ID", "POLAR_PRO_PRODUCT_ID"],
|
|
2599
|
+
paths: [/polar.*webhook/, /webhook.*polar/, /polar.*checkout/, /checkout.*polar/],
|
|
2600
|
+
imports: ["@polar-sh/sdk"],
|
|
2601
|
+
content: [{ pattern: /api\.polar\.sh|polar.*checkout|createCheckoutSession/i, signal: "code: Polar checkout or API usage" }],
|
|
2602
|
+
docs: ["polar"]
|
|
2603
|
+
},
|
|
2604
|
+
{
|
|
2605
|
+
area: "payments",
|
|
2606
|
+
provider: "lemon-squeezy",
|
|
2607
|
+
label: "Lemon Squeezy",
|
|
2608
|
+
packages: [/@lemonsqueezy\//, /^lemonsqueezy\.ts$/],
|
|
2609
|
+
env: ["LEMON_SQUEEZY_API_KEY", "LEMONSQUEEZY_API_KEY", "LEMON_SQUEEZY_WEBHOOK_SECRET", "LEMON_SQUEEZY_STORE_ID", "LEMON_SQUEEZY_VARIANT_ID"],
|
|
2610
|
+
paths: [/lemon.*webhook/, /webhook.*lemon/, /lemon.*checkout/, /checkout.*lemon/, /lemonsqueezy/],
|
|
2611
|
+
imports: ["@lemonsqueezy/lemonsqueezy.js", "lemonsqueezy.ts"],
|
|
2612
|
+
content: [{ pattern: /api\.lemonsqueezy\.com|lemon.*checkout|checkout_url|variant_id/i, signal: "code: Lemon Squeezy checkout or API usage" }],
|
|
2613
|
+
docs: ["lemon squeezy", "lemonsqueezy"]
|
|
2614
|
+
},
|
|
2209
2615
|
{
|
|
2210
2616
|
area: "deployment",
|
|
2211
2617
|
provider: "vercel",
|
|
@@ -2215,6 +2621,55 @@ var PROVIDER_RULES = [
|
|
|
2215
2621
|
paths: [/vercel\.json$/],
|
|
2216
2622
|
docs: ["vercel"]
|
|
2217
2623
|
},
|
|
2624
|
+
{
|
|
2625
|
+
area: "deployment",
|
|
2626
|
+
provider: "netlify",
|
|
2627
|
+
label: "Netlify",
|
|
2628
|
+
packages: [/@netlify\//],
|
|
2629
|
+
env: ["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"],
|
|
2630
|
+
paths: [/netlify\.toml$/, /\.netlify\//, /(^|\/)_redirects$/],
|
|
2631
|
+
imports: ["@netlify/functions"],
|
|
2632
|
+
docs: ["netlify"]
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
area: "deployment",
|
|
2636
|
+
provider: "render",
|
|
2637
|
+
label: "Render",
|
|
2638
|
+
env: ["RENDER_API_KEY", "RENDER_SERVICE_ID"],
|
|
2639
|
+
paths: [/render\.ya?ml$/, /(^|\/)\.render\//],
|
|
2640
|
+
content: [{ pattern: /render\.yaml|render\.yml|render deploy|render service/i, signal: "code: Render deployment config" }],
|
|
2641
|
+
docs: ["render.com", "render deployment"]
|
|
2642
|
+
},
|
|
2643
|
+
{
|
|
2644
|
+
area: "deployment",
|
|
2645
|
+
provider: "railway",
|
|
2646
|
+
label: "Railway",
|
|
2647
|
+
env: ["RAILWAY_TOKEN", "RAILWAY_PROJECT_ID", "RAILWAY_SERVICE_ID"],
|
|
2648
|
+
paths: [/railway\.json$/, /nixpacks\.toml$/, /(^|\/)Procfile$/],
|
|
2649
|
+
content: [{ pattern: /railway up|railway deploy|nixpacks|railway\.json/i, signal: "code: Railway deployment config" }],
|
|
2650
|
+
docs: ["railway"]
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
area: "deployment",
|
|
2654
|
+
provider: "cloudflare",
|
|
2655
|
+
label: "Cloudflare",
|
|
2656
|
+
packages: [/^wrangler$/, /@cloudflare\//],
|
|
2657
|
+
env: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID", "CF_PAGES"],
|
|
2658
|
+
paths: [/wrangler\.toml$/, /wrangler\.json$/, /(^|\/)_headers$/, /(^|\/)_redirects$/],
|
|
2659
|
+
imports: ["@cloudflare/workers-types"],
|
|
2660
|
+
content: [{ pattern: /compatibility_date|pages_build_output_dir|cloudflare pages|cloudflare workers/i, signal: "code: Cloudflare Pages/Workers config" }],
|
|
2661
|
+
docs: ["cloudflare", "workers", "pages"]
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
area: "deployment",
|
|
2665
|
+
provider: "aws",
|
|
2666
|
+
label: "AWS",
|
|
2667
|
+
packages: [/@aws-sdk\//, /^aws-cdk-lib$/, /^serverless$/],
|
|
2668
|
+
env: ["AWS_REGION", "AWS_ACCESS_KEY_ID"],
|
|
2669
|
+
paths: [/serverless\.ya?ml$/, /template\.ya?ml$/, /cdk\.json$/, /amplify\//],
|
|
2670
|
+
imports: ["aws-sdk", "@aws-sdk/client"],
|
|
2671
|
+
docs: ["aws", "amplify", "serverless"]
|
|
2672
|
+
},
|
|
2218
2673
|
{
|
|
2219
2674
|
area: "monitoring",
|
|
2220
2675
|
provider: "sentry",
|
|
@@ -3894,8 +4349,11 @@ function analyzeStackWiring(scan) {
|
|
|
3894
4349
|
analyzeSecretsHygiene(ctx),
|
|
3895
4350
|
analyzeClerkAuth(ctx),
|
|
3896
4351
|
analyzeAuthJsAuth(ctx),
|
|
4352
|
+
analyzeAuth0Auth(ctx),
|
|
4353
|
+
analyzeBetterAuth(ctx),
|
|
3897
4354
|
analyzeSupabaseAuth(ctx),
|
|
3898
4355
|
supabaseDatabase,
|
|
4356
|
+
analyzeFirebaseDatabase(ctx),
|
|
3899
4357
|
analyzeNeonDatabase(ctx),
|
|
3900
4358
|
analyzeTursoDatabase(ctx),
|
|
3901
4359
|
analyzeMongoDatabase(ctx),
|
|
@@ -3903,8 +4361,12 @@ function analyzeStackWiring(scan) {
|
|
|
3903
4361
|
analyzeStripePayments(ctx),
|
|
3904
4362
|
analyzePaddlePayments(ctx),
|
|
3905
4363
|
analyzePolarPayments(ctx),
|
|
4364
|
+
analyzeLemonSqueezyPayments(ctx),
|
|
3906
4365
|
analyzeVercelDeployment(ctx),
|
|
3907
4366
|
analyzeNetlifyDeployment(ctx),
|
|
4367
|
+
analyzeRenderDeployment(ctx),
|
|
4368
|
+
analyzeRailwayDeployment(ctx),
|
|
4369
|
+
analyzeCloudflareDeployment(ctx),
|
|
3908
4370
|
analyzeAwsDeployment(ctx),
|
|
3909
4371
|
analyzeSupabaseLanding(ctx),
|
|
3910
4372
|
analyzePostHogMonitoring(ctx),
|
|
@@ -4175,6 +4637,44 @@ function analyzeAuthJsAuth(ctx) {
|
|
|
4175
4637
|
]
|
|
4176
4638
|
});
|
|
4177
4639
|
}
|
|
4640
|
+
function analyzeAuth0Auth(ctx) {
|
|
4641
|
+
return summarize({
|
|
4642
|
+
key: "auth0-auth",
|
|
4643
|
+
provider: "auth0",
|
|
4644
|
+
providerLabel: "Auth0",
|
|
4645
|
+
area: "auth",
|
|
4646
|
+
areaLabel: "Auth",
|
|
4647
|
+
promptSubject: "Auth0 auth",
|
|
4648
|
+
items: [
|
|
4649
|
+
item2("package-installed", "Auth0 package installed", hasPackage2(ctx, [/@auth0\//]), packageEvidence2(ctx, [/@auth0\//]), "Install the Auth0 package that matches this framework."),
|
|
4650
|
+
item2("env-names-documented", "Auth0 env names documented", /auth0_secret|auth0_issuer_base_url|auth0_client_id|auth0_client_secret|auth0_domain/i.test(ctx.contentBlob), envEvidence2(ctx, [/auth0_secret/i, /auth0_issuer_base_url/i, /auth0_client_id/i, /auth0_client_secret/i, /auth0_domain/i]), "Document Auth0 issuer/domain, client ID, client secret, and app secret env names in safe examples."),
|
|
4651
|
+
item2("auth-route-found", "Auth0 callback or auth route found", /api\/auth|auth0|handleauth|callback/i.test(ctx.pathBlob + "\n" + ctx.contentBlob), pathEvidence2(ctx, /api\/auth|callback/i).concat(fileEvidence(ctx, /auth0|handleauth|callback/i)), "Expose the Auth0 callback/login/logout routes required by this framework."),
|
|
4652
|
+
item2("session-usage-found", "Auth0 session usage found", /getsession|withapirequiredauth|withpagerequiredauth|useuser\s*\(/i.test(ctx.contentBlob), fileEvidence(ctx, /getsession|withapirequiredauth|withpagerequiredauth|useuser\s*\(/i), "Use Auth0 session checks around authenticated app routes and API handlers."),
|
|
4653
|
+
item2("route-protection-found", "Protected route evidence found", /withpagerequiredauth|withapirequiredauth|middleware|protected|requires?auth/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /withpagerequiredauth|withapirequiredauth|middleware|protected|requires?auth/i), "Protect private routes with Auth0 middleware or server session guards."),
|
|
4654
|
+
secretSafetyItem(ctx, "secret-not-exposed", "Auth0 secrets not exposed to frontend", /auth0_client_secret|auth0_secret/i, "Keep Auth0 secrets in server-only env and never expose them through public env variables."),
|
|
4655
|
+
manualItem("production-dashboard-checked", "Production Auth0 app checked", "Confirm callback URLs, logout URLs, allowed origins, social connections, MFA, and production domain in Auth0.")
|
|
4656
|
+
]
|
|
4657
|
+
});
|
|
4658
|
+
}
|
|
4659
|
+
function analyzeBetterAuth(ctx) {
|
|
4660
|
+
return summarize({
|
|
4661
|
+
key: "better-auth-auth",
|
|
4662
|
+
provider: "better-auth",
|
|
4663
|
+
providerLabel: "Better Auth",
|
|
4664
|
+
area: "auth",
|
|
4665
|
+
areaLabel: "Auth",
|
|
4666
|
+
promptSubject: "Better Auth",
|
|
4667
|
+
items: [
|
|
4668
|
+
item2("package-installed", "Better Auth package installed", hasPackage2(ctx, [/^better-auth$/]), packageEvidence2(ctx, [/^better-auth$/]), "Install Better Auth for this app framework."),
|
|
4669
|
+
item2("env-names-documented", "Better Auth env names documented", /better_auth_secret|better_auth_url|auth_secret|database_url/i.test(ctx.contentBlob), envEvidence2(ctx, [/better_auth_secret/i, /better_auth_url/i, /auth_secret/i, /database_url/i]), "Document BETTER_AUTH_SECRET, BETTER_AUTH_URL, and database env names in safe examples."),
|
|
4670
|
+
item2("auth-config-found", "Better Auth config found", /betterauth\s*\(|better-auth|auth\.api|auth\.handler/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /betterauth\s*\(|better-auth|auth\.api|auth\.handler/i), "Add a central Better Auth config and route handler."),
|
|
4671
|
+
item2("database-adapter-found", "Auth database adapter or schema found", /database|adapter|drizzle|prisma|schema|migration/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /adapter|drizzle|prisma|schema|migration/i), "Persist Better Auth users/sessions through a real database adapter and migrations."),
|
|
4672
|
+
item2("session-usage-found", "Session usage found", /getsession|usesession|auth\.api\.getsession|session/i.test(ctx.contentBlob), fileEvidence(ctx, /getsession|usesession|auth\.api\.getsession|session/i), "Use Better Auth session reads around private product routes."),
|
|
4673
|
+
secretSafetyItem(ctx, "secret-not-exposed", "Better Auth secret not exposed to frontend", /better_auth_secret|auth_secret/i, "Keep Better Auth secrets in server-only env and never expose them through public env variables."),
|
|
4674
|
+
manualItem("production-auth-checked", "Production auth settings checked", "Confirm production base URL, trusted origins, OAuth apps, email settings, and session policy.")
|
|
4675
|
+
]
|
|
4676
|
+
});
|
|
4677
|
+
}
|
|
4178
4678
|
function analyzeSupabaseAuth(ctx) {
|
|
4179
4679
|
return summarize({
|
|
4180
4680
|
key: "supabase-auth",
|
|
@@ -4252,6 +4752,38 @@ function analyzePolarPayments(ctx) {
|
|
|
4252
4752
|
]
|
|
4253
4753
|
});
|
|
4254
4754
|
}
|
|
4755
|
+
function analyzeLemonSqueezyPayments(ctx) {
|
|
4756
|
+
return summarize({
|
|
4757
|
+
key: "lemon-squeezy-payments",
|
|
4758
|
+
provider: "lemon-squeezy",
|
|
4759
|
+
providerLabel: "Lemon Squeezy",
|
|
4760
|
+
area: "payments",
|
|
4761
|
+
areaLabel: "Payments",
|
|
4762
|
+
promptSubject: "Lemon Squeezy payments",
|
|
4763
|
+
items: [
|
|
4764
|
+
item2("api-client-found", "Lemon Squeezy SDK or API client found", hasPackage2(ctx, [/@lemonsqueezy\//, /^lemonsqueezy\.ts$/]) || /api\.lemonsqueezy\.com|lemonsqueezy|lemon_squeezy/i.test(ctx.contentBlob), packageEvidence2(ctx, [/@lemonsqueezy\//, /^lemonsqueezy\.ts$/]).concat(fileEvidence(ctx, /api\.lemonsqueezy\.com|lemonsqueezy|lemon_squeezy/i)), "Add a server-side Lemon Squeezy API client for checkout and subscription state."),
|
|
4765
|
+
item2("env-names-documented", "Lemon Squeezy env names documented", /lemon_squeezy_api_key|lemonsqueezy_api_key|lemon_squeezy_webhook_secret|lemon_squeezy_store_id|lemon_squeezy_variant_id/i.test(ctx.contentBlob), envEvidence2(ctx, [/lemon_squeezy_api_key/i, /lemonsqueezy_api_key/i, /lemon_squeezy_webhook_secret/i, /lemon_squeezy_store_id/i, /lemon_squeezy_variant_id/i]), "Document Lemon Squeezy API key, webhook secret, store ID, and variant/product ID env names."),
|
|
4766
|
+
item2("checkout-found", "Lemon Squeezy checkout flow found", /lemon.*checkout|checkout.*lemon|checkout_url|variant_id|store_id/i.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, /lemon.*checkout|checkout.*lemon|checkout_url|variant_id|store_id/i), "Create a checkout URL/session flow for paid plans."),
|
|
4767
|
+
item2("webhook-route-found", "Lemon Squeezy webhook route found", /lemon.*webhook|webhook.*lemon|lemonsqueezy.*webhook/i.test(ctx.pathBlob + "\n" + ctx.contentBlob), pathEvidence2(ctx, /lemon.*webhook|webhook.*lemon|lemonsqueezy.*webhook/i).concat(fileEvidence(ctx, /lemonsqueezy.*webhook|lemon.*webhook/i)), "Add a Lemon Squeezy webhook route for order/subscription lifecycle events."),
|
|
4768
|
+
item2("webhook-signature-found", "Lemon Squeezy webhook verification found", /x-signature|lemon_squeezy_webhook_secret|verify.*lemon|webhook.*signature/i.test(ctx.contentBlob), fileEvidence(ctx, /x-signature|lemon_squeezy_webhook_secret|verify.*lemon|webhook.*signature/i), "Verify Lemon Squeezy webhook signatures before processing billing events."),
|
|
4769
|
+
secretSafetyItem(ctx, "secret-not-exposed", "Lemon Squeezy secret not exposed to frontend", /lemon_squeezy_api_key|lemonsqueezy_api_key|lemon_squeezy_webhook_secret/i, "Move Lemon Squeezy API keys and webhook secrets to server-only code."),
|
|
4770
|
+
manualItem("production-dashboard-checked", "Production Lemon Squeezy store checked", "Confirm products, variants, tax/merchant settings, license keys if used, and webhook URL in Lemon Squeezy.")
|
|
4771
|
+
]
|
|
4772
|
+
});
|
|
4773
|
+
}
|
|
4774
|
+
function analyzeFirebaseDatabase(ctx) {
|
|
4775
|
+
return databaseSummary(ctx, {
|
|
4776
|
+
key: "firebase-database",
|
|
4777
|
+
provider: "firebase",
|
|
4778
|
+
providerLabel: "Firebase",
|
|
4779
|
+
promptSubject: "Firebase database",
|
|
4780
|
+
packagePatterns: [/^firebase$/, /^firebase-admin$/, /@firebase\//],
|
|
4781
|
+
envPatterns: [/firebase_project_id/i, /firestore_database_url/i, /next_public_firebase/i, /vite_firebase/i, /google_application_credentials/i],
|
|
4782
|
+
usagePatterns: [/firebase\/firestore/i, /getfirestore\s*\(|collection\s*\(|doc\s*\(|firebase-admin/i],
|
|
4783
|
+
manualLabel: "Production Firebase project checked",
|
|
4784
|
+
manualHint: "Confirm production project, Firestore rules, indexes, backups, service account scope, and billing limits in Firebase."
|
|
4785
|
+
});
|
|
4786
|
+
}
|
|
4255
4787
|
function analyzeNeonDatabase(ctx) {
|
|
4256
4788
|
return databaseSummary(ctx, {
|
|
4257
4789
|
key: "neon-database",
|
|
@@ -4344,6 +4876,72 @@ function analyzeNetlifyDeployment(ctx) {
|
|
|
4344
4876
|
]
|
|
4345
4877
|
});
|
|
4346
4878
|
}
|
|
4879
|
+
function analyzeRenderDeployment(ctx) {
|
|
4880
|
+
const base = deploymentSummary(ctx, {
|
|
4881
|
+
key: "render-deployment",
|
|
4882
|
+
provider: "render",
|
|
4883
|
+
providerLabel: "Render",
|
|
4884
|
+
promptSubject: "Render deployment",
|
|
4885
|
+
packagePatterns: [],
|
|
4886
|
+
configPatterns: [/render\.ya?ml/i, /(^|\n|\/)\.render\//i],
|
|
4887
|
+
envPatterns: [/render_api_key/i, /render_service_id/i],
|
|
4888
|
+
manualLabel: "Production Render service checked",
|
|
4889
|
+
manualHint: "Confirm service type, build/start commands, env groups, health checks, custom domain, autoscaling, and rollback strategy in Render."
|
|
4890
|
+
});
|
|
4891
|
+
const servicePattern = /render\.ya?ml|healthcheckpath|startcommand|buildcommand|envvars|services:\s*|type:\s*web/i;
|
|
4892
|
+
return summarize({
|
|
4893
|
+
...base,
|
|
4894
|
+
items: [
|
|
4895
|
+
...base.items.filter((entry) => entry.status !== "manual"),
|
|
4896
|
+
item2("service-config-found", "Render service config found", servicePattern.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, servicePattern).concat(pathEvidence2(ctx, servicePattern)), "Add render.yaml or deployment docs covering service type, commands, health checks, and env groups."),
|
|
4897
|
+
...base.items.filter((entry) => entry.status === "manual")
|
|
4898
|
+
]
|
|
4899
|
+
});
|
|
4900
|
+
}
|
|
4901
|
+
function analyzeRailwayDeployment(ctx) {
|
|
4902
|
+
const base = deploymentSummary(ctx, {
|
|
4903
|
+
key: "railway-deployment",
|
|
4904
|
+
provider: "railway",
|
|
4905
|
+
providerLabel: "Railway",
|
|
4906
|
+
promptSubject: "Railway deployment",
|
|
4907
|
+
packagePatterns: [],
|
|
4908
|
+
configPatterns: [/railway\.json/i, /nixpacks\.toml/i, /(^|\n|\/)Procfile\b/i],
|
|
4909
|
+
envPatterns: [/railway_token/i, /railway_project_id/i, /railway_service_id/i],
|
|
4910
|
+
manualLabel: "Production Railway service checked",
|
|
4911
|
+
manualHint: "Confirm service, variables, start command, volumes/databases, custom domain, deploy policy, and logs in Railway."
|
|
4912
|
+
});
|
|
4913
|
+
const servicePattern = /railway\.json|nixpacks\.toml|railway up|railway deploy|Procfile|startCommand|healthcheck/i;
|
|
4914
|
+
return summarize({
|
|
4915
|
+
...base,
|
|
4916
|
+
items: [
|
|
4917
|
+
...base.items.filter((entry) => entry.status !== "manual"),
|
|
4918
|
+
item2("service-config-found", "Railway service config found", servicePattern.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, servicePattern).concat(pathEvidence2(ctx, servicePattern)), "Add Railway/Nixpacks/Procfile config or deployment docs for commands, health checks, and service wiring."),
|
|
4919
|
+
...base.items.filter((entry) => entry.status === "manual")
|
|
4920
|
+
]
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
function analyzeCloudflareDeployment(ctx) {
|
|
4924
|
+
const base = deploymentSummary(ctx, {
|
|
4925
|
+
key: "cloudflare-deployment",
|
|
4926
|
+
provider: "cloudflare",
|
|
4927
|
+
providerLabel: "Cloudflare",
|
|
4928
|
+
promptSubject: "Cloudflare deployment",
|
|
4929
|
+
packagePatterns: [/^wrangler$/, /@cloudflare\//],
|
|
4930
|
+
configPatterns: [/wrangler\.toml/i, /wrangler\.json/i, /_headers\b/i, /_redirects\b/i],
|
|
4931
|
+
envPatterns: [/cloudflare_api_token/i, /cloudflare_account_id/i, /cf_pages/i],
|
|
4932
|
+
manualLabel: "Production Cloudflare project checked",
|
|
4933
|
+
manualHint: "Confirm Pages/Workers project, DNS, routes, compatibility date, env vars/secrets, cache rules, and rollback settings in Cloudflare."
|
|
4934
|
+
});
|
|
4935
|
+
const edgePattern = /wrangler\.toml|wrangler\.json|compatibility_date|pages_build_output_dir|workers_dev|routes?\s*=|cloudflare pages|cloudflare workers/i;
|
|
4936
|
+
return summarize({
|
|
4937
|
+
...base,
|
|
4938
|
+
items: [
|
|
4939
|
+
...base.items.filter((entry) => entry.status !== "manual"),
|
|
4940
|
+
item2("edge-config-found", "Cloudflare Pages or Workers config found", edgePattern.test(ctx.contentBlob + "\n" + ctx.pathBlob), fileEvidence(ctx, edgePattern).concat(pathEvidence2(ctx, edgePattern)), "Add Wrangler/Pages config for build output, compatibility date, routes, and edge runtime behavior."),
|
|
4941
|
+
...base.items.filter((entry) => entry.status === "manual")
|
|
4942
|
+
]
|
|
4943
|
+
});
|
|
4944
|
+
}
|
|
4347
4945
|
function analyzeAwsDeployment(ctx) {
|
|
4348
4946
|
const base = deploymentSummary(ctx, {
|
|
4349
4947
|
key: "aws-deployment",
|
|
@@ -5189,48 +5787,758 @@ function providerLabel2(provider2) {
|
|
|
5189
5787
|
);
|
|
5190
5788
|
}
|
|
5191
5789
|
|
|
5192
|
-
// ../../src/station/
|
|
5193
|
-
|
|
5194
|
-
|
|
5790
|
+
// ../../src/station/verificationLayer/shared/connection.ts
|
|
5791
|
+
var MCP_PROVIDER_MAP = {
|
|
5792
|
+
vercel: "vercel",
|
|
5793
|
+
supabase: "supabase",
|
|
5794
|
+
stripe: "stripe",
|
|
5795
|
+
github: "github"
|
|
5796
|
+
};
|
|
5797
|
+
function resolveProviderConnectionState(provider2, mcpVerifierState) {
|
|
5798
|
+
const registryProvider = MCP_PROVIDER_MAP[provider2];
|
|
5799
|
+
if (!registryProvider || !mcpVerifierState) {
|
|
5800
|
+
return "unknown_runtime";
|
|
5801
|
+
}
|
|
5802
|
+
const record = mcpVerifierState.records.find((entry) => entry.provider === registryProvider);
|
|
5803
|
+
if (!record) {
|
|
5804
|
+
return "unknown_runtime";
|
|
5805
|
+
}
|
|
5806
|
+
switch (record.status) {
|
|
5807
|
+
case "configured":
|
|
5808
|
+
return "configured";
|
|
5809
|
+
case "missing":
|
|
5810
|
+
return "not_configured";
|
|
5811
|
+
case "unsupported":
|
|
5812
|
+
return "unsupported";
|
|
5813
|
+
case "stale":
|
|
5814
|
+
return "configured";
|
|
5815
|
+
default:
|
|
5816
|
+
return "unknown_runtime";
|
|
5817
|
+
}
|
|
5195
5818
|
}
|
|
5196
|
-
function
|
|
5197
|
-
|
|
5819
|
+
function providerResultStatus(input) {
|
|
5820
|
+
if (input.connectionState === "connected") {
|
|
5821
|
+
return input.providerObservationMet ? "verified" : "missing";
|
|
5822
|
+
}
|
|
5823
|
+
if (!input.repoExpectationMet) {
|
|
5824
|
+
return "missing";
|
|
5825
|
+
}
|
|
5826
|
+
if (input.connectionState === "not_configured" || input.connectionState === "configured") {
|
|
5827
|
+
return "needs_mcp";
|
|
5828
|
+
}
|
|
5829
|
+
if (input.connectionState === "unsupported") {
|
|
5830
|
+
return "manual";
|
|
5831
|
+
}
|
|
5832
|
+
return "unknown";
|
|
5198
5833
|
}
|
|
5199
|
-
|
|
5200
|
-
|
|
5834
|
+
|
|
5835
|
+
// ../../src/station/verificationLayer/shared/buildCheck.ts
|
|
5836
|
+
function buildVerificationCheck(input) {
|
|
5837
|
+
const status = input.evidenceSource === "repo" ? input.repoExpectationMet ? "verified" : "missing" : providerResultStatus({
|
|
5838
|
+
connectionState: input.connectionState,
|
|
5839
|
+
repoExpectationMet: input.repoExpectationMet,
|
|
5840
|
+
providerObservationMet: input.providerObservationMet
|
|
5841
|
+
});
|
|
5842
|
+
return {
|
|
5843
|
+
id: providerCheckId(input.provider, input.checkKey),
|
|
5844
|
+
provider: input.provider,
|
|
5845
|
+
area: input.area,
|
|
5846
|
+
title: input.title,
|
|
5847
|
+
description: input.description,
|
|
5848
|
+
requiredEvidence: input.requiredEvidence ?? [],
|
|
5849
|
+
repoSignals: input.repoSignals,
|
|
5850
|
+
providerSignals: input.providerSignals,
|
|
5851
|
+
evidenceSource: input.evidenceSource,
|
|
5852
|
+
status,
|
|
5853
|
+
fixType: input.fixType,
|
|
5854
|
+
severity: input.severity,
|
|
5855
|
+
evidenceRefs: input.evidenceRefs ?? [],
|
|
5856
|
+
manualAction: input.manualAction
|
|
5857
|
+
};
|
|
5201
5858
|
}
|
|
5202
|
-
|
|
5859
|
+
|
|
5860
|
+
// ../../src/station/verificationLayer/shared/repoSignals.ts
|
|
5861
|
+
var ENV_NAME_PATTERN = /\b(?:process\.env|import\.meta\.env)\.([A-Z][A-Z0-9_]{2,})\b/g;
|
|
5862
|
+
var ENV_ASSIGNMENT_PATTERN = /^[ \t]*(?:export[ \t]+)?([A-Z][A-Z0-9_]{2,})[ \t]*=/gm;
|
|
5863
|
+
var SUPABASE_TABLE_PATTERN = /\.from\s*\(\s*['"]([a-z0-9_]+)['"]\s*\)/gi;
|
|
5864
|
+
var STRIPE_WEBHOOK_PATH_PATTERN = /(^|\/)(api\/webhooks\/stripe|api\/stripe\/webhook|webhooks\/stripe)[^/\s]*/i;
|
|
5865
|
+
var GITHUB_WORKFLOW_PATTERN = /(^|\/)\.github\/workflows\/([^/\n]+\.ya?ml)/i;
|
|
5866
|
+
function buildRepoScanContext(scan) {
|
|
5867
|
+
const files = visibleFiles4(scan);
|
|
5203
5868
|
return {
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5869
|
+
files,
|
|
5870
|
+
pathBlob: `${scan.fileTree}
|
|
5871
|
+
${scan.files.map((file) => file.path).join("\n")}`.replace(/\\/g, "/"),
|
|
5872
|
+
contentBlob: files.map((file) => file.lowerContent).join("\n"),
|
|
5873
|
+
deps: scan.packageDeps.map((dep) => dep.toLowerCase())
|
|
5874
|
+
};
|
|
5875
|
+
}
|
|
5876
|
+
function visibleFiles4(scan) {
|
|
5877
|
+
return scan.files.filter((file) => !file.isSecret && typeof file.content === "string").map((file) => ({
|
|
5878
|
+
path: file.path.replace(/\\/g, "/"),
|
|
5879
|
+
normalizedPath: file.path.replace(/\\/g, "/").toLowerCase(),
|
|
5880
|
+
content: file.content,
|
|
5881
|
+
lowerContent: file.content.toLowerCase()
|
|
5882
|
+
}));
|
|
5883
|
+
}
|
|
5884
|
+
function collectReferencedEnvNames(scan, repositoryEvidence) {
|
|
5885
|
+
const names = /* @__PURE__ */ new Set();
|
|
5886
|
+
for (const entry of repositoryEvidence.env) {
|
|
5887
|
+
if (entry.present || entry.evidence.length > 0) {
|
|
5888
|
+
names.add(entry.name);
|
|
5889
|
+
}
|
|
5890
|
+
}
|
|
5891
|
+
for (const file of visibleFiles4(scan)) {
|
|
5892
|
+
if (!/(^|\/)\.env(\.|$)|env\.example|readme/i.test(file.normalizedPath)) {
|
|
5893
|
+
ENV_NAME_PATTERN.lastIndex = 0;
|
|
5894
|
+
let match;
|
|
5895
|
+
while ((match = ENV_NAME_PATTERN.exec(file.content)) !== null) {
|
|
5896
|
+
names.add(match[1]);
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
if (/\.env\.example|env\.example/i.test(file.normalizedPath)) {
|
|
5900
|
+
ENV_ASSIGNMENT_PATTERN.lastIndex = 0;
|
|
5901
|
+
let assignment;
|
|
5902
|
+
while ((assignment = ENV_ASSIGNMENT_PATTERN.exec(file.content)) !== null) {
|
|
5903
|
+
names.add(assignment[1]);
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
const supplemental = collectEnvVarEvidence(scan, [...names]);
|
|
5908
|
+
for (const entry of supplemental) {
|
|
5909
|
+
if (entry.present || entry.evidence.length > 0) {
|
|
5910
|
+
names.add(entry.name);
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
return [...names].sort();
|
|
5914
|
+
}
|
|
5915
|
+
function collectEnvExampleNames(scan) {
|
|
5916
|
+
const names = /* @__PURE__ */ new Set();
|
|
5917
|
+
for (const file of visibleFiles4(scan)) {
|
|
5918
|
+
if (!/\.env\.example|env\.example/i.test(file.normalizedPath)) {
|
|
5919
|
+
continue;
|
|
5920
|
+
}
|
|
5921
|
+
ENV_ASSIGNMENT_PATTERN.lastIndex = 0;
|
|
5922
|
+
let assignment;
|
|
5923
|
+
while ((assignment = ENV_ASSIGNMENT_PATTERN.exec(file.content)) !== null) {
|
|
5924
|
+
names.add(assignment[1]);
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
return [...names].sort();
|
|
5928
|
+
}
|
|
5929
|
+
function hasRlsMigrationEvidence(repo) {
|
|
5930
|
+
return /\/policies\/|_rls\.sql|\brls\b/i.test(repo.pathBlob) || repo.files.some(
|
|
5931
|
+
(file) => /enable\s+row\s+level\s+security|create\s+policy|alter\s+table[\s\S]{0,200}enable\s+row\s+level/i.test(
|
|
5932
|
+
file.content
|
|
5933
|
+
)
|
|
5934
|
+
);
|
|
5935
|
+
}
|
|
5936
|
+
function collectSupabaseReferencedTables(repo) {
|
|
5937
|
+
const tables = /* @__PURE__ */ new Set();
|
|
5938
|
+
for (const file of repo.files) {
|
|
5939
|
+
SUPABASE_TABLE_PATTERN.lastIndex = 0;
|
|
5940
|
+
let match;
|
|
5941
|
+
while ((match = SUPABASE_TABLE_PATTERN.exec(file.content)) !== null) {
|
|
5942
|
+
tables.add(match[1]);
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5945
|
+
return [...tables].sort();
|
|
5946
|
+
}
|
|
5947
|
+
function findStripeWebhookRoute(repo) {
|
|
5948
|
+
const pathMatch = repo.pathBlob.match(STRIPE_WEBHOOK_PATH_PATTERN);
|
|
5949
|
+
if (pathMatch) {
|
|
5950
|
+
return pathMatch[0].replace(/^\//, "");
|
|
5951
|
+
}
|
|
5952
|
+
const file = repo.files.find((entry) => /stripe.*webhook|webhook.*stripe/i.test(entry.normalizedPath));
|
|
5953
|
+
return file ? file.path : null;
|
|
5954
|
+
}
|
|
5955
|
+
function hasStripeWebhookSignature(repo) {
|
|
5956
|
+
return /webhooks\.constructevent/i.test(repo.contentBlob);
|
|
5957
|
+
}
|
|
5958
|
+
function collectStripePriceEnvNames(repo, referencedEnv) {
|
|
5959
|
+
return referencedEnv.filter((name) => /^STRIPE_(PRICE_|PRODUCT_)/i.test(name) || /STRIPE.*PRICE/i.test(name));
|
|
5960
|
+
}
|
|
5961
|
+
function findGithubWorkflowPaths(repo) {
|
|
5962
|
+
const paths = /* @__PURE__ */ new Set();
|
|
5963
|
+
for (const line of repo.pathBlob.split(/\r?\n/)) {
|
|
5964
|
+
const match = line.match(GITHUB_WORKFLOW_PATTERN);
|
|
5965
|
+
if (match) {
|
|
5966
|
+
paths.add(line.trim());
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
return [...paths].sort();
|
|
5970
|
+
}
|
|
5971
|
+
|
|
5972
|
+
// ../../src/station/verificationLayer/mock/mockGitHubVerifier.ts
|
|
5973
|
+
var mockGitHubVerifier = {
|
|
5974
|
+
provider: "github",
|
|
5975
|
+
detectConfig(ctx) {
|
|
5976
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
5977
|
+
const workflows = findGithubWorkflowPaths(repo);
|
|
5978
|
+
const signals = [];
|
|
5979
|
+
if (ctx.scan.stackSignals.hasCI) {
|
|
5980
|
+
signals.push("stack: hasCI");
|
|
5981
|
+
}
|
|
5982
|
+
for (const workflow of workflows) {
|
|
5983
|
+
signals.push(`workflow: ${workflow}`);
|
|
5984
|
+
}
|
|
5985
|
+
const detected = workflows.length > 0 || Boolean(ctx.scan.stackSignals.hasCI);
|
|
5986
|
+
return { provider: "github", detected, signals };
|
|
5987
|
+
},
|
|
5988
|
+
connectStatus(ctx) {
|
|
5989
|
+
return resolveProviderConnectionState("github", ctx.mcpVerifierState);
|
|
5990
|
+
},
|
|
5991
|
+
runChecks(ctx) {
|
|
5992
|
+
const connectionState = this.connectStatus(ctx);
|
|
5993
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
5994
|
+
const workflows = findGithubWorkflowPaths(repo);
|
|
5995
|
+
return [
|
|
5996
|
+
buildVerificationCheck({
|
|
5997
|
+
provider: "github",
|
|
5998
|
+
checkKey: "actions-run-status",
|
|
5999
|
+
area: "testing",
|
|
6000
|
+
title: "GitHub Actions run status",
|
|
6001
|
+
description: "Recent workflow run conclusions require live GitHub API or MCP access.",
|
|
6002
|
+
evidenceSource: "provider",
|
|
6003
|
+
connectionState,
|
|
6004
|
+
repoExpectationMet: workflows.length > 0,
|
|
6005
|
+
providerObservationMet: false,
|
|
6006
|
+
fixType: "mcp-connect",
|
|
6007
|
+
severity: "warning",
|
|
6008
|
+
repoSignals: workflows.map((path) => `workflow: ${path}`),
|
|
6009
|
+
providerSignals: ["GitHub Actions API or MCP"],
|
|
6010
|
+
requiredEvidence: ["Latest workflow run status on default branch"]
|
|
6011
|
+
}),
|
|
6012
|
+
buildVerificationCheck({
|
|
6013
|
+
provider: "github",
|
|
6014
|
+
checkKey: "required-checks",
|
|
6015
|
+
area: "testing",
|
|
6016
|
+
title: "Required GitHub checks",
|
|
6017
|
+
description: "Branch protection and required check contexts need live GitHub verification.",
|
|
6018
|
+
evidenceSource: "provider",
|
|
6019
|
+
connectionState,
|
|
6020
|
+
repoExpectationMet: workflows.length > 0,
|
|
6021
|
+
providerObservationMet: false,
|
|
6022
|
+
fixType: "mcp-connect",
|
|
6023
|
+
severity: "warning",
|
|
6024
|
+
repoSignals: workflows,
|
|
6025
|
+
providerSignals: ["GitHub branch protection API or MCP"],
|
|
6026
|
+
requiredEvidence: ["Required status checks configured for production branch"]
|
|
6027
|
+
}),
|
|
6028
|
+
buildVerificationCheck({
|
|
6029
|
+
provider: "github",
|
|
6030
|
+
checkKey: "failed-checks-recent",
|
|
6031
|
+
area: "testing",
|
|
6032
|
+
title: "Recent failed GitHub checks",
|
|
6033
|
+
description: "Failed workflow runs in the last 7 days require live GitHub verification.",
|
|
6034
|
+
evidenceSource: "provider",
|
|
6035
|
+
connectionState,
|
|
6036
|
+
repoExpectationMet: workflows.length > 0,
|
|
6037
|
+
providerObservationMet: false,
|
|
6038
|
+
fixType: "mcp-connect",
|
|
6039
|
+
severity: "critical",
|
|
6040
|
+
repoSignals: workflows,
|
|
6041
|
+
providerSignals: ["GitHub check run API or MCP"],
|
|
6042
|
+
requiredEvidence: ["No failing required checks on latest default-branch commit"]
|
|
6043
|
+
})
|
|
6044
|
+
];
|
|
6045
|
+
},
|
|
6046
|
+
buildDiffs(ctx) {
|
|
6047
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6048
|
+
const workflows = findGithubWorkflowPaths(repo);
|
|
6049
|
+
if (workflows.length === 0) {
|
|
6050
|
+
return [];
|
|
6051
|
+
}
|
|
6052
|
+
return [
|
|
6053
|
+
{
|
|
6054
|
+
id: providerCheckId("github", "ci-status-unverified"),
|
|
6055
|
+
provider: "github",
|
|
6056
|
+
area: "testing",
|
|
6057
|
+
title: "CI status not verified",
|
|
6058
|
+
description: "GitHub workflow files exist in the repo, but recent Actions conclusions have not been fetched.",
|
|
6059
|
+
repoExpectation: `Workflows: ${workflows.join(", ")}`,
|
|
6060
|
+
providerActual: "Not verified (mock \u2014 connect GitHub MCP read-only)",
|
|
6061
|
+
severity: "warning",
|
|
6062
|
+
suggestedFix: "mcp-connect",
|
|
6063
|
+
evidenceRefs: workflows.map((path) => `workflow: ${path}`)
|
|
6064
|
+
}
|
|
6065
|
+
];
|
|
6066
|
+
}
|
|
6067
|
+
};
|
|
6068
|
+
|
|
6069
|
+
// ../../src/station/verificationLayer/mock/mockStripeVerifier.ts
|
|
6070
|
+
var REQUIRED_STRIPE_EVENTS = [
|
|
6071
|
+
"checkout.session.completed",
|
|
6072
|
+
"customer.subscription.updated",
|
|
6073
|
+
"customer.subscription.deleted",
|
|
6074
|
+
"invoice.payment_failed"
|
|
6075
|
+
];
|
|
6076
|
+
var MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT = false;
|
|
6077
|
+
var MOCK_STRIPE_CONFIGURED_EVENTS = /* @__PURE__ */ new Set();
|
|
6078
|
+
var MOCK_STRIPE_LIVE_PRICE_IDS = /* @__PURE__ */ new Set();
|
|
6079
|
+
var mockStripeVerifier = {
|
|
6080
|
+
provider: "stripe",
|
|
6081
|
+
detectConfig(ctx) {
|
|
6082
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6083
|
+
const signals = [];
|
|
6084
|
+
if (ctx.scan.stackSignals.hasStripe) {
|
|
6085
|
+
signals.push("stack: hasStripe");
|
|
6086
|
+
}
|
|
6087
|
+
if (repo.deps.some((dep) => dep === "stripe" || dep.startsWith("@stripe/"))) {
|
|
6088
|
+
signals.push("package: stripe");
|
|
6089
|
+
}
|
|
6090
|
+
if (findStripeWebhookRoute(repo)) {
|
|
6091
|
+
signals.push(`route: ${findStripeWebhookRoute(repo)}`);
|
|
6092
|
+
}
|
|
6093
|
+
const detected = signals.length > 0;
|
|
6094
|
+
return { provider: "stripe", detected, signals };
|
|
6095
|
+
},
|
|
6096
|
+
connectStatus(ctx) {
|
|
6097
|
+
return resolveProviderConnectionState("stripe", ctx.mcpVerifierState);
|
|
6098
|
+
},
|
|
6099
|
+
runChecks(ctx) {
|
|
6100
|
+
const connectionState = this.connectStatus(ctx);
|
|
6101
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6102
|
+
const webhookRoute = findStripeWebhookRoute(repo);
|
|
6103
|
+
const referencedEnv = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
|
|
6104
|
+
const priceEnvNames = collectStripePriceEnvNames(repo, referencedEnv);
|
|
6105
|
+
const missingEvents = REQUIRED_STRIPE_EVENTS.filter((event) => !MOCK_STRIPE_CONFIGURED_EVENTS.has(event));
|
|
6106
|
+
return [
|
|
6107
|
+
buildVerificationCheck({
|
|
6108
|
+
provider: "stripe",
|
|
6109
|
+
checkKey: "dashboard-webhook-endpoint",
|
|
6110
|
+
area: "payments",
|
|
6111
|
+
title: "Stripe dashboard webhook endpoint",
|
|
6112
|
+
description: webhookRoute ? "Repo defines a webhook route; Stripe dashboard endpoint must match and be reachable." : "No Stripe webhook route found in repo; dashboard endpoint check still applies when Stripe is used.",
|
|
6113
|
+
evidenceSource: "provider",
|
|
6114
|
+
connectionState,
|
|
6115
|
+
repoExpectationMet: Boolean(webhookRoute),
|
|
6116
|
+
providerObservationMet: MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT,
|
|
6117
|
+
fixType: "provider-config",
|
|
6118
|
+
severity: "critical",
|
|
6119
|
+
repoSignals: webhookRoute ? [`route: ${webhookRoute}`] : [],
|
|
6120
|
+
providerSignals: ["Stripe webhooks API or MCP"],
|
|
6121
|
+
requiredEvidence: ["Webhook endpoint URL registered in Stripe"],
|
|
6122
|
+
manualAction: "Register the production webhook URL in Stripe Dashboard \u2192 Developers \u2192 Webhooks."
|
|
6123
|
+
}),
|
|
6124
|
+
buildVerificationCheck({
|
|
6125
|
+
provider: "stripe",
|
|
6126
|
+
checkKey: "webhook-events",
|
|
6127
|
+
area: "payments",
|
|
6128
|
+
title: "Stripe webhook event subscriptions",
|
|
6129
|
+
description: missingEvents.length > 0 ? `Missing required events: ${missingEvents.join(", ")}` : "Required subscription lifecycle events are configured (mock).",
|
|
6130
|
+
evidenceSource: "provider",
|
|
6131
|
+
connectionState,
|
|
6132
|
+
repoExpectationMet: Boolean(webhookRoute && hasStripeWebhookSignature(repo)),
|
|
6133
|
+
providerObservationMet: missingEvents.length === 0,
|
|
6134
|
+
fixType: "provider-config",
|
|
6135
|
+
severity: "critical",
|
|
6136
|
+
repoSignals: hasStripeWebhookSignature(repo) ? ["webhooks.constructEvent in repo"] : [],
|
|
6137
|
+
providerSignals: ["Stripe webhook endpoint event list"],
|
|
6138
|
+
requiredEvidence: REQUIRED_STRIPE_EVENTS.map((event) => `event: ${event}`)
|
|
6139
|
+
}),
|
|
6140
|
+
buildVerificationCheck({
|
|
6141
|
+
provider: "stripe",
|
|
6142
|
+
checkKey: "live-price-ids",
|
|
6143
|
+
area: "payments",
|
|
6144
|
+
title: "Stripe live price IDs",
|
|
6145
|
+
description: priceEnvNames.length > 0 ? "Price/product env vars are referenced in repo; live Stripe prices must match." : "No Stripe price env vars detected; confirm products/prices if billing is enabled.",
|
|
6146
|
+
evidenceSource: "provider",
|
|
6147
|
+
connectionState,
|
|
6148
|
+
repoExpectationMet: priceEnvNames.length > 0,
|
|
6149
|
+
providerObservationMet: priceEnvNames.length > 0 && priceEnvNames.every((name) => MOCK_STRIPE_LIVE_PRICE_IDS.has(name)),
|
|
6150
|
+
fixType: "provider-config",
|
|
6151
|
+
severity: priceEnvNames.length > 0 ? "warning" : "info",
|
|
6152
|
+
repoSignals: priceEnvNames.map((name) => `env: ${name}`),
|
|
6153
|
+
providerSignals: ["Stripe products/prices API or MCP"],
|
|
6154
|
+
requiredEvidence: ["Live price IDs match env configuration"]
|
|
6155
|
+
})
|
|
6156
|
+
];
|
|
6157
|
+
},
|
|
6158
|
+
buildDiffs(ctx) {
|
|
6159
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6160
|
+
const webhookRoute = findStripeWebhookRoute(repo);
|
|
6161
|
+
const diffs = [];
|
|
6162
|
+
if (webhookRoute && !MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT) {
|
|
6163
|
+
diffs.push({
|
|
6164
|
+
id: providerCheckId("stripe", "webhook-endpoint-mismatch"),
|
|
6165
|
+
provider: "stripe",
|
|
6166
|
+
area: "payments",
|
|
6167
|
+
title: "Stripe webhook endpoint not registered",
|
|
6168
|
+
description: "The repo implements a webhook handler, but no matching endpoint is registered in the mock Stripe account.",
|
|
6169
|
+
repoExpectation: `Webhook route: ${webhookRoute}`,
|
|
6170
|
+
providerActual: "No Stripe webhook endpoint (mock empty dashboard)",
|
|
6171
|
+
severity: "critical",
|
|
6172
|
+
suggestedFix: "provider-config",
|
|
6173
|
+
evidenceRefs: [`route: ${webhookRoute}`]
|
|
6174
|
+
});
|
|
6175
|
+
}
|
|
6176
|
+
const referencedEnv = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
|
|
6177
|
+
if (referencedEnv.includes("STRIPE_WEBHOOK_SECRET") && !MOCK_STRIPE_HAS_WEBHOOK_ENDPOINT) {
|
|
6178
|
+
diffs.push({
|
|
6179
|
+
id: providerCheckId("stripe", "webhook-secret-without-endpoint"),
|
|
6180
|
+
provider: "stripe",
|
|
6181
|
+
area: "payments",
|
|
6182
|
+
title: "STRIPE_WEBHOOK_SECRET without dashboard endpoint",
|
|
6183
|
+
description: "Repo expects STRIPE_WEBHOOK_SECRET but Stripe dashboard webhook endpoint is not confirmed.",
|
|
6184
|
+
repoExpectation: "STRIPE_WEBHOOK_SECRET referenced in repo",
|
|
6185
|
+
providerActual: "No webhook endpoint to deliver signed events",
|
|
6186
|
+
severity: "critical",
|
|
6187
|
+
suggestedFix: "provider-config",
|
|
6188
|
+
evidenceRefs: ["env: STRIPE_WEBHOOK_SECRET"]
|
|
6189
|
+
});
|
|
6190
|
+
}
|
|
6191
|
+
return diffs;
|
|
6192
|
+
}
|
|
6193
|
+
};
|
|
6194
|
+
|
|
6195
|
+
// ../../src/station/verificationLayer/mock/mockSupabaseVerifier.ts
|
|
6196
|
+
var mockSupabaseVerifier = {
|
|
6197
|
+
provider: "supabase",
|
|
6198
|
+
detectConfig(ctx) {
|
|
6199
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6200
|
+
const signals = [];
|
|
6201
|
+
if (ctx.scan.stackSignals.hasSupabase) {
|
|
6202
|
+
signals.push("stack: hasSupabase");
|
|
6203
|
+
}
|
|
6204
|
+
if (repo.deps.some((dep) => dep.startsWith("@supabase/"))) {
|
|
6205
|
+
signals.push(`package: ${repo.deps.find((dep) => dep.startsWith("@supabase/"))}`);
|
|
6206
|
+
}
|
|
6207
|
+
if (/\/supabase\/migrations\//i.test(repo.pathBlob)) {
|
|
6208
|
+
signals.push("path: supabase/migrations");
|
|
6209
|
+
}
|
|
6210
|
+
const detected = signals.length > 0;
|
|
6211
|
+
return { provider: "supabase", detected, signals };
|
|
6212
|
+
},
|
|
6213
|
+
connectStatus(ctx) {
|
|
6214
|
+
return resolveProviderConnectionState("supabase", ctx.mcpVerifierState);
|
|
6215
|
+
},
|
|
6216
|
+
runChecks(ctx) {
|
|
6217
|
+
const connectionState = this.connectStatus(ctx);
|
|
6218
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6219
|
+
const tables = collectSupabaseReferencedTables(repo);
|
|
6220
|
+
const rlsInRepo = hasRlsMigrationEvidence(repo);
|
|
6221
|
+
return [
|
|
6222
|
+
buildVerificationCheck({
|
|
6223
|
+
provider: "supabase",
|
|
6224
|
+
checkKey: "live-rls-enabled",
|
|
6225
|
+
area: "database",
|
|
6226
|
+
title: "Live Supabase RLS enabled",
|
|
6227
|
+
description: rlsInRepo ? "Repo contains RLS migration/policy evidence; live project RLS must be confirmed via Supabase MCP or dashboard." : "No RLS migration evidence in repo; live project likely needs RLS before launch.",
|
|
6228
|
+
evidenceSource: "provider",
|
|
6229
|
+
connectionState,
|
|
6230
|
+
repoExpectationMet: rlsInRepo,
|
|
6231
|
+
providerObservationMet: false,
|
|
6232
|
+
fixType: rlsInRepo ? "mcp-connect" : "provider-config",
|
|
6233
|
+
severity: "critical",
|
|
6234
|
+
repoSignals: rlsInRepo ? ["migration/policy references RLS"] : ["no RLS migration detected"],
|
|
6235
|
+
providerSignals: ["Supabase project policy API or MCP"],
|
|
6236
|
+
requiredEvidence: ["RLS enabled on user-owned tables in live project"],
|
|
6237
|
+
manualAction: "Enable RLS and policies in Supabase Dashboard \u2192 Authentication \u2192 Policies."
|
|
6238
|
+
}),
|
|
6239
|
+
buildVerificationCheck({
|
|
6240
|
+
provider: "supabase",
|
|
6241
|
+
checkKey: "live-policies",
|
|
6242
|
+
area: "database",
|
|
6243
|
+
title: "Live Supabase policies",
|
|
6244
|
+
description: "Row policies on referenced tables require live Supabase verification.",
|
|
6245
|
+
evidenceSource: "provider",
|
|
6246
|
+
connectionState,
|
|
6247
|
+
repoExpectationMet: tables.length > 0,
|
|
6248
|
+
providerObservationMet: false,
|
|
6249
|
+
fixType: "mcp-connect",
|
|
6250
|
+
severity: tables.length > 0 ? "critical" : "info",
|
|
6251
|
+
repoSignals: tables.map((table) => `table referenced: ${table}`),
|
|
6252
|
+
providerSignals: ["Supabase policy list API or MCP"],
|
|
6253
|
+
requiredEvidence: ["Policies exist for tables used in application code"]
|
|
6254
|
+
}),
|
|
6255
|
+
buildVerificationCheck({
|
|
6256
|
+
provider: "supabase",
|
|
6257
|
+
checkKey: "referenced-tables-protected",
|
|
6258
|
+
area: "database",
|
|
6259
|
+
title: "Referenced tables protected in live project",
|
|
6260
|
+
description: tables.length > 0 ? `Application code references ${tables.length} Supabase table(s); confirm live RLS/policies cover them.` : "No Supabase table references detected in scanned code.",
|
|
6261
|
+
evidenceSource: "provider",
|
|
6262
|
+
connectionState,
|
|
6263
|
+
repoExpectationMet: tables.length > 0 && rlsInRepo,
|
|
6264
|
+
providerObservationMet: false,
|
|
6265
|
+
fixType: "provider-config",
|
|
6266
|
+
severity: "warning",
|
|
6267
|
+
repoSignals: tables.map((table) => `.from("${table}")`),
|
|
6268
|
+
providerSignals: ["Live table policy map"],
|
|
6269
|
+
requiredEvidence: ["Each referenced public table has RLS + policy in production"]
|
|
6270
|
+
})
|
|
6271
|
+
];
|
|
6272
|
+
},
|
|
6273
|
+
buildDiffs(ctx) {
|
|
6274
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6275
|
+
const tables = collectSupabaseReferencedTables(repo);
|
|
6276
|
+
const rlsInRepo = hasRlsMigrationEvidence(repo);
|
|
6277
|
+
const diffs = [];
|
|
6278
|
+
if (!rlsInRepo && tables.length > 0) {
|
|
6279
|
+
diffs.push({
|
|
6280
|
+
id: providerCheckId("supabase", "rls-migration-gap"),
|
|
6281
|
+
provider: "supabase",
|
|
6282
|
+
area: "database",
|
|
6283
|
+
title: "RLS migration missing for referenced tables",
|
|
6284
|
+
description: "Code queries Supabase tables but no RLS migration/policy evidence was found in the repo.",
|
|
6285
|
+
repoExpectation: `Tables referenced: ${tables.join(", ")}`,
|
|
6286
|
+
providerActual: "Live RLS state unknown without Supabase MCP",
|
|
6287
|
+
severity: "critical",
|
|
6288
|
+
suggestedFix: "repo-fix",
|
|
6289
|
+
evidenceRefs: tables.map((table) => `table: ${table}`)
|
|
6290
|
+
});
|
|
6291
|
+
}
|
|
6292
|
+
if (rlsInRepo) {
|
|
6293
|
+
diffs.push({
|
|
6294
|
+
id: providerCheckId("supabase", "live-rls-unverified"),
|
|
6295
|
+
provider: "supabase",
|
|
6296
|
+
area: "database",
|
|
6297
|
+
title: "Live RLS not verified",
|
|
6298
|
+
description: "Repo includes RLS migration/policy files, but live Supabase project RLS has not been confirmed.",
|
|
6299
|
+
repoExpectation: "RLS policies defined in repo migrations",
|
|
6300
|
+
providerActual: "Not verified (mock \u2014 connect Supabase MCP read-only)",
|
|
6301
|
+
severity: "critical",
|
|
6302
|
+
suggestedFix: "mcp-connect",
|
|
6303
|
+
evidenceRefs: repo.files.filter((file) => /enable\s+row\s+level\s+security|create\s+policy/i.test(file.content)).slice(0, 3).map((file) => `file: ${file.path}`)
|
|
6304
|
+
});
|
|
6305
|
+
}
|
|
6306
|
+
return diffs;
|
|
6307
|
+
}
|
|
6308
|
+
};
|
|
6309
|
+
|
|
6310
|
+
// ../../src/station/verificationLayer/mock/mockVercelVerifier.ts
|
|
6311
|
+
var MOCK_VERCEL_PRODUCTION_ENV = /* @__PURE__ */ new Set();
|
|
6312
|
+
function deploymentEnvCandidates(ctx) {
|
|
6313
|
+
const referenced = collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence);
|
|
6314
|
+
const fromExample = collectEnvExampleNames(ctx.scan);
|
|
6315
|
+
const names = /* @__PURE__ */ new Set([...referenced, ...fromExample]);
|
|
6316
|
+
return [...names].filter((name) => {
|
|
6317
|
+
if (/^VERCEL_/i.test(name)) {
|
|
6318
|
+
return true;
|
|
6319
|
+
}
|
|
6320
|
+
if (/^(DATABASE_URL|NEXT_PUBLIC_|VITE_|SUPABASE_|STRIPE_|CLERK_|SENTRY_|POSTHOG_|PADDLE_|AUTH_|NEXTAUTH_)/i.test(name)) {
|
|
6321
|
+
return true;
|
|
6322
|
+
}
|
|
6323
|
+
return referenced.includes(name) && fromExample.includes(name);
|
|
6324
|
+
});
|
|
6325
|
+
}
|
|
6326
|
+
var mockVercelVerifier = {
|
|
6327
|
+
provider: "vercel",
|
|
6328
|
+
detectConfig(ctx) {
|
|
6329
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6330
|
+
const signals = [];
|
|
6331
|
+
if (ctx.scan.stackSignals.hasVercel) {
|
|
6332
|
+
signals.push("stack: hasVercel");
|
|
6333
|
+
}
|
|
6334
|
+
if (/vercel\.json/i.test(repo.pathBlob)) {
|
|
6335
|
+
signals.push("path: vercel.json");
|
|
6336
|
+
}
|
|
6337
|
+
if (ctx.scan.stackSignals.hasNextJs || ctx.scan.stackSignals.hasVite) {
|
|
6338
|
+
signals.push("framework: Vercel-compatible");
|
|
6339
|
+
}
|
|
6340
|
+
const detected = signals.length > 0;
|
|
6341
|
+
return { provider: "vercel", detected, signals };
|
|
6342
|
+
},
|
|
6343
|
+
connectStatus(ctx) {
|
|
6344
|
+
return resolveProviderConnectionState("vercel", ctx.mcpVerifierState);
|
|
6345
|
+
},
|
|
6346
|
+
runChecks(ctx) {
|
|
6347
|
+
const connectionState = this.connectStatus(ctx);
|
|
6348
|
+
const candidates = deploymentEnvCandidates(ctx);
|
|
6349
|
+
const missingOnVercel = candidates.filter((name) => !MOCK_VERCEL_PRODUCTION_ENV.has(name));
|
|
6350
|
+
const repo = buildRepoScanContext(ctx.scan);
|
|
6351
|
+
const checks = [
|
|
6352
|
+
buildVerificationCheck({
|
|
6353
|
+
provider: "vercel",
|
|
6354
|
+
checkKey: "production-env-sync",
|
|
6355
|
+
area: "deployment",
|
|
6356
|
+
title: "Vercel production environment variables",
|
|
6357
|
+
description: missingOnVercel.length > 0 ? `${missingOnVercel.length} repo-referenced env var(s) are not confirmed in Vercel production.` : "Repo env names align with the mock Vercel production snapshot.",
|
|
6358
|
+
evidenceSource: "provider",
|
|
6359
|
+
connectionState,
|
|
6360
|
+
repoExpectationMet: candidates.length > 0,
|
|
6361
|
+
providerObservationMet: missingOnVercel.length === 0,
|
|
6362
|
+
fixType: "provider-config",
|
|
6363
|
+
severity: missingOnVercel.length > 0 ? "critical" : "info",
|
|
6364
|
+
repoSignals: missingOnVercel.map((name) => `env expected: ${name}`),
|
|
6365
|
+
providerSignals: connectionState === "connected" ? ["Vercel production env API"] : ["Awaiting Vercel MCP read-only env lookup"],
|
|
6366
|
+
requiredEvidence: ["Vercel production env var names and values (redacted)"],
|
|
6367
|
+
manualAction: "Add missing keys in Vercel Project Settings \u2192 Environment Variables (Production)."
|
|
6368
|
+
}),
|
|
6369
|
+
buildVerificationCheck({
|
|
6370
|
+
provider: "vercel",
|
|
6371
|
+
checkKey: "latest-deployment",
|
|
6372
|
+
area: "deployment",
|
|
6373
|
+
title: "Latest Vercel deployment status",
|
|
6374
|
+
description: "Production deployment health requires live Vercel project access.",
|
|
6375
|
+
evidenceSource: "provider",
|
|
6376
|
+
connectionState,
|
|
6377
|
+
repoExpectationMet: Boolean(ctx.scan.stackSignals.hasVercel || /vercel\.json/i.test(repo.pathBlob)),
|
|
6378
|
+
providerObservationMet: false,
|
|
6379
|
+
fixType: "mcp-connect",
|
|
6380
|
+
severity: "warning",
|
|
6381
|
+
repoSignals: repo.pathBlob.split(/\r?\n/).filter((p2) => /vercel\.json/i.test(p2)).slice(0, 3),
|
|
6382
|
+
providerSignals: ["Vercel deployments API or MCP"],
|
|
6383
|
+
requiredEvidence: ["Latest production deployment state"]
|
|
6384
|
+
}),
|
|
6385
|
+
buildVerificationCheck({
|
|
6386
|
+
provider: "vercel",
|
|
6387
|
+
checkKey: "production-domain",
|
|
6388
|
+
area: "deployment",
|
|
6389
|
+
title: "Vercel production domain",
|
|
6390
|
+
description: "Custom domain and DNS status require live Vercel project access.",
|
|
6391
|
+
evidenceSource: "provider",
|
|
6392
|
+
connectionState,
|
|
6393
|
+
repoExpectationMet: Boolean(ctx.scan.stackSignals.hasVercel || ctx.scan.stackSignals.hasNextJs),
|
|
6394
|
+
providerObservationMet: false,
|
|
6395
|
+
fixType: "mcp-connect",
|
|
6396
|
+
severity: "warning",
|
|
6397
|
+
repoSignals: [],
|
|
6398
|
+
providerSignals: ["Vercel domains API or MCP"],
|
|
6399
|
+
requiredEvidence: ["Production domain assignment and DNS verification"]
|
|
6400
|
+
})
|
|
6401
|
+
];
|
|
6402
|
+
return checks;
|
|
6403
|
+
},
|
|
6404
|
+
buildDiffs(ctx) {
|
|
6405
|
+
const candidates = deploymentEnvCandidates(ctx);
|
|
6406
|
+
return candidates.filter((name) => !MOCK_VERCEL_PRODUCTION_ENV.has(name)).map((name) => ({
|
|
6407
|
+
id: providerCheckId("vercel", `env-missing-${name.toLowerCase()}`),
|
|
6408
|
+
provider: "vercel",
|
|
6409
|
+
area: "deployment",
|
|
6410
|
+
title: `${name} missing in Vercel production`,
|
|
6411
|
+
description: `The repo references ${name}, but the mock Vercel production env snapshot does not include it.`,
|
|
6412
|
+
repoExpectation: `Referenced in repo (.env.example, code, or env evidence)`,
|
|
6413
|
+
providerActual: "Not present in Vercel production (mock empty snapshot)",
|
|
6414
|
+
severity: /SECRET|KEY|TOKEN|PASSWORD/i.test(name) ? "critical" : "warning",
|
|
6415
|
+
suggestedFix: "provider-config",
|
|
6416
|
+
evidenceRefs: collectReferencedEnvNames(ctx.scan, ctx.repositoryEvidence).filter((entry) => entry === name).map((entry) => `env: ${entry}`)
|
|
6417
|
+
}));
|
|
6418
|
+
}
|
|
6419
|
+
};
|
|
6420
|
+
|
|
6421
|
+
// ../../src/station/verificationLayer/registry.ts
|
|
6422
|
+
var verifiers = [];
|
|
6423
|
+
function registerProviderVerifier(verifier) {
|
|
6424
|
+
const existing = verifiers.findIndex((entry) => entry.provider === verifier.provider);
|
|
6425
|
+
if (existing >= 0) {
|
|
6426
|
+
verifiers[existing] = verifier;
|
|
6427
|
+
return;
|
|
6428
|
+
}
|
|
6429
|
+
verifiers.push(verifier);
|
|
6430
|
+
}
|
|
6431
|
+
function getRegisteredVerifiers() {
|
|
6432
|
+
return [...verifiers];
|
|
6433
|
+
}
|
|
6434
|
+
|
|
6435
|
+
// ../../src/station/verificationLayer/registerDefaults.ts
|
|
6436
|
+
var defaultsRegistered = false;
|
|
6437
|
+
function ensureDefaultVerifiersRegistered() {
|
|
6438
|
+
if (defaultsRegistered) {
|
|
6439
|
+
return;
|
|
6440
|
+
}
|
|
6441
|
+
registerProviderVerifier(mockVercelVerifier);
|
|
6442
|
+
registerProviderVerifier(mockSupabaseVerifier);
|
|
6443
|
+
registerProviderVerifier(mockStripeVerifier);
|
|
6444
|
+
registerProviderVerifier(mockGitHubVerifier);
|
|
6445
|
+
defaultsRegistered = true;
|
|
6446
|
+
}
|
|
6447
|
+
|
|
6448
|
+
// ../../src/station/verificationLayer/runVerificationLayer.ts
|
|
6449
|
+
function runVerifier(verifier, ctx) {
|
|
6450
|
+
const config = verifier.detectConfig(ctx);
|
|
6451
|
+
if (!config.detected) {
|
|
6452
|
+
return null;
|
|
6453
|
+
}
|
|
6454
|
+
const connectionState = verifier.connectStatus(ctx);
|
|
6455
|
+
const rawChecks = verifier.runChecks(ctx);
|
|
6456
|
+
const checks = rawChecks.map((check) => ({
|
|
6457
|
+
...check,
|
|
6458
|
+
status: coerceProviderVerificationStatus(check.status, connectionState)
|
|
6459
|
+
}));
|
|
6460
|
+
const diffs = verifier.buildDiffs(ctx);
|
|
6461
|
+
return {
|
|
6462
|
+
provider: verifier.provider,
|
|
6463
|
+
connectionState,
|
|
6464
|
+
config,
|
|
6465
|
+
checks,
|
|
6466
|
+
diffs,
|
|
6467
|
+
repoReadinessPercent: computeLayerReadinessPercent(checks, "repo"),
|
|
6468
|
+
providerReadinessPercent: computeLayerReadinessPercent(checks, "provider")
|
|
6469
|
+
};
|
|
6470
|
+
}
|
|
6471
|
+
function runVerificationLayer(ctx, options) {
|
|
6472
|
+
if (options?.registerDefaults !== false) {
|
|
6473
|
+
ensureDefaultVerifiersRegistered();
|
|
6474
|
+
}
|
|
6475
|
+
const providers = getRegisteredVerifiers().map((verifier) => runVerifier(verifier, ctx)).filter((result) => result !== null);
|
|
6476
|
+
const checks = providers.flatMap((result) => result.checks);
|
|
6477
|
+
const diffs = providers.flatMap((result) => result.diffs);
|
|
6478
|
+
const { repoReadinessPercent, providerReadinessPercent } = aggregateReadinessPercents(providers);
|
|
6479
|
+
return {
|
|
6480
|
+
version: 1,
|
|
6481
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6482
|
+
runtimeMode: "mock",
|
|
6483
|
+
providers,
|
|
6484
|
+
checks,
|
|
6485
|
+
diffs,
|
|
6486
|
+
repoReadinessPercent,
|
|
6487
|
+
providerReadinessPercent
|
|
6488
|
+
};
|
|
6489
|
+
}
|
|
6490
|
+
|
|
6491
|
+
// ../../src/station/orchestrator.ts
|
|
6492
|
+
function isScanLimitResult(value) {
|
|
6493
|
+
return value.kind === "scan_limit_reached";
|
|
6494
|
+
}
|
|
6495
|
+
function isManagedRequiredResult(value) {
|
|
6496
|
+
return value.kind === "managed_required";
|
|
6497
|
+
}
|
|
6498
|
+
function isManagedSessionInvalidResult(value) {
|
|
6499
|
+
return value.kind === "managed_session_invalid";
|
|
6500
|
+
}
|
|
6501
|
+
function createStationOrchestrator(deps) {
|
|
6502
|
+
return {
|
|
6503
|
+
async run(input) {
|
|
6504
|
+
const allowLocal = Boolean(await deps.isLocalStationFallbackAllowed?.());
|
|
6505
|
+
const scan = await deps.scanWorkspace(input.workspaceRoot);
|
|
6506
|
+
const productionConnectionChoices = await loadProductionConnectionChoices(deps);
|
|
6507
|
+
const productionConnectionEvidence = detectProductionConnectionEvidence(scan);
|
|
6508
|
+
const productionConnections = summarizeProductionConnections(
|
|
6509
|
+
productionConnectionChoices,
|
|
6510
|
+
productionConnectionEvidence
|
|
6511
|
+
);
|
|
6512
|
+
const productionConnectionContext = buildProductionConnectionContext(
|
|
6513
|
+
productionConnectionChoices,
|
|
6514
|
+
productionConnectionEvidence
|
|
6515
|
+
);
|
|
6516
|
+
const verificationSummary = buildVerificationSummary(scan, productionConnections);
|
|
6517
|
+
const verificationEvidenceContext = buildVerificationEvidenceContext(verificationSummary);
|
|
6518
|
+
const stackWiring = analyzeStackWiring(scan);
|
|
6519
|
+
const stackWiringContext = buildStackWiringContext(stackWiring);
|
|
6520
|
+
const repositoryEvidence = analyzeRepositoryEvidence(scan);
|
|
6521
|
+
const providerRegistry = buildProviderRegistrySnapshot();
|
|
6522
|
+
const staticInfrastructureFlowGraph = buildStaticInfrastructureFlowGraph(scan);
|
|
6523
|
+
const stackAutomation = buildStackAutomationSummary(stackWiring, { staticInfrastructureFlowGraph });
|
|
6524
|
+
const stackAutomationContext = buildStackAutomationContext(stackAutomation);
|
|
6525
|
+
const mcpVerifierState = await deps.getMcpVerifierState?.();
|
|
6526
|
+
const verificationLayer = runVerificationLayer({
|
|
6527
|
+
workspaceRoot: input.workspaceRoot,
|
|
6528
|
+
scan,
|
|
6529
|
+
stackWiring,
|
|
6530
|
+
repositoryEvidence,
|
|
6531
|
+
mcpVerifierState
|
|
6532
|
+
});
|
|
6533
|
+
const missionGraph = buildMissionGraph({
|
|
6534
|
+
stackWiring,
|
|
6535
|
+
repositoryEvidence,
|
|
6536
|
+
staticInfrastructureFlowGraph,
|
|
6537
|
+
verificationLayer
|
|
6538
|
+
});
|
|
6539
|
+
const specContent = await readSpec(input.workspaceRoot);
|
|
6540
|
+
const modelPrompt = buildAnalysisPrompt(
|
|
6541
|
+
scan,
|
|
5234
6542
|
specContent,
|
|
5235
6543
|
productionConnectionContext,
|
|
5236
6544
|
verificationEvidenceContext,
|
|
@@ -5568,25 +6876,49 @@ function generateAgentSummary(artifact) {
|
|
|
5568
6876
|
}
|
|
5569
6877
|
}
|
|
5570
6878
|
lines.push("");
|
|
5571
|
-
lines.push("##
|
|
6879
|
+
lines.push("## Agent-code actions");
|
|
5572
6880
|
if (topGaps.length === 0) {
|
|
5573
|
-
lines.push("_No model gaps returned
|
|
6881
|
+
lines.push("_No model gaps returned. Review mission map checks before changing code._");
|
|
5574
6882
|
} else {
|
|
5575
6883
|
topGaps.forEach((gap, index) => {
|
|
5576
6884
|
lines.push(
|
|
5577
6885
|
`${index + 1}. **${gap.title}** (\`${gap.id}\`, ${gap.severity}, map: \`${gap.primaryMapCategory}\`)`
|
|
5578
6886
|
);
|
|
5579
6887
|
lines.push(` - ${gap.detail}`);
|
|
5580
|
-
lines.push(` -
|
|
6888
|
+
lines.push(` - Command: \`viberaven prompt --gap ${gap.id}\``);
|
|
6889
|
+
if (gap.copyPrompt) {
|
|
6890
|
+
lines.push(` - Prompt: ${gap.copyPrompt}`);
|
|
6891
|
+
}
|
|
6892
|
+
});
|
|
6893
|
+
}
|
|
6894
|
+
const manualChecks = (artifact.missionGraph.areas ?? []).flatMap(
|
|
6895
|
+
(area) => area.providerMissions.flatMap(
|
|
6896
|
+
(mission) => mission.checks.filter(
|
|
6897
|
+
(check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
|
|
6898
|
+
).map((check) => ({ area: area.label, provider: mission.providerLabel, check }))
|
|
6899
|
+
)
|
|
6900
|
+
);
|
|
6901
|
+
lines.push("");
|
|
6902
|
+
lines.push("## Human-provider actions");
|
|
6903
|
+
if (manualChecks.length === 0) {
|
|
6904
|
+
lines.push("_No manual provider actions were identified in this scan._");
|
|
6905
|
+
} else {
|
|
6906
|
+
manualChecks.slice(0, 8).forEach((item3, index) => {
|
|
6907
|
+
lines.push(`${index + 1}. **${item3.check.label}** (${item3.area} / ${item3.provider})`);
|
|
6908
|
+
lines.push(
|
|
6909
|
+
` - ${item3.check.promptHint || "Ask the user to confirm this in the provider dashboard or through read-only MCP."}`
|
|
6910
|
+
);
|
|
5581
6911
|
});
|
|
5582
6912
|
}
|
|
5583
6913
|
lines.push("");
|
|
6914
|
+
lines.push("Do not claim human-provider actions as repo-code fixes.");
|
|
6915
|
+
lines.push("");
|
|
5584
6916
|
lines.push("## Agent workflow");
|
|
5585
|
-
lines.push("1. Pick the highest-severity gap unless the user names an area or provider.");
|
|
5586
|
-
lines.push("2. Run `viberaven prompt --gap <id>`
|
|
5587
|
-
lines.push("3. Implement the fix
|
|
5588
|
-
lines.push("4. Run `npx -y @viberaven/cli@beta scan` again
|
|
5589
|
-
lines.push("5. Tell the user to
|
|
6917
|
+
lines.push("1. Pick the highest-severity agent-code gap unless the user names an area or provider.");
|
|
6918
|
+
lines.push("2. Run `viberaven prompt --gap <id>` or read `copyPrompt` from `last-scan.json`.");
|
|
6919
|
+
lines.push("3. Implement the repo-code fix.");
|
|
6920
|
+
lines.push("4. Run `npx -y @viberaven/cli@beta scan` again.");
|
|
6921
|
+
lines.push("5. Tell the user to review `.viberaven/report.html` for the visual mission map.");
|
|
5590
6922
|
lines.push("");
|
|
5591
6923
|
if (artifact.usage) {
|
|
5592
6924
|
lines.push("## Account usage");
|
|
@@ -5719,6 +7051,24 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
5719
7051
|
|
|
5720
7052
|
|
|
5721
7053
|
|
|
7054
|
+
function normalizeProviderToken(value) {
|
|
7055
|
+
|
|
7056
|
+
return String(value == null ? '' : value)
|
|
7057
|
+
.trim()
|
|
7058
|
+
.toLowerCase()
|
|
7059
|
+
.replace(/&/g, 'and')
|
|
7060
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
7061
|
+
|
|
7062
|
+
}
|
|
7063
|
+
|
|
7064
|
+
function buildProviderStackCommand(areaKey, provider) {
|
|
7065
|
+
|
|
7066
|
+
return 'viberaven stack set ' + areaKey + ' ' + normalizeProviderToken(provider) + ' && viberaven scan';
|
|
7067
|
+
|
|
7068
|
+
}
|
|
7069
|
+
|
|
7070
|
+
|
|
7071
|
+
|
|
5722
7072
|
function normKey(p) {
|
|
5723
7073
|
|
|
5724
7074
|
if (!p) return '';
|
|
@@ -5727,6 +7077,8 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
5727
7077
|
|
|
5728
7078
|
var compact = raw.replace(/[^a-z0-9]+/g, '');
|
|
5729
7079
|
|
|
7080
|
+
if (!compact || compact === 'notselected' || raw === 'not selected' || raw === 'none') return '';
|
|
7081
|
+
|
|
5730
7082
|
if (logoPayload.aliases[raw]) return logoPayload.aliases[raw];
|
|
5731
7083
|
|
|
5732
7084
|
if (logoPayload.aliases[compact]) return logoPayload.aliases[compact];
|
|
@@ -5791,17 +7143,101 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
5791
7143
|
|
|
5792
7144
|
}
|
|
5793
7145
|
|
|
7146
|
+
if (key && logoPayload.logos[key]) return logoPayload.logos[key];
|
|
7147
|
+
|
|
5794
7148
|
if (key && logoPayload.iconUrls && logoPayload.iconUrls[key]) {
|
|
5795
7149
|
|
|
5796
7150
|
return '<img class="provider-logo__img" src="' + esc(logoPayload.iconUrls[key]) + '" alt="" decoding="async" data-provider-logo-key="' + esc(key) + '" />';
|
|
5797
7151
|
|
|
5798
7152
|
}
|
|
5799
7153
|
|
|
5800
|
-
|
|
7154
|
+
return logoPayload.fallbackSvg || '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 20 7.5v9L12 21 4 16.5v-9L12 3Zm0 3.3-5 2.8v5.8l5 2.8 5-2.8V9.1l-5-2.8Zm0 3.2a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"/></svg>';
|
|
7155
|
+
|
|
7156
|
+
}
|
|
7157
|
+
|
|
7158
|
+
|
|
7159
|
+
|
|
7160
|
+
function nodeTone(p) {
|
|
7161
|
+
|
|
7162
|
+
var key = normKey(p);
|
|
7163
|
+
|
|
7164
|
+
if (!key || !logoPayload.nodeTones || !logoPayload.nodeTones[key]) return null;
|
|
7165
|
+
|
|
7166
|
+
return logoPayload.nodeTones[key];
|
|
7167
|
+
|
|
7168
|
+
}
|
|
7169
|
+
|
|
7170
|
+
|
|
7171
|
+
|
|
7172
|
+
function syncMapNode(areaKey, providerOption) {
|
|
7173
|
+
|
|
7174
|
+
var node = document.querySelector('.studio-node[data-area-key="' + areaKey + '"]');
|
|
7175
|
+
|
|
7176
|
+
if (!node) return;
|
|
7177
|
+
|
|
7178
|
+
var area = areas.find(function (a) { return a.key === areaKey; });
|
|
7179
|
+
|
|
7180
|
+
var areaLabel = (area && area.label) || LABELS[areaKey] || areaKey;
|
|
7181
|
+
|
|
7182
|
+
var missions = (area && area.providerMissions) || [];
|
|
7183
|
+
|
|
7184
|
+
var mission = missionForProvider(missions, providerOption);
|
|
7185
|
+
|
|
7186
|
+
var providerLabel = providerLabelFor(areaKey, providerOption, mission, areaLabel);
|
|
7187
|
+
|
|
7188
|
+
var iconKey = normKey(providerOption);
|
|
7189
|
+
|
|
7190
|
+
var logoInner = logoHtml(iconKey || providerOption, providerLabel);
|
|
5801
7191
|
|
|
5802
|
-
var
|
|
7192
|
+
var cls = logoClass(iconKey || providerOption);
|
|
5803
7193
|
|
|
5804
|
-
|
|
7194
|
+
var logoEl = node.querySelector('.studio-node__logo');
|
|
7195
|
+
|
|
7196
|
+
if (logoEl) {
|
|
7197
|
+
|
|
7198
|
+
logoEl.className = 'studio-node__logo provider-logo' + cls;
|
|
7199
|
+
|
|
7200
|
+
logoEl.innerHTML = logoInner;
|
|
7201
|
+
|
|
7202
|
+
}
|
|
7203
|
+
|
|
7204
|
+
node.className = node.className
|
|
7205
|
+
|
|
7206
|
+
.replace(/\\sprovider-logo--[a-z0-9-]+/g, '')
|
|
7207
|
+
|
|
7208
|
+
.replace(/\\sprovider-logo--brand/g, '')
|
|
7209
|
+
|
|
7210
|
+
.trim();
|
|
7211
|
+
|
|
7212
|
+
if (!/\\bprovider-logo\\b/.test(node.className)) node.className += ' provider-logo';
|
|
7213
|
+
|
|
7214
|
+
node.className += cls;
|
|
7215
|
+
|
|
7216
|
+
var provSpan = node.querySelector('.studio-node__provider');
|
|
7217
|
+
|
|
7218
|
+
if (provSpan) provSpan.textContent = providerLabel;
|
|
7219
|
+
|
|
7220
|
+
var tone = nodeTone(providerOption);
|
|
7221
|
+
|
|
7222
|
+
if (tone) {
|
|
7223
|
+
|
|
7224
|
+
node.style.setProperty('--provider-color', tone[0]);
|
|
7225
|
+
|
|
7226
|
+
node.style.setProperty('--provider-glow', tone[1]);
|
|
7227
|
+
|
|
7228
|
+
} else {
|
|
7229
|
+
|
|
7230
|
+
node.style.removeProperty('--provider-color');
|
|
7231
|
+
|
|
7232
|
+
node.style.removeProperty('--provider-glow');
|
|
7233
|
+
|
|
7234
|
+
}
|
|
7235
|
+
|
|
7236
|
+
var metaSpan = node.querySelector('.studio-node__meta');
|
|
7237
|
+
|
|
7238
|
+
var metaText = metaSpan ? metaSpan.textContent : '';
|
|
7239
|
+
|
|
7240
|
+
node.setAttribute('aria-label', areaLabel + ', ' + providerLabel + ', ' + metaText);
|
|
5805
7241
|
|
|
5806
7242
|
}
|
|
5807
7243
|
|
|
@@ -5887,19 +7323,53 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
5887
7323
|
|
|
5888
7324
|
|
|
5889
7325
|
|
|
5890
|
-
function
|
|
7326
|
+
function firstProviderOption(areaKey) {
|
|
5891
7327
|
|
|
5892
|
-
|
|
7328
|
+
var options = providerOptions[areaKey] || [];
|
|
5893
7329
|
|
|
5894
|
-
return
|
|
7330
|
+
return options[0] || null;
|
|
5895
7331
|
|
|
5896
7332
|
}
|
|
5897
7333
|
|
|
5898
7334
|
|
|
5899
7335
|
|
|
5900
|
-
function
|
|
7336
|
+
function iconProviderKey(areaKey, currentProvider, mission, providerLabel) {
|
|
5901
7337
|
|
|
5902
|
-
|
|
7338
|
+
var option = optionFor(areaKey, currentProvider);
|
|
7339
|
+
|
|
7340
|
+
var firstOption = firstProviderOption(areaKey);
|
|
7341
|
+
|
|
7342
|
+
return currentProvider ||
|
|
7343
|
+
|
|
7344
|
+
(mission && (mission.provider || mission.providerLabel || mission.key)) ||
|
|
7345
|
+
|
|
7346
|
+
(option && (option.provider || option.label)) ||
|
|
7347
|
+
|
|
7348
|
+
(firstOption && (firstOption.provider || firstOption.label)) ||
|
|
7349
|
+
|
|
7350
|
+
providerLabel ||
|
|
7351
|
+
|
|
7352
|
+
LABELS[areaKey] ||
|
|
7353
|
+
|
|
7354
|
+
areaKey;
|
|
7355
|
+
|
|
7356
|
+
}
|
|
7357
|
+
|
|
7358
|
+
|
|
7359
|
+
|
|
7360
|
+
function sameProvider(a, b) {
|
|
7361
|
+
|
|
7362
|
+
if (!a || !b) return false;
|
|
7363
|
+
|
|
7364
|
+
return String(a).toLowerCase() === String(b).toLowerCase() || normKey(a) === normKey(b);
|
|
7365
|
+
|
|
7366
|
+
}
|
|
7367
|
+
|
|
7368
|
+
|
|
7369
|
+
|
|
7370
|
+
function projectProviderFor(areaKey, missions) {
|
|
7371
|
+
|
|
7372
|
+
if (projectProviders[areaKey]) return projectProviders[areaKey];
|
|
5903
7373
|
|
|
5904
7374
|
var mission = preferredMission(missions, '');
|
|
5905
7375
|
|
|
@@ -6121,126 +7591,588 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6121
7591
|
|
|
6122
7592
|
var desc = opt.description || benefitText(p);
|
|
6123
7593
|
|
|
6124
|
-
var
|
|
7594
|
+
var providerOption = normKey(p) || normalizeProviderToken(p);
|
|
7595
|
+
|
|
7596
|
+
var status = inProject ? 'Using now' : active ? 'Added to setup' : 'Use this path';
|
|
7597
|
+
|
|
7598
|
+
return '<button type="button" class="studio-choice-tile' + (active ? ' studio-choice-tile--selected' : '') + (inProject ? ' studio-choice-tile--in-project' : '') +
|
|
7599
|
+
|
|
7600
|
+
'" data-provider-option="' + attrEsc(providerOption) + '" data-provider-label="' + attrEsc(p) + '" data-area-key="' + attrEsc(areaKey) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
|
|
7601
|
+
|
|
7602
|
+
'<span class="studio-choice-tile__icon provider-logo' + logoClass(p) + '" aria-hidden="true">' + logoHtml(p, opt.label) + '</span>' +
|
|
7603
|
+
|
|
7604
|
+
'<span class="studio-choice-tile__name">' + esc(opt.label) + '</span>' +
|
|
7605
|
+
|
|
7606
|
+
'<span class="studio-choice-tile__desc">' + esc(desc) + '</span>' +
|
|
7607
|
+
|
|
7608
|
+
'<span class="studio-choice-tile__status">' + status + '</span>' +
|
|
7609
|
+
|
|
7610
|
+
'</button>';
|
|
7611
|
+
|
|
7612
|
+
}).join('');
|
|
7613
|
+
|
|
7614
|
+
var hintLabel = CHOICE_HINTS[areaKey] || ('Choose ' + (LABELS[areaKey] || areaKey).toLowerCase());
|
|
7615
|
+
|
|
7616
|
+
return '<div class="studio-setup-panel__hint"><span>' + esc(hintLabel) + '</span>' + evidenceBadgeHtml(evidenceMissions || missions) + '</div>' +
|
|
7617
|
+
|
|
7618
|
+
'<div class="studio-choice-list" role="group" aria-label="' + attrEsc(hintLabel) + '">' + tiles + '</div>';
|
|
7619
|
+
|
|
7620
|
+
}
|
|
7621
|
+
|
|
7622
|
+
|
|
7623
|
+
|
|
7624
|
+
function buildPrompt(areaKey, missions, providerLabel, automation) {
|
|
7625
|
+
|
|
7626
|
+
if (automation) {
|
|
7627
|
+
|
|
7628
|
+
if (automation.automationLevel === 'manual-only' && automation.verificationPrompt) return automation.verificationPrompt;
|
|
7629
|
+
|
|
7630
|
+
if (automation.repoPrompt) return automation.repoPrompt;
|
|
7631
|
+
|
|
7632
|
+
if (automation.promptRoutes && automation.promptRoutes['repo-fix'] && automation.promptRoutes['repo-fix'].body) {
|
|
7633
|
+
|
|
7634
|
+
return automation.promptRoutes['repo-fix'].body;
|
|
7635
|
+
|
|
7636
|
+
}
|
|
7637
|
+
|
|
7638
|
+
}
|
|
7639
|
+
|
|
7640
|
+
var areaGaps = missions[0] ? gaps.filter(function (g) { return g.primaryMapCategory === areaKey; }) : [];
|
|
7641
|
+
|
|
7642
|
+
if (areaGaps[0] && areaGaps[0].copyPrompt) return areaGaps[0].copyPrompt;
|
|
7643
|
+
|
|
7644
|
+
var missionPrompt = stackPromptFromMission(missions[0], providerLabel);
|
|
7645
|
+
|
|
7646
|
+
if (missionPrompt) return missionPrompt;
|
|
7647
|
+
|
|
7648
|
+
var missing = (missions[0] && missions[0].checks || []).filter(function (c) {
|
|
7649
|
+
|
|
7650
|
+
return c.evidenceClass === 'missing-repo-fix' || c.status === 'missing' || c.status === 'failed';
|
|
7651
|
+
|
|
7652
|
+
});
|
|
7653
|
+
|
|
7654
|
+
if (missing[0] && missing[0].promptHint) return missing[0].promptHint;
|
|
7655
|
+
|
|
7656
|
+
return setupPromptForProvider(areaKey, providerLabel);
|
|
7657
|
+
|
|
7658
|
+
}
|
|
7659
|
+
|
|
7660
|
+
|
|
7661
|
+
|
|
7662
|
+
function setupPromptForProvider(areaKey, providerLabel) {
|
|
7663
|
+
|
|
7664
|
+
var areaLabel = LABELS[areaKey] || areaKey;
|
|
7665
|
+
|
|
7666
|
+
var provider = providerLabel || areaLabel;
|
|
7667
|
+
|
|
7668
|
+
return [
|
|
7669
|
+
|
|
7670
|
+
'Set up ' + provider + ' for the ' + areaLabel + ' production section safely.',
|
|
7671
|
+
|
|
7672
|
+
'',
|
|
7673
|
+
|
|
7674
|
+
'Inspect first:',
|
|
7675
|
+
|
|
7676
|
+
'- Review package.json files, env examples, framework routes, provider helpers, server/client boundaries, and existing ' + areaLabel.toLowerCase() + ' patterns before editing.',
|
|
7677
|
+
|
|
7678
|
+
'- Identify the current framework, folder structure, naming style, validation style, and test/build commands.',
|
|
7679
|
+
|
|
7680
|
+
'- If VibeRaven SIFG leak context is present, treat its leak IDs and allowed files as the source of truth.',
|
|
7681
|
+
|
|
7682
|
+
'',
|
|
7683
|
+
|
|
7684
|
+
'Implement:',
|
|
7685
|
+
|
|
7686
|
+
'- Make the smallest repo-only changes needed to wire the ' + provider + ' path.',
|
|
7687
|
+
|
|
7688
|
+
'- Add the right package or SDK only if it is missing.',
|
|
7689
|
+
|
|
7690
|
+
'- Document required environment variable names in safe examples or setup docs without reading or exposing real secrets.',
|
|
7691
|
+
|
|
7692
|
+
'- Add server-side integration points, route handlers, webhooks, guards, or helpers only where this repo structure expects them.',
|
|
7693
|
+
|
|
7694
|
+
'',
|
|
7695
|
+
|
|
7696
|
+
'Provider constraints:',
|
|
7697
|
+
|
|
7698
|
+
'- Do not call provider APIs, mutate external projects, or edit VibeRaven dashboard state.',
|
|
7699
|
+
|
|
7700
|
+
'- Keep dashboard/provider setup as explicit manual steps.',
|
|
7701
|
+
|
|
7702
|
+
'- Do not claim live provider configuration is complete from repo changes alone.',
|
|
7703
|
+
|
|
7704
|
+
'- Keep secrets in server-only code and env examples. Use placeholder env names only.',
|
|
7705
|
+
|
|
7706
|
+
'',
|
|
7707
|
+
|
|
7708
|
+
'Verification:',
|
|
7709
|
+
|
|
7710
|
+
'- Run the closest relevant build, test, lint, or typecheck command.',
|
|
7711
|
+
|
|
7712
|
+
'- Confirm repo evidence exists for each implemented item.',
|
|
7713
|
+
|
|
7714
|
+
'- List provider dashboard checks separately as manual or read-only MCP verification.',
|
|
7715
|
+
|
|
7716
|
+
'- Rescan VibeRaven after editing so repo evidence can move to verified.'
|
|
7717
|
+
|
|
7718
|
+
].join('\\n');
|
|
7719
|
+
|
|
7720
|
+
|
|
7721
|
+
}
|
|
7722
|
+
|
|
7723
|
+
function evidenceSourceLabel(check) {
|
|
7724
|
+
|
|
7725
|
+
if (!check) return 'Unknown';
|
|
7726
|
+
|
|
7727
|
+
if (check.evidenceSource === 'provider') return 'Provider live';
|
|
7728
|
+
|
|
7729
|
+
if (check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier') return 'MCP';
|
|
7730
|
+
|
|
7731
|
+
if (check.evidenceSource === 'manual' || check.evidenceClass === 'manual-dashboard' || check.status === 'user-confirmed') return 'Manual';
|
|
7732
|
+
|
|
7733
|
+
if (check.evidenceSource === 'repo' || check.evidenceClass === 'repo-verified' || check.evidenceClass === 'repo-file') return 'Repo files';
|
|
7734
|
+
|
|
7735
|
+
return 'Unknown';
|
|
7736
|
+
|
|
7737
|
+
}
|
|
7738
|
+
|
|
7739
|
+
|
|
7740
|
+
|
|
7741
|
+
function contractItem(label, detail, source, tone) {
|
|
7742
|
+
|
|
7743
|
+
return {
|
|
7744
|
+
|
|
7745
|
+
label: label || 'Unknown',
|
|
7746
|
+
|
|
7747
|
+
detail: detail || '',
|
|
7748
|
+
|
|
7749
|
+
source: source || 'Unknown',
|
|
7750
|
+
|
|
7751
|
+
tone: tone || 'neutral'
|
|
7752
|
+
|
|
7753
|
+
};
|
|
7754
|
+
|
|
7755
|
+
}
|
|
7756
|
+
|
|
7757
|
+
|
|
7758
|
+
|
|
7759
|
+
function checkDetail(check) {
|
|
7760
|
+
|
|
7761
|
+
if (!check) return '';
|
|
7762
|
+
|
|
7763
|
+
if (check.evidence && check.evidence[0]) return check.evidence[0];
|
|
7764
|
+
|
|
7765
|
+
if (check.promptHint) return check.promptHint;
|
|
7766
|
+
|
|
7767
|
+
if (check.status === 'unknown') return 'Not checked';
|
|
7768
|
+
|
|
7769
|
+
if (check.status === 'needs-connection') return 'Needs verification';
|
|
7770
|
+
|
|
7771
|
+
return '';
|
|
7772
|
+
|
|
7773
|
+
}
|
|
7774
|
+
|
|
7775
|
+
|
|
7776
|
+
|
|
7777
|
+
function buildCliSidebarContract(areaKey, label, mission, categoryGaps, providerLabel, automation, currentProvider) {
|
|
7778
|
+
|
|
7779
|
+
var connected = [];
|
|
7780
|
+
|
|
7781
|
+
var missing = [];
|
|
7782
|
+
|
|
7783
|
+
var manual = [];
|
|
7784
|
+
|
|
7785
|
+
var checks = mission && Array.isArray(mission.checks) ? mission.checks : [];
|
|
7786
|
+
|
|
7787
|
+
var mcpProvider = (automation && automation.mcpProvider) || '';
|
|
7788
|
+
|
|
7789
|
+
checks.forEach(function (check) {
|
|
7790
|
+
|
|
7791
|
+
var source = evidenceSourceLabel(check);
|
|
7792
|
+
|
|
7793
|
+
var item = contractItem(check.label, checkDetail(check), source, source === 'Repo files' ? 'repo' : source === 'MCP' ? 'mcp' : source === 'Manual' ? 'manual' : 'neutral');
|
|
7794
|
+
|
|
7795
|
+
if ((check.status === 'passed' || check.status === 'user-confirmed') && source !== 'Unknown') {
|
|
7796
|
+
|
|
7797
|
+
connected.push(item);
|
|
7798
|
+
|
|
7799
|
+
return;
|
|
7800
|
+
|
|
7801
|
+
}
|
|
7802
|
+
|
|
7803
|
+
if (check.evidenceClass === 'missing-repo-fix' || check.status === 'missing' || check.status === 'failed') {
|
|
7804
|
+
|
|
7805
|
+
missing.push(contractItem(check.label, checkDetail(check), 'Repo files', 'missing'));
|
|
7806
|
+
|
|
7807
|
+
return;
|
|
7808
|
+
|
|
7809
|
+
}
|
|
7810
|
+
|
|
7811
|
+
if (source === 'Manual' || source === 'MCP' || check.status === 'needs-connection' || check.status === 'unknown') {
|
|
7812
|
+
|
|
7813
|
+
manual.push(contractItem(check.label, checkDetail(check) || 'Not checked', source, 'manual'));
|
|
7814
|
+
|
|
7815
|
+
}
|
|
7816
|
+
|
|
7817
|
+
});
|
|
7818
|
+
|
|
7819
|
+
(Array.isArray(categoryGaps) ? categoryGaps : []).slice(0, 4).forEach(function (gap) {
|
|
7820
|
+
|
|
7821
|
+
missing.push(contractItem(gap.title || 'Product gap needs repo review', gap.detail || 'Agent can inspect and adjust repo implementation.', 'Repo files', 'missing'));
|
|
7822
|
+
|
|
7823
|
+
});
|
|
7824
|
+
|
|
7825
|
+
if (!checks.length) {
|
|
7826
|
+
|
|
7827
|
+
manual.push(contractItem('Provider live proof', 'Not checked', 'Unknown', 'manual'));
|
|
7828
|
+
|
|
7829
|
+
}
|
|
7830
|
+
|
|
7831
|
+
if (!manual.length) {
|
|
7832
|
+
|
|
7833
|
+
manual.push(contractItem('Provider live proof', 'Not checked', 'Unknown', 'manual'));
|
|
7834
|
+
|
|
7835
|
+
}
|
|
7836
|
+
|
|
7837
|
+
return {
|
|
7838
|
+
|
|
7839
|
+
areaKey: areaKey,
|
|
7840
|
+
|
|
7841
|
+
areaLabel: label || LABELS[areaKey] || areaKey,
|
|
7842
|
+
|
|
7843
|
+
providerKey: iconProviderKey(areaKey, currentProvider, mission, providerLabel),
|
|
7844
|
+
|
|
7845
|
+
providerLabel: providerLabel || 'Selected provider',
|
|
7846
|
+
|
|
7847
|
+
connected: connected,
|
|
7848
|
+
|
|
7849
|
+
missing: missing,
|
|
7850
|
+
|
|
7851
|
+
manual: manual,
|
|
7852
|
+
|
|
7853
|
+
repoPercent: typeof (mission && mission.repoReadinessPercent) === 'number'
|
|
7854
|
+
? Math.max(0, Math.min(100, Math.round(mission.repoReadinessPercent)))
|
|
7855
|
+
: typeof (mission && mission.readinessPercent) === 'number'
|
|
7856
|
+
? Math.max(0, Math.min(100, Math.round(mission.readinessPercent)))
|
|
7857
|
+
: 0,
|
|
7858
|
+
|
|
7859
|
+
providerPercent: typeof (mission && mission.providerReadinessPercent) === 'number'
|
|
7860
|
+
? Math.max(0, Math.min(100, Math.round(mission.providerReadinessPercent)))
|
|
7861
|
+
: 0,
|
|
7862
|
+
|
|
7863
|
+
hasMcpAccess: Boolean(providerLabel || mcpProvider) || checks.some(function (check) {
|
|
7864
|
+
return check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier' || check.verificationStatus === 'needs_mcp';
|
|
7865
|
+
})
|
|
7866
|
+
|
|
7867
|
+
};
|
|
7868
|
+
|
|
7869
|
+
}
|
|
7870
|
+
|
|
7871
|
+
|
|
7872
|
+
|
|
7873
|
+
function contractLines(items) {
|
|
7874
|
+
|
|
7875
|
+
return (items && items.length ? items : [{ label: 'None', detail: 'No artifact-backed item.', source: 'Unknown' }]).map(function (item) {
|
|
7876
|
+
|
|
7877
|
+
return '- [' + (item.source || 'Unknown') + '] ' + item.label + (item.detail ? ': ' + item.detail : '');
|
|
7878
|
+
|
|
7879
|
+
}).join('\\n');
|
|
7880
|
+
|
|
7881
|
+
}
|
|
7882
|
+
|
|
7883
|
+
|
|
7884
|
+
|
|
7885
|
+
function focusedContractPrompt(contract) {
|
|
7886
|
+
|
|
7887
|
+
return [
|
|
7888
|
+
|
|
7889
|
+
'VibeRaven selected-node production checklist',
|
|
7890
|
+
|
|
7891
|
+
'',
|
|
7892
|
+
|
|
7893
|
+
'Node: ' + contract.areaLabel,
|
|
7894
|
+
|
|
7895
|
+
'Selected provider context: ' + contract.providerLabel,
|
|
7896
|
+
|
|
7897
|
+
'',
|
|
7898
|
+
|
|
7899
|
+
'Verified evidence:',
|
|
7900
|
+
|
|
7901
|
+
contractLines(contract.connected),
|
|
7902
|
+
|
|
7903
|
+
'',
|
|
7904
|
+
|
|
7905
|
+
'Agent-code actions:',
|
|
7906
|
+
|
|
7907
|
+
contractLines(contract.missing),
|
|
7908
|
+
|
|
7909
|
+
'',
|
|
7910
|
+
|
|
7911
|
+
'Human-provider actions:',
|
|
7912
|
+
|
|
7913
|
+
contractLines(contract.manual),
|
|
7914
|
+
|
|
7915
|
+
'',
|
|
7916
|
+
|
|
7917
|
+
'Rules:',
|
|
7918
|
+
|
|
7919
|
+
'- Selected provider is context only and does not mean connected.',
|
|
7920
|
+
|
|
7921
|
+
'- Connected evidence requires Repo files, Provider live, MCP, or Manual confirmation in the artifact.',
|
|
7922
|
+
|
|
7923
|
+
'- Do not represent human-provider actions as completed code fixes.'
|
|
7924
|
+
|
|
7925
|
+
].join('\\n');
|
|
7926
|
+
|
|
7927
|
+
}
|
|
7928
|
+
|
|
7929
|
+
|
|
7930
|
+
|
|
7931
|
+
function sidebarStatusBadge(contract) {
|
|
7932
|
+
|
|
7933
|
+
if ((contract.connected || []).some(function (item) { return item.source === 'Repo files'; })) return 'Repo evidence found';
|
|
7934
|
+
|
|
7935
|
+
if ((contract.missing || []).length) return 'Missing repo fixes';
|
|
7936
|
+
|
|
7937
|
+
if ((contract.manual || []).length) return 'Manual check';
|
|
7938
|
+
|
|
7939
|
+
return 'Selected only';
|
|
7940
|
+
|
|
7941
|
+
}
|
|
7942
|
+
|
|
7943
|
+
|
|
7944
|
+
|
|
7945
|
+
function sidebarReadinessSentence(contract) {
|
|
7946
|
+
|
|
7947
|
+
var hasRepo = (contract.connected || []).some(function (item) { return item.source === 'Repo files'; });
|
|
7948
|
+
|
|
7949
|
+
var hasLive = (contract.connected || []).some(function (item) { return item.source === 'Provider live' || item.source === 'MCP' || item.source === 'Manual'; });
|
|
7950
|
+
|
|
7951
|
+
if (hasRepo && !hasLive) return 'Repo found. Live not checked.';
|
|
7952
|
+
|
|
7953
|
+
if (hasLive) return 'Live proof exists in the scan artifact.';
|
|
7954
|
+
|
|
7955
|
+
if ((contract.missing || []).length) return 'Repo gaps found. Live not checked.';
|
|
7956
|
+
|
|
7957
|
+
return 'No verified evidence yet.';
|
|
7958
|
+
|
|
7959
|
+
}
|
|
7960
|
+
|
|
7961
|
+
|
|
7962
|
+
|
|
7963
|
+
function contractMetricHtml(label, percent, value, isLive) {
|
|
7964
|
+
|
|
7965
|
+
var safePercent = Math.max(0, Math.min(100, Math.round(percent || 0)));
|
|
7966
|
+
|
|
7967
|
+
return '<div class="studio-sidebar-contract__compact-metric' + (isLive ? ' studio-sidebar-contract__compact-metric--live' : '') + '">' +
|
|
7968
|
+
|
|
7969
|
+
'<span>' + esc(label) + '</span>' +
|
|
7970
|
+
|
|
7971
|
+
'<strong>' + esc(value || (safePercent + '%')) + '</strong>' +
|
|
7972
|
+
|
|
7973
|
+
'<i class="studio-sidebar-contract__compact-meter" aria-hidden="true"><b style="width:' + safePercent + '%"></b></i>' +
|
|
7974
|
+
|
|
7975
|
+
'</div>';
|
|
7976
|
+
|
|
7977
|
+
}
|
|
7978
|
+
|
|
7979
|
+
|
|
7980
|
+
|
|
7981
|
+
function contractReadinessHtml(contract) {
|
|
7982
|
+
|
|
7983
|
+
var providerKey = contract.providerKey || contract.providerLabel || contract.areaLabel;
|
|
7984
|
+
|
|
7985
|
+
var providerStatus = contract.providerPercent > 0 ? contract.providerPercent + '%' : 'Not checked';
|
|
7986
|
+
|
|
7987
|
+
return '<section class="studio-sidebar-contract__readiness" aria-label="' + attrEsc(contract.providerLabel + ' readiness') + '">' +
|
|
7988
|
+
|
|
7989
|
+
'<div class="studio-sidebar-contract__head">' +
|
|
7990
|
+
|
|
7991
|
+
'<span class="studio-sidebar-contract__logo provider-logo' + logoClass(providerKey) + '" title="' + attrEsc(contract.providerLabel) + '" aria-hidden="true">' + logoHtml(providerKey, contract.providerLabel) + '</span>' +
|
|
7992
|
+
|
|
7993
|
+
'<div class="studio-sidebar-contract__head-text"><strong>' + esc(contract.providerLabel) + '</strong><span>' + esc(contract.areaLabel) + '</span></div>' +
|
|
7994
|
+
|
|
7995
|
+
'<span class="studio-sidebar-contract__status">' + esc(sidebarStatusBadge(contract)) + '</span>' +
|
|
7996
|
+
|
|
7997
|
+
'</div>' +
|
|
7998
|
+
|
|
7999
|
+
'<div class="studio-sidebar-contract__compact-metrics">' +
|
|
8000
|
+
|
|
8001
|
+
contractMetricHtml('Repo files', contract.repoPercent, contract.repoPercent + '%', false) +
|
|
8002
|
+
|
|
8003
|
+
contractMetricHtml('Provider live', contract.providerPercent, providerStatus, true) +
|
|
8004
|
+
|
|
8005
|
+
'</div>' +
|
|
8006
|
+
|
|
8007
|
+
'<p class="studio-sidebar-contract__summary">' + esc(sidebarReadinessSentence(contract)) + '</p>' +
|
|
8008
|
+
|
|
8009
|
+
'</section>';
|
|
8010
|
+
|
|
8011
|
+
}
|
|
8012
|
+
|
|
8013
|
+
|
|
8014
|
+
|
|
8015
|
+
function contractRepoSignalsHtml(contract) {
|
|
8016
|
+
|
|
8017
|
+
var repoItems = (contract.connected || []).filter(function (item) { return item.source === 'Repo files'; });
|
|
8018
|
+
|
|
8019
|
+
if (!repoItems.length) return '';
|
|
8020
|
+
|
|
8021
|
+
var rows = repoItems.slice(0, 6).map(function (item) {
|
|
8022
|
+
|
|
8023
|
+
return '<li><strong>' + esc(item.label || 'Repo evidence') + '</strong><span>' + esc(item.source || 'Repo files') + '</span></li>';
|
|
8024
|
+
|
|
8025
|
+
}).join('');
|
|
8026
|
+
|
|
8027
|
+
return '<section class="studio-sidebar-contract__repo-signals">' +
|
|
8028
|
+
|
|
8029
|
+
'<h4>' + repoItems.length + ' repo signal' + (repoItems.length === 1 ? '' : 's') + ' in the codebase.</h4>' +
|
|
8030
|
+
|
|
8031
|
+
'<ul class="studio-sidebar-contract__signal-list">' + rows + '</ul>' +
|
|
8032
|
+
|
|
8033
|
+
'</section>';
|
|
8034
|
+
|
|
8035
|
+
}
|
|
8036
|
+
|
|
8037
|
+
|
|
8038
|
+
|
|
8039
|
+
function contractAttentionItems(contract) {
|
|
8040
|
+
|
|
8041
|
+
var items = [];
|
|
8042
|
+
|
|
8043
|
+
(contract.missing || []).slice(0, 3).forEach(function (item) {
|
|
8044
|
+
|
|
8045
|
+
items.push({
|
|
8046
|
+
|
|
8047
|
+
label: item.label,
|
|
8048
|
+
|
|
8049
|
+
detail: item.detail || 'Repo/config check needs attention.',
|
|
8050
|
+
|
|
8051
|
+
source: 'Agent-code',
|
|
8052
|
+
|
|
8053
|
+
next: 'Ask the agent to inspect related files and patch only repo code/config.'
|
|
6125
8054
|
|
|
6126
|
-
|
|
8055
|
+
});
|
|
6127
8056
|
|
|
6128
|
-
|
|
8057
|
+
});
|
|
6129
8058
|
|
|
6130
|
-
|
|
8059
|
+
(contract.manual || []).slice(0, 3).forEach(function (item) {
|
|
6131
8060
|
|
|
6132
|
-
|
|
8061
|
+
items.push({
|
|
6133
8062
|
|
|
6134
|
-
|
|
8063
|
+
label: item.label,
|
|
6135
8064
|
|
|
6136
|
-
|
|
8065
|
+
detail: item.detail || 'MCP/API/dashboard required.',
|
|
6137
8066
|
|
|
6138
|
-
'
|
|
8067
|
+
source: item.source === 'MCP' ? 'MCP' : 'Manual',
|
|
6139
8068
|
|
|
6140
|
-
|
|
8069
|
+
next: item.source === 'MCP' ? 'Use read-only MCP/API verification.' : 'Ask the user to confirm the provider dashboard.'
|
|
6141
8070
|
|
|
6142
|
-
|
|
8071
|
+
});
|
|
6143
8072
|
|
|
6144
|
-
|
|
8073
|
+
});
|
|
6145
8074
|
|
|
6146
|
-
|
|
8075
|
+
return items;
|
|
6147
8076
|
|
|
6148
8077
|
}
|
|
6149
8078
|
|
|
6150
8079
|
|
|
6151
8080
|
|
|
6152
|
-
function
|
|
8081
|
+
function contractAttentionHtml(contract) {
|
|
6153
8082
|
|
|
6154
|
-
|
|
8083
|
+
var items = contractAttentionItems(contract);
|
|
6155
8084
|
|
|
6156
|
-
|
|
8085
|
+
if (!items.length) return '';
|
|
6157
8086
|
|
|
6158
|
-
|
|
8087
|
+
function attentionRow(item) {
|
|
6159
8088
|
|
|
6160
|
-
|
|
8089
|
+
return '<li class="studio-sidebar-contract__attention-item">' +
|
|
6161
8090
|
|
|
6162
|
-
|
|
8091
|
+
'<div class="studio-sidebar-contract__attention-top"><strong>' + esc(item.label || 'Attention') + '</strong><em>' + esc(item.source || 'Verify') + '</em></div>' +
|
|
6163
8092
|
|
|
6164
|
-
|
|
8093
|
+
(item.detail ? '<span>' + esc(item.detail) + '</span>' : '') +
|
|
8094
|
+
|
|
8095
|
+
(item.next ? '<b>' + esc(item.next) + '</b>' : '') +
|
|
8096
|
+
|
|
8097
|
+
'</li>';
|
|
6165
8098
|
|
|
6166
8099
|
}
|
|
6167
8100
|
|
|
6168
|
-
var
|
|
8101
|
+
var first = attentionRow(items[0]);
|
|
6169
8102
|
|
|
6170
|
-
|
|
8103
|
+
var more = items.length > 1
|
|
6171
8104
|
|
|
6172
|
-
|
|
8105
|
+
? '<li class="studio-sidebar-contract__attention-more"><details class="studio-sidebar-contract__attention-details"><summary>+' + (items.length - 1) + ' more details</summary><ul class="studio-sidebar-contract__attention-detail-list">' +
|
|
6173
8106
|
|
|
6174
|
-
|
|
8107
|
+
items.slice(1).map(function (item) {
|
|
6175
8108
|
|
|
6176
|
-
|
|
8109
|
+
return '<li><strong>' + esc(item.label || 'Attention') + '</strong>' +
|
|
6177
8110
|
|
|
6178
|
-
|
|
8111
|
+
(item.detail ? '<span>' + esc(item.detail) + '</span>' : '') +
|
|
6179
8112
|
|
|
6180
|
-
|
|
8113
|
+
(item.next ? '<b>' + esc(item.next) + '</b>' : '') +
|
|
6181
8114
|
|
|
6182
|
-
|
|
8115
|
+
'</li>';
|
|
6183
8116
|
|
|
6184
|
-
|
|
8117
|
+
}).join('') +
|
|
6185
8118
|
|
|
6186
|
-
|
|
8119
|
+
'</ul></details></li>'
|
|
6187
8120
|
|
|
8121
|
+
: '';
|
|
6188
8122
|
|
|
8123
|
+
return '<section class="studio-sidebar-contract__attention"><h4>Attention</h4><ul class="studio-sidebar-contract__attention-list">' + first + more + '</ul></section>';
|
|
6189
8124
|
|
|
6190
|
-
|
|
8125
|
+
}
|
|
6191
8126
|
|
|
6192
|
-
var areaLabel = LABELS[areaKey] || areaKey;
|
|
6193
8127
|
|
|
6194
|
-
var provider = providerLabel || areaLabel;
|
|
6195
8128
|
|
|
6196
|
-
|
|
8129
|
+
function contractNextActionHtml(contract, prompt) {
|
|
6197
8130
|
|
|
6198
|
-
|
|
8131
|
+
var buttonText = (contract.missing || []).length ? 'Copy focused agent prompt' : (contract.manual || []).length ? 'Copy live check prompt' : 'Copy verification prompt';
|
|
6199
8132
|
|
|
6200
|
-
|
|
8133
|
+
var note = (contract.missing || []).length ? 'Inspect repo wiring, patch scoped gaps, then rescan.' : (contract.manual || []).length ? 'Confirm in dashboard or connect MCP.' : 'Inspect repo wiring, then verify live setup.';
|
|
6201
8134
|
|
|
6202
|
-
|
|
8135
|
+
var accessClass = contract.hasMcpAccess ? ' studio-sidebar-contract__access-state--confirmed' : ' studio-sidebar-contract__access-state--needs-connection';
|
|
6203
8136
|
|
|
6204
|
-
|
|
8137
|
+
var accessLabel = contract.hasMcpAccess ? 'Available' : 'Connect';
|
|
6205
8138
|
|
|
6206
|
-
|
|
8139
|
+
return '<div class="studio-sidebar-contract__next-action">' +
|
|
6207
8140
|
|
|
6208
|
-
'
|
|
8141
|
+
'<strong>Next best action</strong>' +
|
|
6209
8142
|
|
|
6210
|
-
'
|
|
8143
|
+
'<p>' + esc(note) + '</p>' +
|
|
6211
8144
|
|
|
6212
|
-
'
|
|
8145
|
+
'<div class="studio-sidebar-contract__access-state' + accessClass + '"><span>MCP/API access</span><b>' + esc(accessLabel) + '</b></div>' +
|
|
6213
8146
|
|
|
6214
|
-
'
|
|
8147
|
+
'<div class="studio-sidebar-contract__actions"><button type="button" class="studio-action-button studio-action-button--primary" data-copy-prompt="' + attrEsc(prompt) + '">' + esc(buttonText) + '</button></div>' +
|
|
6215
8148
|
|
|
6216
|
-
''
|
|
8149
|
+
'</div>';
|
|
6217
8150
|
|
|
6218
|
-
|
|
8151
|
+
}
|
|
6219
8152
|
|
|
6220
|
-
'- Do not rewrite unrelated product, auth, payment, database, deployment, or analytics code.',
|
|
6221
8153
|
|
|
6222
|
-
'- Do not put secret keys in client code, public env variables, or browser-executed files.',
|
|
6223
8154
|
|
|
6224
|
-
|
|
8155
|
+
function selectedNodeContractHtml(contract) {
|
|
6225
8156
|
|
|
6226
|
-
|
|
8157
|
+
var prompt = focusedContractPrompt(contract);
|
|
6227
8158
|
|
|
6228
|
-
|
|
8159
|
+
return '<section class="studio-sidebar-contract" aria-label="Selected node production checklist">' +
|
|
6229
8160
|
|
|
6230
|
-
|
|
8161
|
+
contractReadinessHtml(contract) +
|
|
6231
8162
|
|
|
6232
|
-
|
|
8163
|
+
contractRepoSignalsHtml(contract) +
|
|
6233
8164
|
|
|
6234
|
-
|
|
8165
|
+
contractAttentionHtml(contract) +
|
|
6235
8166
|
|
|
6236
|
-
|
|
8167
|
+
contractNextActionHtml(contract, prompt) +
|
|
6237
8168
|
|
|
8169
|
+
'</section>';
|
|
6238
8170
|
|
|
6239
8171
|
}
|
|
6240
8172
|
|
|
6241
8173
|
|
|
6242
8174
|
|
|
6243
|
-
function setupActionsHtml(areaKey, missions, providerLabel, automation) {
|
|
8175
|
+
function setupActionsHtml(areaKey, missions, providerLabel, automation, currentProvider) {
|
|
6244
8176
|
|
|
6245
8177
|
var prompt = buildPrompt(areaKey, missions, providerLabel, automation);
|
|
6246
8178
|
|
|
@@ -6264,6 +8196,8 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6264
8196
|
|
|
6265
8197
|
var supportsMcp = Boolean(mcpProvider);
|
|
6266
8198
|
|
|
8199
|
+
var providerKey = iconProviderKey(areaKey, currentProvider, missions[0], providerLabel);
|
|
8200
|
+
|
|
6267
8201
|
var title = esc(providerLabel || LABELS[areaKey] || areaKey) + (isManualOnly ? ' manual check' : hasFixes ? ' fix prompt' : ' setup');
|
|
6268
8202
|
|
|
6269
8203
|
var meta = supportsMcp ? 'MCP verification available' : 'Prompt only';
|
|
@@ -6286,7 +8220,13 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6286
8220
|
|
|
6287
8221
|
return '<section class="studio-setup-actions" aria-label="Setup actions">' +
|
|
6288
8222
|
|
|
6289
|
-
'<div class="studio-setup-actions__head"
|
|
8223
|
+
'<div class="studio-setup-actions__head">' +
|
|
8224
|
+
|
|
8225
|
+
'<span class="studio-setup-actions__logo provider-logo' + logoClass(providerKey) + '" title="' + attrEsc(providerLabel || providerKey) + '" aria-hidden="true">' + logoHtml(providerKey, providerLabel) + '</span>' +
|
|
8226
|
+
|
|
8227
|
+
'<div class="studio-setup-actions__title"><strong>' + title + '</strong><span>' + meta + '</span></div>' +
|
|
8228
|
+
|
|
8229
|
+
'</div>' +
|
|
6290
8230
|
|
|
6291
8231
|
'<p class="studio-setup-actions__copy">' + esc(copy) + '</p>' +
|
|
6292
8232
|
|
|
@@ -6342,39 +8282,40 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6342
8282
|
|
|
6343
8283
|
var repoItems = [
|
|
6344
8284
|
|
|
6345
|
-
{ label: provider + '
|
|
8285
|
+
{ label: provider + ' repo wiring', detail: 'Not checked' },
|
|
6346
8286
|
|
|
6347
|
-
{ label: provider + ' env names
|
|
8287
|
+
{ label: provider + ' env names', detail: 'Unknown' },
|
|
6348
8288
|
|
|
6349
|
-
{ label: provider + ' server
|
|
8289
|
+
{ label: provider + ' server route', detail: 'Unknown' }
|
|
6350
8290
|
|
|
6351
8291
|
];
|
|
6352
8292
|
|
|
6353
8293
|
var manualItems = [
|
|
6354
8294
|
|
|
6355
|
-
{ label: 'Production
|
|
8295
|
+
{ label: 'Production account', detail: 'Not checked' },
|
|
6356
8296
|
|
|
6357
|
-
{ label: '
|
|
8297
|
+
{ label: 'Webhook or credentials', detail: 'Not checked' }
|
|
6358
8298
|
|
|
6359
8299
|
];
|
|
6360
8300
|
|
|
6361
8301
|
var external = automation && automation.mcpProvider
|
|
6362
8302
|
|
|
6363
|
-
? groupHtml('
|
|
8303
|
+
? groupHtml('Provider live check', [{ label: provider + ' read-only MCP check available', detail: 'Use read-only MCP verification when already configured by the IDE.' }], 'external')
|
|
6364
8304
|
|
|
6365
8305
|
: '';
|
|
6366
8306
|
|
|
6367
|
-
return '<section class="studio-verification studio-provider-readiness" aria-label="' +
|
|
8307
|
+
return '<section class="studio-verification studio-provider-readiness" aria-label="' + attrEsc(provider + ' setup readiness') + '">' +
|
|
6368
8308
|
|
|
6369
8309
|
'<h3 class="studio-verification__title">' + esc(provider) + '</h3>' +
|
|
6370
8310
|
|
|
6371
|
-
readinessMetersHtml(0, 0, 'Not checked') +
|
|
8311
|
+
readinessMetersHtml(0, 0, 'Not checked', provider) +
|
|
8312
|
+
|
|
6372
8313
|
|
|
6373
|
-
|
|
8314
|
+
repoEvidenceCardHtml(repoItems.map(function (item) {
|
|
6374
8315
|
|
|
6375
|
-
|
|
8316
|
+
return { label: item.label, detail: item.detail, tone: 'missing' };
|
|
6376
8317
|
|
|
6377
|
-
|
|
8318
|
+
}), repoItems.length) +
|
|
6378
8319
|
|
|
6379
8320
|
external +
|
|
6380
8321
|
|
|
@@ -6386,18 +8327,24 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6386
8327
|
|
|
6387
8328
|
|
|
6388
8329
|
|
|
6389
|
-
function readinessMetersHtml(repoPercent, providerPercent, providerStatus) {
|
|
8330
|
+
function readinessMetersHtml(repoPercent, providerPercent, providerStatus, providerLabel) {
|
|
6390
8331
|
|
|
6391
8332
|
var repo = Math.max(0, Math.min(100, Math.round(repoPercent || 0)));
|
|
6392
8333
|
|
|
6393
8334
|
var provider = Math.max(0, Math.min(100, Math.round(providerPercent || 0)));
|
|
6394
8335
|
var providerValue = providerStatus || (provider + '%');
|
|
6395
8336
|
|
|
6396
|
-
return '<div class="studio-provider
|
|
8337
|
+
return '<div class="verification-card studio-mission-card studio-mission-card--provider" aria-label="Repo and live provider readiness">' +
|
|
8338
|
+
|
|
8339
|
+
'<span class="studio-mission-card__title">' + esc(providerLabel || 'Provider') + '</span>' +
|
|
8340
|
+
|
|
8341
|
+
'<div class="verification-meter studio-mission-card__meters">' +
|
|
6397
8342
|
|
|
6398
|
-
'<
|
|
8343
|
+
'<b class="studio-mission-card__meter-row studio-mission-card__meter-row--repo"><span class="studio-mission-card__meter-label">Repo files</span><span class="verification-meter__bar studio-mission-card__meter" aria-hidden="true"><span class="verification-meter__fill studio-mission-card__meter-fill" style="width:' + repo + '%"></span></span><span class="studio-mission-card__meter-value">' + repo + '%</span></b>' +
|
|
6399
8344
|
|
|
6400
|
-
'<
|
|
8345
|
+
'<b class="studio-mission-card__meter-row studio-mission-card__meter-row--provider"><span class="studio-mission-card__meter-label">Provider live</span><span class="verification-meter__bar studio-mission-card__meter" aria-hidden="true"><span class="verification-meter__fill studio-mission-card__meter-fill" style="width:' + provider + '%"></span></span><span class="studio-mission-card__meter-value">' + esc(providerValue) + '</span></b>' +
|
|
8346
|
+
|
|
8347
|
+
'</div>' +
|
|
6401
8348
|
|
|
6402
8349
|
'</div>';
|
|
6403
8350
|
|
|
@@ -6413,6 +8360,90 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6413
8360
|
|
|
6414
8361
|
}
|
|
6415
8362
|
|
|
8363
|
+
function checkGroupKey(check) {
|
|
8364
|
+
|
|
8365
|
+
if (check.evidenceClass === 'manual-dashboard') return 'manual-dashboard';
|
|
8366
|
+
|
|
8367
|
+
if (check.evidenceSource === 'provider' || check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier' || check.status === 'needs-connection' || check.status === 'unknown') return 'mcp-verifier';
|
|
8368
|
+
|
|
8369
|
+
if (check.evidenceClass === 'repo-verified' || check.status === 'passed') return 'repo-verified';
|
|
8370
|
+
|
|
8371
|
+
return 'missing-repo-fix';
|
|
8372
|
+
|
|
8373
|
+
}
|
|
8374
|
+
|
|
8375
|
+
function providerChecksForMission(mission) {
|
|
8376
|
+
|
|
8377
|
+
return (mission.checks || []).filter(function (check) {
|
|
8378
|
+
|
|
8379
|
+
return check.evidenceSource === 'provider' || check.evidenceSource === 'mcp' || check.evidenceClass === 'mcp-verifier';
|
|
8380
|
+
|
|
8381
|
+
});
|
|
8382
|
+
|
|
8383
|
+
}
|
|
8384
|
+
|
|
8385
|
+
function providerStatusValue(mission, providerPercent) {
|
|
8386
|
+
|
|
8387
|
+
var checks = providerChecksForMission(mission);
|
|
8388
|
+
|
|
8389
|
+
if (!checks.length) return 'No live checks';
|
|
8390
|
+
|
|
8391
|
+
var needsMcp = checks.some(function (check) {
|
|
8392
|
+
|
|
8393
|
+
return check.status === 'needs-connection' || check.verificationStatus === 'needs_mcp';
|
|
8394
|
+
|
|
8395
|
+
});
|
|
8396
|
+
|
|
8397
|
+
if (needsMcp) return 'Needs MCP';
|
|
8398
|
+
|
|
8399
|
+
return providerPercent > 0 ? providerPercent + '%' : 'Not checked';
|
|
8400
|
+
|
|
8401
|
+
}
|
|
8402
|
+
|
|
8403
|
+
function diffHtml(mission) {
|
|
8404
|
+
|
|
8405
|
+
var layer = artifact && artifact.missionGraph && artifact.missionGraph.verificationLayer;
|
|
8406
|
+
|
|
8407
|
+
var diffs = layer && Array.isArray(layer.diffs) ? layer.diffs : [];
|
|
8408
|
+
|
|
8409
|
+
var provider = normalizeProviderToken(mission.provider || '');
|
|
8410
|
+
|
|
8411
|
+
var area = mission.area || '';
|
|
8412
|
+
|
|
8413
|
+
var matches = diffs.filter(function (diff) {
|
|
8414
|
+
|
|
8415
|
+
return normalizeProviderToken(diff.provider || '') === provider && (!area || diff.area === area);
|
|
8416
|
+
|
|
8417
|
+
});
|
|
8418
|
+
|
|
8419
|
+
if (!matches.length) return '';
|
|
8420
|
+
|
|
8421
|
+
var rows = matches.slice(0, 4).map(function (diff) {
|
|
8422
|
+
|
|
8423
|
+
return '<div class="studio-mismatch-card__row">' +
|
|
8424
|
+
|
|
8425
|
+
'<span class="studio-mismatch-card__title">' + esc(diff.title || 'Provider mismatch') + '</span>' +
|
|
8426
|
+
|
|
8427
|
+
'<div class="studio-mismatch-card__comparison">' +
|
|
8428
|
+
|
|
8429
|
+
'<span class="studio-mismatch-card__side studio-mismatch-card__side--repo"><b>Repo</b><em>' + esc(diff.repoExpectation || 'Expected setup evidence in repository') + '</em></span>' +
|
|
8430
|
+
|
|
8431
|
+
'<span class="studio-mismatch-card__side studio-mismatch-card__side--live"><b>Live</b><em>' + esc(diff.providerActual || 'Not verified') + '</em></span>' +
|
|
8432
|
+
|
|
8433
|
+
'</div></div>';
|
|
8434
|
+
|
|
8435
|
+
}).join('');
|
|
8436
|
+
|
|
8437
|
+
return '<div class="studio-mismatch-card">' +
|
|
8438
|
+
|
|
8439
|
+
'<div class="studio-mismatch-card__head"><strong>Mismatch</strong><span class="studio-mismatch-card__count">' + matches.length + '</span></div>' +
|
|
8440
|
+
|
|
8441
|
+
'<p class="studio-mismatch-card__note">Repo evidence is present, but the live provider still needs MCP or dashboard confirmation.</p>' +
|
|
8442
|
+
|
|
8443
|
+
'<div class="studio-mismatch-card__list">' + rows + '</div></div>';
|
|
8444
|
+
|
|
8445
|
+
}
|
|
8446
|
+
|
|
6416
8447
|
|
|
6417
8448
|
|
|
6418
8449
|
function groupHtml(label, items, tone) {
|
|
@@ -6423,7 +8454,7 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6423
8454
|
|
|
6424
8455
|
var rows = slice.map(function (item) {
|
|
6425
8456
|
|
|
6426
|
-
var title = item.detail ? ' title="' +
|
|
8457
|
+
var title = item.detail ? ' title="' + attrEsc(item.detail) + '"' : '';
|
|
6427
8458
|
|
|
6428
8459
|
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
8460
|
|
|
@@ -6445,6 +8476,44 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6445
8476
|
|
|
6446
8477
|
}
|
|
6447
8478
|
|
|
8479
|
+
function repoEvidenceCardHtml(repoItems, missingCount) {
|
|
8480
|
+
|
|
8481
|
+
if (!repoItems.length) return '';
|
|
8482
|
+
|
|
8483
|
+
var rows = repoItems.slice(0, 7).map(function (item) {
|
|
8484
|
+
|
|
8485
|
+
var tone = item.tone || 'verified';
|
|
8486
|
+
var statusLabel = tone === 'missing' ? 'Fix needed' : 'Verified';
|
|
8487
|
+
var title = item.detail ? ' title="' + attrEsc(item.detail) + '"' : '';
|
|
8488
|
+
|
|
8489
|
+
return '<li class="studio-mission-card__check studio-mission-card__check--' + esc(tone) + '"' + title + '>' +
|
|
8490
|
+
|
|
8491
|
+
'<b class="studio-mission-card__check-label">' + esc(item.label) + '</b>' +
|
|
8492
|
+
|
|
8493
|
+
'<em class="studio-mission-card__check-status">' + esc(statusLabel) + '</em>' +
|
|
8494
|
+
|
|
8495
|
+
'</li>';
|
|
8496
|
+
|
|
8497
|
+
}).join('');
|
|
8498
|
+
|
|
8499
|
+
return '<div class="repo-evidence-card studio-mission-card studio-mission-card--repo">' +
|
|
8500
|
+
|
|
8501
|
+
'<span class="studio-mission-card__title">' +
|
|
8502
|
+
|
|
8503
|
+
(missingCount > 0
|
|
8504
|
+
|
|
8505
|
+
? missingCount + ' repo fix' + (missingCount === 1 ? '' : 'es') + ' in the codebase.'
|
|
8506
|
+
|
|
8507
|
+
: repoItems.length + ' repo check' + (repoItems.length === 1 ? '' : 's') + ' verified in the codebase.') +
|
|
8508
|
+
|
|
8509
|
+
'</span>' +
|
|
8510
|
+
|
|
8511
|
+
'<ul class="studio-mission-card__check-list">' + rows + '</ul>' +
|
|
8512
|
+
|
|
8513
|
+
'</div>';
|
|
8514
|
+
|
|
8515
|
+
}
|
|
8516
|
+
|
|
6448
8517
|
|
|
6449
8518
|
|
|
6450
8519
|
function missionBlockHtml(missions, providerLabel) {
|
|
@@ -6467,65 +8536,104 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6467
8536
|
|
|
6468
8537
|
mission.checks.forEach(function (check) {
|
|
6469
8538
|
|
|
6470
|
-
var bucket =
|
|
8539
|
+
var bucket = checkGroupKey(check);
|
|
6471
8540
|
|
|
6472
8541
|
groups[bucket].push(checkToItem(check));
|
|
6473
8542
|
|
|
6474
8543
|
});
|
|
6475
8544
|
|
|
6476
8545
|
var actionable = groups['repo-verified'].length + groups['missing-repo-fix'].length;
|
|
6477
|
-
var
|
|
6478
|
-
var
|
|
8546
|
+
var providerChecks = providerChecksForMission(mission);
|
|
8547
|
+
var providerPercent = typeof mission.providerReadinessPercent === 'number'
|
|
8548
|
+
? Math.max(0, Math.min(100, Math.round(mission.providerReadinessPercent)))
|
|
8549
|
+
: 0;
|
|
8550
|
+
var providerValue = providerStatusValue(mission, providerPercent);
|
|
8551
|
+
var repoPercent = typeof mission.repoReadinessPercent === 'number'
|
|
8552
|
+
? Math.max(0, Math.min(100, Math.round(mission.repoReadinessPercent)))
|
|
8553
|
+
: (mission.readinessPercent || 0);
|
|
8554
|
+
var repoItems = groups['missing-repo-fix'].map(function (item) {
|
|
6479
8555
|
|
|
6480
|
-
return
|
|
8556
|
+
return { label: item.label, detail: item.detail, tone: 'missing' };
|
|
6481
8557
|
|
|
6482
|
-
}).
|
|
6483
|
-
var providerPercent = manualTotal ? Math.round((manualDone / manualTotal) * 100) : 0;
|
|
6484
|
-
var providerValue = manualTotal ? (manualDone > 0 ? providerPercent + '%' : 'Not checked') : 'No live checks';
|
|
8558
|
+
}).concat(groups['repo-verified'].map(function (item) {
|
|
6485
8559
|
|
|
6486
|
-
|
|
8560
|
+
return { label: item.label, detail: item.detail, tone: 'verified' };
|
|
6487
8561
|
|
|
6488
|
-
|
|
8562
|
+
}));
|
|
6489
8563
|
|
|
6490
|
-
|
|
8564
|
+
var body =
|
|
6491
8565
|
|
|
6492
|
-
|
|
8566
|
+
repoEvidenceCardHtml(repoItems, groups['missing-repo-fix'].length) +
|
|
6493
8567
|
|
|
6494
|
-
|
|
8568
|
+
groupHtml('Provider live check', groups['mcp-verifier'], 'external') +
|
|
6495
8569
|
|
|
6496
|
-
|
|
8570
|
+
groupHtml('Manual dashboard check', groups['manual-dashboard'], 'manual') +
|
|
6497
8571
|
|
|
6498
|
-
|
|
8572
|
+
diffHtml(mission);
|
|
6499
8573
|
|
|
6500
|
-
|
|
8574
|
+
if (!body) return '';
|
|
8575
|
+
|
|
8576
|
+
return '<section class="studio-verification studio-wiring studio-mission-graph" aria-label="Mission evidence">' +
|
|
6501
8577
|
|
|
6502
|
-
|
|
8578
|
+
readinessMetersHtml(repoPercent, providerPercent, providerValue, providerLabel || mission.providerLabel || 'Detected evidence') +
|
|
6503
8579
|
|
|
6504
|
-
|
|
8580
|
+
body + '</section>';
|
|
6505
8581
|
|
|
6506
|
-
|
|
8582
|
+
}
|
|
6507
8583
|
|
|
6508
|
-
groupHtml('Manual dashboard check', groups['manual-dashboard'], 'manual');
|
|
6509
8584
|
|
|
6510
|
-
if (!body) return '';
|
|
6511
8585
|
|
|
6512
|
-
|
|
8586
|
+
function isLockedArea(areaKey) {
|
|
8587
|
+
|
|
8588
|
+
var nodes = document.querySelectorAll('.studio-node');
|
|
6513
8589
|
|
|
6514
|
-
|
|
8590
|
+
for (var i = 0; i < nodes.length; i += 1) {
|
|
6515
8591
|
|
|
6516
|
-
|
|
8592
|
+
if (nodes[i].getAttribute('data-area-key') === areaKey) {
|
|
6517
8593
|
|
|
6518
|
-
|
|
8594
|
+
return nodes[i].getAttribute('data-locked-map') === '1';
|
|
6519
8595
|
|
|
6520
|
-
|
|
8596
|
+
}
|
|
8597
|
+
|
|
8598
|
+
}
|
|
8599
|
+
|
|
8600
|
+
return false;
|
|
6521
8601
|
|
|
6522
8602
|
}
|
|
6523
8603
|
|
|
6524
8604
|
|
|
6525
8605
|
|
|
6526
|
-
function
|
|
8606
|
+
function lockedAreaHtml(label) {
|
|
8607
|
+
|
|
8608
|
+
return '<div class="studio-setup-panel__inner studio-setup-panel__inner--locked">' +
|
|
8609
|
+
|
|
8610
|
+
'<div class="studio-setup-panel__head">' +
|
|
8611
|
+
|
|
8612
|
+
'<div class="studio-setup-panel__title"><strong>Upgrade to Pro</strong><span>Full mission map</span></div>' +
|
|
8613
|
+
|
|
8614
|
+
'</div>' +
|
|
8615
|
+
|
|
8616
|
+
'<section class="studio-setup-actions studio-setup-actions--locked" aria-label="Locked Pro section">' +
|
|
8617
|
+
|
|
8618
|
+
'<div class="studio-setup-actions__head">' +
|
|
8619
|
+
|
|
8620
|
+
'<span class="studio-setup-actions__logo provider-logo" aria-hidden="true">' + logoHtml('', label) + '</span>' +
|
|
8621
|
+
|
|
8622
|
+
'<div class="studio-setup-actions__title"><strong>' + esc(label) + ' is locked on the Free plan</strong><span>Pro-only production area</span></div>' +
|
|
8623
|
+
|
|
8624
|
+
'</div>' +
|
|
8625
|
+
|
|
8626
|
+
'<p class="studio-setup-actions__copy">Free shows App Flow, Frontend, Backend / API, Auth, Database, and Payments. Pro unlocks Deployment, Monitoring, Security, Testing, Landing, and Error Handling.</p>' +
|
|
8627
|
+
|
|
8628
|
+
'</section>' +
|
|
8629
|
+
|
|
8630
|
+
'</div>';
|
|
8631
|
+
|
|
8632
|
+
}
|
|
8633
|
+
|
|
8634
|
+
|
|
6527
8635
|
|
|
6528
|
-
|
|
8636
|
+
function render(areaKey) {
|
|
6529
8637
|
|
|
6530
8638
|
var area = areas.find(function (a) { return a.key === areaKey; });
|
|
6531
8639
|
|
|
@@ -6541,6 +8649,14 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6541
8649
|
|
|
6542
8650
|
var label = (area && area.label) || LABELS[areaKey] || areaKey;
|
|
6543
8651
|
|
|
8652
|
+
if (isLockedArea(areaKey)) {
|
|
8653
|
+
|
|
8654
|
+
panel.innerHTML = lockedAreaHtml(label);
|
|
8655
|
+
|
|
8656
|
+
return;
|
|
8657
|
+
|
|
8658
|
+
}
|
|
8659
|
+
|
|
6544
8660
|
var missions = (area && area.providerMissions) || [];
|
|
6545
8661
|
|
|
6546
8662
|
var selectedBeforeRender = selectedProviders[areaKey] || '';
|
|
@@ -6559,6 +8675,10 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6559
8675
|
|
|
6560
8676
|
var automation = automationFor(areaKey, mission, current);
|
|
6561
8677
|
|
|
8678
|
+
var categoryGaps = gaps.filter(function (gap) { return gap.primaryMapCategory === areaKey; });
|
|
8679
|
+
|
|
8680
|
+
var contract = buildCliSidebarContract(areaKey, label, mission, categoryGaps, providerLabel, automation, current);
|
|
8681
|
+
|
|
6562
8682
|
|
|
6563
8683
|
|
|
6564
8684
|
panel.innerHTML =
|
|
@@ -6575,31 +8695,29 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6575
8695
|
|
|
6576
8696
|
addedSetupPathHtml(areaKey, current, providerLabel, missions) +
|
|
6577
8697
|
|
|
6578
|
-
setupActionsHtml(areaKey, panelMissions, providerLabel, automation) +
|
|
8698
|
+
setupActionsHtml(areaKey, panelMissions, providerLabel, automation, current) +
|
|
6579
8699
|
|
|
6580
|
-
|
|
8700
|
+
setupReadinessHtml(areaKey, current, providerLabel, panelMissions, automation) +
|
|
6581
8701
|
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
'</div>';
|
|
8702
|
+
selectedNodeContractHtml(contract) +
|
|
6585
8703
|
|
|
8704
|
+
'</div>';
|
|
6586
8705
|
|
|
6587
8706
|
|
|
6588
|
-
panel.querySelectorAll('[data-copy-prompt]').forEach(function (btn) {
|
|
6589
8707
|
|
|
6590
|
-
|
|
8708
|
+
panel.querySelectorAll('[data-provider-option]').forEach(function (tile) {
|
|
6591
8709
|
|
|
6592
|
-
|
|
8710
|
+
tile.addEventListener('click', function () {
|
|
6593
8711
|
|
|
6594
|
-
|
|
8712
|
+
var providerOption = tile.getAttribute('data-provider-option') || '';
|
|
6595
8713
|
|
|
6596
|
-
|
|
8714
|
+
if (!providerOption) return;
|
|
6597
8715
|
|
|
6598
|
-
|
|
8716
|
+
selectedProviders[areaKey] = providerOption;
|
|
6599
8717
|
|
|
6600
|
-
|
|
8718
|
+
syncMapNode(areaKey, providerOption);
|
|
6601
8719
|
|
|
6602
|
-
|
|
8720
|
+
render(areaKey);
|
|
6603
8721
|
|
|
6604
8722
|
});
|
|
6605
8723
|
|
|
@@ -6607,15 +8725,21 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6607
8725
|
|
|
6608
8726
|
|
|
6609
8727
|
|
|
6610
|
-
panel.querySelectorAll('[data-
|
|
8728
|
+
panel.querySelectorAll('[data-copy-prompt]').forEach(function (btn) {
|
|
6611
8729
|
|
|
6612
8730
|
btn.addEventListener('click', function () {
|
|
6613
8731
|
|
|
6614
|
-
var
|
|
8732
|
+
var text = btn.getAttribute('data-copy-prompt') || '';
|
|
8733
|
+
|
|
8734
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
8735
|
+
|
|
8736
|
+
var label = btn.textContent;
|
|
8737
|
+
|
|
8738
|
+
btn.textContent = 'Copied';
|
|
6615
8739
|
|
|
6616
|
-
|
|
8740
|
+
setTimeout(function () { btn.textContent = label; }, 1200);
|
|
6617
8741
|
|
|
6618
|
-
|
|
8742
|
+
});
|
|
6619
8743
|
|
|
6620
8744
|
});
|
|
6621
8745
|
|
|
@@ -6670,46 +8794,79 @@ var PANEL_CLIENT_SCRIPT = `
|
|
|
6670
8794
|
`;
|
|
6671
8795
|
|
|
6672
8796
|
// src/report/providerLogos.ts
|
|
8797
|
+
var PROVIDER_LOGO_FALLBACK_SVG = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3 20 7.5v9L12 21 4 16.5v-9L12 3Zm0 3.3-5 2.8v5.8l5 2.8 5-2.8V9.1l-5-2.8Zm0 3.2a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"/></svg>';
|
|
6673
8798
|
var PROVIDER_LOGOS = {
|
|
6674
|
-
supabase: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
|
|
6675
|
-
clerk: '<svg viewBox="0 0 24 24" aria-hidden="true"><path
|
|
6676
|
-
authjs: '<svg viewBox="0 0 24 24"
|
|
6677
|
-
auth0: '<svg viewBox="0 0 24 24" aria-hidden="true"><
|
|
6678
|
-
"better-auth": '<svg viewBox="0 0 24 24" aria-hidden="true"><rect
|
|
8799
|
+
supabase: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#3fcf8e" d="M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"/></svg>',
|
|
8800
|
+
clerk: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m21.47 20.829-2.881-2.881a.572.572 0 0 0-.7-.084 6.854 6.854 0 0 1-7.081 0 .576.576 0 0 0-.7.084l-2.881 2.881a.576.576 0 0 0-.103.69.57.57 0 0 0 .166.186 12 12 0 0 0 14.113 0 .58.58 0 0 0 .239-.423.576.576 0 0 0-.172-.453Zm.002-17.668-2.88 2.88a.569.569 0 0 1-.701.084A6.857 6.857 0 0 0 8.724 8.08a6.862 6.862 0 0 0-1.222 3.692 6.86 6.86 0 0 0 .978 3.764.573.573 0 0 1-.083.699l-2.881 2.88a.567.567 0 0 1-.864-.063A11.993 11.993 0 0 1 6.771 2.7a11.99 11.99 0 0 1 14.637-.405.566.566 0 0 1 .232.418.57.57 0 0 1-.168.448Zm-7.118 12.261a3.427 3.427 0 1 0 0-6.854 3.427 3.427 0 0 0 0 6.854Z"/></svg>',
|
|
8801
|
+
authjs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#18c6cf" d="M12 1.4 21 4.9v6.7c0 5.1-3.5 8.7-9 11-5.5-2.3-9-5.9-9-11V4.9l9-3.5Z"/><path fill="#8b2ff5" d="M12 1.4 21 4.9v6.7c0 5.1-3.5 8.7-9 11V1.4Z"/><path fill="#fff7df" d="M12 7a5.2 5.2 0 1 0 0 10.4A5.2 5.2 0 0 0 12 7Z"/><path fill="#ff8a00" d="M10.2 9.6a2.2 2.2 0 0 1 2.9 2.9l3.5 3.5h-2.2v1.5h-1.8V16h-1.4l-1.3-1.3a2.2 2.2 0 0 1 .3-5.1Zm.9 1.8a.7.7 0 1 0 0 1.4.7.7 0 0 0 0-1.4Z"/></svg>',
|
|
8802
|
+
auth0: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#eb5424" d="M21.98 7.448L19.62 0H4.347L2.02 7.448c-1.352 4.312.03 9.206 3.815 12.015L12.007 24l6.157-4.552c3.755-2.81 5.182-7.688 3.815-12.015l-6.16 4.58 2.343 7.45-6.157-4.597-6.158 4.58 2.358-7.433-6.188-4.55 7.63-.045L12.008 0l2.356 7.404 7.615.044z"/></svg>',
|
|
8803
|
+
"better-auth": '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="1.5" y="1.5" width="21" height="21" rx="3" fill="#111827"/><path fill="#fffdf7" d="M5.2 6.2h5.15v4.25H5.2V6.2Zm8.45 0h5.15v4.25h-5.15V6.2Zm-8.45 7.35h5.15v4.25H5.2v-4.25Zm8.45 0h5.15v4.25h-5.15v-4.25Z"/><path fill="#3b82f6" d="M10.35 10.45h3.3v3.1h-3.3v-3.1Z"/></svg>',
|
|
6679
8804
|
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
|
|
6681
|
-
github: '<svg viewBox="0 0 24 24" aria-hidden="true"><path
|
|
6682
|
-
gitlab: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#FC6D26" d="
|
|
6683
|
-
neon: '<svg viewBox="0 0
|
|
6684
|
-
planetscale: '<svg viewBox="0 0 24 24" aria-hidden="true"><path
|
|
6685
|
-
mongodb: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
|
|
6686
|
-
turso: '<svg viewBox="0 0
|
|
8805
|
+
"lemon-squeezy": '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m7.4916 10.835 2.3748-6.5114a3.1497 3.1497 0 0 0-.065-2.3418C9.0315.183 6.9427-.398 5.2928.265 3.643.929 2.71 2.4348 3.512 4.3046l2.8197 6.5615c.219.509.97.489 1.16-.03m1.6798 1.0969 6.5334-2.7758c2.1699-.9219 2.7218-3.6907 1.022-5.2905l-.068-.063c-1.6669-1.5469-4.4217-1.002-5.3706 1.0359L8.3566 11.135c-.234.503.295 1.0199.8159.7979m.373.87 6.6454-2.5119c2.2078-.8349 4.6206.745 4.5886 3.0398l-.002.09c-.048 2.2358-2.3938 3.7376-4.5536 2.9467l-6.6724-2.4418a.595.595 0 0 1-.006-1.1229m-.386 1.9269 6.4375 2.9767a3.2997 3.2997 0 0 1 1.6658 1.6989c.769 1.7998-.283 3.6396-1.9328 4.3016-1.6499.662-3.4097.235-4.2097-1.6359l-2.8027-6.5694c-.217-.509.328-1.009.8419-.772"/></svg>',
|
|
8806
|
+
github: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>',
|
|
8807
|
+
gitlab: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#FC6D26" d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"/></svg>',
|
|
8808
|
+
neon: '<svg viewBox="0 0 64 64" aria-hidden="true"><path fill="#37c38f" d="M63 0.0177909V63.5526L38.4178 42.2501V63.5526H0V0L63 0.0177909ZM7.72251 55.8389H30.6953V25.3238L55.2779 47.0476V7.72922L7.72251 7.71559V55.8389Z"/></svg>',
|
|
8809
|
+
planetscale: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M0 12C0 5.373 5.373 0 12 0c4.873 0 9.067 2.904 10.947 7.077l-15.87 15.87a11.981 11.981 0 0 1-1.935-1.099L14.99 12H12l-8.485 8.485A11.962 11.962 0 0 1 0 12Zm12.004 12L24 12.004C23.998 18.628 18.628 23.998 12.004 24Z"/></svg>',
|
|
8810
|
+
mongodb: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115-.28-.394-.53-.954-.735-1.44-.036.495-.055.685-.523 1.184-.723.566-4.438 3.682-4.74 10.02-.282 5.912 4.27 9.435 4.888 9.884l.07.05A73.49 73.49 0 0 1 11.91 24h.481c.114-1.032.284-2.056.51-3.07.417-.296.604-.463.85-.693a11.342 11.342 0 0 0 3.639-8.464c.01-.814-.103-1.662-.197-2.218Zm-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695-.381-.045-.765-1.76-.765-2.405Z"/></svg>',
|
|
8811
|
+
turso: '<svg viewBox="0 0 241 240" aria-hidden="true"><path fill="#4ff8d2" d="M220.035 83.61C215.365 55.67 190.875 35 190.875 35V65.78L176.335 69.53L167.225 58.56L162.415 68.02C152.495 65.32 138.835 63.58 120.045 63.58C101.255 63.58 87.5949 65.33 77.6749 68.02L72.8649 58.56L63.7549 69.53L49.2149 65.78V35C49.2149 35 24.7249 55.67 20.0549 83.61L52.1949 94.73C53.2449 114.16 61.9849 166.61 64.4849 171.37C67.1449 176.44 81.2649 190.93 92.3149 196.5C92.3149 196.5 96.3149 192.27 98.7549 188.54C101.855 192.19 117.865 204.99 120.055 204.99C122.245 204.99 138.255 192.2 141.355 188.54C143.795 192.27 147.795 196.5 147.795 196.5C158.845 190.93 172.965 176.44 175.625 171.37C178.125 166.61 186.865 114.16 187.915 94.73L220.055 83.61H220.035ZM173.845 128.35L152.095 130.29L154.005 156.96C154.005 156.96 140.775 167.91 120.045 167.91C99.3149 167.91 86.0849 156.96 86.0849 156.96L87.9949 130.29L66.2449 128.35L62.5249 98.31L98.5749 110.79L95.7749 148.18C102.475 149.88 109.525 151.57 120.055 151.57C130.585 151.57 137.625 149.88 144.325 148.18L141.525 110.79L177.575 98.31L173.855 128.35H173.845Z"/></svg>',
|
|
6687
8812
|
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
8813
|
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
8814
|
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="
|
|
8815
|
+
netlify: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.49 19.04h-.23L5.13 17.9v-.23l1.73-1.71h1.2l.15.15v1.2L6.5 19.04ZM5.13 6.31V6.1l1.13-1.13h.23L8.2 6.68v1.2l-.15.15h-1.2L5.13 6.31Zm9.96 9.09h-1.65l-.14-.13v-3.83c0-.68-.27-1.2-1.1-1.23-.42 0-.9 0-1.43.02l-.07.08v4.96l-.14.14H8.9l-.13-.14V8.73l.13-.14h3.7a2.6 2.6 0 0 1 2.61 2.6v4.08l-.13.14Zm-8.37-2.44H.14L0 12.82v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14Zm17.14 0h-6.58l-.14-.14v-1.64l.14-.14h6.58l.14.14v1.64l-.14.14ZM11.05 6.55V1.64l.14-.14h1.65l.14.14v4.9l-.14.14h-1.65l-.14-.13Zm0 15.81v-4.9l.14-.14h1.65l.14.13v4.91l-.14.14h-1.65l-.14-.14Z"/></svg>',
|
|
6691
8816
|
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="
|
|
6693
|
-
posthog: '<svg viewBox="0 0
|
|
6694
|
-
playwright: '<svg viewBox="0 0
|
|
8817
|
+
sentry: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z"/></svg>',
|
|
8818
|
+
posthog: '<svg viewBox="0 0 156 90" aria-hidden="true"><path fill="#f9bd2b" d="M0 68.33c0-2.9 3.505-4.352 5.556-2.302l14.916 14.917c2.05 2.05.598 5.555-2.301 5.555H3.254A3.254 3.254 0 0 1 0 83.246zm0-15.712c0 .863.343 1.69.953 2.301l30.628 30.628c.61.61 1.438.953 2.301.953h16.823c2.9 0 4.351-3.505 2.301-5.555L5.556 33.494C3.506 31.444 0 32.896 0 35.795zm0-32.534c0 .863.343 1.69.953 2.3l63.162 63.163c.61.61 1.439.953 2.302.953h16.822c2.9 0 4.352-3.505 2.302-5.555L5.556.96C3.506-1.09 0 .362 0 3.26zm32.534 0c0 .863.343 1.69.954 2.3l58.56 58.56c2.05 2.05 5.555.599 5.555-2.3V61.82c0-.863-.343-1.691-.953-2.301L38.09.96c-2.05-2.05-5.556-.598-5.556 2.3zM70.624.96c-2.05-2.05-5.555-.598-5.555 2.3v16.824c0 .863.343 1.69.953 2.3L92.047 48.41c2.05 2.05 5.556.598 5.556-2.3V29.285c0-.863-.343-1.69-.953-2.3z"/><path fill="#000000" d="M138.393 68.729 107.76 38.095c-2.05-2.05-5.555-.598-5.555 2.302v42.849a3.254 3.254 0 0 0 3.254 3.254h47.451a3.254 3.254 0 0 0 3.255-3.254v-3.903c0-1.797-1.463-3.232-3.246-3.464a25.14 25.14 0 0 1-14.526-7.15m-20.572 7.36a5.207 5.207 0 0 1-5.205-5.205 5.207 5.207 0 0 1 5.205-5.206 5.21 5.21 0 0 1 5.206 5.206 5.207 5.207 0 0 1-5.206 5.205"/><path fill="#1d4aff" d="m5.28 65.78.276.248 14.916 14.916c1.96 1.961.718 5.254-1.932 5.536l-.37.02H3.255a3.255 3.255 0 0 1-3.232-2.875L0 83.245V68.33c0-2.773 3.207-4.222 5.28-2.549m0-32.533.276.247 26.978 26.98V86.5L.954 54.919a3.26 3.26 0 0 1-.926-1.873L0 52.618V35.796c0-2.774 3.207-4.223 5.28-2.55M0 3.26C0 .488 3.207-.961 5.28.712l.276.248 26.978 26.978v26.028L.954 22.385a3.25 3.25 0 0 1-.926-1.874L0 20.084z"/><path fill="#f54e00" d="m32.534 27.939 31.581 31.58c.51.51.832 1.17.925 1.874l.029.428v24.68L33.488 54.919a3.26 3.26 0 0 1-.925-1.873l-.029-.428zm0 32.533 20.472 20.472c1.961 1.961.718 5.254-1.931 5.536l-.37.02h-18.17zm5.28-59.76.276.248 26.025 26.025c.51.509.832 1.168.925 1.874l.029.427v24.68L33.488 22.385a3.26 3.26 0 0 1-.925-1.874l-.029-.427V3.26c0-2.773 3.208-4.222 5.28-2.549"/></svg>',
|
|
8819
|
+
playwright: '<svg viewBox="0 0 400 400" aria-hidden="true"><path fill="#2d4552" d="M136.444 221.556C123.558 225.213 115.104 231.625 109.535 238.032C114.869 233.364 122.014 229.08 131.652 226.348C141.51 223.554 149.92 223.574 156.869 224.915V219.481C150.941 218.939 144.145 219.371 136.444 221.556ZM108.946 175.876L61.0895 188.484C61.0895 188.484 61.9617 189.716 63.5767 191.36L104.153 180.668C104.153 180.668 103.578 188.077 98.5847 194.705C108.03 187.559 108.946 175.876 108.946 175.876ZM149.005 288.347C81.6582 306.486 46.0272 228.438 35.2396 187.928C30.2556 169.229 28.0799 155.067 27.5 145.928C27.4377 144.979 27.4665 144.179 27.5336 143.446C24.04 143.657 22.3674 145.473 22.7077 150.721C23.2876 159.855 25.4633 174.016 30.4473 192.721C41.2301 233.225 76.8659 311.273 144.213 293.134C158.872 289.185 169.885 281.992 178.152 272.81C170.532 279.692 160.995 285.112 149.005 288.347ZM161.661 128.11V132.903H188.077C187.535 131.206 186.989 129.677 186.447 128.11H161.661Z"/><path fill="#2d4552" d="M193.981 167.584C205.861 170.958 212.144 179.287 215.465 186.658L228.711 190.42C228.711 190.42 226.904 164.623 203.57 157.995C181.741 151.793 168.308 170.124 166.674 172.496C173.024 167.972 182.297 164.268 193.981 167.584ZM299.422 186.777C277.573 180.547 264.145 198.916 262.535 201.255C268.89 196.736 278.158 193.031 289.837 196.362C301.698 199.741 307.976 208.06 311.307 215.436L324.572 219.212C324.572 219.212 322.736 193.41 299.422 186.777ZM286.262 254.795L176.072 223.99C176.072 223.99 177.265 230.038 181.842 237.869L274.617 263.805C282.255 259.386 286.262 254.795 286.262 254.795ZM209.867 321.102C122.618 297.71 133.166 186.543 147.284 133.865C153.097 112.156 159.073 96.0203 164.029 85.204C161.072 84.5953 158.623 86.1529 156.203 91.0746C150.941 101.747 144.212 119.124 137.7 143.45C123.586 196.127 113.038 307.29 200.283 330.682C241.406 341.699 273.442 324.955 297.323 298.659C274.655 319.19 245.714 330.701 209.867 321.102Z"/><path fill="#e2574c" d="M161.661 262.296V239.863L99.3324 257.537C99.3324 257.537 103.938 230.777 136.444 221.556C146.302 218.762 154.713 218.781 161.661 220.123V128.11H192.869C189.471 117.61 186.184 109.526 183.423 103.909C178.856 94.612 174.174 100.775 163.545 109.665C156.059 115.919 137.139 129.261 108.668 136.933C80.1966 144.61 57.179 142.574 47.5752 140.911C33.9601 138.562 26.8387 135.572 27.5049 145.928C28.0847 155.062 30.2605 169.224 35.2445 187.928C46.0272 228.433 81.663 306.481 149.01 288.342C166.602 283.602 179.019 274.233 187.626 262.291H161.661V262.296ZM61.0848 188.484L108.946 175.876C108.946 175.876 107.551 194.288 89.6087 199.018C71.6614 203.743 61.0848 188.484 61.0848 188.484Z"/><path fill="#2ead33" d="M341.786 129.174C329.345 131.355 299.498 134.072 262.612 124.185C225.716 114.304 201.236 97.0224 191.537 88.8994C177.788 77.3834 171.74 69.3802 165.788 81.4857C160.526 92.163 153.797 109.54 147.284 133.866C133.171 186.543 122.623 297.706 209.867 321.098C297.093 344.47 343.53 242.92 357.644 190.238C364.157 165.917 367.013 147.5 367.799 135.625C368.695 122.173 359.455 126.078 341.786 129.174ZM166.497 172.756C166.497 172.756 180.246 151.372 203.565 158C226.899 164.628 228.706 190.425 228.706 190.425L166.497 172.756ZM223.42 268.713C182.403 256.698 176.077 223.99 176.077 223.99L286.262 254.796C286.262 254.791 264.021 280.578 223.42 268.713ZM262.377 201.495C262.377 201.495 276.107 180.126 299.422 186.773C322.736 193.411 324.572 219.208 324.572 219.208L262.377 201.495Z"/><path fill="#d65348" d="M139.88 246.04L99.3324 257.532C99.3324 257.532 103.737 232.44 133.607 222.496L110.647 136.33L108.663 136.933C80.1918 144.611 57.1742 142.574 47.5704 140.911C33.9554 138.563 26.834 135.572 27.5001 145.929C28.08 155.063 30.2557 169.224 35.2397 187.929C46.0225 228.433 81.6583 306.481 149.005 288.342L150.989 287.719L139.88 246.04ZM61.0848 188.485L108.946 175.876C108.946 175.876 107.551 194.288 89.6087 199.018C71.6615 203.743 61.0848 188.485 61.0848 188.485Z"/><path fill="#1d8d22" d="M225.27 269.163L223.415 268.712C182.398 256.698 176.072 223.99 176.072 223.99L232.89 239.872L262.971 124.281L262.607 124.185C225.711 114.304 201.232 97.0224 191.532 88.8994C177.783 77.3834 171.735 69.3802 165.783 81.4857C160.526 92.163 153.797 109.54 147.284 133.866C133.171 186.543 122.623 297.706 209.867 321.097L211.655 321.5L225.27 269.163ZM166.497 172.756C166.497 172.756 180.246 151.372 203.565 158C226.899 164.628 228.706 190.425 228.706 190.425L166.497 172.756Z"/></svg>',
|
|
6695
8820
|
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
8821
|
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
8822
|
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
8823
|
"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
8824
|
"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
8825
|
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="
|
|
6702
|
-
svelte: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="
|
|
6703
|
-
angular: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2
|
|
6704
|
-
nodejs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2.
|
|
8826
|
+
vue: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#42b883" d="M24 1.61H14.06L12 5.16 9.94 1.61H0l12 20.78L24 1.61Z"/><path fill="#35495e" d="M12 14.08 5.16 2.23h4.43L12 6.41l2.41-4.18h4.43L12 14.08Z"/></svg>',
|
|
8827
|
+
svelte: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#ff3e00" d="M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.187 1.099l.208.063-.02.208a1.173 1.173 0 0 0 .21.766 1.297 1.297 0 0 0 1.39.514 1.22 1.22 0 0 0 .54-.31l3.927-3.986a.927.927 0 0 0 .203-.42.908.908 0 0 0-.154-.7 1.01 1.01 0 0 0-1.08-.4.946.946 0 0 0-.42.242l-1.5 1.522a4.15 4.15 0 0 1-1.831 1.045 4.453 4.453 0 0 1-4.767-1.767 4.108 4.108 0 0 1-.702-3.107 3.855 3.855 0 0 1 1.066-2.018l3.927-3.986A4.205 4.205 0 0 1 10.65 3.34a4.442 4.442 0 0 1 4.766 1.767 4.109 4.109 0 0 1 .702 3.108 3.943 3.943 0 0 1-.133.521l-.106.321-.286-.21a7.206 7.206 0 0 0-2.188-1.098l-.208-.063.02-.208a1.18 1.18 0 0 0-.21-.767 1.297 1.297 0 0 0-1.39-.514 1.229 1.229 0 0 0-.54.31L7.15 10.493a.929.929 0 0 0-.203.42.909.909 0 0 0 .154.7 1.01 1.01 0 0 0 1.08.4.943.943 0 0 0 .42-.242l1.5-1.522a4.144 4.144 0 0 1 1.831-1.045 4.443 4.443 0 0 1 4.766 1.767 4.109 4.109 0 0 1 .702 3.107 3.857 3.857 0 0 1-1.066 2.018l-3.927 3.986a4.193 4.193 0 0 1-2.053 1.043Z"/></svg>',
|
|
8828
|
+
angular: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#dd0031" d="M12 2 3.5 5.1l1.3 11.2L12 22l7.2-5.7 1.3-11.2L12 2Z"/><path fill="#c3002f" d="M12 2v20l7.2-5.7 1.3-11.2L12 2Z"/><path fill="#fff" d="M12 5.6 6.7 17.6h2l1.1-2.7h4.4l1.1 2.7h2L12 5.6Zm0 3.8 1.5 3.7h-3L12 9.4Z"/></svg>',
|
|
8829
|
+
nodejs: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#5fa04e" d="M12 2.15 20.7 7.1v9.8L12 21.85 3.3 16.9V7.1L12 2.15Z"/><path fill="#ffffff" d="M8.1 9.15h2.15v4.55c0 1.35-.75 2.15-2.2 2.15-.78 0-1.37-.18-1.85-.48l.57-1.67c.28.17.6.28.95.28.27 0 .38-.15.38-.48V9.15Zm4.05 4.05c.42.5 1.02.82 1.72.82.5 0 .82-.18.82-.52 0-.38-.32-.48-1.08-.7l-.57-.17c-1.15-.35-1.88-.92-1.88-1.95 0-1.13.92-1.72 2.28-1.72 1.02 0 1.82.32 2.4.98l-1.1 1.28c-.35-.35-.78-.55-1.25-.55-.4 0-.65.15-.65.45 0 .32.28.43.93.63l.57.17c1.32.4 2.05.93 2.05 2.05 0 1.25-1.05 1.88-2.52 1.88-1.32 0-2.35-.5-2.95-1.28l1.23-1.37Z"/></svg>',
|
|
6705
8830
|
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
8831
|
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
8832
|
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
8833
|
"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"
|
|
8834
|
+
vitest: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13.74024 1.05293a.49504.49504 0 0 0-.1569.02512.49338.49338 0 0 0-.25056.1876L7.59513 9.56159a.4895.4895 0 0 0-.08373.22327.48846.48846 0 0 0 .03163.23629.4893.4893 0 0 0 .13985.19319.4927.4927 0 0 0 .2149.10481l3.70685.78609-.22947 4.58007a.48834.48834 0 0 0 .08466.30017.49205.49205 0 0 0 .24931.18854c.10157.03398.21174.03444.3135.00064a.49387.49387 0 0 0 .25056-.18761l5.73735-8.29594a.4884.4884 0 0 0 .08404-.22327c.009-.08015-.0016-.16137-.03163-.23629a.48835.48835 0 0 0-.13985-.19319.49318.49318 0 0 0-.2149-.1048l-3.70686-.7861.22947-4.58008a.48802.48802 0 0 0-.08466-.30017.4913.4913 0 0 0-.24931-.18853.49439.49439 0 0 0-.1566-.02574zM1.15697 9.78795c-.30647.0012-.60009.12378-.81679.34048a1.16107 1.16107 0 0 0-.34017.81648 1.162 1.162 0 0 0 .33366.81957l10.84241 10.8421a1.15762 1.15762 0 0 0 .37677.25211 1.1583 1.1583 0 0 0 .44467.08838c.00084 0 .0016-.00031.0025-.00031.00073 0 .0014.00031.0022.00031a1.15827 1.15827 0 0 0 .44467-.08838 1.15731 1.15731 0 0 0 .37677-.2521l10.84236-10.8421a1.16272 1.16272 0 0 0 .33397-.81958c-.0013-.30647-.12376-.59976-.34048-.81648a1.1616 1.1616 0 0 0-.81679-.34048 1.16114 1.16114 0 0 0-.81926.33366l-5.4012 5.4009c-.0078.0074-.01718.01255-.02482.02015L12 20.14011l-4.59776-4.59745c-.0074-.0074-.01659-.01238-.02419-.01954l-5.4015-5.40151a1.162 1.162 0 0 0-.81958-.33366Z"/></svg>',
|
|
6710
8835
|
"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
|
|
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>'
|
|
8836
|
+
"bot-protection": '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#f38020" d="M12 2.4 20 5.5v6.2c0 4.75-3.12 8.1-8 10.1-4.88-2-8-5.35-8-10.1V5.5l8-3.1Z"/><path fill="#fff7df" d="M12 5.9 16.9 8v3.72c0 2.64-1.8 4.64-4.9 6.05-3.1-1.41-4.9-3.41-4.9-6.05V8L12 5.9Z"/><path fill="#f38020" d="M12 8.2a3.15 3.15 0 0 1 3.15 3.15A3.15 3.15 0 0 1 12 14.5a3.15 3.15 0 0 1-3.15-3.15A3.15 3.15 0 0 1 12 8.2Zm0 1.65a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"/></svg>',
|
|
8837
|
+
"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>',
|
|
8838
|
+
firebase: '<svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#ffa000" d="m3.9 18.7 2.4-15.2c.1-.6.9-.8 1.2-.3l2.6 4.9 1.1-2.1c.2-.4.8-.4 1 0l7.9 12.7-8.1 4.5-8.1-4.5Z"/><path fill="#f57c00" d="m12 23.2 8.1-4.5-2.4-14.8c-.1-.6-.9-.8-1.2-.3L12 12.1v11.1Z"/><path fill="#ffca28" d="m3.9 18.7 6.2-10.6 1.9 3.7-8.1 6.9Z"/><path fill="#fff3c4" d="m12 12.1 4.5-8.5c.3-.5 1.1-.3 1.2.3l2.4 14.8-8.1-6.6Z"/></svg>',
|
|
8839
|
+
cloudflare: '<svg viewBox="0 0 32 32" aria-hidden="true"><rect x="7" y="7" width="21" height="21" rx="1.4" fill="#111827"/><rect x="4" y="4" width="21" height="21" rx="1.4" fill="#f38020"/><path fill="#fff7df" d="M11.1 17.6h9.2c1.4 0 2.5-1 2.5-2.2 0-1.1-.9-2-2.2-2.2-.6-2-2.4-3.4-4.6-3.4-1.9 0-3.6 1.1-4.3 2.7-.2 0-.4-.1-.7-.1-1.7 0-3.1 1.2-3.4 2.7-1.1.2-1.9 1-1.9 1.9 0 .9.8 1.6 1.8 1.6h3.6Z"/><path fill="#f38020" d="M18.1 13.3c1.2.4 2.1 1.4 2.4 2.6h.2c.6 0 1.1-.4 1.1-.9 0-.5-.5-.9-1.1-1h-.7l-.2-.7c-.4-1.5-1.8-2.5-3.5-2.5-.7 0-1.3.2-1.9.5 1.7.2 3 .9 3.7 2Z"/></svg>'
|
|
8840
|
+
};
|
|
8841
|
+
var PROVIDER_NODE_TONES = {
|
|
8842
|
+
supabase: ["#3ecf8e", "rgba(62, 207, 142, 0.34)"],
|
|
8843
|
+
clerk: ["#6c47ff", "rgba(108, 71, 255, 0.34)"],
|
|
8844
|
+
authjs: ["#6c47ff", "rgba(108, 71, 255, 0.3)"],
|
|
8845
|
+
auth0: ["#eb5424", "rgba(235, 84, 36, 0.34)"],
|
|
8846
|
+
"better-auth": ["#111827", "rgba(17, 24, 39, 0.22)"],
|
|
8847
|
+
firebase: ["#ffa000", "rgba(255, 160, 0, 0.34)"],
|
|
8848
|
+
neon: ["#00e599", "rgba(0, 229, 153, 0.34)"],
|
|
8849
|
+
planetscale: ["#111827", "rgba(17, 24, 39, 0.22)"],
|
|
8850
|
+
mongodb: ["#47a248", "rgba(71, 162, 72, 0.34)"],
|
|
8851
|
+
turso: ["#4ff8d2", "rgba(79, 248, 210, 0.3)"],
|
|
8852
|
+
stripe: ["#635bff", "rgba(99, 91, 255, 0.34)"],
|
|
8853
|
+
paddle: ["#fddd35", "rgba(253, 221, 53, 0.34)"],
|
|
8854
|
+
polar: ["#1d4aff", "rgba(29, 74, 255, 0.3)"],
|
|
8855
|
+
"lemon-squeezy": ["#ffc233", "rgba(255, 194, 51, 0.34)"],
|
|
8856
|
+
vercel: ["#111827", "rgba(17, 24, 39, 0.22)"],
|
|
8857
|
+
netlify: ["#00c7b7", "rgba(0, 199, 183, 0.32)"],
|
|
8858
|
+
cloudflare: ["#f38020", "rgba(243, 128, 32, 0.34)"],
|
|
8859
|
+
sentry: ["#a855f7", "rgba(168, 85, 247, 0.34)"],
|
|
8860
|
+
posthog: ["#f54e00", "rgba(245, 78, 0, 0.34)"],
|
|
8861
|
+
playwright: ["#45ba4b", "rgba(69, 186, 75, 0.32)"],
|
|
8862
|
+
vitest: ["#fcc72b", "rgba(252, 199, 43, 0.34)"],
|
|
8863
|
+
figma: ["#ff7262", "rgba(255, 114, 98, 0.38)"],
|
|
8864
|
+
react: ["#61dafb", "rgba(97, 218, 251, 0.38)"],
|
|
8865
|
+
vue: ["#42b883", "rgba(66, 184, 131, 0.34)"],
|
|
8866
|
+
angular: ["#dd0031", "rgba(221, 0, 49, 0.34)"],
|
|
8867
|
+
nodejs: ["#83cd29", "rgba(131, 205, 41, 0.34)"],
|
|
8868
|
+
github: ["#111827", "rgba(17, 24, 39, 0.22)"],
|
|
8869
|
+
gitlab: ["#fc6d26", "rgba(252, 109, 38, 0.34)"]
|
|
6713
8870
|
};
|
|
6714
8871
|
var ALIASES = {
|
|
6715
8872
|
authjs: "authjs",
|
|
@@ -6742,6 +8899,8 @@ var ALIASES = {
|
|
|
6742
8899
|
auth0: "auth0",
|
|
6743
8900
|
"better-auth": "better-auth",
|
|
6744
8901
|
betterauth: "better-auth",
|
|
8902
|
+
firebase: "firebase",
|
|
8903
|
+
firestore: "firebase",
|
|
6745
8904
|
"mongodb atlas": "mongodb",
|
|
6746
8905
|
logrocket: "logrocket",
|
|
6747
8906
|
sentry: "sentry",
|
|
@@ -6777,7 +8936,23 @@ var ALIASES = {
|
|
|
6777
8936
|
routes: "route-map",
|
|
6778
8937
|
ratelimit: "rate-limit"
|
|
6779
8938
|
};
|
|
6780
|
-
var INLINE_ONLY_LOGO_KEYS = /* @__PURE__ */ new Set([
|
|
8939
|
+
var INLINE_ONLY_LOGO_KEYS = /* @__PURE__ */ new Set([
|
|
8940
|
+
"auth0",
|
|
8941
|
+
"authjs",
|
|
8942
|
+
"better-auth",
|
|
8943
|
+
"clerk",
|
|
8944
|
+
"cloudflare",
|
|
8945
|
+
"firebase",
|
|
8946
|
+
"lemon-squeezy",
|
|
8947
|
+
"netlify",
|
|
8948
|
+
"paddle",
|
|
8949
|
+
"playwright",
|
|
8950
|
+
"polar",
|
|
8951
|
+
"posthog",
|
|
8952
|
+
"sentry",
|
|
8953
|
+
"svelte",
|
|
8954
|
+
"vitest"
|
|
8955
|
+
]);
|
|
6781
8956
|
var PROVIDER_ASSET_FILES = {
|
|
6782
8957
|
authjs: "provider-authjs.svg",
|
|
6783
8958
|
logrocket: "provider-logrocket.svg",
|
|
@@ -6797,6 +8972,7 @@ var PROVIDER_ICON_URLS = {
|
|
|
6797
8972
|
supabase: "https://cdn.simpleicons.org/supabase/3ECF8E",
|
|
6798
8973
|
clerk: "https://cdn.simpleicons.org/clerk/6C47FF",
|
|
6799
8974
|
auth0: "https://cdn.simpleicons.org/auth0/EB5424",
|
|
8975
|
+
firebase: "https://cdn.simpleicons.org/firebase/FFCA28",
|
|
6800
8976
|
neon: "https://cdn.simpleicons.org/neon/00E599",
|
|
6801
8977
|
planetscale: "https://cdn.simpleicons.org/planetscale/000000",
|
|
6802
8978
|
mongodb: "https://cdn.simpleicons.org/mongodb/47A248",
|
|
@@ -6833,16 +9009,26 @@ var BRAND_LOGO_KEYS = /* @__PURE__ */ new Set([
|
|
|
6833
9009
|
"better-auth",
|
|
6834
9010
|
"clerk",
|
|
6835
9011
|
"gitlab",
|
|
9012
|
+
"lemon-squeezy",
|
|
6836
9013
|
"logrocket",
|
|
9014
|
+
"netlify",
|
|
6837
9015
|
"auth0",
|
|
6838
9016
|
"paddle",
|
|
6839
9017
|
"playwright",
|
|
6840
9018
|
"polar",
|
|
6841
9019
|
"posthog",
|
|
9020
|
+
"sentry",
|
|
6842
9021
|
"stripe",
|
|
9022
|
+
"svelte",
|
|
6843
9023
|
"vitest"
|
|
6844
9024
|
]);
|
|
9025
|
+
function isUnselectedProviderToken(raw, compact) {
|
|
9026
|
+
return !compact || compact === "notselected" || raw === "not selected" || raw === "none";
|
|
9027
|
+
}
|
|
6845
9028
|
function resolveLogoKey(raw, compact) {
|
|
9029
|
+
if (isUnselectedProviderToken(raw, compact)) {
|
|
9030
|
+
return "";
|
|
9031
|
+
}
|
|
6846
9032
|
if (ALIASES[raw]) {
|
|
6847
9033
|
return ALIASES[raw];
|
|
6848
9034
|
}
|
|
@@ -6880,13 +9066,13 @@ function normalizeProviderKey2(providerOrLabel) {
|
|
|
6880
9066
|
function resolveProviderLogoKey(...candidates) {
|
|
6881
9067
|
for (const candidate of candidates) {
|
|
6882
9068
|
const key = normalizeProviderKey2(candidate);
|
|
6883
|
-
if (key && PROVIDER_LOGOS[key]) {
|
|
9069
|
+
if (key && key !== "notselected" && PROVIDER_LOGOS[key]) {
|
|
6884
9070
|
return key;
|
|
6885
9071
|
}
|
|
6886
9072
|
}
|
|
6887
9073
|
for (const candidate of candidates) {
|
|
6888
9074
|
const key = normalizeProviderKey2(candidate);
|
|
6889
|
-
if (key) {
|
|
9075
|
+
if (key && key !== "notselected") {
|
|
6890
9076
|
return key;
|
|
6891
9077
|
}
|
|
6892
9078
|
}
|
|
@@ -6894,7 +9080,7 @@ function resolveProviderLogoKey(...candidates) {
|
|
|
6894
9080
|
}
|
|
6895
9081
|
function providerLogoClass(providerOrLabel, ...moreCandidates) {
|
|
6896
9082
|
const key = resolveProviderLogoKey(providerOrLabel, ...moreCandidates) || normalizeProviderKey2(providerOrLabel);
|
|
6897
|
-
if (!key) {
|
|
9083
|
+
if (!key || key === "notselected") {
|
|
6898
9084
|
return "";
|
|
6899
9085
|
}
|
|
6900
9086
|
return ` provider-logo--${key}${BRAND_LOGO_KEYS.has(key) ? " provider-logo--brand" : ""}`;
|
|
@@ -6912,16 +9098,14 @@ function providerLogoHtml(providerOrLabel, labelFallback, ...moreCandidates) {
|
|
|
6912
9098
|
if (assetUrl) {
|
|
6913
9099
|
return providerIconImgHtml(assetUrl, key);
|
|
6914
9100
|
}
|
|
9101
|
+
if (svg) {
|
|
9102
|
+
return svg;
|
|
9103
|
+
}
|
|
6915
9104
|
const iconUrl = key && PROVIDER_ICON_URLS[key];
|
|
6916
9105
|
if (iconUrl) {
|
|
6917
9106
|
return providerIconImgHtml(iconUrl, key);
|
|
6918
9107
|
}
|
|
6919
|
-
|
|
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>`;
|
|
9108
|
+
return PROVIDER_LOGO_FALLBACK_SVG;
|
|
6925
9109
|
}
|
|
6926
9110
|
var PROVIDER_BENEFITS = {
|
|
6927
9111
|
supabase: "Best when you want auth, data, and storage in one stack.",
|
|
@@ -6946,6 +9130,7 @@ var PROVIDER_BENEFITS = {
|
|
|
6946
9130
|
sentry: "Best first choice for production errors and traces.",
|
|
6947
9131
|
posthog: "Best for product analytics and funnel verification.",
|
|
6948
9132
|
auth0: "Enterprise identity with rules, MFA, and social login.",
|
|
9133
|
+
firebase: "Good fit when auth, Firestore, and hosting stay in one Google stack.",
|
|
6949
9134
|
"better-auth": "Type-safe auth you own in your codebase.",
|
|
6950
9135
|
playwright: "Best for browser-flow verification and UI regression checks.",
|
|
6951
9136
|
logrocket: "Best when session replay is the missing evidence.",
|
|
@@ -6973,7 +9158,9 @@ function providerLogosPayloadJson() {
|
|
|
6973
9158
|
inlineOnly: [...INLINE_ONLY_LOGO_KEYS],
|
|
6974
9159
|
aliases: ALIASES,
|
|
6975
9160
|
benefits: PROVIDER_BENEFITS,
|
|
6976
|
-
brandKeys: [...BRAND_LOGO_KEYS]
|
|
9161
|
+
brandKeys: [...BRAND_LOGO_KEYS],
|
|
9162
|
+
nodeTones: PROVIDER_NODE_TONES,
|
|
9163
|
+
fallbackSvg: PROVIDER_LOGO_FALLBACK_SVG
|
|
6977
9164
|
}).replace(/</g, "\\u003c");
|
|
6978
9165
|
}
|
|
6979
9166
|
|
|
@@ -6996,19 +9183,59 @@ var CATEGORY_META = [
|
|
|
6996
9183
|
{ key: "landing", label: "Landing / Onboarding" },
|
|
6997
9184
|
{ key: "errorHandling", label: "Error Handling" }
|
|
6998
9185
|
];
|
|
9186
|
+
var DEFAULT_FREE_UNLOCKED_MAP_KEYS = [
|
|
9187
|
+
"appFlow",
|
|
9188
|
+
"frontend",
|
|
9189
|
+
"backend",
|
|
9190
|
+
"auth",
|
|
9191
|
+
"database",
|
|
9192
|
+
"payments"
|
|
9193
|
+
];
|
|
9194
|
+
var DEFAULT_FREE_UNLOCKED_MAP_KEY_SET = new Set(DEFAULT_FREE_UNLOCKED_MAP_KEYS);
|
|
9195
|
+
function isMapCategoryUnlocked(artifact, categoryKey) {
|
|
9196
|
+
if (artifact.plan === "pro" || artifact.usage?.plan === "pro") {
|
|
9197
|
+
return true;
|
|
9198
|
+
}
|
|
9199
|
+
if (!artifact.plan && !artifact.usage) {
|
|
9200
|
+
return true;
|
|
9201
|
+
}
|
|
9202
|
+
const keys = Array.isArray(artifact.usage?.unlockedMapCategoryKeys) && artifact.usage.unlockedMapCategoryKeys.length > 0 ? artifact.usage.unlockedMapCategoryKeys : DEFAULT_FREE_UNLOCKED_MAP_KEYS;
|
|
9203
|
+
return keys.includes(categoryKey) && DEFAULT_FREE_UNLOCKED_MAP_KEY_SET.has(categoryKey);
|
|
9204
|
+
}
|
|
6999
9205
|
function gapCountForArea(artifact, areaKey) {
|
|
7000
9206
|
return artifact.gaps.filter((g2) => g2.primaryMapCategory === areaKey).length;
|
|
7001
9207
|
}
|
|
7002
9208
|
function defaultAreaKey(artifact) {
|
|
7003
|
-
const fromGap = artifact.gaps
|
|
9209
|
+
const fromGap = artifact.gaps.find((gap) => isMapCategoryUnlocked(artifact, gap.primaryMapCategory))?.primaryMapCategory;
|
|
7004
9210
|
if (fromGap) {
|
|
7005
9211
|
return fromGap;
|
|
7006
9212
|
}
|
|
7007
9213
|
const areas = artifact.missionGraph.areas ?? [];
|
|
7008
|
-
|
|
9214
|
+
const fromArea = areas.find((area) => isMapCategoryUnlocked(artifact, area.key))?.key;
|
|
9215
|
+
if (fromArea) {
|
|
9216
|
+
return fromArea;
|
|
9217
|
+
}
|
|
9218
|
+
if (isMapCategoryUnlocked(artifact, "frontend")) {
|
|
7009
9219
|
return "frontend";
|
|
7010
9220
|
}
|
|
7011
|
-
return
|
|
9221
|
+
return CATEGORY_META.find((meta) => isMapCategoryUnlocked(artifact, meta.key))?.key ?? "frontend";
|
|
9222
|
+
}
|
|
9223
|
+
function providerLabelForArea(artifact, areaKey, mission, selectedOverride) {
|
|
9224
|
+
if (selectedOverride) {
|
|
9225
|
+
const token = normalizeProviderKey2(selectedOverride);
|
|
9226
|
+
const options = artifact.providerOptions?.[areaKey] ?? [];
|
|
9227
|
+
const match = options.find(
|
|
9228
|
+
(option) => normalizeProviderKey2(option.provider) === token || normalizeProviderKey2(option.label) === token
|
|
9229
|
+
);
|
|
9230
|
+
if (match?.label) {
|
|
9231
|
+
return match.label;
|
|
9232
|
+
}
|
|
9233
|
+
if (mission && missionMatchesProvider(mission, selectedOverride)) {
|
|
9234
|
+
return mission.providerLabel ?? selectedOverride;
|
|
9235
|
+
}
|
|
9236
|
+
return selectedOverride;
|
|
9237
|
+
}
|
|
9238
|
+
return mission?.providerLabel ?? "Not selected";
|
|
7012
9239
|
}
|
|
7013
9240
|
function buildAccountStripHtml(artifact) {
|
|
7014
9241
|
if (!artifact.accountEmail) {
|
|
@@ -7036,28 +9263,35 @@ function buildNodeHtml(artifact, meta, selectedAreaKey) {
|
|
|
7036
9263
|
const area = (artifact.missionGraph.areas ?? []).find((a) => a.key === meta.key);
|
|
7037
9264
|
const selectedOverride = artifact.selectedProviders?.[meta.key] ?? "";
|
|
7038
9265
|
const mission = preferredMissionForArea(area, selectedOverride);
|
|
7039
|
-
const
|
|
7040
|
-
const
|
|
9266
|
+
const providerLabel3 = providerLabelForArea(artifact, meta.key, mission, selectedOverride);
|
|
9267
|
+
const logoProvider = selectedOverride || mission?.provider || mission?.providerLabel || "";
|
|
7041
9268
|
const readiness = mission?.readinessPercent ?? area?.readinessPercent ?? 0;
|
|
7042
9269
|
const openChecks = openChecksForMission(mission);
|
|
9270
|
+
const repoOpenChecks = mission?.checks.filter(
|
|
9271
|
+
(check) => check.evidenceClass === "missing-repo-fix" || check.status === "missing" || check.status === "failed"
|
|
9272
|
+
).length ?? 0;
|
|
9273
|
+
const providerOpenChecks = mission?.checks.filter(
|
|
9274
|
+
(check) => check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.evidenceClass === "mcp-verifier" || check.status === "needs-connection" || check.status === "unknown"
|
|
9275
|
+
).length ?? 0;
|
|
7043
9276
|
const modelGaps = gapCountForArea(artifact, meta.key);
|
|
9277
|
+
const unlocked = isMapCategoryUnlocked(artifact, meta.key);
|
|
7044
9278
|
const stateClass = modelGaps > 0 ? " studio-node--critical" : openChecks > 0 ? " studio-node--warning" : area ? " studio-node--in-project" : "";
|
|
7045
9279
|
const selectedClass = selectedAreaKey === meta.key ? " studio-node--selected" : "";
|
|
7046
|
-
const
|
|
7047
|
-
const
|
|
7048
|
-
const
|
|
7049
|
-
|
|
7050
|
-
providerLabel3,
|
|
7051
|
-
mission?.key,
|
|
7052
|
-
String(selectedProvider)
|
|
7053
|
-
);
|
|
9280
|
+
const lockedClass = unlocked ? "" : " studio-node--locked";
|
|
9281
|
+
const metaText = modelGaps > 0 ? `${modelGaps} stack fix${modelGaps === 1 ? "" : "es"}` : repoOpenChecks > 0 ? `${repoOpenChecks} stack fix${repoOpenChecks === 1 ? "" : "es"}` : providerOpenChecks > 0 ? "Live check" : `${readiness}% health`;
|
|
9282
|
+
const logoClass = providerLogoClass(logoProvider, mission?.key, mission?.providerLabel);
|
|
9283
|
+
const logoInner = providerLogoHtml(logoProvider, providerLabel3, mission?.key, mission?.providerLabel);
|
|
7054
9284
|
const gapBadge = modelGaps > 0 ? `<span class="studio-node__raven-badge" role="presentation">Gap ${modelGaps}</span>` : "";
|
|
7055
|
-
|
|
9285
|
+
const lockBadge = unlocked ? "" : '<span class="studio-node__lock">Pro</span>';
|
|
9286
|
+
const lockedAttrs = unlocked ? "" : ' data-locked-map="1" title="Pro only - this section is locked on the Free plan"';
|
|
9287
|
+
const lockedAria = unlocked ? "" : ", Pro only. Upgrade to unlock";
|
|
9288
|
+
return `<button type="button" class="studio-node studio-node--${escapeHtml(meta.key)} provider-logo${logoClass}${stateClass}${selectedClass}${lockedClass}" data-area-key="${escapeHtml(meta.key)}"${lockedAttrs} aria-label="${escapeHtml(meta.label)}, ${escapeHtml(providerLabel3)}, ${escapeHtml(metaText)}${lockedAria}">
|
|
7056
9289
|
<span class="studio-node__logo provider-logo${logoClass}" aria-hidden="true">${logoInner}</span>
|
|
7057
9290
|
<span class="studio-node__title">${escapeHtml(meta.label)}</span>
|
|
7058
9291
|
<span class="studio-node__provider">${escapeHtml(providerLabel3)}</span>
|
|
7059
9292
|
<span class="studio-node__meta">${escapeHtml(metaText)}</span>
|
|
7060
9293
|
${gapBadge}
|
|
9294
|
+
${lockBadge}
|
|
7061
9295
|
</button>`;
|
|
7062
9296
|
}
|
|
7063
9297
|
function generateReportHtml(artifact) {
|
|
@@ -7067,7 +9301,6 @@ function generateReportHtml(artifact) {
|
|
|
7067
9301
|
const nodesHtml = CATEGORY_META.map((meta) => buildNodeHtml(hydrated, meta, defaultKey)).join("\n");
|
|
7068
9302
|
const accountStrip = buildAccountStripHtml(hydrated);
|
|
7069
9303
|
const logosJson = providerLogosPayloadJson();
|
|
7070
|
-
const scannedLabel = escapeHtml(hydrated.scannedAt);
|
|
7071
9304
|
return `<!DOCTYPE html>
|
|
7072
9305
|
<html lang="en" class="cli-mission-report" data-surface="panel" data-skin="editorial">
|
|
7073
9306
|
<head>
|
|
@@ -7087,8 +9320,10 @@ function generateReportHtml(artifact) {
|
|
|
7087
9320
|
<div class="studio-shell" aria-label="VibeRaven Studio cockpit">
|
|
7088
9321
|
<header class="studio-top-rail" aria-label="Studio status">
|
|
7089
9322
|
<img class="studio-top-rail__logo" src="report/assets/viberaven-logo.png" alt="" width="32" height="32" />
|
|
7090
|
-
<
|
|
7091
|
-
|
|
9323
|
+
<div class="studio-top-rail__brand">
|
|
9324
|
+
<span class="studio-top-rail__wordmark">VIBERAVEN</span>
|
|
9325
|
+
<span class="studio-top-rail__label">MISSION MAP</span>
|
|
9326
|
+
</div>
|
|
7092
9327
|
</header>
|
|
7093
9328
|
<div class="studio-workspace">
|
|
7094
9329
|
<main class="studio-map-canvas" aria-label="Interactive full-stack system map">
|
|
@@ -7097,7 +9332,7 @@ function generateReportHtml(artifact) {
|
|
|
7097
9332
|
<div class="studio-connector-layer" aria-hidden="true"></div>
|
|
7098
9333
|
<div class="studio-core-group">
|
|
7099
9334
|
<div class="studio-core-node" aria-label="Production core score">
|
|
7100
|
-
<
|
|
9335
|
+
<span>VIBERAVEN</span>
|
|
7101
9336
|
<strong>${hydrated.productionCorePercent}%</strong>
|
|
7102
9337
|
<small>Production core</small>
|
|
7103
9338
|
</div>
|
|
@@ -7149,16 +9384,22 @@ var REPORT_ASSET_FILES = [
|
|
|
7149
9384
|
var INLINE_SECRET_PATTERNS = [
|
|
7150
9385
|
/\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
9386
|
/\b(whsec_[A-Za-z0-9]{12,}|rk_(?:live|test)_[A-Za-z0-9]{12,})\b/g,
|
|
7152
|
-
/\
|
|
9387
|
+
/\bghp_[A-Za-z0-9]{36,}\b/g,
|
|
9388
|
+
/\bgithub_pat_[A-Za-z0-9_]{50,}\b/g,
|
|
9389
|
+
/\bxox[bp]-[A-Za-z0-9-]{20,}\b/g,
|
|
9390
|
+
/\bxapp-[A-Za-z0-9-]{20,}\b/g,
|
|
9391
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
9392
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
|
|
7153
9393
|
];
|
|
7154
|
-
var SENSITIVE_ENV_KEYS =
|
|
9394
|
+
var SENSITIVE_ENV_KEYS = /(?:^|_)(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)(?:$|_)/i;
|
|
7155
9395
|
function redactString(value) {
|
|
7156
9396
|
let out = value;
|
|
7157
9397
|
for (const pattern of INLINE_SECRET_PATTERNS) {
|
|
7158
|
-
out = out.replace(pattern, "[REDACTED_SECRET]");
|
|
9398
|
+
out = out.replace(pattern, pattern.source.includes("PRIVATE KEY") ? "[REDACTED_PRIVATE_KEY]" : "[REDACTED_SECRET]");
|
|
7159
9399
|
}
|
|
9400
|
+
out = out.replace(/\bAuthorization\s*:\s*([A-Za-z][A-Za-z0-9._-]*)\s+[^\s;,]+/gi, "Authorization: $1 [REDACTED]");
|
|
7160
9401
|
return out.replace(
|
|
7161
|
-
/\b([A-Za-z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD|PRIVATE_KEY)[A-Za-z0-9_]*)\s*=\s*["']?[^"'\s;,]+["']?/gi,
|
|
9402
|
+
/\b([A-Za-z0-9_]*(?:ACCESS_TOKEN|AUTHORIZATION|API_KEY|SECRET|SECRET_KEY|SERVICE_ROLE_KEY|TOKEN|PASSWORD|PRIVATE_KEY|CREDENTIALS?)[A-Za-z0-9_]*)\s*=\s*["']?[^"'\s;,]+["']?/gi,
|
|
7162
9403
|
"$1=[REDACTED]"
|
|
7163
9404
|
);
|
|
7164
9405
|
}
|
|
@@ -7397,6 +9638,15 @@ function gapTagColor(modelGaps, open) {
|
|
|
7397
9638
|
}
|
|
7398
9639
|
return import_picocolors.default.dim;
|
|
7399
9640
|
}
|
|
9641
|
+
function manualActionCheckCount(artifact) {
|
|
9642
|
+
return (artifact.missionGraph.areas ?? []).reduce((areaTotal, area) => {
|
|
9643
|
+
return areaTotal + area.providerMissions.reduce((missionTotal, mission) => {
|
|
9644
|
+
return missionTotal + mission.checks.filter(
|
|
9645
|
+
(check) => check.evidenceClass === "manual-dashboard" || check.evidenceClass === "mcp-verifier" || check.evidenceSource === "provider" || check.evidenceSource === "mcp" || check.status === "needs-connection" || check.status === "unknown"
|
|
9646
|
+
).length;
|
|
9647
|
+
}, 0);
|
|
9648
|
+
}, 0);
|
|
9649
|
+
}
|
|
7400
9650
|
function printScanSummary(artifact, paths) {
|
|
7401
9651
|
const pct = artifact.productionCorePercent;
|
|
7402
9652
|
const gapCount = artifact.gaps.length;
|
|
@@ -7432,6 +9682,11 @@ function printScanSummary(artifact, paths) {
|
|
|
7432
9682
|
console.log("");
|
|
7433
9683
|
console.log(import_picocolors.default.dim("Press Enter in `viberaven` menu to rescan \xB7 `viberaven prompt` for top gap"));
|
|
7434
9684
|
console.log(import_picocolors.default.dim("Agents: read .viberaven/agent-summary.md"));
|
|
9685
|
+
console.log(formatAgentStatus(READY, `Scan complete. Read ${paths.summaryPath} before changing code.`));
|
|
9686
|
+
const manualCount = manualActionCheckCount(artifact);
|
|
9687
|
+
if (manualCount > 0) {
|
|
9688
|
+
console.log(formatAgentStatus(MANUAL_ACTION_REQUIRED, `${manualCount} provider dashboard or read-only MCP check${manualCount === 1 ? "" : "s"} require user/provider verification. Do not claim these as repo-code fixes.`));
|
|
9689
|
+
}
|
|
7435
9690
|
console.log("");
|
|
7436
9691
|
}
|
|
7437
9692
|
|
|
@@ -7972,7 +10227,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
7972
10227
|
var import_picocolors3 = __toESM(require_picocolors());
|
|
7973
10228
|
|
|
7974
10229
|
// src/version.ts
|
|
7975
|
-
var VERSION = "0.1.0-beta.
|
|
10230
|
+
var VERSION = "0.1.0-beta.3";
|
|
7976
10231
|
|
|
7977
10232
|
// src/tui/runInteractive.ts
|
|
7978
10233
|
async function formatStatusLine() {
|
|
@@ -8164,6 +10419,18 @@ async function runInteractiveSession(startDir = process.cwd()) {
|
|
|
8164
10419
|
}
|
|
8165
10420
|
|
|
8166
10421
|
// src/cli.ts
|
|
10422
|
+
function buildHelpText() {
|
|
10423
|
+
return `First-time terminal flow:
|
|
10424
|
+
|
|
10425
|
+
npx -y @viberaven/cli@beta login
|
|
10426
|
+
npx -y @viberaven/cli@beta status
|
|
10427
|
+
npx -y @viberaven/cli@beta scan --open
|
|
10428
|
+
|
|
10429
|
+
Quota:
|
|
10430
|
+
|
|
10431
|
+
Free: 2 lifetime scans
|
|
10432
|
+
Pro: 50 scans per month`;
|
|
10433
|
+
}
|
|
8167
10434
|
function printHelp() {
|
|
8168
10435
|
console.log(`viberaven ${VERSION} \u2014 launch readiness for AI-built apps
|
|
8169
10436
|
|
|
@@ -8219,6 +10486,7 @@ Security:
|
|
|
8219
10486
|
CLI scans use VibeRaven login \u2014 not OPENAI_API_KEY. See packages/cli/SECURITY.md.
|
|
8220
10487
|
|
|
8221
10488
|
`);
|
|
10489
|
+
console.log(buildHelpText());
|
|
8222
10490
|
}
|
|
8223
10491
|
function parseArgs(argv) {
|
|
8224
10492
|
const flags = {};
|
|
@@ -8230,6 +10498,10 @@ function parseArgs(argv) {
|
|
|
8230
10498
|
flags.help = true;
|
|
8231
10499
|
continue;
|
|
8232
10500
|
}
|
|
10501
|
+
if (arg === "--version" || arg === "-v") {
|
|
10502
|
+
flags.version = true;
|
|
10503
|
+
continue;
|
|
10504
|
+
}
|
|
8233
10505
|
if (arg.startsWith("--")) {
|
|
8234
10506
|
const key = arg.slice(2);
|
|
8235
10507
|
const next = argv[i + 1];
|
|
@@ -8249,6 +10521,9 @@ function parseArgs(argv) {
|
|
|
8249
10521
|
}
|
|
8250
10522
|
return { command, flags, positional };
|
|
8251
10523
|
}
|
|
10524
|
+
function formatScanJsonStdout(artifact) {
|
|
10525
|
+
return JSON.stringify(sanitizeArtifactForDisk(artifact), null, 2);
|
|
10526
|
+
}
|
|
8252
10527
|
async function cmdLogin(flags) {
|
|
8253
10528
|
const apiBaseUrl = resolveApiBaseUrl(typeof flags["api-url"] === "string" ? flags["api-url"] : void 0);
|
|
8254
10529
|
await runDeviceLogin(apiBaseUrl);
|
|
@@ -8300,13 +10575,17 @@ async function cmdScan(flags, positional) {
|
|
|
8300
10575
|
}
|
|
8301
10576
|
return 2;
|
|
8302
10577
|
}
|
|
8303
|
-
|
|
10578
|
+
if (result.kind === "auth_required" || result.kind === "session_invalid") {
|
|
10579
|
+
console.error(formatAgentStatus(LOGIN_REQUIRED, result.message));
|
|
10580
|
+
return 1;
|
|
10581
|
+
}
|
|
10582
|
+
console.error(formatAgentStatus(ERROR, result.message));
|
|
8304
10583
|
return 1;
|
|
8305
10584
|
}
|
|
8306
10585
|
const artifact = await enrichArtifactWithAccount(result.artifact, apiBaseUrl, accessToken);
|
|
8307
10586
|
const paths = await writeScanArtifacts({ artifact, cwd: workspacePath });
|
|
8308
10587
|
if (flags.json) {
|
|
8309
|
-
console.log(
|
|
10588
|
+
console.log(formatScanJsonStdout(artifact));
|
|
8310
10589
|
return 0;
|
|
8311
10590
|
}
|
|
8312
10591
|
printScanSummary(artifact, paths);
|
|
@@ -8429,6 +10708,10 @@ async function main() {
|
|
|
8429
10708
|
printHelp();
|
|
8430
10709
|
return 0;
|
|
8431
10710
|
}
|
|
10711
|
+
if (flags.version || command === "version") {
|
|
10712
|
+
console.log(VERSION);
|
|
10713
|
+
return 0;
|
|
10714
|
+
}
|
|
8432
10715
|
if (!command) {
|
|
8433
10716
|
await runInteractiveSession();
|
|
8434
10717
|
return 0;
|
|
@@ -8454,23 +10737,26 @@ async function main() {
|
|
|
8454
10737
|
return cmdPrompt(flags, positional);
|
|
8455
10738
|
case "stack":
|
|
8456
10739
|
return cmdStack(positional);
|
|
8457
|
-
case "--version":
|
|
8458
|
-
case "-v":
|
|
8459
|
-
case "version":
|
|
8460
|
-
console.log(VERSION);
|
|
8461
|
-
return 0;
|
|
8462
10740
|
default:
|
|
8463
10741
|
console.error(`Unknown command: ${command}`);
|
|
8464
10742
|
printHelp();
|
|
8465
10743
|
return 1;
|
|
8466
10744
|
}
|
|
8467
10745
|
}
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
}
|
|
8473
|
-
|
|
8474
|
-
|
|
10746
|
+
if (require.main === module) {
|
|
10747
|
+
main().then((code) => {
|
|
10748
|
+
if (code !== 0) {
|
|
10749
|
+
process.exitCode = code;
|
|
10750
|
+
}
|
|
10751
|
+
}).catch((error) => {
|
|
10752
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
10753
|
+
process.exitCode = 1;
|
|
10754
|
+
});
|
|
10755
|
+
}
|
|
10756
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
10757
|
+
0 && (module.exports = {
|
|
10758
|
+
buildHelpText,
|
|
10759
|
+
formatScanJsonStdout,
|
|
10760
|
+
parseArgs
|
|
8475
10761
|
});
|
|
8476
10762
|
//# sourceMappingURL=cli.js.map
|