td-ai-tools 1.0.6 → 1.0.8

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
@@ -33,6 +33,12 @@ npx td-ai-tools install
33
33
  npx td-ai-tools install --all
34
34
  npx td-ai-tools install pr-solver
35
35
  npx td-ai-tools install pr-solver horizon-component-library
36
+ npx td-ai-tools update
37
+ npx td-ai-tools update --all
38
+ npx td-ai-tools update pr-solver
39
+ npx td-ai-tools delete
40
+ npx td-ai-tools delete --all
41
+ npx td-ai-tools delete pr-solver
36
42
  ```
37
43
 
38
44
  The installer copies requested items into both agent layouts:
@@ -41,6 +47,10 @@ The installer copies requested items into both agent layouts:
41
47
 
42
48
  This keeps the installed assets available to both Claude-style and `.agents`-style project conventions.
43
49
 
50
+ `install` now errors when the target item already exists. Use `update` to replace an existing installed skill or agent pack.
51
+
52
+ `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.
53
+
44
54
  ## Local Verification
45
55
  Run the local smoke test to package the repo and verify installation into a throwaway project:
46
56
 
package/bin/cli.js CHANGED
@@ -44,7 +44,7 @@ function readFrontmatterField(mdPath, field) {
44
44
  return match ? match[1].trim() : '';
45
45
  }
46
46
 
47
- function installSkill(name) {
47
+ function installSkill(name, { replaceExisting = false } = {}) {
48
48
  const src = path.join(SKILLS_DIR, name);
49
49
  if (!fs.existsSync(src)) {
50
50
  console.error(` [error] Skill "${name}" not found.`);
@@ -52,13 +52,38 @@ function installSkill(name) {
52
52
  }
53
53
  for (const target of INSTALL_TARGETS) {
54
54
  const dest = path.join(TARGET_ROOT, target.root, 'skills', name);
55
+ if (fs.existsSync(dest) && !replaceExisting) {
56
+ console.error(` [error] Skill "${name}" already exists at ${target.root}/skills/${name}/ (use "update" to replace).`);
57
+ return false;
58
+ }
59
+ if (fs.existsSync(dest) && replaceExisting) {
60
+ fs.rmSync(dest, { recursive: true, force: true });
61
+ }
55
62
  copyDir(src, dest);
56
- console.log(` [skill] ${name} -> ${target.root}/skills/${name}/`);
63
+ const action = replaceExisting ? 'updated' : 'installed';
64
+ console.log(` [skill] ${name} ${action} -> ${target.root}/skills/${name}/`);
65
+ }
66
+ return true;
67
+ }
68
+
69
+ function deleteSkill(name) {
70
+ let deletedAny = false;
71
+ for (const target of INSTALL_TARGETS) {
72
+ const dest = path.join(TARGET_ROOT, target.root, 'skills', name);
73
+ if (fs.existsSync(dest)) {
74
+ fs.rmSync(dest, { recursive: true, force: true });
75
+ console.log(` [skill] ${name} deleted from ${target.root}/skills/${name}/`);
76
+ deletedAny = true;
77
+ }
78
+ }
79
+ if (!deletedAny) {
80
+ console.error(` [error] Skill "${name}" is not installed.`);
81
+ return false;
57
82
  }
58
83
  return true;
59
84
  }
60
85
 
61
- function installAgent(name) {
86
+ function installAgent(name, { replaceExisting = false } = {}) {
62
87
  const src = path.join(AGENTS_DIR, name);
63
88
  if (!fs.existsSync(src)) {
64
89
  console.error(` [error] Agent pack "${name}" not found.`);
@@ -66,12 +91,52 @@ function installAgent(name) {
66
91
  }
67
92
  for (const target of INSTALL_TARGETS) {
68
93
  const dest = path.join(TARGET_ROOT, target.root, 'agents', name);
94
+ if (fs.existsSync(dest) && !replaceExisting) {
95
+ console.error(` [error] Agent pack "${name}" already exists at ${target.root}/agents/${name}/ (use "update" to replace).`);
96
+ return false;
97
+ }
98
+ if (fs.existsSync(dest) && replaceExisting) {
99
+ fs.rmSync(dest, { recursive: true, force: true });
100
+ }
69
101
  copyDir(src, dest);
70
- console.log(` [agent] ${name} -> ${target.root}/agents/${name}/`);
102
+ const action = replaceExisting ? 'updated' : 'installed';
103
+ console.log(` [agent] ${name} ${action} -> ${target.root}/agents/${name}/`);
71
104
  }
72
105
  return true;
73
106
  }
74
107
 
108
+ function deleteAgent(name) {
109
+ let deletedAny = false;
110
+ for (const target of INSTALL_TARGETS) {
111
+ const dest = path.join(TARGET_ROOT, target.root, 'agents', name);
112
+ if (fs.existsSync(dest)) {
113
+ fs.rmSync(dest, { recursive: true, force: true });
114
+ console.log(` [agent] ${name} deleted from ${target.root}/agents/${name}/`);
115
+ deletedAny = true;
116
+ }
117
+ }
118
+ if (!deletedAny) {
119
+ console.error(` [error] Agent pack "${name}" is not installed.`);
120
+ return false;
121
+ }
122
+ return true;
123
+ }
124
+
125
+ function getInstalled(type) {
126
+ const installed = new Set();
127
+ for (const target of INSTALL_TARGETS) {
128
+ const dir = path.join(TARGET_ROOT, target.root, type);
129
+ if (fs.existsSync(dir)) {
130
+ for (const entry of fs.readdirSync(dir)) {
131
+ if (fs.statSync(path.join(dir, entry)).isDirectory()) {
132
+ installed.add(entry);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ return [...installed].sort();
138
+ }
139
+
75
140
  function printList() {
76
141
  const skills = getAvailable(SKILLS_DIR);
77
142
  const agents = getAvailable(AGENTS_DIR);
@@ -97,14 +162,27 @@ function buildMenu() {
97
162
  return [...skills, ...agents];
98
163
  }
99
164
 
100
- function installItems(items) {
165
+ function buildDeleteMenu() {
166
+ const skills = getInstalled('skills').map(name => ({ type: 'skill', name }));
167
+ const agents = getInstalled('agents').map(name => ({ type: 'agent', name }));
168
+ return [...skills, ...agents];
169
+ }
170
+
171
+ function installItems(items, options = {}) {
172
+ for (const item of items) {
173
+ if (item.type === 'skill') installSkill(item.name, options);
174
+ else installAgent(item.name, options);
175
+ }
176
+ }
177
+
178
+ function deleteItems(items) {
101
179
  for (const item of items) {
102
- if (item.type === 'skill') installSkill(item.name);
103
- else installAgent(item.name);
180
+ if (item.type === 'skill') deleteSkill(item.name);
181
+ else deleteAgent(item.name);
104
182
  }
105
183
  }
106
184
 
107
- async function interactiveInstall() {
185
+ async function interactiveInstall(mode = 'install') {
108
186
  const menu = buildMenu();
109
187
  if (menu.length === 0) {
110
188
  console.log('No skills or agent packs available.');
@@ -120,7 +198,40 @@ async function interactiveInstall() {
120
198
 
121
199
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
122
200
  return new Promise(resolve => {
123
- rl.question('Select items to install: ', answer => {
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
+ });
215
+ });
216
+ }
217
+
218
+ async function interactiveDelete() {
219
+ const menu = buildDeleteMenu();
220
+ if (menu.length === 0) {
221
+ console.log('No installed skills or agent packs to delete.');
222
+ return;
223
+ }
224
+
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}`);
229
+ });
230
+ console.log('');
231
+
232
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
233
+ return new Promise(resolve => {
234
+ rl.question('Select items to delete: ', answer => {
124
235
  rl.close();
125
236
  const trimmed = answer.trim().toLowerCase();
126
237
  let selected;
@@ -131,7 +242,7 @@ async function interactiveInstall() {
131
242
  selected = indices.filter(i => i >= 0 && i < menu.length).map(i => menu[i]);
132
243
  }
133
244
  console.log('');
134
- installItems(selected);
245
+ deleteItems(selected);
135
246
  resolve();
136
247
  });
137
248
  });
@@ -153,6 +264,22 @@ function resolveNames(names) {
153
264
  return items;
154
265
  }
155
266
 
267
+ function resolveDeleteNames(names) {
268
+ const installedSkills = getInstalled('skills');
269
+ const installedAgents = getInstalled('agents');
270
+ const items = [];
271
+ for (const name of names) {
272
+ if (installedSkills.includes(name)) {
273
+ items.push({ type: 'skill', name });
274
+ } else if (installedAgents.includes(name)) {
275
+ items.push({ type: 'agent', name });
276
+ } else {
277
+ console.error(` [error] "${name}" is not installed.`);
278
+ }
279
+ }
280
+ return items;
281
+ }
282
+
156
283
  async function main() {
157
284
  const args = process.argv.slice(2);
158
285
 
@@ -164,6 +291,12 @@ Usage:
164
291
  npx td-ai-tools install Interactive install
165
292
  npx td-ai-tools install --all Install everything
166
293
  npx td-ai-tools install <name...> Install specific skills or agent packs
294
+ npx td-ai-tools update Interactive update (replaces existing items)
295
+ npx td-ai-tools update --all Update everything
296
+ npx td-ai-tools update <name...> Update specific skills or agent packs
297
+ npx td-ai-tools delete Interactive delete
298
+ npx td-ai-tools delete --all Delete everything
299
+ npx td-ai-tools delete <name...> Delete specific skills or agent packs
167
300
 
168
301
  Skills are installed to: .claude/skills/<name>/
169
302
  .agents/skills/<name>/
@@ -178,7 +311,7 @@ Agent packs installed to: .claude/agents/<name>/
178
311
  console.log('------------');
179
312
 
180
313
  if (!cmd || cmd === 'install' && args.length === 1) {
181
- await interactiveInstall();
314
+ await interactiveInstall('install');
182
315
  return;
183
316
  }
184
317
 
@@ -199,6 +332,38 @@ Agent packs installed to: .claude/agents/<name>/
199
332
  return;
200
333
  }
201
334
 
335
+ if (cmd === 'update') {
336
+ const rest = args.slice(1);
337
+ if (rest.length === 0) {
338
+ await interactiveInstall('update');
339
+ return;
340
+ }
341
+ if (rest[0] === '--all') {
342
+ console.log('');
343
+ installItems(buildMenu(), { replaceExisting: true });
344
+ } else {
345
+ console.log('');
346
+ installItems(resolveNames(rest), { replaceExisting: true });
347
+ }
348
+ return;
349
+ }
350
+
351
+ if (cmd === 'delete') {
352
+ const rest = args.slice(1);
353
+ if (rest.length === 0) {
354
+ await interactiveDelete();
355
+ return;
356
+ }
357
+ if (rest[0] === '--all') {
358
+ console.log('');
359
+ deleteItems(buildDeleteMenu());
360
+ } else {
361
+ console.log('');
362
+ deleteItems(resolveDeleteNames(rest));
363
+ }
364
+ return;
365
+ }
366
+
202
367
  console.error(`Unknown command: "${cmd}". Run with --help for usage.`);
203
368
  process.exit(1);
204
369
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "td-ai-tools",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Install agent skills and packs into your project",
5
5
  "scripts": {
6
6
  "smoke:install": "./scripts/smoke-install.sh"
@@ -8,7 +8,7 @@ machine or one project clone location.
8
8
 
9
9
  - `config.toml.template` — the source of truth. Uses two placeholders:
10
10
  - `__PROJECT_ROOT__` — replaced with the absolute path to the repo root
11
- - `__CHROME_PATH__` — replaced with the absolute path to the Chrome/Chromium binary
11
+ - `__BROWSER_PATH__` — replaced with the absolute path to the Chromium/Chrome binary
12
12
  - `setup-codex.sh` — reads the template, substitutes the placeholders, writes
13
13
  `<repo>/.codex/config.toml`.
14
14
 
@@ -27,7 +27,7 @@ The script prints the values it resolved, e.g.:
27
27
  ```
28
28
  wrote /home/you/code/lio/.codex/config.toml
29
29
  PROJECT_ROOT=/home/you/code/lio
30
- CHROME_PATH=/usr/bin/google-chrome
30
+ BROWSER_PATH=/usr/bin/chromium
31
31
  ```
32
32
 
33
33
  Re-run it any time the template changes or you move the repo.
@@ -41,17 +41,19 @@ Re-run it any time the template changes or you move the repo.
41
41
  2. Falls back to walking three directories up from the script
42
42
  (`.agents/skills/playwright-cli` → repo root) if the repo isn't a git checkout
43
43
 
44
- **`CHROME_PATH`** — derived in this order:
44
+ **`BROWSER_PATH`** — derived in this order:
45
45
 
46
- 1. The `CHROME_PATH` environment variable, if set
47
- 2. First match from `PATH` of: `google-chrome`, `google-chrome-stable`, `chromium`,
48
- `chromium-browser`
49
- 3. Falls back to `/usr/bin/google-chrome` with a warning if nothing is found
46
+ 1. The `BROWSER_PATH` environment variable, if set
47
+ 2. The legacy `CHROME_PATH` environment variable, if set (kept for backwards compat)
48
+ 3. First match from `PATH` of: `chromium`, `chromium-browser`, `google-chrome`,
49
+ `google-chrome-stable` Chromium is preferred because Chrome is often missing
50
+ on WSL shells
51
+ 4. Falls back to `/usr/bin/chromium` with a warning if nothing is found
50
52
 
51
- Override the Chrome binary explicitly when needed:
53
+ Override the browser binary explicitly when needed:
52
54
 
53
55
  ```bash
54
- CHROME_PATH=/opt/google/chrome/chrome .agents/skills/playwright-cli/setup-codex.sh
56
+ BROWSER_PATH=/usr/bin/chromium .agents/skills/playwright-cli/setup-codex.sh
55
57
  ```
56
58
 
57
59
  ## Why a generator instead of variable expansion
@@ -158,6 +158,7 @@ playwright-cli video-stop video.webm
158
158
  ## Open parameters
159
159
  ```bash
160
160
  # Use specific browser when creating session
161
+ playwright-cli open --browser=chromium
161
162
  playwright-cli open --browser=chrome
162
163
  playwright-cli open --browser=firefox
163
164
  playwright-cli open --browser=webkit
@@ -19,10 +19,10 @@ inherit = "all"
19
19
 
20
20
  [shell_environment_policy.set]
21
21
  PLAYWRIGHT_BROWSERS_PATH = "__PROJECT_ROOT__/.artifacts/playwright/ms-playwright"
22
- PLAYWRIGHT_MCP_BROWSER = "chrome"
23
- PLAYWRIGHT_MCP_EXECUTABLE_PATH = "__CHROME_PATH__"
24
- PLAYWRIGHT_DAEMON_SOCKETS_DIR = "__PROJECT_ROOT__/.pw"
25
- BROWSER = "google-chrome"
22
+ PLAYWRIGHT_MCP_BROWSER = "chromium"
23
+ PLAYWRIGHT_MCP_EXECUTABLE_PATH = "__BROWSER_PATH__"
24
+ PLAYWRIGHT_DAEMON_SOCKETS_DIR = "__PROJECT_ROOT__/.tmp/codex-playwright/sockets"
25
+ BROWSER = "chromium"
26
26
  HOME = "__PROJECT_ROOT__/.tmp/codex-playwright/home"
27
27
  XDG_CONFIG_HOME = "__PROJECT_ROOT__/.tmp/codex-playwright/config"
28
28
  XDG_STATE_HOME = "__PROJECT_ROOT__/.tmp/codex-playwright/state"
@@ -11,19 +11,19 @@ else
11
11
  fi
12
12
  OUTPUT="$PROJECT_ROOT/.codex/config.toml"
13
13
 
14
- CHROME_PATH="${CHROME_PATH:-$(command -v google-chrome || command -v google-chrome-stable || command -v chromium || command -v chromium-browser || true)}"
15
- if [ -z "$CHROME_PATH" ]; then
16
- echo "warning: no Chrome/Chromium binary found on PATH; set CHROME_PATH=/path/to/chrome and rerun" >&2
17
- CHROME_PATH="/usr/bin/google-chrome"
14
+ BROWSER_PATH="${BROWSER_PATH:-${CHROME_PATH:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}}"
15
+ if [ -z "$BROWSER_PATH" ]; then
16
+ echo "warning: no Chromium/Chrome binary found on PATH; set BROWSER_PATH=/path/to/browser and rerun" >&2
17
+ BROWSER_PATH="/usr/bin/chromium"
18
18
  fi
19
19
 
20
20
  mkdir -p "$(dirname "$OUTPUT")"
21
21
 
22
22
  sed \
23
23
  -e "s|__PROJECT_ROOT__|$PROJECT_ROOT|g" \
24
- -e "s|__CHROME_PATH__|$CHROME_PATH|g" \
24
+ -e "s|__BROWSER_PATH__|$BROWSER_PATH|g" \
25
25
  "$TEMPLATE" > "$OUTPUT"
26
26
 
27
27
  echo "wrote $OUTPUT"
28
28
  echo " PROJECT_ROOT=$PROJECT_ROOT"
29
- echo " CHROME_PATH=$CHROME_PATH"
29
+ echo " BROWSER_PATH=$BROWSER_PATH"