@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 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á sobrescrever as suas ${entityType} locais. Quaisquer alterações não publicadas serão perdidas. Deseja continuar? (s/N) `, answer => {
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
- deployCmd
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 zipPath = path_1.default.join(exportsPath, `${slug}.zip`);
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
- process.exit(1);
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(`✅ Build concluído: ${slug}.zip preparado para envio.`);
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(`📌 Skill atualizada para a versão ${data.version}.`);
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("❌ Erro durante o deploy:");
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
- const basePath = getWorkspaceRoot();
451
- const agentPath = path_1.default.join(basePath, "agents", slug);
452
- const agentJsonPath = path_1.default.join(agentPath, "agent.json");
453
- if (!(await fs_extra_1.default.pathExists(agentJsonPath))) {
454
- console.error(`❌ agent.json não encontrado em agents/${slug}.`);
455
- process.exit(1);
456
- }
457
- const agentConfig = await fs_extra_1.default.readJson(agentJsonPath);
458
- console.log(`🚀 Atualizando configuração do agente "${slug}"...`);
459
- try {
460
- const agentId = await resolveAgentId(slug, config);
461
- const exists = agentId !== null;
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
- else {
469
- await axios_1.default.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
470
- headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
471
- });
472
- console.log("✅ Configurações do agente atualizadas.");
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
- catch (error) {
479
- console.error(`❌ Erro no deploy da configuração:`, error.response?.data || error.message);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innominatum/agentforge-cli",
3
- "version": "1.0.9",
3
+ "version": "1.1.9",
4
4
  "description": "A powerful command-line interface to scaffold, manage, build, and deploy AI Agents and Skills for the GoClaw platform.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
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á sobrescrever as suas ${entityType} locais. Quaisquer alterações não publicadas serão perdidas. Deseja continuar? (s/N) `, answer => {
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 o build da skill e envia para a API do GoClaw")
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
- const skillPath = path.join(basePath, "skills", slug);
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
- const basePath = getWorkspaceRoot();
484
- const agentPath = path.join(basePath, "agents", slug);
485
- const agentJsonPath = path.join(agentPath, "agent.json");
577
+ await deployAgent(slug, config);
578
+ });
486
579
 
487
- if (!(await fs.pathExists(agentJsonPath))) {
488
- console.error(`❌ agent.json não encontrado em agents/${slug}.`);
489
- process.exit(1);
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
- const agentConfig = await fs.readJson(agentJsonPath);
493
- console.log(`🚀 Atualizando configuração do agente "${slug}"...`);
494
-
495
- try {
496
- const agentId = await resolveAgentId(slug, config);
497
- const exists = agentId !== null;
498
-
499
- if (!exists) {
500
- await axios.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
501
- headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
502
- });
503
- console.log("✅ Agente criado com sucesso.");
504
- } else {
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
- console.log(`🚀 Sincronizando arquivos de contexto...`);
512
- await deployContextFiles(slug, config, agentId);
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
- console.log("✅ Deploy completo concluído!");
515
- } catch (error: any) {
516
- console.error(`❌ Erro no deploy da configuração:`, error.response?.data || error.message);
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`, {
@@ -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 using the files found in `templates/default-agent/`. If the name contains spaces, remember to wrap it in quotes. The CLI will automatically slugify the folder name (e.g., `agents/my-super-agent/`).
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 a new agent or update an existing one (e.g., updating the LLM model). Then, it automatically synchronizes all context files.
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 reads all local `.md`, `.txt`, and `.py` files in your agent folder and uploads them directly to the agent's mind on the server. Perfect for iterating on prompts.
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
- ### Configuration
87
- Before using the deployment commands, make sure to add your GoClaw Bearer Token to the `agentforge.json` file located at the root of your workspace:
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
  {