@aksp/opencrew 1.2.2 → 1.3.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 +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 +2 -2
- package/templates/_opencrew/core/prompts/build.prompt.md +614 -586
- package/templates/_opencrew/core/prompts/design.prompt.md +255 -27
- 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/src/commands/update.js
CHANGED
|
@@ -1,77 +1,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 } 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
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
}
|
package/src/lib/fsx.js
CHANGED
|
@@ -1,76 +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
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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/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
|
+
}
|