claude-simple 0.2.19 → 0.2.21
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/bin/edge-ctl.js
CHANGED
|
@@ -12,8 +12,13 @@ import os from 'os';
|
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const DAEMON_LABEL = 'system/com.claude-simple.edge';
|
|
16
|
+
const DAEMON_PLIST = '/Library/LaunchDaemons/com.claude-simple.edge.plist';
|
|
17
|
+
const AGENT_NAME = 'com.claude-simple.edge';
|
|
18
|
+
const USER_ID = typeof process.getuid === 'function' ? process.getuid() : 0;
|
|
19
|
+
const AGENT_DOMAIN = `gui/${USER_ID}`;
|
|
20
|
+
const AGENT_LABEL = `${AGENT_DOMAIN}/${AGENT_NAME}`;
|
|
21
|
+
const AGENT_PLIST = path.join(os.homedir(), 'Library', 'LaunchAgents', `${AGENT_NAME}.plist`);
|
|
17
22
|
const SYSTEMD_UNIT = 'claude-simple-edge.service';
|
|
18
23
|
const MAC_OUT = path.join(os.homedir(), '.claude-simple-edge', 'edge.out.log');
|
|
19
24
|
const MAC_ERR = path.join(os.homedir(), '.claude-simple-edge', 'edge.err.log');
|
|
@@ -28,6 +33,7 @@ function usage(code = 0) {
|
|
|
28
33
|
logs Last 80 lines of stdout (or journal on Linux)
|
|
29
34
|
logs -f Follow logs
|
|
30
35
|
logs err Stderr only (launchd)
|
|
36
|
+
migrate-user-agent Move macOS Edge into the login session (required for Keychain-backed AGY auth)
|
|
31
37
|
|
|
32
38
|
Also installed as: claude-simple-edge, edge-daemon
|
|
33
39
|
`;
|
|
@@ -51,19 +57,79 @@ function hasCmd(cmd) {
|
|
|
51
57
|
|
|
52
58
|
function detectBackend() {
|
|
53
59
|
if (process.platform === 'darwin') {
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
// A user LaunchAgent can access the login Keychain. Prefer it over the
|
|
61
|
+
// legacy system LaunchDaemon, which intentionally cannot access it.
|
|
62
|
+
if (run('launchctl', ['print', AGENT_LABEL]).status === 0 || fs.existsSync(AGENT_PLIST)) return 'launchd-user';
|
|
63
|
+
if (fs.existsSync(DAEMON_PLIST)) return 'launchd';
|
|
64
|
+
const r = run('launchctl', ['print', DAEMON_LABEL]);
|
|
56
65
|
if (r.status === 0) return 'launchd';
|
|
57
66
|
}
|
|
58
67
|
if (hasCmd('systemctl')) {
|
|
59
68
|
const r = run('systemctl', ['cat', SYSTEMD_UNIT]);
|
|
60
69
|
if (r.status === 0) return 'systemd';
|
|
61
70
|
}
|
|
62
|
-
if (process.platform === 'darwin') return 'launchd';
|
|
71
|
+
if (process.platform === 'darwin') return 'launchd-user';
|
|
63
72
|
if (process.platform === 'linux') return 'systemd';
|
|
64
73
|
return null;
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
function macTarget(backend) {
|
|
77
|
+
return backend === 'launchd-user'
|
|
78
|
+
? { domain: AGENT_DOMAIN, label: AGENT_LABEL, plist: AGENT_PLIST, user: true }
|
|
79
|
+
: { domain: 'system', label: DAEMON_LABEL, plist: DAEMON_PLIST, user: false };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function plistEscape(value) {
|
|
83
|
+
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readDaemonEnvironment() {
|
|
87
|
+
const r = run('plutil', ['-extract', 'EnvironmentVariables', 'json', '-o', '-', DAEMON_PLIST]);
|
|
88
|
+
if (r.status !== 0) throw new Error(r.stderr || 'cannot read the existing Edge environment');
|
|
89
|
+
return JSON.parse(r.stdout);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function userAgentPlist(env) {
|
|
93
|
+
const client = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'client.js');
|
|
94
|
+
const pairs = Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) =>
|
|
95
|
+
`\t\t<key>${plistEscape(key)}</key>\n\t\t<string>${plistEscape(value)}</string>`).join('\n');
|
|
96
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n\t<key>Label</key>\n\t<string>${AGENT_NAME}</string>\n\t<key>EnvironmentVariables</key>\n\t<dict>\n${pairs}\n\t</dict>\n\t<key>KeepAlive</key>\n\t<true/>\n\t<key>ProcessType</key>\n\t<string>Background</string>\n\t<key>ProgramArguments</key>\n\t<array>\n\t\t<string>${plistEscape(process.execPath)}</string>\n\t\t<string>${plistEscape(client)}</string>\n\t</array>\n\t<key>RunAtLoad</key>\n\t<true/>\n\t<key>StandardErrorPath</key>\n\t<string>${plistEscape(MAC_ERR)}</string>\n\t<key>StandardOutPath</key>\n\t<string>${plistEscape(MAC_OUT)}</string>\n\t<key>ThrottleInterval</key>\n\t<integer>3</integer>\n\t<key>WorkingDirectory</key>\n\t<string>${plistEscape(path.join(os.homedir(), '.claude-simple-edge'))}</string>\n</dict>\n</plist>\n`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function cmdMigrateUserAgent() {
|
|
100
|
+
if (process.platform !== 'darwin') fail('user LaunchAgent migration is only needed on macOS');
|
|
101
|
+
let inherited;
|
|
102
|
+
try { inherited = readDaemonEnvironment(); }
|
|
103
|
+
catch (e) { fail(e.message); }
|
|
104
|
+
const home = os.homedir();
|
|
105
|
+
const prefix = [path.join(home, '.local', 'bin'), path.join(home, '.grok', 'bin'), process.env.PATH, inherited.PATH]
|
|
106
|
+
.filter(Boolean).join(':');
|
|
107
|
+
const env = {
|
|
108
|
+
...inherited,
|
|
109
|
+
HOME: home, USER: process.env.USER || os.userInfo().username, LOGNAME: process.env.LOGNAME || os.userInfo().username,
|
|
110
|
+
PATH: [...new Set(prefix.split(':').filter(Boolean))].join(':'),
|
|
111
|
+
};
|
|
112
|
+
const dir = path.dirname(AGENT_PLIST);
|
|
113
|
+
try {
|
|
114
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
115
|
+
const temp = `${AGENT_PLIST}.tmp-${process.pid}`;
|
|
116
|
+
fs.writeFileSync(temp, userAgentPlist(env), { mode: 0o600 });
|
|
117
|
+
// Stop the legacy daemon before the user agent claims the same loopback
|
|
118
|
+
// port. This calls the existing privileged edge command when available.
|
|
119
|
+
const stop = run(process.execPath, [fileURLToPath(import.meta.url), 'stop']);
|
|
120
|
+
if (stop.status !== 0) throw new Error('could not stop the legacy system Edge; run `claude-edge stop` once, then retry');
|
|
121
|
+
fs.renameSync(temp, AGENT_PLIST);
|
|
122
|
+
run('launchctl', ['bootout', AGENT_LABEL]); // harmless if it was not loaded
|
|
123
|
+
const boot = run('launchctl', ['bootstrap', AGENT_DOMAIN, AGENT_PLIST]);
|
|
124
|
+
if (boot.status !== 0) throw new Error(boot.stderr || 'could not bootstrap user Edge agent');
|
|
125
|
+
const start = run('launchctl', ['kickstart', '-k', AGENT_LABEL]);
|
|
126
|
+
if (start.status !== 0) throw new Error(start.stderr || 'could not start user Edge agent');
|
|
127
|
+
} catch (e) {
|
|
128
|
+
fail(`migration failed: ${e.message}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(`migrated to user LaunchAgent: ${AGENT_LABEL}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
67
133
|
function isRoot() {
|
|
68
134
|
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
69
135
|
}
|
|
@@ -125,25 +191,26 @@ function tailFile(file, n) {
|
|
|
125
191
|
}
|
|
126
192
|
|
|
127
193
|
function cmdStatus(backend) {
|
|
128
|
-
ensureRoot();
|
|
129
|
-
if (backend === 'launchd') {
|
|
130
|
-
const
|
|
194
|
+
if (backend === 'launchd') ensureRoot();
|
|
195
|
+
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
196
|
+
const target = macTarget(backend);
|
|
197
|
+
const r = runRoot('launchctl', ['print', target.label]);
|
|
131
198
|
if (r.status !== 0) {
|
|
132
|
-
console.log(`service: ${
|
|
199
|
+
console.log(`service: ${target.label}`);
|
|
133
200
|
console.log('state: not-loaded');
|
|
134
|
-
console.log(`plist: ${
|
|
201
|
+
console.log(`plist: ${target.plist}`);
|
|
135
202
|
if (r.stderr) console.log(r.stderr.trim());
|
|
136
203
|
return;
|
|
137
204
|
}
|
|
138
205
|
const info = parseLaunchdPrint(r.stdout);
|
|
139
206
|
const machine = r.stdout.match(/MACHINE_ID => (\S+)/)?.[1] || process.env.MACHINE_ID || '-';
|
|
140
|
-
console.log(`service: ${
|
|
207
|
+
console.log(`service: ${target.label}`);
|
|
141
208
|
console.log(`machine: ${machine}`);
|
|
142
209
|
console.log(`state: ${info.state}`);
|
|
143
210
|
console.log(`pid: ${info.pid}`);
|
|
144
211
|
console.log(`runs: ${info.runs}`);
|
|
145
212
|
console.log(`listen: ${listenPort()}`);
|
|
146
|
-
console.log(`plist: ${
|
|
213
|
+
console.log(`plist: ${target.plist}`);
|
|
147
214
|
console.log(`stdout: ${MAC_OUT}`);
|
|
148
215
|
console.log(`stderr: ${MAC_ERR}`);
|
|
149
216
|
const last = tailFile(MAC_OUT, 8);
|
|
@@ -174,15 +241,16 @@ function cmdStatus(backend) {
|
|
|
174
241
|
}
|
|
175
242
|
|
|
176
243
|
function cmdStart(backend) {
|
|
177
|
-
ensureRoot();
|
|
178
|
-
if (backend === 'launchd') {
|
|
179
|
-
|
|
180
|
-
|
|
244
|
+
if (backend === 'launchd') ensureRoot();
|
|
245
|
+
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
246
|
+
const target = macTarget(backend);
|
|
247
|
+
runRoot('launchctl', ['enable', target.label]);
|
|
248
|
+
const loaded = runRoot('launchctl', ['print', target.label]);
|
|
181
249
|
if (loaded.status !== 0) {
|
|
182
|
-
const b = runRoot('launchctl', ['bootstrap',
|
|
250
|
+
const b = runRoot('launchctl', ['bootstrap', target.domain, target.plist]);
|
|
183
251
|
if (b.status !== 0) fail('bootstrap failed', b.stderr || b.stdout);
|
|
184
252
|
}
|
|
185
|
-
const k = runRoot('launchctl', ['kickstart', '-k',
|
|
253
|
+
const k = runRoot('launchctl', ['kickstart', '-k', target.label]);
|
|
186
254
|
if (k.status !== 0) fail('start failed', k.stderr || k.stdout);
|
|
187
255
|
console.log('started');
|
|
188
256
|
return;
|
|
@@ -193,10 +261,11 @@ function cmdStart(backend) {
|
|
|
193
261
|
}
|
|
194
262
|
|
|
195
263
|
function cmdStop(backend) {
|
|
196
|
-
ensureRoot();
|
|
197
|
-
if (backend === 'launchd') {
|
|
198
|
-
|
|
199
|
-
runRoot('launchctl', ['
|
|
264
|
+
if (backend === 'launchd') ensureRoot();
|
|
265
|
+
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
266
|
+
const target = macTarget(backend);
|
|
267
|
+
runRoot('launchctl', ['disable', target.label]);
|
|
268
|
+
runRoot('launchctl', ['bootout', target.label]);
|
|
200
269
|
console.log('stopped');
|
|
201
270
|
return;
|
|
202
271
|
}
|
|
@@ -206,15 +275,16 @@ function cmdStop(backend) {
|
|
|
206
275
|
}
|
|
207
276
|
|
|
208
277
|
function cmdRestart(backend) {
|
|
209
|
-
ensureRoot();
|
|
210
|
-
if (backend === 'launchd') {
|
|
211
|
-
|
|
212
|
-
|
|
278
|
+
if (backend === 'launchd') ensureRoot();
|
|
279
|
+
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
280
|
+
const target = macTarget(backend);
|
|
281
|
+
runRoot('launchctl', ['enable', target.label]);
|
|
282
|
+
const loaded = runRoot('launchctl', ['print', target.label]);
|
|
213
283
|
if (loaded.status !== 0) {
|
|
214
|
-
const b = runRoot('launchctl', ['bootstrap',
|
|
284
|
+
const b = runRoot('launchctl', ['bootstrap', target.domain, target.plist]);
|
|
215
285
|
if (b.status !== 0) fail('bootstrap failed', b.stderr || b.stdout);
|
|
216
286
|
}
|
|
217
|
-
const k = runRoot('launchctl', ['kickstart', '-k',
|
|
287
|
+
const k = runRoot('launchctl', ['kickstart', '-k', target.label]);
|
|
218
288
|
if (k.status !== 0) fail('restart failed', k.stderr || k.stdout);
|
|
219
289
|
console.log('restarted');
|
|
220
290
|
return;
|
|
@@ -226,7 +296,7 @@ function cmdRestart(backend) {
|
|
|
226
296
|
|
|
227
297
|
function cmdLogs(backend, mode) {
|
|
228
298
|
if (backend === 'systemd') ensureRoot();
|
|
229
|
-
if (backend === 'launchd') {
|
|
299
|
+
if (backend === 'launchd' || backend === 'launchd-user') {
|
|
230
300
|
if (mode === '-f' || mode === '--follow') {
|
|
231
301
|
const child = spawn('tail', ['-n', '50', '-F', MAC_OUT, MAC_ERR], { stdio: 'inherit' });
|
|
232
302
|
child.on('exit', (c) => process.exit(c || 0));
|
|
@@ -261,6 +331,10 @@ const backend = detectBackend();
|
|
|
261
331
|
if (!backend) fail('no launchd/systemd edge service found');
|
|
262
332
|
|
|
263
333
|
switch (cmd) {
|
|
334
|
+
case 'migrate-user-agent':
|
|
335
|
+
case 'migrate':
|
|
336
|
+
cmdMigrateUserAgent();
|
|
337
|
+
break;
|
|
264
338
|
case 'status':
|
|
265
339
|
cmdStatus(backend);
|
|
266
340
|
break;
|
package/dist/index.html
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
|
6
6
|
<title>Claude Code</title>
|
|
7
|
-
<link rel="stylesheet" href="/style.0.2.
|
|
7
|
+
<link rel="stylesheet" href="/style.0.2.21.min.css">
|
|
8
8
|
<link rel="stylesheet" href="/highlight.min.css">
|
|
9
9
|
<script src="/marked.min.js"></script>
|
|
10
10
|
<script src="/highlight.min.js"></script>
|
|
11
11
|
</head>
|
|
12
12
|
<body class="h-dvh overflow-hidden flex flex-col bg-base-100 text-base-content">
|
|
13
13
|
<div id="root" class="flex flex-col flex-1 overflow-hidden"></div>
|
|
14
|
-
<script type="module" src="/app.0.2.
|
|
14
|
+
<script type="module" src="/app.0.2.21.min.js"></script>
|
|
15
15
|
</body>
|
|
16
16
|
</html>
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -44,10 +44,22 @@ const CREDENTIALS_FILE = process.env.CREDENTIALS_FILE || path.join(rootDir, '.
|
|
|
44
44
|
const WORKSPACES_FILE = process.env.WORKSPACES_FILE || path.join(rootDir, '.workspaces.json');
|
|
45
45
|
const PROJECT_NOTES_FILE = process.env.PROJECT_NOTES_FILE || path.join(rootDir, '.project_notes.json');
|
|
46
46
|
const SESSION_META_FILE = process.env.SESSION_META_FILE || path.join(rootDir, '.session_meta.json');
|
|
47
|
+
// launchd/systemd have a deliberately minimal PATH. Prefer a user-local binary
|
|
48
|
+
// for bare command names so agents installed by their own updaters keep working
|
|
49
|
+
// without requiring the service unit to mirror an interactive shell PATH.
|
|
50
|
+
function resolveUserLocalCommand(configured, fallback) {
|
|
51
|
+
const command = configured || fallback;
|
|
52
|
+
if (command.includes('/') || command.includes('\\')) return command.replace(/^~/, os.homedir());
|
|
53
|
+
for (const dir of [path.join(os.homedir(), '.local', 'bin'), path.join(os.homedir(), 'bin')]) {
|
|
54
|
+
const candidate = path.join(dir, command);
|
|
55
|
+
if (fsSync.existsSync(candidate)) return candidate;
|
|
56
|
+
}
|
|
57
|
+
return command;
|
|
58
|
+
}
|
|
47
59
|
const CLAUDE_CLI_PATH = process.env.CLAUDE_CLI_PATH || 'claude';
|
|
48
60
|
const CODEX_CLI_PATH = process.env.CODEX_CLI_PATH || 'codex';
|
|
49
61
|
const GROK_CLI_PATH = process.env.GROK_CLI_PATH || 'grok';
|
|
50
|
-
const GEMINI_CLI_PATH = process.env.GEMINI_CLI_PATH || process.env.AGY_PATH
|
|
62
|
+
const GEMINI_CLI_PATH = resolveUserLocalCommand(process.env.GEMINI_CLI_PATH || process.env.AGY_PATH, 'agy');
|
|
51
63
|
const CURSOR_AGENT_PATH = process.env.CURSOR_AGENT_PATH || 'cursor-agent';
|
|
52
64
|
const CODEX_HOME = (process.env.CODEX_HOME || path.join(os.homedir(), '.codex')).replace(/^~/, os.homedir());
|
|
53
65
|
const GROK_HOME = (process.env.GROK_HOME || path.join(os.homedir(), '.grok')).replace(/^~/, os.homedir());
|
|
File without changes
|
|
File without changes
|