arcane-cli 1.17.3 → 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
@@ -5,7 +5,7 @@ const require = __createRequire(import.meta.url);
5
5
  // src/index.ts
6
6
  import { Command } from "commander";
7
7
  import { fileURLToPath } from "url";
8
- import { dirname as dirname14, join as join29 } from "path";
8
+ import { dirname as dirname15, join as join31 } from "path";
9
9
 
10
10
  // src/package-info.ts
11
11
  import { createRequire } from "module";
@@ -152,21 +152,21 @@ function runGit(cwd, args, options = {}) {
152
152
  windowsHide: true,
153
153
  encoding: "utf8"
154
154
  },
155
- (error, stdout, stderr) => {
156
- if (error) {
157
- if (error.killed) {
155
+ (error2, stdout, stderr) => {
156
+ if (error2) {
157
+ if (error2.killed) {
158
158
  reject(
159
159
  new GitTimeoutError(
160
160
  // NOTE: embeds the raw args, including any secrets a future
161
161
  // network-class caller might pass (e.g. a credentialed
162
162
  // clone URL). No current caller does; redact before one does.
163
163
  `git ${args.join(" ")} timed out after ${timeoutMs}ms (class: ${commandClass})`,
164
- { cause: error }
164
+ { cause: error2 }
165
165
  )
166
166
  );
167
167
  return;
168
168
  }
169
- reject(error);
169
+ reject(error2);
170
170
  return;
171
171
  }
172
172
  resolve10({ stdout, stderr });
@@ -227,8 +227,8 @@ async function correctUnbornMasterDefault(cwd) {
227
227
  try {
228
228
  await runGit(cwd, ["symbolic-ref", "-q", "refs/heads/main"]);
229
229
  return { corrected: false, to: "main", blockedReason: "target-unreadable" };
230
- } catch (error) {
231
- if (error.code !== 1) {
230
+ } catch (error2) {
231
+ if (error2.code !== 1) {
232
232
  return { corrected: false, to: "main", blockedReason: "target-unreadable" };
233
233
  }
234
234
  }
@@ -663,8 +663,8 @@ function isValidTodoDoneBook(value) {
663
663
  if (value !== value.trim() || value.includes(":")) return false;
664
664
  const normalized = path2.posix.normalize(value.replace(/\\/g, "/"));
665
665
  const segments = normalized.split("/");
666
- const basename5 = segments[segments.length - 1] ?? "";
667
- 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());
668
668
  }
669
669
  function manifestPath(targetDir) {
670
670
  return path2.join(targetDir, MANIFEST_FILE);
@@ -1487,13 +1487,13 @@ function isClientShimPath(path5) {
1487
1487
  }
1488
1488
  var FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
1489
1489
  function parsePromptFrontmatter(content) {
1490
- const match = content.match(FRONTMATTER_PATTERN);
1491
- if (!match) {
1490
+ const match2 = content.match(FRONTMATTER_PATTERN);
1491
+ if (!match2) {
1492
1492
  throw new MissingFrontmatterError(
1493
1493
  "Prompt file has no YAML frontmatter block (expected a leading --- ... --- block)."
1494
1494
  );
1495
1495
  }
1496
- const raw = parseYaml(match[1]);
1496
+ const raw = parseYaml(match2[1]);
1497
1497
  const name = raw?.["name"];
1498
1498
  const description = raw?.["description"];
1499
1499
  if (typeof name !== "string" || name.trim() === "") {
@@ -1906,7 +1906,7 @@ function scale(hex, f) {
1906
1906
  }
1907
1907
  function lockupLines(f = 1) {
1908
1908
  const g = f >= 1 ? arcaneGradient : gradient(STOPS.map((c) => scale(c, f)));
1909
- return WORDMARK.map((line) => INDENT + g(line));
1909
+ return WORDMARK.map((line2) => INDENT + g(line2));
1910
1910
  }
1911
1911
  function printFooter(version) {
1912
1912
  const taglineText = ` ${TAGLINE} \xB7 agent-native SDLC \xB7 v${version}`;
@@ -1941,7 +1941,7 @@ async function printWelcome(version, cwd) {
1941
1941
  if (isAnimatable()) {
1942
1942
  const draw = (lines, redraw) => {
1943
1943
  if (redraw) process.stdout.write(`\x1B[${lines.length}A`);
1944
- 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}
1945
1945
  `);
1946
1946
  };
1947
1947
  const fade = [0.1, 0.22, 0.36, 0.52, 0.68, 0.84, 1];
@@ -1955,7 +1955,7 @@ async function printWelcome(version, cwd) {
1955
1955
  await sleep(140);
1956
1956
  }
1957
1957
  } else {
1958
- for (const line of lockupLines()) console.log(line);
1958
+ for (const line2 of lockupLines()) console.log(line2);
1959
1959
  }
1960
1960
  printFooter(version);
1961
1961
  console.log();
@@ -3004,8 +3004,8 @@ async function performAgentSync(targetDir, assetsDir, roster, options) {
3004
3004
  dryRun: options.dryRun,
3005
3005
  fallbackDir: assetsDir
3006
3006
  });
3007
- for (const line of describeFanoutOutcomes(fanout.outcomes, options.dryRun)) {
3008
- console.log(` ${line}`);
3007
+ for (const line2 of describeFanoutOutcomes(fanout.outcomes, options.dryRun)) {
3008
+ console.log(` ${line2}`);
3009
3009
  }
3010
3010
  if (!options.dryRun) {
3011
3011
  await writeManifest(targetDir, { ...manifest, fanout: fanout.record }, { followLinks: true });
@@ -3316,7 +3316,7 @@ async function detectAdoContext(targetDir) {
3316
3316
  } catch {
3317
3317
  return false;
3318
3318
  }
3319
- 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));
3320
3320
  }
3321
3321
 
3322
3322
  // src/modules/hub.ts
@@ -3550,19 +3550,19 @@ function resolveManifestFlags(flags, current, manifestPath2) {
3550
3550
  if (flags.contentSensitivity !== void 0) requested.content_sensitivity = flags.contentSensitivity;
3551
3551
  if (flags.subjectRoot !== void 0) requested.subject_root = flags.subjectRoot;
3552
3552
  if (flags.pushPolicy !== void 0) requested.push_policy = flags.pushPolicy;
3553
- const invalid = (error) => {
3554
- 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];
3555
3555
  const hint = allowed ? ` Valid values: ${allowed.join(", ")}.` : "";
3556
- 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}`);
3557
3557
  };
3558
3558
  if (requested.role !== void 0 && requested.role !== "hub" && requested.role !== "consumer") {
3559
3559
  throw invalid(new ManifestInvalidFieldError(manifestPath2, "role", requested.role));
3560
3560
  }
3561
3561
  try {
3562
3562
  validateManifestFields(requested, manifestPath2);
3563
- } catch (error) {
3564
- if (error instanceof ManifestInvalidFieldError) throw invalid(error);
3565
- throw error;
3563
+ } catch (error2) {
3564
+ if (error2 instanceof ManifestInvalidFieldError) throw invalid(error2);
3565
+ throw error2;
3566
3566
  }
3567
3567
  if (requested.external_provider !== void 0 && requested.tracking_mode !== "external") {
3568
3568
  throw new ManifestFlagError("--external-provider applies only together with --tracking-mode external.");
@@ -3663,8 +3663,8 @@ async function readHooksPath(cwd) {
3663
3663
  try {
3664
3664
  const { stdout } = await runGit(cwd, ["config", "--show-scope", "--get", "core.hooksPath"]);
3665
3665
  raw = stdout.trim();
3666
- } catch (error) {
3667
- if (error.code !== 1) {
3666
+ } catch (error2) {
3667
+ if (error2.code !== 1) {
3668
3668
  return { value: "", scope: "unknown", unreadable: true };
3669
3669
  }
3670
3670
  return void 0;
@@ -3890,7 +3890,7 @@ async function pushTargets(cwd) {
3890
3890
  const { stdout } = await runGit(cwd, ["remote", "get-url", "--push", "--all", remote]);
3891
3891
  targets.push({
3892
3892
  remote,
3893
- urls: stdout.split("\n").map((line) => line.trim()).filter(Boolean)
3893
+ urls: stdout.split("\n").map((line2) => line2.trim()).filter(Boolean)
3894
3894
  });
3895
3895
  } catch {
3896
3896
  targets.push({ remote, urls: [] });
@@ -3904,7 +3904,7 @@ function originalPushUrlKey(remote) {
3904
3904
  async function readLocalAll(cwd, key) {
3905
3905
  try {
3906
3906
  const { stdout } = await runGit(cwd, ["config", "--local", "--get-all", key]);
3907
- return stdout.split("\n").map((line) => line.trim()).filter(Boolean);
3907
+ return stdout.split("\n").map((line2) => line2.trim()).filter(Boolean);
3908
3908
  } catch {
3909
3909
  return [];
3910
3910
  }
@@ -3916,10 +3916,10 @@ async function readScopedAll(cwd, key) {
3916
3916
  } catch {
3917
3917
  return [];
3918
3918
  }
3919
- return stdout.split("\n").filter((line) => line !== "").map((line) => {
3920
- const separator = line.indexOf(" ");
3921
- if (separator === -1) return { scope: "unknown", value: line.trim() };
3922
- 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() };
3923
3923
  });
3924
3924
  }
3925
3925
  async function effectivePushUrls(cwd, remote) {
@@ -3931,8 +3931,8 @@ async function disablePushUrls(cwd) {
3931
3931
  for (const remote of await listRemotes(cwd)) {
3932
3932
  try {
3933
3933
  results.push(await disableOneRemote(cwd, remote));
3934
- } catch (error) {
3935
- results.push({ remote, status: "failed", reason: describeError(error) });
3934
+ } catch (error2) {
3935
+ results.push({ remote, status: "failed", reason: describeError(error2) });
3936
3936
  }
3937
3937
  }
3938
3938
  return results;
@@ -4007,8 +4007,8 @@ async function restorePushUrls(cwd) {
4007
4007
  for (const remote of await listRemotes(cwd)) {
4008
4008
  try {
4009
4009
  results.push(await restoreOneRemote(cwd, remote));
4010
- } catch (error) {
4011
- results.push({ remote, status: "failed", reason: describeError(error) });
4010
+ } catch (error2) {
4011
+ results.push({ remote, status: "failed", reason: describeError(error2) });
4012
4012
  }
4013
4013
  }
4014
4014
  return results;
@@ -4048,8 +4048,8 @@ async function restoreOneRemote(cwd, remote) {
4048
4048
  async function unsetIgnoringMissing(cwd, key) {
4049
4049
  try {
4050
4050
  await runGit(cwd, ["config", "--local", "--unset-all", key]);
4051
- } catch (error) {
4052
- if (error.code !== 5) throw error;
4051
+ } catch (error2) {
4052
+ if (error2.code !== 5) throw error2;
4053
4053
  }
4054
4054
  }
4055
4055
  async function hadNoPushUrl(cwd, remote) {
@@ -4077,8 +4077,8 @@ async function readLegacyRecord(cwd, remote) {
4077
4077
  }
4078
4078
  return { status: "usable", values };
4079
4079
  }
4080
- function describeError(error) {
4081
- const message = error instanceof Error ? error.message : String(error);
4080
+ function describeError(error2) {
4081
+ const message = error2 instanceof Error ? error2.message : String(error2);
4082
4082
  return message.split("\n")[0].trim();
4083
4083
  }
4084
4084
  function hookInForce(outcome) {
@@ -4086,8 +4086,8 @@ function hookInForce(outcome) {
4086
4086
  }
4087
4087
  async function applyBlockedPushControls(cwd, options = {}) {
4088
4088
  const messages = [];
4089
- const say = (level, text) => {
4090
- const message = { level, text };
4089
+ const say = (level, text2) => {
4090
+ const message = { level, text: text2 };
4091
4091
  messages.push(message);
4092
4092
  options.emit?.(message);
4093
4093
  };
@@ -4377,7 +4377,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
4377
4377
  }
4378
4378
  const escapes = findLinkedEscapes(targetDir, plannedFiles);
4379
4379
  if (escapes.length > 0) {
4380
- for (const line of describeLinkedEscapes(targetDir, escapes)) console.error(line);
4380
+ for (const line2 of describeLinkedEscapes(targetDir, escapes)) console.error(line2);
4381
4381
  console.error(
4382
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."
4383
4383
  );
@@ -4566,7 +4566,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
4566
4566
  const flagLine = options.profile ? manifestFlagLine(manifest) : void 0;
4567
4567
  if (flagLine) {
4568
4568
  printInfo("Some setup questions were left unanswered. Answer them without a prompt with:");
4569
- for (const line of flagLine) printInfo(` ${line}`);
4569
+ for (const line2 of flagLine) printInfo(` ${line2}`);
4570
4570
  }
4571
4571
  if (push_policy === "blocked" || push_policy === "guarded") {
4572
4572
  try {
@@ -4758,9 +4758,9 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
4758
4758
  };
4759
4759
  await writeManifest(storeRoot, manifest, { followLinks: true });
4760
4760
  printStep(`\u2728 ${fileCount} Spells installed to ${storeRoot}/spells/`);
4761
- for (const line of describeFanoutOutcomes(fanout.outcomes)) {
4762
- if (line.startsWith("!")) printWarning(line.slice(2));
4763
- 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}`);
4764
4764
  }
4765
4765
  printSuccess(
4766
4766
  `Initialized the user tier \u2014 ${fileCount} spells, ${Object.keys(fanout.record).length} client files recorded`
@@ -4768,7 +4768,7 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
4768
4768
  console.log();
4769
4769
  printSection("\u{1F9ED} Clients");
4770
4770
  console.log();
4771
- for (const line of VSCODE_USER_TIER_NOTE) printInfo(line);
4771
+ for (const line2 of VSCODE_USER_TIER_NOTE) printInfo(line2);
4772
4772
  printInfo(CLAUDE_PRECEDENCE_NOTE);
4773
4773
  console.log();
4774
4774
  printSection("\u{1F680} Next steps");
@@ -5049,11 +5049,11 @@ function computeLcsDiff(base, other) {
5049
5049
  }
5050
5050
  function arraysEqual(a, b) {
5051
5051
  if (a.length !== b.length) return false;
5052
- return a.every((line, idx) => line === b[idx]);
5052
+ return a.every((line2, idx) => line2 === b[idx]);
5053
5053
  }
5054
5054
  var MAX_LINES_FOR_LCS = 4e3;
5055
- function splitLines(text) {
5056
- return text.split("\n");
5055
+ function splitLines(text2) {
5056
+ return text2.split("\n");
5057
5057
  }
5058
5058
  function merge3(base, ours, theirs) {
5059
5059
  const baseLines = splitLines(base);
@@ -5348,9 +5348,9 @@ async function missingTierWarning(scope, spellScope, homeDir) {
5348
5348
  function reportFlagPatch(flagPatch, before, after, dryRun) {
5349
5349
  console.log(`
5350
5350
  ${dryRun ? "[dry-run] Would record" : "Recorded"} from flags:`);
5351
- for (const line of describeFlagPatch(flagPatch)) console.log(` ${line}`);
5351
+ for (const line2 of describeFlagPatch(flagPatch)) console.log(` ${line2}`);
5352
5352
  if (!dryRun && after.push_policy !== before.push_policy) {
5353
- for (const line of pushPolicyNotice(after.push_policy)) console.log(line);
5353
+ for (const line2 of pushPolicyNotice(after.push_policy)) console.log(line2);
5354
5354
  }
5355
5355
  }
5356
5356
  function defaultScopePatch(options, manifest) {
@@ -5623,7 +5623,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5623
5623
  const homeDir = dirname7(targetDir);
5624
5624
  const tierGate = await missingTierWarning(scope, spellScope);
5625
5625
  if (tierGate.length > 0) {
5626
- for (const line of tierGate) console.error(line);
5626
+ for (const line2 of tierGate) console.error(line2);
5627
5627
  console.error(
5628
5628
  " \u2717 Update refused: nothing was written. Install the tier or change spell_scope, then run `spell update` again."
5629
5629
  );
@@ -5666,7 +5666,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5666
5666
  const linkedEscapes = followDirectoryLinks || manifest.components.length === 0 ? [] : findLinkedEscapes(targetDir, await plannedUpdateWrites(manifest.components, scope, spellScope, assetsDir));
5667
5667
  const refusedForLinks = () => {
5668
5668
  if (linkedEscapes.length === 0) return false;
5669
- for (const line of describeLinkedEscapes(targetDir, linkedEscapes)) console.error(line);
5669
+ for (const line2 of describeLinkedEscapes(targetDir, linkedEscapes)) console.error(line2);
5670
5670
  console.error(
5671
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."
5672
5672
  );
@@ -5702,7 +5702,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5702
5702
  registryChanges.missingRequires = missingRequires.filter((m) => !installing.has(m.name)).map((m) => ({ ...m, leftAloneReason: installPlan.leftAlone.find((l) => l.name === m.name)?.reason }));
5703
5703
  }
5704
5704
  const printRegistryChanges = () => {
5705
- for (const line of formatRegistryChanges(registryChanges)) console.log(line);
5705
+ for (const line2 of formatRegistryChanges(registryChanges)) console.log(line2);
5706
5706
  };
5707
5707
  const applyInstalls = async () => {
5708
5708
  const declined = installPlan.leftAlone.filter((l) => l.kind === "new");
@@ -5710,14 +5710,14 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5710
5710
  console.log(options.dryRun ? "\n[dry-run] Components this update would install (ARC-052):" : "\nComponents installed by this update (ARC-052):");
5711
5711
  for (const l of declined) console.log(` ! Not installed: ${l.name} \u2014 ${l.reason}`);
5712
5712
  if (options.dryRun) {
5713
- for (const line of describeInstallPlan(installPlan)) console.log(line);
5713
+ for (const line2 of describeInstallPlan(installPlan)) console.log(line2);
5714
5714
  return [];
5715
5715
  }
5716
5716
  const { installed, lines } = await executeUpdateInstalls(installPlan, targetDir, packageVersion, {
5717
5717
  linkTracked,
5718
5718
  conversions
5719
5719
  });
5720
- for (const line of lines) console.log(line);
5720
+ for (const line2 of lines) console.log(line2);
5721
5721
  return installed;
5722
5722
  };
5723
5723
  let sameVersionRestore = false;
@@ -5744,7 +5744,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
5744
5744
  dryRun: options.dryRun,
5745
5745
  fallbackDir: assetsDir
5746
5746
  });
5747
- 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}`);
5748
5748
  const scopePatch = defaultScopePatch(options, manifest);
5749
5749
  if (!options.dryRun && (JSON.stringify(fanout.record) !== JSON.stringify(manifest.fanout ?? {}) || scopePatch !== void 0)) {
5750
5750
  await writeManifest(targetDir, { ...manifest, ...scopePatch, fanout: fanout.record }, { followLinks: scope === "user" });
@@ -6048,7 +6048,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
6048
6048
  });
6049
6049
  const lines = describeFanoutOutcomes(fanout.outcomes, options.dryRun);
6050
6050
  if (lines.length > 0) console.log();
6051
- for (const line of lines) console.log(` ${line}`);
6051
+ for (const line2 of lines) console.log(` ${line2}`);
6052
6052
  fanoutRecord = fanout.record;
6053
6053
  }
6054
6054
  if (options.dryRun) {
@@ -6073,7 +6073,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
6073
6073
  console.log(
6074
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:`
6075
6075
  );
6076
- for (const line of manifestFlagLine(answered) ?? []) console.log(` ${line}`);
6076
+ for (const line2 of manifestFlagLine(answered) ?? []) console.log(` ${line2}`);
6077
6077
  }
6078
6078
  }
6079
6079
  const updated = {
@@ -6093,7 +6093,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
6093
6093
  if (Object.keys(flagPatch).length > 0) {
6094
6094
  reportFlagPatch(flagPatch, manifest, updated, false);
6095
6095
  } else if (updated.push_policy !== manifest.push_policy) {
6096
- for (const line of pushPolicyNotice(updated.push_policy)) console.log(line);
6096
+ for (const line2 of pushPolicyNotice(updated.push_policy)) console.log(line2);
6097
6097
  }
6098
6098
  if (conflictedFiles.length > 0) {
6099
6099
  console.log(
@@ -6224,8 +6224,8 @@ async function collectVersions(manifest, packageVersion, options) {
6224
6224
  if (feedUrl === "") {
6225
6225
  try {
6226
6226
  feedUrl = getFeedUrl();
6227
- } catch (error) {
6228
- feedError = `Feed URL unavailable: ${error instanceof Error ? error.message : String(error)}`;
6227
+ } catch (error2) {
6228
+ feedError = `Feed URL unavailable: ${error2 instanceof Error ? error2.message : String(error2)}`;
6229
6229
  }
6230
6230
  }
6231
6231
  lookup = feedUrl ? await checkForUpdate(packageVersion, feedUrl, options.cachePath) : { current: packageVersion, latest: null, updateAvailable: false, error: feedError };
@@ -6388,7 +6388,7 @@ async function runStatus(targetDir, packageVersion, options = {}) {
6388
6388
  }
6389
6389
  if (scope === "user") {
6390
6390
  console.log("");
6391
- for (const line of VSCODE_USER_TIER_NOTE) console.log(` ${line}`);
6391
+ for (const line2 of VSCODE_USER_TIER_NOTE) console.log(` ${line2}`);
6392
6392
  console.log(` ${CLAUDE_PRECEDENCE_NOTE}`);
6393
6393
  }
6394
6394
  }
@@ -6408,9 +6408,9 @@ async function collectAgents(targetDir, options = {}) {
6408
6408
  let resolved;
6409
6409
  try {
6410
6410
  resolved = await resolveRoster(targetDir, "repo", options.homeDir);
6411
- } catch (error) {
6412
- if (error instanceof AgentRosterNotFoundError) return { agents: null, warnings: [] };
6413
- throw error;
6411
+ } catch (error2) {
6412
+ if (error2 instanceof AgentRosterNotFoundError) return { agents: null, warnings: [] };
6413
+ throw error2;
6414
6414
  }
6415
6415
  const origin = resolved.fromUserTier ? "user" : "repo";
6416
6416
  const warnings = [];
@@ -6457,18 +6457,18 @@ function titleAfterId(rest) {
6457
6457
  return rest.replace(/^\s*[—–:-]+\s*/u, "").trim();
6458
6458
  }
6459
6459
  function parseDecisions(content) {
6460
- const text = content.replace(/\r\n?/g, "\n");
6460
+ const text2 = content.replace(/\r\n?/g, "\n");
6461
6461
  const positions = [];
6462
- let match;
6462
+ let match2;
6463
6463
  HEADING.lastIndex = 0;
6464
- while ((match = HEADING.exec(text)) !== null) {
6465
- 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 });
6466
6466
  }
6467
6467
  const entries = [];
6468
6468
  const warnings = [];
6469
6469
  positions.forEach(({ id, heading, index }, i) => {
6470
- const end = i + 1 < positions.length ? positions[i + 1].index : text.length;
6471
- 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);
6472
6472
  const dateMatch = /^\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/m.exec(section);
6473
6473
  const statusMatch = /^\*\*Status:\*\*\s*(\S+)/m.exec(section);
6474
6474
  if (!dateMatch && !statusMatch) {
@@ -6497,11 +6497,11 @@ async function collectDecisions(root) {
6497
6497
  let content;
6498
6498
  try {
6499
6499
  content = await readFile12(join14(root, "DECISIONS.md"), "utf8");
6500
- } catch (error) {
6501
- if (error.code === "ENOENT") return { decisions: null, warnings: [] };
6500
+ } catch (error2) {
6501
+ if (error2.code === "ENOENT") return { decisions: null, warnings: [] };
6502
6502
  return {
6503
6503
  decisions: null,
6504
- 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)})`]
6505
6505
  };
6506
6506
  }
6507
6507
  const { entries, warnings } = parseDecisions(content);
@@ -6515,12 +6515,12 @@ async function collectDecisions(root) {
6515
6515
  var ITEM = /^\s*- \[( |x|X)\] (.*)$/;
6516
6516
  var HEADING2 = /^#{1,6}\s+(.+?)\s*#*\s*$/;
6517
6517
  var TEXT_LIMIT = 200;
6518
- function normalizeItemText(text) {
6519
- return text.replace(/\s+/g, " ").trim();
6518
+ function normalizeItemText(text2) {
6519
+ return text2.replace(/\s+/g, " ").trim();
6520
6520
  }
6521
- function itemLocator(section, text) {
6521
+ function itemLocator(section, text2) {
6522
6522
  const slug = section === null ? "" : headingAnchor(section);
6523
- 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);
6524
6524
  return `${slug === "" ? "root" : slug}/${hash}`;
6525
6525
  }
6526
6526
  function parseBook(content) {
@@ -6528,22 +6528,22 @@ function parseBook(content) {
6528
6528
  let section = null;
6529
6529
  let open = 0;
6530
6530
  let done = 0;
6531
- for (const line of content.split(/\r?\n/)) {
6532
- const heading = HEADING2.exec(line);
6531
+ for (const line2 of content.split(/\r?\n/)) {
6532
+ const heading = HEADING2.exec(line2);
6533
6533
  if (heading) {
6534
6534
  section = heading[1];
6535
6535
  continue;
6536
6536
  }
6537
- const item = ITEM.exec(line);
6537
+ const item = ITEM.exec(line2);
6538
6538
  if (!item) continue;
6539
6539
  const checked = item[1] !== " ";
6540
- const text = item[2].trim();
6540
+ const text2 = item[2].trim();
6541
6541
  if (checked) done++;
6542
6542
  else open++;
6543
6543
  items.push({
6544
- text: text.length > TEXT_LIMIT ? text.slice(0, TEXT_LIMIT) : text,
6544
+ text: text2.length > TEXT_LIMIT ? text2.slice(0, TEXT_LIMIT) : text2,
6545
6545
  section,
6546
- locator: itemLocator(section, text),
6546
+ locator: itemLocator(section, text2),
6547
6547
  checked
6548
6548
  });
6549
6549
  }
@@ -6553,11 +6553,11 @@ async function collectBook(root, fileName) {
6553
6553
  let content;
6554
6554
  try {
6555
6555
  content = await readFile13(join15(root, fileName), "utf8");
6556
- } catch (error) {
6557
- if (error.code === "ENOENT") return { book: null, warnings: [] };
6556
+ } catch (error2) {
6557
+ if (error2.code === "ENOENT") return { book: null, warnings: [] };
6558
6558
  return {
6559
6559
  book: null,
6560
- 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)})`]
6561
6561
  };
6562
6562
  }
6563
6563
  return { book: parseBook(content), warnings: [] };
@@ -6608,16 +6608,16 @@ function featureTracking(frontmatter) {
6608
6608
  };
6609
6609
  }
6610
6610
  function parsePrd(content, displayPath) {
6611
- const text = content.replace(/\r\n?/g, "\n");
6612
- const heading = H1.exec(text);
6611
+ const text2 = content.replace(/\r\n?/g, "\n");
6612
+ const heading = H1.exec(text2);
6613
6613
  const empty = { title: heading ? heading[1] : null, status: null, implementation_status: null, tracking: null };
6614
- const match = FRONTMATTER.exec(text);
6615
- 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` };
6616
6616
  let parsed;
6617
6617
  try {
6618
- parsed = parse2(match[1]);
6619
- } catch (error) {
6620
- 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);
6621
6621
  return { fields: empty, warning: `features: ${displayPath} has malformed frontmatter (${reason})` };
6622
6622
  }
6623
6623
  if (!isRecord3(parsed)) return { fields: empty, warning: `features: ${displayPath} has frontmatter that is not a map` };
@@ -6637,11 +6637,11 @@ async function collectFeatures(root) {
6637
6637
  try {
6638
6638
  const entries = await readdir7(dir, { withFileTypes: true });
6639
6639
  slugs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
6640
- } catch (error) {
6641
- if (error.code === "ENOENT") return { features: null, warnings: [] };
6640
+ } catch (error2) {
6641
+ if (error2.code === "ENOENT") return { features: null, warnings: [] };
6642
6642
  return {
6643
6643
  features: null,
6644
- 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)})`]
6645
6645
  };
6646
6646
  }
6647
6647
  const warnings = [];
@@ -6651,9 +6651,9 @@ async function collectFeatures(root) {
6651
6651
  let content;
6652
6652
  try {
6653
6653
  content = await readFile14(join16(dir, slug, PRD_FILE), "utf8");
6654
- } catch (error) {
6655
- if (error.code === "ENOENT") continue;
6656
- 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)})`);
6657
6657
  continue;
6658
6658
  }
6659
6659
  const { fields, warning } = parsePrd(content, path5);
@@ -6817,10 +6817,10 @@ function serializeState(doc) {
6817
6817
  `;
6818
6818
  }
6819
6819
  function toPosixRelative(root, target) {
6820
- const relative4 = path4.relative(root, target);
6821
- if (relative4 === "") return ".";
6822
- if (path4.isAbsolute(relative4)) return relative4.replace(/\\/g, "/");
6823
- 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("/");
6824
6824
  }
6825
6825
 
6826
6826
  // src/modules/state/git.ts
@@ -6842,10 +6842,10 @@ function stripUserinfo(url) {
6842
6842
  function parseWorktreeList(porcelain, root) {
6843
6843
  const worktrees = [];
6844
6844
  for (const block of porcelain.replace(/\r\n/g, "\n").split(/\n\n+/)) {
6845
- const lines = block.split("\n").filter((line) => line.length > 0);
6846
- 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 "));
6847
6847
  if (!pathLine) continue;
6848
- const branchLine = lines.find((line) => line.startsWith("branch "));
6848
+ const branchLine = lines.find((line2) => line2.startsWith("branch "));
6849
6849
  const branch = branchLine ? branchLine.slice("branch ".length).replace(/^refs\/heads\//, "") : null;
6850
6850
  const absolute = resolveRealPath(pathLine.slice("worktree ".length));
6851
6851
  worktrees.push({ path: toPosixRelative(root, absolute), branch });
@@ -6855,12 +6855,12 @@ function parseWorktreeList(porcelain, root) {
6855
6855
  async function probe(read) {
6856
6856
  try {
6857
6857
  return { ok: true, value: await read() };
6858
- } catch (error) {
6859
- return { ok: false, error };
6858
+ } catch (error2) {
6859
+ return { ok: false, error: error2 };
6860
6860
  }
6861
6861
  }
6862
- function describe(error) {
6863
- return error instanceof Error ? error.message : String(error);
6862
+ function describe(error2) {
6863
+ return error2 instanceof Error ? error2.message : String(error2);
6864
6864
  }
6865
6865
  async function collectGit(cwd, root, options = {}) {
6866
6866
  const git = options.runGit ?? runGit;
@@ -6951,8 +6951,8 @@ async function collectGit(cwd, root, options = {}) {
6951
6951
  warnings
6952
6952
  };
6953
6953
  }
6954
- function isTimeout(error) {
6955
- return error instanceof Error && error.name === "GitTimeoutError";
6954
+ function isTimeout(error2) {
6955
+ return error2 instanceof Error && error2.name === "GitTimeoutError";
6956
6956
  }
6957
6957
 
6958
6958
  // src/modules/state/journal.ts
@@ -6961,13 +6961,13 @@ import { join as join17 } from "path";
6961
6961
  var JOURNAL_DIR = "journal";
6962
6962
  var ENTRY_NAME = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/;
6963
6963
  function journalTitle(content) {
6964
- const text = content.replace(/\r\n?/g, "\n");
6965
- 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);
6966
6966
  if (frontmatter) {
6967
6967
  const title = /^title:\s*(.+)$/m.exec(frontmatter[1]);
6968
6968
  if (title) return title[1].trim().replace(/^(["'])(.*)\1$/, "$2");
6969
6969
  }
6970
- const heading = /^# (.+?)\s*$/m.exec(text);
6970
+ const heading = /^# (.+?)\s*$/m.exec(text2);
6971
6971
  return heading ? heading[1].trim() : null;
6972
6972
  }
6973
6973
  async function collectJournal(root) {
@@ -6976,29 +6976,29 @@ async function collectJournal(root) {
6976
6976
  try {
6977
6977
  const entries = await readdir8(dir, { withFileTypes: true });
6978
6978
  names = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
6979
- } catch (error) {
6980
- if (error.code === "ENOENT") return { journal: null, warnings: [] };
6979
+ } catch (error2) {
6980
+ if (error2.code === "ENOENT") return { journal: null, warnings: [] };
6981
6981
  return {
6982
6982
  journal: null,
6983
- 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)})`]
6984
6984
  };
6985
6985
  }
6986
6986
  const warnings = [];
6987
6987
  const journal = [];
6988
6988
  for (const name of names) {
6989
- const match = ENTRY_NAME.exec(name);
6990
- if (!match) {
6989
+ const match2 = ENTRY_NAME.exec(name);
6990
+ if (!match2) {
6991
6991
  warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} does not match YYYY-MM-DD-<slug>.md and was skipped`);
6992
6992
  continue;
6993
6993
  }
6994
6994
  let title = null;
6995
6995
  try {
6996
6996
  title = journalTitle(await readFile15(join17(dir, name), "utf8"));
6997
- } catch (error) {
6998
- 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)})`);
6999
6999
  }
7000
7000
  if (title === null) warnings.push(`indexes.journal: ${JOURNAL_DIR}/${name} has no title: frontmatter or # heading`);
7001
- 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}` });
7002
7002
  }
7003
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);
7004
7004
  return { journal, warnings };
@@ -7021,9 +7021,9 @@ function execFileWithTimeout(file, args, timeoutMs, options = {}) {
7021
7021
  encoding: "utf8",
7022
7022
  ...options.env ? { env: { ...process.env, ...options.env } } : {}
7023
7023
  },
7024
- (error, stdout, stderr) => {
7025
- if (error) {
7026
- reject(error);
7024
+ (error2, stdout, stderr) => {
7025
+ if (error2) {
7026
+ reject(error2);
7027
7027
  return;
7028
7028
  }
7029
7029
  resolve10({ stdout, stderr });
@@ -7073,14 +7073,14 @@ function parseAdoRemote(url) {
7073
7073
  if (ssh) return { org: ssh[1], project: decodeURIComponent(ssh[2]), repo: decodeURIComponent(ssh[3]) };
7074
7074
  return null;
7075
7075
  }
7076
- function describe2(error) {
7077
- if (error instanceof Error) {
7078
- const code = error.code;
7076
+ function describe2(error2) {
7077
+ if (error2 instanceof Error) {
7078
+ const code = error2.code;
7079
7079
  if (code === "ENOENT") return "the CLI is not installed";
7080
- if (error.killed) return `timed out after ${EXTERNAL_CLI_TIMEOUT_MS}ms`;
7081
- 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];
7082
7082
  }
7083
- return String(error);
7083
+ return String(error2);
7084
7084
  }
7085
7085
  function isRecord5(value) {
7086
7086
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -7089,8 +7089,8 @@ async function lookupGitHub(branch, exec) {
7089
7089
  let stdout;
7090
7090
  try {
7091
7091
  ({ stdout } = await exec("gh", ["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,state"], EXTERNAL_CLI_TIMEOUT_MS));
7092
- } catch (error) {
7093
- 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)})`] };
7094
7094
  }
7095
7095
  let parsed;
7096
7096
  try {
@@ -7137,8 +7137,8 @@ async function lookupAdo(branch, remoteUrl, exec, launch) {
7137
7137
  EXTERNAL_CLI_TIMEOUT_MS,
7138
7138
  launch.env ? { env: launch.env } : {}
7139
7139
  ));
7140
- } catch (error) {
7141
- 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)})`] };
7142
7142
  }
7143
7143
  let parsed;
7144
7144
  try {
@@ -7245,17 +7245,17 @@ async function collectRegistry(root, manifest, options) {
7245
7245
  let content;
7246
7246
  try {
7247
7247
  content = await readFile16(join18(root, businessRoot, "registry.json"), "utf8");
7248
- } catch (error) {
7249
- 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)})`;
7250
7250
  return { registry: null, warnings: [`registry: this repository is a hub but ${relativePath} ${reason}`] };
7251
7251
  }
7252
7252
  let raw;
7253
7253
  try {
7254
7254
  raw = JSON.parse(content);
7255
- } catch (error) {
7255
+ } catch (error2) {
7256
7256
  return {
7257
7257
  registry: null,
7258
- 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)})`]
7259
7259
  };
7260
7260
  }
7261
7261
  return { registry: parseRegistry(raw, options), warnings: [] };
@@ -7270,9 +7270,9 @@ async function guarded(section, warnings, run, fallback) {
7270
7270
  const result = await run();
7271
7271
  warnings.push(...result.warnings);
7272
7272
  return result.value;
7273
- } catch (error) {
7274
- if (error instanceof AgentConfigValidationError) throw error;
7275
- 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`);
7276
7276
  return fallback;
7277
7277
  }
7278
7278
  }
@@ -7405,9 +7405,9 @@ async function collectState(cwd, packageVersion, options) {
7405
7405
 
7406
7406
  // src/commands/state.ts
7407
7407
  var USAGE_POINTER = "spell state prints one JSON document to stdout: run `spell state --json` (spell status is the human view).";
7408
- function writeStdout(text) {
7408
+ function writeStdout(text2) {
7409
7409
  return new Promise((resolve10, reject) => {
7410
- process.stdout.write(text, (error) => error ? reject(error) : resolve10());
7410
+ process.stdout.write(text2, (error2) => error2 ? reject(error2) : resolve10());
7411
7411
  });
7412
7412
  }
7413
7413
  async function runState(targetDir, packageVersion, options = {}, deps = {}) {
@@ -7417,28 +7417,28 @@ async function runState(targetDir, packageVersion, options = {}, deps = {}) {
7417
7417
  return;
7418
7418
  }
7419
7419
  const collect = deps.collect ?? collectState;
7420
- let text;
7420
+ let text2;
7421
7421
  try {
7422
7422
  const doc = await collect(targetDir, packageVersion, {
7423
7423
  online: options.online === true,
7424
7424
  includeLocalPaths: options.includeLocalPaths === true
7425
7425
  });
7426
- text = serializeState(doc);
7427
- } catch (error) {
7428
- if (error instanceof ManifestNotFoundError) {
7426
+ text2 = serializeState(doc);
7427
+ } catch (error2) {
7428
+ if (error2 instanceof ManifestNotFoundError) {
7429
7429
  console.error('Not initialized. Run "spell init" first.');
7430
- } else if (error instanceof AgentConfigValidationError) {
7431
- console.error(error.message);
7430
+ } else if (error2 instanceof AgentConfigValidationError) {
7431
+ console.error(error2.message);
7432
7432
  } else {
7433
- console.error(`spell state: ${error instanceof Error ? error.message : String(error)}`);
7433
+ console.error(`spell state: ${error2 instanceof Error ? error2.message : String(error2)}`);
7434
7434
  }
7435
7435
  process.exitCode = 1;
7436
7436
  return;
7437
7437
  }
7438
7438
  try {
7439
- await (deps.write ?? writeStdout)(text);
7440
- } catch (error) {
7441
- 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)})`);
7442
7442
  process.exitCode = 1;
7443
7443
  }
7444
7444
  }
@@ -7590,7 +7590,7 @@ async function uninstallUserTier(options, storeRoot, manifest) {
7590
7590
  previous: manifest.fanout,
7591
7591
  dryRun: true
7592
7592
  });
7593
- for (const line of describeFanoutOutcomes(preview.outcomes, true)) console.log(line);
7593
+ for (const line2 of describeFanoutOutcomes(preview.outcomes, true)) console.log(line2);
7594
7594
  console.log(`[dry-run] Would remove: ${storeRoot}/.arcane.json`);
7595
7595
  console.log(
7596
7596
  `
@@ -7628,7 +7628,7 @@ async function uninstallUserTier(options, storeRoot, manifest) {
7628
7628
  await rm5(join19(storeRoot, "agents.yaml"), { force: true });
7629
7629
  await removeDirectoryIfEmpty(join19(storeRoot, USER_TIER_SPELLS_DIR));
7630
7630
  await removeDirectoryIfEmpty(storeRoot);
7631
- for (const line of describeFanoutOutcomes(fanout.outcomes)) console.log(` ${line}`);
7631
+ for (const line2 of describeFanoutOutcomes(fanout.outcomes)) console.log(` ${line2}`);
7632
7632
  const pruned = fanout.outcomes.filter((o) => o.status === "pruned").length;
7633
7633
  const kept = fanout.outcomes.filter((o) => o.status === "kept-edited").length;
7634
7634
  console.log(
@@ -7931,9 +7931,9 @@ async function fetchAdoMergeTypePolicies(org, project, repoName, branch, launch
7931
7931
  }
7932
7932
  }
7933
7933
  function parseGitHubRemote(url) {
7934
- const match = /github\.com[/:]([^/]+)\/([^/.]+?)(?:\.git)?\/?$/.exec(url);
7935
- if (!match) return null;
7936
- const [, owner, repo] = match;
7934
+ const match2 = /github\.com[/:]([^/]+)\/([^/.]+?)(?:\.git)?\/?$/.exec(url);
7935
+ if (!match2) return null;
7936
+ const [, owner, repo] = match2;
7937
7937
  if (!owner || !repo) return null;
7938
7938
  return { owner, repo };
7939
7939
  }
@@ -7948,9 +7948,9 @@ function decodeAdoSegment(raw) {
7948
7948
  return decoded;
7949
7949
  }
7950
7950
  function parseAdoRemote2(url) {
7951
- const match = /dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?]+)/.exec(url) ?? /([^./]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/?]+)/.exec(url);
7952
- if (!match) return null;
7953
- 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;
7954
7954
  if (!rawOrg || !rawProject || !rawRepo) return null;
7955
7955
  const org = decodeAdoSegment(rawOrg);
7956
7956
  const project = decodeAdoSegment(rawProject);
@@ -7977,28 +7977,28 @@ function stripFrontmatter(content) {
7977
7977
  const block = FRONTMATTER_PATTERN2.exec(content);
7978
7978
  return block ? content.slice(block[0].length) : content;
7979
7979
  }
7980
- function stripFencedCode(text) {
7980
+ function stripFencedCode(text2) {
7981
7981
  const kept = [];
7982
7982
  let fence = null;
7983
- for (const line of text.split(/\r?\n/)) {
7983
+ for (const line2 of text2.split(/\r?\n/)) {
7984
7984
  if (fence) {
7985
- const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line);
7985
+ const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line2);
7986
7986
  if (close && close[1][0] === fence.char && close[1].length >= fence.length) fence = null;
7987
7987
  kept.push("");
7988
7988
  continue;
7989
7989
  }
7990
- const open = /^ {0,3}(`{3,}|~{3,})/.exec(line);
7991
- 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("`"))) {
7992
7992
  fence = { char: open[1][0], length: open[1].length };
7993
7993
  kept.push("");
7994
7994
  continue;
7995
7995
  }
7996
- kept.push(line);
7996
+ kept.push(line2);
7997
7997
  }
7998
7998
  return kept.join("\n");
7999
7999
  }
8000
- function stripInlineCode(text) {
8001
- return text.split(/\n[ \t]*\n/).map((paragraph) => {
8000
+ function stripInlineCode(text2) {
8001
+ return text2.split(/\n[ \t]*\n/).map((paragraph) => {
8002
8002
  let out = "";
8003
8003
  let i = 0;
8004
8004
  while (i < paragraph.length) {
@@ -8039,7 +8039,7 @@ function stripInlineCode(text) {
8039
8039
  function findPlaceholderTokens(content) {
8040
8040
  const prose = stripInlineCode(stripFencedCode(stripFrontmatter(content)));
8041
8041
  const seen = /* @__PURE__ */ new Set();
8042
- 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]}}`);
8043
8043
  return [...seen];
8044
8044
  }
8045
8045
  function parseRuntimePlaceholders(standardContent) {
@@ -8048,8 +8048,8 @@ function parseRuntimePlaceholders(standardContent) {
8048
8048
  if (start === -1 || end === -1 || end < start) return null;
8049
8049
  const region = standardContent.slice(start + RUNTIME_PLACEHOLDERS_START.length, end);
8050
8050
  const tokens = /* @__PURE__ */ new Set();
8051
- for (const match of region.matchAll(/^[ \t]*[-*][ \t]+`(\{[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*\})`/gm)) {
8052
- 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]);
8053
8053
  }
8054
8054
  return tokens;
8055
8055
  }
@@ -8986,9 +8986,9 @@ function parseTermsFileContent(raw) {
8986
8986
  async function readTermsFile(filePath) {
8987
8987
  try {
8988
8988
  return parseTermsFileContent(await readFile21(filePath, "utf8"));
8989
- } catch (error) {
8989
+ } catch (error2) {
8990
8990
  console.warn(
8991
- `\u26A0 arcane: could not read --terms-file "${filePath}": ${error.message}`
8991
+ `\u26A0 arcane: could not read --terms-file "${filePath}": ${error2.message}`
8992
8992
  );
8993
8993
  return [];
8994
8994
  }
@@ -9251,8 +9251,8 @@ var ChunkedSecretStore = class {
9251
9251
  guard(operation, fn) {
9252
9252
  try {
9253
9253
  return fn();
9254
- } catch (error) {
9255
- throw new KeychainUnavailableError(operation, error);
9254
+ } catch (error2) {
9255
+ throw new KeychainUnavailableError(operation, error2);
9256
9256
  }
9257
9257
  }
9258
9258
  /** Reads the stored secret. Never throws for bad data; throws only when the keychain itself fails. */
@@ -9362,9 +9362,9 @@ function resultPagePath(kind) {
9362
9362
  return join24("sign-in", `sign-in-callback-${kind}.html`);
9363
9363
  }
9364
9364
  function styleHashes(html) {
9365
- return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)].map((match) => {
9366
- const text = match[1].replace(/\r\n?/g, "\n");
9367
- 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")}'`;
9368
9368
  });
9369
9369
  }
9370
9370
  function resultPageHeaders(html) {
@@ -9448,8 +9448,8 @@ async function loadIdentityConfiguration(environment, options = {}) {
9448
9448
  const response = await fetchImpl(environment.discoveryUrl, { redirect: "manual" });
9449
9449
  if (!response.ok) throw new Error(`discovery returned HTTP ${response.status}`);
9450
9450
  metadata = await response.json();
9451
- } catch (error) {
9452
- throw new IdentityUnreachableError(environment.signInHost, error);
9451
+ } catch (error2) {
9452
+ throw new IdentityUnreachableError(environment.signInHost, error2);
9453
9453
  }
9454
9454
  if (typeof metadata.issuer !== "string" || !metadata.token_endpoint || !metadata.authorization_endpoint) {
9455
9455
  throw new IdentityUnreachableError(environment.signInHost, new Error("discovery document incomplete"));
@@ -9574,8 +9574,8 @@ async function browserLogin(config, options) {
9574
9574
  const result = toTokenResult(response);
9575
9575
  await options.onValidated?.(result);
9576
9576
  redirectToResult({ kind: "success", result });
9577
- }).catch((error) => {
9578
- redirectToResult({ kind: "failure", error });
9577
+ }).catch((error2) => {
9578
+ redirectToResult({ kind: "failure", error: error2 });
9579
9579
  });
9580
9580
  });
9581
9581
  timer = setTimeout(() => reject(new LoginTimeoutError()), timeoutMs);
@@ -9607,7 +9607,7 @@ function openInDefaultBrowser(url) {
9607
9607
  const [command, args] = process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
9608
9608
  return new Promise((resolve10) => {
9609
9609
  try {
9610
- const child = execFile3(command, args, { windowsHide: true }, (error) => resolve10(!error));
9610
+ const child = execFile3(command, args, { windowsHide: true }, (error2) => resolve10(!error2));
9611
9611
  child.on("error", () => resolve10(false));
9612
9612
  } catch {
9613
9613
  resolve10(false);
@@ -9713,11 +9713,11 @@ var FileSecretBackend = class {
9713
9713
  writeFileSync(tmp, JSON.stringify(entries), { encoding: "utf8", mode: 384 });
9714
9714
  try {
9715
9715
  this.restrict(tmp);
9716
- } catch (error) {
9716
+ } catch (error2) {
9717
9717
  rmSync(tmp, { force: true });
9718
9718
  throw new Error(
9719
9719
  "Could not restrict the session file to your account, so it was not written.",
9720
- { cause: error }
9720
+ { cause: error2 }
9721
9721
  );
9722
9722
  }
9723
9723
  renameSync(tmp, this.filePath);
@@ -9772,9 +9772,9 @@ function createSessionStorage(options = {}) {
9772
9772
  try {
9773
9773
  const fromKeychain = interpret(keychain.load(), "keychain");
9774
9774
  if (fromKeychain) return fromKeychain;
9775
- } catch (error) {
9776
- if (!(error instanceof KeychainUnavailableError)) throw error;
9777
- keychainError = error;
9775
+ } catch (error2) {
9776
+ if (!(error2 instanceof KeychainUnavailableError)) throw error2;
9777
+ keychainError = error2;
9778
9778
  }
9779
9779
  if (fileBackend.exists()) {
9780
9780
  const fromFile = interpret(file.load(), "file");
@@ -9802,8 +9802,8 @@ function createSessionStorage(options = {}) {
9802
9802
  const hadKeychainSession = keychain.load().state !== "signed-out";
9803
9803
  keychain.clear();
9804
9804
  if (hadKeychainSession) removed.push("keychain");
9805
- } catch (error) {
9806
- keychainError = error;
9805
+ } catch (error2) {
9806
+ keychainError = error2;
9807
9807
  }
9808
9808
  if (fileBackend.exists()) {
9809
9809
  fileBackend.remove();
@@ -9833,8 +9833,8 @@ function defaultLoginDeps(assetsDir) {
9833
9833
  }
9834
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.";
9835
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.";
9836
- function safeErrorText(error) {
9837
- const message = error instanceof Error ? error.message : String(error);
9836
+ function safeErrorText(error2) {
9837
+ const message = error2 instanceof Error ? error2.message : String(error2);
9838
9838
  return message.split("\n")[0].replace(/([?&](?:code|state|id_token|access_token|refresh_token)=)[^&\s]+/gi, "$1[redacted]");
9839
9839
  }
9840
9840
  function toSessionRecord(result, environment) {
@@ -9856,13 +9856,13 @@ async function runLogin(options, deps) {
9856
9856
  let environment;
9857
9857
  try {
9858
9858
  environment = deps.environment();
9859
- } catch (error) {
9860
- if (error instanceof UnknownEnvironmentError) {
9861
- console.error(error.message);
9859
+ } catch (error2) {
9860
+ if (error2 instanceof UnknownEnvironmentError) {
9861
+ console.error(error2.message);
9862
9862
  process.exit(1);
9863
9863
  return;
9864
9864
  }
9865
- throw error;
9865
+ throw error2;
9866
9866
  }
9867
9867
  if (!options.deviceCode && !deps.isInteractive()) {
9868
9868
  console.error(
@@ -9874,9 +9874,9 @@ async function runLogin(options, deps) {
9874
9874
  let config;
9875
9875
  try {
9876
9876
  config = await deps.loadConfiguration(environment);
9877
- } catch (error) {
9877
+ } catch (error2) {
9878
9878
  console.error(
9879
- 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)}`
9880
9880
  );
9881
9881
  process.exit(1);
9882
9882
  return;
@@ -9912,15 +9912,15 @@ async function runLogin(options, deps) {
9912
9912
  });
9913
9913
  if (!record) throw new Error("The browser sign-in ended without storing a session.");
9914
9914
  }
9915
- } catch (error) {
9916
- if (error instanceof KeychainUnavailableError) {
9915
+ } catch (error2) {
9916
+ if (error2 instanceof KeychainUnavailableError) {
9917
9917
  console.error(
9918
- `${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).`
9919
9919
  );
9920
- } else if (error instanceof LoginTimeoutError || error instanceof LoginDeniedError) {
9921
- console.error(`${error.message} Nothing was changed.`);
9920
+ } else if (error2 instanceof LoginTimeoutError || error2 instanceof LoginDeniedError) {
9921
+ console.error(`${error2.message} Nothing was changed.`);
9922
9922
  } else {
9923
- 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.`);
9924
9924
  }
9925
9925
  process.exit(1);
9926
9926
  return;
@@ -9935,21 +9935,21 @@ function runLogout(storage = createSessionStorage()) {
9935
9935
  let removed;
9936
9936
  try {
9937
9937
  removed = storage.clear().removed;
9938
- } catch (error) {
9939
- if (error instanceof SessionClearError) {
9940
- 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. " : "";
9941
9941
  console.error(
9942
- `${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.`
9943
9943
  );
9944
9944
  process.exit(1);
9945
9945
  return;
9946
9946
  }
9947
- if (error instanceof KeychainUnavailableError) {
9948
- 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.`);
9949
9949
  process.exit(1);
9950
9950
  return;
9951
9951
  }
9952
- throw error;
9952
+ throw error2;
9953
9953
  }
9954
9954
  if (removed.length === 0) {
9955
9955
  printSuccess("Nothing was stored; you were already signed out.");
@@ -9973,13 +9973,13 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
9973
9973
  let loaded;
9974
9974
  try {
9975
9975
  loaded = deps.storage.load();
9976
- } catch (error) {
9977
- if (error instanceof KeychainUnavailableError) {
9978
- 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.`);
9979
9979
  process.exit(1);
9980
9980
  return;
9981
9981
  }
9982
- throw error;
9982
+ throw error2;
9983
9983
  }
9984
9984
  if (loaded.state === "signed-out") {
9985
9985
  console.error("Not signed in. Run `spell login`.");
@@ -10011,9 +10011,9 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
10011
10011
  const fresh = await deps.refreshSession(config, record.refreshToken, SIGN_IN_SCOPES);
10012
10012
  deps.storage.save(toSessionRecord(fresh, environment), { insecure: source === "file" });
10013
10013
  printSuccess(`Verified with the identity service at ${environment.signInHost}; the session is active.`);
10014
- } catch (error) {
10014
+ } catch (error2) {
10015
10015
  console.error(
10016
- `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.`
10017
10017
  );
10018
10018
  process.exit(1);
10019
10019
  }
@@ -10033,11 +10033,11 @@ import { join as join26 } from "path";
10033
10033
 
10034
10034
  // src/modules/show-report/plan-parser.ts
10035
10035
  function parseFrontmatter(content) {
10036
- const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
10037
- if (!match) return {};
10036
+ const match2 = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
10037
+ if (!match2) return {};
10038
10038
  const fields = {};
10039
- for (const line of match[1].split(/\r?\n/)) {
10040
- 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);
10041
10041
  if (kv) fields[kv[1]] = kv[2].trim();
10042
10042
  }
10043
10043
  return fields;
@@ -10105,7 +10105,7 @@ function parseEpics(content) {
10105
10105
  }
10106
10106
  function extractSection(content, headingPrefix) {
10107
10107
  const lines = content.split(/\r?\n/);
10108
- const startIndex = lines.findIndex((line) => line.startsWith(headingPrefix));
10108
+ const startIndex = lines.findIndex((line2) => line2.startsWith(headingPrefix));
10109
10109
  if (startIndex === -1) return null;
10110
10110
  let endIndex = lines.length;
10111
10111
  for (let i = startIndex + 1; i < lines.length; i += 1) {
@@ -10127,11 +10127,11 @@ function parseParkedSection(content) {
10127
10127
  }));
10128
10128
  }
10129
10129
  return unwrapBullets(body).flatMap((bullet) => {
10130
- const match = /^\*\*(.+?)\*\*(.*?)\s*—\s*(.+)$/.exec(bullet);
10131
- if (!match) return [];
10132
- const tail = match[2].replace(/\([^)]*\)/g, "").replace(/\s+/g, " ").trim();
10133
- const title = tail ? `${match[1].trim()} ${tail}` : match[1].trim();
10134
- 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()) }];
10135
10135
  });
10136
10136
  }
10137
10137
  function unwrapBullets(body) {
@@ -10141,12 +10141,12 @@ function unwrapBullets(body) {
10141
10141
  if (current) bullets.push(current.join(" ").replace(/\s+/g, " ").trim());
10142
10142
  current = null;
10143
10143
  };
10144
- for (const line of body.split(/\r?\n/)) {
10145
- if (/^- /.test(line)) {
10144
+ for (const line2 of body.split(/\r?\n/)) {
10145
+ if (/^- /.test(line2)) {
10146
10146
  flush();
10147
- current = [line.slice(2).trim()];
10147
+ current = [line2.slice(2).trim()];
10148
10148
  } else if (current !== null) {
10149
- if (/^\s+\S/.test(line)) current.push(line.trim());
10149
+ if (/^\s+\S/.test(line2)) current.push(line2.trim());
10150
10150
  else flush();
10151
10151
  }
10152
10152
  }
@@ -10164,8 +10164,8 @@ function computeCoverageOutcome(content) {
10164
10164
  const dispositioned = total - parkedCount;
10165
10165
  return `${total} of ${total} items dispositioned \u2014 ${dispositioned} routed to an epic, ${parkedCount} parked`;
10166
10166
  }
10167
- function stripMarkdown(text) {
10168
- 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();
10169
10169
  }
10170
10170
 
10171
10171
  // src/modules/show-report/queue-parser.ts
@@ -10211,8 +10211,8 @@ function parseVerificationLedgerSection(content, programNameWords) {
10211
10211
  }
10212
10212
  return rows;
10213
10213
  }
10214
- function stripMarkdown2(text) {
10215
- 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();
10216
10216
  }
10217
10217
 
10218
10218
  // src/modules/show-report/sources.ts
@@ -10250,8 +10250,8 @@ async function getCloseCommit(cwd, completedDate, generatedOutputs = []) {
10250
10250
  }
10251
10251
  const log = await runGitTextOrNull(cwd, ["log", "--format=%H%x09%cs", "HEAD", ...excludeOutputs(generatedOutputs)]);
10252
10252
  if (log === null || log === "") return null;
10253
- for (const line of log.split("\n")) {
10254
- const [sha, landed] = line.split(" ");
10253
+ for (const line2 of log.split("\n")) {
10254
+ const [sha, landed] = line2.split(" ");
10255
10255
  if (sha && landed && landed <= completedDate) return sha;
10256
10256
  }
10257
10257
  return null;
@@ -10348,9 +10348,9 @@ function buildNeedsYou(queueEntries) {
10348
10348
  return entry;
10349
10349
  });
10350
10350
  }
10351
- function truncateAtWord(text, limit) {
10352
- if (text.length <= limit) return text;
10353
- 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);
10354
10354
  const lastSpace = window.lastIndexOf(" ");
10355
10355
  const head = lastSpace > 0 ? window.slice(0, lastSpace) : window;
10356
10356
  return `${head.replace(/[\s,;:.—-]+$/, "")}\u2026`;
@@ -10476,11 +10476,11 @@ var CATEGORY_ORDER = [
10476
10476
  "docs",
10477
10477
  "platform"
10478
10478
  ];
10479
- function escapeHtml(text) {
10480
- 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;");
10481
10481
  }
10482
- function inlineMarkupToHtml(text) {
10483
- const escaped = escapeHtml(text);
10482
+ function inlineMarkupToHtml(text2) {
10483
+ const escaped = escapeHtml(text2);
10484
10484
  const withLinks = escaped.replace(
10485
10485
  /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,
10486
10486
  '<a href="$2">$1</a>'
@@ -10552,12 +10552,12 @@ function applyReportTheme(html, theme) {
10552
10552
  if (!radioTagPattern(theme).test(html)) return { html, applied: false };
10553
10553
  let result = html;
10554
10554
  for (const id of REPORT_THEMES) {
10555
- const match = radioTagPattern(id).exec(result);
10556
- if (!match) continue;
10557
- const original = match[0];
10555
+ const match2 = radioTagPattern(id).exec(result);
10556
+ if (!match2) continue;
10557
+ const original = match2[0];
10558
10558
  const replaced = id === theme ? withChecked(original) : withoutChecked(original);
10559
10559
  if (replaced !== original) {
10560
- 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);
10561
10561
  }
10562
10562
  }
10563
10563
  return { html: result, applied: true };
@@ -10600,9 +10600,9 @@ async function discoverPrograms(rootDir) {
10600
10600
  let entries;
10601
10601
  try {
10602
10602
  entries = (await readdir13(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
10603
- } catch (error) {
10604
- if (error.code === "ENOENT") return { programs: [], skipped: [] };
10605
- throw error;
10603
+ } catch (error2) {
10604
+ if (error2.code === "ENOENT") return { programs: [], skipped: [] };
10605
+ throw error2;
10606
10606
  }
10607
10607
  const programs = [];
10608
10608
  const skipped = [];
@@ -10624,9 +10624,9 @@ function normalizeLineEndings2(content) {
10624
10624
  async function readOptionalFile(path5) {
10625
10625
  try {
10626
10626
  return await readFile24(path5, "utf8");
10627
- } catch (error) {
10628
- if (error.code === "ENOENT") return null;
10629
- throw error;
10627
+ } catch (error2) {
10628
+ if (error2.code === "ENOENT") return null;
10629
+ throw error2;
10630
10630
  }
10631
10631
  }
10632
10632
  async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
@@ -10757,9 +10757,1400 @@ async function runReport(targetDir, options = {}, assetsDir) {
10757
10757
  for (const path5 of result.repaired) console.log(` ${path5}`);
10758
10758
  }
10759
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
+
10760
12151
  // src/index.ts
10761
12152
  import { realpathSync as realpathSync2 } from "fs";
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)";
@@ -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
  }