compound-workflow 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +12 -0
- package/.cursor-plugin/plugin.json +12 -0
- package/README.md +155 -0
- package/package.json +22 -0
- package/scripts/install-cli.mjs +313 -0
- package/scripts/sync-into-repo.sh +103 -0
- package/src/.agents/agents/research/best-practices-researcher.md +132 -0
- package/src/.agents/agents/research/framework-docs-researcher.md +134 -0
- package/src/.agents/agents/research/git-history-analyzer.md +62 -0
- package/src/.agents/agents/research/learnings-researcher.md +288 -0
- package/src/.agents/agents/research/repo-research-analyst.md +146 -0
- package/src/.agents/agents/review/agent-native-reviewer.md +299 -0
- package/src/.agents/agents/workflow/bug-reproduction-validator.md +87 -0
- package/src/.agents/agents/workflow/lint.md +20 -0
- package/src/.agents/agents/workflow/spec-flow-analyzer.md +149 -0
- package/src/.agents/commands/assess.md +60 -0
- package/src/.agents/commands/install.md +53 -0
- package/src/.agents/commands/metrics.md +59 -0
- package/src/.agents/commands/setup.md +9 -0
- package/src/.agents/commands/sync.md +9 -0
- package/src/.agents/commands/test-browser.md +393 -0
- package/src/.agents/commands/workflow/brainstorm.md +252 -0
- package/src/.agents/commands/workflow/compound.md +142 -0
- package/src/.agents/commands/workflow/plan.md +737 -0
- package/src/.agents/commands/workflow/review-v2.md +148 -0
- package/src/.agents/commands/workflow/review.md +110 -0
- package/src/.agents/commands/workflow/triage.md +54 -0
- package/src/.agents/commands/workflow/work.md +439 -0
- package/src/.agents/references/README.md +12 -0
- package/src/.agents/references/standards/README.md +9 -0
- package/src/.agents/scripts/self-check.mjs +227 -0
- package/src/.agents/scripts/sync-opencode.mjs +355 -0
- package/src/.agents/skills/agent-browser/SKILL.md +223 -0
- package/src/.agents/skills/audit-traceability/SKILL.md +260 -0
- package/src/.agents/skills/brainstorming/SKILL.md +250 -0
- package/src/.agents/skills/compound-docs/SKILL.md +533 -0
- package/src/.agents/skills/compound-docs/assets/critical-pattern-template.md +34 -0
- package/src/.agents/skills/compound-docs/assets/resolution-template.md +97 -0
- package/src/.agents/skills/compound-docs/references/yaml-schema.md +87 -0
- package/src/.agents/skills/compound-docs/schema.project.yaml +18 -0
- package/src/.agents/skills/compound-docs/schema.yaml +119 -0
- package/src/.agents/skills/data-foundations/SKILL.md +185 -0
- package/src/.agents/skills/document-review/SKILL.md +108 -0
- package/src/.agents/skills/file-todos/SKILL.md +177 -0
- package/src/.agents/skills/file-todos/assets/todo-template.md +106 -0
- package/src/.agents/skills/financial-workflow-integrity/SKILL.md +423 -0
- package/src/.agents/skills/git-worktree/SKILL.md +268 -0
- package/src/.agents/skills/pii-protection-prisma/SKILL.md +629 -0
- package/src/.agents/skills/process-metrics/SKILL.md +46 -0
- package/src/.agents/skills/process-metrics/assets/daily-template.md +37 -0
- package/src/.agents/skills/process-metrics/assets/monthly-template.md +21 -0
- package/src/.agents/skills/process-metrics/assets/weekly-template.md +25 -0
- package/src/.agents/skills/technical-review/SKILL.md +83 -0
- package/src/AGENTS.md +213 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
function usage(exitCode = 0) {
|
|
6
|
+
const msg = `
|
|
7
|
+
Usage:
|
|
8
|
+
node .agents/scripts/sync-opencode.mjs [--root <repoRoot>] [--dry-run]
|
|
9
|
+
|
|
10
|
+
What it does:
|
|
11
|
+
- Discovers commands from .agents/commands/**/*.md
|
|
12
|
+
- id: frontmatter 'invocation' (preferred) else 'name' else filename
|
|
13
|
+
- Discovers agents from .agents/agents/**/*.md
|
|
14
|
+
- id: frontmatter 'name' else filename
|
|
15
|
+
- Creates/updates repo-root opencode.json with managed command/agent entries
|
|
16
|
+
- Prunes managed entries whose source files no longer exist
|
|
17
|
+
|
|
18
|
+
Output:
|
|
19
|
+
- Always prints resolved root and absolute modified paths
|
|
20
|
+
- Prints idempotency summary (0 changes needed vs updated N managed entries)
|
|
21
|
+
`;
|
|
22
|
+
(exitCode === 0 ? console.log : console.error)(msg.trimStart());
|
|
23
|
+
process.exit(exitCode);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv) {
|
|
27
|
+
const out = { root: process.cwd(), dryRun: false };
|
|
28
|
+
for (let i = 2; i < argv.length; i++) {
|
|
29
|
+
const a = argv[i];
|
|
30
|
+
if (a === "--dry-run") out.dryRun = true;
|
|
31
|
+
else if (a === "--root") {
|
|
32
|
+
const v = argv[i + 1];
|
|
33
|
+
if (!v) usage(1);
|
|
34
|
+
out.root = v;
|
|
35
|
+
i++;
|
|
36
|
+
} else if (a === "-h" || a === "--help") usage(0);
|
|
37
|
+
else usage(1);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function realpathSafe(p) {
|
|
43
|
+
try {
|
|
44
|
+
return fs.realpathSync(p);
|
|
45
|
+
} catch {
|
|
46
|
+
return path.resolve(p);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function walkFiles(dirAbs, predicate) {
|
|
51
|
+
const out = [];
|
|
52
|
+
const stack = [dirAbs];
|
|
53
|
+
while (stack.length) {
|
|
54
|
+
const cur = stack.pop();
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = fs.readdirSync(cur, { withFileTypes: true });
|
|
58
|
+
} catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const e of entries) {
|
|
62
|
+
const p = path.join(cur, e.name);
|
|
63
|
+
if (e.isDirectory()) stack.push(p);
|
|
64
|
+
else if (e.isFile() && predicate(p)) out.push(p);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
out.sort();
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function stripJsonc(input) {
|
|
72
|
+
// Removes // and /* */ comments while preserving strings.
|
|
73
|
+
let out = "";
|
|
74
|
+
let i = 0;
|
|
75
|
+
let inStr = false;
|
|
76
|
+
let strQuote = "";
|
|
77
|
+
let escape = false;
|
|
78
|
+
while (i < input.length) {
|
|
79
|
+
const c = input[i];
|
|
80
|
+
const n = input[i + 1];
|
|
81
|
+
if (inStr) {
|
|
82
|
+
out += c;
|
|
83
|
+
if (escape) escape = false;
|
|
84
|
+
else if (c === "\\") escape = true;
|
|
85
|
+
else if (c === strQuote) inStr = false;
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (c === '"' || c === "'") {
|
|
91
|
+
inStr = true;
|
|
92
|
+
strQuote = c;
|
|
93
|
+
out += c;
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (c === "/" && n === "/") {
|
|
99
|
+
while (i < input.length && input[i] !== "\n") i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (c === "/" && n === "*") {
|
|
103
|
+
i += 2;
|
|
104
|
+
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
|
|
105
|
+
i += 2;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
out += c;
|
|
110
|
+
i++;
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readJsonMaybeJsonc(fileAbs) {
|
|
116
|
+
if (!fs.existsSync(fileAbs)) return null;
|
|
117
|
+
const raw = fs.readFileSync(fileAbs, "utf8");
|
|
118
|
+
const stripped = stripJsonc(raw);
|
|
119
|
+
return JSON.parse(stripped);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseFrontmatter(md) {
|
|
123
|
+
// Only supports simple YAML key: value lines (enough for name/description/invocation).
|
|
124
|
+
if (!md.startsWith("---\n") && !md.startsWith("---\r\n")) return {};
|
|
125
|
+
const end = md.indexOf("\n---", 4);
|
|
126
|
+
if (end === -1) return {};
|
|
127
|
+
const block = md.slice(4, end + 1); // include trailing newline for simpler parsing
|
|
128
|
+
const out = {};
|
|
129
|
+
for (const line of block.split(/\r?\n/)) {
|
|
130
|
+
const m = line.match(/^([A-Za-z0-9_-]+):\s*(.*)\s*$/);
|
|
131
|
+
if (!m) continue;
|
|
132
|
+
const k = m[1];
|
|
133
|
+
let v = m[2] ?? "";
|
|
134
|
+
v = v.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
135
|
+
out[k] = v;
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isUnder(parentAbs, childAbs) {
|
|
141
|
+
const rel = path.relative(parentAbs, childAbs);
|
|
142
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function discoverCommands(rootAbs) {
|
|
146
|
+
const commandsDir = path.join(rootAbs, ".agents", "commands");
|
|
147
|
+
const files = walkFiles(commandsDir, (p) => p.endsWith(".md"));
|
|
148
|
+
const map = new Map();
|
|
149
|
+
const warnings = [];
|
|
150
|
+
for (const fileAbs of files) {
|
|
151
|
+
const rel = path.relative(rootAbs, fileAbs).replaceAll(path.sep, "/");
|
|
152
|
+
const md = fs.readFileSync(fileAbs, "utf8");
|
|
153
|
+
const fm = parseFrontmatter(md);
|
|
154
|
+
const id = (fm.invocation || fm.name || path.basename(fileAbs, ".md")).trim();
|
|
155
|
+
const description = (fm.description || id).trim();
|
|
156
|
+
if (!id) {
|
|
157
|
+
warnings.push(`Command file missing id (name/invocation): ${rel}`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (!fm.description) warnings.push(`Command missing frontmatter description: ${rel} (${id})`);
|
|
161
|
+
|
|
162
|
+
// Prefer first occurrence to avoid silent overwrites.
|
|
163
|
+
if (map.has(id)) {
|
|
164
|
+
warnings.push(`Duplicate command id '${id}': ${rel} (already have ${map.get(id).rel})`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
map.set(id, { id, rel, fileAbs, description });
|
|
168
|
+
}
|
|
169
|
+
return { map, warnings };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function discoverAgents(rootAbs) {
|
|
173
|
+
const agentsDir = path.join(rootAbs, ".agents", "agents");
|
|
174
|
+
const files = walkFiles(agentsDir, (p) => p.endsWith(".md"));
|
|
175
|
+
const map = new Map();
|
|
176
|
+
const warnings = [];
|
|
177
|
+
for (const fileAbs of files) {
|
|
178
|
+
const rel = path.relative(rootAbs, fileAbs).replaceAll(path.sep, "/");
|
|
179
|
+
const md = fs.readFileSync(fileAbs, "utf8");
|
|
180
|
+
const fm = parseFrontmatter(md);
|
|
181
|
+
const id = (fm.name || path.basename(fileAbs, ".md")).trim();
|
|
182
|
+
const description = (fm.description || id).trim();
|
|
183
|
+
if (!id) {
|
|
184
|
+
warnings.push(`Agent file missing id (name): ${rel}`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (!fm.description) warnings.push(`Agent missing frontmatter description: ${rel} (${id})`);
|
|
188
|
+
if (map.has(id)) {
|
|
189
|
+
warnings.push(`Duplicate agent id '${id}': ${rel} (already have ${map.get(id).rel})`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
map.set(id, { id, rel, fileAbs, description });
|
|
193
|
+
}
|
|
194
|
+
return { map, warnings };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function ensureObject(v) {
|
|
198
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function stableStringify(obj) {
|
|
202
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function buildManagedCommandTemplate(cmdRel) {
|
|
206
|
+
// Keep simple + consistent with existing docs.
|
|
207
|
+
return `@AGENTS.md\n@${cmdRel}\nArguments: $ARGUMENTS\n`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildManagedAgentPrompt(agentRel) {
|
|
211
|
+
return `{file:${agentRel}}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isManagedCommand(entry) {
|
|
215
|
+
const tpl = entry?.template;
|
|
216
|
+
return typeof tpl === "string" && tpl.includes("@.agents/commands/");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isManagedAgent(entry) {
|
|
220
|
+
const p = entry?.prompt;
|
|
221
|
+
return typeof p === "string" && p.includes("{file:.agents/agents/");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function fileExists(rootAbs, rel) {
|
|
225
|
+
const abs = path.join(rootAbs, rel);
|
|
226
|
+
return fs.existsSync(abs);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function main() {
|
|
230
|
+
const args = parseArgs(process.argv);
|
|
231
|
+
const rootAbs = realpathSafe(args.root);
|
|
232
|
+
const agentsDir = path.join(rootAbs, ".agents");
|
|
233
|
+
const commandsDir = path.join(agentsDir, "commands");
|
|
234
|
+
const agentsMdAbs = path.join(rootAbs, "AGENTS.md");
|
|
235
|
+
const opencodeAbs = path.join(rootAbs, "opencode.json");
|
|
236
|
+
|
|
237
|
+
if (!fs.existsSync(commandsDir)) {
|
|
238
|
+
console.error(`Error: missing .agents/commands in ${rootAbs}`);
|
|
239
|
+
process.exit(2);
|
|
240
|
+
}
|
|
241
|
+
if (!fs.existsSync(path.join(agentsDir, "agents"))) {
|
|
242
|
+
console.error(`Error: missing .agents/agents in ${rootAbs}`);
|
|
243
|
+
process.exit(2);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const { map: commands, warnings: cmdWarn } = discoverCommands(rootAbs);
|
|
247
|
+
const { map: agents, warnings: agentWarn } = discoverAgents(rootAbs);
|
|
248
|
+
const warnings = [...cmdWarn, ...agentWarn];
|
|
249
|
+
|
|
250
|
+
const existing = readJsonMaybeJsonc(opencodeAbs) ?? {};
|
|
251
|
+
const next = structuredClone(existing);
|
|
252
|
+
|
|
253
|
+
next.$schema = next.$schema || "https://opencode.ai/config.json";
|
|
254
|
+
next.skills = ensureObject(next.skills);
|
|
255
|
+
next.skills.paths = Array.isArray(next.skills.paths) ? next.skills.paths : [".agents/skills"];
|
|
256
|
+
next.command = ensureObject(next.command);
|
|
257
|
+
next.agent = ensureObject(next.agent);
|
|
258
|
+
|
|
259
|
+
let created = 0;
|
|
260
|
+
let updated = 0;
|
|
261
|
+
let pruned = 0;
|
|
262
|
+
|
|
263
|
+
// Upsert commands
|
|
264
|
+
for (const [id, cmd] of commands.entries()) {
|
|
265
|
+
const entry = ensureObject(next.command[id]);
|
|
266
|
+
const desired = {
|
|
267
|
+
...entry,
|
|
268
|
+
description: cmd.description,
|
|
269
|
+
agent: "build",
|
|
270
|
+
template: buildManagedCommandTemplate(cmd.rel),
|
|
271
|
+
};
|
|
272
|
+
const before = JSON.stringify(next.command[id] ?? null);
|
|
273
|
+
const after = JSON.stringify(desired);
|
|
274
|
+
if (!next.command[id]) created++;
|
|
275
|
+
else if (before !== after) updated++;
|
|
276
|
+
next.command[id] = desired;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Upsert agents
|
|
280
|
+
for (const [id, ag] of agents.entries()) {
|
|
281
|
+
const entry = ensureObject(next.agent[id]);
|
|
282
|
+
const desired = {
|
|
283
|
+
...entry,
|
|
284
|
+
description: ag.description,
|
|
285
|
+
mode: "subagent",
|
|
286
|
+
prompt: buildManagedAgentPrompt(ag.rel),
|
|
287
|
+
permission: {
|
|
288
|
+
...(ensureObject(entry.permission)),
|
|
289
|
+
edit: ensureObject(entry.permission).edit ?? "deny",
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
const before = JSON.stringify(next.agent[id] ?? null);
|
|
293
|
+
const after = JSON.stringify(desired);
|
|
294
|
+
if (!next.agent[id]) created++;
|
|
295
|
+
else if (before !== after) updated++;
|
|
296
|
+
next.agent[id] = desired;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Prune stale managed commands
|
|
300
|
+
for (const [id, entry] of Object.entries(next.command)) {
|
|
301
|
+
if (!isManagedCommand(entry)) continue;
|
|
302
|
+
const tpl = entry.template;
|
|
303
|
+
const m = tpl.match(/@(\.agents\/commands\/[^\n\r]+)/);
|
|
304
|
+
const rel = m?.[1];
|
|
305
|
+
if (!rel) continue;
|
|
306
|
+
if (!fileExists(rootAbs, rel)) {
|
|
307
|
+
delete next.command[id];
|
|
308
|
+
pruned++;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Prune stale managed agents
|
|
313
|
+
for (const [id, entry] of Object.entries(next.agent)) {
|
|
314
|
+
if (!isManagedAgent(entry)) continue;
|
|
315
|
+
const p = entry.prompt;
|
|
316
|
+
const m = p.match(/\{file:(\.agents\/agents\/[^}]+)\}/);
|
|
317
|
+
const rel = m?.[1];
|
|
318
|
+
if (!rel) continue;
|
|
319
|
+
if (!fileExists(rootAbs, rel)) {
|
|
320
|
+
delete next.agent[id];
|
|
321
|
+
pruned++;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const afterText = stableStringify(next);
|
|
326
|
+
// Semantic idempotency (ignore formatting / key order differences in the source file).
|
|
327
|
+
const semanticChanged = JSON.stringify(existing) !== JSON.stringify(next);
|
|
328
|
+
|
|
329
|
+
console.log(`Resolved root: ${rootAbs}`);
|
|
330
|
+
console.log(`Target opencode.json: ${opencodeAbs}`);
|
|
331
|
+
if (!fs.existsSync(agentsMdAbs)) console.log(`Note: missing AGENTS.md at ${agentsMdAbs} (commands may still run; templates reference it).`);
|
|
332
|
+
|
|
333
|
+
if (warnings.length) {
|
|
334
|
+
console.log("\nWarnings:");
|
|
335
|
+
for (const w of warnings) console.log(`- ${w}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!semanticChanged) {
|
|
339
|
+
console.log("\nIdempotency: 0 changes needed.");
|
|
340
|
+
process.exit(0);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(`\nPlanned managed changes: created=${created}, updated=${updated}, pruned=${pruned}`);
|
|
344
|
+
if (args.dryRun) {
|
|
345
|
+
console.log("Dry-run: no changes made.");
|
|
346
|
+
process.exit(0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
fs.writeFileSync(opencodeAbs, afterText, "utf8");
|
|
350
|
+
console.log(`Wrote: ${opencodeAbs}`);
|
|
351
|
+
console.log(`Idempotency: updated ${created + updated + pruned} managed entries.`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
main();
|
|
355
|
+
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-browser
|
|
3
|
+
description: Browser automation using Vercel's agent-browser CLI. Use when you need to interact with web pages, fill forms, take screenshots, or scrape data. Alternative to Playwright MCP - uses Bash commands with ref-based element selection. Triggers on "browse website", "fill form", "click button", "take screenshot", "scrape page", "web automation".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# agent-browser: CLI Browser Automation
|
|
7
|
+
|
|
8
|
+
Vercel's headless browser automation CLI designed for AI agents. Uses ref-based selection (@e1, @e2) from accessibility snapshots.
|
|
9
|
+
|
|
10
|
+
## Setup Check
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Check installation
|
|
14
|
+
command -v agent-browser >/dev/null 2>&1 && echo "Installed" || echo "NOT INSTALLED - run: npm install -g agent-browser && agent-browser install"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Install if needed
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g agent-browser
|
|
21
|
+
agent-browser install # Downloads Chromium
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Core Workflow
|
|
25
|
+
|
|
26
|
+
**The snapshot + ref pattern is optimal for LLMs:**
|
|
27
|
+
|
|
28
|
+
1. **Navigate** to URL
|
|
29
|
+
2. **Snapshot** to get interactive elements with refs
|
|
30
|
+
3. **Interact** using refs (@e1, @e2, etc.)
|
|
31
|
+
4. **Re-snapshot** after navigation or DOM changes
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Step 1: Open URL
|
|
35
|
+
agent-browser open https://example.com
|
|
36
|
+
|
|
37
|
+
# Step 2: Get interactive elements with refs
|
|
38
|
+
agent-browser snapshot -i --json
|
|
39
|
+
|
|
40
|
+
# Step 3: Interact using refs
|
|
41
|
+
agent-browser click @e1
|
|
42
|
+
agent-browser fill @e2 "search query"
|
|
43
|
+
|
|
44
|
+
# Step 4: Re-snapshot after changes
|
|
45
|
+
agent-browser snapshot -i
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Key Commands
|
|
49
|
+
|
|
50
|
+
### Navigation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agent-browser open <url> # Navigate to URL
|
|
54
|
+
agent-browser back # Go back
|
|
55
|
+
agent-browser forward # Go forward
|
|
56
|
+
agent-browser reload # Reload page
|
|
57
|
+
agent-browser close # Close browser
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Snapshots (Essential for AI)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
agent-browser snapshot # Full accessibility tree
|
|
64
|
+
agent-browser snapshot -i # Interactive elements only (recommended)
|
|
65
|
+
agent-browser snapshot -i --json # JSON output for parsing
|
|
66
|
+
agent-browser snapshot -c # Compact (remove empty elements)
|
|
67
|
+
agent-browser snapshot -d 3 # Limit depth
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Interactions
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
agent-browser click @e1 # Click element
|
|
74
|
+
agent-browser dblclick @e1 # Double-click
|
|
75
|
+
agent-browser fill @e1 "text" # Clear and fill input
|
|
76
|
+
agent-browser type @e1 "text" # Type without clearing
|
|
77
|
+
agent-browser press Enter # Press key
|
|
78
|
+
agent-browser hover @e1 # Hover element
|
|
79
|
+
agent-browser check @e1 # Check checkbox
|
|
80
|
+
agent-browser uncheck @e1 # Uncheck checkbox
|
|
81
|
+
agent-browser select @e1 "option" # Select dropdown option
|
|
82
|
+
agent-browser scroll down 500 # Scroll (up/down/left/right)
|
|
83
|
+
agent-browser scrollintoview @e1 # Scroll element into view
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Get Information
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
agent-browser get text @e1 # Get element text
|
|
90
|
+
agent-browser get html @e1 # Get element HTML
|
|
91
|
+
agent-browser get value @e1 # Get input value
|
|
92
|
+
agent-browser get attr href @e1 # Get attribute
|
|
93
|
+
agent-browser get title # Get page title
|
|
94
|
+
agent-browser get url # Get current URL
|
|
95
|
+
agent-browser get count "button" # Count matching elements
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Screenshots & PDFs
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
agent-browser screenshot # Viewport screenshot
|
|
102
|
+
agent-browser screenshot --full # Full page
|
|
103
|
+
agent-browser screenshot output.png # Save to file
|
|
104
|
+
agent-browser screenshot --full output.png # Full page to file
|
|
105
|
+
agent-browser pdf output.pdf # Save as PDF
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Wait
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
agent-browser wait @e1 # Wait for element
|
|
112
|
+
agent-browser wait 2000 # Wait milliseconds
|
|
113
|
+
agent-browser wait "text" # Wait for text to appear
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Semantic Locators (Alternative to Refs)
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
agent-browser find role button click --name "Submit"
|
|
120
|
+
agent-browser find text "Sign up" click
|
|
121
|
+
agent-browser find label "Email" fill "user@example.com"
|
|
122
|
+
agent-browser find placeholder "Search..." fill "query"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Sessions (Parallel Browsers)
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Run multiple independent browser sessions
|
|
129
|
+
agent-browser --session browser1 open https://site1.com
|
|
130
|
+
agent-browser --session browser2 open https://site2.com
|
|
131
|
+
|
|
132
|
+
# List active sessions
|
|
133
|
+
agent-browser session list
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Examples
|
|
137
|
+
|
|
138
|
+
### Login Flow
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
agent-browser open https://app.example.com/login
|
|
142
|
+
agent-browser snapshot -i
|
|
143
|
+
# Output shows: textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Sign in" [ref=e3]
|
|
144
|
+
agent-browser fill @e1 "user@example.com"
|
|
145
|
+
agent-browser fill @e2 "password123"
|
|
146
|
+
agent-browser click @e3
|
|
147
|
+
agent-browser wait 2000
|
|
148
|
+
agent-browser snapshot -i # Verify logged in
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Search and Extract
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
agent-browser open https://news.ycombinator.com
|
|
155
|
+
agent-browser snapshot -i --json
|
|
156
|
+
# Parse JSON to find story links
|
|
157
|
+
agent-browser get text @e12 # Get headline text
|
|
158
|
+
agent-browser click @e12 # Click to open story
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Form Filling
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
agent-browser open https://forms.example.com
|
|
165
|
+
agent-browser snapshot -i
|
|
166
|
+
agent-browser fill @e1 "John Doe"
|
|
167
|
+
agent-browser fill @e2 "john@example.com"
|
|
168
|
+
agent-browser select @e3 "United States"
|
|
169
|
+
agent-browser check @e4 # Agree to terms
|
|
170
|
+
agent-browser click @e5 # Submit button
|
|
171
|
+
agent-browser screenshot confirmation.png
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Debug Mode
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Run with visible browser window
|
|
178
|
+
agent-browser --headed open https://example.com
|
|
179
|
+
agent-browser --headed snapshot -i
|
|
180
|
+
agent-browser --headed click @e1
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## JSON Output
|
|
184
|
+
|
|
185
|
+
Add `--json` for structured output:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
agent-browser snapshot -i --json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"success": true,
|
|
195
|
+
"data": {
|
|
196
|
+
"refs": {
|
|
197
|
+
"e1": {"name": "Submit", "role": "button"},
|
|
198
|
+
"e2": {"name": "Email", "role": "textbox"}
|
|
199
|
+
},
|
|
200
|
+
"snapshot": "- button \"Submit\" [ref=e1]\n- textbox \"Email\" [ref=e2]"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## vs Playwright MCP
|
|
206
|
+
|
|
207
|
+
| Feature | agent-browser (CLI) | Playwright MCP |
|
|
208
|
+
|---------|---------------------|----------------|
|
|
209
|
+
| Interface | Bash commands | MCP tools |
|
|
210
|
+
| Selection | Refs (@e1) | Refs (e1) |
|
|
211
|
+
| Output | Text/JSON | Tool responses |
|
|
212
|
+
| Parallel | Sessions | Tabs |
|
|
213
|
+
| Best for | Quick automation | Tool integration |
|
|
214
|
+
|
|
215
|
+
Use agent-browser when:
|
|
216
|
+
- You prefer Bash-based workflows
|
|
217
|
+
- You want simpler CLI commands
|
|
218
|
+
- You need quick one-off automation
|
|
219
|
+
|
|
220
|
+
Use Playwright MCP when:
|
|
221
|
+
- You need deep MCP tool integration
|
|
222
|
+
- You want tool-based responses
|
|
223
|
+
- You're building complex automation
|