@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/dist/index.js
CHANGED
|
@@ -301,29 +301,20 @@ async function resolveAgentId(slug, config) {
|
|
|
301
301
|
const deployCmd = program
|
|
302
302
|
.command("deploy")
|
|
303
303
|
.description("Faz o deploy de entidades para a plataforma GoClaw");
|
|
304
|
-
|
|
305
|
-
.command("skill <slug>")
|
|
306
|
-
.description("Faz o build da skill e envia para a API do GoClaw")
|
|
307
|
-
.action(async (slug) => {
|
|
308
|
-
const config = await getConfig();
|
|
309
|
-
if (!config.goclaw || !config.goclaw.token) {
|
|
310
|
-
console.error("❌ Configure sua chave de API (token) no agentforge.json antes de fazer o deploy.");
|
|
311
|
-
process.exit(1);
|
|
312
|
-
}
|
|
313
|
-
const basePath = getWorkspaceRoot();
|
|
304
|
+
async function deploySkill(slug, config, basePath) {
|
|
314
305
|
const skillPath = path_1.default.join(basePath, "skills", slug);
|
|
315
306
|
const exportsPath = path_1.default.join(basePath, "exports");
|
|
316
|
-
const
|
|
307
|
+
const safeSlug = slug.replace(/[\\\/]/g, '_');
|
|
308
|
+
const zipPath = path_1.default.join(exportsPath, `${safeSlug}.zip`);
|
|
317
309
|
if (!(await fs_extra_1.default.pathExists(skillPath))) {
|
|
318
310
|
console.error(`❌ A skill "${slug}" não foi encontrada em skills/${slug}.`);
|
|
319
|
-
|
|
311
|
+
return;
|
|
320
312
|
}
|
|
321
313
|
await fs_extra_1.default.ensureDir(exportsPath);
|
|
322
314
|
const zip = new adm_zip_1.default();
|
|
323
315
|
zip.addLocalFolder(skillPath, "");
|
|
324
316
|
zip.writeZip(zipPath);
|
|
325
|
-
console.log(
|
|
326
|
-
console.log(`🚀 Fazendo upload para o GoClaw...`);
|
|
317
|
+
console.log(`🚀 Fazendo upload da skill "${slug}" para o GoClaw...`);
|
|
327
318
|
const form = new form_data_1.default();
|
|
328
319
|
form.append("file", fs_extra_1.default.createReadStream(zipPath));
|
|
329
320
|
try {
|
|
@@ -336,11 +327,58 @@ deployCmd
|
|
|
336
327
|
});
|
|
337
328
|
const data = response.data;
|
|
338
329
|
if (data && data.version) {
|
|
339
|
-
console.log(
|
|
330
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados (versão ${data.version}).`);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
console.log(`✅ Arquivos da skill "${slug}" atualizados.`);
|
|
334
|
+
}
|
|
335
|
+
// Sincronizar metadados (visibility, description, tags, etc)
|
|
336
|
+
const skillsListRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
337
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
338
|
+
});
|
|
339
|
+
const remoteSkill = skillsListRes.data.skills?.find((s) => s.slug === slug);
|
|
340
|
+
if (remoteSkill) {
|
|
341
|
+
const metadataPath = path_1.default.join(skillPath, "metadata.json");
|
|
342
|
+
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
343
|
+
console.log(`🚀 Sincronizando metadados da skill "${slug}"...`);
|
|
344
|
+
const metadata = await fs_extra_1.default.readJson(metadataPath);
|
|
345
|
+
// Remover campos que não devem ser enviados no PUT
|
|
346
|
+
const payload = { ...metadata };
|
|
347
|
+
delete payload.id;
|
|
348
|
+
delete payload.slug;
|
|
349
|
+
delete payload.name;
|
|
350
|
+
await axios_1.default.put(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}`, payload, {
|
|
351
|
+
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
352
|
+
});
|
|
353
|
+
console.log(`✅ Metadados sincronizados com sucesso.`);
|
|
354
|
+
}
|
|
355
|
+
// Sincronizar permissões (grants)
|
|
356
|
+
const grantsPath = path_1.default.join(skillPath, "grants.jsonl");
|
|
357
|
+
if (await fs_extra_1.default.pathExists(grantsPath)) {
|
|
358
|
+
console.log(`🚀 Sincronizando permissões (grants) da skill "${slug}"...`);
|
|
359
|
+
const grantsContent = await fs_extra_1.default.readFile(grantsPath, 'utf8');
|
|
360
|
+
const lines = grantsContent.split('\n').filter(l => l.trim());
|
|
361
|
+
for (const line of lines) {
|
|
362
|
+
try {
|
|
363
|
+
const grant = JSON.parse(line);
|
|
364
|
+
if (grant.agent_key) {
|
|
365
|
+
const agentId = await resolveAgentId(grant.agent_key, config);
|
|
366
|
+
if (agentId) {
|
|
367
|
+
await axios_1.default.post(`${config.goclaw.api_url}/v1/skills/${remoteSkill.id}/grants/agent`, { agent_id: agentId, version: grant.pinned_version || null }, { headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" } });
|
|
368
|
+
console.log(` ➕ Permissão concedida ao agente: ${grant.agent_key}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
console.warn(` ⚠️ Falha ao conceder permissão: ${e.response?.data?.error || e.message}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
console.log(`✅ Permissões sincronizadas.`);
|
|
377
|
+
}
|
|
340
378
|
}
|
|
341
379
|
}
|
|
342
380
|
catch (error) {
|
|
343
|
-
console.error(
|
|
381
|
+
console.error(`❌ Erro no deploy da skill "${slug}":`);
|
|
344
382
|
if (error.response) {
|
|
345
383
|
console.error(error.response.data);
|
|
346
384
|
}
|
|
@@ -348,6 +386,18 @@ deployCmd
|
|
|
348
386
|
console.error(error.message);
|
|
349
387
|
}
|
|
350
388
|
}
|
|
389
|
+
}
|
|
390
|
+
deployCmd
|
|
391
|
+
.command("skill <slug>")
|
|
392
|
+
.description("Faz build e upload automático de uma skill para o GoClaw")
|
|
393
|
+
.action(async (slug) => {
|
|
394
|
+
const config = await getConfig();
|
|
395
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
396
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json antes de fazer o deploy.");
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
const basePath = getWorkspaceRoot();
|
|
400
|
+
await deploySkill(slug, config, basePath);
|
|
351
401
|
});
|
|
352
402
|
async function deployContextFiles(slug, config, resolvedId) {
|
|
353
403
|
const agentId = resolvedId || (await resolveAgentId(slug, config)) || slug;
|
|
@@ -357,53 +407,112 @@ async function deployContextFiles(slug, config, resolvedId) {
|
|
|
357
407
|
throw new Error(`Agente não encontrado em agents/${slug}`);
|
|
358
408
|
}
|
|
359
409
|
const files = await fs_extra_1.default.readdir(agentPath);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const memoryDirName = 'memory';
|
|
363
|
-
const contextFiles = files.filter(f => (f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.py')) &&
|
|
364
|
-
f !== 'README.md' &&
|
|
365
|
-
f !== 'agent.json' &&
|
|
366
|
-
!memoryFileNames.includes(f));
|
|
367
|
-
const hasMemoryDir = await fs_extra_1.default.pathExists(path_1.default.join(agentPath, memoryDirName));
|
|
368
|
-
const memoryFilesFound = files.filter(f => memoryFileNames.includes(f));
|
|
369
|
-
const hasMemory = hasMemoryDir || memoryFilesFound.length > 0;
|
|
370
|
-
if (contextFiles.length === 0 && !hasMemory) {
|
|
410
|
+
const itemsToSync = files.filter(f => f !== 'agent.json' && f !== 'README.md');
|
|
411
|
+
if (itemsToSync.length === 0) {
|
|
371
412
|
console.log(`Nenhum ficheiro de contexto ou memória encontrado para "${slug}".`);
|
|
372
413
|
return;
|
|
373
414
|
}
|
|
374
415
|
const tempExportDir = path_1.default.join(basePath, `temp_export_${slug}`);
|
|
375
|
-
const tempContextDir = path_1.default.join(tempExportDir, "context_files");
|
|
376
|
-
const tempMemoryDir = path_1.default.join(tempExportDir, "memory");
|
|
377
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
|
+
}
|
|
378
437
|
try {
|
|
379
|
-
const sections =
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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));
|
|
385
463
|
}
|
|
386
464
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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/");
|
|
393
475
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
await fs_extra_1.default.copy(path_1.default.join(agentPath, memoryDirName), tempMemoryDir);
|
|
476
|
+
else if (entry.startsWith("_system_")) {
|
|
477
|
+
finalEntry = entry.replace("_system_", "_system/");
|
|
397
478
|
}
|
|
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
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch (e) {
|
|
505
|
+
console.warn("Aviso: Falha ao procurar fantasmas para o tarball.", e.message);
|
|
398
506
|
}
|
|
399
507
|
await tar.c({
|
|
400
508
|
gzip: true,
|
|
401
509
|
file: tarPath,
|
|
402
510
|
cwd: tempExportDir
|
|
403
|
-
},
|
|
511
|
+
}, sectionsArray);
|
|
404
512
|
const form = new form_data_1.default();
|
|
405
513
|
form.append("file", fs_extra_1.default.createReadStream(tarPath));
|
|
406
|
-
|
|
514
|
+
// Upload dos ficheiros (aditivo)
|
|
515
|
+
const url = `${config.goclaw.api_url}/v1/agents/${agentId}/import?include=${sectionsArray.join(",")}`;
|
|
407
516
|
await axios_1.default.post(url, form, {
|
|
408
517
|
headers: {
|
|
409
518
|
...form.getHeaders(),
|
|
@@ -411,7 +520,73 @@ async function deployContextFiles(slug, config, resolvedId) {
|
|
|
411
520
|
"X-GoClaw-User-Id": config.goclaw.username || "system"
|
|
412
521
|
}
|
|
413
522
|
});
|
|
414
|
-
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
|
+
}
|
|
415
590
|
}
|
|
416
591
|
finally {
|
|
417
592
|
if (await fs_extra_1.default.pathExists(tempExportDir))
|
|
@@ -481,8 +656,44 @@ deployCmd
|
|
|
481
656
|
}
|
|
482
657
|
await deployAgent(slug, config);
|
|
483
658
|
});
|
|
659
|
+
async function deployAllAgents(config, basePath) {
|
|
660
|
+
const agentsDir = path_1.default.join(basePath, "agents");
|
|
661
|
+
if (await fs_extra_1.default.pathExists(agentsDir)) {
|
|
662
|
+
const agents = await fs_extra_1.default.readdir(agentsDir);
|
|
663
|
+
console.log(`🚀 Iniciando deploy em lote de ${agents.length} agentes...`);
|
|
664
|
+
for (const slug of agents) {
|
|
665
|
+
const agentPath = path_1.default.join(agentsDir, slug);
|
|
666
|
+
if ((await fs_extra_1.default.stat(agentPath)).isDirectory()) {
|
|
667
|
+
await deployAgent(slug, config);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
console.log("Nenhum agente encontrado em agents/.");
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async function deployAllSkills(config, basePath) {
|
|
676
|
+
const skillsDir = path_1.default.join(basePath, "skills");
|
|
677
|
+
if (await fs_extra_1.default.pathExists(skillsDir)) {
|
|
678
|
+
const skills = await fs_extra_1.default.readdir(skillsDir);
|
|
679
|
+
console.log(`🚀 Iniciando deploy em lote de skills...`);
|
|
680
|
+
for (const item of skills) {
|
|
681
|
+
const itemPath = path_1.default.join(skillsDir, item);
|
|
682
|
+
if ((await fs_extra_1.default.stat(itemPath)).isDirectory()) {
|
|
683
|
+
if (item === "system") {
|
|
684
|
+
console.log("⏩ Ignorando pasta 'system/' (skills nativas do GoClaw são apenas de leitura)");
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
await deploySkill(item, config, basePath);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
console.log("Nenhuma skill encontrada em skills/.");
|
|
693
|
+
}
|
|
694
|
+
}
|
|
484
695
|
deployCmd
|
|
485
|
-
.command("
|
|
696
|
+
.command("agents")
|
|
486
697
|
.description("Faz deploy de todos os agentes do workspace")
|
|
487
698
|
.action(async () => {
|
|
488
699
|
const config = await getConfig();
|
|
@@ -491,24 +702,122 @@ deployCmd
|
|
|
491
702
|
process.exit(1);
|
|
492
703
|
}
|
|
493
704
|
const basePath = getWorkspaceRoot();
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
705
|
+
await deployAllAgents(config, basePath);
|
|
706
|
+
console.log("🏁 Deploy de agentes concluído!");
|
|
707
|
+
});
|
|
708
|
+
deployCmd
|
|
709
|
+
.command("skills")
|
|
710
|
+
.description("Faz deploy de todas as skills do workspace")
|
|
711
|
+
.action(async () => {
|
|
712
|
+
const config = await getConfig();
|
|
713
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
714
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
715
|
+
process.exit(1);
|
|
498
716
|
}
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
717
|
+
const basePath = getWorkspaceRoot();
|
|
718
|
+
await deployAllSkills(config, basePath);
|
|
719
|
+
console.log("🏁 Deploy de skills concluído!");
|
|
720
|
+
});
|
|
721
|
+
deployCmd
|
|
722
|
+
.command("all")
|
|
723
|
+
.description("Faz deploy de todos os agentes e skills do workspace")
|
|
724
|
+
.action(async () => {
|
|
725
|
+
const config = await getConfig();
|
|
726
|
+
if (!config.goclaw || !config.goclaw.token) {
|
|
727
|
+
console.error("❌ Configure sua chave de API (token) no agentforge.json.");
|
|
728
|
+
process.exit(1);
|
|
506
729
|
}
|
|
507
|
-
|
|
730
|
+
const basePath = getWorkspaceRoot();
|
|
731
|
+
await deployAllAgents(config, basePath);
|
|
732
|
+
await deployAllSkills(config, basePath);
|
|
733
|
+
console.log("🏁 Deploy completo (agentes e skills) concluído!");
|
|
508
734
|
});
|
|
509
735
|
const pullCmd = program
|
|
510
736
|
.command("pull")
|
|
511
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
|
+
}
|
|
512
821
|
pullCmd
|
|
513
822
|
.command("skills")
|
|
514
823
|
.description("Faz download do arquivo tar.gz de skills do GoClaw e extrai localmente")
|
|
@@ -522,100 +831,97 @@ pullCmd
|
|
|
522
831
|
console.log("❌ Pull cancelado pelo utilizador.");
|
|
523
832
|
return;
|
|
524
833
|
}
|
|
525
|
-
console.log("🧹 Limpando a pasta local de skills...");
|
|
526
|
-
await fs_extra_1.default.emptyDir(path_1.default.join(getWorkspaceRoot(), "skills"));
|
|
527
|
-
console.log("📥 Baixando skills do GoClaw...");
|
|
528
834
|
try {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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 {
|
|
537
857
|
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
538
858
|
response.data.pipe(writer);
|
|
539
859
|
await new Promise((resolve, reject) => {
|
|
540
860
|
writer.on("finish", resolve);
|
|
541
861
|
writer.on("error", reject);
|
|
542
862
|
});
|
|
543
|
-
|
|
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
|
+
});
|
|
544
881
|
await tar.x({
|
|
545
882
|
file: tempTarPath,
|
|
546
|
-
cwd:
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
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
|
+
}
|
|
552
888
|
});
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
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;
|
|
564
901
|
}
|
|
565
902
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
for (const file of files) {
|
|
571
|
-
if (file.isDir)
|
|
572
|
-
continue;
|
|
573
|
-
const fileContentRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
574
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" }
|
|
575
|
-
});
|
|
576
|
-
const filePath = path_1.default.join(getWorkspaceRoot(), "skills", targetFolder, file.path);
|
|
577
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
578
|
-
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 });
|
|
579
907
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
// Remover quaisquer skills fantasmas que o tarball tenha extraído (skills apagadas mas ainda no export)
|
|
586
|
-
const validSlugs = new Set(skills.map((s) => s.is_system === true ? path_1.default.join("system", s.slug) : s.slug));
|
|
587
|
-
const skillsDir = path_1.default.join(getWorkspaceRoot(), "skills");
|
|
588
|
-
if (await fs_extra_1.default.pathExists(skillsDir)) {
|
|
589
|
-
const localItems = await fs_extra_1.default.readdir(skillsDir);
|
|
590
|
-
for (const item of localItems) {
|
|
591
|
-
if (item === "system") {
|
|
592
|
-
const systemDir = path_1.default.join(skillsDir, "system");
|
|
593
|
-
if (await fs_extra_1.default.pathExists(systemDir)) {
|
|
594
|
-
const systemItems = await fs_extra_1.default.readdir(systemDir);
|
|
595
|
-
for (const sysItem of systemItems) {
|
|
596
|
-
if (!validSlugs.has(path_1.default.join("system", sysItem))) {
|
|
597
|
-
await fs_extra_1.default.remove(path_1.default.join(systemDir, sysItem));
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
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);
|
|
601
911
|
}
|
|
602
|
-
else
|
|
603
|
-
await fs_extra_1.default.
|
|
912
|
+
else {
|
|
913
|
+
await fs_extra_1.default.move(filePath, path_1.default.join(agentPath, f), { overwrite: true });
|
|
604
914
|
}
|
|
605
915
|
}
|
|
916
|
+
await fs_extra_1.default.remove(contextDir);
|
|
606
917
|
}
|
|
607
|
-
console.log("✅ Pull concluído com sucesso! As skills foram atualizadas localmente.");
|
|
608
918
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
console.error(`Status HTTP ${error.response.status}`);
|
|
613
|
-
}
|
|
614
|
-
else {
|
|
615
|
-
console.error(error.message);
|
|
919
|
+
finally {
|
|
920
|
+
if (await fs_extra_1.default.pathExists(tempTarPath)) {
|
|
921
|
+
await fs_extra_1.default.remove(tempTarPath);
|
|
616
922
|
}
|
|
617
923
|
}
|
|
618
|
-
}
|
|
924
|
+
}
|
|
619
925
|
pullCmd
|
|
620
926
|
.command("agents")
|
|
621
927
|
.description("Faz download cirúrgico dos agentes (configuração e contexto)")
|
|
@@ -640,77 +946,7 @@ pullCmd
|
|
|
640
946
|
console.log(`Encontrados ${agents.length} agentes. Sincronizando...`);
|
|
641
947
|
for (const agent of agents) {
|
|
642
948
|
const slug = agent.agent_key;
|
|
643
|
-
|
|
644
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export?sections=config,context_files,memory`;
|
|
645
|
-
const response = await axios_1.default.get(url, {
|
|
646
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, "X-GoClaw-User-Id": config.goclaw.username || "system" },
|
|
647
|
-
responseType: "stream"
|
|
648
|
-
});
|
|
649
|
-
const tempTarPath = path_1.default.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
650
|
-
try {
|
|
651
|
-
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
652
|
-
response.data.pipe(writer);
|
|
653
|
-
await new Promise((resolve, reject) => {
|
|
654
|
-
writer.on("finish", resolve);
|
|
655
|
-
writer.on("error", reject);
|
|
656
|
-
});
|
|
657
|
-
const agentPath = path_1.default.join(getWorkspaceRoot(), "agents", slug);
|
|
658
|
-
await fs_extra_1.default.ensureDir(agentPath);
|
|
659
|
-
await tar.x({
|
|
660
|
-
file: tempTarPath,
|
|
661
|
-
cwd: agentPath,
|
|
662
|
-
strip: 0,
|
|
663
|
-
filter: (path) => {
|
|
664
|
-
return path === 'agent.json' || path.startsWith('context_files/') || path.startsWith('memory/') || path === 'MEMORY.md' || path === 'memory.md';
|
|
665
|
-
}
|
|
666
|
-
});
|
|
667
|
-
const contextDir = path_1.default.join(agentPath, "context_files");
|
|
668
|
-
if (await fs_extra_1.default.pathExists(contextDir)) {
|
|
669
|
-
const contextFiles = await fs_extra_1.default.readdir(contextDir);
|
|
670
|
-
for (const f of contextFiles) {
|
|
671
|
-
await fs_extra_1.default.move(path_1.default.join(contextDir, f), path_1.default.join(agentPath, f), { overwrite: true });
|
|
672
|
-
}
|
|
673
|
-
await fs_extra_1.default.remove(contextDir);
|
|
674
|
-
}
|
|
675
|
-
// Reconstruir ficheiros de memória a partir de JSONL
|
|
676
|
-
const memoryDir = path_1.default.join(agentPath, "memory");
|
|
677
|
-
if (await fs_extra_1.default.pathExists(memoryDir)) {
|
|
678
|
-
const processJsonl = async (filePath) => {
|
|
679
|
-
if (!(await fs_extra_1.default.pathExists(filePath)))
|
|
680
|
-
return;
|
|
681
|
-
const content = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
682
|
-
const lines = content.split('\n').filter(l => l.trim());
|
|
683
|
-
for (const line of lines) {
|
|
684
|
-
try {
|
|
685
|
-
const entry = JSON.parse(line);
|
|
686
|
-
if (entry.path && entry.content) {
|
|
687
|
-
const targetPath = path_1.default.join(agentPath, entry.path);
|
|
688
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(targetPath));
|
|
689
|
-
await fs_extra_1.default.writeFile(targetPath, entry.content);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
catch (e) { }
|
|
693
|
-
}
|
|
694
|
-
await fs_extra_1.default.remove(filePath);
|
|
695
|
-
};
|
|
696
|
-
await processJsonl(path_1.default.join(memoryDir, "global.jsonl"));
|
|
697
|
-
const usersDir = path_1.default.join(memoryDir, "users");
|
|
698
|
-
if (await fs_extra_1.default.pathExists(usersDir)) {
|
|
699
|
-
const userFiles = await fs_extra_1.default.readdir(usersDir);
|
|
700
|
-
for (const uf of userFiles) {
|
|
701
|
-
if (uf.endsWith(".jsonl")) {
|
|
702
|
-
await processJsonl(path_1.default.join(usersDir, uf));
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
await fs_extra_1.default.remove(usersDir);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
finally {
|
|
710
|
-
if (await fs_extra_1.default.pathExists(tempTarPath)) {
|
|
711
|
-
await fs_extra_1.default.remove(tempTarPath);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
949
|
+
await pullAgent(slug, agent.id, config);
|
|
714
950
|
}
|
|
715
951
|
console.log("✅ Pull de agentes concluído com sucesso!");
|
|
716
952
|
}
|
|
@@ -740,55 +976,7 @@ pullCmd
|
|
|
740
976
|
// PULL SKILLS INLINE
|
|
741
977
|
console.log('\n--- [1/2] SKILLS ---');
|
|
742
978
|
try {
|
|
743
|
-
|
|
744
|
-
const response = await axios_1.default.get(url, {
|
|
745
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
746
|
-
responseType: 'stream'
|
|
747
|
-
});
|
|
748
|
-
const tempTarPath = path_1.default.join(getWorkspaceRoot(), 'temp_skills.tar.gz');
|
|
749
|
-
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
750
|
-
response.data.pipe(writer);
|
|
751
|
-
await new Promise((resolve, reject) => {
|
|
752
|
-
writer.on('finish', resolve);
|
|
753
|
-
writer.on('error', reject);
|
|
754
|
-
});
|
|
755
|
-
await tar.x({ file: tempTarPath, cwd: getWorkspaceRoot() });
|
|
756
|
-
await fs_extra_1.default.remove(tempTarPath);
|
|
757
|
-
const skillsListRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills`, {
|
|
758
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
759
|
-
});
|
|
760
|
-
const skills = skillsListRes.data.skills || [];
|
|
761
|
-
for (const skill of skills) {
|
|
762
|
-
try {
|
|
763
|
-
const isSystem = skill.is_system === true;
|
|
764
|
-
const targetFolder = isSystem ? path_1.default.join('system', skill.slug) : skill.slug;
|
|
765
|
-
if (isSystem) {
|
|
766
|
-
const originalPath = path_1.default.join(getWorkspaceRoot(), 'skills', skill.slug);
|
|
767
|
-
const newPath = path_1.default.join(getWorkspaceRoot(), 'skills', targetFolder);
|
|
768
|
-
if (await fs_extra_1.default.pathExists(originalPath)) {
|
|
769
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(newPath));
|
|
770
|
-
await fs_extra_1.default.move(originalPath, newPath, { overwrite: true });
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
const filesRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files`, {
|
|
774
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
775
|
-
});
|
|
776
|
-
const files = filesRes.data.files || [];
|
|
777
|
-
for (const file of files) {
|
|
778
|
-
if (file.isDir)
|
|
779
|
-
continue;
|
|
780
|
-
const fileContentRes = await axios_1.default.get(`${config.goclaw.api_url}/v1/skills/${skill.id}/files/${file.path}`, {
|
|
781
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' }
|
|
782
|
-
});
|
|
783
|
-
const filePath = path_1.default.join(getWorkspaceRoot(), 'skills', targetFolder, file.path);
|
|
784
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
785
|
-
await fs_extra_1.default.writeFile(filePath, fileContentRes.data.content || '');
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
catch (fileErr) {
|
|
789
|
-
console.warn(`⚠️ Não foi possível transferir os ficheiros da skill ${skill.slug}: ${fileErr.message}`);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
979
|
+
await pullAllSkills(config);
|
|
792
980
|
console.log('✅ Pull de skills concluído!');
|
|
793
981
|
}
|
|
794
982
|
catch (error) {
|
|
@@ -804,44 +992,7 @@ pullCmd
|
|
|
804
992
|
console.log(`Encontrados ${agents.length} agentes. Sincronizando...`);
|
|
805
993
|
for (const agent of agents) {
|
|
806
994
|
const slug = agent.agent_key;
|
|
807
|
-
|
|
808
|
-
const url = `${config.goclaw.api_url}/v1/agents/${agent.id}/export`;
|
|
809
|
-
const response = await axios_1.default.get(url, {
|
|
810
|
-
headers: { Authorization: `Bearer ${config.goclaw.token}`, 'X-GoClaw-User-Id': config.goclaw.username || 'system' },
|
|
811
|
-
responseType: 'stream'
|
|
812
|
-
});
|
|
813
|
-
const tempTarPath = path_1.default.join(getWorkspaceRoot(), `temp_agent_${slug}.tar.gz`);
|
|
814
|
-
try {
|
|
815
|
-
const writer = fs_extra_1.default.createWriteStream(tempTarPath);
|
|
816
|
-
response.data.pipe(writer);
|
|
817
|
-
await new Promise((resolve, reject) => {
|
|
818
|
-
writer.on('finish', resolve);
|
|
819
|
-
writer.on('error', reject);
|
|
820
|
-
});
|
|
821
|
-
const agentPath = path_1.default.join(getWorkspaceRoot(), 'agents', slug);
|
|
822
|
-
await fs_extra_1.default.ensureDir(agentPath);
|
|
823
|
-
await tar.x({
|
|
824
|
-
file: tempTarPath,
|
|
825
|
-
cwd: agentPath,
|
|
826
|
-
strip: 0,
|
|
827
|
-
filter: (path) => {
|
|
828
|
-
return path === 'agent.json' || path.startsWith('context_files/');
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
const contextDir = path_1.default.join(agentPath, 'context_files');
|
|
832
|
-
if (await fs_extra_1.default.pathExists(contextDir)) {
|
|
833
|
-
const contextFiles = await fs_extra_1.default.readdir(contextDir);
|
|
834
|
-
for (const f of contextFiles) {
|
|
835
|
-
await fs_extra_1.default.move(path_1.default.join(contextDir, f), path_1.default.join(agentPath, f), { overwrite: true });
|
|
836
|
-
}
|
|
837
|
-
await fs_extra_1.default.remove(contextDir);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
finally {
|
|
841
|
-
if (await fs_extra_1.default.pathExists(tempTarPath)) {
|
|
842
|
-
await fs_extra_1.default.remove(tempTarPath);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
995
|
+
await pullAgent(slug, agent.id, config);
|
|
845
996
|
}
|
|
846
997
|
console.log('✅ Pull de agentes concluído!');
|
|
847
998
|
}
|