@tendrilapp/cli 0.1.43 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tendril-mcp.js +15 -1
- package/dist/tendril.js +793 -498
- package/package.json +1 -1
package/dist/tendril.js
CHANGED
|
@@ -1980,8 +1980,8 @@ var init_src = __esm({
|
|
|
1980
1980
|
function variableNameToPath(name) {
|
|
1981
1981
|
return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
|
|
1982
1982
|
}
|
|
1983
|
-
function tokenPathToCssVar(
|
|
1984
|
-
return `--${
|
|
1983
|
+
function tokenPathToCssVar(path58) {
|
|
1984
|
+
return `--${path58.join("-")}`;
|
|
1985
1985
|
}
|
|
1986
1986
|
function toDtcgToken(variable, defaultMode) {
|
|
1987
1987
|
const modes = Object.keys(variable.valuesByMode);
|
|
@@ -2025,11 +2025,11 @@ function toDtcgToken(variable, defaultMode) {
|
|
|
2025
2025
|
}
|
|
2026
2026
|
function mapVariablesToDtcg(variables, defaultMode = "light") {
|
|
2027
2027
|
const entries = variables.map((variable) => {
|
|
2028
|
-
const
|
|
2029
|
-
if (
|
|
2028
|
+
const path58 = variableNameToPath(variable.name);
|
|
2029
|
+
if (path58.length === 0) {
|
|
2030
2030
|
throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
|
|
2031
2031
|
}
|
|
2032
|
-
return { variable, path:
|
|
2032
|
+
return { variable, path: path58 };
|
|
2033
2033
|
});
|
|
2034
2034
|
const groupPrefixes = /* @__PURE__ */ new Set();
|
|
2035
2035
|
for (const e of entries) {
|
|
@@ -2050,21 +2050,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
|
|
|
2050
2050
|
}
|
|
2051
2051
|
const tokens = {};
|
|
2052
2052
|
const flat = [];
|
|
2053
|
-
for (const { variable, path:
|
|
2053
|
+
for (const { variable, path: path58 } of entries) {
|
|
2054
2054
|
const token = toDtcgToken(variable, defaultMode);
|
|
2055
2055
|
let group = tokens;
|
|
2056
|
-
for (const segment of
|
|
2056
|
+
for (const segment of path58.slice(0, -1)) {
|
|
2057
2057
|
const existing = group[segment];
|
|
2058
2058
|
group = existing ?? (group[segment] = {});
|
|
2059
2059
|
}
|
|
2060
|
-
const leaf =
|
|
2060
|
+
const leaf = path58[path58.length - 1];
|
|
2061
2061
|
if (group[leaf] !== void 0) {
|
|
2062
|
-
throw new DtcgMappingError("duplicate-path", `Duplicate token path "${
|
|
2062
|
+
throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path58.join(".")}" (variable ${variable.id})`);
|
|
2063
2063
|
}
|
|
2064
2064
|
group[leaf] = token;
|
|
2065
2065
|
flat.push({
|
|
2066
|
-
path:
|
|
2067
|
-
cssVar: tokenPathToCssVar(
|
|
2066
|
+
path: path58.join("."),
|
|
2067
|
+
cssVar: tokenPathToCssVar(path58),
|
|
2068
2068
|
type: token.$type,
|
|
2069
2069
|
value: token.$value
|
|
2070
2070
|
});
|
|
@@ -2253,9 +2253,9 @@ function boundId(value) {
|
|
|
2253
2253
|
return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
|
|
2254
2254
|
}
|
|
2255
2255
|
function resolveBinding(ctx, id) {
|
|
2256
|
-
const
|
|
2257
|
-
if (
|
|
2258
|
-
return
|
|
2256
|
+
const path58 = ctx.pathById.get(id);
|
|
2257
|
+
if (path58 === void 0) ctx.unresolved.add(id);
|
|
2258
|
+
return path58;
|
|
2259
2259
|
}
|
|
2260
2260
|
function parseVariantProps(name) {
|
|
2261
2261
|
if (!name.includes("=")) return void 0;
|
|
@@ -2290,8 +2290,8 @@ function walk(ctx, raw) {
|
|
|
2290
2290
|
if (!isObject(paint) || paint["visible"] === false) continue;
|
|
2291
2291
|
const id = boundId(paint);
|
|
2292
2292
|
if (id !== void 0) {
|
|
2293
|
-
const
|
|
2294
|
-
if (
|
|
2293
|
+
const path58 = resolveBinding(ctx, id);
|
|
2294
|
+
if (path58 !== void 0) tokens.add(path58);
|
|
2295
2295
|
} else if (typeof paint["color"] === "string") {
|
|
2296
2296
|
ctx.hardcoded.push({ node: name, property, value: paint["color"] });
|
|
2297
2297
|
}
|
|
@@ -2299,8 +2299,8 @@ function walk(ctx, raw) {
|
|
|
2299
2299
|
}
|
|
2300
2300
|
const radiusId = boundId(raw["cornerRadius"]);
|
|
2301
2301
|
if (radiusId !== void 0) {
|
|
2302
|
-
const
|
|
2303
|
-
if (
|
|
2302
|
+
const path58 = resolveBinding(ctx, radiusId);
|
|
2303
|
+
if (path58 !== void 0) tokens.add(path58);
|
|
2304
2304
|
} else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
|
|
2305
2305
|
ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
|
|
2306
2306
|
}
|
|
@@ -2310,10 +2310,10 @@ function walk(ctx, raw) {
|
|
|
2310
2310
|
layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
|
|
2311
2311
|
const gapId = boundId(raw["itemSpacing"]);
|
|
2312
2312
|
if (gapId !== void 0) {
|
|
2313
|
-
const
|
|
2314
|
-
if (
|
|
2315
|
-
layout.gap =
|
|
2316
|
-
tokens.add(
|
|
2313
|
+
const path58 = resolveBinding(ctx, gapId);
|
|
2314
|
+
if (path58 !== void 0) {
|
|
2315
|
+
layout.gap = path58;
|
|
2316
|
+
tokens.add(path58);
|
|
2317
2317
|
}
|
|
2318
2318
|
} else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
|
|
2319
2319
|
ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
|
|
@@ -2322,10 +2322,10 @@ function walk(ctx, raw) {
|
|
|
2322
2322
|
for (const field of PADDING_FIELDS) {
|
|
2323
2323
|
const id = boundId(raw[field]);
|
|
2324
2324
|
if (id !== void 0) {
|
|
2325
|
-
const
|
|
2326
|
-
if (
|
|
2327
|
-
paddingPaths.push(
|
|
2328
|
-
tokens.add(
|
|
2325
|
+
const path58 = resolveBinding(ctx, id);
|
|
2326
|
+
if (path58 !== void 0) {
|
|
2327
|
+
paddingPaths.push(path58);
|
|
2328
|
+
tokens.add(path58);
|
|
2329
2329
|
}
|
|
2330
2330
|
} else if (typeof raw[field] === "number" && raw[field] !== 0) {
|
|
2331
2331
|
ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
|
|
@@ -6239,43 +6239,43 @@ function classifyBundleSurface(files, opts) {
|
|
|
6239
6239
|
const excluded = [];
|
|
6240
6240
|
const unknown = [];
|
|
6241
6241
|
for (const raw of files) {
|
|
6242
|
-
const
|
|
6243
|
-
const inEvidence =
|
|
6244
|
-
if (
|
|
6245
|
-
const fname =
|
|
6242
|
+
const path58 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6243
|
+
const inEvidence = path58.startsWith(`${EVIDENCE_DIR}/`);
|
|
6244
|
+
if (path58.startsWith("fonts/")) {
|
|
6245
|
+
const fname = path58.slice("fonts/".length);
|
|
6246
6246
|
if (!fname.includes("/") && (/\.(woff2?|ttf|otf)$/i.test(fname) || /^(NOTICE|LICENSE|LICENCE)[^/]*\.txt$/i.test(fname))) {
|
|
6247
|
-
excluded.push({ path:
|
|
6247
|
+
excluded.push({ path: path58, reason: "font payload \u2014 not published (fonts policy pending); faces are sha-pinned in component.json requiredFonts" });
|
|
6248
6248
|
continue;
|
|
6249
6249
|
}
|
|
6250
|
-
unknown.push(
|
|
6250
|
+
unknown.push(path58);
|
|
6251
6251
|
continue;
|
|
6252
6252
|
}
|
|
6253
|
-
const name = inEvidence ?
|
|
6253
|
+
const name = inEvidence ? path58.slice(EVIDENCE_DIR.length + 1) : path58;
|
|
6254
6254
|
if (name.includes("/")) {
|
|
6255
|
-
unknown.push(
|
|
6255
|
+
unknown.push(path58);
|
|
6256
6256
|
continue;
|
|
6257
6257
|
}
|
|
6258
6258
|
if (inEvidence) {
|
|
6259
|
-
if (name === "verify-report.json") published.push({ path:
|
|
6260
|
-
else if (name === "diff-legend.txt") published.push({ path:
|
|
6261
|
-
else if (name === "inspect.html") published.push({ path:
|
|
6262
|
-
else if (/-mount-failure\.png$/.test(name)) excluded.push({ path:
|
|
6259
|
+
if (name === "verify-report.json") published.push({ path: path58, role: "verify-report" });
|
|
6260
|
+
else if (name === "diff-legend.txt") published.push({ path: path58, role: "diff-legend" });
|
|
6261
|
+
else if (name === "inspect.html") published.push({ path: path58, role: "inspect-sheet" });
|
|
6262
|
+
else if (/-mount-failure\.png$/.test(name)) excluded.push({ path: path58, reason: "harness failure diagnostic (regenerated every verify run, never published)" });
|
|
6263
6263
|
else {
|
|
6264
6264
|
const hit = EVIDENCE_PATTERNS.find((p) => p.re.test(name));
|
|
6265
|
-
if (hit !== void 0) published.push({ path:
|
|
6266
|
-
else unknown.push(
|
|
6265
|
+
if (hit !== void 0) published.push({ path: path58, role: hit.role });
|
|
6266
|
+
else unknown.push(path58);
|
|
6267
6267
|
}
|
|
6268
6268
|
continue;
|
|
6269
6269
|
}
|
|
6270
|
-
if (name === opts.entry) published.push({ path:
|
|
6271
|
-
else if (name === "styles.css") published.push({ path:
|
|
6272
|
-
else if (name === "tokens.css") published.push({ path:
|
|
6273
|
-
else if (name === "fonts.css") published.push({ path:
|
|
6274
|
-
else if (name === "component.json") published.push({ path:
|
|
6270
|
+
if (name === opts.entry) published.push({ path: path58, role: "entry" });
|
|
6271
|
+
else if (name === "styles.css") published.push({ path: path58, role: "styles" });
|
|
6272
|
+
else if (name === "tokens.css") published.push({ path: path58, role: "tokens" });
|
|
6273
|
+
else if (name === "fonts.css") published.push({ path: path58, role: "fonts" });
|
|
6274
|
+
else if (name === "component.json") published.push({ path: path58, role: "manifest" });
|
|
6275
6275
|
else {
|
|
6276
6276
|
const skip = EXCLUDED.find((e) => e.test(name));
|
|
6277
|
-
if (skip !== void 0) excluded.push({ path:
|
|
6278
|
-
else unknown.push(
|
|
6277
|
+
if (skip !== void 0) excluded.push({ path: path58, reason: skip.reason });
|
|
6278
|
+
else unknown.push(path58);
|
|
6279
6279
|
}
|
|
6280
6280
|
}
|
|
6281
6281
|
const roles = new Set(published.map((p) => p.role));
|
|
@@ -6285,8 +6285,8 @@ function missingInspectCrops(sheetText, publishedPaths) {
|
|
|
6285
6285
|
const held = new Set(publishedPaths);
|
|
6286
6286
|
const missing = /* @__PURE__ */ new Set();
|
|
6287
6287
|
for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
|
|
6288
|
-
const
|
|
6289
|
-
if (!held.has(
|
|
6288
|
+
const path58 = `${EVIDENCE_DIR}/${name}`;
|
|
6289
|
+
if (!held.has(path58)) missing.add(path58);
|
|
6290
6290
|
}
|
|
6291
6291
|
return [...missing].sort();
|
|
6292
6292
|
}
|
|
@@ -6394,10 +6394,10 @@ function readScoredFiles(report) {
|
|
|
6394
6394
|
const entries = Object.entries(value);
|
|
6395
6395
|
if (entries.length === 0) return void 0;
|
|
6396
6396
|
const out = {};
|
|
6397
|
-
for (const [
|
|
6398
|
-
if (
|
|
6397
|
+
for (const [path58, digest] of entries) {
|
|
6398
|
+
if (path58 === "" || path58.startsWith("/") || path58.includes("..")) return void 0;
|
|
6399
6399
|
if (!isSetHash(digest)) return void 0;
|
|
6400
|
-
out[
|
|
6400
|
+
out[path58] = digest;
|
|
6401
6401
|
}
|
|
6402
6402
|
return out;
|
|
6403
6403
|
}
|
|
@@ -6405,11 +6405,11 @@ function compareScoredFiles(recorded, actual) {
|
|
|
6405
6405
|
const missing = [];
|
|
6406
6406
|
const unscored = [];
|
|
6407
6407
|
const changed = [];
|
|
6408
|
-
for (const [
|
|
6409
|
-
if (!(
|
|
6410
|
-
else if (actual[
|
|
6408
|
+
for (const [path58, digest] of Object.entries(recorded)) {
|
|
6409
|
+
if (!(path58 in actual)) missing.push(path58);
|
|
6410
|
+
else if (actual[path58] !== digest) changed.push(path58);
|
|
6411
6411
|
}
|
|
6412
|
-
for (const
|
|
6412
|
+
for (const path58 of Object.keys(actual)) if (!(path58 in recorded)) unscored.push(path58);
|
|
6413
6413
|
return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
|
|
6414
6414
|
}
|
|
6415
6415
|
function scoredRecordingSetHash(report) {
|
|
@@ -8468,6 +8468,9 @@ var init_publish_client = __esm({
|
|
|
8468
8468
|
pollApproval(input) {
|
|
8469
8469
|
return this.json("GET", `/api/publish-approvals/${encodeURIComponent(input.approvalId)}`, null, true);
|
|
8470
8470
|
}
|
|
8471
|
+
whoami() {
|
|
8472
|
+
return this.json("GET", "/api/whoami", null, true);
|
|
8473
|
+
}
|
|
8471
8474
|
begin(input) {
|
|
8472
8475
|
return this.json("POST", "/api/publications", input, true);
|
|
8473
8476
|
}
|
|
@@ -8570,18 +8573,50 @@ var init_publish_client = __esm({
|
|
|
8570
8573
|
}
|
|
8571
8574
|
});
|
|
8572
8575
|
|
|
8576
|
+
// packages/cli/src/figma-token.ts
|
|
8577
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync5, readFileSync as readFileSync20, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
|
|
8578
|
+
import path28 from "node:path";
|
|
8579
|
+
function figmaTokenPath() {
|
|
8580
|
+
return path28.join(path28.dirname(sessionPath()), "figma-oauth.json");
|
|
8581
|
+
}
|
|
8582
|
+
function readFigmaTokens(file = figmaTokenPath()) {
|
|
8583
|
+
if (!existsSync22(file)) return void 0;
|
|
8584
|
+
try {
|
|
8585
|
+
const parsed = JSON.parse(readFileSync20(file, "utf8"));
|
|
8586
|
+
if (typeof parsed.origin !== "string" || typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string" || typeof parsed.tokenExpiresAt !== "string") {
|
|
8587
|
+
return void 0;
|
|
8588
|
+
}
|
|
8589
|
+
return parsed;
|
|
8590
|
+
} catch {
|
|
8591
|
+
return void 0;
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
function writeFigmaTokens(tokens, file = figmaTokenPath()) {
|
|
8595
|
+
mkdirSync5(path28.dirname(file), { recursive: true });
|
|
8596
|
+
writeFileSync9(file, `${JSON.stringify(tokens, null, 2)}
|
|
8597
|
+
`, { mode: 384 });
|
|
8598
|
+
}
|
|
8599
|
+
var RENEW_WITHIN_MS;
|
|
8600
|
+
var init_figma_token = __esm({
|
|
8601
|
+
"packages/cli/src/figma-token.ts"() {
|
|
8602
|
+
"use strict";
|
|
8603
|
+
init_publish_client();
|
|
8604
|
+
RENEW_WITHIN_MS = 7 * 24 * 60 * 6e4;
|
|
8605
|
+
}
|
|
8606
|
+
});
|
|
8607
|
+
|
|
8573
8608
|
// packages/cli/src/entitlement.ts
|
|
8574
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
8609
|
+
import { chmodSync as chmodSync2, existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8575
8610
|
import crypto from "node:crypto";
|
|
8576
8611
|
import os5 from "node:os";
|
|
8577
|
-
import
|
|
8612
|
+
import path29 from "node:path";
|
|
8578
8613
|
function entitlementPath() {
|
|
8579
|
-
return process.env["TENDRIL_ENTITLEMENT_PATH"] ??
|
|
8614
|
+
return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path29.join(os5.homedir(), ".tendril", "entitlement.json");
|
|
8580
8615
|
}
|
|
8581
8616
|
function readStoredEntitlement(file = entitlementPath()) {
|
|
8582
|
-
if (!
|
|
8617
|
+
if (!existsSync23(file)) return void 0;
|
|
8583
8618
|
try {
|
|
8584
|
-
const parsed = JSON.parse(
|
|
8619
|
+
const parsed = JSON.parse(readFileSync21(file, "utf8"));
|
|
8585
8620
|
if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
|
|
8586
8621
|
return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
|
|
8587
8622
|
} catch {
|
|
@@ -8589,8 +8624,8 @@ function readStoredEntitlement(file = entitlementPath()) {
|
|
|
8589
8624
|
}
|
|
8590
8625
|
}
|
|
8591
8626
|
function writeStoredEntitlement(stored, file = entitlementPath()) {
|
|
8592
|
-
|
|
8593
|
-
|
|
8627
|
+
mkdirSync6(path29.dirname(file), { recursive: true });
|
|
8628
|
+
writeFileSync10(file, `${JSON.stringify(stored, null, 2)}
|
|
8594
8629
|
`);
|
|
8595
8630
|
chmodSync2(file, 384);
|
|
8596
8631
|
}
|
|
@@ -8674,9 +8709,9 @@ var init_entitlement = __esm({
|
|
|
8674
8709
|
|
|
8675
8710
|
// packages/cli/src/commands/doctor.ts
|
|
8676
8711
|
import { spawnSync } from "node:child_process";
|
|
8677
|
-
import { existsSync as
|
|
8712
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync8 } from "node:fs";
|
|
8678
8713
|
import os6 from "node:os";
|
|
8679
|
-
import
|
|
8714
|
+
import path30 from "node:path";
|
|
8680
8715
|
function withDeadline(work, ms) {
|
|
8681
8716
|
return Promise.race([
|
|
8682
8717
|
work,
|
|
@@ -8736,17 +8771,17 @@ async function runDoctorChecks(options) {
|
|
|
8736
8771
|
remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
|
|
8737
8772
|
});
|
|
8738
8773
|
}
|
|
8739
|
-
const fontManifest =
|
|
8774
|
+
const fontManifest = path30.join(fontCacheDir(), "manifest.json");
|
|
8740
8775
|
checks.push(
|
|
8741
|
-
|
|
8776
|
+
existsSync24(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync22(fontManifest, "utf8")).length} faces)` } : {
|
|
8742
8777
|
name: "font-cache",
|
|
8743
8778
|
ok: true,
|
|
8744
8779
|
detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
|
|
8745
8780
|
remediation: `Nothing to do now: \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` fetches exactly what a recording declares, and generate/verify name that command \u2014 with the set filled in \u2014 when they need it.`
|
|
8746
8781
|
}
|
|
8747
8782
|
);
|
|
8748
|
-
const pluginRoot =
|
|
8749
|
-
if (
|
|
8783
|
+
const pluginRoot = path30.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
|
|
8784
|
+
if (existsSync24(pluginRoot)) {
|
|
8750
8785
|
try {
|
|
8751
8786
|
const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
8752
8787
|
const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
|
|
@@ -8783,12 +8818,15 @@ async function runDoctorChecks(options) {
|
|
|
8783
8818
|
ent.ok ? ent.mode === "pre-launch" ? { name: "entitlement", ok: true, detail: "pre-launch build \u2014 entitlement gates present but unarmed (no public key shipped); record/generate run freely" } : { name: "entitlement", ok: true, detail: `active plan "${ent.claims.plan}" until ${new Date(ent.claims.exp).toISOString().slice(0, 10)}${ent.stale ? " (stale \u2014 renews at next opportunity)" : ""}` } : { name: "entitlement", ok: false, detail: ent.error, remediation: ent.remediation }
|
|
8784
8819
|
);
|
|
8785
8820
|
checks.push(await portalSessionCheck(options.fetchImpl ?? fetch));
|
|
8821
|
+
checks.push(figmaRestCheck());
|
|
8786
8822
|
const figmaToken = resolveCredential("FIGMA_TOKEN");
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8823
|
+
if (figmaToken) {
|
|
8824
|
+
checks.push({
|
|
8825
|
+
name: "figma-pat",
|
|
8826
|
+
ok: true,
|
|
8827
|
+
detail: `FIGMA_TOKEN is set but unused by any command \u2014 Figma REST access is granted with ${tendrilCommand("figma-connect")} (OAuth), not tokens`
|
|
8828
|
+
});
|
|
8829
|
+
}
|
|
8792
8830
|
return {
|
|
8793
8831
|
ok: checks.filter((c) => c.name !== "figma-pat" && c.name !== "openrouter-key" && c.name !== "path-skew" && c.name !== "portal-session").every((c) => c.ok),
|
|
8794
8832
|
checks
|
|
@@ -8829,6 +8867,25 @@ async function portalSessionCheck(fetchImpl) {
|
|
|
8829
8867
|
...daysLeft !== null && daysLeft <= 7 ? { remediation: `That is ${String(daysLeft)} day(s) away \u2014 run ${tendrilCommand("login")} soon for a fresh session.` } : {}
|
|
8830
8868
|
};
|
|
8831
8869
|
}
|
|
8870
|
+
function figmaRestCheck() {
|
|
8871
|
+
const tokens = readFigmaTokens();
|
|
8872
|
+
if (tokens === void 0) {
|
|
8873
|
+
return {
|
|
8874
|
+
name: "figma-rest",
|
|
8875
|
+
ok: true,
|
|
8876
|
+
detail: `Figma is not connected for REST recording \u2014 large component sets then pay the MCP daily call quota. Run ${tendrilCommand("figma-connect")} and click Allow once (agents: the tendril_figma_connect tool).`
|
|
8877
|
+
};
|
|
8878
|
+
}
|
|
8879
|
+
const daysLeft = Math.floor((Date.parse(tokens.tokenExpiresAt) - Date.now()) / 864e5);
|
|
8880
|
+
if (daysLeft < 0) {
|
|
8881
|
+
return {
|
|
8882
|
+
name: "figma-rest",
|
|
8883
|
+
ok: true,
|
|
8884
|
+
detail: `the stored Figma credential lapsed ${String(-daysLeft)} day(s) ago \u2014 recording renews it automatically on next use, or reconnect with ${tendrilCommand("figma-connect")}`
|
|
8885
|
+
};
|
|
8886
|
+
}
|
|
8887
|
+
return { name: "figma-rest", ok: true, detail: `Figma connected via ${tokens.origin}, credential good ~${String(daysLeft)} more day(s) (renews itself)` };
|
|
8888
|
+
}
|
|
8832
8889
|
function probeVersion(binary) {
|
|
8833
8890
|
const windowsShim = /\.(cmd|bat)$/i.test(binary);
|
|
8834
8891
|
const res = windowsShim ? spawnSync(`"${binary}" --version`, { shell: true, timeout: 5e3, encoding: "utf8" }) : spawnSync(binary, ["--version"], { timeout: 5e3, encoding: "utf8" });
|
|
@@ -8903,6 +8960,7 @@ var init_doctor = __esm({
|
|
|
8903
8960
|
init_invocation();
|
|
8904
8961
|
init_output();
|
|
8905
8962
|
init_publish_client();
|
|
8963
|
+
init_figma_token();
|
|
8906
8964
|
init_entitlement();
|
|
8907
8965
|
DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
|
|
8908
8966
|
DOCTOR_DESCRIPTION = {
|
|
@@ -10018,8 +10076,8 @@ var init_engine_curated = __esm({
|
|
|
10018
10076
|
});
|
|
10019
10077
|
|
|
10020
10078
|
// packages/generate/src/loop.ts
|
|
10021
|
-
import { existsSync as
|
|
10022
|
-
import
|
|
10079
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync8, readFileSync as readFileSync24, renameSync, writeFileSync as writeFileSync12 } from "node:fs";
|
|
10080
|
+
import path33 from "node:path";
|
|
10023
10081
|
import { z as z13 } from "zod";
|
|
10024
10082
|
function objective(scores, behaviors) {
|
|
10025
10083
|
const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
|
|
@@ -10061,9 +10119,9 @@ ${absentLines.join("\n")}` : ""}
|
|
|
10061
10119
|
Fix the FAIL configs (region coordinates are in the recorded screenshot's frame; ink<1 means recorded foreground pixels your render does not cover). Reading the pair: ink credits a recorded pixel when ANY render ink lands within 1px of it, so ink=1 proves coverage \u2014 not presence, shape, or colour. A wrong-shaped mark over the right region, or a wrong-coloured one that is still ink, scores 1.000; and a recorded mark within ~30 channel-sum of the backdrop (#f6f6f6 on white is 27) is not ink at all, so it can be absent at ink 1.000 with no MISSING line. With ink=1 AND low sim, coverage held while pixels differ, so start with the TWO causes below \u2014 but never read ink=1 as "nothing is missing": confirm every faint or small recorded mark (hairlines, dividers, low-contrast controls) exists in your render. GEOMETRY: wrong position/size/radius; the diff shows shifted edges and bands. WRONG TEXT WEIGHT OR FACE: the diff shows a uniform haze over glyph runs; CSS binds the font FAMILY before the weight, a later family in the stack is never consulted for a weight the first one lacks, and font-synthesis: none rules out faux-bold \u2014 so a stack led by a family the kit holds at one weight renders every other weight in that face, silently. Check the font stack against the kit's cached faces before concluding geometry is at fault. LOW ink with high sim means recorded marks are absent or painted invisibly. Do not regress PASS configs. ${mode === "files" ? "Update the files in your candidate directory and re-run the score." : "Reply with the complete corrected files in the same FILE format."}`;
|
|
10062
10120
|
}
|
|
10063
10121
|
function archivePriorRun(outDir) {
|
|
10064
|
-
if (!
|
|
10122
|
+
if (!existsSync26(path33.join(outDir, "run-log.json")) && !existsSync26(path33.join(outDir, "loop-state.json"))) return void 0;
|
|
10065
10123
|
let n = 1;
|
|
10066
|
-
while (
|
|
10124
|
+
while (existsSync26(`${outDir}-prev-${n}`)) n += 1;
|
|
10067
10125
|
renameSync(outDir, `${outDir}-prev-${n}`);
|
|
10068
10126
|
return `${outDir}-prev-${n}`;
|
|
10069
10127
|
}
|
|
@@ -10072,14 +10130,14 @@ async function runEngineLoop(opts) {
|
|
|
10072
10130
|
const plateau = opts.plateau ?? 2;
|
|
10073
10131
|
const progress = opts.onProgress ?? (() => {
|
|
10074
10132
|
});
|
|
10075
|
-
const statePath =
|
|
10076
|
-
const resuming = opts.resume === true &&
|
|
10133
|
+
const statePath = path33.join(opts.outDir, "loop-state.json");
|
|
10134
|
+
const resuming = opts.resume === true && existsSync26(statePath);
|
|
10077
10135
|
if (!resuming) {
|
|
10078
10136
|
const archived = archivePriorRun(opts.outDir);
|
|
10079
10137
|
if (archived !== void 0) progress(`previous run archived to ${archived}`);
|
|
10080
10138
|
}
|
|
10081
|
-
|
|
10082
|
-
const scratch =
|
|
10139
|
+
mkdirSync8(opts.outDir, { recursive: true });
|
|
10140
|
+
const scratch = path33.join(opts.outDir, ".candidate");
|
|
10083
10141
|
let attempts = [];
|
|
10084
10142
|
let log = [];
|
|
10085
10143
|
let best;
|
|
@@ -10087,7 +10145,7 @@ async function runEngineLoop(opts) {
|
|
|
10087
10145
|
let nonAccepted = 0;
|
|
10088
10146
|
let stopReason = "max-iterations";
|
|
10089
10147
|
if (resuming) {
|
|
10090
|
-
const restored = LoopStateSchema.parse(JSON.parse(
|
|
10148
|
+
const restored = LoopStateSchema.parse(JSON.parse(readFileSync24(statePath, "utf8")));
|
|
10091
10149
|
attempts = restored.attempts;
|
|
10092
10150
|
log = restored.iterations;
|
|
10093
10151
|
spentUsd = restored.spentUsd;
|
|
@@ -10102,12 +10160,12 @@ async function runEngineLoop(opts) {
|
|
|
10102
10160
|
progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
|
|
10103
10161
|
}
|
|
10104
10162
|
const persist = () => {
|
|
10105
|
-
|
|
10163
|
+
writeFileSync12(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
|
|
10106
10164
|
`);
|
|
10107
10165
|
};
|
|
10108
10166
|
const writeCandidate = (files) => {
|
|
10109
|
-
|
|
10110
|
-
for (const [name, content] of Object.entries(files))
|
|
10167
|
+
mkdirSync8(scratch, { recursive: true });
|
|
10168
|
+
for (const [name, content] of Object.entries(files)) writeFileSync12(path33.join(scratch, name), content);
|
|
10111
10169
|
};
|
|
10112
10170
|
const scoreCandidate = async (candidate, iter, usd, modelMs) => {
|
|
10113
10171
|
writeCandidate(candidate.files);
|
|
@@ -10165,8 +10223,8 @@ async function runEngineLoop(opts) {
|
|
|
10165
10223
|
const usd = candidate.usage?.usd ?? 0;
|
|
10166
10224
|
spentUsd += usd;
|
|
10167
10225
|
if (candidate.raw !== void 0) {
|
|
10168
|
-
|
|
10169
|
-
|
|
10226
|
+
mkdirSync8(path33.join(opts.outDir, "responses"), { recursive: true });
|
|
10227
|
+
writeFileSync12(path33.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
|
|
10170
10228
|
}
|
|
10171
10229
|
if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
|
|
10172
10230
|
const finish = candidate.usage?.finishReason ?? "?";
|
|
@@ -10192,10 +10250,10 @@ async function runEngineLoop(opts) {
|
|
|
10192
10250
|
}
|
|
10193
10251
|
}
|
|
10194
10252
|
}
|
|
10195
|
-
if (best !== void 0) for (const [name, content] of Object.entries(best.files))
|
|
10253
|
+
if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync12(path33.join(opts.outDir, name), content);
|
|
10196
10254
|
const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
|
|
10197
|
-
|
|
10198
|
-
|
|
10255
|
+
writeFileSync12(
|
|
10256
|
+
path33.join(opts.outDir, "run-log.json"),
|
|
10199
10257
|
`${JSON.stringify(
|
|
10200
10258
|
{
|
|
10201
10259
|
...opts.meta,
|
|
@@ -10262,8 +10320,8 @@ var init_loop2 = __esm({
|
|
|
10262
10320
|
});
|
|
10263
10321
|
|
|
10264
10322
|
// packages/generate/src/brief.ts
|
|
10265
|
-
import { existsSync as
|
|
10266
|
-
import
|
|
10323
|
+
import { existsSync as existsSync27, readFileSync as readFileSync25 } from "node:fs";
|
|
10324
|
+
import path34 from "node:path";
|
|
10267
10325
|
import { PNG as PNG3 } from "pngjs";
|
|
10268
10326
|
function singleAxes2(name) {
|
|
10269
10327
|
const parsed = parseVariantAxes(name);
|
|
@@ -10703,15 +10761,15 @@ function authorBehaviors(api, extras = {}) {
|
|
|
10703
10761
|
};
|
|
10704
10762
|
}
|
|
10705
10763
|
function envelopeText(file) {
|
|
10706
|
-
return envelopeFirstTextPart(JSON.parse(
|
|
10764
|
+
return envelopeFirstTextPart(JSON.parse(readFileSync25(file, "utf8")));
|
|
10707
10765
|
}
|
|
10708
10766
|
function metadataText(file) {
|
|
10709
|
-
return envelopeTextContent(JSON.parse(
|
|
10767
|
+
return envelopeTextContent(JSON.parse(readFileSync25(file, "utf8")));
|
|
10710
10768
|
}
|
|
10711
10769
|
function dismissEvidence(setDir, repSlugs) {
|
|
10712
10770
|
for (const slug of repSlugs) {
|
|
10713
|
-
const f =
|
|
10714
|
-
if (!
|
|
10771
|
+
const f = path34.join(setDir, slug, "get_design_context.json");
|
|
10772
|
+
if (!existsSync27(f)) continue;
|
|
10715
10773
|
const text = envelopeText(f);
|
|
10716
10774
|
const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
|
|
10717
10775
|
if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
|
|
@@ -10738,9 +10796,9 @@ function dismissEvidence(setDir, repSlugs) {
|
|
|
10738
10796
|
const glyphIsTheComponent = (() => {
|
|
10739
10797
|
const slugToCheck = vis.visibleIn[0];
|
|
10740
10798
|
if (slugToCheck === void 0) return false;
|
|
10741
|
-
const metaFile =
|
|
10799
|
+
const metaFile = path34.join(setDir, slugToCheck, "get_metadata.json");
|
|
10742
10800
|
try {
|
|
10743
|
-
const root = parseMetadataStructure(envelopeTextContent(JSON.parse(
|
|
10801
|
+
const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync25(metaFile, "utf8"))));
|
|
10744
10802
|
if (root.children.length !== 1) return false;
|
|
10745
10803
|
const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
|
|
10746
10804
|
return contains(root.children[0]);
|
|
@@ -10760,10 +10818,10 @@ function dismissEvidence(setDir, repSlugs) {
|
|
|
10760
10818
|
return void 0;
|
|
10761
10819
|
}
|
|
10762
10820
|
function recordedReferencePng(setDir, slug) {
|
|
10763
|
-
const f =
|
|
10764
|
-
if (!
|
|
10821
|
+
const f = path34.join(setDir, slug, "get_screenshot.json");
|
|
10822
|
+
if (!existsSync27(f)) return void 0;
|
|
10765
10823
|
try {
|
|
10766
|
-
const env = JSON.parse(
|
|
10824
|
+
const env = JSON.parse(readFileSync25(f, "utf8")).content.find((c) => c.type === "image");
|
|
10767
10825
|
return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
|
|
10768
10826
|
} catch {
|
|
10769
10827
|
return void 0;
|
|
@@ -10843,13 +10901,13 @@ function recordedFontNeeds(setDir, opts = {}) {
|
|
|
10843
10901
|
}
|
|
10844
10902
|
}
|
|
10845
10903
|
const manifest = loadManifest(setDir);
|
|
10846
|
-
const setDefs =
|
|
10847
|
-
if (
|
|
10904
|
+
const setDefs = path34.join(setDir, "get_variable_defs.json");
|
|
10905
|
+
if (existsSync27(setDefs)) fromDefs(envelopeText(setDefs));
|
|
10848
10906
|
for (const rep of manifest.reps) {
|
|
10849
|
-
const ctx =
|
|
10850
|
-
if (
|
|
10851
|
-
const defs =
|
|
10852
|
-
if (
|
|
10907
|
+
const ctx = path34.join(setDir, rep.slug, "get_design_context.json");
|
|
10908
|
+
if (existsSync27(ctx)) fromEmission(envelopeText(ctx));
|
|
10909
|
+
const defs = path34.join(setDir, rep.slug, "get_variable_defs.json");
|
|
10910
|
+
if (existsSync27(defs)) fromDefs(envelopeText(defs));
|
|
10853
10911
|
}
|
|
10854
10912
|
return [...byFamily.entries()].map(([family, paired]) => {
|
|
10855
10913
|
const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
|
|
@@ -10860,10 +10918,10 @@ function symbolFontGlyphCount(setDir, reps) {
|
|
|
10860
10918
|
const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
|
|
10861
10919
|
const glyphs = /* @__PURE__ */ new Set();
|
|
10862
10920
|
for (const rep of reps) {
|
|
10863
|
-
const file =
|
|
10864
|
-
if (!
|
|
10921
|
+
const file = path34.join(setDir, rep, "get_metadata.json");
|
|
10922
|
+
if (!existsSync27(file)) continue;
|
|
10865
10923
|
try {
|
|
10866
|
-
const text = JSON.parse(
|
|
10924
|
+
const text = JSON.parse(readFileSync25(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
10867
10925
|
for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
|
|
10868
10926
|
const name = decodeXmlEntities(m[1]).replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
|
|
10869
10927
|
if (PUA.test(name)) glyphs.add(name);
|
|
@@ -10889,8 +10947,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
10889
10947
|
const propRep = [];
|
|
10890
10948
|
const perRep = [];
|
|
10891
10949
|
for (const slug of repSlugs) {
|
|
10892
|
-
const f =
|
|
10893
|
-
if (!
|
|
10950
|
+
const f = path34.join(setDir, slug, "get_design_context.json");
|
|
10951
|
+
if (!existsSync27(f)) continue;
|
|
10894
10952
|
const code = envelopeText(f);
|
|
10895
10953
|
const props = /* @__PURE__ */ new Map();
|
|
10896
10954
|
for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
|
|
@@ -10916,8 +10974,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
10916
10974
|
const axisValuesBySlug = /* @__PURE__ */ new Map();
|
|
10917
10975
|
const valuesByAxis = /* @__PURE__ */ new Map();
|
|
10918
10976
|
for (const slug of repSlugs) {
|
|
10919
|
-
const metaFile =
|
|
10920
|
-
if (!
|
|
10977
|
+
const metaFile = path34.join(setDir, slug, "get_metadata.json");
|
|
10978
|
+
if (!existsSync27(metaFile)) continue;
|
|
10921
10979
|
const name = symbolName(metadataText(metaFile));
|
|
10922
10980
|
if (name === void 0) continue;
|
|
10923
10981
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -11032,8 +11090,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
11032
11090
|
const poses = [];
|
|
11033
11091
|
const missing = [];
|
|
11034
11092
|
for (const rep of manifest.reps) {
|
|
11035
|
-
const metaFile =
|
|
11036
|
-
if (!
|
|
11093
|
+
const metaFile = path34.join(setDir, rep.slug, "get_metadata.json");
|
|
11094
|
+
if (!existsSync27(metaFile)) {
|
|
11037
11095
|
missing.push(rep.slug);
|
|
11038
11096
|
continue;
|
|
11039
11097
|
}
|
|
@@ -11047,8 +11105,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
11047
11105
|
if (missing.length > 0) {
|
|
11048
11106
|
throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
|
|
11049
11107
|
}
|
|
11050
|
-
const setMeta =
|
|
11051
|
-
const latticeNames = manifest.latticeNames ?? (
|
|
11108
|
+
const setMeta = path34.join(setDir, "get_metadata.json");
|
|
11109
|
+
const latticeNames = manifest.latticeNames ?? (existsSync27(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
|
|
11052
11110
|
const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
|
|
11053
11111
|
const recordedFonts = recordedFontFamilies(setDir);
|
|
11054
11112
|
const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
|
|
@@ -11249,17 +11307,17 @@ var init_brief = __esm({
|
|
|
11249
11307
|
});
|
|
11250
11308
|
|
|
11251
11309
|
// packages/generate/src/segments.ts
|
|
11252
|
-
import { existsSync as
|
|
11253
|
-
import
|
|
11310
|
+
import { existsSync as existsSync28, readFileSync as readFileSync26, readdirSync as readdirSync10 } from "node:fs";
|
|
11311
|
+
import path35 from "node:path";
|
|
11254
11312
|
function repText(set, rep, tool) {
|
|
11255
|
-
const env = JSON.parse(
|
|
11313
|
+
const env = JSON.parse(readFileSync26(path35.join(set, rep, `${tool}.json`), "utf8"));
|
|
11256
11314
|
return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
|
|
11257
11315
|
}
|
|
11258
11316
|
function refPngDims(set, rep) {
|
|
11259
|
-
const f =
|
|
11260
|
-
if (!
|
|
11317
|
+
const f = path35.join(set, rep, "get_screenshot.json");
|
|
11318
|
+
if (!existsSync28(f)) return void 0;
|
|
11261
11319
|
try {
|
|
11262
|
-
const env = JSON.parse(
|
|
11320
|
+
const env = JSON.parse(readFileSync26(f, "utf8")).content.find((c) => c.type === "image");
|
|
11263
11321
|
if (env?.data === void 0) return void 0;
|
|
11264
11322
|
const buf = Buffer.from(env.data, "base64");
|
|
11265
11323
|
if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
|
|
@@ -11325,20 +11383,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
|
|
|
11325
11383
|
}
|
|
11326
11384
|
function buildSegments(task, mode = "fenced") {
|
|
11327
11385
|
const SET = task.set;
|
|
11328
|
-
let defsRecorded =
|
|
11386
|
+
let defsRecorded = existsSync28(path35.join(SET, "get_variable_defs.json"));
|
|
11329
11387
|
let rawDefs = {};
|
|
11330
|
-
if (
|
|
11331
|
-
const text = envelopeFirstTextPart(JSON.parse(
|
|
11388
|
+
if (existsSync28(path35.join(SET, "get_variable_defs.json"))) {
|
|
11389
|
+
const text = envelopeFirstTextPart(JSON.parse(readFileSync26(path35.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
|
|
11332
11390
|
try {
|
|
11333
11391
|
rawDefs = JSON.parse(text);
|
|
11334
11392
|
} catch {
|
|
11335
11393
|
}
|
|
11336
11394
|
} else {
|
|
11337
11395
|
for (const cfg of task.configs) {
|
|
11338
|
-
const f =
|
|
11339
|
-
if (!
|
|
11396
|
+
const f = path35.join(SET, cfg.rep, "get_variable_defs.json");
|
|
11397
|
+
if (!existsSync28(f)) continue;
|
|
11340
11398
|
defsRecorded = true;
|
|
11341
|
-
const text = envelopeFirstTextPart(JSON.parse(
|
|
11399
|
+
const text = envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8"))) || "{}";
|
|
11342
11400
|
try {
|
|
11343
11401
|
for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
|
|
11344
11402
|
} catch {
|
|
@@ -11346,8 +11404,8 @@ function buildSegments(task, mode = "fenced") {
|
|
|
11346
11404
|
}
|
|
11347
11405
|
}
|
|
11348
11406
|
const emissionTexts = task.configs.map((cfg) => {
|
|
11349
|
-
const f =
|
|
11350
|
-
return
|
|
11407
|
+
const f = path35.join(SET, cfg.rep, "get_design_context.json");
|
|
11408
|
+
return existsSync28(f) ? envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8"))) : "";
|
|
11351
11409
|
});
|
|
11352
11410
|
const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
|
|
11353
11411
|
const defs = JSON.stringify(map, null, 1);
|
|
@@ -11365,9 +11423,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
|
|
|
11365
11423
|
for (const cfg of task.configs) {
|
|
11366
11424
|
const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
|
|
11367
11425
|
const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
|
|
11368
|
-
const assets = readdirSync10(
|
|
11426
|
+
const assets = readdirSync10(path35.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
|
|
11369
11427
|
\`\`\`svg
|
|
11370
|
-
${
|
|
11428
|
+
${readFileSync26(path35.join(SET, cfg.rep, f), "utf8")}
|
|
11371
11429
|
\`\`\``).join("\n");
|
|
11372
11430
|
const refNote = (() => {
|
|
11373
11431
|
const dims = refPngDims(SET, cfg.rep);
|
|
@@ -11403,7 +11461,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
|
|
|
11403
11461
|
} else {
|
|
11404
11462
|
parts.push(`
|
|
11405
11463
|
## Output format
|
|
11406
|
-
Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${
|
|
11464
|
+
Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path35.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
|
|
11407
11465
|
}
|
|
11408
11466
|
return parts.join("\n");
|
|
11409
11467
|
}
|
|
@@ -11471,8 +11529,8 @@ var init_adapter = __esm({
|
|
|
11471
11529
|
|
|
11472
11530
|
// packages/generate/src/bundle-emit.ts
|
|
11473
11531
|
import { createHash as createHash6 } from "node:crypto";
|
|
11474
|
-
import { copyFileSync, existsSync as
|
|
11475
|
-
import
|
|
11532
|
+
import { copyFileSync, existsSync as existsSync29, mkdirSync as mkdirSync9, readFileSync as readFileSync27, readdirSync as readdirSync11, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "node:fs";
|
|
11533
|
+
import path36 from "node:path";
|
|
11476
11534
|
function pinFromConfigs(configs) {
|
|
11477
11535
|
const domains = /* @__PURE__ */ new Map();
|
|
11478
11536
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -11541,9 +11599,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11541
11599
|
const notices = [];
|
|
11542
11600
|
const licenseTexts = /* @__PURE__ */ new Map();
|
|
11543
11601
|
for (const face of faces) {
|
|
11544
|
-
const src =
|
|
11545
|
-
const target = `./fonts/${
|
|
11546
|
-
const format = FONT_FORMATS[
|
|
11602
|
+
const src = path36.join(cacheDir, path36.basename(face.file));
|
|
11603
|
+
const target = `./fonts/${path36.basename(face.file)}`;
|
|
11604
|
+
const format = FONT_FORMATS[path36.extname(face.file).toLowerCase()] ?? "truetype";
|
|
11547
11605
|
const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
|
|
11548
11606
|
const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
|
|
11549
11607
|
const license = normalizeFontLicense(face.license);
|
|
@@ -11581,14 +11639,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11581
11639
|
`/* ${decl} */`
|
|
11582
11640
|
);
|
|
11583
11641
|
}
|
|
11584
|
-
} else if (
|
|
11585
|
-
|
|
11586
|
-
copyFileSync(src,
|
|
11642
|
+
} else if (existsSync29(src) && createHash6("sha256").update(readFileSync27(src)).digest("hex") === face.sha256) {
|
|
11643
|
+
mkdirSync9(path36.join(bundleDir, "fonts"), { recursive: true });
|
|
11644
|
+
copyFileSync(src, path36.join(bundleDir, "fonts", path36.basename(face.file)));
|
|
11587
11645
|
licenseTexts.set(terms.file, terms.text);
|
|
11588
11646
|
const upstream = upstreamAttribution(face);
|
|
11589
11647
|
notices.push(
|
|
11590
11648
|
"",
|
|
11591
|
-
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${
|
|
11649
|
+
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path36.basename(face.file)}`,
|
|
11592
11650
|
` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
|
|
11593
11651
|
` source: ${face.source}`,
|
|
11594
11652
|
` sha256: ${face.sha256}`,
|
|
@@ -11602,9 +11660,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11602
11660
|
}
|
|
11603
11661
|
if (lines.length === 0) return null;
|
|
11604
11662
|
if (notices.length > 0) {
|
|
11605
|
-
const fontsDir =
|
|
11606
|
-
for (const [file, text] of licenseTexts)
|
|
11607
|
-
|
|
11663
|
+
const fontsDir = path36.join(bundleDir, "fonts");
|
|
11664
|
+
for (const [file, text] of licenseTexts) writeFileSync13(path36.join(fontsDir, file), text);
|
|
11665
|
+
writeFileSync13(path36.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
|
|
11608
11666
|
`);
|
|
11609
11667
|
header.push(
|
|
11610
11668
|
"/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
|
|
@@ -11616,10 +11674,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11616
11674
|
`;
|
|
11617
11675
|
}
|
|
11618
11676
|
function countLatticeSymbols(setDir) {
|
|
11619
|
-
const manifestFile =
|
|
11620
|
-
if (
|
|
11677
|
+
const manifestFile = path36.join(setDir, "recording-set.json");
|
|
11678
|
+
if (existsSync29(manifestFile)) {
|
|
11621
11679
|
try {
|
|
11622
|
-
const stored = JSON.parse(
|
|
11680
|
+
const stored = JSON.parse(readFileSync27(manifestFile, "utf8"));
|
|
11623
11681
|
if (stored.variantScope !== "component-set") return null;
|
|
11624
11682
|
const lattice = stored.latticeNames;
|
|
11625
11683
|
if (lattice !== void 0 && lattice.length > 0) return lattice.length;
|
|
@@ -11627,13 +11685,13 @@ function countLatticeSymbols(setDir) {
|
|
|
11627
11685
|
}
|
|
11628
11686
|
}
|
|
11629
11687
|
const files = [
|
|
11630
|
-
|
|
11631
|
-
...
|
|
11632
|
-
].filter((f) =>
|
|
11688
|
+
path36.join(setDir, "get_metadata.json"),
|
|
11689
|
+
...existsSync29(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path36.join(setDir, f)) : []
|
|
11690
|
+
].filter((f) => existsSync29(f));
|
|
11633
11691
|
if (files.length === 0) return null;
|
|
11634
11692
|
let count = 0;
|
|
11635
11693
|
for (const f of files) {
|
|
11636
|
-
const text = envelopeTextContent(JSON.parse(
|
|
11694
|
+
const text = envelopeTextContent(JSON.parse(readFileSync27(f, "utf8")));
|
|
11637
11695
|
count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
|
|
11638
11696
|
}
|
|
11639
11697
|
return count > 0 ? count : null;
|
|
@@ -11641,21 +11699,21 @@ function countLatticeSymbols(setDir) {
|
|
|
11641
11699
|
function recordingSetHash(setDir, configs) {
|
|
11642
11700
|
const relPaths = [];
|
|
11643
11701
|
for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
|
|
11644
|
-
if (
|
|
11702
|
+
if (existsSync29(path36.join(setDir, name))) relPaths.push(name);
|
|
11645
11703
|
}
|
|
11646
11704
|
for (const cfg of configs) {
|
|
11647
11705
|
for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
|
|
11648
|
-
if (
|
|
11706
|
+
if (existsSync29(path36.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
|
|
11649
11707
|
}
|
|
11650
|
-
if (
|
|
11651
|
-
for (const asset of readdirSync11(
|
|
11708
|
+
if (existsSync29(path36.join(setDir, cfg.rep))) {
|
|
11709
|
+
for (const asset of readdirSync11(path36.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
|
|
11652
11710
|
relPaths.push(`${cfg.rep}/${asset}`);
|
|
11653
11711
|
}
|
|
11654
11712
|
}
|
|
11655
11713
|
}
|
|
11656
11714
|
return hashRecordingSet(
|
|
11657
11715
|
relPaths,
|
|
11658
|
-
(p) => new Uint8Array(
|
|
11716
|
+
(p) => new Uint8Array(readFileSync27(path36.join(setDir, p))),
|
|
11659
11717
|
(chunks) => {
|
|
11660
11718
|
const h = createHash6("sha256");
|
|
11661
11719
|
for (const c of chunks) h.update(c);
|
|
@@ -11696,8 +11754,8 @@ function emitBundleV1(opts) {
|
|
|
11696
11754
|
const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
|
|
11697
11755
|
const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
11698
11756
|
const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
|
|
11699
|
-
const cssFiles = ["styles.css", "tokens.css"].map((f) =>
|
|
11700
|
-
const families = cssFontFamilies(cssFiles.map((f) =>
|
|
11757
|
+
const cssFiles = ["styles.css", "tokens.css"].map((f) => path36.join(opts.bundleDir, f)).filter((f) => existsSync29(f));
|
|
11758
|
+
const families = cssFontFamilies(cssFiles.map((f) => readFileSync27(f, "utf8")).join("\n"));
|
|
11701
11759
|
const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
|
|
11702
11760
|
family: f.family,
|
|
11703
11761
|
weight: f.weight,
|
|
@@ -11726,7 +11784,7 @@ function emitBundleV1(opts) {
|
|
|
11726
11784
|
// resolvable via verify's --set override).
|
|
11727
11785
|
path: (() => {
|
|
11728
11786
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
11729
|
-
const rel =
|
|
11787
|
+
const rel = path36.relative(base, opts.task.set);
|
|
11730
11788
|
return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
|
|
11731
11789
|
})(),
|
|
11732
11790
|
component: opts.componentName,
|
|
@@ -11763,25 +11821,25 @@ function emitBundleV1(opts) {
|
|
|
11763
11821
|
})
|
|
11764
11822
|
};
|
|
11765
11823
|
const written = [];
|
|
11766
|
-
const manifestPath2 =
|
|
11767
|
-
|
|
11824
|
+
const manifestPath2 = path36.join(opts.bundleDir, "component.json");
|
|
11825
|
+
writeFileSync13(manifestPath2, `${JSON.stringify(manifest, null, 2)}
|
|
11768
11826
|
`);
|
|
11769
11827
|
written.push(manifestPath2);
|
|
11770
|
-
const stylesPath =
|
|
11771
|
-
if (
|
|
11828
|
+
const stylesPath = path36.join(opts.bundleDir, "styles.css");
|
|
11829
|
+
if (existsSync29(stylesPath)) {
|
|
11772
11830
|
const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
|
|
11773
|
-
const current =
|
|
11831
|
+
const current = readFileSync27(stylesPath, "utf8");
|
|
11774
11832
|
const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
|
|
11775
|
-
|
|
11833
|
+
writeFileSync13(stylesPath, `${comment}
|
|
11776
11834
|
${stripped}`);
|
|
11777
11835
|
written.push(stylesPath);
|
|
11778
11836
|
}
|
|
11779
|
-
const fontsCssPath =
|
|
11780
|
-
|
|
11781
|
-
|
|
11837
|
+
const fontsCssPath = path36.join(opts.bundleDir, "fonts.css");
|
|
11838
|
+
rmSync5(path36.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
|
|
11839
|
+
rmSync5(fontsCssPath, { force: true });
|
|
11782
11840
|
const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
|
|
11783
11841
|
if (fontsCss !== null) {
|
|
11784
|
-
|
|
11842
|
+
writeFileSync13(fontsCssPath, fontsCss);
|
|
11785
11843
|
written.push(fontsCssPath);
|
|
11786
11844
|
}
|
|
11787
11845
|
const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
|
|
@@ -12210,8 +12268,8 @@ DEALINGS IN THE FONT SOFTWARE.
|
|
|
12210
12268
|
|
|
12211
12269
|
// packages/generate/src/compose-pins.ts
|
|
12212
12270
|
import { createHash as createHash7 } from "node:crypto";
|
|
12213
|
-
import { existsSync as
|
|
12214
|
-
import
|
|
12271
|
+
import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
|
|
12272
|
+
import path37 from "node:path";
|
|
12215
12273
|
function bundleDirs(roots, depth = 4) {
|
|
12216
12274
|
const found = [];
|
|
12217
12275
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12220,11 +12278,11 @@ function bundleDirs(roots, depth = 4) {
|
|
|
12220
12278
|
try {
|
|
12221
12279
|
key = realpathSync3(dir);
|
|
12222
12280
|
} catch {
|
|
12223
|
-
key =
|
|
12281
|
+
key = path37.resolve(dir);
|
|
12224
12282
|
}
|
|
12225
12283
|
if (seen.has(key)) return;
|
|
12226
12284
|
seen.add(key);
|
|
12227
|
-
if (
|
|
12285
|
+
if (existsSync30(path37.join(dir, "component.json"))) {
|
|
12228
12286
|
found.push(key);
|
|
12229
12287
|
return;
|
|
12230
12288
|
}
|
|
@@ -12237,14 +12295,14 @@ function bundleDirs(roots, depth = 4) {
|
|
|
12237
12295
|
}
|
|
12238
12296
|
for (const e of entries) {
|
|
12239
12297
|
if (e === "node_modules" || e.startsWith(".")) continue;
|
|
12240
|
-
const full =
|
|
12298
|
+
const full = path37.join(dir, e);
|
|
12241
12299
|
try {
|
|
12242
12300
|
if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
|
|
12243
12301
|
} catch {
|
|
12244
12302
|
}
|
|
12245
12303
|
}
|
|
12246
12304
|
};
|
|
12247
|
-
for (const r of roots) walk2(
|
|
12305
|
+
for (const r of roots) walk2(path37.resolve(r), depth);
|
|
12248
12306
|
return found;
|
|
12249
12307
|
}
|
|
12250
12308
|
function composedPins(hostSet, libraryRoots) {
|
|
@@ -12263,7 +12321,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12263
12321
|
let pinned = false;
|
|
12264
12322
|
const failures = [];
|
|
12265
12323
|
for (const rel of partnerRels) {
|
|
12266
|
-
const partnerSet =
|
|
12324
|
+
const partnerSet = path37.resolve(hostSet, rel);
|
|
12267
12325
|
let partnerTask;
|
|
12268
12326
|
let partnerManifest;
|
|
12269
12327
|
try {
|
|
@@ -12292,7 +12350,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12292
12350
|
const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
|
|
12293
12351
|
const matches = candidates.filter((dir) => {
|
|
12294
12352
|
try {
|
|
12295
|
-
const parsed = readBundleManifest(
|
|
12353
|
+
const parsed = readBundleManifest(readFileSync28(path37.join(dir, "component.json"), "utf8"));
|
|
12296
12354
|
return parsed.manifest?.provenance.recordingSet.hash === wantHash;
|
|
12297
12355
|
} catch {
|
|
12298
12356
|
return false;
|
|
@@ -12305,13 +12363,13 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12305
12363
|
continue;
|
|
12306
12364
|
}
|
|
12307
12365
|
if (matches.length > 1) {
|
|
12308
|
-
failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) =>
|
|
12366
|
+
failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path37.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
|
|
12309
12367
|
continue;
|
|
12310
12368
|
}
|
|
12311
12369
|
const bundleDir = matches[0];
|
|
12312
12370
|
let manifest;
|
|
12313
12371
|
try {
|
|
12314
|
-
manifest = readBundleManifest(
|
|
12372
|
+
manifest = readBundleManifest(readFileSync28(path37.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
12315
12373
|
} catch {
|
|
12316
12374
|
manifest = void 0;
|
|
12317
12375
|
}
|
|
@@ -12328,8 +12386,8 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12328
12386
|
const moduleFiles = [];
|
|
12329
12387
|
let fileIssue;
|
|
12330
12388
|
for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
|
|
12331
|
-
const file =
|
|
12332
|
-
if (!
|
|
12389
|
+
const file = path37.join(bundleDir, name);
|
|
12390
|
+
if (!existsSync30(file)) {
|
|
12333
12391
|
if (name === manifest.entry || name === "styles.css") {
|
|
12334
12392
|
fileIssue = `${rel}: partner bundle is missing ${name}`;
|
|
12335
12393
|
break;
|
|
@@ -12338,7 +12396,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12338
12396
|
}
|
|
12339
12397
|
let bytes;
|
|
12340
12398
|
try {
|
|
12341
|
-
bytes =
|
|
12399
|
+
bytes = readFileSync28(file);
|
|
12342
12400
|
} catch {
|
|
12343
12401
|
fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
|
|
12344
12402
|
break;
|
|
@@ -12410,14 +12468,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
|
|
|
12410
12468
|
const checks = [];
|
|
12411
12469
|
let entrySource = "";
|
|
12412
12470
|
try {
|
|
12413
|
-
entrySource =
|
|
12471
|
+
entrySource = readFileSync28(path37.join(candidateDir, hostEntry), "utf8");
|
|
12414
12472
|
} catch {
|
|
12415
12473
|
}
|
|
12416
|
-
const candidateRoot =
|
|
12474
|
+
const candidateRoot = path37.resolve(candidateDir);
|
|
12417
12475
|
for (const pin of pins) {
|
|
12418
12476
|
const dir = composedModuleDir(pin.partnerName);
|
|
12419
|
-
const resolvedDir =
|
|
12420
|
-
if (!resolvedDir.startsWith(candidateRoot +
|
|
12477
|
+
const resolvedDir = path37.resolve(candidateDir, dir);
|
|
12478
|
+
if (!resolvedDir.startsWith(candidateRoot + path37.sep)) {
|
|
12421
12479
|
checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
|
|
12422
12480
|
checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
|
|
12423
12481
|
continue;
|
|
@@ -12428,12 +12486,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
|
|
|
12428
12486
|
wrong.push(`${f.name} refused (not a plain path segment)`);
|
|
12429
12487
|
continue;
|
|
12430
12488
|
}
|
|
12431
|
-
const target =
|
|
12432
|
-
if (!
|
|
12489
|
+
const target = path37.join(candidateDir, dir, f.name);
|
|
12490
|
+
if (!existsSync30(target)) {
|
|
12433
12491
|
wrong.push(`${f.name} missing`);
|
|
12434
12492
|
continue;
|
|
12435
12493
|
}
|
|
12436
|
-
const sha = createHash7("sha256").update(
|
|
12494
|
+
const sha = createHash7("sha256").update(readFileSync28(target)).digest("hex");
|
|
12437
12495
|
if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
|
|
12438
12496
|
}
|
|
12439
12497
|
checks.push({
|
|
@@ -12458,10 +12516,10 @@ function rootClassesFor(emission, nodeId) {
|
|
|
12458
12516
|
}
|
|
12459
12517
|
function regionOverrides(hostSet, partnerSet, instances) {
|
|
12460
12518
|
const read = (setDir, rep) => {
|
|
12461
|
-
const f =
|
|
12462
|
-
if (!
|
|
12519
|
+
const f = path37.join(setDir, rep, "get_design_context.json");
|
|
12520
|
+
if (!existsSync30(f)) return void 0;
|
|
12463
12521
|
try {
|
|
12464
|
-
return envelopeFirstTextPart(JSON.parse(
|
|
12522
|
+
return envelopeFirstTextPart(JSON.parse(readFileSync28(f, "utf8")));
|
|
12465
12523
|
} catch {
|
|
12466
12524
|
return void 0;
|
|
12467
12525
|
}
|
|
@@ -12491,7 +12549,7 @@ var init_compose_pins = __esm({
|
|
|
12491
12549
|
init_src4();
|
|
12492
12550
|
init_brief();
|
|
12493
12551
|
init_bundle_emit();
|
|
12494
|
-
composedModuleDir = (partnerName) =>
|
|
12552
|
+
composedModuleDir = (partnerName) => path37.posix.join("composed", partnerName);
|
|
12495
12553
|
safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
|
|
12496
12554
|
MAX_PINNED_FILE_BYTES = 1024 * 1024;
|
|
12497
12555
|
CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
|
|
@@ -12499,8 +12557,8 @@ var init_compose_pins = __esm({
|
|
|
12499
12557
|
});
|
|
12500
12558
|
|
|
12501
12559
|
// packages/generate/src/motion.ts
|
|
12502
|
-
import { existsSync as
|
|
12503
|
-
import
|
|
12560
|
+
import { existsSync as existsSync31, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
|
|
12561
|
+
import path38 from "node:path";
|
|
12504
12562
|
function springProgress(u, bounce) {
|
|
12505
12563
|
const decay = Math.log(100);
|
|
12506
12564
|
if (bounce <= 0) {
|
|
@@ -12571,10 +12629,10 @@ function reportsNoMotion(text) {
|
|
|
12571
12629
|
});
|
|
12572
12630
|
}
|
|
12573
12631
|
function motionTruthFor(setDir) {
|
|
12574
|
-
const file =
|
|
12575
|
-
if (
|
|
12632
|
+
const file = path38.join(setDir, "get_motion_context.json");
|
|
12633
|
+
if (existsSync31(file) && usableEnvelope(file, "get_motion_context").ok) {
|
|
12576
12634
|
try {
|
|
12577
|
-
const text = envelopeTextContent(JSON.parse(
|
|
12635
|
+
const text = envelopeTextContent(JSON.parse(readFileSync29(file, "utf8")));
|
|
12578
12636
|
if (text.trim() === "") return { state: "recorded-empty" };
|
|
12579
12637
|
return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
|
|
12580
12638
|
} catch {
|
|
@@ -12587,21 +12645,21 @@ function motionTruthFor(setDir) {
|
|
|
12587
12645
|
}
|
|
12588
12646
|
}
|
|
12589
12647
|
function motionDisclosure(bundleDir, setDir) {
|
|
12590
|
-
const sheets = ["styles.css", "tokens.css"].map((f) =>
|
|
12591
|
-
const composedRoot =
|
|
12648
|
+
const sheets = ["styles.css", "tokens.css"].map((f) => path38.join(bundleDir, f));
|
|
12649
|
+
const composedRoot = path38.join(bundleDir, "composed");
|
|
12592
12650
|
try {
|
|
12593
12651
|
for (const entry of readdirSync13(composedRoot).sort()) {
|
|
12594
|
-
const dir =
|
|
12652
|
+
const dir = path38.join(composedRoot, entry);
|
|
12595
12653
|
try {
|
|
12596
12654
|
if (!statSync5(dir).isDirectory()) continue;
|
|
12597
12655
|
} catch {
|
|
12598
12656
|
continue;
|
|
12599
12657
|
}
|
|
12600
|
-
sheets.push(
|
|
12658
|
+
sheets.push(path38.join(dir, "styles.css"), path38.join(dir, "tokens.css"));
|
|
12601
12659
|
}
|
|
12602
12660
|
} catch {
|
|
12603
12661
|
}
|
|
12604
|
-
const css = sheets.filter((f) =>
|
|
12662
|
+
const css = sheets.filter((f) => existsSync31(f)).map((f) => readFileSync29(f, "utf8")).join("\n");
|
|
12605
12663
|
if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
|
|
12606
12664
|
return {
|
|
12607
12665
|
present: true,
|
|
@@ -12968,12 +13026,12 @@ var init_components = __esm({
|
|
|
12968
13026
|
|
|
12969
13027
|
// packages/generate/src/codebase/walk.ts
|
|
12970
13028
|
import fs2 from "node:fs";
|
|
12971
|
-
import
|
|
13029
|
+
import path39 from "node:path";
|
|
12972
13030
|
function resolvedPathIsExcluded(real, roots) {
|
|
12973
|
-
if (isNeverRead(
|
|
13031
|
+
if (isNeverRead(path39.basename(real))) return true;
|
|
12974
13032
|
for (const root of roots) {
|
|
12975
|
-
if (real !== root && !real.startsWith(root +
|
|
12976
|
-
for (const segment of
|
|
13033
|
+
if (real !== root && !real.startsWith(root + path39.sep)) continue;
|
|
13034
|
+
for (const segment of path39.relative(root, real).split(path39.sep).slice(0, -1)) {
|
|
12977
13035
|
if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
|
|
12978
13036
|
}
|
|
12979
13037
|
}
|
|
@@ -12987,7 +13045,7 @@ function containedRealpath(abs, roots) {
|
|
|
12987
13045
|
return null;
|
|
12988
13046
|
}
|
|
12989
13047
|
for (const root of roots) {
|
|
12990
|
-
if (real === root || real.startsWith(root +
|
|
13048
|
+
if (real === root || real.startsWith(root + path39.sep)) return real;
|
|
12991
13049
|
}
|
|
12992
13050
|
return null;
|
|
12993
13051
|
}
|
|
@@ -13020,7 +13078,7 @@ function walkRepo(roots, limits, accept) {
|
|
|
13020
13078
|
continue;
|
|
13021
13079
|
}
|
|
13022
13080
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13023
|
-
const abs =
|
|
13081
|
+
const abs = path39.join(frame.dir, entry.name);
|
|
13024
13082
|
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
13025
13083
|
if (isNeverRead(entry.name)) continue;
|
|
13026
13084
|
const real = containedRealpath(abs, realRoots);
|
|
@@ -13108,18 +13166,18 @@ var init_walk = __esm({
|
|
|
13108
13166
|
/^\.netrc$/i
|
|
13109
13167
|
];
|
|
13110
13168
|
isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
|
|
13111
|
-
toRel = (root, abs) =>
|
|
13169
|
+
toRel = (root, abs) => path39.relative(root, abs).split(path39.sep).join(path39.posix.sep);
|
|
13112
13170
|
}
|
|
13113
13171
|
});
|
|
13114
13172
|
|
|
13115
13173
|
// packages/generate/src/codebase/scan.ts
|
|
13116
13174
|
import crypto2 from "node:crypto";
|
|
13117
13175
|
import fs3 from "node:fs";
|
|
13118
|
-
import
|
|
13176
|
+
import path40 from "node:path";
|
|
13119
13177
|
import postcss3 from "postcss";
|
|
13120
13178
|
function scanCodebase(options) {
|
|
13121
13179
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
13122
|
-
const roots = options.roots.map((r) =>
|
|
13180
|
+
const roots = options.roots.map((r) => path40.resolve(r));
|
|
13123
13181
|
const walk2 = walkRepo(
|
|
13124
13182
|
roots,
|
|
13125
13183
|
{
|
|
@@ -13139,7 +13197,7 @@ function scanCodebase(options) {
|
|
|
13139
13197
|
let bytesRead = 0;
|
|
13140
13198
|
let filesRead = 0;
|
|
13141
13199
|
for (const file of walk2.files) {
|
|
13142
|
-
const base =
|
|
13200
|
+
const base = path40.posix.basename(file.rel);
|
|
13143
13201
|
configFiles.add(file.rel);
|
|
13144
13202
|
if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
|
|
13145
13203
|
const text = readTextFile(file.abs);
|
|
@@ -13155,7 +13213,7 @@ function scanCodebase(options) {
|
|
|
13155
13213
|
}
|
|
13156
13214
|
const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
|
|
13157
13215
|
const components = scanComponents(componentFiles);
|
|
13158
|
-
const packages = manifests.filter((m) =>
|
|
13216
|
+
const packages = manifests.filter((m) => path40.posix.basename(m.rel) === "package.json");
|
|
13159
13217
|
const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
|
|
13160
13218
|
const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
|
|
13161
13219
|
const disclosures = buildDisclosures(
|
|
@@ -13198,13 +13256,13 @@ function scanCodebase(options) {
|
|
|
13198
13256
|
},
|
|
13199
13257
|
components: {
|
|
13200
13258
|
entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
|
|
13201
|
-
fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(
|
|
13259
|
+
fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path40.posix.basename(f.rel)))),
|
|
13202
13260
|
directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
|
|
13203
13261
|
exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
|
|
13204
13262
|
classNameStyle,
|
|
13205
13263
|
colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
|
|
13206
13264
|
barrelFiles: componentFiles.filter(
|
|
13207
|
-
(f) => /^index\.[tj]sx?$/.test(
|
|
13265
|
+
(f) => /^index\.[tj]sx?$/.test(path40.posix.basename(f.rel)) && isReExportOnly(f.text)
|
|
13208
13266
|
).length,
|
|
13209
13267
|
refForwarding: {
|
|
13210
13268
|
forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
|
|
@@ -13227,7 +13285,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
|
|
|
13227
13285
|
};
|
|
13228
13286
|
const deps = /* @__PURE__ */ new Map();
|
|
13229
13287
|
for (const manifest of manifests) {
|
|
13230
|
-
if (
|
|
13288
|
+
if (path40.posix.basename(manifest.rel) !== "package.json") continue;
|
|
13231
13289
|
try {
|
|
13232
13290
|
const parsed = JSON.parse(manifest.text);
|
|
13233
13291
|
for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
@@ -13239,7 +13297,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
|
|
|
13239
13297
|
}
|
|
13240
13298
|
}
|
|
13241
13299
|
for (const cfg of configFiles) {
|
|
13242
|
-
const base =
|
|
13300
|
+
const base = path40.posix.basename(cfg);
|
|
13243
13301
|
if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
|
|
13244
13302
|
if (base === "components.json") add("shadcn-style", "file", cfg);
|
|
13245
13303
|
}
|
|
@@ -13297,8 +13355,8 @@ function collectClassNames(cssFiles) {
|
|
|
13297
13355
|
return [...distinct].sort().map(classifyClassName);
|
|
13298
13356
|
}
|
|
13299
13357
|
function classifyDirectoryLayout(rel) {
|
|
13300
|
-
const base =
|
|
13301
|
-
const dir =
|
|
13358
|
+
const base = path40.posix.basename(rel).replace(/\.[^.]+$/, "");
|
|
13359
|
+
const dir = path40.posix.basename(path40.posix.dirname(rel));
|
|
13302
13360
|
if (base === "index") return "component-dir";
|
|
13303
13361
|
if (base === dir) return "component-dir";
|
|
13304
13362
|
return "flat-file";
|
|
@@ -13322,7 +13380,7 @@ function detectFormatting(manifests, componentFiles) {
|
|
|
13322
13380
|
(a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
|
|
13323
13381
|
);
|
|
13324
13382
|
for (const manifest of byDepth) {
|
|
13325
|
-
const base =
|
|
13383
|
+
const base = path40.posix.basename(manifest.rel);
|
|
13326
13384
|
if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
|
|
13327
13385
|
try {
|
|
13328
13386
|
const parsed = JSON.parse(manifest.text);
|
|
@@ -13340,7 +13398,7 @@ function detectFormatting(manifests, componentFiles) {
|
|
|
13340
13398
|
}
|
|
13341
13399
|
}
|
|
13342
13400
|
for (const manifest of byDepth) {
|
|
13343
|
-
if (
|
|
13401
|
+
if (path40.posix.basename(manifest.rel) !== ".editorconfig") continue;
|
|
13344
13402
|
const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
|
|
13345
13403
|
const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
|
|
13346
13404
|
if (style || width) {
|
|
@@ -13411,12 +13469,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
|
|
|
13411
13469
|
return out;
|
|
13412
13470
|
}
|
|
13413
13471
|
function outPathIsGitIgnored(outPath) {
|
|
13414
|
-
const dir =
|
|
13472
|
+
const dir = path40.dirname(outPath);
|
|
13415
13473
|
try {
|
|
13416
|
-
const ignoreFile =
|
|
13474
|
+
const ignoreFile = path40.join(path40.dirname(dir), ".gitignore");
|
|
13417
13475
|
if (!fs3.existsSync(ignoreFile)) return false;
|
|
13418
13476
|
const patterns = fs3.readFileSync(ignoreFile, "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
13419
|
-
const base =
|
|
13477
|
+
const base = path40.basename(dir);
|
|
13420
13478
|
return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
|
|
13421
13479
|
} catch {
|
|
13422
13480
|
return false;
|
|
@@ -13563,8 +13621,8 @@ __export(profile_exports, {
|
|
|
13563
13621
|
PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
|
|
13564
13622
|
runProfile: () => runProfile
|
|
13565
13623
|
});
|
|
13566
|
-
import { closeSync, constants, existsSync as
|
|
13567
|
-
import
|
|
13624
|
+
import { closeSync, constants, existsSync as existsSync32, mkdirSync as mkdirSync10, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync14 } from "node:fs";
|
|
13625
|
+
import path41 from "node:path";
|
|
13568
13626
|
function escapesScanRoot(outPath, scanRoot) {
|
|
13569
13627
|
const resolveExisting = (target) => {
|
|
13570
13628
|
let cursor = target;
|
|
@@ -13572,23 +13630,23 @@ function escapesScanRoot(outPath, scanRoot) {
|
|
|
13572
13630
|
try {
|
|
13573
13631
|
return realpathSync4(cursor);
|
|
13574
13632
|
} catch {
|
|
13575
|
-
const parent =
|
|
13633
|
+
const parent = path41.dirname(cursor);
|
|
13576
13634
|
if (parent === cursor) return cursor;
|
|
13577
13635
|
cursor = parent;
|
|
13578
13636
|
}
|
|
13579
13637
|
}
|
|
13580
13638
|
};
|
|
13581
13639
|
const root = resolveExisting(scanRoot);
|
|
13582
|
-
const dir = resolveExisting(
|
|
13583
|
-
return dir !== root && !dir.startsWith(root +
|
|
13640
|
+
const dir = resolveExisting(path41.dirname(outPath));
|
|
13641
|
+
return dir !== root && !dir.startsWith(root + path41.sep);
|
|
13584
13642
|
}
|
|
13585
13643
|
function runProfile(options) {
|
|
13586
13644
|
if (options.describe) {
|
|
13587
13645
|
printDescription(PROFILE_DESCRIPTION);
|
|
13588
13646
|
return;
|
|
13589
13647
|
}
|
|
13590
|
-
const dir =
|
|
13591
|
-
if (!
|
|
13648
|
+
const dir = path41.resolve(options.dir ?? ".");
|
|
13649
|
+
if (!existsSync32(dir)) {
|
|
13592
13650
|
fail(options, ExitCode.InputValidation, {
|
|
13593
13651
|
error: `no such directory: ${dir}`,
|
|
13594
13652
|
code: "profile_dir_missing",
|
|
@@ -13596,7 +13654,7 @@ function runProfile(options) {
|
|
|
13596
13654
|
});
|
|
13597
13655
|
}
|
|
13598
13656
|
const profile = scanCodebase({ roots: [dir], ...options.now ? { now: options.now } : {} });
|
|
13599
|
-
const outPath =
|
|
13657
|
+
const outPath = path41.resolve(options.out ?? path41.join(dir, "tendril-out", "codebase-profile.json"));
|
|
13600
13658
|
if (!options.dryRun) {
|
|
13601
13659
|
if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
|
|
13602
13660
|
fail(options, ExitCode.InputValidation, {
|
|
@@ -13605,10 +13663,10 @@ function runProfile(options) {
|
|
|
13605
13663
|
remediation: `\`tendril-out\` in that project is a symlink pointing outside it, so writing the profile there could overwrite an unrelated file. Remove the symlink, or choose an explicit destination: \`${tendrilCommand(`profile --dir ${quoteArg(dir)} --out ./codebase-profile.json`)}\`.`
|
|
13606
13664
|
});
|
|
13607
13665
|
}
|
|
13608
|
-
|
|
13666
|
+
mkdirSync10(path41.dirname(outPath), { recursive: true });
|
|
13609
13667
|
const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
|
|
13610
13668
|
try {
|
|
13611
|
-
|
|
13669
|
+
writeFileSync14(handle, `${JSON.stringify(profile, null, 2)}
|
|
13612
13670
|
`, "utf8");
|
|
13613
13671
|
} finally {
|
|
13614
13672
|
closeSync(handle);
|
|
@@ -13670,7 +13728,7 @@ Written to ${outPath}
|
|
|
13670
13728
|
`);
|
|
13671
13729
|
if (!ignored) {
|
|
13672
13730
|
process.stdout.write(
|
|
13673
|
-
` NOTE: ${
|
|
13731
|
+
` NOTE: ${path41.basename(path41.dirname(outPath))}/ is not gitignored here \u2014 add it to .gitignore, or this profile will show up in your next commit.
|
|
13674
13732
|
`
|
|
13675
13733
|
);
|
|
13676
13734
|
}
|
|
@@ -13804,19 +13862,19 @@ var init_activate = __esm({
|
|
|
13804
13862
|
|
|
13805
13863
|
// packages/cli/src/run-presence.ts
|
|
13806
13864
|
import { createHash as createHash8 } from "node:crypto";
|
|
13807
|
-
import { existsSync as
|
|
13808
|
-
import
|
|
13865
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync11, readFileSync as readFileSync30, rmSync as rmSync6, writeFileSync as writeFileSync15 } from "node:fs";
|
|
13866
|
+
import path42 from "node:path";
|
|
13809
13867
|
function presenceDir() {
|
|
13810
|
-
return
|
|
13868
|
+
return path42.join(path42.dirname(sessionPath()), "runs");
|
|
13811
13869
|
}
|
|
13812
13870
|
function presenceFile(componentName) {
|
|
13813
|
-
return
|
|
13871
|
+
return path42.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
|
|
13814
13872
|
}
|
|
13815
13873
|
function readCached(componentName) {
|
|
13816
13874
|
const file = presenceFile(componentName);
|
|
13817
|
-
if (!
|
|
13875
|
+
if (!existsSync33(file)) return void 0;
|
|
13818
13876
|
try {
|
|
13819
|
-
const parsed = JSON.parse(
|
|
13877
|
+
const parsed = JSON.parse(readFileSync30(file, "utf8"));
|
|
13820
13878
|
return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
|
|
13821
13879
|
} catch {
|
|
13822
13880
|
return void 0;
|
|
@@ -13843,8 +13901,8 @@ async function reportRunPresence(componentName, phase) {
|
|
|
13843
13901
|
}
|
|
13844
13902
|
const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
|
|
13845
13903
|
if (typeof started.runId !== "string") return;
|
|
13846
|
-
|
|
13847
|
-
|
|
13904
|
+
mkdirSync11(presenceDir(), { recursive: true });
|
|
13905
|
+
writeFileSync15(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
|
|
13848
13906
|
`, { mode: 384 });
|
|
13849
13907
|
} catch {
|
|
13850
13908
|
}
|
|
@@ -13853,7 +13911,7 @@ async function endRunPresence(componentName) {
|
|
|
13853
13911
|
try {
|
|
13854
13912
|
const session = readStoredSession();
|
|
13855
13913
|
const cached2 = readCached(componentName);
|
|
13856
|
-
|
|
13914
|
+
rmSync6(presenceFile(componentName), { force: true });
|
|
13857
13915
|
if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
|
|
13858
13916
|
await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
|
|
13859
13917
|
} catch {
|
|
@@ -13877,14 +13935,14 @@ __export(compose_exports, {
|
|
|
13877
13935
|
runCompose: () => runCompose
|
|
13878
13936
|
});
|
|
13879
13937
|
import { createHash as createHash9 } from "node:crypto";
|
|
13880
|
-
import { existsSync as
|
|
13881
|
-
import
|
|
13938
|
+
import { existsSync as existsSync34, readFileSync as readFileSync31, readdirSync as readdirSync14 } from "node:fs";
|
|
13939
|
+
import path43 from "node:path";
|
|
13882
13940
|
function compositionPairsFor(hostSet, roots) {
|
|
13883
|
-
const parent =
|
|
13941
|
+
const parent = path43.dirname(hostSet);
|
|
13884
13942
|
const explicitRoots = [...new Set(roots)];
|
|
13885
13943
|
let skippedParent;
|
|
13886
13944
|
let parentRoot = [];
|
|
13887
|
-
if (!explicitRoots.some((r) =>
|
|
13945
|
+
if (!explicitRoots.some((r) => path43.resolve(r) === path43.resolve(parent))) {
|
|
13888
13946
|
let parentEntries = 0;
|
|
13889
13947
|
try {
|
|
13890
13948
|
parentEntries = readdirSync14(parent).length;
|
|
@@ -13925,7 +13983,7 @@ function substitutionPairs(edges, hostSet) {
|
|
|
13925
13983
|
});
|
|
13926
13984
|
}
|
|
13927
13985
|
const pair = pairs.get(key);
|
|
13928
|
-
const poseDisplay = e.pose.reps.map((r) => `${
|
|
13986
|
+
const poseDisplay = e.pose.reps.map((r) => `${path43.basename(r.dir)}:${r.slug}`).join(", ");
|
|
13929
13987
|
pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
|
|
13930
13988
|
for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
|
|
13931
13989
|
}
|
|
@@ -13937,7 +13995,7 @@ function runCompose(flags) {
|
|
|
13937
13995
|
return;
|
|
13938
13996
|
}
|
|
13939
13997
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
13940
|
-
const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) =>
|
|
13998
|
+
const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path43.resolve(base, d)) : [base];
|
|
13941
13999
|
if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
|
|
13942
14000
|
fail(flags, ExitCode.InputValidation, {
|
|
13943
14001
|
error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
|
|
@@ -13946,7 +14004,7 @@ function runCompose(flags) {
|
|
|
13946
14004
|
});
|
|
13947
14005
|
}
|
|
13948
14006
|
if (flags.set !== void 0) {
|
|
13949
|
-
runComposeConfirm(flags,
|
|
14007
|
+
runComposeConfirm(flags, path43.resolve(base, flags.set), roots);
|
|
13950
14008
|
return;
|
|
13951
14009
|
}
|
|
13952
14010
|
const index = buildComposeIndex(roots);
|
|
@@ -13964,7 +14022,7 @@ function runCompose(flags) {
|
|
|
13964
14022
|
}
|
|
13965
14023
|
let lastHost = "";
|
|
13966
14024
|
for (const e of edges) {
|
|
13967
|
-
const host = `${
|
|
14025
|
+
const host = `${path43.basename(e.hostSet)}`;
|
|
13968
14026
|
if (host !== lastHost) {
|
|
13969
14027
|
process.stdout.write(`
|
|
13970
14028
|
${host}
|
|
@@ -13972,7 +14030,7 @@ ${host}
|
|
|
13972
14030
|
lastHost = host;
|
|
13973
14031
|
}
|
|
13974
14032
|
const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
|
|
13975
|
-
const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${
|
|
14033
|
+
const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path43.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
|
|
13976
14034
|
process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
|
|
13977
14035
|
`);
|
|
13978
14036
|
for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
|
|
@@ -13984,14 +14042,14 @@ ${NOTE}
|
|
|
13984
14042
|
});
|
|
13985
14043
|
}
|
|
13986
14044
|
function runComposeConfirm(flags, hostSet, roots) {
|
|
13987
|
-
if (!
|
|
14045
|
+
if (!existsSync34(path43.join(hostSet, "recording-set.json"))) {
|
|
13988
14046
|
fail(flags, ExitCode.InputValidation, {
|
|
13989
14047
|
error: `no recording-set.json in ${hostSet}`,
|
|
13990
14048
|
code: "no-recording-set",
|
|
13991
14049
|
remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
|
|
13992
14050
|
});
|
|
13993
14051
|
}
|
|
13994
|
-
const scanRoots = [.../* @__PURE__ */ new Set([...roots,
|
|
14052
|
+
const scanRoots = [.../* @__PURE__ */ new Set([...roots, path43.dirname(hostSet)])];
|
|
13995
14053
|
const index = buildComposeIndex(scanRoots);
|
|
13996
14054
|
const edges = composeReport(index);
|
|
13997
14055
|
const pairs = substitutionPairs(edges, hostSet);
|
|
@@ -14074,7 +14132,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
|
|
|
14074
14132
|
// full recording-set hash join lands with pin authoring, where
|
|
14075
14133
|
// task configs exist.)
|
|
14076
14134
|
manifestSha256: Object.fromEntries(
|
|
14077
|
-
p.partnerDirs.map((d) => [
|
|
14135
|
+
p.partnerDirs.map((d) => [path43.relative(hostSet, d), createHash9("sha256").update(readFileSync31(path43.join(d, "recording-set.json"))).digest("hex")])
|
|
14078
14136
|
)
|
|
14079
14137
|
},
|
|
14080
14138
|
// Schema shape ONLY (review: the display-side poseDisplay field
|
|
@@ -14154,10 +14212,10 @@ __export(record_exports, {
|
|
|
14154
14212
|
runRecordPlan: () => runRecordPlan,
|
|
14155
14213
|
runRecordStatus: () => runRecordStatus
|
|
14156
14214
|
});
|
|
14157
|
-
import { existsSync as
|
|
14215
|
+
import { existsSync as existsSync35, mkdtempSync as mkdtempSync2, readFileSync as readFileSync32, readdirSync as readdirSync15 } from "node:fs";
|
|
14158
14216
|
import os7 from "node:os";
|
|
14159
|
-
import
|
|
14160
|
-
import { writeFileSync as
|
|
14217
|
+
import path44 from "node:path";
|
|
14218
|
+
import { writeFileSync as writeFileSync16 } from "node:fs";
|
|
14161
14219
|
function recordsInteractionState(reports) {
|
|
14162
14220
|
const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
|
|
14163
14221
|
return reports.some((r) => evident(r.axis) || r.domain.some(evident));
|
|
@@ -14179,7 +14237,7 @@ function interactionDisclosure(component, reports) {
|
|
|
14179
14237
|
};
|
|
14180
14238
|
}
|
|
14181
14239
|
function symbolsFromMetadataEnvelope(file, sourceFrame) {
|
|
14182
|
-
const env = JSON.parse(
|
|
14240
|
+
const env = JSON.parse(readFileSync32(file, "utf8"));
|
|
14183
14241
|
const text = env.content.map((c) => c.text ?? "").join("\n");
|
|
14184
14242
|
const symbols = [];
|
|
14185
14243
|
const walk2 = (node, ancestor) => {
|
|
@@ -14237,8 +14295,8 @@ function runRecordPlan(opts) {
|
|
|
14237
14295
|
if (rawFile !== void 0) {
|
|
14238
14296
|
try {
|
|
14239
14297
|
const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
|
|
14240
|
-
const tmp =
|
|
14241
|
-
|
|
14298
|
+
const tmp = path44.join(mkdtempSync2(path44.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
|
|
14299
|
+
writeFileSync16(tmp, JSON.stringify(envelope));
|
|
14242
14300
|
metadataEntries.push({ file: tmp });
|
|
14243
14301
|
} catch (err) {
|
|
14244
14302
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14259,7 +14317,7 @@ function runRecordPlan(opts) {
|
|
|
14259
14317
|
let metadataTruncated = false;
|
|
14260
14318
|
for (const { file, frame } of metadataEntries) {
|
|
14261
14319
|
try {
|
|
14262
|
-
const parsed = symbolsFromMetadataEnvelope(
|
|
14320
|
+
const parsed = symbolsFromMetadataEnvelope(path44.resolve(file), frame);
|
|
14263
14321
|
symbols.push(...parsed.symbols);
|
|
14264
14322
|
if (parsed.truncated) metadataTruncated = true;
|
|
14265
14323
|
} catch (err) {
|
|
@@ -14293,7 +14351,7 @@ function runRecordPlan(opts) {
|
|
|
14293
14351
|
if (symbols.length === 0) {
|
|
14294
14352
|
const leads = metadataEntries.flatMap(({ file }) => {
|
|
14295
14353
|
try {
|
|
14296
|
-
const env = JSON.parse(
|
|
14354
|
+
const env = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
|
|
14297
14355
|
return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
|
|
14298
14356
|
} catch {
|
|
14299
14357
|
return [];
|
|
@@ -14386,7 +14444,7 @@ function runRecordPlan(opts) {
|
|
|
14386
14444
|
// a CLI flag and the MCP surface has no parameter for it.
|
|
14387
14445
|
decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
|
|
14388
14446
|
text: "Record a reduced pose set (anchor + one-factor sweeps + conflict crosses) instead of the full variant matrix. This buys calls with coverage \u2014 sampling is blind to multi-axis interactions and the report discloses the poses it skipped \u2014 so only the user may accept that trade. There is no tool parameter for it; the user runs it themselves in their own terminal, before any pose is recorded.",
|
|
14389
|
-
userRuns: [`rm ${quoteArg(
|
|
14447
|
+
userRuns: [`rm ${quoteArg(path44.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
|
|
14390
14448
|
},
|
|
14391
14449
|
{
|
|
14392
14450
|
id: "larger-allowance",
|
|
@@ -14585,7 +14643,7 @@ function runRecordNext(opts) {
|
|
|
14585
14643
|
const progress = payload["progress"];
|
|
14586
14644
|
process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
|
|
14587
14645
|
\u2192 ${payload["note"]}
|
|
14588
|
-
\u2192 then: ${tendrilCommand(`record ingest --set ${
|
|
14646
|
+
\u2192 then: ${tendrilCommand(`record ingest --set ${path44.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
|
|
14589
14647
|
`);
|
|
14590
14648
|
});
|
|
14591
14649
|
}
|
|
@@ -14659,7 +14717,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14659
14717
|
const skipped = [];
|
|
14660
14718
|
const failed = [];
|
|
14661
14719
|
for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
|
|
14662
|
-
if (
|
|
14720
|
+
if (existsSync35(path44.join(setDir, rep, name))) {
|
|
14663
14721
|
skipped.push(name);
|
|
14664
14722
|
continue;
|
|
14665
14723
|
}
|
|
@@ -14681,16 +14739,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14681
14739
|
}
|
|
14682
14740
|
function rawEnvelopeFromFile(file, parts) {
|
|
14683
14741
|
if (parts) {
|
|
14684
|
-
const blocks = JSON.parse(
|
|
14742
|
+
const blocks = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
|
|
14685
14743
|
if (!Array.isArray(blocks) || blocks.length === 0 || blocks.some((p) => typeof p !== "string")) throw new Error("parts file must be a non-empty JSON array of strings");
|
|
14686
14744
|
return { content: blocks.map((text) => ({ type: "text", text })) };
|
|
14687
14745
|
}
|
|
14688
|
-
return { content: [{ type: "text", text:
|
|
14746
|
+
return { content: [{ type: "text", text: readFileSync32(path44.resolve(file), "utf8") }] };
|
|
14689
14747
|
}
|
|
14690
14748
|
async function runRecordIngest(opts) {
|
|
14691
14749
|
let payload;
|
|
14692
14750
|
try {
|
|
14693
|
-
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(
|
|
14751
|
+
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync32(path44.resolve(opts.file), "utf8"));
|
|
14694
14752
|
} catch (err) {
|
|
14695
14753
|
fail(opts, ExitCode.InputValidation, {
|
|
14696
14754
|
error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -14702,7 +14760,7 @@ async function runRecordIngest(opts) {
|
|
|
14702
14760
|
fail(opts, ExitCode.InputValidation, {
|
|
14703
14761
|
error: "--raw is for text tool responses; screenshots are binary",
|
|
14704
14762
|
code: "envelope-invalid",
|
|
14705
|
-
remediation: `Use \`${tendrilCommand(`record fetch --set ${
|
|
14763
|
+
remediation: `Use \`${tendrilCommand(`record fetch --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
|
|
14706
14764
|
});
|
|
14707
14765
|
}
|
|
14708
14766
|
if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
|
|
@@ -14722,7 +14780,7 @@ async function runRecordIngest(opts) {
|
|
|
14722
14780
|
remediation: REINGEST_GUIDANCE
|
|
14723
14781
|
});
|
|
14724
14782
|
}
|
|
14725
|
-
|
|
14783
|
+
writeFileSync16(path44.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
|
|
14726
14784
|
`);
|
|
14727
14785
|
emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14728
14786
|
process.stdout.write("set-level get_variable_defs ingested\n");
|
|
@@ -14746,7 +14804,7 @@ async function runRecordIngest(opts) {
|
|
|
14746
14804
|
remediation: REINGEST_GUIDANCE
|
|
14747
14805
|
});
|
|
14748
14806
|
}
|
|
14749
|
-
|
|
14807
|
+
writeFileSync16(path44.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
|
|
14750
14808
|
`);
|
|
14751
14809
|
emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14752
14810
|
process.stdout.write("set-level get_motion_context ingested\n");
|
|
@@ -14762,7 +14820,7 @@ async function runRecordIngest(opts) {
|
|
|
14762
14820
|
if (assets !== void 0) {
|
|
14763
14821
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14764
14822
|
`);
|
|
14765
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${
|
|
14823
|
+
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
|
|
14766
14824
|
`);
|
|
14767
14825
|
}
|
|
14768
14826
|
});
|
|
@@ -14835,14 +14893,14 @@ async function runRecordIngestRep(opts) {
|
|
|
14835
14893
|
if (assets !== void 0) {
|
|
14836
14894
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14837
14895
|
`);
|
|
14838
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${
|
|
14896
|
+
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
|
|
14839
14897
|
`);
|
|
14840
14898
|
}
|
|
14841
14899
|
});
|
|
14842
14900
|
}
|
|
14843
14901
|
function runRecordAsset(opts) {
|
|
14844
14902
|
if (opts.dir !== void 0) {
|
|
14845
|
-
const dir =
|
|
14903
|
+
const dir = path44.resolve(opts.dir);
|
|
14846
14904
|
const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
|
|
14847
14905
|
if (names.length === 0) {
|
|
14848
14906
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14854,7 +14912,7 @@ function runRecordAsset(opts) {
|
|
|
14854
14912
|
const ingested = [];
|
|
14855
14913
|
try {
|
|
14856
14914
|
for (const name of names) {
|
|
14857
|
-
ingestAsset(opts.setDir, opts.rep, name,
|
|
14915
|
+
ingestAsset(opts.setDir, opts.rep, name, readFileSync32(path44.join(dir, name)));
|
|
14858
14916
|
ingested.push(name);
|
|
14859
14917
|
}
|
|
14860
14918
|
} catch (err) {
|
|
@@ -14874,11 +14932,11 @@ function runRecordAsset(opts) {
|
|
|
14874
14932
|
fail(opts, ExitCode.InputValidation, {
|
|
14875
14933
|
error: "pass --name and --file for a single asset, or --dir for a batch",
|
|
14876
14934
|
code: "asset-rejected",
|
|
14877
|
-
remediation: tendrilCommand(`record asset --set ${
|
|
14935
|
+
remediation: tendrilCommand(`record asset --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
|
|
14878
14936
|
});
|
|
14879
14937
|
}
|
|
14880
14938
|
try {
|
|
14881
|
-
ingestAsset(opts.setDir, opts.rep, opts.name,
|
|
14939
|
+
ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync32(path44.resolve(opts.file)));
|
|
14882
14940
|
emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
|
|
14883
14941
|
process.stdout.write(`ingested ${opts.rep}/${opts.name}
|
|
14884
14942
|
`);
|
|
@@ -14895,8 +14953,8 @@ function runRecordStatus(opts) {
|
|
|
14895
14953
|
const status = sessionStatus(opts.setDir);
|
|
14896
14954
|
const composition = (() => {
|
|
14897
14955
|
try {
|
|
14898
|
-
const setDir =
|
|
14899
|
-
const { open, standing, invalid } = compositionPairsFor(setDir, [
|
|
14956
|
+
const setDir = path44.resolve(opts.setDir);
|
|
14957
|
+
const { open, standing, invalid } = compositionPairsFor(setDir, [path44.dirname(setDir)]);
|
|
14900
14958
|
return {
|
|
14901
14959
|
openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
|
|
14902
14960
|
confirmed: standing.filter((s) => s.status === "confirmed").length,
|
|
@@ -14917,7 +14975,7 @@ function runRecordStatus(opts) {
|
|
|
14917
14975
|
}
|
|
14918
14976
|
}
|
|
14919
14977
|
process.stdout.write(
|
|
14920
|
-
status.motion.recorded ? motionTruthFor(
|
|
14978
|
+
status.motion.recorded ? motionTruthFor(path44.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
|
|
14921
14979
|
` : "MOTION set-level motion context not yet recorded \u2014 `record next` names the call once the reps and token map are done\n" : "MOTION never asked \u2014 this set predates the motion-capture obligation (fresh plans record it; briefs prescribe default motion doctrine only)\n"
|
|
14922
14980
|
);
|
|
14923
14981
|
if ("unavailable" in composition) {
|
|
@@ -14925,7 +14983,7 @@ function runRecordStatus(opts) {
|
|
|
14925
14983
|
`);
|
|
14926
14984
|
} else if (composition.openPairs.length > 0) {
|
|
14927
14985
|
process.stdout.write(
|
|
14928
|
-
`COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${
|
|
14986
|
+
`COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path44.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
|
|
14929
14987
|
`
|
|
14930
14988
|
);
|
|
14931
14989
|
} else if (composition.confirmed > 0) {
|
|
@@ -14965,7 +15023,7 @@ function narrowedRoles(derived, override) {
|
|
|
14965
15023
|
function rolesFromFile(opts, file, derived) {
|
|
14966
15024
|
let json;
|
|
14967
15025
|
try {
|
|
14968
|
-
json = JSON.parse(
|
|
15026
|
+
json = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
|
|
14969
15027
|
} catch (err) {
|
|
14970
15028
|
fail(opts, ExitCode.InputValidation, {
|
|
14971
15029
|
error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
@@ -15003,11 +15061,11 @@ function rolesFromFile(opts, file, derived) {
|
|
|
15003
15061
|
};
|
|
15004
15062
|
}
|
|
15005
15063
|
function runRecordFinish(opts) {
|
|
15006
|
-
if (!
|
|
15064
|
+
if (!existsSync35(path44.join(opts.setDir, "recording-set.json"))) {
|
|
15007
15065
|
fail(opts, ExitCode.InputValidation, {
|
|
15008
15066
|
error: `no recording-set.json in ${opts.setDir}`,
|
|
15009
15067
|
code: "no-recording-set",
|
|
15010
|
-
remediation: `Run \`${tendrilCommand(`record plan --set ${
|
|
15068
|
+
remediation: `Run \`${tendrilCommand(`record plan --set ${path44.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
|
|
15011
15069
|
});
|
|
15012
15070
|
}
|
|
15013
15071
|
const { manifest, raw } = readManifestFile(opts.setDir);
|
|
@@ -15035,17 +15093,17 @@ function runRecordFinish(opts) {
|
|
|
15035
15093
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
15036
15094
|
error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
|
|
15037
15095
|
code: "roles-confirmation-not-interactive",
|
|
15038
|
-
remediation: `A human runs \`${tendrilCommand(`record finish --set ${
|
|
15096
|
+
remediation: `A human runs \`${tendrilCommand(`record finish --set ${path44.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
|
|
15039
15097
|
});
|
|
15040
15098
|
}
|
|
15041
15099
|
const merged = { ...raw, roles };
|
|
15042
|
-
const { issues } = validateRecordingSet(merged, (rel) =>
|
|
15100
|
+
const { issues } = validateRecordingSet(merged, (rel) => existsSync35(path44.join(opts.setDir, rel)));
|
|
15043
15101
|
const errors = issues.filter((i) => i.severity === "error");
|
|
15044
15102
|
if (errors.length > 0) {
|
|
15045
15103
|
fail(opts, ExitCode.InputValidation, {
|
|
15046
15104
|
error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
|
|
15047
15105
|
code: "recording-set-invalid",
|
|
15048
|
-
remediation: `Fix the set (\`${tendrilCommand(`record status --set ${
|
|
15106
|
+
remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path44.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
|
|
15049
15107
|
});
|
|
15050
15108
|
}
|
|
15051
15109
|
for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
|
|
@@ -15096,9 +15154,9 @@ var init_record = __esm({
|
|
|
15096
15154
|
});
|
|
15097
15155
|
|
|
15098
15156
|
// packages/cli/src/font-guidance.ts
|
|
15099
|
-
import
|
|
15157
|
+
import path45 from "node:path";
|
|
15100
15158
|
function fontsUnprovenRemediation(setDir) {
|
|
15101
|
-
const set = setDir === void 0 ? void 0 :
|
|
15159
|
+
const set = setDir === void 0 ? void 0 : path45.resolve(setDir);
|
|
15102
15160
|
if (set !== void 0) {
|
|
15103
15161
|
try {
|
|
15104
15162
|
const needs = recordedFontNeeds(set);
|
|
@@ -15173,8 +15231,8 @@ __export(fonts_exports, {
|
|
|
15173
15231
|
runFontsResolveSet: () => runFontsResolveSet,
|
|
15174
15232
|
runFontsStatus: () => runFontsStatus
|
|
15175
15233
|
});
|
|
15176
|
-
import { existsSync as
|
|
15177
|
-
import
|
|
15234
|
+
import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
|
|
15235
|
+
import path46 from "node:path";
|
|
15178
15236
|
async function runFontsResolve(opts) {
|
|
15179
15237
|
const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
|
|
15180
15238
|
const queryRefusal = systemFaceRefusal(opts.family.trim());
|
|
@@ -15195,7 +15253,7 @@ async function runFontsResolve(opts) {
|
|
|
15195
15253
|
}
|
|
15196
15254
|
}
|
|
15197
15255
|
async function runFontsResolveSet(opts) {
|
|
15198
|
-
const setDir =
|
|
15256
|
+
const setDir = path46.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
|
|
15199
15257
|
let needs = [];
|
|
15200
15258
|
try {
|
|
15201
15259
|
needs = recordedFontNeeds(setDir);
|
|
@@ -15290,16 +15348,16 @@ async function runFontsResolveSet(opts) {
|
|
|
15290
15348
|
}
|
|
15291
15349
|
}
|
|
15292
15350
|
function runFontsStatus(opts) {
|
|
15293
|
-
const manifestPath2 =
|
|
15294
|
-
if (!
|
|
15351
|
+
const manifestPath2 = path46.join(opts.cacheDir, "manifest.json");
|
|
15352
|
+
if (!existsSync36(manifestPath2)) {
|
|
15295
15353
|
fail(opts, ExitCode.FontsUnproven, {
|
|
15296
15354
|
error: `no font cache at ${opts.cacheDir}`,
|
|
15297
15355
|
code: "fonts-unresolved",
|
|
15298
15356
|
remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
|
|
15299
15357
|
});
|
|
15300
15358
|
}
|
|
15301
|
-
const faces = JSON.parse(
|
|
15302
|
-
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(
|
|
15359
|
+
const faces = JSON.parse(readFileSync33(manifestPath2, "utf8"));
|
|
15360
|
+
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path46.resolve(opts.lock), opts.cacheDir) : null;
|
|
15303
15361
|
emitData(opts, { faces, lockVerdicts }, () => {
|
|
15304
15362
|
for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
|
|
15305
15363
|
`);
|
|
@@ -15343,13 +15401,13 @@ function familyMismatch(family, declared) {
|
|
|
15343
15401
|
}
|
|
15344
15402
|
function runFontsAdd(opts) {
|
|
15345
15403
|
if (opts.set !== void 0) {
|
|
15346
|
-
const declared = taskFontFamilies(
|
|
15404
|
+
const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
|
|
15347
15405
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15348
15406
|
if (mismatch !== void 0) {
|
|
15349
15407
|
fail(opts, ExitCode.InputValidation, {
|
|
15350
15408
|
error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. Adding it under this name would cache a face the mount never matches, and scoring would keep refusing for the family that is still missing.`,
|
|
15351
15409
|
code: "font-family-not-declared",
|
|
15352
|
-
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${
|
|
15410
|
+
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path46.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
|
|
15353
15411
|
});
|
|
15354
15412
|
}
|
|
15355
15413
|
} else {
|
|
@@ -15408,13 +15466,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
|
|
|
15408
15466
|
}
|
|
15409
15467
|
function runFontsAddSystem(opts) {
|
|
15410
15468
|
if (opts.set !== void 0) {
|
|
15411
|
-
const declared = taskFontFamilies(
|
|
15469
|
+
const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
|
|
15412
15470
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15413
15471
|
if (mismatch !== void 0) {
|
|
15414
15472
|
fail(opts, ExitCode.InputValidation, {
|
|
15415
15473
|
error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. A face cached under a name the mount never matches leaves scoring refusing for the family that is still missing.`,
|
|
15416
15474
|
code: "font-family-not-declared",
|
|
15417
|
-
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(
|
|
15475
|
+
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path46.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
|
|
15418
15476
|
});
|
|
15419
15477
|
}
|
|
15420
15478
|
} else {
|
|
@@ -15464,12 +15522,12 @@ var init_fonts = __esm({
|
|
|
15464
15522
|
});
|
|
15465
15523
|
|
|
15466
15524
|
// packages/cli/src/profile-input.ts
|
|
15467
|
-
import { existsSync as
|
|
15468
|
-
import
|
|
15525
|
+
import { existsSync as existsSync37, readFileSync as readFileSync34 } from "node:fs";
|
|
15526
|
+
import path47 from "node:path";
|
|
15469
15527
|
function loadCodebaseProfile(flags, profilePath) {
|
|
15470
15528
|
if (profilePath === void 0) return null;
|
|
15471
|
-
const abs =
|
|
15472
|
-
if (!
|
|
15529
|
+
const abs = path47.resolve(profilePath);
|
|
15530
|
+
if (!existsSync37(abs)) {
|
|
15473
15531
|
fail(flags, ExitCode.InputValidation, {
|
|
15474
15532
|
error: `no profile at ${abs}`,
|
|
15475
15533
|
code: "profile_missing",
|
|
@@ -15477,7 +15535,7 @@ function loadCodebaseProfile(flags, profilePath) {
|
|
|
15477
15535
|
});
|
|
15478
15536
|
}
|
|
15479
15537
|
try {
|
|
15480
|
-
return readCodebaseProfile(
|
|
15538
|
+
return readCodebaseProfile(readFileSync34(abs, "utf8"));
|
|
15481
15539
|
} catch (error) {
|
|
15482
15540
|
fail(flags, ExitCode.InputValidation, {
|
|
15483
15541
|
error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -15519,8 +15577,8 @@ __export(verify_exports, {
|
|
|
15519
15577
|
runVerify: () => runVerify,
|
|
15520
15578
|
verdictCaveatsFor: () => verdictCaveatsFor
|
|
15521
15579
|
});
|
|
15522
|
-
import { existsSync as
|
|
15523
|
-
import
|
|
15580
|
+
import { existsSync as existsSync38, readFileSync as readFileSync35, rmSync as rmSync7, writeFileSync as writeFileSync17 } from "node:fs";
|
|
15581
|
+
import path48 from "node:path";
|
|
15524
15582
|
function interactionCoverage(behaviors) {
|
|
15525
15583
|
const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
15526
15584
|
const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
|
|
@@ -15761,12 +15819,12 @@ function compositionReport(input) {
|
|
|
15761
15819
|
function eyeCheck(bundleDir) {
|
|
15762
15820
|
return {
|
|
15763
15821
|
command: tendrilCommand(`inspect "${bundleDir}"`),
|
|
15764
|
-
sheetPath:
|
|
15822
|
+
sheetPath: path48.join(bundleDir, "verify-evidence", "inspect.html"),
|
|
15765
15823
|
note: "magnified recorded-vs-rendered crops of every small node; scores cannot see shape. The command WRITES/refreshes the sheet at sheetPath \u2014 re-run it after every verify; an existing sheet may show stale crops."
|
|
15766
15824
|
};
|
|
15767
15825
|
}
|
|
15768
15826
|
function evidenceArtifacts(evidenceDir, reps) {
|
|
15769
|
-
const named = (name) =>
|
|
15827
|
+
const named = (name) => existsSync38(path48.join(evidenceDir, name)) ? name : null;
|
|
15770
15828
|
return {
|
|
15771
15829
|
legend: named("diff-legend.txt"),
|
|
15772
15830
|
configs: reps.map((rep) => {
|
|
@@ -15814,7 +15872,7 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
15814
15872
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15815
15873
|
const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
15816
15874
|
const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
|
|
15817
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15875
|
+
const registry = Object.values(TASKS).find((t) => path48.resolve(t.set) === path48.resolve(setDir));
|
|
15818
15876
|
const authored = (() => {
|
|
15819
15877
|
if (registry !== void 0) return void 0;
|
|
15820
15878
|
try {
|
|
@@ -15875,19 +15933,19 @@ function verdictCaveatsFor(input) {
|
|
|
15875
15933
|
async function runVerify(opts) {
|
|
15876
15934
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15877
15935
|
let recordingSetDrift;
|
|
15878
|
-
const setOverride = opts.set !== void 0 ?
|
|
15879
|
-
opts = { ...opts, bundleDir:
|
|
15880
|
-
if (!
|
|
15936
|
+
const setOverride = opts.set !== void 0 ? path48.resolve(callerCwd, opts.set) : void 0;
|
|
15937
|
+
opts = { ...opts, bundleDir: path48.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
|
|
15938
|
+
if (!existsSync38(opts.bundleDir)) {
|
|
15881
15939
|
fail(opts, ExitCode.InputValidation, {
|
|
15882
15940
|
error: `bundle directory not found: ${opts.bundleDir}`,
|
|
15883
15941
|
code: "bundle-missing",
|
|
15884
15942
|
remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
|
|
15885
15943
|
});
|
|
15886
15944
|
}
|
|
15887
|
-
const manifestPath2 =
|
|
15945
|
+
const manifestPath2 = path48.join(opts.bundleDir, "component.json");
|
|
15888
15946
|
let manifest;
|
|
15889
|
-
if (
|
|
15890
|
-
const { manifest: parsed, issues } = readBundleManifest(
|
|
15947
|
+
if (existsSync38(manifestPath2)) {
|
|
15948
|
+
const { manifest: parsed, issues } = readBundleManifest(readFileSync35(manifestPath2, "utf8"));
|
|
15891
15949
|
if (issues.length > 0) {
|
|
15892
15950
|
fail(opts, ExitCode.InputValidation, {
|
|
15893
15951
|
error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15918,21 +15976,21 @@ async function runVerify(opts) {
|
|
|
15918
15976
|
task = registry;
|
|
15919
15977
|
} else if (manifest !== void 0) {
|
|
15920
15978
|
const resolveSetDir = (p) => {
|
|
15921
|
-
if (
|
|
15922
|
-
const fromRepo =
|
|
15923
|
-
if (
|
|
15924
|
-
return
|
|
15979
|
+
if (path48.isAbsolute(p)) return p;
|
|
15980
|
+
const fromRepo = path48.resolve(REPO_ROOT, p);
|
|
15981
|
+
if (existsSync38(fromRepo)) return fromRepo;
|
|
15982
|
+
return path48.resolve(callerCwd, p);
|
|
15925
15983
|
};
|
|
15926
15984
|
const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
|
|
15927
|
-
if (!
|
|
15985
|
+
if (!existsSync38(path48.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path48.resolve(t.set) === path48.resolve(setDir))) {
|
|
15928
15986
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
15929
15987
|
error: `recording set not found or unmanifested: ${setDir}`,
|
|
15930
15988
|
code: "recording-set-missing",
|
|
15931
15989
|
remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
|
|
15932
15990
|
});
|
|
15933
15991
|
}
|
|
15934
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15935
|
-
if (registry !== void 0 && !
|
|
15992
|
+
const registry = Object.values(TASKS).find((t) => path48.resolve(t.set) === path48.resolve(setDir));
|
|
15993
|
+
if (registry !== void 0 && !existsSync38(path48.join(setDir, "recording-set.json"))) {
|
|
15936
15994
|
const recordedSlugs = registry.configs.map((c) => c.rep);
|
|
15937
15995
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15938
15996
|
unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
@@ -15964,9 +16022,9 @@ async function runVerify(opts) {
|
|
|
15964
16022
|
recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
|
|
15965
16023
|
}
|
|
15966
16024
|
for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
|
|
15967
|
-
const p =
|
|
15968
|
-
if (!
|
|
15969
|
-
const issues = checkBundleSourceFile(name, new Uint8Array(
|
|
16025
|
+
const p = path48.join(opts.bundleDir, name);
|
|
16026
|
+
if (!existsSync38(p)) continue;
|
|
16027
|
+
const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync35(p)));
|
|
15970
16028
|
if (issues.length > 0) {
|
|
15971
16029
|
fail(opts, ExitCode.InputValidation, {
|
|
15972
16030
|
error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -16004,7 +16062,7 @@ async function runVerify(opts) {
|
|
|
16004
16062
|
warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
|
|
16005
16063
|
}
|
|
16006
16064
|
const missing = task.configs.filter(
|
|
16007
|
-
(c) => !
|
|
16065
|
+
(c) => !existsSync38(path48.join(task.set, c.rep, "get_screenshot.json")) || !existsSync38(path48.join(task.set, c.rep, "get_metadata.json"))
|
|
16008
16066
|
);
|
|
16009
16067
|
if (missing.length > 0) {
|
|
16010
16068
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -16014,8 +16072,8 @@ async function runVerify(opts) {
|
|
|
16014
16072
|
});
|
|
16015
16073
|
}
|
|
16016
16074
|
const bar = BARS2[opts.bar];
|
|
16017
|
-
const evidenceDir =
|
|
16018
|
-
|
|
16075
|
+
const evidenceDir = path48.join(opts.bundleDir, "verify-evidence");
|
|
16076
|
+
rmSync7(evidenceDir, { recursive: true, force: true });
|
|
16019
16077
|
const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
16020
16078
|
const quality = await checkBundleQuality(
|
|
16021
16079
|
opts.bundleDir,
|
|
@@ -16032,7 +16090,7 @@ async function runVerify(opts) {
|
|
|
16032
16090
|
// ASKED, never "follows every convention".
|
|
16033
16091
|
loadCodebaseProfile(opts, opts.profile) ?? void 0
|
|
16034
16092
|
);
|
|
16035
|
-
const bundleCss = ["tokens.css", "styles.css"].map((f) =>
|
|
16093
|
+
const bundleCss = ["tokens.css", "styles.css"].map((f) => path48.join(opts.bundleDir, f)).filter((f) => existsSync38(f)).map((f) => readFileSync35(f, "utf8")).join("\n");
|
|
16036
16094
|
const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
|
|
16037
16095
|
const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
|
|
16038
16096
|
...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
|
|
@@ -16053,10 +16111,10 @@ async function runVerify(opts) {
|
|
|
16053
16111
|
warn(opts, `compositions extension REJECTED (${crossComposition.malformed}) \u2014 the cross-bundle backstop did NOT run over it; repair the manifest entry and re-verify. This is an instrument failure, not a clean bill.`);
|
|
16054
16112
|
}
|
|
16055
16113
|
const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16056
|
-
const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ?
|
|
16114
|
+
const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path48.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
|
|
16057
16115
|
const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
|
|
16058
16116
|
const renderExpectations = verifyPins.pins.map((pin) => ({
|
|
16059
|
-
modulePath:
|
|
16117
|
+
modulePath: path48.join(composedModuleDir(pin.partnerName), pin.entryComponent),
|
|
16060
16118
|
component: pin.entryComponent,
|
|
16061
16119
|
stampName: `${pin.pairKey}:${pin.entryComponent}`,
|
|
16062
16120
|
hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
|
|
@@ -16349,7 +16407,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16349
16407
|
}
|
|
16350
16408
|
process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
|
|
16351
16409
|
`);
|
|
16352
|
-
const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) =>
|
|
16410
|
+
const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path48.join(opts.bundleDir, f)).filter((f) => existsSync38(f)).map((f) => readFileSync35(f, "utf8")).join("\n")));
|
|
16353
16411
|
if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
|
|
16354
16412
|
process.stdout.write(`fonts: scored with Tendril-cache faces \u2014 a consuming app must provision the same families (the bundle ships fonts.css when faces are shippable; sha-pinned list in component.json requiredFonts)
|
|
16355
16413
|
`);
|
|
@@ -16404,7 +16462,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16404
16462
|
persistReport(opts, report, evidenceDir);
|
|
16405
16463
|
}
|
|
16406
16464
|
function persistReport(opts, report, evidenceDir) {
|
|
16407
|
-
if (!
|
|
16465
|
+
if (!existsSync38(evidenceDir)) return;
|
|
16408
16466
|
const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
|
|
16409
16467
|
const withExit = {
|
|
16410
16468
|
...report,
|
|
@@ -16412,9 +16470,9 @@ function persistReport(opts, report, evidenceDir) {
|
|
|
16412
16470
|
...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
|
|
16413
16471
|
};
|
|
16414
16472
|
try {
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
`${JSON.stringify(verifyReportForTransport(withExit,
|
|
16473
|
+
writeFileSync17(
|
|
16474
|
+
path48.join(evidenceDir, VERIFY_REPORT_FILENAME),
|
|
16475
|
+
`${JSON.stringify(verifyReportForTransport(withExit, path48.basename(opts.bundleDir)), null, 2)}
|
|
16418
16476
|
`
|
|
16419
16477
|
);
|
|
16420
16478
|
} catch (e) {
|
|
@@ -16464,11 +16522,11 @@ __export(engine_exports, {
|
|
|
16464
16522
|
runEngineBrief: () => runEngineBrief,
|
|
16465
16523
|
runEngineScore: () => runEngineScore
|
|
16466
16524
|
});
|
|
16467
|
-
import { appendFileSync, existsSync as
|
|
16468
|
-
import
|
|
16525
|
+
import { appendFileSync, existsSync as existsSync39, mkdirSync as mkdirSync12, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
|
|
16526
|
+
import path49 from "node:path";
|
|
16469
16527
|
function resolveEngineTask(opts, callerCwd) {
|
|
16470
|
-
const asPath =
|
|
16471
|
-
const isSet =
|
|
16528
|
+
const asPath = path49.resolve(callerCwd, opts.taskOrSet);
|
|
16529
|
+
const isSet = existsSync39(path49.join(asPath, "recording-set.json"));
|
|
16472
16530
|
const registry = TASKS[opts.taskOrSet];
|
|
16473
16531
|
if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
|
|
16474
16532
|
if (isSet) {
|
|
@@ -16477,7 +16535,7 @@ function resolveEngineTask(opts, callerCwd) {
|
|
|
16477
16535
|
for (const d of authored.disclosures) warn(opts, d);
|
|
16478
16536
|
return {
|
|
16479
16537
|
task: authored.task,
|
|
16480
|
-
name:
|
|
16538
|
+
name: path49.basename(asPath),
|
|
16481
16539
|
ref: asPath,
|
|
16482
16540
|
disclosures: authored.disclosures,
|
|
16483
16541
|
interactionEvidence: authored.api.interactionEvidence,
|
|
@@ -16506,9 +16564,9 @@ function runEngineBrief(opts) {
|
|
|
16506
16564
|
const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
|
|
16507
16565
|
void reportRunPresence(name, "implementing");
|
|
16508
16566
|
const bar = BARS3[opts.bar];
|
|
16509
|
-
if (
|
|
16567
|
+
if (existsSync39(path49.join(task.set, "recording-set.json"))) {
|
|
16510
16568
|
try {
|
|
16511
|
-
const { open, skippedParent } = compositionPairsFor(
|
|
16569
|
+
const { open, skippedParent } = compositionPairsFor(path49.resolve(task.set), [opts.library !== void 0 ? path49.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16512
16570
|
if (skippedParent !== void 0) {
|
|
16513
16571
|
disclosures.push(
|
|
16514
16572
|
`COMPOSITION DISCOVERY PARTIAL: the set's parent directory (${skippedParent.dir}) holds ${String(skippedParent.entries)} entries and was not scanned as a recordings library \u2014 sibling sets there are invisible to pairing. Pass --library <dir> to scan a specific library deliberately.`
|
|
@@ -16517,7 +16575,7 @@ function runEngineBrief(opts) {
|
|
|
16517
16575
|
if (open.length > 0) {
|
|
16518
16576
|
const componentNames = [...new Set(open.map((p) => p.displayName))];
|
|
16519
16577
|
disclosures.push(
|
|
16520
|
-
`COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${
|
|
16578
|
+
`COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path49.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
|
|
16521
16579
|
);
|
|
16522
16580
|
}
|
|
16523
16581
|
} catch (err) {
|
|
@@ -16534,9 +16592,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16534
16592
|
const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
|
|
16535
16593
|
const segments = buildSegments(task, "files");
|
|
16536
16594
|
let notRecorded;
|
|
16537
|
-
const manifestPath2 =
|
|
16538
|
-
if (
|
|
16539
|
-
notRecorded = JSON.parse(
|
|
16595
|
+
const manifestPath2 = path49.join(task.set, "recording-set.json");
|
|
16596
|
+
if (existsSync39(manifestPath2)) {
|
|
16597
|
+
notRecorded = JSON.parse(readFileSync36(manifestPath2, "utf8")).notRecorded;
|
|
16540
16598
|
}
|
|
16541
16599
|
const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
|
|
16542
16600
|
|
|
@@ -16544,7 +16602,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16544
16602
|
DO NOT INVENT PIXELS FOR THESE POSES. Implement them ONLY as the composition of recorded per-axis truth (the custom-property composition rule: one paint axis sets variables, the other consumes) and list every such pose in your report as UNRECORDED-COMPOSED. Recording them is the real fix: re-plan the set (full matrix is the default) and record the missing poses.
|
|
16545
16603
|
${notRecorded}` : "";
|
|
16546
16604
|
let fontProvisioning;
|
|
16547
|
-
if (
|
|
16605
|
+
if (existsSync39(manifestPath2)) {
|
|
16548
16606
|
const missingFams = unprovisionedFamilies(task.set);
|
|
16549
16607
|
const unprovided = unprovisionedFaces(task.set);
|
|
16550
16608
|
const weightOnly = missingFams.length === 0;
|
|
@@ -16566,7 +16624,7 @@ ${notRecorded}` : "";
|
|
|
16566
16624
|
};
|
|
16567
16625
|
}
|
|
16568
16626
|
}
|
|
16569
|
-
const pinsResult = composedPins(task.set, [opts.library !== void 0 ?
|
|
16627
|
+
const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path49.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16570
16628
|
const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
|
|
16571
16629
|
const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
|
|
16572
16630
|
|
|
@@ -16602,10 +16660,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
|
|
|
16602
16660
|
|
|
16603
16661
|
=== TASK PAYLOAD (recorded truth, verbatim) ===
|
|
16604
16662
|
${segments}`;
|
|
16605
|
-
const payloadFile =
|
|
16606
|
-
const candidateDirSuggestion =
|
|
16607
|
-
|
|
16608
|
-
|
|
16663
|
+
const payloadFile = path49.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
|
|
16664
|
+
const candidateDirSuggestion = path49.resolve(callerCwd, `tendril-out/${name}-candidate`);
|
|
16665
|
+
mkdirSync12(path49.dirname(payloadFile), { recursive: true });
|
|
16666
|
+
writeFileSync18(payloadFile, payload);
|
|
16609
16667
|
emitData(
|
|
16610
16668
|
opts,
|
|
16611
16669
|
{
|
|
@@ -16651,7 +16709,7 @@ ${segments}`;
|
|
|
16651
16709
|
// command must search the same bundle roots the pins came
|
|
16652
16710
|
// from, or the oracle and the brief describe different worlds.
|
|
16653
16711
|
`Run \`${tendrilCommand(
|
|
16654
|
-
`engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(
|
|
16712
|
+
`engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path49.resolve(callerCwd, opts.library))}` : ""} --json`
|
|
16655
16713
|
)}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model. If you wrote the bundle elsewhere, pass that directory instead.`,
|
|
16656
16714
|
"Apply the returned feedback and re-score. Stop when all checks pass. Two consecutive non-improving scores mean INSPECT the verify-evidence diffs before deciding \u2014 stop only when inspection yields no fix hypothesis (a measured run was byte-identical twice, then went 27/27 after reading the diffs).",
|
|
16657
16715
|
"Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
|
|
@@ -16666,8 +16724,8 @@ ${segments}`;
|
|
|
16666
16724
|
);
|
|
16667
16725
|
}
|
|
16668
16726
|
function appendScoreHistory(candidateDir, entry) {
|
|
16669
|
-
const file =
|
|
16670
|
-
const starts =
|
|
16727
|
+
const file = path49.join(candidateDir, "score-history.jsonl");
|
|
16728
|
+
const starts = existsSync39(file) ? readFileSync36(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
|
|
16671
16729
|
const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
|
|
16672
16730
|
appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
|
|
16673
16731
|
`);
|
|
@@ -16675,10 +16733,10 @@ function appendScoreHistory(candidateDir, entry) {
|
|
|
16675
16733
|
async function runEngineScore(opts) {
|
|
16676
16734
|
requireEntitlement(opts);
|
|
16677
16735
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16678
|
-
const candidateDir =
|
|
16736
|
+
const candidateDir = path49.resolve(callerCwd, opts.candidateDir);
|
|
16679
16737
|
const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
|
|
16680
16738
|
void reportRunPresence(name, "implementing");
|
|
16681
|
-
if (!
|
|
16739
|
+
if (!existsSync39(candidateDir)) {
|
|
16682
16740
|
fail(opts, ExitCode.InputValidation, {
|
|
16683
16741
|
error: `candidate directory not found: ${candidateDir}`,
|
|
16684
16742
|
code: "candidate-missing",
|
|
@@ -16703,10 +16761,10 @@ async function runEngineScore(opts) {
|
|
|
16703
16761
|
for (const g of missingWeights(task.set)) {
|
|
16704
16762
|
warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
|
|
16705
16763
|
}
|
|
16706
|
-
if (opts.rebind !== true &&
|
|
16764
|
+
if (opts.rebind !== true && existsSync39(path49.join(candidateDir, "component.json"))) {
|
|
16707
16765
|
const prior = (() => {
|
|
16708
16766
|
try {
|
|
16709
|
-
const read = readBundleManifest(
|
|
16767
|
+
const read = readBundleManifest(readFileSync36(path49.join(candidateDir, "component.json"), "utf8"));
|
|
16710
16768
|
return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
|
|
16711
16769
|
} catch {
|
|
16712
16770
|
return { unreadable: true };
|
|
@@ -16728,13 +16786,13 @@ async function runEngineScore(opts) {
|
|
|
16728
16786
|
}
|
|
16729
16787
|
}
|
|
16730
16788
|
const bar = BARS3[opts.bar];
|
|
16731
|
-
const evidenceDir =
|
|
16789
|
+
const evidenceDir = path49.join(candidateDir, "verify-evidence");
|
|
16732
16790
|
const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
16733
16791
|
const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
|
|
16734
16792
|
const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
|
|
16735
16793
|
const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
|
|
16736
16794
|
const occlusionFailures = occlusionRows.filter((o) => !o.pass);
|
|
16737
|
-
const scorePins = composedPins(task.set, [opts.library !== void 0 ?
|
|
16795
|
+
const scorePins = composedPins(task.set, [opts.library !== void 0 ? path49.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16738
16796
|
const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
|
|
16739
16797
|
const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
|
|
16740
16798
|
const parityCoverage = parity.length > 0 ? `${parity.filter((p) => p.pass).length}/${parity.length} hover-forced configs` : "not applicable (no hover-forced configs in this set)";
|
|
@@ -16953,11 +17011,11 @@ var codeconnect_exports = {};
|
|
|
16953
17011
|
__export(codeconnect_exports, {
|
|
16954
17012
|
runCodeConnect: () => runCodeConnect
|
|
16955
17013
|
});
|
|
16956
|
-
import { existsSync as
|
|
16957
|
-
import
|
|
17014
|
+
import { existsSync as existsSync40, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
|
|
17015
|
+
import path50 from "node:path";
|
|
16958
17016
|
function runCodeConnect(opts) {
|
|
16959
17017
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16960
|
-
const bundleDir =
|
|
17018
|
+
const bundleDir = path50.resolve(callerCwd, opts.bundleDir);
|
|
16961
17019
|
let url;
|
|
16962
17020
|
try {
|
|
16963
17021
|
url = new URL(opts.figmaUrl);
|
|
@@ -16973,7 +17031,7 @@ function runCodeConnect(opts) {
|
|
|
16973
17031
|
}
|
|
16974
17032
|
let manifest;
|
|
16975
17033
|
try {
|
|
16976
|
-
const read = readBundleManifest(
|
|
17034
|
+
const read = readBundleManifest(readFileSync37(path50.join(bundleDir, "component.json"), "utf8"));
|
|
16977
17035
|
if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
|
|
16978
17036
|
manifest = read.manifest;
|
|
16979
17037
|
} catch (err) {
|
|
@@ -16983,8 +17041,8 @@ function runCodeConnect(opts) {
|
|
|
16983
17041
|
remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
|
|
16984
17042
|
});
|
|
16985
17043
|
}
|
|
16986
|
-
const setDir =
|
|
16987
|
-
if (!
|
|
17044
|
+
const setDir = path50.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
|
|
17045
|
+
if (!existsSync40(path50.join(setDir, "recording-set.json"))) {
|
|
16988
17046
|
fail(opts, ExitCode.InputValidation, {
|
|
16989
17047
|
error: `recording set not found at ${setDir}`,
|
|
16990
17048
|
code: "codeconnect-no-set",
|
|
@@ -17005,10 +17063,10 @@ function runCodeConnect(opts) {
|
|
|
17005
17063
|
const component = api.component;
|
|
17006
17064
|
const recManifest = loadManifest(setDir);
|
|
17007
17065
|
const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
|
|
17008
|
-
const meta =
|
|
17009
|
-
if (!
|
|
17066
|
+
const meta = path50.join(setDir, r.slug, "get_metadata.json");
|
|
17067
|
+
if (!existsSync40(meta)) return void 0;
|
|
17010
17068
|
try {
|
|
17011
|
-
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(
|
|
17069
|
+
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync37(meta, "utf8"))))?.[1];
|
|
17012
17070
|
} catch {
|
|
17013
17071
|
return void 0;
|
|
17014
17072
|
}
|
|
@@ -17073,7 +17131,7 @@ function runCodeConnect(opts) {
|
|
|
17073
17131
|
axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
|
|
17074
17132
|
fragmentVars.push(varName);
|
|
17075
17133
|
}
|
|
17076
|
-
const entryRel =
|
|
17134
|
+
const entryRel = path50.relative(callerCwd, path50.join(bundleDir, manifest.entry));
|
|
17077
17135
|
const trust = manifest.trustStatement.split("\n")[0] ?? "";
|
|
17078
17136
|
const lines = [
|
|
17079
17137
|
`// url=${opts.figmaUrl}`,
|
|
@@ -17097,8 +17155,8 @@ function runCodeConnect(opts) {
|
|
|
17097
17155
|
`}`,
|
|
17098
17156
|
``
|
|
17099
17157
|
].join("\n");
|
|
17100
|
-
const outFile =
|
|
17101
|
-
|
|
17158
|
+
const outFile = path50.resolve(callerCwd, opts.out ?? path50.join(bundleDir, `${component}.figma.ts`));
|
|
17159
|
+
writeFileSync19(outFile, lines);
|
|
17102
17160
|
emitData(
|
|
17103
17161
|
opts,
|
|
17104
17162
|
{
|
|
@@ -17137,17 +17195,17 @@ var init_codeconnect = __esm({
|
|
|
17137
17195
|
|
|
17138
17196
|
// packages/mcp/src/server.ts
|
|
17139
17197
|
import { createHash as createHash10 } from "node:crypto";
|
|
17140
|
-
import { existsSync as
|
|
17198
|
+
import { existsSync as existsSync41, mkdtempSync as mkdtempSync3, readFileSync as readFileSync38, readdirSync as readdirSync16, writeFileSync as writeFileSync20 } from "node:fs";
|
|
17141
17199
|
import os8 from "node:os";
|
|
17142
|
-
import
|
|
17200
|
+
import path51 from "node:path";
|
|
17143
17201
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17144
17202
|
import { z as z14 } from "zod";
|
|
17145
17203
|
function sourceHash() {
|
|
17146
|
-
const dir =
|
|
17204
|
+
const dir = path51.dirname(fileURLToPath6(import.meta.url));
|
|
17147
17205
|
const h = createHash10("sha256");
|
|
17148
17206
|
for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
|
|
17149
17207
|
h.update(f);
|
|
17150
|
-
h.update(
|
|
17208
|
+
h.update(readFileSync38(path51.join(dir, f)));
|
|
17151
17209
|
}
|
|
17152
17210
|
return h.digest("hex").slice(0, 16);
|
|
17153
17211
|
}
|
|
@@ -17155,10 +17213,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
|
|
|
17155
17213
|
var init_server = __esm({
|
|
17156
17214
|
"packages/mcp/src/server.ts"() {
|
|
17157
17215
|
"use strict";
|
|
17158
|
-
REPO_ROOT3 =
|
|
17159
|
-
CLI_BIN =
|
|
17160
|
-
BUNDLED_CLI =
|
|
17161
|
-
CLI_SPAWN =
|
|
17216
|
+
REPO_ROOT3 = path51.resolve(path51.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
|
|
17217
|
+
CLI_BIN = path51.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
|
|
17218
|
+
BUNDLED_CLI = path51.join(path51.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
|
|
17219
|
+
CLI_SPAWN = existsSync41(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
|
|
17162
17220
|
str = (d) => z14.string().describe(d);
|
|
17163
17221
|
optStr = (d) => z14.string().optional().describe(d);
|
|
17164
17222
|
TOOLS = [
|
|
@@ -17189,13 +17247,13 @@ var init_server = __esm({
|
|
|
17189
17247
|
const single = i["metadata"];
|
|
17190
17248
|
const parts = i["metadataParts"];
|
|
17191
17249
|
if (single !== void 0 || parts !== void 0) {
|
|
17192
|
-
const tmp =
|
|
17250
|
+
const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17193
17251
|
if (single !== void 0) {
|
|
17194
|
-
|
|
17252
|
+
writeFileSync20(tmp, single);
|
|
17195
17253
|
argvOut.push("--metadata-raw-file", tmp);
|
|
17196
17254
|
} else {
|
|
17197
17255
|
if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
|
|
17198
|
-
|
|
17256
|
+
writeFileSync20(tmp, JSON.stringify(parts));
|
|
17199
17257
|
argvOut.push("--metadata-raw-parts-file", tmp);
|
|
17200
17258
|
}
|
|
17201
17259
|
}
|
|
@@ -17245,9 +17303,23 @@ var init_server = __esm({
|
|
|
17245
17303
|
schema: z14.object({}),
|
|
17246
17304
|
argv: () => ["login", "--device-wait"]
|
|
17247
17305
|
},
|
|
17306
|
+
{
|
|
17307
|
+
name: "tendril_figma_connect",
|
|
17308
|
+
description: "Grant this machine read access to the user's Figma files over Figma's API \u2014 phase one of the browser Figma connection. Recording over the Figma MCP transport pays a hard per-day call quota; this connection lets recording fetch over Figma's REST API instead (per-minute, batched), so large component sets stop hitting daily limits. Starts the handshake and returns a connect link \u2014 RELAY IT to the user verbatim: they sign in to their portal account if asked, then click Allow on Figma's consent screen (read-only file access; revocable any time in their Figma settings). Then call tendril_figma_connect_wait to finish. You cannot consent for them: the portal only accepts the browser's signed-in click, never this machine's token. Offer it when a recording plan warns about Figma call limits; requires a portal session (tendril_login).",
|
|
17309
|
+
schema: z14.object({
|
|
17310
|
+
portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
|
|
17311
|
+
}),
|
|
17312
|
+
argv: (i) => ["figma-connect", "--start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
|
|
17313
|
+
},
|
|
17314
|
+
{
|
|
17315
|
+
name: "tendril_figma_connect_wait",
|
|
17316
|
+
description: "Phase two of the browser Figma connection: waits (with live progress) for the user's Allow click on Figma's consent screen, then stores the credential on this machine (it renews itself from then on). Call it right after relaying the connect link. A decline, a lapse, and success each come back as their own sentence \u2014 report the outcome to the user.",
|
|
17317
|
+
schema: z14.object({}),
|
|
17318
|
+
argv: () => ["figma-connect", "--wait"]
|
|
17319
|
+
},
|
|
17248
17320
|
{
|
|
17249
17321
|
name: "tendril_publish",
|
|
17250
|
-
description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim (they click Approve in the browser
|
|
17322
|
+
description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim, along with which account the result says to be signed in as (they click Approve in the browser; approving includes accepting the design system's publishing terms, which is their decision to make, never yours to urge). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
|
|
17251
17323
|
schema: z14.object({
|
|
17252
17324
|
bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
|
|
17253
17325
|
portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
|
|
@@ -17304,14 +17376,14 @@ var init_server = __esm({
|
|
|
17304
17376
|
const bridge = (label, single, parts) => {
|
|
17305
17377
|
if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
|
|
17306
17378
|
if (single === void 0 && parts === void 0) return;
|
|
17307
|
-
const tmp =
|
|
17379
|
+
const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17308
17380
|
if (single !== void 0) {
|
|
17309
|
-
|
|
17381
|
+
writeFileSync20(tmp, single);
|
|
17310
17382
|
argvOut.push(`--${label}-file`, tmp);
|
|
17311
17383
|
} else {
|
|
17312
17384
|
const blocks = parts;
|
|
17313
17385
|
if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
|
|
17314
|
-
|
|
17386
|
+
writeFileSync20(tmp, JSON.stringify(blocks));
|
|
17315
17387
|
argvOut.push(`--${label}-parts-file`, tmp);
|
|
17316
17388
|
}
|
|
17317
17389
|
};
|
|
@@ -17352,12 +17424,12 @@ var init_server = __esm({
|
|
|
17352
17424
|
const file = i["file"];
|
|
17353
17425
|
if ([text, texts, file].filter((x) => x !== void 0).length !== 1) throw new Error("pass exactly one of `text` (single-block response), `texts` (multi-block response), or `file` (a saved envelope)");
|
|
17354
17426
|
if (file !== void 0) return [...base, "--file", file];
|
|
17355
|
-
const tmp =
|
|
17427
|
+
const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17356
17428
|
if (text !== void 0) {
|
|
17357
|
-
|
|
17429
|
+
writeFileSync20(tmp, text);
|
|
17358
17430
|
return [...base, "--file", tmp, "--raw"];
|
|
17359
17431
|
}
|
|
17360
|
-
|
|
17432
|
+
writeFileSync20(tmp, JSON.stringify(texts));
|
|
17361
17433
|
return [...base, "--file", tmp, "--raw-parts"];
|
|
17362
17434
|
}
|
|
17363
17435
|
},
|
|
@@ -17514,13 +17586,13 @@ __export(permissions_exports, {
|
|
|
17514
17586
|
runPermissions: () => runPermissions,
|
|
17515
17587
|
writeSelection: () => writeSelection
|
|
17516
17588
|
});
|
|
17517
|
-
import { existsSync as
|
|
17589
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync13, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
|
|
17518
17590
|
import os9 from "node:os";
|
|
17519
|
-
import
|
|
17591
|
+
import path52 from "node:path";
|
|
17520
17592
|
function mergeAllowlist(file, entries, denyEntries = []) {
|
|
17521
17593
|
let settings = {};
|
|
17522
|
-
if (
|
|
17523
|
-
settings = JSON.parse(
|
|
17594
|
+
if (existsSync42(file) && readFileSync39(file, "utf8").trim() !== "") {
|
|
17595
|
+
settings = JSON.parse(readFileSync39(file, "utf8"));
|
|
17524
17596
|
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
|
|
17525
17597
|
}
|
|
17526
17598
|
const permissions = settings["permissions"] ??= {};
|
|
@@ -17540,8 +17612,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
|
|
|
17540
17612
|
}
|
|
17541
17613
|
if (added.length > 0 || denyAdded.length > 0) {
|
|
17542
17614
|
allow.push(...added);
|
|
17543
|
-
|
|
17544
|
-
|
|
17615
|
+
mkdirSync13(path52.dirname(file), { recursive: true });
|
|
17616
|
+
writeFileSync21(file, `${JSON.stringify(settings, null, 2)}
|
|
17545
17617
|
`);
|
|
17546
17618
|
}
|
|
17547
17619
|
return { added, alreadyPresent, denyAdded };
|
|
@@ -17605,7 +17677,7 @@ async function runPermissions(flags) {
|
|
|
17605
17677
|
}
|
|
17606
17678
|
if (flags.write) {
|
|
17607
17679
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
17608
|
-
const file = flags.user ?
|
|
17680
|
+
const file = flags.user ? path52.join(os9.homedir(), ".claude", "settings.json") : path52.join(base, ".claude", "settings.local.json");
|
|
17609
17681
|
const { entries, denyEntries } = writeSelection(result, flags.user === true);
|
|
17610
17682
|
if (flags.dryRun) {
|
|
17611
17683
|
emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
|
|
@@ -17754,13 +17826,13 @@ __export(inspect_exports, {
|
|
|
17754
17826
|
INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
|
|
17755
17827
|
runInspect: () => runInspect
|
|
17756
17828
|
});
|
|
17757
|
-
import { existsSync as
|
|
17758
|
-
import
|
|
17829
|
+
import { existsSync as existsSync43, readFileSync as readFileSync40, writeFileSync as writeFileSync22 } from "node:fs";
|
|
17830
|
+
import path53 from "node:path";
|
|
17759
17831
|
function readVerifyReport(evidenceDir) {
|
|
17760
|
-
const p =
|
|
17761
|
-
if (!
|
|
17832
|
+
const p = path53.join(evidenceDir, VERIFY_REPORT_FILENAME);
|
|
17833
|
+
if (!existsSync43(p)) return void 0;
|
|
17762
17834
|
try {
|
|
17763
|
-
return JSON.parse(
|
|
17835
|
+
return JSON.parse(readFileSync40(p, "utf8"));
|
|
17764
17836
|
} catch {
|
|
17765
17837
|
return void 0;
|
|
17766
17838
|
}
|
|
@@ -17788,17 +17860,17 @@ async function runInspect(opts) {
|
|
|
17788
17860
|
printDescription(INSPECT_DESCRIPTION);
|
|
17789
17861
|
return;
|
|
17790
17862
|
}
|
|
17791
|
-
const bundleDir =
|
|
17792
|
-
const evidenceDir =
|
|
17793
|
-
const manifestPath2 =
|
|
17794
|
-
if (!
|
|
17863
|
+
const bundleDir = path53.resolve(opts.bundleDir);
|
|
17864
|
+
const evidenceDir = path53.join(bundleDir, "verify-evidence");
|
|
17865
|
+
const manifestPath2 = path53.join(bundleDir, "component.json");
|
|
17866
|
+
if (!existsSync43(evidenceDir) || !existsSync43(manifestPath2)) {
|
|
17795
17867
|
fail(opts, ExitCode.InputValidation, {
|
|
17796
|
-
error: `nothing to inspect in ${bundleDir} \u2014 ${
|
|
17868
|
+
error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync43(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
|
|
17797
17869
|
code: "no-evidence",
|
|
17798
17870
|
remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
|
|
17799
17871
|
});
|
|
17800
17872
|
}
|
|
17801
|
-
const { manifest } = readBundleManifest(
|
|
17873
|
+
const { manifest } = readBundleManifest(readFileSync40(manifestPath2, "utf8"));
|
|
17802
17874
|
if (manifest === void 0) {
|
|
17803
17875
|
fail(opts, ExitCode.InputValidation, {
|
|
17804
17876
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -17806,9 +17878,9 @@ async function runInspect(opts) {
|
|
|
17806
17878
|
remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
|
|
17807
17879
|
});
|
|
17808
17880
|
}
|
|
17809
|
-
const setDir =
|
|
17881
|
+
const setDir = path53.resolve(opts.set ?? manifest.provenance.recordingSet.path);
|
|
17810
17882
|
const report = readVerifyReport(evidenceDir);
|
|
17811
|
-
const reps = Object.keys(manifest.propAdapter).filter((rep) =>
|
|
17883
|
+
const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync43(path53.join(evidenceDir, `${rep}-ref.png`)) && existsSync43(path53.join(evidenceDir, `${rep}-render.png`)));
|
|
17812
17884
|
if (reps.length === 0) {
|
|
17813
17885
|
fail(opts, ExitCode.InputValidation, {
|
|
17814
17886
|
error: "verify-evidence holds no ref/render pairs for this bundle's configs",
|
|
@@ -17819,15 +17891,15 @@ async function runInspect(opts) {
|
|
|
17819
17891
|
let crops = 0;
|
|
17820
17892
|
const sections = [];
|
|
17821
17893
|
for (const rep of reps) {
|
|
17822
|
-
const ref = new Uint8Array(
|
|
17823
|
-
const render = new Uint8Array(
|
|
17894
|
+
const ref = new Uint8Array(readFileSync40(path53.join(evidenceDir, `${rep}-ref.png`)));
|
|
17895
|
+
const render = new Uint8Array(readFileSync40(path53.join(evidenceDir, `${rep}-render.png`)));
|
|
17824
17896
|
const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
|
|
17825
17897
|
const cells = [];
|
|
17826
17898
|
for (const [i, n] of nodes.entries()) {
|
|
17827
17899
|
const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
|
|
17828
17900
|
try {
|
|
17829
|
-
|
|
17830
|
-
|
|
17901
|
+
writeFileSync22(path53.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
|
|
17902
|
+
writeFileSync22(path53.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
|
|
17831
17903
|
} catch {
|
|
17832
17904
|
continue;
|
|
17833
17905
|
}
|
|
@@ -17844,8 +17916,8 @@ async function runInspect(opts) {
|
|
|
17844
17916
|
if (reps.includes(c.rep)) continue;
|
|
17845
17917
|
sections.push(`<section class="missing"><h2>${esc(c.rep)}</h2>${scoreLine(report, c.rep)}<p class="none">No evidence images for this config \u2014 it was scored, but nothing was captured to look at.</p></section>`);
|
|
17846
17918
|
}
|
|
17847
|
-
const sheet =
|
|
17848
|
-
|
|
17919
|
+
const sheet = path53.join(evidenceDir, "inspect.html");
|
|
17920
|
+
writeFileSync22(
|
|
17849
17921
|
sheet,
|
|
17850
17922
|
`<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
|
|
17851
17923
|
body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
|
|
@@ -17918,8 +17990,8 @@ __export(login_exports, {
|
|
|
17918
17990
|
runLogout: () => runLogout
|
|
17919
17991
|
});
|
|
17920
17992
|
import { spawn } from "node:child_process";
|
|
17921
|
-
import { existsSync as
|
|
17922
|
-
import
|
|
17993
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync14, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
|
|
17994
|
+
import path54 from "node:path";
|
|
17923
17995
|
import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
|
|
17924
17996
|
async function runLogin(opts, deps) {
|
|
17925
17997
|
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
|
|
@@ -18013,13 +18085,13 @@ function settleDecision(opts, origin, outcome) {
|
|
|
18013
18085
|
}
|
|
18014
18086
|
}
|
|
18015
18087
|
function pendingLoginPath() {
|
|
18016
|
-
return
|
|
18088
|
+
return path54.join(path54.dirname(sessionPath()), "pending-login.json");
|
|
18017
18089
|
}
|
|
18018
18090
|
async function deviceStartPhase(opts, origin, deps) {
|
|
18019
18091
|
const started = await startHandshake(opts, origin, deps);
|
|
18020
18092
|
const file = pendingLoginPath();
|
|
18021
|
-
|
|
18022
|
-
|
|
18093
|
+
mkdirSync14(path54.dirname(file), { recursive: true });
|
|
18094
|
+
writeFileSync23(file, `${JSON.stringify({ origin, ...started }, null, 2)}
|
|
18023
18095
|
`, { mode: 384 });
|
|
18024
18096
|
deps.openBrowser(started.verificationUrl);
|
|
18025
18097
|
emitData(
|
|
@@ -18044,9 +18116,9 @@ async function deviceStartPhase(opts, origin, deps) {
|
|
|
18044
18116
|
async function deviceWaitPhase(opts, deps) {
|
|
18045
18117
|
const file = pendingLoginPath();
|
|
18046
18118
|
let pending;
|
|
18047
|
-
if (
|
|
18119
|
+
if (existsSync44(file)) {
|
|
18048
18120
|
try {
|
|
18049
|
-
const parsed = JSON.parse(
|
|
18121
|
+
const parsed = JSON.parse(readFileSync41(file, "utf8"));
|
|
18050
18122
|
if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
|
|
18051
18123
|
pending = parsed;
|
|
18052
18124
|
}
|
|
@@ -18060,7 +18132,7 @@ async function deviceWaitPhase(opts, deps) {
|
|
|
18060
18132
|
remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
|
|
18061
18133
|
});
|
|
18062
18134
|
}
|
|
18063
|
-
const done = () =>
|
|
18135
|
+
const done = () => rmSync8(file, { force: true });
|
|
18064
18136
|
const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
|
|
18065
18137
|
let ticks = 0;
|
|
18066
18138
|
const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
|
|
@@ -18183,6 +18255,196 @@ var init_login = __esm({
|
|
|
18183
18255
|
}
|
|
18184
18256
|
});
|
|
18185
18257
|
|
|
18258
|
+
// packages/cli/src/commands/figma-connect.ts
|
|
18259
|
+
var figma_connect_exports = {};
|
|
18260
|
+
__export(figma_connect_exports, {
|
|
18261
|
+
runFigmaConnect: () => runFigmaConnect
|
|
18262
|
+
});
|
|
18263
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync15, readFileSync as readFileSync42, rmSync as rmSync9, writeFileSync as writeFileSync24 } from "node:fs";
|
|
18264
|
+
import path55 from "node:path";
|
|
18265
|
+
function pendingConnectPath() {
|
|
18266
|
+
return path55.join(path55.dirname(sessionPath()), "pending-figma-connect.json");
|
|
18267
|
+
}
|
|
18268
|
+
function resolveOrigin(opts) {
|
|
18269
|
+
const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
18270
|
+
if (named !== "") return named;
|
|
18271
|
+
if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
|
|
18272
|
+
return (readStoredSession()?.origin ?? "").replace(/\/+$/, "");
|
|
18273
|
+
}
|
|
18274
|
+
function bearerFor(opts, origin) {
|
|
18275
|
+
if (origin === "") {
|
|
18276
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18277
|
+
error: "no portal to connect through",
|
|
18278
|
+
code: "no-portal-configured",
|
|
18279
|
+
remediation: `Sign in first (${tendrilCommand("login")}) \u2014 the connect uses your portal account.`
|
|
18280
|
+
});
|
|
18281
|
+
}
|
|
18282
|
+
const found = tokenFor(origin);
|
|
18283
|
+
if (!found.ok) {
|
|
18284
|
+
fail(opts, ExitCode.Auth, {
|
|
18285
|
+
error: `no session for ${origin}`,
|
|
18286
|
+
code: "not-signed-in",
|
|
18287
|
+
remediation: `Run ${tendrilCommand("login")} first \u2014 connecting Figma needs your signed-in portal account.`
|
|
18288
|
+
});
|
|
18289
|
+
}
|
|
18290
|
+
return found.token;
|
|
18291
|
+
}
|
|
18292
|
+
async function runFigmaConnect(opts) {
|
|
18293
|
+
const send = opts.fetchImpl ?? globalThis.fetch;
|
|
18294
|
+
const origin = resolveOrigin(opts);
|
|
18295
|
+
if (opts.wait === true) {
|
|
18296
|
+
await waitPhase(opts, send);
|
|
18297
|
+
return;
|
|
18298
|
+
}
|
|
18299
|
+
const token = bearerFor(opts, origin);
|
|
18300
|
+
const started = await startConnect(opts, send, origin, token);
|
|
18301
|
+
if (opts.start === true) {
|
|
18302
|
+
const pending = { origin, ...started };
|
|
18303
|
+
mkdirSync15(path55.dirname(pendingConnectPath()), { recursive: true });
|
|
18304
|
+
writeFileSync24(pendingConnectPath(), `${JSON.stringify(pending, null, 2)}
|
|
18305
|
+
`, { mode: 384 });
|
|
18306
|
+
(opts.openBrowser ?? (() => {
|
|
18307
|
+
}))(started.connectUrl);
|
|
18308
|
+
emitData(
|
|
18309
|
+
opts,
|
|
18310
|
+
{
|
|
18311
|
+
connectUrl: started.connectUrl,
|
|
18312
|
+
expiresAt: started.expiresAt,
|
|
18313
|
+
origin,
|
|
18314
|
+
next: "Show the user the link \u2014 they sign in (same account) and click Allow on Figma's consent screen. Then run figma-connect --wait (the tendril_figma_connect_wait tool) to finish."
|
|
18315
|
+
},
|
|
18316
|
+
() => {
|
|
18317
|
+
process.stdout.write(`open ${started.connectUrl}
|
|
18318
|
+
`);
|
|
18319
|
+
process.stdout.write(` then: ${tendrilCommand("figma-connect --wait")}
|
|
18320
|
+
`);
|
|
18321
|
+
}
|
|
18322
|
+
);
|
|
18323
|
+
return;
|
|
18324
|
+
}
|
|
18325
|
+
process.stderr.write(`Connect Figma in your browser:
|
|
18326
|
+
${started.connectUrl}
|
|
18327
|
+
`);
|
|
18328
|
+
process.stderr.write(`Waiting for the Allow click (lapses at ${started.expiresAt.slice(11, 16)} UTC)\u2026
|
|
18329
|
+
`);
|
|
18330
|
+
const outcome = await pollToDecision(opts, send, origin, token, started);
|
|
18331
|
+
settle2(opts, origin, outcome);
|
|
18332
|
+
}
|
|
18333
|
+
async function startConnect(opts, send, origin, token) {
|
|
18334
|
+
let response;
|
|
18335
|
+
try {
|
|
18336
|
+
response = await send(`${origin}/api/figma-connect`, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: "{}" });
|
|
18337
|
+
} catch (error) {
|
|
18338
|
+
fail(opts, ExitCode.General, {
|
|
18339
|
+
error: `could not reach ${origin}: ${error.message}`,
|
|
18340
|
+
code: "portal-unreachable",
|
|
18341
|
+
remediation: "Check the address and your connection, then try again."
|
|
18342
|
+
});
|
|
18343
|
+
}
|
|
18344
|
+
const body = await response.json().catch(() => ({}));
|
|
18345
|
+
if (!response.ok || typeof body.connectId !== "string" || typeof body.connectUrl !== "string" || typeof body.expiresAt !== "string") {
|
|
18346
|
+
fail(opts, ExitCode.General, {
|
|
18347
|
+
error: body.refusal ?? `the portal answered ${String(response.status)} to the connect start`,
|
|
18348
|
+
code: "figma-connect-refused",
|
|
18349
|
+
remediation: body.remediation ?? "Fix what is named above and run the connect again."
|
|
18350
|
+
});
|
|
18351
|
+
}
|
|
18352
|
+
return { connectId: body.connectId, connectUrl: body.connectUrl, expiresAt: body.expiresAt, pollSeconds: typeof body.pollSeconds === "number" ? body.pollSeconds : 3 };
|
|
18353
|
+
}
|
|
18354
|
+
async function waitPhase(opts, send) {
|
|
18355
|
+
const file = pendingConnectPath();
|
|
18356
|
+
let pending;
|
|
18357
|
+
if (existsSync45(file)) {
|
|
18358
|
+
try {
|
|
18359
|
+
const parsed = JSON.parse(readFileSync42(file, "utf8"));
|
|
18360
|
+
if (typeof parsed.origin === "string" && typeof parsed.connectId === "string") pending = parsed;
|
|
18361
|
+
} catch {
|
|
18362
|
+
}
|
|
18363
|
+
}
|
|
18364
|
+
if (pending === void 0) {
|
|
18365
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18366
|
+
error: "there is no Figma connection waiting to finish",
|
|
18367
|
+
code: "no-pending-figma-connect",
|
|
18368
|
+
remediation: `Start one first: ${tendrilCommand("figma-connect --start")} (the tendril_figma_connect tool).`
|
|
18369
|
+
});
|
|
18370
|
+
}
|
|
18371
|
+
const token = bearerFor(opts, pending.origin);
|
|
18372
|
+
const outcome = await pollToDecision(opts, send, pending.origin, token, pending);
|
|
18373
|
+
rmSync9(file, { force: true });
|
|
18374
|
+
settle2(opts, pending.origin, outcome);
|
|
18375
|
+
}
|
|
18376
|
+
async function pollToDecision(opts, send, origin, token, started) {
|
|
18377
|
+
const interval = Math.max(1, started.pollSeconds) * 1e3;
|
|
18378
|
+
const total = Math.ceil(WAIT_CAP_SECONDS * 1e3 / interval);
|
|
18379
|
+
for (let tick = 1; tick <= total; tick += 1) {
|
|
18380
|
+
let body = {};
|
|
18381
|
+
try {
|
|
18382
|
+
const response = await send(`${origin}/api/figma-connect/${encodeURIComponent(started.connectId)}`, {
|
|
18383
|
+
headers: { authorization: `Bearer ${token}` }
|
|
18384
|
+
});
|
|
18385
|
+
if (response.ok) body = await response.json();
|
|
18386
|
+
} catch {
|
|
18387
|
+
}
|
|
18388
|
+
if (body.status === "connected" && typeof body.accessToken === "string" && typeof body.refreshToken === "string") {
|
|
18389
|
+
return {
|
|
18390
|
+
status: "connected",
|
|
18391
|
+
tokens: {
|
|
18392
|
+
origin,
|
|
18393
|
+
accessToken: body.accessToken,
|
|
18394
|
+
refreshToken: body.refreshToken,
|
|
18395
|
+
tokenExpiresAt: typeof body.tokenExpiresAt === "string" ? body.tokenExpiresAt : new Date(Date.now() + 80 * 24 * 60 * 6e4).toISOString()
|
|
18396
|
+
}
|
|
18397
|
+
};
|
|
18398
|
+
}
|
|
18399
|
+
if (body.status === "failed") return { status: "failed", reason: body.reason ?? "declined" };
|
|
18400
|
+
if (body.status === "expired") return { status: "expired" };
|
|
18401
|
+
if (body.status === "unknown") return { status: "unknown" };
|
|
18402
|
+
emitProgress(tick, total, "waiting for the Allow click in the browser");
|
|
18403
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
18404
|
+
}
|
|
18405
|
+
return { status: "expired" };
|
|
18406
|
+
}
|
|
18407
|
+
function settle2(opts, origin, outcome) {
|
|
18408
|
+
if (outcome.status === "connected") {
|
|
18409
|
+
writeFigmaTokens(outcome.tokens);
|
|
18410
|
+
emitData(
|
|
18411
|
+
opts,
|
|
18412
|
+
{ status: "connected", origin, tokenExpiresAt: outcome.tokens.tokenExpiresAt, storedAt: figmaTokenPath() },
|
|
18413
|
+
() => {
|
|
18414
|
+
process.stdout.write(`Figma is connected \u2014 recording can use the REST channel now.
|
|
18415
|
+
`);
|
|
18416
|
+
process.stdout.write(` credential: ${figmaTokenPath()} (0600; renews itself through the portal)
|
|
18417
|
+
`);
|
|
18418
|
+
}
|
|
18419
|
+
);
|
|
18420
|
+
return;
|
|
18421
|
+
}
|
|
18422
|
+
if (outcome.status === "failed") {
|
|
18423
|
+
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18424
|
+
error: `the Figma connection was not granted: ${outcome.reason}`,
|
|
18425
|
+
code: "figma-connect-declined",
|
|
18426
|
+
remediation: "Nothing was stored. If minds change, run the connect again \u2014 it makes a fresh request."
|
|
18427
|
+
});
|
|
18428
|
+
}
|
|
18429
|
+
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18430
|
+
error: outcome.status === "expired" ? "the Figma connection lapsed before the Allow click" : "the Figma connection is gone \u2014 it lapsed, or was collected by another terminal",
|
|
18431
|
+
code: "figma-connect-lapsed",
|
|
18432
|
+
remediation: "Run the connect again and finish it within its ten-minute window."
|
|
18433
|
+
});
|
|
18434
|
+
}
|
|
18435
|
+
var WAIT_CAP_SECONDS;
|
|
18436
|
+
var init_figma_connect = __esm({
|
|
18437
|
+
"packages/cli/src/commands/figma-connect.ts"() {
|
|
18438
|
+
"use strict";
|
|
18439
|
+
init_src3();
|
|
18440
|
+
init_figma_token();
|
|
18441
|
+
init_invocation();
|
|
18442
|
+
init_output();
|
|
18443
|
+
init_publish_client();
|
|
18444
|
+
WAIT_CAP_SECONDS = 660;
|
|
18445
|
+
}
|
|
18446
|
+
});
|
|
18447
|
+
|
|
18186
18448
|
// packages/cli/src/commands/share.ts
|
|
18187
18449
|
var share_exports = {};
|
|
18188
18450
|
__export(share_exports, {
|
|
@@ -18295,12 +18557,13 @@ var init_share = __esm({
|
|
|
18295
18557
|
// packages/cli/src/commands/publish.ts
|
|
18296
18558
|
var publish_exports = {};
|
|
18297
18559
|
__export(publish_exports, {
|
|
18560
|
+
resolveOrigin: () => resolveOrigin2,
|
|
18298
18561
|
runPublish: () => runPublish
|
|
18299
18562
|
});
|
|
18300
|
-
import { existsSync as
|
|
18301
|
-
import
|
|
18563
|
+
import { existsSync as existsSync46, readFileSync as readFileSync43, rmSync as rmSync10, writeFileSync as writeFileSync25 } from "node:fs";
|
|
18564
|
+
import path56 from "node:path";
|
|
18302
18565
|
async function runPublish(opts) {
|
|
18303
|
-
const bundleDir =
|
|
18566
|
+
const bundleDir = path56.resolve(opts.bundleDir);
|
|
18304
18567
|
const bundle = readBundle(opts, bundleDir);
|
|
18305
18568
|
const report = bundle.report;
|
|
18306
18569
|
if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
|
|
@@ -18345,7 +18608,7 @@ async function runPublish(opts) {
|
|
|
18345
18608
|
const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
|
|
18346
18609
|
if (sheetEntry !== void 0) {
|
|
18347
18610
|
const missingCrops = missingInspectCrops(
|
|
18348
|
-
|
|
18611
|
+
readFileSync43(path56.join(bundleDir, sheetEntry.path), "utf8"),
|
|
18349
18612
|
surface.published.map((p) => p.path)
|
|
18350
18613
|
);
|
|
18351
18614
|
if (missingCrops.length > 0) {
|
|
@@ -18410,10 +18673,10 @@ async function runPublish(opts) {
|
|
|
18410
18673
|
);
|
|
18411
18674
|
return;
|
|
18412
18675
|
}
|
|
18413
|
-
const origin = (opts
|
|
18676
|
+
const origin = resolveOrigin2(opts);
|
|
18414
18677
|
const client = opts.client ?? httpClient(opts, origin);
|
|
18415
18678
|
if (opts.approveWait === true) {
|
|
18416
|
-
await approveWaitPhase(opts, client, bundleDir);
|
|
18679
|
+
await approveWaitPhase(opts, client, bundleDir, { componentName, figmaFile });
|
|
18417
18680
|
}
|
|
18418
18681
|
void reportRunPresence(componentName, "publishing");
|
|
18419
18682
|
let opened = await client.begin({
|
|
@@ -18435,7 +18698,7 @@ async function runPublish(opts) {
|
|
|
18435
18698
|
if (!opened.ok) {
|
|
18436
18699
|
if (opened.needsConfirmation !== void 0) {
|
|
18437
18700
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18438
|
-
error:
|
|
18701
|
+
error: opened.refusal,
|
|
18439
18702
|
code: "first-publish-unconfirmed",
|
|
18440
18703
|
remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
|
|
18441
18704
|
});
|
|
@@ -18444,8 +18707,8 @@ async function runPublish(opts) {
|
|
|
18444
18707
|
}
|
|
18445
18708
|
const uploaded = [];
|
|
18446
18709
|
for (const object of opened.value.plan.objects) {
|
|
18447
|
-
const file =
|
|
18448
|
-
if (!
|
|
18710
|
+
const file = path56.join(bundleDir, object.relPath);
|
|
18711
|
+
if (!existsSync46(file)) {
|
|
18449
18712
|
fail(opts, ExitCode.InputValidation, {
|
|
18450
18713
|
error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
|
|
18451
18714
|
code: "planned-file-missing",
|
|
@@ -18455,10 +18718,11 @@ async function runPublish(opts) {
|
|
|
18455
18718
|
const sent = await client.upload({
|
|
18456
18719
|
publicationId: opened.value.publicationId,
|
|
18457
18720
|
relPath: object.relPath,
|
|
18458
|
-
bytes: new Uint8Array(
|
|
18721
|
+
bytes: new Uint8Array(readFileSync43(file))
|
|
18459
18722
|
});
|
|
18460
|
-
if (!sent.ok) refuse2(opts, sent, "upload-refused");
|
|
18723
|
+
if (!sent.ok) refuse2(opts, sent, "upload-refused", true);
|
|
18461
18724
|
uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
|
|
18725
|
+
emitProgress(uploaded.length, opened.value.plan.objects.length, `uploading ${object.relPath}`);
|
|
18462
18726
|
}
|
|
18463
18727
|
const committed = await client.commit({ publicationId: opened.value.publicationId });
|
|
18464
18728
|
if (committed.ok) {
|
|
@@ -18472,7 +18736,7 @@ async function runPublish(opts) {
|
|
|
18472
18736
|
remediation: `Run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication and re-sends only what is missing.`
|
|
18473
18737
|
});
|
|
18474
18738
|
}
|
|
18475
|
-
refuse2(opts, committed, "commit-refused");
|
|
18739
|
+
refuse2(opts, committed, "commit-refused", true);
|
|
18476
18740
|
}
|
|
18477
18741
|
emitData(
|
|
18478
18742
|
opts,
|
|
@@ -18500,23 +18764,23 @@ async function runPublish(opts) {
|
|
|
18500
18764
|
);
|
|
18501
18765
|
}
|
|
18502
18766
|
function readBundle(opts, bundleDir) {
|
|
18503
|
-
const manifestPath2 =
|
|
18504
|
-
const reportPath =
|
|
18505
|
-
if (!
|
|
18767
|
+
const manifestPath2 = path56.join(bundleDir, "component.json");
|
|
18768
|
+
const reportPath = path56.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
|
|
18769
|
+
if (!existsSync46(manifestPath2)) {
|
|
18506
18770
|
fail(opts, ExitCode.InputValidation, {
|
|
18507
18771
|
error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
|
|
18508
18772
|
code: "not-a-bundle",
|
|
18509
18773
|
remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
|
|
18510
18774
|
});
|
|
18511
18775
|
}
|
|
18512
|
-
if (!
|
|
18776
|
+
if (!existsSync46(reportPath)) {
|
|
18513
18777
|
fail(opts, ExitCode.InputValidation, {
|
|
18514
18778
|
error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
|
|
18515
18779
|
code: "bundle-not-verified",
|
|
18516
18780
|
remediation: `Run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` first. Verification is free and needs no account; publishing without it would put a page up with no verdict on it.`
|
|
18517
18781
|
});
|
|
18518
18782
|
}
|
|
18519
|
-
const { manifest } = readBundleManifest(
|
|
18783
|
+
const { manifest } = readBundleManifest(readFileSync43(manifestPath2, "utf8"));
|
|
18520
18784
|
if (manifest === void 0) {
|
|
18521
18785
|
fail(opts, ExitCode.InputValidation, {
|
|
18522
18786
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -18524,7 +18788,7 @@ function readBundle(opts, bundleDir) {
|
|
|
18524
18788
|
remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
|
|
18525
18789
|
});
|
|
18526
18790
|
}
|
|
18527
|
-
const reportText =
|
|
18791
|
+
const reportText = readFileSync43(reportPath, "utf8");
|
|
18528
18792
|
let report;
|
|
18529
18793
|
try {
|
|
18530
18794
|
report = JSON.parse(reportText);
|
|
@@ -18544,6 +18808,12 @@ function readBundle(opts, bundleDir) {
|
|
|
18544
18808
|
}
|
|
18545
18809
|
return { manifest, report, reportText, files: bundleFiles(bundleDir) };
|
|
18546
18810
|
}
|
|
18811
|
+
function resolveOrigin2(opts) {
|
|
18812
|
+
const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
18813
|
+
if (named !== "") return named;
|
|
18814
|
+
if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
|
|
18815
|
+
return (readStoredSession()?.origin ?? "").replace(/\/+$/, "");
|
|
18816
|
+
}
|
|
18547
18817
|
function httpClient(opts, origin) {
|
|
18548
18818
|
if (origin === "") {
|
|
18549
18819
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -18576,37 +18846,43 @@ function httpClient(opts, origin) {
|
|
|
18576
18846
|
}
|
|
18577
18847
|
return new HttpPublishClient({ origin, token: found.token });
|
|
18578
18848
|
}
|
|
18579
|
-
function refuse2(opts, sent, code) {
|
|
18849
|
+
function refuse2(opts, sent, code, rejoins = false) {
|
|
18580
18850
|
const detail = sent.detail === void 0 || sent.detail.length === 0 ? "" : `: ${sent.detail.join(", ")}`;
|
|
18851
|
+
const retry = rejoins ? `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one` : `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again`;
|
|
18581
18852
|
fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
|
|
18582
18853
|
error: `${sent.refusal}${detail}`,
|
|
18583
18854
|
code,
|
|
18584
|
-
remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts
|
|
18855
|
+
remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${resolveOrigin2(opts)}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then ${retry}.` : `Fix what is named above and ${retry}.`
|
|
18585
18856
|
});
|
|
18586
18857
|
}
|
|
18587
18858
|
function pendingApprovalPath() {
|
|
18588
|
-
return
|
|
18859
|
+
return path56.join(path56.dirname(sessionPath()), "pending-publish.json");
|
|
18589
18860
|
}
|
|
18590
18861
|
async function runApprovalFlow(opts, client, bundleDir, input) {
|
|
18591
18862
|
const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
|
|
18592
18863
|
if (!requested.ok) refuse2(opts, requested, "approval-request-refused");
|
|
18593
18864
|
const approval = requested.value;
|
|
18865
|
+
const who = await client.whoami?.();
|
|
18866
|
+
const asAccount = who?.ok === true && who.value.email !== "" ? ` signed in as ${who.value.email}` : "";
|
|
18594
18867
|
if (opts.approveStart === true) {
|
|
18595
|
-
|
|
18868
|
+
const pending = { ...approval, bundleDir, componentName: input.componentName, figmaFile: input.figmaFile };
|
|
18869
|
+
writeFileSync25(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
|
|
18596
18870
|
`, { mode: 384 });
|
|
18871
|
+
await endRunPresence(input.componentName);
|
|
18597
18872
|
emitData(
|
|
18598
18873
|
opts,
|
|
18599
18874
|
{
|
|
18600
18875
|
status: "approval-pending",
|
|
18601
18876
|
approveUrl: approval.approveUrl,
|
|
18602
18877
|
componentName: input.componentName,
|
|
18878
|
+
...asAccount === "" ? {} : { approveAsAccount: who?.ok === true ? who.value.email : "" },
|
|
18603
18879
|
expiresAt: approval.expiresAt,
|
|
18604
|
-
next:
|
|
18880
|
+
next: `open the approve page in the browser${asAccount}, click Approve, then finish with --approve-wait`
|
|
18605
18881
|
},
|
|
18606
18882
|
() => {
|
|
18607
18883
|
process.stdout.write(`Approval requested for ${JSON.stringify(input.componentName)}.
|
|
18608
18884
|
`);
|
|
18609
|
-
process.stdout.write(`Approve it here: ${approval.approveUrl}
|
|
18885
|
+
process.stdout.write(`Approve it here${asAccount}: ${approval.approveUrl}
|
|
18610
18886
|
`);
|
|
18611
18887
|
process.stdout.write(`Then finish with: ${tendrilCommand(`publish ${opts.bundleDir} --approve-wait`)}
|
|
18612
18888
|
`);
|
|
@@ -18614,21 +18890,22 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
|
|
|
18614
18890
|
);
|
|
18615
18891
|
return void 0;
|
|
18616
18892
|
}
|
|
18617
|
-
process.stderr.write(`This component's FIRST publish needs your approval in the browser:
|
|
18893
|
+
process.stderr.write(`This component's FIRST publish needs your approval in the browser${asAccount}:
|
|
18618
18894
|
${approval.approveUrl}
|
|
18619
18895
|
`);
|
|
18620
18896
|
process.stderr.write(`Waiting for your decision (lapses at ${approval.expiresAt.slice(11, 16)} UTC)\u2026
|
|
18621
18897
|
`);
|
|
18622
18898
|
const decided = await waitForApproval(opts, client, approval);
|
|
18623
18899
|
if (decided === "approved") return input.begin();
|
|
18900
|
+
await endRunPresence(input.componentName);
|
|
18624
18901
|
failDecision(opts, decided);
|
|
18625
18902
|
}
|
|
18626
|
-
async function approveWaitPhase(opts, client, bundleDir) {
|
|
18903
|
+
async function approveWaitPhase(opts, client, bundleDir, subject) {
|
|
18627
18904
|
const file = pendingApprovalPath();
|
|
18628
18905
|
let pending;
|
|
18629
|
-
if (
|
|
18906
|
+
if (existsSync46(file)) {
|
|
18630
18907
|
try {
|
|
18631
|
-
const parsed = JSON.parse(
|
|
18908
|
+
const parsed = JSON.parse(readFileSync43(file, "utf8"));
|
|
18632
18909
|
if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
|
|
18633
18910
|
pending = { pollSeconds: 3, bundleDir, ...parsed };
|
|
18634
18911
|
}
|
|
@@ -18642,7 +18919,14 @@ async function approveWaitPhase(opts, client, bundleDir) {
|
|
|
18642
18919
|
remediation: `Start one first: ${tendrilCommand(`publish ${opts.bundleDir} --approve-start`)} (the tendril_publish tool).`
|
|
18643
18920
|
});
|
|
18644
18921
|
}
|
|
18645
|
-
|
|
18922
|
+
if (pending.componentName !== void 0 && pending.componentName !== subject.componentName) {
|
|
18923
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18924
|
+
error: `the waiting approval is for ${JSON.stringify(pending.componentName)}, and this publish is ${JSON.stringify(subject.componentName)}`,
|
|
18925
|
+
code: "pending-approval-mismatch",
|
|
18926
|
+
remediation: `Finish that one first (${tendrilCommand(`publish ${pending.bundleDir} --approve-wait`)}), or start this one fresh with --approve-start \u2014 starting one replaces the waiting slot.`
|
|
18927
|
+
});
|
|
18928
|
+
}
|
|
18929
|
+
const done = () => rmSync10(file, { force: true });
|
|
18646
18930
|
const decided = await waitForApproval(opts, client, pending);
|
|
18647
18931
|
done();
|
|
18648
18932
|
if (decided !== "approved") failDecision(opts, decided);
|
|
@@ -18670,7 +18954,7 @@ function failDecision(opts, decided) {
|
|
|
18670
18954
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18671
18955
|
error: decided === "expired" ? "the approval request lapsed before anyone decided it" : "the approval request is gone \u2014 it lapsed and was cleaned up, or was already spent",
|
|
18672
18956
|
code: "publish-approval-lapsed",
|
|
18673
|
-
remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window."
|
|
18957
|
+
remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window. If the approve page showed nothing waiting, the browser is signed in to a DIFFERENT account than this CLI \u2014 the page lists only its own account's requests."
|
|
18674
18958
|
});
|
|
18675
18959
|
}
|
|
18676
18960
|
var APPROVAL_WAIT_CAP_MS;
|
|
@@ -18711,17 +18995,17 @@ __export(generate_recorded_exports, {
|
|
|
18711
18995
|
runGenerateRecorded: () => runGenerateRecorded
|
|
18712
18996
|
});
|
|
18713
18997
|
import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
|
|
18714
|
-
import { existsSync as
|
|
18715
|
-
import
|
|
18998
|
+
import { existsSync as existsSync47, readFileSync as readFileSync44 } from "node:fs";
|
|
18999
|
+
import path57 from "node:path";
|
|
18716
19000
|
async function runGenerateRecorded(opts) {
|
|
18717
19001
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
18718
|
-
const outDirAbs =
|
|
18719
|
-
const recordedAsPath =
|
|
19002
|
+
const outDirAbs = path57.resolve(callerCwd, opts.out);
|
|
19003
|
+
const recordedAsPath = path57.resolve(callerCwd, opts.recorded);
|
|
18720
19004
|
let task;
|
|
18721
19005
|
let taskName;
|
|
18722
19006
|
let authoredApi;
|
|
18723
19007
|
let composition;
|
|
18724
|
-
const isSet =
|
|
19008
|
+
const isSet = existsSync47(path57.join(recordedAsPath, "recording-set.json"));
|
|
18725
19009
|
const registry = TASKS[opts.recorded];
|
|
18726
19010
|
if (registry !== void 0 && !isSet) {
|
|
18727
19011
|
task = registry;
|
|
@@ -18730,7 +19014,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18730
19014
|
try {
|
|
18731
19015
|
const authored = authorTaskFromSet(recordedAsPath);
|
|
18732
19016
|
task = authored.task;
|
|
18733
|
-
taskName =
|
|
19017
|
+
taskName = path57.basename(recordedAsPath);
|
|
18734
19018
|
authoredApi = authored.api;
|
|
18735
19019
|
const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
|
|
18736
19020
|
if (roles.success) composition = roles.data;
|
|
@@ -18764,7 +19048,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18764
19048
|
warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
|
|
18765
19049
|
}
|
|
18766
19050
|
const missing = task.configs.filter(
|
|
18767
|
-
(c) => !
|
|
19051
|
+
(c) => !existsSync47(path57.join(task.set, c.rep, "get_screenshot.json")) || !existsSync47(path57.join(task.set, c.rep, "get_metadata.json")) || !existsSync47(path57.join(task.set, c.rep, "get_design_context.json"))
|
|
18768
19052
|
);
|
|
18769
19053
|
if (missing.length > 0) {
|
|
18770
19054
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -18834,8 +19118,8 @@ async function runGenerateRecorded(opts) {
|
|
|
18834
19118
|
` : `${line}
|
|
18835
19119
|
`);
|
|
18836
19120
|
if (opts.dryRun) {
|
|
18837
|
-
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite:
|
|
18838
|
-
process.stdout.write(`dry-run: nothing sent, nothing written (would write ${
|
|
19121
|
+
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path57.join(outDirAbs, taskName) }, () => {
|
|
19122
|
+
process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path57.join(outDirAbs, taskName)})
|
|
18839
19123
|
`);
|
|
18840
19124
|
});
|
|
18841
19125
|
return;
|
|
@@ -18858,10 +19142,10 @@ async function runGenerateRecorded(opts) {
|
|
|
18858
19142
|
});
|
|
18859
19143
|
}
|
|
18860
19144
|
}
|
|
18861
|
-
const bundleDir =
|
|
18862
|
-
if (
|
|
19145
|
+
const bundleDir = path57.join(outDirAbs, taskName);
|
|
19146
|
+
if (existsSync47(path57.join(bundleDir, "component.json"))) {
|
|
18863
19147
|
try {
|
|
18864
|
-
const prior = readBundleManifest(
|
|
19148
|
+
const prior = readBundleManifest(readFileSync44(path57.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
18865
19149
|
if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
|
|
18866
19150
|
fail(opts, ExitCode.InputValidation, {
|
|
18867
19151
|
error: `${bundleDir} already holds a bundle bound to recording set "${prior.provenance.recordingSet.path}" \u2014 generating here against a different set would silently rewrite its verification identity`,
|
|
@@ -19028,7 +19312,7 @@ init_invocation();
|
|
|
19028
19312
|
init_output();
|
|
19029
19313
|
import { intro, isCancel, outro, password } from "@clack/prompts";
|
|
19030
19314
|
import fs from "node:fs";
|
|
19031
|
-
import
|
|
19315
|
+
import path31 from "node:path";
|
|
19032
19316
|
var INIT_DESCRIPTION = {
|
|
19033
19317
|
name: "init",
|
|
19034
19318
|
summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
|
|
@@ -19070,7 +19354,7 @@ async function runInit(flags) {
|
|
|
19070
19354
|
printDescription(INIT_DESCRIPTION);
|
|
19071
19355
|
return;
|
|
19072
19356
|
}
|
|
19073
|
-
const envPath =
|
|
19357
|
+
const envPath = path31.resolve(process.cwd(), ".env");
|
|
19074
19358
|
const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
|
|
19075
19359
|
let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
|
|
19076
19360
|
let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
|
|
@@ -19091,7 +19375,7 @@ async function runInit(flags) {
|
|
|
19091
19375
|
if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
|
|
19092
19376
|
if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
|
|
19093
19377
|
const changed = [...next].some(([key, value]) => existing.get(key) !== value);
|
|
19094
|
-
const gitignorePath =
|
|
19378
|
+
const gitignorePath = path31.resolve(process.cwd(), ".gitignore");
|
|
19095
19379
|
const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
|
|
19096
19380
|
const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
|
|
19097
19381
|
if (flags.dryRun) {
|
|
@@ -19147,14 +19431,14 @@ init_invocation();
|
|
|
19147
19431
|
init_output();
|
|
19148
19432
|
init_entitlement();
|
|
19149
19433
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
19150
|
-
import { readFileSync as
|
|
19434
|
+
import { readFileSync as readFileSync23, readdirSync as readdirSync9, existsSync as existsSync25 } from "node:fs";
|
|
19151
19435
|
|
|
19152
19436
|
// packages/cli/src/pipeline.ts
|
|
19153
19437
|
init_src2();
|
|
19154
19438
|
init_src5();
|
|
19155
19439
|
init_src4();
|
|
19156
|
-
import { mkdirSync as
|
|
19157
|
-
import
|
|
19440
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
19441
|
+
import path32 from "node:path";
|
|
19158
19442
|
|
|
19159
19443
|
// packages/cli/src/assets-module.ts
|
|
19160
19444
|
init_src();
|
|
@@ -19490,8 +19774,8 @@ async function runGenerationPipeline(input) {
|
|
|
19490
19774
|
});
|
|
19491
19775
|
const written = [];
|
|
19492
19776
|
if (!input.dryRun) {
|
|
19493
|
-
const dir =
|
|
19494
|
-
|
|
19777
|
+
const dir = path32.resolve(input.outDir, semantics.componentName);
|
|
19778
|
+
mkdirSync7(dir, { recursive: true });
|
|
19495
19779
|
const files = {
|
|
19496
19780
|
// Bundle-local tokens: THE emission the component's CSS resolves
|
|
19497
19781
|
// against — the same artifact the verify harness injects. A preview
|
|
@@ -19514,14 +19798,14 @@ async function runGenerationPipeline(input) {
|
|
|
19514
19798
|
`
|
|
19515
19799
|
};
|
|
19516
19800
|
for (const [name, content] of Object.entries(files)) {
|
|
19517
|
-
const filePath =
|
|
19518
|
-
|
|
19801
|
+
const filePath = path32.join(dir, name);
|
|
19802
|
+
writeFileSync11(filePath, content);
|
|
19519
19803
|
written.push(filePath);
|
|
19520
19804
|
}
|
|
19521
19805
|
for (const artifact of emitTokenArtifacts(input.mapping)) {
|
|
19522
|
-
const filePath =
|
|
19523
|
-
|
|
19524
|
-
|
|
19806
|
+
const filePath = path32.resolve(input.outDir, artifact.path);
|
|
19807
|
+
mkdirSync7(path32.dirname(filePath), { recursive: true });
|
|
19808
|
+
writeFileSync11(filePath, artifact.content);
|
|
19525
19809
|
written.push(filePath);
|
|
19526
19810
|
}
|
|
19527
19811
|
}
|
|
@@ -19579,7 +19863,7 @@ var GENERATE_DESCRIPTION = {
|
|
|
19579
19863
|
function resolveProvidedSource(flags, contextFile) {
|
|
19580
19864
|
let raw;
|
|
19581
19865
|
try {
|
|
19582
|
-
raw =
|
|
19866
|
+
raw = readFileSync23(contextFile, "utf8");
|
|
19583
19867
|
} catch {
|
|
19584
19868
|
fail(flags, ExitCode.InputValidation, {
|
|
19585
19869
|
error: `Cannot read context file "${contextFile}".`,
|
|
@@ -19699,11 +19983,11 @@ token mapping (${mapping.flat.length} variables):
|
|
|
19699
19983
|
let initialCode;
|
|
19700
19984
|
let initialSemantics;
|
|
19701
19985
|
try {
|
|
19702
|
-
if (
|
|
19986
|
+
if (existsSync25(flags.out)) {
|
|
19703
19987
|
for (const entry of readdirSync9(flags.out)) {
|
|
19704
19988
|
const cjPath = `${flags.out}/${entry}/component.json`;
|
|
19705
|
-
if (!
|
|
19706
|
-
const cj = JSON.parse(
|
|
19989
|
+
if (!existsSync25(cjPath)) continue;
|
|
19990
|
+
const cj = JSON.parse(readFileSync23(cjPath, "utf8"));
|
|
19707
19991
|
if (cj.name !== void 0 && Array.isArray(cj.props)) {
|
|
19708
19992
|
previousApi = JSON.stringify({
|
|
19709
19993
|
componentName: cj.name,
|
|
@@ -19711,14 +19995,14 @@ token mapping (${mapping.flat.length} variables):
|
|
|
19711
19995
|
});
|
|
19712
19996
|
const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
|
|
19713
19997
|
const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
|
|
19714
|
-
if (flags.refine &&
|
|
19998
|
+
if (flags.refine && existsSync25(tsxPath) && existsSync25(cssPath)) {
|
|
19715
19999
|
initialCode = {
|
|
19716
|
-
tsx:
|
|
19717
|
-
css:
|
|
20000
|
+
tsx: readFileSync23(tsxPath, "utf8"),
|
|
20001
|
+
css: readFileSync23(cssPath, "utf8")
|
|
19718
20002
|
};
|
|
19719
20003
|
const semPath = `${flags.out}/${entry}/semantics.json`;
|
|
19720
|
-
if (
|
|
19721
|
-
initialSemantics = JSON.parse(
|
|
20004
|
+
if (existsSync25(semPath)) {
|
|
20005
|
+
initialSemantics = JSON.parse(readFileSync23(semPath, "utf8"));
|
|
19722
20006
|
}
|
|
19723
20007
|
}
|
|
19724
20008
|
break;
|
|
@@ -20135,6 +20419,17 @@ function buildProgram() {
|
|
|
20135
20419
|
...local["deviceWait"] !== void 0 ? { deviceWait: local["deviceWait"] } : {}
|
|
20136
20420
|
});
|
|
20137
20421
|
});
|
|
20422
|
+
program.command("figma-connect").description("Grant this machine read access to your Figma files over Figma's API (approve in your browser, once) \u2014 recording then escapes the MCP daily call limit.").option("--to <url>", "the portal that brokers the connection (defaults to your signed-in portal)").option("--start", "agents: start the browser consent and return the link immediately").option("--wait", "agents: wait for the Allow click started by --start").action(async (_o, cmd) => {
|
|
20423
|
+
const flags = globalFlags(cmd.parent);
|
|
20424
|
+
const local = cmd.opts();
|
|
20425
|
+
const { runFigmaConnect: runFigmaConnect2 } = await Promise.resolve().then(() => (init_figma_connect(), figma_connect_exports));
|
|
20426
|
+
await runFigmaConnect2({
|
|
20427
|
+
...flags,
|
|
20428
|
+
...local["to"] !== void 0 ? { to: local["to"] } : {},
|
|
20429
|
+
...local["start"] !== void 0 ? { start: local["start"] } : {},
|
|
20430
|
+
...local["wait"] !== void 0 ? { wait: local["wait"] } : {}
|
|
20431
|
+
});
|
|
20432
|
+
});
|
|
20138
20433
|
program.command("logout").description("Forget this machine's stored portal session. Does not end the session itself.").action(async (_o, cmd) => {
|
|
20139
20434
|
const flags = globalFlags(cmd.parent);
|
|
20140
20435
|
const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|