openspec-playwright 0.1.39 → 0.1.41
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 +52 -21
- package/README.zh-CN.md +36 -12
- package/dist/commands/editors.d.ts +3 -5
- package/dist/commands/editors.js +348 -17
- package/dist/commands/editors.js.map +1 -1
- package/dist/commands/init.js +5 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.js +4 -2
- package/dist/commands/update.js.map +1 -1
- package/openspec-playwright-0.1.41.tgz +0 -0
- package/package.json +1 -1
- package/release-notes.md +2 -2
- package/src/commands/editors.ts +395 -21
- package/src/commands/init.ts +5 -3
- package/src/commands/update.ts +4 -2
- package/openspec-playwright-0.1.39.tgz +0 -0
package/src/commands/editors.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
2
3
|
import { join, dirname } from 'path';
|
|
3
4
|
import chalk from 'chalk';
|
|
4
5
|
|
|
@@ -12,11 +13,21 @@ export function escapeYamlValue(value: string): string {
|
|
|
12
13
|
return value;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
/** Format tags as YAML inline array */
|
|
16
|
+
/** Format tags as YAML inline array (escaped) */
|
|
16
17
|
export function formatTagsArray(tags: string[]): string {
|
|
17
18
|
return `[${tags.map(t => escapeYamlValue(t)).join(', ')}]`;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/** Format tags as YAML inline array (plain, no escaping) */
|
|
22
|
+
function formatTagsPlain(tags: string[]): string {
|
|
23
|
+
return `[${tags.join(', ')}]`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Transform /opsx: to /opsx- for OpenCode */
|
|
27
|
+
function transformToHyphenCommands(text: string): string {
|
|
28
|
+
return text.replace(/\/opsx:/g, '/opsx-');
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
/** Command metadata shared across editors */
|
|
21
32
|
export interface CommandMeta {
|
|
22
33
|
id: string;
|
|
@@ -29,23 +40,19 @@ export interface CommandMeta {
|
|
|
29
40
|
|
|
30
41
|
/** Editor adapter — Strategy Pattern */
|
|
31
42
|
export interface EditorAdapter {
|
|
32
|
-
/** Tool identifier */
|
|
33
43
|
toolId: string;
|
|
34
|
-
/** Whether this editor supports SKILL.md */
|
|
35
44
|
hasSkill: boolean;
|
|
36
|
-
/** Get the command file path relative to project root */
|
|
37
45
|
getCommandPath(commandId: string): string;
|
|
38
|
-
/** Format the complete file content */
|
|
39
46
|
formatCommand(meta: CommandMeta): string;
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
// ─── Claude Code ──────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
42
51
|
/** Claude Code: .claude/commands/opsx/<id>.md + SKILL.md */
|
|
43
52
|
const claudeAdapter: EditorAdapter = {
|
|
44
53
|
toolId: 'claude',
|
|
45
54
|
hasSkill: true,
|
|
46
|
-
getCommandPath(id
|
|
47
|
-
return join('.claude', 'commands', 'opsx', `${id}.md`);
|
|
48
|
-
},
|
|
55
|
+
getCommandPath(id) { return join('.claude', 'commands', 'opsx', `${id}.md`); },
|
|
49
56
|
formatCommand(meta) {
|
|
50
57
|
return `---
|
|
51
58
|
name: ${escapeYamlValue(meta.name)}
|
|
@@ -59,13 +66,13 @@ ${meta.body}
|
|
|
59
66
|
},
|
|
60
67
|
};
|
|
61
68
|
|
|
69
|
+
// ─── Cursor ─────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
62
71
|
/** Cursor: .cursor/commands/opsx-<id>.md */
|
|
63
72
|
const cursorAdapter: EditorAdapter = {
|
|
64
73
|
toolId: 'cursor',
|
|
65
74
|
hasSkill: false,
|
|
66
|
-
getCommandPath(id
|
|
67
|
-
return join('.cursor', 'commands', `opsx-${id}.md`);
|
|
68
|
-
},
|
|
75
|
+
getCommandPath(id) { return join('.cursor', 'commands', `opsx-${id}.md`); },
|
|
69
76
|
formatCommand(meta) {
|
|
70
77
|
return `---
|
|
71
78
|
name: /opsx-${meta.id}
|
|
@@ -79,13 +86,13 @@ ${meta.body}
|
|
|
79
86
|
},
|
|
80
87
|
};
|
|
81
88
|
|
|
89
|
+
// ─── Windsurf ────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
82
91
|
/** Windsurf: .windsurf/workflows/opsx-<id>.md */
|
|
83
92
|
const windsurfAdapter: EditorAdapter = {
|
|
84
93
|
toolId: 'windsurf',
|
|
85
94
|
hasSkill: false,
|
|
86
|
-
getCommandPath(id
|
|
87
|
-
return join('.windsurf', 'workflows', `opsx-${id}.md`);
|
|
88
|
-
},
|
|
95
|
+
getCommandPath(id) { return join('.windsurf', 'workflows', `opsx-${id}.md`); },
|
|
89
96
|
formatCommand(meta) {
|
|
90
97
|
return `---
|
|
91
98
|
name: ${escapeYamlValue(meta.name)}
|
|
@@ -99,13 +106,13 @@ ${meta.body}
|
|
|
99
106
|
},
|
|
100
107
|
};
|
|
101
108
|
|
|
109
|
+
// ─── Cline ──────────────────────────────────────────────────────────────────
|
|
110
|
+
|
|
102
111
|
/** Cline: .clinerules/workflows/opsx-<id>.md — markdown header only */
|
|
103
112
|
const clineAdapter: EditorAdapter = {
|
|
104
113
|
toolId: 'cline',
|
|
105
114
|
hasSkill: false,
|
|
106
|
-
getCommandPath(id
|
|
107
|
-
return join('.clinerules', 'workflows', `opsx-${id}.md`);
|
|
108
|
-
},
|
|
115
|
+
getCommandPath(id) { return join('.clinerules', 'workflows', `opsx-${id}.md`); },
|
|
109
116
|
formatCommand(meta) {
|
|
110
117
|
return `# ${meta.name}
|
|
111
118
|
|
|
@@ -116,13 +123,13 @@ ${meta.body}
|
|
|
116
123
|
},
|
|
117
124
|
};
|
|
118
125
|
|
|
126
|
+
// ─── Continue ────────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
119
128
|
/** Continue: .continue/prompts/opsx-<id>.prompt */
|
|
120
129
|
const continueAdapter: EditorAdapter = {
|
|
121
130
|
toolId: 'continue',
|
|
122
131
|
hasSkill: false,
|
|
123
|
-
getCommandPath(id
|
|
124
|
-
return join('.continue', 'prompts', `opsx-${id}.prompt`);
|
|
125
|
-
},
|
|
132
|
+
getCommandPath(id) { return join('.continue', 'prompts', `opsx-${id}.prompt`); },
|
|
126
133
|
formatCommand(meta) {
|
|
127
134
|
return `---
|
|
128
135
|
name: opsx-${meta.id}
|
|
@@ -135,13 +142,353 @@ ${meta.body}
|
|
|
135
142
|
},
|
|
136
143
|
};
|
|
137
144
|
|
|
138
|
-
|
|
145
|
+
// ─── amazon-q ─────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/** Amazon Q: .amazonq/prompts/opsx-<id>.md */
|
|
148
|
+
const amazonqAdapter: EditorAdapter = {
|
|
149
|
+
toolId: 'amazon-q',
|
|
150
|
+
hasSkill: false,
|
|
151
|
+
getCommandPath(id) { return join('.amazonq', 'prompts', `opsx-${id}.md`); },
|
|
152
|
+
formatCommand(meta) {
|
|
153
|
+
return `---
|
|
154
|
+
description: ${meta.description}
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
${meta.body}
|
|
158
|
+
`;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// ─── antigravity ──────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/** Antigravity: .agent/workflows/opsx-<id>.md */
|
|
165
|
+
const antigravityAdapter: EditorAdapter = {
|
|
166
|
+
toolId: 'antigravity',
|
|
167
|
+
hasSkill: false,
|
|
168
|
+
getCommandPath(id) { return join('.agent', 'workflows', `opsx-${id}.md`); },
|
|
169
|
+
formatCommand(meta) {
|
|
170
|
+
return `---
|
|
171
|
+
description: ${meta.description}
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
${meta.body}
|
|
175
|
+
`;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// ─── auggie ────────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
/** Auggie: .augment/commands/opsx-<id>.md */
|
|
182
|
+
const auggieAdapter: EditorAdapter = {
|
|
183
|
+
toolId: 'auggie',
|
|
184
|
+
hasSkill: false,
|
|
185
|
+
getCommandPath(id) { return join('.augment', 'commands', `opsx-${id}.md`); },
|
|
186
|
+
formatCommand(meta) {
|
|
187
|
+
return `---
|
|
188
|
+
description: ${meta.description}
|
|
189
|
+
argument-hint: command arguments
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
${meta.body}
|
|
193
|
+
`;
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// ─── codebuddy ─────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/** CodeBuddy: .codebuddy/commands/opsx/<id>.md */
|
|
200
|
+
const codebuddyAdapter: EditorAdapter = {
|
|
201
|
+
toolId: 'codebuddy',
|
|
202
|
+
hasSkill: false,
|
|
203
|
+
getCommandPath(id) { return join('.codebuddy', 'commands', 'opsx', `${id}.md`); },
|
|
204
|
+
formatCommand(meta) {
|
|
205
|
+
return `---
|
|
206
|
+
name: ${meta.name}
|
|
207
|
+
description: "${meta.description}"
|
|
208
|
+
argument-hint: "[command arguments]"
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
${meta.body}
|
|
212
|
+
`;
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ─── codex ────────────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
/** Codex: <CODEX_HOME>/prompts/opsx-<id>.md — global scope */
|
|
219
|
+
const codexAdapter: EditorAdapter = {
|
|
220
|
+
toolId: 'codex',
|
|
221
|
+
hasSkill: false,
|
|
222
|
+
getCommandPath(id) {
|
|
223
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join(homedir(), '.codex');
|
|
224
|
+
return join(codexHome, 'prompts', `opsx-${id}.md`);
|
|
225
|
+
},
|
|
226
|
+
formatCommand(meta) {
|
|
227
|
+
return `---
|
|
228
|
+
description: ${meta.description}
|
|
229
|
+
argument-hint: command arguments
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
${meta.body}
|
|
233
|
+
`;
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// ─── costrict ─────────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
/** CoStrict: .cospec/openspec/commands/opsx-<id>.md */
|
|
240
|
+
const costrictAdapter: EditorAdapter = {
|
|
241
|
+
toolId: 'costrict',
|
|
242
|
+
hasSkill: false,
|
|
243
|
+
getCommandPath(id) { return join('.cospec', 'openspec', 'commands', `opsx-${id}.md`); },
|
|
244
|
+
formatCommand(meta) {
|
|
245
|
+
return `---
|
|
246
|
+
description: "${meta.description}"
|
|
247
|
+
argument-hint: command arguments
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
${meta.body}
|
|
251
|
+
`;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// ─── crush ────────────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
/** Crush: .crush/commands/opsx/<id>.md — raw values, no escaping */
|
|
258
|
+
const crushAdapter: EditorAdapter = {
|
|
259
|
+
toolId: 'crush',
|
|
260
|
+
hasSkill: false,
|
|
261
|
+
getCommandPath(id) { return join('.crush', 'commands', 'opsx', `${id}.md`); },
|
|
262
|
+
formatCommand(meta) {
|
|
263
|
+
return `---
|
|
264
|
+
name: ${meta.name}
|
|
265
|
+
description: ${meta.description}
|
|
266
|
+
category: ${meta.category}
|
|
267
|
+
tags: ${formatTagsPlain(meta.tags)}
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
${meta.body}
|
|
271
|
+
`;
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// ─── factory ───────────────────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
/** Factory Droid: .factory/commands/opsx-<id>.md */
|
|
278
|
+
const factoryAdapter: EditorAdapter = {
|
|
279
|
+
toolId: 'factory',
|
|
280
|
+
hasSkill: false,
|
|
281
|
+
getCommandPath(id) { return join('.factory', 'commands', `opsx-${id}.md`); },
|
|
282
|
+
formatCommand(meta) {
|
|
283
|
+
return `---
|
|
284
|
+
description: ${meta.description}
|
|
285
|
+
argument-hint: command arguments
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
${meta.body}
|
|
289
|
+
`;
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// ─── gemini ────────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
/** Gemini CLI: .gemini/commands/opsx/<id>.toml */
|
|
296
|
+
const geminiAdapter: EditorAdapter = {
|
|
297
|
+
toolId: 'gemini',
|
|
298
|
+
hasSkill: false,
|
|
299
|
+
getCommandPath(id) { return join('.gemini', 'commands', 'opsx', `${id}.toml`); },
|
|
300
|
+
formatCommand(meta) {
|
|
301
|
+
return `description = "${meta.description}"
|
|
302
|
+
|
|
303
|
+
prompt = """
|
|
304
|
+
${meta.body}
|
|
305
|
+
"""
|
|
306
|
+
`;
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// ─── github-copilot ────────────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
/** GitHub Copilot: .github/prompts/opsx-<id>.prompt.md */
|
|
313
|
+
const githubcopilotAdapter: EditorAdapter = {
|
|
314
|
+
toolId: 'github-copilot',
|
|
315
|
+
hasSkill: false,
|
|
316
|
+
getCommandPath(id) { return join('.github', 'prompts', `opsx-${id}.prompt.md`); },
|
|
317
|
+
formatCommand(meta) {
|
|
318
|
+
return `---
|
|
319
|
+
description: ${meta.description}
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
${meta.body}
|
|
323
|
+
`;
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// ─── iflow ────────────────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
/** iFlow: .iflow/commands/opsx-<id>.md */
|
|
330
|
+
const iflowAdapter: EditorAdapter = {
|
|
331
|
+
toolId: 'iflow',
|
|
332
|
+
hasSkill: false,
|
|
333
|
+
getCommandPath(id) { return join('.iflow', 'commands', `opsx-${id}.md`); },
|
|
334
|
+
formatCommand(meta) {
|
|
335
|
+
return `---
|
|
336
|
+
name: /opsx-${meta.id}
|
|
337
|
+
id: opsx-${meta.id}
|
|
338
|
+
category: ${meta.category}
|
|
339
|
+
description: ${meta.description}
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
${meta.body}
|
|
343
|
+
`;
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// ─── kilocode ────────────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
/** Kilo Code: .kilocode/workflows/opsx-<id>.md — body only */
|
|
350
|
+
const kilocodeAdapter: EditorAdapter = {
|
|
351
|
+
toolId: 'kilocode',
|
|
352
|
+
hasSkill: false,
|
|
353
|
+
getCommandPath(id) { return join('.kilocode', 'workflows', `opsx-${id}.md`); },
|
|
354
|
+
formatCommand(meta) {
|
|
355
|
+
return `${meta.body}
|
|
356
|
+
`;
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// ─── kiro ─────────────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
/** Kiro: .kiro/prompts/opsx-<id>.prompt.md */
|
|
363
|
+
const kiroAdapter: EditorAdapter = {
|
|
364
|
+
toolId: 'kiro',
|
|
365
|
+
hasSkill: false,
|
|
366
|
+
getCommandPath(id) { return join('.kiro', 'prompts', `opsx-${id}.prompt.md`); },
|
|
367
|
+
formatCommand(meta) {
|
|
368
|
+
return `---
|
|
369
|
+
description: ${meta.description}
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
${meta.body}
|
|
373
|
+
`;
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// ─── opencode ─────────────────────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
/** OpenCode: .opencode/commands/opsx-<id>.md — transforms /opsx: to /opsx- */
|
|
380
|
+
const opencodeAdapter: EditorAdapter = {
|
|
381
|
+
toolId: 'opencode',
|
|
382
|
+
hasSkill: false,
|
|
383
|
+
getCommandPath(id) { return join('.opencode', 'commands', `opsx-${id}.md`); },
|
|
384
|
+
formatCommand(meta) {
|
|
385
|
+
const transformed = transformToHyphenCommands(meta.body);
|
|
386
|
+
return `---
|
|
387
|
+
description: ${meta.description}
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
${transformed}
|
|
391
|
+
`;
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// ─── pi ──────────────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
/** Pi: .pi/prompts/opsx-<id>.md */
|
|
398
|
+
const piAdapter: EditorAdapter = {
|
|
399
|
+
toolId: 'pi',
|
|
400
|
+
hasSkill: false,
|
|
401
|
+
getCommandPath(id) { return join('.pi', 'prompts', `opsx-${id}.md`); },
|
|
402
|
+
formatCommand(meta) {
|
|
403
|
+
return `---
|
|
404
|
+
description: ${escapeYamlValue(meta.description)}
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
${meta.body}
|
|
408
|
+
`;
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// ─── qoder ────────────────────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
/** Qoder: .qoder/commands/opsx/<id>.md — raw values, no escaping */
|
|
415
|
+
const qoderAdapter: EditorAdapter = {
|
|
416
|
+
toolId: 'qoder',
|
|
417
|
+
hasSkill: false,
|
|
418
|
+
getCommandPath(id) { return join('.qoder', 'commands', 'opsx', `${id}.md`); },
|
|
419
|
+
formatCommand(meta) {
|
|
420
|
+
return `---
|
|
421
|
+
name: ${meta.name}
|
|
422
|
+
description: ${meta.description}
|
|
423
|
+
category: ${meta.category}
|
|
424
|
+
tags: ${formatTagsPlain(meta.tags)}
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
${meta.body}
|
|
428
|
+
`;
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// ─── qwen ────────────────────────────────────────────────────────────────
|
|
433
|
+
|
|
434
|
+
/** Qwen Code: .qwen/commands/opsx-<id>.toml */
|
|
435
|
+
const qwenAdapter: EditorAdapter = {
|
|
436
|
+
toolId: 'qwen',
|
|
437
|
+
hasSkill: false,
|
|
438
|
+
getCommandPath(id) { return join('.qwen', 'commands', `opsx-${id}.toml`); },
|
|
439
|
+
formatCommand(meta) {
|
|
440
|
+
return `description = "${meta.description}"
|
|
441
|
+
|
|
442
|
+
prompt = """
|
|
443
|
+
${meta.body}
|
|
444
|
+
"""
|
|
445
|
+
`;
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// ─── roocode ─────────────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
/** RooCode: .roo/commands/opsx-<id>.md — markdown header */
|
|
452
|
+
const roocodeAdapter: EditorAdapter = {
|
|
453
|
+
toolId: 'roocode',
|
|
454
|
+
hasSkill: false,
|
|
455
|
+
getCommandPath(id) { return join('.roo', 'commands', `opsx-${id}.md`); },
|
|
456
|
+
formatCommand(meta) {
|
|
457
|
+
return `# ${meta.name}
|
|
458
|
+
|
|
459
|
+
${meta.description}
|
|
460
|
+
|
|
461
|
+
${meta.body}
|
|
462
|
+
`;
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// ─── Detection map ───────────────────────────────────────────────────────
|
|
467
|
+
|
|
139
468
|
const ALL_ADAPTERS: EditorAdapter[] = [
|
|
140
469
|
claudeAdapter,
|
|
141
470
|
cursorAdapter,
|
|
142
471
|
windsurfAdapter,
|
|
143
472
|
clineAdapter,
|
|
144
473
|
continueAdapter,
|
|
474
|
+
amazonqAdapter,
|
|
475
|
+
antigravityAdapter,
|
|
476
|
+
auggieAdapter,
|
|
477
|
+
codebuddyAdapter,
|
|
478
|
+
codexAdapter,
|
|
479
|
+
costrictAdapter,
|
|
480
|
+
crushAdapter,
|
|
481
|
+
factoryAdapter,
|
|
482
|
+
geminiAdapter,
|
|
483
|
+
githubcopilotAdapter,
|
|
484
|
+
iflowAdapter,
|
|
485
|
+
kilocodeAdapter,
|
|
486
|
+
kiroAdapter,
|
|
487
|
+
opencodeAdapter,
|
|
488
|
+
piAdapter,
|
|
489
|
+
qoderAdapter,
|
|
490
|
+
qwenAdapter,
|
|
491
|
+
roocodeAdapter,
|
|
145
492
|
];
|
|
146
493
|
|
|
147
494
|
/** Detect which editors are installed by checking their config directories */
|
|
@@ -152,6 +499,23 @@ export function detectEditors(projectRoot: string): EditorAdapter[] {
|
|
|
152
499
|
['.windsurf', windsurfAdapter],
|
|
153
500
|
['.clinerules', clineAdapter],
|
|
154
501
|
['.continue', continueAdapter],
|
|
502
|
+
['.amazonq', amazonqAdapter],
|
|
503
|
+
['.agent', antigravityAdapter],
|
|
504
|
+
['.augment', auggieAdapter],
|
|
505
|
+
['.codebuddy', codebuddyAdapter],
|
|
506
|
+
['.cospec', costrictAdapter],
|
|
507
|
+
['.crush', crushAdapter],
|
|
508
|
+
['.factory', factoryAdapter],
|
|
509
|
+
['.gemini', geminiAdapter],
|
|
510
|
+
['.github', githubcopilotAdapter],
|
|
511
|
+
['.iflow', iflowAdapter],
|
|
512
|
+
['.kilocode', kilocodeAdapter],
|
|
513
|
+
['.kiro', kiroAdapter],
|
|
514
|
+
['.opencode', opencodeAdapter],
|
|
515
|
+
['.pi', piAdapter],
|
|
516
|
+
['.qoder', qoderAdapter],
|
|
517
|
+
['.qwen', qwenAdapter],
|
|
518
|
+
['.roo', roocodeAdapter],
|
|
155
519
|
];
|
|
156
520
|
|
|
157
521
|
return checks
|
|
@@ -159,6 +523,16 @@ export function detectEditors(projectRoot: string): EditorAdapter[] {
|
|
|
159
523
|
.map(([, adapter]) => adapter);
|
|
160
524
|
}
|
|
161
525
|
|
|
526
|
+
// ─── Codex is global — detect separately ─────────────────────────────────
|
|
527
|
+
|
|
528
|
+
/** Detect Codex by checking if CODEX_HOME or ~/.codex exists */
|
|
529
|
+
export function detectCodex(): EditorAdapter | null {
|
|
530
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join(homedir(), '.codex');
|
|
531
|
+
return existsSync(codexHome) ? codexAdapter : null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ─── Install helpers ───────────────────────────────────────────────────────
|
|
535
|
+
|
|
162
536
|
/** Build the shared command metadata */
|
|
163
537
|
export function buildCommandMeta(body: string): CommandMeta {
|
|
164
538
|
return {
|
package/src/commands/init.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'url';
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { readFile } from 'fs/promises';
|
|
13
13
|
import { syncMcpTools } from './mcpSync.js';
|
|
14
|
-
import { detectEditors, installForAllEditors, installSkill, claudeAdapter } from './editors.js';
|
|
14
|
+
import { detectEditors, detectCodex, installForAllEditors, installSkill, claudeAdapter } from './editors.js';
|
|
15
15
|
|
|
16
16
|
const TEMPLATE_DIR = fileURLToPath(new URL('../../templates', import.meta.url));
|
|
17
17
|
const SCHEMA_DIR = fileURLToPath(new URL('../../schemas', import.meta.url));
|
|
@@ -85,9 +85,11 @@ export async function init(options: InitOptions) {
|
|
|
85
85
|
// 4. Install E2E commands for detected editors
|
|
86
86
|
console.log(chalk.blue('\n─── Installing E2E Commands ───'));
|
|
87
87
|
const detected = detectEditors(projectRoot);
|
|
88
|
-
|
|
88
|
+
const codex = detectCodex();
|
|
89
|
+
const adapters = codex ? [...detected, codex] : detected;
|
|
90
|
+
if (adapters.length > 0) {
|
|
89
91
|
const body = await readFile(CMD_BODY_SRC, 'utf-8');
|
|
90
|
-
installForAllEditors(body,
|
|
92
|
+
installForAllEditors(body, adapters, projectRoot);
|
|
91
93
|
} else {
|
|
92
94
|
const body = await readFile(CMD_BODY_SRC, 'utf-8');
|
|
93
95
|
installForAllEditors(body, [claudeAdapter], projectRoot);
|
package/src/commands/update.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'url';
|
|
|
14
14
|
import chalk from 'chalk';
|
|
15
15
|
import * as tar from 'tar';
|
|
16
16
|
import { syncMcpTools } from './mcpSync.js';
|
|
17
|
-
import { detectEditors, installForAllEditors, installSkill } from './editors.js';
|
|
17
|
+
import { detectEditors, detectCodex, installForAllEditors, installSkill } from './editors.js';
|
|
18
18
|
|
|
19
19
|
const CMD_BODY_SRC = fileURLToPath(new URL('../../.claude/commands/opsx/e2e-body.md', import.meta.url));
|
|
20
20
|
const SCHEMA_DIR = fileURLToPath(new URL('../../schemas', import.meta.url));
|
|
@@ -84,7 +84,9 @@ export async function update(options: UpdateOptions) {
|
|
|
84
84
|
const schemaSrc = join(tmpDir, 'schemas', 'playwright-e2e');
|
|
85
85
|
|
|
86
86
|
// Install commands for all detected editors
|
|
87
|
-
const
|
|
87
|
+
const detected = detectEditors(projectRoot);
|
|
88
|
+
const codex = detectCodex();
|
|
89
|
+
const adapters = codex ? [...detected, codex] : detected;
|
|
88
90
|
if (adapters.length > 0 && existsSync(bodySrc)) {
|
|
89
91
|
const body = readFileSync(bodySrc, 'utf-8');
|
|
90
92
|
installForAllEditors(body, adapters, projectRoot);
|
|
Binary file
|