project-cypher 0.17.0 → 0.17.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/bin/cypher.js +10 -6
- package/package.json +1 -1
package/bin/cypher.js
CHANGED
|
@@ -56,6 +56,7 @@ const devName = () => rawCfg().name || os.hostname();
|
|
|
56
56
|
|
|
57
57
|
// ── Background service (auto-start at boot/login, restarts on crash, waits for internet) ──
|
|
58
58
|
const LOG = path.join(CFG_DIR, 'agent.log'), SELF = [process.execPath, path.resolve(__filename), 'connect'];
|
|
59
|
+
const SVC_PATH = [...new Set([path.dirname(process.execPath), ...(process.env.PATH || '').split(path.delimiter), '/opt/homebrew/bin', '/usr/local/bin', path.join(os.homedir(), '.local/bin'), '/usr/bin', '/bin', '/usr/sbin', '/sbin'])].filter(Boolean).join(path.delimiter);
|
|
59
60
|
const sh = (cmd, args) => require('child_process').spawnSync(cmd, args, { encoding: 'utf8' });
|
|
60
61
|
const SVC = {
|
|
61
62
|
kind: process.platform === 'darwin' ? 'launchd' : process.platform === 'win32' ? 'task' : 'systemd',
|
|
@@ -65,13 +66,13 @@ const SVC = {
|
|
|
65
66
|
fs.mkdirSync(CFG_DIR, { recursive: true });
|
|
66
67
|
if (this.kind === 'systemd') {
|
|
67
68
|
fs.mkdirSync(path.dirname(this.unit), { recursive: true });
|
|
68
|
-
fs.writeFileSync(this.unit, `[Unit]\nDescription=Project Cypher agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nExecStart=${SELF.map(x => `"${x}"`).join(' ')}\nRestart=on-failure\nRestartSec=5\nStandardOutput=append:${LOG}\nStandardError=append:${LOG}\n\n[Install]\nWantedBy=default.target\n`);
|
|
69
|
+
fs.writeFileSync(this.unit, `[Unit]\nDescription=Project Cypher agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nEnvironment="PATH=${SVC_PATH}"\nExecStart=${SELF.map(x => `"${x}"`).join(' ')}\nRestart=on-failure\nRestartSec=5\nStandardOutput=append:${LOG}\nStandardError=append:${LOG}\n\n[Install]\nWantedBy=default.target\n`);
|
|
69
70
|
sh('systemctl', ['--user', 'daemon-reload']); sh('loginctl', ['enable-linger', os.userInfo().username]); // linger = start at boot even before login
|
|
70
71
|
return sh('systemctl', ['--user', 'enable', 'cypher.service']).status === 0;
|
|
71
72
|
}
|
|
72
73
|
if (this.kind === 'launchd') {
|
|
73
74
|
fs.mkdirSync(path.dirname(this.plist), { recursive: true });
|
|
74
|
-
fs.writeFileSync(this.plist, `<?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"><dict><key>Label</key><string>com.cypher.agent</string><key>ProgramArguments</key><array>${SELF.map(x => `<string>${x}</string>`).join('')}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>StandardOutPath</key><string>${LOG}</string><key>StandardErrorPath</key><string>${LOG}</string></dict></plist>\n`);
|
|
75
|
+
fs.writeFileSync(this.plist, `<?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"><dict><key>Label</key><string>com.cypher.agent</string><key>ProgramArguments</key><array>${SELF.map(x => `<string>${x}</string>`).join('')}</array><key>EnvironmentVariables</key><dict><key>PATH</key><string>${SVC_PATH}</string><key>HOME</key><string>${os.homedir()}</string></dict><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>StandardOutPath</key><string>${LOG}</string><key>StandardErrorPath</key><string>${LOG}</string></dict></plist>\n`);
|
|
75
76
|
return true;
|
|
76
77
|
}
|
|
77
78
|
return sh('schtasks', ['/create', '/f', '/tn', 'ProjectCypher', '/sc', 'onlogon', '/rl', 'limited', '/tr', `cmd /c ""${SELF[0]}" "${SELF[1]}" connect >> "${LOG}" 2>&1"`]).status === 0;
|
|
@@ -277,8 +278,11 @@ const cmds = {
|
|
|
277
278
|
// pick up `cypher add` / `remove` while running: re-announce projects when the config changes
|
|
278
279
|
let sig = JSON.stringify(announce());
|
|
279
280
|
const watch = setInterval(() => { const a = announce(), n = JSON.stringify(a); if (n !== sig) { sig = n; tx({ type: 'hello', name: devName(), projects: a }); console.log(`projects updated: ${a.length - 1}`); } }, 2000);
|
|
280
|
-
|
|
281
|
-
ws.on('
|
|
281
|
+
// One reconnect per socket, whatever ended it (close, 502 from the proxy, dead connection).
|
|
282
|
+
let lastPong = Date.now(); ws.on('pong', () => (lastPong = Date.now())); ws.on('ping', () => (lastPong = Date.now())); ws.on('message', () => (lastPong = Date.now()));
|
|
283
|
+
const hb = setInterval(() => { if (ws.readyState !== 1) return; if (Date.now() - lastPong > 70000) { console.log('no answer from the server for 70s, reconnecting'); ws.terminate(); return again(); } try { ws.ping(); } catch {} }, 25000);
|
|
284
|
+
const again = why => { if (ws._re) return; ws._re = true; clearInterval(watch); clearInterval(hb); if (why) console.log(why); setTimeout(open, retry); retry = Math.min(retry * 2, 15000); };
|
|
285
|
+
ws.on('unexpected-response', (req, res) => { if (res.statusCode === 401) { dropToken('This computer is not linked (removed from the app). Run cypher init to link again.'); process.exit(0); } else { console.log(`server answered ${res.statusCode}, retrying...`); again(); req.destroy(); } });
|
|
282
286
|
ws.on('message', raw => {
|
|
283
287
|
let m; try { m = JSON.parse(raw); } catch { return; }
|
|
284
288
|
if (m.type === 'revoked') { runs.forEach(r => r.kill('SIGINT')); dropToken('Disconnected from the app. Run cypher init to link again.'); process.exit(0); }
|
|
@@ -330,8 +334,8 @@ const cmds = {
|
|
|
330
334
|
ch.on('close', code => { runs.delete(proj.id); pending.forEach((p, id) => p.msg.reqId === m.reqId && settle(id, false, 'Run ended.')); if (ch.finished) return;
|
|
331
335
|
if (ch.stopped) tx({ ...ctx, type: 'done', stopped: true }); else tx({ ...ctx, type: 'error', message: (err.trim().split('\n').pop() || 'claude exited ' + code).slice(0, 200) }); });
|
|
332
336
|
});
|
|
333
|
-
ws.on('close', () =>
|
|
334
|
-
ws.on('error', e => console.log('connection error: ' + e.message));
|
|
337
|
+
ws.on('close', () => again(ws._re ? '' : 'disconnected, retrying...'));
|
|
338
|
+
ws.on('error', e => { console.log('connection error: ' + e.message); if (ws.readyState !== 1) again(); });
|
|
335
339
|
};
|
|
336
340
|
open();
|
|
337
341
|
},
|