@great-arrow/cli 0.1.38 → 0.1.40

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 (2) hide show
  1. package/dist/index.js +225 -121
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -706,7 +706,7 @@ var require_graceful_fs = __commonJS({
706
706
  }
707
707
  function ReadStream$open() {
708
708
  var that = this;
709
- open(that.path, that.flags, that.mode, function(err, fd) {
709
+ open2(that.path, that.flags, that.mode, function(err, fd) {
710
710
  if (err) {
711
711
  if (that.autoClose)
712
712
  that.destroy();
@@ -726,7 +726,7 @@ var require_graceful_fs = __commonJS({
726
726
  }
727
727
  function WriteStream$open() {
728
728
  var that = this;
729
- open(that.path, that.flags, that.mode, function(err, fd) {
729
+ open2(that.path, that.flags, that.mode, function(err, fd) {
730
730
  if (err) {
731
731
  that.destroy();
732
732
  that.emit("error", err);
@@ -743,8 +743,8 @@ var require_graceful_fs = __commonJS({
743
743
  return new fs7.WriteStream(path10, options);
744
744
  }
745
745
  var fs$open = fs7.open;
746
- fs7.open = open;
747
- function open(path10, flags, mode, cb) {
746
+ fs7.open = open2;
747
+ function open2(path10, flags, mode, cb) {
748
748
  if (typeof mode === "function")
749
749
  cb = mode, mode = null;
750
750
  return go$open(path10, flags, mode, cb);
@@ -1964,7 +1964,7 @@ var require_minimist = __commonJS({
1964
1964
  var require_rc = __commonJS({
1965
1965
  "node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
1966
1966
  var cc = require_utils();
1967
- var join7 = __require("path").join;
1967
+ var join8 = __require("path").join;
1968
1968
  var deepExtend = require_deep_extend();
1969
1969
  var etc = "/etc";
1970
1970
  var win = process.platform === "win32";
@@ -1989,15 +1989,15 @@ var require_rc = __commonJS({
1989
1989
  }
1990
1990
  if (!win)
1991
1991
  [
1992
- join7(etc, name, "config"),
1993
- join7(etc, name + "rc")
1992
+ join8(etc, name, "config"),
1993
+ join8(etc, name + "rc")
1994
1994
  ].forEach(addConfigFile);
1995
1995
  if (home)
1996
1996
  [
1997
- join7(home, ".config", name, "config"),
1998
- join7(home, ".config", name),
1999
- join7(home, "." + name, "config"),
2000
- join7(home, "." + name + "rc")
1997
+ join8(home, ".config", name, "config"),
1998
+ join8(home, ".config", name),
1999
+ join8(home, "." + name, "config"),
2000
+ join8(home, "." + name + "rc")
2001
2001
  ].forEach(addConfigFile);
2002
2002
  addConfigFile(cc.find("." + name + "rc"));
2003
2003
  if (env4.config) addConfigFile(env4.config);
@@ -2680,7 +2680,7 @@ var require_graceful_fs2 = __commonJS({
2680
2680
  }
2681
2681
  function ReadStream$open() {
2682
2682
  var that = this;
2683
- open(that.path, that.flags, that.mode, function(err, fd) {
2683
+ open2(that.path, that.flags, that.mode, function(err, fd) {
2684
2684
  if (err) {
2685
2685
  if (that.autoClose)
2686
2686
  that.destroy();
@@ -2700,7 +2700,7 @@ var require_graceful_fs2 = __commonJS({
2700
2700
  }
2701
2701
  function WriteStream$open() {
2702
2702
  var that = this;
2703
- open(that.path, that.flags, that.mode, function(err, fd) {
2703
+ open2(that.path, that.flags, that.mode, function(err, fd) {
2704
2704
  if (err) {
2705
2705
  that.destroy();
2706
2706
  that.emit("error", err);
@@ -2717,8 +2717,8 @@ var require_graceful_fs2 = __commonJS({
2717
2717
  return new fs7.WriteStream(path10, options);
2718
2718
  }
2719
2719
  var fs$open = fs7.open;
2720
- fs7.open = open;
2721
- function open(path10, flags, mode, cb) {
2720
+ fs7.open = open2;
2721
+ function open2(path10, flags, mode, cb) {
2722
2722
  if (typeof mode === "function")
2723
2723
  cb = mode, mode = null;
2724
2724
  return go$open(path10, flags, mode, cb);
@@ -9399,7 +9399,7 @@ function isNodeError(value) {
9399
9399
 
9400
9400
  // src/lib/version.ts
9401
9401
  var CLI_PACKAGE_NAME = "@great-arrow/cli";
9402
- var CLI_VERSION = "0.1.38";
9402
+ var CLI_VERSION = "0.1.40";
9403
9403
  var GAD_INSTALL_VERSION = "2026.08.08";
9404
9404
  var GAD_INSTALL_MIN_VERSION = "2026.06.22";
9405
9405
  var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
@@ -10000,10 +10000,10 @@ function isColorEnabled(noColorFlag) {
10000
10000
  }
10001
10001
  function createColor(noColorFlag) {
10002
10002
  const enabled2 = isColorEnabled(noColorFlag);
10003
- const wrap = (open, close) => {
10003
+ const wrap = (open2, close) => {
10004
10004
  return (text2) => {
10005
10005
  if (!enabled2) return text2;
10006
- return `\x1B[${open}m${text2}\x1B[${close}m`;
10006
+ return `\x1B[${open2}m${text2}\x1B[${close}m`;
10007
10007
  };
10008
10008
  };
10009
10009
  return {
@@ -10306,9 +10306,68 @@ var logoutCommand = new Command("logout").description("Remove stored authenticat
10306
10306
  });
10307
10307
 
10308
10308
  // src/lib/auth.ts
10309
+ import { mkdir as mkdir3, open, rm, stat } from "node:fs/promises";
10310
+ import { homedir as homedir3 } from "node:os";
10311
+ import { join as join3 } from "node:path";
10309
10312
  var DEFAULT_API_URL2 = "https://www.greatarrow.ai";
10310
10313
  var REFRESH_SKEW_MS = 12e4;
10311
10314
  var inflightRefresh = null;
10315
+ var REFRESH_LOCK_STALE_MS = 15e3;
10316
+ var REFRESH_LOCK_WAIT_MS = 5e3;
10317
+ var REFRESH_LOCK_POLL_MS = 100;
10318
+ function refreshLockPath() {
10319
+ return join3(homedir3(), ".great-arrow", "refresh.lock");
10320
+ }
10321
+ async function acquireRefreshLock() {
10322
+ const path10 = refreshLockPath();
10323
+ try {
10324
+ await mkdir3(join3(homedir3(), ".great-arrow"), { recursive: true });
10325
+ } catch (err) {
10326
+ debugSwallow("refresh-lock.mkdir", err);
10327
+ }
10328
+ const deadline = Date.now() + REFRESH_LOCK_WAIT_MS;
10329
+ for (; ; ) {
10330
+ try {
10331
+ const handle = await open(path10, "wx");
10332
+ await handle.writeFile(String(process.pid));
10333
+ await handle.close();
10334
+ return true;
10335
+ } catch (err) {
10336
+ if (err.code !== "EEXIST") return true;
10337
+ }
10338
+ const age = await stat(path10).then((st) => Date.now() - st.mtimeMs).catch(() => Number.POSITIVE_INFINITY);
10339
+ if (age > REFRESH_LOCK_STALE_MS) {
10340
+ try {
10341
+ await rm(path10, { force: true });
10342
+ } catch (err) {
10343
+ debugSwallow("refresh-lock.stale-rm", err);
10344
+ }
10345
+ continue;
10346
+ }
10347
+ if (Date.now() >= deadline) return false;
10348
+ await new Promise((r) => setTimeout(r, REFRESH_LOCK_POLL_MS));
10349
+ }
10350
+ }
10351
+ async function releaseRefreshLock() {
10352
+ try {
10353
+ await rm(refreshLockPath(), { force: true });
10354
+ } catch (err) {
10355
+ debugSwallow("refresh-lock.release", err);
10356
+ }
10357
+ }
10358
+ async function rotatedByAnotherProcess(ours) {
10359
+ let stored = null;
10360
+ try {
10361
+ stored = await loadStoredCredential();
10362
+ } catch (err) {
10363
+ debugSwallow("refresh.reread", err);
10364
+ }
10365
+ if (stored?.kind !== "oauth") return null;
10366
+ const theirs = stored.credential;
10367
+ if (theirs.refreshToken === ours.refreshToken) return null;
10368
+ if (Date.now() >= theirs.accessExpiresAt - REFRESH_SKEW_MS) return null;
10369
+ return theirs;
10370
+ }
10312
10371
  async function resolveAuth() {
10313
10372
  const config = await loadConfig();
10314
10373
  const configApiUrl = config.api_url || DEFAULT_API_URL2;
@@ -10367,7 +10426,10 @@ async function ensureFreshAccessToken(credential) {
10367
10426
  }
10368
10427
  }
10369
10428
  async function doRefresh(credential) {
10429
+ const held = await acquireRefreshLock();
10370
10430
  try {
10431
+ const theirs = await rotatedByAnotherProcess(credential);
10432
+ if (theirs) return theirs;
10371
10433
  const refreshed = await refreshTokens({
10372
10434
  apiUrl: credential.apiUrl,
10373
10435
  clientId: credential.clientId,
@@ -10381,6 +10443,8 @@ async function doRefresh(credential) {
10381
10443
  throw sessionExpired();
10382
10444
  }
10383
10445
  throw err;
10446
+ } finally {
10447
+ if (held) await releaseRefreshLock();
10384
10448
  }
10385
10449
  }
10386
10450
  function sessionExpired() {
@@ -12198,7 +12262,7 @@ var work_default = workCommand;
12198
12262
  // src/commands/worktree.ts
12199
12263
  import { execFileSync as execFileSync3 } from "node:child_process";
12200
12264
  import { existsSync, readFileSync as readFileSync2, statSync, writeFileSync } from "node:fs";
12201
- import { basename, dirname as dirname3, isAbsolute, join as join3, resolve } from "node:path";
12265
+ import { basename, dirname as dirname3, isAbsolute, join as join4, resolve } from "node:path";
12202
12266
  var realExec = (cmd, args, opts) => {
12203
12267
  const out = execFileSync3(cmd, args, {
12204
12268
  cwd: opts?.cwd,
@@ -12220,9 +12284,9 @@ function parseDefaultBranch(symbolicRef) {
12220
12284
  return match ? match[1] : null;
12221
12285
  }
12222
12286
  function detectPackageManager(repoRoot) {
12223
- if (existsSync(join3(repoRoot, "pnpm-lock.yaml"))) return "pnpm";
12224
- if (existsSync(join3(repoRoot, "bun.lockb"))) return "bun";
12225
- if (existsSync(join3(repoRoot, "yarn.lock"))) return "yarn";
12287
+ if (existsSync(join4(repoRoot, "pnpm-lock.yaml"))) return "pnpm";
12288
+ if (existsSync(join4(repoRoot, "bun.lockb"))) return "bun";
12289
+ if (existsSync(join4(repoRoot, "yarn.lock"))) return "yarn";
12226
12290
  return "npm";
12227
12291
  }
12228
12292
  function defaultWorktreeDir(repoRoot, branch) {
@@ -12230,7 +12294,7 @@ function defaultWorktreeDir(repoRoot, branch) {
12230
12294
  /[^A-Za-z0-9._-]+/g,
12231
12295
  "-"
12232
12296
  );
12233
- return join3(dirname3(repoRoot), `${basename(repoRoot)}-${slug}`);
12297
+ return join4(dirname3(repoRoot), `${basename(repoRoot)}-${slug}`);
12234
12298
  }
12235
12299
  var AGENT_PROCESS_RE = /(^|\/)(claude|kiro|cursor|codex)/i;
12236
12300
  function psField(pid, field) {
@@ -12253,7 +12317,7 @@ function findAgentPid(startPid) {
12253
12317
  return null;
12254
12318
  }
12255
12319
  function ownGitDir(worktreeDir) {
12256
- const dotGit = join3(worktreeDir, ".git");
12320
+ const dotGit = join4(worktreeDir, ".git");
12257
12321
  if (!existsSync(dotGit)) return null;
12258
12322
  if (statSync(dotGit).isDirectory()) return dotGit;
12259
12323
  const match = readFileSync2(dotGit, "utf8").trim().match(/^gitdir:\s*(.+)$/m);
@@ -12268,7 +12332,7 @@ function stampWorktreeOwner(worktreeDir) {
12268
12332
  const ownerPid = findAgentPid(process.ppid);
12269
12333
  if (!ownerPid) return null;
12270
12334
  writeFileSync(
12271
- join3(gitDir, "agent-owner.json"),
12335
+ join4(gitDir, "agent-owner.json"),
12272
12336
  JSON.stringify(
12273
12337
  {
12274
12338
  ownerPid,
@@ -12362,7 +12426,7 @@ function createTaskWorktree(opts) {
12362
12426
  const stampedPid = opts.stamp === false ? null : stampWorktreeOwner(dir);
12363
12427
  const packageManager = detectPackageManager(repoRoot);
12364
12428
  let installed = false;
12365
- if (opts.install !== false && existsSync(join3(repoRoot, "package.json"))) {
12429
+ if (opts.install !== false && existsSync(join4(repoRoot, "package.json"))) {
12366
12430
  exec2(packageManager, ["install"], { cwd: dir, inherit: true });
12367
12431
  installed = true;
12368
12432
  }
@@ -12431,7 +12495,7 @@ import {
12431
12495
  readFileSync as readFileSync3,
12432
12496
  writeFileSync as writeFileSync2
12433
12497
  } from "node:fs";
12434
- import { isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
12498
+ import { isAbsolute as isAbsolute2, join as join5, resolve as resolve2 } from "node:path";
12435
12499
  var GUARD_MARKER = "# gad-agent-guard v1";
12436
12500
  var SCHEMA_AUDIT_MARKER = "# gad-schema-audit v1";
12437
12501
  var GUARD_FILE = "gad-commit-guard.mjs";
@@ -12614,22 +12678,22 @@ function installCommitGuard(opts) {
12614
12678
  );
12615
12679
  }
12616
12680
  if (!isAbsolute2(commonDir)) commonDir = resolve2(repoRoot, commonDir);
12617
- const hooksDir = join4(commonDir, "hooks");
12681
+ const hooksDir = join5(commonDir, "hooks");
12618
12682
  mkdirSync(hooksDir, { recursive: true });
12619
- const guardPath = join4(hooksDir, GUARD_FILE);
12683
+ const guardPath = join5(hooksDir, GUARD_FILE);
12620
12684
  writeFileSync2(guardPath, buildGuardScript());
12621
12685
  chmodSync(guardPath, 493);
12622
- const huskyHook = join4(repoRoot, ".husky", "pre-commit");
12623
- const hookPath = existsSync2(huskyHook) ? huskyHook : join4(hooksDir, "pre-commit");
12686
+ const huskyHook = join5(repoRoot, ".husky", "pre-commit");
12687
+ const hookPath = existsSync2(huskyHook) ? huskyHook : join5(hooksDir, "pre-commit");
12624
12688
  const existing = existsSync2(hookPath) ? readFileSync3(hookPath, "utf8") : null;
12625
12689
  const plan = planHookInstall(existing);
12626
12690
  if (plan.action !== "noop") {
12627
12691
  writeFileSync2(hookPath, plan.content);
12628
12692
  }
12629
12693
  chmodSync(hookPath, 493);
12630
- const huskyDir = join4(repoRoot, ".husky");
12631
- const huskyPrePush = join4(huskyDir, "pre-push");
12632
- const schemaAuditHookPath = existsSync2(huskyPrePush) || existsSync2(huskyDir) ? huskyPrePush : join4(hooksDir, "pre-push");
12694
+ const huskyDir = join5(repoRoot, ".husky");
12695
+ const huskyPrePush = join5(huskyDir, "pre-push");
12696
+ const schemaAuditHookPath = existsSync2(huskyPrePush) || existsSync2(huskyDir) ? huskyPrePush : join5(hooksDir, "pre-push");
12633
12697
  const existingSchemaAuditHook = existsSync2(schemaAuditHookPath) ? readFileSync3(schemaAuditHookPath, "utf8") : null;
12634
12698
  const schemaAuditPlan = planSchemaAuditHookInstall(existingSchemaAuditHook);
12635
12699
  if (schemaAuditPlan.action !== "noop") {
@@ -12752,9 +12816,9 @@ function guardrailsCommand() {
12752
12816
 
12753
12817
  // src/commands/doctor.ts
12754
12818
  import { existsSync as existsSync3 } from "node:fs";
12755
- import { copyFile, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
12756
- import { homedir as homedir3 } from "node:os";
12757
- import { dirname as dirname4, join as join5 } from "node:path";
12819
+ import { copyFile, mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
12820
+ import { homedir as homedir4 } from "node:os";
12821
+ import { dirname as dirname4, join as join6 } from "node:path";
12758
12822
 
12759
12823
  // src/lib/codex-config.ts
12760
12824
  import { execFileSync as execFileSync5 } from "node:child_process";
@@ -14239,7 +14303,7 @@ async function checkMcp(auth) {
14239
14303
  }
14240
14304
  }
14241
14305
  function defaultCodexConfigPath() {
14242
- return join5(homedir3(), ".codex", "config.toml");
14306
+ return join6(homedir4(), ".codex", "config.toml");
14243
14307
  }
14244
14308
  function codexIssuesToChecks(issues) {
14245
14309
  return issues.map((issue) => {
@@ -14283,7 +14347,7 @@ function codexCommand() {
14283
14347
  if (options.enforce) {
14284
14348
  const apiUrl = extractCodexGreatArrowApiUrl(content) ?? (await resolveAuth()).apiUrl;
14285
14349
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
14286
- await mkdir3(dirname4(configPath), { recursive: true });
14350
+ await mkdir4(dirname4(configPath), { recursive: true });
14287
14351
  if (content.trim()) {
14288
14352
  backupPath = `${configPath}.bak.${timestamp}`;
14289
14353
  await copyFile(configPath, backupPath);
@@ -14377,12 +14441,12 @@ function claudeCommand() {
14377
14441
  isTTY: isTTY(),
14378
14442
  noColor: g.noColor
14379
14443
  });
14380
- const home = homedir3();
14381
- const gaDir = join5(home, ".great-arrow");
14382
- const userConfigPath = join5(home, ".claude.json");
14383
- const settingsPath = join5(home, ".claude", "settings.json");
14384
- const hookConfigPath = join5(gaDir, "hook-config.json");
14385
- const glanceHookPath = join5(gaDir, "claude-glance-hook.mjs");
14444
+ const home = homedir4();
14445
+ const gaDir = join6(home, ".great-arrow");
14446
+ const userConfigPath = join6(home, ".claude.json");
14447
+ const settingsPath = join6(home, ".claude", "settings.json");
14448
+ const hookConfigPath = join6(gaDir, "hook-config.json");
14449
+ const glanceHookPath = join6(gaDir, "claude-glance-hook.mjs");
14386
14450
  const checks = [];
14387
14451
  const repairs = [];
14388
14452
  const backups = [];
@@ -14404,7 +14468,7 @@ function claudeCommand() {
14404
14468
  await copyFile(path10, backupPath);
14405
14469
  backups.push(backupPath);
14406
14470
  }
14407
- await mkdir3(dirname4(path10), { recursive: true });
14471
+ await mkdir4(dirname4(path10), { recursive: true });
14408
14472
  await writeFile3(path10, content, "utf8");
14409
14473
  };
14410
14474
  let auth = null;
@@ -14451,7 +14515,7 @@ function claudeCommand() {
14451
14515
  { signal: AbortSignal.timeout(1e4) }
14452
14516
  );
14453
14517
  if (res.ok) {
14454
- await mkdir3(gaDir, { recursive: true });
14518
+ await mkdir4(gaDir, { recursive: true });
14455
14519
  await writeFile3(glanceHookPath, await res.text(), {
14456
14520
  mode: 493
14457
14521
  });
@@ -14590,8 +14654,8 @@ function claudeCommand() {
14590
14654
  file: "/Library/Application Support/ClaudeCode/managed-settings.json"
14591
14655
  },
14592
14656
  { scope: "user", file: settingsPath },
14593
- { scope: "project", file: join5(cwd, ".claude", "settings.json") },
14594
- { scope: "local", file: join5(cwd, ".claude", "settings.local.json") }
14657
+ { scope: "project", file: join6(cwd, ".claude", "settings.json") },
14658
+ { scope: "local", file: join6(cwd, ".claude", "settings.local.json") }
14595
14659
  ];
14596
14660
  const configured = [];
14597
14661
  for (const { scope, file } of scopes) {
@@ -14660,12 +14724,12 @@ function claudeCommand() {
14660
14724
  });
14661
14725
  }
14662
14726
  function defaultJsonClientConfigs() {
14663
- const home = homedir3();
14727
+ const home = homedir4();
14664
14728
  return [
14665
- { name: "Claude Code", path: join5(home, ".claude.json") },
14666
- { name: "Cursor", path: join5(home, ".cursor", "mcp.json") },
14667
- { name: "Kiro", path: join5(home, ".kiro", "settings", "mcp.json") },
14668
- { name: "Gemini CLI", path: join5(home, ".gemini", "settings.json") }
14729
+ { name: "Claude Code", path: join6(home, ".claude.json") },
14730
+ { name: "Cursor", path: join6(home, ".cursor", "mcp.json") },
14731
+ { name: "Kiro", path: join6(home, ".kiro", "settings", "mcp.json") },
14732
+ { name: "Gemini CLI", path: join6(home, ".gemini", "settings.json") }
14669
14733
  ];
14670
14734
  }
14671
14735
  function clientsCommand() {
@@ -15311,14 +15375,14 @@ async function pathExists(path10) {
15311
15375
  }
15312
15376
  }
15313
15377
  async function installCodexConfig(apiUrl, token, repoUrl) {
15314
- const { homedir: homedir5 } = await import("node:os");
15315
- const { join: join7 } = await import("node:path");
15316
- const { chmod, copyFile: copyFile2, mkdir: mkdir5, readFile: readFile5, writeFile: writeFile5 } = await import("node:fs/promises");
15317
- const codexDir = join7(homedir5(), ".codex");
15318
- const configPath = join7(codexDir, "config.toml");
15319
- const envPath = join7(codexDir, ".env");
15378
+ const { homedir: homedir6 } = await import("node:os");
15379
+ const { join: join8 } = await import("node:path");
15380
+ const { chmod, copyFile: copyFile2, mkdir: mkdir6, readFile: readFile5, writeFile: writeFile5 } = await import("node:fs/promises");
15381
+ const codexDir = join8(homedir6(), ".codex");
15382
+ const configPath = join8(codexDir, "config.toml");
15383
+ const envPath = join8(codexDir, ".env");
15320
15384
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
15321
- await mkdir5(codexDir, { recursive: true });
15385
+ await mkdir6(codexDir, { recursive: true });
15322
15386
  const existingConfig = await pathExists(configPath) ? await readFile5(configPath, "utf8") : "";
15323
15387
  if (existingConfig.trim()) {
15324
15388
  await copyFile2(configPath, `${configPath}.bak.${timestamp}`);
@@ -15614,12 +15678,12 @@ var installCommand = new Command("install").description("Generate MCP configurat
15614
15678
  process.stdout.write(configJson2 + "\n");
15615
15679
  return;
15616
15680
  }
15617
- const { homedir: homedir5 } = await import("node:os");
15618
- const { join: join8 } = await import("node:path");
15619
- const { mkdir: mkdir5, readFile: readFile5, writeFile: writeFile6 } = await import("node:fs/promises");
15620
- const geminiDir = join8(homedir5(), ".gemini");
15621
- const mcpPath = join8(geminiDir, "settings.json");
15622
- await mkdir5(geminiDir, { recursive: true });
15681
+ const { homedir: homedir6 } = await import("node:os");
15682
+ const { join: join9 } = await import("node:path");
15683
+ const { mkdir: mkdir6, readFile: readFile5, writeFile: writeFile6 } = await import("node:fs/promises");
15684
+ const geminiDir = join9(homedir6(), ".gemini");
15685
+ const mcpPath = join9(geminiDir, "settings.json");
15686
+ await mkdir6(geminiDir, { recursive: true });
15623
15687
  let existingConfig = {};
15624
15688
  try {
15625
15689
  const content = await readFile5(mcpPath, "utf-8");
@@ -15640,14 +15704,14 @@ var installCommand = new Command("install").description("Generate MCP configurat
15640
15704
  "utf-8"
15641
15705
  );
15642
15706
  fmt.success(`Wrote gemini MCP config to ${mcpPath}`);
15643
- const agyMcpDir = join8(
15707
+ const agyMcpDir = join9(
15644
15708
  geminiDir,
15645
15709
  "antigravity-cli",
15646
15710
  "mcp",
15647
15711
  MCP_SERVER_NAME
15648
15712
  );
15649
- await mkdir5(agyMcpDir, { recursive: true });
15650
- const instructionsPath = join8(agyMcpDir, "instructions.md");
15713
+ await mkdir6(agyMcpDir, { recursive: true });
15714
+ const instructionsPath = join9(agyMcpDir, "instructions.md");
15651
15715
  const instructionsContent = `You are connected to greatarrow.ai - the user's private workspace memory + integrations brain.
15652
15716
 
15653
15717
  CRITICAL DIRECTIVE (MANDATORY STANDARD):
@@ -15679,8 +15743,8 @@ Always assume full autonomy to leverage these tools to their maximum extent.`;
15679
15743
  return;
15680
15744
  }
15681
15745
  const { writeFile: writeFile5 } = await import("node:fs/promises");
15682
- const { join: join7 } = await import("node:path");
15683
- const outputPath = join7(process.cwd(), ".mcp.json");
15746
+ const { join: join8 } = await import("node:path");
15747
+ const outputPath = join8(process.cwd(), ".mcp.json");
15684
15748
  await writeFile5(outputPath, configJson + "\n", { mode: 384 });
15685
15749
  fmt.success(`Wrote ${clientName} MCP config to .mcp.json`);
15686
15750
  if (await mcpJsonAtRiskOfCommit(process.cwd())) {
@@ -15711,13 +15775,13 @@ async function readKeychainOnly() {
15711
15775
  }
15712
15776
  }
15713
15777
  async function resolveProxyScript(override) {
15714
- const { homedir: homedir5 } = await import("node:os");
15715
- const { join: join7 } = await import("node:path");
15778
+ const { homedir: homedir6 } = await import("node:os");
15779
+ const { join: join8 } = await import("node:path");
15716
15780
  const candidates = [
15717
15781
  override,
15718
15782
  process.env.GAD_PROXY_SCRIPT,
15719
- join7(homedir5(), ".great-arrow", "mcp-server.mjs"),
15720
- join7(process.cwd(), "scripts", "mcp-server.mjs")
15783
+ join8(homedir6(), ".great-arrow", "mcp-server.mjs"),
15784
+ join8(process.cwd(), "scripts", "mcp-server.mjs")
15721
15785
  ].filter((c) => Boolean(c));
15722
15786
  for (const candidate of candidates) {
15723
15787
  if (await pathExists(candidate)) return candidate;
@@ -15741,13 +15805,13 @@ async function runProxyDiagnostics(scriptPath) {
15741
15805
  }
15742
15806
  }
15743
15807
  async function findLegacyStores(cwd) {
15744
- const { join: join7 } = await import("node:path");
15808
+ const { join: join8 } = await import("node:path");
15745
15809
  const { readdir } = await import("node:fs/promises");
15746
15810
  const dirs = /* @__PURE__ */ new Set([cwd]);
15747
15811
  try {
15748
- const worktreeRoot = join7(cwd, ".claude", "worktrees");
15812
+ const worktreeRoot = join8(cwd, ".claude", "worktrees");
15749
15813
  for (const entry of await readdir(worktreeRoot, { withFileTypes: true })) {
15750
- if (entry.isDirectory()) dirs.add(join7(worktreeRoot, entry.name));
15814
+ if (entry.isDirectory()) dirs.add(join8(worktreeRoot, entry.name));
15751
15815
  }
15752
15816
  } catch (err) {
15753
15817
  debugSwallow("mcp.findLegacyStores(worktrees)", err);
@@ -15769,18 +15833,18 @@ async function findLegacyStores(cwd) {
15769
15833
  }
15770
15834
  const stores = [];
15771
15835
  for (const dir of dirs) {
15772
- const path10 = join7(dir, ".mcp", OAUTH_CRED_FILENAME);
15836
+ const path10 = join8(dir, ".mcp", OAUTH_CRED_FILENAME);
15773
15837
  const raw = await readIfExists(path10);
15774
15838
  if (raw !== null) stores.push({ path: path10, raw });
15775
15839
  }
15776
15840
  return stores;
15777
15841
  }
15778
15842
  async function scanConnections() {
15779
- const { homedir: homedir5 } = await import("node:os");
15780
- const { join: join7 } = await import("node:path");
15781
- const home = homedir5();
15843
+ const { homedir: homedir6 } = await import("node:os");
15844
+ const { join: join8 } = await import("node:path");
15845
+ const home = homedir6();
15782
15846
  const hookSettings = await readIfExists(
15783
- join7(home, ".claude", "settings.json")
15847
+ join8(home, ".claude", "settings.json")
15784
15848
  );
15785
15849
  const hooksInstalled = hookSettings ? hasGreatArrowHooks(hookSettings) : false;
15786
15850
  const entries = [];
@@ -15813,16 +15877,16 @@ async function probeEndpoint() {
15813
15877
  }
15814
15878
  }
15815
15879
  async function applyMigration(plan) {
15816
- const { homedir: homedir5 } = await import("node:os");
15817
- const { join: join7 } = await import("node:path");
15818
- const { mkdir: mkdir5, rename, writeFile: writeFile5 } = await import("node:fs/promises");
15880
+ const { homedir: homedir6 } = await import("node:os");
15881
+ const { join: join8 } = await import("node:path");
15882
+ const { mkdir: mkdir6, rename, writeFile: writeFile5 } = await import("node:fs/promises");
15819
15883
  const applied = [];
15820
- const dir = join7(homedir5(), ".great-arrow");
15821
- await mkdir5(dir, { recursive: true, mode: 448 });
15884
+ const dir = join8(homedir6(), ".great-arrow");
15885
+ await mkdir6(dir, { recursive: true, mode: 448 });
15822
15886
  if (plan.seedFrom) {
15823
15887
  const raw = await readIfExists(plan.seedFrom);
15824
15888
  if (raw !== null) {
15825
- const target = join7(dir, OAUTH_CRED_FILENAME);
15889
+ const target = join8(dir, OAUTH_CRED_FILENAME);
15826
15890
  const tmp = `${target}.tmp`;
15827
15891
  await writeFile5(tmp, raw, { mode: 384 });
15828
15892
  await rename(tmp, target);
@@ -15832,7 +15896,7 @@ async function applyMigration(plan) {
15832
15896
  if (plan.syncKeychainToFile) {
15833
15897
  const raw = await readKeychainOnly();
15834
15898
  if (raw) {
15835
- const target = join7(dir, "credential");
15899
+ const target = join8(dir, "credential");
15836
15900
  const tmp = `${target}.tmp`;
15837
15901
  await writeFile5(tmp, raw, { mode: 384 });
15838
15902
  await rename(tmp, target);
@@ -15858,19 +15922,19 @@ var doctorSubcommand = new Command("doctor").description(
15858
15922
  noColor: false
15859
15923
  });
15860
15924
  const c = createColor(jsonFlag);
15861
- const { homedir: homedir5 } = await import("node:os");
15862
- const { join: join7 } = await import("node:path");
15863
- const home = homedir5();
15925
+ const { homedir: homedir6 } = await import("node:os");
15926
+ const { join: join8 } = await import("node:path");
15927
+ const home = homedir6();
15864
15928
  const proxyPath = await resolveProxyScript(options.proxy);
15865
15929
  const diagnostics = proxyPath ? await runProxyDiagnostics(proxyPath) : null;
15866
15930
  const entries = await scanConnections();
15867
15931
  const duplicates = detectDuplicates(entries);
15868
15932
  const keychain = await readKeychainOnly();
15869
15933
  const credentialFile = await readIfExists(
15870
- join7(home, ".great-arrow", "credential")
15934
+ join8(home, ".great-arrow", "credential")
15871
15935
  );
15872
15936
  const userStore = await readIfExists(
15873
- join7(home, ".great-arrow", OAUTH_CRED_FILENAME)
15937
+ join8(home, ".great-arrow", OAUTH_CRED_FILENAME)
15874
15938
  );
15875
15939
  const legacyStores = await findLegacyStores(process.cwd());
15876
15940
  const findings = [
@@ -16287,9 +16351,9 @@ function askCommand() {
16287
16351
  }
16288
16352
 
16289
16353
  // src/lib/glance-footer.ts
16290
- import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
16291
- import { homedir as homedir4 } from "node:os";
16292
- import { join as join6, dirname as dirname5 } from "node:path";
16354
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir5 } from "node:fs/promises";
16355
+ import { homedir as homedir5 } from "node:os";
16356
+ import { join as join7, dirname as dirname5 } from "node:path";
16293
16357
 
16294
16358
  // src/lib/seen.ts
16295
16359
  var SEEN_TIMEOUT_MS = 1e3;
@@ -16327,7 +16391,11 @@ async function reportSeen(opts) {
16327
16391
  }
16328
16392
 
16329
16393
  // src/lib/glance-footer.ts
16330
- var FETCH_TIMEOUT_MS = 1500;
16394
+ var BASE_FETCH_TIMEOUT_MS = 1500;
16395
+ var TIP_FETCH_TIMEOUT_MS = 6e3;
16396
+ function fetchBudgetMs(askForTip) {
16397
+ return askForTip ? TIP_FETCH_TIMEOUT_MS : BASE_FETCH_TIMEOUT_MS;
16398
+ }
16331
16399
  var THROTTLE_INTERVAL_MS = 4 * 60 * 60 * 1e3;
16332
16400
  var TIP_THROTTLE_INTERVAL_MS = 20 * 60 * 60 * 1e3;
16333
16401
  var URGENT_THRESHOLD = 15;
@@ -16341,8 +16409,20 @@ async function maybeShowGlanceFooter(argv) {
16341
16409
  const entry = state[workspaceKey] ?? {};
16342
16410
  const now = Date.now();
16343
16411
  const askForTip = shouldRequestTip({ state: entry, now });
16344
- const payload = await fetchGlance(auth.apiUrl, auth.token, askForTip);
16345
- if (!payload) return;
16412
+ const fetched = await fetchGlance(auth.apiUrl, auth.token, askForTip);
16413
+ const payload = fetched.payload;
16414
+ if (!payload) {
16415
+ if (fetched.tipMaybeSpent) {
16416
+ state[workspaceKey] = nextGlanceState(entry, {
16417
+ now,
16418
+ tipMaybeSpent: true,
16419
+ showNeedsYou: false,
16420
+ topItemId: null
16421
+ });
16422
+ await saveGlanceState(state);
16423
+ }
16424
+ return;
16425
+ }
16346
16426
  const showNeedsYou = payload.urgency !== null && shouldShowGlance({
16347
16427
  urgency: payload.urgency,
16348
16428
  topItemId: topSignature(payload),
@@ -16350,14 +16430,22 @@ async function maybeShowGlanceFooter(argv) {
16350
16430
  now
16351
16431
  });
16352
16432
  const tip = payload.tip;
16353
- if (!showNeedsYou && !tip) return;
16433
+ const nextEntry = nextGlanceState(entry, {
16434
+ now,
16435
+ tipMaybeSpent: fetched.tipMaybeSpent,
16436
+ showNeedsYou,
16437
+ topItemId: topSignature(payload)
16438
+ });
16439
+ if (!showNeedsYou && !tip) {
16440
+ if (nextEntry !== entry) {
16441
+ state[workspaceKey] = nextEntry;
16442
+ await saveGlanceState(state);
16443
+ }
16444
+ return;
16445
+ }
16354
16446
  const printed = showNeedsYou ? renderFooter(payload, auth.apiUrl) : [];
16355
16447
  if (tip) renderTip(tip, auth.apiUrl);
16356
- state[workspaceKey] = {
16357
- ...entry,
16358
- ...showNeedsYou ? { lastShownAt: now, lastTopItemId: topSignature(payload) } : {},
16359
- ...tip ? { lastTipAt: now } : {}
16360
- };
16448
+ state[workspaceKey] = nextEntry;
16361
16449
  await saveGlanceState(state);
16362
16450
  await reportSeen({
16363
16451
  apiUrl: auth.apiUrl,
@@ -16389,7 +16477,8 @@ function firstCommand(argv) {
16389
16477
  }
16390
16478
  async function fetchGlance(apiUrl, token, includeTip) {
16391
16479
  const controller = new AbortController();
16392
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
16480
+ const timer = setTimeout(() => controller.abort(), fetchBudgetMs(includeTip));
16481
+ let tipMaybeSpent = includeTip;
16393
16482
  try {
16394
16483
  const response = await fetch(`${apiUrl}/api/mcp`, {
16395
16484
  method: "POST",
@@ -16410,14 +16499,20 @@ async function fetchGlance(apiUrl, token, includeTip) {
16410
16499
  }),
16411
16500
  signal: controller.signal
16412
16501
  });
16413
- if (!response.ok) return null;
16502
+ if (!response.ok) {
16503
+ tipMaybeSpent = false;
16504
+ return { payload: null, tipMaybeSpent };
16505
+ }
16414
16506
  const data = await response.json();
16415
- if (data.error) return null;
16416
- return normalizeGlance(
16417
- parseMcpPayload(data.result?.content ?? data.result)
16418
- );
16507
+ if (data.error) return { payload: null, tipMaybeSpent };
16508
+ return {
16509
+ payload: normalizeGlance(
16510
+ parseMcpPayload(data.result?.content ?? data.result)
16511
+ ),
16512
+ tipMaybeSpent
16513
+ };
16419
16514
  } catch {
16420
- return null;
16515
+ return { payload: null, tipMaybeSpent };
16421
16516
  } finally {
16422
16517
  clearTimeout(timer);
16423
16518
  }
@@ -16566,6 +16661,15 @@ function topSignature(payload) {
16566
16661
  if (payload.topGoal) return `goal:${payload.topGoal.title}`;
16567
16662
  return "empty";
16568
16663
  }
16664
+ function nextGlanceState(entry, input) {
16665
+ const { now, tipMaybeSpent, showNeedsYou, topItemId } = input;
16666
+ if (!tipMaybeSpent && !showNeedsYou) return entry;
16667
+ return {
16668
+ ...entry,
16669
+ ...showNeedsYou && topItemId !== null ? { lastShownAt: now, lastTopItemId: topItemId } : {},
16670
+ ...tipMaybeSpent ? { lastTipAt: now } : {}
16671
+ };
16672
+ }
16569
16673
  function shouldShowGlance(input) {
16570
16674
  const { urgency, topItemId, state, now } = input;
16571
16675
  if (urgency > URGENT_THRESHOLD) return false;
@@ -16579,7 +16683,7 @@ function shouldRequestTip(input) {
16579
16683
  return now - state.lastTipAt >= TIP_THROTTLE_INTERVAL_MS;
16580
16684
  }
16581
16685
  function getGlanceStatePath() {
16582
- return join6(homedir4(), ".gad", "glance-state.json");
16686
+ return join7(homedir5(), ".gad", "glance-state.json");
16583
16687
  }
16584
16688
  async function loadGlanceState() {
16585
16689
  try {
@@ -16595,7 +16699,7 @@ async function loadGlanceState() {
16595
16699
  }
16596
16700
  async function saveGlanceState(state) {
16597
16701
  const path10 = getGlanceStatePath();
16598
- await mkdir4(dirname5(path10), { recursive: true });
16702
+ await mkdir5(dirname5(path10), { recursive: true });
16599
16703
  await writeFile4(path10, JSON.stringify(state), "utf-8");
16600
16704
  }
16601
16705
  function glanceItemUrl(href, apiUrl) {
@@ -19640,18 +19744,18 @@ var proto = Object.defineProperties(() => {
19640
19744
  }
19641
19745
  }
19642
19746
  });
19643
- var createStyler = (open, close, parent) => {
19747
+ var createStyler = (open2, close, parent) => {
19644
19748
  let openAll;
19645
19749
  let closeAll;
19646
19750
  if (parent === void 0) {
19647
- openAll = open;
19751
+ openAll = open2;
19648
19752
  closeAll = close;
19649
19753
  } else {
19650
- openAll = parent.openAll + open;
19754
+ openAll = parent.openAll + open2;
19651
19755
  closeAll = close + parent.closeAll;
19652
19756
  }
19653
19757
  return {
19654
- open,
19758
+ open: open2,
19655
19759
  close,
19656
19760
  openAll,
19657
19761
  closeAll,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {