td-ai-tools 1.1.4 → 1.1.6

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 (32) hide show
  1. package/README.md +10 -0
  2. package/agents/component-library-section-blocks/AGENTS.md +4 -0
  3. package/agents/horizon-component-library/AGENTS.md +4 -0
  4. package/bin/cli.js +183 -17
  5. package/package.json +1 -1
  6. package/scripts/smoke-install.sh +5 -1
  7. package/skills/README.md +7 -1
  8. package/skills/basecamp/SKILL.md +1 -0
  9. package/skills/cache-reset/SKILL.md +1 -0
  10. package/skills/car-ticket-generator/SKILL.md +1 -0
  11. package/skills/everhour-basecamp-estimates/SKILL.md +1 -0
  12. package/skills/forge-cli/SKILL.md +1 -0
  13. package/skills/horizon-component-migration/SKILL.md +1 -0
  14. package/skills/playwright-cli/PRIMER.md +122 -0
  15. package/skills/playwright-cli/SKILL.md +20 -1
  16. package/skills/playwright-cli/agents/playwright-investigator.md +65 -0
  17. package/skills/playwright-cli/config.toml.template +10 -16
  18. package/skills/playwright-cli/setup.sh +109 -0
  19. package/skills/pr-solver/SKILL.md +1 -0
  20. package/skills/pull-request/SKILL.md +1 -0
  21. package/skills/pull-request-statamic/SKILL.md +1 -0
  22. package/skills/record-changes/SKILL.md +1 -0
  23. package/skills/stylesheet-migration/SKILL.md +1 -0
  24. package/skills/td-js-vanilla-rules/SKILL.md +1 -0
  25. package/skills/td-review/SKILL.md +2 -1
  26. package/skills/barrage/SKILL.md +0 -180
  27. package/skills/barrage/agents/openai.yaml +0 -4
  28. package/skills/barrage/scripts/__pycache__/build_queue.cpython-312.pyc +0 -0
  29. package/skills/barrage/scripts/build_queue.py +0 -276
  30. package/skills/barrage/tests/test_build_queue.py +0 -135
  31. package/skills/playwright-cli/CODEX-CONFIG.md +0 -81
  32. package/skills/playwright-cli/setup-codex.sh +0 -29
package/README.md CHANGED
@@ -45,12 +45,22 @@ The installer copies requested items into both agent layouts:
45
45
 
46
46
  - Skills: `.claude/skills/<name>/` and `.agents/skills/<name>/`
47
47
 
48
+ If a skill bundles a sub-agent prompt (any `.md` with a `name:` field in `skills/<name>/agents/`), the installer also registers it to `.claude/agents/<sub-agent>.md` and `.agents/agents/<sub-agent>.md` so Claude Code and `.agents`-aware runtimes such as Codex can discover the same definition. `delete` removes these registrations alongside the skill.
49
+
48
50
  This keeps the installed assets available to both Claude-style and `.agents`-style project conventions.
49
51
 
50
52
  `install` now errors when the target item already exists. Use `update` to replace an existing installed skill or agent pack.
51
53
 
52
54
  `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
55
 
56
+ ## Versioning
57
+
58
+ Every skill and agent pack carries a `version:` field in its frontmatter (`SKILL.md` / `AGENTS.md`), following semantic versioning (`MAJOR.MINOR.PATCH`). Because the installer copies these files verbatim into the target project, the installed copy itself records the version that was installed — no lockfile is needed.
59
+
60
+ `list` and the interactive `update` menu compare the installed version against the catalog and flag anything out of date, e.g. `v1.0.0 → v1.2.0 ⬆ outdated`. When any installed item is stale, the CLI prints a nudge: `You have N items out of date — run npx td-ai-tools update`. An item installed before versioning existed (no `version:` field) is also treated as out of date.
61
+
62
+ **Maintainers:** bump an item's `version:` whenever you change its contents — `PATCH` for fixes/tweaks, `MINOR` for new behavior, `MAJOR` for breaking changes. Without a bump, users will not see the update flagged.
63
+
54
64
  ## Local Verification
55
65
  Run the local smoke test to package the repo and verify installation into a throwaway project:
56
66
 
@@ -1,3 +1,7 @@
1
+ ---
2
+ version: 1.0.0
3
+ ---
4
+
1
5
  # .codex/AGENTS.md — Theory Digital
2
6
 
3
7
  ## Scope
@@ -1,3 +1,7 @@
1
+ ---
2
+ version: 1.0.0
3
+ ---
4
+
1
5
  # .codex/AGENTS.md — Theory Digital
2
6
 
3
7
  ## Scope
package/bin/cli.js CHANGED
@@ -63,6 +63,121 @@ function readFrontmatterField(mdPath, field) {
63
63
  return match ? match[1].trim() : '';
64
64
  }
65
65
 
66
+ function itemFile(type) {
67
+ return type === 'skill' ? 'SKILL.md' : 'AGENTS.md';
68
+ }
69
+
70
+ function itemSubdir(type) {
71
+ return type === 'skill' ? 'skills' : 'agents';
72
+ }
73
+
74
+ function catalogVersion(type, name) {
75
+ const dir = type === 'skill' ? SKILLS_DIR : AGENTS_DIR;
76
+ return readFrontmatterField(path.join(dir, name, itemFile(type)), 'version') || null;
77
+ }
78
+
79
+ // Reads the version recorded in the user's installed copy. The installer copies
80
+ // SKILL.md/AGENTS.md verbatim, so the installed file is itself the record of what
81
+ // version was installed — no separate lockfile is needed. Returns null when the
82
+ // item is not installed, or when it predates versioning (no version: field).
83
+ function installedVersion(type, name) {
84
+ const sub = itemSubdir(type);
85
+ const file = itemFile(type);
86
+ for (const target of INSTALL_TARGETS) {
87
+ const md = path.join(TARGET_ROOT, target.root, sub, name, file);
88
+ if (fs.existsSync(md)) {
89
+ return readFrontmatterField(md, 'version') || null;
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+
95
+ function compareSemver(a, b) {
96
+ const pa = String(a).split('.').map(n => parseInt(n, 10) || 0);
97
+ const pb = String(b).split('.').map(n => parseInt(n, 10) || 0);
98
+ const len = Math.max(pa.length, pb.length);
99
+ for (let i = 0; i < len; i++) {
100
+ const x = pa[i] || 0;
101
+ const y = pb[i] || 0;
102
+ if (x > y) return 1;
103
+ if (x < y) return -1;
104
+ }
105
+ return 0;
106
+ }
107
+
108
+ // An installed item is outdated when its version is older than the catalog's, or
109
+ // when it has no version: field at all (a legacy install from before versioning).
110
+ function isInstalled(type, name) {
111
+ const sub = itemSubdir(type);
112
+ return INSTALL_TARGETS.some(target =>
113
+ fs.existsSync(path.join(TARGET_ROOT, target.root, sub, name)));
114
+ }
115
+
116
+ function itemStatus(type, name) {
117
+ const available = catalogVersion(type, name);
118
+ const installed = installedVersion(type, name);
119
+ let outdated = false;
120
+ if (available && isInstalled(type, name)) {
121
+ outdated = !installed || compareSemver(installed, available) < 0;
122
+ }
123
+ return { installed, available, outdated };
124
+ }
125
+
126
+ // Display string for an installed item's version state, e.g.
127
+ // "v1.0.0 → v1.2.0 ⬆ outdated" or "v1.2.0 (up to date)". Returns null when there
128
+ // is nothing meaningful to show (item not installed, or catalog has no version).
129
+ function versionAnnotation(type, name) {
130
+ if (!isInstalled(type, name)) return null;
131
+ const { installed, available, outdated } = itemStatus(type, name);
132
+ if (!available) return null;
133
+ if (outdated) {
134
+ const from = installed ? `v${installed}` : 'no version';
135
+ return { text: `${from} → v${available} ⬆ outdated`, outdated: true };
136
+ }
137
+ return { text: `v${installed} (up to date)`, outdated: false };
138
+ }
139
+
140
+ function getOutdatedInstalled() {
141
+ const availableSkills = new Set(getAvailable(SKILLS_DIR));
142
+ const availableAgents = new Set(getAvailable(AGENTS_DIR));
143
+ const out = [];
144
+ for (const name of getInstalled('skills')) {
145
+ if (availableSkills.has(name) && itemStatus('skill', name).outdated) {
146
+ out.push({ type: 'skill', name });
147
+ }
148
+ }
149
+ for (const name of getInstalled('agents')) {
150
+ if (availableAgents.has(name) && itemStatus('agent', name).outdated) {
151
+ out.push({ type: 'agent', name });
152
+ }
153
+ }
154
+ return out;
155
+ }
156
+
157
+ function printOutdatedNudge() {
158
+ const outdated = getOutdatedInstalled();
159
+ if (outdated.length === 0) return;
160
+ const n = outdated.length;
161
+ status('warn',
162
+ `You have ${n} item${n === 1 ? '' : 's'} out of date — run \`npx td-ai-tools update\` to update ${n === 1 ? 'it' : 'them'}.`);
163
+ }
164
+
165
+ // Sub-agent prompts bundled inside a skill: any `*.md` in <skill>/agents/ that
166
+ // declares a `name:` frontmatter field. These are registered to every supported
167
+ // target's agents directory so Claude Code and shared `.agents` runtimes can
168
+ // discover the same definition on install.
169
+ function getBundledAgents(skillSrc) {
170
+ const agentsDir = path.join(skillSrc, 'agents');
171
+ if (!fs.existsSync(agentsDir)) return [];
172
+ return fs.readdirSync(agentsDir)
173
+ .filter(f => f.endsWith('.md'))
174
+ .map(f => {
175
+ const srcPath = path.join(agentsDir, f);
176
+ return { file: f, srcPath, name: readFrontmatterField(srcPath, 'name') };
177
+ })
178
+ .filter(a => a.name);
179
+ }
180
+
66
181
  function installSkill(name, { replaceExisting = false } = {}) {
67
182
  const src = path.join(SKILLS_DIR, name);
68
183
  if (!fs.existsSync(src)) {
@@ -81,12 +196,22 @@ function installSkill(name, { replaceExisting = false } = {}) {
81
196
  copyDir(src, dest);
82
197
  const action = replaceExisting ? 'updated' : 'installed';
83
198
  status('success', `skill: ${name} ${action} → ${target.root}/skills/${name}/`);
199
+
200
+ for (const agent of getBundledAgents(src)) {
201
+ const agentDest = path.join(TARGET_ROOT, target.root, 'agents', agent.file);
202
+ fs.mkdirSync(path.dirname(agentDest), { recursive: true });
203
+ fs.copyFileSync(agent.srcPath, agentDest);
204
+ status('success', ` ↳ sub-agent: ${agent.name} registered → ${target.root}/agents/${agent.file}`);
205
+ }
84
206
  }
85
207
  return true;
86
208
  }
87
209
 
88
210
  function deleteSkill(name) {
89
211
  let deletedAny = false;
212
+ // Determine bundled sub-agents from the catalog source (best effort — only
213
+ // available if the skill still exists in the package).
214
+ const bundledAgents = getBundledAgents(path.join(SKILLS_DIR, name));
90
215
  for (const target of INSTALL_TARGETS) {
91
216
  const dest = path.join(TARGET_ROOT, target.root, 'skills', name);
92
217
  if (fs.existsSync(dest)) {
@@ -94,6 +219,14 @@ function deleteSkill(name) {
94
219
  status('success', `skill: ${name} deleted from ${target.root}/skills/${name}/`);
95
220
  deletedAny = true;
96
221
  }
222
+ for (const agent of bundledAgents) {
223
+ const agentDest = path.join(TARGET_ROOT, target.root, 'agents', agent.file);
224
+ if (fs.existsSync(agentDest)) {
225
+ fs.rmSync(agentDest, { force: true });
226
+ status('success', ` ↳ sub-agent: ${agent.name} unregistered from ${target.root}/agents/${agent.file}`);
227
+ deletedAny = true;
228
+ }
229
+ }
97
230
  }
98
231
  if (!deletedAny) {
99
232
  status('error', `Skill "${name}" is not installed.`);
@@ -172,17 +305,25 @@ function printListPlain() {
172
305
 
173
306
  console.log('\nSkills:');
174
307
  for (const name of skills) {
175
- const desc = readFrontmatterField(path.join(SKILLS_DIR, name, 'SKILL.md'), 'description');
176
- const truncated = desc.length > 72 ? desc.slice(0, 72) + '...' : desc;
177
308
  console.log(` ${name}`);
178
- if (truncated) console.log(` ${truncated}`);
309
+ const ann = versionAnnotation('skill', name);
310
+ if (ann) {
311
+ console.log(` ${ann.text}`);
312
+ } else {
313
+ const desc = readFrontmatterField(path.join(SKILLS_DIR, name, 'SKILL.md'), 'description');
314
+ const truncated = desc.length > 72 ? desc.slice(0, 72) + '...' : desc;
315
+ if (truncated) console.log(` ${truncated}`);
316
+ }
179
317
  }
180
318
 
181
319
  console.log('\nAgent Packs:');
182
320
  for (const name of agents) {
183
321
  console.log(` ${name}`);
322
+ const ann = versionAnnotation('agent', name);
323
+ if (ann) console.log(` ${ann.text}`);
184
324
  }
185
325
  console.log('');
326
+ printOutdatedNudge();
186
327
  }
187
328
 
188
329
  function printList() {
@@ -196,22 +337,30 @@ function printList() {
196
337
 
197
338
  p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
198
339
 
340
+ const lineFor = (type, name, fallbackHint) => {
341
+ const ann = versionAnnotation(type, name);
342
+ if (ann) {
343
+ const colored = ann.outdated ? pc.yellow(ann.text) : pc.dim(ann.text);
344
+ return `${pc.bold(name)}\n ${colored}`;
345
+ }
346
+ return fallbackHint ? `${pc.bold(name)}\n ${pc.dim(fallbackHint)}` : pc.bold(name);
347
+ };
348
+
199
349
  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')
350
+ ? skills.map(name => lineFor('skill', name, getSkillHint(name))).join('\n')
204
351
  : pc.dim('(none)');
205
352
  p.note(skillsBody, `Skills (${skills.length})`);
206
353
 
207
354
  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')
355
+ ? agents.map(name => lineFor('agent', name, getAgentHint(name))).join('\n')
212
356
  : pc.dim('(none)');
213
357
  p.note(agentsBody, `Agent Packs (${agents.length})`);
214
358
 
359
+ const outdated = getOutdatedInstalled();
360
+ if (outdated.length) {
361
+ p.log.warn(pc.yellow(
362
+ `${outdated.length} installed item${outdated.length === 1 ? '' : 's'} out of date — run \`npx td-ai-tools update\`.`));
363
+ }
215
364
  p.outro(pc.dim(`${skills.length} skills, ${agents.length} agent packs available.`));
216
365
  }
217
366
 
@@ -227,8 +376,14 @@ function printSelectionControls(action) {
227
376
  }
228
377
 
229
378
  function buildMenu() {
230
- const skills = getAvailable(SKILLS_DIR).map(name => ({ type: 'skill', name }));
231
- const agents = getAvailable(AGENTS_DIR).map(name => ({ type: 'agent', name }));
379
+ const installedSkills = new Set(getInstalled('skills'));
380
+ const installedAgents = new Set(getInstalled('agents'));
381
+ const skills = getAvailable(SKILLS_DIR)
382
+ .filter(name => !installedSkills.has(name))
383
+ .map(name => ({ type: 'skill', name }));
384
+ const agents = getAvailable(AGENTS_DIR)
385
+ .filter(name => !installedAgents.has(name))
386
+ .map(name => ({ type: 'agent', name }));
232
387
  return [...skills, ...agents];
233
388
  }
234
389
 
@@ -250,13 +405,23 @@ function buildUpdateMenu() {
250
405
  return [...skills, ...agents];
251
406
  }
252
407
 
253
- function toGroupOptions(menu, { hintForInstalled = false } = {}) {
408
+ function menuHint(type, name, { hintForInstalled, showVersions }) {
409
+ if (showVersions) {
410
+ const ann = versionAnnotation(type, name);
411
+ return ann ? ann.text : '';
412
+ }
413
+ if (hintForInstalled) return '';
414
+ return type === 'skill' ? getSkillHint(name) : getAgentHint(name);
415
+ }
416
+
417
+ function toGroupOptions(menu, { hintForInstalled = false, showVersions = false } = {}) {
418
+ const opts = { hintForInstalled, showVersions };
254
419
  const skillItems = menu.filter(i => i.type === 'skill').map(i => {
255
- const hint = hintForInstalled ? '' : getSkillHint(i.name);
420
+ const hint = menuHint('skill', i.name, opts);
256
421
  return { value: `skill:${i.name}`, label: i.name, hint: hint || undefined };
257
422
  });
258
423
  const agentItems = menu.filter(i => i.type === 'agent').map(i => {
259
- const hint = hintForInstalled ? '' : getAgentHint(i.name);
424
+ const hint = menuHint('agent', i.name, opts);
260
425
  return { value: `agent:${i.name}`, label: i.name, hint: hint || undefined };
261
426
  });
262
427
  const out = {};
@@ -301,10 +466,11 @@ async function interactiveInstall(mode = 'install') {
301
466
 
302
467
  p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
303
468
  printSelectionControls(mode);
469
+ printOutdatedNudge();
304
470
 
305
471
  const selection = await p.groupMultiselect({
306
472
  message: `Select items to ${mode}`,
307
- options: toGroupOptions(menu),
473
+ options: toGroupOptions(menu, { showVersions: mode === 'update' }),
308
474
  required: false,
309
475
  });
310
476
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "td-ai-tools",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Install agent skills and packs into your project",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,12 +21,16 @@ popd >/dev/null
21
21
  tarball_path="$tmpdir/$tarball"
22
22
 
23
23
  pushd "$project_dir" >/dev/null
24
- npm exec --yes --cache "$cache_dir" --package "$tarball_path" td-ai-tools install pr-solver horizon-component-library
24
+ npm exec --yes --cache "$cache_dir" --package "$tarball_path" td-ai-tools install pr-solver playwright-cli horizon-component-library
25
25
  popd >/dev/null
26
26
 
27
27
  required_paths=(
28
28
  "$project_dir/.claude/skills/pr-solver/SKILL.md"
29
29
  "$project_dir/.agents/skills/pr-solver/SKILL.md"
30
+ "$project_dir/.claude/skills/playwright-cli/SKILL.md"
31
+ "$project_dir/.agents/skills/playwright-cli/SKILL.md"
32
+ "$project_dir/.claude/agents/playwright-investigator.md"
33
+ "$project_dir/.agents/agents/playwright-investigator.md"
30
34
  "$project_dir/.claude/agents/horizon-component-library/AGENTS.md"
31
35
  "$project_dir/.agents/agents/horizon-component-library/AGENTS.md"
32
36
  )
package/skills/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # skills
2
2
 
3
3
  ## Available Skills
4
- - `barrage`: Convert a group of Basecamp todos or cards into a td-barrage queue.json task file.
5
4
  - `cache-reset`: Clear and warm Laravel and Statamic caches after content/template changes.
6
5
  - `horizon-component-migration`: Bundle Horizon components and recursive dependencies into a migration package for another theme.
6
+ - `playwright-cli`: Drive a real browser for web testing, screenshots, and data extraction. Ships the `playwright-investigator` sub-agent for orchestrated URL investigations.
7
7
  - `pr-solver`: Resolve unresolved GitHub PR review threads with GraphQL-driven workflow.
8
8
  - `pull-request`: Generate structured pull request descriptions for Shopify theme work.
9
9
  - `td-js-vanilla-rules`: Theory Digital vanilla JavaScript standards for Shopify themes.
@@ -12,5 +12,11 @@
12
12
  ## Skill Structure Convention
13
13
  - `<skill-name>/SKILL.md`
14
14
  - `<skill-name>/agents/openai.yaml`
15
+ - `<skill-name>/agents/<sub-agent>.md` (optional) — a reusable sub-agent prompt (frontmatter `name`/`description` + system prompt). Any `.md` here with a `name:` field is auto-registered to both `.claude/agents/` and `.agents/agents/` on install, so Claude Code and `.agents`-aware runtimes such as Codex can discover the same definition.
15
16
  - `<skill-name>/scripts/*` (optional)
16
17
  - `<skill-name>/references/*` (optional)
18
+
19
+ ## Versioning
20
+ - Each `SKILL.md` frontmatter must include a `version:` field using semantic versioning (`MAJOR.MINOR.PATCH`).
21
+ - Bump the version whenever the skill's contents change — `PATCH` for fixes/tweaks, `MINOR` for new behavior, `MAJOR` for breaking changes.
22
+ - The CLI compares the installed version against this catalog version to flag outdated installs in `list` and `update`. If you do not bump the version, users will not be told to update.
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: basecamp
3
+ version: 1.0.0
3
4
  description: |
4
5
  Interact with Basecamp via the Basecamp CLI. Full API coverage: projects, todos, cards,
5
6
  messages, files, schedule, check-ins, timeline, recordings, templates, webhooks,
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: cache-reset
3
+ version: 1.0.0
3
4
  description: Clear and warm Laravel and Statamic caches after content or template changes.
4
5
  alwaysApply: false
5
6
  ---
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: car-ticket-generator
3
+ version: 1.0.0
3
4
  description: Generate a ticket for the codex-auto-runner queue
4
5
  alwaysApply: false
5
6
  ---
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: everhour-basecamp-estimates
3
+ version: 1.0.0
3
4
  description: Bulk update Everhour task estimates from a Basecamp todo or todolist URL, then append bracketed hours to the Basecamp todo titles.
4
5
  alwaysApply: false
5
6
  ---
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: forge-cli
3
+ version: 1.0.0
3
4
  description: Manage Laravel Forge servers, sites, and provisioned resources from the terminal with the Laravel Forge CLI, falling back to the Forge HTTP API (Statamic-relevant endpoints documented here) for anything the CLI does not cover. Use when the user wants to inspect Forge state, switch active servers, deploy sites, update environment variables, view logs, run remote commands, use Tinker, manage services like PHP, Nginx, daemons, and databases, or perform Statamic site setup tasks the CLI lacks a verb for (site/SSL/git/worker/scheduler/backup/recipe/composer-auth management).
4
5
  ---
5
6
 
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  name: horizon-component-migration
3
+ version: 1.0.0
3
4
  description: Bundle Shopify Horizon components into a migration package for a different theme, including recursive dependencies across sections, snippets, blocks, assets, templates, and locale strings. Use when asked to port or extract a Horizon component with all required files and setup notes.
4
5
  ---
5
6
 
@@ -0,0 +1,122 @@
1
+ # playwright-cli — first-run primer
2
+
3
+ Read this once per machine/clone before using the `playwright-cli` skill. It
4
+ covers the one gotcha that breaks the first run, how to configure each agent
5
+ runtime, and how to verify everything works. Delete this file and any reference
6
+ to it after following its instructions.
7
+
8
+ ## TL;DR
9
+
10
+ ```bash
11
+ # 1. binary present?
12
+ command -v playwright-cli || npm i -g @playwright/cli
13
+
14
+ # 2. browser present? (Playwright-managed chromium, not branded Chrome)
15
+ ls "${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"/chromium-* >/dev/null 2>&1 \
16
+ || npx playwright install chromium
17
+
18
+ # 3. write per-agent config (choose one)
19
+ .agents/skills/playwright-cli/setup.sh # shared .agents + Claude settings
20
+ .agents/skills/playwright-cli/setup.sh --all # shared .agents + Claude + Codex
21
+
22
+ # 4. smoke test
23
+ PLAYWRIGHT_MCP_BROWSER=chromium playwright-cli open
24
+ playwright-cli goto https://example.com
25
+ playwright-cli eval "document.title" # -> "Example Domain"
26
+ playwright-cli close
27
+ ```
28
+
29
+ ## The one gotcha
30
+
31
+ `playwright-cli open` defaults to **branded Chrome** at
32
+ `/opt/google/chrome/chrome`. On most dev boxes (including WSL) that binary does
33
+ not exist, so the bare command fails with:
34
+
35
+ ```
36
+ Error: Chromium distribution 'chrome' is not found at /opt/google/chrome/chrome
37
+ ```
38
+
39
+ What *is* installed is the **Playwright-managed chromium** under
40
+ `~/.cache/ms-playwright`. Point the default browser at it in one of two ways:
41
+
42
+ - per-command flag: `playwright-cli open --browser=chromium`
43
+ - environment variable: `PLAYWRIGHT_MCP_BROWSER=chromium` (this is what
44
+ `setup.sh` pins for you — `BROWSER=chromium` does **not** work, only
45
+ `PLAYWRIGHT_MCP_BROWSER`).
46
+
47
+ If chromium isn't installed yet: `npx playwright install chromium`. (This repo
48
+ already uses `@playwright/test`, so it's normally present.)
49
+
50
+ ## Per-agent configuration
51
+
52
+ Run `setup.sh` from the repo root. The script is installed into both
53
+ `.agents/skills/playwright-cli/setup.sh` and
54
+ `.claude/skills/playwright-cli/setup.sh`; prefer the `.agents` path when working
55
+ provider-agnostically. With no flags it writes `.claude` + `.agents`; pass
56
+ `--codex` or `--all` to also write the Codex sandbox config.
57
+
58
+ | Target | File written | What it does |
59
+ |---|---|---|
60
+ | `--claude` | `.claude/settings.json` | Sets `env.PLAYWRIGHT_MCP_BROWSER=chromium` and adds `Bash(playwright-cli:*)` to `permissions.allow`. Merges into any existing file (invalid JSON is backed up to `.bak`). Applies on the **next** Claude Code session. |
61
+ | `--agents` | `.agents/playwright-cli.env` | Shareable env file. Load with `set -a; . .agents/playwright-cli.env; set +a`. |
62
+ | `--codex` | `.codex/config.toml` | Codex sandbox: `network_access`, `writable_roots` (browser cache + artifact dirs), and `PLAYWRIGHT_MCP_BROWSER=chromium`. Generated from `config.toml.template`. |
63
+
64
+ All three output dirs (`.claude/`, `.agents/`, `.codex/`) are gitignored, so
65
+ each clone generates its own — nothing machine-specific is committed.
66
+
67
+ ### Why config differs by agent
68
+
69
+ - **Claude Code** reads `.claude/settings.json` (`env` + `permissions`). It does
70
+ not read `.codex/config.toml`. The env var is all it needs; the permission is
71
+ belt-and-suspenders since `SKILL.md` already declares `allowed-tools`.
72
+ - **Codex** parses `config.toml` as static TOML and does **not** expand `~`,
73
+ `$HOME`, or `${VAR}` in paths — hence the generator that substitutes absolute
74
+ paths at setup time.
75
+ - **`.agents/`** is the agent-agnostic home; any tool/shell can source the env
76
+ file.
77
+
78
+ ## Sub-agent registration
79
+
80
+ Installing this skill registers `playwright-investigator.md` in both agent
81
+ layouts:
82
+
83
+ - `.claude/agents/playwright-investigator.md`
84
+ - `.agents/agents/playwright-investigator.md`
85
+
86
+ Claude Code discovers the `.claude` copy as a custom agent. Codex and other
87
+ `.agents`-aware runtimes should use the `.agents` copy as the sub-agent
88
+ definition when a multi-agent/sub-agent tool is available. The prompt is written
89
+ as a shared contract: the agent needs shell command execution, file reads, and
90
+ access to this `playwright-cli` skill; it returns only the final investigation
91
+ report.
92
+
93
+ ## What the old setup got wrong
94
+
95
+ The previous `setup-codex.sh` / template were Codex-only **and** misconfigured
96
+ for this machine:
97
+
98
+ - redirected `PLAYWRIGHT_BROWSERS_PATH` to an empty project dir while the real
99
+ browsers live in `~/.cache/ms-playwright`;
100
+ - pinned `PLAYWRIGHT_MCP_EXECUTABLE_PATH` to `/usr/bin/chromium`, which doesn't
101
+ exist (there is no system chromium binary — it's Playwright-managed);
102
+ - overrode `HOME`, moving Playwright away from its own browser cache;
103
+ - never addressed the actual first-run failure (the `chrome` default).
104
+
105
+ The current setup drops all of that: it keeps the managed browsers where they
106
+ are and only pins the default-browser name.
107
+
108
+ ## Artifacts & cleanup
109
+
110
+ `playwright-cli` writes snapshots and console/network logs to `.playwright-cli/`
111
+ in the repo root (gitignored). Always `playwright-cli close` when done; use
112
+ `playwright-cli list` / `close-all` / `kill-all` to manage stray browsers.
113
+
114
+ ## Troubleshooting
115
+
116
+ | Symptom | Fix |
117
+ |---|---|
118
+ | `'chrome' is not found at /opt/google/chrome/chrome` | Use `--browser=chromium` or set `PLAYWRIGHT_MCP_BROWSER=chromium` (run `setup.sh`). |
119
+ | `Executable doesn't exist … run "playwright install"` | `npx playwright install chromium`. |
120
+ | `command not found: playwright-cli` | `npm i -g @playwright/cli`, or use `npx playwright-cli …`. |
121
+ | Env var ignored in Claude Code | `.claude/settings.json` is read at session start — restart the session, or pass `--browser=chromium` inline this session. |
122
+ | Codex sandbox can't launch browser | Re-run `setup.sh --codex` so `writable_roots` includes the current `~/.cache/ms-playwright`. |
@@ -1,11 +1,19 @@
1
1
  ---
2
2
  name: playwright-cli
3
+ version: 1.1.0
3
4
  description: Automates browser interactions for web testing, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, take screenshots, test web applications, or extract information from web pages.
4
5
  allowed-tools: Bash(playwright-cli:*)
5
6
  ---
6
7
 
7
8
  # Browser Automation with playwright-cli
8
9
 
10
+ > **First run in a new clone/machine?** Read [PRIMER.md](PRIMER.md) and run
11
+ > `.agents/skills/playwright-cli/setup.sh` once (or the `.claude/...` copy if
12
+ > you are using Claude Code only). For Codex, run it with `--codex` or `--all`.
13
+ > Key gotcha: `playwright-cli` defaults to branded Chrome (usually absent) —
14
+ > use `--browser=chromium` or set `PLAYWRIGHT_MCP_BROWSER=chromium`. Delete this
15
+ > comment and the setup and primer files after a successful setup.
16
+
9
17
  ## Quick start
10
18
 
11
19
  ```bash
@@ -268,6 +276,17 @@ playwright-cli tracing-stop
268
276
  playwright-cli close
269
277
  ```
270
278
 
279
+ ## Sub-agent
280
+
281
+ This skill ships a provider-agnostic sub-agent prompt, **`playwright-investigator`** ([agents/playwright-investigator.md](agents/playwright-investigator.md)), for orchestrated browser work. Dispatch it with a URL and an objective — diagnose the source of a problem, confirm a change applied correctly, or compare visual output against a target (screenshot or reference URL). It drives the browser through these commands, then returns a concise findings report, keeping all playwright-cli commands and raw output out of the orchestrator's context.
282
+
283
+ Installing this skill auto-registers the sub-agent in both supported layouts:
284
+
285
+ - `.claude/agents/playwright-investigator.md` for Claude Code custom agents.
286
+ - `.agents/agents/playwright-investigator.md` for Codex and other runtimes that read the shared `.agents` convention.
287
+
288
+ Claude Code can dispatch it through the Agent tool with no manual setup. In Codex, use the available multi-agent/sub-agent tool when the runtime exposes one, pointing it at the `.agents/agents/playwright-investigator.md` definition. If a runtime reads `.agents` assets but does not expose native sub-agent dispatch, use the same file as the investigator's system prompt and keep the final report contract unchanged.
289
+
271
290
  ## Specific tasks
272
291
 
273
292
  * **Request mocking** [references/request-mocking.md](references/request-mocking.md)
@@ -276,4 +295,4 @@ playwright-cli close
276
295
  * **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)
277
296
  * **Test generation** [references/test-generation.md](references/test-generation.md)
278
297
  * **Tracing** [references/tracing.md](references/tracing.md)
279
- * **Video recording** [references/video-recording.md](references/video-recording.md)
298
+ * **Video recording** [references/video-recording.md](references/video-recording.md)
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: playwright-investigator
3
+ description: Drives a browser via the playwright-cli skill to investigate a website on an orchestrator's behalf. Give it a URL and an objective — diagnose the source of a problem, confirm a change applied correctly, or compare visual output against a target (a screenshot file or a reference URL). It performs the browser work, gathers evidence, and returns a concise findings report. Use it to keep all playwright-cli commands and raw browser/snapshot output out of the orchestrator's context.
4
+ tools: Bash, Read, Glob, Grep, Skill
5
+ ---
6
+
7
+ You are a focused web investigation agent. An orchestrator hands you a URL and an objective; you drive a browser with the `playwright-cli` skill, gather the evidence needed to satisfy your objective, and hand back a clean, self-contained report.
8
+
9
+ Your defining responsibility is **context isolation**. The orchestrator must never see playwright-cli commands, snapshot YAML, console dumps, or network logs. All of that lives and dies inside your context. The orchestrator receives only your final report.
10
+
11
+ ## Runtime Contract
12
+
13
+ This definition is intentionally usable in both Claude Code's `.claude/agents/`
14
+ layout and the shared `.agents/agents/` layout used by Codex-compatible
15
+ runtimes. You need these capabilities, regardless of provider-specific tool
16
+ names:
17
+
18
+ - Run shell commands.
19
+ - Read local files and generated artifacts.
20
+ - Load or follow the `playwright-cli` skill documentation.
21
+
22
+ The `tools:` frontmatter is Claude Code's native declaration; other runtimes
23
+ should map it to the equivalent capabilities above.
24
+
25
+ If your runtime does not expose a native skill-loading tool, read the installed
26
+ `playwright-cli/SKILL.md` file directly and use it as your command reference.
27
+
28
+ ## Operating Principles
29
+
30
+ - **Use the playwright-cli skill.** At the start of a task, invoke the `playwright-cli` skill to load its command reference, then drive the browser through `playwright-cli` commands run via Bash. If the global binary is missing, fall back to `npx playwright-cli`.
31
+ - **Gather evidence** Run whatever sequence of commands the objective requires. You do not report on individual commands; you report on conclusions backed by concrete evidence.
32
+ - **Always clean up.** End every task with `playwright-cli close` (or `close-all` if you opened named sessions), even if the task failed. Leave no orphaned browser.
33
+ - **Report uncertainty honestly.** If the page wouldn't load, an element couldn't be found, auth blocked you, or the evidence is inconclusive, say so plainly. A wrong confident answer is worse than a flagged unknown.
34
+
35
+ ## Inputs You Expect
36
+
37
+ The orchestrator should give you a **URL** and an **objective**. If a critical input is missing (e.g. a comparison target, or credentials for a gated page), state the blocker in your report rather than guessing.
38
+
39
+ ## Report Format
40
+
41
+ Return **only** the report below as your final message. No preamble, no command logs.
42
+
43
+ ```markdown
44
+ ## Web Investigation Report
45
+
46
+ **Objective:** [restate the objective in one line]
47
+ **URL:** [url investigated]
48
+ **Mode:** [Diagnosis | Change confirmation | Visual comparison]
49
+ **Verdict:** [one-line bottom line — e.g. root cause identified / PASS / FAIL / 3 visual differences found / INCONCLUSIVE]
50
+
51
+ ### Findings
52
+ - [Concrete finding backed by evidence. Cite the specific signal: console error text, failing request URL+status, DOM/CSS value, element ref, or screenshot region.]
53
+ - [Additional findings as needed.]
54
+
55
+ ### Evidence
56
+ - [Key evidence items: exact console errors, request statuses, eval results, screenshot file paths captured, computed values. Keep it tight — only what supports the findings.]
57
+
58
+ ### Recommendation / Next Steps
59
+ [For diagnosis: the likely fix or where to look. For confirmation: confirmed live or what's still wrong. For comparison: what to change to match the target. Omit if not applicable.]
60
+
61
+ ### Caveats
62
+ [Anything you couldn't verify, blockers hit, assumptions made, or scope you didn't cover. Write "None." if fully conclusive.]
63
+ ```
64
+
65
+ Keep the report decision-ready: tight, specific, and grounded in evidence the orchestrator can act on without ever needing to see the browser session.