@abelxiaoxing/cadence 2.0.6 → 2.0.8

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 mkdirSync5 } from "node:fs";
7
7
  import { request } from "node:http";
8
- import { dirname as dirname8, join as join10, resolve as resolve5 } from "node:path";
8
+ import { dirname as dirname9, join as join11, resolve as resolve5 } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
 
11
11
  // src/install-config.mjs
@@ -2511,372 +2511,62 @@ var TASK_CONFIG_SCHEMA = "cadence-assistant-checks/1";
2511
2511
  // src/service/autostart.ts
2512
2512
  import { spawnSync as spawnSync2 } from "node:child_process";
2513
2513
  import {
2514
- lstatSync as lstatSync2,
2515
- mkdirSync as mkdirSync3,
2516
- readFileSync,
2517
- realpathSync as realpathSync2,
2518
- renameSync as renameSync2,
2519
- rmSync,
2520
- statSync,
2521
- writeFileSync as writeFileSync2
2522
- } from "node:fs";
2523
- import { homedir as homedir3 } from "node:os";
2524
- import {
2525
- delimiter,
2526
- dirname as dirname2,
2527
- isAbsolute as isAbsolute2,
2528
- join as join2,
2529
- resolve as resolve2,
2530
- sep as sep2,
2531
- win32
2532
- } from "node:path";
2533
- var UNIT = "cadence.service";
2534
- var MARKER = "# Generated by @abelxiaoxing/cadence";
2535
- function wscript(env) {
2536
- const root = env.SystemRoot;
2537
- return win32.join(root && win32.isAbsolute(root) ? root : "C:\\Windows", "System32", "wscript.exe");
2538
- }
2539
- function hostSystem(root, owner) {
2540
- const run = (command, args) => {
2541
- const r = spawnSync2(command, args, {
2542
- encoding: "utf8",
2543
- stdio: ["ignore", "pipe", "ignore"],
2544
- windowsHide: true
2545
- });
2546
- return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
2547
- };
2548
- return {
2549
- platform: process.platform,
2550
- env: process.env,
2551
- home: homedir3(),
2552
- node: process.execPath,
2553
- root,
2554
- unit: UNIT,
2555
- uid: process.getuid?.(),
2556
- systemctl: (args) => run("systemctl", ["--user", ...args]),
2557
- linger: () => {
2558
- const r = run("loginctl", [
2559
- "show-user",
2560
- String(process.getuid?.()),
2561
- "--property=Linger",
2562
- "--value"
2563
- ]);
2564
- return r.status === 0 ? r.stdout.trim() === "yes" : undefined;
2565
- },
2566
- enableLinger: (interactive) => {
2567
- const r = interactive ? spawnSync2("loginctl", ["enable-linger"], { stdio: "inherit" }) : spawnSync2("busctl", [
2568
- "--system",
2569
- "--allow-interactive-authorization=no",
2570
- "--timeout=15",
2571
- "call",
2572
- "org.freedesktop.login1",
2573
- "/org/freedesktop/login1",
2574
- "org.freedesktop.login1.Manager",
2575
- "SetUserLinger",
2576
- "ubb",
2577
- String(process.getuid?.()),
2578
- "true",
2579
- "false"
2580
- ], { stdio: "ignore", timeout: 20000 });
2581
- return !r.error && r.status === 0;
2582
- },
2583
- launchctl: (args) => run("launchctl", args),
2584
- reg: (args) => run("reg", args),
2585
- launch: (path2) => {
2586
- const r = spawnSync2(wscript(process.env), [path2], {
2587
- stdio: "ignore",
2588
- windowsHide: true,
2589
- timeout: 20000
2590
- });
2591
- return !r.error && r.status === 0;
2592
- },
2593
- ...owner
2594
- };
2595
- }
2596
- function expandHome(value, home) {
2597
- if (value === "~")
2598
- return home;
2599
- return value.startsWith("~/") ? join2(home, value.slice(2)) : value;
2600
- }
2601
- function agentDirectory(s) {
2602
- const selected = s.env.PI_CODING_AGENT_DIR;
2603
- return resolve2(selected ? expandHome(selected, s.home) : join2(s.home, ".pi", "agent"));
2604
- }
2605
- function piUserInstall(s) {
2606
- try {
2607
- const managed = join2(agentDirectory(s), "npm", "node_modules", "@abelxiaoxing", "cadence");
2608
- return realpathSync2(managed) === realpathSync2(s.root);
2609
- } catch {
2610
- return false;
2611
- }
2612
- }
2613
- function sdkPackages(root) {
2614
- let start;
2615
- try {
2616
- start = realpathSync2(join2(root, "src"));
2617
- } catch {
2618
- return;
2619
- }
2620
- const found = [];
2621
- for (const name of ["pi-coding-agent", "pi-ai"]) {
2622
- let hit;
2623
- for (let dir = start;!hit; dir = dirname2(dir)) {
2624
- const candidate = join2(dir, "node_modules", "@earendil-works", name, "package.json");
2625
- if (regularFile(candidate))
2626
- hit = candidate;
2627
- else if (dirname2(dir) === dir)
2628
- return;
2629
- }
2630
- found.push(hit);
2631
- }
2632
- return found;
2633
- }
2634
- function unitPath(s) {
2635
- const data = s.env.XDG_DATA_HOME;
2636
- return join2(data && isAbsolute2(data) ? data : join2(s.home, ".local", "share"), "systemd", "user", s.unit);
2637
- }
2638
- function servicePath(value) {
2639
- const kept = (value ?? "").split(delimiter).filter((entry) => isAbsolute2(entry) && !entry.endsWith(`${sep2}node_modules${sep2}.bin`) && !entry.includes(`${sep2}node-gyp-bin`));
2640
- return kept.length ? [...new Set(kept)].join(delimiter) : "/usr/local/bin:/usr/bin:/bin";
2641
- }
2642
- function serviceEnvironment(s) {
2643
- const out = [["PATH", servicePath(s.env.PATH)]];
2644
- for (const key of ["LANG", "LC_ALL"]) {
2645
- const value = s.env[key];
2646
- if (value)
2647
- out.push([key, value]);
2648
- }
2649
- const state = s.env.XDG_STATE_HOME;
2650
- if (state && isAbsolute2(state))
2651
- out.push(["XDG_STATE_HOME", state]);
2652
- if (s.env.PI_CODING_AGENT_DIR)
2653
- out.push(["PI_CODING_AGENT_DIR", agentDirectory(s)]);
2654
- return out;
2655
- }
2656
- function checked(value) {
2657
- if (/[\u0000-\u001f\u007f]/.test(value))
2658
- throw new ContractError("invalid");
2659
- return value;
2660
- }
2661
- var specifiers = (value) => checked(value).replaceAll("%", "%%");
2662
- var quoted = (value) => `"${specifiers(value).replace(/["\\]/g, "\\$&")}"`;
2663
- var argument = (value) => quoted(value).replaceAll("$", "$$$$");
2664
- function buildUnit(u) {
2665
- const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
2666
- return [
2667
- `${MARKER}; \`cadence server install\` and \`restart\` rewrite this file.`,
2668
- "# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
2669
- "[Unit]",
2670
- "Description=Cadence shared task service and WebUI",
2671
- "Documentation=https://github.com/abelxiaoxing/cadence/blob/main/docs/service-management.md",
2672
- `ConditionPathExists=${specifiers(u.cli)}`,
2673
- `ConditionPathExists=${specifiers(join2(u.agentDir, "auth.json"))}`,
2674
- ...u.sdk.map((path2) => `ConditionPathExists=${specifiers(path2)}`),
2675
- "StartLimitIntervalSec=300",
2676
- "StartLimitBurst=3",
2677
- "",
2678
- "[Service]",
2679
- "Type=simple",
2680
- `WorkingDirectory=${specifiers(u.workspace)}`,
2681
- ...u.environment.map(([k, v]) => `Environment=${quoted(`${k}=${v}`)}`),
2682
- `ExecStart=${command(["start", "--foreground", "--workspace", u.workspace])}`,
2683
- `ExecStop=-${command(["stop", "--workspace", u.workspace])}`,
2684
- "KillMode=mixed",
2685
- "TimeoutStopSec=90",
2686
- "Restart=on-failure",
2687
- "RestartSec=10",
2688
- "",
2689
- "[Install]",
2690
- "WantedBy=default.target",
2691
- ""
2692
- ].join(`
2693
- `);
2694
- }
2695
- function readGenerated(path2, marker) {
2696
- try {
2697
- if (!lstatSync2(path2).isFile())
2698
- return null;
2699
- const bytes = readFileSync(path2);
2700
- const text2 = bytes[0] === 255 && bytes[1] === 254 ? bytes.subarray(2).toString("utf16le") : bytes.toString("utf8");
2701
- return text2.startsWith(marker) ? text2 : null;
2702
- } catch (error) {
2703
- if (error.code === "ENOENT")
2704
- return;
2705
- throw error;
2706
- }
2707
- }
2708
- function unitWorkspace(text2) {
2709
- return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
2710
- }
2711
- function directory(path2) {
2712
- try {
2713
- return isAbsolute2(path2) && statSync(path2).isDirectory();
2714
- } catch {
2715
- return false;
2716
- }
2717
- }
2718
- function serviceStateHome(s) {
2719
- const state = s.platform === "win32" ? s.env.LOCALAPPDATA : s.env.XDG_STATE_HOME;
2720
- return state && isAbsolute2(state) ? state : join2(s.home, ...s.platform === "win32" ? ["AppData", "Local"] : [".local", "state"]);
2721
- }
2722
- function selectWorkspace(s, requested, kept) {
2723
- let chosen;
2724
- if (requested) {
2725
- chosen = expandHome(requested, s.home);
2726
- if (!directory(chosen))
2727
- throw new ContractError("invalid");
2728
- chosen = resolve2(chosen);
2729
- } else if (kept && directory(kept))
2730
- chosen = kept;
2731
- const landing = chosen ? effectiveWorkspace(chosen, serviceStateHome(s), s.home) : { workspace: join2(s.home, LANDING_WORKSPACE), redirected: false };
2732
- if (landing.workspace !== chosen)
2733
- mkdirSync3(landing.workspace, { recursive: true });
2734
- return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
2735
- }
2736
- function write(path2, content) {
2737
- mkdirSync3(dirname2(path2), { recursive: true });
2738
- const temporary = `${path2}.${process.pid}.tmp`;
2739
- try {
2740
- writeFileSync2(temporary, content, { mode: 420, flag: "wx" });
2741
- renameSync2(temporary, path2);
2742
- } finally {
2743
- rmSync(temporary, { force: true });
2744
- }
2745
- }
2746
- function regularFile(path2) {
2747
- try {
2748
- return statSync(path2).isFile();
2749
- } catch {
2750
- return false;
2751
- }
2752
- }
2753
- async function settle(s, workspace) {
2754
- if (!workspace)
2755
- return;
2756
- const status = await s.stopOwner(workspace);
2757
- if (status === "stopped")
2758
- return;
2759
- return {
2760
- status: status === "quarantined" || status === "unavailable" ? status : "unconfirmed",
2761
- workspace
2762
- };
2763
- }
2764
- function bootStart(s, attempt) {
2765
- const current = s.linger();
2766
- if (current === undefined)
2767
- return {};
2768
- if (current)
2769
- return { linger: "on" };
2770
- if (!attempt)
2771
- return { linger: "off" };
2772
- return {
2773
- linger: s.enableLinger(false) || s.enableLinger(true) ? "enabled" : "refused"
2774
- };
2775
- }
2776
- function configureAutostart(s, mode, requested, linger = false) {
2777
- if (s.platform !== "linux")
2778
- return { status: "unsupported" };
2779
- if (s.systemctl(["show-environment"]).status !== 0)
2780
- return { status: "unavailable" };
2781
- const path2 = unitPath(s);
2782
- const previous = readGenerated(path2, MARKER);
2783
- if (previous === null)
2784
- return { status: "foreign", unit: path2 };
2785
- if (mode === "restart" && previous === undefined)
2786
- return { status: "absent" };
2787
- const sdk = sdkPackages(s.root);
2788
- if (!sdk)
2789
- return { status: "missing-pi-sdk" };
2790
- const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
2791
- const agentDir = agentDirectory(s);
2792
- const content = buildUnit({
2793
- node: s.node,
2794
- cli: join2(s.root, "src", "service-cli.mjs"),
2795
- workspace,
2796
- agentDir,
2797
- sdk,
2798
- environment: serviceEnvironment(s)
2799
- });
2800
- const enabled = s.systemctl(["is-enabled", s.unit]).stdout.trim();
2801
- const active = s.systemctl(["is-active", "--quiet", s.unit]).status === 0;
2802
- if (active)
2803
- s.systemctl(["stop", s.unit]);
2804
- write(path2, content);
2805
- s.systemctl(["daemon-reload"]);
2806
- const result = {
2807
- unit: path2,
2808
- workspace,
2809
- ...redirectedFrom ? { redirectedFrom } : {}
2810
- };
2811
- if (enabled.startsWith("masked"))
2812
- return { status: "masked", ...result };
2813
- if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
2814
- return { status: "failed", ...result };
2815
- const boot = () => mode === "install" ? bootStart(s, linger) : {};
2816
- if (!regularFile(join2(agentDir, "auth.json")))
2817
- return { status: "waiting-for-pi-login", ...result, ...boot() };
2818
- s.systemctl(["reset-failed", s.unit]);
2819
- const started = s.systemctl(["start", s.unit]).status === 0;
2820
- return { status: started ? "started" : "failed", ...result, ...boot() };
2821
- }
2822
- function stopAutostart(s) {
2823
- if (s.platform !== "linux")
2824
- return { status: "unsupported" };
2825
- const path2 = unitPath(s);
2826
- const previous = readGenerated(path2, MARKER);
2827
- if (previous === null)
2828
- return { status: "foreign", unit: path2 };
2829
- if (previous === undefined)
2830
- return { status: "absent" };
2831
- if (s.systemctl(["show-environment"]).status !== 0)
2832
- return { status: "unavailable", unit: path2 };
2833
- return {
2834
- status: s.systemctl(["stop", s.unit]).status === 0 ? "stopped" : "failed",
2835
- unit: path2,
2836
- workspace: unitWorkspace(previous)
2837
- };
2838
- }
2839
- function removeAutostart(s) {
2840
- if (s.platform !== "linux")
2841
- return { status: "unsupported" };
2842
- const path2 = unitPath(s);
2843
- const previous = readGenerated(path2, MARKER);
2844
- if (previous === null)
2845
- return { status: "foreign", unit: path2 };
2846
- if (previous === undefined)
2847
- return { status: "absent" };
2848
- if (s.systemctl(["show-environment"]).status !== 0)
2849
- return { status: "unavailable", unit: path2 };
2850
- if (s.systemctl(["disable", "--now", s.unit]).status !== 0)
2851
- return { status: "failed", unit: path2 };
2852
- rmSync(path2);
2853
- s.systemctl(["daemon-reload"]);
2854
- s.systemctl(["reset-failed", s.unit]);
2855
- return { status: "removed", unit: path2 };
2856
- }
2857
- function autostartStatus(s) {
2858
- if (s.platform !== "linux")
2859
- return { status: "unsupported" };
2860
- const path2 = unitPath(s);
2861
- const previous = readGenerated(path2, MARKER);
2862
- if (previous === null)
2863
- return { status: "foreign", unit: path2 };
2864
- if (previous === undefined)
2865
- return { status: "absent" };
2866
- return {
2867
- status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
2868
- enabled: s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown",
2869
- unit: path2,
2870
- workspace: unitWorkspace(previous),
2871
- ...bootStart(s, false)
2872
- };
2873
- }
2514
+ lstatSync as lstatSync4,
2515
+ mkdirSync as mkdirSync4,
2516
+ readFileSync,
2517
+ realpathSync as realpathSync3,
2518
+ renameSync as renameSync3,
2519
+ rmSync,
2520
+ statSync as statSync2,
2521
+ writeFileSync as writeFileSync3
2522
+ } from "node:fs";
2523
+ import { homedir as homedir3 } from "node:os";
2524
+ import {
2525
+ delimiter,
2526
+ dirname as dirname4,
2527
+ isAbsolute as isAbsolute3,
2528
+ join as join4,
2529
+ resolve as resolve3,
2530
+ sep as sep3,
2531
+ win32
2532
+ } from "node:path";
2874
2533
 
2875
- // src/service/coordination.ts
2876
- import { randomBytes, randomUUID as randomUUID3 } from "node:crypto";
2877
- import { opendirSync, realpathSync as realpathSync3 } from "node:fs";
2878
- import { hostname as hostname2 } from "node:os";
2879
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join4, relative as relative3, sep as sep4 } from "node:path";
2534
+ // src/service/server-config.ts
2535
+ import { randomBytes } from "node:crypto";
2536
+ import { lstatSync as lstatSync3, realpathSync as realpathSync2, statSync } from "node:fs";
2537
+ import { dirname as dirname3, join as join3 } from "node:path";
2538
+
2539
+ // skills/_shared/load-config.mjs
2540
+ function parseEnvFile(content) {
2541
+ const values = {};
2542
+ const lines = content.replaceAll(`\r
2543
+ `, `
2544
+ `).replaceAll("\r", `
2545
+ `).split(`
2546
+ `);
2547
+ for (let index = 0;index < lines.length; index += 1) {
2548
+ const line = lines[index].trim();
2549
+ if (!line || line.startsWith("#"))
2550
+ continue;
2551
+ const separator = line.indexOf("=");
2552
+ if (separator <= 0)
2553
+ throw new Error(`Invalid configuration syntax at line ${index + 1}`);
2554
+ const name = line.slice(0, separator).trim();
2555
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
2556
+ throw new Error(`Invalid configuration name at line ${index + 1}`);
2557
+ }
2558
+ let value = line.slice(separator + 1).trim();
2559
+ const quote = value[0];
2560
+ if (quote === '"' || quote === "'") {
2561
+ if (value.at(-1) !== quote) {
2562
+ throw new Error(`Unterminated quoted configuration value at line ${index + 1}`);
2563
+ }
2564
+ value = value.slice(1, -1);
2565
+ }
2566
+ values[name] = value;
2567
+ }
2568
+ return values;
2569
+ }
2880
2570
 
2881
2571
  // src/service/private-files.ts
2882
2572
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
@@ -2885,15 +2575,15 @@ import {
2885
2575
  constants as constants3,
2886
2576
  fstatSync as fstatSync2,
2887
2577
  fsyncSync as fsyncSync2,
2888
- lstatSync as lstatSync3,
2889
- mkdirSync as mkdirSync4,
2578
+ lstatSync as lstatSync2,
2579
+ mkdirSync as mkdirSync3,
2890
2580
  openSync as openSync3,
2891
2581
  readSync as readSync2,
2892
- renameSync as renameSync3,
2582
+ renameSync as renameSync2,
2893
2583
  unlinkSync as unlinkSync2,
2894
- writeFileSync as writeFileSync3
2584
+ writeFileSync as writeFileSync2
2895
2585
  } from "node:fs";
2896
- import { dirname as dirname3, isAbsolute as isAbsolute3, join as join3, relative as relative2, resolve as resolve3, sep as sep3 } from "node:path";
2586
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
2897
2587
  var METADATA_BYTES = 16 * 1024;
2898
2588
  var REGISTRY_BYTES = 1024 * 1024;
2899
2589
  var REGISTRY_ENTRIES = 128;
@@ -2945,7 +2635,7 @@ class DirectoryWalks {
2945
2635
  return false;
2946
2636
  let st;
2947
2637
  try {
2948
- st = lstatSync3(path2);
2638
+ st = lstatSync2(path2);
2949
2639
  } catch (error) {
2950
2640
  if (!missing(error))
2951
2641
  throw error;
@@ -2970,13 +2660,13 @@ class PrivateFiles {
2970
2660
  }
2971
2661
  directory(path2, create = false, walks) {
2972
2662
  const tail = relative2(this.namespace, path2);
2973
- if (isAbsolute3(tail) || tail === ".." || tail.startsWith(`..${sep3}`))
2663
+ if (isAbsolute2(tail) || tail === ".." || tail.startsWith(`..${sep2}`))
2974
2664
  refused();
2975
2665
  if (!create) {
2976
- const full = resolve3(path2);
2666
+ const full = resolve2(path2);
2977
2667
  if (walks?.reuse(full, this.ownerOnly))
2978
2668
  return true;
2979
- const namespace = resolve3(this.namespace);
2669
+ const namespace = resolve2(this.namespace);
2980
2670
  let inside = false, last;
2981
2671
  const found = safeDirectory(path2, false, (at2, st) => {
2982
2672
  if (at2 === namespace)
@@ -2991,23 +2681,23 @@ class PrivateFiles {
2991
2681
  }
2992
2682
  if (!safeDirectory(this.namespace, true))
2993
2683
  return false;
2994
- privateMode(lstatSync3(this.namespace), this.ownerOnly);
2684
+ privateMode(lstatSync2(this.namespace), this.ownerOnly);
2995
2685
  let at = this.namespace;
2996
- for (const part of tail.split(sep3).filter(Boolean)) {
2997
- at = join3(at, part);
2686
+ for (const part of tail.split(sep2).filter(Boolean)) {
2687
+ at = join2(at, part);
2998
2688
  let st;
2999
2689
  try {
3000
- st = lstatSync3(at);
2690
+ st = lstatSync2(at);
3001
2691
  } catch (error) {
3002
2692
  if (!missing(error))
3003
2693
  throw error;
3004
2694
  try {
3005
- mkdirSync4(at, { mode: 448 });
2695
+ mkdirSync3(at, { mode: 448 });
3006
2696
  } catch (mkdirError) {
3007
2697
  if (!(mkdirError instanceof Error && ("code" in mkdirError) && mkdirError.code === "EEXIST"))
3008
2698
  throw mkdirError;
3009
2699
  }
3010
- st = lstatSync3(at);
2700
+ st = lstatSync2(at);
3011
2701
  }
3012
2702
  if (!st.isDirectory() || st.isSymbolicLink())
3013
2703
  throw new Error("Unsafe directory");
@@ -3016,11 +2706,11 @@ class PrivateFiles {
3016
2706
  return true;
3017
2707
  }
3018
2708
  read(path2, maximum = METADATA_BYTES, walks) {
3019
- if (!this.directory(dirname3(path2), false, walks))
2709
+ if (!this.directory(dirname2(path2), false, walks))
3020
2710
  return;
3021
2711
  let before;
3022
2712
  try {
3023
- before = lstatSync3(path2);
2713
+ before = lstatSync2(path2);
3024
2714
  } catch (error) {
3025
2715
  if (missing(error))
3026
2716
  return;
@@ -3043,7 +2733,7 @@ class PrivateFiles {
3043
2733
  size += n;
3044
2734
  }
3045
2735
  const bytes = buffer.subarray(0, size);
3046
- if (size !== opened.size || !same(opened, fstatSync2(fd)) || !same(opened, lstatSync3(path2)))
2736
+ if (size !== opened.size || !same(opened, fstatSync2(fd)) || !same(opened, lstatSync2(path2)))
3047
2737
  refused();
3048
2738
  return { bytes, identity: hash(bytes), stamp: stamp(opened) };
3049
2739
  } finally {
@@ -3058,15 +2748,15 @@ class PrivateFiles {
3058
2748
  create(path2, bytes, maximum = METADATA_BYTES) {
3059
2749
  if (Buffer.byteLength(bytes) > maximum)
3060
2750
  throw new ContractError("oversize");
3061
- this.directory(dirname3(path2), true);
2751
+ this.directory(dirname2(path2), true);
3062
2752
  const fd = retryWindowsFileOperation(() => openSync3(path2, "wx", 384));
3063
2753
  try {
3064
- writeFileSync3(fd, bytes);
2754
+ writeFileSync2(fd, bytes);
3065
2755
  fsyncSync2(fd);
3066
2756
  } finally {
3067
2757
  closeSync3(fd);
3068
2758
  }
3069
- this.syncDirectory(dirname3(path2));
2759
+ this.syncDirectory(dirname2(path2));
3070
2760
  const result = this.read(path2, maximum);
3071
2761
  if (!result || result.identity !== hash(bytes))
3072
2762
  refused();
@@ -3074,11 +2764,11 @@ class PrivateFiles {
3074
2764
  }
3075
2765
  replace(path2, expected, bytes, maximum = METADATA_BYTES) {
3076
2766
  this.assert(path2, expected, maximum);
3077
- const temporary = join3(dirname3(path2), `pending-${randomUUID2()}.json`);
2767
+ const temporary = join2(dirname2(path2), `pending-${randomUUID2()}.json`);
3078
2768
  this.create(temporary, bytes, maximum);
3079
2769
  this.assert(path2, expected, maximum);
3080
- retryWindowsFileOperation(() => renameSync3(temporary, path2), () => this.assert(path2, expected, maximum));
3081
- this.syncDirectory(dirname3(path2));
2770
+ retryWindowsFileOperation(() => renameSync2(temporary, path2), () => this.assert(path2, expected, maximum));
2771
+ this.syncDirectory(dirname2(path2));
3082
2772
  const result = this.read(path2, maximum);
3083
2773
  if (!result || result.identity !== hash(bytes))
3084
2774
  refused();
@@ -3087,7 +2777,7 @@ class PrivateFiles {
3087
2777
  remove(path2, expected, maximum = METADATA_BYTES) {
3088
2778
  this.assert(path2, expected, maximum);
3089
2779
  retryWindowsFileOperation(() => unlinkSync2(path2), () => this.assert(path2, expected, maximum));
3090
- this.syncDirectory(dirname3(path2));
2780
+ this.syncDirectory(dirname2(path2));
3091
2781
  }
3092
2782
  syncDirectory(path2) {
3093
2783
  if (process.platform === "win32")
@@ -3100,31 +2790,575 @@ class PrivateFiles {
3100
2790
  }
3101
2791
  }
3102
2792
  }
3103
- function stamp(st) {
2793
+ function stamp(st) {
2794
+ return [
2795
+ st.dev,
2796
+ st.ino,
2797
+ st.size,
2798
+ st.mode,
2799
+ st.uid,
2800
+ st.nlink,
2801
+ st.ctimeMs,
2802
+ st.mtimeMs
2803
+ ].join(":");
2804
+ }
2805
+ function json(snapshot, maximum = METADATA_BYTES) {
2806
+ try {
2807
+ return parseWireJson(new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes), maximum);
2808
+ } catch {
2809
+ return refused();
2810
+ }
2811
+ }
2812
+ function encoded(value) {
2813
+ return `${JSON.stringify(value)}
2814
+ `;
2815
+ }
2816
+
2817
+ // src/service/server-config.ts
2818
+ var DEFAULT_PORT = 17423;
2819
+ var PORT_KEY = "CADENCE_PORT";
2820
+ var TOKEN_KEY = "CADENCE_TOKEN";
2821
+ var CONFIG_BYTES = 64 * 1024;
2822
+ var HEADER = [
2823
+ "# Cadence WebUI for `cadence server install`: edit, then run `cadence server restart`.",
2824
+ "# The token admits anyone who has it to your workspace; keep it private."
2825
+ ];
2826
+ var TOKEN_BYTES = 1024;
2827
+ function accessToken(value) {
2828
+ const result = text(value, TOKEN_BYTES, true);
2829
+ if (utf8Bytes(result) < 32 || /[\p{Cc}\p{Cf}]/u.test(result))
2830
+ throw new ContractError("invalid");
2831
+ return result;
2832
+ }
2833
+ function serverConfigPath(home) {
2834
+ return join3(home, ".pi", "agent", "cadence", ".env");
2835
+ }
2836
+
2837
+ class ServerConfigRefusal extends ContractError {
2838
+ path;
2839
+ reason;
2840
+ constructor(path2, reason) {
2841
+ super("corrupt-selected");
2842
+ this.path = path2;
2843
+ this.reason = reason;
2844
+ }
2845
+ }
2846
+
2847
+ class Unusable extends Error {
2848
+ }
2849
+ var unusable = (reason) => {
2850
+ throw new Unusable(reason);
2851
+ };
2852
+ function syntax(error) {
2853
+ const message = error instanceof Error ? error.message : "";
2854
+ const line = /at line (\d+)$/.exec(message)?.[1];
2855
+ if (!line)
2856
+ return "is not in KEY=value lines";
2857
+ if (message.startsWith("Invalid configuration name"))
2858
+ return `has an invalid name on line ${line} (letters, digits and _ only, no "export")`;
2859
+ if (message.startsWith("Unterminated"))
2860
+ return `has an unterminated quote on line ${line}`;
2861
+ return `has no KEY=value setting on line ${line}`;
2862
+ }
2863
+ function settings(source) {
2864
+ try {
2865
+ parseEnvFile(source);
2866
+ } catch (error) {
2867
+ unusable(`${syntax(error)}, which the research skills refuse too`);
2868
+ }
2869
+ const parts = source.split(/(\r\n|\r|\n)/);
2870
+ const found = {};
2871
+ for (let at = 0;at < parts.length; at += 2) {
2872
+ const line = parts[at].trim();
2873
+ if (!line || line.startsWith("#"))
2874
+ continue;
2875
+ const separator = line.indexOf("=");
2876
+ const key = line.slice(0, separator).trim();
2877
+ const name = key === PORT_KEY ? "port" : key === TOKEN_KEY ? "token" : undefined;
2878
+ if (!name)
2879
+ continue;
2880
+ const previous = found[name];
2881
+ if (previous)
2882
+ unusable(`sets ${key} twice (lines ${previous.line} and ${at / 2 + 1}); keep one`);
2883
+ let value = line.slice(separator + 1).trim();
2884
+ if (value[0] === '"' || value[0] === "'")
2885
+ value = value.slice(1, -1);
2886
+ found[name] = { at, line: at / 2 + 1, value };
2887
+ }
2888
+ return { parts, ...found };
2889
+ }
2890
+ function decode(source) {
2891
+ const { port, token } = settings(source);
2892
+ if (!port)
2893
+ unusable(`has no ${PORT_KEY} line`);
2894
+ if (!token)
2895
+ unusable(`has no ${TOKEN_KEY} line`);
2896
+ const p = port, t = token;
2897
+ const number = Number(p.value);
2898
+ if (!/^[1-9][0-9]{0,4}$/.test(p.value) || number > 65535)
2899
+ unusable(`has ${PORT_KEY} on line ${p.line} that is not a whole number from 1 to 65535`);
2900
+ try {
2901
+ return { port: number, token: accessToken(t.value) };
2902
+ } catch {
2903
+ return unusable(`has ${TOKEN_KEY} on line ${t.line} shorter than 32 characters or with control characters`);
2904
+ }
2905
+ }
2906
+ var code = (error) => error?.code ?? "error";
2907
+ function locate(path2) {
2908
+ let real;
2909
+ try {
2910
+ real = realpathSync2(path2);
2911
+ } catch (error) {
2912
+ if (code(error) !== "ENOENT")
2913
+ unusable(`cannot be read (${code(error)})`);
2914
+ let link = false;
2915
+ try {
2916
+ link = lstatSync3(path2).isSymbolicLink();
2917
+ } catch {}
2918
+ return unusable(link ? "is a link to a file that does not exist" : "does not exist");
2919
+ }
2920
+ return { real, files: new PrivateFiles(dirname3(real), false) };
2921
+ }
2922
+ function explain(real) {
2923
+ const uid = process.getuid?.();
2924
+ try {
2925
+ const st = lstatSync3(real);
2926
+ if (!st.isFile())
2927
+ return "is not a regular file";
2928
+ if (uid !== undefined && st.uid !== uid)
2929
+ return "is not owned by you";
2930
+ if (uid !== undefined && lstatSync3(dirname3(real)).uid !== uid)
2931
+ return `is in ${dirname3(real)}, which is not owned by you`;
2932
+ if (st.nlink !== 1)
2933
+ return "has more than one hard link";
2934
+ if (st.size > CONFIG_BYTES)
2935
+ return "is larger than 64 KiB";
2936
+ return "changed while Cadence read it; try again";
2937
+ } catch (error) {
2938
+ return `cannot be read (${code(error)})`;
2939
+ }
2940
+ }
2941
+ function load(path2) {
2942
+ const { real, files } = locate(path2);
2943
+ let snapshot;
2944
+ try {
2945
+ snapshot = files.read(real, CONFIG_BYTES);
2946
+ } catch {
2947
+ return unusable(explain(real));
2948
+ }
2949
+ if (!snapshot)
2950
+ return unusable("does not exist");
2951
+ let source;
2952
+ try {
2953
+ source = decoded(snapshot);
2954
+ } catch {
2955
+ return unusable("is not UTF-8 text");
2956
+ }
2957
+ return { path: path2, real, files, snapshot, source };
2958
+ }
2959
+ function decoded(snapshot) {
2960
+ return new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes);
2961
+ }
2962
+ function refusing(path2, read) {
2963
+ try {
2964
+ return read();
2965
+ } catch (error) {
2966
+ if (error instanceof ServerConfigRefusal)
2967
+ throw error;
2968
+ throw new ServerConfigRefusal(path2, error instanceof Unusable ? error.message : `cannot be read (${code(error)})`);
2969
+ }
2970
+ }
2971
+ function readServerConfig(path2) {
2972
+ return refusing(path2, () => {
2973
+ const file = load(path2);
2974
+ return { ...file, config: decode(file.source) };
2975
+ });
2976
+ }
2977
+ function ensureServerConfig(home) {
2978
+ const path2 = serverConfigPath(home);
2979
+ const added = refusing(path2, () => {
2980
+ try {
2981
+ ensureUserConfig(home);
2982
+ } catch (error) {
2983
+ unusable(`could not be created (${code(error)})`);
2984
+ }
2985
+ const file2 = load(path2);
2986
+ const found = settings(file2.source);
2987
+ const lines = [];
2988
+ if (!found.port)
2989
+ lines.push(`${PORT_KEY}=${DEFAULT_PORT}`);
2990
+ if (!found.token)
2991
+ lines.push(`${TOKEN_KEY}=${randomBytes(32).toString("base64url")}`);
2992
+ if (lines.length) {
2993
+ const { source } = file2;
2994
+ const eol = source.includes(`\r
2995
+ `) ? `\r
2996
+ ` : `
2997
+ `;
2998
+ const ending = !source || /[\r\n]$/.test(source) ? "" : eol;
2999
+ const gap = source ? eol : "";
3000
+ try {
3001
+ file2.files.replace(file2.real, file2.snapshot, `${source}${ending}${gap}${[...HEADER, ...lines].join(eol)}${eol}`, CONFIG_BYTES);
3002
+ } catch (error) {
3003
+ unusable(`could not be updated (${code(error)})`);
3004
+ }
3005
+ }
3006
+ return lines.map((line) => line.slice(0, line.indexOf("=")));
3007
+ });
3008
+ const file = readServerConfig(path2);
3009
+ const exposed = process.platform !== "win32" && (statSync(file.real).mode & 63) !== 0;
3010
+ return { ...file, added, exposed };
3011
+ }
3012
+
3013
+ // src/service/autostart.ts
3014
+ var UNIT = "cadence.service";
3015
+ var MARKER = "# Generated by @abelxiaoxing/cadence";
3016
+ function wscript(env) {
3017
+ const root = env.SystemRoot;
3018
+ return win32.join(root && win32.isAbsolute(root) ? root : "C:\\Windows", "System32", "wscript.exe");
3019
+ }
3020
+ function hostSystem(root, owner) {
3021
+ const run = (command, args) => {
3022
+ const r = spawnSync2(command, args, {
3023
+ encoding: "utf8",
3024
+ stdio: ["ignore", "pipe", "ignore"],
3025
+ windowsHide: true
3026
+ });
3027
+ return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
3028
+ };
3029
+ return {
3030
+ platform: process.platform,
3031
+ env: process.env,
3032
+ home: homedir3(),
3033
+ node: process.execPath,
3034
+ root,
3035
+ unit: UNIT,
3036
+ uid: process.getuid?.(),
3037
+ systemctl: (args) => run("systemctl", ["--user", ...args]),
3038
+ linger: () => {
3039
+ const r = run("loginctl", [
3040
+ "show-user",
3041
+ String(process.getuid?.()),
3042
+ "--property=Linger",
3043
+ "--value"
3044
+ ]);
3045
+ return r.status === 0 ? r.stdout.trim() === "yes" : undefined;
3046
+ },
3047
+ enableLinger: (interactive) => {
3048
+ const r = interactive ? spawnSync2("loginctl", ["enable-linger"], { stdio: "inherit" }) : spawnSync2("busctl", [
3049
+ "--system",
3050
+ "--allow-interactive-authorization=no",
3051
+ "--timeout=15",
3052
+ "call",
3053
+ "org.freedesktop.login1",
3054
+ "/org/freedesktop/login1",
3055
+ "org.freedesktop.login1.Manager",
3056
+ "SetUserLinger",
3057
+ "ubb",
3058
+ String(process.getuid?.()),
3059
+ "true",
3060
+ "false"
3061
+ ], { stdio: "ignore", timeout: 20000 });
3062
+ return !r.error && r.status === 0;
3063
+ },
3064
+ launchctl: (args) => run("launchctl", args),
3065
+ reg: (args) => run("reg", args),
3066
+ launch: (path2) => {
3067
+ const r = spawnSync2(wscript(process.env), [path2], {
3068
+ stdio: "ignore",
3069
+ windowsHide: true,
3070
+ timeout: 20000
3071
+ });
3072
+ return !r.error && r.status === 0;
3073
+ },
3074
+ ...owner
3075
+ };
3076
+ }
3077
+ function expandHome(value, home) {
3078
+ if (value === "~")
3079
+ return home;
3080
+ return value.startsWith("~/") ? join4(home, value.slice(2)) : value;
3081
+ }
3082
+ function agentDirectory(s) {
3083
+ const selected = s.env.PI_CODING_AGENT_DIR;
3084
+ return resolve3(selected ? expandHome(selected, s.home) : join4(s.home, ".pi", "agent"));
3085
+ }
3086
+ function piUserInstall(s) {
3087
+ try {
3088
+ const managed = join4(agentDirectory(s), "npm", "node_modules", "@abelxiaoxing", "cadence");
3089
+ return realpathSync3(managed) === realpathSync3(s.root);
3090
+ } catch {
3091
+ return false;
3092
+ }
3093
+ }
3094
+ function sdkPackages(root) {
3095
+ let start;
3096
+ try {
3097
+ start = realpathSync3(join4(root, "src"));
3098
+ } catch {
3099
+ return;
3100
+ }
3101
+ const found = [];
3102
+ for (const name of ["pi-coding-agent", "pi-ai"]) {
3103
+ let hit;
3104
+ for (let dir = start;!hit; dir = dirname4(dir)) {
3105
+ const candidate = join4(dir, "node_modules", "@earendil-works", name, "package.json");
3106
+ if (regularFile(candidate))
3107
+ hit = candidate;
3108
+ else if (dirname4(dir) === dir)
3109
+ return;
3110
+ }
3111
+ found.push(hit);
3112
+ }
3113
+ return found;
3114
+ }
3115
+ function unitPath(s) {
3116
+ const data = s.env.XDG_DATA_HOME;
3117
+ return join4(data && isAbsolute3(data) ? data : join4(s.home, ".local", "share"), "systemd", "user", s.unit);
3118
+ }
3119
+ function servicePath(value) {
3120
+ const kept = (value ?? "").split(delimiter).filter((entry) => isAbsolute3(entry) && !entry.endsWith(`${sep3}node_modules${sep3}.bin`) && !entry.includes(`${sep3}node-gyp-bin`));
3121
+ return kept.length ? [...new Set(kept)].join(delimiter) : "/usr/local/bin:/usr/bin:/bin";
3122
+ }
3123
+ function serviceEnvironment(s) {
3124
+ const out = [["PATH", servicePath(s.env.PATH)]];
3125
+ for (const key of ["LANG", "LC_ALL"]) {
3126
+ const value = s.env[key];
3127
+ if (value)
3128
+ out.push([key, value]);
3129
+ }
3130
+ const state = s.env.XDG_STATE_HOME;
3131
+ if (state && isAbsolute3(state))
3132
+ out.push(["XDG_STATE_HOME", state]);
3133
+ if (s.env.PI_CODING_AGENT_DIR)
3134
+ out.push(["PI_CODING_AGENT_DIR", agentDirectory(s)]);
3135
+ return out;
3136
+ }
3137
+ function checked(value) {
3138
+ if (/[\u0000-\u001f\u007f]/.test(value))
3139
+ throw new ContractError("invalid");
3140
+ return value;
3141
+ }
3142
+ var specifiers = (value) => checked(value).replaceAll("%", "%%");
3143
+ var quoted = (value) => `"${specifiers(value).replace(/["\\]/g, "\\$&")}"`;
3144
+ var argument = (value) => quoted(value).replaceAll("$", "$$$$");
3145
+ function buildUnit(u) {
3146
+ const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
3104
3147
  return [
3105
- st.dev,
3106
- st.ino,
3107
- st.size,
3108
- st.mode,
3109
- st.uid,
3110
- st.nlink,
3111
- st.ctimeMs,
3112
- st.mtimeMs
3113
- ].join(":");
3148
+ `${MARKER}; \`cadence server install\` and \`restart\` rewrite this file.`,
3149
+ "# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
3150
+ "[Unit]",
3151
+ "Description=Cadence shared task service and WebUI",
3152
+ "Documentation=https://github.com/abelxiaoxing/cadence/blob/main/docs/service-management.md",
3153
+ `ConditionPathExists=${specifiers(u.cli)}`,
3154
+ `ConditionPathExists=${specifiers(join4(u.agentDir, "auth.json"))}`,
3155
+ ...u.sdk.map((path2) => `ConditionPathExists=${specifiers(path2)}`),
3156
+ "StartLimitIntervalSec=300",
3157
+ "StartLimitBurst=3",
3158
+ "",
3159
+ "[Service]",
3160
+ "Type=simple",
3161
+ `WorkingDirectory=${specifiers(u.workspace)}`,
3162
+ ...u.environment.map(([k, v]) => `Environment=${quoted(`${k}=${v}`)}`),
3163
+ `ExecStart=${command(["start", "--foreground", "--workspace", u.workspace, "--server-config", u.serverConfig])}`,
3164
+ `ExecStop=-${command(["stop", "--workspace", u.workspace])}`,
3165
+ "KillMode=mixed",
3166
+ "TimeoutStopSec=90",
3167
+ "Restart=on-failure",
3168
+ "RestartSec=10",
3169
+ "",
3170
+ "[Install]",
3171
+ "WantedBy=default.target",
3172
+ ""
3173
+ ].join(`
3174
+ `);
3114
3175
  }
3115
- function json(snapshot, maximum = METADATA_BYTES) {
3176
+ function readGenerated(path2, marker) {
3116
3177
  try {
3117
- return parseWireJson(new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes), maximum);
3178
+ if (!lstatSync4(path2).isFile())
3179
+ return null;
3180
+ const bytes = readFileSync(path2);
3181
+ const text2 = bytes[0] === 255 && bytes[1] === 254 ? bytes.subarray(2).toString("utf16le") : bytes.toString("utf8");
3182
+ return text2.startsWith(marker) ? text2 : null;
3183
+ } catch (error) {
3184
+ if (error.code === "ENOENT")
3185
+ return;
3186
+ throw error;
3187
+ }
3188
+ }
3189
+ function unitWorkspace(text2) {
3190
+ return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
3191
+ }
3192
+ function directory(path2) {
3193
+ try {
3194
+ return isAbsolute3(path2) && statSync2(path2).isDirectory();
3118
3195
  } catch {
3119
- return refused();
3196
+ return false;
3120
3197
  }
3121
3198
  }
3122
- function encoded(value) {
3123
- return `${JSON.stringify(value)}
3124
- `;
3199
+ function serviceStateHome(s) {
3200
+ const state = s.platform === "win32" ? s.env.LOCALAPPDATA : s.env.XDG_STATE_HOME;
3201
+ return state && isAbsolute3(state) ? state : join4(s.home, ...s.platform === "win32" ? ["AppData", "Local"] : [".local", "state"]);
3202
+ }
3203
+ function selectWorkspace(s, requested, kept) {
3204
+ let chosen;
3205
+ if (requested) {
3206
+ chosen = expandHome(requested, s.home);
3207
+ if (!directory(chosen))
3208
+ throw new ContractError("invalid");
3209
+ chosen = resolve3(chosen);
3210
+ } else if (kept && directory(kept))
3211
+ chosen = kept;
3212
+ const landing = chosen ? effectiveWorkspace(chosen, serviceStateHome(s), s.home) : { workspace: join4(s.home, LANDING_WORKSPACE), redirected: false };
3213
+ if (landing.workspace !== chosen)
3214
+ mkdirSync4(landing.workspace, { recursive: true });
3215
+ return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
3216
+ }
3217
+ function write(path2, content) {
3218
+ mkdirSync4(dirname4(path2), { recursive: true });
3219
+ const temporary = `${path2}.${process.pid}.tmp`;
3220
+ try {
3221
+ writeFileSync3(temporary, content, { mode: 420, flag: "wx" });
3222
+ renameSync3(temporary, path2);
3223
+ } finally {
3224
+ rmSync(temporary, { force: true });
3225
+ }
3226
+ }
3227
+ function regularFile(path2) {
3228
+ try {
3229
+ return statSync2(path2).isFile();
3230
+ } catch {
3231
+ return false;
3232
+ }
3233
+ }
3234
+ async function settle(s, workspace) {
3235
+ if (!workspace)
3236
+ return;
3237
+ const status = await s.stopOwner(workspace);
3238
+ if (status === "stopped")
3239
+ return;
3240
+ return {
3241
+ status: status === "quarantined" || status === "unavailable" ? status : "unconfirmed",
3242
+ workspace
3243
+ };
3244
+ }
3245
+ function bootStart(s, attempt) {
3246
+ const current = s.linger();
3247
+ if (current === undefined)
3248
+ return {};
3249
+ if (current)
3250
+ return { linger: "on" };
3251
+ if (!attempt)
3252
+ return { linger: "off" };
3253
+ return {
3254
+ linger: s.enableLinger(false) || s.enableLinger(true) ? "enabled" : "refused"
3255
+ };
3256
+ }
3257
+ function configureAutostart(s, mode, requested, linger = false) {
3258
+ if (s.platform !== "linux")
3259
+ return { status: "unsupported" };
3260
+ if (s.systemctl(["show-environment"]).status !== 0)
3261
+ return { status: "unavailable" };
3262
+ const path2 = unitPath(s);
3263
+ const previous = readGenerated(path2, MARKER);
3264
+ if (previous === null)
3265
+ return { status: "foreign", unit: path2 };
3266
+ if (mode === "restart" && previous === undefined)
3267
+ return { status: "absent" };
3268
+ const sdk = sdkPackages(s.root);
3269
+ if (!sdk)
3270
+ return { status: "missing-pi-sdk" };
3271
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
3272
+ const agentDir = agentDirectory(s);
3273
+ const content = buildUnit({
3274
+ node: s.node,
3275
+ cli: join4(s.root, "src", "service-cli.mjs"),
3276
+ workspace,
3277
+ serverConfig: serverConfigPath(s.home),
3278
+ agentDir,
3279
+ sdk,
3280
+ environment: serviceEnvironment(s)
3281
+ });
3282
+ const enabled = s.systemctl(["is-enabled", s.unit]).stdout.trim();
3283
+ const active = s.systemctl(["is-active", "--quiet", s.unit]).status === 0;
3284
+ if (active)
3285
+ s.systemctl(["stop", s.unit]);
3286
+ write(path2, content);
3287
+ s.systemctl(["daemon-reload"]);
3288
+ const result = {
3289
+ unit: path2,
3290
+ workspace,
3291
+ ...redirectedFrom ? { redirectedFrom } : {}
3292
+ };
3293
+ if (enabled.startsWith("masked"))
3294
+ return { status: "masked", ...result };
3295
+ if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
3296
+ return { status: "failed", ...result };
3297
+ const boot = () => mode === "install" ? bootStart(s, linger) : {};
3298
+ if (!regularFile(join4(agentDir, "auth.json")))
3299
+ return { status: "waiting-for-pi-login", ...result, ...boot() };
3300
+ s.systemctl(["reset-failed", s.unit]);
3301
+ const started = s.systemctl(["start", s.unit]).status === 0;
3302
+ return { status: started ? "started" : "failed", ...result, ...boot() };
3303
+ }
3304
+ function stopAutostart(s) {
3305
+ if (s.platform !== "linux")
3306
+ return { status: "unsupported" };
3307
+ const path2 = unitPath(s);
3308
+ const previous = readGenerated(path2, MARKER);
3309
+ if (previous === null)
3310
+ return { status: "foreign", unit: path2 };
3311
+ if (previous === undefined)
3312
+ return { status: "absent" };
3313
+ if (s.systemctl(["show-environment"]).status !== 0)
3314
+ return { status: "unavailable", unit: path2 };
3315
+ return {
3316
+ status: s.systemctl(["stop", s.unit]).status === 0 ? "stopped" : "failed",
3317
+ unit: path2,
3318
+ workspace: unitWorkspace(previous)
3319
+ };
3320
+ }
3321
+ function removeAutostart(s) {
3322
+ if (s.platform !== "linux")
3323
+ return { status: "unsupported" };
3324
+ const path2 = unitPath(s);
3325
+ const previous = readGenerated(path2, MARKER);
3326
+ if (previous === null)
3327
+ return { status: "foreign", unit: path2 };
3328
+ if (previous === undefined)
3329
+ return { status: "absent" };
3330
+ if (s.systemctl(["show-environment"]).status !== 0)
3331
+ return { status: "unavailable", unit: path2 };
3332
+ if (s.systemctl(["disable", "--now", s.unit]).status !== 0)
3333
+ return { status: "failed", unit: path2 };
3334
+ rmSync(path2);
3335
+ s.systemctl(["daemon-reload"]);
3336
+ s.systemctl(["reset-failed", s.unit]);
3337
+ return { status: "removed", unit: path2 };
3338
+ }
3339
+ function autostartStatus(s) {
3340
+ if (s.platform !== "linux")
3341
+ return { status: "unsupported" };
3342
+ const path2 = unitPath(s);
3343
+ const previous = readGenerated(path2, MARKER);
3344
+ if (previous === null)
3345
+ return { status: "foreign", unit: path2 };
3346
+ if (previous === undefined)
3347
+ return { status: "absent" };
3348
+ return {
3349
+ status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
3350
+ enabled: s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown",
3351
+ unit: path2,
3352
+ workspace: unitWorkspace(previous),
3353
+ ...bootStart(s, false)
3354
+ };
3125
3355
  }
3126
3356
 
3127
3357
  // src/service/coordination.ts
3358
+ import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "node:crypto";
3359
+ import { opendirSync, realpathSync as realpathSync4 } from "node:fs";
3360
+ import { hostname as hostname2 } from "node:os";
3361
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join5, relative as relative3, sep as sep4 } from "node:path";
3128
3362
  var registrationKeys = [
3129
3363
  "schema",
3130
3364
  "root",
@@ -3250,17 +3484,17 @@ class ServiceCoordination {
3250
3484
  stateHome;
3251
3485
  constructor(workspace, stateHome) {
3252
3486
  this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
3253
- const namespace = dirname4(this.store.privateRoot);
3487
+ const namespace = dirname5(this.store.privateRoot);
3254
3488
  this.files = new PrivateFiles(namespace);
3255
- this.stateHome = dirname4(namespace);
3256
- this.directory = join4(this.store.privateRoot, "service-v1");
3257
- this.registryDirectory = join4(namespace, "service-registry-v1");
3489
+ this.stateHome = dirname5(namespace);
3490
+ this.directory = join5(this.store.privateRoot, "service-v1");
3491
+ this.registryDirectory = join5(namespace, "service-registry-v1");
3258
3492
  }
3259
3493
  path(name) {
3260
- return join4(this.directory, name);
3494
+ return join5(this.directory, name);
3261
3495
  }
3262
3496
  registryPath(name) {
3263
- return join4(this.registryDirectory, name);
3497
+ return join5(this.registryDirectory, name);
3264
3498
  }
3265
3499
  registry() {
3266
3500
  if (!this.files.directory(this.registryDirectory))
@@ -3280,7 +3514,7 @@ class ServiceCoordination {
3280
3514
  const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
3281
3515
  const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
3282
3516
  for (const [i, row] of entries.entries()) {
3283
- if (row.stateHome !== this.stateHome || realpathSync3(row.root) !== row.root)
3517
+ if (row.stateHome !== this.stateHome || realpathSync4(row.root) !== row.root)
3284
3518
  refused();
3285
3519
  if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
3286
3520
  refused();
@@ -3308,7 +3542,7 @@ class ServiceCoordination {
3308
3542
  "host-config.json",
3309
3543
  "history-v2"
3310
3544
  ]);
3311
- this.files.directory(join4(this.directory, "history-v2"), false, walks);
3545
+ this.files.directory(join5(this.directory, "history-v2"), false, walks);
3312
3546
  }
3313
3547
  selected() {
3314
3548
  const walks = new DirectoryWalks;
@@ -3468,7 +3702,7 @@ class ServiceCoordination {
3468
3702
  throw error;
3469
3703
  }
3470
3704
  }
3471
- acquire(input, resolvedEndpoint = input.endpoint, initializeNew) {
3705
+ acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
3472
3706
  const config = decodeOwnerConfig(input);
3473
3707
  const bound = endpoint(resolvedEndpoint);
3474
3708
  if (!endpointMatches(config.endpoint, bound))
@@ -3483,7 +3717,8 @@ class ServiceCoordination {
3483
3717
  throw new ContractError("quarantine");
3484
3718
  if (this.store.inspectLock())
3485
3719
  throw new ContractError("conflict");
3486
- if (old && old.config.identity !== hash(encoded(config)))
3720
+ const reselect = !!old && old.config.identity !== hash(encoded(config));
3721
+ if (reselect && (!reselectExisting || !old?.hostConfig))
3487
3722
  throw new ContractError("conflict");
3488
3723
  const row = {
3489
3724
  root: this.store.root,
@@ -3500,7 +3735,17 @@ class ServiceCoordination {
3500
3735
  ...registry.value,
3501
3736
  entries: [...registry.value.entries, row]
3502
3737
  }), REGISTRY_BYTES);
3503
- this.initialize(config);
3738
+ if (old?.hostConfig && reselect && reselectExisting) {
3739
+ reselectExisting(old.hostConfig);
3740
+ const saved = this.files.replace(this.path("config.json"), old.config, encoded(config));
3741
+ this.files.replace(this.path("format.json"), old.manifest, encoded({
3742
+ schema: "cadence-service-format/1",
3743
+ root: this.store.root,
3744
+ stateHome: this.stateHome,
3745
+ configIdentity: saved.identity
3746
+ }));
3747
+ } else
3748
+ this.initialize(config);
3504
3749
  if (!old)
3505
3750
  initializeNew?.();
3506
3751
  const checkout = this.store.acquire();
@@ -3517,7 +3762,7 @@ class ServiceCoordination {
3517
3762
  const capability = this.files.create(this.path("local-capability.json"), encoded({
3518
3763
  schema: "cadence-service-local-capability/1",
3519
3764
  epoch: row.epoch,
3520
- capability: randomBytes(32).toString("base64url")
3765
+ capability: randomBytes2(32).toString("base64url")
3521
3766
  }));
3522
3767
  const value = {
3523
3768
  schema: "cadence-service-registration/1",
@@ -3730,12 +3975,12 @@ class OwnerLease {
3730
3975
 
3731
3976
  // src/service/launchd.ts
3732
3977
  import { rmSync as rmSync2 } from "node:fs";
3733
- import { join as join5 } from "node:path";
3978
+ import { join as join6 } from "node:path";
3734
3979
  var LABEL = "com.abelxiaoxing.cadence";
3735
3980
  var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
3736
3981
  <!-- Generated by @abelxiaoxing/cadence`;
3737
3982
  function plistPath(s) {
3738
- return join5(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
3983
+ return join6(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
3739
3984
  }
3740
3985
  var xml = (value) => checked(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3741
3986
  function buildPlist(u) {
@@ -3750,7 +3995,16 @@ function buildPlist(u) {
3750
3995
  ` <string>${LABEL}</string>`,
3751
3996
  "\t<key>ProgramArguments</key>",
3752
3997
  "\t<array>",
3753
- ...[u.node, u.cli, "start", "--foreground", "--workspace", u.workspace].map(string),
3998
+ ...[
3999
+ u.node,
4000
+ u.cli,
4001
+ "start",
4002
+ "--foreground",
4003
+ "--workspace",
4004
+ u.workspace,
4005
+ "--server-config",
4006
+ u.serverConfig
4007
+ ].map(string),
3754
4008
  "\t</array>",
3755
4009
  "\t<key>WorkingDirectory</key>",
3756
4010
  ` <string>${xml(u.workspace)}</string>`,
@@ -3806,10 +4060,11 @@ async function configureLaunchAgent(s, mode, requested) {
3806
4060
  s.launchctl(["bootout", target(s)]);
3807
4061
  write(path2, buildPlist({
3808
4062
  node: s.node,
3809
- cli: join5(s.root, "src", "service-cli.mjs"),
4063
+ cli: join6(s.root, "src", "service-cli.mjs"),
3810
4064
  workspace,
4065
+ serverConfig: serverConfigPath(s.home),
3811
4066
  environment: serviceEnvironment(s),
3812
- log: join5(s.home, "Library", "Logs", "cadence.log")
4067
+ log: join6(s.home, "Library", "Logs", "cadence.log")
3813
4068
  }));
3814
4069
  const result = {
3815
4070
  unit: path2,
@@ -3820,7 +4075,7 @@ async function configureLaunchAgent(s, mode, requested) {
3820
4075
  s.launchctl(["enable", target(s)]);
3821
4076
  else if (disabled(s))
3822
4077
  return { status: "disabled", ...result };
3823
- if (!regularFile(join5(agentDirectory(s), "auth.json")))
4078
+ if (!regularFile(join6(agentDirectory(s), "auth.json")))
3824
4079
  return { status: "waiting-for-pi-login", ...result };
3825
4080
  const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
3826
4081
  return { status: started ? "started" : "failed", ...result };
@@ -3869,14 +4124,14 @@ function launchAgentStatus(s) {
3869
4124
 
3870
4125
  // src/service/login-run.ts
3871
4126
  import { rmSync as rmSync3 } from "node:fs";
3872
- import { isAbsolute as isAbsolute5, join as join6 } from "node:path";
4127
+ import { isAbsolute as isAbsolute5, join as join7 } from "node:path";
3873
4128
  var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
3874
4129
  var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
3875
4130
  var RUN_VALUE = "Cadence";
3876
4131
  var MARKER3 = "' Generated by @abelxiaoxing/cadence";
3877
4132
  function launcherPath(s) {
3878
4133
  const local = s.env.LOCALAPPDATA;
3879
- return join6(local && isAbsolute5(local) ? local : join6(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
4134
+ return join7(local && isAbsolute5(local) ? local : join7(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
3880
4135
  }
3881
4136
  var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
3882
4137
  function argument2(value) {
@@ -3885,7 +4140,7 @@ function argument2(value) {
3885
4140
  return `"${value}"`;
3886
4141
  }
3887
4142
  function buildLauncher(u) {
3888
- const command = `${argument2(u.node)} ${argument2(u.cli)} start --workspace ${argument2(u.workspace)}`;
4143
+ const command = `${argument2(u.node)} ${argument2(u.cli)} start --workspace ${argument2(u.workspace)} --server-config ${argument2(u.serverConfig)}`;
3889
4144
  return [
3890
4145
  `${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
3891
4146
  `' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
@@ -3929,8 +4184,9 @@ async function configureLoginRun(s, mode, requested) {
3929
4184
  const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
3930
4185
  write(path2, utf16(buildLauncher({
3931
4186
  node: s.node,
3932
- cli: join6(s.root, "src", "service-cli.mjs"),
4187
+ cli: join7(s.root, "src", "service-cli.mjs"),
3933
4188
  workspace,
4189
+ serverConfig: serverConfigPath(s.home),
3934
4190
  environment
3935
4191
  })));
3936
4192
  const result = {
@@ -3944,7 +4200,7 @@ async function configureLoginRun(s, mode, requested) {
3944
4200
  return { status: "failed", ...result };
3945
4201
  s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
3946
4202
  }
3947
- if (!regularFile(join6(agentDirectory(s), "auth.json")))
4203
+ if (!regularFile(join7(agentDirectory(s), "auth.json")))
3948
4204
  return { status: "waiting-for-pi-login", ...result };
3949
4205
  return { status: s.launch(path2) ? "started" : "failed", ...result };
3950
4206
  }
@@ -3997,8 +4253,8 @@ import { isatty } from "node:tty";
3997
4253
 
3998
4254
  // src/history/recovery.ts
3999
4255
  import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
4000
- import { lstatSync as lstatSync6, opendirSync as opendirSync3 } from "node:fs";
4001
- import { join as join8, relative as relative4 } from "node:path";
4256
+ import { lstatSync as lstatSync7, opendirSync as opendirSync3 } from "node:fs";
4257
+ import { join as join9, relative as relative4 } from "node:path";
4002
4258
 
4003
4259
  // src/service/recovery.ts
4004
4260
  async function inspectRecovery(coordination, history) {
@@ -4046,19 +4302,19 @@ async function resolveRecovery(coordination, confirmation, history) {
4046
4302
  }
4047
4303
 
4048
4304
  // src/history/files.ts
4049
- import { createHash as createHash4, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "node:crypto";
4305
+ import { createHash as createHash4, randomBytes as randomBytes3, randomUUID as randomUUID4 } from "node:crypto";
4050
4306
  import {
4051
4307
  closeSync as closeSync5,
4052
4308
  constants as constants5,
4053
4309
  fstatSync as fstatSync4,
4054
4310
  fsyncSync as fsyncSync3,
4055
- lstatSync as lstatSync5,
4311
+ lstatSync as lstatSync6,
4056
4312
  opendirSync as opendirSync2,
4057
4313
  openSync as openSync5,
4058
4314
  statfsSync
4059
4315
  } from "node:fs";
4060
4316
  import { open } from "node:fs/promises";
4061
- import { dirname as dirname6, join as join7 } from "node:path";
4317
+ import { dirname as dirname7, join as join8 } from "node:path";
4062
4318
 
4063
4319
  // src/history/admission.ts
4064
4320
  function decodeRunAdmission(value) {
@@ -4082,19 +4338,19 @@ import {
4082
4338
  closeSync as closeSync4,
4083
4339
  constants as constants4,
4084
4340
  fstatSync as fstatSync3,
4085
- lstatSync as lstatSync4,
4341
+ lstatSync as lstatSync5,
4086
4342
  openSync as openSync4,
4087
4343
  readSync as readSync3
4088
4344
  } from "node:fs";
4089
- import { dirname as dirname5 } from "node:path";
4345
+ import { dirname as dirname6 } from "node:path";
4090
4346
  function bad() {
4091
4347
  throw new ContractError("corrupt-selected");
4092
4348
  }
4093
4349
  var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
4094
4350
  function selected(files, path2, read, walks) {
4095
- if (!files.files.directory(dirname5(path2), false, walks))
4351
+ if (!files.files.directory(dirname6(path2), false, walks))
4096
4352
  bad();
4097
- const before = lstatSync4(path2);
4353
+ const before = lstatSync5(path2);
4098
4354
  if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
4099
4355
  bad();
4100
4356
  const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
@@ -4102,7 +4358,7 @@ function selected(files, path2, read, walks) {
4102
4358
  if (stamp2(fstatSync3(fd)) !== stamp2(before))
4103
4359
  bad();
4104
4360
  const result = read(fd, before.size, stamp2(before));
4105
- if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync4(path2)) !== stamp2(before))
4361
+ if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync5(path2)) !== stamp2(before))
4106
4362
  bad();
4107
4363
  return result;
4108
4364
  } finally {
@@ -4589,14 +4845,14 @@ class HistoryFiles {
4589
4845
  this.coordination = coordination;
4590
4846
  this.format = format;
4591
4847
  this.formatSnapshot = formatSnapshot;
4592
- this.directory = join7(coordination.directory, "history-v2");
4848
+ this.directory = join8(coordination.directory, "history-v2");
4593
4849
  this.files = coordination.files;
4594
4850
  }
4595
4851
  static open(coordination) {
4596
- const dir = join7(coordination.directory, "history-v2");
4852
+ const dir = join8(coordination.directory, "history-v2");
4597
4853
  if (!coordination.files.directory(dir))
4598
4854
  return;
4599
- const file = coordination.files.read(join7(dir, "format.json"));
4855
+ const file = coordination.files.read(join8(dir, "format.json"));
4600
4856
  if (!file)
4601
4857
  corrupt();
4602
4858
  const format = decodeFormat(json(file));
@@ -4612,7 +4868,7 @@ class HistoryFiles {
4612
4868
  existing.assertWritable();
4613
4869
  return existing;
4614
4870
  }
4615
- const dir = join7(c.directory, "history-v2");
4871
+ const dir = join8(c.directory, "history-v2");
4616
4872
  const format = {
4617
4873
  schema: FORMAT,
4618
4874
  storeId: randomUUID4(),
@@ -4622,19 +4878,19 @@ class HistoryFiles {
4622
4878
  c.files.directory(dir, true);
4623
4879
  for (const name of ["commands", "ledger", "runs", "indexes"]) {
4624
4880
  lease.assert();
4625
- c.files.directory(join7(dir, name), true);
4881
+ c.files.directory(join8(dir, name), true);
4626
4882
  }
4627
4883
  lease.assert();
4628
- const keyPath = join7(dir, "read-proof-key.json");
4884
+ const keyPath = join8(dir, "read-proof-key.json");
4629
4885
  c.files.create(keyPath, encoded({
4630
4886
  schema: "cadence-history-read-key/1",
4631
4887
  storeId: format.storeId,
4632
- key: randomBytes2(32).toString("base64url")
4888
+ key: randomBytes3(32).toString("base64url")
4633
4889
  }));
4634
4890
  lease.assert();
4635
- const snapshot = c.files.create(join7(dir, "format.json"), encoded(format));
4891
+ const snapshot = c.files.create(join8(dir, "format.json"), encoded(format));
4636
4892
  const result = new HistoryFiles(c, format, snapshot);
4637
- result.syncParents(join7(dir, "format.json"));
4893
+ result.syncParents(join8(dir, "format.json"));
4638
4894
  result.assertWritable();
4639
4895
  return result;
4640
4896
  }
@@ -4669,7 +4925,7 @@ class HistoryFiles {
4669
4925
  throw new ContractError("quarantine");
4670
4926
  }
4671
4927
  assertFormat() {
4672
- this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot);
4928
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot);
4673
4929
  }
4674
4930
  readProofMaterial(budget) {
4675
4931
  if (this.format.schema !== FORMAT)
@@ -4678,7 +4934,7 @@ class HistoryFiles {
4678
4934
  this.assertReadFormat(budget);
4679
4935
  else
4680
4936
  this.assertFormat();
4681
- const selected2 = this.readBudgeted(join7(this.directory, "read-proof-key.json"), 512, budget);
4937
+ const selected2 = this.readBudgeted(join8(this.directory, "read-proof-key.json"), 512, budget);
4682
4938
  if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
4683
4939
  corrupt();
4684
4940
  try {
@@ -4706,17 +4962,17 @@ class HistoryFiles {
4706
4962
  return;
4707
4963
  }
4708
4964
  this.assertFormat();
4709
- assertIncarnation(this, join7(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
4965
+ assertIncarnation(this, join8(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
4710
4966
  }
4711
4967
  segmentPath(segment) {
4712
- return join7(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4968
+ return join8(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4713
4969
  }
4714
4970
  artifactPath(ref) {
4715
- return join7(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
4971
+ return join8(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
4716
4972
  }
4717
4973
  admissionPath(runId) {
4718
4974
  decodeId(runId);
4719
- return join7(this.directory, "runs", runId, "admission.json");
4975
+ return join8(this.directory, "runs", runId, "admission.json");
4720
4976
  }
4721
4977
  writeAdmission(lease, input) {
4722
4978
  lease.assert();
@@ -4730,7 +4986,7 @@ class HistoryFiles {
4730
4986
  return this.files.read(path2, maximum);
4731
4987
  let size;
4732
4988
  try {
4733
- size = lstatSync5(path2).size;
4989
+ size = lstatSync6(path2).size;
4734
4990
  } catch {
4735
4991
  return this.files.read(path2, 0, budget.walks);
4736
4992
  }
@@ -4742,7 +4998,7 @@ class HistoryFiles {
4742
4998
  readCommand(commandId, budget) {
4743
4999
  decodeId(commandId);
4744
5000
  this.assertReadFormat(budget);
4745
- return this.readBudgeted(join7(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
5001
+ return this.readBudgeted(join8(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
4746
5002
  }
4747
5003
  readAdmission(runId, identity, budget) {
4748
5004
  return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
@@ -4763,7 +5019,7 @@ class HistoryFiles {
4763
5019
  syncParents(file) {
4764
5020
  if (process.platform === "win32")
4765
5021
  return;
4766
- for (let path2 = dirname6(file);; path2 = dirname6(path2)) {
5022
+ for (let path2 = dirname7(file);; path2 = dirname7(path2)) {
4767
5023
  if (!this.files.directory(path2))
4768
5024
  corrupt();
4769
5025
  const fd = openSync5(path2, constants5.O_RDONLY | constants5.O_DIRECTORY);
@@ -4774,13 +5030,13 @@ class HistoryFiles {
4774
5030
  }
4775
5031
  if (path2 === this.coordination.directory)
4776
5032
  break;
4777
- if (path2 === dirname6(path2))
5033
+ if (path2 === dirname7(path2))
4778
5034
  corrupt();
4779
5035
  }
4780
5036
  }
4781
5037
  assertReadFormat(budget) {
4782
5038
  budget.reserve(this.formatSnapshot.bytes.length);
4783
- this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
5039
+ this.files.assert(join8(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
4784
5040
  }
4785
5041
  assertPublished(segment, end, seq) {
4786
5042
  const fence = publicationFences.get(this.directory);
@@ -4818,7 +5074,7 @@ class HistoryFiles {
4818
5074
  const dir = opendirSync2(path2);
4819
5075
  try {
4820
5076
  for (let e = dir.readSync();e; e = dir.readSync()) {
4821
- const p = join7(path2, e.name), st = lstatSync5(p);
5077
+ const p = join8(path2, e.name), st = lstatSync6(p);
4822
5078
  if (st.isDirectory())
4823
5079
  bytes += walk(p, depth + 1);
4824
5080
  else {
@@ -4853,7 +5109,7 @@ class HistoryFiles {
4853
5109
  state.bytes = 0;
4854
5110
  state.identity = null;
4855
5111
  let count = 0;
4856
- const ledger = join7(this.directory, "ledger");
5112
+ const ledger = join8(this.directory, "ledger");
4857
5113
  if (!this.files.directory(ledger))
4858
5114
  corrupt();
4859
5115
  const dir = opendirSync2(ledger);
@@ -4901,9 +5157,9 @@ class HistoryFiles {
4901
5157
  const line = file.bytes.subarray(at, end + 1);
4902
5158
  if (line.length > RECORD_BYTES)
4903
5159
  corrupt();
4904
- let decoded;
5160
+ let decoded2;
4905
5161
  try {
4906
- decoded = decodeJournalText(new TextDecoder("utf-8", { fatal: true }).decode(line));
5162
+ decoded2 = decodeJournalText(new TextDecoder("utf-8", { fatal: true }).decode(line));
4907
5163
  } catch {
4908
5164
  corrupt();
4909
5165
  }
@@ -4911,29 +5167,29 @@ class HistoryFiles {
4911
5167
  state.records++;
4912
5168
  if (state.records > LIMITS.segmentRecords)
4913
5169
  corrupt();
4914
- if (decoded.kind === "record") {
5170
+ if (decoded2.kind === "record") {
4915
5171
  let base = state.committedSeq;
4916
- if (decoded.value.kind === "recovery") {
4917
- if (pending.length || at !== line.length || n !== decoded.value.previous.segment + 1)
5172
+ if (decoded2.value.kind === "recovery") {
5173
+ if (pending.length || at !== line.length || n !== decoded2.value.previous.segment + 1)
4918
5174
  corrupt();
4919
- if (decoded.value.previous.segment !== preceding.segment || decoded.value.previous.bytes !== preceding.bytes || decoded.value.previous.sha256 !== (preceding.identity ?? hash("")) || decoded.value.previous.committedSeq !== preceding.committedSeq || decoded.value.previous.highSeq !== (preceding.highSeq ?? preceding.committedSeq) || decoded.value.previous.committedBytes !== (preceding.committedBytes ?? 0))
5175
+ if (decoded2.value.previous.segment !== preceding.segment || decoded2.value.previous.bytes !== preceding.bytes || decoded2.value.previous.sha256 !== (preceding.identity ?? hash("")) || decoded2.value.previous.committedSeq !== preceding.committedSeq || decoded2.value.previous.highSeq !== (preceding.highSeq ?? preceding.committedSeq) || decoded2.value.previous.committedBytes !== (preceding.committedBytes ?? 0))
4920
5176
  corrupt();
4921
- base = decoded.value.previous.highSeq;
5177
+ base = decoded2.value.previous.highSeq;
4922
5178
  } else if (preceding.tail && state.committedBytes === 0)
4923
5179
  corrupt();
4924
5180
  const next = BigInt(base) + BigInt(pending.length) + 1n;
4925
- if (decoded.seq !== String(next))
5181
+ if (decoded2.seq !== String(next))
4926
5182
  corrupt();
4927
- state.highSeq = decoded.seq;
5183
+ state.highSeq = decoded2.seq;
4928
5184
  pendingBytes += line.length;
4929
5185
  if (pendingBytes > 2 * RECORD_BYTES)
4930
5186
  corrupt();
4931
- pending.push(decoded);
5187
+ pending.push(decoded2);
4932
5188
  checksum.update(line);
4933
5189
  continue;
4934
5190
  }
4935
5191
  const artifacts = batchArtifacts(pending);
4936
- if (!pending.length || !validBatchSequence(pending, decoded.previous) || decoded.previous !== state.committedSeq || decoded.first !== pending[0].seq || decoded.last !== pending.at(-1)?.seq || decoded.count !== pending.length || decoded.sha256 !== checksum.digest("hex") || canonicalJson(decoded.artifacts, RECORD_BYTES) !== canonicalJson(artifacts, RECORD_BYTES))
5192
+ if (!pending.length || !validBatchSequence(pending, decoded2.previous) || decoded2.previous !== state.committedSeq || decoded2.first !== pending[0].seq || decoded2.last !== pending.at(-1)?.seq || decoded2.count !== pending.length || decoded2.sha256 !== checksum.digest("hex") || canonicalJson(decoded2.artifacts, RECORD_BYTES) !== canonicalJson(artifacts, RECORD_BYTES))
4937
5193
  corrupt();
4938
5194
  if (verifyArtifacts)
4939
5195
  for (const artifact of artifacts)
@@ -4946,12 +5202,12 @@ class HistoryFiles {
4946
5202
  if (e.kind === "owner" && e.value.kind === "fact" && e.value.fact.kind === "parent-admitted")
4947
5203
  this.readAdmission(e.value.fact.runId, e.value.fact.identity, budget);
4948
5204
  }
4949
- state.committedSeq = decoded.last;
5205
+ state.committedSeq = decoded2.last;
4950
5206
  state.committedBytes = at;
4951
- state.navigationRoot = decoded.navigationRoot;
5207
+ state.navigationRoot = decoded2.navigationRoot;
4952
5208
  yield {
4953
5209
  rows: pending,
4954
- marker: decoded,
5210
+ marker: decoded2,
4955
5211
  segment: n,
4956
5212
  identity: file.identity,
4957
5213
  segmentBytes: file.bytes.length
@@ -5018,7 +5274,7 @@ class HistoryFiles {
5018
5274
  let committed;
5019
5275
  const fd = await open(path2, constants5.O_WRONLY | constants5.O_APPEND | (constants5.O_NOFOLLOW ?? 0));
5020
5276
  const assertOriginal = (bytes) => {
5021
- const original = fstatSync4(fd.fd), selected2 = lstatSync5(path2);
5277
+ const original = fstatSync4(fd.fd), selected2 = lstatSync6(path2);
5022
5278
  if (original.dev !== selected2.dev || original.ino !== selected2.ino || original.nlink !== 1)
5023
5279
  corrupt();
5024
5280
  const snapshot = this.files.read(path2, LIMITS.segmentBytes);
@@ -5135,7 +5391,7 @@ class HistoryFiles {
5135
5391
  try {
5136
5392
  lease.assert();
5137
5393
  this.files.assert(path2, before, LIMITS.segmentBytes);
5138
- const opened = await fd.stat(), selected2 = lstatSync5(path2);
5394
+ const opened = await fd.stat(), selected2 = lstatSync6(path2);
5139
5395
  if (opened.ino !== selected2.ino || opened.dev !== selected2.dev || opened.nlink !== 1)
5140
5396
  corrupt();
5141
5397
  beforeEffect?.();
@@ -5170,7 +5426,7 @@ class HistoryFiles {
5170
5426
  checkpoint(lease, state) {
5171
5427
  lease.assert();
5172
5428
  this.assertWritable();
5173
- const path2 = join7(this.directory, "checkpoint.json"), old = this.files.read(path2);
5429
+ const path2 = join8(this.directory, "checkpoint.json"), old = this.files.read(path2);
5174
5430
  const body = encoded({
5175
5431
  schema: "cadence-history-checkpoint/2",
5176
5432
  storeId: this.format.storeId,
@@ -5583,7 +5839,7 @@ function digestHistory(files, exclude) {
5583
5839
  const walk = (path2, depth) => {
5584
5840
  if (depth > 8 || !files.files.directory(path2))
5585
5841
  throw new ContractError("corrupt-selected");
5586
- const directory2 = lstatSync6(path2);
5842
+ const directory2 = lstatSync7(path2);
5587
5843
  digest.update(`incarnation:${directory2.dev}:${directory2.ino}
5588
5844
  `);
5589
5845
  const dir = opendirSync3(path2), names2 = [];
@@ -5597,11 +5853,11 @@ function digestHistory(files, exclude) {
5597
5853
  dir.closeSync();
5598
5854
  }
5599
5855
  for (const name of names2.sort()) {
5600
- const selected2 = join8(path2, name);
5856
+ const selected2 = join9(path2, name);
5601
5857
  if (selected2 === exclude)
5602
5858
  continue;
5603
5859
  const rel = relative4(files.directory, selected2);
5604
- if (lstatSync6(selected2).isDirectory()) {
5860
+ if (lstatSync7(selected2).isDirectory()) {
5605
5861
  digest.update(`directory:${Buffer.byteLength(rel)}:${rel}
5606
5862
  `);
5607
5863
  walk(selected2, depth + 1);
@@ -5908,7 +6164,7 @@ async function localRecovery(c, operation) {
5908
6164
 
5909
6165
  // src/service/runtime.ts
5910
6166
  import { homedir as homedir4 } from "node:os";
5911
- import { dirname as dirname7, isAbsolute as isAbsolute6, join as join9, resolve as resolve4 } from "node:path";
6167
+ import { dirname as dirname8, isAbsolute as isAbsolute6, join as join10, resolve as resolve4 } from "node:path";
5912
6168
 
5913
6169
  // src/service/config.ts
5914
6170
  import { isIP } from "node:net";
@@ -5978,10 +6234,11 @@ function decodeHostConfig(value) {
5978
6234
  const tokenSource = oneOf(o.tokenSource, [
5979
6235
  "generated",
5980
6236
  "environment",
5981
- "file"
6237
+ "file",
6238
+ "config"
5982
6239
  ]);
5983
6240
  const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
5984
- if (o.tokenSource === "file" !== (tokenFile !== null))
6241
+ if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
5985
6242
  throw new ContractError("invalid");
5986
6243
  return {
5987
6244
  schema: literal(o.schema, "cadence-service-host/1"),
@@ -6042,13 +6299,13 @@ function selectStart(options, ambient) {
6042
6299
  throw new ContractError("invalid");
6043
6300
  let fromFile;
6044
6301
  if (options.config) {
6045
- const path2 = resolve4(options.config), f = new PrivateFiles(dirname7(path2));
6302
+ const path2 = resolve4(options.config), f = new PrivateFiles(dirname8(path2));
6046
6303
  const bytes = f.read(path2);
6047
6304
  if (!bytes)
6048
6305
  throw new ContractError("corrupt-selected");
6049
6306
  fromFile = decodeHostConfig(json(bytes));
6050
6307
  }
6051
- const selected2 = old ?? fromFile ?? decodeHostConfig({
6308
+ const base = old ?? fromFile ?? decodeHostConfig({
6052
6309
  schema: "cadence-service-host/1",
6053
6310
  cwd: workspace.cwd,
6054
6311
  http: {
@@ -6057,10 +6314,23 @@ function selectStart(options, ambient) {
6057
6314
  port: options.port ?? 17423,
6058
6315
  proxy: null
6059
6316
  },
6060
- agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join9(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
6317
+ agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join10(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
6061
6318
  tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
6062
6319
  tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
6063
6320
  });
6321
+ const server = options.serverConfig !== undefined ? resolve4(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
6322
+ let selected2 = base;
6323
+ if (server) {
6324
+ if (options.tokenFile !== undefined)
6325
+ throw new ContractError("conflict");
6326
+ const { port } = readServerConfig(server).config;
6327
+ selected2 = decodeHostConfig({
6328
+ ...base,
6329
+ http: { ...base.http, port },
6330
+ tokenSource: "config",
6331
+ tokenFile: server
6332
+ });
6333
+ }
6064
6334
  if (fromFile && encoded(fromFile) !== encoded(selected2) || options.port !== undefined && options.port !== selected2.http.port || options.host !== undefined && options.host !== selected2.http.host || options.agentDir !== undefined && resolve4(options.agentDir) !== selected2.agentDir || options.tokenFile !== undefined && resolve4(options.tokenFile) !== selected2.tokenFile || env && selected2.tokenSource !== "environment" || selected2.tokenSource === "environment" && !env || resolveWorkspace(selected2.cwd).root !== workspace.root)
6065
6335
  throw new ContractError("conflict");
6066
6336
  return {
@@ -6211,6 +6481,11 @@ async function stopService(c) {
6211
6481
  function failedStatus(result) {
6212
6482
  return !!(result && typeof result === "object" && ("status" in result) && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)));
6213
6483
  }
6484
+ function serverConfigHint(error) {
6485
+ return `Cadence changed nothing: ${error.path} ${error.reason}.
6486
+ Fix it and run the command again; the file holds the WebUI port and token as ${PORT_KEY}=<1-65535> and ${TOKEN_KEY}=<at least 32 characters>, one line each.
6487
+ `;
6488
+ }
6214
6489
  function startupGuidance(result, input) {
6215
6490
  let report;
6216
6491
  try {
@@ -6229,10 +6504,10 @@ function startupGuidance(result, input) {
6229
6504
  return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
6230
6505
  `;
6231
6506
  case "pi-auth":
6232
- return `${join10(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
6507
+ return `${join11(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
6233
6508
  `;
6234
6509
  case "pi-models":
6235
- 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.
6510
+ return `${join11(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.
6236
6511
  `;
6237
6512
  case "pi-runtime":
6238
6513
  return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
@@ -6245,15 +6520,26 @@ ${inspect}` : "";
6245
6520
  async function startService(options, foreground, hostUrl) {
6246
6521
  const network = networkSnapshot(process.env);
6247
6522
  const env = hostEnvironment(process.env);
6248
- const input = decodeHostInput(selectStart(options, process.env));
6523
+ let input;
6524
+ try {
6525
+ input = decodeHostInput(selectStart(options, process.env));
6526
+ } catch (error) {
6527
+ if (error instanceof ServerConfigRefusal)
6528
+ process.stderr.write(serverConfigHint(error));
6529
+ throw error;
6530
+ }
6249
6531
  const serialized = `${JSON.stringify(input)}
6250
6532
  `;
6251
6533
  if (Buffer.byteLength(serialized) > STARTUP_INPUT_BYTES)
6252
6534
  throw new ContractError("oversize");
6253
6535
  const c = new ServiceCoordination(input.workspace, input.stateHome);
6254
6536
  const existing = await serviceStatus(c);
6255
- if (existing && typeof existing === "object" && "status" in existing && existing.status === "alive")
6537
+ if (existing && typeof existing === "object" && "status" in existing && existing.status === "alive") {
6538
+ const running = readHostConfig(c);
6539
+ if (running && encoded(running) !== encoded(input.config))
6540
+ throw new ContractError("conflict");
6256
6541
  return existing;
6542
+ }
6257
6543
  if (!sameNetwork(network, networkSnapshot(process.env)))
6258
6544
  throw new ContractError("stale");
6259
6545
  const child = spawn(process.execPath, [
@@ -6267,9 +6553,9 @@ async function startService(options, foreground, hostUrl) {
6267
6553
  stdio: ["pipe", "pipe", "ignore"]
6268
6554
  });
6269
6555
  let completed = false;
6270
- const closure = new Promise((resolve6) => child.once("close", (code, signal) => {
6556
+ const closure = new Promise((resolve6) => child.once("close", (code2, signal) => {
6271
6557
  completed = true;
6272
- if (foreground && (code !== 0 || signal !== null))
6558
+ if (foreground && (code2 !== 0 || signal !== null))
6273
6559
  process.exitCode = 1;
6274
6560
  resolve6();
6275
6561
  }));
@@ -6358,6 +6644,14 @@ async function observeStarted(workspace) {
6358
6644
  }
6359
6645
  return { service };
6360
6646
  }
6647
+ async function privateLink(workspace) {
6648
+ try {
6649
+ const value = closed(await nativeCall(new ServiceCoordination(workspace), "token-link", true), ["link"]);
6650
+ return text(value.link, 4096, true);
6651
+ } catch {
6652
+ return;
6653
+ }
6654
+ }
6361
6655
  var ownerControl = {
6362
6656
  async ownerStatus(workspace) {
6363
6657
  try {
@@ -6406,11 +6700,11 @@ async function serverAction(s, action, requested, linger) {
6406
6700
  return { status: "unsupported" };
6407
6701
  }
6408
6702
  }
6409
- function serverGuidance(s, action, result) {
6410
- const cli = piUserInstall(s) ? `node ${JSON.stringify(join10(s.root, "src", "service-cli.mjs"))}` : "cadence";
6703
+ function serverGuidance(s, action, result, link) {
6704
+ const cli = piUserInstall(s) ? `node ${JSON.stringify(join11(s.root, "src", "service-cli.mjs"))}` : "cadence";
6411
6705
  const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
6412
6706
  ` : "";
6413
- return note + serverStatusGuidance(s, action, result, cli);
6707
+ return note + serverStatusGuidance(s, action, result, cli, link);
6414
6708
  }
6415
6709
  function lingerGuidance(linger) {
6416
6710
  switch (linger) {
@@ -6437,10 +6731,11 @@ function managerDetail(s) {
6437
6731
  return `run its launcher to see why: wscript ${JSON.stringify(launcherPath(s))}`;
6438
6732
  }
6439
6733
  }
6440
- function serverStatusGuidance(s, action, result, cli) {
6734
+ function serverStatusGuidance(s, action, result, cli, link) {
6441
6735
  const workspace = JSON.stringify(result.workspace ?? "");
6442
6736
  const manage = `Manage: ${cli} server status|restart|stop|uninstall`;
6443
6737
  const when = s.platform === "linux" ? "at boot or login" : "at login";
6738
+ const config = `Port and token: ${PORT_KEY} and ${TOKEN_KEY} in ${serverConfigPath(s.home)} (edit them, then: ${cli} server restart)`;
6444
6739
  switch (result.status) {
6445
6740
  case "started": {
6446
6741
  const linger = lingerGuidance(result.linger);
@@ -6449,14 +6744,22 @@ function serverStatusGuidance(s, action, result, cli) {
6449
6744
  Inspect: ${cli} diagnose --workspace ${workspace}
6450
6745
  ${manage}
6451
6746
  ${linger}`;
6452
- return `Cadence WebUI: ${result.endpoint} (workspace ${result.workspace}; starts ${when})
6747
+ if (!link)
6748
+ return `Cadence WebUI: ${result.endpoint} (workspace ${result.workspace}; starts ${when})
6453
6749
  Enter with your private link: ${cli} token-link --workspace ${workspace} --reveal
6750
+ ${config}
6751
+ ${manage}
6752
+ ${linger}`;
6753
+ return `Cadence WebUI: ${link}
6754
+ Open this private link (workspace ${result.workspace}; starts ${when}); its token admits anyone who has it.
6755
+ ${config}
6454
6756
  ${manage}
6455
6757
  ${linger}`;
6456
6758
  }
6457
6759
  case "waiting-for-pi-login":
6458
- return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join10(agentDirectory(s), "auth.json")}.
6760
+ return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join11(agentDirectory(s), "auth.json")}.
6459
6761
  Log in to Pi, then: ${cli} server restart
6762
+ ${config}
6460
6763
  ${lingerGuidance(result.linger)}`;
6461
6764
  case "stopped":
6462
6765
  return action === "stop" ? `Cadence stopped; it starts again ${when}, or now with: ${cli} server restart
@@ -6531,9 +6834,25 @@ async function serverMain(argv, hostUrl) {
6531
6834
  }
6532
6835
  if (argv.length)
6533
6836
  throw new ContractError("invalid");
6534
- const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))), ownerControl);
6837
+ const s = hostSystem(dirname9(dirname9(fileURLToPath(hostUrl))), ownerControl);
6535
6838
  if (action === "install")
6536
6839
  process.stderr.write(researchTemplate(s.home));
6840
+ if (action === "install" || action === "restart") {
6841
+ try {
6842
+ const { added, exposed } = ensureServerConfig(s.home);
6843
+ const path2 = serverConfigPath(s.home);
6844
+ if (added.length)
6845
+ process.stderr.write(`Cadence added ${added.map((key) => key === PORT_KEY ? `${key}=${DEFAULT_PORT}` : `a random private ${key}`).join(" and ")} to ${path2}
6846
+ `);
6847
+ if (exposed)
6848
+ process.stderr.write(`Note: other users can read ${path2}; chmod 600 ${JSON.stringify(path2)} keeps ${TOKEN_KEY} private.
6849
+ `);
6850
+ } catch (error) {
6851
+ if (error instanceof ServerConfigRefusal)
6852
+ process.stderr.write(serverConfigHint(error));
6853
+ throw error;
6854
+ }
6855
+ }
6537
6856
  let result;
6538
6857
  try {
6539
6858
  result = await serverAction(s, action, requested, linger);
@@ -6543,9 +6862,13 @@ async function serverMain(argv, hostUrl) {
6543
6862
  `);
6544
6863
  throw error;
6545
6864
  }
6546
- if (result.status === "started" && result.workspace)
6865
+ let link;
6866
+ if (result.status === "started" && result.workspace) {
6547
6867
  Object.assign(result, await observeStarted(result.workspace));
6548
- process.stderr.write(serverGuidance(s, action, result));
6868
+ if (result.service === "alive")
6869
+ link = await privateLink(result.workspace);
6870
+ }
6871
+ process.stderr.write(serverGuidance(s, action, result, link));
6549
6872
  process.stdout.write(`${JSON.stringify(result)}
6550
6873
  `);
6551
6874
  if ([
@@ -6567,7 +6890,7 @@ async function managementMain(argv, hostUrl) {
6567
6890
  if (command === "--help" && !argv.length) {
6568
6891
  process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
6569
6892
  cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
6570
- 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.
6893
+ 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, --server-config (the .env whose CADENCE_PORT and CADENCE_TOKEN the background service uses).
6571
6894
  Return/quit detaches clients; only explicit Cancel/Stop requests stop. Unconfirmed/quarantined is not stopped.
6572
6895
  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.
6573
6896
  `);
@@ -6627,6 +6950,8 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
6627
6950
  options.agentDir = value;
6628
6951
  else if (flag === "--token-file")
6629
6952
  options.tokenFile = value;
6953
+ else if (flag === "--server-config")
6954
+ options.serverConfig = value;
6630
6955
  else if (flag === "--port")
6631
6956
  options.port = portValue(value);
6632
6957
  else if (flag === "--host" && (value === "127.0.0.1" || value === "::1"))