create-cascade-skill 0.1.6 → 0.1.8
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/index.js +502 -287
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -1,44 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve, join } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { emitKeypressEvents } from "node:readline";
|
|
8
|
+
|
|
9
|
+
const ANSI_RESET = "\x1b[0m";
|
|
10
|
+
const ANSI_BOLD = "\x1b[1m";
|
|
11
|
+
const ANSI_DIM = "\x1b[2m";
|
|
12
|
+
const ANSI_CYAN = "\x1b[36m";
|
|
13
|
+
const ANSI_GREEN = "\x1b[32m";
|
|
14
|
+
const ANSI_YELLOW = "\x1b[33m";
|
|
15
|
+
|
|
16
|
+
function getHomeDirectory(options) {
|
|
17
|
+
if (options?.home) {
|
|
18
|
+
return options.home;
|
|
19
|
+
}
|
|
20
|
+
return (
|
|
21
|
+
process.env.CASCADE_SKILL_HOME ||
|
|
22
|
+
process.env.HOME ||
|
|
23
|
+
process.env.USERPROFILE ||
|
|
24
|
+
process.cwd()
|
|
25
|
+
);
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
function commandExists(command) {
|
|
22
|
-
const lookup = process.platform === "win32" ? "where" : "which"
|
|
23
|
-
const result = spawnSync(lookup, [command], { stdio: "ignore" })
|
|
24
|
-
return result.status === 0
|
|
29
|
+
const lookup = process.platform === "win32" ? "where" : "which";
|
|
30
|
+
const result = spawnSync(lookup, [command], { stdio: "ignore" });
|
|
31
|
+
return result.status === 0;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
function parseAgentsArg(value) {
|
|
28
35
|
return value
|
|
29
36
|
.split(",")
|
|
30
37
|
.map((part) => part.trim().toLowerCase())
|
|
31
|
-
.filter((part) => part.length > 0)
|
|
38
|
+
.filter((part) => part.length > 0);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
function unique(items) {
|
|
35
|
-
return [...new Set(items)]
|
|
42
|
+
return [...new Set(items)];
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
function getAgents() {
|
|
39
|
-
const home = getHomeDirectory()
|
|
40
|
-
const appData =
|
|
41
|
-
|
|
45
|
+
function getAgents(options) {
|
|
46
|
+
const home = getHomeDirectory(options);
|
|
47
|
+
const appData =
|
|
48
|
+
options?.appData ||
|
|
49
|
+
process.env.CASCADE_SKILL_APPDATA ||
|
|
50
|
+
process.env.APPDATA ||
|
|
51
|
+
join(home, "AppData", "Roaming");
|
|
42
52
|
return [
|
|
43
53
|
{
|
|
44
54
|
id: "codex",
|
|
@@ -67,13 +77,29 @@ function getAgents() {
|
|
|
67
77
|
installPath: join(home, ".cursor", "skills", "cascadetui", "SKILL.md"),
|
|
68
78
|
flavor: "cursor",
|
|
69
79
|
},
|
|
80
|
+
{
|
|
81
|
+
id: "factory",
|
|
82
|
+
label: "Factory (Droid CLI)",
|
|
83
|
+
description: "Install in ~/.factory/skills/cascadetui/SKILL.md",
|
|
84
|
+
commands: ["droid"],
|
|
85
|
+
detectPaths: [join(home, ".factory")],
|
|
86
|
+
installPath: join(home, ".factory", "skills", "cascadetui", "SKILL.md"),
|
|
87
|
+
flavor: "factory",
|
|
88
|
+
},
|
|
70
89
|
{
|
|
71
90
|
id: "windsurf",
|
|
72
91
|
label: "Windsurf",
|
|
73
92
|
description: "Install in ~/.codeium/windsurf/skills/cascadetui/SKILL.md",
|
|
74
93
|
commands: ["windsurf"],
|
|
75
94
|
detectPaths: [join(home, ".codeium"), join(appData, "Codeium")],
|
|
76
|
-
installPath: join(
|
|
95
|
+
installPath: join(
|
|
96
|
+
home,
|
|
97
|
+
".codeium",
|
|
98
|
+
"windsurf",
|
|
99
|
+
"skills",
|
|
100
|
+
"cascadetui",
|
|
101
|
+
"SKILL.md"
|
|
102
|
+
),
|
|
77
103
|
flavor: "generic",
|
|
78
104
|
},
|
|
79
105
|
{
|
|
@@ -136,7 +162,14 @@ function getAgents() {
|
|
|
136
162
|
description: "Install in ~/.config/goose/skills/cascadetui/SKILL.md",
|
|
137
163
|
commands: ["goose"],
|
|
138
164
|
detectPaths: [join(home, ".config", "goose"), join(appData, "goose")],
|
|
139
|
-
installPath: join(
|
|
165
|
+
installPath: join(
|
|
166
|
+
home,
|
|
167
|
+
".config",
|
|
168
|
+
"goose",
|
|
169
|
+
"skills",
|
|
170
|
+
"cascadetui",
|
|
171
|
+
"SKILL.md"
|
|
172
|
+
),
|
|
140
173
|
flavor: "generic",
|
|
141
174
|
},
|
|
142
175
|
{
|
|
@@ -148,107 +181,129 @@ function getAgents() {
|
|
|
148
181
|
installPath: join(home, ".ami", "skills", "cascadetui", "SKILL.md"),
|
|
149
182
|
flavor: "generic",
|
|
150
183
|
},
|
|
151
|
-
]
|
|
184
|
+
];
|
|
152
185
|
}
|
|
153
186
|
|
|
154
187
|
function parseArgs(argv) {
|
|
155
|
-
const args = argv.slice(2)
|
|
188
|
+
const args = argv.slice(2);
|
|
156
189
|
const options = {
|
|
157
190
|
agents: [],
|
|
158
191
|
allDetected: false,
|
|
159
192
|
list: false,
|
|
160
193
|
dryRun: false,
|
|
194
|
+
force: false,
|
|
161
195
|
help: false,
|
|
162
|
-
|
|
163
|
-
|
|
196
|
+
home: undefined,
|
|
197
|
+
};
|
|
164
198
|
for (let i = 0; i < args.length; i += 1) {
|
|
165
|
-
const arg = args[i]
|
|
166
|
-
|
|
199
|
+
const arg = args[i];
|
|
167
200
|
if (arg === "--agents" || arg === "-a") {
|
|
168
|
-
options.agents.push(...parseAgentsArg(args[i + 1] || ""))
|
|
169
|
-
i += 1
|
|
170
|
-
continue
|
|
201
|
+
options.agents.push(...parseAgentsArg(args[i + 1] || ""));
|
|
202
|
+
i += 1;
|
|
203
|
+
continue;
|
|
171
204
|
}
|
|
172
205
|
if (arg.startsWith("--agents=")) {
|
|
173
|
-
options.agents.push(...parseAgentsArg(arg.slice("--agents=".length)))
|
|
174
|
-
continue
|
|
206
|
+
options.agents.push(...parseAgentsArg(arg.slice("--agents=".length)));
|
|
207
|
+
continue;
|
|
175
208
|
}
|
|
176
209
|
if (arg === "--all-detected") {
|
|
177
|
-
options.allDetected = true
|
|
178
|
-
continue
|
|
210
|
+
options.allDetected = true;
|
|
211
|
+
continue;
|
|
179
212
|
}
|
|
180
213
|
if (arg === "--list") {
|
|
181
|
-
options.list = true
|
|
182
|
-
continue
|
|
214
|
+
options.list = true;
|
|
215
|
+
continue;
|
|
183
216
|
}
|
|
184
217
|
if (arg === "--dry-run") {
|
|
185
|
-
options.dryRun = true
|
|
186
|
-
continue
|
|
218
|
+
options.dryRun = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (arg === "--force") {
|
|
222
|
+
options.force = true;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (arg === "--home") {
|
|
226
|
+
options.home = args[i + 1];
|
|
227
|
+
i += 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (arg.startsWith("--home=")) {
|
|
231
|
+
options.home = arg.slice("--home=".length);
|
|
232
|
+
continue;
|
|
187
233
|
}
|
|
188
234
|
if (arg === "--help" || arg === "-h") {
|
|
189
|
-
options.help = true
|
|
190
|
-
continue
|
|
235
|
+
options.help = true;
|
|
236
|
+
continue;
|
|
191
237
|
}
|
|
192
|
-
throw new Error(`Unknown argument: ${arg}`)
|
|
238
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
193
239
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return options
|
|
240
|
+
options.agents = unique(options.agents);
|
|
241
|
+
return options;
|
|
197
242
|
}
|
|
198
243
|
|
|
199
244
|
function printHelp() {
|
|
200
|
-
console.log("Usage: npx create-cascade-skill [options]")
|
|
201
|
-
console.log("")
|
|
202
|
-
console.log("Options:")
|
|
203
|
-
console.log(" -a, --agents <ids> Comma-separated agent IDs to install")
|
|
204
|
-
console.log(" --all-detected Install for all detected agents")
|
|
205
|
-
console.log(" --list Print supported and detected agents")
|
|
206
|
-
console.log(" --dry-run Preview files without writing")
|
|
207
|
-
console.log("
|
|
208
|
-
console.log("")
|
|
209
|
-
console.log("
|
|
210
|
-
console.log("
|
|
211
|
-
console.log("
|
|
212
|
-
console.log(" npx create-cascade-skill
|
|
213
|
-
console.log(" npx create-cascade-skill --
|
|
245
|
+
console.log("Usage: npx create-cascade-skill [options]");
|
|
246
|
+
console.log("");
|
|
247
|
+
console.log("Options:");
|
|
248
|
+
console.log(" -a, --agents <ids> Comma-separated agent IDs to install");
|
|
249
|
+
console.log(" --all-detected Install for all detected agents");
|
|
250
|
+
console.log(" --list Print supported and detected agents");
|
|
251
|
+
console.log(" --dry-run Preview files without writing");
|
|
252
|
+
console.log(" --force Overwrite SKILL.md when it differs");
|
|
253
|
+
console.log(" --home <path> Override home directory used for detection/install");
|
|
254
|
+
console.log(" -h, --help Show help");
|
|
255
|
+
console.log("");
|
|
256
|
+
console.log("Examples:");
|
|
257
|
+
console.log(" npx create-cascade-skill");
|
|
258
|
+
console.log(" npx create-cascade-skill --all-detected");
|
|
259
|
+
console.log(" npx create-cascade-skill --agents codex,cursor,cline");
|
|
260
|
+
console.log(" npx create-cascade-skill --agents codex --dry-run");
|
|
261
|
+
console.log(" npx create-cascade-skill --agents windsurf --home ./sandbox --dry-run");
|
|
214
262
|
}
|
|
215
263
|
|
|
216
264
|
function detectAgents(agents) {
|
|
217
265
|
return agents.map((agent) => {
|
|
218
|
-
const commandHit = agent.commands.some((command) => commandExists(command))
|
|
219
|
-
const pathHit = agent.detectPaths.some((path) => existsSync(path))
|
|
220
|
-
return { ...agent, detected: commandHit || pathHit }
|
|
221
|
-
})
|
|
266
|
+
const commandHit = agent.commands.some((command) => commandExists(command));
|
|
267
|
+
const pathHit = agent.detectPaths.some((path) => existsSync(path));
|
|
268
|
+
return { ...agent, detected: commandHit || pathHit };
|
|
269
|
+
});
|
|
222
270
|
}
|
|
223
271
|
|
|
224
272
|
function printList(agents) {
|
|
225
|
-
console.log(`${ANSI_BOLD}Supported agents${ANSI_RESET}`)
|
|
273
|
+
console.log(`${ANSI_BOLD}Supported agents${ANSI_RESET}`);
|
|
226
274
|
for (const agent of agents) {
|
|
227
|
-
const marker = agent.detected
|
|
228
|
-
|
|
275
|
+
const marker = agent.detected
|
|
276
|
+
? `${ANSI_GREEN}detected${ANSI_RESET}`
|
|
277
|
+
: `${ANSI_YELLOW}not detected${ANSI_RESET}`;
|
|
278
|
+
console.log(`- ${agent.id.padEnd(12)} ${marker} ${agent.label}`);
|
|
229
279
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
280
|
+
const detected = agents
|
|
281
|
+
.filter((agent) => agent.detected)
|
|
282
|
+
.map((agent) => agent.id);
|
|
283
|
+
console.log("");
|
|
233
284
|
if (detected.length > 0) {
|
|
234
|
-
console.log(`Detected IDs: ${detected.join(", ")}`)
|
|
235
|
-
console.log(
|
|
285
|
+
console.log(`Detected IDs: ${detected.join(", ")}`);
|
|
286
|
+
console.log(
|
|
287
|
+
`Install all detected: npx create-cascade-skill --agents ${detected.join(
|
|
288
|
+
","
|
|
289
|
+
)}`
|
|
290
|
+
);
|
|
236
291
|
} else {
|
|
237
|
-
console.log("Detected IDs: none")
|
|
292
|
+
console.log("Detected IDs: none");
|
|
238
293
|
}
|
|
239
294
|
}
|
|
240
295
|
|
|
241
296
|
function validateAgentIds(selected, agents) {
|
|
242
|
-
const allowed = new Set(agents.map((agent) => agent.id))
|
|
297
|
+
const allowed = new Set(agents.map((agent) => agent.id));
|
|
243
298
|
for (const id of selected) {
|
|
244
299
|
if (!allowed.has(id)) {
|
|
245
|
-
throw new Error(`Unknown agent id: ${id}`)
|
|
300
|
+
throw new Error(`Unknown agent id: ${id}`);
|
|
246
301
|
}
|
|
247
302
|
}
|
|
248
303
|
}
|
|
249
304
|
|
|
250
305
|
function promptLine(rl, label) {
|
|
251
|
-
return rl.question(label)
|
|
306
|
+
return rl.question(label);
|
|
252
307
|
}
|
|
253
308
|
|
|
254
309
|
function toSelectableOptions(agents) {
|
|
@@ -256,306 +311,466 @@ function toSelectableOptions(agents) {
|
|
|
256
311
|
id: agent.id,
|
|
257
312
|
label: agent.label,
|
|
258
313
|
description: `${agent.description}${agent.detected ? " [detected]" : ""}`,
|
|
259
|
-
}))
|
|
314
|
+
}));
|
|
260
315
|
}
|
|
261
316
|
|
|
262
317
|
async function selectMany(rl, label, options, preselectedIds) {
|
|
263
318
|
if (!process.stdin.isTTY) {
|
|
264
|
-
return preselectedIds
|
|
319
|
+
return preselectedIds;
|
|
265
320
|
}
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
let
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
let renderedOnce = false
|
|
273
|
-
|
|
321
|
+
const stdin = process.stdin;
|
|
322
|
+
const stdout = process.stdout;
|
|
323
|
+
let selectedIndex = 0;
|
|
324
|
+
let selected = new Set(preselectedIds);
|
|
325
|
+
const totalLines = options.length + 3;
|
|
326
|
+
let renderedOnce = false;
|
|
274
327
|
const render = () => {
|
|
275
328
|
if (renderedOnce) {
|
|
276
|
-
stdout.write(`\x1b[${totalLines}F`)
|
|
329
|
+
stdout.write(`\x1b[${totalLines}F`);
|
|
277
330
|
} else {
|
|
278
|
-
stdout.write("\n")
|
|
331
|
+
stdout.write("\n");
|
|
279
332
|
}
|
|
280
|
-
|
|
281
|
-
stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`)
|
|
333
|
+
stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`);
|
|
282
334
|
for (let i = 0; i < options.length; i += 1) {
|
|
283
|
-
const option = options[i]
|
|
284
|
-
const isCursor = i === selectedIndex
|
|
285
|
-
const isSelected = selected.has(option.id)
|
|
286
|
-
const cursor = isCursor
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
335
|
+
const option = options[i];
|
|
336
|
+
const isCursor = i === selectedIndex;
|
|
337
|
+
const isSelected = selected.has(option.id);
|
|
338
|
+
const cursor = isCursor
|
|
339
|
+
? `${ANSI_BOLD}${ANSI_CYAN}>${ANSI_RESET}`
|
|
340
|
+
: " ";
|
|
341
|
+
const mark = isSelected ? `${ANSI_GREEN}[x]${ANSI_RESET}` : "[ ]";
|
|
342
|
+
const styleStart = isCursor ? `${ANSI_BOLD}${ANSI_CYAN}` : "";
|
|
343
|
+
const styleEnd = isCursor ? ANSI_RESET : "";
|
|
344
|
+
stdout.write(
|
|
345
|
+
`${cursor} ${mark} ${styleStart}${option.label}${styleEnd} ${ANSI_DIM}(${option.id}) - ${option.description}${ANSI_RESET}\n`
|
|
346
|
+
);
|
|
291
347
|
}
|
|
292
|
-
stdout.write(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
348
|
+
stdout.write(
|
|
349
|
+
`${ANSI_DIM}Use Up/Down, Space to toggle, A to toggle all, Enter to confirm${ANSI_RESET}\n`
|
|
350
|
+
);
|
|
351
|
+
renderedOnce = true;
|
|
352
|
+
};
|
|
296
353
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
297
354
|
const cleanup = () => {
|
|
298
|
-
stdin.off("keypress", onKeyPress)
|
|
355
|
+
stdin.off("keypress", onKeyPress);
|
|
299
356
|
if (stdin.isTTY) {
|
|
300
|
-
stdin.setRawMode(false)
|
|
357
|
+
stdin.setRawMode(false);
|
|
301
358
|
}
|
|
302
|
-
stdout.write("\n")
|
|
303
|
-
}
|
|
304
|
-
|
|
359
|
+
stdout.write("\n");
|
|
360
|
+
};
|
|
305
361
|
const onKeyPress = (_, key) => {
|
|
306
362
|
if (!key) {
|
|
307
|
-
return
|
|
363
|
+
return;
|
|
308
364
|
}
|
|
309
|
-
|
|
310
365
|
if (key.ctrl && key.name === "c") {
|
|
311
|
-
cleanup()
|
|
312
|
-
rejectPromise(new Error("Operation cancelled"))
|
|
313
|
-
return
|
|
366
|
+
cleanup();
|
|
367
|
+
rejectPromise(new Error("Operation cancelled"));
|
|
368
|
+
return;
|
|
314
369
|
}
|
|
315
|
-
|
|
316
370
|
if (key.name === "up") {
|
|
317
|
-
selectedIndex =
|
|
318
|
-
|
|
319
|
-
|
|
371
|
+
selectedIndex =
|
|
372
|
+
selectedIndex === 0 ? options.length - 1 : selectedIndex - 1;
|
|
373
|
+
render();
|
|
374
|
+
return;
|
|
320
375
|
}
|
|
321
|
-
|
|
322
376
|
if (key.name === "down") {
|
|
323
|
-
selectedIndex =
|
|
324
|
-
|
|
325
|
-
|
|
377
|
+
selectedIndex =
|
|
378
|
+
selectedIndex === options.length - 1 ? 0 : selectedIndex + 1;
|
|
379
|
+
render();
|
|
380
|
+
return;
|
|
326
381
|
}
|
|
327
|
-
|
|
328
382
|
if (key.name === "space") {
|
|
329
|
-
const id = options[selectedIndex].id
|
|
383
|
+
const id = options[selectedIndex].id;
|
|
330
384
|
if (selected.has(id)) {
|
|
331
|
-
selected.delete(id)
|
|
385
|
+
selected.delete(id);
|
|
332
386
|
} else {
|
|
333
|
-
selected.add(id)
|
|
387
|
+
selected.add(id);
|
|
334
388
|
}
|
|
335
|
-
render()
|
|
336
|
-
return
|
|
389
|
+
render();
|
|
390
|
+
return;
|
|
337
391
|
}
|
|
338
|
-
|
|
339
392
|
if (key.name === "a") {
|
|
340
393
|
if (selected.size === options.length) {
|
|
341
|
-
selected = new Set()
|
|
394
|
+
selected = new Set();
|
|
342
395
|
} else {
|
|
343
|
-
selected = new Set(options.map((option) => option.id))
|
|
396
|
+
selected = new Set(options.map((option) => option.id));
|
|
344
397
|
}
|
|
345
|
-
render()
|
|
346
|
-
return
|
|
398
|
+
render();
|
|
399
|
+
return;
|
|
347
400
|
}
|
|
348
|
-
|
|
349
401
|
if (key.name === "return") {
|
|
350
|
-
cleanup()
|
|
351
|
-
resolvePromise([...selected])
|
|
402
|
+
cleanup();
|
|
403
|
+
resolvePromise([...selected]);
|
|
352
404
|
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
stdin.
|
|
357
|
-
stdin.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
})
|
|
405
|
+
};
|
|
406
|
+
emitKeypressEvents(stdin);
|
|
407
|
+
stdin.setRawMode(true);
|
|
408
|
+
stdin.resume();
|
|
409
|
+
stdin.on("keypress", onKeyPress);
|
|
410
|
+
render();
|
|
411
|
+
});
|
|
361
412
|
}
|
|
362
413
|
|
|
363
|
-
function
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
compatibility
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
414
|
+
function getSkillFrontmatter(agent) {
|
|
415
|
+
const baseName = "cascadetui";
|
|
416
|
+
const description =
|
|
417
|
+
"Build terminal user interfaces with CascadeTUI. Use this skill to scaffold, debug, and refactor Cascade-based TUIs (layout, input, rendering, keyboard navigation, React/Solid bindings). Triggers: Cascade, CascadeTUI, TUI, terminal UI, keybindings, focus, renderer.";
|
|
418
|
+
let compatibility = "Requires Bun and TypeScript.";
|
|
419
|
+
if (agent.flavor === "claude") {
|
|
420
|
+
compatibility += " Designed for Claude Code.";
|
|
421
|
+
} else if (agent.flavor === "cursor") {
|
|
422
|
+
compatibility += " Designed for Cursor.";
|
|
423
|
+
} else if (agent.flavor === "codex") {
|
|
424
|
+
compatibility += " Designed for OpenAI Codex.";
|
|
425
|
+
} else if (agent.flavor === "factory") {
|
|
426
|
+
compatibility += " Designed for Factory (Droid CLI).";
|
|
427
|
+
} else {
|
|
428
|
+
compatibility += ` Designed for ${agent.label}.`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const allowedTools =
|
|
432
|
+
agent.flavor === "factory"
|
|
433
|
+
? "Read, Bash, Write"
|
|
434
|
+
: "Bash(bun:*) Bash(npm:*) Bash(node:*)";
|
|
435
|
+
|
|
436
|
+
const extraFactoryFrontmatter =
|
|
437
|
+
agent.flavor === "factory"
|
|
438
|
+
? `\nuser-invocable: true\ndisable-model-invocation: false`
|
|
439
|
+
: "";
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
`---\n` +
|
|
443
|
+
`name: ${baseName}\n` +
|
|
444
|
+
`description: ${description}\n` +
|
|
445
|
+
`compatibility: ${compatibility}\n` +
|
|
446
|
+
`allowed-tools: ${allowedTools}` +
|
|
447
|
+
`${extraFactoryFrontmatter}\n` +
|
|
448
|
+
`metadata:\n` +
|
|
449
|
+
` author: cascadetui\n` +
|
|
450
|
+
` version: "1.2"\n` +
|
|
451
|
+
`---`
|
|
452
|
+
);
|
|
372
453
|
}
|
|
373
454
|
|
|
374
|
-
function
|
|
375
|
-
return `# CascadeTUI Skill
|
|
376
|
-
|
|
377
|
-
##
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
-
|
|
392
|
-
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
455
|
+
function getSkillBody() {
|
|
456
|
+
return `# CascadeTUI Engineering Skill
|
|
457
|
+
|
|
458
|
+
## Use This When
|
|
459
|
+
|
|
460
|
+
Activate for tasks involving:
|
|
461
|
+
- Building a new terminal UI (TUI) with CascadeTUI
|
|
462
|
+
- Fixing layout, rendering glitches, or resize bugs
|
|
463
|
+
- Keyboard navigation, focus, selection, shortcuts, input handling
|
|
464
|
+
- React/Solid bindings on top of CascadeTUI core
|
|
465
|
+
- Performance issues (re-render storms, slow lists) or state determinism
|
|
466
|
+
|
|
467
|
+
## Output Expectations
|
|
468
|
+
|
|
469
|
+
When implementing or refactoring, produce:
|
|
470
|
+
- A minimal, runnable entrypoint that demonstrates the behavior
|
|
471
|
+
- Deterministic state updates and predictable render cycles
|
|
472
|
+
- Clear keybindings and focus behavior
|
|
473
|
+
- A short verification checklist (commands + manual steps)
|
|
474
|
+
|
|
475
|
+
## Project Workflow (Bun-first)
|
|
476
|
+
|
|
477
|
+
1) Ensure dependencies
|
|
478
|
+
\`\`\`bash
|
|
479
|
+
bun install
|
|
480
|
+
\`\`\`
|
|
481
|
+
|
|
482
|
+
2) Run the app (or a repro script)
|
|
483
|
+
\`\`\`bash
|
|
484
|
+
bun run dev
|
|
485
|
+
\`\`\`
|
|
486
|
+
|
|
487
|
+
3) Add a tiny repro when debugging
|
|
488
|
+
- Create \`scripts/repro.ts\` or a minimal app entrypoint
|
|
489
|
+
- Keep it self-contained: one screen, one interaction, one bug
|
|
490
|
+
|
|
491
|
+
## Design Rules (CascadeTUI-specific)
|
|
492
|
+
|
|
493
|
+
### Deterministic UI
|
|
494
|
+
- Treat rendering as a pure function of state
|
|
495
|
+
- Avoid hidden mutable globals for UI state
|
|
496
|
+
- Prefer single source of truth (one store or a small set of state atoms)
|
|
497
|
+
|
|
498
|
+
### Layout & Composition
|
|
499
|
+
- Compose screens with containers and consistent spacing
|
|
500
|
+
- Keep one responsibility per component: layout vs input vs domain logic
|
|
501
|
+
- Use stable keys for lists; avoid index keys if items can move
|
|
502
|
+
|
|
503
|
+
### Input, Focus, and Navigation
|
|
504
|
+
- Define a keymap per screen (Up/Down, Enter, Esc, Tab, Ctrl shortcuts)
|
|
505
|
+
- Always document primary actions and an escape/back path
|
|
506
|
+
- Ensure focus is explicit: which element receives keys right now
|
|
507
|
+
- Handle terminal resize: reflow layout and keep selection stable
|
|
508
|
+
|
|
509
|
+
### Rendering & Performance
|
|
510
|
+
- Avoid rebuilding large trees on every keypress
|
|
511
|
+
- For large lists: paginate, virtualize, or reduce per-row computation
|
|
512
|
+
- Batch state updates; avoid cascading updates during render
|
|
513
|
+
|
|
514
|
+
## Debugging Playbook
|
|
515
|
+
|
|
516
|
+
When something is wrong:
|
|
517
|
+
1) Confirm the bug in a tiny repro
|
|
518
|
+
2) Log state transitions around the interaction
|
|
519
|
+
3) Verify input events fire once (no duplicated handlers)
|
|
520
|
+
4) Verify keys/ids are stable (especially lists)
|
|
521
|
+
5) Verify resize behavior by changing terminal size rapidly
|
|
522
|
+
|
|
523
|
+
Common failure modes:
|
|
524
|
+
- Duplicate listeners attached on re-render
|
|
525
|
+
- Non-stable list keys causing selection jumps
|
|
526
|
+
- Async state updates racing; UI shows stale selection
|
|
527
|
+
- Layout constraints (width/height) not propagated as expected
|
|
528
|
+
|
|
529
|
+
## Quick Recipes
|
|
530
|
+
|
|
531
|
+
### Add a consistent keymap footer
|
|
532
|
+
- Show the active shortcuts at the bottom (e.g. \`q\` quit, \`/\` search, arrows navigate)
|
|
533
|
+
- Keep it updated per screen
|
|
534
|
+
|
|
535
|
+
### Search + List pattern
|
|
536
|
+
- Input line at top
|
|
537
|
+
- Filtered list in the middle
|
|
538
|
+
- Details/preview panel (optional)
|
|
539
|
+
- Enter selects, Esc clears/back
|
|
540
|
+
|
|
541
|
+
### React binding guidance
|
|
542
|
+
- Keep bridge components thin
|
|
543
|
+
- Avoid passing unstable props that trigger full-tree rerenders
|
|
544
|
+
- Prefer memoization at boundaries (list row, heavy panels)
|
|
545
|
+
|
|
546
|
+
## Verification Checklist
|
|
547
|
+
|
|
548
|
+
Run:
|
|
549
|
+
\`\`\`bash
|
|
550
|
+
bun run typecheck
|
|
551
|
+
bun run lint
|
|
552
|
+
bun test
|
|
553
|
+
\`\`\`
|
|
554
|
+
|
|
555
|
+
Manual:
|
|
556
|
+
- Start app in small and large terminals
|
|
557
|
+
- Resize while a list item is selected
|
|
558
|
+
- Navigate with keyboard only
|
|
559
|
+
- Confirm exit behavior (Ctrl+C and explicit quit key)
|
|
560
|
+
`;
|
|
400
561
|
}
|
|
401
562
|
|
|
402
|
-
function
|
|
563
|
+
function getImprovedClaudeAppendix() {
|
|
403
564
|
return `## Documentation Index
|
|
404
|
-
|
|
405
|
-
Use this file to discover all available pages before exploring further.
|
|
565
|
+
|
|
566
|
+
Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt. Use this file to discover all available pages before exploring further.
|
|
406
567
|
|
|
407
568
|
## Claude Code Skill Notes
|
|
408
|
-
- Keep this skill in a global location: \`~/.claude/skills/cascadetui/SKILL.md\`.
|
|
409
|
-
- Skills can be auto-invoked by Claude when relevant, or manually via \`/cascadetui\`.
|
|
410
|
-
- Keep the skill content concise and place optional supporting files beside \`SKILL.md\` when needed.
|
|
411
|
-
`
|
|
412
|
-
}
|
|
413
569
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
version: "1.0"
|
|
422
|
-
---`
|
|
570
|
+
- Place this skill in a global location: \`~/.claude/skills/cascadetui/SKILL.md\`. Do not nest additional directories deeper than one level.
|
|
571
|
+
- Claude automatically loads skills whose \`name\` and \`description\` fields match the user's request; keep them descriptive and concise.
|
|
572
|
+
- Follow the naming conventions from the Agent Skills specification: lowercase alphanumeric and hyphens only, no reserved words (anthropic, claude).
|
|
573
|
+
- Use progressive disclosure: keep \`SKILL.md\` under 500 lines and move lengthy instructions to \`references/\` files.
|
|
574
|
+
- Ask clarifying questions when requirements are ambiguous or when multiple interpretations exist.
|
|
575
|
+
- Keep scripts deterministic and self-contained; avoid side effects that require external network access unless absolutely necessary.
|
|
576
|
+
`;
|
|
423
577
|
}
|
|
424
578
|
|
|
425
|
-
function
|
|
579
|
+
function getImprovedCursorAppendix() {
|
|
426
580
|
return `## Cursor Skill Notes
|
|
427
|
-
|
|
428
|
-
-
|
|
429
|
-
-
|
|
430
|
-
-
|
|
431
|
-
|
|
581
|
+
|
|
582
|
+
- Install this skill globally under \`~/.cursor/skills/cascadetui/SKILL.md\`.
|
|
583
|
+
- Cursor uses the open Agent Skills format with YAML frontmatter; ensure the \`name\` and \`description\` fields align with your directory name and skill triggers.
|
|
584
|
+
- Ask clarifying questions when user requests lack details.
|
|
585
|
+
- Keep supporting materials in \`references/\`, \`scripts/\`, and \`assets/\` for progressive disclosure.
|
|
586
|
+
- Avoid referencing frameworks (React, Solid) unless specifically requested by the user.
|
|
587
|
+
- Use determinism and idempotent commands; Cursor may re-run instructions if the output is ambiguous.
|
|
588
|
+
`;
|
|
432
589
|
}
|
|
433
590
|
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
|
|
591
|
+
function getImprovedFactoryAppendix() {
|
|
592
|
+
return `## Factory (Droid CLI) Skill Notes
|
|
593
|
+
|
|
594
|
+
- Skills are discovered from:
|
|
595
|
+
- Workspace: \`<repo>/.factory/skills/<skill-name>/SKILL.md\`
|
|
596
|
+
- Personal: \`~/.factory/skills/<skill-name>/SKILL.md\`
|
|
597
|
+
- Compatibility: \`<repo>/.agent/skills/\` :contentReference[oaicite:1]{index=1}
|
|
598
|
+
- This installer writes to the personal location by default: \`~/.factory/skills/cascadetui/SKILL.md\`.
|
|
599
|
+
- If you want to share the skill with teammates, copy it into your repo under \`.factory/skills/cascadetui/SKILL.md\` and commit it. :contentReference[oaicite:2]{index=2}
|
|
600
|
+
- Invocation control:
|
|
601
|
+
- \`disable-model-invocation: true\` to require manual \`/cascadetui\` invocation
|
|
602
|
+
- \`user-invocable: false\` to hide it from slash commands and keep it model-only :contentReference[oaicite:3]{index=3}
|
|
603
|
+
- Restart \`droid\` after adding/updating skills so it rescans them. :contentReference[oaicite:4]{index=4}
|
|
604
|
+
`;
|
|
605
|
+
}
|
|
437
606
|
|
|
438
|
-
|
|
607
|
+
function getGenericAppendix(agent) {
|
|
608
|
+
return `## ${agent.label} Skill Notes
|
|
439
609
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
610
|
+
- Install this skill globally under the agent's skills directory (for example, \`${agent.installPath}\`).
|
|
611
|
+
- This agent supports the open Agent Skills format; ensure the \`name\` and \`description\` fields match the directory name and capture when to trigger this skill.
|
|
612
|
+
- Use progressive disclosure: place detailed guides, examples, or scripts in \`references/\` and \`scripts/\` directories to minimise the size of \`SKILL.md\`.
|
|
613
|
+
- Ask for clarification when the user's request is ambiguous.
|
|
614
|
+
- Follow the principles outlined in this skill for minimal, typed, and deterministic code.`;
|
|
615
|
+
}
|
|
443
616
|
|
|
617
|
+
function getSkillContent(agent) {
|
|
618
|
+
const frontmatter = getSkillFrontmatter(agent);
|
|
619
|
+
const body = getSkillBody();
|
|
620
|
+
if (agent.flavor === "claude") {
|
|
621
|
+
return frontmatter + "\n\n" + body + "\n\n" + getImprovedClaudeAppendix();
|
|
622
|
+
}
|
|
444
623
|
if (agent.flavor === "cursor") {
|
|
445
|
-
return
|
|
446
|
-
|
|
447
|
-
${getBaseSkillBody()}
|
|
448
|
-
|
|
449
|
-
${getCursorAppendix()}
|
|
450
|
-
`
|
|
624
|
+
return frontmatter + "\n\n" + body + "\n\n" + getImprovedCursorAppendix();
|
|
451
625
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
`
|
|
626
|
+
if (agent.flavor === "factory") {
|
|
627
|
+
return frontmatter + "\n\n" + body + "\n\n" + getImprovedFactoryAppendix();
|
|
628
|
+
}
|
|
629
|
+
return frontmatter + "\n\n" + body + "\n\n" + getGenericAppendix(agent);
|
|
457
630
|
}
|
|
458
631
|
|
|
459
|
-
function installSkill(agent,
|
|
460
|
-
const content = getSkillContent(agent)
|
|
461
|
-
const targetFile = agent.installPath
|
|
462
|
-
const targetDir = resolve(targetFile, "..")
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return { agent: agent.id, path: targetFile, written: false }
|
|
632
|
+
function installSkill(agent, options) {
|
|
633
|
+
const content = getSkillContent(agent);
|
|
634
|
+
const targetFile = agent.installPath;
|
|
635
|
+
const targetDir = resolve(targetFile, "..");
|
|
636
|
+
if (options.dryRun) {
|
|
637
|
+
return { agent: agent.id, path: targetFile, written: false, skipped: false, reason: "dry-run" };
|
|
466
638
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
639
|
+
if (existsSync(targetFile)) {
|
|
640
|
+
try {
|
|
641
|
+
const existing = readFileSync(targetFile, "utf8");
|
|
642
|
+
if (existing === content) {
|
|
643
|
+
return {
|
|
644
|
+
agent: agent.id,
|
|
645
|
+
path: targetFile,
|
|
646
|
+
written: false,
|
|
647
|
+
skipped: true,
|
|
648
|
+
reason: "already up-to-date",
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
if (!options.force) {
|
|
652
|
+
return {
|
|
653
|
+
agent: agent.id,
|
|
654
|
+
path: targetFile,
|
|
655
|
+
written: false,
|
|
656
|
+
skipped: true,
|
|
657
|
+
reason: "exists (use --force to overwrite)",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
} catch {
|
|
661
|
+
if (!options.force) {
|
|
662
|
+
return {
|
|
663
|
+
agent: agent.id,
|
|
664
|
+
path: targetFile,
|
|
665
|
+
written: false,
|
|
666
|
+
skipped: true,
|
|
667
|
+
reason: "exists (unreadable; use --force to overwrite)",
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
mkdirSync(targetDir, { recursive: true });
|
|
673
|
+
writeFileSync(targetFile, content);
|
|
674
|
+
return { agent: agent.id, path: targetFile, written: true, skipped: false, reason: "written" };
|
|
471
675
|
}
|
|
472
676
|
|
|
473
677
|
async function resolveAgentsToInstall(rl, detectedAgents, options) {
|
|
474
678
|
if (options.agents.length > 0) {
|
|
475
|
-
validateAgentIds(options.agents, detectedAgents)
|
|
476
|
-
return options.agents
|
|
679
|
+
validateAgentIds(options.agents, detectedAgents);
|
|
680
|
+
return options.agents;
|
|
477
681
|
}
|
|
478
|
-
|
|
479
|
-
|
|
682
|
+
const detectedIds = detectedAgents
|
|
683
|
+
.filter((agent) => agent.detected)
|
|
684
|
+
.map((agent) => agent.id);
|
|
480
685
|
if (options.allDetected) {
|
|
481
|
-
return detectedIds
|
|
686
|
+
return detectedIds;
|
|
482
687
|
}
|
|
483
|
-
|
|
484
688
|
if (!process.stdin.isTTY) {
|
|
485
|
-
return detectedIds
|
|
689
|
+
return detectedIds;
|
|
486
690
|
}
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
691
|
+
const selectable = toSelectableOptions(detectedAgents);
|
|
692
|
+
const preselected =
|
|
693
|
+
detectedIds.length > 0
|
|
694
|
+
? detectedIds
|
|
695
|
+
: detectedAgents.slice(0, 1).map((agent) => agent.id);
|
|
696
|
+
console.log("");
|
|
492
697
|
if (detectedIds.length > 0) {
|
|
493
|
-
console.log(`Detected agents: ${detectedIds.join(", ")}`)
|
|
494
|
-
console.log(
|
|
698
|
+
console.log(`Detected agents: ${detectedIds.join(", ")}`);
|
|
699
|
+
console.log(
|
|
700
|
+
`Quick install command: npx create-cascade-skill --agents ${detectedIds.join(
|
|
701
|
+
","
|
|
702
|
+
)}`
|
|
703
|
+
);
|
|
495
704
|
} else {
|
|
496
|
-
console.log("No agent was auto-detected. Select manually.")
|
|
705
|
+
console.log("No agent was auto-detected. Select manually.");
|
|
497
706
|
}
|
|
498
|
-
|
|
499
|
-
|
|
707
|
+
const selected = await selectMany(
|
|
708
|
+
rl,
|
|
709
|
+
"Choose agent targets for CascadeTUI skill:",
|
|
710
|
+
selectable,
|
|
711
|
+
preselected
|
|
712
|
+
);
|
|
500
713
|
if (selected.length > 0) {
|
|
501
|
-
return selected
|
|
714
|
+
return selected;
|
|
502
715
|
}
|
|
503
|
-
|
|
504
|
-
|
|
716
|
+
const fallback = (
|
|
717
|
+
await promptLine(
|
|
718
|
+
rl,
|
|
719
|
+
"No agent selected. Enter comma-separated IDs (or leave empty to cancel): "
|
|
720
|
+
)
|
|
721
|
+
).trim();
|
|
505
722
|
if (!fallback) {
|
|
506
|
-
return []
|
|
723
|
+
return [];
|
|
507
724
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
return parsed
|
|
725
|
+
const parsed = unique(parseAgentsArg(fallback));
|
|
726
|
+
validateAgentIds(parsed, detectedAgents);
|
|
727
|
+
return parsed;
|
|
512
728
|
}
|
|
513
729
|
|
|
514
730
|
async function main() {
|
|
515
|
-
const options = parseArgs(process.argv)
|
|
731
|
+
const options = parseArgs(process.argv);
|
|
516
732
|
if (options.help) {
|
|
517
|
-
printHelp()
|
|
518
|
-
return
|
|
733
|
+
printHelp();
|
|
734
|
+
return;
|
|
519
735
|
}
|
|
520
|
-
|
|
521
|
-
const agents = detectAgents(getAgents())
|
|
736
|
+
const agents = detectAgents(getAgents({ home: options.home }));
|
|
522
737
|
if (options.list) {
|
|
523
|
-
printList(agents)
|
|
524
|
-
return
|
|
738
|
+
printList(agents);
|
|
739
|
+
return;
|
|
525
740
|
}
|
|
526
|
-
|
|
527
741
|
const rl = createInterface({
|
|
528
742
|
input: process.stdin,
|
|
529
743
|
output: process.stdout,
|
|
530
|
-
})
|
|
531
|
-
|
|
744
|
+
});
|
|
532
745
|
try {
|
|
533
|
-
const selectedIds = await resolveAgentsToInstall(rl, agents, options)
|
|
746
|
+
const selectedIds = await resolveAgentsToInstall(rl, agents, options);
|
|
534
747
|
if (selectedIds.length === 0) {
|
|
535
|
-
console.log("Nothing to install.")
|
|
536
|
-
return
|
|
748
|
+
console.log("Nothing to install.");
|
|
749
|
+
return;
|
|
537
750
|
}
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
console.log("")
|
|
543
|
-
console.log(`${ANSI_BOLD}CascadeTUI skill installer${ANSI_RESET}`)
|
|
751
|
+
const selectedAgents = agents.filter((agent) => selectedIds.includes(agent.id));
|
|
752
|
+
const results = selectedAgents.map((agent) => installSkill(agent, options));
|
|
753
|
+
console.log("");
|
|
754
|
+
console.log(`${ANSI_BOLD}CascadeTUI skill installer${ANSI_RESET}`);
|
|
544
755
|
for (const result of results) {
|
|
545
|
-
const prefix = result.written
|
|
546
|
-
|
|
756
|
+
const prefix = result.written
|
|
757
|
+
? `${ANSI_GREEN}installed${ANSI_RESET}`
|
|
758
|
+
: result.skipped
|
|
759
|
+
? `${ANSI_YELLOW}skipped${ANSI_RESET}`
|
|
760
|
+
: `${ANSI_YELLOW}planned${ANSI_RESET}`;
|
|
761
|
+
const suffix = result.reason ? ` (${result.reason})` : "";
|
|
762
|
+
console.log(`- ${result.agent}: ${prefix} -> ${result.path}${suffix}`);
|
|
547
763
|
}
|
|
548
|
-
|
|
549
764
|
if (options.dryRun) {
|
|
550
|
-
console.log("")
|
|
551
|
-
console.log("Dry run complete. Re-run without --dry-run to write files.")
|
|
765
|
+
console.log("");
|
|
766
|
+
console.log("Dry run complete. Re-run without --dry-run to write files.");
|
|
552
767
|
}
|
|
553
768
|
} finally {
|
|
554
|
-
rl.close()
|
|
769
|
+
rl.close();
|
|
555
770
|
}
|
|
556
771
|
}
|
|
557
772
|
|
|
558
773
|
main().catch((error) => {
|
|
559
|
-
console.error(error instanceof Error ? error.message : String(error))
|
|
560
|
-
process.exit(1)
|
|
561
|
-
})
|
|
774
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
775
|
+
process.exit(1);
|
|
776
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-cascade-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Install CascadeTUI skills for external coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"publish:pkg": "bun scripts/publish.ts"
|
|
20
|
+
"publish:pkg": "bun scripts/publish.ts",
|
|
21
|
+
"test:skill": "bun scripts/user-test.ts"
|
|
21
22
|
}
|
|
22
23
|
}
|