@abelxiaoxing/cadence 2.0.4 → 2.0.5

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.
@@ -5,7 +5,7 @@
5
5
  import { spawn } from "node:child_process";
6
6
  import { existsSync, mkdirSync as mkdirSync4 } from "node:fs";
7
7
  import { request } from "node:http";
8
- import { dirname as dirname8, join as join8, resolve as resolve5 } from "node:path";
8
+ import { dirname as dirname8, join as join10, resolve as resolve5 } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
 
11
11
  // src/task-types.ts
@@ -2484,14 +2484,27 @@ import {
2484
2484
  writeFileSync as writeFileSync2
2485
2485
  } from "node:fs";
2486
2486
  import { homedir as homedir2 } from "node:os";
2487
- import { delimiter, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
2487
+ import {
2488
+ delimiter,
2489
+ dirname as dirname2,
2490
+ isAbsolute as isAbsolute2,
2491
+ join as join2,
2492
+ resolve as resolve2,
2493
+ sep as sep2,
2494
+ win32
2495
+ } from "node:path";
2488
2496
  var UNIT = "cadence.service";
2489
2497
  var MARKER = "# Generated by @abelxiaoxing/cadence";
2490
- function hostSystem(root) {
2498
+ function wscript(env) {
2499
+ const root = env.SystemRoot;
2500
+ return win32.join(root && win32.isAbsolute(root) ? root : "C:\\Windows", "System32", "wscript.exe");
2501
+ }
2502
+ function hostSystem(root, owner) {
2491
2503
  const run = (command, args) => {
2492
2504
  const r = spawnSync2(command, args, {
2493
2505
  encoding: "utf8",
2494
- stdio: ["ignore", "pipe", "ignore"]
2506
+ stdio: ["ignore", "pipe", "ignore"],
2507
+ windowsHide: true
2495
2508
  });
2496
2509
  return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
2497
2510
  };
@@ -2502,6 +2515,7 @@ function hostSystem(root) {
2502
2515
  node: process.execPath,
2503
2516
  root,
2504
2517
  unit: UNIT,
2518
+ uid: process.getuid?.(),
2505
2519
  systemctl: (args) => run("systemctl", ["--user", ...args]),
2506
2520
  linger: () => {
2507
2521
  const r = run("loginctl", [
@@ -2528,7 +2542,18 @@ function hostSystem(root) {
2528
2542
  "false"
2529
2543
  ], { stdio: "ignore", timeout: 20000 });
2530
2544
  return !r.error && r.status === 0;
2531
- }
2545
+ },
2546
+ launchctl: (args) => run("launchctl", args),
2547
+ reg: (args) => run("reg", args),
2548
+ launch: (path) => {
2549
+ const r = spawnSync2(wscript(process.env), [path], {
2550
+ stdio: "ignore",
2551
+ windowsHide: true,
2552
+ timeout: 20000
2553
+ });
2554
+ return !r.error && r.status === 0;
2555
+ },
2556
+ ...owner
2532
2557
  };
2533
2558
  }
2534
2559
  function expandHome(value, home) {
@@ -2602,7 +2627,7 @@ var argument = (value) => quoted(value).replaceAll("$", "$$$$");
2602
2627
  function buildUnit(u) {
2603
2628
  const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
2604
2629
  return [
2605
- `${MARKER} on Pi package install/update; upgrades rewrite this file.`,
2630
+ `${MARKER}; \`cadence server install\` and \`restart\` rewrite this file.`,
2606
2631
  "# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
2607
2632
  "[Unit]",
2608
2633
  "Description=Cadence shared task service and WebUI",
@@ -2630,19 +2655,20 @@ function buildUnit(u) {
2630
2655
  ].join(`
2631
2656
  `);
2632
2657
  }
2633
- function readGenerated(path) {
2658
+ function readGenerated(path, marker) {
2634
2659
  try {
2635
2660
  if (!lstatSync2(path).isFile())
2636
2661
  return null;
2637
- const text2 = readFileSync(path, "utf8");
2638
- return text2.startsWith(MARKER) ? text2 : null;
2662
+ const bytes = readFileSync(path);
2663
+ const text2 = bytes[0] === 255 && bytes[1] === 254 ? bytes.subarray(2).toString("utf16le") : bytes.toString("utf8");
2664
+ return text2.startsWith(marker) ? text2 : null;
2639
2665
  } catch (error) {
2640
2666
  if (error.code === "ENOENT")
2641
2667
  return;
2642
2668
  throw error;
2643
2669
  }
2644
2670
  }
2645
- function generatedWorkspace(text2) {
2671
+ function unitWorkspace(text2) {
2646
2672
  return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
2647
2673
  }
2648
2674
  function directory(path) {
@@ -2652,23 +2678,20 @@ function directory(path) {
2652
2678
  return false;
2653
2679
  }
2654
2680
  }
2655
- function unitStateHome(s) {
2656
- const state = s.env.XDG_STATE_HOME;
2657
- return state && isAbsolute2(state) ? state : join2(s.home, ".local", "state");
2681
+ function serviceStateHome(s) {
2682
+ const state = s.platform === "win32" ? s.env.LOCALAPPDATA : s.env.XDG_STATE_HOME;
2683
+ return state && isAbsolute2(state) ? state : join2(s.home, ...s.platform === "win32" ? ["AppData", "Local"] : [".local", "state"]);
2658
2684
  }
2659
- function selectWorkspace(s, requested, previous) {
2685
+ function selectWorkspace(s, requested, kept) {
2660
2686
  let chosen;
2661
2687
  if (requested) {
2662
2688
  chosen = expandHome(requested, s.home);
2663
2689
  if (!directory(chosen))
2664
2690
  throw new ContractError("invalid");
2665
2691
  chosen = resolve2(chosen);
2666
- } else {
2667
- const kept = generatedWorkspace(previous);
2668
- if (kept && directory(kept))
2669
- chosen = kept;
2670
- }
2671
- const landing = chosen ? effectiveWorkspace(chosen, unitStateHome(s), s.home) : { workspace: join2(s.home, LANDING_WORKSPACE), redirected: false };
2692
+ } else if (kept && directory(kept))
2693
+ chosen = kept;
2694
+ const landing = chosen ? effectiveWorkspace(chosen, serviceStateHome(s), s.home) : { workspace: join2(s.home, LANDING_WORKSPACE), redirected: false };
2672
2695
  if (landing.workspace !== chosen)
2673
2696
  mkdirSync2(landing.workspace, { recursive: true });
2674
2697
  return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
@@ -2690,7 +2713,18 @@ function regularFile(path) {
2690
2713
  return false;
2691
2714
  }
2692
2715
  }
2693
- function bootStart(s, attempt, interactive) {
2716
+ async function settle(s, workspace) {
2717
+ if (!workspace)
2718
+ return;
2719
+ const status = await s.stopOwner(workspace);
2720
+ if (status === "stopped")
2721
+ return;
2722
+ return {
2723
+ status: status === "quarantined" || status === "unavailable" ? status : "unconfirmed",
2724
+ workspace
2725
+ };
2726
+ }
2727
+ function bootStart(s, attempt) {
2694
2728
  const current = s.linger();
2695
2729
  if (current === undefined)
2696
2730
  return {};
@@ -2698,23 +2732,25 @@ function bootStart(s, attempt, interactive) {
2698
2732
  return { linger: "on" };
2699
2733
  if (!attempt)
2700
2734
  return { linger: "off" };
2701
- return { linger: s.enableLinger(interactive) ? "enabled" : "refused" };
2735
+ return {
2736
+ linger: s.enableLinger(false) || s.enableLinger(true) ? "enabled" : "refused"
2737
+ };
2702
2738
  }
2703
2739
  function configureAutostart(s, mode, requested, linger = false) {
2704
2740
  if (s.platform !== "linux")
2705
- return { status: "skipped" };
2706
- if (mode === "refresh" && (s.env.CADENCE_AUTOSTART === "0" || !piUserInstall(s)))
2707
- return { status: "skipped" };
2741
+ return { status: "unsupported" };
2708
2742
  if (s.systemctl(["show-environment"]).status !== 0)
2709
2743
  return { status: "unavailable" };
2710
2744
  const path = unitPath(s);
2711
- const previous = readGenerated(path);
2745
+ const previous = readGenerated(path, MARKER);
2712
2746
  if (previous === null)
2713
2747
  return { status: "foreign", unit: path };
2748
+ if (mode === "restart" && previous === undefined)
2749
+ return { status: "absent" };
2714
2750
  const sdk = sdkPackages(s.root);
2715
2751
  if (!sdk)
2716
2752
  return { status: "missing-pi-sdk" };
2717
- const { workspace, redirectedFrom } = selectWorkspace(s, requested ?? s.env.CADENCE_WORKSPACE, previous);
2753
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
2718
2754
  const agentDir = agentDirectory(s);
2719
2755
  const content = buildUnit({
2720
2756
  node: s.node,
@@ -2737,23 +2773,37 @@ function configureAutostart(s, mode, requested, linger = false) {
2737
2773
  };
2738
2774
  if (enabled.startsWith("masked"))
2739
2775
  return { status: "masked", ...result };
2740
- const wanted = mode === "install" || previous === undefined || enabled === "enabled";
2741
- if (wanted && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
2776
+ if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
2742
2777
  return { status: "failed", ...result };
2743
- if (!wanted && !active)
2744
- return { status: "disabled", ...result };
2745
- const boot = () => wanted ? bootStart(s, mode === "install" ? linger : previous === undefined && s.env.CADENCE_LINGER !== "0", mode === "install") : {};
2778
+ const boot = () => mode === "install" ? bootStart(s, linger) : {};
2746
2779
  if (!regularFile(join2(agentDir, "auth.json")))
2747
2780
  return { status: "waiting-for-pi-login", ...result, ...boot() };
2748
2781
  s.systemctl(["reset-failed", s.unit]);
2749
2782
  const started = s.systemctl(["start", s.unit]).status === 0;
2750
2783
  return { status: started ? "started" : "failed", ...result, ...boot() };
2751
2784
  }
2785
+ function stopAutostart(s) {
2786
+ if (s.platform !== "linux")
2787
+ return { status: "unsupported" };
2788
+ const path = unitPath(s);
2789
+ const previous = readGenerated(path, MARKER);
2790
+ if (previous === null)
2791
+ return { status: "foreign", unit: path };
2792
+ if (previous === undefined)
2793
+ return { status: "absent" };
2794
+ if (s.systemctl(["show-environment"]).status !== 0)
2795
+ return { status: "unavailable", unit: path };
2796
+ return {
2797
+ status: s.systemctl(["stop", s.unit]).status === 0 ? "stopped" : "failed",
2798
+ unit: path,
2799
+ workspace: unitWorkspace(previous)
2800
+ };
2801
+ }
2752
2802
  function removeAutostart(s) {
2753
2803
  if (s.platform !== "linux")
2754
- return { status: "skipped" };
2804
+ return { status: "unsupported" };
2755
2805
  const path = unitPath(s);
2756
- const previous = readGenerated(path);
2806
+ const previous = readGenerated(path, MARKER);
2757
2807
  if (previous === null)
2758
2808
  return { status: "foreign", unit: path };
2759
2809
  if (previous === undefined)
@@ -2769,9 +2819,9 @@ function removeAutostart(s) {
2769
2819
  }
2770
2820
  function autostartStatus(s) {
2771
2821
  if (s.platform !== "linux")
2772
- return { status: "skipped" };
2822
+ return { status: "unsupported" };
2773
2823
  const path = unitPath(s);
2774
- const previous = readGenerated(path);
2824
+ const previous = readGenerated(path, MARKER);
2775
2825
  if (previous === null)
2776
2826
  return { status: "foreign", unit: path };
2777
2827
  if (previous === undefined)
@@ -2780,8 +2830,8 @@ function autostartStatus(s) {
2780
2830
  status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
2781
2831
  enabled: s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown",
2782
2832
  unit: path,
2783
- workspace: generatedWorkspace(previous),
2784
- ...bootStart(s, false, false)
2833
+ workspace: unitWorkspace(previous),
2834
+ ...bootStart(s, false)
2785
2835
  };
2786
2836
  }
2787
2837
 
@@ -3641,13 +3691,277 @@ class OwnerLease {
3641
3691
  }
3642
3692
  }
3643
3693
 
3694
+ // src/service/launchd.ts
3695
+ import { rmSync as rmSync2 } from "node:fs";
3696
+ import { join as join5 } from "node:path";
3697
+ var LABEL = "com.abelxiaoxing.cadence";
3698
+ var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
3699
+ <!-- Generated by @abelxiaoxing/cadence`;
3700
+ function plistPath(s) {
3701
+ return join5(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
3702
+ }
3703
+ var xml = (value) => checked(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3704
+ function buildPlist(u) {
3705
+ const string = (value) => ` <string>${xml(value)}</string>`;
3706
+ return [
3707
+ '<?xml version="1.0" encoding="UTF-8"?>',
3708
+ "<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
3709
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
3710
+ '<plist version="1.0">',
3711
+ "<dict>",
3712
+ "\t<key>Label</key>",
3713
+ ` <string>${LABEL}</string>`,
3714
+ "\t<key>ProgramArguments</key>",
3715
+ "\t<array>",
3716
+ ...[u.node, u.cli, "start", "--foreground", "--workspace", u.workspace].map(string),
3717
+ "\t</array>",
3718
+ "\t<key>WorkingDirectory</key>",
3719
+ ` <string>${xml(u.workspace)}</string>`,
3720
+ "\t<key>EnvironmentVariables</key>",
3721
+ "\t<dict>",
3722
+ ...u.environment.flatMap(([k, v]) => [
3723
+ ` <key>${xml(k)}</key>`,
3724
+ string(v)
3725
+ ]),
3726
+ "\t</dict>",
3727
+ "\t<key>RunAtLoad</key>",
3728
+ "\t<true/>",
3729
+ "\t<key>ProcessType</key>",
3730
+ "\t<string>Background</string>",
3731
+ "\t<key>StandardOutPath</key>",
3732
+ ` <string>${xml(u.log)}</string>`,
3733
+ "\t<key>StandardErrorPath</key>",
3734
+ ` <string>${xml(u.log)}</string>`,
3735
+ "</dict>",
3736
+ "</plist>",
3737
+ ""
3738
+ ].join(`
3739
+ `);
3740
+ }
3741
+ function plistWorkspace(text2) {
3742
+ const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
3743
+ return m?.[1].replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
3744
+ }
3745
+ var domain = (s) => `gui/${s.uid}`;
3746
+ var target = (s) => `${domain(s)}/${LABEL}`;
3747
+ var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
3748
+ function disabled(s) {
3749
+ return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
3750
+ }
3751
+ async function configureLaunchAgent(s, mode, requested) {
3752
+ if (s.uid === undefined)
3753
+ return { status: "unavailable" };
3754
+ const path = plistPath(s);
3755
+ const previous = readGenerated(path, MARKER2);
3756
+ if (previous === null)
3757
+ return { status: "foreign", unit: path };
3758
+ if (mode === "restart" && previous === undefined)
3759
+ return { status: "absent" };
3760
+ const sdk = sdkPackages(s.root);
3761
+ if (!sdk)
3762
+ return { status: "missing-pi-sdk" };
3763
+ const kept = plistWorkspace(previous);
3764
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, kept);
3765
+ const refused2 = await settle(s, kept);
3766
+ if (refused2)
3767
+ return { ...refused2, unit: path };
3768
+ if (loaded(s))
3769
+ s.launchctl(["bootout", target(s)]);
3770
+ write(path, buildPlist({
3771
+ node: s.node,
3772
+ cli: join5(s.root, "src", "service-cli.mjs"),
3773
+ workspace,
3774
+ environment: serviceEnvironment(s),
3775
+ log: join5(s.home, "Library", "Logs", "cadence.log")
3776
+ }));
3777
+ const result = {
3778
+ unit: path,
3779
+ workspace,
3780
+ ...redirectedFrom ? { redirectedFrom } : {}
3781
+ };
3782
+ if (mode === "install")
3783
+ s.launchctl(["enable", target(s)]);
3784
+ else if (disabled(s))
3785
+ return { status: "disabled", ...result };
3786
+ if (!regularFile(join5(agentDirectory(s), "auth.json")))
3787
+ return { status: "waiting-for-pi-login", ...result };
3788
+ const started = s.launchctl(["bootstrap", domain(s), path]).status === 0;
3789
+ return { status: started ? "started" : "failed", ...result };
3790
+ }
3791
+ async function stopLaunchAgent(s) {
3792
+ const path = plistPath(s);
3793
+ const previous = readGenerated(path, MARKER2);
3794
+ if (previous === null)
3795
+ return { status: "foreign", unit: path };
3796
+ if (previous === undefined)
3797
+ return { status: "absent" };
3798
+ const workspace = plistWorkspace(previous);
3799
+ const refused2 = await settle(s, workspace);
3800
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path };
3801
+ }
3802
+ async function removeLaunchAgent(s) {
3803
+ const path = plistPath(s);
3804
+ const previous = readGenerated(path, MARKER2);
3805
+ if (previous === null)
3806
+ return { status: "foreign", unit: path };
3807
+ if (previous === undefined)
3808
+ return { status: "absent" };
3809
+ const refused2 = await settle(s, plistWorkspace(previous));
3810
+ if (refused2)
3811
+ return { ...refused2, unit: path };
3812
+ if (s.uid !== undefined && loaded(s))
3813
+ s.launchctl(["bootout", target(s)]);
3814
+ rmSync2(path);
3815
+ return { status: "removed", unit: path };
3816
+ }
3817
+ function launchAgentStatus(s) {
3818
+ const path = plistPath(s);
3819
+ const previous = readGenerated(path, MARKER2);
3820
+ if (previous === null)
3821
+ return { status: "foreign", unit: path };
3822
+ if (previous === undefined)
3823
+ return { status: "absent" };
3824
+ const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
3825
+ return {
3826
+ status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
3827
+ enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
3828
+ unit: path,
3829
+ workspace: plistWorkspace(previous)
3830
+ };
3831
+ }
3832
+
3833
+ // src/service/login-run.ts
3834
+ import { rmSync as rmSync3 } from "node:fs";
3835
+ import { isAbsolute as isAbsolute5, join as join6 } from "node:path";
3836
+ var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
3837
+ var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
3838
+ var RUN_VALUE = "Cadence";
3839
+ var MARKER3 = "' Generated by @abelxiaoxing/cadence";
3840
+ function launcherPath(s) {
3841
+ const local = s.env.LOCALAPPDATA;
3842
+ return join6(local && isAbsolute5(local) ? local : join6(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
3843
+ }
3844
+ var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
3845
+ function argument2(value) {
3846
+ if (checked(value).includes('"'))
3847
+ throw new ContractError("invalid");
3848
+ return `"${value}"`;
3849
+ }
3850
+ function buildLauncher(u) {
3851
+ const command = `${argument2(u.node)} ${argument2(u.cli)} start --workspace ${argument2(u.workspace)}`;
3852
+ return [
3853
+ `${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
3854
+ `' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
3855
+ 'Set shell = CreateObject("WScript.Shell")',
3856
+ ...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
3857
+ ...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
3858
+ `shell.CurrentDirectory = ${literal2(u.workspace)}`,
3859
+ `shell.Run ${literal2(command)}, 0, False`,
3860
+ ""
3861
+ ].join(`\r
3862
+ `);
3863
+ }
3864
+ var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
3865
+ function launcherWorkspace(text2) {
3866
+ return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
3867
+ }
3868
+ var runEntry = (path, s) => `"${wscript(s.env)}" "${path}"`;
3869
+ var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
3870
+ function approvedOff(s) {
3871
+ const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
3872
+ const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
3873
+ return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
3874
+ }
3875
+ async function configureLoginRun(s, mode, requested) {
3876
+ const path = launcherPath(s);
3877
+ const previous = readGenerated(path, MARKER3);
3878
+ if (previous === null)
3879
+ return { status: "foreign", unit: path };
3880
+ if (previous === undefined && present(s, RUN_KEY))
3881
+ return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
3882
+ if (mode === "restart" && previous === undefined)
3883
+ return { status: "absent" };
3884
+ const sdk = sdkPackages(s.root);
3885
+ if (!sdk)
3886
+ return { status: "missing-pi-sdk" };
3887
+ const kept = launcherWorkspace(previous);
3888
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, kept);
3889
+ const refused2 = await settle(s, kept);
3890
+ if (refused2)
3891
+ return { ...refused2, unit: path };
3892
+ const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
3893
+ write(path, utf16(buildLauncher({
3894
+ node: s.node,
3895
+ cli: join6(s.root, "src", "service-cli.mjs"),
3896
+ workspace,
3897
+ environment
3898
+ })));
3899
+ const result = {
3900
+ unit: path,
3901
+ workspace,
3902
+ ...redirectedFrom ? { redirectedFrom } : {}
3903
+ };
3904
+ if (mode === "install") {
3905
+ const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path, s)];
3906
+ if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
3907
+ return { status: "failed", ...result };
3908
+ s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
3909
+ }
3910
+ if (!regularFile(join6(agentDirectory(s), "auth.json")))
3911
+ return { status: "waiting-for-pi-login", ...result };
3912
+ return { status: s.launch(path) ? "started" : "failed", ...result };
3913
+ }
3914
+ async function stopLoginRun(s) {
3915
+ const path = launcherPath(s);
3916
+ const previous = readGenerated(path, MARKER3);
3917
+ if (previous === null)
3918
+ return { status: "foreign", unit: path };
3919
+ if (previous === undefined)
3920
+ return { status: "absent" };
3921
+ const workspace = launcherWorkspace(previous);
3922
+ const refused2 = await settle(s, workspace);
3923
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path };
3924
+ }
3925
+ async function removeLoginRun(s) {
3926
+ const path = launcherPath(s);
3927
+ const previous = readGenerated(path, MARKER3);
3928
+ if (previous === null)
3929
+ return { status: "foreign", unit: path };
3930
+ if (previous === undefined)
3931
+ return { status: "absent" };
3932
+ const refused2 = await settle(s, launcherWorkspace(previous));
3933
+ if (refused2)
3934
+ return { ...refused2, unit: path };
3935
+ for (const key of [RUN_KEY, APPROVED_KEY])
3936
+ if (present(s, key))
3937
+ s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
3938
+ rmSync3(path);
3939
+ return { status: "removed", unit: path };
3940
+ }
3941
+ async function loginRunStatus(s) {
3942
+ const path = launcherPath(s);
3943
+ const previous = readGenerated(path, MARKER3);
3944
+ if (previous === null)
3945
+ return { status: "foreign", unit: path };
3946
+ if (previous === undefined)
3947
+ return { status: "absent" };
3948
+ const workspace = launcherWorkspace(previous);
3949
+ const owner = workspace ? await s.ownerStatus(workspace) : "unavailable";
3950
+ return {
3951
+ status: owner === "alive" ? "active" : "inactive",
3952
+ enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
3953
+ unit: path,
3954
+ workspace
3955
+ };
3956
+ }
3957
+
3644
3958
  // src/service/recovery-cli.ts
3645
3959
  import { isatty } from "node:tty";
3646
3960
 
3647
3961
  // src/history/recovery.ts
3648
3962
  import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
3649
3963
  import { lstatSync as lstatSync6, opendirSync as opendirSync3 } from "node:fs";
3650
- import { join as join6, relative as relative4 } from "node:path";
3964
+ import { join as join8, relative as relative4 } from "node:path";
3651
3965
 
3652
3966
  // src/service/recovery.ts
3653
3967
  async function inspectRecovery(coordination, history) {
@@ -3707,7 +4021,7 @@ import {
3707
4021
  statfsSync
3708
4022
  } from "node:fs";
3709
4023
  import { open } from "node:fs/promises";
3710
- import { dirname as dirname6, join as join5 } from "node:path";
4024
+ import { dirname as dirname6, join as join7 } from "node:path";
3711
4025
 
3712
4026
  // src/history/admission.ts
3713
4027
  function decodeRunAdmission(value) {
@@ -4238,14 +4552,14 @@ class HistoryFiles {
4238
4552
  this.coordination = coordination;
4239
4553
  this.format = format;
4240
4554
  this.formatSnapshot = formatSnapshot;
4241
- this.directory = join5(coordination.directory, "history-v2");
4555
+ this.directory = join7(coordination.directory, "history-v2");
4242
4556
  this.files = coordination.files;
4243
4557
  }
4244
4558
  static open(coordination) {
4245
- const dir = join5(coordination.directory, "history-v2");
4559
+ const dir = join7(coordination.directory, "history-v2");
4246
4560
  if (!coordination.files.directory(dir))
4247
4561
  return;
4248
- const file = coordination.files.read(join5(dir, "format.json"));
4562
+ const file = coordination.files.read(join7(dir, "format.json"));
4249
4563
  if (!file)
4250
4564
  corrupt();
4251
4565
  const format = decodeFormat(json(file));
@@ -4261,7 +4575,7 @@ class HistoryFiles {
4261
4575
  existing.assertWritable();
4262
4576
  return existing;
4263
4577
  }
4264
- const dir = join5(c.directory, "history-v2");
4578
+ const dir = join7(c.directory, "history-v2");
4265
4579
  const format = {
4266
4580
  schema: FORMAT,
4267
4581
  storeId: randomUUID4(),
@@ -4271,19 +4585,19 @@ class HistoryFiles {
4271
4585
  c.files.directory(dir, true);
4272
4586
  for (const name of ["commands", "ledger", "runs", "indexes"]) {
4273
4587
  lease.assert();
4274
- c.files.directory(join5(dir, name), true);
4588
+ c.files.directory(join7(dir, name), true);
4275
4589
  }
4276
4590
  lease.assert();
4277
- const keyPath = join5(dir, "read-proof-key.json");
4591
+ const keyPath = join7(dir, "read-proof-key.json");
4278
4592
  c.files.create(keyPath, encoded({
4279
4593
  schema: "cadence-history-read-key/1",
4280
4594
  storeId: format.storeId,
4281
4595
  key: randomBytes2(32).toString("base64url")
4282
4596
  }));
4283
4597
  lease.assert();
4284
- const snapshot = c.files.create(join5(dir, "format.json"), encoded(format));
4598
+ const snapshot = c.files.create(join7(dir, "format.json"), encoded(format));
4285
4599
  const result = new HistoryFiles(c, format, snapshot);
4286
- result.syncParents(join5(dir, "format.json"));
4600
+ result.syncParents(join7(dir, "format.json"));
4287
4601
  result.assertWritable();
4288
4602
  return result;
4289
4603
  }
@@ -4318,7 +4632,7 @@ class HistoryFiles {
4318
4632
  throw new ContractError("quarantine");
4319
4633
  }
4320
4634
  assertFormat() {
4321
- this.files.assert(join5(this.directory, "format.json"), this.formatSnapshot);
4635
+ this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot);
4322
4636
  }
4323
4637
  readProofMaterial(budget) {
4324
4638
  if (this.format.schema !== FORMAT)
@@ -4327,7 +4641,7 @@ class HistoryFiles {
4327
4641
  this.assertReadFormat(budget);
4328
4642
  else
4329
4643
  this.assertFormat();
4330
- const selected2 = this.readBudgeted(join5(this.directory, "read-proof-key.json"), 512, budget);
4644
+ const selected2 = this.readBudgeted(join7(this.directory, "read-proof-key.json"), 512, budget);
4331
4645
  if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
4332
4646
  corrupt();
4333
4647
  try {
@@ -4355,17 +4669,17 @@ class HistoryFiles {
4355
4669
  return;
4356
4670
  }
4357
4671
  this.assertFormat();
4358
- assertIncarnation(this, join5(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
4672
+ assertIncarnation(this, join7(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
4359
4673
  }
4360
4674
  segmentPath(segment) {
4361
- return join5(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4675
+ return join7(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4362
4676
  }
4363
4677
  artifactPath(ref) {
4364
- return join5(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
4678
+ return join7(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
4365
4679
  }
4366
4680
  admissionPath(runId) {
4367
4681
  decodeId(runId);
4368
- return join5(this.directory, "runs", runId, "admission.json");
4682
+ return join7(this.directory, "runs", runId, "admission.json");
4369
4683
  }
4370
4684
  writeAdmission(lease, input) {
4371
4685
  lease.assert();
@@ -4391,7 +4705,7 @@ class HistoryFiles {
4391
4705
  readCommand(commandId, budget) {
4392
4706
  decodeId(commandId);
4393
4707
  this.assertReadFormat(budget);
4394
- return this.readBudgeted(join5(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
4708
+ return this.readBudgeted(join7(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
4395
4709
  }
4396
4710
  readAdmission(runId, identity, budget) {
4397
4711
  return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
@@ -4429,7 +4743,7 @@ class HistoryFiles {
4429
4743
  }
4430
4744
  assertReadFormat(budget) {
4431
4745
  budget.reserve(this.formatSnapshot.bytes.length);
4432
- this.files.assert(join5(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
4746
+ this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
4433
4747
  }
4434
4748
  assertPublished(segment, end, seq) {
4435
4749
  const fence = publicationFences.get(this.directory);
@@ -4467,7 +4781,7 @@ class HistoryFiles {
4467
4781
  const dir = opendirSync2(path);
4468
4782
  try {
4469
4783
  for (let e = dir.readSync();e; e = dir.readSync()) {
4470
- const p = join5(path, e.name), st = lstatSync5(p);
4784
+ const p = join7(path, e.name), st = lstatSync5(p);
4471
4785
  if (st.isDirectory())
4472
4786
  bytes += walk(p, depth + 1);
4473
4787
  else {
@@ -4502,7 +4816,7 @@ class HistoryFiles {
4502
4816
  state.bytes = 0;
4503
4817
  state.identity = null;
4504
4818
  let count = 0;
4505
- const ledger = join5(this.directory, "ledger");
4819
+ const ledger = join7(this.directory, "ledger");
4506
4820
  if (!this.files.directory(ledger))
4507
4821
  corrupt();
4508
4822
  const dir = opendirSync2(ledger);
@@ -4819,7 +5133,7 @@ class HistoryFiles {
4819
5133
  checkpoint(lease, state) {
4820
5134
  lease.assert();
4821
5135
  this.assertWritable();
4822
- const path = join5(this.directory, "checkpoint.json"), old = this.files.read(path);
5136
+ const path = join7(this.directory, "checkpoint.json"), old = this.files.read(path);
4823
5137
  const body = encoded({
4824
5138
  schema: "cadence-history-checkpoint/2",
4825
5139
  storeId: this.format.storeId,
@@ -5246,7 +5560,7 @@ function digestHistory(files, exclude) {
5246
5560
  dir.closeSync();
5247
5561
  }
5248
5562
  for (const name of names2.sort()) {
5249
- const selected2 = join6(path, name);
5563
+ const selected2 = join8(path, name);
5250
5564
  if (selected2 === exclude)
5251
5565
  continue;
5252
5566
  const rel = relative4(files.directory, selected2);
@@ -5557,7 +5871,7 @@ async function localRecovery(c, operation) {
5557
5871
 
5558
5872
  // src/service/runtime.ts
5559
5873
  import { homedir as homedir3 } from "node:os";
5560
- import { dirname as dirname7, isAbsolute as isAbsolute5, join as join7, resolve as resolve4 } from "node:path";
5874
+ import { dirname as dirname7, isAbsolute as isAbsolute6, join as join9, resolve as resolve4 } from "node:path";
5561
5875
 
5562
5876
  // src/service/config.ts
5563
5877
  import { isIP } from "node:net";
@@ -5611,7 +5925,7 @@ function localOrigin(policy, actualPort) {
5611
5925
  var STARTUP_INPUT_BYTES = 16 * 1024;
5612
5926
  function absolute(value) {
5613
5927
  const result = text(value, 4096, true);
5614
- if (!isAbsolute5(result))
5928
+ if (!isAbsolute6(result))
5615
5929
  throw new ContractError("invalid");
5616
5930
  return result;
5617
5931
  }
@@ -5706,7 +6020,7 @@ function selectStart(options, ambient) {
5706
6020
  port: options.port ?? 17423,
5707
6021
  proxy: null
5708
6022
  },
5709
- agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join7(ambient.HOME ?? ambient.USERPROFILE ?? homedir3(), ".pi", "agent")),
6023
+ agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join9(ambient.HOME ?? ambient.USERPROFILE ?? homedir3(), ".pi", "agent")),
5710
6024
  tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
5711
6025
  tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
5712
6026
  });
@@ -5878,10 +6192,10 @@ function startupGuidance(result, input) {
5878
6192
  return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
5879
6193
  `;
5880
6194
  case "pi-auth":
5881
- return `${join8(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
6195
+ return `${join10(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
5882
6196
  `;
5883
6197
  case "pi-models":
5884
- return `${join8(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
6198
+ return `${join10(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
5885
6199
  `;
5886
6200
  case "pi-runtime":
5887
6201
  return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
@@ -6007,14 +6321,59 @@ async function observeStarted(workspace) {
6007
6321
  }
6008
6322
  return { service };
6009
6323
  }
6010
- function autostartGuidance(s, result) {
6011
- const name = s.unit.replace(/\.service$/, "");
6012
- const cli = JSON.stringify(join8(s.root, "src", "service-cli.mjs"));
6013
- const workspace = JSON.stringify(result.workspace ?? "");
6014
- const manage = `Manage: systemctl --user status|restart|stop ${name}; logs: journalctl --user -u ${name}; opt out: systemctl --user disable --now ${name}`;
6324
+ var ownerControl = {
6325
+ async ownerStatus(workspace) {
6326
+ try {
6327
+ const observed = await serviceStatus(new ServiceCoordination(workspace));
6328
+ return observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
6329
+ } catch {
6330
+ return "unavailable";
6331
+ }
6332
+ },
6333
+ async stopOwner(workspace) {
6334
+ try {
6335
+ const c = new ServiceCoordination(workspace);
6336
+ const observed = await serviceStatus(c);
6337
+ const status = observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
6338
+ if (status === "absent" || status === "stopped")
6339
+ return "stopped";
6340
+ return status === "alive" ? (await stopService(c)).status : status;
6341
+ } catch {
6342
+ return "unavailable";
6343
+ }
6344
+ }
6345
+ };
6346
+ async function serverAction(s, action, requested, linger) {
6347
+ const install = action === "install" || action === "restart";
6348
+ const mode = action === "install" ? "install" : "restart";
6349
+ switch (s.platform) {
6350
+ case "linux":
6351
+ if (install)
6352
+ return configureAutostart(s, mode, requested, linger);
6353
+ if (action === "stop")
6354
+ return stopAutostart(s);
6355
+ return action === "uninstall" ? removeAutostart(s) : autostartStatus(s);
6356
+ case "darwin":
6357
+ if (install)
6358
+ return configureLaunchAgent(s, mode, requested);
6359
+ if (action === "stop")
6360
+ return stopLaunchAgent(s);
6361
+ return action === "uninstall" ? removeLaunchAgent(s) : launchAgentStatus(s);
6362
+ case "win32":
6363
+ if (install)
6364
+ return configureLoginRun(s, mode, requested);
6365
+ if (action === "stop")
6366
+ return stopLoginRun(s);
6367
+ return action === "uninstall" ? removeLoginRun(s) : loginRunStatus(s);
6368
+ default:
6369
+ return { status: "unsupported" };
6370
+ }
6371
+ }
6372
+ function serverGuidance(s, action, result) {
6373
+ const cli = piUserInstall(s) ? `node ${JSON.stringify(join10(s.root, "src", "service-cli.mjs"))}` : "cadence";
6015
6374
  const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
6016
6375
  ` : "";
6017
- return note + autostartStatusGuidance(s, result, name, cli, workspace, manage);
6376
+ return note + serverStatusGuidance(s, action, result, cli);
6018
6377
  }
6019
6378
  function lingerGuidance(linger) {
6020
6379
  switch (linger) {
@@ -6031,83 +6390,124 @@ function lingerGuidance(linger) {
6031
6390
  return "";
6032
6391
  }
6033
6392
  }
6034
- function autostartStatusGuidance(s, result, name, cli, workspace, manage) {
6393
+ function managerDetail(s) {
6394
+ switch (s.platform) {
6395
+ case "linux":
6396
+ return "see systemctl --user status cadence and journalctl --user -u cadence";
6397
+ case "darwin":
6398
+ return `see launchctl print gui/${s.uid}/${LABEL} and ~/Library/Logs/cadence.log`;
6399
+ default:
6400
+ return `run its launcher to see why: wscript ${JSON.stringify(launcherPath(s))}`;
6401
+ }
6402
+ }
6403
+ function serverStatusGuidance(s, action, result, cli) {
6404
+ const workspace = JSON.stringify(result.workspace ?? "");
6405
+ const manage = `Manage: ${cli} server status|restart|stop|uninstall`;
6406
+ const when = s.platform === "linux" ? "at boot or login" : "at login";
6035
6407
  switch (result.status) {
6036
6408
  case "started": {
6037
6409
  const linger = lingerGuidance(result.linger);
6038
6410
  if (result.service !== "alive")
6039
- return `${s.unit} started for ${result.workspace}, but the owner reports ${result.service}.
6040
- Inspect: node ${cli} diagnose --workspace ${workspace}
6411
+ return `Cadence started for ${result.workspace}, but the owner reports ${result.service}.
6412
+ Inspect: ${cli} diagnose --workspace ${workspace}
6041
6413
  ${manage}
6042
6414
  ${linger}`;
6043
- return `Cadence WebUI: ${result.endpoint} (${s.unit}, workspace ${result.workspace})
6044
- Enter with your private link: node ${cli} token-link --workspace ${workspace} --reveal
6415
+ return `Cadence WebUI: ${result.endpoint} (workspace ${result.workspace}; starts ${when})
6416
+ Enter with your private link: ${cli} token-link --workspace ${workspace} --reveal
6045
6417
  ${manage}
6046
6418
  ${linger}`;
6047
6419
  }
6048
6420
  case "waiting-for-pi-login":
6049
- return `${s.unit} is enabled for ${result.workspace}; it starts once Pi has ${join8(agentDirectory(s), "auth.json")}.
6050
- Log in to Pi, then: systemctl --user start ${name}
6421
+ return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join10(agentDirectory(s), "auth.json")}.
6422
+ Log in to Pi, then: ${cli} server restart
6051
6423
  ${lingerGuidance(result.linger)}`;
6424
+ case "stopped":
6425
+ return action === "stop" ? `Cadence stopped; it starts again ${when}, or now with: ${cli} server restart
6426
+ ` : "";
6427
+ case "absent":
6428
+ return action === "restart" || action === "stop" ? `Cadence's background service is not installed: ${cli} server install
6429
+ ` : "";
6052
6430
  case "disabled":
6053
- return `${s.unit} refreshed for ${result.workspace}; left disabled as configured.
6431
+ return `Cadence's service definition was rewritten for ${result.workspace}; left disabled as configured.
6054
6432
  `;
6055
6433
  case "masked":
6056
- return `${s.unit} is masked; Cadence did not enable or start it.
6434
+ return `cadence.service is masked; Cadence did not enable or start it.
6057
6435
  `;
6058
6436
  case "missing-pi-sdk":
6059
- return `Cadence did not register or refresh ${s.unit}: the Pi SDK packages pi-coding-agent and pi-ai (scope @earendil-works) do not resolve from ${s.root}, for example after installing without optional dependencies; an owner without them would quarantine.
6437
+ return `Cadence did not register its service: the Pi SDK packages pi-coding-agent and pi-ai (scope @earendil-works) do not resolve from ${s.root}, for example after installing without optional dependencies; an owner without them would quarantine.
6060
6438
  `;
6061
6439
  case "foreign":
6062
6440
  return `${result.unit} was not generated by Cadence; left unchanged.
6063
6441
  `;
6064
6442
  case "unavailable":
6065
- return `No systemd user manager; start Cadence explicitly: node ${cli} start --workspace /absolute/project
6443
+ return result.workspace ? `Cadence changed nothing: its owner could not be observed. Inspect: ${cli} diagnose --workspace ${workspace}
6444
+ ` : `No user service manager; start Cadence explicitly: ${cli} start --workspace /absolute/project
6445
+ `;
6446
+ case "unsupported":
6447
+ return `Cadence has no background service for ${s.platform}; start it explicitly: ${cli} start --workspace /absolute/project
6448
+ `;
6449
+ case "unconfirmed":
6450
+ case "quarantined":
6451
+ return `Cadence changed nothing: its owner's orderly Stop was not confirmed (${result.status}). Inspect: ${cli} diagnose --workspace ${workspace}
6066
6452
  `;
6067
6453
  case "failed":
6068
- return `Could not enable or start ${s.unit}; see systemctl --user status ${name} and journalctl --user -u ${name}.
6454
+ return `Could not register or start Cadence's service; ${managerDetail(s)}.
6069
6455
  ${lingerGuidance(result.linger)}`;
6070
6456
  default:
6071
6457
  return "";
6072
6458
  }
6073
6459
  }
6074
- async function autostartMain(argv, hostUrl) {
6075
- const action = argv.shift();
6460
+ var SERVER_ACTIONS = [
6461
+ "install",
6462
+ "status",
6463
+ "restart",
6464
+ "stop",
6465
+ "uninstall"
6466
+ ];
6467
+ async function serverMain(argv, hostUrl) {
6468
+ const action = SERVER_ACTIONS.find((a) => a === argv[0]);
6469
+ if (!action)
6470
+ throw new ContractError("invalid");
6471
+ argv.shift();
6076
6472
  let requested;
6077
- let linger = false;
6473
+ let linger = process.env.CADENCE_LINGER !== "0";
6474
+ let noLinger = false;
6078
6475
  while (action === "install" && argv.length) {
6079
6476
  if (argv[0] === "--workspace" && argv[1] && requested === undefined)
6080
6477
  requested = resolve5(argv.splice(0, 2)[1]);
6081
- else if (argv[0] === "--linger" && !linger) {
6478
+ else if (argv[0] === "--no-linger" && !noLinger) {
6082
6479
  argv.shift();
6083
- linger = true;
6480
+ noLinger = true;
6481
+ linger = false;
6084
6482
  } else
6085
6483
  break;
6086
6484
  }
6087
- if (argv.length || !["install", "refresh", "remove", "status"].includes(action ?? ""))
6485
+ if (argv.length)
6088
6486
  throw new ContractError("invalid");
6089
- const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))));
6487
+ const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))), ownerControl);
6090
6488
  let result;
6091
- if (action === "remove")
6092
- result = removeAutostart(s);
6093
- else if (action === "status")
6094
- result = autostartStatus(s);
6095
- else {
6096
- try {
6097
- result = configureAutostart(s, action === "install" ? "install" : "refresh", requested, linger);
6098
- } catch (error) {
6099
- if (error instanceof ContractError && error.code === "invalid")
6100
- process.stderr.write(`Cadence autostart needs an existing absolute workspace directory (--workspace or CADENCE_WORKSPACE).
6489
+ try {
6490
+ result = await serverAction(s, action, requested, linger);
6491
+ } catch (error) {
6492
+ if (error instanceof ContractError && error.code === "invalid")
6493
+ process.stderr.write(`Cadence server install needs an existing absolute --workspace directory.
6101
6494
  `);
6102
- throw error;
6103
- }
6104
- if (result.status === "started" && result.workspace)
6105
- Object.assign(result, await observeStarted(result.workspace));
6106
- process.stderr.write(autostartGuidance(s, result));
6495
+ throw error;
6107
6496
  }
6497
+ if (result.status === "started" && result.workspace)
6498
+ Object.assign(result, await observeStarted(result.workspace));
6499
+ process.stderr.write(serverGuidance(s, action, result));
6108
6500
  process.stdout.write(`${JSON.stringify(result)}
6109
6501
  `);
6110
- if (["failed", "foreign", "unavailable"].includes(result.status))
6502
+ if ([
6503
+ "failed",
6504
+ "foreign",
6505
+ "unavailable",
6506
+ "unsupported",
6507
+ "unconfirmed",
6508
+ "quarantined",
6509
+ "missing-pi-sdk"
6510
+ ].includes(result.status) || result.status === "absent" && (action === "restart" || action === "stop"))
6111
6511
  process.exitCode = 1;
6112
6512
  }
6113
6513
  async function managementMain(argv, hostUrl) {
@@ -6116,16 +6516,16 @@ async function managementMain(argv, hostUrl) {
6116
6516
  throw new ContractError("unsupported-context");
6117
6517
  const command = argv.shift();
6118
6518
  if (command === "--help" && !argv.length) {
6119
- process.stdout.write(`cadence-service start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
6120
- cadence-service autostart install [--workspace PATH] [--linger]|refresh|remove|status
6121
- Start is explicit, or the Linux systemd user unit that Pi's user-scope install registers (CADENCE_AUTOSTART=0, disable or mask opts out); its first registration also enables linger for boot start when logind allows it without a password (CADENCE_LINGER=0 opts out), and --linger asks for it explicitly. Default detached or --foreground. Start accepts --config, --agent-dir, --host, --port, --token-file.
6519
+ process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
6520
+ cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
6521
+ Start is explicit, or the background service that server install registers: a systemd user unit on Linux, started at boot through linger (--no-linger or CADENCE_LINGER=0 keeps it to login), a LaunchAgent on macOS and a Run entry on Windows, both started at login. Server restart, stop and uninstall use the orderly Stop. cadence-service is the same command. Default detached or --foreground. Start accepts --config, --agent-dir, --host, --port, --token-file.
6122
6522
  Return/quit detaches clients; only explicit Cancel/Stop requests stop. Unconfirmed/quarantined is not stopped.
6123
6523
  Recovery resolve requires this local native terminal, exact unchanged dead-owner resources and separate process/credential/storage acknowledgments. No --yes, network recovery, killing, repair or cleanup.
6124
6524
  `);
6125
6525
  return;
6126
6526
  }
6127
- if (command === "autostart") {
6128
- await autostartMain(argv, hostUrl);
6527
+ if (command === "server") {
6528
+ await serverMain(argv, hostUrl);
6129
6529
  return;
6130
6530
  }
6131
6531
  let recovery;