arcane-cli 1.11.4 → 1.12.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
@@ -6,7 +6,7 @@ const require = __createRequire(import.meta.url);
6
6
  import { Command } from "commander";
7
7
  import { createRequire as createRequire3 } from "module";
8
8
  import { fileURLToPath } from "url";
9
- import { dirname as dirname12, join as join21 } from "path";
9
+ import { dirname as dirname12, join as join22 } from "path";
10
10
 
11
11
  // src/commands/init.ts
12
12
  import { select as select3, confirm as confirm2, input as input3 } from "@inquirer/prompts";
@@ -7715,21 +7715,25 @@ var ChunkedSecretStore = class {
7715
7715
  };
7716
7716
 
7717
7717
  // src/modules/identity-config.ts
7718
+ function defineIdentityEnvironment(config) {
7719
+ return {
7720
+ ...config,
7721
+ discoveryUrl: `https://${config.signInHost}/${config.tenantId}/v2.0/.well-known/openid-configuration`
7722
+ };
7723
+ }
7718
7724
  var IDENTITY_ENVIRONMENTS = {
7719
- production: {
7725
+ production: defineIdentityEnvironment({
7720
7726
  environment: "production",
7721
7727
  tenantId: "9f8edbfb-291c-4c4a-b9cc-8820a04f8973",
7722
7728
  signInHost: "arcaneai.ciamlogin.com",
7723
- clientId: "7cd8f05d-9979-4458-8ca2-47c67748c41f",
7724
- discoveryUrl: "https://arcaneai.ciamlogin.com/9f8edbfb-291c-4c4a-b9cc-8820a04f8973/v2.0/.well-known/openid-configuration"
7725
- },
7726
- development: {
7729
+ clientId: "7cd8f05d-9979-4458-8ca2-47c67748c41f"
7730
+ }),
7731
+ development: defineIdentityEnvironment({
7727
7732
  environment: "development",
7728
7733
  tenantId: "b365de0e-3026-4afe-bf5b-556fccb4ffcf",
7729
7734
  signInHost: "arcaneaidev.ciamlogin.com",
7730
- clientId: "651be720-83cc-452b-8346-abf20713ad12",
7731
- discoveryUrl: "https://arcaneaidev.ciamlogin.com/b365de0e-3026-4afe-bf5b-556fccb4ffcf/v2.0/.well-known/openid-configuration"
7732
- }
7735
+ clientId: "651be720-83cc-452b-8346-abf20713ad12"
7736
+ })
7733
7737
  };
7734
7738
  var SIGN_IN_SCOPES = ["openid", "profile", "email", "offline_access"];
7735
7739
  var ENVIRONMENT_VARIABLE = "ARCANE_ENVIRONMENT";
@@ -7752,6 +7756,70 @@ function resolveIdentityEnvironment(env = process.env) {
7752
7756
  import { createServer } from "http";
7753
7757
  import { execFile as execFile3 } from "child_process";
7754
7758
  import * as client from "openid-client";
7759
+
7760
+ // src/modules/result-pages.ts
7761
+ import { createHash as createHash3 } from "crypto";
7762
+ import { readFile as readFile16 } from "fs/promises";
7763
+ import { join as join17 } from "path";
7764
+ var RESULT_KINDS = ["success", "denied", "failure"];
7765
+ function resultPagePath(kind) {
7766
+ return join17("sign-in", `sign-in-callback-${kind}.html`);
7767
+ }
7768
+ function styleHashes(html) {
7769
+ return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)].map((match) => {
7770
+ const text = match[1].replace(/\r\n?/g, "\n");
7771
+ return `'sha256-${createHash3("sha256").update(text, "utf8").digest("base64")}'`;
7772
+ });
7773
+ }
7774
+ function resultPageHeaders(html) {
7775
+ const hashes = [...new Set(styleHashes(html))];
7776
+ return {
7777
+ "cache-control": "no-store",
7778
+ "content-security-policy": [
7779
+ "default-src 'none'",
7780
+ `style-src ${hashes.length > 0 ? hashes.join(" ") : "'none'"}`,
7781
+ "img-src data:",
7782
+ "font-src data:",
7783
+ "base-uri 'none'",
7784
+ "form-action 'none'",
7785
+ "frame-ancestors 'none'"
7786
+ ].join("; "),
7787
+ "content-type": "text/html; charset=utf-8",
7788
+ "referrer-policy": "no-referrer",
7789
+ "x-content-type-options": "nosniff"
7790
+ };
7791
+ }
7792
+ function resultPage(html) {
7793
+ return { html, headers: resultPageHeaders(html) };
7794
+ }
7795
+ function plainPage(heading, next) {
7796
+ return resultPage(
7797
+ [
7798
+ '<!doctype html><html lang="en"><meta charset="utf-8">',
7799
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
7800
+ `<title>${heading} \u2014 Arcane</title>`,
7801
+ "<style>:root{color-scheme:light dark}body{font-family:system-ui,sans-serif;margin:3rem;line-height:1.5}</style>",
7802
+ `<body><h1>${heading}</h1><p>${next}</p></body></html>`
7803
+ ].join("")
7804
+ );
7805
+ }
7806
+ var PLAIN_RESULT_PAGES = {
7807
+ success: plainPage("Signed in to Arcane", "Return to your terminal. Your browser can be closed now."),
7808
+ denied: plainPage("Sign-in was not completed", "Return to your terminal to try again. No new session was stored."),
7809
+ failure: plainPage("Could not finish sign-in", "Return to your terminal for details. No new session was stored.")
7810
+ };
7811
+ async function loadResultPages(assetsDir) {
7812
+ try {
7813
+ const [success, denied, failure] = await Promise.all(
7814
+ RESULT_KINDS.map(async (kind) => resultPage(await readFile16(join17(assetsDir, resultPagePath(kind)), "utf8")))
7815
+ );
7816
+ return { success, denied, failure };
7817
+ } catch {
7818
+ return null;
7819
+ }
7820
+ }
7821
+
7822
+ // src/modules/oidc.ts
7755
7823
  var IdentityUnreachableError = class extends Error {
7756
7824
  constructor(host, cause) {
7757
7825
  super(`Could not reach the identity service at ${host}.`);
@@ -7765,6 +7833,12 @@ var LoginTimeoutError = class extends Error {
7765
7833
  this.name = "LoginTimeoutError";
7766
7834
  }
7767
7835
  };
7836
+ var LoginBrowserUnavailableError = class extends Error {
7837
+ constructor() {
7838
+ super("No browser could be opened. Run `spell login --device-code` and finish signing in on another device.");
7839
+ this.name = "LoginBrowserUnavailableError";
7840
+ }
7841
+ };
7768
7842
  var LoginDeniedError = class extends Error {
7769
7843
  constructor(code) {
7770
7844
  super(`The identity service refused the sign-in (${code}).`);
@@ -7807,16 +7881,8 @@ function toTokenResult(tokens, previousRefreshToken) {
7807
7881
  expiresAt: new Date(now.getTime() + seconds * 1e3)
7808
7882
  };
7809
7883
  }
7810
- var CLOSE_PAGE = [
7811
- '<!doctype html><meta charset="utf-8"><title>Arcane</title>',
7812
- '<body style="font-family:system-ui;margin:3rem"><h1>Signed in to Arcane</h1>',
7813
- "<p>You can close this window and go back to the terminal.</p></body>"
7814
- ].join("");
7815
- var FAILED_PAGE = [
7816
- '<!doctype html><meta charset="utf-8"><title>Arcane</title>',
7817
- '<body style="font-family:system-ui;margin:3rem"><h1>Sign-in not completed</h1>',
7818
- "<p>Go back to the terminal for what to do next.</p></body>"
7819
- ].join("");
7884
+ var resultPath = (kind) => `/result/${kind}`;
7885
+ var RESULT_PATHS = new Set(RESULT_KINDS.map(resultPath));
7820
7886
  async function browserLogin(config, options) {
7821
7887
  const codeVerifier = client.randomPKCECodeVerifier();
7822
7888
  const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
@@ -7841,8 +7907,11 @@ async function browserLogin(config, options) {
7841
7907
  state,
7842
7908
  nonce
7843
7909
  });
7910
+ const pages = options.pages ?? PLAIN_RESULT_PAGES;
7844
7911
  const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
7845
7912
  let timer;
7913
+ let callbackConsumed = false;
7914
+ let browserResult;
7846
7915
  const close = () => {
7847
7916
  if (timer) clearTimeout(timer);
7848
7917
  server.closeAllConnections?.();
@@ -7851,14 +7920,53 @@ async function browserLogin(config, options) {
7851
7920
  const tokens = new Promise((resolve5, reject) => {
7852
7921
  server.on("request", (req, res) => {
7853
7922
  const url = new URL(req.url ?? "/", redirectUri);
7923
+ if (RESULT_PATHS.has(url.pathname)) {
7924
+ if (!browserResult || url.pathname !== resultPath(browserResult.kind) || url.search !== "") {
7925
+ res.writeHead(404).end();
7926
+ return;
7927
+ }
7928
+ const served = browserResult;
7929
+ const page = pages[served.kind];
7930
+ const settle = () => {
7931
+ if (served.kind === "success") resolve5(served.result);
7932
+ else reject(served.error);
7933
+ };
7934
+ res.once("finish", settle).once("close", settle);
7935
+ res.writeHead(200, page.headers).end(page.html);
7936
+ return;
7937
+ }
7854
7938
  if (url.pathname !== "/" || !url.searchParams.has("code") && !url.searchParams.has("error")) {
7855
7939
  res.writeHead(404).end();
7856
7940
  return;
7857
7941
  }
7942
+ if (callbackConsumed) {
7943
+ res.writeHead(409, { "cache-control": "no-store" }).end();
7944
+ return;
7945
+ }
7946
+ if (url.searchParams.get("state") !== state) {
7947
+ res.writeHead(400, pages.failure.headers).end(pages.failure.html);
7948
+ return;
7949
+ }
7950
+ callbackConsumed = true;
7951
+ const redirectToResult = (result) => {
7952
+ browserResult = result;
7953
+ res.writeHead(303, {
7954
+ "cache-control": "no-store",
7955
+ location: resultPath(result.kind),
7956
+ "referrer-policy": "no-referrer"
7957
+ }).end();
7958
+ if (result.kind === "success") {
7959
+ if (timer) clearTimeout(timer);
7960
+ timer = setTimeout(() => resolve5(result.result), Math.min(timeoutMs, 5e3));
7961
+ }
7962
+ };
7858
7963
  const errorCode = url.searchParams.get("error");
7859
7964
  if (errorCode) {
7860
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(FAILED_PAGE);
7861
- reject(new LoginDeniedError(errorCode.replace(/[^a-z_]/gi, "")));
7965
+ const code = errorCode.replace(/[^a-z_]/gi, "");
7966
+ redirectToResult({
7967
+ kind: code === "access_denied" ? "denied" : "failure",
7968
+ error: new LoginDeniedError(code)
7969
+ });
7862
7970
  return;
7863
7971
  }
7864
7972
  client.authorizationCodeGrant(config, url, {
@@ -7866,12 +7974,12 @@ async function browserLogin(config, options) {
7866
7974
  expectedState: state,
7867
7975
  expectedNonce: nonce,
7868
7976
  idTokenExpected: true
7869
- }).then((response) => {
7870
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(CLOSE_PAGE);
7871
- resolve5(toTokenResult(response));
7977
+ }).then(async (response) => {
7978
+ const result = toTokenResult(response);
7979
+ await options.onValidated?.(result);
7980
+ redirectToResult({ kind: "success", result });
7872
7981
  }).catch((error) => {
7873
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(FAILED_PAGE);
7874
- reject(error);
7982
+ redirectToResult({ kind: "failure", error });
7875
7983
  });
7876
7984
  });
7877
7985
  timer = setTimeout(() => reject(new LoginTimeoutError()), timeoutMs);
@@ -7880,7 +7988,10 @@ async function browserLogin(config, options) {
7880
7988
  });
7881
7989
  try {
7882
7990
  const opened = await options.openUrl(authorizationUrl.href);
7883
- if (!opened) options.onCannotOpen?.(authorizationUrl.href);
7991
+ if (!opened) {
7992
+ options.onCannotOpen?.();
7993
+ throw new LoginBrowserUnavailableError();
7994
+ }
7884
7995
  return await tokens;
7885
7996
  } finally {
7886
7997
  close();
@@ -7912,7 +8023,7 @@ function openInDefaultBrowser(url) {
7912
8023
  import { execFileSync } from "child_process";
7913
8024
  import { mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync, existsSync as existsSync2 } from "fs";
7914
8025
  import { userInfo } from "os";
7915
- import { dirname as dirname10, join as join17 } from "path";
8026
+ import { dirname as dirname10, join as join18 } from "path";
7916
8027
 
7917
8028
  // src/modules/keychain-backend.ts
7918
8029
  import { createRequire as createRequire2 } from "module";
@@ -7938,6 +8049,17 @@ function createKeychainBackend(service = KEYCHAIN_SERVICE) {
7938
8049
 
7939
8050
  // src/modules/session-store.ts
7940
8051
  var INSECURE_SESSION_FILE_NAME = "session-insecure.json";
8052
+ var SessionClearError = class extends Error {
8053
+ constructor(keychainError, removed) {
8054
+ super(keychainError.message);
8055
+ this.keychainError = keychainError;
8056
+ this.removed = removed;
8057
+ this.name = "SessionClearError";
8058
+ this.cause = keychainError;
8059
+ }
8060
+ keychainError;
8061
+ removed;
8062
+ };
7941
8063
  function isIsoDate(value) {
7942
8064
  return typeof value === "string" && !Number.isNaN(Date.parse(value));
7943
8065
  }
@@ -8035,7 +8157,7 @@ function restrictToCurrentUser(filePath) {
8035
8157
  });
8036
8158
  }
8037
8159
  function insecureSessionFilePath(homeDir) {
8038
- return join17(userTierRoot(homeDir), INSECURE_SESSION_FILE_NAME);
8160
+ return join18(userTierRoot(homeDir), INSECURE_SESSION_FILE_NAME);
8039
8161
  }
8040
8162
  function createSessionStorage(options = {}) {
8041
8163
  const keychain = new ChunkedSecretStore(options.keychainBackend ?? createKeychainBackend());
@@ -8081,8 +8203,9 @@ function createSessionStorage(options = {}) {
8081
8203
  const removed = [];
8082
8204
  let keychainError;
8083
8205
  try {
8084
- if (keychain.load().state !== "signed-out") removed.push("keychain");
8206
+ const hadKeychainSession = keychain.load().state !== "signed-out";
8085
8207
  keychain.clear();
8208
+ if (hadKeychainSession) removed.push("keychain");
8086
8209
  } catch (error) {
8087
8210
  keychainError = error;
8088
8211
  }
@@ -8090,6 +8213,9 @@ function createSessionStorage(options = {}) {
8090
8213
  fileBackend.remove();
8091
8214
  removed.push("file");
8092
8215
  }
8216
+ if (keychainError instanceof KeychainUnavailableError) {
8217
+ throw new SessionClearError(keychainError, removed);
8218
+ }
8093
8219
  if (keychainError) throw keychainError;
8094
8220
  return { removed };
8095
8221
  }
@@ -8097,7 +8223,7 @@ function createSessionStorage(options = {}) {
8097
8223
  }
8098
8224
 
8099
8225
  // src/commands/login.ts
8100
- function defaultLoginDeps() {
8226
+ function defaultLoginDeps(assetsDir) {
8101
8227
  return {
8102
8228
  storage: createSessionStorage(),
8103
8229
  environment: () => resolveIdentityEnvironment(),
@@ -8105,10 +8231,12 @@ function defaultLoginDeps() {
8105
8231
  browserLogin,
8106
8232
  deviceLogin,
8107
8233
  openUrl: openInDefaultBrowser,
8108
- isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY)
8234
+ isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
8235
+ resultPages: () => loadResultPages(assetsDir)
8109
8236
  };
8110
8237
  }
8111
8238
  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.";
8239
+ 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.";
8112
8240
  function safeErrorText(error) {
8113
8241
  const message = error instanceof Error ? error.message : String(error);
8114
8242
  return message.split("\n")[0].replace(/([?&](?:code|state|id_token|access_token|refresh_token)=)[^&\s]+/gi, "$1[redacted]");
@@ -8128,7 +8256,7 @@ function describeAccount(record) {
8128
8256
  const who = record.email ? `${record.email} (${record.sub})` : record.sub;
8129
8257
  return record.environment === "development" ? `${who} [development]` : who;
8130
8258
  }
8131
- async function runLogin(options, deps = defaultLoginDeps()) {
8259
+ async function runLogin(options, deps) {
8132
8260
  let environment;
8133
8261
  try {
8134
8262
  environment = deps.environment();
@@ -8157,51 +8285,51 @@ async function runLogin(options, deps = defaultLoginDeps()) {
8157
8285
  process.exit(1);
8158
8286
  return;
8159
8287
  }
8160
- let result;
8288
+ let record;
8289
+ const saveValidatedSession = (result) => {
8290
+ const next = toSessionRecord(result, environment);
8291
+ deps.storage.save(next, { insecure: Boolean(options.insecureStorage) });
8292
+ record = next;
8293
+ };
8161
8294
  try {
8162
8295
  if (options.deviceCode) {
8163
- result = await deps.deviceLogin(config, {
8296
+ const result = await deps.deviceLogin(config, {
8164
8297
  scopes: SIGN_IN_SCOPES,
8165
8298
  prompt: (uri, code, seconds) => {
8166
8299
  printInfo(`Open ${uri} on any device and enter the code ${code}.`);
8167
8300
  printInfo(`Waiting up to ${Math.round(seconds / 60)} minutes for you to finish signing in.`);
8168
8301
  }
8169
8302
  });
8303
+ saveValidatedSession(result);
8170
8304
  } else {
8305
+ const pages = await deps.resultPages();
8306
+ if (!pages) printWarning(MISSING_RESULT_PAGES_WARNING);
8171
8307
  printInfo("Opening your browser to sign in to Arcane. Come back here when it says you are signed in.");
8172
- result = await deps.browserLogin(config, {
8308
+ await deps.browserLogin(config, {
8173
8309
  scopes: SIGN_IN_SCOPES,
8174
8310
  openUrl: deps.openUrl,
8175
- onCannotOpen: (url) => {
8176
- printWarning("No browser could be opened. Open this address yourself, or run `spell login --device-code`:");
8177
- printInfo(url);
8311
+ pages: pages ?? void 0,
8312
+ onValidated: saveValidatedSession,
8313
+ onCannotOpen: () => {
8314
+ printWarning("No browser could be opened. Run `spell login --device-code` to sign in on another device.");
8178
8315
  }
8179
8316
  });
8317
+ if (!record) throw new Error("The browser sign-in ended without storing a session.");
8180
8318
  }
8181
- } catch (error) {
8182
- if (error instanceof LoginTimeoutError || error instanceof LoginDeniedError) {
8183
- console.error(`${error.message} Nothing was changed.`);
8184
- } else {
8185
- console.error(`The sign-in did not complete: ${safeErrorText(error)}. Nothing was changed.`);
8186
- }
8187
- process.exit(1);
8188
- return;
8189
- }
8190
- const record = toSessionRecord(result, environment);
8191
- try {
8192
- deps.storage.save(record, { insecure: Boolean(options.insecureStorage) });
8193
8319
  } catch (error) {
8194
8320
  if (error instanceof KeychainUnavailableError) {
8195
8321
  console.error(
8196
8322
  `${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).`
8197
8323
  );
8198
- process.exit(1);
8199
- return;
8324
+ } else if (error instanceof LoginTimeoutError || error instanceof LoginDeniedError) {
8325
+ console.error(`${error.message} Nothing was changed.`);
8326
+ } else {
8327
+ console.error(`The sign-in did not complete: ${safeErrorText(error)}. Nothing was changed.`);
8200
8328
  }
8201
- console.error(`The session could not be stored: ${safeErrorText(error)}`);
8202
8329
  process.exit(1);
8203
8330
  return;
8204
8331
  }
8332
+ if (!record) throw new Error("The sign-in completed without a stored session.");
8205
8333
  printSuccess(`Signed in as ${describeAccount(record)}.`);
8206
8334
  if (options.insecureStorage) printWarning(INSECURE_STORAGE_WARNING);
8207
8335
  }
@@ -8212,6 +8340,14 @@ function runLogout(storage = createSessionStorage()) {
8212
8340
  try {
8213
8341
  removed = storage.clear().removed;
8214
8342
  } catch (error) {
8343
+ if (error instanceof SessionClearError) {
8344
+ const fileResult = error.removed.includes("file") ? "The insecure session file was removed. " : "";
8345
+ console.error(
8346
+ `${fileResult}${error.keychainError.message} Any session in the OS keychain could not be verified or removed.`
8347
+ );
8348
+ process.exit(1);
8349
+ return;
8350
+ }
8215
8351
  if (error instanceof KeychainUnavailableError) {
8216
8352
  console.error(`${error.message} The session could not be removed from it.`);
8217
8353
  process.exit(1);
@@ -8289,15 +8425,15 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
8289
8425
 
8290
8426
  // src/commands/report.ts
8291
8427
  import { access as access5 } from "fs/promises";
8292
- import { join as join20 } from "path";
8428
+ import { join as join21 } from "path";
8293
8429
 
8294
8430
  // src/modules/show-report/generate.ts
8295
- import { mkdir as mkdir8, readdir as readdir11, readFile as readFile17, writeFile as writeFile12 } from "fs/promises";
8296
- import { basename as basename3, dirname as dirname11, isAbsolute, join as join19, relative as relative3, resolve as resolve4 } from "path";
8431
+ import { mkdir as mkdir8, readdir as readdir11, readFile as readFile18, writeFile as writeFile12 } from "fs/promises";
8432
+ import { basename as basename3, dirname as dirname11, isAbsolute, join as join20, relative as relative3, resolve as resolve4 } from "path";
8297
8433
 
8298
8434
  // src/modules/show-report/model.ts
8299
- import { readFile as readFile16 } from "fs/promises";
8300
- import { join as join18 } from "path";
8435
+ import { readFile as readFile17 } from "fs/promises";
8436
+ import { join as join19 } from "path";
8301
8437
 
8302
8438
  // src/modules/show-report/plan-parser.ts
8303
8439
  function parseFrontmatter(content) {
@@ -8570,7 +8706,7 @@ async function getCast(cwd, fromRef, toRef, generatedOutputs = []) {
8570
8706
  // src/modules/show-report/model.ts
8571
8707
  async function readOptional(path4) {
8572
8708
  try {
8573
- return await readFile16(path4, "utf8");
8709
+ return await readFile17(path4, "utf8");
8574
8710
  } catch {
8575
8711
  return null;
8576
8712
  }
@@ -8666,13 +8802,13 @@ function buildCorrections(ledgerRows, programName) {
8666
8802
  };
8667
8803
  }
8668
8804
  async function buildShowReportModel(options) {
8669
- const planAbsPath = join18(options.rootDir, options.planRelPath);
8670
- const queueAbsPath = join18(options.rootDir, options.queueRelPath);
8805
+ const planAbsPath = join19(options.rootDir, options.planRelPath);
8806
+ const queueAbsPath = join19(options.rootDir, options.queueRelPath);
8671
8807
  const ledgerRelPath = "docs/verification-ledger.md";
8672
- const ledgerAbsPath = join18(options.rootDir, ledgerRelPath);
8673
- const decisionsAbsPath = join18(options.rootDir, "DECISIONS.md");
8808
+ const ledgerAbsPath = join19(options.rootDir, ledgerRelPath);
8809
+ const decisionsAbsPath = join19(options.rootDir, "DECISIONS.md");
8674
8810
  const [planContent, queueContent, ledgerContent, decisionsContent] = await Promise.all([
8675
- readFile16(planAbsPath, "utf8"),
8811
+ readFile17(planAbsPath, "utf8"),
8676
8812
  readOptional(queueAbsPath),
8677
8813
  readOptional(ledgerAbsPath),
8678
8814
  readOptional(decisionsAbsPath)
@@ -8868,7 +9004,7 @@ function toPosix(path4) {
8868
9004
  async function programFromPlan(rootDir, slug, planRelPath) {
8869
9005
  let planContent;
8870
9006
  try {
8871
- planContent = await readFile17(join19(rootDir, planRelPath), "utf8");
9007
+ planContent = await readFile18(join20(rootDir, planRelPath), "utf8");
8872
9008
  } catch {
8873
9009
  return { skipped: { slug, reason: `no PLAN.md at ${planRelPath}` } };
8874
9010
  }
@@ -8880,13 +9016,13 @@ async function programFromPlan(rootDir, slug, planRelPath) {
8880
9016
  program: {
8881
9017
  slug,
8882
9018
  planRelPath,
8883
- queueRelPath: toPosix(join19(dirname11(planRelPath), "OPERATOR-QUEUE.md")),
9019
+ queueRelPath: toPosix(join20(dirname11(planRelPath), "OPERATOR-QUEUE.md")),
8884
9020
  ledgerProgramName: programNameFromTitle(frontmatter.title ?? slug)
8885
9021
  }
8886
9022
  };
8887
9023
  }
8888
9024
  async function discoverPrograms(rootDir) {
8889
- const plansDir = join19(rootDir, "docs", "plans");
9025
+ const plansDir = join20(rootDir, "docs", "plans");
8890
9026
  let entries;
8891
9027
  try {
8892
9028
  entries = (await readdir11(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
@@ -8913,14 +9049,14 @@ function normalizeLineEndings2(content) {
8913
9049
  }
8914
9050
  async function readOptionalFile(path4) {
8915
9051
  try {
8916
- return await readFile17(path4, "utf8");
9052
+ return await readFile18(path4, "utf8");
8917
9053
  } catch (error) {
8918
9054
  if (error.code === "ENOENT") return null;
8919
9055
  throw error;
8920
9056
  }
8921
9057
  }
8922
9058
  async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
8923
- const planContent = await readFile17(join19(rootDir, program2.planRelPath), "utf8");
9059
+ const planContent = await readFile18(join20(rootDir, program2.planRelPath), "utf8");
8924
9060
  const frontmatter = parseFrontmatter(planContent);
8925
9061
  const model = await buildShowReportModel({
8926
9062
  rootDir,
@@ -8940,7 +9076,7 @@ async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
8940
9076
  };
8941
9077
  }
8942
9078
  async function runReportGeneration(options) {
8943
- const templateHtml = await readFile17(options.templatePath, "utf8");
9079
+ const templateHtml = await readFile18(options.templatePath, "utf8");
8944
9080
  const theme = options.theme ?? "auto";
8945
9081
  const drifted = [];
8946
9082
  const repaired = [];
@@ -8958,12 +9094,12 @@ async function runReportGeneration(options) {
8958
9094
  [`${outRel}/show-report.html`, generated.html]
8959
9095
  ];
8960
9096
  for (const [relPath, expected] of outputs) {
8961
- const actual = await readOptionalFile(join19(options.rootDir, relPath));
9097
+ const actual = await readOptionalFile(join20(options.rootDir, relPath));
8962
9098
  if (actual === null || normalizeLineEndings2(actual) !== normalizeLineEndings2(expected)) {
8963
9099
  drifted.push(`${relPath} does not match a regeneration from its sources.`);
8964
9100
  if (options.mode === "write") {
8965
9101
  await mkdir8(outAbs, { recursive: true });
8966
- await writeFile12(join19(options.rootDir, relPath), expected, "utf8");
9102
+ await writeFile12(join20(options.rootDir, relPath), expected, "utf8");
8967
9103
  repaired.push(relPath);
8968
9104
  }
8969
9105
  }
@@ -8974,7 +9110,7 @@ async function runReportGeneration(options) {
8974
9110
 
8975
9111
  // src/commands/report.ts
8976
9112
  async function runReport(targetDir, options = {}, assetsDir) {
8977
- const templatePath = join20(assetsDir, "report", "show-report.template.html");
9113
+ const templatePath = join21(assetsDir, "report", "show-report.template.html");
8978
9114
  try {
8979
9115
  await access5(templatePath);
8980
9116
  } catch {
@@ -9051,7 +9187,7 @@ async function runReport(targetDir, options = {}, assetsDir) {
9051
9187
  import { realpathSync } from "fs";
9052
9188
  var require3 = createRequire3(import.meta.url);
9053
9189
  var pkg = require3("../package.json");
9054
- var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join21(dirname12(fileURLToPath(import.meta.url)), "assets");
9190
+ var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join22(dirname12(fileURLToPath(import.meta.url)), "assets");
9055
9191
  var program = new Command();
9056
9192
  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)";
9057
9193
  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)";
@@ -9178,7 +9314,7 @@ program.command("login").description("Sign in to your Arcane account (browser by
9178
9314
  "--insecure-storage",
9179
9315
  "Keep the session in a file under ~/.arcane instead of the OS keychain; protected only by your account's file permissions"
9180
9316
  ).action(async (opts) => {
9181
- await runLogin({ deviceCode: opts.deviceCode, insecureStorage: opts.insecureStorage });
9317
+ await runLogin({ deviceCode: opts.deviceCode, insecureStorage: opts.insecureStorage }, defaultLoginDeps(ASSETS_DIR));
9182
9318
  });
9183
9319
  program.command("logout").description("Sign out on this machine: remove the stored session").action(() => {
9184
9320
  runLogout();