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,478 @@
1
+ import { readFile, readdir, mkdir, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { StateManager } from './state-manager.js';
4
+ import { AgentSession } from './agent-session.js';
5
+ import { composeAgentContext, parseFrontmatter } from './context.js';
6
+ import { buildTools } from './tools.js';
7
+
8
+ /**
9
+ * PipelineRunner — programmatic pipeline execution engine.
10
+ *
11
+ * Translates the 400-line runner.pipeline.md LLM instructions
12
+ * into deterministic Node.js code.
13
+ */
14
+ export class PipelineRunner {
15
+ /**
16
+ * @param {object} options
17
+ * @param {string} options.targetDir - Project root directory
18
+ * @param {string} options.departmentName - Department directory name
19
+ * @param {object} options.provider - Provider instance from createProvider()
20
+ * @param {(msg: string) => void} [options.onLog] - Logging callback
21
+ * @param {() => Promise<string>} [options.onCheckpoint] - Checkpoint approval callback
22
+ */
23
+ constructor({ targetDir, departmentName, provider, onLog, onCheckpoint }) {
24
+ this.targetDir = targetDir;
25
+ this.departmentName = departmentName;
26
+ this.departmentDir = join(targetDir, 'departments', departmentName);
27
+ this.provider = provider;
28
+ this.onLog = onLog || ((msg) => console.log(` ${msg}`));
29
+ this.onCheckpoint = onCheckpoint;
30
+ this.stateManager = null;
31
+ this.runId = null;
32
+ this.completedAgentIds = [];
33
+ this.mainSession = null;
34
+ this.versionCache = new Map();
35
+ }
36
+
37
+ /**
38
+ * Execute the full pipeline.
39
+ * @returns {Promise<{success: boolean, runId: string}>}
40
+ */
41
+ async run() {
42
+ // --- INITIALIZATION ---
43
+ const { department, agents, pipeline, skills } = await this._loadDepartment();
44
+
45
+ this.onLog(`🏢 ${department.name || this.departmentName}`);
46
+ this.onLog(`📋 ${pipeline.steps.length} steps | ${agents.length} agents`);
47
+
48
+ // Generate run ID
49
+ this.runId = await this._generateRunId();
50
+ await mkdir(join(this.departmentDir, 'output', this.runId), { recursive: true });
51
+ this.onLog(`📁 Run: ${this.runId}`);
52
+
53
+ // Initialize state manager
54
+ this.stateManager = new StateManager({
55
+ departmentDir: this.departmentDir,
56
+ departmentCode: department.code || this.departmentName,
57
+ agents,
58
+ totalSteps: pipeline.steps.length,
59
+ });
60
+ await this.stateManager.initialize();
61
+
62
+ // Build tools
63
+ const tools = await buildTools(skills, this.targetDir);
64
+
65
+ // --- STEP EXECUTION LOOP ---
66
+ try {
67
+ for (let i = 0; i < pipeline.steps.length; i++) {
68
+ const stepFile = pipeline.steps[i];
69
+ const stepPath = join(this.departmentDir, 'pipeline', 'steps', stepFile);
70
+ const stepContent = await readFile(stepPath, 'utf-8');
71
+ const { meta: stepMeta, body: stepBody } = parseFrontmatter(stepContent);
72
+
73
+ const stepId = stepMeta.step_id || stepFile.replace(/\.md$/, '');
74
+ const agentId = stepMeta.agent;
75
+ const execution = stepMeta.execution || 'subagent';
76
+ const modelTier = stepMeta.model_tier || 'powerful';
77
+
78
+ this.onLog('');
79
+ this.onLog(`━━━ Step ${i + 1}/${pipeline.steps.length}: ${stepId} ━━━`);
80
+
81
+ // --- DASHBOARD HANDOFF (between steps) ---
82
+ if (i > 0 && agentId) {
83
+ const prevStep = pipeline.steps[i - 1];
84
+ const prevContent = await readFile(
85
+ join(this.departmentDir, 'pipeline', 'steps', prevStep), 'utf-8'
86
+ );
87
+ const { meta: prevMeta } = parseFrontmatter(prevContent);
88
+ const prevAgentId = prevMeta.agent;
89
+
90
+ if (prevAgentId && prevAgentId !== agentId) {
91
+ await this.stateManager.setDelivering(prevAgentId, agentId);
92
+ await sleep(3000);
93
+ await this.stateManager.setWorking(prevAgentId, agentId);
94
+ }
95
+ }
96
+
97
+ // --- CHECKPOINT ---
98
+ if (stepMeta.type === 'checkpoint') {
99
+ await this._executeCheckpoint(stepMeta, stepBody, i, agentId);
100
+ continue;
101
+ }
102
+
103
+ // --- UPDATE STATE ---
104
+ await this.stateManager.updateStep(i, stepId, agentId, this.completedAgentIds);
105
+
106
+ // --- RESOLVE AGENT ---
107
+ const agentPath = this._resolveAgentPath(agentId, agents);
108
+
109
+ // --- COMPOSE CONTEXT ---
110
+ const systemPrompt = await composeAgentContext({
111
+ agentPath,
112
+ stepMeta,
113
+ departmentDir: this.departmentDir,
114
+ targetDir: this.targetDir,
115
+ });
116
+
117
+ // --- BUILD USER PROMPT ---
118
+ const userPrompt = await this._buildUserPrompt(stepMeta, stepBody);
119
+
120
+ // --- EXECUTE ---
121
+ if (execution === 'subagent') {
122
+ await this._executeSubagent({
123
+ stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId,
124
+ });
125
+ } else {
126
+ // inline
127
+ await this._executeInline({
128
+ stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId,
129
+ });
130
+ }
131
+
132
+ // Track completed agent
133
+ if (agentId && !this.completedAgentIds.includes(agentId)) {
134
+ this.completedAgentIds.push(agentId);
135
+ }
136
+ }
137
+
138
+ // --- POST-COMPLETION ---
139
+ await this.stateManager.complete();
140
+ await this.stateManager.archive(this.runId);
141
+
142
+ this.onLog('');
143
+ this.onLog('✅ Pipeline completed!');
144
+ this.onLog(`📁 Output: departments/${this.departmentName}/output/${this.runId}/`);
145
+
146
+ // Wait 10s for dashboard to show completed state, then clean up
147
+ await sleep(10_000);
148
+ await this.stateManager.cleanup();
149
+
150
+ return { success: true, runId: this.runId };
151
+
152
+ } catch (err) {
153
+ await this.stateManager.fail(err.message);
154
+ this.onLog(`\n❌ Pipeline failed: ${err.message}`);
155
+ return { success: false, runId: this.runId };
156
+ }
157
+ }
158
+
159
+ // ── Execution modes ──────────────────────────────────────────
160
+
161
+ async _executeSubagent({ stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId }) {
162
+ const agentInfo = this._findAgent(agentId);
163
+ this.onLog(`🔍 ${agentInfo?.icon || '🤖'} ${agentInfo?.name || agentId} is working in the background...`);
164
+
165
+ const model = this.provider.getModel(modelTier);
166
+ const session = new AgentSession({ model, systemPrompt, tools });
167
+ const result = await session.run(userPrompt);
168
+
169
+ // Verify output file if specified
170
+ if (stepMeta.outputFile) {
171
+ const outputPath = this._transformOutputPath(stepMeta.outputFile);
172
+ await this._verifyOrWriteOutput(outputPath, result);
173
+ }
174
+
175
+ // Veto condition check
176
+ if (stepMeta.veto) {
177
+ await this._checkVeto(result, stepMeta, systemPrompt, tools, modelTier);
178
+ }
179
+
180
+ this.onLog(`✓ ${agentInfo?.name || agentId} completed`);
181
+ }
182
+
183
+ async _executeInline({ stepMeta, systemPrompt, userPrompt, modelTier, tools, agentId, stepId }) {
184
+ const agentInfo = this._findAgent(agentId);
185
+ this.onLog(`${agentInfo?.icon || '🤖'} ${agentInfo?.name || agentId} is working...`);
186
+
187
+ const model = this.provider.getModel(modelTier);
188
+
189
+ // Inline steps share the main session
190
+ if (!this.mainSession) {
191
+ this.mainSession = new AgentSession({ model, systemPrompt, tools });
192
+ } else {
193
+ // Update system prompt for new agent
194
+ this.mainSession.systemPrompt = systemPrompt;
195
+ this.mainSession.model = model;
196
+ }
197
+
198
+ const result = await this.mainSession.runStreaming(userPrompt, (chunk) => {
199
+ process.stdout.write(chunk);
200
+ });
201
+ console.log(''); // newline after streaming
202
+
203
+ if (stepMeta.outputFile) {
204
+ const outputPath = this._transformOutputPath(stepMeta.outputFile);
205
+ await this._verifyOrWriteOutput(outputPath, result);
206
+ }
207
+
208
+ this.onLog(`✓ ${agentInfo?.name || agentId} completed`);
209
+ }
210
+
211
+ async _executeCheckpoint(stepMeta, stepBody, stepIndex, agentId) {
212
+ const question = stepMeta.approval_question || stepBody.trim() || 'Approve and continue?';
213
+
214
+ this.onLog(`⏸️ Checkpoint: ${stepMeta.step_id || 'approval'}`);
215
+
216
+ // Set checkpoint state for dashboard
217
+ if (agentId) {
218
+ await this.stateManager.setCheckpoint(agentId, {
219
+ step: stepMeta.step_id || `step-${stepIndex}`,
220
+ question,
221
+ context: stepBody,
222
+ });
223
+ }
224
+
225
+ // Get approval — CLI or dashboard
226
+ let response;
227
+ if (this.onCheckpoint) {
228
+ response = await this.onCheckpoint(question, stepMeta);
229
+ } else {
230
+ response = await this._cliCheckpoint(question);
231
+ }
232
+
233
+ this.onLog(`✓ Checkpoint resolved: ${response.action}`);
234
+
235
+ // Write response to outputFile if specified (run_id injection only, no version folder)
236
+ if (stepMeta.outputFile) {
237
+ const outputPath = this._transformOutputPathStep1Only(stepMeta.outputFile);
238
+ const { writeFile: fsWriteFile } = await import('node:fs/promises');
239
+ const { dirname: pathDirname } = await import('node:path');
240
+ const { mkdir: fsMkdir } = await import('node:fs/promises');
241
+ await fsMkdir(pathDirname(outputPath), { recursive: true });
242
+ await fsWriteFile(outputPath, response.instruction || response.action, 'utf-8');
243
+ }
244
+
245
+ // Update state back to running
246
+ await this.stateManager.updateStep(stepIndex, stepMeta.step_id || '', agentId, this.completedAgentIds);
247
+ }
248
+
249
+ async _cliCheckpoint(question) {
250
+ const { default: selectFn } = await import('@inquirer/select');
251
+ const action = await selectFn({
252
+ message: question,
253
+ choices: [
254
+ { name: 'Approve', value: 'approve' },
255
+ { name: 'Revise', value: 'revise' },
256
+ ],
257
+ });
258
+
259
+ let instruction = '';
260
+ if (action === 'revise') {
261
+ const { default: inputFn } = await import('@inquirer/input');
262
+ instruction = await inputFn({ message: 'Revision instructions:' });
263
+ }
264
+
265
+ return { action, instruction };
266
+ }
267
+
268
+ // ── Veto conditions ──────────────────────────────────────────
269
+
270
+ async _checkVeto(output, stepMeta, systemPrompt, tools, modelTier) {
271
+ // Simple veto check using fast model
272
+ const model = this.provider.getModel('fast');
273
+ const vetoSession = new AgentSession({
274
+ model,
275
+ systemPrompt: 'You are a quality checker. Evaluate if the output violates any veto conditions. Reply with PASS if all conditions are met, or FAIL: <reason> if any are violated.',
276
+ tools: {},
277
+ });
278
+
279
+ const vetoPrompt = `## Veto Conditions\n${stepMeta.veto}\n\n## Output to Check\n${output}`;
280
+ const result = await vetoSession.run(vetoPrompt);
281
+
282
+ if (result.toUpperCase().startsWith('FAIL')) {
283
+ this.onLog(`⚠️ Veto triggered: ${result}`);
284
+ // Could implement retry logic here (max 2 attempts per runner.pipeline.md)
285
+ }
286
+ }
287
+
288
+ // ── Output path transformation ───────────────────────────────
289
+
290
+ /**
291
+ * Apply full output path transformation (Step 1 + Step 2).
292
+ * Step 1: Insert run_id after departments/{name}/output/
293
+ * Step 2: Insert version folder (v1, v2, etc.) before filename
294
+ */
295
+ _transformOutputPath(rawPath) {
296
+ let path = this._transformOutputPathStep1Only(rawPath);
297
+
298
+ // Step 2: Version folder
299
+ const parts = path.replace(/\\/g, '/').split('/');
300
+ const filename = parts.pop();
301
+ const groupDir = parts.join('/');
302
+
303
+ // Check cache for this group
304
+ if (this.versionCache.has(groupDir)) {
305
+ return join(groupDir, this.versionCache.get(groupDir), filename);
306
+ }
307
+
308
+ // Detect existing versions
309
+ let version = 'v1';
310
+ try {
311
+ const { readdirSync } = require('node:fs');
312
+ const entries = readdirSync(groupDir);
313
+ const versions = entries
314
+ .filter(e => /^v\d+$/.test(e))
315
+ .map(e => parseInt(e.slice(1)))
316
+ .sort((a, b) => a - b);
317
+ if (versions.length > 0) {
318
+ version = `v${versions[versions.length - 1] + 1}`;
319
+ }
320
+ } catch {
321
+ // Directory doesn't exist yet — v1
322
+ }
323
+
324
+ this.versionCache.set(groupDir, version);
325
+ return join(groupDir, version, filename);
326
+ }
327
+
328
+ /**
329
+ * Step 1 only: Insert run_id after departments/{name}/output/
330
+ * Used for checkpoint files (no version folder).
331
+ */
332
+ _transformOutputPathStep1Only(rawPath) {
333
+ const normalized = rawPath.replace(/\\/g, '/');
334
+ const outputPrefix = `departments/${this.departmentName}/output/`;
335
+
336
+ if (normalized.startsWith(outputPrefix)) {
337
+ const rest = normalized.slice(outputPrefix.length);
338
+ return join(this.targetDir, outputPrefix, this.runId, rest);
339
+ }
340
+
341
+ // If path doesn't match pattern, resolve relative to targetDir
342
+ return join(this.targetDir, normalized);
343
+ }
344
+
345
+ // ── Helper methods ───────────────────────────────────────────
346
+
347
+ async _loadDepartment() {
348
+ const { parse } = await import('yaml');
349
+
350
+ // Read department.yaml
351
+ const deptYaml = await readFile(join(this.departmentDir, 'department.yaml'), 'utf-8');
352
+ const department = parse(deptYaml);
353
+
354
+ // Read department-party.csv for agent info
355
+ const agents = await this._parsePartyCSV();
356
+
357
+ // Read pipeline.yaml
358
+ const pipelineYaml = await readFile(
359
+ join(this.departmentDir, 'pipeline', 'pipeline.yaml'), 'utf-8'
360
+ );
361
+ const pipelineRaw = parse(pipelineYaml);
362
+
363
+ // Extract step file list from pipeline
364
+ const steps = [];
365
+ if (Array.isArray(pipelineRaw?.steps)) {
366
+ for (const step of pipelineRaw.steps) {
367
+ if (typeof step === 'string') {
368
+ steps.push(step);
369
+ } else if (step?.file) {
370
+ steps.push(step.file);
371
+ }
372
+ }
373
+ }
374
+
375
+ // Extract skills list
376
+ const skills = department.skills || [];
377
+
378
+ return { department, agents, pipeline: { steps }, skills };
379
+ }
380
+
381
+ async _parsePartyCSV() {
382
+ const partyPath = join(this.departmentDir, 'department-party.csv');
383
+ try {
384
+ const content = await readFile(partyPath, 'utf-8');
385
+ const lines = content.split('\n').filter(l => l.trim());
386
+
387
+ // Skip header if present
388
+ const startIdx = lines[0]?.includes('id,') || lines[0]?.includes('id\t') ? 1 : 0;
389
+
390
+ return lines.slice(startIdx).map(line => {
391
+ const [id, name, icon] = line.split(',').map(s => s?.trim());
392
+ return { id: id || '', name: name || id || '', icon: icon || '🤖' };
393
+ });
394
+ } catch {
395
+ // No party CSV — fallback to department.yaml agents
396
+ const deptYaml = await readFile(join(this.departmentDir, 'department.yaml'), 'utf-8');
397
+ const { parse } = await import('yaml');
398
+ const dept = parse(deptYaml);
399
+ return (dept.agents || []).map((a, i) => {
400
+ const id = typeof a === 'string' ? a : Object.keys(a)[0];
401
+ return { id, name: id, icon: '🤖' };
402
+ });
403
+ }
404
+ }
405
+
406
+ _resolveAgentPath(agentId, agents) {
407
+ // Try standard location
408
+ return join(this.departmentDir, 'agents', `${agentId}.agent.md`);
409
+ }
410
+
411
+ _findAgent(agentId) {
412
+ if (!this.stateManager) return null;
413
+ return this.stateManager.agents.find(a => a.id === agentId);
414
+ }
415
+
416
+ async _buildUserPrompt(stepMeta, stepBody) {
417
+ const parts = [stepBody];
418
+
419
+ // Include input file content if specified
420
+ if (stepMeta.inputFile) {
421
+ try {
422
+ const inputPath = join(this.targetDir, stepMeta.inputFile);
423
+ const inputContent = await readFile(inputPath, 'utf-8');
424
+ parts.push(`\n--- INPUT ---\n${inputContent}`);
425
+ } catch {
426
+ parts.push(`\n--- INPUT ---\n(Input file not found: ${stepMeta.inputFile})`);
427
+ }
428
+ }
429
+
430
+ // Include transformed output path
431
+ if (stepMeta.outputFile) {
432
+ const outputPath = this._transformOutputPath(stepMeta.outputFile);
433
+ parts.push(`\n--- OUTPUT PATH ---\nWrite your output to: ${outputPath}`);
434
+ }
435
+
436
+ return parts.join('\n');
437
+ }
438
+
439
+ async _verifyOrWriteOutput(outputPath, content) {
440
+ const { writeFile: fsWrite } = await import('node:fs/promises');
441
+ const { dirname: pathDirname } = await import('node:path');
442
+ const dir = pathDirname(outputPath);
443
+ await mkdir(dir, { recursive: true });
444
+
445
+ // Check if the agent already wrote the file via tools
446
+ try {
447
+ await stat(outputPath);
448
+ return; // File exists — agent wrote it
449
+ } catch {
450
+ // Agent didn't write it — write the response text as output
451
+ await fsWrite(outputPath, content, 'utf-8');
452
+ }
453
+ }
454
+
455
+ async _generateRunId() {
456
+ const now = new Date();
457
+ const pad = (n) => String(n).padStart(2, '0');
458
+ let baseId = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
459
+
460
+ const outputDir = join(this.departmentDir, 'output');
461
+ try {
462
+ const entries = await readdir(outputDir);
463
+ let id = baseId;
464
+ let suffix = 2;
465
+ while (entries.includes(id)) {
466
+ id = `${baseId}-${suffix}`;
467
+ suffix++;
468
+ }
469
+ return id;
470
+ } catch {
471
+ return baseId;
472
+ }
473
+ }
474
+ }
475
+
476
+ function sleep(ms) {
477
+ return new Promise(resolve => setTimeout(resolve, ms));
478
+ }
package/src/prompt.js ADDED
@@ -0,0 +1,46 @@
1
+ import { t } from './i18n.js';
2
+
3
+ export function createPrompt() {
4
+ return {
5
+ async ask(question) {
6
+ const { default: input } = await import('@inquirer/input');
7
+ return input({ message: question });
8
+ },
9
+ async choose(question, options) {
10
+ const { default: select } = await import('@inquirer/select');
11
+
12
+ const choices = options.map(opt => ({
13
+ name: opt.label,
14
+ value: opt,
15
+ }));
16
+
17
+ return select({
18
+ message: ` ${question}`,
19
+ choices,
20
+ loop: false,
21
+ });
22
+ },
23
+ async multiChoose(question, options, { validate } = {}) {
24
+ const { default: checkbox, Separator } = await import('@inquirer/checkbox');
25
+
26
+ const choices = options.map(opt => {
27
+ if (opt.separator) return new Separator(opt.label);
28
+ return {
29
+ name: opt.label,
30
+ value: opt.value,
31
+ checked: opt.checked ?? false,
32
+ disabled: opt.disabled ? t('comingSoon') : false,
33
+ };
34
+ });
35
+
36
+ return checkbox({
37
+ message: ` ${question}`,
38
+ choices,
39
+ loop: false,
40
+ validate: validate ?? ((selected) =>
41
+ selected.length > 0 || t('atLeastOneIde')),
42
+ });
43
+ },
44
+ close() {},
45
+ };
46
+ }
@@ -0,0 +1,156 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ const CONFIG_PATH = '_goiabaseeds/config.yaml';
5
+
6
+ const PROVIDER_PACKAGES = {
7
+ anthropic: '@ai-sdk/anthropic',
8
+ openai: '@ai-sdk/openai',
9
+ google: '@ai-sdk/google',
10
+ 'github-copilot': '@ai-sdk/openai',
11
+ };
12
+
13
+ const ENV_KEYS = {
14
+ anthropic: 'ANTHROPIC_API_KEY',
15
+ openai: 'OPENAI_API_KEY',
16
+ google: 'GOOGLE_GENERATIVE_AI_API_KEY',
17
+ 'github-copilot': 'GITHUB_TOKEN',
18
+ };
19
+
20
+ const DEFAULT_CONFIG = {
21
+ runtime: 'standalone',
22
+ default_provider: 'anthropic',
23
+ providers: {
24
+ anthropic: {
25
+ models: {
26
+ powerful: 'claude-sonnet-4-20250514',
27
+ fast: 'claude-haiku-4-5-20251001',
28
+ },
29
+ },
30
+ openai: {
31
+ models: {
32
+ powerful: 'gpt-4o',
33
+ fast: 'gpt-4o-mini',
34
+ },
35
+ },
36
+ google: {
37
+ models: {
38
+ powerful: 'gemini-2.5-pro',
39
+ fast: 'gemini-2.0-flash',
40
+ },
41
+ },
42
+ 'github-copilot': {
43
+ models: {
44
+ powerful: 'gpt-4o',
45
+ fast: 'gpt-4o-mini',
46
+ },
47
+ },
48
+ },
49
+ };
50
+
51
+ /**
52
+ * Parse a simple YAML config file.
53
+ * Uses the `yaml` package for nested structures.
54
+ */
55
+ export async function loadProviderConfig(targetDir) {
56
+ const configPath = join(targetDir, CONFIG_PATH);
57
+ try {
58
+ const raw = await readFile(configPath, 'utf-8');
59
+ const { parse } = await import('yaml');
60
+ const parsed = parse(raw);
61
+ return {
62
+ runtime: parsed?.runtime || DEFAULT_CONFIG.runtime,
63
+ default_provider: parsed?.default_provider || DEFAULT_CONFIG.default_provider,
64
+ providers: { ...DEFAULT_CONFIG.providers, ...parsed?.providers },
65
+ };
66
+ } catch (err) {
67
+ if (err.code === 'ENOENT') return { ...DEFAULT_CONFIG };
68
+ throw err;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Create a provider instance with model resolution.
74
+ *
75
+ * @param {object} config - The provider config from loadProviderConfig()
76
+ * @returns {{ getModel(tier: string): object, getProviderName(): string, listModels(): object, getEnvKey(): string }}
77
+ */
78
+ export async function createProvider(config) {
79
+ const providerName = config.default_provider;
80
+ const providerConfig = config.providers[providerName];
81
+
82
+ if (!providerConfig) {
83
+ throw new Error(`Provider "${providerName}" not found in config. Available: ${Object.keys(config.providers).join(', ')}`);
84
+ }
85
+
86
+ const envKey = ENV_KEYS[providerName];
87
+ const apiKey = process.env[envKey];
88
+
89
+ if (!apiKey) {
90
+ throw new Error(
91
+ `Missing API key for ${providerName}. Set the ${envKey} environment variable.\n` +
92
+ ` Example: export ${envKey}=your-key-here`
93
+ );
94
+ }
95
+
96
+ // Dynamic import — only load the SDK package for the configured provider
97
+ const pkgName = PROVIDER_PACKAGES[providerName];
98
+ let sdkModule;
99
+ try {
100
+ sdkModule = await import(pkgName);
101
+ } catch {
102
+ throw new Error(
103
+ `Provider package "${pkgName}" not installed.\n` +
104
+ ` Run: npm install ${pkgName}`
105
+ );
106
+ }
107
+
108
+ // Build the provider factory
109
+ let providerInstance;
110
+
111
+ if (providerName === 'github-copilot') {
112
+ // Copilot uses OpenAI SDK with custom endpoint
113
+ const { createOpenAI } = sdkModule;
114
+ providerInstance = createOpenAI({
115
+ apiKey,
116
+ baseURL: 'https://api.githubcopilot.com',
117
+ });
118
+ } else if (providerName === 'anthropic') {
119
+ const { createAnthropic } = sdkModule;
120
+ providerInstance = createAnthropic({ apiKey });
121
+ } else if (providerName === 'openai') {
122
+ const { createOpenAI } = sdkModule;
123
+ providerInstance = createOpenAI({ apiKey });
124
+ } else if (providerName === 'google') {
125
+ const { createGoogleGenerativeAI } = sdkModule;
126
+ providerInstance = createGoogleGenerativeAI({ apiKey });
127
+ }
128
+
129
+ return {
130
+ /**
131
+ * Get an AI SDK model instance for the given tier.
132
+ * @param {'fast'|'powerful'} tier
133
+ * @returns {object} AI SDK model instance
134
+ */
135
+ getModel(tier) {
136
+ const modelId = tier === 'fast'
137
+ ? providerConfig.models.fast
138
+ : providerConfig.models.powerful;
139
+ return providerInstance(modelId);
140
+ },
141
+
142
+ getProviderName() {
143
+ return providerName;
144
+ },
145
+
146
+ getEnvKey() {
147
+ return envKey;
148
+ },
149
+
150
+ listModels() {
151
+ return { ...providerConfig.models };
152
+ },
153
+ };
154
+ }
155
+
156
+ export { DEFAULT_CONFIG, ENV_KEYS, PROVIDER_PACKAGES };