@innominatum/agentforge-cli 1.1.9 → 1.1.22
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/.github/workflows/publish.yml +4 -7
- package/dist/index.js +313 -263
- package/package.json +1 -1
- package/src/index.ts +337 -299
package/src/index.ts
CHANGED
|
@@ -434,66 +434,121 @@ async function deployContextFiles(slug: string, config: any, resolvedId?: string
|
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
const files = await fs.readdir(agentPath);
|
|
437
|
-
|
|
438
|
-
// Identificar ficheiros de contexto e de memória
|
|
439
|
-
const memoryFileNames = ['MEMORY.md', 'memory.md'];
|
|
440
|
-
const memoryDirName = 'memory';
|
|
441
|
-
|
|
442
|
-
const contextFiles = files.filter(f =>
|
|
443
|
-
(f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.py')) &&
|
|
444
|
-
f !== 'README.md' &&
|
|
445
|
-
f !== 'agent.json' &&
|
|
446
|
-
!memoryFileNames.includes(f)
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
const hasMemoryDir = await fs.pathExists(path.join(agentPath, memoryDirName));
|
|
450
|
-
const memoryFilesFound = files.filter(f => memoryFileNames.includes(f));
|
|
451
|
-
const hasMemory = hasMemoryDir || memoryFilesFound.length > 0;
|
|
437
|
+
const itemsToSync = files.filter(f => f !== 'agent.json' && f !== 'README.md');
|
|
452
438
|
|
|
453
|
-
if (
|
|
439
|
+
if (itemsToSync.length === 0) {
|
|
454
440
|
console.log(`Nenhum ficheiro de contexto ou memória encontrado para "${slug}".`);
|
|
455
441
|
return;
|
|
456
442
|
}
|
|
457
443
|
|
|
458
444
|
const tempExportDir = path.join(basePath, `temp_export_${slug}`);
|
|
459
|
-
const tempContextDir = path.join(tempExportDir, "context_files");
|
|
460
|
-
const tempMemoryDir = path.join(tempExportDir, "memory");
|
|
461
445
|
const tarPath = path.join(basePath, `temp_export_${slug}.tar.gz`);
|
|
462
446
|
|
|
447
|
+
// Guardar lista de ficheiros locais para pruning
|
|
448
|
+
const localFilePaths: string[] = [];
|
|
449
|
+
async function collectFilesRecursive(dir: string, baseDir: string): Promise<string[]> {
|
|
450
|
+
const results: string[] = [];
|
|
451
|
+
if (!(await fs.pathExists(dir))) return results;
|
|
452
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
453
|
+
for (const entry of entries) {
|
|
454
|
+
const fullPath = path.join(dir, entry.name);
|
|
455
|
+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
456
|
+
if (entry.isDirectory()) {
|
|
457
|
+
const subResults = await collectFilesRecursive(fullPath, baseDir);
|
|
458
|
+
results.push(...subResults);
|
|
459
|
+
} else {
|
|
460
|
+
results.push(relativePath);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return results;
|
|
464
|
+
}
|
|
465
|
+
|
|
463
466
|
try {
|
|
464
|
-
const sections
|
|
467
|
+
const sections = new Set<string>();
|
|
465
468
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
469
|
+
// Processar ficheiros/pastas da raiz do agente
|
|
470
|
+
for (const item of itemsToSync) {
|
|
471
|
+
const itemPath = path.join(agentPath, item);
|
|
472
|
+
const isDir = (await fs.stat(itemPath)).isDirectory();
|
|
473
|
+
|
|
474
|
+
const section = "context_files";
|
|
475
|
+
sections.add(section);
|
|
476
|
+
|
|
477
|
+
const targetDir = path.join(tempExportDir, section);
|
|
478
|
+
await fs.ensureDir(targetDir);
|
|
479
|
+
|
|
480
|
+
if (isDir) {
|
|
481
|
+
// Obter todos os ficheiros da pasta (ex: memory, _system)
|
|
482
|
+
// e achatá-os (flatten) com o nome da pasta como prefixo (ex: memory_arquivo.md)
|
|
483
|
+
// O GoClaw espera que os ficheiros de contexto não tenham pastas, mas sim prefixos achatados!
|
|
484
|
+
const subFiles = await fs.readdir(itemPath);
|
|
485
|
+
for (const sub of subFiles) {
|
|
486
|
+
const subPath = path.join(itemPath, sub);
|
|
487
|
+
const isSubDir = (await fs.stat(subPath)).isDirectory();
|
|
488
|
+
if (!isSubDir) {
|
|
489
|
+
const flatName = `${item}_${sub}`;
|
|
490
|
+
await fs.copy(subPath, path.join(targetDir, flatName));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
await fs.copy(itemPath, path.join(targetDir, item));
|
|
471
495
|
}
|
|
472
496
|
}
|
|
473
497
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
498
|
+
// Coletar ficheiros para pruning
|
|
499
|
+
const sectionDir = path.join(tempExportDir, "context_files");
|
|
500
|
+
const sectionEntries = await collectFilesRecursive(sectionDir, sectionDir);
|
|
501
|
+
for (const entry of sectionEntries) {
|
|
502
|
+
// O GoClaw retorna os caminhos das memórias com barras (memory/arquivo.md)
|
|
503
|
+
// Nós achatamos para o upload (memory_arquivo.md). Para o pruning não apagar ficheiros
|
|
504
|
+
// válidos acidentalmente, temos que re-mapear o nome achatado para a versão com barra.
|
|
505
|
+
let finalEntry = entry;
|
|
506
|
+
if (entry.startsWith("memory_")) {
|
|
507
|
+
finalEntry = entry.replace("memory_", "memory/");
|
|
508
|
+
} else if (entry.startsWith("_system_")) {
|
|
509
|
+
finalEntry = entry.replace("_system_", "_system/");
|
|
480
510
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
511
|
+
localFilePaths.push(finalEntry);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const sectionsArray = Array.from(sections);
|
|
515
|
+
|
|
516
|
+
// --- HACK PARA APAGAR FICHEIROS FÍSICOS DO GOCLAW ---
|
|
517
|
+
// O GoClaw não apaga ficheiros do disco (context_files) quando os apagamos da DB (memory_documents).
|
|
518
|
+
// Isto faz com que os ficheiros voltem como "fantasmas" durante o pull.
|
|
519
|
+
// Solução: Injetar ficheiros vazios no tarball para os órfãos, forçando o /import a esmagá-los com 0 bytes!
|
|
520
|
+
try {
|
|
521
|
+
const docsUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents`;
|
|
522
|
+
const preDocsRes = await axios.get(docsUrl, {
|
|
523
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
524
|
+
});
|
|
525
|
+
const preDocs = preDocsRes.data || [];
|
|
526
|
+
for (const pDoc of preDocs) {
|
|
527
|
+
if (pDoc.path && !localFilePaths.includes(pDoc.path)) {
|
|
528
|
+
const flatGhost = pDoc.path.replace(/[\/\\]/g, '_');
|
|
529
|
+
const ghostPath = path.join(tempExportDir, "context_files", flatGhost);
|
|
530
|
+
await fs.ensureDir(path.dirname(ghostPath));
|
|
531
|
+
await fs.writeFile(ghostPath, " "); // 1 byte soft-delete payload
|
|
532
|
+
if (!sectionsArray.includes("context_files")) {
|
|
533
|
+
sectionsArray.push("context_files");
|
|
534
|
+
}
|
|
535
|
+
}
|
|
484
536
|
}
|
|
537
|
+
} catch (e: any) {
|
|
538
|
+
console.warn("Aviso: Falha ao procurar fantasmas para o tarball.", e.message);
|
|
485
539
|
}
|
|
486
540
|
|
|
487
541
|
await tar.c({
|
|
488
542
|
gzip: true,
|
|
489
543
|
file: tarPath,
|
|
490
544
|
cwd: tempExportDir
|
|
491
|
-
},
|
|
545
|
+
}, sectionsArray);
|
|
492
546
|
|
|
493
547
|
const form = new FormData();
|
|
494
548
|
form.append("file", fs.createReadStream(tarPath));
|
|
495
549
|
|
|
496
|
-
|
|
550
|
+
// Upload dos ficheiros (aditivo)
|
|
551
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agentId}/import?include=${sectionsArray.join(",")}`;
|
|
497
552
|
await axios.post(url, form, {
|
|
498
553
|
headers: {
|
|
499
554
|
...form.getHeaders(),
|
|
@@ -502,7 +557,76 @@ async function deployContextFiles(slug: string, config: any, resolvedId?: string
|
|
|
502
557
|
}
|
|
503
558
|
});
|
|
504
559
|
|
|
505
|
-
console.log(`✅ Upload
|
|
560
|
+
console.log(`✅ Upload de ficheiros e subpastas de contexto concluído com sucesso!`);
|
|
561
|
+
|
|
562
|
+
// Atualização forçada de memórias editadas (bypassa a proteção de overwrite do /import)
|
|
563
|
+
for (const localPath of localFilePaths) {
|
|
564
|
+
if (localPath.startsWith('memory/') && localPath.endsWith('.md')) {
|
|
565
|
+
try {
|
|
566
|
+
// O ficheiro físico no tempExportDir está achatado (memory_arquivo.md)
|
|
567
|
+
const flatFileName = localPath.replace("memory/", "memory_");
|
|
568
|
+
const content = await fs.readFile(path.join(sectionDir, flatFileName), 'utf8');
|
|
569
|
+
|
|
570
|
+
// O endpoint usa {path...} portanto não podemos fazer URL encode das barras
|
|
571
|
+
const putUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents/${localPath}`;
|
|
572
|
+
await axios.put(putUrl, { content }, {
|
|
573
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
574
|
+
});
|
|
575
|
+
console.log(`✅ Edição de memória forçada com sucesso: ${localPath}`);
|
|
576
|
+
} catch (putErr: any) {
|
|
577
|
+
console.warn(`⚠️ Aviso na edição de ${localPath}: O conteúdo pode não ter sido alterado. (${putErr.message})`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// --- Início do Pruning (Remover ficheiros órfãos do servidor) ---
|
|
583
|
+
try {
|
|
584
|
+
const documentsUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents`;
|
|
585
|
+
const docsResponse = await axios.get(documentsUrl, {
|
|
586
|
+
headers: {
|
|
587
|
+
Authorization: `Bearer ${config.goclaw.token}`,
|
|
588
|
+
"X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const remoteDocs = docsResponse.data || [];
|
|
593
|
+
let deletedCount = 0;
|
|
594
|
+
|
|
595
|
+
for (const doc of remoteDocs) {
|
|
596
|
+
if (!doc.path) continue;
|
|
597
|
+
|
|
598
|
+
// Verifica se o ficheiro remoto existe na nossa lista de ficheiros locais
|
|
599
|
+
if (!localFilePaths.includes(doc.path)) {
|
|
600
|
+
console.log(`🧹 Removendo memória órfã no servidor: ${doc.path}`);
|
|
601
|
+
|
|
602
|
+
try {
|
|
603
|
+
// O endpoint do GoClaw espera o caminho exato sem URL encode (route genérica {path...})
|
|
604
|
+
// E é mandatório passar o user_id dono do documento, senão dá erro 500.
|
|
605
|
+
const deleteUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents/${doc.path}`;
|
|
606
|
+
await axios.delete(deleteUrl, {
|
|
607
|
+
headers: {
|
|
608
|
+
Authorization: `Bearer ${config.goclaw.token}`,
|
|
609
|
+
"X-GoClaw-User-Id": doc.user_id || config.goclaw.username || "system"
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
deletedCount++;
|
|
613
|
+
} catch (delErr: any) {
|
|
614
|
+
const errorData = delErr.response?.data?.error || "";
|
|
615
|
+
if (delErr.response?.status === 500 && errorData.includes("not found")) {
|
|
616
|
+
console.log(`✅ ${doc.path} já estava removido da base de dados.`);
|
|
617
|
+
} else {
|
|
618
|
+
console.warn(`⚠️ Não foi possível apagar ${doc.path}: ${delErr.message}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (deletedCount > 0) {
|
|
624
|
+
console.log(`✅ Pruning concluído: ${deletedCount} ficheiro(s) apagado(s) do GoClaw.`);
|
|
625
|
+
}
|
|
626
|
+
} catch (pruneErr: any) {
|
|
627
|
+
console.warn(`⚠️ Aviso: Falha ao fazer pruning das memórias: ${pruneErr.message}`);
|
|
628
|
+
}
|
|
629
|
+
|
|
506
630
|
} finally {
|
|
507
631
|
if (await fs.pathExists(tempExportDir)) await fs.remove(tempExportDir);
|
|
508
632
|
if (await fs.pathExists(tarPath)) await fs.remove(tarPath);
|
|
@@ -663,6 +787,97 @@ const pullCmd = program
|
|
|
663
787
|
.command("pull")
|
|
664
788
|
.description("Sincroniza entidades do GoClaw para o workspace local");
|
|
665
789
|
|
|
790
|
+
async function pullAllSkills(config: any) {
|
|
791
|
+
console.log("🧹 Limpando a pasta local de skills...");
|
|
792
|
+
await fs.emptyDir(path.join(getWorkspaceRoot(), "skills"));
|
|
793
|
+
|
|
794
|
+
console.log("📥 Baixando skills do GoClaw...");
|
|
795
|
+
const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
|
|
796
|
+
const response = await axios.get(url, {
|
|
797
|
+
headers: {
|
|
798
|
+
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
799
|
+
},
|
|
800
|
+
responseType: "stream"
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
const tempTarPath = path.join(getWorkspaceRoot(), "temp_skills.tar.gz");
|
|
804
|
+
const writer = fs.createWriteStream(tempTarPath);
|
|
805
|
+
response.data.pipe(writer);
|
|
806
|
+
|
|
807
|
+
await new Promise((resolve, reject) => {
|
|
808
|
+
writer.on("finish", resolve);
|
|
809
|
+
writer.on("error", reject);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
console.log("📦 Extraindo skills para a pasta local...");
|
|
813
|
+
await tar.x({
|
|
814
|
+
file: tempTarPath,
|
|
815
|
+
cwd: getWorkspaceRoot()
|
|
816
|
+
});
|
|
817
|
+
await fs.remove(tempTarPath);
|
|
818
|
+
|
|
819
|
+
console.log("📥 Baixando ficheiros de código das skills...");
|
|
820
|
+
const skillsListRes = await axios.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
821
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
const skills = skillsListRes.data.skills || [];
|
|
825
|
+
for (const skill of skills) {
|
|
826
|
+
try {
|
|
827
|
+
const isSystem = skill.is_system === true;
|
|
828
|
+
const targetFolder = isSystem ? path.join("system", skill.slug) : skill.slug;
|
|
829
|
+
|
|
830
|
+
if (isSystem) {
|
|
831
|
+
const originalPath = path.join(getWorkspaceRoot(), "skills", skill.slug);
|
|
832
|
+
const newPath = path.join(getWorkspaceRoot(), "skills", targetFolder);
|
|
833
|
+
if (await fs.pathExists(originalPath)) {
|
|
834
|
+
await fs.ensureDir(path.dirname(newPath));
|
|
835
|
+
await fs.move(originalPath, newPath, { overwrite: true });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const filesRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
840
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const files = filesRes.data.files || [];
|
|
844
|
+
for (const file of files) {
|
|
845
|
+
if (file.isDir) continue;
|
|
846
|
+
const fileContentRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
847
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
848
|
+
});
|
|
849
|
+
const filePath = path.join(getWorkspaceRoot(), "skills", targetFolder, file.path);
|
|
850
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
851
|
+
await fs.writeFile(filePath, fileContentRes.data.content || "");
|
|
852
|
+
}
|
|
853
|
+
} catch (fileErr: any) {
|
|
854
|
+
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Remover quaisquer skills fantasmas que o tarball tenha extraído
|
|
859
|
+
const validSlugs = new Set(skills.map((s: any) => s.is_system === true ? path.join("system", s.slug) : s.slug));
|
|
860
|
+
const skillsDir = path.join(getWorkspaceRoot(), "skills");
|
|
861
|
+
if (await fs.pathExists(skillsDir)) {
|
|
862
|
+
const localItems = await fs.readdir(skillsDir);
|
|
863
|
+
for (const item of localItems) {
|
|
864
|
+
if (item === "system") {
|
|
865
|
+
const systemDir = path.join(skillsDir, "system");
|
|
866
|
+
if (await fs.pathExists(systemDir)) {
|
|
867
|
+
const systemItems = await fs.readdir(systemDir);
|
|
868
|
+
for (const sysItem of systemItems) {
|
|
869
|
+
if (!validSlugs.has(path.join("system", sysItem))) {
|
|
870
|
+
await fs.remove(path.join(systemDir, sysItem));
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
} else if (!validSlugs.has(item)) {
|
|
875
|
+
await fs.remove(path.join(skillsDir, item));
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
666
881
|
pullCmd
|
|
667
882
|
.command("skills")
|
|
668
883
|
.description("Faz download do arquivo tar.gz de skills do GoClaw e extrai localmente")
|
|
@@ -678,106 +893,101 @@ pullCmd
|
|
|
678
893
|
return;
|
|
679
894
|
}
|
|
680
895
|
|
|
681
|
-
console.log("🧹 Limpando a pasta local de skills...");
|
|
682
|
-
await fs.emptyDir(path.join(getWorkspaceRoot(), "skills"));
|
|
683
|
-
|
|
684
|
-
console.log("📥 Baixando skills do GoClaw...");
|
|
685
896
|
try {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
897
|
+
await pullAllSkills(config);
|
|
898
|
+
console.log("✅ Pull de skills concluído com sucesso! As skills foram atualizadas localmente.");
|
|
899
|
+
} catch (error: any) {
|
|
900
|
+
console.error("❌ Erro durante o pull das skills:");
|
|
901
|
+
if (error.response) {
|
|
902
|
+
console.error(`Status HTTP ${error.response.status}`);
|
|
903
|
+
} else {
|
|
904
|
+
console.error(error.message);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
});
|
|
693
908
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
909
|
+
async function pullAgent(slug: string, agentId: string, config: any) {
|
|
910
|
+
console.log(`📦 Baixando agente: ${slug}...`);
|
|
911
|
+
|
|
912
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agentId}/export`;
|
|
913
|
+
const response = await axios.get(url, {
|
|
914
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
|
|
915
|
+
responseType: "stream"
|
|
916
|
+
});
|
|
697
917
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
918
|
+
const tempTarPath = path.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
919
|
+
|
|
920
|
+
try {
|
|
921
|
+
const writer = fs.createWriteStream(tempTarPath);
|
|
922
|
+
response.data.pipe(writer);
|
|
702
923
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
});
|
|
708
|
-
await fs.remove(tempTarPath);
|
|
924
|
+
await new Promise((resolve, reject) => {
|
|
925
|
+
writer.on("finish", resolve);
|
|
926
|
+
writer.on("error", reject);
|
|
927
|
+
});
|
|
709
928
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
for (const skill of skills) {
|
|
717
|
-
try {
|
|
718
|
-
const isSystem = skill.is_system === true;
|
|
719
|
-
const targetFolder = isSystem ? path.join("system", skill.slug) : skill.slug;
|
|
720
|
-
|
|
721
|
-
if (isSystem) {
|
|
722
|
-
const originalPath = path.join(getWorkspaceRoot(), "skills", skill.slug);
|
|
723
|
-
const newPath = path.join(getWorkspaceRoot(), "skills", targetFolder);
|
|
724
|
-
if (await fs.pathExists(originalPath)) {
|
|
725
|
-
await fs.ensureDir(path.dirname(newPath));
|
|
726
|
-
await fs.move(originalPath, newPath, { overwrite: true });
|
|
727
|
-
}
|
|
728
|
-
}
|
|
929
|
+
const agentPath = path.join(getWorkspaceRoot(), "agents", slug);
|
|
930
|
+
if (await fs.pathExists(agentPath)) {
|
|
931
|
+
await fs.emptyDir(agentPath);
|
|
932
|
+
} else {
|
|
933
|
+
await fs.ensureDir(agentPath);
|
|
934
|
+
}
|
|
729
935
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
});
|
|
740
|
-
const filePath = path.join(getWorkspaceRoot(), "skills", targetFolder, file.path);
|
|
741
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
742
|
-
await fs.writeFile(filePath, fileContentRes.data.content || "");
|
|
743
|
-
}
|
|
744
|
-
} catch (fileErr: any) {
|
|
745
|
-
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
746
|
-
}
|
|
936
|
+
// Obter os caminhos reais (com barras) da API para reverter o flattening do export
|
|
937
|
+
const docsRes = await axios.get(`${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents`, {
|
|
938
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
939
|
+
});
|
|
940
|
+
const pathMap: Record<string, string> = {};
|
|
941
|
+
(docsRes.data || []).forEach((d: any) => {
|
|
942
|
+
if (d.path) {
|
|
943
|
+
const flat = d.path.replace(/[\/\\]/g, '_');
|
|
944
|
+
pathMap[flat] = d.path;
|
|
747
945
|
}
|
|
946
|
+
});
|
|
748
947
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
948
|
+
await tar.x({
|
|
949
|
+
file: tempTarPath,
|
|
950
|
+
cwd: agentPath,
|
|
951
|
+
strip: 0,
|
|
952
|
+
filter: (path) => {
|
|
953
|
+
return path === 'agent.json' || path.startsWith('context_files/');
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
const contextDir = path.join(agentPath, "context_files");
|
|
958
|
+
if (await fs.pathExists(contextDir)) {
|
|
959
|
+
const contextFiles = await fs.readdir(contextDir);
|
|
960
|
+
for (const f of contextFiles) {
|
|
961
|
+
const filePath = path.join(contextDir, f);
|
|
962
|
+
const stat = await fs.stat(filePath);
|
|
963
|
+
if (stat.isFile()) {
|
|
964
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
965
|
+
if (content === " " || content === "") {
|
|
966
|
+
// Fantasma neutralizado pelo nosso script de deploy! Deita fora!
|
|
967
|
+
await fs.remove(filePath);
|
|
968
|
+
continue;
|
|
767
969
|
}
|
|
768
970
|
}
|
|
769
|
-
}
|
|
770
971
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
972
|
+
if (pathMap[f]) {
|
|
973
|
+
const targetPath = path.join(agentPath, pathMap[f]);
|
|
974
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
975
|
+
await fs.move(filePath, targetPath, { overwrite: true });
|
|
976
|
+
} else if (f.startsWith('_system_') || f.startsWith('memory_')) {
|
|
977
|
+
// É um stub de diretório do export do GoClaw (ex: _system_dreaming_) ou um órfão esmagado. Ignoramos.
|
|
978
|
+
await fs.remove(filePath);
|
|
979
|
+
} else {
|
|
980
|
+
await fs.move(filePath, path.join(agentPath, f), { overwrite: true });
|
|
981
|
+
}
|
|
778
982
|
}
|
|
983
|
+
await fs.remove(contextDir);
|
|
779
984
|
}
|
|
780
|
-
}
|
|
985
|
+
} finally {
|
|
986
|
+
if (await fs.pathExists(tempTarPath)) {
|
|
987
|
+
await fs.remove(tempTarPath);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
781
991
|
|
|
782
992
|
pullCmd
|
|
783
993
|
.command("agents")
|
|
@@ -808,84 +1018,9 @@ pullCmd
|
|
|
808
1018
|
|
|
809
1019
|
for (const agent of agents) {
|
|
810
1020
|
const slug = agent.agent_key;
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export?sections=config,context_files,memory`;
|
|
814
|
-
const response = await axios.get(url, {
|
|
815
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
|
|
816
|
-
responseType: "stream"
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
const tempTarPath = path.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
820
|
-
|
|
821
|
-
try {
|
|
822
|
-
const writer = fs.createWriteStream(tempTarPath);
|
|
823
|
-
response.data.pipe(writer);
|
|
824
|
-
|
|
825
|
-
await new Promise((resolve, reject) => {
|
|
826
|
-
writer.on("finish", resolve);
|
|
827
|
-
writer.on("error", reject);
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
const agentPath = path.join(getWorkspaceRoot(), "agents", slug);
|
|
831
|
-
await fs.ensureDir(agentPath);
|
|
832
|
-
|
|
833
|
-
await tar.x({
|
|
834
|
-
file: tempTarPath,
|
|
835
|
-
cwd: agentPath,
|
|
836
|
-
strip: 0,
|
|
837
|
-
filter: (path) => {
|
|
838
|
-
return path === 'agent.json' || path.startsWith('context_files/') || path.startsWith('memory/') || path === 'MEMORY.md' || path === 'memory.md';
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
const contextDir = path.join(agentPath, "context_files");
|
|
843
|
-
if (await fs.pathExists(contextDir)) {
|
|
844
|
-
const contextFiles = await fs.readdir(contextDir);
|
|
845
|
-
for (const f of contextFiles) {
|
|
846
|
-
await fs.move(path.join(contextDir, f), path.join(agentPath, f), { overwrite: true });
|
|
847
|
-
}
|
|
848
|
-
await fs.remove(contextDir);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// Reconstruir ficheiros de memória a partir de JSONL
|
|
852
|
-
const memoryDir = path.join(agentPath, "memory");
|
|
853
|
-
if (await fs.pathExists(memoryDir)) {
|
|
854
|
-
const processJsonl = async (filePath: string) => {
|
|
855
|
-
if (!(await fs.pathExists(filePath))) return;
|
|
856
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
857
|
-
const lines = content.split('\n').filter(l => l.trim());
|
|
858
|
-
for (const line of lines) {
|
|
859
|
-
try {
|
|
860
|
-
const entry = JSON.parse(line);
|
|
861
|
-
if (entry.path && entry.content) {
|
|
862
|
-
const targetPath = path.join(agentPath, entry.path);
|
|
863
|
-
await fs.ensureDir(path.dirname(targetPath));
|
|
864
|
-
await fs.writeFile(targetPath, entry.content);
|
|
865
|
-
}
|
|
866
|
-
} catch (e) {}
|
|
867
|
-
}
|
|
868
|
-
await fs.remove(filePath);
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
await processJsonl(path.join(memoryDir, "global.jsonl"));
|
|
872
|
-
const usersDir = path.join(memoryDir, "users");
|
|
873
|
-
if (await fs.pathExists(usersDir)) {
|
|
874
|
-
const userFiles = await fs.readdir(usersDir);
|
|
875
|
-
for (const uf of userFiles) {
|
|
876
|
-
if (uf.endsWith(".jsonl")) {
|
|
877
|
-
await processJsonl(path.join(usersDir, uf));
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
await fs.remove(usersDir);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
} finally {
|
|
884
|
-
if (await fs.pathExists(tempTarPath)) {
|
|
885
|
-
await fs.remove(tempTarPath);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
1021
|
+
await pullAgent(slug, agent.id, config);
|
|
888
1022
|
}
|
|
1023
|
+
|
|
889
1024
|
console.log("✅ Pull de agentes concluído com sucesso!");
|
|
890
1025
|
} catch (error: any) {
|
|
891
1026
|
if (error.response && error.response.status) {
|
|
@@ -917,61 +1052,7 @@ pullCmd
|
|
|
917
1052
|
// PULL SKILLS INLINE
|
|
918
1053
|
console.log('\n--- [1/2] SKILLS ---');
|
|
919
1054
|
try {
|
|
920
|
-
|
|
921
|
-
const response = await axios.get(url, {
|
|
922
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
923
|
-
responseType: 'stream'
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
const tempTarPath = path.join(getWorkspaceRoot(), 'temp_skills.tar.gz');
|
|
927
|
-
const writer = fs.createWriteStream(tempTarPath);
|
|
928
|
-
response.data.pipe(writer);
|
|
929
|
-
|
|
930
|
-
await new Promise((resolve, reject) => {
|
|
931
|
-
writer.on('finish', resolve);
|
|
932
|
-
writer.on('error', reject);
|
|
933
|
-
});
|
|
934
|
-
|
|
935
|
-
await tar.x({ file: tempTarPath, cwd: getWorkspaceRoot() });
|
|
936
|
-
await fs.remove(tempTarPath);
|
|
937
|
-
|
|
938
|
-
const skillsListRes = await axios.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
939
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
const skills = skillsListRes.data.skills || [];
|
|
943
|
-
for (const skill of skills) {
|
|
944
|
-
try {
|
|
945
|
-
const isSystem = skill.is_system === true;
|
|
946
|
-
const targetFolder = isSystem ? path.join('system', skill.slug) : skill.slug;
|
|
947
|
-
|
|
948
|
-
if (isSystem) {
|
|
949
|
-
const originalPath = path.join(getWorkspaceRoot(), 'skills', skill.slug);
|
|
950
|
-
const newPath = path.join(getWorkspaceRoot(), 'skills', targetFolder);
|
|
951
|
-
if (await fs.pathExists(originalPath)) {
|
|
952
|
-
await fs.ensureDir(path.dirname(newPath));
|
|
953
|
-
await fs.move(originalPath, newPath, { overwrite: true });
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
const filesRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
958
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
959
|
-
});
|
|
960
|
-
|
|
961
|
-
const files = filesRes.data.files || [];
|
|
962
|
-
for (const file of files) {
|
|
963
|
-
if (file.isDir) continue;
|
|
964
|
-
const fileContentRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
965
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
966
|
-
});
|
|
967
|
-
const filePath = path.join(getWorkspaceRoot(), 'skills', targetFolder, file.path);
|
|
968
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
969
|
-
await fs.writeFile(filePath, fileContentRes.data.content || '');
|
|
970
|
-
}
|
|
971
|
-
} catch (fileErr: any) {
|
|
972
|
-
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
1055
|
+
await pullAllSkills(config);
|
|
975
1056
|
console.log('✅ Pull de skills concluído!');
|
|
976
1057
|
} catch (error: any) {
|
|
977
1058
|
console.error('❌ Erro durante o pull das skills:', error.message);
|
|
@@ -989,50 +1070,7 @@ pullCmd
|
|
|
989
1070
|
|
|
990
1071
|
for (const agent of agents) {
|
|
991
1072
|
const slug = agent.agent_key;
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
|
|
995
|
-
const response = await axios.get(url, {
|
|
996
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
997
|
-
responseType: 'stream'
|
|
998
|
-
});
|
|
999
|
-
|
|
1000
|
-
const tempTarPath = path.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
1001
|
-
|
|
1002
|
-
try {
|
|
1003
|
-
const writer = fs.createWriteStream(tempTarPath);
|
|
1004
|
-
response.data.pipe(writer);
|
|
1005
|
-
|
|
1006
|
-
await new Promise((resolve, reject) => {
|
|
1007
|
-
writer.on('finish', resolve);
|
|
1008
|
-
writer.on('error', reject);
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
const agentPath = path.join(getWorkspaceRoot(), 'agents', slug);
|
|
1012
|
-
await fs.ensureDir(agentPath);
|
|
1013
|
-
|
|
1014
|
-
await tar.x({
|
|
1015
|
-
file: tempTarPath,
|
|
1016
|
-
cwd: agentPath,
|
|
1017
|
-
strip: 0,
|
|
1018
|
-
filter: (path) => {
|
|
1019
|
-
return path === 'agent.json' || path.startsWith('context_files/');
|
|
1020
|
-
}
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
const contextDir = path.join(agentPath, 'context_files');
|
|
1024
|
-
if (await fs.pathExists(contextDir)) {
|
|
1025
|
-
const contextFiles = await fs.readdir(contextDir);
|
|
1026
|
-
for (const f of contextFiles) {
|
|
1027
|
-
await fs.move(path.join(contextDir, f), path.join(agentPath, f), { overwrite: true });
|
|
1028
|
-
}
|
|
1029
|
-
await fs.remove(contextDir);
|
|
1030
|
-
}
|
|
1031
|
-
} finally {
|
|
1032
|
-
if (await fs.pathExists(tempTarPath)) {
|
|
1033
|
-
await fs.remove(tempTarPath);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1073
|
+
await pullAgent(slug, agent.id, config);
|
|
1036
1074
|
}
|
|
1037
1075
|
console.log('✅ Pull de agentes concluído!');
|
|
1038
1076
|
} catch (error: any) {
|