@fabioforest/openclaw 3.0.0 → 3.4.1
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 +417 -46
- package/bin/openclaw.js +37 -8
- package/lib/cli/assist.js +84 -0
- package/lib/cli/doctor.js +37 -3
- package/lib/cli/ide.js +218 -0
- package/lib/cli/init.js +135 -79
- package/lib/cli/inspect.js +58 -0
- package/lib/cli/orchestrate.js +43 -15
- package/lib/cli/update.js +113 -47
- package/lib/context/collector.js +104 -0
- package/lib/context/index.js +75 -0
- package/lib/router/match.js +107 -0
- package/lib/setup/config_wizard.js +2 -0
- package/package.json +2 -2
- package/templates/.agent/agents/workflow-automator.md +31 -0
- package/templates/.agent/rules/CONSENT_FIRST.md +24 -0
- package/templates/.agent/rules/DEV_MODE.md +18 -0
- package/templates/.agent/rules/ROUTER_PROTOCOL.md +22 -0
- package/templates/.agent/rules/WEB_AUTOMATION.md +52 -0
- package/templates/.agent/skills/content-sourcer/SKILL.md +48 -0
- package/templates/.agent/skills/context-flush/SKILL.md +30 -0
- package/templates/.agent/skills/drive-organizer/SKILL.md +40 -0
- package/templates/.agent/skills/linkedin-optimizer/SKILL.md +48 -0
- package/templates/.agent/skills/mission-control/SKILL.md +37 -0
- package/templates/.agent/skills/openclaw-assist/SKILL.md +30 -0
- package/templates/.agent/skills/openclaw-dev/SKILL.md +26 -0
- package/templates/.agent/skills/openclaw-inspect/SKILL.md +21 -0
- package/templates/.agent/skills/openclaw-installation-debugger/scripts/debug.js +16 -2
- package/templates/.agent/skills/openclaw-router/SKILL.md +34 -0
- package/templates/.agent/skills/openclaw-security/SKILL.md +21 -0
- package/templates/.agent/skills/site-tester/SKILL.md +49 -0
- package/templates/.agent/skills/smart-router/SKILL.md +116 -0
- package/templates/.agent/skills/web-scraper/SKILL.md +51 -0
- package/templates/.agent/state/MEMORY.md +8 -0
- package/templates/.agent/state/mission_control.json +34 -0
- package/templates/.agent/workflows/ai-capture.md +39 -0
package/lib/cli/doctor.js
CHANGED
|
@@ -166,12 +166,16 @@ async function checkPort() {
|
|
|
166
166
|
try {
|
|
167
167
|
const inUse = await portInUse(18789, "127.0.0.1");
|
|
168
168
|
if (inUse) {
|
|
169
|
-
return {
|
|
169
|
+
return {
|
|
170
|
+
name: "porta 18789",
|
|
171
|
+
status: "ok",
|
|
172
|
+
message: "✅ Control UI acessível em http://127.0.0.1:18789"
|
|
173
|
+
};
|
|
170
174
|
}
|
|
171
175
|
return {
|
|
172
176
|
name: "porta 18789",
|
|
173
177
|
status: "warn",
|
|
174
|
-
message: "Porta livre — serviço OpenClaw pode estar parado",
|
|
178
|
+
message: "Porta livre — serviço OpenClaw pode estar parado (UI indisponível)",
|
|
175
179
|
};
|
|
176
180
|
} catch {
|
|
177
181
|
return {
|
|
@@ -182,6 +186,30 @@ async function checkPort() {
|
|
|
182
186
|
}
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Verifica interfaces de rede para detectar VPN WireGuard ativa (Linux only).
|
|
191
|
+
* @returns {CheckResult}
|
|
192
|
+
*/
|
|
193
|
+
function checkVpnInterface() {
|
|
194
|
+
const osType = require("os").type();
|
|
195
|
+
if (osType !== "Linux") {
|
|
196
|
+
return { name: "vpn interface", status: "ok", message: "N/A (não é Linux)" };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const interfaces = require("os").networkInterfaces();
|
|
201
|
+
const wgInterface = Object.keys(interfaces).find(iface => iface.startsWith("wg"));
|
|
202
|
+
|
|
203
|
+
if (wgInterface) {
|
|
204
|
+
return { name: "vpn interface", status: "ok", message: `Detectada: ${wgInterface}` };
|
|
205
|
+
} else {
|
|
206
|
+
return { name: "vpn interface", status: "warn", message: "Nenhuma interface wg* encontrada" };
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
return { name: "vpn interface", status: "warn", message: "Erro ao listar interfaces" };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
185
213
|
/**
|
|
186
214
|
* Verifica ambiente detectado.
|
|
187
215
|
* @returns {CheckResult}
|
|
@@ -252,6 +280,12 @@ async function run({ targetPath, flags }) {
|
|
|
252
280
|
// 4. .agent/
|
|
253
281
|
results.push(...checkAgentDir(agentDir));
|
|
254
282
|
|
|
283
|
+
// 5. VPN (se Linux)
|
|
284
|
+
const vpnCheck = checkVpnInterface();
|
|
285
|
+
if (vpnCheck.message !== "N/A (não é Linux)") {
|
|
286
|
+
results.push(vpnCheck);
|
|
287
|
+
}
|
|
288
|
+
|
|
255
289
|
// Exibir relatório
|
|
256
290
|
printReport(results);
|
|
257
291
|
console.log("");
|
|
@@ -263,4 +297,4 @@ async function run({ targetPath, flags }) {
|
|
|
263
297
|
}
|
|
264
298
|
}
|
|
265
299
|
|
|
266
|
-
module.exports = { run, checkConfig, checkAgentDir, checkPort, checkEnvironment };
|
|
300
|
+
module.exports = { run, checkConfig, checkAgentDir, checkPort, checkEnvironment, checkVpnInterface };
|
package/lib/cli/ide.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comando CLI: ide
|
|
5
|
+
*
|
|
6
|
+
* Gerencia a instalação do OpenClaw AI OS em IDEs.
|
|
7
|
+
* Sub-comandos: install (plan/apply), doctor.
|
|
8
|
+
* Sempre segue o protocolo consent-first.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const readline = require("readline");
|
|
14
|
+
const { detectContext, getAuditHeader } = require("../context");
|
|
15
|
+
const { copyDirRecursive } = require("./init");
|
|
16
|
+
|
|
17
|
+
// Caminho dos templates do pacote
|
|
18
|
+
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
|
|
19
|
+
|
|
20
|
+
function ask(q) {
|
|
21
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
return new Promise((res) => rl.question(q, (ans) => { rl.close(); res(ans.trim()); }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Grava log de auditoria para o comando ide.
|
|
27
|
+
*/
|
|
28
|
+
function writeAudit(targetPath, lines, flags) {
|
|
29
|
+
if (flags.audit === false) return;
|
|
30
|
+
const auditDir = path.join(targetPath, ".agent", "audit");
|
|
31
|
+
if (!fs.existsSync(auditDir)) {
|
|
32
|
+
try { fs.mkdirSync(auditDir, { recursive: true }); } catch (e) { }
|
|
33
|
+
}
|
|
34
|
+
const filename = `ide-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
|
|
35
|
+
try {
|
|
36
|
+
fs.writeFileSync(path.join(auditDir, filename), lines.join("\n") + "\n", "utf8");
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error("⚠️ Falha ao gravar auditoria:", e.message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Executa o comando ide.
|
|
44
|
+
* Uso: openclaw ide install [--apply] [--force]
|
|
45
|
+
* openclaw ide doctor
|
|
46
|
+
*
|
|
47
|
+
* @param {object} options
|
|
48
|
+
* @param {string} options.targetPath — diretório alvo
|
|
49
|
+
* @param {object} options.flags — flags do CLI
|
|
50
|
+
*/
|
|
51
|
+
async function run({ targetPath, flags }) {
|
|
52
|
+
// Detectar sub-comando a partir de args restantes
|
|
53
|
+
// O bin/openclaw.js passa o comando "ide", então precisamos
|
|
54
|
+
// verificar os args para o sub-comando
|
|
55
|
+
const args = process.argv.slice(3);
|
|
56
|
+
const subCmd = args.find(a => !a.startsWith("-")) || "install";
|
|
57
|
+
|
|
58
|
+
if (subCmd === "doctor") {
|
|
59
|
+
return runDoctor({ targetPath, flags });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return runInstall({ targetPath, flags });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Sub-comando: ide install
|
|
67
|
+
* Instala .agent/ no projeto para uso em IDEs (plan/apply).
|
|
68
|
+
*/
|
|
69
|
+
async function runInstall({ targetPath, flags }) {
|
|
70
|
+
const agentDst = path.join(targetPath, ".agent");
|
|
71
|
+
const agentSrc = path.join(TEMPLATES_DIR, ".agent");
|
|
72
|
+
const ctx = detectContext(targetPath);
|
|
73
|
+
const planMode = !flags.apply;
|
|
74
|
+
const audit = [getAuditHeader(ctx, "ide install", flags)];
|
|
75
|
+
|
|
76
|
+
console.log(`\n🧭 IDE Install — Plano (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
|
|
77
|
+
console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
|
|
78
|
+
|
|
79
|
+
if (fs.existsSync(agentDst) && !flags.force) {
|
|
80
|
+
console.log(" ✅ KEEP .agent/ (já existe)");
|
|
81
|
+
console.log(" 📦 MERGE Novos templates serão adicionados (preservando existentes)");
|
|
82
|
+
} else if (fs.existsSync(agentDst) && flags.force) {
|
|
83
|
+
console.log(" 🔥 DELETE .agent/ (--force)");
|
|
84
|
+
console.log(" 📦 COPY templates/.agent -> .agent/");
|
|
85
|
+
} else {
|
|
86
|
+
console.log(" 📁 CREATE .agent/");
|
|
87
|
+
console.log(" 📦 COPY templates/.agent -> .agent/");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// State templates (mission_control.json, MEMORY.md)
|
|
91
|
+
const stateDir = path.join(agentDst, "state");
|
|
92
|
+
if (!fs.existsSync(stateDir)) {
|
|
93
|
+
console.log(" 📁 CREATE .agent/state/ (Mission Control + MEMORY)");
|
|
94
|
+
} else {
|
|
95
|
+
console.log(" ✅ KEEP .agent/state/ (já existe)");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (planMode) {
|
|
99
|
+
console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
|
|
100
|
+
console.log(" Para aplicar, rode: npx openclaw ide install --apply");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Confirmação
|
|
105
|
+
if (!flags.yes) {
|
|
106
|
+
if (flags.force && fs.existsSync(agentDst)) {
|
|
107
|
+
const phrase = await ask("⚠️ Digite 'DELETE .agent' para confirmar: ");
|
|
108
|
+
if (phrase !== "DELETE .agent") {
|
|
109
|
+
console.log("⏹️ Cancelado.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
const ok = await ask("\nAplicar instalação IDE? (y/N): ");
|
|
114
|
+
if (ok.toLowerCase() !== "y") {
|
|
115
|
+
console.log("⏹️ Cancelado.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Execução
|
|
122
|
+
try {
|
|
123
|
+
console.log("\n🚀 Executando...");
|
|
124
|
+
|
|
125
|
+
if (fs.existsSync(agentDst) && flags.force) {
|
|
126
|
+
fs.rmSync(agentDst, { recursive: true, force: true });
|
|
127
|
+
audit.push("- ACT: DELETED .agent/");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const isMerge = fs.existsSync(agentDst);
|
|
131
|
+
const stats = copyDirRecursive(agentSrc, agentDst, undefined, isMerge);
|
|
132
|
+
audit.push(`- ACT: ${isMerge ? "MERGED" : "COPIED"} templates (Files: ${stats.files}, Skipped: ${stats.skipped})`);
|
|
133
|
+
|
|
134
|
+
// Criar state se necessário
|
|
135
|
+
const stateTarget = path.join(agentDst, "state");
|
|
136
|
+
if (!fs.existsSync(stateTarget)) {
|
|
137
|
+
fs.mkdirSync(stateTarget, { recursive: true });
|
|
138
|
+
// Criar mission_control.json default
|
|
139
|
+
const mcDefault = {
|
|
140
|
+
project_status: "active",
|
|
141
|
+
project_name: path.basename(targetPath),
|
|
142
|
+
sprint_goal: "",
|
|
143
|
+
agents: [
|
|
144
|
+
{ id: "orchestrator", role: "orchestrator", active: true },
|
|
145
|
+
{ id: "researcher", role: "researcher", active: true },
|
|
146
|
+
{ id: "writer", role: "writer", active: true },
|
|
147
|
+
],
|
|
148
|
+
task_queue: [],
|
|
149
|
+
history: [],
|
|
150
|
+
settings: {
|
|
151
|
+
work_dir: "mission_control",
|
|
152
|
+
max_tasks_per_tick: 2,
|
|
153
|
+
default_priority: "medium",
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
fs.writeFileSync(path.join(stateTarget, "mission_control.json"), JSON.stringify(mcDefault, null, 2));
|
|
157
|
+
fs.writeFileSync(path.join(stateTarget, "MEMORY.md"), "# Memória Persistente\n\n(Adicione aqui resumos e decisões importantes)\n");
|
|
158
|
+
audit.push("- ACT: CREATED .agent/state/ (mission_control.json + MEMORY.md)");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log("\n✨ IDE install concluído com sucesso!");
|
|
162
|
+
writeAudit(targetPath, audit, flags);
|
|
163
|
+
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error(`\n❌ Falha: ${err.message}`);
|
|
166
|
+
audit.push(`\n## ERROR: ${err.message}`);
|
|
167
|
+
writeAudit(targetPath, audit, flags);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Sub-comando: ide doctor
|
|
174
|
+
* Verifica se a IDE está "armada" com regras, hooks e skills.
|
|
175
|
+
*/
|
|
176
|
+
async function runDoctor({ targetPath }) {
|
|
177
|
+
const agentDir = path.join(targetPath, ".agent");
|
|
178
|
+
const checks = [];
|
|
179
|
+
|
|
180
|
+
console.log("\n🏥 IDE Doctor — Verificando instalação para IDE:\n");
|
|
181
|
+
|
|
182
|
+
// Verificar .agent/
|
|
183
|
+
checks.push({ name: ".agent/", ok: fs.existsSync(agentDir) });
|
|
184
|
+
|
|
185
|
+
// Verificar rules
|
|
186
|
+
const rulesDir = path.join(agentDir, "rules");
|
|
187
|
+
const requiredRules = ["CONSENT_FIRST.md", "ROUTER_PROTOCOL.md"];
|
|
188
|
+
for (const rule of requiredRules) {
|
|
189
|
+
checks.push({ name: `rules/${rule}`, ok: fs.existsSync(path.join(rulesDir, rule)) });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Verificar skills críticas
|
|
193
|
+
const skillsDir = path.join(agentDir, "skills");
|
|
194
|
+
const criticalSkills = ["openclaw-router", "openclaw-inspect", "openclaw-dev"];
|
|
195
|
+
for (const skill of criticalSkills) {
|
|
196
|
+
checks.push({ name: `skills/${skill}/SKILL.md`, ok: fs.existsSync(path.join(skillsDir, skill, "SKILL.md")) });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Verificar hooks
|
|
200
|
+
const hooksDir = path.join(agentDir, "hooks");
|
|
201
|
+
checks.push({ name: "hooks/pre-tool-use.js", ok: fs.existsSync(path.join(hooksDir, "pre-tool-use.js")) });
|
|
202
|
+
|
|
203
|
+
// Exibir resultado
|
|
204
|
+
let allOk = true;
|
|
205
|
+
for (const c of checks) {
|
|
206
|
+
const icon = c.ok ? "✅" : "❌";
|
|
207
|
+
console.log(` ${icon} ${c.name}`);
|
|
208
|
+
if (!c.ok) allOk = false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (allOk) {
|
|
212
|
+
console.log("\n🎉 IDE está totalmente configurada!");
|
|
213
|
+
} else {
|
|
214
|
+
console.log("\n⚠️ Componentes ausentes. Rode: npx openclaw ide install --apply");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = { run };
|
package/lib/cli/init.js
CHANGED
|
@@ -10,19 +10,42 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require("fs");
|
|
12
12
|
const path = require("path");
|
|
13
|
+
const readline = require("readline");
|
|
13
14
|
const { initConfigDefaults, writeJsonSafe } = require("../config");
|
|
15
|
+
const { detectContext, getAuditHeader } = require("../context");
|
|
14
16
|
|
|
15
17
|
// Caminho dos templates incluídos no pacote
|
|
16
18
|
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
|
|
17
19
|
|
|
20
|
+
function ask(q) {
|
|
21
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
return new Promise((res) => rl.question(q, (ans) => { rl.close(); res(ans.trim()); }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function safeRel(targetPath, p) {
|
|
26
|
+
return path.relative(targetPath, p);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeAudit(targetPath, lines, flags) {
|
|
30
|
+
if (flags.audit === false) return;
|
|
31
|
+
const auditDir = path.join(targetPath, ".agent", "audit");
|
|
32
|
+
if (!fs.existsSync(auditDir)) {
|
|
33
|
+
// Tenta criar apenas se estivermos em modo apply, mas aqui já devemos estar
|
|
34
|
+
try { fs.mkdirSync(auditDir, { recursive: true }); } catch (e) { }
|
|
35
|
+
}
|
|
36
|
+
const filename = `init-${new Date().toISOString().replace(/[:.]/g, "-")}.md`;
|
|
37
|
+
const auditPath = path.join(auditDir, filename);
|
|
38
|
+
try {
|
|
39
|
+
fs.writeFileSync(auditPath, lines.join("\n") + "\n", "utf8");
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.error("⚠️ Falha ao gravar auditoria:", e.message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
18
45
|
/**
|
|
19
|
-
* Copia diretório recursivamente
|
|
20
|
-
* @param {string} src — diretório fonte
|
|
21
|
-
* @param {string} dest — diretório destino
|
|
22
|
-
* @param {object} [stats] — contador de arquivos copiados
|
|
23
|
-
* @returns {object} stats com { files, dirs }
|
|
46
|
+
* Copia diretório recursivamente (Utilitário mantido)
|
|
24
47
|
*/
|
|
25
|
-
function copyDirRecursive(src, dest, stats = { files: 0, dirs: 0 }) {
|
|
48
|
+
function copyDirRecursive(src, dest, stats = { files: 0, dirs: 0, skipped: 0 }, merge = false) {
|
|
26
49
|
if (!fs.existsSync(dest)) {
|
|
27
50
|
fs.mkdirSync(dest, { recursive: true });
|
|
28
51
|
stats.dirs++;
|
|
@@ -35,110 +58,143 @@ function copyDirRecursive(src, dest, stats = { files: 0, dirs: 0 }) {
|
|
|
35
58
|
const destPath = path.join(dest, entry.name);
|
|
36
59
|
|
|
37
60
|
if (entry.isDirectory()) {
|
|
38
|
-
copyDirRecursive(srcPath, destPath, stats);
|
|
61
|
+
copyDirRecursive(srcPath, destPath, stats, merge);
|
|
39
62
|
} else {
|
|
40
|
-
fs.
|
|
41
|
-
|
|
63
|
+
if (merge && fs.existsSync(destPath)) {
|
|
64
|
+
stats.skipped++;
|
|
65
|
+
} else {
|
|
66
|
+
fs.copyFileSync(srcPath, destPath);
|
|
67
|
+
stats.files++;
|
|
68
|
+
}
|
|
42
69
|
}
|
|
43
70
|
}
|
|
44
|
-
|
|
45
71
|
return stats;
|
|
46
72
|
}
|
|
47
73
|
|
|
48
74
|
/**
|
|
49
|
-
* Executa o comando init.
|
|
50
|
-
* @param {object} options
|
|
51
|
-
* @param {string} options.targetPath — diretório alvo
|
|
52
|
-
* @param {object} options.flags — flags do CLI (force, quiet)
|
|
75
|
+
* Executa o comando init com segurança.
|
|
53
76
|
*/
|
|
54
77
|
async function run({ targetPath, flags }) {
|
|
55
78
|
const agentDir = path.join(targetPath, ".agent");
|
|
56
79
|
const configPath = path.join(targetPath, "openclaw.json");
|
|
80
|
+
const ctx = detectContext(targetPath);
|
|
57
81
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
// Default: Plan Mode (read-only), exceto se --apply for passado
|
|
83
|
+
const planMode = !flags.apply;
|
|
84
|
+
|
|
85
|
+
const actions = [];
|
|
86
|
+
const audit = [getAuditHeader(ctx, "init", flags)];
|
|
87
|
+
const errors = [];
|
|
64
88
|
|
|
65
|
-
//
|
|
89
|
+
// 1. Validar Templates
|
|
66
90
|
if (!fs.existsSync(TEMPLATES_DIR)) {
|
|
67
|
-
console.error("❌ Templates não encontrados. Pacote
|
|
91
|
+
console.error("❌ Templates não encontrados. Pacote corrompido.");
|
|
68
92
|
process.exit(1);
|
|
69
93
|
}
|
|
70
94
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (!flags.quiet) {
|
|
78
|
-
console.log("⚠️ --force: substituindo .agent/ existente\n");
|
|
95
|
+
// 2. Construir Plano
|
|
96
|
+
if (fs.existsSync(agentDir)) {
|
|
97
|
+
if (!flags.force && !flags.merge) {
|
|
98
|
+
console.error("❌ Diretório .agent/ já existe.");
|
|
99
|
+
console.error(" Use --merge (seguro) ou --force (destrutivo).");
|
|
100
|
+
process.exit(1);
|
|
79
101
|
}
|
|
80
|
-
|
|
102
|
+
if (flags.force) {
|
|
103
|
+
actions.push({ type: "DELETE_DIR", path: agentDir, reason: "--force requested" });
|
|
104
|
+
actions.push({ type: "CREATE_DIR", path: agentDir });
|
|
105
|
+
actions.push({ type: "COPY_DIR", from: TEMPLATES_DIR, to: agentDir });
|
|
106
|
+
} else if (flags.merge) {
|
|
107
|
+
actions.push({ type: "MERGE_DIR", from: TEMPLATES_DIR, to: agentDir, reason: "--merge requested" });
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
actions.push({ type: "CREATE_DIR", path: agentDir });
|
|
111
|
+
actions.push({ type: "COPY_DIR", from: TEMPLATES_DIR, to: agentDir });
|
|
81
112
|
}
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
if (!fs.existsSync(configPath)) {
|
|
115
|
+
actions.push({ type: "CREATE_FILE", path: configPath, reason: "Default config" });
|
|
116
|
+
} else {
|
|
117
|
+
actions.push({ type: "NOOP", path: configPath, reason: "Config exists" });
|
|
118
|
+
}
|
|
85
119
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
120
|
+
// 3. Exibir Plano
|
|
121
|
+
console.log(`\n🧭 Plano de Execução (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
|
|
122
|
+
console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
|
|
123
|
+
|
|
124
|
+
for (const a of actions) {
|
|
125
|
+
if (a.type === "DELETE_DIR") console.log(` 🔥 DELETE ${safeRel(targetPath, a.path)} (${a.reason})`);
|
|
126
|
+
if (a.type === "CREATE_DIR") console.log(` 📁 CREATE ${safeRel(targetPath, a.path)}`);
|
|
127
|
+
if (a.type === "COPY_DIR") console.log(` 📦 COPY templates -> ${safeRel(targetPath, a.to)}`);
|
|
128
|
+
if (a.type === "MERGE_DIR") console.log(` 🔄 MERGE templates -> ${safeRel(targetPath, a.to)} (Preservando existentes)`);
|
|
129
|
+
if (a.type === "CREATE_FILE") console.log(` 📝 CREATE ${safeRel(targetPath, a.path)}`);
|
|
130
|
+
if (a.type === "NOOP") console.log(` ✅ KEEP ${safeRel(targetPath, a.path)}`);
|
|
90
131
|
}
|
|
91
132
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
133
|
+
if (planMode) {
|
|
134
|
+
console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
|
|
135
|
+
console.log(" Para aplicar, rode: npx openclaw init --apply [--merge|--force]");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
96
138
|
|
|
97
|
-
|
|
98
|
-
|
|
139
|
+
// 4. Confirmação
|
|
140
|
+
if (!flags.yes) {
|
|
141
|
+
if (actions.some(a => a.type === "DELETE_DIR")) {
|
|
142
|
+
console.log("\n⚠️ PERIGO: Operação destrutiva detectada (--force).");
|
|
143
|
+
const phrase = await ask("Digite 'DELETE .agent' para confirmar: ");
|
|
144
|
+
if (phrase !== "DELETE .agent") {
|
|
145
|
+
console.log("⏹️ Cancelado.");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
const ok = await ask("\nAplicar este plano? (y/N): ");
|
|
150
|
+
if (ok.toLowerCase() !== "y") {
|
|
151
|
+
console.log("⏹️ Cancelado.");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
99
154
|
}
|
|
100
|
-
} else if (!flags.quiet) {
|
|
101
|
-
console.log("📋 openclaw.json já existe — mantido\n");
|
|
102
155
|
}
|
|
103
156
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
console.log("
|
|
107
|
-
listInstalledStructure(agentDir, " ");
|
|
157
|
+
// 5. Execução
|
|
158
|
+
try {
|
|
159
|
+
console.log("\n🚀 Executando...");
|
|
108
160
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
161
|
+
for (const a of actions) {
|
|
162
|
+
if (a.type === "DELETE_DIR") {
|
|
163
|
+
fs.rmSync(a.path, { recursive: true, force: true });
|
|
164
|
+
audit.push(`- ACT: DELETED ${a.path}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
115
167
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// Diretórios primeiro, depois arquivos
|
|
125
|
-
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
126
|
-
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
127
|
-
return a.name.localeCompare(b.name);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
for (let i = 0; i < entries.length; i++) {
|
|
131
|
-
const entry = entries[i];
|
|
132
|
-
const isLast = i === entries.length - 1;
|
|
133
|
-
const connector = isLast ? "└── " : "├── ";
|
|
134
|
-
const icon = entry.isDirectory() ? "📁" : "📄";
|
|
135
|
-
|
|
136
|
-
console.log(`${prefix}${connector}${icon} ${entry.name}`);
|
|
168
|
+
// Executar cópia/merge se necessário
|
|
169
|
+
const copyAction = actions.find(a => a.type === "COPY_DIR" || a.type === "MERGE_DIR");
|
|
170
|
+
if (copyAction) {
|
|
171
|
+
const isMerge = copyAction.type === "MERGE_DIR";
|
|
172
|
+
const stats = copyDirRecursive(TEMPLATES_DIR, agentDir, undefined, isMerge);
|
|
173
|
+
audit.push(`- ACT: ${isMerge ? "MERGED" : "COPIED"} templates (Files: ${stats.files}, Skipped: ${stats.skipped})`);
|
|
174
|
+
console.log(` ✅ Templates processados.`);
|
|
175
|
+
}
|
|
137
176
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
177
|
+
// Criar config se necessário
|
|
178
|
+
if (actions.some(a => a.type === "CREATE_FILE" && a.path === configPath)) {
|
|
179
|
+
const defaults = initConfigDefaults({});
|
|
180
|
+
writeJsonSafe(configPath, defaults);
|
|
181
|
+
audit.push(`- ACT: CREATED openclaw.json`);
|
|
182
|
+
console.log(` ✅ Config criada.`);
|
|
141
183
|
}
|
|
184
|
+
|
|
185
|
+
// Gravar contexto se ainda não existe
|
|
186
|
+
const contextDir = path.join(agentDir, "context");
|
|
187
|
+
if (!fs.existsSync(contextDir)) fs.mkdirSync(contextDir, { recursive: true });
|
|
188
|
+
fs.writeFileSync(path.join(contextDir, "context.json"), JSON.stringify(ctx, null, 2));
|
|
189
|
+
|
|
190
|
+
console.log("\n✨ Concluído com sucesso!");
|
|
191
|
+
writeAudit(targetPath, audit, flags);
|
|
192
|
+
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error(`\n❌ Falha na execução: ${err.message}`);
|
|
195
|
+
audit.push(`\n## ERROR: ${err.message}`);
|
|
196
|
+
writeAudit(targetPath, audit, flags);
|
|
197
|
+
process.exit(1);
|
|
142
198
|
}
|
|
143
199
|
}
|
|
144
200
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comando CLI: inspect
|
|
5
|
+
*
|
|
6
|
+
* 100% Read-Only. Coleta e exibe contexto do ambiente sem alterar nada.
|
|
7
|
+
* Usado como primeiro passo antes de qualquer ação.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const collectContext = require("../context/collector");
|
|
12
|
+
|
|
13
|
+
// Caminho dos templates do pacote
|
|
14
|
+
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Executa o comando inspect (read-only puro).
|
|
18
|
+
* @param {object} options
|
|
19
|
+
* @param {string} options.targetPath — diretório alvo
|
|
20
|
+
* @param {object} options.flags — flags do CLI
|
|
21
|
+
*/
|
|
22
|
+
async function run({ targetPath, flags }) {
|
|
23
|
+
const ctx = collectContext({
|
|
24
|
+
targetPath,
|
|
25
|
+
templatesDir: TEMPLATES_DIR,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (flags.quiet) {
|
|
29
|
+
// Modo silencioso: só JSON
|
|
30
|
+
console.log(JSON.stringify(ctx, null, 2));
|
|
31
|
+
return ctx;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("\n🔎 OpenClaw Inspect (Read-Only)\n");
|
|
35
|
+
console.log(` 🖥️ Plataforma: ${ctx.env.platform}`);
|
|
36
|
+
console.log(` 🐳 Docker: ${ctx.env.docker}`);
|
|
37
|
+
console.log(` 🪟 WSL: ${ctx.env.wsl}`);
|
|
38
|
+
console.log(` 💻 IDE: ${ctx.ide}`);
|
|
39
|
+
console.log(` 📂 Path: ${ctx.targetPath}`);
|
|
40
|
+
console.log(` 📦 OpenClaw instalado: ${ctx.openclaw.hasAgentDir ? "Sim" : "Não"}`);
|
|
41
|
+
console.log(` 📋 Config: ${ctx.openclaw.hasConfig ? "Sim" : "Não"}`);
|
|
42
|
+
console.log(` 🐙 Git repo: ${ctx.git.isRepo ? "Sim" : "Não"}`);
|
|
43
|
+
|
|
44
|
+
if (ctx.skillsInstalled.length > 0) {
|
|
45
|
+
console.log(`\n 🧠 Skills instaladas (${ctx.skillsInstalled.length}):`);
|
|
46
|
+
ctx.skillsInstalled.forEach(s => console.log(` • ${s.name}`));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (ctx.skillsInTemplates.length > 0) {
|
|
50
|
+
console.log(`\n 📦 Skills disponíveis nos templates (${ctx.skillsInTemplates.length}):`);
|
|
51
|
+
ctx.skillsInTemplates.forEach(s => console.log(` • ${s.name}`));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log("\n✅ Inspect concluído (nenhuma alteração feita).\n");
|
|
55
|
+
return ctx;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { run };
|