@fabioforest/openclaw 3.7.1 → 3.9.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 CHANGED
@@ -425,6 +425,37 @@ Verifica conectividade, proxy, versões e integridade do ambiente.
425
425
 
426
426
  ---
427
427
 
428
+ ## 🤖 Agentes Especializados (Personas)
429
+
430
+ O OpenClaw vem com **agentes pré-configurados** que combinam skills específicas para realizar funções complexas:
431
+
432
+ | Persona | Foco | Skills Principais |
433
+ |---------|------|-------------------|
434
+ | **`sysadmin-proativo`** | Manutenção de servidores, logs, segurança | `vps-cloud-infra`, `openclaw-ops`, `openclaw-security` |
435
+ | **`workflow-automator`** | Criação e execução de automações | `ai-capture`, `mission-control`, `openclaw-dev` |
436
+ | **`setup-specialist`** | Onboarding e configuração inicial | `universal-setup`, `ai-provider-setup`, `openclaw-installation-debugger` |
437
+
438
+ **Como usar:**
439
+ Basta pedir no chat: *"Atue como sysadmin e verifique os logs do servidor"* ou *"Inicie o workflow-automator para criar uma automação de tickets"*. O `openclaw-router` ativará a persona correta.
440
+
441
+ ---
442
+
443
+ ## ⚡ Workflows Prontos
444
+
445
+ Além de skills isoladas, o OpenClaw traz **fluxos de trabalho completos** (runbooks executáveis):
446
+
447
+ | Workflow | Descrição | Comando Trigger |
448
+ |----------|-----------|-----------------|
449
+ | **`ai-capture`** | Captura inteligente de dados/tickets usando IA | *"Iniciar captura de dados"* |
450
+ | **`doctor`** | Diagnóstico e reparo automático do ambiente | `openclaw doctor` |
451
+ | **`healthcheck`** | Verificação rápida de saúde (API, DB, cache) | `openclaw healthcheck` |
452
+ | **`restart-openclaw`** | Reinício seguro e auditado do serviço | `openclaw restart` |
453
+
454
+ **Execução:**
455
+ Workflows são arquivos `.md` em `.agent/workflows/` que o agente lê e executa passo a passo, garantindo consistência e auditoria.
456
+
457
+ ---
458
+
428
459
  ## 🔒 Segurança
429
460
 
430
461
  O OpenClaw segue 3 princípios fundamentais:
package/bin/openclaw.js CHANGED
@@ -32,6 +32,7 @@ const COMMANDS = {
32
32
  inspect: "../lib/cli/inspect",
33
33
  assist: "../lib/cli/assist",
34
34
  ide: "../lib/cli/ide",
35
+ setup: "../lib/cli/setup",
35
36
  };
36
37
 
37
38
  /**
@@ -145,18 +146,6 @@ async function main() {
145
146
  // Resolver caminho de destino
146
147
  const targetPath = path.resolve(flags.path || ".");
147
148
 
148
- // Comando setup — roda wizard diretamente
149
- if (command === "setup") {
150
- const wizardPath = path.join(__dirname, "..", "lib", "setup", "config_wizard.js");
151
- try {
152
- require(wizardPath);
153
- } catch (err) {
154
- console.error(`❌ Erro ao rodar setup wizard: ${err.message}`);
155
- process.exit(1);
156
- }
157
- return;
158
- }
159
-
160
149
  // Verificar se o comando existe
161
150
  if (!COMMANDS[command]) {
162
151
  console.error(`❌ Comando desconhecido: "${command}"`);
package/lib/cli/ide.js CHANGED
@@ -13,6 +13,7 @@ const path = require("path");
13
13
  const readline = require("readline");
14
14
  const { detectContext, getAuditHeader } = require("../context");
15
15
  const { copyDirRecursive } = require("./init");
16
+ const { writeCliAudit } = require("../utils/audit-writer");
16
17
 
17
18
  // Caminho dos templates do pacote
18
19
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
@@ -22,21 +23,9 @@ function ask(q) {
22
23
  return new Promise((res) => rl.question(q, (ans) => { rl.close(); res(ans.trim()); }));
23
24
  }
24
25
 
25
- /**
26
- * Grava log de auditoria para o comando ide.
27
- */
26
+ // writeAudit extraído para lib/utils/audit-writer.js (DRY)
28
27
  function writeAudit(targetPath, lines, flags) {
29
- if (flags.audit === false) return;
30
- const auditDir = path.join(targetPath, ".agent", "audit");
31
- if (!fs.existsSync(auditDir)) {
32
- try { fs.mkdirSync(auditDir, { recursive: true }); } catch (e) { }
33
- }
34
- const filename = `ide-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
35
- try {
36
- fs.writeFileSync(path.join(auditDir, filename), lines.join("\n") + "\n", "utf8");
37
- } catch (e) {
38
- console.error("⚠️ Falha ao gravar auditoria:", e.message);
39
- }
28
+ writeCliAudit(targetPath, lines, flags, "ide");
40
29
  }
41
30
 
42
31
  /**
@@ -184,7 +173,7 @@ async function runDoctor({ targetPath }) {
184
173
 
185
174
  // Verificar rules
186
175
  const rulesDir = path.join(agentDir, "rules");
187
- const requiredRules = ["CONSENT_FIRST.md", "ROUTER_PROTOCOL.md"];
176
+ const requiredRules = ["CONSENT_FIRST.md", "ROUTER_PROTOCOL.md", "SECURITY.md", "WEB_AUTOMATION.md"];
188
177
  for (const rule of requiredRules) {
189
178
  checks.push({ name: `rules/${rule}`, ok: fs.existsSync(path.join(rulesDir, rule)) });
190
179
  }
@@ -200,6 +189,11 @@ async function runDoctor({ targetPath }) {
200
189
  const hooksDir = path.join(agentDir, "hooks");
201
190
  checks.push({ name: "hooks/pre-tool-use.js", ok: fs.existsSync(path.join(hooksDir, "pre-tool-use.js")) });
202
191
 
192
+ // Verificar State Persistence
193
+ const stateDir = path.join(agentDir, "state");
194
+ checks.push({ name: "state/mission_control.json", ok: fs.existsSync(path.join(stateDir, "mission_control.json")) });
195
+ checks.push({ name: "state/MEMORY.md", ok: fs.existsSync(path.join(stateDir, "MEMORY.md")) });
196
+
203
197
  // Exibir resultado
204
198
  let allOk = true;
205
199
  for (const c of checks) {
package/lib/cli/init.js CHANGED
@@ -13,6 +13,7 @@ const path = require("path");
13
13
  const readline = require("readline");
14
14
  const { initConfigDefaults, writeJsonSafe } = require("../config");
15
15
  const { detectContext, getAuditHeader } = require("../context");
16
+ const { writeCliAudit } = require("../utils/audit-writer");
16
17
 
17
18
  // Caminho dos templates incluídos no pacote
18
19
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
@@ -26,20 +27,9 @@ function safeRel(targetPath, p) {
26
27
  return path.relative(targetPath, p);
27
28
  }
28
29
 
30
+ // writeAudit extraído para lib/utils/audit-writer.js (DRY)
29
31
  function writeAudit(targetPath, lines, flags) {
30
- if (flags.audit === false) return;
31
- const auditDir = path.join(targetPath, ".agent", "audit");
32
- if (!fs.existsSync(auditDir)) {
33
- // Tenta criar apenas se estivermos em modo apply, mas aqui já devemos estar
34
- try { fs.mkdirSync(auditDir, { recursive: true }); } catch (e) { }
35
- }
36
- const filename = `init-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
37
- const auditPath = path.join(auditDir, filename);
38
- try {
39
- fs.writeFileSync(auditPath, lines.join("\n") + "\n", "utf8");
40
- } catch (e) {
41
- console.error("⚠️ Falha ao gravar auditoria:", e.message);
42
- }
32
+ writeCliAudit(targetPath, lines, flags, "init");
43
33
  }
44
34
 
45
35
  /**
@@ -0,0 +1,25 @@
1
+ const path = require("path");
2
+
3
+ module.exports = {
4
+ async run({ targetPath, flags, templatesDir }) {
5
+ // Regras propostas pelo usuario:
6
+ // - default é PLAN (flags.plan true no bin/openclaw.js)
7
+ // - APPLY só com --apply
8
+ // - Não alterar nada fora de --apply
9
+ const wizard = require(path.join(__dirname, "..", "setup", "config_wizard.js"));
10
+
11
+ const base = targetPath || process.cwd();
12
+
13
+ // Mantendo consistência com os outros comandos que exportam .run() e recebem { targetPath, flags }
14
+ return wizard({
15
+ base,
16
+ flags: {
17
+ plan: flags.plan !== false, // default true
18
+ apply: !!flags.apply, // só executa com apply
19
+ yes: !!flags.yes, // skip prompts (ainda assim não faz nada se não apply)
20
+ force: !!flags.force, // para ações destrutivas (se você quiser usar)
21
+ },
22
+ templatesDir,
23
+ });
24
+ }
25
+ };
package/lib/cli/update.js CHANGED
@@ -14,6 +14,7 @@ const path = require("path");
14
14
  const crypto = require("crypto");
15
15
  const readline = require("readline");
16
16
  const { detectContext, getAuditHeader } = require("../context");
17
+ const { writeCliAudit } = require("../utils/audit-writer");
17
18
 
18
19
  // Caminho dos templates incluídos no pacote
19
20
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
@@ -27,19 +28,9 @@ function safeRel(targetPath, p) {
27
28
  return path.relative(targetPath, p);
28
29
  }
29
30
 
31
+ // writeAudit extraído para lib/utils/audit-writer.js (DRY)
30
32
  function writeAudit(targetPath, lines, flags) {
31
- if (flags.audit === false) return;
32
- const auditDir = path.join(targetPath, ".agent", "audit");
33
- if (!fs.existsSync(auditDir)) {
34
- try { fs.mkdirSync(auditDir, { recursive: true }); } catch (e) { }
35
- }
36
- const filename = `update-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
37
- const auditPath = path.join(auditDir, filename);
38
- try {
39
- fs.writeFileSync(auditPath, lines.join("\n") + "\n", "utf8");
40
- } catch (e) {
41
- console.error("⚠️ Falha ao gravar auditoria:", e.message);
42
- }
33
+ writeCliAudit(targetPath, lines, flags, "update");
43
34
  }
44
35
 
45
36
  /**
@@ -50,14 +41,36 @@ function fileHash(filePath) {
50
41
  return crypto.createHash("sha256").update(content).digest("hex");
51
42
  }
52
43
 
44
+ /**
45
+ * Simples gerador de diff rústico mas efetivo para CLI
46
+ */
47
+ function simpleDiff(oldStr, newStr) {
48
+ const oldLines = oldStr.split("\n");
49
+ const newLines = newStr.split("\n");
50
+ let diffStr = "";
51
+
52
+ const maxLength = Math.max(oldLines.length, newLines.length);
53
+ let diffCount = 0;
54
+
55
+ for (let i = 0; i < maxLength && diffCount < 10; i++) {
56
+ if (oldLines[i] !== newLines[i]) {
57
+ if (oldLines[i] !== undefined) diffStr += `\x1b[31m- ${oldLines[i]}\x1b[0m\n`;
58
+ if (newLines[i] !== undefined) diffStr += `\x1b[32m+ ${newLines[i]}\x1b[0m\n`;
59
+ diffCount++;
60
+ }
61
+ }
62
+ if (diffCount >= 10) diffStr += `\x1b[90m... (diff truncado para 10 linhas)\x1b[0m\n`;
63
+
64
+ return diffStr || " (Nenhuma diferença detetada no payload comparável)";
65
+ }
66
+
53
67
  /**
54
68
  * Analisa atualizações necessárias.
55
69
  * Retorna lista de ações planejadas.
56
70
  */
57
71
  function planUpdates(src, dest, actions = { added: [], updated: [], skipped: [] }) {
58
72
  if (!fs.existsSync(dest)) {
59
- // Diretório não existe no destino, será criado implicitamente na cópia
60
- // Mas a lógica recursiva precisa entrar
73
+ // Diretório não existe no destino, será acompanhado na copia de arquivos
61
74
  }
62
75
 
63
76
  const entries = fs.readdirSync(src, { withFileTypes: true });
@@ -124,7 +137,9 @@ async function run({ targetPath, flags }) {
124
137
  }
125
138
  if (actions.updated.length > 0) {
126
139
  console.log(`\n🔄 Modificados (${actions.updated.length}):`);
127
- actions.updated.forEach(a => console.log(` ~ UPDATE ${safeRel(targetPath, a.dest)} (Backup gerado)`));
140
+ actions.updated.forEach(a => {
141
+ console.log(` ~ UPDATE ${safeRel(targetPath, a.dest)} (Exige confirmação interativa)`);
142
+ });
128
143
  }
129
144
  if (actions.skipped.length > 0) {
130
145
  console.log(`\n⏭️ Ignorados (${actions.skipped.length} arquivos idênticos)`);
@@ -137,20 +152,21 @@ async function run({ targetPath, flags }) {
137
152
 
138
153
  if (planMode) {
139
154
  console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
140
- console.log(" Para aplicar, rode: npx openclaw update --apply");
155
+ console.log(" Dica: a opção --merge foi desativada no modo interativo para forçar diff por arquivo.");
156
+ console.log(" Para aplicar e resolver conflitos: npx openclaw update --apply");
141
157
  return;
142
158
  }
143
159
 
144
- // 3. Confirmação
145
- if (!flags.yes) {
146
- const ok = await ask("\nAplicar este plano? (y/N): ");
160
+ // 3. Confirmação inicial
161
+ if (!flags.yes && actions.updated.length === 0) {
162
+ const ok = await ask("\nAplicar e copiar arquivos novos? (y/N): ");
147
163
  if (ok.toLowerCase() !== "y") {
148
164
  console.log("⏹️ Cancelado.");
149
165
  return;
150
166
  }
151
167
  }
152
168
 
153
- // 4. Execução
169
+ // 4. Execução Interativa Segura (Conflitos file-by-file)
154
170
  try {
155
171
  console.log("\n🚀 Executando atualizações...");
156
172
 
@@ -160,18 +176,42 @@ async function run({ targetPath, flags }) {
160
176
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
161
177
  }
162
178
 
179
+ // Action: Added
163
180
  for (const action of actions.added) {
164
181
  ensureDir(action.dest);
165
182
  fs.copyFileSync(action.src, action.dest);
166
183
  audit.push(`- ACT: CREATED ${safeRel(targetPath, action.dest)}`);
167
184
  }
168
185
 
186
+ // Action: Updated (Conflict Resolver)
169
187
  for (const action of actions.updated) {
170
188
  ensureDir(action.dest);
171
- const backupPath = action.dest + ".bak";
172
- fs.copyFileSync(action.dest, backupPath);
173
- fs.copyFileSync(action.src, action.dest);
174
- audit.push(`- ACT: UPDATED ${safeRel(targetPath, action.dest)} (Backup: ${path.basename(backupPath)})`);
189
+ const rPath = safeRel(targetPath, action.dest);
190
+
191
+ let overwrite = flags.yes || flags.force;
192
+
193
+ if (!overwrite) {
194
+ console.log(`\n⚠️ CONFLITO DETECTADO: ${rPath}`);
195
+ console.log("------------------------------------------------");
196
+ const oldContent = fs.readFileSync(action.dest, "utf-8");
197
+ const newContent = fs.readFileSync(action.src, "utf-8");
198
+ console.log(simpleDiff(oldContent, newContent));
199
+ console.log("------------------------------------------------");
200
+
201
+ const ans = await ask(`Substituir a sua versão de ${rPath} pelo código acima? [y/N]: `);
202
+ overwrite = ans.toLowerCase() === "y";
203
+ }
204
+
205
+ if (overwrite) {
206
+ const backupPath = action.dest + ".bak";
207
+ fs.copyFileSync(action.dest, backupPath);
208
+ fs.copyFileSync(action.src, action.dest);
209
+ console.log(`✅ Sobrescrito: ${rPath} (Backup guardado localmente: .bak)`);
210
+ audit.push(`- ACT: UPDATED ${rPath} (Backup: ${path.basename(backupPath)})`);
211
+ } else {
212
+ console.log(`⏭️ Ignorado (Mantido customização em ${rPath})`);
213
+ audit.push(`- ACT: SKIPPED UPDATE FOR CUSTOMIZED FILE ${rPath}`);
214
+ }
175
215
  }
176
216
 
177
217
  console.log("\n✨ Atualização concluída com sucesso!");
@@ -1,188 +1,266 @@
1
- #!/usr/bin/env node
2
- /**
3
- * OpenClaw OS - Universal Setup Wizard
4
- *
5
- * Orquestrador principal que delega para os módulos lib/:
6
- * - detect.js → detecção de ambiente (Docker/WSL2/Mac/Linux/VPS)
7
- * - config.js → leitura/escrita JSON atômica + defaults
8
- * - security.js → tokens, masking e verificação de porta
9
- * - channels.js → validação e configuração de canais
10
- *
11
- * Princípios:
12
- * - Seguro por padrão: bind localhost + auth token
13
- * - Cross-platform: detecção automática de ambiente
14
- * - Não destrutivo: nunca sobrescreve sem confirmação
15
- *
16
- * @module config_wizard
17
- * @version 2.0.0
18
- * @author OpenClaw DevOps
19
- */
1
+ // lib/setup/config_wizard.js
20
2
  const fs = require("fs");
21
3
  const os = require("os");
22
4
  const path = require("path");
23
- const readline = require("readline");
5
+ const crypto = require("crypto");
24
6
 
25
- // Módulos extraídos para lib/ — cada um com responsabilidade única
7
+ // Módulos extraídos para lib/
26
8
  const { detectEnvironment } = require("../detect");
27
- const { readJsonSafe, writeJsonSafe, ensureFile, initConfigDefaults } = require("../config");
9
+ const { readJsonSafe, writeJsonSafe, initConfigDefaults } = require("../config");
28
10
  const { mask, generateToken, portInUse } = require("../security");
29
11
  const { supportedChannels, configureChannel } = require("../channels");
30
12
 
31
- /** Interface readline para perguntas interativas */
32
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
33
-
34
- /**
35
- * Faz uma pergunta ao usuário via stdin e retorna a resposta trimada.
36
- * @param {string} q - A pergunta a ser exibida
37
- * @returns {Promise<string>} Resposta do usuário (trimada)
38
- */
39
- function ask(q) { return new Promise(res => rl.question(q, ans => res(ans.trim()))); }
40
-
41
- /**
42
- * Função principal do wizard de setup.
43
- * Orquestra todo o fluxo interativo:
44
- * 1. Detecta ambiente
45
- * 2. Configura gateway (bind + auth)
46
- * 3. Gera/solicita token de autenticação
47
- * 4. Sugere sandbox em VPS
48
- * 5. Configura canais (Telegram/Discord/WhatsApp)
49
- * 6. Configura allowlist de filesystem
50
- * 7. Cria arquivos de persistência (MEMORY.md, SOUL.md, AGENTS.md)
51
- * 8. Verifica porta 18789
52
- * 9. Sugere hardening em VPS
53
- * @returns {Promise<void>}
54
- */
55
- async function main() {
56
- console.log("\n🧠 OpenClaw OS — Universal Setup Wizard\n");
57
-
58
- // --- 1. Detecção de ambiente (delegado para lib/detect) ---
13
+ // util simples
14
+ function exists(p) { try { return fs.existsSync(p); } catch { return false; } }
15
+
16
+ async function ask(prompt) {
17
+ const readline = require("readline");
18
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
19
+ return new Promise((res) => rl.question(prompt, (ans) => { rl.close(); res(ans.trim()); }));
20
+ }
21
+
22
+ // CONSENT-FIRST: cria arquivo somente com apply + confirmação
23
+ async function ensureFileWithConsent({ filePath, content, flags }) {
24
+ if (exists(filePath)) {
25
+ console.log(`✅ KEEP ${path.basename(filePath)} (já existe)`);
26
+ return { changed: false };
27
+ }
28
+
29
+ console.log(`📝 PLAN criar ${path.basename(filePath)} (não existe)`);
30
+
31
+ if (!flags.apply) {
32
+ return { changed: false, planned: true };
33
+ }
34
+
35
+ if (!flags.yes) {
36
+ const ok = (await ask(`Criar ${path.basename(filePath)}? (y/N): `)).toLowerCase() === "y";
37
+ if (!ok) {
38
+ console.log(`⏹️ SKIP ${path.basename(filePath)}`);
39
+ return { changed: false };
40
+ }
41
+ }
42
+
43
+ fs.writeFileSync(filePath, content, "utf8");
44
+ console.log(`✅ DONE ${path.basename(filePath)}`);
45
+ return { changed: true };
46
+ }
47
+
48
+ function deepClone(obj) {
49
+ return JSON.parse(JSON.stringify(obj));
50
+ }
51
+
52
+ function diffKeys(before, after) {
53
+ // diff simples: mostra chaves alteradas
54
+ const changes = [];
55
+ const walk = (a, b, prefix = "") => {
56
+ const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]);
57
+ for (const k of keys) {
58
+ const p = prefix ? `${prefix}.${k}` : k;
59
+ const va = a ? a[k] : undefined;
60
+ const vb = b ? b[k] : undefined;
61
+ const oba = va && typeof va === "object" && !Array.isArray(va);
62
+ const obb = vb && typeof vb === "object" && !Array.isArray(vb);
63
+ if (oba && obb) walk(va, vb, p);
64
+ else if (JSON.stringify(va) !== JSON.stringify(vb)) changes.push(p);
65
+ }
66
+ };
67
+ walk(before, after, "");
68
+ return changes;
69
+ }
70
+
71
+ module.exports = async function configWizard({ base, flags, templatesDir } = {}) {
72
+ const targetBase = base || process.cwd();
73
+ const f = flags || { plan: true, apply: false, yes: false, force: false };
74
+
75
+ // Segurança: default é PLAN (se alguém chamar sem flags)
76
+ if (!("apply" in f) && !("plan" in f)) {
77
+ f.plan = true;
78
+ f.apply = false;
79
+ }
80
+
81
+ console.log("\n🧙 OpenClaw Setup Wizard — CONSENT-FIRST");
82
+ console.log(`📍 Base: ${targetBase}`);
83
+ console.log(`🧪 Mode: ${f.apply ? "APPLY" : "PLAN (read-only)"}`);
84
+
59
85
  const env = detectEnvironment();
60
- console.log(`Ambiente detectado: ${env}`);
61
-
62
- const base = process.cwd();
63
- const configPath = path.join(base, "openclaw.json");
64
-
65
- // --- 2. Leitura da configuração existente (delegado para lib/config) ---
66
- let config = {};
67
- if (fs.existsSync(configPath)) {
68
- const parsed = readJsonSafe(configPath);
69
- if (!parsed) {
70
- console.error("✖ openclaw.json existe mas não é um JSON válido. Corrija e rode novamente.");
71
- process.exit(2);
86
+ console.log(`🌍 Ambiente: ${env}`);
87
+
88
+ const configPath = path.join(targetBase, "openclaw.json");
89
+ const hasConfig = exists(configPath);
90
+
91
+ // 1) Inspecionar contexto
92
+ console.log("\n1) Contexto:");
93
+ console.log(`- openclaw.json: ${hasConfig ? "encontrado" : "não encontrado"}`);
94
+
95
+ // Se não tem config, em PLAN só sugere; em APPLY pergunta se quer criar.
96
+ let config = hasConfig ? readJsonSafe(configPath) : null;
97
+ if (!config) {
98
+ console.log("\n📝 PLAN criar openclaw.json (não existe ou inválido)");
99
+ if (!f.apply) {
100
+ console.log("ℹ️ Dica: rode `openclaw setup --apply` para criar/configurar.");
101
+ } else {
102
+ if (!f.yes) {
103
+ const ok = (await ask("Criar openclaw.json básico? (y/N): ")).toLowerCase() === "y";
104
+ if (!ok) {
105
+ console.log("❎ Cancelado. Nenhuma alteração feita.");
106
+ return;
107
+ }
108
+ }
109
+ config = {};
72
110
  }
73
- config = parsed;
74
111
  }
75
112
 
76
- // Inicializa seções padrão sem sobrescrever existentes
77
- config = initConfigDefaults(config);
113
+ // Se chegou aqui e config ainda é null (por PLAN, por exemplo), vamos simular o defaults para o diff
114
+ if (!config) config = {};
78
115
 
79
- let needWrite = !fs.existsSync(configPath);
116
+ // 2) Propor ajustes seguros (NÃO aplicar ainda)
117
+ const before = deepClone(config);
80
118
 
81
- // --- 3. gateway.bind: segurança por padrão (localhost only) ---
82
- const desiredBind = "127.0.0.1";
83
- if (config.gateway.bind !== desiredBind) {
84
- const ans = await ask(`gateway.bind está "${config.gateway.bind ?? "(vazio)"}". Ajustar para "${desiredBind}"? (y/n): `);
85
- if (ans.toLowerCase() === "y") { config.gateway.bind = desiredBind; needWrite = true; }
86
- } else {
87
- console.log("✔ gateway.bind já está seguro (127.0.0.1)");
119
+ config = initConfigDefaults(config);
120
+
121
+ // gateway.bind seguro
122
+ config.gateway = config.gateway || {};
123
+ config.gateway.bind = config.gateway.bind || "127.0.0.1";
124
+ if (config.gateway.bind !== "127.0.0.1") {
125
+ if (f.apply) {
126
+ const ans = f.yes ? "y" : await ask(`gateway.bind está "${config.gateway.bind}". Ajustar para "127.0.0.1"? (y/n): `);
127
+ if (ans.toLowerCase() === "y") config.gateway.bind = "127.0.0.1";
128
+ } else {
129
+ config.gateway.bind = "127.0.0.1"; // Simulando a mudança para o diff no modo PLAN
130
+ }
88
131
  }
89
132
 
90
- // --- 4. auth.mode: token obrigatório ---
91
- const desiredAuthMode = "token";
92
- if (config.auth.mode !== desiredAuthMode) {
93
- const ans = await ask(`auth.mode está "${config.auth.mode ?? "(vazio)"}". Ajustar para "${desiredAuthMode}"? (y/n): `);
94
- if (ans.toLowerCase() === "y") { config.auth.mode = desiredAuthMode; needWrite = true; }
95
- } else {
96
- console.log(" auth.mode está em token");
133
+ // auth mode seguro
134
+ config.auth = config.auth || {};
135
+ config.auth.mode = config.auth.mode || "token";
136
+ if (config.auth.mode !== "token") {
137
+ if (f.apply) {
138
+ const ans = f.yes ? "y" : await ask(`auth.mode está "${config.auth.mode}". Ajustar para "token"? (y/n): `);
139
+ if (ans.toLowerCase() === "y") config.auth.mode = "token";
140
+ } else {
141
+ config.auth.mode = "token"; // Simulação
142
+ }
97
143
  }
98
144
 
99
- // --- 5. Geração de token de autenticação (delegado para lib/security) ---
100
- if (config.auth.mode === "token") {
101
- config.auth.token = config.auth.token || "";
102
- if (!config.auth.token) {
103
- const ans = await ask("Nenhum token encontrado. Gerar um token seguro automaticamente? (y/n): ");
104
- if (ans.toLowerCase() === "y") {
105
- config.auth.token = generateToken();
106
- console.log(`✔ Token gerado: ${mask(config.auth.token)} (salvo no openclaw.json)`);
107
- needWrite = true;
145
+ if (!config.auth.token) {
146
+ // gera token somente se APPLY e com consentimento
147
+ if (f.apply) {
148
+ if (!f.yes) {
149
+ const ok = (await ask("Gerar token de auth automaticamente? (y/N): ")).toLowerCase() === "y";
150
+ if (ok) config.auth.token = generateToken();
108
151
  } else {
109
- const manual = await ask("Cole um token: ");
110
- if (manual) { config.auth.token = manual; needWrite = true; }
152
+ config.auth.token = generateToken();
111
153
  }
112
154
  } else {
113
- console.log(`✔ Token já configurado (${mask(config.auth.token)})`);
155
+ config.auth.token = "<TOKEN_GERADO_NO_APPLY>"; // Simulação para o diff
156
+ }
157
+ }
158
+
159
+ // 3) Canais: apenas PLANO por padrão; em APPLY perguntar
160
+ console.log("\n2) Canais disponíveis:", supportedChannels().join(", "));
161
+ if (!f.apply) {
162
+ console.log("🧭 PLAN (não vou alterar canais sem --apply)");
163
+ } else {
164
+ const pick = f.yes ? "" : await ask(`Qual canal ativar? (${supportedChannels().join("/")}/nenhum): `);
165
+ const channelChoice = (pick || "").toLowerCase();
166
+ if (supportedChannels().includes(channelChoice)) {
167
+ if (!f.yes) console.log(`ℹ️ Vou configurar '${channelChoice}'.`);
168
+ await configureChannel(config, channelChoice, ask);
114
169
  }
115
170
  }
116
171
 
117
- // --- 6. Sandbox: sugestão para VPS rodando como root ---
118
- if (env === "linux-vps-root") {
119
- if (config.sandbox.mode !== "non-main") {
120
- const ans = await ask(`Detectei VPS/root. Ativar sandbox mode "non-main" para isolar execuções? (y/n): `);
121
- if (ans.toLowerCase() === "y") { config.sandbox.mode = "non-main"; needWrite = true; }
172
+ // Filesystem allowlist
173
+ if (f.apply) {
174
+ console.log("\n📁 Acesso a arquivos locais (mínimo necessário)");
175
+ console.log("Adicione apenas pastas que o OpenClaw realmente precisa acessar.");
176
+ const addPath = await ask("Adicionar uma pasta allowlist agora? (caminho ou ENTER para pular): ");
177
+ if (addPath) {
178
+ const resolved = addPath.replace(/^~\//, os.homedir() + path.sep);
179
+ config.filesystem.allowlist.push(resolved);
180
+ console.log(`✔ Allowlist adicionada: ${resolved}`);
122
181
  }
123
182
  }
124
183
 
125
- // --- 7. Configuração de canais (delegado para lib/channels) ---
126
- console.log("\n📣 Canais (opcional)");
127
- const channelList = supportedChannels().join("/");
128
- const ch = await ask(`Ativar agora? (${channelList}/nenhum): `);
129
- const channelChoice = ch.toLowerCase();
184
+ // 4) Mostrar plano de mudanças no JSON
185
+ const changedPaths = diffKeys(before, config);
186
+ console.log("\n3) Plano de mudanças em openclaw.json:");
187
+ if (changedPaths.length === 0) console.log("- (nenhuma mudança necessária)");
188
+ else changedPaths.forEach(p => console.log(`- ${p}`));
130
189
 
131
- if (supportedChannels().includes(channelChoice)) {
132
- // configureChannel recebe uma função ask injetável (testável)
133
- const configured = await configureChannel(config, channelChoice, ask);
134
- if (configured) needWrite = true;
190
+ // 5) Aplicar mudanças somente com consentimento
191
+ if (!f.apply) {
192
+ console.log("\n✅ Setup finalizado em PLAN. Nenhuma alteração aplicada.");
135
193
  } else {
136
- console.log("↪ Pulando canais.");
194
+ let shouldWrite = true;
195
+ if (changedPaths.length > 0) {
196
+ if (!f.yes) {
197
+ const ok = (await ask("\nAplicar alterações em openclaw.json? (y/N): ")).toLowerCase() === "y";
198
+ if (!ok) {
199
+ console.log("❎ Cancelado. openclaw.json não será alterado.");
200
+ shouldWrite = false;
201
+ }
202
+ }
203
+ if (shouldWrite) {
204
+ writeJsonSafe(configPath, config);
205
+ console.log(`✅ DONE openclaw.json atualizado em ${configPath}`);
206
+ }
207
+ } else {
208
+ if (!hasConfig && shouldWrite) {
209
+ writeJsonSafe(configPath, config);
210
+ console.log(`✅ DONE openclaw.json criado em ${configPath}`);
211
+ }
212
+ }
137
213
  }
138
214
 
139
- // --- 8. Filesystem allowlist: princípio do menor privilégio ---
140
- console.log("\n📁 Acesso a arquivos locais (mínimo necessário)");
141
- console.log("Adicione apenas pastas que o OpenClaw realmente precisa acessar.");
142
- const addPath = await ask("Adicionar uma pasta allowlist agora? (caminho ou ENTER para pular): ");
143
- if (addPath) {
144
- const resolved = addPath.replace(/^~\//, os.homedir() + path.sep);
145
- config.filesystem.allowlist.push(resolved);
146
- needWrite = true;
147
- console.log(`✔ Allowlist adicionada: ${resolved}`);
148
- }
215
+ // 6) Persistência por projeto (IDE option B): somente com consentimento
216
+ const agentDir = path.join(targetBase, ".agent");
217
+ const stateDir = path.join(agentDir, "state");
218
+
219
+ console.log("\n4) Persistência por projeto (.agent/state):");
220
+ console.log("🧭 PLAN criar MEMORY.md / SOUL.md / AGENTS.md somente com consentimento");
149
221
 
150
- // --- 9. Arquivos de persistência (delegado para lib/config.ensureFile) ---
151
- ensureFile(path.join(base, "MEMORY.md"), "# MEMORY.md\n\n- Preferências e notas persistentes do OpenClaw.\n");
152
- ensureFile(path.join(base, "SOUL.md"), "# SOUL.md\n\n- Identidade e regras de comportamento (ver AGENTS.md).\n");
153
- ensureFile(path.join(base, "AGENTS.md"), "# AGENTS.md\n\nVocê é um SysAdmin Proativo. Use VPN-first, bind localhost e token.\n");
154
-
155
- // --- 10. Checagem de porta (delegado para lib/security.portInUse) ---
156
- const port = 18789;
157
- console.log("\n🔎 Checagens rápidas");
158
- const inUse = await portInUse("127.0.0.1", port);
159
- if (inUse) console.log(`ℹ Porta ${port} respondeu em 127.0.0.1 (ok se OpenClaw está rodando).`);
160
- else console.log(`ℹ Porta ${port} não respondeu em 127.0.0.1 (ok se ainda não iniciou).`);
161
-
162
- // --- 11. Hardening: recomendações para VPS ---
163
- if (env === "linux-vps-root") {
164
- console.log("\n🛡 Hardening (recomendado)");
165
- console.log("- Crie um usuário não-root (ex: clawuser) e desative login por senha no SSH.");
166
- console.log("- Ative firewall (UFW) e fail2ban.");
167
- console.log("- Exponha publicamente apenas WireGuard (UDP) se usar VPN.");
222
+ // criamos state se já existir .agent ou se usuário quiser criar (em apply)
223
+ if (f.apply && !exists(agentDir)) {
224
+ if (!f.yes) {
225
+ const ok = (await ask("Criar pasta .agent/ (para estado por projeto)? (y/N): ")).toLowerCase() === "y";
226
+ if (ok) fs.mkdirSync(stateDir, { recursive: true });
227
+ } else {
228
+ fs.mkdirSync(stateDir, { recursive: true });
229
+ }
168
230
  }
169
231
 
170
- // --- 12. Persistência da configuração (delegado para lib/config.writeJsonSafe) ---
171
- if (needWrite) {
172
- writeJsonSafe(configPath, config);
173
- console.log("\n✔ openclaw.json atualizado/criado com segurança.");
232
+ if (exists(agentDir) || exists(stateDir)) {
233
+ if (!exists(stateDir) && f.apply) fs.mkdirSync(stateDir, { recursive: true });
234
+
235
+ await ensureFileWithConsent({
236
+ filePath: path.join(stateDir, "MEMORY.md"),
237
+ content: "# MEMORY.md (por projeto)\n\n- Resumos úteis, decisões e preferências persistentes do projeto.\n",
238
+ flags: f
239
+ });
240
+
241
+ await ensureFileWithConsent({
242
+ filePath: path.join(stateDir, "SOUL.md"),
243
+ content: "# SOUL.md\n\n- Identidade e regras de comportamento do agente.\n",
244
+ flags: f
245
+ });
246
+
247
+ await ensureFileWithConsent({
248
+ filePath: path.join(stateDir, "AGENTS.md"),
249
+ content: "# AGENTS.md\n\n- Agentes e personas disponíveis neste projeto.\n",
250
+ flags: f
251
+ });
174
252
  } else {
175
- console.log("\n✔ Nenhuma alteração necessária no openclaw.json.");
253
+ console.log("ℹ️ .agent não existe neste projeto. (ok) — nada será criado em PLAN.");
176
254
  }
177
255
 
178
- console.log("\n✅ Setup finalizado.");
179
- console.log("\n🌐 CONTROL UI: http://127.0.0.1:18789");
180
- console.log(" Acesse para gerenciar seus agentes visualmente.\n");
181
- console.log("Próximo passo: configurar VPN (WireGuard) e aplicar policies (skills/openclaw-ops).");
182
- rl.close();
183
- }
256
+ // Checagens adicionais informativas (Healthcheck da porta)
257
+ if (f.apply) {
258
+ const port = 18789;
259
+ console.log("\n🔎 Checagens rápidas");
260
+ const inUse = await portInUse("127.0.0.1", port);
261
+ if (inUse) console.log(`ℹ Porta ${port} respondeu em 127.0.0.1 (ok se OpenClaw está rodando).`);
262
+ else console.log(`ℹ Porta ${port} não respondeu em 127.0.0.1 (ok se ainda não iniciou).`);
263
+ }
184
264
 
185
- main().catch((e) => {
186
- console.error("✖ Erro:", e && e.message ? e.message : e);
187
- process.exit(1);
188
- });
265
+ console.log("\n✅ Wizard concluído.");
266
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Utilitário compartilhado para gravação de audit logs CLI.
5
+ *
6
+ * Centraliza a lógica de escrita de audit logs markdown que antes
7
+ * estava duplicada em init.js, update.js e ide.js.
8
+ *
9
+ * @module lib/utils/audit-writer
10
+ */
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+
15
+ /**
16
+ * Grava um log de auditoria em formato markdown no diretório .agent/audit/.
17
+ *
18
+ * @param {string} targetPath — diretório raiz do projeto
19
+ * @param {string[]} lines — linhas do relatório de auditoria
20
+ * @param {object} flags — flags do CLI (verifica flags.audit)
21
+ * @param {string} prefix — prefixo do arquivo (ex: "init", "update", "ide")
22
+ */
23
+ function writeCliAudit(targetPath, lines, flags, prefix = "cli") {
24
+ // Se auditoria desabilitada via flag --no-audit, não grava
25
+ if (flags.audit === false) return;
26
+
27
+ const auditDir = path.join(targetPath, ".agent", "audit");
28
+ if (!fs.existsSync(auditDir)) {
29
+ try {
30
+ fs.mkdirSync(auditDir, { recursive: true });
31
+ } catch (e) {
32
+ // Silencia erro se não conseguir criar diretório
33
+ }
34
+ }
35
+
36
+ const filename = `${prefix}-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
37
+ const auditPath = path.join(auditDir, filename);
38
+
39
+ try {
40
+ fs.writeFileSync(auditPath, lines.join("\n") + "\n", "utf8");
41
+ } catch (e) {
42
+ console.error("⚠️ Falha ao gravar auditoria:", e.message);
43
+ }
44
+ }
45
+
46
+ module.exports = { writeCliAudit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabioforest/openclaw",
3
- "version": "3.7.1",
3
+ "version": "3.9.0",
4
4
  "description": "Agentes autônomos para engenharia de software",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -44,4 +44,4 @@
44
44
  "engines": {
45
45
  "node": ">=18"
46
46
  }
47
- }
47
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: Roteamento de Skill Padrão e Seguro (Chat-first Workflow)
3
+ ---
4
+ # Chat-first Workflow (Roteamento Inteligente)
5
+
6
+ O OpenClaw adota um fluxo "chat-first" nas interações com o desenvolvedor, onde o Agent sempre entra como o Roteador central (descrito em `.agent/skills/openclaw-router/SKILL.md`) antes de selecionar qualquer action.
7
+
8
+ ## Passos Operacionais (Agent Actions)
9
+ 1. **INSPECT**: A partir do prompt do usuário, identifique a real necessidade invocando a análise do repositório/arquivos locais. Nunca tome decisões sem checar o ambiente `.agent/`.
10
+ 2. **ROUTE**: Baseado na intenção identificada, ative mentalmente a Skill correspondente. Se o usuário pedir um script de deploy, ative `devops-toolkit`. Se pedir frontend component, ative `openclaw-dev`.
11
+ 3. **PLAN**: Com a Skill ativada, escreva um `.md` no diretório temporário/contextual descrevendo sua tática baseada nas regras do repositório para resolver o problema, solicitando feedback com o comando correspondente ao final da sua mensagem (`/plan`, `/execute`).
12
+ 4. **CONSENT**: Aguarde a confirmação de que o plano pode ser seguido (salvo explicitamente em `--yes` outputs passados pelo desenvolvedor).
13
+ 5. **APPLY**: Execute os comandos na máquina, gere ou atualize código.
14
+ 6. **AUDIT**: Notifique via CLI Audit (se usando CLI) ou inclua logs para os registros em `MEMORY.md`.
15
+
16
+ > **MANDATORY**: Nunca gere ou modifique configurações sem antes consultar e entender o projeto através do `inspect` e da lista de Skills aprovadas (em `openclaw.json` ou `AGENTS.md`).