@innominatum/agentforge-cli 1.0.1
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/LICENSE +393 -0
- package/README.md +72 -0
- package/dist/index.js +739 -0
- package/documents/goclaw-llms-full.txt +28973 -0
- package/package.json +52 -0
- package/src/index.ts +805 -0
- package/templates/CLI_MANUAL.md +98 -0
- package/templates/default-agent/AGENTS.md +26 -0
- package/templates/default-agent/CAPABILITIES.md +15 -0
- package/templates/default-agent/HEARTBEAT.md +19 -0
- package/templates/default-agent/IDENTITY.md +8 -0
- package/templates/default-agent/MEMORY.md +23 -0
- package/templates/default-agent/SOUL.md +31 -0
- package/templates/default-agent/USER.md +13 -0
- package/templates/default-agent/USER_PREDEFINED.md +13 -0
- package/templates/default-skill/SKILL.md +9 -0
- package/tsconfig.json +14 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import slugify from "slugify";
|
|
7
|
+
import AdmZip from "adm-zip";
|
|
8
|
+
import FormData from "form-data";
|
|
9
|
+
import * as tar from "tar";
|
|
10
|
+
import axios from "axios";
|
|
11
|
+
import * as readline from "readline";
|
|
12
|
+
|
|
13
|
+
function confirmOverwrite(entityType: string): Promise<boolean> {
|
|
14
|
+
const rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return new Promise(resolve => {
|
|
20
|
+
rl.question(`⚠️ Atenção: O pull irá sobrescrever as suas ${entityType} locais. Quaisquer alterações não publicadas serão perdidas. Deseja continuar? (s/N) `, answer => {
|
|
21
|
+
rl.close();
|
|
22
|
+
const isYes = answer.toLowerCase() === 's' || answer.toLowerCase() === 'sim' || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
23
|
+
resolve(isYes);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getWorkspaceRoot(): string {
|
|
29
|
+
let dir = process.cwd();
|
|
30
|
+
while (dir !== path.parse(dir).root) {
|
|
31
|
+
if (fs.existsSync(path.join(dir, "agentforge.json")) || fs.existsSync(path.join(dir, "agentforge.yml"))) {
|
|
32
|
+
return dir;
|
|
33
|
+
}
|
|
34
|
+
dir = path.dirname(dir);
|
|
35
|
+
}
|
|
36
|
+
if (fs.existsSync(path.join(dir, "agentforge.json")) || fs.existsSync(path.join(dir, "agentforge.yml"))) {
|
|
37
|
+
return dir;
|
|
38
|
+
}
|
|
39
|
+
console.error("❌ Erro: Não foi possível encontrar a raiz do workspace (agentforge.json). Certifique-se de estar dentro do projeto.");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const program = new Command();
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.name("agentforge")
|
|
47
|
+
.description("CLI para gerir agentes, equipas e templates de agentes")
|
|
48
|
+
.version("0.1.0");
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command("init")
|
|
52
|
+
.alias("start")
|
|
53
|
+
.description("Cria a estrutura inicial do workspace de agentes")
|
|
54
|
+
.action(async () => {
|
|
55
|
+
const basePath = process.cwd();
|
|
56
|
+
|
|
57
|
+
const folders = [
|
|
58
|
+
"agents",
|
|
59
|
+
"documents",
|
|
60
|
+
"templates/default-agent",
|
|
61
|
+
"exports"
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (const folder of folders) {
|
|
65
|
+
await fs.ensureDir(path.join(basePath, folder));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const config = {
|
|
69
|
+
workspace: "agentforge",
|
|
70
|
+
version: 1,
|
|
71
|
+
goclaw: {
|
|
72
|
+
api_url: "http://localhost:18790",
|
|
73
|
+
username: "system",
|
|
74
|
+
token: "",
|
|
75
|
+
default_provider: "ollama-cloud",
|
|
76
|
+
default_model: "deepseek-v4-pro",
|
|
77
|
+
skills_import_endpoint: "/v1/skills/import",
|
|
78
|
+
skills_export_endpoint: "/v1/skills/export"
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await fs.writeJson(path.join(basePath, "agentforge.json"), config, { spaces: 2 });
|
|
83
|
+
|
|
84
|
+
// Copiar o manual da CLI para servir de README do workspace
|
|
85
|
+
const cliManualPath = path.join(__dirname, "../templates/CLI_MANUAL.md");
|
|
86
|
+
const workspaceReadmePath = path.join(basePath, "README.md");
|
|
87
|
+
if (await fs.pathExists(cliManualPath)) {
|
|
88
|
+
await fs.copy(cliManualPath, workspaceReadmePath);
|
|
89
|
+
} else {
|
|
90
|
+
await fs.writeFile(
|
|
91
|
+
workspaceReadmePath,
|
|
92
|
+
`# Agent Workspace\n\nWorkspace criado pela AgentForge CLI.\n`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Opcional: Copiar os templates originais da CLI para o workspace do usuário
|
|
97
|
+
const cliTemplatePath = path.join(__dirname, "../templates/default-agent");
|
|
98
|
+
const workspaceTemplatePath = path.join(basePath, "templates/default-agent");
|
|
99
|
+
|
|
100
|
+
if (await fs.pathExists(cliTemplatePath)) {
|
|
101
|
+
await fs.copy(cliTemplatePath, workspaceTemplatePath);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Copiar documentação do GoClaw para a pasta documents
|
|
105
|
+
const cliDocPath = path.join(__dirname, "../goclaw-llms-full.txt");
|
|
106
|
+
const workspaceDocPath = path.join(basePath, "documents/goclaw-llms-full.txt");
|
|
107
|
+
if (await fs.pathExists(cliDocPath)) {
|
|
108
|
+
await fs.copy(cliDocPath, workspaceDocPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log("Workspace de agentes criado com sucesso.");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const newCmd = program
|
|
115
|
+
.command("new")
|
|
116
|
+
.description("Cria novas entidades (agentes, skills, etc)");
|
|
117
|
+
|
|
118
|
+
program
|
|
119
|
+
.command("manual")
|
|
120
|
+
.alias("help-docs")
|
|
121
|
+
.description("Exibe o manual completo de uso da AgentForge CLI")
|
|
122
|
+
.action(async () => {
|
|
123
|
+
const cliManualPath = path.join(__dirname, "../templates/CLI_MANUAL.md");
|
|
124
|
+
if (await fs.pathExists(cliManualPath)) {
|
|
125
|
+
const content = await fs.readFile(cliManualPath, "utf-8");
|
|
126
|
+
console.log(content);
|
|
127
|
+
} else {
|
|
128
|
+
console.error("❌ Manual não encontrado.");
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
newCmd
|
|
133
|
+
.command("agent <name>")
|
|
134
|
+
.description("Cria um novo agente com os ficheiros base da template")
|
|
135
|
+
.action(async (name: string) => {
|
|
136
|
+
const basePath = getWorkspaceRoot();
|
|
137
|
+
const slug = slugify(name, { lower: true, strict: true });
|
|
138
|
+
|
|
139
|
+
const agentPath = path.join(basePath, "agents", slug);
|
|
140
|
+
|
|
141
|
+
if (await fs.pathExists(agentPath)) {
|
|
142
|
+
console.error(`❌ O agente "${name}" já existe em agents/${slug}.`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await fs.ensureDir(agentPath);
|
|
147
|
+
|
|
148
|
+
const workspaceTemplatePath = path.join(basePath, "templates/default-agent");
|
|
149
|
+
const cliTemplatePath = path.join(__dirname, "../templates/default-agent");
|
|
150
|
+
|
|
151
|
+
let sourceTemplatePath = "";
|
|
152
|
+
if (await fs.pathExists(workspaceTemplatePath)) {
|
|
153
|
+
sourceTemplatePath = workspaceTemplatePath;
|
|
154
|
+
} else if (await fs.pathExists(cliTemplatePath)) {
|
|
155
|
+
sourceTemplatePath = cliTemplatePath;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (sourceTemplatePath !== "") {
|
|
159
|
+
await fs.copy(sourceTemplatePath, agentPath);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const config = await getConfig();
|
|
163
|
+
const agentJson = {
|
|
164
|
+
agent_key: slug,
|
|
165
|
+
display_name: name,
|
|
166
|
+
agent_type: "custom",
|
|
167
|
+
status: "active",
|
|
168
|
+
emoji: "🔥",
|
|
169
|
+
context_window: 200000,
|
|
170
|
+
max_tool_iterations: 30,
|
|
171
|
+
provider: config.goclaw?.default_provider || "ollama cloud",
|
|
172
|
+
model: config.goclaw?.default_model || "deepseek-v4-pro",
|
|
173
|
+
frontmatter: `Expertise summary for ${name}`
|
|
174
|
+
};
|
|
175
|
+
await fs.writeJson(path.join(agentPath, "agent.json"), agentJson, { spaces: 2 });
|
|
176
|
+
} catch (err) {
|
|
177
|
+
// Fallback se não conseguir ler config
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(`✅ Agente "${name}" criado com sucesso em agents/${slug} usando templates!`);
|
|
181
|
+
} else {
|
|
182
|
+
console.warn("⚠️ Nenhuma pasta de templates encontrada. Criando estrutura básica...");
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const config = await getConfig();
|
|
186
|
+
const agentJson = {
|
|
187
|
+
agent_key: slug,
|
|
188
|
+
display_name: name,
|
|
189
|
+
agent_type: "custom",
|
|
190
|
+
provider: config.goclaw?.default_provider || "ollama cloud",
|
|
191
|
+
model: config.goclaw?.default_model || "deepseek-v4-pro",
|
|
192
|
+
other_config: {
|
|
193
|
+
description: `Agent ${name} created by AgentForge`
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
await fs.writeJson(path.join(agentPath, "agent.json"), agentJson, { spaces: 2 });
|
|
197
|
+
} catch (err) {}
|
|
198
|
+
|
|
199
|
+
await fs.writeFile(
|
|
200
|
+
path.join(agentPath, "SOUL.md"),
|
|
201
|
+
`# ${name}\n\nAgente criado pela AgentForge CLI.\n`
|
|
202
|
+
);
|
|
203
|
+
await fs.writeFile(
|
|
204
|
+
path.join(agentPath, "HEARTBEAT.md"),
|
|
205
|
+
`# Instruções de Heartbeat\n`
|
|
206
|
+
);
|
|
207
|
+
console.log(`✅ Agente "${name}" criado com sucesso em agents/${slug}.`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
newCmd
|
|
212
|
+
.command("skill <name>")
|
|
213
|
+
.description("Cria uma nova skill usando o template base")
|
|
214
|
+
.action(async (name: string) => {
|
|
215
|
+
const basePath = getWorkspaceRoot();
|
|
216
|
+
const slug = slugify(name, { lower: true, strict: true });
|
|
217
|
+
|
|
218
|
+
const skillPath = path.join(basePath, "skills", slug);
|
|
219
|
+
|
|
220
|
+
if (await fs.pathExists(skillPath)) {
|
|
221
|
+
console.error(`❌ A skill "${name}" já existe em skills/${slug}.`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await fs.ensureDir(skillPath);
|
|
226
|
+
|
|
227
|
+
const workspaceTemplatePath = path.join(basePath, "templates/default-skill");
|
|
228
|
+
const cliTemplatePath = path.join(__dirname, "../templates/default-skill");
|
|
229
|
+
|
|
230
|
+
let sourceTemplatePath = "";
|
|
231
|
+
if (await fs.pathExists(workspaceTemplatePath)) {
|
|
232
|
+
sourceTemplatePath = workspaceTemplatePath;
|
|
233
|
+
} else if (await fs.pathExists(cliTemplatePath)) {
|
|
234
|
+
sourceTemplatePath = cliTemplatePath;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (sourceTemplatePath !== "") {
|
|
238
|
+
await fs.copy(sourceTemplatePath, skillPath);
|
|
239
|
+
|
|
240
|
+
// Update the {{name}} placeholder in SKILL.md
|
|
241
|
+
const skillMdPath = path.join(skillPath, "SKILL.md");
|
|
242
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
243
|
+
let content = await fs.readFile(skillMdPath, 'utf8');
|
|
244
|
+
content = content.replace(/{{name}}/g, name);
|
|
245
|
+
await fs.writeFile(skillMdPath, content);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log(`✅ Skill "${name}" criada com sucesso em skills/${slug} usando templates!`);
|
|
249
|
+
} else {
|
|
250
|
+
console.warn("⚠️ Nenhum template de skill encontrado. Criando um SKILL.md vazio.");
|
|
251
|
+
await fs.writeFile(
|
|
252
|
+
path.join(skillPath, "SKILL.md"),
|
|
253
|
+
`---\nname: "${name}"\ndescription: "Skill description"\ndeps: []\n---\n\n## Instruções\n`
|
|
254
|
+
);
|
|
255
|
+
console.log(`✅ Skill "${name}" criada com sucesso em skills/${slug}.`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const buildCmd = program
|
|
260
|
+
.command("build")
|
|
261
|
+
.description("Realiza o build (empacotamento) de entidades");
|
|
262
|
+
|
|
263
|
+
buildCmd
|
|
264
|
+
.command("skill <slug>")
|
|
265
|
+
.description("Empacota uma skill em um arquivo .zip na pasta exports/")
|
|
266
|
+
.action(async (slug: string) => {
|
|
267
|
+
const basePath = getWorkspaceRoot();
|
|
268
|
+
const skillPath = path.join(basePath, "skills", slug);
|
|
269
|
+
const exportsPath = path.join(basePath, "exports");
|
|
270
|
+
const zipPath = path.join(exportsPath, `${slug}.zip`);
|
|
271
|
+
|
|
272
|
+
if (!(await fs.pathExists(skillPath))) {
|
|
273
|
+
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
await fs.ensureDir(exportsPath);
|
|
278
|
+
|
|
279
|
+
const zip = new AdmZip();
|
|
280
|
+
zip.addLocalFolder(skillPath, "");
|
|
281
|
+
zip.writeZip(zipPath);
|
|
282
|
+
|
|
283
|
+
console.log(`✅ Build concluído: ${slug}.zip salvo na pasta exports/`);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
async function getConfig() {
|
|
287
|
+
const root = getWorkspaceRoot();
|
|
288
|
+
const configPath = path.join(root, "agentforge.json");
|
|
289
|
+
if (!(await fs.pathExists(configPath))) {
|
|
290
|
+
console.error("❌ Arquivo agentforge.json não encontrado. Execute 'agentforge init' primeiro.");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
const config = await fs.readJson(configPath);
|
|
294
|
+
if (config.goclaw && config.goclaw.api_url) {
|
|
295
|
+
config.goclaw.api_url = config.goclaw.api_url.replace(/\/$/, "");
|
|
296
|
+
}
|
|
297
|
+
return config;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const deployCmd = program
|
|
301
|
+
.command("deploy")
|
|
302
|
+
.description("Faz o deploy de entidades para a plataforma GoClaw");
|
|
303
|
+
|
|
304
|
+
deployCmd
|
|
305
|
+
.command("skill <slug>")
|
|
306
|
+
.description("Faz o build da skill e envia para a API do GoClaw")
|
|
307
|
+
.action(async (slug: string) => {
|
|
308
|
+
const config = await getConfig();
|
|
309
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
310
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json antes de fazer o deploy.");
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const basePath = getWorkspaceRoot();
|
|
315
|
+
const skillPath = path.join(basePath, "skills", slug);
|
|
316
|
+
const exportsPath = path.join(basePath, "exports");
|
|
317
|
+
const zipPath = path.join(exportsPath, `${slug}.zip`);
|
|
318
|
+
|
|
319
|
+
if (!(await fs.pathExists(skillPath))) {
|
|
320
|
+
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await fs.ensureDir(exportsPath);
|
|
325
|
+
const zip = new AdmZip();
|
|
326
|
+
zip.addLocalFolder(skillPath, "");
|
|
327
|
+
zip.writeZip(zipPath);
|
|
328
|
+
|
|
329
|
+
console.log(`✅ Build concluído: ${slug}.zip preparado para envio.`);
|
|
330
|
+
|
|
331
|
+
console.log(`🚀 Fazendo upload para o GoClaw...`);
|
|
332
|
+
const form = new FormData();
|
|
333
|
+
form.append("file", fs.createReadStream(zipPath));
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const url = `${config.goclaw.api_url}/v1/skills/upload`;
|
|
337
|
+
const response = await axios.post(url, form, {
|
|
338
|
+
headers: {
|
|
339
|
+
...form.getHeaders(),
|
|
340
|
+
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
const data = response.data;
|
|
344
|
+
if (data && data.version) {
|
|
345
|
+
console.log(`📌 Skill atualizada para a versão ${data.version}.`);
|
|
346
|
+
}
|
|
347
|
+
} catch (error: any) {
|
|
348
|
+
console.error("❌ Erro durante o deploy:");
|
|
349
|
+
if (error.response) {
|
|
350
|
+
console.error(error.response.data);
|
|
351
|
+
} else {
|
|
352
|
+
console.error(error.message);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
async function deployContextFiles(slug: string, config: any) {
|
|
358
|
+
const basePath = getWorkspaceRoot();
|
|
359
|
+
const agentPath = path.join(basePath, "agents", slug);
|
|
360
|
+
if (!(await fs.pathExists(agentPath))) {
|
|
361
|
+
throw new Error(`Agente não encontrado em agents/${slug}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const files = await fs.readdir(agentPath);
|
|
365
|
+
const contextFiles = files.filter(f => f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.py')).filter(f => f !== 'README.md' && f !== 'agent.json');
|
|
366
|
+
|
|
367
|
+
if (contextFiles.length === 0) {
|
|
368
|
+
console.log(`Nenhum arquivo de contexto encontrado para "${slug}".`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const tempExportDir = path.join(basePath, `temp_export_${slug}`);
|
|
373
|
+
const tempContextDir = path.join(tempExportDir, "context_files");
|
|
374
|
+
const tarPath = path.join(basePath, `temp_export_${slug}.tar.gz`);
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
await fs.ensureDir(tempContextDir);
|
|
378
|
+
for (const file of contextFiles) {
|
|
379
|
+
await fs.copy(path.join(agentPath, file), path.join(tempContextDir, file));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
await tar.c({
|
|
383
|
+
gzip: true,
|
|
384
|
+
file: tarPath,
|
|
385
|
+
cwd: tempExportDir
|
|
386
|
+
}, ["context_files"]);
|
|
387
|
+
|
|
388
|
+
const form = new FormData();
|
|
389
|
+
form.append("file", fs.createReadStream(tarPath));
|
|
390
|
+
|
|
391
|
+
const url = `${config.goclaw.api_url}/v1/agents/${slug}/import?include=context_files`;
|
|
392
|
+
await axios.post(url, form, {
|
|
393
|
+
headers: {
|
|
394
|
+
...form.getHeaders(),
|
|
395
|
+
Authorization: `Bearer ${config.goclaw.token}`,
|
|
396
|
+
"X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
console.log(`✅ Upload cirúrgico de ${contextFiles.length} arquivos concluído com sucesso!`);
|
|
401
|
+
} finally {
|
|
402
|
+
if (await fs.pathExists(tempExportDir)) await fs.remove(tempExportDir);
|
|
403
|
+
if (await fs.pathExists(tarPath)) await fs.remove(tarPath);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
deployCmd
|
|
408
|
+
.command("context <slug>")
|
|
409
|
+
.description("Faz upload dos arquivos de contexto diretamente para o agente usando a API de importação")
|
|
410
|
+
.action(async (slug: string) => {
|
|
411
|
+
const config = await getConfig();
|
|
412
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
413
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
console.log(`🚀 Sincronizando arquivos de contexto do agente "${slug}"...`);
|
|
418
|
+
try {
|
|
419
|
+
await deployContextFiles(slug, config);
|
|
420
|
+
console.log("✅ Deploy de contexto concluído!");
|
|
421
|
+
} catch (error: any) {
|
|
422
|
+
console.error("❌ Erro ao enviar contexto:", error.response?.data || error.message);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
deployCmd
|
|
427
|
+
.command("agent <slug>")
|
|
428
|
+
.description("Faz deploy completo do agente (configuração + arquivos de contexto)")
|
|
429
|
+
.action(async (slug: string) => {
|
|
430
|
+
const config = await getConfig();
|
|
431
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
432
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const basePath = getWorkspaceRoot();
|
|
437
|
+
const agentPath = path.join(basePath, "agents", slug);
|
|
438
|
+
const agentJsonPath = path.join(agentPath, "agent.json");
|
|
439
|
+
|
|
440
|
+
if (!(await fs.pathExists(agentJsonPath))) {
|
|
441
|
+
console.error(`❌ agent.json não encontrado em agents/${slug}.`);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const agentConfig = await fs.readJson(agentJsonPath);
|
|
446
|
+
console.log(`🚀 Atualizando configuração do agente "${slug}"...`);
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
let exists = true;
|
|
450
|
+
try {
|
|
451
|
+
await axios.get(`${config.goclaw.api_url}/v1/agents/${slug}`, {
|
|
452
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
453
|
+
});
|
|
454
|
+
} catch (e: any) {
|
|
455
|
+
if (e.response && e.response.status === 404) {
|
|
456
|
+
exists = false;
|
|
457
|
+
} else {
|
|
458
|
+
throw e;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!exists) {
|
|
463
|
+
await axios.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
|
|
464
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
465
|
+
});
|
|
466
|
+
console.log("✅ Agente criado com sucesso.");
|
|
467
|
+
} else {
|
|
468
|
+
await axios.put(`${config.goclaw.api_url}/v1/agents/${slug}`, agentConfig, {
|
|
469
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
470
|
+
});
|
|
471
|
+
console.log("✅ Configurações do agente atualizadas.");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
console.log(`🚀 Sincronizando arquivos de contexto...`);
|
|
475
|
+
await deployContextFiles(slug, config);
|
|
476
|
+
|
|
477
|
+
console.log("✅ Deploy completo concluído!");
|
|
478
|
+
} catch (error: any) {
|
|
479
|
+
console.error(`❌ Erro no deploy da configuração:`, error.response?.data || error.message);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const pullCmd = program
|
|
484
|
+
.command("pull")
|
|
485
|
+
.description("Sincroniza entidades do GoClaw para o workspace local");
|
|
486
|
+
|
|
487
|
+
pullCmd
|
|
488
|
+
.command("skills")
|
|
489
|
+
.description("Faz download do arquivo tar.gz de skills do GoClaw e extrai localmente")
|
|
490
|
+
.action(async () => {
|
|
491
|
+
const config = await getConfig();
|
|
492
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
493
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json antes de fazer o pull.");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (!(await confirmOverwrite("skills"))) {
|
|
498
|
+
console.log("❌ Pull cancelado pelo utilizador.");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
console.log("📥 Baixando skills do GoClaw...");
|
|
503
|
+
try {
|
|
504
|
+
const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
|
|
505
|
+
const response = await axios.get(url, {
|
|
506
|
+
headers: {
|
|
507
|
+
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
508
|
+
},
|
|
509
|
+
responseType: "stream"
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const tempTarPath = path.join(getWorkspaceRoot(), "temp_skills.tar.gz");
|
|
513
|
+
const writer = fs.createWriteStream(tempTarPath);
|
|
514
|
+
response.data.pipe(writer);
|
|
515
|
+
|
|
516
|
+
await new Promise((resolve, reject) => {
|
|
517
|
+
writer.on("finish", resolve);
|
|
518
|
+
writer.on("error", reject);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
console.log("📦 Extraindo skills para a pasta local...");
|
|
522
|
+
await tar.x({
|
|
523
|
+
file: tempTarPath,
|
|
524
|
+
cwd: getWorkspaceRoot()
|
|
525
|
+
});
|
|
526
|
+
await fs.remove(tempTarPath);
|
|
527
|
+
|
|
528
|
+
console.log("📥 Baixando ficheiros de código das skills...");
|
|
529
|
+
const skillsListRes = await axios.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
530
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const skills = skillsListRes.data.skills || [];
|
|
534
|
+
for (const skill of skills) {
|
|
535
|
+
try {
|
|
536
|
+
const isSystem = skill.is_system === true;
|
|
537
|
+
const targetFolder = isSystem ? path.join("system", skill.slug) : skill.slug;
|
|
538
|
+
|
|
539
|
+
if (isSystem) {
|
|
540
|
+
const originalPath = path.join(getWorkspaceRoot(), "skills", skill.slug);
|
|
541
|
+
const newPath = path.join(getWorkspaceRoot(), "skills", targetFolder);
|
|
542
|
+
if (await fs.pathExists(originalPath)) {
|
|
543
|
+
await fs.ensureDir(path.dirname(newPath));
|
|
544
|
+
await fs.move(originalPath, newPath, { overwrite: true });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const filesRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
549
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const files = filesRes.data.files || [];
|
|
553
|
+
for (const file of files) {
|
|
554
|
+
if (file.isDir) continue;
|
|
555
|
+
const fileContentRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
556
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
557
|
+
});
|
|
558
|
+
const filePath = path.join(getWorkspaceRoot(), "skills", targetFolder, file.path);
|
|
559
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
560
|
+
await fs.writeFile(filePath, fileContentRes.data.content || "");
|
|
561
|
+
}
|
|
562
|
+
} catch (fileErr: any) {
|
|
563
|
+
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
console.log("✅ Pull concluído com sucesso! As skills foram atualizadas localmente.");
|
|
568
|
+
} catch (error: any) {
|
|
569
|
+
console.error("❌ Erro durante o pull das skills:");
|
|
570
|
+
if (error.response) {
|
|
571
|
+
console.error(`Status HTTP ${error.response.status}`);
|
|
572
|
+
} else {
|
|
573
|
+
console.error(error.message);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
pullCmd
|
|
579
|
+
.command("agents")
|
|
580
|
+
.description("Faz download cirúrgico dos agentes (configuração e contexto)")
|
|
581
|
+
.action(async () => {
|
|
582
|
+
const config = await getConfig();
|
|
583
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
584
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json antes de fazer o pull.");
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!(await confirmOverwrite("agentes"))) {
|
|
589
|
+
console.log("❌ Pull cancelado pelo utilizador.");
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
console.log("📥 Buscando lista de agentes do GoClaw...");
|
|
594
|
+
try {
|
|
595
|
+
const listResponse = await axios.get(`${config.goclaw.api_url}/v1/agents`, {
|
|
596
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
const agents = listResponse.data.agents || [];
|
|
600
|
+
console.log(`Encontrados ${agents.length} agentes. Sincronizando...`);
|
|
601
|
+
|
|
602
|
+
for (const agent of agents) {
|
|
603
|
+
const slug = agent.agent_key;
|
|
604
|
+
console.log(`📦 Baixando agente: ${slug}...`);
|
|
605
|
+
|
|
606
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
|
|
607
|
+
const response = await axios.get(url, {
|
|
608
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
|
|
609
|
+
responseType: "stream"
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const tempTarPath = path.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
const writer = fs.createWriteStream(tempTarPath);
|
|
616
|
+
response.data.pipe(writer);
|
|
617
|
+
|
|
618
|
+
await new Promise((resolve, reject) => {
|
|
619
|
+
writer.on("finish", resolve);
|
|
620
|
+
writer.on("error", reject);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
const agentPath = path.join(getWorkspaceRoot(), "agents", slug);
|
|
624
|
+
await fs.ensureDir(agentPath);
|
|
625
|
+
|
|
626
|
+
await tar.x({
|
|
627
|
+
file: tempTarPath,
|
|
628
|
+
cwd: agentPath,
|
|
629
|
+
strip: 0,
|
|
630
|
+
filter: (path) => {
|
|
631
|
+
return path === 'agent.json' || path.startsWith('context_files/');
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
const contextDir = path.join(agentPath, "context_files");
|
|
636
|
+
if (await fs.pathExists(contextDir)) {
|
|
637
|
+
const contextFiles = await fs.readdir(contextDir);
|
|
638
|
+
for (const f of contextFiles) {
|
|
639
|
+
await fs.move(path.join(contextDir, f), path.join(agentPath, f), { overwrite: true });
|
|
640
|
+
}
|
|
641
|
+
await fs.remove(contextDir);
|
|
642
|
+
}
|
|
643
|
+
} finally {
|
|
644
|
+
if (await fs.pathExists(tempTarPath)) {
|
|
645
|
+
await fs.remove(tempTarPath);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
console.log("✅ Pull de agentes concluído com sucesso!");
|
|
650
|
+
} catch (error: any) {
|
|
651
|
+
if (error.response && error.response.status) {
|
|
652
|
+
console.error(`❌ Erro durante o pull dos agentes: HTTP ${error.response.status} - Verifique suas credenciais e permissões no agentforge.json (username deve ser o dono do agente).`);
|
|
653
|
+
} else {
|
|
654
|
+
console.error("❌ Erro durante o pull dos agentes:", error.message);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
pullCmd
|
|
661
|
+
.command('all')
|
|
662
|
+
.description('Faz download cirúrgico de todos os agentes e skills do GoClaw para a pasta local')
|
|
663
|
+
.action(async () => {
|
|
664
|
+
const config = await getConfig();
|
|
665
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
666
|
+
console.error('❌ Configure sua chave de API (token) no agentforge.json antes de fazer o pull.');
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (!(await confirmOverwrite('TUDO (agentes e skills)'))) {
|
|
671
|
+
console.log('❌ Pull cancelado pelo utilizador.');
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
console.log('🔄 Iniciando sincronização completa (pull all)...');
|
|
676
|
+
|
|
677
|
+
// PULL SKILLS INLINE
|
|
678
|
+
console.log('\n--- [1/2] SKILLS ---');
|
|
679
|
+
try {
|
|
680
|
+
const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
|
|
681
|
+
const response = await axios.get(url, {
|
|
682
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
683
|
+
responseType: 'stream'
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
const tempTarPath = path.join(getWorkspaceRoot(), 'temp_skills.tar.gz');
|
|
687
|
+
const writer = fs.createWriteStream(tempTarPath);
|
|
688
|
+
response.data.pipe(writer);
|
|
689
|
+
|
|
690
|
+
await new Promise((resolve, reject) => {
|
|
691
|
+
writer.on('finish', resolve);
|
|
692
|
+
writer.on('error', reject);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
await tar.x({ file: tempTarPath, cwd: getWorkspaceRoot() });
|
|
696
|
+
await fs.remove(tempTarPath);
|
|
697
|
+
|
|
698
|
+
const skillsListRes = await axios.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
699
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
const skills = skillsListRes.data.skills || [];
|
|
703
|
+
for (const skill of skills) {
|
|
704
|
+
try {
|
|
705
|
+
const isSystem = skill.is_system === true;
|
|
706
|
+
const targetFolder = isSystem ? path.join('system', skill.slug) : skill.slug;
|
|
707
|
+
|
|
708
|
+
if (isSystem) {
|
|
709
|
+
const originalPath = path.join(getWorkspaceRoot(), 'skills', skill.slug);
|
|
710
|
+
const newPath = path.join(getWorkspaceRoot(), 'skills', targetFolder);
|
|
711
|
+
if (await fs.pathExists(originalPath)) {
|
|
712
|
+
await fs.ensureDir(path.dirname(newPath));
|
|
713
|
+
await fs.move(originalPath, newPath, { overwrite: true });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const filesRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
718
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
const files = filesRes.data.files || [];
|
|
722
|
+
for (const file of files) {
|
|
723
|
+
if (file.isDir) continue;
|
|
724
|
+
const fileContentRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
725
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
726
|
+
});
|
|
727
|
+
const filePath = path.join(getWorkspaceRoot(), 'skills', targetFolder, file.path);
|
|
728
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
729
|
+
await fs.writeFile(filePath, fileContentRes.data.content || '');
|
|
730
|
+
}
|
|
731
|
+
} catch (fileErr: any) {
|
|
732
|
+
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
console.log('✅ Pull de skills concluído!');
|
|
736
|
+
} catch (error: any) {
|
|
737
|
+
console.error('❌ Erro durante o pull das skills:', error.message);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// PULL AGENTS INLINE
|
|
741
|
+
console.log('\n--- [2/2] AGENTS ---');
|
|
742
|
+
try {
|
|
743
|
+
const listResponse = await axios.get(`${config.goclaw.api_url}/v1/agents`, {
|
|
744
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
const agents = listResponse.data.agents || [];
|
|
748
|
+
console.log(`Encontrados ${agents.length} agentes. Sincronizando...`);
|
|
749
|
+
|
|
750
|
+
for (const agent of agents) {
|
|
751
|
+
const slug = agent.agent_key;
|
|
752
|
+
console.log(`📦 Baixando agente: ${slug}...`);
|
|
753
|
+
|
|
754
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
|
|
755
|
+
const response = await axios.get(url, {
|
|
756
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
757
|
+
responseType: 'stream'
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
const tempTarPath = path.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
761
|
+
|
|
762
|
+
try {
|
|
763
|
+
const writer = fs.createWriteStream(tempTarPath);
|
|
764
|
+
response.data.pipe(writer);
|
|
765
|
+
|
|
766
|
+
await new Promise((resolve, reject) => {
|
|
767
|
+
writer.on('finish', resolve);
|
|
768
|
+
writer.on('error', reject);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
const agentPath = path.join(getWorkspaceRoot(), 'agents', slug);
|
|
772
|
+
await fs.ensureDir(agentPath);
|
|
773
|
+
|
|
774
|
+
await tar.x({
|
|
775
|
+
file: tempTarPath,
|
|
776
|
+
cwd: agentPath,
|
|
777
|
+
strip: 0,
|
|
778
|
+
filter: (path) => {
|
|
779
|
+
return path === 'agent.json' || path.startsWith('context_files/');
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
const contextDir = path.join(agentPath, 'context_files');
|
|
784
|
+
if (await fs.pathExists(contextDir)) {
|
|
785
|
+
const contextFiles = await fs.readdir(contextDir);
|
|
786
|
+
for (const f of contextFiles) {
|
|
787
|
+
await fs.move(path.join(contextDir, f), path.join(agentPath, f), { overwrite: true });
|
|
788
|
+
}
|
|
789
|
+
await fs.remove(contextDir);
|
|
790
|
+
}
|
|
791
|
+
} finally {
|
|
792
|
+
if (await fs.pathExists(tempTarPath)) {
|
|
793
|
+
await fs.remove(tempTarPath);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
console.log('✅ Pull de agentes concluído!');
|
|
798
|
+
} catch (error: any) {
|
|
799
|
+
console.error('❌ Erro durante o pull dos agentes:', error.message);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
console.log('\n🚀 SYNC COMPLETO! Workspace atualizado.');
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
program.parse();
|