|
1
|
"use strict"; |
|
2
|
var __create = Object.create; |
|
3
|
var __defProp = Object.defineProperty; |
|
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; |
|
5
|
var __getOwnPropNames = Object.getOwnPropertyNames; |
|
6
|
var __getProtoOf = Object.getPrototypeOf; |
|
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty; |
|
8
|
var __export = (target, all) => { |
|
9
|
for (var name in all) |
|
10
|
__defProp(target, name, { get: all[name], enumerable: true }); |
|
11
|
}; |
|
12
|
var __copyProps = (to, from, except, desc) => { |
|
13
|
if (from && typeof from === "object" || typeof from === "function") { |
|
14
|
for (let key of __getOwnPropNames(from)) |
|
15
|
if (!__hasOwnProp.call(to, key) && key !== except) |
|
16
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); |
|
17
|
} |
|
18
|
return to; |
|
19
|
}; |
|
20
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( |
|
21
|
// If the importer is in node compatibility mode or this is not an ESM |
|
22
|
// file that has been converted to a CommonJS file using a Babel- |
|
23
|
// compatible transform (i.e. "__esModule" has not been set), then set |
|
24
|
// "default" to the CommonJS "module.exports" for node compatibility. |
|
25
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, |
|
26
|
mod |
|
27
|
)); |
|
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); |
|
29
|
var generateAgents_exports = {}; |
|
30
|
__export(generateAgents_exports, { |
|
31
|
ClaudeGenerator: () => ClaudeGenerator, |
|
32
|
CopilotGenerator: () => CopilotGenerator, |
|
33
|
OpencodeGenerator: () => OpencodeGenerator, |
|
34
|
VSCodeGenerator: () => VSCodeGenerator |
|
35
|
}); |
|
36
|
module.exports = __toCommonJS(generateAgents_exports); |
|
37
|
var import_fs = __toESM(require("fs")); |
|
38
|
var import_path = __toESM(require("path")); |
|
39
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle"); |
|
40
|
var import_utils = require("playwright-core/lib/utils"); |
|
41
|
var import_seed = require("../mcp/test/seed"); |
|
42
|
var import_agentParser = require("./agentParser"); |
|
43
|
async function loadAgentSpecs() { |
|
44
|
const files = await import_fs.default.promises.readdir(__dirname); |
|
45
|
return Promise.all(files.filter((file) => file.endsWith(".agent.md")).map((file) => (0, import_agentParser.parseAgentSpec)(import_path.default.join(__dirname, file)))); |
|
46
|
} |
|
47
|
class ClaudeGenerator { |
|
48
|
static async init(config, projectName, prompts) { |
|
49
|
await initRepo(config, projectName, { |
|
50
|
promptsFolder: prompts ? ".claude/prompts" : void 0 |
|
51
|
}); |
|
52
|
const agents = await loadAgentSpecs(); |
|
53
|
await import_fs.default.promises.mkdir(".claude/agents", { recursive: true }); |
|
54
|
for (const agent of agents) |
|
55
|
await writeFile(`.claude/agents/${agent.name}.md`, ClaudeGenerator.agentSpec(agent), "\u{1F916}", "agent definition"); |
|
56
|
await writeFile(".mcp.json", JSON.stringify({ |
|
57
|
mcpServers: { |
|
58
|
"playwright-test": { |
|
59
|
command: "npx", |
|
60
|
args: ["playwright", "run-test-mcp-server"] |
|
61
|
} |
|
62
|
} |
|
63
|
}, null, 2), "\u{1F527}", "mcp configuration"); |
|
64
|
initRepoDone(); |
|
65
|
} |
|
66
|
static agentSpec(agent) { |
|
67
|
const claudeToolMap = /* @__PURE__ */ new Map([ |
|
68
|
["search", ["Glob", "Grep", "Read", "LS"]], |
|
69
|
["edit", ["Edit", "MultiEdit", "Write"]] |
|
70
|
]); |
|
71
|
function asClaudeTool(tool) { |
|
72
|
const [first, second] = tool.split("/"); |
|
73
|
if (!second) |
|
74
|
return (claudeToolMap.get(first) || [first]).join(", "); |
|
75
|
return `mcp__${first}__${second}`; |
|
76
|
} |
|
77
|
const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : ""; |
|
78
|
const lines = []; |
|
79
|
const header = { |
|
80
|
name: agent.name, |
|
81
|
description: agent.description + examples, |
|
82
|
tools: agent.tools.map((tool) => asClaudeTool(tool)).join(", "), |
|
83
|
model: agent.model, |
|
84
|
color: agent.color |
|
85
|
}; |
|
86
|
lines.push(`---`); |
|
87
|
lines.push(import_utilsBundle.yaml.stringify(header, { lineWidth: 1e5 }) + `---`); |
|
88
|
lines.push(""); |
|
89
|
lines.push(agent.instructions); |
|
90
|
return lines.join("\n"); |
|
91
|
} |
|
92
|
} |
|
93
|
class OpencodeGenerator { |
|
94
|
static async init(config, projectName, prompts) { |
|
95
|
await initRepo(config, projectName, { |
|
96
|
defaultAgentName: "Build", |
|
97
|
promptsFolder: prompts ? ".opencode/prompts" : void 0 |
|
98
|
}); |
|
99
|
const agents = await loadAgentSpecs(); |
|
100
|
for (const agent of agents) { |
|
101
|
const prompt = [agent.instructions]; |
|
102
|
prompt.push(""); |
|
103
|
prompt.push(...agent.examples.map((example) => `<example>${example}</example>`)); |
|
104
|
await writeFile(`.opencode/prompts/${agent.name}.md`, prompt.join("\n"), "\u{1F916}", "agent definition"); |
|
105
|
} |
|
106
|
await writeFile("opencode.json", OpencodeGenerator.configuration(agents), "\u{1F527}", "opencode configuration"); |
|
107
|
initRepoDone(); |
|
108
|
} |
|
109
|
static configuration(agents) { |
|
110
|
const opencodeToolMap = /* @__PURE__ */ new Map([ |
|
111
|
["search", ["ls", "glob", "grep", "read"]], |
|
112
|
["edit", ["edit", "write"]] |
|
113
|
]); |
|
114
|
const asOpencodeTool = (tools, tool) => { |
|
115
|
const [first, second] = tool.split("/"); |
|
116
|
if (!second) { |
|
117
|
for (const tool2 of opencodeToolMap.get(first) || [first]) |
|
118
|
tools[tool2] = true; |
|
119
|
} else { |
|
120
|
tools[`${first}*${second}`] = true; |
|
121
|
} |
|
122
|
}; |
|
123
|
const result = {}; |
|
124
|
result["$schema"] = "https://opencode.ai/config.json"; |
|
125
|
result["mcp"] = {}; |
|
126
|
result["tools"] = { |
|
127
|
"playwright*": false |
|
128
|
}; |
|
129
|
result["agent"] = {}; |
|
130
|
for (const agent of agents) { |
|
131
|
const tools = {}; |
|
132
|
result["agent"][agent.name] = { |
|
133
|
description: agent.description, |
|
134
|
mode: "subagent", |
|
135
|
prompt: `{file:.opencode/prompts/${agent.name}.md}`, |
|
136
|
tools |
|
137
|
}; |
|
138
|
for (const tool of agent.tools) |
|
139
|
asOpencodeTool(tools, tool); |
|
140
|
} |
|
141
|
result["mcp"]["playwright-test"] = { |
|
142
|
type: "local", |
|
143
|
command: ["npx", "playwright", "run-test-mcp-server"], |
|
144
|
enabled: true |
|
145
|
}; |
|
146
|
return JSON.stringify(result, null, 2); |
|
147
|
} |
|
148
|
} |
|
149
|
class CopilotGenerator { |
|
150
|
static async init(config, projectName, prompts) { |
|
151
|
await initRepo(config, projectName, { |
|
152
|
defaultAgentName: "agent", |
|
153
|
promptsFolder: prompts ? ".github/prompts" : void 0, |
|
154
|
promptSuffix: "prompt" |
|
155
|
}); |
|
156
|
const agents = await loadAgentSpecs(); |
|
157
|
await import_fs.default.promises.mkdir(".github/agents", { recursive: true }); |
|
158
|
for (const agent of agents) |
|
159
|
await writeFile(`.github/agents/${agent.name}.agent.md`, CopilotGenerator.agentSpec(agent), "\u{1F916}", "agent definition"); |
|
160
|
await deleteFile(`.github/chatmodes/ \u{1F3AD} planner.chatmode.md`, "legacy planner chatmode"); |
|
161
|
await deleteFile(`.github/chatmodes/\u{1F3AD} generator.chatmode.md`, "legacy generator chatmode"); |
|
162
|
await deleteFile(`.github/chatmodes/\u{1F3AD} healer.chatmode.md`, "legacy healer chatmode"); |
|
163
|
await deleteFile(`.github/agents/ \u{1F3AD} planner.agent.md`, "legacy planner agent"); |
|
164
|
await deleteFile(`.github/agents/\u{1F3AD} generator.agent.md`, "legacy generator agent"); |
|
165
|
await deleteFile(`.github/agents/\u{1F3AD} healer.agent.md`, "legacy healer agent"); |
|
166
|
await VSCodeGenerator.appendToMCPJson(); |
|
167
|
const mcpConfig = { mcpServers: CopilotGenerator.mcpServers }; |
|
168
|
if (!import_fs.default.existsSync(".github/copilot-setup-steps.yml")) { |
|
169
|
const yaml2 = import_fs.default.readFileSync(import_path.default.join(__dirname, "copilot-setup-steps.yml"), "utf-8"); |
|
170
|
await writeFile(".github/workflows/copilot-setup-steps.yml", yaml2, "\u{1F527}", "GitHub Copilot setup steps"); |
|
171
|
} |
|
172
|
console.log(""); |
|
173
|
console.log(""); |
|
174
|
console.log(" \u{1F527} TODO: GitHub > Settings > Copilot > Coding agent > MCP configuration"); |
|
175
|
console.log("------------------------------------------------------------------"); |
|
176
|
console.log(JSON.stringify(mcpConfig, null, 2)); |
|
177
|
console.log("------------------------------------------------------------------"); |
|
178
|
initRepoDone(); |
|
179
|
} |
|
180
|
static agentSpec(agent) { |
|
181
|
const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : ""; |
|
182
|
const lines = []; |
|
183
|
const header = { |
|
184
|
"name": agent.name, |
|
185
|
"description": agent.description + examples, |
|
186
|
"tools": agent.tools, |
|
187
|
"model": "Claude Sonnet 4", |
|
188
|
"mcp-servers": CopilotGenerator.mcpServers |
|
189
|
}; |
|
190
|
lines.push(`---`); |
|
191
|
lines.push(import_utilsBundle.yaml.stringify(header, { lineWidth: 1e5 }) + `---`); |
|
192
|
lines.push(""); |
|
193
|
lines.push(agent.instructions); |
|
194
|
lines.push(""); |
|
195
|
return lines.join("\n"); |
|
196
|
} |
|
197
|
static { |
|
198
|
this.mcpServers = { |
|
199
|
"playwright-test": { |
|
200
|
"type": "stdio", |
|
201
|
"command": "npx", |
|
202
|
"args": [ |
|
203
|
"playwright", |
|
204
|
"run-test-mcp-server" |
|
205
|
], |
|
206
|
"tools": ["*"] |
|
207
|
} |
|
208
|
}; |
|
209
|
} |
|
210
|
} |
|
211
|
class VSCodeGenerator { |
|
212
|
static async init(config, projectName) { |
|
213
|
await initRepo(config, projectName, { |
|
214
|
promptsFolder: void 0 |
|
215
|
}); |
|
216
|
const agents = await loadAgentSpecs(); |
|
217
|
const nameMap = /* @__PURE__ */ new Map([ |
|
218
|
["playwright-test-planner", " \u{1F3AD} planner"], |
|
219
|
["playwright-test-generator", "\u{1F3AD} generator"], |
|
220
|
["playwright-test-healer", "\u{1F3AD} healer"] |
|
221
|
]); |
|
222
|
await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true }); |
|
223
|
for (const agent of agents) |
|
224
|
await writeFile(`.github/chatmodes/${nameMap.get(agent.name)}.chatmode.md`, VSCodeGenerator.agentSpec(agent), "\u{1F916}", "chatmode definition"); |
|
225
|
await VSCodeGenerator.appendToMCPJson(); |
|
226
|
initRepoDone(); |
|
227
|
} |
|
228
|
static async appendToMCPJson() { |
|
229
|
await import_fs.default.promises.mkdir(".vscode", { recursive: true }); |
|
230
|
const mcpJsonPath = ".vscode/mcp.json"; |
|
231
|
let mcpJson = { |
|
232
|
servers: {}, |
|
233
|
inputs: [] |
|
234
|
}; |
|
235
|
try { |
|
236
|
mcpJson = JSON.parse(import_fs.default.readFileSync(mcpJsonPath, "utf8")); |
|
237
|
} catch { |
|
238
|
} |
|
239
|
if (!mcpJson.servers) |
|
240
|
mcpJson.servers = {}; |
|
241
|
mcpJson.servers["playwright-test"] = { |
|
242
|
type: "stdio", |
|
243
|
command: "npx", |
|
244
|
args: ["playwright", "run-test-mcp-server"] |
|
245
|
}; |
|
246
|
await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2), "\u{1F527}", "mcp configuration"); |
|
247
|
} |
|
248
|
static agentSpec(agent) { |
|
249
|
const vscodeToolMap = /* @__PURE__ */ new Map([ |
|
250
|
["search", ["search/listDirectory", "search/fileSearch", "search/textSearch"]], |
|
251
|
["read", ["search/readFile"]], |
|
252
|
["edit", ["edit/editFiles"]], |
|
253
|
["write", ["edit/createFile", "edit/createDirectory"]] |
|
254
|
]); |
|
255
|
const vscodeToolsOrder = ["edit/createFile", "edit/createDirectory", "edit/editFiles", "search/fileSearch", "search/textSearch", "search/listDirectory", "search/readFile"]; |
|
256
|
const vscodeMcpName = "playwright-test"; |
|
257
|
function asVscodeTool(tool) { |
|
258
|
const [first, second] = tool.split("/"); |
|
259
|
if (second) |
|
260
|
return `${vscodeMcpName}/${second}`; |
|
261
|
return vscodeToolMap.get(first) || first; |
|
262
|
} |
|
263
|
const tools = agent.tools.map(asVscodeTool).flat().sort((a, b) => { |
|
264
|
const indexA = vscodeToolsOrder.indexOf(a); |
|
265
|
const indexB = vscodeToolsOrder.indexOf(b); |
|
266
|
if (indexA === -1 && indexB === -1) |
|
267
|
return a.localeCompare(b); |
|
268
|
if (indexA === -1) |
|
269
|
return 1; |
|
270
|
if (indexB === -1) |
|
271
|
return -1; |
|
272
|
return indexA - indexB; |
|
273
|
}).map((tool) => `'${tool}'`).join(", "); |
|
274
|
const lines = []; |
|
275
|
lines.push(`---`); |
|
276
|
lines.push(`description: ${agent.description}.`); |
|
277
|
lines.push(`tools: [${tools}]`); |
|
278
|
lines.push(`---`); |
|
279
|
lines.push(""); |
|
280
|
lines.push(agent.instructions); |
|
281
|
for (const example of agent.examples) |
|
282
|
lines.push(`<example>${example}</example>`); |
|
283
|
lines.push(""); |
|
284
|
return lines.join("\n"); |
|
285
|
} |
|
286
|
} |
|
287
|
async function writeFile(filePath, content, icon, description) { |
|
288
|
console.log(` ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`); |
|
289
|
await (0, import_utils.mkdirIfNeeded)(filePath); |
|
290
|
await import_fs.default.promises.writeFile(filePath, content, "utf-8"); |
|
291
|
} |
|
292
|
async function deleteFile(filePath, description) { |
|
293
|
try { |
|
294
|
if (!import_fs.default.existsSync(filePath)) |
|
295
|
return; |
|
296
|
} catch { |
|
297
|
return; |
|
298
|
} |
|
299
|
console.log(` \u2702\uFE0F ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`); |
|
300
|
await import_fs.default.promises.unlink(filePath); |
|
301
|
} |
|
302
|
async function initRepo(config, projectName, options) { |
|
303
|
const project = (0, import_seed.seedProject)(config, projectName); |
|
304
|
console.log(` \u{1F3AD} Using project "${project.project.name}" as a primary project`); |
|
305
|
if (!import_fs.default.existsSync("specs")) { |
|
306
|
await import_fs.default.promises.mkdir("specs"); |
|
307
|
await writeFile(import_path.default.join("specs", "README.md"), `# Specs |
|
308
|
|
|
309
|
This is a directory for test plans. |
|
310
|
`, "\u{1F4DD}", "directory for test plans"); |
|
311
|
} |
|
312
|
let seedFile = await (0, import_seed.findSeedFile)(project); |
|
313
|
if (!seedFile) { |
|
314
|
seedFile = (0, import_seed.defaultSeedFile)(project); |
|
315
|
await writeFile(seedFile, import_seed.seedFileContent, "\u{1F331}", "default environment seed file"); |
|
316
|
} |
|
317
|
if (options.promptsFolder) { |
|
318
|
await import_fs.default.promises.mkdir(options.promptsFolder, { recursive: true }); |
|
319
|
for (const promptFile of await import_fs.default.promises.readdir(__dirname)) { |
|
320
|
if (!promptFile.endsWith(".prompt.md")) |
|
321
|
continue; |
|
322
|
const shortName = promptFile.replace(".prompt.md", ""); |
|
323
|
const fileName = options.promptSuffix ? `${shortName}.${options.promptSuffix}.md` : `${shortName}.md`; |
|
324
|
const content = await loadPrompt(promptFile, { |
|
325
|
defaultAgentName: "default", |
|
326
|
...options, |
|
327
|
seedFile: import_path.default.relative(process.cwd(), seedFile) |
|
328
|
}); |
|
329
|
await writeFile(import_path.default.join(options.promptsFolder, fileName), content, "\u{1F4DD}", "prompt template"); |
|
330
|
} |
|
331
|
} |
|
332
|
} |
|
333
|
function initRepoDone() { |
|
334
|
console.log(" \u2705 Done."); |
|
335
|
} |
|
336
|
async function loadPrompt(file, params) { |
|
337
|
const content = await import_fs.default.promises.readFile(import_path.default.join(__dirname, file), "utf-8"); |
|
338
|
return Object.entries(params).reduce((acc, [key, value]) => { |
|
339
|
return acc.replace(new RegExp(`\\\${${key}}`, "g"), value); |
|
340
|
}, content); |
|
341
|
} |
|
342
|
// Annotate the CommonJS export names for ESM import in node: |
|
343
|
0 && (module.exports = { |
|
344
|
ClaudeGenerator, |
|
345
|
CopilotGenerator, |
|
346
|
OpencodeGenerator, |
|
347
|
VSCodeGenerator |
|
348
|
}); |
|
349
|
|