create-cascade-skill 0.1.7 → 0.1.9

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 +238 -110
  2. package/package.json +3 -2
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { resolve, join } from "node:path";
4
4
  import process from "node:process";
5
5
  import { spawnSync } from "node:child_process";
@@ -13,8 +13,16 @@ const ANSI_CYAN = "\x1b[36m";
13
13
  const ANSI_GREEN = "\x1b[32m";
14
14
  const ANSI_YELLOW = "\x1b[33m";
15
15
 
16
- function getHomeDirectory() {
17
- return process.env.HOME || process.env.USERPROFILE || process.cwd();
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
+ );
18
26
  }
19
27
 
20
28
  function commandExists(command) {
@@ -34,9 +42,13 @@ function unique(items) {
34
42
  return [...new Set(items)];
35
43
  }
36
44
 
37
- function getAgents() {
38
- const home = getHomeDirectory();
39
- const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
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");
40
52
  return [
41
53
  {
42
54
  id: "codex",
@@ -65,6 +77,15 @@ function getAgents() {
65
77
  installPath: join(home, ".cursor", "skills", "cascadetui", "SKILL.md"),
66
78
  flavor: "cursor",
67
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
+ },
68
89
  {
69
90
  id: "windsurf",
70
91
  label: "Windsurf",
@@ -170,7 +191,9 @@ function parseArgs(argv) {
170
191
  allDetected: false,
171
192
  list: false,
172
193
  dryRun: false,
194
+ force: false,
173
195
  help: false,
196
+ home: undefined,
174
197
  };
175
198
  for (let i = 0; i < args.length; i += 1) {
176
199
  const arg = args[i];
@@ -195,6 +218,19 @@ function parseArgs(argv) {
195
218
  options.dryRun = true;
196
219
  continue;
197
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;
233
+ }
198
234
  if (arg === "--help" || arg === "-h") {
199
235
  options.help = true;
200
236
  continue;
@@ -213,6 +249,8 @@ function printHelp() {
213
249
  console.log(" --all-detected Install for all detected agents");
214
250
  console.log(" --list Print supported and detected agents");
215
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");
216
254
  console.log(" -h, --help Show help");
217
255
  console.log("");
218
256
  console.log("Examples:");
@@ -220,6 +258,7 @@ function printHelp() {
220
258
  console.log(" npx create-cascade-skill --all-detected");
221
259
  console.log(" npx create-cascade-skill --agents codex,cursor,cline");
222
260
  console.log(" npx create-cascade-skill --agents codex --dry-run");
261
+ console.log(" npx create-cascade-skill --agents windsurf --home ./sandbox --dry-run");
223
262
  }
224
263
 
225
264
  function detectAgents(agents) {
@@ -271,8 +310,7 @@ function toSelectableOptions(agents) {
271
310
  return agents.map((agent) => ({
272
311
  id: agent.id,
273
312
  label: agent.label,
274
- description: `${agent.description}${agent.detected ? " [detected]" : ""
275
- }`,
313
+ description: `${agent.description}${agent.detected ? " [detected]" : ""}`,
276
314
  }));
277
315
  }
278
316
 
@@ -300,9 +338,7 @@ async function selectMany(rl, label, options, preselectedIds) {
300
338
  const cursor = isCursor
301
339
  ? `${ANSI_BOLD}${ANSI_CYAN}>${ANSI_RESET}`
302
340
  : " ";
303
- const mark = isSelected
304
- ? `${ANSI_GREEN}[x]${ANSI_RESET}`
305
- : "[ ]";
341
+ const mark = isSelected ? `${ANSI_GREEN}[x]${ANSI_RESET}` : "[ ]";
306
342
  const styleStart = isCursor ? `${ANSI_BOLD}${ANSI_CYAN}` : "";
307
343
  const styleEnd = isCursor ? ANSI_RESET : "";
308
344
  stdout.write(
@@ -378,7 +414,7 @@ async function selectMany(rl, label, options, preselectedIds) {
378
414
  function getSkillFrontmatter(agent) {
379
415
  const baseName = "cascadetui";
380
416
  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.";
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.";
382
418
  let compatibility = "Requires Bun and TypeScript.";
383
419
  if (agent.flavor === "claude") {
384
420
  compatibility += " Designed for Claude Code.";
@@ -386,83 +422,141 @@ function getSkillFrontmatter(agent) {
386
422
  compatibility += " Designed for Cursor.";
387
423
  } else if (agent.flavor === "codex") {
388
424
  compatibility += " Designed for OpenAI Codex.";
425
+ } else if (agent.flavor === "factory") {
426
+ compatibility += " Designed for Factory (Droid CLI).";
389
427
  } else {
390
428
  compatibility += ` Designed for ${agent.label}.`;
391
429
  }
392
- const allowedTools = "Bash(bun:*) Bash(npm:*) Bash(node:*)";
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
+
393
441
  return (
394
442
  `---\n` +
395
443
  `name: ${baseName}\n` +
396
444
  `description: ${description}\n` +
397
445
  `compatibility: ${compatibility}\n` +
398
- `allowed-tools: ${allowedTools}\n` +
446
+ `allowed-tools: ${allowedTools}` +
447
+ `${extraFactoryFrontmatter}\n` +
399
448
  `metadata:\n` +
400
449
  ` author: cascadetui\n` +
401
- ` version: "1.1"\n` +
450
+ ` version: "1.2"\n` +
402
451
  `---`
403
452
  );
404
453
  }
405
454
 
406
455
  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).
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)
466
560
  `;
467
561
  }
468
562
 
@@ -494,6 +588,22 @@ function getImprovedCursorAppendix() {
494
588
  `;
495
589
  }
496
590
 
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
+ }
606
+
497
607
  function getGenericAppendix(agent) {
498
608
  return `## ${agent.label} Skill Notes
499
609
 
@@ -508,34 +618,60 @@ function getSkillContent(agent) {
508
618
  const frontmatter = getSkillFrontmatter(agent);
509
619
  const body = getSkillBody();
510
620
  if (agent.flavor === "claude") {
511
- return (
512
- frontmatter +
513
- "\n\n" +
514
- body +
515
- "\n\n" +
516
- getImprovedClaudeAppendix()
517
- );
621
+ return frontmatter + "\n\n" + body + "\n\n" + getImprovedClaudeAppendix();
518
622
  }
519
623
  if (agent.flavor === "cursor") {
520
- return (
521
- frontmatter + "\n\n" + body + "\n\n" + getImprovedCursorAppendix()
522
- );
624
+ return frontmatter + "\n\n" + body + "\n\n" + getImprovedCursorAppendix();
523
625
  }
524
- return (
525
- frontmatter + "\n\n" + body + "\n\n" + getGenericAppendix(agent)
526
- );
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);
527
630
  }
528
631
 
529
- function installSkill(agent, dryRun) {
632
+ function installSkill(agent, options) {
530
633
  const content = getSkillContent(agent);
531
634
  const targetFile = agent.installPath;
532
635
  const targetDir = resolve(targetFile, "..");
533
- if (dryRun) {
534
- return { agent: agent.id, path: targetFile, written: false };
636
+ if (options.dryRun) {
637
+ return { agent: agent.id, path: targetFile, written: false, skipped: false, reason: "dry-run" };
638
+ }
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
+ }
535
671
  }
536
672
  mkdirSync(targetDir, { recursive: true });
537
673
  writeFileSync(targetFile, content);
538
- return { agent: agent.id, path: targetFile, written: true };
674
+ return { agent: agent.id, path: targetFile, written: true, skipped: false, reason: "written" };
539
675
  }
540
676
 
541
677
  async function resolveAgentsToInstall(rl, detectedAgents, options) {
@@ -582,8 +718,7 @@ async function resolveAgentsToInstall(rl, detectedAgents, options) {
582
718
  rl,
583
719
  "No agent selected. Enter comma-separated IDs (or leave empty to cancel): "
584
720
  )
585
- )
586
- .trim();
721
+ ).trim();
587
722
  if (!fallback) {
588
723
  return [];
589
724
  }
@@ -598,7 +733,7 @@ async function main() {
598
733
  printHelp();
599
734
  return;
600
735
  }
601
- const agents = detectAgents(getAgents());
736
+ const agents = detectAgents(getAgents({ home: options.home }));
602
737
  if (options.list) {
603
738
  printList(agents);
604
739
  return;
@@ -613,27 +748,22 @@ async function main() {
613
748
  console.log("Nothing to install.");
614
749
  return;
615
750
  }
616
- const selectedAgents = agents.filter((agent) =>
617
- selectedIds.includes(agent.id)
618
- );
619
- const results = selectedAgents.map((agent) =>
620
- installSkill(agent, options.dryRun)
621
- );
751
+ const selectedAgents = agents.filter((agent) => selectedIds.includes(agent.id));
752
+ const results = selectedAgents.map((agent) => installSkill(agent, options));
622
753
  console.log("");
623
754
  console.log(`${ANSI_BOLD}CascadeTUI skill installer${ANSI_RESET}`);
624
755
  for (const result of results) {
625
756
  const prefix = result.written
626
757
  ? `${ANSI_GREEN}installed${ANSI_RESET}`
627
- : `${ANSI_YELLOW}planned${ANSI_RESET}`;
628
- console.log(
629
- `- ${result.agent}: ${prefix} -> ${result.path}`
630
- );
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}`);
631
763
  }
632
764
  if (options.dryRun) {
633
765
  console.log("");
634
- console.log(
635
- "Dry run complete. Re-run without --dry-run to write files."
636
- );
766
+ console.log("Dry run complete. Re-run without --dry-run to write files.");
637
767
  }
638
768
  } finally {
639
769
  rl.close();
@@ -641,8 +771,6 @@ async function main() {
641
771
  }
642
772
 
643
773
  main().catch((error) => {
644
- console.error(
645
- error instanceof Error ? error.message : String(error)
646
- );
774
+ console.error(error instanceof Error ? error.message : String(error));
647
775
  process.exit(1);
648
776
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cascade-skill",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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
  }