arcane-cli 1.17.2 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,9 +4,13 @@ const require = __createRequire(import.meta.url);
4
4
 
5
5
  // src/index.ts
6
6
  import { Command } from "commander";
7
- import { createRequire as createRequire3 } from "module";
8
7
  import { fileURLToPath } from "url";
9
- import { dirname as dirname14, join as join29 } from "path";
8
+ import { dirname as dirname15, join as join31 } from "path";
9
+
10
+ // src/package-info.ts
11
+ import { createRequire } from "module";
12
+ var require2 = createRequire(import.meta.url);
13
+ var packageJson = require2("../package.json");
10
14
 
11
15
  // src/commands/init.ts
12
16
  import { select as select3, confirm as confirm2, input as input3 } from "@inquirer/prompts";
@@ -148,21 +152,21 @@ function runGit(cwd, args, options = {}) {
148
152
  windowsHide: true,
149
153
  encoding: "utf8"
150
154
  },
151
- (error, stdout, stderr) => {
152
- if (error) {
153
- if (error.killed) {
155
+ (error2, stdout, stderr) => {
156
+ if (error2) {
157
+ if (error2.killed) {
154
158
  reject(
155
159
  new GitTimeoutError(
156
160
  // NOTE: embeds the raw args, including any secrets a future
157
161
  // network-class caller might pass (e.g. a credentialed
158
162
  // clone URL). No current caller does; redact before one does.
159
163
  `git ${args.join(" ")} timed out after ${timeoutMs}ms (class: ${commandClass})`,
160
- { cause: error }
164
+ { cause: error2 }
161
165
  )
162
166
  );
163
167
  return;
164
168
  }
165
- reject(error);
169
+ reject(error2);
166
170
  return;
167
171
  }
168
172
  resolve10({ stdout, stderr });
@@ -223,8 +227,8 @@ async function correctUnbornMasterDefault(cwd) {
223
227
  try {
224
228
  await runGit(cwd, ["symbolic-ref", "-q", "refs/heads/main"]);
225
229
  return { corrected: false, to: "main", blockedReason: "target-unreadable" };
226
- } catch (error) {
227
- if (error.code !== 1) {
230
+ } catch (error2) {
231
+ if (error2.code !== 1) {
228
232
  return { corrected: false, to: "main", blockedReason: "target-unreadable" };
229
233
  }
230
234
  }
@@ -659,8 +663,8 @@ function isValidTodoDoneBook(value) {
659
663
  if (value !== value.trim() || value.includes(":")) return false;
660
664
  const normalized = path2.posix.normalize(value.replace(/\\/g, "/"));
661
665
  const segments = normalized.split("/");
662
- const basename5 = segments[segments.length - 1] ?? "";
663
- return /^[^/]+\.md$/i.test(basename5) && !/^\.md$/i.test(basename5) && !segments.slice(0, -1).some((segment) => segment.startsWith(".") && segment !== ".") && !TODO_DONE_BOOK_RESERVED.has(basename5.toLowerCase());
666
+ const basename6 = segments[segments.length - 1] ?? "";
667
+ return /^[^/]+\.md$/i.test(basename6) && !/^\.md$/i.test(basename6) && !segments.slice(0, -1).some((segment) => segment.startsWith(".") && segment !== ".") && !TODO_DONE_BOOK_RESERVED.has(basename6.toLowerCase());
664
668
  }
665
669
  function manifestPath(targetDir) {
666
670
  return path2.join(targetDir, MANIFEST_FILE);
@@ -1483,13 +1487,13 @@ function isClientShimPath(path5) {
1483
1487
  }
1484
1488
  var FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
1485
1489
  function parsePromptFrontmatter(content) {
1486
- const match = content.match(FRONTMATTER_PATTERN);
1487
- if (!match) {
1490
+ const match2 = content.match(FRONTMATTER_PATTERN);
1491
+ if (!match2) {
1488
1492
  throw new MissingFrontmatterError(
1489
1493
  "Prompt file has no YAML frontmatter block (expected a leading --- ... --- block)."
1490
1494
  );
1491
1495
  }
1492
- const raw = parseYaml(match[1]);
1496
+ const raw = parseYaml(match2[1]);
1493
1497
  const name = raw?.["name"];
1494
1498
  const description = raw?.["description"];
1495
1499
  if (typeof name !== "string" || name.trim() === "") {
@@ -1902,7 +1906,7 @@ function scale(hex, f) {
1902
1906
  }
1903
1907
  function lockupLines(f = 1) {
1904
1908
  const g = f >= 1 ? arcaneGradient : gradient(STOPS.map((c) => scale(c, f)));
1905
- return WORDMARK.map((line) => INDENT + g(line));
1909
+ return WORDMARK.map((line2) => INDENT + g(line2));
1906
1910
  }
1907
1911
  function printFooter(version) {
1908
1912
  const taglineText = ` ${TAGLINE} \xB7 agent-native SDLC \xB7 v${version}`;
@@ -1937,7 +1941,7 @@ async function printWelcome(version, cwd) {
1937
1941
  if (isAnimatable()) {
1938
1942
  const draw = (lines, redraw) => {
1939
1943
  if (redraw) process.stdout.write(`\x1B[${lines.length}A`);
1940
- for (const line of lines) process.stdout.write(`\r\x1B[2K${line}
1944
+ for (const line2 of lines) process.stdout.write(`\r\x1B[2K${line2}
1941
1945
  `);
1942
1946
  };
1943
1947
  const fade = [0.1, 0.22, 0.36, 0.52, 0.68, 0.84, 1];
@@ -1951,7 +1955,7 @@ async function printWelcome(version, cwd) {
1951
1955
  await sleep(140);
1952
1956
  }
1953
1957
  } else {
1954
- for (const line of lockupLines()) console.log(line);
1958
+ for (const line2 of lockupLines()) console.log(line2);
1955
1959
  }
1956
1960
  printFooter(version);
1957
1961
  console.log();
@@ -3000,8 +3004,8 @@ async function performAgentSync(targetDir, assetsDir, roster, options) {
3000
3004
  dryRun: options.dryRun,
3001
3005
  fallbackDir: assetsDir
3002
3006
  });
3003
- for (const line of describeFanoutOutcomes(fanout.outcomes, options.dryRun)) {
3004
- console.log(` ${line}`);
3007
+ for (const line2 of describeFanoutOutcomes(fanout.outcomes, options.dryRun)) {
3008
+ console.log(` ${line2}`);
3005
3009
  }
3006
3010
  if (!options.dryRun) {
3007
3011
  await writeManifest(targetDir, { ...manifest, fanout: fanout.record }, { followLinks: true });
@@ -3312,7 +3316,7 @@ async function detectAdoContext(targetDir) {
3312
3316
  } catch {
3313
3317
  return false;
3314
3318
  }
3315
- return stdout.split("\n").map((line) => line.slice(line.indexOf(" ") + 1)).some((url) => url.trim() !== "" && isAdoRemoteUrl(url));
3319
+ return stdout.split("\n").map((line2) => line2.slice(line2.indexOf(" ") + 1)).some((url) => url.trim() !== "" && isAdoRemoteUrl(url));
3316
3320
  }
3317
3321
 
3318
3322
  // src/modules/hub.ts
@@ -3546,19 +3550,19 @@ function resolveManifestFlags(flags, current, manifestPath2) {
3546
3550
  if (flags.contentSensitivity !== void 0) requested.content_sensitivity = flags.contentSensitivity;
3547
3551
  if (flags.subjectRoot !== void 0) requested.subject_root = flags.subjectRoot;
3548
3552
  if (flags.pushPolicy !== void 0) requested.push_policy = flags.pushPolicy;
3549
- const invalid = (error) => {
3550
- const allowed = error.field === "role" ? ["hub", "consumer"] : MANIFEST_ENUM_VALUES[error.field];
3553
+ const invalid = (error2) => {
3554
+ const allowed = error2.field === "role" ? ["hub", "consumer"] : MANIFEST_ENUM_VALUES[error2.field];
3551
3555
  const hint = allowed ? ` Valid values: ${allowed.join(", ")}.` : "";
3552
- return new ManifestFlagError(`Invalid ${FLAG_NAMES[error.field] ?? error.field}: ${error.message}${hint}`);
3556
+ return new ManifestFlagError(`Invalid ${FLAG_NAMES[error2.field] ?? error2.field}: ${error2.message}${hint}`);
3553
3557
  };
3554
3558
  if (requested.role !== void 0 && requested.role !== "hub" && requested.role !== "consumer") {
3555
3559
  throw invalid(new ManifestInvalidFieldError(manifestPath2, "role", requested.role));
3556
3560
  }
3557
3561
  try {
3558
3562
  validateManifestFields(requested, manifestPath2);
3559
- } catch (error) {
3560
- if (error instanceof ManifestInvalidFieldError) throw invalid(error);
3561
- throw error;
3563
+ } catch (error2) {
3564
+ if (error2 instanceof ManifestInvalidFieldError) throw invalid(error2);
3565
+ throw error2;
3562
3566
  }
3563
3567
  if (requested.external_provider !== void 0 && requested.tracking_mode !== "external") {
3564
3568
  throw new ManifestFlagError("--external-provider applies only together with --tracking-mode external.");
@@ -3659,8 +3663,8 @@ async function readHooksPath(cwd) {
3659
3663
  try {
3660
3664
  const { stdout } = await runGit(cwd, ["config", "--show-scope", "--get", "core.hooksPath"]);
3661
3665
  raw = stdout.trim();
3662
- } catch (error) {
3663
- if (error.code !== 1) {
3666
+ } catch (error2) {
3667
+ if (error2.code !== 1) {
3664
3668
  return { value: "", scope: "unknown", unreadable: true };
3665
3669
  }
3666
3670
  return void 0;
@@ -3886,7 +3890,7 @@ async function pushTargets(cwd) {
3886
3890
  const { stdout } = await runGit(cwd, ["remote", "get-url", "--push", "--all", remote]);
3887
3891
  targets.push({
3888
3892
  remote,
3889
- urls: stdout.split("\n").map((line) => line.trim()).filter(Boolean)
3893
+ urls: stdout.split("\n").map((line2) => line2.trim()).filter(Boolean)
3890
3894
  });
3891
3895
  } catch {
3892
3896
  targets.push({ remote, urls: [] });
@@ -3900,7 +3904,7 @@ function originalPushUrlKey(remote) {
3900
3904
  async function readLocalAll(cwd, key) {
3901
3905
  try {
3902
3906
  const { stdout } = await runGit(cwd, ["config", "--local", "--get-all", key]);
3903
- return stdout.split("\n").map((line) => line.trim()).filter(Boolean);
3907
+ return stdout.split("\n").map((line2) => line2.trim()).filter(Boolean);
3904
3908
  } catch {
3905
3909
  return [];
3906
3910
  }
@@ -3912,10 +3916,10 @@ async function readScopedAll(cwd, key) {
3912
3916
  } catch {
3913
3917
  return [];
3914
3918
  }
3915
- return stdout.split("\n").filter((line) => line !== "").map((line) => {
3916
- const separator = line.indexOf(" ");
3917
- if (separator === -1) return { scope: "unknown", value: line.trim() };
3918
- return { scope: line.slice(0, separator).trim(), value: line.slice(separator + 1).trim() };
3919
+ return stdout.split("\n").filter((line2) => line2 !== "").map((line2) => {
3920
+ const separator = line2.indexOf(" ");
3921
+ if (separator === -1) return { scope: "unknown", value: line2.trim() };
3922
+ return { scope: line2.slice(0, separator).trim(), value: line2.slice(separator + 1).trim() };
3919
3923
  });
3920
3924
  }
3921
3925
  async function effectivePushUrls(cwd, remote) {
@@ -3927,8 +3931,8 @@ async function disablePushUrls(cwd) {
3927
3931
  for (const remote of await listRemotes(cwd)) {
3928
3932
  try {
3929
3933
  results.push(await disableOneRemote(cwd, remote));
3930
- } catch (error) {
3931
- results.push({ remote, status: "failed", reason: describeError(error) });
3934
+ } catch (error2) {
3935
+ results.push({ remote, status: "failed", reason: describeError(error2) });
3932
3936
  }
3933
3937
  }
3934
3938
  return results;
@@ -4003,8 +4007,8 @@ async function restorePushUrls(cwd) {
4003
4007
  for (const remote of await listRemotes(cwd)) {
4004
4008
  try {
4005
4009
  results.push(await restoreOneRemote(cwd, remote));
4006
- } catch (error) {
4007
- results.push({ remote, status: "failed", reason: describeError(error) });
4010
+ } catch (error2) {
4011
+ results.push({ remote, status: "failed", reason: describeError(error2) });
4008
4012
  }
4009
4013
  }
4010
4014
  return results;
@@ -4044,8 +4048,8 @@ async function restoreOneRemote(cwd, remote) {
4044
4048
  async function unsetIgnoringMissing(cwd, key) {
4045
4049
  try {
4046
4050
  await runGit(cwd, ["config", "--local", "--unset-all", key]);
4047
- } catch (error) {
4048
- if (error.code !== 5) throw error;
4051
+ } catch (error2) {
4052
+ if (error2.code !== 5) throw error2;
4049
4053
  }
4050
4054
  }
4051
4055
  async function hadNoPushUrl(cwd, remote) {
@@ -4073,8 +4077,8 @@ async function readLegacyRecord(cwd, remote) {
4073
4077
  }
4074
4078
  return { status: "usable", values };
4075
4079
  }
4076
- function describeError(error) {
4077
- const message = error instanceof Error ? error.message : String(error);
4080
+ function describeError(error2) {
4081
+ const message = error2 instanceof Error ? error2.message : String(error2);
4078
4082
  return message.split("\n")[0].trim();
4079
4083
  }
4080
4084
  function hookInForce(outcome) {
@@ -4082,8 +4086,8 @@ function hookInForce(outcome) {
4082
4086
  }
4083
4087
  async function applyBlockedPushControls(cwd, options = {}) {
4084
4088
  const messages = [];
4085
- const say = (level, text) => {
4086
- const message = { level, text };
4089
+ const say = (level, text2) => {
4090
+ const message = { level, text: text2 };
4087
4091
  messages.push(message);
4088
4092
  options.emit?.(message);
4089
4093
  };
@@ -4373,7 +4377,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
4373
4377
  }
4374
4378
  const escapes = findLinkedEscapes(targetDir, plannedFiles);
4375
4379
  if (escapes.length > 0) {
4376
- for (const line of describeLinkedEscapes(targetDir, escapes)) console.error(line);
4380
+ for (const line2 of describeLinkedEscapes(targetDir, escapes)) console.error(line2);
4377
4381
  console.error(
4378
4382
  " \u2717 Init refused: nothing was installed. Arcane writes managed files only inside its target directory: replace each link named above with a directory, then run `spell init` again."
4379
4383
  );
@@ -4562,7 +4566,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
4562
4566
  const flagLine = options.profile ? manifestFlagLine(manifest) : void 0;
4563
4567
  if (flagLine) {
4564
4568
  printInfo("Some setup questions were left unanswered. Answer them without a prompt with:");
4565
- for (const line of flagLine) printInfo(` ${line}`);
4569
+ for (const line2 of flagLine) printInfo(` ${line2}`);
4566
4570
  }
4567
4571
  if (push_policy === "blocked" || push_policy === "guarded") {
4568
4572
  try {
@@ -4754,9 +4758,9 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
4754
4758
  };
4755
4759
  await writeManifest(storeRoot, manifest, { followLinks: true });
4756
4760
  printStep(`\u2728 ${fileCount} Spells installed to ${storeRoot}/spells/`);
4757
- for (const line of describeFanoutOutcomes(fanout.outcomes)) {
4758
- if (line.startsWith("!")) printWarning(line.slice(2));
4759
- else printStep(`\u{1F517} ${line}`);
4761
+ for (const line2 of describeFanoutOutcomes(fanout.outcomes)) {
4762
+ if (line2.startsWith("!")) printWarning(line2.slice(2));
4763
+ else printStep(`\u{1F517} ${line2}`);
4760
4764
  }
4761
4765
  printSuccess(
4762
4766
  `Initialized the user tier \u2014 ${fileCount} spells, ${Object.keys(fanout.record).length} client files recorded`
@@ -4764,7 +4768,7 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
4764
4768
  console.log();
4765
4769
  printSection("\u{1F9ED} Clients");
4766
4770
  console.log();
4767
- for (const line of VSCODE_USER_TIER_NOTE) printInfo(line);
4771
+ for (const line2 of VSCODE_USER_TIER_NOTE) printInfo(line2);
4768
4772
  printInfo(CLAUDE_PRECEDENCE_NOTE);
4769
4773
  console.log();
4770
4774
  printSection("\u{1F680} Next steps");
@@ -5045,11 +5049,11 @@ function computeLcsDiff(base, other) {
5045
5049
  }
5046
5050
  function arraysEqual(a, b) {
5047
5051
  if (a.length !== b.length) return false;
5048
- return a.every((line, idx) => line === b[idx]);
5052
+ return a.every((line2, idx) => line2 === b[idx]);
5049
5053
  }
5050
5054
  var MAX_LINES_FOR_LCS = 4e3;
5051
- function splitLines(text) {
5052
- return text.split("\n");
5055
+ function splitLines(text2) {
5056
+ return text2.split("\n");
5053
5057
  }
5054
5058
  function merge3(base, ours, theirs) {
5055
5059
  const baseLines = splitLines(base);
@@ -5344,9 +5348,9 @@ async function missingTierWarning(scope, spellScope, homeDir) {
5344
5348
  function reportFlagPatch(flagPatch, before, after, dryRun) {
5345
5349
  console.log(`
5346
5350
  ${dryRun ? "[dry-run] Would record" : "Recorded"} from flags:`);
5347
- for (const line of describeFlagPatch(flagPatch)) console.log(` ${line}`);
5351
+ for (const line2 of describeFlagPatch(flagPatch)) console.log(` ${line2}`);
5348
5352
  if (!dryRun && after.push_policy !== before.push_policy) {
5349
- for (const line of pushPolicyNotice(after.push_policy)) console.log(line);
5353
+ for (const line2 of pushPolicyNotice(after.push_policy)) console.log(line2);
5350
5354
  }
5351
5355
  }
5352
5356
  function defaultScopePatch(options, manifest) {
@@ -5619,7 +5623,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5619
5623
  const homeDir = dirname7(targetDir);
5620
5624
  const tierGate = await missingTierWarning(scope, spellScope);
5621
5625
  if (tierGate.length > 0) {
5622
- for (const line of tierGate) console.error(line);
5626
+ for (const line2 of tierGate) console.error(line2);
5623
5627
  console.error(
5624
5628
  " \u2717 Update refused: nothing was written. Install the tier or change spell_scope, then run `spell update` again."
5625
5629
  );
@@ -5662,7 +5666,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5662
5666
  const linkedEscapes = followDirectoryLinks || manifest.components.length === 0 ? [] : findLinkedEscapes(targetDir, await plannedUpdateWrites(manifest.components, scope, spellScope, assetsDir));
5663
5667
  const refusedForLinks = () => {
5664
5668
  if (linkedEscapes.length === 0) return false;
5665
- for (const line of describeLinkedEscapes(targetDir, linkedEscapes)) console.error(line);
5669
+ for (const line2 of describeLinkedEscapes(targetDir, linkedEscapes)) console.error(line2);
5666
5670
  console.error(
5667
5671
  " \u2717 Update refused: nothing was written. Arcane writes and deletes managed files only inside its target directory: replace each link named above with a directory, then run `spell update` again."
5668
5672
  );
@@ -5698,7 +5702,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5698
5702
  registryChanges.missingRequires = missingRequires.filter((m) => !installing.has(m.name)).map((m) => ({ ...m, leftAloneReason: installPlan.leftAlone.find((l) => l.name === m.name)?.reason }));
5699
5703
  }
5700
5704
  const printRegistryChanges = () => {
5701
- for (const line of formatRegistryChanges(registryChanges)) console.log(line);
5705
+ for (const line2 of formatRegistryChanges(registryChanges)) console.log(line2);
5702
5706
  };
5703
5707
  const applyInstalls = async () => {
5704
5708
  const declined = installPlan.leftAlone.filter((l) => l.kind === "new");
@@ -5706,14 +5710,14 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5706
5710
  console.log(options.dryRun ? "\n[dry-run] Components this update would install (ARC-052):" : "\nComponents installed by this update (ARC-052):");
5707
5711
  for (const l of declined) console.log(` ! Not installed: ${l.name} \u2014 ${l.reason}`);
5708
5712
  if (options.dryRun) {
5709
- for (const line of describeInstallPlan(installPlan)) console.log(line);
5713
+ for (const line2 of describeInstallPlan(installPlan)) console.log(line2);
5710
5714
  return [];
5711
5715
  }
5712
5716
  const { installed, lines } = await executeUpdateInstalls(installPlan, targetDir, packageVersion, {
5713
5717
  linkTracked,
5714
5718
  conversions
5715
5719
  });
5716
- for (const line of lines) console.log(line);
5720
+ for (const line2 of lines) console.log(line2);
5717
5721
  return installed;
5718
5722
  };
5719
5723
  let sameVersionRestore = false;
@@ -5740,7 +5744,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5740
5744
  dryRun: options.dryRun,
5741
5745
  fallbackDir: assetsDir
5742
5746
  });
5743
- for (const line of describeFanoutOutcomes(fanout.outcomes, options.dryRun)) console.log(` ${line}`);
5747
+ for (const line2 of describeFanoutOutcomes(fanout.outcomes, options.dryRun)) console.log(` ${line2}`);
5744
5748
  const scopePatch = defaultScopePatch(options, manifest);
5745
5749
  if (!options.dryRun && (JSON.stringify(fanout.record) !== JSON.stringify(manifest.fanout ?? {}) || scopePatch !== void 0)) {
5746
5750
  await writeManifest(targetDir, { ...manifest, ...scopePatch, fanout: fanout.record }, { followLinks: scope === "user" });
@@ -6044,7 +6048,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
6044
6048
  });
6045
6049
  const lines = describeFanoutOutcomes(fanout.outcomes, options.dryRun);
6046
6050
  if (lines.length > 0) console.log();
6047
- for (const line of lines) console.log(` ${line}`);
6051
+ for (const line2 of lines) console.log(` ${line2}`);
6048
6052
  fanoutRecord = fanout.record;
6049
6053
  }
6050
6054
  if (options.dryRun) {
@@ -6069,7 +6073,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
6069
6073
  console.log(
6070
6074
  ` ! Skipped ${pending.length} manifest question${pending.length === 1 ? "" : "s"} (${pending.map((r) => r.field).join(", ")}) \u2014 no interactive terminal. Run \`spell update\` from a terminal, or answer by flags:`
6071
6075
  );
6072
- for (const line of manifestFlagLine(answered) ?? []) console.log(` ${line}`);
6076
+ for (const line2 of manifestFlagLine(answered) ?? []) console.log(` ${line2}`);
6073
6077
  }
6074
6078
  }
6075
6079
  const updated = {
@@ -6089,7 +6093,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
6089
6093
  if (Object.keys(flagPatch).length > 0) {
6090
6094
  reportFlagPatch(flagPatch, manifest, updated, false);
6091
6095
  } else if (updated.push_policy !== manifest.push_policy) {
6092
- for (const line of pushPolicyNotice(updated.push_policy)) console.log(line);
6096
+ for (const line2 of pushPolicyNotice(updated.push_policy)) console.log(line2);
6093
6097
  }
6094
6098
  if (conflictedFiles.length > 0) {
6095
6099
  console.log(
@@ -6117,14 +6121,11 @@ import { dirname as dirname9 } from "path";
6117
6121
  import { readFile as readFile10, writeFile as writeFile10, mkdir as mkdir6 } from "fs/promises";
6118
6122
  import { dirname as dirname8, join as join12 } from "path";
6119
6123
  import { homedir as homedir3 } from "os";
6120
- import { createRequire } from "module";
6121
- var require2 = createRequire(import.meta.url);
6122
6124
  function getFeedUrl() {
6123
- const pkg2 = require2("../../package.json");
6124
- const url = pkg2.publishConfig?.registry;
6125
+ const url = packageJson.publishConfig?.registry;
6125
6126
  if (!url) {
6126
6127
  throw new Error(
6127
- "publishConfig.registry is not set in package.json. Cannot resolve npm feed URL."
6128
+ "publishConfig.registry is not set in package.json, so the npm feed URL cannot be resolved"
6128
6129
  );
6129
6130
  }
6130
6131
  return url;
@@ -6219,14 +6220,15 @@ async function collectVersions(manifest, packageVersion, options) {
6219
6220
  let lookup = null;
6220
6221
  if (options.online) {
6221
6222
  let feedUrl = options.feedUrl ?? "";
6223
+ let feedError = "";
6222
6224
  if (feedUrl === "") {
6223
6225
  try {
6224
6226
  feedUrl = getFeedUrl();
6225
- } catch {
6226
- feedUrl = "";
6227
+ } catch (error2) {
6228
+ feedError = `Feed URL unavailable: ${error2 instanceof Error ? error2.message : String(error2)}`;
6227
6229
  }
6228
6230
  }
6229
- lookup = feedUrl ? await checkForUpdate(packageVersion, feedUrl, options.cachePath) : { current: packageVersion, latest: null, updateAvailable: false, error: "Feed URL unavailable" };
6231
+ lookup = feedUrl ? await checkForUpdate(packageVersion, feedUrl, options.cachePath) : { current: packageVersion, latest: null, updateAvailable: false, error: feedError };
6230
6232
  if (lookup.latest === null) {
6231
6233
  warnings.push(
6232
6234
  `versions.latest: the npm lookup gave no answer${lookup.error ? ` (${lookup.error})` : ""}; latest and drift.cli_behind_latest are null`
@@ -6358,7 +6360,7 @@ async function runStatus(targetDir, packageVersion, options = {}) {
6358
6360
  }
6359
6361
  }
6360
6362
  console.log("");
6361
- const latestStr = versionResult.error ? "unable to check" : versions.latest ?? versions.files;
6363
+ const latestStr = versions.latest ?? "unable to check";
6362
6364
  let footer = ` Installed: ${versions.cli} Latest: ${latestStr}`;
6363
6365
  if (versionResult.updateAvailable) {
6364
6366
  footer += " (spell update available)";
@@ -6386,7 +6388,7 @@ async function runStatus(targetDir, packageVersion, options = {}) {
6386
6388
  }
6387
6389
  if (scope === "user") {
6388
6390
  console.log("");
6389
- for (const line of VSCODE_USER_TIER_NOTE) console.log(` ${line}`);
6391
+ for (const line2 of VSCODE_USER_TIER_NOTE) console.log(` ${line2}`);
6390
6392
  console.log(` ${CLAUDE_PRECEDENCE_NOTE}`);
6391
6393
  }
6392
6394
  }
@@ -6406,9 +6408,9 @@ async function collectAgents(targetDir, options = {}) {
6406
6408
  let resolved;
6407
6409
  try {
6408
6410
  resolved = await resolveRoster(targetDir, "repo", options.homeDir);
6409
- } catch (error) {
6410
- if (error instanceof AgentRosterNotFoundError) return { agents: null, warnings: [] };
6411
- throw error;
6411
+ } catch (error2) {
6412
+ if (error2 instanceof AgentRosterNotFoundError) return { agents: null, warnings: [] };
6413
+ throw error2;
6412
6414
  }
6413
6415
  const origin = resolved.fromUserTier ? "user" : "repo";
6414
6416
  const warnings = [];
@@ -6455,18 +6457,18 @@ function titleAfterId(rest) {
6455
6457
  return rest.replace(/^\s*[—–:-]+\s*/u, "").trim();
6456
6458
  }
6457
6459
  function parseDecisions(content) {
6458
- const text = content.replace(/\r\n?/g, "\n");
6460
+ const text2 = content.replace(/\r\n?/g, "\n");
6459
6461
  const positions = [];
6460
- let match;
6462
+ let match2;
6461
6463
  HEADING.lastIndex = 0;
6462
- while ((match = HEADING.exec(text)) !== null) {
6463
- positions.push({ id: `${match[1]}-${match[2]}`, heading: `${match[1]}-${match[2]}${match[3]}`, index: match.index });
6464
+ while ((match2 = HEADING.exec(text2)) !== null) {
6465
+ positions.push({ id: `${match2[1]}-${match2[2]}`, heading: `${match2[1]}-${match2[2]}${match2[3]}`, index: match2.index });
6464
6466
  }
6465
6467
  const entries = [];
6466
6468
  const warnings = [];
6467
6469
  positions.forEach(({ id, heading, index }, i) => {
6468
- const end = i + 1 < positions.length ? positions[i + 1].index : text.length;
6469
- const section = text.slice(index, end);
6470
+ const end = i + 1 < positions.length ? positions[i + 1].index : text2.length;
6471
+ const section = text2.slice(index, end);
6470
6472
  const dateMatch = /^\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/m.exec(section);
6471
6473
  const statusMatch = /^\*\*Status:\*\*\s*(\S+)/m.exec(section);
6472
6474
  if (!dateMatch && !statusMatch) {
@@ -6495,11 +6497,11 @@ async function collectDecisions(root) {
6495
6497
  let content;
6496
6498
  try {
6497
6499
  content = await readFile12(join14(root, "DECISIONS.md"), "utf8");
6498
- } catch (error) {
6499
- if (error.code === "ENOENT") return { decisions: null, warnings: [] };
6500
+ } catch (error2) {
6501
+ if (error2.code === "ENOENT") return { decisions: null, warnings: [] };
6500
6502
  return {
6501
6503
  decisions: null,
6502
- warnings: [`indexes.decisions: DECISIONS.md could not be read (${error instanceof Error ? error.message : String(error)})`]
6504
+ warnings: [`indexes.decisions: DECISIONS.md could not be read (${error2 instanceof Error ? error2.message : String(error2)})`]
6503
6505
  };
6504
6506
  }
6505
6507
  const { entries, warnings } = parseDecisions(content);
@@ -6513,12 +6515,12 @@ async function collectDecisions(root) {
6513
6515
  var ITEM = /^\s*- \[( |x|X)\] (.*)$/;
6514
6516
  var HEADING2 = /^#{1,6}\s+(.+?)\s*#*\s*$/;
6515
6517
  var TEXT_LIMIT = 200;
6516
- function normalizeItemText(text) {
6517
- return text.replace(/\s+/g, " ").trim();
6518
+ function normalizeItemText(text2) {
6519
+ return text2.replace(/\s+/g, " ").trim();
6518
6520
  }
6519
- function itemLocator(section, text) {
6521
+ function itemLocator(section, text2) {
6520
6522
  const slug = section === null ? "" : headingAnchor(section);
6521
- const hash = createHash3("sha256").update(normalizeItemText(text), "utf8").digest("hex").slice(0, 12);
6523
+ const hash = createHash3("sha256").update(normalizeItemText(text2), "utf8").digest("hex").slice(0, 12);
6522
6524
  return `${slug === "" ? "root" : slug}/${hash}`;
6523
6525
  }
6524
6526
  function parseBook(content) {
@@ -6526,22 +6528,22 @@ function parseBook(content) {
6526
6528
  let section = null;
6527
6529
  let open = 0;
6528
6530
  let done = 0;
6529
- for (const line of content.split(/\r?\n/)) {
6530
- const heading = HEADING2.exec(line);
6531
+ for (const line2 of content.split(/\r?\n/)) {
6532
+ const heading = HEADING2.exec(line2);
6531
6533
  if (heading) {
6532
6534
  section = heading[1];
6533
6535
  continue;
6534
6536
  }
6535
- const item = ITEM.exec(line);
6537
+ const item = ITEM.exec(line2);
6536
6538
  if (!item) continue;
6537
6539
  const checked = item[1] !== " ";
6538
- const text = item[2].trim();
6540
+ const text2 = item[2].trim();
6539
6541
  if (checked) done++;
6540
6542
  else open++;
6541
6543
  items.push({
6542
- text: text.length > TEXT_LIMIT ? text.slice(0, TEXT_LIMIT) : text,
6544
+ text: text2.length > TEXT_LIMIT ? text2.slice(0, TEXT_LIMIT) : text2,
6543
6545
  section,
6544
- locator: itemLocator(section, text),
6546
+ locator: itemLocator(section, text2),
6545
6547
  checked
6546
6548
  });
6547
6549
  }
@@ -6551,11 +6553,11 @@ async function collectBook(root, fileName) {
6551
6553
  let content;
6552
6554
  try {
6553
6555
  content = await readFile13(join15(root, fileName), "utf8");
6554
- } catch (error) {
6555
- if (error.code === "ENOENT") return { book: null, warnings: [] };
6556
+ } catch (error2) {
6557
+ if (error2.code === "ENOENT") return { book: null, warnings: [] };
6556
6558
  return {
6557
6559
  book: null,
6558
- warnings: [`indexes: ${fileName} could not be read (${error instanceof Error ? error.message : String(error)})`]
6560
+ warnings: [`indexes: ${fileName} could not be read (${error2 instanceof Error ? error2.message : String(error2)})`]
6559
6561
  };
6560
6562
  }
6561
6563
  return { book: parseBook(content), warnings: [] };
@@ -6606,16 +6608,16 @@ function featureTracking(frontmatter) {
6606
6608
  };
6607
6609
  }
6608
6610
  function parsePrd(content, displayPath) {
6609
- const text = content.replace(/\r\n?/g, "\n");
6610
- const heading = H1.exec(text);
6611
+ const text2 = content.replace(/\r\n?/g, "\n");
6612
+ const heading = H1.exec(text2);
6611
6613
  const empty = { title: heading ? heading[1] : null, status: null, implementation_status: null, tracking: null };
6612
- const match = FRONTMATTER.exec(text);
6613
- if (!match) return { fields: empty, warning: `features: ${displayPath} has no frontmatter` };
6614
+ const match2 = FRONTMATTER.exec(text2);
6615
+ if (!match2) return { fields: empty, warning: `features: ${displayPath} has no frontmatter` };
6614
6616
  let parsed;
6615
6617
  try {
6616
- parsed = parse2(match[1]);
6617
- } catch (error) {
6618
- const reason = error instanceof Error ? error.message.split("\n")[0] : String(error);
6618
+ parsed = parse2(match2[1]);
6619
+ } catch (error2) {
6620
+ const reason = error2 instanceof Error ? error2.message.split("\n")[0] : String(error2);
6619
6621
  return { fields: empty, warning: `features: ${displayPath} has malformed frontmatter (${reason})` };
6620
6622
  }
6621
6623
  if (!isRecord3(parsed)) return { fields: empty, warning: `features: ${displayPath} has frontmatter that is not a map` };
@@ -6635,11 +6637,11 @@ async function collectFeatures(root) {
6635
6637
  try {
6636
6638
  const entries = await readdir7(dir, { withFileTypes: true });
6637
6639
  slugs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
6638
- } catch (error) {
6639
- if (error.code === "ENOENT") return { features: null, warnings: [] };
6640
+ } catch (error2) {
6641
+ if (error2.code === "ENOENT") return { features: null, warnings: [] };
6640
6642
  return {
6641
6643
  features: null,
6642
- warnings: [`features: ${FEATURES_DIR}/ could not be read (${error instanceof Error ? error.message : String(error)})`]
6644
+ warnings: [`features: ${FEATURES_DIR}/ could not be read (${error2 instanceof Error ? error2.message : String(error2)})`]
6643
6645
  };
6644
6646
  }
6645
6647
  const warnings = [];
@@ -6649,9 +6651,9 @@ async function collectFeatures(root) {
6649
6651
  let content;
6650
6652
  try {
6651
6653
  content = await readFile14(join16(dir, slug, PRD_FILE), "utf8");
6652
- } catch (error) {
6653
- if (error.code === "ENOENT") continue;
6654
- warnings.push(`features: ${path5} could not be read (${error instanceof Error ? error.message : String(error)})`);
6654
+ } catch (error2) {
6655
+ if (error2.code === "ENOENT") continue;
6656
+ warnings.push(`features: ${path5} could not be read (${error2 instanceof Error ? error2.message : String(error2)})`);
6655
6657
  continue;
6656
6658
  }
6657
6659
  const { fields, warning } = parsePrd(content, path5);
@@ -6815,10 +6817,10 @@ function serializeState(doc) {
6815
6817
  `;
6816
6818
  }
6817
6819
  function toPosixRelative(root, target) {
6818
- const relative4 = path4.relative(root, target);
6819
- if (relative4 === "") return ".";
6820
- if (path4.isAbsolute(relative4)) return relative4.replace(/\\/g, "/");
6821
- return relative4.split(path4.sep).join("/");
6820
+ const relative5 = path4.relative(root, target);
6821
+ if (relative5 === "") return ".";
6822
+ if (path4.isAbsolute(relative5)) return relative5.replace(/\\/g, "/");
6823
+ return relative5.split(path4.sep).join("/");
6822
6824
  }
6823
6825
 
6824
6826
  // src/modules/state/git.ts
@@ -6840,10 +6842,10 @@ function stripUserinfo(url) {
6840
6842
  function parseWorktreeList(porcelain, root) {
6841
6843
  const worktrees = [];
6842
6844
  for (const block of porcelain.replace(/\r\n/g, "\n").split(/\n\n+/)) {
6843
- const lines = block.split("\n").filter((line) => line.length > 0);
6844
- const pathLine = lines.find((line) => line.startsWith("worktree "));
6845
+ const lines = block.split("\n").filter((line2) => line2.length > 0);
6846
+ const pathLine = lines.find((line2) => line2.startsWith("worktree "));
6845
6847
  if (!pathLine) continue;
6846
- const branchLine = lines.find((line) => line.startsWith("branch "));
6848
+ const branchLine = lines.find((line2) => line2.startsWith("branch "));
6847
6849
  const branch = branchLine ? branchLine.slice("branch ".length).replace(/^refs\/heads\//, "") : null;
6848
6850
  const absolute = resolveRealPath(pathLine.slice("worktree ".length));
6849
6851
  worktrees.push({ path: toPosixRelative(root, absolute), branch });
@@ -6853,12 +6855,12 @@ function parseWorktreeList(porcelain, root) {
6853
6855
  async function probe(read) {
6854
6856
  try {
6855
6857
  return { ok: true, value: await read() };
6856
- } catch (error) {
6857
- return { ok: false, error };
6858
+ } catch (error2) {
6859
+ return { ok: false, error: error2 };
6858
6860
  }
6859
6861
  }
6860
- function describe(error) {
6861
- return error instanceof Error ? error.message : String(error);
6862
+ function describe(error2) {
6863
+ return error2 instanceof Error ? error2.message : String(error2);
6862
6864
  }
6863
6865
  async function collectGit(cwd, root, options = {}) {
6864
6866
  const git = options.runGit ?? runGit;
@@ -6949,8 +6951,8 @@ async function collectGit(cwd, root, options = {}) {
6949
6951
  warnings
6950
6952
  };
6951
6953
  }
6952
- function isTimeout(error) {
6953
- return error instanceof Error && error.name === "GitTimeoutError";
6954
+ function isTimeout(error2) {
6955
+ return error2 instanceof Error && error2.name === "GitTimeoutError";
6954
6956
  }
6955
6957
 
6956
6958
  // src/modules/state/journal.ts
@@ -6959,13 +6961,13 @@ import { join as join17 } from "path";
6959
6961
  var JOURNAL_DIR = "journal";
6960
6962
  var ENTRY_NAME = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/;
6961
6963
  function journalTitle(content) {
6962
- const text = content.replace(/\r\n?/g, "\n");
6963
- const frontmatter = /^---\n([\s\S]*?)\n---/.exec(text);
6964
+ const text2 = content.replace(/\r\n?/g, "\n");
6965
+ const frontmatter = /^---\n([\s\S]*?)\n---/.exec(text2);
6964
6966
  if (frontmatter) {
6965
6967
  const title = /^title:\s*(.+)$/m.exec(frontmatter[1]);
6966
6968
  if (title) return title[1].trim().replace(/^(["'])(.*)\1$/, "$2");
6967
6969
  }
6968
- const heading = /^# (.+?)\s*$/m.exec(text);
6970
+ const heading = /^# (.+?)\s*$/m.exec(text2);
6969
6971
  return heading ? heading[1].trim() : null;
6970
6972
  }
6971
6973
  async function collectJournal(root) {
@@ -6974,29 +6976,29 @@ async function collectJournal(root) {
6974
6976
  try {
6975
6977
  const entries = await readdir8(dir, { withFileTypes: true });
6976
6978
  names = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
6977
- } catch (error) {
6978
- if (error.code === "ENOENT") return { journal: null, warnings: [] };
6979
+ } catch (error2) {
6980
+ if (error2.code === "ENOENT") return { journal: null, warnings: [] };
6979
6981
  return {
6980
6982
  journal: null,
6981
- warnings: [`indexes.journal: ${JOURNAL_DIR}/ could not be read (${error instanceof Error ? error.message : String(error)})`]
6983
+ warnings: [`indexes.journal: ${JOURNAL_DIR}/ could not be read (${error2 instanceof Error ? error2.message : String(error2)})`]
6982
6984
  };
6983
6985
  }
6984
6986
  const warnings = [];
6985
6987
  const journal = [];
6986
6988
  for (const name of names) {
6987
- const match = ENTRY_NAME.exec(name);
6988
- if (!match) {
6989
+ const match2 = ENTRY_NAME.exec(name);
6990
+ if (!match2) {
6989
6991
  warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} does not match YYYY-MM-DD-<slug>.md and was skipped`);
6990
6992
  continue;
6991
6993
  }
6992
6994
  let title = null;
6993
6995
  try {
6994
6996
  title = journalTitle(await readFile15(join17(dir, name), "utf8"));
6995
- } catch (error) {
6996
- warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} could not be read (${error instanceof Error ? error.message : String(error)})`);
6997
+ } catch (error2) {
6998
+ warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} could not be read (${error2 instanceof Error ? error2.message : String(error2)})`);
6997
6999
  }
6998
7000
  if (title === null) warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} has no title: frontmatter or # heading`);
6999
- journal.push({ date: match[1], slug: match[2], title, path: `${JOURNAL_DIR}/${name}` });
7001
+ journal.push({ date: match2[1], slug: match2[2], title, path: `${JOURNAL_DIR}/${name}` });
7000
7002
  }
7001
7003
  journal.sort((a, b) => a.date < b.date ? -1 : a.date > b.date ? 1 : a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
7002
7004
  return { journal, warnings };
@@ -7019,9 +7021,9 @@ function execFileWithTimeout(file, args, timeoutMs, options = {}) {
7019
7021
  encoding: "utf8",
7020
7022
  ...options.env ? { env: { ...process.env, ...options.env } } : {}
7021
7023
  },
7022
- (error, stdout, stderr) => {
7023
- if (error) {
7024
- reject(error);
7024
+ (error2, stdout, stderr) => {
7025
+ if (error2) {
7026
+ reject(error2);
7025
7027
  return;
7026
7028
  }
7027
7029
  resolve10({ stdout, stderr });
@@ -7071,14 +7073,14 @@ function parseAdoRemote(url) {
7071
7073
  if (ssh) return { org: ssh[1], project: decodeURIComponent(ssh[2]), repo: decodeURIComponent(ssh[3]) };
7072
7074
  return null;
7073
7075
  }
7074
- function describe2(error) {
7075
- if (error instanceof Error) {
7076
- const code = error.code;
7076
+ function describe2(error2) {
7077
+ if (error2 instanceof Error) {
7078
+ const code = error2.code;
7077
7079
  if (code === "ENOENT") return "the CLI is not installed";
7078
- if (error.killed) return `timed out after ${EXTERNAL_CLI_TIMEOUT_MS}ms`;
7079
- return error.message.split("\n")[0];
7080
+ if (error2.killed) return `timed out after ${EXTERNAL_CLI_TIMEOUT_MS}ms`;
7081
+ return error2.message.split("\n")[0];
7080
7082
  }
7081
- return String(error);
7083
+ return String(error2);
7082
7084
  }
7083
7085
  function isRecord5(value) {
7084
7086
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -7087,8 +7089,8 @@ async function lookupGitHub(branch, exec) {
7087
7089
  let stdout;
7088
7090
  try {
7089
7091
  ({ stdout } = await exec("gh", ["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,state"], EXTERNAL_CLI_TIMEOUT_MS));
7090
- } catch (error) {
7091
- return { pull_request: null, warnings: [`git.pull_request: gh pr list failed (${describe2(error)})`] };
7092
+ } catch (error2) {
7093
+ return { pull_request: null, warnings: [`git.pull_request: gh pr list failed (${describe2(error2)})`] };
7092
7094
  }
7093
7095
  let parsed;
7094
7096
  try {
@@ -7135,8 +7137,8 @@ async function lookupAdo(branch, remoteUrl, exec, launch) {
7135
7137
  EXTERNAL_CLI_TIMEOUT_MS,
7136
7138
  launch.env ? { env: launch.env } : {}
7137
7139
  ));
7138
- } catch (error) {
7139
- return { pull_request: null, warnings: [`git.pull_request: az repos pr list failed (${describe2(error)})`] };
7140
+ } catch (error2) {
7141
+ return { pull_request: null, warnings: [`git.pull_request: az repos pr list failed (${describe2(error2)})`] };
7140
7142
  }
7141
7143
  let parsed;
7142
7144
  try {
@@ -7243,17 +7245,17 @@ async function collectRegistry(root, manifest, options) {
7243
7245
  let content;
7244
7246
  try {
7245
7247
  content = await readFile16(join18(root, businessRoot, "registry.json"), "utf8");
7246
- } catch (error) {
7247
- const reason = error.code === "ENOENT" ? "is missing" : `could not be read (${error instanceof Error ? error.message : String(error)})`;
7248
+ } catch (error2) {
7249
+ const reason = error2.code === "ENOENT" ? "is missing" : `could not be read (${error2 instanceof Error ? error2.message : String(error2)})`;
7248
7250
  return { registry: null, warnings: [`registry: this repository is a hub but ${relativePath} ${reason}`] };
7249
7251
  }
7250
7252
  let raw;
7251
7253
  try {
7252
7254
  raw = JSON.parse(content);
7253
- } catch (error) {
7255
+ } catch (error2) {
7254
7256
  return {
7255
7257
  registry: null,
7256
- warnings: [`registry: ${relativePath} is not valid JSON (${error instanceof Error ? error.message : String(error)})`]
7258
+ warnings: [`registry: ${relativePath} is not valid JSON (${error2 instanceof Error ? error2.message : String(error2)})`]
7257
7259
  };
7258
7260
  }
7259
7261
  return { registry: parseRegistry(raw, options), warnings: [] };
@@ -7268,9 +7270,9 @@ async function guarded(section, warnings, run, fallback) {
7268
7270
  const result = await run();
7269
7271
  warnings.push(...result.warnings);
7270
7272
  return result.value;
7271
- } catch (error) {
7272
- if (error instanceof AgentConfigValidationError) throw error;
7273
- warnings.push(`${section}: collector failed (${error instanceof Error ? error.message : String(error)}); the section is null`);
7273
+ } catch (error2) {
7274
+ if (error2 instanceof AgentConfigValidationError) throw error2;
7275
+ warnings.push(`${section}: collector failed (${error2 instanceof Error ? error2.message : String(error2)}); the section is null`);
7274
7276
  return fallback;
7275
7277
  }
7276
7278
  }
@@ -7403,9 +7405,9 @@ async function collectState(cwd, packageVersion, options) {
7403
7405
 
7404
7406
  // src/commands/state.ts
7405
7407
  var USAGE_POINTER = "spell state prints one JSON document to stdout: run `spell state --json` (spell status is the human view).";
7406
- function writeStdout(text) {
7408
+ function writeStdout(text2) {
7407
7409
  return new Promise((resolve10, reject) => {
7408
- process.stdout.write(text, (error) => error ? reject(error) : resolve10());
7410
+ process.stdout.write(text2, (error2) => error2 ? reject(error2) : resolve10());
7409
7411
  });
7410
7412
  }
7411
7413
  async function runState(targetDir, packageVersion, options = {}, deps = {}) {
@@ -7415,28 +7417,28 @@ async function runState(targetDir, packageVersion, options = {}, deps = {}) {
7415
7417
  return;
7416
7418
  }
7417
7419
  const collect = deps.collect ?? collectState;
7418
- let text;
7420
+ let text2;
7419
7421
  try {
7420
7422
  const doc = await collect(targetDir, packageVersion, {
7421
7423
  online: options.online === true,
7422
7424
  includeLocalPaths: options.includeLocalPaths === true
7423
7425
  });
7424
- text = serializeState(doc);
7425
- } catch (error) {
7426
- if (error instanceof ManifestNotFoundError) {
7426
+ text2 = serializeState(doc);
7427
+ } catch (error2) {
7428
+ if (error2 instanceof ManifestNotFoundError) {
7427
7429
  console.error('Not initialized. Run "spell init" first.');
7428
- } else if (error instanceof AgentConfigValidationError) {
7429
- console.error(error.message);
7430
+ } else if (error2 instanceof AgentConfigValidationError) {
7431
+ console.error(error2.message);
7430
7432
  } else {
7431
- console.error(`spell state: ${error instanceof Error ? error.message : String(error)}`);
7433
+ console.error(`spell state: ${error2 instanceof Error ? error2.message : String(error2)}`);
7432
7434
  }
7433
7435
  process.exitCode = 1;
7434
7436
  return;
7435
7437
  }
7436
7438
  try {
7437
- await (deps.write ?? writeStdout)(text);
7438
- } catch (error) {
7439
- console.error(`spell state: stdout is not writable (${error instanceof Error ? error.message : String(error)})`);
7439
+ await (deps.write ?? writeStdout)(text2);
7440
+ } catch (error2) {
7441
+ console.error(`spell state: stdout is not writable (${error2 instanceof Error ? error2.message : String(error2)})`);
7440
7442
  process.exitCode = 1;
7441
7443
  }
7442
7444
  }
@@ -7588,7 +7590,7 @@ async function uninstallUserTier(options, storeRoot, manifest) {
7588
7590
  previous: manifest.fanout,
7589
7591
  dryRun: true
7590
7592
  });
7591
- for (const line of describeFanoutOutcomes(preview.outcomes, true)) console.log(line);
7593
+ for (const line2 of describeFanoutOutcomes(preview.outcomes, true)) console.log(line2);
7592
7594
  console.log(`[dry-run] Would remove: ${storeRoot}/.arcane.json`);
7593
7595
  console.log(
7594
7596
  `
@@ -7626,7 +7628,7 @@ async function uninstallUserTier(options, storeRoot, manifest) {
7626
7628
  await rm5(join19(storeRoot, "agents.yaml"), { force: true });
7627
7629
  await removeDirectoryIfEmpty(join19(storeRoot, USER_TIER_SPELLS_DIR));
7628
7630
  await removeDirectoryIfEmpty(storeRoot);
7629
- for (const line of describeFanoutOutcomes(fanout.outcomes)) console.log(` ${line}`);
7631
+ for (const line2 of describeFanoutOutcomes(fanout.outcomes)) console.log(` ${line2}`);
7630
7632
  const pruned = fanout.outcomes.filter((o) => o.status === "pruned").length;
7631
7633
  const kept = fanout.outcomes.filter((o) => o.status === "kept-edited").length;
7632
7634
  console.log(
@@ -7929,9 +7931,9 @@ async function fetchAdoMergeTypePolicies(org, project, repoName, branch, launch
7929
7931
  }
7930
7932
  }
7931
7933
  function parseGitHubRemote(url) {
7932
- const match = /github\.com[/:]([^/]+)\/([^/.]+?)(?:\.git)?\/?$/.exec(url);
7933
- if (!match) return null;
7934
- const [, owner, repo] = match;
7934
+ const match2 = /github\.com[/:]([^/]+)\/([^/.]+?)(?:\.git)?\/?$/.exec(url);
7935
+ if (!match2) return null;
7936
+ const [, owner, repo] = match2;
7935
7937
  if (!owner || !repo) return null;
7936
7938
  return { owner, repo };
7937
7939
  }
@@ -7946,9 +7948,9 @@ function decodeAdoSegment(raw) {
7946
7948
  return decoded;
7947
7949
  }
7948
7950
  function parseAdoRemote2(url) {
7949
- const match = /dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?]+)/.exec(url) ?? /([^./]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/?]+)/.exec(url);
7950
- if (!match) return null;
7951
- const [, rawOrg, rawProject, rawRepo] = match;
7951
+ const match2 = /dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?]+)/.exec(url) ?? /([^./]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/?]+)/.exec(url);
7952
+ if (!match2) return null;
7953
+ const [, rawOrg, rawProject, rawRepo] = match2;
7952
7954
  if (!rawOrg || !rawProject || !rawRepo) return null;
7953
7955
  const org = decodeAdoSegment(rawOrg);
7954
7956
  const project = decodeAdoSegment(rawProject);
@@ -7975,28 +7977,28 @@ function stripFrontmatter(content) {
7975
7977
  const block = FRONTMATTER_PATTERN2.exec(content);
7976
7978
  return block ? content.slice(block[0].length) : content;
7977
7979
  }
7978
- function stripFencedCode(text) {
7980
+ function stripFencedCode(text2) {
7979
7981
  const kept = [];
7980
7982
  let fence = null;
7981
- for (const line of text.split(/\r?\n/)) {
7983
+ for (const line2 of text2.split(/\r?\n/)) {
7982
7984
  if (fence) {
7983
- const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line);
7985
+ const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line2);
7984
7986
  if (close && close[1][0] === fence.char && close[1].length >= fence.length) fence = null;
7985
7987
  kept.push("");
7986
7988
  continue;
7987
7989
  }
7988
- const open = /^ {0,3}(`{3,}|~{3,})/.exec(line);
7989
- if (open && !(open[1][0] === "`" && line.slice(open[0].length).includes("`"))) {
7990
+ const open = /^ {0,3}(`{3,}|~{3,})/.exec(line2);
7991
+ if (open && !(open[1][0] === "`" && line2.slice(open[0].length).includes("`"))) {
7990
7992
  fence = { char: open[1][0], length: open[1].length };
7991
7993
  kept.push("");
7992
7994
  continue;
7993
7995
  }
7994
- kept.push(line);
7996
+ kept.push(line2);
7995
7997
  }
7996
7998
  return kept.join("\n");
7997
7999
  }
7998
- function stripInlineCode(text) {
7999
- return text.split(/\n[ \t]*\n/).map((paragraph) => {
8000
+ function stripInlineCode(text2) {
8001
+ return text2.split(/\n[ \t]*\n/).map((paragraph) => {
8000
8002
  let out = "";
8001
8003
  let i = 0;
8002
8004
  while (i < paragraph.length) {
@@ -8037,7 +8039,7 @@ function stripInlineCode(text) {
8037
8039
  function findPlaceholderTokens(content) {
8038
8040
  const prose = stripInlineCode(stripFencedCode(stripFrontmatter(content)));
8039
8041
  const seen = /* @__PURE__ */ new Set();
8040
- for (const match of prose.matchAll(PLACEHOLDER_PATTERN)) seen.add(`{${match[1]}}`);
8042
+ for (const match2 of prose.matchAll(PLACEHOLDER_PATTERN)) seen.add(`{${match2[1]}}`);
8041
8043
  return [...seen];
8042
8044
  }
8043
8045
  function parseRuntimePlaceholders(standardContent) {
@@ -8046,8 +8048,8 @@ function parseRuntimePlaceholders(standardContent) {
8046
8048
  if (start === -1 || end === -1 || end < start) return null;
8047
8049
  const region = standardContent.slice(start + RUNTIME_PLACEHOLDERS_START.length, end);
8048
8050
  const tokens = /* @__PURE__ */ new Set();
8049
- for (const match of region.matchAll(/^[ \t]*[-*][ \t]+`(\{[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*\})`/gm)) {
8050
- tokens.add(match[1]);
8051
+ for (const match2 of region.matchAll(/^[ \t]*[-*][ \t]+`(\{[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*\})`/gm)) {
8052
+ tokens.add(match2[1]);
8051
8053
  }
8052
8054
  return tokens;
8053
8055
  }
@@ -8984,9 +8986,9 @@ function parseTermsFileContent(raw) {
8984
8986
  async function readTermsFile(filePath) {
8985
8987
  try {
8986
8988
  return parseTermsFileContent(await readFile21(filePath, "utf8"));
8987
- } catch (error) {
8989
+ } catch (error2) {
8988
8990
  console.warn(
8989
- `\u26A0 arcane: could not read --terms-file "${filePath}": ${error.message}`
8991
+ `\u26A0 arcane: could not read --terms-file "${filePath}": ${error2.message}`
8990
8992
  );
8991
8993
  return [];
8992
8994
  }
@@ -9249,8 +9251,8 @@ var ChunkedSecretStore = class {
9249
9251
  guard(operation, fn) {
9250
9252
  try {
9251
9253
  return fn();
9252
- } catch (error) {
9253
- throw new KeychainUnavailableError(operation, error);
9254
+ } catch (error2) {
9255
+ throw new KeychainUnavailableError(operation, error2);
9254
9256
  }
9255
9257
  }
9256
9258
  /** Reads the stored secret. Never throws for bad data; throws only when the keychain itself fails. */
@@ -9360,9 +9362,9 @@ function resultPagePath(kind) {
9360
9362
  return join24("sign-in", `sign-in-callback-${kind}.html`);
9361
9363
  }
9362
9364
  function styleHashes(html) {
9363
- return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)].map((match) => {
9364
- const text = match[1].replace(/\r\n?/g, "\n");
9365
- return `'sha256-${createHash5("sha256").update(text, "utf8").digest("base64")}'`;
9365
+ return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)].map((match2) => {
9366
+ const text2 = match2[1].replace(/\r\n?/g, "\n");
9367
+ return `'sha256-${createHash5("sha256").update(text2, "utf8").digest("base64")}'`;
9366
9368
  });
9367
9369
  }
9368
9370
  function resultPageHeaders(html) {
@@ -9446,8 +9448,8 @@ async function loadIdentityConfiguration(environment, options = {}) {
9446
9448
  const response = await fetchImpl(environment.discoveryUrl, { redirect: "manual" });
9447
9449
  if (!response.ok) throw new Error(`discovery returned HTTP ${response.status}`);
9448
9450
  metadata = await response.json();
9449
- } catch (error) {
9450
- throw new IdentityUnreachableError(environment.signInHost, error);
9451
+ } catch (error2) {
9452
+ throw new IdentityUnreachableError(environment.signInHost, error2);
9451
9453
  }
9452
9454
  if (typeof metadata.issuer !== "string" || !metadata.token_endpoint || !metadata.authorization_endpoint) {
9453
9455
  throw new IdentityUnreachableError(environment.signInHost, new Error("discovery document incomplete"));
@@ -9572,8 +9574,8 @@ async function browserLogin(config, options) {
9572
9574
  const result = toTokenResult(response);
9573
9575
  await options.onValidated?.(result);
9574
9576
  redirectToResult({ kind: "success", result });
9575
- }).catch((error) => {
9576
- redirectToResult({ kind: "failure", error });
9577
+ }).catch((error2) => {
9578
+ redirectToResult({ kind: "failure", error: error2 });
9577
9579
  });
9578
9580
  });
9579
9581
  timer = setTimeout(() => reject(new LoginTimeoutError()), timeoutMs);
@@ -9605,7 +9607,7 @@ function openInDefaultBrowser(url) {
9605
9607
  const [command, args] = process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
9606
9608
  return new Promise((resolve10) => {
9607
9609
  try {
9608
- const child = execFile3(command, args, { windowsHide: true }, (error) => resolve10(!error));
9610
+ const child = execFile3(command, args, { windowsHide: true }, (error2) => resolve10(!error2));
9609
9611
  child.on("error", () => resolve10(false));
9610
9612
  } catch {
9611
9613
  resolve10(false);
@@ -9711,11 +9713,11 @@ var FileSecretBackend = class {
9711
9713
  writeFileSync(tmp, JSON.stringify(entries), { encoding: "utf8", mode: 384 });
9712
9714
  try {
9713
9715
  this.restrict(tmp);
9714
- } catch (error) {
9716
+ } catch (error2) {
9715
9717
  rmSync(tmp, { force: true });
9716
9718
  throw new Error(
9717
9719
  "Could not restrict the session file to your account, so it was not written.",
9718
- { cause: error }
9720
+ { cause: error2 }
9719
9721
  );
9720
9722
  }
9721
9723
  renameSync(tmp, this.filePath);
@@ -9770,9 +9772,9 @@ function createSessionStorage(options = {}) {
9770
9772
  try {
9771
9773
  const fromKeychain = interpret(keychain.load(), "keychain");
9772
9774
  if (fromKeychain) return fromKeychain;
9773
- } catch (error) {
9774
- if (!(error instanceof KeychainUnavailableError)) throw error;
9775
- keychainError = error;
9775
+ } catch (error2) {
9776
+ if (!(error2 instanceof KeychainUnavailableError)) throw error2;
9777
+ keychainError = error2;
9776
9778
  }
9777
9779
  if (fileBackend.exists()) {
9778
9780
  const fromFile = interpret(file.load(), "file");
@@ -9800,8 +9802,8 @@ function createSessionStorage(options = {}) {
9800
9802
  const hadKeychainSession = keychain.load().state !== "signed-out";
9801
9803
  keychain.clear();
9802
9804
  if (hadKeychainSession) removed.push("keychain");
9803
- } catch (error) {
9804
- keychainError = error;
9805
+ } catch (error2) {
9806
+ keychainError = error2;
9805
9807
  }
9806
9808
  if (fileBackend.exists()) {
9807
9809
  fileBackend.remove();
@@ -9831,8 +9833,8 @@ function defaultLoginDeps(assetsDir) {
9831
9833
  }
9832
9834
  var INSECURE_STORAGE_WARNING = "Insecure storage: the session is kept in a file protected only by your account's file permissions, not in the OS keychain. Run `spell logout` to remove it.";
9833
9835
  var MISSING_RESULT_PAGES_WARNING = "This arcane-cli install is missing its sign-in result pages (assets/sign-in/), so the browser will show a plain page. Reinstall the package to restore them.";
9834
- function safeErrorText(error) {
9835
- const message = error instanceof Error ? error.message : String(error);
9836
+ function safeErrorText(error2) {
9837
+ const message = error2 instanceof Error ? error2.message : String(error2);
9836
9838
  return message.split("\n")[0].replace(/([?&](?:code|state|id_token|access_token|refresh_token)=)[^&\s]+/gi, "$1[redacted]");
9837
9839
  }
9838
9840
  function toSessionRecord(result, environment) {
@@ -9854,13 +9856,13 @@ async function runLogin(options, deps) {
9854
9856
  let environment;
9855
9857
  try {
9856
9858
  environment = deps.environment();
9857
- } catch (error) {
9858
- if (error instanceof UnknownEnvironmentError) {
9859
- console.error(error.message);
9859
+ } catch (error2) {
9860
+ if (error2 instanceof UnknownEnvironmentError) {
9861
+ console.error(error2.message);
9860
9862
  process.exit(1);
9861
9863
  return;
9862
9864
  }
9863
- throw error;
9865
+ throw error2;
9864
9866
  }
9865
9867
  if (!options.deviceCode && !deps.isInteractive()) {
9866
9868
  console.error(
@@ -9872,9 +9874,9 @@ async function runLogin(options, deps) {
9872
9874
  let config;
9873
9875
  try {
9874
9876
  config = await deps.loadConfiguration(environment);
9875
- } catch (error) {
9877
+ } catch (error2) {
9876
9878
  console.error(
9877
- error instanceof IdentityUnreachableError ? `${error.message} Check your network connection and try again.` : `Could not prepare the sign-in: ${safeErrorText(error)}`
9879
+ error2 instanceof IdentityUnreachableError ? `${error2.message} Check your network connection and try again.` : `Could not prepare the sign-in: ${safeErrorText(error2)}`
9878
9880
  );
9879
9881
  process.exit(1);
9880
9882
  return;
@@ -9910,15 +9912,15 @@ async function runLogin(options, deps) {
9910
9912
  });
9911
9913
  if (!record) throw new Error("The browser sign-in ended without storing a session.");
9912
9914
  }
9913
- } catch (error) {
9914
- if (error instanceof KeychainUnavailableError) {
9915
+ } catch (error2) {
9916
+ if (error2 instanceof KeychainUnavailableError) {
9915
9917
  console.error(
9916
- `${error.message} Nothing was stored. If this machine has no keychain, run \`spell login --insecure-storage\` to keep the session in a file instead (protected only by your account's file permissions).`
9918
+ `${error2.message} Nothing was stored. If this machine has no keychain, run \`spell login --insecure-storage\` to keep the session in a file instead (protected only by your account's file permissions).`
9917
9919
  );
9918
- } else if (error instanceof LoginTimeoutError || error instanceof LoginDeniedError) {
9919
- console.error(`${error.message} Nothing was changed.`);
9920
+ } else if (error2 instanceof LoginTimeoutError || error2 instanceof LoginDeniedError) {
9921
+ console.error(`${error2.message} Nothing was changed.`);
9920
9922
  } else {
9921
- console.error(`The sign-in did not complete: ${safeErrorText(error)}. Nothing was changed.`);
9923
+ console.error(`The sign-in did not complete: ${safeErrorText(error2)}. Nothing was changed.`);
9922
9924
  }
9923
9925
  process.exit(1);
9924
9926
  return;
@@ -9933,21 +9935,21 @@ function runLogout(storage = createSessionStorage()) {
9933
9935
  let removed;
9934
9936
  try {
9935
9937
  removed = storage.clear().removed;
9936
- } catch (error) {
9937
- if (error instanceof SessionClearError) {
9938
- const fileResult = error.removed.includes("file") ? "The insecure session file was removed. " : "";
9938
+ } catch (error2) {
9939
+ if (error2 instanceof SessionClearError) {
9940
+ const fileResult = error2.removed.includes("file") ? "The insecure session file was removed. " : "";
9939
9941
  console.error(
9940
- `${fileResult}${error.keychainError.message} Any session in the OS keychain could not be verified or removed.`
9942
+ `${fileResult}${error2.keychainError.message} Any session in the OS keychain could not be verified or removed.`
9941
9943
  );
9942
9944
  process.exit(1);
9943
9945
  return;
9944
9946
  }
9945
- if (error instanceof KeychainUnavailableError) {
9946
- console.error(`${error.message} The session could not be removed from it.`);
9947
+ if (error2 instanceof KeychainUnavailableError) {
9948
+ console.error(`${error2.message} The session could not be removed from it.`);
9947
9949
  process.exit(1);
9948
9950
  return;
9949
9951
  }
9950
- throw error;
9952
+ throw error2;
9951
9953
  }
9952
9954
  if (removed.length === 0) {
9953
9955
  printSuccess("Nothing was stored; you were already signed out.");
@@ -9971,13 +9973,13 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
9971
9973
  let loaded;
9972
9974
  try {
9973
9975
  loaded = deps.storage.load();
9974
- } catch (error) {
9975
- if (error instanceof KeychainUnavailableError) {
9976
- console.error(`${error.message} Cannot tell whether you are signed in.`);
9976
+ } catch (error2) {
9977
+ if (error2 instanceof KeychainUnavailableError) {
9978
+ console.error(`${error2.message} Cannot tell whether you are signed in.`);
9977
9979
  process.exit(1);
9978
9980
  return;
9979
9981
  }
9980
- throw error;
9982
+ throw error2;
9981
9983
  }
9982
9984
  if (loaded.state === "signed-out") {
9983
9985
  console.error("Not signed in. Run `spell login`.");
@@ -10009,9 +10011,9 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
10009
10011
  const fresh = await deps.refreshSession(config, record.refreshToken, SIGN_IN_SCOPES);
10010
10012
  deps.storage.save(toSessionRecord(fresh, environment), { insecure: source === "file" });
10011
10013
  printSuccess(`Verified with the identity service at ${environment.signInHost}; the session is active.`);
10012
- } catch (error) {
10014
+ } catch (error2) {
10013
10015
  console.error(
10014
- `The identity service did not accept the session (${safeErrorText(error)}). Run \`spell login\` again.`
10016
+ `The identity service did not accept the session (${safeErrorText(error2)}). Run \`spell login\` again.`
10015
10017
  );
10016
10018
  process.exit(1);
10017
10019
  }
@@ -10031,11 +10033,11 @@ import { join as join26 } from "path";
10031
10033
 
10032
10034
  // src/modules/show-report/plan-parser.ts
10033
10035
  function parseFrontmatter(content) {
10034
- const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
10035
- if (!match) return {};
10036
+ const match2 = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
10037
+ if (!match2) return {};
10036
10038
  const fields = {};
10037
- for (const line of match[1].split(/\r?\n/)) {
10038
- const kv = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line);
10039
+ for (const line2 of match2[1].split(/\r?\n/)) {
10040
+ const kv = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line2);
10039
10041
  if (kv) fields[kv[1]] = kv[2].trim();
10040
10042
  }
10041
10043
  return fields;
@@ -10103,7 +10105,7 @@ function parseEpics(content) {
10103
10105
  }
10104
10106
  function extractSection(content, headingPrefix) {
10105
10107
  const lines = content.split(/\r?\n/);
10106
- const startIndex = lines.findIndex((line) => line.startsWith(headingPrefix));
10108
+ const startIndex = lines.findIndex((line2) => line2.startsWith(headingPrefix));
10107
10109
  if (startIndex === -1) return null;
10108
10110
  let endIndex = lines.length;
10109
10111
  for (let i = startIndex + 1; i < lines.length; i += 1) {
@@ -10125,11 +10127,11 @@ function parseParkedSection(content) {
10125
10127
  }));
10126
10128
  }
10127
10129
  return unwrapBullets(body).flatMap((bullet) => {
10128
- const match = /^\*\*(.+?)\*\*(.*?)\s*—\s*(.+)$/.exec(bullet);
10129
- if (!match) return [];
10130
- const tail = match[2].replace(/\([^)]*\)/g, "").replace(/\s+/g, " ").trim();
10131
- const title = tail ? `${match[1].trim()} ${tail}` : match[1].trim();
10132
- return [{ title: stripMarkdown(title), reason: stripMarkdown(match[3].trim()) }];
10130
+ const match2 = /^\*\*(.+?)\*\*(.*?)\s*—\s*(.+)$/.exec(bullet);
10131
+ if (!match2) return [];
10132
+ const tail = match2[2].replace(/\([^)]*\)/g, "").replace(/\s+/g, " ").trim();
10133
+ const title = tail ? `${match2[1].trim()} ${tail}` : match2[1].trim();
10134
+ return [{ title: stripMarkdown(title), reason: stripMarkdown(match2[3].trim()) }];
10133
10135
  });
10134
10136
  }
10135
10137
  function unwrapBullets(body) {
@@ -10139,12 +10141,12 @@ function unwrapBullets(body) {
10139
10141
  if (current) bullets.push(current.join(" ").replace(/\s+/g, " ").trim());
10140
10142
  current = null;
10141
10143
  };
10142
- for (const line of body.split(/\r?\n/)) {
10143
- if (/^- /.test(line)) {
10144
+ for (const line2 of body.split(/\r?\n/)) {
10145
+ if (/^- /.test(line2)) {
10144
10146
  flush();
10145
- current = [line.slice(2).trim()];
10147
+ current = [line2.slice(2).trim()];
10146
10148
  } else if (current !== null) {
10147
- if (/^\s+\S/.test(line)) current.push(line.trim());
10149
+ if (/^\s+\S/.test(line2)) current.push(line2.trim());
10148
10150
  else flush();
10149
10151
  }
10150
10152
  }
@@ -10162,8 +10164,8 @@ function computeCoverageOutcome(content) {
10162
10164
  const dispositioned = total - parkedCount;
10163
10165
  return `${total} of ${total} items dispositioned \u2014 ${dispositioned} routed to an epic, ${parkedCount} parked`;
10164
10166
  }
10165
- function stripMarkdown(text) {
10166
- return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*/g, "").replace(/`/g, "").trim();
10167
+ function stripMarkdown(text2) {
10168
+ return text2.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*/g, "").replace(/`/g, "").trim();
10167
10169
  }
10168
10170
 
10169
10171
  // src/modules/show-report/queue-parser.ts
@@ -10209,8 +10211,8 @@ function parseVerificationLedgerSection(content, programNameWords) {
10209
10211
  }
10210
10212
  return rows;
10211
10213
  }
10212
- function stripMarkdown2(text) {
10213
- return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*/g, "").replace(/`/g, "").trim();
10214
+ function stripMarkdown2(text2) {
10215
+ return text2.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*/g, "").replace(/`/g, "").trim();
10214
10216
  }
10215
10217
 
10216
10218
  // src/modules/show-report/sources.ts
@@ -10248,8 +10250,8 @@ async function getCloseCommit(cwd, completedDate, generatedOutputs = []) {
10248
10250
  }
10249
10251
  const log = await runGitTextOrNull(cwd, ["log", "--format=%H%x09%cs", "HEAD", ...excludeOutputs(generatedOutputs)]);
10250
10252
  if (log === null || log === "") return null;
10251
- for (const line of log.split("\n")) {
10252
- const [sha, landed] = line.split(" ");
10253
+ for (const line2 of log.split("\n")) {
10254
+ const [sha, landed] = line2.split(" ");
10253
10255
  if (sha && landed && landed <= completedDate) return sha;
10254
10256
  }
10255
10257
  return null;
@@ -10346,9 +10348,9 @@ function buildNeedsYou(queueEntries) {
10346
10348
  return entry;
10347
10349
  });
10348
10350
  }
10349
- function truncateAtWord(text, limit) {
10350
- if (text.length <= limit) return text;
10351
- const window = text.slice(0, limit - 1);
10351
+ function truncateAtWord(text2, limit) {
10352
+ if (text2.length <= limit) return text2;
10353
+ const window = text2.slice(0, limit - 1);
10352
10354
  const lastSpace = window.lastIndexOf(" ");
10353
10355
  const head = lastSpace > 0 ? window.slice(0, lastSpace) : window;
10354
10356
  return `${head.replace(/[\s,;:.—-]+$/, "")}\u2026`;
@@ -10474,11 +10476,11 @@ var CATEGORY_ORDER = [
10474
10476
  "docs",
10475
10477
  "platform"
10476
10478
  ];
10477
- function escapeHtml(text) {
10478
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
10479
+ function escapeHtml(text2) {
10480
+ return text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
10479
10481
  }
10480
- function inlineMarkupToHtml(text) {
10481
- const escaped = escapeHtml(text);
10482
+ function inlineMarkupToHtml(text2) {
10483
+ const escaped = escapeHtml(text2);
10482
10484
  const withLinks = escaped.replace(
10483
10485
  /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,
10484
10486
  '<a href="$2">$1</a>'
@@ -10550,12 +10552,12 @@ function applyReportTheme(html, theme) {
10550
10552
  if (!radioTagPattern(theme).test(html)) return { html, applied: false };
10551
10553
  let result = html;
10552
10554
  for (const id of REPORT_THEMES) {
10553
- const match = radioTagPattern(id).exec(result);
10554
- if (!match) continue;
10555
- const original = match[0];
10555
+ const match2 = radioTagPattern(id).exec(result);
10556
+ if (!match2) continue;
10557
+ const original = match2[0];
10556
10558
  const replaced = id === theme ? withChecked(original) : withoutChecked(original);
10557
10559
  if (replaced !== original) {
10558
- result = result.slice(0, match.index) + replaced + result.slice(match.index + original.length);
10560
+ result = result.slice(0, match2.index) + replaced + result.slice(match2.index + original.length);
10559
10561
  }
10560
10562
  }
10561
10563
  return { html: result, applied: true };
@@ -10598,9 +10600,9 @@ async function discoverPrograms(rootDir) {
10598
10600
  let entries;
10599
10601
  try {
10600
10602
  entries = (await readdir13(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
10601
- } catch (error) {
10602
- if (error.code === "ENOENT") return { programs: [], skipped: [] };
10603
- throw error;
10603
+ } catch (error2) {
10604
+ if (error2.code === "ENOENT") return { programs: [], skipped: [] };
10605
+ throw error2;
10604
10606
  }
10605
10607
  const programs = [];
10606
10608
  const skipped = [];
@@ -10622,9 +10624,9 @@ function normalizeLineEndings2(content) {
10622
10624
  async function readOptionalFile(path5) {
10623
10625
  try {
10624
10626
  return await readFile24(path5, "utf8");
10625
- } catch (error) {
10626
- if (error.code === "ENOENT") return null;
10627
- throw error;
10627
+ } catch (error2) {
10628
+ if (error2.code === "ENOENT") return null;
10629
+ throw error2;
10628
10630
  }
10629
10631
  }
10630
10632
  async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
@@ -10755,11 +10757,1400 @@ async function runReport(targetDir, options = {}, assetsDir) {
10755
10757
  for (const path5 of result.repaired) console.log(` ${path5}`);
10756
10758
  }
10757
10759
 
10760
+ // src/modules/agent-ref.ts
10761
+ import { randomBytes as randomBytes2 } from "crypto";
10762
+ var REF_TYPES = ["agent", "template", "session"];
10763
+ var UUID_V7 = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
10764
+ var ANY_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
10765
+ function createUuidV7Generator(options = {}) {
10766
+ const now = options.now ?? Date.now;
10767
+ const random = options.random ?? ((n) => randomBytes2(n));
10768
+ let lastMs = -1;
10769
+ let counter = 0;
10770
+ return () => {
10771
+ let ms = Math.floor(now());
10772
+ const bytes = random(10);
10773
+ if (ms > lastMs) {
10774
+ counter = (bytes[0] << 8 | bytes[1]) & 2047;
10775
+ } else {
10776
+ ms = lastMs;
10777
+ counter += 1;
10778
+ if (counter > 4095) {
10779
+ ms += 1;
10780
+ counter = (bytes[0] << 8 | bytes[1]) & 2047;
10781
+ }
10782
+ }
10783
+ lastMs = ms;
10784
+ const out = new Uint8Array(16);
10785
+ let t = ms;
10786
+ for (let i = 5; i >= 0; i -= 1) {
10787
+ out[i] = t % 256;
10788
+ t = Math.floor(t / 256);
10789
+ }
10790
+ out[6] = 112 | counter >> 8 & 15;
10791
+ out[7] = counter & 255;
10792
+ out[8] = 128 | bytes[2] & 63;
10793
+ for (let i = 9; i < 16; i += 1) out[i] = bytes[i - 6];
10794
+ const hex = Array.from(out, (b) => b.toString(16).padStart(2, "0")).join("");
10795
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
10796
+ };
10797
+ }
10798
+ var defaultUuidV7 = createUuidV7Generator();
10799
+ function uuidv7() {
10800
+ return defaultUuidV7();
10801
+ }
10802
+ function mintRef(type, generate = uuidv7) {
10803
+ if (!REF_TYPES.includes(type)) {
10804
+ throw new Error(`"${String(type)}" is not a registered reference type`);
10805
+ }
10806
+ return `arcane:${type}:${generate()}`;
10807
+ }
10808
+ function mintSessionRef() {
10809
+ return mintRef("session");
10810
+ }
10811
+ function parseRef(text2, expected) {
10812
+ if (typeof text2 !== "string") return { ok: false, reason: "a reference must be a string" };
10813
+ if (text2.length === 0) return { ok: false, reason: "a reference must not be empty" };
10814
+ if (text2 !== text2.trim()) return { ok: false, reason: "a reference must not carry surrounding whitespace" };
10815
+ if (text2 !== text2.toLowerCase()) return { ok: false, reason: "a reference must be lowercase" };
10816
+ const parts = text2.split(":");
10817
+ if (parts.length !== 3 || parts[0] !== "arcane") {
10818
+ return { ok: false, reason: 'a reference must have the form "arcane:<type>:<uuid>"' };
10819
+ }
10820
+ const type = parts[1];
10821
+ const uuid = parts[2];
10822
+ if (!REF_TYPES.includes(type)) {
10823
+ return { ok: false, reason: `"${type}" is not a registered reference type` };
10824
+ }
10825
+ if (uuid.startsWith("{") || uuid.endsWith("}")) {
10826
+ return { ok: false, reason: "a reference's uuid must not be braced" };
10827
+ }
10828
+ if (!ANY_UUID.test(uuid)) return { ok: false, reason: "a reference's uuid must be in canonical hex form" };
10829
+ if (!UUID_V7.test(uuid)) return { ok: false, reason: "a reference's uuid must be a version 7 UUID" };
10830
+ if (expected !== void 0 && type !== expected) {
10831
+ return { ok: false, reason: `expected a ${expected} reference, got one of type ${type}` };
10832
+ }
10833
+ return { ok: true, type, uuid };
10834
+ }
10835
+
10836
+ // src/modules/presence/client.ts
10837
+ import { request } from "http";
10838
+
10839
+ // src/modules/presence/store.ts
10840
+ import { randomBytes as randomBytes3 } from "crypto";
10841
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
10842
+ import { join as join29, relative as relative4 } from "path";
10843
+
10844
+ // src/modules/presence/types.ts
10845
+ var PRESENCE_CONTRACT = "v0";
10846
+ var DEFAULT_PORT = 27226;
10847
+ var PRESENCE_ENV = { port: "ARCANE_PRESENCE_PORT", token: "ARCANE_PRESENCE_TOKEN" };
10848
+ var LOOPBACK_HOST = "127.0.0.1";
10849
+ var MAX_BODY_BYTES = 65536;
10850
+ var MAX_LINE_CHARS = 256;
10851
+ var MAX_NEEDS_YOU = 20;
10852
+ var MAX_SUBSCRIBERS = 32;
10853
+ var MAX_SUBSCRIBER_BUFFER_BYTES = 1048576;
10854
+ var MAX_CONNECTIONS = 128;
10855
+ var MAX_SESSIONS = 256;
10856
+ var MAX_ALIASES = 32;
10857
+ var CONNECTIONS_CHECK_MS = 1e3;
10858
+ var HEADERS_TIMEOUT_MS = 5e3;
10859
+ var REQUEST_TIMEOUT_MS = 1e4;
10860
+ var STALE_AFTER_MS = 9e4;
10861
+ var EXPIRE_AFTER_MS = 864e5;
10862
+ var KEEPALIVE_MS = 15e3;
10863
+ var SWEEP_MS = 5e3;
10864
+ var HARNESSES = ["claude-code", "copilot", "codex", "openclaw", "cron", "other"];
10865
+ var REPORT_STATUSES = ["working", "waiting", "blocked", "idle"];
10866
+ var ALIAS_CONTEXTS = ["openclaw", "slug"];
10867
+ var HELLO_EVENT = "server.hello";
10868
+ function presenceCapabilities() {
10869
+ return { observe: true, actions: [] };
10870
+ }
10871
+ function nullAgentBlock() {
10872
+ return { ref: null, slug: null, name: null, origin: null, definition_revision: null, aliases: [] };
10873
+ }
10874
+
10875
+ // src/modules/presence/store.ts
10876
+ var PRESENCE_DIR_NAME = "presence";
10877
+ var STORE_FILES = { token: "token", serverInfo: "server.json" };
10878
+ var PresenceStoreError = class extends Error {
10879
+ };
10880
+ function home(options) {
10881
+ return options.home ?? resolveHomeDir();
10882
+ }
10883
+ function presenceDir(options = {}) {
10884
+ return join29(userTierRoot(home(options)), PRESENCE_DIR_NAME);
10885
+ }
10886
+ function tokenPath(options = {}) {
10887
+ return join29(presenceDir(options), STORE_FILES.token);
10888
+ }
10889
+ function serverInfoPath(options = {}) {
10890
+ return join29(presenceDir(options), STORE_FILES.serverInfo);
10891
+ }
10892
+ function ensureStore(options = {}) {
10893
+ const root = userTierRoot(home(options));
10894
+ const dir = presenceDir(options);
10895
+ mkdirSync2(root, { recursive: true });
10896
+ const realRoot = resolveRealPath(root);
10897
+ if (relative4(realRoot, resolveRealPath(dir)) !== PRESENCE_DIR_NAME) {
10898
+ throw new PresenceStoreError(`Refusing to use ${dir}: it resolves outside ${root}.`);
10899
+ }
10900
+ if (relative4(realRoot, resolveRealPath(tokenPath(options))) !== join29(PRESENCE_DIR_NAME, STORE_FILES.token)) {
10901
+ throw new PresenceStoreError(`Refusing to use ${tokenPath(options)}: it resolves outside ${dir}.`);
10902
+ }
10903
+ mkdirSync2(dir, { recursive: true, mode: 448 });
10904
+ chmodSync(dir, 448);
10905
+ return dir;
10906
+ }
10907
+ function writeRestricted(target, content, options) {
10908
+ const restrict = options.restrict ?? restrictToCurrentUser;
10909
+ const tmp = `${target}.${process.pid}.tmp`;
10910
+ writeFileSync2(tmp, content, { encoding: "utf8", mode: 384 });
10911
+ try {
10912
+ restrict(tmp);
10913
+ } catch (error2) {
10914
+ rmSync2(tmp, { force: true });
10915
+ throw new PresenceStoreError(`Could not restrict ${target} to your account, so it was not written.`, { cause: error2 });
10916
+ }
10917
+ renameSync2(tmp, target);
10918
+ chmodSync(target, 384);
10919
+ }
10920
+ function generateToken() {
10921
+ return randomBytes3(32).toString("base64url");
10922
+ }
10923
+ function readTokenFile(options) {
10924
+ let raw;
10925
+ try {
10926
+ raw = readFileSync3(tokenPath(options), "utf8");
10927
+ } catch {
10928
+ return null;
10929
+ }
10930
+ const token = raw.replace(/[\r\n]+$/, "").trim();
10931
+ return token.length > 0 ? token : null;
10932
+ }
10933
+ function ensureToken(options = {}) {
10934
+ ensureStore(options);
10935
+ const existing = options.rotate === true ? null : readTokenFile(options);
10936
+ if (existing !== null) {
10937
+ chmodSync(tokenPath(options), 384);
10938
+ (options.restrict ?? restrictToCurrentUser)(tokenPath(options));
10939
+ return existing;
10940
+ }
10941
+ const token = generateToken();
10942
+ writeRestricted(tokenPath(options), `${token}
10943
+ `, options);
10944
+ return token;
10945
+ }
10946
+ function readToken(options = {}) {
10947
+ const fromEnv = (options.env ?? process.env)[PRESENCE_ENV.token];
10948
+ if (fromEnv !== void 0 && fromEnv.replace(/[\r\n]+$/, "") !== "") return fromEnv.replace(/[\r\n]+$/, "");
10949
+ return readTokenFile(options);
10950
+ }
10951
+ function writeServerInfo(info, options = {}) {
10952
+ ensureStore(options);
10953
+ writeRestricted(serverInfoPath(options), `${JSON.stringify(info, null, 2)}
10954
+ `, options);
10955
+ }
10956
+ function readServerInfo(options = {}) {
10957
+ let parsed;
10958
+ try {
10959
+ parsed = JSON.parse(readFileSync3(serverInfoPath(options), "utf8"));
10960
+ } catch {
10961
+ return null;
10962
+ }
10963
+ if (typeof parsed !== "object" || parsed === null) return null;
10964
+ const o = parsed;
10965
+ const generator = o["generator"];
10966
+ const valid = typeof o["url"] === "string" && Number.isInteger(o["pid"]) && o["pid"] > 0 && Number.isInteger(o["port"]) && o["port"] >= 1 && o["port"] <= 65535 && typeof o["started_at"] === "string" && typeof generator === "object" && generator !== null && generator["name"] === "arcane-cli" && typeof generator["version"] === "string";
10967
+ if (!valid) return null;
10968
+ return {
10969
+ url: o["url"],
10970
+ pid: o["pid"],
10971
+ port: o["port"],
10972
+ started_at: o["started_at"],
10973
+ generator: { name: "arcane-cli", version: generator["version"] }
10974
+ };
10975
+ }
10976
+ function removeServerInfo(options = {}) {
10977
+ rmSync2(serverInfoPath(options), { force: true });
10978
+ }
10979
+
10980
+ // src/modules/presence/client.ts
10981
+ var CONNECT_TIMEOUT_MS = 1e3;
10982
+ var RESPONSE_TIMEOUT_MS = 1500;
10983
+ function pidAlive(pid) {
10984
+ try {
10985
+ process.kill(pid, 0);
10986
+ return true;
10987
+ } catch {
10988
+ return false;
10989
+ }
10990
+ }
10991
+ function resolveEndpoint(options = {}) {
10992
+ const info = readServerInfo(options);
10993
+ if (info === null || !pidAlive(info.pid)) return null;
10994
+ const token = readToken({ ...options, env: options.env ?? process.env });
10995
+ if (token === null) return null;
10996
+ return { info, token };
10997
+ }
10998
+ function errorFields(body) {
10999
+ const e = body?.error;
11000
+ return { code: typeof e?.code === "string" ? e.code : null, field: typeof e?.field === "string" ? e.field : null };
11001
+ }
11002
+ function send(port, token, method, path5, body, options = {}) {
11003
+ const connectTimeout = options.connectTimeoutMs ?? CONNECT_TIMEOUT_MS;
11004
+ const responseTimeout = options.responseTimeoutMs ?? RESPONSE_TIMEOUT_MS;
11005
+ const payload = body === void 0 ? void 0 : JSON.stringify(body);
11006
+ return new Promise((resolve10) => {
11007
+ let settled = false;
11008
+ const done = (result) => {
11009
+ if (settled) return;
11010
+ settled = true;
11011
+ clearTimeout(connectTimer);
11012
+ clearTimeout(responseTimer);
11013
+ resolve10(result);
11014
+ };
11015
+ const headers = { Authorization: `Bearer ${token}`, Accept: "application/json" };
11016
+ if (payload !== void 0) {
11017
+ headers["Content-Type"] = "application/json";
11018
+ headers["Content-Length"] = Buffer.byteLength(payload);
11019
+ }
11020
+ const req = request({ host: LOOPBACK_HOST, port, method, path: path5, headers, agent: false }, (res) => {
11021
+ const chunks = [];
11022
+ res.on("data", (c) => chunks.push(c));
11023
+ res.on("error", () => done({ kind: "unreachable" }));
11024
+ res.on("end", () => {
11025
+ const status = res.statusCode ?? 0;
11026
+ const text2 = Buffer.concat(chunks).toString("utf8");
11027
+ let parsed = null;
11028
+ try {
11029
+ parsed = text2.length > 0 ? JSON.parse(text2) : null;
11030
+ } catch {
11031
+ parsed = null;
11032
+ }
11033
+ if (status >= 200 && status < 300 && parsed !== null) done({ kind: "ok", status, body: parsed });
11034
+ else if (status === 401 || status === 400 || status === 404 || status === 413) done({ kind: "refused", status, ...errorFields(parsed) });
11035
+ else done({ kind: "unreachable" });
11036
+ });
11037
+ });
11038
+ const connectTimer = setTimeout(() => {
11039
+ req.destroy();
11040
+ done({ kind: "unreachable" });
11041
+ }, connectTimeout);
11042
+ const responseTimer = setTimeout(() => {
11043
+ req.destroy();
11044
+ done({ kind: "unreachable" });
11045
+ }, connectTimeout + responseTimeout);
11046
+ req.on("socket", (socket) => {
11047
+ socket.once("connect", () => clearTimeout(connectTimer));
11048
+ });
11049
+ req.on("error", () => done({ kind: "unreachable" }));
11050
+ if (payload !== void 0) req.write(payload);
11051
+ req.end();
11052
+ });
11053
+ }
11054
+ async function callPresence(method, path5, body, options = {}) {
11055
+ const endpoint = resolveEndpoint(options);
11056
+ if (endpoint === null) return { kind: "not-configured" };
11057
+ return send(endpoint.info.port, endpoint.token, method, path5, body, options);
11058
+ }
11059
+
11060
+ // src/modules/presence/mcp.ts
11061
+ import { createInterface } from "readline";
11062
+
11063
+ // src/modules/presence/reporter.ts
11064
+ import { basename as basename5, dirname as dirname14 } from "path";
11065
+
11066
+ // src/modules/presence/agent.ts
11067
+ async function v2Profiles(targetDir, homeDir) {
11068
+ try {
11069
+ const { agents } = await collectAgents(targetDir, homeDir === void 0 ? {} : { homeDir });
11070
+ if (agents === null) return [];
11071
+ return agents.entries.map((e) => ({
11072
+ ref: null,
11073
+ slug: e.id,
11074
+ name: e.name,
11075
+ origin: e.origin,
11076
+ definition_revision: e.definition_revision,
11077
+ aliases: [],
11078
+ definition: e.definition
11079
+ }));
11080
+ } catch {
11081
+ return [];
11082
+ }
11083
+ }
11084
+ function matchAgent(profiles, value) {
11085
+ const bySlug = profiles.filter((p) => p.slug === value);
11086
+ if (bySlug.length === 1) return bySlug[0] ?? null;
11087
+ if (bySlug.length > 1) return null;
11088
+ const byAlias = profiles.filter((p) => p.aliases.some((a) => a.value === value));
11089
+ return byAlias.length === 1 ? byAlias[0] ?? null : null;
11090
+ }
11091
+ function defaultAgent(profiles) {
11092
+ const orchestrators = profiles.filter((p) => p.definition === "orchestrator");
11093
+ return orchestrators.length === 1 ? orchestrators[0] ?? null : null;
11094
+ }
11095
+ function toAgentBlock(profile) {
11096
+ if (profile === null) return nullAgentBlock();
11097
+ return {
11098
+ ref: profile.ref,
11099
+ slug: profile.slug,
11100
+ name: profile.name,
11101
+ origin: profile.origin,
11102
+ definition_revision: profile.definition_revision,
11103
+ aliases: profile.aliases.map((a) => ({ value: a.value, context: a.context }))
11104
+ };
11105
+ }
11106
+ async function resolveSessionAgent(targetDir, agentFlag, deps = {}) {
11107
+ const provider = deps.profiles ?? ((dir) => v2Profiles(dir, deps.homeDir));
11108
+ let profiles;
11109
+ try {
11110
+ profiles = await provider(targetDir);
11111
+ } catch {
11112
+ return nullAgentBlock();
11113
+ }
11114
+ return toAgentBlock(agentFlag === void 0 ? defaultAgent(profiles) : matchAgent(profiles, agentFlag));
11115
+ }
11116
+
11117
+ // src/modules/presence/harness.ts
11118
+ var HARNESS_TABLE = [
11119
+ // Observed 2026-11-05 in Claude Code 2.1.81 (desktop app 4.16336.0) on macOS; progress.txt, PS-10.
11120
+ { harness: "claude-code", variable: "CLAUDECODE", value: "1" },
11121
+ // Observed 2026-11-05 in Codex 0.166.0-alpha.5.1, both the ChatGPT desktop app and `codex exec`; progress.txt.
11122
+ { harness: "codex", variable: "CODEX_CI", value: "1" },
11123
+ // Observed 2026-11-05 in Copilot agent mode in VS Code 1.143.1; progress.txt.
11124
+ { harness: "copilot", variable: "COPILOT_AGENT", value: "1" },
11125
+ // Observed 2026-11-05 in the GitHub Copilot desktop app 1.1.34; progress.txt.
11126
+ { harness: "copilot", variable: "COPILOT_CLI", value: "1" }
11127
+ ];
11128
+ function detectHarness(env) {
11129
+ for (const row of HARNESS_TABLE) if (env[row.variable] === row.value) return row.harness;
11130
+ return "other";
11131
+ }
11132
+
11133
+ // src/modules/presence/reporter.ts
11134
+ var NEEDS_YOU_SEPARATOR = " \u2014 ";
11135
+ function parseNeedsYou(line2) {
11136
+ const at = line2.indexOf(NEEDS_YOU_SEPARATOR);
11137
+ if (at < 0) return { action: line2, reason: null };
11138
+ return { action: line2.slice(0, at), reason: line2.slice(at + NEEDS_YOU_SEPARATOR.length) };
11139
+ }
11140
+ async function quiet(fn) {
11141
+ try {
11142
+ return await fn();
11143
+ } catch {
11144
+ return null;
11145
+ }
11146
+ }
11147
+ async function locateCheckout(cwd, deps = {}) {
11148
+ const git = deps.runGit ?? runGit;
11149
+ const out = await quiet(() => git(cwd, ["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-dir", "--git-common-dir"]));
11150
+ const lines = out?.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean) ?? [];
11151
+ if (lines.length !== 3) return null;
11152
+ const [top, gitDir, commonDir] = lines;
11153
+ return { checkout: resolveRealPath(top), gitDir: resolveRealPath(gitDir), commonDir: resolveRealPath(commonDir) };
11154
+ }
11155
+ async function locate(cwd, deps) {
11156
+ const found = await locateCheckout(cwd, deps);
11157
+ if (found === null) return null;
11158
+ const git = deps.runGit ?? runGit;
11159
+ const readManifest2 = deps.readManifest ?? readManifest;
11160
+ const [manifest, symbolic, remote] = await Promise.all([
11161
+ quiet(() => readManifest2(found.checkout)),
11162
+ quiet(() => git(found.checkout, ["symbolic-ref", "--short", "-q", "HEAD"])),
11163
+ quiet(() => git(found.checkout, ["remote", "get-url", "origin"]))
11164
+ ]);
11165
+ if (manifest === null) return null;
11166
+ let branch = symbolic?.stdout.trim() || null;
11167
+ if (branch === null) branch = (await quiet(() => git(found.checkout, ["rev-parse", "--short", "HEAD"])))?.stdout.trim() || null;
11168
+ const root = basename5(found.commonDir) === ".git" ? dirname14(found.commonDir) : found.checkout;
11169
+ const url = remote?.stdout.trim() ?? "";
11170
+ return {
11171
+ repository: { root, name: basename5(root), remote: url === "" ? null : { provider: remoteProvider(url), url: stripUserinfo(url) } },
11172
+ checkout: { path: found.checkout, branch, linked_worktree: found.gitDir !== found.commonDir }
11173
+ };
11174
+ }
11175
+ function refusedLine(verb, result) {
11176
+ const detail = [result.code, result.field].filter((v) => v !== null).join(" ");
11177
+ return `spell presence ${verb}: the presence service refused it (HTTP ${result.status}${detail === "" ? "" : `, ${detail}`}).`;
11178
+ }
11179
+ async function sendReport(cwd, flags, deps = {}) {
11180
+ const endpoint = resolveEndpoint(deps);
11181
+ if (endpoint === null) return { kind: "silent" };
11182
+ const located = await locate(cwd, deps);
11183
+ if (located === null) return { kind: "silent" };
11184
+ const env = deps.env ?? process.env;
11185
+ const resolveAgent = deps.resolveAgent ?? ((dir, flag) => resolveSessionAgent(dir, flag));
11186
+ const agent2 = await quiet(() => resolveAgent(located.checkout.path, flags.agent)) ?? nullAgentBlock();
11187
+ const harness = flags.harness ?? (deps.detectHarness ?? detectHarness)(env);
11188
+ const body = {
11189
+ status: flags.status,
11190
+ harness,
11191
+ agent: agent2,
11192
+ repository: located.repository,
11193
+ checkout: located.checkout,
11194
+ needs_you: (flags.needsYou ?? []).map(parseNeedsYou)
11195
+ };
11196
+ if (flags.spell !== void 0) body["spell"] = flags.spell;
11197
+ if (flags.task !== void 0) body["task"] = flags.task;
11198
+ if (flags.message !== void 0) body["message"] = flags.message === "" ? null : flags.message;
11199
+ const result = await send(endpoint.info.port, endpoint.token, "POST", "/v0/sessions/report", body, deps);
11200
+ if (result.kind === "ok") return { kind: "sent", record: result.body };
11201
+ if (result.kind === "refused") return { kind: "refused", line: refusedLine("report", result) };
11202
+ return { kind: "silent" };
11203
+ }
11204
+ async function sendEnd(cwd, deps = {}) {
11205
+ const endpoint = resolveEndpoint(deps);
11206
+ if (endpoint === null) return { kind: "silent" };
11207
+ const found = await locateCheckout(cwd, deps);
11208
+ if (found === null) return { kind: "silent" };
11209
+ const result = await send(endpoint.info.port, endpoint.token, "POST", "/v0/sessions/end", { checkout: { path: found.checkout } }, deps);
11210
+ if (result.kind === "ok") return { kind: "sent", record: result.body };
11211
+ if (result.kind === "refused" && result.status !== 404) return { kind: "refused", line: refusedLine("end", result) };
11212
+ return { kind: "silent" };
11213
+ }
11214
+
11215
+ // src/modules/presence/mcp.ts
11216
+ var MODERN_VERSIONS = ["2026-07-28"];
11217
+ var LEGACY_VERSIONS = ["2025-11-25", "2025-06-18", "2024-11-05"];
11218
+ var SUPPORTED_VERSIONS = [...MODERN_VERSIONS, ...LEGACY_VERSIONS];
11219
+ var SERVER_NAME = "arcane-presence";
11220
+ var NOT_CONFIGURED_TEXT = "no presence channel is configured on this machine";
11221
+ var VERSION_KEY = "io.modelcontextprotocol/protocolVersion";
11222
+ var SERVER_INFO_KEY = "io.modelcontextprotocol/serverInfo";
11223
+ var SESSIONS_URI = "arcane://presence/sessions";
11224
+ var CAPABILITIES_URI = "arcane://presence/capabilities";
11225
+ var SERVER_CAPABILITIES = { tools: {}, resources: {} };
11226
+ var LINE = { type: "string", maxLength: 256 };
11227
+ var PRESENCE_TOOLS = [
11228
+ {
11229
+ name: "presence_report",
11230
+ title: "Report presence",
11231
+ description: "Report this session to the local presence service (the same as `spell presence report`). Observation only: it changes what a dashboard shows, nothing else. Answers that no channel is configured when no service runs.",
11232
+ inputSchema: {
11233
+ type: "object",
11234
+ additionalProperties: false,
11235
+ required: ["status"],
11236
+ properties: {
11237
+ status: { type: "string", enum: [...REPORT_STATUSES] },
11238
+ task: { ...LINE, description: "What the session is doing, in one line." },
11239
+ spell: { ...LINE, description: "The spell running, e.g. spell-implement." },
11240
+ message: { ...LINE, description: "A free one-line note; an empty string clears it." },
11241
+ needs_you: { type: "array", maxItems: 20, items: { type: "string" }, description: "Lines of a Needs you block, '<action> \u2014 <reason>'." },
11242
+ agent: { type: "string", description: "The roster agent (a slug or an alias); default: the orchestrator." },
11243
+ harness: { type: "string", enum: [...HARNESSES] },
11244
+ cwd: { type: "string", description: "The checkout to report for; default: the server's working directory." }
11245
+ }
11246
+ }
11247
+ },
11248
+ {
11249
+ name: "presence_end",
11250
+ title: "End presence",
11251
+ description: "End this checkout's session on the local presence service (the same as `spell presence end`).",
11252
+ inputSchema: {
11253
+ type: "object",
11254
+ additionalProperties: false,
11255
+ properties: { cwd: { type: "string", description: "The checkout; default: the server's working directory." } }
11256
+ }
11257
+ }
11258
+ ];
11259
+ var PRESENCE_RESOURCES = [
11260
+ { uri: SESSIONS_URI, name: "presence-sessions", title: "Presence sessions", description: "The GET /v0/sessions document: every active and stale session.", mimeType: "application/json" },
11261
+ { uri: CAPABILITIES_URI, name: "presence-capabilities", title: "Presence capabilities", description: "The capability descriptor: observation only, no actions.", mimeType: "application/json" }
11262
+ ];
11263
+ var RpcError = class extends Error {
11264
+ constructor(code, message, data) {
11265
+ super(message);
11266
+ this.code = code;
11267
+ this.data = data;
11268
+ }
11269
+ code;
11270
+ data;
11271
+ };
11272
+ function isObject(value) {
11273
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11274
+ }
11275
+ function text(value, isError = false) {
11276
+ return { content: [{ type: "text", text: value }], isError };
11277
+ }
11278
+ function stringArg(args, key) {
11279
+ const v = args[key];
11280
+ if (v === void 0) return void 0;
11281
+ if (typeof v !== "string") throw new RpcError(-32602, `${key} must be a string`);
11282
+ return v;
11283
+ }
11284
+ function serveMcp(options) {
11285
+ const deps = options.deps ?? {};
11286
+ const cwd = options.cwd ?? process.cwd();
11287
+ const log = options.log ?? ((line2) => process.stderr.write(`${line2}
11288
+ `));
11289
+ const write = (message) => {
11290
+ options.output.write(`${JSON.stringify(message)}
11291
+ `);
11292
+ };
11293
+ const reply = (id, result) => write({ jsonrpc: "2.0", id, result });
11294
+ const fail = (id, error2) => write({ jsonrpc: "2.0", id, error: error2 });
11295
+ const report = async (args) => {
11296
+ const status = stringArg(args, "status");
11297
+ if (status === void 0) return text("status is required: one of working, waiting, blocked, idle.", true);
11298
+ const needs = args["needs_you"];
11299
+ if (needs !== void 0 && (!Array.isArray(needs) || needs.some((n) => typeof n !== "string"))) {
11300
+ return text("needs_you must be a list of strings.", true);
11301
+ }
11302
+ const flags = { status };
11303
+ for (const key of ["task", "spell", "message", "agent", "harness"]) {
11304
+ const v = stringArg(args, key);
11305
+ if (v !== void 0) flags[key] = v;
11306
+ }
11307
+ if (Array.isArray(needs)) flags.needsYou = needs;
11308
+ if (resolveEndpoint(deps) === null) return text(NOT_CONFIGURED_TEXT);
11309
+ const where = stringArg(args, "cwd") ?? cwd;
11310
+ const outcome = await sendReport(where, flags, deps);
11311
+ if (outcome.kind === "refused") return text(outcome.line, true);
11312
+ if (outcome.kind === "silent") return text(`Nothing was reported: ${where} is not inside an Arcane repository, or the service did not answer.`);
11313
+ const record = outcome.record;
11314
+ return { ...text(`Reported ${record.status} for ${record.session}.`), structuredContent: record };
11315
+ };
11316
+ const end = async (args) => {
11317
+ if (resolveEndpoint(deps) === null) return text(NOT_CONFIGURED_TEXT);
11318
+ const where = stringArg(args, "cwd") ?? cwd;
11319
+ const outcome = await sendEnd(where, deps);
11320
+ if (outcome.kind === "refused") return text(outcome.line, true);
11321
+ if (outcome.kind === "silent") return text(`No session was open for ${where}.`);
11322
+ return { ...text(`Ended ${outcome.record.session}.`), structuredContent: outcome.record };
11323
+ };
11324
+ const read = async (uri) => {
11325
+ const resource = PRESENCE_RESOURCES.find((r) => r.uri === uri);
11326
+ if (resource === void 0) throw new RpcError(-32602, "Resource not found", { uri });
11327
+ const result = await callPresence("GET", resource.uri === SESSIONS_URI ? "/v0/sessions" : "/v0/health", void 0, deps);
11328
+ if (result.kind !== "ok") return { contents: [{ uri: resource.uri, mimeType: "text/plain", text: NOT_CONFIGURED_TEXT }] };
11329
+ const body = resource.uri === SESSIONS_URI ? result.body : result.body.capabilities;
11330
+ return { contents: [{ uri: resource.uri, mimeType: "application/json", text: JSON.stringify(body) }] };
11331
+ };
11332
+ const handle = async (method, params) => {
11333
+ switch (method) {
11334
+ case "ping":
11335
+ return {};
11336
+ case "tools/list":
11337
+ return { tools: PRESENCE_TOOLS };
11338
+ case "tools/call": {
11339
+ const name = params["name"];
11340
+ const args = isObject(params["arguments"]) ? params["arguments"] : {};
11341
+ try {
11342
+ if (name === "presence_report") return await report(args);
11343
+ if (name === "presence_end") return await end(args);
11344
+ } catch (error2) {
11345
+ if (error2 instanceof RpcError && error2.code === -32602) return text(error2.message, true);
11346
+ throw error2;
11347
+ }
11348
+ throw new RpcError(-32602, `Unknown tool: ${String(name)}`);
11349
+ }
11350
+ case "resources/list":
11351
+ return { resources: PRESENCE_RESOURCES };
11352
+ case "resources/read":
11353
+ return read(params["uri"]);
11354
+ default:
11355
+ throw new RpcError(-32601, `Method not found: ${method}`);
11356
+ }
11357
+ };
11358
+ const dispatch = async (raw) => {
11359
+ let message;
11360
+ try {
11361
+ message = JSON.parse(raw);
11362
+ } catch {
11363
+ fail(null, { code: -32700, message: "Parse error" });
11364
+ return;
11365
+ }
11366
+ if (!isObject(message) || message["jsonrpc"] !== "2.0" || typeof message["method"] !== "string") {
11367
+ const id2 = isObject(message) && (typeof message["id"] === "string" || typeof message["id"] === "number") ? message["id"] : null;
11368
+ fail(id2, { code: -32600, message: "Invalid Request" });
11369
+ return;
11370
+ }
11371
+ const method = message["method"];
11372
+ const hasId = "id" in message;
11373
+ const rawId = message["id"];
11374
+ if (hasId && rawId !== null && typeof rawId !== "string" && typeof rawId !== "number") {
11375
+ fail(null, { code: -32600, message: "Invalid Request" });
11376
+ return;
11377
+ }
11378
+ const id = rawId ?? null;
11379
+ const params = isObject(message["params"]) ? message["params"] : {};
11380
+ if (!hasId) return;
11381
+ try {
11382
+ if (method === "initialize") {
11383
+ const requested2 = params["protocolVersion"];
11384
+ const protocolVersion = typeof requested2 === "string" && LEGACY_VERSIONS.includes(requested2) ? requested2 : LEGACY_VERSIONS[0];
11385
+ reply(id, { protocolVersion, capabilities: SERVER_CAPABILITIES, serverInfo: { name: SERVER_NAME, version: options.version } });
11386
+ return;
11387
+ }
11388
+ const meta = isObject(params["_meta"]) ? params["_meta"] : {};
11389
+ const requested = meta[VERSION_KEY];
11390
+ const modern = typeof requested === "string" && MODERN_VERSIONS.includes(requested);
11391
+ if (requested !== void 0 && (typeof requested !== "string" || !SUPPORTED_VERSIONS.includes(requested))) {
11392
+ fail(id, { code: -32022, message: "Unsupported protocol version", data: { supported: SUPPORTED_VERSIONS, requested } });
11393
+ return;
11394
+ }
11395
+ if (method === "server/discover") {
11396
+ reply(id, { resultType: "complete", supportedVersions: SUPPORTED_VERSIONS, capabilities: SERVER_CAPABILITIES, _meta: { [SERVER_INFO_KEY]: { name: SERVER_NAME, version: options.version } } });
11397
+ return;
11398
+ }
11399
+ const result = await handle(method, params);
11400
+ reply(id, modern ? { resultType: "complete", ...result } : result);
11401
+ } catch (error2) {
11402
+ if (error2 instanceof RpcError) {
11403
+ fail(id, error2.data === void 0 ? { code: error2.code, message: error2.message } : { code: error2.code, message: error2.message, data: error2.data });
11404
+ } else {
11405
+ log(`spell presence mcp: internal error on ${method}`);
11406
+ fail(id, { code: -32603, message: "Internal error" });
11407
+ }
11408
+ }
11409
+ };
11410
+ return new Promise((resolve10) => {
11411
+ const lines = createInterface({ input: options.input, crlfDelay: Infinity });
11412
+ let chain = Promise.resolve();
11413
+ lines.on("line", (line2) => {
11414
+ if (line2.trim() === "") return;
11415
+ chain = chain.then(() => dispatch(line2));
11416
+ });
11417
+ lines.on("close", () => {
11418
+ void chain.then(() => resolve10());
11419
+ });
11420
+ });
11421
+ }
11422
+
11423
+ // src/modules/presence/log.ts
11424
+ var EVENT = /^[a-z]+(\.[a-z_]+)*$/;
11425
+ var SESSION = /^arcane:session:[0-9a-f-]{36}$/;
11426
+ var STATUS = /^[a-z]+$/;
11427
+ var sink = (line2) => {
11428
+ process.stderr.write(`${line2}
11429
+ `);
11430
+ };
11431
+ function logEvent(event, session, status) {
11432
+ const e = EVENT.test(event) ? event : "-";
11433
+ const s = session !== null && SESSION.test(session) ? session : "-";
11434
+ const st = status !== null && STATUS.test(status) ? status : "-";
11435
+ sink(`${(/* @__PURE__ */ new Date()).toISOString()} presence ${e} ${s} ${st}`);
11436
+ }
11437
+
11438
+ // src/modules/presence/server.ts
11439
+ import { createServer as createServer2 } from "http";
11440
+
11441
+ // src/modules/presence/auth.ts
11442
+ import { createHash as createHash6, timingSafeEqual } from "crypto";
11443
+ var BEARER = /^Bearer ([^\s]+)$/;
11444
+ function digest(value) {
11445
+ return createHash6("sha256").update(value, "utf8").digest();
11446
+ }
11447
+ function checkBearer(header, token) {
11448
+ if (typeof header !== "string" || token.length === 0) return false;
11449
+ const match2 = BEARER.exec(header);
11450
+ if (match2 === null) return false;
11451
+ return timingSafeEqual(digest(match2[1]), digest(token));
11452
+ }
11453
+
11454
+ // src/modules/presence/validate.ts
11455
+ var Refusal = class extends Error {
11456
+ constructor(code, field, message) {
11457
+ super(message);
11458
+ this.code = code;
11459
+ this.field = field;
11460
+ }
11461
+ code;
11462
+ field;
11463
+ };
11464
+ var AGENT_REF = /^arcane:agent:[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
11465
+ var REVISION = /^sha256:[0-9a-f]{64}$/;
11466
+ var CONTROL = new RegExp("[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]", "g");
11467
+ function oneLine(value) {
11468
+ const flat = value.replace(CONTROL, " ");
11469
+ const points = Array.from(flat);
11470
+ return points.length > MAX_LINE_CHARS ? points.slice(0, MAX_LINE_CHARS).join("") : flat;
11471
+ }
11472
+ function isObject2(value) {
11473
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11474
+ }
11475
+ function closed(value, field, required, optional = []) {
11476
+ if (!isObject2(value)) throw new Refusal("invalid_field", field, `${field ?? "the body"} must be an object`);
11477
+ const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
11478
+ for (const key of Object.keys(value)) {
11479
+ if (!allowed.has(key)) throw new Refusal("unknown_field", join30(field, key), `${join30(field, key)} is not a field of this contract`);
11480
+ }
11481
+ for (const key of required) {
11482
+ if (!(key in value)) throw new Refusal("missing_field", join30(field, key), `${join30(field, key)} is required`);
11483
+ }
11484
+ return value;
11485
+ }
11486
+ function join30(parent, key) {
11487
+ return parent === void 0 ? key : `${parent}.${key}`;
11488
+ }
11489
+ function nonEmpty(value, field) {
11490
+ if (typeof value !== "string" || value.length === 0) throw new Refusal("invalid_field", field, `${field} must be a non-empty string`);
11491
+ return value;
11492
+ }
11493
+ function line(value, field, nullable) {
11494
+ if (value === null && nullable) return null;
11495
+ if (typeof value !== "string") throw new Refusal("invalid_field", field, `${field} must be a string${nullable ? " or null" : ""}`);
11496
+ return oneLine(value);
11497
+ }
11498
+ function nonEmptyLine(value, field) {
11499
+ const out = line(value, field, false);
11500
+ if (out.trim().length === 0) throw new Refusal("invalid_field", field, `${field} must not be empty`);
11501
+ return out;
11502
+ }
11503
+ function oneOf(value, allowed, field) {
11504
+ if (typeof value !== "string" || !allowed.includes(value)) {
11505
+ throw new Refusal("invalid_field", field, `${field} must be one of ${allowed.join(", ")}`);
11506
+ }
11507
+ return value;
11508
+ }
11509
+ function agent(value) {
11510
+ const a = closed(value, "agent", ["ref", "slug", "name", "origin", "definition_revision", "aliases"]);
11511
+ if (a["ref"] !== null && (typeof a["ref"] !== "string" || !AGENT_REF.test(a["ref"]))) {
11512
+ throw new Refusal("invalid_field", "agent.ref", "agent.ref must be an arcane:agent: reference or null");
11513
+ }
11514
+ if (a["origin"] !== null && a["origin"] !== "repo" && a["origin"] !== "user") {
11515
+ throw new Refusal("invalid_field", "agent.origin", "agent.origin must be repo, user or null");
11516
+ }
11517
+ if (a["definition_revision"] !== null && (typeof a["definition_revision"] !== "string" || !REVISION.test(a["definition_revision"]))) {
11518
+ throw new Refusal("invalid_field", "agent.definition_revision", "agent.definition_revision must be sha256:<64 hex> or null");
11519
+ }
11520
+ if (!Array.isArray(a["aliases"])) throw new Refusal("invalid_field", "agent.aliases", "agent.aliases must be a list");
11521
+ if (a["aliases"].length > MAX_ALIASES) throw new Refusal("invalid_field", "agent.aliases", `agent.aliases carries at most ${MAX_ALIASES} entries`);
11522
+ const aliases = a["aliases"].map((item, i) => {
11523
+ const field = `agent.aliases.${i}`;
11524
+ const alias = closed(item, field, ["value", "context"]);
11525
+ return { value: nonEmptyLine(alias["value"], `${field}.value`), context: oneOf(alias["context"], ALIAS_CONTEXTS, `${field}.context`) };
11526
+ });
11527
+ return {
11528
+ ref: a["ref"],
11529
+ slug: line(a["slug"], "agent.slug", true),
11530
+ name: line(a["name"], "agent.name", true),
11531
+ origin: a["origin"],
11532
+ definition_revision: a["definition_revision"],
11533
+ aliases
11534
+ };
11535
+ }
11536
+ function repository(value) {
11537
+ const r = closed(value, "repository", ["root", "name", "remote"]);
11538
+ let remote = null;
11539
+ if (r["remote"] !== null) {
11540
+ const rem = closed(r["remote"], "repository.remote", ["provider", "url"]);
11541
+ if (typeof rem["url"] !== "string") throw new Refusal("invalid_field", "repository.remote.url", "repository.remote.url must be a string");
11542
+ remote = { provider: oneOf(rem["provider"], ["github", "ado", "other"], "repository.remote.provider"), url: rem["url"] };
11543
+ }
11544
+ return { root: nonEmpty(r["root"], "repository.root"), name: nonEmptyLine(r["name"], "repository.name"), remote };
11545
+ }
11546
+ function checkout(value) {
11547
+ const c = closed(value, "checkout", ["path", "branch", "linked_worktree"]);
11548
+ if (typeof c["linked_worktree"] !== "boolean") {
11549
+ throw new Refusal("invalid_field", "checkout.linked_worktree", "checkout.linked_worktree must be true or false");
11550
+ }
11551
+ return { path: nonEmpty(c["path"], "checkout.path"), branch: line(c["branch"], "checkout.branch", true), linked_worktree: c["linked_worktree"] };
11552
+ }
11553
+ function needsYou(value) {
11554
+ if (!Array.isArray(value)) throw new Refusal("invalid_field", "needs_you", "needs_you must be a list");
11555
+ if (value.length > MAX_NEEDS_YOU) {
11556
+ throw new Refusal("too_many_lines", "needs_you", `needs_you carries at most ${MAX_NEEDS_YOU} lines; ${value.length} were sent`);
11557
+ }
11558
+ return value.map((item, i) => {
11559
+ const field = `needs_you.${i}`;
11560
+ const n = closed(item, field, ["action", "reason"]);
11561
+ return { action: nonEmptyLine(n["action"], `${field}.action`), reason: line(n["reason"], `${field}.reason`, true) };
11562
+ });
11563
+ }
11564
+ function refuse(fn) {
11565
+ try {
11566
+ return { ok: true, value: fn() };
11567
+ } catch (error2) {
11568
+ if (error2 instanceof Refusal) {
11569
+ return error2.field === void 0 ? { ok: false, code: error2.code, message: error2.message } : { ok: false, code: error2.code, field: error2.field, message: error2.message };
11570
+ }
11571
+ throw error2;
11572
+ }
11573
+ }
11574
+ function validateReport(value) {
11575
+ return refuse(() => {
11576
+ const b = closed(value, void 0, ["status", "harness", "agent", "repository", "checkout"], ["spell", "task", "message", "needs_you"]);
11577
+ if (b["status"] === "done") {
11578
+ throw new Refusal("invalid_field", "status", "status done is set by ending a session: use POST /v0/sessions/end (spell presence end)");
11579
+ }
11580
+ const out = {
11581
+ status: oneOf(b["status"], REPORT_STATUSES, "status"),
11582
+ harness: oneOf(b["harness"], HARNESSES, "harness"),
11583
+ agent: agent(b["agent"]),
11584
+ repository: repository(b["repository"]),
11585
+ checkout: checkout(b["checkout"])
11586
+ };
11587
+ for (const key of ["spell", "task", "message"]) {
11588
+ if (key in b) out[key] = line(b[key], key, true);
11589
+ }
11590
+ if ("needs_you" in b) out.needs_you = needsYou(b["needs_you"]);
11591
+ return out;
11592
+ });
11593
+ }
11594
+ function validateEnd(value) {
11595
+ return refuse(() => {
11596
+ const b = closed(value, void 0, ["checkout"]);
11597
+ const c = closed(b["checkout"], "checkout", ["path"]);
11598
+ return { checkout: { path: nonEmpty(c["path"], "checkout.path") } };
11599
+ });
11600
+ }
11601
+
11602
+ // src/modules/presence/server.ts
11603
+ var PRESENCE_ROUTES = [
11604
+ ["GET", "/v0/health"],
11605
+ ["POST", "/v0/sessions/report"],
11606
+ ["POST", "/v0/sessions/end"],
11607
+ ["GET", "/v0/sessions"],
11608
+ ["GET", "/v0/sessions/{session}"],
11609
+ ["GET", "/v0/events"]
11610
+ ];
11611
+ var JSON_HEADERS = {
11612
+ "Content-Type": "application/json; charset=utf-8",
11613
+ "Cache-Control": "no-store",
11614
+ "X-Content-Type-Options": "nosniff"
11615
+ };
11616
+ function send2(res, status, body, extra = {}) {
11617
+ const text2 = `${JSON.stringify(body)}
11618
+ `;
11619
+ res.writeHead(status, { ...JSON_HEADERS, "Content-Length": Buffer.byteLength(text2), ...extra });
11620
+ res.end(text2);
11621
+ }
11622
+ function error(res, status, code, message, field, extra = {}) {
11623
+ const body = { error: field === void 0 ? { code, message } : { code, field, message } };
11624
+ send2(res, status, body, extra);
11625
+ }
11626
+ function unauthorized(res) {
11627
+ res.writeHead(401, { "Content-Length": 0, "WWW-Authenticate": "Bearer", "Cache-Control": "no-store", Connection: "close" });
11628
+ res.end();
11629
+ }
11630
+ function readBody(req, res) {
11631
+ return new Promise((resolve10) => {
11632
+ const tooLarge = () => {
11633
+ error(res, 413, "too_large", `bodies are limited to ${MAX_BODY_BYTES} bytes`, void 0, { Connection: "close" });
11634
+ req.resume();
11635
+ resolve10(null);
11636
+ };
11637
+ const declared = Number(req.headers["content-length"] ?? "0");
11638
+ if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
11639
+ tooLarge();
11640
+ return;
11641
+ }
11642
+ const chunks = [];
11643
+ let size = 0;
11644
+ let done = false;
11645
+ req.on("data", (chunk) => {
11646
+ if (done) return;
11647
+ size += chunk.length;
11648
+ if (size > MAX_BODY_BYTES) {
11649
+ done = true;
11650
+ tooLarge();
11651
+ return;
11652
+ }
11653
+ chunks.push(chunk);
11654
+ });
11655
+ req.on("end", () => {
11656
+ if (done) return;
11657
+ done = true;
11658
+ resolve10(Buffer.concat(chunks).toString("utf8"));
11659
+ });
11660
+ req.on("error", () => {
11661
+ if (done) return;
11662
+ done = true;
11663
+ resolve10(null);
11664
+ });
11665
+ });
11666
+ }
11667
+ function parseJson(text2) {
11668
+ try {
11669
+ return { ok: true, value: JSON.parse(text2) };
11670
+ } catch {
11671
+ return { ok: false };
11672
+ }
11673
+ }
11674
+ function match(method, pathname) {
11675
+ const allow = [];
11676
+ for (const route of PRESENCE_ROUTES) {
11677
+ const [m, pattern] = route;
11678
+ let param;
11679
+ let hit = pattern === pathname;
11680
+ if (!hit && pattern.endsWith("/{session}")) {
11681
+ const prefix = pattern.slice(0, -"{session}".length);
11682
+ const rest = pathname.startsWith(prefix) ? pathname.slice(prefix.length) : "";
11683
+ if (rest.length > 0 && !rest.includes("/") && !PRESENCE_ROUTES.some(([, p]) => p === pathname)) {
11684
+ hit = true;
11685
+ try {
11686
+ param = decodeURIComponent(rest);
11687
+ } catch {
11688
+ param = "";
11689
+ }
11690
+ }
11691
+ }
11692
+ if (!hit) continue;
11693
+ if (m === method) return param === void 0 ? { route } : { route, param };
11694
+ allow.push(m);
11695
+ }
11696
+ return allow.length > 0 ? { allow } : null;
11697
+ }
11698
+ function createPresenceServer(options) {
11699
+ const now = options.now ?? Date.now;
11700
+ const keepaliveMs = options.keepaliveMs ?? KEEPALIVE_MS;
11701
+ const maxSubscribers = options.maxSubscribers ?? MAX_SUBSCRIBERS;
11702
+ const { table } = options;
11703
+ const token = typeof options.token === "function" ? options.token : () => options.token;
11704
+ const feeds = /* @__PURE__ */ new Set();
11705
+ table.listen((e) => logEvent(e.event, e.data.session, e.data.status));
11706
+ const health = () => ({
11707
+ ok: true,
11708
+ generator: options.generator,
11709
+ contract: PRESENCE_CONTRACT,
11710
+ capabilities: presenceCapabilities(),
11711
+ sessions: table.count(),
11712
+ started_at: options.startedAt
11713
+ });
11714
+ const snapshot = () => ({
11715
+ capabilities: presenceCapabilities(),
11716
+ generated_at: new Date(now()).toISOString(),
11717
+ sessions: table.snapshot()
11718
+ });
11719
+ const writeEvent = (res, name, data) => {
11720
+ if (res.writableLength > MAX_SUBSCRIBER_BUFFER_BYTES) {
11721
+ res.destroy();
11722
+ feeds.delete(res);
11723
+ return;
11724
+ }
11725
+ res.write(`event: ${name}
11726
+ data: ${JSON.stringify(data)}
11727
+
11728
+ `);
11729
+ };
11730
+ table.listen((e) => {
11731
+ for (const feed of feeds) writeEvent(feed, e.event, e.data);
11732
+ });
11733
+ const events = (req, res) => {
11734
+ if (feeds.size >= maxSubscribers) {
11735
+ error(res, 503, "too_many_subscribers", `at most ${maxSubscribers} event feeds are served at once`, void 0, { "Retry-After": "5" });
11736
+ return;
11737
+ }
11738
+ res.writeHead(200, {
11739
+ "Content-Type": "text/event-stream; charset=utf-8",
11740
+ "Cache-Control": "no-store",
11741
+ "X-Content-Type-Options": "nosniff",
11742
+ Connection: "keep-alive"
11743
+ });
11744
+ feeds.add(res);
11745
+ const hello = { contract: PRESENCE_CONTRACT, capabilities: presenceCapabilities(), sessions: table.snapshot() };
11746
+ res.write(`event: ${HELLO_EVENT}
11747
+ data: ${JSON.stringify(hello)}
11748
+
11749
+ `);
11750
+ const keepalive = setInterval(() => res.write(": keepalive\n\n"), keepaliveMs);
11751
+ keepalive.unref();
11752
+ const drop = () => {
11753
+ clearInterval(keepalive);
11754
+ feeds.delete(res);
11755
+ };
11756
+ req.on("close", drop);
11757
+ res.on("close", drop);
11758
+ };
11759
+ const handle = async (req, res) => {
11760
+ if (!checkBearer(req.headers.authorization, token())) {
11761
+ unauthorized(res);
11762
+ req.resume();
11763
+ return;
11764
+ }
11765
+ const pathname = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
11766
+ const found = match(req.method ?? "GET", pathname);
11767
+ if (found === null) {
11768
+ error(res, 404, "not_found", "no such route");
11769
+ req.resume();
11770
+ return;
11771
+ }
11772
+ if ("allow" in found) {
11773
+ error(res, 405, "method_not_allowed", `use ${found.allow.join(", ")}`, void 0, { Allow: found.allow.join(", ") });
11774
+ req.resume();
11775
+ return;
11776
+ }
11777
+ const path5 = found.route[1];
11778
+ if (path5 === "/v0/health") return send2(res, 200, health());
11779
+ if (path5 === "/v0/sessions") return send2(res, 200, snapshot());
11780
+ if (path5 === "/v0/events") return events(req, res);
11781
+ if (path5 === "/v0/sessions/{session}") {
11782
+ const parsed = parseRef(found.param, "session");
11783
+ const record = parsed.ok ? table.get(found.param ?? "") : null;
11784
+ if (record === null) return error(res, 404, "not_found", "no such session");
11785
+ return send2(res, 200, record);
11786
+ }
11787
+ const text2 = await readBody(req, res);
11788
+ if (text2 === null) return;
11789
+ const json = parseJson(text2);
11790
+ if (!json.ok) return error(res, 400, "invalid_json", "the body is not valid JSON");
11791
+ if (path5 === "/v0/sessions/report") {
11792
+ const result2 = validateReport(json.value);
11793
+ if (!result2.ok) return error(res, 400, result2.code, result2.message, result2.field);
11794
+ const isNew = table.snapshot().every((r) => r.checkout.path !== result2.value.checkout.path);
11795
+ if (isNew && table.count() >= MAX_SESSIONS) {
11796
+ return error(res, 503, "too_many_sessions", `at most ${MAX_SESSIONS} sessions are held at once`, void 0, { "Retry-After": "60" });
11797
+ }
11798
+ const { record, created } = table.report(result2.value);
11799
+ return send2(res, created ? 201 : 200, record);
11800
+ }
11801
+ const result = validateEnd(json.value);
11802
+ if (!result.ok) return error(res, 400, result.code, result.message, result.field);
11803
+ const ended = table.end(result.value.checkout.path);
11804
+ if (ended === null) return error(res, 404, "not_found", "no session reports from that checkout", "checkout.path");
11805
+ return send2(res, 200, ended);
11806
+ };
11807
+ const server = createServer2({ connectionsCheckingInterval: CONNECTIONS_CHECK_MS }, (req, res) => {
11808
+ handle(req, res).catch(() => {
11809
+ if (!res.headersSent) error(res, 500, "internal", "the service could not answer");
11810
+ else res.destroy();
11811
+ });
11812
+ });
11813
+ server.maxConnections = MAX_CONNECTIONS;
11814
+ server.headersTimeout = HEADERS_TIMEOUT_MS;
11815
+ server.requestTimeout = REQUEST_TIMEOUT_MS;
11816
+ return {
11817
+ server,
11818
+ closeFeeds() {
11819
+ for (const feed of feeds) feed.end();
11820
+ feeds.clear();
11821
+ },
11822
+ subscriberCount() {
11823
+ return feeds.size;
11824
+ }
11825
+ };
11826
+ }
11827
+
11828
+ // src/modules/presence/sessions.ts
11829
+ function copy(record) {
11830
+ return structuredClone(record);
11831
+ }
11832
+ function sameNeedsYou(a, b) {
11833
+ if (a.length !== b.length) return false;
11834
+ return a.every((item, i) => item.action === b[i]?.action && item.reason === b[i]?.reason);
11835
+ }
11836
+ function byStart(a, b) {
11837
+ if (a.started_at !== b.started_at) return a.started_at < b.started_at ? -1 : 1;
11838
+ if (a.session === b.session) return 0;
11839
+ return a.session < b.session ? -1 : 1;
11840
+ }
11841
+ function createSessionTable(options) {
11842
+ const now = options.now ?? Date.now;
11843
+ const staleAfterMs = options.staleAfterMs ?? STALE_AFTER_MS;
11844
+ const expireAfterMs = options.expireAfterMs ?? EXPIRE_AFTER_MS;
11845
+ const entries = /* @__PURE__ */ new Map();
11846
+ const listeners = /* @__PURE__ */ new Set();
11847
+ const emit = (name, record) => {
11848
+ const event = { event: name, data: copy(record) };
11849
+ for (const listener of listeners) listener({ event: name, data: copy(record) });
11850
+ return event;
11851
+ };
11852
+ return {
11853
+ report(body) {
11854
+ const ms = now();
11855
+ const at = new Date(ms).toISOString();
11856
+ const key = body.checkout.path;
11857
+ const needsYou2 = (body.needs_you ?? []).map((n) => ({ action: n.action, reason: n.reason }));
11858
+ const existing = entries.get(key);
11859
+ if (existing === void 0) {
11860
+ const record2 = {
11861
+ session: options.mint(),
11862
+ agent: structuredClone(body.agent),
11863
+ harness: body.harness,
11864
+ repository: structuredClone(body.repository),
11865
+ checkout: structuredClone(body.checkout),
11866
+ spell: body.spell ?? null,
11867
+ task: body.task ?? null,
11868
+ message: body.message ?? null,
11869
+ status: body.status,
11870
+ needs_you: needsYou2,
11871
+ started_at: at,
11872
+ reported_at: at,
11873
+ ended_at: null,
11874
+ stale: false
11875
+ };
11876
+ entries.set(key, { record: record2, reportedMs: ms });
11877
+ emit("session.started", record2);
11878
+ return { record: copy(record2), created: true, event: "session.started" };
11879
+ }
11880
+ const record = existing.record;
11881
+ const wasStale = record.stale;
11882
+ const needsChanged = !sameNeedsYou(record.needs_you, needsYou2);
11883
+ record.agent = structuredClone(body.agent);
11884
+ record.harness = body.harness;
11885
+ record.repository = structuredClone(body.repository);
11886
+ record.checkout = structuredClone(body.checkout);
11887
+ if (body.spell !== void 0) record.spell = body.spell;
11888
+ if (body.task !== void 0) record.task = body.task;
11889
+ if (body.message !== void 0) record.message = body.message;
11890
+ record.status = body.status;
11891
+ record.needs_you = needsYou2;
11892
+ record.reported_at = at;
11893
+ record.stale = false;
11894
+ existing.reportedMs = ms;
11895
+ const event = wasStale ? "session.resumed" : needsChanged ? "session.needs_you" : "session.updated";
11896
+ emit(event, record);
11897
+ return { record: copy(record), created: false, event };
11898
+ },
11899
+ end(checkoutPath) {
11900
+ const existing = entries.get(checkoutPath);
11901
+ if (existing === void 0) return null;
11902
+ entries.delete(checkoutPath);
11903
+ const record = existing.record;
11904
+ record.status = "done";
11905
+ record.ended_at = new Date(now()).toISOString();
11906
+ emit("session.ended", record);
11907
+ return copy(record);
11908
+ },
11909
+ get(session) {
11910
+ for (const { record } of entries.values()) if (record.session === session) return copy(record);
11911
+ return null;
11912
+ },
11913
+ snapshot() {
11914
+ return [...entries.values()].map((e) => copy(e.record)).sort(byStart);
11915
+ },
11916
+ count() {
11917
+ return entries.size;
11918
+ },
11919
+ sweep() {
11920
+ const ms = now();
11921
+ const emitted = [];
11922
+ for (const [key, entry] of [...entries.entries()]) {
11923
+ const silent = ms - entry.reportedMs;
11924
+ if (silent >= expireAfterMs) {
11925
+ entries.delete(key);
11926
+ entry.record.stale = true;
11927
+ emitted.push(emit("session.expired", entry.record));
11928
+ } else if (silent >= staleAfterMs && !entry.record.stale) {
11929
+ entry.record.stale = true;
11930
+ emitted.push(emit("session.stale", entry.record));
11931
+ }
11932
+ }
11933
+ return emitted;
11934
+ },
11935
+ listen(listener) {
11936
+ listeners.add(listener);
11937
+ return () => {
11938
+ listeners.delete(listener);
11939
+ };
11940
+ }
11941
+ };
11942
+ }
11943
+
11944
+ // src/commands/presence.ts
11945
+ var NOT_CONFIGURED = "No presence channel is configured on this machine.";
11946
+ var consoleIo = {
11947
+ out: (line2) => console.log(line2),
11948
+ err: (line2) => console.error(line2)
11949
+ };
11950
+ function resolvePort(flag, env) {
11951
+ const raw = flag ?? env[PRESENCE_ENV.port] ?? String(DEFAULT_PORT);
11952
+ if (!/^\d+$/.test(raw.trim())) return null;
11953
+ const port = Number(raw.trim());
11954
+ return port >= 0 && port <= 65535 ? port : null;
11955
+ }
11956
+ var ServeError = class extends Error {
11957
+ };
11958
+ async function startPresenceService(options, context = {}) {
11959
+ const env = context.env ?? process.env;
11960
+ const store = context.store ?? {};
11961
+ const port = resolvePort(options.port, env);
11962
+ if (port === null) throw new ServeError(`The port must be a whole number from 0 to 65535 (--port or ${PRESENCE_ENV.port}).`);
11963
+ let token = "";
11964
+ const table = createSessionTable({ mint: mintSessionRef });
11965
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
11966
+ const presence = createPresenceServer({
11967
+ token: () => token,
11968
+ table,
11969
+ generator: { name: "arcane-cli", version: context.version ?? packageJson.version },
11970
+ startedAt
11971
+ });
11972
+ const { server } = presence;
11973
+ await new Promise((resolve10, reject) => {
11974
+ const onError = (error2) => {
11975
+ server.off("listening", onListening);
11976
+ reject(
11977
+ error2.code === "EADDRINUSE" ? new ServeError(`Port ${port} is already in use. Is a presence service already running? (spell presence status)`) : new ServeError(`Could not listen on ${LOOPBACK_HOST}:${port}: ${error2.message}`)
11978
+ );
11979
+ };
11980
+ const onListening = () => {
11981
+ server.off("error", onError);
11982
+ resolve10();
11983
+ };
11984
+ server.once("error", onError);
11985
+ server.once("listening", onListening);
11986
+ server.listen(port, LOOPBACK_HOST);
11987
+ });
11988
+ const closeServer = () => new Promise((resolve10) => {
11989
+ presence.closeFeeds();
11990
+ server.closeAllConnections();
11991
+ server.close(() => resolve10());
11992
+ });
11993
+ const actualPort = server.address().port;
11994
+ const url = `http://${LOOPBACK_HOST}:${actualPort}`;
11995
+ const recorded = readServerInfo(store);
11996
+ if (recorded !== null && recorded.pid !== process.pid && pidAlive(recorded.pid)) {
11997
+ const health = await callPresence("GET", "/v0/health", void 0, { ...store, env });
11998
+ if (health.kind === "ok" || health.kind === "refused" && health.status === 401) {
11999
+ await closeServer();
12000
+ throw new ServeError(`A presence service is already running at ${recorded.url} (pid ${recorded.pid}). Stop it first: spell presence stop`);
12001
+ }
12002
+ }
12003
+ try {
12004
+ token = ensureToken({ ...store, rotate: options.rotate === true });
12005
+ writeServerInfo({ url, pid: process.pid, port: actualPort, started_at: startedAt, generator: { name: "arcane-cli", version: context.version ?? packageJson.version } }, store);
12006
+ } catch (error2) {
12007
+ await closeServer();
12008
+ throw new ServeError(error2 instanceof PresenceStoreError ? error2.message : `Could not write the presence store: ${String(error2)}`);
12009
+ }
12010
+ server.on("error", () => logEvent("server.error", null, null));
12011
+ const sweep = setInterval(() => table.sweep(), SWEEP_MS);
12012
+ sweep.unref();
12013
+ let stopped = false;
12014
+ return {
12015
+ url,
12016
+ port: actualPort,
12017
+ async stop() {
12018
+ if (stopped) return;
12019
+ stopped = true;
12020
+ clearInterval(sweep);
12021
+ await closeServer();
12022
+ const recorded2 = readServerInfo(store);
12023
+ if (recorded2 !== null && recorded2.pid === process.pid && recorded2.started_at === startedAt) removeServerInfo(store);
12024
+ }
12025
+ };
12026
+ }
12027
+ async function runPresenceServe(options, io = consoleIo) {
12028
+ let signalled = false;
12029
+ let onSignal = () => {
12030
+ signalled = true;
12031
+ };
12032
+ const handler = () => onSignal();
12033
+ process.on("SIGINT", handler);
12034
+ process.on("SIGTERM", handler);
12035
+ const detach = () => {
12036
+ process.off("SIGINT", handler);
12037
+ process.off("SIGTERM", handler);
12038
+ };
12039
+ let service;
12040
+ try {
12041
+ service = await startPresenceService(options);
12042
+ } catch (error2) {
12043
+ detach();
12044
+ io.err(error2 instanceof Error ? error2.message : String(error2));
12045
+ process.exitCode = 1;
12046
+ return;
12047
+ }
12048
+ io.out(`Presence service listening on ${service.url} (loopback only). Stop it with Ctrl+C or \`spell presence stop\`.`);
12049
+ await new Promise((resolve10) => {
12050
+ onSignal = () => {
12051
+ detach();
12052
+ void service.stop().then(() => {
12053
+ io.out("Presence service stopped.");
12054
+ resolve10();
12055
+ });
12056
+ };
12057
+ if (signalled) onSignal();
12058
+ });
12059
+ process.exitCode = 0;
12060
+ }
12061
+ function isHealth(body) {
12062
+ const b = body;
12063
+ return b !== null && typeof b === "object" && b.ok === true && b.contract === "v0" && typeof b.started_at === "string";
12064
+ }
12065
+ async function runPresenceStatus(io = consoleIo, store = {}) {
12066
+ const info = readServerInfo(store);
12067
+ if (info === null) {
12068
+ io.out(NOT_CONFIGURED);
12069
+ return 1;
12070
+ }
12071
+ const result = await callPresence("GET", "/v0/health", void 0, store);
12072
+ if (result.kind === "ok" && isHealth(result.body)) {
12073
+ io.out(`Presence service: ${info.url}`);
12074
+ io.out(` pid: ${info.pid}`);
12075
+ io.out(` version: ${result.body.generator.version}`);
12076
+ io.out(` sessions: ${result.body.sessions}`);
12077
+ return 0;
12078
+ }
12079
+ if (result.kind === "refused" && result.status === 401) {
12080
+ io.out(`The presence service at ${info.url} refused this machine's token (rotated, or ${PRESENCE_ENV.token} is set to another value).`);
12081
+ return 1;
12082
+ }
12083
+ io.out(`The presence service recorded at ${info.url} is not reachable.`);
12084
+ return 1;
12085
+ }
12086
+ async function waitForExit(pid, ms) {
12087
+ const until = Date.now() + ms;
12088
+ while (Date.now() < until) {
12089
+ if (!pidAlive(pid)) return true;
12090
+ await new Promise((r) => setTimeout(r, 50));
12091
+ }
12092
+ return !pidAlive(pid);
12093
+ }
12094
+ async function runPresenceStop(io = consoleIo, store = {}) {
12095
+ const info = readServerInfo(store);
12096
+ if (info === null) {
12097
+ io.out("No presence service is running.");
12098
+ return 0;
12099
+ }
12100
+ const result = await callPresence("GET", "/v0/health", void 0, store);
12101
+ if (result.kind === "ok" && isHealth(result.body) && result.body.started_at === info.started_at) {
12102
+ try {
12103
+ process.kill(info.pid, "SIGTERM");
12104
+ } catch {
12105
+ }
12106
+ if (!await waitForExit(info.pid, 3e3)) {
12107
+ io.out(`Asked the presence service (pid ${info.pid}) to stop; it is still running after 3 s.`);
12108
+ return 0;
12109
+ }
12110
+ const after = readServerInfo(store);
12111
+ if (after !== null && after.started_at === info.started_at) removeServerInfo(store);
12112
+ io.out(`Stopped the presence service (pid ${info.pid}).`);
12113
+ return 0;
12114
+ }
12115
+ if (result.kind === "refused" && result.status === 401) {
12116
+ io.out(`The presence service at ${info.url} refused this machine's token, so it was not stopped.`);
12117
+ return 0;
12118
+ }
12119
+ if (result.kind === "not-configured" && pidAlive(info.pid)) {
12120
+ io.out(`The presence service recorded at ${info.url} (pid ${info.pid}) is running, but there is no token here to confirm it, so it was not stopped.`);
12121
+ return 0;
12122
+ }
12123
+ removeServerInfo(store);
12124
+ io.out("No presence service was answering; removed its stale record.");
12125
+ return 0;
12126
+ }
12127
+ var REPORT_DEADLINE_MS = 2e3;
12128
+ async function withDeadline(work, ms) {
12129
+ let timer;
12130
+ const deadline = new Promise((resolve10) => {
12131
+ timer = setTimeout(() => resolve10({ outcome: null, timedOut: true }), ms);
12132
+ });
12133
+ const run = await Promise.race([work.then((outcome) => ({ outcome, timedOut: false })).catch(() => ({ outcome: null, timedOut: false })), deadline]);
12134
+ clearTimeout(timer);
12135
+ return run;
12136
+ }
12137
+ function finish(run, io) {
12138
+ if (run.outcome?.kind === "refused") io.err(run.outcome.line);
12139
+ return run;
12140
+ }
12141
+ async function runPresenceReport(cwd, flags, io = consoleIo, deps = {}, deadlineMs = REPORT_DEADLINE_MS) {
12142
+ return finish(await withDeadline(sendReport(cwd, flags, deps), deadlineMs), io);
12143
+ }
12144
+ async function runPresenceEnd(cwd, io = consoleIo, deps = {}, deadlineMs = REPORT_DEADLINE_MS) {
12145
+ return finish(await withDeadline(sendEnd(cwd, deps), deadlineMs), io);
12146
+ }
12147
+ async function runPresenceMcp() {
12148
+ await serveMcp({ input: process.stdin, output: process.stdout, version: packageJson.version });
12149
+ }
12150
+
10758
12151
  // src/index.ts
10759
12152
  import { realpathSync as realpathSync2 } from "fs";
10760
- var require3 = createRequire3(import.meta.url);
10761
- var pkg = require3("../package.json");
10762
- var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join29(dirname14(fileURLToPath(import.meta.url)), "assets");
12153
+ var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join31(dirname15(fileURLToPath(import.meta.url)), "assets");
10763
12154
  var program = new Command();
10764
12155
  var USER_FLAG_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 spells shared by every repository on this machine, fanned out to ~/.agents/skills (Codex, Copilot) and ~/.claude/commands (Claude Code)";
10765
12156
  var USER_TIER_AGENTS_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 one agent roster shared by every repository on this machine, rendered to ~/.copilot/agents (VS Code agent modes)";
@@ -10782,7 +12173,7 @@ function manifestFlagsFrom(opts) {
10782
12173
  subjectRoot: opts.subjectRoot
10783
12174
  };
10784
12175
  }
10785
- program.name("spell").description("Arcane framework CLI \u2014 scaffold and manage governance files").version(pkg.version).action(async () => {
12176
+ program.name("spell").description("Arcane framework CLI \u2014 scaffold and manage governance files").version(packageJson.version).action(async () => {
10786
12177
  if (program.args.length > 0) {
10787
12178
  const [attempted] = program.args;
10788
12179
  console.error(`spell: unrecognized command '${attempted}'`);
@@ -10798,7 +12189,7 @@ program.name("spell").description("Arcane framework CLI \u2014 scaffold and mana
10798
12189
  process.exitCode = 1;
10799
12190
  return;
10800
12191
  }
10801
- await printWelcome(pkg.version, process.cwd());
12192
+ await printWelcome(packageJson.version, process.cwd());
10802
12193
  program.outputHelp();
10803
12194
  });
10804
12195
  withManifestFlags(
@@ -10815,7 +12206,7 @@ withManifestFlags(
10815
12206
  },
10816
12207
  targetDirFor(opts),
10817
12208
  ASSETS_DIR,
10818
- pkg.version
12209
+ packageJson.version
10819
12210
  );
10820
12211
  }
10821
12212
  );
@@ -10826,7 +12217,7 @@ program.command("add <components...>").description("Add one or more components t
10826
12217
  { force: opts.force, dryRun: opts.dryRun },
10827
12218
  process.cwd(),
10828
12219
  ASSETS_DIR,
10829
- pkg.version
12220
+ packageJson.version
10830
12221
  );
10831
12222
  }
10832
12223
  );
@@ -10867,11 +12258,11 @@ withManifestFlags(
10867
12258
  },
10868
12259
  targetDirFor(opts),
10869
12260
  ASSETS_DIR,
10870
- pkg.version
12261
+ packageJson.version
10871
12262
  );
10872
12263
  });
10873
12264
  program.command("status").description("Show installed components and check for updates").option("--user", USER_FLAG_DESCRIPTION).action(async (opts) => {
10874
- await runStatus(targetDirFor(opts), pkg.version, { user: opts.user });
12265
+ await runStatus(targetDirFor(opts), packageJson.version, { user: opts.user });
10875
12266
  });
10876
12267
  program.command("state").description("Export this repository's Arcane state as one JSON document (schema https://arcaneai.net/schemas/state/v1)").option("--json", "Print the document to stdout (required; `spell status` is the human view)").option(
10877
12268
  "--online",
@@ -10880,7 +12271,7 @@ program.command("state").description("Export this repository's Arcane state as o
10880
12271
  "--include-local-paths",
10881
12272
  "Hub only: add each registered repository's clone path on this machine. Other machines' paths are never exported"
10882
12273
  ).action(async (opts) => {
10883
- await runState(process.cwd(), pkg.version, opts);
12274
+ await runState(process.cwd(), packageJson.version, opts);
10884
12275
  });
10885
12276
  program.command("uninstall").description("Remove all installed Arcane framework files").option("--yes", "Skip confirmation prompt").option("--dry-run", "Preview what would be removed without deleting files").option("--user", USER_FLAG_DESCRIPTION).action(async (opts) => {
10886
12277
  await runUninstall({ yes: opts.yes, dryRun: opts.dryRun, user: opts.user }, targetDirFor(opts));
@@ -10906,7 +12297,7 @@ program.command("whoami").description("Show who is signed in; exits 1 when signe
10906
12297
  program.command("doctor").description("Check your environment for Arcane prerequisites").option("--fix", "Automatically create missing session continuity files").option("--leaks", "Run only the on-demand secrets/credential scan (ARC-037), skipping the default checks").action(async (opts) => {
10907
12298
  await runDoctor(
10908
12299
  process.cwd(),
10909
- { fix: opts.fix, leaks: opts.leaks, packageVersion: pkg.version },
12300
+ { fix: opts.fix, leaks: opts.leaks, packageVersion: packageJson.version },
10910
12301
  ASSETS_DIR
10911
12302
  );
10912
12303
  });
@@ -10978,6 +12369,33 @@ agentsCommand.command("list").description("Show the current agent roster with as
10978
12369
  const scope = opts.user ? "user" : "repo";
10979
12370
  await runAgentsList(agentsTargetDir(scope, process.cwd()), scope);
10980
12371
  });
12372
+ var presenceCommand = program.command("presence").description("The local presence channel: which session works on what, right now (ARC-060). Loopback only; nothing runs until `serve`");
12373
+ presenceCommand.command("serve").description("Run the presence service in the foreground on 127.0.0.1 until Ctrl+C or `spell presence stop`").option("--port <port>", "Port on 127.0.0.1 (default 27226, or ARCANE_PRESENCE_PORT); 0 picks a free one").option("--rotate", "Replace the token in ~/.arcane/presence/token; every subscriber must re-read it").action(async (opts) => {
12374
+ await runPresenceServe(opts);
12375
+ });
12376
+ presenceCommand.command("status").description("Say whether a presence service is running and answering this machine's token (exit 0) or not (exit 1)").action(async () => {
12377
+ process.exitCode = await runPresenceStatus();
12378
+ });
12379
+ presenceCommand.command("stop").description("Stop the presence service this machine recorded; exits 0 whether or not one was running").action(async () => {
12380
+ process.exitCode = await runPresenceStop();
12381
+ });
12382
+ function collectLine(value, previous) {
12383
+ return [...previous, value];
12384
+ }
12385
+ var exitZero = () => process.exit(0);
12386
+ presenceCommand.command("report").description("Report this checkout's session to the presence service. Silent, exit 0, when no channel is configured; never blocks").requiredOption("--status <status>", "working | waiting | blocked | idle").option("--task <line>", "What the session is doing, in one line").option("--spell <id>", "The spell running, e.g. spell-implement").option("--message <line>", "A free one-line note; an empty value clears it").option("--needs-you <line>", "One line of a Needs you block, '<action> \u2014 <reason>'; repeat per line", collectLine, []).option("--agent <slug>", "The roster agent this session reports as (a slug or an alias); default: the orchestrator").option("--harness <id>", "claude-code | copilot | codex | openclaw | cron | other; default: detected, else other").exitOverride(exitZero).action(async (opts) => {
12387
+ const run = await runPresenceReport(process.cwd(), opts);
12388
+ process.exitCode = 0;
12389
+ if (run.timedOut) process.exit(0);
12390
+ });
12391
+ presenceCommand.command("end").description("End this checkout's session on the presence service. Silent, exit 0, when there is none").exitOverride(exitZero).action(async () => {
12392
+ const run = await runPresenceEnd(process.cwd());
12393
+ process.exitCode = 0;
12394
+ if (run.timedOut) process.exit(0);
12395
+ });
12396
+ presenceCommand.command("mcp").description("Serve the presence channel as an MCP server over stdio (two tools, two resources). Register it in your own client; Arcane writes no client configuration").action(async () => {
12397
+ await runPresenceMcp();
12398
+ });
10981
12399
  if (realpathSync2(process.argv[1]) === fileURLToPath(import.meta.url)) {
10982
12400
  await program.parseAsync(process.argv);
10983
12401
  }