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.
- package/README.md +10 -0
- package/agents/component-library-section-blocks/AGENTS.md +4 -0
- package/agents/horizon-component-library/AGENTS.md +4 -0
- package/bin/cli.js +183 -17
- package/package.json +1 -1
- package/scripts/smoke-install.sh +5 -1
- package/skills/README.md +7 -1
- package/skills/basecamp/SKILL.md +1 -0
- package/skills/cache-reset/SKILL.md +1 -0
- package/skills/car-ticket-generator/SKILL.md +1 -0
- package/skills/everhour-basecamp-estimates/SKILL.md +1 -0
- package/skills/forge-cli/SKILL.md +1 -0
- package/skills/horizon-component-migration/SKILL.md +1 -0
- package/skills/playwright-cli/PRIMER.md +122 -0
- package/skills/playwright-cli/SKILL.md +20 -1
- package/skills/playwright-cli/agents/playwright-investigator.md +65 -0
- package/skills/playwright-cli/config.toml.template +10 -16
- package/skills/playwright-cli/setup.sh +109 -0
- package/skills/pr-solver/SKILL.md +1 -0
- package/skills/pull-request/SKILL.md +1 -0
- package/skills/pull-request-statamic/SKILL.md +1 -0
- package/skills/record-changes/SKILL.md +1 -0
- package/skills/stylesheet-migration/SKILL.md +1 -0
- package/skills/td-js-vanilla-rules/SKILL.md +1 -0
- package/skills/td-review/SKILL.md +2 -1
- package/skills/barrage/SKILL.md +0 -180
- package/skills/barrage/agents/openai.yaml +0 -4
- package/skills/barrage/scripts/__pycache__/build_queue.cpython-312.pyc +0 -0
- package/skills/barrage/scripts/build_queue.py +0 -276
- package/skills/barrage/tests/test_build_queue.py +0 -135
- package/skills/playwright-cli/CODEX-CONFIG.md +0 -81
- 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
|
|
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
|
-
|
|
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
|
|
231
|
-
const
|
|
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
|
|
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 =
|
|
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 =
|
|
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
package/scripts/smoke-install.sh
CHANGED
|
@@ -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.
|
package/skills/basecamp/SKILL.md
CHANGED
|
@@ -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.
|