@innominatum/agentforge-cli 1.1.9 → 1.1.23
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/templates/CLI_MANUAL.md +55 -82
package/dist/index.js
CHANGED
|
@@ -407,53 +407,112 @@ async function deployContextFiles(slug, config, resolvedId) {
|
|
|
407
407
|
throw new Error(`Agente não encontrado em agents/${slug}`);
|
|
408
408
|
}
|
|
409
409
|
const files = await fs_extra_1.default.readdir(agentPath);
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const memoryDirName = 'memory';
|
|
413
|
-
const contextFiles = files.filter(f => (f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.py')) &&
|
|
414
|
-
f !== 'README.md' &&
|
|
415
|
-
f !== 'agent.json' &&
|
|
416
|
-
!memoryFileNames.includes(f));
|
|
417
|
-
const hasMemoryDir = await fs_extra_1.default.pathExists(path_1.default.join(agentPath, memoryDirName));
|
|
418
|
-
const memoryFilesFound = files.filter(f => memoryFileNames.includes(f));
|
|
419
|
-
const hasMemory = hasMemoryDir || memoryFilesFound.length > 0;
|
|
420
|
-
if (contextFiles.length === 0 && !hasMemory) {
|
|
410
|
+
const itemsToSync = files.filter(f => f !== 'agent.json' && f !== 'README.md');
|
|
411
|
+
if (itemsToSync.length === 0) {
|
|
421
412
|
console.log(`Nenhum ficheiro de contexto ou memória encontrado para "${slug}".`);
|
|
422
413
|
return;
|
|
423
414
|
}
|
|
424
415
|
const tempExportDir = path_1.default.join(basePath, `temp_export_${slug}`);
|
|
425
|
-
const tempContextDir = path_1.default.join(tempExportDir, "context_files");
|
|
426
|
-
const tempMemoryDir = path_1.default.join(tempExportDir, "memory");
|
|
427
416
|
const tarPath = path_1.default.join(basePath, `temp_export_${slug}.tar.gz`);
|
|
417
|
+
// Guardar lista de ficheiros locais para pruning
|
|
418
|
+
const localFilePaths = [];
|
|
419
|
+
async function collectFilesRecursive(dir, baseDir) {
|
|
420
|
+
const results = [];
|
|
421
|
+
if (!(await fs_extra_1.default.pathExists(dir)))
|
|
422
|
+
return results;
|
|
423
|
+
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
424
|
+
for (const entry of entries) {
|
|
425
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
426
|
+
const relativePath = path_1.default.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
427
|
+
if (entry.isDirectory()) {
|
|
428
|
+
const subResults = await collectFilesRecursive(fullPath, baseDir);
|
|
429
|
+
results.push(...subResults);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
results.push(relativePath);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return results;
|
|
436
|
+
}
|
|
428
437
|
try {
|
|
429
|
-
const sections =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
438
|
+
const sections = new Set();
|
|
439
|
+
// Processar ficheiros/pastas da raiz do agente
|
|
440
|
+
for (const item of itemsToSync) {
|
|
441
|
+
const itemPath = path_1.default.join(agentPath, item);
|
|
442
|
+
const isDir = (await fs_extra_1.default.stat(itemPath)).isDirectory();
|
|
443
|
+
const section = "context_files";
|
|
444
|
+
sections.add(section);
|
|
445
|
+
const targetDir = path_1.default.join(tempExportDir, section);
|
|
446
|
+
await fs_extra_1.default.ensureDir(targetDir);
|
|
447
|
+
if (isDir) {
|
|
448
|
+
// Obter todos os ficheiros da pasta (ex: memory, _system)
|
|
449
|
+
// e achatá-os (flatten) com o nome da pasta como prefixo (ex: memory_arquivo.md)
|
|
450
|
+
// O GoClaw espera que os ficheiros de contexto não tenham pastas, mas sim prefixos achatados!
|
|
451
|
+
const subFiles = await fs_extra_1.default.readdir(itemPath);
|
|
452
|
+
for (const sub of subFiles) {
|
|
453
|
+
const subPath = path_1.default.join(itemPath, sub);
|
|
454
|
+
const isSubDir = (await fs_extra_1.default.stat(subPath)).isDirectory();
|
|
455
|
+
if (!isSubDir) {
|
|
456
|
+
const flatName = `${item}_${sub}`;
|
|
457
|
+
await fs_extra_1.default.copy(subPath, path_1.default.join(targetDir, flatName));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
await fs_extra_1.default.copy(itemPath, path_1.default.join(targetDir, item));
|
|
435
463
|
}
|
|
436
464
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
465
|
+
// Coletar ficheiros para pruning
|
|
466
|
+
const sectionDir = path_1.default.join(tempExportDir, "context_files");
|
|
467
|
+
const sectionEntries = await collectFilesRecursive(sectionDir, sectionDir);
|
|
468
|
+
for (const entry of sectionEntries) {
|
|
469
|
+
// O GoClaw retorna os caminhos das memórias com barras (memory/arquivo.md)
|
|
470
|
+
// Nós achatamos para o upload (memory_arquivo.md). Para o pruning não apagar ficheiros
|
|
471
|
+
// válidos acidentalmente, temos que re-mapear o nome achatado para a versão com barra.
|
|
472
|
+
let finalEntry = entry;
|
|
473
|
+
if (entry.startsWith("memory_")) {
|
|
474
|
+
finalEntry = entry.replace("memory_", "memory/");
|
|
475
|
+
}
|
|
476
|
+
else if (entry.startsWith("_system_")) {
|
|
477
|
+
finalEntry = entry.replace("_system_", "_system/");
|
|
443
478
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
479
|
+
localFilePaths.push(finalEntry);
|
|
480
|
+
}
|
|
481
|
+
const sectionsArray = Array.from(sections);
|
|
482
|
+
// --- HACK PARA APAGAR FICHEIROS FÍSICOS DO GOCLAW ---
|
|
483
|
+
// O GoClaw não apaga ficheiros do disco (context_files) quando os apagamos da DB (memory_documents).
|
|
484
|
+
// Isto faz com que os ficheiros voltem como "fantasmas" durante o pull.
|
|
485
|
+
// Solução: Injetar ficheiros vazios no tarball para os órfãos, forçando o /import a esmagá-los com 0 bytes!
|
|
486
|
+
try {
|
|
487
|
+
const docsUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents`;
|
|
488
|
+
const preDocsRes = await axios_1.default.get(docsUrl, {
|
|
489
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
490
|
+
});
|
|
491
|
+
const preDocs = preDocsRes.data || [];
|
|
492
|
+
for (const pDoc of preDocs) {
|
|
493
|
+
if (pDoc.path && !localFilePaths.includes(pDoc.path)) {
|
|
494
|
+
const flatGhost = pDoc.path.replace(/[\/\\]/g, '_');
|
|
495
|
+
const ghostPath = path_1.default.join(tempExportDir, "context_files", flatGhost);
|
|
496
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(ghostPath));
|
|
497
|
+
await fs_extra_1.default.writeFile(ghostPath, " "); // 1 byte soft-delete payload
|
|
498
|
+
if (!sectionsArray.includes("context_files")) {
|
|
499
|
+
sectionsArray.push("context_files");
|
|
500
|
+
}
|
|
501
|
+
}
|
|
447
502
|
}
|
|
448
503
|
}
|
|
504
|
+
catch (e) {
|
|
505
|
+
console.warn("Aviso: Falha ao procurar fantasmas para o tarball.", e.message);
|
|
506
|
+
}
|
|
449
507
|
await tar.c({
|
|
450
508
|
gzip: true,
|
|
451
509
|
file: tarPath,
|
|
452
510
|
cwd: tempExportDir
|
|
453
|
-
},
|
|
511
|
+
}, sectionsArray);
|
|
454
512
|
const form = new form_data_1.default();
|
|
455
513
|
form.append("file", fs_extra_1.default.createReadStream(tarPath));
|
|
456
|
-
|
|
514
|
+
// Upload dos ficheiros (aditivo)
|
|
515
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agentId}/import?include=${sectionsArray.join(",")}`;
|
|
457
516
|
await axios_1.default.post(url, form, {
|
|
458
517
|
headers: {
|
|
459
518
|
...form.getHeaders(),
|
|
@@ -461,7 +520,73 @@ async function deployContextFiles(slug, config, resolvedId) {
|
|
|
461
520
|
"X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
462
521
|
}
|
|
463
522
|
});
|
|
464
|
-
console.log(`✅ Upload
|
|
523
|
+
console.log(`✅ Upload de ficheiros e subpastas de contexto concluído com sucesso!`);
|
|
524
|
+
// Atualização forçada de memórias editadas (bypassa a proteção de overwrite do /import)
|
|
525
|
+
for (const localPath of localFilePaths) {
|
|
526
|
+
if (localPath.startsWith('memory/') && localPath.endsWith('.md')) {
|
|
527
|
+
try {
|
|
528
|
+
// O ficheiro físico no tempExportDir está achatado (memory_arquivo.md)
|
|
529
|
+
const flatFileName = localPath.replace("memory/", "memory_");
|
|
530
|
+
const content = await fs_extra_1.default.readFile(path_1.default.join(sectionDir, flatFileName), 'utf8');
|
|
531
|
+
// O endpoint usa {path...} portanto não podemos fazer URL encode das barras
|
|
532
|
+
const putUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents/${localPath}`;
|
|
533
|
+
await axios_1.default.put(putUrl, { content }, {
|
|
534
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
535
|
+
});
|
|
536
|
+
console.log(`✅ Edição de memória forçada com sucesso: ${localPath}`);
|
|
537
|
+
}
|
|
538
|
+
catch (putErr) {
|
|
539
|
+
console.warn(`⚠️ Aviso na edição de ${localPath}: O conteúdo pode não ter sido alterado. (${putErr.message})`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// --- Início do Pruning (Remover ficheiros órfãos do servidor) ---
|
|
544
|
+
try {
|
|
545
|
+
const documentsUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents`;
|
|
546
|
+
const docsResponse = await axios_1.default.get(documentsUrl, {
|
|
547
|
+
headers: {
|
|
548
|
+
Authorization: `Bearer ${config.goclaw.token}`,
|
|
549
|
+
"X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
const remoteDocs = docsResponse.data || [];
|
|
553
|
+
let deletedCount = 0;
|
|
554
|
+
for (const doc of remoteDocs) {
|
|
555
|
+
if (!doc.path)
|
|
556
|
+
continue;
|
|
557
|
+
// Verifica se o ficheiro remoto existe na nossa lista de ficheiros locais
|
|
558
|
+
if (!localFilePaths.includes(doc.path)) {
|
|
559
|
+
console.log(`🧹 Removendo memória órfã no servidor: ${doc.path}`);
|
|
560
|
+
try {
|
|
561
|
+
// O endpoint do GoClaw espera o caminho exato sem URL encode (route genérica {path...})
|
|
562
|
+
// E é mandatório passar o user_id dono do documento, senão dá erro 500.
|
|
563
|
+
const deleteUrl = `${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents/${doc.path}`;
|
|
564
|
+
await axios_1.default.delete(deleteUrl, {
|
|
565
|
+
headers: {
|
|
566
|
+
Authorization: `Bearer ${config.goclaw.token}`,
|
|
567
|
+
"X-GoClaw-User-Id": doc.user_id || config.goclaw.username || "system"
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
deletedCount++;
|
|
571
|
+
}
|
|
572
|
+
catch (delErr) {
|
|
573
|
+
const errorData = delErr.response?.data?.error || "";
|
|
574
|
+
if (delErr.response?.status === 500 && errorData.includes("not found")) {
|
|
575
|
+
console.log(`✅ ${doc.path} já estava removido da base de dados.`);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
console.warn(`⚠️ Não foi possível apagar ${doc.path}: ${delErr.message}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (deletedCount > 0) {
|
|
584
|
+
console.log(`✅ Pruning concluído: ${deletedCount} ficheiro(s) apagado(s) do GoClaw.`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
catch (pruneErr) {
|
|
588
|
+
console.warn(`⚠️ Aviso: Falha ao fazer pruning das memórias: ${pruneErr.message}`);
|
|
589
|
+
}
|
|
465
590
|
}
|
|
466
591
|
finally {
|
|
467
592
|
if (await fs_extra_1.default.pathExists(tempExportDir))
|
|
@@ -610,6 +735,89 @@ deployCmd
|
|
|
610
735
|
const pullCmd = program
|
|
611
736
|
.command("pull")
|
|
612
737
|
.description("Sincroniza entidades do GoClaw para o workspace local");
|
|
738
|
+
async function pullAllSkills(config) {
|
|
739
|
+
console.log("🧹 Limpando a pasta local de skills...");
|
|
740
|
+
await fs_extra_1.default.emptyDir(path_1.default.join(getWorkspaceRoot(), "skills"));
|
|
741
|
+
console.log("📥 Baixando skills do GoClaw...");
|
|
742
|
+
const url = `${config.goclaw.api_url}${config.goclaw.skills_export_endpoint || '/v1/skills/export'}`;
|
|
743
|
+
const response = await axios_1.default.get(url, {
|
|
744
|
+
headers: {
|
|
745
|
+
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
746
|
+
},
|
|
747
|
+
responseType: "stream"
|
|
748
|
+
});
|
|
749
|
+
const tempTarPath = path_1.default.join(getWorkspaceRoot(), "temp_skills.tar.gz");
|
|
750
|
+
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
751
|
+
response.data.pipe(writer);
|
|
752
|
+
await new Promise((resolve, reject) => {
|
|
753
|
+
writer.on("finish", resolve);
|
|
754
|
+
writer.on("error", reject);
|
|
755
|
+
});
|
|
756
|
+
console.log("📦 Extraindo skills para a pasta local...");
|
|
757
|
+
await tar.x({
|
|
758
|
+
file: tempTarPath,
|
|
759
|
+
cwd: getWorkspaceRoot()
|
|
760
|
+
});
|
|
761
|
+
await fs_extra_1.default.remove(tempTarPath);
|
|
762
|
+
console.log("📥 Baixando ficheiros de código das skills...");
|
|
763
|
+
const skillsListRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
764
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
765
|
+
});
|
|
766
|
+
const skills = skillsListRes.data.skills || [];
|
|
767
|
+
for (const skill of skills) {
|
|
768
|
+
try {
|
|
769
|
+
const isSystem = skill.is_system === true;
|
|
770
|
+
const targetFolder = isSystem ? path_1.default.join("system", skill.slug) : skill.slug;
|
|
771
|
+
if (isSystem) {
|
|
772
|
+
const originalPath = path_1.default.join(getWorkspaceRoot(), "skills", skill.slug);
|
|
773
|
+
const newPath = path_1.default.join(getWorkspaceRoot(), "skills", targetFolder);
|
|
774
|
+
if (await fs_extra_1.default.pathExists(originalPath)) {
|
|
775
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(newPath));
|
|
776
|
+
await fs_extra_1.default.move(originalPath, newPath, { overwrite: true });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const filesRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
780
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
781
|
+
});
|
|
782
|
+
const files = filesRes.data.files || [];
|
|
783
|
+
for (const file of files) {
|
|
784
|
+
if (file.isDir)
|
|
785
|
+
continue;
|
|
786
|
+
const fileContentRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
787
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
788
|
+
});
|
|
789
|
+
const filePath = path_1.default.join(getWorkspaceRoot(), "skills", targetFolder, file.path);
|
|
790
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
791
|
+
await fs_extra_1.default.writeFile(filePath, fileContentRes.data.content || "");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
catch (fileErr) {
|
|
795
|
+
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// Remover quaisquer skills fantasmas que o tarball tenha extraído
|
|
799
|
+
const validSlugs = new Set(skills.map((s) => s.is_system === true ? path_1.default.join("system", s.slug) : s.slug));
|
|
800
|
+
const skillsDir = path_1.default.join(getWorkspaceRoot(), "skills");
|
|
801
|
+
if (await fs_extra_1.default.pathExists(skillsDir)) {
|
|
802
|
+
const localItems = await fs_extra_1.default.readdir(skillsDir);
|
|
803
|
+
for (const item of localItems) {
|
|
804
|
+
if (item === "system") {
|
|
805
|
+
const systemDir = path_1.default.join(skillsDir, "system");
|
|
806
|
+
if (await fs_extra_1.default.pathExists(systemDir)) {
|
|
807
|
+
const systemItems = await fs_extra_1.default.readdir(systemDir);
|
|
808
|
+
for (const sysItem of systemItems) {
|
|
809
|
+
if (!validSlugs.has(path_1.default.join("system", sysItem))) {
|
|
810
|
+
await fs_extra_1.default.remove(path_1.default.join(systemDir, sysItem));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else if (!validSlugs.has(item)) {
|
|
816
|
+
await fs_extra_1.default.remove(path_1.default.join(skillsDir, item));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
613
821
|
pullCmd
|
|
614
822
|
.command("skills")
|
|
615
823
|
.description("Faz download do arquivo tar.gz de skills do GoClaw e extrai localmente")
|
|
@@ -623,100 +831,97 @@ pullCmd
|
|
|
623
831
|
console.log("❌ Pull cancelado pelo utilizador.");
|
|
624
832
|
return;
|
|
625
833
|
}
|
|
626
|
-
console.log("🧹 Limpando a pasta local de skills...");
|
|
627
|
-
await fs_extra_1.default.emptyDir(path_1.default.join(getWorkspaceRoot(), "skills"));
|
|
628
|
-
console.log("📥 Baixando skills do GoClaw...");
|
|
629
834
|
try {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
835
|
+
await pullAllSkills(config);
|
|
836
|
+
console.log("✅ Pull de skills concluído com sucesso! As skills foram atualizadas localmente.");
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
console.error("❌ Erro durante o pull das skills:");
|
|
840
|
+
if (error.response) {
|
|
841
|
+
console.error(`Status HTTP ${error.response.status}`);
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
console.error(error.message);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
async function pullAgent(slug, agentId, config) {
|
|
849
|
+
console.log(`📦 Baixando agente: ${slug}...`);
|
|
850
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agentId}/export`;
|
|
851
|
+
const response = await axios_1.default.get(url, {
|
|
852
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
|
|
853
|
+
responseType: "stream"
|
|
854
|
+
});
|
|
855
|
+
const tempTarPath = path_1.default.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
856
|
+
try {
|
|
638
857
|
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
639
858
|
response.data.pipe(writer);
|
|
640
859
|
await new Promise((resolve, reject) => {
|
|
641
860
|
writer.on("finish", resolve);
|
|
642
861
|
writer.on("error", reject);
|
|
643
862
|
});
|
|
644
|
-
|
|
863
|
+
const agentPath = path_1.default.join(getWorkspaceRoot(), "agents", slug);
|
|
864
|
+
if (await fs_extra_1.default.pathExists(agentPath)) {
|
|
865
|
+
await fs_extra_1.default.emptyDir(agentPath);
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
await fs_extra_1.default.ensureDir(agentPath);
|
|
869
|
+
}
|
|
870
|
+
// Obter os caminhos reais (com barras) da API para reverter o flattening do export
|
|
871
|
+
const docsRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/agents/${agentId}/memory/documents`, {
|
|
872
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
873
|
+
});
|
|
874
|
+
const pathMap = {};
|
|
875
|
+
(docsRes.data || []).forEach((d) => {
|
|
876
|
+
if (d.path) {
|
|
877
|
+
const flat = d.path.replace(/[\/\\]/g, '_');
|
|
878
|
+
pathMap[flat] = d.path;
|
|
879
|
+
}
|
|
880
|
+
});
|
|
645
881
|
await tar.x({
|
|
646
882
|
file: tempTarPath,
|
|
647
|
-
cwd:
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
883
|
+
cwd: agentPath,
|
|
884
|
+
strip: 0,
|
|
885
|
+
filter: (path) => {
|
|
886
|
+
return path === 'agent.json' || path.startsWith('context_files/');
|
|
887
|
+
}
|
|
653
888
|
});
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
if (
|
|
663
|
-
|
|
664
|
-
await fs_extra_1.default.
|
|
889
|
+
const contextDir = path_1.default.join(agentPath, "context_files");
|
|
890
|
+
if (await fs_extra_1.default.pathExists(contextDir)) {
|
|
891
|
+
const contextFiles = await fs_extra_1.default.readdir(contextDir);
|
|
892
|
+
for (const f of contextFiles) {
|
|
893
|
+
const filePath = path_1.default.join(contextDir, f);
|
|
894
|
+
const stat = await fs_extra_1.default.stat(filePath);
|
|
895
|
+
if (stat.isFile()) {
|
|
896
|
+
const content = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
897
|
+
if (content === " " || content === "") {
|
|
898
|
+
// Fantasma neutralizado pelo nosso script de deploy! Deita fora!
|
|
899
|
+
await fs_extra_1.default.remove(filePath);
|
|
900
|
+
continue;
|
|
665
901
|
}
|
|
666
902
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
for (const file of files) {
|
|
672
|
-
if (file.isDir)
|
|
673
|
-
continue;
|
|
674
|
-
const fileContentRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
675
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
676
|
-
});
|
|
677
|
-
const filePath = path_1.default.join(getWorkspaceRoot(), "skills", targetFolder, file.path);
|
|
678
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
679
|
-
await fs_extra_1.default.writeFile(filePath, fileContentRes.data.content || "");
|
|
903
|
+
if (pathMap[f]) {
|
|
904
|
+
const targetPath = path_1.default.join(agentPath, pathMap[f]);
|
|
905
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(targetPath));
|
|
906
|
+
await fs_extra_1.default.move(filePath, targetPath, { overwrite: true });
|
|
680
907
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
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
|
-
}
|
|
908
|
+
else if (f.startsWith('_system_') || f.startsWith('memory_')) {
|
|
909
|
+
// É um stub de diretório do export do GoClaw (ex: _system_dreaming_) ou um órfão esmagado. Ignoramos.
|
|
910
|
+
await fs_extra_1.default.remove(filePath);
|
|
702
911
|
}
|
|
703
|
-
else
|
|
704
|
-
await fs_extra_1.default.
|
|
912
|
+
else {
|
|
913
|
+
await fs_extra_1.default.move(filePath, path_1.default.join(agentPath, f), { overwrite: true });
|
|
705
914
|
}
|
|
706
915
|
}
|
|
916
|
+
await fs_extra_1.default.remove(contextDir);
|
|
707
917
|
}
|
|
708
|
-
console.log("✅ Pull concluído com sucesso! As skills foram atualizadas localmente.");
|
|
709
918
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
console.error(`Status HTTP ${error.response.status}`);
|
|
714
|
-
}
|
|
715
|
-
else {
|
|
716
|
-
console.error(error.message);
|
|
919
|
+
finally {
|
|
920
|
+
if (await fs_extra_1.default.pathExists(tempTarPath)) {
|
|
921
|
+
await fs_extra_1.default.remove(tempTarPath);
|
|
717
922
|
}
|
|
718
923
|
}
|
|
719
|
-
}
|
|
924
|
+
}
|
|
720
925
|
pullCmd
|
|
721
926
|
.command("agents")
|
|
722
927
|
.description("Faz download cirúrgico dos agentes (configuração e contexto)")
|
|
@@ -741,77 +946,7 @@ pullCmd
|
|
|
741
946
|
console.log(`Encontrados ${agents.length} agentes. Sincronizando...`);
|
|
742
947
|
for (const agent of agents) {
|
|
743
948
|
const slug = agent.agent_key;
|
|
744
|
-
|
|
745
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export?sections=config,context_files,memory`;
|
|
746
|
-
const response = await axios_1.default.get(url, {
|
|
747
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
|
|
748
|
-
responseType: "stream"
|
|
749
|
-
});
|
|
750
|
-
const tempTarPath = path_1.default.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
751
|
-
try {
|
|
752
|
-
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
753
|
-
response.data.pipe(writer);
|
|
754
|
-
await new Promise((resolve, reject) => {
|
|
755
|
-
writer.on("finish", resolve);
|
|
756
|
-
writer.on("error", reject);
|
|
757
|
-
});
|
|
758
|
-
const agentPath = path_1.default.join(getWorkspaceRoot(), "agents", slug);
|
|
759
|
-
await fs_extra_1.default.ensureDir(agentPath);
|
|
760
|
-
await tar.x({
|
|
761
|
-
file: tempTarPath,
|
|
762
|
-
cwd: agentPath,
|
|
763
|
-
strip: 0,
|
|
764
|
-
filter: (path) => {
|
|
765
|
-
return path === 'agent.json' || path.startsWith('context_files/') || path.startsWith('memory/') || path === 'MEMORY.md' || path === 'memory.md';
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
const contextDir = path_1.default.join(agentPath, "context_files");
|
|
769
|
-
if (await fs_extra_1.default.pathExists(contextDir)) {
|
|
770
|
-
const contextFiles = await fs_extra_1.default.readdir(contextDir);
|
|
771
|
-
for (const f of contextFiles) {
|
|
772
|
-
await fs_extra_1.default.move(path_1.default.join(contextDir, f), path_1.default.join(agentPath, f), { overwrite: true });
|
|
773
|
-
}
|
|
774
|
-
await fs_extra_1.default.remove(contextDir);
|
|
775
|
-
}
|
|
776
|
-
// Reconstruir ficheiros de memória a partir de JSONL
|
|
777
|
-
const memoryDir = path_1.default.join(agentPath, "memory");
|
|
778
|
-
if (await fs_extra_1.default.pathExists(memoryDir)) {
|
|
779
|
-
const processJsonl = async (filePath) => {
|
|
780
|
-
if (!(await fs_extra_1.default.pathExists(filePath)))
|
|
781
|
-
return;
|
|
782
|
-
const content = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
783
|
-
const lines = content.split('\n').filter(l => l.trim());
|
|
784
|
-
for (const line of lines) {
|
|
785
|
-
try {
|
|
786
|
-
const entry = JSON.parse(line);
|
|
787
|
-
if (entry.path && entry.content) {
|
|
788
|
-
const targetPath = path_1.default.join(agentPath, entry.path);
|
|
789
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(targetPath));
|
|
790
|
-
await fs_extra_1.default.writeFile(targetPath, entry.content);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
catch (e) { }
|
|
794
|
-
}
|
|
795
|
-
await fs_extra_1.default.remove(filePath);
|
|
796
|
-
};
|
|
797
|
-
await processJsonl(path_1.default.join(memoryDir, "global.jsonl"));
|
|
798
|
-
const usersDir = path_1.default.join(memoryDir, "users");
|
|
799
|
-
if (await fs_extra_1.default.pathExists(usersDir)) {
|
|
800
|
-
const userFiles = await fs_extra_1.default.readdir(usersDir);
|
|
801
|
-
for (const uf of userFiles) {
|
|
802
|
-
if (uf.endsWith(".jsonl")) {
|
|
803
|
-
await processJsonl(path_1.default.join(usersDir, uf));
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
await fs_extra_1.default.remove(usersDir);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
finally {
|
|
811
|
-
if (await fs_extra_1.default.pathExists(tempTarPath)) {
|
|
812
|
-
await fs_extra_1.default.remove(tempTarPath);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
949
|
+
await pullAgent(slug, agent.id, config);
|
|
815
950
|
}
|
|
816
951
|
console.log("✅ Pull de agentes concluído com sucesso!");
|
|
817
952
|
}
|
|
@@ -841,55 +976,7 @@ pullCmd
|
|
|
841
976
|
// PULL SKILLS INLINE
|
|
842
977
|
console.log('\n--- [1/2] SKILLS ---');
|
|
843
978
|
try {
|
|
844
|
-
|
|
845
|
-
const response = await axios_1.default.get(url, {
|
|
846
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
847
|
-
responseType: 'stream'
|
|
848
|
-
});
|
|
849
|
-
const tempTarPath = path_1.default.join(getWorkspaceRoot(), 'temp_skills.tar.gz');
|
|
850
|
-
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
851
|
-
response.data.pipe(writer);
|
|
852
|
-
await new Promise((resolve, reject) => {
|
|
853
|
-
writer.on('finish', resolve);
|
|
854
|
-
writer.on('error', reject);
|
|
855
|
-
});
|
|
856
|
-
await tar.x({ file: tempTarPath, cwd: getWorkspaceRoot() });
|
|
857
|
-
await fs_extra_1.default.remove(tempTarPath);
|
|
858
|
-
const skillsListRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
859
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
860
|
-
});
|
|
861
|
-
const skills = skillsListRes.data.skills || [];
|
|
862
|
-
for (const skill of skills) {
|
|
863
|
-
try {
|
|
864
|
-
const isSystem = skill.is_system === true;
|
|
865
|
-
const targetFolder = isSystem ? path_1.default.join('system', skill.slug) : skill.slug;
|
|
866
|
-
if (isSystem) {
|
|
867
|
-
const originalPath = path_1.default.join(getWorkspaceRoot(), 'skills', skill.slug);
|
|
868
|
-
const newPath = path_1.default.join(getWorkspaceRoot(), 'skills', targetFolder);
|
|
869
|
-
if (await fs_extra_1.default.pathExists(originalPath)) {
|
|
870
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(newPath));
|
|
871
|
-
await fs_extra_1.default.move(originalPath, newPath, { overwrite: true });
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
const filesRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
875
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
876
|
-
});
|
|
877
|
-
const files = filesRes.data.files || [];
|
|
878
|
-
for (const file of files) {
|
|
879
|
-
if (file.isDir)
|
|
880
|
-
continue;
|
|
881
|
-
const fileContentRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
882
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
883
|
-
});
|
|
884
|
-
const filePath = path_1.default.join(getWorkspaceRoot(), 'skills', targetFolder, file.path);
|
|
885
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
886
|
-
await fs_extra_1.default.writeFile(filePath, fileContentRes.data.content || '');
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
catch (fileErr) {
|
|
890
|
-
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
979
|
+
await pullAllSkills(config);
|
|
893
980
|
console.log('✅ Pull de skills concluído!');
|
|
894
981
|
}
|
|
895
982
|
catch (error) {
|
|
@@ -905,44 +992,7 @@ pullCmd
|
|
|
905
992
|
console.log(`Encontrados ${agents.length} agentes. Sincronizando...`);
|
|
906
993
|
for (const agent of agents) {
|
|
907
994
|
const slug = agent.agent_key;
|
|
908
|
-
|
|
909
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
|
|
910
|
-
const response = await axios_1.default.get(url, {
|
|
911
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
912
|
-
responseType: 'stream'
|
|
913
|
-
});
|
|
914
|
-
const tempTarPath = path_1.default.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
915
|
-
try {
|
|
916
|
-
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
917
|
-
response.data.pipe(writer);
|
|
918
|
-
await new Promise((resolve, reject) => {
|
|
919
|
-
writer.on('finish', resolve);
|
|
920
|
-
writer.on('error', reject);
|
|
921
|
-
});
|
|
922
|
-
const agentPath = path_1.default.join(getWorkspaceRoot(), 'agents', slug);
|
|
923
|
-
await fs_extra_1.default.ensureDir(agentPath);
|
|
924
|
-
await tar.x({
|
|
925
|
-
file: tempTarPath,
|
|
926
|
-
cwd: agentPath,
|
|
927
|
-
strip: 0,
|
|
928
|
-
filter: (path) => {
|
|
929
|
-
return path === 'agent.json' || path.startsWith('context_files/');
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
const contextDir = path_1.default.join(agentPath, 'context_files');
|
|
933
|
-
if (await fs_extra_1.default.pathExists(contextDir)) {
|
|
934
|
-
const contextFiles = await fs_extra_1.default.readdir(contextDir);
|
|
935
|
-
for (const f of contextFiles) {
|
|
936
|
-
await fs_extra_1.default.move(path_1.default.join(contextDir, f), path_1.default.join(agentPath, f), { overwrite: true });
|
|
937
|
-
}
|
|
938
|
-
await fs_extra_1.default.remove(contextDir);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
finally {
|
|
942
|
-
if (await fs_extra_1.default.pathExists(tempTarPath)) {
|
|
943
|
-
await fs_extra_1.default.remove(tempTarPath);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
995
|
+
await pullAgent(slug, agent.id, config);
|
|
946
996
|
}
|
|
947
997
|
console.log('✅ Pull de agentes concluído!');
|
|
948
998
|
}
|