@fabioforest/openclaw 3.0.0
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/README.md +88 -0
- package/bin/openclaw.js +152 -0
- package/lib/channels.js +84 -0
- package/lib/cli/debug.js +12 -0
- package/lib/cli/doctor.js +266 -0
- package/lib/cli/init.js +145 -0
- package/lib/cli/orchestrate.js +43 -0
- package/lib/cli/status.js +128 -0
- package/lib/cli/update.js +122 -0
- package/lib/config.js +68 -0
- package/lib/detect.js +49 -0
- package/lib/ops/audit.js +156 -0
- package/lib/ops/enroll.js +133 -0
- package/lib/ops/exec.js +140 -0
- package/lib/ops/healthcheck.js +167 -0
- package/lib/ops/policy.js +153 -0
- package/lib/ops/transfer.js +152 -0
- package/lib/ops/update-safe.js +173 -0
- package/lib/ops/vpn.js +131 -0
- package/lib/security.js +48 -0
- package/lib/setup/config_wizard.js +186 -0
- package/package.json +47 -0
- package/templates/.agent/agents/setup-specialist.md +24 -0
- package/templates/.agent/agents/sysadmin-proativo.md +31 -0
- package/templates/.agent/hooks/pre-tool-use.js +109 -0
- package/templates/.agent/rules/SECURITY.md +7 -0
- package/templates/.agent/skills/openclaw-installation-debugger/SKILL.md +37 -0
- package/templates/.agent/skills/openclaw-installation-debugger/scripts/debug.js +165 -0
- package/templates/.agent/skills/openclaw-ops/01-openclaw-vpn-wireguard/SKILL.md +20 -0
- package/templates/.agent/skills/openclaw-ops/02-openclaw-enroll-host/SKILL.md +14 -0
- package/templates/.agent/skills/openclaw-ops/03-openclaw-policy-baseline/SKILL.md +17 -0
- package/templates/.agent/skills/openclaw-ops/04-openclaw-remote-exec-runbooks/SKILL.md +13 -0
- package/templates/.agent/skills/openclaw-ops/05-openclaw-file-transfer-safe/SKILL.md +10 -0
- package/templates/.agent/skills/openclaw-ops/06-openclaw-audit-logging/SKILL.md +13 -0
- package/templates/.agent/skills/openclaw-ops/07-openclaw-safe-update/SKILL.md +9 -0
- package/templates/.agent/skills/openclaw-ops/08-openclaw-healthchecks/SKILL.md +10 -0
- package/templates/.agent/skills/universal-setup/SKILL.md +26 -0
- package/templates/.agent/workflows/doctor.md +20 -0
- package/templates/.agent/workflows/healthcheck.md +22 -0
- package/templates/.agent/workflows/healthcheck.runbook.md +9 -0
- package/templates/.agent/workflows/restart.md +20 -0
- package/templates/.agent/workflows/restart.runbook.md +8 -0
- package/templates/.agent/workflows/setup.md +18 -0
package/lib/cli/init.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comando CLI: init
|
|
5
|
+
*
|
|
6
|
+
* Copia templates/.agent/ para o diretório destino.
|
|
7
|
+
* Verifica se .agent/ já existe (bloqueia sem --force).
|
|
8
|
+
* Cria openclaw.json com defaults via lib/config.js.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const { initConfigDefaults, writeJsonSafe } = require("../config");
|
|
14
|
+
|
|
15
|
+
// Caminho dos templates incluídos no pacote
|
|
16
|
+
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Copia diretório recursivamente.
|
|
20
|
+
* @param {string} src — diretório fonte
|
|
21
|
+
* @param {string} dest — diretório destino
|
|
22
|
+
* @param {object} [stats] — contador de arquivos copiados
|
|
23
|
+
* @returns {object} stats com { files, dirs }
|
|
24
|
+
*/
|
|
25
|
+
function copyDirRecursive(src, dest, stats = { files: 0, dirs: 0 }) {
|
|
26
|
+
if (!fs.existsSync(dest)) {
|
|
27
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
28
|
+
stats.dirs++;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
32
|
+
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const srcPath = path.join(src, entry.name);
|
|
35
|
+
const destPath = path.join(dest, entry.name);
|
|
36
|
+
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
copyDirRecursive(srcPath, destPath, stats);
|
|
39
|
+
} else {
|
|
40
|
+
fs.copyFileSync(srcPath, destPath);
|
|
41
|
+
stats.files++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return stats;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Executa o comando init.
|
|
50
|
+
* @param {object} options
|
|
51
|
+
* @param {string} options.targetPath — diretório alvo
|
|
52
|
+
* @param {object} options.flags — flags do CLI (force, quiet)
|
|
53
|
+
*/
|
|
54
|
+
async function run({ targetPath, flags }) {
|
|
55
|
+
const agentDir = path.join(targetPath, ".agent");
|
|
56
|
+
const configPath = path.join(targetPath, "openclaw.json");
|
|
57
|
+
|
|
58
|
+
// Verificar se já existe
|
|
59
|
+
if (fs.existsSync(agentDir) && !flags.force) {
|
|
60
|
+
console.error("❌ Diretório .agent/ já existe.");
|
|
61
|
+
console.error(" Use --force para sobrescrever ou 'openclaw update' para atualizar.");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Verificar se templates existem
|
|
66
|
+
if (!fs.existsSync(TEMPLATES_DIR)) {
|
|
67
|
+
console.error("❌ Templates não encontrados. Pacote pode estar corrompido.");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!flags.quiet) {
|
|
72
|
+
console.log("🦀 OpenClaw — Inicializando projeto...\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Se --force e já existe, alertar
|
|
76
|
+
if (fs.existsSync(agentDir) && flags.force) {
|
|
77
|
+
if (!flags.quiet) {
|
|
78
|
+
console.log("⚠️ --force: substituindo .agent/ existente\n");
|
|
79
|
+
}
|
|
80
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Copiar templates
|
|
84
|
+
const stats = copyDirRecursive(TEMPLATES_DIR, agentDir);
|
|
85
|
+
|
|
86
|
+
if (!flags.quiet) {
|
|
87
|
+
console.log(`✅ .agent/ instalado com sucesso!`);
|
|
88
|
+
console.log(` 📁 ${stats.dirs} diretórios criados`);
|
|
89
|
+
console.log(` 📄 ${stats.files} arquivos copiados\n`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Criar openclaw.json com defaults (se não existir)
|
|
93
|
+
if (!fs.existsSync(configPath)) {
|
|
94
|
+
const defaults = initConfigDefaults({});
|
|
95
|
+
writeJsonSafe(configPath, defaults);
|
|
96
|
+
|
|
97
|
+
if (!flags.quiet) {
|
|
98
|
+
console.log("📋 openclaw.json criado com configurações padrão\n");
|
|
99
|
+
}
|
|
100
|
+
} else if (!flags.quiet) {
|
|
101
|
+
console.log("📋 openclaw.json já existe — mantido\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Resumo final
|
|
105
|
+
if (!flags.quiet) {
|
|
106
|
+
console.log("📂 Estrutura instalada:");
|
|
107
|
+
listInstalledStructure(agentDir, " ");
|
|
108
|
+
|
|
109
|
+
console.log("\n🚀 Próximos passos:");
|
|
110
|
+
console.log(" 1. openclaw setup — configurar ambiente");
|
|
111
|
+
console.log(" 2. openclaw doctor — verificar saúde");
|
|
112
|
+
console.log(" 3. openclaw status — ver status\n");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Lista a estrutura instalada de forma visual.
|
|
118
|
+
* @param {string} dir — diretório para listar
|
|
119
|
+
* @param {string} prefix — prefixo para indentação
|
|
120
|
+
*/
|
|
121
|
+
function listInstalledStructure(dir, prefix = "") {
|
|
122
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
123
|
+
.sort((a, b) => {
|
|
124
|
+
// Diretórios primeiro, depois arquivos
|
|
125
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
126
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
127
|
+
return a.name.localeCompare(b.name);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < entries.length; i++) {
|
|
131
|
+
const entry = entries[i];
|
|
132
|
+
const isLast = i === entries.length - 1;
|
|
133
|
+
const connector = isLast ? "└── " : "├── ";
|
|
134
|
+
const icon = entry.isDirectory() ? "📁" : "📄";
|
|
135
|
+
|
|
136
|
+
console.log(`${prefix}${connector}${icon} ${entry.name}`);
|
|
137
|
+
|
|
138
|
+
if (entry.isDirectory()) {
|
|
139
|
+
const childPrefix = prefix + (isLast ? " " : "│ ");
|
|
140
|
+
listInstalledStructure(path.join(dir, entry.name), childPrefix);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { run, copyDirRecursive };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const initCmd = require("./init");
|
|
6
|
+
const doctorCmd = require("./doctor");
|
|
7
|
+
const debugCmd = require("./debug");
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
run: async function ({ targetPath, flags }) {
|
|
11
|
+
const agentDir = path.join(targetPath, ".agent");
|
|
12
|
+
|
|
13
|
+
console.log("\n🤖 OpenClaw Auto-Orchestrator\n");
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(agentDir)) {
|
|
16
|
+
console.log("⚠️ Instalação não encontrada.");
|
|
17
|
+
console.log("🚀 Iniciando processo de instalação (init)...");
|
|
18
|
+
|
|
19
|
+
// Repassa flags, força path atual
|
|
20
|
+
await initCmd.run({ targetPath, flags });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log("✅ Instalação detectada.");
|
|
25
|
+
console.log("🏥 Executando verificação de saúde (doctor)...");
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Doctor lança erro ou exit(1) se falhar?
|
|
29
|
+
// Precisamos capturar o exit code do doctor se ele for desenhado para matar o processo.
|
|
30
|
+
// O doctor atual usa exit(1) em erros críticos ou apenas loga?
|
|
31
|
+
// Vamos checar o doctor depois. Por enquanto, assumimos que ele roda.
|
|
32
|
+
|
|
33
|
+
await doctorCmd.run({ targetPath, flags });
|
|
34
|
+
|
|
35
|
+
console.log("\n✨ Sistema parece saudável.");
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.log("\n❌ Doctor encontrou problemas ou falhou.");
|
|
38
|
+
console.log("🔍 Iniciando diagnóstico avançado (debug)...");
|
|
39
|
+
|
|
40
|
+
await debugCmd.run({ targetPath, flags });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comando CLI: status
|
|
5
|
+
*
|
|
6
|
+
* Mostra status da instalação do OpenClaw no projeto.
|
|
7
|
+
* Verifica presença de .agent/, openclaw.json, lista componentes
|
|
8
|
+
* e exibe configuração ativa sem revelar tokens.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const { readJsonSafe } = require("../config");
|
|
14
|
+
const { mask } = require("../security");
|
|
15
|
+
|
|
16
|
+
const pkg = require("../../package.json");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Conta arquivos em um diretório recursivamente.
|
|
20
|
+
* @param {string} dir — diretório para contar
|
|
21
|
+
* @returns {number} contagem de arquivos
|
|
22
|
+
*/
|
|
23
|
+
function countFiles(dir) {
|
|
24
|
+
if (!fs.existsSync(dir)) return 0;
|
|
25
|
+
let count = 0;
|
|
26
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
count += countFiles(path.join(dir, entry.name));
|
|
30
|
+
} else {
|
|
31
|
+
count++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return count;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Lista itens de um diretório (primeiro nível).
|
|
39
|
+
* @param {string} dir — diretório para listar
|
|
40
|
+
* @returns {string[]} nomes dos itens
|
|
41
|
+
*/
|
|
42
|
+
function listItems(dir) {
|
|
43
|
+
if (!fs.existsSync(dir)) return [];
|
|
44
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
45
|
+
.filter((e) => e.isDirectory())
|
|
46
|
+
.map((e) => e.name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Executa o comando status.
|
|
51
|
+
* @param {object} options
|
|
52
|
+
* @param {string} options.targetPath — diretório alvo
|
|
53
|
+
* @param {object} options.flags — flags do CLI
|
|
54
|
+
*/
|
|
55
|
+
async function run({ targetPath, flags }) {
|
|
56
|
+
const agentDir = path.join(targetPath, ".agent");
|
|
57
|
+
const configPath = path.join(targetPath, "openclaw.json");
|
|
58
|
+
|
|
59
|
+
console.log(`\n🦀 OpenClaw Status — v${pkg.version}\n`);
|
|
60
|
+
console.log(`📂 Projeto: ${targetPath}\n`);
|
|
61
|
+
|
|
62
|
+
// Verificar .agent/
|
|
63
|
+
const agentExists = fs.existsSync(agentDir);
|
|
64
|
+
console.log(`${agentExists ? "✅" : "❌"} .agent/ ${agentExists ? "instalado" : "não encontrado"}`);
|
|
65
|
+
|
|
66
|
+
if (agentExists) {
|
|
67
|
+
// Listar componentes instalados
|
|
68
|
+
const skillsDir = path.join(agentDir, "skills");
|
|
69
|
+
const agentsDir = path.join(agentDir, "agents");
|
|
70
|
+
const rulesDir = path.join(agentDir, "rules");
|
|
71
|
+
const workflowsDir = path.join(agentDir, "workflows");
|
|
72
|
+
const hooksDir = path.join(agentDir, "hooks");
|
|
73
|
+
|
|
74
|
+
const skills = listItems(skillsDir);
|
|
75
|
+
const agentFiles = fs.existsSync(agentsDir)
|
|
76
|
+
? fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md"))
|
|
77
|
+
: [];
|
|
78
|
+
const ruleFiles = fs.existsSync(rulesDir)
|
|
79
|
+
? fs.readdirSync(rulesDir).filter((f) => f.endsWith(".md"))
|
|
80
|
+
: [];
|
|
81
|
+
const workflowFiles = fs.existsSync(workflowsDir)
|
|
82
|
+
? fs.readdirSync(workflowsDir).filter((f) => f.endsWith(".md"))
|
|
83
|
+
: [];
|
|
84
|
+
const hookFiles = fs.existsSync(hooksDir)
|
|
85
|
+
? fs.readdirSync(hooksDir).filter((f) => f.endsWith(".js"))
|
|
86
|
+
: [];
|
|
87
|
+
|
|
88
|
+
console.log(`\n📦 Componentes instalados:`);
|
|
89
|
+
console.log(` Skills: ${skills.length > 0 ? skills.join(", ") : "nenhuma"}`);
|
|
90
|
+
console.log(` Agents: ${agentFiles.length > 0 ? agentFiles.map((f) => f.replace(".md", "")).join(", ") : "nenhum"}`);
|
|
91
|
+
console.log(` Rules: ${ruleFiles.length > 0 ? ruleFiles.map((f) => f.replace(".md", "")).join(", ") : "nenhuma"}`);
|
|
92
|
+
console.log(` Workflows: ${workflowFiles.length > 0 ? workflowFiles.map((f) => f.replace(".md", "")).join(", ") : "nenhum"}`);
|
|
93
|
+
console.log(` Hooks: ${hookFiles.length > 0 ? hookFiles.join(", ") : "nenhum"}`);
|
|
94
|
+
console.log(` Total: ${countFiles(agentDir)} arquivos`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Verificar openclaw.json
|
|
98
|
+
const configExists = fs.existsSync(configPath);
|
|
99
|
+
console.log(`\n${configExists ? "✅" : "❌"} openclaw.json ${configExists ? "encontrado" : "não encontrado"}`);
|
|
100
|
+
|
|
101
|
+
if (configExists) {
|
|
102
|
+
const config = readJsonSafe(configPath);
|
|
103
|
+
if (config) {
|
|
104
|
+
console.log(`\n⚙️ Configuração ativa:`);
|
|
105
|
+
console.log(` bind: ${config.gateway?.bind || "não definido"}`);
|
|
106
|
+
console.log(` auth.mode: ${config.auth?.mode || "não definido"}`);
|
|
107
|
+
console.log(` token: ${config.auth?.token ? mask(config.auth.token) : "não definido"}`);
|
|
108
|
+
|
|
109
|
+
// Canais configurados
|
|
110
|
+
const channels = config.channels || {};
|
|
111
|
+
const activeChannels = Object.entries(channels)
|
|
112
|
+
.filter(([, v]) => v && v.token)
|
|
113
|
+
.map(([k]) => k);
|
|
114
|
+
console.log(` canais: ${activeChannels.length > 0 ? activeChannels.join(", ") : "nenhum"}`);
|
|
115
|
+
|
|
116
|
+
// Ambiente
|
|
117
|
+
if (config.environment) {
|
|
118
|
+
console.log(` ambiente: ${config.environment}`);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
console.log(" ⚠️ Erro ao ler openclaw.json — arquivo pode estar corrompido");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log("");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { run };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comando CLI: update
|
|
5
|
+
*
|
|
6
|
+
* Atualiza templates .agent/ preservando customizações do usuário.
|
|
7
|
+
* Compara arquivos por hash SHA-256 e só sobrescreve se:
|
|
8
|
+
* - O arquivo não foi customizado pelo usuário (hash original)
|
|
9
|
+
* - Ou se o template tem uma versão mais nova
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const crypto = require("crypto");
|
|
15
|
+
|
|
16
|
+
// Caminho dos templates incluídos no pacote
|
|
17
|
+
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Calcula o SHA-256 de um arquivo.
|
|
21
|
+
* @param {string} filePath — caminho do arquivo
|
|
22
|
+
* @returns {string} hash em hex
|
|
23
|
+
*/
|
|
24
|
+
function fileHash(filePath) {
|
|
25
|
+
const content = fs.readFileSync(filePath);
|
|
26
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compara e atualiza um diretório recursivamente.
|
|
31
|
+
* @param {string} src — diretório fonte (template)
|
|
32
|
+
* @param {string} dest — diretório destino (instalado)
|
|
33
|
+
* @param {object} stats — contadores { updated, skipped, added }
|
|
34
|
+
*/
|
|
35
|
+
function updateDirRecursive(src, dest, stats) {
|
|
36
|
+
if (!fs.existsSync(dest)) {
|
|
37
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
41
|
+
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const srcPath = path.join(src, entry.name);
|
|
44
|
+
const destPath = path.join(dest, entry.name);
|
|
45
|
+
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
updateDirRecursive(srcPath, destPath, stats);
|
|
48
|
+
} else {
|
|
49
|
+
if (!fs.existsSync(destPath)) {
|
|
50
|
+
// Arquivo novo — copiar
|
|
51
|
+
fs.copyFileSync(srcPath, destPath);
|
|
52
|
+
stats.added.push(path.relative(dest, destPath) || entry.name);
|
|
53
|
+
} else {
|
|
54
|
+
// Arquivo já existe — comparar hashes
|
|
55
|
+
const srcHash = fileHash(srcPath);
|
|
56
|
+
const destHash = fileHash(destPath);
|
|
57
|
+
|
|
58
|
+
if (srcHash === destHash) {
|
|
59
|
+
// Idêntico — nada a fazer
|
|
60
|
+
stats.skipped.push(path.relative(dest, destPath) || entry.name);
|
|
61
|
+
} else {
|
|
62
|
+
// Diferente — arquivo foi customizado ou template atualizado
|
|
63
|
+
// Preserva o original do usuário fazendo backup
|
|
64
|
+
const backupPath = destPath + ".bak";
|
|
65
|
+
fs.copyFileSync(destPath, backupPath);
|
|
66
|
+
fs.copyFileSync(srcPath, destPath);
|
|
67
|
+
stats.updated.push(path.relative(dest, destPath) || entry.name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Executa o comando update.
|
|
76
|
+
* @param {object} options
|
|
77
|
+
* @param {string} options.targetPath — diretório alvo
|
|
78
|
+
* @param {object} options.flags — flags do CLI
|
|
79
|
+
*/
|
|
80
|
+
async function run({ targetPath, flags }) {
|
|
81
|
+
const agentDir = path.join(targetPath, ".agent");
|
|
82
|
+
|
|
83
|
+
if (!fs.existsSync(agentDir)) {
|
|
84
|
+
console.error("❌ Diretório .agent/ não encontrado.");
|
|
85
|
+
console.error(" Rode 'openclaw init' primeiro para instalar os templates.");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(TEMPLATES_DIR)) {
|
|
90
|
+
console.error("❌ Templates não encontrados. Pacote pode estar corrompido.");
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!flags.quiet) {
|
|
95
|
+
console.log("\n🔄 OpenClaw Update — Atualizando templates...\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const stats = { updated: [], skipped: [], added: [] };
|
|
99
|
+
updateDirRecursive(TEMPLATES_DIR, agentDir, stats);
|
|
100
|
+
|
|
101
|
+
if (!flags.quiet) {
|
|
102
|
+
if (stats.added.length > 0) {
|
|
103
|
+
console.log(`📄 Novos (${stats.added.length}):`);
|
|
104
|
+
stats.added.forEach((f) => console.log(` + ${f}`));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (stats.updated.length > 0) {
|
|
108
|
+
console.log(`\n🔄 Atualizados (${stats.updated.length}):`);
|
|
109
|
+
stats.updated.forEach((f) => console.log(` ~ ${f} (backup: ${f}.bak)`));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (stats.skipped.length > 0) {
|
|
113
|
+
console.log(`\n⏭️ Sem alteração (${stats.skipped.length}):`);
|
|
114
|
+
stats.skipped.forEach((f) => console.log(` = ${f}`));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const total = stats.added.length + stats.updated.length;
|
|
118
|
+
console.log(`\n✅ Update concluído: ${total} alterações, ${stats.skipped.length} mantidos\n`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { run, updateDirRecursive, fileHash };
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de manipulação de configuração (JSON) para o OpenClaw Setup Wizard.
|
|
3
|
+
* Oferece leitura/escrita atômica de JSON e criação idempotente de arquivos.
|
|
4
|
+
*
|
|
5
|
+
* @module lib/config
|
|
6
|
+
*/
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Lê e parseia um arquivo JSON de forma segura.
|
|
12
|
+
* Retorna null se o arquivo não existir ou não for JSON válido.
|
|
13
|
+
* @param {string} filePath - Caminho absoluto do arquivo JSON
|
|
14
|
+
* @returns {object|null} Objeto parseado ou null em caso de erro
|
|
15
|
+
*/
|
|
16
|
+
function readJsonSafe(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Escreve um objeto como JSON de forma atômica (write-then-rename).
|
|
26
|
+
* Previne corrupção de arquivo em caso de falha durante escrita.
|
|
27
|
+
* @param {string} filePath - Caminho absoluto do arquivo destino
|
|
28
|
+
* @param {object} obj - Objeto a ser serializado como JSON
|
|
29
|
+
*/
|
|
30
|
+
function writeJsonSafe(filePath, obj) {
|
|
31
|
+
const tmp = filePath + ".tmp";
|
|
32
|
+
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2), "utf8");
|
|
33
|
+
fs.renameSync(tmp, filePath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Garante que um arquivo existe, criando com conteúdo padrão se necessário.
|
|
38
|
+
* Não sobrescreve arquivos existentes (idempotente).
|
|
39
|
+
* @param {string} filePath - Caminho absoluto do arquivo
|
|
40
|
+
* @param {string} content - Conteúdo padrão caso o arquivo precise ser criado
|
|
41
|
+
* @returns {boolean} true se o arquivo foi criado, false se já existia
|
|
42
|
+
*/
|
|
43
|
+
function ensureFile(filePath, content) {
|
|
44
|
+
if (!fs.existsSync(filePath)) {
|
|
45
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
46
|
+
console.log(`✔ Criado ${path.basename(filePath)}`);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Inicializa a estrutura de configuração com seções padrão.
|
|
54
|
+
* Não sobrescreve seções que já existam no objeto config.
|
|
55
|
+
* @param {object} config - Objeto de configuração parcial
|
|
56
|
+
* @returns {object} Configuração com todas as seções garantidas
|
|
57
|
+
*/
|
|
58
|
+
function initConfigDefaults(config) {
|
|
59
|
+
config.gateway = config.gateway || {};
|
|
60
|
+
config.auth = config.auth || {};
|
|
61
|
+
config.channels = config.channels || {};
|
|
62
|
+
config.filesystem = config.filesystem || {};
|
|
63
|
+
config.filesystem.allowlist = config.filesystem.allowlist || [];
|
|
64
|
+
config.sandbox = config.sandbox || {};
|
|
65
|
+
return config;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { readJsonSafe, writeJsonSafe, ensureFile, initConfigDefaults };
|
package/lib/detect.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de detecção de ambiente para o OpenClaw Setup Wizard.
|
|
3
|
+
* Identifica o tipo de sistema operacional e contexto de execução
|
|
4
|
+
* (Docker, WSL2, VPS, local) para adaptar o comportamento do wizard.
|
|
5
|
+
*
|
|
6
|
+
* @module lib/detect
|
|
7
|
+
*/
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const os = require("os");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detecta se o processo está rodando dentro de um container Docker.
|
|
13
|
+
* Verifica dois indicadores: /.dockerenv e menção a "docker" no cgroup.
|
|
14
|
+
* @returns {boolean} true se está em Docker
|
|
15
|
+
*/
|
|
16
|
+
function isDocker() {
|
|
17
|
+
return fs.existsSync("/.dockerenv") ||
|
|
18
|
+
(fs.existsSync("/proc/1/cgroup") && fs.readFileSync("/proc/1/cgroup", "utf8").includes("docker"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Detecta se o processo está rodando dentro do WSL2.
|
|
23
|
+
* Usa o release do kernel ("microsoft") e a env WSL_DISTRO_NAME.
|
|
24
|
+
* @returns {boolean} true se está em WSL2
|
|
25
|
+
*/
|
|
26
|
+
function isWSL() {
|
|
27
|
+
return os.platform() === "linux" &&
|
|
28
|
+
(os.release().toLowerCase().includes("microsoft") || !!process.env.WSL_DISTRO_NAME);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Detecta o ambiente de execução para adaptar o wizard.
|
|
33
|
+
* Ordem de prioridade: Docker > WSL2 > Windows > Mac > Linux VPS (root) > Linux > desconhecido.
|
|
34
|
+
* @returns {"docker"|"wsl2"|"windows"|"mac"|"linux-vps-root"|"linux"|"unknown"} Tipo de ambiente
|
|
35
|
+
*/
|
|
36
|
+
function detectEnvironment() {
|
|
37
|
+
if (isDocker()) return "docker";
|
|
38
|
+
if (isWSL()) return "wsl2";
|
|
39
|
+
if (os.platform() === "win32") return "windows";
|
|
40
|
+
if (os.platform() === "darwin") return "mac";
|
|
41
|
+
if (os.platform() === "linux") {
|
|
42
|
+
const user = os.userInfo().username;
|
|
43
|
+
if (user === "root") return "linux-vps-root";
|
|
44
|
+
return "linux";
|
|
45
|
+
}
|
|
46
|
+
return "unknown";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { isDocker, isWSL, detectEnvironment };
|