@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.
- package/CHANGELOG.md +231 -139
- package/README.md +286 -150
- package/package.json +63 -63
- package/src/cli.js +137 -136
- package/src/commands/init.js +159 -125
- package/src/commands/update.js +95 -87
- package/src/lib/fsx.js +127 -127
- package/src/lib/ides.js +61 -4
- package/templates/.mcp.json +9 -9
- package/templates/AGENTS.md +139 -133
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/_memory/preferences.md +11 -11
- package/templates/_opencrew/core/prompts/build.prompt.md +633 -614
- package/templates/_opencrew/core/prompts/repair.prompt.md +119 -119
- package/templates/_opencrew/core/runner.pipeline.md +829 -729
- package/templates/_opencrew/core/skills.engine.md +490 -490
- package/templates/gitignore +1 -0
- package/templates/skills/README.md +22 -22
- package/templates/skills/catalog.json +61 -61
- package/templates/skills/instagram-publisher/SKILL.md +119 -119
package/src/commands/update.js
CHANGED
|
@@ -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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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:
|
|
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: [
|
|
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: [
|
|
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',
|
package/templates/.mcp.json
CHANGED
|
@@ -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
|
+
}
|