tutuca 0.9.30 → 0.9.32

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/README.md CHANGED
@@ -95,6 +95,7 @@ tutuca help [command]
95
95
  | `lint [name]` | Run lint checks — all, or one by name (exit 2 on errors) |
96
96
  | `render [name] [--title t] [--view v]` | Render examples to HTML |
97
97
  | `doctor` | Lint + render smoke test over the whole module |
98
+ | `install-skill [--user] [--force]` | Install the tutuca Claude Code skill (no module path needed) |
98
99
 
99
100
  Global flags: `-f, --format <cli\|md\|json\|html>`, `-o, --output <file>`, `--pretty`, `-h, --help`.
100
101
 
@@ -138,6 +139,27 @@ The invocation stays short even without wrapping, but common patterns:
138
139
  - **`justfile` / `Makefile`** — one recipe per subcommand, passing through positionals
139
140
  - **Programmatic** — `import "tutuca/cli"` (the bundled entry) for custom build integration
140
141
 
142
+ ## Use with Claude Code
143
+
144
+ Tutuca ships an LLM-facing reference (`SKILL.md` + `core.md` / `cli.md` /
145
+ `advanced.md`) packaged as a [Claude Code skill](https://docs.claude.com/en/docs/claude-code/skills).
146
+ Once installed, Claude auto-loads it whenever a session touches tutuca
147
+ components, views, macros, or the CLI.
148
+
149
+ ```sh
150
+ # project-scoped: writes ./.claude/skills/tutuca/ (commit it for the team)
151
+ npx tutuca install-skill
152
+
153
+ # or user-scoped: writes ~/.claude/skills/tutuca/
154
+ npx tutuca install-skill --user
155
+
156
+ # overwrite an existing install
157
+ npx tutuca install-skill --force
158
+ ```
159
+
160
+ The skill content is generated from `docs/llm/`, so the same reference
161
+ runs locally (`tutuca <module> doctor`) and inside Claude.
162
+
141
163
  ## License
142
164
 
143
165
  MIT
@@ -1147,6 +1147,8 @@ class AttrParser {
1147
1147
  this.parseThen(s);
1148
1148
  else if (directiveName.startsWith("else."))
1149
1149
  this.parseElse(s);
1150
+ else
1151
+ this.px.onParseIssue("unknown-directive", { name: directiveName, value: s });
1150
1152
  }
1151
1153
  _parseWhen(s) {
1152
1154
  if (this.eachAttr !== null)
@@ -1332,6 +1334,30 @@ function optimizeNode(node) {
1332
1334
  node.optimize();
1333
1335
  return node;
1334
1336
  }
1337
+ function processXExtras(node, attrs, opName, startIdx, px) {
1338
+ const consumed = X_OP_CONSUMED[opName];
1339
+ const wrappable = X_OP_WRAPPABLE.has(opName);
1340
+ const wrappers = [];
1341
+ for (let i = startIdx;i < attrs.length; i++) {
1342
+ const a = attrs[i];
1343
+ const aName = a.name;
1344
+ if (consumed.has(aName))
1345
+ continue;
1346
+ if (wrappable && X_ATTR_WRAPPERS[aName]) {
1347
+ wrappers.push([X_ATTR_WRAPPERS[aName], vp.parseCondValue(a.value, px)]);
1348
+ continue;
1349
+ }
1350
+ const issueInfo = { op: opName, name: aName, value: a.value };
1351
+ px.onParseIssue("unknown-x-attr", issueInfo);
1352
+ }
1353
+ for (let i = wrappers.length - 1;i >= 0; i--) {
1354
+ const [Cls, val] = wrappers[i];
1355
+ const wrapper = px.addNodeIf(Cls, val, node);
1356
+ if (wrapper !== null)
1357
+ node = wrapper;
1358
+ }
1359
+ return node;
1360
+ }
1335
1361
  function wrap(node, px, wrappers) {
1336
1362
  if (wrappers) {
1337
1363
  for (let i = wrappers.length - 1;i >= 0; i--) {
@@ -1394,7 +1420,7 @@ class ParseContext {
1394
1420
  parseHTML(html) {
1395
1421
  const t = this.document.createElement("template");
1396
1422
  t.innerHTML = html;
1397
- return Array.from(t.content.childNodes);
1423
+ return t.content.childNodes;
1398
1424
  }
1399
1425
  addNodeIf(Class, val, extra) {
1400
1426
  if (val !== null) {
@@ -1438,6 +1464,9 @@ class ParseContext {
1438
1464
  return this.nodes[id] ?? null;
1439
1465
  }
1440
1466
  onAttributes(_attrs, _wrapperAttrs, _textChild, _isMacroCall) {}
1467
+ onParseIssue(kind, info) {
1468
+ console.warn(`tutuca parse issue [${kind}]`, info);
1469
+ }
1441
1470
  }
1442
1471
  function condenseChildsWhites(childs) {
1443
1472
  if (childs.length === 0)
@@ -1517,7 +1546,7 @@ function compileModifiers(eventName, names) {
1517
1546
  return w(this, f, args, ctx);
1518
1547
  };
1519
1548
  }
1520
- var TextNode, CommentNode, ChildsNode, DomNode, FragmentNode, maybeFragment = (xs) => xs.length === 1 ? xs[0] : new FragmentNode(xs), VALID_NODE_RE, ANode, MacroNode, RenderViewId, RenderNode, RenderItNode, RenderEachNode, RenderTextNode, RenderOnceNode, WrapperNode, ShowNode, HideNode, PushViewNameNode, SlotNode, ScopeNode, EachNode, filterAlwaysTrue = (_v, _k, _seq) => true, nullLoopWith = (seq) => ({ seq }), WRAPPER_NODES, _htmlBlockTags = "ADDRESS,ARTICLE,ASIDE,BLOCKQUOTE,CAPTION,COL,COLGROUP,DETAILS,DIALOG,DIV,DD,DL,DT,FIELDSET,FIGCAPTION,FIGURE,FOOTER,FORM,H1,H2,H3,H4,H5,H6,HEADER,HGROUP,HR,LEGEND,LI,MAIN,MENU,NAV,OL,P,PRE,SECTION,SUMMARY,TABLE,TBODY,TD,TFOOT,TH,THEAD,TR,UL", HTML_BLOCK_TAGS, isBlockDomNode = (n) => {
1549
+ var TextNode, CommentNode, ChildsNode, DomNode, FragmentNode, maybeFragment = (xs) => xs.length === 1 ? xs[0] : new FragmentNode(xs), VALID_NODE_RE, ANode, MacroNode, RenderViewId, RenderNode, RenderItNode, RenderEachNode, RenderTextNode, RenderOnceNode, WrapperNode, ShowNode, HideNode, PushViewNameNode, SlotNode, ScopeNode, EachNode, filterAlwaysTrue = (_v, _k, _seq) => true, nullLoopWith = (seq) => ({ seq }), X_OP_CONSUMED, X_OP_WRAPPABLE, X_ATTR_WRAPPERS, WRAPPER_NODES, _htmlBlockTags = "ADDRESS,ARTICLE,ASIDE,BLOCKQUOTE,CAPTION,COL,COLGROUP,DETAILS,DIALOG,DIV,DD,DL,DT,FIELDSET,FIGCAPTION,FIGURE,FOOTER,FORM,H1,H2,H3,H4,H5,H6,HEADER,HGROUP,HR,LEGEND,LI,MAIN,MENU,NAV,OL,P,PRE,SECTION,SUMMARY,TABLE,TBODY,TD,TFOOT,TH,THEAD,TR,UL", HTML_BLOCK_TAGS, isBlockDomNode = (n) => {
1521
1550
  const node = n instanceof FragmentNode ? n.childs[0] : n;
1522
1551
  return node instanceof DomNode && HTML_BLOCK_TAGS.has(node.tagName);
1523
1552
  }, isMac, fwdIfCtxPred = (pred) => (w) => (that, f, args, ctx) => pred(ctx) ? w(that, f, args, ctx) : that, fwdIfKey = (keyName) => fwdIfCtxPred((ctx) => ctx.e.key === keyName), fwdCtrl, fwdMeta, fwdAlt, metaWraps, MOD_WRAPPERS_BY_EVENT, identityModifierWrapper = (f, _ctx) => f;
@@ -1614,7 +1643,21 @@ var init_anode = __esm(() => {
1614
1643
  return this.val.toPathItem();
1615
1644
  }
1616
1645
  static parse(html, px) {
1617
- return ANode.fromDOM(px.parseHTML(html)[0] ?? new px.Text(""), px);
1646
+ const nodes = px.parseHTML(html);
1647
+ if (nodes.length === 0)
1648
+ return new CommentNode("Empty View in ANode.parse");
1649
+ if (nodes.length === 1)
1650
+ return ANode.fromDOM(nodes[0], px);
1651
+ const childs = [];
1652
+ for (let i = 0;i < nodes.length; i++) {
1653
+ const child = ANode.fromDOM(nodes[i], px);
1654
+ if (child !== null)
1655
+ childs.push(child);
1656
+ }
1657
+ const trimmed = condenseChildsWhites(childs);
1658
+ if (trimmed.length === 0)
1659
+ return new CommentNode("Empty View in ANode.parse");
1660
+ return maybeFragment(trimmed);
1618
1661
  }
1619
1662
  static fromDOM(node, px) {
1620
1663
  if (node instanceof px.Text)
@@ -1629,27 +1672,37 @@ var init_anode = __esm(() => {
1629
1672
  if (tag === "X" || isPseudoX) {
1630
1673
  if (attrs.length === 0)
1631
1674
  return maybeFragment(childs);
1632
- const { name, value } = attrs[isPseudoX ? 1 : 0];
1675
+ const opIdx = isPseudoX ? 1 : 0;
1676
+ const { name, value } = attrs[opIdx];
1633
1677
  const as = attrs.getNamedItem("as")?.value ?? null;
1678
+ let node2;
1634
1679
  switch (name) {
1635
1680
  case "slot":
1636
- return new SlotNode(null, vp.const(value), maybeFragment(childs));
1637
- case "text": {
1638
- const v = vp.parseText(value, px);
1639
- return v !== null ? new RenderTextNode(null, v) : null;
1640
- }
1681
+ node2 = new SlotNode(null, vp.const(value), maybeFragment(childs));
1682
+ break;
1683
+ case "text":
1684
+ node2 = px.addNodeIf(RenderTextNode, vp.parseText(value, px));
1685
+ break;
1641
1686
  case "render":
1642
- return px.addNodeIf(RenderNode, vp.parseRender(value, px), as);
1687
+ node2 = px.addNodeIf(RenderNode, vp.parseRender(value, px), as);
1688
+ break;
1643
1689
  case "render-it":
1644
- return px.addNodeIf(RenderItNode, vp.bindValIt, as);
1690
+ node2 = px.addNodeIf(RenderItNode, vp.bindValIt, as);
1691
+ break;
1645
1692
  case "render-each":
1646
- return RenderEachNode.parse(px, vp, value, as, attrs);
1693
+ node2 = RenderEachNode.parse(px, vp, value, as, attrs);
1694
+ break;
1647
1695
  case "show":
1648
- return px.addNodeIf(ShowNode, vp.parseCondValue(value, px), maybeFragment(childs));
1696
+ node2 = px.addNodeIf(ShowNode, vp.parseCondValue(value, px), maybeFragment(childs));
1697
+ break;
1649
1698
  case "hide":
1650
- return px.addNodeIf(HideNode, vp.parseCondValue(value, px), maybeFragment(childs));
1699
+ node2 = px.addNodeIf(HideNode, vp.parseCondValue(value, px), maybeFragment(childs));
1700
+ break;
1701
+ default:
1702
+ px.onParseIssue("unknown-x-op", { name, value });
1703
+ return new CommentNode(`Error: InvalidSpecialTagOp ${name}=${value}`);
1651
1704
  }
1652
- return new CommentNode(`Error: InvalidSpecialTagOp ${name}=${value}`);
1705
+ return processXExtras(node2, attrs, name, (isPseudoX ? 1 : 0) + 1, px);
1653
1706
  } else if (tag.charCodeAt(1) === 58 && tag.charCodeAt(0) === 88) {
1654
1707
  const macroName = tag.slice(2).toLowerCase();
1655
1708
  if (macroName === "slot") {
@@ -1833,6 +1886,17 @@ var init_anode = __esm(() => {
1833
1886
  }
1834
1887
  static register = true;
1835
1888
  };
1889
+ X_OP_CONSUMED = {
1890
+ slot: new Set,
1891
+ text: new Set,
1892
+ render: new Set(["as"]),
1893
+ "render-it": new Set(["as"]),
1894
+ "render-each": new Set(["as", "when", "loop-with"]),
1895
+ show: new Set,
1896
+ hide: new Set
1897
+ };
1898
+ X_OP_WRAPPABLE = new Set(["text", "render", "render-it", "render-each"]);
1899
+ X_ATTR_WRAPPERS = { show: ShowNode, hide: HideNode };
1836
1900
  WRAPPER_NODES = {
1837
1901
  slot: SlotNode,
1838
1902
  show: ShowNode,
@@ -2025,7 +2089,11 @@ class ComponentStack {
2025
2089
  }
2026
2090
  }
2027
2091
  registerMacros(macros) {
2028
- Object.assign(this.macros, macros);
2092
+ for (const key in macros) {
2093
+ const lower = key.toLowerCase();
2094
+ console.assert(this.macros[lower] === undefined, "macro key collision", lower);
2095
+ this.macros[lower] = macros[key];
2096
+ }
2029
2097
  }
2030
2098
  getCompFor(v) {
2031
2099
  return this.comps.getCompFor(v);
@@ -2069,11 +2137,29 @@ function checkComponent(Comp, lx = new LintContext) {
2069
2137
  });
2070
2138
  }
2071
2139
  function checkView(lx, view, Comp, referencedAlters, referencedComputed) {
2140
+ checkParseIssues(lx, view);
2072
2141
  checkRenderItInLoop(lx, view);
2073
2142
  checkEventModifiers(lx, view);
2074
2143
  checkKnownHandlerNames(lx, view, Comp, referencedAlters, referencedComputed);
2075
2144
  checkMacroCallArgs(lx, view, Comp);
2076
2145
  }
2146
+ function checkParseIssues(lx, view) {
2147
+ const issues = view.ctx.parseIssues;
2148
+ if (!issues)
2149
+ return;
2150
+ for (const { kind, info } of issues) {
2151
+ const id = PARSE_ISSUE_KIND_TO_LINT_ID[kind];
2152
+ if (!id)
2153
+ continue;
2154
+ lx.error(id, info);
2155
+ const known = AT_PREFIX_HINT_KNOWN_BY_KIND[kind];
2156
+ if (known && info.name?.startsWith("@")) {
2157
+ const suggestion = info.name.slice(1);
2158
+ if (known.has(suggestion))
2159
+ lx.hint(MAYBE_DROP_AT_PREFIX, { ...info, suggestion });
2160
+ }
2161
+ }
2162
+ }
2077
2163
  function checkMacroCallArgs(lx, view, Comp) {
2078
2164
  const { scope } = Comp;
2079
2165
  for (const macroNode of view.ctx.macroNodes) {
@@ -2083,7 +2169,10 @@ function checkMacroCallArgs(lx, view, Comp) {
2083
2169
  const { defaults } = macro;
2084
2170
  for (const argName in macroNode.attrs) {
2085
2171
  if (!(argName in defaults)) {
2086
- lx.error(UNKNOWN_MACRO_ARG, { name: argName, macroName: macroNode.name });
2172
+ lx.error(UNKNOWN_MACRO_ARG, {
2173
+ name: argName,
2174
+ macroName: macroNode.name
2175
+ });
2087
2176
  }
2088
2177
  }
2089
2178
  }
@@ -2244,7 +2333,7 @@ function checkConsistentAttrVal(lx, val, fields, proto, computed, scope, alter,
2244
2333
  if (alter[val.name] === undefined) {
2245
2334
  lx.error(ALT_HANDLER_NOT_DEFINED, { name: val.name });
2246
2335
  }
2247
- } else if (valName !== "ConstVal" && valName !== "BindVal") {
2336
+ } else if (valName !== "ConstVal" && valName !== "BindVal" && valName !== "DynVal") {
2248
2337
  console.log(val);
2249
2338
  }
2250
2339
  }
@@ -2359,9 +2448,28 @@ class LintContext {
2359
2448
  this.reports.push({ id, info, level, context: { ...this.frame } });
2360
2449
  }
2361
2450
  }
2362
- var ALT_HANDLER_NOT_DEFINED = "ALT_HANDLER_NOT_DEFINED", ALT_HANDLER_NOT_REFERENCED = "ALT_HANDLER_NOT_REFERENCED", RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP", UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER", UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME", INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED", INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED", INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED", INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD", INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER", FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED", COMPUTED_VAL_NOT_DEFINED = "COMPUTED_VAL_NOT_DEFINED", COMPUTED_NOT_REFERENCED = "COMPUTED_NOT_REFERENCED", DUPLICATE_ATTR_DEFINITION = "DUPLICATE_ATTR_DEFINITION", UNKNOWN_REQUEST_NAME = "UNKNOWN_REQUEST_NAME", UNKNOWN_COMPONENT_NAME = "UNKNOWN_COMPONENT_NAME", UNKNOWN_MACRO_ARG = "UNKNOWN_MACRO_ARG", LEVEL_WARN = "warn", LEVEL_ERROR = "error", LEVEL_HINT = "hint", NO_WRAPPERS, KNOWN_HANDLER_NAMES, LintParseContext;
2451
+ var ALT_HANDLER_NOT_DEFINED = "ALT_HANDLER_NOT_DEFINED", ALT_HANDLER_NOT_REFERENCED = "ALT_HANDLER_NOT_REFERENCED", RENDER_IT_OUTSIDE_OF_LOOP = "RENDER_IT_OUTSIDE_OF_LOOP", UNKNOWN_EVENT_MODIFIER = "UNKNOWN_EVENT_MODIFIER", UNKNOWN_HANDLER_ARG_NAME = "UNKNOWN_HANDLER_ARG_NAME", INPUT_HANDLER_NOT_IMPLEMENTED = "INPUT_HANDLER_NOT_IMPLEMENTED", INPUT_HANDLER_NOT_REFERENCED = "INPUT_HANDLER_NOT_REFERENCED", INPUT_HANDLER_METHOD_NOT_IMPLEMENTED = "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED", INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD = "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD", INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER = "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER", FIELD_VAL_NOT_DEFINED = "FIELD_VAL_NOT_DEFINED", COMPUTED_VAL_NOT_DEFINED = "COMPUTED_VAL_NOT_DEFINED", COMPUTED_NOT_REFERENCED = "COMPUTED_NOT_REFERENCED", DUPLICATE_ATTR_DEFINITION = "DUPLICATE_ATTR_DEFINITION", UNKNOWN_REQUEST_NAME = "UNKNOWN_REQUEST_NAME", UNKNOWN_COMPONENT_NAME = "UNKNOWN_COMPONENT_NAME", UNKNOWN_MACRO_ARG = "UNKNOWN_MACRO_ARG", UNKNOWN_DIRECTIVE = "UNKNOWN_DIRECTIVE", UNKNOWN_X_OP = "UNKNOWN_X_OP", UNKNOWN_X_ATTR = "UNKNOWN_X_ATTR", MAYBE_DROP_AT_PREFIX = "MAYBE_DROP_AT_PREFIX", PARSE_ISSUE_KIND_TO_LINT_ID, X_KNOWN_OP_NAMES, X_KNOWN_ATTR_NAMES, AT_PREFIX_HINT_KNOWN_BY_KIND, LEVEL_WARN = "warn", LEVEL_ERROR = "error", LEVEL_HINT = "hint", NO_WRAPPERS, KNOWN_HANDLER_NAMES, LintParseContext;
2363
2452
  var init_lint_check = __esm(() => {
2364
2453
  init_anode();
2454
+ PARSE_ISSUE_KIND_TO_LINT_ID = {
2455
+ "unknown-directive": UNKNOWN_DIRECTIVE,
2456
+ "unknown-x-op": UNKNOWN_X_OP,
2457
+ "unknown-x-attr": UNKNOWN_X_ATTR
2458
+ };
2459
+ X_KNOWN_OP_NAMES = new Set([
2460
+ "slot",
2461
+ "text",
2462
+ "render",
2463
+ "render-it",
2464
+ "render-each",
2465
+ "show",
2466
+ "hide"
2467
+ ]);
2468
+ X_KNOWN_ATTR_NAMES = new Set(["as", "when", "loop-with", "show", "hide"]);
2469
+ AT_PREFIX_HINT_KNOWN_BY_KIND = {
2470
+ "unknown-x-op": X_KNOWN_OP_NAMES,
2471
+ "unknown-x-attr": X_KNOWN_ATTR_NAMES
2472
+ };
2365
2473
  NO_WRAPPERS = {};
2366
2474
  KNOWN_HANDLER_NAMES = new Set([
2367
2475
  "value",
@@ -2387,10 +2495,14 @@ var init_lint_check = __esm(() => {
2387
2495
  constructor(document2, Text, Comment) {
2388
2496
  super(document2, Text, Comment);
2389
2497
  this.attrs = [];
2498
+ this.parseIssues = [];
2390
2499
  }
2391
2500
  onAttributes(attrs, wrapperAttrs, textChild, isMacroCall = false) {
2392
2501
  this.attrs.push({ attrs, wrapperAttrs, textChild, isMacroCall });
2393
2502
  }
2503
+ onParseIssue(kind, info) {
2504
+ this.parseIssues.push({ kind, info });
2505
+ }
2394
2506
  };
2395
2507
  });
2396
2508
 
@@ -7719,7 +7831,7 @@ class Renderer {
7719
7831
  this.cache = new WeakMapDomCache;
7720
7832
  }
7721
7833
  getSeqInfo(seq) {
7722
- return isIndexed(seq) ? imIndexedIter : isKeyed(seq) ? imKeyedIter : seqInfoByClass.get(seq?.constructor) ?? unkIter;
7834
+ return isIndexed(seq) ? imIndexedIter : isKeyed(seq) ? imKeyedIter : seq?.[SEQ_INFO] ?? unkIter;
7723
7835
  }
7724
7836
  renderTag(tag, attrs, childs) {
7725
7837
  return h(tag, attrs, childs);
@@ -7835,12 +7947,12 @@ var DATASET_ATTRS, imIndexedIter = (seq, visit) => {
7835
7947
  }, imKeyedIter = (seq, visit) => {
7836
7948
  for (const [k, v] of seq.toSeq().entries())
7837
7949
  visit(k, v, "sk");
7838
- }, unkIter = () => {}, seqInfoByClass;
7950
+ }, unkIter = () => {}, SEQ_INFO;
7839
7951
  var init_renderer = __esm(() => {
7840
7952
  init_immutable();
7841
7953
  init_vdom();
7842
7954
  DATASET_ATTRS = ["nid", "cid", "eid", "vid", "si", "sk"];
7843
- seqInfoByClass = new Map;
7955
+ SEQ_INFO = Symbol.for("tutuca.seqInfo");
7844
7956
  });
7845
7957
 
7846
7958
  // src/util/render.js
@@ -8040,11 +8152,105 @@ var init__registry = __esm(() => {
8040
8152
  };
8041
8153
  });
8042
8154
 
8155
+ // tools/cli/commands/install-skill.js
8156
+ var exports_install_skill = {};
8157
+ __export(exports_install_skill, {
8158
+ run: () => run,
8159
+ describe: () => describe
8160
+ });
8161
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
8162
+ import { homedir } from "node:os";
8163
+ import { dirname, resolve } from "node:path";
8164
+ import { parseArgs } from "node:util";
8165
+ import { fileURLToPath } from "node:url";
8166
+ function findSkillDir() {
8167
+ const here = dirname(fileURLToPath(import.meta.url));
8168
+ const candidates = [resolve(here, "..", "..", "..", "skill"), resolve(here, "..", "skill")];
8169
+ for (const c of candidates) {
8170
+ if (existsSync(resolve(c, "SKILL.md")))
8171
+ return c;
8172
+ }
8173
+ return null;
8174
+ }
8175
+ function targetDir(scope) {
8176
+ if (scope === "user")
8177
+ return resolve(homedir(), ".claude/skills/tutuca");
8178
+ return resolve(process.cwd(), ".claude/skills/tutuca");
8179
+ }
8180
+ async function run(argv) {
8181
+ const parsed = parseArgs({
8182
+ args: argv,
8183
+ options: {
8184
+ user: { type: "boolean", default: false },
8185
+ project: { type: "boolean", default: false },
8186
+ force: { type: "boolean", short: "f", default: false },
8187
+ help: { type: "boolean", short: "h", default: false }
8188
+ },
8189
+ allowPositionals: false
8190
+ });
8191
+ if (parsed.values.help) {
8192
+ process.stdout.write(`tutuca install-skill [--user | --project] [--force]
8193
+ ` + `
8194
+ ` + ` Copies SKILL.md + core.md + cli.md + advanced.md into
8195
+ ` + ` .claude/skills/tutuca/. Defaults to --project (cwd).
8196
+ ` + ` --user installs at ~/.claude/skills/tutuca/.
8197
+ ` + ` --force overwrites existing files.
8198
+ `);
8199
+ return;
8200
+ }
8201
+ if (parsed.values.user && parsed.values.project) {
8202
+ process.stderr.write(`tutuca: --user and --project are mutually exclusive
8203
+ `);
8204
+ process.exit(1);
8205
+ }
8206
+ const scope = parsed.values.user ? "user" : "project";
8207
+ const target = targetDir(scope);
8208
+ const src = findSkillDir();
8209
+ if (!src) {
8210
+ process.stderr.write(`tutuca: skill assets not found alongside this CLI.
8211
+ ` + "If you're running from a checkout, run `bun scripts/build-skill.js` first.\n");
8212
+ process.exit(1);
8213
+ }
8214
+ if (existsSync(target) && !parsed.values.force) {
8215
+ const existing = readdirSync(target).filter((n) => SKILL_FILES.includes(n));
8216
+ if (existing.length > 0) {
8217
+ process.stderr.write(`tutuca: ${target} already contains skill files. Re-run with --force to overwrite.
8218
+ `);
8219
+ process.exit(1);
8220
+ }
8221
+ }
8222
+ mkdirSync(target, { recursive: true });
8223
+ for (const name of SKILL_FILES) {
8224
+ const from = resolve(src, name);
8225
+ if (!existsSync(from)) {
8226
+ process.stderr.write(`tutuca: missing skill asset: ${from}
8227
+ `);
8228
+ process.exit(1);
8229
+ }
8230
+ const buf = readFileSync(from);
8231
+ writeFileSync(resolve(target, name), buf);
8232
+ }
8233
+ const rel = scope === "project" ? ".claude/skills/tutuca" : target;
8234
+ process.stdout.write(`installed tutuca skill → ${rel}
8235
+ `);
8236
+ process.stdout.write(`Open a Claude Code session in this directory to use it.
8237
+ `);
8238
+ }
8239
+ var describe = "Install the tutuca Claude Code skill into .claude/skills/tutuca/.", SKILL_FILES;
8240
+ var init_install_skill = __esm(() => {
8241
+ SKILL_FILES = ["SKILL.md", "core.md", "cli.md", "advanced.md"];
8242
+ });
8243
+
8043
8244
  // tools/tutuca.js
8044
8245
  init__registry();
8045
8246
 
8046
8247
  // tools/cli/commands/help.js
8047
- var describe = "Show usage. `help <command>` for per-command detail.";
8248
+ var exports_help = {};
8249
+ __export(exports_help, {
8250
+ run: () => run2,
8251
+ describe: () => describe2
8252
+ });
8253
+ var describe2 = "Show usage. `help <command>` for per-command detail.";
8048
8254
  var OVERVIEW = `tutuca — CLI for inspecting, documenting, linting and rendering tutuca
8049
8255
  components defined in an ES module.
8050
8256
 
@@ -8122,6 +8328,11 @@ COMMANDS (no module required)
8122
8328
  Without [command]: prints this full reference.
8123
8329
  With [command]: prints that command's one-line description.
8124
8330
 
8331
+ install-skill [--user | --project] [--force]
8332
+ Copy the bundled Claude Code skill (SKILL.md + core/cli/advanced.md)
8333
+ into .claude/skills/tutuca/. Default scope is --project (cwd);
8334
+ --user installs at ~/.claude/skills/tutuca/. --force overwrites.
8335
+
8125
8336
  GLOBAL FLAGS
8126
8337
  -f, --format <cli|md|json|html>
8127
8338
  Output format. Defaults per command:
@@ -8163,19 +8374,22 @@ EXAMPLES
8163
8374
  # CI smoke test
8164
8375
  tutuca ./src/components.js doctor
8165
8376
  `;
8166
- async function run(argv) {
8377
+ async function run2(argv) {
8167
8378
  const target = argv?.[0];
8168
8379
  if (!target) {
8169
8380
  process.stdout.write(OVERVIEW);
8170
8381
  return;
8171
8382
  }
8172
8383
  if (target === "help") {
8173
- process.stdout.write(`help: ${describe}
8384
+ process.stdout.write(`help: ${describe2}
8174
8385
  `);
8175
8386
  return;
8176
8387
  }
8177
8388
  const { COMMANDS: COMMANDS2 } = await Promise.resolve().then(() => (init__registry(), exports__registry));
8178
- const cmd = COMMANDS2[target];
8389
+ const noModule = {
8390
+ "install-skill": await Promise.resolve().then(() => (init_install_skill(), exports_install_skill))
8391
+ };
8392
+ const cmd = COMMANDS2[target] ?? noModule[target];
8179
8393
  if (!cmd) {
8180
8394
  process.stderr.write(`tutuca: unknown command: ${target}
8181
8395
  `);
@@ -8187,8 +8401,11 @@ async function run(argv) {
8187
8401
  process.stdout.write("Run `tutuca help` for the full reference including signatures and flags.\n");
8188
8402
  }
8189
8403
 
8404
+ // tools/tutuca.js
8405
+ init_install_skill();
8406
+
8190
8407
  // tools/cli/with-module.js
8191
- import { parseArgs } from "node:util";
8408
+ import { parseArgs as parseArgs2 } from "node:util";
8192
8409
 
8193
8410
  // tools/cli/env.js
8194
8411
  init_anode();
@@ -8218,16 +8435,16 @@ async function createNodeEnv() {
8218
8435
  }
8219
8436
 
8220
8437
  // tools/cli/load.js
8221
- import { resolve } from "node:path";
8438
+ import { resolve as resolve2 } from "node:path";
8222
8439
  async function loadAndNormalize(modulePath) {
8223
- const abs = resolve(modulePath);
8440
+ const abs = resolve2(modulePath);
8224
8441
  const mod = await import(abs);
8225
8442
  const { normalized } = normalizeModule(mod, { path: abs });
8226
8443
  return normalized;
8227
8444
  }
8228
8445
 
8229
8446
  // tools/cli/output.js
8230
- import { writeFileSync } from "node:fs";
8447
+ import { writeFileSync as writeFileSync2 } from "node:fs";
8231
8448
 
8232
8449
  // tools/format/cli.js
8233
8450
  var exports_cli = {};
@@ -8619,7 +8836,7 @@ async function formatResult(formatName, result, options = {}) {
8619
8836
  async function emit(result, { format: format5, pretty, output }) {
8620
8837
  const text = await formatResult(format5, result, { pretty });
8621
8838
  if (output) {
8622
- writeFileSync(output, text);
8839
+ writeFileSync2(output, text);
8623
8840
  } else {
8624
8841
  process.stdout.write(text);
8625
8842
  if (!text.endsWith(`
@@ -8631,7 +8848,7 @@ async function emit(result, { format: format5, pretty, output }) {
8631
8848
 
8632
8849
  // tools/cli/with-module.js
8633
8850
  async function runCommand(cmd, argv, globalOpts) {
8634
- const parsed = parseArgs({
8851
+ const parsed = parseArgs2({
8635
8852
  args: argv,
8636
8853
  options: cmd.parseOptions ?? {},
8637
8854
  allowPositionals: true
@@ -8652,6 +8869,10 @@ async function runCommand(cmd, argv, globalOpts) {
8652
8869
  }
8653
8870
 
8654
8871
  // tools/tutuca.js
8872
+ var NO_MODULE_COMMANDS = {
8873
+ help: exports_help,
8874
+ "install-skill": exports_install_skill
8875
+ };
8655
8876
  function usageError(msg) {
8656
8877
  process.stderr.write(`tutuca: ${msg}
8657
8878
  Run \`tutuca help\` for usage.
@@ -8688,13 +8909,13 @@ function extractGlobals(argv) {
8688
8909
  async function main() {
8689
8910
  const { opts, rest } = extractGlobals(process.argv.slice(2));
8690
8911
  if (rest.length === 0 || opts.help && rest.length === 0) {
8691
- await run([], opts);
8912
+ await run2([], opts);
8692
8913
  return;
8693
8914
  }
8694
8915
  let command;
8695
8916
  let commandArgs;
8696
- if (rest[0] === "help") {
8697
- command = "help";
8917
+ if (NO_MODULE_COMMANDS[rest[0]]) {
8918
+ command = rest[0];
8698
8919
  commandArgs = rest.slice(1);
8699
8920
  } else if (opts.module) {
8700
8921
  command = rest[0];
@@ -8706,12 +8927,13 @@ async function main() {
8706
8927
  command = rest[1];
8707
8928
  commandArgs = rest.slice(2);
8708
8929
  }
8709
- if (opts.help) {
8710
- await run([command], opts);
8930
+ if (NO_MODULE_COMMANDS[command]) {
8931
+ const args = opts.help ? [...commandArgs, "--help"] : commandArgs;
8932
+ await NO_MODULE_COMMANDS[command].run(args, opts);
8711
8933
  return;
8712
8934
  }
8713
- if (command === "help") {
8714
- await run(commandArgs, opts);
8935
+ if (opts.help) {
8936
+ await run2([command], opts);
8715
8937
  return;
8716
8938
  }
8717
8939
  const cmd = COMMANDS[command];