@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 +31 -0
- package/bin/openclaw.js +1 -12
- package/lib/cli/ide.js +9 -15
- package/lib/cli/init.js +3 -13
- package/lib/cli/setup.js +25 -0
- package/lib/cli/update.js +64 -24
- package/lib/setup/config_wizard.js +228 -150
- package/lib/utils/audit-writer.js +46 -0
- package/package.json +2 -2
- package/templates/.agent/workflows/chat-first.md +16 -0
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
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/lib/cli/setup.js
ADDED
|
@@ -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
|
-
|
|
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á
|
|
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 =>
|
|
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("
|
|
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
|
|
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
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
5
|
+
const crypto = require("crypto");
|
|
24
6
|
|
|
25
|
-
// Módulos extraídos para lib/
|
|
7
|
+
// Módulos extraídos para lib/
|
|
26
8
|
const { detectEnvironment } = require("../detect");
|
|
27
|
-
const { readJsonSafe, writeJsonSafe,
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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(
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
//
|
|
77
|
-
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
|
-
|
|
116
|
+
// 2) Propor ajustes seguros (NÃO aplicar ainda)
|
|
117
|
+
const before = deepClone(config);
|
|
80
118
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
110
|
-
if (manual) { config.auth.token = manual; needWrite = true; }
|
|
152
|
+
config.auth.token = generateToken();
|
|
111
153
|
}
|
|
112
154
|
} else {
|
|
113
|
-
|
|
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
|
-
//
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
// Só 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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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("
|
|
253
|
+
console.log("ℹ️ .agent não existe neste projeto. (ok) — nada será criado em PLAN.");
|
|
176
254
|
}
|
|
177
255
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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.
|
|
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`).
|