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,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
|
|
File without changes
|