@fabioforest/openclaw 3.0.0 → 3.4.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/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/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 };
|
package/lib/cli/orchestrate.js
CHANGED
|
@@ -6,6 +6,19 @@ const initCmd = require("./init");
|
|
|
6
6
|
const doctorCmd = require("./doctor");
|
|
7
7
|
const debugCmd = require("./debug");
|
|
8
8
|
|
|
9
|
+
const readline = require("readline");
|
|
10
|
+
|
|
11
|
+
function askQuestion(query) {
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout,
|
|
15
|
+
});
|
|
16
|
+
return new Promise((resolve) => rl.question(query, (ans) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(ans);
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
module.exports = {
|
|
10
23
|
run: async function ({ targetPath, flags }) {
|
|
11
24
|
const agentDir = path.join(targetPath, ".agent");
|
|
@@ -15,29 +28,44 @@ module.exports = {
|
|
|
15
28
|
if (!fs.existsSync(agentDir)) {
|
|
16
29
|
console.log("⚠️ Instalação não encontrada.");
|
|
17
30
|
console.log("🚀 Iniciando processo de instalação (init)...");
|
|
18
|
-
|
|
19
|
-
// Repassa flags, força path atual
|
|
20
31
|
await initCmd.run({ targetPath, flags });
|
|
21
32
|
return;
|
|
22
33
|
}
|
|
23
34
|
|
|
24
|
-
console.log("✅ Instalação detectada.");
|
|
25
|
-
console.log("
|
|
35
|
+
console.log("✅ Instalação detectada! Contexto preservado.");
|
|
36
|
+
console.log(" (Arquivos em .agent/ encontrados)\n");
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
console.log("O que deseja fazer?");
|
|
39
|
+
console.log(" [1] 🏥 Verificar Saúde (Doctor) - Recomendado");
|
|
40
|
+
console.log(" [2] 🔄 Atualizar (Safe Merge) - Adiciona novidades, mantém edições");
|
|
41
|
+
console.log(" [3] ⚠️ Reinstalar (Force) - ALERTA: Sobrescreve TUDO");
|
|
42
|
+
console.log(" [4] 🚪 Sair\n");
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
const ans = await askQuestion("Escolha uma opção [1-4]: ");
|
|
45
|
+
const choice = ans.trim();
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
} catch (err) {
|
|
37
|
-
console.log("\n❌ Doctor encontrou problemas ou falhou.");
|
|
38
|
-
console.log("🔍 Iniciando diagnóstico avançado (debug)...");
|
|
47
|
+
console.log(""); // quebra de linha
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
if (choice === "1" || choice === "") {
|
|
50
|
+
console.log("🏥 Iniciando Doctor...");
|
|
51
|
+
await doctorCmd.run({ targetPath, flags });
|
|
52
|
+
} else if (choice === "2") {
|
|
53
|
+
console.log("🔄 Iniciando Safe Merge...");
|
|
54
|
+
// Como o usuário já confirmou interativamente aqui, passamos apply + yes
|
|
55
|
+
const safeFlags = { ...flags, merge: true, apply: true, yes: true };
|
|
56
|
+
await initCmd.run({ targetPath, flags: safeFlags });
|
|
57
|
+
} else if (choice === "3") {
|
|
58
|
+
const confirm = await askQuestion("Tem certeza? Isso apagará todas as suas customizações em .agent/. Digite 'sim' para confirmar: ");
|
|
59
|
+
if (confirm.toLowerCase() === "sim") {
|
|
60
|
+
console.log("⚠️ Iniciando Reinstalação Forçada...");
|
|
61
|
+
// Aqui passamos force, apply e yes (já confirmou a string "sim")
|
|
62
|
+
const forceFlags = { ...flags, force: true, apply: true, yes: true };
|
|
63
|
+
await initCmd.run({ targetPath, flags: forceFlags });
|
|
64
|
+
} else {
|
|
65
|
+
console.log("⏹️ Cancelado.");
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.log("👋 Saindo.");
|
|
41
69
|
}
|
|
42
70
|
}
|
|
43
71
|
};
|
package/lib/cli/update.js
CHANGED
|
@@ -12,14 +12,38 @@
|
|
|
12
12
|
const fs = require("fs");
|
|
13
13
|
const path = require("path");
|
|
14
14
|
const crypto = require("crypto");
|
|
15
|
+
const readline = require("readline");
|
|
16
|
+
const { detectContext, getAuditHeader } = require("../context");
|
|
15
17
|
|
|
16
18
|
// Caminho dos templates incluídos no pacote
|
|
17
19
|
const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
|
|
18
20
|
|
|
21
|
+
function ask(q) {
|
|
22
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
+
return new Promise((res) => rl.question(q, (ans) => { rl.close(); res(ans.trim()); }));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function safeRel(targetPath, p) {
|
|
27
|
+
return path.relative(targetPath, p);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeAudit(targetPath, lines, flags) {
|
|
31
|
+
if (flags.audit === false) return;
|
|
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
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
19
45
|
/**
|
|
20
|
-
* Calcula o SHA-256 de um arquivo
|
|
21
|
-
* @param {string} filePath — caminho do arquivo
|
|
22
|
-
* @returns {string} hash em hex
|
|
46
|
+
* Calcula o SHA-256 de um arquivo (Utilitário mantido)
|
|
23
47
|
*/
|
|
24
48
|
function fileHash(filePath) {
|
|
25
49
|
const content = fs.readFileSync(filePath);
|
|
@@ -27,14 +51,13 @@ function fileHash(filePath) {
|
|
|
27
51
|
}
|
|
28
52
|
|
|
29
53
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @param {string} dest — diretório destino (instalado)
|
|
33
|
-
* @param {object} stats — contadores { updated, skipped, added }
|
|
54
|
+
* Analisa atualizações necessárias.
|
|
55
|
+
* Retorna lista de ações planejadas.
|
|
34
56
|
*/
|
|
35
|
-
function
|
|
57
|
+
function planUpdates(src, dest, actions = { added: [], updated: [], skipped: [] }) {
|
|
36
58
|
if (!fs.existsSync(dest)) {
|
|
37
|
-
|
|
59
|
+
// Diretório não existe no destino, será criado implicitamente na cópia
|
|
60
|
+
// Mas a lógica recursiva precisa entrar
|
|
38
61
|
}
|
|
39
62
|
|
|
40
63
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -44,79 +67,122 @@ function updateDirRecursive(src, dest, stats) {
|
|
|
44
67
|
const destPath = path.join(dest, entry.name);
|
|
45
68
|
|
|
46
69
|
if (entry.isDirectory()) {
|
|
47
|
-
|
|
70
|
+
if (!fs.existsSync(destPath)) {
|
|
71
|
+
// Diretório novo, tudo dentro será added
|
|
72
|
+
// Simplificação: marcar diretório como added e não recursar?
|
|
73
|
+
// Melhor recursar para listar arquivos
|
|
74
|
+
}
|
|
75
|
+
planUpdates(srcPath, destPath, actions);
|
|
48
76
|
} else {
|
|
49
77
|
if (!fs.existsSync(destPath)) {
|
|
50
|
-
|
|
51
|
-
fs.copyFileSync(srcPath, destPath);
|
|
52
|
-
stats.added.push(path.relative(dest, destPath) || entry.name);
|
|
78
|
+
actions.added.push({ src: srcPath, dest: destPath });
|
|
53
79
|
} else {
|
|
54
|
-
// Arquivo já existe — comparar hashes
|
|
55
80
|
const srcHash = fileHash(srcPath);
|
|
56
81
|
const destHash = fileHash(destPath);
|
|
57
|
-
|
|
58
82
|
if (srcHash === destHash) {
|
|
59
|
-
|
|
60
|
-
stats.skipped.push(path.relative(dest, destPath) || entry.name);
|
|
83
|
+
actions.skipped.push({ src: srcPath, dest: destPath });
|
|
61
84
|
} else {
|
|
62
|
-
|
|
63
|
-
// Preserva o original do usuário fazendo backup
|
|
64
|
-
const backupPath = destPath + ".bak";
|
|
65
|
-
fs.copyFileSync(destPath, backupPath);
|
|
66
|
-
fs.copyFileSync(srcPath, destPath);
|
|
67
|
-
stats.updated.push(path.relative(dest, destPath) || entry.name);
|
|
85
|
+
actions.updated.push({ src: srcPath, dest: destPath });
|
|
68
86
|
}
|
|
69
87
|
}
|
|
70
88
|
}
|
|
71
89
|
}
|
|
90
|
+
return actions;
|
|
72
91
|
}
|
|
73
92
|
|
|
74
93
|
/**
|
|
75
|
-
* Executa o comando update.
|
|
76
|
-
* @param {object} options
|
|
77
|
-
* @param {string} options.targetPath — diretório alvo
|
|
78
|
-
* @param {object} options.flags — flags do CLI
|
|
94
|
+
* Executa o comando update com segurança.
|
|
79
95
|
*/
|
|
80
96
|
async function run({ targetPath, flags }) {
|
|
81
97
|
const agentDir = path.join(targetPath, ".agent");
|
|
98
|
+
const ctx = detectContext(targetPath);
|
|
99
|
+
|
|
100
|
+
// Default: Plan Mode
|
|
101
|
+
const planMode = !flags.apply;
|
|
82
102
|
|
|
83
103
|
if (!fs.existsSync(agentDir)) {
|
|
84
104
|
console.error("❌ Diretório .agent/ não encontrado.");
|
|
85
|
-
console.error(" Rode 'openclaw init' primeiro
|
|
105
|
+
console.error(" Rode 'openclaw init' primeiro.");
|
|
86
106
|
process.exit(1);
|
|
87
107
|
}
|
|
88
|
-
|
|
89
108
|
if (!fs.existsSync(TEMPLATES_DIR)) {
|
|
90
|
-
console.error("❌ Templates não encontrados.
|
|
109
|
+
console.error("❌ Templates não encontrados.");
|
|
91
110
|
process.exit(1);
|
|
92
111
|
}
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
// 1. Planejar
|
|
114
|
+
const actions = planUpdates(TEMPLATES_DIR, agentDir);
|
|
115
|
+
const audit = [getAuditHeader(ctx, "update", flags)];
|
|
116
|
+
|
|
117
|
+
// 2. Exibir Plano
|
|
118
|
+
console.log(`\n🧭 Plano de Atualização (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
|
|
119
|
+
console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
|
|
120
|
+
|
|
121
|
+
if (actions.added.length > 0) {
|
|
122
|
+
console.log(`📄 Novos (${actions.added.length}):`);
|
|
123
|
+
actions.added.forEach(a => console.log(` + CREATE ${safeRel(targetPath, a.dest)}`));
|
|
124
|
+
}
|
|
125
|
+
if (actions.updated.length > 0) {
|
|
126
|
+
console.log(`\n🔄 Modificados (${actions.updated.length}):`);
|
|
127
|
+
actions.updated.forEach(a => console.log(` ~ UPDATE ${safeRel(targetPath, a.dest)} (Backup gerado)`));
|
|
128
|
+
}
|
|
129
|
+
if (actions.skipped.length > 0) {
|
|
130
|
+
console.log(`\n⏭️ Ignorados (${actions.skipped.length} arquivos idênticos)`);
|
|
96
131
|
}
|
|
97
132
|
|
|
98
|
-
|
|
99
|
-
|
|
133
|
+
if (actions.added.length === 0 && actions.updated.length === 0) {
|
|
134
|
+
console.log("\n✅ Tudo atualizado. Nenhuma alteração necessária.");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (planMode) {
|
|
139
|
+
console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
|
|
140
|
+
console.log(" Para aplicar, rode: npx openclaw update --apply");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
100
143
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
144
|
+
// 3. Confirmação
|
|
145
|
+
if (!flags.yes) {
|
|
146
|
+
const ok = await ask("\nAplicar este plano? (y/N): ");
|
|
147
|
+
if (ok.toLowerCase() !== "y") {
|
|
148
|
+
console.log("⏹️ Cancelado.");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 4. Execução
|
|
154
|
+
try {
|
|
155
|
+
console.log("\n🚀 Executando atualizações...");
|
|
156
|
+
|
|
157
|
+
// Criar diretórios necessários
|
|
158
|
+
function ensureDir(p) {
|
|
159
|
+
const dir = path.dirname(p);
|
|
160
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
105
161
|
}
|
|
106
162
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
163
|
+
for (const action of actions.added) {
|
|
164
|
+
ensureDir(action.dest);
|
|
165
|
+
fs.copyFileSync(action.src, action.dest);
|
|
166
|
+
audit.push(`- ACT: CREATED ${safeRel(targetPath, action.dest)}`);
|
|
110
167
|
}
|
|
111
168
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
169
|
+
for (const action of actions.updated) {
|
|
170
|
+
ensureDir(action.dest);
|
|
171
|
+
const backupPath = action.dest + ".bak";
|
|
172
|
+
fs.copyFileSync(action.dest, backupPath);
|
|
173
|
+
fs.copyFileSync(action.src, action.dest);
|
|
174
|
+
audit.push(`- ACT: UPDATED ${safeRel(targetPath, action.dest)} (Backup: ${path.basename(backupPath)})`);
|
|
115
175
|
}
|
|
116
176
|
|
|
117
|
-
|
|
118
|
-
|
|
177
|
+
console.log("\n✨ Atualização concluída com sucesso!");
|
|
178
|
+
writeAudit(targetPath, audit, flags);
|
|
179
|
+
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error(`\n❌ Falha na execução: ${err.message}`);
|
|
182
|
+
audit.push(`\n## ERROR: ${err.message}`);
|
|
183
|
+
writeAudit(targetPath, audit, flags);
|
|
184
|
+
process.exit(1);
|
|
119
185
|
}
|
|
120
186
|
}
|
|
121
187
|
|
|
122
|
-
module.exports = { run,
|
|
188
|
+
module.exports = { run, fileHash };
|