@fabioforest/openclaw 3.8.0 → 3.10.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
  /**
@@ -42,7 +43,8 @@ const COMMANDS = {
42
43
  function parseArgs(argv) {
43
44
  const flags = {
44
45
  plan: true, // Default: PLAN mode (read-only)
45
- audit: true // Default: Generate audit logs
46
+ audit: true, // Default: Generate audit logs
47
+ _: [] // Captura subcomandos/args
46
48
  };
47
49
  let command = null;
48
50
 
@@ -61,7 +63,6 @@ function parseArgs(argv) {
61
63
  flags.quiet = true;
62
64
  } else if (arg === "--apply") {
63
65
  flags.apply = true;
64
- flags.plan = false; // Apply overrides Plan default
65
66
  } else if (arg === "--plan") {
66
67
  flags.plan = true;
67
68
  } else if (arg === "--yes" || arg === "-y") {
@@ -70,13 +71,21 @@ function parseArgs(argv) {
70
71
  flags.audit = false;
71
72
  } else if (arg === "--merge") {
72
73
  flags.merge = true;
73
- } else if (!arg.startsWith("-") && !command) {
74
- command = arg;
74
+ } else if (!arg.startsWith("-")) {
75
+ if (!command) {
76
+ command = arg;
77
+ } else {
78
+ flags._.push(arg); // Captura subcomandos/args
79
+ }
75
80
  }
76
81
  }
77
82
 
78
- // Se apply foi passado, plan é false (a menos que forçado explicitamente, mas apply vence na lógica acima)
79
- // Se o usuário não passar nada, plan=true (default)
83
+ // Regra de precedência: apply sempre vence plan
84
+ if (flags.apply) flags.plan = false;
85
+
86
+ // Constrói ajudantes semânticos a partir de _
87
+ flags.subcommand = flags._[0] || null;
88
+ flags.args = flags._.slice(1);
80
89
 
81
90
  return { command, flags };
82
91
  }
@@ -145,18 +154,6 @@ async function main() {
145
154
  // Resolver caminho de destino
146
155
  const targetPath = path.resolve(flags.path || ".");
147
156
 
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
157
  // Verificar se o comando existe
161
158
  if (!COMMANDS[command]) {
162
159
  console.error(`❌ Comando desconhecido: "${command}"`);
package/lib/cli/ide.js CHANGED
@@ -14,6 +14,7 @@ const readline = require("readline");
14
14
  const { detectContext, getAuditHeader } = require("../context");
15
15
  const { copyDirRecursive } = require("./init");
16
16
  const { writeCliAudit } = require("../utils/audit-writer");
17
+ const { guardPlan } = require("../utils/scope_guard");
17
18
 
18
19
  // Caminho dos templates do pacote
19
20
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
@@ -28,21 +29,9 @@ function writeAudit(targetPath, lines, flags) {
28
29
  writeCliAudit(targetPath, lines, flags, "ide");
29
30
  }
30
31
 
31
- /**
32
- * Executa o comando ide.
33
- * Uso: openclaw ide install [--apply] [--force]
34
- * openclaw ide doctor
35
- *
36
- * @param {object} options
37
- * @param {string} options.targetPath — diretório alvo
38
- * @param {object} options.flags — flags do CLI
39
- */
40
32
  async function run({ targetPath, flags }) {
41
- // Detectar sub-comando a partir de args restantes
42
- // O bin/openclaw.js passa o comando "ide", então precisamos
43
- // verificar os args para o sub-comando
44
- const args = process.argv.slice(3);
45
- const subCmd = args.find(a => !a.startsWith("-")) || "install";
33
+ // Detectar sub-comando alinhado com o parseArgs global
34
+ const subCmd = flags.subcommand || "install";
46
35
 
47
36
  if (subCmd === "doctor") {
48
37
  return runDoctor({ targetPath, flags });
@@ -84,6 +73,16 @@ async function runInstall({ targetPath, flags }) {
84
73
  console.log(" ✅ KEEP .agent/state/ (já existe)");
85
74
  }
86
75
 
76
+ // 2.5 Scope Guard
77
+ const intents = { writes: [agentDst, stateDir], deletes: [], overwrites: [] };
78
+ // Se for um force, significa que um overwrite/delete de diretório ocorrerá
79
+ if (fs.existsSync(agentDst) && flags.force) {
80
+ intents.deletes.push(agentDst);
81
+ intents.overwrites.push(agentDst);
82
+ }
83
+
84
+ await guardPlan(targetPath, intents, flags);
85
+
87
86
  if (planMode) {
88
87
  console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
89
88
  console.log(" Para aplicar, rode: npx openclaw ide install --apply");
@@ -173,7 +172,7 @@ async function runDoctor({ targetPath }) {
173
172
 
174
173
  // Verificar rules
175
174
  const rulesDir = path.join(agentDir, "rules");
176
- const requiredRules = ["CONSENT_FIRST.md", "ROUTER_PROTOCOL.md"];
175
+ const requiredRules = ["CONSENT_FIRST.md", "ROUTER_PROTOCOL.md", "SECURITY.md", "WEB_AUTOMATION.md"];
177
176
  for (const rule of requiredRules) {
178
177
  checks.push({ name: `rules/${rule}`, ok: fs.existsSync(path.join(rulesDir, rule)) });
179
178
  }
@@ -189,6 +188,11 @@ async function runDoctor({ targetPath }) {
189
188
  const hooksDir = path.join(agentDir, "hooks");
190
189
  checks.push({ name: "hooks/pre-tool-use.js", ok: fs.existsSync(path.join(hooksDir, "pre-tool-use.js")) });
191
190
 
191
+ // Verificar State Persistence
192
+ const stateDir = path.join(agentDir, "state");
193
+ checks.push({ name: "state/mission_control.json", ok: fs.existsSync(path.join(stateDir, "mission_control.json")) });
194
+ checks.push({ name: "state/MEMORY.md", ok: fs.existsSync(path.join(stateDir, "MEMORY.md")) });
195
+
192
196
  // Exibir resultado
193
197
  let allOk = true;
194
198
  for (const c of checks) {
package/lib/cli/init.js CHANGED
@@ -14,6 +14,7 @@ const readline = require("readline");
14
14
  const { initConfigDefaults, writeJsonSafe } = require("../config");
15
15
  const { detectContext, getAuditHeader } = require("../context");
16
16
  const { writeCliAudit } = require("../utils/audit-writer");
17
+ const { guardPlan } = require("../utils/scope_guard");
17
18
 
18
19
  // Caminho dos templates incluídos no pacote
19
20
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
@@ -107,6 +108,17 @@ async function run({ targetPath, flags }) {
107
108
  actions.push({ type: "NOOP", path: configPath, reason: "Config exists" });
108
109
  }
109
110
 
111
+ // 2.5. Acionar Scope Guard
112
+ const intents = { writes: [], deletes: [], overwrites: [] };
113
+ for (const a of actions) {
114
+ if (a.type === "DELETE_DIR") intents.deletes.push(a.path);
115
+ if (a.type === "CREATE_DIR") intents.writes.push(a.path);
116
+ if (a.type === "CREATE_FILE") intents.writes.push(a.path);
117
+ if (a.type === "COPY_DIR" || a.type === "MERGE_DIR") intents.writes.push(a.to);
118
+ }
119
+
120
+ await guardPlan(targetPath, intents, flags);
121
+
110
122
  // 3. Exibir Plano
111
123
  console.log(`\n🧭 Plano de Execução (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
112
124
  console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
@@ -122,7 +134,7 @@ async function run({ targetPath, flags }) {
122
134
 
123
135
  if (planMode) {
124
136
  console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
125
- console.log(" Para aplicar, rode: npx openclaw init --apply [--merge|--force]");
137
+ console.log(" Para aplicar, rode o comando com --apply");
126
138
  return;
127
139
  }
128
140
 
@@ -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
+ };
@@ -15,6 +15,7 @@ const fs = require("fs");
15
15
  const path = require("path");
16
16
  const readline = require("readline");
17
17
  const { detectContext, getAuditHeader } = require("../context");
18
+ const { guardPlan } = require("../utils/scope_guard");
18
19
 
19
20
  function ask(q) {
20
21
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -103,6 +104,10 @@ async function run({ targetPath, flags }) {
103
104
  console.log(` 🔴 REMOVER openclaw.json`);
104
105
  }
105
106
 
107
+ // Acionar Scope Guard para todos os deletes listados
108
+ const intents = { writes: [], deletes: toRemove.map(i => i.path), overwrites: [] };
109
+ await guardPlan(targetPath, intents, flags);
110
+
106
111
  // Verificar audit logs que seriam perdidos
107
112
  const auditDir = path.join(agentDir, "audit");
108
113
  if (fs.existsSync(auditDir)) {
package/lib/cli/update.js CHANGED
@@ -15,6 +15,7 @@ const crypto = require("crypto");
15
15
  const readline = require("readline");
16
16
  const { detectContext, getAuditHeader } = require("../context");
17
17
  const { writeCliAudit } = require("../utils/audit-writer");
18
+ const { guardPlan } = require("../utils/scope_guard");
18
19
 
19
20
  // Caminho dos templates incluídos no pacote
20
21
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
@@ -41,14 +42,36 @@ function fileHash(filePath) {
41
42
  return crypto.createHash("sha256").update(content).digest("hex");
42
43
  }
43
44
 
45
+ /**
46
+ * Simples gerador de diff rústico mas efetivo para CLI
47
+ */
48
+ function simpleDiff(oldStr, newStr) {
49
+ const oldLines = oldStr.split("\n");
50
+ const newLines = newStr.split("\n");
51
+ let diffStr = "";
52
+
53
+ const maxLength = Math.max(oldLines.length, newLines.length);
54
+ let diffCount = 0;
55
+
56
+ for (let i = 0; i < maxLength && diffCount < 10; i++) {
57
+ if (oldLines[i] !== newLines[i]) {
58
+ if (oldLines[i] !== undefined) diffStr += `\x1b[31m- ${oldLines[i]}\x1b[0m\n`;
59
+ if (newLines[i] !== undefined) diffStr += `\x1b[32m+ ${newLines[i]}\x1b[0m\n`;
60
+ diffCount++;
61
+ }
62
+ }
63
+ if (diffCount >= 10) diffStr += `\x1b[90m... (diff truncado para 10 linhas)\x1b[0m\n`;
64
+
65
+ return diffStr || " (Nenhuma diferença detetada no payload comparável)";
66
+ }
67
+
44
68
  /**
45
69
  * Analisa atualizações necessárias.
46
70
  * Retorna lista de ações planejadas.
47
71
  */
48
72
  function planUpdates(src, dest, actions = { added: [], updated: [], skipped: [] }) {
49
73
  if (!fs.existsSync(dest)) {
50
- // Diretório não existe no destino, será criado implicitamente na cópia
51
- // Mas a lógica recursiva precisa entrar
74
+ // Diretório não existe no destino, será acompanhado na copia de arquivos
52
75
  }
53
76
 
54
77
  const entries = fs.readdirSync(src, { withFileTypes: true });
@@ -105,6 +128,12 @@ async function run({ targetPath, flags }) {
105
128
  const actions = planUpdates(TEMPLATES_DIR, agentDir);
106
129
  const audit = [getAuditHeader(ctx, "update", flags)];
107
130
 
131
+ // 1.5 Acionar Scope Guard
132
+ const intents = { writes: [], deletes: [], overwrites: [] };
133
+ for (const a of actions.added) intents.writes.push(a.dest);
134
+ for (const a of actions.updated) intents.overwrites.push(a.dest);
135
+ await guardPlan(targetPath, intents, flags);
136
+
108
137
  // 2. Exibir Plano
109
138
  console.log(`\n🧭 Plano de Atualização (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
110
139
  console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
@@ -115,7 +144,9 @@ async function run({ targetPath, flags }) {
115
144
  }
116
145
  if (actions.updated.length > 0) {
117
146
  console.log(`\n🔄 Modificados (${actions.updated.length}):`);
118
- actions.updated.forEach(a => console.log(` ~ UPDATE ${safeRel(targetPath, a.dest)} (Backup gerado)`));
147
+ actions.updated.forEach(a => {
148
+ console.log(` ~ UPDATE ${safeRel(targetPath, a.dest)} (Exige confirmação interativa)`);
149
+ });
119
150
  }
120
151
  if (actions.skipped.length > 0) {
121
152
  console.log(`\n⏭️ Ignorados (${actions.skipped.length} arquivos idênticos)`);
@@ -128,20 +159,21 @@ async function run({ targetPath, flags }) {
128
159
 
129
160
  if (planMode) {
130
161
  console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
131
- console.log(" Para aplicar, rode: npx openclaw update --apply");
162
+ console.log(" Dica: a opção --merge foi desativada no modo interativo para forçar diff por arquivo.");
163
+ console.log(" Para aplicar e resolver conflitos: npx openclaw update --apply");
132
164
  return;
133
165
  }
134
166
 
135
- // 3. Confirmação
136
- if (!flags.yes) {
137
- const ok = await ask("\nAplicar este plano? (y/N): ");
167
+ // 3. Confirmação inicial
168
+ if (!flags.yes && actions.updated.length === 0) {
169
+ const ok = await ask("\nAplicar e copiar arquivos novos? (y/N): ");
138
170
  if (ok.toLowerCase() !== "y") {
139
171
  console.log("⏹️ Cancelado.");
140
172
  return;
141
173
  }
142
174
  }
143
175
 
144
- // 4. Execução
176
+ // 4. Execução Interativa Segura (Conflitos file-by-file)
145
177
  try {
146
178
  console.log("\n🚀 Executando atualizações...");
147
179
 
@@ -151,18 +183,42 @@ async function run({ targetPath, flags }) {
151
183
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
152
184
  }
153
185
 
186
+ // Action: Added
154
187
  for (const action of actions.added) {
155
188
  ensureDir(action.dest);
156
189
  fs.copyFileSync(action.src, action.dest);
157
190
  audit.push(`- ACT: CREATED ${safeRel(targetPath, action.dest)}`);
158
191
  }
159
192
 
193
+ // Action: Updated (Conflict Resolver)
160
194
  for (const action of actions.updated) {
161
195
  ensureDir(action.dest);
162
- const backupPath = action.dest + ".bak";
163
- fs.copyFileSync(action.dest, backupPath);
164
- fs.copyFileSync(action.src, action.dest);
165
- audit.push(`- ACT: UPDATED ${safeRel(targetPath, action.dest)} (Backup: ${path.basename(backupPath)})`);
196
+ const rPath = safeRel(targetPath, action.dest);
197
+
198
+ let overwrite = flags.yes || flags.force;
199
+
200
+ if (!overwrite) {
201
+ console.log(`\n⚠️ CONFLITO DETECTADO: ${rPath}`);
202
+ console.log("------------------------------------------------");
203
+ const oldContent = fs.readFileSync(action.dest, "utf-8");
204
+ const newContent = fs.readFileSync(action.src, "utf-8");
205
+ console.log(simpleDiff(oldContent, newContent));
206
+ console.log("------------------------------------------------");
207
+
208
+ const ans = await ask(`Substituir a sua versão de ${rPath} pelo código acima? [y/N]: `);
209
+ overwrite = ans.toLowerCase() === "y";
210
+ }
211
+
212
+ if (overwrite) {
213
+ const backupPath = action.dest + ".bak";
214
+ fs.copyFileSync(action.dest, backupPath);
215
+ fs.copyFileSync(action.src, action.dest);
216
+ console.log(`✅ Sobrescrito: ${rPath} (Backup guardado localmente: .bak)`);
217
+ audit.push(`- ACT: UPDATED ${rPath} (Backup: ${path.basename(backupPath)})`);
218
+ } else {
219
+ console.log(`⏭️ Ignorado (Mantido customização em ${rPath})`);
220
+ audit.push(`- ACT: SKIPPED UPDATE FOR CUSTOMIZED FILE ${rPath}`);
221
+ }
166
222
  }
167
223
 
168
224
  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,112 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const readline = require("readline");
6
+
7
+ /**
8
+ * Utilitário para prompt interativo.
9
+ */
10
+ function askQuestion(query) {
11
+ const rl = readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout,
14
+ });
15
+ return new Promise((resolve) => rl.question(query, (ans) => {
16
+ rl.close();
17
+ resolve(ans.trim());
18
+ }));
19
+ }
20
+
21
+ /**
22
+ * Valida se um caminho pretendido está dentro do diretório seguro.
23
+ * @param {string} targetPath - Caminho do projeto destino.
24
+ * @param {string} fileToMutate - O arquivo/pasta a ser mexido.
25
+ * @returns {boolean} - True se .agent/** ou default safe.
26
+ */
27
+ function isPathInSafeScope(targetPath, fileToMutate) {
28
+ // Escopo seguro default: pasta `.agent` na raiz do targetPath
29
+ const safeScope = path.join(targetPath, ".agent");
30
+ const absoluteMutate = path.resolve(targetPath, fileToMutate);
31
+
32
+ // Arquivos no diretório pai principal que não deveriam ser bloqueados:
33
+ // Padrão do projeto autoriza explicitamente "openclaw.json", ".env.example" no wizard?
34
+ // Para simplificar, focamos na "prisão" primária.
35
+
36
+ // allow openclaw.json default config
37
+ if (absoluteMutate === path.join(targetPath, "openclaw.json")) return true;
38
+
39
+ // Retorna true se começa com o caminho the escopo.
40
+ return absoluteMutate.startsWith(safeScope);
41
+ }
42
+
43
+ /**
44
+ * Escudo de Proteção "Scope Guard"
45
+ * Evita que o CLI apague ou modifique arquivos da aplicação nativa (ex: index.js, package.json do usuário).
46
+ *
47
+ * @param {string} targetPath - Caminho de execução (process.cwd geralmente)
48
+ * @param {object} intents - Objeto contendo listas { writes: [], deletes: [], overwrites: [] } com paths relativos ou absolutos
49
+ * @param {object} flags - As CLI Flags (`force`, `yes`, `apply`)
50
+ */
51
+ async function guardPlan(targetPath, intents = { writes: [], deletes: [], overwrites: [] }, flags = {}) {
52
+ const outOfScope = {
53
+ writes: intents.writes.filter(p => !isPathInSafeScope(targetPath, p)),
54
+ deletes: intents.deletes.filter(p => !isPathInSafeScope(targetPath, p)),
55
+ overwrites: intents.overwrites.filter(p => !isPathInSafeScope(targetPath, p)),
56
+ };
57
+
58
+ const totalRisks = outOfScope.writes.length + outOfScope.deletes.length + outOfScope.overwrites.length;
59
+
60
+ // Se tudo ok (nenhuma fuga da jail), permitimos silenciosamente sem drama
61
+ if (totalRisks === 0) return true;
62
+
63
+ console.log("\n🛡️ [SCOPE GUARD ALERTA DE SEGURANÇA]");
64
+ console.log("-----------------------------------------");
65
+ console.log(`Detectado tentativa de alterar dados FORA do sandbox (.agent/**).`);
66
+
67
+ if (outOfScope.writes.length > 0) {
68
+ console.log("⚠️ Arquivos a GERAR fora do escopo:", outOfScope.writes);
69
+ }
70
+ if (outOfScope.overwrites.length > 0) {
71
+ console.log("🧨 Arquivos a SOBRESCREVER fora do escopo:", outOfScope.overwrites);
72
+ }
73
+ if (outOfScope.deletes.length > 0) {
74
+ console.log("🔥 DELETE SOLICITADO fora do escopo:", outOfScope.deletes);
75
+ }
76
+ console.log("-----------------------------------------\n");
77
+
78
+ // Lógica para modo interativo. Se flag Force vier sem ask, permitimos por conta-risco.
79
+ if (flags.force) {
80
+ console.log("⚠️ Flag --force detectada. Destravando ação destrutiva fora da sandbox.");
81
+ return true;
82
+ }
83
+
84
+ if (flags.yes) {
85
+ console.log("❌ Bloqueado! Não permitimos escapes da sandbox via '--yes'. Você deve rodar o processo interativamente ou usar --force.");
86
+ process.exit(1);
87
+ }
88
+
89
+ // Interactive confirmation com hard-stop (obriga escrever DESTRUCTIVE para deletes/overwrites)
90
+ if (outOfScope.deletes.length > 0 || outOfScope.overwrites.length > 0) {
91
+ const ans = await askQuestion("Perigo crítico. Para aprovar sobrescritas ou apagamentos fora do .agent, digite 'DESTRUCTIVE': ");
92
+ if (ans !== "DESTRUCTIVE") {
93
+ console.log("⏹️ Cancelado pelo Módulo Scope Guard.");
94
+ process.exit(1);
95
+ }
96
+ return true;
97
+ }
98
+
99
+ // Para WRITES brandos: conf simples
100
+ const ans = await askQuestion("Aprovar criação de novas rotas fora do sandbox? [y/N]: ");
101
+ if (ans.toLowerCase() !== "y") {
102
+ console.log("⏹️ Cancelado pelo Módulo Scope Guard.");
103
+ process.exit(1);
104
+ }
105
+
106
+ return true;
107
+ }
108
+
109
+ module.exports = {
110
+ guardPlan,
111
+ isPathInSafeScope
112
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabioforest/openclaw",
3
- "version": "3.8.0",
3
+ "version": "3.10.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`).