ps-claw 1.0.7 → 1.0.9
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 +225 -94
- package/cli.mjs +37 -58
- package/package.json +1 -2
- package/ps-claw.mjs +0 -661
package/README.md
CHANGED
|
@@ -1,169 +1,300 @@
|
|
|
1
1
|
# PS Claw 🦞
|
|
2
2
|
|
|
3
|
-
**PS Claw**
|
|
3
|
+
**PS Claw** — Agente de IA autônomo leve com interface web estilo ChatGPT. Multi-canal (Telegram, Discord, WhatsApp) com suporte a modelos de múltiplos provedores (Claude, GPT-4, Gemini).
|
|
4
4
|
|
|
5
|
-
Fork enxuto com
|
|
5
|
+
Fork enxuto do [OpenClaw](https://github.com/openclaw/openclaw) focado em leveza, facilidade de uso e sem dependências pesadas.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## ⚡
|
|
9
|
+
## ⚡ Início Rápido (5 minutos)
|
|
10
10
|
|
|
11
11
|
### Requisitos
|
|
12
|
-
- [Node.js](https://nodejs.org/) v22.19
|
|
12
|
+
- [Node.js](https://nodejs.org/) **v22.19+** (baixe a versão LTS)
|
|
13
|
+
- npm (incluído com Node.js)
|
|
13
14
|
|
|
14
|
-
### Instalar
|
|
15
|
+
### 1️⃣ Instalar e executar
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
|
-
|
|
18
|
+
npx ps-claw@latest web
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
Pronto!
|
|
21
|
+
Pronto! A interface web abre em **http://localhost:3000** 🎉
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
npm i -g ps-claw
|
|
24
|
-
```
|
|
23
|
+
---
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
## 🌐 Interface Web
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```
|
|
27
|
+
### O que é?
|
|
28
|
+
Interface visual estilo ChatGPT para conversar com o agente de IA. Salva histórico no navegador, permite trocar modelos, configurar gateways.
|
|
31
29
|
|
|
32
|
-
###
|
|
30
|
+
### Como usar?
|
|
33
31
|
|
|
34
32
|
```bash
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
# Abrir a interface web
|
|
34
|
+
npx ps-claw web
|
|
35
|
+
|
|
36
|
+
# Ou executar tudo junto (agente + web)
|
|
37
|
+
npx ps-claw all
|
|
39
38
|
```
|
|
40
39
|
|
|
40
|
+
Acesse: **http://localhost:3000**
|
|
41
|
+
|
|
42
|
+
### Configuração na interface
|
|
43
|
+
|
|
44
|
+
1. Vá para a aba **🔌 Gateways**
|
|
45
|
+
- Clique **+ Adicionar**
|
|
46
|
+
- URL: `http://localhost:18789` (PS Claw local) ou qualquer API OpenAI-compatível
|
|
47
|
+
- Nome: "Meu Gateway"
|
|
48
|
+
- Clique **Adicionar**
|
|
49
|
+
|
|
50
|
+
2. Vá para a aba **🤖 Modelos & Provedores**
|
|
51
|
+
- Adicione suas **chaves de API**:
|
|
52
|
+
- **Claude** (Anthropic): `sk-ant-...`
|
|
53
|
+
- **GPT-4** (OpenAI): `sk-...`
|
|
54
|
+
- **Gemini** (Google): `AIza...`
|
|
55
|
+
- **Mistral**: sua chave
|
|
56
|
+
- Selecione um modelo
|
|
57
|
+
- Clique no modelo para usá-lo
|
|
58
|
+
|
|
59
|
+
3. Volte para **💬 Chat** e comece a conversar! 💬
|
|
60
|
+
|
|
41
61
|
---
|
|
42
62
|
|
|
43
|
-
##
|
|
63
|
+
## 🔑 Chaves de API — Como obter
|
|
64
|
+
|
|
65
|
+
### 🟠 Anthropic (Claude)
|
|
66
|
+
|
|
67
|
+
1. Acesse https://console.anthropic.com
|
|
68
|
+
2. Faça login ou crie conta
|
|
69
|
+
3. Vá para **API Keys**
|
|
70
|
+
4. Clique **Create Key**
|
|
71
|
+
5. Copie a chave `sk-ant-...`
|
|
72
|
+
6. Cole na aba **Modelos** do PS Claw
|
|
73
|
+
|
|
74
|
+
**Grátis?** Sim, Claude oferece créditos iniciais ($5-$20). Depois é por uso.
|
|
75
|
+
|
|
76
|
+
### 🟢 OpenAI (GPT-4, GPT-4o)
|
|
77
|
+
|
|
78
|
+
1. Acesse https://platform.openai.com
|
|
79
|
+
2. Faça login ou crie conta
|
|
80
|
+
3. Vá para **API Keys**
|
|
81
|
+
4. Clique **Create new secret key**
|
|
82
|
+
5. Copie a chave `sk-...`
|
|
83
|
+
6. Cole na aba **Modelos** do PS Claw
|
|
84
|
+
|
|
85
|
+
**Grátis?** Sim, trial de $5-$18. Depois é por uso (mais barato que Claude).
|
|
86
|
+
|
|
87
|
+
### 🔵 Google (Gemini)
|
|
88
|
+
|
|
89
|
+
1. Acesse https://aistudio.google.com/apikey
|
|
90
|
+
2. Clique **Create API Key**
|
|
91
|
+
3. Selecione um projeto ou crie novo
|
|
92
|
+
4. Copie a chave `AIza...`
|
|
93
|
+
5. Cole na aba **Modelos** do PS Claw
|
|
44
94
|
|
|
45
|
-
|
|
95
|
+
**Grátis?** Sim, 60 chamadas por minuto para sempre.
|
|
46
96
|
|
|
47
|
-
|
|
97
|
+
### 🟣 Mistral
|
|
98
|
+
|
|
99
|
+
1. Acesse https://console.mistral.ai
|
|
100
|
+
2. Faça login ou crie conta
|
|
101
|
+
3. Vá para **API Keys**
|
|
102
|
+
4. Clique **Generate a new key**
|
|
103
|
+
5. Copie a chave
|
|
104
|
+
6. Cole na aba **Modelos** do PS Claw
|
|
105
|
+
|
|
106
|
+
**Grátis?** Sim, trial de crédito. Depois por uso.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 🚀 Alternativas de Uso
|
|
111
|
+
|
|
112
|
+
### Via Git Clone (desenvolvedores)
|
|
48
113
|
|
|
49
114
|
```bash
|
|
50
|
-
ps-claw
|
|
51
|
-
ps-claw
|
|
52
|
-
|
|
53
|
-
ps-claw
|
|
54
|
-
ps-claw gateway status # Verifica status do gateway
|
|
55
|
-
ps-claw doctor # Diagnóstico do sistema
|
|
56
|
-
ps-claw models list # Lista modelos disponíveis
|
|
57
|
-
ps-claw secrets set openai # Configura chave OpenAI
|
|
58
|
-
ps-claw secrets set anthropic # Configura chave Anthropic
|
|
59
|
-
ps-claw configure # Configuração interativa
|
|
115
|
+
git clone https://github.com/Pedro21062014/ps-claw-v2.git
|
|
116
|
+
cd ps-claw-v2
|
|
117
|
+
npm install
|
|
118
|
+
npx ps-claw web
|
|
60
119
|
```
|
|
61
120
|
|
|
62
|
-
###
|
|
121
|
+
### Instalar Globalmente
|
|
63
122
|
|
|
64
123
|
```bash
|
|
124
|
+
npm install -g ps-claw@latest
|
|
65
125
|
ps-claw web
|
|
66
126
|
```
|
|
67
127
|
|
|
68
|
-
|
|
128
|
+
Se não funcionar em Windows, use `npx ps-claw web` em vez disso.
|
|
69
129
|
|
|
70
|
-
|
|
71
|
-
- 💬 **Chat** — conversas com histórico salvo localmente
|
|
72
|
-
- 🔗 **Gateways** — conectar e gerenciar APIs (OpenAI, Anthropic, Ollama, LM Studio, etc.)
|
|
73
|
-
- 🧠 **Modelos** — selecionar modelos por provedor (GPT-4o, Claude Opus 4.5, Gemini 2.5 Pro, DeepSeek...)
|
|
74
|
-
- ⚙️ **Config** — temperatura, max tokens, system prompt, chaves de API, export/import de configurações
|
|
130
|
+
### Atualizar
|
|
75
131
|
|
|
76
|
-
|
|
132
|
+
```bash
|
|
133
|
+
npx ps-claw update
|
|
134
|
+
# ou
|
|
135
|
+
npm install -g ps-claw@latest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## ⚙️ Configurações Avançadas
|
|
141
|
+
|
|
142
|
+
### Variáveis de Ambiente
|
|
77
143
|
|
|
78
144
|
```bash
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
145
|
+
# Porta da interface web (padrão: 3000)
|
|
146
|
+
set PS_CLAW_WEB_PORT=3000
|
|
147
|
+
|
|
148
|
+
# Porta do gateway PS Claw (padrão: 18789)
|
|
149
|
+
set PS_CLAW_GATEWAY_PORT=18789
|
|
150
|
+
|
|
151
|
+
# Token do gateway (se tiver autenticação)
|
|
152
|
+
set OPENCLAW_GATEWAY_TOKEN=seu_token_aqui
|
|
87
153
|
```
|
|
88
154
|
|
|
89
|
-
|
|
155
|
+
### Usar outro Gateway
|
|
156
|
+
|
|
157
|
+
Se você tem um PS Claw rodando em outro servidor:
|
|
158
|
+
|
|
159
|
+
1. Na interface, aba **🔌 Gateways**
|
|
160
|
+
2. Adicione a URL: `http://seu-servidor:18789`
|
|
161
|
+
3. Pronto! Usa esse gateway
|
|
162
|
+
|
|
163
|
+
### Usar API OpenAI-compatível
|
|
164
|
+
|
|
165
|
+
Muitos serviços são compatíveis com OpenAI API:
|
|
166
|
+
|
|
167
|
+
- **Ollama** (modelos locais): `http://localhost:11434`
|
|
168
|
+
- **Vllm** (inference server): `http://localhost:8000`
|
|
169
|
+
- **LiteLLM**: qualquer URL proxy
|
|
170
|
+
|
|
171
|
+
Configure na aba **Gateways** com a URL do seu servidor.
|
|
90
172
|
|
|
91
173
|
---
|
|
92
174
|
|
|
93
|
-
##
|
|
175
|
+
## 📱 Canais (Telegram, Discord, WhatsApp)
|
|
176
|
+
|
|
177
|
+
Você pode conectar o PS Claw a:
|
|
178
|
+
|
|
179
|
+
- **Telegram** — Adicionar bot ao chat
|
|
180
|
+
- **Discord** — Adicionar bot ao servidor
|
|
181
|
+
- **WhatsApp** — Integração via Twilio ou Baileys
|
|
182
|
+
- **Slack** — Bot em workspace
|
|
183
|
+
|
|
184
|
+
Configure na interface ou edite `.env`:
|
|
94
185
|
|
|
95
186
|
```bash
|
|
96
|
-
|
|
187
|
+
# .env.example
|
|
188
|
+
TELEGRAM_BOT_TOKEN=seu_token
|
|
189
|
+
DISCORD_BOT_TOKEN=seu_token
|
|
190
|
+
WHATSAPP_PHONE=seu_numero
|
|
97
191
|
```
|
|
98
192
|
|
|
99
|
-
|
|
193
|
+
Copie `.env.example` para `.env` e preencha.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🐳 Docker
|
|
100
198
|
|
|
101
199
|
```bash
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
200
|
+
# Build
|
|
201
|
+
docker build -t ps-claw .
|
|
202
|
+
|
|
203
|
+
# Run
|
|
204
|
+
docker run -p 3000:3000 ps-claw web
|
|
106
205
|
```
|
|
107
206
|
|
|
108
207
|
---
|
|
109
208
|
|
|
110
|
-
## ✅
|
|
209
|
+
## ✅ Recursos Incluídos
|
|
111
210
|
|
|
112
211
|
| Recurso | Status |
|
|
113
|
-
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
| Busca na web (DuckDuckGo, Brave) | ✅ |
|
|
120
|
-
| Suporte a MCP / Skills | ✅ |
|
|
212
|
+
|---------|--------|
|
|
213
|
+
| Interface web estilo ChatGPT | ✅ |
|
|
214
|
+
| Chat com histórico | ✅ |
|
|
215
|
+
| Múltiplos modelos | ✅ |
|
|
216
|
+
| Telegram, Discord, WhatsApp | ✅ |
|
|
217
|
+
| Busca na web | ✅ |
|
|
121
218
|
| Memória persistente | ✅ |
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
219
|
+
| CLI + API | ✅ |
|
|
220
|
+
| Suporte MCP/Skills | ✅ |
|
|
221
|
+
| Apps iOS/Android | ❌ removido |
|
|
222
|
+
| Geração de vídeo/música | ❌ removido |
|
|
223
|
+
| Transcrição em tempo real | ❌ removido |
|
|
126
224
|
|
|
127
225
|
---
|
|
128
226
|
|
|
129
|
-
##
|
|
227
|
+
## 🆘 Resolução de Problemas
|
|
228
|
+
|
|
229
|
+
### "ps-claw: comando não encontrado"
|
|
230
|
+
|
|
231
|
+
**Solução:** Use `npx ps-claw web` em vez de `ps-claw web`
|
|
232
|
+
|
|
233
|
+
### "localhost:3000 recusou conexão"
|
|
234
|
+
|
|
235
|
+
**Solução:** Verifique se algum programa já usa a porta 3000:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Windows
|
|
239
|
+
netstat -ano | findstr :3000
|
|
240
|
+
|
|
241
|
+
# Mac/Linux
|
|
242
|
+
lsof -i :3000
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Se estiver em uso, mude a porta:
|
|
130
246
|
|
|
131
247
|
```bash
|
|
132
|
-
|
|
248
|
+
set PS_CLAW_WEB_PORT=3001
|
|
249
|
+
npx ps-claw web
|
|
133
250
|
```
|
|
134
251
|
|
|
252
|
+
### "Gateway offline"
|
|
253
|
+
|
|
254
|
+
**Solução:** Verifique se:
|
|
255
|
+
|
|
256
|
+
1. A URL está correta (ex: `http://localhost:18789`)
|
|
257
|
+
2. O gateway está rodando (se for local, execute `npx ps-claw start`)
|
|
258
|
+
3. Firewall não está bloqueando
|
|
259
|
+
|
|
260
|
+
### "Chave de API inválida"
|
|
261
|
+
|
|
262
|
+
**Solução:**
|
|
263
|
+
|
|
264
|
+
1. Copie a chave completa (sem espaços)
|
|
265
|
+
2. Verifique se é uma chave válida (não expirou)
|
|
266
|
+
3. Cole de novo na aba **Modelos**
|
|
267
|
+
|
|
135
268
|
---
|
|
136
269
|
|
|
137
|
-
##
|
|
270
|
+
## 📚 Documentação Completa
|
|
138
271
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
│ └── build-all.mjs ← Script de build
|
|
144
|
-
├── web-ui/
|
|
145
|
-
│ ├── server.mjs ← Servidor da interface web
|
|
146
|
-
│ └── public/
|
|
147
|
-
│ └── index.html ← Interface web (Chat, Gateways, Modelos, Config)
|
|
148
|
-
├── src/ ← Código-fonte TypeScript
|
|
149
|
-
├── extensions/ ← Provedores (OpenAI, Anthropic, DeepSeek...)
|
|
150
|
-
├── packages/ ← Bibliotecas internas
|
|
151
|
-
├── .env.example ← Exemplo de configuração
|
|
152
|
-
└── package.json
|
|
153
|
-
```
|
|
272
|
+
- [OpenClaw (original)](https://github.com/openclaw/openclaw)
|
|
273
|
+
- [Anthropic Claude](https://console.anthropic.com/docs)
|
|
274
|
+
- [OpenAI API](https://platform.openai.com/docs)
|
|
275
|
+
- [Google Gemini](https://aistudio.google.com/app/apikey)
|
|
154
276
|
|
|
155
277
|
---
|
|
156
278
|
|
|
157
|
-
##
|
|
279
|
+
## 📄 Licença
|
|
158
280
|
|
|
159
|
-
|
|
281
|
+
MIT — Baseado no [OpenClaw](https://github.com/openclaw/openclaw) por Peter Steinberger
|
|
160
282
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 🤝 Contribuições
|
|
286
|
+
|
|
287
|
+
Pull requests bem-vindo! Para mudanças grandes, abra uma issue primeiro.
|
|
164
288
|
|
|
165
289
|
---
|
|
166
290
|
|
|
167
|
-
##
|
|
291
|
+
## 💬 Suporte
|
|
292
|
+
|
|
293
|
+
- GitHub Issues: https://github.com/Pedro21062014/ps-claw-v2/issues
|
|
294
|
+
- Discussões: https://github.com/Pedro21062014/ps-claw-v2/discussions
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
**Aproveite o PS Claw! 🦞**
|
|
168
299
|
|
|
169
|
-
|
|
300
|
+
Dúvidas? Abra uma issue no GitHub ou entre em contato! ✨
|
package/cli.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* PS Claw CLI —
|
|
5
|
-
* Uso: ps-claw [comando]
|
|
6
|
-
* Comandos: start | web | all | update | help
|
|
4
|
+
* PS Claw CLI — standalone, sem dependência do dist/
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
import { spawn } from "node:child_process";
|
|
@@ -33,7 +31,7 @@ ${C.cyan}${C.bold} ██████╔╝███████╗ ██
|
|
|
33
31
|
${C.cyan}${C.bold} ██╔═══╝ ╚════██║ ██║ ██║ ██╔══██║██║███╗██║${C.reset}
|
|
34
32
|
${C.cyan}${C.bold} ██║ ███████║ ╚██████╗███████╗██║ ██║╚███╔███╔╝${C.reset}
|
|
35
33
|
${C.cyan}${C.bold} ╚═╝ ╚══════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝${C.reset}
|
|
36
|
-
${C.dim}v1.0.
|
|
34
|
+
${C.dim}v1.0.8 — Lightweight AI Agent Gateway${C.reset}
|
|
37
35
|
`);
|
|
38
36
|
}
|
|
39
37
|
|
|
@@ -41,79 +39,60 @@ function help() {
|
|
|
41
39
|
banner();
|
|
42
40
|
console.log(` ${C.bold}Comandos:${C.reset}
|
|
43
41
|
|
|
44
|
-
${C.green}ps-claw
|
|
45
|
-
${C.green}ps-claw
|
|
46
|
-
${C.green}ps-claw all${C.reset}
|
|
47
|
-
${C.green}ps-claw update${C.reset}
|
|
48
|
-
${C.green}ps-claw help${C.reset}
|
|
49
|
-
|
|
50
|
-
${C.bold}Início rápido:${C.reset}
|
|
51
|
-
|
|
52
|
-
${C.dim}# Via npm (global)${C.reset}
|
|
53
|
-
npm install -g ps-claw
|
|
54
|
-
ps-claw all
|
|
55
|
-
|
|
56
|
-
${C.dim}# Via git clone${C.reset}
|
|
57
|
-
git clone https://github.com/Pedro21062014/ps-claw-v2.git
|
|
58
|
-
cd ps-claw-v2 && npm install
|
|
59
|
-
ps-claw all
|
|
42
|
+
${C.green}npx ps-claw web${C.reset} Abre a interface web em http://localhost:3000
|
|
43
|
+
${C.green}npx ps-claw start${C.reset} Inicia o agente PS Claw
|
|
44
|
+
${C.green}npx ps-claw all${C.reset} Inicia tudo junto
|
|
45
|
+
${C.green}npx ps-claw update${C.reset} Atualiza o PS Claw
|
|
46
|
+
${C.green}npx ps-claw help${C.reset} Esta mensagem
|
|
60
47
|
|
|
61
48
|
${C.bold}Interface web:${C.reset} http://localhost:3000
|
|
62
49
|
`);
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
function startWeb() {
|
|
53
|
+
const srv = path.join(__dirname, "web-ui", "server.mjs");
|
|
54
|
+
if (!existsSync(srv)) {
|
|
55
|
+
console.error(`${C.red}❌ web-ui/server.mjs não encontrado!${C.reset}`);
|
|
68
56
|
process.exit(1);
|
|
69
57
|
}
|
|
70
|
-
|
|
58
|
+
console.log(`${C.green}🌐 Iniciando Interface Web...${C.reset}`);
|
|
59
|
+
console.log(`${C.cyan} Acesse: http://localhost:3000${C.reset}\n`);
|
|
60
|
+
const proc = spawn(process.execPath, [srv], { stdio: "inherit" });
|
|
71
61
|
proc.on("exit", code => process.exit(code ?? 0));
|
|
72
|
-
return proc;
|
|
73
62
|
}
|
|
74
63
|
|
|
75
64
|
function startAgent() {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
// Usa o ps-claw.mjs original do OpenClaw se existir e tiver dist/
|
|
66
|
+
// Caso contrário, avisa o usuário e abre a web
|
|
67
|
+
const distEntry = path.join(__dirname, "dist", "entry.mjs");
|
|
68
|
+
const distEntryJs = path.join(__dirname, "dist", "entry.js");
|
|
69
|
+
|
|
70
|
+
if (!existsSync(distEntry) && !existsSync(distEntryJs)) {
|
|
71
|
+
console.log(`${C.yellow}⚠️ O agente requer configuração adicional (dist/).${C.reset}`);
|
|
72
|
+
console.log(`${C.dim} Para usar a interface web, execute: npx ps-claw web${C.reset}\n`);
|
|
73
|
+
console.log(`${C.green}🌐 Iniciando Interface Web automaticamente...${C.reset}`);
|
|
74
|
+
console.log(`${C.cyan} Acesse: http://localhost:3000${C.reset}\n`);
|
|
75
|
+
startWeb();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
run(srv);
|
|
79
|
+
console.log(`${C.green}🦞 Iniciando PS Claw Agent...${C.reset}`);
|
|
80
|
+
const proc = spawn(process.execPath, [path.join(__dirname, "ps-claw.mjs"), ...args.slice(1)], { stdio: "inherit" });
|
|
81
|
+
proc.on("exit", code => process.exit(code ?? 0));
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
function startAll() {
|
|
87
85
|
banner();
|
|
88
|
-
|
|
89
|
-
const webFile = path.join(__dirname, "web-ui", "server.mjs");
|
|
90
|
-
|
|
91
|
-
console.log(`${C.green}🦞 Iniciando PS Claw Agent...${C.reset}`);
|
|
92
|
-
const agent = spawn(process.execPath, [agentFile], { stdio: "inherit" });
|
|
93
|
-
|
|
94
|
-
setTimeout(() => {
|
|
95
|
-
if (existsSync(webFile)) {
|
|
96
|
-
console.log(`\n${C.cyan}🌐 Iniciando Interface Web → http://localhost:3000${C.reset}\n`);
|
|
97
|
-
const web = spawn(process.execPath, [webFile], { stdio: "inherit" });
|
|
98
|
-
web.on("exit", code => process.exit(code ?? 0));
|
|
99
|
-
}
|
|
100
|
-
}, 1500);
|
|
101
|
-
|
|
102
|
-
agent.on("exit", code => process.exit(code ?? 0));
|
|
103
|
-
process.on("SIGINT", () => { agent.kill(); process.exit(0); });
|
|
86
|
+
startWeb();
|
|
104
87
|
}
|
|
105
88
|
|
|
106
89
|
function update() {
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const proc = spawn("bash", [script], { stdio: "inherit" });
|
|
116
|
-
proc.on("exit", code => process.exit(code ?? 0));
|
|
90
|
+
console.log(`${C.yellow}🔄 Atualizando PS Claw...${C.reset}\n`);
|
|
91
|
+
const proc = spawn("npm", ["install", "-g", "ps-claw@latest"], { stdio: "inherit", shell: true });
|
|
92
|
+
proc.on("exit", code => {
|
|
93
|
+
if (code === 0) console.log(`\n${C.green}✅ PS Claw atualizado!${C.reset}`);
|
|
94
|
+
process.exit(code ?? 0);
|
|
95
|
+
});
|
|
117
96
|
}
|
|
118
97
|
|
|
119
98
|
switch (cmd) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ps-claw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "PS Claw - AI Agent Gateway with multi-provider support, web UI, and CLI. Lightweight fork of OpenClaw.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"cli.mjs",
|
|
44
|
-
"ps-claw.mjs",
|
|
45
44
|
"web-ui/",
|
|
46
45
|
"update.sh",
|
|
47
46
|
"README.md",
|
package/ps-claw.mjs
DELETED
|
@@ -1,661 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
|
-
import { access } from "node:fs/promises";
|
|
6
|
-
import module from "node:module";
|
|
7
|
-
import os from "node:os";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
|
|
11
|
-
const MIN_NODE_MAJOR = 22;
|
|
12
|
-
const MIN_NODE_MINOR = 19;
|
|
13
|
-
const MIN_NODE_VERSION = `${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}`;
|
|
14
|
-
|
|
15
|
-
const parseNodeVersion = (rawVersion) => {
|
|
16
|
-
const [majorRaw = "0", minorRaw = "0"] = rawVersion.split(".");
|
|
17
|
-
return {
|
|
18
|
-
major: Number(majorRaw),
|
|
19
|
-
minor: Number(minorRaw),
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const isSupportedNodeVersion = (version) =>
|
|
24
|
-
version.major > MIN_NODE_MAJOR ||
|
|
25
|
-
(version.major === MIN_NODE_MAJOR && version.minor >= MIN_NODE_MINOR);
|
|
26
|
-
|
|
27
|
-
const ensureSupportedNodeVersion = () => {
|
|
28
|
-
if (isSupportedNodeVersion(parseNodeVersion(process.versions.node))) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
process.stderr.write(
|
|
33
|
-
`ps-claw: Node.js v${MIN_NODE_VERSION}+ is required (current: v${process.versions.node}).\n` +
|
|
34
|
-
"If you use nvm, run:\n" +
|
|
35
|
-
` nvm install ${MIN_NODE_MAJOR}\n` +
|
|
36
|
-
` nvm use ${MIN_NODE_MAJOR}\n` +
|
|
37
|
-
` nvm alias default ${MIN_NODE_MAJOR}\n`,
|
|
38
|
-
);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
ensureSupportedNodeVersion();
|
|
43
|
-
|
|
44
|
-
if (tryOutputLauncherVersion(process.argv)) {
|
|
45
|
-
process.exit(0);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const isSourceCheckoutLauncher = () =>
|
|
49
|
-
existsSync(new URL("./.git", import.meta.url)) ||
|
|
50
|
-
existsSync(new URL("./src/entry.ts", import.meta.url));
|
|
51
|
-
|
|
52
|
-
const isNodeCompileCacheDisabled = () => process.env.NODE_DISABLE_COMPILE_CACHE !== undefined;
|
|
53
|
-
const isNodeCompileCacheRequested = () =>
|
|
54
|
-
Boolean(process.env.NODE_COMPILE_CACHE) && !isNodeCompileCacheDisabled();
|
|
55
|
-
const sanitizeCompileCachePathSegment = (value) => {
|
|
56
|
-
const normalized = value.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
57
|
-
return normalized.length > 0 ? normalized : "unknown";
|
|
58
|
-
};
|
|
59
|
-
const readPackageVersion = () => {
|
|
60
|
-
try {
|
|
61
|
-
const parsed = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
|
|
62
|
-
if (typeof parsed?.version === "string" && parsed.version.trim().length > 0) {
|
|
63
|
-
return parsed.version;
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
// Fall through to an install-metadata-only cache key.
|
|
67
|
-
}
|
|
68
|
-
return "unknown";
|
|
69
|
-
};
|
|
70
|
-
const resolvePackagedCompileCacheDirectory = () => {
|
|
71
|
-
const packageJsonUrl = new URL("./package.json", import.meta.url);
|
|
72
|
-
const version = sanitizeCompileCachePathSegment(readPackageVersion());
|
|
73
|
-
let installMarker = "no-package-json";
|
|
74
|
-
try {
|
|
75
|
-
const stat = statSync(packageJsonUrl);
|
|
76
|
-
installMarker = `${Math.trunc(stat.mtimeMs)}-${stat.size}`;
|
|
77
|
-
} catch {
|
|
78
|
-
// Package archives should always have package.json, but keep startup best-effort.
|
|
79
|
-
}
|
|
80
|
-
const baseDirectory = isNodeCompileCacheRequested()
|
|
81
|
-
? process.env.NODE_COMPILE_CACHE
|
|
82
|
-
: path.join(os.tmpdir(), "node-compile-cache");
|
|
83
|
-
return path.join(
|
|
84
|
-
baseDirectory,
|
|
85
|
-
"ps-claw",
|
|
86
|
-
version,
|
|
87
|
-
sanitizeCompileCachePathSegment(installMarker),
|
|
88
|
-
);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const respawnSignals =
|
|
92
|
-
process.platform === "win32"
|
|
93
|
-
? ["SIGTERM", "SIGINT", "SIGBREAK"]
|
|
94
|
-
: ["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT"];
|
|
95
|
-
const respawnSignalExitGraceMs = 1_000;
|
|
96
|
-
const respawnSignalForceKillGraceMs = 1_000;
|
|
97
|
-
const respawnSignalHardExitGraceMs = 1_000;
|
|
98
|
-
|
|
99
|
-
const runRespawnedChild = (command, args, env) => {
|
|
100
|
-
const child = spawn(command, args, {
|
|
101
|
-
stdio: "inherit",
|
|
102
|
-
env,
|
|
103
|
-
});
|
|
104
|
-
const listeners = new Map();
|
|
105
|
-
// This intentionally overlaps with src/entry.compile-cache.ts; keep the
|
|
106
|
-
// respawn supervision behavior in sync until the launcher can share TS code.
|
|
107
|
-
// Give the child a moment to honor forwarded signals, then exit the wrapper so
|
|
108
|
-
// a child that ignores SIGTERM cannot keep the launcher alive indefinitely.
|
|
109
|
-
let signalExitTimer = null;
|
|
110
|
-
let signalForceKillTimer = null;
|
|
111
|
-
let signalHardExitTimer = null;
|
|
112
|
-
const detach = () => {
|
|
113
|
-
for (const [signal, listener] of listeners) {
|
|
114
|
-
process.off(signal, listener);
|
|
115
|
-
}
|
|
116
|
-
listeners.clear();
|
|
117
|
-
if (signalExitTimer) {
|
|
118
|
-
clearTimeout(signalExitTimer);
|
|
119
|
-
signalExitTimer = null;
|
|
120
|
-
}
|
|
121
|
-
if (signalForceKillTimer) {
|
|
122
|
-
clearTimeout(signalForceKillTimer);
|
|
123
|
-
signalForceKillTimer = null;
|
|
124
|
-
}
|
|
125
|
-
if (signalHardExitTimer) {
|
|
126
|
-
clearTimeout(signalHardExitTimer);
|
|
127
|
-
signalHardExitTimer = null;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
const forceKillChild = () => {
|
|
131
|
-
try {
|
|
132
|
-
child.kill(process.platform === "win32" ? "SIGTERM" : "SIGKILL");
|
|
133
|
-
} catch {
|
|
134
|
-
// Best-effort shutdown fallback.
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
const requestChildTermination = () => {
|
|
138
|
-
try {
|
|
139
|
-
child.kill("SIGTERM");
|
|
140
|
-
} catch {
|
|
141
|
-
// Best-effort shutdown fallback.
|
|
142
|
-
}
|
|
143
|
-
signalForceKillTimer = setTimeout(() => {
|
|
144
|
-
forceKillChild();
|
|
145
|
-
signalHardExitTimer = setTimeout(() => {
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}, respawnSignalHardExitGraceMs);
|
|
148
|
-
signalHardExitTimer.unref?.();
|
|
149
|
-
}, respawnSignalForceKillGraceMs);
|
|
150
|
-
signalForceKillTimer.unref?.();
|
|
151
|
-
};
|
|
152
|
-
const scheduleParentExit = () => {
|
|
153
|
-
if (signalExitTimer) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
signalExitTimer = setTimeout(() => {
|
|
157
|
-
requestChildTermination();
|
|
158
|
-
}, respawnSignalExitGraceMs);
|
|
159
|
-
signalExitTimer.unref?.();
|
|
160
|
-
};
|
|
161
|
-
for (const signal of respawnSignals) {
|
|
162
|
-
const listener = () => {
|
|
163
|
-
try {
|
|
164
|
-
child.kill(signal);
|
|
165
|
-
} catch {
|
|
166
|
-
// Best-effort signal forwarding.
|
|
167
|
-
}
|
|
168
|
-
scheduleParentExit();
|
|
169
|
-
};
|
|
170
|
-
try {
|
|
171
|
-
process.on(signal, listener);
|
|
172
|
-
listeners.set(signal, listener);
|
|
173
|
-
} catch {
|
|
174
|
-
// Unsupported signal on this platform.
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
child.once("exit", (code, signal) => {
|
|
178
|
-
detach();
|
|
179
|
-
if (signal) {
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}
|
|
182
|
-
process.exit(code ?? 1);
|
|
183
|
-
});
|
|
184
|
-
child.once("error", (error) => {
|
|
185
|
-
detach();
|
|
186
|
-
process.stderr.write(
|
|
187
|
-
`[ps-claw] Failed to respawn launcher: ${
|
|
188
|
-
error instanceof Error ? (error.stack ?? error.message) : String(error)
|
|
189
|
-
}\n`,
|
|
190
|
-
);
|
|
191
|
-
process.exit(1);
|
|
192
|
-
});
|
|
193
|
-
return true;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const respawnWithoutCompileCacheIfNeeded = () => {
|
|
197
|
-
if (!isSourceCheckoutLauncher()) {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
if (process.env.PS_CLAW_SOURCE_COMPILE_CACHE_RESPAWNED === "1") {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
if (!module.getCompileCacheDir?.() && !isNodeCompileCacheRequested()) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
const env = {
|
|
207
|
-
...process.env,
|
|
208
|
-
NODE_DISABLE_COMPILE_CACHE: "1",
|
|
209
|
-
PS_CLAW_SOURCE_COMPILE_CACHE_RESPAWNED: "1",
|
|
210
|
-
};
|
|
211
|
-
delete env.NODE_COMPILE_CACHE;
|
|
212
|
-
return runRespawnedChild(
|
|
213
|
-
process.execPath,
|
|
214
|
-
[...process.execArgv, fileURLToPath(import.meta.url), ...process.argv.slice(2)],
|
|
215
|
-
env,
|
|
216
|
-
);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const respawnWithPackagedCompileCacheIfNeeded = () => {
|
|
220
|
-
if (isSourceCheckoutLauncher() || isNodeCompileCacheDisabled()) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
if (process.env.PS_CLAW_PACKAGED_COMPILE_CACHE_RESPAWNED === "1") {
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
const currentDirectory = module.getCompileCacheDir?.();
|
|
227
|
-
if (!currentDirectory) {
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
const desiredDirectory = resolvePackagedCompileCacheDirectory();
|
|
231
|
-
if (path.resolve(currentDirectory) === path.resolve(desiredDirectory)) {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
const env = {
|
|
235
|
-
...process.env,
|
|
236
|
-
NODE_COMPILE_CACHE: desiredDirectory,
|
|
237
|
-
PS_CLAW_PACKAGED_COMPILE_CACHE_RESPAWNED: "1",
|
|
238
|
-
};
|
|
239
|
-
return runRespawnedChild(
|
|
240
|
-
process.execPath,
|
|
241
|
-
[...process.execArgv, fileURLToPath(import.meta.url), ...process.argv.slice(2)],
|
|
242
|
-
env,
|
|
243
|
-
);
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const waitingForCompileCacheRespawn =
|
|
247
|
-
respawnWithoutCompileCacheIfNeeded() || respawnWithPackagedCompileCacheIfNeeded();
|
|
248
|
-
|
|
249
|
-
// https://nodejs.org/api/module.html#module-compile-cache
|
|
250
|
-
if (
|
|
251
|
-
!waitingForCompileCacheRespawn &&
|
|
252
|
-
module.enableCompileCache &&
|
|
253
|
-
!isNodeCompileCacheDisabled() &&
|
|
254
|
-
!isSourceCheckoutLauncher()
|
|
255
|
-
) {
|
|
256
|
-
try {
|
|
257
|
-
module.enableCompileCache(resolvePackagedCompileCacheDirectory());
|
|
258
|
-
} catch {
|
|
259
|
-
// Ignore errors
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const getErrorMessage = (err) =>
|
|
264
|
-
err && typeof err === "object" && "message" in err && typeof err.message === "string"
|
|
265
|
-
? err.message
|
|
266
|
-
: "";
|
|
267
|
-
|
|
268
|
-
const isModuleNotFoundError = (err) =>
|
|
269
|
-
err && typeof err === "object" && "code" in err && err.code === "ERR_MODULE_NOT_FOUND";
|
|
270
|
-
|
|
271
|
-
const isDirectModuleNotFoundError = (err, specifier) => {
|
|
272
|
-
const message = getErrorMessage(err);
|
|
273
|
-
const bunSpecifierMiss =
|
|
274
|
-
message.includes(`Cannot find module '${specifier}'`) ||
|
|
275
|
-
message.includes(`Cannot find module "${specifier}"`);
|
|
276
|
-
const launcherPath = fileURLToPath(import.meta.url);
|
|
277
|
-
const bunLauncherImporterMiss =
|
|
278
|
-
message.includes(` from '${launcherPath}'`) || message.includes(` from "${launcherPath}"`);
|
|
279
|
-
|
|
280
|
-
const expectedUrl = new URL(specifier, import.meta.url);
|
|
281
|
-
const expectedPath = fileURLToPath(expectedUrl);
|
|
282
|
-
const nodePathMiss =
|
|
283
|
-
message.includes(`Cannot find module '${expectedPath}'`) ||
|
|
284
|
-
message.includes(`Cannot find module "${expectedPath}"`);
|
|
285
|
-
|
|
286
|
-
if (isModuleNotFoundError(err)) {
|
|
287
|
-
if (err && typeof err === "object" && "url" in err && err.url === expectedUrl.href) {
|
|
288
|
-
return true;
|
|
289
|
-
}
|
|
290
|
-
return nodePathMiss || (bunSpecifierMiss && bunLauncherImporterMiss);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return bunSpecifierMiss && bunLauncherImporterMiss;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const installProcessWarningFilter = async () => {
|
|
297
|
-
// Keep bootstrap warnings consistent with the TypeScript runtime.
|
|
298
|
-
for (const specifier of ["./dist/warning-filter.js", "./dist/warning-filter.mjs"]) {
|
|
299
|
-
try {
|
|
300
|
-
const mod = await import(specifier);
|
|
301
|
-
if (typeof mod.installProcessWarningFilter === "function") {
|
|
302
|
-
mod.installProcessWarningFilter();
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
} catch (err) {
|
|
306
|
-
if (isDirectModuleNotFoundError(err, specifier)) {
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
throw err;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const tryImport = async (specifier) => {
|
|
315
|
-
try {
|
|
316
|
-
await import(specifier);
|
|
317
|
-
return true;
|
|
318
|
-
} catch (err) {
|
|
319
|
-
// Only swallow direct entry misses; rethrow transitive resolution failures.
|
|
320
|
-
if (isDirectModuleNotFoundError(err, specifier)) {
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
throw err;
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
const exists = async (specifier) => {
|
|
328
|
-
try {
|
|
329
|
-
await access(new URL(specifier, import.meta.url));
|
|
330
|
-
return true;
|
|
331
|
-
} catch {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const buildMissingEntryErrorMessage = async () => {
|
|
337
|
-
const lines = ["ps-claw: missing dist/entry.(m)js (build output)."];
|
|
338
|
-
if (!(await exists("./src/entry.ts"))) {
|
|
339
|
-
return lines.join("\n");
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
lines.push("This install looks like an unbuilt source tree or GitHub source archive.");
|
|
343
|
-
lines.push(
|
|
344
|
-
"Build locally with `pnpm install && pnpm build`, or install a built package instead.",
|
|
345
|
-
);
|
|
346
|
-
lines.push(
|
|
347
|
-
"For pinned GitHub installs, use `npm install -g github:ps-claw/ps-claw#<ref>` instead of a raw `/archive/<ref>.tar.gz` URL.",
|
|
348
|
-
);
|
|
349
|
-
lines.push("For releases, use `npm install -g ps-claw@latest`.");
|
|
350
|
-
return lines.join("\n");
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
const isBareRootHelpInvocation = (argv) =>
|
|
354
|
-
argv.length === 3 && (argv[2] === "--help" || argv[2] === "-h");
|
|
355
|
-
|
|
356
|
-
const resolvePrecomputedCommandHelp = (argv) => {
|
|
357
|
-
if (argv.length !== 4 || (argv[3] !== "--help" && argv[3] !== "-h")) {
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
if (argv[2] === "browser") {
|
|
361
|
-
return { command: "browser", metadataKey: "browserHelpText" };
|
|
362
|
-
}
|
|
363
|
-
if (argv[2] === "secrets") {
|
|
364
|
-
return { command: "secrets", metadataKey: "secretsHelpText" };
|
|
365
|
-
}
|
|
366
|
-
if (argv[2] === "nodes") {
|
|
367
|
-
return { command: "nodes", metadataKey: "nodesHelpText" };
|
|
368
|
-
}
|
|
369
|
-
return null;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const isHelpFastPathDisabled = () =>
|
|
373
|
-
process.env.PS_CLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH === "1";
|
|
374
|
-
|
|
375
|
-
const normalizeLauncherHomeValue = (value) => {
|
|
376
|
-
const trimmed = value?.trim();
|
|
377
|
-
return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : undefined;
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
const resolveLauncherOsHomeDir = () =>
|
|
381
|
-
normalizeLauncherHomeValue(process.env.HOME) ??
|
|
382
|
-
normalizeLauncherHomeValue(process.env.USERPROFILE) ??
|
|
383
|
-
os.homedir();
|
|
384
|
-
|
|
385
|
-
const resolveLauncherHomeDir = () => {
|
|
386
|
-
const explicit = normalizeLauncherHomeValue(process.env.PS_CLAW_HOME);
|
|
387
|
-
const rawHome =
|
|
388
|
-
explicit && (explicit === "~" || explicit.startsWith("~/") || explicit.startsWith("~\\"))
|
|
389
|
-
? explicit.replace(/^~(?=$|[\\/])/, resolveLauncherOsHomeDir())
|
|
390
|
-
: (explicit ?? resolveLauncherOsHomeDir());
|
|
391
|
-
return path.resolve(rawHome);
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
const resolveLauncherUserPath = (input) => {
|
|
395
|
-
if (input === "~") {
|
|
396
|
-
return resolveLauncherHomeDir();
|
|
397
|
-
}
|
|
398
|
-
if (input.startsWith("~/") || input.startsWith("~\\")) {
|
|
399
|
-
return path.join(resolveLauncherHomeDir(), input.slice(2));
|
|
400
|
-
}
|
|
401
|
-
return path.resolve(input);
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
const resolveLauncherConfigPaths = () => {
|
|
405
|
-
const explicit = process.env.PS_CLAW_CONFIG_PATH?.trim();
|
|
406
|
-
if (explicit) {
|
|
407
|
-
return [resolveLauncherUserPath(explicit)];
|
|
408
|
-
}
|
|
409
|
-
const stateOverride = process.env.PS_CLAW_STATE_DIR?.trim();
|
|
410
|
-
if (stateOverride) {
|
|
411
|
-
const stateDir = resolveLauncherUserPath(stateOverride);
|
|
412
|
-
return [path.join(stateDir, "ps-claw.json"), path.join(stateDir, "clawdbot.json")];
|
|
413
|
-
}
|
|
414
|
-
const homeDir = resolveLauncherHomeDir();
|
|
415
|
-
return [
|
|
416
|
-
path.join(homeDir, ".ps-claw", "ps-claw.json"),
|
|
417
|
-
path.join(homeDir, ".ps-claw", "clawdbot.json"),
|
|
418
|
-
path.join(homeDir, ".clawdbot", "ps-claw.json"),
|
|
419
|
-
path.join(homeDir, ".clawdbot", "clawdbot.json"),
|
|
420
|
-
];
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
const shouldDeferRootHelpToRuntimeEntry = () => {
|
|
424
|
-
if (
|
|
425
|
-
process.env.PS_CLAW_BUNDLED_PLUGINS_DIR?.trim() ||
|
|
426
|
-
process.env.PS_CLAW_DISABLE_BUNDLED_PLUGINS?.trim()
|
|
427
|
-
) {
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
for (const configPath of resolveLauncherConfigPaths()) {
|
|
431
|
-
try {
|
|
432
|
-
const raw = readFileSync(configPath, "utf8");
|
|
433
|
-
return /\bplugins\b|\$include\b/.test(raw);
|
|
434
|
-
} catch {
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return false;
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
const loadPrecomputedHelpText = (key) => {
|
|
442
|
-
try {
|
|
443
|
-
const raw = readFileSync(new URL("./dist/cli-startup-metadata.json", import.meta.url), "utf8");
|
|
444
|
-
const parsed = JSON.parse(raw);
|
|
445
|
-
const value = parsed?.[key];
|
|
446
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
447
|
-
} catch {
|
|
448
|
-
return null;
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
function tryOutputLauncherVersion(argv) {
|
|
453
|
-
try {
|
|
454
|
-
if (normalizeLauncherMetadataValue(process.env.PS_CLAW_CONTAINER)) {
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
if (!isLauncherVersionFastPathArgv(argv)) {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
const version = resolveLauncherVersion();
|
|
461
|
-
const commit = resolveLauncherCommit();
|
|
462
|
-
process.stdout.write(commit ? `PS Claw ${version} (${commit})\n` : `PS Claw ${version}\n`);
|
|
463
|
-
return true;
|
|
464
|
-
} catch {
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function isLauncherVersionFastPathArgv(argv) {
|
|
470
|
-
return argv.length === 3 && (argv[2] === "--version" || argv[2] === "-V" || argv[2] === "-v");
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function normalizeLauncherMetadataValue(value) {
|
|
474
|
-
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
475
|
-
return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : undefined;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function readLauncherJson(relativePath) {
|
|
479
|
-
try {
|
|
480
|
-
return JSON.parse(readFileSync(new URL(relativePath, import.meta.url), "utf8"));
|
|
481
|
-
} catch {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function resolveLauncherVersion() {
|
|
487
|
-
const packageJson = readLauncherJson("./package.json");
|
|
488
|
-
const packageVersion = normalizeLauncherMetadataValue(packageJson?.version);
|
|
489
|
-
if (packageVersion) {
|
|
490
|
-
return packageVersion;
|
|
491
|
-
}
|
|
492
|
-
const buildInfo = readLauncherJson("./dist/build-info.json");
|
|
493
|
-
const buildVersion = normalizeLauncherMetadataValue(buildInfo?.version);
|
|
494
|
-
if (buildVersion) {
|
|
495
|
-
return buildVersion;
|
|
496
|
-
}
|
|
497
|
-
return normalizeLauncherMetadataValue(process.env.PS_CLAW_BUNDLED_VERSION) ?? "0.0.0";
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function resolveLauncherCommit() {
|
|
501
|
-
const envCommit = formatLauncherCommit(process.env.GIT_COMMIT ?? process.env.GIT_SHA);
|
|
502
|
-
if (envCommit) {
|
|
503
|
-
return envCommit;
|
|
504
|
-
}
|
|
505
|
-
return (
|
|
506
|
-
readLauncherGitCommit() ??
|
|
507
|
-
formatLauncherCommit(readLauncherJson("./dist/build-info.json")?.commit) ??
|
|
508
|
-
formatLauncherCommit(readLauncherJson("./package.json")?.gitHead) ??
|
|
509
|
-
formatLauncherCommit(readLauncherJson("./package.json")?.githead)
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function formatLauncherCommit(value) {
|
|
514
|
-
if (typeof value !== "string") {
|
|
515
|
-
return null;
|
|
516
|
-
}
|
|
517
|
-
const match = value.trim().match(/[0-9a-fA-F]{7,40}/);
|
|
518
|
-
return match ? match[0].slice(0, 7).toLowerCase() : null;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function readLauncherGitCommit() {
|
|
522
|
-
try {
|
|
523
|
-
const gitPath = fileURLToPath(new URL("./.git", import.meta.url));
|
|
524
|
-
const headPath = resolveLauncherGitHeadPath(gitPath);
|
|
525
|
-
if (!headPath) {
|
|
526
|
-
return null;
|
|
527
|
-
}
|
|
528
|
-
const head = readFileSync(headPath, "utf8").trim();
|
|
529
|
-
if (!head) {
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
if (!head.startsWith("ref:")) {
|
|
533
|
-
return formatLauncherCommit(head);
|
|
534
|
-
}
|
|
535
|
-
const ref = head.replace(/^ref:\s*/i, "").trim();
|
|
536
|
-
if (!ref.startsWith("refs/") || path.isAbsolute(ref) || ref.split("/").includes("..")) {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
const refsBase = resolveLauncherGitRefsBase(headPath);
|
|
540
|
-
const refPath = path.resolve(refsBase, ref);
|
|
541
|
-
const rel = path.relative(refsBase, refPath);
|
|
542
|
-
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
543
|
-
return null;
|
|
544
|
-
}
|
|
545
|
-
try {
|
|
546
|
-
return formatLauncherCommit(readFileSync(refPath, "utf8"));
|
|
547
|
-
} catch {
|
|
548
|
-
return readLauncherPackedRef(refsBase, ref);
|
|
549
|
-
}
|
|
550
|
-
} catch {
|
|
551
|
-
return null;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function resolveLauncherGitHeadPath(gitPath) {
|
|
556
|
-
try {
|
|
557
|
-
if (statSync(gitPath).isDirectory()) {
|
|
558
|
-
return path.join(gitPath, "HEAD");
|
|
559
|
-
}
|
|
560
|
-
const raw = readFileSync(gitPath, "utf8").trim();
|
|
561
|
-
if (!raw.startsWith("gitdir:")) {
|
|
562
|
-
return null;
|
|
563
|
-
}
|
|
564
|
-
return path.join(
|
|
565
|
-
path.resolve(path.dirname(gitPath), raw.slice("gitdir:".length).trim()),
|
|
566
|
-
"HEAD",
|
|
567
|
-
);
|
|
568
|
-
} catch {
|
|
569
|
-
return null;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function resolveLauncherGitRefsBase(headPath) {
|
|
574
|
-
const gitDir = path.dirname(headPath);
|
|
575
|
-
try {
|
|
576
|
-
const commonDir = readFileSync(path.join(gitDir, "commondir"), "utf8").trim();
|
|
577
|
-
return commonDir ? path.resolve(gitDir, commonDir) : gitDir;
|
|
578
|
-
} catch {
|
|
579
|
-
return gitDir;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
function readLauncherPackedRef(refsBase, ref) {
|
|
584
|
-
try {
|
|
585
|
-
const packedRefs = readFileSync(path.join(refsBase, "packed-refs"), "utf8");
|
|
586
|
-
for (const line of packedRefs.split("\n")) {
|
|
587
|
-
if (!line || line.startsWith("#") || line.startsWith("^")) {
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
const [commit, packedRef] = line.trim().split(/\s+/, 2);
|
|
591
|
-
if (packedRef === ref) {
|
|
592
|
-
return formatLauncherCommit(commit);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
} catch {
|
|
596
|
-
// fall through
|
|
597
|
-
}
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const tryOutputBareRootHelp = async () => {
|
|
602
|
-
if (!isBareRootHelpInvocation(process.argv)) {
|
|
603
|
-
return false;
|
|
604
|
-
}
|
|
605
|
-
if (shouldDeferRootHelpToRuntimeEntry()) {
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
const precomputed = loadPrecomputedHelpText("rootHelpText");
|
|
609
|
-
if (precomputed) {
|
|
610
|
-
process.stdout.write(precomputed);
|
|
611
|
-
return true;
|
|
612
|
-
}
|
|
613
|
-
for (const specifier of ["./dist/cli/program/root-help.js", "./dist/cli/program/root-help.mjs"]) {
|
|
614
|
-
try {
|
|
615
|
-
const mod = await import(specifier);
|
|
616
|
-
if (typeof mod.outputRootHelp === "function") {
|
|
617
|
-
await mod.outputRootHelp();
|
|
618
|
-
return true;
|
|
619
|
-
}
|
|
620
|
-
} catch (err) {
|
|
621
|
-
if (isDirectModuleNotFoundError(err, specifier)) {
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
throw err;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
return false;
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
const tryOutputPrecomputedCommandHelp = () => {
|
|
631
|
-
const commandHelp = resolvePrecomputedCommandHelp(process.argv);
|
|
632
|
-
if (!commandHelp) {
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
if (commandHelp.command === "nodes" && shouldDeferRootHelpToRuntimeEntry()) {
|
|
636
|
-
return false;
|
|
637
|
-
}
|
|
638
|
-
const precomputed = loadPrecomputedHelpText(commandHelp.metadataKey);
|
|
639
|
-
if (!precomputed) {
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
|
-
process.stdout.write(precomputed);
|
|
643
|
-
return true;
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
if (!waitingForCompileCacheRespawn) {
|
|
647
|
-
if (!isHelpFastPathDisabled() && (await tryOutputBareRootHelp())) {
|
|
648
|
-
// OK
|
|
649
|
-
} else if (!isHelpFastPathDisabled() && tryOutputPrecomputedCommandHelp()) {
|
|
650
|
-
// OK
|
|
651
|
-
} else {
|
|
652
|
-
await installProcessWarningFilter();
|
|
653
|
-
if (await tryImport("./dist/entry.js")) {
|
|
654
|
-
// OK
|
|
655
|
-
} else if (await tryImport("./dist/entry.mjs")) {
|
|
656
|
-
// OK
|
|
657
|
-
} else {
|
|
658
|
-
throw new Error(await buildMissingEntryErrorMessage());
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|