@cantinasecurity/apex-cli 0.1.0 → 0.1.1

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.
@@ -25,9 +25,11 @@ If the Apex MCP server is not configured, fall back to the local CLI:
25
25
  - Prefer scripted commands over the interactive shell.
26
26
  - Use `--non-interactive` and `--no-open` for automation-friendly CLI calls.
27
27
  - Use `--json` whenever structured output is helpful.
28
+ - `apex credits` reports standard credits and audit scan entitlements when Bedrock returns both balances.
28
29
  - Work from the target repository directory so Apex can resolve `.apex/workspace.json`.
29
30
  - `apex-doctor` reports whether Apex will use remote materialization or a local snapshot upload for each selected source.
30
31
  - Plain local directories and dirty git worktrees can scan through local snapshot uploads without provider access.
32
+ - Audit scans use `--mode audit` in user-facing CLI calls. The legacy `ultra` mode remains accepted as an alias, but audit scans still require provider-backed GitHub or GitLab sources.
31
33
  - `apex-workspace-use` accepts a workspace name, prefix, or ID.
32
34
  - Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
33
35
 
package/README.md CHANGED
@@ -98,7 +98,7 @@ If Apex asks for a workspace name, that is the Apex workspace name for the curre
98
98
  Supported shell commands:
99
99
 
100
100
  - `/credits`
101
- - `/scan [standard|ultra]`
101
+ - `/scan [standard|audit]`
102
102
  - `/scans`
103
103
  - `/findings [scan-id]`
104
104
  - `/export [scan-id]`
@@ -147,6 +147,8 @@ Helpful workspace flags:
147
147
  - `--company <id-or-handle>` to choose the Apex company when more than one is available
148
148
  - `--workspace-name <name>` to set the Apex workspace name for this directory
149
149
 
150
+ `apex credits` shows standard scan credits plus audit scan entitlements when the server returns them.
151
+
150
152
  ## Local Source Scans
151
153
 
152
154
  `apex scan` now works against any local source root you point it at:
@@ -159,10 +161,11 @@ Useful flags:
159
161
 
160
162
  - `--repo <path>` to scan one or more explicit local roots
161
163
  - `--source-mode auto|remote|local` to control remote-first fallback behavior
164
+ - `--mode standard|audit` to choose the scan mode
162
165
 
163
166
  `auto` is the default. `remote` requires Apex to materialize from a remote repository. `local` forces a local snapshot upload even when a clean remote path is available.
164
167
 
165
- Ultra scans still require provider-backed GitHub or GitLab repositories that Apex can materialize remotely without a local snapshot fallback.
168
+ Audit scans still require provider-backed GitHub or GitLab repositories that Apex can materialize remotely without a local snapshot fallback. `ultra` remains accepted as a backwards-compatible alias for the audit scan mode.
166
169
 
167
170
  ## LLM / MCP Usage
168
171
 
@@ -243,7 +246,7 @@ For Claude Code, the packaged project skill can be installed into the current re
243
246
 
244
247
  ## Development Notes
245
248
 
246
- The CLI uses the Apex `/api/cli/v2/**` local-source routes for scan planning and snapshot uploads, with legacy `/api/cli/v1/**` routes still used for provider-backed flows such as ultra scans. Local state is stored under:
249
+ The CLI uses the Apex `/api/cli/v2/**` local-source routes for scan planning and snapshot uploads, with legacy `/api/cli/v1/**` routes still used for provider-backed flows such as audit scans. Local state is stored under:
247
250
 
248
251
  - `~/.config/apex/config.json`
249
252
  - `~/.config/apex/credentials.json`
package/dist/commands.js CHANGED
@@ -36,6 +36,37 @@ function getSuggestedWorkspaceRef(workspace) {
36
36
  function formatWorkspaceUseCommand(workspace) {
37
37
  return `apex workspace use ${formatCommandArgument(getSuggestedWorkspaceRef(workspace))}`;
38
38
  }
39
+ function getOptionalCount(value) {
40
+ return typeof value === "number" && Number.isFinite(value)
41
+ ? Math.max(0, Math.floor(value))
42
+ : null;
43
+ }
44
+ function formatAuditScanBalance(scanBalance) {
45
+ const purchased = getOptionalCount(scanBalance.auditPurchased);
46
+ const used = getOptionalCount(scanBalance.auditUsed);
47
+ const remaining = getOptionalCount(scanBalance.auditRemaining);
48
+ const available = getOptionalCount(scanBalance.auditAvailable) ??
49
+ remaining ??
50
+ (purchased !== null && used !== null ? Math.max(0, purchased - used) : null);
51
+ if (purchased === null && used === null && available === null) {
52
+ return null;
53
+ }
54
+ const detailParts = [];
55
+ if (purchased !== null) {
56
+ detailParts.push(`${purchased} purchased`);
57
+ }
58
+ if (used !== null) {
59
+ detailParts.push(`${used} used`);
60
+ }
61
+ if (available !== null) {
62
+ const detailSuffix = detailParts.length ? ` (${detailParts.join(", ")})` : "";
63
+ return `Audit scans: ${available} available${detailSuffix}`;
64
+ }
65
+ return `Audit scans: ${detailParts.join(", ")}`;
66
+ }
67
+ function normalizeRequestedScanMode(value) {
68
+ return value === "ultra" || value === "audit" ? "ultra" : "standard";
69
+ }
39
70
  async function requireWorkspaceBinding(cwd) {
40
71
  const binding = await loadWorkspaceBinding(cwd);
41
72
  if (binding) {
@@ -144,7 +175,15 @@ export async function commandCredits(client, cwd, flags) {
144
175
  return payload;
145
176
  }
146
177
  logLine(`Company: ${payload.company.handle ?? payload.company.id}`, flags);
147
- logLine(`Credits: ${payload.scanBalance.remaining} remaining (${payload.scanBalance.purchased} purchased, ${payload.scanBalance.used} used)`, flags);
178
+ logLine(`Standard credits: ${payload.scanBalance.remaining} remaining (${payload.scanBalance.purchased} purchased, ${payload.scanBalance.used} used)`, flags);
179
+ const auditBalance = formatAuditScanBalance(payload.scanBalance);
180
+ if (auditBalance) {
181
+ logLine(auditBalance, flags);
182
+ }
183
+ const redeemableAuditScans = getOptionalCount(payload.scanBalance.auditRedeemableFromCredits);
184
+ if (redeemableAuditScans && redeemableAuditScans > 0) {
185
+ logLine(`Redeemable audit scans from standard credits: ${redeemableAuditScans}`, flags);
186
+ }
148
187
  logLine(`Scans enabled: ${payload.scansEnabled ? "yes" : "no"}`, flags);
149
188
  return payload;
150
189
  }
@@ -244,7 +283,7 @@ export async function commandScan(client, cwd, flags) {
244
283
  const me = await ensureAuthenticated(client, flags);
245
284
  const selection = await selectWorkspaceTarget(cwd, me, flags);
246
285
  const result = await withLoadingIndicator("Resolving workspace and scan plan...", flags, () => resolveWorkspaceSelection(client, selection));
247
- const requestedMode = getFlagString(flags, "mode") === "ultra" ? "ultra" : "standard";
286
+ const requestedMode = normalizeRequestedScanMode(getFlagString(flags, "mode"));
248
287
  if (!result.resolve.workspaceId) {
249
288
  throw new Error("Workspace resolution did not return a workspaceId.");
250
289
  }
@@ -255,14 +294,14 @@ export async function commandScan(client, cwd, flags) {
255
294
  let scan;
256
295
  if (requestedMode === "ultra") {
257
296
  if (!supportsLegacyRemoteFlow(result.resolve.plannedSources)) {
258
- throw new Error("Ultra scans currently require provider-backed GitHub or GitLab repositories without local snapshot fallbacks.");
297
+ throw new Error("Audit scans currently require provider-backed GitHub or GitLab repositories without local snapshot fallbacks.");
259
298
  }
260
- const legacyResolve = await withLoadingIndicator("Preparing ultra scan...", flags, () => resolveWorkspaceSelectionLegacy(client, selection, resolvedWorkspaceId));
299
+ const legacyResolve = await withLoadingIndicator("Preparing audit scan...", flags, () => resolveWorkspaceSelectionLegacy(client, selection, resolvedWorkspaceId));
261
300
  if (!legacyResolve.workspaceId) {
262
301
  throw new Error("Workspace resolution did not return a workspaceId.");
263
302
  }
264
303
  workspaceId = legacyResolve.workspaceId;
265
- scan = await withLoadingIndicator("Starting ultra scan...", flags, () => client.request(`/api/cli/v1/local-workspaces/${encodeURIComponent(workspaceId)}/scan`, {
304
+ scan = await withLoadingIndicator("Starting audit scan...", flags, () => client.request(`/api/cli/v1/local-workspaces/${encodeURIComponent(workspaceId)}/scan`, {
266
305
  method: "POST",
267
306
  json: {
268
307
  mode: requestedMode,
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 for the active company
3
+ apex credits Show scan credits and audit 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
@@ -28,7 +28,7 @@ Flags:
28
28
  Choose the export format for findings
29
29
  --output <path> Write exported findings to this file path
30
30
  --limit <count> Limit the number of findings returned
31
- --mode standard|ultra Choose the scan mode
31
+ --mode standard|audit Choose the scan mode
32
32
  --source-mode auto|remote|local Control remote-vs-local source materialization
33
33
  --force Start a new scan even if another scan is active
34
34
  --repo <path> Include one or more explicit local source roots
@@ -39,6 +39,7 @@ Flags:
39
39
 
40
40
  Tips:
41
41
  apex scan uses the current directory name as the default workspace name unless you pass --workspace-name.
42
+ audit is the user-facing name for the legacy ultra scan mode; ultra remains accepted as an alias.
42
43
  apex workspace use accepts a workspace name, prefix, or ID.
43
44
  Quote workspace names that contain spaces:
44
45
  apex workspace use "Core Platform"
@@ -46,8 +47,8 @@ Tips:
46
47
  export const SHELL_HELP_TEXT = `Press Tab to autocomplete commands and common arguments.
47
48
 
48
49
  Commands:
49
- /credits Show scan credits for the active company
50
- /scan [standard|ultra] Start a new Apex scan for this workspace
50
+ /credits Show scan credits and audit scan entitlements for the active company
51
+ /scan [standard|audit] Start a new Apex scan for this workspace
51
52
  /scans List scans for this workspace
52
53
  /findings [scan-id] List findings for the latest or selected scan
53
54
  /export [scan-id] Export findings for the latest or selected scan
@@ -70,6 +71,7 @@ Commands:
70
71
  /exit Exit Apex
71
72
 
72
73
  Tips:
74
+ audit is the user-facing name for the legacy ultra scan mode; ultra remains accepted as an alias.
73
75
  /workspace use accepts a workspace name, prefix, or ID.
74
76
  Quote workspace names that contain spaces: /workspace use "Core Platform"
75
77
  `;
package/dist/mcp.js CHANGED
@@ -19,6 +19,7 @@ Suggested workflow:
19
19
  Notes:
20
20
  - Pass cwd explicitly for repository-specific operations.
21
21
  - Tool calls are always non-interactive and never auto-open a browser.
22
+ - Use mode "audit" for audit scans; "ultra" remains accepted as a legacy alias.
22
23
  - Doctor reports whether Apex will use remote materialization or a local snapshot upload for each source.`;
23
24
  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.";
24
25
  const MISSING_PROVIDER_CONNECTIONS_ERROR = "Missing provider connections. Re-run interactively or pre-connect providers.";
@@ -248,7 +249,7 @@ function registerTools(server) {
248
249
  }, (value) => `Apex doctor completed for ${String(value.cwd)}.`));
249
250
  server.registerTool("apex-credits", {
250
251
  title: "Get Apex Credits",
251
- description: "Show scan credits for the active or selected company.",
252
+ description: "Show scan credits and audit scan entitlements for the active or selected company.",
252
253
  inputSchema: {
253
254
  cwd: z.string().optional(),
254
255
  company: z.string().optional(),
@@ -320,13 +321,13 @@ function registerTools(server) {
320
321
  }, (value) => `Bound ${String(value.cwd)} to an Apex workspace.`));
321
322
  server.registerTool("apex-scan", {
322
323
  title: "Start Apex Scan",
323
- description: "Start a new Apex scan for any local repository or directory. Pass cwd explicitly for reliable workspace resolution.",
324
+ description: "Start a new Apex scan for any local repository or directory. Pass cwd explicitly for reliable workspace resolution. Use mode audit for audit scans.",
324
325
  inputSchema: {
325
326
  cwd: z.string().optional(),
326
327
  company: z.string().optional(),
327
328
  workspaceName: z.string().optional(),
328
329
  repoPaths: z.array(z.string()).optional(),
329
- mode: z.enum(["standard", "ultra"]).optional(),
330
+ mode: z.enum(["standard", "audit", "ultra"]).optional(),
330
331
  sourceMode: z.enum(["auto", "remote", "local"]).optional(),
331
332
  force: z.boolean().optional(),
332
333
  },
package/dist/scan.js CHANGED
@@ -30,6 +30,10 @@ function readNumber(...values) {
30
30
  }
31
31
  return null;
32
32
  }
33
+ function readScanMode(...values) {
34
+ const mode = readString(...values);
35
+ return mode === "ultra" ? "audit" : mode;
36
+ }
33
37
  function getScanListItems(payload) {
34
38
  if (Array.isArray(payload)) {
35
39
  return payload;
@@ -63,7 +67,7 @@ function normalizeScanRecord(payload) {
63
67
  displayName: readString(record.displayName, record.display_name, record.name),
64
68
  sequenceNumber: readNumber(record.sequenceNumber, record.sequence_number),
65
69
  status: readString(record.status) ?? "unknown",
66
- mode: readString(record.mode, record.scanType, record.scan_type),
70
+ mode: readScanMode(record.mode, record.scanType, record.scan_type),
67
71
  scanUrl: readString(record.scanUrl, record.url, record.scan_url),
68
72
  createdAt: readTimestamp(record, "createdAt", "created_at"),
69
73
  startedAt: readTimestamp(record, "startedAt", "started_at"),
package/dist/shell.js CHANGED
@@ -6,7 +6,7 @@ import { readLine } from "./prompt.js";
6
6
  import { formatApiError } from "./api-client.js";
7
7
  const SHELL_COMPLETIONS = [
8
8
  { command: "credits" },
9
- { command: "scan", args: ["standard", "ultra"] },
9
+ { command: "scan", args: ["standard", "audit", "ultra"] },
10
10
  { command: "scans" },
11
11
  { command: "findings" },
12
12
  { command: "export" },
@@ -178,8 +178,8 @@ async function runShellCommand(client, cwd, parsed, shellFlags, session) {
178
178
  return {};
179
179
  case "scan": {
180
180
  const mode = parsed.args[0];
181
- if (mode && !["standard", "ultra"].includes(mode)) {
182
- process.stderr.write("Usage: /scan [standard|ultra]\n");
181
+ if (mode && !["standard", "audit", "ultra"].includes(mode)) {
182
+ process.stderr.write("Usage: /scan [standard|audit|ultra]\n");
183
183
  return {};
184
184
  }
185
185
  const result = await commandScan(client, cwd, mode ? withFlag(shellFlags, "mode", mode) : shellFlags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cantinasecurity/apex-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Standalone CLI and MCP server for Apex.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -21,8 +21,10 @@ Workflow:
21
21
  Guidelines:
22
22
 
23
23
  - Do not rely on interactive CLI prompts. The MCP tools are intentionally non-interactive.
24
+ - `apex-credits` reports standard credits and audit scan entitlements when Bedrock returns both balances.
24
25
  - `apex-doctor` reports whether Apex will use remote materialization or a local snapshot upload for each selected source.
25
26
  - Apex can scan plain local directories and dirty git worktrees without provider connections by using local snapshot uploads.
27
+ - Audit scans use `mode: "audit"` in user-facing instructions. The legacy `ultra` mode remains accepted as an alias, but audit scans still require provider-backed GitHub or GitLab sources.
26
28
  - `apex-workspace-use` accepts a workspace name, prefix, or ID.
27
29
  - Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
28
30
  - Use `force: true` on `apex-scan` only when the user explicitly wants to replace or overlap an active scan.