@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
package/src/cli.ts
CHANGED
|
@@ -84,6 +84,7 @@ if (isShorthand) {
|
|
|
84
84
|
'run',
|
|
85
85
|
'run-all',
|
|
86
86
|
'script',
|
|
87
|
+
'skills',
|
|
87
88
|
'env:setup',
|
|
88
89
|
'sync',
|
|
89
90
|
'changed',
|
|
@@ -153,6 +154,7 @@ const main = defineCommand({
|
|
|
153
154
|
run: () => import('./commands/run').then((m) => m.runCommand),
|
|
154
155
|
'run-all': () => import('./commands/run-all').then((m) => m.runAllCommand),
|
|
155
156
|
script: () => import('./commands/script').then((m) => m.scriptCommand),
|
|
157
|
+
skills: () => import('./commands/skills').then((m) => m.skillsCommand),
|
|
156
158
|
sync: () => import('./commands/sync').then((m) => m.syncCommand),
|
|
157
159
|
changed: () => import('./commands/changed').then((m) => m.changedCommand),
|
|
158
160
|
},
|
package/src/commands/docs.ts
CHANGED
|
@@ -2,19 +2,9 @@
|
|
|
2
2
|
* Docs command - list and retrieve documentation files from the package
|
|
3
3
|
*/
|
|
4
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'
|
|
5
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
16
6
|
import { createRequire } from 'node:module'
|
|
17
|
-
import { dirname, join
|
|
7
|
+
import { dirname, join } from 'node:path'
|
|
18
8
|
|
|
19
9
|
import { defineCommand } from 'citty'
|
|
20
10
|
import pc from 'picocolors'
|
|
@@ -313,281 +303,6 @@ const ejectCommand = defineCommand({
|
|
|
313
303
|
},
|
|
314
304
|
})
|
|
315
305
|
|
|
316
|
-
// check if content has yaml frontmatter with required skill fields
|
|
317
|
-
function hasSkillFrontmatter(content: string): boolean {
|
|
318
|
-
if (!content.startsWith('---')) return false
|
|
319
|
-
const endIndex = content.indexOf('---', 3)
|
|
320
|
-
if (endIndex === -1) return false
|
|
321
|
-
|
|
322
|
-
const frontmatter = content.slice(3, endIndex)
|
|
323
|
-
return frontmatter.includes('name:') && frontmatter.includes('description:')
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// check if content has dev: true in frontmatter
|
|
327
|
-
function isDevOnly(content: string): boolean {
|
|
328
|
-
if (!content.startsWith('---')) return false
|
|
329
|
-
const endIndex = content.indexOf('---', 3)
|
|
330
|
-
if (endIndex === -1) return false
|
|
331
|
-
|
|
332
|
-
const frontmatter = content.slice(3, endIndex)
|
|
333
|
-
return /\bdev:\s*true\b/.test(frontmatter)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// extract title and description from markdown content (for docs without frontmatter)
|
|
337
|
-
function extractDocMeta(content: string): { title: string; description: string } {
|
|
338
|
-
const lines = content.split('\n')
|
|
339
|
-
let title = ''
|
|
340
|
-
let description = ''
|
|
341
|
-
|
|
342
|
-
// skip frontmatter if present
|
|
343
|
-
let startLine = 0
|
|
344
|
-
if (lines[0]?.trim() === '---') {
|
|
345
|
-
for (let i = 1; i < lines.length; i++) {
|
|
346
|
-
if (lines[i]?.trim() === '---') {
|
|
347
|
-
startLine = i + 1
|
|
348
|
-
break
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
for (let i = startLine; i < lines.length; i++) {
|
|
354
|
-
const trimmed = lines[i]?.trim() || ''
|
|
355
|
-
// first h1 heading is the title
|
|
356
|
-
if (!title && trimmed.startsWith('# ')) {
|
|
357
|
-
title = trimmed.slice(2).trim()
|
|
358
|
-
continue
|
|
359
|
-
}
|
|
360
|
-
// first non-empty, non-heading line after title is description
|
|
361
|
-
if (title && trimmed && !trimmed.startsWith('#')) {
|
|
362
|
-
description = trimmed
|
|
363
|
-
break
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return { title, description }
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// convert doc name to skill-friendly name (lowercase, hyphens)
|
|
371
|
-
function toSkillName(name: string): string {
|
|
372
|
-
return name
|
|
373
|
-
.toLowerCase()
|
|
374
|
-
.replace(/[^a-z0-9-]/g, '-')
|
|
375
|
-
.replace(/-+/g, '-')
|
|
376
|
-
.replace(/^-|-$/g, '')
|
|
377
|
-
.slice(0, 64)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// collect all docs from both package and local directories
|
|
381
|
-
function collectAllDocs(
|
|
382
|
-
cwd: string
|
|
383
|
-
): Array<{ name: string; path: string; source: 'package' | 'local' }> {
|
|
384
|
-
const docs: Array<{ name: string; path: string; source: 'package' | 'local' }> = []
|
|
385
|
-
const seen = new Set<string>()
|
|
386
|
-
|
|
387
|
-
// local docs override package docs
|
|
388
|
-
const localDocsDir = join(cwd, 'docs')
|
|
389
|
-
if (existsSync(localDocsDir)) {
|
|
390
|
-
const files = readdirSync(localDocsDir).filter((f) => f.endsWith('.md'))
|
|
391
|
-
for (const file of files) {
|
|
392
|
-
const name = file.replace(/\.md$/, '')
|
|
393
|
-
docs.push({ name, path: join(localDocsDir, file), source: 'local' })
|
|
394
|
-
seen.add(name)
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// package docs (only if not overridden by local)
|
|
399
|
-
if (existsSync(DOCS_DIR)) {
|
|
400
|
-
const files = readdirSync(DOCS_DIR).filter((f) => f.endsWith('.md'))
|
|
401
|
-
for (const file of files) {
|
|
402
|
-
const name = file.replace(/\.md$/, '')
|
|
403
|
-
if (!seen.has(name)) {
|
|
404
|
-
docs.push({ name, path: join(DOCS_DIR, file), source: 'package' })
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return docs
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const SKILL_PREFIX = 'takeout-'
|
|
413
|
-
|
|
414
|
-
const skillsCommand = defineCommand({
|
|
415
|
-
meta: {
|
|
416
|
-
name: 'skills',
|
|
417
|
-
description: 'Generate Claude Code skills from documentation',
|
|
418
|
-
},
|
|
419
|
-
args: {
|
|
420
|
-
clean: {
|
|
421
|
-
type: 'boolean',
|
|
422
|
-
description: 'Remove existing skills before generating',
|
|
423
|
-
default: false,
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
async run({ args }) {
|
|
427
|
-
const cwd = process.cwd()
|
|
428
|
-
const skillsDir = join(cwd, '.claude', 'skills')
|
|
429
|
-
|
|
430
|
-
console.info()
|
|
431
|
-
console.info(pc.bold(pc.cyan('🧠 Generate Claude Code Skills')))
|
|
432
|
-
console.info()
|
|
433
|
-
|
|
434
|
-
// collect all docs
|
|
435
|
-
const docs = collectAllDocs(cwd)
|
|
436
|
-
|
|
437
|
-
if (docs.length === 0) {
|
|
438
|
-
console.info(pc.yellow('No documentation files found'))
|
|
439
|
-
return
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
console.info(pc.dim(`Found ${docs.length} documentation files`))
|
|
443
|
-
console.info()
|
|
444
|
-
|
|
445
|
-
// clean existing takeout skills if requested
|
|
446
|
-
if (args.clean && existsSync(skillsDir)) {
|
|
447
|
-
console.info(pc.yellow('Cleaning existing takeout skills...'))
|
|
448
|
-
const existing = readdirSync(skillsDir)
|
|
449
|
-
for (const dir of existing) {
|
|
450
|
-
if (dir.startsWith(SKILL_PREFIX)) {
|
|
451
|
-
rmSync(join(skillsDir, dir), { recursive: true })
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// ensure skills directory exists
|
|
457
|
-
if (!existsSync(skillsDir)) {
|
|
458
|
-
mkdirSync(skillsDir, { recursive: true })
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
let symlinked = 0
|
|
462
|
-
let generated = 0
|
|
463
|
-
let unchanged = 0
|
|
464
|
-
|
|
465
|
-
const isDev = !!process.env.IS_TAMAGUI_DEV
|
|
466
|
-
|
|
467
|
-
for (const doc of docs) {
|
|
468
|
-
// read doc content
|
|
469
|
-
const content = readFileSync(doc.path, 'utf-8')
|
|
470
|
-
|
|
471
|
-
// skip dev-only docs unless IS_TAMAGUI_DEV is set
|
|
472
|
-
if (isDevOnly(content) && !isDev) continue
|
|
473
|
-
|
|
474
|
-
const hasFrontmatter = hasSkillFrontmatter(content)
|
|
475
|
-
|
|
476
|
-
if (hasFrontmatter) {
|
|
477
|
-
// extract skill name from frontmatter
|
|
478
|
-
const nameMatch = content.match(/^---\s*\nname:\s*([^\n]+)/m)
|
|
479
|
-
if (!nameMatch) continue
|
|
480
|
-
|
|
481
|
-
const skillName = nameMatch[1]!.trim()
|
|
482
|
-
const skillDir = join(skillsDir, skillName)
|
|
483
|
-
const skillFile = join(skillDir, 'SKILL.md')
|
|
484
|
-
|
|
485
|
-
// ensure skill directory exists
|
|
486
|
-
if (!existsSync(skillDir)) {
|
|
487
|
-
mkdirSync(skillDir, { recursive: true })
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// symlink it directly
|
|
491
|
-
const relativePath = relative(skillDir, doc.path)
|
|
492
|
-
|
|
493
|
-
// check if something exists at the skill path (including broken symlinks)
|
|
494
|
-
let shouldCreate = true
|
|
495
|
-
try {
|
|
496
|
-
const stat = lstatSync(skillFile)
|
|
497
|
-
if (stat.isSymbolicLink() && existsSync(skillFile)) {
|
|
498
|
-
// valid symlink - check if content matches
|
|
499
|
-
const existingContent = readFileSync(skillFile, 'utf-8')
|
|
500
|
-
if (existingContent === content) {
|
|
501
|
-
unchanged++
|
|
502
|
-
shouldCreate = false
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
if (shouldCreate) {
|
|
506
|
-
// remove existing file/symlink to recreate
|
|
507
|
-
unlinkSync(skillFile)
|
|
508
|
-
}
|
|
509
|
-
} catch {
|
|
510
|
-
// nothing exists at the path
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (!shouldCreate) continue
|
|
514
|
-
|
|
515
|
-
symlinkSync(relativePath, skillFile)
|
|
516
|
-
symlinked++
|
|
517
|
-
|
|
518
|
-
const sourceLabel = doc.source === 'local' ? pc.blue('local') : pc.dim('package')
|
|
519
|
-
console.info(
|
|
520
|
-
` ${pc.green('⟷')} ${skillName} ${sourceLabel} ${pc.dim('(symlink)')}`
|
|
521
|
-
)
|
|
522
|
-
} else {
|
|
523
|
-
// doc lacks frontmatter - generate skill file with frontmatter
|
|
524
|
-
const baseName = toSkillName(doc.name)
|
|
525
|
-
const skillName = `${SKILL_PREFIX}${baseName}`
|
|
526
|
-
const skillDir = join(skillsDir, skillName)
|
|
527
|
-
const skillFile = join(skillDir, 'SKILL.md')
|
|
528
|
-
|
|
529
|
-
// ensure skill directory exists
|
|
530
|
-
if (!existsSync(skillDir)) {
|
|
531
|
-
mkdirSync(skillDir, { recursive: true })
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const { title, description } = extractDocMeta(content)
|
|
535
|
-
const skillDescription = description
|
|
536
|
-
? `${title}. ${description}`.slice(0, 1024)
|
|
537
|
-
: title.slice(0, 1024)
|
|
538
|
-
|
|
539
|
-
const skillContent = `---
|
|
540
|
-
name: ${skillName}
|
|
541
|
-
description: ${skillDescription}
|
|
542
|
-
---
|
|
543
|
-
|
|
544
|
-
${content}
|
|
545
|
-
`
|
|
546
|
-
|
|
547
|
-
// check if skill already exists and is identical
|
|
548
|
-
let shouldWrite = true
|
|
549
|
-
try {
|
|
550
|
-
const stat = lstatSync(skillFile)
|
|
551
|
-
if (stat.isSymbolicLink()) {
|
|
552
|
-
// remove symlink to replace with generated file
|
|
553
|
-
unlinkSync(skillFile)
|
|
554
|
-
} else {
|
|
555
|
-
const existing = readFileSync(skillFile, 'utf-8')
|
|
556
|
-
if (existing === skillContent) {
|
|
557
|
-
unchanged++
|
|
558
|
-
shouldWrite = false
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
} catch {
|
|
562
|
-
// nothing exists at the path
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (!shouldWrite) continue
|
|
566
|
-
|
|
567
|
-
writeFileSync(skillFile, skillContent)
|
|
568
|
-
generated++
|
|
569
|
-
|
|
570
|
-
const sourceLabel = doc.source === 'local' ? pc.blue('local') : pc.dim('package')
|
|
571
|
-
console.info(
|
|
572
|
-
` ${pc.green('✓')} ${skillName} ${sourceLabel} ${pc.dim('(generated)')}`
|
|
573
|
-
)
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
console.info()
|
|
578
|
-
console.info(pc.bold('Summary:'))
|
|
579
|
-
if (symlinked > 0) console.info(` ${pc.green(`${symlinked} symlinked`)}`)
|
|
580
|
-
if (generated > 0)
|
|
581
|
-
console.info(
|
|
582
|
-
` ${pc.yellow(`${generated} generated`)} ${pc.dim('(add frontmatter to source to enable symlink)')}`
|
|
583
|
-
)
|
|
584
|
-
if (unchanged > 0) console.info(` ${pc.dim(`${unchanged} unchanged`)}`)
|
|
585
|
-
console.info()
|
|
586
|
-
console.info(pc.dim(`Skills in ${skillsDir}`))
|
|
587
|
-
console.info()
|
|
588
|
-
},
|
|
589
|
-
})
|
|
590
|
-
|
|
591
306
|
export const docsCommand = defineCommand({
|
|
592
307
|
meta: {
|
|
593
308
|
name: 'docs',
|
|
@@ -598,6 +313,5 @@ export const docsCommand = defineCommand({
|
|
|
598
313
|
get: getCommand,
|
|
599
314
|
path: pathCommand,
|
|
600
315
|
eject: ejectCommand,
|
|
601
|
-
skills: skillsCommand,
|
|
602
316
|
},
|
|
603
317
|
})
|