@abelxiaoxing/cadence 2.0.3 → 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
@@ -564,7 +564,8 @@ var thinkingLevels = [
564
564
  "low",
565
565
  "medium",
566
566
  "high",
567
- "xhigh"
567
+ "xhigh",
568
+ "max"
568
569
  ];
569
570
  function decodeModelSelector(v) {
570
571
  const o = closed(v, ["provider", "model", "api", "thinking"]);
@@ -2483,14 +2484,27 @@ import {
2483
2484
  writeFileSync as writeFileSync2
2484
2485
  } from "node:fs";
2485
2486
  import { homedir as homedir2 } from "node:os";
2486
- 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";
2487
2496
  var UNIT = "cadence.service";
2488
2497
  var MARKER = "# Generated by @abelxiaoxing/cadence";
2489
- 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) {
2490
2503
  const run = (command, args) => {
2491
2504
  const r = spawnSync2(command, args, {
2492
2505
  encoding: "utf8",
2493
- stdio: ["ignore", "pipe", "ignore"]
2506
+ stdio: ["ignore", "pipe", "ignore"],
2507
+ windowsHide: true
2494
2508
  });
2495
2509
  return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
2496
2510
  };
@@ -2501,6 +2515,7 @@ function hostSystem(root) {
2501
2515
  node: process.execPath,
2502
2516
  root,
2503
2517
  unit: UNIT,
2518
+ uid: process.getuid?.(),
2504
2519
  systemctl: (args) => run("systemctl", ["--user", ...args]),
2505
2520
  linger: () => {
2506
2521
  const r = run("loginctl", [
@@ -2527,7 +2542,18 @@ function hostSystem(root) {
2527
2542
  "false"
2528
2543
  ], { stdio: "ignore", timeout: 20000 });
2529
2544
  return !r.error && r.status === 0;
2530
- }
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
2531
2557
  };
2532
2558
  }
2533
2559
  function expandHome(value, home) {
@@ -2601,7 +2627,7 @@ var argument = (value) => quoted(value).replaceAll("$", "$$$$");
2601
2627
  function buildUnit(u) {
2602
2628
  const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
2603
2629
  return [
2604
- `${MARKER} on Pi package install/update; upgrades rewrite this file.`,
2630
+ `${MARKER}; \`cadence server install\` and \`restart\` rewrite this file.`,
2605
2631
  "# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
2606
2632
  "[Unit]",
2607
2633
  "Description=Cadence shared task service and WebUI",
@@ -2629,19 +2655,20 @@ function buildUnit(u) {
2629
2655
  ].join(`
2630
2656
  `);
2631
2657
  }
2632
- function readGenerated(path) {
2658
+ function readGenerated(path, marker) {
2633
2659
  try {
2634
2660
  if (!lstatSync2(path).isFile())
2635
2661
  return null;
2636
- const text2 = readFileSync(path, "utf8");
2637
- 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;
2638
2665
  } catch (error) {
2639
2666
  if (error.code === "ENOENT")
2640
2667
  return;
2641
2668
  throw error;
2642
2669
  }
2643
2670
  }
2644
- function generatedWorkspace(text2) {
2671
+ function unitWorkspace(text2) {
2645
2672
  return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
2646
2673
  }
2647
2674
  function directory(path) {
@@ -2651,23 +2678,20 @@ function directory(path) {
2651
2678
  return false;
2652
2679
  }
2653
2680
  }
2654
- function unitStateHome(s) {
2655
- const state = s.env.XDG_STATE_HOME;
2656
- 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"]);
2657
2684
  }
2658
- function selectWorkspace(s, requested, previous) {
2685
+ function selectWorkspace(s, requested, kept) {
2659
2686
  let chosen;
2660
2687
  if (requested) {
2661
2688
  chosen = expandHome(requested, s.home);
2662
2689
  if (!directory(chosen))
2663
2690
  throw new ContractError("invalid");
2664
2691
  chosen = resolve2(chosen);
2665
- } else {
2666
- const kept = generatedWorkspace(previous);
2667
- if (kept && directory(kept))
2668
- chosen = kept;
2669
- }
2670
- 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 };
2671
2695
  if (landing.workspace !== chosen)
2672
2696
  mkdirSync2(landing.workspace, { recursive: true });
2673
2697
  return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
@@ -2689,7 +2713,18 @@ function regularFile(path) {
2689
2713
  return false;
2690
2714
  }
2691
2715
  }
2692
- 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) {
2693
2728
  const current = s.linger();
2694
2729
  if (current === undefined)
2695
2730
  return {};
@@ -2697,23 +2732,25 @@ function bootStart(s, attempt, interactive) {
2697
2732
  return { linger: "on" };
2698
2733
  if (!attempt)
2699
2734
  return { linger: "off" };
2700
- return { linger: s.enableLinger(interactive) ? "enabled" : "refused" };
2735
+ return {
2736
+ linger: s.enableLinger(false) || s.enableLinger(true) ? "enabled" : "refused"
2737
+ };
2701
2738
  }
2702
2739
  function configureAutostart(s, mode, requested, linger = false) {
2703
2740
  if (s.platform !== "linux")
2704
- return { status: "skipped" };
2705
- if (mode === "refresh" && (s.env.CADENCE_AUTOSTART === "0" || !piUserInstall(s)))
2706
- return { status: "skipped" };
2741
+ return { status: "unsupported" };
2707
2742
  if (s.systemctl(["show-environment"]).status !== 0)
2708
2743
  return { status: "unavailable" };
2709
2744
  const path = unitPath(s);
2710
- const previous = readGenerated(path);
2745
+ const previous = readGenerated(path, MARKER);
2711
2746
  if (previous === null)
2712
2747
  return { status: "foreign", unit: path };
2748
+ if (mode === "restart" && previous === undefined)
2749
+ return { status: "absent" };
2713
2750
  const sdk = sdkPackages(s.root);
2714
2751
  if (!sdk)
2715
2752
  return { status: "missing-pi-sdk" };
2716
- const { workspace, redirectedFrom } = selectWorkspace(s, requested ?? s.env.CADENCE_WORKSPACE, previous);
2753
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
2717
2754
  const agentDir = agentDirectory(s);
2718
2755
  const content = buildUnit({
2719
2756
  node: s.node,
@@ -2736,23 +2773,37 @@ function configureAutostart(s, mode, requested, linger = false) {
2736
2773
  };
2737
2774
  if (enabled.startsWith("masked"))
2738
2775
  return { status: "masked", ...result };
2739
- const wanted = mode === "install" || previous === undefined || enabled === "enabled";
2740
- if (wanted && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
2776
+ if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
2741
2777
  return { status: "failed", ...result };
2742
- if (!wanted && !active)
2743
- return { status: "disabled", ...result };
2744
- 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) : {};
2745
2779
  if (!regularFile(join2(agentDir, "auth.json")))
2746
2780
  return { status: "waiting-for-pi-login", ...result, ...boot() };
2747
2781
  s.systemctl(["reset-failed", s.unit]);
2748
2782
  const started = s.systemctl(["start", s.unit]).status === 0;
2749
2783
  return { status: started ? "started" : "failed", ...result, ...boot() };
2750
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
+ }
2751
2802
  function removeAutostart(s) {
2752
2803
  if (s.platform !== "linux")
2753
- return { status: "skipped" };
2804
+ return { status: "unsupported" };
2754
2805
  const path = unitPath(s);
2755
- const previous = readGenerated(path);
2806
+ const previous = readGenerated(path, MARKER);
2756
2807
  if (previous === null)
2757
2808
  return { status: "foreign", unit: path };
2758
2809
  if (previous === undefined)
@@ -2768,9 +2819,9 @@ function removeAutostart(s) {
2768
2819
  }
2769
2820
  function autostartStatus(s) {
2770
2821
  if (s.platform !== "linux")
2771
- return { status: "skipped" };
2822
+ return { status: "unsupported" };
2772
2823
  const path = unitPath(s);
2773
- const previous = readGenerated(path);
2824
+ const previous = readGenerated(path, MARKER);
2774
2825
  if (previous === null)
2775
2826
  return { status: "foreign", unit: path };
2776
2827
  if (previous === undefined)
@@ -2779,8 +2830,8 @@ function autostartStatus(s) {
2779
2830
  status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
2780
2831
  enabled: s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown",
2781
2832
  unit: path,
2782
- workspace: generatedWorkspace(previous),
2783
- ...bootStart(s, false, false)
2833
+ workspace: unitWorkspace(previous),
2834
+ ...bootStart(s, false)
2784
2835
  };
2785
2836
  }
2786
2837
 
@@ -3640,13 +3691,277 @@ class OwnerLease {
3640
3691
  }
3641
3692
  }
3642
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
+
3643
3958
  // src/service/recovery-cli.ts
3644
3959
  import { isatty } from "node:tty";
3645
3960
 
3646
3961
  // src/history/recovery.ts
3647
3962
  import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
3648
3963
  import { lstatSync as lstatSync6, opendirSync as opendirSync3 } from "node:fs";
3649
- import { join as join6, relative as relative4 } from "node:path";
3964
+ import { join as join8, relative as relative4 } from "node:path";
3650
3965
 
3651
3966
  // src/service/recovery.ts
3652
3967
  async function inspectRecovery(coordination, history) {
@@ -3706,7 +4021,7 @@ import {
3706
4021
  statfsSync
3707
4022
  } from "node:fs";
3708
4023
  import { open } from "node:fs/promises";
3709
- import { dirname as dirname6, join as join5 } from "node:path";
4024
+ import { dirname as dirname6, join as join7 } from "node:path";
3710
4025
 
3711
4026
  // src/history/admission.ts
3712
4027
  function decodeRunAdmission(value) {
@@ -4237,14 +4552,14 @@ class HistoryFiles {
4237
4552
  this.coordination = coordination;
4238
4553
  this.format = format;
4239
4554
  this.formatSnapshot = formatSnapshot;
4240
- this.directory = join5(coordination.directory, "history-v2");
4555
+ this.directory = join7(coordination.directory, "history-v2");
4241
4556
  this.files = coordination.files;
4242
4557
  }
4243
4558
  static open(coordination) {
4244
- const dir = join5(coordination.directory, "history-v2");
4559
+ const dir = join7(coordination.directory, "history-v2");
4245
4560
  if (!coordination.files.directory(dir))
4246
4561
  return;
4247
- const file = coordination.files.read(join5(dir, "format.json"));
4562
+ const file = coordination.files.read(join7(dir, "format.json"));
4248
4563
  if (!file)
4249
4564
  corrupt();
4250
4565
  const format = decodeFormat(json(file));
@@ -4260,7 +4575,7 @@ class HistoryFiles {
4260
4575
  existing.assertWritable();
4261
4576
  return existing;
4262
4577
  }
4263
- const dir = join5(c.directory, "history-v2");
4578
+ const dir = join7(c.directory, "history-v2");
4264
4579
  const format = {
4265
4580
  schema: FORMAT,
4266
4581
  storeId: randomUUID4(),
@@ -4270,19 +4585,19 @@ class HistoryFiles {
4270
4585
  c.files.directory(dir, true);
4271
4586
  for (const name of ["commands", "ledger", "runs", "indexes"]) {
4272
4587
  lease.assert();
4273
- c.files.directory(join5(dir, name), true);
4588
+ c.files.directory(join7(dir, name), true);
4274
4589
  }
4275
4590
  lease.assert();
4276
- const keyPath = join5(dir, "read-proof-key.json");
4591
+ const keyPath = join7(dir, "read-proof-key.json");
4277
4592
  c.files.create(keyPath, encoded({
4278
4593
  schema: "cadence-history-read-key/1",
4279
4594
  storeId: format.storeId,
4280
4595
  key: randomBytes2(32).toString("base64url")
4281
4596
  }));
4282
4597
  lease.assert();
4283
- const snapshot = c.files.create(join5(dir, "format.json"), encoded(format));
4598
+ const snapshot = c.files.create(join7(dir, "format.json"), encoded(format));
4284
4599
  const result = new HistoryFiles(c, format, snapshot);
4285
- result.syncParents(join5(dir, "format.json"));
4600
+ result.syncParents(join7(dir, "format.json"));
4286
4601
  result.assertWritable();
4287
4602
  return result;
4288
4603
  }
@@ -4317,7 +4632,7 @@ class HistoryFiles {
4317
4632
  throw new ContractError("quarantine");
4318
4633
  }
4319
4634
  assertFormat() {
4320
- this.files.assert(join5(this.directory, "format.json"), this.formatSnapshot);
4635
+ this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot);
4321
4636
  }
4322
4637
  readProofMaterial(budget) {
4323
4638
  if (this.format.schema !== FORMAT)
@@ -4326,7 +4641,7 @@ class HistoryFiles {
4326
4641
  this.assertReadFormat(budget);
4327
4642
  else
4328
4643
  this.assertFormat();
4329
- 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);
4330
4645
  if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
4331
4646
  corrupt();
4332
4647
  try {
@@ -4354,17 +4669,17 @@ class HistoryFiles {
4354
4669
  return;
4355
4670
  }
4356
4671
  this.assertFormat();
4357
- 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);
4358
4673
  }
4359
4674
  segmentPath(segment) {
4360
- return join5(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4675
+ return join7(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4361
4676
  }
4362
4677
  artifactPath(ref) {
4363
- 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`);
4364
4679
  }
4365
4680
  admissionPath(runId) {
4366
4681
  decodeId(runId);
4367
- return join5(this.directory, "runs", runId, "admission.json");
4682
+ return join7(this.directory, "runs", runId, "admission.json");
4368
4683
  }
4369
4684
  writeAdmission(lease, input) {
4370
4685
  lease.assert();
@@ -4390,7 +4705,7 @@ class HistoryFiles {
4390
4705
  readCommand(commandId, budget) {
4391
4706
  decodeId(commandId);
4392
4707
  this.assertReadFormat(budget);
4393
- 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);
4394
4709
  }
4395
4710
  readAdmission(runId, identity, budget) {
4396
4711
  return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
@@ -4428,7 +4743,7 @@ class HistoryFiles {
4428
4743
  }
4429
4744
  assertReadFormat(budget) {
4430
4745
  budget.reserve(this.formatSnapshot.bytes.length);
4431
- 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);
4432
4747
  }
4433
4748
  assertPublished(segment, end, seq) {
4434
4749
  const fence = publicationFences.get(this.directory);
@@ -4466,7 +4781,7 @@ class HistoryFiles {
4466
4781
  const dir = opendirSync2(path);
4467
4782
  try {
4468
4783
  for (let e = dir.readSync();e; e = dir.readSync()) {
4469
- const p = join5(path, e.name), st = lstatSync5(p);
4784
+ const p = join7(path, e.name), st = lstatSync5(p);
4470
4785
  if (st.isDirectory())
4471
4786
  bytes += walk(p, depth + 1);
4472
4787
  else {
@@ -4501,7 +4816,7 @@ class HistoryFiles {
4501
4816
  state.bytes = 0;
4502
4817
  state.identity = null;
4503
4818
  let count = 0;
4504
- const ledger = join5(this.directory, "ledger");
4819
+ const ledger = join7(this.directory, "ledger");
4505
4820
  if (!this.files.directory(ledger))
4506
4821
  corrupt();
4507
4822
  const dir = opendirSync2(ledger);
@@ -4818,7 +5133,7 @@ class HistoryFiles {
4818
5133
  checkpoint(lease, state) {
4819
5134
  lease.assert();
4820
5135
  this.assertWritable();
4821
- 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);
4822
5137
  const body = encoded({
4823
5138
  schema: "cadence-history-checkpoint/2",
4824
5139
  storeId: this.format.storeId,
@@ -5245,7 +5560,7 @@ function digestHistory(files, exclude) {
5245
5560
  dir.closeSync();
5246
5561
  }
5247
5562
  for (const name of names2.sort()) {
5248
- const selected2 = join6(path, name);
5563
+ const selected2 = join8(path, name);
5249
5564
  if (selected2 === exclude)
5250
5565
  continue;
5251
5566
  const rel = relative4(files.directory, selected2);
@@ -5556,7 +5871,7 @@ async function localRecovery(c, operation) {
5556
5871
 
5557
5872
  // src/service/runtime.ts
5558
5873
  import { homedir as homedir3 } from "node:os";
5559
- 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";
5560
5875
 
5561
5876
  // src/service/config.ts
5562
5877
  import { isIP } from "node:net";
@@ -5610,7 +5925,7 @@ function localOrigin(policy, actualPort) {
5610
5925
  var STARTUP_INPUT_BYTES = 16 * 1024;
5611
5926
  function absolute(value) {
5612
5927
  const result = text(value, 4096, true);
5613
- if (!isAbsolute5(result))
5928
+ if (!isAbsolute6(result))
5614
5929
  throw new ContractError("invalid");
5615
5930
  return result;
5616
5931
  }
@@ -5705,7 +6020,7 @@ function selectStart(options, ambient) {
5705
6020
  port: options.port ?? 17423,
5706
6021
  proxy: null
5707
6022
  },
5708
- 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")),
5709
6024
  tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
5710
6025
  tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
5711
6026
  });
@@ -5877,10 +6192,10 @@ function startupGuidance(result, input) {
5877
6192
  return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
5878
6193
  `;
5879
6194
  case "pi-auth":
5880
- 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.
5881
6196
  `;
5882
6197
  case "pi-models":
5883
- 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.
5884
6199
  `;
5885
6200
  case "pi-runtime":
5886
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.
@@ -6006,14 +6321,59 @@ async function observeStarted(workspace) {
6006
6321
  }
6007
6322
  return { service };
6008
6323
  }
6009
- function autostartGuidance(s, result) {
6010
- const name = s.unit.replace(/\.service$/, "");
6011
- const cli = JSON.stringify(join8(s.root, "src", "service-cli.mjs"));
6012
- const workspace = JSON.stringify(result.workspace ?? "");
6013
- 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";
6014
6374
  const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
6015
6375
  ` : "";
6016
- return note + autostartStatusGuidance(s, result, name, cli, workspace, manage);
6376
+ return note + serverStatusGuidance(s, action, result, cli);
6017
6377
  }
6018
6378
  function lingerGuidance(linger) {
6019
6379
  switch (linger) {
@@ -6030,83 +6390,124 @@ function lingerGuidance(linger) {
6030
6390
  return "";
6031
6391
  }
6032
6392
  }
6033
- 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";
6034
6407
  switch (result.status) {
6035
6408
  case "started": {
6036
6409
  const linger = lingerGuidance(result.linger);
6037
6410
  if (result.service !== "alive")
6038
- return `${s.unit} started for ${result.workspace}, but the owner reports ${result.service}.
6039
- 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}
6040
6413
  ${manage}
6041
6414
  ${linger}`;
6042
- return `Cadence WebUI: ${result.endpoint} (${s.unit}, workspace ${result.workspace})
6043
- 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
6044
6417
  ${manage}
6045
6418
  ${linger}`;
6046
6419
  }
6047
6420
  case "waiting-for-pi-login":
6048
- return `${s.unit} is enabled for ${result.workspace}; it starts once Pi has ${join8(agentDirectory(s), "auth.json")}.
6049
- 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
6050
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
+ ` : "";
6051
6430
  case "disabled":
6052
- 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.
6053
6432
  `;
6054
6433
  case "masked":
6055
- 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.
6056
6435
  `;
6057
6436
  case "missing-pi-sdk":
6058
- 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.
6059
6438
  `;
6060
6439
  case "foreign":
6061
6440
  return `${result.unit} was not generated by Cadence; left unchanged.
6062
6441
  `;
6063
6442
  case "unavailable":
6064
- 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}
6065
6452
  `;
6066
6453
  case "failed":
6067
- 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)}.
6068
6455
  ${lingerGuidance(result.linger)}`;
6069
6456
  default:
6070
6457
  return "";
6071
6458
  }
6072
6459
  }
6073
- async function autostartMain(argv, hostUrl) {
6074
- 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();
6075
6472
  let requested;
6076
- let linger = false;
6473
+ let linger = process.env.CADENCE_LINGER !== "0";
6474
+ let noLinger = false;
6077
6475
  while (action === "install" && argv.length) {
6078
6476
  if (argv[0] === "--workspace" && argv[1] && requested === undefined)
6079
6477
  requested = resolve5(argv.splice(0, 2)[1]);
6080
- else if (argv[0] === "--linger" && !linger) {
6478
+ else if (argv[0] === "--no-linger" && !noLinger) {
6081
6479
  argv.shift();
6082
- linger = true;
6480
+ noLinger = true;
6481
+ linger = false;
6083
6482
  } else
6084
6483
  break;
6085
6484
  }
6086
- if (argv.length || !["install", "refresh", "remove", "status"].includes(action ?? ""))
6485
+ if (argv.length)
6087
6486
  throw new ContractError("invalid");
6088
- const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))));
6487
+ const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))), ownerControl);
6089
6488
  let result;
6090
- if (action === "remove")
6091
- result = removeAutostart(s);
6092
- else if (action === "status")
6093
- result = autostartStatus(s);
6094
- else {
6095
- try {
6096
- result = configureAutostart(s, action === "install" ? "install" : "refresh", requested, linger);
6097
- } catch (error) {
6098
- if (error instanceof ContractError && error.code === "invalid")
6099
- 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.
6100
6494
  `);
6101
- throw error;
6102
- }
6103
- if (result.status === "started" && result.workspace)
6104
- Object.assign(result, await observeStarted(result.workspace));
6105
- process.stderr.write(autostartGuidance(s, result));
6495
+ throw error;
6106
6496
  }
6497
+ if (result.status === "started" && result.workspace)
6498
+ Object.assign(result, await observeStarted(result.workspace));
6499
+ process.stderr.write(serverGuidance(s, action, result));
6107
6500
  process.stdout.write(`${JSON.stringify(result)}
6108
6501
  `);
6109
- 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"))
6110
6511
  process.exitCode = 1;
6111
6512
  }
6112
6513
  async function managementMain(argv, hostUrl) {
@@ -6115,16 +6516,16 @@ async function managementMain(argv, hostUrl) {
6115
6516
  throw new ContractError("unsupported-context");
6116
6517
  const command = argv.shift();
6117
6518
  if (command === "--help" && !argv.length) {
6118
- process.stdout.write(`cadence-service start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
6119
- cadence-service autostart install [--workspace PATH] [--linger]|refresh|remove|status
6120
- 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.
6121
6522
  Return/quit detaches clients; only explicit Cancel/Stop requests stop. Unconfirmed/quarantined is not stopped.
6122
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.
6123
6524
  `);
6124
6525
  return;
6125
6526
  }
6126
- if (command === "autostart") {
6127
- await autostartMain(argv, hostUrl);
6527
+ if (command === "server") {
6528
+ await serverMain(argv, hostUrl);
6128
6529
  return;
6129
6530
  }
6130
6531
  let recovery;