@aksp/opencrew 1.2.2 → 1.3.0
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 +139 -139
- package/README.md +150 -150
- package/package.json +63 -63
- package/src/cli.js +136 -136
- package/src/commands/init.js +125 -103
- package/src/commands/update.js +87 -77
- package/src/lib/fsx.js +127 -76
- package/templates/.mcp.json +9 -9
- package/templates/AGENTS.md +133 -133
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/_memory/preferences.md +11 -10
- package/templates/_opencrew/agents/copywriter.agent.md +66 -0
- package/templates/_opencrew/agents/designer.agent.md +65 -0
- package/templates/_opencrew/agents/researcher.agent.md +95 -0
- package/templates/_opencrew/agents/reviewer.agent.md +76 -0
- package/templates/_opencrew/agents/strategist.agent.md +64 -0
- package/templates/_opencrew/core/architect.agent.yaml +1 -1
- package/templates/_opencrew/core/prompts/build.prompt.md +614 -586
- package/templates/_opencrew/core/prompts/design.prompt.md +254 -26
- package/templates/_opencrew/core/prompts/discovery.prompt.md +42 -1
- package/templates/_opencrew/core/prompts/export.prompt.md +133 -0
- package/templates/_opencrew/core/prompts/repair.prompt.md +119 -119
- package/templates/_opencrew/core/prompts/sherlock-seo.md +216 -0
- package/templates/_opencrew/core/prompts/sherlock-shared.md +73 -1
- package/templates/_opencrew/core/prompts/sherlock-trends.md +238 -0
- package/templates/_opencrew/core/prompts/sherlock-web.md +220 -0
- package/templates/_opencrew/core/runner.pipeline.md +729 -642
- package/templates/_opencrew/core/skills.engine.md +490 -429
- package/templates/crews/blog-semanal/discovery.template.yaml +35 -0
- package/templates/crews/instagram-carrossel/discovery.template.yaml +35 -0
- package/templates/crews/lancamento-produto/discovery.template.yaml +39 -0
- package/templates/crews/newsletter-mensal/discovery.template.yaml +29 -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/package.json
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@aksp/opencrew",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"publishConfig": {
|
|
5
|
-
"access": "public"
|
|
6
|
-
},
|
|
7
|
-
"description": "A reworked build of OpenSquad (by Renato Asse) — create AI agent crews that work together, right from your IDE. Multi-agent orchestration with an npm installer.",
|
|
8
|
-
"type": "module",
|
|
9
|
-
"bin": {
|
|
10
|
-
"opencrew": "bin/opencrew.js"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"bin/",
|
|
14
|
-
"src/",
|
|
15
|
-
"templates/",
|
|
16
|
-
"README.md",
|
|
17
|
-
"LICENSE",
|
|
18
|
-
"CHANGELOG.md"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"start": "node bin/opencrew.js",
|
|
22
|
-
"test": "node --test tests/cli.test.js tests/docs.test.js tests/fsx.test.js tests/ides.test.js tests/init.test.js tests/paths.test.js tests/ui.test.js tests/update.test.js",
|
|
23
|
-
"lint": "eslint src/ tests/ scripts/",
|
|
24
|
-
"version": "node scripts/stamp-version.js && git add templates/_opencrew/.opencrew-version"
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"ai-agents",
|
|
28
|
-
"multi-agent",
|
|
29
|
-
"orchestration",
|
|
30
|
-
"crews",
|
|
31
|
-
"claude-code",
|
|
32
|
-
"cursor",
|
|
33
|
-
"codex",
|
|
34
|
-
"gemini",
|
|
35
|
-
"opencode",
|
|
36
|
-
"cli"
|
|
37
|
-
],
|
|
38
|
-
"author": {
|
|
39
|
-
"name": "aksp",
|
|
40
|
-
"url": "https://www.npmjs.com/~aksp"
|
|
41
|
-
},
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/alberthpalhares/opencrew.git"
|
|
45
|
-
},
|
|
46
|
-
"homepage": "https://github.com/alberthpalhares/opencrew#readme",
|
|
47
|
-
"bugs": {
|
|
48
|
-
"url": "https://github.com/alberthpalhares/opencrew/issues"
|
|
49
|
-
},
|
|
50
|
-
"license": "MIT",
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=20.0.0"
|
|
53
|
-
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"@inquirer/checkbox": "^5.1.0",
|
|
56
|
-
"@inquirer/confirm": "^5.1.0"
|
|
57
|
-
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@eslint/js": "^9.0.0",
|
|
60
|
-
"eslint": "^9.0.0",
|
|
61
|
-
"globals": "^17.9.0"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@aksp/opencrew",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "A reworked build of OpenSquad (by Renato Asse) — create AI agent crews that work together, right from your IDE. Multi-agent orchestration with an npm installer.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"opencrew": "bin/opencrew.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"templates/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"CHANGELOG.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"start": "node bin/opencrew.js",
|
|
22
|
+
"test": "node --test tests/cli.test.js tests/docs.test.js tests/fsx.test.js tests/ides.test.js tests/init.test.js tests/paths.test.js tests/ui.test.js tests/update.test.js",
|
|
23
|
+
"lint": "eslint src/ tests/ scripts/",
|
|
24
|
+
"version": "node scripts/stamp-version.js && git add templates/_opencrew/.opencrew-version"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"ai-agents",
|
|
28
|
+
"multi-agent",
|
|
29
|
+
"orchestration",
|
|
30
|
+
"crews",
|
|
31
|
+
"claude-code",
|
|
32
|
+
"cursor",
|
|
33
|
+
"codex",
|
|
34
|
+
"gemini",
|
|
35
|
+
"opencode",
|
|
36
|
+
"cli"
|
|
37
|
+
],
|
|
38
|
+
"author": {
|
|
39
|
+
"name": "aksp",
|
|
40
|
+
"url": "https://www.npmjs.com/~aksp"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/alberthpalhares/opencrew.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/alberthpalhares/opencrew#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/alberthpalhares/opencrew/issues"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20.0.0"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@inquirer/checkbox": "^5.1.0",
|
|
56
|
+
"@inquirer/confirm": "^5.1.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@eslint/js": "^9.0.0",
|
|
60
|
+
"eslint": "^9.0.0",
|
|
61
|
+
"globals": "^17.9.0"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
import { readJson } from './lib/fsx.js';
|
|
2
|
-
import { packageJsonPath } from './lib/paths.js';
|
|
3
|
-
import { allIdeIds } from './lib/ides.js';
|
|
4
|
-
import { init } from './commands/init.js';
|
|
5
|
-
import { update } from './commands/update.js';
|
|
6
|
-
import { c, log, err, warn, info } from './lib/ui.js';
|
|
7
|
-
|
|
8
|
-
// Extract the minimum required Node version from an engines.node range string.
|
|
9
|
-
// Handles: ">=20.0.0", "^20.5", ">=18.0.0 || >=20.0.0", plain "20.0.0".
|
|
10
|
-
function minNodeVersion(range) {
|
|
11
|
-
// Split on || and take the lowest version (user is expected to meet at least one).
|
|
12
|
-
const parts = range.split(/\s*\|\|\s*/);
|
|
13
|
-
let lowest = null;
|
|
14
|
-
for (const part of parts) {
|
|
15
|
-
const v = part.replace(/[^0-9.]/g, '');
|
|
16
|
-
if (!v) continue;
|
|
17
|
-
if (!lowest || lt(v, lowest)) lowest = v;
|
|
18
|
-
}
|
|
19
|
-
return lowest;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Simple semver comparison (no prerelease tags). Returns true if a < b.
|
|
23
|
-
function lt(a, b) {
|
|
24
|
-
const pa = a.split('.').map(Number);
|
|
25
|
-
const pb = b.split('.').map(Number);
|
|
26
|
-
for (let i = 0; i < 3; i++) {
|
|
27
|
-
const na = pa[i] || 0;
|
|
28
|
-
const nb = pb[i] || 0;
|
|
29
|
-
if (na !== nb) return na < nb;
|
|
30
|
-
}
|
|
31
|
-
return false; // equal
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function parseArgs(argv) {
|
|
35
|
-
const opts = { _: [] };
|
|
36
|
-
for (const a of argv) {
|
|
37
|
-
if (a.startsWith('--')) {
|
|
38
|
-
const eq = a.indexOf('=');
|
|
39
|
-
const k = eq > -1 ? a.slice(2, eq) : a.slice(2);
|
|
40
|
-
const v = eq > -1 ? a.slice(eq + 1) : true;
|
|
41
|
-
opts[k] = v;
|
|
42
|
-
} else if (a.startsWith('-') && a.length === 2) {
|
|
43
|
-
// Short flags: -y → yes, -v → version, -h → help
|
|
44
|
-
const short = a[1];
|
|
45
|
-
opts[short] = true;
|
|
46
|
-
} else {
|
|
47
|
-
opts._.push(a);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// Normalize short flags to their long-form equivalents.
|
|
51
|
-
if (opts.y) opts.yes = true;
|
|
52
|
-
if (opts.v) opts.version = true;
|
|
53
|
-
if (opts.h) opts.help = true;
|
|
54
|
-
return opts;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function help(version) {
|
|
58
|
-
log(`
|
|
59
|
-
${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — create AI agent crews that work together
|
|
60
|
-
|
|
61
|
-
${c.bold('Usage')}
|
|
62
|
-
npx @aksp/opencrew <command> [options]
|
|
63
|
-
|
|
64
|
-
${c.bold('Commands')}
|
|
65
|
-
init Scaffold an opencrew workspace in the current folder
|
|
66
|
-
update Refresh the framework (keeps your crews, memory and .env)
|
|
67
|
-
upgrade Alias for update
|
|
68
|
-
help Show this help
|
|
69
|
-
version Print the version
|
|
70
|
-
|
|
71
|
-
${c.bold('Options for init')}
|
|
72
|
-
--ide=a,b Preselect IDEs (skip the prompt). Valid: ${allIdeIds().join(', ')}
|
|
73
|
-
--all Configure every supported IDE
|
|
74
|
-
--yes, -y Non-interactive; accept defaults
|
|
75
|
-
|
|
76
|
-
${c.bold('Options for update')}
|
|
77
|
-
--check Dry-run: report whether an update is available without making changes
|
|
78
|
-
|
|
79
|
-
${c.bold('Examples')}
|
|
80
|
-
npx @aksp/opencrew init
|
|
81
|
-
npx @aksp/opencrew init --ide=claude-code,codex
|
|
82
|
-
npx @aksp/opencrew init --all
|
|
83
|
-
npx @aksp/opencrew init -y
|
|
84
|
-
npx @aksp/opencrew update
|
|
85
|
-
npx @aksp/opencrew update --check
|
|
86
|
-
`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function run(argv) {
|
|
90
|
-
const opts = parseArgs(argv);
|
|
91
|
-
|
|
92
|
-
let version = 'unknown';
|
|
93
|
-
let engines = {};
|
|
94
|
-
try {
|
|
95
|
-
const pkg = await readJson(packageJsonPath);
|
|
96
|
-
version = pkg.version;
|
|
97
|
-
engines = pkg.engines || {};
|
|
98
|
-
} catch {
|
|
99
|
-
err('Could not read package.json. The installation may be corrupted.');
|
|
100
|
-
info('Try reinstalling: npm install @aksp/opencrew');
|
|
101
|
-
process.exitCode = 1;
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Validate Node version against engines.node requirement.
|
|
106
|
-
if (engines.node) {
|
|
107
|
-
const required = minNodeVersion(engines.node);
|
|
108
|
-
const current = process.versions.node;
|
|
109
|
-
if (required && lt(current, required)) {
|
|
110
|
-
warn(`opencrew requires Node.js ${engines.node}. You have v${current}.`);
|
|
111
|
-
info(`Upgrade Node or use a compatible version.`);
|
|
112
|
-
process.exitCode = 1;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const cmd = opts._[0] || (opts.help ? 'help' : 'init');
|
|
118
|
-
|
|
119
|
-
switch (cmd) {
|
|
120
|
-
case 'init':
|
|
121
|
-
return init(opts);
|
|
122
|
-
case 'update':
|
|
123
|
-
case 'upgrade':
|
|
124
|
-
return update(opts);
|
|
125
|
-
case 'version':
|
|
126
|
-
case '--version':
|
|
127
|
-
return log(version);
|
|
128
|
-
case 'help':
|
|
129
|
-
case '--help':
|
|
130
|
-
return help(version);
|
|
131
|
-
default:
|
|
132
|
-
err(`Unknown command: ${cmd}`);
|
|
133
|
-
help(version);
|
|
134
|
-
process.exitCode = 1;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
1
|
+
import { readJson } from './lib/fsx.js';
|
|
2
|
+
import { packageJsonPath } from './lib/paths.js';
|
|
3
|
+
import { allIdeIds } from './lib/ides.js';
|
|
4
|
+
import { init } from './commands/init.js';
|
|
5
|
+
import { update } from './commands/update.js';
|
|
6
|
+
import { c, log, err, warn, info } from './lib/ui.js';
|
|
7
|
+
|
|
8
|
+
// Extract the minimum required Node version from an engines.node range string.
|
|
9
|
+
// Handles: ">=20.0.0", "^20.5", ">=18.0.0 || >=20.0.0", plain "20.0.0".
|
|
10
|
+
function minNodeVersion(range) {
|
|
11
|
+
// Split on || and take the lowest version (user is expected to meet at least one).
|
|
12
|
+
const parts = range.split(/\s*\|\|\s*/);
|
|
13
|
+
let lowest = null;
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
const v = part.replace(/[^0-9.]/g, '');
|
|
16
|
+
if (!v) continue;
|
|
17
|
+
if (!lowest || lt(v, lowest)) lowest = v;
|
|
18
|
+
}
|
|
19
|
+
return lowest;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Simple semver comparison (no prerelease tags). Returns true if a < b.
|
|
23
|
+
function lt(a, b) {
|
|
24
|
+
const pa = a.split('.').map(Number);
|
|
25
|
+
const pb = b.split('.').map(Number);
|
|
26
|
+
for (let i = 0; i < 3; i++) {
|
|
27
|
+
const na = pa[i] || 0;
|
|
28
|
+
const nb = pb[i] || 0;
|
|
29
|
+
if (na !== nb) return na < nb;
|
|
30
|
+
}
|
|
31
|
+
return false; // equal
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseArgs(argv) {
|
|
35
|
+
const opts = { _: [] };
|
|
36
|
+
for (const a of argv) {
|
|
37
|
+
if (a.startsWith('--')) {
|
|
38
|
+
const eq = a.indexOf('=');
|
|
39
|
+
const k = eq > -1 ? a.slice(2, eq) : a.slice(2);
|
|
40
|
+
const v = eq > -1 ? a.slice(eq + 1) : true;
|
|
41
|
+
opts[k] = v;
|
|
42
|
+
} else if (a.startsWith('-') && a.length === 2) {
|
|
43
|
+
// Short flags: -y → yes, -v → version, -h → help
|
|
44
|
+
const short = a[1];
|
|
45
|
+
opts[short] = true;
|
|
46
|
+
} else {
|
|
47
|
+
opts._.push(a);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Normalize short flags to their long-form equivalents.
|
|
51
|
+
if (opts.y) opts.yes = true;
|
|
52
|
+
if (opts.v) opts.version = true;
|
|
53
|
+
if (opts.h) opts.help = true;
|
|
54
|
+
return opts;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function help(version) {
|
|
58
|
+
log(`
|
|
59
|
+
${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — create AI agent crews that work together
|
|
60
|
+
|
|
61
|
+
${c.bold('Usage')}
|
|
62
|
+
npx @aksp/opencrew <command> [options]
|
|
63
|
+
|
|
64
|
+
${c.bold('Commands')}
|
|
65
|
+
init Scaffold an opencrew workspace in the current folder
|
|
66
|
+
update Refresh the framework (keeps your crews, memory and .env)
|
|
67
|
+
upgrade Alias for update
|
|
68
|
+
help Show this help
|
|
69
|
+
version Print the version
|
|
70
|
+
|
|
71
|
+
${c.bold('Options for init')}
|
|
72
|
+
--ide=a,b Preselect IDEs (skip the prompt). Valid: ${allIdeIds().join(', ')}
|
|
73
|
+
--all Configure every supported IDE
|
|
74
|
+
--yes, -y Non-interactive; accept defaults
|
|
75
|
+
|
|
76
|
+
${c.bold('Options for update')}
|
|
77
|
+
--check Dry-run: report whether an update is available without making changes
|
|
78
|
+
|
|
79
|
+
${c.bold('Examples')}
|
|
80
|
+
npx @aksp/opencrew init
|
|
81
|
+
npx @aksp/opencrew init --ide=claude-code,codex
|
|
82
|
+
npx @aksp/opencrew init --all
|
|
83
|
+
npx @aksp/opencrew init -y
|
|
84
|
+
npx @aksp/opencrew update
|
|
85
|
+
npx @aksp/opencrew update --check
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function run(argv) {
|
|
90
|
+
const opts = parseArgs(argv);
|
|
91
|
+
|
|
92
|
+
let version = 'unknown';
|
|
93
|
+
let engines = {};
|
|
94
|
+
try {
|
|
95
|
+
const pkg = await readJson(packageJsonPath);
|
|
96
|
+
version = pkg.version;
|
|
97
|
+
engines = pkg.engines || {};
|
|
98
|
+
} catch {
|
|
99
|
+
err('Could not read package.json. The installation may be corrupted.');
|
|
100
|
+
info('Try reinstalling: npm install @aksp/opencrew');
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Validate Node version against engines.node requirement.
|
|
106
|
+
if (engines.node) {
|
|
107
|
+
const required = minNodeVersion(engines.node);
|
|
108
|
+
const current = process.versions.node;
|
|
109
|
+
if (required && lt(current, required)) {
|
|
110
|
+
warn(`opencrew requires Node.js ${engines.node}. You have v${current}.`);
|
|
111
|
+
info(`Upgrade Node or use a compatible version.`);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const cmd = opts._[0] || (opts.version ? 'version' : opts.help ? 'help' : 'init');
|
|
118
|
+
|
|
119
|
+
switch (cmd) {
|
|
120
|
+
case 'init':
|
|
121
|
+
return init(opts);
|
|
122
|
+
case 'update':
|
|
123
|
+
case 'upgrade':
|
|
124
|
+
return update(opts);
|
|
125
|
+
case 'version':
|
|
126
|
+
case '--version':
|
|
127
|
+
return log(version);
|
|
128
|
+
case 'help':
|
|
129
|
+
case '--help':
|
|
130
|
+
return help(version);
|
|
131
|
+
default:
|
|
132
|
+
err(`Unknown command: ${cmd}`);
|
|
133
|
+
help(version);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -1,103 +1,125 @@
|
|
|
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 } from '../lib/fsx.js';
|
|
5
|
-
import { ideById, allIdeIds } from '../lib/ides.js';
|
|
6
|
-
import { pickIdes } from '../lib/prompts.js';
|
|
7
|
-
import { c, log, info, ok, warn, step } from '../lib/ui.js';
|
|
8
|
-
|
|
9
|
-
export async function init(opts = {}) {
|
|
10
|
-
const target = process.cwd();
|
|
11
|
-
const pkg = await readJson(packageJsonPath);
|
|
12
|
-
const version = pkg.version;
|
|
13
|
-
|
|
14
|
-
log(`\n${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — scaffolding a crew workspace`);
|
|
15
|
-
log(c.dim(`Target: ${target}\n`));
|
|
16
|
-
|
|
17
|
-
const alreadyInstalled = await exists(path.join(target, '_opencrew', 'core'));
|
|
18
|
-
if (alreadyInstalled) {
|
|
19
|
-
warn('An opencrew workspace already exists here.');
|
|
20
|
-
info(`To update only the framework, use: ${c.cyan('npx @aksp/opencrew update')}`);
|
|
21
|
-
info(`To reinstall from scratch, delete _opencrew/ first, then run init again.`);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 1. Copy the framework payload (never clobber user work).
|
|
26
|
-
step('Installing framework files');
|
|
27
|
-
const copied = { count: 0 };
|
|
28
|
-
await copyDir(path.join(templatesDir, '_opencrew'), path.join(target, '_opencrew'), {
|
|
29
|
-
overwrite: false,
|
|
30
|
-
// Never ship stray logs or browser sessions; keep the empty dir via .gitkeep only.
|
|
31
|
-
skip: (rel) =>
|
|
32
|
-
(rel.startsWith('logs/') && rel !== 'logs/.gitkeep') ||
|
|
33
|
-
rel.startsWith('_browser_profile/'),
|
|
34
|
-
onCopy: () => (copied.count += 1),
|
|
35
|
-
});
|
|
36
|
-
await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), {
|
|
37
|
-
overwrite: false,
|
|
38
|
-
onCopy: () => (copied.count += 1),
|
|
39
|
-
});
|
|
40
|
-
await copyDir(path.join(templatesDir, 'crews'), path.join(target, 'crews'), {
|
|
41
|
-
overwrite: false,
|
|
42
|
-
});
|
|
43
|
-
ok(`Framework files ready (${copied.count} written, existing files preserved)`);
|
|
44
|
-
|
|
45
|
-
// 2.
|
|
46
|
-
step('Writing configuration');
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (ids
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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 { ideById, allIdeIds } from '../lib/ides.js';
|
|
6
|
+
import { pickIdes } from '../lib/prompts.js';
|
|
7
|
+
import { c, log, info, ok, warn, step } from '../lib/ui.js';
|
|
8
|
+
|
|
9
|
+
export async function init(opts = {}) {
|
|
10
|
+
const target = process.cwd();
|
|
11
|
+
const pkg = await readJson(packageJsonPath);
|
|
12
|
+
const version = pkg.version;
|
|
13
|
+
|
|
14
|
+
log(`\n${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — scaffolding a crew workspace`);
|
|
15
|
+
log(c.dim(`Target: ${target}\n`));
|
|
16
|
+
|
|
17
|
+
const alreadyInstalled = await exists(path.join(target, '_opencrew', 'core'));
|
|
18
|
+
if (alreadyInstalled) {
|
|
19
|
+
warn('An opencrew workspace already exists here.');
|
|
20
|
+
info(`To update only the framework, use: ${c.cyan('npx @aksp/opencrew update')}`);
|
|
21
|
+
info(`To reinstall from scratch, delete _opencrew/ first, then run init again.`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 1. Copy the framework payload (never clobber user work).
|
|
26
|
+
step('Installing framework files');
|
|
27
|
+
const copied = { count: 0 };
|
|
28
|
+
await copyDir(path.join(templatesDir, '_opencrew'), path.join(target, '_opencrew'), {
|
|
29
|
+
overwrite: false,
|
|
30
|
+
// Never ship stray logs or browser sessions; keep the empty dir via .gitkeep only.
|
|
31
|
+
skip: (rel) =>
|
|
32
|
+
(rel.startsWith('logs/') && rel !== 'logs/.gitkeep') ||
|
|
33
|
+
rel.startsWith('_browser_profile/'),
|
|
34
|
+
onCopy: () => (copied.count += 1),
|
|
35
|
+
});
|
|
36
|
+
await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), {
|
|
37
|
+
overwrite: false,
|
|
38
|
+
onCopy: () => (copied.count += 1),
|
|
39
|
+
});
|
|
40
|
+
await copyDir(path.join(templatesDir, 'crews'), path.join(target, 'crews'), {
|
|
41
|
+
overwrite: false,
|
|
42
|
+
});
|
|
43
|
+
ok(`Framework files ready (${copied.count} written, existing files preserved)`);
|
|
44
|
+
|
|
45
|
+
// 2. System doc + root configs.
|
|
46
|
+
step('Writing configuration');
|
|
47
|
+
|
|
48
|
+
// Full system definition lives in _opencrew/core/ — never at project root.
|
|
49
|
+
// The root AGENTS.md is just a thin bridge (like CLAUDE.md, GEMINI.md, etc.).
|
|
50
|
+
await writeFileSafe(path.join(target, '_opencrew', 'core', 'system.md'), await tpl('AGENTS.md'));
|
|
51
|
+
ok('_opencrew/core/system.md (full system definition)');
|
|
52
|
+
|
|
53
|
+
const agentsBridge = '# opencrew\n\n'
|
|
54
|
+
+ 'The opencrew system definition lives at `_opencrew/core/system.md`.\n'
|
|
55
|
+
+ 'Read that file and adopt the opencrew system role — follow all initialization,\n'
|
|
56
|
+
+ 'command routing, and workflow instructions defined there.\n\n'
|
|
57
|
+
+ 'Type `/opencrew` to open the main menu.\n';
|
|
58
|
+
|
|
59
|
+
const agentsResult = await writeBridgeFile(path.join(target, 'AGENTS.md'), agentsBridge);
|
|
60
|
+
if (agentsResult.merged) info('AGENTS.md (merged — existing content preserved)');
|
|
61
|
+
else ok('AGENTS.md (bridge to system.md)');
|
|
62
|
+
|
|
63
|
+
const mcpWritten = await writeFileSafe(path.join(target, '.mcp.json'), await tpl('.mcp.json'), {
|
|
64
|
+
overwrite: false,
|
|
65
|
+
});
|
|
66
|
+
info(mcpWritten ? '.mcp.json' : '.mcp.json (kept existing)');
|
|
67
|
+
|
|
68
|
+
await writeFileSafe(path.join(target, '.env.example'), await tpl('.env.example'));
|
|
69
|
+
const giWritten = await writeFileSafe(path.join(target, '.gitignore'), await tpl('gitignore'), {
|
|
70
|
+
overwrite: false,
|
|
71
|
+
});
|
|
72
|
+
info(giWritten ? '.gitignore' : '.gitignore (kept existing)');
|
|
73
|
+
|
|
74
|
+
// 3. IDE bridge files.
|
|
75
|
+
step('Configuring AI IDEs');
|
|
76
|
+
let ids = normalizeIdes(opts.ide);
|
|
77
|
+
if (opts.all) ids = allIdeIds();
|
|
78
|
+
if (opts.yes) ids = allIdeIds();
|
|
79
|
+
if (!ids) ids = await pickIdes();
|
|
80
|
+
|
|
81
|
+
for (const id of ids) {
|
|
82
|
+
const ide = ideById(id);
|
|
83
|
+
if (!ide) {
|
|
84
|
+
warn(`Unknown IDE "${id}" — skipped. Valid: ${allIdeIds().join(', ')}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
for (const f of ide.files) {
|
|
88
|
+
const fp = path.join(target, f.path);
|
|
89
|
+
// Files that require frontmatter at byte 0 (YAML, .mdc) must NOT be wrapped in HTML markers.
|
|
90
|
+
const hasFrontmatter = f.content.startsWith('---');
|
|
91
|
+
if (hasFrontmatter) {
|
|
92
|
+
await writeFileSafe(fp, f.content, { overwrite: false });
|
|
93
|
+
} else {
|
|
94
|
+
const result = await writeBridgeFile(fp, f.content);
|
|
95
|
+
if (result.merged) info(`${f.path} (merged — existing content preserved)`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
ok(`${ide.label} → ${ide.files.map((f) => f.path).join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (ids.includes('claude-code')) {
|
|
102
|
+
warn(`opencrew ships its own Playwright MCP server (.mcp.json) — disable Claude Code's`);
|
|
103
|
+
warn(`native Playwright plugin/extension to avoid the two conflicting.`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 4. Version stamp.
|
|
107
|
+
await fs.writeFile(path.join(target, '_opencrew', '.opencrew-version'), version + '\n');
|
|
108
|
+
|
|
109
|
+
// 5. Done.
|
|
110
|
+
log(`\n${c.green(c.bold('Done!'))} opencrew is installed.\n`);
|
|
111
|
+
log(`${c.bold('Next steps:')}`);
|
|
112
|
+
log(` 1. Open this folder in your AI IDE.`);
|
|
113
|
+
log(` 2. Type ${c.cyan('/opencrew')} to start (first run sets up your company profile).`);
|
|
114
|
+
log(` No API keys needed up front — opencrew asks for them in chat only if a skill you use requires one.\n`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function tpl(name) {
|
|
118
|
+
return fs.readFile(path.join(templatesDir, name), 'utf8');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeIdes(val) {
|
|
122
|
+
if (!val || val === true) return null;
|
|
123
|
+
const list = Array.isArray(val) ? val : String(val).split(',');
|
|
124
|
+
return list.map((s) => s.trim()).filter(Boolean);
|
|
125
|
+
}
|