create-cascade-skill 0.1.6 → 0.1.7

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.
Files changed (2) hide show
  1. package/index.js +370 -283
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,44 +1,42 @@
1
1
  #!/usr/bin/env node
2
-
3
- import { existsSync, mkdirSync, writeFileSync } from "node:fs"
4
- import { resolve, join } from "node:path"
5
- import process from "node:process"
6
- import { spawnSync } from "node:child_process"
7
- import { createInterface } from "node:readline/promises"
8
- import { emitKeypressEvents } from "node:readline"
9
-
10
- const ANSI_RESET = "\x1b[0m"
11
- const ANSI_BOLD = "\x1b[1m"
12
- const ANSI_DIM = "\x1b[2m"
13
- const ANSI_CYAN = "\x1b[36m"
14
- const ANSI_GREEN = "\x1b[32m"
15
- const ANSI_YELLOW = "\x1b[33m"
2
+ import { existsSync, mkdirSync, 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";
16
15
 
17
16
  function getHomeDirectory() {
18
- return process.env.HOME || process.env.USERPROFILE || process.cwd()
17
+ return process.env.HOME || process.env.USERPROFILE || process.cwd();
19
18
  }
20
19
 
21
20
  function commandExists(command) {
22
- const lookup = process.platform === "win32" ? "where" : "which"
23
- const result = spawnSync(lookup, [command], { stdio: "ignore" })
24
- return result.status === 0
21
+ const lookup = process.platform === "win32" ? "where" : "which";
22
+ const result = spawnSync(lookup, [command], { stdio: "ignore" });
23
+ return result.status === 0;
25
24
  }
26
25
 
27
26
  function parseAgentsArg(value) {
28
27
  return value
29
28
  .split(",")
30
29
  .map((part) => part.trim().toLowerCase())
31
- .filter((part) => part.length > 0)
30
+ .filter((part) => part.length > 0);
32
31
  }
33
32
 
34
33
  function unique(items) {
35
- return [...new Set(items)]
34
+ return [...new Set(items)];
36
35
  }
37
36
 
38
37
  function getAgents() {
39
- const home = getHomeDirectory()
40
- const appData = process.env.APPDATA || join(home, "AppData", "Roaming")
41
-
38
+ const home = getHomeDirectory();
39
+ const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
42
40
  return [
43
41
  {
44
42
  id: "codex",
@@ -73,7 +71,14 @@ function getAgents() {
73
71
  description: "Install in ~/.codeium/windsurf/skills/cascadetui/SKILL.md",
74
72
  commands: ["windsurf"],
75
73
  detectPaths: [join(home, ".codeium"), join(appData, "Codeium")],
76
- installPath: join(home, ".codeium", "windsurf", "skills", "cascadetui", "SKILL.md"),
74
+ installPath: join(
75
+ home,
76
+ ".codeium",
77
+ "windsurf",
78
+ "skills",
79
+ "cascadetui",
80
+ "SKILL.md"
81
+ ),
77
82
  flavor: "generic",
78
83
  },
79
84
  {
@@ -136,7 +141,14 @@ function getAgents() {
136
141
  description: "Install in ~/.config/goose/skills/cascadetui/SKILL.md",
137
142
  commands: ["goose"],
138
143
  detectPaths: [join(home, ".config", "goose"), join(appData, "goose")],
139
- installPath: join(home, ".config", "goose", "skills", "cascadetui", "SKILL.md"),
144
+ installPath: join(
145
+ home,
146
+ ".config",
147
+ "goose",
148
+ "skills",
149
+ "cascadetui",
150
+ "SKILL.md"
151
+ ),
140
152
  flavor: "generic",
141
153
  },
142
154
  {
@@ -148,414 +160,489 @@ function getAgents() {
148
160
  installPath: join(home, ".ami", "skills", "cascadetui", "SKILL.md"),
149
161
  flavor: "generic",
150
162
  },
151
- ]
163
+ ];
152
164
  }
153
165
 
154
166
  function parseArgs(argv) {
155
- const args = argv.slice(2)
167
+ const args = argv.slice(2);
156
168
  const options = {
157
169
  agents: [],
158
170
  allDetected: false,
159
171
  list: false,
160
172
  dryRun: false,
161
173
  help: false,
162
- }
163
-
174
+ };
164
175
  for (let i = 0; i < args.length; i += 1) {
165
- const arg = args[i]
166
-
176
+ const arg = args[i];
167
177
  if (arg === "--agents" || arg === "-a") {
168
- options.agents.push(...parseAgentsArg(args[i + 1] || ""))
169
- i += 1
170
- continue
178
+ options.agents.push(...parseAgentsArg(args[i + 1] || ""));
179
+ i += 1;
180
+ continue;
171
181
  }
172
182
  if (arg.startsWith("--agents=")) {
173
- options.agents.push(...parseAgentsArg(arg.slice("--agents=".length)))
174
- continue
183
+ options.agents.push(...parseAgentsArg(arg.slice("--agents=".length)));
184
+ continue;
175
185
  }
176
186
  if (arg === "--all-detected") {
177
- options.allDetected = true
178
- continue
187
+ options.allDetected = true;
188
+ continue;
179
189
  }
180
190
  if (arg === "--list") {
181
- options.list = true
182
- continue
191
+ options.list = true;
192
+ continue;
183
193
  }
184
194
  if (arg === "--dry-run") {
185
- options.dryRun = true
186
- continue
195
+ options.dryRun = true;
196
+ continue;
187
197
  }
188
198
  if (arg === "--help" || arg === "-h") {
189
- options.help = true
190
- continue
199
+ options.help = true;
200
+ continue;
191
201
  }
192
- throw new Error(`Unknown argument: ${arg}`)
202
+ throw new Error(`Unknown argument: ${arg}`);
193
203
  }
194
-
195
- options.agents = unique(options.agents)
196
- return options
204
+ options.agents = unique(options.agents);
205
+ return options;
197
206
  }
198
207
 
199
208
  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(" -h, --help Show help")
208
- console.log("")
209
- console.log("Examples:")
210
- console.log(" npx create-cascade-skill")
211
- console.log(" npx create-cascade-skill --all-detected")
212
- console.log(" npx create-cascade-skill --agents codex,cursor,cline")
213
- console.log(" npx create-cascade-skill --agents codex --dry-run")
209
+ console.log("Usage: npx create-cascade-skill [options]");
210
+ console.log("");
211
+ console.log("Options:");
212
+ console.log(" -a, --agents <ids> Comma-separated agent IDs to install");
213
+ console.log(" --all-detected Install for all detected agents");
214
+ console.log(" --list Print supported and detected agents");
215
+ console.log(" --dry-run Preview files without writing");
216
+ console.log(" -h, --help Show help");
217
+ console.log("");
218
+ console.log("Examples:");
219
+ console.log(" npx create-cascade-skill");
220
+ console.log(" npx create-cascade-skill --all-detected");
221
+ console.log(" npx create-cascade-skill --agents codex,cursor,cline");
222
+ console.log(" npx create-cascade-skill --agents codex --dry-run");
214
223
  }
215
224
 
216
225
  function detectAgents(agents) {
217
226
  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
- })
227
+ const commandHit = agent.commands.some((command) => commandExists(command));
228
+ const pathHit = agent.detectPaths.some((path) => existsSync(path));
229
+ return { ...agent, detected: commandHit || pathHit };
230
+ });
222
231
  }
223
232
 
224
233
  function printList(agents) {
225
- console.log(`${ANSI_BOLD}Supported agents${ANSI_RESET}`)
234
+ console.log(`${ANSI_BOLD}Supported agents${ANSI_RESET}`);
226
235
  for (const agent of agents) {
227
- const marker = agent.detected ? `${ANSI_GREEN}detected${ANSI_RESET}` : `${ANSI_YELLOW}not detected${ANSI_RESET}`
228
- console.log(`- ${agent.id.padEnd(12)} ${marker} ${agent.label}`)
236
+ const marker = agent.detected
237
+ ? `${ANSI_GREEN}detected${ANSI_RESET}`
238
+ : `${ANSI_YELLOW}not detected${ANSI_RESET}`;
239
+ console.log(`- ${agent.id.padEnd(12)} ${marker} ${agent.label}`);
229
240
  }
230
-
231
- const detected = agents.filter((agent) => agent.detected).map((agent) => agent.id)
232
- console.log("")
241
+ const detected = agents
242
+ .filter((agent) => agent.detected)
243
+ .map((agent) => agent.id);
244
+ console.log("");
233
245
  if (detected.length > 0) {
234
- console.log(`Detected IDs: ${detected.join(", ")}`)
235
- console.log(`Install all detected: npx create-cascade-skill --agents ${detected.join(",")}`)
246
+ console.log(`Detected IDs: ${detected.join(", ")}`);
247
+ console.log(
248
+ `Install all detected: npx create-cascade-skill --agents ${detected.join(
249
+ ","
250
+ )}`
251
+ );
236
252
  } else {
237
- console.log("Detected IDs: none")
253
+ console.log("Detected IDs: none");
238
254
  }
239
255
  }
240
256
 
241
257
  function validateAgentIds(selected, agents) {
242
- const allowed = new Set(agents.map((agent) => agent.id))
258
+ const allowed = new Set(agents.map((agent) => agent.id));
243
259
  for (const id of selected) {
244
260
  if (!allowed.has(id)) {
245
- throw new Error(`Unknown agent id: ${id}`)
261
+ throw new Error(`Unknown agent id: ${id}`);
246
262
  }
247
263
  }
248
264
  }
249
265
 
250
266
  function promptLine(rl, label) {
251
- return rl.question(label)
267
+ return rl.question(label);
252
268
  }
253
269
 
254
270
  function toSelectableOptions(agents) {
255
271
  return agents.map((agent) => ({
256
272
  id: agent.id,
257
273
  label: agent.label,
258
- description: `${agent.description}${agent.detected ? " [detected]" : ""}`,
259
- }))
274
+ description: `${agent.description}${agent.detected ? " [detected]" : ""
275
+ }`,
276
+ }));
260
277
  }
261
278
 
262
279
  async function selectMany(rl, label, options, preselectedIds) {
263
280
  if (!process.stdin.isTTY) {
264
- return preselectedIds
281
+ return preselectedIds;
265
282
  }
266
-
267
- const stdin = process.stdin
268
- const stdout = process.stdout
269
- let selectedIndex = 0
270
- let selected = new Set(preselectedIds)
271
- const totalLines = options.length + 3
272
- let renderedOnce = false
273
-
283
+ const stdin = process.stdin;
284
+ const stdout = process.stdout;
285
+ let selectedIndex = 0;
286
+ let selected = new Set(preselectedIds);
287
+ const totalLines = options.length + 3;
288
+ let renderedOnce = false;
274
289
  const render = () => {
275
290
  if (renderedOnce) {
276
- stdout.write(`\x1b[${totalLines}F`)
291
+ stdout.write(`\x1b[${totalLines}F`);
277
292
  } else {
278
- stdout.write("\n")
293
+ stdout.write("\n");
279
294
  }
280
-
281
- stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`)
295
+ stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`);
282
296
  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 ? `${ANSI_BOLD}${ANSI_CYAN}>${ANSI_RESET}` : " "
287
- const mark = isSelected ? `${ANSI_GREEN}[x]${ANSI_RESET}` : "[ ]"
288
- const styleStart = isCursor ? `${ANSI_BOLD}${ANSI_CYAN}` : ""
289
- const styleEnd = isCursor ? ANSI_RESET : ""
290
- stdout.write(`${cursor} ${mark} ${styleStart}${option.label}${styleEnd} ${ANSI_DIM}(${option.id}) - ${option.description}${ANSI_RESET}\n`)
297
+ const option = options[i];
298
+ const isCursor = i === selectedIndex;
299
+ const isSelected = selected.has(option.id);
300
+ const cursor = isCursor
301
+ ? `${ANSI_BOLD}${ANSI_CYAN}>${ANSI_RESET}`
302
+ : " ";
303
+ const mark = isSelected
304
+ ? `${ANSI_GREEN}[x]${ANSI_RESET}`
305
+ : "[ ]";
306
+ const styleStart = isCursor ? `${ANSI_BOLD}${ANSI_CYAN}` : "";
307
+ const styleEnd = isCursor ? ANSI_RESET : "";
308
+ stdout.write(
309
+ `${cursor} ${mark} ${styleStart}${option.label}${styleEnd} ${ANSI_DIM}(${option.id}) - ${option.description}${ANSI_RESET}\n`
310
+ );
291
311
  }
292
- stdout.write(`${ANSI_DIM}Use Up/Down, Space to toggle, A to toggle all, Enter to confirm${ANSI_RESET}\n`)
293
- renderedOnce = true
294
- }
295
-
312
+ stdout.write(
313
+ `${ANSI_DIM}Use Up/Down, Space to toggle, A to toggle all, Enter to confirm${ANSI_RESET}\n`
314
+ );
315
+ renderedOnce = true;
316
+ };
296
317
  return new Promise((resolvePromise, rejectPromise) => {
297
318
  const cleanup = () => {
298
- stdin.off("keypress", onKeyPress)
319
+ stdin.off("keypress", onKeyPress);
299
320
  if (stdin.isTTY) {
300
- stdin.setRawMode(false)
321
+ stdin.setRawMode(false);
301
322
  }
302
- stdout.write("\n")
303
- }
304
-
323
+ stdout.write("\n");
324
+ };
305
325
  const onKeyPress = (_, key) => {
306
326
  if (!key) {
307
- return
327
+ return;
308
328
  }
309
-
310
329
  if (key.ctrl && key.name === "c") {
311
- cleanup()
312
- rejectPromise(new Error("Operation cancelled"))
313
- return
330
+ cleanup();
331
+ rejectPromise(new Error("Operation cancelled"));
332
+ return;
314
333
  }
315
-
316
334
  if (key.name === "up") {
317
- selectedIndex = selectedIndex === 0 ? options.length - 1 : selectedIndex - 1
318
- render()
319
- return
335
+ selectedIndex =
336
+ selectedIndex === 0 ? options.length - 1 : selectedIndex - 1;
337
+ render();
338
+ return;
320
339
  }
321
-
322
340
  if (key.name === "down") {
323
- selectedIndex = selectedIndex === options.length - 1 ? 0 : selectedIndex + 1
324
- render()
325
- return
341
+ selectedIndex =
342
+ selectedIndex === options.length - 1 ? 0 : selectedIndex + 1;
343
+ render();
344
+ return;
326
345
  }
327
-
328
346
  if (key.name === "space") {
329
- const id = options[selectedIndex].id
347
+ const id = options[selectedIndex].id;
330
348
  if (selected.has(id)) {
331
- selected.delete(id)
349
+ selected.delete(id);
332
350
  } else {
333
- selected.add(id)
351
+ selected.add(id);
334
352
  }
335
- render()
336
- return
353
+ render();
354
+ return;
337
355
  }
338
-
339
356
  if (key.name === "a") {
340
357
  if (selected.size === options.length) {
341
- selected = new Set()
358
+ selected = new Set();
342
359
  } else {
343
- selected = new Set(options.map((option) => option.id))
360
+ selected = new Set(options.map((option) => option.id));
344
361
  }
345
- render()
346
- return
362
+ render();
363
+ return;
347
364
  }
348
-
349
365
  if (key.name === "return") {
350
- cleanup()
351
- resolvePromise([...selected])
366
+ cleanup();
367
+ resolvePromise([...selected]);
352
368
  }
353
- }
354
-
355
- emitKeypressEvents(stdin)
356
- stdin.setRawMode(true)
357
- stdin.resume()
358
- stdin.on("keypress", onKeyPress)
359
- render()
360
- })
369
+ };
370
+ emitKeypressEvents(stdin);
371
+ stdin.setRawMode(true);
372
+ stdin.resume();
373
+ stdin.on("keypress", onKeyPress);
374
+ render();
375
+ });
361
376
  }
362
377
 
363
- function getBaseSkillFrontmatter() {
364
- return `---
365
- name: cascadetui
366
- description: Build and maintain terminal UIs with CascadeTUI. Use when creating components, layouts, renderables, keyboard interactions, and debugging terminal UI behavior in Cascade-based projects.
367
- compatibility: Requires Bun and TypeScript. Designed for agents that support Agent Skills and SKILL.md frontmatter.
368
- metadata:
369
- author: cascadetui
370
- version: "1.0"
371
- ---`
378
+ function getSkillFrontmatter(agent) {
379
+ const baseName = "cascadetui";
380
+ const description =
381
+ "Build terminal user interfaces with CascadeTUI. Use this skill when building, debugging, or refactoring Cascade-based TUIs. Triggers: Cascade, TUI, terminal UI, keyboard navigation, component layout.";
382
+ let compatibility = "Requires Bun and TypeScript.";
383
+ if (agent.flavor === "claude") {
384
+ compatibility += " Designed for Claude Code.";
385
+ } else if (agent.flavor === "cursor") {
386
+ compatibility += " Designed for Cursor.";
387
+ } else if (agent.flavor === "codex") {
388
+ compatibility += " Designed for OpenAI Codex.";
389
+ } else {
390
+ compatibility += ` Designed for ${agent.label}.`;
391
+ }
392
+ const allowedTools = "Bash(bun:*) Bash(npm:*) Bash(node:*)";
393
+ return (
394
+ `---\n` +
395
+ `name: ${baseName}\n` +
396
+ `description: ${description}\n` +
397
+ `compatibility: ${compatibility}\n` +
398
+ `allowed-tools: ${allowedTools}\n` +
399
+ `metadata:\n` +
400
+ ` author: cascadetui\n` +
401
+ ` version: "1.1"\n` +
402
+ `---`
403
+ );
372
404
  }
373
405
 
374
- function getBaseSkillBody() {
375
- return `# CascadeTUI Skill
376
-
377
- ## When To Use
378
- - Use this skill when the user is building or debugging a terminal UI with CascadeTUI.
379
- - Use this skill for component layout, rendering behavior, keyboard handling, and interaction logic.
380
- - Use this skill for scaffold and setup guidance in Cascade projects.
381
-
382
- ## Instructions
383
- 1. Prefer Bun commands and APIs.
384
- 2. Prefer \`@cascadetui/core\` for low-level renderables unless the user explicitly asks for React or Solid.
385
- 3. Use \`@cascadetui/react\` or \`@cascadetui/solid\` only when requested.
386
- 4. Keep code minimal, typed, deterministic, and production-ready.
387
- 5. Reproduce rendering bugs with a minimal reproducible test case before applying fixes.
388
- 6. Validate keyboard, focus, and resize behavior for interactive components.
389
-
390
- ## Scaffolding
391
- - \`bun create cascade my-app\`
392
- - \`cd my-app\`
393
- - \`bun install\`
394
- - \`bun run dev\`
395
-
396
- ## Notes
397
- - Favor clear state transitions and predictable rendering.
398
- - Avoid adding dependencies that duplicate Bun runtime capabilities.
399
- `
406
+ function getSkillBody() {
407
+ return `# Building Terminal UIs with CascadeTUI
408
+
409
+ ## When to Activate
410
+
411
+ - Use this skill when asked to build, debug, or refactor a text-based user interface using the CascadeTUI library.
412
+ - Trigger this skill if the user mentions "Cascade", "TUI", "terminal UI", "keyboard navigation", or "component layout".
413
+ - Use when scaffolding new projects or customizing existing ones.
414
+
415
+ ## Key Principles
416
+
417
+ - **Bun first**: Use the Bun runtime and its native package manager for running scripts and managing dependencies. Avoid using Node-specific commands or features unless absolutely required.
418
+ - **Core Library**: The \`@cascadetui/core\` package provides the primitives for rendering, layout, and input handling. Use it for basic applications. Only reach for the React (\`@cascadetui/react\`) or Solid (\`@cascadetui/solid\`) wrappers when explicitly requested.
419
+ - **Typed and deterministic**: Write TypeScript code with explicit types. Avoid dynamic evaluation and unpredictable side effects.
420
+ - **Minimal reproducible examples**: When diagnosing issues, start by isolating the problem in a tiny script or component that reproduces the bug. Only after reproducing should you propose fixes.
421
+ - **Interactive validation**: Test keyboard navigation, focus management, and resize events across multiple terminal sizes. Ensure accessible shortcuts and fallback navigation.
422
+
423
+ ## Typical Workflow
424
+
425
+ 1. **Scaffold a project**
426
+ Use Bun's project generator to create a new CascadeTUI app:
427
+ \`\`\`bash
428
+ bun create cascade my-app
429
+ cd my-app
430
+ bun install
431
+ bun run dev
432
+ \`\`\`
433
+
434
+ 2. **Develop components**
435
+ - Keep components pure and functions side-effect free where possible.
436
+ - Compose layouts using containers (horizontal, vertical, grid) and ensure consistent spacing.
437
+ - Use dedicated input handlers rather than global listeners.
438
+
439
+ 3. **Debug rendering**
440
+ - If a component fails to render or update, add logging and ensure the state drives the UI deterministically.
441
+ - Recreate the failing state in isolation.
442
+ - Check for improper asynchronous updates.
443
+
444
+ 4. **Keyboard & interaction**
445
+ - Provide intuitive key bindings with documentation.
446
+ - Implement focus rings or highlight states.
447
+ - Handle edge cases like simultaneous key presses, terminal resize, and loss of focus.
448
+
449
+ 5. **Performance considerations**
450
+ - Avoid expensive re-renders inside tight loops.
451
+ - Batch state updates where possible.
452
+ - Profile for memory leaks or event listener accumulation.
453
+
454
+ ## Best Practices
455
+
456
+ - **State management**: Use a predictable state container or simple \`useState\`-like patterns. Avoid hidden state.
457
+ - **Testing**: Write unit tests for components and integration tests for complex flows. Use snapshot tests carefully.
458
+ - **Documentation**: Document all public-facing components with usage examples and list supported props.
459
+ - **Clean-up**: Remove unused dependencies and scripts. Keep the codebase tidy for readability.
460
+
461
+ ## Additional Resources
462
+
463
+ - Official CascadeTUI documentation (if available).
464
+ - Bun runtime documentation: https://bun.sh/docs for details on Bun-specific APIs.
465
+ - Example projects in the CascadeTUI GitHub repository (if available).
466
+ `;
400
467
  }
401
468
 
402
- function getClaudeAppendix() {
469
+ function getImprovedClaudeAppendix() {
403
470
  return `## Documentation Index
404
- Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
405
- Use this file to discover all available pages before exploring further.
471
+
472
+ 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
473
 
407
474
  ## 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
475
 
414
- function getCursorFrontmatter() {
415
- return `---
416
- name: cascadetui
417
- description: Build and maintain terminal UIs with CascadeTUI. Use when creating components, layouts, renderables, keyboard interactions, and debugging terminal UI behavior in Cascade-based projects.
418
- compatibility: Cursor Agent Skills format with global installation under ~/.cursor/skills.
419
- metadata:
420
- author: cascadetui
421
- version: "1.0"
422
- ---`
476
+ - Place this skill in a global location: \`~/.claude/skills/cascadetui/SKILL.md\`. Do not nest additional directories deeper than one level.
477
+ - Claude automatically loads skills whose \`name\` and \`description\` fields match the user's request; keep them descriptive and concise.
478
+ - Follow the naming conventions from the Agent Skills specification: lowercase alphanumeric and hyphens only, no reserved words (anthropic, claude).
479
+ - Use progressive disclosure: keep \`SKILL.md\` under 500 lines and move lengthy instructions to \`references/\` files.
480
+ - Ask clarifying questions when requirements are ambiguous or when multiple interpretations exist.
481
+ - Keep scripts deterministic and self-contained; avoid side effects that require external network access unless absolutely necessary.
482
+ `;
423
483
  }
424
484
 
425
- function getCursorAppendix() {
485
+ function getImprovedCursorAppendix() {
426
486
  return `## Cursor Skill Notes
427
- - Use global installation first: \`~/.cursor/skills/cascadetui/SKILL.md\`.
428
- - This skill follows the Agent Skills standard with YAML frontmatter.
429
- - Ask clarification questions when requirements are ambiguous.
430
- - Keep supporting material in \`references/\`, \`scripts/\`, and \`assets/\` only if needed.
431
- `
487
+
488
+ - Install this skill globally under \`~/.cursor/skills/cascadetui/SKILL.md\`.
489
+ - Cursor uses the open Agent Skills format with YAML frontmatter; ensure the \`name\` and \`description\` fields align with your directory name and skill triggers.
490
+ - Ask clarifying questions when user requests lack details.
491
+ - Keep supporting materials in \`references/\`, \`scripts/\`, and \`assets/\` for progressive disclosure.
492
+ - Avoid referencing frameworks (React, Solid) unless specifically requested by the user.
493
+ - Use determinism and idempotent commands; Cursor may re-run instructions if the output is ambiguous.
494
+ `;
432
495
  }
433
496
 
434
- function getSkillContent(agent) {
435
- if (agent.flavor === "claude") {
436
- return `${getBaseSkillFrontmatter()}
497
+ function getGenericAppendix(agent) {
498
+ return `## ${agent.label} Skill Notes
437
499
 
438
- ${getBaseSkillBody()}
500
+ - Install this skill globally under the agent's skills directory (for example, \`${agent.installPath}\`).
501
+ - 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.
502
+ - Use progressive disclosure: place detailed guides, examples, or scripts in \`references/\` and \`scripts/\` directories to minimise the size of \`SKILL.md\`.
503
+ - Ask for clarification when the user's request is ambiguous.
504
+ - Follow the principles outlined in this skill for minimal, typed, and deterministic code.`;
505
+ }
439
506
 
440
- ${getClaudeAppendix()}
441
- `
507
+ function getSkillContent(agent) {
508
+ const frontmatter = getSkillFrontmatter(agent);
509
+ const body = getSkillBody();
510
+ if (agent.flavor === "claude") {
511
+ return (
512
+ frontmatter +
513
+ "\n\n" +
514
+ body +
515
+ "\n\n" +
516
+ getImprovedClaudeAppendix()
517
+ );
442
518
  }
443
-
444
519
  if (agent.flavor === "cursor") {
445
- return `${getCursorFrontmatter()}
446
-
447
- ${getBaseSkillBody()}
448
-
449
- ${getCursorAppendix()}
450
- `
520
+ return (
521
+ frontmatter + "\n\n" + body + "\n\n" + getImprovedCursorAppendix()
522
+ );
451
523
  }
452
-
453
- return `${getBaseSkillFrontmatter()}
454
-
455
- ${getBaseSkillBody()}
456
- `
524
+ return (
525
+ frontmatter + "\n\n" + body + "\n\n" + getGenericAppendix(agent)
526
+ );
457
527
  }
458
528
 
459
529
  function installSkill(agent, dryRun) {
460
- const content = getSkillContent(agent)
461
- const targetFile = agent.installPath
462
- const targetDir = resolve(targetFile, "..")
463
-
530
+ const content = getSkillContent(agent);
531
+ const targetFile = agent.installPath;
532
+ const targetDir = resolve(targetFile, "..");
464
533
  if (dryRun) {
465
- return { agent: agent.id, path: targetFile, written: false }
534
+ return { agent: agent.id, path: targetFile, written: false };
466
535
  }
467
-
468
- mkdirSync(targetDir, { recursive: true })
469
- writeFileSync(targetFile, content)
470
- return { agent: agent.id, path: targetFile, written: true }
536
+ mkdirSync(targetDir, { recursive: true });
537
+ writeFileSync(targetFile, content);
538
+ return { agent: agent.id, path: targetFile, written: true };
471
539
  }
472
540
 
473
541
  async function resolveAgentsToInstall(rl, detectedAgents, options) {
474
542
  if (options.agents.length > 0) {
475
- validateAgentIds(options.agents, detectedAgents)
476
- return options.agents
543
+ validateAgentIds(options.agents, detectedAgents);
544
+ return options.agents;
477
545
  }
478
-
479
- const detectedIds = detectedAgents.filter((agent) => agent.detected).map((agent) => agent.id)
546
+ const detectedIds = detectedAgents
547
+ .filter((agent) => agent.detected)
548
+ .map((agent) => agent.id);
480
549
  if (options.allDetected) {
481
- return detectedIds
550
+ return detectedIds;
482
551
  }
483
-
484
552
  if (!process.stdin.isTTY) {
485
- return detectedIds
553
+ return detectedIds;
486
554
  }
487
-
488
- const selectable = toSelectableOptions(detectedAgents)
489
- const preselected = detectedIds.length > 0 ? detectedIds : detectedAgents.slice(0, 1).map((agent) => agent.id)
490
-
491
- console.log("")
555
+ const selectable = toSelectableOptions(detectedAgents);
556
+ const preselected =
557
+ detectedIds.length > 0
558
+ ? detectedIds
559
+ : detectedAgents.slice(0, 1).map((agent) => agent.id);
560
+ console.log("");
492
561
  if (detectedIds.length > 0) {
493
- console.log(`Detected agents: ${detectedIds.join(", ")}`)
494
- console.log(`Quick install command: npx create-cascade-skill --agents ${detectedIds.join(",")}`)
562
+ console.log(`Detected agents: ${detectedIds.join(", ")}`);
563
+ console.log(
564
+ `Quick install command: npx create-cascade-skill --agents ${detectedIds.join(
565
+ ","
566
+ )}`
567
+ );
495
568
  } else {
496
- console.log("No agent was auto-detected. Select manually.")
569
+ console.log("No agent was auto-detected. Select manually.");
497
570
  }
498
-
499
- const selected = await selectMany(rl, "Choose agent targets for CascadeTUI skill:", selectable, preselected)
571
+ const selected = await selectMany(
572
+ rl,
573
+ "Choose agent targets for CascadeTUI skill:",
574
+ selectable,
575
+ preselected
576
+ );
500
577
  if (selected.length > 0) {
501
- return selected
578
+ return selected;
502
579
  }
503
-
504
- const fallback = (await promptLine(rl, "No agent selected. Enter comma-separated IDs (or leave empty to cancel): ")).trim()
580
+ const fallback = (
581
+ await promptLine(
582
+ rl,
583
+ "No agent selected. Enter comma-separated IDs (or leave empty to cancel): "
584
+ )
585
+ )
586
+ .trim();
505
587
  if (!fallback) {
506
- return []
588
+ return [];
507
589
  }
508
-
509
- const parsed = unique(parseAgentsArg(fallback))
510
- validateAgentIds(parsed, detectedAgents)
511
- return parsed
590
+ const parsed = unique(parseAgentsArg(fallback));
591
+ validateAgentIds(parsed, detectedAgents);
592
+ return parsed;
512
593
  }
513
594
 
514
595
  async function main() {
515
- const options = parseArgs(process.argv)
596
+ const options = parseArgs(process.argv);
516
597
  if (options.help) {
517
- printHelp()
518
- return
598
+ printHelp();
599
+ return;
519
600
  }
520
-
521
- const agents = detectAgents(getAgents())
601
+ const agents = detectAgents(getAgents());
522
602
  if (options.list) {
523
- printList(agents)
524
- return
603
+ printList(agents);
604
+ return;
525
605
  }
526
-
527
606
  const rl = createInterface({
528
607
  input: process.stdin,
529
608
  output: process.stdout,
530
- })
531
-
609
+ });
532
610
  try {
533
- const selectedIds = await resolveAgentsToInstall(rl, agents, options)
611
+ const selectedIds = await resolveAgentsToInstall(rl, agents, options);
534
612
  if (selectedIds.length === 0) {
535
- console.log("Nothing to install.")
536
- return
613
+ console.log("Nothing to install.");
614
+ return;
537
615
  }
538
-
539
- const selectedAgents = agents.filter((agent) => selectedIds.includes(agent.id))
540
- const results = selectedAgents.map((agent) => installSkill(agent, options.dryRun))
541
-
542
- console.log("")
543
- console.log(`${ANSI_BOLD}CascadeTUI skill installer${ANSI_RESET}`)
616
+ const selectedAgents = agents.filter((agent) =>
617
+ selectedIds.includes(agent.id)
618
+ );
619
+ const results = selectedAgents.map((agent) =>
620
+ installSkill(agent, options.dryRun)
621
+ );
622
+ console.log("");
623
+ console.log(`${ANSI_BOLD}CascadeTUI skill installer${ANSI_RESET}`);
544
624
  for (const result of results) {
545
- const prefix = result.written ? `${ANSI_GREEN}installed${ANSI_RESET}` : `${ANSI_YELLOW}planned${ANSI_RESET}`
546
- console.log(`- ${result.agent}: ${prefix} -> ${result.path}`)
625
+ const prefix = result.written
626
+ ? `${ANSI_GREEN}installed${ANSI_RESET}`
627
+ : `${ANSI_YELLOW}planned${ANSI_RESET}`;
628
+ console.log(
629
+ `- ${result.agent}: ${prefix} -> ${result.path}`
630
+ );
547
631
  }
548
-
549
632
  if (options.dryRun) {
550
- console.log("")
551
- console.log("Dry run complete. Re-run without --dry-run to write files.")
633
+ console.log("");
634
+ console.log(
635
+ "Dry run complete. Re-run without --dry-run to write files."
636
+ );
552
637
  }
553
638
  } finally {
554
- rl.close()
639
+ rl.close();
555
640
  }
556
641
  }
557
642
 
558
643
  main().catch((error) => {
559
- console.error(error instanceof Error ? error.message : String(error))
560
- process.exit(1)
561
- })
644
+ console.error(
645
+ error instanceof Error ? error.message : String(error)
646
+ );
647
+ process.exit(1);
648
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cascade-skill",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Install CascadeTUI skills for external coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",