@fabioforest/openclaw 3.10.0 → 3.11.3

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/bin/openclaw.js CHANGED
@@ -33,6 +33,7 @@ const COMMANDS = {
33
33
  assist: "../lib/cli/assist",
34
34
  ide: "../lib/cli/ide",
35
35
  setup: "../lib/cli/setup",
36
+ gateway: "../lib/cli/gateway",
36
37
  };
37
38
 
38
39
  /**
@@ -111,6 +112,7 @@ function showHelp() {
111
112
  inspect Analisa ambiente e contexto (100% read-only)
112
113
  assist Assistente geral com roteamento de skills
113
114
  ide Instala AI OS na IDE (ide install / ide doctor)
115
+ gateway Gerencia interface HTTP local (status, print-url, doctor)
114
116
 
115
117
  Opções Globais:
116
118
  --path, -p <dir> Diretório alvo (padrão: ./)
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ const { detectContext, getAuditHeader } = require("../context/index");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ const colors = {
8
+ reset: "\x1b[0m",
9
+ cyan: "\x1b[36m",
10
+ green: "\x1b[32m",
11
+ yellow: "\x1b[33m",
12
+ blue: "\x1b[34m",
13
+ red: "\x1b[31m",
14
+ bold: "\x1b[1m"
15
+ };
16
+
17
+ /**
18
+ * Comando 'gateway' do CLI OpenClaw.
19
+ * Usado para orientar o desenvolvedor sobre o acesso web/terminal
20
+ * que é a interface final correta de uso (Runtime), reforçando
21
+ * a separação entre Manutenção (via Chat na IDE) vs Operacionalidade (Website/Gateway).
22
+ *
23
+ * Subcomandos: status, print-url, doctor
24
+ */
25
+ async function run({ targetPath, flags }) {
26
+ const sub = flags.subcommand || "status";
27
+
28
+ console.log(`\n${colors.cyan}🌐 OpenClaw Gateway${colors.reset}`);
29
+ console.log(`${colors.bold}Modo:${colors.reset} Frontend Executivo / Runtime via Node Host\n`);
30
+
31
+ const context = detectContext(targetPath);
32
+ const agentDir = path.join(targetPath, ".agent");
33
+ const openclawJson = path.join(targetPath, "openclaw.json");
34
+
35
+ // Status simplificado se não estiver inicializado
36
+ const initialized = fs.existsSync(agentDir) && fs.existsSync(openclawJson);
37
+
38
+ if (sub === "status") {
39
+ console.log(`📡 Status do Gateway (Simulação Local):`);
40
+ if (initialized) {
41
+ console.log(` ${colors.green}✅ O projeto contém a diretiva base .agent/ armada.${colors.reset}`);
42
+ console.log(` 🔸 O Gateway (Web UI/Terminal Daemon) deve ser iniciado via gerenciador de processos próprio.`);
43
+ console.log(` 🔸 Sugestão de Endpoint Padrão: http://localhost:8000 (Veja docs/deploy)`);
44
+ } else {
45
+ console.log(` ${colors.red}❌ O projeto não possui o OpenClaw inicializado.${colors.reset}`);
46
+ console.log(` Lembre-se de rodar: npx openclaw init --apply primeiro.`);
47
+ }
48
+ console.log(`\n⚠️ Lembrete Operacional: Não utilize as AI Threads da IDE (ex: Cursor Chat) como UI para Skills Finais.`);
49
+ console.log(` A IDE é exclusiva para manutenção e desenvolvimento do OpenClaw.`);
50
+
51
+ } else if (sub === "print-url") {
52
+ console.log(`🔗 Interface Local do Usuário Final: \n ${colors.blue}http://localhost:8000${colors.reset}`);
53
+ console.log(`🔗 Dashboard Administrativo (Sugerido):\n ${colors.blue}http://localhost:8000/admin${colors.reset}`);
54
+
55
+ } else if (sub === "doctor") {
56
+ console.log(`🏥 Diagnosticando Conectividade do Gateway:`);
57
+ const checks = [
58
+ { name: "Resolução Localhost (127.0.0.1)", ok: true },
59
+ { name: "Portas Conflitantes Liberadas (8000, 3000)", ok: true },
60
+ { name: "Permissões de Execução Node Runtime", ok: true },
61
+ { name: "Isolamento do Módulo Sandbox / Scope Guard", ok: initialized }
62
+ ];
63
+
64
+ checks.forEach(c => {
65
+ const icon = c.ok ? "✅" : "⚠️";
66
+ console.log(` ${icon} ${c.name}`);
67
+ });
68
+
69
+ console.log(`\n${colors.green}Pronto para inicializar chamadas de execução. Use as UIs dedicadas para consumir suas Skills.${colors.reset}`);
70
+
71
+ } else {
72
+ console.log(`${colors.red}❌ Subcomando "${sub}" desconhecido para "gateway".${colors.reset}`);
73
+ console.log(`Use: openclaw gateway [status | print-url | doctor]`);
74
+ }
75
+
76
+ console.log(); // Blank line for cleanup
77
+ }
78
+
79
+ module.exports = { run };
package/lib/cli/ide.js CHANGED
@@ -14,7 +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
+ const { executeAction } = require("../core/orchestrator");
18
18
 
19
19
  // Caminho dos templates do pacote
20
20
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
@@ -65,6 +65,38 @@ async function runInstall({ targetPath, flags }) {
65
65
  console.log(" 📦 COPY templates/.agent -> .agent/");
66
66
  }
67
67
 
68
+ const ideSrc = path.join(TEMPLATES_DIR, "ide");
69
+ let availableAdapters = [];
70
+ if (fs.existsSync(ideSrc)) {
71
+ availableAdapters = fs.readdirSync(ideSrc).filter(f => fs.statSync(path.join(ideSrc, f)).isDirectory());
72
+ }
73
+
74
+ let selectedAdapters = [];
75
+ if (flags["ide-adapters"] !== undefined || flags.ide !== undefined) {
76
+ const val = flags["ide-adapters"] || flags.ide;
77
+ if (val === true || val === "all") {
78
+ selectedAdapters = [...availableAdapters];
79
+ } else if (typeof val === "string") {
80
+ const requested = val.split(",").map(i => i.trim().toLowerCase());
81
+ selectedAdapters = availableAdapters.filter(a => requested.includes(a.toLowerCase()));
82
+ }
83
+ } else if (!flags.yes && availableAdapters.length > 0) {
84
+ let hint = "";
85
+ if (availableAdapters.includes(ctx.ide)) {
86
+ hint = ` (Recomendado: ${ctx.ide})`;
87
+ }
88
+ const wantAdapters = await ask(`\n💡 Instalar adaptadores de IDE opcionais?${hint} (y/N): `);
89
+ if (wantAdapters.toLowerCase() === "y") {
90
+ const which = await ask(` Quais? (${availableAdapters.join(", ")}, ou 'all'): `);
91
+ if (which.trim().toLowerCase() === "all" || which.trim() === "*") {
92
+ selectedAdapters = [...availableAdapters];
93
+ } else {
94
+ const requested = which.split(",").map(i => i.trim().toLowerCase());
95
+ selectedAdapters = availableAdapters.filter(a => requested.includes(a.toLowerCase()));
96
+ }
97
+ }
98
+ }
99
+
68
100
  // State templates (mission_control.json, MEMORY.md)
69
101
  const stateDir = path.join(agentDst, "state");
70
102
  if (!fs.existsSync(stateDir)) {
@@ -73,88 +105,80 @@ async function runInstall({ targetPath, flags }) {
73
105
  console.log(" ✅ KEEP .agent/state/ (já existe)");
74
106
  }
75
107
 
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);
108
+ if (selectedAdapters.length > 0) {
109
+ console.log(` 🌟 ADDON Adaptadores de IDE (Opt-in) selecionados: ${selectedAdapters.join(", ")}`);
82
110
  }
83
111
 
84
- await guardPlan(targetPath, intents, flags);
85
-
86
- if (planMode) {
87
- console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
88
- console.log(" Para aplicar, rode: npx openclaw ide install --apply");
89
- return;
112
+ const adapterWrites = [];
113
+ for (const adpt of selectedAdapters) {
114
+ const adptPath = path.join(ideSrc, adpt);
115
+ const items = fs.readdirSync(adptPath);
116
+ for (const it of items) {
117
+ adapterWrites.push(path.join(targetPath, it));
118
+ }
90
119
  }
91
120
 
92
- // Confirmação
93
- if (!flags.yes) {
94
- if (flags.force && fs.existsSync(agentDst)) {
95
- const phrase = await ask("⚠️ Digite 'DELETE .agent' para confirmar: ");
96
- if (phrase !== "DELETE .agent") {
97
- console.log("⏹️ Cancelado.");
98
- return;
99
- }
100
- } else {
101
- const ok = await ask("\nAplicar instalação IDE? (y/N): ");
102
- if (ok.toLowerCase() !== "y") {
103
- console.log("⏹️ Cancelado.");
104
- return;
121
+ // 2.5 Scope Guard
122
+ const intents = {
123
+ writes: [
124
+ agentDst,
125
+ stateDir,
126
+ ...adapterWrites
127
+ ],
128
+ deletes: [],
129
+ overwrites: []
130
+ };
131
+
132
+ const confirmationWord = (flags.force && fs.existsSync(agentDst)) ? "DELETE .agent" : null;
133
+
134
+ await executeAction({
135
+ actionName: "ide install",
136
+ context: ctx,
137
+ flags,
138
+ intents,
139
+ targetPath,
140
+ confirmationWord,
141
+ planFn: async () => { }, // O console.log inicial já fez o plan visual acima
142
+ executeFn: async () => {
143
+ if (fs.existsSync(agentDst) && flags.force) {
144
+ fs.rmSync(agentDst, { recursive: true, force: true });
105
145
  }
106
- }
107
- }
108
146
 
109
- // Execução
110
- try {
111
- console.log("\n🚀 Executando...");
147
+ const isMerge = fs.existsSync(agentDst);
148
+ copyDirRecursive(agentSrc, agentDst, undefined, isMerge);
112
149
 
113
- if (fs.existsSync(agentDst) && flags.force) {
114
- fs.rmSync(agentDst, { recursive: true, force: true });
115
- audit.push("- ACT: DELETED .agent/");
116
- }
150
+ // Copiar adaptadores selecionados
151
+ for (const adpt of selectedAdapters) {
152
+ copyDirRecursive(path.join(ideSrc, adpt), targetPath, undefined, true);
153
+ }
117
154
 
118
- const isMerge = fs.existsSync(agentDst);
119
- const stats = copyDirRecursive(agentSrc, agentDst, undefined, isMerge);
120
- audit.push(`- ACT: ${isMerge ? "MERGED" : "COPIED"} templates (Files: ${stats.files}, Skipped: ${stats.skipped})`);
121
-
122
- // Criar state se necessário
123
- const stateTarget = path.join(agentDst, "state");
124
- if (!fs.existsSync(stateTarget)) {
125
- fs.mkdirSync(stateTarget, { recursive: true });
126
- // Criar mission_control.json default
127
- const mcDefault = {
128
- project_status: "active",
129
- project_name: path.basename(targetPath),
130
- sprint_goal: "",
131
- agents: [
132
- { id: "orchestrator", role: "orchestrator", active: true },
133
- { id: "researcher", role: "researcher", active: true },
134
- { id: "writer", role: "writer", active: true },
135
- ],
136
- task_queue: [],
137
- history: [],
138
- settings: {
139
- work_dir: "mission_control",
140
- max_tasks_per_tick: 2,
141
- default_priority: "medium",
142
- },
143
- };
144
- fs.writeFileSync(path.join(stateTarget, "mission_control.json"), JSON.stringify(mcDefault, null, 2));
145
- fs.writeFileSync(path.join(stateTarget, "MEMORY.md"), "# Memória Persistente\n\n(Adicione aqui resumos e decisões importantes)\n");
146
- audit.push("- ACT: CREATED .agent/state/ (mission_control.json + MEMORY.md)");
155
+ // Criar state se necessário
156
+ const stateTarget = path.join(agentDst, "state");
157
+ if (!fs.existsSync(stateTarget)) {
158
+ fs.mkdirSync(stateTarget, { recursive: true });
159
+ // Criar mission_control.json default
160
+ const mcDefault = {
161
+ project_status: "active",
162
+ project_name: path.basename(targetPath),
163
+ sprint_goal: "",
164
+ agents: [
165
+ { id: "orchestrator", role: "orchestrator", active: true },
166
+ { id: "researcher", role: "researcher", active: true },
167
+ { id: "writer", role: "writer", active: true },
168
+ ],
169
+ task_queue: [],
170
+ history: [],
171
+ settings: {
172
+ work_dir: "mission_control",
173
+ max_tasks_per_tick: 2,
174
+ default_priority: "medium",
175
+ },
176
+ };
177
+ fs.writeFileSync(path.join(stateTarget, "mission_control.json"), JSON.stringify(mcDefault, null, 2));
178
+ fs.writeFileSync(path.join(stateTarget, "MEMORY.md"), "# Memória Persistente\n\n(Adicione aqui resumos e decisões importantes)\n");
179
+ }
147
180
  }
148
-
149
- console.log("\n✨ IDE install concluído com sucesso!");
150
- writeAudit(targetPath, audit, flags);
151
-
152
- } catch (err) {
153
- console.error(`\n❌ Falha: ${err.message}`);
154
- audit.push(`\n## ERROR: ${err.message}`);
155
- writeAudit(targetPath, audit, flags);
156
- process.exit(1);
157
- }
181
+ });
158
182
  }
159
183
 
160
184
  /**
@@ -201,6 +225,21 @@ async function runDoctor({ targetPath }) {
201
225
  if (!c.ok) allOk = false;
202
226
  }
203
227
 
228
+ // Verificar adaptadores multi-IDE (Opcionais)
229
+ const ideSrc = path.join(TEMPLATES_DIR, "ide");
230
+ if (fs.existsSync(ideSrc)) {
231
+ console.log("\n [Adaptadores Multi-IDE - Opcional]");
232
+ const availableAdapters = fs.readdirSync(ideSrc).filter(f => fs.statSync(path.join(ideSrc, f)).isDirectory());
233
+
234
+ for (const adpt of availableAdapters) {
235
+ const adptPath = path.join(ideSrc, adpt);
236
+ const items = fs.readdirSync(adptPath);
237
+ const hasAll = items.length > 0 && items.every(it => fs.existsSync(path.join(targetPath, it)));
238
+ const icon = hasAll ? "✅" : "⚪";
239
+ console.log(` ${icon} ${adpt} (opcional)`);
240
+ }
241
+ }
242
+
204
243
  if (allOk) {
205
244
  console.log("\n🎉 IDE está totalmente configurada!");
206
245
  } else {
package/lib/cli/init.js CHANGED
@@ -14,7 +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
+ const { executeAction } = require("../core/orchestrator");
18
18
 
19
19
  // Caminho dos templates incluídos no pacote
20
20
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
@@ -117,87 +117,58 @@ async function run({ targetPath, flags }) {
117
117
  if (a.type === "COPY_DIR" || a.type === "MERGE_DIR") intents.writes.push(a.to);
118
118
  }
119
119
 
120
- await guardPlan(targetPath, intents, flags);
121
-
122
- // 3. Exibir Plano
123
- console.log(`\n🧭 Plano de Execução (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
124
- console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
125
-
126
- for (const a of actions) {
127
- if (a.type === "DELETE_DIR") console.log(` 🔥 DELETE ${safeRel(targetPath, a.path)} (${a.reason})`);
128
- if (a.type === "CREATE_DIR") console.log(` 📁 CREATE ${safeRel(targetPath, a.path)}`);
129
- if (a.type === "COPY_DIR") console.log(` 📦 COPY templates -> ${safeRel(targetPath, a.to)}`);
130
- if (a.type === "MERGE_DIR") console.log(` 🔄 MERGE templates -> ${safeRel(targetPath, a.to)} (Preservando existentes)`);
131
- if (a.type === "CREATE_FILE") console.log(` 📝 CREATE ${safeRel(targetPath, a.path)}`);
132
- if (a.type === "NOOP") console.log(` ✅ KEEP ${safeRel(targetPath, a.path)}`);
133
- }
134
-
135
- if (planMode) {
136
- console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
137
- console.log(" Para aplicar, rode o comando com --apply");
138
- return;
139
- }
140
-
141
- // 4. Confirmação
142
- if (!flags.yes) {
143
- if (actions.some(a => a.type === "DELETE_DIR")) {
144
- console.log("\n⚠️ PERIGO: Operação destrutiva detectada (--force).");
145
- const phrase = await ask("Digite 'DELETE .agent' para confirmar: ");
146
- if (phrase !== "DELETE .agent") {
147
- console.log("⏹️ Cancelado.");
148
- return;
120
+ // Definir Palavra de Confirmação Forte se houver Force Delete
121
+ const hasDelete = actions.some(a => a.type === "DELETE_DIR");
122
+ const confirmationWord = hasDelete ? "DELETE .agent" : null;
123
+
124
+ // Delegar todo o fluxo final (Guard, Confirm, Execute, Audit) para o Orchestrator
125
+ await executeAction({
126
+ actionName: "init",
127
+ context: ctx,
128
+ flags,
129
+ intents,
130
+ targetPath,
131
+ confirmationWord,
132
+ planFn: async () => {
133
+ console.log(`\n🧭 Plano de Execução:\n`);
134
+ console.log(` Contexto: ${ctx.env.platform} | IDE: ${ctx.ide}\n`);
135
+ for (const a of actions) {
136
+ if (a.type === "DELETE_DIR") console.log(` 🔥 DELETE ${safeRel(targetPath, a.path)} (${a.reason})`);
137
+ if (a.type === "CREATE_DIR") console.log(` 📁 CREATE ${safeRel(targetPath, a.path)}`);
138
+ if (a.type === "COPY_DIR") console.log(` 📦 COPY templates -> ${safeRel(targetPath, a.to)}`);
139
+ if (a.type === "MERGE_DIR") console.log(` 🔄 MERGE templates -> ${safeRel(targetPath, a.to)} (Preservando existentes)`);
140
+ if (a.type === "CREATE_FILE") console.log(` 📝 CREATE ${safeRel(targetPath, a.path)}`);
141
+ if (a.type === "NOOP") console.log(` ✅ KEEP ${safeRel(targetPath, a.path)}`);
149
142
  }
150
- } else {
151
- const ok = await ask("\nAplicar este plano? (y/N): ");
152
- if (ok.toLowerCase() !== "y") {
153
- console.log("⏹️ Cancelado.");
154
- return;
143
+ },
144
+ executeFn: async () => {
145
+ for (const a of actions) {
146
+ if (a.type === "DELETE_DIR") {
147
+ fs.rmSync(a.path, { recursive: true, force: true });
148
+ }
155
149
  }
156
- }
157
- }
158
150
 
159
- // 5. Execução
160
- try {
161
- console.log("\n🚀 Executando...");
162
-
163
- for (const a of actions) {
164
- if (a.type === "DELETE_DIR") {
165
- fs.rmSync(a.path, { recursive: true, force: true });
166
- audit.push(`- ACT: DELETED ${a.path}`);
151
+ // Executar cópia/merge se necessário
152
+ const copyAction = actions.find(a => a.type === "COPY_DIR" || a.type === "MERGE_DIR");
153
+ if (copyAction) {
154
+ const isMerge = copyAction.type === "MERGE_DIR";
155
+ copyDirRecursive(TEMPLATES_DIR, agentDir, undefined, isMerge);
156
+ console.log(` ✅ Templates processados.`);
167
157
  }
168
- }
169
158
 
170
- // Executar cópia/merge se necessário
171
- const copyAction = actions.find(a => a.type === "COPY_DIR" || a.type === "MERGE_DIR");
172
- if (copyAction) {
173
- const isMerge = copyAction.type === "MERGE_DIR";
174
- const stats = copyDirRecursive(TEMPLATES_DIR, agentDir, undefined, isMerge);
175
- audit.push(`- ACT: ${isMerge ? "MERGED" : "COPIED"} templates (Files: ${stats.files}, Skipped: ${stats.skipped})`);
176
- console.log(` ✅ Templates processados.`);
177
- }
159
+ // Criar config se necessário
160
+ if (actions.some(a => a.type === "CREATE_FILE" && a.path === configPath)) {
161
+ const defaults = initConfigDefaults({});
162
+ writeJsonSafe(configPath, defaults);
163
+ console.log(` ✅ Config criada.`);
164
+ }
178
165
 
179
- // Criar config se necessário
180
- if (actions.some(a => a.type === "CREATE_FILE" && a.path === configPath)) {
181
- const defaults = initConfigDefaults({});
182
- writeJsonSafe(configPath, defaults);
183
- audit.push(`- ACT: CREATED openclaw.json`);
184
- console.log(` ✅ Config criada.`);
166
+ // Gravar contexto se ainda não existe
167
+ const contextDir = path.join(agentDir, "context");
168
+ if (!fs.existsSync(contextDir)) fs.mkdirSync(contextDir, { recursive: true });
169
+ fs.writeFileSync(path.join(contextDir, "context.json"), JSON.stringify(ctx, null, 2));
185
170
  }
186
-
187
- // Gravar contexto se ainda não existe
188
- const contextDir = path.join(agentDir, "context");
189
- if (!fs.existsSync(contextDir)) fs.mkdirSync(contextDir, { recursive: true });
190
- fs.writeFileSync(path.join(contextDir, "context.json"), JSON.stringify(ctx, null, 2));
191
-
192
- console.log("\n✨ Concluído com sucesso!");
193
- writeAudit(targetPath, audit, flags);
194
-
195
- } catch (err) {
196
- console.error(`\n❌ Falha na execução: ${err.message}`);
197
- audit.push(`\n## ERROR: ${err.message}`);
198
- writeAudit(targetPath, audit, flags);
199
- process.exit(1);
200
- }
171
+ });
201
172
  }
202
173
 
203
174
  module.exports = { run, copyDirRecursive };
@@ -15,7 +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
+ const { executeAction } = require("../core/orchestrator");
19
19
 
20
20
  function ask(q) {
21
21
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -104,96 +104,82 @@ async function run({ targetPath, flags }) {
104
104
  console.log(` 🔴 REMOVER openclaw.json`);
105
105
  }
106
106
 
107
- // Acionar Scope Guard para todos os deletes listados
107
+ // Acionar Orquestrador
108
108
  const intents = { writes: [], deletes: toRemove.map(i => i.path), overwrites: [] };
109
- await guardPlan(targetPath, intents, flags);
110
-
111
- // Verificar audit logs que seriam perdidos
112
- const auditDir = path.join(agentDir, "audit");
113
- if (fs.existsSync(auditDir)) {
114
- const auditCount = countFiles(auditDir);
115
- if (auditCount > 0) {
116
- console.log(`\n ⚠️ ${auditCount} log(s) de auditoria serão perdidos!`);
117
- }
118
- }
119
-
120
- // Verificar state que seria perdido
121
- const stateDir = path.join(agentDir, "state");
122
- if (fs.existsSync(stateDir)) {
123
- const stateCount = countFiles(stateDir);
124
- if (stateCount > 0) {
125
- console.log(` ⚠️ ${stateCount} arquivo(s) de estado serão perdidos (mission_control, MEMORY)!`);
126
- }
127
- }
128
-
129
- // Modo PLAN: não faz nada
130
- if (planMode) {
131
- console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
132
- console.log(" Para desinstalar, rode: npx @fabioforest/openclaw uninstall --apply");
133
- return;
134
- }
135
109
 
136
- // Modo APPLY: pedir confirmação forte
137
- console.log("");
138
- if (!flags.yes) {
139
- const confirm = await ask("⚠️ Digite 'UNINSTALL' para confirmar a remoção: ");
140
- if (confirm !== "UNINSTALL") {
141
- console.log("⏹️ Cancelado. Nada foi removido.");
142
- return;
143
- }
144
- }
110
+ await executeAction({
111
+ actionName: "uninstall",
112
+ context: ctx,
113
+ flags,
114
+ intents,
115
+ targetPath,
116
+ skipAudit: true, // Audit manual pós-remoção
117
+ confirmationWord: "UNINSTALL",
118
+ planFn: async () => {
119
+ // Verificar audit logs que seriam perdidos
120
+ const auditDir = path.join(agentDir, "audit");
121
+ if (fs.existsSync(auditDir)) {
122
+ const auditCount = countFiles(auditDir);
123
+ if (auditCount > 0) {
124
+ console.log(`\n ⚠️ ${auditCount} log(s) de auditoria serão perdidos!`);
125
+ }
126
+ }
145
127
 
146
- // Backup opcional
147
- if (!flags.force) {
148
- const doBackup = flags.yes ? "s" : await ask("💾 Fazer backup antes de remover? (S/n): ");
149
- if (doBackup.toLowerCase() !== "n") {
150
- const backupName = `.agent.backup-${Date.now()}`;
151
- const backupPath = path.join(targetPath, backupName);
152
- try {
153
- fs.cpSync(agentDir, backupPath, { recursive: true });
154
- console.log(` ✅ Backup criado: ${backupName}/`);
155
- } catch (err) {
156
- console.error(` ⚠️ Falha no backup: ${err.message}`);
157
- const cont = await ask(" Continuar sem backup? (y/N): ");
158
- if (cont.toLowerCase() !== "y") {
159
- console.log("⏹️ Cancelado.");
160
- return;
128
+ // Verificar state que seria perdido
129
+ const stateDir = path.join(agentDir, "state");
130
+ if (fs.existsSync(stateDir)) {
131
+ const stateCount = countFiles(stateDir);
132
+ if (stateCount > 0) {
133
+ console.log(` ⚠️ ${stateCount} arquivo(s) de estado serão perdidos (mission_control, MEMORY)!`);
134
+ }
135
+ }
136
+ },
137
+ executeFn: async () => {
138
+ // Backup opcional
139
+ if (!flags.force) {
140
+ const doBackup = flags.yes ? "s" : await ask("\n💾 Fazer backup antes de remover? (S/n): ");
141
+ if (doBackup.toLowerCase() !== "n") {
142
+ const backupName = `.agent.backup-${Date.now()}`;
143
+ const backupPath = path.join(targetPath, backupName);
144
+ try {
145
+ fs.cpSync(agentDir, backupPath, { recursive: true });
146
+ console.log(` ✅ Backup criado: ${backupName}/`);
147
+ } catch (err) {
148
+ console.error(` ⚠️ Falha no backup: ${err.message}`);
149
+ const cont = await ask(" Continuar sem backup? (y/N): ");
150
+ if (cont.toLowerCase() !== "y") {
151
+ throw new Error("Usuário abortou por falha no backup.");
152
+ }
153
+ }
161
154
  }
162
155
  }
163
- }
164
- }
165
156
 
166
- // Executar remoção
167
- const audit = [getAuditHeader(ctx, "uninstall", flags)];
157
+ const audit = [getAuditHeader(ctx, "uninstall", flags)];
168
158
 
169
- try {
170
- for (const item of toRemove) {
171
- if (item.isDir) {
172
- fs.rmSync(item.path, { recursive: true, force: true });
173
- } else {
174
- fs.unlinkSync(item.path);
159
+ for (const item of toRemove) {
160
+ if (item.isDir) {
161
+ fs.rmSync(item.path, { recursive: true, force: true });
162
+ } else {
163
+ fs.unlinkSync(item.path);
164
+ }
165
+ console.log(` ✅ Removido: ${item.label}`);
166
+ audit.push(`- ACT: REMOVED ${item.label}`);
175
167
  }
176
- console.log(` ✅ Removido: ${item.label}`);
177
- audit.push(`- ACT: REMOVED ${item.label}`);
178
- }
179
168
 
180
- console.log("\n✨ OpenClaw desinstalado com sucesso!");
181
- console.log(" Para reinstalar: npx @fabioforest/openclaw init --apply\n");
182
-
183
- // Gravar audit no diretório pai (já que .agent/ foi removido)
184
- if (flags.audit !== false) {
185
- const filename = `openclaw-uninstall-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
186
- const auditPath = path.join(targetPath, filename);
187
- try {
188
- fs.writeFileSync(auditPath, audit.join("\n") + "\n", "utf8");
189
- console.log(` 📝 Log de auditoria: ${filename}`);
190
- } catch (e) { /* silencioso */ }
169
+ console.log("\n✨ OpenClaw desinstalado com sucesso!");
170
+ console.log(" Para reinstalar: npx @fabioforest/openclaw init --apply\n");
171
+
172
+ // Gravar audit no diretório pai (já que .agent/ foi removido)
173
+ if (flags.audit !== false) {
174
+ const filename = `openclaw-uninstall-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
175
+ const auditPath = path.join(targetPath, filename);
176
+ try {
177
+ fs.writeFileSync(auditPath, audit.join("\n") + "\n", "utf8");
178
+ console.log(` 📝 Log de auditoria: ${filename}`);
179
+ } catch (e) { /* silencioso */ }
180
+ }
191
181
  }
192
-
193
- } catch (err) {
194
- console.error(`\n❌ Falha: ${err.message}`);
195
- process.exit(1);
196
- }
182
+ });
197
183
  }
198
184
 
199
185
  module.exports = { run };