claude-simple 0.2.28 → 0.2.31

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.
Files changed (77) hide show
  1. package/README.md +17 -1
  2. package/bin/edge-ctl.js +151 -4
  3. package/client.js +8 -2
  4. package/dist/addon-fit.min.js +0 -0
  5. package/dist/app.0.2.31.min.js +1308 -0
  6. package/dist/index.html +2 -2
  7. package/dist/style.0.2.31.min.css +2012 -0
  8. package/dist/xterm.min.css +0 -0
  9. package/dist/xterm.min.js +0 -0
  10. package/gateway.js +0 -0
  11. package/package.json +2 -2
  12. package/public/addon-fit.min.js +0 -0
  13. package/public/agent-panel.js +47 -20
  14. package/public/api.js +1 -1
  15. package/public/app.js +5 -5
  16. package/public/base.css +0 -0
  17. package/public/cache.js +19 -0
  18. package/public/chat.js +80 -19
  19. package/public/files.js +26 -10
  20. package/public/git.js +2 -2
  21. package/public/icons/icon-180.png +0 -0
  22. package/public/icons/icon-192.png +0 -0
  23. package/public/icons/icon-512.png +0 -0
  24. package/public/icons/icon-maskable-512.png +0 -0
  25. package/public/index.html +2 -2
  26. package/public/lib.js +0 -0
  27. package/public/manifest.webmanifest +0 -0
  28. package/public/markdown.js +16 -0
  29. package/public/math.js +145 -7
  30. package/public/mobile.js +0 -0
  31. package/public/notifications.js +0 -0
  32. package/public/offline.html +0 -0
  33. package/public/pwa.js +1 -1
  34. package/public/router.js +0 -0
  35. package/public/scroll-follow.js +78 -0
  36. package/public/settings.js +41 -0
  37. package/public/shell/boot.js +0 -0
  38. package/public/shell/dashboard.js +0 -0
  39. package/public/shell/hub.js +0 -0
  40. package/public/shell/layout.js +0 -0
  41. package/public/shell/notes.js +0 -0
  42. package/public/shell/session-context.js +0 -0
  43. package/public/shell/session-list.js +49 -16
  44. package/public/shell/session-nav.js +66 -18
  45. package/public/shell/templates.js +0 -0
  46. package/public/shell/util.js +0 -0
  47. package/public/shell.js +0 -0
  48. package/public/state.js +7 -7
  49. package/public/style.css +5 -0
  50. package/public/sw.js +10 -5
  51. package/public/terminal.js +0 -0
  52. package/public/wake-lock.js +0 -0
  53. package/public/workspace-path.js +61 -0
  54. package/public/xterm.min.css +0 -0
  55. package/public/xterm.min.js +0 -0
  56. package/src/agent-quota.js +228 -0
  57. package/src/agents/claude.js +0 -0
  58. package/src/agents/codex.js +2 -0
  59. package/src/agents/cursor.js +2 -1
  60. package/src/agents/gemini.js +2 -1
  61. package/src/agents/grok.js +6 -1
  62. package/src/agents/meta-agent.js +7 -4
  63. package/src/agents/models.js +21 -12
  64. package/src/agents/sessions.js +133 -69
  65. package/src/agents/stream-batch.js +0 -0
  66. package/src/app.js +80 -36
  67. package/src/auto-update.js +2 -0
  68. package/src/collab.js +3 -0
  69. package/src/platform.js +133 -0
  70. package/src/project-api-path.js +14 -0
  71. package/src/runtime/approvals.js +0 -0
  72. package/src/runtime/chat-broadcast.js +12 -0
  73. package/src/runtime/outbox.js +0 -0
  74. package/src/runtime/port-websocket.js +80 -0
  75. package/src/runtime/protocol.js +0 -0
  76. package/dist/app.0.2.28.min.js +0 -1220
  77. package/dist/style.0.2.28.min.css +0 -2007
@@ -0,0 +1,133 @@
1
+ /**
2
+ * OS helpers so Edge can run on Windows as well as macOS/Linux.
3
+ * Unix remains the default; Windows gets cmd/PowerShell, .cmd spawn, and
4
+ * case-insensitive path checks.
5
+ */
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import fs from 'fs';
9
+
10
+ export function isWin(platform = process.platform) {
11
+ return platform === 'win32';
12
+ }
13
+
14
+ export function expandHome(p, home = os.homedir()) {
15
+ if (!p) return p;
16
+ if (p === '~') return home;
17
+ if (p.startsWith('~/') || p.startsWith('~\\')) return path.join(home, p.slice(2));
18
+ return p;
19
+ }
20
+
21
+ function normalizeForCompare(p, platform) {
22
+ let s = String(p);
23
+ if (isWin(platform)) s = s.replace(/[/\\]+/g, '\\').replace(/\\+$/, '');
24
+ else s = path.normalize(s);
25
+ return isWin(platform) ? s.toLowerCase() : s;
26
+ }
27
+
28
+ export function pathsEqual(a, b, platform = process.platform) {
29
+ if (a == null || b == null) return a === b;
30
+ return normalizeForCompare(a, platform) === normalizeForCompare(b, platform);
31
+ }
32
+
33
+ /** True if `resolved` is `base` or a descendant (not a sibling prefix). */
34
+ export function containedPath(base, resolved, platform = process.platform) {
35
+ const b = path.resolve(base);
36
+ const r = path.resolve(resolved);
37
+ const rel = path.relative(b, r);
38
+ if (!rel) return true;
39
+ if (path.isAbsolute(rel)) return false;
40
+ const first = rel.split(/[/\\]/)[0];
41
+ if (first === '..') return false;
42
+ if (isWin(platform) && rel.toLowerCase().startsWith('..')) return false;
43
+ return true;
44
+ }
45
+
46
+ export function defaultInteractiveShell({ platform = process.platform, env = process.env } = {}) {
47
+ if (isWin(platform)) {
48
+ const comspec = env.ComSpec || env.COMSPEC;
49
+ const shellEnv = env.SHELL || '';
50
+ // Git Bash sets SHELL=...\bash.exe; Edge still wants a native Windows shell.
51
+ if (shellEnv && /\.(exe|cmd|bat)$/i.test(shellEnv) && !/(^|[\\/])(bash|zsh|sh|dash|fish)(\.exe)?$/i.test(shellEnv)) {
52
+ return { bin: shellEnv, args: [] };
53
+ }
54
+ const sysroot = env.SystemRoot || env.SYSTEMROOT || 'C:\\Windows';
55
+ const ps = path.join(sysroot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
56
+ if (fs.existsSync(ps)) {
57
+ return { bin: ps, args: ['-NoLogo', '-NoExit'] };
58
+ }
59
+ return { bin: comspec || path.join(sysroot, 'System32', 'cmd.exe'), args: [] };
60
+ }
61
+ return { bin: env.SHELL || 'zsh', args: ['-i'] };
62
+ }
63
+
64
+ export function shellExecSpec(command, { platform = process.platform, env = process.env } = {}) {
65
+ if (isWin(platform)) {
66
+ const sysroot = env.SystemRoot || env.SYSTEMROOT || 'C:\\Windows';
67
+ const cmd = env.ComSpec || env.COMSPEC || path.join(sysroot, 'System32', 'cmd.exe');
68
+ return { bin: cmd, args: ['/d', '/s', '/c', command] };
69
+ }
70
+ const bin = env.SHELL || 'bash';
71
+ return { bin, args: ['-c', command] };
72
+ }
73
+
74
+ export function spawnCommandOpts({ platform = process.platform, extra = {} } = {}) {
75
+ if (isWin(platform)) {
76
+ return { shell: true, windowsHide: true, ...extra };
77
+ }
78
+ return { shell: false, ...extra };
79
+ }
80
+
81
+ export function processKillSignal(platform = process.platform) {
82
+ return isWin(platform) ? 'SIGTERM' : 'SIGHUP';
83
+ }
84
+
85
+ export function userLocalBinDirs(home = os.homedir(), platform = process.platform) {
86
+ const dirs = [
87
+ path.join(home, '.local', 'bin'),
88
+ path.join(home, 'bin'),
89
+ path.join(home, '.grok', 'bin'),
90
+ ];
91
+ if (isWin(platform)) {
92
+ dirs.push(
93
+ path.join(home, 'AppData', 'Roaming', 'npm'),
94
+ path.join(home, 'AppData', 'Local', 'npm'),
95
+ );
96
+ }
97
+ return dirs;
98
+ }
99
+
100
+ /**
101
+ * Resolve a bare command to an absolute .exe/.cmd on Windows so agents can be
102
+ * spawned with stdio pipes and shell:false. Unix returns the name unchanged.
103
+ */
104
+ export function resolveWinExecutable(command, {
105
+ platform = process.platform,
106
+ env = process.env,
107
+ extraDirs = [],
108
+ exists = fs.existsSync,
109
+ } = {}) {
110
+ if (!command) return command;
111
+ if (!isWin(platform)) return command;
112
+ if (command.includes('/') || command.includes('\\') || /\.[a-z0-9]+$/i.test(command)) {
113
+ return expandHome(command, env.USERPROFILE || env.HOME);
114
+ }
115
+ const exts = String(env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
116
+ .split(';')
117
+ .map(e => e.trim())
118
+ .filter(Boolean);
119
+ const pathDirs = String(env.PATH || '').split(';').map(d => d.trim()).filter(Boolean);
120
+ const dirs = [...extraDirs, ...pathDirs];
121
+ for (const dir of dirs) {
122
+ for (const ext of ['', ...exts]) {
123
+ const candidate = path.join(dir, command + ext);
124
+ if (exists(candidate)) return candidate;
125
+ }
126
+ }
127
+ return command;
128
+ }
129
+
130
+ export function spawnAgentOpts({ platform = process.platform } = {}) {
131
+ if (isWin(platform)) return { shell: false, windowsHide: true };
132
+ return { shell: false };
133
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Files/git APIs take the real directory in ?root=. The :id path segment is
3
+ * unused. Clients that put a Windows cwd (C:\\Users\\... or C:/Users/...) into
4
+ * :id make extra slashes; Hub/Express then 404. Collapse those to `_`.
5
+ */
6
+ export function canonicalizeProjectApiPath(pathname) {
7
+ const m = String(pathname || '').match(
8
+ /^(\/api\/projects\/)(.+)\/(files|file|git\/(?:status|diff|log))$/,
9
+ );
10
+ if (!m) return pathname;
11
+ const id = m[2];
12
+ if (!/[\\/:]/.test(id)) return pathname;
13
+ return `${m[1]}_/${m[3]}`;
14
+ }
File without changes
@@ -0,0 +1,12 @@
1
+ // The run owner (including a Hub task tunnel) must receive every frame even
2
+ // when it has not subscribed to the newly assigned native session ID.
3
+ export function broadcastChatFrame(clients, sessionId, data, ownerWs, send) {
4
+ const recipients = new Set();
5
+ if (ownerWs) recipients.add(ownerWs);
6
+ if (sessionId) {
7
+ for (const client of clients) {
8
+ if (client.sessionId === sessionId) recipients.add(client.ws);
9
+ }
10
+ }
11
+ for (const ws of recipients) send(ws, data);
12
+ }
File without changes
@@ -0,0 +1,80 @@
1
+ import { WebSocket } from 'ws';
2
+ import { isLoopbackHost } from '../collab.js';
3
+
4
+ const MAX_FRAME = 16 * 1024 * 1024;
5
+ const validClose = code => code === 1000 || code === 1001 || code === 1002 || code === 1003 ||
6
+ (code >= 1007 && code <= 1014) || (code >= 3000 && code <= 4999);
7
+
8
+ // Separate from agent tunnels: arbitrary application frames must remain opaque.
9
+ export function createPortWebSocketRelay(send, { allowRemote = false } = {}) {
10
+ const sockets = new Map();
11
+ function handle(msg) {
12
+ if (!msg.type?.startsWith('port-ws-')) return false;
13
+ const id = msg.tunnelId;
14
+ if (msg.type === 'port-ws-open') {
15
+ if (sockets.has(id)) return true;
16
+ try {
17
+ const params = new URLSearchParams(msg.query);
18
+ const host = params.get('host') || '127.0.0.1';
19
+ const port = Number(params.get('port'));
20
+ if ((!allowRemote && !isLoopbackHost(host)) || !Number.isInteger(port) || port < 1 || port > 65535) throw new Error('invalid target');
21
+ const url = new URL(`ws://${host.includes(':') ? `[${host}]` : host}:${port}`);
22
+ url.pathname = params.get('path') || '/';
23
+ url.search = params.get('q') || '';
24
+ const headers = {};
25
+ for (const name of ['Cookie', 'Origin', 'User-Agent']) {
26
+ if (typeof msg.headers?.[name] === 'string') headers[name] = msg.headers[name];
27
+ }
28
+ const ws = new WebSocket(url, msg.protocols || [], { headers, handshakeTimeout: 15000, maxPayload: MAX_FRAME, followRedirects: false });
29
+ const entry = { ws, timer: null };
30
+ sockets.set(id, entry);
31
+ ws.on('open', () => {
32
+ ws.pause();
33
+ entry.timer = setTimeout(() => ws.terminate(), 15000);
34
+ send({ type: 'port-ws-ready', tunnelId: id, protocol: ws.protocol });
35
+ });
36
+ ws.on('message', (data, binary) => {
37
+ send({ type: 'port-ws-msg', tunnelId: id, frameType: binary ? 2 : 1, data: data.toString('base64') });
38
+ });
39
+ ws.on('close', code => {
40
+ clearTimeout(entry.timer);
41
+ if (sockets.get(id) === entry) sockets.delete(id);
42
+ send({ type: 'port-ws-close', tunnelId: id, closeCode: validClose(code) ? code : 1011 });
43
+ });
44
+ ws.on('error', () => {
45
+ send({ type: 'port-ws-error', tunnelId: id });
46
+ ws.terminate();
47
+ });
48
+ } catch {
49
+ send({ type: 'port-ws-error', tunnelId: id });
50
+ }
51
+ return true;
52
+ }
53
+ const entry = sockets.get(id);
54
+ if (!entry) return true;
55
+ if (msg.type === 'port-ws-start') {
56
+ clearTimeout(entry.timer);
57
+ entry.ws.resume();
58
+ } else if (msg.type === 'port-ws-msg' && entry.ws.readyState === WebSocket.OPEN) {
59
+ if (![1, 2].includes(msg.frameType) || typeof msg.data !== 'string' || msg.data.length > MAX_FRAME * 4 / 3 + 4) {
60
+ entry.ws.terminate();
61
+ } else if (entry.ws.bufferedAmount > MAX_FRAME) {
62
+ entry.ws.close(1013, 'backpressure');
63
+ } else {
64
+ entry.ws.send(Buffer.from(msg.data, 'base64'), { binary: msg.frameType === 2 });
65
+ }
66
+ } else if (msg.type === 'port-ws-close') {
67
+ clearTimeout(entry.timer);
68
+ if (entry.ws.readyState === WebSocket.OPEN) entry.ws.close(validClose(msg.closeCode) ? msg.closeCode : 1000);
69
+ else entry.ws.terminate();
70
+ }
71
+ return true;
72
+ }
73
+ return {
74
+ handle,
75
+ closeAll() {
76
+ for (const { ws, timer } of sockets.values()) { clearTimeout(timer); ws.terminate(); }
77
+ sockets.clear();
78
+ },
79
+ };
80
+ }
File without changes