@great-arrow/cli 0.1.20 → 0.1.25

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 +35 -6
  2. package/dist/index.js +204 -180
  3. package/package.json +6 -7
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @great-arrow/cli (`gad`)
2
2
 
3
- Terminal-based access to the Great Arrow Digital platform — memory management, workspace administration, integration diagnostics, MCP tooling, and deployment operations.
3
+ Terminal-based access to the Great Arrow Digital platform — memory management, workspace administration, integration diagnostics, and MCP tooling.
4
4
 
5
5
  ## Install
6
6
 
@@ -109,6 +109,14 @@ override: `ALLOW_FOREIGN_WORKTREE=1`.
109
109
  # Full health check (token, API, integrations, MCP)
110
110
  gad doctor
111
111
 
112
+ # Claude Code surface: server entry, hook wiring, hook credential — and
113
+ # whether every hook you have CONFIGURED can actually run. That last check
114
+ # reads all four settings scopes (managed, user, project, local), because a
115
+ # hook that cannot execute fails silently on every single session and no
116
+ # repo-side test can see your user or local file.
117
+ gad doctor claude
118
+ gad doctor claude --enforce # back up and repair what is repairable
119
+
112
120
  # Integration management
113
121
  gad integrations list
114
122
  gad integrations sync github
@@ -145,7 +153,31 @@ gad mcp install gemini
145
153
  ```
146
154
 
147
155
  `gad mcp install codex` writes Codex's native `~/.codex/config.toml` server
148
- entry and stores the bearer token in `~/.codex/.env`.
156
+ entry and stores the bearer token in `~/.codex/.env`. The entry launches the
157
+ stdio bridge, which detects the current checkout's git origin for every Codex
158
+ session; it does not pin one repository in the global config.
159
+
160
+ Workspace targeting precedence is explicit command target, current git repo,
161
+ then the saved default outside a repo:
162
+
163
+ ```bash
164
+ # One command only; accepts an id, slug, or exact name.
165
+ gad --workspace great-arrow-digital todo list
166
+
167
+ # Save a default and see the previous/current workspace in the confirmation.
168
+ gad workspace switch great-arrow-digital
169
+
170
+ # Inside a linked git checkout, the repo workspace wins over that saved default.
171
+ gad workspace list
172
+ ```
173
+
174
+ `workspace_list` and `tools_discover` are compact by default. MCP callers can
175
+ request `detail:"full"` when diagnostics are needed; when a stable tool name is
176
+ known, `tools_discover({ tool_name: exactName })` returns one schema without
177
+ walking the full catalog or performing session/recommendation datastore reads.
178
+ High-frequency `todo_log` and `work_heartbeat` calls skip the enrichment batch
179
+ and render a single branded line, preserving the current workspace cue without
180
+ repeating board/status payloads.
149
181
 
150
182
  #### Keeping local MCP installs current
151
183
 
@@ -175,12 +207,9 @@ gad agent invoke research-analyst --task "Summarize Q2 metrics"
175
207
  gad ask "What did we decide about the caching layer?"
176
208
  ```
177
209
 
178
- ### Deployment & Operations
210
+ ### Operations
179
211
 
180
212
  ```bash
181
- gad deploy # Interactive confirmation
182
- gad deploy --confirm # CI/CD (no prompt)
183
- gad migrate # Run pending DB migrations
184
213
  gad smoke # MCP smoke test suite
185
214
  ```
186
215
 
package/dist/index.js CHANGED
@@ -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.20";
9402
+ var CLI_VERSION = "0.1.25";
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";
@@ -9427,9 +9427,10 @@ var ACCOUNT = "api-token";
9427
9427
  function credentialFilePath() {
9428
9428
  return join(homedir(), ".great-arrow", "credential");
9429
9429
  }
9430
- async function loadKeytar() {
9430
+ async function loadEntry() {
9431
9431
  try {
9432
- return await import("keytar");
9432
+ const { AsyncEntry } = await import("@napi-rs/keyring");
9433
+ return new AsyncEntry(SERVICE, ACCOUNT);
9433
9434
  } catch {
9434
9435
  return null;
9435
9436
  }
@@ -9444,10 +9445,10 @@ async function readFileFallback() {
9444
9445
  }
9445
9446
  }
9446
9447
  async function getToken() {
9447
- const keytar = await loadKeytar();
9448
- if (keytar) {
9448
+ const entry = await loadEntry();
9449
+ if (entry) {
9449
9450
  try {
9450
- const value = await keytar.getPassword(SERVICE, ACCOUNT);
9451
+ const value = await entry.getPassword();
9451
9452
  if (value) return value;
9452
9453
  } catch (err) {
9453
9454
  debugSwallow("keychain.getToken", err);
@@ -9456,10 +9457,10 @@ async function getToken() {
9456
9457
  return readFileFallback();
9457
9458
  }
9458
9459
  async function setToken(token) {
9459
- const keytar = await loadKeytar();
9460
- if (keytar) {
9460
+ const entry = await loadEntry();
9461
+ if (entry) {
9461
9462
  try {
9462
- await keytar.setPassword(SERVICE, ACCOUNT, token);
9463
+ await entry.setPassword(token);
9463
9464
  return true;
9464
9465
  } catch (err) {
9465
9466
  debugSwallow("keychain.setToken", err);
@@ -9475,10 +9476,10 @@ async function setToken(token) {
9475
9476
  }
9476
9477
  }
9477
9478
  async function deleteToken() {
9478
- const keytar = await loadKeytar();
9479
- if (keytar) {
9479
+ const entry = await loadEntry();
9480
+ if (entry) {
9480
9481
  try {
9481
- await keytar.deletePassword(SERVICE, ACCOUNT);
9482
+ await entry.deletePassword();
9482
9483
  } catch (err) {
9483
9484
  debugSwallow("keychain.deleteToken", err);
9484
9485
  }
@@ -10507,6 +10508,17 @@ async function captureCliEvent(input) {
10507
10508
  }
10508
10509
  }
10509
10510
 
10511
+ // src/lib/workspace-target.ts
10512
+ var commandWorkspaceTarget;
10513
+ var savedWorkspaceTarget;
10514
+ function setCommandWorkspaceTarget(target, savedDefault) {
10515
+ commandWorkspaceTarget = target?.trim() || void 0;
10516
+ savedWorkspaceTarget = savedDefault?.trim() || void 0;
10517
+ }
10518
+ function getCommandWorkspaceTarget(options = {}) {
10519
+ return commandWorkspaceTarget ?? (options.hasRepo ? void 0 : savedWorkspaceTarget);
10520
+ }
10521
+
10510
10522
  // src/lib/client.ts
10511
10523
  var DEFAULT_TIMEOUT_MS2 = 3e4;
10512
10524
  var MAX_RETRIES = 5;
@@ -10559,11 +10571,13 @@ var GadClient = class {
10559
10571
  token;
10560
10572
  apiUrl;
10561
10573
  repoUrl;
10574
+ workspaceId;
10562
10575
  timeout;
10563
10576
  constructor(options) {
10564
10577
  this.token = options.token;
10565
10578
  this.apiUrl = options.apiUrl.replace(/\/$/, "");
10566
10579
  this.repoUrl = options.repoUrl ?? detectGitOrigin();
10580
+ this.workspaceId = options.workspaceId?.trim() || getCommandWorkspaceTarget({ hasRepo: Boolean(this.repoUrl) }) || null;
10567
10581
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS2;
10568
10582
  }
10569
10583
  /**
@@ -10581,6 +10595,9 @@ var GadClient = class {
10581
10595
  if (this.repoUrl) {
10582
10596
  headers["X-Repo-URL"] = this.repoUrl;
10583
10597
  }
10598
+ if (this.workspaceId) {
10599
+ headers["X-Workspace-Id"] = this.workspaceId;
10600
+ }
10584
10601
  return headers;
10585
10602
  }
10586
10603
  /**
@@ -11136,11 +11153,22 @@ var switchCommand = new Command("switch").description("Set the active workspace
11136
11153
  noColor: false
11137
11154
  });
11138
11155
  const client = await createClient();
11139
- await client.mcpCall("workspace_switch", { workspaceId: nameOrId });
11156
+ const result = await client.mcpCall("workspace_switch", {
11157
+ workspaceId: nameOrId
11158
+ });
11159
+ const payload = typeof result === "object" && result !== null ? result : {};
11160
+ const current = typeof payload.current_workspace === "object" && payload.current_workspace !== null ? payload.current_workspace : null;
11161
+ const resolvedId = typeof current?.id === "string" ? current.id : nameOrId;
11162
+ const resolvedName = typeof current?.name === "string" ? current.name : nameOrId;
11140
11163
  const config = await loadConfig();
11141
- config.default_workspace = nameOrId;
11164
+ config.default_workspace = resolvedId;
11142
11165
  await saveConfig(config);
11143
- fmt.success(`Switched to workspace: ${nameOrId}`);
11166
+ const previous = typeof payload.previous_workspace === "object" && payload.previous_workspace !== null ? payload.previous_workspace : null;
11167
+ const previousName = typeof previous?.name === "string" ? previous.name : null;
11168
+ const previousId = typeof previous?.id === "string" ? previous.id : void 0;
11169
+ fmt.success(
11170
+ previousName && previousId && previousName !== resolvedName ? `Switched workspace: ${previousName} \u2192 ${resolvedName} (use '${previousId}' to switch back)` : `Working in workspace: ${resolvedName}`
11171
+ );
11144
11172
  });
11145
11173
  var infoColumns = [
11146
11174
  { key: "name", header: "Name" },
@@ -12669,6 +12697,7 @@ import { dirname as dirname4, join as join5 } from "node:path";
12669
12697
  import { execFileSync as execFileSync5 } from "node:child_process";
12670
12698
  var CODEX_SERVER_NAME = "great-arrow";
12671
12699
  var CODEX_CLIENT_ID = "codex-cli";
12700
+ var CODEX_APPROVAL_POLICY = 'approval_policy = "never"';
12672
12701
  var SAFE_GLOBAL_MCP_SERVERS = /* @__PURE__ */ new Set([
12673
12702
  CODEX_SERVER_NAME,
12674
12703
  "great-arrow-kiro-terminal",
@@ -12744,19 +12773,19 @@ function repoKey(repoUrl) {
12744
12773
  return null;
12745
12774
  }
12746
12775
  }
12747
- function generateCodexConfigToml(apiUrl, repoUrl) {
12748
- const repoHeader = repoUrl ? `X-Repo-URL = "${tomlEscape(repoUrl)}"
12749
- ` : "";
12750
- return `approval_policy = "never"
12776
+ function generateCodexConfigToml(apiUrl, _repoUrl) {
12777
+ return `${CODEX_APPROVAL_POLICY}
12751
12778
 
12752
12779
  [mcp_servers.${CODEX_SERVER_NAME}]
12753
- url = "${tomlEscape(mcpEndpoint(apiUrl))}"
12754
- bearer_token_env_var = "GREAT_ARROW_TOKEN"
12780
+ command = "npx"
12781
+ args = ["-y", "@great-arrow/cli", "mcp", "start"]
12782
+ env_vars = ["GREAT_ARROW_TOKEN"]
12755
12783
 
12756
- [mcp_servers.${CODEX_SERVER_NAME}.http_headers]
12757
- X-MCP-Client = "${CODEX_CLIENT_ID}"
12784
+ [mcp_servers.${CODEX_SERVER_NAME}.env]
12785
+ GAD_API_URL = "${tomlEscape(normalizeApiUrl(apiUrl))}"
12786
+ GAD_MCP_CLIENT = "${CODEX_CLIENT_ID}"
12758
12787
  ${GAD_INSTALL_HEADER} = "${GAD_INSTALL_VERSION}"
12759
- ${repoHeader}`;
12788
+ `;
12760
12789
  }
12761
12790
  function generateCodexEnv(token) {
12762
12791
  return `GREAT_ARROW_TOKEN=${token}
@@ -12768,8 +12797,10 @@ function extractCodexGreatArrowApiUrl(content) {
12768
12797
  );
12769
12798
  if (!text2) return null;
12770
12799
  const match = text2.match(/^\s*url\s*=\s*"([^"]+)"\s*$/m);
12771
- if (!match?.[1]) return null;
12772
- const url = match[1].trim();
12800
+ const dynamicMatch = text2.match(/^\s*GAD_API_URL\s*=\s*"([^"]+)"\s*$/m);
12801
+ const configured = match?.[1] ?? dynamicMatch?.[1];
12802
+ if (!configured) return null;
12803
+ const url = configured.trim();
12773
12804
  return url.endsWith("/api/mcp") ? url.slice(0, -"/api/mcp".length) : url.replace(/\/+$/, "");
12774
12805
  }
12775
12806
  function splitTomlSections(content) {
@@ -12818,14 +12849,20 @@ function removeServerSections(content, servers) {
12818
12849
  }).map((section) => section.text).join("").replace(/\n{3,}/g, "\n\n");
12819
12850
  }
12820
12851
  function upsertCodexServerBlock(existingContent, apiUrl, repoUrl) {
12821
- const block = generateCodexConfigToml(apiUrl, repoUrl).trimEnd();
12852
+ const block = generateCodexConfigToml(apiUrl, repoUrl).replace(/^approval_policy\s*=.*\n\n/, "").trimEnd();
12822
12853
  const withoutGreatArrow = removeServerSections(
12823
12854
  existingContent,
12824
12855
  /* @__PURE__ */ new Set([CODEX_SERVER_NAME])
12825
- ).replace(/\s*$/, "");
12826
- if (!withoutGreatArrow) return `${block}
12856
+ ).replace(/^\s*approval_policy\s*=.*(?:\n|$)/gm, "").replace(/^\s+/, "").replace(/\s*$/, "");
12857
+ if (!withoutGreatArrow) {
12858
+ return `${CODEX_APPROVAL_POLICY}
12859
+
12860
+ ${block}
12827
12861
  `;
12828
- return `${withoutGreatArrow}
12862
+ }
12863
+ return `${CODEX_APPROVAL_POLICY}
12864
+
12865
+ ${withoutGreatArrow}
12829
12866
 
12830
12867
  ${block}
12831
12868
  `;
@@ -12879,6 +12916,7 @@ function inspectCodexConfig(content, expectedRepoUrl) {
12879
12916
  const hasGreatArrowServer = grouped.has(CODEX_SERVER_NAME);
12880
12917
  const greatArrowInstallVersion = extractInstallVersion(greatArrowText);
12881
12918
  const greatArrowRepoKeys = extractConfiguredRepoKeys(greatArrowText);
12919
+ const greatArrowUsesDynamicRepo = /^\s*command\s*=\s*"npx"\s*$/m.test(greatArrowText) && /@great-arrow\/cli/.test(greatArrowText) && /"mcp"\s*,\s*"start"/.test(greatArrowText) && !/(?:MEMORY_REPO_URL|GAD_REPO_URL)\s*=/.test(greatArrowText);
12882
12920
  const greatArrowHasRepoHeader = Boolean(
12883
12921
  expectedRepoKey && greatArrowRepoKeys.includes(expectedRepoKey)
12884
12922
  );
@@ -12908,10 +12946,10 @@ function inspectCodexConfig(content, expectedRepoUrl) {
12908
12946
  );
12909
12947
  if (expectedRepoKey) {
12910
12948
  issues.push(
12911
- greatArrowHasRepoHeader ? {
12912
- code: "repo_header_present",
12949
+ greatArrowHasRepoHeader || greatArrowUsesDynamicRepo ? {
12950
+ code: greatArrowUsesDynamicRepo ? "repo_routing_dynamic" : "repo_header_present",
12913
12951
  level: "pass",
12914
- message: `Great Arrow is pinned to ${expectedRepoUrl}.`
12952
+ message: greatArrowUsesDynamicRepo ? "Great Arrow detects the current git origin for each Codex session." : `Great Arrow is pinned to ${expectedRepoUrl}.`
12915
12953
  } : {
12916
12954
  code: "repo_header_missing",
12917
12955
  level: "warn",
@@ -12977,6 +13015,7 @@ function inspectCodexConfig(content, expectedRepoUrl) {
12977
13015
  expectedRepoKey,
12978
13016
  hasGreatArrowServer,
12979
13017
  greatArrowHasRepoHeader,
13018
+ greatArrowUsesDynamicRepo,
12980
13019
  greatArrowInstallVersion,
12981
13020
  greatArrowInstallVersionCurrent: Boolean(greatArrowInstallVersion) && compareVersion(greatArrowInstallVersion, GAD_INSTALL_VERSION) >= 0,
12982
13021
  unmanagedServers,
@@ -13361,6 +13400,62 @@ function inspectHookConfig(content) {
13361
13400
  }
13362
13401
  return { parseError: false, url, token, issues };
13363
13402
  }
13403
+ function collectConfiguredHooks(scope, file, content) {
13404
+ let parsed;
13405
+ try {
13406
+ parsed = content.trim() ? JSON.parse(content) : {};
13407
+ } catch {
13408
+ return [];
13409
+ }
13410
+ const hooks = asObject(asObject(parsed)?.hooks ?? null);
13411
+ if (!hooks) return [];
13412
+ const out = [];
13413
+ for (const [event, groups] of Object.entries(hooks)) {
13414
+ if (!Array.isArray(groups)) continue;
13415
+ for (const group of groups) {
13416
+ if (!group || !Array.isArray(group.hooks)) continue;
13417
+ for (const hook of group.hooks) {
13418
+ if (hook && typeof hook.command === "string" && hook.command.trim()) {
13419
+ out.push({ scope, file, event, command: hook.command });
13420
+ }
13421
+ }
13422
+ }
13423
+ }
13424
+ return out;
13425
+ }
13426
+ function nodeScriptTarget(command) {
13427
+ const m = command.match(/\bnode\s+(?:"([^"]+)"|'([^']+)'|(\S+))/);
13428
+ return m ? m[1] ?? m[2] ?? m[3] ?? null : null;
13429
+ }
13430
+ function gadVerb(command) {
13431
+ return command.match(/(?:^|[;&|]\s*)gad\s+([a-z][a-z-]*)/)?.[1] ?? null;
13432
+ }
13433
+ function inspectConfiguredHooks(hooks, deps) {
13434
+ const issues = [];
13435
+ for (const hook of hooks) {
13436
+ const where = `${hook.event} hook in ${hook.file}`;
13437
+ const verb = gadVerb(hook.command);
13438
+ if (verb !== null && !deps.knownGadVerbs.has(verb)) {
13439
+ issues.push({
13440
+ code: "hook_unknown_gad_verb",
13441
+ level: "fail",
13442
+ message: `${where} runs \`gad ${verb}\`, which this CLI does not define \u2014 it fails on every ${hook.event}`,
13443
+ hint: `Remove that hook from ${hook.file}, or correct the verb (\`gad --help\` lists them).`
13444
+ });
13445
+ continue;
13446
+ }
13447
+ const script = nodeScriptTarget(hook.command);
13448
+ if (script && !script.includes("$") && !deps.fileExists(script)) {
13449
+ issues.push({
13450
+ code: "hook_script_missing",
13451
+ level: "fail",
13452
+ message: `${where} runs a script that does not exist: ${script}`,
13453
+ hint: `Re-run the installer to restore it, or remove the hook from ${hook.file}.`
13454
+ });
13455
+ }
13456
+ }
13457
+ return issues;
13458
+ }
13364
13459
 
13365
13460
  // src/lib/mcp-doctor.ts
13366
13461
  import { createHash as createHash2 } from "node:crypto";
@@ -14395,6 +14490,49 @@ function claudeCommand() {
14395
14490
  }
14396
14491
  }
14397
14492
  }
14493
+ {
14494
+ let root = command;
14495
+ while (root.parent) root = root.parent;
14496
+ const knownGadVerbs = /* @__PURE__ */ new Set();
14497
+ for (const sub of root.commands) {
14498
+ knownGadVerbs.add(sub.name());
14499
+ for (const alias of sub.aliases()) knownGadVerbs.add(alias);
14500
+ }
14501
+ const cwd = process.cwd();
14502
+ const scopes = [
14503
+ { scope: "managed", file: "/etc/claude-code/managed-settings.json" },
14504
+ {
14505
+ scope: "managed",
14506
+ file: "/Library/Application Support/ClaudeCode/managed-settings.json"
14507
+ },
14508
+ { scope: "user", file: settingsPath },
14509
+ { scope: "project", file: join5(cwd, ".claude", "settings.json") },
14510
+ { scope: "local", file: join5(cwd, ".claude", "settings.local.json") }
14511
+ ];
14512
+ const configured = [];
14513
+ for (const { scope, file } of scopes) {
14514
+ if (!existsSync3(file)) continue;
14515
+ configured.push(
14516
+ ...collectConfiguredHooks(
14517
+ scope,
14518
+ file,
14519
+ await readFile3(file, "utf8")
14520
+ )
14521
+ );
14522
+ }
14523
+ const hookIssues = inspectConfiguredHooks(configured, {
14524
+ knownGadVerbs,
14525
+ fileExists: existsSync3
14526
+ });
14527
+ toChecks("Configured hooks", hookIssues);
14528
+ if (configured.length > 0 && hookIssues.length === 0) {
14529
+ checks.push({
14530
+ name: "Configured hooks: all runnable",
14531
+ status: "pass",
14532
+ message: `${configured.length} command hook(s) across ${new Set(configured.map((h) => h.file)).size} settings file(s) resolve`
14533
+ });
14534
+ }
14535
+ }
14398
14536
  if (options.enforce) {
14399
14537
  const enforcement = {
14400
14538
  name: "Claude surface: enforcement",
@@ -14814,7 +14952,8 @@ async function resolveBridgeAuth(options = {}) {
14814
14952
  const explicit = [
14815
14953
  [options.tokenFlag, "flag"],
14816
14954
  [env4.GAD_API_TOKEN, "GAD_API_TOKEN"],
14817
- [env4.GAD_TOKEN, "GAD_TOKEN"]
14955
+ [env4.GAD_TOKEN, "GAD_TOKEN"],
14956
+ [env4.GREAT_ARROW_TOKEN, "GREAT_ARROW_TOKEN"]
14818
14957
  ];
14819
14958
  for (const [raw, source] of explicit) {
14820
14959
  const token = raw?.trim();
@@ -14859,12 +14998,15 @@ function buildBridgeHeaders(options) {
14859
14998
  accept: "application/json",
14860
14999
  authorization: `Bearer ${options.token}`,
14861
15000
  "user-agent": `gad-cli/${CLI_VERSION} gad-install/${GAD_INSTALL_VERSION}`,
14862
- "x-mcp-client": BRIDGE_CLIENT_ID,
15001
+ "x-mcp-client": options.clientId?.trim().slice(0, 64) || BRIDGE_CLIENT_ID,
14863
15002
  [GAD_CLI_VERSION_HEADER]: CLI_VERSION,
14864
15003
  [GAD_INSTALL_HEADER]: GAD_INSTALL_VERSION
14865
15004
  };
14866
15005
  if (options.repoUrl) headers["x-repo-url"] = options.repoUrl;
15006
+ if (options.workspaceId) headers["x-workspace-id"] = options.workspaceId;
14867
15007
  if (options.sessionId) headers["x-mcp-session-id"] = options.sessionId;
15008
+ if (options.deviceName) headers["x-device-name"] = options.deviceName;
15009
+ if (options.machineId) headers["x-machine-id"] = options.machineId;
14868
15010
  return headers;
14869
15011
  }
14870
15012
  function rpcError(id, code, message) {
@@ -14946,13 +15088,18 @@ async function runStdioBridge(options) {
14946
15088
  const timeoutMs = Number(env4.MEMORY_TIMEOUT_MS ?? DEFAULT_TIMEOUT_MS3);
14947
15089
  const sessionId = (env4.GAD_SESSION_ID ?? env4.MEMORY_SESSION_ID)?.trim() || `${BRIDGE_CLIENT_ID}:${Date.now()}-${randomUUID().slice(0, 8)}`;
14948
15090
  const repoUrl = (env4.GAD_REPO_URL ?? env4.MEMORY_REPO_URL)?.trim() || detectGitOrigin3();
15091
+ const workspaceId = (env4.GAD_WORKSPACE_ID ?? env4.GAD_WORKSPACE)?.trim() || getCommandWorkspaceTarget({ hasRepo: Boolean(repoUrl) });
14949
15092
  const headers = buildBridgeHeaders({
14950
15093
  token: options.auth.token,
14951
15094
  repoUrl,
14952
- sessionId
15095
+ workspaceId,
15096
+ sessionId,
15097
+ clientId: env4.GAD_MCP_CLIENT,
15098
+ deviceName: env4.GAD_DEVICE_NAME,
15099
+ machineId: env4.GAD_MACHINE_ID
14953
15100
  });
14954
15101
  writeErr(
14955
- `[gad mcp] Bridging stdio \u2194 ${endpoint} (auth: ${options.auth.source}${repoUrl ? `, repo: ${repoUrl}` : ""})`
15102
+ `[gad mcp] Bridging stdio \u2194 ${endpoint} (auth: ${options.auth.source}${workspaceId ? `, workspace: ${workspaceId}` : ""}${repoUrl ? `, repo: ${repoUrl}` : ""})`
14956
15103
  );
14957
15104
  const config = { endpoint, headers, timeoutMs };
14958
15105
  const rl = createInterface2({ input, crlfDelay: Infinity });
@@ -15444,8 +15591,9 @@ async function readIfExists(path10) {
15444
15591
  }
15445
15592
  async function readKeychainOnly() {
15446
15593
  try {
15447
- const keytar = await import("keytar");
15448
- return await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
15594
+ const { AsyncEntry } = await import("@napi-rs/keyring");
15595
+ const entry = new AsyncEntry(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
15596
+ return await entry.getPassword() ?? null;
15449
15597
  } catch {
15450
15598
  return null;
15451
15599
  }
@@ -15716,135 +15864,6 @@ ${c.dim("Credential migration")}
15716
15864
  var mcpCommand = new Command("mcp").description("Manage and diagnose the MCP server connection").addCommand(startCommand).addCommand(statusCommand).addCommand(logsCommand).addCommand(testCommand).addCommand(doctorSubcommand).addCommand(installCommand);
15717
15865
  var mcp_default = mcpCommand;
15718
15866
 
15719
- // src/commands/deploy.ts
15720
- var DEPLOY_TIMEOUT_MS = 9e4;
15721
- function deployCommand() {
15722
- const cmd = new Command("deploy").description("Trigger a production deployment").option("--confirm", "Skip interactive confirmation (required in CI)").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options, command) => {
15723
- const g = globalOutputOpts(command);
15724
- const fmt = createFormatter({
15725
- json: g.json,
15726
- quiet: g.quiet,
15727
- isTTY: isTTY(),
15728
- noColor: g.noColor
15729
- });
15730
- if (!options.confirm) {
15731
- if (!isTTY()) {
15732
- fmt.error(
15733
- "Cannot deploy without confirmation in non-interactive mode."
15734
- );
15735
- fmt.info("Pass --confirm to bypass interactive confirmation in CI.");
15736
- process.exit(3 /* VALIDATION_ERROR */);
15737
- }
15738
- const confirmed = await confirm(
15739
- "\u26A0\uFE0F This will deploy to production. Continue?"
15740
- );
15741
- if (!confirmed) {
15742
- fmt.info("Deploy cancelled.");
15743
- return;
15744
- }
15745
- }
15746
- const auth = await resolveAuth();
15747
- const client = new GadClient({
15748
- token: auth.token,
15749
- apiUrl: auth.apiUrl,
15750
- timeout: DEPLOY_TIMEOUT_MS
15751
- });
15752
- if (!options.quiet) {
15753
- fmt.info("Deploying to production...");
15754
- }
15755
- const result = await client.mcpCall("deploy_to_vercel", {});
15756
- if (g.json) {
15757
- process.stdout.write(JSON.stringify(result) + "\n");
15758
- return;
15759
- }
15760
- if (result.error) {
15761
- fmt.error(`Deployment failed: ${result.error}`);
15762
- process.exit(2 /* NETWORK_ERROR */);
15763
- }
15764
- if (result.url) {
15765
- fmt.success(`Deployment triggered successfully`);
15766
- fmt.info(`URL: ${result.url}`);
15767
- } else {
15768
- fmt.success("Deployment triggered successfully");
15769
- }
15770
- if (result.status) {
15771
- fmt.info(`Status: ${result.status}`);
15772
- }
15773
- if (result.buildId) {
15774
- fmt.info(`Build ID: ${result.buildId}`);
15775
- }
15776
- });
15777
- return cmd;
15778
- }
15779
-
15780
- // src/commands/migrate.ts
15781
- async function ensureConfirmation(hasConfirmFlag, tty2) {
15782
- if (hasConfirmFlag) {
15783
- return true;
15784
- }
15785
- if (!tty2) {
15786
- process.stderr.write(
15787
- "\u2717 --confirm flag is required in non-interactive environments.\n Use: gad migrate --confirm\n"
15788
- );
15789
- process.exit(3 /* VALIDATION_ERROR */);
15790
- }
15791
- const confirmed = await confirm(
15792
- "This will run pending database migrations. Continue?"
15793
- );
15794
- if (!confirmed) {
15795
- process.stderr.write("Aborted.\n");
15796
- }
15797
- return confirmed;
15798
- }
15799
- function displayResults(result, fmt) {
15800
- const applied = result?.applied ?? 0;
15801
- const migrations = result?.migrations;
15802
- if (applied === 0) {
15803
- fmt.success("No pending migrations. Database is up to date.");
15804
- return;
15805
- }
15806
- fmt.success(
15807
- `${applied} migration${applied === 1 ? "" : "s"} applied successfully.`
15808
- );
15809
- if (migrations && migrations.length > 0) {
15810
- fmt.table(migrations, [
15811
- { key: "name", header: "Migration", width: 40 },
15812
- { key: "status", header: "Status", width: 12 }
15813
- ]);
15814
- }
15815
- }
15816
- var migrateCommand = new Command("migrate").description("Run pending database migrations").option("--confirm", "Skip confirmation prompt (required in non-TTY)").option("--json", "Output results as JSON").option("--quiet", "Suppress decorative output").action(async (opts, command) => {
15817
- const g = globalOutputOpts(command);
15818
- const tty2 = isTTY();
15819
- const fmt = createFormatter({
15820
- json: g.json,
15821
- quiet: g.quiet,
15822
- isTTY: tty2,
15823
- noColor: !tty2
15824
- });
15825
- const confirmed = await ensureConfirmation(Boolean(opts.confirm), tty2);
15826
- if (!confirmed) {
15827
- return;
15828
- }
15829
- const auth = await resolveAuth();
15830
- const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
15831
- fmt.info("Running pending migrations\u2026");
15832
- const result = await client.mcpCall("apply_migration", {});
15833
- if (g.json) {
15834
- const applied = result?.applied ?? 0;
15835
- const status = result?.status ?? "complete";
15836
- fmt.table(
15837
- [{ applied, status }],
15838
- [
15839
- { key: "applied", header: "Applied" },
15840
- { key: "status", header: "Status" }
15841
- ]
15842
- );
15843
- } else {
15844
- displayResults(result, fmt);
15845
- }
15846
- });
15847
-
15848
15867
  // src/commands/smoke.ts
15849
15868
  var SMOKE_TESTS = [
15850
15869
  {
@@ -17350,10 +17369,12 @@ function renderFooter(payload, apiUrl) {
17350
17369
  lines.push(`${c.dim("\u2192")} ${c.cyan("gad status")} for the full picture.`);
17351
17370
  process.stderr.write("\n" + brandedBlock("needs you", lines) + "\n");
17352
17371
  }
17372
+ var GAD_LOGO = `<img src="https://www.greatarrow.ai/icon.svg" width="16" alt="Great Arrow" style="vertical-align:middle;display:inline-block;margin-right:4px;" />`;
17353
17373
  function brandedBlock(title, lines) {
17354
- const header = `\u{1F3F9} Great Arrow \xB7 ${title}`;
17355
- const rule = "\u2500".repeat(Math.min(60, header.length));
17356
- return [header, rule, ...lines].join("\n");
17374
+ const header = `${GAD_LOGO} **Great Arrow** \xB7 ${title}`;
17375
+ const visualLength = header.replace(GAD_LOGO, " ").length;
17376
+ const rule = "\u2500".repeat(Math.min(60, visualLength));
17377
+ return [header, rule, ...lines].join("\\n");
17357
17378
  }
17358
17379
 
17359
17380
  // src/commands/status.ts
@@ -17632,8 +17653,6 @@ var COMMANDS = [
17632
17653
  "doctor",
17633
17654
  "integrations",
17634
17655
  "mcp",
17635
- "deploy",
17636
- "migrate",
17637
17656
  "smoke",
17638
17657
  "agent",
17639
17658
  "ask",
@@ -17706,8 +17725,6 @@ _gad() {
17706
17725
  'doctor:Run health checks'
17707
17726
  'integrations:Manage integrations (list, sync)'
17708
17727
  'mcp:MCP server management (status, logs, test, install)'
17709
- 'deploy:Deploy to production'
17710
- 'migrate:Run database migrations'
17711
17728
  'smoke:Run smoke tests'
17712
17729
  'agent:Manage agents (list, invoke)'
17713
17730
  'ask:Ask the orchestrator a question'
@@ -17766,8 +17783,6 @@ function generateFish() {
17766
17783
  doctor: "Run health checks",
17767
17784
  integrations: "Manage integrations",
17768
17785
  mcp: "MCP server management",
17769
- deploy: "Deploy to production",
17770
- migrate: "Run database migrations",
17771
17786
  smoke: "Run smoke tests",
17772
17787
  agent: "Manage agents",
17773
17788
  ask: "Ask the orchestrator a question",
@@ -21243,7 +21258,18 @@ if (shouldRunUpdateNotifier(process.argv)) {
21243
21258
  pkg: { name: CLI_PACKAGE_NAME, version: CLI_VERSION }
21244
21259
  }).notify();
21245
21260
  }
21246
- var program2 = new Command().name("gad").description("Great Arrow Digital CLI \u2014 terminal access to the GAD platform").version(CLI_VERSION, "-v, --version").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").option("--no-glance", "Suppress the ambient proactive footer");
21261
+ var program2 = new Command().name("gad").description("Great Arrow Digital CLI \u2014 terminal access to the GAD platform").version(CLI_VERSION, "-v, --version").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").option("--no-glance", "Suppress the ambient proactive footer").option(
21262
+ "--workspace <id-or-slug-or-name>",
21263
+ "Target one accessible workspace for this command without changing the saved default"
21264
+ );
21265
+ program2.hook(
21266
+ "preAction",
21267
+ async (_thisCommand, actionCommand) => {
21268
+ const options = actionCommand.optsWithGlobals();
21269
+ const config = await loadConfig();
21270
+ setCommandWorkspaceTarget(options.workspace, config.default_workspace);
21271
+ }
21272
+ );
21247
21273
  void captureCliEvent({
21248
21274
  apiUrl: process.env.GAD_API_URL ?? "https://www.greatarrow.ai",
21249
21275
  name: "startup",
@@ -21268,8 +21294,6 @@ program2.addCommand(mcp_default);
21268
21294
  program2.addCommand(agentCommand);
21269
21295
  program2.addCommand(askCommand());
21270
21296
  program2.addCommand(localCommand());
21271
- program2.addCommand(deployCommand());
21272
- program2.addCommand(migrateCommand);
21273
21297
  program2.addCommand(smokeCommand());
21274
21298
  program2.addCommand(openCommand());
21275
21299
  program2.addCommand(completionsCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.25",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,11 +17,13 @@
17
17
  "typecheck": "tsc --noEmit"
18
18
  },
19
19
  "dependencies": {
20
+ "@napi-rs/keyring": "1.3.0",
20
21
  "commander": "^15.0.0",
21
- "update-notifier": "^7.3.1"
22
+ "update-notifier": "^7.3.1",
23
+ "nanoid": "^3.3.18"
22
24
  },
23
25
  "devDependencies": {
24
- "@types/node": "^26.1.1",
26
+ "@types/node": "^26.1.2",
25
27
  "esbuild": "^0.28.1",
26
28
  "fast-check": "^4.9.0",
27
29
  "typescript": "^7.0.2",
@@ -44,11 +46,8 @@
44
46
  "url": "https://www.greatarrow.ai/support"
45
47
  },
46
48
  "author": "GreatArrow.ai <support@greatarrow.ai>",
47
- "optionalDependencies": {
48
- "keytar": "^7.9.0"
49
- },
50
49
  "overrides": {
51
50
  "postcss": ">=8.5.23",
52
- "nanoid": "^3.3.17"
51
+ "nanoid": "^3.3.18"
53
52
  }
54
53
  }