@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/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 };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context Collector — Read-only snapshot do ambiente.
|
|
5
|
+
* Nunca altera arquivos. Apenas lê e retorna dados.
|
|
6
|
+
*
|
|
7
|
+
* Baseado no módulo do openclaw-agents-addons,
|
|
8
|
+
* adaptado para o projeto principal.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const os = require("os");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
function exists(p) {
|
|
16
|
+
try { return fs.existsSync(p); } catch { return false; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detecta IDE ativa no workspace.
|
|
21
|
+
*/
|
|
22
|
+
function detectIDE(targetPath) {
|
|
23
|
+
if (exists(path.join(targetPath, ".cursor"))) return "cursor";
|
|
24
|
+
if (exists(path.join(targetPath, ".vscode"))) return "vscode";
|
|
25
|
+
if (exists(path.join(targetPath, ".idea"))) return "jetbrains";
|
|
26
|
+
return "unknown";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detecta ambiente de execução (SO, Docker, WSL).
|
|
31
|
+
*/
|
|
32
|
+
function detectEnvironment() {
|
|
33
|
+
const platform = os.platform();
|
|
34
|
+
const docker = exists("/.dockerenv") ||
|
|
35
|
+
(exists("/proc/1/cgroup") && fs.readFileSync("/proc/1/cgroup", "utf8").includes("docker"));
|
|
36
|
+
const wsl = platform === "linux" &&
|
|
37
|
+
(os.release().toLowerCase().includes("microsoft") || !!process.env.WSL_DISTRO_NAME);
|
|
38
|
+
return { platform, docker, wsl };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detecta instalação existente do OpenClaw.
|
|
43
|
+
*/
|
|
44
|
+
function detectOpenClaw(targetPath) {
|
|
45
|
+
const agentDir = path.join(targetPath, ".agent");
|
|
46
|
+
const config = path.join(targetPath, "openclaw.json");
|
|
47
|
+
const dockerCompose = path.join(targetPath, "docker-compose.yml");
|
|
48
|
+
return {
|
|
49
|
+
hasAgentDir: exists(agentDir),
|
|
50
|
+
hasConfig: exists(config),
|
|
51
|
+
hasDockerCompose: exists(dockerCompose),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Lista skills disponíveis nos templates.
|
|
57
|
+
*/
|
|
58
|
+
function listSkillsFromTemplates(templatesDir) {
|
|
59
|
+
const skillsDir = path.join(templatesDir, ".agent", "skills");
|
|
60
|
+
if (!exists(skillsDir)) return [];
|
|
61
|
+
const out = [];
|
|
62
|
+
for (const name of fs.readdirSync(skillsDir)) {
|
|
63
|
+
const skillPath = path.join(skillsDir, name, "SKILL.md");
|
|
64
|
+
if (exists(skillPath)) out.push({ name, skillPath });
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Lista skills instaladas no workspace do usuário.
|
|
71
|
+
*/
|
|
72
|
+
function listInstalledSkills(targetPath) {
|
|
73
|
+
const skillsDir = path.join(targetPath, ".agent", "skills");
|
|
74
|
+
if (!exists(skillsDir)) return [];
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const name of fs.readdirSync(skillsDir)) {
|
|
77
|
+
const skillPath = path.join(skillsDir, name, "SKILL.md");
|
|
78
|
+
if (exists(skillPath)) out.push({ name, skillPath });
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Coleta contexto completo (read-only).
|
|
85
|
+
* @param {object} options
|
|
86
|
+
* @param {string} options.targetPath — diretório do workspace
|
|
87
|
+
* @param {string} options.templatesDir — diretório de templates do pacote
|
|
88
|
+
* @returns {object} snapshot do contexto
|
|
89
|
+
*/
|
|
90
|
+
function collectContext({ targetPath, templatesDir }) {
|
|
91
|
+
const env = detectEnvironment();
|
|
92
|
+
return {
|
|
93
|
+
targetPath,
|
|
94
|
+
env,
|
|
95
|
+
ide: detectIDE(targetPath),
|
|
96
|
+
openclaw: detectOpenClaw(targetPath),
|
|
97
|
+
git: { isRepo: exists(path.join(targetPath, ".git")) },
|
|
98
|
+
skillsInTemplates: listSkillsFromTemplates(templatesDir),
|
|
99
|
+
skillsInstalled: listInstalledSkills(targetPath),
|
|
100
|
+
ts: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = collectContext;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detecta o ambiente de execução e metadados de contexto.
|
|
9
|
+
* Retorna um objeto seguro para logs e auditoria.
|
|
10
|
+
*/
|
|
11
|
+
function detectContext(cwd = process.cwd()) {
|
|
12
|
+
const ctx = {
|
|
13
|
+
timestamp: new Date().toISOString(),
|
|
14
|
+
platform: os.platform(),
|
|
15
|
+
cwd: cwd,
|
|
16
|
+
env: "local",
|
|
17
|
+
ide: "unknown",
|
|
18
|
+
isDocker: false,
|
|
19
|
+
hasExistingInstall: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// 1. Detectar Docker
|
|
23
|
+
if (fs.existsSync("/.dockerenv") || fs.existsSync("/run/.containerenv")) {
|
|
24
|
+
ctx.isDocker = true;
|
|
25
|
+
ctx.env = "docker";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. Detectar IDEs comuns
|
|
29
|
+
const ideMarkers = [
|
|
30
|
+
{ name: "vscode", path: ".vscode" },
|
|
31
|
+
{ name: "cursor", path: ".cursor" },
|
|
32
|
+
{ name: "idea", path: ".idea" },
|
|
33
|
+
{ name: "antigravity", path: ".agent/antigravity" } // Marcador fictício ou real se existir
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
for (const m of ideMarkers) {
|
|
37
|
+
if (fs.existsSync(path.join(cwd, m.path))) {
|
|
38
|
+
ctx.ide = m.name;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Detectar instalação existente
|
|
44
|
+
const agentDir = path.join(cwd, ".agent");
|
|
45
|
+
const configPath = path.join(cwd, "openclaw.json");
|
|
46
|
+
|
|
47
|
+
if (fs.existsSync(agentDir) || fs.existsSync(configPath)) {
|
|
48
|
+
ctx.hasExistingInstall = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return ctx;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Gera um cabeçalho de auditoria formatado em Markdown.
|
|
56
|
+
*/
|
|
57
|
+
function getAuditHeader(ctx, command, flags) {
|
|
58
|
+
return [
|
|
59
|
+
`# OpenClaw Audit Log`,
|
|
60
|
+
`- **Time**: ${ctx.timestamp}`,
|
|
61
|
+
`- **Command**: ${command}`,
|
|
62
|
+
`- **Mode**: ${flags.plan ? "PLAN (Simulation)" : "APPLY (Execution)"}`,
|
|
63
|
+
`- **Environment**: ${ctx.env} (Docker: ${ctx.isDocker})`,
|
|
64
|
+
`- **IDE**: ${ctx.ide}`,
|
|
65
|
+
`- **Existing Install**: ${ctx.hasExistingInstall}`,
|
|
66
|
+
`- **Flags**: ${JSON.stringify(flags)}`,
|
|
67
|
+
`---`,
|
|
68
|
+
``
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
detectContext,
|
|
74
|
+
getAuditHeader
|
|
75
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Skill Matcher — Escolhe a skill mais adequada para uma solicitação.
|
|
5
|
+
*
|
|
6
|
+
* Faz parse do YAML frontmatter de cada SKILL.md e pontua
|
|
7
|
+
* a relevância com base nos triggers e descrição.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extrai metadados (name, description, triggers) do frontmatter YAML.
|
|
15
|
+
* @param {string} md — conteúdo Markdown com frontmatter
|
|
16
|
+
* @returns {object|null} metadados extraídos ou null
|
|
17
|
+
*/
|
|
18
|
+
function parseFrontmatter(md) {
|
|
19
|
+
const m = md.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
20
|
+
if (!m) return null;
|
|
21
|
+
|
|
22
|
+
const yaml = m[1];
|
|
23
|
+
|
|
24
|
+
// Extrair listas (ex: triggers)
|
|
25
|
+
const getList = (key) => {
|
|
26
|
+
const r = new RegExp(`^${key}:\\s*\\n([\\s\\S]*?)(\\n\\w|$)`, "m");
|
|
27
|
+
const mm = yaml.match(r);
|
|
28
|
+
if (!mm) return [];
|
|
29
|
+
return mm[1]
|
|
30
|
+
.split("\n")
|
|
31
|
+
.map(l => l.trim())
|
|
32
|
+
.filter(l => l.startsWith("-"))
|
|
33
|
+
.map(l => l.replace(/^-\s*/, "").trim())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Extrair escalares (ex: name, description)
|
|
38
|
+
const getScalar = (key) => {
|
|
39
|
+
const r = new RegExp(`^${key}:\\s*(.+)$`, "m");
|
|
40
|
+
const mm = yaml.match(r);
|
|
41
|
+
return mm ? mm[1].trim() : "";
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
name: getScalar("name"),
|
|
46
|
+
description: getScalar("description"),
|
|
47
|
+
triggers: getList("triggers"),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Carrega todas as skills de um diretório de templates.
|
|
53
|
+
* @param {string} skillsDir — diretório com subpastas de skills
|
|
54
|
+
* @returns {Array} lista de skills com metadados
|
|
55
|
+
*/
|
|
56
|
+
function loadSkills(skillsDir) {
|
|
57
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
58
|
+
const skills = [];
|
|
59
|
+
for (const folder of fs.readdirSync(skillsDir)) {
|
|
60
|
+
const p = path.join(skillsDir, folder, "SKILL.md");
|
|
61
|
+
if (!fs.existsSync(p)) continue;
|
|
62
|
+
const md = fs.readFileSync(p, "utf8");
|
|
63
|
+
const meta = parseFrontmatter(md);
|
|
64
|
+
if (!meta) continue;
|
|
65
|
+
skills.push({ ...meta, path: p, folder });
|
|
66
|
+
}
|
|
67
|
+
return skills;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Calcula score de relevância de uma skill para um texto.
|
|
72
|
+
* @param {object} skill — skill com triggers e description
|
|
73
|
+
* @param {string} text — texto do usuário
|
|
74
|
+
* @returns {number} pontuação (maior = mais relevante)
|
|
75
|
+
*/
|
|
76
|
+
function scoreSkill(skill, text) {
|
|
77
|
+
const t = (text || "").toLowerCase();
|
|
78
|
+
let s = 0;
|
|
79
|
+
for (const trig of (skill.triggers || [])) {
|
|
80
|
+
if (t.includes(String(trig).toLowerCase())) s += 5;
|
|
81
|
+
}
|
|
82
|
+
// Bonus parcial para match de descrição
|
|
83
|
+
if (skill.description && t.includes(skill.description.toLowerCase().slice(0, 12))) s += 1;
|
|
84
|
+
return s;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Encontra a skill mais relevante para uma solicitação.
|
|
89
|
+
* @param {object} options
|
|
90
|
+
* @param {string} options.skillsDir — diretório de skills (templates/.agent/skills)
|
|
91
|
+
* @param {string} options.userText — texto da solicitação do usuário
|
|
92
|
+
* @returns {object} { chosen, alternatives, ranked }
|
|
93
|
+
*/
|
|
94
|
+
function matchSkill({ skillsDir, userText }) {
|
|
95
|
+
const skills = loadSkills(skillsDir);
|
|
96
|
+
const ranked = skills
|
|
97
|
+
.map(sk => ({ sk, score: scoreSkill(sk, userText) }))
|
|
98
|
+
.sort((a, b) => b.score - a.score);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
chosen: ranked[0]?.sk || null,
|
|
102
|
+
alternatives: ranked.slice(1, 4).map(x => x.sk),
|
|
103
|
+
ranked,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { matchSkill, loadSkills, parseFrontmatter };
|
|
@@ -176,6 +176,8 @@ async function main() {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
console.log("\n✅ Setup finalizado.");
|
|
179
|
+
console.log("\n🌐 CONTROL UI: http://127.0.0.1:18789");
|
|
180
|
+
console.log(" Acesse para gerenciar seus agentes visualmente.\n");
|
|
179
181
|
console.log("Próximo passo: configurar VPN (WireGuard) e aplicar policies (skills/openclaw-ops).");
|
|
180
182
|
rl.close();
|
|
181
183
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabioforest/openclaw",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.4.1",
|
|
4
|
+
"description": "Agentes autônomos para engenharia de software",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Workflow Automator
|
|
3
|
+
description: Especialista em criar, validar e otimizar workflows e automações do OpenClaw.
|
|
4
|
+
system_prompt: |
|
|
5
|
+
Você é o Workflow Automator, um agente especializado na arquitetura de automação do OpenClaw.
|
|
6
|
+
|
|
7
|
+
## Suas Responsabilidades
|
|
8
|
+
1. **Criar Workflows**: Escrever arquivos `.md` válidos em `.agent/workflows/` seguindo a sintaxe de nodes.
|
|
9
|
+
2. **Explicar Conceitos**: Ensinar sobre `AI Capture`, `Interactive Nodes` e `Message Nodes`.
|
|
10
|
+
3. **Validar Sintaxe**: Garantir que o YAML frontmatter e a estrutura dos passos estejam corretos.
|
|
11
|
+
|
|
12
|
+
## Conhecimento de Sintaxe
|
|
13
|
+
Você domina a estrutura de workflows do OpenClaw:
|
|
14
|
+
- **Frontmatter**: `description`, `params` (opcional).
|
|
15
|
+
- **Steps**: Lista numerada ou bullets.
|
|
16
|
+
- **AI Capture**: Uso de prompts para extrair JSON de conversas.
|
|
17
|
+
- **Integração**: Como chamar skills dentro de workflows.
|
|
18
|
+
|
|
19
|
+
## Personalidade
|
|
20
|
+
Técnico, preciso e focado em eficiência. Você adora transformar processos manuais em arquivos `.md` elegantes.
|
|
21
|
+
---
|
|
22
|
+
# Workflow Automator
|
|
23
|
+
|
|
24
|
+
Olá! Eu sou o especialista em **Workflows Inteligentes**.
|
|
25
|
+
|
|
26
|
+
Posso ajudar você a:
|
|
27
|
+
1. Criar um workflow de **Onboarding** que coleta dados do usuário.
|
|
28
|
+
2. Configurar um **AI Capture** para estruturar pedidos ou tickets.
|
|
29
|
+
3. Debugar um workflow que não está rodando corretamente.
|
|
30
|
+
|
|
31
|
+
Comando sugerido: `Crie um workflow para coletar feedback de usuários`
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Regra de Ouro: Consentimento Prévio para Alterações
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Consent First (Segurança Absoluta)
|
|
6
|
+
|
|
7
|
+
Como um agente OpenClaw, você opera sob um contrato estrito de "Read-Only por Padrão".
|
|
8
|
+
|
|
9
|
+
## 1. Regra de Ouro
|
|
10
|
+
**Nunca altere, apague ou crie arquivos sem que o usuário tenha solicitado explicitamente essa ação específica.**
|
|
11
|
+
|
|
12
|
+
## 2. Protocolo de Modificação
|
|
13
|
+
Antes de qualquer operação de escrita (write, edit, delete, move), você deve:
|
|
14
|
+
1. **Analisar**: Entender o contexto e o impacto.
|
|
15
|
+
2. **Planejar**: Explicar ao usuário o que será feito.
|
|
16
|
+
3. **Confirmar**: Perguntar "Posso prosseguir?" ou aguardar comando explícito (ex: `--apply`).
|
|
17
|
+
|
|
18
|
+
## 3. Proibições Estritas
|
|
19
|
+
- Nunca execute `rm -rf`, `git clean` ou deletar diretórios inteiros sem um aviso gigante e confirmação dupla.
|
|
20
|
+
- Nunca sobrescreva arquivos de configuração (`openclaw.json`, `.env`) silenciosamente.
|
|
21
|
+
- Nunca assuma que pode "consertar" algo sem perguntar antes.
|
|
22
|
+
|
|
23
|
+
## 4. Auditoria
|
|
24
|
+
Sempre que realizar uma alteração, registre o que foi feito. O sistema já gera logs em `.agent/audit/`, mas você deve comunicar o sucesso ao usuário com clareza.
|