@abelxiaoxing/cadence 2.0.9 → 2.0.11

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.
@@ -3,9 +3,9 @@
3
3
 
4
4
  // src/service/cli.ts
5
5
  import { spawn } from "node:child_process";
6
- import { existsSync, mkdirSync as mkdirSync5 } from "node:fs";
6
+ import { existsSync as existsSync3, mkdirSync as mkdirSync7 } from "node:fs";
7
7
  import { request } from "node:http";
8
- import { dirname as dirname9, join as join11, resolve as resolve5 } from "node:path";
8
+ import { dirname as dirname9, join as join12, resolve as resolve5 } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
 
11
11
  // src/install-config.mjs
@@ -2225,15 +2225,19 @@ function readBounded(path2, max, packaged = false) {
2225
2225
  }
2226
2226
  }
2227
2227
  function gitEnvironment() {
2228
+ const root = process.getuid?.() === 0;
2228
2229
  return {
2229
- ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_"))),
2230
+ ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_") && !(root && name === "SUDO_UID"))),
2230
2231
  GIT_OPTIONAL_LOCKS: "0",
2231
2232
  LC_ALL: "C"
2232
2233
  };
2233
2234
  }
2235
+ function gitArguments(args) {
2236
+ return process.platform !== "win32" && process.getuid?.() === 0 ? ["-c", "safe.directory=", "-c", "core.fsmonitor=false", ...args] : args;
2237
+ }
2234
2238
  function resolveWorkspace(cwd) {
2235
2239
  const actual = realpathSync(cwd);
2236
- const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
2240
+ const result = spawnSync("git", gitArguments(["rev-parse", "--show-toplevel"]), {
2237
2241
  cwd: actual,
2238
2242
  env: gitEnvironment(),
2239
2243
  encoding: "utf8",
@@ -2296,8 +2300,13 @@ function stateHomeInside(root, stateHome = defaultStateHome()) {
2296
2300
  const canonical = realpathSync(root);
2297
2301
  return contains(canonical, privateRootFor(canonical, stateHome));
2298
2302
  }
2303
+ function privateWorkspace(requested, stateHome = defaultStateHome()) {
2304
+ return contains(join(canonicalAmbientRoot(stateHome), "cadence-assistant-v1"), canonicalAmbientRoot(requested));
2305
+ }
2299
2306
  var LANDING_WORKSPACE = "cadence";
2300
2307
  function effectiveWorkspace(requested, stateHome, home = homedir2()) {
2308
+ if (privateWorkspace(requested, stateHome))
2309
+ throw new Error("Private state cannot be a workspace");
2301
2310
  if (!stateHomeInside(resolveWorkspace(requested).root, stateHome))
2302
2311
  return { workspace: requested, redirected: false };
2303
2312
  return { workspace: join(home, LANDING_WORKSPACE), redirected: true };
@@ -2309,6 +2318,8 @@ class TaskStore {
2309
2318
  privateRoot;
2310
2319
  constructor(root, stateHome = defaultStateHome()) {
2311
2320
  this.root = realpathSync(root);
2321
+ if (privateWorkspace(this.root, stateHome))
2322
+ throw new Error("Private state cannot be a workspace");
2312
2323
  this.tasks = join(this.root, ".cadence", "assistant", "tasks");
2313
2324
  this.privateRoot = privateRootFor(this.root, stateHome);
2314
2325
  if (contains(this.root, this.privateRoot))
@@ -2512,7 +2523,7 @@ var TASK_CONFIG_SCHEMA = "cadence-assistant-checks/1";
2512
2523
  import { spawnSync as spawnSync2 } from "node:child_process";
2513
2524
  import {
2514
2525
  lstatSync as lstatSync4,
2515
- mkdirSync as mkdirSync4,
2526
+ mkdirSync as mkdirSync5,
2516
2527
  readFileSync,
2517
2528
  realpathSync as realpathSync3,
2518
2529
  renameSync as renameSync3,
@@ -2533,7 +2544,13 @@ import {
2533
2544
 
2534
2545
  // src/service/server-config.ts
2535
2546
  import { randomBytes } from "node:crypto";
2536
- import { lstatSync as lstatSync3, realpathSync as realpathSync2, statSync } from "node:fs";
2547
+ import {
2548
+ existsSync,
2549
+ lstatSync as lstatSync3,
2550
+ mkdirSync as mkdirSync4,
2551
+ realpathSync as realpathSync2,
2552
+ statSync
2553
+ } from "node:fs";
2537
2554
  import { dirname as dirname3, join as join3 } from "node:path";
2538
2555
 
2539
2556
  // skills/_shared/load-config.mjs
@@ -2830,8 +2847,10 @@ function accessToken(value) {
2830
2847
  throw new ContractError("invalid");
2831
2848
  return result;
2832
2849
  }
2833
- function serverConfigPath(home) {
2834
- return join3(home, ".pi", "agent", "cadence", ".env");
2850
+ function serverConfigPath(home, name) {
2851
+ if (name !== undefined && !/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(name))
2852
+ throw new ContractError("invalid");
2853
+ return name === undefined ? join3(home, ".pi", "agent", "cadence", ".env") : join3(home, ".pi", "agent", "cadence", "servers", `${name}.env`);
2835
2854
  }
2836
2855
 
2837
2856
  class ServerConfigRefusal extends ContractError {
@@ -2974,19 +2993,33 @@ function readServerConfig(path2) {
2974
2993
  return { ...file, config: decode(file.source) };
2975
2994
  });
2976
2995
  }
2977
- function ensureServerConfig(home) {
2978
- const path2 = serverConfigPath(home);
2996
+ function ensureServerConfig(home, options = {}) {
2997
+ const path2 = serverConfigPath(home, options.name);
2998
+ if (options.port !== undefined && (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535))
2999
+ throw new ContractError("invalid");
2979
3000
  const added = refusing(path2, () => {
2980
3001
  try {
2981
- ensureUserConfig(home);
3002
+ if (options.name === undefined)
3003
+ ensureUserConfig(home);
3004
+ else if (!existsSync(path2)) {
3005
+ if (options.port === undefined)
3006
+ unusable("needs --port for a new named service");
3007
+ mkdirSync4(dirname3(path2), { recursive: true, mode: 448 });
3008
+ const directory = realpathSync2(dirname3(path2));
3009
+ new PrivateFiles(directory, false).create(join3(directory, `${options.name}.env`), "");
3010
+ }
2982
3011
  } catch (error) {
3012
+ if (error instanceof Unusable)
3013
+ throw error;
2983
3014
  unusable(`could not be created (${code(error)})`);
2984
3015
  }
2985
3016
  const file2 = load(path2);
2986
3017
  const found = settings(file2.source);
2987
3018
  const lines = [];
3019
+ if (found.port && options.port !== undefined && found.port.value !== String(options.port))
3020
+ unusable(`already selects another ${PORT_KEY}; edit that line explicitly, then restart`);
2988
3021
  if (!found.port)
2989
- lines.push(`${PORT_KEY}=${DEFAULT_PORT}`);
3022
+ lines.push(`${PORT_KEY}=${options.port ?? DEFAULT_PORT}`);
2990
3023
  if (!found.token)
2991
3024
  lines.push(`${TOKEN_KEY}=${randomBytes(32).toString("base64url")}`);
2992
3025
  if (lines.length) {
@@ -3017,7 +3050,9 @@ function wscript(env) {
3017
3050
  const root = env.SystemRoot;
3018
3051
  return win32.join(root && win32.isAbsolute(root) ? root : "C:\\Windows", "System32", "wscript.exe");
3019
3052
  }
3020
- function hostSystem(root, owner) {
3053
+ function hostSystem(root, owner, selection = {}) {
3054
+ const scope = selection.scope ?? (process.platform === "linux" && process.getuid?.() === 0 ? "system" : "user");
3055
+ const unit = serviceUnit(selection.name);
3021
3056
  const run = (command, args) => {
3022
3057
  const r = spawnSync2(command, args, {
3023
3058
  encoding: "utf8",
@@ -3032,9 +3067,10 @@ function hostSystem(root, owner) {
3032
3067
  home: homedir3(),
3033
3068
  node: process.execPath,
3034
3069
  root,
3035
- unit: UNIT,
3070
+ unit,
3071
+ scope,
3036
3072
  uid: process.getuid?.(),
3037
- systemctl: (args) => run("systemctl", ["--user", ...args]),
3073
+ systemctl: (args) => run("systemctl", [scope === "system" ? "--system" : "--user", ...args]),
3038
3074
  linger: () => {
3039
3075
  const r = run("loginctl", [
3040
3076
  "show-user",
@@ -3074,6 +3110,24 @@ function hostSystem(root, owner) {
3074
3110
  ...owner
3075
3111
  };
3076
3112
  }
3113
+ function serviceUnit(name) {
3114
+ if (name === undefined)
3115
+ return UNIT;
3116
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/.test(name))
3117
+ throw new ContractError("invalid");
3118
+ return `cadence-${name}.service`;
3119
+ }
3120
+ function serviceName(s) {
3121
+ if (s.unit === UNIT)
3122
+ return;
3123
+ const name = /^cadence-([A-Za-z0-9][A-Za-z0-9_-]{0,47})\.service$/.exec(s.unit)?.[1];
3124
+ if (!name)
3125
+ throw new ContractError("invalid");
3126
+ return name;
3127
+ }
3128
+ function serviceConfigPath(s) {
3129
+ return serverConfigPath(s.home, serviceName(s));
3130
+ }
3077
3131
  function expandHome(value, home) {
3078
3132
  if (value === "~")
3079
3133
  return home;
@@ -3113,6 +3167,9 @@ function sdkPackages(root) {
3113
3167
  return found;
3114
3168
  }
3115
3169
  function unitPath(s) {
3170
+ serviceName(s);
3171
+ if (s.scope === "system")
3172
+ return join4(s.unitDirectory ?? "/etc/systemd/system", s.unit);
3116
3173
  const data = s.env.XDG_DATA_HOME;
3117
3174
  return join4(data && isAbsolute3(data) ? data : join4(s.home, ".local", "share"), "systemd", "user", s.unit);
3118
3175
  }
@@ -3122,6 +3179,8 @@ function servicePath(value) {
3122
3179
  }
3123
3180
  function serviceEnvironment(s) {
3124
3181
  const out = [["PATH", servicePath(s.env.PATH)]];
3182
+ if (s.scope === "system")
3183
+ out.push(["HOME", s.home], ["USER", "root"], ["LOGNAME", "root"]);
3125
3184
  for (const key of ["LANG", "LC_ALL"]) {
3126
3185
  const value = s.env[key];
3127
3186
  if (value)
@@ -3146,7 +3205,7 @@ function buildUnit(u) {
3146
3205
  const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
3147
3206
  return [
3148
3207
  `${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`.",
3208
+ u.system ? "# System service (root); manage explicitly with cadence server <action> --system. No user manager or linger." : "# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
3150
3209
  "[Unit]",
3151
3210
  "Description=Cadence shared task service and WebUI",
3152
3211
  "Documentation=https://github.com/abelxiaoxing/cadence/blob/main/docs/service-management.md",
@@ -3158,6 +3217,7 @@ function buildUnit(u) {
3158
3217
  "",
3159
3218
  "[Service]",
3160
3219
  "Type=simple",
3220
+ ...u.system ? ["User=root"] : [],
3161
3221
  `WorkingDirectory=${specifiers(u.workspace)}`,
3162
3222
  ...u.environment.map(([k, v]) => `Environment=${quoted(`${k}=${v}`)}`),
3163
3223
  `ExecStart=${command(["start", "--foreground", "--workspace", u.workspace, "--server-config", u.serverConfig])}`,
@@ -3168,7 +3228,7 @@ function buildUnit(u) {
3168
3228
  "RestartSec=10",
3169
3229
  "",
3170
3230
  "[Install]",
3171
- "WantedBy=default.target",
3231
+ u.system ? "WantedBy=multi-user.target" : "WantedBy=default.target",
3172
3232
  ""
3173
3233
  ].join(`
3174
3234
  `);
@@ -3200,7 +3260,7 @@ function serviceStateHome(s) {
3200
3260
  const state = s.platform === "win32" ? s.env.LOCALAPPDATA : s.env.XDG_STATE_HOME;
3201
3261
  return state && isAbsolute3(state) ? state : join4(s.home, ...s.platform === "win32" ? ["AppData", "Local"] : [".local", "state"]);
3202
3262
  }
3203
- function selectWorkspace(s, requested, kept) {
3263
+ function selectWorkspace(s, requested, kept, create = true) {
3204
3264
  let chosen;
3205
3265
  if (requested) {
3206
3266
  chosen = expandHome(requested, s.home);
@@ -3210,12 +3270,12 @@ function selectWorkspace(s, requested, kept) {
3210
3270
  } else if (kept && directory(kept))
3211
3271
  chosen = kept;
3212
3272
  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 });
3273
+ if (create && landing.workspace !== chosen)
3274
+ mkdirSync5(landing.workspace, { recursive: true });
3215
3275
  return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
3216
3276
  }
3217
3277
  function write(path2, content) {
3218
- mkdirSync4(dirname4(path2), { recursive: true });
3278
+ mkdirSync5(dirname4(path2), { recursive: true });
3219
3279
  const temporary = `${path2}.${process.pid}.tmp`;
3220
3280
  try {
3221
3281
  writeFileSync3(temporary, content, { mode: 420, flag: "wx" });
@@ -3255,7 +3315,7 @@ function bootStart(s, attempt) {
3255
3315
  };
3256
3316
  }
3257
3317
  function configureAutostart(s, mode, requested, linger = false) {
3258
- if (s.platform !== "linux")
3318
+ if (s.platform !== "linux" || s.scope === "system")
3259
3319
  return { status: "unsupported" };
3260
3320
  if (s.systemctl(["show-environment"]).status !== 0)
3261
3321
  return { status: "unavailable" };
@@ -3274,15 +3334,19 @@ function configureAutostart(s, mode, requested, linger = false) {
3274
3334
  node: s.node,
3275
3335
  cli: join4(s.root, "src", "service-cli.mjs"),
3276
3336
  workspace,
3277
- serverConfig: serverConfigPath(s.home),
3337
+ serverConfig: serviceConfigPath(s),
3278
3338
  agentDir,
3279
3339
  sdk,
3280
3340
  environment: serviceEnvironment(s)
3281
3341
  });
3282
3342
  const enabled = s.systemctl(["is-enabled", s.unit]).stdout.trim();
3283
3343
  const active = s.systemctl(["is-active", "--quiet", s.unit]).status === 0;
3284
- if (active)
3285
- s.systemctl(["stop", s.unit]);
3344
+ if (active && s.systemctl(["stop", s.unit]).status !== 0)
3345
+ return {
3346
+ status: "unconfirmed",
3347
+ unit: path2,
3348
+ workspace: unitWorkspace(previous)
3349
+ };
3286
3350
  write(path2, content);
3287
3351
  s.systemctl(["daemon-reload"]);
3288
3352
  const result = {
@@ -3302,7 +3366,7 @@ function configureAutostart(s, mode, requested, linger = false) {
3302
3366
  return { status: started ? "started" : "failed", ...result, ...boot() };
3303
3367
  }
3304
3368
  function stopAutostart(s) {
3305
- if (s.platform !== "linux")
3369
+ if (s.platform !== "linux" || s.scope === "system")
3306
3370
  return { status: "unsupported" };
3307
3371
  const path2 = unitPath(s);
3308
3372
  const previous = readGenerated(path2, MARKER);
@@ -3319,7 +3383,7 @@ function stopAutostart(s) {
3319
3383
  };
3320
3384
  }
3321
3385
  function removeAutostart(s) {
3322
- if (s.platform !== "linux")
3386
+ if (s.platform !== "linux" || s.scope === "system")
3323
3387
  return { status: "unsupported" };
3324
3388
  const path2 = unitPath(s);
3325
3389
  const previous = readGenerated(path2, MARKER);
@@ -3337,7 +3401,7 @@ function removeAutostart(s) {
3337
3401
  return { status: "removed", unit: path2 };
3338
3402
  }
3339
3403
  function autostartStatus(s) {
3340
- if (s.platform !== "linux")
3404
+ if (s.platform !== "linux" || s.scope === "system")
3341
3405
  return { status: "unsupported" };
3342
3406
  const path2 = unitPath(s);
3343
3407
  const previous = readGenerated(path2, MARKER);
@@ -6380,6 +6444,212 @@ function decodeStartupReport(value) {
6380
6444
  };
6381
6445
  }
6382
6446
 
6447
+ // src/service/system-service.ts
6448
+ import { existsSync as existsSync2, mkdirSync as mkdirSync6, realpathSync as realpathSync5, rmSync as rmSync4 } from "node:fs";
6449
+ import { isAbsolute as isAbsolute7, join as join11 } from "node:path";
6450
+ var MARKER4 = "# Generated by @abelxiaoxing/cadence";
6451
+ function boundary(s) {
6452
+ if (s.platform !== "linux" || s.scope !== "system")
6453
+ return { status: "unsupported" };
6454
+ if (s.uid !== 0)
6455
+ return { status: "root-required", scope: "system" };
6456
+ }
6457
+ function legacyPath(s) {
6458
+ return unitPath({ ...s, scope: "user", unitDirectory: undefined });
6459
+ }
6460
+ function observed(s) {
6461
+ const path2 = unitPath(s);
6462
+ let previous = readGenerated(path2, MARKER4);
6463
+ if (typeof previous === "string" && (!isAbsolute7(unitWorkspace(previous) ?? "") || !previous.includes(`
6464
+ User=root
6465
+ `)))
6466
+ previous = null;
6467
+ const result = { unit: path2, scope: "system" };
6468
+ return { path: path2, previous, result };
6469
+ }
6470
+ function masked(s) {
6471
+ return s.systemctl(["is-enabled", s.unit]).stdout.trim().startsWith("masked");
6472
+ }
6473
+ function sameStateEnvironment(s, previous) {
6474
+ try {
6475
+ const values = new Map;
6476
+ for (const line of previous.split(`
6477
+ `)) {
6478
+ if (!line.startsWith("Environment="))
6479
+ continue;
6480
+ const value = JSON.parse(line.slice("Environment=".length));
6481
+ if (typeof value !== "string")
6482
+ return false;
6483
+ const at = value.indexOf("=");
6484
+ const key = value.slice(0, at);
6485
+ if (key !== "HOME" && key !== "XDG_STATE_HOME")
6486
+ continue;
6487
+ if (values.has(key))
6488
+ return false;
6489
+ values.set(key, value.slice(at + 1).replaceAll("%%", "%"));
6490
+ }
6491
+ const state = s.env.XDG_STATE_HOME;
6492
+ return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state && isAbsolute7(state) ? state : undefined);
6493
+ } catch {
6494
+ return false;
6495
+ }
6496
+ }
6497
+ async function ownerBoundary(s, workspace, managed) {
6498
+ const status = await s.ownerStatus(workspace);
6499
+ if (managed) {
6500
+ if (status === "absent")
6501
+ return { status: "unavailable", workspace };
6502
+ } else if (status !== "absent" && status !== "stopped") {
6503
+ return {
6504
+ status: status === "alive" ? "project-running" : "unavailable",
6505
+ workspace
6506
+ };
6507
+ }
6508
+ }
6509
+ async function configureSystemService(s, mode, requested) {
6510
+ const refused2 = boundary(s);
6511
+ if (refused2)
6512
+ return refused2;
6513
+ if (existsSync2(legacyPath(s)))
6514
+ return { status: "scope-conflict", unit: legacyPath(s), scope: "system" };
6515
+ if (s.systemctl(["show-environment"]).status !== 0)
6516
+ return { status: "unavailable", scope: "system" };
6517
+ const { path: path2, previous, result } = observed(s);
6518
+ if (masked(s))
6519
+ return { status: "masked", ...result };
6520
+ if (previous === null)
6521
+ return { status: "foreign", ...result };
6522
+ if (mode === "restart" && previous === undefined)
6523
+ return { status: "absent", scope: "system" };
6524
+ const sdk = sdkPackages(s.root);
6525
+ if (!sdk)
6526
+ return { status: "missing-pi-sdk", scope: "system" };
6527
+ if (previous && !existsSync2(unitWorkspace(previous)))
6528
+ return { status: "workspace-changed", ...result };
6529
+ const selected2 = selectWorkspace(s, requested, unitWorkspace(previous), false);
6530
+ const selectedReal = existsSync2(selected2.workspace) ? realpathSync5(selected2.workspace) : undefined;
6531
+ const agentDir = agentDirectory(s);
6532
+ const content = buildUnit({
6533
+ system: true,
6534
+ node: s.node,
6535
+ cli: join11(s.root, "src", "service-cli.mjs"),
6536
+ workspace: selected2.workspace,
6537
+ serverConfig: serviceConfigPath(s),
6538
+ agentDir,
6539
+ sdk,
6540
+ environment: serviceEnvironment(s)
6541
+ });
6542
+ if (previous && !sameStateEnvironment(s, previous))
6543
+ return { status: "environment-changed", ...result };
6544
+ const active = s.systemctl(["is-active", "--quiet", s.unit]);
6545
+ if (active.status !== 0 && active.status !== 3 && !(active.status === 4 && previous === undefined))
6546
+ return { status: "unavailable", ...result };
6547
+ if (active.status === 0 && previous === undefined)
6548
+ return { status: "foreign", ...result };
6549
+ if (selectedReal && (previous === undefined || selected2.workspace !== unitWorkspace(previous))) {
6550
+ const target2 = await ownerBoundary(s, selected2.workspace, false);
6551
+ if (target2)
6552
+ return { ...selected2, ...target2, ...result };
6553
+ }
6554
+ const workspace = unitWorkspace(previous);
6555
+ if (workspace) {
6556
+ const refused3 = await ownerBoundary(s, workspace, active.status === 0);
6557
+ if (refused3)
6558
+ return { ...refused3, ...result };
6559
+ }
6560
+ const unsettled = active.status === 0 ? await settle(s, workspace) : undefined;
6561
+ if (unsettled)
6562
+ return { ...unsettled, ...result };
6563
+ if (readGenerated(path2, MARKER4) !== previous)
6564
+ return { status: "foreign", ...result };
6565
+ if (selectedReal) {
6566
+ try {
6567
+ if (realpathSync5(selected2.workspace) !== selectedReal)
6568
+ return { status: "workspace-changed", ...result };
6569
+ } catch {
6570
+ return { status: "workspace-changed", ...result };
6571
+ }
6572
+ }
6573
+ if (active.status === 0 && s.systemctl(["stop", s.unit]).status !== 0)
6574
+ return {
6575
+ status: "unconfirmed",
6576
+ workspace: unitWorkspace(previous),
6577
+ ...result
6578
+ };
6579
+ mkdirSync6(selected2.workspace, { recursive: true });
6580
+ write(path2, content);
6581
+ if (s.systemctl(["daemon-reload"]).status !== 0)
6582
+ return { status: "failed", ...result, ...selected2 };
6583
+ if (mode === "install" && s.systemctl(["enable", s.unit]).status !== 0)
6584
+ return { status: "failed", ...result, ...selected2 };
6585
+ if (!regularFile(join11(agentDir, "auth.json")))
6586
+ return { status: "waiting-for-pi-login", ...result, ...selected2 };
6587
+ s.systemctl(["reset-failed", s.unit]);
6588
+ return {
6589
+ status: s.systemctl(["start", s.unit]).status === 0 ? "started" : "failed",
6590
+ ...result,
6591
+ ...selected2
6592
+ };
6593
+ }
6594
+ async function stopSystemService(s, remove = false) {
6595
+ const refused2 = boundary(s);
6596
+ if (refused2)
6597
+ return refused2;
6598
+ const { path: path2, previous, result } = observed(s);
6599
+ if (previous === null)
6600
+ return { status: masked(s) ? "masked" : "foreign", ...result };
6601
+ if (previous === undefined)
6602
+ return { status: "absent", scope: "system" };
6603
+ if (s.systemctl(["show-environment"]).status !== 0)
6604
+ return { status: "unavailable", ...result };
6605
+ const workspace = unitWorkspace(previous);
6606
+ if (!sameStateEnvironment(s, previous))
6607
+ return { status: "environment-changed", workspace, ...result };
6608
+ const active = s.systemctl(["is-active", "--quiet", s.unit]);
6609
+ if (active.status !== 0 && active.status !== 3)
6610
+ return { status: "unavailable", workspace, ...result };
6611
+ const owner = await ownerBoundary(s, workspace, active.status === 0);
6612
+ if (owner)
6613
+ return { ...owner, ...result };
6614
+ const unsettled = active.status === 0 ? await settle(s, workspace) : undefined;
6615
+ if (unsettled)
6616
+ return { ...unsettled, ...result };
6617
+ if (readGenerated(path2, MARKER4) !== previous)
6618
+ return { status: "foreign", ...result };
6619
+ if (active.status === 0 && s.systemctl(["stop", s.unit]).status !== 0)
6620
+ return { status: "unconfirmed", workspace, ...result };
6621
+ if (!remove)
6622
+ return { status: "stopped", workspace, ...result };
6623
+ if (s.systemctl(["disable", s.unit]).status !== 0)
6624
+ return { status: "failed", workspace, ...result };
6625
+ if (readGenerated(path2, MARKER4) !== previous)
6626
+ return { status: "foreign", ...result };
6627
+ rmSync4(path2);
6628
+ if (s.systemctl(["daemon-reload"]).status !== 0)
6629
+ return { status: "failed", ...result };
6630
+ s.systemctl(["reset-failed", s.unit]);
6631
+ return { status: "removed", ...result };
6632
+ }
6633
+ function systemServiceStatus(s) {
6634
+ const refused2 = boundary(s);
6635
+ if (refused2)
6636
+ return refused2;
6637
+ const { previous, result } = observed(s);
6638
+ if (previous === undefined)
6639
+ return { status: "absent", scope: "system" };
6640
+ const enabled = s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown";
6641
+ if (enabled.startsWith("masked"))
6642
+ return { status: "masked", enabled, ...result };
6643
+ if (previous === null)
6644
+ return { status: "foreign", ...result };
6645
+ return {
6646
+ status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
6647
+ enabled,
6648
+ workspace: unitWorkspace(previous),
6649
+ ...result
6650
+ };
6651
+ }
6652
+
6383
6653
  // src/service/cli.ts
6384
6654
  function nativeCall(c, operation, reveal = false) {
6385
6655
  const access = c.localAccess();
@@ -6444,14 +6714,14 @@ function nativeCall(c, operation, reveal = false) {
6444
6714
  }
6445
6715
  var pause = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
6446
6716
  async function serviceStatus(c, diagnose = false) {
6447
- const observed = c.inspect();
6448
- if (observed.status === "absent" || observed.status === "stopped")
6449
- return observed;
6717
+ const observed2 = c.inspect();
6718
+ if (observed2.status === "absent" || observed2.status === "stopped")
6719
+ return observed2;
6450
6720
  try {
6451
6721
  return await nativeCall(c, diagnose ? "diagnose" : "status");
6452
6722
  } catch {
6453
6723
  return {
6454
- status: observed.status === "quarantined" ? "quarantined" : "unavailable"
6724
+ status: observed2.status === "quarantined" ? "quarantined" : "unavailable"
6455
6725
  };
6456
6726
  }
6457
6727
  }
@@ -6467,12 +6737,12 @@ async function stopService(c) {
6467
6737
  }
6468
6738
  const deadline = performance.now() + LIMITS.stopWaitMs;
6469
6739
  while (performance.now() < deadline) {
6470
- const observed = c.inspect();
6471
- if ("epoch" in observed && observed.epoch === epoch && observed.status === "stopped")
6740
+ const observed2 = c.inspect();
6741
+ if ("epoch" in observed2 && observed2.epoch === epoch && observed2.status === "stopped")
6472
6742
  return { status: "stopped", epoch };
6473
- if (observed.status === "quarantined")
6743
+ if (observed2.status === "quarantined")
6474
6744
  return { status: "quarantined", epoch };
6475
- if ("epoch" in observed && observed.epoch !== null && observed.epoch !== epoch)
6745
+ if ("epoch" in observed2 && observed2.epoch !== null && observed2.epoch !== epoch)
6476
6746
  return { status: "unconfirmed", epoch };
6477
6747
  await pause(25);
6478
6748
  }
@@ -6504,10 +6774,10 @@ function startupGuidance(result, input) {
6504
6774
  return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
6505
6775
  `;
6506
6776
  case "pi-auth":
6507
- return `${join11(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
6777
+ return `${join12(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
6508
6778
  `;
6509
6779
  case "pi-models":
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.
6780
+ return `${join12(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.
6511
6781
  `;
6512
6782
  case "pi-runtime":
6513
6783
  return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
@@ -6632,9 +6902,9 @@ async function observeStarted(workspace) {
6632
6902
  while (performance.now() < deadline) {
6633
6903
  try {
6634
6904
  const c = new ServiceCoordination(workspace);
6635
- const observed = await serviceStatus(c);
6636
- if (observed && typeof observed === "object" && "status" in observed)
6637
- service = String(observed.status);
6905
+ const observed2 = await serviceStatus(c);
6906
+ if (observed2 && typeof observed2 === "object" && "status" in observed2)
6907
+ service = String(observed2.status);
6638
6908
  if (service === "alive")
6639
6909
  return { service, endpoint: c.localAccess().endpoint };
6640
6910
  if (service === "quarantined")
@@ -6644,9 +6914,9 @@ async function observeStarted(workspace) {
6644
6914
  }
6645
6915
  return { service };
6646
6916
  }
6647
- async function privateLink(workspace) {
6917
+ async function privateLink(workspace, stateHome) {
6648
6918
  try {
6649
- const value = closed(await nativeCall(new ServiceCoordination(workspace), "token-link", true), ["link"]);
6919
+ const value = closed(await nativeCall(new ServiceCoordination(workspace, stateHome), "token-link", true), ["link"]);
6650
6920
  return text(value.link, 4096, true);
6651
6921
  } catch {
6652
6922
  return;
@@ -6655,8 +6925,8 @@ async function privateLink(workspace) {
6655
6925
  var ownerControl = {
6656
6926
  async ownerStatus(workspace) {
6657
6927
  try {
6658
- const observed = await serviceStatus(new ServiceCoordination(workspace));
6659
- return observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
6928
+ const observed2 = await serviceStatus(new ServiceCoordination(workspace));
6929
+ return observed2 && typeof observed2 === "object" && "status" in observed2 ? String(observed2.status) : "unavailable";
6660
6930
  } catch {
6661
6931
  return "unavailable";
6662
6932
  }
@@ -6664,8 +6934,8 @@ var ownerControl = {
6664
6934
  async stopOwner(workspace) {
6665
6935
  try {
6666
6936
  const c = new ServiceCoordination(workspace);
6667
- const observed = await serviceStatus(c);
6668
- const status = observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
6937
+ const observed2 = await serviceStatus(c);
6938
+ const status = observed2 && typeof observed2 === "object" && "status" in observed2 ? String(observed2.status) : "unavailable";
6669
6939
  if (status === "absent" || status === "stopped")
6670
6940
  return "stopped";
6671
6941
  return status === "alive" ? (await stopService(c)).status : status;
@@ -6679,6 +6949,13 @@ async function serverAction(s, action, requested, linger) {
6679
6949
  const mode = action === "install" ? "install" : "restart";
6680
6950
  switch (s.platform) {
6681
6951
  case "linux":
6952
+ if (s.scope === "system") {
6953
+ if (install)
6954
+ return configureSystemService(s, mode, requested);
6955
+ if (action === "stop" || action === "uninstall")
6956
+ return stopSystemService(s, action === "uninstall");
6957
+ return systemServiceStatus(s);
6958
+ }
6682
6959
  if (install)
6683
6960
  return configureAutostart(s, mode, requested, linger);
6684
6961
  if (action === "stop")
@@ -6701,7 +6978,7 @@ async function serverAction(s, action, requested, linger) {
6701
6978
  }
6702
6979
  }
6703
6980
  function serverGuidance(s, action, result, link) {
6704
- const cli = piUserInstall(s) ? `node ${JSON.stringify(join11(s.root, "src", "service-cli.mjs"))}` : "cadence";
6981
+ const cli = piUserInstall(s) ? `node ${JSON.stringify(join12(s.root, "src", "service-cli.mjs"))}` : "cadence";
6705
6982
  const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
6706
6983
  ` : "";
6707
6984
  return note + serverStatusGuidance(s, action, result, cli, link);
@@ -6724,7 +7001,7 @@ function lingerGuidance(linger) {
6724
7001
  function managerDetail(s) {
6725
7002
  switch (s.platform) {
6726
7003
  case "linux":
6727
- return "see systemctl --user status cadence and journalctl --user -u cadence";
7004
+ return `see systemctl ${s.scope === "system" ? "--system" : "--user"} status ${s.unit} and journalctl ${s.scope === "system" ? "" : "--user "}-u ${s.unit}`;
6728
7005
  case "darwin":
6729
7006
  return `see launchctl print gui/${s.uid}/${LABEL} and ~/Library/Logs/cadence.log`;
6730
7007
  default:
@@ -6733,9 +7010,10 @@ function managerDetail(s) {
6733
7010
  }
6734
7011
  function serverStatusGuidance(s, action, result, cli, link) {
6735
7012
  const workspace = JSON.stringify(result.workspace ?? "");
6736
- const manage = `Manage: ${cli} server status|restart|stop|uninstall`;
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)`;
7013
+ const flags = `${s.scope === "system" ? " --system" : s.platform === "linux" ? " --user" : ""}${serviceName(s) ? ` --name ${serviceName(s)}` : ""}`;
7014
+ const manage = `Manage: ${cli} server status|restart|stop|uninstall${flags}`;
7015
+ const when = s.scope === "system" ? "at boot, as root (no linger)" : s.platform === "linux" ? "at boot or login" : "at login";
7016
+ const config = `Port and token: ${PORT_KEY} and ${TOKEN_KEY} in ${serviceConfigPath(s)} (edit them, then: ${cli} server restart${flags})`;
6739
7017
  switch (result.status) {
6740
7018
  case "started": {
6741
7019
  const linger = lingerGuidance(result.linger);
@@ -6757,12 +7035,12 @@ ${manage}
6757
7035
  ${linger}`;
6758
7036
  }
6759
7037
  case "waiting-for-pi-login":
6760
- return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join11(agentDirectory(s), "auth.json")}.
6761
- Log in to Pi, then: ${cli} server restart
7038
+ return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join12(agentDirectory(s), "auth.json")}.
7039
+ Log in to Pi, then: ${cli} server restart${flags}
6762
7040
  ${config}
6763
7041
  ${lingerGuidance(result.linger)}`;
6764
7042
  case "stopped":
6765
- return action === "stop" ? `Cadence stopped; it starts again ${when}, or now with: ${cli} server restart
7043
+ return action === "stop" ? `Cadence stopped; it starts again ${when}, or now with: ${cli} server restart${flags}
6766
7044
  ` : "";
6767
7045
  case "absent":
6768
7046
  return action === "restart" || action === "stop" ? `Cadence's background service is not installed: ${cli} server install
@@ -6771,7 +7049,22 @@ ${lingerGuidance(result.linger)}`;
6771
7049
  return `Cadence's service definition was rewritten for ${result.workspace}; left disabled as configured.
6772
7050
  `;
6773
7051
  case "masked":
6774
- return `cadence.service is masked; Cadence did not enable or start it.
7052
+ return `${s.unit} is masked; Cadence did not enable or start it.
7053
+ `;
7054
+ case "root-required":
7055
+ return `System service management requires root. Run this command explicitly as root (for example sudo -H); Cadence never elevates itself. Root uses its own Pi credentials and private state, not copied user profiles.
7056
+ `;
7057
+ case "scope-conflict":
7058
+ return `The user service ${result.unit} still exists. Stop/uninstall it explicitly with --user before installing --system; no service or state was migrated.
7059
+ `;
7060
+ case "project-running":
7061
+ return `This project already has a running owner. Stop it explicitly before registering a different supervisor: ${cli} stop --workspace ${workspace}
7062
+ `;
7063
+ case "environment-changed":
7064
+ return `HOME or XDG_STATE_HOME differs from the generated unit. Use its original environment for orderly stop/uninstall before explicitly installing with the new state selection; no migration or lock cleanup was attempted.
7065
+ `;
7066
+ case "workspace-changed":
7067
+ return `Workspace changed during orderly Stop; nothing was rewritten. Observe and retry explicitly.
6775
7068
  `;
6776
7069
  case "missing-pi-sdk":
6777
7070
  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.
@@ -6781,7 +7074,7 @@ ${lingerGuidance(result.linger)}`;
6781
7074
  `;
6782
7075
  case "unavailable":
6783
7076
  return result.workspace ? `Cadence changed nothing: its owner could not be observed. Inspect: ${cli} diagnose --workspace ${workspace}
6784
- ` : `No user service manager; start Cadence explicitly: ${cli} start --workspace /absolute/project
7077
+ ` : `No selected service manager; start Cadence explicitly: ${cli} start --workspace /absolute/project
6785
7078
  `;
6786
7079
  case "unsupported":
6787
7080
  return `Cadence has no background service for ${s.platform}; start it explicitly: ${cli} start --workspace /absolute/project
@@ -6821,28 +7114,56 @@ async function serverMain(argv, hostUrl) {
6821
7114
  argv.shift();
6822
7115
  let requested;
6823
7116
  let linger = process.env.CADENCE_LINGER !== "0";
6824
- let noLinger = false;
6825
- while (action === "install" && argv.length) {
6826
- if (argv[0] === "--workspace" && argv[1] && requested === undefined)
6827
- requested = resolve5(argv.splice(0, 2)[1]);
6828
- else if (argv[0] === "--no-linger" && !noLinger) {
6829
- argv.shift();
6830
- noLinger = true;
7117
+ let scope;
7118
+ let name, port;
7119
+ const seen = new Set;
7120
+ while (argv.length) {
7121
+ const flag = argv.shift();
7122
+ if (!flag || seen.has(flag))
7123
+ throw new ContractError("invalid");
7124
+ seen.add(flag);
7125
+ if (flag === "--system" || flag === "--user") {
7126
+ if (scope)
7127
+ throw new ContractError("invalid");
7128
+ scope = flag === "--system" ? "system" : "user";
7129
+ } else if (flag === "--no-linger" && action === "install")
6831
7130
  linger = false;
7131
+ else if (flag === "--name" && argv[0])
7132
+ name = argv.shift();
7133
+ else if (flag === "--workspace" && action === "install" && argv[0])
7134
+ requested = resolve5(argv.shift());
7135
+ else if (flag === "--port" && action === "install" && argv[0]) {
7136
+ port = portValue(argv.shift());
7137
+ if (!port)
7138
+ throw new ContractError("invalid");
6832
7139
  } else
6833
- break;
7140
+ throw new ContractError("invalid");
7141
+ }
7142
+ const s = hostSystem(dirname9(dirname9(fileURLToPath(hostUrl))), ownerControl, {
7143
+ scope,
7144
+ name
7145
+ });
7146
+ if ((scope || name !== undefined) && s.platform !== "linux")
7147
+ throw new ContractError("unsupported-context");
7148
+ if (s.scope === "system" && s.uid !== 0) {
7149
+ const result2 = { status: "root-required", scope: "system" };
7150
+ process.stderr.write(serverGuidance(s, action, result2, undefined));
7151
+ process.stdout.write(`${JSON.stringify(result2)}
7152
+ `);
7153
+ process.exitCode = 1;
7154
+ return;
6834
7155
  }
6835
- if (argv.length)
6836
- throw new ContractError("invalid");
6837
- const s = hostSystem(dirname9(dirname9(fileURLToPath(hostUrl))), ownerControl);
6838
7156
  if (action === "install")
6839
7157
  process.stderr.write(researchTemplate(s.home));
6840
7158
  if (action === "install" || action === "restart") {
6841
7159
  try {
6842
- const { added, exposed } = ensureServerConfig(s.home);
6843
- const path2 = serverConfigPath(s.home);
7160
+ const { added, exposed } = ensureServerConfig(s.home, {
7161
+ name: serviceName(s),
7162
+ port
7163
+ });
7164
+ const path2 = serviceConfigPath(s);
6844
7165
  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}
7166
+ process.stderr.write(`Cadence added ${added.map((key) => key === PORT_KEY ? `${key}=${port ?? DEFAULT_PORT}` : `a random private ${key}`).join(" and ")} to ${path2}
6846
7167
  `);
6847
7168
  if (exposed)
6848
7169
  process.stderr.write(`Note: other users can read ${path2}; chmod 600 ${JSON.stringify(path2)} keeps ${TOKEN_KEY} private.
@@ -6878,10 +7199,72 @@ async function serverMain(argv, hostUrl) {
6878
7199
  "unsupported",
6879
7200
  "unconfirmed",
6880
7201
  "quarantined",
6881
- "missing-pi-sdk"
7202
+ "missing-pi-sdk",
7203
+ "root-required",
7204
+ "scope-conflict",
7205
+ "workspace-changed",
7206
+ "project-running",
7207
+ "environment-changed"
6882
7208
  ].includes(result.status) || result.status === "absent" && (action === "restart" || action === "stop"))
6883
7209
  process.exitCode = 1;
6884
7210
  }
7211
+ async function projectMain(argv, hostUrl) {
7212
+ if (argv.shift() !== "open")
7213
+ throw new ContractError("invalid");
7214
+ const options = { workspace: process.cwd() };
7215
+ let create = false, selected2 = false;
7216
+ const seen = new Set;
7217
+ while (argv.length) {
7218
+ const flag = argv.shift();
7219
+ if (!flag.startsWith("--") && !selected2) {
7220
+ options.workspace = resolve5(flag);
7221
+ selected2 = true;
7222
+ } else if (!seen.has(flag) && flag === "--create") {
7223
+ seen.add(flag);
7224
+ create = true;
7225
+ } else if (!seen.has(flag) && (flag === "--state-home" || flag === "--agent-dir") && argv[0]) {
7226
+ seen.add(flag);
7227
+ const value = resolve5(argv.shift());
7228
+ if (flag === "--state-home")
7229
+ options.stateHome = value;
7230
+ else
7231
+ options.agentDir = value;
7232
+ } else
7233
+ throw new ContractError("invalid");
7234
+ }
7235
+ if (privateWorkspace(options.workspace, options.stateHome))
7236
+ throw new ContractError("forbidden");
7237
+ if (create)
7238
+ mkdirSync7(options.workspace, { recursive: true });
7239
+ const landing = effectiveWorkspace(options.workspace, options.stateHome);
7240
+ if (landing.redirected) {
7241
+ process.stderr.write(`Cadence workspace: ${landing.workspace} (${options.workspace} contains Cadence's private state)
7242
+ `);
7243
+ mkdirSync7(landing.workspace, { recursive: true });
7244
+ }
7245
+ options.workspace = landing.workspace;
7246
+ const c = new ServiceCoordination(options.workspace, options.stateHome);
7247
+ const kept = readHostConfig(c);
7248
+ if (!kept)
7249
+ options.port = 0;
7250
+ const result = await startService(options, false, hostUrl);
7251
+ const alive = result && typeof result === "object" && "status" in result && result.status === "alive";
7252
+ const config = alive ? readHostConfig(c) : undefined;
7253
+ process.stdout.write(`${JSON.stringify({ ...result, workspace: c.store.root, ...config ? { cwd: config.cwd, endpoint: c.localAccess().endpoint } : {} })}
7254
+ `);
7255
+ if (alive) {
7256
+ const link = await privateLink(c.store.root, options.stateHome);
7257
+ process.stderr.write(`Project owner ready: ${c.store.root} (task cwd: ${config?.cwd}). No task was created or started.
7258
+ `);
7259
+ if (link)
7260
+ process.stderr.write(`Private WebUI link: ${link}
7261
+ `);
7262
+ process.stderr.write(`Native entry: open Pi in this project and use /cadence. Stop only this project: cadence stop --workspace ${JSON.stringify(c.store.root)}${options.stateHome ? ` --state-home ${JSON.stringify(options.stateHome)}` : ""}
7263
+ `);
7264
+ }
7265
+ if (failedStatus(result))
7266
+ process.exitCode = 1;
7267
+ }
6885
7268
  async function managementMain(argv, hostUrl) {
6886
7269
  try {
6887
7270
  if (!process.versions.node || process.versions.bun)
@@ -6890,12 +7273,19 @@ async function managementMain(argv, hostUrl) {
6890
7273
  if (command === "--help" && !argv.length) {
6891
7274
  process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
6892
7275
  cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
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).
7276
+ Linux server options: --system (root, no linger) or --user; --name NAME selects an independent service; install also accepts --port PORT (required for a new named service). Root defaults to --system. No automatic sudo or profile migration.
7277
+ cadence project open [PATH] [--create] [--agent-dir PATH] [--state-home PATH]
7278
+ Project open explicitly starts/reuses that checkout owner, selecting a private free port for a new project. It never creates or starts a task.
7279
+ Start is explicit, or the background service that server install registers: a system/root unit on Linux without linger, or --user with optional linger (--no-linger or CADENCE_LINGER=0 keeps user scope 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).
6894
7280
  Return/quit detaches clients; only explicit Cancel/Stop requests stop. Unconfirmed/quarantined is not stopped.
6895
7281
  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.
6896
7282
  `);
6897
7283
  return;
6898
7284
  }
7285
+ if (command === "project") {
7286
+ await projectMain(argv, hostUrl);
7287
+ return;
7288
+ }
6899
7289
  if (command === "server") {
6900
7290
  await serverMain(argv, hostUrl);
6901
7291
  return;
@@ -6966,12 +7356,12 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
6966
7356
  `);
6967
7357
  options.workspace = landing.workspace;
6968
7358
  if (command === "start")
6969
- mkdirSync5(landing.workspace, { recursive: true });
7359
+ mkdirSync7(landing.workspace, { recursive: true });
6970
7360
  }
6971
7361
  let result;
6972
7362
  if (command === "start")
6973
7363
  result = await startService(options, foreground, hostUrl);
6974
- else if (landing.redirected && !existsSync(landing.workspace)) {
7364
+ else if (landing.redirected && !existsSync3(landing.workspace)) {
6975
7365
  if (command !== "status" && command !== "diagnose")
6976
7366
  throw new ContractError("unavailable");
6977
7367
  result = { status: "absent" };