@cantinasecurity/apex-cli 0.1.6 → 0.1.9
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/.claude/skills/apex-cli/SKILL.md +4 -3
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.mcp.claude.json +1 -1
- package/.mcp.codex.json +1 -1
- package/MARKETPLACE.md +1 -1
- package/README.md +8 -11
- package/dist/api-client.js +4 -0
- package/dist/commands.js +42 -12
- package/dist/findings.js +62 -19
- package/dist/help.js +7 -7
- package/dist/mcp.js +9 -7
- package/dist/scan.js +32 -1
- package/dist/shell.js +7 -1
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +3 -2
|
@@ -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
|
|
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
|
-
-
|
|
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.
|
|
9
|
+
"version": "0.1.9"
|
|
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.
|
|
17
|
+
"version": "0.1.9"
|
|
18
18
|
},
|
|
19
19
|
"description": "Run Apex security scans and review findings from Claude Code.",
|
|
20
|
-
"version": "0.1.
|
|
20
|
+
"version": "0.1.9",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "Cantina",
|
|
23
23
|
"email": "support@cantina.xyz"
|
package/.mcp.claude.json
CHANGED
package/.mcp.codex.json
CHANGED
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.
|
|
16
|
+
npx -y -p @cantinasecurity/apex-cli@0.1.9 apex-mcp
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
That keeps marketplace installs independent of a user's global `apex` install.
|
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ Supported shell commands:
|
|
|
109
109
|
- `/export [scan-id]`
|
|
110
110
|
- `/workspaces`
|
|
111
111
|
- `/cancel-scan [scan-id]`
|
|
112
|
-
- `/status`
|
|
112
|
+
- `/status [scan-id]`
|
|
113
113
|
- `/doctor`
|
|
114
114
|
- `/update`
|
|
115
115
|
- `/logout`
|
|
@@ -142,7 +142,7 @@ Supported shell commands:
|
|
|
142
142
|
- `apex workspace`
|
|
143
143
|
- `apex workspace use <workspace-name|workspace-prefix|workspace-id>`
|
|
144
144
|
- `apex cancel-scan [scan-id]`
|
|
145
|
-
- `apex status`
|
|
145
|
+
- `apex status [--scan <scan-id>]`
|
|
146
146
|
- `apex doctor`
|
|
147
147
|
- `apex login`
|
|
148
148
|
- `apex logout`
|
|
@@ -156,7 +156,7 @@ Helpful workspace flags:
|
|
|
156
156
|
- `--company <id-or-handle>` to choose the Apex company when more than one is available
|
|
157
157
|
- `--workspace-name <name>` to set the Apex workspace name for this directory
|
|
158
158
|
|
|
159
|
-
`apex credits` shows standard scan credits plus audit scan entitlements when the server returns them.
|
|
159
|
+
`apex credits` shows standard scan credits plus audit and fix review scan entitlements when the server returns them.
|
|
160
160
|
|
|
161
161
|
## Finding Review Feedback
|
|
162
162
|
|
|
@@ -174,7 +174,7 @@ Finding review collaboration now has explicit write commands:
|
|
|
174
174
|
|
|
175
175
|
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
176
|
|
|
177
|
-
Finding comments, valid/invalid feedback, and fix review scan starts
|
|
177
|
+
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
178
|
|
|
179
179
|
Invalid feedback requires a dismissal reason. Valid feedback can include `--suggested-severity extreme|critical|high|medium|low|informational`.
|
|
180
180
|
|
|
@@ -185,10 +185,7 @@ Fix review scans use a two-step callback flow for agents that create a PR outsid
|
|
|
185
185
|
|
|
186
186
|
The matching MCP flow is `apex-finding-feedback` with `status: "valid"`, `labels: ["fixed"]`, and `fixPrUrls`, followed by `apex-finding-fix-review`.
|
|
187
187
|
|
|
188
|
-
|
|
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`
|
|
188
|
+
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
189
|
|
|
193
190
|
## Local Source Scans
|
|
194
191
|
|
|
@@ -300,7 +297,7 @@ The npm package also includes marketplace-ready plugin artifacts:
|
|
|
300
297
|
- `.claude-plugin/plugin.json` and `.mcp.claude.json` for Claude Code plugin installs
|
|
301
298
|
- `.claude-plugin/marketplace.json` for a Claude marketplace entry backed by the public npm package
|
|
302
299
|
|
|
303
|
-
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.
|
|
300
|
+
These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.9 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
|
|
304
301
|
|
|
305
302
|
The repository also includes `.agents/plugins/marketplace.json` for local Codex marketplace testing from a checkout.
|
|
306
303
|
|
|
@@ -328,11 +325,11 @@ The CLI uses the Apex `/api/cli/v2/**` local-source routes for scan planning and
|
|
|
328
325
|
- `~/.config/apex/credentials.json`
|
|
329
326
|
- `.apex/workspace.json`
|
|
330
327
|
|
|
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`.
|
|
328
|
+
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
329
|
|
|
333
330
|
To move between existing Apex workspaces from the CLI:
|
|
334
331
|
|
|
335
332
|
1. Run `apex workspaces` to list the workspaces available to your active company.
|
|
336
333
|
2. Run `apex workspace use <workspace-name|workspace-prefix|workspace-id>` to bind the current directory.
|
|
337
334
|
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.
|
|
335
|
+
4. Use `apex scans`, `apex status --scan <scan-id>`, `apex findings`, and `apex export findings` against that binding.
|
package/dist/api-client.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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,7 +16,7 @@ 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
|
|
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
|
|
@@ -29,7 +29,7 @@ export const CLI_HELP_TEXT = `Usage:
|
|
|
29
29
|
Flags:
|
|
30
30
|
--company <id-or-handle> Choose the Apex company to use
|
|
31
31
|
--workspace-name <name> Set the Apex workspace name for this directory
|
|
32
|
-
--scan <scan-id> Select a specific scan for findings or export
|
|
32
|
+
--scan <scan-id> Select a specific scan for status, findings, or export
|
|
33
33
|
--status valid|invalid Set the finding feedback status explicitly
|
|
34
34
|
--content <markdown> Supply comment content for apex findings comment
|
|
35
35
|
--comment <markdown> Supply rationale for apex findings feedback
|
|
@@ -61,7 +61,7 @@ Tips:
|
|
|
61
61
|
PR scans require --mode pr and at least one --pr selection for a GitHub repository.
|
|
62
62
|
audit is the current scan mode for audit scans; ultra remains accepted as a legacy alias.
|
|
63
63
|
apex workspace use accepts a workspace name, prefix, or ID.
|
|
64
|
-
Finding comments, feedback, and fix review scans
|
|
64
|
+
Finding comments, feedback, and fix review scans use the same Apex login credentials as other CLI commands.
|
|
65
65
|
Invalid finding feedback requires --dismissal-reason.
|
|
66
66
|
Fix review scans require valid feedback with --label fixed and at least one --fix-pr-url, then apex findings fix-review.
|
|
67
67
|
Finding identifiers such as KERN2-25 resolve against the selected scan; pass --scan or use the finding UUID directly when needed.
|
|
@@ -71,7 +71,7 @@ Tips:
|
|
|
71
71
|
export const SHELL_HELP_TEXT = `Press Tab to autocomplete commands and common arguments.
|
|
72
72
|
|
|
73
73
|
Commands:
|
|
74
|
-
/credits Show scan credits
|
|
74
|
+
/credits Show scan credits plus audit and fix review scan entitlements for the active company
|
|
75
75
|
/scan [standard|audit] Start a new Apex scan for this workspace
|
|
76
76
|
/scan pr <pr-number> Start a PR scan for this workspace
|
|
77
77
|
/scans List scans for this workspace
|
|
@@ -87,7 +87,7 @@ Commands:
|
|
|
87
87
|
/export [scan-id] Export findings for the latest or selected scan
|
|
88
88
|
/workspaces List accessible workspaces for the active company
|
|
89
89
|
/cancel-scan [scan-id] Cancel a running or most recent scan
|
|
90
|
-
/status
|
|
90
|
+
/status [scan-id] Show progress for the most recent or selected scan
|
|
91
91
|
/doctor Validate auth, repos, connections, and workspace binding
|
|
92
92
|
/update Update the local Apex CLI install and exit the shell
|
|
93
93
|
/logout Sign out locally and exit the shell
|
|
@@ -107,7 +107,7 @@ Tips:
|
|
|
107
107
|
PR scans require a GitHub PR number and provider-backed repository access.
|
|
108
108
|
audit is the current scan mode for audit scans; ultra remains accepted as a legacy alias.
|
|
109
109
|
/workspace use accepts a workspace name, prefix, or ID.
|
|
110
|
-
/findings comment, /findings feedback, and /findings fix-review
|
|
110
|
+
/findings comment, /findings feedback, and /findings fix-review use the current Apex login.
|
|
111
111
|
Invalid finding feedback requires a dismissal reason.
|
|
112
112
|
Fix review scans require fixed valid feedback with a Fix PR URL. Use scripted CLI or MCP for attaching Fix PR URLs.
|
|
113
113
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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/shell.js
CHANGED
|
@@ -208,7 +208,13 @@ async function runShellCommand(client, cwd, parsed, shellFlags, session) {
|
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
210
|
case "status":
|
|
211
|
-
|
|
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/package.json
CHANGED
package/skills/apex-cli/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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.
|