@aksp/opencrew 1.3.3 → 1.4.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.
@@ -1,87 +1,95 @@
1
- import path from 'node:path';
2
- import { promises as fs } from 'node:fs';
3
- import { templatesDir, packageJsonPath } from '../lib/paths.js';
4
- import { copyDir, exists, writeFileSafe, readJson, writeBridgeFile } from '../lib/fsx.js';
5
- import { c, log, info, ok, warn, step } from '../lib/ui.js';
6
-
7
- // Update refreshes ONLY the framework. It never touches:
8
- // crews/, _opencrew/_memory/, _opencrew/_browser_profile/, .env, IDE bridges.
9
- // Note: catalog skills (skills/<name>/ that ship with the package) ARE fully
10
- // overwritten below — user edits to a catalog skill's own files are not preserved.
11
- // Only skill directories that don't exist in the package's templates/skills/ at all
12
- // (i.e. custom/user-authored skills) are left untouched.
13
- export async function update(opts = {}) {
14
- const target = process.cwd();
15
- const pkg = await readJson(packageJsonPath);
16
- const version = pkg.version;
17
-
18
- if (!(await exists(path.join(target, '_opencrew', 'core')))) {
19
- warn('No opencrew workspace found here.');
20
- info(`Run ${c.cyan('npx @aksp/opencrew init')} first.`);
21
- return;
22
- }
23
-
24
- const versionFile = path.join(target, '_opencrew', '.opencrew-version');
25
- const current = (await exists(versionFile))
26
- ? (await fs.readFile(versionFile, 'utf8')).trim()
27
- : 'unknown';
28
-
29
- log(`\n${c.bold(c.cyan('opencrew update'))}`);
30
- log(c.dim(`Installed: ${current} → Package: ${version}\n`));
31
-
32
- if (opts.check) {
33
- if (current === version) {
34
- ok(`Up to date (v${version}).`);
35
- } else {
36
- info(`Update available: v${current} → v${version}.`);
37
- info(`Run ${c.cyan('npx @aksp/opencrew update')} to apply.`);
38
- process.exitCode = 1;
39
- }
40
- return;
41
- }
42
-
43
- if (current === version) {
44
- ok('Already up to date. Refreshing framework files anyway.');
45
- }
46
-
47
- // Refresh core framework: _opencrew/core is fully overwritten (it is not user data).
48
- step('Refreshing framework');
49
- let n = 0;
50
- await copyDir(path.join(templatesDir, '_opencrew', 'core'), path.join(target, '_opencrew', 'core'), {
51
- overwrite: true,
52
- onCopy: () => (n += 1),
53
- });
54
- ok(`_opencrew/core refreshed (${n} files)`);
55
-
56
- // Refresh catalog skills: every skill shipped in templates/skills/ is fully
57
- // overwritten (edits to a catalog skill's files do not survive an update).
58
- // Skill directories that only exist in the user's project — i.e. not part of
59
- // the catalog — are never touched, since copyDir only visits paths that exist
60
- // in the source (templates/skills/).
61
- step('Refreshing catalog skills');
62
- warn('Catalog skills are fully overwritten — your edits to any built-in skill files will be lost.');
63
- let s = 0;
64
- await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), {
65
- overwrite: true,
66
- onCopy: () => (s += 1),
67
- });
68
- ok(`Catalog skills refreshed (${s} files)`);
69
-
70
- // System doc — full definition in _opencrew/core/, thin bridge at root.
71
- const systemContent = await fs.readFile(path.join(templatesDir, 'AGENTS.md'), 'utf8');
72
- await writeFileSafe(path.join(target, '_opencrew', 'core', 'system.md'), systemContent);
73
- ok('_opencrew/core/system.md refreshed');
74
-
75
- const agentsBridge = '# opencrew\n\n'
76
- + 'The opencrew system definition lives at `_opencrew/core/system.md`.\n'
77
- + 'Read that file and adopt the opencrew system role — follow all initialization,\n'
78
- + 'command routing, and workflow instructions defined there.\n\n'
79
- + 'Type `/opencrew` to open the main menu.\n';
80
-
81
- await writeBridgeFile(path.join(target, 'AGENTS.md'), agentsBridge);
82
- ok('AGENTS.md refreshed');
83
-
84
- await fs.writeFile(versionFile, version + '\n');
85
- log(`\n${c.green(c.bold('Updated to v' + version))}.`);
86
- log(c.dim('Your crews, memory, IDE bridges and .env were left untouched.\n'));
87
- }
1
+ import path from 'node:path';
2
+ import { promises as fs } from 'node:fs';
3
+ import { templatesDir, packageJsonPath } from '../lib/paths.js';
4
+ import { copyDir, exists, writeFileSafe, readJson, writeBridgeFile, readFile } from '../lib/fsx.js';
5
+ import { c, log, info, ok, warn, step } from '../lib/ui.js';
6
+
7
+ // Update refreshes ONLY the framework. It never touches:
8
+ // crews/, _opencrew/_memory/, _opencrew/_browser_profile/, .env, IDE bridges.
9
+ // Note: catalog skills (skills/<name>/ that ship with the package) ARE fully
10
+ // overwritten below — user edits to a catalog skill's own files are not preserved.
11
+ // Only skill directories that don't exist in the package's templates/skills/ at all
12
+ // (i.e. custom/user-authored skills) are left untouched.
13
+ export async function update(opts = {}) {
14
+ const target = process.cwd();
15
+ const pkg = await readJson(packageJsonPath);
16
+ const version = pkg.version;
17
+
18
+ if (!(await exists(path.join(target, '_opencrew', 'core')))) {
19
+ warn('No opencrew workspace found here.');
20
+ info(`Run ${c.cyan('npx @aksp/opencrew init')} first.`);
21
+ return;
22
+ }
23
+
24
+ const versionFile = path.join(target, '_opencrew', '.opencrew-version');
25
+ const current = (await exists(versionFile))
26
+ ? (await fs.readFile(versionFile, 'utf8')).trim()
27
+ : 'unknown';
28
+
29
+ log(`\n${c.bold(c.cyan('opencrew update'))}`);
30
+ log(c.dim(`Installed: ${current} → Package: ${version}\n`));
31
+
32
+ if (opts.check) {
33
+ if (current === version) {
34
+ ok(`Up to date (v${version}).`);
35
+ } else {
36
+ info(`Update available: v${current} → v${version}.`);
37
+ info(`Run ${c.cyan('npx @aksp/opencrew update')} to apply.`);
38
+ process.exitCode = 1;
39
+ }
40
+ return;
41
+ }
42
+
43
+ if (current === version) {
44
+ ok('Already up to date. Refreshing framework files anyway.');
45
+ }
46
+
47
+ // Refresh core framework: _opencrew/core is fully overwritten (it is not user data).
48
+ step('Refreshing framework');
49
+ let n = 0;
50
+ await copyDir(path.join(templatesDir, '_opencrew', 'core'), path.join(target, '_opencrew', 'core'), {
51
+ overwrite: true,
52
+ onCopy: () => (n += 1),
53
+ });
54
+ ok(`_opencrew/core refreshed (${n} files)`);
55
+
56
+ // Refresh catalog skills: every skill shipped in templates/skills/ is fully
57
+ // overwritten (edits to a catalog skill's files do not survive an update).
58
+ // Skill directories that only exist in the user's project — i.e. not part of
59
+ // the catalog — are never touched, since copyDir only visits paths that exist
60
+ // in the source (templates/skills/).
61
+ step('Refreshing catalog skills');
62
+ warn('Catalog skills are fully overwritten — your edits to any built-in skill files will be lost.');
63
+ let s = 0;
64
+ await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), {
65
+ overwrite: true,
66
+ onCopy: () => (s += 1),
67
+ });
68
+ ok(`Catalog skills refreshed (${s} files)`);
69
+
70
+ // System doc — full definition in _opencrew/core/, thin bridge at root.
71
+ const systemContent = await fs.readFile(path.join(templatesDir, 'AGENTS.md'), 'utf8');
72
+ await writeFileSafe(path.join(target, '_opencrew', 'core', 'system.md'), systemContent);
73
+ ok('_opencrew/core/system.md refreshed');
74
+
75
+ const agentsBridge = '# opencrew\n\n'
76
+ + 'The opencrew system definition lives at `_opencrew/core/system.md`.\n'
77
+ + 'Read that file and adopt the opencrew system role — follow all initialization,\n'
78
+ + 'command routing, and workflow instructions defined there.\n\n'
79
+ + 'Type `/opencrew` to open the main menu.\n';
80
+
81
+ // If AGENTS.md is a legacy full-system doc (pre-v1.3), replace it entirely with the thin bridge.
82
+ const agentsPath = path.join(target, 'AGENTS.md');
83
+ const existingAgents = await readFile(agentsPath);
84
+ if (existingAgents.includes('# opencrew Instructions') && !existingAgents.includes('<!-- opencrew:start -->')) {
85
+ await writeFileSafe(agentsPath, agentsBridge);
86
+ ok('AGENTS.md (migrated from legacy full-system to thin bridge)');
87
+ } else {
88
+ await writeBridgeFile(agentsPath, agentsBridge);
89
+ ok('AGENTS.md refreshed');
90
+ }
91
+
92
+ await fs.writeFile(versionFile, version + '\n');
93
+ log(`\n${c.green(c.bold('Updated to v' + version))}.`);
94
+ log(c.dim('Your crews, memory, IDE bridges and .env were left untouched.\n'));
95
+ }
package/src/lib/fsx.js CHANGED
@@ -1,127 +1,127 @@
1
- // Filesystem helpers (no external deps).
2
- import { promises as fs } from 'node:fs';
3
- import path from 'node:path';
4
-
5
- export async function exists(p) {
6
- try {
7
- await fs.access(p);
8
- return true;
9
- } catch {
10
- return false;
11
- }
12
- }
13
-
14
- export async function ensureDir(p) {
15
- await fs.mkdir(p, { recursive: true });
16
- }
17
-
18
- /**
19
- * Recursively copy `src` into `dest`.
20
- * @param {object} opts
21
- * @param {boolean} opts.overwrite Overwrite existing files (default false).
22
- * @param {(rel:string)=>boolean} opts.skip Return true to skip a relative path.
23
- * @param {(rel:string)=>void} opts.onCopy Called for each file copied.
24
- */
25
- export async function copyDir(src, dest, opts = {}) {
26
- const { overwrite = false, skip = () => false, onCopy, _root = src } = opts;
27
- await ensureDir(dest);
28
- const entries = await fs.readdir(src, { withFileTypes: true });
29
- for (const entry of entries) {
30
- const from = path.join(src, entry.name);
31
- const to = path.join(dest, entry.name);
32
- const rel = path.relative(_root, from).split(path.sep).join('/');
33
- if (skip(rel)) continue;
34
- if (entry.isDirectory()) {
35
- await copyDir(from, to, { ...opts, _root });
36
- } else {
37
- const present = await exists(to);
38
- if (present && !overwrite) continue;
39
- await ensureDir(path.dirname(to));
40
- await fs.copyFile(from, to);
41
- if (onCopy) onCopy(rel);
42
- }
43
- }
44
- }
45
-
46
- export async function writeFileSafe(p, content, { overwrite = true } = {}) {
47
- if (!overwrite && (await exists(p))) return false;
48
- await ensureDir(path.dirname(p));
49
- await fs.writeFile(p, content);
50
- return true;
51
- }
52
-
53
- /**
54
- * Write a bridge file using marked-block strategy.
55
- * - File doesn't exist → creates with content wrapped in HTML markers
56
- * - File exists with markers → replaces only the block between markers
57
- * - File exists without markers → prepends marked block, preserves existing content
58
- *
59
- * @param {string} p File path
60
- * @param {string} block Content to place between markers
61
- * @param {object} opts
62
- * @param {string} opts.marker Marker name (default: 'opencrew')
63
- * @returns {Promise<{written: boolean, merged: boolean}>}
64
- */
65
- export async function writeBridgeFile(p, block, { marker = 'opencrew' } = {}) {
66
- const start = `<!-- ${marker}:start -->`;
67
- const end = `<!-- ${marker}:end -->`;
68
- const marked = `${start}\n${block.trimEnd()}\n${end}`;
69
-
70
- if (!(await exists(p))) {
71
- await ensureDir(path.dirname(p));
72
- await fs.writeFile(p, marked + '\n');
73
- return { written: true, merged: false };
74
- }
75
-
76
- const existing = await fs.readFile(p, 'utf8');
77
-
78
- // Already has markers → replace just the block, keep everything else
79
- if (existing.includes(start) && existing.includes(end)) {
80
- const updated = existing.replace(
81
- new RegExp(escapeRx(start) + '[\\s\\S]*?' + escapeRx(end), 'g'),
82
- marked,
83
- );
84
- if (updated !== existing) {
85
- await fs.writeFile(p, updated);
86
- return { written: true, merged: false };
87
- }
88
- return { written: false, merged: false };
89
- }
90
-
91
- // User content exists → prepend block, preserve everything
92
- await fs.writeFile(p, marked + '\n\n' + existing.trimStart());
93
- return { written: true, merged: true };
94
- }
95
-
96
- function escapeRx(s) {
97
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
98
- }
99
-
100
- export async function readFile(p) {
101
- return fs.readFile(p, 'utf8');
102
- }
103
-
104
- /**
105
- * Remove a directory and all its contents.
106
- * Does nothing silently if the path does not exist.
107
- * @param {string} p Directory path to remove.
108
- * @returns {Promise<boolean>} true if something was deleted, false if it didn't exist.
109
- */
110
- export async function deleteDir(p) {
111
- if (!(await exists(p))) return false;
112
- try {
113
- await fs.rm(p, { recursive: true, force: true });
114
- return true;
115
- } catch {
116
- return false;
117
- }
118
- }
119
-
120
- export async function readJson(p) {
121
- try {
122
- return JSON.parse(await fs.readFile(p, 'utf8'));
123
- } catch (e) {
124
- const reason = e.code === 'ENOENT' ? 'file not found' : e.message;
125
- throw new Error(`Failed to read ${p}: ${reason}`);
126
- }
127
- }
1
+ // Filesystem helpers (no external deps).
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ export async function exists(p) {
6
+ try {
7
+ await fs.access(p);
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ export async function ensureDir(p) {
15
+ await fs.mkdir(p, { recursive: true });
16
+ }
17
+
18
+ /**
19
+ * Recursively copy `src` into `dest`.
20
+ * @param {object} opts
21
+ * @param {boolean} opts.overwrite Overwrite existing files (default false).
22
+ * @param {(rel:string)=>boolean} opts.skip Return true to skip a relative path.
23
+ * @param {(rel:string)=>void} opts.onCopy Called for each file copied.
24
+ */
25
+ export async function copyDir(src, dest, opts = {}) {
26
+ const { overwrite = false, skip = () => false, onCopy, _root = src } = opts;
27
+ await ensureDir(dest);
28
+ const entries = await fs.readdir(src, { withFileTypes: true });
29
+ for (const entry of entries) {
30
+ const from = path.join(src, entry.name);
31
+ const to = path.join(dest, entry.name);
32
+ const rel = path.relative(_root, from).split(path.sep).join('/');
33
+ if (skip(rel)) continue;
34
+ if (entry.isDirectory()) {
35
+ await copyDir(from, to, { ...opts, _root });
36
+ } else {
37
+ const present = await exists(to);
38
+ if (present && !overwrite) continue;
39
+ await ensureDir(path.dirname(to));
40
+ await fs.copyFile(from, to);
41
+ if (onCopy) onCopy(rel);
42
+ }
43
+ }
44
+ }
45
+
46
+ export async function writeFileSafe(p, content, { overwrite = true } = {}) {
47
+ if (!overwrite && (await exists(p))) return false;
48
+ await ensureDir(path.dirname(p));
49
+ await fs.writeFile(p, content);
50
+ return true;
51
+ }
52
+
53
+ /**
54
+ * Write a bridge file using marked-block strategy.
55
+ * - File doesn't exist → creates with content wrapped in HTML markers
56
+ * - File exists with markers → replaces only the block between markers
57
+ * - File exists without markers → prepends marked block, preserves existing content
58
+ *
59
+ * @param {string} p File path
60
+ * @param {string} block Content to place between markers
61
+ * @param {object} opts
62
+ * @param {string} opts.marker Marker name (default: 'opencrew')
63
+ * @returns {Promise<{written: boolean, merged: boolean}>}
64
+ */
65
+ export async function writeBridgeFile(p, block, { marker = 'opencrew' } = {}) {
66
+ const start = `<!-- ${marker}:start -->`;
67
+ const end = `<!-- ${marker}:end -->`;
68
+ const marked = `${start}\n${block.trimEnd()}\n${end}`;
69
+
70
+ if (!(await exists(p))) {
71
+ await ensureDir(path.dirname(p));
72
+ await fs.writeFile(p, marked + '\n');
73
+ return { written: true, merged: false };
74
+ }
75
+
76
+ const existing = await fs.readFile(p, 'utf8');
77
+
78
+ // Already has markers → replace just the block, keep everything else
79
+ if (existing.includes(start) && existing.includes(end)) {
80
+ const updated = existing.replace(
81
+ new RegExp(escapeRx(start) + '[\\s\\S]*?' + escapeRx(end), 'g'),
82
+ marked,
83
+ );
84
+ if (updated !== existing) {
85
+ await fs.writeFile(p, updated);
86
+ return { written: true, merged: false };
87
+ }
88
+ return { written: false, merged: false };
89
+ }
90
+
91
+ // User content exists → prepend block, preserve everything
92
+ await fs.writeFile(p, marked + '\n\n' + existing.trimStart());
93
+ return { written: true, merged: true };
94
+ }
95
+
96
+ function escapeRx(s) {
97
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
98
+ }
99
+
100
+ export async function readFile(p) {
101
+ return fs.readFile(p, 'utf8');
102
+ }
103
+
104
+ /**
105
+ * Remove a directory and all its contents.
106
+ * Does nothing silently if the path does not exist.
107
+ * @param {string} p Directory path to remove.
108
+ * @returns {Promise<boolean>} true if something was deleted, false if it didn't exist.
109
+ */
110
+ export async function deleteDir(p) {
111
+ if (!(await exists(p))) return false;
112
+ try {
113
+ await fs.rm(p, { recursive: true, force: true });
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
120
+ export async function readJson(p) {
121
+ try {
122
+ return JSON.parse(await fs.readFile(p, 'utf8'));
123
+ } catch (e) {
124
+ const reason = e.code === 'ENOENT' ? 'file not found' : e.message;
125
+ throw new Error(`Failed to read ${p}: ${reason}`);
126
+ }
127
+ }
package/src/lib/ides.js CHANGED
@@ -41,6 +41,41 @@ Type \`/opencrew\` to open the main menu.
41
41
  - All checkpoint questions use \`AskUserQuestion\`.
42
42
  - opencrew ships its own Playwright MCP (\`.mcp.json\`); disable the native Playwright plugin.
43
43
  - Do not manually edit files under \`_opencrew/core/\` unless you know what you're doing.
44
+
45
+ ## STATUS.md (gestão de sessão)
46
+
47
+ This project uses \`STATUS.md\` for session continuity. The file is local (gitignored).
48
+
49
+ **At the start of every session:**
50
+ - Read \`STATUS.md\`. If it doesn't exist, create it with the template below.
51
+ - Report a 3-line summary: what was in progress, what's next, any blockers.
52
+
53
+ **During the session:**
54
+ - Move items from ⬜ Pendente to 🔄 Em andamento when you start working on them.
55
+ - Move items to ✅ Concluído when finished.
56
+ - Add new items that emerge during work.
57
+
58
+ **Before ending the session:**
59
+ - Ensure \`STATUS.md\` reflects the real state.
60
+ - Update \`Última sessão\` timestamp.
61
+
62
+ **Template:**
63
+ \`\`\`markdown
64
+ # STATUS — OpenCrew
65
+
66
+ > Última sessão: {today}
67
+ > Skill: /status
68
+
69
+ ## 🔄 Em andamento
70
+
71
+ ## ⬜ Pendente
72
+
73
+ ## ✅ Concluído (esta sessão)
74
+
75
+ ## 📋 Backlog
76
+
77
+ ## 💡 Decisões
78
+ \`\`\`
44
79
  `;
45
80
 
46
81
  const render = (title, extra = '') =>
@@ -50,6 +85,16 @@ const render = (title, extra = '') =>
50
85
  * Each IDE lists the files its bridge writes. `content` is plain text.
51
86
  * `mdc` files (Cursor) get a small frontmatter so the rule always applies.
52
87
  */
88
+ // Shared by IDEs that use `.agents/skills/` (Codex, Antigravity 2.0, Gemini CLI, Warp, etc.).
89
+ // This is the emerging standard location for workspace-level skills.
90
+ const AGENTS_SKILL = `---
91
+ name: opencrew
92
+ description: Run opencrew — multi-agent orchestration. Use when the user types /opencrew or asks to create, run, or manage crews.
93
+ ---
94
+
95
+ ${BRIDGE}
96
+ `;
97
+
53
98
  export const IDES = [
54
99
  {
55
100
  id: 'claude-code',
@@ -64,7 +109,7 @@ export const IDES = [
64
109
  label: 'Codex (OpenAI)',
65
110
  // Codex reads AGENTS.md natively — always shipped. Add a slash-style helper too.
66
111
  files: [
67
- { path: '.agents/skills/opencrew/SKILL.md', content: `---\nname: opencrew\ndescription: Run opencrew — multi-agent orchestration. Use when the user types /opencrew or asks to create, run, or manage crews.\n---\n\n${BRIDGE}\n` },
112
+ { path: '.agents/skills/opencrew/SKILL.md', content: AGENTS_SKILL },
68
113
  ],
69
114
  },
70
115
  {
@@ -96,18 +141,30 @@ export const IDES = [
96
141
  path: '.agent/rules/opencrew.md',
97
142
  content: `---\nname: opencrew\n---\n\n${BRIDGE}\n\n## Antigravity specifics\n\n- This environment does not support background/parallel subagents. Run all tasks inline and\n sequentially — never announce parallel work and then skip it.\n- Ask only one question per message; present options as a numbered list.\n`,
98
143
  },
99
- { path: '.agent/workflows/opencrew.md', content: render('opencrew Workflow (Antigravity)') },
144
+ { path: '.agent/workflows/opencrew.md', content: `---\nname: opencrew\ndescription: opencrew — multi-agent orchestration. Use when the user types /opencrew or asks to create, run, or manage crews.\n---\n\n${render('opencrew Workflow (Antigravity)')}` },
145
+ // .agents/ (plural) — standard location for Antigravity 2.0+ and CLI.
146
+ // Skills are discovered by the IDE, workflows appear as slash commands.
147
+ { path: '.agents/skills/opencrew/SKILL.md', content: AGENTS_SKILL },
148
+ { path: '.agents/workflows/opencrew.md', content: `---\nname: opencrew\ndescription: opencrew — multi-agent orchestration. Use when the user types /opencrew or asks to create, run, or manage crews.\n---\n\n${BRIDGE}\n` },
100
149
  ],
101
150
  },
102
151
  {
103
152
  id: 'gemini',
104
153
  label: 'Gemini CLI',
105
- files: [{ path: 'GEMINI.md', content: render('opencrew — Gemini CLI') }],
154
+ files: [
155
+ { path: 'GEMINI.md', content: render('opencrew — Gemini CLI') },
156
+ // Gemini CLI also reads .agents/skills/ for slash commands.
157
+ { path: '.agents/skills/opencrew/SKILL.md', content: AGENTS_SKILL },
158
+ ],
106
159
  },
107
160
  {
108
161
  id: 'qwen',
109
162
  label: 'Qwen Code',
110
- files: [{ path: 'QWEN.md', content: render('opencrew — Qwen Code') }],
163
+ files: [
164
+ { path: 'QWEN.md', content: render('opencrew — Qwen Code') },
165
+ // Qwen Code also reads .agents/skills/ for slash commands.
166
+ { path: '.agents/skills/opencrew/SKILL.md', content: AGENTS_SKILL },
167
+ ],
111
168
  },
112
169
  {
113
170
  id: 'trae',
@@ -1,9 +1,9 @@
1
- {
2
- "_comment": "The Playwright MCP version is pinned for stability. To upgrade: npx @playwright/mcp@latest --version, then update the version below and run 'npx @aksp/opencrew update' to refresh.",
3
- "mcpServers": {
4
- "playwright": {
5
- "command": "npx",
6
- "args": ["@playwright/mcp@0.0.78", "--config", "_opencrew/config/playwright.config.json"]
7
- }
8
- }
9
- }
1
+ {
2
+ "_comment": "The Playwright MCP version is pinned for stability. To upgrade: npx @playwright/mcp@latest --version, then update the version below and run 'npx @aksp/opencrew update' to refresh.",
3
+ "mcpServers": {
4
+ "playwright": {
5
+ "command": "npx",
6
+ "args": ["@playwright/mcp@0.0.78", "--config", "_opencrew/config/playwright.config.json"]
7
+ }
8
+ }
9
+ }