@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.
- package/README.md +88 -0
- package/bin/openclaw.js +152 -0
- package/lib/channels.js +84 -0
- package/lib/cli/debug.js +12 -0
- package/lib/cli/doctor.js +266 -0
- package/lib/cli/init.js +145 -0
- package/lib/cli/orchestrate.js +43 -0
- package/lib/cli/status.js +128 -0
- package/lib/cli/update.js +122 -0
- package/lib/config.js +68 -0
- package/lib/detect.js +49 -0
- package/lib/ops/audit.js +156 -0
- package/lib/ops/enroll.js +133 -0
- package/lib/ops/exec.js +140 -0
- package/lib/ops/healthcheck.js +167 -0
- package/lib/ops/policy.js +153 -0
- package/lib/ops/transfer.js +152 -0
- package/lib/ops/update-safe.js +173 -0
- package/lib/ops/vpn.js +131 -0
- package/lib/security.js +48 -0
- package/lib/setup/config_wizard.js +186 -0
- package/package.json +47 -0
- package/templates/.agent/agents/setup-specialist.md +24 -0
- package/templates/.agent/agents/sysadmin-proativo.md +31 -0
- package/templates/.agent/hooks/pre-tool-use.js +109 -0
- package/templates/.agent/rules/SECURITY.md +7 -0
- package/templates/.agent/skills/openclaw-installation-debugger/SKILL.md +37 -0
- package/templates/.agent/skills/openclaw-installation-debugger/scripts/debug.js +165 -0
- package/templates/.agent/skills/openclaw-ops/01-openclaw-vpn-wireguard/SKILL.md +20 -0
- package/templates/.agent/skills/openclaw-ops/02-openclaw-enroll-host/SKILL.md +14 -0
- package/templates/.agent/skills/openclaw-ops/03-openclaw-policy-baseline/SKILL.md +17 -0
- package/templates/.agent/skills/openclaw-ops/04-openclaw-remote-exec-runbooks/SKILL.md +13 -0
- package/templates/.agent/skills/openclaw-ops/05-openclaw-file-transfer-safe/SKILL.md +10 -0
- package/templates/.agent/skills/openclaw-ops/06-openclaw-audit-logging/SKILL.md +13 -0
- package/templates/.agent/skills/openclaw-ops/07-openclaw-safe-update/SKILL.md +9 -0
- package/templates/.agent/skills/openclaw-ops/08-openclaw-healthchecks/SKILL.md +10 -0
- package/templates/.agent/skills/universal-setup/SKILL.md +26 -0
- package/templates/.agent/workflows/doctor.md +20 -0
- package/templates/.agent/workflows/healthcheck.md +22 -0
- package/templates/.agent/workflows/healthcheck.runbook.md +9 -0
- package/templates/.agent/workflows/restart.md +20 -0
- package/templates/.agent/workflows/restart.runbook.md +8 -0
- 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
|
package/bin/openclaw.js
ADDED
|
@@ -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
|
+
});
|
package/lib/channels.js
ADDED
|
@@ -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 };
|
package/lib/cli/debug.js
ADDED
|
@@ -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 };
|