@cdot65/prisma-airs-cli 5.3.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]) => ({
@@ -4747,11 +5196,11 @@ function parseNonnegativePage(value) {
4747
5196
  }
4748
5197
 
4749
5198
  // src/cli/commands/aigateway-report.ts
4750
- import { lstat } from "fs/promises";
4751
- import { resolve } from "path";
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";
5202
+ import { z as z2 } from "zod";
5203
+ import { randomUUID as randomUUID2 } from "crypto";
4755
5204
  function registerAiGatewayReportCommand(aigateway) {
4756
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>",
@@ -4778,7 +5227,7 @@ function registerAiGatewayReportCommand(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 registerAiGatewayReportCommand(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
  });
@@ -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,
@@ -6292,10 +6758,10 @@ async function backupTargets(opts) {
6292
6758
  }
6293
6759
 
6294
6760
  // src/cli/commands/redteam-report.ts
6295
- import { lstat as lstat2 } from "fs/promises";
6296
- import { resolve as resolve2 } from "path";
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";
6764
+ import { randomUUID as randomUUID4 } from "crypto";
6299
6765
  function registerRedTeamReportCommand(redteam, scanReport) {
6300
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>",
@@ -6328,11 +6794,11 @@ function registerRedTeamReportCommand(redteam, scanReport) {
6328
6794
  usageError("--title must contain 1\u2013240 characters");
6329
6795
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
6330
6796
  usageError("--output-file cannot be empty");
6331
- const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID3().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
6332
- 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);
6333
6799
  try {
6334
6800
  if (outputPath) {
6335
- const existing = await lstat2(outputPath).catch((error) => {
6801
+ const existing = await lstat3(outputPath).catch((error) => {
6336
6802
  if (error.code !== "ENOENT") throw error;
6337
6803
  return void 0;
6338
6804
  });
@@ -7605,7 +8071,7 @@ function registerRedteamCommand(program) {
7605
8071
  }
7606
8072
 
7607
8073
  // src/cli/commands/runtime.ts
7608
- import { randomUUID as randomUUID7 } from "crypto";
8074
+ import { randomUUID as randomUUID8 } from "crypto";
7609
8075
  import * as fs5 from "fs";
7610
8076
  import { readFile as readFile10 } from "fs/promises";
7611
8077
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
@@ -7853,7 +8319,7 @@ function mergeProfilePolicy(existing, overrides) {
7853
8319
  }
7854
8320
 
7855
8321
  // src/cli/bulk-scan-lock.ts
7856
- import { randomUUID as randomUUID4 } from "crypto";
8322
+ import { randomUUID as randomUUID5 } from "crypto";
7857
8323
  import * as fs3 from "fs/promises";
7858
8324
  function errorCode(error) {
7859
8325
  return error?.code;
@@ -7884,7 +8350,7 @@ function processIsAlive(pid) {
7884
8350
  }
7885
8351
  }
7886
8352
  async function installLock(lockPath, record) {
7887
- const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID4()}`;
8353
+ const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID5()}`;
7888
8354
  try {
7889
8355
  await fs3.writeFile(candidate, JSON.stringify(record), {
7890
8356
  encoding: "utf-8",
@@ -7902,7 +8368,7 @@ async function acquireBulkScanLock(statePath) {
7902
8368
  version: 1,
7903
8369
  pid: process.pid,
7904
8370
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7905
- token: randomUUID4()
8371
+ token: randomUUID5()
7906
8372
  };
7907
8373
  for (let attempt = 0; attempt < 2; attempt++) {
7908
8374
  try {
@@ -7938,32 +8404,32 @@ async function acquireBulkScanLock(statePath) {
7938
8404
  }
7939
8405
 
7940
8406
  // src/cli/bulk-scan-state.ts
7941
- import { randomUUID as randomUUID5 } from "crypto";
8407
+ import { randomUUID as randomUUID6 } from "crypto";
7942
8408
  import * as fs4 from "fs/promises";
7943
8409
  import * as path2 from "path";
7944
- import { z as z2 } from "zod";
7945
- var BulkScanResultSchema = z2.object({
7946
- index: z2.number().int().nonnegative(),
7947
- reqId: z2.number().int().nonnegative(),
7948
- prompt: z2.string(),
7949
- response: z2.string().optional(),
7950
- scanId: z2.string(),
7951
- reportId: z2.string(),
7952
- action: z2.enum(["allow", "block", "failed"]),
7953
- category: z2.string(),
7954
- triggered: z2.boolean(),
7955
- detections: z2.record(z2.boolean()),
7956
- 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()
7957
8423
  });
7958
- var BulkScanItemSchema = z2.object({
7959
- index: z2.number().int().nonnegative(),
7960
- reqId: z2.number().int().nonnegative(),
7961
- prompt: z2.string(),
7962
- status: z2.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
7963
- scanId: z2.string().min(1).optional(),
7964
- 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(),
7965
8431
  result: BulkScanResultSchema.optional(),
7966
- error: z2.string().optional()
8432
+ error: z4.string().optional()
7967
8433
  }).superRefine((item, ctx) => {
7968
8434
  if (item.reqId !== item.index) {
7969
8435
  ctx.addIssue({ code: "custom", message: "reqId must match the stable input index" });
@@ -8001,15 +8467,15 @@ var BulkScanItemSchema = z2.object({
8001
8467
  }
8002
8468
  }
8003
8469
  });
8004
- var BulkScanStateSchema = z2.object({
8005
- version: z2.literal(2),
8006
- profile: z2.string().min(1),
8007
- sessionId: z2.string().optional(),
8008
- outputFile: z2.string().min(1),
8009
- batchSize: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8010
- createdAt: z2.string().datetime(),
8011
- updatedAt: z2.string().datetime(),
8012
- 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)
8013
8479
  }).superRefine((state, ctx) => {
8014
8480
  const indices = /* @__PURE__ */ new Set();
8015
8481
  for (const [position, item] of state.items.entries()) {
@@ -8035,8 +8501,8 @@ async function saveBulkScanState(state, dir, filePath) {
8035
8501
  }
8036
8502
  await fs4.mkdir(dir, { recursive: true, mode: 448 });
8037
8503
  if (!filePath) await fs4.chmod(dir, 448);
8038
- const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID5()}.bulk-scan.json`);
8039
- 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()}`;
8040
8506
  try {
8041
8507
  await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
8042
8508
  encoding: "utf-8",
@@ -9161,11 +9627,11 @@ function registerCleanupCommand(parent) {
9161
9627
  }
9162
9628
 
9163
9629
  // src/cli/commands/runtime-dashboard.ts
9164
- import { lstat as lstat3 } from "fs/promises";
9165
- import { resolve as resolve4 } from "path";
9166
- 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";
9167
9633
  var TIME_UNITS = ["hour", "hours", "day", "days"];
9168
- function integer2(value, name, minimum = 0) {
9634
+ function integer3(value, name, minimum = 0) {
9169
9635
  const n = Number(value);
9170
9636
  if (!/^\d+$/.test(String(value)) || !Number.isSafeInteger(n) || n < minimum)
9171
9637
  throw new CliUsageError(`${name} must be a safe integer >= ${minimum}`);
@@ -9177,7 +9643,7 @@ function text(value, name) {
9177
9643
  }
9178
9644
  function window(opts) {
9179
9645
  return {
9180
- timeInterval: integer2(opts.interval, "--interval", 1),
9646
+ timeInterval: integer3(opts.interval, "--interval", 1),
9181
9647
  timeUnit: text(opts.unit, "--unit")
9182
9648
  };
9183
9649
  }
@@ -9192,7 +9658,7 @@ function identity(opts) {
9192
9658
  function scan(opts) {
9193
9659
  return {
9194
9660
  scanId: text(opts.scanId, "--scan-id"),
9195
- scanSubReqId: integer2(opts.scanSubReqId, "--scan-sub-req-id")
9661
+ scanSubReqId: integer3(opts.scanSubReqId, "--scan-sub-req-id")
9196
9662
  };
9197
9663
  }
9198
9664
  function timeOptions(command, interval = "1", unit = "day", allowedUnits = TIME_UNITS) {
@@ -9209,7 +9675,7 @@ function timeOptions(command, interval = "1", unit = "day", allowedUnits = TIME_
9209
9675
  unit
9210
9676
  );
9211
9677
  }
9212
- function pageOptions(command) {
9678
+ function pageOptions2(command) {
9213
9679
  return command.option("--limit <n>", "Items per page", "25").option("--offset <n>", "Item offset", "0");
9214
9680
  }
9215
9681
  function sessionOptions(command) {
@@ -9238,8 +9704,8 @@ function registerRuntimeDashboardCommands(runtime) {
9238
9704
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
9239
9705
  );
9240
9706
  if (opts.outputFile) {
9241
- const path3 = resolve4(text(opts.outputFile, "--output-file"));
9242
- const existing = await lstat3(path3).catch((error) => {
9707
+ const path3 = resolve5(text(opts.outputFile, "--output-file"));
9708
+ const existing = await lstat4(path3).catch((error) => {
9243
9709
  if (error.code !== "ENOENT") throw error;
9244
9710
  return void 0;
9245
9711
  });
@@ -9252,9 +9718,9 @@ function registerRuntimeDashboardCommands(runtime) {
9252
9718
  numRetries: 0
9253
9719
  });
9254
9720
  const result = await read(client.dashboard);
9255
- 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);
9256
9722
  if (command.name() === "scan-content" && opts.outputFile) {
9257
- await writeReportFile(resolve4(String(opts.outputFile)), `${output}
9723
+ await writeReportFile(resolve5(String(opts.outputFile)), `${output}
9258
9724
  `);
9259
9725
  ui.status("Scan content exported to the requested private file (0600).");
9260
9726
  } else console.log(output);
@@ -9269,7 +9735,7 @@ function registerRuntimeDashboardCommands(runtime) {
9269
9735
  });
9270
9736
  }
9271
9737
  register6(
9272
- pageOptions(
9738
+ pageOptions2(
9273
9739
  timeOptions(
9274
9740
  dashboard.command("applications").description("One page of application activity"),
9275
9741
  "1",
@@ -9278,19 +9744,19 @@ function registerRuntimeDashboardCommands(runtime) {
9278
9744
  )
9279
9745
  ),
9280
9746
  (opts) => {
9281
- const interval = integer2(opts.interval, "--interval", 1);
9747
+ const interval = integer3(opts.interval, "--interval", 1);
9282
9748
  const unit = text(opts.unit, "--unit");
9283
9749
  if (![1, 7, 30, 60].includes(interval) || !["day", "days", "hour"].includes(unit))
9284
9750
  throw new CliUsageError(
9285
9751
  "Application overview supports interval 1/7/30/60 and unit day/days/hour"
9286
9752
  );
9287
- const query = {
9753
+ const query2 = {
9288
9754
  timeInterval: interval,
9289
9755
  timeUnit: unit,
9290
- limit: integer2(opts.limit, "--limit", 1),
9291
- offset: integer2(opts.offset, "--offset")
9756
+ limit: integer3(opts.limit, "--limit", 1),
9757
+ offset: integer3(opts.offset, "--offset")
9292
9758
  };
9293
- return (client) => client.applicationsOverview(query);
9759
+ return (client) => client.applicationsOverview(query2);
9294
9760
  }
9295
9761
  );
9296
9762
  for (const [name, method] of [
@@ -9305,16 +9771,16 @@ function registerRuntimeDashboardCommands(runtime) {
9305
9771
  ["days"]
9306
9772
  ),
9307
9773
  (opts) => {
9308
- const interval = integer2(opts.interval, "--interval", 1);
9774
+ const interval = integer3(opts.interval, "--interval", 1);
9309
9775
  if (![7, 30, 60].includes(interval) || opts.unit !== "days")
9310
9776
  throw new CliUsageError("Application detail requires 7, 30, or 60 days");
9311
- const query = {
9777
+ const query2 = {
9312
9778
  appId: text(opts.appId, "--app-id"),
9313
9779
  appName: text(opts.appName, "--app-name"),
9314
9780
  timeInterval: interval,
9315
9781
  timeUnit: "days"
9316
9782
  };
9317
- return (client) => client[method](query);
9783
+ return (client) => client[method](query2);
9318
9784
  }
9319
9785
  );
9320
9786
  }
@@ -9326,35 +9792,35 @@ function registerRuntimeDashboardCommands(runtime) {
9326
9792
  register6(
9327
9793
  timeOptions(dashboard.command(name), days ? "30" : "1", days ? "days" : "day"),
9328
9794
  (opts) => {
9329
- const query = window(opts);
9330
- return (client) => client[method](query);
9795
+ const query2 = window(opts);
9796
+ return (client) => client[method](query2);
9331
9797
  }
9332
9798
  );
9333
9799
  }
9334
9800
  register6(timeOptions(sessions.command("chart")), (opts) => {
9335
- const query = window(opts);
9336
- return (client) => client.sessionsChart(query);
9801
+ const query2 = window(opts);
9802
+ return (client) => client.sessionsChart(query2);
9337
9803
  });
9338
9804
  register6(
9339
- pageOptions(
9805
+ pageOptions2(
9340
9806
  timeOptions(
9341
9807
  sessions.command("list").description("Session entries; --all walks pages with completeness checks")
9342
9808
  )
9343
9809
  ).option("--all", "Walk all pages (up to 100 pages)").option("--max <n>", "Item cap with --all; 0 removes item cap", "10000"),
9344
9810
  (opts) => {
9345
- const query = {
9811
+ const query2 = {
9346
9812
  ...window(opts),
9347
- limit: integer2(opts.limit, "--limit", 1),
9348
- offset: integer2(opts.offset, "--offset")
9813
+ limit: integer3(opts.limit, "--limit", 1),
9814
+ offset: integer3(opts.offset, "--offset")
9349
9815
  };
9350
- const maximum = integer2(opts.max, "--max");
9816
+ const maximum = integer3(opts.max, "--max");
9351
9817
  return async (client) => {
9352
9818
  const items = [];
9353
9819
  const seen = /* @__PURE__ */ new Set();
9354
9820
  let expectedTotal;
9355
- let offset = query.offset;
9821
+ let offset = query2.offset;
9356
9822
  for (let pageIndex = 0; pageIndex < 100; pageIndex++) {
9357
- const page = await client.sessionsOverview({ ...query, offset });
9823
+ const page = await client.sessionsOverview({ ...query2, offset });
9358
9824
  if (!opts.all) {
9359
9825
  ui.status(
9360
9826
  `Returned ${page.items.length} of ${page.pagination.total_items ?? "unknown"} sessions; this is one page.`
@@ -9362,7 +9828,7 @@ function registerRuntimeDashboardCommands(runtime) {
9362
9828
  return page.items;
9363
9829
  }
9364
9830
  const total = page.pagination.total_items;
9365
- 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)
9366
9832
  throw new Error("Unstable pagination");
9367
9833
  expectedTotal = total;
9368
9834
  for (const item of page.items) {
@@ -9385,7 +9851,7 @@ function registerRuntimeDashboardCommands(runtime) {
9385
9851
  }
9386
9852
  if (offset === total) {
9387
9853
  ui.status(
9388
- `Retrieved ${items.length} sessions from offset ${query.offset}; pagination complete.`
9854
+ `Retrieved ${items.length} sessions from offset ${query2.offset}; pagination complete.`
9389
9855
  );
9390
9856
  return items;
9391
9857
  }
@@ -9401,33 +9867,33 @@ function registerRuntimeDashboardCommands(runtime) {
9401
9867
  };
9402
9868
  }
9403
9869
  );
9404
- register6(pageOptions(sessionOptions(sessions.command("get"))), (opts) => {
9405
- const query = {
9870
+ register6(pageOptions2(sessionOptions(sessions.command("get"))), (opts) => {
9871
+ const query2 = {
9406
9872
  ...identity(opts),
9407
- limit: integer2(opts.limit, "--limit", 1),
9408
- offset: integer2(opts.offset, "--offset")
9873
+ limit: integer3(opts.limit, "--limit", 1),
9874
+ offset: integer3(opts.offset, "--offset")
9409
9875
  };
9410
- return (client) => client.session(query);
9876
+ return (client) => client.session(query2);
9411
9877
  });
9412
9878
  register6(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
9413
- const query = { ...identity(opts), ...scan(opts) };
9414
- return (client) => client.sessionTransaction(query);
9879
+ const query2 = { ...identity(opts), ...scan(opts) };
9880
+ return (client) => client.sessionTransaction(query2);
9415
9881
  });
9416
9882
  register6(
9417
9883
  scanOptions(
9418
9884
  sessions.command("scan-content").description("Explicit sensitive scan-content read; never automatically fetched")
9419
9885
  ).option("--show-content", "Explicitly display stored content on stdout").option("--output-file <path>", "Private new file for stored content (no overwrite)"),
9420
9886
  (opts) => {
9421
- const query = scan(opts);
9422
- return (client) => client.scanContent(query);
9887
+ const query2 = scan(opts);
9888
+ return (client) => client.scanContent(query2);
9423
9889
  }
9424
9890
  );
9425
9891
  }
9426
9892
 
9427
9893
  // src/cli/commands/runtime-report.ts
9428
- import { lstat as lstat4 } from "fs/promises";
9429
- import { resolve as resolve5 } from "path";
9430
- 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";
9431
9897
  function registerRuntimeReportCommand(runtime) {
9432
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(
9433
9899
  "--output-file <path>",
@@ -9451,11 +9917,11 @@ function registerRuntimeReportCommand(runtime) {
9451
9917
  usageError("--title must contain 1\u2013240 characters");
9452
9918
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
9453
9919
  usageError("--output-file cannot be empty");
9454
- const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID6().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9455
- 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);
9456
9922
  try {
9457
9923
  if (outputPath) {
9458
- const existing = await lstat4(outputPath).catch((error) => {
9924
+ const existing = await lstat5(outputPath).catch((error) => {
9459
9925
  if (error.code !== "ENOENT") throw error;
9460
9926
  return void 0;
9461
9927
  });
@@ -9942,7 +10408,7 @@ function completedBulkResults(state) {
9942
10408
  }
9943
10409
  async function writeBulkResults(outputPath, results) {
9944
10410
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
9945
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID7()}`;
10411
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID8()}`;
9946
10412
  try {
9947
10413
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
9948
10414
  encoding: "utf-8",
@@ -10745,7 +11211,8 @@ var READ_COMMAND_NAMES = /* @__PURE__ */ new Set([
10745
11211
  "version",
10746
11212
  "versions",
10747
11213
  "violation",
10748
- "violations"
11214
+ "violations",
11215
+ "vulnerabilities"
10749
11216
  ]);
10750
11217
  function applyListDeleteAliases(cmd) {
10751
11218
  for (const sub of cmd.commands) {
@@ -10787,7 +11254,9 @@ function buildProgram() {
10787
11254
  program.hook("preAction", async (_thisCommand, actionCommand) => {
10788
11255
  const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
10789
11256
  setQuiet(Boolean(root.quiet));
10790
- const isEnvironmentReport = actionCommand.name() === "report" && ["runtime", "redteam", "aigateway"].includes(actionCommand.parent?.name() ?? "") && !(actionCommand.parent?.name() === "redteam" && actionCommand.args.length > 0);
11257
+ const isEnvironmentReport = actionCommand.name() === "report" && ["runtime", "redteam", "aigateway", "agentguard"].includes(
11258
+ actionCommand.parent?.name() ?? ""
11259
+ ) && !(actionCommand.parent?.name() === "redteam" && actionCommand.args.length > 0);
10791
11260
  if (isEnvironmentReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10792
11261
  usageError(
10793
11262
  "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
@@ -10803,10 +11272,16 @@ function buildProgram() {
10803
11272
  if (root.debug) {
10804
11273
  const logPath = join4(
10805
11274
  process.cwd(),
10806
- `debug-api-${Date.now()}-${randomUUID8().slice(0, 8)}.jsonl`
11275
+ `debug-api-${Date.now()}-${randomUUID9().slice(0, 8)}.jsonl`
10807
11276
  );
10808
11277
  try {
10809
- 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 });
10810
11285
  } catch {
10811
11286
  ui.error(
10812
11287
  "Cannot create the debug log in the current working directory. Run from a writable directory or omit --debug."
@@ -10819,6 +11294,7 @@ function buildProgram() {
10819
11294
  registerRuntimeCommand(program);
10820
11295
  registerRedteamCommand(program);
10821
11296
  registerModelSecurityCommand(program);
11297
+ registerAgentGuardCommand(program);
10822
11298
  registerAiGatewayCommand(program);
10823
11299
  registerConfigCommand(program);
10824
11300
  registerDoctorCommand(program);