@innominatum/agentforge-cli 1.0.1 → 1.0.7

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.
@@ -0,0 +1,36 @@
1
+ name: Publish Package to npmjs
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ # Alternatively, you can uncomment the following to publish whenever you push to main
7
+ # push:
8
+ # branches:
9
+ # - main
10
+
11
+ jobs:
12
+ build-and-publish:
13
+ runs-on: ubuntu-latest
14
+ env:
15
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
16
+ permissions:
17
+ contents: read
18
+ id-token: write # Required for NPM Trusted Publishing (OIDC)
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Setup Node.js
24
+ uses: actions/setup-node@v4
25
+ with:
26
+ node-version: '20.x'
27
+ registry-url: 'https://registry.npmjs.org'
28
+
29
+ - name: Install dependencies
30
+ run: npm ci
31
+
32
+ - name: Build TypeScript code
33
+ run: npm run build
34
+
35
+ - name: Publish to NPM
36
+ run: npm publish --access public --provenance
package/README.md CHANGED
@@ -8,6 +8,14 @@ A powerful command-line interface to scaffold, manage, build, and deploy AI Agen
8
8
 
9
9
  ## Installation & Local Development
10
10
 
11
+ ### Global Installation (Recommended)
12
+ You can install the AgentForge CLI globally directly from NPM:
13
+
14
+ ```bash
15
+ npm install -g @innominatum/agentforge-cli
16
+ ```
17
+
18
+ ### Local Development
11
19
  Since you are modifying or maintaining the CLI source code, you should install and use it globally on your local machine using NPM's symlink feature:
12
20
 
13
21
  1. Clone this repository and navigate to its folder.
@@ -63,10 +71,23 @@ If you add a new command or change how the CLI works:
63
71
 
64
72
  ## NPM Publishing (Maintainers)
65
73
 
66
- When this CLI is ready for public release, ensure the following steps are taken:
67
- 1. Update the `version` in `package.json` (e.g., `"version": "1.0.1"`).
68
- 2. Ensure the `name` is correctly set to `"agentforge-cli"`.
69
- 3. Verify that the `"bin"` property is pointing to `"./dist/index.js"`.
70
- 4. Run `npm run build` to ensure the `dist/` directory is fully updated.
71
- 5. Login to your npm account using `npm login`.
72
- 6. Publish the package using `npm publish`.
74
+ ### Manual Publishing
75
+ When this CLI is ready for a new release:
76
+ 1. Update the `version` in `package.json` (e.g., from `"1.0.1"` to `"1.0.2"`).
77
+ 2. Run `npm run build` to ensure the `dist/` directory is fully updated.
78
+ 3. Login to your npm account using `npm login`.
79
+ 4. Publish the package using `npm publish --access public` (you will be prompted for your 2FA security key).
80
+
81
+ ### Automated Publishing (Trusted Publishing / OIDC)
82
+ To fully automate your CI/CD pipeline securely without using any hardcoded NPM tokens or bypassing 2FA, NPM provides "Trusted Publishing" with GitHub Actions:
83
+
84
+ 1. Go to the NPM website and navigate to the `@innominatum/agentforge-cli` package.
85
+ 2. Go to **Settings** > **Publishing Access**.
86
+ 3. Under **Trusted Publishers**, click **Add Publisher** > **GitHub Actions**.
87
+ 4. Fill in the repository details:
88
+ - **GitHub Organization/User:** `Innominatum-pt`
89
+ - **GitHub Repository:** `agentforge-cli`
90
+ - **Workflow file (optional):** `publish.yml`
91
+ 5. Click **Add Publisher**.
92
+
93
+ Once configured, the `.github/workflows/publish.yml` workflow provided in this repository will automatically securely authenticate via OIDC and publish your package whenever you create a new GitHub Release.
package/dist/index.js CHANGED
@@ -285,6 +285,19 @@ async function getConfig() {
285
285
  }
286
286
  return config;
287
287
  }
288
+ async function resolveAgentId(slug, config) {
289
+ try {
290
+ const listResponse = await axios_1.default.get(`${config.goclaw.api_url}/v1/agents`, {
291
+ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
292
+ });
293
+ const agents = listResponse.data.agents || [];
294
+ const agent = agents.find((a) => a.agent_key === slug);
295
+ return agent ? agent.id : null;
296
+ }
297
+ catch (error) {
298
+ return null;
299
+ }
300
+ }
288
301
  const deployCmd = program
289
302
  .command("deploy")
290
303
  .description("Faz o deploy de entidades para a plataforma GoClaw");
@@ -336,34 +349,61 @@ deployCmd
336
349
  }
337
350
  }
338
351
  });
339
- async function deployContextFiles(slug, config) {
352
+ async function deployContextFiles(slug, config, resolvedId) {
353
+ const agentId = resolvedId || (await resolveAgentId(slug, config)) || slug;
340
354
  const basePath = getWorkspaceRoot();
341
355
  const agentPath = path_1.default.join(basePath, "agents", slug);
342
356
  if (!(await fs_extra_1.default.pathExists(agentPath))) {
343
357
  throw new Error(`Agente não encontrado em agents/${slug}`);
344
358
  }
345
359
  const files = await fs_extra_1.default.readdir(agentPath);
346
- const contextFiles = files.filter(f => f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.py')).filter(f => f !== 'README.md' && f !== 'agent.json');
347
- if (contextFiles.length === 0) {
348
- console.log(`Nenhum arquivo de contexto encontrado para "${slug}".`);
360
+ // Identificar ficheiros de contexto e de memória
361
+ const memoryFileNames = ['MEMORY.md', 'memory.md'];
362
+ const memoryDirName = 'memory';
363
+ const contextFiles = files.filter(f => (f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.py')) &&
364
+ f !== 'README.md' &&
365
+ f !== 'agent.json' &&
366
+ !memoryFileNames.includes(f));
367
+ const hasMemoryDir = await fs_extra_1.default.pathExists(path_1.default.join(agentPath, memoryDirName));
368
+ const memoryFilesFound = files.filter(f => memoryFileNames.includes(f));
369
+ const hasMemory = hasMemoryDir || memoryFilesFound.length > 0;
370
+ if (contextFiles.length === 0 && !hasMemory) {
371
+ console.log(`Nenhum ficheiro de contexto ou memória encontrado para "${slug}".`);
349
372
  return;
350
373
  }
351
374
  const tempExportDir = path_1.default.join(basePath, `temp_export_${slug}`);
352
375
  const tempContextDir = path_1.default.join(tempExportDir, "context_files");
376
+ const tempMemoryDir = path_1.default.join(tempExportDir, "memory");
353
377
  const tarPath = path_1.default.join(basePath, `temp_export_${slug}.tar.gz`);
354
378
  try {
355
- await fs_extra_1.default.ensureDir(tempContextDir);
356
- for (const file of contextFiles) {
357
- await fs_extra_1.default.copy(path_1.default.join(agentPath, file), path_1.default.join(tempContextDir, file));
379
+ const sections = [];
380
+ if (contextFiles.length > 0) {
381
+ sections.push("context_files");
382
+ await fs_extra_1.default.ensureDir(tempContextDir);
383
+ for (const file of contextFiles) {
384
+ await fs_extra_1.default.copy(path_1.default.join(agentPath, file), path_1.default.join(tempContextDir, file));
385
+ }
386
+ }
387
+ if (hasMemory) {
388
+ sections.push("memory");
389
+ await fs_extra_1.default.ensureDir(tempMemoryDir);
390
+ // Copiar ficheiros de memória explícitos para a pasta memory no arquivo
391
+ for (const file of memoryFilesFound) {
392
+ await fs_extra_1.default.copy(path_1.default.join(agentPath, file), path_1.default.join(tempMemoryDir, file));
393
+ }
394
+ // Copiar conteúdo da pasta memory se existir
395
+ if (hasMemoryDir) {
396
+ await fs_extra_1.default.copy(path_1.default.join(agentPath, memoryDirName), tempMemoryDir);
397
+ }
358
398
  }
359
399
  await tar.c({
360
400
  gzip: true,
361
401
  file: tarPath,
362
402
  cwd: tempExportDir
363
- }, ["context_files"]);
403
+ }, sections);
364
404
  const form = new form_data_1.default();
365
405
  form.append("file", fs_extra_1.default.createReadStream(tarPath));
366
- const url = `${config.goclaw.api_url}/v1/agents/${slug}/import?include=context_files`;
406
+ const url = `${config.goclaw.api_url}/v1/agents/${agentId}/import?include=${sections.join(",")}`;
367
407
  await axios_1.default.post(url, form, {
368
408
  headers: {
369
409
  ...form.getHeaders(),
@@ -371,7 +411,7 @@ async function deployContextFiles(slug, config) {
371
411
  "X-GoClaw-User-Id": config.goclaw.username || "system"
372
412
  }
373
413
  });
374
- console.log(`✅ Upload cirúrgico de ${contextFiles.length} arquivos concluído com sucesso!`);
414
+ console.log(`✅ Upload cirúrgico de ${contextFiles.length} ficheiros de contexto e ${hasMemory ? 'dados de memória' : 'sem memória'} concluído com sucesso!`);
375
415
  }
376
416
  finally {
377
417
  if (await fs_extra_1.default.pathExists(tempExportDir))
@@ -417,20 +457,8 @@ deployCmd
417
457
  const agentConfig = await fs_extra_1.default.readJson(agentJsonPath);
418
458
  console.log(`🚀 Atualizando configuração do agente "${slug}"...`);
419
459
  try {
420
- let exists = true;
421
- try {
422
- await axios_1.default.get(`${config.goclaw.api_url}/v1/agents/${slug}`, {
423
- headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
424
- });
425
- }
426
- catch (e) {
427
- if (e.response && e.response.status === 404) {
428
- exists = false;
429
- }
430
- else {
431
- throw e;
432
- }
433
- }
460
+ const agentId = await resolveAgentId(slug, config);
461
+ const exists = agentId !== null;
434
462
  if (!exists) {
435
463
  await axios_1.default.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
436
464
  headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
@@ -438,13 +466,13 @@ deployCmd
438
466
  console.log("✅ Agente criado com sucesso.");
439
467
  }
440
468
  else {
441
- await axios_1.default.put(`${config.goclaw.api_url}/v1/agents/${slug}`, agentConfig, {
469
+ await axios_1.default.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
442
470
  headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
443
471
  });
444
472
  console.log("✅ Configurações do agente atualizadas.");
445
473
  }
446
474
  console.log(`🚀 Sincronizando arquivos de contexto...`);
447
- await deployContextFiles(slug, config);
475
+ await deployContextFiles(slug, config, agentId);
448
476
  console.log("✅ Deploy completo concluído!");
449
477
  }
450
478
  catch (error) {
@@ -560,7 +588,7 @@ pullCmd
560
588
  for (const agent of agents) {
561
589
  const slug = agent.agent_key;
562
590
  console.log(`📦 Baixando agente: ${slug}...`);
563
- const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
591
+ const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export?sections=config,context_files,memory`;
564
592
  const response = await axios_1.default.get(url, {
565
593
  headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
566
594
  responseType: "stream"
@@ -580,7 +608,7 @@ pullCmd
580
608
  cwd: agentPath,
581
609
  strip: 0,
582
610
  filter: (path) => {
583
- return path === 'agent.json' || path.startsWith('context_files/');
611
+ return path === 'agent.json' || path.startsWith('context_files/') || path.startsWith('memory/') || path === 'MEMORY.md' || path === 'memory.md';
584
612
  }
585
613
  });
586
614
  const contextDir = path_1.default.join(agentPath, "context_files");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innominatum/agentforge-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.7",
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": {
@@ -16,6 +16,9 @@
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/Innominatum-pt/agentforge-cli.git"
18
18
  },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
19
22
  "keywords": [
20
23
  "ai",
21
24
  "agents",
@@ -49,4 +52,4 @@
49
52
  "tsx": "^4.21.0",
50
53
  "typescript": "^6.0.3"
51
54
  }
52
- }
55
+ }
package/src/index.ts CHANGED
@@ -297,6 +297,19 @@ async function getConfig() {
297
297
  return config;
298
298
  }
299
299
 
300
+ async function resolveAgentId(slug: string, config: any): Promise<string | null> {
301
+ try {
302
+ const listResponse = await axios.get(`${config.goclaw.api_url}/v1/agents`, {
303
+ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
304
+ });
305
+ const agents = listResponse.data.agents || [];
306
+ const agent = agents.find((a: any) => a.agent_key === slug);
307
+ return agent ? agent.id : null;
308
+ } catch (error) {
309
+ return null;
310
+ }
311
+ }
312
+
300
313
  const deployCmd = program
301
314
  .command("deploy")
302
315
  .description("Faz o deploy de entidades para a plataforma GoClaw");
@@ -354,7 +367,8 @@ deployCmd
354
367
  }
355
368
  });
356
369
 
357
- async function deployContextFiles(slug: string, config: any) {
370
+ async function deployContextFiles(slug: string, config: any, resolvedId?: string | null) {
371
+ const agentId = resolvedId || (await resolveAgentId(slug, config)) || slug;
358
372
  const basePath = getWorkspaceRoot();
359
373
  const agentPath = path.join(basePath, "agents", slug);
360
374
  if (!(await fs.pathExists(agentPath))) {
@@ -362,33 +376,66 @@ async function deployContextFiles(slug: string, config: any) {
362
376
  }
363
377
 
364
378
  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}".`);
379
+
380
+ // Identificar ficheiros de contexto e de memória
381
+ const memoryFileNames = ['MEMORY.md', 'memory.md'];
382
+ const memoryDirName = 'memory';
383
+
384
+ const contextFiles = files.filter(f =>
385
+ (f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.py')) &&
386
+ f !== 'README.md' &&
387
+ f !== 'agent.json' &&
388
+ !memoryFileNames.includes(f)
389
+ );
390
+
391
+ const hasMemoryDir = await fs.pathExists(path.join(agentPath, memoryDirName));
392
+ const memoryFilesFound = files.filter(f => memoryFileNames.includes(f));
393
+ const hasMemory = hasMemoryDir || memoryFilesFound.length > 0;
394
+
395
+ if (contextFiles.length === 0 && !hasMemory) {
396
+ console.log(`Nenhum ficheiro de contexto ou memória encontrado para "${slug}".`);
369
397
  return;
370
398
  }
371
399
 
372
400
  const tempExportDir = path.join(basePath, `temp_export_${slug}`);
373
401
  const tempContextDir = path.join(tempExportDir, "context_files");
402
+ const tempMemoryDir = path.join(tempExportDir, "memory");
374
403
  const tarPath = path.join(basePath, `temp_export_${slug}.tar.gz`);
375
404
 
376
405
  try {
377
- await fs.ensureDir(tempContextDir);
378
- for (const file of contextFiles) {
379
- await fs.copy(path.join(agentPath, file), path.join(tempContextDir, file));
406
+ const sections: string[] = [];
407
+
408
+ if (contextFiles.length > 0) {
409
+ sections.push("context_files");
410
+ await fs.ensureDir(tempContextDir);
411
+ for (const file of contextFiles) {
412
+ await fs.copy(path.join(agentPath, file), path.join(tempContextDir, file));
413
+ }
414
+ }
415
+
416
+ if (hasMemory) {
417
+ sections.push("memory");
418
+ await fs.ensureDir(tempMemoryDir);
419
+ // Copiar ficheiros de memória explícitos para a pasta memory no arquivo
420
+ for (const file of memoryFilesFound) {
421
+ await fs.copy(path.join(agentPath, file), path.join(tempMemoryDir, file));
422
+ }
423
+ // Copiar conteúdo da pasta memory se existir
424
+ if (hasMemoryDir) {
425
+ await fs.copy(path.join(agentPath, memoryDirName), tempMemoryDir);
426
+ }
380
427
  }
381
428
 
382
429
  await tar.c({
383
430
  gzip: true,
384
431
  file: tarPath,
385
432
  cwd: tempExportDir
386
- }, ["context_files"]);
433
+ }, sections);
387
434
 
388
435
  const form = new FormData();
389
436
  form.append("file", fs.createReadStream(tarPath));
390
437
 
391
- const url = `${config.goclaw.api_url}/v1/agents/${slug}/import?include=context_files`;
438
+ const url = `${config.goclaw.api_url}/v1/agents/${agentId}/import?include=${sections.join(",")}`;
392
439
  await axios.post(url, form, {
393
440
  headers: {
394
441
  ...form.getHeaders(),
@@ -397,7 +444,7 @@ async function deployContextFiles(slug: string, config: any) {
397
444
  }
398
445
  });
399
446
 
400
- console.log(`✅ Upload cirúrgico de ${contextFiles.length} arquivos concluído com sucesso!`);
447
+ console.log(`✅ Upload cirúrgico de ${contextFiles.length} ficheiros de contexto e ${hasMemory ? 'dados de memória' : 'sem memória'} concluído com sucesso!`);
401
448
  } finally {
402
449
  if (await fs.pathExists(tempExportDir)) await fs.remove(tempExportDir);
403
450
  if (await fs.pathExists(tarPath)) await fs.remove(tarPath);
@@ -446,18 +493,8 @@ deployCmd
446
493
  console.log(`🚀 Atualizando configuração do agente "${slug}"...`);
447
494
 
448
495
  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
- }
496
+ const agentId = await resolveAgentId(slug, config);
497
+ const exists = agentId !== null;
461
498
 
462
499
  if (!exists) {
463
500
  await axios.post(`${config.goclaw.api_url}/v1/agents`, agentConfig, {
@@ -465,14 +502,14 @@ deployCmd
465
502
  });
466
503
  console.log("✅ Agente criado com sucesso.");
467
504
  } else {
468
- await axios.put(`${config.goclaw.api_url}/v1/agents/${slug}`, agentConfig, {
505
+ await axios.put(`${config.goclaw.api_url}/v1/agents/${agentId}`, agentConfig, {
469
506
  headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
470
507
  });
471
508
  console.log("✅ Configurações do agente atualizadas.");
472
509
  }
473
510
 
474
511
  console.log(`🚀 Sincronizando arquivos de contexto...`);
475
- await deployContextFiles(slug, config);
512
+ await deployContextFiles(slug, config, agentId);
476
513
 
477
514
  console.log("✅ Deploy completo concluído!");
478
515
  } catch (error: any) {
@@ -603,7 +640,7 @@ pullCmd
603
640
  const slug = agent.agent_key;
604
641
  console.log(`📦 Baixando agente: ${slug}...`);
605
642
 
606
- const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
643
+ const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export?sections=config,context_files,memory`;
607
644
  const response = await axios.get(url, {
608
645
  headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
609
646
  responseType: "stream"
@@ -628,7 +665,7 @@ pullCmd
628
665
  cwd: agentPath,
629
666
  strip: 0,
630
667
  filter: (path) => {
631
- return path === 'agent.json' || path.startsWith('context_files/');
668
+ return path === 'agent.json' || path.startsWith('context_files/') || path.startsWith('memory/') || path === 'MEMORY.md' || path === 'memory.md';
632
669
  }
633
670
  });
634
671