td-ai-tools 1.0.8 → 1.1.1
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/bin/cli.js +176 -82
- package/package.json +7 -2
package/bin/cli.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import * as p from '@clack/prompts';
|
|
6
|
+
import pc from 'picocolors';
|
|
3
7
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const readline = require('readline');
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
7
10
|
|
|
8
11
|
const TOOLKIT_ROOT = path.resolve(__dirname, '..');
|
|
9
12
|
const SKILLS_DIR = path.join(TOOLKIT_ROOT, 'skills');
|
|
@@ -14,6 +17,22 @@ const INSTALL_TARGETS = [
|
|
|
14
17
|
{ label: 'agents', root: '.agents' }
|
|
15
18
|
];
|
|
16
19
|
|
|
20
|
+
const IS_TTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
21
|
+
|
|
22
|
+
function status(kind, msg) {
|
|
23
|
+
if (IS_TTY) {
|
|
24
|
+
if (kind === 'success') p.log.success(msg);
|
|
25
|
+
else if (kind === 'step') p.log.step(msg);
|
|
26
|
+
else if (kind === 'warn') p.log.warn(msg);
|
|
27
|
+
else if (kind === 'error') p.log.error(msg);
|
|
28
|
+
else p.log.info(msg);
|
|
29
|
+
} else {
|
|
30
|
+
const stream = (kind === 'error' || kind === 'warn') ? console.error : console.log;
|
|
31
|
+
const prefix = kind === 'error' ? ' [error] ' : kind === 'warn' ? ' [warn] ' : ' ';
|
|
32
|
+
stream(prefix + msg);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
function copyDir(src, dest) {
|
|
18
37
|
const srcStat = fs.statSync(src);
|
|
19
38
|
fs.mkdirSync(dest, { recursive: true, mode: srcStat.mode });
|
|
@@ -47,13 +66,13 @@ function readFrontmatterField(mdPath, field) {
|
|
|
47
66
|
function installSkill(name, { replaceExisting = false } = {}) {
|
|
48
67
|
const src = path.join(SKILLS_DIR, name);
|
|
49
68
|
if (!fs.existsSync(src)) {
|
|
50
|
-
|
|
69
|
+
status('error', `Skill "${name}" not found.`);
|
|
51
70
|
return false;
|
|
52
71
|
}
|
|
53
72
|
for (const target of INSTALL_TARGETS) {
|
|
54
73
|
const dest = path.join(TARGET_ROOT, target.root, 'skills', name);
|
|
55
74
|
if (fs.existsSync(dest) && !replaceExisting) {
|
|
56
|
-
|
|
75
|
+
status('warn', `Skill "${name}" already exists at ${target.root}/skills/${name}/ (use "update" to replace).`);
|
|
57
76
|
return false;
|
|
58
77
|
}
|
|
59
78
|
if (fs.existsSync(dest) && replaceExisting) {
|
|
@@ -61,7 +80,7 @@ function installSkill(name, { replaceExisting = false } = {}) {
|
|
|
61
80
|
}
|
|
62
81
|
copyDir(src, dest);
|
|
63
82
|
const action = replaceExisting ? 'updated' : 'installed';
|
|
64
|
-
|
|
83
|
+
status('success', `skill: ${name} ${action} → ${target.root}/skills/${name}/`);
|
|
65
84
|
}
|
|
66
85
|
return true;
|
|
67
86
|
}
|
|
@@ -72,12 +91,12 @@ function deleteSkill(name) {
|
|
|
72
91
|
const dest = path.join(TARGET_ROOT, target.root, 'skills', name);
|
|
73
92
|
if (fs.existsSync(dest)) {
|
|
74
93
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
75
|
-
|
|
94
|
+
status('success', `skill: ${name} deleted from ${target.root}/skills/${name}/`);
|
|
76
95
|
deletedAny = true;
|
|
77
96
|
}
|
|
78
97
|
}
|
|
79
98
|
if (!deletedAny) {
|
|
80
|
-
|
|
99
|
+
status('error', `Skill "${name}" is not installed.`);
|
|
81
100
|
return false;
|
|
82
101
|
}
|
|
83
102
|
return true;
|
|
@@ -86,13 +105,13 @@ function deleteSkill(name) {
|
|
|
86
105
|
function installAgent(name, { replaceExisting = false } = {}) {
|
|
87
106
|
const src = path.join(AGENTS_DIR, name);
|
|
88
107
|
if (!fs.existsSync(src)) {
|
|
89
|
-
|
|
108
|
+
status('error', `Agent pack "${name}" not found.`);
|
|
90
109
|
return false;
|
|
91
110
|
}
|
|
92
111
|
for (const target of INSTALL_TARGETS) {
|
|
93
112
|
const dest = path.join(TARGET_ROOT, target.root, 'agents', name);
|
|
94
113
|
if (fs.existsSync(dest) && !replaceExisting) {
|
|
95
|
-
|
|
114
|
+
status('warn', `Agent pack "${name}" already exists at ${target.root}/agents/${name}/ (use "update" to replace).`);
|
|
96
115
|
return false;
|
|
97
116
|
}
|
|
98
117
|
if (fs.existsSync(dest) && replaceExisting) {
|
|
@@ -100,7 +119,7 @@ function installAgent(name, { replaceExisting = false } = {}) {
|
|
|
100
119
|
}
|
|
101
120
|
copyDir(src, dest);
|
|
102
121
|
const action = replaceExisting ? 'updated' : 'installed';
|
|
103
|
-
|
|
122
|
+
status('success', `agent: ${name} ${action} → ${target.root}/agents/${name}/`);
|
|
104
123
|
}
|
|
105
124
|
return true;
|
|
106
125
|
}
|
|
@@ -111,12 +130,12 @@ function deleteAgent(name) {
|
|
|
111
130
|
const dest = path.join(TARGET_ROOT, target.root, 'agents', name);
|
|
112
131
|
if (fs.existsSync(dest)) {
|
|
113
132
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
114
|
-
|
|
133
|
+
status('success', `agent: ${name} deleted from ${target.root}/agents/${name}/`);
|
|
115
134
|
deletedAny = true;
|
|
116
135
|
}
|
|
117
136
|
}
|
|
118
137
|
if (!deletedAny) {
|
|
119
|
-
|
|
138
|
+
status('error', `Agent pack "${name}" is not installed.`);
|
|
120
139
|
return false;
|
|
121
140
|
}
|
|
122
141
|
return true;
|
|
@@ -137,7 +156,17 @@ function getInstalled(type) {
|
|
|
137
156
|
return [...installed].sort();
|
|
138
157
|
}
|
|
139
158
|
|
|
140
|
-
function
|
|
159
|
+
function getSkillHint(name) {
|
|
160
|
+
const desc = readFrontmatterField(path.join(SKILLS_DIR, name, 'SKILL.md'), 'description');
|
|
161
|
+
return desc.length > 72 ? desc.slice(0, 72) + '…' : desc;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getAgentHint(name) {
|
|
165
|
+
const desc = readFrontmatterField(path.join(AGENTS_DIR, name, 'AGENTS.md'), 'description');
|
|
166
|
+
return desc.length > 72 ? desc.slice(0, 72) + '…' : desc;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function printListPlain() {
|
|
141
170
|
const skills = getAvailable(SKILLS_DIR);
|
|
142
171
|
const agents = getAvailable(AGENTS_DIR);
|
|
143
172
|
|
|
@@ -156,6 +185,36 @@ function printList() {
|
|
|
156
185
|
console.log('');
|
|
157
186
|
}
|
|
158
187
|
|
|
188
|
+
function printList() {
|
|
189
|
+
if (!IS_TTY) {
|
|
190
|
+
printListPlain();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const skills = getAvailable(SKILLS_DIR);
|
|
195
|
+
const agents = getAvailable(AGENTS_DIR);
|
|
196
|
+
|
|
197
|
+
p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
|
|
198
|
+
|
|
199
|
+
const skillsBody = skills.length
|
|
200
|
+
? skills.map(name => {
|
|
201
|
+
const hint = getSkillHint(name);
|
|
202
|
+
return hint ? `${pc.bold(name)}\n ${pc.dim(hint)}` : pc.bold(name);
|
|
203
|
+
}).join('\n')
|
|
204
|
+
: pc.dim('(none)');
|
|
205
|
+
p.note(skillsBody, `Skills (${skills.length})`);
|
|
206
|
+
|
|
207
|
+
const agentsBody = agents.length
|
|
208
|
+
? agents.map(name => {
|
|
209
|
+
const hint = getAgentHint(name);
|
|
210
|
+
return hint ? `${pc.bold(name)}\n ${pc.dim(hint)}` : pc.bold(name);
|
|
211
|
+
}).join('\n')
|
|
212
|
+
: pc.dim('(none)');
|
|
213
|
+
p.note(agentsBody, `Agent Packs (${agents.length})`);
|
|
214
|
+
|
|
215
|
+
p.outro(pc.dim(`${skills.length} skills, ${agents.length} agent packs available.`));
|
|
216
|
+
}
|
|
217
|
+
|
|
159
218
|
function buildMenu() {
|
|
160
219
|
const skills = getAvailable(SKILLS_DIR).map(name => ({ type: 'skill', name }));
|
|
161
220
|
const agents = getAvailable(AGENTS_DIR).map(name => ({ type: 'agent', name }));
|
|
@@ -168,6 +227,28 @@ function buildDeleteMenu() {
|
|
|
168
227
|
return [...skills, ...agents];
|
|
169
228
|
}
|
|
170
229
|
|
|
230
|
+
function toGroupOptions(menu, { hintForInstalled = false } = {}) {
|
|
231
|
+
const skillItems = menu.filter(i => i.type === 'skill').map(i => {
|
|
232
|
+
const hint = hintForInstalled ? '' : getSkillHint(i.name);
|
|
233
|
+
return { value: `skill:${i.name}`, label: i.name, hint: hint || undefined };
|
|
234
|
+
});
|
|
235
|
+
const agentItems = menu.filter(i => i.type === 'agent').map(i => {
|
|
236
|
+
const hint = hintForInstalled ? '' : getAgentHint(i.name);
|
|
237
|
+
return { value: `agent:${i.name}`, label: i.name, hint: hint || undefined };
|
|
238
|
+
});
|
|
239
|
+
const out = {};
|
|
240
|
+
if (skillItems.length) out['Skills'] = skillItems;
|
|
241
|
+
if (agentItems.length) out['Agent Packs'] = agentItems;
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function fromGroupValues(values) {
|
|
246
|
+
return values.map(v => {
|
|
247
|
+
const idx = v.indexOf(':');
|
|
248
|
+
return { type: v.slice(0, idx), name: v.slice(idx + 1) };
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
171
252
|
function installItems(items, options = {}) {
|
|
172
253
|
for (const item of items) {
|
|
173
254
|
if (item.type === 'skill') installSkill(item.name, options);
|
|
@@ -185,67 +266,78 @@ function deleteItems(items) {
|
|
|
185
266
|
async function interactiveInstall(mode = 'install') {
|
|
186
267
|
const menu = buildMenu();
|
|
187
268
|
if (menu.length === 0) {
|
|
188
|
-
|
|
269
|
+
status('info', 'No skills or agent packs available.');
|
|
189
270
|
return;
|
|
190
271
|
}
|
|
272
|
+
if (!IS_TTY) {
|
|
273
|
+
status('error', `interactive ${mode} requires a TTY. Use "${mode} --all" or "${mode} <name>...".`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
191
276
|
|
|
192
|
-
|
|
193
|
-
menu.forEach((item, i) => {
|
|
194
|
-
const label = item.type === 'skill' ? 'skill' : 'agent';
|
|
195
|
-
console.log(` [${i + 1}] ${label}: ${item.name}`);
|
|
196
|
-
});
|
|
197
|
-
console.log('');
|
|
277
|
+
p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
|
|
198
278
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const trimmed = answer.trim().toLowerCase();
|
|
204
|
-
let selected;
|
|
205
|
-
if (trimmed === 'all') {
|
|
206
|
-
selected = menu;
|
|
207
|
-
} else {
|
|
208
|
-
const indices = trimmed.split(/\s+/).map(n => parseInt(n, 10) - 1);
|
|
209
|
-
selected = indices.filter(i => i >= 0 && i < menu.length).map(i => menu[i]);
|
|
210
|
-
}
|
|
211
|
-
console.log('');
|
|
212
|
-
installItems(selected, { replaceExisting: mode === 'update' });
|
|
213
|
-
resolve();
|
|
214
|
-
});
|
|
279
|
+
const selection = await p.groupMultiselect({
|
|
280
|
+
message: `Select items to ${mode}`,
|
|
281
|
+
options: toGroupOptions(menu),
|
|
282
|
+
required: false,
|
|
215
283
|
});
|
|
284
|
+
|
|
285
|
+
if (p.isCancel(selection)) {
|
|
286
|
+
p.cancel('Cancelled.');
|
|
287
|
+
process.exit(0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const items = fromGroupValues(selection);
|
|
291
|
+
if (items.length === 0) {
|
|
292
|
+
p.outro(pc.dim('Nothing selected.'));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
installItems(items, { replaceExisting: mode === 'update' });
|
|
297
|
+
p.outro(mode === 'update' ? pc.green('Update complete.') : pc.green('Install complete.'));
|
|
216
298
|
}
|
|
217
299
|
|
|
218
300
|
async function interactiveDelete() {
|
|
219
301
|
const menu = buildDeleteMenu();
|
|
220
302
|
if (menu.length === 0) {
|
|
221
|
-
|
|
303
|
+
status('info', 'No installed skills or agent packs to delete.');
|
|
222
304
|
return;
|
|
223
305
|
}
|
|
306
|
+
if (!IS_TTY) {
|
|
307
|
+
status('error', 'interactive delete requires a TTY. Use "delete --all" or "delete <name>...".');
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
224
310
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
311
|
+
p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
|
|
312
|
+
|
|
313
|
+
const selection = await p.groupMultiselect({
|
|
314
|
+
message: 'Select items to delete',
|
|
315
|
+
options: toGroupOptions(menu, { hintForInstalled: true }),
|
|
316
|
+
required: false,
|
|
229
317
|
});
|
|
230
|
-
console.log('');
|
|
231
318
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
resolve();
|
|
247
|
-
});
|
|
319
|
+
if (p.isCancel(selection)) {
|
|
320
|
+
p.cancel('Cancelled.');
|
|
321
|
+
process.exit(0);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const items = fromGroupValues(selection);
|
|
325
|
+
if (items.length === 0) {
|
|
326
|
+
p.outro(pc.dim('Nothing selected.'));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const confirmed = await p.confirm({
|
|
331
|
+
message: `Delete ${items.length} item(s)?`,
|
|
332
|
+
initialValue: false,
|
|
248
333
|
});
|
|
334
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
335
|
+
p.cancel('Cancelled.');
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
deleteItems(items);
|
|
340
|
+
p.outro(pc.green('Delete complete.'));
|
|
249
341
|
}
|
|
250
342
|
|
|
251
343
|
function resolveNames(names) {
|
|
@@ -258,7 +350,7 @@ function resolveNames(names) {
|
|
|
258
350
|
} else if (agents.includes(name)) {
|
|
259
351
|
items.push({ type: 'agent', name });
|
|
260
352
|
} else {
|
|
261
|
-
|
|
353
|
+
status('error', `"${name}" not found as a skill or agent pack.`);
|
|
262
354
|
}
|
|
263
355
|
}
|
|
264
356
|
return items;
|
|
@@ -274,18 +366,13 @@ function resolveDeleteNames(names) {
|
|
|
274
366
|
} else if (installedAgents.includes(name)) {
|
|
275
367
|
items.push({ type: 'agent', name });
|
|
276
368
|
} else {
|
|
277
|
-
|
|
369
|
+
status('error', `"${name}" is not installed.`);
|
|
278
370
|
}
|
|
279
371
|
}
|
|
280
372
|
return items;
|
|
281
373
|
}
|
|
282
374
|
|
|
283
|
-
|
|
284
|
-
const args = process.argv.slice(2);
|
|
285
|
-
|
|
286
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
287
|
-
console.log(`
|
|
288
|
-
Usage:
|
|
375
|
+
const HELP_TEXT = `Usage:
|
|
289
376
|
npx td-ai-tools Interactive install
|
|
290
377
|
npx td-ai-tools list List available skills and agent packs
|
|
291
378
|
npx td-ai-tools install Interactive install
|
|
@@ -299,18 +386,31 @@ Usage:
|
|
|
299
386
|
npx td-ai-tools delete <name...> Delete specific skills or agent packs
|
|
300
387
|
|
|
301
388
|
Skills are installed to: .claude/skills/<name>/
|
|
302
|
-
|
|
389
|
+
.agents/skills/<name>/
|
|
303
390
|
Agent packs installed to: .claude/agents/<name>/
|
|
304
|
-
|
|
305
|
-
|
|
391
|
+
.agents/agents/<name>/`;
|
|
392
|
+
|
|
393
|
+
function printHelp() {
|
|
394
|
+
if (IS_TTY) {
|
|
395
|
+
p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
|
|
396
|
+
p.note(HELP_TEXT, 'Usage');
|
|
397
|
+
p.outro(pc.dim('Run with no arguments for interactive install.'));
|
|
398
|
+
} else {
|
|
399
|
+
console.log('\n' + HELP_TEXT + '\n');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function main() {
|
|
404
|
+
const args = process.argv.slice(2);
|
|
405
|
+
|
|
406
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
407
|
+
printHelp();
|
|
306
408
|
return;
|
|
307
409
|
}
|
|
308
410
|
|
|
309
411
|
const cmd = args[0];
|
|
310
|
-
console.log('AgentToolkit');
|
|
311
|
-
console.log('------------');
|
|
312
412
|
|
|
313
|
-
if (!cmd || cmd === 'install' && args.length === 1) {
|
|
413
|
+
if (!cmd || (cmd === 'install' && args.length === 1)) {
|
|
314
414
|
await interactiveInstall('install');
|
|
315
415
|
return;
|
|
316
416
|
}
|
|
@@ -323,10 +423,8 @@ Agent packs installed to: .claude/agents/<name>/
|
|
|
323
423
|
if (cmd === 'install') {
|
|
324
424
|
const rest = args.slice(1);
|
|
325
425
|
if (rest[0] === '--all') {
|
|
326
|
-
console.log('');
|
|
327
426
|
installItems(buildMenu());
|
|
328
427
|
} else {
|
|
329
|
-
console.log('');
|
|
330
428
|
installItems(resolveNames(rest));
|
|
331
429
|
}
|
|
332
430
|
return;
|
|
@@ -339,10 +437,8 @@ Agent packs installed to: .claude/agents/<name>/
|
|
|
339
437
|
return;
|
|
340
438
|
}
|
|
341
439
|
if (rest[0] === '--all') {
|
|
342
|
-
console.log('');
|
|
343
440
|
installItems(buildMenu(), { replaceExisting: true });
|
|
344
441
|
} else {
|
|
345
|
-
console.log('');
|
|
346
442
|
installItems(resolveNames(rest), { replaceExisting: true });
|
|
347
443
|
}
|
|
348
444
|
return;
|
|
@@ -355,20 +451,18 @@ Agent packs installed to: .claude/agents/<name>/
|
|
|
355
451
|
return;
|
|
356
452
|
}
|
|
357
453
|
if (rest[0] === '--all') {
|
|
358
|
-
console.log('');
|
|
359
454
|
deleteItems(buildDeleteMenu());
|
|
360
455
|
} else {
|
|
361
|
-
console.log('');
|
|
362
456
|
deleteItems(resolveDeleteNames(rest));
|
|
363
457
|
}
|
|
364
458
|
return;
|
|
365
459
|
}
|
|
366
460
|
|
|
367
|
-
|
|
461
|
+
status('error', `Unknown command: "${cmd}". Run with --help for usage.`);
|
|
368
462
|
process.exit(1);
|
|
369
463
|
}
|
|
370
464
|
|
|
371
465
|
main().catch(err => {
|
|
372
|
-
|
|
466
|
+
status('error', err.message);
|
|
373
467
|
process.exit(1);
|
|
374
468
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "td-ai-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Install agent skills and packs into your project",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"smoke:install": "./scripts/smoke-install.sh"
|
|
7
8
|
},
|
|
@@ -15,8 +16,12 @@
|
|
|
15
16
|
"skills/",
|
|
16
17
|
"agents/"
|
|
17
18
|
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@clack/prompts": "^1.4.0",
|
|
21
|
+
"picocolors": "^1.1.0"
|
|
22
|
+
},
|
|
18
23
|
"engines": {
|
|
19
|
-
"node": ">=
|
|
24
|
+
"node": ">=20.12"
|
|
20
25
|
},
|
|
21
26
|
"keywords": [
|
|
22
27
|
"claude",
|