claude-simple 0.2.0 → 0.2.2
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/client.js +64 -3
- package/dist/{app.0.2.0.min.js → app.0.2.2.min.js} +150 -97
- package/dist/index.html +2 -2
- package/dist/{style.0.2.0.min.css → style.0.2.2.min.css} +103 -4
- package/gateway.js +7 -2
- package/package.json +1 -1
- package/public/agent-panel.js +10 -1
- package/public/chat.js +9 -1
- package/public/files.js +60 -21
- package/public/index.html +2 -2
- package/public/shell/dashboard.js +62 -4
- package/public/shell/notes.js +24 -9
- package/public/shell/session-nav.js +53 -21
- package/public/shell/templates.js +30 -6
- package/public/shell.js +25 -3
- package/public/style.css +102 -3
- package/public/sw.js +2 -2
- package/server.js +5 -1
- package/src/app.js +35 -9
- package/src/auto-update.js +215 -0
- package/src/runtime/protocol.js +2 -2
package/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createApp, init } from './src/app.js';
|
|
2
|
+
import { createApp, init, hasBusyAgent } from './src/app.js';
|
|
3
|
+
import { startAutoUpdate } from './src/auto-update.js';
|
|
3
4
|
import { WebSocket } from 'ws';
|
|
4
5
|
import os from 'os';
|
|
5
6
|
import crypto from 'crypto';
|
|
@@ -18,6 +19,10 @@ const { server } = createApp();
|
|
|
18
19
|
await new Promise(resolve => server.listen(LOCAL_PORT, '127.0.0.1', resolve));
|
|
19
20
|
console.log(`[client] Local app listening on 127.0.0.1:${LOCAL_PORT}`);
|
|
20
21
|
|
|
22
|
+
// Self-update from npm (default every 12h). Disable with AUTO_UPDATE=0.
|
|
23
|
+
// Requires global install + systemd Restart= (or equivalent) for full apply.
|
|
24
|
+
startAutoUpdate({ role: 'edge-client', isBusy: hasBusyAgent });
|
|
25
|
+
|
|
21
26
|
// ─── Active WS tunnels: tunnelId → local WebSocket ───────────────────────────
|
|
22
27
|
const tunnels = new Map();
|
|
23
28
|
|
|
@@ -25,6 +30,30 @@ const tunnels = new Map();
|
|
|
25
30
|
let controlWs = null;
|
|
26
31
|
let reconnectDelay = 1000;
|
|
27
32
|
|
|
33
|
+
/** Drop hop-by-hop / encoding headers so hub clients see the decoded body. */
|
|
34
|
+
function sanitizeProxyHeaders(headers) {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [k, v] of Object.entries(headers || {})) {
|
|
37
|
+
const lk = k.toLowerCase();
|
|
38
|
+
if (
|
|
39
|
+
lk === 'content-encoding'
|
|
40
|
+
|| lk === 'content-length'
|
|
41
|
+
|| lk === 'transfer-encoding'
|
|
42
|
+
|| lk === 'connection'
|
|
43
|
+
|| lk === 'keep-alive'
|
|
44
|
+
|| lk === 'proxy-authenticate'
|
|
45
|
+
|| lk === 'proxy-authorization'
|
|
46
|
+
|| lk === 'te'
|
|
47
|
+
|| lk === 'trailer'
|
|
48
|
+
|| lk === 'upgrade'
|
|
49
|
+
) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
out[k] = v;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
28
57
|
function sendControl(msg) {
|
|
29
58
|
if (controlWs?.readyState === WebSocket.OPEN) {
|
|
30
59
|
controlWs.send(JSON.stringify(msg));
|
|
@@ -56,13 +85,22 @@ function connect() {
|
|
|
56
85
|
if (msg.type === 'http-req') {
|
|
57
86
|
try {
|
|
58
87
|
const url = `http://127.0.0.1:${LOCAL_PORT}${msg.path}`;
|
|
88
|
+
// Node fetch auto-decompresses gzip/br. If we forward Content-Encoding
|
|
89
|
+
// with the already-plain body, browsers try to gunzip JSON and fail
|
|
90
|
+
// (chat history / large API payloads look "empty" or parse-error).
|
|
91
|
+
const reqHeaders = { ...(msg.headers || {}), host: `127.0.0.1:${LOCAL_PORT}` };
|
|
92
|
+
delete reqHeaders['accept-encoding'];
|
|
93
|
+
delete reqHeaders['Accept-Encoding'];
|
|
94
|
+
reqHeaders['Accept-Encoding'] = 'identity';
|
|
95
|
+
|
|
59
96
|
const res = await fetch(url, {
|
|
60
97
|
method: msg.method,
|
|
61
|
-
headers:
|
|
98
|
+
headers: reqHeaders,
|
|
62
99
|
body: msg.body || undefined,
|
|
63
100
|
});
|
|
64
|
-
const headers = Object.fromEntries(res.headers.entries());
|
|
101
|
+
const headers = sanitizeProxyHeaders(Object.fromEntries(res.headers.entries()));
|
|
65
102
|
const contentType = res.headers.get('content-type') || '';
|
|
103
|
+
const contentDisp = res.headers.get('content-disposition') || '';
|
|
66
104
|
|
|
67
105
|
// SSE (and any other body-less-until-done response) is forwarded as
|
|
68
106
|
// it arrives instead of buffered in full, so remote/hub viewers get
|
|
@@ -84,7 +122,30 @@ function connect() {
|
|
|
84
122
|
return;
|
|
85
123
|
}
|
|
86
124
|
|
|
125
|
+
// Binary responses (file downloads, images, …) must NOT go through
|
|
126
|
+
// res.text() — UTF-8 decoding corrupts bytes and downloads break on hub.
|
|
127
|
+
const isBinary =
|
|
128
|
+
/attachment/i.test(contentDisp)
|
|
129
|
+
|| /^(application\/octet-stream|application\/pdf|application\/zip|application\/gzip|application\/x-|application\/vnd\.|image\/|audio\/|video\/|font\/)/i.test(contentType)
|
|
130
|
+
|| (contentType !== '' && !/^(text\/|application\/(json|javascript|xml|problem\+json|x-www-form-urlencoded)|multipart\/)/i.test(contentType));
|
|
131
|
+
|
|
132
|
+
if (isBinary) {
|
|
133
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
134
|
+
headers['content-length'] = String(buf.length);
|
|
135
|
+
sendControl({
|
|
136
|
+
type: 'http-res',
|
|
137
|
+
reqId: msg.reqId,
|
|
138
|
+
status: res.status,
|
|
139
|
+
headers,
|
|
140
|
+
body: buf.toString('base64'),
|
|
141
|
+
encoding: 'base64',
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
87
146
|
const body = await res.text();
|
|
147
|
+
// Body is always decoded text here — length must match UTF-8 bytes.
|
|
148
|
+
headers['content-length'] = String(Buffer.byteLength(body));
|
|
88
149
|
sendControl({
|
|
89
150
|
type: 'http-res',
|
|
90
151
|
reqId: msg.reqId,
|