@fabioforest/openclaw 3.9.0 → 3.10.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 CHANGED
@@ -43,7 +43,8 @@ const COMMANDS = {
43
43
  function parseArgs(argv) {
44
44
  const flags = {
45
45
  plan: true, // Default: PLAN mode (read-only)
46
- audit: true // Default: Generate audit logs
46
+ audit: true, // Default: Generate audit logs
47
+ _: [] // Captura subcomandos/args
47
48
  };
48
49
  let command = null;
49
50
 
@@ -62,7 +63,6 @@ function parseArgs(argv) {
62
63
  flags.quiet = true;
63
64
  } else if (arg === "--apply") {
64
65
  flags.apply = true;
65
- flags.plan = false; // Apply overrides Plan default
66
66
  } else if (arg === "--plan") {
67
67
  flags.plan = true;
68
68
  } else if (arg === "--yes" || arg === "-y") {
@@ -71,13 +71,21 @@ function parseArgs(argv) {
71
71
  flags.audit = false;
72
72
  } else if (arg === "--merge") {
73
73
  flags.merge = true;
74
- } else if (!arg.startsWith("-") && !command) {
75
- command = arg;
74
+ } else if (!arg.startsWith("-")) {
75
+ if (!command) {
76
+ command = arg;
77
+ } else {
78
+ flags._.push(arg); // Captura subcomandos/args
79
+ }
76
80
  }
77
81
  }
78
82
 
79
- // Se apply foi passado, plan é false (a menos que forçado explicitamente, mas apply vence na lógica acima)
80
- // Se o usuário não passar nada, plan=true (default)
83
+ // Regra de precedência: apply sempre vence plan
84
+ if (flags.apply) flags.plan = false;
85
+
86
+ // Constrói ajudantes semânticos a partir de _
87
+ flags.subcommand = flags._[0] || null;
88
+ flags.args = flags._.slice(1);
81
89
 
82
90
  return { command, flags };
83
91
  }
package/lib/cli/ide.js CHANGED
@@ -14,6 +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
18
 
18
19
  // Caminho dos templates do pacote
19
20
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates");
@@ -28,21 +29,9 @@ function writeAudit(targetPath, lines, flags) {
28
29
  writeCliAudit(targetPath, lines, flags, "ide");
29
30
  }
30
31
 
31
- /**
32
- * Executa o comando ide.
33
- * Uso: openclaw ide install [--apply] [--force]
34
- * openclaw ide doctor
35
- *
36
- * @param {object} options
37
- * @param {string} options.targetPath — diretório alvo
38
- * @param {object} options.flags — flags do CLI
39
- */
40
32
  async function run({ targetPath, flags }) {
41
- // Detectar sub-comando a partir de args restantes
42
- // O bin/openclaw.js passa o comando "ide", então precisamos
43
- // verificar os args para o sub-comando
44
- const args = process.argv.slice(3);
45
- const subCmd = args.find(a => !a.startsWith("-")) || "install";
33
+ // Detectar sub-comando alinhado com o parseArgs global
34
+ const subCmd = flags.subcommand || "install";
46
35
 
47
36
  if (subCmd === "doctor") {
48
37
  return runDoctor({ targetPath, flags });
@@ -84,6 +73,16 @@ async function runInstall({ targetPath, flags }) {
84
73
  console.log(" ✅ KEEP .agent/state/ (já existe)");
85
74
  }
86
75
 
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);
82
+ }
83
+
84
+ await guardPlan(targetPath, intents, flags);
85
+
87
86
  if (planMode) {
88
87
  console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
89
88
  console.log(" Para aplicar, rode: npx openclaw ide install --apply");
package/lib/cli/init.js CHANGED
@@ -14,6 +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
18
 
18
19
  // Caminho dos templates incluídos no pacote
19
20
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
@@ -107,6 +108,17 @@ async function run({ targetPath, flags }) {
107
108
  actions.push({ type: "NOOP", path: configPath, reason: "Config exists" });
108
109
  }
109
110
 
111
+ // 2.5. Acionar Scope Guard
112
+ const intents = { writes: [], deletes: [], overwrites: [] };
113
+ for (const a of actions) {
114
+ if (a.type === "DELETE_DIR") intents.deletes.push(a.path);
115
+ if (a.type === "CREATE_DIR") intents.writes.push(a.path);
116
+ if (a.type === "CREATE_FILE") intents.writes.push(a.path);
117
+ if (a.type === "COPY_DIR" || a.type === "MERGE_DIR") intents.writes.push(a.to);
118
+ }
119
+
120
+ await guardPlan(targetPath, intents, flags);
121
+
110
122
  // 3. Exibir Plano
111
123
  console.log(`\n🧭 Plano de Execução (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
112
124
  console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
@@ -122,7 +134,7 @@ async function run({ targetPath, flags }) {
122
134
 
123
135
  if (planMode) {
124
136
  console.log("\n🔒 Modo PLAN (Read-Only). Nenhuma alteração feita.");
125
- console.log(" Para aplicar, rode: npx openclaw init --apply [--merge|--force]");
137
+ console.log(" Para aplicar, rode o comando com --apply");
126
138
  return;
127
139
  }
128
140
 
@@ -15,6 +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
19
 
19
20
  function ask(q) {
20
21
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -103,6 +104,10 @@ async function run({ targetPath, flags }) {
103
104
  console.log(` 🔴 REMOVER openclaw.json`);
104
105
  }
105
106
 
107
+ // Acionar Scope Guard para todos os deletes listados
108
+ const intents = { writes: [], deletes: toRemove.map(i => i.path), overwrites: [] };
109
+ await guardPlan(targetPath, intents, flags);
110
+
106
111
  // Verificar audit logs que seriam perdidos
107
112
  const auditDir = path.join(agentDir, "audit");
108
113
  if (fs.existsSync(auditDir)) {
package/lib/cli/update.js CHANGED
@@ -15,6 +15,7 @@ const crypto = require("crypto");
15
15
  const readline = require("readline");
16
16
  const { detectContext, getAuditHeader } = require("../context");
17
17
  const { writeCliAudit } = require("../utils/audit-writer");
18
+ const { guardPlan } = require("../utils/scope_guard");
18
19
 
19
20
  // Caminho dos templates incluídos no pacote
20
21
  const TEMPLATES_DIR = path.join(__dirname, "..", "..", "templates", ".agent");
@@ -127,6 +128,12 @@ async function run({ targetPath, flags }) {
127
128
  const actions = planUpdates(TEMPLATES_DIR, agentDir);
128
129
  const audit = [getAuditHeader(ctx, "update", flags)];
129
130
 
131
+ // 1.5 Acionar Scope Guard
132
+ const intents = { writes: [], deletes: [], overwrites: [] };
133
+ for (const a of actions.added) intents.writes.push(a.dest);
134
+ for (const a of actions.updated) intents.overwrites.push(a.dest);
135
+ await guardPlan(targetPath, intents, flags);
136
+
130
137
  // 2. Exibir Plano
131
138
  console.log(`\n🧭 Plano de Atualização (${planMode ? "SIMULAÇÃO" : "APPLY"}):\n`);
132
139
  console.log(` Contexto: ${ctx.env} | IDE: ${ctx.ide}\n`);
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const readline = require("readline");
6
+
7
+ /**
8
+ * Utilitário para prompt interativo.
9
+ */
10
+ function askQuestion(query) {
11
+ const rl = readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout,
14
+ });
15
+ return new Promise((resolve) => rl.question(query, (ans) => {
16
+ rl.close();
17
+ resolve(ans.trim());
18
+ }));
19
+ }
20
+
21
+ /**
22
+ * Valida se um caminho pretendido está dentro do diretório seguro.
23
+ * @param {string} targetPath - Caminho do projeto destino.
24
+ * @param {string} fileToMutate - O arquivo/pasta a ser mexido.
25
+ * @returns {boolean} - True se .agent/** ou default safe.
26
+ */
27
+ function isPathInSafeScope(targetPath, fileToMutate) {
28
+ // Escopo seguro default: pasta `.agent` na raiz do targetPath
29
+ const safeScope = path.join(targetPath, ".agent");
30
+ const absoluteMutate = path.resolve(targetPath, fileToMutate);
31
+
32
+ // Arquivos no diretório pai principal que não deveriam ser bloqueados:
33
+ // Padrão do projeto autoriza explicitamente "openclaw.json", ".env.example" no wizard?
34
+ // Para simplificar, focamos na "prisão" primária.
35
+
36
+ // allow openclaw.json default config
37
+ if (absoluteMutate === path.join(targetPath, "openclaw.json")) return true;
38
+
39
+ // Retorna true se começa com o caminho the escopo.
40
+ return absoluteMutate.startsWith(safeScope);
41
+ }
42
+
43
+ /**
44
+ * Escudo de Proteção "Scope Guard"
45
+ * Evita que o CLI apague ou modifique arquivos da aplicação nativa (ex: index.js, package.json do usuário).
46
+ *
47
+ * @param {string} targetPath - Caminho de execução (process.cwd geralmente)
48
+ * @param {object} intents - Objeto contendo listas { writes: [], deletes: [], overwrites: [] } com paths relativos ou absolutos
49
+ * @param {object} flags - As CLI Flags (`force`, `yes`, `apply`)
50
+ */
51
+ async function guardPlan(targetPath, intents = { writes: [], deletes: [], overwrites: [] }, flags = {}) {
52
+ const outOfScope = {
53
+ writes: intents.writes.filter(p => !isPathInSafeScope(targetPath, p)),
54
+ deletes: intents.deletes.filter(p => !isPathInSafeScope(targetPath, p)),
55
+ overwrites: intents.overwrites.filter(p => !isPathInSafeScope(targetPath, p)),
56
+ };
57
+
58
+ const totalRisks = outOfScope.writes.length + outOfScope.deletes.length + outOfScope.overwrites.length;
59
+
60
+ // Se tudo ok (nenhuma fuga da jail), permitimos silenciosamente sem drama
61
+ if (totalRisks === 0) return true;
62
+
63
+ console.log("\n🛡️ [SCOPE GUARD ALERTA DE SEGURANÇA]");
64
+ console.log("-----------------------------------------");
65
+ console.log(`Detectado tentativa de alterar dados FORA do sandbox (.agent/**).`);
66
+
67
+ if (outOfScope.writes.length > 0) {
68
+ console.log("⚠️ Arquivos a GERAR fora do escopo:", outOfScope.writes);
69
+ }
70
+ if (outOfScope.overwrites.length > 0) {
71
+ console.log("🧨 Arquivos a SOBRESCREVER fora do escopo:", outOfScope.overwrites);
72
+ }
73
+ if (outOfScope.deletes.length > 0) {
74
+ console.log("🔥 DELETE SOLICITADO fora do escopo:", outOfScope.deletes);
75
+ }
76
+ console.log("-----------------------------------------\n");
77
+
78
+ // Lógica para modo interativo. Se flag Force vier sem ask, permitimos por conta-risco.
79
+ if (flags.force) {
80
+ console.log("⚠️ Flag --force detectada. Destravando ação destrutiva fora da sandbox.");
81
+ return true;
82
+ }
83
+
84
+ if (flags.yes) {
85
+ console.log("❌ Bloqueado! Não permitimos escapes da sandbox via '--yes'. Você deve rodar o processo interativamente ou usar --force.");
86
+ process.exit(1);
87
+ }
88
+
89
+ // Interactive confirmation com hard-stop (obriga escrever DESTRUCTIVE para deletes/overwrites)
90
+ if (outOfScope.deletes.length > 0 || outOfScope.overwrites.length > 0) {
91
+ const ans = await askQuestion("Perigo crítico. Para aprovar sobrescritas ou apagamentos fora do .agent, digite 'DESTRUCTIVE': ");
92
+ if (ans !== "DESTRUCTIVE") {
93
+ console.log("⏹️ Cancelado pelo Módulo Scope Guard.");
94
+ process.exit(1);
95
+ }
96
+ return true;
97
+ }
98
+
99
+ // Para WRITES brandos: conf simples
100
+ const ans = await askQuestion("Aprovar criação de novas rotas fora do sandbox? [y/N]: ");
101
+ if (ans.toLowerCase() !== "y") {
102
+ console.log("⏹️ Cancelado pelo Módulo Scope Guard.");
103
+ process.exit(1);
104
+ }
105
+
106
+ return true;
107
+ }
108
+
109
+ module.exports = {
110
+ guardPlan,
111
+ isPathInSafeScope
112
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabioforest/openclaw",
3
- "version": "3.9.0",
3
+ "version": "3.10.0",
4
4
  "description": "Agentes autônomos para engenharia de software",
5
5
  "publishConfig": {
6
6
  "access": "public"