@tendrilapp/cli 0.1.44 → 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 +14 -0
- package/dist/tendril.js +760 -489
- 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) {
|
|
@@ -8573,18 +8573,50 @@ var init_publish_client = __esm({
|
|
|
8573
8573
|
}
|
|
8574
8574
|
});
|
|
8575
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
|
+
|
|
8576
8608
|
// packages/cli/src/entitlement.ts
|
|
8577
|
-
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";
|
|
8578
8610
|
import crypto from "node:crypto";
|
|
8579
8611
|
import os5 from "node:os";
|
|
8580
|
-
import
|
|
8612
|
+
import path29 from "node:path";
|
|
8581
8613
|
function entitlementPath() {
|
|
8582
|
-
return process.env["TENDRIL_ENTITLEMENT_PATH"] ??
|
|
8614
|
+
return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path29.join(os5.homedir(), ".tendril", "entitlement.json");
|
|
8583
8615
|
}
|
|
8584
8616
|
function readStoredEntitlement(file = entitlementPath()) {
|
|
8585
|
-
if (!
|
|
8617
|
+
if (!existsSync23(file)) return void 0;
|
|
8586
8618
|
try {
|
|
8587
|
-
const parsed = JSON.parse(
|
|
8619
|
+
const parsed = JSON.parse(readFileSync21(file, "utf8"));
|
|
8588
8620
|
if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
|
|
8589
8621
|
return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
|
|
8590
8622
|
} catch {
|
|
@@ -8592,8 +8624,8 @@ function readStoredEntitlement(file = entitlementPath()) {
|
|
|
8592
8624
|
}
|
|
8593
8625
|
}
|
|
8594
8626
|
function writeStoredEntitlement(stored, file = entitlementPath()) {
|
|
8595
|
-
|
|
8596
|
-
|
|
8627
|
+
mkdirSync6(path29.dirname(file), { recursive: true });
|
|
8628
|
+
writeFileSync10(file, `${JSON.stringify(stored, null, 2)}
|
|
8597
8629
|
`);
|
|
8598
8630
|
chmodSync2(file, 384);
|
|
8599
8631
|
}
|
|
@@ -8677,9 +8709,9 @@ var init_entitlement = __esm({
|
|
|
8677
8709
|
|
|
8678
8710
|
// packages/cli/src/commands/doctor.ts
|
|
8679
8711
|
import { spawnSync } from "node:child_process";
|
|
8680
|
-
import { existsSync as
|
|
8712
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync8 } from "node:fs";
|
|
8681
8713
|
import os6 from "node:os";
|
|
8682
|
-
import
|
|
8714
|
+
import path30 from "node:path";
|
|
8683
8715
|
function withDeadline(work, ms) {
|
|
8684
8716
|
return Promise.race([
|
|
8685
8717
|
work,
|
|
@@ -8739,17 +8771,17 @@ async function runDoctorChecks(options) {
|
|
|
8739
8771
|
remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
|
|
8740
8772
|
});
|
|
8741
8773
|
}
|
|
8742
|
-
const fontManifest =
|
|
8774
|
+
const fontManifest = path30.join(fontCacheDir(), "manifest.json");
|
|
8743
8775
|
checks.push(
|
|
8744
|
-
|
|
8776
|
+
existsSync24(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync22(fontManifest, "utf8")).length} faces)` } : {
|
|
8745
8777
|
name: "font-cache",
|
|
8746
8778
|
ok: true,
|
|
8747
8779
|
detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
|
|
8748
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.`
|
|
8749
8781
|
}
|
|
8750
8782
|
);
|
|
8751
|
-
const pluginRoot =
|
|
8752
|
-
if (
|
|
8783
|
+
const pluginRoot = path30.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
|
|
8784
|
+
if (existsSync24(pluginRoot)) {
|
|
8753
8785
|
try {
|
|
8754
8786
|
const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
8755
8787
|
const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
|
|
@@ -8786,12 +8818,15 @@ async function runDoctorChecks(options) {
|
|
|
8786
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 }
|
|
8787
8819
|
);
|
|
8788
8820
|
checks.push(await portalSessionCheck(options.fetchImpl ?? fetch));
|
|
8821
|
+
checks.push(figmaRestCheck());
|
|
8789
8822
|
const figmaToken = resolveCredential("FIGMA_TOKEN");
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
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
|
+
}
|
|
8795
8830
|
return {
|
|
8796
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),
|
|
8797
8832
|
checks
|
|
@@ -8832,6 +8867,25 @@ async function portalSessionCheck(fetchImpl) {
|
|
|
8832
8867
|
...daysLeft !== null && daysLeft <= 7 ? { remediation: `That is ${String(daysLeft)} day(s) away \u2014 run ${tendrilCommand("login")} soon for a fresh session.` } : {}
|
|
8833
8868
|
};
|
|
8834
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
|
+
}
|
|
8835
8889
|
function probeVersion(binary) {
|
|
8836
8890
|
const windowsShim = /\.(cmd|bat)$/i.test(binary);
|
|
8837
8891
|
const res = windowsShim ? spawnSync(`"${binary}" --version`, { shell: true, timeout: 5e3, encoding: "utf8" }) : spawnSync(binary, ["--version"], { timeout: 5e3, encoding: "utf8" });
|
|
@@ -8906,6 +8960,7 @@ var init_doctor = __esm({
|
|
|
8906
8960
|
init_invocation();
|
|
8907
8961
|
init_output();
|
|
8908
8962
|
init_publish_client();
|
|
8963
|
+
init_figma_token();
|
|
8909
8964
|
init_entitlement();
|
|
8910
8965
|
DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
|
|
8911
8966
|
DOCTOR_DESCRIPTION = {
|
|
@@ -10021,8 +10076,8 @@ var init_engine_curated = __esm({
|
|
|
10021
10076
|
});
|
|
10022
10077
|
|
|
10023
10078
|
// packages/generate/src/loop.ts
|
|
10024
|
-
import { existsSync as
|
|
10025
|
-
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";
|
|
10026
10081
|
import { z as z13 } from "zod";
|
|
10027
10082
|
function objective(scores, behaviors) {
|
|
10028
10083
|
const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
|
|
@@ -10064,9 +10119,9 @@ ${absentLines.join("\n")}` : ""}
|
|
|
10064
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."}`;
|
|
10065
10120
|
}
|
|
10066
10121
|
function archivePriorRun(outDir) {
|
|
10067
|
-
if (!
|
|
10122
|
+
if (!existsSync26(path33.join(outDir, "run-log.json")) && !existsSync26(path33.join(outDir, "loop-state.json"))) return void 0;
|
|
10068
10123
|
let n = 1;
|
|
10069
|
-
while (
|
|
10124
|
+
while (existsSync26(`${outDir}-prev-${n}`)) n += 1;
|
|
10070
10125
|
renameSync(outDir, `${outDir}-prev-${n}`);
|
|
10071
10126
|
return `${outDir}-prev-${n}`;
|
|
10072
10127
|
}
|
|
@@ -10075,14 +10130,14 @@ async function runEngineLoop(opts) {
|
|
|
10075
10130
|
const plateau = opts.plateau ?? 2;
|
|
10076
10131
|
const progress = opts.onProgress ?? (() => {
|
|
10077
10132
|
});
|
|
10078
|
-
const statePath =
|
|
10079
|
-
const resuming = opts.resume === true &&
|
|
10133
|
+
const statePath = path33.join(opts.outDir, "loop-state.json");
|
|
10134
|
+
const resuming = opts.resume === true && existsSync26(statePath);
|
|
10080
10135
|
if (!resuming) {
|
|
10081
10136
|
const archived = archivePriorRun(opts.outDir);
|
|
10082
10137
|
if (archived !== void 0) progress(`previous run archived to ${archived}`);
|
|
10083
10138
|
}
|
|
10084
|
-
|
|
10085
|
-
const scratch =
|
|
10139
|
+
mkdirSync8(opts.outDir, { recursive: true });
|
|
10140
|
+
const scratch = path33.join(opts.outDir, ".candidate");
|
|
10086
10141
|
let attempts = [];
|
|
10087
10142
|
let log = [];
|
|
10088
10143
|
let best;
|
|
@@ -10090,7 +10145,7 @@ async function runEngineLoop(opts) {
|
|
|
10090
10145
|
let nonAccepted = 0;
|
|
10091
10146
|
let stopReason = "max-iterations";
|
|
10092
10147
|
if (resuming) {
|
|
10093
|
-
const restored = LoopStateSchema.parse(JSON.parse(
|
|
10148
|
+
const restored = LoopStateSchema.parse(JSON.parse(readFileSync24(statePath, "utf8")));
|
|
10094
10149
|
attempts = restored.attempts;
|
|
10095
10150
|
log = restored.iterations;
|
|
10096
10151
|
spentUsd = restored.spentUsd;
|
|
@@ -10105,12 +10160,12 @@ async function runEngineLoop(opts) {
|
|
|
10105
10160
|
progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
|
|
10106
10161
|
}
|
|
10107
10162
|
const persist = () => {
|
|
10108
|
-
|
|
10163
|
+
writeFileSync12(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
|
|
10109
10164
|
`);
|
|
10110
10165
|
};
|
|
10111
10166
|
const writeCandidate = (files) => {
|
|
10112
|
-
|
|
10113
|
-
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);
|
|
10114
10169
|
};
|
|
10115
10170
|
const scoreCandidate = async (candidate, iter, usd, modelMs) => {
|
|
10116
10171
|
writeCandidate(candidate.files);
|
|
@@ -10168,8 +10223,8 @@ async function runEngineLoop(opts) {
|
|
|
10168
10223
|
const usd = candidate.usage?.usd ?? 0;
|
|
10169
10224
|
spentUsd += usd;
|
|
10170
10225
|
if (candidate.raw !== void 0) {
|
|
10171
|
-
|
|
10172
|
-
|
|
10226
|
+
mkdirSync8(path33.join(opts.outDir, "responses"), { recursive: true });
|
|
10227
|
+
writeFileSync12(path33.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
|
|
10173
10228
|
}
|
|
10174
10229
|
if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
|
|
10175
10230
|
const finish = candidate.usage?.finishReason ?? "?";
|
|
@@ -10195,10 +10250,10 @@ async function runEngineLoop(opts) {
|
|
|
10195
10250
|
}
|
|
10196
10251
|
}
|
|
10197
10252
|
}
|
|
10198
|
-
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);
|
|
10199
10254
|
const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
|
|
10200
|
-
|
|
10201
|
-
|
|
10255
|
+
writeFileSync12(
|
|
10256
|
+
path33.join(opts.outDir, "run-log.json"),
|
|
10202
10257
|
`${JSON.stringify(
|
|
10203
10258
|
{
|
|
10204
10259
|
...opts.meta,
|
|
@@ -10265,8 +10320,8 @@ var init_loop2 = __esm({
|
|
|
10265
10320
|
});
|
|
10266
10321
|
|
|
10267
10322
|
// packages/generate/src/brief.ts
|
|
10268
|
-
import { existsSync as
|
|
10269
|
-
import
|
|
10323
|
+
import { existsSync as existsSync27, readFileSync as readFileSync25 } from "node:fs";
|
|
10324
|
+
import path34 from "node:path";
|
|
10270
10325
|
import { PNG as PNG3 } from "pngjs";
|
|
10271
10326
|
function singleAxes2(name) {
|
|
10272
10327
|
const parsed = parseVariantAxes(name);
|
|
@@ -10706,15 +10761,15 @@ function authorBehaviors(api, extras = {}) {
|
|
|
10706
10761
|
};
|
|
10707
10762
|
}
|
|
10708
10763
|
function envelopeText(file) {
|
|
10709
|
-
return envelopeFirstTextPart(JSON.parse(
|
|
10764
|
+
return envelopeFirstTextPart(JSON.parse(readFileSync25(file, "utf8")));
|
|
10710
10765
|
}
|
|
10711
10766
|
function metadataText(file) {
|
|
10712
|
-
return envelopeTextContent(JSON.parse(
|
|
10767
|
+
return envelopeTextContent(JSON.parse(readFileSync25(file, "utf8")));
|
|
10713
10768
|
}
|
|
10714
10769
|
function dismissEvidence(setDir, repSlugs) {
|
|
10715
10770
|
for (const slug of repSlugs) {
|
|
10716
|
-
const f =
|
|
10717
|
-
if (!
|
|
10771
|
+
const f = path34.join(setDir, slug, "get_design_context.json");
|
|
10772
|
+
if (!existsSync27(f)) continue;
|
|
10718
10773
|
const text = envelopeText(f);
|
|
10719
10774
|
const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
|
|
10720
10775
|
if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
|
|
@@ -10741,9 +10796,9 @@ function dismissEvidence(setDir, repSlugs) {
|
|
|
10741
10796
|
const glyphIsTheComponent = (() => {
|
|
10742
10797
|
const slugToCheck = vis.visibleIn[0];
|
|
10743
10798
|
if (slugToCheck === void 0) return false;
|
|
10744
|
-
const metaFile =
|
|
10799
|
+
const metaFile = path34.join(setDir, slugToCheck, "get_metadata.json");
|
|
10745
10800
|
try {
|
|
10746
|
-
const root = parseMetadataStructure(envelopeTextContent(JSON.parse(
|
|
10801
|
+
const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync25(metaFile, "utf8"))));
|
|
10747
10802
|
if (root.children.length !== 1) return false;
|
|
10748
10803
|
const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
|
|
10749
10804
|
return contains(root.children[0]);
|
|
@@ -10763,10 +10818,10 @@ function dismissEvidence(setDir, repSlugs) {
|
|
|
10763
10818
|
return void 0;
|
|
10764
10819
|
}
|
|
10765
10820
|
function recordedReferencePng(setDir, slug) {
|
|
10766
|
-
const f =
|
|
10767
|
-
if (!
|
|
10821
|
+
const f = path34.join(setDir, slug, "get_screenshot.json");
|
|
10822
|
+
if (!existsSync27(f)) return void 0;
|
|
10768
10823
|
try {
|
|
10769
|
-
const env = JSON.parse(
|
|
10824
|
+
const env = JSON.parse(readFileSync25(f, "utf8")).content.find((c) => c.type === "image");
|
|
10770
10825
|
return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
|
|
10771
10826
|
} catch {
|
|
10772
10827
|
return void 0;
|
|
@@ -10846,13 +10901,13 @@ function recordedFontNeeds(setDir, opts = {}) {
|
|
|
10846
10901
|
}
|
|
10847
10902
|
}
|
|
10848
10903
|
const manifest = loadManifest(setDir);
|
|
10849
|
-
const setDefs =
|
|
10850
|
-
if (
|
|
10904
|
+
const setDefs = path34.join(setDir, "get_variable_defs.json");
|
|
10905
|
+
if (existsSync27(setDefs)) fromDefs(envelopeText(setDefs));
|
|
10851
10906
|
for (const rep of manifest.reps) {
|
|
10852
|
-
const ctx =
|
|
10853
|
-
if (
|
|
10854
|
-
const defs =
|
|
10855
|
-
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));
|
|
10856
10911
|
}
|
|
10857
10912
|
return [...byFamily.entries()].map(([family, paired]) => {
|
|
10858
10913
|
const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
|
|
@@ -10863,10 +10918,10 @@ function symbolFontGlyphCount(setDir, reps) {
|
|
|
10863
10918
|
const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
|
|
10864
10919
|
const glyphs = /* @__PURE__ */ new Set();
|
|
10865
10920
|
for (const rep of reps) {
|
|
10866
|
-
const file =
|
|
10867
|
-
if (!
|
|
10921
|
+
const file = path34.join(setDir, rep, "get_metadata.json");
|
|
10922
|
+
if (!existsSync27(file)) continue;
|
|
10868
10923
|
try {
|
|
10869
|
-
const text = JSON.parse(
|
|
10924
|
+
const text = JSON.parse(readFileSync25(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
10870
10925
|
for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
|
|
10871
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)));
|
|
10872
10927
|
if (PUA.test(name)) glyphs.add(name);
|
|
@@ -10892,8 +10947,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
10892
10947
|
const propRep = [];
|
|
10893
10948
|
const perRep = [];
|
|
10894
10949
|
for (const slug of repSlugs) {
|
|
10895
|
-
const f =
|
|
10896
|
-
if (!
|
|
10950
|
+
const f = path34.join(setDir, slug, "get_design_context.json");
|
|
10951
|
+
if (!existsSync27(f)) continue;
|
|
10897
10952
|
const code = envelopeText(f);
|
|
10898
10953
|
const props = /* @__PURE__ */ new Map();
|
|
10899
10954
|
for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
|
|
@@ -10919,8 +10974,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
10919
10974
|
const axisValuesBySlug = /* @__PURE__ */ new Map();
|
|
10920
10975
|
const valuesByAxis = /* @__PURE__ */ new Map();
|
|
10921
10976
|
for (const slug of repSlugs) {
|
|
10922
|
-
const metaFile =
|
|
10923
|
-
if (!
|
|
10977
|
+
const metaFile = path34.join(setDir, slug, "get_metadata.json");
|
|
10978
|
+
if (!existsSync27(metaFile)) continue;
|
|
10924
10979
|
const name = symbolName(metadataText(metaFile));
|
|
10925
10980
|
if (name === void 0) continue;
|
|
10926
10981
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -11035,8 +11090,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
11035
11090
|
const poses = [];
|
|
11036
11091
|
const missing = [];
|
|
11037
11092
|
for (const rep of manifest.reps) {
|
|
11038
|
-
const metaFile =
|
|
11039
|
-
if (!
|
|
11093
|
+
const metaFile = path34.join(setDir, rep.slug, "get_metadata.json");
|
|
11094
|
+
if (!existsSync27(metaFile)) {
|
|
11040
11095
|
missing.push(rep.slug);
|
|
11041
11096
|
continue;
|
|
11042
11097
|
}
|
|
@@ -11050,8 +11105,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
11050
11105
|
if (missing.length > 0) {
|
|
11051
11106
|
throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
|
|
11052
11107
|
}
|
|
11053
|
-
const setMeta =
|
|
11054
|
-
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);
|
|
11055
11110
|
const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
|
|
11056
11111
|
const recordedFonts = recordedFontFamilies(setDir);
|
|
11057
11112
|
const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
|
|
@@ -11252,17 +11307,17 @@ var init_brief = __esm({
|
|
|
11252
11307
|
});
|
|
11253
11308
|
|
|
11254
11309
|
// packages/generate/src/segments.ts
|
|
11255
|
-
import { existsSync as
|
|
11256
|
-
import
|
|
11310
|
+
import { existsSync as existsSync28, readFileSync as readFileSync26, readdirSync as readdirSync10 } from "node:fs";
|
|
11311
|
+
import path35 from "node:path";
|
|
11257
11312
|
function repText(set, rep, tool) {
|
|
11258
|
-
const env = JSON.parse(
|
|
11313
|
+
const env = JSON.parse(readFileSync26(path35.join(set, rep, `${tool}.json`), "utf8"));
|
|
11259
11314
|
return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
|
|
11260
11315
|
}
|
|
11261
11316
|
function refPngDims(set, rep) {
|
|
11262
|
-
const f =
|
|
11263
|
-
if (!
|
|
11317
|
+
const f = path35.join(set, rep, "get_screenshot.json");
|
|
11318
|
+
if (!existsSync28(f)) return void 0;
|
|
11264
11319
|
try {
|
|
11265
|
-
const env = JSON.parse(
|
|
11320
|
+
const env = JSON.parse(readFileSync26(f, "utf8")).content.find((c) => c.type === "image");
|
|
11266
11321
|
if (env?.data === void 0) return void 0;
|
|
11267
11322
|
const buf = Buffer.from(env.data, "base64");
|
|
11268
11323
|
if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
|
|
@@ -11328,20 +11383,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
|
|
|
11328
11383
|
}
|
|
11329
11384
|
function buildSegments(task, mode = "fenced") {
|
|
11330
11385
|
const SET = task.set;
|
|
11331
|
-
let defsRecorded =
|
|
11386
|
+
let defsRecorded = existsSync28(path35.join(SET, "get_variable_defs.json"));
|
|
11332
11387
|
let rawDefs = {};
|
|
11333
|
-
if (
|
|
11334
|
-
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"))) || "{}";
|
|
11335
11390
|
try {
|
|
11336
11391
|
rawDefs = JSON.parse(text);
|
|
11337
11392
|
} catch {
|
|
11338
11393
|
}
|
|
11339
11394
|
} else {
|
|
11340
11395
|
for (const cfg of task.configs) {
|
|
11341
|
-
const f =
|
|
11342
|
-
if (!
|
|
11396
|
+
const f = path35.join(SET, cfg.rep, "get_variable_defs.json");
|
|
11397
|
+
if (!existsSync28(f)) continue;
|
|
11343
11398
|
defsRecorded = true;
|
|
11344
|
-
const text = envelopeFirstTextPart(JSON.parse(
|
|
11399
|
+
const text = envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8"))) || "{}";
|
|
11345
11400
|
try {
|
|
11346
11401
|
for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
|
|
11347
11402
|
} catch {
|
|
@@ -11349,8 +11404,8 @@ function buildSegments(task, mode = "fenced") {
|
|
|
11349
11404
|
}
|
|
11350
11405
|
}
|
|
11351
11406
|
const emissionTexts = task.configs.map((cfg) => {
|
|
11352
|
-
const f =
|
|
11353
|
-
return
|
|
11407
|
+
const f = path35.join(SET, cfg.rep, "get_design_context.json");
|
|
11408
|
+
return existsSync28(f) ? envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8"))) : "";
|
|
11354
11409
|
});
|
|
11355
11410
|
const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
|
|
11356
11411
|
const defs = JSON.stringify(map, null, 1);
|
|
@@ -11368,9 +11423,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
|
|
|
11368
11423
|
for (const cfg of task.configs) {
|
|
11369
11424
|
const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
|
|
11370
11425
|
const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
|
|
11371
|
-
const assets = readdirSync10(
|
|
11426
|
+
const assets = readdirSync10(path35.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
|
|
11372
11427
|
\`\`\`svg
|
|
11373
|
-
${
|
|
11428
|
+
${readFileSync26(path35.join(SET, cfg.rep, f), "utf8")}
|
|
11374
11429
|
\`\`\``).join("\n");
|
|
11375
11430
|
const refNote = (() => {
|
|
11376
11431
|
const dims = refPngDims(SET, cfg.rep);
|
|
@@ -11406,7 +11461,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
|
|
|
11406
11461
|
} else {
|
|
11407
11462
|
parts.push(`
|
|
11408
11463
|
## Output format
|
|
11409
|
-
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.`);
|
|
11410
11465
|
}
|
|
11411
11466
|
return parts.join("\n");
|
|
11412
11467
|
}
|
|
@@ -11474,8 +11529,8 @@ var init_adapter = __esm({
|
|
|
11474
11529
|
|
|
11475
11530
|
// packages/generate/src/bundle-emit.ts
|
|
11476
11531
|
import { createHash as createHash6 } from "node:crypto";
|
|
11477
|
-
import { copyFileSync, existsSync as
|
|
11478
|
-
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";
|
|
11479
11534
|
function pinFromConfigs(configs) {
|
|
11480
11535
|
const domains = /* @__PURE__ */ new Map();
|
|
11481
11536
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -11544,9 +11599,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11544
11599
|
const notices = [];
|
|
11545
11600
|
const licenseTexts = /* @__PURE__ */ new Map();
|
|
11546
11601
|
for (const face of faces) {
|
|
11547
|
-
const src =
|
|
11548
|
-
const target = `./fonts/${
|
|
11549
|
-
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";
|
|
11550
11605
|
const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
|
|
11551
11606
|
const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
|
|
11552
11607
|
const license = normalizeFontLicense(face.license);
|
|
@@ -11584,14 +11639,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11584
11639
|
`/* ${decl} */`
|
|
11585
11640
|
);
|
|
11586
11641
|
}
|
|
11587
|
-
} else if (
|
|
11588
|
-
|
|
11589
|
-
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)));
|
|
11590
11645
|
licenseTexts.set(terms.file, terms.text);
|
|
11591
11646
|
const upstream = upstreamAttribution(face);
|
|
11592
11647
|
notices.push(
|
|
11593
11648
|
"",
|
|
11594
|
-
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${
|
|
11649
|
+
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path36.basename(face.file)}`,
|
|
11595
11650
|
` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
|
|
11596
11651
|
` source: ${face.source}`,
|
|
11597
11652
|
` sha256: ${face.sha256}`,
|
|
@@ -11605,9 +11660,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11605
11660
|
}
|
|
11606
11661
|
if (lines.length === 0) return null;
|
|
11607
11662
|
if (notices.length > 0) {
|
|
11608
|
-
const fontsDir =
|
|
11609
|
-
for (const [file, text] of licenseTexts)
|
|
11610
|
-
|
|
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")}
|
|
11611
11666
|
`);
|
|
11612
11667
|
header.push(
|
|
11613
11668
|
"/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
|
|
@@ -11619,10 +11674,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11619
11674
|
`;
|
|
11620
11675
|
}
|
|
11621
11676
|
function countLatticeSymbols(setDir) {
|
|
11622
|
-
const manifestFile =
|
|
11623
|
-
if (
|
|
11677
|
+
const manifestFile = path36.join(setDir, "recording-set.json");
|
|
11678
|
+
if (existsSync29(manifestFile)) {
|
|
11624
11679
|
try {
|
|
11625
|
-
const stored = JSON.parse(
|
|
11680
|
+
const stored = JSON.parse(readFileSync27(manifestFile, "utf8"));
|
|
11626
11681
|
if (stored.variantScope !== "component-set") return null;
|
|
11627
11682
|
const lattice = stored.latticeNames;
|
|
11628
11683
|
if (lattice !== void 0 && lattice.length > 0) return lattice.length;
|
|
@@ -11630,13 +11685,13 @@ function countLatticeSymbols(setDir) {
|
|
|
11630
11685
|
}
|
|
11631
11686
|
}
|
|
11632
11687
|
const files = [
|
|
11633
|
-
|
|
11634
|
-
...
|
|
11635
|
-
].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));
|
|
11636
11691
|
if (files.length === 0) return null;
|
|
11637
11692
|
let count = 0;
|
|
11638
11693
|
for (const f of files) {
|
|
11639
|
-
const text = envelopeTextContent(JSON.parse(
|
|
11694
|
+
const text = envelopeTextContent(JSON.parse(readFileSync27(f, "utf8")));
|
|
11640
11695
|
count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
|
|
11641
11696
|
}
|
|
11642
11697
|
return count > 0 ? count : null;
|
|
@@ -11644,21 +11699,21 @@ function countLatticeSymbols(setDir) {
|
|
|
11644
11699
|
function recordingSetHash(setDir, configs) {
|
|
11645
11700
|
const relPaths = [];
|
|
11646
11701
|
for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
|
|
11647
|
-
if (
|
|
11702
|
+
if (existsSync29(path36.join(setDir, name))) relPaths.push(name);
|
|
11648
11703
|
}
|
|
11649
11704
|
for (const cfg of configs) {
|
|
11650
11705
|
for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
|
|
11651
|
-
if (
|
|
11706
|
+
if (existsSync29(path36.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
|
|
11652
11707
|
}
|
|
11653
|
-
if (
|
|
11654
|
-
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-"))) {
|
|
11655
11710
|
relPaths.push(`${cfg.rep}/${asset}`);
|
|
11656
11711
|
}
|
|
11657
11712
|
}
|
|
11658
11713
|
}
|
|
11659
11714
|
return hashRecordingSet(
|
|
11660
11715
|
relPaths,
|
|
11661
|
-
(p) => new Uint8Array(
|
|
11716
|
+
(p) => new Uint8Array(readFileSync27(path36.join(setDir, p))),
|
|
11662
11717
|
(chunks) => {
|
|
11663
11718
|
const h = createHash6("sha256");
|
|
11664
11719
|
for (const c of chunks) h.update(c);
|
|
@@ -11699,8 +11754,8 @@ function emitBundleV1(opts) {
|
|
|
11699
11754
|
const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
|
|
11700
11755
|
const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
11701
11756
|
const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
|
|
11702
|
-
const cssFiles = ["styles.css", "tokens.css"].map((f) =>
|
|
11703
|
-
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"));
|
|
11704
11759
|
const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
|
|
11705
11760
|
family: f.family,
|
|
11706
11761
|
weight: f.weight,
|
|
@@ -11729,7 +11784,7 @@ function emitBundleV1(opts) {
|
|
|
11729
11784
|
// resolvable via verify's --set override).
|
|
11730
11785
|
path: (() => {
|
|
11731
11786
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
11732
|
-
const rel =
|
|
11787
|
+
const rel = path36.relative(base, opts.task.set);
|
|
11733
11788
|
return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
|
|
11734
11789
|
})(),
|
|
11735
11790
|
component: opts.componentName,
|
|
@@ -11766,25 +11821,25 @@ function emitBundleV1(opts) {
|
|
|
11766
11821
|
})
|
|
11767
11822
|
};
|
|
11768
11823
|
const written = [];
|
|
11769
|
-
const manifestPath2 =
|
|
11770
|
-
|
|
11824
|
+
const manifestPath2 = path36.join(opts.bundleDir, "component.json");
|
|
11825
|
+
writeFileSync13(manifestPath2, `${JSON.stringify(manifest, null, 2)}
|
|
11771
11826
|
`);
|
|
11772
11827
|
written.push(manifestPath2);
|
|
11773
|
-
const stylesPath =
|
|
11774
|
-
if (
|
|
11828
|
+
const stylesPath = path36.join(opts.bundleDir, "styles.css");
|
|
11829
|
+
if (existsSync29(stylesPath)) {
|
|
11775
11830
|
const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
|
|
11776
|
-
const current =
|
|
11831
|
+
const current = readFileSync27(stylesPath, "utf8");
|
|
11777
11832
|
const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
|
|
11778
|
-
|
|
11833
|
+
writeFileSync13(stylesPath, `${comment}
|
|
11779
11834
|
${stripped}`);
|
|
11780
11835
|
written.push(stylesPath);
|
|
11781
11836
|
}
|
|
11782
|
-
const fontsCssPath =
|
|
11783
|
-
|
|
11784
|
-
|
|
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 });
|
|
11785
11840
|
const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
|
|
11786
11841
|
if (fontsCss !== null) {
|
|
11787
|
-
|
|
11842
|
+
writeFileSync13(fontsCssPath, fontsCss);
|
|
11788
11843
|
written.push(fontsCssPath);
|
|
11789
11844
|
}
|
|
11790
11845
|
const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
|
|
@@ -12213,8 +12268,8 @@ DEALINGS IN THE FONT SOFTWARE.
|
|
|
12213
12268
|
|
|
12214
12269
|
// packages/generate/src/compose-pins.ts
|
|
12215
12270
|
import { createHash as createHash7 } from "node:crypto";
|
|
12216
|
-
import { existsSync as
|
|
12217
|
-
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";
|
|
12218
12273
|
function bundleDirs(roots, depth = 4) {
|
|
12219
12274
|
const found = [];
|
|
12220
12275
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12223,11 +12278,11 @@ function bundleDirs(roots, depth = 4) {
|
|
|
12223
12278
|
try {
|
|
12224
12279
|
key = realpathSync3(dir);
|
|
12225
12280
|
} catch {
|
|
12226
|
-
key =
|
|
12281
|
+
key = path37.resolve(dir);
|
|
12227
12282
|
}
|
|
12228
12283
|
if (seen.has(key)) return;
|
|
12229
12284
|
seen.add(key);
|
|
12230
|
-
if (
|
|
12285
|
+
if (existsSync30(path37.join(dir, "component.json"))) {
|
|
12231
12286
|
found.push(key);
|
|
12232
12287
|
return;
|
|
12233
12288
|
}
|
|
@@ -12240,14 +12295,14 @@ function bundleDirs(roots, depth = 4) {
|
|
|
12240
12295
|
}
|
|
12241
12296
|
for (const e of entries) {
|
|
12242
12297
|
if (e === "node_modules" || e.startsWith(".")) continue;
|
|
12243
|
-
const full =
|
|
12298
|
+
const full = path37.join(dir, e);
|
|
12244
12299
|
try {
|
|
12245
12300
|
if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
|
|
12246
12301
|
} catch {
|
|
12247
12302
|
}
|
|
12248
12303
|
}
|
|
12249
12304
|
};
|
|
12250
|
-
for (const r of roots) walk2(
|
|
12305
|
+
for (const r of roots) walk2(path37.resolve(r), depth);
|
|
12251
12306
|
return found;
|
|
12252
12307
|
}
|
|
12253
12308
|
function composedPins(hostSet, libraryRoots) {
|
|
@@ -12266,7 +12321,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12266
12321
|
let pinned = false;
|
|
12267
12322
|
const failures = [];
|
|
12268
12323
|
for (const rel of partnerRels) {
|
|
12269
|
-
const partnerSet =
|
|
12324
|
+
const partnerSet = path37.resolve(hostSet, rel);
|
|
12270
12325
|
let partnerTask;
|
|
12271
12326
|
let partnerManifest;
|
|
12272
12327
|
try {
|
|
@@ -12295,7 +12350,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12295
12350
|
const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
|
|
12296
12351
|
const matches = candidates.filter((dir) => {
|
|
12297
12352
|
try {
|
|
12298
|
-
const parsed = readBundleManifest(
|
|
12353
|
+
const parsed = readBundleManifest(readFileSync28(path37.join(dir, "component.json"), "utf8"));
|
|
12299
12354
|
return parsed.manifest?.provenance.recordingSet.hash === wantHash;
|
|
12300
12355
|
} catch {
|
|
12301
12356
|
return false;
|
|
@@ -12308,13 +12363,13 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12308
12363
|
continue;
|
|
12309
12364
|
}
|
|
12310
12365
|
if (matches.length > 1) {
|
|
12311
|
-
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`);
|
|
12312
12367
|
continue;
|
|
12313
12368
|
}
|
|
12314
12369
|
const bundleDir = matches[0];
|
|
12315
12370
|
let manifest;
|
|
12316
12371
|
try {
|
|
12317
|
-
manifest = readBundleManifest(
|
|
12372
|
+
manifest = readBundleManifest(readFileSync28(path37.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
12318
12373
|
} catch {
|
|
12319
12374
|
manifest = void 0;
|
|
12320
12375
|
}
|
|
@@ -12331,8 +12386,8 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12331
12386
|
const moduleFiles = [];
|
|
12332
12387
|
let fileIssue;
|
|
12333
12388
|
for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
|
|
12334
|
-
const file =
|
|
12335
|
-
if (!
|
|
12389
|
+
const file = path37.join(bundleDir, name);
|
|
12390
|
+
if (!existsSync30(file)) {
|
|
12336
12391
|
if (name === manifest.entry || name === "styles.css") {
|
|
12337
12392
|
fileIssue = `${rel}: partner bundle is missing ${name}`;
|
|
12338
12393
|
break;
|
|
@@ -12341,7 +12396,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
12341
12396
|
}
|
|
12342
12397
|
let bytes;
|
|
12343
12398
|
try {
|
|
12344
|
-
bytes =
|
|
12399
|
+
bytes = readFileSync28(file);
|
|
12345
12400
|
} catch {
|
|
12346
12401
|
fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
|
|
12347
12402
|
break;
|
|
@@ -12413,14 +12468,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
|
|
|
12413
12468
|
const checks = [];
|
|
12414
12469
|
let entrySource = "";
|
|
12415
12470
|
try {
|
|
12416
|
-
entrySource =
|
|
12471
|
+
entrySource = readFileSync28(path37.join(candidateDir, hostEntry), "utf8");
|
|
12417
12472
|
} catch {
|
|
12418
12473
|
}
|
|
12419
|
-
const candidateRoot =
|
|
12474
|
+
const candidateRoot = path37.resolve(candidateDir);
|
|
12420
12475
|
for (const pin of pins) {
|
|
12421
12476
|
const dir = composedModuleDir(pin.partnerName);
|
|
12422
|
-
const resolvedDir =
|
|
12423
|
-
if (!resolvedDir.startsWith(candidateRoot +
|
|
12477
|
+
const resolvedDir = path37.resolve(candidateDir, dir);
|
|
12478
|
+
if (!resolvedDir.startsWith(candidateRoot + path37.sep)) {
|
|
12424
12479
|
checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
|
|
12425
12480
|
checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
|
|
12426
12481
|
continue;
|
|
@@ -12431,12 +12486,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
|
|
|
12431
12486
|
wrong.push(`${f.name} refused (not a plain path segment)`);
|
|
12432
12487
|
continue;
|
|
12433
12488
|
}
|
|
12434
|
-
const target =
|
|
12435
|
-
if (!
|
|
12489
|
+
const target = path37.join(candidateDir, dir, f.name);
|
|
12490
|
+
if (!existsSync30(target)) {
|
|
12436
12491
|
wrong.push(`${f.name} missing`);
|
|
12437
12492
|
continue;
|
|
12438
12493
|
}
|
|
12439
|
-
const sha = createHash7("sha256").update(
|
|
12494
|
+
const sha = createHash7("sha256").update(readFileSync28(target)).digest("hex");
|
|
12440
12495
|
if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
|
|
12441
12496
|
}
|
|
12442
12497
|
checks.push({
|
|
@@ -12461,10 +12516,10 @@ function rootClassesFor(emission, nodeId) {
|
|
|
12461
12516
|
}
|
|
12462
12517
|
function regionOverrides(hostSet, partnerSet, instances) {
|
|
12463
12518
|
const read = (setDir, rep) => {
|
|
12464
|
-
const f =
|
|
12465
|
-
if (!
|
|
12519
|
+
const f = path37.join(setDir, rep, "get_design_context.json");
|
|
12520
|
+
if (!existsSync30(f)) return void 0;
|
|
12466
12521
|
try {
|
|
12467
|
-
return envelopeFirstTextPart(JSON.parse(
|
|
12522
|
+
return envelopeFirstTextPart(JSON.parse(readFileSync28(f, "utf8")));
|
|
12468
12523
|
} catch {
|
|
12469
12524
|
return void 0;
|
|
12470
12525
|
}
|
|
@@ -12494,7 +12549,7 @@ var init_compose_pins = __esm({
|
|
|
12494
12549
|
init_src4();
|
|
12495
12550
|
init_brief();
|
|
12496
12551
|
init_bundle_emit();
|
|
12497
|
-
composedModuleDir = (partnerName) =>
|
|
12552
|
+
composedModuleDir = (partnerName) => path37.posix.join("composed", partnerName);
|
|
12498
12553
|
safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
|
|
12499
12554
|
MAX_PINNED_FILE_BYTES = 1024 * 1024;
|
|
12500
12555
|
CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
|
|
@@ -12502,8 +12557,8 @@ var init_compose_pins = __esm({
|
|
|
12502
12557
|
});
|
|
12503
12558
|
|
|
12504
12559
|
// packages/generate/src/motion.ts
|
|
12505
|
-
import { existsSync as
|
|
12506
|
-
import
|
|
12560
|
+
import { existsSync as existsSync31, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
|
|
12561
|
+
import path38 from "node:path";
|
|
12507
12562
|
function springProgress(u, bounce) {
|
|
12508
12563
|
const decay = Math.log(100);
|
|
12509
12564
|
if (bounce <= 0) {
|
|
@@ -12574,10 +12629,10 @@ function reportsNoMotion(text) {
|
|
|
12574
12629
|
});
|
|
12575
12630
|
}
|
|
12576
12631
|
function motionTruthFor(setDir) {
|
|
12577
|
-
const file =
|
|
12578
|
-
if (
|
|
12632
|
+
const file = path38.join(setDir, "get_motion_context.json");
|
|
12633
|
+
if (existsSync31(file) && usableEnvelope(file, "get_motion_context").ok) {
|
|
12579
12634
|
try {
|
|
12580
|
-
const text = envelopeTextContent(JSON.parse(
|
|
12635
|
+
const text = envelopeTextContent(JSON.parse(readFileSync29(file, "utf8")));
|
|
12581
12636
|
if (text.trim() === "") return { state: "recorded-empty" };
|
|
12582
12637
|
return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
|
|
12583
12638
|
} catch {
|
|
@@ -12590,21 +12645,21 @@ function motionTruthFor(setDir) {
|
|
|
12590
12645
|
}
|
|
12591
12646
|
}
|
|
12592
12647
|
function motionDisclosure(bundleDir, setDir) {
|
|
12593
|
-
const sheets = ["styles.css", "tokens.css"].map((f) =>
|
|
12594
|
-
const composedRoot =
|
|
12648
|
+
const sheets = ["styles.css", "tokens.css"].map((f) => path38.join(bundleDir, f));
|
|
12649
|
+
const composedRoot = path38.join(bundleDir, "composed");
|
|
12595
12650
|
try {
|
|
12596
12651
|
for (const entry of readdirSync13(composedRoot).sort()) {
|
|
12597
|
-
const dir =
|
|
12652
|
+
const dir = path38.join(composedRoot, entry);
|
|
12598
12653
|
try {
|
|
12599
12654
|
if (!statSync5(dir).isDirectory()) continue;
|
|
12600
12655
|
} catch {
|
|
12601
12656
|
continue;
|
|
12602
12657
|
}
|
|
12603
|
-
sheets.push(
|
|
12658
|
+
sheets.push(path38.join(dir, "styles.css"), path38.join(dir, "tokens.css"));
|
|
12604
12659
|
}
|
|
12605
12660
|
} catch {
|
|
12606
12661
|
}
|
|
12607
|
-
const css = sheets.filter((f) =>
|
|
12662
|
+
const css = sheets.filter((f) => existsSync31(f)).map((f) => readFileSync29(f, "utf8")).join("\n");
|
|
12608
12663
|
if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
|
|
12609
12664
|
return {
|
|
12610
12665
|
present: true,
|
|
@@ -12971,12 +13026,12 @@ var init_components = __esm({
|
|
|
12971
13026
|
|
|
12972
13027
|
// packages/generate/src/codebase/walk.ts
|
|
12973
13028
|
import fs2 from "node:fs";
|
|
12974
|
-
import
|
|
13029
|
+
import path39 from "node:path";
|
|
12975
13030
|
function resolvedPathIsExcluded(real, roots) {
|
|
12976
|
-
if (isNeverRead(
|
|
13031
|
+
if (isNeverRead(path39.basename(real))) return true;
|
|
12977
13032
|
for (const root of roots) {
|
|
12978
|
-
if (real !== root && !real.startsWith(root +
|
|
12979
|
-
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)) {
|
|
12980
13035
|
if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
|
|
12981
13036
|
}
|
|
12982
13037
|
}
|
|
@@ -12990,7 +13045,7 @@ function containedRealpath(abs, roots) {
|
|
|
12990
13045
|
return null;
|
|
12991
13046
|
}
|
|
12992
13047
|
for (const root of roots) {
|
|
12993
|
-
if (real === root || real.startsWith(root +
|
|
13048
|
+
if (real === root || real.startsWith(root + path39.sep)) return real;
|
|
12994
13049
|
}
|
|
12995
13050
|
return null;
|
|
12996
13051
|
}
|
|
@@ -13023,7 +13078,7 @@ function walkRepo(roots, limits, accept) {
|
|
|
13023
13078
|
continue;
|
|
13024
13079
|
}
|
|
13025
13080
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13026
|
-
const abs =
|
|
13081
|
+
const abs = path39.join(frame.dir, entry.name);
|
|
13027
13082
|
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
13028
13083
|
if (isNeverRead(entry.name)) continue;
|
|
13029
13084
|
const real = containedRealpath(abs, realRoots);
|
|
@@ -13111,18 +13166,18 @@ var init_walk = __esm({
|
|
|
13111
13166
|
/^\.netrc$/i
|
|
13112
13167
|
];
|
|
13113
13168
|
isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
|
|
13114
|
-
toRel = (root, abs) =>
|
|
13169
|
+
toRel = (root, abs) => path39.relative(root, abs).split(path39.sep).join(path39.posix.sep);
|
|
13115
13170
|
}
|
|
13116
13171
|
});
|
|
13117
13172
|
|
|
13118
13173
|
// packages/generate/src/codebase/scan.ts
|
|
13119
13174
|
import crypto2 from "node:crypto";
|
|
13120
13175
|
import fs3 from "node:fs";
|
|
13121
|
-
import
|
|
13176
|
+
import path40 from "node:path";
|
|
13122
13177
|
import postcss3 from "postcss";
|
|
13123
13178
|
function scanCodebase(options) {
|
|
13124
13179
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
13125
|
-
const roots = options.roots.map((r) =>
|
|
13180
|
+
const roots = options.roots.map((r) => path40.resolve(r));
|
|
13126
13181
|
const walk2 = walkRepo(
|
|
13127
13182
|
roots,
|
|
13128
13183
|
{
|
|
@@ -13142,7 +13197,7 @@ function scanCodebase(options) {
|
|
|
13142
13197
|
let bytesRead = 0;
|
|
13143
13198
|
let filesRead = 0;
|
|
13144
13199
|
for (const file of walk2.files) {
|
|
13145
|
-
const base =
|
|
13200
|
+
const base = path40.posix.basename(file.rel);
|
|
13146
13201
|
configFiles.add(file.rel);
|
|
13147
13202
|
if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
|
|
13148
13203
|
const text = readTextFile(file.abs);
|
|
@@ -13158,7 +13213,7 @@ function scanCodebase(options) {
|
|
|
13158
13213
|
}
|
|
13159
13214
|
const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
|
|
13160
13215
|
const components = scanComponents(componentFiles);
|
|
13161
|
-
const packages = manifests.filter((m) =>
|
|
13216
|
+
const packages = manifests.filter((m) => path40.posix.basename(m.rel) === "package.json");
|
|
13162
13217
|
const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
|
|
13163
13218
|
const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
|
|
13164
13219
|
const disclosures = buildDisclosures(
|
|
@@ -13201,13 +13256,13 @@ function scanCodebase(options) {
|
|
|
13201
13256
|
},
|
|
13202
13257
|
components: {
|
|
13203
13258
|
entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
|
|
13204
|
-
fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(
|
|
13259
|
+
fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path40.posix.basename(f.rel)))),
|
|
13205
13260
|
directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
|
|
13206
13261
|
exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
|
|
13207
13262
|
classNameStyle,
|
|
13208
13263
|
colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
|
|
13209
13264
|
barrelFiles: componentFiles.filter(
|
|
13210
|
-
(f) => /^index\.[tj]sx?$/.test(
|
|
13265
|
+
(f) => /^index\.[tj]sx?$/.test(path40.posix.basename(f.rel)) && isReExportOnly(f.text)
|
|
13211
13266
|
).length,
|
|
13212
13267
|
refForwarding: {
|
|
13213
13268
|
forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
|
|
@@ -13230,7 +13285,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
|
|
|
13230
13285
|
};
|
|
13231
13286
|
const deps = /* @__PURE__ */ new Map();
|
|
13232
13287
|
for (const manifest of manifests) {
|
|
13233
|
-
if (
|
|
13288
|
+
if (path40.posix.basename(manifest.rel) !== "package.json") continue;
|
|
13234
13289
|
try {
|
|
13235
13290
|
const parsed = JSON.parse(manifest.text);
|
|
13236
13291
|
for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
@@ -13242,7 +13297,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
|
|
|
13242
13297
|
}
|
|
13243
13298
|
}
|
|
13244
13299
|
for (const cfg of configFiles) {
|
|
13245
|
-
const base =
|
|
13300
|
+
const base = path40.posix.basename(cfg);
|
|
13246
13301
|
if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
|
|
13247
13302
|
if (base === "components.json") add("shadcn-style", "file", cfg);
|
|
13248
13303
|
}
|
|
@@ -13300,8 +13355,8 @@ function collectClassNames(cssFiles) {
|
|
|
13300
13355
|
return [...distinct].sort().map(classifyClassName);
|
|
13301
13356
|
}
|
|
13302
13357
|
function classifyDirectoryLayout(rel) {
|
|
13303
|
-
const base =
|
|
13304
|
-
const dir =
|
|
13358
|
+
const base = path40.posix.basename(rel).replace(/\.[^.]+$/, "");
|
|
13359
|
+
const dir = path40.posix.basename(path40.posix.dirname(rel));
|
|
13305
13360
|
if (base === "index") return "component-dir";
|
|
13306
13361
|
if (base === dir) return "component-dir";
|
|
13307
13362
|
return "flat-file";
|
|
@@ -13325,7 +13380,7 @@ function detectFormatting(manifests, componentFiles) {
|
|
|
13325
13380
|
(a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
|
|
13326
13381
|
);
|
|
13327
13382
|
for (const manifest of byDepth) {
|
|
13328
|
-
const base =
|
|
13383
|
+
const base = path40.posix.basename(manifest.rel);
|
|
13329
13384
|
if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
|
|
13330
13385
|
try {
|
|
13331
13386
|
const parsed = JSON.parse(manifest.text);
|
|
@@ -13343,7 +13398,7 @@ function detectFormatting(manifests, componentFiles) {
|
|
|
13343
13398
|
}
|
|
13344
13399
|
}
|
|
13345
13400
|
for (const manifest of byDepth) {
|
|
13346
|
-
if (
|
|
13401
|
+
if (path40.posix.basename(manifest.rel) !== ".editorconfig") continue;
|
|
13347
13402
|
const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
|
|
13348
13403
|
const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
|
|
13349
13404
|
if (style || width) {
|
|
@@ -13414,12 +13469,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
|
|
|
13414
13469
|
return out;
|
|
13415
13470
|
}
|
|
13416
13471
|
function outPathIsGitIgnored(outPath) {
|
|
13417
|
-
const dir =
|
|
13472
|
+
const dir = path40.dirname(outPath);
|
|
13418
13473
|
try {
|
|
13419
|
-
const ignoreFile =
|
|
13474
|
+
const ignoreFile = path40.join(path40.dirname(dir), ".gitignore");
|
|
13420
13475
|
if (!fs3.existsSync(ignoreFile)) return false;
|
|
13421
13476
|
const patterns = fs3.readFileSync(ignoreFile, "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
13422
|
-
const base =
|
|
13477
|
+
const base = path40.basename(dir);
|
|
13423
13478
|
return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
|
|
13424
13479
|
} catch {
|
|
13425
13480
|
return false;
|
|
@@ -13566,8 +13621,8 @@ __export(profile_exports, {
|
|
|
13566
13621
|
PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
|
|
13567
13622
|
runProfile: () => runProfile
|
|
13568
13623
|
});
|
|
13569
|
-
import { closeSync, constants, existsSync as
|
|
13570
|
-
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";
|
|
13571
13626
|
function escapesScanRoot(outPath, scanRoot) {
|
|
13572
13627
|
const resolveExisting = (target) => {
|
|
13573
13628
|
let cursor = target;
|
|
@@ -13575,23 +13630,23 @@ function escapesScanRoot(outPath, scanRoot) {
|
|
|
13575
13630
|
try {
|
|
13576
13631
|
return realpathSync4(cursor);
|
|
13577
13632
|
} catch {
|
|
13578
|
-
const parent =
|
|
13633
|
+
const parent = path41.dirname(cursor);
|
|
13579
13634
|
if (parent === cursor) return cursor;
|
|
13580
13635
|
cursor = parent;
|
|
13581
13636
|
}
|
|
13582
13637
|
}
|
|
13583
13638
|
};
|
|
13584
13639
|
const root = resolveExisting(scanRoot);
|
|
13585
|
-
const dir = resolveExisting(
|
|
13586
|
-
return dir !== root && !dir.startsWith(root +
|
|
13640
|
+
const dir = resolveExisting(path41.dirname(outPath));
|
|
13641
|
+
return dir !== root && !dir.startsWith(root + path41.sep);
|
|
13587
13642
|
}
|
|
13588
13643
|
function runProfile(options) {
|
|
13589
13644
|
if (options.describe) {
|
|
13590
13645
|
printDescription(PROFILE_DESCRIPTION);
|
|
13591
13646
|
return;
|
|
13592
13647
|
}
|
|
13593
|
-
const dir =
|
|
13594
|
-
if (!
|
|
13648
|
+
const dir = path41.resolve(options.dir ?? ".");
|
|
13649
|
+
if (!existsSync32(dir)) {
|
|
13595
13650
|
fail(options, ExitCode.InputValidation, {
|
|
13596
13651
|
error: `no such directory: ${dir}`,
|
|
13597
13652
|
code: "profile_dir_missing",
|
|
@@ -13599,7 +13654,7 @@ function runProfile(options) {
|
|
|
13599
13654
|
});
|
|
13600
13655
|
}
|
|
13601
13656
|
const profile = scanCodebase({ roots: [dir], ...options.now ? { now: options.now } : {} });
|
|
13602
|
-
const outPath =
|
|
13657
|
+
const outPath = path41.resolve(options.out ?? path41.join(dir, "tendril-out", "codebase-profile.json"));
|
|
13603
13658
|
if (!options.dryRun) {
|
|
13604
13659
|
if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
|
|
13605
13660
|
fail(options, ExitCode.InputValidation, {
|
|
@@ -13608,10 +13663,10 @@ function runProfile(options) {
|
|
|
13608
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`)}\`.`
|
|
13609
13664
|
});
|
|
13610
13665
|
}
|
|
13611
|
-
|
|
13666
|
+
mkdirSync10(path41.dirname(outPath), { recursive: true });
|
|
13612
13667
|
const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
|
|
13613
13668
|
try {
|
|
13614
|
-
|
|
13669
|
+
writeFileSync14(handle, `${JSON.stringify(profile, null, 2)}
|
|
13615
13670
|
`, "utf8");
|
|
13616
13671
|
} finally {
|
|
13617
13672
|
closeSync(handle);
|
|
@@ -13673,7 +13728,7 @@ Written to ${outPath}
|
|
|
13673
13728
|
`);
|
|
13674
13729
|
if (!ignored) {
|
|
13675
13730
|
process.stdout.write(
|
|
13676
|
-
` 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.
|
|
13677
13732
|
`
|
|
13678
13733
|
);
|
|
13679
13734
|
}
|
|
@@ -13807,19 +13862,19 @@ var init_activate = __esm({
|
|
|
13807
13862
|
|
|
13808
13863
|
// packages/cli/src/run-presence.ts
|
|
13809
13864
|
import { createHash as createHash8 } from "node:crypto";
|
|
13810
|
-
import { existsSync as
|
|
13811
|
-
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";
|
|
13812
13867
|
function presenceDir() {
|
|
13813
|
-
return
|
|
13868
|
+
return path42.join(path42.dirname(sessionPath()), "runs");
|
|
13814
13869
|
}
|
|
13815
13870
|
function presenceFile(componentName) {
|
|
13816
|
-
return
|
|
13871
|
+
return path42.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
|
|
13817
13872
|
}
|
|
13818
13873
|
function readCached(componentName) {
|
|
13819
13874
|
const file = presenceFile(componentName);
|
|
13820
|
-
if (!
|
|
13875
|
+
if (!existsSync33(file)) return void 0;
|
|
13821
13876
|
try {
|
|
13822
|
-
const parsed = JSON.parse(
|
|
13877
|
+
const parsed = JSON.parse(readFileSync30(file, "utf8"));
|
|
13823
13878
|
return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
|
|
13824
13879
|
} catch {
|
|
13825
13880
|
return void 0;
|
|
@@ -13846,8 +13901,8 @@ async function reportRunPresence(componentName, phase) {
|
|
|
13846
13901
|
}
|
|
13847
13902
|
const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
|
|
13848
13903
|
if (typeof started.runId !== "string") return;
|
|
13849
|
-
|
|
13850
|
-
|
|
13904
|
+
mkdirSync11(presenceDir(), { recursive: true });
|
|
13905
|
+
writeFileSync15(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
|
|
13851
13906
|
`, { mode: 384 });
|
|
13852
13907
|
} catch {
|
|
13853
13908
|
}
|
|
@@ -13856,7 +13911,7 @@ async function endRunPresence(componentName) {
|
|
|
13856
13911
|
try {
|
|
13857
13912
|
const session = readStoredSession();
|
|
13858
13913
|
const cached2 = readCached(componentName);
|
|
13859
|
-
|
|
13914
|
+
rmSync6(presenceFile(componentName), { force: true });
|
|
13860
13915
|
if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
|
|
13861
13916
|
await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
|
|
13862
13917
|
} catch {
|
|
@@ -13880,14 +13935,14 @@ __export(compose_exports, {
|
|
|
13880
13935
|
runCompose: () => runCompose
|
|
13881
13936
|
});
|
|
13882
13937
|
import { createHash as createHash9 } from "node:crypto";
|
|
13883
|
-
import { existsSync as
|
|
13884
|
-
import
|
|
13938
|
+
import { existsSync as existsSync34, readFileSync as readFileSync31, readdirSync as readdirSync14 } from "node:fs";
|
|
13939
|
+
import path43 from "node:path";
|
|
13885
13940
|
function compositionPairsFor(hostSet, roots) {
|
|
13886
|
-
const parent =
|
|
13941
|
+
const parent = path43.dirname(hostSet);
|
|
13887
13942
|
const explicitRoots = [...new Set(roots)];
|
|
13888
13943
|
let skippedParent;
|
|
13889
13944
|
let parentRoot = [];
|
|
13890
|
-
if (!explicitRoots.some((r) =>
|
|
13945
|
+
if (!explicitRoots.some((r) => path43.resolve(r) === path43.resolve(parent))) {
|
|
13891
13946
|
let parentEntries = 0;
|
|
13892
13947
|
try {
|
|
13893
13948
|
parentEntries = readdirSync14(parent).length;
|
|
@@ -13928,7 +13983,7 @@ function substitutionPairs(edges, hostSet) {
|
|
|
13928
13983
|
});
|
|
13929
13984
|
}
|
|
13930
13985
|
const pair = pairs.get(key);
|
|
13931
|
-
const poseDisplay = e.pose.reps.map((r) => `${
|
|
13986
|
+
const poseDisplay = e.pose.reps.map((r) => `${path43.basename(r.dir)}:${r.slug}`).join(", ");
|
|
13932
13987
|
pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
|
|
13933
13988
|
for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
|
|
13934
13989
|
}
|
|
@@ -13940,7 +13995,7 @@ function runCompose(flags) {
|
|
|
13940
13995
|
return;
|
|
13941
13996
|
}
|
|
13942
13997
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
13943
|
-
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];
|
|
13944
13999
|
if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
|
|
13945
14000
|
fail(flags, ExitCode.InputValidation, {
|
|
13946
14001
|
error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
|
|
@@ -13949,7 +14004,7 @@ function runCompose(flags) {
|
|
|
13949
14004
|
});
|
|
13950
14005
|
}
|
|
13951
14006
|
if (flags.set !== void 0) {
|
|
13952
|
-
runComposeConfirm(flags,
|
|
14007
|
+
runComposeConfirm(flags, path43.resolve(base, flags.set), roots);
|
|
13953
14008
|
return;
|
|
13954
14009
|
}
|
|
13955
14010
|
const index = buildComposeIndex(roots);
|
|
@@ -13967,7 +14022,7 @@ function runCompose(flags) {
|
|
|
13967
14022
|
}
|
|
13968
14023
|
let lastHost = "";
|
|
13969
14024
|
for (const e of edges) {
|
|
13970
|
-
const host = `${
|
|
14025
|
+
const host = `${path43.basename(e.hostSet)}`;
|
|
13971
14026
|
if (host !== lastHost) {
|
|
13972
14027
|
process.stdout.write(`
|
|
13973
14028
|
${host}
|
|
@@ -13975,7 +14030,7 @@ ${host}
|
|
|
13975
14030
|
lastHost = host;
|
|
13976
14031
|
}
|
|
13977
14032
|
const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
|
|
13978
|
-
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(", ")})` : "";
|
|
13979
14034
|
process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
|
|
13980
14035
|
`);
|
|
13981
14036
|
for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
|
|
@@ -13987,14 +14042,14 @@ ${NOTE}
|
|
|
13987
14042
|
});
|
|
13988
14043
|
}
|
|
13989
14044
|
function runComposeConfirm(flags, hostSet, roots) {
|
|
13990
|
-
if (!
|
|
14045
|
+
if (!existsSync34(path43.join(hostSet, "recording-set.json"))) {
|
|
13991
14046
|
fail(flags, ExitCode.InputValidation, {
|
|
13992
14047
|
error: `no recording-set.json in ${hostSet}`,
|
|
13993
14048
|
code: "no-recording-set",
|
|
13994
14049
|
remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
|
|
13995
14050
|
});
|
|
13996
14051
|
}
|
|
13997
|
-
const scanRoots = [.../* @__PURE__ */ new Set([...roots,
|
|
14052
|
+
const scanRoots = [.../* @__PURE__ */ new Set([...roots, path43.dirname(hostSet)])];
|
|
13998
14053
|
const index = buildComposeIndex(scanRoots);
|
|
13999
14054
|
const edges = composeReport(index);
|
|
14000
14055
|
const pairs = substitutionPairs(edges, hostSet);
|
|
@@ -14077,7 +14132,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
|
|
|
14077
14132
|
// full recording-set hash join lands with pin authoring, where
|
|
14078
14133
|
// task configs exist.)
|
|
14079
14134
|
manifestSha256: Object.fromEntries(
|
|
14080
|
-
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")])
|
|
14081
14136
|
)
|
|
14082
14137
|
},
|
|
14083
14138
|
// Schema shape ONLY (review: the display-side poseDisplay field
|
|
@@ -14157,10 +14212,10 @@ __export(record_exports, {
|
|
|
14157
14212
|
runRecordPlan: () => runRecordPlan,
|
|
14158
14213
|
runRecordStatus: () => runRecordStatus
|
|
14159
14214
|
});
|
|
14160
|
-
import { existsSync as
|
|
14215
|
+
import { existsSync as existsSync35, mkdtempSync as mkdtempSync2, readFileSync as readFileSync32, readdirSync as readdirSync15 } from "node:fs";
|
|
14161
14216
|
import os7 from "node:os";
|
|
14162
|
-
import
|
|
14163
|
-
import { writeFileSync as
|
|
14217
|
+
import path44 from "node:path";
|
|
14218
|
+
import { writeFileSync as writeFileSync16 } from "node:fs";
|
|
14164
14219
|
function recordsInteractionState(reports) {
|
|
14165
14220
|
const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
|
|
14166
14221
|
return reports.some((r) => evident(r.axis) || r.domain.some(evident));
|
|
@@ -14182,7 +14237,7 @@ function interactionDisclosure(component, reports) {
|
|
|
14182
14237
|
};
|
|
14183
14238
|
}
|
|
14184
14239
|
function symbolsFromMetadataEnvelope(file, sourceFrame) {
|
|
14185
|
-
const env = JSON.parse(
|
|
14240
|
+
const env = JSON.parse(readFileSync32(file, "utf8"));
|
|
14186
14241
|
const text = env.content.map((c) => c.text ?? "").join("\n");
|
|
14187
14242
|
const symbols = [];
|
|
14188
14243
|
const walk2 = (node, ancestor) => {
|
|
@@ -14240,8 +14295,8 @@ function runRecordPlan(opts) {
|
|
|
14240
14295
|
if (rawFile !== void 0) {
|
|
14241
14296
|
try {
|
|
14242
14297
|
const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
|
|
14243
|
-
const tmp =
|
|
14244
|
-
|
|
14298
|
+
const tmp = path44.join(mkdtempSync2(path44.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
|
|
14299
|
+
writeFileSync16(tmp, JSON.stringify(envelope));
|
|
14245
14300
|
metadataEntries.push({ file: tmp });
|
|
14246
14301
|
} catch (err) {
|
|
14247
14302
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14262,7 +14317,7 @@ function runRecordPlan(opts) {
|
|
|
14262
14317
|
let metadataTruncated = false;
|
|
14263
14318
|
for (const { file, frame } of metadataEntries) {
|
|
14264
14319
|
try {
|
|
14265
|
-
const parsed = symbolsFromMetadataEnvelope(
|
|
14320
|
+
const parsed = symbolsFromMetadataEnvelope(path44.resolve(file), frame);
|
|
14266
14321
|
symbols.push(...parsed.symbols);
|
|
14267
14322
|
if (parsed.truncated) metadataTruncated = true;
|
|
14268
14323
|
} catch (err) {
|
|
@@ -14296,7 +14351,7 @@ function runRecordPlan(opts) {
|
|
|
14296
14351
|
if (symbols.length === 0) {
|
|
14297
14352
|
const leads = metadataEntries.flatMap(({ file }) => {
|
|
14298
14353
|
try {
|
|
14299
|
-
const env = JSON.parse(
|
|
14354
|
+
const env = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
|
|
14300
14355
|
return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
|
|
14301
14356
|
} catch {
|
|
14302
14357
|
return [];
|
|
@@ -14389,7 +14444,7 @@ function runRecordPlan(opts) {
|
|
|
14389
14444
|
// a CLI flag and the MCP surface has no parameter for it.
|
|
14390
14445
|
decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
|
|
14391
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.",
|
|
14392
|
-
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`]
|
|
14393
14448
|
},
|
|
14394
14449
|
{
|
|
14395
14450
|
id: "larger-allowance",
|
|
@@ -14588,7 +14643,7 @@ function runRecordNext(opts) {
|
|
|
14588
14643
|
const progress = payload["progress"];
|
|
14589
14644
|
process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
|
|
14590
14645
|
\u2192 ${payload["note"]}
|
|
14591
|
-
\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>`)}
|
|
14592
14647
|
`);
|
|
14593
14648
|
});
|
|
14594
14649
|
}
|
|
@@ -14662,7 +14717,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14662
14717
|
const skipped = [];
|
|
14663
14718
|
const failed = [];
|
|
14664
14719
|
for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
|
|
14665
|
-
if (
|
|
14720
|
+
if (existsSync35(path44.join(setDir, rep, name))) {
|
|
14666
14721
|
skipped.push(name);
|
|
14667
14722
|
continue;
|
|
14668
14723
|
}
|
|
@@ -14684,16 +14739,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14684
14739
|
}
|
|
14685
14740
|
function rawEnvelopeFromFile(file, parts) {
|
|
14686
14741
|
if (parts) {
|
|
14687
|
-
const blocks = JSON.parse(
|
|
14742
|
+
const blocks = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
|
|
14688
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");
|
|
14689
14744
|
return { content: blocks.map((text) => ({ type: "text", text })) };
|
|
14690
14745
|
}
|
|
14691
|
-
return { content: [{ type: "text", text:
|
|
14746
|
+
return { content: [{ type: "text", text: readFileSync32(path44.resolve(file), "utf8") }] };
|
|
14692
14747
|
}
|
|
14693
14748
|
async function runRecordIngest(opts) {
|
|
14694
14749
|
let payload;
|
|
14695
14750
|
try {
|
|
14696
|
-
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"));
|
|
14697
14752
|
} catch (err) {
|
|
14698
14753
|
fail(opts, ExitCode.InputValidation, {
|
|
14699
14754
|
error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -14705,7 +14760,7 @@ async function runRecordIngest(opts) {
|
|
|
14705
14760
|
fail(opts, ExitCode.InputValidation, {
|
|
14706
14761
|
error: "--raw is for text tool responses; screenshots are binary",
|
|
14707
14762
|
code: "envelope-invalid",
|
|
14708
|
-
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.`
|
|
14709
14764
|
});
|
|
14710
14765
|
}
|
|
14711
14766
|
if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
|
|
@@ -14725,7 +14780,7 @@ async function runRecordIngest(opts) {
|
|
|
14725
14780
|
remediation: REINGEST_GUIDANCE
|
|
14726
14781
|
});
|
|
14727
14782
|
}
|
|
14728
|
-
|
|
14783
|
+
writeFileSync16(path44.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
|
|
14729
14784
|
`);
|
|
14730
14785
|
emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14731
14786
|
process.stdout.write("set-level get_variable_defs ingested\n");
|
|
@@ -14749,7 +14804,7 @@ async function runRecordIngest(opts) {
|
|
|
14749
14804
|
remediation: REINGEST_GUIDANCE
|
|
14750
14805
|
});
|
|
14751
14806
|
}
|
|
14752
|
-
|
|
14807
|
+
writeFileSync16(path44.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
|
|
14753
14808
|
`);
|
|
14754
14809
|
emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14755
14810
|
process.stdout.write("set-level get_motion_context ingested\n");
|
|
@@ -14765,7 +14820,7 @@ async function runRecordIngest(opts) {
|
|
|
14765
14820
|
if (assets !== void 0) {
|
|
14766
14821
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14767
14822
|
`);
|
|
14768
|
-
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>`)}
|
|
14769
14824
|
`);
|
|
14770
14825
|
}
|
|
14771
14826
|
});
|
|
@@ -14838,14 +14893,14 @@ async function runRecordIngestRep(opts) {
|
|
|
14838
14893
|
if (assets !== void 0) {
|
|
14839
14894
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14840
14895
|
`);
|
|
14841
|
-
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>`)}
|
|
14842
14897
|
`);
|
|
14843
14898
|
}
|
|
14844
14899
|
});
|
|
14845
14900
|
}
|
|
14846
14901
|
function runRecordAsset(opts) {
|
|
14847
14902
|
if (opts.dir !== void 0) {
|
|
14848
|
-
const dir =
|
|
14903
|
+
const dir = path44.resolve(opts.dir);
|
|
14849
14904
|
const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
|
|
14850
14905
|
if (names.length === 0) {
|
|
14851
14906
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -14857,7 +14912,7 @@ function runRecordAsset(opts) {
|
|
|
14857
14912
|
const ingested = [];
|
|
14858
14913
|
try {
|
|
14859
14914
|
for (const name of names) {
|
|
14860
|
-
ingestAsset(opts.setDir, opts.rep, name,
|
|
14915
|
+
ingestAsset(opts.setDir, opts.rep, name, readFileSync32(path44.join(dir, name)));
|
|
14861
14916
|
ingested.push(name);
|
|
14862
14917
|
}
|
|
14863
14918
|
} catch (err) {
|
|
@@ -14877,11 +14932,11 @@ function runRecordAsset(opts) {
|
|
|
14877
14932
|
fail(opts, ExitCode.InputValidation, {
|
|
14878
14933
|
error: "pass --name and --file for a single asset, or --dir for a batch",
|
|
14879
14934
|
code: "asset-rejected",
|
|
14880
|
-
remediation: tendrilCommand(`record asset --set ${
|
|
14935
|
+
remediation: tendrilCommand(`record asset --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
|
|
14881
14936
|
});
|
|
14882
14937
|
}
|
|
14883
14938
|
try {
|
|
14884
|
-
ingestAsset(opts.setDir, opts.rep, opts.name,
|
|
14939
|
+
ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync32(path44.resolve(opts.file)));
|
|
14885
14940
|
emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
|
|
14886
14941
|
process.stdout.write(`ingested ${opts.rep}/${opts.name}
|
|
14887
14942
|
`);
|
|
@@ -14898,8 +14953,8 @@ function runRecordStatus(opts) {
|
|
|
14898
14953
|
const status = sessionStatus(opts.setDir);
|
|
14899
14954
|
const composition = (() => {
|
|
14900
14955
|
try {
|
|
14901
|
-
const setDir =
|
|
14902
|
-
const { open, standing, invalid } = compositionPairsFor(setDir, [
|
|
14956
|
+
const setDir = path44.resolve(opts.setDir);
|
|
14957
|
+
const { open, standing, invalid } = compositionPairsFor(setDir, [path44.dirname(setDir)]);
|
|
14903
14958
|
return {
|
|
14904
14959
|
openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
|
|
14905
14960
|
confirmed: standing.filter((s) => s.status === "confirmed").length,
|
|
@@ -14920,7 +14975,7 @@ function runRecordStatus(opts) {
|
|
|
14920
14975
|
}
|
|
14921
14976
|
}
|
|
14922
14977
|
process.stdout.write(
|
|
14923
|
-
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\`
|
|
14924
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"
|
|
14925
14980
|
);
|
|
14926
14981
|
if ("unavailable" in composition) {
|
|
@@ -14928,7 +14983,7 @@ function runRecordStatus(opts) {
|
|
|
14928
14983
|
`);
|
|
14929
14984
|
} else if (composition.openPairs.length > 0) {
|
|
14930
14985
|
process.stdout.write(
|
|
14931
|
-
`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.
|
|
14932
14987
|
`
|
|
14933
14988
|
);
|
|
14934
14989
|
} else if (composition.confirmed > 0) {
|
|
@@ -14968,7 +15023,7 @@ function narrowedRoles(derived, override) {
|
|
|
14968
15023
|
function rolesFromFile(opts, file, derived) {
|
|
14969
15024
|
let json;
|
|
14970
15025
|
try {
|
|
14971
|
-
json = JSON.parse(
|
|
15026
|
+
json = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
|
|
14972
15027
|
} catch (err) {
|
|
14973
15028
|
fail(opts, ExitCode.InputValidation, {
|
|
14974
15029
|
error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
@@ -15006,11 +15061,11 @@ function rolesFromFile(opts, file, derived) {
|
|
|
15006
15061
|
};
|
|
15007
15062
|
}
|
|
15008
15063
|
function runRecordFinish(opts) {
|
|
15009
|
-
if (!
|
|
15064
|
+
if (!existsSync35(path44.join(opts.setDir, "recording-set.json"))) {
|
|
15010
15065
|
fail(opts, ExitCode.InputValidation, {
|
|
15011
15066
|
error: `no recording-set.json in ${opts.setDir}`,
|
|
15012
15067
|
code: "no-recording-set",
|
|
15013
|
-
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.`
|
|
15014
15069
|
});
|
|
15015
15070
|
}
|
|
15016
15071
|
const { manifest, raw } = readManifestFile(opts.setDir);
|
|
@@ -15038,17 +15093,17 @@ function runRecordFinish(opts) {
|
|
|
15038
15093
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
15039
15094
|
error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
|
|
15040
15095
|
code: "roles-confirmation-not-interactive",
|
|
15041
|
-
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.`
|
|
15042
15097
|
});
|
|
15043
15098
|
}
|
|
15044
15099
|
const merged = { ...raw, roles };
|
|
15045
|
-
const { issues } = validateRecordingSet(merged, (rel) =>
|
|
15100
|
+
const { issues } = validateRecordingSet(merged, (rel) => existsSync35(path44.join(opts.setDir, rel)));
|
|
15046
15101
|
const errors = issues.filter((i) => i.severity === "error");
|
|
15047
15102
|
if (errors.length > 0) {
|
|
15048
15103
|
fail(opts, ExitCode.InputValidation, {
|
|
15049
15104
|
error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
|
|
15050
15105
|
code: "recording-set-invalid",
|
|
15051
|
-
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\`.`
|
|
15052
15107
|
});
|
|
15053
15108
|
}
|
|
15054
15109
|
for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
|
|
@@ -15099,9 +15154,9 @@ var init_record = __esm({
|
|
|
15099
15154
|
});
|
|
15100
15155
|
|
|
15101
15156
|
// packages/cli/src/font-guidance.ts
|
|
15102
|
-
import
|
|
15157
|
+
import path45 from "node:path";
|
|
15103
15158
|
function fontsUnprovenRemediation(setDir) {
|
|
15104
|
-
const set = setDir === void 0 ? void 0 :
|
|
15159
|
+
const set = setDir === void 0 ? void 0 : path45.resolve(setDir);
|
|
15105
15160
|
if (set !== void 0) {
|
|
15106
15161
|
try {
|
|
15107
15162
|
const needs = recordedFontNeeds(set);
|
|
@@ -15176,8 +15231,8 @@ __export(fonts_exports, {
|
|
|
15176
15231
|
runFontsResolveSet: () => runFontsResolveSet,
|
|
15177
15232
|
runFontsStatus: () => runFontsStatus
|
|
15178
15233
|
});
|
|
15179
|
-
import { existsSync as
|
|
15180
|
-
import
|
|
15234
|
+
import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
|
|
15235
|
+
import path46 from "node:path";
|
|
15181
15236
|
async function runFontsResolve(opts) {
|
|
15182
15237
|
const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
|
|
15183
15238
|
const queryRefusal = systemFaceRefusal(opts.family.trim());
|
|
@@ -15198,7 +15253,7 @@ async function runFontsResolve(opts) {
|
|
|
15198
15253
|
}
|
|
15199
15254
|
}
|
|
15200
15255
|
async function runFontsResolveSet(opts) {
|
|
15201
|
-
const setDir =
|
|
15256
|
+
const setDir = path46.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
|
|
15202
15257
|
let needs = [];
|
|
15203
15258
|
try {
|
|
15204
15259
|
needs = recordedFontNeeds(setDir);
|
|
@@ -15293,16 +15348,16 @@ async function runFontsResolveSet(opts) {
|
|
|
15293
15348
|
}
|
|
15294
15349
|
}
|
|
15295
15350
|
function runFontsStatus(opts) {
|
|
15296
|
-
const manifestPath2 =
|
|
15297
|
-
if (!
|
|
15351
|
+
const manifestPath2 = path46.join(opts.cacheDir, "manifest.json");
|
|
15352
|
+
if (!existsSync36(manifestPath2)) {
|
|
15298
15353
|
fail(opts, ExitCode.FontsUnproven, {
|
|
15299
15354
|
error: `no font cache at ${opts.cacheDir}`,
|
|
15300
15355
|
code: "fonts-unresolved",
|
|
15301
15356
|
remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
|
|
15302
15357
|
});
|
|
15303
15358
|
}
|
|
15304
|
-
const faces = JSON.parse(
|
|
15305
|
-
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;
|
|
15306
15361
|
emitData(opts, { faces, lockVerdicts }, () => {
|
|
15307
15362
|
for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
|
|
15308
15363
|
`);
|
|
@@ -15346,13 +15401,13 @@ function familyMismatch(family, declared) {
|
|
|
15346
15401
|
}
|
|
15347
15402
|
function runFontsAdd(opts) {
|
|
15348
15403
|
if (opts.set !== void 0) {
|
|
15349
|
-
const declared = taskFontFamilies(
|
|
15404
|
+
const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
|
|
15350
15405
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15351
15406
|
if (mismatch !== void 0) {
|
|
15352
15407
|
fail(opts, ExitCode.InputValidation, {
|
|
15353
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.`,
|
|
15354
15409
|
code: "font-family-not-declared",
|
|
15355
|
-
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.`
|
|
15356
15411
|
});
|
|
15357
15412
|
}
|
|
15358
15413
|
} else {
|
|
@@ -15411,13 +15466,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
|
|
|
15411
15466
|
}
|
|
15412
15467
|
function runFontsAddSystem(opts) {
|
|
15413
15468
|
if (opts.set !== void 0) {
|
|
15414
|
-
const declared = taskFontFamilies(
|
|
15469
|
+
const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
|
|
15415
15470
|
const mismatch = familyMismatch(opts.family, declared);
|
|
15416
15471
|
if (mismatch !== void 0) {
|
|
15417
15472
|
fail(opts, ExitCode.InputValidation, {
|
|
15418
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.`,
|
|
15419
15474
|
code: "font-family-not-declared",
|
|
15420
|
-
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.`
|
|
15421
15476
|
});
|
|
15422
15477
|
}
|
|
15423
15478
|
} else {
|
|
@@ -15467,12 +15522,12 @@ var init_fonts = __esm({
|
|
|
15467
15522
|
});
|
|
15468
15523
|
|
|
15469
15524
|
// packages/cli/src/profile-input.ts
|
|
15470
|
-
import { existsSync as
|
|
15471
|
-
import
|
|
15525
|
+
import { existsSync as existsSync37, readFileSync as readFileSync34 } from "node:fs";
|
|
15526
|
+
import path47 from "node:path";
|
|
15472
15527
|
function loadCodebaseProfile(flags, profilePath) {
|
|
15473
15528
|
if (profilePath === void 0) return null;
|
|
15474
|
-
const abs =
|
|
15475
|
-
if (!
|
|
15529
|
+
const abs = path47.resolve(profilePath);
|
|
15530
|
+
if (!existsSync37(abs)) {
|
|
15476
15531
|
fail(flags, ExitCode.InputValidation, {
|
|
15477
15532
|
error: `no profile at ${abs}`,
|
|
15478
15533
|
code: "profile_missing",
|
|
@@ -15480,7 +15535,7 @@ function loadCodebaseProfile(flags, profilePath) {
|
|
|
15480
15535
|
});
|
|
15481
15536
|
}
|
|
15482
15537
|
try {
|
|
15483
|
-
return readCodebaseProfile(
|
|
15538
|
+
return readCodebaseProfile(readFileSync34(abs, "utf8"));
|
|
15484
15539
|
} catch (error) {
|
|
15485
15540
|
fail(flags, ExitCode.InputValidation, {
|
|
15486
15541
|
error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -15522,8 +15577,8 @@ __export(verify_exports, {
|
|
|
15522
15577
|
runVerify: () => runVerify,
|
|
15523
15578
|
verdictCaveatsFor: () => verdictCaveatsFor
|
|
15524
15579
|
});
|
|
15525
|
-
import { existsSync as
|
|
15526
|
-
import
|
|
15580
|
+
import { existsSync as existsSync38, readFileSync as readFileSync35, rmSync as rmSync7, writeFileSync as writeFileSync17 } from "node:fs";
|
|
15581
|
+
import path48 from "node:path";
|
|
15527
15582
|
function interactionCoverage(behaviors) {
|
|
15528
15583
|
const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
15529
15584
|
const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
|
|
@@ -15764,12 +15819,12 @@ function compositionReport(input) {
|
|
|
15764
15819
|
function eyeCheck(bundleDir) {
|
|
15765
15820
|
return {
|
|
15766
15821
|
command: tendrilCommand(`inspect "${bundleDir}"`),
|
|
15767
|
-
sheetPath:
|
|
15822
|
+
sheetPath: path48.join(bundleDir, "verify-evidence", "inspect.html"),
|
|
15768
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."
|
|
15769
15824
|
};
|
|
15770
15825
|
}
|
|
15771
15826
|
function evidenceArtifacts(evidenceDir, reps) {
|
|
15772
|
-
const named = (name) =>
|
|
15827
|
+
const named = (name) => existsSync38(path48.join(evidenceDir, name)) ? name : null;
|
|
15773
15828
|
return {
|
|
15774
15829
|
legend: named("diff-legend.txt"),
|
|
15775
15830
|
configs: reps.map((rep) => {
|
|
@@ -15817,7 +15872,7 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
15817
15872
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15818
15873
|
const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
15819
15874
|
const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
|
|
15820
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15875
|
+
const registry = Object.values(TASKS).find((t) => path48.resolve(t.set) === path48.resolve(setDir));
|
|
15821
15876
|
const authored = (() => {
|
|
15822
15877
|
if (registry !== void 0) return void 0;
|
|
15823
15878
|
try {
|
|
@@ -15878,19 +15933,19 @@ function verdictCaveatsFor(input) {
|
|
|
15878
15933
|
async function runVerify(opts) {
|
|
15879
15934
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15880
15935
|
let recordingSetDrift;
|
|
15881
|
-
const setOverride = opts.set !== void 0 ?
|
|
15882
|
-
opts = { ...opts, bundleDir:
|
|
15883
|
-
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)) {
|
|
15884
15939
|
fail(opts, ExitCode.InputValidation, {
|
|
15885
15940
|
error: `bundle directory not found: ${opts.bundleDir}`,
|
|
15886
15941
|
code: "bundle-missing",
|
|
15887
15942
|
remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
|
|
15888
15943
|
});
|
|
15889
15944
|
}
|
|
15890
|
-
const manifestPath2 =
|
|
15945
|
+
const manifestPath2 = path48.join(opts.bundleDir, "component.json");
|
|
15891
15946
|
let manifest;
|
|
15892
|
-
if (
|
|
15893
|
-
const { manifest: parsed, issues } = readBundleManifest(
|
|
15947
|
+
if (existsSync38(manifestPath2)) {
|
|
15948
|
+
const { manifest: parsed, issues } = readBundleManifest(readFileSync35(manifestPath2, "utf8"));
|
|
15894
15949
|
if (issues.length > 0) {
|
|
15895
15950
|
fail(opts, ExitCode.InputValidation, {
|
|
15896
15951
|
error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15921,21 +15976,21 @@ async function runVerify(opts) {
|
|
|
15921
15976
|
task = registry;
|
|
15922
15977
|
} else if (manifest !== void 0) {
|
|
15923
15978
|
const resolveSetDir = (p) => {
|
|
15924
|
-
if (
|
|
15925
|
-
const fromRepo =
|
|
15926
|
-
if (
|
|
15927
|
-
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);
|
|
15928
15983
|
};
|
|
15929
15984
|
const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
|
|
15930
|
-
if (!
|
|
15985
|
+
if (!existsSync38(path48.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path48.resolve(t.set) === path48.resolve(setDir))) {
|
|
15931
15986
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
15932
15987
|
error: `recording set not found or unmanifested: ${setDir}`,
|
|
15933
15988
|
code: "recording-set-missing",
|
|
15934
15989
|
remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
|
|
15935
15990
|
});
|
|
15936
15991
|
}
|
|
15937
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15938
|
-
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"))) {
|
|
15939
15994
|
const recordedSlugs = registry.configs.map((c) => c.rep);
|
|
15940
15995
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15941
15996
|
unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
@@ -15967,9 +16022,9 @@ async function runVerify(opts) {
|
|
|
15967
16022
|
recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
|
|
15968
16023
|
}
|
|
15969
16024
|
for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
|
|
15970
|
-
const p =
|
|
15971
|
-
if (!
|
|
15972
|
-
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)));
|
|
15973
16028
|
if (issues.length > 0) {
|
|
15974
16029
|
fail(opts, ExitCode.InputValidation, {
|
|
15975
16030
|
error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -16007,7 +16062,7 @@ async function runVerify(opts) {
|
|
|
16007
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)`);
|
|
16008
16063
|
}
|
|
16009
16064
|
const missing = task.configs.filter(
|
|
16010
|
-
(c) => !
|
|
16065
|
+
(c) => !existsSync38(path48.join(task.set, c.rep, "get_screenshot.json")) || !existsSync38(path48.join(task.set, c.rep, "get_metadata.json"))
|
|
16011
16066
|
);
|
|
16012
16067
|
if (missing.length > 0) {
|
|
16013
16068
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -16017,8 +16072,8 @@ async function runVerify(opts) {
|
|
|
16017
16072
|
});
|
|
16018
16073
|
}
|
|
16019
16074
|
const bar = BARS2[opts.bar];
|
|
16020
|
-
const evidenceDir =
|
|
16021
|
-
|
|
16075
|
+
const evidenceDir = path48.join(opts.bundleDir, "verify-evidence");
|
|
16076
|
+
rmSync7(evidenceDir, { recursive: true, force: true });
|
|
16022
16077
|
const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
16023
16078
|
const quality = await checkBundleQuality(
|
|
16024
16079
|
opts.bundleDir,
|
|
@@ -16035,7 +16090,7 @@ async function runVerify(opts) {
|
|
|
16035
16090
|
// ASKED, never "follows every convention".
|
|
16036
16091
|
loadCodebaseProfile(opts, opts.profile) ?? void 0
|
|
16037
16092
|
);
|
|
16038
|
-
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");
|
|
16039
16094
|
const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
|
|
16040
16095
|
const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
|
|
16041
16096
|
...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
|
|
@@ -16056,10 +16111,10 @@ async function runVerify(opts) {
|
|
|
16056
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.`);
|
|
16057
16112
|
}
|
|
16058
16113
|
const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16059
|
-
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: [] };
|
|
16060
16115
|
const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
|
|
16061
16116
|
const renderExpectations = verifyPins.pins.map((pin) => ({
|
|
16062
|
-
modulePath:
|
|
16117
|
+
modulePath: path48.join(composedModuleDir(pin.partnerName), pin.entryComponent),
|
|
16063
16118
|
component: pin.entryComponent,
|
|
16064
16119
|
stampName: `${pin.pairKey}:${pin.entryComponent}`,
|
|
16065
16120
|
hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
|
|
@@ -16352,7 +16407,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16352
16407
|
}
|
|
16353
16408
|
process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
|
|
16354
16409
|
`);
|
|
16355
|
-
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")));
|
|
16356
16411
|
if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
|
|
16357
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)
|
|
16358
16413
|
`);
|
|
@@ -16407,7 +16462,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
16407
16462
|
persistReport(opts, report, evidenceDir);
|
|
16408
16463
|
}
|
|
16409
16464
|
function persistReport(opts, report, evidenceDir) {
|
|
16410
|
-
if (!
|
|
16465
|
+
if (!existsSync38(evidenceDir)) return;
|
|
16411
16466
|
const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
|
|
16412
16467
|
const withExit = {
|
|
16413
16468
|
...report,
|
|
@@ -16415,9 +16470,9 @@ function persistReport(opts, report, evidenceDir) {
|
|
|
16415
16470
|
...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
|
|
16416
16471
|
};
|
|
16417
16472
|
try {
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
`${JSON.stringify(verifyReportForTransport(withExit,
|
|
16473
|
+
writeFileSync17(
|
|
16474
|
+
path48.join(evidenceDir, VERIFY_REPORT_FILENAME),
|
|
16475
|
+
`${JSON.stringify(verifyReportForTransport(withExit, path48.basename(opts.bundleDir)), null, 2)}
|
|
16421
16476
|
`
|
|
16422
16477
|
);
|
|
16423
16478
|
} catch (e) {
|
|
@@ -16467,11 +16522,11 @@ __export(engine_exports, {
|
|
|
16467
16522
|
runEngineBrief: () => runEngineBrief,
|
|
16468
16523
|
runEngineScore: () => runEngineScore
|
|
16469
16524
|
});
|
|
16470
|
-
import { appendFileSync, existsSync as
|
|
16471
|
-
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";
|
|
16472
16527
|
function resolveEngineTask(opts, callerCwd) {
|
|
16473
|
-
const asPath =
|
|
16474
|
-
const isSet =
|
|
16528
|
+
const asPath = path49.resolve(callerCwd, opts.taskOrSet);
|
|
16529
|
+
const isSet = existsSync39(path49.join(asPath, "recording-set.json"));
|
|
16475
16530
|
const registry = TASKS[opts.taskOrSet];
|
|
16476
16531
|
if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
|
|
16477
16532
|
if (isSet) {
|
|
@@ -16480,7 +16535,7 @@ function resolveEngineTask(opts, callerCwd) {
|
|
|
16480
16535
|
for (const d of authored.disclosures) warn(opts, d);
|
|
16481
16536
|
return {
|
|
16482
16537
|
task: authored.task,
|
|
16483
|
-
name:
|
|
16538
|
+
name: path49.basename(asPath),
|
|
16484
16539
|
ref: asPath,
|
|
16485
16540
|
disclosures: authored.disclosures,
|
|
16486
16541
|
interactionEvidence: authored.api.interactionEvidence,
|
|
@@ -16509,9 +16564,9 @@ function runEngineBrief(opts) {
|
|
|
16509
16564
|
const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
|
|
16510
16565
|
void reportRunPresence(name, "implementing");
|
|
16511
16566
|
const bar = BARS3[opts.bar];
|
|
16512
|
-
if (
|
|
16567
|
+
if (existsSync39(path49.join(task.set, "recording-set.json"))) {
|
|
16513
16568
|
try {
|
|
16514
|
-
const { open, skippedParent } = compositionPairsFor(
|
|
16569
|
+
const { open, skippedParent } = compositionPairsFor(path49.resolve(task.set), [opts.library !== void 0 ? path49.resolve(callerCwd, opts.library) : callerCwd]);
|
|
16515
16570
|
if (skippedParent !== void 0) {
|
|
16516
16571
|
disclosures.push(
|
|
16517
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.`
|
|
@@ -16520,7 +16575,7 @@ function runEngineBrief(opts) {
|
|
|
16520
16575
|
if (open.length > 0) {
|
|
16521
16576
|
const componentNames = [...new Set(open.map((p) => p.displayName))];
|
|
16522
16577
|
disclosures.push(
|
|
16523
|
-
`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.`
|
|
16524
16579
|
);
|
|
16525
16580
|
}
|
|
16526
16581
|
} catch (err) {
|
|
@@ -16537,9 +16592,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16537
16592
|
const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
|
|
16538
16593
|
const segments = buildSegments(task, "files");
|
|
16539
16594
|
let notRecorded;
|
|
16540
|
-
const manifestPath2 =
|
|
16541
|
-
if (
|
|
16542
|
-
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;
|
|
16543
16598
|
}
|
|
16544
16599
|
const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
|
|
16545
16600
|
|
|
@@ -16547,7 +16602,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
16547
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.
|
|
16548
16603
|
${notRecorded}` : "";
|
|
16549
16604
|
let fontProvisioning;
|
|
16550
|
-
if (
|
|
16605
|
+
if (existsSync39(manifestPath2)) {
|
|
16551
16606
|
const missingFams = unprovisionedFamilies(task.set);
|
|
16552
16607
|
const unprovided = unprovisionedFaces(task.set);
|
|
16553
16608
|
const weightOnly = missingFams.length === 0;
|
|
@@ -16569,7 +16624,7 @@ ${notRecorded}` : "";
|
|
|
16569
16624
|
};
|
|
16570
16625
|
}
|
|
16571
16626
|
}
|
|
16572
|
-
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]);
|
|
16573
16628
|
const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
|
|
16574
16629
|
const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
|
|
16575
16630
|
|
|
@@ -16605,10 +16660,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
|
|
|
16605
16660
|
|
|
16606
16661
|
=== TASK PAYLOAD (recorded truth, verbatim) ===
|
|
16607
16662
|
${segments}`;
|
|
16608
|
-
const payloadFile =
|
|
16609
|
-
const candidateDirSuggestion =
|
|
16610
|
-
|
|
16611
|
-
|
|
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);
|
|
16612
16667
|
emitData(
|
|
16613
16668
|
opts,
|
|
16614
16669
|
{
|
|
@@ -16654,7 +16709,7 @@ ${segments}`;
|
|
|
16654
16709
|
// command must search the same bundle roots the pins came
|
|
16655
16710
|
// from, or the oracle and the brief describe different worlds.
|
|
16656
16711
|
`Run \`${tendrilCommand(
|
|
16657
|
-
`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`
|
|
16658
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.`,
|
|
16659
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).",
|
|
16660
16715
|
"Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
|
|
@@ -16669,8 +16724,8 @@ ${segments}`;
|
|
|
16669
16724
|
);
|
|
16670
16725
|
}
|
|
16671
16726
|
function appendScoreHistory(candidateDir, entry) {
|
|
16672
|
-
const file =
|
|
16673
|
-
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;
|
|
16674
16729
|
const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
|
|
16675
16730
|
appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
|
|
16676
16731
|
`);
|
|
@@ -16678,10 +16733,10 @@ function appendScoreHistory(candidateDir, entry) {
|
|
|
16678
16733
|
async function runEngineScore(opts) {
|
|
16679
16734
|
requireEntitlement(opts);
|
|
16680
16735
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16681
|
-
const candidateDir =
|
|
16736
|
+
const candidateDir = path49.resolve(callerCwd, opts.candidateDir);
|
|
16682
16737
|
const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
|
|
16683
16738
|
void reportRunPresence(name, "implementing");
|
|
16684
|
-
if (!
|
|
16739
|
+
if (!existsSync39(candidateDir)) {
|
|
16685
16740
|
fail(opts, ExitCode.InputValidation, {
|
|
16686
16741
|
error: `candidate directory not found: ${candidateDir}`,
|
|
16687
16742
|
code: "candidate-missing",
|
|
@@ -16706,10 +16761,10 @@ async function runEngineScore(opts) {
|
|
|
16706
16761
|
for (const g of missingWeights(task.set)) {
|
|
16707
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)`);
|
|
16708
16763
|
}
|
|
16709
|
-
if (opts.rebind !== true &&
|
|
16764
|
+
if (opts.rebind !== true && existsSync39(path49.join(candidateDir, "component.json"))) {
|
|
16710
16765
|
const prior = (() => {
|
|
16711
16766
|
try {
|
|
16712
|
-
const read = readBundleManifest(
|
|
16767
|
+
const read = readBundleManifest(readFileSync36(path49.join(candidateDir, "component.json"), "utf8"));
|
|
16713
16768
|
return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
|
|
16714
16769
|
} catch {
|
|
16715
16770
|
return { unreadable: true };
|
|
@@ -16731,13 +16786,13 @@ async function runEngineScore(opts) {
|
|
|
16731
16786
|
}
|
|
16732
16787
|
}
|
|
16733
16788
|
const bar = BARS3[opts.bar];
|
|
16734
|
-
const evidenceDir =
|
|
16789
|
+
const evidenceDir = path49.join(candidateDir, "verify-evidence");
|
|
16735
16790
|
const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
16736
16791
|
const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
|
|
16737
16792
|
const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
|
|
16738
16793
|
const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
|
|
16739
16794
|
const occlusionFailures = occlusionRows.filter((o) => !o.pass);
|
|
16740
|
-
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]);
|
|
16741
16796
|
const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
|
|
16742
16797
|
const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
|
|
16743
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)";
|
|
@@ -16956,11 +17011,11 @@ var codeconnect_exports = {};
|
|
|
16956
17011
|
__export(codeconnect_exports, {
|
|
16957
17012
|
runCodeConnect: () => runCodeConnect
|
|
16958
17013
|
});
|
|
16959
|
-
import { existsSync as
|
|
16960
|
-
import
|
|
17014
|
+
import { existsSync as existsSync40, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
|
|
17015
|
+
import path50 from "node:path";
|
|
16961
17016
|
function runCodeConnect(opts) {
|
|
16962
17017
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16963
|
-
const bundleDir =
|
|
17018
|
+
const bundleDir = path50.resolve(callerCwd, opts.bundleDir);
|
|
16964
17019
|
let url;
|
|
16965
17020
|
try {
|
|
16966
17021
|
url = new URL(opts.figmaUrl);
|
|
@@ -16976,7 +17031,7 @@ function runCodeConnect(opts) {
|
|
|
16976
17031
|
}
|
|
16977
17032
|
let manifest;
|
|
16978
17033
|
try {
|
|
16979
|
-
const read = readBundleManifest(
|
|
17034
|
+
const read = readBundleManifest(readFileSync37(path50.join(bundleDir, "component.json"), "utf8"));
|
|
16980
17035
|
if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
|
|
16981
17036
|
manifest = read.manifest;
|
|
16982
17037
|
} catch (err) {
|
|
@@ -16986,8 +17041,8 @@ function runCodeConnect(opts) {
|
|
|
16986
17041
|
remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
|
|
16987
17042
|
});
|
|
16988
17043
|
}
|
|
16989
|
-
const setDir =
|
|
16990
|
-
if (!
|
|
17044
|
+
const setDir = path50.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
|
|
17045
|
+
if (!existsSync40(path50.join(setDir, "recording-set.json"))) {
|
|
16991
17046
|
fail(opts, ExitCode.InputValidation, {
|
|
16992
17047
|
error: `recording set not found at ${setDir}`,
|
|
16993
17048
|
code: "codeconnect-no-set",
|
|
@@ -17008,10 +17063,10 @@ function runCodeConnect(opts) {
|
|
|
17008
17063
|
const component = api.component;
|
|
17009
17064
|
const recManifest = loadManifest(setDir);
|
|
17010
17065
|
const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
|
|
17011
|
-
const meta =
|
|
17012
|
-
if (!
|
|
17066
|
+
const meta = path50.join(setDir, r.slug, "get_metadata.json");
|
|
17067
|
+
if (!existsSync40(meta)) return void 0;
|
|
17013
17068
|
try {
|
|
17014
|
-
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(
|
|
17069
|
+
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync37(meta, "utf8"))))?.[1];
|
|
17015
17070
|
} catch {
|
|
17016
17071
|
return void 0;
|
|
17017
17072
|
}
|
|
@@ -17076,7 +17131,7 @@ function runCodeConnect(opts) {
|
|
|
17076
17131
|
axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
|
|
17077
17132
|
fragmentVars.push(varName);
|
|
17078
17133
|
}
|
|
17079
|
-
const entryRel =
|
|
17134
|
+
const entryRel = path50.relative(callerCwd, path50.join(bundleDir, manifest.entry));
|
|
17080
17135
|
const trust = manifest.trustStatement.split("\n")[0] ?? "";
|
|
17081
17136
|
const lines = [
|
|
17082
17137
|
`// url=${opts.figmaUrl}`,
|
|
@@ -17100,8 +17155,8 @@ function runCodeConnect(opts) {
|
|
|
17100
17155
|
`}`,
|
|
17101
17156
|
``
|
|
17102
17157
|
].join("\n");
|
|
17103
|
-
const outFile =
|
|
17104
|
-
|
|
17158
|
+
const outFile = path50.resolve(callerCwd, opts.out ?? path50.join(bundleDir, `${component}.figma.ts`));
|
|
17159
|
+
writeFileSync19(outFile, lines);
|
|
17105
17160
|
emitData(
|
|
17106
17161
|
opts,
|
|
17107
17162
|
{
|
|
@@ -17140,17 +17195,17 @@ var init_codeconnect = __esm({
|
|
|
17140
17195
|
|
|
17141
17196
|
// packages/mcp/src/server.ts
|
|
17142
17197
|
import { createHash as createHash10 } from "node:crypto";
|
|
17143
|
-
import { existsSync as
|
|
17198
|
+
import { existsSync as existsSync41, mkdtempSync as mkdtempSync3, readFileSync as readFileSync38, readdirSync as readdirSync16, writeFileSync as writeFileSync20 } from "node:fs";
|
|
17144
17199
|
import os8 from "node:os";
|
|
17145
|
-
import
|
|
17200
|
+
import path51 from "node:path";
|
|
17146
17201
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
17147
17202
|
import { z as z14 } from "zod";
|
|
17148
17203
|
function sourceHash() {
|
|
17149
|
-
const dir =
|
|
17204
|
+
const dir = path51.dirname(fileURLToPath6(import.meta.url));
|
|
17150
17205
|
const h = createHash10("sha256");
|
|
17151
17206
|
for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
|
|
17152
17207
|
h.update(f);
|
|
17153
|
-
h.update(
|
|
17208
|
+
h.update(readFileSync38(path51.join(dir, f)));
|
|
17154
17209
|
}
|
|
17155
17210
|
return h.digest("hex").slice(0, 16);
|
|
17156
17211
|
}
|
|
@@ -17158,10 +17213,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
|
|
|
17158
17213
|
var init_server = __esm({
|
|
17159
17214
|
"packages/mcp/src/server.ts"() {
|
|
17160
17215
|
"use strict";
|
|
17161
|
-
REPO_ROOT3 =
|
|
17162
|
-
CLI_BIN =
|
|
17163
|
-
BUNDLED_CLI =
|
|
17164
|
-
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] };
|
|
17165
17220
|
str = (d) => z14.string().describe(d);
|
|
17166
17221
|
optStr = (d) => z14.string().optional().describe(d);
|
|
17167
17222
|
TOOLS = [
|
|
@@ -17192,13 +17247,13 @@ var init_server = __esm({
|
|
|
17192
17247
|
const single = i["metadata"];
|
|
17193
17248
|
const parts = i["metadataParts"];
|
|
17194
17249
|
if (single !== void 0 || parts !== void 0) {
|
|
17195
|
-
const tmp =
|
|
17250
|
+
const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17196
17251
|
if (single !== void 0) {
|
|
17197
|
-
|
|
17252
|
+
writeFileSync20(tmp, single);
|
|
17198
17253
|
argvOut.push("--metadata-raw-file", tmp);
|
|
17199
17254
|
} else {
|
|
17200
17255
|
if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
|
|
17201
|
-
|
|
17256
|
+
writeFileSync20(tmp, JSON.stringify(parts));
|
|
17202
17257
|
argvOut.push("--metadata-raw-parts-file", tmp);
|
|
17203
17258
|
}
|
|
17204
17259
|
}
|
|
@@ -17248,6 +17303,20 @@ var init_server = __esm({
|
|
|
17248
17303
|
schema: z14.object({}),
|
|
17249
17304
|
argv: () => ["login", "--device-wait"]
|
|
17250
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
|
+
},
|
|
17251
17320
|
{
|
|
17252
17321
|
name: "tendril_publish",
|
|
17253
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).",
|
|
@@ -17307,14 +17376,14 @@ var init_server = __esm({
|
|
|
17307
17376
|
const bridge = (label, single, parts) => {
|
|
17308
17377
|
if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
|
|
17309
17378
|
if (single === void 0 && parts === void 0) return;
|
|
17310
|
-
const tmp =
|
|
17379
|
+
const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17311
17380
|
if (single !== void 0) {
|
|
17312
|
-
|
|
17381
|
+
writeFileSync20(tmp, single);
|
|
17313
17382
|
argvOut.push(`--${label}-file`, tmp);
|
|
17314
17383
|
} else {
|
|
17315
17384
|
const blocks = parts;
|
|
17316
17385
|
if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
|
|
17317
|
-
|
|
17386
|
+
writeFileSync20(tmp, JSON.stringify(blocks));
|
|
17318
17387
|
argvOut.push(`--${label}-parts-file`, tmp);
|
|
17319
17388
|
}
|
|
17320
17389
|
};
|
|
@@ -17355,12 +17424,12 @@ var init_server = __esm({
|
|
|
17355
17424
|
const file = i["file"];
|
|
17356
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)");
|
|
17357
17426
|
if (file !== void 0) return [...base, "--file", file];
|
|
17358
|
-
const tmp =
|
|
17427
|
+
const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
17359
17428
|
if (text !== void 0) {
|
|
17360
|
-
|
|
17429
|
+
writeFileSync20(tmp, text);
|
|
17361
17430
|
return [...base, "--file", tmp, "--raw"];
|
|
17362
17431
|
}
|
|
17363
|
-
|
|
17432
|
+
writeFileSync20(tmp, JSON.stringify(texts));
|
|
17364
17433
|
return [...base, "--file", tmp, "--raw-parts"];
|
|
17365
17434
|
}
|
|
17366
17435
|
},
|
|
@@ -17517,13 +17586,13 @@ __export(permissions_exports, {
|
|
|
17517
17586
|
runPermissions: () => runPermissions,
|
|
17518
17587
|
writeSelection: () => writeSelection
|
|
17519
17588
|
});
|
|
17520
|
-
import { existsSync as
|
|
17589
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync13, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
|
|
17521
17590
|
import os9 from "node:os";
|
|
17522
|
-
import
|
|
17591
|
+
import path52 from "node:path";
|
|
17523
17592
|
function mergeAllowlist(file, entries, denyEntries = []) {
|
|
17524
17593
|
let settings = {};
|
|
17525
|
-
if (
|
|
17526
|
-
settings = JSON.parse(
|
|
17594
|
+
if (existsSync42(file) && readFileSync39(file, "utf8").trim() !== "") {
|
|
17595
|
+
settings = JSON.parse(readFileSync39(file, "utf8"));
|
|
17527
17596
|
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
|
|
17528
17597
|
}
|
|
17529
17598
|
const permissions = settings["permissions"] ??= {};
|
|
@@ -17543,8 +17612,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
|
|
|
17543
17612
|
}
|
|
17544
17613
|
if (added.length > 0 || denyAdded.length > 0) {
|
|
17545
17614
|
allow.push(...added);
|
|
17546
|
-
|
|
17547
|
-
|
|
17615
|
+
mkdirSync13(path52.dirname(file), { recursive: true });
|
|
17616
|
+
writeFileSync21(file, `${JSON.stringify(settings, null, 2)}
|
|
17548
17617
|
`);
|
|
17549
17618
|
}
|
|
17550
17619
|
return { added, alreadyPresent, denyAdded };
|
|
@@ -17608,7 +17677,7 @@ async function runPermissions(flags) {
|
|
|
17608
17677
|
}
|
|
17609
17678
|
if (flags.write) {
|
|
17610
17679
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
17611
|
-
const file = flags.user ?
|
|
17680
|
+
const file = flags.user ? path52.join(os9.homedir(), ".claude", "settings.json") : path52.join(base, ".claude", "settings.local.json");
|
|
17612
17681
|
const { entries, denyEntries } = writeSelection(result, flags.user === true);
|
|
17613
17682
|
if (flags.dryRun) {
|
|
17614
17683
|
emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
|
|
@@ -17757,13 +17826,13 @@ __export(inspect_exports, {
|
|
|
17757
17826
|
INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
|
|
17758
17827
|
runInspect: () => runInspect
|
|
17759
17828
|
});
|
|
17760
|
-
import { existsSync as
|
|
17761
|
-
import
|
|
17829
|
+
import { existsSync as existsSync43, readFileSync as readFileSync40, writeFileSync as writeFileSync22 } from "node:fs";
|
|
17830
|
+
import path53 from "node:path";
|
|
17762
17831
|
function readVerifyReport(evidenceDir) {
|
|
17763
|
-
const p =
|
|
17764
|
-
if (!
|
|
17832
|
+
const p = path53.join(evidenceDir, VERIFY_REPORT_FILENAME);
|
|
17833
|
+
if (!existsSync43(p)) return void 0;
|
|
17765
17834
|
try {
|
|
17766
|
-
return JSON.parse(
|
|
17835
|
+
return JSON.parse(readFileSync40(p, "utf8"));
|
|
17767
17836
|
} catch {
|
|
17768
17837
|
return void 0;
|
|
17769
17838
|
}
|
|
@@ -17791,17 +17860,17 @@ async function runInspect(opts) {
|
|
|
17791
17860
|
printDescription(INSPECT_DESCRIPTION);
|
|
17792
17861
|
return;
|
|
17793
17862
|
}
|
|
17794
|
-
const bundleDir =
|
|
17795
|
-
const evidenceDir =
|
|
17796
|
-
const manifestPath2 =
|
|
17797
|
-
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)) {
|
|
17798
17867
|
fail(opts, ExitCode.InputValidation, {
|
|
17799
|
-
error: `nothing to inspect in ${bundleDir} \u2014 ${
|
|
17868
|
+
error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync43(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
|
|
17800
17869
|
code: "no-evidence",
|
|
17801
17870
|
remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
|
|
17802
17871
|
});
|
|
17803
17872
|
}
|
|
17804
|
-
const { manifest } = readBundleManifest(
|
|
17873
|
+
const { manifest } = readBundleManifest(readFileSync40(manifestPath2, "utf8"));
|
|
17805
17874
|
if (manifest === void 0) {
|
|
17806
17875
|
fail(opts, ExitCode.InputValidation, {
|
|
17807
17876
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -17809,9 +17878,9 @@ async function runInspect(opts) {
|
|
|
17809
17878
|
remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
|
|
17810
17879
|
});
|
|
17811
17880
|
}
|
|
17812
|
-
const setDir =
|
|
17881
|
+
const setDir = path53.resolve(opts.set ?? manifest.provenance.recordingSet.path);
|
|
17813
17882
|
const report = readVerifyReport(evidenceDir);
|
|
17814
|
-
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`)));
|
|
17815
17884
|
if (reps.length === 0) {
|
|
17816
17885
|
fail(opts, ExitCode.InputValidation, {
|
|
17817
17886
|
error: "verify-evidence holds no ref/render pairs for this bundle's configs",
|
|
@@ -17822,15 +17891,15 @@ async function runInspect(opts) {
|
|
|
17822
17891
|
let crops = 0;
|
|
17823
17892
|
const sections = [];
|
|
17824
17893
|
for (const rep of reps) {
|
|
17825
|
-
const ref = new Uint8Array(
|
|
17826
|
-
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`)));
|
|
17827
17896
|
const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
|
|
17828
17897
|
const cells = [];
|
|
17829
17898
|
for (const [i, n] of nodes.entries()) {
|
|
17830
17899
|
const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
|
|
17831
17900
|
try {
|
|
17832
|
-
|
|
17833
|
-
|
|
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));
|
|
17834
17903
|
} catch {
|
|
17835
17904
|
continue;
|
|
17836
17905
|
}
|
|
@@ -17847,8 +17916,8 @@ async function runInspect(opts) {
|
|
|
17847
17916
|
if (reps.includes(c.rep)) continue;
|
|
17848
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>`);
|
|
17849
17918
|
}
|
|
17850
|
-
const sheet =
|
|
17851
|
-
|
|
17919
|
+
const sheet = path53.join(evidenceDir, "inspect.html");
|
|
17920
|
+
writeFileSync22(
|
|
17852
17921
|
sheet,
|
|
17853
17922
|
`<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
|
|
17854
17923
|
body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
|
|
@@ -17921,8 +17990,8 @@ __export(login_exports, {
|
|
|
17921
17990
|
runLogout: () => runLogout
|
|
17922
17991
|
});
|
|
17923
17992
|
import { spawn } from "node:child_process";
|
|
17924
|
-
import { existsSync as
|
|
17925
|
-
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";
|
|
17926
17995
|
import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
|
|
17927
17996
|
async function runLogin(opts, deps) {
|
|
17928
17997
|
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
|
|
@@ -18016,13 +18085,13 @@ function settleDecision(opts, origin, outcome) {
|
|
|
18016
18085
|
}
|
|
18017
18086
|
}
|
|
18018
18087
|
function pendingLoginPath() {
|
|
18019
|
-
return
|
|
18088
|
+
return path54.join(path54.dirname(sessionPath()), "pending-login.json");
|
|
18020
18089
|
}
|
|
18021
18090
|
async function deviceStartPhase(opts, origin, deps) {
|
|
18022
18091
|
const started = await startHandshake(opts, origin, deps);
|
|
18023
18092
|
const file = pendingLoginPath();
|
|
18024
|
-
|
|
18025
|
-
|
|
18093
|
+
mkdirSync14(path54.dirname(file), { recursive: true });
|
|
18094
|
+
writeFileSync23(file, `${JSON.stringify({ origin, ...started }, null, 2)}
|
|
18026
18095
|
`, { mode: 384 });
|
|
18027
18096
|
deps.openBrowser(started.verificationUrl);
|
|
18028
18097
|
emitData(
|
|
@@ -18047,9 +18116,9 @@ async function deviceStartPhase(opts, origin, deps) {
|
|
|
18047
18116
|
async function deviceWaitPhase(opts, deps) {
|
|
18048
18117
|
const file = pendingLoginPath();
|
|
18049
18118
|
let pending;
|
|
18050
|
-
if (
|
|
18119
|
+
if (existsSync44(file)) {
|
|
18051
18120
|
try {
|
|
18052
|
-
const parsed = JSON.parse(
|
|
18121
|
+
const parsed = JSON.parse(readFileSync41(file, "utf8"));
|
|
18053
18122
|
if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
|
|
18054
18123
|
pending = parsed;
|
|
18055
18124
|
}
|
|
@@ -18063,7 +18132,7 @@ async function deviceWaitPhase(opts, deps) {
|
|
|
18063
18132
|
remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
|
|
18064
18133
|
});
|
|
18065
18134
|
}
|
|
18066
|
-
const done = () =>
|
|
18135
|
+
const done = () => rmSync8(file, { force: true });
|
|
18067
18136
|
const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
|
|
18068
18137
|
let ticks = 0;
|
|
18069
18138
|
const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
|
|
@@ -18186,6 +18255,196 @@ var init_login = __esm({
|
|
|
18186
18255
|
}
|
|
18187
18256
|
});
|
|
18188
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
|
+
|
|
18189
18448
|
// packages/cli/src/commands/share.ts
|
|
18190
18449
|
var share_exports = {};
|
|
18191
18450
|
__export(share_exports, {
|
|
@@ -18298,13 +18557,13 @@ var init_share = __esm({
|
|
|
18298
18557
|
// packages/cli/src/commands/publish.ts
|
|
18299
18558
|
var publish_exports = {};
|
|
18300
18559
|
__export(publish_exports, {
|
|
18301
|
-
resolveOrigin: () =>
|
|
18560
|
+
resolveOrigin: () => resolveOrigin2,
|
|
18302
18561
|
runPublish: () => runPublish
|
|
18303
18562
|
});
|
|
18304
|
-
import { existsSync as
|
|
18305
|
-
import
|
|
18563
|
+
import { existsSync as existsSync46, readFileSync as readFileSync43, rmSync as rmSync10, writeFileSync as writeFileSync25 } from "node:fs";
|
|
18564
|
+
import path56 from "node:path";
|
|
18306
18565
|
async function runPublish(opts) {
|
|
18307
|
-
const bundleDir =
|
|
18566
|
+
const bundleDir = path56.resolve(opts.bundleDir);
|
|
18308
18567
|
const bundle = readBundle(opts, bundleDir);
|
|
18309
18568
|
const report = bundle.report;
|
|
18310
18569
|
if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
|
|
@@ -18349,7 +18608,7 @@ async function runPublish(opts) {
|
|
|
18349
18608
|
const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
|
|
18350
18609
|
if (sheetEntry !== void 0) {
|
|
18351
18610
|
const missingCrops = missingInspectCrops(
|
|
18352
|
-
|
|
18611
|
+
readFileSync43(path56.join(bundleDir, sheetEntry.path), "utf8"),
|
|
18353
18612
|
surface.published.map((p) => p.path)
|
|
18354
18613
|
);
|
|
18355
18614
|
if (missingCrops.length > 0) {
|
|
@@ -18414,7 +18673,7 @@ async function runPublish(opts) {
|
|
|
18414
18673
|
);
|
|
18415
18674
|
return;
|
|
18416
18675
|
}
|
|
18417
|
-
const origin =
|
|
18676
|
+
const origin = resolveOrigin2(opts);
|
|
18418
18677
|
const client = opts.client ?? httpClient(opts, origin);
|
|
18419
18678
|
if (opts.approveWait === true) {
|
|
18420
18679
|
await approveWaitPhase(opts, client, bundleDir, { componentName, figmaFile });
|
|
@@ -18448,8 +18707,8 @@ async function runPublish(opts) {
|
|
|
18448
18707
|
}
|
|
18449
18708
|
const uploaded = [];
|
|
18450
18709
|
for (const object of opened.value.plan.objects) {
|
|
18451
|
-
const file =
|
|
18452
|
-
if (!
|
|
18710
|
+
const file = path56.join(bundleDir, object.relPath);
|
|
18711
|
+
if (!existsSync46(file)) {
|
|
18453
18712
|
fail(opts, ExitCode.InputValidation, {
|
|
18454
18713
|
error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
|
|
18455
18714
|
code: "planned-file-missing",
|
|
@@ -18459,10 +18718,11 @@ async function runPublish(opts) {
|
|
|
18459
18718
|
const sent = await client.upload({
|
|
18460
18719
|
publicationId: opened.value.publicationId,
|
|
18461
18720
|
relPath: object.relPath,
|
|
18462
|
-
bytes: new Uint8Array(
|
|
18721
|
+
bytes: new Uint8Array(readFileSync43(file))
|
|
18463
18722
|
});
|
|
18464
18723
|
if (!sent.ok) refuse2(opts, sent, "upload-refused", true);
|
|
18465
18724
|
uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
|
|
18725
|
+
emitProgress(uploaded.length, opened.value.plan.objects.length, `uploading ${object.relPath}`);
|
|
18466
18726
|
}
|
|
18467
18727
|
const committed = await client.commit({ publicationId: opened.value.publicationId });
|
|
18468
18728
|
if (committed.ok) {
|
|
@@ -18504,23 +18764,23 @@ async function runPublish(opts) {
|
|
|
18504
18764
|
);
|
|
18505
18765
|
}
|
|
18506
18766
|
function readBundle(opts, bundleDir) {
|
|
18507
|
-
const manifestPath2 =
|
|
18508
|
-
const reportPath =
|
|
18509
|
-
if (!
|
|
18767
|
+
const manifestPath2 = path56.join(bundleDir, "component.json");
|
|
18768
|
+
const reportPath = path56.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
|
|
18769
|
+
if (!existsSync46(manifestPath2)) {
|
|
18510
18770
|
fail(opts, ExitCode.InputValidation, {
|
|
18511
18771
|
error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
|
|
18512
18772
|
code: "not-a-bundle",
|
|
18513
18773
|
remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
|
|
18514
18774
|
});
|
|
18515
18775
|
}
|
|
18516
|
-
if (!
|
|
18776
|
+
if (!existsSync46(reportPath)) {
|
|
18517
18777
|
fail(opts, ExitCode.InputValidation, {
|
|
18518
18778
|
error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
|
|
18519
18779
|
code: "bundle-not-verified",
|
|
18520
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.`
|
|
18521
18781
|
});
|
|
18522
18782
|
}
|
|
18523
|
-
const { manifest } = readBundleManifest(
|
|
18783
|
+
const { manifest } = readBundleManifest(readFileSync43(manifestPath2, "utf8"));
|
|
18524
18784
|
if (manifest === void 0) {
|
|
18525
18785
|
fail(opts, ExitCode.InputValidation, {
|
|
18526
18786
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -18528,7 +18788,7 @@ function readBundle(opts, bundleDir) {
|
|
|
18528
18788
|
remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
|
|
18529
18789
|
});
|
|
18530
18790
|
}
|
|
18531
|
-
const reportText =
|
|
18791
|
+
const reportText = readFileSync43(reportPath, "utf8");
|
|
18532
18792
|
let report;
|
|
18533
18793
|
try {
|
|
18534
18794
|
report = JSON.parse(reportText);
|
|
@@ -18548,7 +18808,7 @@ function readBundle(opts, bundleDir) {
|
|
|
18548
18808
|
}
|
|
18549
18809
|
return { manifest, report, reportText, files: bundleFiles(bundleDir) };
|
|
18550
18810
|
}
|
|
18551
|
-
function
|
|
18811
|
+
function resolveOrigin2(opts) {
|
|
18552
18812
|
const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
18553
18813
|
if (named !== "") return named;
|
|
18554
18814
|
if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
|
|
@@ -18592,11 +18852,11 @@ function refuse2(opts, sent, code, rejoins = false) {
|
|
|
18592
18852
|
fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
|
|
18593
18853
|
error: `${sent.refusal}${detail}`,
|
|
18594
18854
|
code,
|
|
18595
|
-
remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${
|
|
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}.`
|
|
18596
18856
|
});
|
|
18597
18857
|
}
|
|
18598
18858
|
function pendingApprovalPath() {
|
|
18599
|
-
return
|
|
18859
|
+
return path56.join(path56.dirname(sessionPath()), "pending-publish.json");
|
|
18600
18860
|
}
|
|
18601
18861
|
async function runApprovalFlow(opts, client, bundleDir, input) {
|
|
18602
18862
|
const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
|
|
@@ -18606,7 +18866,7 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
|
|
|
18606
18866
|
const asAccount = who?.ok === true && who.value.email !== "" ? ` signed in as ${who.value.email}` : "";
|
|
18607
18867
|
if (opts.approveStart === true) {
|
|
18608
18868
|
const pending = { ...approval, bundleDir, componentName: input.componentName, figmaFile: input.figmaFile };
|
|
18609
|
-
|
|
18869
|
+
writeFileSync25(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
|
|
18610
18870
|
`, { mode: 384 });
|
|
18611
18871
|
await endRunPresence(input.componentName);
|
|
18612
18872
|
emitData(
|
|
@@ -18643,9 +18903,9 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
|
|
|
18643
18903
|
async function approveWaitPhase(opts, client, bundleDir, subject) {
|
|
18644
18904
|
const file = pendingApprovalPath();
|
|
18645
18905
|
let pending;
|
|
18646
|
-
if (
|
|
18906
|
+
if (existsSync46(file)) {
|
|
18647
18907
|
try {
|
|
18648
|
-
const parsed = JSON.parse(
|
|
18908
|
+
const parsed = JSON.parse(readFileSync43(file, "utf8"));
|
|
18649
18909
|
if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
|
|
18650
18910
|
pending = { pollSeconds: 3, bundleDir, ...parsed };
|
|
18651
18911
|
}
|
|
@@ -18666,7 +18926,7 @@ async function approveWaitPhase(opts, client, bundleDir, subject) {
|
|
|
18666
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.`
|
|
18667
18927
|
});
|
|
18668
18928
|
}
|
|
18669
|
-
const done = () =>
|
|
18929
|
+
const done = () => rmSync10(file, { force: true });
|
|
18670
18930
|
const decided = await waitForApproval(opts, client, pending);
|
|
18671
18931
|
done();
|
|
18672
18932
|
if (decided !== "approved") failDecision(opts, decided);
|
|
@@ -18735,17 +18995,17 @@ __export(generate_recorded_exports, {
|
|
|
18735
18995
|
runGenerateRecorded: () => runGenerateRecorded
|
|
18736
18996
|
});
|
|
18737
18997
|
import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
|
|
18738
|
-
import { existsSync as
|
|
18739
|
-
import
|
|
18998
|
+
import { existsSync as existsSync47, readFileSync as readFileSync44 } from "node:fs";
|
|
18999
|
+
import path57 from "node:path";
|
|
18740
19000
|
async function runGenerateRecorded(opts) {
|
|
18741
19001
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
18742
|
-
const outDirAbs =
|
|
18743
|
-
const recordedAsPath =
|
|
19002
|
+
const outDirAbs = path57.resolve(callerCwd, opts.out);
|
|
19003
|
+
const recordedAsPath = path57.resolve(callerCwd, opts.recorded);
|
|
18744
19004
|
let task;
|
|
18745
19005
|
let taskName;
|
|
18746
19006
|
let authoredApi;
|
|
18747
19007
|
let composition;
|
|
18748
|
-
const isSet =
|
|
19008
|
+
const isSet = existsSync47(path57.join(recordedAsPath, "recording-set.json"));
|
|
18749
19009
|
const registry = TASKS[opts.recorded];
|
|
18750
19010
|
if (registry !== void 0 && !isSet) {
|
|
18751
19011
|
task = registry;
|
|
@@ -18754,7 +19014,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18754
19014
|
try {
|
|
18755
19015
|
const authored = authorTaskFromSet(recordedAsPath);
|
|
18756
19016
|
task = authored.task;
|
|
18757
|
-
taskName =
|
|
19017
|
+
taskName = path57.basename(recordedAsPath);
|
|
18758
19018
|
authoredApi = authored.api;
|
|
18759
19019
|
const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
|
|
18760
19020
|
if (roles.success) composition = roles.data;
|
|
@@ -18788,7 +19048,7 @@ async function runGenerateRecorded(opts) {
|
|
|
18788
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)`);
|
|
18789
19049
|
}
|
|
18790
19050
|
const missing = task.configs.filter(
|
|
18791
|
-
(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"))
|
|
18792
19052
|
);
|
|
18793
19053
|
if (missing.length > 0) {
|
|
18794
19054
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -18858,8 +19118,8 @@ async function runGenerateRecorded(opts) {
|
|
|
18858
19118
|
` : `${line}
|
|
18859
19119
|
`);
|
|
18860
19120
|
if (opts.dryRun) {
|
|
18861
|
-
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite:
|
|
18862
|
-
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)})
|
|
18863
19123
|
`);
|
|
18864
19124
|
});
|
|
18865
19125
|
return;
|
|
@@ -18882,10 +19142,10 @@ async function runGenerateRecorded(opts) {
|
|
|
18882
19142
|
});
|
|
18883
19143
|
}
|
|
18884
19144
|
}
|
|
18885
|
-
const bundleDir =
|
|
18886
|
-
if (
|
|
19145
|
+
const bundleDir = path57.join(outDirAbs, taskName);
|
|
19146
|
+
if (existsSync47(path57.join(bundleDir, "component.json"))) {
|
|
18887
19147
|
try {
|
|
18888
|
-
const prior = readBundleManifest(
|
|
19148
|
+
const prior = readBundleManifest(readFileSync44(path57.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
18889
19149
|
if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
|
|
18890
19150
|
fail(opts, ExitCode.InputValidation, {
|
|
18891
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`,
|
|
@@ -19052,7 +19312,7 @@ init_invocation();
|
|
|
19052
19312
|
init_output();
|
|
19053
19313
|
import { intro, isCancel, outro, password } from "@clack/prompts";
|
|
19054
19314
|
import fs from "node:fs";
|
|
19055
|
-
import
|
|
19315
|
+
import path31 from "node:path";
|
|
19056
19316
|
var INIT_DESCRIPTION = {
|
|
19057
19317
|
name: "init",
|
|
19058
19318
|
summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
|
|
@@ -19094,7 +19354,7 @@ async function runInit(flags) {
|
|
|
19094
19354
|
printDescription(INIT_DESCRIPTION);
|
|
19095
19355
|
return;
|
|
19096
19356
|
}
|
|
19097
|
-
const envPath =
|
|
19357
|
+
const envPath = path31.resolve(process.cwd(), ".env");
|
|
19098
19358
|
const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
|
|
19099
19359
|
let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
|
|
19100
19360
|
let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
|
|
@@ -19115,7 +19375,7 @@ async function runInit(flags) {
|
|
|
19115
19375
|
if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
|
|
19116
19376
|
if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
|
|
19117
19377
|
const changed = [...next].some(([key, value]) => existing.get(key) !== value);
|
|
19118
|
-
const gitignorePath =
|
|
19378
|
+
const gitignorePath = path31.resolve(process.cwd(), ".gitignore");
|
|
19119
19379
|
const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
|
|
19120
19380
|
const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
|
|
19121
19381
|
if (flags.dryRun) {
|
|
@@ -19171,14 +19431,14 @@ init_invocation();
|
|
|
19171
19431
|
init_output();
|
|
19172
19432
|
init_entitlement();
|
|
19173
19433
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
19174
|
-
import { readFileSync as
|
|
19434
|
+
import { readFileSync as readFileSync23, readdirSync as readdirSync9, existsSync as existsSync25 } from "node:fs";
|
|
19175
19435
|
|
|
19176
19436
|
// packages/cli/src/pipeline.ts
|
|
19177
19437
|
init_src2();
|
|
19178
19438
|
init_src5();
|
|
19179
19439
|
init_src4();
|
|
19180
|
-
import { mkdirSync as
|
|
19181
|
-
import
|
|
19440
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
19441
|
+
import path32 from "node:path";
|
|
19182
19442
|
|
|
19183
19443
|
// packages/cli/src/assets-module.ts
|
|
19184
19444
|
init_src();
|
|
@@ -19514,8 +19774,8 @@ async function runGenerationPipeline(input) {
|
|
|
19514
19774
|
});
|
|
19515
19775
|
const written = [];
|
|
19516
19776
|
if (!input.dryRun) {
|
|
19517
|
-
const dir =
|
|
19518
|
-
|
|
19777
|
+
const dir = path32.resolve(input.outDir, semantics.componentName);
|
|
19778
|
+
mkdirSync7(dir, { recursive: true });
|
|
19519
19779
|
const files = {
|
|
19520
19780
|
// Bundle-local tokens: THE emission the component's CSS resolves
|
|
19521
19781
|
// against — the same artifact the verify harness injects. A preview
|
|
@@ -19538,14 +19798,14 @@ async function runGenerationPipeline(input) {
|
|
|
19538
19798
|
`
|
|
19539
19799
|
};
|
|
19540
19800
|
for (const [name, content] of Object.entries(files)) {
|
|
19541
|
-
const filePath =
|
|
19542
|
-
|
|
19801
|
+
const filePath = path32.join(dir, name);
|
|
19802
|
+
writeFileSync11(filePath, content);
|
|
19543
19803
|
written.push(filePath);
|
|
19544
19804
|
}
|
|
19545
19805
|
for (const artifact of emitTokenArtifacts(input.mapping)) {
|
|
19546
|
-
const filePath =
|
|
19547
|
-
|
|
19548
|
-
|
|
19806
|
+
const filePath = path32.resolve(input.outDir, artifact.path);
|
|
19807
|
+
mkdirSync7(path32.dirname(filePath), { recursive: true });
|
|
19808
|
+
writeFileSync11(filePath, artifact.content);
|
|
19549
19809
|
written.push(filePath);
|
|
19550
19810
|
}
|
|
19551
19811
|
}
|
|
@@ -19603,7 +19863,7 @@ var GENERATE_DESCRIPTION = {
|
|
|
19603
19863
|
function resolveProvidedSource(flags, contextFile) {
|
|
19604
19864
|
let raw;
|
|
19605
19865
|
try {
|
|
19606
|
-
raw =
|
|
19866
|
+
raw = readFileSync23(contextFile, "utf8");
|
|
19607
19867
|
} catch {
|
|
19608
19868
|
fail(flags, ExitCode.InputValidation, {
|
|
19609
19869
|
error: `Cannot read context file "${contextFile}".`,
|
|
@@ -19723,11 +19983,11 @@ token mapping (${mapping.flat.length} variables):
|
|
|
19723
19983
|
let initialCode;
|
|
19724
19984
|
let initialSemantics;
|
|
19725
19985
|
try {
|
|
19726
|
-
if (
|
|
19986
|
+
if (existsSync25(flags.out)) {
|
|
19727
19987
|
for (const entry of readdirSync9(flags.out)) {
|
|
19728
19988
|
const cjPath = `${flags.out}/${entry}/component.json`;
|
|
19729
|
-
if (!
|
|
19730
|
-
const cj = JSON.parse(
|
|
19989
|
+
if (!existsSync25(cjPath)) continue;
|
|
19990
|
+
const cj = JSON.parse(readFileSync23(cjPath, "utf8"));
|
|
19731
19991
|
if (cj.name !== void 0 && Array.isArray(cj.props)) {
|
|
19732
19992
|
previousApi = JSON.stringify({
|
|
19733
19993
|
componentName: cj.name,
|
|
@@ -19735,14 +19995,14 @@ token mapping (${mapping.flat.length} variables):
|
|
|
19735
19995
|
});
|
|
19736
19996
|
const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
|
|
19737
19997
|
const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
|
|
19738
|
-
if (flags.refine &&
|
|
19998
|
+
if (flags.refine && existsSync25(tsxPath) && existsSync25(cssPath)) {
|
|
19739
19999
|
initialCode = {
|
|
19740
|
-
tsx:
|
|
19741
|
-
css:
|
|
20000
|
+
tsx: readFileSync23(tsxPath, "utf8"),
|
|
20001
|
+
css: readFileSync23(cssPath, "utf8")
|
|
19742
20002
|
};
|
|
19743
20003
|
const semPath = `${flags.out}/${entry}/semantics.json`;
|
|
19744
|
-
if (
|
|
19745
|
-
initialSemantics = JSON.parse(
|
|
20004
|
+
if (existsSync25(semPath)) {
|
|
20005
|
+
initialSemantics = JSON.parse(readFileSync23(semPath, "utf8"));
|
|
19746
20006
|
}
|
|
19747
20007
|
}
|
|
19748
20008
|
break;
|
|
@@ -20159,6 +20419,17 @@ function buildProgram() {
|
|
|
20159
20419
|
...local["deviceWait"] !== void 0 ? { deviceWait: local["deviceWait"] } : {}
|
|
20160
20420
|
});
|
|
20161
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
|
+
});
|
|
20162
20433
|
program.command("logout").description("Forget this machine's stored portal session. Does not end the session itself.").action(async (_o, cmd) => {
|
|
20163
20434
|
const flags = globalFlags(cmd.parent);
|
|
20164
20435
|
const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|