@fabioforest/openclaw 3.8.0 → 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 +6 -1
- package/lib/cli/setup.js +25 -0
- package/lib/cli/update.js +61 -12
- package/lib/setup/config_wizard.js +228 -150
- 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
|
@@ -173,7 +173,7 @@ async function runDoctor({ targetPath }) {
|
|
|
173
173
|
|
|
174
174
|
// Verificar rules
|
|
175
175
|
const rulesDir = path.join(agentDir, "rules");
|
|
176
|
-
const requiredRules = ["CONSENT_FIRST.md", "ROUTER_PROTOCOL.md"];
|
|
176
|
+
const requiredRules = ["CONSENT_FIRST.md", "ROUTER_PROTOCOL.md", "SECURITY.md", "WEB_AUTOMATION.md"];
|
|
177
177
|
for (const rule of requiredRules) {
|
|
178
178
|
checks.push({ name: `rules/${rule}`, ok: fs.existsSync(path.join(rulesDir, rule)) });
|
|
179
179
|
}
|
|
@@ -189,6 +189,11 @@ async function runDoctor({ targetPath }) {
|
|
|
189
189
|
const hooksDir = path.join(agentDir, "hooks");
|
|
190
190
|
checks.push({ name: "hooks/pre-tool-use.js", ok: fs.existsSync(path.join(hooksDir, "pre-tool-use.js")) });
|
|
191
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
|
+
|
|
192
197
|
// Exibir resultado
|
|
193
198
|
let allOk = true;
|
|
194
199
|
for (const c of checks) {
|
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
|
@@ -41,14 +41,36 @@ function fileHash(filePath) {
|
|
|
41
41
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
42
42
|
}
|
|
43
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
|
+
|
|
44
67
|
/**
|
|
45
68
|
* Analisa atualizações necessárias.
|
|
46
69
|
* Retorna lista de ações planejadas.
|
|
47
70
|
*/
|
|
48
71
|
function planUpdates(src, dest, actions = { added: [], updated: [], skipped: [] }) {
|
|
49
72
|
if (!fs.existsSync(dest)) {
|
|
50
|
-
// Diretório não existe no destino, será
|
|
51
|
-
// Mas a lógica recursiva precisa entrar
|
|
73
|
+
// Diretório não existe no destino, será acompanhado na copia de arquivos
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -115,7 +137,9 @@ async function run({ targetPath, flags }) {
|
|
|
115
137
|
}
|
|
116
138
|
if (actions.updated.length > 0) {
|
|
117
139
|
console.log(`\n🔄 Modificados (${actions.updated.length}):`);
|
|
118
|
-
actions.updated.forEach(a =>
|
|
140
|
+
actions.updated.forEach(a => {
|
|
141
|
+
console.log(` ~ UPDATE ${safeRel(targetPath, a.dest)} (Exige confirmação interativa)`);
|
|
142
|
+
});
|
|
119
143
|
}
|
|
120
144
|
if (actions.skipped.length > 0) {
|
|
121
145
|
console.log(`\n⏭️ Ignorados (${actions.skipped.length} arquivos idênticos)`);
|
|
@@ -128,20 +152,21 @@ async function run({ targetPath, flags }) {
|
|
|
128
152
|
|
|
129
153
|
if (planMode) {
|
|
130
154
|
console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
|
|
131
|
-
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");
|
|
132
157
|
return;
|
|
133
158
|
}
|
|
134
159
|
|
|
135
|
-
// 3. Confirmação
|
|
136
|
-
if (!flags.yes) {
|
|
137
|
-
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): ");
|
|
138
163
|
if (ok.toLowerCase() !== "y") {
|
|
139
164
|
console.log("⏹️ Cancelado.");
|
|
140
165
|
return;
|
|
141
166
|
}
|
|
142
167
|
}
|
|
143
168
|
|
|
144
|
-
// 4. Execução
|
|
169
|
+
// 4. Execução Interativa Segura (Conflitos file-by-file)
|
|
145
170
|
try {
|
|
146
171
|
console.log("\n🚀 Executando atualizações...");
|
|
147
172
|
|
|
@@ -151,18 +176,42 @@ async function run({ targetPath, flags }) {
|
|
|
151
176
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
152
177
|
}
|
|
153
178
|
|
|
179
|
+
// Action: Added
|
|
154
180
|
for (const action of actions.added) {
|
|
155
181
|
ensureDir(action.dest);
|
|
156
182
|
fs.copyFileSync(action.src, action.dest);
|
|
157
183
|
audit.push(`- ACT: CREATED ${safeRel(targetPath, action.dest)}`);
|
|
158
184
|
}
|
|
159
185
|
|
|
186
|
+
// Action: Updated (Conflict Resolver)
|
|
160
187
|
for (const action of actions.updated) {
|
|
161
188
|
ensureDir(action.dest);
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
+
}
|
|
166
215
|
}
|
|
167
216
|
|
|
168
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
|
+
};
|
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`).
|