ps-claw 2026.5.30
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/LICENSE +24 -0
- package/README.md +149 -0
- package/dist/entry.js +163 -0
- package/dist/entry.mjs +163 -0
- package/dist/web-ui/public/index.html +1179 -0
- package/dist/web-ui/server.mjs +192 -0
- package/package.json +1961 -0
- package/patches/.gitkeep +1 -0
- package/pnpm-workspace.yaml +121 -0
- package/ps-claw.mjs +661 -0
- package/src/agents/templates/HEARTBEAT.md +3 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PS Claw — Interface Web
|
|
5
|
+
* Servidor leve que serve a UI e faz proxy para o gateway do PS Claw
|
|
6
|
+
* Suporta multiplos gateways, API de modelos e configuracoes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import http from "node:http";
|
|
10
|
+
import https from "node:https";
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
const WEB_PORT = process.env.PS_CLAW_WEB_PORT || 3000;
|
|
18
|
+
const GATEWAY_PORT = process.env.PS_CLAW_GATEWAY_PORT || 18789;
|
|
19
|
+
const GATEWAY_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN || "";
|
|
20
|
+
|
|
21
|
+
function serveFile(res, filePath, contentType) {
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(filePath);
|
|
24
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
25
|
+
res.end(content);
|
|
26
|
+
} catch {
|
|
27
|
+
res.writeHead(404);
|
|
28
|
+
res.end("Not found");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function proxyToGateway(req, res, bodyData, targetHost, targetPort, targetPath, authToken) {
|
|
33
|
+
const options = {
|
|
34
|
+
hostname: targetHost,
|
|
35
|
+
port: targetPort,
|
|
36
|
+
path: targetPath,
|
|
37
|
+
method: req.method,
|
|
38
|
+
headers: {
|
|
39
|
+
...req.headers,
|
|
40
|
+
host: `${targetHost}:${targetPort}`,
|
|
41
|
+
...(authToken && { authorization: `Bearer ${authToken}` }),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const proxyReq = http.request(options, (proxyRes) => {
|
|
46
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
47
|
+
proxyRes.pipe(res);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
proxyReq.on("error", () => {
|
|
51
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
52
|
+
res.end(JSON.stringify({ error: "Gateway do PS Claw nao esta rodando. Execute: ps-claw gateway run" }));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (bodyData) proxyReq.write(bodyData);
|
|
56
|
+
proxyReq.end();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseUrl(urlStr) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = new URL(urlStr);
|
|
62
|
+
return {
|
|
63
|
+
protocol: parsed.protocol,
|
|
64
|
+
hostname: parsed.hostname,
|
|
65
|
+
port: parsed.port || (parsed.protocol === "https:" ? "443" : "80"),
|
|
66
|
+
path: parsed.pathname + parsed.search,
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const server = http.createServer((req, res) => {
|
|
74
|
+
// CORS
|
|
75
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
76
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
77
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
78
|
+
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
|
79
|
+
|
|
80
|
+
// Proxy para o gateway local (padrao)
|
|
81
|
+
if (req.url.startsWith("/gateway")) {
|
|
82
|
+
let body = "";
|
|
83
|
+
req.on("data", chunk => body += chunk);
|
|
84
|
+
req.on("end", () => proxyToGateway(
|
|
85
|
+
req, res, body || null,
|
|
86
|
+
"127.0.0.1", GATEWAY_PORT,
|
|
87
|
+
req.url.replace("/gateway", ""),
|
|
88
|
+
GATEWAY_TOKEN
|
|
89
|
+
));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// API: Health check do gateway local
|
|
94
|
+
if (req.url === "/api/health" && req.method === "GET") {
|
|
95
|
+
const healthReq = http.request({
|
|
96
|
+
hostname: "127.0.0.1",
|
|
97
|
+
port: GATEWAY_PORT,
|
|
98
|
+
path: "/health",
|
|
99
|
+
method: "GET",
|
|
100
|
+
...(GATEWAY_TOKEN && { headers: { authorization: `Bearer ${GATEWAY_TOKEN}` } }),
|
|
101
|
+
}, (healthRes) => {
|
|
102
|
+
let data = "";
|
|
103
|
+
healthRes.on("data", chunk => data += chunk);
|
|
104
|
+
healthRes.on("end", () => {
|
|
105
|
+
res.writeHead(healthRes.statusCode, { "Content-Type": "application/json" });
|
|
106
|
+
res.end(data);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
healthReq.on("error", () => {
|
|
110
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
111
|
+
res.end(JSON.stringify({ status: "offline", error: "Gateway offline" }));
|
|
112
|
+
});
|
|
113
|
+
healthReq.end();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// API: Listar modelos disponiveis
|
|
118
|
+
if (req.url === "/api/models" && req.method === "GET") {
|
|
119
|
+
const models = [
|
|
120
|
+
{ id: "gpt-4o", name: "GPT-4o", provider: "openai" },
|
|
121
|
+
{ id: "gpt-4o-mini", name: "GPT-4o Mini", provider: "openai" },
|
|
122
|
+
{ id: "gpt-4-turbo", name: "GPT-4 Turbo", provider: "openai" },
|
|
123
|
+
{ id: "claude-opus-4-5", name: "Claude Opus 4.5", provider: "anthropic" },
|
|
124
|
+
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", provider: "anthropic" },
|
|
125
|
+
{ id: "claude-3-5-sonnet", name: "Claude 3.5 Sonnet", provider: "anthropic" },
|
|
126
|
+
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", provider: "google" },
|
|
127
|
+
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash", provider: "google" },
|
|
128
|
+
{ id: "deepseek-chat", name: "DeepSeek Chat", provider: "deepseek" },
|
|
129
|
+
];
|
|
130
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
131
|
+
res.end(JSON.stringify({ models }));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// API: Proxy para gateway remoto
|
|
136
|
+
if (req.url.startsWith("/api/proxy") && req.method === "POST") {
|
|
137
|
+
let body = "";
|
|
138
|
+
req.on("data", chunk => body += chunk);
|
|
139
|
+
req.on("end", () => {
|
|
140
|
+
try {
|
|
141
|
+
const { url: targetUrl, path: targetPath, token, method, body: reqBody } = JSON.parse(body);
|
|
142
|
+
const parsed = parseUrl(targetUrl);
|
|
143
|
+
if (!parsed) {
|
|
144
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
145
|
+
res.end(JSON.stringify({ error: "URL invalida" }));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
proxyToGateway(
|
|
149
|
+
{ method: method || "GET", headers: {} },
|
|
150
|
+
res, reqBody ? JSON.stringify(reqBody) : null,
|
|
151
|
+
parsed.hostname, parsed.port,
|
|
152
|
+
targetPath || parsed.path,
|
|
153
|
+
token
|
|
154
|
+
);
|
|
155
|
+
} catch {
|
|
156
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
157
|
+
res.end(JSON.stringify({ error: "Requisicao invalida" }));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Servir arquivos estaticos
|
|
164
|
+
const url = req.url === "/" ? "/index.html" : req.url;
|
|
165
|
+
const filePath = path.join(__dirname, "public", url);
|
|
166
|
+
const ext = path.extname(filePath);
|
|
167
|
+
const mimeTypes = {
|
|
168
|
+
".html": "text/html; charset=utf-8",
|
|
169
|
+
".js": "application/javascript",
|
|
170
|
+
".mjs": "application/javascript",
|
|
171
|
+
".css": "text/css",
|
|
172
|
+
".png": "image/png",
|
|
173
|
+
".svg": "image/svg+xml",
|
|
174
|
+
".ico": "image/x-icon",
|
|
175
|
+
".json": "application/json",
|
|
176
|
+
".woff": "font/woff",
|
|
177
|
+
".woff2": "font/woff2",
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
serveFile(res, filePath, mimeTypes[ext] || "text/plain");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
server.listen(WEB_PORT, () => {
|
|
184
|
+
console.log("");
|
|
185
|
+
console.log(" 🦞 PS Claw — Interface Web");
|
|
186
|
+
console.log(" ─────────────────────────────");
|
|
187
|
+
console.log(` ✅ Rodando em: http://localhost:${WEB_PORT}`);
|
|
188
|
+
console.log(` 🔗 Gateway: http://localhost:${GATEWAY_PORT}`);
|
|
189
|
+
console.log("");
|
|
190
|
+
console.log(" Abra o navegador em: http://localhost:" + WEB_PORT);
|
|
191
|
+
console.log("");
|
|
192
|
+
});
|