@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.
Files changed (43) hide show
  1. package/README.md +88 -0
  2. package/bin/openclaw.js +152 -0
  3. package/lib/channels.js +84 -0
  4. package/lib/cli/debug.js +12 -0
  5. package/lib/cli/doctor.js +266 -0
  6. package/lib/cli/init.js +145 -0
  7. package/lib/cli/orchestrate.js +43 -0
  8. package/lib/cli/status.js +128 -0
  9. package/lib/cli/update.js +122 -0
  10. package/lib/config.js +68 -0
  11. package/lib/detect.js +49 -0
  12. package/lib/ops/audit.js +156 -0
  13. package/lib/ops/enroll.js +133 -0
  14. package/lib/ops/exec.js +140 -0
  15. package/lib/ops/healthcheck.js +167 -0
  16. package/lib/ops/policy.js +153 -0
  17. package/lib/ops/transfer.js +152 -0
  18. package/lib/ops/update-safe.js +173 -0
  19. package/lib/ops/vpn.js +131 -0
  20. package/lib/security.js +48 -0
  21. package/lib/setup/config_wizard.js +186 -0
  22. package/package.json +47 -0
  23. package/templates/.agent/agents/setup-specialist.md +24 -0
  24. package/templates/.agent/agents/sysadmin-proativo.md +31 -0
  25. package/templates/.agent/hooks/pre-tool-use.js +109 -0
  26. package/templates/.agent/rules/SECURITY.md +7 -0
  27. package/templates/.agent/skills/openclaw-installation-debugger/SKILL.md +37 -0
  28. package/templates/.agent/skills/openclaw-installation-debugger/scripts/debug.js +165 -0
  29. package/templates/.agent/skills/openclaw-ops/01-openclaw-vpn-wireguard/SKILL.md +20 -0
  30. package/templates/.agent/skills/openclaw-ops/02-openclaw-enroll-host/SKILL.md +14 -0
  31. package/templates/.agent/skills/openclaw-ops/03-openclaw-policy-baseline/SKILL.md +17 -0
  32. package/templates/.agent/skills/openclaw-ops/04-openclaw-remote-exec-runbooks/SKILL.md +13 -0
  33. package/templates/.agent/skills/openclaw-ops/05-openclaw-file-transfer-safe/SKILL.md +10 -0
  34. package/templates/.agent/skills/openclaw-ops/06-openclaw-audit-logging/SKILL.md +13 -0
  35. package/templates/.agent/skills/openclaw-ops/07-openclaw-safe-update/SKILL.md +9 -0
  36. package/templates/.agent/skills/openclaw-ops/08-openclaw-healthchecks/SKILL.md +10 -0
  37. package/templates/.agent/skills/universal-setup/SKILL.md +26 -0
  38. package/templates/.agent/workflows/doctor.md +20 -0
  39. package/templates/.agent/workflows/healthcheck.md +22 -0
  40. package/templates/.agent/workflows/healthcheck.runbook.md +9 -0
  41. package/templates/.agent/workflows/restart.md +20 -0
  42. package/templates/.agent/workflows/restart.runbook.md +8 -0
  43. package/templates/.agent/workflows/setup.md +18 -0
@@ -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 };