claude-simple 0.2.28 → 0.2.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/README.md +17 -1
- package/bin/edge-ctl.js +151 -4
- package/client.js +8 -2
- package/dist/addon-fit.min.js +0 -0
- package/dist/app.0.2.30.min.js +1308 -0
- package/dist/index.html +2 -2
- package/dist/style.0.2.30.min.css +2012 -0
- package/dist/xterm.min.css +0 -0
- package/dist/xterm.min.js +0 -0
- package/gateway.js +0 -0
- package/package.json +2 -2
- package/public/addon-fit.min.js +0 -0
- package/public/agent-panel.js +47 -20
- package/public/api.js +1 -1
- package/public/app.js +5 -5
- package/public/base.css +0 -0
- package/public/cache.js +19 -0
- package/public/chat.js +76 -19
- package/public/files.js +26 -10
- package/public/git.js +2 -2
- package/public/icons/icon-180.png +0 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/icon-maskable-512.png +0 -0
- package/public/index.html +2 -2
- package/public/lib.js +0 -0
- package/public/manifest.webmanifest +0 -0
- package/public/markdown.js +16 -0
- package/public/math.js +145 -7
- package/public/mobile.js +0 -0
- package/public/notifications.js +0 -0
- package/public/offline.html +0 -0
- package/public/pwa.js +1 -1
- package/public/router.js +0 -0
- package/public/scroll-follow.js +78 -0
- package/public/settings.js +41 -0
- package/public/shell/boot.js +0 -0
- package/public/shell/dashboard.js +0 -0
- package/public/shell/hub.js +0 -0
- package/public/shell/layout.js +0 -0
- package/public/shell/notes.js +0 -0
- package/public/shell/session-context.js +0 -0
- package/public/shell/session-list.js +49 -16
- package/public/shell/session-nav.js +64 -17
- package/public/shell/templates.js +0 -0
- package/public/shell/util.js +0 -0
- package/public/shell.js +0 -0
- package/public/state.js +7 -7
- package/public/style.css +5 -0
- package/public/sw.js +10 -5
- package/public/terminal.js +0 -0
- package/public/wake-lock.js +0 -0
- package/public/workspace-path.js +61 -0
- package/public/xterm.min.css +0 -0
- package/public/xterm.min.js +0 -0
- package/src/agent-quota.js +228 -0
- package/src/agents/claude.js +0 -0
- package/src/agents/codex.js +2 -0
- package/src/agents/cursor.js +2 -1
- package/src/agents/gemini.js +2 -1
- package/src/agents/grok.js +6 -1
- package/src/agents/meta-agent.js +7 -4
- package/src/agents/models.js +21 -12
- package/src/agents/sessions.js +133 -69
- package/src/agents/stream-batch.js +0 -0
- package/src/app.js +80 -36
- package/src/auto-update.js +2 -0
- package/src/collab.js +3 -0
- package/src/platform.js +133 -0
- package/src/project-api-path.js +14 -0
- package/src/runtime/approvals.js +0 -0
- package/src/runtime/chat-broadcast.js +12 -0
- package/src/runtime/outbox.js +0 -0
- package/src/runtime/port-websocket.js +80 -0
- package/src/runtime/protocol.js +0 -0
- package/dist/app.0.2.28.min.js +0 -1220
- package/dist/style.0.2.28.min.css +0 -2007
package/README.md
CHANGED
|
@@ -109,6 +109,22 @@ Edge-only env (when connecting to a hub):
|
|
|
109
109
|
|
|
110
110
|
`claude-edge migrate-user-agent` 可将旧的 macOS system Edge 迁移到用户会话。对只使用无用户凭据的后台 agent,system service 仍可选;默认建议使用用户 LaunchAgent。
|
|
111
111
|
|
|
112
|
+
### Windows Edge
|
|
113
|
+
|
|
114
|
+
Windows 没有 launchd/systemd。在业务机安装 Node 后:
|
|
115
|
+
|
|
116
|
+
```bat
|
|
117
|
+
set GATEWAY_URL=wss://your-hub/machine-connect
|
|
118
|
+
set MACHINE_TOKEN=long-shared-secret
|
|
119
|
+
set MACHINE_ID=win-box
|
|
120
|
+
claude-edge start
|
|
121
|
+
claude-edge status
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
进程、日志和 env 落在 `%USERPROFILE%\.claude-simple-edge\`。Shell 标签使用 PowerShell/cmd(不走 unix pty)。Session / Files 路径按 Windows 盘符与大小写不敏感比较。
|
|
125
|
+
|
|
126
|
+
计划任务必须跑在**当前登录用户**(`LogonType=Interactive` / 用户登录时),不要用 SYSTEM / Session 0,否则 Grok/Codex 会读不到用户的 `auth.json` / Credential Manager,网页上会反复要求登录。
|
|
127
|
+
|
|
112
128
|
## Features
|
|
113
129
|
|
|
114
130
|
### Chat
|
|
@@ -260,6 +276,6 @@ The Memory tab lists files for the selected session's agent and loads their cont
|
|
|
260
276
|
| `npm start` / `npm run serve` | Production standalone |
|
|
261
277
|
| `npm run build` | Bundle frontend into `dist/` |
|
|
262
278
|
| `npm run client` | Edge worker (requires hub) |
|
|
263
|
-
| `claude-edge status\|start\|stop\|restart\|logs` | Supervised edge (launchd / systemd). Also `claude-simple-edge`, `edge-daemon` |
|
|
279
|
+
| `claude-edge status\|start\|stop\|restart\|logs` | Supervised edge (launchd / systemd / Windows user process). Also `claude-simple-edge`, `edge-daemon` |
|
|
264
280
|
| `npm run gateway` | Legacy Node hub |
|
|
265
281
|
| `npm run gateway:build` | Build Go hub via `gateway/Makefile` |
|
package/bin/edge-ctl.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* claude-edge status|start|stop|restart|logs
|
|
5
5
|
* claude-edge daemon <same>
|
|
6
6
|
*
|
|
7
|
-
* Detects launchd (macOS
|
|
7
|
+
* Detects launchd (macOS), systemd (Linux), or a user-session process on Windows.
|
|
8
8
|
*/
|
|
9
9
|
import { spawn, spawnSync } from 'child_process';
|
|
10
10
|
import fs from 'fs';
|
|
@@ -22,6 +22,17 @@ const AGENT_PLIST = path.join(os.homedir(), 'Library', 'LaunchAgents', `${AGENT_
|
|
|
22
22
|
const SYSTEMD_UNIT = 'claude-simple-edge.service';
|
|
23
23
|
const MAC_OUT = path.join(os.homedir(), '.claude-simple-edge', 'edge.out.log');
|
|
24
24
|
const MAC_ERR = path.join(os.homedir(), '.claude-simple-edge', 'edge.err.log');
|
|
25
|
+
const WIN_DIR = path.join(os.homedir(), '.claude-simple-edge');
|
|
26
|
+
const WIN_PID = path.join(WIN_DIR, 'edge.pid');
|
|
27
|
+
const WIN_OUT = path.join(WIN_DIR, 'edge.out.log');
|
|
28
|
+
const WIN_ERR = path.join(WIN_DIR, 'edge.err.log');
|
|
29
|
+
const WIN_ENV = path.join(WIN_DIR, 'edge.env.json');
|
|
30
|
+
const WIN_TASK = 'ClaudeSimpleEdge';
|
|
31
|
+
const WIN_ENV_KEYS = [
|
|
32
|
+
'GATEWAY_URL', 'MACHINE_TOKEN', 'MACHINE_ID', 'LOCAL_PORT', 'AGENTS',
|
|
33
|
+
'CLAUDE_CONFIG_DIRS', 'CODEX_HOME', 'GROK_HOME', 'GEMINI_HOME', 'CURSOR_HOME',
|
|
34
|
+
'AUTO_UPDATE',
|
|
35
|
+
];
|
|
25
36
|
|
|
26
37
|
function usage(code = 0) {
|
|
27
38
|
const text = `Usage: claude-edge <command>
|
|
@@ -35,6 +46,9 @@ function usage(code = 0) {
|
|
|
35
46
|
logs err Stderr only (launchd)
|
|
36
47
|
migrate-user-agent Move macOS Edge into the login session (required for Keychain-backed AGY auth)
|
|
37
48
|
|
|
49
|
+
Windows uses a user-session process (pid + logs under %USERPROFILE%\\.claude-simple-edge).
|
|
50
|
+
start persists GATEWAY_URL / MACHINE_TOKEN from the current environment.
|
|
51
|
+
|
|
38
52
|
Also installed as: claude-simple-edge, edge-daemon
|
|
39
53
|
`;
|
|
40
54
|
if (code) process.stderr.write(text);
|
|
@@ -70,6 +84,7 @@ function detectBackend() {
|
|
|
70
84
|
}
|
|
71
85
|
if (process.platform === 'darwin') return 'launchd-user';
|
|
72
86
|
if (process.platform === 'linux') return 'systemd';
|
|
87
|
+
if (process.platform === 'win32') return 'win32';
|
|
73
88
|
return null;
|
|
74
89
|
}
|
|
75
90
|
|
|
@@ -177,6 +192,13 @@ function parseLaunchdPrint(text) {
|
|
|
177
192
|
|
|
178
193
|
function listenPort() {
|
|
179
194
|
const port = process.env.LOCAL_PORT || '13000';
|
|
195
|
+
if (process.platform === 'win32') {
|
|
196
|
+
const r = run('netstat', ['-ano']);
|
|
197
|
+
if (r.status === 0 && r.stdout && new RegExp(`:${port}\\s+.*LISTENING`, 'i').test(r.stdout)) {
|
|
198
|
+
return `127.0.0.1:${port}`;
|
|
199
|
+
}
|
|
200
|
+
return 'down';
|
|
201
|
+
}
|
|
180
202
|
const r = run('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN']);
|
|
181
203
|
if (r.status === 0 && r.stdout.trim()) return `127.0.0.1:${port}`;
|
|
182
204
|
const ss = run('ss', ['-lnt']);
|
|
@@ -186,11 +208,128 @@ function listenPort() {
|
|
|
186
208
|
|
|
187
209
|
function tailFile(file, n) {
|
|
188
210
|
if (!fs.existsSync(file)) return '';
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
try {
|
|
212
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
213
|
+
const lines = text.split(/\r?\n/);
|
|
214
|
+
return lines.slice(Math.max(0, lines.length - n)).join('\n').trimEnd();
|
|
215
|
+
} catch {
|
|
216
|
+
return '';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function pidAlive(pid) {
|
|
221
|
+
if (!pid) return false;
|
|
222
|
+
try {
|
|
223
|
+
process.kill(pid, 0);
|
|
224
|
+
return true;
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function readWinPid() {
|
|
231
|
+
try {
|
|
232
|
+
const n = parseInt(fs.readFileSync(WIN_PID, 'utf8').trim(), 10);
|
|
233
|
+
return Number.isFinite(n) ? n : 0;
|
|
234
|
+
} catch {
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function loadWinEnv() {
|
|
240
|
+
try {
|
|
241
|
+
const parsed = JSON.parse(fs.readFileSync(WIN_ENV, 'utf8'));
|
|
242
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
243
|
+
} catch {
|
|
244
|
+
return {};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function persistWinEnv() {
|
|
249
|
+
fs.mkdirSync(WIN_DIR, { recursive: true });
|
|
250
|
+
const prev = loadWinEnv();
|
|
251
|
+
const next = { ...prev };
|
|
252
|
+
for (const key of WIN_ENV_KEYS) {
|
|
253
|
+
if (process.env[key]) next[key] = process.env[key];
|
|
254
|
+
}
|
|
255
|
+
fs.writeFileSync(WIN_ENV, JSON.stringify(next, null, 2));
|
|
256
|
+
return next;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function clientScript() {
|
|
260
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'client.js');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function tryWinStartupTask(nodePath, client) {
|
|
264
|
+
const tr = `"${nodePath}" "${client}"`;
|
|
265
|
+
run('schtasks', ['/Create', '/TN', WIN_TASK, '/SC', 'ONLOGON', '/RL', 'LIMITED', '/F', '/TR', tr]);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function cmdWinStatus() {
|
|
269
|
+
const pid = readWinPid();
|
|
270
|
+
const alive = pidAlive(pid);
|
|
271
|
+
const env = loadWinEnv();
|
|
272
|
+
console.log('service: windows-user');
|
|
273
|
+
console.log(`machine: ${env.MACHINE_ID || process.env.MACHINE_ID || '-'}`);
|
|
274
|
+
console.log(`state: ${alive ? 'running' : 'stopped'}`);
|
|
275
|
+
console.log(`pid: ${alive ? pid : '-'}`);
|
|
276
|
+
console.log(`listen: ${listenPort()}`);
|
|
277
|
+
console.log(`dir: ${WIN_DIR}`);
|
|
278
|
+
console.log(`stdout: ${WIN_OUT}`);
|
|
279
|
+
console.log(`stderr: ${WIN_ERR}`);
|
|
280
|
+
const last = tailFile(WIN_OUT, 8);
|
|
281
|
+
if (last) console.log(`\n--- last logs ---\n${last}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function cmdWinStart() {
|
|
285
|
+
const existing = readWinPid();
|
|
286
|
+
if (pidAlive(existing)) {
|
|
287
|
+
console.log(`already running (pid ${existing})`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const envFile = persistWinEnv();
|
|
291
|
+
if (!envFile.GATEWAY_URL || !envFile.MACHINE_TOKEN) {
|
|
292
|
+
fail('set GATEWAY_URL and MACHINE_TOKEN in this shell (they are saved under .claude-simple-edge\\edge.env.json)');
|
|
293
|
+
}
|
|
294
|
+
fs.mkdirSync(WIN_DIR, { recursive: true });
|
|
295
|
+
const out = fs.openSync(WIN_OUT, 'a');
|
|
296
|
+
const err = fs.openSync(WIN_ERR, 'a');
|
|
297
|
+
const child = spawn(process.execPath, [clientScript()], {
|
|
298
|
+
cwd: WIN_DIR,
|
|
299
|
+
env: { ...process.env, ...envFile },
|
|
300
|
+
detached: true,
|
|
301
|
+
stdio: ['ignore', out, err],
|
|
302
|
+
windowsHide: true,
|
|
303
|
+
});
|
|
304
|
+
fs.writeFileSync(WIN_PID, String(child.pid));
|
|
305
|
+
child.unref();
|
|
306
|
+
tryWinStartupTask(process.execPath, clientScript());
|
|
307
|
+
console.log(`started pid ${child.pid}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function cmdWinStop() {
|
|
311
|
+
const pid = readWinPid();
|
|
312
|
+
if (pidAlive(pid)) {
|
|
313
|
+
const killed = run('taskkill', ['/PID', String(pid), '/T', '/F']);
|
|
314
|
+
if (killed.status !== 0) {
|
|
315
|
+
try { process.kill(pid, 'SIGTERM'); } catch {}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
try { fs.unlinkSync(WIN_PID); } catch {}
|
|
319
|
+
run('schtasks', ['/End', '/TN', WIN_TASK]);
|
|
320
|
+
console.log('stopped');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function cmdWinLogs(mode) {
|
|
324
|
+
if (mode === '-f' || mode === '--follow') {
|
|
325
|
+
fail('log follow is not supported on Windows; use `claude-edge logs` and re-run');
|
|
326
|
+
}
|
|
327
|
+
const file = (mode === 'err' || mode === 'error' || mode === 'stderr') ? WIN_ERR : WIN_OUT;
|
|
328
|
+
process.stdout.write(tailFile(file, 80) + '\n');
|
|
191
329
|
}
|
|
192
330
|
|
|
193
331
|
function cmdStatus(backend) {
|
|
332
|
+
if (backend === 'win32') return cmdWinStatus();
|
|
194
333
|
if (backend === 'launchd') ensureRoot();
|
|
195
334
|
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
196
335
|
const target = macTarget(backend);
|
|
@@ -241,6 +380,7 @@ function cmdStatus(backend) {
|
|
|
241
380
|
}
|
|
242
381
|
|
|
243
382
|
function cmdStart(backend) {
|
|
383
|
+
if (backend === 'win32') return cmdWinStart();
|
|
244
384
|
if (backend === 'launchd') ensureRoot();
|
|
245
385
|
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
246
386
|
const target = macTarget(backend);
|
|
@@ -261,6 +401,7 @@ function cmdStart(backend) {
|
|
|
261
401
|
}
|
|
262
402
|
|
|
263
403
|
function cmdStop(backend) {
|
|
404
|
+
if (backend === 'win32') return cmdWinStop();
|
|
264
405
|
if (backend === 'launchd') ensureRoot();
|
|
265
406
|
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
266
407
|
const target = macTarget(backend);
|
|
@@ -275,6 +416,11 @@ function cmdStop(backend) {
|
|
|
275
416
|
}
|
|
276
417
|
|
|
277
418
|
function cmdRestart(backend) {
|
|
419
|
+
if (backend === 'win32') {
|
|
420
|
+
cmdWinStop();
|
|
421
|
+
cmdWinStart();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
278
424
|
if (backend === 'launchd') ensureRoot();
|
|
279
425
|
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
280
426
|
const target = macTarget(backend);
|
|
@@ -295,6 +441,7 @@ function cmdRestart(backend) {
|
|
|
295
441
|
}
|
|
296
442
|
|
|
297
443
|
function cmdLogs(backend, mode) {
|
|
444
|
+
if (backend === 'win32') return cmdWinLogs(mode);
|
|
298
445
|
if (backend === 'systemd') ensureRoot();
|
|
299
446
|
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
300
447
|
if (mode === '-f' || mode === '--follow') {
|
|
@@ -328,7 +475,7 @@ const rest = raw.slice(1);
|
|
|
328
475
|
if (['-h', '--help', 'help'].includes(cmd)) usage(0);
|
|
329
476
|
|
|
330
477
|
const backend = detectBackend();
|
|
331
|
-
if (!backend) fail('no launchd/systemd edge service found');
|
|
478
|
+
if (!backend) fail('no launchd/systemd/windows edge service found');
|
|
332
479
|
|
|
333
480
|
switch (cmd) {
|
|
334
481
|
case 'migrate-user-agent':
|
package/client.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createApp, init, hasBusyAgent } from './src/app.js';
|
|
3
3
|
import { startAutoUpdate } from './src/auto-update.js';
|
|
4
4
|
import { WebSocket } from 'ws';
|
|
5
|
+
import { createPortWebSocketRelay } from './src/runtime/port-websocket.js';
|
|
5
6
|
import os from 'os';
|
|
6
7
|
import crypto from 'crypto';
|
|
7
8
|
import zlib from 'zlib';
|
|
@@ -14,6 +15,7 @@ const MACHINE_TOKEN = process.env.MACHINE_TOKEN; // shared secret with gateway
|
|
|
14
15
|
const LOCAL_PORT = parseInt(process.env.LOCAL_PORT || '13000');
|
|
15
16
|
const SYNC_DIR = process.env.EDGE_SYNC_DIR || path.join(os.homedir(), '.claude-simple-edge');
|
|
16
17
|
const SYNC_FILE = path.join(SYNC_DIR, 'hub-stream-outbox.json');
|
|
18
|
+
const portWebSockets = createPortWebSocketRelay(sendControl, { allowRemote: process.env.PORT_PROXY_ALLOW_REMOTE === '1' });
|
|
17
19
|
|
|
18
20
|
if (!GATEWAY_URL) { console.error('GATEWAY_URL is required'); process.exit(1); }
|
|
19
21
|
if (!MACHINE_TOKEN) { console.error('MACHINE_TOKEN is required'); process.exit(1); }
|
|
@@ -362,6 +364,7 @@ function connect() {
|
|
|
362
364
|
method: msg.method,
|
|
363
365
|
headers: reqHeaders,
|
|
364
366
|
body: reqBody,
|
|
367
|
+
redirect: 'manual',
|
|
365
368
|
});
|
|
366
369
|
const headers = sanitizeProxyHeaders(Object.fromEntries(res.headers.entries()));
|
|
367
370
|
const contentType = res.headers.get('content-type') || '';
|
|
@@ -442,6 +445,8 @@ function connect() {
|
|
|
442
445
|
return;
|
|
443
446
|
}
|
|
444
447
|
|
|
448
|
+
if (portWebSockets.handle(msg)) return;
|
|
449
|
+
|
|
445
450
|
// ── WS tunnel open ────────────────────────────────────────────────────────
|
|
446
451
|
if (msg.type === 'ws-open') {
|
|
447
452
|
const { tunnelId, path, query } = msg;
|
|
@@ -499,11 +504,12 @@ function connect() {
|
|
|
499
504
|
});
|
|
500
505
|
|
|
501
506
|
controlWs.on('close', () => {
|
|
507
|
+
portWebSockets.closeAll();
|
|
502
508
|
syncSent = new Set();
|
|
503
509
|
console.log(`[client] Disconnected. Reconnecting in ${reconnectDelay}ms ...`);
|
|
504
510
|
// Close all open tunnels
|
|
505
|
-
for (const
|
|
506
|
-
try {
|
|
511
|
+
for (const tunnel of tunnels.values()) {
|
|
512
|
+
try { tunnel.ws.terminate(); } catch {}
|
|
507
513
|
}
|
|
508
514
|
tunnels.clear();
|
|
509
515
|
setTimeout(connect, reconnectDelay);
|
package/dist/addon-fit.min.js
CHANGED
|
File without changes
|