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.
Files changed (129) hide show
  1. package/README.md +173 -0
  2. package/bin/goiabaseeds.js +98 -0
  3. package/eslint.config.js +14 -0
  4. package/package.json +61 -0
  5. package/skills/README.md +60 -0
  6. package/skills/apify/SKILL.md +55 -0
  7. package/skills/blotato/SKILL.md +63 -0
  8. package/skills/canva/SKILL.md +60 -0
  9. package/skills/goiabaseeds-agent-creator/SKILL.md +192 -0
  10. package/skills/goiabaseeds-skill-creator/SKILL.md +407 -0
  11. package/skills/goiabaseeds-skill-creator/agents/analyzer.md +274 -0
  12. package/skills/goiabaseeds-skill-creator/agents/comparator.md +202 -0
  13. package/skills/goiabaseeds-skill-creator/agents/grader.md +223 -0
  14. package/skills/goiabaseeds-skill-creator/assets/eval_review.html +146 -0
  15. package/skills/goiabaseeds-skill-creator/eval-viewer/generate_review.py +471 -0
  16. package/skills/goiabaseeds-skill-creator/eval-viewer/viewer.html +1325 -0
  17. package/skills/goiabaseeds-skill-creator/references/schemas.md +430 -0
  18. package/skills/goiabaseeds-skill-creator/references/skill-format.md +235 -0
  19. package/skills/goiabaseeds-skill-creator/scripts/__init__.py +0 -0
  20. package/skills/goiabaseeds-skill-creator/scripts/aggregate_benchmark.py +401 -0
  21. package/skills/goiabaseeds-skill-creator/scripts/quick_validate.py +103 -0
  22. package/skills/goiabaseeds-skill-creator/scripts/run_eval.py +310 -0
  23. package/skills/goiabaseeds-skill-creator/scripts/utils.py +47 -0
  24. package/skills/image-creator/SKILL.md +155 -0
  25. package/skills/image-fetcher/SKILL.md +91 -0
  26. package/skills/image-generator/SKILL.md +124 -0
  27. package/skills/image-generator/scripts/generate.py +175 -0
  28. package/skills/instagram-publisher/SKILL.md +118 -0
  29. package/skills/instagram-publisher/scripts/publish.js +164 -0
  30. package/src/agent-session.js +110 -0
  31. package/src/agents-cli.js +158 -0
  32. package/src/agents.js +134 -0
  33. package/src/bundle-detector.js +75 -0
  34. package/src/bundle.js +286 -0
  35. package/src/context.js +142 -0
  36. package/src/export.js +52 -0
  37. package/src/i18n.js +48 -0
  38. package/src/init.js +367 -0
  39. package/src/locales/en.json +72 -0
  40. package/src/locales/es.json +71 -0
  41. package/src/locales/pt-BR.json +71 -0
  42. package/src/logger.js +38 -0
  43. package/src/models-cli.js +165 -0
  44. package/src/pipeline-runner.js +478 -0
  45. package/src/prompt.js +46 -0
  46. package/src/provider.js +156 -0
  47. package/src/readme/README.md +181 -0
  48. package/src/run.js +100 -0
  49. package/src/runs.js +90 -0
  50. package/src/skills-cli.js +157 -0
  51. package/src/skills.js +146 -0
  52. package/src/state-manager.js +280 -0
  53. package/src/tools.js +158 -0
  54. package/src/update.js +140 -0
  55. package/templates/_goiabaseeds/.goiabaseeds-version +1 -0
  56. package/templates/_goiabaseeds/_investigations/.gitkeep +0 -0
  57. package/templates/_goiabaseeds/config/playwright.config.json +11 -0
  58. package/templates/_goiabaseeds/core/architect.agent.yaml +1141 -0
  59. package/templates/_goiabaseeds/core/best-practices/_catalog.yaml +116 -0
  60. package/templates/_goiabaseeds/core/best-practices/blog-post.md +132 -0
  61. package/templates/_goiabaseeds/core/best-practices/blog-seo.md +127 -0
  62. package/templates/_goiabaseeds/core/best-practices/copywriting.md +428 -0
  63. package/templates/_goiabaseeds/core/best-practices/data-analysis.md +401 -0
  64. package/templates/_goiabaseeds/core/best-practices/email-newsletter.md +118 -0
  65. package/templates/_goiabaseeds/core/best-practices/email-sales.md +110 -0
  66. package/templates/_goiabaseeds/core/best-practices/image-design.md +349 -0
  67. package/templates/_goiabaseeds/core/best-practices/instagram-feed.md +235 -0
  68. package/templates/_goiabaseeds/core/best-practices/instagram-reels.md +112 -0
  69. package/templates/_goiabaseeds/core/best-practices/instagram-stories.md +107 -0
  70. package/templates/_goiabaseeds/core/best-practices/linkedin-article.md +116 -0
  71. package/templates/_goiabaseeds/core/best-practices/linkedin-post.md +121 -0
  72. package/templates/_goiabaseeds/core/best-practices/researching.md +347 -0
  73. package/templates/_goiabaseeds/core/best-practices/review.md +269 -0
  74. package/templates/_goiabaseeds/core/best-practices/social-networks-publishing.md +294 -0
  75. package/templates/_goiabaseeds/core/best-practices/strategist.md +344 -0
  76. package/templates/_goiabaseeds/core/best-practices/technical-writing.md +363 -0
  77. package/templates/_goiabaseeds/core/best-practices/twitter-post.md +105 -0
  78. package/templates/_goiabaseeds/core/best-practices/twitter-thread.md +122 -0
  79. package/templates/_goiabaseeds/core/best-practices/whatsapp-broadcast.md +107 -0
  80. package/templates/_goiabaseeds/core/best-practices/youtube-script.md +122 -0
  81. package/templates/_goiabaseeds/core/best-practices/youtube-shorts.md +112 -0
  82. package/templates/_goiabaseeds/core/prompts/auguste.dupin.prompt.md +1008 -0
  83. package/templates/_goiabaseeds/core/runner.pipeline.md +467 -0
  84. package/templates/_goiabaseeds/core/skills.engine.md +381 -0
  85. package/templates/dashboard/index.html +12 -0
  86. package/templates/dashboard/package-lock.json +2082 -0
  87. package/templates/dashboard/package.json +28 -0
  88. package/templates/dashboard/src/App.tsx +46 -0
  89. package/templates/dashboard/src/components/DepartmentCard.tsx +47 -0
  90. package/templates/dashboard/src/components/DepartmentSelector.tsx +61 -0
  91. package/templates/dashboard/src/components/StatusBadge.tsx +32 -0
  92. package/templates/dashboard/src/components/StatusBar.tsx +97 -0
  93. package/templates/dashboard/src/hooks/useDepartmentSocket.ts +84 -0
  94. package/templates/dashboard/src/lib/formatTime.ts +16 -0
  95. package/templates/dashboard/src/lib/normalizeState.ts +25 -0
  96. package/templates/dashboard/src/main.tsx +10 -0
  97. package/templates/dashboard/src/office/AgentDesk.tsx +151 -0
  98. package/templates/dashboard/src/office/HandoffEnvelope.tsx +108 -0
  99. package/templates/dashboard/src/office/OfficeScene.tsx +147 -0
  100. package/templates/dashboard/src/office/drawDesk.ts +263 -0
  101. package/templates/dashboard/src/office/drawFurniture.ts +129 -0
  102. package/templates/dashboard/src/office/drawRoom.ts +51 -0
  103. package/templates/dashboard/src/office/palette.ts +181 -0
  104. package/templates/dashboard/src/office/textures.ts +254 -0
  105. package/templates/dashboard/src/plugin/departmentWatcher.ts +210 -0
  106. package/templates/dashboard/src/store/useDepartmentStore.ts +56 -0
  107. package/templates/dashboard/src/styles/globals.css +36 -0
  108. package/templates/dashboard/src/types/state.ts +64 -0
  109. package/templates/dashboard/src/vite-env.d.ts +1 -0
  110. package/templates/dashboard/tsconfig.json +24 -0
  111. package/templates/dashboard/vite.config.ts +13 -0
  112. package/templates/departments/.gitkeep +0 -0
  113. package/templates/ide-templates/antigravity/.agent/rules/goiabaseeds.md +55 -0
  114. package/templates/ide-templates/antigravity/.agent/workflows/goiabaseeds.md +102 -0
  115. package/templates/ide-templates/claude-code/.claude/skills/goiabaseeds/SKILL.md +182 -0
  116. package/templates/ide-templates/claude-code/.mcp.json +8 -0
  117. package/templates/ide-templates/claude-code/CLAUDE.md +43 -0
  118. package/templates/ide-templates/codex/.agents/skills/goiabaseeds/SKILL.md +6 -0
  119. package/templates/ide-templates/codex/AGENTS.md +105 -0
  120. package/templates/ide-templates/cursor/.cursor/commands/goiabaseeds.md +9 -0
  121. package/templates/ide-templates/cursor/.cursor/mcp.json +8 -0
  122. package/templates/ide-templates/cursor/.cursor/rules/goiabaseeds.mdc +48 -0
  123. package/templates/ide-templates/cursor/.cursorignore +3 -0
  124. package/templates/ide-templates/opencode/.opencode/commands/goiabaseeds.md +9 -0
  125. package/templates/ide-templates/opencode/AGENTS.md +105 -0
  126. package/templates/ide-templates/vscode-copilot/.github/prompts/goiabaseeds.prompt.md +201 -0
  127. package/templates/ide-templates/vscode-copilot/.vscode/mcp.json +8 -0
  128. package/templates/ide-templates/vscode-copilot/.vscode/settings.json +3 -0
  129. 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
+ }