td-ai-tools 1.1.11 → 1.2.0
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
CHANGED
|
@@ -36,9 +36,11 @@ npx td-ai-tools list
|
|
|
36
36
|
npx td-ai-tools install
|
|
37
37
|
npx td-ai-tools install --all
|
|
38
38
|
npx td-ai-tools install pr-solver
|
|
39
|
+
npx td-ai-tools install --setup playwright-cli
|
|
39
40
|
npx td-ai-tools install pr-solver horizon-component-library
|
|
40
41
|
npx td-ai-tools update
|
|
41
42
|
npx td-ai-tools update --all
|
|
43
|
+
npx td-ai-tools update --setup playwright-cli
|
|
42
44
|
npx td-ai-tools update pr-solver
|
|
43
45
|
npx td-ai-tools delete
|
|
44
46
|
npx td-ai-tools delete --all
|
|
@@ -53,6 +55,8 @@ If a skill bundles a sub-agent prompt (any `.md` with a `name:` field in `skills
|
|
|
53
55
|
|
|
54
56
|
This keeps the installed assets available to both Claude-style and `.agents`-style project conventions.
|
|
55
57
|
|
|
58
|
+
Some skills provide a recognized setup command (`setup.sh`, `scripts/setup.sh`, or `package.json` with `scripts.setup`). Non-interactive installs and updates run setup only when you pass `--setup`; interactive installs/updates ask for confirmation only when the selected skills include a recognized setup command. Accepted setup runs once inside each installed copy: `.claude/skills/<name>/` and `.agents/skills/<name>/`.
|
|
59
|
+
|
|
56
60
|
`install` now errors when the target item already exists. Use `update` to replace an existing installed skill or agent pack.
|
|
57
61
|
|
|
58
62
|
`delete` removes installed items from both `.claude/` and `.agents/` target directories, and works on any installed skill or agent pack regardless of whether it is in the catalogue.
|
package/bin/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '../lib/catalog.js';
|
|
13
13
|
import { readFrontmatterField } from '../lib/frontmatter.js';
|
|
14
14
|
import {
|
|
15
|
-
installSkill, installAgent, deleteSkill, deleteAgent,
|
|
15
|
+
installSkill, installAgent, deleteSkill, deleteAgent, skillHasSetup,
|
|
16
16
|
} from '../lib/installer.js';
|
|
17
17
|
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -139,13 +139,18 @@ function hintFor(type, name) {
|
|
|
139
139
|
*
|
|
140
140
|
* @param {MenuItem[]} items - Items to install.
|
|
141
141
|
* @param {{replaceExisting?: boolean}} [options] - Forwarded to the installer.
|
|
142
|
-
* @returns {
|
|
142
|
+
* @returns {boolean} Whether every installer reported success.
|
|
143
143
|
*/
|
|
144
144
|
function installItems(items, options = {}) {
|
|
145
|
+
const { runSetup = false, ...installOptions } = options;
|
|
146
|
+
let ok = true;
|
|
145
147
|
for (const item of items) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
const installed = item.type === 'skill'
|
|
149
|
+
? installSkill(ctx, item.name, { ...installOptions, runSetup, report: status })
|
|
150
|
+
: installAgent(ctx, item.name, { ...installOptions, report: status });
|
|
151
|
+
if (!installed) ok = false;
|
|
148
152
|
}
|
|
153
|
+
return ok;
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
/**
|
|
@@ -354,6 +359,52 @@ function fromGroupValues(values) {
|
|
|
354
359
|
});
|
|
355
360
|
}
|
|
356
361
|
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Whether any selected skill has a recognized setup script.
|
|
365
|
+
*
|
|
366
|
+
* @param {MenuItem[]} items - Items selected for install/update.
|
|
367
|
+
* @returns {boolean}
|
|
368
|
+
*/
|
|
369
|
+
function hasSetupSkills(items) {
|
|
370
|
+
return items.some(item => item.type === 'skill' && skillHasSetup(ctx, item.name));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Prompts before running setup for selected skills that provide a recognized setup script.
|
|
375
|
+
*
|
|
376
|
+
* @param {MenuItem[]} items - Items selected for install/update.
|
|
377
|
+
* @returns {Promise<boolean>} Whether setup should run.
|
|
378
|
+
*/
|
|
379
|
+
async function confirmSetup(items) {
|
|
380
|
+
if (!hasSetupSkills(items)) return false;
|
|
381
|
+
const answer = await p.confirm({
|
|
382
|
+
message: 'Run setup scripts for selected skills that provide one? Setup runs in both .claude and .agents copies.',
|
|
383
|
+
initialValue: false,
|
|
384
|
+
});
|
|
385
|
+
if (p.isCancel(answer)) {
|
|
386
|
+
p.cancel('Cancelled.');
|
|
387
|
+
process.exit(0);
|
|
388
|
+
}
|
|
389
|
+
return Boolean(answer);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Removes recognized global install flags from positional arguments.
|
|
394
|
+
*
|
|
395
|
+
* @param {string[]} args - Raw command arguments after the command name.
|
|
396
|
+
* @returns {{rest: string[], runSetup: boolean}}
|
|
397
|
+
*/
|
|
398
|
+
function parseInstallFlags(args) {
|
|
399
|
+
let runSetup = false;
|
|
400
|
+
const rest = [];
|
|
401
|
+
for (const arg of args) {
|
|
402
|
+
if (arg === '--setup') runSetup = true;
|
|
403
|
+
else rest.push(arg);
|
|
404
|
+
}
|
|
405
|
+
return { rest, runSetup };
|
|
406
|
+
}
|
|
407
|
+
|
|
357
408
|
/**
|
|
358
409
|
* Runs the interactive install/update flow (multiselect → apply). Exits the
|
|
359
410
|
* process on a non-TTY stdin or user cancellation.
|
|
@@ -395,7 +446,13 @@ async function interactiveInstall(mode = 'install') {
|
|
|
395
446
|
return;
|
|
396
447
|
}
|
|
397
448
|
|
|
398
|
-
|
|
449
|
+
const runSetup = await confirmSetup(items);
|
|
450
|
+
const ok = installItems(items, { replaceExisting: mode === 'update', runSetup });
|
|
451
|
+
if (!ok) {
|
|
452
|
+
p.outro(pc.red(`${mode === 'update' ? 'Update' : 'Install'} completed with errors.`));
|
|
453
|
+
process.exitCode = 1;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
399
456
|
p.outro(mode === 'update' ? pc.green('Update complete.') : pc.green('Install complete.'));
|
|
400
457
|
}
|
|
401
458
|
|
|
@@ -527,9 +584,13 @@ const HELP_TEXT = `Usage:
|
|
|
527
584
|
npx td-ai-tools list List available skills and agent packs
|
|
528
585
|
npx td-ai-tools install Interactive install
|
|
529
586
|
npx td-ai-tools install --all Install everything
|
|
587
|
+
npx td-ai-tools install --setup <name...>
|
|
588
|
+
Install and run recognized skill setup scripts
|
|
530
589
|
npx td-ai-tools install <name...> Install specific skills or agent packs
|
|
531
590
|
npx td-ai-tools update Interactive update (installed items in the catalog)
|
|
532
591
|
npx td-ai-tools update --all Update all installed items found in the catalog
|
|
592
|
+
npx td-ai-tools update --setup <name...>
|
|
593
|
+
Update and run recognized skill setup scripts
|
|
533
594
|
npx td-ai-tools update <name...> Update specific installed skills or agent packs
|
|
534
595
|
npx td-ai-tools delete Interactive delete
|
|
535
596
|
npx td-ai-tools delete --all Delete everything
|
|
@@ -581,17 +642,17 @@ async function main() {
|
|
|
581
642
|
}
|
|
582
643
|
|
|
583
644
|
if (cmd === 'install') {
|
|
584
|
-
const rest = args.slice(1);
|
|
645
|
+
const { rest, runSetup } = parseInstallFlags(args.slice(1));
|
|
585
646
|
if (rest[0] === '--all') {
|
|
586
|
-
installItems(buildMenu());
|
|
647
|
+
if (!installItems(buildMenu(), { runSetup })) process.exitCode = 1;
|
|
587
648
|
} else {
|
|
588
|
-
installItems(resolveNames(rest));
|
|
649
|
+
if (!installItems(resolveNames(rest), { runSetup })) process.exitCode = 1;
|
|
589
650
|
}
|
|
590
651
|
return;
|
|
591
652
|
}
|
|
592
653
|
|
|
593
654
|
if (cmd === 'update') {
|
|
594
|
-
const rest = args.slice(1);
|
|
655
|
+
const { rest, runSetup } = parseInstallFlags(args.slice(1));
|
|
595
656
|
if (rest.length === 0) {
|
|
596
657
|
await interactiveInstall('update');
|
|
597
658
|
return;
|
|
@@ -602,9 +663,9 @@ async function main() {
|
|
|
602
663
|
status('info', 'No installed skills or agent packs match the catalog.');
|
|
603
664
|
return;
|
|
604
665
|
}
|
|
605
|
-
installItems(menu, { replaceExisting: true });
|
|
666
|
+
if (!installItems(menu, { replaceExisting: true, runSetup })) process.exitCode = 1;
|
|
606
667
|
} else {
|
|
607
|
-
installItems(resolveUpdateNames(rest), { replaceExisting: true });
|
|
668
|
+
if (!installItems(resolveUpdateNames(rest), { replaceExisting: true, runSetup })) process.exitCode = 1;
|
|
608
669
|
}
|
|
609
670
|
return;
|
|
610
671
|
}
|
package/lib/installer.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
9
10
|
import { copyDir } from './fs-utils.js';
|
|
10
11
|
import { bundledAgentsIn } from './catalog.js';
|
|
11
12
|
|
|
@@ -24,6 +25,82 @@ import { bundledAgentsIn } from './catalog.js';
|
|
|
24
25
|
/** @type {ReportFn} */
|
|
25
26
|
const noop = () => {};
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Recognized setup commands, in priority order, relative to an installed skill.
|
|
30
|
+
* @type {{file: string, command: string, args: string[]}[]}
|
|
31
|
+
*/
|
|
32
|
+
const SKILL_SETUP_CANDIDATES = [
|
|
33
|
+
{ file: 'setup.sh', command: 'bash', args: ['setup.sh'] },
|
|
34
|
+
{ file: path.join('scripts', 'setup.sh'), command: 'bash', args: [path.join('scripts', 'setup.sh')] },
|
|
35
|
+
{ file: 'package.json', command: 'npm', args: ['run', 'setup'] },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Finds the setup command for a skill directory, if one uses a recognized convention.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} skillDir - Source or installed skill directory.
|
|
42
|
+
* @returns {({file: string, command: string, args: string[]}|null)} The setup command, or `null`.
|
|
43
|
+
*/
|
|
44
|
+
export function findSkillSetup(skillDir) {
|
|
45
|
+
for (const candidate of SKILL_SETUP_CANDIDATES) {
|
|
46
|
+
const filePath = path.join(skillDir, candidate.file);
|
|
47
|
+
if (!fs.existsSync(filePath)) continue;
|
|
48
|
+
if (candidate.file === 'package.json') {
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
51
|
+
if (!pkg?.scripts?.setup) continue;
|
|
52
|
+
} catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return candidate;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether a catalog skill provides a setup command using a recognized convention.
|
|
63
|
+
*
|
|
64
|
+
* @param {Ctx} ctx
|
|
65
|
+
* @param {string} name - Skill name.
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
export function skillHasSetup(ctx, name) {
|
|
69
|
+
return Boolean(findSkillSetup(path.join(ctx.skillsDir, name)));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Runs a skill setup command from the installed skill directory.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} name - Skill name.
|
|
76
|
+
* @param {Target} target - The destination layout.
|
|
77
|
+
* @param {string} dest - Installed skill directory.
|
|
78
|
+
* @param {ReportFn} report
|
|
79
|
+
* @returns {boolean} Whether setup succeeded or no setup existed.
|
|
80
|
+
*/
|
|
81
|
+
function runSkillSetup(name, target, dest, report) {
|
|
82
|
+
const setup = findSkillSetup(dest);
|
|
83
|
+
if (!setup) return true;
|
|
84
|
+
|
|
85
|
+
report('step', `setup: ${name} running ${setup.command} ${setup.args.join(' ')} in ${target.root}/skills/${name}/`);
|
|
86
|
+
const result = spawnSync(setup.command, setup.args, {
|
|
87
|
+
cwd: dest,
|
|
88
|
+
stdio: 'inherit',
|
|
89
|
+
shell: false,
|
|
90
|
+
});
|
|
91
|
+
if (result.error) {
|
|
92
|
+
report('error', `setup: ${name} failed in ${target.root}/skills/${name}/: ${result.error.message}`);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (result.status !== 0) {
|
|
96
|
+
report('error', `setup: ${name} failed in ${target.root}/skills/${name}/ with exit code ${result.status}`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
report('success', `setup: ${name} completed in ${target.root}/skills/${name}/`);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
27
104
|
/**
|
|
28
105
|
* Copies an item's bundled sub-agents into a target's `agents/` directory.
|
|
29
106
|
*
|
|
@@ -52,10 +129,12 @@ function registerBundledAgents(ctx, target, srcDir, report) {
|
|
|
52
129
|
* @param {string} name - Skill name.
|
|
53
130
|
* @param {object} [options]
|
|
54
131
|
* @param {boolean} [options.replaceExisting=false] - Overwrite an existing install (used by `update`).
|
|
132
|
+
* @param {boolean} [options.runSetup=false] - Run recognized setup scripts after each target copy.
|
|
55
133
|
* @param {ReportFn} [options.report] - Progress sink.
|
|
56
|
-
* @returns {boolean} `false` if the skill is not in the catalog, otherwise `true`.
|
|
134
|
+
* @returns {boolean} `false` if the skill is not in the catalog or setup fails, otherwise `true`.
|
|
57
135
|
*/
|
|
58
|
-
export function installSkill(ctx, name, { replaceExisting = false, report = noop } = {}) {
|
|
136
|
+
export function installSkill(ctx, name, { replaceExisting = false, runSetup = false, report = noop } = {}) {
|
|
137
|
+
let ok = true;
|
|
59
138
|
const src = path.join(ctx.skillsDir, name);
|
|
60
139
|
if (!fs.existsSync(src)) {
|
|
61
140
|
report('error', `Skill "${name}" not found.`);
|
|
@@ -71,8 +150,9 @@ export function installSkill(ctx, name, { replaceExisting = false, report = noop
|
|
|
71
150
|
copyDir(src, dest);
|
|
72
151
|
report('success', `skill: ${name} ${replaceExisting ? 'updated' : 'installed'} → ${target.root}/skills/${name}/`);
|
|
73
152
|
registerBundledAgents(ctx, target, src, report);
|
|
153
|
+
if (runSetup && !runSkillSetup(name, target, dest, report)) ok = false;
|
|
74
154
|
}
|
|
75
|
-
return
|
|
155
|
+
return ok;
|
|
76
156
|
}
|
|
77
157
|
|
|
78
158
|
/**
|
package/package.json
CHANGED
|
Binary file
|