@innominatum/agentforge-cli 1.0.9 → 1.1.3

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);
@@ -420,6 +420,38 @@ async function deployContextFiles(slug, config, resolvedId) {
420
420
  await fs_extra_1.default.remove(tarPath);
421
421
  }
422
422
  }
423
+ async function deployAgent(slug, config) {
424
+ const basePath = getWorkspaceRoot();
425
+ const agentPath = path_1.default.join(basePath, "agents", slug);
426
+ const agentJsonPath = path_1.default.join(agentPath, "agent.json");
427
+ if (!(await fs_extra_1.default.pathExists(agentJsonPath))) {
428
+ console.error(`❌ agent.json não encontrado em agents/${slug}.`);
429
+ return;
430
+ }
431
+ const agentConfig = await fs_extra_1.default.readJson(agentJsonPath);
432
+ console.log(`🚀 Sincronizando agente "${slug}"...`);
433
+ try {
434
+ const agentId = await resolveAgentId(slug, config);
435
+ const exists = agentId !== null;
436
+ if (!exists) {
437
+ await axios_1.default.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
438
+ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
439
+ });
440
+ console.log(`✅ Agente "${slug}" criado.`);
441
+ }
442
+ else {
443
+ await axios_1.default.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
444
+ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
445
+ });
446
+ console.log(`✅ Configuração de "${slug}" atualizada.`);
447
+ }
448
+ await deployContextFiles(slug, config, agentId);
449
+ console.log(`✅ Agente "${slug}" sincronizado com sucesso!`);
450
+ }
451
+ catch (error) {
452
+ console.error(`❌ Erro no deploy de "${slug}":`, error.response?.data || error.message);
453
+ }
454
+ }
423
455
  deployCmd
424
456
  .command("context <slug>")
425
457
  .description("Faz upload dos arquivos de contexto diretamente para o agente usando a API de importação")
@@ -447,37 +479,32 @@ deployCmd
447
479
  console.error("❌ Configure sua chave de API (token) no agentforge.json.");
448
480
  process.exit(1);
449
481
  }
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}.`);
482
+ await deployAgent(slug, config);
483
+ });
484
+ deployCmd
485
+ .command("all")
486
+ .description("Faz deploy de todos os agentes do workspace")
487
+ .action(async () => {
488
+ const config = await getConfig();
489
+ if (!config.goclaw || !config.goclaw.token) {
490
+ console.error("❌ Configure sua chave de API (token) no agentforge.json.");
455
491
  process.exit(1);
456
492
  }
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.");
467
- }
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.");
473
- }
474
- console.log(`🚀 Sincronizando arquivos de contexto...`);
475
- await deployContextFiles(slug, config, agentId);
476
- console.log("✅ Deploy completo concluído!");
493
+ const basePath = getWorkspaceRoot();
494
+ const agentsDir = path_1.default.join(basePath, "agents");
495
+ if (!(await fs_extra_1.default.pathExists(agentsDir))) {
496
+ console.log("Nenhum agente encontrado em agents/.");
497
+ return;
477
498
  }
478
- catch (error) {
479
- console.error(`❌ Erro no deploy da configuração:`, error.response?.data || error.message);
499
+ const agents = await fs_extra_1.default.readdir(agentsDir);
500
+ console.log(`🚀 Iniciando deploy em lote de ${agents.length} agentes...`);
501
+ for (const slug of agents) {
502
+ const agentPath = path_1.default.join(agentsDir, slug);
503
+ if ((await fs_extra_1.default.stat(agentPath)).isDirectory()) {
504
+ await deployAgent(slug, config);
505
+ }
480
506
  }
507
+ console.log("🏁 Deploy em lote concluído!");
481
508
  });
482
509
  const pullCmd = program
483
510
  .command("pull")
@@ -495,6 +522,8 @@ pullCmd
495
522
  console.log("❌ Pull cancelado pelo utilizador.");
496
523
  return;
497
524
  }
525
+ console.log("🧹 Limpando a pasta local de skills...");
526
+ await fs_extra_1.default.emptyDir(path_1.default.join(getWorkspaceRoot(), "skills"));
498
527
  console.log("📥 Baixando skills do GoClaw...");
499
528
  try {
500
529
  const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
@@ -553,6 +582,28 @@ pullCmd
553
582
  console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
554
583
  }
555
584
  }
585
+ // Remover quaisquer skills fantasmas que o tarball tenha extraído (skills apagadas mas ainda no export)
586
+ const validSlugs = new Set(skills.map((s) => s.is_system === true ? path_1.default.join("system", s.slug) : s.slug));
587
+ const skillsDir = path_1.default.join(getWorkspaceRoot(), "skills");
588
+ if (await fs_extra_1.default.pathExists(skillsDir)) {
589
+ const localItems = await fs_extra_1.default.readdir(skillsDir);
590
+ for (const item of localItems) {
591
+ if (item === "system") {
592
+ const systemDir = path_1.default.join(skillsDir, "system");
593
+ if (await fs_extra_1.default.pathExists(systemDir)) {
594
+ const systemItems = await fs_extra_1.default.readdir(systemDir);
595
+ for (const sysItem of systemItems) {
596
+ if (!validSlugs.has(path_1.default.join("system", sysItem))) {
597
+ await fs_extra_1.default.remove(path_1.default.join(systemDir, sysItem));
598
+ }
599
+ }
600
+ }
601
+ }
602
+ else if (!validSlugs.has(item)) {
603
+ await fs_extra_1.default.remove(path_1.default.join(skillsDir, item));
604
+ }
605
+ }
606
+ }
556
607
  console.log("✅ Pull concluído com sucesso! As skills foram atualizadas localmente.");
557
608
  }
558
609
  catch (error) {
@@ -578,6 +629,8 @@ pullCmd
578
629
  console.log("❌ Pull cancelado pelo utilizador.");
579
630
  return;
580
631
  }
632
+ console.log("🧹 Limpando a pasta local de agentes...");
633
+ await fs_extra_1.default.emptyDir(path_1.default.join(getWorkspaceRoot(), "agents"));
581
634
  console.log("📥 Buscando lista de agentes do GoClaw...");
582
635
  try {
583
636
  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.3",
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);
@@ -451,6 +451,42 @@ async function deployContextFiles(slug: string, config: any, resolvedId?: string
451
451
  }
452
452
  }
453
453
 
454
+ async function deployAgent(slug: string, config: any) {
455
+ const basePath = getWorkspaceRoot();
456
+ const agentPath = path.join(basePath, "agents", slug);
457
+ const agentJsonPath = path.join(agentPath, "agent.json");
458
+
459
+ if (!(await fs.pathExists(agentJsonPath))) {
460
+ console.error(`❌ agent.json não encontrado em agents/${slug}.`);
461
+ return;
462
+ }
463
+
464
+ const agentConfig = await fs.readJson(agentJsonPath);
465
+ console.log(`🚀 Sincronizando agente "${slug}"...`);
466
+
467
+ try {
468
+ const agentId = await resolveAgentId(slug, config);
469
+ const exists = agentId !== null;
470
+
471
+ if (!exists) {
472
+ await axios.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
473
+ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
474
+ });
475
+ console.log(`✅ Agente "${slug}" criado.`);
476
+ } else {
477
+ await axios.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
478
+ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
479
+ });
480
+ console.log(`✅ Configuração de "${slug}" atualizada.`);
481
+ }
482
+
483
+ await deployContextFiles(slug, config, agentId);
484
+ console.log(`✅ Agente "${slug}" sincronizado com sucesso!`);
485
+ } catch (error: any) {
486
+ console.error(`❌ Erro no deploy de "${slug}":`, error.response?.data || error.message);
487
+ }
488
+ }
489
+
454
490
  deployCmd
455
491
  .command("context <slug>")
456
492
  .description("Faz upload dos arquivos de contexto diretamente para o agente usando a API de importação")
@@ -480,41 +516,38 @@ deployCmd
480
516
  process.exit(1);
481
517
  }
482
518
 
483
- const basePath = getWorkspaceRoot();
484
- const agentPath = path.join(basePath, "agents", slug);
485
- const agentJsonPath = path.join(agentPath, "agent.json");
519
+ await deployAgent(slug, config);
520
+ });
486
521
 
487
- if (!(await fs.pathExists(agentJsonPath))) {
488
- console.error(`❌ agent.json não encontrado em agents/${slug}.`);
522
+ deployCmd
523
+ .command("all")
524
+ .description("Faz deploy de todos os agentes do workspace")
525
+ .action(async () => {
526
+ const config = await getConfig();
527
+ if (!config.goclaw || !config.goclaw.token) {
528
+ console.error("❌ Configure sua chave de API (token) no agentforge.json.");
489
529
  process.exit(1);
490
530
  }
491
531
 
492
- const agentConfig = await fs.readJson(agentJsonPath);
493
- console.log(`🚀 Atualizando configuração do agente "${slug}"...`);
532
+ const basePath = getWorkspaceRoot();
533
+ const agentsDir = path.join(basePath, "agents");
494
534
 
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.");
509
- }
510
-
511
- console.log(`🚀 Sincronizando arquivos de contexto...`);
512
- await deployContextFiles(slug, config, agentId);
535
+ if (!(await fs.pathExists(agentsDir))) {
536
+ console.log("Nenhum agente encontrado em agents/.");
537
+ return;
538
+ }
513
539
 
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);
540
+ const agents = await fs.readdir(agentsDir);
541
+ console.log(`🚀 Iniciando deploy em lote de ${agents.length} agentes...`);
542
+
543
+ for (const slug of agents) {
544
+ const agentPath = path.join(agentsDir, slug);
545
+ if ((await fs.stat(agentPath)).isDirectory()) {
546
+ await deployAgent(slug, config);
547
+ }
517
548
  }
549
+
550
+ console.log("🏁 Deploy em lote concluído!");
518
551
  });
519
552
 
520
553
  const pullCmd = program
@@ -536,6 +569,9 @@ pullCmd
536
569
  return;
537
570
  }
538
571
 
572
+ console.log("🧹 Limpando a pasta local de skills...");
573
+ await fs.emptyDir(path.join(getWorkspaceRoot(), "skills"));
574
+
539
575
  console.log("📥 Baixando skills do GoClaw...");
540
576
  try {
541
577
  const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
@@ -601,6 +637,28 @@ pullCmd
601
637
  }
602
638
  }
603
639
 
640
+ // Remover quaisquer skills fantasmas que o tarball tenha extraído (skills apagadas mas ainda no export)
641
+ const validSlugs = new Set(skills.map((s: any) => s.is_system === true ? path.join("system", s.slug) : s.slug));
642
+ const skillsDir = path.join(getWorkspaceRoot(), "skills");
643
+ if (await fs.pathExists(skillsDir)) {
644
+ const localItems = await fs.readdir(skillsDir);
645
+ for (const item of localItems) {
646
+ if (item === "system") {
647
+ const systemDir = path.join(skillsDir, "system");
648
+ if (await fs.pathExists(systemDir)) {
649
+ const systemItems = await fs.readdir(systemDir);
650
+ for (const sysItem of systemItems) {
651
+ if (!validSlugs.has(path.join("system", sysItem))) {
652
+ await fs.remove(path.join(systemDir, sysItem));
653
+ }
654
+ }
655
+ }
656
+ } else if (!validSlugs.has(item)) {
657
+ await fs.remove(path.join(skillsDir, item));
658
+ }
659
+ }
660
+ }
661
+
604
662
  console.log("✅ Pull concluído com sucesso! As skills foram atualizadas localmente.");
605
663
  } catch (error: any) {
606
664
  console.error("❌ Erro durante o pull das skills:");
@@ -627,6 +685,9 @@ pullCmd
627
685
  return;
628
686
  }
629
687
 
688
+ console.log("🧹 Limpando a pasta local de agentes...");
689
+ await fs.emptyDir(path.join(getWorkspaceRoot(), "agents"));
690
+
630
691
  console.log("📥 Buscando lista de agentes do GoClaw...");
631
692
  try {
632
693
  const listResponse = await axios.get(`${config.goclaw.api_url}/v1/agents`, {
@@ -41,26 +41,32 @@ 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 all
47
+ ```
48
+ Performs a full deployment (config + context + memory) for **all agents** found in your `agents/` directory. This is the most efficient way to synchronize your entire team after making cross-cutting changes.
49
+
44
50
  ### Agent Management
45
51
  ```bash
46
52
  agentforge new agent "<Agent Name>"
47
53
  ```
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/`).
54
+ 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
55
 
50
56
  ```bash
51
57
  agentforge pull agents
52
58
  ```
53
- Downloads all agents from the GoClaw server and extracts them locally.
59
+ 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
60
 
55
61
  ```bash
56
62
  agentforge deploy agent <slug>
57
63
  ```
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.
64
+ **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
65
 
60
66
  ```bash
61
67
  agentforge deploy context <slug>
62
68
  ```
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.
69
+ **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
70
 
65
71
  ### Skill Management
66
72
  ```bash
@@ -83,8 +89,18 @@ agentforge deploy skill <slug>
83
89
  ```
84
90
  Automatically builds your skill into a `.zip` file and securely uploads it to your GoClaw server via the Upload API.
85
91
 
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:
92
+ ---
93
+
94
+ ## Memory Support 🧠
95
+ AgentForge CLI handles agent memory as first-class citizens:
96
+ - **MEMORY.md**: Use this file at the root of your agent folder for curated, long-term memory.
97
+ - **memory/**: Any files inside this folder (e.g., chat histories, user-specific facts) are synchronized.
98
+ - **Auto-Conversion**: When pulling, the CLI converts GoClaw's `.jsonl` exports back into `.md` files for easier editing in VS Code.
99
+
100
+ ---
101
+
102
+ ## Configuration
103
+ Before using the deployment commands, make sure to add your GoClaw Bearer Token to the `agentforge.json` file:
88
104
 
89
105
  ```json
90
106
  {