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.
Files changed (2) hide show
  1. package/bin/orch +165 -2
  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 'nohup orch daemon &' to run in background${RESET}\n`);
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrating",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "Stream terminal sessions to the orchestrat.ing dashboard",
5
5
  "type": "module",
6
6
  "bin": {