@great-arrow/cli 0.1.2 → 0.1.4

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 +661 -238
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14,11 +14,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
14
14
  throw Error('Dynamic require of "' + x + '" is not supported');
15
15
  });
16
16
  var __commonJS = (cb, mod) => function __require2() {
17
- try {
18
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
- } catch (e) {
20
- throw mod = 0, e;
21
- }
17
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
22
18
  };
23
19
  var __copyProps = (to, from, except, desc) => {
24
20
  if (from && typeof from === "object" || typeof from === "function") {
@@ -37,9 +33,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
37
33
  mod
38
34
  ));
39
35
 
40
- // node_modules/commander/lib/error.js
36
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/error.js
41
37
  var require_error = __commonJS({
42
- "node_modules/commander/lib/error.js"(exports) {
38
+ "node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/error.js"(exports) {
43
39
  var CommanderError2 = class extends Error {
44
40
  /**
45
41
  * Constructs the CommanderError class
@@ -72,9 +68,9 @@ var require_error = __commonJS({
72
68
  }
73
69
  });
74
70
 
75
- // node_modules/commander/lib/argument.js
71
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/argument.js
76
72
  var require_argument = __commonJS({
77
- "node_modules/commander/lib/argument.js"(exports) {
73
+ "node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/argument.js"(exports) {
78
74
  var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
79
75
  var Argument2 = class {
80
76
  /**
@@ -199,9 +195,9 @@ var require_argument = __commonJS({
199
195
  }
200
196
  });
201
197
 
202
- // node_modules/commander/lib/help.js
198
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/help.js
203
199
  var require_help = __commonJS({
204
- "node_modules/commander/lib/help.js"(exports) {
200
+ "node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/help.js"(exports) {
205
201
  var { humanReadableArgName } = require_argument();
206
202
  var Help2 = class {
207
203
  constructor() {
@@ -768,9 +764,9 @@ ${itemIndentStr}`);
768
764
  }
769
765
  });
770
766
 
771
- // node_modules/commander/lib/option.js
767
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/option.js
772
768
  var require_option = __commonJS({
773
- "node_modules/commander/lib/option.js"(exports) {
769
+ "node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/option.js"(exports) {
774
770
  var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
775
771
  var Option2 = class {
776
772
  /**
@@ -1069,9 +1065,9 @@ var require_option = __commonJS({
1069
1065
  }
1070
1066
  });
1071
1067
 
1072
- // node_modules/commander/lib/suggestSimilar.js
1068
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
1073
1069
  var require_suggestSimilar = __commonJS({
1074
- "node_modules/commander/lib/suggestSimilar.js"(exports) {
1070
+ "node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js"(exports) {
1075
1071
  var maxDistance = 3;
1076
1072
  function editDistance(a, b) {
1077
1073
  if (Math.abs(a.length - b.length) > maxDistance)
@@ -1149,9 +1145,9 @@ var require_suggestSimilar = __commonJS({
1149
1145
  }
1150
1146
  });
1151
1147
 
1152
- // node_modules/commander/lib/command.js
1148
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/command.js
1153
1149
  var require_command = __commonJS({
1154
- "node_modules/commander/lib/command.js"(exports) {
1150
+ "node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/command.js"(exports) {
1155
1151
  var EventEmitter = __require("node:events").EventEmitter;
1156
1152
  var childProcess = __require("node:child_process");
1157
1153
  var path = __require("node:path");
@@ -3308,9 +3304,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3308
3304
  }
3309
3305
  });
3310
3306
 
3311
- // node_modules/commander/index.js
3307
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/index.js
3312
3308
  var require_commander = __commonJS({
3313
- "node_modules/commander/index.js"(exports) {
3309
+ "node_modules/.pnpm/commander@13.1.0/node_modules/commander/index.js"(exports) {
3314
3310
  var { Argument: Argument2 } = require_argument();
3315
3311
  var { Command: Command2 } = require_command();
3316
3312
  var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
@@ -3330,7 +3326,7 @@ var require_commander = __commonJS({
3330
3326
  }
3331
3327
  });
3332
3328
 
3333
- // node_modules/commander/esm.mjs
3329
+ // node_modules/.pnpm/commander@13.1.0/node_modules/commander/esm.mjs
3334
3330
  var import_index = __toESM(require_commander(), 1);
3335
3331
  var {
3336
3332
  program,
@@ -3862,6 +3858,61 @@ async function resolveToken(configToken) {
3862
3858
 
3863
3859
  // src/lib/client.ts
3864
3860
  import { execSync } from "node:child_process";
3861
+
3862
+ // src/lib/mcp-payload.ts
3863
+ function isTextBlock(value) {
3864
+ return typeof value === "object" && value !== null && value.type === "text" && typeof value.text === "string";
3865
+ }
3866
+ function extractJsonSlice(text) {
3867
+ const start = text.search(/[{[]/);
3868
+ if (start === -1) return null;
3869
+ let depth = 0;
3870
+ let inString = false;
3871
+ let escaped = false;
3872
+ for (let i = start; i < text.length; i++) {
3873
+ const ch = text[i];
3874
+ if (escaped) {
3875
+ escaped = false;
3876
+ continue;
3877
+ }
3878
+ if (inString) {
3879
+ if (ch === "\\") escaped = true;
3880
+ else if (ch === '"') inString = false;
3881
+ continue;
3882
+ }
3883
+ if (ch === '"') inString = true;
3884
+ else if (ch === "{" || ch === "[") depth++;
3885
+ else if (ch === "}" || ch === "]") {
3886
+ depth--;
3887
+ if (depth === 0) return text.slice(start, i + 1);
3888
+ }
3889
+ }
3890
+ return null;
3891
+ }
3892
+ function parseMcpPayload(result) {
3893
+ let text = null;
3894
+ if (typeof result === "string") {
3895
+ text = result;
3896
+ } else if (Array.isArray(result)) {
3897
+ const block = result.find(isTextBlock);
3898
+ if (block) text = block.text;
3899
+ }
3900
+ if (text === null) return result;
3901
+ try {
3902
+ return JSON.parse(text);
3903
+ } catch {
3904
+ }
3905
+ const slice = extractJsonSlice(text);
3906
+ if (slice !== null) {
3907
+ try {
3908
+ return JSON.parse(slice);
3909
+ } catch {
3910
+ }
3911
+ }
3912
+ return text;
3913
+ }
3914
+
3915
+ // src/lib/client.ts
3865
3916
  var DEFAULT_TIMEOUT_MS = 3e4;
3866
3917
  var MAX_RETRIES = 3;
3867
3918
  var BASE_DELAY_MS = 1e3;
@@ -3995,7 +4046,7 @@ var GadClient = class {
3995
4046
  * @param toolName - The MCP tool name to invoke
3996
4047
  * @param args - Arguments to pass to the tool
3997
4048
  * @param timeoutMs - Optional timeout override (e.g., 90s for agent calls)
3998
- * @returns The tool result content
4049
+ * @returns The decoded JSON payload from the tool result content
3999
4050
  */
4000
4051
  async mcpCall(toolName, args, timeoutMs) {
4001
4052
  const url = `${this.apiUrl}/api/mcp`;
@@ -4035,7 +4086,15 @@ var GadClient = class {
4035
4086
  data.error.data ?? void 0
4036
4087
  );
4037
4088
  }
4038
- return data.result?.content ?? data.result;
4089
+ const payload = parseMcpPayload(data.result?.content ?? data.result);
4090
+ if (typeof payload === "string" && payload.includes("[truncated: result exceeded")) {
4091
+ throw new CliError(
4092
+ `Tool '${toolName}' response was truncated by the server before the data completed.`,
4093
+ 3 /* VALIDATION_ERROR */,
4094
+ "Narrow the request (lower --limit / -k, or add filters) and try again."
4095
+ );
4096
+ }
4097
+ return payload;
4039
4098
  }
4040
4099
  /**
4041
4100
  * Make a GET request to a REST API endpoint.
@@ -4094,6 +4153,18 @@ var GadClient = class {
4094
4153
  }
4095
4154
  };
4096
4155
 
4156
+ // src/lib/global-opts.ts
4157
+ function globalOutputOpts(cmd) {
4158
+ const merged = cmd.optsWithGlobals();
4159
+ return {
4160
+ json: Boolean(merged.json),
4161
+ quiet: Boolean(merged.quiet),
4162
+ // --no-color sets `color: false`; some commands declare `--no-color`
4163
+ // locally which surfaces as `noColor`.
4164
+ noColor: merged.color === false || Boolean(merged.noColor)
4165
+ };
4166
+ }
4167
+
4097
4168
  // src/commands/whoami.ts
4098
4169
  function maskToken2(token) {
4099
4170
  if (token.length <= 8) {
@@ -4108,7 +4179,8 @@ function extractWorkspaceName(data) {
4108
4179
  return data.workspace?.name || data.workspaceName || "unknown";
4109
4180
  }
4110
4181
  function whoamiCommand() {
4111
- const cmd = new Command("whoami").description("Display authenticated user info").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options) => {
4182
+ const cmd = new Command("whoami").description("Display authenticated user info").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options, command) => {
4183
+ const g = globalOutputOpts(command);
4112
4184
  const auth = await resolveAuth();
4113
4185
  const client = new GadClient({
4114
4186
  token: auth.token,
@@ -4131,10 +4203,10 @@ function whoamiCommand() {
4131
4203
  const workspaceName = extractWorkspaceName(introspect);
4132
4204
  const tokenPrefix = maskToken2(auth.token);
4133
4205
  const fmt = createFormatter({
4134
- json: options.json ?? false,
4135
- quiet: options.quiet ?? false,
4206
+ json: g.json,
4207
+ quiet: g.quiet,
4136
4208
  isTTY: isTTY(),
4137
- noColor: options.color === false
4209
+ noColor: g.noColor
4138
4210
  });
4139
4211
  const data = {
4140
4212
  email,
@@ -4225,7 +4297,7 @@ var memoryCommand = new Command("memory").description(
4225
4297
  memoryCommand.command("create").description("Create a new memory in the active workspace").requiredOption("--title <title>", "Memory title").option("--content <content>", "Memory content (or pipe via stdin)").option("--tags <tags>", "Comma-separated tags").option(
4226
4298
  "--layer <layer>",
4227
4299
  "Memory layer (WORKING, EPISODIC, SEMANTIC, PROCEDURAL)"
4228
- ).action(async (opts) => {
4300
+ ).action(async (opts, command) => {
4229
4301
  const client = await buildClient();
4230
4302
  let content = opts.content;
4231
4303
  if (!content) {
@@ -4262,7 +4334,8 @@ memoryCommand.command("create").description("Create a new memory in the active w
4262
4334
  const memoryId = typeof rawId === "string" ? rawId : "created";
4263
4335
  fmt.success(`Memory created: ${memoryId}`);
4264
4336
  });
4265
- memoryCommand.command("search <query>").description("Semantic search across memories").option("--json", "Output as JSON").option("-k, --limit <number>", "Max results", "10").option("--layer <layer>", "Filter by layer").action(async (query, opts) => {
4337
+ memoryCommand.command("search <query>").description("Semantic search across memories").option("--json", "Output as JSON").option("-k, --limit <number>", "Max results", "10").option("--layer <layer>", "Filter by layer").action(async (query, opts, command) => {
4338
+ const g = globalOutputOpts(command);
4266
4339
  const client = await buildClient();
4267
4340
  const args = {
4268
4341
  query,
@@ -4272,22 +4345,27 @@ memoryCommand.command("search <query>").description("Semantic search across memo
4272
4345
  args.layer = opts.layer;
4273
4346
  }
4274
4347
  const result = await client.mcpCall("memory_search", args);
4275
- const rawResults = Array.isArray(result) ? result : result?.results ?? [];
4348
+ const data = result ?? {};
4349
+ const rawResults = Array.isArray(result) ? result : data.hits ?? data.results ?? [];
4276
4350
  const rows = rawResults.map((item) => ({
4277
4351
  ...item,
4352
+ title: item.title ?? item.memoryTitle,
4278
4353
  preview: extractPreview(item)
4279
4354
  }));
4280
4355
  const fmt = createFormatter({
4281
- json: Boolean(opts.json),
4356
+ json: g.json,
4282
4357
  quiet: false,
4283
4358
  isTTY: isTTY(),
4284
4359
  noColor: false
4285
4360
  });
4286
4361
  fmt.table(rows, searchColumns);
4287
4362
  });
4288
- memoryCommand.command("list").description("List recent memories with optional filters").option("--json", "Output as JSON").option("--layer <layer>", "Filter by memory layer").option("--tag <tag>", "Filter by tag").action(async (opts) => {
4363
+ memoryCommand.command("list").description("List recent memories with optional filters").option("--json", "Output as JSON").option("--limit <number>", "Max results", "20").option("--layer <layer>", "Filter by memory layer").option("--tag <tag>", "Filter by tag").action(async (opts, command) => {
4364
+ const g = globalOutputOpts(command);
4289
4365
  const client = await buildClient();
4290
- const args = {};
4366
+ const args = {
4367
+ limit: Number.parseInt(opts.limit, 10)
4368
+ };
4291
4369
  if (opts.layer) {
4292
4370
  args.layer = opts.layer;
4293
4371
  }
@@ -4295,21 +4373,23 @@ memoryCommand.command("list").description("List recent memories with optional fi
4295
4373
  args.tag = opts.tag;
4296
4374
  }
4297
4375
  const result = await client.mcpCall("memory_list", args);
4298
- const rawResults = Array.isArray(result) ? result : result?.results ?? [];
4376
+ const data = result ?? {};
4377
+ const rawResults = Array.isArray(result) ? result : data.memories ?? data.results ?? [];
4299
4378
  const fmt = createFormatter({
4300
- json: Boolean(opts.json),
4379
+ json: g.json,
4301
4380
  quiet: false,
4302
4381
  isTTY: isTTY(),
4303
4382
  noColor: false
4304
4383
  });
4305
4384
  fmt.table(rawResults, listColumns);
4306
4385
  });
4307
- memoryCommand.command("get <id>").description("Show full content and metadata of a memory").option("--json", "Output as JSON").action(async (id, opts) => {
4386
+ memoryCommand.command("get <id>").description("Show full content and metadata of a memory").option("--json", "Output as JSON").action(async (id, opts, command) => {
4387
+ const g = globalOutputOpts(command);
4308
4388
  const client = await buildClient();
4309
4389
  const result = await client.mcpCall("memory_get", { id });
4310
4390
  const data = result ?? {};
4311
4391
  const fmt = createFormatter({
4312
- json: Boolean(opts.json),
4392
+ json: g.json,
4313
4393
  quiet: false,
4314
4394
  isTTY: isTTY(),
4315
4395
  noColor: false
@@ -4380,22 +4460,10 @@ async function createClient() {
4380
4460
  const auth = await resolveAuth();
4381
4461
  return new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
4382
4462
  }
4383
- function extractMcpContent(result) {
4384
- if (Array.isArray(result) && result.length > 0) {
4385
- const first = result[0];
4386
- if (typeof first === "object" && first !== null && "type" in first && first.type === "text" && "text" in first) {
4387
- try {
4388
- return JSON.parse(first.text);
4389
- } catch {
4390
- return first.text;
4391
- }
4392
- }
4393
- }
4394
- return result;
4395
- }
4396
4463
  var listCommand = new Command("list").description("List all accessible workspaces").option("--json", "Output as JSON").action(async (options) => {
4397
- const parentOpts = listCommand.parent?.opts() ?? {};
4398
- const jsonFlag = options.json || parentOpts.json || false;
4464
+ const jsonFlag = Boolean(
4465
+ options.json || listCommand.optsWithGlobals().json
4466
+ );
4399
4467
  const fmt = createFormatter({
4400
4468
  json: jsonFlag,
4401
4469
  quiet: false,
@@ -4404,7 +4472,7 @@ var listCommand = new Command("list").description("List all accessible workspace
4404
4472
  });
4405
4473
  const client = await createClient();
4406
4474
  const result = await client.mcpCall("workspace_list", {});
4407
- const data = extractMcpContent(result);
4475
+ const data = result;
4408
4476
  let workspaces = [];
4409
4477
  if (Array.isArray(data)) {
4410
4478
  workspaces = data;
@@ -4466,8 +4534,9 @@ var infoColumns = [
4466
4534
  }
4467
4535
  ];
4468
4536
  var infoCommand = new Command("info").description("Show active workspace settings, member count, and integrations").option("--json", "Output as JSON").action(async (options) => {
4469
- const parentOpts = infoCommand.parent?.opts() ?? {};
4470
- const jsonFlag = options.json || parentOpts.json || false;
4537
+ const jsonFlag = Boolean(
4538
+ options.json || infoCommand.optsWithGlobals().json
4539
+ );
4471
4540
  const fmt = createFormatter({
4472
4541
  json: jsonFlag,
4473
4542
  quiet: false,
@@ -4476,13 +4545,14 @@ var infoCommand = new Command("info").description("Show active workspace setting
4476
4545
  });
4477
4546
  const client = await createClient();
4478
4547
  const result = await client.mcpCall("workspace_settings_get", {});
4479
- const data = extractMcpContent(result);
4548
+ const data = result;
4480
4549
  const workspace = typeof data === "object" && data !== null ? data : {};
4481
4550
  fmt.detail(workspace, infoColumns);
4482
4551
  });
4483
4552
  var membersCommand = new Command("members").description("List workspace members with roles and last-active timestamps").option("--json", "Output as JSON").action(async (options) => {
4484
- const parentOpts = membersCommand.parent?.opts() ?? {};
4485
- const jsonFlag = options.json || parentOpts.json || false;
4553
+ const jsonFlag = Boolean(
4554
+ options.json || membersCommand.optsWithGlobals().json
4555
+ );
4486
4556
  const fmt = createFormatter({
4487
4557
  json: jsonFlag,
4488
4558
  quiet: false,
@@ -4491,7 +4561,7 @@ var membersCommand = new Command("members").description("List workspace members
4491
4561
  });
4492
4562
  const client = await createClient();
4493
4563
  const result = await client.mcpCall("workspace_members", {});
4494
- const data = extractMcpContent(result);
4564
+ const data = result;
4495
4565
  let members = [];
4496
4566
  if (Array.isArray(data)) {
4497
4567
  members = data;
@@ -4544,7 +4614,7 @@ var listCommand2 = new Command("list").description("List open todos sorted by pr
4544
4614
  "Filter by status (open, in_progress, review, done, dismissed)"
4545
4615
  ).option("--json", "Output as JSON").action(
4546
4616
  async (options, cmd) => {
4547
- const parentOpts = cmd.parent?.opts() ?? {};
4617
+ const parentOpts = cmd.optsWithGlobals();
4548
4618
  const jsonOutput = options.json || parentOpts.json || false;
4549
4619
  const fmt = createFormatter({
4550
4620
  json: jsonOutput,
@@ -4554,10 +4624,9 @@ var listCommand2 = new Command("list").description("List open todos sorted by pr
4554
4624
  });
4555
4625
  const auth = await resolveAuth();
4556
4626
  const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
4557
- const args = {};
4558
- if (options.status) {
4559
- args.status = options.status;
4560
- }
4627
+ const args = {
4628
+ status: options.status ?? "open"
4629
+ };
4561
4630
  const result = await client.mcpCall("todo_list", args);
4562
4631
  let todos = [];
4563
4632
  if (Array.isArray(result)) {
@@ -4634,6 +4703,271 @@ var updateCommand = new Command("update").description("Update a todo's status").
4634
4703
  var todoCommand = new Command("todo").description("Manage kanban todos").addCommand(listCommand2).addCommand(createCommand2).addCommand(doneCommand).addCommand(updateCommand);
4635
4704
  var todo_default = todoCommand;
4636
4705
 
4706
+ // src/commands/doctor.ts
4707
+ import { existsSync } from "node:fs";
4708
+ import { copyFile, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
4709
+ import { homedir as homedir2 } from "node:os";
4710
+ import { dirname as dirname2, join as join2 } from "node:path";
4711
+
4712
+ // src/lib/codex-config.ts
4713
+ import { execFileSync } from "node:child_process";
4714
+ var CODEX_SERVER_NAME = "great-arrow";
4715
+ var CODEX_CLIENT_ID = "codex-cli";
4716
+ var SAFE_GLOBAL_MCP_SERVERS = /* @__PURE__ */ new Set([
4717
+ CODEX_SERVER_NAME,
4718
+ "great-arrow-kiro-terminal",
4719
+ "chrome-devtools",
4720
+ "node_repl",
4721
+ "playwright",
4722
+ "postman",
4723
+ "stripe",
4724
+ "vercel",
4725
+ "xcodebuildmcp"
4726
+ ]);
4727
+ function normalizeApiUrl(apiUrl) {
4728
+ return apiUrl.replace(/\/+$/, "");
4729
+ }
4730
+ function mcpEndpoint(apiUrl) {
4731
+ return `${normalizeApiUrl(apiUrl)}/api/mcp`;
4732
+ }
4733
+ function tomlEscape(value) {
4734
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4735
+ }
4736
+ function detectGitOrigin2(cwd = process.cwd()) {
4737
+ try {
4738
+ const env = { ...process.env };
4739
+ for (const key of Object.keys(env)) {
4740
+ if (key.startsWith("GIT_")) delete env[key];
4741
+ }
4742
+ const url = execFileSync(
4743
+ "git",
4744
+ ["-C", cwd, "remote", "get-url", "origin"],
4745
+ {
4746
+ encoding: "utf8",
4747
+ stdio: ["ignore", "pipe", "ignore"],
4748
+ env,
4749
+ timeout: 1e3
4750
+ }
4751
+ ).trim();
4752
+ return url || null;
4753
+ } catch {
4754
+ return null;
4755
+ }
4756
+ }
4757
+ function repoKey(repoUrl) {
4758
+ if (!repoUrl) return null;
4759
+ const trimmed = repoUrl.trim();
4760
+ if (!trimmed) return null;
4761
+ const scpLike = trimmed.match(/^git@([^:]+):(.+)$/);
4762
+ const asUrl = scpLike ? `https://${scpLike[1]}/${scpLike[2]}` : trimmed.replace(/^ssh:\/\/git@/i, "https://");
4763
+ try {
4764
+ const parsed = new URL(asUrl);
4765
+ const host = parsed.hostname.toLowerCase();
4766
+ let path = parsed.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
4767
+ path = path.replace(/\.git$/i, "").toLowerCase();
4768
+ return path ? `${host}/${path}` : null;
4769
+ } catch {
4770
+ return null;
4771
+ }
4772
+ }
4773
+ function generateCodexConfigToml(apiUrl, repoUrl) {
4774
+ const repoHeader = repoUrl ? `X-Repo-URL = "${tomlEscape(repoUrl)}"
4775
+ ` : "";
4776
+ return `[mcp_servers.${CODEX_SERVER_NAME}]
4777
+ transport = "streamable_http"
4778
+ url = "${tomlEscape(mcpEndpoint(apiUrl))}"
4779
+ bearer_token_env_var = "GREAT_ARROW_TOKEN"
4780
+
4781
+ [mcp_servers.${CODEX_SERVER_NAME}.http_headers]
4782
+ X-MCP-Client = "${CODEX_CLIENT_ID}"
4783
+ ${repoHeader}`;
4784
+ }
4785
+ function generateCodexEnv(token) {
4786
+ return `GREAT_ARROW_TOKEN=${token}
4787
+ `;
4788
+ }
4789
+ function extractCodexGreatArrowApiUrl(content) {
4790
+ const text = groupedServerText(splitTomlSections(content)).get(
4791
+ CODEX_SERVER_NAME
4792
+ );
4793
+ if (!text) return null;
4794
+ const match = text.match(/^\s*url\s*=\s*"([^"]+)"\s*$/m);
4795
+ if (!match?.[1]) return null;
4796
+ const url = match[1].trim();
4797
+ return url.endsWith("/api/mcp") ? url.slice(0, -"/api/mcp".length) : url.replace(/\/+$/, "");
4798
+ }
4799
+ function splitTomlSections(content) {
4800
+ const hasTrailingNewline = content.endsWith("\n");
4801
+ const lines = content.split(/\n/);
4802
+ if (hasTrailingNewline) lines.pop();
4803
+ const sections = [];
4804
+ let table = null;
4805
+ let current = [];
4806
+ const flush = () => {
4807
+ if (current.length === 0) return;
4808
+ sections.push({ table, text: `${current.join("\n")}
4809
+ ` });
4810
+ current = [];
4811
+ };
4812
+ for (const line of lines) {
4813
+ const match = line.match(/^\s*\[([^\]]+)]\s*$/);
4814
+ if (match) {
4815
+ flush();
4816
+ table = match[1] ?? null;
4817
+ }
4818
+ current.push(line);
4819
+ }
4820
+ flush();
4821
+ return sections;
4822
+ }
4823
+ function tableServerName(table) {
4824
+ if (!table) return null;
4825
+ const match = table.match(/^mcp_servers\.(?:"([^"]+)"|([^.\s]+))(?:\.|$)/);
4826
+ return match?.[1] ?? match?.[2] ?? null;
4827
+ }
4828
+ function groupedServerText(sections) {
4829
+ const grouped = /* @__PURE__ */ new Map();
4830
+ for (const section of sections) {
4831
+ const server = tableServerName(section.table);
4832
+ if (!server) continue;
4833
+ grouped.set(server, `${grouped.get(server) ?? ""}${section.text}`);
4834
+ }
4835
+ return grouped;
4836
+ }
4837
+ function removeServerSections(content, servers) {
4838
+ const sections = splitTomlSections(content);
4839
+ return sections.filter((section) => {
4840
+ const server = tableServerName(section.table);
4841
+ return !server || !servers.has(server);
4842
+ }).map((section) => section.text).join("").replace(/\n{3,}/g, "\n\n");
4843
+ }
4844
+ function upsertCodexServerBlock(existingContent, apiUrl, repoUrl) {
4845
+ const block = generateCodexConfigToml(apiUrl, repoUrl).trimEnd();
4846
+ const withoutGreatArrow = removeServerSections(
4847
+ existingContent,
4848
+ /* @__PURE__ */ new Set([CODEX_SERVER_NAME])
4849
+ ).replace(/\s*$/, "");
4850
+ if (!withoutGreatArrow) return `${block}
4851
+ `;
4852
+ return `${withoutGreatArrow}
4853
+
4854
+ ${block}
4855
+ `;
4856
+ }
4857
+ function extractConfiguredRepoKeys(text) {
4858
+ const keys = /* @__PURE__ */ new Set();
4859
+ const repoSetting = /(?:X-Repo-URL|MEMORY_REPO_URL|GAD_REPO_URL)\s*=\s*"([^"]+)"/gi;
4860
+ for (const match of text.matchAll(repoSetting)) {
4861
+ const key = repoKey(match[1]);
4862
+ if (key) keys.add(key);
4863
+ }
4864
+ const gitUrls = /(?:https:\/\/|ssh:\/\/git@|git@)(?:github\.com|gitlab\.com|bitbucket\.org)[:/][^\s"',\]]+/gi;
4865
+ for (const match of text.matchAll(gitUrls)) {
4866
+ const key = repoKey(match[0]);
4867
+ if (key) keys.add(key);
4868
+ }
4869
+ return [...keys];
4870
+ }
4871
+ function hasJobbyJobMarker(server, text) {
4872
+ return /jobby\s*job|jobbyjob|jobbyjob\.ai/i.test(`${server}
4873
+ ${text}`);
4874
+ }
4875
+ function isUnmanagedServer(server) {
4876
+ return !SAFE_GLOBAL_MCP_SERVERS.has(server);
4877
+ }
4878
+ function inspectCodexConfig(content, expectedRepoUrl) {
4879
+ const expectedRepoKey = repoKey(expectedRepoUrl);
4880
+ const grouped = groupedServerText(splitTomlSections(content));
4881
+ const serverNames = [...grouped.keys()].sort();
4882
+ const greatArrowText = grouped.get(CODEX_SERVER_NAME) ?? "";
4883
+ const hasGreatArrowServer = grouped.has(CODEX_SERVER_NAME);
4884
+ const greatArrowRepoKeys = extractConfiguredRepoKeys(greatArrowText);
4885
+ const greatArrowHasRepoHeader = Boolean(
4886
+ expectedRepoKey && greatArrowRepoKeys.includes(expectedRepoKey)
4887
+ );
4888
+ const unmanagedServers = serverNames.filter(isUnmanagedServer);
4889
+ const staleRepoServers = [];
4890
+ for (const [server, text] of grouped.entries()) {
4891
+ if (server === CODEX_SERVER_NAME) continue;
4892
+ const keys = extractConfiguredRepoKeys(text);
4893
+ const mismatchedExplicitRepo = expectedRepoKey && keys.length > 0 && !keys.some((key) => key === expectedRepoKey);
4894
+ if (mismatchedExplicitRepo || hasJobbyJobMarker(server, text)) {
4895
+ staleRepoServers.push(server);
4896
+ }
4897
+ }
4898
+ staleRepoServers.sort();
4899
+ const issues = [];
4900
+ issues.push(
4901
+ hasGreatArrowServer ? {
4902
+ code: "great_arrow_present",
4903
+ level: "pass",
4904
+ message: "Codex has a Great Arrow MCP server block."
4905
+ } : {
4906
+ code: "great_arrow_missing",
4907
+ level: "fail",
4908
+ message: "Codex is missing the Great Arrow MCP server block.",
4909
+ hint: "Run `gad mcp install codex` from the repo root."
4910
+ }
4911
+ );
4912
+ if (expectedRepoKey) {
4913
+ issues.push(
4914
+ greatArrowHasRepoHeader ? {
4915
+ code: "repo_header_present",
4916
+ level: "pass",
4917
+ message: `Great Arrow is pinned to ${expectedRepoUrl}.`
4918
+ } : {
4919
+ code: "repo_header_missing",
4920
+ level: "warn",
4921
+ message: "Great Arrow is not pinned with X-Repo-URL.",
4922
+ hint: "Run `gad mcp install codex` from the repo root to add repo routing."
4923
+ }
4924
+ );
4925
+ }
4926
+ if (staleRepoServers.length > 0) {
4927
+ issues.push({
4928
+ code: "stale_repo_servers",
4929
+ level: "fail",
4930
+ message: `Found stale repo-scoped MCP server(s): ${staleRepoServers.join(", ")}.`,
4931
+ hint: "Run `gad doctor codex --enforce` to remove the stale server blocks."
4932
+ });
4933
+ } else {
4934
+ issues.push({
4935
+ code: "no_stale_repo_servers",
4936
+ level: "pass",
4937
+ message: "No stale repo-scoped MCP server blocks found."
4938
+ });
4939
+ }
4940
+ if (unmanagedServers.length > 0) {
4941
+ issues.push({
4942
+ code: "unmanaged_servers",
4943
+ level: "warn",
4944
+ message: `Codex has unmanaged MCP server(s): ${unmanagedServers.join(", ")}.`,
4945
+ hint: "Review these manually if they are not intentionally global."
4946
+ });
4947
+ }
4948
+ return {
4949
+ expectedRepoUrl: expectedRepoUrl ?? null,
4950
+ expectedRepoKey,
4951
+ hasGreatArrowServer,
4952
+ greatArrowHasRepoHeader,
4953
+ unmanagedServers,
4954
+ staleRepoServers,
4955
+ issues
4956
+ };
4957
+ }
4958
+ function repairCodexConfig(content, staleRepoServers) {
4959
+ if (staleRepoServers.length === 0) {
4960
+ return { content, removedServers: [] };
4961
+ }
4962
+ const removed = [...new Set(staleRepoServers)].sort();
4963
+ const repaired = removeServerSections(content, new Set(removed));
4964
+ return {
4965
+ content: repaired.endsWith("\n") ? repaired : `${repaired}
4966
+ `,
4967
+ removedServers: removed
4968
+ };
4969
+ }
4970
+
4637
4971
  // src/commands/doctor.ts
4638
4972
  function renderCheck(check, color2) {
4639
4973
  const { status, name, message, hint } = check;
@@ -4717,8 +5051,12 @@ async function checkIntegrations(auth) {
4717
5051
  const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
4718
5052
  try {
4719
5053
  const result = await client.mcpCall("integrations_list", {});
4720
- const integrations = Array.isArray(result) ? result : [];
4721
- const errorIntegrations = integrations.filter((i) => i.status === "error");
5054
+ const data = result ?? {};
5055
+ const integrations = Array.isArray(result) ? result : Array.isArray(data.integrations) ? data.integrations : [];
5056
+ const errorIntegrations = integrations.filter((i) => {
5057
+ const status = (i.status ?? "").toUpperCase();
5058
+ return status === "ERROR" || status === "NEEDS_REAUTH";
5059
+ });
4722
5060
  if (errorIntegrations.length === 0) {
4723
5061
  return {
4724
5062
  name: "Integrations",
@@ -4730,7 +5068,7 @@ async function checkIntegrations(auth) {
4730
5068
  return {
4731
5069
  name: "Integrations",
4732
5070
  status: "warn",
4733
- message: `${errorIntegrations.length} integration(s) with errors: ${kinds}`,
5071
+ message: `${errorIntegrations.length} of ${integrations.length} integration(s) need attention: ${kinds}`,
4734
5072
  hint: "Run 'gad integrations list' for details, or 'gad integrations sync <kind>' to retry."
4735
5073
  };
4736
5074
  } catch {
@@ -4768,18 +5106,145 @@ async function checkMcp(auth) {
4768
5106
  };
4769
5107
  }
4770
5108
  }
5109
+ function defaultCodexConfigPath() {
5110
+ return join2(homedir2(), ".codex", "config.toml");
5111
+ }
5112
+ function codexIssuesToChecks(issues) {
5113
+ return issues.map((issue) => {
5114
+ const check = {
5115
+ name: `Codex Config: ${issue.code}`,
5116
+ status: issue.level,
5117
+ message: issue.server ? `${issue.message} (${issue.server})` : issue.message
5118
+ };
5119
+ if (issue.hint) check.hint = issue.hint;
5120
+ return check;
5121
+ });
5122
+ }
5123
+ function codexCommand() {
5124
+ return new Command("codex").description(
5125
+ "Check Codex MCP config for wrong-repo entries and optional repair"
5126
+ ).option(
5127
+ "--config <path>",
5128
+ "Path to Codex config.toml",
5129
+ defaultCodexConfigPath()
5130
+ ).option(
5131
+ "--expected-repo <url>",
5132
+ "Expected repo URL (defaults to current git origin)"
5133
+ ).option(
5134
+ "--enforce",
5135
+ "Back up config, rewrite Great Arrow repo pin, and remove stale repo-scoped MCP servers"
5136
+ ).option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(
5137
+ async (options, command) => {
5138
+ const g = globalOutputOpts(command);
5139
+ const color2 = createColor(g.noColor);
5140
+ const fmt = createFormatter({
5141
+ json: g.json,
5142
+ quiet: g.quiet,
5143
+ isTTY: isTTY(),
5144
+ noColor: g.noColor
5145
+ });
5146
+ const configPath = options.config ?? defaultCodexConfigPath();
5147
+ const expectedRepoUrl = options.expectedRepo ?? detectGitOrigin2();
5148
+ let content = existsSync(configPath) ? await readFile2(configPath, "utf8") : "";
5149
+ let backupPath = null;
5150
+ let removedServers = [];
5151
+ if (options.enforce) {
5152
+ const apiUrl = extractCodexGreatArrowApiUrl(content) ?? (await resolveAuth()).apiUrl;
5153
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5154
+ await mkdir2(dirname2(configPath), { recursive: true });
5155
+ if (content.trim()) {
5156
+ backupPath = `${configPath}.bak.${timestamp}`;
5157
+ await copyFile(configPath, backupPath);
5158
+ }
5159
+ content = upsertCodexServerBlock(content, apiUrl, expectedRepoUrl);
5160
+ const preRepair = inspectCodexConfig(content, expectedRepoUrl);
5161
+ const repaired = repairCodexConfig(
5162
+ content,
5163
+ preRepair.staleRepoServers
5164
+ );
5165
+ content = repaired.content;
5166
+ removedServers = repaired.removedServers;
5167
+ await writeFile2(configPath, content, "utf8");
5168
+ }
5169
+ const inspection = inspectCodexConfig(content, expectedRepoUrl);
5170
+ const checks = codexIssuesToChecks(inspection.issues);
5171
+ if (options.enforce) {
5172
+ const enforcementCheck = {
5173
+ name: "Codex Config: enforcement",
5174
+ status: "pass",
5175
+ message: removedServers.length > 0 ? `Removed stale server(s): ${removedServers.join(", ")}` : "No stale server blocks needed removal"
5176
+ };
5177
+ if (backupPath) {
5178
+ enforcementCheck.hint = `Backup written to ${backupPath}`;
5179
+ }
5180
+ checks.push(enforcementCheck);
5181
+ }
5182
+ if (g.json) {
5183
+ process.stdout.write(
5184
+ JSON.stringify(
5185
+ {
5186
+ configPath,
5187
+ expectedRepoUrl,
5188
+ enforce: Boolean(options.enforce),
5189
+ backupPath,
5190
+ removedServers,
5191
+ inspection,
5192
+ checks
5193
+ },
5194
+ null,
5195
+ 2
5196
+ ) + "\n"
5197
+ );
5198
+ } else {
5199
+ if (!expectedRepoUrl) {
5200
+ process.stdout.write(
5201
+ renderCheck(
5202
+ {
5203
+ name: "Codex Config: repo detection",
5204
+ status: "warn",
5205
+ message: "No git origin detected for this directory",
5206
+ hint: "Run from a repo root or pass --expected-repo."
5207
+ },
5208
+ color2
5209
+ ) + "\n"
5210
+ );
5211
+ }
5212
+ for (const check of checks) {
5213
+ process.stdout.write(renderCheck(check, color2) + "\n");
5214
+ }
5215
+ if (!g.quiet) {
5216
+ const failed = checks.filter((c) => c.status === "fail").length;
5217
+ const warned = checks.filter((c) => c.status === "warn").length;
5218
+ process.stdout.write("\n");
5219
+ if (failed === 0 && warned === 0) {
5220
+ fmt.success("Codex config guard passed");
5221
+ } else if (failed === 0) {
5222
+ fmt.warn(`Codex config guard passed with ${warned} warning(s)`);
5223
+ } else {
5224
+ fmt.error(
5225
+ `Codex config guard found ${failed} failure(s), ${warned} warning(s)`
5226
+ );
5227
+ }
5228
+ }
5229
+ }
5230
+ if (checks.some((check) => check.status === "fail")) {
5231
+ process.exitCode = 1;
5232
+ }
5233
+ }
5234
+ );
5235
+ }
4771
5236
  function doctorCommand() {
4772
- const cmd = new Command("doctor").description("Run a comprehensive health check").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options) => {
4773
- const noColor = options.color === false;
4774
- const color2 = createColor(noColor);
5237
+ const cmd = new Command("doctor").description("Run a comprehensive health check").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options, command) => {
5238
+ const g = globalOutputOpts(command);
5239
+ const color2 = createColor(g.noColor);
4775
5240
  const fmt = createFormatter({
4776
- json: options.json ?? false,
4777
- quiet: options.quiet ?? false,
5241
+ json: g.json,
5242
+ quiet: g.quiet,
4778
5243
  isTTY: isTTY(),
4779
- noColor
5244
+ noColor: g.noColor
4780
5245
  });
4781
5246
  const checks = [];
4782
- const isJson = options.json ?? false;
5247
+ const isJson = g.json;
4783
5248
  const { check: tokenCheck, auth } = await checkToken();
4784
5249
  checks.push(tokenCheck);
4785
5250
  if (!isJson) {
@@ -4802,7 +5267,7 @@ function doctorCommand() {
4802
5267
  }
4803
5268
  if (isJson) {
4804
5269
  process.stdout.write(JSON.stringify(checks) + "\n");
4805
- } else if (!options.quiet) {
5270
+ } else if (!g.quiet) {
4806
5271
  const passed = checks.filter((c) => c.status === "pass").length;
4807
5272
  const warned = checks.filter((c) => c.status === "warn").length;
4808
5273
  const failed = checks.filter((c) => c.status === "fail").length;
@@ -4816,6 +5281,7 @@ function doctorCommand() {
4816
5281
  }
4817
5282
  }
4818
5283
  });
5284
+ cmd.addCommand(codexCommand());
4819
5285
  return cmd;
4820
5286
  }
4821
5287
 
@@ -4865,19 +5331,6 @@ async function createClient2() {
4865
5331
  const auth = await resolveAuth();
4866
5332
  return new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
4867
5333
  }
4868
- function extractMcpContent2(result) {
4869
- if (Array.isArray(result) && result.length > 0) {
4870
- const first = result[0];
4871
- if (typeof first === "object" && first !== null && "type" in first && first.type === "text" && "text" in first) {
4872
- try {
4873
- return JSON.parse(first.text);
4874
- } catch {
4875
- return first.text;
4876
- }
4877
- }
4878
- }
4879
- return result;
4880
- }
4881
5334
  function formatErrorDetail(rawError) {
4882
5335
  if (rawError === null || rawError === void 0) return null;
4883
5336
  if (typeof rawError === "string") return rawError;
@@ -4927,8 +5380,9 @@ function handleSyncResponse(fmt, kind, response) {
4927
5380
  }
4928
5381
  }
4929
5382
  var listCommand3 = new Command("list").description("List all connected integrations with sync status").option("--json", "Output as JSON").action(async (options) => {
4930
- const parentOpts = listCommand3.parent?.opts() ?? {};
4931
- const jsonFlag = options.json || parentOpts.json || false;
5383
+ const jsonFlag = Boolean(
5384
+ options.json || listCommand3.optsWithGlobals().json
5385
+ );
4932
5386
  const fmt = createFormatter({
4933
5387
  json: jsonFlag,
4934
5388
  quiet: false,
@@ -4937,7 +5391,7 @@ var listCommand3 = new Command("list").description("List all connected integrati
4937
5391
  });
4938
5392
  const client = await createClient2();
4939
5393
  const result = await client.mcpCall("integrations_list", {});
4940
- const data = extractMcpContent2(result);
5394
+ const data = result;
4941
5395
  let integrations = [];
4942
5396
  if (Array.isArray(data)) {
4943
5397
  integrations = data;
@@ -4964,7 +5418,7 @@ var syncCommand = new Command("sync").description("Trigger a manual sync for the
4964
5418
  fmt.info(`Triggering sync for "${kind}"...`);
4965
5419
  try {
4966
5420
  const result = await client.mcpCall("integration_sync", { kind });
4967
- const data = extractMcpContent2(result);
5421
+ const data = result;
4968
5422
  if (typeof data === "object" && data !== null) {
4969
5423
  handleSyncResponse(fmt, kind, data);
4970
5424
  } else {
@@ -4990,39 +5444,20 @@ var SUPPORTED_CLIENTS = [
4990
5444
  "codex",
4991
5445
  "gemini"
4992
5446
  ];
4993
- var MCP_SERVER_NAME = "great-arrow";
5447
+ var MCP_SERVER_NAME = CODEX_SERVER_NAME;
4994
5448
  var CLIENT_IDS = {
4995
5449
  claude: "claude-code-cli",
4996
5450
  cursor: "cursor",
4997
5451
  kiro: "kiro",
4998
- codex: "codex-cli",
5452
+ codex: CODEX_CLIENT_ID,
4999
5453
  gemini: "gemini-cli"
5000
5454
  };
5001
- function normalizeApiUrl(apiUrl) {
5002
- return apiUrl.replace(/\/+$/, "");
5003
- }
5004
- function mcpEndpoint(apiUrl) {
5005
- return `${normalizeApiUrl(apiUrl)}/api/mcp`;
5006
- }
5007
5455
  async function createClient3() {
5008
5456
  const auth = await resolveAuth();
5009
5457
  const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
5010
5458
  return { client, apiUrl: auth.apiUrl };
5011
5459
  }
5012
- function extractMcpContent3(result) {
5013
- if (Array.isArray(result) && result.length > 0) {
5014
- const first = result[0];
5015
- if (typeof first === "object" && first !== null && "type" in first && first.type === "text" && "text" in first) {
5016
- try {
5017
- return JSON.parse(first.text);
5018
- } catch {
5019
- return first.text;
5020
- }
5021
- }
5022
- }
5023
- return result;
5024
- }
5025
- function generateMcpConfig(client, apiUrl, token) {
5460
+ function generateMcpConfig(client, apiUrl, token, repoUrl) {
5026
5461
  const endpoint = mcpEndpoint(apiUrl);
5027
5462
  if (client === "codex") {
5028
5463
  return {
@@ -5036,7 +5471,11 @@ function generateMcpConfig(client, apiUrl, token) {
5036
5471
  MEMORY_API_URL: apiUrl,
5037
5472
  MEMORY_CLIENT_NAME: CLIENT_IDS.codex,
5038
5473
  GAD_TOKEN: token,
5039
- GAD_API_URL: apiUrl
5474
+ GAD_API_URL: apiUrl,
5475
+ ...repoUrl ? {
5476
+ MEMORY_REPO_URL: repoUrl,
5477
+ GAD_REPO_URL: repoUrl
5478
+ } : {}
5040
5479
  }
5041
5480
  }
5042
5481
  }
@@ -5049,44 +5488,13 @@ function generateMcpConfig(client, apiUrl, token) {
5049
5488
  url: endpoint,
5050
5489
  headers: {
5051
5490
  Authorization: `Bearer ${token}`,
5052
- "X-MCP-Client": CLIENT_IDS[client]
5491
+ "X-MCP-Client": CLIENT_IDS[client],
5492
+ ...repoUrl ? { "X-Repo-URL": repoUrl } : {}
5053
5493
  }
5054
5494
  }
5055
5495
  }
5056
5496
  };
5057
5497
  }
5058
- function tomlEscape(value) {
5059
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
5060
- }
5061
- function generateCodexConfigToml(apiUrl) {
5062
- return `[mcp_servers.${MCP_SERVER_NAME}]
5063
- transport = "streamable_http"
5064
- url = "${tomlEscape(mcpEndpoint(apiUrl))}"
5065
- bearer_token_env_var = "GREAT_ARROW_TOKEN"
5066
- `;
5067
- }
5068
- function generateCodexEnv(token) {
5069
- return `GREAT_ARROW_TOKEN=${token}
5070
- `;
5071
- }
5072
- function upsertCodexServerBlock(existingContent, apiUrl) {
5073
- const block = generateCodexConfigToml(apiUrl).trimEnd();
5074
- const serverBlockPattern = /^\[mcp_servers\.great-arrow\]\n[\s\S]*?(?=^\[|\s*$)/m;
5075
- if (!existingContent.trim()) {
5076
- return `${block}
5077
- `;
5078
- }
5079
- if (serverBlockPattern.test(existingContent)) {
5080
- const updated = existingContent.replace(serverBlockPattern, block);
5081
- return updated.endsWith("\n") ? updated : `${updated}
5082
- `;
5083
- }
5084
- const trimmedRight = existingContent.replace(/\s*$/, "");
5085
- return `${trimmedRight}
5086
-
5087
- ${block}
5088
- `;
5089
- }
5090
5498
  function upsertEnvValue(existingContent, key, value) {
5091
5499
  const lines = existingContent.split(/\r?\n/).filter((line) => line.trim() !== "" && !line.startsWith(`${key}=`));
5092
5500
  lines.push(`${key}=${value}`);
@@ -5102,29 +5510,29 @@ async function pathExists(path) {
5102
5510
  return false;
5103
5511
  }
5104
5512
  }
5105
- async function installCodexConfig(apiUrl, token) {
5106
- const { homedir: homedir2 } = await import("node:os");
5107
- const { join: join2 } = await import("node:path");
5108
- const { chmod, copyFile, mkdir: mkdir2, readFile: readFile2, writeFile: writeFile2 } = await import("node:fs/promises");
5109
- const codexDir = join2(homedir2(), ".codex");
5110
- const configPath = join2(codexDir, "config.toml");
5111
- const envPath = join2(codexDir, ".env");
5513
+ async function installCodexConfig(apiUrl, token, repoUrl) {
5514
+ const { homedir: homedir3 } = await import("node:os");
5515
+ const { join: join3 } = await import("node:path");
5516
+ const { chmod, copyFile: copyFile2, mkdir: mkdir3, readFile: readFile3, writeFile: writeFile3 } = await import("node:fs/promises");
5517
+ const codexDir = join3(homedir3(), ".codex");
5518
+ const configPath = join3(codexDir, "config.toml");
5519
+ const envPath = join3(codexDir, ".env");
5112
5520
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5113
- await mkdir2(codexDir, { recursive: true });
5114
- const existingConfig = await pathExists(configPath) ? await readFile2(configPath, "utf8") : "";
5521
+ await mkdir3(codexDir, { recursive: true });
5522
+ const existingConfig = await pathExists(configPath) ? await readFile3(configPath, "utf8") : "";
5115
5523
  if (existingConfig.trim()) {
5116
- await copyFile(configPath, `${configPath}.bak.${timestamp}`);
5524
+ await copyFile2(configPath, `${configPath}.bak.${timestamp}`);
5117
5525
  }
5118
- await writeFile2(
5526
+ await writeFile3(
5119
5527
  configPath,
5120
- upsertCodexServerBlock(existingConfig, apiUrl),
5528
+ upsertCodexServerBlock(existingConfig, apiUrl, repoUrl),
5121
5529
  "utf8"
5122
5530
  );
5123
- const existingEnv = await pathExists(envPath) ? await readFile2(envPath, "utf8") : "";
5531
+ const existingEnv = await pathExists(envPath) ? await readFile3(envPath, "utf8") : "";
5124
5532
  if (existingEnv.trim()) {
5125
- await copyFile(envPath, `${envPath}.bak.${timestamp}`);
5533
+ await copyFile2(envPath, `${envPath}.bak.${timestamp}`);
5126
5534
  }
5127
- await writeFile2(
5535
+ await writeFile3(
5128
5536
  envPath,
5129
5537
  upsertEnvValue(existingEnv, "GREAT_ARROW_TOKEN", token),
5130
5538
  "utf8"
@@ -5141,8 +5549,9 @@ var statusColumns = [
5141
5549
  var statusCommand = new Command("status").description(
5142
5550
  "Show MCP server connection status, proxy version, and session count"
5143
5551
  ).option("--json", "Output as JSON").action(async (options) => {
5144
- const parentOpts = statusCommand.parent?.opts() ?? {};
5145
- const jsonFlag = options.json || parentOpts.json || false;
5552
+ const jsonFlag = Boolean(
5553
+ options.json || statusCommand.optsWithGlobals().json
5554
+ );
5146
5555
  const fmt = createFormatter({
5147
5556
  json: jsonFlag,
5148
5557
  quiet: false,
@@ -5158,7 +5567,7 @@ var statusCommand = new Command("status").description(
5158
5567
  };
5159
5568
  try {
5160
5569
  const result = await client.mcpCall("workspace_introspect", {});
5161
- const data = extractMcpContent3(result);
5570
+ const data = result;
5162
5571
  if (typeof data === "object" && data !== null) {
5163
5572
  const info = data;
5164
5573
  statusData = {
@@ -5197,8 +5606,9 @@ function renderLogEntry(entry, c) {
5197
5606
  `);
5198
5607
  }
5199
5608
  var logsCommand = new Command("logs").description("Tail MCP connection log with colorized output").option("--lines <n>", "Number of recent log lines to show", "20").option("--json", "Output as JSON").action(async (options) => {
5200
- const parentOpts = logsCommand.parent?.opts() ?? {};
5201
- const jsonFlag = options.json || parentOpts.json || false;
5609
+ const jsonFlag = Boolean(
5610
+ options.json || logsCommand.optsWithGlobals().json
5611
+ );
5202
5612
  const fmt = createFormatter({
5203
5613
  json: jsonFlag,
5204
5614
  quiet: false,
@@ -5212,7 +5622,7 @@ var logsCommand = new Command("logs").description("Tail MCP connection log with
5212
5622
  const result = await client.mcpCall("integration_sync_logs", {
5213
5623
  limit: lineCount
5214
5624
  });
5215
- const data = extractMcpContent3(result);
5625
+ const data = result;
5216
5626
  if (jsonFlag) {
5217
5627
  fmt.table(Array.isArray(data) ? data : [], [
5218
5628
  { key: "timestamp", header: "Timestamp" },
@@ -5278,7 +5688,7 @@ async function checkJsonRpc(client, verbose, c) {
5278
5688
  if (verbose) process.stdout.write(`${c.dim("\u2192")} JSON-RPC handshake... `);
5279
5689
  try {
5280
5690
  const result = await client.mcpCall("mcp_capabilities", {});
5281
- const data = extractMcpContent3(result);
5691
+ const data = result;
5282
5692
  let toolCount = "ok";
5283
5693
  if (typeof data === "object" && data !== null) {
5284
5694
  const record = data;
@@ -5300,8 +5710,9 @@ async function checkJsonRpc(client, verbose, c) {
5300
5710
  var testCommand = new Command("test").description(
5301
5711
  "Perform full MCP handshake diagnostic (DNS, edge ping, JSON-RPC)"
5302
5712
  ).option("--json", "Output as JSON").action(async (options) => {
5303
- const parentOpts = testCommand.parent?.opts() ?? {};
5304
- const jsonFlag = options.json || parentOpts.json || false;
5713
+ const jsonFlag = Boolean(
5714
+ options.json || testCommand.optsWithGlobals().json
5715
+ );
5305
5716
  const fmt = createFormatter({
5306
5717
  json: jsonFlag,
5307
5718
  quiet: false,
@@ -5336,67 +5747,75 @@ ${summary}.
5336
5747
  var installCommand = new Command("install").description("Generate MCP configuration for a supported AI client").argument(
5337
5748
  "<client>",
5338
5749
  `AI client to configure (${SUPPORTED_CLIENTS.join(", ")})`
5339
- ).option("--stdout", "Print config to stdout instead of writing file").action(async (clientName, options) => {
5340
- const fmt = createFormatter({
5341
- json: false,
5342
- quiet: false,
5343
- isTTY: isTTY(),
5344
- noColor: false
5345
- });
5346
- if (!SUPPORTED_CLIENTS.includes(clientName)) {
5347
- fmt.error(
5348
- `Unsupported client: "${clientName}". Supported clients: ${SUPPORTED_CLIENTS.join(", ")}`
5349
- );
5350
- process.exit(3);
5351
- }
5352
- const auth = await resolveAuth();
5353
- if (clientName === "codex") {
5354
- if (options.stdout) {
5355
- process.stdout.write(`# ~/.codex/config.toml
5750
+ ).option("--stdout", "Print config to stdout instead of writing file").option(
5751
+ "--repo-url <url>",
5752
+ "Repo URL to pin MCP requests to (defaults to current git origin)"
5753
+ ).action(
5754
+ async (clientName, options) => {
5755
+ const fmt = createFormatter({
5756
+ json: false,
5757
+ quiet: false,
5758
+ isTTY: isTTY(),
5759
+ noColor: false
5760
+ });
5761
+ if (!SUPPORTED_CLIENTS.includes(clientName)) {
5762
+ fmt.error(
5763
+ `Unsupported client: "${clientName}". Supported clients: ${SUPPORTED_CLIENTS.join(", ")}`
5764
+ );
5765
+ process.exit(3);
5766
+ }
5767
+ const auth = await resolveAuth();
5768
+ const repoUrl = options.repoUrl ?? detectGitOrigin2();
5769
+ if (clientName === "codex") {
5770
+ if (options.stdout) {
5771
+ process.stdout.write(`# ~/.codex/config.toml
5356
5772
  `);
5357
- process.stdout.write(generateCodexConfigToml(auth.apiUrl));
5358
- process.stdout.write(`
5773
+ process.stdout.write(generateCodexConfigToml(auth.apiUrl, repoUrl));
5774
+ process.stdout.write(`
5359
5775
  # ~/.codex/.env
5360
5776
  `);
5361
- process.stdout.write(generateCodexEnv(auth.token));
5777
+ process.stdout.write(generateCodexEnv(auth.token));
5778
+ return;
5779
+ }
5780
+ const { configPath, envPath } = await installCodexConfig(
5781
+ auth.apiUrl,
5782
+ auth.token,
5783
+ repoUrl
5784
+ );
5785
+ fmt.success(`Wrote Codex MCP config to ${configPath} and ${envPath}`);
5362
5786
  return;
5363
5787
  }
5364
- const { configPath, envPath } = await installCodexConfig(
5788
+ const config = generateMcpConfig(
5789
+ clientName,
5365
5790
  auth.apiUrl,
5366
- auth.token
5791
+ auth.token,
5792
+ repoUrl
5367
5793
  );
5368
- fmt.success(`Wrote Codex MCP config to ${configPath} and ${envPath}`);
5369
- return;
5370
- }
5371
- const config = generateMcpConfig(
5372
- clientName,
5373
- auth.apiUrl,
5374
- auth.token
5375
- );
5376
- const configJson = JSON.stringify(config, null, 2);
5377
- if (options.stdout) {
5378
- process.stdout.write(configJson + "\n");
5379
- return;
5794
+ const configJson = JSON.stringify(config, null, 2);
5795
+ if (options.stdout) {
5796
+ process.stdout.write(configJson + "\n");
5797
+ return;
5798
+ }
5799
+ const { writeFile: writeFile3 } = await import("node:fs/promises");
5800
+ const { join: join3 } = await import("node:path");
5801
+ const outputPath = join3(process.cwd(), ".mcp.json");
5802
+ await writeFile3(outputPath, configJson + "\n", "utf-8");
5803
+ fmt.success(`Wrote ${clientName} MCP config to .mcp.json`);
5380
5804
  }
5381
- const { writeFile: writeFile2 } = await import("node:fs/promises");
5382
- const { join: join2 } = await import("node:path");
5383
- const outputPath = join2(process.cwd(), ".mcp.json");
5384
- await writeFile2(outputPath, configJson + "\n", "utf-8");
5385
- fmt.success(`Wrote ${clientName} MCP config to .mcp.json`);
5386
- });
5805
+ );
5387
5806
  var mcpCommand = new Command("mcp").description("Manage and diagnose the MCP server connection").addCommand(statusCommand).addCommand(logsCommand).addCommand(testCommand).addCommand(installCommand);
5388
5807
  var mcp_default = mcpCommand;
5389
5808
 
5390
5809
  // src/commands/deploy.ts
5391
5810
  var DEPLOY_TIMEOUT_MS = 9e4;
5392
5811
  function deployCommand() {
5393
- 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) => {
5394
- const noColor = options.color === false;
5812
+ 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) => {
5813
+ const g = globalOutputOpts(command);
5395
5814
  const fmt = createFormatter({
5396
- json: options.json ?? false,
5397
- quiet: options.quiet ?? false,
5815
+ json: g.json,
5816
+ quiet: g.quiet,
5398
5817
  isTTY: isTTY(),
5399
- noColor
5818
+ noColor: g.noColor
5400
5819
  });
5401
5820
  if (!options.confirm) {
5402
5821
  if (!isTTY()) {
@@ -5424,7 +5843,7 @@ function deployCommand() {
5424
5843
  fmt.info("Deploying to production...");
5425
5844
  }
5426
5845
  const result = await client.mcpCall("deploy_to_vercel", {});
5427
- if (options.json) {
5846
+ if (g.json) {
5428
5847
  process.stdout.write(JSON.stringify(result) + "\n");
5429
5848
  return;
5430
5849
  }
@@ -5484,11 +5903,12 @@ function displayResults(result, fmt) {
5484
5903
  ]);
5485
5904
  }
5486
5905
  }
5487
- 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) => {
5906
+ 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) => {
5907
+ const g = globalOutputOpts(command);
5488
5908
  const tty = isTTY();
5489
5909
  const fmt = createFormatter({
5490
- json: Boolean(opts.json),
5491
- quiet: Boolean(opts.quiet),
5910
+ json: g.json,
5911
+ quiet: g.quiet,
5492
5912
  isTTY: tty,
5493
5913
  noColor: !tty
5494
5914
  });
@@ -5500,7 +5920,7 @@ var migrateCommand = new Command("migrate").description("Run pending database mi
5500
5920
  const client = new GadClient({ token: auth.token, apiUrl: auth.apiUrl });
5501
5921
  fmt.info("Running pending migrations\u2026");
5502
5922
  const result = await client.mcpCall("apply_migration", {});
5503
- if (opts.json) {
5923
+ if (g.json) {
5504
5924
  const applied = result?.applied ?? 0;
5505
5925
  const status = result?.status ?? "complete";
5506
5926
  fmt.table(
@@ -5590,17 +6010,17 @@ function outputJsonSummary(results, totalDurationMs) {
5590
6010
  process.stdout.write(JSON.stringify(output) + "\n");
5591
6011
  }
5592
6012
  function smokeCommand() {
5593
- const cmd = new Command("smoke").description("Run MCP smoke tests to verify platform connectivity").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options) => {
5594
- const noColor = options.color === false;
5595
- const color2 = createColor(noColor);
6013
+ const cmd = new Command("smoke").description("Run MCP smoke tests to verify platform connectivity").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (options, command) => {
6014
+ const g = globalOutputOpts(command);
6015
+ const color2 = createColor(g.noColor);
5596
6016
  const fmt = createFormatter({
5597
- json: options.json ?? false,
5598
- quiet: options.quiet ?? false,
6017
+ json: g.json,
6018
+ quiet: g.quiet,
5599
6019
  isTTY: isTTY(),
5600
- noColor
6020
+ noColor: g.noColor
5601
6021
  });
5602
- const isJson = options.json ?? false;
5603
- const isQuiet = options.quiet ?? false;
6022
+ const isJson = g.json;
6023
+ const isQuiet = g.quiet;
5604
6024
  const auth = await resolveAuth();
5605
6025
  const client = new GadClient({
5606
6026
  token: auth.token,
@@ -5656,7 +6076,8 @@ async function buildClient2() {
5656
6076
  var agentCommand = new Command("agent").description(
5657
6077
  "List and invoke AI agents"
5658
6078
  );
5659
- agentCommand.command("list").description("Display available specialist and custom agents").option("--json", "Output as JSON").action(async (opts) => {
6079
+ agentCommand.command("list").description("Display available specialist and custom agents").option("--json", "Output as JSON").action(async (opts, command) => {
6080
+ const g = globalOutputOpts(command);
5660
6081
  const client = await buildClient2();
5661
6082
  const result = await client.mcpCall("agent_list", {});
5662
6083
  let agents;
@@ -5667,14 +6088,15 @@ agentCommand.command("list").description("Display available specialist and custo
5667
6088
  agents = data?.agents ?? data?.results ?? [];
5668
6089
  }
5669
6090
  const fmt = createFormatter({
5670
- json: Boolean(opts.json),
6091
+ json: g.json,
5671
6092
  quiet: false,
5672
6093
  isTTY: isTTY(),
5673
6094
  noColor: false
5674
6095
  });
5675
6096
  fmt.table(agents, agentListColumns);
5676
6097
  });
5677
- agentCommand.command("invoke <agent-id>").description("Execute an agent with a task").requiredOption("--task <task>", "Task description for the agent").option("--json", "Output raw agent response as JSON").option("--context <context>", "Additional context for the agent").action(async (agentId, opts) => {
6098
+ agentCommand.command("invoke <agent-id>").description("Execute an agent with a task").requiredOption("--task <task>", "Task description for the agent").option("--json", "Output raw agent response as JSON").option("--context <context>", "Additional context for the agent").action(async (agentId, opts, command) => {
6099
+ const g = globalOutputOpts(command);
5678
6100
  const client = await buildClient2();
5679
6101
  const args = {
5680
6102
  agentId,
@@ -5684,7 +6106,7 @@ agentCommand.command("invoke <agent-id>").description("Execute an agent with a t
5684
6106
  args.context = opts.context;
5685
6107
  }
5686
6108
  const result = await client.mcpCall("agent_invoke", args, AGENT_TIMEOUT_MS);
5687
- if (opts.json) {
6109
+ if (g.json) {
5688
6110
  const output = typeof result === "string" ? result : JSON.stringify(result, null, 2);
5689
6111
  process.stdout.write(output + "\n");
5690
6112
  return;
@@ -5720,15 +6142,16 @@ function extractAnswer(data) {
5720
6142
  return result.answer || result.text || result.result || result.content || "";
5721
6143
  }
5722
6144
  function askCommand() {
5723
- const cmd = new Command("ask").description("Ask the orchestrator a question").argument("<question>", "The question to ask").option("--json", "Output raw response as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (question, options) => {
6145
+ const cmd = new Command("ask").description("Ask the orchestrator a question").argument("<question>", "The question to ask").option("--json", "Output raw response as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output").action(async (question, options, command) => {
6146
+ const g = globalOutputOpts(command);
5724
6147
  const auth = await resolveAuth();
5725
6148
  const client = new GadClient({
5726
6149
  token: auth.token,
5727
6150
  apiUrl: auth.apiUrl
5728
6151
  });
5729
6152
  const fmt = createFormatter({
5730
- json: options.json ?? false,
5731
- quiet: options.quiet ?? false,
6153
+ json: g.json,
6154
+ quiet: g.quiet,
5732
6155
  isTTY: isTTY(),
5733
6156
  noColor: options.color === false
5734
6157
  });
@@ -5737,7 +6160,7 @@ function askCommand() {
5737
6160
  { query: question },
5738
6161
  ORCHESTRATOR_TIMEOUT_MS
5739
6162
  );
5740
- if (options.json) {
6163
+ if (g.json) {
5741
6164
  process.stdout.write(JSON.stringify(result) + "\n");
5742
6165
  return;
5743
6166
  }
@@ -6002,7 +6425,7 @@ if (nodeVersion < 20) {
6002
6425
  console.error(" Upgrade Node.js: https://nodejs.org/");
6003
6426
  process.exit(1);
6004
6427
  }
6005
- var VERSION = "0.1.2";
6428
+ var VERSION = "0.1.4";
6006
6429
  var program2 = new Command().name("gad").description("Great Arrow Digital CLI \u2014 terminal access to the GAD platform").version(VERSION, "-v, --version").option("--json", "Output as JSON").option("--quiet", "Suppress decorative output").option("--no-color", "Disable color output");
6007
6430
  program2.addCommand(login_default);
6008
6431
  program2.addCommand(logoutCommand);