@innominatum/agentforge-cli 1.1.3 → 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 +443 -292
- package/package.json +1 -1
- package/src/index.ts +503 -356
- package/templates/CLI_MANUAL.md +11 -1
package/src/index.ts
CHANGED
|
@@ -314,9 +314,106 @@ const deployCmd = program
|
|
|
314
314
|
.command("deploy")
|
|
315
315
|
.description("Faz o deploy de entidades para a plataforma GoClaw");
|
|
316
316
|
|
|
317
|
+
async function deploySkill(slug: string, config: any, basePath: string) {
|
|
318
|
+
const skillPath = path.join(basePath, "skills", slug);
|
|
319
|
+
const exportsPath = path.join(basePath, "exports");
|
|
320
|
+
const safeSlug = slug.replace(/[\\\/]/g, '_');
|
|
321
|
+
const zipPath = path.join(exportsPath, `${safeSlug}.zip`);
|
|
322
|
+
|
|
323
|
+
if (!(await fs.pathExists(skillPath))) {
|
|
324
|
+
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await fs.ensureDir(exportsPath);
|
|
329
|
+
const zip = new AdmZip();
|
|
330
|
+
zip.addLocalFolder(skillPath, "");
|
|
331
|
+
zip.writeZip(zipPath);
|
|
332
|
+
|
|
333
|
+
console.log(`🚀 Fazendo upload da skill "${slug}" para o GoClaw...`);
|
|
334
|
+
const form = new FormData();
|
|
335
|
+
form.append("file", fs.createReadStream(zipPath));
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const url = `${config.goclaw.api_url}/v1/skills/upload`;
|
|
339
|
+
const response = await axios.post(url, form, {
|
|
340
|
+
headers: {
|
|
341
|
+
...form.getHeaders(),
|
|
342
|
+
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
const data = response.data;
|
|
346
|
+
if (data && data.version) {
|
|
347
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados (versão ${data.version}).`);
|
|
348
|
+
} else {
|
|
349
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados.`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Sincronizar metadados (visibility, description, tags, etc)
|
|
353
|
+
const skillsListRes = await axios.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
354
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
355
|
+
});
|
|
356
|
+
const remoteSkill = skillsListRes.data.skills?.find((s: any) => s.slug === slug);
|
|
357
|
+
|
|
358
|
+
if (remoteSkill) {
|
|
359
|
+
const metadataPath = path.join(skillPath, "metadata.json");
|
|
360
|
+
if (await fs.pathExists(metadataPath)) {
|
|
361
|
+
console.log(`🚀 Sincronizando metadados da skill "${slug}"...`);
|
|
362
|
+
const metadata = await fs.readJson(metadataPath);
|
|
363
|
+
|
|
364
|
+
// Remover campos que não devem ser enviados no PUT
|
|
365
|
+
const payload = { ...metadata };
|
|
366
|
+
delete payload.id;
|
|
367
|
+
delete payload.slug;
|
|
368
|
+
delete payload.name;
|
|
369
|
+
|
|
370
|
+
await axios.put(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}`, payload, {
|
|
371
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
372
|
+
});
|
|
373
|
+
console.log(`✅ Metadados sincronizados com sucesso.`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Sincronizar permissões (grants)
|
|
377
|
+
const grantsPath = path.join(skillPath, "grants.jsonl");
|
|
378
|
+
if (await fs.pathExists(grantsPath)) {
|
|
379
|
+
console.log(`🚀 Sincronizando permissões (grants) da skill "${slug}"...`);
|
|
380
|
+
const grantsContent = await fs.readFile(grantsPath, 'utf8');
|
|
381
|
+
const lines = grantsContent.split('\n').filter(l => l.trim());
|
|
382
|
+
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
try {
|
|
385
|
+
const grant = JSON.parse(line);
|
|
386
|
+
if (grant.agent_key) {
|
|
387
|
+
const agentId = await resolveAgentId(grant.agent_key, config);
|
|
388
|
+
if (agentId) {
|
|
389
|
+
await axios.post(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}/grants/agent`,
|
|
390
|
+
{ agent_id: agentId, version: grant.pinned_version || null },
|
|
391
|
+
{ headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" } }
|
|
392
|
+
);
|
|
393
|
+
console.log(` ➕ Permissão concedida ao agente: ${grant.agent_key}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch (e: any) {
|
|
397
|
+
console.warn(` ⚠️ Falha ao conceder permissão: ${e.response?.data?.error || e.message}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
console.log(`✅ Permissões sincronizadas.`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
} catch (error: any) {
|
|
405
|
+
console.error(`❌ Erro no deploy da skill "${slug}":`);
|
|
406
|
+
if (error.response) {
|
|
407
|
+
console.error(error.response.data);
|
|
408
|
+
} else {
|
|
409
|
+
console.error(error.message);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
317
414
|
deployCmd
|
|
318
415
|
.command("skill <slug>")
|
|
319
|
-
.description("Faz
|
|
416
|
+
.description("Faz build e upload automático de uma skill para o GoClaw")
|
|
320
417
|
.action(async (slug: string) => {
|
|
321
418
|
const config = await getConfig();
|
|
322
419
|
if (!config.goclaw || !config.goclaw.token) {
|
|
@@ -325,46 +422,7 @@ deployCmd
|
|
|
325
422
|
}
|
|
326
423
|
|
|
327
424
|
const basePath = getWorkspaceRoot();
|
|
328
|
-
|
|
329
|
-
const exportsPath = path.join(basePath, "exports");
|
|
330
|
-
const zipPath = path.join(exportsPath, `${slug}.zip`);
|
|
331
|
-
|
|
332
|
-
if (!(await fs.pathExists(skillPath))) {
|
|
333
|
-
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
334
|
-
process.exit(1);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
await fs.ensureDir(exportsPath);
|
|
338
|
-
const zip = new AdmZip();
|
|
339
|
-
zip.addLocalFolder(skillPath, "");
|
|
340
|
-
zip.writeZip(zipPath);
|
|
341
|
-
|
|
342
|
-
console.log(`✅ Build concluído: ${slug}.zip preparado para envio.`);
|
|
343
|
-
|
|
344
|
-
console.log(`🚀 Fazendo upload para o GoClaw...`);
|
|
345
|
-
const form = new FormData();
|
|
346
|
-
form.append("file", fs.createReadStream(zipPath));
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
const url = `${config.goclaw.api_url}/v1/skills/upload`;
|
|
350
|
-
const response = await axios.post(url, form, {
|
|
351
|
-
headers: {
|
|
352
|
-
...form.getHeaders(),
|
|
353
|
-
Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
const data = response.data;
|
|
357
|
-
if (data && data.version) {
|
|
358
|
-
console.log(`📌 Skill atualizada para a versão ${data.version}.`);
|
|
359
|
-
}
|
|
360
|
-
} catch (error: any) {
|
|
361
|
-
console.error("❌ Erro durante o deploy:");
|
|
362
|
-
if (error.response) {
|
|
363
|
-
console.error(error.response.data);
|
|
364
|
-
} else {
|
|
365
|
-
console.error(error.message);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
425
|
+
await deploySkill(slug, config, basePath);
|
|
368
426
|
});
|
|
369
427
|
|
|
370
428
|
async function deployContextFiles(slug: string, config: any, resolvedId?: string | null) {
|
|
@@ -376,66 +434,121 @@ async function deployContextFiles(slug: string, config: any, resolvedId?: string
|
|
|
376
434
|
}
|
|
377
435
|
|
|
378
436
|
const files = await fs.readdir(agentPath);
|
|
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;
|
|
437
|
+
const itemsToSync = files.filter(f => f !== 'agent.json' && f !== 'README.md');
|
|
394
438
|
|
|
395
|
-
if (
|
|
439
|
+
if (itemsToSync.length === 0) {
|
|
396
440
|
console.log(`Nenhum ficheiro de contexto ou memória encontrado para "${slug}".`);
|
|
397
441
|
return;
|
|
398
442
|
}
|
|
399
443
|
|
|
400
444
|
const tempExportDir = path.join(basePath, `temp_export_${slug}`);
|
|
401
|
-
const tempContextDir = path.join(tempExportDir, "context_files");
|
|
402
|
-
const tempMemoryDir = path.join(tempExportDir, "memory");
|
|
403
445
|
const tarPath = path.join(basePath, `temp_export_${slug}.tar.gz`);
|
|
404
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
|
+
|
|
405
466
|
try {
|
|
406
|
-
const sections
|
|
467
|
+
const sections = new Set<string>();
|
|
407
468
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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));
|
|
413
495
|
}
|
|
414
496
|
}
|
|
415
497
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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/");
|
|
422
510
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
+
}
|
|
426
536
|
}
|
|
537
|
+
} catch (e: any) {
|
|
538
|
+
console.warn("Aviso: Falha ao procurar fantasmas para o tarball.", e.message);
|
|
427
539
|
}
|
|
428
540
|
|
|
429
541
|
await tar.c({
|
|
430
542
|
gzip: true,
|
|
431
543
|
file: tarPath,
|
|
432
544
|
cwd: tempExportDir
|
|
433
|
-
},
|
|
545
|
+
}, sectionsArray);
|
|
434
546
|
|
|
435
547
|
const form = new FormData();
|
|
436
548
|
form.append("file", fs.createReadStream(tarPath));
|
|
437
549
|
|
|
438
|
-
|
|
550
|
+
// Upload dos ficheiros (aditivo)
|
|
551
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agentId}/import?include=${sectionsArray.join(",")}`;
|
|
439
552
|
await axios.post(url, form, {
|
|
440
553
|
headers: {
|
|
441
554
|
...form.getHeaders(),
|
|
@@ -444,7 +557,76 @@ async function deployContextFiles(slug: string, config: any, resolvedId?: string
|
|
|
444
557
|
}
|
|
445
558
|
});
|
|
446
559
|
|
|
447
|
-
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
|
+
|
|
448
630
|
} finally {
|
|
449
631
|
if (await fs.pathExists(tempExportDir)) await fs.remove(tempExportDir);
|
|
450
632
|
if (await fs.pathExists(tarPath)) await fs.remove(tarPath);
|
|
@@ -519,8 +701,45 @@ deployCmd
|
|
|
519
701
|
await deployAgent(slug, config);
|
|
520
702
|
});
|
|
521
703
|
|
|
704
|
+
|
|
705
|
+
async function deployAllAgents(config: any, basePath: string) {
|
|
706
|
+
const agentsDir = path.join(basePath, "agents");
|
|
707
|
+
if (await fs.pathExists(agentsDir)) {
|
|
708
|
+
const agents = await fs.readdir(agentsDir);
|
|
709
|
+
console.log(`🚀 Iniciando deploy em lote de ${agents.length} agentes...`);
|
|
710
|
+
for (const slug of agents) {
|
|
711
|
+
const agentPath = path.join(agentsDir, slug);
|
|
712
|
+
if ((await fs.stat(agentPath)).isDirectory()) {
|
|
713
|
+
await deployAgent(slug, config);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
console.log("Nenhum agente encontrado em agents/.");
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async function deployAllSkills(config: any, basePath: string) {
|
|
722
|
+
const skillsDir = path.join(basePath, "skills");
|
|
723
|
+
if (await fs.pathExists(skillsDir)) {
|
|
724
|
+
const skills = await fs.readdir(skillsDir);
|
|
725
|
+
console.log(`🚀 Iniciando deploy em lote de skills...`);
|
|
726
|
+
for (const item of skills) {
|
|
727
|
+
const itemPath = path.join(skillsDir, item);
|
|
728
|
+
if ((await fs.stat(itemPath)).isDirectory()) {
|
|
729
|
+
if (item === "system") {
|
|
730
|
+
console.log("⏩ Ignorando pasta 'system/' (skills nativas do GoClaw são apenas de leitura)");
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
await deploySkill(item, config, basePath);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
} else {
|
|
737
|
+
console.log("Nenhuma skill encontrada em skills/.");
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
522
741
|
deployCmd
|
|
523
|
-
.command("
|
|
742
|
+
.command("agents")
|
|
524
743
|
.description("Faz deploy de todos os agentes do workspace")
|
|
525
744
|
.action(async () => {
|
|
526
745
|
const config = await getConfig();
|
|
@@ -528,32 +747,137 @@ deployCmd
|
|
|
528
747
|
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
529
748
|
process.exit(1);
|
|
530
749
|
}
|
|
531
|
-
|
|
532
750
|
const basePath = getWorkspaceRoot();
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
751
|
+
await deployAllAgents(config, basePath);
|
|
752
|
+
console.log("🏁 Deploy de agentes concluído!");
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
deployCmd
|
|
756
|
+
.command("skills")
|
|
757
|
+
.description("Faz deploy de todas as skills do workspace")
|
|
758
|
+
.action(async () => {
|
|
759
|
+
const config = await getConfig();
|
|
760
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
761
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
762
|
+
process.exit(1);
|
|
538
763
|
}
|
|
764
|
+
const basePath = getWorkspaceRoot();
|
|
765
|
+
await deployAllSkills(config, basePath);
|
|
766
|
+
console.log("🏁 Deploy de skills concluído!");
|
|
767
|
+
});
|
|
539
768
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
769
|
+
deployCmd
|
|
770
|
+
.command("all")
|
|
771
|
+
.description("Faz deploy de todos os agentes e skills do workspace")
|
|
772
|
+
.action(async () => {
|
|
773
|
+
const config = await getConfig();
|
|
774
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
775
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
776
|
+
process.exit(1);
|
|
548
777
|
}
|
|
778
|
+
|
|
779
|
+
const basePath = getWorkspaceRoot();
|
|
780
|
+
await deployAllAgents(config, basePath);
|
|
781
|
+
await deployAllSkills(config, basePath);
|
|
549
782
|
|
|
550
|
-
console.log("🏁 Deploy
|
|
783
|
+
console.log("🏁 Deploy completo (agentes e skills) concluído!");
|
|
551
784
|
});
|
|
552
785
|
|
|
553
786
|
const pullCmd = program
|
|
554
787
|
.command("pull")
|
|
555
788
|
.description("Sincroniza entidades do GoClaw para o workspace local");
|
|
556
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
|
+
|
|
557
881
|
pullCmd
|
|
558
882
|
.command("skills")
|
|
559
883
|
.description("Faz download do arquivo tar.gz de skills do GoClaw e extrai localmente")
|
|
@@ -569,106 +893,101 @@ pullCmd
|
|
|
569
893
|
return;
|
|
570
894
|
}
|
|
571
895
|
|
|
572
|
-
console.log("🧹 Limpando a pasta local de skills...");
|
|
573
|
-
await fs.emptyDir(path.join(getWorkspaceRoot(), "skills"));
|
|
574
|
-
|
|
575
|
-
console.log("📥 Baixando skills do GoClaw...");
|
|
576
896
|
try {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
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
|
+
});
|
|
584
908
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
+
});
|
|
588
917
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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);
|
|
593
923
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
});
|
|
599
|
-
await fs.remove(tempTarPath);
|
|
924
|
+
await new Promise((resolve, reject) => {
|
|
925
|
+
writer.on("finish", resolve);
|
|
926
|
+
writer.on("error", reject);
|
|
927
|
+
});
|
|
600
928
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
for (const skill of skills) {
|
|
608
|
-
try {
|
|
609
|
-
const isSystem = skill.is_system === true;
|
|
610
|
-
const targetFolder = isSystem ? path.join("system", skill.slug) : skill.slug;
|
|
611
|
-
|
|
612
|
-
if (isSystem) {
|
|
613
|
-
const originalPath = path.join(getWorkspaceRoot(), "skills", skill.slug);
|
|
614
|
-
const newPath = path.join(getWorkspaceRoot(), "skills", targetFolder);
|
|
615
|
-
if (await fs.pathExists(originalPath)) {
|
|
616
|
-
await fs.ensureDir(path.dirname(newPath));
|
|
617
|
-
await fs.move(originalPath, newPath, { overwrite: true });
|
|
618
|
-
}
|
|
619
|
-
}
|
|
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
|
+
}
|
|
620
935
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
});
|
|
631
|
-
const filePath = path.join(getWorkspaceRoot(), "skills", targetFolder, file.path);
|
|
632
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
633
|
-
await fs.writeFile(filePath, fileContentRes.data.content || "");
|
|
634
|
-
}
|
|
635
|
-
} catch (fileErr: any) {
|
|
636
|
-
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
637
|
-
}
|
|
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;
|
|
638
945
|
}
|
|
946
|
+
});
|
|
639
947
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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;
|
|
658
969
|
}
|
|
659
970
|
}
|
|
660
|
-
}
|
|
661
971
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
+
}
|
|
669
982
|
}
|
|
983
|
+
await fs.remove(contextDir);
|
|
670
984
|
}
|
|
671
|
-
}
|
|
985
|
+
} finally {
|
|
986
|
+
if (await fs.pathExists(tempTarPath)) {
|
|
987
|
+
await fs.remove(tempTarPath);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
672
991
|
|
|
673
992
|
pullCmd
|
|
674
993
|
.command("agents")
|
|
@@ -699,84 +1018,9 @@ pullCmd
|
|
|
699
1018
|
|
|
700
1019
|
for (const agent of agents) {
|
|
701
1020
|
const slug = agent.agent_key;
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export?sections=config,context_files,memory`;
|
|
705
|
-
const response = await axios.get(url, {
|
|
706
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
|
|
707
|
-
responseType: "stream"
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
const tempTarPath = path.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
711
|
-
|
|
712
|
-
try {
|
|
713
|
-
const writer = fs.createWriteStream(tempTarPath);
|
|
714
|
-
response.data.pipe(writer);
|
|
715
|
-
|
|
716
|
-
await new Promise((resolve, reject) => {
|
|
717
|
-
writer.on("finish", resolve);
|
|
718
|
-
writer.on("error", reject);
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
const agentPath = path.join(getWorkspaceRoot(), "agents", slug);
|
|
722
|
-
await fs.ensureDir(agentPath);
|
|
723
|
-
|
|
724
|
-
await tar.x({
|
|
725
|
-
file: tempTarPath,
|
|
726
|
-
cwd: agentPath,
|
|
727
|
-
strip: 0,
|
|
728
|
-
filter: (path) => {
|
|
729
|
-
return path === 'agent.json' || path.startsWith('context_files/') || path.startsWith('memory/') || path === 'MEMORY.md' || path === 'memory.md';
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
const contextDir = path.join(agentPath, "context_files");
|
|
734
|
-
if (await fs.pathExists(contextDir)) {
|
|
735
|
-
const contextFiles = await fs.readdir(contextDir);
|
|
736
|
-
for (const f of contextFiles) {
|
|
737
|
-
await fs.move(path.join(contextDir, f), path.join(agentPath, f), { overwrite: true });
|
|
738
|
-
}
|
|
739
|
-
await fs.remove(contextDir);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// Reconstruir ficheiros de memória a partir de JSONL
|
|
743
|
-
const memoryDir = path.join(agentPath, "memory");
|
|
744
|
-
if (await fs.pathExists(memoryDir)) {
|
|
745
|
-
const processJsonl = async (filePath: string) => {
|
|
746
|
-
if (!(await fs.pathExists(filePath))) return;
|
|
747
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
748
|
-
const lines = content.split('\n').filter(l => l.trim());
|
|
749
|
-
for (const line of lines) {
|
|
750
|
-
try {
|
|
751
|
-
const entry = JSON.parse(line);
|
|
752
|
-
if (entry.path && entry.content) {
|
|
753
|
-
const targetPath = path.join(agentPath, entry.path);
|
|
754
|
-
await fs.ensureDir(path.dirname(targetPath));
|
|
755
|
-
await fs.writeFile(targetPath, entry.content);
|
|
756
|
-
}
|
|
757
|
-
} catch (e) {}
|
|
758
|
-
}
|
|
759
|
-
await fs.remove(filePath);
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
await processJsonl(path.join(memoryDir, "global.jsonl"));
|
|
763
|
-
const usersDir = path.join(memoryDir, "users");
|
|
764
|
-
if (await fs.pathExists(usersDir)) {
|
|
765
|
-
const userFiles = await fs.readdir(usersDir);
|
|
766
|
-
for (const uf of userFiles) {
|
|
767
|
-
if (uf.endsWith(".jsonl")) {
|
|
768
|
-
await processJsonl(path.join(usersDir, uf));
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
await fs.remove(usersDir);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
} finally {
|
|
775
|
-
if (await fs.pathExists(tempTarPath)) {
|
|
776
|
-
await fs.remove(tempTarPath);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
1021
|
+
await pullAgent(slug, agent.id, config);
|
|
779
1022
|
}
|
|
1023
|
+
|
|
780
1024
|
console.log("✅ Pull de agentes concluído com sucesso!");
|
|
781
1025
|
} catch (error: any) {
|
|
782
1026
|
if (error.response && error.response.status) {
|
|
@@ -808,61 +1052,7 @@ pullCmd
|
|
|
808
1052
|
// PULL SKILLS INLINE
|
|
809
1053
|
console.log('\n--- [1/2] SKILLS ---');
|
|
810
1054
|
try {
|
|
811
|
-
|
|
812
|
-
const response = await axios.get(url, {
|
|
813
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
814
|
-
responseType: 'stream'
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
const tempTarPath = path.join(getWorkspaceRoot(), 'temp_skills.tar.gz');
|
|
818
|
-
const writer = fs.createWriteStream(tempTarPath);
|
|
819
|
-
response.data.pipe(writer);
|
|
820
|
-
|
|
821
|
-
await new Promise((resolve, reject) => {
|
|
822
|
-
writer.on('finish', resolve);
|
|
823
|
-
writer.on('error', reject);
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
await tar.x({ file: tempTarPath, cwd: getWorkspaceRoot() });
|
|
827
|
-
await fs.remove(tempTarPath);
|
|
828
|
-
|
|
829
|
-
const skillsListRes = await axios.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
830
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
const skills = skillsListRes.data.skills || [];
|
|
834
|
-
for (const skill of skills) {
|
|
835
|
-
try {
|
|
836
|
-
const isSystem = skill.is_system === true;
|
|
837
|
-
const targetFolder = isSystem ? path.join('system', skill.slug) : skill.slug;
|
|
838
|
-
|
|
839
|
-
if (isSystem) {
|
|
840
|
-
const originalPath = path.join(getWorkspaceRoot(), 'skills', skill.slug);
|
|
841
|
-
const newPath = path.join(getWorkspaceRoot(), 'skills', targetFolder);
|
|
842
|
-
if (await fs.pathExists(originalPath)) {
|
|
843
|
-
await fs.ensureDir(path.dirname(newPath));
|
|
844
|
-
await fs.move(originalPath, newPath, { overwrite: true });
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const filesRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
849
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
const files = filesRes.data.files || [];
|
|
853
|
-
for (const file of files) {
|
|
854
|
-
if (file.isDir) continue;
|
|
855
|
-
const fileContentRes = await axios.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
856
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
857
|
-
});
|
|
858
|
-
const filePath = path.join(getWorkspaceRoot(), 'skills', targetFolder, file.path);
|
|
859
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
860
|
-
await fs.writeFile(filePath, fileContentRes.data.content || '');
|
|
861
|
-
}
|
|
862
|
-
} catch (fileErr: any) {
|
|
863
|
-
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
1055
|
+
await pullAllSkills(config);
|
|
866
1056
|
console.log('✅ Pull de skills concluído!');
|
|
867
1057
|
} catch (error: any) {
|
|
868
1058
|
console.error('❌ Erro durante o pull das skills:', error.message);
|
|
@@ -880,50 +1070,7 @@ pullCmd
|
|
|
880
1070
|
|
|
881
1071
|
for (const agent of agents) {
|
|
882
1072
|
const slug = agent.agent_key;
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
|
|
886
|
-
const response = await axios.get(url, {
|
|
887
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
888
|
-
responseType: 'stream'
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
const tempTarPath = path.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
892
|
-
|
|
893
|
-
try {
|
|
894
|
-
const writer = fs.createWriteStream(tempTarPath);
|
|
895
|
-
response.data.pipe(writer);
|
|
896
|
-
|
|
897
|
-
await new Promise((resolve, reject) => {
|
|
898
|
-
writer.on('finish', resolve);
|
|
899
|
-
writer.on('error', reject);
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
const agentPath = path.join(getWorkspaceRoot(), 'agents', slug);
|
|
903
|
-
await fs.ensureDir(agentPath);
|
|
904
|
-
|
|
905
|
-
await tar.x({
|
|
906
|
-
file: tempTarPath,
|
|
907
|
-
cwd: agentPath,
|
|
908
|
-
strip: 0,
|
|
909
|
-
filter: (path) => {
|
|
910
|
-
return path === 'agent.json' || path.startsWith('context_files/');
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
const contextDir = path.join(agentPath, 'context_files');
|
|
915
|
-
if (await fs.pathExists(contextDir)) {
|
|
916
|
-
const contextFiles = await fs.readdir(contextDir);
|
|
917
|
-
for (const f of contextFiles) {
|
|
918
|
-
await fs.move(path.join(contextDir, f), path.join(agentPath, f), { overwrite: true });
|
|
919
|
-
}
|
|
920
|
-
await fs.remove(contextDir);
|
|
921
|
-
}
|
|
922
|
-
} finally {
|
|
923
|
-
if (await fs.pathExists(tempTarPath)) {
|
|
924
|
-
await fs.remove(tempTarPath);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
1073
|
+
await pullAgent(slug, agent.id, config);
|
|
927
1074
|
}
|
|
928
1075
|
console.log('✅ Pull de agentes concluído!');
|
|
929
1076
|
} catch (error: any) {
|