tutuca 0.9.55 → 0.9.57
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 +25 -21
- package/dist/tutuca-cli.js +1129 -541
- package/dist/tutuca-dev.js +11 -3
- package/dist/tutuca-dev.min.js +2 -2
- package/dist/tutuca-extra.js +5 -1
- package/dist/tutuca-extra.min.js +1 -1
- package/dist/tutuca.js +5 -1
- package/dist/tutuca.min.js +1 -1
- package/package.json +2 -2
- package/skill/immutable-js/SKILL.md +12 -13
- package/skill/tutuca/cli.md +82 -29
- package/skill/tutuca/core.md +7 -7
- package/skill/tutuca/testing.md +5 -5
package/dist/tutuca-cli.js
CHANGED
|
@@ -3326,14 +3326,18 @@ class ComponentSummary {
|
|
|
3326
3326
|
}
|
|
3327
3327
|
|
|
3328
3328
|
class ComponentList {
|
|
3329
|
-
constructor({ items }) {
|
|
3329
|
+
constructor({ items, total = null, truncated = false }) {
|
|
3330
3330
|
this.items = items;
|
|
3331
|
+
this.total = total ?? items.length;
|
|
3332
|
+
this.truncated = truncated;
|
|
3331
3333
|
}
|
|
3332
3334
|
}
|
|
3333
3335
|
|
|
3334
3336
|
class ExampleIndex {
|
|
3335
|
-
constructor({ sections }) {
|
|
3337
|
+
constructor({ sections, total = null, truncated = false }) {
|
|
3336
3338
|
this.sections = sections;
|
|
3339
|
+
this.total = total ?? sections.reduce((n, s) => n + (s.items?.length ?? 0), 0);
|
|
3340
|
+
this.truncated = truncated;
|
|
3337
3341
|
}
|
|
3338
3342
|
}
|
|
3339
3343
|
|
|
@@ -3815,13 +3819,37 @@ function summarize(comp) {
|
|
|
3815
3819
|
fields
|
|
3816
3820
|
});
|
|
3817
3821
|
}
|
|
3818
|
-
function listComponents(normalized, { name = null } = {}) {
|
|
3822
|
+
function listComponents(normalized, { name = null, limit = 0 } = {}) {
|
|
3819
3823
|
const comps = normalized.components;
|
|
3820
3824
|
const picked = name === null ? comps : comps.filter((c) => c.name === name);
|
|
3821
|
-
|
|
3825
|
+
const total = picked.length;
|
|
3826
|
+
const capped = limit > 0 ? picked.slice(0, limit) : picked;
|
|
3827
|
+
return new ComponentList({
|
|
3828
|
+
items: capped.map(summarize),
|
|
3829
|
+
total,
|
|
3830
|
+
truncated: capped.length < total
|
|
3831
|
+
});
|
|
3822
3832
|
}
|
|
3823
|
-
function listExamples(normalized) {
|
|
3824
|
-
|
|
3833
|
+
function listExamples(normalized, { limit = 0 } = {}) {
|
|
3834
|
+
const sections = normalized.sections;
|
|
3835
|
+
const total = sections.reduce((n, s) => n + (s.items?.length ?? 0), 0);
|
|
3836
|
+
if (limit <= 0) {
|
|
3837
|
+
return new ExampleIndex({ sections, total, truncated: false });
|
|
3838
|
+
}
|
|
3839
|
+
const capped = [];
|
|
3840
|
+
let remaining = limit;
|
|
3841
|
+
for (const s of sections) {
|
|
3842
|
+
if (remaining <= 0)
|
|
3843
|
+
break;
|
|
3844
|
+
const items = s.items.slice(0, remaining);
|
|
3845
|
+
capped.push({ ...s, items });
|
|
3846
|
+
remaining -= items.length;
|
|
3847
|
+
}
|
|
3848
|
+
return new ExampleIndex({
|
|
3849
|
+
sections: capped,
|
|
3850
|
+
total,
|
|
3851
|
+
truncated: capped.reduce((n, s) => n + s.items.length, 0) < total
|
|
3852
|
+
});
|
|
3825
3853
|
}
|
|
3826
3854
|
var init_list = () => {};
|
|
3827
3855
|
|
|
@@ -8726,7 +8754,8 @@ var init_transactor = __esm(() => {
|
|
|
8726
8754
|
}
|
|
8727
8755
|
handlerProp = null;
|
|
8728
8756
|
getHandlerForName(comp) {
|
|
8729
|
-
|
|
8757
|
+
const handlers = comp?.[this.handlerProp];
|
|
8758
|
+
return handlers?.[this.name] ?? handlers?.$unknown ?? nullHandler;
|
|
8730
8759
|
}
|
|
8731
8760
|
getHandlerAndArgs(_root, instance, comps) {
|
|
8732
8761
|
const handler = this.getHandlerForName(comps.getCompFor(instance));
|
|
@@ -8754,6 +8783,9 @@ var init_transactor = __esm(() => {
|
|
|
8754
8783
|
}
|
|
8755
8784
|
};
|
|
8756
8785
|
EventContext = class EventContext extends Dispatcher {
|
|
8786
|
+
get name() {
|
|
8787
|
+
return this.parent?.name ?? null;
|
|
8788
|
+
}
|
|
8757
8789
|
stopPropagation() {
|
|
8758
8790
|
return this.parent.stopPropagation();
|
|
8759
8791
|
}
|
|
@@ -14082,6 +14114,12 @@ var exports__registry = {};
|
|
|
14082
14114
|
__export(exports__registry, {
|
|
14083
14115
|
COMMANDS: () => COMMANDS
|
|
14084
14116
|
});
|
|
14117
|
+
function parseLimit(raw) {
|
|
14118
|
+
if (raw === undefined || raw === null)
|
|
14119
|
+
return 0;
|
|
14120
|
+
const n = Number.parseInt(raw, 10);
|
|
14121
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
14122
|
+
}
|
|
14085
14123
|
var COMMANDS;
|
|
14086
14124
|
var init__registry = __esm(() => {
|
|
14087
14125
|
init_chai();
|
|
@@ -14092,7 +14130,7 @@ var init__registry = __esm(() => {
|
|
|
14092
14130
|
init_render2();
|
|
14093
14131
|
init_test();
|
|
14094
14132
|
COMMANDS = {
|
|
14095
|
-
|
|
14133
|
+
get: {
|
|
14096
14134
|
describe: "Summarize the module's exports and counts.",
|
|
14097
14135
|
defaultFormat: "cli",
|
|
14098
14136
|
run: (normalized) => describeModule(normalized.mod, { path: normalized.path })
|
|
@@ -14100,15 +14138,20 @@ var init__registry = __esm(() => {
|
|
|
14100
14138
|
list: {
|
|
14101
14139
|
describe: "List components in the module.",
|
|
14102
14140
|
defaultFormat: "cli",
|
|
14103
|
-
|
|
14141
|
+
parseOptions: { limit: { type: "string" } },
|
|
14142
|
+
run: (normalized, { values, positionals }) => listComponents(normalized, {
|
|
14143
|
+
name: positionals[0] ?? null,
|
|
14144
|
+
limit: parseLimit(values.limit)
|
|
14145
|
+
})
|
|
14104
14146
|
},
|
|
14105
14147
|
examples: {
|
|
14106
14148
|
describe: "List examples in the module.",
|
|
14107
14149
|
defaultFormat: "cli",
|
|
14108
|
-
|
|
14150
|
+
parseOptions: { limit: { type: "string" } },
|
|
14151
|
+
run: (normalized, { values }) => listExamples(normalized, { limit: parseLimit(values.limit) })
|
|
14109
14152
|
},
|
|
14110
|
-
|
|
14111
|
-
describe: "
|
|
14153
|
+
show: {
|
|
14154
|
+
describe: "Show API docs for components (optional <name> for one).",
|
|
14112
14155
|
defaultFormat: "md",
|
|
14113
14156
|
run: (normalized, { positionals }) => docComponents(normalized, { name: positionals[0] ?? null })
|
|
14114
14157
|
},
|
|
@@ -14159,160 +14202,807 @@ var init__registry = __esm(() => {
|
|
|
14159
14202
|
};
|
|
14160
14203
|
});
|
|
14161
14204
|
|
|
14162
|
-
// tools/
|
|
14163
|
-
|
|
14164
|
-
|
|
14165
|
-
|
|
14166
|
-
|
|
14167
|
-
|
|
14168
|
-
|
|
14169
|
-
|
|
14170
|
-
|
|
14171
|
-
|
|
14172
|
-
|
|
14173
|
-
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
if (existsSync(resolve(c, "tutuca", "SKILL.md")))
|
|
14178
|
-
return c;
|
|
14205
|
+
// tools/format/lint.js
|
|
14206
|
+
function unsupportedExprMessage(info) {
|
|
14207
|
+
const v = JSON.stringify(info.value);
|
|
14208
|
+
const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
|
|
14209
|
+
switch (info.role) {
|
|
14210
|
+
case "attr":
|
|
14211
|
+
return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
|
|
14212
|
+
case "directive":
|
|
14213
|
+
return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
|
|
14214
|
+
case "if":
|
|
14215
|
+
return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
|
|
14216
|
+
case "x-op":
|
|
14217
|
+
return `Unsupported ${label} ${v} in <x ${info.op}>`;
|
|
14218
|
+
default:
|
|
14219
|
+
return `Unsupported ${label} ${v}`;
|
|
14179
14220
|
}
|
|
14180
|
-
return null;
|
|
14181
|
-
}
|
|
14182
|
-
function targetDir(scope, name, dotAgents) {
|
|
14183
|
-
const base = scope === "user" ? homedir() : process.cwd();
|
|
14184
|
-
const dir = dotAgents ? ".agents/skills" : ".claude/skills";
|
|
14185
|
-
return resolve(base, dir, name);
|
|
14186
14221
|
}
|
|
14187
|
-
function
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14222
|
+
function badValueMessage(info) {
|
|
14223
|
+
const v = JSON.stringify(info.value);
|
|
14224
|
+
switch (info.role) {
|
|
14225
|
+
case "attr":
|
|
14226
|
+
return `Cannot parse value ${v} for attribute ':${info.attr}'`;
|
|
14227
|
+
case "directive":
|
|
14228
|
+
return `Cannot parse value ${v} for directive '@${info.directive}'`;
|
|
14229
|
+
case "if":
|
|
14230
|
+
return `Cannot parse condition ${v} for '@if.${info.attr}'`;
|
|
14231
|
+
case "x-op":
|
|
14232
|
+
return `Cannot parse value ${v} for <x ${info.op}>`;
|
|
14233
|
+
case "handler-name":
|
|
14234
|
+
return `Cannot parse handler name ${v}`;
|
|
14235
|
+
case "handler-arg":
|
|
14236
|
+
return `Cannot parse handler argument ${v}`;
|
|
14237
|
+
case "macro-var":
|
|
14238
|
+
return `Macro variable '^${info.name}' is not defined`;
|
|
14239
|
+
default:
|
|
14240
|
+
return `Cannot parse value ${v}`;
|
|
14195
14241
|
}
|
|
14196
|
-
return false;
|
|
14197
14242
|
}
|
|
14198
|
-
function
|
|
14199
|
-
|
|
14200
|
-
if (!existsSync(resolve(src, "SKILL.md"))) {
|
|
14201
|
-
process.stderr.write(`tutuca: missing skill assets for ${skill.name} at ${src}
|
|
14202
|
-
`);
|
|
14203
|
-
process.exit(1);
|
|
14204
|
-
}
|
|
14205
|
-
const target = targetDir(scope, skill.name, dotAgents);
|
|
14206
|
-
if (targetHasSkillFiles(target) && !force) {
|
|
14207
|
-
process.stderr.write(`tutuca: ${target} already contains skill files. Re-run with --force to overwrite.
|
|
14208
|
-
`);
|
|
14209
|
-
process.exit(1);
|
|
14210
|
-
}
|
|
14211
|
-
mkdirSync(target, { recursive: true });
|
|
14212
|
-
cpSync(src, target, { recursive: true });
|
|
14213
|
-
const baseDir = dotAgents ? ".agents/skills" : ".claude/skills";
|
|
14214
|
-
const rel = scope === "project" ? `${baseDir}/${skill.name}` : target;
|
|
14215
|
-
process.stdout.write(`installed ${skill.name} skill → ${rel}
|
|
14216
|
-
`);
|
|
14243
|
+
function tagDisplay(tag) {
|
|
14244
|
+
return tag ? String(tag).toLowerCase() : null;
|
|
14217
14245
|
}
|
|
14218
|
-
|
|
14219
|
-
const
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
help: { type: "boolean", short: "h", default: false }
|
|
14230
|
-
},
|
|
14231
|
-
allowPositionals: false
|
|
14232
|
-
});
|
|
14233
|
-
if (parsed.values.help) {
|
|
14234
|
-
process.stdout.write(`tutuca install-skill [--user | --project] [--margaui-skill | --immutable-skill | --all] [--dot-agents] [--force]
|
|
14235
|
-
` + `
|
|
14236
|
-
` + ` Installs Claude Code skill assets into .claude/skills/<name>/.
|
|
14237
|
-
` + ` Defaults to --project (cwd); --user installs at ~/.claude/skills/.
|
|
14238
|
-
` + `
|
|
14239
|
-
` + ` Selection:
|
|
14240
|
-
` + ` (default) install the tutuca skill
|
|
14241
|
-
` + ` --margaui-skill install the margaui skill instead
|
|
14242
|
-
` + ` --immutable-skill install the immutable-js skill instead
|
|
14243
|
-
` + ` --all install every bundled skill (tutuca + margaui + immutable-js)
|
|
14244
|
-
` + `
|
|
14245
|
-
` + ` --dot-agents installs into .agents/skills/ instead of .claude/skills/.
|
|
14246
|
-
` + ` --force overwrites existing files.
|
|
14247
|
-
`);
|
|
14248
|
-
return;
|
|
14249
|
-
}
|
|
14250
|
-
if (parsed.values.user && parsed.values.project) {
|
|
14251
|
-
process.stderr.write(`tutuca: --user and --project are mutually exclusive
|
|
14252
|
-
`);
|
|
14253
|
-
process.exit(1);
|
|
14254
|
-
}
|
|
14255
|
-
const selectionFlags = ["margaui-skill", "immutable-skill", "all"].filter((k) => parsed.values[k]);
|
|
14256
|
-
if (selectionFlags.length > 1) {
|
|
14257
|
-
process.stderr.write(`tutuca: ${selectionFlags.map((f) => `--${f}`).join(", ")} are mutually exclusive
|
|
14258
|
-
`);
|
|
14259
|
-
process.exit(1);
|
|
14260
|
-
}
|
|
14261
|
-
const scope = parsed.values.user ? "user" : "project";
|
|
14262
|
-
const root = findSkillsRoot();
|
|
14263
|
-
if (!root) {
|
|
14264
|
-
process.stderr.write(`tutuca: skill assets not found alongside this CLI.
|
|
14265
|
-
` + "If you're running from a checkout, run `bun scripts/build-skill.js` first.\n");
|
|
14266
|
-
process.exit(1);
|
|
14246
|
+
function fmtTagSuffix(info) {
|
|
14247
|
+
const t = tagDisplay(info?.tag);
|
|
14248
|
+
return t && t !== "x" ? ` on <${t}>` : "";
|
|
14249
|
+
}
|
|
14250
|
+
function fmtOriginSuffix(info) {
|
|
14251
|
+
if (!info)
|
|
14252
|
+
return "";
|
|
14253
|
+
const parts = [];
|
|
14254
|
+
if (info.originAttr) {
|
|
14255
|
+
const branch = info.branch ? `[${info.branch}]` : "";
|
|
14256
|
+
parts.push(`in ${info.originAttr}${branch}`);
|
|
14267
14257
|
}
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
selected = SKILLS;
|
|
14271
|
-
} else {
|
|
14272
|
-
const selFlag = SKILLS.find((s) => s.flag && parsed.values[s.flag]);
|
|
14273
|
-
selected = selFlag ? [selFlag] : SKILLS.filter((s) => s.name === "tutuca");
|
|
14258
|
+
if (info.handlerName) {
|
|
14259
|
+
parts.push(`handler '${info.handlerName}'${info.argIndex !== undefined ? ` arg ${info.argIndex}` : ""}`);
|
|
14274
14260
|
}
|
|
14275
|
-
|
|
14276
|
-
|
|
14261
|
+
const t = tagDisplay(info.tag);
|
|
14262
|
+
if (t && t !== "x")
|
|
14263
|
+
parts.push(`on <${t}>`);
|
|
14264
|
+
return parts.length ? ` (${parts.join(", ")})` : "";
|
|
14265
|
+
}
|
|
14266
|
+
function fmtEventSuffix(info) {
|
|
14267
|
+
if (info?.originAttr)
|
|
14268
|
+
return ` in ${info.originAttr}`;
|
|
14269
|
+
if (info?.eventName)
|
|
14270
|
+
return ` in @on.${info.eventName}`;
|
|
14271
|
+
return "";
|
|
14272
|
+
}
|
|
14273
|
+
function lintIdToMessage(id, info) {
|
|
14274
|
+
switch (id) {
|
|
14275
|
+
case "RENDER_IT_OUTSIDE_OF_LOOP":
|
|
14276
|
+
return "<x render-it> used outside of a loop";
|
|
14277
|
+
case "UNKNOWN_EVENT_MODIFIER": {
|
|
14278
|
+
const mods = info.handler?.modifiers ?? [info.modifier];
|
|
14279
|
+
const written = `@on.${info.name}+${mods.join("+")}`;
|
|
14280
|
+
return `Unknown modifier '+${info.modifier}' in '${written}'`;
|
|
14281
|
+
}
|
|
14282
|
+
case "UNKNOWN_HANDLER_ARG_NAME":
|
|
14283
|
+
return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
|
|
14284
|
+
case "INPUT_HANDLER_NOT_IMPLEMENTED":
|
|
14285
|
+
return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
14286
|
+
case "INPUT_HANDLER_NOT_REFERENCED":
|
|
14287
|
+
return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
|
|
14288
|
+
case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
|
|
14289
|
+
return `Method '.${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
14290
|
+
case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
|
|
14291
|
+
return `'.${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
|
|
14292
|
+
case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
|
|
14293
|
+
return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
|
|
14294
|
+
case "FIELD_VAL_NOT_DEFINED":
|
|
14295
|
+
return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
|
|
14296
|
+
case "DUPLICATE_ATTR_DEFINITION": {
|
|
14297
|
+
const sources = info.sources?.length ? ` (${info.sources.join(", ")})` : "";
|
|
14298
|
+
const tag = info.tag ? ` on <${String(info.tag).toLowerCase()}>` : "";
|
|
14299
|
+
return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
|
|
14300
|
+
}
|
|
14301
|
+
case "IF_NO_BRANCH_SET":
|
|
14302
|
+
return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
|
|
14303
|
+
case "UNKNOWN_REQUEST_NAME":
|
|
14304
|
+
return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
|
|
14305
|
+
case "UNKNOWN_COMPONENT_NAME":
|
|
14306
|
+
return `Unknown component '${info.name}'${fmtOriginSuffix(info)}`;
|
|
14307
|
+
case "ALT_HANDLER_NOT_DEFINED":
|
|
14308
|
+
return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
|
|
14309
|
+
case "ALT_HANDLER_NOT_REFERENCED":
|
|
14310
|
+
return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
|
|
14311
|
+
case "UNKNOWN_MACRO_ARG":
|
|
14312
|
+
return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
|
|
14313
|
+
case "UNKNOWN_DIRECTIVE":
|
|
14314
|
+
return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
|
|
14315
|
+
case "UNKNOWN_X_OP":
|
|
14316
|
+
return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
|
|
14317
|
+
case "UNKNOWN_X_ATTR":
|
|
14318
|
+
return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
|
|
14319
|
+
case "MAYBE_DROP_AT_PREFIX": {
|
|
14320
|
+
const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
|
|
14321
|
+
return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
|
|
14322
|
+
}
|
|
14323
|
+
case "BAD_VALUE":
|
|
14324
|
+
return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
|
|
14325
|
+
case "UNSUPPORTED_EXPR_SYNTAX":
|
|
14326
|
+
return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
|
|
14327
|
+
case "REDUNDANT_TEMPLATE_STRING":
|
|
14328
|
+
return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
|
|
14329
|
+
case "UNKNOWN_COMPONENT_SPEC_KEY":
|
|
14330
|
+
return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
|
|
14331
|
+
case "HTML_TAG_NAME_HAS_UPPERCASE":
|
|
14332
|
+
return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
|
|
14333
|
+
case "HTML_SVG_TAG_WILL_LOWERCASE":
|
|
14334
|
+
return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
|
|
14335
|
+
case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
|
|
14336
|
+
return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} — ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
|
|
14337
|
+
case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
|
|
14338
|
+
return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
|
|
14339
|
+
case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
|
|
14340
|
+
return `Void element <${info.tag}> has an explicit close tag${fmtLocationSuffix(info)}`;
|
|
14341
|
+
case "HTML_DUPLICATE_FORM":
|
|
14342
|
+
return `Nested <form> — the inner form will be dropped by the parser${fmtLocationSuffix(info)}`;
|
|
14343
|
+
case "HTML_NESTED_INTERACTIVE":
|
|
14344
|
+
return `<${info.tag}> nested inside another <${info.tag}> — adoption agency will reorder${fmtLocationSuffix(info)}`;
|
|
14345
|
+
case "HTML_MISNESTED_FORMATTING":
|
|
14346
|
+
return `Misnested formatting tag </${info.tag}> — adoption agency will reorder nodes${fmtLocationSuffix(info)}`;
|
|
14347
|
+
case "HTML_UNEXPECTED_END_TAG":
|
|
14348
|
+
return `Unexpected end tag </${info.tag}>${fmtLocationSuffix(info)}`;
|
|
14349
|
+
case "HTML_UNCLOSED_BEFORE_END":
|
|
14350
|
+
return `<${info.unclosed}> still open when </${info.tag}> was seen — implicitly closed${fmtLocationSuffix(info)}`;
|
|
14351
|
+
case "HTML_DUPLICATE_ATTRIBUTE":
|
|
14352
|
+
return `Duplicate attribute '${info.name}' — second occurrence dropped${fmtLocationSuffix(info)}`;
|
|
14353
|
+
case "HTML_ATTRIBUTES_ON_END_TAG":
|
|
14354
|
+
return `Attributes on end tag </${info.tag}> — dropped by the parser${fmtLocationSuffix(info)}`;
|
|
14355
|
+
case "HTML_SELF_CLOSING_END_TAG":
|
|
14356
|
+
return `Self-closing end tag </${info.tag}/> — trailing '/' is meaningless${fmtLocationSuffix(info)}`;
|
|
14357
|
+
case "HTML_MISSING_ATTRIBUTE_VALUE":
|
|
14358
|
+
return `Attribute '${info.name}' is missing a value${fmtLocationSuffix(info)}`;
|
|
14359
|
+
case "HTML_CDATA_IN_HTML_NAMESPACE":
|
|
14360
|
+
return `CDATA section in HTML namespace — reinterpreted as a bogus comment${fmtLocationSuffix(info)}`;
|
|
14361
|
+
case "HTML_BOGUS_COMMENT":
|
|
14362
|
+
return `Bogus comment — content dropped by the parser${fmtLocationSuffix(info)}`;
|
|
14363
|
+
case "HTML_SVG_ATTR_WILL_LOWERCASE":
|
|
14364
|
+
return `SVG attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
|
|
14365
|
+
case "HTML_MATHML_ATTR_WILL_LOWERCASE":
|
|
14366
|
+
return `MathML attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
|
|
14367
|
+
case "LINT_ERROR":
|
|
14368
|
+
return info.message;
|
|
14369
|
+
default:
|
|
14370
|
+
return id;
|
|
14277
14371
|
}
|
|
14278
|
-
process.stdout.write(`Open a Claude Code session in this directory to use it.
|
|
14279
|
-
`);
|
|
14280
14372
|
}
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14373
|
+
function suggestionToMessage(suggestion) {
|
|
14374
|
+
if (!suggestion)
|
|
14375
|
+
return null;
|
|
14376
|
+
switch (suggestion.kind) {
|
|
14377
|
+
case "replace-name":
|
|
14378
|
+
return `did you mean '${suggestion.to}'?`;
|
|
14379
|
+
case "drop-prefix":
|
|
14380
|
+
return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
|
|
14381
|
+
case "add-prefix":
|
|
14382
|
+
return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
|
|
14383
|
+
case "remove":
|
|
14384
|
+
return `remove ${suggestion.what}`;
|
|
14385
|
+
case "rewrite":
|
|
14386
|
+
return `use '${suggestion.to}' instead of '${suggestion.from}'`;
|
|
14387
|
+
case "wrap":
|
|
14388
|
+
return `wrap it in ${suggestion.to}`;
|
|
14389
|
+
case "rephrase":
|
|
14390
|
+
return suggestion.text ?? null;
|
|
14391
|
+
default:
|
|
14392
|
+
return null;
|
|
14393
|
+
}
|
|
14394
|
+
}
|
|
14395
|
+
function fmtLocationSuffix(info) {
|
|
14396
|
+
const loc = info?.location;
|
|
14397
|
+
if (!loc)
|
|
14398
|
+
return "";
|
|
14399
|
+
return ` at line ${loc.line}, col ${loc.column}`;
|
|
14400
|
+
}
|
|
14401
|
+
function htmlActionPhrase(action, tag, parent) {
|
|
14402
|
+
switch (action) {
|
|
14403
|
+
case "ignored":
|
|
14404
|
+
return `the parser will drop this <${tag}>`;
|
|
14405
|
+
case "drop":
|
|
14406
|
+
return `the parser will drop this <${tag}>`;
|
|
14407
|
+
case "auto-close-implicit":
|
|
14408
|
+
return `the parser will close <${parent ?? "?"}> first, then place <${tag}> as a sibling`;
|
|
14409
|
+
case "foster-parent":
|
|
14410
|
+
return `the parser will move <${tag}> outside <${parent ?? "?"}> (foster-parenting)`;
|
|
14411
|
+
case "foreign-breakout":
|
|
14412
|
+
return `the parser will exit foreign content and re-process <${tag}> in HTML mode`;
|
|
14413
|
+
default:
|
|
14414
|
+
return `parser action: ${action}`;
|
|
14415
|
+
}
|
|
14416
|
+
}
|
|
14417
|
+
var UNSUPPORTED_EXPR_LABEL;
|
|
14418
|
+
var init_lint2 = __esm(() => {
|
|
14419
|
+
UNSUPPORTED_EXPR_LABEL = {
|
|
14420
|
+
ternary: "ternary expression",
|
|
14421
|
+
comparison: "comparison",
|
|
14422
|
+
logical: "logical expression",
|
|
14423
|
+
"call-with-args": "method call with arguments"
|
|
14424
|
+
};
|
|
14425
|
+
});
|
|
14426
|
+
|
|
14427
|
+
// tools/cli/errors.js
|
|
14428
|
+
var exports_errors = {};
|
|
14429
|
+
__export(exports_errors, {
|
|
14430
|
+
parseArgsErrorShape: () => parseArgsErrorShape,
|
|
14431
|
+
emitError: () => emitError,
|
|
14432
|
+
didYouMean: () => didYouMean,
|
|
14433
|
+
CODES: () => CODES
|
|
14434
|
+
});
|
|
14435
|
+
function isJsonFormat(opts) {
|
|
14436
|
+
return opts?.format === "json";
|
|
14437
|
+
}
|
|
14438
|
+
function emitError(opts, shape) {
|
|
14439
|
+
const exit = shape.exit ?? 1;
|
|
14440
|
+
if (isJsonFormat(opts)) {
|
|
14441
|
+
const envelope = {
|
|
14442
|
+
error: {
|
|
14443
|
+
code: shape.code ?? CODES.INTERNAL,
|
|
14444
|
+
message: shape.message
|
|
14445
|
+
}
|
|
14446
|
+
};
|
|
14447
|
+
if (shape.suggestion)
|
|
14448
|
+
envelope.error.suggestion = shape.suggestion;
|
|
14449
|
+
if (shape.hint)
|
|
14450
|
+
envelope.error.hint = shape.hint;
|
|
14451
|
+
if (shape.where)
|
|
14452
|
+
envelope.error.where = shape.where;
|
|
14453
|
+
process.stderr.write(`${JSON.stringify(envelope)}
|
|
14454
|
+
`);
|
|
14455
|
+
} else {
|
|
14456
|
+
const where = shape.where ? `[${shape.where}] ` : "";
|
|
14457
|
+
process.stderr.write(`tutuca: ${where}${shape.message}
|
|
14458
|
+
`);
|
|
14459
|
+
const tail = suggestionToMessage(shape.suggestion);
|
|
14460
|
+
if (tail)
|
|
14461
|
+
process.stderr.write(` ${tail}
|
|
14462
|
+
`);
|
|
14463
|
+
if (shape.hint)
|
|
14464
|
+
process.stderr.write(` hint: ${shape.hint}
|
|
14465
|
+
`);
|
|
14466
|
+
}
|
|
14467
|
+
process.exit(exit);
|
|
14468
|
+
}
|
|
14469
|
+
function didYouMean(name, candidates) {
|
|
14470
|
+
const close = closestName(name, candidates);
|
|
14471
|
+
return close ? { kind: "replace-name", from: name, to: close } : null;
|
|
14472
|
+
}
|
|
14473
|
+
function parseArgsErrorShape(err, validFlags) {
|
|
14474
|
+
if (!err?.code?.startsWith?.("ERR_PARSE_ARGS_"))
|
|
14475
|
+
return null;
|
|
14476
|
+
if (err.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
|
14477
|
+
const m = err.message.match(/Unknown option '(-{1,2}[^']+)'/);
|
|
14478
|
+
const raw = m?.[1] ?? null;
|
|
14479
|
+
const stripped = raw?.replace(/^-+/, "");
|
|
14480
|
+
const candidates = [...validFlags ?? []];
|
|
14481
|
+
const close = stripped ? closestName(stripped, candidates) : null;
|
|
14482
|
+
return {
|
|
14483
|
+
code: CODES.USAGE_UNKNOWN_FLAG,
|
|
14484
|
+
message: raw ? `Unknown flag '${raw}'` : err.message,
|
|
14485
|
+
suggestion: close ? { kind: "replace-name", from: raw, to: `--${close}` } : null,
|
|
14486
|
+
hint: candidates.length ? `Valid flags: ${candidates.map((f) => `--${f}`).join(", ")}` : null
|
|
14487
|
+
};
|
|
14488
|
+
}
|
|
14489
|
+
if (err.code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE") {
|
|
14490
|
+
return { code: CODES.USAGE_BAD_FLAG_VALUE, message: err.message };
|
|
14491
|
+
}
|
|
14492
|
+
return { code: err.code, message: err.message };
|
|
14493
|
+
}
|
|
14494
|
+
var CODES;
|
|
14495
|
+
var init_errors = __esm(() => {
|
|
14496
|
+
init_lint2();
|
|
14497
|
+
CODES = {
|
|
14498
|
+
USAGE_UNKNOWN_COMMAND: "ERR_USAGE_UNKNOWN_COMMAND",
|
|
14499
|
+
USAGE_UNKNOWN_FLAG: "ERR_USAGE_UNKNOWN_FLAG",
|
|
14500
|
+
USAGE_BAD_FLAG_VALUE: "ERR_USAGE_BAD_FLAG_VALUE",
|
|
14501
|
+
USAGE_MISSING_MODULE: "ERR_USAGE_MISSING_MODULE",
|
|
14502
|
+
USAGE_MUTUALLY_EXCLUSIVE: "ERR_USAGE_MUTUALLY_EXCLUSIVE",
|
|
14503
|
+
USAGE_MISSING_ARGUMENT: "ERR_USAGE_MISSING_ARGUMENT",
|
|
14504
|
+
FORMAT_UNKNOWN: "ERR_FORMAT_UNKNOWN",
|
|
14505
|
+
FORMAT_UNSUPPORTED: "ERR_FORMAT_UNSUPPORTED",
|
|
14506
|
+
MODULE_LOAD_FAILED: "ERR_MODULE_LOAD_FAILED",
|
|
14507
|
+
MODULE_SHAPE_MISMATCH: "EXAMPLES_SHAPE_MISMATCH",
|
|
14508
|
+
SKILL_ASSETS_MISSING: "ERR_SKILL_ASSETS_MISSING",
|
|
14509
|
+
SKILL_TARGET_EXISTS: "ERR_SKILL_TARGET_EXISTS",
|
|
14510
|
+
INTERNAL: "ERR_INTERNAL"
|
|
14511
|
+
};
|
|
14512
|
+
});
|
|
14513
|
+
|
|
14514
|
+
// tools/cli/commands/feedback.js
|
|
14515
|
+
var exports_feedback = {};
|
|
14516
|
+
__export(exports_feedback, {
|
|
14517
|
+
run: () => run2,
|
|
14518
|
+
describe: () => describe2
|
|
14519
|
+
});
|
|
14520
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
14521
|
+
import { homedir } from "node:os";
|
|
14522
|
+
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
14523
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14524
|
+
import { parseArgs } from "node:util";
|
|
14525
|
+
function readPackageVersion() {
|
|
14526
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
14527
|
+
const candidates = [
|
|
14528
|
+
resolve2(here, "..", "..", "..", "package.json"),
|
|
14529
|
+
resolve2(here, "..", "package.json")
|
|
14530
|
+
];
|
|
14531
|
+
for (const p of candidates) {
|
|
14532
|
+
if (existsSync2(p)) {
|
|
14533
|
+
try {
|
|
14534
|
+
return JSON.parse(readFileSync2(p, "utf8")).version ?? null;
|
|
14535
|
+
} catch {}
|
|
14536
|
+
}
|
|
14537
|
+
}
|
|
14538
|
+
return null;
|
|
14539
|
+
}
|
|
14540
|
+
function readStdinSync() {
|
|
14541
|
+
if (process.stdin.isTTY)
|
|
14542
|
+
return "";
|
|
14543
|
+
try {
|
|
14544
|
+
return readFileSync2(0, "utf8");
|
|
14545
|
+
} catch {
|
|
14546
|
+
return "";
|
|
14547
|
+
}
|
|
14548
|
+
}
|
|
14549
|
+
async function run2(argv, opts = {}) {
|
|
14550
|
+
const parsed = parseArgs({
|
|
14551
|
+
args: argv,
|
|
14552
|
+
options: { help: { type: "boolean", short: "h", default: false } },
|
|
14553
|
+
allowPositionals: true
|
|
14554
|
+
});
|
|
14555
|
+
if (parsed.values.help) {
|
|
14556
|
+
process.stdout.write(HELP);
|
|
14557
|
+
return;
|
|
14558
|
+
}
|
|
14559
|
+
const positional = parsed.positionals.join(" ").trim();
|
|
14560
|
+
const message = positional || readStdinSync().trim();
|
|
14561
|
+
if (!message) {
|
|
14562
|
+
emitError(opts, {
|
|
14563
|
+
code: CODES.USAGE_MISSING_ARGUMENT,
|
|
14564
|
+
message: "feedback requires a message (positional arg or piped stdin)",
|
|
14565
|
+
hint: 'Example: `tutuca feedback "the lint code FIELD_VAL_NOT_DEFINED was confusing"` ' + "or `echo ... | tutuca feedback`."
|
|
14566
|
+
});
|
|
14567
|
+
}
|
|
14568
|
+
const record = {
|
|
14569
|
+
ts: new Date().toISOString(),
|
|
14570
|
+
version: readPackageVersion(),
|
|
14571
|
+
message
|
|
14572
|
+
};
|
|
14573
|
+
const dir = resolve2(homedir(), ".tutuca");
|
|
14574
|
+
const file = resolve2(dir, "feedback.jsonl");
|
|
14575
|
+
mkdirSync(dir, { recursive: true });
|
|
14576
|
+
appendFileSync(file, `${JSON.stringify(record)}
|
|
14577
|
+
`);
|
|
14578
|
+
process.stdout.write(`recorded feedback → ${file}
|
|
14579
|
+
`);
|
|
14580
|
+
}
|
|
14581
|
+
var describe2 = "Record freeform feedback (bug, confusion, suggestion) to ~/.tutuca/feedback.jsonl.", HELP;
|
|
14582
|
+
var init_feedback = __esm(() => {
|
|
14583
|
+
init_errors();
|
|
14584
|
+
HELP = `tutuca feedback [message]
|
|
14585
|
+
` + `
|
|
14586
|
+
` + ` Append a feedback record to ~/.tutuca/feedback.jsonl (created if missing).
|
|
14587
|
+
` + ` Use this for bugs, confusing messages, or suggestions about the CLI,
|
|
14588
|
+
` + ` bundled skills, docs, or the library itself.
|
|
14589
|
+
` + `
|
|
14590
|
+
` + ` Provide the message as a positional argument:
|
|
14591
|
+
` + ` tutuca feedback "the lint code FIELD_VAL_NOT_DEFINED was confusing"
|
|
14592
|
+
` + `
|
|
14593
|
+
` + ` ...or via stdin (useful for multi-line notes):
|
|
14594
|
+
` + ` echo "..." | tutuca feedback
|
|
14595
|
+
` + ` tutuca feedback < notes.txt
|
|
14596
|
+
` + `
|
|
14597
|
+
` + ` Each record is one JSON object per line: {ts, version, message}.
|
|
14598
|
+
`;
|
|
14599
|
+
});
|
|
14600
|
+
|
|
14601
|
+
// tools/cli/commands/install-skill.js
|
|
14602
|
+
var exports_install_skill = {};
|
|
14603
|
+
__export(exports_install_skill, {
|
|
14604
|
+
run: () => run3,
|
|
14605
|
+
describe: () => describe3
|
|
14606
|
+
});
|
|
14607
|
+
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync } from "node:fs";
|
|
14608
|
+
import { homedir as homedir2 } from "node:os";
|
|
14609
|
+
import { dirname as dirname3, relative, resolve as resolve3 } from "node:path";
|
|
14610
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
14611
|
+
import { parseArgs as parseArgs2 } from "node:util";
|
|
14612
|
+
function findSkillsRoot() {
|
|
14613
|
+
const here = dirname3(fileURLToPath3(import.meta.url));
|
|
14614
|
+
const candidates = [resolve3(here, "..", "..", "..", "skill"), resolve3(here, "..", "skill")];
|
|
14615
|
+
for (const c of candidates) {
|
|
14616
|
+
if (existsSync3(resolve3(c, "tutuca", "SKILL.md")))
|
|
14617
|
+
return c;
|
|
14618
|
+
}
|
|
14619
|
+
return null;
|
|
14620
|
+
}
|
|
14621
|
+
function targetDir(scope, name, dotAgents) {
|
|
14622
|
+
const base = scope === "user" ? homedir2() : process.cwd();
|
|
14623
|
+
const dir = dotAgents ? ".agents/skills" : ".claude/skills";
|
|
14624
|
+
return resolve3(base, dir, name);
|
|
14625
|
+
}
|
|
14626
|
+
function targetHasSkillFiles(dir) {
|
|
14627
|
+
if (!existsSync3(dir))
|
|
14628
|
+
return false;
|
|
14629
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
14630
|
+
if (entry.isFile() && entry.name.endsWith(".md"))
|
|
14631
|
+
return true;
|
|
14632
|
+
if (entry.isDirectory() && targetHasSkillFiles(resolve3(dir, entry.name)))
|
|
14633
|
+
return true;
|
|
14634
|
+
}
|
|
14635
|
+
return false;
|
|
14636
|
+
}
|
|
14637
|
+
function listFiles(srcRoot, srcDir, acc) {
|
|
14638
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
14639
|
+
const full = resolve3(srcDir, entry.name);
|
|
14640
|
+
if (entry.isDirectory())
|
|
14641
|
+
listFiles(srcRoot, full, acc);
|
|
14642
|
+
else if (entry.isFile())
|
|
14643
|
+
acc.push(relative(srcRoot, full));
|
|
14644
|
+
}
|
|
14645
|
+
return acc;
|
|
14646
|
+
}
|
|
14647
|
+
function installSkill(skill, root, scope, force, dotAgents, dryRun, opts) {
|
|
14648
|
+
const src = resolve3(root, skill.srcSubdir);
|
|
14649
|
+
if (!existsSync3(resolve3(src, "SKILL.md"))) {
|
|
14650
|
+
emitError(opts, {
|
|
14651
|
+
code: CODES.SKILL_ASSETS_MISSING,
|
|
14652
|
+
message: `missing skill assets for '${skill.name}' at ${src}`,
|
|
14653
|
+
hint: "If you're running from a checkout, run `bun scripts/build-skill.js` first."
|
|
14654
|
+
});
|
|
14655
|
+
}
|
|
14656
|
+
const target = targetDir(scope, skill.name, dotAgents);
|
|
14657
|
+
if (targetHasSkillFiles(target) && !force) {
|
|
14658
|
+
emitError(opts, {
|
|
14659
|
+
code: CODES.SKILL_TARGET_EXISTS,
|
|
14660
|
+
message: `${target} already contains skill files`,
|
|
14661
|
+
hint: "Re-run with --force to overwrite, or --dry-run to see what would change."
|
|
14662
|
+
});
|
|
14663
|
+
}
|
|
14664
|
+
const baseDir = dotAgents ? ".agents/skills" : ".claude/skills";
|
|
14665
|
+
const rel = scope === "project" ? `${baseDir}/${skill.name}` : target;
|
|
14666
|
+
if (dryRun) {
|
|
14667
|
+
const files = listFiles(src, src, []);
|
|
14668
|
+
process.stdout.write(`would install ${skill.name} skill → ${rel}
|
|
14669
|
+
`);
|
|
14670
|
+
for (const f of files)
|
|
14671
|
+
process.stdout.write(` + ${f}
|
|
14672
|
+
`);
|
|
14673
|
+
return;
|
|
14674
|
+
}
|
|
14675
|
+
mkdirSync2(target, { recursive: true });
|
|
14676
|
+
cpSync(src, target, { recursive: true });
|
|
14677
|
+
process.stdout.write(`installed ${skill.name} skill → ${rel}
|
|
14678
|
+
`);
|
|
14679
|
+
}
|
|
14680
|
+
async function run3(argv, opts = {}) {
|
|
14681
|
+
const parsed = parseArgs2({
|
|
14682
|
+
args: argv,
|
|
14683
|
+
options: {
|
|
14684
|
+
user: { type: "boolean", default: false },
|
|
14685
|
+
project: { type: "boolean", default: false },
|
|
14686
|
+
"margaui-skill": { type: "boolean", default: false },
|
|
14687
|
+
"immutable-skill": { type: "boolean", default: false },
|
|
14688
|
+
all: { type: "boolean", default: false },
|
|
14689
|
+
"dot-agents": { type: "boolean", default: false },
|
|
14690
|
+
"dry-run": { type: "boolean", default: false },
|
|
14691
|
+
force: { type: "boolean", short: "f", default: false },
|
|
14692
|
+
help: { type: "boolean", short: "h", default: false }
|
|
14693
|
+
},
|
|
14694
|
+
allowPositionals: false
|
|
14695
|
+
});
|
|
14696
|
+
if (parsed.values.help) {
|
|
14697
|
+
process.stdout.write(`tutuca install-skill [--user | --project] [--margaui-skill | --immutable-skill | --all]
|
|
14698
|
+
` + ` [--dot-agents] [--dry-run] [--force]
|
|
14699
|
+
` + `
|
|
14700
|
+
` + ` Installs Claude Code skill assets into .claude/skills/<name>/.
|
|
14701
|
+
` + ` Defaults to --project (cwd); --user installs at ~/.claude/skills/.
|
|
14702
|
+
` + `
|
|
14703
|
+
` + ` Selection:
|
|
14704
|
+
` + ` (default) install the tutuca skill
|
|
14705
|
+
` + ` --margaui-skill install the margaui skill instead
|
|
14706
|
+
` + ` --immutable-skill install the immutable-js skill instead
|
|
14707
|
+
` + ` --all install every bundled skill (tutuca + margaui + immutable-js)
|
|
14708
|
+
` + `
|
|
14709
|
+
` + ` --dot-agents installs into .agents/skills/ instead of .claude/skills/.
|
|
14710
|
+
` + ` --dry-run prints the files that would be written without touching the filesystem.
|
|
14711
|
+
` + ` --force overwrites existing files.
|
|
14712
|
+
`);
|
|
14713
|
+
return;
|
|
14714
|
+
}
|
|
14715
|
+
if (parsed.values.user && parsed.values.project) {
|
|
14716
|
+
emitError(opts, {
|
|
14717
|
+
code: CODES.USAGE_MUTUALLY_EXCLUSIVE,
|
|
14718
|
+
message: "--user and --project are mutually exclusive",
|
|
14719
|
+
hint: "Use --user for ~/.claude/skills/ or --project (the default) for ./.claude/skills/."
|
|
14720
|
+
});
|
|
14721
|
+
}
|
|
14722
|
+
const selectionFlags = ["margaui-skill", "immutable-skill", "all"].filter((k) => parsed.values[k]);
|
|
14723
|
+
if (selectionFlags.length > 1) {
|
|
14724
|
+
emitError(opts, {
|
|
14725
|
+
code: CODES.USAGE_MUTUALLY_EXCLUSIVE,
|
|
14726
|
+
message: `${selectionFlags.map((f) => `--${f}`).join(", ")} are mutually exclusive`,
|
|
14727
|
+
hint: "Pick one skill, or use --all to install every bundled skill."
|
|
14728
|
+
});
|
|
14729
|
+
}
|
|
14730
|
+
const scope = parsed.values.user ? "user" : "project";
|
|
14731
|
+
const root = findSkillsRoot();
|
|
14732
|
+
if (!root) {
|
|
14733
|
+
emitError(opts, {
|
|
14734
|
+
code: CODES.SKILL_ASSETS_MISSING,
|
|
14735
|
+
message: "skill assets not found alongside this CLI",
|
|
14736
|
+
hint: "If you're running from a checkout, run `bun scripts/build-skill.js` first."
|
|
14737
|
+
});
|
|
14738
|
+
}
|
|
14739
|
+
let selected;
|
|
14740
|
+
if (parsed.values.all) {
|
|
14741
|
+
selected = SKILLS;
|
|
14742
|
+
} else {
|
|
14743
|
+
const selFlag = SKILLS.find((s) => s.flag && parsed.values[s.flag]);
|
|
14744
|
+
selected = selFlag ? [selFlag] : SKILLS.filter((s) => s.name === "tutuca");
|
|
14745
|
+
}
|
|
14746
|
+
for (const skill of selected) {
|
|
14747
|
+
installSkill(skill, root, scope, parsed.values.force, parsed.values["dot-agents"], parsed.values["dry-run"], opts);
|
|
14748
|
+
}
|
|
14749
|
+
if (!parsed.values["dry-run"]) {
|
|
14750
|
+
process.stdout.write(`Open a Claude Code session in this directory to use it.
|
|
14751
|
+
`);
|
|
14752
|
+
}
|
|
14753
|
+
}
|
|
14754
|
+
var describe3 = "Install Claude Code skills (tutuca, margaui, immutable-js) into .claude/skills/.", SKILLS;
|
|
14755
|
+
var init_install_skill = __esm(() => {
|
|
14756
|
+
init_errors();
|
|
14757
|
+
SKILLS = [
|
|
14284
14758
|
{ name: "tutuca", srcSubdir: "tutuca", flag: null },
|
|
14285
14759
|
{ name: "margaui", srcSubdir: "margaui", flag: "margaui-skill" },
|
|
14286
14760
|
{ name: "immutable-js", srcSubdir: "immutable-js", flag: "immutable-skill" }
|
|
14287
14761
|
];
|
|
14288
14762
|
});
|
|
14289
14763
|
|
|
14764
|
+
// tools/cli/commands/agent-context.js
|
|
14765
|
+
init__registry();
|
|
14766
|
+
var exports_agent_context = {};
|
|
14767
|
+
__export(exports_agent_context, {
|
|
14768
|
+
run: () => run,
|
|
14769
|
+
describe: () => describe
|
|
14770
|
+
});
|
|
14771
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14772
|
+
import { dirname, resolve } from "node:path";
|
|
14773
|
+
import { fileURLToPath } from "node:url";
|
|
14774
|
+
var describe = "Print a machine-readable schema (commands, flags, exit codes) as JSON.";
|
|
14775
|
+
var SCHEMA_VERSION = 1;
|
|
14776
|
+
var GLOBAL_FLAGS = [
|
|
14777
|
+
{
|
|
14778
|
+
name: "json",
|
|
14779
|
+
type: "boolean",
|
|
14780
|
+
description: "Shorthand for --format=json. Errors also emit JSON on stderr."
|
|
14781
|
+
},
|
|
14782
|
+
{
|
|
14783
|
+
name: "format",
|
|
14784
|
+
short: "f",
|
|
14785
|
+
type: "string",
|
|
14786
|
+
enum: ["cli", "md", "json", "html"],
|
|
14787
|
+
description: "Output format. Per-command default below; html is render-only; json works for every module command."
|
|
14788
|
+
},
|
|
14789
|
+
{
|
|
14790
|
+
name: "output",
|
|
14791
|
+
short: "o",
|
|
14792
|
+
type: "string",
|
|
14793
|
+
description: "Write to file instead of stdout."
|
|
14794
|
+
},
|
|
14795
|
+
{ name: "pretty", type: "boolean", description: "Pretty-print HTML/JSON output." },
|
|
14796
|
+
{
|
|
14797
|
+
name: "module",
|
|
14798
|
+
type: "string",
|
|
14799
|
+
description: "Alternative to first-positional module path."
|
|
14800
|
+
},
|
|
14801
|
+
{ name: "help", short: "h", type: "boolean", description: "Show help." }
|
|
14802
|
+
];
|
|
14803
|
+
var EXIT_CODES = [
|
|
14804
|
+
{ code: 0, meaning: "success" },
|
|
14805
|
+
{ code: 1, meaning: "usage error (bad args, missing module, bad module shape)" },
|
|
14806
|
+
{ code: 2, meaning: "lint findings at error level (lint command)" },
|
|
14807
|
+
{ code: 3, meaning: "render crash (render command)" },
|
|
14808
|
+
{ code: 4, meaning: "test failures (test command)" }
|
|
14809
|
+
];
|
|
14810
|
+
var ERROR_CODES = [
|
|
14811
|
+
"ERR_USAGE_UNKNOWN_COMMAND",
|
|
14812
|
+
"ERR_USAGE_UNKNOWN_FLAG",
|
|
14813
|
+
"ERR_USAGE_BAD_FLAG_VALUE",
|
|
14814
|
+
"ERR_USAGE_MISSING_MODULE",
|
|
14815
|
+
"ERR_USAGE_MISSING_ARGUMENT",
|
|
14816
|
+
"ERR_USAGE_MUTUALLY_EXCLUSIVE",
|
|
14817
|
+
"ERR_FORMAT_UNKNOWN",
|
|
14818
|
+
"ERR_FORMAT_UNSUPPORTED",
|
|
14819
|
+
"EXAMPLES_SHAPE_MISMATCH",
|
|
14820
|
+
"ERR_SKILL_ASSETS_MISSING",
|
|
14821
|
+
"ERR_SKILL_TARGET_EXISTS"
|
|
14822
|
+
];
|
|
14823
|
+
var COMMAND_FLAGS = {
|
|
14824
|
+
list: [
|
|
14825
|
+
{
|
|
14826
|
+
name: "limit",
|
|
14827
|
+
type: "string",
|
|
14828
|
+
description: "Cap number of components emitted (0 = all). Truncated output is signaled by `truncated: true` in JSON."
|
|
14829
|
+
}
|
|
14830
|
+
],
|
|
14831
|
+
examples: [
|
|
14832
|
+
{
|
|
14833
|
+
name: "limit",
|
|
14834
|
+
type: "string",
|
|
14835
|
+
description: "Cap total number of example items emitted (0 = all)."
|
|
14836
|
+
}
|
|
14837
|
+
],
|
|
14838
|
+
render: [
|
|
14839
|
+
{ name: "title", type: "string", description: "Filter by example title." },
|
|
14840
|
+
{ name: "view", type: "string", description: "Override example's view name." }
|
|
14841
|
+
],
|
|
14842
|
+
test: [
|
|
14843
|
+
{
|
|
14844
|
+
name: "grep",
|
|
14845
|
+
type: "string",
|
|
14846
|
+
description: "Substring match against the full test path."
|
|
14847
|
+
},
|
|
14848
|
+
{
|
|
14849
|
+
name: "bail",
|
|
14850
|
+
type: "boolean",
|
|
14851
|
+
description: "Stop on first failure; remaining tests reported as skip."
|
|
14852
|
+
}
|
|
14853
|
+
]
|
|
14854
|
+
};
|
|
14855
|
+
var NO_MODULE_COMMANDS_META = {
|
|
14856
|
+
help: {
|
|
14857
|
+
describe: "Show usage. `help <command>` for per-command detail.",
|
|
14858
|
+
needsModule: false,
|
|
14859
|
+
flags: [{ name: "help", short: "h", type: "boolean" }],
|
|
14860
|
+
positionals: [{ name: "command", required: false }]
|
|
14861
|
+
},
|
|
14862
|
+
feedback: {
|
|
14863
|
+
describe: "Append a feedback record to ~/.tutuca/feedback.jsonl.",
|
|
14864
|
+
needsModule: false,
|
|
14865
|
+
flags: [{ name: "help", short: "h", type: "boolean" }],
|
|
14866
|
+
positionals: [
|
|
14867
|
+
{
|
|
14868
|
+
name: "message",
|
|
14869
|
+
required: false,
|
|
14870
|
+
note: "Falls back to piped stdin when omitted."
|
|
14871
|
+
}
|
|
14872
|
+
]
|
|
14873
|
+
},
|
|
14874
|
+
"install-skill": {
|
|
14875
|
+
describe: "Copy bundled Claude Code skill assets into .claude/skills/<name>/.",
|
|
14876
|
+
needsModule: false,
|
|
14877
|
+
flags: [
|
|
14878
|
+
{ name: "user", type: "boolean", description: "Install at ~/.claude/skills/." },
|
|
14879
|
+
{
|
|
14880
|
+
name: "project",
|
|
14881
|
+
type: "boolean",
|
|
14882
|
+
description: "Install at ./.claude/skills/ (default)."
|
|
14883
|
+
},
|
|
14884
|
+
{ name: "margaui-skill", type: "boolean" },
|
|
14885
|
+
{ name: "immutable-skill", type: "boolean" },
|
|
14886
|
+
{ name: "all", type: "boolean", description: "Install every bundled skill." },
|
|
14887
|
+
{
|
|
14888
|
+
name: "dot-agents",
|
|
14889
|
+
type: "boolean",
|
|
14890
|
+
description: "Use .agents/skills/ instead of .claude/skills/."
|
|
14891
|
+
},
|
|
14892
|
+
{
|
|
14893
|
+
name: "dry-run",
|
|
14894
|
+
type: "boolean",
|
|
14895
|
+
description: "Print files that would be written; don't touch disk."
|
|
14896
|
+
},
|
|
14897
|
+
{
|
|
14898
|
+
name: "force",
|
|
14899
|
+
short: "f",
|
|
14900
|
+
type: "boolean",
|
|
14901
|
+
description: "Overwrite existing files."
|
|
14902
|
+
},
|
|
14903
|
+
{ name: "help", short: "h", type: "boolean" }
|
|
14904
|
+
],
|
|
14905
|
+
positionals: []
|
|
14906
|
+
},
|
|
14907
|
+
"agent-context": {
|
|
14908
|
+
describe: "Print this schema as JSON.",
|
|
14909
|
+
needsModule: false,
|
|
14910
|
+
flags: [],
|
|
14911
|
+
positionals: []
|
|
14912
|
+
}
|
|
14913
|
+
};
|
|
14914
|
+
function packageVersion() {
|
|
14915
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14916
|
+
const candidates = [
|
|
14917
|
+
resolve(here, "..", "..", "..", "package.json"),
|
|
14918
|
+
resolve(here, "..", "..", "package.json")
|
|
14919
|
+
];
|
|
14920
|
+
for (const p of candidates) {
|
|
14921
|
+
if (existsSync(p)) {
|
|
14922
|
+
try {
|
|
14923
|
+
return JSON.parse(readFileSync(p, "utf8")).version ?? null;
|
|
14924
|
+
} catch {}
|
|
14925
|
+
}
|
|
14926
|
+
}
|
|
14927
|
+
return null;
|
|
14928
|
+
}
|
|
14929
|
+
function moduleCommandSchema(name, cmd) {
|
|
14930
|
+
return {
|
|
14931
|
+
name,
|
|
14932
|
+
describe: cmd.describe,
|
|
14933
|
+
needsModule: true,
|
|
14934
|
+
needsEnv: !!cmd.needsEnv,
|
|
14935
|
+
defaultFormat: cmd.defaultFormat,
|
|
14936
|
+
flags: COMMAND_FLAGS[name] ?? [],
|
|
14937
|
+
positionals: [
|
|
14938
|
+
{
|
|
14939
|
+
name: "module-path",
|
|
14940
|
+
required: true,
|
|
14941
|
+
description: "Path to the ES module to inspect."
|
|
14942
|
+
},
|
|
14943
|
+
{
|
|
14944
|
+
name: "name",
|
|
14945
|
+
required: false,
|
|
14946
|
+
description: "Component name filter (operate on one component)."
|
|
14947
|
+
}
|
|
14948
|
+
]
|
|
14949
|
+
};
|
|
14950
|
+
}
|
|
14951
|
+
async function run() {
|
|
14952
|
+
const moduleCmds = Object.entries(COMMANDS).map(([name, cmd]) => moduleCommandSchema(name, cmd));
|
|
14953
|
+
const noModuleCmds = Object.entries(NO_MODULE_COMMANDS_META).map(([name, meta]) => ({
|
|
14954
|
+
name,
|
|
14955
|
+
...meta
|
|
14956
|
+
}));
|
|
14957
|
+
const schema = {
|
|
14958
|
+
schemaVersion: SCHEMA_VERSION,
|
|
14959
|
+
cli: "tutuca",
|
|
14960
|
+
version: packageVersion(),
|
|
14961
|
+
invocation: {
|
|
14962
|
+
synopsis: "tutuca <command> <module-path> [name] [flags]",
|
|
14963
|
+
moduleFirst: false,
|
|
14964
|
+
moduleFlag: "module",
|
|
14965
|
+
note: "For module-required commands, the module path is the second positional. Use --module=<path> as an alternative."
|
|
14966
|
+
},
|
|
14967
|
+
globalFlags: GLOBAL_FLAGS,
|
|
14968
|
+
formats: ["cli", "md", "json", "html"],
|
|
14969
|
+
exitCodes: EXIT_CODES,
|
|
14970
|
+
errorCodes: ERROR_CODES,
|
|
14971
|
+
commands: [...moduleCmds, ...noModuleCmds]
|
|
14972
|
+
};
|
|
14973
|
+
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
14974
|
+
`);
|
|
14975
|
+
}
|
|
14976
|
+
|
|
14290
14977
|
// tools/tutuca.js
|
|
14291
14978
|
init__registry();
|
|
14979
|
+
init_feedback();
|
|
14292
14980
|
|
|
14293
14981
|
// tools/cli/commands/help.js
|
|
14294
14982
|
var exports_help = {};
|
|
14295
14983
|
__export(exports_help, {
|
|
14296
|
-
run: () =>
|
|
14297
|
-
describe: () =>
|
|
14984
|
+
run: () => run4,
|
|
14985
|
+
describe: () => describe4
|
|
14298
14986
|
});
|
|
14299
|
-
var
|
|
14987
|
+
var describe4 = "Show usage. `help <command>` for per-command detail.";
|
|
14300
14988
|
var OVERVIEW = `tutuca — CLI for inspecting, documenting, linting and rendering tutuca
|
|
14301
14989
|
components defined in an ES module.
|
|
14302
14990
|
|
|
14303
14991
|
SYNOPSIS
|
|
14304
|
-
tutuca <module-path>
|
|
14992
|
+
tutuca <command> <module-path> [name] [flags]
|
|
14305
14993
|
tutuca help [command]
|
|
14306
14994
|
tutuca {-h | --help}
|
|
14307
14995
|
|
|
14308
14996
|
INVOCATION SHAPE
|
|
14309
|
-
- <
|
|
14310
|
-
module resolvable by Node (absolute, or relative to cwd).
|
|
14997
|
+
- <command> comes FIRST. <module-path> is the second positional — a path
|
|
14998
|
+
to an ES module resolvable by Node (absolute, or relative to cwd).
|
|
14999
|
+
Use --module=<path> as an alternative if the path conflicts.
|
|
14311
15000
|
- [name] is an OPTIONAL component-name filter. Omit it to operate on all
|
|
14312
|
-
components; pass it to operate on exactly one (e.g. \`
|
|
14313
|
-
-
|
|
14314
|
-
|
|
14315
|
-
- \`help\`
|
|
15001
|
+
components; pass it to operate on exactly one (e.g. \`show Button\`).
|
|
15002
|
+
- Per-command flags follow the module path; global flags can appear
|
|
15003
|
+
anywhere. Unknown flags are rejected by the subcommand's parser.
|
|
15004
|
+
- \`help\`, \`feedback\`, \`install-skill\`, \`agent-context\` do NOT take a
|
|
15005
|
+
module path.
|
|
14316
15006
|
|
|
14317
15007
|
MODULE CONVENTION
|
|
14318
15008
|
A module passed to tutuca must export one or more of:
|
|
@@ -14346,27 +15036,31 @@ MODULE CONVENTION
|
|
|
14346
15036
|
export function getRoot() // optional; returned by info
|
|
14347
15037
|
|
|
14348
15038
|
COMMANDS (require <module-path>)
|
|
14349
|
-
|
|
15039
|
+
get <module>
|
|
14350
15040
|
Summarize which getX() exports are present and count components,
|
|
14351
15041
|
macros, request handlers, examples, and sections. Good first step.
|
|
14352
15042
|
|
|
14353
|
-
list
|
|
15043
|
+
list <module> [name] [--limit <n>]
|
|
14354
15044
|
List each component with its declared views and fields (name, type).
|
|
15045
|
+
--limit caps the number of components emitted (0 = all). Truncated
|
|
15046
|
+
output ends with a "… N more" footer in cli/md, or \`truncated:true\`
|
|
15047
|
+
in JSON.
|
|
14355
15048
|
|
|
14356
|
-
examples
|
|
15049
|
+
examples <module> [--limit <n>]
|
|
14357
15050
|
Print the module's example sections: title, description, items.
|
|
14358
15051
|
Each item shows its resolved component name and view.
|
|
15052
|
+
--limit caps the total number of items emitted (0 = all).
|
|
14359
15053
|
|
|
14360
|
-
|
|
14361
|
-
|
|
15054
|
+
show <module> [name]
|
|
15055
|
+
Show API docs (methods, input handlers, fields with their
|
|
14362
15056
|
auto-generated accessor/mutator methods) for every component, or
|
|
14363
15057
|
for the single component whose name matches [name].
|
|
14364
15058
|
|
|
14365
|
-
lint [name]
|
|
15059
|
+
lint <module> [name]
|
|
14366
15060
|
Run the built-in component linter. Reports at levels error / warn
|
|
14367
15061
|
/ hint. Exits 2 if ANY finding is at error level.
|
|
14368
15062
|
|
|
14369
|
-
render [name] [--title <t>] [--view <v>]
|
|
15063
|
+
render <module> [name] [--title <t>] [--view <v>]
|
|
14370
15064
|
Render examples to HTML by running each example value through the
|
|
14371
15065
|
component tree in a headless DOM. Filters:
|
|
14372
15066
|
[name] only examples whose value is an instance of <name>
|
|
@@ -14374,7 +15068,7 @@ COMMANDS (require <module-path>)
|
|
|
14374
15068
|
--view <v> override the example's view name
|
|
14375
15069
|
Exits 3 if any render crashes.
|
|
14376
15070
|
|
|
14377
|
-
test [name] [--grep <pattern>] [--bail]
|
|
15071
|
+
test <module> [name] [--grep <pattern>] [--bail]
|
|
14378
15072
|
Run tests defined by getTests({ describe, test, expect }). Filters:
|
|
14379
15073
|
[name] only tests whose tagged componentName equals <name>
|
|
14380
15074
|
--grep <p> substring match against the full test path
|
|
@@ -14387,7 +15081,18 @@ COMMANDS (no module required)
|
|
|
14387
15081
|
Without [command]: prints this full reference.
|
|
14388
15082
|
With [command]: prints that command's one-line description.
|
|
14389
15083
|
|
|
14390
|
-
|
|
15084
|
+
feedback [message]
|
|
15085
|
+
Append a feedback record (one JSON object per line) to
|
|
15086
|
+
~/.tutuca/feedback.jsonl. Use for bugs, confusing messages,
|
|
15087
|
+
or suggestions about the CLI, skills, docs, or the library.
|
|
15088
|
+
Message comes from the positional arg or piped stdin.
|
|
15089
|
+
|
|
15090
|
+
agent-context
|
|
15091
|
+
Print a machine-readable schema of every command, flag, exit code,
|
|
15092
|
+
and error code as JSON on stdout. Use this once to teach an agent the
|
|
15093
|
+
shape of the CLI; the schema is versioned (schemaVersion field).
|
|
15094
|
+
|
|
15095
|
+
install-skill [--user | --project] [--margaui-skill | --immutable-skill | --all] [--dot-agents] [--dry-run] [--force]
|
|
14391
15096
|
Copy bundled Claude Code skill assets into .claude/skills/<name>/.
|
|
14392
15097
|
Scope: --project (cwd, default) or --user (~/.claude/skills/).
|
|
14393
15098
|
Selection (default is the tutuca skill):
|
|
@@ -14395,9 +15100,13 @@ COMMANDS (no module required)
|
|
|
14395
15100
|
--immutable-skill install the immutable-js skill instead
|
|
14396
15101
|
--all install every bundled skill
|
|
14397
15102
|
--dot-agents installs into .agents/skills/ instead of .claude/skills/.
|
|
15103
|
+
--dry-run prints the files that would be written without touching disk.
|
|
14398
15104
|
--force overwrites existing files.
|
|
14399
15105
|
|
|
14400
15106
|
GLOBAL FLAGS
|
|
15107
|
+
--json Shorthand for \`--format=json\`. Recommended for
|
|
15108
|
+
agent/script consumers — error envelopes are
|
|
15109
|
+
also emitted as JSON on stderr.
|
|
14401
15110
|
-f, --format <cli|md|json|html>
|
|
14402
15111
|
Output format. Defaults per command:
|
|
14403
15112
|
info, list, examples, lint -> cli
|
|
@@ -14419,44 +15128,56 @@ EXIT CODES
|
|
|
14419
15128
|
3 render crash
|
|
14420
15129
|
4 test failures
|
|
14421
15130
|
|
|
15131
|
+
ERROR FORMAT
|
|
15132
|
+
Diagnostics go to stderr; structured output goes to stdout. Under
|
|
15133
|
+
\`--json\`, errors are emitted as a single-line JSON envelope on stderr:
|
|
15134
|
+
{"error":{"code":"ERR_...","message":"...","suggestion":{...},"hint":"..."}}
|
|
15135
|
+
Errors include "did you mean" suggestions for unknown commands and flags
|
|
15136
|
+
in the same shape as lint suggestions.
|
|
15137
|
+
|
|
14422
15138
|
EXAMPLES
|
|
14423
15139
|
# Inspect a module
|
|
14424
|
-
tutuca ./src/components.js
|
|
15140
|
+
tutuca get ./src/components.js
|
|
14425
15141
|
|
|
14426
15142
|
# Machine-readable docs for one component
|
|
14427
|
-
tutuca ./src/components.js
|
|
15143
|
+
tutuca show ./src/components.js Button --json -o docs/button.json
|
|
14428
15144
|
|
|
14429
15145
|
# Render every example, pretty-printed HTML to a file
|
|
14430
|
-
tutuca ./src/components.js
|
|
15146
|
+
tutuca render ./src/components.js -f html --pretty -o out/examples.html
|
|
14431
15147
|
|
|
14432
15148
|
# Render a single example
|
|
14433
|
-
tutuca ./src/components.js
|
|
15149
|
+
tutuca render ./src/components.js Button --title "Disabled state"
|
|
14434
15150
|
|
|
14435
15151
|
# Post-edit verification: lint, then render the example you changed
|
|
14436
|
-
tutuca ./src/components.js
|
|
14437
|
-
tutuca ./src/components.js
|
|
15152
|
+
tutuca lint ./src/components.js
|
|
15153
|
+
tutuca render ./src/components.js --title "Disabled state"
|
|
14438
15154
|
`;
|
|
14439
|
-
async function
|
|
15155
|
+
async function run4(argv, opts = {}) {
|
|
14440
15156
|
const target = argv?.[0];
|
|
14441
15157
|
if (!target) {
|
|
14442
15158
|
process.stdout.write(OVERVIEW);
|
|
14443
15159
|
return;
|
|
14444
15160
|
}
|
|
14445
15161
|
if (target === "help") {
|
|
14446
|
-
process.stdout.write(`help: ${
|
|
15162
|
+
process.stdout.write(`help: ${describe4}
|
|
14447
15163
|
`);
|
|
14448
15164
|
return;
|
|
14449
15165
|
}
|
|
14450
15166
|
const { COMMANDS: COMMANDS2 } = await Promise.resolve().then(() => (init__registry(), exports__registry));
|
|
14451
15167
|
const noModule = {
|
|
15168
|
+
feedback: await Promise.resolve().then(() => (init_feedback(), exports_feedback)),
|
|
14452
15169
|
"install-skill": await Promise.resolve().then(() => (init_install_skill(), exports_install_skill))
|
|
14453
15170
|
};
|
|
14454
15171
|
const cmd = COMMANDS2[target] ?? noModule[target];
|
|
14455
15172
|
if (!cmd) {
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14459
|
-
|
|
15173
|
+
const { CODES: CODES2, didYouMean: didYouMean2, emitError: emitError2 } = await Promise.resolve().then(() => (init_errors(), exports_errors));
|
|
15174
|
+
const known = [...Object.keys(COMMANDS2), ...Object.keys(noModule), "help"];
|
|
15175
|
+
emitError2(opts, {
|
|
15176
|
+
code: CODES2.USAGE_UNKNOWN_COMMAND,
|
|
15177
|
+
message: `Unknown command '${target}'`,
|
|
15178
|
+
suggestion: didYouMean2(target, known),
|
|
15179
|
+
hint: "Run `tutuca help` for the full reference."
|
|
15180
|
+
});
|
|
14460
15181
|
}
|
|
14461
15182
|
process.stdout.write(`${target}: ${cmd.describe}
|
|
14462
15183
|
`);
|
|
@@ -14465,299 +15186,82 @@ async function run2(argv) {
|
|
|
14465
15186
|
|
|
14466
15187
|
// tools/tutuca.js
|
|
14467
15188
|
init_install_skill();
|
|
15189
|
+
init_errors();
|
|
14468
15190
|
|
|
14469
15191
|
// tools/cli/with-module.js
|
|
14470
|
-
import { parseArgs as
|
|
14471
|
-
|
|
14472
|
-
// tools/cli/env.js
|
|
14473
|
-
init_anode();
|
|
14474
|
-
init_lint_check();
|
|
14475
|
-
import { JSDOM, VirtualConsole } from "jsdom";
|
|
14476
|
-
async function createNodeEnv() {
|
|
14477
|
-
const virtualConsole = new VirtualConsole;
|
|
14478
|
-
virtualConsole.forwardTo(console, { jsdomErrors: "none" });
|
|
14479
|
-
virtualConsole.on("jsdomError", (err) => {
|
|
14480
|
-
if (err?.message?.includes("Could not parse CSS stylesheet"))
|
|
14481
|
-
return;
|
|
14482
|
-
console.error(err.message);
|
|
14483
|
-
});
|
|
14484
|
-
const dom = new JSDOM("<!DOCTYPE html><html><head></head><body></body></html>", {
|
|
14485
|
-
virtualConsole
|
|
14486
|
-
});
|
|
14487
|
-
const { document: document2, Text, Comment } = dom.window;
|
|
14488
|
-
globalThis.document = document2;
|
|
14489
|
-
|
|
14490
|
-
class HeadlessParseContext extends ParseContext {
|
|
14491
|
-
constructor() {
|
|
14492
|
-
super(document2, Text, Comment);
|
|
14493
|
-
}
|
|
14494
|
-
}
|
|
14495
|
-
|
|
14496
|
-
class HeadlessLintParseContext extends LintParseContext {
|
|
14497
|
-
constructor() {
|
|
14498
|
-
super(document2, Text, Comment);
|
|
14499
|
-
}
|
|
14500
|
-
}
|
|
14501
|
-
return {
|
|
14502
|
-
document: dom.window.document,
|
|
14503
|
-
ParseContext: HeadlessParseContext,
|
|
14504
|
-
LintParseContext: HeadlessLintParseContext
|
|
14505
|
-
};
|
|
14506
|
-
}
|
|
14507
|
-
|
|
14508
|
-
// tools/cli/load.js
|
|
14509
|
-
import { resolve as resolve2 } from "node:path";
|
|
14510
|
-
async function loadAndNormalize(modulePath) {
|
|
14511
|
-
const abs = resolve2(modulePath);
|
|
14512
|
-
const mod = await import(abs);
|
|
14513
|
-
const { normalized } = normalizeModule(mod, { path: abs });
|
|
14514
|
-
return normalized;
|
|
14515
|
-
}
|
|
14516
|
-
|
|
14517
|
-
// tools/cli/output.js
|
|
14518
|
-
import { writeFileSync } from "node:fs";
|
|
14519
|
-
|
|
14520
|
-
// tools/format/cli.js
|
|
14521
|
-
var exports_cli = {};
|
|
14522
|
-
__export(exports_cli, {
|
|
14523
|
-
supports: () => supports,
|
|
14524
|
-
format: () => format
|
|
14525
|
-
});
|
|
14526
|
-
|
|
14527
|
-
// tools/format/_dispatch.js
|
|
14528
|
-
function makeFormatter(name, table) {
|
|
14529
|
-
return {
|
|
14530
|
-
supports: new Set(Object.keys(table)),
|
|
14531
|
-
async format(result, opts) {
|
|
14532
|
-
const fn = table[result.constructor.name];
|
|
14533
|
-
if (!fn) {
|
|
14534
|
-
throw new Error(`${name} formatter missing dispatch for ${result.constructor.name}`);
|
|
14535
|
-
}
|
|
14536
|
-
return await fn(result, opts);
|
|
14537
|
-
}
|
|
14538
|
-
};
|
|
14539
|
-
}
|
|
14540
|
-
|
|
14541
|
-
// tools/format/lint.js
|
|
14542
|
-
var UNSUPPORTED_EXPR_LABEL = {
|
|
14543
|
-
ternary: "ternary expression",
|
|
14544
|
-
comparison: "comparison",
|
|
14545
|
-
logical: "logical expression",
|
|
14546
|
-
"call-with-args": "method call with arguments"
|
|
14547
|
-
};
|
|
14548
|
-
function unsupportedExprMessage(info) {
|
|
14549
|
-
const v = JSON.stringify(info.value);
|
|
14550
|
-
const label = UNSUPPORTED_EXPR_LABEL[info.detected] ?? "expression";
|
|
14551
|
-
switch (info.role) {
|
|
14552
|
-
case "attr":
|
|
14553
|
-
return `Unsupported ${label} ${v} in dynamic attribute ':${info.attr}'`;
|
|
14554
|
-
case "directive":
|
|
14555
|
-
return `Unsupported ${label} ${v} in directive '@${info.directive}'`;
|
|
14556
|
-
case "if":
|
|
14557
|
-
return `Unsupported ${label} ${v} in '@if.${info.attr}' condition`;
|
|
14558
|
-
case "x-op":
|
|
14559
|
-
return `Unsupported ${label} ${v} in <x ${info.op}>`;
|
|
14560
|
-
default:
|
|
14561
|
-
return `Unsupported ${label} ${v}`;
|
|
14562
|
-
}
|
|
14563
|
-
}
|
|
14564
|
-
function badValueMessage(info) {
|
|
14565
|
-
const v = JSON.stringify(info.value);
|
|
14566
|
-
switch (info.role) {
|
|
14567
|
-
case "attr":
|
|
14568
|
-
return `Cannot parse value ${v} for attribute ':${info.attr}'`;
|
|
14569
|
-
case "directive":
|
|
14570
|
-
return `Cannot parse value ${v} for directive '@${info.directive}'`;
|
|
14571
|
-
case "if":
|
|
14572
|
-
return `Cannot parse condition ${v} for '@if.${info.attr}'`;
|
|
14573
|
-
case "x-op":
|
|
14574
|
-
return `Cannot parse value ${v} for <x ${info.op}>`;
|
|
14575
|
-
case "handler-name":
|
|
14576
|
-
return `Cannot parse handler name ${v}`;
|
|
14577
|
-
case "handler-arg":
|
|
14578
|
-
return `Cannot parse handler argument ${v}`;
|
|
14579
|
-
case "macro-var":
|
|
14580
|
-
return `Macro variable '^${info.name}' is not defined`;
|
|
14581
|
-
default:
|
|
14582
|
-
return `Cannot parse value ${v}`;
|
|
14583
|
-
}
|
|
14584
|
-
}
|
|
14585
|
-
function tagDisplay(tag) {
|
|
14586
|
-
return tag ? String(tag).toLowerCase() : null;
|
|
14587
|
-
}
|
|
14588
|
-
function fmtTagSuffix(info) {
|
|
14589
|
-
const t = tagDisplay(info?.tag);
|
|
14590
|
-
return t && t !== "x" ? ` on <${t}>` : "";
|
|
14591
|
-
}
|
|
14592
|
-
function fmtOriginSuffix(info) {
|
|
14593
|
-
if (!info)
|
|
14594
|
-
return "";
|
|
14595
|
-
const parts = [];
|
|
14596
|
-
if (info.originAttr) {
|
|
14597
|
-
const branch = info.branch ? `[${info.branch}]` : "";
|
|
14598
|
-
parts.push(`in ${info.originAttr}${branch}`);
|
|
14599
|
-
}
|
|
14600
|
-
if (info.handlerName) {
|
|
14601
|
-
parts.push(`handler '${info.handlerName}'${info.argIndex !== undefined ? ` arg ${info.argIndex}` : ""}`);
|
|
14602
|
-
}
|
|
14603
|
-
const t = tagDisplay(info.tag);
|
|
14604
|
-
if (t && t !== "x")
|
|
14605
|
-
parts.push(`on <${t}>`);
|
|
14606
|
-
return parts.length ? ` (${parts.join(", ")})` : "";
|
|
14607
|
-
}
|
|
14608
|
-
function fmtEventSuffix(info) {
|
|
14609
|
-
if (info?.originAttr)
|
|
14610
|
-
return ` in ${info.originAttr}`;
|
|
14611
|
-
if (info?.eventName)
|
|
14612
|
-
return ` in @on.${info.eventName}`;
|
|
14613
|
-
return "";
|
|
14614
|
-
}
|
|
14615
|
-
function lintIdToMessage(id, info) {
|
|
14616
|
-
switch (id) {
|
|
14617
|
-
case "RENDER_IT_OUTSIDE_OF_LOOP":
|
|
14618
|
-
return "<x render-it> used outside of a loop";
|
|
14619
|
-
case "UNKNOWN_EVENT_MODIFIER": {
|
|
14620
|
-
const mods = info.handler?.modifiers ?? [info.modifier];
|
|
14621
|
-
const written = `@on.${info.name}+${mods.join("+")}`;
|
|
14622
|
-
return `Unknown modifier '+${info.modifier}' in '${written}'`;
|
|
14623
|
-
}
|
|
14624
|
-
case "UNKNOWN_HANDLER_ARG_NAME":
|
|
14625
|
-
return `Unknown handler argument '${info.name}'${fmtOriginSuffix(info)}`;
|
|
14626
|
-
case "INPUT_HANDLER_NOT_IMPLEMENTED":
|
|
14627
|
-
return `Input handler '${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
14628
|
-
case "INPUT_HANDLER_NOT_REFERENCED":
|
|
14629
|
-
return `Input handler '${info.name}' is defined but never used — remove it or wire it to an @on.* event`;
|
|
14630
|
-
case "INPUT_HANDLER_METHOD_NOT_IMPLEMENTED":
|
|
14631
|
-
return `Method '.${info.name}' is not implemented${fmtEventSuffix(info)}`;
|
|
14632
|
-
case "INPUT_HANDLER_FOR_INPUT_HANDLER_METHOD":
|
|
14633
|
-
return `'.${info.name}' is a method reference, but '${info.name}' is defined as an input handler${fmtEventSuffix(info)}`;
|
|
14634
|
-
case "INPUT_HANDLER_METHOD_FOR_INPUT_HANDLER":
|
|
14635
|
-
return `'${info.name}' is an input handler reference, but '${info.name}' is defined as a method${fmtEventSuffix(info)}`;
|
|
14636
|
-
case "FIELD_VAL_NOT_DEFINED":
|
|
14637
|
-
return `Field '.${info.name}' is not defined${fmtOriginSuffix(info)}`;
|
|
14638
|
-
case "DUPLICATE_ATTR_DEFINITION": {
|
|
14639
|
-
const sources = info.sources?.length ? ` (${info.sources.join(", ")})` : "";
|
|
14640
|
-
const tag = info.tag ? ` on <${String(info.tag).toLowerCase()}>` : "";
|
|
14641
|
-
return `Attribute '${info.name}' is set ${info.sources?.length ?? "multiple"} times${sources}${tag}`;
|
|
14642
|
-
}
|
|
14643
|
-
case "IF_NO_BRANCH_SET":
|
|
14644
|
-
return `'@if.${info.attr}' has no '@then' or '@else' branch — add '@then="…"' or '@else="…"' (or both)${fmtTagSuffix(info)}`;
|
|
14645
|
-
case "UNKNOWN_REQUEST_NAME":
|
|
14646
|
-
return `Unknown request '!${info.name}'${fmtOriginSuffix(info)}`;
|
|
14647
|
-
case "UNKNOWN_COMPONENT_NAME":
|
|
14648
|
-
return `Unknown component '${info.name}'${fmtOriginSuffix(info)}`;
|
|
14649
|
-
case "ALT_HANDLER_NOT_DEFINED":
|
|
14650
|
-
return `Alter handler '${info.name}' is not defined${fmtOriginSuffix(info)}`;
|
|
14651
|
-
case "ALT_HANDLER_NOT_REFERENCED":
|
|
14652
|
-
return `Alter handler '${info.name}' is defined but never used — remove it or reference it from @when, @enrich-with, or @loop-with`;
|
|
14653
|
-
case "UNKNOWN_MACRO_ARG":
|
|
14654
|
-
return `Argument '${info.name}' is not declared in macro '${info.macroName}'`;
|
|
14655
|
-
case "UNKNOWN_DIRECTIVE":
|
|
14656
|
-
return `Unknown directive '@${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
|
|
14657
|
-
case "UNKNOWN_X_OP":
|
|
14658
|
-
return `Unknown <x> op '${info.name}=${JSON.stringify(info.value)}'${fmtTagSuffix(info)}`;
|
|
14659
|
-
case "UNKNOWN_X_ATTR":
|
|
14660
|
-
return `Unknown attribute '${info.name}=${JSON.stringify(info.value)}' on <x ${info.op}>${fmtTagSuffix(info)}`;
|
|
14661
|
-
case "MAYBE_DROP_AT_PREFIX": {
|
|
14662
|
-
const written = info.value !== undefined ? `${info.name}=${JSON.stringify(info.value)}` : info.name;
|
|
14663
|
-
return `'${written}' on <x> looks like a directive but is actually an x op/attr written with a leading '@'`;
|
|
14664
|
-
}
|
|
14665
|
-
case "BAD_VALUE":
|
|
14666
|
-
return `${badValueMessage(info)}${fmtTagSuffix(info)}`;
|
|
14667
|
-
case "UNSUPPORTED_EXPR_SYNTAX":
|
|
14668
|
-
return `${unsupportedExprMessage(info)}${fmtTagSuffix(info)}`;
|
|
14669
|
-
case "REDUNDANT_TEMPLATE_STRING":
|
|
14670
|
-
return `Redundant template string — '{${info.simpler}}' should be just '${info.simpler}'${fmtOriginSuffix(info)}`;
|
|
14671
|
-
case "UNKNOWN_COMPONENT_SPEC_KEY":
|
|
14672
|
-
return `Unknown component spec key '${info.key}' — value will be ignored at runtime`;
|
|
14673
|
-
case "HTML_TAG_NAME_HAS_UPPERCASE":
|
|
14674
|
-
return `Tag <${info.raw}> will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
|
|
14675
|
-
case "HTML_SVG_TAG_WILL_LOWERCASE":
|
|
14676
|
-
return `SVG tag <${info.raw}> is not in the WHATWG case-correction list — will be lowercased to <${info.lowercased}>${fmtLocationSuffix(info)}`;
|
|
14677
|
-
case "HTML_TAG_NOT_ALLOWED_IN_PARENT":
|
|
14678
|
-
return `<${info.tag}> not allowed under <${info.parent ?? "?"}> in ${info.mode} — ${htmlActionPhrase(info.action, info.tag, info.parent)}${fmtLocationSuffix(info)}`;
|
|
14679
|
-
case "HTML_TEXT_NOT_ALLOWED_IN_PARENT":
|
|
14680
|
-
return `Non-whitespace text not allowed in ${info.mode}: ${JSON.stringify(info.snippet)}${fmtLocationSuffix(info)}`;
|
|
14681
|
-
case "HTML_VOID_ELEMENT_HAS_CLOSE_TAG":
|
|
14682
|
-
return `Void element <${info.tag}> has an explicit close tag${fmtLocationSuffix(info)}`;
|
|
14683
|
-
case "HTML_DUPLICATE_FORM":
|
|
14684
|
-
return `Nested <form> — the inner form will be dropped by the parser${fmtLocationSuffix(info)}`;
|
|
14685
|
-
case "HTML_NESTED_INTERACTIVE":
|
|
14686
|
-
return `<${info.tag}> nested inside another <${info.tag}> — adoption agency will reorder${fmtLocationSuffix(info)}`;
|
|
14687
|
-
case "HTML_MISNESTED_FORMATTING":
|
|
14688
|
-
return `Misnested formatting tag </${info.tag}> — adoption agency will reorder nodes${fmtLocationSuffix(info)}`;
|
|
14689
|
-
case "HTML_UNEXPECTED_END_TAG":
|
|
14690
|
-
return `Unexpected end tag </${info.tag}>${fmtLocationSuffix(info)}`;
|
|
14691
|
-
case "HTML_UNCLOSED_BEFORE_END":
|
|
14692
|
-
return `<${info.unclosed}> still open when </${info.tag}> was seen — implicitly closed${fmtLocationSuffix(info)}`;
|
|
14693
|
-
case "HTML_DUPLICATE_ATTRIBUTE":
|
|
14694
|
-
return `Duplicate attribute '${info.name}' — second occurrence dropped${fmtLocationSuffix(info)}`;
|
|
14695
|
-
case "HTML_ATTRIBUTES_ON_END_TAG":
|
|
14696
|
-
return `Attributes on end tag </${info.tag}> — dropped by the parser${fmtLocationSuffix(info)}`;
|
|
14697
|
-
case "HTML_SELF_CLOSING_END_TAG":
|
|
14698
|
-
return `Self-closing end tag </${info.tag}/> — trailing '/' is meaningless${fmtLocationSuffix(info)}`;
|
|
14699
|
-
case "HTML_MISSING_ATTRIBUTE_VALUE":
|
|
14700
|
-
return `Attribute '${info.name}' is missing a value${fmtLocationSuffix(info)}`;
|
|
14701
|
-
case "HTML_CDATA_IN_HTML_NAMESPACE":
|
|
14702
|
-
return `CDATA section in HTML namespace — reinterpreted as a bogus comment${fmtLocationSuffix(info)}`;
|
|
14703
|
-
case "HTML_BOGUS_COMMENT":
|
|
14704
|
-
return `Bogus comment — content dropped by the parser${fmtLocationSuffix(info)}`;
|
|
14705
|
-
case "HTML_SVG_ATTR_WILL_LOWERCASE":
|
|
14706
|
-
return `SVG attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
|
|
14707
|
-
case "HTML_MATHML_ATTR_WILL_LOWERCASE":
|
|
14708
|
-
return `MathML attribute '${info.raw}' will be rewritten to '${info.canonical}'${fmtLocationSuffix(info)}`;
|
|
14709
|
-
case "LINT_ERROR":
|
|
14710
|
-
return info.message;
|
|
14711
|
-
default:
|
|
14712
|
-
return id;
|
|
15192
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
15193
|
+
|
|
15194
|
+
// tools/cli/env.js
|
|
15195
|
+
init_anode();
|
|
15196
|
+
init_lint_check();
|
|
15197
|
+
import { JSDOM, VirtualConsole } from "jsdom";
|
|
15198
|
+
async function createNodeEnv() {
|
|
15199
|
+
const virtualConsole = new VirtualConsole;
|
|
15200
|
+
virtualConsole.forwardTo(console, { jsdomErrors: "none" });
|
|
15201
|
+
virtualConsole.on("jsdomError", (err) => {
|
|
15202
|
+
if (err?.message?.includes("Could not parse CSS stylesheet"))
|
|
15203
|
+
return;
|
|
15204
|
+
console.error(err.message);
|
|
15205
|
+
});
|
|
15206
|
+
const dom = new JSDOM("<!DOCTYPE html><html><head></head><body></body></html>", {
|
|
15207
|
+
virtualConsole
|
|
15208
|
+
});
|
|
15209
|
+
const { document: document2, Text, Comment } = dom.window;
|
|
15210
|
+
globalThis.document = document2;
|
|
15211
|
+
|
|
15212
|
+
class HeadlessParseContext extends ParseContext {
|
|
15213
|
+
constructor() {
|
|
15214
|
+
super(document2, Text, Comment);
|
|
15215
|
+
}
|
|
14713
15216
|
}
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
case "replace-name":
|
|
14720
|
-
return `did you mean '${suggestion.to}'?`;
|
|
14721
|
-
case "drop-prefix":
|
|
14722
|
-
return `did you mean '${suggestion.to}'? (drop the leading '${suggestion.from.slice(0, suggestion.from.length - suggestion.to.length)}')`;
|
|
14723
|
-
case "add-prefix":
|
|
14724
|
-
return `did you mean '${suggestion.to}'? (add the leading '${suggestion.to.slice(0, suggestion.to.length - suggestion.from.length)}')`;
|
|
14725
|
-
case "remove":
|
|
14726
|
-
return `remove ${suggestion.what}`;
|
|
14727
|
-
case "rewrite":
|
|
14728
|
-
return `use '${suggestion.to}' instead of '${suggestion.from}'`;
|
|
14729
|
-
case "wrap":
|
|
14730
|
-
return `wrap it in ${suggestion.to}`;
|
|
14731
|
-
case "rephrase":
|
|
14732
|
-
return suggestion.text ?? null;
|
|
14733
|
-
default:
|
|
14734
|
-
return null;
|
|
15217
|
+
|
|
15218
|
+
class HeadlessLintParseContext extends LintParseContext {
|
|
15219
|
+
constructor() {
|
|
15220
|
+
super(document2, Text, Comment);
|
|
15221
|
+
}
|
|
14735
15222
|
}
|
|
15223
|
+
return {
|
|
15224
|
+
document: dom.window.document,
|
|
15225
|
+
ParseContext: HeadlessParseContext,
|
|
15226
|
+
LintParseContext: HeadlessLintParseContext
|
|
15227
|
+
};
|
|
14736
15228
|
}
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
15229
|
+
|
|
15230
|
+
// tools/cli/load.js
|
|
15231
|
+
import { resolve as resolve4 } from "node:path";
|
|
15232
|
+
async function loadAndNormalize(modulePath) {
|
|
15233
|
+
const abs = resolve4(modulePath);
|
|
15234
|
+
const mod = await import(abs);
|
|
15235
|
+
const { normalized } = normalizeModule(mod, { path: abs });
|
|
15236
|
+
return normalized;
|
|
14742
15237
|
}
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
15238
|
+
|
|
15239
|
+
// tools/cli/output.js
|
|
15240
|
+
import { writeFileSync } from "node:fs";
|
|
15241
|
+
|
|
15242
|
+
// tools/format/cli.js
|
|
15243
|
+
var exports_cli = {};
|
|
15244
|
+
__export(exports_cli, {
|
|
15245
|
+
supports: () => supports,
|
|
15246
|
+
format: () => format
|
|
15247
|
+
});
|
|
15248
|
+
|
|
15249
|
+
// tools/format/_dispatch.js
|
|
15250
|
+
function makeFormatter(name, table) {
|
|
15251
|
+
return {
|
|
15252
|
+
supports: new Set(Object.keys(table)),
|
|
15253
|
+
async format(result, opts) {
|
|
15254
|
+
const fn = table[result.constructor.name];
|
|
15255
|
+
if (!fn) {
|
|
15256
|
+
throw new Error(`${name} formatter missing dispatch for ${result.constructor.name}`);
|
|
15257
|
+
}
|
|
15258
|
+
return await fn(result, opts);
|
|
15259
|
+
}
|
|
15260
|
+
};
|
|
14758
15261
|
}
|
|
14759
15262
|
|
|
14760
15263
|
// tools/format/cli.js
|
|
15264
|
+
init_lint2();
|
|
14761
15265
|
function fmtModuleInfo(info) {
|
|
14762
15266
|
const lines = [];
|
|
14763
15267
|
lines.push(`Module: ${info.path ?? "<in-memory>"}`);
|
|
@@ -14785,6 +15289,10 @@ function fmtComponentList(list) {
|
|
|
14785
15289
|
for (const f of c.fields)
|
|
14786
15290
|
lines.push(` - ${f.name}: ${f.type}`);
|
|
14787
15291
|
}
|
|
15292
|
+
if (list.truncated) {
|
|
15293
|
+
const remaining = list.total - list.items.length;
|
|
15294
|
+
lines.push(`… ${remaining} more component(s) — re-run with \`--limit 0\` for all, or pass [name] to filter.`);
|
|
15295
|
+
}
|
|
14788
15296
|
return lines.join(`
|
|
14789
15297
|
`);
|
|
14790
15298
|
}
|
|
@@ -14792,12 +15300,18 @@ function fmtExampleIndex(idx) {
|
|
|
14792
15300
|
if (idx.sections.length === 0)
|
|
14793
15301
|
return "(no examples)";
|
|
14794
15302
|
const lines = [];
|
|
15303
|
+
let shown = 0;
|
|
14795
15304
|
for (const s of idx.sections) {
|
|
14796
15305
|
lines.push(`${s.title}${s.description ? ` — ${s.description}` : ""}`);
|
|
14797
15306
|
for (const item of s.items) {
|
|
14798
15307
|
lines.push(` - ${item.title}${item.description ? ` — ${item.description}` : ""} [${item.componentName}${item.view !== "main" ? `/${item.view}` : ""}]`);
|
|
15308
|
+
shown += 1;
|
|
14799
15309
|
}
|
|
14800
15310
|
}
|
|
15311
|
+
if (idx.truncated) {
|
|
15312
|
+
const remaining = idx.total - shown;
|
|
15313
|
+
lines.push(`… ${remaining} more example(s) — re-run with \`--limit 0\` for all.`);
|
|
15314
|
+
}
|
|
14801
15315
|
return lines.join(`
|
|
14802
15316
|
`);
|
|
14803
15317
|
}
|
|
@@ -14919,12 +15433,70 @@ var { supports, format } = makeFormatter("cli", {
|
|
|
14919
15433
|
TestReport: fmtTestReport
|
|
14920
15434
|
});
|
|
14921
15435
|
|
|
15436
|
+
// tools/format/html.js
|
|
15437
|
+
var exports_html = {};
|
|
15438
|
+
__export(exports_html, {
|
|
15439
|
+
supports: () => supports2,
|
|
15440
|
+
format: () => format2
|
|
15441
|
+
});
|
|
15442
|
+
async function fmtRenderBatch2(result, { pretty = false } = {}) {
|
|
15443
|
+
const prettify = pretty ? (await import("prettier")).format : null;
|
|
15444
|
+
const parts = [];
|
|
15445
|
+
for (const section of result.sections) {
|
|
15446
|
+
parts.push(`<!-- === ${section.title} === -->`);
|
|
15447
|
+
for (const item of section.items) {
|
|
15448
|
+
if (item.error) {
|
|
15449
|
+
parts.push(`<!-- ERROR ${item.title}: ${item.error.message} -->`);
|
|
15450
|
+
continue;
|
|
15451
|
+
}
|
|
15452
|
+
let html = item.html;
|
|
15453
|
+
if (prettify) {
|
|
15454
|
+
try {
|
|
15455
|
+
html = (await prettify(html, { parser: "html" })).trimEnd();
|
|
15456
|
+
} catch {}
|
|
15457
|
+
}
|
|
15458
|
+
parts.push(`<!-- ${item.title} -->`);
|
|
15459
|
+
parts.push(html);
|
|
15460
|
+
}
|
|
15461
|
+
}
|
|
15462
|
+
return parts.join(`
|
|
15463
|
+
`);
|
|
15464
|
+
}
|
|
15465
|
+
var { supports: supports2, format: format2 } = makeFormatter("html", {
|
|
15466
|
+
RenderBatch: fmtRenderBatch2
|
|
15467
|
+
});
|
|
15468
|
+
|
|
15469
|
+
// tools/format/json.js
|
|
15470
|
+
var exports_json = {};
|
|
15471
|
+
__export(exports_json, {
|
|
15472
|
+
supports: () => supports3,
|
|
15473
|
+
format: () => format3
|
|
15474
|
+
});
|
|
15475
|
+
function replacer(_key, v) {
|
|
15476
|
+
if (v instanceof Set)
|
|
15477
|
+
return [...v];
|
|
15478
|
+
return v;
|
|
15479
|
+
}
|
|
15480
|
+
function fmtJson(result, { pretty = false } = {}) {
|
|
15481
|
+
return JSON.stringify(result, replacer, pretty ? 2 : 0);
|
|
15482
|
+
}
|
|
15483
|
+
var { supports: supports3, format: format3 } = makeFormatter("json", {
|
|
15484
|
+
ModuleInfo: fmtJson,
|
|
15485
|
+
ComponentList: fmtJson,
|
|
15486
|
+
ExampleIndex: fmtJson,
|
|
15487
|
+
ComponentDocs: fmtJson,
|
|
15488
|
+
LintReport: fmtJson,
|
|
15489
|
+
RenderBatch: fmtJson,
|
|
15490
|
+
TestReport: fmtJson
|
|
15491
|
+
});
|
|
15492
|
+
|
|
14922
15493
|
// tools/format/md.js
|
|
14923
15494
|
var exports_md = {};
|
|
14924
15495
|
__export(exports_md, {
|
|
14925
|
-
supports: () =>
|
|
14926
|
-
format: () =>
|
|
15496
|
+
supports: () => supports4,
|
|
15497
|
+
format: () => format4
|
|
14927
15498
|
});
|
|
15499
|
+
init_lint2();
|
|
14928
15500
|
function fmtComponentDocs2(docs) {
|
|
14929
15501
|
const lines = [];
|
|
14930
15502
|
for (const comp of docs.items) {
|
|
@@ -14958,7 +15530,7 @@ function fmtComponentDocs2(docs) {
|
|
|
14958
15530
|
return lines.join(`
|
|
14959
15531
|
`);
|
|
14960
15532
|
}
|
|
14961
|
-
async function
|
|
15533
|
+
async function fmtRenderBatch3(batch, { pretty = false } = {}) {
|
|
14962
15534
|
const prettify = pretty ? (await import("prettier")).format : null;
|
|
14963
15535
|
const lines = [];
|
|
14964
15536
|
for (const section of batch.sections) {
|
|
@@ -15105,9 +15677,9 @@ function fmtTestReport2(report) {
|
|
|
15105
15677
|
return lines.join(`
|
|
15106
15678
|
`);
|
|
15107
15679
|
}
|
|
15108
|
-
var { supports:
|
|
15680
|
+
var { supports: supports4, format: format4 } = makeFormatter("md", {
|
|
15109
15681
|
ComponentDocs: fmtComponentDocs2,
|
|
15110
|
-
RenderBatch:
|
|
15682
|
+
RenderBatch: fmtRenderBatch3,
|
|
15111
15683
|
ExampleIndex: fmtExampleIndex2,
|
|
15112
15684
|
LintReport: fmtLintReport2,
|
|
15113
15685
|
ModuleInfo: fmtModuleInfo2,
|
|
@@ -15115,77 +15687,34 @@ var { supports: supports2, format: format2 } = makeFormatter("md", {
|
|
|
15115
15687
|
TestReport: fmtTestReport2
|
|
15116
15688
|
});
|
|
15117
15689
|
|
|
15118
|
-
// tools/format/json.js
|
|
15119
|
-
var exports_json = {};
|
|
15120
|
-
__export(exports_json, {
|
|
15121
|
-
supports: () => supports3,
|
|
15122
|
-
format: () => format3
|
|
15123
|
-
});
|
|
15124
|
-
function replacer(_key, v) {
|
|
15125
|
-
if (v instanceof Set)
|
|
15126
|
-
return [...v];
|
|
15127
|
-
return v;
|
|
15128
|
-
}
|
|
15129
|
-
function fmtJson(result, { pretty = false } = {}) {
|
|
15130
|
-
return JSON.stringify(result, replacer, pretty ? 2 : 0);
|
|
15131
|
-
}
|
|
15132
|
-
var { supports: supports3, format: format3 } = makeFormatter("json", {
|
|
15133
|
-
ModuleInfo: fmtJson,
|
|
15134
|
-
ComponentList: fmtJson,
|
|
15135
|
-
ExampleIndex: fmtJson,
|
|
15136
|
-
ComponentDocs: fmtJson,
|
|
15137
|
-
LintReport: fmtJson,
|
|
15138
|
-
RenderBatch: fmtJson,
|
|
15139
|
-
TestReport: fmtJson
|
|
15140
|
-
});
|
|
15141
|
-
|
|
15142
|
-
// tools/format/html.js
|
|
15143
|
-
var exports_html = {};
|
|
15144
|
-
__export(exports_html, {
|
|
15145
|
-
supports: () => supports4,
|
|
15146
|
-
format: () => format4
|
|
15147
|
-
});
|
|
15148
|
-
async function fmtRenderBatch3(result, { pretty = false } = {}) {
|
|
15149
|
-
const prettify = pretty ? (await import("prettier")).format : null;
|
|
15150
|
-
const parts = [];
|
|
15151
|
-
for (const section of result.sections) {
|
|
15152
|
-
parts.push(`<!-- === ${section.title} === -->`);
|
|
15153
|
-
for (const item of section.items) {
|
|
15154
|
-
if (item.error) {
|
|
15155
|
-
parts.push(`<!-- ERROR ${item.title}: ${item.error.message} -->`);
|
|
15156
|
-
continue;
|
|
15157
|
-
}
|
|
15158
|
-
let html = item.html;
|
|
15159
|
-
if (prettify) {
|
|
15160
|
-
try {
|
|
15161
|
-
html = (await prettify(html, { parser: "html" })).trimEnd();
|
|
15162
|
-
} catch {}
|
|
15163
|
-
}
|
|
15164
|
-
parts.push(`<!-- ${item.title} -->`);
|
|
15165
|
-
parts.push(html);
|
|
15166
|
-
}
|
|
15167
|
-
}
|
|
15168
|
-
return parts.join(`
|
|
15169
|
-
`);
|
|
15170
|
-
}
|
|
15171
|
-
var { supports: supports4, format: format4 } = makeFormatter("html", {
|
|
15172
|
-
RenderBatch: fmtRenderBatch3
|
|
15173
|
-
});
|
|
15174
|
-
|
|
15175
15690
|
// tools/format/index.js
|
|
15176
15691
|
var FORMATTERS = { cli: exports_cli, md: exports_md, json: exports_json, html: exports_html };
|
|
15177
15692
|
function pickFormatter(name) {
|
|
15178
15693
|
const f = FORMATTERS[name];
|
|
15179
15694
|
if (!f) {
|
|
15180
|
-
|
|
15695
|
+
const err = new Error(`Unknown format '${name}'. Valid formats: ${Object.keys(FORMATTERS).join(", ")}`);
|
|
15696
|
+
err.code = "ERR_FORMAT_UNKNOWN";
|
|
15697
|
+
err.formatName = name;
|
|
15698
|
+
err.validFormats = Object.keys(FORMATTERS);
|
|
15699
|
+
throw err;
|
|
15181
15700
|
}
|
|
15182
15701
|
return f;
|
|
15183
15702
|
}
|
|
15703
|
+
function formattersSupporting(kind) {
|
|
15704
|
+
return Object.entries(FORMATTERS).filter(([, f]) => f.supports?.has(kind)).map(([name]) => name);
|
|
15705
|
+
}
|
|
15184
15706
|
async function formatResult(formatName, result, options = {}) {
|
|
15185
15707
|
const f = pickFormatter(formatName);
|
|
15186
15708
|
const kind = result.constructor.name;
|
|
15187
15709
|
if (!f.supports?.has(kind)) {
|
|
15188
|
-
|
|
15710
|
+
const supported = formattersSupporting(kind);
|
|
15711
|
+
const supportedTail = supported.length ? ` Use one of: ${supported.join(", ")}.` : "";
|
|
15712
|
+
const err = new Error(`Format '${formatName}' does not support ${kind}.${supportedTail}`);
|
|
15713
|
+
err.code = "ERR_FORMAT_UNSUPPORTED";
|
|
15714
|
+
err.formatName = formatName;
|
|
15715
|
+
err.resultKind = kind;
|
|
15716
|
+
err.supportedFormats = supported;
|
|
15717
|
+
throw err;
|
|
15189
15718
|
}
|
|
15190
15719
|
return await f.format(result, options);
|
|
15191
15720
|
}
|
|
@@ -15206,7 +15735,7 @@ async function emit(result, { format: format5, pretty, output }) {
|
|
|
15206
15735
|
|
|
15207
15736
|
// tools/cli/with-module.js
|
|
15208
15737
|
async function runCommand(cmd, argv, globalOpts) {
|
|
15209
|
-
const parsed =
|
|
15738
|
+
const parsed = parseArgs3({
|
|
15210
15739
|
args: argv,
|
|
15211
15740
|
options: cmd.parseOptions ?? {},
|
|
15212
15741
|
allowPositionals: true
|
|
@@ -15229,23 +15758,29 @@ async function runCommand(cmd, argv, globalOpts) {
|
|
|
15229
15758
|
// tools/tutuca.js
|
|
15230
15759
|
var NO_MODULE_COMMANDS = {
|
|
15231
15760
|
help: exports_help,
|
|
15232
|
-
|
|
15761
|
+
feedback: exports_feedback,
|
|
15762
|
+
"install-skill": exports_install_skill,
|
|
15763
|
+
"agent-context": exports_agent_context
|
|
15233
15764
|
};
|
|
15234
|
-
|
|
15235
|
-
|
|
15236
|
-
Run \`tutuca help\` for usage.
|
|
15237
|
-
`);
|
|
15238
|
-
process.exit(1);
|
|
15239
|
-
}
|
|
15765
|
+
var VALID_FORMATS = ["cli", "md", "json", "html"];
|
|
15766
|
+
var GLOBAL_FLAGS2 = ["format", "output", "pretty", "module", "json", "help", "no-color"];
|
|
15240
15767
|
function extractGlobals(argv) {
|
|
15241
15768
|
const rest = [];
|
|
15242
|
-
const opts = {
|
|
15769
|
+
const opts = {
|
|
15770
|
+
format: null,
|
|
15771
|
+
output: null,
|
|
15772
|
+
pretty: false,
|
|
15773
|
+
module: null,
|
|
15774
|
+
help: false
|
|
15775
|
+
};
|
|
15243
15776
|
for (let i = 0;i < argv.length; i++) {
|
|
15244
15777
|
const a = argv[i];
|
|
15245
15778
|
if (a === "-f" || a === "--format") {
|
|
15246
15779
|
opts.format = argv[++i];
|
|
15247
15780
|
} else if (a.startsWith("--format=")) {
|
|
15248
15781
|
opts.format = a.slice("--format=".length);
|
|
15782
|
+
} else if (a === "--json") {
|
|
15783
|
+
opts.format = "json";
|
|
15249
15784
|
} else if (a === "-o" || a === "--output") {
|
|
15250
15785
|
opts.output = argv[++i];
|
|
15251
15786
|
} else if (a.startsWith("--output=")) {
|
|
@@ -15262,41 +15797,59 @@ function extractGlobals(argv) {
|
|
|
15262
15797
|
rest.push(a);
|
|
15263
15798
|
}
|
|
15264
15799
|
}
|
|
15800
|
+
if (opts.format != null && !VALID_FORMATS.includes(opts.format)) {
|
|
15801
|
+
emitError(opts, {
|
|
15802
|
+
code: CODES.FORMAT_UNKNOWN,
|
|
15803
|
+
message: `Unknown format '${opts.format}'`,
|
|
15804
|
+
suggestion: didYouMean(opts.format, VALID_FORMATS),
|
|
15805
|
+
hint: `Valid formats: ${VALID_FORMATS.join(", ")}`
|
|
15806
|
+
});
|
|
15807
|
+
}
|
|
15265
15808
|
return { opts, rest };
|
|
15266
15809
|
}
|
|
15810
|
+
function dispatchKnownCommands() {
|
|
15811
|
+
return [...Object.keys(COMMANDS), ...Object.keys(NO_MODULE_COMMANDS)];
|
|
15812
|
+
}
|
|
15267
15813
|
async function main() {
|
|
15268
15814
|
const { opts, rest } = extractGlobals(process.argv.slice(2));
|
|
15269
15815
|
if (rest.length === 0 || opts.help && rest.length === 0) {
|
|
15270
|
-
await
|
|
15816
|
+
await run4([], opts);
|
|
15271
15817
|
return;
|
|
15272
15818
|
}
|
|
15273
|
-
|
|
15274
|
-
let commandArgs;
|
|
15275
|
-
if (NO_MODULE_COMMANDS[rest[0]]) {
|
|
15276
|
-
command = rest[0];
|
|
15277
|
-
commandArgs = rest.slice(1);
|
|
15278
|
-
} else if (opts.module) {
|
|
15279
|
-
command = rest[0];
|
|
15280
|
-
commandArgs = rest.slice(1);
|
|
15281
|
-
} else {
|
|
15282
|
-
if (rest.length < 2)
|
|
15283
|
-
return usageError("expected <module-path> <command>");
|
|
15284
|
-
opts.module = rest[0];
|
|
15285
|
-
command = rest[1];
|
|
15286
|
-
commandArgs = rest.slice(2);
|
|
15287
|
-
}
|
|
15819
|
+
const command = rest[0];
|
|
15288
15820
|
if (NO_MODULE_COMMANDS[command]) {
|
|
15289
|
-
const
|
|
15821
|
+
const commandArgs2 = rest.slice(1);
|
|
15822
|
+
const args = opts.help ? [...commandArgs2, "--help"] : commandArgs2;
|
|
15290
15823
|
await NO_MODULE_COMMANDS[command].run(args, opts);
|
|
15291
15824
|
return;
|
|
15292
15825
|
}
|
|
15293
15826
|
if (opts.help) {
|
|
15294
|
-
await
|
|
15827
|
+
await run4([command], opts);
|
|
15295
15828
|
return;
|
|
15296
15829
|
}
|
|
15297
15830
|
const cmd = COMMANDS[command];
|
|
15298
|
-
if (!cmd)
|
|
15299
|
-
|
|
15831
|
+
if (!cmd) {
|
|
15832
|
+
const known = dispatchKnownCommands();
|
|
15833
|
+
emitError(opts, {
|
|
15834
|
+
code: CODES.USAGE_UNKNOWN_COMMAND,
|
|
15835
|
+
message: `Unknown command '${command}'`,
|
|
15836
|
+
suggestion: didYouMean(command, known),
|
|
15837
|
+
hint: "Run `tutuca help` for the full reference."
|
|
15838
|
+
});
|
|
15839
|
+
}
|
|
15840
|
+
let commandArgs;
|
|
15841
|
+
if (opts.module) {
|
|
15842
|
+
commandArgs = rest.slice(1);
|
|
15843
|
+
} else if (rest.length < 2 || rest[1].startsWith("-")) {
|
|
15844
|
+
emitError(opts, {
|
|
15845
|
+
code: CODES.USAGE_MISSING_MODULE,
|
|
15846
|
+
message: `'${command}' requires a module path`,
|
|
15847
|
+
hint: `Pass it as the second positional: \`tutuca ${command} <module-path>\`, or use --module=<path>.`
|
|
15848
|
+
});
|
|
15849
|
+
} else {
|
|
15850
|
+
opts.module = rest[1];
|
|
15851
|
+
commandArgs = rest.slice(2);
|
|
15852
|
+
}
|
|
15300
15853
|
try {
|
|
15301
15854
|
await runCommand(cmd, commandArgs, opts);
|
|
15302
15855
|
} catch (e) {
|
|
@@ -15306,21 +15859,56 @@ async function main() {
|
|
|
15306
15859
|
parts.push(opts.module);
|
|
15307
15860
|
if (e.where)
|
|
15308
15861
|
parts.push(`@ ${e.where}`);
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15862
|
+
emitError(opts, {
|
|
15863
|
+
code: CODES.MODULE_SHAPE_MISMATCH,
|
|
15864
|
+
message: e.message,
|
|
15865
|
+
where: parts.length ? parts.join(" ") : null
|
|
15866
|
+
});
|
|
15867
|
+
}
|
|
15868
|
+
if (e?.code === "ERR_FORMAT_UNKNOWN") {
|
|
15869
|
+
emitError(opts, {
|
|
15870
|
+
code: CODES.FORMAT_UNKNOWN,
|
|
15871
|
+
message: e.message,
|
|
15872
|
+
suggestion: didYouMean(e.formatName, e.validFormats ?? VALID_FORMATS)
|
|
15873
|
+
});
|
|
15874
|
+
}
|
|
15875
|
+
if (e?.code === "ERR_FORMAT_UNSUPPORTED") {
|
|
15876
|
+
emitError(opts, {
|
|
15877
|
+
code: CODES.FORMAT_UNSUPPORTED,
|
|
15878
|
+
message: e.message,
|
|
15879
|
+
suggestion: e.supportedFormats?.length ? { kind: "rewrite", from: e.formatName, to: e.supportedFormats[0] } : null
|
|
15880
|
+
});
|
|
15313
15881
|
}
|
|
15314
15882
|
if (e?.code?.startsWith?.("ERR_PARSE_ARGS_")) {
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15883
|
+
const validFlags = [...Object.keys(cmd.parseOptions ?? {}), ...GLOBAL_FLAGS2];
|
|
15884
|
+
const shape = parseArgsErrorShape(e, validFlags);
|
|
15885
|
+
emitError(opts, {
|
|
15886
|
+
...shape,
|
|
15887
|
+
hint: shape.hint ?? `Run \`tutuca help ${command}\` for valid flags.`
|
|
15888
|
+
});
|
|
15318
15889
|
}
|
|
15319
15890
|
throw e;
|
|
15320
15891
|
}
|
|
15321
15892
|
}
|
|
15322
15893
|
main().catch((e) => {
|
|
15323
|
-
process.
|
|
15894
|
+
const wantsJson = process.argv.includes("--json") || process.argv.includes("--format=json") || process.argv.includes("-f=json") || (() => {
|
|
15895
|
+
const i = process.argv.findIndex((a) => a === "-f" || a === "--format");
|
|
15896
|
+
return i >= 0 && process.argv[i + 1] === "json";
|
|
15897
|
+
})();
|
|
15898
|
+
if (wantsJson) {
|
|
15899
|
+
const envelope = {
|
|
15900
|
+
error: {
|
|
15901
|
+
code: e?.code ?? CODES.INTERNAL,
|
|
15902
|
+
message: e?.message ?? String(e)
|
|
15903
|
+
}
|
|
15904
|
+
};
|
|
15905
|
+
if (e?.where)
|
|
15906
|
+
envelope.error.where = e.where;
|
|
15907
|
+
process.stderr.write(`${JSON.stringify(envelope)}
|
|
15908
|
+
`);
|
|
15909
|
+
} else {
|
|
15910
|
+
process.stderr.write(`${e.stack ?? e.message ?? e}
|
|
15324
15911
|
`);
|
|
15912
|
+
}
|
|
15325
15913
|
process.exit(1);
|
|
15326
15914
|
});
|