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.
Files changed (2) hide show
  1. package/bin/cli.js +176 -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,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
- console.log('No skills or agent packs available.');
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
- 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('');
277
+ p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
198
278
 
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
- });
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
- console.log('No installed skills or agent packs to delete.');
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
- 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}`);
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
- 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
- });
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
- console.error(` [error] "${name}" not found as a skill or agent pack.`);
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
- console.error(` [error] "${name}" is not installed.`);
369
+ status('error', `"${name}" is not installed.`);
278
370
  }
279
371
  }
280
372
  return items;
281
373
  }
282
374
 
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:
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
- .agents/skills/<name>/
389
+ .agents/skills/<name>/
303
390
  Agent packs installed to: .claude/agents/<name>/
304
- .agents/agents/<name>/
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
- console.error(`Unknown command: "${cmd}". Run with --help for usage.`);
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
- console.error(err.message);
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.0.8",
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": ">=16"
24
+ "node": ">=20.12"
20
25
  },
21
26
  "keywords": [
22
27
  "claude",