@great-arrow/cli 0.1.66 → 0.1.67

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.
Files changed (3) hide show
  1. package/README.md +7 -5
  2. package/dist/index.js +270 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -49,11 +49,13 @@ gad logout
49
49
  gad auth token [--json]
50
50
  ```
51
51
 
52
- Credentials live in your OS keychain (Keychain Access / libsecret / Credential
53
- Vault). OAuth access tokens are short-lived and refreshed automatically; `GAD_TOKEN`
54
- in the environment always takes precedence for CI.
55
-
56
- Tokens are stored in the OS keychain (macOS Keychain / Windows Credential Manager / Linux Secret Service). You can also set `GAD_TOKEN` as an environment variable.
52
+ Credentials live in your mode-`0600` `~/.great-arrow/credential` store (inside
53
+ `0700` `~/.great-arrow/`) and/or your OS keychain (Keychain Access / libsecret /
54
+ Credential Vault). Keeping `~/.great-arrow/credential` synced and reading it
55
+ first avoids repeated macOS Keychain `cdhash` password prompts when multiple
56
+ Node.js versions (`nvm`, `volta`, Homebrew) or the stdio MCP proxy access
57
+ automatically refreshed OAuth tokens. `GAD_TOKEN` in the environment always
58
+ takes precedence for CI.
57
59
 
58
60
  ## Commands
59
61
 
package/dist/index.js CHANGED
@@ -108,7 +108,7 @@ var require_polyfills = __commonJS({
108
108
  }
109
109
  if (platform2 === "win32") {
110
110
  fs6.rename = typeof fs6.rename !== "function" ? fs6.rename : (function(fs$rename) {
111
- function rename3(from, to, cb) {
111
+ function rename4(from, to, cb) {
112
112
  var start = Date.now();
113
113
  var backoff = 0;
114
114
  fs$rename(from, to, function CB(er) {
@@ -128,8 +128,8 @@ var require_polyfills = __commonJS({
128
128
  if (cb) cb(er);
129
129
  });
130
130
  }
131
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename3, fs$rename);
132
- return rename3;
131
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename4, fs$rename);
132
+ return rename4;
133
133
  })(fs6.rename);
134
134
  }
135
135
  fs6.read = typeof fs6.read !== "function" ? fs6.read : (function(fs$read) {
@@ -2082,7 +2082,7 @@ var require_polyfills2 = __commonJS({
2082
2082
  }
2083
2083
  if (platform2 === "win32") {
2084
2084
  fs6.rename = typeof fs6.rename !== "function" ? fs6.rename : (function(fs$rename) {
2085
- function rename3(from, to, cb) {
2085
+ function rename4(from, to, cb) {
2086
2086
  var start = Date.now();
2087
2087
  var backoff = 0;
2088
2088
  fs$rename(from, to, function CB(er) {
@@ -2102,8 +2102,8 @@ var require_polyfills2 = __commonJS({
2102
2102
  if (cb) cb(er);
2103
2103
  });
2104
2104
  }
2105
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename3, fs$rename);
2106
- return rename3;
2105
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename4, fs$rename);
2106
+ return rename4;
2107
2107
  })(fs6.rename);
2108
2108
  }
2109
2109
  fs6.read = typeof fs6.read !== "function" ? fs6.read : (function(fs$read) {
@@ -9437,7 +9437,7 @@ function isNodeError(value) {
9437
9437
 
9438
9438
  // src/lib/version.ts
9439
9439
  var CLI_PACKAGE_NAME = "@great-arrow/cli";
9440
- var CLI_VERSION = "0.1.66";
9440
+ var CLI_VERSION = "0.1.67";
9441
9441
  var GAD_INSTALL_VERSION = "2026.08.08";
9442
9442
  var GAD_INSTALL_MIN_VERSION = "2026.06.22";
9443
9443
  var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
@@ -9450,7 +9450,14 @@ import { execFile } from "node:child_process";
9450
9450
  import { randomUUID } from "node:crypto";
9451
9451
 
9452
9452
  // src/lib/keychain.ts
9453
- import { mkdir, writeFile, readFile, unlink } from "node:fs/promises";
9453
+ import {
9454
+ chmod,
9455
+ mkdir,
9456
+ readFile,
9457
+ rename,
9458
+ unlink,
9459
+ writeFile
9460
+ } from "node:fs/promises";
9454
9461
  import { homedir } from "node:os";
9455
9462
  import { join, dirname } from "node:path";
9456
9463
 
@@ -9465,10 +9472,70 @@ function debugSwallow(scope, err2) {
9465
9472
  // src/lib/keychain.ts
9466
9473
  var SERVICE = "GreatArrowDigital";
9467
9474
  var ACCOUNT = "api-token";
9475
+ var LOGGED_OUT_TOMBSTONE = "LOGGED_OUT";
9468
9476
  function slotFilePath(account) {
9469
9477
  if (account === ACCOUNT) return join(homedir(), ".great-arrow", "credential");
9470
9478
  return join(homedir(), ".great-arrow", "slots", encodeURIComponent(account));
9471
9479
  }
9480
+ function tokenMigratedMarkerPath() {
9481
+ return join(homedir(), ".great-arrow", "credential.migrated");
9482
+ }
9483
+ function isWellFormedTokenContent(trimmed) {
9484
+ if (trimmed === "" || trimmed === LOGGED_OUT_TOMBSTONE) return false;
9485
+ if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
9486
+ try {
9487
+ const parsed = JSON.parse(trimmed);
9488
+ if (typeof parsed !== "object" || parsed === null) return false;
9489
+ const o = parsed;
9490
+ return o.tokenType === "oauth" && typeof o.accessToken === "string" && o.accessToken.length > 0 && typeof o.refreshToken === "string" && o.refreshToken.length > 0 && typeof o.accessExpiresAt === "number" && typeof o.refreshExpiresAt === "number" && typeof o.clientId === "string" && typeof o.apiUrl === "string";
9491
+ } catch {
9492
+ return false;
9493
+ }
9494
+ }
9495
+ return true;
9496
+ }
9497
+ async function inspectTokenFile() {
9498
+ const filePath = slotFilePath(ACCOUNT);
9499
+ try {
9500
+ const raw = await readFile(filePath, "utf-8");
9501
+ try {
9502
+ await chmod(dirname(filePath), 448);
9503
+ await chmod(filePath, 384);
9504
+ } catch (chmodErr) {
9505
+ debugSwallow("keychain.inspectTokenFile(chmod)", chmodErr);
9506
+ }
9507
+ const trimmed = raw.trim();
9508
+ if (trimmed === LOGGED_OUT_TOMBSTONE) {
9509
+ return { status: "tombstone" };
9510
+ }
9511
+ if (!isWellFormedTokenContent(trimmed)) {
9512
+ return { status: "corrupt" };
9513
+ }
9514
+ return { status: "valid", value: trimmed };
9515
+ } catch (err2) {
9516
+ const code = err2?.code;
9517
+ const msg = err2 instanceof Error ? err2.message : "";
9518
+ if (code === "ENOENT" || msg.includes("ENOENT")) {
9519
+ return { status: "missing" };
9520
+ }
9521
+ debugSwallow("keychain.inspectTokenFile", err2);
9522
+ return { status: "corrupt" };
9523
+ }
9524
+ }
9525
+ async function hasTokenMigratedMarker() {
9526
+ try {
9527
+ const raw = await readFile(tokenMigratedMarkerPath(), "utf-8");
9528
+ return raw.trim() !== "";
9529
+ } catch (err2) {
9530
+ const code = err2?.code;
9531
+ const msg = err2 instanceof Error ? err2.message : "";
9532
+ if (code === "ENOENT" || msg.includes("ENOENT")) {
9533
+ return false;
9534
+ }
9535
+ debugSwallow("keychain.hasTokenMigratedMarker", err2);
9536
+ return true;
9537
+ }
9538
+ }
9472
9539
  async function loadEntry(account) {
9473
9540
  try {
9474
9541
  const { AsyncEntry } = await import("@napi-rs/keyring");
@@ -9486,24 +9553,114 @@ async function readFileFallback(account) {
9486
9553
  return null;
9487
9554
  }
9488
9555
  }
9556
+ async function writeAtomicProtectedFile(path10, value) {
9557
+ const dir = dirname(path10);
9558
+ const tmpPath = `${path10}.tmp.${process.pid}.${Date.now()}`;
9559
+ try {
9560
+ await mkdir(dir, { recursive: true, mode: 448 });
9561
+ await chmod(dir, 448);
9562
+ await writeFile(tmpPath, value, { mode: 384 });
9563
+ await chmod(tmpPath, 384);
9564
+ await rename(tmpPath, path10);
9565
+ return true;
9566
+ } catch (err2) {
9567
+ try {
9568
+ await unlink(tmpPath);
9569
+ } catch (cleanupErr) {
9570
+ debugSwallow("keychain.writeFileFallback(cleanup)", cleanupErr);
9571
+ }
9572
+ debugSwallow("keychain.writeFileFallback", err2);
9573
+ return false;
9574
+ }
9575
+ }
9576
+ async function writeFileFallback(account, value) {
9577
+ if (account === ACCOUNT && preferFileStoreWithoutKeyring()) {
9578
+ const priorState = await inspectTokenFile();
9579
+ if (priorState.status === "valid" || priorState.status === "tombstone") {
9580
+ if (!await writeAtomicProtectedFile(tokenMigratedMarkerPath(), "1")) {
9581
+ return false;
9582
+ }
9583
+ return writeAtomicProtectedFile(slotFilePath(ACCOUNT), value);
9584
+ }
9585
+ if (!await writeAtomicProtectedFile(slotFilePath(ACCOUNT), value)) {
9586
+ return false;
9587
+ }
9588
+ if (!await writeAtomicProtectedFile(tokenMigratedMarkerPath(), "1")) {
9589
+ await removeFileFallback(ACCOUNT);
9590
+ return false;
9591
+ }
9592
+ return true;
9593
+ }
9594
+ return writeAtomicProtectedFile(slotFilePath(account), value);
9595
+ }
9489
9596
  async function getSecretSlot(account) {
9490
- const entry = await loadEntry(account);
9597
+ if (account !== ACCOUNT) {
9598
+ const entry2 = await loadEntry(account);
9599
+ if (entry2) {
9600
+ try {
9601
+ const value = await entry2.getPassword();
9602
+ if (value) return value;
9603
+ } catch (err2) {
9604
+ debugSwallow("keychain.getSecretSlot", err2);
9605
+ }
9606
+ }
9607
+ return readFileFallback(account);
9608
+ }
9609
+ const fileState = await inspectTokenFile();
9610
+ if (fileState.status === "tombstone" || fileState.status === "corrupt") {
9611
+ return null;
9612
+ }
9613
+ if (preferFileStoreWithoutKeyring()) {
9614
+ if (fileState.status === "valid") return fileState.value;
9615
+ if (await hasTokenMigratedMarker()) {
9616
+ return null;
9617
+ }
9618
+ const entry2 = await loadEntry(ACCOUNT);
9619
+ if (entry2) {
9620
+ try {
9621
+ const value = await entry2.getPassword();
9622
+ const trimmed = value?.trim() ?? "";
9623
+ if (trimmed && isWellFormedTokenContent(trimmed)) {
9624
+ const migrated = await writeFileFallback(ACCOUNT, trimmed);
9625
+ if (!migrated) {
9626
+ return null;
9627
+ }
9628
+ return trimmed;
9629
+ }
9630
+ } catch (err2) {
9631
+ debugSwallow("keychain.getSecretSlot", err2);
9632
+ }
9633
+ }
9634
+ return null;
9635
+ }
9636
+ const entry = await loadEntry(ACCOUNT);
9491
9637
  if (entry) {
9492
9638
  try {
9493
9639
  const value = await entry.getPassword();
9494
- if (value) return value;
9640
+ const trimmed = value?.trim() ?? "";
9641
+ if (trimmed && isWellFormedTokenContent(trimmed)) {
9642
+ return trimmed;
9643
+ }
9495
9644
  } catch (err2) {
9496
9645
  debugSwallow("keychain.getSecretSlot", err2);
9497
9646
  }
9498
9647
  }
9499
- return readFileFallback(account);
9648
+ return fileState.status === "valid" ? fileState.value : null;
9649
+ }
9650
+ function preferFileStoreWithoutKeyring() {
9651
+ return process.platform === "darwin" && !process.env.VITEST && process.env.GAD_FORCE_KEYRING !== "1";
9500
9652
  }
9501
9653
  async function setSecretSlot(account, value) {
9654
+ if (account === ACCOUNT && preferFileStoreWithoutKeyring()) {
9655
+ return writeFileFallback(ACCOUNT, value);
9656
+ }
9502
9657
  const entry = await loadEntry(account);
9503
9658
  if (entry) {
9504
9659
  try {
9505
9660
  await entry.setPassword(value);
9506
- await removeFileFallback(account);
9661
+ if (!await removeFileFallback(account)) {
9662
+ return false;
9663
+ }
9507
9664
  return true;
9508
9665
  } catch (err2) {
9509
9666
  debugSwallow("keychain.setSecretSlot", err2);
@@ -9515,25 +9672,47 @@ async function setSecretSlot(account, value) {
9515
9672
  return false;
9516
9673
  }
9517
9674
  }
9518
- try {
9519
- const path10 = slotFilePath(account);
9520
- await mkdir(dirname(path10), { recursive: true, mode: 448 });
9521
- await writeFile(path10, value, { mode: 384 });
9522
- return true;
9523
- } catch {
9524
- return false;
9525
- }
9675
+ return writeFileFallback(account, value);
9526
9676
  }
9527
9677
  async function deleteSecretSlot(account) {
9528
- const entry = await loadEntry(account);
9678
+ if (account !== ACCOUNT) {
9679
+ const entry2 = await loadEntry(account);
9680
+ if (entry2) {
9681
+ try {
9682
+ await entry2.deletePassword();
9683
+ } catch (err2) {
9684
+ debugSwallow("keychain.deleteSecretSlot", err2);
9685
+ }
9686
+ }
9687
+ await removeFileFallback(account);
9688
+ return;
9689
+ }
9690
+ let keychainCleared = false;
9691
+ const entry = await loadEntry(ACCOUNT);
9529
9692
  if (entry) {
9530
9693
  try {
9531
9694
  await entry.deletePassword();
9695
+ keychainCleared = true;
9532
9696
  } catch (err2) {
9533
9697
  debugSwallow("keychain.deleteSecretSlot", err2);
9534
9698
  }
9699
+ } else {
9700
+ keychainCleared = true;
9701
+ }
9702
+ const tombstoned = await writeFileFallback(ACCOUNT, LOGGED_OUT_TOMBSTONE);
9703
+ if (!tombstoned) {
9704
+ if (preferFileStoreWithoutKeyring()) {
9705
+ throw new Error(
9706
+ "Could not remove stored credentials from keychain or credential file."
9707
+ );
9708
+ }
9709
+ const removed = await removeFileFallback(ACCOUNT);
9710
+ if (!keychainCleared || !removed) {
9711
+ throw new Error(
9712
+ "Could not remove stored credentials from keychain or credential file."
9713
+ );
9714
+ }
9535
9715
  }
9536
- await removeFileFallback(account);
9537
9716
  }
9538
9717
  function getToken() {
9539
9718
  return getSecretSlot(ACCOUNT);
@@ -9547,10 +9726,13 @@ function deleteToken() {
9547
9726
  async function removeFileFallback(account) {
9548
9727
  try {
9549
9728
  await unlink(slotFilePath(account));
9729
+ return true;
9550
9730
  } catch (err2) {
9551
- if (err2?.code !== "ENOENT") {
9552
- debugSwallow("keychain.removeFileFallback", err2);
9731
+ if (err2?.code === "ENOENT") {
9732
+ return true;
9553
9733
  }
9734
+ debugSwallow("keychain.removeFileFallback", err2);
9735
+ return false;
9554
9736
  }
9555
9737
  }
9556
9738
 
@@ -9563,16 +9745,17 @@ function isOAuthCredential(value) {
9563
9745
  function parseStoredValue(raw) {
9564
9746
  if (!raw) return null;
9565
9747
  const trimmed = raw.trim();
9566
- if (trimmed === "") return null;
9567
- if (trimmed.startsWith("{")) {
9748
+ if (trimmed === "" || trimmed === "LOGGED_OUT") return null;
9749
+ if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
9568
9750
  try {
9569
9751
  const parsed = JSON.parse(trimmed);
9570
9752
  if (isOAuthCredential(parsed)) {
9571
9753
  return { kind: "oauth", credential: parsed };
9572
9754
  }
9573
9755
  } catch {
9574
- return { kind: "pat", token: trimmed };
9756
+ return null;
9575
9757
  }
9758
+ return null;
9576
9759
  }
9577
9760
  return { kind: "pat", token: trimmed };
9578
9761
  }
@@ -10782,12 +10965,24 @@ var logoutCommand = new Command("logout").description("Remove stored authenticat
10782
10965
  tokenTypeHint: "refresh_token"
10783
10966
  });
10784
10967
  }
10785
- await clearStoredCredential();
10968
+ let clearError = null;
10969
+ try {
10970
+ await clearStoredCredential();
10971
+ } catch (err2) {
10972
+ clearError = err2;
10973
+ }
10786
10974
  const config2 = await loadConfig();
10787
10975
  if (config2.token) {
10788
10976
  config2.token = "";
10789
10977
  await saveConfig(config2);
10790
10978
  }
10979
+ if (clearError) {
10980
+ fmt.error(
10981
+ clearError instanceof Error ? clearError.message : "Could not remove stored authentication credentials."
10982
+ );
10983
+ process.exitCode = 1 /* AUTH_ERROR */;
10984
+ return;
10985
+ }
10791
10986
  fmt.success("Logged out successfully.");
10792
10987
  });
10793
10988
 
@@ -14673,11 +14868,11 @@ function inspectConfiguredHooks(hooks, deps) {
14673
14868
  // src/lib/suggest-hook.ts
14674
14869
  import { existsSync as existsSync3 } from "node:fs";
14675
14870
  import {
14676
- chmod,
14871
+ chmod as chmod2,
14677
14872
  copyFile,
14678
14873
  mkdir as mkdir4,
14679
14874
  readFile as readFile3,
14680
- rename,
14875
+ rename as rename2,
14681
14876
  unlink as unlink2,
14682
14877
  writeFile as writeFile3
14683
14878
  } from "node:fs/promises";
@@ -14768,7 +14963,7 @@ async function atomicWrite(path10, content, mode) {
14768
14963
  await mkdir4(dirname4(path10), { recursive: true });
14769
14964
  const tmp = `${path10}.tmp-${process.pid}`;
14770
14965
  await writeFile3(tmp, content, mode === void 0 ? "utf8" : { mode });
14771
- await rename(tmp, path10);
14966
+ await rename2(tmp, path10);
14772
14967
  }
14773
14968
  function hasHookCredential(content) {
14774
14969
  try {
@@ -14795,7 +14990,7 @@ async function enableSuggestHook(opts) {
14795
14990
  );
14796
14991
  await mkdir4(dirname4(p.scriptPath), { recursive: true, mode: 448 });
14797
14992
  await copyFile(opts.packagedScript, p.scriptPath);
14798
- await chmod(p.scriptPath, 493);
14993
+ await chmod2(p.scriptPath, 493);
14799
14994
  if (plan.changed) await atomicWrite(p.settingsPath, plan.content);
14800
14995
  return { changed: plan.changed };
14801
14996
  }
@@ -15293,8 +15488,8 @@ function fingerprint(value) {
15293
15488
  }
15294
15489
  function describeStoredCredential(raw) {
15295
15490
  const trimmed = raw?.trim();
15296
- if (!trimmed) return null;
15297
- if (trimmed.startsWith("{")) {
15491
+ if (!trimmed || trimmed === "LOGGED_OUT") return null;
15492
+ if (trimmed.startsWith("{") || /\s/.test(trimmed)) {
15298
15493
  try {
15299
15494
  const parsed = asObject3(JSON.parse(trimmed));
15300
15495
  const refreshToken = parsed?.refreshToken;
@@ -15332,7 +15527,9 @@ function freshness(shape) {
15332
15527
  return shape.refreshExpiresAt ?? shape.accessExpiresAt ?? 0;
15333
15528
  }
15334
15529
  function auditCredentialStores(input2) {
15335
- const keychain = describeStoredCredential(input2.keychain);
15530
+ const loggedOut = input2.file?.trim() === "LOGGED_OUT";
15531
+ const effectiveKeychain = loggedOut || input2.fileAuthoritative ? null : input2.keychain;
15532
+ const keychain = describeStoredCredential(effectiveKeychain);
15336
15533
  const file2 = describeStoredCredential(input2.file);
15337
15534
  const findings = [];
15338
15535
  if (!keychain && !file2) {
@@ -15463,14 +15660,16 @@ function planMigration(input2) {
15463
15660
  if ((store.shape.refreshExpiresAt ?? 0) <= input2.now) return true;
15464
15661
  return Boolean(effective) && freshness(store.shape) <= freshness(effective);
15465
15662
  }).map((store) => store.path);
15466
- const keychain = describeStoredCredential(input2.keychain);
15663
+ const loggedOut = input2.file?.trim() === "LOGGED_OUT";
15664
+ const keychain = loggedOut || input2.fileAuthoritative ? null : describeStoredCredential(input2.keychain);
15467
15665
  const file2 = describeStoredCredential(input2.file);
15468
15666
  let syncKeychainToFile = false;
15469
15667
  if (keychain && keychain.kind !== "corrupt") {
15470
15668
  const keychainUsable = keychain.kind !== "oauth" || (keychain.refreshExpiresAt ?? 0) > input2.now;
15471
15669
  if (keychainUsable) {
15472
- if (!file2) syncKeychainToFile = true;
15473
- else if (keychain.fingerprint !== file2.fingerprint) {
15670
+ if (!file2 && !loggedOut && !input2.fileAuthoritative) {
15671
+ syncKeychainToFile = true;
15672
+ } else if (file2 && keychain.fingerprint !== file2.fingerprint) {
15474
15673
  syncKeychainToFile = freshness(keychain) > freshness(file2);
15475
15674
  }
15476
15675
  }
@@ -16837,7 +17036,7 @@ async function pathExists(path10) {
16837
17036
  async function installCodexConfig(apiUrl, token, repoUrl) {
16838
17037
  const { homedir: homedir11 } = await import("node:os");
16839
17038
  const { join: join13 } = await import("node:path");
16840
- const { chmod: chmod3, copyFile: copyFile3, mkdir: mkdir9, readFile: readFile9, writeFile: writeFile7 } = await import("node:fs/promises");
17039
+ const { chmod: chmod4, copyFile: copyFile3, mkdir: mkdir9, readFile: readFile9, writeFile: writeFile7 } = await import("node:fs/promises");
16841
17040
  const codexDir = join13(homedir11(), ".codex");
16842
17041
  const configPath = join13(codexDir, "config.toml");
16843
17042
  const envPath = join13(codexDir, ".env");
@@ -16861,7 +17060,7 @@ async function installCodexConfig(apiUrl, token, repoUrl) {
16861
17060
  upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
16862
17061
  "utf8"
16863
17062
  );
16864
- await chmod3(envPath, 384);
17063
+ await chmod4(envPath, 384);
16865
17064
  return { configPath, envPath };
16866
17065
  }
16867
17066
  var startCommand = new Command("start").description(
@@ -17353,17 +17552,31 @@ async function probeEndpoint() {
17353
17552
  async function applyMigration(plan) {
17354
17553
  const { homedir: homedir11 } = await import("node:os");
17355
17554
  const { join: join13 } = await import("node:path");
17356
- const { mkdir: mkdir9, rename: rename3, writeFile: writeFile7 } = await import("node:fs/promises");
17555
+ const { chmod: chmod4, mkdir: mkdir9, rename: rename4, unlink: unlink3, writeFile: writeFile7 } = await import("node:fs/promises");
17357
17556
  const applied = [];
17358
17557
  const dir = join13(homedir11(), ".great-arrow");
17359
17558
  await mkdir9(dir, { recursive: true, mode: 448 });
17559
+ await chmod4(dir, 448);
17560
+ const writeAtomic0600 = async (target, raw) => {
17561
+ const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
17562
+ try {
17563
+ await writeFile7(tmp, raw, { mode: 384, flag: "wx" });
17564
+ await chmod4(tmp, 384);
17565
+ await rename4(tmp, target);
17566
+ } catch (err2) {
17567
+ try {
17568
+ await unlink3(tmp);
17569
+ } catch (cleanupErr) {
17570
+ debugSwallow("mcp.applyMigration(cleanup)", cleanupErr);
17571
+ }
17572
+ throw err2;
17573
+ }
17574
+ };
17360
17575
  if (plan.seedFrom) {
17361
17576
  const raw = await readIfExists(plan.seedFrom);
17362
17577
  if (raw !== null) {
17363
17578
  const target = join13(dir, OAUTH_CRED_FILENAME);
17364
- const tmp = `${target}.tmp`;
17365
- await writeFile7(tmp, raw, { mode: 384 });
17366
- await rename3(tmp, target);
17579
+ await writeAtomic0600(target, raw);
17367
17580
  applied.push(`Copied ${plan.seedFrom} \u2192 ${target}`);
17368
17581
  }
17369
17582
  }
@@ -17371,9 +17584,8 @@ async function applyMigration(plan) {
17371
17584
  const raw = await readKeychainOnly();
17372
17585
  if (raw) {
17373
17586
  const target = join13(dir, "credential");
17374
- const tmp = `${target}.tmp`;
17375
- await writeFile7(tmp, raw, { mode: 384 });
17376
- await rename3(tmp, target);
17587
+ await writeAtomic0600(target, raw);
17588
+ await writeAtomic0600(join13(dir, "credential.migrated"), "1");
17377
17589
  applied.push(`Wrote the keychain credential to ${target}`);
17378
17590
  }
17379
17591
  }
@@ -17403,10 +17615,14 @@ var doctorSubcommand = new Command("doctor").description(
17403
17615
  const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
17404
17616
  const entries = await scanConnections();
17405
17617
  const duplicates = detectDuplicates(entries);
17406
- const keychain = await readKeychainOnly();
17407
17618
  const credentialFile = await readIfExists(
17408
17619
  join13(home, ".great-arrow", "credential")
17409
17620
  );
17621
+ const credentialMigrated = await readIfExists(
17622
+ join13(home, ".great-arrow", "credential.migrated")
17623
+ ) !== null;
17624
+ const fileAuthoritative = credentialFile?.trim() === "LOGGED_OUT" || credentialMigrated || process.platform === "darwin" && credentialFile !== null;
17625
+ const keychain = fileAuthoritative ? null : await readKeychainOnly();
17410
17626
  const userStore = await readIfExists(
17411
17627
  join13(home, ".great-arrow", OAUTH_CRED_FILENAME)
17412
17628
  );
@@ -17416,6 +17632,7 @@ var doctorSubcommand = new Command("doctor").description(
17416
17632
  ...auditCredentialStores({
17417
17633
  keychain,
17418
17634
  file: credentialFile,
17635
+ fileAuthoritative,
17419
17636
  now: Date.now()
17420
17637
  }),
17421
17638
  await probeEndpoint()
@@ -17441,6 +17658,7 @@ var doctorSubcommand = new Command("doctor").description(
17441
17658
  userStore,
17442
17659
  keychain,
17443
17660
  file: credentialFile,
17661
+ fileAuthoritative,
17444
17662
  now: Date.now()
17445
17663
  });
17446
17664
  const applied = options.migrate ? await applyMigration(plan) : [];
@@ -49948,11 +50166,11 @@ import { Writable } from "node:stream";
49948
50166
  // src/lib/keys-apply.ts
49949
50167
  import { spawn } from "node:child_process";
49950
50168
  import {
49951
- chmod as chmod2,
50169
+ chmod as chmod3,
49952
50170
  mkdir as mkdir8,
49953
50171
  readFile as readFile7,
49954
50172
  realpath,
49955
- rename as rename2,
50173
+ rename as rename3,
49956
50174
  stat as stat2,
49957
50175
  writeFile as writeFile6
49958
50176
  } from "node:fs/promises";
@@ -50058,8 +50276,8 @@ function formatEnvValue(value, quote) {
50058
50276
  async function writeAtomic(path10, content, mode) {
50059
50277
  const tmp = `${path10}.tmp`;
50060
50278
  await writeFile6(tmp, content, { mode });
50061
- await chmod2(tmp, mode);
50062
- await rename2(tmp, path10);
50279
+ await chmod3(tmp, mode);
50280
+ await rename3(tmp, path10);
50063
50281
  }
50064
50282
  async function applyEnvFile(path10, key, value) {
50065
50283
  const file2 = expandHome(path10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {