@tendrilapp/cli 0.1.23 → 0.1.24
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/SKILL.md +12 -7
- package/dist/tendril-mcp.js +3 -3
- package/dist/tendril.js +831 -670
- package/package.json +1 -1
package/dist/tendril.js
CHANGED
|
@@ -14,11 +14,11 @@ var __export = (target, all) => {
|
|
|
14
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// packages/core/src/ir.ts
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
var IrLayoutSchema, IrMotionSchema, IrPositionSchema, IrNodeSchema, ComponentIrSchema;
|
|
20
20
|
var init_ir = __esm({
|
|
21
|
-
"
|
|
21
|
+
"packages/core/src/ir.ts"() {
|
|
22
22
|
"use strict";
|
|
23
23
|
IrLayoutSchema = z.object({
|
|
24
24
|
mode: z.enum(["flex-row", "flex-column", "grid", "none"]),
|
|
@@ -110,14 +110,14 @@ var init_ir = __esm({
|
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
//
|
|
113
|
+
// packages/figma/src/source.ts
|
|
114
114
|
var init_source = __esm({
|
|
115
|
-
"
|
|
115
|
+
"packages/figma/src/source.ts"() {
|
|
116
116
|
"use strict";
|
|
117
117
|
}
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
//
|
|
120
|
+
// packages/figma/src/svg-safety.ts
|
|
121
121
|
function checkSvgSafety(source) {
|
|
122
122
|
const issues = [];
|
|
123
123
|
if (!/<\s*svg\b/i.test(source)) issues.push("not an <svg> document");
|
|
@@ -132,7 +132,7 @@ function assetExportName(key) {
|
|
|
132
132
|
}
|
|
133
133
|
var UNSAFE_PATTERNS;
|
|
134
134
|
var init_svg_safety = __esm({
|
|
135
|
-
"
|
|
135
|
+
"packages/figma/src/svg-safety.ts"() {
|
|
136
136
|
"use strict";
|
|
137
137
|
UNSAFE_PATTERNS = [
|
|
138
138
|
[/<\s*script\b/i, "script element"],
|
|
@@ -146,11 +146,11 @@ var init_svg_safety = __esm({
|
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
//
|
|
149
|
+
// packages/figma/src/provided.ts
|
|
150
150
|
import { z as z2 } from "zod";
|
|
151
151
|
var ProvidedVariableSchema, RangeSchema, PinnedPlacementSchema, VariantValueFactsSchema, ProvidedMetadataSchema, ProvidedContextSchema, ProvidedContextSource;
|
|
152
152
|
var init_provided = __esm({
|
|
153
|
-
"
|
|
153
|
+
"packages/figma/src/provided.ts"() {
|
|
154
154
|
"use strict";
|
|
155
155
|
init_svg_safety();
|
|
156
156
|
ProvidedVariableSchema = z2.object({
|
|
@@ -269,7 +269,7 @@ var init_provided = __esm({
|
|
|
269
269
|
}
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
-
//
|
|
272
|
+
// packages/figma/src/mcp/client.ts
|
|
273
273
|
function parseMessages(body, contentType) {
|
|
274
274
|
if (body.trim() === "") return [];
|
|
275
275
|
if (!contentType.includes("text/event-stream")) {
|
|
@@ -285,7 +285,7 @@ function parseMessages(body, contentType) {
|
|
|
285
285
|
}
|
|
286
286
|
var PROTOCOL_VERSION, McpUnavailableError, McpProtocolError, McpHttpClient;
|
|
287
287
|
var init_client = __esm({
|
|
288
|
-
"
|
|
288
|
+
"packages/figma/src/mcp/client.ts"() {
|
|
289
289
|
"use strict";
|
|
290
290
|
PROTOCOL_VERSION = "2025-06-18";
|
|
291
291
|
McpUnavailableError = class extends Error {
|
|
@@ -389,7 +389,7 @@ var init_client = __esm({
|
|
|
389
389
|
}
|
|
390
390
|
});
|
|
391
391
|
|
|
392
|
-
//
|
|
392
|
+
// packages/figma/src/mcp/normalize.ts
|
|
393
393
|
function decodeXmlEntities(v) {
|
|
394
394
|
return v.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
395
395
|
}
|
|
@@ -472,7 +472,7 @@ function mergeVariantAxes(childNames) {
|
|
|
472
472
|
}
|
|
473
473
|
var TAG_TO_TYPE, TAG_RE, ATTR_RE;
|
|
474
474
|
var init_normalize = __esm({
|
|
475
|
-
"
|
|
475
|
+
"packages/figma/src/mcp/normalize.ts"() {
|
|
476
476
|
"use strict";
|
|
477
477
|
init_svg_safety();
|
|
478
478
|
TAG_TO_TYPE = {
|
|
@@ -488,10 +488,10 @@ var init_normalize = __esm({
|
|
|
488
488
|
}
|
|
489
489
|
});
|
|
490
490
|
|
|
491
|
-
//
|
|
491
|
+
// packages/figma/src/mock/fixtures/button.ts
|
|
492
492
|
var buttonVariables, buttonMetadata, buttonNodeTree;
|
|
493
493
|
var init_button = __esm({
|
|
494
|
-
"
|
|
494
|
+
"packages/figma/src/mock/fixtures/button.ts"() {
|
|
495
495
|
"use strict";
|
|
496
496
|
buttonVariables = [
|
|
497
497
|
{
|
|
@@ -600,10 +600,10 @@ var init_button = __esm({
|
|
|
600
600
|
}
|
|
601
601
|
});
|
|
602
602
|
|
|
603
|
-
//
|
|
603
|
+
// packages/figma/src/mock/mock-source.ts
|
|
604
604
|
var MockFigmaSource;
|
|
605
605
|
var init_mock_source = __esm({
|
|
606
|
-
"
|
|
606
|
+
"packages/figma/src/mock/mock-source.ts"() {
|
|
607
607
|
"use strict";
|
|
608
608
|
init_button();
|
|
609
609
|
MockFigmaSource = class {
|
|
@@ -640,7 +640,7 @@ var init_mock_source = __esm({
|
|
|
640
640
|
}
|
|
641
641
|
});
|
|
642
642
|
|
|
643
|
-
//
|
|
643
|
+
// packages/figma/src/css-ident.ts
|
|
644
644
|
function canonicalCssIdentPart(part) {
|
|
645
645
|
return part.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
646
646
|
}
|
|
@@ -649,27 +649,27 @@ function variableNameToCssVar(name) {
|
|
|
649
649
|
return `--${parts.join("-")}`;
|
|
650
650
|
}
|
|
651
651
|
var init_css_ident = __esm({
|
|
652
|
-
"
|
|
652
|
+
"packages/figma/src/css-ident.ts"() {
|
|
653
653
|
"use strict";
|
|
654
654
|
}
|
|
655
655
|
});
|
|
656
656
|
|
|
657
|
-
//
|
|
657
|
+
// packages/figma/src/mcp/var-decoder.ts
|
|
658
658
|
var init_var_decoder = __esm({
|
|
659
|
-
"
|
|
659
|
+
"packages/figma/src/mcp/var-decoder.ts"() {
|
|
660
660
|
"use strict";
|
|
661
661
|
init_css_ident();
|
|
662
662
|
}
|
|
663
663
|
});
|
|
664
664
|
|
|
665
|
-
//
|
|
665
|
+
// packages/figma/src/mcp/font-normalize.ts
|
|
666
666
|
var init_font_normalize = __esm({
|
|
667
|
-
"
|
|
667
|
+
"packages/figma/src/mcp/font-normalize.ts"() {
|
|
668
668
|
"use strict";
|
|
669
669
|
}
|
|
670
670
|
});
|
|
671
671
|
|
|
672
|
-
//
|
|
672
|
+
// packages/figma/src/provided-recording.ts
|
|
673
673
|
import { z as z3 } from "zod";
|
|
674
674
|
function checkPngPayload(base64) {
|
|
675
675
|
let raw;
|
|
@@ -684,7 +684,7 @@ function checkPngPayload(base64) {
|
|
|
684
684
|
}
|
|
685
685
|
var MAX_EMISSION_BYTES, MAX_IMAGE_BYTES, MAX_ASSET_BYTES, PNG_MAGIC, TextEnvelopeSchema, ImageEnvelopeSchema, ProvidedRepSchema, ManifestWireSchema, ProvidedRecordingSetSchema;
|
|
686
686
|
var init_provided_recording = __esm({
|
|
687
|
-
"
|
|
687
|
+
"packages/figma/src/provided-recording.ts"() {
|
|
688
688
|
"use strict";
|
|
689
689
|
init_svg_safety();
|
|
690
690
|
MAX_EMISSION_BYTES = 2 * 1024 * 1024;
|
|
@@ -752,7 +752,7 @@ var init_provided_recording = __esm({
|
|
|
752
752
|
}
|
|
753
753
|
});
|
|
754
754
|
|
|
755
|
-
//
|
|
755
|
+
// packages/figma/src/recording/axis-defaults.ts
|
|
756
756
|
function resolveAxisDefaultWithRule(domain, recorded, override) {
|
|
757
757
|
if (override !== void 0) return { value: override, rule: "override" };
|
|
758
758
|
const literal = domain.find((v) => kebab(v) === "default" || kebab(v) === "enabled");
|
|
@@ -787,7 +787,7 @@ function resolveAxisDefault(domain, recorded, override) {
|
|
|
787
787
|
}
|
|
788
788
|
var INTERACTION_STATES, BOOLEAN_STATES, ENGAGED_STATES, INTERACTION_EVIDENCE_VALUES, kebab, OFF_VALUES, ON_VALUES;
|
|
789
789
|
var init_axis_defaults = __esm({
|
|
790
|
-
"
|
|
790
|
+
"packages/figma/src/recording/axis-defaults.ts"() {
|
|
791
791
|
"use strict";
|
|
792
792
|
INTERACTION_STATES = /* @__PURE__ */ new Set(["hover", "focus", "focus-visible", "active", "pressed"]);
|
|
793
793
|
BOOLEAN_STATES = /* @__PURE__ */ new Set(["disabled", "loading"]);
|
|
@@ -810,7 +810,7 @@ var init_axis_defaults = __esm({
|
|
|
810
810
|
}
|
|
811
811
|
});
|
|
812
812
|
|
|
813
|
-
//
|
|
813
|
+
// packages/figma/src/recording/plan.ts
|
|
814
814
|
function singleAxes(name) {
|
|
815
815
|
const parsed = parseVariantAxes(name);
|
|
816
816
|
if (parsed === void 0) return void 0;
|
|
@@ -920,7 +920,7 @@ function planQueue(symbols, opts = {}) {
|
|
|
920
920
|
}
|
|
921
921
|
var kebab2;
|
|
922
922
|
var init_plan = __esm({
|
|
923
|
-
"
|
|
923
|
+
"packages/figma/src/recording/plan.ts"() {
|
|
924
924
|
"use strict";
|
|
925
925
|
init_normalize();
|
|
926
926
|
init_axis_defaults();
|
|
@@ -928,7 +928,7 @@ var init_plan = __esm({
|
|
|
928
928
|
}
|
|
929
929
|
});
|
|
930
930
|
|
|
931
|
-
//
|
|
931
|
+
// packages/figma/src/recording/session.ts
|
|
932
932
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
933
933
|
import path from "node:path";
|
|
934
934
|
import { z as z4 } from "zod";
|
|
@@ -1078,7 +1078,7 @@ function ingestAsset(setDir, slug, name, content) {
|
|
|
1078
1078
|
}
|
|
1079
1079
|
var RECORD_TOOLS, INTERIOR_TOOL, REQUIRED_TOOLS, SessionManifestSchema, manifestPath, PROTOCOL_ORDER, byProtocolOrder;
|
|
1080
1080
|
var init_session = __esm({
|
|
1081
|
-
"
|
|
1081
|
+
"packages/figma/src/recording/session.ts"() {
|
|
1082
1082
|
"use strict";
|
|
1083
1083
|
init_svg_safety();
|
|
1084
1084
|
init_provided_recording();
|
|
@@ -1123,7 +1123,7 @@ var init_session = __esm({
|
|
|
1123
1123
|
}
|
|
1124
1124
|
});
|
|
1125
1125
|
|
|
1126
|
-
//
|
|
1126
|
+
// packages/figma/src/recording/roles.ts
|
|
1127
1127
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
1128
1128
|
import path2 from "node:path";
|
|
1129
1129
|
function emissionIdSets(emission) {
|
|
@@ -1192,16 +1192,16 @@ function deriveRoles(setDir, manifest) {
|
|
|
1192
1192
|
}
|
|
1193
1193
|
var NODE_ID_RE;
|
|
1194
1194
|
var init_roles = __esm({
|
|
1195
|
-
"
|
|
1195
|
+
"packages/figma/src/recording/roles.ts"() {
|
|
1196
1196
|
"use strict";
|
|
1197
1197
|
init_session();
|
|
1198
1198
|
NODE_ID_RE = /data-node-id="([^"]+)"/g;
|
|
1199
1199
|
}
|
|
1200
1200
|
});
|
|
1201
1201
|
|
|
1202
|
-
//
|
|
1202
|
+
// packages/figma/src/index.ts
|
|
1203
1203
|
var init_src = __esm({
|
|
1204
|
-
"
|
|
1204
|
+
"packages/figma/src/index.ts"() {
|
|
1205
1205
|
"use strict";
|
|
1206
1206
|
init_source();
|
|
1207
1207
|
init_provided();
|
|
@@ -1220,12 +1220,12 @@ var init_src = __esm({
|
|
|
1220
1220
|
}
|
|
1221
1221
|
});
|
|
1222
1222
|
|
|
1223
|
-
//
|
|
1223
|
+
// packages/tokens/src/dtcg.ts
|
|
1224
1224
|
function variableNameToPath(name) {
|
|
1225
1225
|
return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
|
|
1226
1226
|
}
|
|
1227
|
-
function tokenPathToCssVar(
|
|
1228
|
-
return `--${
|
|
1227
|
+
function tokenPathToCssVar(path40) {
|
|
1228
|
+
return `--${path40.join("-")}`;
|
|
1229
1229
|
}
|
|
1230
1230
|
function toDtcgToken(variable, defaultMode) {
|
|
1231
1231
|
const modes = Object.keys(variable.valuesByMode);
|
|
@@ -1269,11 +1269,11 @@ function toDtcgToken(variable, defaultMode) {
|
|
|
1269
1269
|
}
|
|
1270
1270
|
function mapVariablesToDtcg(variables, defaultMode = "light") {
|
|
1271
1271
|
const entries = variables.map((variable) => {
|
|
1272
|
-
const
|
|
1273
|
-
if (
|
|
1272
|
+
const path40 = variableNameToPath(variable.name);
|
|
1273
|
+
if (path40.length === 0) {
|
|
1274
1274
|
throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
|
|
1275
1275
|
}
|
|
1276
|
-
return { variable, path:
|
|
1276
|
+
return { variable, path: path40 };
|
|
1277
1277
|
});
|
|
1278
1278
|
const groupPrefixes = /* @__PURE__ */ new Set();
|
|
1279
1279
|
for (const e of entries) {
|
|
@@ -1294,21 +1294,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
|
|
|
1294
1294
|
}
|
|
1295
1295
|
const tokens = {};
|
|
1296
1296
|
const flat = [];
|
|
1297
|
-
for (const { variable, path:
|
|
1297
|
+
for (const { variable, path: path40 } of entries) {
|
|
1298
1298
|
const token = toDtcgToken(variable, defaultMode);
|
|
1299
1299
|
let group = tokens;
|
|
1300
|
-
for (const segment of
|
|
1300
|
+
for (const segment of path40.slice(0, -1)) {
|
|
1301
1301
|
const existing = group[segment];
|
|
1302
1302
|
group = existing ?? (group[segment] = {});
|
|
1303
1303
|
}
|
|
1304
|
-
const leaf =
|
|
1304
|
+
const leaf = path40[path40.length - 1];
|
|
1305
1305
|
if (group[leaf] !== void 0) {
|
|
1306
|
-
throw new DtcgMappingError("duplicate-path", `Duplicate token path "${
|
|
1306
|
+
throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path40.join(".")}" (variable ${variable.id})`);
|
|
1307
1307
|
}
|
|
1308
1308
|
group[leaf] = token;
|
|
1309
1309
|
flat.push({
|
|
1310
|
-
path:
|
|
1311
|
-
cssVar: tokenPathToCssVar(
|
|
1310
|
+
path: path40.join("."),
|
|
1311
|
+
cssVar: tokenPathToCssVar(path40),
|
|
1312
1312
|
type: token.$type,
|
|
1313
1313
|
value: token.$value
|
|
1314
1314
|
});
|
|
@@ -1326,7 +1326,7 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
|
|
|
1326
1326
|
}
|
|
1327
1327
|
var DIMENSION_COLLECTIONS, DtcgMappingError;
|
|
1328
1328
|
var init_dtcg = __esm({
|
|
1329
|
-
"
|
|
1329
|
+
"packages/tokens/src/dtcg.ts"() {
|
|
1330
1330
|
"use strict";
|
|
1331
1331
|
init_src();
|
|
1332
1332
|
DIMENSION_COLLECTIONS = /* @__PURE__ */ new Set(["spacing", "radius", "size", "border-width"]);
|
|
@@ -1341,7 +1341,7 @@ var init_dtcg = __esm({
|
|
|
1341
1341
|
}
|
|
1342
1342
|
});
|
|
1343
1343
|
|
|
1344
|
-
//
|
|
1344
|
+
// packages/tokens/src/emit.ts
|
|
1345
1345
|
function decomposeFontValue(raw, valueByCssVar) {
|
|
1346
1346
|
const field = (name) => new RegExp(`${name}:\\s*("[^"]*"|[^,)]+)`).exec(raw)?.[1]?.trim();
|
|
1347
1347
|
const resolve = (v) => {
|
|
@@ -1408,7 +1408,7 @@ export default {
|
|
|
1408
1408
|
}
|
|
1409
1409
|
var STYLE_WEIGHTS;
|
|
1410
1410
|
var init_emit = __esm({
|
|
1411
|
-
"
|
|
1411
|
+
"packages/tokens/src/emit.ts"() {
|
|
1412
1412
|
"use strict";
|
|
1413
1413
|
init_src();
|
|
1414
1414
|
STYLE_WEIGHTS = {
|
|
@@ -1427,7 +1427,7 @@ var init_emit = __esm({
|
|
|
1427
1427
|
}
|
|
1428
1428
|
});
|
|
1429
1429
|
|
|
1430
|
-
//
|
|
1430
|
+
// packages/tokens/src/synthesize.ts
|
|
1431
1431
|
function synthesizeSpacingVariables(tree, variantFacts, existing) {
|
|
1432
1432
|
if (existing.some((v) => v.collection === "spacing")) return [];
|
|
1433
1433
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -1466,7 +1466,7 @@ function synthesizeSpacingVariables(tree, variantFacts, existing) {
|
|
|
1466
1466
|
}
|
|
1467
1467
|
var SPACING_FIELDS;
|
|
1468
1468
|
var init_synthesize = __esm({
|
|
1469
|
-
"
|
|
1469
|
+
"packages/tokens/src/synthesize.ts"() {
|
|
1470
1470
|
"use strict";
|
|
1471
1471
|
SPACING_FIELDS = [
|
|
1472
1472
|
"paddingTop",
|
|
@@ -1478,9 +1478,9 @@ var init_synthesize = __esm({
|
|
|
1478
1478
|
}
|
|
1479
1479
|
});
|
|
1480
1480
|
|
|
1481
|
-
//
|
|
1481
|
+
// packages/tokens/src/index.ts
|
|
1482
1482
|
var init_src2 = __esm({
|
|
1483
|
-
"
|
|
1483
|
+
"packages/tokens/src/index.ts"() {
|
|
1484
1484
|
"use strict";
|
|
1485
1485
|
init_dtcg();
|
|
1486
1486
|
init_emit();
|
|
@@ -1489,7 +1489,7 @@ var init_src2 = __esm({
|
|
|
1489
1489
|
}
|
|
1490
1490
|
});
|
|
1491
1491
|
|
|
1492
|
-
//
|
|
1492
|
+
// packages/core/src/ir-builder.ts
|
|
1493
1493
|
function isObject(value) {
|
|
1494
1494
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1495
1495
|
}
|
|
@@ -1497,9 +1497,9 @@ function boundId(value) {
|
|
|
1497
1497
|
return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
|
|
1498
1498
|
}
|
|
1499
1499
|
function resolveBinding(ctx, id) {
|
|
1500
|
-
const
|
|
1501
|
-
if (
|
|
1502
|
-
return
|
|
1500
|
+
const path40 = ctx.pathById.get(id);
|
|
1501
|
+
if (path40 === void 0) ctx.unresolved.add(id);
|
|
1502
|
+
return path40;
|
|
1503
1503
|
}
|
|
1504
1504
|
function parseVariantProps(name) {
|
|
1505
1505
|
if (!name.includes("=")) return void 0;
|
|
@@ -1534,8 +1534,8 @@ function walk(ctx, raw) {
|
|
|
1534
1534
|
if (!isObject(paint) || paint["visible"] === false) continue;
|
|
1535
1535
|
const id = boundId(paint);
|
|
1536
1536
|
if (id !== void 0) {
|
|
1537
|
-
const
|
|
1538
|
-
if (
|
|
1537
|
+
const path40 = resolveBinding(ctx, id);
|
|
1538
|
+
if (path40 !== void 0) tokens.add(path40);
|
|
1539
1539
|
} else if (typeof paint["color"] === "string") {
|
|
1540
1540
|
ctx.hardcoded.push({ node: name, property, value: paint["color"] });
|
|
1541
1541
|
}
|
|
@@ -1543,8 +1543,8 @@ function walk(ctx, raw) {
|
|
|
1543
1543
|
}
|
|
1544
1544
|
const radiusId = boundId(raw["cornerRadius"]);
|
|
1545
1545
|
if (radiusId !== void 0) {
|
|
1546
|
-
const
|
|
1547
|
-
if (
|
|
1546
|
+
const path40 = resolveBinding(ctx, radiusId);
|
|
1547
|
+
if (path40 !== void 0) tokens.add(path40);
|
|
1548
1548
|
} else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
|
|
1549
1549
|
ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
|
|
1550
1550
|
}
|
|
@@ -1554,10 +1554,10 @@ function walk(ctx, raw) {
|
|
|
1554
1554
|
layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
|
|
1555
1555
|
const gapId = boundId(raw["itemSpacing"]);
|
|
1556
1556
|
if (gapId !== void 0) {
|
|
1557
|
-
const
|
|
1558
|
-
if (
|
|
1559
|
-
layout.gap =
|
|
1560
|
-
tokens.add(
|
|
1557
|
+
const path40 = resolveBinding(ctx, gapId);
|
|
1558
|
+
if (path40 !== void 0) {
|
|
1559
|
+
layout.gap = path40;
|
|
1560
|
+
tokens.add(path40);
|
|
1561
1561
|
}
|
|
1562
1562
|
} else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
|
|
1563
1563
|
ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
|
|
@@ -1566,10 +1566,10 @@ function walk(ctx, raw) {
|
|
|
1566
1566
|
for (const field of PADDING_FIELDS) {
|
|
1567
1567
|
const id = boundId(raw[field]);
|
|
1568
1568
|
if (id !== void 0) {
|
|
1569
|
-
const
|
|
1570
|
-
if (
|
|
1571
|
-
paddingPaths.push(
|
|
1572
|
-
tokens.add(
|
|
1569
|
+
const path40 = resolveBinding(ctx, id);
|
|
1570
|
+
if (path40 !== void 0) {
|
|
1571
|
+
paddingPaths.push(path40);
|
|
1572
|
+
tokens.add(path40);
|
|
1573
1573
|
}
|
|
1574
1574
|
} else if (typeof raw[field] === "number" && raw[field] !== 0) {
|
|
1575
1575
|
ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
|
|
@@ -1674,7 +1674,7 @@ function canonicalizeFactIdents(variantFacts) {
|
|
|
1674
1674
|
}
|
|
1675
1675
|
var RAW_PAYLOAD_FLAG_BYTES, MIN_HEALTHY_RATIO, RATIO_CHECK_MIN_BYTES, PADDING_FIELDS;
|
|
1676
1676
|
var init_ir_builder = __esm({
|
|
1677
|
-
"
|
|
1677
|
+
"packages/core/src/ir-builder.ts"() {
|
|
1678
1678
|
"use strict";
|
|
1679
1679
|
init_src2();
|
|
1680
1680
|
init_src2();
|
|
@@ -1685,10 +1685,10 @@ var init_ir_builder = __esm({
|
|
|
1685
1685
|
}
|
|
1686
1686
|
});
|
|
1687
1687
|
|
|
1688
|
-
//
|
|
1688
|
+
// packages/core/src/exit-codes.ts
|
|
1689
1689
|
var ExitCode;
|
|
1690
1690
|
var init_exit_codes = __esm({
|
|
1691
|
-
"
|
|
1691
|
+
"packages/core/src/exit-codes.ts"() {
|
|
1692
1692
|
"use strict";
|
|
1693
1693
|
ExitCode = {
|
|
1694
1694
|
Success: 0,
|
|
@@ -1709,7 +1709,7 @@ var init_exit_codes = __esm({
|
|
|
1709
1709
|
}
|
|
1710
1710
|
});
|
|
1711
1711
|
|
|
1712
|
-
//
|
|
1712
|
+
// packages/core/src/readiness.ts
|
|
1713
1713
|
function collectDefaultNamedLayers(tree, found) {
|
|
1714
1714
|
const name = tree["name"];
|
|
1715
1715
|
const visible = tree["visible"];
|
|
@@ -1759,15 +1759,15 @@ function scoreReadiness(metadata, tree) {
|
|
|
1759
1759
|
}
|
|
1760
1760
|
var DEFAULT_LAYER_NAME;
|
|
1761
1761
|
var init_readiness = __esm({
|
|
1762
|
-
"
|
|
1762
|
+
"packages/core/src/readiness.ts"() {
|
|
1763
1763
|
"use strict";
|
|
1764
1764
|
DEFAULT_LAYER_NAME = /^(Group|Frame|Rectangle|Ellipse|Vector|Component)\s*\d*$/;
|
|
1765
1765
|
}
|
|
1766
1766
|
});
|
|
1767
1767
|
|
|
1768
|
-
//
|
|
1768
|
+
// packages/core/src/index.ts
|
|
1769
1769
|
var init_src3 = __esm({
|
|
1770
|
-
"
|
|
1770
|
+
"packages/core/src/index.ts"() {
|
|
1771
1771
|
"use strict";
|
|
1772
1772
|
init_ir();
|
|
1773
1773
|
init_ir_builder();
|
|
@@ -1776,20 +1776,72 @@ var init_src3 = __esm({
|
|
|
1776
1776
|
}
|
|
1777
1777
|
});
|
|
1778
1778
|
|
|
1779
|
-
//
|
|
1780
|
-
import {
|
|
1781
|
-
import { existsSync as existsSync3, readdirSync } from "node:fs";
|
|
1779
|
+
// packages/cli/src/invocation.ts
|
|
1780
|
+
import { existsSync as existsSync3, realpathSync } from "node:fs";
|
|
1782
1781
|
import path3 from "node:path";
|
|
1782
|
+
import { fileURLToPath } from "node:url";
|
|
1783
|
+
function findPathTendril(pathEnv, platform) {
|
|
1784
|
+
const dirs = pathEnv.split(path3.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
|
|
1785
|
+
const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
|
|
1786
|
+
for (const dir of dirs) {
|
|
1787
|
+
for (const name of names) {
|
|
1788
|
+
const candidate = path3.join(dir, name);
|
|
1789
|
+
if (existsSync3(candidate)) return candidate;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
function packageRootOf(file) {
|
|
1795
|
+
let dir = path3.dirname(file);
|
|
1796
|
+
for (; ; ) {
|
|
1797
|
+
if (existsSync3(path3.join(dir, "package.json"))) return dir;
|
|
1798
|
+
const parent = path3.dirname(dir);
|
|
1799
|
+
if (parent === dir) return null;
|
|
1800
|
+
dir = parent;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
function resolveInvocation(pathEnv, platform, selfFile = fileURLToPath(import.meta.url)) {
|
|
1804
|
+
const binary = findPathTendril(pathEnv, platform);
|
|
1805
|
+
if (binary === null) return NPX_INVOCATION;
|
|
1806
|
+
try {
|
|
1807
|
+
const onPath = packageRootOf(realpathSync(binary));
|
|
1808
|
+
const self = packageRootOf(realpathSync(selfFile));
|
|
1809
|
+
if (onPath === null || self === null) return NPX_INVOCATION;
|
|
1810
|
+
return realpathSync(onPath) === realpathSync(self) ? "tendril" : NPX_INVOCATION;
|
|
1811
|
+
} catch {
|
|
1812
|
+
return NPX_INVOCATION;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function tendrilInvocation() {
|
|
1816
|
+
cached ??= resolveInvocation(process.env["PATH"] ?? "", process.platform);
|
|
1817
|
+
return cached;
|
|
1818
|
+
}
|
|
1819
|
+
function tendrilCommand(args) {
|
|
1820
|
+
return `${tendrilInvocation()} ${args}`;
|
|
1821
|
+
}
|
|
1822
|
+
var NPX_INVOCATION, cached;
|
|
1823
|
+
var init_invocation = __esm({
|
|
1824
|
+
"packages/cli/src/invocation.ts"() {
|
|
1825
|
+
"use strict";
|
|
1826
|
+
NPX_INVOCATION = "npx -y -p @tendrilapp/cli@latest tendril";
|
|
1827
|
+
cached = null;
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1831
|
+
// packages/verify/src/browser.ts
|
|
1832
|
+
import { execFileSync } from "node:child_process";
|
|
1833
|
+
import { existsSync as existsSync4, readdirSync } from "node:fs";
|
|
1834
|
+
import path4 from "node:path";
|
|
1783
1835
|
function resolveChrome() {
|
|
1784
1836
|
const fromEnv = process.env["TENDRIL_CHROME"] ?? process.env["CHROME_PATH"];
|
|
1785
1837
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
1786
|
-
if (!
|
|
1838
|
+
if (!existsSync4(fromEnv)) {
|
|
1787
1839
|
throw new Error(`CHROME_PATH points at ${fromEnv}, which does not exist \u2014 fix the variable or unset it to use discovery`);
|
|
1788
1840
|
}
|
|
1789
1841
|
return fromEnv;
|
|
1790
1842
|
}
|
|
1791
1843
|
const candidates = process.platform === "darwin" ? MAC_CANDIDATES : process.platform === "win32" ? WIN_CANDIDATES : [];
|
|
1792
|
-
for (const c of candidates) if (
|
|
1844
|
+
for (const c of candidates) if (existsSync4(c)) return c;
|
|
1793
1845
|
if (process.platform !== "win32") {
|
|
1794
1846
|
for (const name of PATH_NAMES) {
|
|
1795
1847
|
try {
|
|
@@ -1803,7 +1855,7 @@ function resolveChrome() {
|
|
|
1803
1855
|
}
|
|
1804
1856
|
function versionFromInstallDir(exePath) {
|
|
1805
1857
|
try {
|
|
1806
|
-
const builds = readdirSync(
|
|
1858
|
+
const builds = readdirSync(path4.dirname(exePath), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\d+(\.\d+){3}$/.test(e.name)).map((e) => e.name.split(".").map(Number));
|
|
1807
1859
|
builds.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2] || a[3] - b[3]);
|
|
1808
1860
|
return builds.length === 0 ? null : builds[builds.length - 1].join(".");
|
|
1809
1861
|
} catch {
|
|
@@ -1815,7 +1867,7 @@ function windowsBrowserName(exePath) {
|
|
|
1815
1867
|
if (p.includes("\\google\\chrome\\")) return "Google Chrome";
|
|
1816
1868
|
if (p.includes("\\microsoft\\edge\\")) return "Microsoft Edge";
|
|
1817
1869
|
if (p.includes("chromium")) return "Chromium";
|
|
1818
|
-
return
|
|
1870
|
+
return path4.win32.basename(exePath, ".exe");
|
|
1819
1871
|
}
|
|
1820
1872
|
function chromeVersion() {
|
|
1821
1873
|
if (_version !== void 0) return _version;
|
|
@@ -1834,7 +1886,7 @@ function chromeVersion() {
|
|
|
1834
1886
|
}
|
|
1835
1887
|
var MAC_CANDIDATES, WIN_CANDIDATES, PATH_NAMES, _version;
|
|
1836
1888
|
var init_browser = __esm({
|
|
1837
|
-
"
|
|
1889
|
+
"packages/verify/src/browser.ts"() {
|
|
1838
1890
|
"use strict";
|
|
1839
1891
|
MAC_CANDIDATES = [
|
|
1840
1892
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
@@ -1849,33 +1901,33 @@ var init_browser = __esm({
|
|
|
1849
1901
|
}
|
|
1850
1902
|
});
|
|
1851
1903
|
|
|
1852
|
-
//
|
|
1853
|
-
import { existsSync as
|
|
1904
|
+
// packages/verify/src/runtime.ts
|
|
1905
|
+
import { existsSync as existsSync5, mkdtempSync, symlinkSync } from "node:fs";
|
|
1854
1906
|
import { createRequire } from "node:module";
|
|
1855
1907
|
import os from "node:os";
|
|
1856
|
-
import
|
|
1857
|
-
import { fileURLToPath } from "node:url";
|
|
1908
|
+
import path5 from "node:path";
|
|
1909
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1858
1910
|
function runtimePackageRoot() {
|
|
1859
1911
|
const env = process.env["TENDRIL_PACKAGE_ROOT"];
|
|
1860
|
-
if (env !== void 0 && env !== "") return
|
|
1861
|
-
return
|
|
1912
|
+
if (env !== void 0 && env !== "") return path5.resolve(env);
|
|
1913
|
+
return path5.resolve(path5.dirname(fileURLToPath2(import.meta.url)), "..");
|
|
1862
1914
|
}
|
|
1863
1915
|
function runtimeNodeModules() {
|
|
1864
1916
|
const root = runtimePackageRoot();
|
|
1865
|
-
for (let dir = root; ; dir =
|
|
1866
|
-
const candidate =
|
|
1867
|
-
if (
|
|
1868
|
-
if (
|
|
1917
|
+
for (let dir = root; ; dir = path5.dirname(dir)) {
|
|
1918
|
+
const candidate = path5.basename(dir) === "node_modules" ? dir : path5.join(dir, "node_modules");
|
|
1919
|
+
if (existsSync5(path5.join(candidate, "react"))) return candidate;
|
|
1920
|
+
if (path5.dirname(dir) === dir) break;
|
|
1869
1921
|
}
|
|
1870
1922
|
throw new Error(
|
|
1871
1923
|
`Tendril's installed dependencies are missing: no node_modules containing react found at or above ${root}. This is an INSTALLATION problem, not a component error \u2014 reinstall with \`npm install -g @tendrilapp/cli\` (or \`tendrilapp\`) and retry.`
|
|
1872
1924
|
);
|
|
1873
1925
|
}
|
|
1874
1926
|
function newScratchDir(prefix) {
|
|
1875
|
-
const dir = mkdtempSync(
|
|
1927
|
+
const dir = mkdtempSync(path5.join(os.tmpdir(), `tendril-${prefix}-`));
|
|
1876
1928
|
const nodeModules = runtimeNodeModules();
|
|
1877
1929
|
try {
|
|
1878
|
-
symlinkSync(nodeModules,
|
|
1930
|
+
symlinkSync(nodeModules, path5.join(dir, "node_modules"), "junction");
|
|
1879
1931
|
} catch (err) {
|
|
1880
1932
|
throw new Error(
|
|
1881
1933
|
`Tendril could not link its dependencies into the scratch dir (${nodeModules} -> ${dir}): ${err instanceof Error ? err.message : String(err)}. This is an environment problem, not a component error.`
|
|
@@ -1884,7 +1936,7 @@ function newScratchDir(prefix) {
|
|
|
1884
1936
|
return dir;
|
|
1885
1937
|
}
|
|
1886
1938
|
function reactPinPlugin() {
|
|
1887
|
-
const req = createRequire(
|
|
1939
|
+
const req = createRequire(path5.join(runtimePackageRoot(), "package.json"));
|
|
1888
1940
|
return {
|
|
1889
1941
|
name: "tendril-react-pin",
|
|
1890
1942
|
setup(b) {
|
|
@@ -1899,12 +1951,12 @@ function reactPinPlugin() {
|
|
|
1899
1951
|
};
|
|
1900
1952
|
}
|
|
1901
1953
|
var init_runtime = __esm({
|
|
1902
|
-
"
|
|
1954
|
+
"packages/verify/src/runtime.ts"() {
|
|
1903
1955
|
"use strict";
|
|
1904
1956
|
}
|
|
1905
1957
|
});
|
|
1906
1958
|
|
|
1907
|
-
//
|
|
1959
|
+
// packages/verify/src/gates.ts
|
|
1908
1960
|
function scanForbiddenPatterns(source) {
|
|
1909
1961
|
const violations = [];
|
|
1910
1962
|
const lines = source.split("\n");
|
|
@@ -1923,7 +1975,7 @@ function scanForbiddenPatterns(source) {
|
|
|
1923
1975
|
}
|
|
1924
1976
|
var FORBIDDEN_PATTERNS;
|
|
1925
1977
|
var init_gates = __esm({
|
|
1926
|
-
"
|
|
1978
|
+
"packages/verify/src/gates.ts"() {
|
|
1927
1979
|
"use strict";
|
|
1928
1980
|
FORBIDDEN_PATTERNS = [
|
|
1929
1981
|
{ name: "eslint-disable", regex: /eslint-disable/ },
|
|
@@ -1935,11 +1987,11 @@ var init_gates = __esm({
|
|
|
1935
1987
|
}
|
|
1936
1988
|
});
|
|
1937
1989
|
|
|
1938
|
-
//
|
|
1990
|
+
// packages/verify/src/tsc-check.ts
|
|
1939
1991
|
import ts from "typescript";
|
|
1940
|
-
import
|
|
1992
|
+
import path6 from "node:path";
|
|
1941
1993
|
function runTscStrict(files) {
|
|
1942
|
-
const program = ts.createProgram(files.map((f) =>
|
|
1994
|
+
const program = ts.createProgram(files.map((f) => path6.resolve(f)), STRICT_OPTIONS);
|
|
1943
1995
|
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
1944
1996
|
const mapped = diagnostics.map((d) => {
|
|
1945
1997
|
const file = d.file?.fileName;
|
|
@@ -1955,7 +2007,7 @@ function runTscStrict(files) {
|
|
|
1955
2007
|
}
|
|
1956
2008
|
var STRICT_OPTIONS;
|
|
1957
2009
|
var init_tsc_check = __esm({
|
|
1958
|
-
"
|
|
2010
|
+
"packages/verify/src/tsc-check.ts"() {
|
|
1959
2011
|
"use strict";
|
|
1960
2012
|
STRICT_OPTIONS = {
|
|
1961
2013
|
strict: true,
|
|
@@ -1975,7 +2027,7 @@ var init_tsc_check = __esm({
|
|
|
1975
2027
|
}
|
|
1976
2028
|
});
|
|
1977
2029
|
|
|
1978
|
-
//
|
|
2030
|
+
// packages/verify/src/token-lint.ts
|
|
1979
2031
|
import strictValue from "stylelint-declaration-strict-value";
|
|
1980
2032
|
import stylelint from "stylelint";
|
|
1981
2033
|
async function runTokenLint(css, fileLabel = "generated.css", definedVars2, recordedMapEmpty = false) {
|
|
@@ -2036,7 +2088,7 @@ async function runTokenLint(css, fileLabel = "generated.css", definedVars2, reco
|
|
|
2036
2088
|
}
|
|
2037
2089
|
var TOKEN_ENFORCED_PROPERTIES, ALLOWED_KEYWORDS, STYLELINT_CONFIG, LEGAL_CUSTOM_PROP;
|
|
2038
2090
|
var init_token_lint = __esm({
|
|
2039
|
-
"
|
|
2091
|
+
"packages/verify/src/token-lint.ts"() {
|
|
2040
2092
|
"use strict";
|
|
2041
2093
|
TOKEN_ENFORCED_PROPERTIES = [
|
|
2042
2094
|
"/color$/",
|
|
@@ -2096,9 +2148,9 @@ var init_token_lint = __esm({
|
|
|
2096
2148
|
}
|
|
2097
2149
|
});
|
|
2098
2150
|
|
|
2099
|
-
//
|
|
2151
|
+
// packages/verify/src/loop.ts
|
|
2100
2152
|
import { rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2101
|
-
import
|
|
2153
|
+
import path7 from "node:path";
|
|
2102
2154
|
async function runChecks(componentName, files, extraFiles, definedVars2) {
|
|
2103
2155
|
const findings = [];
|
|
2104
2156
|
for (const violation of scanForbiddenPatterns(files.tsx)) {
|
|
@@ -2113,10 +2165,10 @@ async function runChecks(componentName, files, extraFiles, definedVars2) {
|
|
|
2113
2165
|
}
|
|
2114
2166
|
const workDir = newScratchDir("loop");
|
|
2115
2167
|
try {
|
|
2116
|
-
const tsxPath =
|
|
2168
|
+
const tsxPath = path7.join(workDir, `${componentName}.tsx`);
|
|
2117
2169
|
writeFileSync2(tsxPath, files.tsx);
|
|
2118
2170
|
for (const [name, content] of Object.entries(extraFiles ?? {})) {
|
|
2119
|
-
writeFileSync2(
|
|
2171
|
+
writeFileSync2(path7.join(workDir, name), content);
|
|
2120
2172
|
}
|
|
2121
2173
|
const tsc = runTscStrict([tsxPath]);
|
|
2122
2174
|
for (const d of tsc.diagnostics) {
|
|
@@ -2170,7 +2222,7 @@ function definedVarsFrom(tokensCss) {
|
|
|
2170
2222
|
}
|
|
2171
2223
|
var SKIPPED_CHECKS;
|
|
2172
2224
|
var init_loop = __esm({
|
|
2173
|
-
"
|
|
2225
|
+
"packages/verify/src/loop.ts"() {
|
|
2174
2226
|
"use strict";
|
|
2175
2227
|
init_runtime();
|
|
2176
2228
|
init_gates();
|
|
@@ -2180,7 +2232,7 @@ var init_loop = __esm({
|
|
|
2180
2232
|
}
|
|
2181
2233
|
});
|
|
2182
2234
|
|
|
2183
|
-
//
|
|
2235
|
+
// packages/verify/src/report.ts
|
|
2184
2236
|
import { z as z5 } from "zod";
|
|
2185
2237
|
function isTokenEnforcedProperty(property) {
|
|
2186
2238
|
return enforcedMatchers.some((m) => m.test(property));
|
|
@@ -2220,7 +2272,7 @@ function buildReport(input) {
|
|
|
2220
2272
|
}
|
|
2221
2273
|
var ReportSchema, enforcedMatchers;
|
|
2222
2274
|
var init_report = __esm({
|
|
2223
|
-
"
|
|
2275
|
+
"packages/verify/src/report.ts"() {
|
|
2224
2276
|
"use strict";
|
|
2225
2277
|
init_token_lint();
|
|
2226
2278
|
ReportSchema = z5.object({
|
|
@@ -2282,7 +2334,7 @@ var init_report = __esm({
|
|
|
2282
2334
|
}
|
|
2283
2335
|
});
|
|
2284
2336
|
|
|
2285
|
-
//
|
|
2337
|
+
// packages/verify/src/mount-limits.ts
|
|
2286
2338
|
function lcdTextEnabled() {
|
|
2287
2339
|
return process.env["TENDRIL_ENABLE_LCD_TEXT"] === "1";
|
|
2288
2340
|
}
|
|
@@ -2300,14 +2352,14 @@ function raceMountDeadline(work, deadlineMs, onDeadline) {
|
|
|
2300
2352
|
}
|
|
2301
2353
|
var MEMORY_CAP_ARG, ENGINE_MOUNT_DEADLINE_MS;
|
|
2302
2354
|
var init_mount_limits = __esm({
|
|
2303
|
-
"
|
|
2355
|
+
"packages/verify/src/mount-limits.ts"() {
|
|
2304
2356
|
"use strict";
|
|
2305
2357
|
MEMORY_CAP_ARG = "--js-flags=--max-old-space-size=512";
|
|
2306
2358
|
ENGINE_MOUNT_DEADLINE_MS = 3e4;
|
|
2307
2359
|
}
|
|
2308
2360
|
});
|
|
2309
2361
|
|
|
2310
|
-
//
|
|
2362
|
+
// packages/verify/src/image-diff.ts
|
|
2311
2363
|
import { PNG } from "pngjs";
|
|
2312
2364
|
import pixelmatch from "pixelmatch";
|
|
2313
2365
|
function channelDistance(a, b) {
|
|
@@ -2749,7 +2801,7 @@ function cropPng(png, x, y, width, height) {
|
|
|
2749
2801
|
}
|
|
2750
2802
|
var INK_DELTA, ABSENT_RADIUS, ABSENT_MIN_PX;
|
|
2751
2803
|
var init_image_diff = __esm({
|
|
2752
|
-
"
|
|
2804
|
+
"packages/verify/src/image-diff.ts"() {
|
|
2753
2805
|
"use strict";
|
|
2754
2806
|
INK_DELTA = 30;
|
|
2755
2807
|
ABSENT_RADIUS = 3;
|
|
@@ -2757,9 +2809,9 @@ var init_image_diff = __esm({
|
|
|
2757
2809
|
}
|
|
2758
2810
|
});
|
|
2759
2811
|
|
|
2760
|
-
//
|
|
2761
|
-
import
|
|
2762
|
-
import { fileURLToPath as
|
|
2812
|
+
// packages/verify/src/visual-facts.ts
|
|
2813
|
+
import path8 from "node:path";
|
|
2814
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2763
2815
|
import { build } from "esbuild";
|
|
2764
2816
|
import { chromium } from "playwright-core";
|
|
2765
2817
|
function resolveAxisValue(declared, props, axis, value) {
|
|
@@ -3298,12 +3350,12 @@ ${input.files.css}
|
|
|
3298
3350
|
}
|
|
3299
3351
|
var RESOLVE_DIR, TOLERANCE_PX, WIDTH_SLACK, IMAGE_SIMILARITY_FLOOR, INK_RECALL_FLOOR;
|
|
3300
3352
|
var init_visual_facts = __esm({
|
|
3301
|
-
"
|
|
3353
|
+
"packages/verify/src/visual-facts.ts"() {
|
|
3302
3354
|
"use strict";
|
|
3303
3355
|
init_browser();
|
|
3304
3356
|
init_mount_limits();
|
|
3305
3357
|
init_image_diff();
|
|
3306
|
-
RESOLVE_DIR =
|
|
3358
|
+
RESOLVE_DIR = path8.resolve(path8.dirname(fileURLToPath3(import.meta.url)), "..");
|
|
3307
3359
|
TOLERANCE_PX = 2;
|
|
3308
3360
|
WIDTH_SLACK = 0.25;
|
|
3309
3361
|
IMAGE_SIMILARITY_FLOOR = 0.8;
|
|
@@ -3311,27 +3363,27 @@ var init_visual_facts = __esm({
|
|
|
3311
3363
|
}
|
|
3312
3364
|
});
|
|
3313
3365
|
|
|
3314
|
-
//
|
|
3315
|
-
import
|
|
3316
|
-
import { fileURLToPath as
|
|
3366
|
+
// packages/verify/src/paths.ts
|
|
3367
|
+
import path9 from "node:path";
|
|
3368
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3317
3369
|
var VERIFY_PKG_DIR, REPO_ROOT;
|
|
3318
3370
|
var init_paths = __esm({
|
|
3319
|
-
"
|
|
3371
|
+
"packages/verify/src/paths.ts"() {
|
|
3320
3372
|
"use strict";
|
|
3321
|
-
VERIFY_PKG_DIR =
|
|
3322
|
-
REPO_ROOT =
|
|
3373
|
+
VERIFY_PKG_DIR = path9.resolve(path9.dirname(fileURLToPath4(import.meta.url)), "..");
|
|
3374
|
+
REPO_ROOT = path9.resolve(VERIFY_PKG_DIR, "..", "..");
|
|
3323
3375
|
}
|
|
3324
3376
|
});
|
|
3325
3377
|
|
|
3326
|
-
//
|
|
3378
|
+
// packages/verify/src/font-resolve.ts
|
|
3327
3379
|
import { createHash } from "node:crypto";
|
|
3328
|
-
import { existsSync as
|
|
3380
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3329
3381
|
import os2 from "node:os";
|
|
3330
|
-
import
|
|
3382
|
+
import path10 from "node:path";
|
|
3331
3383
|
function fontCacheDir() {
|
|
3332
3384
|
const env = process.env["TENDRIL_FONT_CACHE"];
|
|
3333
|
-
if (env !== void 0 && env !== "") return
|
|
3334
|
-
return
|
|
3385
|
+
if (env !== void 0 && env !== "") return path10.resolve(env);
|
|
3386
|
+
return path10.join(os2.homedir(), ".tendril", "fonts");
|
|
3335
3387
|
}
|
|
3336
3388
|
function normalizeFontLicense(value) {
|
|
3337
3389
|
return typeof value === "string" && FONT_LICENSES.includes(value) ? value : "unknown";
|
|
@@ -3396,25 +3448,25 @@ async function resolveFonts(family, weights, cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3396
3448
|
}
|
|
3397
3449
|
const bytes = new Uint8Array(await fileRes.arrayBuffer());
|
|
3398
3450
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
3399
|
-
const file =
|
|
3451
|
+
const file = path10.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}.woff2`);
|
|
3400
3452
|
writeFileSync3(file, bytes);
|
|
3401
3453
|
resolved.push({ family, weight, source: url, sha256, file, license: meta.license, ...meta.family !== null ? { servedFamily: meta.family } : {} });
|
|
3402
3454
|
} catch (err) {
|
|
3403
3455
|
failures.push({ family, weight, reason: `download failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
3404
3456
|
}
|
|
3405
3457
|
}
|
|
3406
|
-
const mPath =
|
|
3407
|
-
const prior =
|
|
3408
|
-
const portable2 = resolved.map((m) => ({ ...m, file:
|
|
3458
|
+
const mPath = path10.join(cacheDir, "manifest.json");
|
|
3459
|
+
const prior = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
|
|
3460
|
+
const portable2 = resolved.map((m) => ({ ...m, file: path10.basename(m.file) }));
|
|
3409
3461
|
const merged = [...prior.filter((p) => !resolved.some((m) => m.family === p.family && m.weight === p.weight)), ...portable2];
|
|
3410
3462
|
if (resolved.length > 0) writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
|
|
3411
3463
|
`);
|
|
3412
3464
|
return { resolved, failures };
|
|
3413
3465
|
}
|
|
3414
3466
|
function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE) {
|
|
3415
|
-
const src =
|
|
3416
|
-
if (!
|
|
3417
|
-
const ext =
|
|
3467
|
+
const src = path10.resolve(filePath);
|
|
3468
|
+
if (!existsSync6(src)) throw new Error(`font file not found: ${src}`);
|
|
3469
|
+
const ext = path10.extname(src).toLowerCase();
|
|
3418
3470
|
if (![".woff2", ".woff", ".ttf", ".otf"].includes(ext)) {
|
|
3419
3471
|
throw new Error(`unsupported font file "${ext}" \u2014 use .woff2 (preferred), .woff, .ttf or .otf`);
|
|
3420
3472
|
}
|
|
@@ -3422,20 +3474,20 @@ function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3422
3474
|
if (bytes.length === 0) throw new Error(`font file is empty: ${src}`);
|
|
3423
3475
|
mkdirSync2(cacheDir, { recursive: true });
|
|
3424
3476
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
3425
|
-
const file =
|
|
3477
|
+
const file = path10.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}${ext}`);
|
|
3426
3478
|
writeFileSync3(file, bytes);
|
|
3427
|
-
const face = { family, weight, source: `local:${
|
|
3428
|
-
const mPath =
|
|
3429
|
-
const prior =
|
|
3430
|
-
const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file:
|
|
3479
|
+
const face = { family, weight, source: `local:${path10.basename(src)}`, sha256, file, license: "unknown" };
|
|
3480
|
+
const mPath = path10.join(cacheDir, "manifest.json");
|
|
3481
|
+
const prior = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
|
|
3482
|
+
const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path10.basename(file) }];
|
|
3431
3483
|
writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
|
|
3432
3484
|
`);
|
|
3433
3485
|
return face;
|
|
3434
3486
|
}
|
|
3435
3487
|
function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
|
|
3436
3488
|
const lock = JSON.parse(readFileSync3(lockPath, "utf8"));
|
|
3437
|
-
const mPath =
|
|
3438
|
-
const manifest =
|
|
3489
|
+
const mPath = path10.join(cacheDir, "manifest.json");
|
|
3490
|
+
const manifest = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
|
|
3439
3491
|
return lock.map((l) => {
|
|
3440
3492
|
const m = manifest.find((x) => x.family === l.family && x.weight === l.weight);
|
|
3441
3493
|
if (m === void 0) return { family: l.family, weight: l.weight, status: "missing" };
|
|
@@ -3443,8 +3495,8 @@ function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3443
3495
|
});
|
|
3444
3496
|
}
|
|
3445
3497
|
function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
3446
|
-
const mPath =
|
|
3447
|
-
if (!
|
|
3498
|
+
const mPath = path10.join(cacheDir, "manifest.json");
|
|
3499
|
+
if (!existsSync6(mPath)) return [];
|
|
3448
3500
|
let entries;
|
|
3449
3501
|
try {
|
|
3450
3502
|
entries = JSON.parse(readFileSync3(mPath, "utf8"));
|
|
@@ -3461,8 +3513,8 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3461
3513
|
return [...byFamily.entries()].map(([family, w]) => ({ family, weights: [...w].sort((a, b) => a - b) }));
|
|
3462
3514
|
}
|
|
3463
3515
|
function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
|
|
3464
|
-
const mPath =
|
|
3465
|
-
const manifest =
|
|
3516
|
+
const mPath = path10.join(cacheDir, "manifest.json");
|
|
3517
|
+
const manifest = existsSync6(mPath) ? JSON.parse(readFileSync3(mPath, "utf8")) : [];
|
|
3466
3518
|
const wanted = new Set(families.map((f) => f.toLowerCase()));
|
|
3467
3519
|
return manifest.filter((f) => wanted.has(f.family.toLowerCase())).map((f) => ({
|
|
3468
3520
|
family: f.family,
|
|
@@ -3475,8 +3527,8 @@ function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3475
3527
|
}));
|
|
3476
3528
|
}
|
|
3477
3529
|
function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
|
|
3478
|
-
const mPath =
|
|
3479
|
-
if (!
|
|
3530
|
+
const mPath = path10.join(cacheDir, "manifest.json");
|
|
3531
|
+
if (!existsSync6(mPath)) return [];
|
|
3480
3532
|
let entries;
|
|
3481
3533
|
try {
|
|
3482
3534
|
entries = JSON.parse(readFileSync3(mPath, "utf8"));
|
|
@@ -3488,8 +3540,8 @@ function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3488
3540
|
).map((e) => ({ family: e.family, weight: e.weight, sha256: e.sha256 }));
|
|
3489
3541
|
}
|
|
3490
3542
|
function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
3491
|
-
const mPath =
|
|
3492
|
-
if (!
|
|
3543
|
+
const mPath = path10.join(cacheDir, "manifest.json");
|
|
3544
|
+
if (!existsSync6(mPath)) return [];
|
|
3493
3545
|
let entries;
|
|
3494
3546
|
try {
|
|
3495
3547
|
entries = JSON.parse(readFileSync3(mPath, "utf8"));
|
|
@@ -3499,8 +3551,8 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3499
3551
|
const byFamily = /* @__PURE__ */ new Map();
|
|
3500
3552
|
for (const e of entries) {
|
|
3501
3553
|
if (typeof e.family !== "string" || typeof e.weight !== "number" || typeof e.file !== "string") continue;
|
|
3502
|
-
const file =
|
|
3503
|
-
if (!
|
|
3554
|
+
const file = path10.isAbsolute(e.file) && existsSync6(e.file) ? e.file : path10.resolve(cacheDir, path10.basename(e.file));
|
|
3555
|
+
if (!existsSync6(file)) continue;
|
|
3504
3556
|
if (createHash("sha256").update(readFileSync3(file)).digest("hex") !== e.sha256) continue;
|
|
3505
3557
|
const set = byFamily.get(e.family) ?? /* @__PURE__ */ new Set();
|
|
3506
3558
|
set.add(e.weight);
|
|
@@ -3510,7 +3562,7 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
3510
3562
|
}
|
|
3511
3563
|
var DEFAULT_FONT_CACHE, UA, FONT_LICENSES, GOOGLE_LICENSE_IDS, GOOGLE_FONT_FILE_PREFIX;
|
|
3512
3564
|
var init_font_resolve = __esm({
|
|
3513
|
-
"
|
|
3565
|
+
"packages/verify/src/font-resolve.ts"() {
|
|
3514
3566
|
"use strict";
|
|
3515
3567
|
DEFAULT_FONT_CACHE = fontCacheDir();
|
|
3516
3568
|
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36";
|
|
@@ -3525,65 +3577,76 @@ var init_font_resolve = __esm({
|
|
|
3525
3577
|
}
|
|
3526
3578
|
});
|
|
3527
3579
|
|
|
3528
|
-
//
|
|
3580
|
+
// packages/verify/src/font-faces.ts
|
|
3529
3581
|
import { createHash as createHash2 } from "node:crypto";
|
|
3530
|
-
import { existsSync as
|
|
3531
|
-
import
|
|
3532
|
-
function
|
|
3533
|
-
if (!
|
|
3582
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
|
|
3583
|
+
import path11 from "node:path";
|
|
3584
|
+
function injectedGroups(manifestPath2) {
|
|
3585
|
+
if (!existsSync7(manifestPath2)) return { groups: [], shared: false };
|
|
3534
3586
|
const claimed = JSON.parse(readFileSync4(manifestPath2, "utf8"));
|
|
3535
|
-
const
|
|
3536
|
-
if (path10.isAbsolute(f) && existsSync6(f)) return f;
|
|
3537
|
-
return path10.resolve(path10.dirname(manifestPath2), path10.basename(f));
|
|
3538
|
-
};
|
|
3539
|
-
const manifest = claimed.filter((f) => {
|
|
3540
|
-
const file = resolveEntry(f.file);
|
|
3541
|
-
if (!existsSync6(file)) return false;
|
|
3542
|
-
return createHash2("sha256").update(readFileSync4(file)).digest("hex") === f.sha256;
|
|
3543
|
-
});
|
|
3544
|
-
const resolveFile = (f) => {
|
|
3545
|
-
if (path10.isAbsolute(f) && existsSync6(f)) return f;
|
|
3546
|
-
return path10.resolve(path10.dirname(manifestPath2), path10.basename(f));
|
|
3547
|
-
};
|
|
3587
|
+
const resolveFile = (f) => path11.isAbsolute(f) && existsSync7(f) ? f : path11.resolve(path11.dirname(manifestPath2), path11.basename(f));
|
|
3548
3588
|
const byFile = /* @__PURE__ */ new Map();
|
|
3549
|
-
for (const f of
|
|
3589
|
+
for (const f of claimed) {
|
|
3590
|
+
const file = resolveFile(f.file);
|
|
3591
|
+
if (!existsSync7(file)) continue;
|
|
3592
|
+
if (createHash2("sha256").update(readFileSync4(file)).digest("hex") !== f.sha256) continue;
|
|
3550
3593
|
const k = `${f.family}:${f.file}`;
|
|
3551
|
-
const e = byFile.get(k) ?? { family: f.family, weights: [], file
|
|
3594
|
+
const e = byFile.get(k) ?? { family: f.family, weights: [], file };
|
|
3552
3595
|
e.weights.push(f.weight);
|
|
3553
3596
|
byFile.set(k, e);
|
|
3554
3597
|
}
|
|
3555
|
-
const
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3598
|
+
const groups = [...byFile.values()];
|
|
3599
|
+
return { groups, shared: new Set(groups.map((e) => e.file)).size < groups.length };
|
|
3600
|
+
}
|
|
3601
|
+
function fontFaceCss(manifestPath2 = path11.join(fontCacheDir(), "manifest.json")) {
|
|
3602
|
+
const { groups, shared } = injectedGroups(manifestPath2);
|
|
3603
|
+
return groups.map((e) => {
|
|
3604
|
+
const weight = shared || e.weights.length > 1 ? `${SPAN[0]} ${SPAN[1]}` : String(e.weights[0]);
|
|
3559
3605
|
return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync4(e.file).toString("base64")}) format('woff2'); }`;
|
|
3560
3606
|
}).join("\n");
|
|
3561
3607
|
}
|
|
3608
|
+
function injectedFamilyWeights(manifestPath2 = path11.join(fontCacheDir(), "manifest.json")) {
|
|
3609
|
+
const { groups, shared } = injectedGroups(manifestPath2);
|
|
3610
|
+
const out = /* @__PURE__ */ new Map();
|
|
3611
|
+
for (const g of groups) {
|
|
3612
|
+
const key = g.family.toLowerCase();
|
|
3613
|
+
const e = out.get(key) ?? { weights: /* @__PURE__ */ new Set(), span: false };
|
|
3614
|
+
for (const w of g.weights) e.weights.add(w);
|
|
3615
|
+
if (shared || g.weights.length > 1) e.span = true;
|
|
3616
|
+
out.set(key, e);
|
|
3617
|
+
}
|
|
3618
|
+
return out;
|
|
3619
|
+
}
|
|
3620
|
+
function servesWeight(coverage, weight) {
|
|
3621
|
+
return coverage.weights.has(weight) || coverage.span && weight >= SPAN[0] && weight <= SPAN[1];
|
|
3622
|
+
}
|
|
3623
|
+
var SPAN;
|
|
3562
3624
|
var init_font_faces = __esm({
|
|
3563
|
-
"
|
|
3625
|
+
"packages/verify/src/font-faces.ts"() {
|
|
3564
3626
|
"use strict";
|
|
3565
3627
|
init_font_resolve();
|
|
3628
|
+
SPAN = [100, 700];
|
|
3566
3629
|
}
|
|
3567
3630
|
});
|
|
3568
3631
|
|
|
3569
|
-
//
|
|
3570
|
-
import { readFileSync as readFileSync5, readdirSync as readdirSync2, existsSync as
|
|
3571
|
-
import
|
|
3632
|
+
// packages/verify/src/admission.ts
|
|
3633
|
+
import { readFileSync as readFileSync5, readdirSync as readdirSync2, existsSync as existsSync8, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3634
|
+
import path12 from "node:path";
|
|
3572
3635
|
import { build as build2 } from "esbuild";
|
|
3573
3636
|
import postcss from "postcss";
|
|
3574
3637
|
import tailwindcss from "tailwindcss";
|
|
3575
3638
|
import { chromium as chromium2 } from "playwright-core";
|
|
3576
3639
|
function fontWeightsByFamily() {
|
|
3577
|
-
const mPath =
|
|
3640
|
+
const mPath = path12.join(fontCacheDir(), "manifest.json");
|
|
3578
3641
|
const out = /* @__PURE__ */ new Map();
|
|
3579
|
-
if (!
|
|
3642
|
+
if (!existsSync8(mPath)) return out;
|
|
3580
3643
|
for (const f of JSON.parse(readFileSync5(mPath, "utf8")))
|
|
3581
3644
|
out.set(f.family, [...out.get(f.family) ?? [], f.weight]);
|
|
3582
3645
|
return out;
|
|
3583
3646
|
}
|
|
3584
3647
|
var FONT_WEIGHTS;
|
|
3585
3648
|
var init_admission = __esm({
|
|
3586
|
-
"
|
|
3649
|
+
"packages/verify/src/admission.ts"() {
|
|
3587
3650
|
"use strict";
|
|
3588
3651
|
init_paths();
|
|
3589
3652
|
init_src();
|
|
@@ -3596,11 +3659,11 @@ var init_admission = __esm({
|
|
|
3596
3659
|
}
|
|
3597
3660
|
});
|
|
3598
3661
|
|
|
3599
|
-
//
|
|
3600
|
-
import
|
|
3662
|
+
// packages/verify/src/tasks.ts
|
|
3663
|
+
import path13 from "node:path";
|
|
3601
3664
|
var CALENDAR_CONFIGS, CALENDAR_API, CALENDAR_BEHAVIORS, BUTTON_CONFIGS, BUTTON_API, COMBO_FIX, COMBO_CONFIGS, COMBO_API, MODAL_CONFIGS, MODAL_API, BUTTON_BEHAVIORS, COMBO_BEHAVIORS, MODAL_BEHAVIORS, TASKS;
|
|
3602
3665
|
var init_tasks = __esm({
|
|
3603
|
-
"
|
|
3666
|
+
"packages/verify/src/tasks.ts"() {
|
|
3604
3667
|
"use strict";
|
|
3605
3668
|
init_paths();
|
|
3606
3669
|
CALENDAR_CONFIGS = [
|
|
@@ -3757,7 +3820,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
|
|
|
3757
3820
|
];
|
|
3758
3821
|
TASKS = {
|
|
3759
3822
|
calendar: {
|
|
3760
|
-
set:
|
|
3823
|
+
set: path13.join(REPO_ROOT, "examples/recordings/shadcn-poc-calendar"),
|
|
3761
3824
|
entry: "Calendar.tsx",
|
|
3762
3825
|
configs: CALENDAR_CONFIGS,
|
|
3763
3826
|
systemApi: CALENDAR_API,
|
|
@@ -3765,7 +3828,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
|
|
|
3765
3828
|
prelude: { controls: ['[data-tendril-part="day"]'], textInputs: [] }
|
|
3766
3829
|
},
|
|
3767
3830
|
"shadcn-button": {
|
|
3768
|
-
set:
|
|
3831
|
+
set: path13.join(REPO_ROOT, "examples/recordings/shadcn-poc-button"),
|
|
3769
3832
|
entry: "Button.tsx",
|
|
3770
3833
|
configs: BUTTON_CONFIGS,
|
|
3771
3834
|
systemApi: BUTTON_API,
|
|
@@ -3773,7 +3836,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
|
|
|
3773
3836
|
prelude: { controls: ["> *"], textInputs: [] }
|
|
3774
3837
|
},
|
|
3775
3838
|
combobox: {
|
|
3776
|
-
set:
|
|
3839
|
+
set: path13.join(REPO_ROOT, "examples/recordings/carbon-poc-combobox"),
|
|
3777
3840
|
entry: "ComboBox.tsx",
|
|
3778
3841
|
configs: COMBO_CONFIGS,
|
|
3779
3842
|
systemApi: COMBO_API,
|
|
@@ -3781,7 +3844,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
|
|
|
3781
3844
|
prelude: { controls: ['[role="option"]'], textInputs: ["input"], popover: { selector: '[role="listbox"]', trigger: "input" } }
|
|
3782
3845
|
},
|
|
3783
3846
|
modal: {
|
|
3784
|
-
set:
|
|
3847
|
+
set: path13.join(REPO_ROOT, "examples/recordings/carbon-poc-modal"),
|
|
3785
3848
|
entry: "Modal.tsx",
|
|
3786
3849
|
configs: MODAL_CONFIGS,
|
|
3787
3850
|
systemApi: MODAL_API,
|
|
@@ -3792,15 +3855,15 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
|
|
|
3792
3855
|
}
|
|
3793
3856
|
});
|
|
3794
3857
|
|
|
3795
|
-
//
|
|
3858
|
+
// packages/verify/src/behavior.ts
|
|
3796
3859
|
var behavior_exports = {};
|
|
3797
3860
|
__export(behavior_exports, {
|
|
3798
3861
|
checkBehaviors: () => checkBehaviors,
|
|
3799
3862
|
compileMount: () => compileMount,
|
|
3800
3863
|
recordingIsDark: () => recordingIsDark
|
|
3801
3864
|
});
|
|
3802
|
-
import { existsSync as
|
|
3803
|
-
import
|
|
3865
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
3866
|
+
import path14 from "node:path";
|
|
3804
3867
|
import { build as build3 } from "esbuild";
|
|
3805
3868
|
import { chromium as chromium3 } from "playwright-core";
|
|
3806
3869
|
import { PNG as PNG2 } from "pngjs";
|
|
@@ -3809,12 +3872,12 @@ function getFontFaces() {
|
|
|
3809
3872
|
return _fontFaces;
|
|
3810
3873
|
}
|
|
3811
3874
|
async function compileMount(task, bundleDir) {
|
|
3812
|
-
const entryTsx =
|
|
3813
|
-
if (!
|
|
3875
|
+
const entryTsx = path14.join(bundleDir, task.entry);
|
|
3876
|
+
if (!existsSync9(entryTsx)) return { error: `${task.entry} missing` };
|
|
3814
3877
|
const mountSrc = `
|
|
3815
3878
|
import { createElement } from "react";
|
|
3816
3879
|
import { createRoot } from "react-dom/client";
|
|
3817
|
-
import * as B from ${JSON.stringify(
|
|
3880
|
+
import * as B from ${JSON.stringify(path14.resolve(entryTsx))};
|
|
3818
3881
|
const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; spyProps?: string[] } }).__cfg;
|
|
3819
3882
|
const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
|
|
3820
3883
|
// Callbacks cannot ride the JSON config: specs NAME spy props and the
|
|
@@ -4066,8 +4129,8 @@ async function runSteps(page, spec, renderPose) {
|
|
|
4066
4129
|
function recordingIsDark(task) {
|
|
4067
4130
|
const rep = task.configs[0]?.rep;
|
|
4068
4131
|
if (rep === void 0) return false;
|
|
4069
|
-
const f =
|
|
4070
|
-
if (!
|
|
4132
|
+
const f = path14.join(task.set, rep, "get_screenshot.json");
|
|
4133
|
+
if (!existsSync9(f)) return false;
|
|
4071
4134
|
try {
|
|
4072
4135
|
const env = JSON.parse(readFileSync6(f, "utf8")).content.find((c) => c.type === "image");
|
|
4073
4136
|
if (env?.data === void 0) return false;
|
|
@@ -4141,7 +4204,7 @@ async function checkBehaviors(task, bundleDir, opts = {}) {
|
|
|
4141
4204
|
const deadlineMs = timeoutMs + 1e4;
|
|
4142
4205
|
const js = await compileMount(task, bundleDir);
|
|
4143
4206
|
if (typeof js !== "string") return task.behaviors.map((b) => ({ id: b.id, pass: false, detail: js.error }));
|
|
4144
|
-
const css = ["tokens.css", "styles.css"].map((f) =>
|
|
4207
|
+
const css = ["tokens.css", "styles.css"].map((f) => path14.join(bundleDir, f)).filter((f) => existsSync9(f)).map((f) => readFileSync6(f, "utf8")).join("\n");
|
|
4145
4208
|
const server = await chromium3.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
|
|
4146
4209
|
const browser = await chromium3.connect(server.wsEndpoint());
|
|
4147
4210
|
const results = [];
|
|
@@ -4248,7 +4311,7 @@ body{margin:0;padding:20px}
|
|
|
4248
4311
|
}
|
|
4249
4312
|
var _fontFaces, ANIMATED_PROBE, COMMIT_GAP_CLOSED, POSE_GAP_FLOOR, PRELUDE_ROOT_PROPS;
|
|
4250
4313
|
var init_behavior = __esm({
|
|
4251
|
-
"
|
|
4314
|
+
"packages/verify/src/behavior.ts"() {
|
|
4252
4315
|
"use strict";
|
|
4253
4316
|
init_runtime();
|
|
4254
4317
|
init_browser();
|
|
@@ -4283,9 +4346,9 @@ var init_behavior = __esm({
|
|
|
4283
4346
|
}
|
|
4284
4347
|
});
|
|
4285
4348
|
|
|
4286
|
-
//
|
|
4287
|
-
import { readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as
|
|
4288
|
-
import
|
|
4349
|
+
// packages/verify/src/bundle-quality.ts
|
|
4350
|
+
import { readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
|
|
4351
|
+
import path15 from "node:path";
|
|
4289
4352
|
function definedVars(tokensCss) {
|
|
4290
4353
|
if (tokensCss === void 0) return void 0;
|
|
4291
4354
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -4294,7 +4357,7 @@ function definedVars(tokensCss) {
|
|
|
4294
4357
|
}
|
|
4295
4358
|
function recordedTokenMapEmpty(setDir, reps) {
|
|
4296
4359
|
const readMap = (file) => {
|
|
4297
|
-
if (!
|
|
4360
|
+
if (!existsSync10(file)) return void 0;
|
|
4298
4361
|
try {
|
|
4299
4362
|
const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync7(file, "utf8"))) || "{}");
|
|
4300
4363
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
@@ -4302,28 +4365,93 @@ function recordedTokenMapEmpty(setDir, reps) {
|
|
|
4302
4365
|
return {};
|
|
4303
4366
|
}
|
|
4304
4367
|
};
|
|
4305
|
-
const setLevel = readMap(
|
|
4368
|
+
const setLevel = readMap(path15.join(setDir, "get_variable_defs.json"));
|
|
4306
4369
|
if (setLevel !== void 0) return Object.keys(setLevel).length === 0;
|
|
4307
4370
|
let recorded = false;
|
|
4308
4371
|
for (const rep of reps) {
|
|
4309
|
-
const m = readMap(
|
|
4372
|
+
const m = readMap(path15.join(setDir, rep, "get_variable_defs.json"));
|
|
4310
4373
|
if (m === void 0) continue;
|
|
4311
4374
|
recorded = true;
|
|
4312
4375
|
if (Object.keys(m).length > 0) return false;
|
|
4313
4376
|
}
|
|
4314
4377
|
return recorded;
|
|
4315
4378
|
}
|
|
4316
|
-
|
|
4379
|
+
function scannable(css) {
|
|
4380
|
+
const blank = (m) => m.replace(/[^\n]/g, " ");
|
|
4381
|
+
return css.replace(/\/\*[^]*?\*\//g, blank).replace(/@font-face\s*{[^}]*}/g, blank);
|
|
4382
|
+
}
|
|
4383
|
+
function usedWeights(css) {
|
|
4384
|
+
const out = /* @__PURE__ */ new Set();
|
|
4385
|
+
for (const m of css.matchAll(/font-weight\s*:\s*([^;}]+)/g)) {
|
|
4386
|
+
const v = declValue(m[1]).toLowerCase();
|
|
4387
|
+
if (v === "bold") out.add(700);
|
|
4388
|
+
else if (v === "normal") out.add(400);
|
|
4389
|
+
else if (/^\d{1,4}$/.test(v)) out.add(Number(v));
|
|
4390
|
+
}
|
|
4391
|
+
return [...out].sort((a, b) => a - b);
|
|
4392
|
+
}
|
|
4393
|
+
function fontStacks(css) {
|
|
4394
|
+
const out = [];
|
|
4395
|
+
for (const m of css.matchAll(/font-family\s*:\s*([^;}]+)/g)) {
|
|
4396
|
+
const text = declValue(m[1]);
|
|
4397
|
+
const families = text.split(",").map((f) => f.trim().replace(/^['"]|['"]$/g, "")).filter((f) => f !== "");
|
|
4398
|
+
if (families.length > 0) out.push({ families, line: css.slice(0, m.index).split("\n").length, text });
|
|
4399
|
+
}
|
|
4400
|
+
return out;
|
|
4401
|
+
}
|
|
4402
|
+
function fontStackFindings(sheets, coverage) {
|
|
4403
|
+
if (coverage.size === 0) return [];
|
|
4404
|
+
const scanned = sheets.map((s) => ({ file: s.file, css: scannable(s.css) }));
|
|
4405
|
+
const weights = [...new Set(scanned.flatMap((s) => usedWeights(s.css)))].sort((a, b) => a - b);
|
|
4406
|
+
if (weights.length === 0) return [];
|
|
4407
|
+
const findings = [];
|
|
4408
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4409
|
+
for (const sheet of scanned) {
|
|
4410
|
+
for (const stack of fontStacks(sheet.css)) {
|
|
4411
|
+
if (seen.has(stack.text)) continue;
|
|
4412
|
+
seen.add(stack.text);
|
|
4413
|
+
const family = stack.families[0];
|
|
4414
|
+
const bound = coverage.get(family.toLowerCase());
|
|
4415
|
+
if (bound === void 0) continue;
|
|
4416
|
+
const unserved = weights.filter((w) => !servesWeight(bound, w));
|
|
4417
|
+
if (unserved.length === 0) continue;
|
|
4418
|
+
const rescue = stack.families.slice(1).find((f) => {
|
|
4419
|
+
const c = coverage.get(f.toLowerCase());
|
|
4420
|
+
return c !== void 0 && unserved.every((w) => servesWeight(c, w));
|
|
4421
|
+
});
|
|
4422
|
+
const has = bound.span ? "100\u2013700" : [...bound.weights].sort((a, b) => a - b).join(", ");
|
|
4423
|
+
const list = unserved.join(", ");
|
|
4424
|
+
const renders = !bound.span && bound.weights.size === 1 ? `renders in the ${[...bound.weights][0]} face` : "renders in whichever of those faces is nearest";
|
|
4425
|
+
findings.push({
|
|
4426
|
+
kind: "font-stack",
|
|
4427
|
+
file: sheet.file,
|
|
4428
|
+
line: stack.line,
|
|
4429
|
+
message: `font-family ${stack.text} binds to '${family}', which the font kit provides at ${has} \u2014 CSS matches the FAMILY first and picks a weight only inside it (a later family is never consulted for a missing weight, and the prelude's font-synthesis: none rules out faux-bold), so text at weight ${list} ${renders}, silently. ` + (rescue !== void 0 ? `'${rescue}' in this same stack provides ${list}: list it first.` : `No family in this stack provides ${list} \u2014 if the recording shows ${unserved.length === 1 ? "that weight" : "those weights"}, run \`tendril fonts resolve "${family}" --weights ${unserved.join(" ")}\` and re-score.`) + " ADVISORY: quality feedback only \u2014 it never moves a score, a tier, or the verdict."
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
return findings;
|
|
4434
|
+
}
|
|
4435
|
+
async function checkBundleQuality(bundleDir, entry, set, fontManifest) {
|
|
4317
4436
|
const findings = [];
|
|
4318
|
-
const entryPath =
|
|
4319
|
-
const cssPath =
|
|
4320
|
-
const tokensPath =
|
|
4321
|
-
const css =
|
|
4322
|
-
const tokensCss =
|
|
4323
|
-
|
|
4437
|
+
const entryPath = path15.join(bundleDir, entry);
|
|
4438
|
+
const cssPath = path15.join(bundleDir, "styles.css");
|
|
4439
|
+
const tokensPath = path15.join(bundleDir, "tokens.css");
|
|
4440
|
+
const css = existsSync10(cssPath) ? readFileSync7(cssPath, "utf8") : "";
|
|
4441
|
+
const tokensCss = existsSync10(tokensPath) ? readFileSync7(tokensPath, "utf8") : void 0;
|
|
4442
|
+
findings.push(
|
|
4443
|
+
...fontStackFindings(
|
|
4444
|
+
[
|
|
4445
|
+
{ file: "styles.css", css },
|
|
4446
|
+
...tokensCss === void 0 ? [] : [{ file: "tokens.css", css: tokensCss }]
|
|
4447
|
+
],
|
|
4448
|
+
injectedFamilyWeights(fontManifest)
|
|
4449
|
+
)
|
|
4450
|
+
);
|
|
4451
|
+
if (existsSync10(entryPath)) {
|
|
4324
4452
|
const workDir = newScratchDir("quality");
|
|
4325
4453
|
try {
|
|
4326
|
-
const tsxPath =
|
|
4454
|
+
const tsxPath = path15.join(workDir, entry);
|
|
4327
4455
|
writeFileSync5(tsxPath, readFileSync7(entryPath, "utf8"));
|
|
4328
4456
|
for (const d of runTscStrict([tsxPath]).diagnostics) {
|
|
4329
4457
|
findings.push({ kind: "tsc", file: entry, ...d.line === void 0 ? {} : { line: d.line }, message: `TS${d.code}: ${d.message}` });
|
|
@@ -4340,17 +4468,20 @@ async function checkBundleQuality(bundleDir, entry, set) {
|
|
|
4340
4468
|
}
|
|
4341
4469
|
return { findings, tokensAbsent: tokensCss === void 0 && !/var\(\s*--/.test(css) };
|
|
4342
4470
|
}
|
|
4471
|
+
var declValue;
|
|
4343
4472
|
var init_bundle_quality = __esm({
|
|
4344
|
-
"
|
|
4473
|
+
"packages/verify/src/bundle-quality.ts"() {
|
|
4345
4474
|
"use strict";
|
|
4346
4475
|
init_src();
|
|
4476
|
+
init_font_faces();
|
|
4347
4477
|
init_runtime();
|
|
4348
4478
|
init_tsc_check();
|
|
4349
4479
|
init_token_lint();
|
|
4480
|
+
declValue = (raw) => raw.trim().replace(/\s*!important$/i, "").trim();
|
|
4350
4481
|
}
|
|
4351
4482
|
});
|
|
4352
4483
|
|
|
4353
|
-
//
|
|
4484
|
+
// packages/verify/src/effect-geometry.ts
|
|
4354
4485
|
function fold(into, dx, dy, radius, spread) {
|
|
4355
4486
|
const reach = radius + spread;
|
|
4356
4487
|
into.top = Math.max(into.top, reach - dy);
|
|
@@ -4382,15 +4513,15 @@ function extentsExplainPad(near, far, pad, tolerance = 2) {
|
|
|
4382
4513
|
}
|
|
4383
4514
|
var NO_EXTENTS;
|
|
4384
4515
|
var init_effect_geometry = __esm({
|
|
4385
|
-
"
|
|
4516
|
+
"packages/verify/src/effect-geometry.ts"() {
|
|
4386
4517
|
"use strict";
|
|
4387
4518
|
NO_EXTENTS = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
4388
4519
|
}
|
|
4389
4520
|
});
|
|
4390
4521
|
|
|
4391
|
-
//
|
|
4392
|
-
import { existsSync as
|
|
4393
|
-
import
|
|
4522
|
+
// packages/verify/src/bundle-score.ts
|
|
4523
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4524
|
+
import path16 from "node:path";
|
|
4394
4525
|
import { build as build4 } from "esbuild";
|
|
4395
4526
|
import { chromium as chromium4 } from "playwright-core";
|
|
4396
4527
|
function getFontFaces2() {
|
|
@@ -4444,7 +4575,7 @@ function chooseSeat(cropAt, ref, padW, padH, backdrop, origin, extents) {
|
|
|
4444
4575
|
}
|
|
4445
4576
|
function metadataRoot(set, rep) {
|
|
4446
4577
|
try {
|
|
4447
|
-
const text = JSON.parse(readFileSync8(
|
|
4578
|
+
const text = JSON.parse(readFileSync8(path16.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
4448
4579
|
return parseMetadataStructure(text);
|
|
4449
4580
|
} catch {
|
|
4450
4581
|
return void 0;
|
|
@@ -4499,17 +4630,17 @@ function smallSemanticNodes(set, rep, maxArea = 1024) {
|
|
|
4499
4630
|
});
|
|
4500
4631
|
}
|
|
4501
4632
|
function repMeta(set, rep) {
|
|
4502
|
-
const text = JSON.parse(readFileSync8(
|
|
4633
|
+
const text = JSON.parse(readFileSync8(path16.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
4503
4634
|
const root = parseMetadataStructure(text);
|
|
4504
4635
|
return { w: Math.round(root.width ?? 100), h: Math.round(root.height ?? 40) };
|
|
4505
4636
|
}
|
|
4506
4637
|
function repRef(set, rep) {
|
|
4507
|
-
const env = JSON.parse(readFileSync8(
|
|
4638
|
+
const env = JSON.parse(readFileSync8(path16.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
|
|
4508
4639
|
return Uint8Array.from(Buffer.from(env?.data ?? "", "base64"));
|
|
4509
4640
|
}
|
|
4510
4641
|
function repEffectExtents(set, rep) {
|
|
4511
|
-
const file =
|
|
4512
|
-
if (!
|
|
4642
|
+
const file = path16.join(set, rep, "get_design_context.json");
|
|
4643
|
+
if (!existsSync11(file)) return void 0;
|
|
4513
4644
|
try {
|
|
4514
4645
|
const text = JSON.parse(readFileSync8(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
4515
4646
|
const extents = shadowExtents(text);
|
|
@@ -4522,13 +4653,13 @@ async function scoreBundleForTask(task, bundleDir, bar = BAR, opts = {}) {
|
|
|
4522
4653
|
if (opts.evidenceDir !== void 0) mkdirSync3(opts.evidenceDir, { recursive: true });
|
|
4523
4654
|
const CONFIGS2 = task.configs;
|
|
4524
4655
|
const timeoutMs = opts.mountDeadlineMs ?? ENGINE_MOUNT_DEADLINE_MS;
|
|
4525
|
-
const entryTsx =
|
|
4526
|
-
if (!
|
|
4527
|
-
const css = ["tokens.css", "styles.css"].map((f) =>
|
|
4656
|
+
const entryTsx = path16.join(bundleDir, task.entry);
|
|
4657
|
+
if (!existsSync11(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
|
|
4658
|
+
const css = ["tokens.css", "styles.css"].map((f) => path16.join(bundleDir, f)).filter((f) => existsSync11(f)).map((f) => readFileSync8(f, "utf8")).join("\n");
|
|
4528
4659
|
const mountSrc = `
|
|
4529
4660
|
import { createElement } from "react";
|
|
4530
4661
|
import { createRoot } from "react-dom/client";
|
|
4531
|
-
import * as B from ${JSON.stringify(
|
|
4662
|
+
import * as B from ${JSON.stringify(path16.resolve(entryTsx))};
|
|
4532
4663
|
const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
|
|
4533
4664
|
const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
|
|
4534
4665
|
const root = document.getElementById("root");
|
|
@@ -4623,12 +4754,12 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
|
|
|
4623
4754
|
return name === void 0 ? c : { ...c, name };
|
|
4624
4755
|
});
|
|
4625
4756
|
if (opts.evidenceDir !== void 0) {
|
|
4626
|
-
writeFileSync6(
|
|
4627
|
-
writeFileSync6(
|
|
4628
|
-
writeFileSync6(
|
|
4757
|
+
writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
|
|
4758
|
+
writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
|
|
4759
|
+
writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref));
|
|
4629
4760
|
for (const [i, c] of absent.slice(0, 3).entries()) {
|
|
4630
|
-
writeFileSync6(
|
|
4631
|
-
writeFileSync6(
|
|
4761
|
+
writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
|
|
4762
|
+
writeFileSync6(path16.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
|
|
4632
4763
|
}
|
|
4633
4764
|
}
|
|
4634
4765
|
return {
|
|
@@ -4688,7 +4819,7 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
|
|
|
4688
4819
|
}
|
|
4689
4820
|
var BAR, MARGIN, _fontFaces2, CONFIGS, SEAT_WINDOW, EFFECT_BLEED_MAX;
|
|
4690
4821
|
var init_bundle_score = __esm({
|
|
4691
|
-
"
|
|
4822
|
+
"packages/verify/src/bundle-score.ts"() {
|
|
4692
4823
|
"use strict";
|
|
4693
4824
|
init_runtime();
|
|
4694
4825
|
init_browser();
|
|
@@ -4706,11 +4837,11 @@ var init_bundle_score = __esm({
|
|
|
4706
4837
|
}
|
|
4707
4838
|
});
|
|
4708
4839
|
|
|
4709
|
-
//
|
|
4840
|
+
// packages/verify/src/containment.ts
|
|
4710
4841
|
import { build as build5 } from "esbuild";
|
|
4711
4842
|
import { chromium as chromium5 } from "playwright-core";
|
|
4712
4843
|
var init_containment = __esm({
|
|
4713
|
-
"
|
|
4844
|
+
"packages/verify/src/containment.ts"() {
|
|
4714
4845
|
"use strict";
|
|
4715
4846
|
init_runtime();
|
|
4716
4847
|
init_browser();
|
|
@@ -4720,14 +4851,14 @@ var init_containment = __esm({
|
|
|
4720
4851
|
}
|
|
4721
4852
|
});
|
|
4722
4853
|
|
|
4723
|
-
//
|
|
4854
|
+
// packages/verify/src/prelude.ts
|
|
4724
4855
|
var PRELUDE_CONTRACT;
|
|
4725
4856
|
var init_prelude = __esm({
|
|
4726
|
-
"
|
|
4857
|
+
"packages/verify/src/prelude.ts"() {
|
|
4727
4858
|
"use strict";
|
|
4728
4859
|
PRELUDE_CONTRACT = `BUNDLE PRELUDE (machine-verified on computed styles \u2014 the harness page provides NONE of this; your CSS must):
|
|
4729
4860
|
- Containment reset on the component subtree: margin 0, box-sizing border-box, line-height/letter-spacing/font-family inherited from the root (host globals must not leak in).
|
|
4730
|
-
- Component root: -webkit-font-smoothing: antialiased and -moz-osx-font-smoothing: grayscale (recorded rasterization); font-synthesis: none (
|
|
4861
|
+
- Component root: -webkit-font-smoothing: antialiased and -moz-osx-font-smoothing: grayscale (recorded rasterization); font-synthesis: none (no faux bold or italic \u2014 text renders in the face your stack actually binds, so a weight the kit cannot serve reaches the score instead of being faked; it does NOT fall through to the next family in the stack); color-scheme MATCHING YOUR RECORDING (light for a light capture, dark for a dark one) and direction: ltr \u2014 pin them, so the render cannot follow the viewer OS preference and drift from the capture it is graded against; isolation: isolate (own stacking context; overlay z-indexes never fight the host).
|
|
4731
4862
|
- Interactive controls (buttons, options): touch-action: manipulation and user-select: none. Text inputs stay selectable (never user-select: none on them).
|
|
4732
4863
|
- Scrollable popovers/menus: overscroll-behavior: contain.
|
|
4733
4864
|
- Focus indicators bind to :focus-visible, never bare :focus. If the recording contains a focus pose, style the indicator from that recorded truth. If NO focus pose is recorded, do NOT invent ring colors/widths/offsets \u2014 an invented ring is unrecorded pixels; keep the browser's default indicator for keyboard focus and state the gap in your report. Text inputs match :focus-visible even on mouse click BY SPEC; the ONE permitted refinement is suppressing the indicator on a POSITIVELY OBSERVED pointer press, fail-safe toward showing it (programmatic focus, restored focus, and assistive tech all count as keyboard and keep the ring \u2014 WCAG 2.4.7 binds keyboard operation). Never suppress more broadly than an observed press, and never restyle what you kept.
|
|
@@ -4735,9 +4866,9 @@ var init_prelude = __esm({
|
|
|
4735
4866
|
}
|
|
4736
4867
|
});
|
|
4737
4868
|
|
|
4738
|
-
//
|
|
4739
|
-
import { existsSync as
|
|
4740
|
-
import
|
|
4869
|
+
// packages/verify/src/parity.ts
|
|
4870
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
4871
|
+
import path17 from "node:path";
|
|
4741
4872
|
import { chromium as chromium6 } from "playwright-core";
|
|
4742
4873
|
function getFontFaces3() {
|
|
4743
4874
|
_fontFaces3 ??= fontFaceCss();
|
|
@@ -4766,7 +4897,7 @@ async function checkHoverParity(task, bundleDir, opts = {}) {
|
|
|
4766
4897
|
const deadlineMs = timeoutMs + 1e4;
|
|
4767
4898
|
const js = await compileMount(task, bundleDir);
|
|
4768
4899
|
if (typeof js !== "string") return configs.map((c) => ({ id: `parity:${c.rep}`, pass: false, detail: js.error }));
|
|
4769
|
-
const css = ["tokens.css", "styles.css"].map((f) =>
|
|
4900
|
+
const css = ["tokens.css", "styles.css"].map((f) => path17.join(bundleDir, f)).filter((f) => existsSync12(f)).map((f) => readFileSync9(f, "utf8")).join("\n");
|
|
4770
4901
|
const server = await chromium6.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
|
|
4771
4902
|
const browser = await chromium6.connect(server.wsEndpoint());
|
|
4772
4903
|
const results = [];
|
|
@@ -4835,7 +4966,7 @@ body{margin:0;padding:20px}
|
|
|
4835
4966
|
}
|
|
4836
4967
|
var _fontFaces3;
|
|
4837
4968
|
var init_parity = __esm({
|
|
4838
|
-
"
|
|
4969
|
+
"packages/verify/src/parity.ts"() {
|
|
4839
4970
|
"use strict";
|
|
4840
4971
|
init_browser();
|
|
4841
4972
|
init_behavior();
|
|
@@ -4844,10 +4975,10 @@ var init_parity = __esm({
|
|
|
4844
4975
|
}
|
|
4845
4976
|
});
|
|
4846
4977
|
|
|
4847
|
-
//
|
|
4978
|
+
// packages/verify/src/composition.ts
|
|
4848
4979
|
import { createRequire as createRequire2 } from "node:module";
|
|
4849
|
-
import { existsSync as
|
|
4850
|
-
import
|
|
4980
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
|
|
4981
|
+
import path18 from "node:path";
|
|
4851
4982
|
import { build as build6 } from "esbuild";
|
|
4852
4983
|
import { chromium as chromium7 } from "playwright-core";
|
|
4853
4984
|
function getFontFaces4() {
|
|
@@ -4855,9 +4986,9 @@ function getFontFaces4() {
|
|
|
4855
4986
|
return _fontFaces4;
|
|
4856
4987
|
}
|
|
4857
4988
|
async function compileInstrumentedMount(task, bundleDir) {
|
|
4858
|
-
const entryTsx =
|
|
4859
|
-
if (!
|
|
4860
|
-
const requireFromVerify = createRequire2(
|
|
4989
|
+
const entryTsx = path18.join(bundleDir, task.entry);
|
|
4990
|
+
if (!existsSync13(entryTsx)) return { error: `${task.entry} missing` };
|
|
4991
|
+
const requireFromVerify = createRequire2(path18.join(VERIFY_PKG_DIR, "package.json"));
|
|
4861
4992
|
let realJsxPath;
|
|
4862
4993
|
try {
|
|
4863
4994
|
realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
|
|
@@ -4868,7 +4999,7 @@ async function compileInstrumentedMount(task, bundleDir) {
|
|
|
4868
4999
|
import { createElement } from "react";
|
|
4869
5000
|
import { createRoot } from "react-dom/client";
|
|
4870
5001
|
import { __registerParts } from "react/jsx-runtime";
|
|
4871
|
-
import * as B from ${JSON.stringify(
|
|
5002
|
+
import * as B from ${JSON.stringify(path18.resolve(entryTsx))};
|
|
4872
5003
|
const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
|
|
4873
5004
|
const pairs: Array<[unknown, string]> = [];
|
|
4874
5005
|
for (const name of cfg.partComponents) {
|
|
@@ -4923,7 +5054,7 @@ function expectedParts(task, roles, mainSlug) {
|
|
|
4923
5054
|
}
|
|
4924
5055
|
function interiorRegions(setDir, roles) {
|
|
4925
5056
|
const mains = roles.main;
|
|
4926
|
-
const withInterior = mains.filter((m) =>
|
|
5057
|
+
const withInterior = mains.filter((m) => existsSync13(path18.join(setDir, m, "get_metadata_interior.json")));
|
|
4927
5058
|
if (withInterior.length === 0) {
|
|
4928
5059
|
return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
|
|
4929
5060
|
}
|
|
@@ -4940,7 +5071,7 @@ async function checkCropComposition(task, bundleDir, regions, opts = {}) {
|
|
|
4940
5071
|
if (typeof js !== "string") {
|
|
4941
5072
|
return regions.map((r) => ({ id: cropId(r), pass: false, similarity: 0, inkRecall: 0, detail: js.error }));
|
|
4942
5073
|
}
|
|
4943
|
-
const css = ["tokens.css", "styles.css"].map((f) =>
|
|
5074
|
+
const css = ["tokens.css", "styles.css"].map((f) => path18.join(bundleDir, f)).filter((f) => existsSync13(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
|
|
4944
5075
|
const server = await chromium7.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
|
|
4945
5076
|
const browser = await chromium7.connect(server.wsEndpoint());
|
|
4946
5077
|
const PAD = 4;
|
|
@@ -5045,7 +5176,7 @@ async function checkStructuralComposition(task, bundleDir, roles, opts = {}) {
|
|
|
5045
5176
|
const deadlineMs = timeoutMs + 1e4;
|
|
5046
5177
|
const js = await compileInstrumentedMount(task, bundleDir);
|
|
5047
5178
|
if (typeof js !== "string") return [...results, ...mains.map((m) => ({ id: `composition:${m}`, pass: false, detail: js.error }))];
|
|
5048
|
-
const css = ["tokens.css", "styles.css"].map((f) =>
|
|
5179
|
+
const css = ["tokens.css", "styles.css"].map((f) => path18.join(bundleDir, f)).filter((f) => existsSync13(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
|
|
5049
5180
|
const server = await chromium7.launchServer({ executablePath: resolveChrome(), headless: true, args: mountArgs() });
|
|
5050
5181
|
const browser = await chromium7.connect(server.wsEndpoint());
|
|
5051
5182
|
try {
|
|
@@ -5110,7 +5241,7 @@ body{margin:0;padding:20px}
|
|
|
5110
5241
|
}
|
|
5111
5242
|
var _fontFaces4, REAL_JSX_SPEC, JSX_SHIM, CROP_BAR, cropId;
|
|
5112
5243
|
var init_composition = __esm({
|
|
5113
|
-
"
|
|
5244
|
+
"packages/verify/src/composition.ts"() {
|
|
5114
5245
|
"use strict";
|
|
5115
5246
|
init_runtime();
|
|
5116
5247
|
init_browser();
|
|
@@ -5134,18 +5265,18 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
|
|
|
5134
5265
|
}
|
|
5135
5266
|
});
|
|
5136
5267
|
|
|
5137
|
-
//
|
|
5138
|
-
import { existsSync as
|
|
5139
|
-
import
|
|
5268
|
+
// packages/verify/src/occlusion.ts
|
|
5269
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
5270
|
+
import path19 from "node:path";
|
|
5140
5271
|
import { build as build7 } from "esbuild";
|
|
5141
5272
|
import { chromium as chromium8 } from "playwright-core";
|
|
5142
5273
|
async function compileTwoUp(task, bundleDir) {
|
|
5143
|
-
const entryTsx =
|
|
5144
|
-
if (!
|
|
5274
|
+
const entryTsx = path19.join(bundleDir, task.entry);
|
|
5275
|
+
if (!existsSync14(entryTsx)) return { error: `${task.entry} missing` };
|
|
5145
5276
|
const src = `
|
|
5146
5277
|
import { createElement } from "react";
|
|
5147
5278
|
import { createRoot } from "react-dom/client";
|
|
5148
|
-
import * as B from ${JSON.stringify(
|
|
5279
|
+
import * as B from ${JSON.stringify(path19.resolve(entryTsx))};
|
|
5149
5280
|
const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
|
|
5150
5281
|
const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
|
|
5151
5282
|
for (const id of ["first", "second"]) {
|
|
@@ -5283,7 +5414,7 @@ body{margin:0;padding:16px;display:flex;flex-direction:column;gap:8px;align-item
|
|
|
5283
5414
|
}
|
|
5284
5415
|
var OVERLAY_SELECTOR;
|
|
5285
5416
|
var init_occlusion = __esm({
|
|
5286
|
-
"
|
|
5417
|
+
"packages/verify/src/occlusion.ts"() {
|
|
5287
5418
|
"use strict";
|
|
5288
5419
|
init_runtime();
|
|
5289
5420
|
init_browser();
|
|
@@ -5292,9 +5423,9 @@ var init_occlusion = __esm({
|
|
|
5292
5423
|
}
|
|
5293
5424
|
});
|
|
5294
5425
|
|
|
5295
|
-
//
|
|
5426
|
+
// packages/verify/src/index.ts
|
|
5296
5427
|
var init_src4 = __esm({
|
|
5297
|
-
"
|
|
5428
|
+
"packages/verify/src/index.ts"() {
|
|
5298
5429
|
"use strict";
|
|
5299
5430
|
init_browser();
|
|
5300
5431
|
init_runtime();
|
|
@@ -5322,22 +5453,22 @@ var init_src4 = __esm({
|
|
|
5322
5453
|
}
|
|
5323
5454
|
});
|
|
5324
5455
|
|
|
5325
|
-
//
|
|
5326
|
-
import { existsSync as
|
|
5327
|
-
import
|
|
5456
|
+
// packages/cli/src/environment.ts
|
|
5457
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
|
|
5458
|
+
import path20 from "node:path";
|
|
5328
5459
|
import { createHash as createHash3 } from "node:crypto";
|
|
5329
|
-
import { fileURLToPath as
|
|
5460
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
5330
5461
|
function cliVersion() {
|
|
5331
5462
|
try {
|
|
5332
|
-
return JSON.parse(readFileSync11(
|
|
5463
|
+
return JSON.parse(readFileSync11(path20.join(path20.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
|
|
5333
5464
|
} catch {
|
|
5334
5465
|
return "dev";
|
|
5335
5466
|
}
|
|
5336
5467
|
}
|
|
5337
5468
|
function environmentStamp(taskFamilies) {
|
|
5338
|
-
const manifestPath2 =
|
|
5469
|
+
const manifestPath2 = path20.join(fontCacheDir(), "manifest.json");
|
|
5339
5470
|
let fontsHash = null;
|
|
5340
|
-
if (
|
|
5471
|
+
if (existsSync15(manifestPath2)) {
|
|
5341
5472
|
try {
|
|
5342
5473
|
const entries = JSON.parse(readFileSync11(manifestPath2, "utf8"));
|
|
5343
5474
|
const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
|
|
@@ -5357,20 +5488,20 @@ function environmentStamp(taskFamilies) {
|
|
|
5357
5488
|
return { chrome, chromeVersion: ver === null ? null : ver + lcdSuffix, fontsManifestSha256: fontsHash };
|
|
5358
5489
|
}
|
|
5359
5490
|
var init_environment = __esm({
|
|
5360
|
-
"
|
|
5491
|
+
"packages/cli/src/environment.ts"() {
|
|
5361
5492
|
"use strict";
|
|
5362
5493
|
init_src4();
|
|
5363
5494
|
}
|
|
5364
5495
|
});
|
|
5365
5496
|
|
|
5366
|
-
//
|
|
5497
|
+
// packages/cli/src/describe.ts
|
|
5367
5498
|
function printDescription(description) {
|
|
5368
5499
|
process.stdout.write(`${JSON.stringify(description, null, 2)}
|
|
5369
5500
|
`);
|
|
5370
5501
|
}
|
|
5371
5502
|
var COMMON_EXIT_CODES;
|
|
5372
5503
|
var init_describe = __esm({
|
|
5373
|
-
"
|
|
5504
|
+
"packages/cli/src/describe.ts"() {
|
|
5374
5505
|
"use strict";
|
|
5375
5506
|
COMMON_EXIT_CODES = {
|
|
5376
5507
|
0: "success",
|
|
@@ -5382,9 +5513,9 @@ var init_describe = __esm({
|
|
|
5382
5513
|
}
|
|
5383
5514
|
});
|
|
5384
5515
|
|
|
5385
|
-
//
|
|
5386
|
-
import { existsSync as
|
|
5387
|
-
import
|
|
5516
|
+
// packages/cli/src/env.ts
|
|
5517
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
|
|
5518
|
+
import path21 from "node:path";
|
|
5388
5519
|
function parseEnv(content) {
|
|
5389
5520
|
const entries = /* @__PURE__ */ new Map();
|
|
5390
5521
|
for (const line of content.split("\n")) {
|
|
@@ -5396,17 +5527,17 @@ function parseEnv(content) {
|
|
|
5396
5527
|
function resolveCredential(name) {
|
|
5397
5528
|
const fromProcess = process.env[name];
|
|
5398
5529
|
if (fromProcess) return fromProcess;
|
|
5399
|
-
const envPath =
|
|
5400
|
-
if (!
|
|
5530
|
+
const envPath = path21.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
|
|
5531
|
+
if (!existsSync16(envPath)) return void 0;
|
|
5401
5532
|
return parseEnv(readFileSync12(envPath, "utf8")).get(name);
|
|
5402
5533
|
}
|
|
5403
5534
|
var init_env = __esm({
|
|
5404
|
-
"
|
|
5535
|
+
"packages/cli/src/env.ts"() {
|
|
5405
5536
|
"use strict";
|
|
5406
5537
|
}
|
|
5407
5538
|
});
|
|
5408
5539
|
|
|
5409
|
-
//
|
|
5540
|
+
// packages/cli/src/output.ts
|
|
5410
5541
|
var output_exports = {};
|
|
5411
5542
|
__export(output_exports, {
|
|
5412
5543
|
PROGRESS_PREFIX: () => PROGRESS_PREFIX,
|
|
@@ -5450,23 +5581,23 @@ function warn(flags, message) {
|
|
|
5450
5581
|
}
|
|
5451
5582
|
var PROGRESS_PREFIX;
|
|
5452
5583
|
var init_output = __esm({
|
|
5453
|
-
"
|
|
5584
|
+
"packages/cli/src/output.ts"() {
|
|
5454
5585
|
"use strict";
|
|
5455
5586
|
init_src3();
|
|
5456
5587
|
PROGRESS_PREFIX = "@tendril-progress ";
|
|
5457
5588
|
}
|
|
5458
5589
|
});
|
|
5459
5590
|
|
|
5460
|
-
//
|
|
5461
|
-
import { chmodSync, existsSync as
|
|
5591
|
+
// packages/cli/src/entitlement.ts
|
|
5592
|
+
import { chmodSync, existsSync as existsSync17, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5462
5593
|
import crypto from "node:crypto";
|
|
5463
5594
|
import os3 from "node:os";
|
|
5464
|
-
import
|
|
5595
|
+
import path22 from "node:path";
|
|
5465
5596
|
function entitlementPath() {
|
|
5466
|
-
return process.env["TENDRIL_ENTITLEMENT_PATH"] ??
|
|
5597
|
+
return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path22.join(os3.homedir(), ".tendril", "entitlement.json");
|
|
5467
5598
|
}
|
|
5468
5599
|
function readStoredEntitlement(file = entitlementPath()) {
|
|
5469
|
-
if (!
|
|
5600
|
+
if (!existsSync17(file)) return void 0;
|
|
5470
5601
|
try {
|
|
5471
5602
|
const parsed = JSON.parse(readFileSync13(file, "utf8"));
|
|
5472
5603
|
if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
|
|
@@ -5476,7 +5607,7 @@ function readStoredEntitlement(file = entitlementPath()) {
|
|
|
5476
5607
|
}
|
|
5477
5608
|
}
|
|
5478
5609
|
function writeStoredEntitlement(stored, file = entitlementPath()) {
|
|
5479
|
-
mkdirSync4(
|
|
5610
|
+
mkdirSync4(path22.dirname(file), { recursive: true });
|
|
5480
5611
|
writeFileSync7(file, `${JSON.stringify(stored, null, 2)}
|
|
5481
5612
|
`);
|
|
5482
5613
|
chmodSync(file, 384);
|
|
@@ -5502,11 +5633,11 @@ function checkEntitlement(opts = {}) {
|
|
|
5502
5633
|
const now = opts.now ?? Date.now();
|
|
5503
5634
|
const stored = "stored" in opts ? opts.stored : readStoredEntitlement();
|
|
5504
5635
|
if (stored === void 0) {
|
|
5505
|
-
return { ok: false, code: "entitlement-required", error: "no entitlement on this machine \u2014 record and generate need an active Tendril plan (verify stays free, always)", remediation:
|
|
5636
|
+
return { ok: false, code: "entitlement-required", error: "no entitlement on this machine \u2014 record and generate need an active Tendril plan (verify stays free, always)", remediation: activateRemediation() };
|
|
5506
5637
|
}
|
|
5507
5638
|
const parsed = parseEntitlementToken(stored.token);
|
|
5508
5639
|
if ("error" in parsed) {
|
|
5509
|
-
return { ok: false, code: "entitlement-invalid", error: `stored entitlement is unreadable: ${parsed.error}`, remediation:
|
|
5640
|
+
return { ok: false, code: "entitlement-invalid", error: `stored entitlement is unreadable: ${parsed.error}`, remediation: activateRemediation() };
|
|
5510
5641
|
}
|
|
5511
5642
|
const pem = keys[parsed.claims.kid];
|
|
5512
5643
|
const valid = pem !== void 0 && (() => {
|
|
@@ -5517,14 +5648,14 @@ function checkEntitlement(opts = {}) {
|
|
|
5517
5648
|
}
|
|
5518
5649
|
})();
|
|
5519
5650
|
if (!valid) {
|
|
5520
|
-
return { ok: false, code: "entitlement-invalid", error: "stored entitlement failed signature verification", remediation:
|
|
5651
|
+
return { ok: false, code: "entitlement-invalid", error: "stored entitlement failed signature verification", remediation: activateRemediation() };
|
|
5521
5652
|
}
|
|
5522
5653
|
if (now > parsed.claims.exp + TOLERANCE_MS) {
|
|
5523
5654
|
return {
|
|
5524
5655
|
ok: false,
|
|
5525
5656
|
code: "entitlement-expired",
|
|
5526
5657
|
error: "entitlement expired (and the offline tolerance window has passed)",
|
|
5527
|
-
remediation: `Reconnect and ${
|
|
5658
|
+
remediation: `Reconnect and ${activateRemediation()}`
|
|
5528
5659
|
};
|
|
5529
5660
|
}
|
|
5530
5661
|
if (now < stored.lastRefreshAt - CLOCK_ROLLBACK_MS) {
|
|
@@ -5532,7 +5663,7 @@ function checkEntitlement(opts = {}) {
|
|
|
5532
5663
|
ok: false,
|
|
5533
5664
|
code: "entitlement-clock",
|
|
5534
5665
|
error: "system clock sits more than a day before the last entitlement refresh",
|
|
5535
|
-
remediation: `Fix the system clock, then ${
|
|
5666
|
+
remediation: `Fix the system clock, then ${activateRemediation()}`
|
|
5536
5667
|
};
|
|
5537
5668
|
}
|
|
5538
5669
|
return { ok: true, mode: "active", claims: parsed.claims, stale: now > parsed.claims.exp };
|
|
@@ -5543,26 +5674,27 @@ function requireEntitlement(flags) {
|
|
|
5543
5674
|
fail(flags, ExitCode.Auth, { error: status.error, code: status.code, remediation: status.remediation });
|
|
5544
5675
|
}
|
|
5545
5676
|
}
|
|
5546
|
-
var ENT_PREFIX, PUBLIC_KEYS, TOLERANCE_MS, CLOCK_ROLLBACK_MS, b64urlDecode,
|
|
5677
|
+
var ENT_PREFIX, PUBLIC_KEYS, TOLERANCE_MS, CLOCK_ROLLBACK_MS, b64urlDecode, activateRemediation;
|
|
5547
5678
|
var init_entitlement = __esm({
|
|
5548
|
-
"
|
|
5679
|
+
"packages/cli/src/entitlement.ts"() {
|
|
5549
5680
|
"use strict";
|
|
5550
5681
|
init_src3();
|
|
5682
|
+
init_invocation();
|
|
5551
5683
|
init_output();
|
|
5552
5684
|
ENT_PREFIX = "tendril-ent.v1.";
|
|
5553
5685
|
PUBLIC_KEYS = {};
|
|
5554
5686
|
TOLERANCE_MS = 24 * 60 * 60 * 1e3;
|
|
5555
5687
|
CLOCK_ROLLBACK_MS = 24 * 60 * 60 * 1e3;
|
|
5556
5688
|
b64urlDecode = (s) => Buffer.from(s, "base64url");
|
|
5557
|
-
|
|
5689
|
+
activateRemediation = () => `Run \`${tendrilCommand("activate")}\` in your terminal (a browser approval \u2014 never paste license material into an agent chat).`;
|
|
5558
5690
|
}
|
|
5559
5691
|
});
|
|
5560
5692
|
|
|
5561
|
-
//
|
|
5693
|
+
// packages/cli/src/commands/doctor.ts
|
|
5562
5694
|
import { spawnSync } from "node:child_process";
|
|
5563
|
-
import { existsSync as
|
|
5695
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14, readdirSync as readdirSync3 } from "node:fs";
|
|
5564
5696
|
import os4 from "node:os";
|
|
5565
|
-
import
|
|
5697
|
+
import path23 from "node:path";
|
|
5566
5698
|
async function runDoctorChecks(options) {
|
|
5567
5699
|
const checks = [];
|
|
5568
5700
|
const mcpUrl = options.mcpUrl ?? DEFAULT_MCP_URL;
|
|
@@ -5592,7 +5724,7 @@ async function runDoctorChecks(options) {
|
|
|
5592
5724
|
name: "openrouter-key",
|
|
5593
5725
|
ok: false,
|
|
5594
5726
|
detail: "OPENROUTER_API_KEY not set \u2014 optional; only the curated API-model engine needs it",
|
|
5595
|
-
remediation:
|
|
5727
|
+
remediation: `Run \`${tendrilCommand("init")}\` to store your OpenRouter key (BYOK) if you plan to use the curated engine.`
|
|
5596
5728
|
}
|
|
5597
5729
|
);
|
|
5598
5730
|
try {
|
|
@@ -5606,17 +5738,17 @@ async function runDoctorChecks(options) {
|
|
|
5606
5738
|
remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
|
|
5607
5739
|
});
|
|
5608
5740
|
}
|
|
5609
|
-
const fontManifest =
|
|
5741
|
+
const fontManifest = path23.join(fontCacheDir(), "manifest.json");
|
|
5610
5742
|
checks.push(
|
|
5611
|
-
|
|
5743
|
+
existsSync18(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync14(fontManifest, "utf8")).length} faces)` } : {
|
|
5612
5744
|
name: "font-cache",
|
|
5613
5745
|
ok: true,
|
|
5614
5746
|
detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
|
|
5615
|
-
remediation:
|
|
5747
|
+
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.`
|
|
5616
5748
|
}
|
|
5617
5749
|
);
|
|
5618
|
-
const pluginRoot =
|
|
5619
|
-
if (
|
|
5750
|
+
const pluginRoot = path23.join(os4.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
|
|
5751
|
+
if (existsSync18(pluginRoot)) {
|
|
5620
5752
|
try {
|
|
5621
5753
|
const versions = readdirSync3(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
5622
5754
|
const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
|
|
@@ -5660,17 +5792,6 @@ async function runDoctorChecks(options) {
|
|
|
5660
5792
|
});
|
|
5661
5793
|
return { ok: checks.filter((c) => c.name !== "figma-pat" && c.name !== "openrouter-key" && c.name !== "path-skew").every((c) => c.ok), checks };
|
|
5662
5794
|
}
|
|
5663
|
-
function findPathTendril(pathEnv, platform) {
|
|
5664
|
-
const dirs = pathEnv.split(path22.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
|
|
5665
|
-
const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
|
|
5666
|
-
for (const dir of dirs) {
|
|
5667
|
-
for (const name of names) {
|
|
5668
|
-
const candidate = path22.join(dir, name);
|
|
5669
|
-
if (existsSync17(candidate)) return candidate;
|
|
5670
|
-
}
|
|
5671
|
-
}
|
|
5672
|
-
return null;
|
|
5673
|
-
}
|
|
5674
5795
|
function probeVersion(binary) {
|
|
5675
5796
|
const windowsShim = /\.(cmd|bat)$/i.test(binary);
|
|
5676
5797
|
const res = windowsShim ? spawnSync(`"${binary}" --version`, { shell: true, timeout: 5e3, encoding: "utf8" }) : spawnSync(binary, ["--version"], { timeout: 5e3, encoding: "utf8" });
|
|
@@ -5735,13 +5856,14 @@ async function runDoctor(flags) {
|
|
|
5735
5856
|
}
|
|
5736
5857
|
var DEFAULT_MCP_URL, DOCTOR_DESCRIPTION;
|
|
5737
5858
|
var init_doctor = __esm({
|
|
5738
|
-
"
|
|
5859
|
+
"packages/cli/src/commands/doctor.ts"() {
|
|
5739
5860
|
"use strict";
|
|
5740
5861
|
init_src4();
|
|
5741
5862
|
init_src();
|
|
5742
5863
|
init_describe();
|
|
5743
5864
|
init_env();
|
|
5744
5865
|
init_environment();
|
|
5866
|
+
init_invocation();
|
|
5745
5867
|
init_output();
|
|
5746
5868
|
init_entitlement();
|
|
5747
5869
|
DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
|
|
@@ -5763,7 +5885,7 @@ var init_doctor = __esm({
|
|
|
5763
5885
|
}
|
|
5764
5886
|
});
|
|
5765
5887
|
|
|
5766
|
-
//
|
|
5888
|
+
// packages/llm/src/model-config.ts
|
|
5767
5889
|
import { z as z6 } from "zod";
|
|
5768
5890
|
function resolveModel(config, requestedId) {
|
|
5769
5891
|
const entry = config.allowlist.find((m) => m.id === requestedId);
|
|
@@ -5784,7 +5906,7 @@ function resolveModel(config, requestedId) {
|
|
|
5784
5906
|
}
|
|
5785
5907
|
var ModelStatusSchema, ModelEntrySchema, ModelConfigSchema, DEFAULT_MODEL_CONFIG;
|
|
5786
5908
|
var init_model_config = __esm({
|
|
5787
|
-
"
|
|
5909
|
+
"packages/llm/src/model-config.ts"() {
|
|
5788
5910
|
"use strict";
|
|
5789
5911
|
ModelStatusSchema = z6.enum(["verified", "degraded"]);
|
|
5790
5912
|
ModelEntrySchema = z6.object({
|
|
@@ -5873,10 +5995,10 @@ var init_model_config = __esm({
|
|
|
5873
5995
|
}
|
|
5874
5996
|
});
|
|
5875
5997
|
|
|
5876
|
-
//
|
|
5998
|
+
// packages/llm/src/openrouter.ts
|
|
5877
5999
|
var FALLBACK_KINDS, DEFAULT_REQUEST_TIMEOUT_MS, OpenRouterClient;
|
|
5878
6000
|
var init_openrouter = __esm({
|
|
5879
|
-
"
|
|
6001
|
+
"packages/llm/src/openrouter.ts"() {
|
|
5880
6002
|
"use strict";
|
|
5881
6003
|
FALLBACK_KINDS = /* @__PURE__ */ new Set(["rate-limit", "server", "network", "timeout"]);
|
|
5882
6004
|
DEFAULT_REQUEST_TIMEOUT_MS = 6e5;
|
|
@@ -6003,7 +6125,7 @@ var init_openrouter = __esm({
|
|
|
6003
6125
|
}
|
|
6004
6126
|
});
|
|
6005
6127
|
|
|
6006
|
-
//
|
|
6128
|
+
// packages/llm/src/semantics.ts
|
|
6007
6129
|
import { z as z7 } from "zod";
|
|
6008
6130
|
function portable(node) {
|
|
6009
6131
|
if (Array.isArray(node)) return node.map(portable);
|
|
@@ -6041,7 +6163,7 @@ function docsJsonSchema() {
|
|
|
6041
6163
|
}
|
|
6042
6164
|
var PropTypeSchema, SemanticsSchema, CodegenOutputSchema, DocsOutputSchema;
|
|
6043
6165
|
var init_semantics = __esm({
|
|
6044
|
-
"
|
|
6166
|
+
"packages/llm/src/semantics.ts"() {
|
|
6045
6167
|
"use strict";
|
|
6046
6168
|
PropTypeSchema = z7.discriminatedUnion("kind", [
|
|
6047
6169
|
z7.object({ kind: z7.literal("enum"), values: z7.array(z7.string()).min(1) }),
|
|
@@ -6130,10 +6252,10 @@ var init_semantics = __esm({
|
|
|
6130
6252
|
}
|
|
6131
6253
|
});
|
|
6132
6254
|
|
|
6133
|
-
//
|
|
6255
|
+
// packages/llm/src/golden/button.ts
|
|
6134
6256
|
var goldenButtonSemantics, goldenButtonCode, goldenButtonDocs;
|
|
6135
6257
|
var init_button2 = __esm({
|
|
6136
|
-
"
|
|
6258
|
+
"packages/llm/src/golden/button.ts"() {
|
|
6137
6259
|
"use strict";
|
|
6138
6260
|
goldenButtonSemantics = {
|
|
6139
6261
|
componentName: "Button",
|
|
@@ -6291,7 +6413,7 @@ handling come from the platform.
|
|
|
6291
6413
|
}
|
|
6292
6414
|
});
|
|
6293
6415
|
|
|
6294
|
-
//
|
|
6416
|
+
// packages/llm/src/prompt.ts
|
|
6295
6417
|
function buildStablePrefix(input) {
|
|
6296
6418
|
const tokens = [...input.tokenLines].sort().join("\n");
|
|
6297
6419
|
const golden = `GOLDEN EXAMPLE (canonical quality bar)
|
|
@@ -6325,7 +6447,7 @@ ${input.irJson}` });
|
|
|
6325
6447
|
}
|
|
6326
6448
|
var SYSTEM_RULES;
|
|
6327
6449
|
var init_prompt = __esm({
|
|
6328
|
-
"
|
|
6450
|
+
"packages/llm/src/prompt.ts"() {
|
|
6329
6451
|
"use strict";
|
|
6330
6452
|
init_button2();
|
|
6331
6453
|
SYSTEM_RULES = `You generate production React components from a design-system IR.
|
|
@@ -6421,10 +6543,10 @@ strictly in the requested JSON shape.`;
|
|
|
6421
6543
|
}
|
|
6422
6544
|
});
|
|
6423
6545
|
|
|
6424
|
-
//
|
|
6546
|
+
// packages/llm/src/generation-model.ts
|
|
6425
6547
|
var MockGenerationModel, MAX_SCHEMA_ATTEMPTS, OpenRouterGenerationModel;
|
|
6426
6548
|
var init_generation_model = __esm({
|
|
6427
|
-
"
|
|
6549
|
+
"packages/llm/src/generation-model.ts"() {
|
|
6428
6550
|
"use strict";
|
|
6429
6551
|
init_openrouter();
|
|
6430
6552
|
init_prompt();
|
|
@@ -6581,9 +6703,9 @@ ${JSON.stringify(semantics)}`,
|
|
|
6581
6703
|
}
|
|
6582
6704
|
});
|
|
6583
6705
|
|
|
6584
|
-
//
|
|
6706
|
+
// packages/llm/src/index.ts
|
|
6585
6707
|
var init_src5 = __esm({
|
|
6586
|
-
"
|
|
6708
|
+
"packages/llm/src/index.ts"() {
|
|
6587
6709
|
"use strict";
|
|
6588
6710
|
init_model_config();
|
|
6589
6711
|
init_openrouter();
|
|
@@ -6594,11 +6716,11 @@ var init_src5 = __esm({
|
|
|
6594
6716
|
}
|
|
6595
6717
|
});
|
|
6596
6718
|
|
|
6597
|
-
//
|
|
6719
|
+
// packages/metadata/src/component-json.ts
|
|
6598
6720
|
import { z as z8 } from "zod";
|
|
6599
6721
|
var TokenUseSchema, AntiPatternSchema, ComponentJsonSchema;
|
|
6600
6722
|
var init_component_json = __esm({
|
|
6601
|
-
"
|
|
6723
|
+
"packages/metadata/src/component-json.ts"() {
|
|
6602
6724
|
"use strict";
|
|
6603
6725
|
TokenUseSchema = z8.object({
|
|
6604
6726
|
/** DTCG token path, e.g. "color.primary". */
|
|
@@ -6650,7 +6772,7 @@ var init_component_json = __esm({
|
|
|
6650
6772
|
}
|
|
6651
6773
|
});
|
|
6652
6774
|
|
|
6653
|
-
//
|
|
6775
|
+
// packages/metadata/src/extract.ts
|
|
6654
6776
|
import { parse } from "react-docgen";
|
|
6655
6777
|
function extractProps(tsxSource, filename) {
|
|
6656
6778
|
const docs = parse(tsxSource, { filename });
|
|
@@ -6723,13 +6845,13 @@ function buildComponentJson(input) {
|
|
|
6723
6845
|
});
|
|
6724
6846
|
}
|
|
6725
6847
|
var init_extract = __esm({
|
|
6726
|
-
"
|
|
6848
|
+
"packages/metadata/src/extract.ts"() {
|
|
6727
6849
|
"use strict";
|
|
6728
6850
|
init_component_json();
|
|
6729
6851
|
}
|
|
6730
6852
|
});
|
|
6731
6853
|
|
|
6732
|
-
//
|
|
6854
|
+
// packages/metadata/src/recording-set.ts
|
|
6733
6855
|
import { z as z9 } from "zod";
|
|
6734
6856
|
function roleLossToken(loss) {
|
|
6735
6857
|
return loss.kind === "main" ? `main:${loss.main}` : `part:${loss.part}${EDGE_ARROW}${loss.main}`;
|
|
@@ -6790,7 +6912,7 @@ function validateRecordingSet(manifest, fileExists) {
|
|
|
6790
6912
|
}
|
|
6791
6913
|
var RECORDING_SET_VERSION, EnvelopeSchema, RepEntrySchema, RolesSchema, EDGE_ARROW, RecordingSetManifestSchema, REQUIRED_REP_FILES;
|
|
6792
6914
|
var init_recording_set = __esm({
|
|
6793
|
-
"
|
|
6915
|
+
"packages/metadata/src/recording-set.ts"() {
|
|
6794
6916
|
"use strict";
|
|
6795
6917
|
RECORDING_SET_VERSION = 1;
|
|
6796
6918
|
EnvelopeSchema = z9.object({
|
|
@@ -6851,7 +6973,7 @@ var init_recording_set = __esm({
|
|
|
6851
6973
|
}
|
|
6852
6974
|
});
|
|
6853
6975
|
|
|
6854
|
-
//
|
|
6976
|
+
// packages/metadata/src/bundle.ts
|
|
6855
6977
|
import { z as z10 } from "zod";
|
|
6856
6978
|
function readBundleManifest(raw) {
|
|
6857
6979
|
if (Buffer.byteLength(raw, "utf8") > MAX_BUNDLE_MANIFEST_BYTES) {
|
|
@@ -6920,7 +7042,7 @@ function hashRecordingSet(relPaths, readFile, sha256) {
|
|
|
6920
7042
|
}
|
|
6921
7043
|
var BUNDLE_VERSION, ConfigStatusSchema, PinnedPropSchema, PropAdapterSchema, RequiredFontSchema, ConfigClaimSchema, BehaviorClaimSchema, BundleProvenanceSchema, BundleComponentJsonSchema, MAX_BUNDLE_MANIFEST_BYTES, MAX_BUNDLE_SOURCE_BYTES;
|
|
6922
7044
|
var init_bundle = __esm({
|
|
6923
|
-
"
|
|
7045
|
+
"packages/metadata/src/bundle.ts"() {
|
|
6924
7046
|
"use strict";
|
|
6925
7047
|
init_recording_set();
|
|
6926
7048
|
BUNDLE_VERSION = 1;
|
|
@@ -7018,9 +7140,9 @@ var init_bundle = __esm({
|
|
|
7018
7140
|
}
|
|
7019
7141
|
});
|
|
7020
7142
|
|
|
7021
|
-
//
|
|
7143
|
+
// packages/metadata/src/index.ts
|
|
7022
7144
|
var init_src6 = __esm({
|
|
7023
|
-
"
|
|
7145
|
+
"packages/metadata/src/index.ts"() {
|
|
7024
7146
|
"use strict";
|
|
7025
7147
|
init_component_json();
|
|
7026
7148
|
init_extract();
|
|
@@ -7029,7 +7151,7 @@ var init_src6 = __esm({
|
|
|
7029
7151
|
}
|
|
7030
7152
|
});
|
|
7031
7153
|
|
|
7032
|
-
//
|
|
7154
|
+
// packages/cli/src/commands/activate.ts
|
|
7033
7155
|
var activate_exports = {};
|
|
7034
7156
|
__export(activate_exports, {
|
|
7035
7157
|
ENTITLEMENT_SERVICE_URL: () => ENTITLEMENT_SERVICE_URL,
|
|
@@ -7054,7 +7176,7 @@ async function runActivate(flags) {
|
|
|
7054
7176
|
fail(flags, ExitCode.Auth, {
|
|
7055
7177
|
error: `could not start activation: ${err instanceof Error ? err.message : String(err)}`,
|
|
7056
7178
|
code: "entitlement-service-unreachable",
|
|
7057
|
-
remediation:
|
|
7179
|
+
remediation: `Check your connection and retry \`${tendrilCommand("activate")}\`.`
|
|
7058
7180
|
});
|
|
7059
7181
|
}
|
|
7060
7182
|
process.stderr.write(`To activate Tendril, visit:
|
|
@@ -7081,7 +7203,7 @@ Waiting for approval\u2026
|
|
|
7081
7203
|
fail(flags, ExitCode.Auth, {
|
|
7082
7204
|
error: `activation was not approved (HTTP ${res.status})`,
|
|
7083
7205
|
code: "entitlement-denied",
|
|
7084
|
-
remediation:
|
|
7206
|
+
remediation: `Retry \`${tendrilCommand("activate")}\`; if it persists, check the account's plan in the portal.`
|
|
7085
7207
|
});
|
|
7086
7208
|
}
|
|
7087
7209
|
token = (await res.json()).token;
|
|
@@ -7091,7 +7213,7 @@ Waiting for approval\u2026
|
|
|
7091
7213
|
fail(flags, ExitCode.Auth, {
|
|
7092
7214
|
error: "activation timed out before the browser approval arrived",
|
|
7093
7215
|
code: "entitlement-timeout",
|
|
7094
|
-
remediation:
|
|
7216
|
+
remediation: `Run \`${tendrilCommand("activate")}\` again and complete the browser step within the shown window.`
|
|
7095
7217
|
});
|
|
7096
7218
|
}
|
|
7097
7219
|
const parsed = parseEntitlementToken(token);
|
|
@@ -7099,7 +7221,7 @@ Waiting for approval\u2026
|
|
|
7099
7221
|
fail(flags, ExitCode.Auth, {
|
|
7100
7222
|
error: `service returned an unusable token: ${parsed.error}`,
|
|
7101
7223
|
code: "entitlement-invalid",
|
|
7102
|
-
remediation:
|
|
7224
|
+
remediation: `Retry \`${tendrilCommand("activate")}\`; report this if it persists \u2014 it is a service-side fault.`
|
|
7103
7225
|
});
|
|
7104
7226
|
}
|
|
7105
7227
|
if (Object.keys(PUBLIC_KEYS).length > 0) {
|
|
@@ -7114,16 +7236,17 @@ Waiting for approval\u2026
|
|
|
7114
7236
|
}
|
|
7115
7237
|
var ENTITLEMENT_SERVICE_URL;
|
|
7116
7238
|
var init_activate = __esm({
|
|
7117
|
-
"
|
|
7239
|
+
"packages/cli/src/commands/activate.ts"() {
|
|
7118
7240
|
"use strict";
|
|
7119
7241
|
init_src3();
|
|
7120
7242
|
init_entitlement();
|
|
7243
|
+
init_invocation();
|
|
7121
7244
|
init_output();
|
|
7122
7245
|
ENTITLEMENT_SERVICE_URL = void 0;
|
|
7123
7246
|
}
|
|
7124
7247
|
});
|
|
7125
7248
|
|
|
7126
|
-
//
|
|
7249
|
+
// packages/cli/src/commands/record.ts
|
|
7127
7250
|
var record_exports = {};
|
|
7128
7251
|
__export(record_exports, {
|
|
7129
7252
|
instanceLeads: () => instanceLeads,
|
|
@@ -7140,9 +7263,9 @@ __export(record_exports, {
|
|
|
7140
7263
|
runRecordPlan: () => runRecordPlan,
|
|
7141
7264
|
runRecordStatus: () => runRecordStatus
|
|
7142
7265
|
});
|
|
7143
|
-
import { existsSync as
|
|
7266
|
+
import { existsSync as existsSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, readdirSync as readdirSync5 } from "node:fs";
|
|
7144
7267
|
import os5 from "node:os";
|
|
7145
|
-
import
|
|
7268
|
+
import path26 from "node:path";
|
|
7146
7269
|
import { writeFileSync as writeFileSync9 } from "node:fs";
|
|
7147
7270
|
function symbolsFromMetadataEnvelope(file, sourceFrame) {
|
|
7148
7271
|
const env = JSON.parse(readFileSync16(file, "utf8"));
|
|
@@ -7203,7 +7326,7 @@ function runRecordPlan(opts) {
|
|
|
7203
7326
|
if (rawFile !== void 0) {
|
|
7204
7327
|
try {
|
|
7205
7328
|
const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
|
|
7206
|
-
const tmp =
|
|
7329
|
+
const tmp = path26.join(mkdtempSync2(path26.join(os5.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
|
|
7207
7330
|
writeFileSync9(tmp, JSON.stringify(envelope));
|
|
7208
7331
|
metadataEntries.push({ file: tmp });
|
|
7209
7332
|
} catch (err) {
|
|
@@ -7225,7 +7348,7 @@ function runRecordPlan(opts) {
|
|
|
7225
7348
|
let metadataTruncated = false;
|
|
7226
7349
|
for (const { file, frame } of metadataEntries) {
|
|
7227
7350
|
try {
|
|
7228
|
-
const parsed = symbolsFromMetadataEnvelope(
|
|
7351
|
+
const parsed = symbolsFromMetadataEnvelope(path26.resolve(file), frame);
|
|
7229
7352
|
symbols.push(...parsed.symbols);
|
|
7230
7353
|
if (parsed.truncated) metadataTruncated = true;
|
|
7231
7354
|
} catch (err) {
|
|
@@ -7258,7 +7381,7 @@ function runRecordPlan(opts) {
|
|
|
7258
7381
|
if (symbols.length === 0) {
|
|
7259
7382
|
const leads = metadataEntries.flatMap(({ file }) => {
|
|
7260
7383
|
try {
|
|
7261
|
-
const env = JSON.parse(readFileSync16(
|
|
7384
|
+
const env = JSON.parse(readFileSync16(path26.resolve(file), "utf8"));
|
|
7262
7385
|
return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
|
|
7263
7386
|
} catch {
|
|
7264
7387
|
return [];
|
|
@@ -7363,7 +7486,7 @@ function nextPayload(setDir) {
|
|
|
7363
7486
|
const instruction = nextInstruction(setDir);
|
|
7364
7487
|
const status = sessionStatus(setDir);
|
|
7365
7488
|
const progress = { recordedReps: status.reps.filter((x) => x.missing.length === 0).length, totalReps: status.reps.length };
|
|
7366
|
-
if (instruction === null && !
|
|
7489
|
+
if (instruction === null && !existsSync20(path26.join(setDir, "get_variable_defs.json"))) {
|
|
7367
7490
|
const manifest = loadManifest(setDir);
|
|
7368
7491
|
const frameNode = Object.keys(manifest.sourceFrames ?? {})[0] ?? manifest.reps[0]?.nodeId ?? "";
|
|
7369
7492
|
return { slug: "__set__", nodeId: frameNode, tool: "get_variable_defs", note: `SET-LEVEL: call get_variable_defs on the component frame and ingest with --rep __set__. ${ENVELOPE_HELP}`, progress };
|
|
@@ -7380,7 +7503,7 @@ function runRecordNext(opts) {
|
|
|
7380
7503
|
const progress = payload["progress"];
|
|
7381
7504
|
process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
|
|
7382
7505
|
\u2192 ${payload["note"]}
|
|
7383
|
-
\u2192 then:
|
|
7506
|
+
\u2192 then: ${tendrilCommand(`record ingest --set ${path26.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
|
|
7384
7507
|
`);
|
|
7385
7508
|
});
|
|
7386
7509
|
}
|
|
@@ -7454,7 +7577,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
7454
7577
|
const skipped = [];
|
|
7455
7578
|
const failed = [];
|
|
7456
7579
|
for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
|
|
7457
|
-
if (
|
|
7580
|
+
if (existsSync20(path26.join(setDir, rep, name))) {
|
|
7458
7581
|
skipped.push(name);
|
|
7459
7582
|
continue;
|
|
7460
7583
|
}
|
|
@@ -7476,16 +7599,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
7476
7599
|
}
|
|
7477
7600
|
function rawEnvelopeFromFile(file, parts) {
|
|
7478
7601
|
if (parts) {
|
|
7479
|
-
const blocks = JSON.parse(readFileSync16(
|
|
7602
|
+
const blocks = JSON.parse(readFileSync16(path26.resolve(file), "utf8"));
|
|
7480
7603
|
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");
|
|
7481
7604
|
return { content: blocks.map((text) => ({ type: "text", text })) };
|
|
7482
7605
|
}
|
|
7483
|
-
return { content: [{ type: "text", text: readFileSync16(
|
|
7606
|
+
return { content: [{ type: "text", text: readFileSync16(path26.resolve(file), "utf8") }] };
|
|
7484
7607
|
}
|
|
7485
7608
|
async function runRecordIngest(opts) {
|
|
7486
7609
|
let payload;
|
|
7487
7610
|
try {
|
|
7488
|
-
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync16(
|
|
7611
|
+
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync16(path26.resolve(opts.file), "utf8"));
|
|
7489
7612
|
} catch (err) {
|
|
7490
7613
|
fail(opts, ExitCode.InputValidation, {
|
|
7491
7614
|
error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -7497,7 +7620,7 @@ async function runRecordIngest(opts) {
|
|
|
7497
7620
|
fail(opts, ExitCode.InputValidation, {
|
|
7498
7621
|
error: "--raw is for text tool responses; screenshots are binary",
|
|
7499
7622
|
code: "envelope-invalid",
|
|
7500
|
-
remediation:
|
|
7623
|
+
remediation: `Use \`${tendrilCommand(`record fetch --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
|
|
7501
7624
|
});
|
|
7502
7625
|
}
|
|
7503
7626
|
if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
|
|
@@ -7509,7 +7632,7 @@ async function runRecordIngest(opts) {
|
|
|
7509
7632
|
remediation: "Save the get_variable_defs response verbatim as a text envelope."
|
|
7510
7633
|
});
|
|
7511
7634
|
}
|
|
7512
|
-
writeFileSync9(
|
|
7635
|
+
writeFileSync9(path26.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
|
|
7513
7636
|
`);
|
|
7514
7637
|
emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
7515
7638
|
process.stdout.write("set-level get_variable_defs ingested\n");
|
|
@@ -7525,7 +7648,7 @@ async function runRecordIngest(opts) {
|
|
|
7525
7648
|
if (assets !== void 0) {
|
|
7526
7649
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
7527
7650
|
`);
|
|
7528
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it
|
|
7651
|
+
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
|
|
7529
7652
|
`);
|
|
7530
7653
|
}
|
|
7531
7654
|
});
|
|
@@ -7598,14 +7721,14 @@ async function runRecordIngestRep(opts) {
|
|
|
7598
7721
|
if (assets !== void 0) {
|
|
7599
7722
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
7600
7723
|
`);
|
|
7601
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it
|
|
7724
|
+
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
|
|
7602
7725
|
`);
|
|
7603
7726
|
}
|
|
7604
7727
|
});
|
|
7605
7728
|
}
|
|
7606
7729
|
function runRecordAsset(opts) {
|
|
7607
7730
|
if (opts.dir !== void 0) {
|
|
7608
|
-
const dir =
|
|
7731
|
+
const dir = path26.resolve(opts.dir);
|
|
7609
7732
|
const names = readdirSync5(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
|
|
7610
7733
|
if (names.length === 0) {
|
|
7611
7734
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -7617,7 +7740,7 @@ function runRecordAsset(opts) {
|
|
|
7617
7740
|
const ingested = [];
|
|
7618
7741
|
try {
|
|
7619
7742
|
for (const name of names) {
|
|
7620
|
-
ingestAsset(opts.setDir, opts.rep, name, readFileSync16(
|
|
7743
|
+
ingestAsset(opts.setDir, opts.rep, name, readFileSync16(path26.join(dir, name)));
|
|
7621
7744
|
ingested.push(name);
|
|
7622
7745
|
}
|
|
7623
7746
|
} catch (err) {
|
|
@@ -7637,11 +7760,11 @@ function runRecordAsset(opts) {
|
|
|
7637
7760
|
fail(opts, ExitCode.InputValidation, {
|
|
7638
7761
|
error: "pass --name and --file for a single asset, or --dir for a batch",
|
|
7639
7762
|
code: "asset-rejected",
|
|
7640
|
-
remediation:
|
|
7763
|
+
remediation: tendrilCommand(`record asset --set ${path26.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
|
|
7641
7764
|
});
|
|
7642
7765
|
}
|
|
7643
7766
|
try {
|
|
7644
|
-
ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync16(
|
|
7767
|
+
ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync16(path26.resolve(opts.file)));
|
|
7645
7768
|
emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
|
|
7646
7769
|
process.stdout.write(`ingested ${opts.rep}/${opts.name}
|
|
7647
7770
|
`);
|
|
@@ -7677,7 +7800,7 @@ function narrowedRoles(derived, override) {
|
|
|
7677
7800
|
function rolesFromFile(opts, file, derived) {
|
|
7678
7801
|
let json;
|
|
7679
7802
|
try {
|
|
7680
|
-
json = JSON.parse(readFileSync16(
|
|
7803
|
+
json = JSON.parse(readFileSync16(path26.resolve(file), "utf8"));
|
|
7681
7804
|
} catch (err) {
|
|
7682
7805
|
fail(opts, ExitCode.InputValidation, {
|
|
7683
7806
|
error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
@@ -7715,11 +7838,11 @@ function rolesFromFile(opts, file, derived) {
|
|
|
7715
7838
|
};
|
|
7716
7839
|
}
|
|
7717
7840
|
function runRecordFinish(opts) {
|
|
7718
|
-
if (!
|
|
7841
|
+
if (!existsSync20(path26.join(opts.setDir, "recording-set.json"))) {
|
|
7719
7842
|
fail(opts, ExitCode.InputValidation, {
|
|
7720
7843
|
error: `no recording-set.json in ${opts.setDir}`,
|
|
7721
7844
|
code: "no-recording-set",
|
|
7722
|
-
remediation: `Run
|
|
7845
|
+
remediation: `Run \`${tendrilCommand(`record plan --set ${path26.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
|
|
7723
7846
|
});
|
|
7724
7847
|
}
|
|
7725
7848
|
const { manifest, raw } = readManifestFile(opts.setDir);
|
|
@@ -7747,17 +7870,17 @@ function runRecordFinish(opts) {
|
|
|
7747
7870
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
7748
7871
|
error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
|
|
7749
7872
|
code: "roles-confirmation-not-interactive",
|
|
7750
|
-
remediation:
|
|
7873
|
+
remediation: `A human runs \`${tendrilCommand(`record finish --set ${path26.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
|
|
7751
7874
|
});
|
|
7752
7875
|
}
|
|
7753
7876
|
const merged = { ...raw, roles };
|
|
7754
|
-
const { issues } = validateRecordingSet(merged, (rel) =>
|
|
7877
|
+
const { issues } = validateRecordingSet(merged, (rel) => existsSync20(path26.join(opts.setDir, rel)));
|
|
7755
7878
|
const errors = issues.filter((i) => i.severity === "error");
|
|
7756
7879
|
if (errors.length > 0) {
|
|
7757
7880
|
fail(opts, ExitCode.InputValidation, {
|
|
7758
7881
|
error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
|
|
7759
7882
|
code: "recording-set-invalid",
|
|
7760
|
-
remediation:
|
|
7883
|
+
remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path26.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
|
|
7761
7884
|
});
|
|
7762
7885
|
}
|
|
7763
7886
|
for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
|
|
@@ -7770,27 +7893,28 @@ function runRecordFinish(opts) {
|
|
|
7770
7893
|
}
|
|
7771
7894
|
var ENVELOPE_HELP, isAutoFetchAssetUrl, describeRoleLoss;
|
|
7772
7895
|
var init_record = __esm({
|
|
7773
|
-
"
|
|
7896
|
+
"packages/cli/src/commands/record.ts"() {
|
|
7774
7897
|
"use strict";
|
|
7775
7898
|
init_src3();
|
|
7776
7899
|
init_src();
|
|
7777
7900
|
init_src6();
|
|
7778
7901
|
init_output();
|
|
7779
7902
|
init_entitlement();
|
|
7780
|
-
|
|
7903
|
+
init_invocation();
|
|
7904
|
+
ENVELOPE_HELP = `Envelope format: text tools save {"content":[{"type":"text","text":"<VERBATIM response text incl. any Currently-selected-nodes block>"}]}; get_screenshot: do NOT download the image yourself \u2014 pass its image_url to \`${tendrilCommand("record fetch")}\` (MCP: tendril_record_fetch), which pulls the bytes to disk directly. That keeps the pixel ground truth out of your context and costs one approval instead of a shell command per asset. Assets are asset-<first-8-hex-of-figma-uuid>.<ext>; their URLs appear as const declarations inside the design-context text and also expire.`;
|
|
7781
7905
|
isAutoFetchAssetUrl = (url) => isFigmaAssetUrl(url) || isLocalAssetUrl(url);
|
|
7782
7906
|
describeRoleLoss = (loss) => loss.kind === "main" ? `drops main "${loss.main}"` : `stops "${loss.part}" being a part of main "${loss.main}"`;
|
|
7783
7907
|
}
|
|
7784
7908
|
});
|
|
7785
7909
|
|
|
7786
|
-
//
|
|
7910
|
+
// packages/generate/src/engine.ts
|
|
7787
7911
|
var init_engine = __esm({
|
|
7788
|
-
"
|
|
7912
|
+
"packages/generate/src/engine.ts"() {
|
|
7789
7913
|
"use strict";
|
|
7790
7914
|
}
|
|
7791
7915
|
});
|
|
7792
7916
|
|
|
7793
|
-
//
|
|
7917
|
+
// packages/generate/src/parse.ts
|
|
7794
7918
|
function parseFiles(text, entry) {
|
|
7795
7919
|
const out = /* @__PURE__ */ new Map();
|
|
7796
7920
|
const re = /```[a-z]*[^\n]*\n(?:\/\/|\/\*)[ \t]*FILE:[ \t]*([\w.]+)[ \t]*(?:\*\/)?[ \t]*\n([\s\S]*?)```/g;
|
|
@@ -7823,15 +7947,15 @@ ${content}\`\`\``);
|
|
|
7823
7947
|
return blocks.join("\n");
|
|
7824
7948
|
}
|
|
7825
7949
|
var init_parse = __esm({
|
|
7826
|
-
"
|
|
7950
|
+
"packages/generate/src/parse.ts"() {
|
|
7827
7951
|
"use strict";
|
|
7828
7952
|
}
|
|
7829
7953
|
});
|
|
7830
7954
|
|
|
7831
|
-
//
|
|
7955
|
+
// packages/generate/src/engine-curated.ts
|
|
7832
7956
|
var CuratedEngine;
|
|
7833
7957
|
var init_engine_curated = __esm({
|
|
7834
|
-
"
|
|
7958
|
+
"packages/generate/src/engine-curated.ts"() {
|
|
7835
7959
|
"use strict";
|
|
7836
7960
|
init_parse();
|
|
7837
7961
|
CuratedEngine = class {
|
|
@@ -7969,9 +8093,9 @@ var init_engine_curated = __esm({
|
|
|
7969
8093
|
}
|
|
7970
8094
|
});
|
|
7971
8095
|
|
|
7972
|
-
//
|
|
7973
|
-
import { existsSync as
|
|
7974
|
-
import
|
|
8096
|
+
// packages/generate/src/loop.ts
|
|
8097
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readFileSync as readFileSync17, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8098
|
+
import path27 from "node:path";
|
|
7975
8099
|
import { z as z11 } from "zod";
|
|
7976
8100
|
function objective(scores, behaviors) {
|
|
7977
8101
|
const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
|
|
@@ -8005,12 +8129,12 @@ ${preludeLines.join("\n")}` : ""}${absentLines.length > 0 ? `
|
|
|
8005
8129
|
MISSING FEATURES (absent-ink clusters \u2014 recorded marks your render leaves out or paints invisibly; GATING at the cert bar. Fix the named node's ink \u2014 a config carrying one cannot certify):
|
|
8006
8130
|
${absentLines.join("\n")}` : ""}
|
|
8007
8131
|
|
|
8008
|
-
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=1 with LOW sim means nothing is missing \u2014
|
|
8132
|
+
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=1 with LOW sim means nothing is MISSING (do not chase missing ink) \u2014 the same pixels are being painted wrong, and there are TWO causes. 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."}`;
|
|
8009
8133
|
}
|
|
8010
8134
|
function archivePriorRun(outDir) {
|
|
8011
|
-
if (!
|
|
8135
|
+
if (!existsSync21(path27.join(outDir, "run-log.json")) && !existsSync21(path27.join(outDir, "loop-state.json"))) return void 0;
|
|
8012
8136
|
let n = 1;
|
|
8013
|
-
while (
|
|
8137
|
+
while (existsSync21(`${outDir}-prev-${n}`)) n += 1;
|
|
8014
8138
|
renameSync(outDir, `${outDir}-prev-${n}`);
|
|
8015
8139
|
return `${outDir}-prev-${n}`;
|
|
8016
8140
|
}
|
|
@@ -8019,14 +8143,14 @@ async function runEngineLoop(opts) {
|
|
|
8019
8143
|
const plateau = opts.plateau ?? 2;
|
|
8020
8144
|
const progress = opts.onProgress ?? (() => {
|
|
8021
8145
|
});
|
|
8022
|
-
const statePath =
|
|
8023
|
-
const resuming = opts.resume === true &&
|
|
8146
|
+
const statePath = path27.join(opts.outDir, "loop-state.json");
|
|
8147
|
+
const resuming = opts.resume === true && existsSync21(statePath);
|
|
8024
8148
|
if (!resuming) {
|
|
8025
8149
|
const archived = archivePriorRun(opts.outDir);
|
|
8026
8150
|
if (archived !== void 0) progress(`previous run archived to ${archived}`);
|
|
8027
8151
|
}
|
|
8028
8152
|
mkdirSync6(opts.outDir, { recursive: true });
|
|
8029
|
-
const scratch =
|
|
8153
|
+
const scratch = path27.join(opts.outDir, ".candidate");
|
|
8030
8154
|
let attempts = [];
|
|
8031
8155
|
let log = [];
|
|
8032
8156
|
let best;
|
|
@@ -8054,7 +8178,7 @@ async function runEngineLoop(opts) {
|
|
|
8054
8178
|
};
|
|
8055
8179
|
const writeCandidate = (files) => {
|
|
8056
8180
|
mkdirSync6(scratch, { recursive: true });
|
|
8057
|
-
for (const [name, content] of Object.entries(files)) writeFileSync10(
|
|
8181
|
+
for (const [name, content] of Object.entries(files)) writeFileSync10(path27.join(scratch, name), content);
|
|
8058
8182
|
};
|
|
8059
8183
|
const scoreCandidate = async (candidate, iter, usd, modelMs) => {
|
|
8060
8184
|
writeCandidate(candidate.files);
|
|
@@ -8112,8 +8236,8 @@ async function runEngineLoop(opts) {
|
|
|
8112
8236
|
const usd = candidate.usage?.usd ?? 0;
|
|
8113
8237
|
spentUsd += usd;
|
|
8114
8238
|
if (candidate.raw !== void 0) {
|
|
8115
|
-
mkdirSync6(
|
|
8116
|
-
writeFileSync10(
|
|
8239
|
+
mkdirSync6(path27.join(opts.outDir, "responses"), { recursive: true });
|
|
8240
|
+
writeFileSync10(path27.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
|
|
8117
8241
|
}
|
|
8118
8242
|
if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
|
|
8119
8243
|
const finish = candidate.usage?.finishReason ?? "?";
|
|
@@ -8139,10 +8263,10 @@ async function runEngineLoop(opts) {
|
|
|
8139
8263
|
}
|
|
8140
8264
|
}
|
|
8141
8265
|
}
|
|
8142
|
-
if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(
|
|
8266
|
+
if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path27.join(opts.outDir, name), content);
|
|
8143
8267
|
const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
|
|
8144
8268
|
writeFileSync10(
|
|
8145
|
-
|
|
8269
|
+
path27.join(opts.outDir, "run-log.json"),
|
|
8146
8270
|
`${JSON.stringify(
|
|
8147
8271
|
{
|
|
8148
8272
|
...opts.meta,
|
|
@@ -8163,7 +8287,7 @@ async function runEngineLoop(opts) {
|
|
|
8163
8287
|
}
|
|
8164
8288
|
var better, RegionSchema, ConfigScoreSchema, BehaviorResultSchema, LoopStateSchema;
|
|
8165
8289
|
var init_loop2 = __esm({
|
|
8166
|
-
"
|
|
8290
|
+
"packages/generate/src/loop.ts"() {
|
|
8167
8291
|
"use strict";
|
|
8168
8292
|
better = (a, b) => a[0] !== b[0] ? a[0] > b[0] : a[1] !== b[1] ? a[1] > b[1] : a[2] > b[2];
|
|
8169
8293
|
RegionSchema = z11.object({ x0: z11.number(), y0: z11.number(), x1: z11.number(), y1: z11.number(), density: z11.number() });
|
|
@@ -8208,9 +8332,9 @@ var init_loop2 = __esm({
|
|
|
8208
8332
|
}
|
|
8209
8333
|
});
|
|
8210
8334
|
|
|
8211
|
-
//
|
|
8212
|
-
import { existsSync as
|
|
8213
|
-
import
|
|
8335
|
+
// packages/generate/src/brief.ts
|
|
8336
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18 } from "node:fs";
|
|
8337
|
+
import path28 from "node:path";
|
|
8214
8338
|
function singleAxes2(name) {
|
|
8215
8339
|
const parsed = parseVariantAxes(name);
|
|
8216
8340
|
if (parsed === void 0) return void 0;
|
|
@@ -8449,8 +8573,8 @@ function envelopeText(file) {
|
|
|
8449
8573
|
}
|
|
8450
8574
|
function dismissEvidence(setDir, repSlugs) {
|
|
8451
8575
|
for (const slug of repSlugs) {
|
|
8452
|
-
const f =
|
|
8453
|
-
if (!
|
|
8576
|
+
const f = path28.join(setDir, slug, "get_design_context.json");
|
|
8577
|
+
if (!existsSync22(f)) continue;
|
|
8454
8578
|
const text = envelopeText(f);
|
|
8455
8579
|
const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*(?:true|false)\b/i.exec(text) ?? /\b(\w*dismiss\w*)\??\s*:\s*boolean\b/i.exec(text);
|
|
8456
8580
|
if (propHit !== null) return `emission prop "${propHit[1]}"`;
|
|
@@ -8494,13 +8618,13 @@ function recordedFontNeeds(setDir, opts = {}) {
|
|
|
8494
8618
|
}
|
|
8495
8619
|
};
|
|
8496
8620
|
const manifest = loadManifest(setDir);
|
|
8497
|
-
const setDefs =
|
|
8498
|
-
if (
|
|
8621
|
+
const setDefs = path28.join(setDir, "get_variable_defs.json");
|
|
8622
|
+
if (existsSync22(setDefs)) fromDefs(envelopeText(setDefs));
|
|
8499
8623
|
for (const rep of manifest.reps) {
|
|
8500
|
-
const ctx =
|
|
8501
|
-
if (
|
|
8502
|
-
const defs =
|
|
8503
|
-
if (
|
|
8624
|
+
const ctx = path28.join(setDir, rep.slug, "get_design_context.json");
|
|
8625
|
+
if (existsSync22(ctx)) fromEmission(envelopeText(ctx));
|
|
8626
|
+
const defs = path28.join(setDir, rep.slug, "get_variable_defs.json");
|
|
8627
|
+
if (existsSync22(defs)) fromDefs(envelopeText(defs));
|
|
8504
8628
|
}
|
|
8505
8629
|
return [...byFamily.entries()].map(([family, paired]) => {
|
|
8506
8630
|
const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
|
|
@@ -8511,8 +8635,8 @@ function symbolFontGlyphCount(setDir, reps) {
|
|
|
8511
8635
|
const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
|
|
8512
8636
|
const glyphs = /* @__PURE__ */ new Set();
|
|
8513
8637
|
for (const rep of reps) {
|
|
8514
|
-
const file =
|
|
8515
|
-
if (!
|
|
8638
|
+
const file = path28.join(setDir, rep, "get_metadata.json");
|
|
8639
|
+
if (!existsSync22(file)) continue;
|
|
8516
8640
|
try {
|
|
8517
8641
|
const text = JSON.parse(readFileSync18(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
8518
8642
|
for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
|
|
@@ -8540,8 +8664,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
8540
8664
|
const propRep = [];
|
|
8541
8665
|
const perRep = [];
|
|
8542
8666
|
for (const slug of repSlugs) {
|
|
8543
|
-
const f =
|
|
8544
|
-
if (!
|
|
8667
|
+
const f = path28.join(setDir, slug, "get_design_context.json");
|
|
8668
|
+
if (!existsSync22(f)) continue;
|
|
8545
8669
|
const code = envelopeText(f);
|
|
8546
8670
|
const props = /* @__PURE__ */ new Map();
|
|
8547
8671
|
for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
|
|
@@ -8566,8 +8690,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
8566
8690
|
}
|
|
8567
8691
|
const axisValuesBySlug = /* @__PURE__ */ new Map();
|
|
8568
8692
|
for (const slug of repSlugs) {
|
|
8569
|
-
const metaFile =
|
|
8570
|
-
if (!
|
|
8693
|
+
const metaFile = path28.join(setDir, slug, "get_metadata.json");
|
|
8694
|
+
if (!existsSync22(metaFile)) continue;
|
|
8571
8695
|
const name = symbolName(envelopeText(metaFile));
|
|
8572
8696
|
if (name === void 0) continue;
|
|
8573
8697
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -8666,8 +8790,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
8666
8790
|
const poses = [];
|
|
8667
8791
|
const missing = [];
|
|
8668
8792
|
for (const rep of manifest.reps) {
|
|
8669
|
-
const metaFile =
|
|
8670
|
-
if (!
|
|
8793
|
+
const metaFile = path28.join(setDir, rep.slug, "get_metadata.json");
|
|
8794
|
+
if (!existsSync22(metaFile)) {
|
|
8671
8795
|
missing.push(rep.slug);
|
|
8672
8796
|
continue;
|
|
8673
8797
|
}
|
|
@@ -8681,8 +8805,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
8681
8805
|
if (missing.length > 0) {
|
|
8682
8806
|
throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
|
|
8683
8807
|
}
|
|
8684
|
-
const setMeta =
|
|
8685
|
-
const latticeNames = manifest.latticeNames ?? (
|
|
8808
|
+
const setMeta = path28.join(setDir, "get_metadata.json");
|
|
8809
|
+
const latticeNames = manifest.latticeNames ?? (existsSync22(setMeta) ? [...envelopeText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
|
|
8686
8810
|
const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
|
|
8687
8811
|
const recordedFonts = recordedFontFamilies(setDir);
|
|
8688
8812
|
const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
|
|
@@ -8801,7 +8925,7 @@ RESOLVED FOR THIS RECORDING: it is ${opts.colorScheme}-mode truth, so pin color-
|
|
|
8801
8925
|
}
|
|
8802
8926
|
var PoseCompletenessError, kebab3, camel, pascal, RESERVED_PROPS, axisPropName, isStateAxis, DISMISS_NAMES, symbolName, STYLE_WEIGHTS2, styleWeight;
|
|
8803
8927
|
var init_brief = __esm({
|
|
8804
|
-
"
|
|
8928
|
+
"packages/generate/src/brief.ts"() {
|
|
8805
8929
|
"use strict";
|
|
8806
8930
|
init_src();
|
|
8807
8931
|
init_src4();
|
|
@@ -8855,11 +8979,11 @@ var init_brief = __esm({
|
|
|
8855
8979
|
}
|
|
8856
8980
|
});
|
|
8857
8981
|
|
|
8858
|
-
//
|
|
8859
|
-
import { existsSync as
|
|
8860
|
-
import
|
|
8982
|
+
// packages/generate/src/segments.ts
|
|
8983
|
+
import { existsSync as existsSync23, readFileSync as readFileSync19, readdirSync as readdirSync6 } from "node:fs";
|
|
8984
|
+
import path29 from "node:path";
|
|
8861
8985
|
function repText(set, rep, tool) {
|
|
8862
|
-
const env = JSON.parse(readFileSync19(
|
|
8986
|
+
const env = JSON.parse(readFileSync19(path29.join(set, rep, `${tool}.json`), "utf8"));
|
|
8863
8987
|
return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
|
|
8864
8988
|
}
|
|
8865
8989
|
function stripFigmaInstructions(emission) {
|
|
@@ -8920,16 +9044,16 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
|
|
|
8920
9044
|
function buildSegments(task, mode = "fenced") {
|
|
8921
9045
|
const SET = task.set;
|
|
8922
9046
|
let rawDefs = {};
|
|
8923
|
-
if (
|
|
8924
|
-
const text = envelopeFirstTextPart(JSON.parse(readFileSync19(
|
|
9047
|
+
if (existsSync23(path29.join(SET, "get_variable_defs.json"))) {
|
|
9048
|
+
const text = envelopeFirstTextPart(JSON.parse(readFileSync19(path29.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
|
|
8925
9049
|
try {
|
|
8926
9050
|
rawDefs = JSON.parse(text);
|
|
8927
9051
|
} catch {
|
|
8928
9052
|
}
|
|
8929
9053
|
} else {
|
|
8930
9054
|
for (const cfg of task.configs) {
|
|
8931
|
-
const f =
|
|
8932
|
-
if (!
|
|
9055
|
+
const f = path29.join(SET, cfg.rep, "get_variable_defs.json");
|
|
9056
|
+
if (!existsSync23(f)) continue;
|
|
8933
9057
|
const text = envelopeFirstTextPart(JSON.parse(readFileSync19(f, "utf8"))) || "{}";
|
|
8934
9058
|
try {
|
|
8935
9059
|
for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
|
|
@@ -8938,8 +9062,8 @@ function buildSegments(task, mode = "fenced") {
|
|
|
8938
9062
|
}
|
|
8939
9063
|
}
|
|
8940
9064
|
const emissionTexts = task.configs.map((cfg) => {
|
|
8941
|
-
const f =
|
|
8942
|
-
return
|
|
9065
|
+
const f = path29.join(SET, cfg.rep, "get_design_context.json");
|
|
9066
|
+
return existsSync23(f) ? envelopeFirstTextPart(JSON.parse(readFileSync19(f, "utf8"))) : "";
|
|
8943
9067
|
});
|
|
8944
9068
|
const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
|
|
8945
9069
|
const defs = JSON.stringify(map, null, 1);
|
|
@@ -8954,9 +9078,9 @@ ${defs}
|
|
|
8954
9078
|
for (const cfg of task.configs) {
|
|
8955
9079
|
const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
|
|
8956
9080
|
const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
|
|
8957
|
-
const assets = readdirSync6(
|
|
9081
|
+
const assets = readdirSync6(path29.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
|
|
8958
9082
|
\`\`\`svg
|
|
8959
|
-
${readFileSync19(
|
|
9083
|
+
${readFileSync19(path29.join(SET, cfg.rep, f), "utf8")}
|
|
8960
9084
|
\`\`\``).join("\n");
|
|
8961
9085
|
parts.push(`
|
|
8962
9086
|
## Config ${cfg.rep} \u2192 ${cfg.component} ${JSON.stringify(cfg.props)}
|
|
@@ -8981,20 +9105,20 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
|
|
|
8981
9105
|
} else {
|
|
8982
9106
|
parts.push(`
|
|
8983
9107
|
## Output format
|
|
8984
|
-
Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into
|
|
9108
|
+
Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path29.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.`);
|
|
8985
9109
|
}
|
|
8986
9110
|
return parts.join("\n");
|
|
8987
9111
|
}
|
|
8988
9112
|
var cssIdent;
|
|
8989
9113
|
var init_segments = __esm({
|
|
8990
|
-
"
|
|
9114
|
+
"packages/generate/src/segments.ts"() {
|
|
8991
9115
|
"use strict";
|
|
8992
9116
|
init_src();
|
|
8993
9117
|
cssIdent = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
8994
9118
|
}
|
|
8995
9119
|
});
|
|
8996
9120
|
|
|
8997
|
-
//
|
|
9121
|
+
// packages/generate/src/consent.ts
|
|
8998
9122
|
function formatConsentLines(e) {
|
|
8999
9123
|
return [
|
|
9000
9124
|
`engine=${e.engineId} model=${e.modelId} route=${e.providerRoute}`,
|
|
@@ -9004,12 +9128,12 @@ function formatConsentLines(e) {
|
|
|
9004
9128
|
];
|
|
9005
9129
|
}
|
|
9006
9130
|
var init_consent = __esm({
|
|
9007
|
-
"
|
|
9131
|
+
"packages/generate/src/consent.ts"() {
|
|
9008
9132
|
"use strict";
|
|
9009
9133
|
}
|
|
9010
9134
|
});
|
|
9011
9135
|
|
|
9012
|
-
//
|
|
9136
|
+
// packages/generate/src/adapter.ts
|
|
9013
9137
|
function resolveAdapterSettings(entry, overrides = {}) {
|
|
9014
9138
|
const notes = [];
|
|
9015
9139
|
let maxTokens = DEFAULT_MAX_TOKENS;
|
|
@@ -9041,16 +9165,16 @@ function resolveAdapterSettings(entry, overrides = {}) {
|
|
|
9041
9165
|
}
|
|
9042
9166
|
var DEFAULT_MAX_TOKENS;
|
|
9043
9167
|
var init_adapter = __esm({
|
|
9044
|
-
"
|
|
9168
|
+
"packages/generate/src/adapter.ts"() {
|
|
9045
9169
|
"use strict";
|
|
9046
9170
|
DEFAULT_MAX_TOKENS = 2e4;
|
|
9047
9171
|
}
|
|
9048
9172
|
});
|
|
9049
9173
|
|
|
9050
|
-
//
|
|
9174
|
+
// packages/generate/src/bundle-emit.ts
|
|
9051
9175
|
import { createHash as createHash4 } from "node:crypto";
|
|
9052
|
-
import { copyFileSync, existsSync as
|
|
9053
|
-
import
|
|
9176
|
+
import { copyFileSync, existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync20, readdirSync as readdirSync7, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9177
|
+
import path30 from "node:path";
|
|
9054
9178
|
function pinFromConfigs(configs) {
|
|
9055
9179
|
const domains = /* @__PURE__ */ new Map();
|
|
9056
9180
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -9119,9 +9243,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
9119
9243
|
const notices = [];
|
|
9120
9244
|
const licenseTexts = /* @__PURE__ */ new Map();
|
|
9121
9245
|
for (const face of faces) {
|
|
9122
|
-
const src =
|
|
9123
|
-
const target = `./fonts/${
|
|
9124
|
-
const format = FONT_FORMATS[
|
|
9246
|
+
const src = path30.join(cacheDir, path30.basename(face.file));
|
|
9247
|
+
const target = `./fonts/${path30.basename(face.file)}`;
|
|
9248
|
+
const format = FONT_FORMATS[path30.extname(face.file).toLowerCase()] ?? "truetype";
|
|
9125
9249
|
const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
|
|
9126
9250
|
const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
|
|
9127
9251
|
const license = normalizeFontLicense(face.license);
|
|
@@ -9151,14 +9275,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
9151
9275
|
`/* ${decl} */`
|
|
9152
9276
|
);
|
|
9153
9277
|
}
|
|
9154
|
-
} else if (
|
|
9155
|
-
mkdirSync7(
|
|
9156
|
-
copyFileSync(src,
|
|
9278
|
+
} else if (existsSync24(src) && createHash4("sha256").update(readFileSync20(src)).digest("hex") === face.sha256) {
|
|
9279
|
+
mkdirSync7(path30.join(bundleDir, "fonts"), { recursive: true });
|
|
9280
|
+
copyFileSync(src, path30.join(bundleDir, "fonts", path30.basename(face.file)));
|
|
9157
9281
|
licenseTexts.set(terms.file, terms.text);
|
|
9158
9282
|
const upstream = upstreamAttribution(face);
|
|
9159
9283
|
notices.push(
|
|
9160
9284
|
"",
|
|
9161
|
-
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${
|
|
9285
|
+
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path30.basename(face.file)}`,
|
|
9162
9286
|
` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
|
|
9163
9287
|
` source: ${face.source}`,
|
|
9164
9288
|
` sha256: ${face.sha256}`,
|
|
@@ -9172,9 +9296,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
9172
9296
|
}
|
|
9173
9297
|
if (lines.length === 0) return null;
|
|
9174
9298
|
if (notices.length > 0) {
|
|
9175
|
-
const fontsDir =
|
|
9176
|
-
for (const [file, text] of licenseTexts) writeFileSync11(
|
|
9177
|
-
writeFileSync11(
|
|
9299
|
+
const fontsDir = path30.join(bundleDir, "fonts");
|
|
9300
|
+
for (const [file, text] of licenseTexts) writeFileSync11(path30.join(fontsDir, file), text);
|
|
9301
|
+
writeFileSync11(path30.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
|
|
9178
9302
|
`);
|
|
9179
9303
|
header.push(
|
|
9180
9304
|
"/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
|
|
@@ -9186,8 +9310,8 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
9186
9310
|
`;
|
|
9187
9311
|
}
|
|
9188
9312
|
function countLatticeSymbols(setDir) {
|
|
9189
|
-
const manifestFile =
|
|
9190
|
-
if (
|
|
9313
|
+
const manifestFile = path30.join(setDir, "recording-set.json");
|
|
9314
|
+
if (existsSync24(manifestFile)) {
|
|
9191
9315
|
try {
|
|
9192
9316
|
const lattice = JSON.parse(readFileSync20(manifestFile, "utf8")).latticeNames;
|
|
9193
9317
|
if (lattice !== void 0 && lattice.length > 0) return lattice.length;
|
|
@@ -9195,9 +9319,9 @@ function countLatticeSymbols(setDir) {
|
|
|
9195
9319
|
}
|
|
9196
9320
|
}
|
|
9197
9321
|
const files = [
|
|
9198
|
-
|
|
9199
|
-
...
|
|
9200
|
-
].filter((f) =>
|
|
9322
|
+
path30.join(setDir, "get_metadata.json"),
|
|
9323
|
+
...existsSync24(setDir) ? readdirSync7(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path30.join(setDir, f)) : []
|
|
9324
|
+
].filter((f) => existsSync24(f));
|
|
9201
9325
|
if (files.length === 0) return null;
|
|
9202
9326
|
let count = 0;
|
|
9203
9327
|
for (const f of files) {
|
|
@@ -9209,21 +9333,21 @@ function countLatticeSymbols(setDir) {
|
|
|
9209
9333
|
function recordingSetHash(setDir, configs) {
|
|
9210
9334
|
const relPaths = [];
|
|
9211
9335
|
for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
|
|
9212
|
-
if (
|
|
9336
|
+
if (existsSync24(path30.join(setDir, name))) relPaths.push(name);
|
|
9213
9337
|
}
|
|
9214
9338
|
for (const cfg of configs) {
|
|
9215
9339
|
for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
|
|
9216
|
-
if (
|
|
9340
|
+
if (existsSync24(path30.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
|
|
9217
9341
|
}
|
|
9218
|
-
if (
|
|
9219
|
-
for (const asset of readdirSync7(
|
|
9342
|
+
if (existsSync24(path30.join(setDir, cfg.rep))) {
|
|
9343
|
+
for (const asset of readdirSync7(path30.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
|
|
9220
9344
|
relPaths.push(`${cfg.rep}/${asset}`);
|
|
9221
9345
|
}
|
|
9222
9346
|
}
|
|
9223
9347
|
}
|
|
9224
9348
|
return hashRecordingSet(
|
|
9225
9349
|
relPaths,
|
|
9226
|
-
(p) => new Uint8Array(readFileSync20(
|
|
9350
|
+
(p) => new Uint8Array(readFileSync20(path30.join(setDir, p))),
|
|
9227
9351
|
(chunks) => {
|
|
9228
9352
|
const h = createHash4("sha256");
|
|
9229
9353
|
for (const c of chunks) h.update(c);
|
|
@@ -9246,7 +9370,7 @@ function emitBundleV1(opts) {
|
|
|
9246
9370
|
const lattice = countLatticeSymbols(opts.task.set);
|
|
9247
9371
|
const interaction = opts.behaviors.filter((b) => !b.id.startsWith("prelude:"));
|
|
9248
9372
|
const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
|
|
9249
|
-
const cssFiles = ["styles.css", "tokens.css"].map((f) =>
|
|
9373
|
+
const cssFiles = ["styles.css", "tokens.css"].map((f) => path30.join(opts.bundleDir, f)).filter((f) => existsSync24(f));
|
|
9250
9374
|
const families = cssFontFamilies(cssFiles.map((f) => readFileSync20(f, "utf8")).join("\n"));
|
|
9251
9375
|
const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
|
|
9252
9376
|
family: f.family,
|
|
@@ -9276,7 +9400,7 @@ function emitBundleV1(opts) {
|
|
|
9276
9400
|
// resolvable via verify's --set override).
|
|
9277
9401
|
path: (() => {
|
|
9278
9402
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
9279
|
-
const rel =
|
|
9403
|
+
const rel = path30.relative(base, opts.task.set);
|
|
9280
9404
|
return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
|
|
9281
9405
|
})(),
|
|
9282
9406
|
component: opts.componentName,
|
|
@@ -9300,12 +9424,12 @@ function emitBundleV1(opts) {
|
|
|
9300
9424
|
})
|
|
9301
9425
|
};
|
|
9302
9426
|
const written = [];
|
|
9303
|
-
const manifestPath2 =
|
|
9427
|
+
const manifestPath2 = path30.join(opts.bundleDir, "component.json");
|
|
9304
9428
|
writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
|
|
9305
9429
|
`);
|
|
9306
9430
|
written.push(manifestPath2);
|
|
9307
|
-
const stylesPath =
|
|
9308
|
-
if (
|
|
9431
|
+
const stylesPath = path30.join(opts.bundleDir, "styles.css");
|
|
9432
|
+
if (existsSync24(stylesPath)) {
|
|
9309
9433
|
const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
|
|
9310
9434
|
const current = readFileSync20(stylesPath, "utf8");
|
|
9311
9435
|
const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
|
|
@@ -9313,8 +9437,8 @@ function emitBundleV1(opts) {
|
|
|
9313
9437
|
${stripped}`);
|
|
9314
9438
|
written.push(stylesPath);
|
|
9315
9439
|
}
|
|
9316
|
-
const fontsCssPath =
|
|
9317
|
-
rmSync3(
|
|
9440
|
+
const fontsCssPath = path30.join(opts.bundleDir, "fonts.css");
|
|
9441
|
+
rmSync3(path30.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
|
|
9318
9442
|
rmSync3(fontsCssPath, { force: true });
|
|
9319
9443
|
const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
|
|
9320
9444
|
if (fontsCss !== null) {
|
|
@@ -9327,7 +9451,7 @@ ${stripped}`);
|
|
|
9327
9451
|
}
|
|
9328
9452
|
var FONT_FORMATS, BARS, NOTICE_PREAMBLE, OFL_1_1_TEXT, UFL_1_0_TEXT, APACHE_2_0_TEXT, REDISTRIBUTABLE;
|
|
9329
9453
|
var init_bundle_emit = __esm({
|
|
9330
|
-
"
|
|
9454
|
+
"packages/generate/src/bundle-emit.ts"() {
|
|
9331
9455
|
"use strict";
|
|
9332
9456
|
init_src6();
|
|
9333
9457
|
init_src();
|
|
@@ -9742,9 +9866,9 @@ DEALINGS IN THE FONT SOFTWARE.
|
|
|
9742
9866
|
}
|
|
9743
9867
|
});
|
|
9744
9868
|
|
|
9745
|
-
//
|
|
9869
|
+
// packages/generate/src/index.ts
|
|
9746
9870
|
var init_src7 = __esm({
|
|
9747
|
-
"
|
|
9871
|
+
"packages/generate/src/index.ts"() {
|
|
9748
9872
|
"use strict";
|
|
9749
9873
|
init_engine();
|
|
9750
9874
|
init_engine_curated();
|
|
@@ -9758,7 +9882,7 @@ var init_src7 = __esm({
|
|
|
9758
9882
|
}
|
|
9759
9883
|
});
|
|
9760
9884
|
|
|
9761
|
-
//
|
|
9885
|
+
// packages/cli/src/commands/fonts.ts
|
|
9762
9886
|
var fonts_exports = {};
|
|
9763
9887
|
__export(fonts_exports, {
|
|
9764
9888
|
DEFAULT_FONT_CACHE: () => DEFAULT_FONT_CACHE,
|
|
@@ -9768,8 +9892,8 @@ __export(fonts_exports, {
|
|
|
9768
9892
|
runFontsResolveSet: () => runFontsResolveSet,
|
|
9769
9893
|
runFontsStatus: () => runFontsStatus
|
|
9770
9894
|
});
|
|
9771
|
-
import { existsSync as
|
|
9772
|
-
import
|
|
9895
|
+
import { existsSync as existsSync25, readFileSync as readFileSync21 } from "node:fs";
|
|
9896
|
+
import path31 from "node:path";
|
|
9773
9897
|
async function runFontsResolve(opts) {
|
|
9774
9898
|
const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
|
|
9775
9899
|
emitData(opts, result, () => {
|
|
@@ -9784,7 +9908,7 @@ async function runFontsResolve(opts) {
|
|
|
9784
9908
|
}
|
|
9785
9909
|
}
|
|
9786
9910
|
async function runFontsResolveSet(opts) {
|
|
9787
|
-
const setDir =
|
|
9911
|
+
const setDir = path31.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
|
|
9788
9912
|
let needs = [];
|
|
9789
9913
|
try {
|
|
9790
9914
|
needs = recordedFontNeeds(setDir);
|
|
@@ -9792,19 +9916,19 @@ async function runFontsResolveSet(opts) {
|
|
|
9792
9916
|
fail(opts, ExitCode.InputValidation, {
|
|
9793
9917
|
error: `cannot read font needs from ${setDir}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
9794
9918
|
code: "font-needs-unreadable",
|
|
9795
|
-
remediation:
|
|
9919
|
+
remediation: `Point --set at a directory recorded with \`${tendrilCommand("record plan")}\` (it carries recording-set.json), or resolve a family explicitly: ${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}.`
|
|
9796
9920
|
});
|
|
9797
9921
|
}
|
|
9798
9922
|
if (needs.length === 0) {
|
|
9799
9923
|
fail(opts, ExitCode.InputValidation, {
|
|
9800
9924
|
error: `recording set ${setDir} declares no detectable font families`,
|
|
9801
9925
|
code: "font-needs-empty",
|
|
9802
|
-
remediation:
|
|
9926
|
+
remediation: `Resolve the family the design uses explicitly: ${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}.`
|
|
9803
9927
|
});
|
|
9804
9928
|
}
|
|
9805
9929
|
const inCache = new Set(resolvedFontFamilies(opts.cacheDir).flatMap((f) => f.weights.map((w) => `${f.family.toLowerCase()}:${w}`)));
|
|
9806
9930
|
const unstamped = new Map(facesMissingLicense(opts.cacheDir).map((f) => [`${f.family.toLowerCase()}:${f.weight}`, f.sha256]));
|
|
9807
|
-
const
|
|
9931
|
+
const cached2 = [];
|
|
9808
9932
|
const resolved = [];
|
|
9809
9933
|
const failures = [];
|
|
9810
9934
|
const relicensed = [];
|
|
@@ -9817,7 +9941,7 @@ async function runFontsResolveSet(opts) {
|
|
|
9817
9941
|
const key = `${need.family.toLowerCase()}:${w}`;
|
|
9818
9942
|
if (!inCache.has(key)) missing.push(w);
|
|
9819
9943
|
else if (unstamped.has(key)) toStamp.push(w);
|
|
9820
|
-
else
|
|
9944
|
+
else cached2.push({ family: need.family, weight: w });
|
|
9821
9945
|
}
|
|
9822
9946
|
if (missing.length > 0) {
|
|
9823
9947
|
const result = await resolveFonts(need.family, missing, opts.cacheDir);
|
|
@@ -9834,10 +9958,10 @@ async function runFontsResolveSet(opts) {
|
|
|
9834
9958
|
unlicensed.push(...result.failures);
|
|
9835
9959
|
}
|
|
9836
9960
|
}
|
|
9837
|
-
emitData(opts, { set: setDir, needs, cached, resolved, relicensed, unlicensed, failures }, () => {
|
|
9961
|
+
emitData(opts, { set: setDir, needs, cached: cached2, resolved, relicensed, unlicensed, failures }, () => {
|
|
9838
9962
|
process.stdout.write(`set declares: ${needs.map((n) => `${n.family} (${n.weights.join(", ")})`).join(" \xB7 ")}
|
|
9839
9963
|
`);
|
|
9840
|
-
for (const f of
|
|
9964
|
+
for (const f of cached2) process.stdout.write(`cached ${f.family} ${f.weight}
|
|
9841
9965
|
`);
|
|
9842
9966
|
for (const f of resolved) process.stdout.write(`resolved ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
|
|
9843
9967
|
`);
|
|
@@ -9857,16 +9981,16 @@ async function runFontsResolveSet(opts) {
|
|
|
9857
9981
|
}
|
|
9858
9982
|
}
|
|
9859
9983
|
function runFontsStatus(opts) {
|
|
9860
|
-
const manifestPath2 =
|
|
9861
|
-
if (!
|
|
9984
|
+
const manifestPath2 = path31.join(opts.cacheDir, "manifest.json");
|
|
9985
|
+
if (!existsSync25(manifestPath2)) {
|
|
9862
9986
|
fail(opts, ExitCode.FontsUnproven, {
|
|
9863
9987
|
error: `no font cache at ${opts.cacheDir}`,
|
|
9864
9988
|
code: "fonts-unresolved",
|
|
9865
|
-
remediation:
|
|
9989
|
+
remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
|
|
9866
9990
|
});
|
|
9867
9991
|
}
|
|
9868
9992
|
const faces = JSON.parse(readFileSync21(manifestPath2, "utf8"));
|
|
9869
|
-
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(
|
|
9993
|
+
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path31.resolve(opts.lock), opts.cacheDir) : null;
|
|
9870
9994
|
emitData(opts, { faces, lockVerdicts }, () => {
|
|
9871
9995
|
for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
|
|
9872
9996
|
`);
|
|
@@ -9897,7 +10021,10 @@ function runFontsAdd(opts) {
|
|
|
9897
10021
|
fail(opts, ExitCode.InputValidation, {
|
|
9898
10022
|
error: err instanceof Error ? err.message : String(err),
|
|
9899
10023
|
code: "font-add-failed",
|
|
9900
|
-
|
|
10024
|
+
// The face file is the user's own licensed asset: this command
|
|
10025
|
+
// can name every argument but that one, so it stays a marked
|
|
10026
|
+
// blank rather than a fabricated path that looks real.
|
|
10027
|
+
remediation: `Pass a font file you are licensed to use, by absolute path: ${tendrilCommand(`fonts add "${opts.family}" ${opts.weight} <absolute-path-to-face.woff2>`)}`
|
|
9901
10028
|
});
|
|
9902
10029
|
}
|
|
9903
10030
|
emitData(opts, face, () => {
|
|
@@ -9906,28 +10033,32 @@ function runFontsAdd(opts) {
|
|
|
9906
10033
|
});
|
|
9907
10034
|
}
|
|
9908
10035
|
var init_fonts = __esm({
|
|
9909
|
-
"
|
|
10036
|
+
"packages/cli/src/commands/fonts.ts"() {
|
|
9910
10037
|
"use strict";
|
|
9911
10038
|
init_src3();
|
|
9912
10039
|
init_src4();
|
|
9913
10040
|
init_src7();
|
|
9914
10041
|
init_output();
|
|
10042
|
+
init_invocation();
|
|
9915
10043
|
}
|
|
9916
10044
|
});
|
|
9917
10045
|
|
|
9918
|
-
//
|
|
10046
|
+
// packages/cli/src/font-guidance.ts
|
|
10047
|
+
import path32 from "node:path";
|
|
9919
10048
|
function fontsUnprovenRemediation(setDir) {
|
|
9920
|
-
|
|
10049
|
+
const set = setDir === void 0 ? void 0 : path32.resolve(setDir);
|
|
10050
|
+
if (set !== void 0) {
|
|
9921
10051
|
try {
|
|
9922
|
-
const needs = recordedFontNeeds(
|
|
10052
|
+
const needs = recordedFontNeeds(set);
|
|
9923
10053
|
if (needs.length > 0) {
|
|
9924
10054
|
const families = needs.map((n) => `"${n.family}"`).join(", ");
|
|
9925
|
-
return `Run
|
|
10055
|
+
return `Run \`${tendrilCommand(`fonts resolve --set ${set}`)}\` \u2014 the recording declares ${families}; faces land in ${DEFAULT_FONT_CACHE}.`;
|
|
9926
10056
|
}
|
|
9927
10057
|
} catch {
|
|
9928
10058
|
}
|
|
9929
10059
|
}
|
|
9930
|
-
|
|
10060
|
+
const target = set ?? "<recording-dir>";
|
|
10061
|
+
return `Run \`${tendrilCommand(`fonts resolve --set ${target}`)}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) to populate the font cache \u2014 the cache read is ${DEFAULT_FONT_CACHE}.`;
|
|
9931
10062
|
}
|
|
9932
10063
|
function taskFontFamilies(setDir) {
|
|
9933
10064
|
try {
|
|
@@ -9969,28 +10100,31 @@ function missingWeights(setDir, cacheDir) {
|
|
|
9969
10100
|
return gaps;
|
|
9970
10101
|
}
|
|
9971
10102
|
var init_font_guidance = __esm({
|
|
9972
|
-
"
|
|
10103
|
+
"packages/cli/src/font-guidance.ts"() {
|
|
9973
10104
|
"use strict";
|
|
9974
10105
|
init_src4();
|
|
9975
10106
|
init_src7();
|
|
10107
|
+
init_invocation();
|
|
9976
10108
|
}
|
|
9977
10109
|
});
|
|
9978
10110
|
|
|
9979
|
-
//
|
|
10111
|
+
// packages/cli/src/commands/verify.ts
|
|
9980
10112
|
var verify_exports = {};
|
|
9981
10113
|
__export(verify_exports, {
|
|
9982
10114
|
NO_ROLE_MANIFEST: () => NO_ROLE_MANIFEST,
|
|
9983
10115
|
ROLES_NOT_RESOLVED: () => ROLES_NOT_RESOLVED,
|
|
9984
10116
|
checkSummarySegments: () => checkSummarySegments,
|
|
9985
10117
|
compositionReport: () => compositionReport,
|
|
10118
|
+
eyeCheck: () => eyeCheck,
|
|
9986
10119
|
failureTally: () => failureTally,
|
|
9987
10120
|
foldConfigStatus: () => foldConfigStatus,
|
|
9988
10121
|
interactionCoverage: () => interactionCoverage,
|
|
10122
|
+
occlusionReport: () => occlusionReport,
|
|
9989
10123
|
resolveComposition: () => resolveComposition,
|
|
9990
10124
|
runVerify: () => runVerify
|
|
9991
10125
|
});
|
|
9992
|
-
import { existsSync as
|
|
9993
|
-
import
|
|
10126
|
+
import { existsSync as existsSync26, readFileSync as readFileSync22 } from "node:fs";
|
|
10127
|
+
import path33 from "node:path";
|
|
9994
10128
|
function interactionCoverage(behaviors) {
|
|
9995
10129
|
const interaction = behaviors.filter((b) => !b.id.startsWith("prelude:"));
|
|
9996
10130
|
return {
|
|
@@ -10038,9 +10172,18 @@ function checkSummarySegments(input) {
|
|
|
10038
10172
|
const tally = (rows) => `${rows.filter((r) => r.pass).length}/${rows.length}`;
|
|
10039
10173
|
const { availability } = input;
|
|
10040
10174
|
const composition = availability.unavailable !== void 0 ? `composition NOT CHECKED (${availability.short})` : `composition ${tally(input.structural)} structural, ${input.crops !== void 0 ? `${tally(input.crops)} crops` : "crops unavailable"} (roles: ${rolesSourceLabel(availability.roles)}${(availability.roles.narrowingAccepted ?? []).length > 0 ? `, ${(availability.roles.narrowingAccepted ?? []).length} narrowing(s) accepted: ${(availability.roles.narrowingAccepted ?? []).join(", ")}` : ""})`;
|
|
10041
|
-
const
|
|
10175
|
+
const occ = occlusionReport(input.occlusion);
|
|
10176
|
+
const occlusion = "unavailable" in occ ? `occlusion not applicable (${NO_OVERLAY_DECLARED})` : `occlusion ${tally(input.occlusion)}`;
|
|
10042
10177
|
return ` \xB7 ${composition} \xB7 ${occlusion}`;
|
|
10043
10178
|
}
|
|
10179
|
+
function occlusionReport(occlusion) {
|
|
10180
|
+
if (occlusion.length === 0) {
|
|
10181
|
+
return {
|
|
10182
|
+
unavailable: "the bundle declares no overlay (no open-state config and no prelude popover) \u2014 the sibling-occlusion check DOES NOT APPLY and nothing about overlay stacking was measured; an empty check list is this state, not a clean bill"
|
|
10183
|
+
};
|
|
10184
|
+
}
|
|
10185
|
+
return { checks: occlusion.length, passed: occlusion.filter((o) => o.pass).length };
|
|
10186
|
+
}
|
|
10044
10187
|
function compositionReport(input) {
|
|
10045
10188
|
const { availability, regions } = input;
|
|
10046
10189
|
if (availability.unavailable !== void 0) return { unavailable: availability.unavailable };
|
|
@@ -10057,6 +10200,13 @@ function compositionReport(input) {
|
|
|
10057
10200
|
crops: input.crops ?? { unavailable: cropsUnavailable }
|
|
10058
10201
|
};
|
|
10059
10202
|
}
|
|
10203
|
+
function eyeCheck(bundleDir) {
|
|
10204
|
+
return {
|
|
10205
|
+
command: tendrilCommand(`inspect "${bundleDir}"`),
|
|
10206
|
+
sheetPath: path33.join(bundleDir, "verify-evidence", "inspect.html"),
|
|
10207
|
+
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."
|
|
10208
|
+
};
|
|
10209
|
+
}
|
|
10060
10210
|
function failureTally(t) {
|
|
10061
10211
|
return `${t.configs.length} config(s), ${t.behaviors.length} behavior(s), ${t.structural.length + t.crops.length} composition check(s), ${t.occlusion.length} occlusion check(s) below the ${t.bar} bar${t.evidenceUnverified ? "; PLUS the interaction-evidence instrument gate failed (operability could not be verified \u2014 see FAIL interaction-evidence above)" : ""} \u2014 bundle written, verdict honest (Q4)`;
|
|
10062
10212
|
}
|
|
@@ -10065,7 +10215,7 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
10065
10215
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
10066
10216
|
const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
10067
10217
|
const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
|
|
10068
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
10218
|
+
const registry = Object.values(TASKS).find((t) => path33.resolve(t.set) === path33.resolve(setDir));
|
|
10069
10219
|
const authored = (() => {
|
|
10070
10220
|
if (registry !== void 0) return void 0;
|
|
10071
10221
|
try {
|
|
@@ -10074,7 +10224,7 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
10074
10224
|
fail(opts, ExitCode.InputValidation, {
|
|
10075
10225
|
error: `cannot author the verification task from ${setDir}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
10076
10226
|
code: "AUTHORING_FAILED",
|
|
10077
|
-
remediation:
|
|
10227
|
+
remediation: `Fix the recording set (duplicate/colliding poses fail authoring by design \u2014 check \`${tendrilCommand(`record status --set ${setDir}`)}\` and the set's variant names).`
|
|
10078
10228
|
});
|
|
10079
10229
|
}
|
|
10080
10230
|
})();
|
|
@@ -10097,18 +10247,18 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
10097
10247
|
}
|
|
10098
10248
|
async function runVerify(opts) {
|
|
10099
10249
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
10100
|
-
const setOverride = opts.set !== void 0 ?
|
|
10101
|
-
opts = { ...opts, bundleDir:
|
|
10102
|
-
if (!
|
|
10250
|
+
const setOverride = opts.set !== void 0 ? path33.resolve(callerCwd, opts.set) : void 0;
|
|
10251
|
+
opts = { ...opts, bundleDir: path33.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
|
|
10252
|
+
if (!existsSync26(opts.bundleDir)) {
|
|
10103
10253
|
fail(opts, ExitCode.InputValidation, {
|
|
10104
10254
|
error: `bundle directory not found: ${opts.bundleDir}`,
|
|
10105
10255
|
code: "bundle-missing",
|
|
10106
10256
|
remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
|
|
10107
10257
|
});
|
|
10108
10258
|
}
|
|
10109
|
-
const manifestPath2 =
|
|
10259
|
+
const manifestPath2 = path33.join(opts.bundleDir, "component.json");
|
|
10110
10260
|
let manifest;
|
|
10111
|
-
if (
|
|
10261
|
+
if (existsSync26(manifestPath2)) {
|
|
10112
10262
|
const { manifest: parsed, issues } = readBundleManifest(readFileSync22(manifestPath2, "utf8"));
|
|
10113
10263
|
if (issues.length > 0) {
|
|
10114
10264
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -10136,21 +10286,21 @@ async function runVerify(opts) {
|
|
|
10136
10286
|
task = registry;
|
|
10137
10287
|
} else if (manifest !== void 0) {
|
|
10138
10288
|
const resolveSetDir = (p) => {
|
|
10139
|
-
if (
|
|
10140
|
-
const fromRepo =
|
|
10141
|
-
if (
|
|
10142
|
-
return
|
|
10289
|
+
if (path33.isAbsolute(p)) return p;
|
|
10290
|
+
const fromRepo = path33.resolve(REPO_ROOT, p);
|
|
10291
|
+
if (existsSync26(fromRepo)) return fromRepo;
|
|
10292
|
+
return path33.resolve(callerCwd, p);
|
|
10143
10293
|
};
|
|
10144
10294
|
const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
|
|
10145
|
-
if (!
|
|
10295
|
+
if (!existsSync26(path33.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path33.resolve(t.set) === path33.resolve(setDir))) {
|
|
10146
10296
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
10147
10297
|
error: `recording set not found or unmanifested: ${setDir}`,
|
|
10148
10298
|
code: "recording-set-missing",
|
|
10149
10299
|
remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
|
|
10150
10300
|
});
|
|
10151
10301
|
}
|
|
10152
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
10153
|
-
if (registry !== void 0 && !
|
|
10302
|
+
const registry = Object.values(TASKS).find((t) => path33.resolve(t.set) === path33.resolve(setDir));
|
|
10303
|
+
if (registry !== void 0 && !existsSync26(path33.join(setDir, "recording-set.json"))) {
|
|
10154
10304
|
const recordedSlugs = registry.configs.map((c) => c.rep);
|
|
10155
10305
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
10156
10306
|
unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
@@ -10174,8 +10324,8 @@ async function runVerify(opts) {
|
|
|
10174
10324
|
warn(opts, `recording set content differs from the bundle's provenance stamp (${hash.slice(0, 12)}\u2026 vs ${manifest.provenance.recordingSet.hash.slice(0, 12)}\u2026) \u2014 scores apply to the CURRENT set`);
|
|
10175
10325
|
}
|
|
10176
10326
|
for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
|
|
10177
|
-
const p =
|
|
10178
|
-
if (!
|
|
10327
|
+
const p = path33.join(opts.bundleDir, name);
|
|
10328
|
+
if (!existsSync26(p)) continue;
|
|
10179
10329
|
const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync22(p)));
|
|
10180
10330
|
if (issues.length > 0) {
|
|
10181
10331
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -10214,7 +10364,7 @@ async function runVerify(opts) {
|
|
|
10214
10364
|
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)`);
|
|
10215
10365
|
}
|
|
10216
10366
|
const missing = task.configs.filter(
|
|
10217
|
-
(c) => !
|
|
10367
|
+
(c) => !existsSync26(path33.join(task.set, c.rep, "get_screenshot.json")) || !existsSync26(path33.join(task.set, c.rep, "get_metadata.json"))
|
|
10218
10368
|
);
|
|
10219
10369
|
if (missing.length > 0) {
|
|
10220
10370
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -10224,10 +10374,10 @@ async function runVerify(opts) {
|
|
|
10224
10374
|
});
|
|
10225
10375
|
}
|
|
10226
10376
|
const bar = BARS2[opts.bar];
|
|
10227
|
-
const evidenceDir =
|
|
10377
|
+
const evidenceDir = path33.join(opts.bundleDir, "verify-evidence");
|
|
10228
10378
|
const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
10229
10379
|
const quality = await checkBundleQuality(opts.bundleDir, task.entry, { dir: task.set, reps: task.configs.map((c) => c.rep) });
|
|
10230
|
-
const bundleCss = ["tokens.css", "styles.css"].map((f) =>
|
|
10380
|
+
const bundleCss = ["tokens.css", "styles.css"].map((f) => path33.join(opts.bundleDir, f)).filter((f) => existsSync26(f)).map((f) => readFileSync22(f, "utf8")).join("\n");
|
|
10231
10381
|
const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss);
|
|
10232
10382
|
const parity = await checkHoverParity(task, opts.bundleDir);
|
|
10233
10383
|
const behaviors = [...await checkBehaviors(task, opts.bundleDir), ...parity];
|
|
@@ -10312,6 +10462,10 @@ async function runVerify(opts) {
|
|
|
10312
10462
|
// silently inherited neither.
|
|
10313
10463
|
quality: { findings: quality.findings, tokensAbsent: quality.tokensAbsent },
|
|
10314
10464
|
occlusion,
|
|
10465
|
+
// The rows' STATE, which the rows alone cannot carry (see
|
|
10466
|
+
// occlusionReport): the agent channel had no counterpart to the
|
|
10467
|
+
// human line's "occlusion not applicable (no overlay declared)".
|
|
10468
|
+
occlusionCheck: occlusionReport(occlusion),
|
|
10315
10469
|
configs: statuses,
|
|
10316
10470
|
behaviors,
|
|
10317
10471
|
evidence: { dir: evidenceDir, files: "per config: <rep>-render.png, <rep>-ref.png, <rep>-diff.png" },
|
|
@@ -10323,15 +10477,7 @@ async function runVerify(opts) {
|
|
|
10323
10477
|
...certBlockedByAbsentInk.length > 0 ? { certBlockedByAbsentInk } : {},
|
|
10324
10478
|
...weightGaps.length > 0 ? { fontWeightGaps: weightGaps } : {},
|
|
10325
10479
|
...lcdTextEnabled() ? { environmentOverrides: ["enable-lcd-text"] } : {},
|
|
10326
|
-
|
|
10327
|
-
// footer, so MCP-driven agents — the documented path — were never
|
|
10328
|
-
// told the check exists and shipped without ever opening a sheet.
|
|
10329
|
-
// Structured here so every consumer sees it.
|
|
10330
|
-
eyeCheck: {
|
|
10331
|
-
command: `tendril inspect "${opts.bundleDir}"`,
|
|
10332
|
-
sheetPath: path31.join(opts.bundleDir, "verify-evidence", "inspect.html"),
|
|
10333
|
-
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."
|
|
10334
|
-
}
|
|
10480
|
+
eyeCheck: eyeCheck(opts.bundleDir)
|
|
10335
10481
|
};
|
|
10336
10482
|
emitData(opts, report, () => {
|
|
10337
10483
|
for (const s of statuses) {
|
|
@@ -10425,18 +10571,20 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
10425
10571
|
}
|
|
10426
10572
|
process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
|
|
10427
10573
|
`);
|
|
10428
|
-
const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) =>
|
|
10574
|
+
const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path33.join(opts.bundleDir, f)).filter((f) => existsSync26(f)).map((f) => readFileSync22(f, "utf8")).join("\n")));
|
|
10429
10575
|
if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
|
|
10430
10576
|
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)
|
|
10431
10577
|
`);
|
|
10432
10578
|
}
|
|
10433
10579
|
for (const g of weightGaps) {
|
|
10434
|
-
process.stdout.write(
|
|
10435
|
-
`);
|
|
10580
|
+
process.stdout.write(
|
|
10581
|
+
`fonts (weight gap): "${g.family}" declares ${g.declared.join(", ")} \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (advisory, never a gate). Fix: ${tendrilCommand(`fonts resolve --set ${task.set}`)}
|
|
10582
|
+
`
|
|
10583
|
+
);
|
|
10436
10584
|
}
|
|
10437
10585
|
process.stdout.write(`evidence: ${evidenceDir} (render/ref/diff per config)
|
|
10438
10586
|
`);
|
|
10439
|
-
process.stdout.write(`eye check:
|
|
10587
|
+
process.stdout.write(`eye check: ${report.eyeCheck.command} \u2014 magnified recorded-vs-rendered crops of every small node; scores cannot see shape (re-run after every verify)
|
|
10440
10588
|
`);
|
|
10441
10589
|
});
|
|
10442
10590
|
if (opts.bar === "cert" && substitutedFamilies.length > 0) {
|
|
@@ -10476,9 +10624,9 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
10476
10624
|
process.exitCode = ExitCode.VerificationFailed;
|
|
10477
10625
|
}
|
|
10478
10626
|
}
|
|
10479
|
-
var BARS2, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES;
|
|
10627
|
+
var BARS2, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES, NO_OVERLAY_DECLARED;
|
|
10480
10628
|
var init_verify = __esm({
|
|
10481
|
-
"
|
|
10629
|
+
"packages/cli/src/commands/verify.ts"() {
|
|
10482
10630
|
"use strict";
|
|
10483
10631
|
init_src3();
|
|
10484
10632
|
init_src6();
|
|
@@ -10487,6 +10635,7 @@ var init_verify = __esm({
|
|
|
10487
10635
|
init_src4();
|
|
10488
10636
|
init_environment();
|
|
10489
10637
|
init_font_guidance();
|
|
10638
|
+
init_invocation();
|
|
10490
10639
|
init_output();
|
|
10491
10640
|
BARS2 = {
|
|
10492
10641
|
pass: { sim: 0.95, ink: 0.95 },
|
|
@@ -10501,45 +10650,46 @@ var init_verify = __esm({
|
|
|
10501
10650
|
unavailable: "this run never resolved a role manifest \u2014 the legacy --task adapter and pre-manifest reference sets skip the set's roles block entirely, so composition (structural + crop) was NEVER CHECKED and the set was never asked what it declares; nothing here says the main renders the SHIPPED part modules rather than a pixel-identical re-implementation"
|
|
10502
10651
|
});
|
|
10503
10652
|
UNSTAMPED_ROLES = "unstamped";
|
|
10653
|
+
NO_OVERLAY_DECLARED = "no overlay declared";
|
|
10504
10654
|
}
|
|
10505
10655
|
});
|
|
10506
10656
|
|
|
10507
|
-
//
|
|
10657
|
+
// packages/cli/src/commands/engine.ts
|
|
10508
10658
|
var engine_exports = {};
|
|
10509
10659
|
__export(engine_exports, {
|
|
10510
10660
|
runEngineBrief: () => runEngineBrief,
|
|
10511
10661
|
runEngineScore: () => runEngineScore
|
|
10512
10662
|
});
|
|
10513
|
-
import { appendFileSync, existsSync as
|
|
10514
|
-
import
|
|
10663
|
+
import { appendFileSync, existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "node:fs";
|
|
10664
|
+
import path34 from "node:path";
|
|
10515
10665
|
function resolveEngineTask(opts, callerCwd) {
|
|
10516
|
-
const asPath =
|
|
10517
|
-
const isSet =
|
|
10666
|
+
const asPath = path34.resolve(callerCwd, opts.taskOrSet);
|
|
10667
|
+
const isSet = existsSync27(path34.join(asPath, "recording-set.json"));
|
|
10518
10668
|
const registry = TASKS[opts.taskOrSet];
|
|
10519
|
-
if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, disclosures: [], interactionEvidence: [] };
|
|
10669
|
+
if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: [] };
|
|
10520
10670
|
if (isSet) {
|
|
10521
10671
|
try {
|
|
10522
10672
|
const authored = authorTaskFromSet(asPath);
|
|
10523
10673
|
for (const d of authored.disclosures) warn(opts, d);
|
|
10524
|
-
return { task: authored.task, name:
|
|
10674
|
+
return { task: authored.task, name: path34.basename(asPath), ref: asPath, disclosures: authored.disclosures, interactionEvidence: authored.api.interactionEvidence, apiPin: { props: authored.api.apiPin.props, forcedStates: authored.api.apiPin.forcedStates } };
|
|
10525
10675
|
} catch (err) {
|
|
10526
10676
|
fail(opts, ExitCode.InputValidation, {
|
|
10527
10677
|
error: `cannot author a task from ${asPath}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
10528
10678
|
code: "AUTHORING_FAILED",
|
|
10529
|
-
remediation:
|
|
10679
|
+
remediation: `Complete the recording set (${tendrilCommand(`record status --set ${asPath}`)}) or use a reference task name.`
|
|
10530
10680
|
});
|
|
10531
10681
|
}
|
|
10532
10682
|
}
|
|
10533
10683
|
fail(opts, ExitCode.InputValidation, {
|
|
10534
10684
|
error: `"${opts.taskOrSet}" is neither a reference task nor a recorded set`,
|
|
10535
10685
|
code: "UNKNOWN_RECORDED_INPUT",
|
|
10536
|
-
remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with
|
|
10686
|
+
remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with \`${tendrilCommand("record plan")}\`.`
|
|
10537
10687
|
});
|
|
10538
10688
|
}
|
|
10539
10689
|
function runEngineBrief(opts) {
|
|
10540
10690
|
requireEntitlement(opts);
|
|
10541
10691
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
10542
|
-
const { task, name, disclosures } = resolveEngineTask(opts, callerCwd);
|
|
10692
|
+
const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
|
|
10543
10693
|
const bar = BARS3[opts.bar];
|
|
10544
10694
|
const disclosureBlock = disclosures.length > 0 ? `
|
|
10545
10695
|
|
|
@@ -10548,8 +10698,8 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
10548
10698
|
const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock;
|
|
10549
10699
|
const segments = buildSegments(task, "files");
|
|
10550
10700
|
let notRecorded;
|
|
10551
|
-
const manifestPath2 =
|
|
10552
|
-
if (
|
|
10701
|
+
const manifestPath2 = path34.join(task.set, "recording-set.json");
|
|
10702
|
+
if (existsSync27(manifestPath2)) {
|
|
10553
10703
|
notRecorded = JSON.parse(readFileSync23(manifestPath2, "utf8")).notRecorded;
|
|
10554
10704
|
}
|
|
10555
10705
|
const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
|
|
@@ -10558,7 +10708,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
10558
10708
|
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.
|
|
10559
10709
|
${notRecorded}` : "";
|
|
10560
10710
|
let fontProvisioning;
|
|
10561
|
-
if (
|
|
10711
|
+
if (existsSync27(manifestPath2)) {
|
|
10562
10712
|
const missingFams = unprovisionedFamilies(task.set);
|
|
10563
10713
|
const unprovided = unprovisionedFaces(task.set);
|
|
10564
10714
|
const weightOnly = missingFams.length === 0;
|
|
@@ -10566,16 +10716,17 @@ ${notRecorded}` : "";
|
|
|
10566
10716
|
const names = unprovided.map((f) => `'${f}'`).join(", ");
|
|
10567
10717
|
const PROPRIETARY = /^(sf pro|sf compact|sf mono|new york|pingfang|segoe ui|helvetica neue|proxima nova|avenir)/i;
|
|
10568
10718
|
const allProprietary = unprovided.every((f) => PROPRIETARY.test(f.trim()));
|
|
10719
|
+
const resolveCommand = tendrilCommand(`fonts resolve --set ${task.set}`);
|
|
10569
10720
|
fontProvisioning = {
|
|
10570
10721
|
unprovided,
|
|
10571
10722
|
question: {
|
|
10572
10723
|
prompt: weightOnly ? `This design implies font ${unprovided.length === 1 ? "weight" : "weights"} the local kit lacks: ${names} (the ${missingFams.length === 0 && unprovided.length === 1 ? "family itself is" : "families themselves are"} provisioned). How should it be handled?` : `This design's text uses ${names}, which is not in the local font kit. How should it be handled?`,
|
|
10573
10724
|
options: [
|
|
10574
|
-
allProprietary && !weightOnly ? `(Recommended) ${names} ${unprovided.length === 1 ? "is a proprietary face" : "are proprietary faces"} \u2014 Google Fonts cannot serve ${unprovided.length === 1 ? "it" : "them"}, so skip \`fonts resolve\`: run
|
|
10725
|
+
allProprietary && !weightOnly ? `(Recommended) ${names} ${unprovided.length === 1 ? "is a proprietary face" : "are proprietary faces"} \u2014 Google Fonts cannot serve ${unprovided.length === 1 ? "it" : "them"}, so skip \`fonts resolve\`: run \`${tendrilCommand('fonts add "<Family>" <weight> <file>')}\` with the .woff2/.ttf/.otf you hold a licence for (the file never leaves this machine), then re-run this brief \u2014 exact text fidelity.` : `(Recommended) Run \`${resolveCommand}\` \u2014 open-source families are fetched automatically. Any face that fails there is one you license privately: run \`${tendrilCommand('fonts add "<Family>" <weight> <file>')}\` with the .woff2/.ttf/.otf you hold (the file never leaves this machine). Then re-run this brief \u2014 exact text fidelity.`,
|
|
10575
10726
|
weightOnly ? "Continue with nearest-weight rendering: the family is provisioned, certification remains possible, and the gap rides the report as an advisory (fontWeightGaps); resolving removes it." : 'Continue with a substitute face: generation proceeds in a provided family, no config can score "certified" under it (cert-bar runs exit fonts-unproven after their report), the substitution is disclosed, and small text differences are expected and not fixable from CSS.'
|
|
10576
10727
|
]
|
|
10577
10728
|
},
|
|
10578
|
-
nonInteractive: weightOnly ? `Run
|
|
10729
|
+
nonInteractive: weightOnly ? `Run \`${resolveCommand}\` if quick, or continue \u2014 the weight gap is an advisory, never a gate.` : opts.bar === "cert" ? `Resolve the families first (\`${resolveCommand}\`) \u2014 this loop targets the cert bar, and scoring exits fonts-unproven under a substitute, so continuing without resolving is a dead end.` : "Continue with the substitute and state the substitution in your report."
|
|
10579
10730
|
};
|
|
10580
10731
|
}
|
|
10581
10732
|
}
|
|
@@ -10583,8 +10734,8 @@ ${notRecorded}` : "";
|
|
|
10583
10734
|
|
|
10584
10735
|
=== TASK PAYLOAD (recorded truth, verbatim) ===
|
|
10585
10736
|
${segments}`;
|
|
10586
|
-
const payloadFile =
|
|
10587
|
-
mkdirSync8(
|
|
10737
|
+
const payloadFile = path34.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
|
|
10738
|
+
mkdirSync8(path34.dirname(payloadFile), { recursive: true });
|
|
10588
10739
|
writeFileSync12(payloadFile, payload);
|
|
10589
10740
|
emitData(
|
|
10590
10741
|
opts,
|
|
@@ -10613,7 +10764,7 @@ ${segments}`;
|
|
|
10613
10764
|
"Settle the proposer model (see modelSelection \u2014 ask the user when one is present), then:",
|
|
10614
10765
|
`Read ${payloadFile} completely \u2014 it is the system brief plus every recorded config's emission, box, and assets.`,
|
|
10615
10766
|
`Write the complete bundle files (${task.entry}, styles.css, optional tokens.css) into a candidate directory.`,
|
|
10616
|
-
`Run
|
|
10767
|
+
`Run \`${tendrilCommand(`engine score ${ref} <candidateDir> --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"} --json`)}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model.`,
|
|
10617
10768
|
"Apply the returned feedback and re-score. Stop when all checks pass or two consecutive scores fail to improve.",
|
|
10618
10769
|
"Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
|
|
10619
10770
|
]
|
|
@@ -10626,18 +10777,23 @@ ${segments}`;
|
|
|
10626
10777
|
}
|
|
10627
10778
|
);
|
|
10628
10779
|
}
|
|
10780
|
+
function appendScoreHistory(candidateDir, entry) {
|
|
10781
|
+
appendFileSync(path34.join(candidateDir, "score-history.jsonl"), `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry })}
|
|
10782
|
+
`);
|
|
10783
|
+
}
|
|
10629
10784
|
async function runEngineScore(opts) {
|
|
10630
10785
|
requireEntitlement(opts);
|
|
10631
10786
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
10632
|
-
const candidateDir =
|
|
10787
|
+
const candidateDir = path34.resolve(callerCwd, opts.candidateDir);
|
|
10633
10788
|
const { task, name, apiPin, interactionEvidence } = resolveEngineTask(opts, callerCwd);
|
|
10634
|
-
if (!
|
|
10789
|
+
if (!existsSync27(candidateDir)) {
|
|
10635
10790
|
fail(opts, ExitCode.InputValidation, {
|
|
10636
10791
|
error: `candidate directory not found: ${candidateDir}`,
|
|
10637
10792
|
code: "candidate-missing",
|
|
10638
10793
|
remediation: `Write ${task.entry} and styles.css into a directory and pass it.`
|
|
10639
10794
|
});
|
|
10640
10795
|
}
|
|
10796
|
+
appendScoreHistory(candidateDir, { event: "round-start", bar: opts.bar });
|
|
10641
10797
|
if (!fontsResolved()) {
|
|
10642
10798
|
fail(opts, ExitCode.FontsUnproven, {
|
|
10643
10799
|
error: "resolved font cache absent \u2014 scoring under system font substitution is refused",
|
|
@@ -10655,10 +10811,10 @@ async function runEngineScore(opts) {
|
|
|
10655
10811
|
for (const g of missingWeights(task.set)) {
|
|
10656
10812
|
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)`);
|
|
10657
10813
|
}
|
|
10658
|
-
if (opts.rebind !== true &&
|
|
10814
|
+
if (opts.rebind !== true && existsSync27(path34.join(candidateDir, "component.json"))) {
|
|
10659
10815
|
const prior = (() => {
|
|
10660
10816
|
try {
|
|
10661
|
-
const read = readBundleManifest(readFileSync23(
|
|
10817
|
+
const read = readBundleManifest(readFileSync23(path34.join(candidateDir, "component.json"), "utf8"));
|
|
10662
10818
|
return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
|
|
10663
10819
|
} catch {
|
|
10664
10820
|
return { unreadable: true };
|
|
@@ -10680,7 +10836,7 @@ async function runEngineScore(opts) {
|
|
|
10680
10836
|
}
|
|
10681
10837
|
}
|
|
10682
10838
|
const bar = BARS3[opts.bar];
|
|
10683
|
-
const evidenceDir =
|
|
10839
|
+
const evidenceDir = path34.join(candidateDir, "verify-evidence");
|
|
10684
10840
|
const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
10685
10841
|
const parity = await checkHoverParity(task, candidateDir);
|
|
10686
10842
|
const behaviors = [...await checkBehaviors(task, candidateDir), ...parity];
|
|
@@ -10723,11 +10879,7 @@ METRIC DEADBAND (read before iterating on near-misses): the scored similarity/in
|
|
|
10723
10879
|
environment: environmentStamp(taskFontFamilies(task.set)),
|
|
10724
10880
|
substitutedFamilies
|
|
10725
10881
|
});
|
|
10726
|
-
|
|
10727
|
-
path32.join(candidateDir, "score-history.jsonl"),
|
|
10728
|
-
`${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), bar: opts.bar, pass: obj[0], total, certified: certifiedReps.length, floor: obj[1], mean: obj[2] })}
|
|
10729
|
-
`
|
|
10730
|
-
);
|
|
10882
|
+
appendScoreHistory(candidateDir, { event: "round-scored", bar: opts.bar, pass: obj[0], total, certified: certifiedReps.length, floor: obj[1], mean: obj[2] });
|
|
10731
10883
|
emitData(
|
|
10732
10884
|
opts,
|
|
10733
10885
|
{
|
|
@@ -10793,7 +10945,7 @@ ${obj[0]}/${total} \xB7 certified ${certifiedReps.length}/${total} \xB7 floor=${
|
|
|
10793
10945
|
}
|
|
10794
10946
|
var BARS3;
|
|
10795
10947
|
var init_engine2 = __esm({
|
|
10796
|
-
"
|
|
10948
|
+
"packages/cli/src/commands/engine.ts"() {
|
|
10797
10949
|
"use strict";
|
|
10798
10950
|
init_src3();
|
|
10799
10951
|
init_src7();
|
|
@@ -10802,6 +10954,7 @@ var init_engine2 = __esm({
|
|
|
10802
10954
|
init_font_guidance();
|
|
10803
10955
|
init_src4();
|
|
10804
10956
|
init_output();
|
|
10957
|
+
init_invocation();
|
|
10805
10958
|
init_entitlement();
|
|
10806
10959
|
init_verify();
|
|
10807
10960
|
init_src4();
|
|
@@ -10812,16 +10965,16 @@ var init_engine2 = __esm({
|
|
|
10812
10965
|
}
|
|
10813
10966
|
});
|
|
10814
10967
|
|
|
10815
|
-
//
|
|
10968
|
+
// packages/cli/src/commands/codeconnect.ts
|
|
10816
10969
|
var codeconnect_exports = {};
|
|
10817
10970
|
__export(codeconnect_exports, {
|
|
10818
10971
|
runCodeConnect: () => runCodeConnect
|
|
10819
10972
|
});
|
|
10820
|
-
import { existsSync as
|
|
10821
|
-
import
|
|
10973
|
+
import { existsSync as existsSync28, readFileSync as readFileSync24, writeFileSync as writeFileSync13 } from "node:fs";
|
|
10974
|
+
import path35 from "node:path";
|
|
10822
10975
|
function runCodeConnect(opts) {
|
|
10823
10976
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
10824
|
-
const bundleDir =
|
|
10977
|
+
const bundleDir = path35.resolve(callerCwd, opts.bundleDir);
|
|
10825
10978
|
let url;
|
|
10826
10979
|
try {
|
|
10827
10980
|
url = new URL(opts.figmaUrl);
|
|
@@ -10837,7 +10990,7 @@ function runCodeConnect(opts) {
|
|
|
10837
10990
|
}
|
|
10838
10991
|
let manifest;
|
|
10839
10992
|
try {
|
|
10840
|
-
const read = readBundleManifest(readFileSync24(
|
|
10993
|
+
const read = readBundleManifest(readFileSync24(path35.join(bundleDir, "component.json"), "utf8"));
|
|
10841
10994
|
if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
|
|
10842
10995
|
manifest = read.manifest;
|
|
10843
10996
|
} catch (err) {
|
|
@@ -10847,8 +11000,8 @@ function runCodeConnect(opts) {
|
|
|
10847
11000
|
remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
|
|
10848
11001
|
});
|
|
10849
11002
|
}
|
|
10850
|
-
const setDir =
|
|
10851
|
-
if (!
|
|
11003
|
+
const setDir = path35.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
|
|
11004
|
+
if (!existsSync28(path35.join(setDir, "recording-set.json"))) {
|
|
10852
11005
|
fail(opts, ExitCode.InputValidation, {
|
|
10853
11006
|
error: `recording set not found at ${setDir}`,
|
|
10854
11007
|
code: "codeconnect-no-set",
|
|
@@ -10862,15 +11015,15 @@ function runCodeConnect(opts) {
|
|
|
10862
11015
|
fail(opts, ExitCode.InputValidation, {
|
|
10863
11016
|
error: `cannot author the API from ${setDir}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
10864
11017
|
code: "codeconnect-authoring-failed",
|
|
10865
|
-
remediation:
|
|
11018
|
+
remediation: `The set must be a complete protocol recording (${tendrilCommand(`record status --set ${setDir}`)}).`
|
|
10866
11019
|
});
|
|
10867
11020
|
}
|
|
10868
11021
|
const api = authored.api;
|
|
10869
11022
|
const component = api.component;
|
|
10870
11023
|
const recManifest = loadManifest(setDir);
|
|
10871
11024
|
const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
|
|
10872
|
-
const meta =
|
|
10873
|
-
if (!
|
|
11025
|
+
const meta = path35.join(setDir, r.slug, "get_metadata.json");
|
|
11026
|
+
if (!existsSync28(meta)) return void 0;
|
|
10874
11027
|
try {
|
|
10875
11028
|
return /name="([^"]*)"/.exec(envelopeFirstTextPart(JSON.parse(readFileSync24(meta, "utf8"))))?.[1];
|
|
10876
11029
|
} catch {
|
|
@@ -10889,7 +11042,7 @@ function runCodeConnect(opts) {
|
|
|
10889
11042
|
fail(opts, ExitCode.InputValidation, {
|
|
10890
11043
|
error: "the recording set's content no longer matches the hash this bundle was scored against \u2014 its verification claims describe a different recording",
|
|
10891
11044
|
code: "codeconnect-set-drift",
|
|
10892
|
-
remediation:
|
|
11045
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${bundleDir} --set ${setDir}`)}\` (or engine score) against the current set, then re-emit.`
|
|
10893
11046
|
});
|
|
10894
11047
|
}
|
|
10895
11048
|
const boolProps = api.props.filter((p) => p.kind === "boolean");
|
|
@@ -10930,7 +11083,7 @@ function runCodeConnect(opts) {
|
|
|
10930
11083
|
axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
|
|
10931
11084
|
fragmentVars.push(varName);
|
|
10932
11085
|
}
|
|
10933
|
-
const entryRel =
|
|
11086
|
+
const entryRel = path35.relative(callerCwd, path35.join(bundleDir, manifest.entry));
|
|
10934
11087
|
const trust = manifest.trustStatement.split("\n")[0] ?? "";
|
|
10935
11088
|
const lines = [
|
|
10936
11089
|
`// url=${opts.figmaUrl}`,
|
|
@@ -10951,7 +11104,7 @@ function runCodeConnect(opts) {
|
|
|
10951
11104
|
`}`,
|
|
10952
11105
|
``
|
|
10953
11106
|
].join("\n");
|
|
10954
|
-
const outFile =
|
|
11107
|
+
const outFile = path35.resolve(callerCwd, opts.out ?? path35.join(bundleDir, `${component}.figma.ts`));
|
|
10955
11108
|
writeFileSync13(outFile, lines);
|
|
10956
11109
|
emitData(
|
|
10957
11110
|
opts,
|
|
@@ -10976,42 +11129,43 @@ function runCodeConnect(opts) {
|
|
|
10976
11129
|
}
|
|
10977
11130
|
var kebab4, q;
|
|
10978
11131
|
var init_codeconnect = __esm({
|
|
10979
|
-
"
|
|
11132
|
+
"packages/cli/src/commands/codeconnect.ts"() {
|
|
10980
11133
|
"use strict";
|
|
10981
11134
|
init_src3();
|
|
10982
11135
|
init_src7();
|
|
10983
11136
|
init_src();
|
|
10984
11137
|
init_src6();
|
|
11138
|
+
init_invocation();
|
|
10985
11139
|
init_output();
|
|
10986
11140
|
kebab4 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
10987
11141
|
q = (s) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
10988
11142
|
}
|
|
10989
11143
|
});
|
|
10990
11144
|
|
|
10991
|
-
//
|
|
11145
|
+
// packages/mcp/src/server.ts
|
|
10992
11146
|
import { createHash as createHash5 } from "node:crypto";
|
|
10993
|
-
import { existsSync as
|
|
11147
|
+
import { existsSync as existsSync29, mkdtempSync as mkdtempSync3, readFileSync as readFileSync25, readdirSync as readdirSync8, writeFileSync as writeFileSync14 } from "node:fs";
|
|
10994
11148
|
import os6 from "node:os";
|
|
10995
|
-
import
|
|
10996
|
-
import { fileURLToPath as
|
|
11149
|
+
import path36 from "node:path";
|
|
11150
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
10997
11151
|
import { z as z12 } from "zod";
|
|
10998
11152
|
function sourceHash() {
|
|
10999
|
-
const dir =
|
|
11153
|
+
const dir = path36.dirname(fileURLToPath6(import.meta.url));
|
|
11000
11154
|
const h = createHash5("sha256");
|
|
11001
11155
|
for (const f of readdirSync8(dir).filter((n) => n.endsWith(".ts")).sort()) {
|
|
11002
11156
|
h.update(f);
|
|
11003
|
-
h.update(readFileSync25(
|
|
11157
|
+
h.update(readFileSync25(path36.join(dir, f)));
|
|
11004
11158
|
}
|
|
11005
11159
|
return h.digest("hex").slice(0, 16);
|
|
11006
11160
|
}
|
|
11007
11161
|
var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
|
|
11008
11162
|
var init_server = __esm({
|
|
11009
|
-
"
|
|
11163
|
+
"packages/mcp/src/server.ts"() {
|
|
11010
11164
|
"use strict";
|
|
11011
|
-
REPO_ROOT3 =
|
|
11012
|
-
CLI_BIN =
|
|
11013
|
-
BUNDLED_CLI =
|
|
11014
|
-
CLI_SPAWN =
|
|
11165
|
+
REPO_ROOT3 = path36.resolve(path36.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
|
|
11166
|
+
CLI_BIN = path36.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
|
|
11167
|
+
BUNDLED_CLI = path36.join(path36.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
|
|
11168
|
+
CLI_SPAWN = existsSync29(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
|
|
11015
11169
|
str = (d) => z12.string().describe(d);
|
|
11016
11170
|
optStr = (d) => z12.string().optional().describe(d);
|
|
11017
11171
|
TOOLS = [
|
|
@@ -11041,7 +11195,7 @@ var init_server = __esm({
|
|
|
11041
11195
|
const single = i["metadata"];
|
|
11042
11196
|
const parts = i["metadataParts"];
|
|
11043
11197
|
if (single !== void 0 || parts !== void 0) {
|
|
11044
|
-
const tmp =
|
|
11198
|
+
const tmp = path36.join(mkdtempSync3(path36.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
11045
11199
|
if (single !== void 0) {
|
|
11046
11200
|
writeFileSync14(tmp, single);
|
|
11047
11201
|
argvOut.push("--metadata-raw-file", tmp);
|
|
@@ -11112,7 +11266,7 @@ var init_server = __esm({
|
|
|
11112
11266
|
const bridge = (label, single, parts) => {
|
|
11113
11267
|
if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
|
|
11114
11268
|
if (single === void 0 && parts === void 0) return;
|
|
11115
|
-
const tmp =
|
|
11269
|
+
const tmp = path36.join(mkdtempSync3(path36.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
11116
11270
|
if (single !== void 0) {
|
|
11117
11271
|
writeFileSync14(tmp, single);
|
|
11118
11272
|
argvOut.push(`--${label}-file`, tmp);
|
|
@@ -11155,7 +11309,7 @@ var init_server = __esm({
|
|
|
11155
11309
|
const file = i["file"];
|
|
11156
11310
|
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)");
|
|
11157
11311
|
if (file !== void 0) return [...base, "--file", file];
|
|
11158
|
-
const tmp =
|
|
11312
|
+
const tmp = path36.join(mkdtempSync3(path36.join(os6.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
11159
11313
|
if (text !== void 0) {
|
|
11160
11314
|
writeFileSync14(tmp, text);
|
|
11161
11315
|
return [...base, "--file", tmp, "--raw"];
|
|
@@ -11302,7 +11456,7 @@ var init_server = __esm({
|
|
|
11302
11456
|
}
|
|
11303
11457
|
});
|
|
11304
11458
|
|
|
11305
|
-
//
|
|
11459
|
+
// packages/cli/src/commands/permissions.ts
|
|
11306
11460
|
var permissions_exports = {};
|
|
11307
11461
|
__export(permissions_exports, {
|
|
11308
11462
|
PERMISSIONS_DESCRIPTION: () => PERMISSIONS_DESCRIPTION,
|
|
@@ -11310,12 +11464,12 @@ __export(permissions_exports, {
|
|
|
11310
11464
|
mergeAllowlist: () => mergeAllowlist,
|
|
11311
11465
|
runPermissions: () => runPermissions
|
|
11312
11466
|
});
|
|
11313
|
-
import { existsSync as
|
|
11467
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync15 } from "node:fs";
|
|
11314
11468
|
import os7 from "node:os";
|
|
11315
|
-
import
|
|
11469
|
+
import path37 from "node:path";
|
|
11316
11470
|
function mergeAllowlist(file, entries) {
|
|
11317
11471
|
let settings = {};
|
|
11318
|
-
if (
|
|
11472
|
+
if (existsSync30(file) && readFileSync26(file, "utf8").trim() !== "") {
|
|
11319
11473
|
settings = JSON.parse(readFileSync26(file, "utf8"));
|
|
11320
11474
|
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
|
|
11321
11475
|
}
|
|
@@ -11328,7 +11482,7 @@ function mergeAllowlist(file, entries) {
|
|
|
11328
11482
|
const alreadyPresent = entries.filter((e) => present.has(e));
|
|
11329
11483
|
if (added.length > 0) {
|
|
11330
11484
|
allow.push(...added);
|
|
11331
|
-
mkdirSync9(
|
|
11485
|
+
mkdirSync9(path37.dirname(file), { recursive: true });
|
|
11332
11486
|
writeFileSync15(file, `${JSON.stringify(settings, null, 2)}
|
|
11333
11487
|
`);
|
|
11334
11488
|
}
|
|
@@ -11364,7 +11518,7 @@ async function runPermissions(flags) {
|
|
|
11364
11518
|
const result = await buildPermissions({ ...flags.mcpUrl ? { mcpUrl: flags.mcpUrl } : {} });
|
|
11365
11519
|
if (flags.write) {
|
|
11366
11520
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
11367
|
-
const file = flags.user ?
|
|
11521
|
+
const file = flags.user ? path37.join(os7.homedir(), ".claude", "settings.json") : path37.join(base, ".claude", "settings.local.json");
|
|
11368
11522
|
if (flags.dryRun) {
|
|
11369
11523
|
emitData(flags, { file, wouldAdd: result.toolEntries }, () => {
|
|
11370
11524
|
process.stdout.write(`dry-run: would merge ${result.toolEntries.length} per-tool entries into ${file}
|
|
@@ -11397,7 +11551,7 @@ async function runPermissions(flags) {
|
|
|
11397
11551
|
`Claude Code allowlist for the Tendril pipeline.
|
|
11398
11552
|
One command installs it (project-local, idempotent):
|
|
11399
11553
|
|
|
11400
|
-
|
|
11554
|
+
${tendrilCommand("permissions --claude --write")}
|
|
11401
11555
|
|
|
11402
11556
|
Or paste into .claude/settings.json under permissions.allow:
|
|
11403
11557
|
|
|
@@ -11416,12 +11570,13 @@ ${result.directConfigNote}
|
|
|
11416
11570
|
}
|
|
11417
11571
|
var FIGMA_TOOL_FALLBACK, PERMISSIONS_DESCRIPTION, TENDRIL_PLUGIN_PREFIX, FIGMA_PLUGIN_PREFIX;
|
|
11418
11572
|
var init_permissions = __esm({
|
|
11419
|
-
"
|
|
11573
|
+
"packages/cli/src/commands/permissions.ts"() {
|
|
11420
11574
|
"use strict";
|
|
11421
11575
|
init_src3();
|
|
11422
11576
|
init_src();
|
|
11423
11577
|
init_server();
|
|
11424
11578
|
init_describe();
|
|
11579
|
+
init_invocation();
|
|
11425
11580
|
init_output();
|
|
11426
11581
|
init_doctor();
|
|
11427
11582
|
FIGMA_TOOL_FALLBACK = ["get_metadata", "get_design_context", "get_screenshot", "get_variable_defs", "get_motion_context", "get_figjam"];
|
|
@@ -11451,27 +11606,27 @@ var init_permissions = __esm({
|
|
|
11451
11606
|
}
|
|
11452
11607
|
});
|
|
11453
11608
|
|
|
11454
|
-
//
|
|
11609
|
+
// packages/cli/src/commands/inspect.ts
|
|
11455
11610
|
var inspect_exports = {};
|
|
11456
11611
|
__export(inspect_exports, {
|
|
11457
11612
|
INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
|
|
11458
11613
|
runInspect: () => runInspect
|
|
11459
11614
|
});
|
|
11460
|
-
import { existsSync as
|
|
11461
|
-
import
|
|
11615
|
+
import { existsSync as existsSync31, readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "node:fs";
|
|
11616
|
+
import path38 from "node:path";
|
|
11462
11617
|
async function runInspect(opts) {
|
|
11463
11618
|
if (opts.describe) {
|
|
11464
11619
|
printDescription(INSPECT_DESCRIPTION);
|
|
11465
11620
|
return;
|
|
11466
11621
|
}
|
|
11467
|
-
const bundleDir =
|
|
11468
|
-
const evidenceDir =
|
|
11469
|
-
const manifestPath2 =
|
|
11470
|
-
if (!
|
|
11622
|
+
const bundleDir = path38.resolve(opts.bundleDir);
|
|
11623
|
+
const evidenceDir = path38.join(bundleDir, "verify-evidence");
|
|
11624
|
+
const manifestPath2 = path38.join(bundleDir, "component.json");
|
|
11625
|
+
if (!existsSync31(evidenceDir) || !existsSync31(manifestPath2)) {
|
|
11471
11626
|
fail(opts, ExitCode.InputValidation, {
|
|
11472
|
-
error: `nothing to inspect in ${bundleDir} \u2014 ${
|
|
11627
|
+
error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync31(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
|
|
11473
11628
|
code: "no-evidence",
|
|
11474
|
-
remediation:
|
|
11629
|
+
remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
|
|
11475
11630
|
});
|
|
11476
11631
|
}
|
|
11477
11632
|
const { manifest } = readBundleManifest(readFileSync27(manifestPath2, "utf8"));
|
|
@@ -11482,27 +11637,27 @@ async function runInspect(opts) {
|
|
|
11482
11637
|
remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
|
|
11483
11638
|
});
|
|
11484
11639
|
}
|
|
11485
|
-
const setDir =
|
|
11486
|
-
const reps = Object.keys(manifest.propAdapter).filter((rep) =>
|
|
11640
|
+
const setDir = path38.resolve(opts.set ?? manifest.provenance.recordingSet.path);
|
|
11641
|
+
const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync31(path38.join(evidenceDir, `${rep}-ref.png`)) && existsSync31(path38.join(evidenceDir, `${rep}-render.png`)));
|
|
11487
11642
|
if (reps.length === 0) {
|
|
11488
11643
|
fail(opts, ExitCode.InputValidation, {
|
|
11489
11644
|
error: "verify-evidence holds no ref/render pairs for this bundle's configs",
|
|
11490
11645
|
code: "no-evidence",
|
|
11491
|
-
remediation:
|
|
11646
|
+
remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
|
|
11492
11647
|
});
|
|
11493
11648
|
}
|
|
11494
11649
|
let crops = 0;
|
|
11495
11650
|
const sections = [];
|
|
11496
11651
|
for (const rep of reps) {
|
|
11497
|
-
const ref = new Uint8Array(readFileSync27(
|
|
11498
|
-
const render = new Uint8Array(readFileSync27(
|
|
11652
|
+
const ref = new Uint8Array(readFileSync27(path38.join(evidenceDir, `${rep}-ref.png`)));
|
|
11653
|
+
const render = new Uint8Array(readFileSync27(path38.join(evidenceDir, `${rep}-render.png`)));
|
|
11499
11654
|
const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
|
|
11500
11655
|
const cells = [];
|
|
11501
11656
|
for (const [i, n] of nodes.entries()) {
|
|
11502
11657
|
const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
|
|
11503
11658
|
try {
|
|
11504
|
-
writeFileSync16(
|
|
11505
|
-
writeFileSync16(
|
|
11659
|
+
writeFileSync16(path38.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
|
|
11660
|
+
writeFileSync16(path38.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
|
|
11506
11661
|
} catch {
|
|
11507
11662
|
continue;
|
|
11508
11663
|
}
|
|
@@ -11515,7 +11670,7 @@ async function runInspect(opts) {
|
|
|
11515
11670
|
`<section><h2>${esc(rep)}</h2><div class="full"><span><em>recorded</em><img src="./${rep}-ref.png"></span><span><em>rendered</em><img src="./${rep}-render.png"></span><span><em>diff</em><img src="./${rep}-diff.png"></span></div>` + (cells.length > 0 ? `<div class="grid">${cells.join("")}</div>` : `<p class="none">no small recorded nodes in this config's sweep</p>`) + `</section>`
|
|
11516
11671
|
);
|
|
11517
11672
|
}
|
|
11518
|
-
const sheet =
|
|
11673
|
+
const sheet = path38.join(evidenceDir, "inspect.html");
|
|
11519
11674
|
writeFileSync16(
|
|
11520
11675
|
sheet,
|
|
11521
11676
|
`<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
|
|
@@ -11540,12 +11695,13 @@ ${reps.length} config(s), ${crops} detail crop pair(s) \u2014 open the sheet and
|
|
|
11540
11695
|
}
|
|
11541
11696
|
var INSPECT_DESCRIPTION, esc;
|
|
11542
11697
|
var init_inspect = __esm({
|
|
11543
|
-
"
|
|
11698
|
+
"packages/cli/src/commands/inspect.ts"() {
|
|
11544
11699
|
"use strict";
|
|
11545
11700
|
init_src3();
|
|
11546
11701
|
init_src6();
|
|
11547
11702
|
init_src4();
|
|
11548
11703
|
init_describe();
|
|
11704
|
+
init_invocation();
|
|
11549
11705
|
init_output();
|
|
11550
11706
|
INSPECT_DESCRIPTION = {
|
|
11551
11707
|
name: "inspect",
|
|
@@ -11568,7 +11724,7 @@ var init_inspect = __esm({
|
|
|
11568
11724
|
}
|
|
11569
11725
|
});
|
|
11570
11726
|
|
|
11571
|
-
//
|
|
11727
|
+
// packages/cli/src/commands/generate-route.ts
|
|
11572
11728
|
var generate_route_exports = {};
|
|
11573
11729
|
__export(generate_route_exports, {
|
|
11574
11730
|
routeGenerate: () => routeGenerate
|
|
@@ -11581,28 +11737,28 @@ function routeGenerate(input) {
|
|
|
11581
11737
|
return { path: "facts", ...input.url !== void 0 ? { url: input.url } : {} };
|
|
11582
11738
|
}
|
|
11583
11739
|
var init_generate_route = __esm({
|
|
11584
|
-
"
|
|
11740
|
+
"packages/cli/src/commands/generate-route.ts"() {
|
|
11585
11741
|
"use strict";
|
|
11586
11742
|
}
|
|
11587
11743
|
});
|
|
11588
11744
|
|
|
11589
|
-
//
|
|
11745
|
+
// packages/cli/src/commands/generate-recorded.ts
|
|
11590
11746
|
var generate_recorded_exports = {};
|
|
11591
11747
|
__export(generate_recorded_exports, {
|
|
11592
11748
|
runGenerateRecorded: () => runGenerateRecorded
|
|
11593
11749
|
});
|
|
11594
11750
|
import { confirm as confirm3, isCancel as isCancel3 } from "@clack/prompts";
|
|
11595
|
-
import { existsSync as
|
|
11596
|
-
import
|
|
11751
|
+
import { existsSync as existsSync32, readFileSync as readFileSync28 } from "node:fs";
|
|
11752
|
+
import path39 from "node:path";
|
|
11597
11753
|
async function runGenerateRecorded(opts) {
|
|
11598
11754
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
11599
|
-
const outDirAbs =
|
|
11600
|
-
const recordedAsPath =
|
|
11755
|
+
const outDirAbs = path39.resolve(callerCwd, opts.out);
|
|
11756
|
+
const recordedAsPath = path39.resolve(callerCwd, opts.recorded);
|
|
11601
11757
|
let task;
|
|
11602
11758
|
let taskName;
|
|
11603
11759
|
let authoredApi;
|
|
11604
11760
|
let composition;
|
|
11605
|
-
const isSet =
|
|
11761
|
+
const isSet = existsSync32(path39.join(recordedAsPath, "recording-set.json"));
|
|
11606
11762
|
const registry = TASKS[opts.recorded];
|
|
11607
11763
|
if (registry !== void 0 && !isSet) {
|
|
11608
11764
|
task = registry;
|
|
@@ -11611,7 +11767,7 @@ async function runGenerateRecorded(opts) {
|
|
|
11611
11767
|
try {
|
|
11612
11768
|
const authored = authorTaskFromSet(recordedAsPath);
|
|
11613
11769
|
task = authored.task;
|
|
11614
|
-
taskName =
|
|
11770
|
+
taskName = path39.basename(recordedAsPath);
|
|
11615
11771
|
authoredApi = authored.api;
|
|
11616
11772
|
const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
|
|
11617
11773
|
if (roles.success) composition = roles.data;
|
|
@@ -11620,14 +11776,14 @@ async function runGenerateRecorded(opts) {
|
|
|
11620
11776
|
fail(opts, ExitCode.InputValidation, {
|
|
11621
11777
|
error: `cannot author a task from ${recordedAsPath}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
11622
11778
|
code: "AUTHORING_FAILED",
|
|
11623
|
-
remediation:
|
|
11779
|
+
remediation: `Complete the recording set (${tendrilCommand(`record status --set ${recordedAsPath}`)}) or use a reference-corpus task name.`
|
|
11624
11780
|
});
|
|
11625
11781
|
}
|
|
11626
11782
|
} else {
|
|
11627
11783
|
fail(opts, ExitCode.InputValidation, {
|
|
11628
11784
|
error: `"${opts.recorded}" is neither a reference task nor a recorded set (no recording-set.json found)`,
|
|
11629
11785
|
code: "UNKNOWN_RECORDED_INPUT",
|
|
11630
|
-
remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with
|
|
11786
|
+
remediation: `Pick a task (${Object.keys(TASKS).join(", ")}) or point at a set recorded with \`${tendrilCommand("record plan")}\`.`
|
|
11631
11787
|
});
|
|
11632
11788
|
}
|
|
11633
11789
|
if (!fontsResolved()) {
|
|
@@ -11645,13 +11801,13 @@ async function runGenerateRecorded(opts) {
|
|
|
11645
11801
|
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)`);
|
|
11646
11802
|
}
|
|
11647
11803
|
const missing = task.configs.filter(
|
|
11648
|
-
(c) => !
|
|
11804
|
+
(c) => !existsSync32(path39.join(task.set, c.rep, "get_screenshot.json")) || !existsSync32(path39.join(task.set, c.rep, "get_metadata.json")) || !existsSync32(path39.join(task.set, c.rep, "get_design_context.json"))
|
|
11649
11805
|
);
|
|
11650
11806
|
if (missing.length > 0) {
|
|
11651
11807
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
11652
11808
|
error: `recording set ${task.set} is missing envelopes for ${missing.length} rep(s): ${missing.slice(0, 3).map((c) => c.rep).join(", ")}${missing.length > 3 ? ", \u2026" : ""}`,
|
|
11653
11809
|
code: "recording-incomplete",
|
|
11654
|
-
remediation:
|
|
11810
|
+
remediation: `Resume the recording session (\`${tendrilCommand(`record next --set ${task.set}`)}\`) until \`${tendrilCommand(`record status --set ${task.set}`)}\` reports the set complete.`
|
|
11655
11811
|
});
|
|
11656
11812
|
}
|
|
11657
11813
|
const modelId = opts.model ?? DEFAULT_MODEL_CONFIG.curatedDefault ?? DEFAULT_MODEL_CONFIG.bulk;
|
|
@@ -11672,7 +11828,7 @@ async function runGenerateRecorded(opts) {
|
|
|
11672
11828
|
fail(opts, ExitCode.Auth, {
|
|
11673
11829
|
error: "OPENROUTER_API_KEY is not configured \u2014 the curated engine needs an OpenRouter-compatible key.",
|
|
11674
11830
|
code: "MISSING_OPENROUTER_KEY",
|
|
11675
|
-
remediation:
|
|
11831
|
+
remediation: `Two paths: run \`${tendrilCommand("init")}\` to store your OpenRouter key (BYOK \u2014 your key, your inference), or drive the agent-harness engine explicitly with your own agent host.`
|
|
11676
11832
|
});
|
|
11677
11833
|
}
|
|
11678
11834
|
const bar = BARS4[opts.bar];
|
|
@@ -11715,8 +11871,8 @@ async function runGenerateRecorded(opts) {
|
|
|
11715
11871
|
` : `${line}
|
|
11716
11872
|
`);
|
|
11717
11873
|
if (opts.dryRun) {
|
|
11718
|
-
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite:
|
|
11719
|
-
process.stdout.write(`dry-run: nothing sent, nothing written (would write ${
|
|
11874
|
+
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path39.join(outDirAbs, taskName) }, () => {
|
|
11875
|
+
process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path39.join(outDirAbs, taskName)})
|
|
11720
11876
|
`);
|
|
11721
11877
|
});
|
|
11722
11878
|
return;
|
|
@@ -11739,10 +11895,10 @@ async function runGenerateRecorded(opts) {
|
|
|
11739
11895
|
});
|
|
11740
11896
|
}
|
|
11741
11897
|
}
|
|
11742
|
-
const bundleDir =
|
|
11743
|
-
if (
|
|
11898
|
+
const bundleDir = path39.join(outDirAbs, taskName);
|
|
11899
|
+
if (existsSync32(path39.join(bundleDir, "component.json"))) {
|
|
11744
11900
|
try {
|
|
11745
|
-
const prior = readBundleManifest(readFileSync28(
|
|
11901
|
+
const prior = readBundleManifest(readFileSync28(path39.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
11746
11902
|
if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
|
|
11747
11903
|
fail(opts, ExitCode.InputValidation, {
|
|
11748
11904
|
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`,
|
|
@@ -11868,7 +12024,7 @@ ${certified}/${statuses.length} certified \xB7 ${statuses.length - pixelFailures
|
|
|
11868
12024
|
}
|
|
11869
12025
|
var BARS4;
|
|
11870
12026
|
var init_generate_recorded = __esm({
|
|
11871
|
-
"
|
|
12027
|
+
"packages/cli/src/commands/generate-recorded.ts"() {
|
|
11872
12028
|
"use strict";
|
|
11873
12029
|
init_src3();
|
|
11874
12030
|
init_src5();
|
|
@@ -11879,6 +12035,7 @@ var init_generate_recorded = __esm({
|
|
|
11879
12035
|
init_env();
|
|
11880
12036
|
init_environment();
|
|
11881
12037
|
init_font_guidance();
|
|
12038
|
+
init_invocation();
|
|
11882
12039
|
init_output();
|
|
11883
12040
|
BARS4 = {
|
|
11884
12041
|
pass: { sim: 0.95, ink: 0.95 },
|
|
@@ -11887,24 +12044,27 @@ var init_generate_recorded = __esm({
|
|
|
11887
12044
|
}
|
|
11888
12045
|
});
|
|
11889
12046
|
|
|
11890
|
-
//
|
|
12047
|
+
// packages/cli/src/bin.ts
|
|
11891
12048
|
init_src3();
|
|
12049
|
+
init_invocation();
|
|
11892
12050
|
import { CommanderError } from "commander";
|
|
11893
12051
|
|
|
11894
|
-
//
|
|
12052
|
+
// packages/cli/src/program.ts
|
|
11895
12053
|
init_src3();
|
|
11896
12054
|
init_environment();
|
|
12055
|
+
init_invocation();
|
|
11897
12056
|
init_doctor();
|
|
11898
12057
|
import { Command } from "commander";
|
|
11899
12058
|
|
|
11900
|
-
//
|
|
12059
|
+
// packages/cli/src/commands/init.ts
|
|
11901
12060
|
init_src3();
|
|
11902
12061
|
init_describe();
|
|
11903
12062
|
init_env();
|
|
12063
|
+
init_invocation();
|
|
11904
12064
|
init_output();
|
|
11905
12065
|
import { intro, isCancel, outro, password } from "@clack/prompts";
|
|
11906
12066
|
import fs from "node:fs";
|
|
11907
|
-
import
|
|
12067
|
+
import path24 from "node:path";
|
|
11908
12068
|
var INIT_DESCRIPTION = {
|
|
11909
12069
|
name: "init",
|
|
11910
12070
|
summary: "Configure Figma and OpenRouter credentials in .env (idempotent).",
|
|
@@ -11941,7 +12101,7 @@ async function runInit(flags) {
|
|
|
11941
12101
|
printDescription(INIT_DESCRIPTION);
|
|
11942
12102
|
return;
|
|
11943
12103
|
}
|
|
11944
|
-
const envPath =
|
|
12104
|
+
const envPath = path24.resolve(process.cwd(), ".env");
|
|
11945
12105
|
const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
|
|
11946
12106
|
let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
|
|
11947
12107
|
let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
|
|
@@ -11950,7 +12110,7 @@ async function runInit(flags) {
|
|
|
11950
12110
|
fail(flags, ExitCode.InputValidation, {
|
|
11951
12111
|
error: "Missing credentials and prompts are disabled.",
|
|
11952
12112
|
code: "MISSING_CREDENTIALS",
|
|
11953
|
-
remediation:
|
|
12113
|
+
remediation: `Pass --figma-token <token> and --openrouter-key <key>, or run \`${tendrilCommand("init")}\` in an interactive terminal.`
|
|
11954
12114
|
});
|
|
11955
12115
|
}
|
|
11956
12116
|
if (interactive) {
|
|
@@ -11962,7 +12122,7 @@ async function runInit(flags) {
|
|
|
11962
12122
|
next.set(ENV_KEYS.figma, figmaToken);
|
|
11963
12123
|
next.set(ENV_KEYS.openrouter, openrouterKey);
|
|
11964
12124
|
const changed = existing.get(ENV_KEYS.figma) !== next.get(ENV_KEYS.figma) || existing.get(ENV_KEYS.openrouter) !== next.get(ENV_KEYS.openrouter);
|
|
11965
|
-
const gitignorePath =
|
|
12125
|
+
const gitignorePath = path24.resolve(process.cwd(), ".gitignore");
|
|
11966
12126
|
const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
|
|
11967
12127
|
const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
|
|
11968
12128
|
if (flags.dryRun) {
|
|
@@ -12007,26 +12167,27 @@ async function runInit(flags) {
|
|
|
12007
12167
|
});
|
|
12008
12168
|
}
|
|
12009
12169
|
|
|
12010
|
-
//
|
|
12170
|
+
// packages/cli/src/commands/generate.ts
|
|
12011
12171
|
init_src3();
|
|
12012
12172
|
init_src();
|
|
12013
12173
|
init_src5();
|
|
12014
12174
|
init_src2();
|
|
12015
12175
|
init_describe();
|
|
12016
12176
|
init_env();
|
|
12177
|
+
init_invocation();
|
|
12017
12178
|
init_output();
|
|
12018
12179
|
init_entitlement();
|
|
12019
12180
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
12020
|
-
import { readFileSync as readFileSync15, readdirSync as readdirSync4, existsSync as
|
|
12181
|
+
import { readFileSync as readFileSync15, readdirSync as readdirSync4, existsSync as existsSync19 } from "node:fs";
|
|
12021
12182
|
|
|
12022
|
-
//
|
|
12183
|
+
// packages/cli/src/pipeline.ts
|
|
12023
12184
|
init_src2();
|
|
12024
12185
|
init_src4();
|
|
12025
12186
|
init_src6();
|
|
12026
12187
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
12027
|
-
import
|
|
12188
|
+
import path25 from "node:path";
|
|
12028
12189
|
|
|
12029
|
-
//
|
|
12190
|
+
// packages/cli/src/assets-module.ts
|
|
12030
12191
|
init_src();
|
|
12031
12192
|
var KEBAB_ATTRS = {
|
|
12032
12193
|
"fill-rule": "fillRule",
|
|
@@ -12137,7 +12298,7 @@ function buildAssetsModule(assets, aliases = {}) {
|
|
|
12137
12298
|
return { source, flags };
|
|
12138
12299
|
}
|
|
12139
12300
|
|
|
12140
|
-
//
|
|
12301
|
+
// packages/cli/src/pipeline.ts
|
|
12141
12302
|
var VerifyFailedError = class extends Error {
|
|
12142
12303
|
constructor(loop) {
|
|
12143
12304
|
super(
|
|
@@ -12360,7 +12521,7 @@ async function runGenerationPipeline(input) {
|
|
|
12360
12521
|
});
|
|
12361
12522
|
const written = [];
|
|
12362
12523
|
if (!input.dryRun) {
|
|
12363
|
-
const dir =
|
|
12524
|
+
const dir = path25.resolve(input.outDir, semantics.componentName);
|
|
12364
12525
|
mkdirSync5(dir, { recursive: true });
|
|
12365
12526
|
const files = {
|
|
12366
12527
|
// Bundle-local tokens: THE emission the component's CSS resolves
|
|
@@ -12384,13 +12545,13 @@ async function runGenerationPipeline(input) {
|
|
|
12384
12545
|
`
|
|
12385
12546
|
};
|
|
12386
12547
|
for (const [name, content] of Object.entries(files)) {
|
|
12387
|
-
const filePath =
|
|
12548
|
+
const filePath = path25.join(dir, name);
|
|
12388
12549
|
writeFileSync8(filePath, content);
|
|
12389
12550
|
written.push(filePath);
|
|
12390
12551
|
}
|
|
12391
12552
|
for (const artifact of emitTokenArtifacts(input.mapping)) {
|
|
12392
|
-
const filePath =
|
|
12393
|
-
mkdirSync5(
|
|
12553
|
+
const filePath = path25.resolve(input.outDir, artifact.path);
|
|
12554
|
+
mkdirSync5(path25.dirname(filePath), { recursive: true });
|
|
12394
12555
|
writeFileSync8(filePath, artifact.content);
|
|
12395
12556
|
written.push(filePath);
|
|
12396
12557
|
}
|
|
@@ -12398,7 +12559,7 @@ async function runGenerationPipeline(input) {
|
|
|
12398
12559
|
return { semantics, loop: finalLoop, report, componentJson, usageMd: docs.usageMd, written };
|
|
12399
12560
|
}
|
|
12400
12561
|
|
|
12401
|
-
//
|
|
12562
|
+
// packages/cli/src/commands/generate.ts
|
|
12402
12563
|
var TOOL_VERSION = "0.0.0";
|
|
12403
12564
|
var GENERATE_DESCRIPTION = {
|
|
12404
12565
|
name: "generate",
|
|
@@ -12454,7 +12615,7 @@ function resolveProvidedSource(flags, contextFile) {
|
|
|
12454
12615
|
fail(flags, ExitCode.InputValidation, {
|
|
12455
12616
|
error: `Cannot read context file "${contextFile}".`,
|
|
12456
12617
|
code: "CONTEXT_UNREADABLE",
|
|
12457
|
-
remediation:
|
|
12618
|
+
remediation: `Pass a readable JSON file via --context; run \`${tendrilCommand("generate --describe")}\` for the payload shape.`
|
|
12458
12619
|
});
|
|
12459
12620
|
}
|
|
12460
12621
|
try {
|
|
@@ -12463,7 +12624,7 @@ function resolveProvidedSource(flags, contextFile) {
|
|
|
12463
12624
|
fail(flags, ExitCode.InputValidation, {
|
|
12464
12625
|
error: `Context file "${contextFile}" is not a valid design-context payload: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
12465
12626
|
code: "CONTEXT_INVALID",
|
|
12466
|
-
remediation:
|
|
12627
|
+
remediation: `The payload must match { nodeTree, variables[], metadata, screenshotBase64? } \u2014 see \`${tendrilCommand("generate --describe")}\` and docs/adr/003-figma-access-strategy.md.`
|
|
12467
12628
|
});
|
|
12468
12629
|
}
|
|
12469
12630
|
}
|
|
@@ -12473,7 +12634,7 @@ function resolveSource(flags, url) {
|
|
|
12473
12634
|
fail(flags, ExitCode.General, {
|
|
12474
12635
|
error: "Live Figma transports (desktop MCP / REST) are not wired up yet.",
|
|
12475
12636
|
code: "NOT_IMPLEMENTED",
|
|
12476
|
-
remediation:
|
|
12637
|
+
remediation: `Use \`${tendrilCommand("generate --context <file>")}\` with an agent-fetched payload, or mock://button for the offline fixture; live transports are task 1.2 in docs/PLAN.md.`
|
|
12477
12638
|
});
|
|
12478
12639
|
}
|
|
12479
12640
|
fail(flags, ExitCode.InputValidation, {
|
|
@@ -12492,7 +12653,7 @@ async function runGenerate(url, flags) {
|
|
|
12492
12653
|
fail(flags, ExitCode.InputValidation, {
|
|
12493
12654
|
error: "Missing input: pass a recording set or reference task (recorded truth, the default), mock://button, or --context <file>.",
|
|
12494
12655
|
code: "MISSING_ARGUMENT",
|
|
12495
|
-
remediation:
|
|
12656
|
+
remediation: `Run \`${tendrilCommand("generate --describe")}\` to see arguments and examples.`
|
|
12496
12657
|
});
|
|
12497
12658
|
}
|
|
12498
12659
|
url ??= `provided://${flags.context}`;
|
|
@@ -12569,10 +12730,10 @@ token mapping (${mapping.flat.length} variables):
|
|
|
12569
12730
|
let initialCode;
|
|
12570
12731
|
let initialSemantics;
|
|
12571
12732
|
try {
|
|
12572
|
-
if (
|
|
12733
|
+
if (existsSync19(flags.out)) {
|
|
12573
12734
|
for (const entry of readdirSync4(flags.out)) {
|
|
12574
12735
|
const cjPath = `${flags.out}/${entry}/component.json`;
|
|
12575
|
-
if (!
|
|
12736
|
+
if (!existsSync19(cjPath)) continue;
|
|
12576
12737
|
const cj = JSON.parse(readFileSync15(cjPath, "utf8"));
|
|
12577
12738
|
if (cj.name !== void 0 && Array.isArray(cj.props)) {
|
|
12578
12739
|
previousApi = JSON.stringify({
|
|
@@ -12581,13 +12742,13 @@ token mapping (${mapping.flat.length} variables):
|
|
|
12581
12742
|
});
|
|
12582
12743
|
const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
|
|
12583
12744
|
const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
|
|
12584
|
-
if (flags.refine &&
|
|
12745
|
+
if (flags.refine && existsSync19(tsxPath) && existsSync19(cssPath)) {
|
|
12585
12746
|
initialCode = {
|
|
12586
12747
|
tsx: readFileSync15(tsxPath, "utf8"),
|
|
12587
12748
|
css: readFileSync15(cssPath, "utf8")
|
|
12588
12749
|
};
|
|
12589
12750
|
const semPath = `${flags.out}/${entry}/semantics.json`;
|
|
12590
|
-
if (
|
|
12751
|
+
if (existsSync19(semPath)) {
|
|
12591
12752
|
initialSemantics = JSON.parse(readFileSync15(semPath, "utf8"));
|
|
12592
12753
|
}
|
|
12593
12754
|
}
|
|
@@ -12617,7 +12778,7 @@ token mapping (${mapping.flat.length} variables):
|
|
|
12617
12778
|
fail(flags, ExitCode.Auth, {
|
|
12618
12779
|
error: "OPENROUTER_API_KEY is not configured.",
|
|
12619
12780
|
code: "MISSING_OPENROUTER_KEY",
|
|
12620
|
-
remediation:
|
|
12781
|
+
remediation: `Run \`${tendrilCommand("init")}\` to store your OpenRouter key in .env (BYOK \u2014 your key, your inference).`
|
|
12621
12782
|
});
|
|
12622
12783
|
}
|
|
12623
12784
|
genModel = new OpenRouterGenerationModel(new OpenRouterClient({ apiKey }), model.id, {
|
|
@@ -12726,7 +12887,7 @@ bundle written to ${flags.out}/${result.semantics.componentName}/:
|
|
|
12726
12887
|
);
|
|
12727
12888
|
}
|
|
12728
12889
|
|
|
12729
|
-
//
|
|
12890
|
+
// packages/cli/src/program.ts
|
|
12730
12891
|
init_src4();
|
|
12731
12892
|
function globalFlags(cmd) {
|
|
12732
12893
|
const opts = cmd.optsWithGlobals();
|
|
@@ -12840,7 +13001,7 @@ function buildProgram() {
|
|
|
12840
13001
|
fail2(flags, ExitCode.InputValidation, {
|
|
12841
13002
|
error: family === void 0 ? "nothing to resolve \u2014 pass a family name or --set <recording-dir>" : "pass a family name OR --set, not both \u2014 --set derives families from the recording itself",
|
|
12842
13003
|
code: "INPUT_VALIDATION",
|
|
12843
|
-
remediation:
|
|
13004
|
+
remediation: `${tendrilCommand("fonts resolve --set <recording-dir>")}, or ${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}`
|
|
12844
13005
|
});
|
|
12845
13006
|
}
|
|
12846
13007
|
if (set !== void 0) {
|
|
@@ -12983,7 +13144,7 @@ function buildProgram() {
|
|
|
12983
13144
|
return program;
|
|
12984
13145
|
}
|
|
12985
13146
|
|
|
12986
|
-
//
|
|
13147
|
+
// packages/cli/src/bin.ts
|
|
12987
13148
|
try {
|
|
12988
13149
|
await buildProgram().parseAsync(process.argv);
|
|
12989
13150
|
} catch (err) {
|
|
@@ -12994,7 +13155,7 @@ try {
|
|
|
12994
13155
|
const cliError2 = {
|
|
12995
13156
|
error: err.message,
|
|
12996
13157
|
code: "INPUT_VALIDATION",
|
|
12997
|
-
remediation:
|
|
13158
|
+
remediation: `Run \`${tendrilCommand("--help")}\` or \`${tendrilCommand("<command> --describe")}\` for usage.`
|
|
12998
13159
|
};
|
|
12999
13160
|
process.stderr.write(`${JSON.stringify(cliError2)}
|
|
13000
13161
|
`);
|