relife2 1.0.0 → 1.0.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/dist/cli.js CHANGED
@@ -14,13 +14,13 @@ var jsloader_exports = {};
14
14
  __export(jsloader_exports, {
15
15
  loadJsFile: () => loadJsFile
16
16
  });
17
- import * as fs5 from "node:fs";
17
+ import * as fs7 from "node:fs";
18
18
  import { createRequire } from "node:module";
19
- import path6 from "node:path";
19
+ import path7 from "node:path";
20
20
  import { pathToFileURL } from "node:url";
21
21
  async function loadJsFile(file) {
22
- const ext = path6.extname(file).toLowerCase();
23
- const source = fs5.readFileSync(file, "utf8");
22
+ const ext = path7.extname(file).toLowerCase();
23
+ const source = fs7.readFileSync(file, "utf8");
24
24
  if (ext === ".mjs") return importFreshEs(file);
25
25
  if (ext === ".cjs") return loadCjs(file, source);
26
26
  if (CJS_MARKER.test(source)) return loadCjs(file, source);
@@ -51,7 +51,7 @@ function compileCjs(file, source) {
51
51
  const nodeModule = req("node:module");
52
52
  const mod = new nodeModule.Module(file, null);
53
53
  mod.filename = file;
54
- mod.paths = nodeModule._nodeModulePaths(path6.dirname(file));
54
+ mod.paths = nodeModule._nodeModulePaths(path7.dirname(file));
55
55
  mod._compile(source, file);
56
56
  return mod.exports;
57
57
  }
@@ -65,6 +65,7 @@ var init_jsloader = __esm({
65
65
 
66
66
  // src/daemon/client.ts
67
67
  import { spawn } from "node:child_process";
68
+ import fs3 from "node:fs";
68
69
  import net from "node:net";
69
70
  import path3 from "node:path";
70
71
 
@@ -318,28 +319,82 @@ var RpcClient = class {
318
319
  async function ensureDaemonClient() {
319
320
  const runtime = runtimeDir();
320
321
  const data = dataDir();
322
+ ensureDirs(runtime, data);
321
323
  let sock = await tryConnect(runtime);
324
+ if (sock !== null) {
325
+ return await validateClient(sock);
326
+ }
327
+ cleanStaleLockIfNeeded(runtime);
328
+ spawnDaemon();
329
+ for (let i = 0; i < 80 && sock === null; i++) {
330
+ await sleep(100);
331
+ sock = await tryConnect(runtime);
332
+ }
322
333
  if (sock === null) {
334
+ cleanStaleLockIfNeeded(runtime);
323
335
  spawnDaemon();
324
- for (let i = 0; i < 100 && sock === null; i++) {
336
+ for (let i = 0; i < 40 && sock === null; i++) {
325
337
  await sleep(100);
326
338
  sock = await tryConnect(runtime);
327
339
  }
328
340
  }
329
341
  if (sock === null) {
330
342
  throw new Error(
331
- `cannot connect to relife2 daemon (runtime: ${runtime}, data: ${data}); inspect ${path3.join(data, "logs", "daemon.log")}`
343
+ `cannot connect to relife2 daemon (runtime: ${runtime}, data: ${data}); try 'relife2 doctor --fix' or inspect ${path3.join(data, "logs", "daemon.log")}`
332
344
  );
333
345
  }
346
+ return await validateClient(sock);
347
+ }
348
+ async function validateClient(sock) {
334
349
  const client = new RpcClient(sock);
335
- const ping = await client.call("ping");
336
- if (ping.protocol !== PROTOCOL_VERSION) {
337
- client.close();
338
- throw new Error(
339
- `daemon protocol mismatch (daemon: ${ping.protocol}, cli: ${PROTOCOL_VERSION}); run 'relife2 kill' to stop the stale daemon`
340
- );
350
+ try {
351
+ const ping = await client.call("ping");
352
+ if (ping.protocol !== PROTOCOL_VERSION) {
353
+ client.close();
354
+ throw new Error(
355
+ `daemon protocol mismatch (daemon: ${ping.protocol}, cli: ${PROTOCOL_VERSION}); run 'relife2 kill' to stop the stale daemon`
356
+ );
357
+ }
358
+ return client;
359
+ } catch (err) {
360
+ try {
361
+ client.close();
362
+ } catch {
363
+ }
364
+ throw err;
365
+ }
366
+ }
367
+ function ensureDirs(runtime, data) {
368
+ for (const dir of [runtime, data, path3.join(data, "logs"), path3.join(data, "state")]) {
369
+ try {
370
+ fs3.mkdirSync(dir, { recursive: true });
371
+ } catch {
372
+ }
373
+ }
374
+ }
375
+ function cleanStaleLockIfNeeded(runtime) {
376
+ const file = lockPath(runtime);
377
+ try {
378
+ const raw = fs3.readFileSync(file, "utf8");
379
+ const parsed = JSON.parse(raw);
380
+ const pid = typeof parsed.pid === "number" ? parsed.pid : null;
381
+ if (pid === null) {
382
+ fs3.unlinkSync(file);
383
+ return;
384
+ }
385
+ try {
386
+ process.kill(pid, 0);
387
+ } catch (e) {
388
+ const code = e.code;
389
+ if (code === "ESRCH") {
390
+ try {
391
+ fs3.unlinkSync(file);
392
+ } catch {
393
+ }
394
+ }
395
+ }
396
+ } catch {
341
397
  }
342
- return client;
343
398
  }
344
399
  function spawnDaemon() {
345
400
  const arg1 = process.argv[1];
@@ -493,7 +548,7 @@ async function run2(args) {
493
548
 
494
549
  // src/commands/dev.ts
495
550
  import { spawn as spawn2, spawnSync } from "node:child_process";
496
- import * as fs3 from "node:fs";
551
+ import * as fs4 from "node:fs";
497
552
  import path4 from "node:path";
498
553
  var DEV_VALUE_OPTS = /* @__PURE__ */ new Set(["watch", "interpreter", "env", "name", "w"]);
499
554
  function hasBun() {
@@ -511,7 +566,7 @@ function isBunProject(cwd) {
511
566
  let cur = path4.resolve(cwd);
512
567
  const home = path4.resolve(process.env.HOME ?? "");
513
568
  for (let i = 0; i < 12; i++) {
514
- if (fs3.existsSync(path4.join(cur, "bun.lockb")) || fs3.existsSync(path4.join(cur, "bun.lock")))
569
+ if (fs4.existsSync(path4.join(cur, "bun.lockb")) || fs4.existsSync(path4.join(cur, "bun.lock")))
515
570
  return true;
516
571
  if (cur === home || cur === path4.dirname(cur)) break;
517
572
  cur = path4.dirname(cur);
@@ -523,9 +578,10 @@ function collectSubDirs(root) {
523
578
  const stack = [root];
524
579
  while (stack.length > 0) {
525
580
  const dir = stack.pop();
581
+ if (!dir) continue;
526
582
  let entries = [];
527
583
  try {
528
- entries = fs3.readdirSync(dir, { withFileTypes: true });
584
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
529
585
  } catch {
530
586
  continue;
531
587
  }
@@ -555,7 +611,7 @@ async function run3(args) {
555
611
  return 1;
556
612
  }
557
613
  const script = path4.resolve(scriptArg);
558
- if (!fs3.existsSync(script)) {
614
+ if (!fs4.existsSync(script)) {
559
615
  console.error(`dev: script not found: ${script}`);
560
616
  return 1;
561
617
  }
@@ -584,7 +640,7 @@ async function run3(args) {
584
640
  return runManualWatch(script, cwd, interpreter, passthrough, watchRaw);
585
641
  }
586
642
  async function runBunHot(script, cwd, appArgs, watchRaw) {
587
- const extraWatch = watchRaw ? ["--watch"] : [];
643
+ const _extraWatch = watchRaw ? ["--watch"] : [];
588
644
  const bunArgs = ["--hot", script, ...appArgs];
589
645
  console.log(`dev (bun --hot): bun ${bunArgs.join(" ")} [cwd ${cwd}]`);
590
646
  if (watchRaw)
@@ -663,16 +719,16 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
663
719
  }, 200);
664
720
  };
665
721
  for (const target of targets) {
666
- if (!fs3.existsSync(target)) {
722
+ if (!fs4.existsSync(target)) {
667
723
  console.error(`dev: watch path does not exist: ${target} \u2014 skipping`);
668
724
  continue;
669
725
  }
670
- const stat = fs3.statSync(target);
726
+ const stat = fs4.statSync(target);
671
727
  if (!stat.isDirectory()) {
672
728
  const dir = path4.dirname(target);
673
729
  const base = path4.basename(target);
674
730
  try {
675
- const w = fs3.watch(dir, (_ev, filename) => {
731
+ const w = fs4.watch(dir, (_ev, filename) => {
676
732
  if (filename && filename !== base) return;
677
733
  scheduleRestart(target);
678
734
  });
@@ -684,7 +740,7 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
684
740
  }
685
741
  if (process.platform === "win32" || process.platform === "darwin") {
686
742
  try {
687
- const w = fs3.watch(target, { recursive: true }, () => scheduleRestart(target));
743
+ const w = fs4.watch(target, { recursive: true }, () => scheduleRestart(target));
688
744
  watchers.push(w);
689
745
  } catch (err) {
690
746
  console.error(`dev: cannot watch ${target}: ${err.message}`);
@@ -693,7 +749,7 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
693
749
  const dirs = collectSubDirs(target);
694
750
  for (const dir of dirs) {
695
751
  try {
696
- const w = fs3.watch(dir, () => scheduleRestart(dir));
752
+ const w = fs4.watch(dir, () => scheduleRestart(dir));
697
753
  watchers.push(w);
698
754
  } catch {
699
755
  }
@@ -788,8 +844,32 @@ Examples:
788
844
  }
789
845
 
790
846
  // src/commands/doctor.ts
791
- async function run4(_args) {
792
- const client = await ensureDaemonClient();
847
+ import fs5 from "node:fs";
848
+ import os2 from "node:os";
849
+ import path5 from "node:path";
850
+ async function run4(args) {
851
+ const fix = args.includes("--fix") || args.includes("-f");
852
+ if (fix) {
853
+ const healed = healLocally();
854
+ if (healed.length > 0) {
855
+ for (const m of healed) console.log(`healed: ${m}`);
856
+ } else {
857
+ console.log("heal: nothing to fix locally");
858
+ }
859
+ }
860
+ let client;
861
+ try {
862
+ client = await ensureDaemonClient();
863
+ } catch (err) {
864
+ if (fix) {
865
+ console.error(`doctor: daemon still not reachable after heal: ${err.message}`);
866
+ console.error("try: rm -rf ~/.relife2/state/* && relife2 kill; relife2 list");
867
+ } else {
868
+ console.error(err.message);
869
+ console.error("hint: relife2 doctor --fix (cleans stale lock + *.tmp.cjs)");
870
+ }
871
+ return 1;
872
+ }
793
873
  try {
794
874
  const report = await client.call("doctor", {});
795
875
  let hadErrors = false;
@@ -806,21 +886,143 @@ async function run4(_args) {
806
886
  console.log(
807
887
  `checks: ${report.summary.total} total, ${report.summary.ok} ok, ${report.summary.warnings} warnings, ${report.summary.errors} errors`
808
888
  );
889
+ if (hadErrors && !fix) {
890
+ console.log("hint: relife2 doctor --fix (cleans stale lock + *.tmp.cjs)");
891
+ }
809
892
  return hadErrors ? 1 : 0;
810
893
  } finally {
811
894
  client.close();
812
895
  }
813
896
  }
897
+ function healLocally() {
898
+ const out = [];
899
+ const runtime = runtimeDir();
900
+ const data = dataDir();
901
+ for (const dir of [runtime, data, path5.join(data, "logs"), path5.join(data, "state")]) {
902
+ try {
903
+ fs5.mkdirSync(dir, { recursive: true });
904
+ } catch {
905
+ }
906
+ }
907
+ const lockFile = path5.join(runtime, "daemon.lock");
908
+ try {
909
+ const raw = fs5.readFileSync(lockFile, "utf8");
910
+ const parsed = JSON.parse(raw);
911
+ const pid = typeof parsed.pid === "number" ? parsed.pid : null;
912
+ let alive = false;
913
+ if (pid !== null) {
914
+ try {
915
+ process.kill(pid, 0);
916
+ alive = true;
917
+ } catch (e) {
918
+ const code = e.code;
919
+ if (code === "EPERM") alive = true;
920
+ else if (code === "ESRCH") alive = false;
921
+ }
922
+ }
923
+ if (pid === null || !alive) {
924
+ fs5.unlinkSync(lockFile);
925
+ out.push(`removed stale lock ${lockFile} (pid=${pid})`);
926
+ }
927
+ } catch {
928
+ }
929
+ const home = os2.homedir();
930
+ if (home && fs5.existsSync(home)) {
931
+ try {
932
+ const tmpLeftovers = collectTmpLeftovers(home, 6, 50);
933
+ for (const f of tmpLeftovers) {
934
+ try {
935
+ fs5.unlinkSync(f);
936
+ out.push(`removed leftover ${path5.relative(home, f)}`);
937
+ } catch {
938
+ }
939
+ }
940
+ } catch {
941
+ }
942
+ }
943
+ try {
944
+ const tmpDir = os2.tmpdir();
945
+ const entries = fs5.readdirSync(tmpDir);
946
+ const now = Date.now();
947
+ for (const name of entries) {
948
+ if (!name.startsWith("relife2-") || !name.endsWith(".tmp.cjs")) continue;
949
+ const full = path5.join(tmpDir, name);
950
+ try {
951
+ const st = fs5.statSync(full);
952
+ if (now - st.mtimeMs > 60 * 60 * 1e3) {
953
+ fs5.unlinkSync(full);
954
+ out.push(`removed old tmp ${name}`);
955
+ }
956
+ } catch {
957
+ }
958
+ }
959
+ } catch {
960
+ }
961
+ return out;
962
+ }
963
+ function collectTmpLeftovers(root, maxDepth, limit) {
964
+ const out = [];
965
+ const stack = [{ dir: root, depth: 0 }];
966
+ const ignored = /* @__PURE__ */ new Set([
967
+ "node_modules",
968
+ ".git",
969
+ ".hg",
970
+ ".svn",
971
+ "vendor",
972
+ ".next",
973
+ ".output",
974
+ "dist",
975
+ "build",
976
+ "target",
977
+ "__pycache__",
978
+ ".cache",
979
+ ".pnpm",
980
+ ".yarn",
981
+ ".turbo",
982
+ ".parcel-cache",
983
+ "Library",
984
+ "AppData",
985
+ ".local",
986
+ "proc",
987
+ "sys",
988
+ "dev"
989
+ ]);
990
+ while (stack.length > 0 && out.length < limit) {
991
+ const cur = stack.pop();
992
+ if (!cur) break;
993
+ if (cur.depth > maxDepth) continue;
994
+ let entries;
995
+ try {
996
+ entries = fs5.readdirSync(cur.dir, { withFileTypes: true });
997
+ } catch {
998
+ continue;
999
+ }
1000
+ for (const ent of entries) {
1001
+ if (out.length >= limit) break;
1002
+ const full = path5.join(cur.dir, ent.name);
1003
+ if (ent.isFile() && ent.name.includes(".tmp.") && ent.name.endsWith(".cjs")) {
1004
+ if (ent.name.includes(".config.")) out.push(full);
1005
+ continue;
1006
+ }
1007
+ if (!ent.isDirectory()) continue;
1008
+ if (ignored.has(ent.name)) continue;
1009
+ if (ent.name.startsWith(".") && cur.depth < 2) continue;
1010
+ if (ent.isSymbolicLink()) continue;
1011
+ if (cur.depth + 1 <= maxDepth) stack.push({ dir: full, depth: cur.depth + 1 });
1012
+ }
1013
+ }
1014
+ return out;
1015
+ }
814
1016
 
815
1017
  // src/commands/find.ts
816
- import * as fs7 from "node:fs";
817
- import os4 from "node:os";
818
- import path8 from "node:path";
1018
+ import * as fs9 from "node:fs";
1019
+ import os5 from "node:os";
1020
+ import path9 from "node:path";
819
1021
 
820
1022
  // src/config/loader.ts
821
- import * as fs6 from "node:fs";
822
- import os3 from "node:os";
823
- import path7 from "node:path";
1023
+ import * as fs8 from "node:fs";
1024
+ import os4 from "node:os";
1025
+ import path8 from "node:path";
824
1026
 
825
1027
  // src/util/cron.ts
826
1028
  var FIELD_RANGES = [
@@ -939,19 +1141,19 @@ function parseMemory(v) {
939
1141
  }
940
1142
 
941
1143
  // src/util/portable.ts
942
- import * as fs4 from "node:fs";
943
- import os2 from "node:os";
944
- import path5 from "node:path";
1144
+ import * as fs6 from "node:fs";
1145
+ import os3 from "node:os";
1146
+ import path6 from "node:path";
945
1147
  function isDir(p) {
946
1148
  try {
947
- return fs4.statSync(p).isDirectory();
1149
+ return fs6.statSync(p).isDirectory();
948
1150
  } catch {
949
1151
  return false;
950
1152
  }
951
1153
  }
952
1154
  function isFile(p) {
953
1155
  try {
954
- return fs4.statSync(p).isFile();
1156
+ return fs6.statSync(p).isFile();
955
1157
  } catch {
956
1158
  return false;
957
1159
  }
@@ -973,18 +1175,18 @@ function searchByBasename(root, basename, maxDepth = 4) {
973
1175
  if (cur.depth > maxDepth) continue;
974
1176
  let entries;
975
1177
  try {
976
- entries = fs4.readdirSync(cur.dir, { withFileTypes: true });
1178
+ entries = fs6.readdirSync(cur.dir, { withFileTypes: true });
977
1179
  } catch {
978
1180
  continue;
979
1181
  }
980
1182
  for (const e of entries) {
981
- const full = path5.join(cur.dir, e.name);
1183
+ const full = path6.join(cur.dir, e.name);
982
1184
  if (e.isFile() && e.name === basename) return full;
983
1185
  if (e.isDirectory() && !e.isSymbolicLink()) {
984
1186
  if (e.name === "node_modules" || e.name === ".git") continue;
985
1187
  const real = (() => {
986
1188
  try {
987
- return fs4.realpathSync(full);
1189
+ return fs6.realpathSync(full);
988
1190
  } catch {
989
1191
  return full;
990
1192
  }
@@ -1002,20 +1204,20 @@ function fixCwd(staleCwd, sourceDir) {
1002
1204
  const candidates = [];
1003
1205
  const tail = stripHomePrefix(staleCwd);
1004
1206
  if (tail !== null) {
1005
- candidates.push(path5.join(os2.homedir(), tail));
1006
- candidates.push(path5.join(path5.dirname(os2.homedir()), tail));
1007
- candidates.push(path5.join(path5.dirname(sourceDir), tail));
1008
- if (isDir(sourceDir) && !staleCwd.startsWith(os2.homedir())) candidates.push(sourceDir);
1207
+ candidates.push(path6.join(os3.homedir(), tail));
1208
+ candidates.push(path6.join(path6.dirname(os3.homedir()), tail));
1209
+ candidates.push(path6.join(path6.dirname(sourceDir), tail));
1210
+ if (isDir(sourceDir) && !staleCwd.startsWith(os3.homedir())) candidates.push(sourceDir);
1009
1211
  }
1010
- const proj = path5.basename(sourceDir);
1212
+ const proj = path6.basename(sourceDir);
1011
1213
  if (proj) {
1012
- const marker = `${path5.sep}${proj}${path5.sep}`;
1214
+ const marker = `${path6.sep}${proj}${path6.sep}`;
1013
1215
  const altMarker = `/${proj}/`;
1014
1216
  const idx = staleCwd.includes(marker) ? staleCwd.lastIndexOf(marker) : staleCwd.lastIndexOf(altMarker);
1015
1217
  if (idx >= 0) {
1016
1218
  const suffix = staleCwd.slice(idx + proj.length + 2);
1017
- candidates.push(path5.join(sourceDir, suffix));
1018
- candidates.push(path5.join(path5.dirname(sourceDir), proj, suffix));
1219
+ candidates.push(path6.join(sourceDir, suffix));
1220
+ candidates.push(path6.join(path6.dirname(sourceDir), proj, suffix));
1019
1221
  }
1020
1222
  }
1021
1223
  for (const c of candidates) {
@@ -1030,33 +1232,33 @@ function fixCwd(staleCwd, sourceDir) {
1030
1232
  }
1031
1233
  function fixScript(staleScript, cwd, sourceDir) {
1032
1234
  if (isFile(staleScript)) return { path: staleScript, fixed: false };
1033
- const base = path5.basename(staleScript);
1235
+ const base = path6.basename(staleScript);
1034
1236
  const candidates = [];
1035
- candidates.push(path5.join(cwd, base));
1036
- candidates.push(path5.join(sourceDir, base));
1237
+ candidates.push(path6.join(cwd, base));
1238
+ candidates.push(path6.join(sourceDir, base));
1037
1239
  const tail = stripHomePrefix(staleScript);
1038
1240
  if (tail !== null) {
1039
- candidates.push(path5.join(os2.homedir(), tail));
1040
- candidates.push(path5.join(path5.dirname(os2.homedir()), tail));
1041
- candidates.push(path5.join(sourceDir, tail));
1241
+ candidates.push(path6.join(os3.homedir(), tail));
1242
+ candidates.push(path6.join(path6.dirname(os3.homedir()), tail));
1243
+ candidates.push(path6.join(sourceDir, tail));
1042
1244
  const tailParts = tail.split("/");
1043
- if (tailParts.length > 1) candidates.push(path5.join(sourceDir, tailParts.slice(1).join("/")));
1245
+ if (tailParts.length > 1) candidates.push(path6.join(sourceDir, tailParts.slice(1).join("/")));
1044
1246
  }
1045
- const proj = path5.basename(sourceDir);
1247
+ const proj = path6.basename(sourceDir);
1046
1248
  if (proj) {
1047
1249
  const marker = `/${proj}/`;
1048
1250
  const idx = staleScript.lastIndexOf(marker);
1049
1251
  if (idx >= 0) {
1050
1252
  const suffix = staleScript.slice(idx + proj.length + 2);
1051
- candidates.push(path5.join(sourceDir, suffix));
1052
- candidates.push(path5.join(cwd, suffix));
1253
+ candidates.push(path6.join(sourceDir, suffix));
1254
+ candidates.push(path6.join(cwd, suffix));
1053
1255
  }
1054
1256
  }
1055
1257
  const parts = staleScript.split(/[/\\]/).filter(Boolean);
1056
1258
  if (parts.length >= 2) {
1057
- const lastTwo = parts.slice(-2).join(path5.sep);
1058
- candidates.push(path5.join(sourceDir, lastTwo));
1059
- candidates.push(path5.join(cwd, lastTwo));
1259
+ const lastTwo = parts.slice(-2).join(path6.sep);
1260
+ candidates.push(path6.join(sourceDir, lastTwo));
1261
+ candidates.push(path6.join(cwd, lastTwo));
1060
1262
  }
1061
1263
  for (const c of candidates) {
1062
1264
  if (c && isFile(c))
@@ -1136,9 +1338,17 @@ var LATER_KEYS = {
1136
1338
  uid: "M6",
1137
1339
  gid: "M6"
1138
1340
  };
1139
- var CONFIG_FILE_RE = /^(?:(?:ecosystem|pm2|relife2)[\w.-]*|[\w.-]*?)\.config\.(?:c?js|mjs|json|ts)$/i;
1341
+ var RELIFE2_CONFIG_RE = /^(?:ecosystem|pm2|relife2)[\w.-]*\.config\.(?:c?js|mjs|json|ts)$/i;
1342
+ var ANY_CONFIG_RE = /^[\w.-]*\.config\.(?:c?js|mjs|json|ts)$/i;
1140
1343
  function looksLikeConfigFile(file) {
1141
- return CONFIG_FILE_RE.test(path7.basename(file));
1344
+ const base = path8.basename(file);
1345
+ if (base.includes(".tmp.")) return false;
1346
+ return ANY_CONFIG_RE.test(base);
1347
+ }
1348
+ function isRelife2ConfigFile(file) {
1349
+ const base = path8.basename(file);
1350
+ if (base.includes(".tmp.")) return false;
1351
+ return RELIFE2_CONFIG_RE.test(base);
1142
1352
  }
1143
1353
  function findDefaultConfig(cwd) {
1144
1354
  const candidates = [
@@ -1155,16 +1365,16 @@ function findDefaultConfig(cwd) {
1155
1365
  "pm2.config.cjs"
1156
1366
  ];
1157
1367
  for (const name of candidates) {
1158
- const full = path7.join(cwd, name);
1159
- if (fs6.existsSync(full)) return full;
1368
+ const full = path8.join(cwd, name);
1369
+ if (fs8.existsSync(full)) return full;
1160
1370
  }
1161
1371
  return null;
1162
1372
  }
1163
1373
  async function loadConfigFile(filePath, opts) {
1164
- const ext = path7.extname(filePath).toLowerCase();
1374
+ const ext = path8.extname(filePath).toLowerCase();
1165
1375
  let exported;
1166
1376
  if (ext === ".json") {
1167
- exported = JSON.parse(fs6.readFileSync(filePath, "utf8"));
1377
+ exported = JSON.parse(fs8.readFileSync(filePath, "utf8"));
1168
1378
  } else if (ext === ".ts") {
1169
1379
  exported = await loadTsFile(filePath);
1170
1380
  } else {
@@ -1174,7 +1384,7 @@ async function loadConfigFile(filePath, opts) {
1174
1384
  }
1175
1385
  if (exported instanceof Promise) exported = await exported;
1176
1386
  }
1177
- return normalizeExported(exported, path7.dirname(filePath), opts);
1387
+ return normalizeExported(exported, path8.dirname(filePath), opts);
1178
1388
  }
1179
1389
  async function loadTsFile(file) {
1180
1390
  try {
@@ -1183,16 +1393,19 @@ async function loadTsFile(file) {
1183
1393
  const mod = await import(url);
1184
1394
  return mod.default ?? mod;
1185
1395
  } catch {
1186
- const raw = fs6.readFileSync(file, "utf8");
1396
+ const raw = fs8.readFileSync(file, "utf8");
1187
1397
  const stripped = raw.replace(/^\s*import\s+type\s+.*$/gm, "").replace(/:\s*[\w<>[\]|&\s,?]+(?=[=;,\n)}])/g, "").replace(/\s+as\s+const/g, "");
1188
- const tmp = `${file}.tmp.cjs`;
1398
+ const tmp = path8.join(
1399
+ os4.tmpdir(),
1400
+ `relife2-${path8.basename(file).replace(/[^a-zA-Z0-9._-]/g, "_")}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.tmp.cjs`
1401
+ );
1189
1402
  try {
1190
- fs6.writeFileSync(tmp, stripped, "utf8");
1403
+ fs8.writeFileSync(tmp, stripped, "utf8");
1191
1404
  const { loadJsFile: lf } = await Promise.resolve().then(() => (init_jsloader(), jsloader_exports));
1192
1405
  return await lf(tmp);
1193
1406
  } finally {
1194
1407
  try {
1195
- fs6.unlinkSync(tmp);
1408
+ fs8.unlinkSync(tmp);
1196
1409
  } catch {
1197
1410
  }
1198
1411
  }
@@ -1248,20 +1461,20 @@ function normalizeApp(raw, sourceDir, warnings, index, envName) {
1248
1461
  const instances = parseInstances(input.instances, index);
1249
1462
  const execMode = parseExecMode(input.exec_mode, index);
1250
1463
  const rawCwd = typeof input.cwd === "string" ? input.cwd : void 0;
1251
- const cwdRaw = path7.resolve(sourceDir, rawCwd ?? ".");
1464
+ const cwdRaw = path8.resolve(sourceDir, rawCwd ?? ".");
1252
1465
  const cwdFix = fixCwd(cwdRaw, sourceDir);
1253
1466
  const cwd = cwdFix.path;
1254
1467
  if (cwdFix.fixed && cwdFix.reason) warnings.push(cwdFix.reason);
1255
1468
  const hasSep = scriptRaw.includes("/") || scriptRaw.includes("\\");
1256
1469
  const isJsExt = /\.(?:c?js|mjs|ts|mts|cts)$/i.test(scriptRaw);
1257
- const scriptRawResolved = hasSep || isJsExt ? path7.resolve(cwd, scriptRaw) : scriptRaw;
1470
+ const scriptRawResolved = hasSep || isJsExt ? path8.resolve(cwd, scriptRaw) : scriptRaw;
1258
1471
  let script = scriptRawResolved;
1259
- if (path7.isAbsolute(scriptRawResolved)) {
1472
+ if (path8.isAbsolute(scriptRawResolved)) {
1260
1473
  const f = fixScript(scriptRawResolved, cwd, sourceDir);
1261
1474
  if (f.fixed) {
1262
1475
  warnings.push(f.reason ?? `script '${scriptRawResolved}' \u2192 '${f.path}'`);
1263
1476
  script = f.path;
1264
- } else if (!fs6.existsSync(scriptRawResolved) && f.reason) {
1477
+ } else if (!fs8.existsSync(scriptRawResolved) && f.reason) {
1265
1478
  warnings.push(f.reason);
1266
1479
  }
1267
1480
  }
@@ -1278,11 +1491,11 @@ function normalizeApp(raw, sourceDir, warnings, index, envName) {
1278
1491
  }
1279
1492
  const envFile = typeof input.env_file === "string" && input.env_file !== "" ? input.env_file : void 0;
1280
1493
  if (envFile !== void 0) {
1281
- const envPath = path7.resolve(cwd, envFile);
1494
+ const envPath = path8.resolve(cwd, envFile);
1282
1495
  let parsed = null;
1283
- if (fs6.existsSync(envPath) && fs6.statSync(envPath).isFile()) {
1496
+ if (fs8.existsSync(envPath) && fs8.statSync(envPath).isFile()) {
1284
1497
  try {
1285
- parsed = parseDotenv(fs6.readFileSync(envPath, "utf8"));
1498
+ parsed = parseDotenv(fs8.readFileSync(envPath, "utf8"));
1286
1499
  } catch (err) {
1287
1500
  warnings.push(
1288
1501
  `apps[${index}] env_file '${envFile}' could not be read: ${err.message}`
@@ -1405,7 +1618,7 @@ function parseWatchField(v, field, index) {
1405
1618
  }
1406
1619
  function parseInstances(v, index) {
1407
1620
  if (v === void 0 || v === null) return 1;
1408
- if (v === "max" || v === "MAX") return os3.cpus().length || 1;
1621
+ if (v === "max" || v === "MAX") return os4.cpus().length || 1;
1409
1622
  const n = Number(v);
1410
1623
  if (!Number.isFinite(n) || n < 1) {
1411
1624
  throw new ConfigError(`apps[${index}] 'instances' must be a positive number or 'max'`);
@@ -1420,7 +1633,7 @@ function parseExecMode(v, index) {
1420
1633
  throw new ConfigError(`apps[${index}] 'exec_mode' must be 'fork' or 'cluster'`);
1421
1634
  }
1422
1635
  function defaultName(script) {
1423
- const base = path7.basename(script);
1636
+ const base = path8.basename(script);
1424
1637
  return base.replace(/\.(?:c?js|mjs|ts|mts|cts)$/i, "") || base;
1425
1638
  }
1426
1639
  function normalizeSingleApp(input, sourceDir, envName) {
@@ -1468,7 +1681,7 @@ function findConfigs(root, maxDepth) {
1468
1681
  if (depth > maxDepth) continue;
1469
1682
  let real;
1470
1683
  try {
1471
- real = fs7.realpathSync(dir);
1684
+ real = fs9.realpathSync(dir);
1472
1685
  } catch {
1473
1686
  continue;
1474
1687
  }
@@ -1476,13 +1689,13 @@ function findConfigs(root, maxDepth) {
1476
1689
  visited.add(real);
1477
1690
  let entries;
1478
1691
  try {
1479
- entries = fs7.readdirSync(dir, { withFileTypes: true });
1692
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
1480
1693
  } catch {
1481
1694
  continue;
1482
1695
  }
1483
1696
  for (const ent of entries) {
1484
- const full = path8.join(dir, ent.name);
1485
- if (ent.isFile() && looksLikeConfigFile(ent.name)) {
1697
+ const full = path9.join(dir, ent.name);
1698
+ if (ent.isFile() && isRelife2ConfigFile(ent.name)) {
1486
1699
  out.push(full);
1487
1700
  continue;
1488
1701
  }
@@ -1511,8 +1724,8 @@ async function enrich(found) {
1511
1724
  return entries;
1512
1725
  }
1513
1726
  function relToRoot(p, root) {
1514
- const rel = path8.relative(root, p);
1515
- return rel === "" ? path8.basename(p) : rel;
1727
+ const rel = path9.relative(root, p);
1728
+ return rel === "" ? path9.basename(p) : rel;
1516
1729
  }
1517
1730
  async function run5(args) {
1518
1731
  const { positionals, opts } = parseCliArgs(args, VALUE_OPTS);
@@ -1522,12 +1735,12 @@ async function run5(args) {
1522
1735
  let root = opts.get("root");
1523
1736
  if (root === void 0) {
1524
1737
  if (allFlag) {
1525
- root = process.platform === "win32" ? path8.parse(process.cwd()).root : "/";
1738
+ root = process.platform === "win32" ? path9.parse(process.cwd()).root : "/";
1526
1739
  } else {
1527
- root = os4.homedir() || process.cwd();
1740
+ root = os5.homedir() || process.cwd();
1528
1741
  }
1529
1742
  }
1530
- root = path8.resolve(root);
1743
+ root = path9.resolve(root);
1531
1744
  let depth = 6;
1532
1745
  const depthRaw = opts.get("depth");
1533
1746
  if (depthRaw !== void 0) {
@@ -1539,7 +1752,7 @@ async function run5(args) {
1539
1752
  depth = Math.round(n);
1540
1753
  }
1541
1754
  if (allFlag) depth = Math.max(depth, 8);
1542
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) {
1755
+ if (!fs9.existsSync(root) || !fs9.statSync(root).isDirectory()) {
1543
1756
  console.error(`find: root '${root}' does not exist or is not a directory`);
1544
1757
  return 1;
1545
1758
  }
@@ -1630,10 +1843,10 @@ Starting ${toStart.length} config(s) not yet running\u2026
1630
1843
  const res = await client.call("start", {
1631
1844
  target: { type: "config", path: r.path }
1632
1845
  });
1633
- for (const w of res.warnings ?? []) console.error(`warning ${path8.basename(r.path)}: ${w}`);
1846
+ for (const w of res.warnings ?? []) console.error(`warning ${path9.basename(r.path)}: ${w}`);
1634
1847
  for (const a of res.apps) {
1635
1848
  if (a.status === "errored") {
1636
- console.error(`x ${a.name} (${path8.basename(r.path)}): ${a.message ?? "failed"}`);
1849
+ console.error(`x ${a.name} (${path9.basename(r.path)}): ${a.message ?? "failed"}`);
1637
1850
  failed++;
1638
1851
  } else {
1639
1852
  console.log(
@@ -1643,7 +1856,7 @@ Starting ${toStart.length} config(s) not yet running\u2026
1643
1856
  }
1644
1857
  }
1645
1858
  } catch (err) {
1646
- console.error(`x ${path8.basename(r.path)}: ${err.message}`);
1859
+ console.error(`x ${path9.basename(r.path)}: ${err.message}`);
1647
1860
  failed++;
1648
1861
  }
1649
1862
  }
@@ -1677,9 +1890,9 @@ async function run6(args) {
1677
1890
  }
1678
1891
 
1679
1892
  // src/commands/fromCaddy.ts
1680
- import * as fs8 from "node:fs";
1681
- import os5 from "node:os";
1682
- import path9 from "node:path";
1893
+ import * as fs10 from "node:fs";
1894
+ import os6 from "node:os";
1895
+ import path10 from "node:path";
1683
1896
  var LOOPBACK_RE = /(?:127\.0\.0\.1|localhost|::1):(\d{2,5})/g;
1684
1897
  var ENTRY_PATTERNS = [
1685
1898
  ".output/server/index.mjs",
@@ -1852,37 +2065,37 @@ function parseCaddyfile(content) {
1852
2065
  }
1853
2066
  function findEntry(dir) {
1854
2067
  for (const pat of ENTRY_PATTERNS) {
1855
- const p = path9.join(dir, pat);
1856
- if (fs8.existsSync(p)) return { script: pat, exists: true };
2068
+ const p = path10.join(dir, pat);
2069
+ if (fs10.existsSync(p)) return { script: pat, exists: true };
1857
2070
  }
1858
2071
  return null;
1859
2072
  }
1860
2073
  function guessDir(site, _port, hint, caddyfileDir) {
1861
2074
  const warnings = [];
1862
2075
  if (hint?.dir) {
1863
- const d = path9.isAbsolute(hint.dir) ? hint.dir : path9.resolve(caddyfileDir, hint.dir);
2076
+ const d = path10.isAbsolute(hint.dir) ? hint.dir : path10.resolve(caddyfileDir, hint.dir);
1864
2077
  return { dir: d, warnings };
1865
2078
  }
1866
2079
  const searchRoots = [];
1867
- const homedir = os5.homedir();
2080
+ const homedir = os6.homedir();
1868
2081
  if (homedir) searchRoots.push(homedir);
1869
2082
  if (caddyfileDir) searchRoots.push(caddyfileDir);
1870
- if (caddyfileDir) searchRoots.push(path9.resolve(caddyfileDir, ".."));
2083
+ if (caddyfileDir) searchRoots.push(path10.resolve(caddyfileDir, ".."));
1871
2084
  const uniqRoots = [...new Set(searchRoots)].filter(Boolean);
1872
2085
  const siteName = sanitizeName(site);
1873
2086
  const candidates = [];
1874
- if (homedir) candidates.push(path9.join(homedir, siteName));
1875
- if (homedir) candidates.push(path9.join(homedir, siteName.split("-")[0]));
1876
- for (const r of uniqRoots) candidates.push(path9.join(r, siteName));
2087
+ if (homedir) candidates.push(path10.join(homedir, siteName));
2088
+ if (homedir) candidates.push(path10.join(homedir, siteName.split("-")[0]));
2089
+ for (const r of uniqRoots) candidates.push(path10.join(r, siteName));
1877
2090
  const rawDir = site.split(".")[0].replace(/[^a-zA-Z0-9_-]/g, "");
1878
- if (rawDir && homedir) candidates.push(path9.join(homedir, rawDir));
2091
+ if (rawDir && homedir) candidates.push(path10.join(homedir, rawDir));
1879
2092
  for (const cand of candidates) {
1880
- if (fs8.existsSync(cand) && fs8.statSync(cand).isDirectory()) {
2093
+ if (fs10.existsSync(cand) && fs10.statSync(cand).isDirectory()) {
1881
2094
  const e = findEntry(cand);
1882
2095
  if (e) return { dir: cand, warnings };
1883
2096
  }
1884
2097
  }
1885
- const fallback = homedir ? path9.join(homedir, siteName) : path9.join(caddyfileDir || process.cwd(), siteName);
2098
+ const fallback = homedir ? path10.join(homedir, siteName) : path10.join(caddyfileDir || process.cwd(), siteName);
1886
2099
  warnings.push(
1887
2100
  `no existing dir found for ${site} \u2192 using fallback ${fallback} (create or set via '# relife2: dir=...')`
1888
2101
  );
@@ -1914,15 +2127,15 @@ async function run7(args) {
1914
2127
  caddyfilePath ? null : "./Caddyfile",
1915
2128
  caddyfilePath ? null : "Caddyfile",
1916
2129
  caddyfilePath ? null : "/etc/caddy/Caddyfile",
1917
- caddyfilePath ? null : path9.join(os5.homedir(), "Caddyfile")
2130
+ caddyfilePath ? null : path10.join(os6.homedir(), "Caddyfile")
1918
2131
  ].filter(Boolean);
1919
2132
  let resolvedCaddy = null;
1920
2133
  let content = null;
1921
2134
  for (const cand of candidates) {
1922
- const p = path9.resolve(cand);
1923
- if (fs8.existsSync(p)) {
2135
+ const p = path10.resolve(cand);
2136
+ if (fs10.existsSync(p)) {
1924
2137
  resolvedCaddy = p;
1925
- content = fs8.readFileSync(p, "utf8");
2138
+ content = fs10.readFileSync(p, "utf8");
1926
2139
  break;
1927
2140
  }
1928
2141
  }
@@ -1933,7 +2146,7 @@ async function run7(args) {
1933
2146
  );
1934
2147
  return 1;
1935
2148
  }
1936
- const caddyfileDir = path9.dirname(resolvedCaddy);
2149
+ const caddyfileDir = path10.dirname(resolvedCaddy);
1937
2150
  const { sites } = parseCaddyfile(content);
1938
2151
  if (sites.length === 0) {
1939
2152
  console.error(`from-caddy: no sites found in ${resolvedCaddy}`);
@@ -1981,7 +2194,7 @@ async function run7(args) {
1981
2194
  let relScript = "";
1982
2195
  if (entry) {
1983
2196
  relScript = entry;
1984
- exists = fs8.existsSync(path9.isAbsolute(entry) ? entry : path9.join(dir, entry));
2197
+ exists = fs10.existsSync(path10.isAbsolute(entry) ? entry : path10.join(dir, entry));
1985
2198
  if (!exists) warnings.push(`hint script '${entry}' for ${name} not found under ${dir}`);
1986
2199
  } else {
1987
2200
  const found = findEntry(dir);
@@ -1999,7 +2212,7 @@ async function run7(args) {
1999
2212
  let interpreter = site.hint?.interpreter;
2000
2213
  if (!interpreter) {
2001
2214
  if (relScript.endsWith(".mjs") || relScript.endsWith(".ts")) {
2002
- const hasBun2 = fs8.existsSync(path9.join(dir, "bun.lockb")) || fs8.existsSync(path9.join(dir, "bun.lock"));
2215
+ const hasBun2 = fs10.existsSync(path10.join(dir, "bun.lockb")) || fs10.existsSync(path10.join(dir, "bun.lock"));
2003
2216
  if (hasBun2) interpreter = "bun";
2004
2217
  }
2005
2218
  }
@@ -2080,7 +2293,7 @@ Would write ${planned.length} app(s) to ${outPath ?? "relife2.config.cjs (dry-ru
2080
2293
  console.log(`Run without --dry-run to generate.`);
2081
2294
  return 0;
2082
2295
  }
2083
- const resolvedOut = path9.resolve(outPath ?? "relife2.config.cjs");
2296
+ const resolvedOut = path10.resolve(outPath ?? "relife2.config.cjs");
2084
2297
  const appsCode = planned.map((p) => {
2085
2298
  const interp = p._interpreter;
2086
2299
  const isCaddy = p.name === "caddy";
@@ -2100,8 +2313,8 @@ Would write ${planned.length} app(s) to ${outPath ?? "relife2.config.cjs (dry-ru
2100
2313
  lines.push(` cwd: '${p.dir.replace(/'/g, "\\'")}',`);
2101
2314
  lines.push(` interpreter: 'none',`);
2102
2315
  } else {
2103
- const scriptEsc = p.script.replace(/'/g, "\\'");
2104
- const dirEsc = p.dir.replace(/'/g, "\\'");
2316
+ const _scriptEsc = p.script.replace(/'/g, "\\'");
2317
+ const _dirEsc = p.dir.replace(/'/g, "\\'");
2105
2318
  lines.push(` script: ${JSON.stringify(p.script)},`);
2106
2319
  lines.push(` cwd: ${JSON.stringify(p.dir)},`);
2107
2320
  if (interp) lines.push(` interpreter: ${JSON.stringify(interp)},`);
@@ -2137,7 +2350,7 @@ ${appsCode}
2137
2350
  ]
2138
2351
  };
2139
2352
  `;
2140
- fs8.writeFileSync(resolvedOut, fileContent, "utf8");
2353
+ fs10.writeFileSync(resolvedOut, fileContent, "utf8");
2141
2354
  console.log(
2142
2355
  `from-caddy: parsed ${sites.length} site(s), ${planned.length} upstream(s) from ${resolvedCaddy}`
2143
2356
  );
@@ -2155,7 +2368,7 @@ Warnings:`);
2155
2368
  console.log(`
2156
2369
  Wrote ${resolvedOut} (${planned.length} app(s))`);
2157
2370
  console.log(
2158
- `Next: relife2 inspect-config ${path9.basename(resolvedOut)} \u2192 relife2 start ${path9.basename(resolvedOut)}`
2371
+ `Next: relife2 inspect-config ${path10.basename(resolvedOut)} \u2192 relife2 start ${path10.basename(resolvedOut)}`
2159
2372
  );
2160
2373
  return 0;
2161
2374
  }
@@ -2184,7 +2397,7 @@ function printUsage2() {
2184
2397
  }
2185
2398
 
2186
2399
  // src/version.ts
2187
- var VERSION = "1.0.0";
2400
+ var VERSION = "1.0.2";
2188
2401
 
2189
2402
  // src/commands/help.ts
2190
2403
  var HELP_TEXT = `relife2 ${VERSION} \u2014 process manager for Node.js & Bun apps (PM2 alternative, done right)
@@ -2210,7 +2423,7 @@ Commands:
2210
2423
  flush [name|all] Empty the app log file(s)
2211
2424
  rotate [--max-size <bytes>] [--retain <n>] [name|all]
2212
2425
  Rotate log files exceeding the size threshold (M3)
2213
- doctor Run integrity checks on the running daemon (M3)
2426
+ doctor [--fix] Run integrity checks on the running daemon (M3) \u2014 --fix heals stale lock + *.tmp.cjs
2214
2427
  metrics Show daemon self-metrics + per-app summary (M3)
2215
2428
  monit [--once] Live dashboard (M6): cpu/mem/uptime per app, q to quit
2216
2429
  find [start] [--root <dir>] [--depth <n>] [--json] [--all]
@@ -2242,13 +2455,13 @@ function printHelp() {
2242
2455
  }
2243
2456
 
2244
2457
  // src/commands/importpm2.ts
2245
- import * as fs9 from "node:fs";
2246
- import os6 from "node:os";
2247
- import path10 from "node:path";
2458
+ import * as fs11 from "node:fs";
2459
+ import os7 from "node:os";
2460
+ import path11 from "node:path";
2248
2461
  async function run8(args) {
2249
2462
  const target = args[0];
2250
- const dumpPath = target !== void 0 ? path10.resolve(target) : path10.join(os6.homedir(), ".pm2", "dump.pm2");
2251
- if (!fs9.existsSync(dumpPath)) {
2463
+ const dumpPath = target !== void 0 ? path11.resolve(target) : path11.join(os7.homedir(), ".pm2", "dump.pm2");
2464
+ if (!fs11.existsSync(dumpPath)) {
2252
2465
  console.error(`import-pm2: dump file not found: ${dumpPath}`);
2253
2466
  console.error(
2254
2467
  " pm2 saves to ~/.pm2/dump.pm2 via `pm2 save` \u2014 run that first, or pass a path:"
@@ -2258,7 +2471,7 @@ async function run8(args) {
2258
2471
  }
2259
2472
  let raw;
2260
2473
  try {
2261
- raw = JSON.parse(fs9.readFileSync(dumpPath, "utf8"));
2474
+ raw = JSON.parse(fs11.readFileSync(dumpPath, "utf8"));
2262
2475
  } catch (err) {
2263
2476
  console.error(`import-pm2: cannot parse ${dumpPath}: ${err.message}`);
2264
2477
  return 1;
@@ -2301,11 +2514,11 @@ async function run8(args) {
2301
2514
  }
2302
2515
  ]
2303
2516
  };
2304
- const tmpPath = path10.join(
2305
- os6.tmpdir(),
2517
+ const tmpPath = path11.join(
2518
+ os7.tmpdir(),
2306
2519
  `relife2-import-${Date.now()}-${Math.random().toString(36).slice(2)}.cjs`
2307
2520
  );
2308
- fs9.writeFileSync(tmpPath, `module.exports = ${JSON.stringify(tmpConfig)}`, "utf8");
2521
+ fs11.writeFileSync(tmpPath, `module.exports = ${JSON.stringify(tmpConfig)}`, "utf8");
2309
2522
  try {
2310
2523
  const res = await client.call("start", {
2311
2524
  target: { type: "config", path: tmpPath }
@@ -2321,7 +2534,7 @@ async function run8(args) {
2321
2534
  console.error(`import ${name}: ${err.message}`);
2322
2535
  } finally {
2323
2536
  try {
2324
- fs9.unlinkSync(tmpPath);
2537
+ fs11.unlinkSync(tmpPath);
2325
2538
  } catch {
2326
2539
  }
2327
2540
  }
@@ -2338,7 +2551,7 @@ imported ${imported} app(s) from ${dumpPath}`);
2338
2551
  }
2339
2552
 
2340
2553
  // src/commands/inspectconfig.ts
2341
- import path11 from "node:path";
2554
+ import path12 from "node:path";
2342
2555
  async function run9(args) {
2343
2556
  const { positionals } = parseCliArgs(args);
2344
2557
  const file = positionals[0];
@@ -2348,7 +2561,7 @@ async function run9(args) {
2348
2561
  }
2349
2562
  let loaded;
2350
2563
  try {
2351
- loaded = await loadConfigFile(path11.resolve(file));
2564
+ loaded = await loadConfigFile(path12.resolve(file));
2352
2565
  } catch (err) {
2353
2566
  console.error(`relife2: ${err.message}`);
2354
2567
  return 1;
@@ -2417,7 +2630,7 @@ function printTable(rows) {
2417
2630
  }
2418
2631
 
2419
2632
  // src/commands/logs.ts
2420
- import * as fs10 from "node:fs";
2633
+ import * as fs12 from "node:fs";
2421
2634
  var VALUE_OPTS2 = /* @__PURE__ */ new Set(["lines"]);
2422
2635
  async function run12(args) {
2423
2636
  const { positionals, opts } = parseCliArgs(args, VALUE_OPTS2);
@@ -2457,7 +2670,7 @@ function parseLines(v) {
2457
2670
  }
2458
2671
  function printTail(file, lines) {
2459
2672
  try {
2460
- const text = fs10.readFileSync(file, "utf8");
2673
+ const text = fs12.readFileSync(file, "utf8");
2461
2674
  const all = text.split(/\r?\n/);
2462
2675
  if (all.length > 0 && all[all.length - 1] === "") all.pop();
2463
2676
  const slice = all.slice(Math.max(0, all.length - lines));
@@ -2483,13 +2696,13 @@ async function followFiles(files) {
2483
2696
  const from = positions.get(f) ?? 0;
2484
2697
  if (size > from) {
2485
2698
  try {
2486
- const fd = fs10.openSync(f, "r");
2699
+ const fd = fs12.openSync(f, "r");
2487
2700
  try {
2488
2701
  const buf = Buffer.alloc(size - from);
2489
- fs10.readSync(fd, buf, 0, buf.length, from);
2702
+ fs12.readSync(fd, buf, 0, buf.length, from);
2490
2703
  process.stdout.write(buf);
2491
2704
  } finally {
2492
- fs10.closeSync(fd);
2705
+ fs12.closeSync(fd);
2493
2706
  }
2494
2707
  } catch {
2495
2708
  }
@@ -2500,7 +2713,7 @@ async function followFiles(files) {
2500
2713
  }
2501
2714
  function fileSize(file) {
2502
2715
  try {
2503
- return fs10.statSync(file).size;
2716
+ return fs12.statSync(file).size;
2504
2717
  } catch {
2505
2718
  return 0;
2506
2719
  }
@@ -2790,7 +3003,7 @@ async function run20(_args) {
2790
3003
  }
2791
3004
 
2792
3005
  // src/commands/start.ts
2793
- import path12 from "node:path";
3006
+ import path13 from "node:path";
2794
3007
  var VALUE_OPTS4 = /* @__PURE__ */ new Set([
2795
3008
  "name",
2796
3009
  "env",
@@ -2839,7 +3052,7 @@ async function run21(args) {
2839
3052
  const res = await client.call("start", {
2840
3053
  target: {
2841
3054
  type: isConfig ? "config" : "script",
2842
- path: path12.resolve(target),
3055
+ path: path13.resolve(target),
2843
3056
  name,
2844
3057
  args: passthrough.length > 0 ? passthrough : void 0,
2845
3058
  envName: envName ?? void 0,
@@ -2866,11 +3079,11 @@ async function run21(args) {
2866
3079
 
2867
3080
  // src/commands/startup.ts
2868
3081
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
2869
- import * as fs11 from "node:fs";
2870
- import os7 from "node:os";
2871
- import path13 from "node:path";
3082
+ import * as fs13 from "node:fs";
3083
+ import os8 from "node:os";
3084
+ import path14 from "node:path";
2872
3085
  import { fileURLToPath } from "node:url";
2873
- var SYSTEMD_USER_DIR = path13.join(os7.homedir(), ".config", "systemd", "user");
3086
+ var SYSTEMD_USER_DIR = path14.join(os8.homedir(), ".config", "systemd", "user");
2874
3087
  var UNIT_NAME = "relife2.service";
2875
3088
  function getRelife2Bin() {
2876
3089
  const which = spawnSync2(process.platform === "win32" ? "where" : "which", ["relife2"], {
@@ -2888,13 +3101,13 @@ function getRelife2Bin() {
2888
3101
  }
2889
3102
  try {
2890
3103
  const thisFile = fileURLToPath(import.meta.url);
2891
- const cliPath = path13.resolve(path13.dirname(thisFile), "..", "cli.js");
2892
- if (fs11.existsSync(cliPath)) {
3104
+ const cliPath = path14.resolve(path14.dirname(thisFile), "..", "cli.js");
3105
+ if (fs13.existsSync(cliPath)) {
2893
3106
  return `node ${cliPath}`;
2894
3107
  }
2895
3108
  } catch {
2896
3109
  }
2897
- return `node ${path13.resolve(arg1 ?? process.cwd())}`;
3110
+ return `node ${path14.resolve(arg1 ?? process.cwd())}`;
2898
3111
  }
2899
3112
  function generateUnit(bin) {
2900
3113
  const envHint = process.env.RELIFE2_DIR !== void 0 ? `
@@ -2915,6 +3128,49 @@ Environment=RELIFE2_DAEMON=1${envHint}
2915
3128
  WantedBy=default.target
2916
3129
  `;
2917
3130
  }
3131
+ function isLingerEnabled(user) {
3132
+ try {
3133
+ const out = execSync(`loginctl show-user ${user} -p Linger --value`, {
3134
+ encoding: "utf8",
3135
+ stdio: ["ignore", "pipe", "ignore"],
3136
+ timeout: 3e3
3137
+ });
3138
+ const v = out.trim().toLowerCase();
3139
+ if (v === "yes") return true;
3140
+ if (v === "no") return false;
3141
+ } catch {
3142
+ }
3143
+ try {
3144
+ const out = execSync(`loginctl show-user ${user}`, {
3145
+ encoding: "utf8",
3146
+ stdio: ["ignore", "pipe", "ignore"],
3147
+ timeout: 3e3
3148
+ });
3149
+ if (/Linger=yes/i.test(out)) return true;
3150
+ if (/Linger=no/i.test(out)) return false;
3151
+ } catch {
3152
+ }
3153
+ return null;
3154
+ }
3155
+ function tryEnableLinger(user) {
3156
+ const attempts = [
3157
+ { cmd: `loginctl enable-linger ${user}`, label: "loginctl" },
3158
+ { cmd: `sudo -n loginctl enable-linger ${user}`, label: "sudo -n" },
3159
+ { cmd: `sudo loginctl enable-linger ${user}`, label: "sudo" }
3160
+ ];
3161
+ for (const a of attempts) {
3162
+ try {
3163
+ execSync(a.cmd, { stdio: ["ignore", "pipe", "pipe"], timeout: 8e3 });
3164
+ const enabled = isLingerEnabled(user);
3165
+ if (enabled === true || enabled === null) {
3166
+ console.log(`enabled linger for user ${user} via ${a.label}`);
3167
+ return true;
3168
+ }
3169
+ } catch {
3170
+ }
3171
+ }
3172
+ return false;
3173
+ }
2918
3174
  async function run22(_args) {
2919
3175
  if (process.platform === "win32") {
2920
3176
  console.error(
@@ -2928,47 +3184,67 @@ async function run22(_args) {
2928
3184
  }
2929
3185
  const bin = getRelife2Bin();
2930
3186
  const unitContent = generateUnit(bin);
2931
- const unitPath = path13.join(SYSTEMD_USER_DIR, UNIT_NAME);
3187
+ const unitPath = path14.join(SYSTEMD_USER_DIR, UNIT_NAME);
2932
3188
  try {
2933
- fs11.mkdirSync(SYSTEMD_USER_DIR, { recursive: true });
3189
+ fs13.mkdirSync(SYSTEMD_USER_DIR, { recursive: true });
2934
3190
  } catch (err) {
2935
3191
  console.error(`cannot create ${SYSTEMD_USER_DIR}: ${err.message}`);
2936
3192
  return 1;
2937
3193
  }
2938
3194
  try {
2939
- fs11.writeFileSync(unitPath, unitContent);
3195
+ fs13.writeFileSync(unitPath, unitContent);
2940
3196
  console.log(`written systemd user unit: ${unitPath}`);
2941
3197
  } catch (err) {
2942
3198
  console.error(`cannot write ${unitPath}: ${err.message}`);
2943
3199
  return 1;
2944
3200
  }
2945
- try {
2946
- const user = process.env.USER ?? os7.userInfo().username ?? "root";
2947
- execSync(`loginctl enable-linger ${user}`, { stdio: ["ignore", "pipe", "pipe"] });
2948
- console.log(`enabled linger for user ${user}`);
2949
- } catch (err) {
2950
- console.error(
2951
- `warning: could not run 'loginctl enable-linger' (is systemd-logind running?): ${err instanceof Error ? err.message : String(err)}`
2952
- );
2953
- console.error("your apps will only start after you log in. Run manually:");
2954
- console.error(` loginctl enable-linger ${process.env.USER ?? os7.userInfo().username}`);
3201
+ const user = process.env.USER ?? process.env.LOGNAME ?? os8.userInfo().username ?? "opc";
3202
+ const lingerState = isLingerEnabled(user);
3203
+ if (lingerState === true) {
3204
+ console.log(`linger already enabled for user ${user}`);
3205
+ } else if (lingerState === false) {
3206
+ console.log(`linger is disabled for ${user} \u2014 enabling\u2026`);
3207
+ const ok = tryEnableLinger(user);
3208
+ if (!ok) {
3209
+ console.error(`warning: could not enable linger (is systemd-logind running?)`);
3210
+ console.error(`your apps will only start after you log in. Run manually:`);
3211
+ console.error(` sudo loginctl enable-linger ${user}`);
3212
+ }
3213
+ } else {
3214
+ const ok = tryEnableLinger(user);
3215
+ if (!ok) {
3216
+ console.error(`note: could not verify linger status (no logind?) \u2014 skipping`);
3217
+ }
2955
3218
  }
2956
3219
  try {
2957
- execSync("systemctl --user daemon-reload", { stdio: ["ignore", "pipe", "pipe"] });
2958
- execSync(`systemctl --user enable ${UNIT_NAME}`, { stdio: ["ignore", "pipe", "pipe"] });
3220
+ execSync("systemctl --user daemon-reload", {
3221
+ stdio: ["ignore", "pipe", "pipe"],
3222
+ timeout: 8e3
3223
+ });
3224
+ execSync(`systemctl --user enable ${UNIT_NAME}`, {
3225
+ stdio: ["ignore", "pipe", "pipe"],
3226
+ timeout: 8e3
3227
+ });
2959
3228
  console.log(`enabled and reloaded: systemctl --user enable ${UNIT_NAME}`);
3229
+ try {
3230
+ execSync(`systemctl --user start ${UNIT_NAME}`, {
3231
+ stdio: ["ignore", "pipe", "pipe"],
3232
+ timeout: 8e3
3233
+ });
3234
+ console.log(`started: systemctl --user start ${UNIT_NAME}`);
3235
+ } catch {
3236
+ }
2960
3237
  } catch (err) {
2961
3238
  console.error(
2962
3239
  `warning: could not enable unit (is systemd available?): ${err instanceof Error ? err.message : String(err)}`
2963
3240
  );
2964
3241
  console.error("run manually:");
2965
3242
  console.error(" systemctl --user daemon-reload");
2966
- console.error(` systemctl --user enable ${UNIT_NAME}`);
3243
+ console.error(` systemctl --user enable --now ${UNIT_NAME}`);
2967
3244
  }
2968
3245
  console.log("");
2969
3246
  console.log("relife2 will now start automatically on boot.");
2970
3247
  console.log(`ExecStart is: ${bin}`);
2971
- console.log("To start now, run: systemctl --user start relife2.service");
2972
3248
  console.log("To check status: systemctl --user status relife2.service");
2973
3249
  return 0;
2974
3250
  }
@@ -3164,14 +3440,14 @@ function detectRuntime() {
3164
3440
  }
3165
3441
 
3166
3442
  // src/daemon/daemon.ts
3167
- import * as fs18 from "node:fs";
3168
- import os8 from "node:os";
3169
- import path19 from "node:path";
3443
+ import * as fs20 from "node:fs";
3444
+ import os9 from "node:os";
3445
+ import path20 from "node:path";
3170
3446
  import * as zlib from "node:zlib";
3171
3447
 
3172
3448
  // src/daemon/applog.ts
3173
- import * as fs12 from "node:fs";
3174
- import path14 from "node:path";
3449
+ import * as fs14 from "node:fs";
3450
+ import path15 from "node:path";
3175
3451
 
3176
3452
  // src/util/datefmt.ts
3177
3453
  function formatDate(pattern, date = /* @__PURE__ */ new Date()) {
@@ -3196,12 +3472,12 @@ function formatDate(pattern, date = /* @__PURE__ */ new Date()) {
3196
3472
 
3197
3473
  // src/daemon/applog.ts
3198
3474
  var DEFAULT_LOG_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS Z";
3199
- var DEFAULT_LOGS_DIR = (base) => path14.join(base, "logs");
3475
+ var DEFAULT_LOGS_DIR = (base) => path15.join(base, "logs");
3200
3476
  function resolveLogPaths(app, base) {
3201
3477
  const safe = app.name.replace(/[^\w.-]/g, "_");
3202
- const out = app.outFile ? path14.resolve(app.cwd, app.outFile) : path14.join(DEFAULT_LOGS_DIR(base), `${safe}.out.log`);
3478
+ const out = app.outFile ? path15.resolve(app.cwd, app.outFile) : path15.join(DEFAULT_LOGS_DIR(base), `${safe}.out.log`);
3203
3479
  if (app.mergeLogs) return { out, err: null };
3204
- const err = app.errFile ? path14.resolve(app.cwd, app.errFile) : path14.join(DEFAULT_LOGS_DIR(base), `${safe}.err.log`);
3480
+ const err = app.errFile ? path15.resolve(app.cwd, app.errFile) : path15.join(DEFAULT_LOGS_DIR(base), `${safe}.err.log`);
3205
3481
  return { out, err };
3206
3482
  }
3207
3483
  var AppLogSink = class {
@@ -3246,8 +3522,8 @@ var AppLogSink = class {
3246
3522
  const out = `${this.prefix}${line}
3247
3523
  `;
3248
3524
  try {
3249
- ensureDirSync(path14.dirname(target));
3250
- fs12.appendFileSync(target, out);
3525
+ ensureDirSync(path15.dirname(target));
3526
+ fs14.appendFileSync(target, out);
3251
3527
  } catch {
3252
3528
  }
3253
3529
  }
@@ -3255,18 +3531,18 @@ var AppLogSink = class {
3255
3531
  };
3256
3532
 
3257
3533
  // src/daemon/logger.ts
3258
- import fs13 from "node:fs";
3259
- import path15 from "node:path";
3534
+ import fs15 from "node:fs";
3535
+ import path16 from "node:path";
3260
3536
  function createDaemonLogger(base) {
3261
- const logsDir = path15.join(base, "logs");
3537
+ const logsDir = path16.join(base, "logs");
3262
3538
  ensureDirSync(logsDir);
3263
- const mainFile = path15.join(logsDir, "daemon.log");
3539
+ const mainFile = path16.join(logsDir, "daemon.log");
3264
3540
  const outRoot = logsDir;
3265
3541
  function log(msg, extra) {
3266
3542
  const line = `${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), msg, ...extra ?? {} })}
3267
3543
  `;
3268
3544
  try {
3269
- fs13.appendFileSync(mainFile, line);
3545
+ fs15.appendFileSync(mainFile, line);
3270
3546
  } catch {
3271
3547
  }
3272
3548
  }
@@ -3275,13 +3551,13 @@ function createDaemonLogger(base) {
3275
3551
  }
3276
3552
  function appOut(name, chunk) {
3277
3553
  try {
3278
- fs13.appendFileSync(path15.join(outRoot, `${safeName(name)}.out.log`), chunk);
3554
+ fs15.appendFileSync(path16.join(outRoot, `${safeName(name)}.out.log`), chunk);
3279
3555
  } catch {
3280
3556
  }
3281
3557
  }
3282
3558
  function appErr(name, chunk) {
3283
3559
  try {
3284
- fs13.appendFileSync(path15.join(outRoot, `${safeName(name)}.err.log`), chunk);
3560
+ fs15.appendFileSync(path16.join(outRoot, `${safeName(name)}.err.log`), chunk);
3285
3561
  } catch {
3286
3562
  }
3287
3563
  }
@@ -3290,12 +3566,12 @@ function createDaemonLogger(base) {
3290
3566
 
3291
3567
  // src/daemon/pm.ts
3292
3568
  import { spawn as spawn3 } from "node:child_process";
3293
- import * as fs15 from "node:fs";
3294
- import path17 from "node:path";
3569
+ import * as fs17 from "node:fs";
3570
+ import path18 from "node:path";
3295
3571
 
3296
3572
  // src/daemon/command.ts
3297
3573
  import { existsSync as existsSync8 } from "node:fs";
3298
- import path16 from "node:path";
3574
+ import path17 from "node:path";
3299
3575
  import process2 from "node:process";
3300
3576
  var JS_RE = /\.(?:c?js|mjs|ts|mts|cts)$/i;
3301
3577
  function isJsScript(p) {
@@ -3305,10 +3581,10 @@ function isBunProject2(scriptDir) {
3305
3581
  let dir = scriptDir;
3306
3582
  const home = process2.env.HOME ?? "";
3307
3583
  while (true) {
3308
- if (existsSync8(path16.join(dir, "bun.lockb")) || existsSync8(path16.join(dir, "bun.lock"))) {
3584
+ if (existsSync8(path17.join(dir, "bun.lockb")) || existsSync8(path17.join(dir, "bun.lock"))) {
3309
3585
  return true;
3310
3586
  }
3311
- const parent = path16.dirname(dir);
3587
+ const parent = path17.dirname(dir);
3312
3588
  if (parent === dir) break;
3313
3589
  if (home !== "" && dir === home) break;
3314
3590
  dir = parent;
@@ -3344,7 +3620,7 @@ function buildSpawnSpec(app) {
3344
3620
 
3345
3621
  // src/daemon/memproc.ts
3346
3622
  import { execFileSync } from "node:child_process";
3347
- import * as fs14 from "node:fs";
3623
+ import * as fs16 from "node:fs";
3348
3624
  function processRss(pid) {
3349
3625
  if (pid <= 0) return void 0;
3350
3626
  try {
@@ -3360,7 +3636,7 @@ function processRss(pid) {
3360
3636
  }
3361
3637
  }
3362
3638
  function rssFromProc(pid) {
3363
- const status = fs14.readFileSync(`/proc/${pid}/status`, "utf8");
3639
+ const status = fs16.readFileSync(`/proc/${pid}/status`, "utf8");
3364
3640
  const match = /^VmRSS:\s+(\d+)\s+kB/m.exec(status);
3365
3641
  if (match?.[1] === void 0) return void 0;
3366
3642
  return Number(match[1]) * 1024;
@@ -3435,7 +3711,7 @@ var ProcessManager = class {
3435
3711
  });
3436
3712
  record.config.cwd = cwdFix.path;
3437
3713
  }
3438
- if (path17.isAbsolute(record.config.script) && !fs15.existsSync(record.config.script)) {
3714
+ if (path18.isAbsolute(record.config.script) && !fs17.existsSync(record.config.script)) {
3439
3715
  const sFix = fixScript(record.config.script, record.config.cwd, record.config.sourceDir);
3440
3716
  if (sFix.fixed && sFix.reason) {
3441
3717
  this.logger.log(sFix.reason);
@@ -3449,7 +3725,7 @@ var ProcessManager = class {
3449
3725
  } else if (!sFix.fixed) {
3450
3726
  this.fail(
3451
3727
  record,
3452
- `script not found: '${record.config.script}'. ${sFix.reason ?? ""} Run 'relife2 find --root ${path17.dirname(record.config.sourceDir)}' to locate configs or fix the config.`
3728
+ `script not found: '${record.config.script}'. ${sFix.reason ?? ""} Run 'relife2 find --root ${path18.dirname(record.config.sourceDir)}' to locate configs or fix the config.`
3453
3729
  );
3454
3730
  this.store.saveSync();
3455
3731
  return;
@@ -3765,19 +4041,19 @@ var ProcessManager = class {
3765
4041
  const watch3 = record.config.watch;
3766
4042
  if (watch3 === void 0 || watch3 === false) return;
3767
4043
  if (this.watchers.has(record.name)) return;
3768
- const rawPaths = Array.isArray(watch3) ? watch3.map((p) => path17.resolve(record.config.cwd, p)) : [record.config.cwd];
4044
+ const rawPaths = Array.isArray(watch3) ? watch3.map((p) => path18.resolve(record.config.cwd, p)) : [record.config.cwd];
3769
4045
  const watchers = [];
3770
4046
  for (const target of rawPaths) {
3771
4047
  try {
3772
- if (!fs15.existsSync(target)) {
4048
+ if (!fs17.existsSync(target)) {
3773
4049
  this.logger.log(`watch: path '${target}' does not exist yet; skipping`);
3774
4050
  continue;
3775
4051
  }
3776
- const stat = fs15.statSync(target);
4052
+ const stat = fs17.statSync(target);
3777
4053
  if (!stat.isDirectory()) {
3778
- const dir = path17.dirname(target);
3779
- const base = path17.basename(target);
3780
- const watcher = fs15.watch(dir, (_eventType, filename) => {
4054
+ const dir = path18.dirname(target);
4055
+ const base = path18.basename(target);
4056
+ const watcher = fs17.watch(dir, (_eventType, filename) => {
3781
4057
  if (filename !== null && filename !== base) return;
3782
4058
  this.onWatchChange(record.name, target);
3783
4059
  });
@@ -3785,7 +4061,7 @@ var ProcessManager = class {
3785
4061
  continue;
3786
4062
  }
3787
4063
  if (process.platform === "win32" || process.platform === "darwin") {
3788
- const watcher = fs15.watch(target, { recursive: true }, () => {
4064
+ const watcher = fs17.watch(target, { recursive: true }, () => {
3789
4065
  this.onWatchChange(record.name, target);
3790
4066
  });
3791
4067
  watchers.push(watcher);
@@ -3793,7 +4069,7 @@ var ProcessManager = class {
3793
4069
  const dirs = this.collectSubDirs(target);
3794
4070
  for (const dir of dirs) {
3795
4071
  try {
3796
- const watcher = fs15.watch(dir, () => {
4072
+ const watcher = fs17.watch(dir, () => {
3797
4073
  this.onWatchChange(record.name, target);
3798
4074
  });
3799
4075
  watchers.push(watcher);
@@ -3820,7 +4096,7 @@ var ProcessManager = class {
3820
4096
  const dir = queue.shift();
3821
4097
  let entries;
3822
4098
  try {
3823
- entries = fs15.readdirSync(dir, { withFileTypes: true });
4099
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
3824
4100
  } catch {
3825
4101
  continue;
3826
4102
  }
@@ -3828,7 +4104,7 @@ var ProcessManager = class {
3828
4104
  if (!entry.isDirectory()) continue;
3829
4105
  if (entry.name.startsWith(".")) continue;
3830
4106
  if (entry.name === "node_modules" || entry.name === ".git") continue;
3831
- const full = path17.join(dir, entry.name);
4107
+ const full = path18.join(dir, entry.name);
3832
4108
  if (seen.has(full)) continue;
3833
4109
  seen.add(full);
3834
4110
  out.push(full);
@@ -3928,17 +4204,17 @@ var ProcessManager = class {
3928
4204
  };
3929
4205
 
3930
4206
  // src/daemon/state.ts
3931
- import fs16 from "node:fs";
3932
- import path18 from "node:path";
4207
+ import fs18 from "node:fs";
4208
+ import path19 from "node:path";
3933
4209
  var StateStore = class {
3934
4210
  apps = /* @__PURE__ */ new Map();
3935
4211
  snapshotPath;
3936
4212
  journalPath;
3937
4213
  constructor(base) {
3938
- const stateDir = path18.join(base, "state");
4214
+ const stateDir = path19.join(base, "state");
3939
4215
  ensureDirSync(stateDir);
3940
- this.snapshotPath = path18.join(stateDir, "snapshot.json");
3941
- this.journalPath = path18.join(stateDir, "journal.jsonl");
4216
+ this.snapshotPath = path19.join(stateDir, "snapshot.json");
4217
+ this.journalPath = path19.join(stateDir, "journal.jsonl");
3942
4218
  }
3943
4219
  load() {
3944
4220
  const data = readJsonFile(this.snapshotPath);
@@ -3975,7 +4251,7 @@ var StateStore = class {
3975
4251
  const line = `${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), type, ...data ?? {} })}
3976
4252
  `;
3977
4253
  try {
3978
- fs16.appendFileSync(this.journalPath, line);
4254
+ fs18.appendFileSync(this.journalPath, line);
3979
4255
  } catch {
3980
4256
  }
3981
4257
  }
@@ -3987,7 +4263,7 @@ var StateStore = class {
3987
4263
  };
3988
4264
 
3989
4265
  // src/daemon/transport.ts
3990
- import fs17 from "node:fs";
4266
+ import fs19 from "node:fs";
3991
4267
  import net2 from "node:net";
3992
4268
  async function createDaemonTransport(base) {
3993
4269
  return process.platform === "win32" ? createWinTransport(base) : createPosixTransport(base);
@@ -4001,7 +4277,7 @@ async function createPosixTransport(base) {
4001
4277
  if (err.code !== "EADDRINUSE") return null;
4002
4278
  if (await probeActive(sockPath)) return null;
4003
4279
  try {
4004
- fs17.unlinkSync(sockPath);
4280
+ fs19.unlinkSync(sockPath);
4005
4281
  } catch {
4006
4282
  }
4007
4283
  }
@@ -4054,7 +4330,7 @@ function probeActive(sockPath) {
4054
4330
  }
4055
4331
  function readPortFile(base) {
4056
4332
  try {
4057
- const raw = fs17.readFileSync(portFilePath(base), "utf8");
4333
+ const raw = fs19.readFileSync(portFilePath(base), "utf8");
4058
4334
  return Number.parseInt(raw.trim(), 10) || null;
4059
4335
  } catch {
4060
4336
  return null;
@@ -4062,7 +4338,7 @@ function readPortFile(base) {
4062
4338
  }
4063
4339
  function writePortFile(base, port) {
4064
4340
  try {
4065
- fs17.writeFileSync(portFilePath(base), String(port));
4341
+ fs19.writeFileSync(portFilePath(base), String(port));
4066
4342
  } catch {
4067
4343
  }
4068
4344
  }
@@ -4071,7 +4347,7 @@ function writePortFile(base, port) {
4071
4347
  async function runDaemon() {
4072
4348
  const data = dataDir();
4073
4349
  const runtime = runtimeDir();
4074
- ensureDirSync(path19.join(data, "logs"));
4350
+ ensureDirSync(path20.join(data, "logs"));
4075
4351
  const logger = createDaemonLogger(data);
4076
4352
  logger.log("daemon starting", { pid: process.pid, version: VERSION, platform: process.platform });
4077
4353
  const lock = acquireLock(runtime);
@@ -4286,8 +4562,8 @@ var METHODS = {
4286
4562
  const files = paths.err === null ? [paths.out] : [paths.out, paths.err];
4287
4563
  for (const f of files) {
4288
4564
  try {
4289
- ensureDirSync(path19.dirname(f));
4290
- fs18.writeFileSync(f, "");
4565
+ ensureDirSync(path20.dirname(f));
4566
+ fs20.writeFileSync(f, "");
4291
4567
  } catch (err) {
4292
4568
  logger.log(`flush ${rec.name}: cannot truncate ${f}: ${err.message}`);
4293
4569
  }
@@ -4320,7 +4596,7 @@ var METHODS = {
4320
4596
  for (const f of files) {
4321
4597
  if (!pathExists(f)) continue;
4322
4598
  try {
4323
- const stat = fs18.statSync(f);
4599
+ const stat = fs20.statSync(f);
4324
4600
  if (stat.size >= max) {
4325
4601
  rotateFile(f, n);
4326
4602
  rotated.push(f);
@@ -4374,11 +4650,11 @@ var METHODS = {
4374
4650
  },
4375
4651
  doctor: (_params, { store, base, transport }) => {
4376
4652
  const checks = [];
4377
- const snapshotPath = path19.join(base, "state", "snapshot.json");
4653
+ const snapshotPath = path20.join(base, "state", "snapshot.json");
4378
4654
  const stateIssues = [];
4379
4655
  if (pathExists(snapshotPath)) {
4380
4656
  try {
4381
- const data = JSON.parse(fs18.readFileSync(snapshotPath, "utf8"));
4657
+ const data = JSON.parse(fs20.readFileSync(snapshotPath, "utf8"));
4382
4658
  if (data.schema === 1 && typeof data.apps === "object") {
4383
4659
  stateIssues.push({
4384
4660
  severity: "ok",
@@ -4411,11 +4687,11 @@ var METHODS = {
4411
4687
  ok: !stateIssues.some((i) => i.severity === "error"),
4412
4688
  issues: stateIssues
4413
4689
  });
4414
- const lockFile = path19.join(runtimeDir(), "daemon.lock");
4690
+ const lockFile = path20.join(runtimeDir(), "daemon.lock");
4415
4691
  const lockIssues = [];
4416
4692
  if (pathExists(lockFile)) {
4417
4693
  try {
4418
- const lock = JSON.parse(fs18.readFileSync(lockFile, "utf8"));
4694
+ const lock = JSON.parse(fs20.readFileSync(lockFile, "utf8"));
4419
4695
  if (typeof lock.pid === "number" && isAlive(lock.pid)) {
4420
4696
  lockIssues.push({
4421
4697
  severity: "ok",
@@ -4526,7 +4802,7 @@ async function startApps(target, ctx) {
4526
4802
  if (target.interpreter !== void 0) input.interpreter = target.interpreter;
4527
4803
  if (target.cwd !== void 0) input.cwd = target.cwd;
4528
4804
  if (target.cliOverrides) Object.assign(input, target.cliOverrides);
4529
- const single = normalizeSingleApp(input, path19.dirname(target.path), target.envName);
4805
+ const single = normalizeSingleApp(input, path20.dirname(target.path), target.envName);
4530
4806
  apps = [single.app];
4531
4807
  warnings = single.warnings;
4532
4808
  }
@@ -4591,7 +4867,7 @@ function expandTargets(store, name) {
4591
4867
  function applyCliOverrides(app, overrides) {
4592
4868
  if (overrides.instances !== void 0) {
4593
4869
  const v = overrides.instances;
4594
- if (v === "max" || v === "MAX") app.instances = os8.cpus().length || 1;
4870
+ if (v === "max" || v === "MAX") app.instances = os9.cpus().length || 1;
4595
4871
  else {
4596
4872
  const n = Number(v);
4597
4873
  if (Number.isFinite(n) && n >= 1) app.instances = Math.round(n);
@@ -4651,15 +4927,15 @@ function rotateFile(file, retain) {
4651
4927
  const newName = `${file}.${i + 1}.gz`;
4652
4928
  if (pathExists(oldName)) {
4653
4929
  if (i + 1 >= retain) {
4654
- fs18.unlinkSync(oldName);
4930
+ fs20.unlinkSync(oldName);
4655
4931
  } else {
4656
4932
  if (i === 0) {
4657
- const content = fs18.readFileSync(file);
4658
- fs18.unlinkSync(file);
4933
+ const content = fs20.readFileSync(file);
4934
+ fs20.unlinkSync(file);
4659
4935
  const gz = zlib.gzipSync(content);
4660
- fs18.writeFileSync(newName, gz);
4936
+ fs20.writeFileSync(newName, gz);
4661
4937
  } else {
4662
- fs18.renameSync(oldName, newName);
4938
+ fs20.renameSync(oldName, newName);
4663
4939
  }
4664
4940
  }
4665
4941
  }
@@ -4747,6 +5023,7 @@ async function run25(argv) {
4747
5023
  return 0;
4748
5024
  }
4749
5025
  const impl = COMMANDS[command];
5026
+ if (!impl) return 1;
4750
5027
  return impl(args.slice(1));
4751
5028
  }
4752
5029
  if (hasFlag(args, "v", "version")) {