td-ai-tools 1.1.3 → 1.1.5
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 +175 -15
- package/package.json +1 -1
- package/scripts/smoke-install.sh +5 -1
- package/skills/README.md +7 -0
- 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/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
|
|
|
@@ -250,13 +399,23 @@ function buildUpdateMenu() {
|
|
|
250
399
|
return [...skills, ...agents];
|
|
251
400
|
}
|
|
252
401
|
|
|
253
|
-
function
|
|
402
|
+
function menuHint(type, name, { hintForInstalled, showVersions }) {
|
|
403
|
+
if (showVersions) {
|
|
404
|
+
const ann = versionAnnotation(type, name);
|
|
405
|
+
return ann ? ann.text : '';
|
|
406
|
+
}
|
|
407
|
+
if (hintForInstalled) return '';
|
|
408
|
+
return type === 'skill' ? getSkillHint(name) : getAgentHint(name);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function toGroupOptions(menu, { hintForInstalled = false, showVersions = false } = {}) {
|
|
412
|
+
const opts = { hintForInstalled, showVersions };
|
|
254
413
|
const skillItems = menu.filter(i => i.type === 'skill').map(i => {
|
|
255
|
-
const hint =
|
|
414
|
+
const hint = menuHint('skill', i.name, opts);
|
|
256
415
|
return { value: `skill:${i.name}`, label: i.name, hint: hint || undefined };
|
|
257
416
|
});
|
|
258
417
|
const agentItems = menu.filter(i => i.type === 'agent').map(i => {
|
|
259
|
-
const hint =
|
|
418
|
+
const hint = menuHint('agent', i.name, opts);
|
|
260
419
|
return { value: `agent:${i.name}`, label: i.name, hint: hint || undefined };
|
|
261
420
|
});
|
|
262
421
|
const out = {};
|
|
@@ -301,10 +460,11 @@ async function interactiveInstall(mode = 'install') {
|
|
|
301
460
|
|
|
302
461
|
p.intro(pc.bgCyan(pc.black(' AgentToolkit ')));
|
|
303
462
|
printSelectionControls(mode);
|
|
463
|
+
printOutdatedNudge();
|
|
304
464
|
|
|
305
465
|
const selection = await p.groupMultiselect({
|
|
306
466
|
message: `Select items to ${mode}`,
|
|
307
|
-
options: toGroupOptions(menu),
|
|
467
|
+
options: toGroupOptions(menu, { showVersions: mode === 'update' }),
|
|
308
468
|
required: false,
|
|
309
469
|
});
|
|
310
470
|
|
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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
## Available Skills
|
|
4
4
|
- `cache-reset`: Clear and warm Laravel and Statamic caches after content/template changes.
|
|
5
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.
|
|
6
7
|
- `pr-solver`: Resolve unresolved GitHub PR review threads with GraphQL-driven workflow.
|
|
7
8
|
- `pull-request`: Generate structured pull request descriptions for Shopify theme work.
|
|
8
9
|
- `td-js-vanilla-rules`: Theory Digital vanilla JavaScript standards for Shopify themes.
|
|
@@ -11,5 +12,11 @@
|
|
|
11
12
|
## Skill Structure Convention
|
|
12
13
|
- `<skill-name>/SKILL.md`
|
|
13
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.
|
|
14
16
|
- `<skill-name>/scripts/*` (optional)
|
|
15
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.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
# Codex sandbox config for playwright-cli. Generated by setup.sh --codex.
|
|
2
|
+
# setup.sh substitutes the repo root and the Playwright browser-cache path
|
|
3
|
+
# (e.g. ~/.cache/ms-playwright) into the absolute paths below.
|
|
4
|
+
|
|
1
5
|
sandbox_mode = "workspace-write"
|
|
2
6
|
|
|
3
7
|
[sandbox_workspace_write]
|
|
4
8
|
network_access = true
|
|
5
|
-
exclude_tmpdir_env_var = true
|
|
6
|
-
exclude_slash_tmp = true
|
|
7
9
|
writable_roots = [
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"__PROJECT_ROOT__/.tmp/codex-playwright",
|
|
10
|
+
# The Playwright-managed browsers live here; the sandbox must read/exec them.
|
|
11
|
+
"__BROWSERS_PATH__",
|
|
11
12
|
"__PROJECT_ROOT__/.playwright-cli",
|
|
12
13
|
"__PROJECT_ROOT__/test-results",
|
|
13
14
|
"__PROJECT_ROOT__/playwright-report",
|
|
@@ -18,15 +19,8 @@ writable_roots = [
|
|
|
18
19
|
inherit = "all"
|
|
19
20
|
|
|
20
21
|
[shell_environment_policy.set]
|
|
21
|
-
|
|
22
|
+
# Use the already-installed Playwright chromium, NOT branded chrome
|
|
23
|
+
# (/opt/google/chrome/chrome), which is usually absent.
|
|
22
24
|
PLAYWRIGHT_MCP_BROWSER = "chromium"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
BROWSER = "chromium"
|
|
26
|
-
HOME = "__PROJECT_ROOT__/.tmp/codex-playwright/home"
|
|
27
|
-
XDG_CONFIG_HOME = "__PROJECT_ROOT__/.tmp/codex-playwright/config"
|
|
28
|
-
XDG_STATE_HOME = "__PROJECT_ROOT__/.tmp/codex-playwright/state"
|
|
29
|
-
TMPDIR = "__PROJECT_ROOT__/.tmp/codex-playwright"
|
|
30
|
-
TEMP = "__PROJECT_ROOT__/.tmp/codex-playwright"
|
|
31
|
-
TMP = "__PROJECT_ROOT__/.tmp/codex-playwright"
|
|
32
|
-
XDG_CACHE_HOME = "__PROJECT_ROOT__/.cache/codex-playwright"
|
|
25
|
+
# Pin the browser cache so the sandbox and the CLI agree on its location.
|
|
26
|
+
PLAYWRIGHT_BROWSERS_PATH = "__BROWSERS_PATH__"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Bootstraps playwright-cli configuration for one or more agent runtimes.
|
|
5
|
+
#
|
|
6
|
+
# ./setup.sh # default: --claude --agents
|
|
7
|
+
# ./setup.sh --claude # only .claude/settings.json
|
|
8
|
+
# ./setup.sh --agents # only .agents/playwright-cli.env
|
|
9
|
+
# ./setup.sh --codex # only .codex/config.toml (sandbox)
|
|
10
|
+
# ./setup.sh --all # .claude + .agents + .codex
|
|
11
|
+
#
|
|
12
|
+
# What it pins everywhere: PLAYWRIGHT_MCP_BROWSER=chromium.
|
|
13
|
+
# playwright-cli defaults to *branded* Chrome (/opt/google/chrome/chrome),
|
|
14
|
+
# which is usually NOT installed. The Playwright-managed chromium is, so we
|
|
15
|
+
# point the default browser at it. We do NOT override HOME / browsers-path /
|
|
16
|
+
# executable-path: the managed browsers already live in the shared cache and
|
|
17
|
+
# overriding those was the bug in the old Codex-only script.
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
|
|
21
|
+
# --- resolve project root ---------------------------------------------------
|
|
22
|
+
if PROJECT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null)" && [ -n "$PROJECT_ROOT" ]; then
|
|
23
|
+
:
|
|
24
|
+
else
|
|
25
|
+
# .agents/skills/playwright-cli or .claude/skills/playwright-cli
|
|
26
|
+
# -> repo root (three levels up)
|
|
27
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# --- parse targets ----------------------------------------------------------
|
|
31
|
+
DO_CLAUDE=0; DO_AGENTS=0; DO_CODEX=0
|
|
32
|
+
if [ "$#" -eq 0 ]; then
|
|
33
|
+
DO_CLAUDE=1; DO_AGENTS=1
|
|
34
|
+
else
|
|
35
|
+
for arg in "$@"; do
|
|
36
|
+
case "$arg" in
|
|
37
|
+
--claude) DO_CLAUDE=1 ;;
|
|
38
|
+
--agents) DO_AGENTS=1 ;;
|
|
39
|
+
--codex) DO_CODEX=1 ;;
|
|
40
|
+
--all) DO_CLAUDE=1; DO_AGENTS=1; DO_CODEX=1 ;;
|
|
41
|
+
-h|--help)
|
|
42
|
+
sed -n '3,17p' "$0" | sed 's/^# \{0,1\}//'
|
|
43
|
+
exit 0 ;;
|
|
44
|
+
*) echo "unknown arg: $arg (try --help)" >&2; exit 2 ;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# --- browser detection (Playwright-managed, not a system binary) ------------
|
|
50
|
+
BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"
|
|
51
|
+
DEFAULT_BROWSER="chromium"
|
|
52
|
+
if compgen -G "$BROWSERS_PATH/chromium-*" >/dev/null 2>&1; then
|
|
53
|
+
echo "✓ Playwright chromium found in $BROWSERS_PATH"
|
|
54
|
+
else
|
|
55
|
+
echo "⚠ No Playwright chromium in $BROWSERS_PATH — run: npx playwright install chromium" >&2
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo " PROJECT_ROOT=$PROJECT_ROOT"
|
|
59
|
+
echo " DEFAULT_BROWSER=$DEFAULT_BROWSER"
|
|
60
|
+
|
|
61
|
+
# --- Claude Code: .claude/settings.json (merge, don't clobber) --------------
|
|
62
|
+
if [ "$DO_CLAUDE" -eq 1 ]; then
|
|
63
|
+
OUT="$PROJECT_ROOT/.claude/settings.json"
|
|
64
|
+
mkdir -p "$(dirname "$OUT")"
|
|
65
|
+
node - "$OUT" "$DEFAULT_BROWSER" <<'NODE'
|
|
66
|
+
const fs = require('fs');
|
|
67
|
+
const [, , out, browser] = process.argv;
|
|
68
|
+
let cfg = {};
|
|
69
|
+
if (fs.existsSync(out)) {
|
|
70
|
+
try { cfg = JSON.parse(fs.readFileSync(out, 'utf8')); }
|
|
71
|
+
catch { fs.copyFileSync(out, out + '.bak'); console.error(`! ${out} was invalid JSON; backed up to .bak`); }
|
|
72
|
+
}
|
|
73
|
+
cfg.env = cfg.env || {};
|
|
74
|
+
cfg.env.PLAYWRIGHT_MCP_BROWSER = browser;
|
|
75
|
+
cfg.permissions = cfg.permissions || {};
|
|
76
|
+
const allow = new Set(cfg.permissions.allow || []);
|
|
77
|
+
allow.add('Bash(playwright-cli:*)');
|
|
78
|
+
cfg.permissions.allow = [...allow];
|
|
79
|
+
fs.writeFileSync(out, JSON.stringify(cfg, null, 2) + '\n');
|
|
80
|
+
NODE
|
|
81
|
+
echo "wrote $OUT (env.PLAYWRIGHT_MCP_BROWSER + permissions.allow)"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# --- Shared .agents convention: .agents/playwright-cli.env ------------------
|
|
85
|
+
if [ "$DO_AGENTS" -eq 1 ]; then
|
|
86
|
+
OUT="$PROJECT_ROOT/.agents/playwright-cli.env"
|
|
87
|
+
mkdir -p "$(dirname "$OUT")"
|
|
88
|
+
cat > "$OUT" <<EOF
|
|
89
|
+
# Shared playwright-cli environment (agent-agnostic).
|
|
90
|
+
# Load it before running playwright-cli, e.g.:
|
|
91
|
+
# set -a; . .agents/playwright-cli.env; set +a
|
|
92
|
+
#
|
|
93
|
+
# Forces the Playwright-managed chromium instead of branded Chrome, which is
|
|
94
|
+
# usually not installed.
|
|
95
|
+
PLAYWRIGHT_MCP_BROWSER=$DEFAULT_BROWSER
|
|
96
|
+
EOF
|
|
97
|
+
echo "wrote $OUT"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# --- Codex: .codex/config.toml (sandbox) — opt-in ---------------------------
|
|
101
|
+
if [ "$DO_CODEX" -eq 1 ]; then
|
|
102
|
+
OUT="$PROJECT_ROOT/.codex/config.toml"
|
|
103
|
+
mkdir -p "$(dirname "$OUT")"
|
|
104
|
+
sed \
|
|
105
|
+
-e "s|__PROJECT_ROOT__|$PROJECT_ROOT|g" \
|
|
106
|
+
-e "s|__BROWSERS_PATH__|$BROWSERS_PATH|g" \
|
|
107
|
+
"$SCRIPT_DIR/config.toml.template" > "$OUT"
|
|
108
|
+
echo "wrote $OUT"
|
|
109
|
+
fi
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pr-solver
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: Resolve GitHub pull request feedback by querying unresolved review conversations with the GitHub GraphQL API and implementing code changes for each unresolved thread. Use when a task includes a GitHub PR URL, asks to address open review comments, or asks to clear unresolved conversations; request a PR link if it is missing.
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pull-request
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: Generates well-structured GitHub pull request descriptions for Shopify theme development teams by analyzing git diffs and gathering context. Use this skill whenever a developer asks to create a PR, open a pull request, write a PR description, or submit code for review — even if they just say "make a PR" or "create a pull request". Also trigger when someone mentions needing a PR title, PR summary, or PR testing steps.
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: record-changes
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: Update `docs/changes.md` by summarizing the current branch against the primary development branch. Use when a developer asks to record branch changes, document theme customizations, refresh the project change log, or write a branch summary into `docs/changes.md`. Prefer comparing against `main`, but fall back to `origin/main`, `master`, or `origin/master` when the repository does not have a local `main` branch.
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: stylesheet-migration
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: Migrate Shopify Liquid `{% stylesheet %}` blocks into theme CSS assets using bundled Python scripts. Use when moving inline section or snippet stylesheet tags into `assets/*.css`, especially in `sections/` and `snippets/`. By default the scripts only process filenames that start with `td-`, creating `section-<source>.css` for files in `sections/` and `component-<source>.css` for files in `snippets/`, unless the caller specifies a different prefix or disables the prefix filter.
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: td-js-vanilla-rules
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: Theory Digital vanilla JavaScript standards for Shopify theme work. Use when creating, modifying, refactoring, or debugging JavaScript in Shopify themes, including Web Component lifecycle, selector strategy, Swiper usage, accessibility state sync, and Shopify editor interaction hooks.
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: td-review
|
|
3
|
+
version: 1.0.0
|
|
3
4
|
description: Run parallel code review agents on a PR (including TD theme compliance) and produce a synthesized findings report. Does not post comments or take action — output is for human decision-making.
|
|
4
5
|
user_invocable: true
|
|
5
6
|
arguments:
|
|
@@ -29,7 +30,7 @@ This skill depends on external agent definitions that must be available at runti
|
|
|
29
30
|
- `compound-engineering:research:git-history-analyzer`
|
|
30
31
|
|
|
31
32
|
**Custom agent (this repository):**
|
|
32
|
-
- `td-theme-reviewer` — Theory Digital's theme compliance reviewer.
|
|
33
|
+
- `td-theme-reviewer` — Theory Digital's theme compliance reviewer. Bundled with this skill and auto-registered to `.claude/agents/td-theme-reviewer.md` on install, so it is available as a custom agent type with no manual setup.
|
|
33
34
|
|
|
34
35
|
**CLI tools:**
|
|
35
36
|
- `gh` — GitHub CLI, authenticated with access to the target repository.
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# Codex sandbox config bootstrap
|
|
2
|
-
|
|
3
|
-
This directory contains a template + generator that produces `<repo>/.codex/config.toml`
|
|
4
|
-
on demand, so the file does not need to be committed with hardcoded paths tied to one
|
|
5
|
-
machine or one project clone location.
|
|
6
|
-
|
|
7
|
-
## Files
|
|
8
|
-
|
|
9
|
-
- `config.toml.template` — the source of truth. Uses two placeholders:
|
|
10
|
-
- `__PROJECT_ROOT__` — replaced with the absolute path to the repo root
|
|
11
|
-
- `__BROWSER_PATH__` — replaced with the absolute path to the Chromium/Chrome binary
|
|
12
|
-
- `setup-codex.sh` — reads the template, substitutes the placeholders, writes
|
|
13
|
-
`<repo>/.codex/config.toml`.
|
|
14
|
-
|
|
15
|
-
The generated `.codex/` directory is gitignored, so each clone gets its own copy.
|
|
16
|
-
|
|
17
|
-
## Usage
|
|
18
|
-
|
|
19
|
-
Run once after cloning the repo (or after moving the clone to a new path):
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
.agents/skills/playwright-cli/setup-codex.sh
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The script prints the values it resolved, e.g.:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
wrote /home/you/code/lio/.codex/config.toml
|
|
29
|
-
PROJECT_ROOT=/home/you/code/lio
|
|
30
|
-
BROWSER_PATH=/usr/bin/chromium
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Re-run it any time the template changes or you move the repo.
|
|
34
|
-
|
|
35
|
-
## How paths are resolved
|
|
36
|
-
|
|
37
|
-
**`PROJECT_ROOT`** — derived in this order:
|
|
38
|
-
|
|
39
|
-
1. `git rev-parse --show-toplevel` from the script's directory (works for any clone
|
|
40
|
-
location, on any machine, regardless of username)
|
|
41
|
-
2. Falls back to walking three directories up from the script
|
|
42
|
-
(`.agents/skills/playwright-cli` → repo root) if the repo isn't a git checkout
|
|
43
|
-
|
|
44
|
-
**`BROWSER_PATH`** — derived in this order:
|
|
45
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
Override the browser binary explicitly when needed:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
BROWSER_PATH=/usr/bin/chromium .agents/skills/playwright-cli/setup-codex.sh
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Why a generator instead of variable expansion
|
|
60
|
-
|
|
61
|
-
Codex CLI parses `config.toml` as static TOML and does not expand `~`, `$HOME`, or
|
|
62
|
-
`${VAR}` in path values — they would be treated as literal strings. Generating the
|
|
63
|
-
file at setup time is the simplest way to keep the template portable while still
|
|
64
|
-
producing the absolute paths Codex expects at runtime.
|
|
65
|
-
|
|
66
|
-
## What the generated config does
|
|
67
|
-
|
|
68
|
-
The output `.codex/config.toml` configures Codex's sandbox so Playwright can run
|
|
69
|
-
inside it:
|
|
70
|
-
|
|
71
|
-
- `sandbox_workspace_write.writable_roots` — directories Playwright is allowed to
|
|
72
|
-
write to (browser cache, artifacts, test reports, daemon sockets)
|
|
73
|
-
- `shell_environment_policy.set` — pins `PLAYWRIGHT_*`, `XDG_*`, and `TMPDIR`-family
|
|
74
|
-
env vars to repo-local paths so Playwright stores its browser, cache, and runtime
|
|
75
|
-
state under the project rather than the user's home directory
|
|
76
|
-
|
|
77
|
-
## Editing
|
|
78
|
-
|
|
79
|
-
Edit `config.toml.template` (not the generated file) and re-run `setup-codex.sh`.
|
|
80
|
-
Changes to `.codex/config.toml` directly will be overwritten the next time the
|
|
81
|
-
generator runs.
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
TEMPLATE="$SCRIPT_DIR/config.toml.template"
|
|
6
|
-
|
|
7
|
-
if PROJECT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null)" && [ -n "$PROJECT_ROOT" ]; then
|
|
8
|
-
:
|
|
9
|
-
else
|
|
10
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
11
|
-
fi
|
|
12
|
-
OUTPUT="$PROJECT_ROOT/.codex/config.toml"
|
|
13
|
-
|
|
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
|
-
fi
|
|
19
|
-
|
|
20
|
-
mkdir -p "$(dirname "$OUTPUT")"
|
|
21
|
-
|
|
22
|
-
sed \
|
|
23
|
-
-e "s|__PROJECT_ROOT__|$PROJECT_ROOT|g" \
|
|
24
|
-
-e "s|__BROWSER_PATH__|$BROWSER_PATH|g" \
|
|
25
|
-
"$TEMPLATE" > "$OUTPUT"
|
|
26
|
-
|
|
27
|
-
echo "wrote $OUTPUT"
|
|
28
|
-
echo " PROJECT_ROOT=$PROJECT_ROOT"
|
|
29
|
-
echo " BROWSER_PATH=$BROWSER_PATH"
|