@innominatum/agentforge-cli 1.0.9 → 1.1.9
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/dist/index.js +198 -44
- package/package.json +1 -1
- package/src/index.ts +240 -70
- package/templates/CLI_MANUAL.md +32 -6
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ function confirmOverwrite(entityType) {
|
|
|
52
52
|
output: process.stdout
|
|
53
53
|
});
|
|
54
54
|
return new Promise(resolve => {
|
|
55
|
-
rl.question(`⚠️ Atenção: O pull irá
|
|
55
|
+
rl.question(`⚠️ Atenção: O pull irá APAGAR as suas ${entityType} locais e substituí-las pelo estado do servidor. Quaisquer alterações ou entidades não publicadas serão PERDIDAS. Deseja continuar? (s/N) `, answer => {
|
|
56
56
|
rl.close();
|
|
57
57
|
const isYes = answer.toLowerCase() === 's' || answer.toLowerCase() === 'sim' || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
58
58
|
resolve(isYes);
|
|
@@ -301,29 +301,20 @@ async function resolveAgentId(slug, config) {
|
|
|
301
301
|
const deployCmd = program
|
|
302
302
|
.command("deploy")
|
|
303
303
|
.description("Faz o deploy de entidades para a plataforma GoClaw");
|
|
304
|
-
|
|
305
|
-
.command("skill <slug>")
|
|
306
|
-
.description("Faz o build da skill e envia para a API do GoClaw")
|
|
307
|
-
.action(async (slug) => {
|
|
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
|
-
const basePath = getWorkspaceRoot();
|
|
304
|
+
async function deploySkill(slug, config, basePath) {
|
|
314
305
|
const skillPath = path_1.default.join(basePath, "skills", slug);
|
|
315
306
|
const exportsPath = path_1.default.join(basePath, "exports");
|
|
316
|
-
const
|
|
307
|
+
const safeSlug = slug.replace(/[\\\/]/g, '_');
|
|
308
|
+
const zipPath = path_1.default.join(exportsPath, `${safeSlug}.zip`);
|
|
317
309
|
if (!(await fs_extra_1.default.pathExists(skillPath))) {
|
|
318
310
|
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
319
|
-
|
|
311
|
+
return;
|
|
320
312
|
}
|
|
321
313
|
await fs_extra_1.default.ensureDir(exportsPath);
|
|
322
314
|
const zip = new adm_zip_1.default();
|
|
323
315
|
zip.addLocalFolder(skillPath, "");
|
|
324
316
|
zip.writeZip(zipPath);
|
|
325
|
-
console.log(
|
|
326
|
-
console.log(`🚀 Fazendo upload para o GoClaw...`);
|
|
317
|
+
console.log(`🚀 Fazendo upload da skill "${slug}" para o GoClaw...`);
|
|
327
318
|
const form = new form_data_1.default();
|
|
328
319
|
form.append("file", fs_extra_1.default.createReadStream(zipPath));
|
|
329
320
|
try {
|
|
@@ -336,11 +327,58 @@ deployCmd
|
|
|
336
327
|
});
|
|
337
328
|
const data = response.data;
|
|
338
329
|
if (data && data.version) {
|
|
339
|
-
console.log(
|
|
330
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados (versão ${data.version}).`);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados.`);
|
|
334
|
+
}
|
|
335
|
+
// Sincronizar metadados (visibility, description, tags, etc)
|
|
336
|
+
const skillsListRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
337
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
338
|
+
});
|
|
339
|
+
const remoteSkill = skillsListRes.data.skills?.find((s) => s.slug === slug);
|
|
340
|
+
if (remoteSkill) {
|
|
341
|
+
const metadataPath = path_1.default.join(skillPath, "metadata.json");
|
|
342
|
+
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
343
|
+
console.log(`🚀 Sincronizando metadados da skill "${slug}"...`);
|
|
344
|
+
const metadata = await fs_extra_1.default.readJson(metadataPath);
|
|
345
|
+
// Remover campos que não devem ser enviados no PUT
|
|
346
|
+
const payload = { ...metadata };
|
|
347
|
+
delete payload.id;
|
|
348
|
+
delete payload.slug;
|
|
349
|
+
delete payload.name;
|
|
350
|
+
await axios_1.default.put(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}`, payload, {
|
|
351
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
352
|
+
});
|
|
353
|
+
console.log(`✅ Metadados sincronizados com sucesso.`);
|
|
354
|
+
}
|
|
355
|
+
// Sincronizar permissões (grants)
|
|
356
|
+
const grantsPath = path_1.default.join(skillPath, "grants.jsonl");
|
|
357
|
+
if (await fs_extra_1.default.pathExists(grantsPath)) {
|
|
358
|
+
console.log(`🚀 Sincronizando permissões (grants) da skill "${slug}"...`);
|
|
359
|
+
const grantsContent = await fs_extra_1.default.readFile(grantsPath, 'utf8');
|
|
360
|
+
const lines = grantsContent.split('\n').filter(l => l.trim());
|
|
361
|
+
for (const line of lines) {
|
|
362
|
+
try {
|
|
363
|
+
const grant = JSON.parse(line);
|
|
364
|
+
if (grant.agent_key) {
|
|
365
|
+
const agentId = await resolveAgentId(grant.agent_key, config);
|
|
366
|
+
if (agentId) {
|
|
367
|
+
await axios_1.default.post(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}/grants/agent`, { agent_id: agentId, version: grant.pinned_version || null }, { headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" } });
|
|
368
|
+
console.log(` ➕ Permissão concedida ao agente: ${grant.agent_key}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
console.warn(` ⚠️ Falha ao conceder permissão: ${e.response?.data?.error || e.message}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
console.log(`✅ Permissões sincronizadas.`);
|
|
377
|
+
}
|
|
340
378
|
}
|
|
341
379
|
}
|
|
342
380
|
catch (error) {
|
|
343
|
-
console.error(
|
|
381
|
+
console.error(`❌ Erro no deploy da skill "${slug}":`);
|
|
344
382
|
if (error.response) {
|
|
345
383
|
console.error(error.response.data);
|
|
346
384
|
}
|
|
@@ -348,6 +386,18 @@ deployCmd
|
|
|
348
386
|
console.error(error.message);
|
|
349
387
|
}
|
|
350
388
|
}
|
|
389
|
+
}
|
|
390
|
+
deployCmd
|
|
391
|
+
.command("skill <slug>")
|
|
392
|
+
.description("Faz build e upload automático de uma skill para o GoClaw")
|
|
393
|
+
.action(async (slug) => {
|
|
394
|
+
const config = await getConfig();
|
|
395
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
396
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json antes de fazer o deploy.");
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
const basePath = getWorkspaceRoot();
|
|
400
|
+
await deploySkill(slug, config, basePath);
|
|
351
401
|
});
|
|
352
402
|
async function deployContextFiles(slug, config, resolvedId) {
|
|
353
403
|
const agentId = resolvedId || (await resolveAgentId(slug, config)) || slug;
|
|
@@ -420,6 +470,38 @@ async function deployContextFiles(slug, config, resolvedId) {
|
|
|
420
470
|
await fs_extra_1.default.remove(tarPath);
|
|
421
471
|
}
|
|
422
472
|
}
|
|
473
|
+
async function deployAgent(slug, config) {
|
|
474
|
+
const basePath = getWorkspaceRoot();
|
|
475
|
+
const agentPath = path_1.default.join(basePath, "agents", slug);
|
|
476
|
+
const agentJsonPath = path_1.default.join(agentPath, "agent.json");
|
|
477
|
+
if (!(await fs_extra_1.default.pathExists(agentJsonPath))) {
|
|
478
|
+
console.error(`❌ agent.json não encontrado em agents/${slug}.`);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const agentConfig = await fs_extra_1.default.readJson(agentJsonPath);
|
|
482
|
+
console.log(`🚀 Sincronizando agente "${slug}"...`);
|
|
483
|
+
try {
|
|
484
|
+
const agentId = await resolveAgentId(slug, config);
|
|
485
|
+
const exists = agentId !== null;
|
|
486
|
+
if (!exists) {
|
|
487
|
+
await axios_1.default.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
|
|
488
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
489
|
+
});
|
|
490
|
+
console.log(`✅ Agente "${slug}" criado.`);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
await axios_1.default.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
|
|
494
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
495
|
+
});
|
|
496
|
+
console.log(`✅ Configuração de "${slug}" atualizada.`);
|
|
497
|
+
}
|
|
498
|
+
await deployContextFiles(slug, config, agentId);
|
|
499
|
+
console.log(`✅ Agente "${slug}" sincronizado com sucesso!`);
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
console.error(`❌ Erro no deploy de "${slug}":`, error.response?.data || error.message);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
423
505
|
deployCmd
|
|
424
506
|
.command("context <slug>")
|
|
425
507
|
.description("Faz upload dos arquivos de contexto diretamente para o agente usando a API de importação")
|
|
@@ -447,37 +529,83 @@ deployCmd
|
|
|
447
529
|
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
448
530
|
process.exit(1);
|
|
449
531
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (!exists) {
|
|
463
|
-
await axios_1.default.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.");
|
|
532
|
+
await deployAgent(slug, config);
|
|
533
|
+
});
|
|
534
|
+
async function deployAllAgents(config, basePath) {
|
|
535
|
+
const agentsDir = path_1.default.join(basePath, "agents");
|
|
536
|
+
if (await fs_extra_1.default.pathExists(agentsDir)) {
|
|
537
|
+
const agents = await fs_extra_1.default.readdir(agentsDir);
|
|
538
|
+
console.log(`🚀 Iniciando deploy em lote de ${agents.length} agentes...`);
|
|
539
|
+
for (const slug of agents) {
|
|
540
|
+
const agentPath = path_1.default.join(agentsDir, slug);
|
|
541
|
+
if ((await fs_extra_1.default.stat(agentPath)).isDirectory()) {
|
|
542
|
+
await deployAgent(slug, config);
|
|
543
|
+
}
|
|
467
544
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
console.log("Nenhum agente encontrado em agents/.");
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async function deployAllSkills(config, basePath) {
|
|
551
|
+
const skillsDir = path_1.default.join(basePath, "skills");
|
|
552
|
+
if (await fs_extra_1.default.pathExists(skillsDir)) {
|
|
553
|
+
const skills = await fs_extra_1.default.readdir(skillsDir);
|
|
554
|
+
console.log(`🚀 Iniciando deploy em lote de skills...`);
|
|
555
|
+
for (const item of skills) {
|
|
556
|
+
const itemPath = path_1.default.join(skillsDir, item);
|
|
557
|
+
if ((await fs_extra_1.default.stat(itemPath)).isDirectory()) {
|
|
558
|
+
if (item === "system") {
|
|
559
|
+
console.log("⏩ Ignorando pasta 'system/' (skills nativas do GoClaw são apenas de leitura)");
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
await deploySkill(item, config, basePath);
|
|
563
|
+
}
|
|
473
564
|
}
|
|
474
|
-
console.log(`🚀 Sincronizando arquivos de contexto...`);
|
|
475
|
-
await deployContextFiles(slug, config, agentId);
|
|
476
|
-
console.log("✅ Deploy completo concluído!");
|
|
477
565
|
}
|
|
478
|
-
|
|
479
|
-
console.
|
|
566
|
+
else {
|
|
567
|
+
console.log("Nenhuma skill encontrada em skills/.");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
deployCmd
|
|
571
|
+
.command("agents")
|
|
572
|
+
.description("Faz deploy de todos os agentes do workspace")
|
|
573
|
+
.action(async () => {
|
|
574
|
+
const config = await getConfig();
|
|
575
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
576
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
const basePath = getWorkspaceRoot();
|
|
580
|
+
await deployAllAgents(config, basePath);
|
|
581
|
+
console.log("🏁 Deploy de agentes concluído!");
|
|
582
|
+
});
|
|
583
|
+
deployCmd
|
|
584
|
+
.command("skills")
|
|
585
|
+
.description("Faz deploy de todas as skills do workspace")
|
|
586
|
+
.action(async () => {
|
|
587
|
+
const config = await getConfig();
|
|
588
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
589
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
const basePath = getWorkspaceRoot();
|
|
593
|
+
await deployAllSkills(config, basePath);
|
|
594
|
+
console.log("🏁 Deploy de skills concluído!");
|
|
595
|
+
});
|
|
596
|
+
deployCmd
|
|
597
|
+
.command("all")
|
|
598
|
+
.description("Faz deploy de todos os agentes e skills do workspace")
|
|
599
|
+
.action(async () => {
|
|
600
|
+
const config = await getConfig();
|
|
601
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
602
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
603
|
+
process.exit(1);
|
|
480
604
|
}
|
|
605
|
+
const basePath = getWorkspaceRoot();
|
|
606
|
+
await deployAllAgents(config, basePath);
|
|
607
|
+
await deployAllSkills(config, basePath);
|
|
608
|
+
console.log("🏁 Deploy completo (agentes e skills) concluído!");
|
|
481
609
|
});
|
|
482
610
|
const pullCmd = program
|
|
483
611
|
.command("pull")
|
|
@@ -495,6 +623,8 @@ pullCmd
|
|
|
495
623
|
console.log("❌ Pull cancelado pelo utilizador.");
|
|
496
624
|
return;
|
|
497
625
|
}
|
|
626
|
+
console.log("🧹 Limpando a pasta local de skills...");
|
|
627
|
+
await fs_extra_1.default.emptyDir(path_1.default.join(getWorkspaceRoot(), "skills"));
|
|
498
628
|
console.log("📥 Baixando skills do GoClaw...");
|
|
499
629
|
try {
|
|
500
630
|
const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
|
|
@@ -553,6 +683,28 @@ pullCmd
|
|
|
553
683
|
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
554
684
|
}
|
|
555
685
|
}
|
|
686
|
+
// Remover quaisquer skills fantasmas que o tarball tenha extraído (skills apagadas mas ainda no export)
|
|
687
|
+
const validSlugs = new Set(skills.map((s) => s.is_system === true ? path_1.default.join("system", s.slug) : s.slug));
|
|
688
|
+
const skillsDir = path_1.default.join(getWorkspaceRoot(), "skills");
|
|
689
|
+
if (await fs_extra_1.default.pathExists(skillsDir)) {
|
|
690
|
+
const localItems = await fs_extra_1.default.readdir(skillsDir);
|
|
691
|
+
for (const item of localItems) {
|
|
692
|
+
if (item === "system") {
|
|
693
|
+
const systemDir = path_1.default.join(skillsDir, "system");
|
|
694
|
+
if (await fs_extra_1.default.pathExists(systemDir)) {
|
|
695
|
+
const systemItems = await fs_extra_1.default.readdir(systemDir);
|
|
696
|
+
for (const sysItem of systemItems) {
|
|
697
|
+
if (!validSlugs.has(path_1.default.join("system", sysItem))) {
|
|
698
|
+
await fs_extra_1.default.remove(path_1.default.join(systemDir, sysItem));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
else if (!validSlugs.has(item)) {
|
|
704
|
+
await fs_extra_1.default.remove(path_1.default.join(skillsDir, item));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
556
708
|
console.log("✅ Pull concluído com sucesso! As skills foram atualizadas localmente.");
|
|
557
709
|
}
|
|
558
710
|
catch (error) {
|
|
@@ -578,6 +730,8 @@ pullCmd
|
|
|
578
730
|
console.log("❌ Pull cancelado pelo utilizador.");
|
|
579
731
|
return;
|
|
580
732
|
}
|
|
733
|
+
console.log("🧹 Limpando a pasta local de agentes...");
|
|
734
|
+
await fs_extra_1.default.emptyDir(path_1.default.join(getWorkspaceRoot(), "agents"));
|
|
581
735
|
console.log("📥 Buscando lista de agentes do GoClaw...");
|
|
582
736
|
try {
|
|
583
737
|
const listResponse = await axios_1.default.get(`${config.goclaw.api_url}/v1/agents`, {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ function confirmOverwrite(entityType: string): Promise<boolean> {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
return new Promise(resolve => {
|
|
20
|
-
rl.question(`⚠️ Atenção: O pull irá
|
|
20
|
+
rl.question(`⚠️ Atenção: O pull irá APAGAR as suas ${entityType} locais e substituí-las pelo estado do servidor. Quaisquer alterações ou entidades não publicadas serão PERDIDAS. Deseja continuar? (s/N) `, answer => {
|
|
21
21
|
rl.close();
|
|
22
22
|
const isYes = answer.toLowerCase() === 's' || answer.toLowerCase() === 'sim' || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
23
23
|
resolve(isYes);
|
|
@@ -314,9 +314,106 @@ const deployCmd = program
|
|
|
314
314
|
.command("deploy")
|
|
315
315
|
.description("Faz o deploy de entidades para a plataforma GoClaw");
|
|
316
316
|
|
|
317
|
+
async function deploySkill(slug: string, config: any, basePath: string) {
|
|
318
|
+
const skillPath = path.join(basePath, "skills", slug);
|
|
319
|
+
const exportsPath = path.join(basePath, "exports");
|
|
320
|
+
const safeSlug = slug.replace(/[\\\/]/g, '_');
|
|
321
|
+
const zipPath = path.join(exportsPath, `${safeSlug}.zip`);
|
|
322
|
+
|
|
323
|
+
if (!(await fs.pathExists(skillPath))) {
|
|
324
|
+
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await fs.ensureDir(exportsPath);
|
|
329
|
+
const zip = new AdmZip();
|
|
330
|
+
zip.addLocalFolder(skillPath, "");
|
|
331
|
+
zip.writeZip(zipPath);
|
|
332
|
+
|
|
333
|
+
console.log(`🚀 Fazendo upload da skill "${slug}" para o GoClaw...`);
|
|
334
|
+
const form = new FormData();
|
|
335
|
+
form.append("file", fs.createReadStream(zipPath));
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const url = `${config.goclaw.api_url}/v1/skills/upload`;
|
|
339
|
+
const response = await axios.post(url, form, {
|
|
340
|
+
headers: {
|
|
341
|
+
...form.getHeaders(),
|
|
342
|
+
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
const data = response.data;
|
|
346
|
+
if (data && data.version) {
|
|
347
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados (versão ${data.version}).`);
|
|
348
|
+
} else {
|
|
349
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados.`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Sincronizar metadados (visibility, description, tags, etc)
|
|
353
|
+
const skillsListRes = await axios.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
354
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
355
|
+
});
|
|
356
|
+
const remoteSkill = skillsListRes.data.skills?.find((s: any) => s.slug === slug);
|
|
357
|
+
|
|
358
|
+
if (remoteSkill) {
|
|
359
|
+
const metadataPath = path.join(skillPath, "metadata.json");
|
|
360
|
+
if (await fs.pathExists(metadataPath)) {
|
|
361
|
+
console.log(`🚀 Sincronizando metadados da skill "${slug}"...`);
|
|
362
|
+
const metadata = await fs.readJson(metadataPath);
|
|
363
|
+
|
|
364
|
+
// Remover campos que não devem ser enviados no PUT
|
|
365
|
+
const payload = { ...metadata };
|
|
366
|
+
delete payload.id;
|
|
367
|
+
delete payload.slug;
|
|
368
|
+
delete payload.name;
|
|
369
|
+
|
|
370
|
+
await axios.put(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}`, payload, {
|
|
371
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
372
|
+
});
|
|
373
|
+
console.log(`✅ Metadados sincronizados com sucesso.`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Sincronizar permissões (grants)
|
|
377
|
+
const grantsPath = path.join(skillPath, "grants.jsonl");
|
|
378
|
+
if (await fs.pathExists(grantsPath)) {
|
|
379
|
+
console.log(`🚀 Sincronizando permissões (grants) da skill "${slug}"...`);
|
|
380
|
+
const grantsContent = await fs.readFile(grantsPath, 'utf8');
|
|
381
|
+
const lines = grantsContent.split('\n').filter(l => l.trim());
|
|
382
|
+
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
try {
|
|
385
|
+
const grant = JSON.parse(line);
|
|
386
|
+
if (grant.agent_key) {
|
|
387
|
+
const agentId = await resolveAgentId(grant.agent_key, config);
|
|
388
|
+
if (agentId) {
|
|
389
|
+
await axios.post(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}/grants/agent`,
|
|
390
|
+
{ agent_id: agentId, version: grant.pinned_version || null },
|
|
391
|
+
{ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" } }
|
|
392
|
+
);
|
|
393
|
+
console.log(` ➕ Permissão concedida ao agente: ${grant.agent_key}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch (e: any) {
|
|
397
|
+
console.warn(` ⚠️ Falha ao conceder permissão: ${e.response?.data?.error || e.message}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
console.log(`✅ Permissões sincronizadas.`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
} catch (error: any) {
|
|
405
|
+
console.error(`❌ Erro no deploy da skill "${slug}":`);
|
|
406
|
+
if (error.response) {
|
|
407
|
+
console.error(error.response.data);
|
|
408
|
+
} else {
|
|
409
|
+
console.error(error.message);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
317
414
|
deployCmd
|
|
318
415
|
.command("skill <slug>")
|
|
319
|
-
.description("Faz
|
|
416
|
+
.description("Faz build e upload automático de uma skill para o GoClaw")
|
|
320
417
|
.action(async (slug: string) => {
|
|
321
418
|
const config = await getConfig();
|
|
322
419
|
if (!config.goclaw || !config.goclaw.token) {
|
|
@@ -325,46 +422,7 @@ deployCmd
|
|
|
325
422
|
}
|
|
326
423
|
|
|
327
424
|
const basePath = getWorkspaceRoot();
|
|
328
|
-
|
|
329
|
-
const exportsPath = path.join(basePath, "exports");
|
|
330
|
-
const zipPath = path.join(exportsPath, `${slug}.zip`);
|
|
331
|
-
|
|
332
|
-
if (!(await fs.pathExists(skillPath))) {
|
|
333
|
-
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
334
|
-
process.exit(1);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
await fs.ensureDir(exportsPath);
|
|
338
|
-
const zip = new AdmZip();
|
|
339
|
-
zip.addLocalFolder(skillPath, "");
|
|
340
|
-
zip.writeZip(zipPath);
|
|
341
|
-
|
|
342
|
-
console.log(`✅ Build concluído: ${slug}.zip preparado para envio.`);
|
|
343
|
-
|
|
344
|
-
console.log(`🚀 Fazendo upload para o GoClaw...`);
|
|
345
|
-
const form = new FormData();
|
|
346
|
-
form.append("file", fs.createReadStream(zipPath));
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
const url = `${config.goclaw.api_url}/v1/skills/upload`;
|
|
350
|
-
const response = await axios.post(url, form, {
|
|
351
|
-
headers: {
|
|
352
|
-
...form.getHeaders(),
|
|
353
|
-
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
const data = response.data;
|
|
357
|
-
if (data && data.version) {
|
|
358
|
-
console.log(`📌 Skill atualizada para a versão ${data.version}.`);
|
|
359
|
-
}
|
|
360
|
-
} catch (error: any) {
|
|
361
|
-
console.error("❌ Erro durante o deploy:");
|
|
362
|
-
if (error.response) {
|
|
363
|
-
console.error(error.response.data);
|
|
364
|
-
} else {
|
|
365
|
-
console.error(error.message);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
425
|
+
await deploySkill(slug, config, basePath);
|
|
368
426
|
});
|
|
369
427
|
|
|
370
428
|
async function deployContextFiles(slug: string, config: any, resolvedId?: string | null) {
|
|
@@ -451,6 +509,42 @@ async function deployContextFiles(slug: string, config: any, resolvedId?: string
|
|
|
451
509
|
}
|
|
452
510
|
}
|
|
453
511
|
|
|
512
|
+
async function deployAgent(slug: string, config: any) {
|
|
513
|
+
const basePath = getWorkspaceRoot();
|
|
514
|
+
const agentPath = path.join(basePath, "agents", slug);
|
|
515
|
+
const agentJsonPath = path.join(agentPath, "agent.json");
|
|
516
|
+
|
|
517
|
+
if (!(await fs.pathExists(agentJsonPath))) {
|
|
518
|
+
console.error(`❌ agent.json não encontrado em agents/${slug}.`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const agentConfig = await fs.readJson(agentJsonPath);
|
|
523
|
+
console.log(`🚀 Sincronizando agente "${slug}"...`);
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const agentId = await resolveAgentId(slug, config);
|
|
527
|
+
const exists = agentId !== null;
|
|
528
|
+
|
|
529
|
+
if (!exists) {
|
|
530
|
+
await axios.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
|
|
531
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
532
|
+
});
|
|
533
|
+
console.log(`✅ Agente "${slug}" criado.`);
|
|
534
|
+
} else {
|
|
535
|
+
await axios.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
|
|
536
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
537
|
+
});
|
|
538
|
+
console.log(`✅ Configuração de "${slug}" atualizada.`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
await deployContextFiles(slug, config, agentId);
|
|
542
|
+
console.log(`✅ Agente "${slug}" sincronizado com sucesso!`);
|
|
543
|
+
} catch (error: any) {
|
|
544
|
+
console.error(`❌ Erro no deploy de "${slug}":`, error.response?.data || error.message);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
454
548
|
deployCmd
|
|
455
549
|
.command("context <slug>")
|
|
456
550
|
.description("Faz upload dos arquivos de contexto diretamente para o agente usando a API de importação")
|
|
@@ -480,41 +574,89 @@ deployCmd
|
|
|
480
574
|
process.exit(1);
|
|
481
575
|
}
|
|
482
576
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const agentJsonPath = path.join(agentPath, "agent.json");
|
|
577
|
+
await deployAgent(slug, config);
|
|
578
|
+
});
|
|
486
579
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
580
|
+
|
|
581
|
+
async function deployAllAgents(config: any, basePath: string) {
|
|
582
|
+
const agentsDir = path.join(basePath, "agents");
|
|
583
|
+
if (await fs.pathExists(agentsDir)) {
|
|
584
|
+
const agents = await fs.readdir(agentsDir);
|
|
585
|
+
console.log(`🚀 Iniciando deploy em lote de ${agents.length} agentes...`);
|
|
586
|
+
for (const slug of agents) {
|
|
587
|
+
const agentPath = path.join(agentsDir, slug);
|
|
588
|
+
if ((await fs.stat(agentPath)).isDirectory()) {
|
|
589
|
+
await deployAgent(slug, config);
|
|
590
|
+
}
|
|
490
591
|
}
|
|
592
|
+
} else {
|
|
593
|
+
console.log("Nenhum agente encontrado em agents/.");
|
|
594
|
+
}
|
|
595
|
+
}
|
|
491
596
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
await axios.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
|
|
506
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
507
|
-
});
|
|
508
|
-
console.log("✅ Configurações do agente atualizadas.");
|
|
597
|
+
async function deployAllSkills(config: any, basePath: string) {
|
|
598
|
+
const skillsDir = path.join(basePath, "skills");
|
|
599
|
+
if (await fs.pathExists(skillsDir)) {
|
|
600
|
+
const skills = await fs.readdir(skillsDir);
|
|
601
|
+
console.log(`🚀 Iniciando deploy em lote de skills...`);
|
|
602
|
+
for (const item of skills) {
|
|
603
|
+
const itemPath = path.join(skillsDir, item);
|
|
604
|
+
if ((await fs.stat(itemPath)).isDirectory()) {
|
|
605
|
+
if (item === "system") {
|
|
606
|
+
console.log("⏩ Ignorando pasta 'system/' (skills nativas do GoClaw são apenas de leitura)");
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
await deploySkill(item, config, basePath);
|
|
509
610
|
}
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
console.log("Nenhuma skill encontrada em skills/.");
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
deployCmd
|
|
618
|
+
.command("agents")
|
|
619
|
+
.description("Faz deploy de todos os agentes do workspace")
|
|
620
|
+
.action(async () => {
|
|
621
|
+
const config = await getConfig();
|
|
622
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
623
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
const basePath = getWorkspaceRoot();
|
|
627
|
+
await deployAllAgents(config, basePath);
|
|
628
|
+
console.log("🏁 Deploy de agentes concluído!");
|
|
629
|
+
});
|
|
510
630
|
|
|
511
|
-
|
|
512
|
-
|
|
631
|
+
deployCmd
|
|
632
|
+
.command("skills")
|
|
633
|
+
.description("Faz deploy de todas as skills do workspace")
|
|
634
|
+
.action(async () => {
|
|
635
|
+
const config = await getConfig();
|
|
636
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
637
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
638
|
+
process.exit(1);
|
|
639
|
+
}
|
|
640
|
+
const basePath = getWorkspaceRoot();
|
|
641
|
+
await deployAllSkills(config, basePath);
|
|
642
|
+
console.log("🏁 Deploy de skills concluído!");
|
|
643
|
+
});
|
|
513
644
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
645
|
+
deployCmd
|
|
646
|
+
.command("all")
|
|
647
|
+
.description("Faz deploy de todos os agentes e skills do workspace")
|
|
648
|
+
.action(async () => {
|
|
649
|
+
const config = await getConfig();
|
|
650
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
651
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
652
|
+
process.exit(1);
|
|
517
653
|
}
|
|
654
|
+
|
|
655
|
+
const basePath = getWorkspaceRoot();
|
|
656
|
+
await deployAllAgents(config, basePath);
|
|
657
|
+
await deployAllSkills(config, basePath);
|
|
658
|
+
|
|
659
|
+
console.log("🏁 Deploy completo (agentes e skills) concluído!");
|
|
518
660
|
});
|
|
519
661
|
|
|
520
662
|
const pullCmd = program
|
|
@@ -536,6 +678,9 @@ pullCmd
|
|
|
536
678
|
return;
|
|
537
679
|
}
|
|
538
680
|
|
|
681
|
+
console.log("🧹 Limpando a pasta local de skills...");
|
|
682
|
+
await fs.emptyDir(path.join(getWorkspaceRoot(), "skills"));
|
|
683
|
+
|
|
539
684
|
console.log("📥 Baixando skills do GoClaw...");
|
|
540
685
|
try {
|
|
541
686
|
const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
|
|
@@ -601,6 +746,28 @@ pullCmd
|
|
|
601
746
|
}
|
|
602
747
|
}
|
|
603
748
|
|
|
749
|
+
// Remover quaisquer skills fantasmas que o tarball tenha extraído (skills apagadas mas ainda no export)
|
|
750
|
+
const validSlugs = new Set(skills.map((s: any) => s.is_system === true ? path.join("system", s.slug) : s.slug));
|
|
751
|
+
const skillsDir = path.join(getWorkspaceRoot(), "skills");
|
|
752
|
+
if (await fs.pathExists(skillsDir)) {
|
|
753
|
+
const localItems = await fs.readdir(skillsDir);
|
|
754
|
+
for (const item of localItems) {
|
|
755
|
+
if (item === "system") {
|
|
756
|
+
const systemDir = path.join(skillsDir, "system");
|
|
757
|
+
if (await fs.pathExists(systemDir)) {
|
|
758
|
+
const systemItems = await fs.readdir(systemDir);
|
|
759
|
+
for (const sysItem of systemItems) {
|
|
760
|
+
if (!validSlugs.has(path.join("system", sysItem))) {
|
|
761
|
+
await fs.remove(path.join(systemDir, sysItem));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
} else if (!validSlugs.has(item)) {
|
|
766
|
+
await fs.remove(path.join(skillsDir, item));
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
604
771
|
console.log("✅ Pull concluído com sucesso! As skills foram atualizadas localmente.");
|
|
605
772
|
} catch (error: any) {
|
|
606
773
|
console.error("❌ Erro durante o pull das skills:");
|
|
@@ -627,6 +794,9 @@ pullCmd
|
|
|
627
794
|
return;
|
|
628
795
|
}
|
|
629
796
|
|
|
797
|
+
console.log("🧹 Limpando a pasta local de agentes...");
|
|
798
|
+
await fs.emptyDir(path.join(getWorkspaceRoot(), "agents"));
|
|
799
|
+
|
|
630
800
|
console.log("📥 Buscando lista de agentes do GoClaw...");
|
|
631
801
|
try {
|
|
632
802
|
const listResponse = await axios.get(`${config.goclaw.api_url}/v1/agents`, {
|
package/templates/CLI_MANUAL.md
CHANGED
|
@@ -41,26 +41,42 @@ agentforge pull all
|
|
|
41
41
|
```
|
|
42
42
|
Downloads all agents and skills from the GoClaw server. It performs a **surgical extraction**, retrieving only the core `agent.json`, `context_files/`, and skill definitions to keep your workspace perfectly clean. Note: This will ask for confirmation before overwriting local files.
|
|
43
43
|
|
|
44
|
+
### Bulk Deployment
|
|
45
|
+
```bash
|
|
46
|
+
agentforge deploy agents
|
|
47
|
+
```
|
|
48
|
+
Performs a full deployment (config + context + memory) for **all agents** found in your `agents/` directory.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
agentforge deploy skills
|
|
52
|
+
```
|
|
53
|
+
Packages and uploads **all skills** (including those in the `system/` directory) found in your `skills/` directory.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
agentforge deploy all
|
|
57
|
+
```
|
|
58
|
+
Performs a full deployment for **all agents and skills**. This is the most efficient way to synchronize your entire team and toolset after making cross-cutting changes.
|
|
59
|
+
|
|
44
60
|
### Agent Management
|
|
45
61
|
```bash
|
|
46
62
|
agentforge new agent "<Agent Name>"
|
|
47
63
|
```
|
|
48
|
-
Creates a new agent inside the `agents/` directory
|
|
64
|
+
Creates a new agent inside the `agents/` directory. The default `agent_type` is now `predefined`, aligning with GoClaw's official standard for agents with established personalities.
|
|
49
65
|
|
|
50
66
|
```bash
|
|
51
67
|
agentforge pull agents
|
|
52
68
|
```
|
|
53
|
-
Downloads all agents from the GoClaw server and extracts them locally.
|
|
69
|
+
Downloads all agents from the GoClaw server and extracts them locally. It automatically reconstructs memory files from GoClaw's internal JSONL format back into readable Markdown files.
|
|
54
70
|
|
|
55
71
|
```bash
|
|
56
72
|
agentforge deploy agent <slug>
|
|
57
73
|
```
|
|
58
|
-
**Full Deployment:** Reads the local `agent.json` to create
|
|
74
|
+
**Full Deployment:** Reads the local `agent.json` to create or update an agent. Then, it synchronizes all context files and memory (including `MEMORY.md` and the `memory/` folder).
|
|
59
75
|
|
|
60
76
|
```bash
|
|
61
77
|
agentforge deploy context <slug>
|
|
62
78
|
```
|
|
63
|
-
**Hot Reload for Context:** Fast-path deployment that
|
|
79
|
+
**Hot Reload for Context & Memory:** Fast-path deployment that uploads all local context files (`.md`, `.txt`, `.py`) and memory data directly to the server.
|
|
64
80
|
|
|
65
81
|
### Skill Management
|
|
66
82
|
```bash
|
|
@@ -83,8 +99,18 @@ agentforge deploy skill <slug>
|
|
|
83
99
|
```
|
|
84
100
|
Automatically builds your skill into a `.zip` file and securely uploads it to your GoClaw server via the Upload API.
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Memory Support 🧠
|
|
105
|
+
AgentForge CLI handles agent memory as first-class citizens:
|
|
106
|
+
- **MEMORY.md**: Use this file at the root of your agent folder for curated, long-term memory.
|
|
107
|
+
- **memory/**: Any files inside this folder (e.g., chat histories, user-specific facts) are synchronized.
|
|
108
|
+
- **Auto-Conversion**: When pulling, the CLI converts GoClaw's `.jsonl` exports back into `.md` files for easier editing in VS Code.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
Before using the deployment commands, make sure to add your GoClaw Bearer Token to the `agentforge.json` file:
|
|
88
114
|
|
|
89
115
|
```json
|
|
90
116
|
{
|