@aksp/opencrew 1.4.1 → 1.5.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 +116 -0
- package/README.md +38 -17
- package/bin/opencrew.js +4 -4
- package/package.json +4 -3
- package/src/cli.js +93 -43
- package/src/commands/init.js +79 -53
- package/src/commands/update.js +44 -17
- package/src/lib/errors.js +12 -0
- package/src/lib/fsx.js +18 -9
- package/src/lib/ides.js +9 -34
- package/templates/AGENTS.md +42 -63
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/core/best-practices/copywriting.md +4 -1
- package/templates/_opencrew/core/best-practices/image-design.md +5 -5
- package/templates/_opencrew/core/best-practices/instagram-feed.md +4 -4
- package/templates/_opencrew/core/best-practices/instagram-reels.md +1 -1
- package/templates/_opencrew/core/best-practices/review.md +7 -0
- package/templates/_opencrew/core/best-practices/social-networks-publishing.md +8 -8
- package/templates/_opencrew/core/prompts/build.prompt.md +25 -0
- package/templates/_opencrew/core/prompts/design.prompt.md +9 -5
- package/templates/_opencrew/core/runner.pipeline.md +56 -7
- package/templates/_opencrew/core/scripts/verificar/leitura.mjs +99 -0
- package/templates/_opencrew/core/scripts/verificar/regras.mjs +127 -0
- package/templates/_opencrew/core/scripts/verificar.mjs +118 -0
- package/templates/gitignore +3 -1
- package/templates/skills/image-ai-generator/SKILL.md +9 -5
- package/templates/skills/image-creator/SKILL.md +5 -3
- package/templates/skills/image-fetcher/SKILL.md +1 -1
- package/templates/skills/instagram-publisher/SKILL.md +34 -16
- package/templates/skills/instagram-publisher/scripts/publish.js +58 -27
- package/templates/skills/template-designer/SKILL.md +3 -3
- package/templates/skills/template-designer/base-templates/model-a.html +1 -1
- package/templates/skills/template-designer/base-templates/model-b.html +1 -1
- package/templates/skills/template-designer/base-templates/model-c.html +1 -1
package/src/commands/init.js
CHANGED
|
@@ -2,26 +2,30 @@ import path from 'node:path';
|
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
3
|
import { templatesDir, packageJsonPath } from '../lib/paths.js';
|
|
4
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';
|
|
5
|
+
import { ideById, allIdeIds, AGENTS_BRIDGE } from '../lib/ides.js';
|
|
6
|
+
import { pickIdes as promptIdes } from '../lib/prompts.js';
|
|
7
|
+
import { UsageError } from '../lib/errors.js';
|
|
7
8
|
import { c, log, info, ok, warn, step } from '../lib/ui.js';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
const STAMP = path.join('_opencrew', '.opencrew-version');
|
|
11
|
+
// .gitignore / .env.example belong to the user: opencrew only owns a marked block at the end.
|
|
12
|
+
const SHARED_BLOCK = { comment: 'hash', position: 'append' };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} opts parsed CLI options
|
|
16
|
+
* @param {{ pickIdes?: () => Promise<string[]> }} deps injectable for tests
|
|
17
|
+
*/
|
|
18
|
+
export async function init(opts = {}, { pickIdes = promptIdes } = {}) {
|
|
10
19
|
const target = process.cwd();
|
|
11
20
|
const pkg = await readJson(packageJsonPath);
|
|
12
21
|
const version = pkg.version;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const alreadyInstalled = await exists(path.join(target, '_opencrew', 'core'));
|
|
22
|
+
const state = await workspaceState(target);
|
|
16
23
|
|
|
17
24
|
// --repair-bridges mode: regenerate IDE bridge files in an existing workspace.
|
|
18
|
-
if (
|
|
25
|
+
if (opts['repair-bridges'] && state !== 'none') {
|
|
26
|
+
const ids = await resolveIdes(opts, async () => allIdeIds());
|
|
19
27
|
log(`\n${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — repairing IDE bridges`);
|
|
20
28
|
log(c.dim(`Target: ${target}\n`));
|
|
21
|
-
|
|
22
|
-
let ids = normalizeIdes(opts.ide);
|
|
23
|
-
if (opts.all) ids = allIdeIds();
|
|
24
|
-
if (opts.yes || !ids) ids = allIdeIds();
|
|
25
29
|
await writeBridges(target, ids, { overwrite: true });
|
|
26
30
|
|
|
27
31
|
log(`\n${c.green(c.bold('Done!'))} IDE bridges regenerated.\n`);
|
|
@@ -29,7 +33,7 @@ export async function init(opts = {}) {
|
|
|
29
33
|
return;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
if (
|
|
36
|
+
if (state === 'complete') {
|
|
33
37
|
warn('An opencrew workspace already exists here.');
|
|
34
38
|
info(`To update only the framework, use: ${c.cyan('npx @aksp/opencrew update')}`);
|
|
35
39
|
info(`To repair IDE bridges, use: ${c.cyan('npx @aksp/opencrew init --repair-bridges')}`);
|
|
@@ -37,28 +41,18 @@ export async function init(opts = {}) {
|
|
|
37
41
|
return;
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
// Every choice is resolved BEFORE the first write: a bad --ide or Ctrl+C on the
|
|
45
|
+
// prompt leaves the folder exactly as it was.
|
|
46
|
+
const ids = await resolveIdes(opts, pickIdes);
|
|
47
|
+
|
|
40
48
|
log(`\n${c.bold(c.cyan('opencrew'))} ${c.dim('v' + version)} — scaffolding a crew workspace`);
|
|
41
49
|
log(c.dim(`Target: ${target}\n`));
|
|
50
|
+
if (state === 'partial') info('A previous install was interrupted — resuming (existing files are kept).');
|
|
42
51
|
|
|
43
52
|
// 1. Copy the framework payload (never clobber user work).
|
|
44
53
|
step('Installing framework files');
|
|
45
|
-
const copied =
|
|
46
|
-
|
|
47
|
-
overwrite: false,
|
|
48
|
-
// Never ship stray logs or browser sessions; keep the empty dir via .gitkeep only.
|
|
49
|
-
skip: (rel) =>
|
|
50
|
-
(rel.startsWith('logs/') && rel !== 'logs/.gitkeep') ||
|
|
51
|
-
rel.startsWith('_browser_profile/'),
|
|
52
|
-
onCopy: () => (copied.count += 1),
|
|
53
|
-
});
|
|
54
|
-
await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), {
|
|
55
|
-
overwrite: false,
|
|
56
|
-
onCopy: () => (copied.count += 1),
|
|
57
|
-
});
|
|
58
|
-
await copyDir(path.join(templatesDir, 'crews'), path.join(target, 'crews'), {
|
|
59
|
-
overwrite: false,
|
|
60
|
-
});
|
|
61
|
-
ok(`Framework files ready (${copied.count} written, existing files preserved)`);
|
|
54
|
+
const copied = await installPayload(target);
|
|
55
|
+
ok(`Framework files ready (${copied} written, existing files preserved)`);
|
|
62
56
|
|
|
63
57
|
// 2. System doc + root configs.
|
|
64
58
|
step('Writing configuration');
|
|
@@ -68,13 +62,7 @@ export async function init(opts = {}) {
|
|
|
68
62
|
await writeFileSafe(path.join(target, '_opencrew', 'core', 'system.md'), await tpl('AGENTS.md'));
|
|
69
63
|
ok('_opencrew/core/system.md (full system definition)');
|
|
70
64
|
|
|
71
|
-
const
|
|
72
|
-
+ 'The opencrew system definition lives at `_opencrew/core/system.md`.\n'
|
|
73
|
-
+ 'Read that file and adopt the opencrew system role — follow all initialization,\n'
|
|
74
|
-
+ 'command routing, and workflow instructions defined there.\n\n'
|
|
75
|
-
+ 'Type `/opencrew` to open the main menu.\n';
|
|
76
|
-
|
|
77
|
-
const agentsResult = await writeBridgeFile(path.join(target, 'AGENTS.md'), agentsBridge);
|
|
65
|
+
const agentsResult = await writeBridgeFile(path.join(target, 'AGENTS.md'), AGENTS_BRIDGE);
|
|
78
66
|
if (agentsResult.merged) info('AGENTS.md (merged — existing content preserved)');
|
|
79
67
|
else ok('AGENTS.md (bridge to system.md)');
|
|
80
68
|
|
|
@@ -83,18 +71,13 @@ export async function init(opts = {}) {
|
|
|
83
71
|
});
|
|
84
72
|
info(mcpWritten ? '.mcp.json' : '.mcp.json (kept existing)');
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
info(giWritten ? '.gitignore' : '.gitignore (kept existing)');
|
|
74
|
+
for (const [file, template] of [['.env.example', '.env.example'], ['.gitignore', 'gitignore']]) {
|
|
75
|
+
const res = await writeBridgeFile(path.join(target, file), await tpl(template), SHARED_BLOCK);
|
|
76
|
+
info(res.merged ? `${file} (opencrew block added at the end — your lines kept)` : file);
|
|
77
|
+
}
|
|
91
78
|
|
|
92
79
|
// 3. IDE bridge files.
|
|
93
80
|
step('Configuring AI IDEs');
|
|
94
|
-
let ids = normalizeIdes(opts.ide);
|
|
95
|
-
if (opts.all) ids = allIdeIds();
|
|
96
|
-
if (opts.yes) ids = allIdeIds();
|
|
97
|
-
if (!ids) ids = await pickIdes();
|
|
98
81
|
await writeBridges(target, ids, { overwrite: false });
|
|
99
82
|
|
|
100
83
|
if (ids.includes('claude-code')) {
|
|
@@ -102,8 +85,8 @@ export async function init(opts = {}) {
|
|
|
102
85
|
warn(`native Playwright plugin/extension to avoid the two conflicting.`);
|
|
103
86
|
}
|
|
104
87
|
|
|
105
|
-
// 4. Version stamp.
|
|
106
|
-
await fs.writeFile(path.join(target,
|
|
88
|
+
// 4. Version stamp — written LAST: it is what marks the install as complete.
|
|
89
|
+
await fs.writeFile(path.join(target, STAMP), version + '\n');
|
|
107
90
|
|
|
108
91
|
// 5. Done.
|
|
109
92
|
log(`\n${c.green(c.bold('Done!'))} opencrew is installed.\n`);
|
|
@@ -113,10 +96,56 @@ export async function init(opts = {}) {
|
|
|
113
96
|
log(` No API keys needed up front — opencrew asks for them in chat only if a skill you use requires one.\n`);
|
|
114
97
|
}
|
|
115
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Copy the framework payload into `target` without overwriting anything.
|
|
101
|
+
* Never copies the version stamp: only a finished init writes it.
|
|
102
|
+
* @returns {Promise<number>} files written
|
|
103
|
+
*/
|
|
104
|
+
export async function installPayload(target) {
|
|
105
|
+
let count = 0;
|
|
106
|
+
const onCopy = () => (count += 1);
|
|
107
|
+
await copyDir(path.join(templatesDir, '_opencrew'), path.join(target, '_opencrew'), {
|
|
108
|
+
overwrite: false,
|
|
109
|
+
// Never ship stray logs, browser sessions or the template's own stamp.
|
|
110
|
+
skip: (rel) =>
|
|
111
|
+
(rel.startsWith('logs/') && rel !== 'logs/.gitkeep') ||
|
|
112
|
+
rel.startsWith('_browser_profile/') ||
|
|
113
|
+
rel === '.opencrew-version',
|
|
114
|
+
onCopy,
|
|
115
|
+
});
|
|
116
|
+
await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), { overwrite: false, onCopy });
|
|
117
|
+
await copyDir(path.join(templatesDir, 'crews'), path.join(target, 'crews'), { overwrite: false });
|
|
118
|
+
return count;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 'none' (no core) · 'partial' (core without stamp: interrupted install) · 'complete'. */
|
|
122
|
+
async function workspaceState(target) {
|
|
123
|
+
if (!(await exists(path.join(target, '_opencrew', 'core')))) return 'none';
|
|
124
|
+
return (await exists(path.join(target, STAMP))) ? 'complete' : 'partial';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Decide which IDEs to configure. --all / --yes → every IDE; --ide → validated list;
|
|
129
|
+
* nothing → `fallback()` (the interactive prompt). Throws UsageError if --ide names no
|
|
130
|
+
* valid IDE.
|
|
131
|
+
*/
|
|
132
|
+
async function resolveIdes(opts, fallback) {
|
|
133
|
+
if (opts.all || opts.yes) return allIdeIds();
|
|
134
|
+
const ids = normalizeIdes(opts.ide);
|
|
135
|
+
if (!ids) return fallback();
|
|
136
|
+
const invalid = ids.filter((id) => !ideById(id));
|
|
137
|
+
const valid = ids.filter((id) => ideById(id));
|
|
138
|
+
if (!valid.length) {
|
|
139
|
+
throw new UsageError(`Unknown IDE "${invalid.join('", "')}". Valid: ${allIdeIds().join(', ')}`);
|
|
140
|
+
}
|
|
141
|
+
for (const id of invalid) warn(`Unknown IDE "${id}" — skipped. Valid: ${allIdeIds().join(', ')}`);
|
|
142
|
+
return valid;
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
/**
|
|
117
146
|
* Write IDE bridge files to the target directory.
|
|
118
147
|
* @param {string} target — project root
|
|
119
|
-
* @param {string[]} ids — IDE ids to configure
|
|
148
|
+
* @param {string[]} ids — validated IDE ids to configure
|
|
120
149
|
* @param {{ overwrite: boolean }} opts
|
|
121
150
|
*/
|
|
122
151
|
async function writeBridges(target, ids, { overwrite }) {
|
|
@@ -124,10 +153,6 @@ async function writeBridges(target, ids, { overwrite }) {
|
|
|
124
153
|
|
|
125
154
|
for (const id of ids) {
|
|
126
155
|
const ide = ideById(id);
|
|
127
|
-
if (!ide) {
|
|
128
|
-
warn(`Unknown IDE "${id}" — skipped. Valid: ${allIdeIds().join(', ')}`);
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
156
|
for (const f of ide.files) {
|
|
132
157
|
if (writtenPaths.has(f.path)) {
|
|
133
158
|
info(`${f.path} (shared path — written once)`);
|
|
@@ -155,5 +180,6 @@ async function tpl(name) {
|
|
|
155
180
|
function normalizeIdes(val) {
|
|
156
181
|
if (!val || val === true) return null;
|
|
157
182
|
const list = Array.isArray(val) ? val : String(val).split(',');
|
|
158
|
-
|
|
183
|
+
const ids = list.map((s) => s.trim()).filter(Boolean);
|
|
184
|
+
return ids.length ? ids : null;
|
|
159
185
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -2,10 +2,12 @@ import path from 'node:path';
|
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
3
|
import { templatesDir, packageJsonPath } from '../lib/paths.js';
|
|
4
4
|
import { copyDir, exists, writeFileSafe, readJson, writeBridgeFile, readFile } from '../lib/fsx.js';
|
|
5
|
+
import { AGENTS_BRIDGE, LEAKED_STATUS_SECTION, ideById } from '../lib/ides.js';
|
|
5
6
|
import { c, log, info, ok, warn, step } from '../lib/ui.js';
|
|
6
7
|
|
|
7
8
|
// Update refreshes ONLY the framework. It never touches:
|
|
8
|
-
// crews/, _opencrew/_memory/, _opencrew/_browser_profile/, .env, IDE bridges
|
|
9
|
+
// crews/, _opencrew/_memory/, _opencrew/_browser_profile/, .env, IDE bridges
|
|
10
|
+
// (one exception: the CLAUDE.md block leaked by 1.4.0/1.4.1 — see removeLeakedStatusSection).
|
|
9
11
|
// Note: catalog skills (skills/<name>/ that ship with the package) ARE fully
|
|
10
12
|
// overwritten below — user edits to a catalog skill's own files are not preserved.
|
|
11
13
|
// Only skill directories that don't exist in the package's templates/skills/ at all
|
|
@@ -72,24 +74,49 @@ export async function update(opts = {}) {
|
|
|
72
74
|
await writeFileSafe(path.join(target, '_opencrew', 'core', 'system.md'), systemContent);
|
|
73
75
|
ok('_opencrew/core/system.md refreshed');
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
}
|
|
77
|
+
await refreshAgentsBridge(target);
|
|
78
|
+
await removeLeakedStatusSection(target);
|
|
91
79
|
|
|
80
|
+
// Stamp last: a crash above leaves the old version, so the next update retries.
|
|
92
81
|
await fs.writeFile(versionFile, version + '\n');
|
|
93
82
|
log(`\n${c.green(c.bold('Updated to v' + version))}.`);
|
|
94
83
|
log(c.dim('Your crews, memory, IDE bridges and .env were left untouched.\n'));
|
|
95
84
|
}
|
|
85
|
+
|
|
86
|
+
// Root AGENTS.md: create it if missing; a legacy full-system doc (pre-v1.3) is backed up
|
|
87
|
+
// byte for byte and replaced by the thin bridge; otherwise only the marked block changes.
|
|
88
|
+
async function refreshAgentsBridge(target) {
|
|
89
|
+
const agentsPath = path.join(target, 'AGENTS.md');
|
|
90
|
+
if (!(await exists(agentsPath))) {
|
|
91
|
+
await writeBridgeFile(agentsPath, AGENTS_BRIDGE);
|
|
92
|
+
ok('AGENTS.md (bridge created)');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const existing = await readFile(agentsPath);
|
|
96
|
+
if (existing.includes('# opencrew Instructions') && !existing.includes('<!-- opencrew:start -->')) {
|
|
97
|
+
const backup = await freeBackupPath(agentsPath);
|
|
98
|
+
await fs.copyFile(agentsPath, backup);
|
|
99
|
+
await writeFileSafe(agentsPath, AGENTS_BRIDGE);
|
|
100
|
+
ok(`AGENTS.md (migrated from legacy full-system to thin bridge — backed up to ${path.basename(backup)})`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await writeBridgeFile(agentsPath, AGENTS_BRIDGE);
|
|
104
|
+
ok('AGENTS.md refreshed');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function freeBackupPath(file) {
|
|
108
|
+
const bak = `${file}.bak`;
|
|
109
|
+
if (!(await exists(bak))) return bak;
|
|
110
|
+
return `${file}.bak-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 1.4.0/1.4.1 shipped the maintainer's STATUS.md workflow inside CLAUDE.md's opencrew
|
|
114
|
+
// block. Rewrite that block (only that block, only if the leak is there).
|
|
115
|
+
async function removeLeakedStatusSection(target) {
|
|
116
|
+
const claudePath = path.join(target, 'CLAUDE.md');
|
|
117
|
+
if (!(await exists(claudePath))) return;
|
|
118
|
+
if (!(await readFile(claudePath)).includes(LEAKED_STATUS_SECTION)) return;
|
|
119
|
+
const bridge = ideById('claude-code').files.find((f) => f.path === 'CLAUDE.md');
|
|
120
|
+
await writeBridgeFile(claudePath, bridge.content);
|
|
121
|
+
ok('CLAUDE.md (removed the STATUS.md section shipped by mistake in 1.4.0/1.4.1)');
|
|
122
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Error types the CLI turns into exit codes (see src/cli.js → exitCodeFor).
|
|
2
|
+
|
|
3
|
+
/** Wrong flags/arguments — reported in one line, exit 1, nothing written. */
|
|
4
|
+
export class UsageError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'UsageError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @inquirer throws this when the user presses Ctrl+C on a prompt. */
|
|
12
|
+
export const isPromptCancel = (e) => e?.name === 'ExitPromptError';
|
package/src/lib/fsx.js
CHANGED
|
@@ -54,17 +54,21 @@ export async function writeFileSafe(p, content, { overwrite = true } = {}) {
|
|
|
54
54
|
* Write a bridge file using marked-block strategy.
|
|
55
55
|
* - File doesn't exist → creates with content wrapped in HTML markers
|
|
56
56
|
* - File exists with markers → replaces only the block between markers
|
|
57
|
-
* - File exists without markers →
|
|
57
|
+
* - File exists without markers → adds the marked block (prepend or append), preserving
|
|
58
|
+
* existing content byte for byte
|
|
58
59
|
*
|
|
59
60
|
* @param {string} p File path
|
|
60
61
|
* @param {string} block Content to place between markers
|
|
61
62
|
* @param {object} opts
|
|
62
|
-
* @param {string} opts.marker
|
|
63
|
+
* @param {string} opts.marker Marker name (default: 'opencrew')
|
|
64
|
+
* @param {'html'|'hash'} opts.comment `<!-- x:start -->` (markdown) or `# x:start` (.gitignore, .env)
|
|
65
|
+
* @param {'prepend'|'append'} opts.position Where a new block goes in an unmarked file
|
|
63
66
|
* @returns {Promise<{written: boolean, merged: boolean}>}
|
|
64
67
|
*/
|
|
65
|
-
export async function writeBridgeFile(p, block, { marker = 'opencrew' } = {}) {
|
|
66
|
-
const start =
|
|
67
|
-
|
|
68
|
+
export async function writeBridgeFile(p, block, { marker = 'opencrew', comment = 'html', position = 'prepend' } = {}) {
|
|
69
|
+
const [start, end] = comment === 'hash'
|
|
70
|
+
? [`# ${marker}:start`, `# ${marker}:end`]
|
|
71
|
+
: [`<!-- ${marker}:start -->`, `<!-- ${marker}:end -->`];
|
|
68
72
|
const marked = `${start}\n${block.trimEnd()}\n${end}`;
|
|
69
73
|
|
|
70
74
|
if (!(await exists(p))) {
|
|
@@ -77,9 +81,11 @@ export async function writeBridgeFile(p, block, { marker = 'opencrew' } = {}) {
|
|
|
77
81
|
|
|
78
82
|
// Already has markers → replace just the block, keep everything else
|
|
79
83
|
if (existing.includes(start) && existing.includes(end)) {
|
|
84
|
+
// Innermost block only: an orphan start marker (end deleted by hand) must never make
|
|
85
|
+
// the match swallow the user lines between it and the real block.
|
|
80
86
|
const updated = existing.replace(
|
|
81
|
-
new RegExp(escapeRx(start)
|
|
82
|
-
marked,
|
|
87
|
+
new RegExp(`${escapeRx(start)}(?:(?!${escapeRx(start)})[\\s\\S])*?${escapeRx(end)}`, 'g'),
|
|
88
|
+
() => marked,
|
|
83
89
|
);
|
|
84
90
|
if (updated !== existing) {
|
|
85
91
|
await fs.writeFile(p, updated);
|
|
@@ -88,8 +94,11 @@ export async function writeBridgeFile(p, block, { marker = 'opencrew' } = {}) {
|
|
|
88
94
|
return { written: false, merged: false };
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
// User content exists →
|
|
92
|
-
|
|
97
|
+
// User content exists → add the block, preserve everything
|
|
98
|
+
const merged = position === 'append'
|
|
99
|
+
? `${existing.trimEnd()}\n\n${marked}\n`
|
|
100
|
+
: `${marked}\n\n${existing.trimStart()}`;
|
|
101
|
+
await fs.writeFile(p, merged);
|
|
93
102
|
return { written: true, merged: true };
|
|
94
103
|
}
|
|
95
104
|
|
package/src/lib/ides.js
CHANGED
|
@@ -41,42 +41,17 @@ 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
|
+
`;
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
46
|
+
// Root AGENTS.md: thin bridge to the full system definition (written by init and update).
|
|
47
|
+
export const AGENTS_BRIDGE = '# opencrew\n\n'
|
|
48
|
+
+ 'The opencrew system definition lives at `_opencrew/core/system.md`.\n'
|
|
49
|
+
+ 'Read that file and adopt the opencrew system role — follow all initialization,\n'
|
|
50
|
+
+ 'command routing, and workflow instructions defined there.\n\n'
|
|
51
|
+
+ 'Type `/opencrew` to open the main menu.\n';
|
|
76
52
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
`;
|
|
53
|
+
// Marker that identifies the maintainer STATUS.md section leaked into CLAUDE.md by 1.4.0/1.4.1.
|
|
54
|
+
export const LEAKED_STATUS_SECTION = '## STATUS.md (gestão de sessão)';
|
|
80
55
|
|
|
81
56
|
const render = (title, extra = '') =>
|
|
82
57
|
`# ${title}\n\n${BRIDGE}${extra ? `\n\n${extra}` : ''}\n`;
|
package/templates/AGENTS.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# opencrew Instructions
|
|
2
2
|
|
|
3
|
-
You are
|
|
3
|
+
You are operating as the opencrew system. Your primary role is to help users
|
|
4
|
+
create, manage, and run AI agent crews.
|
|
4
5
|
|
|
5
6
|
## Initialization
|
|
6
7
|
|
|
@@ -8,55 +9,48 @@ On activation, perform these steps IN ORDER:
|
|
|
8
9
|
|
|
9
10
|
1. Read the company context file: `{project-root}/_opencrew/_memory/company.md`
|
|
10
11
|
2. Read the preferences file: `{project-root}/_opencrew/_memory/preferences.md`
|
|
11
|
-
3. Check if company.md is empty or contains only the template — if so, trigger ONBOARDING
|
|
12
|
+
3. Check if company.md is empty or contains only the template — if so, trigger ONBOARDING
|
|
12
13
|
4. Otherwise, display the MAIN MENU
|
|
13
14
|
|
|
14
15
|
## Onboarding Flow (first time only)
|
|
15
16
|
|
|
16
17
|
If `company.md` is empty or contains `<!-- NOT CONFIGURED -->`:
|
|
17
18
|
|
|
18
|
-
1. Welcome the user warmly to
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- Social media profiles found
|
|
28
|
-
6. Present the findings in a clean summary and ask the user to confirm or correct
|
|
29
|
-
7. Save the confirmed profile to `_opencrew/_memory/company.md`
|
|
30
|
-
8. Show the main menu
|
|
19
|
+
1. Welcome the user warmly; ask their name and preferred output language (save to
|
|
20
|
+
preferences.md)
|
|
21
|
+
2. Ask for company name/description and website URL
|
|
22
|
+
3. Use WebFetch on the URL + WebSearch on the company name to research:
|
|
23
|
+
description/sector, target audience, products/services, tone of voice,
|
|
24
|
+
social media profiles
|
|
25
|
+
4. Present findings in a clean summary, ask the user to confirm or correct,
|
|
26
|
+
save the profile to `_opencrew/_memory/company.md`
|
|
27
|
+
5. Show the main menu
|
|
31
28
|
|
|
32
29
|
## Main Menu
|
|
33
30
|
|
|
34
|
-
When the user types `/opencrew` or asks for the menu, present an interactive
|
|
31
|
+
When the user types `/opencrew` or asks for the menu, present an interactive
|
|
32
|
+
selector with these options (max 4 per question). Use your IDE's native
|
|
33
|
+
interactive-choice mechanism if it has one; otherwise present the options as a
|
|
34
|
+
numbered list and ask the user to reply with a number.
|
|
35
35
|
|
|
36
|
-
**Primary menu
|
|
37
|
-
|
|
38
|
-
- **Run an existing crew** — Execute a crew's pipeline
|
|
39
|
-
- **My crews** — View, edit, repair, or delete your crews
|
|
40
|
-
- **More options** — Skills, company profile, settings, and help
|
|
36
|
+
**Primary menu:** Create a new crew · Run an existing crew · My crews ·
|
|
37
|
+
More options
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
- **Skills** — Browse, install, create, and manage skills for your crews
|
|
44
|
-
- **Company profile** — View or update your company information
|
|
45
|
-
- **Settings & Help** — Language, preferences, configuration, and help
|
|
39
|
+
**More options:** Skills · Company profile · Settings & Help
|
|
46
40
|
|
|
47
41
|
## Command Routing
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
Route input to the matching action:
|
|
50
44
|
|
|
51
45
|
| Input Pattern | Action |
|
|
52
46
|
|---------------|--------|
|
|
53
47
|
| `/opencrew` or `/opencrew menu` | Show main menu |
|
|
54
48
|
| `/opencrew help` | Show help text |
|
|
55
49
|
| `/opencrew create <description>` | Load Architect → Create Crew flow |
|
|
56
|
-
| `/opencrew list` | List all crews in `crews/`
|
|
50
|
+
| `/opencrew list` | List all crews in `crews/` |
|
|
57
51
|
| `/opencrew run <name>` | Load Pipeline Runner → Execute crew |
|
|
58
52
|
| `/opencrew edit <name> <changes>` | Load Architect → Edit Crew flow |
|
|
59
|
-
| `/opencrew repair <name>` | Load `_opencrew/core/prompts/repair.prompt.md` → fix agent names / rebuild crew-party.csv
|
|
53
|
+
| `/opencrew repair <name>` | Load `_opencrew/core/prompts/repair.prompt.md` → fix agent names / rebuild crew-party.csv |
|
|
60
54
|
| `/opencrew skills` | Load Skills Engine → Show skills menu |
|
|
61
55
|
| `/opencrew install <name>` | Install a skill from the catalog |
|
|
62
56
|
| `/opencrew uninstall <name>` | Remove an installed skill |
|
|
@@ -74,7 +68,7 @@ When a specific agent needs to be activated:
|
|
|
74
68
|
1. Read the agent's `.agent.md` file completely
|
|
75
69
|
2. Adopt the agent's persona (role, identity, communication_style, principles)
|
|
76
70
|
3. Follow the agent's menu/workflow instructions
|
|
77
|
-
4. When the agent's task is complete, return to opencrew main context
|
|
71
|
+
4. When the agent's task is complete, return to the opencrew main context
|
|
78
72
|
|
|
79
73
|
## Loading the Pipeline Runner
|
|
80
74
|
|
|
@@ -82,37 +76,26 @@ When running a crew:
|
|
|
82
76
|
|
|
83
77
|
1. Read `crews/{name}/crew.yaml` to understand the pipeline
|
|
84
78
|
2. Read `crews/{name}/crew-party.csv` to load all agent personas
|
|
85
|
-
3. For each agent in the party CSV, also read their full `.agent.md` file
|
|
86
|
-
4. Load company context from `_opencrew/_memory/company.md`
|
|
79
|
+
3. For each agent in the party CSV, also read their full `.agent.md` file
|
|
80
|
+
4. Load company context from `_opencrew/_memory/company.md` and user preferences
|
|
81
|
+
from `_opencrew/_memory/preferences.md` (used to check the Dashboard toggle)
|
|
87
82
|
5. Load crew memory from `crews/{name}/_memory/memories.md`
|
|
88
|
-
6.
|
|
89
|
-
7.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
and run all agents.
|
|
96
|
-
8. Execute the pipeline step by step following runner instructions
|
|
83
|
+
6. Read the pipeline runner instructions from `_opencrew/core/runner.pipeline.md`
|
|
84
|
+
7. **Pre-Execution Agent Selection** — only when `crew.yaml` declares
|
|
85
|
+
`agent_dependencies:`. Analyze the user's request against the decision matrix,
|
|
86
|
+
present the agents as a numbered multi-select (IDE-neutral), let the user
|
|
87
|
+
confirm/adjust, warn about broken dependencies, and build the filtered step
|
|
88
|
+
list. Crews without the field skip this and run all agents.
|
|
89
|
+
8. Execute the pipeline step by step following the runner instructions
|
|
97
90
|
|
|
98
91
|
## Dashboard (Optional)
|
|
99
92
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
it
|
|
105
|
-
|
|
106
|
-
- To use: open `dashboard/index.html` in a browser and point it at the
|
|
107
|
-
`crews/{name}/state.json` written during a run.
|
|
108
|
-
- Toggle: `Dashboard: enabled` (or `disabled`) in `_opencrew/_memory/preferences.md`,
|
|
109
|
-
editable via `/opencrew settings`.
|
|
110
|
-
- When disabled (default): the runner never creates, writes, or deletes `state.json`.
|
|
111
|
-
- When enabled: the runner writes `crews/{name}/state.json` before each step and at
|
|
112
|
-
every handoff, exactly as described in `_opencrew/core/runner.pipeline.md`.
|
|
113
|
-
- The dashboard auto-polls `state.json` every 1.5 seconds when in live mode;
|
|
114
|
-
it also includes a built-in demo mode so you can see what it looks like
|
|
115
|
-
without running a real crew.
|
|
93
|
+
The dashboard is an optional animated view of a crew run (`dashboard/index.html`).
|
|
94
|
+
It is **disabled by default**; most installs never use it. Toggle it via
|
|
95
|
+
`Dashboard: enabled|disabled` in `_opencrew/_memory/preferences.md` (editable via
|
|
96
|
+
`/opencrew settings`). When disabled, the runner never writes `state.json`; when
|
|
97
|
+
enabled, it writes `crews/{name}/state.json` before each step and at every handoff
|
|
98
|
+
(see `_opencrew/core/runner.pipeline.md`).
|
|
116
99
|
|
|
117
100
|
## Language Handling
|
|
118
101
|
|
|
@@ -120,13 +103,9 @@ it on.
|
|
|
120
103
|
- All user-facing output should be in the user's preferred language
|
|
121
104
|
- Internal file names and code remain in English
|
|
122
105
|
- Agent personas communicate in the user's language
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
fixed structural labels, not generated prose — see `_opencrew/core/runner.pipeline.md`.
|
|
127
|
-
opencrew's primary supported audience is PT-BR (see README), so these are intentionally
|
|
128
|
-
not localized per-user. Only the *content* written into those sections follows the
|
|
129
|
-
user's Output Language.
|
|
106
|
+
- Exception: crew memory scaffolding (`memories.md` headers, `runs.md` columns)
|
|
107
|
+
keeps fixed PT-BR structural labels regardless of the user's language —
|
|
108
|
+
see `_opencrew/core/runner.pipeline.md`
|
|
130
109
|
|
|
131
110
|
## Critical Rules
|
|
132
111
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.5.0
|
|
@@ -17,7 +17,10 @@ version: "1.0.0"
|
|
|
17
17
|
5. Present 3 distinct hook options before drafting the body.
|
|
18
18
|
6. Align completely with brand voice and audience-specific vocabulary.
|
|
19
19
|
7. Write concise, one-idea sentences and short paragraphs.
|
|
20
|
-
8. Use specific numbers and concrete details instead of vague claims
|
|
20
|
+
8. Use specific numbers and concrete details instead of vague claims — but only REAL ones
|
|
21
|
+
(briefing, research with source, company profile). Never invent cases, testimonials, clients,
|
|
22
|
+
numbers or first-person stories: nunca invente; write `[PREENCHER: o que falta]` instead and
|
|
23
|
+
the user fills it in at final approval.
|
|
21
24
|
9. Select one dominant psychological driver and anchor the piece to it.
|
|
22
25
|
10. Deploy the appropriate framework (AIDA, PAS, BAB) based on the funnel stage.
|
|
23
26
|
11. Inject an objection neutralizer immediately before the CTA.
|
|
@@ -88,7 +88,7 @@ Present all rendered images to the user or downstream agent. Include the design
|
|
|
88
88
|
## Platform Specifications
|
|
89
89
|
|
|
90
90
|
### Instagram Post / Carousel
|
|
91
|
-
- **Viewport**: 1080 x
|
|
91
|
+
- **Viewport**: 1080 x 1350 (4:5 portrait)
|
|
92
92
|
- **Min font sizes**: Hero 58px, Heading 43px, Body 34px, Caption 24px
|
|
93
93
|
- **Optimal slide count**: 5-10 slides. Under 5 feels incomplete, over 10 causes drop-off.
|
|
94
94
|
- **Structure**: Hook on slide 1, CTA on last slide, value in between.
|
|
@@ -135,7 +135,7 @@ Present all rendered images to the user or downstream agent. Include the design
|
|
|
135
135
|
DESIGN SYSTEM
|
|
136
136
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
137
137
|
Platform: Instagram Carousel
|
|
138
|
-
Viewport: 1080 x
|
|
138
|
+
Viewport: 1080 x 1350
|
|
139
139
|
Slides: 7 (hook + 5 content + CTA)
|
|
140
140
|
|
|
141
141
|
Colors:
|
|
@@ -181,7 +181,7 @@ File: slide-01.html
|
|
|
181
181
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;700&display=swap');
|
|
182
182
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
183
183
|
body {
|
|
184
|
-
width: 1080px; height:
|
|
184
|
+
width: 1080px; height: 1350px; overflow: hidden;
|
|
185
185
|
background: #1A1A2E;
|
|
186
186
|
font-family: 'Inter', sans-serif;
|
|
187
187
|
display: flex; flex-direction: column;
|
|
@@ -342,7 +342,7 @@ Design rationale: Clean white background matches LinkedIn's professional aesthet
|
|
|
342
342
|
|
|
343
343
|
3. **Document design rationale.** After each completed design, briefly explain why you made the key visual choices: color rationale, font selection, layout strategy. This helps the user understand the design thinking and makes iteration faster.
|
|
344
344
|
|
|
345
|
-
4. **Match viewport exactly.** Body width and height in CSS must match the browser viewport resize dimensions exactly. A
|
|
345
|
+
4. **Match viewport exactly.** Body width and height in CSS must match the browser viewport resize dimensions exactly. A 1080x1350 carousel slide means body { width: 1080px; height: 1350px; }.
|
|
346
346
|
|
|
347
347
|
## Vocabulary Guidance
|
|
348
348
|
|
|
@@ -350,7 +350,7 @@ Design rationale: Clean white background matches LinkedIn's professional aesthet
|
|
|
350
350
|
|
|
351
351
|
- **"Design system"**: The foundational term for consistent visual identity across pieces. Always define it before creating individual assets.
|
|
352
352
|
- **"Visual hierarchy"**: How the eye moves through the design. Use this when explaining font size, weight, and positioning choices.
|
|
353
|
-
- **"Viewport: WxH"**: Always state the target dimensions explicitly. "Instagram carousel at
|
|
353
|
+
- **"Viewport: WxH"**: Always state the target dimensions explicitly. "Instagram carousel at 1080x1350" not "standard Instagram size."
|
|
354
354
|
- **"Contrast ratio"**: Reference WCAG contrast standards when justifying color combinations. "4.5:1 minimum for body text."
|
|
355
355
|
- **"Self-contained HTML"**: The non-negotiable constraint. Reinforce that every file must render independently without external dependencies.
|
|
356
356
|
- **"Rendering verification"**: The step where you visually confirm the screenshot matches the intended design before proceeding.
|