@tendrilapp/cli 0.1.17 → 0.1.18

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 CHANGED
@@ -23,8 +23,11 @@ Non-negotiables (the CLI enforces these; do not fight them):
23
23
  ## Recording a new component (needs Figma MCP)
24
24
 
25
25
  1. Fetch the component set's frame metadata via Figma MCP
26
- (`get_metadata` on the frame); save the VERBATIM response to a file.
27
- 2. `tendril_record_plan` with that file the queue plan. The plan
26
+ (`get_metadata` on the frame).
27
+ 2. `tendril_record_plan` with the response text passed VERBATIM via
28
+ `metadataParts` (one array entry per response block, in order —
29
+ single-block responses may use `metadata` instead; nothing is saved
30
+ to a file, and never hand-join blocks) → the queue plan. The plan
28
31
  records the FULL variant matrix — every pose the set defines
29
32
  (sampling exists only as a human-run CLI flag; the MCP surface
30
33
  cannot sample). CROSS-CHECK `variantsFound` in the plan output
@@ -51,6 +54,9 @@ Non-negotiables (the CLI enforces these; do not fight them):
51
54
  every open-source face the recording declares, no questions needed.
52
55
  Only faces that FAIL there are a licensing decision for the user:
53
56
  offer `tendril fonts add` (Recommended) or a disclosed substitute.
57
+ A substitute never certifies: scores are capped at pass and a
58
+ cert-bar run exits fonts-unproven after its report — resolving the
59
+ real faces is the only path to certification.
54
60
  3. Record each planned rep with FOUR tool calls: make the THREE
55
61
  Figma MCP calls in protocol order — get_metadata, then
56
62
  get_design_context (excludeScreenshot=true), then get_screenshot —
@@ -25,26 +25,44 @@ var optStr = (d) => z.string().optional().describe(d);
25
25
  var TOOLS = [
26
26
  {
27
27
  name: "tendril_record_plan",
28
- description: "ENTRY POINT for implementing/building a React component from a Figma design or figma.com URL \u2014 start the Tendril pipeline here (after loading the tendril skill, if installed). Plans a recording session: computes the rep queue (anchor + one-factor + conflict crosses) from verbatim get_metadata envelope file(s) and persists the set manifest. Resumes if the set already exists. The output may carry USER QUESTIONS \u2014 defaultsToConfirm (which pose is the component's default) or a multiple-component-sets error (which set to record): render them to a present user and apply the answers via `defaults` / `componentSet`; non-interactive runs follow each question's stated fallback. Recording cost is stated in figmaCallEstimate, never asked about \u2014 proceed with what the user provided.",
28
+ description: "ENTRY POINT for implementing/building a React component from a Figma design or figma.com URL \u2014 start the Tendril pipeline here (after loading the tendril skill, if installed). Plans a recording session: computes the rep queue (anchor + one-factor + conflict crosses) from the verbatim get_metadata response (pass its blocks via metadataParts \u2014 no file to write) and persists the set manifest. Resumes if the set already exists. The output may carry USER QUESTIONS \u2014 defaultsToConfirm (which pose is the component's default) or a multiple-component-sets error (which set to record): render them to a present user and apply the answers via `defaults` / `componentSet`; non-interactive runs follow each question's stated fallback. Recording cost is stated in figmaCallEstimate, never asked about \u2014 proceed with what the user provided.",
29
29
  schema: z.object({
30
30
  setDir: str("recording set directory to create/resume"),
31
31
  component: str("component/system name"),
32
- metadataFiles: z.array(z.string()).describe('verbatim get_metadata envelope file paths \u2014 the tool response saved AS-IS, JSON shape {"content":[{"type":"text","text":"<frame \u2026>"}]}; raw copied text is rejected. Optionally <file>@<frameId>'),
32
+ // Parts array FIRST, same reason as ingest_rep: real responses
33
+ // are usually multi-block, and the file param made plan the ONE
34
+ // remaining hand-built-envelope entry point (run 10: the agent
35
+ // wrote the file twice — once as text, once as JSON envelope).
36
+ metadataParts: z.array(z.string()).optional().describe("the frame-level get_metadata response blocks, every block in order, each verbatim \u2014 never hand-join or save them yourself; this is the NORMAL param"),
37
+ metadata: optStr("ONLY when get_metadata genuinely returned one single block: its text verbatim (otherwise use metadataParts)"),
38
+ metadataFiles: z.array(z.string()).optional().describe('saved verbatim get_metadata envelope file paths \u2014 JSON shape {"content":[{"type":"text","text":"<frame \u2026>"}]}; optionally <file>@<frameId>. Prefer metadataParts: no file to write'),
33
39
  defaults: z.array(z.string()).optional().describe(`axis defaults as "Axis=Value" (from the user's defaultsToConfirm answers; may re-plan a set with nothing recorded yet)`),
34
40
  componentSet: optStr("record only this component set (the user's pick from a multiple-component-sets error)")
35
41
  }),
36
- argv: (i) => [
37
- "record",
38
- "plan",
39
- "--set",
40
- i["setDir"],
41
- "--component",
42
- i["component"],
43
- "--metadata",
44
- ...i["metadataFiles"],
45
- ...i["defaults"] !== void 0 ? ["--default", ...i["defaults"]] : [],
46
- ...i["componentSet"] !== void 0 ? ["--component-set", i["componentSet"]] : []
47
- ]
42
+ // Texts ride temp files, never argv: Windows caps a command line at
43
+ // ~32 KB and metadata envelopes can exceed it.
44
+ argv: (i) => {
45
+ const argvOut = ["record", "plan", "--set", i["setDir"], "--component", i["component"]];
46
+ if (i["metadata"] !== void 0 && i["metadataParts"] !== void 0) throw new Error("pass at most one of `metadata` and `metadataParts`");
47
+ const files = i["metadataFiles"];
48
+ if (files !== void 0 && files.length > 0) argvOut.push("--metadata", ...files);
49
+ const single = i["metadata"];
50
+ const parts = i["metadataParts"];
51
+ if (single !== void 0 || parts !== void 0) {
52
+ const tmp = path.join(mkdtempSync(path.join(os.tmpdir(), "tendril-envelope-")), "response.txt");
53
+ if (single !== void 0) {
54
+ writeFileSync(tmp, single);
55
+ argvOut.push("--metadata-raw-file", tmp);
56
+ } else {
57
+ if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
58
+ writeFileSync(tmp, JSON.stringify(parts));
59
+ argvOut.push("--metadata-raw-parts-file", tmp);
60
+ }
61
+ }
62
+ if (argvOut.length === 6) throw new Error("nothing to plan from \u2014 pass metadataParts (normal), metadata (single-block), or metadataFiles");
63
+ argvOut.push(...i["defaults"] !== void 0 ? ["--default", ...i["defaults"]] : [], ...i["componentSet"] !== void 0 ? ["--component-set", i["componentSet"]] : []);
64
+ return argvOut;
65
+ }
48
66
  },
49
67
  {
50
68
  name: "tendril_doctor",
@@ -81,7 +99,7 @@ var TOOLS = [
81
99
  // multi-block (run 6: 49/49 reps — metadata 2 blocks, design
82
100
  // context 5-6), so the arrays are the norm and the single-string
83
101
  // params the rare case, not the reverse.
84
- metadataParts: z.array(z.string()).optional().describe("get_metadata response blocks, every block in order, each verbatim \u2014 never hand-join blocks yourself. Real responses are almost always multi-block; this is the NORMAL param."),
102
+ metadataParts: z.array(z.string()).optional().describe("get_metadata response blocks, every block in order, each verbatim \u2014 never hand-join blocks yourself. Multi-block responses are common (run 6: 49/49 reps); a single block wrapped in a one-element array is equally fine."),
85
103
  contextParts: z.array(z.string()).optional().describe("get_design_context response blocks, every block in order, each verbatim \u2014 the NORMAL param (real responses arrive as 5-6 blocks)"),
86
104
  screenshotUrl: optStr("image_url from the get_screenshot response, verbatim \u2014 the CLI downloads it; the bytes never pass through your context"),
87
105
  metadata: optStr("ONLY when get_metadata genuinely returned one single block: its text verbatim (otherwise use metadataParts)"),
@@ -379,7 +397,7 @@ var IMPLEMENT_PROMPT = {
379
397
  build: (figmaUrl) => [
380
398
  `Implement the Figma component at ${figmaUrl} as a pixel-verified React component using the Tendril pipeline.`,
381
399
  "- If the tendril skill (or plugin) is installed, load it and follow it.",
382
- "- Otherwise: start at tendril_record_plan; while recording, make exactly the Figma call each tendril_record_next step names (get_metadata comes before any get_design_context) and save responses verbatim; then tendril_engine_brief, implement the candidate, and tendril_engine_score until the bar passes; finish with tendril_verify.",
400
+ "- Otherwise: start at tendril_record_plan; while recording, make exactly the Figma call each tendril_record_next step names (get_metadata comes before any get_design_context) and pass every response VERBATIM to the ingest tools (no files to save \u2014 the parts params take the response blocks directly); then tendril_engine_brief, implement the candidate, and tendril_engine_score until the bar passes; finish with tendril_verify.",
383
401
  "- Verdicts come only from Tendril's scores \u2014 never claim or estimate fidelity numbers yourself."
384
402
  ].join("\n")
385
403
  };