@great-arrow/cli 0.1.23 → 0.1.26

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 +25 -1
  2. package/dist/index.js +115 -26
  3. package/package.json +5 -4
package/README.md CHANGED
@@ -153,7 +153,31 @@ gad mcp install gemini
153
153
  ```
154
154
 
155
155
  `gad mcp install codex` writes Codex's native `~/.codex/config.toml` server
156
- 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.
157
181
 
158
182
  #### Keeping local MCP installs current
159
183
 
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.23";
9402
+ var CLI_VERSION = "0.1.26";
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";
@@ -10508,6 +10508,17 @@ async function captureCliEvent(input) {
10508
10508
  }
10509
10509
  }
10510
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
+
10511
10522
  // src/lib/client.ts
10512
10523
  var DEFAULT_TIMEOUT_MS2 = 3e4;
10513
10524
  var MAX_RETRIES = 5;
@@ -10560,11 +10571,13 @@ var GadClient = class {
10560
10571
  token;
10561
10572
  apiUrl;
10562
10573
  repoUrl;
10574
+ workspaceId;
10563
10575
  timeout;
10564
10576
  constructor(options) {
10565
10577
  this.token = options.token;
10566
10578
  this.apiUrl = options.apiUrl.replace(/\/$/, "");
10567
10579
  this.repoUrl = options.repoUrl ?? detectGitOrigin();
10580
+ this.workspaceId = options.workspaceId?.trim() || getCommandWorkspaceTarget({ hasRepo: Boolean(this.repoUrl) }) || null;
10568
10581
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS2;
10569
10582
  }
10570
10583
  /**
@@ -10582,6 +10595,9 @@ var GadClient = class {
10582
10595
  if (this.repoUrl) {
10583
10596
  headers["X-Repo-URL"] = this.repoUrl;
10584
10597
  }
10598
+ if (this.workspaceId) {
10599
+ headers["X-Workspace-Id"] = this.workspaceId;
10600
+ }
10585
10601
  return headers;
10586
10602
  }
10587
10603
  /**
@@ -11137,11 +11153,22 @@ var switchCommand = new Command("switch").description("Set the active workspace
11137
11153
  noColor: false
11138
11154
  });
11139
11155
  const client = await createClient();
11140
- 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;
11141
11163
  const config = await loadConfig();
11142
- config.default_workspace = nameOrId;
11164
+ config.default_workspace = resolvedId;
11143
11165
  await saveConfig(config);
11144
- 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
+ );
11145
11172
  });
11146
11173
  var infoColumns = [
11147
11174
  { key: "name", header: "Name" },
@@ -12746,19 +12773,19 @@ function repoKey(repoUrl) {
12746
12773
  return null;
12747
12774
  }
12748
12775
  }
12749
- function generateCodexConfigToml(apiUrl, repoUrl) {
12750
- const repoHeader = repoUrl ? `X-Repo-URL = "${tomlEscape(repoUrl)}"
12751
- ` : "";
12776
+ function generateCodexConfigToml(apiUrl, _repoUrl) {
12752
12777
  return `${CODEX_APPROVAL_POLICY}
12753
12778
 
12754
12779
  [mcp_servers.${CODEX_SERVER_NAME}]
12755
- url = "${tomlEscape(mcpEndpoint(apiUrl))}"
12756
- bearer_token_env_var = "GREAT_ARROW_TOKEN"
12780
+ command = "npx"
12781
+ args = ["-y", "@great-arrow/cli", "mcp", "start"]
12782
+ env_vars = ["GREAT_ARROW_TOKEN"]
12757
12783
 
12758
- [mcp_servers.${CODEX_SERVER_NAME}.http_headers]
12759
- 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}"
12760
12787
  ${GAD_INSTALL_HEADER} = "${GAD_INSTALL_VERSION}"
12761
- ${repoHeader}`;
12788
+ `;
12762
12789
  }
12763
12790
  function generateCodexEnv(token) {
12764
12791
  return `GREAT_ARROW_TOKEN=${token}
@@ -12770,8 +12797,10 @@ function extractCodexGreatArrowApiUrl(content) {
12770
12797
  );
12771
12798
  if (!text2) return null;
12772
12799
  const match = text2.match(/^\s*url\s*=\s*"([^"]+)"\s*$/m);
12773
- if (!match?.[1]) return null;
12774
- 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();
12775
12804
  return url.endsWith("/api/mcp") ? url.slice(0, -"/api/mcp".length) : url.replace(/\/+$/, "");
12776
12805
  }
12777
12806
  function splitTomlSections(content) {
@@ -12887,6 +12916,7 @@ function inspectCodexConfig(content, expectedRepoUrl) {
12887
12916
  const hasGreatArrowServer = grouped.has(CODEX_SERVER_NAME);
12888
12917
  const greatArrowInstallVersion = extractInstallVersion(greatArrowText);
12889
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);
12890
12920
  const greatArrowHasRepoHeader = Boolean(
12891
12921
  expectedRepoKey && greatArrowRepoKeys.includes(expectedRepoKey)
12892
12922
  );
@@ -12916,10 +12946,10 @@ function inspectCodexConfig(content, expectedRepoUrl) {
12916
12946
  );
12917
12947
  if (expectedRepoKey) {
12918
12948
  issues.push(
12919
- greatArrowHasRepoHeader ? {
12920
- code: "repo_header_present",
12949
+ greatArrowHasRepoHeader || greatArrowUsesDynamicRepo ? {
12950
+ code: greatArrowUsesDynamicRepo ? "repo_routing_dynamic" : "repo_header_present",
12921
12951
  level: "pass",
12922
- 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}.`
12923
12953
  } : {
12924
12954
  code: "repo_header_missing",
12925
12955
  level: "warn",
@@ -12985,6 +13015,7 @@ function inspectCodexConfig(content, expectedRepoUrl) {
12985
13015
  expectedRepoKey,
12986
13016
  hasGreatArrowServer,
12987
13017
  greatArrowHasRepoHeader,
13018
+ greatArrowUsesDynamicRepo,
12988
13019
  greatArrowInstallVersion,
12989
13020
  greatArrowInstallVersionCurrent: Boolean(greatArrowInstallVersion) && compareVersion(greatArrowInstallVersion, GAD_INSTALL_VERSION) >= 0,
12990
13021
  unmanagedServers,
@@ -14921,7 +14952,8 @@ async function resolveBridgeAuth(options = {}) {
14921
14952
  const explicit = [
14922
14953
  [options.tokenFlag, "flag"],
14923
14954
  [env4.GAD_API_TOKEN, "GAD_API_TOKEN"],
14924
- [env4.GAD_TOKEN, "GAD_TOKEN"]
14955
+ [env4.GAD_TOKEN, "GAD_TOKEN"],
14956
+ [env4.GREAT_ARROW_TOKEN, "GREAT_ARROW_TOKEN"]
14925
14957
  ];
14926
14958
  for (const [raw, source] of explicit) {
14927
14959
  const token = raw?.trim();
@@ -14966,12 +14998,15 @@ function buildBridgeHeaders(options) {
14966
14998
  accept: "application/json",
14967
14999
  authorization: `Bearer ${options.token}`,
14968
15000
  "user-agent": `gad-cli/${CLI_VERSION} gad-install/${GAD_INSTALL_VERSION}`,
14969
- "x-mcp-client": BRIDGE_CLIENT_ID,
15001
+ "x-mcp-client": options.clientId?.trim().slice(0, 64) || BRIDGE_CLIENT_ID,
14970
15002
  [GAD_CLI_VERSION_HEADER]: CLI_VERSION,
14971
15003
  [GAD_INSTALL_HEADER]: GAD_INSTALL_VERSION
14972
15004
  };
14973
15005
  if (options.repoUrl) headers["x-repo-url"] = options.repoUrl;
15006
+ if (options.workspaceId) headers["x-workspace-id"] = options.workspaceId;
14974
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;
14975
15010
  return headers;
14976
15011
  }
14977
15012
  function rpcError(id, code, message) {
@@ -15053,13 +15088,18 @@ async function runStdioBridge(options) {
15053
15088
  const timeoutMs = Number(env4.MEMORY_TIMEOUT_MS ?? DEFAULT_TIMEOUT_MS3);
15054
15089
  const sessionId = (env4.GAD_SESSION_ID ?? env4.MEMORY_SESSION_ID)?.trim() || `${BRIDGE_CLIENT_ID}:${Date.now()}-${randomUUID().slice(0, 8)}`;
15055
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) });
15056
15092
  const headers = buildBridgeHeaders({
15057
15093
  token: options.auth.token,
15058
15094
  repoUrl,
15059
- sessionId
15095
+ workspaceId,
15096
+ sessionId,
15097
+ clientId: env4.GAD_MCP_CLIENT,
15098
+ deviceName: env4.GAD_DEVICE_NAME,
15099
+ machineId: env4.GAD_MACHINE_ID
15060
15100
  });
15061
15101
  writeErr(
15062
- `[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}` : ""})`
15063
15103
  );
15064
15104
  const config = { endpoint, headers, timeoutMs };
15065
15105
  const rl = createInterface2({ input, crlfDelay: Infinity });
@@ -17247,12 +17287,25 @@ function normalizeGlance(raw) {
17247
17287
  };
17248
17288
  }
17249
17289
  }
17290
+ let warRoom = null;
17291
+ if (typeof obj.warRoom === "object" && obj.warRoom !== null) {
17292
+ const w = obj.warRoom;
17293
+ warRoom = {
17294
+ priorityCount: typeof w.priorityCount === "number" ? w.priorityCount : 0,
17295
+ unreadTotal: typeof w.unreadTotal === "number" ? w.unreadTotal : 0,
17296
+ openRequestCount: typeof w.openRequestCount === "number" ? w.openRequestCount : 0,
17297
+ topSender: typeof w.topSender === "string" ? w.topSender : null,
17298
+ newestSender: typeof w.newestSender === "string" ? w.newestSender : null,
17299
+ newestPreview: typeof w.newestPreview === "string" ? w.newestPreview : null
17300
+ };
17301
+ }
17250
17302
  return {
17251
17303
  workspace: typeof obj.workspace === "string" && obj.workspace !== "" ? obj.workspace : null,
17252
17304
  needsYou,
17253
17305
  topGoal,
17254
17306
  integrations,
17255
17307
  agents,
17308
+ warRoom,
17256
17309
  urgency: typeof obj.urgency === "number" ? obj.urgency : null,
17257
17310
  generatedAt: typeof obj.generatedAt === "string" ? obj.generatedAt : (/* @__PURE__ */ new Date()).toISOString()
17258
17311
  };
@@ -17261,6 +17314,9 @@ function asArray2(value) {
17261
17314
  return Array.isArray(value) ? value : [];
17262
17315
  }
17263
17316
  function topSignature(payload) {
17317
+ if (payload.warRoom && payload.warRoom.priorityCount > 0) {
17318
+ return `warroom:${payload.warRoom.newestSender ?? "peer"}`;
17319
+ }
17264
17320
  const top = payload.needsYou[0];
17265
17321
  if (top) return `needs:${top.kind}:${top.title}`;
17266
17322
  const integration = payload.integrations[0];
@@ -17306,6 +17362,12 @@ function glanceItemUrl(href, apiUrl) {
17306
17362
  function renderFooter(payload, apiUrl) {
17307
17363
  const c = createColor(false);
17308
17364
  const lines = [];
17365
+ const wr = payload.warRoom;
17366
+ if (wr && wr.priorityCount > 0) {
17367
+ lines.push(
17368
+ `${c.yellow("!")} ${wr.priorityCount} war-room message${wr.priorityCount === 1 ? "" : "s"} for you${wr.newestSender ? ` \u2014 ${wr.newestSender}` : ""}`
17369
+ );
17370
+ }
17309
17371
  for (const item of payload.needsYou.slice(0, 2)) {
17310
17372
  lines.push(`${c.yellow("!")} ${item.title}`);
17311
17373
  if (item.action) {
@@ -17329,10 +17391,12 @@ function renderFooter(payload, apiUrl) {
17329
17391
  lines.push(`${c.dim("\u2192")} ${c.cyan("gad status")} for the full picture.`);
17330
17392
  process.stderr.write("\n" + brandedBlock("needs you", lines) + "\n");
17331
17393
  }
17394
+ 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;" />`;
17332
17395
  function brandedBlock(title, lines) {
17333
- const header = `\u{1F3F9} Great Arrow \xB7 ${title}`;
17334
- const rule = "\u2500".repeat(Math.min(60, header.length));
17335
- return [header, rule, ...lines].join("\n");
17396
+ const header = `${GAD_LOGO} **Great Arrow** \xB7 ${title}`;
17397
+ const visualLength = header.replace(GAD_LOGO, " ").length;
17398
+ const rule = "\u2500".repeat(Math.min(60, visualLength));
17399
+ return [header, rule, ...lines].join("\\n");
17336
17400
  }
17337
17401
 
17338
17402
  // src/commands/status.ts
@@ -17383,7 +17447,7 @@ function renderStatus(payload, plain, apiUrl) {
17383
17447
  lines.push(c.badge(` ${payload.workspace} `));
17384
17448
  lines.push("");
17385
17449
  }
17386
- const hasAny = payload.needsYou.length > 0 || payload.topGoal !== null || payload.integrations.length > 0 || payload.agents.length > 0;
17450
+ const hasAny = payload.needsYou.length > 0 || payload.topGoal !== null || payload.integrations.length > 0 || payload.agents.length > 0 || payload.warRoom !== null && payload.warRoom.priorityCount > 0;
17387
17451
  if (!hasAny) {
17388
17452
  lines.push(
17389
17453
  `${c.green("\u2713")} You're all clear \u2014 nothing needs you right now.`
@@ -17391,6 +17455,20 @@ function renderStatus(payload, plain, apiUrl) {
17391
17455
  process.stdout.write(brandedBlock("status", lines) + "\n");
17392
17456
  return;
17393
17457
  }
17458
+ if (payload.warRoom && payload.warRoom.priorityCount > 0) {
17459
+ const wr = payload.warRoom;
17460
+ lines.push(c.bold("War room"));
17461
+ lines.push(
17462
+ ` ${c.yellow("!")} ${wr.priorityCount} message${wr.priorityCount === 1 ? "" : "s"} for you${wr.newestSender ? ` \u2014 ${wr.newestSender}` : ""}`
17463
+ );
17464
+ if (wr.newestPreview) {
17465
+ lines.push(` ${c.dim(wr.newestPreview)}`);
17466
+ }
17467
+ lines.push(
17468
+ ` ${c.dim("\u2192")} ${c.cyan(glanceItemUrl("/team?tab=war-room", apiUrl) ?? "")}`
17469
+ );
17470
+ lines.push("");
17471
+ }
17394
17472
  if (payload.needsYou.length > 0) {
17395
17473
  lines.push(c.bold("Needs you"));
17396
17474
  for (const item of payload.needsYou) {
@@ -21216,7 +21294,18 @@ if (shouldRunUpdateNotifier(process.argv)) {
21216
21294
  pkg: { name: CLI_PACKAGE_NAME, version: CLI_VERSION }
21217
21295
  }).notify();
21218
21296
  }
21219
- 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");
21297
+ 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(
21298
+ "--workspace <id-or-slug-or-name>",
21299
+ "Target one accessible workspace for this command without changing the saved default"
21300
+ );
21301
+ program2.hook(
21302
+ "preAction",
21303
+ async (_thisCommand, actionCommand) => {
21304
+ const options = actionCommand.optsWithGlobals();
21305
+ const config = await loadConfig();
21306
+ setCommandWorkspaceTarget(options.workspace, config.default_workspace);
21307
+ }
21308
+ );
21220
21309
  void captureCliEvent({
21221
21310
  apiUrl: process.env.GAD_API_URL ?? "https://www.greatarrow.ai",
21222
21311
  name: "startup",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.1.23",
3
+ "version": "0.1.26",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,10 +19,11 @@
19
19
  "dependencies": {
20
20
  "@napi-rs/keyring": "1.3.0",
21
21
  "commander": "^15.0.0",
22
- "update-notifier": "^7.3.1"
22
+ "update-notifier": "^7.3.1",
23
+ "nanoid": "^3.3.18"
23
24
  },
24
25
  "devDependencies": {
25
- "@types/node": "^26.1.1",
26
+ "@types/node": "^26.1.2",
26
27
  "esbuild": "^0.28.1",
27
28
  "fast-check": "^4.9.0",
28
29
  "typescript": "^7.0.2",
@@ -47,6 +48,6 @@
47
48
  "author": "GreatArrow.ai <support@greatarrow.ai>",
48
49
  "overrides": {
49
50
  "postcss": ">=8.5.23",
50
- "nanoid": "^3.3.17"
51
+ "nanoid": "^3.3.18"
51
52
  }
52
53
  }