orchestrating 0.1.33 → 0.1.35
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/orch +165 -2
- package/package.json +1 -1
package/bin/orch
CHANGED
|
@@ -243,14 +243,177 @@ if (firstArg === "logout") {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
if (firstArg === "daemon") {
|
|
246
|
-
// Parse daemon flags: orch daemon [--projects <path>]
|
|
247
246
|
const daemonArgs = process.argv.slice(3);
|
|
247
|
+
const pidFile = path.join(os.homedir(), ".orch-daemon.pid");
|
|
248
|
+
|
|
249
|
+
// orch daemon --stop
|
|
250
|
+
if (daemonArgs.includes("--stop")) {
|
|
251
|
+
if (existsSync(pidFile)) {
|
|
252
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
253
|
+
try {
|
|
254
|
+
process.kill(pid, "SIGTERM");
|
|
255
|
+
unlinkSync(pidFile);
|
|
256
|
+
console.log(`Daemon stopped (pid ${pid}).`);
|
|
257
|
+
} catch {
|
|
258
|
+
unlinkSync(pidFile);
|
|
259
|
+
console.log("Daemon was not running. Cleaned up pid file.");
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
console.log("No daemon running.");
|
|
263
|
+
}
|
|
264
|
+
process.exit(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// orch daemon --status
|
|
268
|
+
if (daemonArgs.includes("--status")) {
|
|
269
|
+
if (existsSync(pidFile)) {
|
|
270
|
+
const pid = parseInt(readFileSync(pidFile, "utf-8").trim(), 10);
|
|
271
|
+
try {
|
|
272
|
+
process.kill(pid, 0); // Check if process exists
|
|
273
|
+
console.log(`Daemon running (pid ${pid}).`);
|
|
274
|
+
} catch {
|
|
275
|
+
console.log("Daemon not running (stale pid file).");
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
console.log("Daemon not running.");
|
|
279
|
+
}
|
|
280
|
+
process.exit(0);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Parse flags
|
|
248
284
|
let projectsDir = null;
|
|
285
|
+
const background = daemonArgs.includes("--background") || daemonArgs.includes("-b");
|
|
249
286
|
for (let di = 0; di < daemonArgs.length; di++) {
|
|
250
287
|
if ((daemonArgs[di] === "--projects" || daemonArgs[di] === "-d") && daemonArgs[di + 1]) {
|
|
251
288
|
projectsDir = daemonArgs[++di];
|
|
252
289
|
}
|
|
253
290
|
}
|
|
291
|
+
|
|
292
|
+
// orch daemon --enable — register as system service (survives reboot)
|
|
293
|
+
if (daemonArgs.includes("--enable")) {
|
|
294
|
+
const orchBin = process.argv[1] || execSync("which orch", { encoding: "utf-8" }).trim();
|
|
295
|
+
const nodeBin = process.execPath;
|
|
296
|
+
let extraArgs = "";
|
|
297
|
+
if (projectsDir) extraArgs = ` --projects ${projectsDir}`;
|
|
298
|
+
|
|
299
|
+
if (process.platform === "darwin") {
|
|
300
|
+
// macOS: launchd plist
|
|
301
|
+
const plistDir = path.join(os.homedir(), "Library", "LaunchAgents");
|
|
302
|
+
const plistPath = path.join(plistDir, "ing.orchestrat.daemon.plist");
|
|
303
|
+
mkdirSync(plistDir, { recursive: true });
|
|
304
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
305
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
306
|
+
<plist version="1.0">
|
|
307
|
+
<dict>
|
|
308
|
+
<key>Label</key>
|
|
309
|
+
<string>ing.orchestrat.daemon</string>
|
|
310
|
+
<key>ProgramArguments</key>
|
|
311
|
+
<array>
|
|
312
|
+
<string>${nodeBin}</string>
|
|
313
|
+
<string>${orchBin}</string>
|
|
314
|
+
<string>daemon</string>${projectsDir ? `\n <string>--projects</string>\n <string>${projectsDir}</string>` : ""}
|
|
315
|
+
</array>
|
|
316
|
+
<key>RunAtLoad</key>
|
|
317
|
+
<true/>
|
|
318
|
+
<key>KeepAlive</key>
|
|
319
|
+
<true/>
|
|
320
|
+
<key>StandardOutPath</key>
|
|
321
|
+
<string>${path.join(os.homedir(), ".orch-daemon.log")}</string>
|
|
322
|
+
<key>StandardErrorPath</key>
|
|
323
|
+
<string>${path.join(os.homedir(), ".orch-daemon.log")}</string>
|
|
324
|
+
<key>EnvironmentVariables</key>
|
|
325
|
+
<dict>
|
|
326
|
+
<key>PATH</key>
|
|
327
|
+
<string>${process.env.PATH}</string>
|
|
328
|
+
</dict>
|
|
329
|
+
</dict>
|
|
330
|
+
</plist>`;
|
|
331
|
+
writeFileSync(plistPath, plist);
|
|
332
|
+
execSync(`launchctl load -w "${plistPath}"`);
|
|
333
|
+
console.log("Daemon enabled — will start on login and auto-restart.");
|
|
334
|
+
console.log(`Logs: ~/.orch-daemon.log`);
|
|
335
|
+
console.log(`Disable: orch daemon --disable`);
|
|
336
|
+
} else {
|
|
337
|
+
// Linux: systemd user service
|
|
338
|
+
const serviceDir = path.join(os.homedir(), ".config", "systemd", "user");
|
|
339
|
+
const servicePath = path.join(serviceDir, "orch-daemon.service");
|
|
340
|
+
mkdirSync(serviceDir, { recursive: true });
|
|
341
|
+
const service = `[Unit]
|
|
342
|
+
Description=orchestrat.ing daemon
|
|
343
|
+
After=network.target
|
|
344
|
+
|
|
345
|
+
[Service]
|
|
346
|
+
ExecStart=${nodeBin} ${orchBin} daemon${extraArgs}
|
|
347
|
+
Restart=always
|
|
348
|
+
RestartSec=5
|
|
349
|
+
Environment=PATH=${process.env.PATH}
|
|
350
|
+
|
|
351
|
+
[Install]
|
|
352
|
+
WantedBy=default.target
|
|
353
|
+
`;
|
|
354
|
+
writeFileSync(servicePath, service);
|
|
355
|
+
execSync("systemctl --user daemon-reload");
|
|
356
|
+
execSync("systemctl --user enable --now orch-daemon.service");
|
|
357
|
+
// Enable lingering so user services run without active login session
|
|
358
|
+
try { execSync(`loginctl enable-linger ${os.userInfo().username}`); } catch {}
|
|
359
|
+
console.log("Daemon enabled — will start on boot and auto-restart.");
|
|
360
|
+
console.log(`Logs: journalctl --user -u orch-daemon -f`);
|
|
361
|
+
console.log(`Disable: orch daemon --disable`);
|
|
362
|
+
}
|
|
363
|
+
process.exit(0);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// orch daemon --disable — remove system service
|
|
367
|
+
if (daemonArgs.includes("--disable")) {
|
|
368
|
+
if (process.platform === "darwin") {
|
|
369
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "ing.orchestrat.daemon.plist");
|
|
370
|
+
if (existsSync(plistPath)) {
|
|
371
|
+
try { execSync(`launchctl unload "${plistPath}"`); } catch {}
|
|
372
|
+
unlinkSync(plistPath);
|
|
373
|
+
console.log("Daemon disabled — removed from login items.");
|
|
374
|
+
} else {
|
|
375
|
+
console.log("Daemon was not enabled.");
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
const servicePath = path.join(os.homedir(), ".config", "systemd", "user", "orch-daemon.service");
|
|
379
|
+
if (existsSync(servicePath)) {
|
|
380
|
+
try { execSync("systemctl --user disable --now orch-daemon.service"); } catch {}
|
|
381
|
+
unlinkSync(servicePath);
|
|
382
|
+
execSync("systemctl --user daemon-reload");
|
|
383
|
+
console.log("Daemon disabled — removed systemd service.");
|
|
384
|
+
} else {
|
|
385
|
+
console.log("Daemon was not enabled.");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
process.exit(0);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// orch daemon --background — fork and exit
|
|
392
|
+
if (background) {
|
|
393
|
+
const orchPath = fileURLToPath(import.meta.url);
|
|
394
|
+
const fwdArgs = ["daemon"];
|
|
395
|
+
if (projectsDir) fwdArgs.push("--projects", projectsDir);
|
|
396
|
+
const logFile = path.join(os.homedir(), ".orch-daemon.log");
|
|
397
|
+
const out = (await import("fs")).openSync(logFile, "a");
|
|
398
|
+
const child = spawn(process.execPath, [orchPath, ...fwdArgs], {
|
|
399
|
+
stdio: ["ignore", out, out],
|
|
400
|
+
detached: true,
|
|
401
|
+
env: { ...process.env },
|
|
402
|
+
});
|
|
403
|
+
child.unref();
|
|
404
|
+
writeFileSync(pidFile, String(child.pid));
|
|
405
|
+
console.log(`Daemon started in background (pid ${child.pid}).`);
|
|
406
|
+
console.log(`Logs: ${logFile}`);
|
|
407
|
+
console.log(`Stop: orch daemon --stop`);
|
|
408
|
+
process.exit(0);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Foreground — write pid file for --stop/--status
|
|
412
|
+
writeFileSync(pidFile, String(process.pid));
|
|
413
|
+
process.on("exit", () => { try { unlinkSync(pidFile); } catch {} });
|
|
414
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
415
|
+
process.on("SIGINT", () => process.exit(0));
|
|
416
|
+
|
|
254
417
|
await handleDaemon(projectsDir);
|
|
255
418
|
// handleDaemon runs forever (or exits on fatal error)
|
|
256
419
|
}
|
|
@@ -309,7 +472,7 @@ async function handleDaemon(projectsDir) {
|
|
|
309
472
|
|
|
310
473
|
process.stderr.write(`${GREEN}${PREFIX} Listening for remote sessions as "${hostname}"${RESET}\n`);
|
|
311
474
|
process.stderr.write(`${DIM}${PREFIX} Projects: ${scanRoot} (${directories.length} dirs)${RESET}\n`);
|
|
312
|
-
process.stderr.write(`${DIM}${PREFIX} Tip: Use '
|
|
475
|
+
process.stderr.write(`${DIM}${PREFIX} Tip: Use 'orch daemon -b' to run in background${RESET}\n`);
|
|
313
476
|
|
|
314
477
|
let reconnecting = false;
|
|
315
478
|
const recentRequests = new Set();
|