@cdot65/prisma-airs-cli 5.2.0 → 5.4.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.
package/dist/cli/index.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  validateTopic,
31
31
  writeBackupFile,
32
32
  writeReportFile
33
- } from "../chunk-7Z76VCB3.js";
33
+ } from "../chunk-36Z6IPH6.js";
34
34
 
35
35
  // src/cli/index.ts
36
36
  import "dotenv/config";
@@ -49,189 +49,36 @@ function installProcessGuards() {
49
49
  }
50
50
 
51
51
  // src/cli/program.ts
52
- import { randomUUID as randomUUID8 } from "crypto";
52
+ import { randomUUID as randomUUID9 } from "crypto";
53
53
  import { readFileSync as readFileSync4 } from "fs";
54
54
  import { dirname as dirname4, join as join4 } from "path";
55
55
  import { fileURLToPath } from "url";
56
56
  import { Command } from "commander";
57
57
 
58
- // src/cli/commands/aigateway.ts
58
+ // src/cli/commands/agentguard.ts
59
+ import { randomUUID } from "crypto";
60
+ import { lstat } from "fs/promises";
61
+ import { resolve } from "path";
59
62
  import {
60
- GatewayDefaultsInputSchema,
61
- GatewayRateLimitInputSchema,
62
- GatewayUsageLimitInputSchema
63
+ AgentGuardClient,
64
+ AgentGuardPageOptionsSchema,
65
+ AgentGuardScanListOptionsSchema,
66
+ AgentGuardStatsOptionsSchema
63
67
  } from "@cdot65/prisma-airs-sdk";
68
+ import { dump as dump3 } from "js-yaml";
69
+ import { z } from "zod";
64
70
 
65
- // src/airs/aigateway.ts
66
- import {
67
- AIGatewayChartFiltersSchema,
68
- AIGatewayClient
69
- } from "@cdot65/prisma-airs-sdk";
70
- function toLimitArray(value) {
71
- if (Array.isArray(value)) return value;
72
- if (value !== null && typeof value === "object") return [value];
73
- return [];
74
- }
75
- function normalizeWorkspace(raw) {
76
- return {
77
- id: raw.id,
78
- slug: raw.slug,
79
- name: raw.name,
80
- icon: raw.icon,
81
- description: raw.description,
82
- createdAt: raw.created_at,
83
- lastUpdatedAt: raw.last_updated_at,
84
- isDefault: Boolean(raw.is_default),
85
- status: raw.status,
86
- scopeName: raw.scope_name
87
- };
88
- }
89
- function normalizeWorkspaceDetail(raw) {
71
+ // src/config/client-options.ts
72
+ function agentGuardClientOptions(config) {
90
73
  return {
91
- ...normalizeWorkspace(raw),
92
- defaults: raw.defaults,
93
- usageLimits: toLimitArray(raw.usage_limits),
94
- rateLimits: toLimitArray(raw.rate_limits),
95
- securitySettings: raw.security_settings,
96
- dataPlaneSecuritySettings: raw.data_plane_security_settings,
97
- settings: raw.settings
74
+ clientId: config.mgmtClientId,
75
+ clientSecret: config.mgmtClientSecret,
76
+ tsgId: config.mgmtTsgId,
77
+ dataEndpoint: config.agentGuardDataEndpoint,
78
+ mgmtEndpoint: config.agentGuardMgmtEndpoint,
79
+ tokenEndpoint: config.agentGuardTokenEndpoint ?? config.mgmtTokenEndpoint
98
80
  };
99
81
  }
100
- function aiGatewayGrantHint(err) {
101
- const status = err?.status ?? err?.statusCode;
102
- if (status !== 403) return void 0;
103
- const message = err instanceof Error ? err.message : String(err);
104
- const grant = message.includes("AB03") ? "the service account is missing a workspace-scope grant (data plane, /ai_gw/v2)" : "the service account is missing a tenant-root admin grant (admin plane, /ai_gw/admin/v2)";
105
- return `${grant}. SCM Access Management edits the existing role row by default \u2014 use "Add Role" so the account ends up with both role rows, not one row moved.`;
106
- }
107
- var SdkAiGatewayService = class {
108
- client;
109
- constructor(opts) {
110
- this.client = new AIGatewayClient(opts);
111
- }
112
- async listWorkspaces(options) {
113
- const response = await this.client.workspaces.list(options);
114
- return response.data.map(normalizeWorkspace);
115
- }
116
- async listAllWorkspaces() {
117
- const [active, archived] = await Promise.all([
118
- this.client.workspaces.list({ plane: "admin" }),
119
- this.client.workspaces.list({ plane: "admin", status: "archived" })
120
- ]);
121
- return [
122
- ...active.data,
123
- ...archived.data
124
- ].map(normalizeWorkspace);
125
- }
126
- async getWorkspace(workspaceRef, options) {
127
- try {
128
- const raw = await this.client.workspaces.get(workspaceRef, options);
129
- return normalizeWorkspaceDetail(raw);
130
- } catch (err) {
131
- const status = err.statusCode;
132
- if (status !== 404) throw err;
133
- const resolved = await this.resolveWorkspaceRef(workspaceRef, [
134
- options?.plane ?? "data",
135
- "admin"
136
- ]);
137
- if (resolved === workspaceRef) throw err;
138
- const raw = await this.client.workspaces.get(resolved, options);
139
- return normalizeWorkspaceDetail(raw);
140
- }
141
- }
142
- async createWorkspace(request) {
143
- const body = {
144
- name: request.name,
145
- scope_name: request.scopeName
146
- };
147
- if (request.description !== void 0) body.description = request.description;
148
- if (request.icon !== void 0) body.icon = request.icon;
149
- if (request.defaults !== void 0) body.defaults = request.defaults;
150
- if (request.users !== void 0) body.users = request.users;
151
- if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
152
- if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
153
- const created = await this.client.workspaces.create(body);
154
- return this.refetchAfterWrite(created.id, created);
155
- }
156
- async updateWorkspace(workspaceRef, request) {
157
- const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
158
- const body = {};
159
- if (request.name !== void 0) body.name = request.name;
160
- if (request.description !== void 0) body.description = request.description;
161
- if (request.icon !== void 0) body.icon = request.icon;
162
- if (request.defaults !== void 0) body.defaults = request.defaults;
163
- if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
164
- if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
165
- await this.client.workspaces.update(ref, body);
166
- return this.getWorkspace(ref, { plane: "admin" });
167
- }
168
- async deleteWorkspace(workspaceRef) {
169
- const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
170
- await this.client.workspaces.delete(ref);
171
- }
172
- /**
173
- * The API accepts only a UUID or slug as a workspace ref — a display name
174
- * gets a misleading 400 AB01 ("No update fields provided") on writes.
175
- * Match a user-supplied ref against the workspace list so name | slug |
176
- * uuid all work. Unmatched refs pass through so the API's own error stands.
177
- */
178
- async resolveWorkspaceRef(ref, planes, requireSlug = false) {
179
- for (const plane of planes) {
180
- let rows;
181
- try {
182
- rows = await this.listWorkspaces({ plane });
183
- } catch {
184
- continue;
185
- }
186
- const byRef = rows.find((w) => w.id === ref || w.slug === ref);
187
- if (byRef) return requireSlug ? byRef.slug : ref;
188
- const byName = rows.filter((w) => w.name === ref);
189
- if (byName.length > 1) {
190
- throw new Error(
191
- `workspace name '${ref}' is ambiguous (${byName.map((w) => w.slug).join(", ")}) \u2014 use a slug or UUID`
192
- );
193
- }
194
- if (byName.length === 1) return byName[0].slug;
195
- }
196
- return ref;
197
- }
198
- async getTelemetryCost(opts) {
199
- const { workspaceSlug: workspaceRef, days = 7, ...rawFilters } = opts;
200
- const filters = AIGatewayChartFiltersSchema.parse(rawFilters);
201
- if (!Number.isSafeInteger(days) || days <= 0)
202
- throw new Error("Expected days to be a positive integer");
203
- const workspaceSlug = await this.resolveWorkspaceRef(workspaceRef, ["data", "admin"], true);
204
- const raw = await this.client.telemetry.cost({
205
- workspaceSlug,
206
- days,
207
- ...filters
208
- });
209
- return {
210
- workspaceSlug,
211
- days,
212
- totalCents: raw.data.total,
213
- totalUsd: raw.data.total / 100,
214
- avgCents: raw.data.avg,
215
- avgUsd: raw.data.avg / 100,
216
- quotaExceeded: raw.data.isQuotaExceeded,
217
- records: raw.data.records.map((r) => ({
218
- date: r.x,
219
- costCents: r.y,
220
- costUsd: r.y / 100
221
- }))
222
- };
223
- }
224
- /** Re-read after a write, falling back to the (partial) write response if the get fails. */
225
- async refetchAfterWrite(workspaceRef, writeResponse) {
226
- try {
227
- return await this.getWorkspace(workspaceRef, { plane: "admin" });
228
- } catch {
229
- return normalizeWorkspaceDetail(writeResponse);
230
- }
231
- }
232
- };
233
-
234
- // src/config/client-options.ts
235
82
  function managementClientOptions(config) {
236
83
  return {
237
84
  clientId: config.mgmtClientId,
@@ -283,6 +130,207 @@ function modelSecurityClientOptions(config) {
283
130
  };
284
131
  }
285
132
 
133
+ // src/reports/agentguard.ts
134
+ async function collectAgentGuardReport(client, options = {}) {
135
+ const maxPages = options.maxPages ?? 40;
136
+ if (!Number.isSafeInteger(maxPages) || maxPages < 1 || maxPages > 100)
137
+ throw new Error("maxPages must be between 1 and 100");
138
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
139
+ const end = now();
140
+ const window2 = {
141
+ start: new Date(end.getTime() - 30 * 864e5).toISOString(),
142
+ end: end.toISOString()
143
+ };
144
+ const report = {
145
+ schemaVersion: 1,
146
+ product: "Prisma AIRS AI Supply Chain \u2014 AgentGuard",
147
+ title: options.title ?? "AgentGuard environment report",
148
+ collectionStartedAt: end.toISOString(),
149
+ generatedAt: "",
150
+ window: window2,
151
+ windowLabel: "30-day scan inventory; rolling 30_DAYS statistics; current rule catalog",
152
+ schemaLabel: "Experimental browser APIs",
153
+ health: "unknown",
154
+ sources: [],
155
+ findings: [],
156
+ tables: [],
157
+ limitations: [
158
+ "Read-only evidence, not a security certification or numerical health score. Browser APIs are undocumented and may change.",
159
+ "Scans use explicit start/end timestamps; statistics use the server rolling 30_DAYS window. These independently read sources are not an atomic snapshot and need not reconcile.",
160
+ "Scan rows are not unique skills. Parent/batch rows are excluded from outcome counts to avoid counting them alongside children; per-scan vulnerability counts are never summed.",
161
+ "Rule default states describe the catalog, not effective tenant policies. No scan creation, policy changes or skill uploads are performed.",
162
+ "Missing or incomplete evidence is unknown, not zero. Scan names, identifiers, URLs, device metadata, source code and finding text are excluded."
163
+ ]
164
+ };
165
+ const label = (v, allowed) => v === null ? "UNKNOWN" : allowed.includes(v) ? v : "OTHER";
166
+ async function source(name, method, range, read) {
167
+ const s = {
168
+ name,
169
+ method,
170
+ window: range,
171
+ status: "unavailable",
172
+ pages: 0,
173
+ records: 0,
174
+ notes: []
175
+ };
176
+ report.sources.push(s);
177
+ try {
178
+ await read(s);
179
+ if (s.status !== "partial") s.status = "complete";
180
+ } catch {
181
+ s.status = s.records ? "partial" : "unavailable";
182
+ s.notes.push("Request or validation failed; no upstream error body included.");
183
+ }
184
+ if (s.status !== "complete")
185
+ report.findings.push({
186
+ priority: "review",
187
+ title: `${name} incomplete`,
188
+ evidence: s.notes.join(" "),
189
+ recommendation: "Check service access or increase the page budget, then regenerate.",
190
+ source: name
191
+ });
192
+ }
193
+ async function pages(s, read, pageCount = false) {
194
+ const rows = [];
195
+ const seen = /* @__PURE__ */ new Set();
196
+ let total;
197
+ try {
198
+ for (let page = 0; page < maxPages; page++) {
199
+ const r = await read(rows.length);
200
+ s.pages++;
201
+ if (!Number.isSafeInteger(r.total) || r.total < 0 || !Array.isArray(r.rows) || r.rows.length > 10)
202
+ throw new Error();
203
+ total ??= r.total;
204
+ if (!pageCount && (r.total !== total || rows.length + r.rows.length > total))
205
+ throw new Error();
206
+ for (const row of r.rows) {
207
+ if (!row.uuid || seen.has(row.uuid)) throw new Error();
208
+ seen.add(row.uuid);
209
+ rows.push(row);
210
+ s.records = rows.length;
211
+ }
212
+ if (pageCount ? r.rows.length < 10 : rows.length === total) return rows;
213
+ if (!r.rows.length) throw new Error();
214
+ }
215
+ s.notes.push(`Reached the ${maxPages}-page budget.`);
216
+ throw new Error();
217
+ } catch (error) {
218
+ if (!rows.length) throw error;
219
+ s.status = "partial";
220
+ s.notes.push(
221
+ "Inventory incomplete: pagination changed, repeated, stopped early, failed or reached its budget."
222
+ );
223
+ return rows;
224
+ }
225
+ }
226
+ await source("Scans", "listScans()", `${window2.start} to ${window2.end}`, async (s) => {
227
+ const rows = await pages(s, async (skip) => {
228
+ const p = await client.listScans({
229
+ skip,
230
+ limit: 10,
231
+ start_time: window2.start,
232
+ end_time: window2.end,
233
+ isBackgroundRefresh: false
234
+ });
235
+ return { rows: p.scans, total: p.pagination.total_items };
236
+ });
237
+ const inside = rows.filter((r) => {
238
+ const t = Date.parse(r.created_at);
239
+ return Number.isFinite(t) && t >= Date.parse(window2.start) && t <= end.getTime();
240
+ });
241
+ if (inside.length !== rows.length) {
242
+ s.status = "partial";
243
+ s.notes.push("Rows with missing or out-of-window creation timestamps were excluded.");
244
+ }
245
+ const leaf = inside.filter((r) => !r.is_batch && r.child_scan_uuids.length === 0);
246
+ const groups = /* @__PURE__ */ new Map();
247
+ for (const r of leaf) {
248
+ const key = `${label(r.artifact_type, ["SKILL", "AGENT"])} / ${label(r.eval_outcome, ["BLOCKED", "ALLOWED", "PENDING", "ERROR"])}`;
249
+ groups.set(key, (groups.get(key) ?? 0) + 1);
250
+ }
251
+ report.tables.push({
252
+ title: "Scan outcomes",
253
+ note: `Collected ${s.records} rows; excluded ${inside.length - leaf.length} parent/batch rows. ${s.status === "partial" ? "Partial inventory only." : "Complete inventory for requested window."}`,
254
+ headers: ["Artifact / outcome", "Scan rows"],
255
+ rows: [...groups].sort(([a], [b]) => a.localeCompare(b))
256
+ });
257
+ const blocked = leaf.filter((r) => r.eval_outcome === "BLOCKED").length;
258
+ if (blocked)
259
+ report.findings.push({
260
+ priority: "attention",
261
+ title: "Blocked scans require review",
262
+ evidence: `${blocked} collected non-parent scan rows have BLOCKED evaluations.`,
263
+ recommendation: "Inspect the affected scans and findings before approving the artifacts.",
264
+ source: "Scans"
265
+ });
266
+ const unsettled = leaf.filter(
267
+ (r) => r.status !== "COMPLETED" || !["ALLOWED", "BLOCKED"].includes(r.eval_outcome ?? "")
268
+ ).length;
269
+ if (unsettled)
270
+ report.findings.push({
271
+ priority: "review",
272
+ title: "Scans without a completed decision",
273
+ evidence: `${unsettled} collected non-parent rows are pending, failed or have an unfamiliar status.`,
274
+ recommendation: "Review scan status; absence of a completed decision is not a passing evaluation.",
275
+ source: "Scans"
276
+ });
277
+ });
278
+ await source("Statistics", "getScanStats()", "Server rolling 30_DAYS", async (s) => {
279
+ const r = await client.getScanStats({ time_period: "30_DAYS" });
280
+ report.tables.push({
281
+ title: "Skill statistics",
282
+ note: "Server aggregates; percentage changes may be unavailable.",
283
+ headers: ["Metric", "Count", "Change (%)"],
284
+ rows: [
285
+ [
286
+ "Unique skills scanned",
287
+ r.unique_skills_scanned.count,
288
+ r.unique_skills_scanned.percent_change
289
+ ],
290
+ [
291
+ "Vulnerabilities found",
292
+ r.total_vulnerabilities_found.count,
293
+ r.total_vulnerabilities_found.percent_change
294
+ ]
295
+ ]
296
+ });
297
+ if (r.total_vulnerabilities_found.count > 0)
298
+ report.findings.push({
299
+ priority: "attention",
300
+ title: "Skill vulnerabilities reported",
301
+ evidence: `${r.total_vulnerabilities_found.count} findings reported in the server statistics window.`,
302
+ recommendation: "Review vulnerability details and remediate before use; findings are not a count of distinct vulnerable skills.",
303
+ source: "Statistics"
304
+ });
305
+ s.records = 1;
306
+ s.pages = 1;
307
+ });
308
+ await source("Rules", "listRules()", "Current catalog", async (s) => {
309
+ const rows = await pages(
310
+ s,
311
+ async (skip) => {
312
+ const p = await client.listRules({ skip, limit: 10 });
313
+ return { rows: p.rules, total: p.pagination.total_items };
314
+ },
315
+ true
316
+ );
317
+ const groups = /* @__PURE__ */ new Map();
318
+ for (const r of rows) {
319
+ const state = label(r.default_state, ["BLOCKING", "ALLOWING"]);
320
+ groups.set(state, (groups.get(state) ?? 0) + 1);
321
+ }
322
+ report.tables.push({
323
+ title: "Rule catalog defaults",
324
+ note: "These are not effective tenant policy settings.",
325
+ headers: ["Default state", "Rules"],
326
+ rows: [...groups].sort(([a], [b]) => a.localeCompare(b))
327
+ });
328
+ });
329
+ report.health = report.sources.every((s) => s.status === "unavailable") ? "unknown" : report.findings.some((f) => f.priority === "attention") ? "attention" : report.findings.length ? "review" : "no-findings";
330
+ report.generatedAt = now().toISOString();
331
+ return report;
332
+ }
333
+
286
334
  // src/cli/renderer/aigateway.ts
287
335
  import chalk4 from "chalk";
288
336
  import { dump as yamlDump } from "js-yaml";
@@ -2787,140 +2835,541 @@ function renderCustomerAppList(apps, format = "pretty") {
2787
2835
  );
2788
2836
  return;
2789
2837
  }
2790
- ui.section("Customer Apps:");
2791
- for (const a of apps) {
2792
- if (a.id) ui.dim(a.id);
2793
- const desc = a.description ? chalk9.dim(` \u2014 ${a.description.slice(0, 80)}`) : "";
2794
- console.log(` ${a.name}${desc}`);
2838
+ ui.section("Customer Apps:");
2839
+ for (const a of apps) {
2840
+ if (a.id) ui.dim(a.id);
2841
+ const desc = a.description ? chalk9.dim(` \u2014 ${a.description.slice(0, 80)}`) : "";
2842
+ console.log(` ${a.name}${desc}`);
2843
+ }
2844
+ console.log();
2845
+ }
2846
+ function renderCustomerAppDetail(app) {
2847
+ ui.section("Customer App Detail:");
2848
+ const pairs = [];
2849
+ if (app.id) pairs.push(["ID", app.id]);
2850
+ pairs.push(["Name", app.name]);
2851
+ if (app.description) pairs.push(["Desc", app.description]);
2852
+ pairs.push(["Data", JSON.stringify(app.raw, null, 2).slice(0, 500)]);
2853
+ ui.keyValue(pairs);
2854
+ console.log();
2855
+ }
2856
+ function renderCustomerAppConsumption(data, format = "pretty") {
2857
+ const fmt = (n, scale) => n == null ? "-" : `${n}${scale ?? ""}`;
2858
+ if (format !== "pretty") {
2859
+ const rows = data.detectors.map((d) => ({
2860
+ app_name: data.appName,
2861
+ app_id: data.appId,
2862
+ monitoring_since: data.monitoringSince ?? "",
2863
+ daily_avg: fmt(data.tokens.dailyAverage, data.tokens.dailyAverageScale),
2864
+ monthly_total: fmt(data.tokens.monthlyTotal, data.tokens.monthlyTotalScale),
2865
+ sessions_total: data.sessions.total,
2866
+ sessions_violating: data.sessions.violating,
2867
+ detector: d.type,
2868
+ critical: d.critical,
2869
+ high: d.high,
2870
+ medium: d.medium,
2871
+ low: d.low,
2872
+ total: d.total
2873
+ }));
2874
+ console.log(
2875
+ formatOutput(
2876
+ rows,
2877
+ [
2878
+ { key: "app_name", label: "App" },
2879
+ { key: "app_id", label: "AppId" },
2880
+ { key: "monitoring_since", label: "MonitoringSince" },
2881
+ { key: "daily_avg", label: "DailyAvg" },
2882
+ { key: "monthly_total", label: "MonthlyTotal" },
2883
+ { key: "sessions_total", label: "Sessions" },
2884
+ { key: "sessions_violating", label: "Violating" },
2885
+ { key: "detector", label: "Detector" },
2886
+ { key: "critical", label: "C" },
2887
+ { key: "high", label: "H" },
2888
+ { key: "medium", label: "M" },
2889
+ { key: "low", label: "L" },
2890
+ { key: "total", label: "Total" }
2891
+ ],
2892
+ format
2893
+ )
2894
+ );
2895
+ return;
2896
+ }
2897
+ ui.header(data.appName, `(${data.appId})`);
2898
+ const pairs = [];
2899
+ if (data.monitoringSince) pairs.push(["Monitoring since", data.monitoringSince]);
2900
+ if (data.source) pairs.push(["Source", data.source]);
2901
+ if (data.cloud) pairs.push(["Cloud", data.cloud]);
2902
+ if (data.profiles.length > 0) pairs.push(["Profiles", data.profiles.join(", ")]);
2903
+ if (pairs.length > 0) ui.keyValue(pairs);
2904
+ ui.section("Token consumption:");
2905
+ ui.keyValue([
2906
+ ["Daily avg", fmt(data.tokens.dailyAverage, data.tokens.dailyAverageScale)],
2907
+ ["Monthly total", fmt(data.tokens.monthlyTotal, data.tokens.monthlyTotalScale)]
2908
+ ]);
2909
+ ui.section("Sessions:");
2910
+ ui.keyValue([
2911
+ ["Total", data.sessions.total],
2912
+ ["Violating", data.sessions.violating]
2913
+ ]);
2914
+ const firing = data.detectors.filter((d) => d.total > 0);
2915
+ ui.section(
2916
+ `Detectors (${data.totalViolating} violating, ${firing.length}/${data.detectors.length} firing):`
2917
+ );
2918
+ if (firing.length === 0) {
2919
+ ui.dim("no detector violations in window");
2920
+ } else {
2921
+ ui.table(
2922
+ [
2923
+ { key: "detector", label: "Detector" },
2924
+ { key: "total", label: "Total" },
2925
+ { key: "severity", label: "Severity" }
2926
+ ],
2927
+ firing.map((d) => ({
2928
+ detector: d.type,
2929
+ total: d.total,
2930
+ severity: `c=${d.critical} h=${d.high} m=${d.medium} l=${d.low}`
2931
+ }))
2932
+ );
2933
+ }
2934
+ console.log();
2935
+ }
2936
+ function renderDeploymentProfileList(profiles, format = "pretty") {
2937
+ if (profiles.length === 0) {
2938
+ ui.emptyList("deployment profiles");
2939
+ return;
2940
+ }
2941
+ if (format !== "pretty") {
2942
+ const rows = profiles.map((p) => ({
2943
+ name: p.raw.dp_name ?? p.raw.profile_name ?? p.raw.name ?? "",
2944
+ status: p.raw.status ?? "",
2945
+ authCode: p.raw.auth_code ?? ""
2946
+ }));
2947
+ console.log(
2948
+ formatOutput(
2949
+ rows,
2950
+ [
2951
+ { key: "name", label: "Name" },
2952
+ { key: "status", label: "Status" },
2953
+ { key: "authCode", label: "Auth Code" }
2954
+ ],
2955
+ format
2956
+ )
2957
+ );
2958
+ return;
2959
+ }
2960
+ ui.section("Deployment Profiles:");
2961
+ for (const p of profiles) {
2962
+ const name = p.raw.dp_name ?? p.raw.profile_name ?? p.raw.name ?? "unknown";
2963
+ const status = p.raw.status;
2964
+ const authCode = p.raw.auth_code;
2965
+ const statusColor5 = status === "active" ? chalk9.green : chalk9.dim;
2966
+ console.log(
2967
+ ` ${name}${status ? ` ${statusColor5(status)}` : ""}${authCode ? ` ${chalk9.dim(authCode)}` : ""}`
2968
+ );
2969
+ }
2970
+ console.log();
2971
+ }
2972
+
2973
+ // src/cli/commands/agentguard.ts
2974
+ var createClient = async () => new AgentGuardClient({ ...agentGuardClientOptions(await loadConfig()), numRetries: 0 });
2975
+ var integer = (value) => {
2976
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
2977
+ usageError("Pagination requires a safe nonnegative integer");
2978
+ return Number(value);
2979
+ };
2980
+ var pageOptions = (cmd) => cmd.option("--limit <n>", "Page size", integer, 10).option("--offset <n>", "Starting offset", integer, 0);
2981
+ var readOutput = (cmd) => cmd.option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml");
2982
+ var query = (schema, value) => {
2983
+ const result = schema.safeParse(value);
2984
+ if (!result.success)
2985
+ usageError(
2986
+ "Invalid AgentGuard query. Check pagination, UUID and ISO timestamps; statistics support 30_DAYS only."
2987
+ );
2988
+ return result.data;
2989
+ };
2990
+ function emit(value, format) {
2991
+ if (format === "json") console.log(JSON.stringify(value, null, 2));
2992
+ else if (format === "yaml") console.log(dump3(value, { noRefs: true, lineWidth: -1 }).trimEnd());
2993
+ else {
2994
+ const records = Object.values(value).find(Array.isArray);
2995
+ if (records) {
2996
+ const keys = [...new Set(records.flatMap((r) => Object.keys(r)))].filter((k) => !["description", "remediation"].includes(k)).slice(0, 9);
2997
+ console.log(
2998
+ formatOutput(
2999
+ records,
3000
+ keys.map((key) => ({ key, label: key.replaceAll("_", " ") })),
3001
+ format === "pretty" ? "table" : format
3002
+ )
3003
+ );
3004
+ ui.status(`Returned ${records.length} records; see JSON/YAML for pagination metadata.`);
3005
+ return;
3006
+ }
3007
+ const rows = Object.entries(value).map(([key, v]) => ({
3008
+ key,
3009
+ value: typeof v === "object" ? JSON.stringify(v) : String(v)
3010
+ }));
3011
+ console.log(
3012
+ formatOutput(
3013
+ rows,
3014
+ [
3015
+ { key: "key", label: "Field" },
3016
+ { key: "value", label: "Value" }
3017
+ ],
3018
+ format === "pretty" ? "table" : format
3019
+ )
3020
+ );
3021
+ }
3022
+ }
3023
+ async function run(read, cmd) {
3024
+ try {
3025
+ const format = await resolveOutput(cmd, cmd.opts());
3026
+ emit(await read(), format);
3027
+ } catch (error) {
3028
+ const status = error?.statusCode;
3029
+ ui.error(
3030
+ `AgentGuard request failed${typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599 ? ` (HTTP ${status})` : ""}. Check credentials, query options and service availability. No upstream body is included.`
3031
+ );
3032
+ process.exitCode = 1;
3033
+ }
3034
+ }
3035
+ async function inventory(read, options, pageCount = false) {
3036
+ if (options.max !== void 0 && (!Number.isSafeInteger(options.max) || options.max < 0))
3037
+ usageError("--max must be a safe nonnegative integer");
3038
+ const rows = [];
3039
+ const seen = /* @__PURE__ */ new Set();
3040
+ let total;
3041
+ const cap = options.all ? options.max || Number.MAX_SAFE_INTEGER : options.limit;
3042
+ for (let page = 0; page < 1e3; page++) {
3043
+ const limit = Math.min(options.limit, cap - rows.length);
3044
+ const p = await read(options.offset + rows.length, limit);
3045
+ total ??= p.total;
3046
+ if (!pageCount && p.total !== total || p.rows.length > limit)
3047
+ throw new Error("Unstable pagination");
3048
+ for (const row of p.rows) {
3049
+ if (!row.uuid || seen.has(row.uuid)) throw new Error("Repeated identity");
3050
+ seen.add(row.uuid);
3051
+ rows.push(row);
3052
+ }
3053
+ const next = options.offset + rows.length;
3054
+ if (!pageCount && next > total && rows.length) throw new Error("Invalid total");
3055
+ const complete = pageCount ? p.rows.length < limit : next >= total;
3056
+ if (!options.all || complete || rows.length === cap)
3057
+ return {
3058
+ rows,
3059
+ pagination: {
3060
+ total_items: pageCount ? complete && options.offset === 0 ? rows.length : null : total,
3061
+ returned: rows.length,
3062
+ skip: options.offset,
3063
+ next_offset: complete ? null : next,
3064
+ truncated: !complete
3065
+ }
3066
+ };
3067
+ if (!p.rows.length) throw new Error("Premature empty page");
3068
+ }
3069
+ throw new Error("Pagination safety limit reached");
3070
+ }
3071
+ function registerAgentGuardCommand(program) {
3072
+ const group = program.command("agentguard").description("AI Supply Chain agent and skill scan telemetry (experimental, OAuth)");
3073
+ const scans = group.command("scans").description("Inspect agent and skill scans (read-only)");
3074
+ const list = readOutput(
3075
+ pageOptions(
3076
+ scans.command("list").description("List one page of scans; sensitive metadata is excluded")
3077
+ )
3078
+ ).option("--start <iso>", "Inclusive creation time start (ISO timestamp)").option("--end <iso>", "Creation time end (ISO timestamp)").action(async (opts) => {
3079
+ const options = query(AgentGuardScanListOptionsSchema, {
3080
+ skip: opts.offset,
3081
+ limit: opts.limit,
3082
+ start_time: opts.start,
3083
+ end_time: opts.end,
3084
+ isBackgroundRefresh: false
3085
+ });
3086
+ await run(async () => {
3087
+ const client = await createClient();
3088
+ const p = await inventory(async (skip, limit) => {
3089
+ const page = await client.listScans({ ...options, skip, limit });
3090
+ return { rows: page.scans, total: page.pagination.total_items };
3091
+ }, opts);
3092
+ return {
3093
+ pagination: p.pagination,
3094
+ scans: p.rows.map((r) => ({
3095
+ uuid: r.uuid,
3096
+ artifact_type: r.artifact_type,
3097
+ status: r.status,
3098
+ eval_outcome: r.eval_outcome,
3099
+ created_at: r.created_at,
3100
+ vulnerability_count: r.summary?.vulnerability_count ?? null,
3101
+ attack_chain_count: r.summary?.attack_chain_count ?? null,
3102
+ is_batch: r.is_batch,
3103
+ parent_uuid: r.parent_uuid
3104
+ }))
3105
+ };
3106
+ }, list);
3107
+ });
3108
+ const vulnerabilities = readOutput(
3109
+ scans.command("vulnerabilities <scanUuid>").description("List finding metadata; --include-content explicitly reveals sensitive details")
3110
+ ).option("--include-content", "Include source code, paths and finding text (sensitive)").action(async (id, opts) => {
3111
+ query(z.string().uuid(), id);
3112
+ await run(async () => {
3113
+ const p = await (await createClient()).listScanVulnerabilities(id);
3114
+ return {
3115
+ pagination: p.pagination,
3116
+ vulnerabilities: p.vulnerabilities.map(
3117
+ (r) => opts.includeContent ? r : {
3118
+ uuid: r.uuid,
3119
+ scan_uuid: r.scan_uuid,
3120
+ vulnerability_type: r.vulnerability_type,
3121
+ attack_chain_count: r.attack_chain_count
3122
+ }
3123
+ ),
3124
+ content_included: !!opts.includeContent
3125
+ };
3126
+ }, vulnerabilities);
3127
+ });
3128
+ const stats = readOutput(
3129
+ group.command("stats").description("Read server rolling 30-day skill statistics")
3130
+ ).option("--time-period <period>", "Verified period: 30_DAYS", "30_DAYS").action(async (opts) => {
3131
+ const options = query(AgentGuardStatsOptionsSchema, { time_period: opts.timePeriod });
3132
+ await run(async () => (await createClient()).getScanStats(options), stats);
3133
+ });
3134
+ const rulesGroup = group.command("rules").description("Rule catalog (not effective tenant policy)");
3135
+ const rules = readOutput(
3136
+ pageOptions(rulesGroup.command("list").description("List rule catalog defaults"))
3137
+ ).action(async (opts) => {
3138
+ const options = query(AgentGuardPageOptionsSchema, { skip: opts.offset, limit: opts.limit });
3139
+ await run(async () => {
3140
+ const client = await createClient();
3141
+ const p = await inventory(
3142
+ async (skip, limit) => {
3143
+ const page = await client.listRules({ ...options, skip, limit });
3144
+ return { rows: page.rules, total: page.pagination.total_items };
3145
+ },
3146
+ opts,
3147
+ true
3148
+ );
3149
+ return { pagination: p.pagination, rules: p.rows };
3150
+ }, rules);
3151
+ });
3152
+ group.command("report").description("Generate an aggregate-only 30-day HTML/Markdown report").option("--output <format>", "Deliverable: html (default) or markdown").option("--output-file <path>", "New file in CWD by default; - for stdout; never overwrite").option("--max-pages <n>", "Page budget per inventory (1\u2013100, 10 rows/page)", integer, 40).option("--title <text>", "Report title", "AgentGuard environment report").option("--strict", "Exit 1 after writing if any source is incomplete").action(async (opts) => {
3153
+ const format = opts.output ?? program.opts().output ?? "html";
3154
+ if (!["html", "markdown"].includes(format))
3155
+ usageError("Report output must be html or markdown");
3156
+ if (opts.maxPages < 1 || opts.maxPages > 100)
3157
+ usageError("--max-pages must be between 1 and 100");
3158
+ if (!opts.title.trim() || opts.title.length > 240)
3159
+ usageError("--title must contain 1\u2013240 characters");
3160
+ if (opts.outputFile !== void 0 && !opts.outputFile.trim())
3161
+ usageError("--output-file cannot be empty");
3162
+ const path3 = opts.outputFile === "-" ? void 0 : resolve(
3163
+ opts.outputFile ?? `airs-agentguard-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID().slice(0, 8)}.${format === "html" ? "html" : "md"}`
3164
+ );
3165
+ try {
3166
+ if (path3) {
3167
+ const exists = await lstat(path3).catch((e) => {
3168
+ if (e.code !== "ENOENT") throw e;
3169
+ return void 0;
3170
+ });
3171
+ if (exists) throw new Error();
3172
+ }
3173
+ const data = await collectAgentGuardReport(await createClient(), {
3174
+ title: opts.title,
3175
+ maxPages: opts.maxPages
3176
+ });
3177
+ const output = format === "html" ? renderEnvironmentReportHtml(data) : renderEnvironmentReportMarkdown(data);
3178
+ if (path3) {
3179
+ await writeReportFile(path3, output);
3180
+ ui.status(`Report \u2192 ${path3.replace(/[\p{Cc}\p{Cf}]/gu, "_")} (mode 0600)`);
3181
+ } else
3182
+ await new Promise(
3183
+ (done, reject) => process.stdout.write(output, (error) => error ? reject(error) : done())
3184
+ );
3185
+ const incomplete = data.sources.filter((s) => s.status !== "complete").length;
3186
+ if (incomplete)
3187
+ ui.status(`${incomplete} source(s) incomplete; review the evidence section.`);
3188
+ if (data.health === "unknown" || opts.strict && incomplete) process.exitCode = 1;
3189
+ } catch {
3190
+ ui.error(
3191
+ "AgentGuard report failed. Check credentials and a writable, new output path. Existing files are never overwritten."
3192
+ );
3193
+ process.exitCode = 1;
3194
+ }
3195
+ });
3196
+ }
3197
+
3198
+ // src/cli/commands/aigateway.ts
3199
+ import {
3200
+ GatewayDefaultsInputSchema,
3201
+ GatewayRateLimitInputSchema,
3202
+ GatewayUsageLimitInputSchema
3203
+ } from "@cdot65/prisma-airs-sdk";
3204
+
3205
+ // src/airs/aigateway.ts
3206
+ import {
3207
+ AIGatewayChartFiltersSchema,
3208
+ AIGatewayClient
3209
+ } from "@cdot65/prisma-airs-sdk";
3210
+ function toLimitArray(value) {
3211
+ if (Array.isArray(value)) return value;
3212
+ if (value !== null && typeof value === "object") return [value];
3213
+ return [];
3214
+ }
3215
+ function normalizeWorkspace(raw) {
3216
+ return {
3217
+ id: raw.id,
3218
+ slug: raw.slug,
3219
+ name: raw.name,
3220
+ icon: raw.icon,
3221
+ description: raw.description,
3222
+ createdAt: raw.created_at,
3223
+ lastUpdatedAt: raw.last_updated_at,
3224
+ isDefault: Boolean(raw.is_default),
3225
+ status: raw.status,
3226
+ scopeName: raw.scope_name
3227
+ };
3228
+ }
3229
+ function normalizeWorkspaceDetail(raw) {
3230
+ return {
3231
+ ...normalizeWorkspace(raw),
3232
+ defaults: raw.defaults,
3233
+ usageLimits: toLimitArray(raw.usage_limits),
3234
+ rateLimits: toLimitArray(raw.rate_limits),
3235
+ securitySettings: raw.security_settings,
3236
+ dataPlaneSecuritySettings: raw.data_plane_security_settings,
3237
+ settings: raw.settings
3238
+ };
3239
+ }
3240
+ function aiGatewayGrantHint(err) {
3241
+ const status = err?.status ?? err?.statusCode;
3242
+ if (status !== 403) return void 0;
3243
+ const message = err instanceof Error ? err.message : String(err);
3244
+ const grant = message.includes("AB03") ? "the service account is missing a workspace-scope grant (data plane, /ai_gw/v2)" : "the service account is missing a tenant-root admin grant (admin plane, /ai_gw/admin/v2)";
3245
+ return `${grant}. SCM Access Management edits the existing role row by default \u2014 use "Add Role" so the account ends up with both role rows, not one row moved.`;
3246
+ }
3247
+ var SdkAiGatewayService = class {
3248
+ client;
3249
+ constructor(opts) {
3250
+ this.client = new AIGatewayClient(opts);
3251
+ }
3252
+ async listWorkspaces(options) {
3253
+ const response = await this.client.workspaces.list(options);
3254
+ return response.data.map(normalizeWorkspace);
3255
+ }
3256
+ async listAllWorkspaces() {
3257
+ const [active, archived] = await Promise.all([
3258
+ this.client.workspaces.list({ plane: "admin" }),
3259
+ this.client.workspaces.list({ plane: "admin", status: "archived" })
3260
+ ]);
3261
+ return [
3262
+ ...active.data,
3263
+ ...archived.data
3264
+ ].map(normalizeWorkspace);
3265
+ }
3266
+ async getWorkspace(workspaceRef, options) {
3267
+ try {
3268
+ const raw = await this.client.workspaces.get(workspaceRef, options);
3269
+ return normalizeWorkspaceDetail(raw);
3270
+ } catch (err) {
3271
+ const status = err.statusCode;
3272
+ if (status !== 404) throw err;
3273
+ const resolved = await this.resolveWorkspaceRef(workspaceRef, [
3274
+ options?.plane ?? "data",
3275
+ "admin"
3276
+ ]);
3277
+ if (resolved === workspaceRef) throw err;
3278
+ const raw = await this.client.workspaces.get(resolved, options);
3279
+ return normalizeWorkspaceDetail(raw);
3280
+ }
3281
+ }
3282
+ async createWorkspace(request) {
3283
+ const body = {
3284
+ name: request.name,
3285
+ scope_name: request.scopeName
3286
+ };
3287
+ if (request.description !== void 0) body.description = request.description;
3288
+ if (request.icon !== void 0) body.icon = request.icon;
3289
+ if (request.defaults !== void 0) body.defaults = request.defaults;
3290
+ if (request.users !== void 0) body.users = request.users;
3291
+ if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
3292
+ if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
3293
+ const created = await this.client.workspaces.create(body);
3294
+ return this.refetchAfterWrite(created.id, created);
2795
3295
  }
2796
- console.log();
2797
- }
2798
- function renderCustomerAppDetail(app) {
2799
- ui.section("Customer App Detail:");
2800
- const pairs = [];
2801
- if (app.id) pairs.push(["ID", app.id]);
2802
- pairs.push(["Name", app.name]);
2803
- if (app.description) pairs.push(["Desc", app.description]);
2804
- pairs.push(["Data", JSON.stringify(app.raw, null, 2).slice(0, 500)]);
2805
- ui.keyValue(pairs);
2806
- console.log();
2807
- }
2808
- function renderCustomerAppConsumption(data, format = "pretty") {
2809
- const fmt = (n, scale) => n == null ? "-" : `${n}${scale ?? ""}`;
2810
- if (format !== "pretty") {
2811
- const rows = data.detectors.map((d) => ({
2812
- app_name: data.appName,
2813
- app_id: data.appId,
2814
- monitoring_since: data.monitoringSince ?? "",
2815
- daily_avg: fmt(data.tokens.dailyAverage, data.tokens.dailyAverageScale),
2816
- monthly_total: fmt(data.tokens.monthlyTotal, data.tokens.monthlyTotalScale),
2817
- sessions_total: data.sessions.total,
2818
- sessions_violating: data.sessions.violating,
2819
- detector: d.type,
2820
- critical: d.critical,
2821
- high: d.high,
2822
- medium: d.medium,
2823
- low: d.low,
2824
- total: d.total
2825
- }));
2826
- console.log(
2827
- formatOutput(
2828
- rows,
2829
- [
2830
- { key: "app_name", label: "App" },
2831
- { key: "app_id", label: "AppId" },
2832
- { key: "monitoring_since", label: "MonitoringSince" },
2833
- { key: "daily_avg", label: "DailyAvg" },
2834
- { key: "monthly_total", label: "MonthlyTotal" },
2835
- { key: "sessions_total", label: "Sessions" },
2836
- { key: "sessions_violating", label: "Violating" },
2837
- { key: "detector", label: "Detector" },
2838
- { key: "critical", label: "C" },
2839
- { key: "high", label: "H" },
2840
- { key: "medium", label: "M" },
2841
- { key: "low", label: "L" },
2842
- { key: "total", label: "Total" }
2843
- ],
2844
- format
2845
- )
2846
- );
2847
- return;
3296
+ async updateWorkspace(workspaceRef, request) {
3297
+ const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
3298
+ const body = {};
3299
+ if (request.name !== void 0) body.name = request.name;
3300
+ if (request.description !== void 0) body.description = request.description;
3301
+ if (request.icon !== void 0) body.icon = request.icon;
3302
+ if (request.defaults !== void 0) body.defaults = request.defaults;
3303
+ if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
3304
+ if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
3305
+ await this.client.workspaces.update(ref, body);
3306
+ return this.getWorkspace(ref, { plane: "admin" });
2848
3307
  }
2849
- ui.header(data.appName, `(${data.appId})`);
2850
- const pairs = [];
2851
- if (data.monitoringSince) pairs.push(["Monitoring since", data.monitoringSince]);
2852
- if (data.source) pairs.push(["Source", data.source]);
2853
- if (data.cloud) pairs.push(["Cloud", data.cloud]);
2854
- if (data.profiles.length > 0) pairs.push(["Profiles", data.profiles.join(", ")]);
2855
- if (pairs.length > 0) ui.keyValue(pairs);
2856
- ui.section("Token consumption:");
2857
- ui.keyValue([
2858
- ["Daily avg", fmt(data.tokens.dailyAverage, data.tokens.dailyAverageScale)],
2859
- ["Monthly total", fmt(data.tokens.monthlyTotal, data.tokens.monthlyTotalScale)]
2860
- ]);
2861
- ui.section("Sessions:");
2862
- ui.keyValue([
2863
- ["Total", data.sessions.total],
2864
- ["Violating", data.sessions.violating]
2865
- ]);
2866
- const firing = data.detectors.filter((d) => d.total > 0);
2867
- ui.section(
2868
- `Detectors (${data.totalViolating} violating, ${firing.length}/${data.detectors.length} firing):`
2869
- );
2870
- if (firing.length === 0) {
2871
- ui.dim("no detector violations in window");
2872
- } else {
2873
- ui.table(
2874
- [
2875
- { key: "detector", label: "Detector" },
2876
- { key: "total", label: "Total" },
2877
- { key: "severity", label: "Severity" }
2878
- ],
2879
- firing.map((d) => ({
2880
- detector: d.type,
2881
- total: d.total,
2882
- severity: `c=${d.critical} h=${d.high} m=${d.medium} l=${d.low}`
2883
- }))
2884
- );
3308
+ async deleteWorkspace(workspaceRef) {
3309
+ const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
3310
+ await this.client.workspaces.delete(ref);
2885
3311
  }
2886
- console.log();
2887
- }
2888
- function renderDeploymentProfileList(profiles, format = "pretty") {
2889
- if (profiles.length === 0) {
2890
- ui.emptyList("deployment profiles");
2891
- return;
3312
+ /**
3313
+ * The API accepts only a UUID or slug as a workspace ref — a display name
3314
+ * gets a misleading 400 AB01 ("No update fields provided") on writes.
3315
+ * Match a user-supplied ref against the workspace list so name | slug |
3316
+ * uuid all work. Unmatched refs pass through so the API's own error stands.
3317
+ */
3318
+ async resolveWorkspaceRef(ref, planes, requireSlug = false) {
3319
+ for (const plane of planes) {
3320
+ let rows;
3321
+ try {
3322
+ rows = await this.listWorkspaces({ plane });
3323
+ } catch {
3324
+ continue;
3325
+ }
3326
+ const byRef = rows.find((w) => w.id === ref || w.slug === ref);
3327
+ if (byRef) return requireSlug ? byRef.slug : ref;
3328
+ const byName = rows.filter((w) => w.name === ref);
3329
+ if (byName.length > 1) {
3330
+ throw new Error(
3331
+ `workspace name '${ref}' is ambiguous (${byName.map((w) => w.slug).join(", ")}) \u2014 use a slug or UUID`
3332
+ );
3333
+ }
3334
+ if (byName.length === 1) return byName[0].slug;
3335
+ }
3336
+ return ref;
2892
3337
  }
2893
- if (format !== "pretty") {
2894
- const rows = profiles.map((p) => ({
2895
- name: p.raw.dp_name ?? p.raw.profile_name ?? p.raw.name ?? "",
2896
- status: p.raw.status ?? "",
2897
- authCode: p.raw.auth_code ?? ""
2898
- }));
2899
- console.log(
2900
- formatOutput(
2901
- rows,
2902
- [
2903
- { key: "name", label: "Name" },
2904
- { key: "status", label: "Status" },
2905
- { key: "authCode", label: "Auth Code" }
2906
- ],
2907
- format
2908
- )
2909
- );
2910
- return;
3338
+ async getTelemetryCost(opts) {
3339
+ const { workspaceSlug: workspaceRef, days = 7, ...rawFilters } = opts;
3340
+ const filters = AIGatewayChartFiltersSchema.parse(rawFilters);
3341
+ if (!Number.isSafeInteger(days) || days <= 0)
3342
+ throw new Error("Expected days to be a positive integer");
3343
+ const workspaceSlug = await this.resolveWorkspaceRef(workspaceRef, ["data", "admin"], true);
3344
+ const raw = await this.client.telemetry.cost({
3345
+ workspaceSlug,
3346
+ days,
3347
+ ...filters
3348
+ });
3349
+ return {
3350
+ workspaceSlug,
3351
+ days,
3352
+ totalCents: raw.data.total,
3353
+ totalUsd: raw.data.total / 100,
3354
+ avgCents: raw.data.avg,
3355
+ avgUsd: raw.data.avg / 100,
3356
+ quotaExceeded: raw.data.isQuotaExceeded,
3357
+ records: raw.data.records.map((r) => ({
3358
+ date: r.x,
3359
+ costCents: r.y,
3360
+ costUsd: r.y / 100
3361
+ }))
3362
+ };
2911
3363
  }
2912
- ui.section("Deployment Profiles:");
2913
- for (const p of profiles) {
2914
- const name = p.raw.dp_name ?? p.raw.profile_name ?? p.raw.name ?? "unknown";
2915
- const status = p.raw.status;
2916
- const authCode = p.raw.auth_code;
2917
- const statusColor5 = status === "active" ? chalk9.green : chalk9.dim;
2918
- console.log(
2919
- ` ${name}${status ? ` ${statusColor5(status)}` : ""}${authCode ? ` ${chalk9.dim(authCode)}` : ""}`
2920
- );
3364
+ /** Re-read after a write, falling back to the (partial) write response if the get fails. */
3365
+ async refetchAfterWrite(workspaceRef, writeResponse) {
3366
+ try {
3367
+ return await this.getWorkspace(workspaceRef, { plane: "admin" });
3368
+ } catch {
3369
+ return normalizeWorkspaceDetail(writeResponse);
3370
+ }
2921
3371
  }
2922
- console.log();
2923
- }
3372
+ };
2924
3373
 
2925
3374
  // src/cli/confirm.ts
2926
3375
  async function confirmOrAbort(message, force, options = {}) {
@@ -2958,8 +3407,8 @@ import {
2958
3407
  GatewayInferenceInputCreateEmbeddingRequestSchema,
2959
3408
  GatewayInferenceInputCreateResponseSchema
2960
3409
  } from "@cdot65/prisma-airs-sdk";
2961
- import { dump as dump3 } from "js-yaml";
2962
- function integer(raw, flag) {
3410
+ import { dump as dump4 } from "js-yaml";
3411
+ function integer2(raw, flag) {
2963
3412
  const value = Number(raw);
2964
3413
  if (!Number.isSafeInteger(value) || value < 1)
2965
3414
  throw new CliUsageError(`${flag} must be a positive integer`);
@@ -2992,11 +3441,11 @@ async function inferenceBody(kind, prompt, opts, defaultModel) {
2992
3441
  body.model = model;
2993
3442
  if (opts.stream !== void 0) body.stream = opts.stream;
2994
3443
  if (opts.maxTokens !== void 0)
2995
- body[kind === "responses" ? "max_output_tokens" : "max_completion_tokens"] = integer(
3444
+ body[kind === "responses" ? "max_output_tokens" : "max_completion_tokens"] = integer2(
2996
3445
  opts.maxTokens,
2997
3446
  "--max-tokens"
2998
3447
  );
2999
- if (opts.dimensions !== void 0) body.dimensions = integer(opts.dimensions, "--dimensions");
3448
+ if (opts.dimensions !== void 0) body.dimensions = integer2(opts.dimensions, "--dimensions");
3000
3449
  if (opts.encoding !== void 0) body.encoding_format = opts.encoding;
3001
3450
  if (kind === "responses" && body.store === void 0) body.store = false;
3002
3451
  return body;
@@ -3052,14 +3501,14 @@ function registerAiGatewayInference(parent) {
3052
3501
  const client = new AIGatewayInferenceClient({
3053
3502
  endpoint: config.aiGwInferenceEndpoint,
3054
3503
  apiKey: config.aiGwInferenceApiKey,
3055
- timeoutMs: integer(opts.timeout ?? "60000", "--timeout"),
3504
+ timeoutMs: integer2(opts.timeout ?? "60000", "--timeout"),
3056
3505
  numRetries: 0
3057
3506
  });
3058
3507
  process.once("SIGINT", interrupt);
3059
3508
  process.once("SIGTERM", interrupt);
3060
3509
  process.stdout.on("error", outputError);
3061
- const write = (text2) => new Promise((resolve6, reject) => {
3062
- process.stdout.write(text2, (error) => error ? reject(error) : resolve6());
3510
+ const write = (text2) => new Promise((resolve7, reject) => {
3511
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve7());
3063
3512
  });
3064
3513
  const requestOptions = { signal: controller.signal };
3065
3514
  const result = kind === "chat" ? await client.createChatCompletion(
@@ -3086,7 +3535,7 @@ function registerAiGatewayInference(parent) {
3086
3535
  }
3087
3536
  if (format === "pretty") await write("\n");
3088
3537
  } else {
3089
- const text2 = format === "yaml" ? dump3(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
3538
+ const text2 = format === "yaml" ? dump4(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
3090
3539
  await write(`${text2}
3091
3540
  `);
3092
3541
  if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
@@ -3224,7 +3673,7 @@ function headersToRecord(headers) {
3224
3673
  }
3225
3674
  return headers;
3226
3675
  }
3227
- function installDebugLogger(logPath) {
3676
+ function installDebugLogger(logPath, options = {}) {
3228
3677
  mkdirSync(dirname(logPath), { recursive: true, mode: 448 });
3229
3678
  writeFileSync(logPath, "", { encoding: "utf8", mode: 384, flag: "wx" });
3230
3679
  const originalFetch = globalThis.fetch;
@@ -3239,7 +3688,7 @@ function installDebugLogger(logPath) {
3239
3688
  if (!isAirsUrl(url) && !inference) {
3240
3689
  return originalFetch(input, init2);
3241
3690
  }
3242
- const dashboard = /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url);
3691
+ const dashboard = options.omitBodies || /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url) || /\/aiag\//.test(new URL(url).pathname);
3243
3692
  const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
3244
3693
  const reqHeaders = redactHeaders(rawHeaders);
3245
3694
  const loggedUrl = redactUrl(url);
@@ -3317,7 +3766,7 @@ function installDebugLogger(logPath) {
3317
3766
  // src/cli/commands/aigateway/shared.ts
3318
3767
  import { open, readFile as readFile2, unlink } from "fs/promises";
3319
3768
  import { AIGatewayClient as AIGatewayClient2 } from "@cdot65/prisma-airs-sdk";
3320
- import { dump as dump4, load } from "js-yaml";
3769
+ import { dump as dump5, load } from "js-yaml";
3321
3770
  async function defaultClientFactory() {
3322
3771
  const config = await loadConfig();
3323
3772
  return new AIGatewayClient2(aiGatewayClientOptions(config));
@@ -3398,7 +3847,7 @@ function renderGatewayList(items, format, label) {
3398
3847
  return;
3399
3848
  }
3400
3849
  if (format === "yaml") {
3401
- console.log(dump4(items, { noRefs: true, lineWidth: -1 }).trimEnd());
3850
+ console.log(dump5(items, { noRefs: true, lineWidth: -1 }).trimEnd());
3402
3851
  return;
3403
3852
  }
3404
3853
  if (items.length === 0) {
@@ -3416,7 +3865,7 @@ function renderGatewayDetail(value, format) {
3416
3865
  return;
3417
3866
  }
3418
3867
  if (format === "yaml") {
3419
- console.log(dump4(item, { noRefs: true, lineWidth: -1 }).trimEnd());
3868
+ console.log(dump5(item, { noRefs: true, lineWidth: -1 }).trimEnd());
3420
3869
  return;
3421
3870
  }
3422
3871
  const rows = Object.entries(item).map(([key, raw]) => ({
@@ -4746,23 +5195,23 @@ function parseNonnegativePage(value) {
4746
5195
  return Number(value);
4747
5196
  }
4748
5197
 
4749
- // src/cli/commands/aigateway-dashboard.ts
4750
- import { lstat } from "fs/promises";
4751
- import { resolve } from "path";
5198
+ // src/cli/commands/aigateway-report.ts
5199
+ import { lstat as lstat2 } from "fs/promises";
5200
+ import { resolve as resolve2 } from "path";
4752
5201
  import { AIGatewayClient as AIGatewayClient3 } from "@cdot65/prisma-airs-sdk";
4753
- import { z } from "zod";
4754
- import { randomUUID } from "crypto";
4755
- function registerAiGatewayDashboardCommand(aigateway) {
4756
- const command = aigateway.command("dashboard").description("Generate an AI Gateway environment dashboard (read-only)").requiredOption("--workspace <ref>", "Workspace slug, UUID, or unique display name").option("--days <n>", "Rolling window in days (default: 1)", "1").option("--start <iso>", "Explicit ISO-8601 start; requires --end").option("--end <iso>", "Explicit ISO-8601 end; requires --start").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
5202
+ import { z as z2 } from "zod";
5203
+ import { randomUUID as randomUUID2 } from "crypto";
5204
+ function registerAiGatewayReportCommand(aigateway) {
5205
+ const command = aigateway.command("report").alias("dashboard").description("Generate an AI Gateway environment report (read-only)").requiredOption("--workspace <ref>", "Workspace slug, UUID, or unique display name").option("--days <n>", "Rolling window in days (default: 1)", "1").option("--start <iso>", "Explicit ISO-8601 start; requires --end").option("--end <iso>", "Explicit ISO-8601 end; requires --start").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
4757
5206
  "--output-file <path>",
4758
5207
  "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
4759
5208
  ).option("--title <text>", "Report title", "AI Gateway environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (50 transactions/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
4760
5209
  "after",
4761
5210
  examples(
4762
- "airs aigateway dashboard --workspace ws-develo-71f8d8 --output-file ./airs-daily.html",
4763
- "airs aigateway dashboard --workspace ws-develo-71f8d8 --output markdown --output-file ./airs-daily.md",
4764
- "airs aigateway dashboard --workspace ws-develo-71f8d8",
4765
- "airs aigateway dashboard --workspace ws-develo-71f8d8 --strict --max-pages 20 --output-file - > daily.html"
5211
+ "airs aigateway report --workspace ws-develo-71f8d8 --output-file ./airs-daily.html",
5212
+ "airs aigateway report --workspace ws-develo-71f8d8 --output markdown --output-file ./airs-daily.md",
5213
+ "airs aigateway report --workspace ws-develo-71f8d8",
5214
+ "airs aigateway report --workspace ws-develo-71f8d8 --strict --max-pages 20 --output-file - > daily.html"
4766
5215
  )
4767
5216
  ).action(async (opts) => {
4768
5217
  const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
@@ -4778,7 +5227,7 @@ function registerAiGatewayDashboardCommand(aigateway) {
4778
5227
  usageError("Use --days or --start/--end, not both");
4779
5228
  const end = opts.end ? new Date(opts.end) : /* @__PURE__ */ new Date();
4780
5229
  const start = opts.start ? new Date(opts.start) : new Date(end.getTime() - Number(opts.days) * 864e5);
4781
- const iso = z.string().datetime({ offset: true });
5230
+ const iso = z2.string().datetime({ offset: true });
4782
5231
  if (opts.start && (!iso.safeParse(opts.start).success || !iso.safeParse(opts.end).success) || !Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || start >= end)
4783
5232
  usageError("Provide a valid increasing ISO-8601 window with timezone");
4784
5233
  const maxPages = Number(opts.maxPages);
@@ -4788,11 +5237,11 @@ function registerAiGatewayDashboardCommand(aigateway) {
4788
5237
  usageError("--title must contain 1\u2013240 characters");
4789
5238
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
4790
5239
  usageError("--output-file cannot be empty");
4791
- const defaultName = `airs-aigateway-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
4792
- const outputPath = opts.outputFile === "-" ? void 0 : resolve(opts.outputFile ?? defaultName);
5240
+ const defaultName = `airs-aigateway-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID2().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
5241
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve2(opts.outputFile ?? defaultName);
4793
5242
  try {
4794
5243
  if (outputPath) {
4795
- const existing = await lstat(outputPath).catch((error) => {
5244
+ const existing = await lstat2(outputPath).catch((error) => {
4796
5245
  if (error.code !== "ENOENT") throw error;
4797
5246
  return void 0;
4798
5247
  });
@@ -4896,7 +5345,7 @@ function scopeNameLooksUnrelated(name, scopeName) {
4896
5345
  function registerAiGatewayCommand(program) {
4897
5346
  const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
4898
5347
  registerAiGatewayInventory(aigateway);
4899
- registerAiGatewayDashboardCommand(aigateway);
5348
+ registerAiGatewayReportCommand(aigateway);
4900
5349
  registerAiGatewayInference(aigateway);
4901
5350
  const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
4902
5351
  const workspaceList = workspace.command("list").description("List workspaces (default: active workspaces you are scoped to)").option("--plane <plane>", "Plane to read from: data (scoped) or admin (whole tenant)").option("--status <status>", "Filter by lifecycle state: active or archived").option("--all", "Merge active + archived admin-plane reads (whole tenant, both states)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
@@ -5339,7 +5788,7 @@ function registerConfigCommand(program) {
5339
5788
  }
5340
5789
 
5341
5790
  // src/cli/commands/doctor.ts
5342
- import { randomUUID as randomUUID2 } from "crypto";
5791
+ import { randomUUID as randomUUID3 } from "crypto";
5343
5792
  import { readFile as readFile4 } from "fs/promises";
5344
5793
  import { init, Scanner } from "@cdot65/prisma-airs-sdk";
5345
5794
  var DOCTOR_TIMEOUT_MS = 5e3;
@@ -5437,8 +5886,8 @@ async function withTimeout(promise, ms) {
5437
5886
  try {
5438
5887
  return await Promise.race([
5439
5888
  promise,
5440
- new Promise((resolve6) => {
5441
- timer = setTimeout(() => resolve6(TIMED_OUT), ms);
5889
+ new Promise((resolve7) => {
5890
+ timer = setTimeout(() => resolve7(TIMED_OUT), ms);
5442
5891
  })
5443
5892
  ]);
5444
5893
  } finally {
@@ -5593,7 +6042,7 @@ async function defaultScannerProbe() {
5593
6042
  const config = await loadConfig();
5594
6043
  init(runtimeInitOptions(config));
5595
6044
  const scanner = new Scanner();
5596
- return scanner.queryByScanIds([randomUUID2()]);
6045
+ return scanner.queryByScanIds([randomUUID3()]);
5597
6046
  }
5598
6047
  async function defaultMgmtProbe() {
5599
6048
  const config = await loadConfig();
@@ -5698,6 +6147,21 @@ function registerDoctorCommand(program) {
5698
6147
  // src/cli/commands/modelsecurity.ts
5699
6148
  import { execFileSync, spawn } from "child_process";
5700
6149
  import * as fs from "fs";
6150
+ import { z as z3 } from "zod";
6151
+ function supplyChainWindow(opts) {
6152
+ const parsed = z3.object({
6153
+ start: z3.string().datetime({ offset: true }).optional(),
6154
+ end: z3.string().datetime({ offset: true }).optional(),
6155
+ modelVersion: z3.string().uuid().optional()
6156
+ }).refine((v) => !v.start || !v.end || Date.parse(v.start) <= Date.parse(v.end)).safeParse(opts);
6157
+ if (!parsed.success)
6158
+ usageError("Use valid ISO timestamps with start <= end and a valid model-version UUID.");
6159
+ return {
6160
+ startTime: parsed.data.start,
6161
+ endTime: parsed.data.end,
6162
+ modelVersionUuid: parsed.data.modelVersion
6163
+ };
6164
+ }
5701
6165
  var VALID_EXTRAS = ["all", "aws", "gcp", "azure", "artifactory", "gitlab"];
5702
6166
  function hasBin(bin) {
5703
6167
  try {
@@ -5707,13 +6171,13 @@ function hasBin(bin) {
5707
6171
  return false;
5708
6172
  }
5709
6173
  }
5710
- function run(bin, args, label) {
5711
- return new Promise((resolve6, reject) => {
6174
+ function run2(bin, args, label) {
6175
+ return new Promise((resolve7, reject) => {
5712
6176
  const child = spawn(bin, args, { stdio: "inherit" });
5713
6177
  child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
5714
6178
  child.on("close", (code) => {
5715
6179
  if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
5716
- else resolve6();
6180
+ else resolve7();
5717
6181
  });
5718
6182
  });
5719
6183
  }
@@ -5847,7 +6311,7 @@ function registerModelSecurityCommand(program) {
5847
6311
  }
5848
6312
  for (const step of steps) {
5849
6313
  ui.status(`\u2192 ${step.label}`);
5850
- await run(step.bin, step.args, step.label);
6314
+ await run2(step.bin, step.args, step.label);
5851
6315
  }
5852
6316
  ui.success("model-security-client installed successfully.");
5853
6317
  if (useUv) {
@@ -6003,7 +6467,7 @@ function registerModelSecurityCommand(program) {
6003
6467
  }
6004
6468
  });
6005
6469
  const scans = ms.command("scans").description("Model security scan operations");
6006
- const scansList = scans.command("list").description("List model security scans").option("--eval-outcome <outcome>", "Filter by eval outcome").option("--source-type <type>", "Filter by source type").option("--scan-origin <origin>", "Filter by scan origin").option("--search <query>", "Search scans").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
6470
+ const scansList = scans.command("list").description("List model security scans").option("--model-version <uuid>", "Filter scans by model version UUID").option("--start <iso>", "Creation time start (ISO timestamp)").option("--end <iso>", "Creation time end (ISO timestamp)").option("--eval-outcome <outcome>", "Filter by eval outcome").option("--source-type <type>", "Filter by source type").option("--scan-origin <origin>", "Filter by scan origin").option("--search <query>", "Search scans").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
6007
6471
  "after",
6008
6472
  examples(
6009
6473
  "airs model-security scans list",
@@ -6017,6 +6481,7 @@ function registerModelSecurityCommand(program) {
6017
6481
  const service = await createService2();
6018
6482
  const listOptions = {
6019
6483
  evalOutcome: opts.evalOutcome,
6484
+ ...supplyChainWindow(opts),
6020
6485
  sourceType: opts.sourceType,
6021
6486
  scanOrigin: opts.scanOrigin,
6022
6487
  search: opts.search,
@@ -6111,7 +6576,7 @@ function registerModelSecurityCommand(program) {
6111
6576
  }
6112
6577
  });
6113
6578
  const models = ms.command("models").description("Browse the scanned model catalog (read-only)");
6114
- const modelsList = models.command("list").description("List models in the catalog").option("--search <text>", "Filter by search text").option("--search-query <text>", "Filter by model UUID or name").option("--sort-field <field>", "Sort field: created_at, updated_at").option("--sort-order <order>", "Sort order: asc, desc").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText("after", examples("airs model-security models list")).action(async (opts) => {
6579
+ const modelsList = models.command("list").description("List models in the catalog").option("--start <iso>", "Creation time start (ISO timestamp)").option("--end <iso>", "Creation time end (ISO timestamp)").option("--search <text>", "Filter by search text").option("--search-query <text>", "Filter by model UUID or name").option("--sort-field <field>", "Sort field: created_at, updated_at").option("--sort-order <order>", "Sort order: asc, desc").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText("after", examples("airs model-security models list")).action(async (opts) => {
6115
6580
  try {
6116
6581
  const fmt = await resolveOutput(modelsList, opts);
6117
6582
  if (fmt === "pretty") renderModelSecurityHeader();
@@ -6119,6 +6584,7 @@ function registerModelSecurityCommand(program) {
6119
6584
  const listOptions = {
6120
6585
  search: opts.search,
6121
6586
  searchQuery: opts.searchQuery,
6587
+ ...supplyChainWindow(opts),
6122
6588
  sortField: opts.sortField,
6123
6589
  sortOrder: opts.sortOrder,
6124
6590
  limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
@@ -6291,24 +6757,33 @@ async function backupTargets(opts) {
6291
6757
  return results;
6292
6758
  }
6293
6759
 
6294
- // src/cli/commands/redteam-dashboard.ts
6295
- import { lstat as lstat2 } from "fs/promises";
6296
- import { resolve as resolve2 } from "path";
6760
+ // src/cli/commands/redteam-report.ts
6761
+ import { lstat as lstat3 } from "fs/promises";
6762
+ import { resolve as resolve3 } from "path";
6297
6763
  import { RedTeamClient } from "@cdot65/prisma-airs-sdk";
6298
- import { randomUUID as randomUUID3 } from "crypto";
6299
- function registerRedTeamDashboardCommand(redteam) {
6300
- const command = redteam.command("dashboard").description("Generate a Red Team environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
6764
+ import { randomUUID as randomUUID4 } from "crypto";
6765
+ function registerRedTeamReportCommand(redteam, scanReport) {
6766
+ const command = redteam.command("report [jobId]").alias("dashboard").description("Generate an environment report, or view an individual scan report by job ID").option("--attacks", "Individual scan only: include attack list", false).option("--severity <level>", "Individual scan only: filter attacks by severity").option("--limit <n>", "Individual scan only: max attacks to show", "20").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
6301
6767
  "--output-file <path>",
6302
6768
  "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
6303
6769
  ).option("--title <text>", "Report title", "Red Team environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (15 records/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
6304
6770
  "after",
6305
6771
  examples(
6306
- "airs redteam dashboard --output-file ./airs-daily.html",
6307
- "airs redteam dashboard --output markdown --output-file ./airs-daily.md",
6308
- "airs redteam dashboard",
6309
- "airs redteam dashboard --strict --max-pages 20 --output-file - > daily.html"
6772
+ "airs redteam report --output-file ./airs-daily.html",
6773
+ "airs redteam report --output markdown --output-file ./airs-daily.md",
6774
+ "airs redteam report",
6775
+ "airs redteam report --strict --max-pages 20 --output-file - > daily.html"
6310
6776
  )
6311
- ).action(async (opts) => {
6777
+ ).action(async (jobId, opts) => {
6778
+ const explicit = (name) => command.getOptionValueSource(name) === "cli";
6779
+ if (jobId !== void 0) {
6780
+ if (["output", "outputFile", "title", "maxPages", "strict"].some(explicit) || ["html", "markdown"].includes(command.parent?.parent?.opts().output))
6781
+ usageError("Environment report options cannot be combined with a scan job ID");
6782
+ await scanReport(jobId, opts);
6783
+ return;
6784
+ }
6785
+ if (["attacks", "severity", "limit"].some(explicit))
6786
+ usageError("--attacks, --severity and --limit require a scan job ID");
6312
6787
  const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
6313
6788
  if (format !== "html" && format !== "markdown")
6314
6789
  usageError("Report output must be html or markdown");
@@ -6319,11 +6794,11 @@ function registerRedTeamDashboardCommand(redteam) {
6319
6794
  usageError("--title must contain 1\u2013240 characters");
6320
6795
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
6321
6796
  usageError("--output-file cannot be empty");
6322
- const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID3().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
6323
- const outputPath = opts.outputFile === "-" ? void 0 : resolve2(opts.outputFile ?? defaultName);
6797
+ const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID4().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
6798
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve3(opts.outputFile ?? defaultName);
6324
6799
  try {
6325
6800
  if (outputPath) {
6326
- const existing = await lstat2(outputPath).catch((error) => {
6801
+ const existing = await lstat3(outputPath).catch((error) => {
6327
6802
  if (error.code !== "ENOENT") throw error;
6328
6803
  return void 0;
6329
6804
  });
@@ -6603,7 +7078,6 @@ function parseAdapterVariables(input) {
6603
7078
  }
6604
7079
  function registerRedteamCommand(program) {
6605
7080
  const redteam = program.command("redteam").description("AI Red Team scan operations");
6606
- registerRedTeamDashboardCommand(redteam);
6607
7081
  redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
6608
7082
  try {
6609
7083
  renderRedteamHeader();
@@ -6986,7 +7460,7 @@ function registerRedteamCommand(program) {
6986
7460
  fail(err);
6987
7461
  }
6988
7462
  });
6989
- redteam.command("report <jobId>").description("View scan report").option("--attacks", "Include attack list", false).option("--severity <level>", "Filter attacks by severity").option("--limit <n>", "Max attacks to show", "20").action(async (jobId, opts) => {
7463
+ registerRedTeamReportCommand(redteam, async (jobId, opts) => {
6990
7464
  try {
6991
7465
  renderRedteamHeader();
6992
7466
  const service = await createService3();
@@ -7597,7 +8071,7 @@ function registerRedteamCommand(program) {
7597
8071
  }
7598
8072
 
7599
8073
  // src/cli/commands/runtime.ts
7600
- import { randomUUID as randomUUID7 } from "crypto";
8074
+ import { randomUUID as randomUUID8 } from "crypto";
7601
8075
  import * as fs5 from "fs";
7602
8076
  import { readFile as readFile10 } from "fs/promises";
7603
8077
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
@@ -7845,7 +8319,7 @@ function mergeProfilePolicy(existing, overrides) {
7845
8319
  }
7846
8320
 
7847
8321
  // src/cli/bulk-scan-lock.ts
7848
- import { randomUUID as randomUUID4 } from "crypto";
8322
+ import { randomUUID as randomUUID5 } from "crypto";
7849
8323
  import * as fs3 from "fs/promises";
7850
8324
  function errorCode(error) {
7851
8325
  return error?.code;
@@ -7876,7 +8350,7 @@ function processIsAlive(pid) {
7876
8350
  }
7877
8351
  }
7878
8352
  async function installLock(lockPath, record) {
7879
- const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID4()}`;
8353
+ const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID5()}`;
7880
8354
  try {
7881
8355
  await fs3.writeFile(candidate, JSON.stringify(record), {
7882
8356
  encoding: "utf-8",
@@ -7894,7 +8368,7 @@ async function acquireBulkScanLock(statePath) {
7894
8368
  version: 1,
7895
8369
  pid: process.pid,
7896
8370
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7897
- token: randomUUID4()
8371
+ token: randomUUID5()
7898
8372
  };
7899
8373
  for (let attempt = 0; attempt < 2; attempt++) {
7900
8374
  try {
@@ -7930,32 +8404,32 @@ async function acquireBulkScanLock(statePath) {
7930
8404
  }
7931
8405
 
7932
8406
  // src/cli/bulk-scan-state.ts
7933
- import { randomUUID as randomUUID5 } from "crypto";
8407
+ import { randomUUID as randomUUID6 } from "crypto";
7934
8408
  import * as fs4 from "fs/promises";
7935
8409
  import * as path2 from "path";
7936
- import { z as z2 } from "zod";
7937
- var BulkScanResultSchema = z2.object({
7938
- index: z2.number().int().nonnegative(),
7939
- reqId: z2.number().int().nonnegative(),
7940
- prompt: z2.string(),
7941
- response: z2.string().optional(),
7942
- scanId: z2.string(),
7943
- reportId: z2.string(),
7944
- action: z2.enum(["allow", "block", "failed"]),
7945
- category: z2.string(),
7946
- triggered: z2.boolean(),
7947
- detections: z2.record(z2.boolean()),
7948
- error: z2.string().optional()
8410
+ import { z as z4 } from "zod";
8411
+ var BulkScanResultSchema = z4.object({
8412
+ index: z4.number().int().nonnegative(),
8413
+ reqId: z4.number().int().nonnegative(),
8414
+ prompt: z4.string(),
8415
+ response: z4.string().optional(),
8416
+ scanId: z4.string(),
8417
+ reportId: z4.string(),
8418
+ action: z4.enum(["allow", "block", "failed"]),
8419
+ category: z4.string(),
8420
+ triggered: z4.boolean(),
8421
+ detections: z4.record(z4.boolean()),
8422
+ error: z4.string().optional()
7949
8423
  });
7950
- var BulkScanItemSchema = z2.object({
7951
- index: z2.number().int().nonnegative(),
7952
- reqId: z2.number().int().nonnegative(),
7953
- prompt: z2.string(),
7954
- status: z2.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
7955
- scanId: z2.string().min(1).optional(),
7956
- receiptReportId: z2.string().optional(),
8424
+ var BulkScanItemSchema = z4.object({
8425
+ index: z4.number().int().nonnegative(),
8426
+ reqId: z4.number().int().nonnegative(),
8427
+ prompt: z4.string(),
8428
+ status: z4.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
8429
+ scanId: z4.string().min(1).optional(),
8430
+ receiptReportId: z4.string().optional(),
7957
8431
  result: BulkScanResultSchema.optional(),
7958
- error: z2.string().optional()
8432
+ error: z4.string().optional()
7959
8433
  }).superRefine((item, ctx) => {
7960
8434
  if (item.reqId !== item.index) {
7961
8435
  ctx.addIssue({ code: "custom", message: "reqId must match the stable input index" });
@@ -7993,15 +8467,15 @@ var BulkScanItemSchema = z2.object({
7993
8467
  }
7994
8468
  }
7995
8469
  });
7996
- var BulkScanStateSchema = z2.object({
7997
- version: z2.literal(2),
7998
- profile: z2.string().min(1),
7999
- sessionId: z2.string().optional(),
8000
- outputFile: z2.string().min(1),
8001
- batchSize: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8002
- createdAt: z2.string().datetime(),
8003
- updatedAt: z2.string().datetime(),
8004
- items: z2.array(BulkScanItemSchema).min(1)
8470
+ var BulkScanStateSchema = z4.object({
8471
+ version: z4.literal(2),
8472
+ profile: z4.string().min(1),
8473
+ sessionId: z4.string().optional(),
8474
+ outputFile: z4.string().min(1),
8475
+ batchSize: z4.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8476
+ createdAt: z4.string().datetime(),
8477
+ updatedAt: z4.string().datetime(),
8478
+ items: z4.array(BulkScanItemSchema).min(1)
8005
8479
  }).superRefine((state, ctx) => {
8006
8480
  const indices = /* @__PURE__ */ new Set();
8007
8481
  for (const [position, item] of state.items.entries()) {
@@ -8027,8 +8501,8 @@ async function saveBulkScanState(state, dir, filePath) {
8027
8501
  }
8028
8502
  await fs4.mkdir(dir, { recursive: true, mode: 448 });
8029
8503
  if (!filePath) await fs4.chmod(dir, 448);
8030
- const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID5()}.bulk-scan.json`);
8031
- const temporary = `${target}.tmp-${process.pid}-${randomUUID5()}`;
8504
+ const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID6()}.bulk-scan.json`);
8505
+ const temporary = `${target}.tmp-${process.pid}-${randomUUID6()}`;
8032
8506
  try {
8033
8507
  await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
8034
8508
  encoding: "utf-8",
@@ -9153,11 +9627,11 @@ function registerCleanupCommand(parent) {
9153
9627
  }
9154
9628
 
9155
9629
  // src/cli/commands/runtime-dashboard.ts
9156
- import { lstat as lstat3 } from "fs/promises";
9157
- import { resolve as resolve4 } from "path";
9158
- import { dump as dump5 } from "js-yaml";
9630
+ import { lstat as lstat4 } from "fs/promises";
9631
+ import { resolve as resolve5 } from "path";
9632
+ import { dump as dump6 } from "js-yaml";
9159
9633
  var TIME_UNITS = ["hour", "hours", "day", "days"];
9160
- function integer2(value, name, minimum = 0) {
9634
+ function integer3(value, name, minimum = 0) {
9161
9635
  const n = Number(value);
9162
9636
  if (!/^\d+$/.test(String(value)) || !Number.isSafeInteger(n) || n < minimum)
9163
9637
  throw new CliUsageError(`${name} must be a safe integer >= ${minimum}`);
@@ -9169,7 +9643,7 @@ function text(value, name) {
9169
9643
  }
9170
9644
  function window(opts) {
9171
9645
  return {
9172
- timeInterval: integer2(opts.interval, "--interval", 1),
9646
+ timeInterval: integer3(opts.interval, "--interval", 1),
9173
9647
  timeUnit: text(opts.unit, "--unit")
9174
9648
  };
9175
9649
  }
@@ -9184,7 +9658,7 @@ function identity(opts) {
9184
9658
  function scan(opts) {
9185
9659
  return {
9186
9660
  scanId: text(opts.scanId, "--scan-id"),
9187
- scanSubReqId: integer2(opts.scanSubReqId, "--scan-sub-req-id")
9661
+ scanSubReqId: integer3(opts.scanSubReqId, "--scan-sub-req-id")
9188
9662
  };
9189
9663
  }
9190
9664
  function timeOptions(command, interval = "1", unit = "day", allowedUnits = TIME_UNITS) {
@@ -9201,7 +9675,7 @@ function timeOptions(command, interval = "1", unit = "day", allowedUnits = TIME_
9201
9675
  unit
9202
9676
  );
9203
9677
  }
9204
- function pageOptions(command) {
9678
+ function pageOptions2(command) {
9205
9679
  return command.option("--limit <n>", "Items per page", "25").option("--offset <n>", "Item offset", "0");
9206
9680
  }
9207
9681
  function sessionOptions(command) {
@@ -9230,8 +9704,8 @@ function registerRuntimeDashboardCommands(runtime) {
9230
9704
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
9231
9705
  );
9232
9706
  if (opts.outputFile) {
9233
- const path3 = resolve4(text(opts.outputFile, "--output-file"));
9234
- const existing = await lstat3(path3).catch((error) => {
9707
+ const path3 = resolve5(text(opts.outputFile, "--output-file"));
9708
+ const existing = await lstat4(path3).catch((error) => {
9235
9709
  if (error.code !== "ENOENT") throw error;
9236
9710
  return void 0;
9237
9711
  });
@@ -9244,9 +9718,9 @@ function registerRuntimeDashboardCommands(runtime) {
9244
9718
  numRetries: 0
9245
9719
  });
9246
9720
  const result = await read(client.dashboard);
9247
- const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
9721
+ const output = format === "yaml" ? dump6(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
9248
9722
  if (command.name() === "scan-content" && opts.outputFile) {
9249
- await writeReportFile(resolve4(String(opts.outputFile)), `${output}
9723
+ await writeReportFile(resolve5(String(opts.outputFile)), `${output}
9250
9724
  `);
9251
9725
  ui.status("Scan content exported to the requested private file (0600).");
9252
9726
  } else console.log(output);
@@ -9261,7 +9735,7 @@ function registerRuntimeDashboardCommands(runtime) {
9261
9735
  });
9262
9736
  }
9263
9737
  register6(
9264
- pageOptions(
9738
+ pageOptions2(
9265
9739
  timeOptions(
9266
9740
  dashboard.command("applications").description("One page of application activity"),
9267
9741
  "1",
@@ -9270,19 +9744,19 @@ function registerRuntimeDashboardCommands(runtime) {
9270
9744
  )
9271
9745
  ),
9272
9746
  (opts) => {
9273
- const interval = integer2(opts.interval, "--interval", 1);
9747
+ const interval = integer3(opts.interval, "--interval", 1);
9274
9748
  const unit = text(opts.unit, "--unit");
9275
9749
  if (![1, 7, 30, 60].includes(interval) || !["day", "days", "hour"].includes(unit))
9276
9750
  throw new CliUsageError(
9277
9751
  "Application overview supports interval 1/7/30/60 and unit day/days/hour"
9278
9752
  );
9279
- const query = {
9753
+ const query2 = {
9280
9754
  timeInterval: interval,
9281
9755
  timeUnit: unit,
9282
- limit: integer2(opts.limit, "--limit", 1),
9283
- offset: integer2(opts.offset, "--offset")
9756
+ limit: integer3(opts.limit, "--limit", 1),
9757
+ offset: integer3(opts.offset, "--offset")
9284
9758
  };
9285
- return (client) => client.applicationsOverview(query);
9759
+ return (client) => client.applicationsOverview(query2);
9286
9760
  }
9287
9761
  );
9288
9762
  for (const [name, method] of [
@@ -9297,16 +9771,16 @@ function registerRuntimeDashboardCommands(runtime) {
9297
9771
  ["days"]
9298
9772
  ),
9299
9773
  (opts) => {
9300
- const interval = integer2(opts.interval, "--interval", 1);
9774
+ const interval = integer3(opts.interval, "--interval", 1);
9301
9775
  if (![7, 30, 60].includes(interval) || opts.unit !== "days")
9302
9776
  throw new CliUsageError("Application detail requires 7, 30, or 60 days");
9303
- const query = {
9777
+ const query2 = {
9304
9778
  appId: text(opts.appId, "--app-id"),
9305
9779
  appName: text(opts.appName, "--app-name"),
9306
9780
  timeInterval: interval,
9307
9781
  timeUnit: "days"
9308
9782
  };
9309
- return (client) => client[method](query);
9783
+ return (client) => client[method](query2);
9310
9784
  }
9311
9785
  );
9312
9786
  }
@@ -9318,35 +9792,35 @@ function registerRuntimeDashboardCommands(runtime) {
9318
9792
  register6(
9319
9793
  timeOptions(dashboard.command(name), days ? "30" : "1", days ? "days" : "day"),
9320
9794
  (opts) => {
9321
- const query = window(opts);
9322
- return (client) => client[method](query);
9795
+ const query2 = window(opts);
9796
+ return (client) => client[method](query2);
9323
9797
  }
9324
9798
  );
9325
9799
  }
9326
9800
  register6(timeOptions(sessions.command("chart")), (opts) => {
9327
- const query = window(opts);
9328
- return (client) => client.sessionsChart(query);
9801
+ const query2 = window(opts);
9802
+ return (client) => client.sessionsChart(query2);
9329
9803
  });
9330
9804
  register6(
9331
- pageOptions(
9805
+ pageOptions2(
9332
9806
  timeOptions(
9333
9807
  sessions.command("list").description("Session entries; --all walks pages with completeness checks")
9334
9808
  )
9335
9809
  ).option("--all", "Walk all pages (up to 100 pages)").option("--max <n>", "Item cap with --all; 0 removes item cap", "10000"),
9336
9810
  (opts) => {
9337
- const query = {
9811
+ const query2 = {
9338
9812
  ...window(opts),
9339
- limit: integer2(opts.limit, "--limit", 1),
9340
- offset: integer2(opts.offset, "--offset")
9813
+ limit: integer3(opts.limit, "--limit", 1),
9814
+ offset: integer3(opts.offset, "--offset")
9341
9815
  };
9342
- const maximum = integer2(opts.max, "--max");
9816
+ const maximum = integer3(opts.max, "--max");
9343
9817
  return async (client) => {
9344
9818
  const items = [];
9345
9819
  const seen = /* @__PURE__ */ new Set();
9346
9820
  let expectedTotal;
9347
- let offset = query.offset;
9821
+ let offset = query2.offset;
9348
9822
  for (let pageIndex = 0; pageIndex < 100; pageIndex++) {
9349
- const page = await client.sessionsOverview({ ...query, offset });
9823
+ const page = await client.sessionsOverview({ ...query2, offset });
9350
9824
  if (!opts.all) {
9351
9825
  ui.status(
9352
9826
  `Returned ${page.items.length} of ${page.pagination.total_items ?? "unknown"} sessions; this is one page.`
@@ -9354,7 +9828,7 @@ function registerRuntimeDashboardCommands(runtime) {
9354
9828
  return page.items;
9355
9829
  }
9356
9830
  const total = page.pagination.total_items;
9357
- if (!Number.isSafeInteger(total) || total < 0 || page.pagination.skip !== offset || page.pagination.limit !== query.limit || page.items.length > query.limit || expectedTotal !== void 0 && total !== expectedTotal)
9831
+ if (!Number.isSafeInteger(total) || total < 0 || page.pagination.skip !== offset || page.pagination.limit !== query2.limit || page.items.length > query2.limit || expectedTotal !== void 0 && total !== expectedTotal)
9358
9832
  throw new Error("Unstable pagination");
9359
9833
  expectedTotal = total;
9360
9834
  for (const item of page.items) {
@@ -9377,7 +9851,7 @@ function registerRuntimeDashboardCommands(runtime) {
9377
9851
  }
9378
9852
  if (offset === total) {
9379
9853
  ui.status(
9380
- `Retrieved ${items.length} sessions from offset ${query.offset}; pagination complete.`
9854
+ `Retrieved ${items.length} sessions from offset ${query2.offset}; pagination complete.`
9381
9855
  );
9382
9856
  return items;
9383
9857
  }
@@ -9393,35 +9867,35 @@ function registerRuntimeDashboardCommands(runtime) {
9393
9867
  };
9394
9868
  }
9395
9869
  );
9396
- register6(pageOptions(sessionOptions(sessions.command("get"))), (opts) => {
9397
- const query = {
9870
+ register6(pageOptions2(sessionOptions(sessions.command("get"))), (opts) => {
9871
+ const query2 = {
9398
9872
  ...identity(opts),
9399
- limit: integer2(opts.limit, "--limit", 1),
9400
- offset: integer2(opts.offset, "--offset")
9873
+ limit: integer3(opts.limit, "--limit", 1),
9874
+ offset: integer3(opts.offset, "--offset")
9401
9875
  };
9402
- return (client) => client.session(query);
9876
+ return (client) => client.session(query2);
9403
9877
  });
9404
9878
  register6(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
9405
- const query = { ...identity(opts), ...scan(opts) };
9406
- return (client) => client.sessionTransaction(query);
9879
+ const query2 = { ...identity(opts), ...scan(opts) };
9880
+ return (client) => client.sessionTransaction(query2);
9407
9881
  });
9408
9882
  register6(
9409
9883
  scanOptions(
9410
9884
  sessions.command("scan-content").description("Explicit sensitive scan-content read; never automatically fetched")
9411
9885
  ).option("--show-content", "Explicitly display stored content on stdout").option("--output-file <path>", "Private new file for stored content (no overwrite)"),
9412
9886
  (opts) => {
9413
- const query = scan(opts);
9414
- return (client) => client.scanContent(query);
9887
+ const query2 = scan(opts);
9888
+ return (client) => client.scanContent(query2);
9415
9889
  }
9416
9890
  );
9417
9891
  }
9418
9892
 
9419
9893
  // src/cli/commands/runtime-report.ts
9420
- import { lstat as lstat4 } from "fs/promises";
9421
- import { resolve as resolve5 } from "path";
9422
- import { randomUUID as randomUUID6 } from "crypto";
9894
+ import { lstat as lstat5 } from "fs/promises";
9895
+ import { resolve as resolve6 } from "path";
9896
+ import { randomUUID as randomUUID7 } from "crypto";
9423
9897
  function registerRuntimeReportCommand(runtime) {
9424
- const command = runtime.command("report").description("Generate a daily AI Runtime Security environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
9898
+ const command = runtime.command("report").description("Generate a daily AI Runtime Security environment report (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
9425
9899
  "--output-file <path>",
9426
9900
  "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
9427
9901
  ).option("--title <text>", "Report title", "Daily environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (25 sessions/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
@@ -9443,11 +9917,11 @@ function registerRuntimeReportCommand(runtime) {
9443
9917
  usageError("--title must contain 1\u2013240 characters");
9444
9918
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
9445
9919
  usageError("--output-file cannot be empty");
9446
- const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID6().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9447
- const outputPath = opts.outputFile === "-" ? void 0 : resolve5(opts.outputFile ?? defaultName);
9920
+ const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID7().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9921
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve6(opts.outputFile ?? defaultName);
9448
9922
  try {
9449
9923
  if (outputPath) {
9450
- const existing = await lstat4(outputPath).catch((error) => {
9924
+ const existing = await lstat5(outputPath).catch((error) => {
9451
9925
  if (error.code !== "ENOENT") throw error;
9452
9926
  return void 0;
9453
9927
  });
@@ -9934,7 +10408,7 @@ function completedBulkResults(state) {
9934
10408
  }
9935
10409
  async function writeBulkResults(outputPath, results) {
9936
10410
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
9937
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID7()}`;
10411
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID8()}`;
9938
10412
  try {
9939
10413
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
9940
10414
  encoding: "utf-8",
@@ -10737,7 +11211,8 @@ var READ_COMMAND_NAMES = /* @__PURE__ */ new Set([
10737
11211
  "version",
10738
11212
  "versions",
10739
11213
  "violation",
10740
- "violations"
11214
+ "violations",
11215
+ "vulnerabilities"
10741
11216
  ]);
10742
11217
  function applyListDeleteAliases(cmd) {
10743
11218
  for (const sub of cmd.commands) {
@@ -10779,12 +11254,14 @@ function buildProgram() {
10779
11254
  program.hook("preAction", async (_thisCommand, actionCommand) => {
10780
11255
  const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
10781
11256
  setQuiet(Boolean(root.quiet));
10782
- const isEnvironmentReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime" || actionCommand.name() === "dashboard" && ["redteam", "aigateway"].includes(actionCommand.parent?.name() ?? "");
11257
+ const isEnvironmentReport = actionCommand.name() === "report" && ["runtime", "redteam", "aigateway", "agentguard"].includes(
11258
+ actionCommand.parent?.name() ?? ""
11259
+ ) && !(actionCommand.parent?.name() === "redteam" && actionCommand.args.length > 0);
10783
11260
  if (isEnvironmentReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10784
11261
  usageError(
10785
11262
  "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
10786
11263
  );
10787
- if (!isEnvironmentReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
11264
+ if (!isEnvironmentReport && !(actionCommand.name() === "report" && actionCommand.parent?.name() === "redteam") && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10788
11265
  try {
10789
11266
  const format = await resolveOutput(actionCommand, actionCommand.opts());
10790
11267
  actionCommand.setOptionValueWithSource("output", format, "implied");
@@ -10795,10 +11272,16 @@ function buildProgram() {
10795
11272
  if (root.debug) {
10796
11273
  const logPath = join4(
10797
11274
  process.cwd(),
10798
- `debug-api-${Date.now()}-${randomUUID8().slice(0, 8)}.jsonl`
11275
+ `debug-api-${Date.now()}-${randomUUID9().slice(0, 8)}.jsonl`
10799
11276
  );
10800
11277
  try {
10801
- installDebugLogger(logPath);
11278
+ let ancestor = actionCommand;
11279
+ let agentGuard = false;
11280
+ while (ancestor) {
11281
+ if (ancestor.name() === "agentguard") agentGuard = true;
11282
+ ancestor = ancestor.parent;
11283
+ }
11284
+ installDebugLogger(logPath, { omitBodies: agentGuard });
10802
11285
  } catch {
10803
11286
  ui.error(
10804
11287
  "Cannot create the debug log in the current working directory. Run from a writable directory or omit --debug."
@@ -10811,6 +11294,7 @@ function buildProgram() {
10811
11294
  registerRuntimeCommand(program);
10812
11295
  registerRedteamCommand(program);
10813
11296
  registerModelSecurityCommand(program);
11297
+ registerAgentGuardCommand(program);
10814
11298
  registerAiGatewayCommand(program);
10815
11299
  registerConfigCommand(program);
10816
11300
  registerDoctorCommand(program);