@take-out/cli 0.1.11 → 0.1.13
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 +5 -5
- package/dist/cjs/cli.cjs +2 -1
- package/dist/cjs/cli.js +2 -0
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/commands/docs.cjs +1 -167
- package/dist/cjs/commands/docs.js +1 -138
- package/dist/cjs/commands/docs.js.map +1 -1
- package/dist/cjs/commands/skills.cjs +355 -0
- package/dist/cjs/commands/skills.js +277 -0
- package/dist/cjs/commands/skills.js.map +6 -0
- package/dist/cjs/utils/parallel-runner.cjs +24 -3
- package/dist/cjs/utils/parallel-runner.js +27 -3
- package/dist/cjs/utils/parallel-runner.js.map +1 -1
- package/dist/cjs/utils/script-listing.cjs +1 -1
- package/dist/cjs/utils/script-listing.js +1 -1
- package/dist/cjs/utils/script-listing.js.map +1 -1
- package/dist/esm/cli.js +2 -0
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/cli.mjs +2 -1
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/commands/docs.js +3 -150
- package/dist/esm/commands/docs.js.map +1 -1
- package/dist/esm/commands/docs.mjs +3 -169
- package/dist/esm/commands/docs.mjs.map +1 -1
- package/dist/esm/commands/skills.js +273 -0
- package/dist/esm/commands/skills.js.map +6 -0
- package/dist/esm/commands/skills.mjs +320 -0
- package/dist/esm/commands/skills.mjs.map +1 -0
- package/dist/esm/utils/parallel-runner.js +27 -3
- package/dist/esm/utils/parallel-runner.js.map +1 -1
- package/dist/esm/utils/parallel-runner.mjs +24 -3
- package/dist/esm/utils/parallel-runner.mjs.map +1 -1
- package/dist/esm/utils/script-listing.js +1 -1
- package/dist/esm/utils/script-listing.js.map +1 -1
- package/dist/esm/utils/script-listing.mjs +1 -1
- package/dist/esm/utils/script-listing.mjs.map +1 -1
- package/package.json +4 -4
- package/src/cli.ts +2 -0
- package/src/commands/docs.ts +2 -288
- package/src/commands/skills.ts +528 -0
- package/src/utils/parallel-runner.ts +42 -1
- package/src/utils/script-listing.ts +1 -0
- package/types/commands/docs.d.ts.map +1 -1
- package/types/commands/skills.d.ts +5 -0
- package/types/commands/skills.d.ts.map +1 -0
- package/types/utils/parallel-runner.d.ts.map +1 -1
- package/types/utils/script-listing.d.ts.map +1 -1
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skills command group - manage claude code skills
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
symlinkSync,
|
|
13
|
+
unlinkSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
} from 'node:fs'
|
|
16
|
+
import { createRequire } from 'node:module'
|
|
17
|
+
import { dirname, join, relative } from 'node:path'
|
|
18
|
+
import { fileURLToPath } from 'node:url'
|
|
19
|
+
|
|
20
|
+
import { defineCommand } from 'citty'
|
|
21
|
+
import pc from 'picocolors'
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
type ScriptMetadata,
|
|
25
|
+
discoverScripts,
|
|
26
|
+
getAllScriptMetadata,
|
|
27
|
+
getLocalScriptsDir,
|
|
28
|
+
} from '../utils/script-utils'
|
|
29
|
+
|
|
30
|
+
// --- shared helpers ---
|
|
31
|
+
|
|
32
|
+
const BUILTIN_COMMANDS: Array<{ name: string; description: string }> = [
|
|
33
|
+
{ name: 'onboard', description: 'setup wizard for new projects' },
|
|
34
|
+
{ name: 'docs', description: 'view documentation' },
|
|
35
|
+
{ name: 'env:setup', description: 'setup environment variables' },
|
|
36
|
+
{ name: 'run', description: 'run scripts in parallel' },
|
|
37
|
+
{ name: 'script', description: 'manage and run scripts' },
|
|
38
|
+
{ name: 'sync', description: 'sync fork with upstream takeout' },
|
|
39
|
+
{ name: 'changed', description: 'show changes since last sync' },
|
|
40
|
+
{ name: 'skills', description: 'manage claude code skills' },
|
|
41
|
+
{ name: 'completion', description: 'shell completion setup' },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
function findScriptsPackageRoot(): string | null {
|
|
45
|
+
try {
|
|
46
|
+
const resolved = import.meta.resolve('@take-out/scripts/package.json')
|
|
47
|
+
const packageJsonPath = fileURLToPath(new URL(resolved))
|
|
48
|
+
return join(packageJsonPath, '..', 'src')
|
|
49
|
+
} catch {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- summary skill generation ---
|
|
55
|
+
|
|
56
|
+
function buildSummaryDescription(
|
|
57
|
+
localScripts: Map<string, string>,
|
|
58
|
+
builtInScripts: Map<string, string>
|
|
59
|
+
): string {
|
|
60
|
+
const categories = new Set<string>()
|
|
61
|
+
const keywords = new Set<string>()
|
|
62
|
+
|
|
63
|
+
for (const [name] of [...localScripts, ...builtInScripts]) {
|
|
64
|
+
keywords.add(name)
|
|
65
|
+
if (name.includes('/')) {
|
|
66
|
+
categories.add(name.split('/')[0]!)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const cmd of BUILTIN_COMMANDS) {
|
|
71
|
+
keywords.add(cmd.name)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const categoryList = [...categories].sort().join(', ')
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
`CLI scripts and commands reference for the tko (takeout) CLI. ` +
|
|
78
|
+
`Use when the user asks to run scripts, manage the project, or needs to know what commands are available. ` +
|
|
79
|
+
`tko, takeout, CLI, scripts, commands, bun tko, project tasks, automation, ` +
|
|
80
|
+
`${categoryList}, ${[...keywords].sort().join(', ')}`
|
|
81
|
+
).slice(0, 2048)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildSummaryContent(
|
|
85
|
+
localScripts: Map<string, string>,
|
|
86
|
+
builtInScripts: Map<string, string>,
|
|
87
|
+
metadata: Map<string, ScriptMetadata>
|
|
88
|
+
): string {
|
|
89
|
+
const description = buildSummaryDescription(localScripts, builtInScripts)
|
|
90
|
+
|
|
91
|
+
const lines: string[] = []
|
|
92
|
+
lines.push('---')
|
|
93
|
+
lines.push('name: tko-scripts')
|
|
94
|
+
lines.push(`description: ${description}`)
|
|
95
|
+
lines.push('---')
|
|
96
|
+
lines.push('')
|
|
97
|
+
lines.push('# tko CLI - scripts & commands')
|
|
98
|
+
lines.push('')
|
|
99
|
+
lines.push('run with `bun tko <command>` or `bun tko <script-name>`.')
|
|
100
|
+
lines.push('')
|
|
101
|
+
|
|
102
|
+
// built-in commands
|
|
103
|
+
lines.push('## built-in commands')
|
|
104
|
+
lines.push('')
|
|
105
|
+
for (const cmd of BUILTIN_COMMANDS) {
|
|
106
|
+
lines.push(` ${cmd.name} - ${cmd.description}`)
|
|
107
|
+
}
|
|
108
|
+
lines.push('')
|
|
109
|
+
|
|
110
|
+
// helper to group and format scripts
|
|
111
|
+
const formatSection = (title: string, scripts: Map<string, string>) => {
|
|
112
|
+
if (scripts.size === 0) return
|
|
113
|
+
|
|
114
|
+
const categories = new Map<string, Array<string>>()
|
|
115
|
+
const rootScripts: string[] = []
|
|
116
|
+
|
|
117
|
+
for (const [name] of scripts) {
|
|
118
|
+
if (name.includes('/')) {
|
|
119
|
+
const category = name.split('/')[0]!
|
|
120
|
+
if (!categories.has(category)) {
|
|
121
|
+
categories.set(category, [])
|
|
122
|
+
}
|
|
123
|
+
categories.get(category)!.push(name)
|
|
124
|
+
} else {
|
|
125
|
+
rootScripts.push(name)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
lines.push(`## ${title}`)
|
|
130
|
+
lines.push('')
|
|
131
|
+
|
|
132
|
+
for (const name of rootScripts) {
|
|
133
|
+
const meta = metadata.get(name)
|
|
134
|
+
const desc = meta?.description ? ` - ${meta.description}` : ''
|
|
135
|
+
const args = meta?.args?.length ? ` [${meta.args.join(', ')}]` : ''
|
|
136
|
+
lines.push(` ${name}${desc}${args}`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const [category, categoryScripts] of categories) {
|
|
140
|
+
lines.push('')
|
|
141
|
+
lines.push(` ${category}/`)
|
|
142
|
+
for (const name of categoryScripts) {
|
|
143
|
+
const shortName = name.substring(category.length + 1)
|
|
144
|
+
const meta = metadata.get(name)
|
|
145
|
+
const desc = meta?.description ? ` - ${meta.description}` : ''
|
|
146
|
+
const args = meta?.args?.length ? ` [${meta.args.join(', ')}]` : ''
|
|
147
|
+
lines.push(` ${shortName}${desc}${args}`)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
lines.push('')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
formatSection('local scripts', localScripts)
|
|
155
|
+
formatSection('built-in scripts', builtInScripts)
|
|
156
|
+
|
|
157
|
+
// usage
|
|
158
|
+
lines.push('## usage')
|
|
159
|
+
lines.push('')
|
|
160
|
+
lines.push('```bash')
|
|
161
|
+
lines.push('bun tko <command> # run a built-in command')
|
|
162
|
+
lines.push('bun tko <script-name> # execute direct script')
|
|
163
|
+
lines.push(
|
|
164
|
+
'bun tko <group> <script> # execute nested script (e.g. bun tko aws health)'
|
|
165
|
+
)
|
|
166
|
+
lines.push('bun tko run s1 s2 s3 # run multiple scripts in parallel')
|
|
167
|
+
lines.push('bun tko script new <path> # create a new script')
|
|
168
|
+
lines.push('```')
|
|
169
|
+
lines.push('')
|
|
170
|
+
|
|
171
|
+
return lines.join('\n')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function generateSummary(cwd: string): Promise<boolean> {
|
|
175
|
+
const skillsDir = join(cwd, '.claude', 'skills')
|
|
176
|
+
const skillName = 'tko-scripts'
|
|
177
|
+
const skillDir = join(skillsDir, skillName)
|
|
178
|
+
const skillFile = join(skillDir, 'SKILL.md')
|
|
179
|
+
|
|
180
|
+
// discover all scripts
|
|
181
|
+
const localScripts = discoverScripts(getLocalScriptsDir())
|
|
182
|
+
const builtInDir = findScriptsPackageRoot()
|
|
183
|
+
const builtInScripts = builtInDir ? discoverScripts(builtInDir) : new Map()
|
|
184
|
+
|
|
185
|
+
const allScripts = new Map([...localScripts, ...builtInScripts])
|
|
186
|
+
const metadata = await getAllScriptMetadata(allScripts)
|
|
187
|
+
|
|
188
|
+
const totalScripts = localScripts.size + builtInScripts.size
|
|
189
|
+
console.info(
|
|
190
|
+
pc.dim(
|
|
191
|
+
`found ${totalScripts} scripts (${localScripts.size} local, ${builtInScripts.size} built-in) + ${BUILTIN_COMMANDS.length} commands`
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
const content = buildSummaryContent(localScripts, builtInScripts, metadata)
|
|
196
|
+
|
|
197
|
+
// check if unchanged
|
|
198
|
+
try {
|
|
199
|
+
const existing = readFileSync(skillFile, 'utf-8')
|
|
200
|
+
if (existing === content) {
|
|
201
|
+
console.info(` ${pc.dim('tko-scripts')} ${pc.dim('unchanged')}`)
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
// doesn't exist yet
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!existsSync(skillDir)) {
|
|
209
|
+
mkdirSync(skillDir, { recursive: true })
|
|
210
|
+
}
|
|
211
|
+
writeFileSync(skillFile, content)
|
|
212
|
+
console.info(` ${pc.green('✓')} tko-scripts`)
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// --- doc skills generation ---
|
|
217
|
+
|
|
218
|
+
const require = createRequire(import.meta.url)
|
|
219
|
+
let DOCS_DIR: string
|
|
220
|
+
try {
|
|
221
|
+
DOCS_DIR = dirname(require.resolve('@take-out/docs/package.json'))
|
|
222
|
+
} catch {
|
|
223
|
+
DOCS_DIR = ''
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const SKILL_PREFIX = 'takeout-'
|
|
227
|
+
|
|
228
|
+
function hasSkillFrontmatter(content: string): boolean {
|
|
229
|
+
if (!content.startsWith('---')) return false
|
|
230
|
+
const endIndex = content.indexOf('---', 3)
|
|
231
|
+
if (endIndex === -1) return false
|
|
232
|
+
const frontmatter = content.slice(3, endIndex)
|
|
233
|
+
return frontmatter.includes('name:') && frontmatter.includes('description:')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function isDevOnly(content: string): boolean {
|
|
237
|
+
if (!content.startsWith('---')) return false
|
|
238
|
+
const endIndex = content.indexOf('---', 3)
|
|
239
|
+
if (endIndex === -1) return false
|
|
240
|
+
const frontmatter = content.slice(3, endIndex)
|
|
241
|
+
return /\bdev:\s*true\b/.test(frontmatter)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function extractDocMeta(content: string): { title: string; description: string } {
|
|
245
|
+
const lines = content.split('\n')
|
|
246
|
+
let title = ''
|
|
247
|
+
let description = ''
|
|
248
|
+
|
|
249
|
+
let startLine = 0
|
|
250
|
+
if (lines[0]?.trim() === '---') {
|
|
251
|
+
for (let i = 1; i < lines.length; i++) {
|
|
252
|
+
if (lines[i]?.trim() === '---') {
|
|
253
|
+
startLine = i + 1
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
260
|
+
const trimmed = lines[i]?.trim() || ''
|
|
261
|
+
if (!title && trimmed.startsWith('# ')) {
|
|
262
|
+
title = trimmed.slice(2).trim()
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
if (title && trimmed && !trimmed.startsWith('#')) {
|
|
266
|
+
description = trimmed
|
|
267
|
+
break
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { title, description }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function toSkillName(name: string): string {
|
|
275
|
+
return name
|
|
276
|
+
.toLowerCase()
|
|
277
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
278
|
+
.replace(/-+/g, '-')
|
|
279
|
+
.replace(/^-|-$/g, '')
|
|
280
|
+
.slice(0, 64)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function collectAllDocs(
|
|
284
|
+
cwd: string
|
|
285
|
+
): Array<{ name: string; path: string; source: 'package' | 'local' }> {
|
|
286
|
+
const docs: Array<{ name: string; path: string; source: 'package' | 'local' }> = []
|
|
287
|
+
const seen = new Set<string>()
|
|
288
|
+
|
|
289
|
+
const localDocsDir = join(cwd, 'docs')
|
|
290
|
+
if (existsSync(localDocsDir)) {
|
|
291
|
+
const files = readdirSync(localDocsDir).filter((f) => f.endsWith('.md'))
|
|
292
|
+
for (const file of files) {
|
|
293
|
+
const name = file.replace(/\.md$/, '')
|
|
294
|
+
docs.push({ name, path: join(localDocsDir, file), source: 'local' })
|
|
295
|
+
seen.add(name)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (DOCS_DIR && existsSync(DOCS_DIR)) {
|
|
300
|
+
const files = readdirSync(DOCS_DIR).filter((f) => f.endsWith('.md'))
|
|
301
|
+
for (const file of files) {
|
|
302
|
+
const name = file.replace(/\.md$/, '')
|
|
303
|
+
if (!seen.has(name)) {
|
|
304
|
+
docs.push({ name, path: join(DOCS_DIR, file), source: 'package' })
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return docs
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function generateDocSkills(
|
|
313
|
+
cwd: string,
|
|
314
|
+
clean: boolean
|
|
315
|
+
): Promise<{ symlinked: number; generated: number; unchanged: number }> {
|
|
316
|
+
const skillsDir = join(cwd, '.claude', 'skills')
|
|
317
|
+
const docs = collectAllDocs(cwd)
|
|
318
|
+
|
|
319
|
+
if (docs.length === 0) {
|
|
320
|
+
console.info(pc.yellow('no documentation files found'))
|
|
321
|
+
return { symlinked: 0, generated: 0, unchanged: 0 }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.info(pc.dim(`found ${docs.length} documentation files`))
|
|
325
|
+
|
|
326
|
+
if (clean && existsSync(skillsDir)) {
|
|
327
|
+
const existing = readdirSync(skillsDir)
|
|
328
|
+
for (const dir of existing) {
|
|
329
|
+
if (dir.startsWith(SKILL_PREFIX)) {
|
|
330
|
+
rmSync(join(skillsDir, dir), { recursive: true })
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!existsSync(skillsDir)) {
|
|
336
|
+
mkdirSync(skillsDir, { recursive: true })
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let symlinked = 0
|
|
340
|
+
let generated = 0
|
|
341
|
+
let unchanged = 0
|
|
342
|
+
const isDev = !!process.env.IS_TAMAGUI_DEV
|
|
343
|
+
|
|
344
|
+
for (const doc of docs) {
|
|
345
|
+
const content = readFileSync(doc.path, 'utf-8')
|
|
346
|
+
if (isDevOnly(content) && !isDev) continue
|
|
347
|
+
|
|
348
|
+
const hasFrontmatter = hasSkillFrontmatter(content)
|
|
349
|
+
|
|
350
|
+
if (hasFrontmatter) {
|
|
351
|
+
const nameMatch = content.match(/^---\s*\nname:\s*([^\n]+)/m)
|
|
352
|
+
if (!nameMatch) continue
|
|
353
|
+
|
|
354
|
+
const skillName = nameMatch[1]!.trim()
|
|
355
|
+
const skillDir = join(skillsDir, skillName)
|
|
356
|
+
const skillFile = join(skillDir, 'SKILL.md')
|
|
357
|
+
|
|
358
|
+
if (!existsSync(skillDir)) {
|
|
359
|
+
mkdirSync(skillDir, { recursive: true })
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const relativePath = relative(skillDir, doc.path)
|
|
363
|
+
|
|
364
|
+
let shouldCreate = true
|
|
365
|
+
try {
|
|
366
|
+
const stat = lstatSync(skillFile)
|
|
367
|
+
if (stat.isSymbolicLink() && existsSync(skillFile)) {
|
|
368
|
+
const existingContent = readFileSync(skillFile, 'utf-8')
|
|
369
|
+
if (existingContent === content) {
|
|
370
|
+
unchanged++
|
|
371
|
+
shouldCreate = false
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (shouldCreate) {
|
|
375
|
+
unlinkSync(skillFile)
|
|
376
|
+
}
|
|
377
|
+
} catch {
|
|
378
|
+
// nothing exists
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!shouldCreate) continue
|
|
382
|
+
|
|
383
|
+
symlinkSync(relativePath, skillFile)
|
|
384
|
+
symlinked++
|
|
385
|
+
|
|
386
|
+
const sourceLabel = doc.source === 'local' ? pc.blue('local') : pc.dim('package')
|
|
387
|
+
console.info(
|
|
388
|
+
` ${pc.green('⟷')} ${skillName} ${sourceLabel} ${pc.dim('(symlink)')}`
|
|
389
|
+
)
|
|
390
|
+
} else {
|
|
391
|
+
const baseName = toSkillName(doc.name)
|
|
392
|
+
const skillName = `${SKILL_PREFIX}${baseName}`
|
|
393
|
+
const skillDir = join(skillsDir, skillName)
|
|
394
|
+
const skillFile = join(skillDir, 'SKILL.md')
|
|
395
|
+
|
|
396
|
+
if (!existsSync(skillDir)) {
|
|
397
|
+
mkdirSync(skillDir, { recursive: true })
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const { title, description } = extractDocMeta(content)
|
|
401
|
+
const skillDescription = description
|
|
402
|
+
? `${title}. ${description}`.slice(0, 1024)
|
|
403
|
+
: title.slice(0, 1024)
|
|
404
|
+
|
|
405
|
+
const skillContent = `---
|
|
406
|
+
name: ${skillName}
|
|
407
|
+
description: ${skillDescription}
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
${content}
|
|
411
|
+
`
|
|
412
|
+
|
|
413
|
+
let shouldWrite = true
|
|
414
|
+
try {
|
|
415
|
+
const stat = lstatSync(skillFile)
|
|
416
|
+
if (stat.isSymbolicLink()) {
|
|
417
|
+
unlinkSync(skillFile)
|
|
418
|
+
} else {
|
|
419
|
+
const existing = readFileSync(skillFile, 'utf-8')
|
|
420
|
+
if (existing === skillContent) {
|
|
421
|
+
unchanged++
|
|
422
|
+
shouldWrite = false
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} catch {
|
|
426
|
+
// nothing exists
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!shouldWrite) continue
|
|
430
|
+
|
|
431
|
+
writeFileSync(skillFile, skillContent)
|
|
432
|
+
generated++
|
|
433
|
+
|
|
434
|
+
const sourceLabel = doc.source === 'local' ? pc.blue('local') : pc.dim('package')
|
|
435
|
+
console.info(
|
|
436
|
+
` ${pc.green('✓')} ${skillName} ${sourceLabel} ${pc.dim('(generated)')}`
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return { symlinked, generated, unchanged }
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// --- commands ---
|
|
445
|
+
|
|
446
|
+
const scriptsCommand = defineCommand({
|
|
447
|
+
meta: {
|
|
448
|
+
name: 'scripts',
|
|
449
|
+
description: 'Generate a skill summarizing all tko scripts and commands',
|
|
450
|
+
},
|
|
451
|
+
async run() {
|
|
452
|
+
const cwd = process.cwd()
|
|
453
|
+
|
|
454
|
+
console.info()
|
|
455
|
+
console.info(pc.bold(pc.cyan('Generate scripts skill')))
|
|
456
|
+
console.info()
|
|
457
|
+
|
|
458
|
+
await generateSummary(cwd)
|
|
459
|
+
|
|
460
|
+
console.info()
|
|
461
|
+
},
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
const generateCommand = defineCommand({
|
|
465
|
+
meta: {
|
|
466
|
+
name: 'generate',
|
|
467
|
+
description: 'Generate all Claude Code skills (doc skills + summary)',
|
|
468
|
+
},
|
|
469
|
+
args: {
|
|
470
|
+
clean: {
|
|
471
|
+
type: 'boolean',
|
|
472
|
+
description: 'Remove existing takeout-* skills before generating',
|
|
473
|
+
default: false,
|
|
474
|
+
},
|
|
475
|
+
'skip-internal-docs': {
|
|
476
|
+
type: 'boolean',
|
|
477
|
+
description: 'Skip generating skills from internal documentation files',
|
|
478
|
+
default: false,
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
async run({ args }) {
|
|
482
|
+
const cwd = process.cwd()
|
|
483
|
+
const skillsDir = join(cwd, '.claude', 'skills')
|
|
484
|
+
|
|
485
|
+
console.info()
|
|
486
|
+
console.info(pc.bold(pc.cyan('Generate all skills')))
|
|
487
|
+
console.info()
|
|
488
|
+
|
|
489
|
+
let symlinked = 0
|
|
490
|
+
let generated = 0
|
|
491
|
+
let unchanged = 0
|
|
492
|
+
|
|
493
|
+
// 1. doc skills (unless skipped)
|
|
494
|
+
if (!args['skip-internal-docs']) {
|
|
495
|
+
const docStats = await generateDocSkills(cwd, args.clean)
|
|
496
|
+
symlinked = docStats.symlinked
|
|
497
|
+
generated = docStats.generated
|
|
498
|
+
unchanged = docStats.unchanged
|
|
499
|
+
console.info()
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// 2. scripts summary skill
|
|
503
|
+
await generateSummary(cwd)
|
|
504
|
+
|
|
505
|
+
// summary
|
|
506
|
+
console.info()
|
|
507
|
+
console.info(pc.bold('summary:'))
|
|
508
|
+
if (symlinked > 0) console.info(` ${pc.green(`${symlinked} symlinked`)}`)
|
|
509
|
+
if (generated > 0)
|
|
510
|
+
console.info(
|
|
511
|
+
` ${pc.yellow(`${generated} generated`)} ${pc.dim('(add frontmatter to enable symlink)')}`
|
|
512
|
+
)
|
|
513
|
+
if (unchanged > 0) console.info(` ${pc.dim(`${unchanged} unchanged`)}`)
|
|
514
|
+
console.info(pc.dim(` skills in ${skillsDir}`))
|
|
515
|
+
console.info()
|
|
516
|
+
},
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
export const skillsCommand = defineCommand({
|
|
520
|
+
meta: {
|
|
521
|
+
name: 'skills',
|
|
522
|
+
description: 'Manage Claude Code skills',
|
|
523
|
+
},
|
|
524
|
+
subCommands: {
|
|
525
|
+
generate: generateCommand,
|
|
526
|
+
scripts: scriptsCommand,
|
|
527
|
+
},
|
|
528
|
+
})
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Utility for running scripts in parallel with color-coded output
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { spawn } from 'node:child_process'
|
|
5
|
+
import { spawn, type ChildProcess } from 'node:child_process'
|
|
6
6
|
import { cpus } from 'node:os'
|
|
7
7
|
|
|
8
8
|
const colors = [
|
|
@@ -40,6 +40,41 @@ interface ScriptToRun {
|
|
|
40
40
|
args?: string[]
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// track all spawned processes for cleanup
|
|
44
|
+
const spawnedProcesses: ChildProcess[] = []
|
|
45
|
+
let cleaning = false
|
|
46
|
+
|
|
47
|
+
function killAllProcessGroups(signal: NodeJS.Signals = 'SIGTERM') {
|
|
48
|
+
for (const proc of spawnedProcesses) {
|
|
49
|
+
if (proc.pid) {
|
|
50
|
+
// negative pid kills the entire process group (detached: true makes them group leaders)
|
|
51
|
+
try {
|
|
52
|
+
process.kill(-proc.pid, signal)
|
|
53
|
+
} catch (_) {
|
|
54
|
+
try {
|
|
55
|
+
process.kill(proc.pid, signal)
|
|
56
|
+
} catch (_) {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cleanupAndExit() {
|
|
63
|
+
if (cleaning) return
|
|
64
|
+
cleaning = true
|
|
65
|
+
|
|
66
|
+
process.stdout.write('\n\x1b[0m')
|
|
67
|
+
killAllProcessGroups('SIGTERM')
|
|
68
|
+
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
killAllProcessGroups('SIGKILL')
|
|
71
|
+
process.exit(0)
|
|
72
|
+
}, 150)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
process.on('SIGINT', cleanupAndExit)
|
|
76
|
+
process.on('SIGTERM', cleanupAndExit)
|
|
77
|
+
|
|
43
78
|
export async function runScriptsInParallel(
|
|
44
79
|
scripts: ScriptToRun[],
|
|
45
80
|
options: {
|
|
@@ -100,9 +135,12 @@ function runSingleScript(script: ScriptToRun, colorIndex: number): Promise<void>
|
|
|
100
135
|
detached: true,
|
|
101
136
|
})
|
|
102
137
|
|
|
138
|
+
spawnedProcesses.push(proc)
|
|
139
|
+
|
|
103
140
|
let stderrBuffer = ''
|
|
104
141
|
|
|
105
142
|
proc.stdout.on('data', (data) => {
|
|
143
|
+
if (cleaning) return
|
|
106
144
|
const lines = data.toString().split('\n')
|
|
107
145
|
for (const line of lines) {
|
|
108
146
|
if (line) console.info(`${color}${prefixLabel}${reset} ${line}`)
|
|
@@ -113,6 +151,7 @@ function runSingleScript(script: ScriptToRun, colorIndex: number): Promise<void>
|
|
|
113
151
|
const dataStr = data.toString()
|
|
114
152
|
stderrBuffer += dataStr
|
|
115
153
|
|
|
154
|
+
if (cleaning) return
|
|
116
155
|
const lines = dataStr.split('\n')
|
|
117
156
|
for (const line of lines) {
|
|
118
157
|
if (line && !isBunShellNoise(line)) {
|
|
@@ -127,6 +166,8 @@ function runSingleScript(script: ScriptToRun, colorIndex: number): Promise<void>
|
|
|
127
166
|
})
|
|
128
167
|
|
|
129
168
|
proc.on('close', (code) => {
|
|
169
|
+
if (cleaning) return
|
|
170
|
+
|
|
130
171
|
if (code && code !== 0) {
|
|
131
172
|
// detect if this was a bun shell error (already printed inline)
|
|
132
173
|
// check for ShellError or the bun shell stack trace pattern
|
|
@@ -101,6 +101,7 @@ export async function listAllScripts(includeCommands = true) {
|
|
|
101
101
|
console.info(` ${pc.green('env:setup')} - Setup environment variables`)
|
|
102
102
|
console.info(` ${pc.green('run')} - Run scripts in parallel`)
|
|
103
103
|
console.info(` ${pc.green('script')} - Manage and run scripts`)
|
|
104
|
+
console.info(` ${pc.green('skills')} - Manage Claude Code skills`)
|
|
104
105
|
console.info(` ${pc.green('sync')} - Sync fork with upstream Takeout`)
|
|
105
106
|
console.info(` ${pc.green('changed')} - Show changes since last sync`)
|
|
106
107
|
console.info(` ${pc.green('completion')} - Shell completion setup`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/commands/docs.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/commands/docs.ts"],"names":[],"mappings":"AAAA;;GAEG;AA+SH,eAAO,MAAM,WAAW,qDAWtB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAAA;;GAEG;AAogBH,eAAO,MAAM,aAAa,qDASxB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parallel-runner.d.ts","sourceRoot":"","sources":["../../src/utils/parallel-runner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkCH,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;
|
|
1
|
+
{"version":3,"file":"parallel-runner.d.ts","sourceRoot":"","sources":["../../src/utils/parallel-runner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkCH,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAqCD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,WAAW,EAAE,EACtB,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACnB,GACL,OAAO,CAAC,IAAI,CAAC,CAuCf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script-listing.d.ts","sourceRoot":"","sources":["../../src/utils/script-listing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsFH,wBAAsB,cAAc,CAAC,eAAe,UAAO,
|
|
1
|
+
{"version":3,"file":"script-listing.d.ts","sourceRoot":"","sources":["../../src/utils/script-listing.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsFH,wBAAsB,cAAc,CAAC,eAAe,UAAO,iBA2D1D;AAGD,wBAAsB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA2ChF"}
|