@fabioforest/openclaw 3.0.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.
Files changed (43) hide show
  1. package/README.md +88 -0
  2. package/bin/openclaw.js +152 -0
  3. package/lib/channels.js +84 -0
  4. package/lib/cli/debug.js +12 -0
  5. package/lib/cli/doctor.js +266 -0
  6. package/lib/cli/init.js +145 -0
  7. package/lib/cli/orchestrate.js +43 -0
  8. package/lib/cli/status.js +128 -0
  9. package/lib/cli/update.js +122 -0
  10. package/lib/config.js +68 -0
  11. package/lib/detect.js +49 -0
  12. package/lib/ops/audit.js +156 -0
  13. package/lib/ops/enroll.js +133 -0
  14. package/lib/ops/exec.js +140 -0
  15. package/lib/ops/healthcheck.js +167 -0
  16. package/lib/ops/policy.js +153 -0
  17. package/lib/ops/transfer.js +152 -0
  18. package/lib/ops/update-safe.js +173 -0
  19. package/lib/ops/vpn.js +131 -0
  20. package/lib/security.js +48 -0
  21. package/lib/setup/config_wizard.js +186 -0
  22. package/package.json +47 -0
  23. package/templates/.agent/agents/setup-specialist.md +24 -0
  24. package/templates/.agent/agents/sysadmin-proativo.md +31 -0
  25. package/templates/.agent/hooks/pre-tool-use.js +109 -0
  26. package/templates/.agent/rules/SECURITY.md +7 -0
  27. package/templates/.agent/skills/openclaw-installation-debugger/SKILL.md +37 -0
  28. package/templates/.agent/skills/openclaw-installation-debugger/scripts/debug.js +165 -0
  29. package/templates/.agent/skills/openclaw-ops/01-openclaw-vpn-wireguard/SKILL.md +20 -0
  30. package/templates/.agent/skills/openclaw-ops/02-openclaw-enroll-host/SKILL.md +14 -0
  31. package/templates/.agent/skills/openclaw-ops/03-openclaw-policy-baseline/SKILL.md +17 -0
  32. package/templates/.agent/skills/openclaw-ops/04-openclaw-remote-exec-runbooks/SKILL.md +13 -0
  33. package/templates/.agent/skills/openclaw-ops/05-openclaw-file-transfer-safe/SKILL.md +10 -0
  34. package/templates/.agent/skills/openclaw-ops/06-openclaw-audit-logging/SKILL.md +13 -0
  35. package/templates/.agent/skills/openclaw-ops/07-openclaw-safe-update/SKILL.md +9 -0
  36. package/templates/.agent/skills/openclaw-ops/08-openclaw-healthchecks/SKILL.md +10 -0
  37. package/templates/.agent/skills/universal-setup/SKILL.md +26 -0
  38. package/templates/.agent/workflows/doctor.md +20 -0
  39. package/templates/.agent/workflows/healthcheck.md +22 -0
  40. package/templates/.agent/workflows/healthcheck.runbook.md +9 -0
  41. package/templates/.agent/workflows/restart.md +20 -0
  42. package/templates/.agent/workflows/restart.runbook.md +8 -0
  43. package/templates/.agent/workflows/setup.md +18 -0
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # OpenClaw OS
2
+
3
+ CLI e starter kit para configuração segura do OpenClaw em VPS, Mac, Windows e Docker.
4
+
5
+ ## Instalação
6
+
7
+ ```bash
8
+ # Via npx (recomendado)
9
+ npx @fabioforest/openclaw init
10
+
11
+ # Ou instale globalmente
12
+ npm install -g @fabioforest/openclaw
13
+ openclaw init
14
+ ```
15
+
16
+ ## Comandos
17
+
18
+ | Comando | Descrição |
19
+ |---------|-----------|
20
+ | `openclaw init` | Instala templates `.agent/` no projeto |
21
+ | `openclaw update` | Atualiza templates preservando customizações |
22
+ | `openclaw status` | Mostra status da instalação |
23
+ | `openclaw doctor` | Healthcheck automatizado do ambiente |
24
+ | `openclaw setup` | Roda wizard interativo de configuração |
25
+
26
+ ### Opções
27
+
28
+ ```bash
29
+ openclaw init --force # Sobrescreve .agent/ existente
30
+ openclaw init --path ./dir # Instala em diretório específico
31
+ openclaw doctor --quiet # Saída mínima
32
+ ```
33
+
34
+ ## O que é instalado
35
+
36
+ O comando `init` cria a seguinte estrutura no seu projeto:
37
+
38
+ ```
39
+ .agent/
40
+ ├── agents/ # Personas de agente (ex: sysadmin-proativo)
41
+ ├── hooks/ # Hooks de segurança (PreToolUse)
42
+ ├── rules/ # Guardrails de segurança
43
+ ├── skills/
44
+ │ ├── universal-setup/ # Wizard de configuração interativo
45
+ │ └── openclaw-ops/ # 8 skills operacionais
46
+ └── workflows/ # Runbooks e slash commands
47
+ ```
48
+
49
+ ### Skills Operacionais
50
+
51
+ | # | Skill | Descrição |
52
+ |---|-------|-----------|
53
+ | 01 | VPN WireGuard | Provisiona VPN entre VPS e hosts |
54
+ | 02 | Enroll Host | Onboarding com aprovação humana |
55
+ | 03 | Policy Baseline | RBAC + allowlists deny-by-default |
56
+ | 04 | Remote Exec | Runbooks com timeout e auditoria |
57
+ | 05 | File Transfer | Transferência com hash e allowlist |
58
+ | 06 | Audit Logging | Log JSON com redaction de segredos |
59
+ | 07 | Safe Update | Canary + rollback automático |
60
+ | 08 | Healthchecks | Circuit breaker + auto-restart |
61
+
62
+ ## Segurança
63
+
64
+ - **VPN-first** — sem VPN, sem acesso remoto
65
+ - **bind localhost** + **auth token** por padrão
66
+ - **Hook `pre-tool-use`** — bloqueia comandos destrutivos (`rm -rf`, `mkfs`, `dd`, `shutdown`)
67
+ - **Break-glass** — acesso emergencial com expiração automática
68
+ - **Auditoria** — todos os eventos logados com `request_id`
69
+
70
+ ## Desenvolvimento
71
+
72
+ ```bash
73
+ # Instalar dependências
74
+ npm install
75
+
76
+ # Rodar testes
77
+ npm test
78
+
79
+ # Testes com watch
80
+ npm run test:watch
81
+
82
+ # Coverage
83
+ npm run test:coverage
84
+ ```
85
+
86
+ ## Licença
87
+
88
+ MIT
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * OpenClaw CLI — Entry point principal.
6
+ *
7
+ * Parseia argumentos e despacha para o comando correto.
8
+ * Sem dependências externas — usa apenas process.argv.
9
+ *
10
+ * Comandos:
11
+ * init [--force] [--path <dir>] Instala templates .agent/ no projeto
12
+ * update [--path <dir>] Atualiza templates preservando customizações
13
+ * status [--path <dir>] Mostra status da instalação
14
+ * doctor [--path <dir>] Healthcheck automatizado
15
+ * setup Roda wizard interativo
16
+ * --help Mostra ajuda
17
+ * --version Mostra versão
18
+ */
19
+
20
+ const path = require("path");
21
+ const pkg = require("../package.json");
22
+
23
+ // Comandos disponíveis e seus módulos
24
+ const COMMANDS = {
25
+ init: "../lib/cli/init",
26
+ update: "../lib/cli/update",
27
+ status: "../lib/cli/status",
28
+ doctor: "../lib/cli/doctor",
29
+ debug: "../lib/cli/debug",
30
+ check: "../lib/cli/orchestrate",
31
+ };
32
+
33
+ /**
34
+ * Parseia os argumentos da linha de comando.
35
+ * @param {string[]} argv — process.argv.slice(2)
36
+ * @returns {{ command: string|null, flags: object }}
37
+ */
38
+ function parseArgs(argv) {
39
+ const flags = {};
40
+ let command = null;
41
+
42
+ for (let i = 0; i < argv.length; i++) {
43
+ const arg = argv[i];
44
+
45
+ if (arg === "--help" || arg === "-h") {
46
+ flags.help = true;
47
+ } else if (arg === "--version" || arg === "-v") {
48
+ flags.version = true;
49
+ } else if (arg === "--force" || arg === "-f") {
50
+ flags.force = true;
51
+ } else if (arg === "--path" || arg === "-p") {
52
+ // Próximo argumento é o caminho
53
+ flags.path = argv[++i] || ".";
54
+ } else if (arg === "--quiet" || arg === "-q") {
55
+ flags.quiet = true;
56
+ } else if (!arg.startsWith("-") && !command) {
57
+ command = arg;
58
+ }
59
+ }
60
+
61
+ return { command, flags };
62
+ }
63
+
64
+ /**
65
+ * Exibe a mensagem de ajuda.
66
+ */
67
+ function showHelp() {
68
+ console.log(`
69
+ 🦀 OpenClaw CLI v${pkg.version}
70
+
71
+ Uso: openclaw <comando> [opções]
72
+
73
+ Comandos:
74
+ init Instala templates .agent/ no projeto atual
75
+ update Atualiza templates preservando customizações
76
+ status Mostra status da instalação
77
+ doctor Healthcheck automatizado do ambiente
78
+ setup Roda wizard interativo de configuração
79
+ debug Diagnóstico avançado de instalação e rede
80
+ check Orquestrador inteligente (instala ou repara)
81
+
82
+ Opções:
83
+ --path, -p <dir> Diretório alvo (padrão: ./)
84
+ --force, -f Sobrescreve .agent/ existente (init)
85
+ --quiet, -q Saída mínima
86
+ --help, -h Mostra esta ajuda
87
+ --version, -v Mostra a versão
88
+
89
+ Exemplos:
90
+ npx openclaw init
91
+ npx openclaw init --force --path ./meu-projeto
92
+ npx openclaw doctor
93
+ npx openclaw status
94
+ `);
95
+ }
96
+
97
+ /**
98
+ * Ponto de entrada principal.
99
+ */
100
+ async function main() {
101
+ const { command, flags } = parseArgs(process.argv.slice(2));
102
+
103
+ // Flags globais
104
+ if (flags.version) {
105
+ console.log(`openclaw v${pkg.version}`);
106
+ return;
107
+ }
108
+
109
+ if (flags.help || !command) {
110
+ showHelp();
111
+ return;
112
+ }
113
+
114
+ // Resolver caminho de destino
115
+ const targetPath = path.resolve(flags.path || ".");
116
+
117
+ // Comando setup — roda wizard diretamente
118
+ if (command === "setup") {
119
+ const wizardPath = path.join(__dirname, "..", "lib", "setup", "config_wizard.js");
120
+ try {
121
+ require(wizardPath);
122
+ } catch (err) {
123
+ console.error(`❌ Erro ao rodar setup wizard: ${err.message}`);
124
+ process.exit(1);
125
+ }
126
+ return;
127
+ }
128
+
129
+ // Verificar se o comando existe
130
+ if (!COMMANDS[command]) {
131
+ console.error(`❌ Comando desconhecido: "${command}"`);
132
+ console.error(` Use "openclaw --help" para ver comandos disponíveis.`);
133
+ process.exit(1);
134
+ }
135
+
136
+ // Carregar e executar o comando
137
+ try {
138
+ const commandModule = require(COMMANDS[command]);
139
+ await commandModule.run({ targetPath, flags });
140
+ } catch (err) {
141
+ console.error(`❌ Erro ao executar "${command}": ${err.message}`);
142
+ if (!flags.quiet) {
143
+ console.error(err.stack);
144
+ }
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ main().catch((err) => {
150
+ console.error("❌ Erro inesperado:", err.message);
151
+ process.exit(1);
152
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Módulo de configuração de canais de comunicação para o OpenClaw Setup Wizard.
3
+ * Suporta Telegram, Discord e WhatsApp com validação de formato de token.
4
+ *
5
+ * @module lib/channels
6
+ */
7
+
8
+ /**
9
+ * Mapa de validadores de token por canal.
10
+ * Cada entrada contém:
11
+ * - fn: função de validação que retorna boolean
12
+ * - hint: dica de formato para exibição em caso de erro
13
+ * @type {Object.<string, {fn: Function, hint: string}>}
14
+ */
15
+ const CHANNEL_VALIDATORS = {
16
+ telegram: {
17
+ fn: (token) => /^\d+:[A-Za-z0-9_-]{20,}$/.test(token),
18
+ hint: "Formato esperado: 123456789:ABCDefghIJKLMnopqr...",
19
+ },
20
+ discord: {
21
+ fn: (token) => /^[A-Za-z0-9_.-]{50,}$/.test(token),
22
+ hint: "Token do bot (50+ caracteres alfanuméricos com pontos)",
23
+ },
24
+ whatsapp: {
25
+ fn: (token) => /^[A-Za-z0-9_.-]{20,}$/.test(token),
26
+ hint: "Token da API WhatsApp Business (20+ caracteres)",
27
+ },
28
+ };
29
+
30
+ /**
31
+ * Lista de canais suportados pelo wizard.
32
+ * @returns {string[]} Nomes dos canais suportados
33
+ */
34
+ function supportedChannels() {
35
+ return Object.keys(CHANNEL_VALIDATORS);
36
+ }
37
+
38
+ /**
39
+ * Valida o formato de um token para um canal específico.
40
+ * @param {string} channelName - Nome do canal ("telegram", "discord", "whatsapp")
41
+ * @param {string} token - Token a validar
42
+ * @returns {{ valid: boolean, hint: string }} Resultado da validação com hint
43
+ */
44
+ function validateToken(channelName, token) {
45
+ const validator = CHANNEL_VALIDATORS[channelName];
46
+ if (!validator) return { valid: false, hint: `Canal desconhecido: ${channelName}` };
47
+ return {
48
+ valid: validator.fn(token),
49
+ hint: validator.hint,
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Configura um canal no objeto de configuração.
55
+ * Solicita token via askFn, valida formato e salva na config.
56
+ * @param {object} config - Objeto de config do openclaw.json (referência, modificado in-place)
57
+ * @param {string} channelName - Nome do canal ("telegram", "discord", "whatsapp")
58
+ * @param {Function} askFn - Função async para perguntar ao usuário (recebe string, retorna string)
59
+ * @returns {Promise<boolean>} true se o canal foi configurado com sucesso
60
+ */
61
+ async function configureChannel(config, channelName, askFn) {
62
+ if (!CHANNEL_VALIDATORS[channelName]) {
63
+ console.log(`✖ Canal desconhecido: ${channelName}`);
64
+ return false;
65
+ }
66
+
67
+ const token = await askFn(`${channelName} token: `);
68
+ if (!token) return false;
69
+
70
+ const { valid, hint } = validateToken(channelName, token);
71
+ if (!valid) {
72
+ console.log(`⚠ Formato de token inválido. ${hint}`);
73
+ const force = await askFn("Salvar mesmo assim? (y/n): ");
74
+ if (force.toLowerCase() !== "y") return false;
75
+ }
76
+
77
+ config.channels = config.channels || {};
78
+ config.channels[channelName] = { token };
79
+ const displayName = channelName.charAt(0).toUpperCase() + channelName.slice(1);
80
+ console.log(`✔ ${displayName} configurado.`);
81
+ return true;
82
+ }
83
+
84
+ module.exports = { CHANNEL_VALIDATORS, supportedChannels, validateToken, configureChannel };
@@ -0,0 +1,12 @@
1
+ const { runDebug } = require("../../templates/.agent/skills/openclaw-installation-debugger/scripts/debug.js");
2
+
3
+ module.exports = {
4
+ run: async function ({ targetPath, flags }) {
5
+ try {
6
+ await runDebug();
7
+ } catch (err) {
8
+ console.error("Erro fatal ao executar debug:", err);
9
+ process.exit(1);
10
+ }
11
+ }
12
+ };
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Comando CLI: doctor
5
+ *
6
+ * Healthcheck automatizado que verifica:
7
+ * - openclaw.json (bind, token, canais)
8
+ * - Porta 18789 em localhost
9
+ * - WireGuard handshake (se aplicável)
10
+ * - Integridade dos arquivos .agent/
11
+ * - Hook pre-tool-use ativo
12
+ *
13
+ * Gera relatório ✅/❌ com sugestões de correção.
14
+ */
15
+
16
+ const fs = require("fs");
17
+ const path = require("path");
18
+ const { readJsonSafe } = require("../config");
19
+ const { portInUse } = require("../security");
20
+ const { detectEnvironment } = require("../detect");
21
+
22
+ /**
23
+ * Resultado individual de um check.
24
+ * @typedef {{ name: string, status: 'ok'|'warn'|'fail', message: string }} CheckResult
25
+ */
26
+
27
+ /**
28
+ * Verifica a configuração do openclaw.json.
29
+ * @param {string} configPath — caminho do arquivo
30
+ * @returns {CheckResult[]}
31
+ */
32
+ function checkConfig(configPath) {
33
+ const results = [];
34
+
35
+ if (!fs.existsSync(configPath)) {
36
+ results.push({
37
+ name: "openclaw.json",
38
+ status: "fail",
39
+ message: "Arquivo não encontrado — rode 'openclaw init' primeiro",
40
+ });
41
+ return results;
42
+ }
43
+
44
+ const config = readJsonSafe(configPath);
45
+ if (!config) {
46
+ results.push({
47
+ name: "openclaw.json",
48
+ status: "fail",
49
+ message: "Arquivo corrompido — não é JSON válido",
50
+ });
51
+ return results;
52
+ }
53
+
54
+ // Verificar bind
55
+ const bind = config.gateway?.bind;
56
+ if (bind === "127.0.0.1") {
57
+ results.push({ name: "gateway.bind", status: "ok", message: "127.0.0.1 (localhost)" });
58
+ } else if (bind === "0.0.0.0") {
59
+ results.push({
60
+ name: "gateway.bind",
61
+ status: "fail",
62
+ message: "0.0.0.0 — exposto publicamente! Mude para 127.0.0.1",
63
+ });
64
+ } else {
65
+ results.push({
66
+ name: "gateway.bind",
67
+ status: "warn",
68
+ message: bind ? `${bind} — verifique se é intencional` : "não definido",
69
+ });
70
+ }
71
+
72
+ // Verificar auth token
73
+ const token = config.auth?.token;
74
+ if (token && token.length >= 24) {
75
+ results.push({ name: "auth.token", status: "ok", message: `Token configurado (${token.length} chars)` });
76
+ } else if (token) {
77
+ results.push({
78
+ name: "auth.token",
79
+ status: "warn",
80
+ message: `Token curto (${token.length} chars) — mínimo recomendado: 24`,
81
+ });
82
+ } else {
83
+ results.push({
84
+ name: "auth.token",
85
+ status: "fail",
86
+ message: "Token não configurado — rode 'openclaw setup'",
87
+ });
88
+ }
89
+
90
+ // Verificar auth mode
91
+ const authMode = config.auth?.mode;
92
+ if (authMode === "token") {
93
+ results.push({ name: "auth.mode", status: "ok", message: "token" });
94
+ } else {
95
+ results.push({
96
+ name: "auth.mode",
97
+ status: "warn",
98
+ message: authMode ? `${authMode} — recomendado: 'token'` : "não definido",
99
+ });
100
+ }
101
+
102
+ return results;
103
+ }
104
+
105
+ /**
106
+ * Verifica a integridade do diretório .agent/.
107
+ * @param {string} agentDir — caminho do .agent/
108
+ * @returns {CheckResult[]}
109
+ */
110
+ function checkAgentDir(agentDir) {
111
+ const results = [];
112
+
113
+ if (!fs.existsSync(agentDir)) {
114
+ results.push({
115
+ name: ".agent/",
116
+ status: "fail",
117
+ message: "Diretório não encontrado — rode 'openclaw init'",
118
+ });
119
+ return results;
120
+ }
121
+
122
+ // Verificar subdiretórios esperados
123
+ const expectedDirs = ["agents", "rules", "skills", "workflows"];
124
+ for (const dir of expectedDirs) {
125
+ const dirPath = path.join(agentDir, dir);
126
+ if (fs.existsSync(dirPath)) {
127
+ const count = fs.readdirSync(dirPath).length;
128
+ results.push({
129
+ name: `.agent/${dir}/`,
130
+ status: "ok",
131
+ message: `${count} item(ns)`,
132
+ });
133
+ } else {
134
+ results.push({
135
+ name: `.agent/${dir}/`,
136
+ status: "warn",
137
+ message: "Diretório ausente",
138
+ });
139
+ }
140
+ }
141
+
142
+ // Verificar hook pre-tool-use
143
+ const hookPath = path.join(agentDir, "hooks", "pre-tool-use.js");
144
+ if (fs.existsSync(hookPath)) {
145
+ results.push({
146
+ name: "hooks/pre-tool-use",
147
+ status: "ok",
148
+ message: "Hook de segurança ativo",
149
+ });
150
+ } else {
151
+ results.push({
152
+ name: "hooks/pre-tool-use",
153
+ status: "warn",
154
+ message: "Hook de segurança não encontrado — comandos destrutivos não serão bloqueados",
155
+ });
156
+ }
157
+
158
+ return results;
159
+ }
160
+
161
+ /**
162
+ * Verifica se a porta 18789 está em uso.
163
+ * @returns {Promise<CheckResult>}
164
+ */
165
+ async function checkPort() {
166
+ try {
167
+ const inUse = await portInUse(18789, "127.0.0.1");
168
+ if (inUse) {
169
+ return { name: "porta 18789", status: "ok", message: "Serviço respondendo em localhost:18789" };
170
+ }
171
+ return {
172
+ name: "porta 18789",
173
+ status: "warn",
174
+ message: "Porta livre — serviço OpenClaw pode estar parado",
175
+ };
176
+ } catch {
177
+ return {
178
+ name: "porta 18789",
179
+ status: "warn",
180
+ message: "Não foi possível verificar a porta",
181
+ };
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Verifica ambiente detectado.
187
+ * @returns {CheckResult}
188
+ */
189
+ function checkEnvironment() {
190
+ const env = detectEnvironment();
191
+ return {
192
+ name: "ambiente",
193
+ status: "ok",
194
+ message: env || "desconhecido",
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Exibe o relatório de resultados.
200
+ * @param {CheckResult[]} results — resultados dos checks
201
+ */
202
+ function printReport(results) {
203
+ const icons = { ok: "✅", warn: "⚠️", fail: "❌" };
204
+
205
+ for (const r of results) {
206
+ const icon = icons[r.status] || "❓";
207
+ const padding = " ".repeat(Math.max(0, 22 - r.name.length));
208
+ console.log(` ${icon} ${r.name}${padding} ${r.message}`);
209
+ }
210
+
211
+ // Resumo
212
+ const okCount = results.filter((r) => r.status === "ok").length;
213
+ const warnCount = results.filter((r) => r.status === "warn").length;
214
+ const failCount = results.filter((r) => r.status === "fail").length;
215
+
216
+ console.log(`\n📊 Resumo: ${okCount} ok, ${warnCount} avisos, ${failCount} erros`);
217
+
218
+ if (failCount > 0) {
219
+ console.log("\n💡 Sugestões:");
220
+ for (const r of results.filter((r) => r.status === "fail")) {
221
+ console.log(` • ${r.name}: ${r.message}`);
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Executa o comando doctor.
228
+ * @param {object} options
229
+ * @param {string} options.targetPath — diretório alvo
230
+ * @param {object} options.flags — flags do CLI
231
+ */
232
+ async function run({ targetPath, flags }) {
233
+ const agentDir = path.join(targetPath, ".agent");
234
+ const configPath = path.join(targetPath, "openclaw.json");
235
+
236
+ if (!flags.quiet) {
237
+ console.log("\n🩺 OpenClaw Doctor — Diagnóstico do ambiente\n");
238
+ }
239
+
240
+ const results = [];
241
+
242
+ // 1. Ambiente
243
+ results.push(checkEnvironment());
244
+
245
+ // 2. Configuração
246
+ results.push(...checkConfig(configPath));
247
+
248
+ // 3. Porta
249
+ const portResult = await checkPort();
250
+ results.push(portResult);
251
+
252
+ // 4. .agent/
253
+ results.push(...checkAgentDir(agentDir));
254
+
255
+ // Exibir relatório
256
+ printReport(results);
257
+ console.log("");
258
+
259
+ // Exit code baseado nos resultados
260
+ const hasFailures = results.some((r) => r.status === "fail");
261
+ if (hasFailures) {
262
+ process.exitCode = 1;
263
+ }
264
+ }
265
+
266
+ module.exports = { run, checkConfig, checkAgentDir, checkPort, checkEnvironment };