goiabaseeds 1.0.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/README.md +173 -0
- package/bin/goiabaseeds.js +98 -0
- package/eslint.config.js +14 -0
- package/package.json +61 -0
- package/skills/README.md +60 -0
- package/skills/apify/SKILL.md +55 -0
- package/skills/blotato/SKILL.md +63 -0
- package/skills/canva/SKILL.md +60 -0
- package/skills/goiabaseeds-agent-creator/SKILL.md +192 -0
- package/skills/goiabaseeds-skill-creator/SKILL.md +407 -0
- package/skills/goiabaseeds-skill-creator/agents/analyzer.md +274 -0
- package/skills/goiabaseeds-skill-creator/agents/comparator.md +202 -0
- package/skills/goiabaseeds-skill-creator/agents/grader.md +223 -0
- package/skills/goiabaseeds-skill-creator/assets/eval_review.html +146 -0
- package/skills/goiabaseeds-skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/goiabaseeds-skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/goiabaseeds-skill-creator/references/schemas.md +430 -0
- package/skills/goiabaseeds-skill-creator/references/skill-format.md +235 -0
- package/skills/goiabaseeds-skill-creator/scripts/__init__.py +0 -0
- package/skills/goiabaseeds-skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/goiabaseeds-skill-creator/scripts/quick_validate.py +103 -0
- package/skills/goiabaseeds-skill-creator/scripts/run_eval.py +310 -0
- package/skills/goiabaseeds-skill-creator/scripts/utils.py +47 -0
- package/skills/image-creator/SKILL.md +155 -0
- package/skills/image-fetcher/SKILL.md +91 -0
- package/skills/image-generator/SKILL.md +124 -0
- package/skills/image-generator/scripts/generate.py +175 -0
- package/skills/instagram-publisher/SKILL.md +118 -0
- package/skills/instagram-publisher/scripts/publish.js +164 -0
- package/src/agent-session.js +110 -0
- package/src/agents-cli.js +158 -0
- package/src/agents.js +134 -0
- package/src/bundle-detector.js +75 -0
- package/src/bundle.js +286 -0
- package/src/context.js +142 -0
- package/src/export.js +52 -0
- package/src/i18n.js +48 -0
- package/src/init.js +367 -0
- package/src/locales/en.json +72 -0
- package/src/locales/es.json +71 -0
- package/src/locales/pt-BR.json +71 -0
- package/src/logger.js +38 -0
- package/src/models-cli.js +165 -0
- package/src/pipeline-runner.js +478 -0
- package/src/prompt.js +46 -0
- package/src/provider.js +156 -0
- package/src/readme/README.md +181 -0
- package/src/run.js +100 -0
- package/src/runs.js +90 -0
- package/src/skills-cli.js +157 -0
- package/src/skills.js +146 -0
- package/src/state-manager.js +280 -0
- package/src/tools.js +158 -0
- package/src/update.js +140 -0
- package/templates/_goiabaseeds/.goiabaseeds-version +1 -0
- package/templates/_goiabaseeds/_investigations/.gitkeep +0 -0
- package/templates/_goiabaseeds/config/playwright.config.json +11 -0
- package/templates/_goiabaseeds/core/architect.agent.yaml +1141 -0
- package/templates/_goiabaseeds/core/best-practices/_catalog.yaml +116 -0
- package/templates/_goiabaseeds/core/best-practices/blog-post.md +132 -0
- package/templates/_goiabaseeds/core/best-practices/blog-seo.md +127 -0
- package/templates/_goiabaseeds/core/best-practices/copywriting.md +428 -0
- package/templates/_goiabaseeds/core/best-practices/data-analysis.md +401 -0
- package/templates/_goiabaseeds/core/best-practices/email-newsletter.md +118 -0
- package/templates/_goiabaseeds/core/best-practices/email-sales.md +110 -0
- package/templates/_goiabaseeds/core/best-practices/image-design.md +349 -0
- package/templates/_goiabaseeds/core/best-practices/instagram-feed.md +235 -0
- package/templates/_goiabaseeds/core/best-practices/instagram-reels.md +112 -0
- package/templates/_goiabaseeds/core/best-practices/instagram-stories.md +107 -0
- package/templates/_goiabaseeds/core/best-practices/linkedin-article.md +116 -0
- package/templates/_goiabaseeds/core/best-practices/linkedin-post.md +121 -0
- package/templates/_goiabaseeds/core/best-practices/researching.md +347 -0
- package/templates/_goiabaseeds/core/best-practices/review.md +269 -0
- package/templates/_goiabaseeds/core/best-practices/social-networks-publishing.md +294 -0
- package/templates/_goiabaseeds/core/best-practices/strategist.md +344 -0
- package/templates/_goiabaseeds/core/best-practices/technical-writing.md +363 -0
- package/templates/_goiabaseeds/core/best-practices/twitter-post.md +105 -0
- package/templates/_goiabaseeds/core/best-practices/twitter-thread.md +122 -0
- package/templates/_goiabaseeds/core/best-practices/whatsapp-broadcast.md +107 -0
- package/templates/_goiabaseeds/core/best-practices/youtube-script.md +122 -0
- package/templates/_goiabaseeds/core/best-practices/youtube-shorts.md +112 -0
- package/templates/_goiabaseeds/core/prompts/auguste.dupin.prompt.md +1008 -0
- package/templates/_goiabaseeds/core/runner.pipeline.md +467 -0
- package/templates/_goiabaseeds/core/skills.engine.md +381 -0
- package/templates/dashboard/index.html +12 -0
- package/templates/dashboard/package-lock.json +2082 -0
- package/templates/dashboard/package.json +28 -0
- package/templates/dashboard/src/App.tsx +46 -0
- package/templates/dashboard/src/components/DepartmentCard.tsx +47 -0
- package/templates/dashboard/src/components/DepartmentSelector.tsx +61 -0
- package/templates/dashboard/src/components/StatusBadge.tsx +32 -0
- package/templates/dashboard/src/components/StatusBar.tsx +97 -0
- package/templates/dashboard/src/hooks/useDepartmentSocket.ts +84 -0
- package/templates/dashboard/src/lib/formatTime.ts +16 -0
- package/templates/dashboard/src/lib/normalizeState.ts +25 -0
- package/templates/dashboard/src/main.tsx +10 -0
- package/templates/dashboard/src/office/AgentDesk.tsx +151 -0
- package/templates/dashboard/src/office/HandoffEnvelope.tsx +108 -0
- package/templates/dashboard/src/office/OfficeScene.tsx +147 -0
- package/templates/dashboard/src/office/drawDesk.ts +263 -0
- package/templates/dashboard/src/office/drawFurniture.ts +129 -0
- package/templates/dashboard/src/office/drawRoom.ts +51 -0
- package/templates/dashboard/src/office/palette.ts +181 -0
- package/templates/dashboard/src/office/textures.ts +254 -0
- package/templates/dashboard/src/plugin/departmentWatcher.ts +210 -0
- package/templates/dashboard/src/store/useDepartmentStore.ts +56 -0
- package/templates/dashboard/src/styles/globals.css +36 -0
- package/templates/dashboard/src/types/state.ts +64 -0
- package/templates/dashboard/src/vite-env.d.ts +1 -0
- package/templates/dashboard/tsconfig.json +24 -0
- package/templates/dashboard/vite.config.ts +13 -0
- package/templates/departments/.gitkeep +0 -0
- package/templates/ide-templates/antigravity/.agent/rules/goiabaseeds.md +55 -0
- package/templates/ide-templates/antigravity/.agent/workflows/goiabaseeds.md +102 -0
- package/templates/ide-templates/claude-code/.claude/skills/goiabaseeds/SKILL.md +182 -0
- package/templates/ide-templates/claude-code/.mcp.json +8 -0
- package/templates/ide-templates/claude-code/CLAUDE.md +43 -0
- package/templates/ide-templates/codex/.agents/skills/goiabaseeds/SKILL.md +6 -0
- package/templates/ide-templates/codex/AGENTS.md +105 -0
- package/templates/ide-templates/cursor/.cursor/commands/goiabaseeds.md +9 -0
- package/templates/ide-templates/cursor/.cursor/mcp.json +8 -0
- package/templates/ide-templates/cursor/.cursor/rules/goiabaseeds.mdc +48 -0
- package/templates/ide-templates/cursor/.cursorignore +3 -0
- package/templates/ide-templates/opencode/.opencode/commands/goiabaseeds.md +9 -0
- package/templates/ide-templates/opencode/AGENTS.md +105 -0
- package/templates/ide-templates/vscode-copilot/.github/prompts/goiabaseeds.prompt.md +201 -0
- package/templates/ide-templates/vscode-copilot/.vscode/mcp.json +8 -0
- package/templates/ide-templates/vscode-copilot/.vscode/settings.json +3 -0
- package/templates/package.json +8 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { listInstalled, installAgent, removeAgent, getAgentMeta, getLocalizedDescription } from './agents.js';
|
|
5
|
+
import { loadLocale, t, getLocaleCode } from './i18n.js';
|
|
6
|
+
import { loadSavedLocale } from './init.js';
|
|
7
|
+
import { logEvent } from './logger.js';
|
|
8
|
+
|
|
9
|
+
async function confirm(question) {
|
|
10
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
rl.question(question, (answer) => {
|
|
13
|
+
rl.close();
|
|
14
|
+
resolve(answer.trim().toLowerCase());
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function agentsCli(subcommand, args, targetDir) {
|
|
20
|
+
// Require initialized project
|
|
21
|
+
try {
|
|
22
|
+
await stat(join(targetDir, '_goiabaseeds'));
|
|
23
|
+
} catch {
|
|
24
|
+
await loadLocale('English');
|
|
25
|
+
console.log(`\n ${t('agentsNotInitialized')}\n`);
|
|
26
|
+
return { success: false };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await loadSavedLocale(targetDir);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (subcommand === 'list' || !subcommand) {
|
|
33
|
+
await runList(targetDir);
|
|
34
|
+
} else if (subcommand === 'install') {
|
|
35
|
+
const installed = await runInstall(args[0], targetDir);
|
|
36
|
+
if (installed === false) return { success: false };
|
|
37
|
+
} else if (subcommand === 'remove') {
|
|
38
|
+
const removed = await runRemove(args[0], targetDir);
|
|
39
|
+
if (removed === false) return { success: false };
|
|
40
|
+
} else if (subcommand === 'update') {
|
|
41
|
+
await runUpdate(targetDir);
|
|
42
|
+
} else if (subcommand === 'update-one') {
|
|
43
|
+
await runUpdateOne(args[0], targetDir);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(`\n ${t('agentsUnknownCommand', { cmd: subcommand })}\n`);
|
|
46
|
+
return { success: false };
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.log(`\n ${t('agentsError', { message: err.message })}\n`);
|
|
50
|
+
return { success: false };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { success: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runList(targetDir) {
|
|
57
|
+
console.log(`\n GoiabaSeeds Agents\n`);
|
|
58
|
+
|
|
59
|
+
const installed = await listInstalled(targetDir);
|
|
60
|
+
|
|
61
|
+
if (installed.length > 0) {
|
|
62
|
+
console.log(` ${t('agentsInstalledHeader')}`);
|
|
63
|
+
for (const id of installed) {
|
|
64
|
+
const meta = await getAgentMeta(id);
|
|
65
|
+
if (meta) {
|
|
66
|
+
const desc = getLocalizedDescription(meta, getLocaleCode());
|
|
67
|
+
const parts = [meta.name];
|
|
68
|
+
if (meta.icon) parts.unshift(meta.icon);
|
|
69
|
+
if (meta.category) parts.push(`(${meta.category})`);
|
|
70
|
+
parts.push(`- ${desc.split('.')[0]}`);
|
|
71
|
+
console.log(` ${parts.join(' ')}`);
|
|
72
|
+
} else {
|
|
73
|
+
console.log(` ${id}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
console.log(` ${t('agentsNoneInstalled')}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`\n Browse available agents at: https://github.com/netoribeiro/goiabaseeds/tree/main/agents\n`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function runInstall(id, targetDir) {
|
|
84
|
+
if (!id) {
|
|
85
|
+
console.log('\n Usage: goiabaseeds agents install <id>\n');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const installed = await listInstalled(targetDir);
|
|
90
|
+
if (installed.includes(id)) {
|
|
91
|
+
const answer = await confirm(`\n ${t('agentsAlreadyInstalled', { id })}`);
|
|
92
|
+
// Accept 'y' (English) or 's' (Portuguese "sim") as affirmative answers
|
|
93
|
+
if (answer !== 'y' && answer !== 's') return false;
|
|
94
|
+
console.log(` ${t('agentsInstalling', { id })}`);
|
|
95
|
+
await installAgent(id, targetDir);
|
|
96
|
+
console.log(` ${t('agentsReinstalled', { id })}\n`);
|
|
97
|
+
await logEvent('agent:install', { name: id, reinstall: true }, targetDir);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`\n ${t('agentsInstalling', { id })}`);
|
|
102
|
+
await installAgent(id, targetDir);
|
|
103
|
+
console.log(` ${t('agentsInstalled', { id })}\n`);
|
|
104
|
+
await logEvent('agent:install', { name: id }, targetDir);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function runRemove(id, targetDir) {
|
|
108
|
+
if (!id) {
|
|
109
|
+
console.log('\n Usage: goiabaseeds agents remove <id>\n');
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const installed = await listInstalled(targetDir);
|
|
114
|
+
if (!installed.includes(id)) {
|
|
115
|
+
console.log(`\n ${t('agentsNotInstalled', { id })}\n`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(`\n ${t('agentsRemoving', { id })}`);
|
|
120
|
+
await removeAgent(id, targetDir);
|
|
121
|
+
await logEvent('agent:remove', { name: id }, targetDir);
|
|
122
|
+
console.log(` ${t('agentsRemoved', { id })}\n`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function runUpdate(targetDir) {
|
|
126
|
+
const installed = await listInstalled(targetDir);
|
|
127
|
+
if (installed.length === 0) {
|
|
128
|
+
console.log(`\n ${t('agentsUpdateNone')}\n`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(`\n ${t('agentsUpdating')}`);
|
|
133
|
+
for (const id of installed) {
|
|
134
|
+
console.log(` ${t('agentsInstalling', { id })}`);
|
|
135
|
+
await installAgent(id, targetDir);
|
|
136
|
+
console.log(` ${t('agentsInstalled', { id })}`);
|
|
137
|
+
}
|
|
138
|
+
await logEvent('agent:update', { count: installed.length }, targetDir);
|
|
139
|
+
console.log(`\n ${t('agentsUpdateDone', { count: installed.length })}\n`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function runUpdateOne(id, targetDir) {
|
|
143
|
+
if (!id) {
|
|
144
|
+
console.log('\n Usage: goiabaseeds update <name>\n');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const installed = await listInstalled(targetDir);
|
|
149
|
+
if (!installed.includes(id)) {
|
|
150
|
+
console.log(`\n ${t('agentsNotInstalled', { id })}\n`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`\n ${t('agentsInstalling', { id })}`);
|
|
155
|
+
await installAgent(id, targetDir);
|
|
156
|
+
await logEvent('agent:update', { name: id }, targetDir);
|
|
157
|
+
console.log(` ${t('agentsInstalled', { id })}\n`);
|
|
158
|
+
}
|
package/src/agents.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { copyFile, mkdir, readdir, readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const BUNDLED_AGENTS_DIR = join(__dirname, '..', 'agents');
|
|
7
|
+
|
|
8
|
+
const metaCache = new Map();
|
|
9
|
+
|
|
10
|
+
export async function listInstalled(targetDir) {
|
|
11
|
+
try {
|
|
12
|
+
const agentsDir = join(targetDir, 'agents');
|
|
13
|
+
const entries = await readdir(agentsDir, { withFileTypes: true });
|
|
14
|
+
return entries
|
|
15
|
+
.filter((e) => e.isFile() && e.name.endsWith('.agent.md'))
|
|
16
|
+
.map((e) => e.name.replace(/\.agent\.md$/, ''));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
if (err.code === 'ENOENT') return [];
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function listAvailable() {
|
|
24
|
+
try {
|
|
25
|
+
const entries = await readdir(BUNDLED_AGENTS_DIR, { withFileTypes: true });
|
|
26
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
27
|
+
} catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getAgentMeta(id) {
|
|
33
|
+
if (metaCache.has(id)) return metaCache.get(id);
|
|
34
|
+
try {
|
|
35
|
+
const raw = await readFile(join(BUNDLED_AGENTS_DIR, id, 'AGENT.md'), 'utf-8');
|
|
36
|
+
const content = raw.replace(/\r\n/g, '\n');
|
|
37
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
38
|
+
if (!fmMatch) return { name: id, description: '', descriptions: {}, category: '', icon: '', version: '' };
|
|
39
|
+
|
|
40
|
+
const fm = fmMatch[1];
|
|
41
|
+
const name = fm.match(/^name:\s*(.+)$/m)?.[1]?.trim() || id;
|
|
42
|
+
const category = fm.match(/^category:\s*(.+)$/m)?.[1]?.trim() || '';
|
|
43
|
+
const icon = fm.match(/^icon:\s*(.+)$/m)?.[1]?.trim() || '';
|
|
44
|
+
const version = fm.match(/^version:\s*(.+)$/m)?.[1]?.trim() || '';
|
|
45
|
+
|
|
46
|
+
// description may use YAML folded scalar (>)
|
|
47
|
+
let description = '';
|
|
48
|
+
const descBlock = fm.match(/^description:\s*>\s*\n((?:\s{2,}.+\n?)+)/m);
|
|
49
|
+
if (descBlock) {
|
|
50
|
+
description = descBlock[1].replace(/\n\s*/g, ' ').trim();
|
|
51
|
+
} else {
|
|
52
|
+
const descInline = fm.match(/^description:\s*(.+)$/m);
|
|
53
|
+
if (descInline) description = descInline[1].trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// localized descriptions: description_pt-BR, description_es, etc.
|
|
57
|
+
const descriptions = {};
|
|
58
|
+
for (const code of ['pt-BR', 'es']) {
|
|
59
|
+
const key = `description_${code}`;
|
|
60
|
+
// folded scalar
|
|
61
|
+
const blockMatch = fm.match(new RegExp(`^${key}:\\s*>\\s*\\n((?:\\s{2,}.+\\n?)+)`, 'm'));
|
|
62
|
+
if (blockMatch) {
|
|
63
|
+
descriptions[code] = blockMatch[1].replace(/\n\s*/g, ' ').trim();
|
|
64
|
+
} else {
|
|
65
|
+
// inline
|
|
66
|
+
const inlineMatch = fm.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
|
|
67
|
+
if (inlineMatch) descriptions[code] = inlineMatch[1].trim();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result = { name, description, descriptions, category, icon, version };
|
|
72
|
+
metaCache.set(id, result);
|
|
73
|
+
return result;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.code === 'ENOENT') {
|
|
76
|
+
metaCache.set(id, null);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateAgentId(id) {
|
|
84
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
|
|
85
|
+
throw new Error(`Invalid agent id: '${id}'`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function installAgent(id, targetDir) {
|
|
90
|
+
validateAgentId(id);
|
|
91
|
+
const srcFile = join(BUNDLED_AGENTS_DIR, id, 'AGENT.md');
|
|
92
|
+
try {
|
|
93
|
+
await readFile(srcFile);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (err.code === 'ENOENT') throw new Error(`Agent '${id}' not found in registry`, { cause: err });
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
const destDir = join(targetDir, 'agents');
|
|
99
|
+
await mkdir(destDir, { recursive: true });
|
|
100
|
+
await copyFile(srcFile, join(destDir, `${id}.agent.md`));
|
|
101
|
+
metaCache.delete(id);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function removeAgent(id, targetDir) {
|
|
105
|
+
validateAgentId(id);
|
|
106
|
+
const agentFile = join(targetDir, 'agents', `${id}.agent.md`);
|
|
107
|
+
await rm(agentFile, { force: true });
|
|
108
|
+
metaCache.delete(id);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function clearMetaCache() {
|
|
112
|
+
metaCache.clear();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function getAgentVersion(id, targetDir) {
|
|
116
|
+
try {
|
|
117
|
+
const agentPath = join(targetDir, 'agents', `${id}.agent.md`);
|
|
118
|
+
const content = await readFile(agentPath, 'utf-8');
|
|
119
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
120
|
+
if (!fmMatch) return null;
|
|
121
|
+
const versionMatch = fmMatch[1].match(/^version:\s*(.+)$/m);
|
|
122
|
+
return versionMatch ? versionMatch[1].trim() : null;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (err.code === 'ENOENT') return null;
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function getLocalizedDescription(meta, localeCode) {
|
|
130
|
+
if (localeCode && localeCode !== 'en' && meta.descriptions?.[localeCode]) {
|
|
131
|
+
return meta.descriptions[localeCode];
|
|
132
|
+
}
|
|
133
|
+
return meta.description;
|
|
134
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Detect if current working directory is a bundled department
|
|
6
|
+
* @param {string} cwd - Current working directory
|
|
7
|
+
* @returns {boolean} True if bundled, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
export function isBundled(cwd) {
|
|
10
|
+
return existsSync(join(cwd, '_bundle'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the base path for resolving dependencies
|
|
15
|
+
* @param {string} cwd - Current working directory
|
|
16
|
+
* @returns {string} Path to resolve dependencies from
|
|
17
|
+
*/
|
|
18
|
+
export function getBasePath(cwd) {
|
|
19
|
+
if (isBundled(cwd)) {
|
|
20
|
+
return join(cwd, '_bundle');
|
|
21
|
+
}
|
|
22
|
+
// For project mode, we need to find parent _goiabaseeds
|
|
23
|
+
// This assumes cwd is a department directory under departments/
|
|
24
|
+
return join(cwd, '..', '..', '_goiabaseeds');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a path based on execution mode (bundled or project)
|
|
29
|
+
* @param {string} relativePath - Path relative to project root (e.g., "_goiabaseeds/core/runner.pipeline.md")
|
|
30
|
+
* @param {string} cwd - Current working directory (department directory)
|
|
31
|
+
* @returns {string} Resolved absolute path
|
|
32
|
+
*/
|
|
33
|
+
export function resolvePath(relativePath, cwd) {
|
|
34
|
+
if (!isBundled(cwd)) {
|
|
35
|
+
// Project mode: resolve relative to parent project
|
|
36
|
+
// Assuming cwd is departments/{name}/
|
|
37
|
+
const projectRoot = join(cwd, '..', '..');
|
|
38
|
+
return join(projectRoot, relativePath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Bundle mode: translate paths
|
|
42
|
+
if (relativePath.startsWith('_goiabaseeds/')) {
|
|
43
|
+
// _goiabaseeds/ → _bundle/_goiabaseeds/
|
|
44
|
+
const bundlePath = relativePath.replace('_goiabaseeds/', '_bundle/_goiabaseeds/');
|
|
45
|
+
return join(cwd, bundlePath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (relativePath.startsWith('skills/')) {
|
|
49
|
+
// skills/ → _bundle/skills/
|
|
50
|
+
const bundlePath = relativePath.replace('skills/', '_bundle/skills/');
|
|
51
|
+
return join(cwd, bundlePath);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (relativePath.startsWith('_config/')) {
|
|
55
|
+
// _config/ paths stay the same
|
|
56
|
+
return join(cwd, relativePath);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Default: treat as relative to bundle root
|
|
60
|
+
return join(cwd, relativePath);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get mode information for debugging/logging
|
|
65
|
+
* @param {string} cwd - Current working directory
|
|
66
|
+
* @returns {Object} Mode info
|
|
67
|
+
*/
|
|
68
|
+
export function getModeInfo(cwd) {
|
|
69
|
+
const bundled = isBundled(cwd);
|
|
70
|
+
return {
|
|
71
|
+
mode: bundled ? 'bundle' : 'project',
|
|
72
|
+
isBundled: bundled,
|
|
73
|
+
basePath: getBasePath(cwd)
|
|
74
|
+
};
|
|
75
|
+
}
|
package/src/bundle.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { copyFile, mkdir, readFile, writeFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve, relative } from 'node:path';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bundle a department into a self-contained package
|
|
7
|
+
* @param {string} departmentName - Name of department to bundle
|
|
8
|
+
* @param {string} projectDir - Project root directory
|
|
9
|
+
* @param {Object} options - Bundle options
|
|
10
|
+
* @param {string} [options.output] - Custom output path (defaults to departments/{dept}/dist/)
|
|
11
|
+
* @param {boolean} [options.zip] - Create ZIP file (not implemented yet)
|
|
12
|
+
* @returns {Object} Result with success status and path
|
|
13
|
+
*/
|
|
14
|
+
export async function bundleDepartment(departmentName, projectDir, options = {}) {
|
|
15
|
+
try {
|
|
16
|
+
const departmentPath = join(projectDir, 'departments', departmentName);
|
|
17
|
+
const outputPath = options.output || join(projectDir, 'departments', departmentName, 'dist');
|
|
18
|
+
const bundlePath = join(outputPath, departmentName);
|
|
19
|
+
|
|
20
|
+
// Verify department exists
|
|
21
|
+
if (!existsSync(departmentPath)) {
|
|
22
|
+
return { success: false, error: `Department not found: ${departmentName}` };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Verify department.yaml exists
|
|
26
|
+
const deptYamlPath = join(departmentPath, 'department.yaml');
|
|
27
|
+
if (!existsSync(deptYamlPath)) {
|
|
28
|
+
return { success: false, error: `department.yaml not found in ${departmentName}` };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(`\n📦 Bundling department: ${departmentName}`);
|
|
32
|
+
|
|
33
|
+
// Create bundle directory structure
|
|
34
|
+
await mkdir(join(bundlePath, '_bundle', '_goiabaseeds', 'core', 'best-practices'), { recursive: true });
|
|
35
|
+
await mkdir(join(bundlePath, '_bundle', '_goiabaseeds', 'core', 'agents'), { recursive: true });
|
|
36
|
+
await mkdir(join(bundlePath, '_bundle', 'skills'), { recursive: true });
|
|
37
|
+
await mkdir(join(bundlePath, '_config'), { recursive: true });
|
|
38
|
+
|
|
39
|
+
console.log(` ✓ Created bundle directory structure`);
|
|
40
|
+
|
|
41
|
+
// Step 1: Copy core runner
|
|
42
|
+
const runnerSource = join(projectDir, '_goiabaseeds', 'core', 'runner.pipeline.md');
|
|
43
|
+
const runnerDest = join(bundlePath, '_bundle', '_goiabaseeds', 'core', 'runner.pipeline.md');
|
|
44
|
+
if (existsSync(runnerSource)) {
|
|
45
|
+
await copyFile(runnerSource, runnerDest);
|
|
46
|
+
console.log(` ✓ Bundled pipeline runner`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Step 2: Copy best practices
|
|
50
|
+
const bestPracticesSource = join(projectDir, '_goiabaseeds', 'core', 'best-practices');
|
|
51
|
+
if (existsSync(bestPracticesSource)) {
|
|
52
|
+
const files = await readdir(bestPracticesSource);
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const src = join(bestPracticesSource, file);
|
|
55
|
+
const dst = join(bundlePath, '_bundle', '_goiabaseeds', 'core', 'best-practices', file);
|
|
56
|
+
await copyFile(src, dst);
|
|
57
|
+
}
|
|
58
|
+
console.log(` ✓ Bundled best practices (${files.length} files)`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Step 3: Copy base agents
|
|
62
|
+
const agentsSource = join(projectDir, '_goiabaseeds', 'core', 'agents');
|
|
63
|
+
if (existsSync(agentsSource)) {
|
|
64
|
+
const files = await readdir(agentsSource);
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
const src = join(agentsSource, file);
|
|
67
|
+
const dst = join(bundlePath, '_bundle', '_goiabaseeds', 'core', 'agents', file);
|
|
68
|
+
await copyFile(src, dst);
|
|
69
|
+
}
|
|
70
|
+
console.log(` ✓ Bundled base agents (${files.length} files)`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Step 4: Copy and analyze department.yaml to find skills
|
|
74
|
+
const deptYamlContent = await readFile(deptYamlPath, 'utf-8');
|
|
75
|
+
const skills = extractSkillsFromYaml(deptYamlContent);
|
|
76
|
+
|
|
77
|
+
// Step 5: Copy skills
|
|
78
|
+
const skillsDir = join(projectDir, 'skills');
|
|
79
|
+
for (const skill of skills) {
|
|
80
|
+
const skillSource = join(skillsDir, skill);
|
|
81
|
+
if (existsSync(skillSource)) {
|
|
82
|
+
await copyDirRecursive(skillSource, join(bundlePath, '_bundle', 'skills', skill));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log(` ✓ Bundled ${skills.length} skill(s)`);
|
|
86
|
+
|
|
87
|
+
// Step 6: Copy company context and preferences
|
|
88
|
+
const companySource = join(projectDir, '_goiabaseeds', '_memory', 'company.md');
|
|
89
|
+
const prefsSource = join(projectDir, '_goiabaseeds', '_memory', 'preferences.md');
|
|
90
|
+
|
|
91
|
+
if (existsSync(companySource)) {
|
|
92
|
+
await copyFile(companySource, join(bundlePath, '_config', 'company.md'));
|
|
93
|
+
}
|
|
94
|
+
if (existsSync(prefsSource)) {
|
|
95
|
+
await copyFile(prefsSource, join(bundlePath, '_config', 'preferences.md'));
|
|
96
|
+
}
|
|
97
|
+
console.log(` ✓ Bundled company context and preferences`);
|
|
98
|
+
|
|
99
|
+
// Step 7: Copy all department files (agents, pipeline, data, memory, output)
|
|
100
|
+
const dirsToCopy = ['agents', 'pipeline', '_memory', 'output'];
|
|
101
|
+
const filesToCopy = ['department-party.csv'];
|
|
102
|
+
|
|
103
|
+
for (const dir of dirsToCopy) {
|
|
104
|
+
const src = join(departmentPath, dir);
|
|
105
|
+
if (existsSync(src)) {
|
|
106
|
+
const dst = join(bundlePath, dir);
|
|
107
|
+
await copyDirRecursive(src, dst);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Copy individual files
|
|
112
|
+
for (const file of filesToCopy) {
|
|
113
|
+
const src = join(departmentPath, file);
|
|
114
|
+
if (existsSync(src)) {
|
|
115
|
+
const dst = join(bundlePath, file);
|
|
116
|
+
await copyFile(src, dst);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.log(` ✓ Copied department files and structure`);
|
|
120
|
+
|
|
121
|
+
// Step 8: Rewrite department.yaml with bundle paths
|
|
122
|
+
const updatedYaml = rewriteYamlPaths(deptYamlContent);
|
|
123
|
+
await writeFile(join(bundlePath, 'department.yaml'), updatedYaml);
|
|
124
|
+
|
|
125
|
+
// Step 9: Create bundle metadata
|
|
126
|
+
const versionFile = join(projectDir, '_goiabaseeds', '.goiabaseeds-version');
|
|
127
|
+
let version = 'unknown';
|
|
128
|
+
if (existsSync(versionFile)) {
|
|
129
|
+
version = (await readFile(versionFile, 'utf-8')).trim();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const metadata = {
|
|
133
|
+
bundleVersion: 1,
|
|
134
|
+
departmentName,
|
|
135
|
+
createdAt: new Date().toISOString(),
|
|
136
|
+
goiabaseeds_version: version,
|
|
137
|
+
skills: skills.sort(),
|
|
138
|
+
author: 'GoiabaSeeds Bundle Generator'
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
await writeFile(
|
|
142
|
+
join(bundlePath, '_bundle', '.meta.json'),
|
|
143
|
+
JSON.stringify(metadata, null, 2)
|
|
144
|
+
);
|
|
145
|
+
console.log(` ✓ Created bundle metadata`);
|
|
146
|
+
|
|
147
|
+
// Step 10: Create a README for the bundle
|
|
148
|
+
const readmeContent = `# ${departmentName} — GoiabaSeeds Bundle
|
|
149
|
+
|
|
150
|
+
This is a self-contained GoiabaSeeds department bundle created on ${new Date().toLocaleDateString()}.
|
|
151
|
+
|
|
152
|
+
## Quick Start
|
|
153
|
+
|
|
154
|
+
1. **Unzip this folder** (if it came as a ZIP)
|
|
155
|
+
2. **Navigate to the department directory:**
|
|
156
|
+
\`\`\`bash
|
|
157
|
+
cd ${departmentName}
|
|
158
|
+
\`\`\`
|
|
159
|
+
3. **Run the department:**
|
|
160
|
+
\`\`\`bash
|
|
161
|
+
npx goiabaseeds run
|
|
162
|
+
\`\`\`
|
|
163
|
+
|
|
164
|
+
## About This Bundle
|
|
165
|
+
|
|
166
|
+
- **Created**: ${new Date().toLocaleString()}
|
|
167
|
+
- **GoiabaSeeds Version**: ${version}
|
|
168
|
+
- **Included Skills**: ${skills.join(', ') || 'None (uses native skills only)'}
|
|
169
|
+
- **Department**: ${departmentName}
|
|
170
|
+
|
|
171
|
+
## What's Inside
|
|
172
|
+
|
|
173
|
+
\`\`\`
|
|
174
|
+
${departmentName}/
|
|
175
|
+
├── _bundle/ # Bundled GoiabaSeeds runtime and dependencies
|
|
176
|
+
│ ├── _goiabaseeds/ # Core execution engine
|
|
177
|
+
│ └── skills/ # Bundled custom skills
|
|
178
|
+
├── _config/ # Department-specific configuration
|
|
179
|
+
│ ├── company.md # Company context
|
|
180
|
+
│ └── preferences.md # User preferences
|
|
181
|
+
├── agents/ # Custom agent definitions
|
|
182
|
+
├── pipeline/ # Department pipeline and data
|
|
183
|
+
├── _memory/ # Execution memory and learnings
|
|
184
|
+
├── department.yaml # Department configuration
|
|
185
|
+
├── department-party.csv # Agent roster
|
|
186
|
+
└── output/ # Execution artifacts
|
|
187
|
+
\`\`\`
|
|
188
|
+
|
|
189
|
+
## Using This Bundle
|
|
190
|
+
|
|
191
|
+
Once you extract and run this department, it will:
|
|
192
|
+
1. Load all agents from the orchestration
|
|
193
|
+
2. Execute the complete pipeline with all agents working together
|
|
194
|
+
3. Store outputs and memory locally
|
|
195
|
+
4. Can be shared again or customized by your team
|
|
196
|
+
|
|
197
|
+
## Support
|
|
198
|
+
|
|
199
|
+
For questions about GoiabaSeeds, visit: https://github.com/netoribeiro/goiabaseeds
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
*This bundle is self-contained and can be moved, shared, and run on any machine with Node.js >= 20.0.0*
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
await writeFile(join(bundlePath, 'README.md'), readmeContent);
|
|
207
|
+
console.log(` ✓ Created README`);
|
|
208
|
+
|
|
209
|
+
console.log(`\n✅ Bundle created successfully!\n Location: ${bundlePath}\n`);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
success: true,
|
|
213
|
+
path: bundlePath,
|
|
214
|
+
bundleSize: skills.length,
|
|
215
|
+
department: departmentName
|
|
216
|
+
};
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return { success: false, error: error.message };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Extract skill names from department.yaml content
|
|
224
|
+
*/
|
|
225
|
+
function extractSkillsFromYaml(yamlContent) {
|
|
226
|
+
const skills = new Set();
|
|
227
|
+
const skillsMatch = yamlContent.match(/skills:\s*\n([\s\S]*?)(?:\n\w+:|$)/);
|
|
228
|
+
|
|
229
|
+
if (skillsMatch) {
|
|
230
|
+
const skillsBlock = skillsMatch[1];
|
|
231
|
+
const skillLines = skillsBlock.split('\n').filter(line => line.trim().startsWith('-'));
|
|
232
|
+
skillLines.forEach(line => {
|
|
233
|
+
const match = line.match(/- (.+?)(?:\s*#|$)/);
|
|
234
|
+
if (match) {
|
|
235
|
+
const skill = match[1].trim();
|
|
236
|
+
// Don't include native skills
|
|
237
|
+
if (!['web_search', 'web_fetch'].includes(skill)) {
|
|
238
|
+
skills.add(skill);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return Array.from(skills);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Rewrite YAML paths for bundled mode
|
|
249
|
+
*/
|
|
250
|
+
function rewriteYamlPaths(yamlContent) {
|
|
251
|
+
let updated = yamlContent;
|
|
252
|
+
|
|
253
|
+
// Replace company path
|
|
254
|
+
updated = updated.replace(
|
|
255
|
+
/company:\s*['"]?_goiabaseeds\/_memory\/company\.md['"]?/,
|
|
256
|
+
'company: "_config/company.md"'
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Replace preferences path
|
|
260
|
+
updated = updated.replace(
|
|
261
|
+
/preferences:\s*['"]?_goiabaseeds\/_memory\/preferences\.md['"]?/,
|
|
262
|
+
'preferences: "_config/preferences.md"'
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return updated;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Recursively copy directory
|
|
270
|
+
*/
|
|
271
|
+
async function copyDirRecursive(src, dst) {
|
|
272
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
273
|
+
|
|
274
|
+
for (const entry of entries) {
|
|
275
|
+
const srcPath = join(src, entry.name);
|
|
276
|
+
const dstPath = join(dst, entry.name);
|
|
277
|
+
|
|
278
|
+
if (entry.isDirectory()) {
|
|
279
|
+
await mkdir(dstPath, { recursive: true });
|
|
280
|
+
await copyDirRecursive(srcPath, dstPath);
|
|
281
|
+
} else {
|
|
282
|
+
await mkdir(dst, { recursive: true });
|
|
283
|
+
await copyFile(srcPath, dstPath);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|