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,280 @@
1
+ import { copyFile, readFile, writeFile, mkdir, rm } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+
4
+ /**
5
+ * StateManager — encapsulates all state.json read/write operations.
6
+ *
7
+ * Output matches the DepartmentState interface in dashboard/src/types/state.ts:
8
+ * {
9
+ * department: string,
10
+ * status: "idle" | "running" | "completed" | "checkpoint",
11
+ * step: { current: number, total: number, label: string },
12
+ * agents: Agent[],
13
+ * handoff: Handoff | null,
14
+ * approvals?: { pending: PendingApproval[] },
15
+ * startedAt: string | null,
16
+ * updatedAt: string,
17
+ * completedAt?: string,
18
+ * failedAt?: string
19
+ * }
20
+ */
21
+ export class StateManager {
22
+ /**
23
+ * @param {object} options
24
+ * @param {string} options.departmentDir - Absolute path to the department directory
25
+ * @param {string} options.departmentCode - Department code identifier
26
+ * @param {Array<{id: string, name: string, icon: string}>} options.agents - Agent list from department-party.csv
27
+ * @param {number} options.totalSteps - Total number of pipeline steps
28
+ */
29
+ constructor({ departmentDir, departmentCode, agents, totalSteps }) {
30
+ this.departmentDir = departmentDir;
31
+ this.departmentCode = departmentCode;
32
+ this.totalSteps = totalSteps;
33
+ this.statePath = join(departmentDir, 'state.json');
34
+ this.startedAt = null;
35
+
36
+ // Build agents array with desk positions
37
+ // Formula from runner.pipeline.md: col = (index % 3) + 1, row = floor(index / 3) + 1
38
+ this.agents = agents.map((agent, index) => ({
39
+ id: agent.id,
40
+ name: agent.name,
41
+ icon: agent.icon,
42
+ status: 'idle',
43
+ deliverTo: null,
44
+ desk: {
45
+ col: (index % 3) + 1,
46
+ row: Math.floor(index / 3) + 1,
47
+ },
48
+ approval: null,
49
+ }));
50
+ }
51
+
52
+ /**
53
+ * Write initial idle state with all agents at desk positions.
54
+ */
55
+ async initialize() {
56
+ const state = {
57
+ department: this.departmentCode,
58
+ status: 'idle',
59
+ step: { current: 0, total: this.totalSteps, label: '' },
60
+ agents: this.agents.map(a => ({ ...a })),
61
+ handoff: null,
62
+ startedAt: null,
63
+ updatedAt: new Date().toISOString(),
64
+ };
65
+ await this._write(state);
66
+ }
67
+
68
+ /**
69
+ * Update state for a new step execution.
70
+ * Sets status to running, active agent to working, completed agents to done.
71
+ *
72
+ * @param {number} stepIndex - 0-based step index
73
+ * @param {string} label - Step ID / label
74
+ * @param {string} activeAgentId - Agent ID currently working
75
+ * @param {string[]} [completedAgentIds] - Agent IDs that have finished
76
+ */
77
+ async updateStep(stepIndex, label, activeAgentId, completedAgentIds = []) {
78
+ if (!this.startedAt) {
79
+ this.startedAt = new Date().toISOString();
80
+ }
81
+
82
+ const agents = this.agents.map(a => {
83
+ const copy = { ...a };
84
+ if (a.id === activeAgentId) {
85
+ copy.status = 'working';
86
+ } else if (completedAgentIds.includes(a.id)) {
87
+ copy.status = 'done';
88
+ } else {
89
+ copy.status = 'idle';
90
+ }
91
+ copy.deliverTo = null;
92
+ copy.approval = null;
93
+ return copy;
94
+ });
95
+
96
+ const state = {
97
+ department: this.departmentCode,
98
+ status: 'running',
99
+ step: { current: stepIndex + 1, total: this.totalSteps, label },
100
+ agents,
101
+ handoff: null,
102
+ startedAt: this.startedAt,
103
+ updatedAt: new Date().toISOString(),
104
+ };
105
+ await this._write(state);
106
+ }
107
+
108
+ /**
109
+ * Set delivering handoff animation (part 1).
110
+ * From agent status = "delivering" with deliverTo set.
111
+ *
112
+ * @param {string} fromId - Agent delivering
113
+ * @param {string} toId - Agent receiving
114
+ */
115
+ async setDelivering(fromId, toId) {
116
+ const current = await this._read();
117
+ if (!current) return;
118
+
119
+ for (const agent of current.agents) {
120
+ if (agent.id === fromId) {
121
+ agent.status = 'delivering';
122
+ agent.deliverTo = toId;
123
+ }
124
+ }
125
+
126
+ current.handoff = {
127
+ from: fromId,
128
+ to: toId,
129
+ startedAt: new Date().toISOString(),
130
+ };
131
+ current.updatedAt = new Date().toISOString();
132
+
133
+ await this._write(current);
134
+ }
135
+
136
+ /**
137
+ * Complete handoff animation (part 2).
138
+ * From agent = done, to agent = working.
139
+ *
140
+ * @param {string} fromId - Agent that delivered
141
+ * @param {string} toId - Agent now working
142
+ */
143
+ async setWorking(fromId, toId) {
144
+ const current = await this._read();
145
+ if (!current) return;
146
+
147
+ for (const agent of current.agents) {
148
+ if (agent.id === fromId) {
149
+ agent.status = 'done';
150
+ agent.deliverTo = null;
151
+ } else if (agent.id === toId) {
152
+ agent.status = 'working';
153
+ }
154
+ }
155
+
156
+ current.handoff = null;
157
+ current.updatedAt = new Date().toISOString();
158
+
159
+ await this._write(current);
160
+ }
161
+
162
+ /**
163
+ * Set checkpoint state with approval info.
164
+ *
165
+ * @param {string} agentId - Agent requesting approval
166
+ * @param {object} approval - Approval details
167
+ * @param {string} approval.step - Step ID
168
+ * @param {string} approval.question - Approval question
169
+ * @param {string} [approval.context] - Additional context
170
+ */
171
+ async setCheckpoint(agentId, approval) {
172
+ const current = await this._read();
173
+ if (!current) return;
174
+
175
+ current.status = 'checkpoint';
176
+
177
+ for (const agent of current.agents) {
178
+ if (agent.id === agentId) {
179
+ agent.status = 'waiting_approval';
180
+ agent.approval = {
181
+ needed: true,
182
+ step: approval.step,
183
+ question: approval.question,
184
+ context: approval.context || '',
185
+ requestedAt: new Date().toISOString(),
186
+ };
187
+ }
188
+ }
189
+
190
+ if (!current.approvals) current.approvals = { pending: [] };
191
+ current.approvals.pending.push({
192
+ agentId,
193
+ step: approval.step,
194
+ question: approval.question,
195
+ requestedAt: new Date().toISOString(),
196
+ });
197
+
198
+ current.updatedAt = new Date().toISOString();
199
+
200
+ await this._write(current);
201
+ }
202
+
203
+ /**
204
+ * Set pipeline as completed. All agents → done.
205
+ */
206
+ async complete() {
207
+ const current = await this._read();
208
+ if (!current) return;
209
+
210
+ current.status = 'completed';
211
+ current.completedAt = new Date().toISOString();
212
+ current.updatedAt = current.completedAt;
213
+ current.handoff = null;
214
+ current.approvals = undefined;
215
+
216
+ for (const agent of current.agents) {
217
+ agent.status = 'done';
218
+ agent.deliverTo = null;
219
+ agent.approval = null;
220
+ }
221
+
222
+ await this._write(current);
223
+ }
224
+
225
+ /**
226
+ * Set pipeline as failed.
227
+ * @param {string} reason - Failure reason
228
+ */
229
+ async fail(reason) {
230
+ const current = await this._read();
231
+ if (!current) return;
232
+
233
+ current.status = 'failed';
234
+ current.failedAt = new Date().toISOString();
235
+ current.updatedAt = current.failedAt;
236
+ current.error = reason;
237
+
238
+ await this._write(current);
239
+ }
240
+
241
+ /**
242
+ * Archive state.json to run output folder and delete working copy.
243
+ * @param {string} runId - Run ID (e.g., "2026-03-31-143022")
244
+ */
245
+ async archive(runId) {
246
+ const destDir = join(this.departmentDir, 'output', runId);
247
+ await mkdir(destDir, { recursive: true });
248
+ const destPath = join(destDir, 'state.json');
249
+ try {
250
+ await copyFile(this.statePath, destPath);
251
+ } catch {
252
+ // State file may not exist if pipeline failed early
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Delete the working state.json.
258
+ */
259
+ async cleanup() {
260
+ try {
261
+ await rm(this.statePath);
262
+ } catch {
263
+ // Already deleted or doesn't exist
264
+ }
265
+ }
266
+
267
+ async _write(state) {
268
+ await mkdir(dirname(this.statePath), { recursive: true });
269
+ await writeFile(this.statePath, JSON.stringify(state, null, 2), 'utf-8');
270
+ }
271
+
272
+ async _read() {
273
+ try {
274
+ const raw = await readFile(this.statePath, 'utf-8');
275
+ return JSON.parse(raw);
276
+ } catch {
277
+ return null;
278
+ }
279
+ }
280
+ }
package/src/tools.js ADDED
@@ -0,0 +1,158 @@
1
+ import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+
5
+ /**
6
+ * Build AI SDK-compatible tools for a department run.
7
+ *
8
+ * @param {string[]} departmentSkills - Skills declared in department.yaml
9
+ * @param {string} targetDir - Project root directory
10
+ * @returns {Promise<object>} Map of tool name → AI SDK tool definition
11
+ */
12
+ export async function buildTools(departmentSkills, targetDir) {
13
+ const { tool } = await import('ai');
14
+ const { z } = await import('zod');
15
+
16
+ const tools = {};
17
+
18
+ // Core tools — always available
19
+
20
+ tools.readFile = tool({
21
+ description: 'Read a file from disk. Returns the file content as a string.',
22
+ parameters: z.object({
23
+ path: z.string().describe('Absolute or relative file path to read'),
24
+ }),
25
+ execute: async ({ path }) => {
26
+ try {
27
+ const resolvedPath = join(targetDir, path);
28
+ return await readFile(resolvedPath, 'utf-8');
29
+ } catch (err) {
30
+ return `Error reading file: ${err.message}`;
31
+ }
32
+ },
33
+ });
34
+
35
+ tools.writeFile = tool({
36
+ description: 'Write content to a file. Creates parent directories if needed.',
37
+ parameters: z.object({
38
+ path: z.string().describe('Absolute or relative file path to write'),
39
+ content: z.string().describe('Content to write to the file'),
40
+ }),
41
+ execute: async ({ path, content }) => {
42
+ try {
43
+ const resolvedPath = join(targetDir, path);
44
+ await mkdir(dirname(resolvedPath), { recursive: true });
45
+ await writeFile(resolvedPath, content, 'utf-8');
46
+ return `File written: ${path}`;
47
+ } catch (err) {
48
+ return `Error writing file: ${err.message}`;
49
+ }
50
+ },
51
+ });
52
+
53
+ tools.listDirectory = tool({
54
+ description: 'List files and directories in a given path.',
55
+ parameters: z.object({
56
+ path: z.string().describe('Directory path to list'),
57
+ }),
58
+ execute: async ({ path }) => {
59
+ try {
60
+ const resolvedPath = join(targetDir, path);
61
+ const entries = await readdir(resolvedPath, { withFileTypes: true });
62
+ return entries
63
+ .map(e => `${e.isDirectory() ? '[DIR] ' : ''}${e.name}`)
64
+ .join('\n');
65
+ } catch (err) {
66
+ return `Error listing directory: ${err.message}`;
67
+ }
68
+ },
69
+ });
70
+
71
+ tools.bash = tool({
72
+ description: 'Execute a bash/shell command and return its output.',
73
+ parameters: z.object({
74
+ command: z.string().describe('The shell command to execute'),
75
+ }),
76
+ execute: async ({ command }) => {
77
+ try {
78
+ const output = execSync(command, {
79
+ cwd: targetDir,
80
+ encoding: 'utf-8',
81
+ timeout: 60_000,
82
+ maxBuffer: 1024 * 1024 * 5, // 5MB
83
+ });
84
+ return output || '(no output)';
85
+ } catch (err) {
86
+ return `Command failed (exit ${err.status}): ${err.stderr || err.message}`;
87
+ }
88
+ },
89
+ });
90
+
91
+ tools.webFetch = tool({
92
+ description: 'Fetch the text content of a URL.',
93
+ parameters: z.object({
94
+ url: z.string().url().describe('The URL to fetch'),
95
+ }),
96
+ execute: async ({ url }) => {
97
+ try {
98
+ const response = await fetch(url, {
99
+ headers: { 'User-Agent': 'GoiabaSeeds/1.0' },
100
+ signal: AbortSignal.timeout(30_000),
101
+ });
102
+ if (!response.ok) {
103
+ return `HTTP ${response.status}: ${response.statusText}`;
104
+ }
105
+ const text = await response.text();
106
+ // Truncate to avoid overwhelming context
107
+ return text.length > 50_000 ? text.slice(0, 50_000) + '\n...(truncated)' : text;
108
+ } catch (err) {
109
+ return `Error fetching URL: ${err.message}`;
110
+ }
111
+ },
112
+ });
113
+
114
+ // Conditional tools — only if department declares the skill
115
+
116
+ if (departmentSkills.includes('web_search')) {
117
+ const tavilyKey = process.env.TAVILY_API_KEY;
118
+
119
+ tools.webSearch = tool({
120
+ description: 'Search the web for information. Returns search results with titles, URLs, and snippets.',
121
+ parameters: z.object({
122
+ query: z.string().describe('The search query'),
123
+ maxResults: z.number().optional().default(5).describe('Maximum number of results'),
124
+ }),
125
+ execute: async ({ query, maxResults }) => {
126
+ if (!tavilyKey) {
127
+ return 'Web search unavailable: TAVILY_API_KEY not set. Set it in your environment to enable web search.';
128
+ }
129
+ try {
130
+ const response = await fetch('https://api.tavily.com/search', {
131
+ method: 'POST',
132
+ headers: { 'Content-Type': 'application/json' },
133
+ body: JSON.stringify({
134
+ api_key: tavilyKey,
135
+ query,
136
+ max_results: maxResults,
137
+ include_answer: true,
138
+ }),
139
+ signal: AbortSignal.timeout(15_000),
140
+ });
141
+ if (!response.ok) {
142
+ return `Search API error: HTTP ${response.status}`;
143
+ }
144
+ const data = await response.json();
145
+ const results = (data.results || []).map(r =>
146
+ `**${r.title}**\n${r.url}\n${r.content || ''}`
147
+ );
148
+ const answer = data.answer ? `**Summary:** ${data.answer}\n\n` : '';
149
+ return answer + results.join('\n\n');
150
+ } catch (err) {
151
+ return `Search error: ${err.message}`;
152
+ }
153
+ },
154
+ });
155
+ }
156
+
157
+ return tools;
158
+ }
package/src/update.js ADDED
@@ -0,0 +1,140 @@
1
+ import { cp, mkdir, readFile, stat } from 'node:fs/promises';
2
+ import { join, dirname, relative } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { loadLocale, t } from './i18n.js';
5
+ import { getTemplateEntries, loadSavedLocale } from './init.js';
6
+ import { listAvailable as listAvailableSkills, listInstalled as listInstalledSkills, installSkill, getSkillMeta } from './skills.js';
7
+ import { logEvent } from './logger.js';
8
+
9
+ async function loadSavedIdes(targetDir) {
10
+ try {
11
+ const prefsPath = join(targetDir, '_goiabaseeds', '_memory', 'preferences.md');
12
+ const content = await readFile(prefsPath, 'utf-8');
13
+ const match = content.match(/\*\*IDEs:\*\*\s*(.+)/);
14
+ if (match) {
15
+ return match[1].trim().split(/,\s*/);
16
+ }
17
+ } catch {
18
+ // No preferences file
19
+ }
20
+ return ['claude-code'];
21
+ }
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+ const TEMPLATES_DIR = join(__dirname, '..', 'templates');
25
+
26
+ const PROTECTED_PATHS = [
27
+ '_goiabaseeds/_memory',
28
+ '_goiabaseeds/_investigations',
29
+ 'agents',
30
+ 'departments',
31
+ ];
32
+
33
+ function isProtected(relativePath) {
34
+ const normalized = relativePath.replaceAll('\\', '/');
35
+ return PROTECTED_PATHS.some(
36
+ (p) => normalized === p || normalized.startsWith(p + '/')
37
+ );
38
+ }
39
+
40
+ export async function update(targetDir) {
41
+ console.log('\n 🔄 GoiabaSeeds — Update\n');
42
+
43
+ // 1. Check initialized
44
+ try {
45
+ await stat(join(targetDir, '_goiabaseeds'));
46
+ } catch {
47
+ await loadLocale('English');
48
+ console.log(` ${t('updateNotInitialized')}`);
49
+ return { success: false };
50
+ }
51
+
52
+ // 2. Load user's locale
53
+ await loadSavedLocale(targetDir);
54
+
55
+ // 3. Read versions
56
+ let currentVersion = null;
57
+ try {
58
+ currentVersion = (
59
+ await readFile(join(targetDir, '_goiabaseeds', '.goiabaseeds-version'), 'utf-8')
60
+ ).trim();
61
+ } catch {
62
+ // Legacy install — no version file
63
+ }
64
+
65
+ const newVersion = (
66
+ await readFile(join(TEMPLATES_DIR, '_goiabaseeds', '.goiabaseeds-version'), 'utf-8')
67
+ ).trim();
68
+
69
+ // 4. Announce
70
+ if (currentVersion) {
71
+ console.log(
72
+ ` ${t('updateStarting', { old: `v${currentVersion}`, new: `v${newVersion}` })}`
73
+ );
74
+ } else {
75
+ console.log(` ${t('updateStartingUnknown', { new: `v${newVersion}` })}`);
76
+ }
77
+
78
+ // 5. Copy common templates, skipping protected paths and ide-templates/
79
+ const entries = await getTemplateEntries(TEMPLATES_DIR);
80
+ let count = 0;
81
+
82
+ for (const entry of entries) {
83
+ const relativePath = relative(TEMPLATES_DIR, entry);
84
+ if (isProtected(relativePath)) continue;
85
+ // Skip ide-templates — handled separately below
86
+ if (relativePath.replaceAll('\\', '/').startsWith('ide-templates/')) continue;
87
+
88
+ const destPath = join(targetDir, relativePath);
89
+ await mkdir(dirname(destPath), { recursive: true });
90
+ await cp(entry, destPath);
91
+ console.log(` ${t('updatedFile', { path: relativePath.replaceAll('\\', '/') })}`);
92
+ count++;
93
+ }
94
+
95
+ // 6. Copy IDE-specific templates based on saved preferences
96
+ const ides = await loadSavedIdes(targetDir);
97
+ for (const ide of ides) {
98
+ const ideSrcDir = join(TEMPLATES_DIR, 'ide-templates', ide);
99
+ let ideEntries;
100
+ try {
101
+ ideEntries = await getTemplateEntries(ideSrcDir);
102
+ } catch {
103
+ continue; // no template dir for this IDE
104
+ }
105
+ for (const entry of ideEntries) {
106
+ const relPath = relative(ideSrcDir, entry);
107
+ if (isProtected(relPath)) continue;
108
+
109
+ const destPath = join(targetDir, relPath);
110
+ await mkdir(dirname(destPath), { recursive: true });
111
+ await cp(entry, destPath);
112
+ console.log(` ${t('updatedFile', { path: relPath.replaceAll('\\', '/') })}`);
113
+ count++;
114
+ }
115
+ }
116
+
117
+ // 6b. Install new non-MCP, non-hybrid bundled skills not already present
118
+ const availableSkills = await listAvailableSkills();
119
+ const installedSkills = await listInstalledSkills(targetDir);
120
+ for (const id of availableSkills) {
121
+ if (id === 'goiabaseeds-skill-creator') continue;
122
+ if (installedSkills.includes(id)) continue;
123
+ const meta = await getSkillMeta(id);
124
+ if (!meta) continue;
125
+ if (meta.type === 'mcp' || meta.type === 'hybrid') continue;
126
+ await installSkill(id, targetDir);
127
+ console.log(` ${t('createdFile', { path: `skills/${id}/SKILL.md` })}`);
128
+ count++;
129
+ }
130
+
131
+ // 7. Summary
132
+ console.log(`\n ${t('updateFileCount', { count })}`);
133
+ console.log(` ${t('updatePreserved')}`);
134
+ console.log(` ${t('updateSuccess', { version: `v${newVersion}` })}`);
135
+ console.log(`\n ${t('updateLatestHint')}\n`);
136
+
137
+ await logEvent('update', { from: currentVersion || 'unknown', to: newVersion }, targetDir);
138
+
139
+ return { success: true };
140
+ }
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,11 @@
1
+ {
2
+ "browser": {
3
+ "browserName": "chromium",
4
+ "isolated": false,
5
+ "userDataDir": "_goiabaseeds/_browser_profile",
6
+ "launchOptions": {
7
+ "headless": true,
8
+ "channel": "chrome"
9
+ }
10
+ }
11
+ }