@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.
- package/README.md +35 -6
- package/dist/index.js +204 -180
- 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
|
|
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
|
-
###
|
|
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.
|
|
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
|
|
9430
|
+
async function loadEntry() {
|
|
9431
9431
|
try {
|
|
9432
|
-
|
|
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
|
|
9448
|
-
if (
|
|
9448
|
+
const entry = await loadEntry();
|
|
9449
|
+
if (entry) {
|
|
9449
9450
|
try {
|
|
9450
|
-
const value = await
|
|
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
|
|
9460
|
-
if (
|
|
9460
|
+
const entry = await loadEntry();
|
|
9461
|
+
if (entry) {
|
|
9461
9462
|
try {
|
|
9462
|
-
await
|
|
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
|
|
9479
|
-
if (
|
|
9479
|
+
const entry = await loadEntry();
|
|
9480
|
+
if (entry) {
|
|
9480
9481
|
try {
|
|
9481
|
-
await
|
|
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", {
|
|
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 =
|
|
11164
|
+
config.default_workspace = resolvedId;
|
|
11142
11165
|
await saveConfig(config);
|
|
11143
|
-
|
|
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,
|
|
12748
|
-
|
|
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
|
-
|
|
12754
|
-
|
|
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}.
|
|
12757
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12772
|
-
const
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
15448
|
-
|
|
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 =
|
|
17355
|
-
const
|
|
17356
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
51
|
+
"nanoid": "^3.3.18"
|
|
53
52
|
}
|
|
54
53
|
}
|