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 +22 -0
- package/dist/tutuca-cli.js +261 -39
- package/dist/tutuca-dev.js +159 -26
- package/dist/tutuca-dev.min.js +3 -3
- package/dist/tutuca-extra.js +92 -24
- package/dist/tutuca-extra.min.js +3 -3
- package/dist/tutuca.js +89 -21
- package/dist/tutuca.min.js +3 -3
- package/package.json +9 -4
- package/skill/SKILL.md +36 -0
- package/skill/advanced.md +170 -0
- package/skill/cli.md +113 -0
- package/skill/core.md +687 -0
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
|
package/dist/tutuca-cli.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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
|
-
|
|
1687
|
+
node2 = px.addNodeIf(RenderNode, vp.parseRender(value, px), as);
|
|
1688
|
+
break;
|
|
1643
1689
|
case "render-it":
|
|
1644
|
-
|
|
1690
|
+
node2 = px.addNodeIf(RenderItNode, vp.bindValIt, as);
|
|
1691
|
+
break;
|
|
1645
1692
|
case "render-each":
|
|
1646
|
-
|
|
1693
|
+
node2 = RenderEachNode.parse(px, vp, value, as, attrs);
|
|
1694
|
+
break;
|
|
1647
1695
|
case "show":
|
|
1648
|
-
|
|
1696
|
+
node2 = px.addNodeIf(ShowNode, vp.parseCondValue(value, px), maybeFragment(childs));
|
|
1697
|
+
break;
|
|
1649
1698
|
case "hide":
|
|
1650
|
-
|
|
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
|
|
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
|
-
|
|
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, {
|
|
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 :
|
|
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 = () => {},
|
|
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
|
-
|
|
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
|
|
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
|
|
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: ${
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
8912
|
+
await run2([], opts);
|
|
8692
8913
|
return;
|
|
8693
8914
|
}
|
|
8694
8915
|
let command;
|
|
8695
8916
|
let commandArgs;
|
|
8696
|
-
if (rest[0]
|
|
8697
|
-
command =
|
|
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 (
|
|
8710
|
-
|
|
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 (
|
|
8714
|
-
await
|
|
8935
|
+
if (opts.help) {
|
|
8936
|
+
await run2([command], opts);
|
|
8715
8937
|
return;
|
|
8716
8938
|
}
|
|
8717
8939
|
const cmd = COMMANDS[command];
|