td-ai-tools 1.0.8 → 1.1.2

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.
Files changed (2) hide show
  1. package/bin/cli.js +189 -82
  2. package/package.json +7 -2
package/bin/cli.js CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
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 fs = require('fs');
5
- const path = require('path');
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
- console.error(` [error] Skill "${name}" not found.`);
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
- console.error(` [error] Skill "${name}" already exists at ${target.root}/skills/${name}/ (use "update" to replace).`);
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
- console.log(` [skill] ${name} ${action} -> ${target.root}/skills/${name}/`);
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
- console.log(` [skill] ${name} deleted from ${target.root}/skills/${name}/`);
94
+ status('success', `skill: ${name} deleted from ${target.root}/skills/${name}/`);
76
95
  deletedAny = true;
77
96
  }
78
97
  }
79
98
  if (!deletedAny) {
80
- console.error(` [error] Skill "${name}" is not installed.`);
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
- console.error(` [error] Agent pack "${name}" not found.`);
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
- console.error(` [error] Agent pack "${name}" already exists at ${target.root}/agents/${name}/ (use "update" to replace).`);
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
- console.log(` [agent] ${name} ${action} -> ${target.root}/agents/${name}/`);
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
- console.log(` [agent] ${name} deleted from ${target.root}/agents/${name}/`);
133
+ status('success', `agent: ${name} deleted from ${target.root}/agents/${name}/`);
115
134
  deletedAny = true;
116
135
  }
117
136
  }
118
137
  if (!deletedAny) {
119
- console.error(` [error] Agent pack "${name}" is not installed.`);
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 printList() {
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,47 @@ 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
+
218
+ function printSelectionControls(action) {
219
+ p.note(
220
+ [
221
+ `Use Up/Down to move through the ${action} list.`,
222
+ 'Press Space to select or deselect items.',
223
+ 'Press Enter to confirm your choices.'
224
+ ].join('\n'),
225
+ 'Controls'
226
+ );
227
+ }
228
+
159
229
  function buildMenu() {
160
230
  const skills = getAvailable(SKILLS_DIR).map(name => ({ type: 'skill', name }));
161
231
  const agents = getAvailable(AGENTS_DIR).map(name => ({ type: 'agent', name }));
@@ -168,6 +238,28 @@ function buildDeleteMenu() {
168
238
  return [...skills, ...agents];
169
239
  }
170
240
 
241
+ function toGroupOptions(menu, { hintForInstalled = false } = {}) {
242
+ const skillItems = menu.filter(i => i.type === 'skill').map(i => {
243
+ const hint = hintForInstalled ? '' : getSkillHint(i.name);
244
+ return { value: `skill:${i.name}`, label: i.name, hint: hint || undefined };
245
+ });
246
+ const agentItems = menu.filter(i => i.type === 'agent').map(i => {
247
+ const hint = hintForInstalled ? '' : getAgentHint(i.name);
248
+ return { value: `agent:${i.name}`, label: i.name, hint: hint || undefined };
249
+ });
250
+ const out = {};
251
+ if (skillItems.length) out['Skills'] = skillItems;
252
+ if (agentItems.length) out['Agent Packs'] = agentItems;
253
+ return out;
254
+ }
255
+
256
+ function fromGroupValues(values) {
257
+ return values.map(v => {
258
+ const idx = v.indexOf(':');
259
+ return { type: v.slice(0, idx), name: v.slice(idx + 1) };
260
+ });
261
+ }
262
+
171
263
  function installItems(items, options = {}) {
172
264
  for (const item of items) {
173
265
  if (item.type === 'skill') installSkill(item.name, options);
@@ -185,67 +277,80 @@ function deleteItems(items) {
185
277
  async function interactiveInstall(mode = 'install') {
186
278
  const menu = buildMenu();
187
279
  if (menu.length === 0) {
188
- console.log('No skills or agent packs available.');
280
+ status('info', 'No skills or agent packs available.');
189
281
  return;
190
282
  }
283
+ if (!IS_TTY) {
284
+ status('error', `interactive ${mode} requires a TTY. Use "${mode} --all" or "${mode} <name>...".`);
285
+ process.exit(1);
286
+ }
191
287
 
192
- console.log('\nAvailable (enter numbers separated by spaces, or "all"):\n');
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('');
288
+ p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
289
+ printSelectionControls(mode);
198
290
 
199
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
200
- return new Promise(resolve => {
201
- rl.question(`Select items to ${mode}: `, answer => {
202
- rl.close();
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
- });
291
+ const selection = await p.groupMultiselect({
292
+ message: `Select items to ${mode}`,
293
+ options: toGroupOptions(menu),
294
+ required: false,
215
295
  });
296
+
297
+ if (p.isCancel(selection)) {
298
+ p.cancel('Cancelled.');
299
+ process.exit(0);
300
+ }
301
+
302
+ const items = fromGroupValues(selection);
303
+ if (items.length === 0) {
304
+ p.outro(pc.dim('Nothing selected.'));
305
+ return;
306
+ }
307
+
308
+ installItems(items, { replaceExisting: mode === 'update' });
309
+ p.outro(mode === 'update' ? pc.green('Update complete.') : pc.green('Install complete.'));
216
310
  }
217
311
 
218
312
  async function interactiveDelete() {
219
313
  const menu = buildDeleteMenu();
220
314
  if (menu.length === 0) {
221
- console.log('No installed skills or agent packs to delete.');
315
+ status('info', 'No installed skills or agent packs to delete.');
222
316
  return;
223
317
  }
318
+ if (!IS_TTY) {
319
+ status('error', 'interactive delete requires a TTY. Use "delete --all" or "delete <name>...".');
320
+ process.exit(1);
321
+ }
224
322
 
225
- console.log('\nInstalled (enter numbers separated by spaces, or "all"):\n');
226
- menu.forEach((item, i) => {
227
- const label = item.type === 'skill' ? 'skill' : 'agent';
228
- console.log(` [${i + 1}] ${label}: ${item.name}`);
323
+ p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
324
+ printSelectionControls('delete');
325
+
326
+ const selection = await p.groupMultiselect({
327
+ message: 'Select items to delete',
328
+ options: toGroupOptions(menu, { hintForInstalled: true }),
329
+ required: false,
229
330
  });
230
- console.log('');
231
331
 
232
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
233
- return new Promise(resolve => {
234
- rl.question('Select items to delete: ', answer => {
235
- rl.close();
236
- const trimmed = answer.trim().toLowerCase();
237
- let selected;
238
- if (trimmed === 'all') {
239
- selected = menu;
240
- } else {
241
- const indices = trimmed.split(/\s+/).map(n => parseInt(n, 10) - 1);
242
- selected = indices.filter(i => i >= 0 && i < menu.length).map(i => menu[i]);
243
- }
244
- console.log('');
245
- deleteItems(selected);
246
- resolve();
247
- });
332
+ if (p.isCancel(selection)) {
333
+ p.cancel('Cancelled.');
334
+ process.exit(0);
335
+ }
336
+
337
+ const items = fromGroupValues(selection);
338
+ if (items.length === 0) {
339
+ p.outro(pc.dim('Nothing selected.'));
340
+ return;
341
+ }
342
+
343
+ const confirmed = await p.confirm({
344
+ message: `Delete ${items.length} item(s)?`,
345
+ initialValue: false,
248
346
  });
347
+ if (p.isCancel(confirmed) || !confirmed) {
348
+ p.cancel('Cancelled.');
349
+ process.exit(0);
350
+ }
351
+
352
+ deleteItems(items);
353
+ p.outro(pc.green('Delete complete.'));
249
354
  }
250
355
 
251
356
  function resolveNames(names) {
@@ -258,7 +363,7 @@ function resolveNames(names) {
258
363
  } else if (agents.includes(name)) {
259
364
  items.push({ type: 'agent', name });
260
365
  } else {
261
- console.error(` [error] "${name}" not found as a skill or agent pack.`);
366
+ status('error', `"${name}" not found as a skill or agent pack.`);
262
367
  }
263
368
  }
264
369
  return items;
@@ -274,18 +379,13 @@ function resolveDeleteNames(names) {
274
379
  } else if (installedAgents.includes(name)) {
275
380
  items.push({ type: 'agent', name });
276
381
  } else {
277
- console.error(` [error] "${name}" is not installed.`);
382
+ status('error', `"${name}" is not installed.`);
278
383
  }
279
384
  }
280
385
  return items;
281
386
  }
282
387
 
283
- async function main() {
284
- const args = process.argv.slice(2);
285
-
286
- if (args.includes('--help') || args.includes('-h')) {
287
- console.log(`
288
- Usage:
388
+ const HELP_TEXT = `Usage:
289
389
  npx td-ai-tools Interactive install
290
390
  npx td-ai-tools list List available skills and agent packs
291
391
  npx td-ai-tools install Interactive install
@@ -299,18 +399,31 @@ Usage:
299
399
  npx td-ai-tools delete <name...> Delete specific skills or agent packs
300
400
 
301
401
  Skills are installed to: .claude/skills/<name>/
302
- .agents/skills/<name>/
402
+ .agents/skills/<name>/
303
403
  Agent packs installed to: .claude/agents/<name>/
304
- .agents/agents/<name>/
305
- `);
404
+ .agents/agents/<name>/`;
405
+
406
+ function printHelp() {
407
+ if (IS_TTY) {
408
+ p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
409
+ p.note(HELP_TEXT, 'Usage');
410
+ p.outro(pc.dim('Run with no arguments for interactive install.'));
411
+ } else {
412
+ console.log('\n' + HELP_TEXT + '\n');
413
+ }
414
+ }
415
+
416
+ async function main() {
417
+ const args = process.argv.slice(2);
418
+
419
+ if (args.includes('--help') || args.includes('-h')) {
420
+ printHelp();
306
421
  return;
307
422
  }
308
423
 
309
424
  const cmd = args[0];
310
- console.log('AgentToolkit');
311
- console.log('------------');
312
425
 
313
- if (!cmd || cmd === 'install' && args.length === 1) {
426
+ if (!cmd || (cmd === 'install' && args.length === 1)) {
314
427
  await interactiveInstall('install');
315
428
  return;
316
429
  }
@@ -323,10 +436,8 @@ Agent packs installed to: .claude/agents/<name>/
323
436
  if (cmd === 'install') {
324
437
  const rest = args.slice(1);
325
438
  if (rest[0] === '--all') {
326
- console.log('');
327
439
  installItems(buildMenu());
328
440
  } else {
329
- console.log('');
330
441
  installItems(resolveNames(rest));
331
442
  }
332
443
  return;
@@ -339,10 +450,8 @@ Agent packs installed to: .claude/agents/<name>/
339
450
  return;
340
451
  }
341
452
  if (rest[0] === '--all') {
342
- console.log('');
343
453
  installItems(buildMenu(), { replaceExisting: true });
344
454
  } else {
345
- console.log('');
346
455
  installItems(resolveNames(rest), { replaceExisting: true });
347
456
  }
348
457
  return;
@@ -355,20 +464,18 @@ Agent packs installed to: .claude/agents/<name>/
355
464
  return;
356
465
  }
357
466
  if (rest[0] === '--all') {
358
- console.log('');
359
467
  deleteItems(buildDeleteMenu());
360
468
  } else {
361
- console.log('');
362
469
  deleteItems(resolveDeleteNames(rest));
363
470
  }
364
471
  return;
365
472
  }
366
473
 
367
- console.error(`Unknown command: "${cmd}". Run with --help for usage.`);
474
+ status('error', `Unknown command: "${cmd}". Run with --help for usage.`);
368
475
  process.exit(1);
369
476
  }
370
477
 
371
478
  main().catch(err => {
372
- console.error(err.message);
479
+ status('error', err.message);
373
480
  process.exit(1);
374
481
  });
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "td-ai-tools",
3
- "version": "1.0.8",
3
+ "version": "1.1.2",
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": ">=16"
24
+ "node": ">=20.12"
20
25
  },
21
26
  "keywords": [
22
27
  "claude",