@cantinasecurity/apex-cli 0.1.6 → 0.1.10

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.
@@ -28,7 +28,7 @@ If the Apex MCP server is not configured, fall back to the local CLI:
28
28
  - Prefer scripted commands over the interactive shell.
29
29
  - Use `--non-interactive` and `--no-open` for automation-friendly CLI calls.
30
30
  - Use `--json` whenever structured output is helpful.
31
- - `apex credits` reports standard credits and audit scan entitlements when Bedrock returns both balances.
31
+ - `apex credits` reports standard credits plus audit and fix review scan entitlements when Bedrock returns those balances.
32
32
  - Work from the target repository directory so Apex can resolve `.apex/workspace.json`.
33
33
  - `apex scan` scans the current working directory by default; pass `--repo` only when the user asks to scan explicit alternate roots.
34
34
  - `apex-doctor` reports whether Apex will use remote materialization or a local snapshot upload for each selected source.
@@ -37,7 +37,8 @@ If the Apex MCP server is not configured, fall back to the local CLI:
37
37
  - PR scans use `--mode pr --pr <number>` in CLI calls, or `mode: "pr"` with `pullRequests` in MCP. They require one provider-backed GitHub repository.
38
38
  - `apex-workspace-use` accepts a workspace name, prefix, or ID.
39
39
  - Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
40
- - Finding comments, feedback, and fix review scan starts currently require `CANTINA_AUTH_TOKEN` in the MCP server environment because those writes go through the Cantina web-app routes instead of the Apex CLI bearer-token routes.
40
+ - When checking a scan that is not the workspace binding's latest scan, pass `scanId` to `apex-status`; use `apex scans` or `apex-scans` first if you need to discover scan IDs.
41
+ - Finding comments, feedback, and fix review scan starts use the same Apex device-login credentials as read tools. If a write tool reports missing auth, re-run `apex-auth-status` and complete `apex-auth-start` / `apex-auth-wait` instead of asking for browser cookies or auth tokens.
41
42
  - Invalid finding feedback requires `dismissalReason`; valid feedback can include `suggestedSeverity`, including `extreme`.
42
43
  - Fix PR callback feedback requires valid feedback with `labels: ["fixed"]` and `fixPrUrls`; start the fix review scan with `apex-finding-fix-review` after saving that feedback.
43
44
  - Finding identifiers such as `KERN2-25` resolve against the selected or latest scan for the current workspace binding. Pass an explicit `scanId` when needed, or use the finding UUID directly.
@@ -46,7 +47,7 @@ If the Apex MCP server is not configured, fall back to the local CLI:
46
47
 
47
48
  - Start a scan for the current repository or directory: run `apex-doctor`, bind a workspace if needed, then call `apex-scan`.
48
49
  - Start a PR scan: call `apex-scan` with `mode: "pr"` and `pullRequests`.
49
- - Check an active scan: call `apex-status`, then `apex-findings` when the scan completes.
50
+ - Check an active scan: call `apex-status`, passing `scanId` when the scan is not the latest binding, then `apex-findings` when the scan completes.
50
51
  - Export findings for review: call `apex-export-findings` with `format` set to `markdown`, `json`, or `gitlab-sast`.
51
52
  - Leave review feedback: call `apex-finding-comment` to add a note, or `apex-finding-feedback` with `status` set to `valid` or `invalid`.
52
53
  - Add a Fix PR and run fix review: call `apex-finding-feedback` with `status: "valid"`, `labels: ["fixed"]`, and `fixPrUrls`, then call `apex-finding-fix-review`.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Cantina agent plugins for security review workflows.",
9
- "version": "0.1.6"
9
+ "version": "0.1.10"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -14,10 +14,10 @@
14
14
  "source": {
15
15
  "source": "npm",
16
16
  "package": "@cantinasecurity/apex-cli",
17
- "version": "0.1.6"
17
+ "version": "0.1.10"
18
18
  },
19
19
  "description": "Run Apex security scans and review findings from Claude Code.",
20
- "version": "0.1.6",
20
+ "version": "0.1.10",
21
21
  "author": {
22
22
  "name": "Cantina",
23
23
  "email": "support@cantina.xyz"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.10",
4
4
  "description": "Run Apex security scans and review findings from Claude Code.",
5
5
  "author": {
6
6
  "name": "Cantina",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.10",
4
4
  "description": "Run Apex security scans and review findings from Codex.",
5
5
  "author": {
6
6
  "name": "Cantina",
package/.mcp.claude.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "args": [
6
6
  "-y",
7
7
  "-p",
8
- "@cantinasecurity/apex-cli@0.1.6",
8
+ "@cantinasecurity/apex-cli@0.1.10",
9
9
  "apex-mcp"
10
10
  ]
11
11
  }
package/.mcp.codex.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "args": [
5
5
  "-y",
6
6
  "-p",
7
- "@cantinasecurity/apex-cli@0.1.6",
7
+ "@cantinasecurity/apex-cli@0.1.10",
8
8
  "apex-mcp"
9
9
  ]
10
10
  }
package/MARKETPLACE.md CHANGED
@@ -13,7 +13,7 @@ This package is prepared as both a Claude Code plugin and a Codex plugin. The pu
13
13
  The plugin MCP configs launch the pinned npm CLI package with:
14
14
 
15
15
  ```bash
16
- npx -y -p @cantinasecurity/apex-cli@0.1.6 apex-mcp
16
+ npx -y -p @cantinasecurity/apex-cli@0.1.10 apex-mcp
17
17
  ```
18
18
 
19
19
  That keeps marketplace installs independent of a user's global `apex` install.
package/README.md CHANGED
@@ -19,15 +19,17 @@ apex setup
19
19
 
20
20
  `apex setup` is the lowest-friction path for agent clients. It:
21
21
 
22
- - registers Apex as an MCP server in any installed Codex and Claude Code CLIs
22
+ - registers Apex as an MCP server in any installed Codex CLI, Claude Code, and GitHub Copilot CLI clients
23
23
  - installs the Codex skill into `$CODEX_HOME/skills/apex-cli`
24
24
  - installs the Claude project skill into `.claude/skills/apex-cli` in the current repository
25
+ - installs the GitHub Copilot CLI skill into `$COPILOT_HOME/skills/apex-cli` or `~/.copilot/skills/apex-cli`
25
26
 
26
27
  If you only want one client, run:
27
28
 
28
29
  ```bash
29
30
  apex setup codex
30
31
  apex setup claude
32
+ apex setup copilot
31
33
  ```
32
34
 
33
35
  If one client is not installed yet, `apex setup` skips it automatically. If you target a client explicitly, its CLI must already be installed.
@@ -109,7 +111,7 @@ Supported shell commands:
109
111
  - `/export [scan-id]`
110
112
  - `/workspaces`
111
113
  - `/cancel-scan [scan-id]`
112
- - `/status`
114
+ - `/status [scan-id]`
113
115
  - `/doctor`
114
116
  - `/update`
115
117
  - `/logout`
@@ -142,11 +144,11 @@ Supported shell commands:
142
144
  - `apex workspace`
143
145
  - `apex workspace use <workspace-name|workspace-prefix|workspace-id>`
144
146
  - `apex cancel-scan [scan-id]`
145
- - `apex status`
147
+ - `apex status [--scan <scan-id>]`
146
148
  - `apex doctor`
147
149
  - `apex login`
148
150
  - `apex logout`
149
- - `apex setup [all|codex|claude]`
151
+ - `apex setup [all|codex|claude|copilot]`
150
152
  - `apex update`
151
153
  - `apex connect github`
152
154
  - `apex connect gitlab`
@@ -156,7 +158,7 @@ Helpful workspace flags:
156
158
  - `--company <id-or-handle>` to choose the Apex company when more than one is available
157
159
  - `--workspace-name <name>` to set the Apex workspace name for this directory
158
160
 
159
- `apex credits` shows standard scan credits plus audit scan entitlements when the server returns them.
161
+ `apex credits` shows standard scan credits plus audit and fix review scan entitlements when the server returns them.
160
162
 
161
163
  ## Finding Review Feedback
162
164
 
@@ -174,7 +176,7 @@ Finding review collaboration now has explicit write commands:
174
176
 
175
177
  Identifiers such as `KERN2-25` are resolved against the selected or latest scan for the current workspace binding. Pass `--scan <scan-id>` when you need a specific scan, or pass the finding UUID directly to skip workspace-based resolution.
176
178
 
177
- Finding comments, valid/invalid feedback, and fix review scan starts currently require a Cantina web session token in `CANTINA_AUTH_TOKEN`. Set it to the value of your logged-in `auth_token` cookie before using the write commands or the corresponding MCP tools.
179
+ Finding comments, valid/invalid feedback, and fix review scan starts use the same Apex login credentials as read commands. In MCP clients, `apex-auth-start` followed by `apex-auth-wait` is enough to authenticate these write tools.
178
180
 
179
181
  Invalid feedback requires a dismissal reason. Valid feedback can include `--suggested-severity extreme|critical|high|medium|low|informational`.
180
182
 
@@ -185,10 +187,7 @@ Fix review scans use a two-step callback flow for agents that create a PR outsid
185
187
 
186
188
  The matching MCP flow is `apex-finding-feedback` with `status: "valid"`, `labels: ["fixed"]`, and `fixPrUrls`, followed by `apex-finding-fix-review`.
187
189
 
188
- This is intentionally documented as a separate auth requirement because the current Apex read APIs and finding review write APIs do not accept the same credentials:
189
-
190
- - read operations such as `apex findings`, `apex export findings`, and `apex-findings` use the Apex CLI device-login bearer token
191
- - finding comments and feedback currently go through the Cantina web-app routes and require `CANTINA_AUTH_TOKEN`
190
+ The matching CLI and MCP flows intentionally use the same device-login session so agents should not ask users to paste browser cookies or auth tokens.
192
191
 
193
192
  ## Local Source Scans
194
193
 
@@ -222,7 +221,7 @@ If Apex is installed globally, prefer:
222
221
  apex setup
223
222
  ```
224
223
 
225
- That registers Apex for installed Codex and Claude Code clients automatically.
224
+ That registers Apex for installed Codex CLI, Claude Code, and GitHub Copilot CLI clients automatically.
226
225
 
227
226
  If you want to wire clients manually instead, Apex ships a stable `apex-mcp` binary. For Codex:
228
227
 
@@ -236,6 +235,12 @@ For Claude Code:
236
235
  claude mcp add --scope user apex -- apex-mcp
237
236
  ```
238
237
 
238
+ For GitHub Copilot CLI:
239
+
240
+ ```bash
241
+ copilot mcp add apex --type stdio --tools "*" -- apex-mcp
242
+ ```
243
+
239
244
  For any other MCP client, configure it to launch:
240
245
 
241
246
  ```json
@@ -288,7 +293,7 @@ The MCP server exposes Apex-specific tools for:
288
293
 
289
294
  For repository-scoped operations, pass `cwd` explicitly so the server can resolve the right `.apex/workspace.json` binding and repository roots.
290
295
 
291
- For Codex-style clients, the packaged skill can be installed with `apex setup codex`. The repo-local source lives at `skills/apex-cli/SKILL.md`.
296
+ For Codex-style clients, the packaged skill can be installed with `apex setup codex`. For GitHub Copilot CLI, the same skill is installed into `~/.copilot/skills/apex-cli` with `apex setup copilot`. The repo-local source lives at `skills/apex-cli/SKILL.md`.
292
297
 
293
298
  For Claude Code, the packaged project skill can be installed into the current repository with `apex setup claude`. The repo-local source lives at `.claude/skills/apex-cli/SKILL.md`. Anthropic documents project skills as filesystem directories under `.claude/skills/<name>/SKILL.md`, and the Claude Agent SDK uses the same location when the `Skill` tool is enabled.
294
299
 
@@ -300,7 +305,7 @@ The npm package also includes marketplace-ready plugin artifacts:
300
305
  - `.claude-plugin/plugin.json` and `.mcp.claude.json` for Claude Code plugin installs
301
306
  - `.claude-plugin/marketplace.json` for a Claude marketplace entry backed by the public npm package
302
307
 
303
- These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.6 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
308
+ These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.10 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
304
309
 
305
310
  The repository also includes `.agents/plugins/marketplace.json` for local Codex marketplace testing from a checkout.
306
311
 
@@ -328,11 +333,11 @@ The CLI uses the Apex `/api/cli/v2/**` local-source routes for scan planning and
328
333
  - `~/.config/apex/credentials.json`
329
334
  - `.apex/workspace.json`
330
335
 
331
- If a scan is already running in the current workspace, `apex scan` and `/scan` now require confirmation before starting another one. Scripted usage can opt in explicitly with `--force`.
336
+ If a scan is already running in the current workspace, `apex scan` and `/scan` now require confirmation before starting another one. Scripted usage can opt in explicitly with `--force`. Active-looking workspace scan rows are checked against the scan progress endpoint before the CLI treats them as blockers, so stale list entries do not hide terminal states such as `cancelled`.
332
337
 
333
338
  To move between existing Apex workspaces from the CLI:
334
339
 
335
340
  1. Run `apex workspaces` to list the workspaces available to your active company.
336
341
  2. Run `apex workspace use <workspace-name|workspace-prefix|workspace-id>` to bind the current directory.
337
342
  3. If the workspace name contains spaces, quote it, for example `apex workspace use "Core Platform"`.
338
- 4. Use `apex scans`, `apex findings`, and `apex export findings` against that binding.
343
+ 4. Use `apex scans`, `apex status --scan <scan-id>`, `apex findings`, and `apex export findings` against that binding.
@@ -45,6 +45,10 @@ export class ApexApiClient {
45
45
  async getCredentials() {
46
46
  return loadCredentials();
47
47
  }
48
+ async getAccessToken() {
49
+ const credentials = await this.refreshIfNeeded().catch(() => null);
50
+ return credentials?.accessToken ?? null;
51
+ }
48
52
  async clearCredentials() {
49
53
  await clearCredentials();
50
54
  }
package/dist/commands.js CHANGED
@@ -3,11 +3,11 @@ import { logout } from "./auth.js";
3
3
  import { ApiError } from "./api-client.js";
4
4
  import { openInBrowser } from "./browser.js";
5
5
  import { loadConfig, saveConfig } from "./config.js";
6
- import { createFindingComment, fetchScanExport, fetchScanFindings, isFindingUuid, normalizeFindingRef, requireCantinaAuthToken, resolveFindingSelection, resolveScanSelection, startFindingFixReviewScan, submitFindingFeedback, } from "./findings.js";
6
+ import { createFindingComment, fetchScanExport, fetchScanFindings, isFindingUuid, normalizeFindingRef, resolveFindingSelection, resolveScanSelection, startFindingFixReviewScan, submitFindingFeedback, } from "./findings.js";
7
7
  import { prepareExplicitScanSources, supportsLegacyRemoteFlow, } from "./local-source-scan.js";
8
8
  import { logLine, printJson, withLoadingIndicator } from "./output.js";
9
9
  import { confirm } from "./prompt.js";
10
- import { cancelScan, fetchWorkspaceScans, findMostRelevantActiveScan, getScanDisplayLabel, getScanDisplayId, getTrackedScanId, isActiveScanStatus, matchesScanId, selectDefaultScanToCancel, } from "./scan.js";
10
+ import { cancelScan, fetchScanProgress, fetchWorkspaceScans, findMostRelevantActiveScan, getScanDisplayLabel, getScanDisplayId, getTrackedScanId, isActiveScanStatus, matchesScanId, selectDefaultScanToCancel, } from "./scan.js";
11
11
  import { chooseCompany, createWorkspaceBinding, ensureAuthenticated, remediateMissingConnections, resolveWorkspaceSelection, resolveWorkspaceSelectionLegacy, resolveWorkspaceAllowingMissingConnections, selectWorkspaceTarget, } from "./session.js";
12
12
  import { createWorkspaceBindingFromSummary, fetchCompanyCredits, fetchCompanyWorkspaces, findWorkspaceByRef, } from "./workspaces.js";
13
13
  import { loadWorkspaceBinding, saveWorkspaceBinding } from "./workspace-binding.js";
@@ -81,6 +81,29 @@ function formatAuditScanBalance(scanBalance) {
81
81
  }
82
82
  return `Audit scans: ${detailParts.join(", ")}`;
83
83
  }
84
+ function formatFixReviewScanBalance(scanBalance) {
85
+ const purchased = getOptionalCount(scanBalance.fixReviewPurchased);
86
+ const used = getOptionalCount(scanBalance.fixReviewUsed);
87
+ const remaining = getOptionalCount(scanBalance.fixReviewRemaining);
88
+ const available = getOptionalCount(scanBalance.fixReviewAvailable) ??
89
+ remaining ??
90
+ (purchased !== null && used !== null ? Math.max(0, purchased - used) : null);
91
+ if (purchased === null && used === null && available === null) {
92
+ return null;
93
+ }
94
+ const detailParts = [];
95
+ if (purchased !== null) {
96
+ detailParts.push(`${purchased} purchased`);
97
+ }
98
+ if (used !== null) {
99
+ detailParts.push(`${used} used`);
100
+ }
101
+ if (available !== null) {
102
+ const detailSuffix = detailParts.length ? ` (${detailParts.join(", ")})` : "";
103
+ return `Fix review scans: ${available} available${detailSuffix}`;
104
+ }
105
+ return `Fix review scans: ${detailParts.join(", ")}`;
106
+ }
84
107
  function normalizeRequestedScanMode(value) {
85
108
  if (value === "ultra" || value === "audit") {
86
109
  return "audit";
@@ -450,6 +473,14 @@ export async function commandCredits(client, cwd, flags) {
450
473
  if (redeemableAuditScans && redeemableAuditScans > 0) {
451
474
  logLine(`Redeemable audit scans from standard credits: ${redeemableAuditScans}`, flags);
452
475
  }
476
+ const fixReviewBalance = formatFixReviewScanBalance(payload.scanBalance);
477
+ if (fixReviewBalance) {
478
+ logLine(fixReviewBalance, flags);
479
+ }
480
+ const redeemableFixReviewScans = getOptionalCount(payload.scanBalance.fixReviewRedeemableFromCredits);
481
+ if (redeemableFixReviewScans && redeemableFixReviewScans > 0) {
482
+ logLine(`Redeemable fix review scans from standard credits: ${redeemableFixReviewScans}`, flags);
483
+ }
453
484
  logLine(`Scans enabled: ${payload.scansEnabled ? "yes" : "no"}`, flags);
454
485
  return payload;
455
486
  }
@@ -507,24 +538,26 @@ export async function commandDoctor(client, cwd, flags) {
507
538
  export async function commandStatus(client, cwd, flags) {
508
539
  await ensureAuthenticated(client, flags);
509
540
  const binding = await requireWorkspaceBinding(cwd);
510
- if (!binding.lastScanId) {
541
+ const requestedScanId = getFlagString(flags, "scan");
542
+ const scanId = requestedScanId ?? binding.lastScanId;
543
+ if (!scanId) {
511
544
  throw new Error("No scan has been started from this directory yet. Run `apex scan` first.");
512
545
  }
513
- const progress = await withLoadingIndicator("Loading scan status...", flags, () => client.request(`/api/cli/v1/scans/${binding.lastScanId}/progress`));
546
+ const progress = await withLoadingIndicator("Loading scan status...", flags, () => fetchScanProgress(client, scanId));
514
547
  if (isJsonMode(flags)) {
515
- printJson({ binding, progress });
516
- return { binding, progress };
548
+ printJson({ binding, scanId, progress });
549
+ return { binding, scanId, progress };
517
550
  }
518
551
  logLine(`Workspace: ${binding.workspaceName}`, flags);
519
- logLine(`Scan: ${binding.lastScanId}`, flags);
552
+ logLine(`Scan: ${scanId}`, flags);
520
553
  logLine(`Status: ${progress.progress?.status ?? "unknown"}`, flags);
521
554
  if (typeof progress.progress?.progressPct === "number") {
522
555
  logLine(`Progress: ${progress.progress.progressPct}%`, flags);
523
556
  }
524
- if (binding.lastScanUrl) {
557
+ if (binding.lastScanUrl && (!requestedScanId || requestedScanId === binding.lastScanId)) {
525
558
  logLine(`View: ${binding.lastScanUrl}`, flags);
526
559
  }
527
- return { binding, progress };
560
+ return { binding, scanId, progress };
528
561
  }
529
562
  export async function commandConnect(client, cwd, flags, provider) {
530
563
  const me = await ensureAuthenticated(client, flags);
@@ -923,7 +956,6 @@ export async function commandFindingComment(client, cwd, flags, requestedFinding
923
956
  if (!trimmedContent) {
924
957
  throw new Error("Usage: apex findings comment <finding-id|finding-identifier> --content \"<markdown>\"");
925
958
  }
926
- requireCantinaAuthToken();
927
959
  const currentBinding = await loadWorkspaceBinding(cwd);
928
960
  const needsWorkspaceResolution = !isFindingUuid(findingRef);
929
961
  const binding = needsWorkspaceResolution
@@ -998,7 +1030,6 @@ export async function commandFindingFeedback(client, cwd, flags, params) {
998
1030
  if (fixPrUrls.length > 0 && labels?.[0] !== "fixed") {
999
1031
  throw new Error("Fix PR URLs require the fixed feedback label.");
1000
1032
  }
1001
- requireCantinaAuthToken();
1002
1033
  const currentBinding = await loadWorkspaceBinding(cwd);
1003
1034
  const needsWorkspaceResolution = !isFindingUuid(findingRef);
1004
1035
  const binding = needsWorkspaceResolution
@@ -1063,7 +1094,6 @@ export async function commandFindingFeedback(client, cwd, flags, params) {
1063
1094
  }
1064
1095
  export async function commandFindingFixReview(client, cwd, flags, requestedFindingRef) {
1065
1096
  const findingRef = normalizeFindingRefInput(requestedFindingRef);
1066
- requireCantinaAuthToken();
1067
1097
  const currentBinding = await loadWorkspaceBinding(cwd);
1068
1098
  const needsWorkspaceResolution = !isFindingUuid(findingRef);
1069
1099
  const binding = needsWorkspaceResolution
package/dist/findings.js CHANGED
@@ -2,7 +2,7 @@ import { fetchWorkspaceScans, getScanDisplayId, matchesScanId, } from "./scan.js
2
2
  import { ApiError } from "./api-client.js";
3
3
  const FINDING_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4
4
  const CANTINA_AUTH_TOKEN_ENV = "CANTINA_AUTH_TOKEN";
5
- const CANTINA_AUTH_TOKEN_ERROR = "Finding comments, feedback, and fix review scans currently require `CANTINA_AUTH_TOKEN` from a logged-in Cantina/Apex browser session. Export `CANTINA_AUTH_TOKEN` and retry.";
5
+ const FINDING_WRITE_AUTH_ERROR = "Finding comments, feedback, and fix review scans require Apex authentication. Run `apex login` or use MCP `apex-auth-start` followed by `apex-auth-wait`, then retry.";
6
6
  function parsePositiveInt(value) {
7
7
  if (!value)
8
8
  return null;
@@ -32,11 +32,8 @@ function normalizeCantinaAuthToken(value) {
32
32
  }
33
33
  return token.length > 0 ? token : null;
34
34
  }
35
- export function requireCantinaAuthToken() {
35
+ export function getCantinaAuthToken() {
36
36
  const token = normalizeCantinaAuthToken(process.env[CANTINA_AUTH_TOKEN_ENV]);
37
- if (!token) {
38
- throw new Error(CANTINA_AUTH_TOKEN_ERROR);
39
- }
40
37
  return token;
41
38
  }
42
39
  export function isFindingUuid(value) {
@@ -63,22 +60,68 @@ function matchesFindingRef(finding, findingRef) {
63
60
  .some((value) => value.trim().toLowerCase() === normalizedRef);
64
61
  }
65
62
  async function requestFindingAppRoute(client, path, options = {}) {
66
- const token = requireCantinaAuthToken();
67
63
  const baseUrl = await client.getBaseUrl();
68
- const headers = new Headers(options.headers ?? {});
69
- // These routes still authenticate through the web app cookie, not Apex CLI bearer auth.
70
- headers.set("Cookie", `auth_token=${token}`);
71
- headers.set("Accept", "application/json");
72
- if (options.json !== undefined) {
73
- headers.set("Content-Type", "application/json");
74
- }
75
- const response = await fetch(new URL(path, baseUrl), {
76
- ...options,
77
- headers,
78
- body: options.json !== undefined ? JSON.stringify(options.json) : options.body,
79
- });
80
- const body = await parseWebRouteResponse(response);
64
+ const accessToken = await client.getAccessToken();
65
+ const legacyCookieToken = getCantinaAuthToken();
66
+ let attemptedBearerAuth = false;
67
+ if (!accessToken && !legacyCookieToken) {
68
+ throw new Error(FINDING_WRITE_AUTH_ERROR);
69
+ }
70
+ const send = async (auth) => {
71
+ const headers = new Headers(options.headers ?? {});
72
+ headers.set("Accept", "application/json");
73
+ if (options.json !== undefined) {
74
+ headers.set("Content-Type", "application/json");
75
+ }
76
+ if (auth.type === "bearer") {
77
+ headers.set("Authorization", `Bearer ${auth.token}`);
78
+ }
79
+ else {
80
+ headers.set("Cookie", `auth_token=${auth.token}`);
81
+ }
82
+ return fetch(new URL(path, baseUrl), {
83
+ ...options,
84
+ headers,
85
+ body: options.json !== undefined ? JSON.stringify(options.json) : options.body,
86
+ });
87
+ };
88
+ const sendAndParse = async (auth) => {
89
+ if (auth.type === "bearer") {
90
+ attemptedBearerAuth = true;
91
+ }
92
+ const response = await send(auth);
93
+ const body = await parseWebRouteResponse(response);
94
+ return { response, body };
95
+ };
96
+ const primaryAuth = accessToken
97
+ ? { type: "bearer", token: accessToken }
98
+ : { type: "cookie", token: legacyCookieToken };
99
+ let { response, body } = await sendAndParse(primaryAuth);
100
+ if (!response.ok && response.status === 401 && primaryAuth.type === "bearer") {
101
+ const refreshedToken = await client
102
+ .refreshSession()
103
+ .then((credentials) => credentials?.accessToken ?? null)
104
+ .catch(() => null);
105
+ if (refreshedToken) {
106
+ ({ response, body } = await sendAndParse({
107
+ type: "bearer",
108
+ token: refreshedToken,
109
+ }));
110
+ }
111
+ }
112
+ if (!response.ok && response.status === 401 && primaryAuth.type === "bearer" && legacyCookieToken) {
113
+ ({ response, body } = await sendAndParse({
114
+ type: "cookie",
115
+ token: legacyCookieToken,
116
+ }));
117
+ }
81
118
  if (!response.ok) {
119
+ if (response.status === 401) {
120
+ if (attemptedBearerAuth) {
121
+ await client.clearCredentials();
122
+ }
123
+ throw new Error(FINDING_WRITE_AUTH_ERROR);
124
+ }
82
125
  throw new ApiError(`Request failed with ${response.status}`, response.status, body);
83
126
  }
84
127
  return body;
package/dist/help.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export const CLI_HELP_TEXT = `Usage:
2
2
  apex Open the interactive Apex shell
3
- apex credits Show scan credits and audit scan entitlements for the active company
3
+ apex credits Show scan credits plus audit and fix review scan entitlements for the active company
4
4
  apex scan Create or resolve a workspace for this directory and start a scan
5
5
  apex scans List scans for the current workspace binding
6
6
  apex findings List findings for the latest or selected scan
@@ -16,12 +16,13 @@ export const CLI_HELP_TEXT = `Usage:
16
16
  apex workspace use <workspace-name|workspace-prefix|workspace-id>
17
17
  Bind this directory to an existing Apex workspace
18
18
  apex cancel-scan [scan-id] Cancel a running scan
19
- apex status Show the latest scan progress for this directory
19
+ apex status Show progress for the latest or selected scan
20
20
  apex doctor Validate auth, repos, connections, and workspace binding
21
21
  apex login Sign in to Apex
22
22
  apex logout Sign out locally
23
23
  apex mcp Start the Apex MCP server over stdio
24
- apex setup [all|codex|claude] Configure Apex for Codex and Claude Code
24
+ apex setup [all|codex|claude|copilot]
25
+ Configure Apex for Codex, Claude Code, and GitHub Copilot CLI
25
26
  apex update Update the local Apex CLI install
26
27
  apex connect github Open the GitHub connection flow
27
28
  apex connect gitlab Open the GitLab connection flow
@@ -29,7 +30,7 @@ export const CLI_HELP_TEXT = `Usage:
29
30
  Flags:
30
31
  --company <id-or-handle> Choose the Apex company to use
31
32
  --workspace-name <name> Set the Apex workspace name for this directory
32
- --scan <scan-id> Select a specific scan for findings or export
33
+ --scan <scan-id> Select a specific scan for status, findings, or export
33
34
  --status valid|invalid Set the finding feedback status explicitly
34
35
  --content <markdown> Supply comment content for apex findings comment
35
36
  --comment <markdown> Supply rationale for apex findings feedback
@@ -61,7 +62,7 @@ Tips:
61
62
  PR scans require --mode pr and at least one --pr selection for a GitHub repository.
62
63
  audit is the current scan mode for audit scans; ultra remains accepted as a legacy alias.
63
64
  apex workspace use accepts a workspace name, prefix, or ID.
64
- Finding comments, feedback, and fix review scans currently require CANTINA_AUTH_TOKEN from a logged-in Cantina/Apex browser session.
65
+ Finding comments, feedback, and fix review scans use the same Apex login credentials as other CLI commands.
65
66
  Invalid finding feedback requires --dismissal-reason.
66
67
  Fix review scans require valid feedback with --label fixed and at least one --fix-pr-url, then apex findings fix-review.
67
68
  Finding identifiers such as KERN2-25 resolve against the selected scan; pass --scan or use the finding UUID directly when needed.
@@ -71,7 +72,7 @@ Tips:
71
72
  export const SHELL_HELP_TEXT = `Press Tab to autocomplete commands and common arguments.
72
73
 
73
74
  Commands:
74
- /credits Show scan credits and audit scan entitlements for the active company
75
+ /credits Show scan credits plus audit and fix review scan entitlements for the active company
75
76
  /scan [standard|audit] Start a new Apex scan for this workspace
76
77
  /scan pr <pr-number> Start a PR scan for this workspace
77
78
  /scans List scans for this workspace
@@ -87,7 +88,7 @@ Commands:
87
88
  /export [scan-id] Export findings for the latest or selected scan
88
89
  /workspaces List accessible workspaces for the active company
89
90
  /cancel-scan [scan-id] Cancel a running or most recent scan
90
- /status Show progress for the most recent scan
91
+ /status [scan-id] Show progress for the most recent or selected scan
91
92
  /doctor Validate auth, repos, connections, and workspace binding
92
93
  /update Update the local Apex CLI install and exit the shell
93
94
  /logout Sign out locally and exit the shell
@@ -107,7 +108,7 @@ Tips:
107
108
  PR scans require a GitHub PR number and provider-backed repository access.
108
109
  audit is the current scan mode for audit scans; ultra remains accepted as a legacy alias.
109
110
  /workspace use accepts a workspace name, prefix, or ID.
110
- /findings comment, /findings feedback, and /findings fix-review require CANTINA_AUTH_TOKEN in the shell environment.
111
+ /findings comment, /findings feedback, and /findings fix-review use the current Apex login.
111
112
  Invalid finding feedback requires a dismissal reason.
112
113
  Fix review scans require fixed valid feedback with a Fix PR URL. Use scripted CLI or MCP for attaching Fix PR URLs.
113
114
  Use scripted CLI flags for advanced feedback options such as suggested severity or dismissal reason.
package/dist/mcp.js CHANGED
@@ -22,6 +22,7 @@ Suggested workflow:
22
22
  Notes:
23
23
  - Pass cwd explicitly for repository-specific operations.
24
24
  - Tool calls are always non-interactive and never auto-open a browser.
25
+ - Finding write tools use the same Apex device-login credentials as read tools.
25
26
  - Use mode "audit" for audit scans; "ultra" remains accepted as a legacy alias. Use mode "pr" for GitHub pull request scans.
26
27
  - Doctor reports whether Apex will use remote materialization or a local snapshot upload for each source.`;
27
28
  const WORKSPACE_BINDING_ERROR = "This directory is not bound to an Apex workspace yet. Run `apex workspaces` to list workspace names, prefixes, and IDs, then bind one with `apex workspace use \"<workspace name>\"`, or run `apex scan` to create or resolve one automatically.";
@@ -270,7 +271,7 @@ function registerTools(server) {
270
271
  }, (value) => `Apex doctor completed for ${String(value.cwd)}.`));
271
272
  server.registerTool("apex-credits", {
272
273
  title: "Get Apex Credits",
273
- description: "Show scan credits and audit scan entitlements for the active or selected company.",
274
+ description: "Show scan credits plus audit and fix review scan entitlements for the active or selected company.",
274
275
  inputSchema: {
275
276
  cwd: z.string().optional(),
276
277
  company: z.string().optional(),
@@ -380,15 +381,16 @@ function registerTools(server) {
380
381
  }, (value) => `Started an Apex scan for ${String(value.cwd)}.`));
381
382
  server.registerTool("apex-status", {
382
383
  title: "Get Apex Scan Status",
383
- description: "Show progress for the most recent Apex scan in a directory.",
384
+ description: "Show progress for the most recent or selected Apex scan in a directory.",
384
385
  inputSchema: {
385
386
  cwd: z.string().optional(),
387
+ scanId: z.string().optional(),
386
388
  },
387
- }, async ({ cwd }) => runTool("apex-status", async () => {
389
+ }, async ({ cwd, scanId }) => runTool("apex-status", async () => {
388
390
  const client = new ApexApiClient();
389
391
  await requireAuthenticated(client);
390
392
  const targetCwd = resolveCwd(cwd);
391
- const payload = await commandStatus(client, targetCwd, buildFlags({}));
393
+ const payload = await commandStatus(client, targetCwd, buildFlags({ scanId }));
392
394
  return {
393
395
  cwd: targetCwd,
394
396
  ...payload,
@@ -450,7 +452,7 @@ function registerTools(server) {
450
452
  }, (value) => `Fetched Apex findings for ${String(value.cwd)}.`));
451
453
  server.registerTool("apex-finding-comment", {
452
454
  title: "Add Apex Finding Comment",
453
- description: "Add a comment or note to an Apex finding. Requires CANTINA_AUTH_TOKEN in the MCP server environment.",
455
+ description: "Add a comment or note to an Apex finding using the current Apex login.",
454
456
  inputSchema: {
455
457
  cwd: z.string().optional(),
456
458
  findingRef: z.string(),
@@ -471,7 +473,7 @@ function registerTools(server) {
471
473
  }, (value) => `Added a finding comment for ${String(value.findingRef)}.`));
472
474
  server.registerTool("apex-finding-feedback", {
473
475
  title: "Leave Apex Finding Feedback",
474
- description: "Leave valid or invalid feedback on an Apex finding. To attach a fix PR, send status valid with label fixed and fixPrUrls. Requires CANTINA_AUTH_TOKEN in the MCP server environment.",
476
+ description: "Leave valid or invalid feedback on an Apex finding using the current Apex login. To attach a fix PR, send status valid with label fixed and fixPrUrls.",
475
477
  inputSchema: {
476
478
  cwd: z.string().optional(),
477
479
  findingRef: z.string(),
@@ -509,7 +511,7 @@ function registerTools(server) {
509
511
  "finding"))} feedback for ${String(value.findingRef)}.`));
510
512
  server.registerTool("apex-finding-fix-review", {
511
513
  title: "Start Apex Finding Fix Review Scan",
512
- description: "Start a fix review scan for a finding after fixed feedback with one or more Fix PR URLs has been saved. Requires CANTINA_AUTH_TOKEN in the MCP server environment.",
514
+ description: "Start a fix review scan for a finding after fixed feedback with one or more Fix PR URLs has been saved.",
513
515
  inputSchema: {
514
516
  cwd: z.string().optional(),
515
517
  findingRef: z.string(),
package/dist/scan.js CHANGED
@@ -121,6 +121,33 @@ export function normalizeWorkspaceScans(payload) {
121
121
  export function isActiveScanStatus(status) {
122
122
  return status ? ACTIVE_SCAN_STATUSES.has(status.trim().toLowerCase()) : false;
123
123
  }
124
+ function mergeScanProgress(scan, progress) {
125
+ const status = readString(progress.progress?.status) ?? scan.status;
126
+ const startedAt = progress.progress?.startedAt ?? scan.startedAt;
127
+ const finishedAt = !isActiveScanStatus(status) && !scan.finishedAt
128
+ ? scan.updatedAt
129
+ : scan.finishedAt;
130
+ return {
131
+ ...scan,
132
+ kernelScanId: progress.kernelScanId ?? scan.kernelScanId,
133
+ status,
134
+ startedAt,
135
+ finishedAt,
136
+ };
137
+ }
138
+ async function reconcileActiveScanStatus(client, scan) {
139
+ if (!isActiveScanStatus(scan.status)) {
140
+ return scan;
141
+ }
142
+ let progress;
143
+ try {
144
+ progress = await fetchScanProgress(client, getScanDisplayId(scan));
145
+ }
146
+ catch {
147
+ return scan;
148
+ }
149
+ return mergeScanProgress(scan, progress);
150
+ }
124
151
  export function findMostRelevantActiveScan(scans) {
125
152
  return scans.find((scan) => isActiveScanStatus(scan.status)) ?? null;
126
153
  }
@@ -148,7 +175,11 @@ export async function fetchWorkspaceScans(client, workspaceId) {
148
175
  { path: `/api/workspaces/${encodedWorkspaceId}/scans` },
149
176
  { path: `/api/scans?workspaceId=${encodedWorkspaceId}` },
150
177
  ]);
151
- return normalizeWorkspaceScans(payload);
178
+ const scans = normalizeWorkspaceScans(payload);
179
+ return Promise.all(scans.map((scan) => reconcileActiveScanStatus(client, scan)));
180
+ }
181
+ export async function fetchScanProgress(client, scanId) {
182
+ return client.request(`/api/cli/v1/scans/${encodeURIComponent(scanId)}/progress`);
152
183
  }
153
184
  export async function cancelScan(client, scanId) {
154
185
  const encodedScanId = encodeURIComponent(scanId);
package/dist/setup.js CHANGED
@@ -10,6 +10,7 @@ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)),
10
10
  const CODEX_SKILL_NAME = "apex-cli";
11
11
  const MCP_SERVER_NAME = "apex";
12
12
  const execFile = promisify(execFileCallback);
13
+ const SETUP_CLIENTS = ["codex", "claude", "copilot"];
13
14
  function quoteShellArg(value) {
14
15
  return /[^A-Za-z0-9_./:-]/.test(value)
15
16
  ? `'${value.replace(/'/g, `'\\''`)}'`
@@ -73,9 +74,28 @@ function getCodexHome() {
73
74
  }
74
75
  return path.join(os.homedir(), ".codex");
75
76
  }
77
+ function getCopilotHome() {
78
+ const configured = process.env.COPILOT_HOME?.trim();
79
+ if (configured) {
80
+ return configured;
81
+ }
82
+ return path.join(os.homedir(), ".copilot");
83
+ }
76
84
  function normalizeArgs(value) {
77
85
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
78
86
  }
87
+ function normalizeTools(value) {
88
+ if (Array.isArray(value)) {
89
+ return value.filter((item) => typeof item === "string");
90
+ }
91
+ if (typeof value === "string") {
92
+ return value
93
+ .split(",")
94
+ .map((item) => item.trim())
95
+ .filter(Boolean);
96
+ }
97
+ return [];
98
+ }
79
99
  function codexConfigMatches(existing, launch) {
80
100
  if (!existing?.transport) {
81
101
  return false;
@@ -92,6 +112,17 @@ function claudeConfigMatches(existing, launch) {
92
112
  JSON.stringify(normalizeArgs(existing.args)) === JSON.stringify(launch.args) &&
93
113
  normalizedEnv === 0);
94
114
  }
115
+ function copilotConfigMatches(existing, launch) {
116
+ const env = existing?.env;
117
+ const normalizedEnv = env && typeof env === "object" && !Array.isArray(env) ? Object.keys(env).length : 0;
118
+ const normalizedTools = normalizeTools(existing?.tools);
119
+ return ((existing?.type === "stdio" || existing?.type === "local") &&
120
+ existing.command === launch.command &&
121
+ JSON.stringify(normalizeArgs(existing.args)) === JSON.stringify(launch.args) &&
122
+ normalizedEnv === 0 &&
123
+ normalizedTools.length === 1 &&
124
+ normalizedTools[0] === "*");
125
+ }
95
126
  async function writeManagedFile(filePath, content) {
96
127
  const current = await readTextFile(filePath);
97
128
  if (current === content) {
@@ -127,6 +158,20 @@ async function readClaudeUserMcpConfig() {
127
158
  return null;
128
159
  }
129
160
  }
161
+ async function readCopilotUserMcpConfig() {
162
+ const configPath = path.join(getCopilotHome(), "mcp-config.json");
163
+ const raw = await readTextFile(configPath);
164
+ if (!raw) {
165
+ return null;
166
+ }
167
+ try {
168
+ const parsed = JSON.parse(raw);
169
+ return parsed.mcpServers?.[MCP_SERVER_NAME] ?? null;
170
+ }
171
+ catch {
172
+ return null;
173
+ }
174
+ }
130
175
  async function configureCodex(launch) {
131
176
  const existing = await readCodexMcpConfig();
132
177
  const mcpStatus = existing === null
@@ -197,6 +242,49 @@ async function configureClaude(cwd, launch) {
197
242
  },
198
243
  ];
199
244
  }
245
+ async function configureCopilot(launch) {
246
+ const existing = await readCopilotUserMcpConfig();
247
+ const mcpStatus = existing === null
248
+ ? "installed"
249
+ : copilotConfigMatches(existing, launch)
250
+ ? "unchanged"
251
+ : "updated";
252
+ if (existing) {
253
+ await execText("copilot", ["mcp", "remove", MCP_SERVER_NAME]);
254
+ }
255
+ await execText("copilot", [
256
+ "mcp",
257
+ "add",
258
+ MCP_SERVER_NAME,
259
+ "--type",
260
+ "stdio",
261
+ "--tools",
262
+ "*",
263
+ "--",
264
+ launch.command,
265
+ ...launch.args,
266
+ ]);
267
+ const skillSource = path.join(PACKAGE_ROOT, "skills", CODEX_SKILL_NAME, "SKILL.md");
268
+ const skillTarget = path.join(getCopilotHome(), "skills", CODEX_SKILL_NAME, "SKILL.md");
269
+ const skillContent = await readFile(skillSource, "utf8");
270
+ const skillStatus = await writeManagedFile(skillTarget, skillContent);
271
+ return [
272
+ {
273
+ client: "copilot",
274
+ kind: "mcp",
275
+ status: mcpStatus,
276
+ path: path.join(getCopilotHome(), "mcp-config.json"),
277
+ detail: `Registered ${MCP_SERVER_NAME} -> ${launch.label} in GitHub Copilot CLI user config`,
278
+ },
279
+ {
280
+ client: "copilot",
281
+ kind: "skill",
282
+ status: skillStatus,
283
+ path: skillTarget,
284
+ detail: "Installed the Apex GitHub Copilot CLI skill",
285
+ },
286
+ ];
287
+ }
200
288
  function summarizeStep(step) {
201
289
  const prefix = `${step.client} ${step.kind}`;
202
290
  if (step.path) {
@@ -204,29 +292,51 @@ function summarizeStep(step) {
204
292
  }
205
293
  return `${prefix}: ${step.status}`;
206
294
  }
295
+ function isSetupClient(value) {
296
+ return SETUP_CLIENTS.includes(value);
297
+ }
298
+ function displayClientName(client) {
299
+ switch (client) {
300
+ case "codex":
301
+ return "Codex";
302
+ case "claude":
303
+ return "Claude Code";
304
+ case "copilot":
305
+ return "GitHub Copilot CLI";
306
+ }
307
+ }
308
+ function isMissingClientError(error) {
309
+ return error instanceof Error && /spawn (codex|claude|copilot) ENOENT/i.test(error.message);
310
+ }
311
+ async function configureClient(client, cwd, launch) {
312
+ switch (client) {
313
+ case "codex":
314
+ return configureCodex(launch);
315
+ case "claude":
316
+ return configureClaude(cwd, launch);
317
+ case "copilot":
318
+ return configureCopilot(launch);
319
+ }
320
+ }
207
321
  export async function commandSetup(cwd, flags, requestedTarget, packageRoot = PACKAGE_ROOT) {
208
- if (requestedTarget !== null &&
209
- requestedTarget !== "all" &&
210
- requestedTarget !== "codex" &&
211
- requestedTarget !== "claude") {
212
- throw new Error("Usage: apex setup [all|codex|claude]");
322
+ if (requestedTarget !== null && requestedTarget !== "all" && !isSetupClient(requestedTarget)) {
323
+ throw new Error("Usage: apex setup [all|codex|claude|copilot]");
213
324
  }
214
325
  const target = requestedTarget ?? "all";
215
326
  const launch = await resolveMcpLaunchSpec(packageRoot);
216
327
  const steps = [];
217
- const requestedClients = target === "all" ? ["codex", "claude"] : [target];
328
+ const requestedClients = target === "all" ? [...SETUP_CLIENTS] : [target];
218
329
  let configuredClients = 0;
219
330
  for (const client of requestedClients) {
220
331
  try {
221
- const clientSteps = client === "codex" ? await configureCodex(launch) : await configureClaude(cwd, launch);
332
+ const clientSteps = await configureClient(client, cwd, launch);
222
333
  steps.push(...clientSteps);
223
334
  configuredClients += 1;
224
335
  }
225
336
  catch (error) {
226
- const missingClient = error instanceof Error &&
227
- /spawn (codex|claude) ENOENT/i.test(error.message);
337
+ const missingClient = isMissingClientError(error);
228
338
  if (missingClient && target !== "all") {
229
- throw new Error(`${client === "codex" ? "Codex" : "Claude Code"} is not installed on this machine. Install it first, then re-run \`apex setup ${client}\`.`);
339
+ throw new Error(`${displayClientName(client)} is not installed on this machine. Install it first, then re-run \`apex setup ${client}\`.`);
230
340
  }
231
341
  if (!missingClient) {
232
342
  throw error;
@@ -236,19 +346,19 @@ export async function commandSetup(cwd, flags, requestedTarget, packageRoot = PA
236
346
  kind: "mcp",
237
347
  status: "skipped",
238
348
  path: null,
239
- detail: `${client} is not installed on this machine`,
349
+ detail: `${displayClientName(client)} is not installed on this machine`,
240
350
  });
241
351
  steps.push({
242
352
  client,
243
353
  kind: "skill",
244
354
  status: "skipped",
245
355
  path: null,
246
- detail: `${client} is not installed on this machine`,
356
+ detail: `${displayClientName(client)} is not installed on this machine`,
247
357
  });
248
358
  }
249
359
  }
250
360
  if (configuredClients === 0) {
251
- throw new Error("Neither Codex nor Claude Code is installed. Install one of them first, then re-run `apex setup`.");
361
+ throw new Error("Neither Codex, Claude Code, nor GitHub Copilot CLI is installed. Install one of them first, then re-run `apex setup`.");
252
362
  }
253
363
  const payload = {
254
364
  target,
@@ -271,5 +381,8 @@ export async function commandSetup(cwd, flags, requestedTarget, packageRoot = PA
271
381
  if (requestedClients.includes("claude")) {
272
382
  logLine("Re-run `apex setup claude` in each repository where you want the Claude project skill.", flags);
273
383
  }
384
+ if (requestedClients.includes("copilot")) {
385
+ logLine("Restart GitHub Copilot CLI or run `/skills reload` to pick up a newly installed or updated skill.", flags);
386
+ }
274
387
  return payload;
275
388
  }
package/dist/shell.js CHANGED
@@ -208,7 +208,13 @@ async function runShellCommand(client, cwd, parsed, shellFlags, session) {
208
208
  };
209
209
  }
210
210
  case "status":
211
- await commandStatus(client, cwd, shellFlags);
211
+ if (parsed.args.length > 1) {
212
+ process.stderr.write("Usage: /status [scan-id]\n");
213
+ return {};
214
+ }
215
+ await commandStatus(client, cwd, parsed.args[0]
216
+ ? withFlag(shellFlags, "scan", parsed.args[0])
217
+ : shellFlags);
212
218
  return {};
213
219
  case "scans":
214
220
  await commandScans(client, cwd, shellFlags);
package/dist/update.js CHANGED
@@ -456,7 +456,7 @@ export async function commandUpdate(flags, packageRoot = PACKAGE_ROOT) {
456
456
  printJson(payload);
457
457
  }
458
458
  else {
459
- logLine("Apex CLI updated. Re-run `apex` to use the latest version. Re-run `apex setup` to refresh copied Codex or Claude skill files.", flags);
459
+ logLine("Apex CLI updated. Re-run `apex` to use the latest version. Re-run `apex setup` to refresh copied Codex, Claude Code, or GitHub Copilot CLI skill files.", flags);
460
460
  }
461
461
  return payload;
462
462
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cantinasecurity/apex-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.10",
4
4
  "description": "Standalone CLI and MCP server for Apex.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -5,7 +5,7 @@ description: Use when a user wants to start Apex scans, inspect findings, bind w
5
5
 
6
6
  # Apex CLI
7
7
 
8
- This skill is bundled with Apex CLI and can be installed into Codex with `apex setup codex`.
8
+ This skill is bundled with Apex CLI and can be installed into Codex with `apex setup codex` or GitHub Copilot CLI with `apex setup copilot`.
9
9
 
10
10
  Prefer the Apex MCP tools over running `apex` in the shell when the server is available.
11
11
 
@@ -24,7 +24,7 @@ Workflow:
24
24
  Guidelines:
25
25
 
26
26
  - Do not rely on interactive CLI prompts. The MCP tools are intentionally non-interactive.
27
- - `apex-credits` reports standard credits and audit scan entitlements when Bedrock returns both balances.
27
+ - `apex-credits` reports standard credits plus audit and fix review scan entitlements when Bedrock returns those balances.
28
28
  - `apex-scan` scans the provided `cwd` by default; pass `repoPaths` only when the user asks to scan explicit alternate roots.
29
29
  - `apex-doctor` reports whether Apex will use remote materialization or a local snapshot upload for each selected source.
30
30
  - Apex can scan plain local directories and dirty git worktrees without provider connections by using local snapshot uploads.
@@ -33,8 +33,9 @@ Guidelines:
33
33
  - `apex-workspace-use` accepts a workspace name, prefix, or ID.
34
34
  - Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
35
35
  - Use `force: true` on `apex-scan` only when the user explicitly wants to replace or overlap an active scan.
36
+ - When checking a scan that is not the workspace binding's latest scan, pass `scanId` to `apex-status`; use `apex-scans` first if you need to discover scan IDs.
36
37
  - Prefer `apex-findings` for quick inspection and `apex-export-findings` when the user needs a file artifact.
37
- - Finding comments, feedback, and fix review scan starts currently require `CANTINA_AUTH_TOKEN` in the MCP server environment because those writes go through the Cantina web-app routes instead of the Apex CLI bearer-token routes.
38
+ - Finding comments, feedback, and fix review scan starts use the same Apex device-login credentials as read tools. If a write tool reports missing auth, re-run `apex-auth-status` and complete `apex-auth-start` / `apex-auth-wait` instead of asking for browser cookies or auth tokens.
38
39
  - Invalid finding feedback requires `dismissalReason`; valid feedback can include `suggestedSeverity`, including `extreme`.
39
40
  - Fix PR callback feedback requires valid feedback with `labels: ["fixed"]` and `fixPrUrls`; start the fix review scan with `apex-finding-fix-review` after saving that feedback.
40
41
  - Finding identifiers such as `KERN2-25` resolve against the selected or latest scan for the current workspace binding. Pass an explicit scan when needed, or use the finding UUID directly.