create-cascade-skill 0.1.6
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 +38 -0
- package/index.js +561 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# create-cascade-skill
|
|
2
|
+
|
|
3
|
+
Install CascadeTUI skills for external coding agents with an interactive CLI.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-cascade-skill
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Detects installed agents automatically.
|
|
14
|
+
- Supports multi-select installation in one run.
|
|
15
|
+
- Provides a direct command for all detected agents.
|
|
16
|
+
- Works in interactive and non-interactive modes.
|
|
17
|
+
- Installs global `SKILL.md` files with Agent Skills frontmatter.
|
|
18
|
+
|
|
19
|
+
## Supported agents
|
|
20
|
+
|
|
21
|
+
- `codex`
|
|
22
|
+
- `claude-code`
|
|
23
|
+
- `cursor`
|
|
24
|
+
- `windsurf`
|
|
25
|
+
- `cline`
|
|
26
|
+
- `roo-code`
|
|
27
|
+
- `continue`
|
|
28
|
+
- `aider`
|
|
29
|
+
- `gemini-cli`
|
|
30
|
+
- `openhands`
|
|
31
|
+
- `goose`
|
|
32
|
+
- `ami`
|
|
33
|
+
|
|
34
|
+
## CLI options
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx create-cascade-skill --help
|
|
38
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
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"
|
|
16
|
+
|
|
17
|
+
function getHomeDirectory() {
|
|
18
|
+
return process.env.HOME || process.env.USERPROFILE || process.cwd()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function commandExists(command) {
|
|
22
|
+
const lookup = process.platform === "win32" ? "where" : "which"
|
|
23
|
+
const result = spawnSync(lookup, [command], { stdio: "ignore" })
|
|
24
|
+
return result.status === 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseAgentsArg(value) {
|
|
28
|
+
return value
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((part) => part.trim().toLowerCase())
|
|
31
|
+
.filter((part) => part.length > 0)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function unique(items) {
|
|
35
|
+
return [...new Set(items)]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getAgents() {
|
|
39
|
+
const home = getHomeDirectory()
|
|
40
|
+
const appData = process.env.APPDATA || join(home, "AppData", "Roaming")
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
id: "codex",
|
|
45
|
+
label: "OpenAI Codex",
|
|
46
|
+
description: "Install in ~/.codex/skills/cascadetui/SKILL.md",
|
|
47
|
+
commands: ["codex"],
|
|
48
|
+
detectPaths: [join(home, ".codex")],
|
|
49
|
+
installPath: join(home, ".codex", "skills", "cascadetui", "SKILL.md"),
|
|
50
|
+
flavor: "codex",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "claude-code",
|
|
54
|
+
label: "Claude Code",
|
|
55
|
+
description: "Install in ~/.claude/skills/cascadetui/SKILL.md",
|
|
56
|
+
commands: ["claude"],
|
|
57
|
+
detectPaths: [join(home, ".claude")],
|
|
58
|
+
installPath: join(home, ".claude", "skills", "cascadetui", "SKILL.md"),
|
|
59
|
+
flavor: "claude",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "cursor",
|
|
63
|
+
label: "Cursor",
|
|
64
|
+
description: "Install in ~/.cursor/skills/cascadetui/SKILL.md",
|
|
65
|
+
commands: ["cursor"],
|
|
66
|
+
detectPaths: [join(home, ".cursor"), join(appData, "Cursor")],
|
|
67
|
+
installPath: join(home, ".cursor", "skills", "cascadetui", "SKILL.md"),
|
|
68
|
+
flavor: "cursor",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "windsurf",
|
|
72
|
+
label: "Windsurf",
|
|
73
|
+
description: "Install in ~/.codeium/windsurf/skills/cascadetui/SKILL.md",
|
|
74
|
+
commands: ["windsurf"],
|
|
75
|
+
detectPaths: [join(home, ".codeium"), join(appData, "Codeium")],
|
|
76
|
+
installPath: join(home, ".codeium", "windsurf", "skills", "cascadetui", "SKILL.md"),
|
|
77
|
+
flavor: "generic",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "cline",
|
|
81
|
+
label: "Cline",
|
|
82
|
+
description: "Install in ~/.cline/skills/cascadetui/SKILL.md",
|
|
83
|
+
commands: ["cline"],
|
|
84
|
+
detectPaths: [join(home, ".cline")],
|
|
85
|
+
installPath: join(home, ".cline", "skills", "cascadetui", "SKILL.md"),
|
|
86
|
+
flavor: "generic",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "roo-code",
|
|
90
|
+
label: "Roo Code",
|
|
91
|
+
description: "Install in ~/.roo/skills/cascadetui/SKILL.md",
|
|
92
|
+
commands: ["roo", "roo-code"],
|
|
93
|
+
detectPaths: [join(home, ".roo"), join(home, ".roocode")],
|
|
94
|
+
installPath: join(home, ".roo", "skills", "cascadetui", "SKILL.md"),
|
|
95
|
+
flavor: "generic",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "continue",
|
|
99
|
+
label: "Continue",
|
|
100
|
+
description: "Install in ~/.continue/skills/cascadetui/SKILL.md",
|
|
101
|
+
commands: ["continue"],
|
|
102
|
+
detectPaths: [join(home, ".continue")],
|
|
103
|
+
installPath: join(home, ".continue", "skills", "cascadetui", "SKILL.md"),
|
|
104
|
+
flavor: "generic",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "aider",
|
|
108
|
+
label: "Aider",
|
|
109
|
+
description: "Install in ~/.aider/skills/cascadetui/SKILL.md",
|
|
110
|
+
commands: ["aider"],
|
|
111
|
+
detectPaths: [join(home, ".aider.conf.yml"), join(home, ".aider")],
|
|
112
|
+
installPath: join(home, ".aider", "skills", "cascadetui", "SKILL.md"),
|
|
113
|
+
flavor: "generic",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "gemini-cli",
|
|
117
|
+
label: "Gemini CLI",
|
|
118
|
+
description: "Install in ~/.gemini/skills/cascadetui/SKILL.md",
|
|
119
|
+
commands: ["gemini"],
|
|
120
|
+
detectPaths: [join(home, ".gemini")],
|
|
121
|
+
installPath: join(home, ".gemini", "skills", "cascadetui", "SKILL.md"),
|
|
122
|
+
flavor: "generic",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "openhands",
|
|
126
|
+
label: "OpenHands",
|
|
127
|
+
description: "Install in ~/.openhands/skills/cascadetui/SKILL.md",
|
|
128
|
+
commands: ["openhands"],
|
|
129
|
+
detectPaths: [join(home, ".openhands")],
|
|
130
|
+
installPath: join(home, ".openhands", "skills", "cascadetui", "SKILL.md"),
|
|
131
|
+
flavor: "generic",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "goose",
|
|
135
|
+
label: "Goose",
|
|
136
|
+
description: "Install in ~/.config/goose/skills/cascadetui/SKILL.md",
|
|
137
|
+
commands: ["goose"],
|
|
138
|
+
detectPaths: [join(home, ".config", "goose"), join(appData, "goose")],
|
|
139
|
+
installPath: join(home, ".config", "goose", "skills", "cascadetui", "SKILL.md"),
|
|
140
|
+
flavor: "generic",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "ami",
|
|
144
|
+
label: "Ami",
|
|
145
|
+
description: "Install in ~/.ami/skills/cascadetui/SKILL.md",
|
|
146
|
+
commands: ["ami"],
|
|
147
|
+
detectPaths: [join(home, ".ami")],
|
|
148
|
+
installPath: join(home, ".ami", "skills", "cascadetui", "SKILL.md"),
|
|
149
|
+
flavor: "generic",
|
|
150
|
+
},
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseArgs(argv) {
|
|
155
|
+
const args = argv.slice(2)
|
|
156
|
+
const options = {
|
|
157
|
+
agents: [],
|
|
158
|
+
allDetected: false,
|
|
159
|
+
list: false,
|
|
160
|
+
dryRun: false,
|
|
161
|
+
help: false,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
165
|
+
const arg = args[i]
|
|
166
|
+
|
|
167
|
+
if (arg === "--agents" || arg === "-a") {
|
|
168
|
+
options.agents.push(...parseAgentsArg(args[i + 1] || ""))
|
|
169
|
+
i += 1
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
if (arg.startsWith("--agents=")) {
|
|
173
|
+
options.agents.push(...parseAgentsArg(arg.slice("--agents=".length)))
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
if (arg === "--all-detected") {
|
|
177
|
+
options.allDetected = true
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
if (arg === "--list") {
|
|
181
|
+
options.list = true
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
if (arg === "--dry-run") {
|
|
185
|
+
options.dryRun = true
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
if (arg === "--help" || arg === "-h") {
|
|
189
|
+
options.help = true
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
options.agents = unique(options.agents)
|
|
196
|
+
return options
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
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")
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function detectAgents(agents) {
|
|
217
|
+
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
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function printList(agents) {
|
|
225
|
+
console.log(`${ANSI_BOLD}Supported agents${ANSI_RESET}`)
|
|
226
|
+
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}`)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const detected = agents.filter((agent) => agent.detected).map((agent) => agent.id)
|
|
232
|
+
console.log("")
|
|
233
|
+
if (detected.length > 0) {
|
|
234
|
+
console.log(`Detected IDs: ${detected.join(", ")}`)
|
|
235
|
+
console.log(`Install all detected: npx create-cascade-skill --agents ${detected.join(",")}`)
|
|
236
|
+
} else {
|
|
237
|
+
console.log("Detected IDs: none")
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function validateAgentIds(selected, agents) {
|
|
242
|
+
const allowed = new Set(agents.map((agent) => agent.id))
|
|
243
|
+
for (const id of selected) {
|
|
244
|
+
if (!allowed.has(id)) {
|
|
245
|
+
throw new Error(`Unknown agent id: ${id}`)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function promptLine(rl, label) {
|
|
251
|
+
return rl.question(label)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function toSelectableOptions(agents) {
|
|
255
|
+
return agents.map((agent) => ({
|
|
256
|
+
id: agent.id,
|
|
257
|
+
label: agent.label,
|
|
258
|
+
description: `${agent.description}${agent.detected ? " [detected]" : ""}`,
|
|
259
|
+
}))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function selectMany(rl, label, options, preselectedIds) {
|
|
263
|
+
if (!process.stdin.isTTY) {
|
|
264
|
+
return preselectedIds
|
|
265
|
+
}
|
|
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
|
+
|
|
274
|
+
const render = () => {
|
|
275
|
+
if (renderedOnce) {
|
|
276
|
+
stdout.write(`\x1b[${totalLines}F`)
|
|
277
|
+
} else {
|
|
278
|
+
stdout.write("\n")
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`)
|
|
282
|
+
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`)
|
|
291
|
+
}
|
|
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
|
+
|
|
296
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
297
|
+
const cleanup = () => {
|
|
298
|
+
stdin.off("keypress", onKeyPress)
|
|
299
|
+
if (stdin.isTTY) {
|
|
300
|
+
stdin.setRawMode(false)
|
|
301
|
+
}
|
|
302
|
+
stdout.write("\n")
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const onKeyPress = (_, key) => {
|
|
306
|
+
if (!key) {
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (key.ctrl && key.name === "c") {
|
|
311
|
+
cleanup()
|
|
312
|
+
rejectPromise(new Error("Operation cancelled"))
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (key.name === "up") {
|
|
317
|
+
selectedIndex = selectedIndex === 0 ? options.length - 1 : selectedIndex - 1
|
|
318
|
+
render()
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (key.name === "down") {
|
|
323
|
+
selectedIndex = selectedIndex === options.length - 1 ? 0 : selectedIndex + 1
|
|
324
|
+
render()
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (key.name === "space") {
|
|
329
|
+
const id = options[selectedIndex].id
|
|
330
|
+
if (selected.has(id)) {
|
|
331
|
+
selected.delete(id)
|
|
332
|
+
} else {
|
|
333
|
+
selected.add(id)
|
|
334
|
+
}
|
|
335
|
+
render()
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (key.name === "a") {
|
|
340
|
+
if (selected.size === options.length) {
|
|
341
|
+
selected = new Set()
|
|
342
|
+
} else {
|
|
343
|
+
selected = new Set(options.map((option) => option.id))
|
|
344
|
+
}
|
|
345
|
+
render()
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (key.name === "return") {
|
|
350
|
+
cleanup()
|
|
351
|
+
resolvePromise([...selected])
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
emitKeypressEvents(stdin)
|
|
356
|
+
stdin.setRawMode(true)
|
|
357
|
+
stdin.resume()
|
|
358
|
+
stdin.on("keypress", onKeyPress)
|
|
359
|
+
render()
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
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
|
+
---`
|
|
372
|
+
}
|
|
373
|
+
|
|
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
|
+
`
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function getClaudeAppendix() {
|
|
403
|
+
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.
|
|
406
|
+
|
|
407
|
+
## 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
|
+
|
|
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
|
+
---`
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function getCursorAppendix() {
|
|
426
|
+
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
|
+
`
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function getSkillContent(agent) {
|
|
435
|
+
if (agent.flavor === "claude") {
|
|
436
|
+
return `${getBaseSkillFrontmatter()}
|
|
437
|
+
|
|
438
|
+
${getBaseSkillBody()}
|
|
439
|
+
|
|
440
|
+
${getClaudeAppendix()}
|
|
441
|
+
`
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (agent.flavor === "cursor") {
|
|
445
|
+
return `${getCursorFrontmatter()}
|
|
446
|
+
|
|
447
|
+
${getBaseSkillBody()}
|
|
448
|
+
|
|
449
|
+
${getCursorAppendix()}
|
|
450
|
+
`
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return `${getBaseSkillFrontmatter()}
|
|
454
|
+
|
|
455
|
+
${getBaseSkillBody()}
|
|
456
|
+
`
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function installSkill(agent, dryRun) {
|
|
460
|
+
const content = getSkillContent(agent)
|
|
461
|
+
const targetFile = agent.installPath
|
|
462
|
+
const targetDir = resolve(targetFile, "..")
|
|
463
|
+
|
|
464
|
+
if (dryRun) {
|
|
465
|
+
return { agent: agent.id, path: targetFile, written: false }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
mkdirSync(targetDir, { recursive: true })
|
|
469
|
+
writeFileSync(targetFile, content)
|
|
470
|
+
return { agent: agent.id, path: targetFile, written: true }
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function resolveAgentsToInstall(rl, detectedAgents, options) {
|
|
474
|
+
if (options.agents.length > 0) {
|
|
475
|
+
validateAgentIds(options.agents, detectedAgents)
|
|
476
|
+
return options.agents
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const detectedIds = detectedAgents.filter((agent) => agent.detected).map((agent) => agent.id)
|
|
480
|
+
if (options.allDetected) {
|
|
481
|
+
return detectedIds
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (!process.stdin.isTTY) {
|
|
485
|
+
return detectedIds
|
|
486
|
+
}
|
|
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("")
|
|
492
|
+
if (detectedIds.length > 0) {
|
|
493
|
+
console.log(`Detected agents: ${detectedIds.join(", ")}`)
|
|
494
|
+
console.log(`Quick install command: npx create-cascade-skill --agents ${detectedIds.join(",")}`)
|
|
495
|
+
} else {
|
|
496
|
+
console.log("No agent was auto-detected. Select manually.")
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const selected = await selectMany(rl, "Choose agent targets for CascadeTUI skill:", selectable, preselected)
|
|
500
|
+
if (selected.length > 0) {
|
|
501
|
+
return selected
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const fallback = (await promptLine(rl, "No agent selected. Enter comma-separated IDs (or leave empty to cancel): ")).trim()
|
|
505
|
+
if (!fallback) {
|
|
506
|
+
return []
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const parsed = unique(parseAgentsArg(fallback))
|
|
510
|
+
validateAgentIds(parsed, detectedAgents)
|
|
511
|
+
return parsed
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function main() {
|
|
515
|
+
const options = parseArgs(process.argv)
|
|
516
|
+
if (options.help) {
|
|
517
|
+
printHelp()
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const agents = detectAgents(getAgents())
|
|
522
|
+
if (options.list) {
|
|
523
|
+
printList(agents)
|
|
524
|
+
return
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const rl = createInterface({
|
|
528
|
+
input: process.stdin,
|
|
529
|
+
output: process.stdout,
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
const selectedIds = await resolveAgentsToInstall(rl, agents, options)
|
|
534
|
+
if (selectedIds.length === 0) {
|
|
535
|
+
console.log("Nothing to install.")
|
|
536
|
+
return
|
|
537
|
+
}
|
|
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}`)
|
|
544
|
+
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}`)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (options.dryRun) {
|
|
550
|
+
console.log("")
|
|
551
|
+
console.log("Dry run complete. Re-run without --dry-run to write files.")
|
|
552
|
+
}
|
|
553
|
+
} finally {
|
|
554
|
+
rl.close()
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
main().catch((error) => {
|
|
559
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
560
|
+
process.exit(1)
|
|
561
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-cascade-skill",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "Install CascadeTUI skills for external coding agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/kirosnn/cascade.git",
|
|
10
|
+
"directory": "packages/skill"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"create-cascade-skill": "index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"index.js",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"publish:pkg": "bun scripts/publish.ts"
|
|
21
|
+
}
|
|
22
|
+
}
|