@cdot65/prisma-airs-cli 3.2.0 → 4.0.0

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
@@ -20,7 +20,7 @@ import {
20
20
  sanitizeFilename,
21
21
  validateTopic,
22
22
  writeBackupFile
23
- } from "../chunk-TTBN7YHC.js";
23
+ } from "../chunk-W5YDJS7H.js";
24
24
 
25
25
  // src/cli/index.ts
26
26
  import "dotenv/config";
@@ -45,20 +45,221 @@ import { dirname as dirname4, join as join4 } from "path";
45
45
  import { fileURLToPath } from "url";
46
46
  import { Command } from "commander";
47
47
 
48
- // src/cli/examples.ts
49
- function examples(...lines) {
50
- return `
51
- Examples:
52
- ${lines.map((l) => ` $ ${l}`).join("\n")}
53
- `;
48
+ // src/airs/aigateway.ts
49
+ import {
50
+ AIGatewayClient
51
+ } from "@cdot65/prisma-airs-sdk";
52
+ function toLimitArray(value) {
53
+ if (Array.isArray(value)) return value;
54
+ if (value !== null && typeof value === "object") return [value];
55
+ return [];
56
+ }
57
+ function normalizeWorkspace(raw) {
58
+ return {
59
+ id: raw.id,
60
+ slug: raw.slug,
61
+ name: raw.name,
62
+ icon: raw.icon,
63
+ description: raw.description,
64
+ createdAt: raw.created_at,
65
+ lastUpdatedAt: raw.last_updated_at,
66
+ isDefault: Boolean(raw.is_default),
67
+ status: raw.status,
68
+ scopeName: raw.scope_name
69
+ };
54
70
  }
71
+ function normalizeWorkspaceDetail(raw) {
72
+ return {
73
+ ...normalizeWorkspace(raw),
74
+ defaults: raw.defaults,
75
+ usageLimits: toLimitArray(raw.usage_limits),
76
+ rateLimits: toLimitArray(raw.rate_limits),
77
+ securitySettings: raw.security_settings,
78
+ dataPlaneSecuritySettings: raw.data_plane_security_settings,
79
+ settings: raw.settings
80
+ };
81
+ }
82
+ function aiGatewayGrantHint(err) {
83
+ const status = err?.status ?? err?.statusCode;
84
+ if (status !== 403) return void 0;
85
+ const message = err instanceof Error ? err.message : String(err);
86
+ 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)";
87
+ 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.`;
88
+ }
89
+ var SdkAiGatewayService = class {
90
+ client;
91
+ constructor(opts) {
92
+ this.client = new AIGatewayClient(opts);
93
+ }
94
+ async listWorkspaces(options) {
95
+ const response = await this.client.workspaces.list(options);
96
+ return response.data.map(normalizeWorkspace);
97
+ }
98
+ async listAllWorkspaces() {
99
+ const [active, archived] = await Promise.all([
100
+ this.client.workspaces.list({ plane: "admin" }),
101
+ this.client.workspaces.list({ plane: "admin", status: "archived" })
102
+ ]);
103
+ return [
104
+ ...active.data,
105
+ ...archived.data
106
+ ].map(normalizeWorkspace);
107
+ }
108
+ async getWorkspace(workspaceRef, options) {
109
+ try {
110
+ const raw = await this.client.workspaces.get(workspaceRef, options);
111
+ return normalizeWorkspaceDetail(raw);
112
+ } catch (err) {
113
+ const status = err.statusCode;
114
+ if (status !== 404) throw err;
115
+ const resolved = await this.resolveWorkspaceRef(workspaceRef, [
116
+ options?.plane ?? "data",
117
+ "admin"
118
+ ]);
119
+ if (resolved === workspaceRef) throw err;
120
+ const raw = await this.client.workspaces.get(resolved, options);
121
+ return normalizeWorkspaceDetail(raw);
122
+ }
123
+ }
124
+ async createWorkspace(request) {
125
+ const body = {
126
+ name: request.name,
127
+ scope_name: request.scopeName
128
+ };
129
+ if (request.description !== void 0) body.description = request.description;
130
+ if (request.icon !== void 0) body.icon = request.icon;
131
+ if (request.defaults !== void 0) body.defaults = request.defaults;
132
+ if (request.users !== void 0) body.users = request.users;
133
+ if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
134
+ if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
135
+ const created = await this.client.workspaces.create(body);
136
+ return this.refetchAfterWrite(created.id, created);
137
+ }
138
+ async updateWorkspace(workspaceRef, request) {
139
+ const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
140
+ const body = {};
141
+ if (request.name !== void 0) body.name = request.name;
142
+ if (request.description !== void 0) body.description = request.description;
143
+ if (request.icon !== void 0) body.icon = request.icon;
144
+ if (request.defaults !== void 0) body.defaults = request.defaults;
145
+ if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
146
+ if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
147
+ await this.client.workspaces.update(ref, body);
148
+ return this.getWorkspace(ref, { plane: "admin" });
149
+ }
150
+ async deleteWorkspace(workspaceRef) {
151
+ const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
152
+ await this.client.workspaces.delete(ref);
153
+ }
154
+ /**
155
+ * The API accepts only a UUID or slug as a workspace ref — a display name
156
+ * gets a misleading 400 AB01 ("No update fields provided") on writes.
157
+ * Match a user-supplied ref against the workspace list so name | slug |
158
+ * uuid all work. Unmatched refs pass through so the API's own error stands.
159
+ */
160
+ async resolveWorkspaceRef(ref, planes) {
161
+ for (const plane of planes) {
162
+ let rows;
163
+ try {
164
+ rows = await this.listWorkspaces({ plane });
165
+ } catch {
166
+ continue;
167
+ }
168
+ if (rows.some((w) => w.id === ref || w.slug === ref)) return ref;
169
+ const byName = rows.filter((w) => w.name === ref);
170
+ if (byName.length > 1) {
171
+ throw new Error(
172
+ `workspace name '${ref}' is ambiguous (${byName.map((w) => w.slug).join(", ")}) \u2014 use a slug or UUID`
173
+ );
174
+ }
175
+ if (byName.length === 1) return byName[0].slug;
176
+ }
177
+ return ref;
178
+ }
179
+ async getTelemetryCost(opts) {
180
+ const days = opts.days ?? 7;
181
+ const workspaceSlug = await this.resolveWorkspaceRef(opts.workspaceSlug, ["data", "admin"]);
182
+ const raw = await this.client.telemetry.cost({
183
+ workspaceSlug,
184
+ days
185
+ });
186
+ return {
187
+ workspaceSlug,
188
+ days,
189
+ totalCents: raw.data.total,
190
+ totalUsd: raw.data.total / 100,
191
+ avgCents: raw.data.avg,
192
+ avgUsd: raw.data.avg / 100,
193
+ quotaExceeded: raw.data.isQuotaExceeded,
194
+ records: raw.data.records.map((r) => ({
195
+ date: r.x,
196
+ costCents: r.y,
197
+ costUsd: r.y / 100
198
+ }))
199
+ };
200
+ }
201
+ /** Re-read after a write, falling back to the (partial) write response if the get fails. */
202
+ async refetchAfterWrite(workspaceRef, writeResponse) {
203
+ try {
204
+ return await this.getWorkspace(workspaceRef, { plane: "admin" });
205
+ } catch {
206
+ return normalizeWorkspaceDetail(writeResponse);
207
+ }
208
+ }
209
+ };
55
210
 
56
- // src/cli/renderer/ui.ts
57
- import chalk3 from "chalk";
211
+ // src/config/client-options.ts
212
+ function runtimeInitOptions(config) {
213
+ return {
214
+ apiKey: config.airsApiKey,
215
+ apiToken: config.airsApiToken,
216
+ apiEndpoint: config.airsApiEndpoint,
217
+ numRetries: config.airsNumRetries
218
+ };
219
+ }
220
+ function redTeamClientOptions(config) {
221
+ return {
222
+ clientId: config.mgmtClientId,
223
+ clientSecret: config.mgmtClientSecret,
224
+ tsgId: config.mgmtTsgId,
225
+ dataEndpoint: config.redTeamDataEndpoint,
226
+ mgmtEndpoint: config.redTeamMgmtEndpoint,
227
+ tokenEndpoint: config.redTeamTokenEndpoint ?? config.mgmtTokenEndpoint,
228
+ networkBrokerEndpoint: config.redTeamNetworkBrokerEndpoint
229
+ };
230
+ }
231
+ function aiGatewayClientOptions(config) {
232
+ return {
233
+ clientId: config.mgmtClientId,
234
+ clientSecret: config.mgmtClientSecret,
235
+ tsgId: config.mgmtTsgId,
236
+ dataEndpoint: config.aiGwDataEndpoint,
237
+ adminEndpoint: config.aiGwAdminEndpoint,
238
+ tokenEndpoint: config.aiGwTokenEndpoint ?? config.mgmtTokenEndpoint
239
+ };
240
+ }
241
+ function modelSecurityClientOptions(config) {
242
+ return {
243
+ clientId: config.mgmtClientId,
244
+ clientSecret: config.mgmtClientSecret,
245
+ tsgId: config.mgmtTsgId,
246
+ dataEndpoint: config.modelSecDataEndpoint,
247
+ mgmtEndpoint: config.modelSecMgmtEndpoint,
248
+ tokenEndpoint: config.modelSecTokenEndpoint ?? config.mgmtTokenEndpoint
249
+ };
250
+ }
251
+
252
+ // src/cli/renderer/aigateway.ts
253
+ import chalk4 from "chalk";
254
+ import { dump as yamlDump } from "js-yaml";
58
255
 
59
256
  // src/cli/renderer/common.ts
60
257
  import chalk2 from "chalk";
258
+ import { dump } from "js-yaml";
259
+ var CliUsageError = class extends Error {
260
+ };
61
261
  function fail(err) {
262
+ if (err instanceof CliUsageError) usageError(err.message);
62
263
  const message = err instanceof Error ? err.message : String(err);
63
264
  const status = err?.status ?? err?.statusCode;
64
265
  console.error(chalk2.red(`
@@ -76,44 +277,81 @@ function usageError(message) {
76
277
  `));
77
278
  process.exit(2);
78
279
  }
79
- var OUTPUT_FORMATS = ["pretty", "table", "csv", "json", "yaml"];
280
+ var OUTPUT_FORMATS = [
281
+ "pretty",
282
+ "table",
283
+ "markdown",
284
+ "csv",
285
+ "json",
286
+ "yaml"
287
+ ];
288
+ async function resolveOutput(command, opts, resolution = {}) {
289
+ const localIsExplicit = command.getOptionValueSource?.("output") === "cli";
290
+ const root = command.parent ? command.optsWithGlobals() : command.opts();
291
+ let configured;
292
+ try {
293
+ configured = (await loadConfig()).defaultOutput;
294
+ } catch (error) {
295
+ if (process.env.PANW_CLI_OUTPUT !== void 0) configured = process.env.PANW_CLI_OUTPUT;
296
+ else throw error;
297
+ }
298
+ const candidate = String(localIsExplicit ? opts.output : root.output ?? configured ?? "pretty");
299
+ if (!OUTPUT_FORMATS.includes(candidate))
300
+ throw new CliUsageError(
301
+ `Invalid output format '${candidate}'. Expected: ${OUTPUT_FORMATS.join(", ")}`
302
+ );
303
+ const format = candidate;
304
+ const allowed = resolution.allowed ?? OUTPUT_FORMATS;
305
+ if (!allowed.includes(format))
306
+ throw new CliUsageError(
307
+ `Output format '${format}' is not supported here. Expected: ${allowed.join(", ")}`
308
+ );
309
+ return format;
310
+ }
311
+ function displayValue(value) {
312
+ if (value == null) return "";
313
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
314
+ }
315
+ function csvCell(value) {
316
+ const text = displayValue(value);
317
+ return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
318
+ }
319
+ function markdownCell(value) {
320
+ return displayValue(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
321
+ }
80
322
  function formatOutput(rows, columns, format) {
81
- if (rows.length === 0) return "";
82
- const vals = (row, key) => String(row[key] ?? "");
323
+ if (rows.length === 0) return format === "json" ? "[]" : "";
324
+ const projected = rows.map((row) => columns.map((column) => row[column.key]));
83
325
  switch (format) {
84
326
  case "json":
85
- return JSON.stringify(
86
- rows.map((r) => {
87
- const obj = {};
88
- for (const c of columns) obj[c.key] = r[c.key];
89
- return obj;
90
- }),
91
- null,
92
- 2
93
- );
94
- case "csv": {
95
- const header = columns.map((c) => c.label).join(",");
96
- const lines = rows.map(
97
- (r) => columns.map((c) => {
98
- const v = vals(r, c.key);
99
- return v.includes(",") || v.includes('"') ? `"${v.replace(/"/g, '""')}"` : v;
100
- }).join(",")
101
- );
102
- return [header, ...lines].join("\n");
103
- }
104
- case "yaml": {
105
- return rows.map((r) => columns.map((c) => `${c.key}: ${vals(r, c.key)}`).join("\n")).join("\n---\n");
327
+ return JSON.stringify(rows, null, 2);
328
+ case "yaml":
329
+ return dump(rows, { noRefs: true, lineWidth: -1 }).trimEnd();
330
+ case "csv":
331
+ return [
332
+ columns.map((column) => csvCell(column.label)).join(","),
333
+ ...projected.map((values) => values.map(csvCell).join(","))
334
+ ].join("\n");
335
+ case "markdown": {
336
+ const header = `| ${columns.map((column) => markdownCell(column.label)).join(" | ")} |`;
337
+ const divider = `| ${columns.map(() => "---").join(" | ")} |`;
338
+ return [
339
+ header,
340
+ divider,
341
+ ...projected.map((values) => `| ${values.map(markdownCell).join(" | ")} |`)
342
+ ].join("\n");
106
343
  }
107
344
  case "table": {
345
+ const values = projected.map((row) => row.map(displayValue));
108
346
  const widths = columns.map(
109
- (c) => Math.max(c.label.length, ...rows.map((r) => vals(r, c.key).length))
347
+ (column, index) => Math.max(column.label.length, ...values.map((row) => row[index].length))
110
348
  );
111
- const sep = widths.map((w) => "\u2500".repeat(w + 2)).join("\u253C");
112
- const header = columns.map((c, i) => ` ${c.label.padEnd(widths[i])} `).join("\u2502");
113
- const body = rows.map(
114
- (r) => columns.map((c, i) => ` ${vals(r, c.key).padEnd(widths[i])} `).join("\u2502")
349
+ const separator = widths.map((width) => "\u2500".repeat(width + 2)).join("\u253C");
350
+ const header = columns.map((column, index) => ` ${column.label.padEnd(widths[index])} `).join("\u2502");
351
+ const body = values.map(
352
+ (row) => row.map((value, index) => ` ${value.padEnd(widths[index])} `).join("\u2502")
115
353
  );
116
- return [header, sep, ...body].join("\n");
354
+ return [header, separator, ...body].join("\n");
117
355
  }
118
356
  default:
119
357
  return "";
@@ -121,6 +359,7 @@ function formatOutput(rows, columns, format) {
121
359
  }
122
360
 
123
361
  // src/cli/renderer/ui.ts
362
+ import chalk3 from "chalk";
124
363
  var INDENT = " ";
125
364
  var quietMode = false;
126
365
  function setQuiet(quiet) {
@@ -211,6 +450,187 @@ ${INDENT}${chalk3.bold(label)}
211
450
  }
212
451
  };
213
452
 
453
+ // src/cli/renderer/view.ts
454
+ import { dump as dump2 } from "js-yaml";
455
+ function asRecord(item) {
456
+ return item;
457
+ }
458
+ function project(view, item) {
459
+ const source = asRecord(item);
460
+ return Object.fromEntries(
461
+ view.columns.map((column) => [column.key, column.get ? column.get(item) : source[column.key]])
462
+ );
463
+ }
464
+ function renderPageStatus(page) {
465
+ if (!page) return;
466
+ if (page.all) ui.status(`Showing all ${page.total ?? page.returned}`);
467
+ else if (page.total !== void 0)
468
+ ui.status(
469
+ `Showing ${page.returned} of ${page.total}${page.next !== void 0 ? ` (next --offset ${page.next})` : ""}`
470
+ );
471
+ else if (page.next !== void 0) ui.status(`Showing ${page.returned} (more available)`);
472
+ else ui.status(`Showing ${page.returned}`);
473
+ }
474
+ function emitList(view, items, format, opts = {}) {
475
+ if (format === "pretty") {
476
+ if (items.length === 0) ui.emptyList(view.name);
477
+ else view.pretty.list(items);
478
+ } else {
479
+ const rows = format === "json" || format === "yaml" ? items.map((item) => view.structured?.(item) ?? asRecord(item)) : items.map((item) => project(view, item));
480
+ const rendered = formatOutput(rows, view.columns, format);
481
+ if (rendered) console.log(rendered);
482
+ }
483
+ renderPageStatus(opts.page);
484
+ }
485
+ function emitDetail(view, item, format) {
486
+ if (format === "pretty") {
487
+ view.pretty.detail(item);
488
+ return;
489
+ }
490
+ const structured = view.structured?.(item) ?? asRecord(item);
491
+ if (format === "json") console.log(JSON.stringify(structured, null, 2));
492
+ else if (format === "yaml")
493
+ console.log(dump2(structured, { noRefs: true, lineWidth: -1 }).trimEnd());
494
+ else {
495
+ const rows = Object.entries(structured).map(([key, value]) => ({
496
+ key,
497
+ value: value != null && typeof value === "object" ? JSON.stringify(value) : value
498
+ }));
499
+ console.log(
500
+ formatOutput(
501
+ rows,
502
+ [
503
+ { key: "key", label: "Key" },
504
+ { key: "value", label: "Value" }
505
+ ],
506
+ format
507
+ )
508
+ );
509
+ }
510
+ }
511
+
512
+ // src/cli/renderer/aigateway.ts
513
+ function renderAiGatewayHeader() {
514
+ ui.header("Prisma AIRS \u2014 AI Gateway", "Gateway workspace operations");
515
+ }
516
+ function statusColor(status) {
517
+ switch (status.toLowerCase()) {
518
+ case "active":
519
+ return chalk4.green;
520
+ case "archived":
521
+ return chalk4.yellow;
522
+ default:
523
+ return chalk4.dim;
524
+ }
525
+ }
526
+ function statusLabel(status) {
527
+ return status ?? "unknown";
528
+ }
529
+ function renderWorkspaceList(workspaces, format = "pretty") {
530
+ if (workspaces.length === 0) {
531
+ ui.emptyList("workspaces");
532
+ return;
533
+ }
534
+ if (format !== "pretty") {
535
+ const rows = workspaces.map((w) => ({
536
+ id: w.id,
537
+ slug: w.slug,
538
+ name: w.name,
539
+ status: statusLabel(w.status),
540
+ isDefault: w.isDefault,
541
+ scopeName: w.scopeName ?? ""
542
+ }));
543
+ console.log(
544
+ formatOutput(
545
+ rows,
546
+ [
547
+ { key: "id", label: "ID" },
548
+ { key: "slug", label: "Slug" },
549
+ { key: "name", label: "Name" },
550
+ { key: "status", label: "Status" },
551
+ { key: "isDefault", label: "Default" },
552
+ { key: "scopeName", label: "Scope" }
553
+ ],
554
+ format
555
+ )
556
+ );
557
+ return;
558
+ }
559
+ ui.section("AI Gateway Workspaces:");
560
+ for (const w of workspaces) {
561
+ ui.dim(w.id);
562
+ const status = statusColor(statusLabel(w.status))(statusLabel(w.status));
563
+ const dflt = w.isDefault ? chalk4.cyan(" default") : "";
564
+ console.log(` ${w.name} ${chalk4.dim(w.slug)} ${status}${dflt}`);
565
+ if (w.scopeName) console.log(` ${chalk4.dim(`scope: ${w.scopeName}`)}`);
566
+ console.log();
567
+ }
568
+ }
569
+ function renderWorkspaceDetail(workspace, format = "pretty") {
570
+ if (format !== "pretty") {
571
+ console.log(format === "json" ? JSON.stringify(workspace, null, 2) : yamlDump(workspace));
572
+ return;
573
+ }
574
+ ui.section("Workspace Detail:");
575
+ const pairs = [
576
+ ["ID", workspace.id],
577
+ ["Slug", workspace.slug],
578
+ ["Name", workspace.name],
579
+ ["Status", statusColor(statusLabel(workspace.status))(statusLabel(workspace.status))],
580
+ ["Default", workspace.isDefault ? "yes" : "no"]
581
+ ];
582
+ if (workspace.description != null) pairs.push(["Description", workspace.description]);
583
+ if (workspace.scopeName != null) pairs.push(["Scope", workspace.scopeName]);
584
+ if (workspace.createdAt != null) pairs.push(["Created", workspace.createdAt]);
585
+ if (workspace.lastUpdatedAt != null) pairs.push(["Updated", workspace.lastUpdatedAt]);
586
+ ui.keyValue(pairs);
587
+ if (workspace.defaults && Object.keys(workspace.defaults).length > 0) {
588
+ ui.section("Defaults:");
589
+ console.log(chalk4.dim(JSON.stringify(workspace.defaults, null, 2)));
590
+ }
591
+ if (workspace.usageLimits.length > 0) {
592
+ ui.section("Usage Limits:");
593
+ console.log(chalk4.dim(JSON.stringify(workspace.usageLimits, null, 2)));
594
+ }
595
+ if (workspace.rateLimits.length > 0) {
596
+ ui.section("Rate Limits:");
597
+ console.log(chalk4.dim(JSON.stringify(workspace.rateLimits, null, 2)));
598
+ }
599
+ if (workspace.securitySettings && Object.keys(workspace.securitySettings).length > 0) {
600
+ ui.section("Security Settings:");
601
+ ui.keyValue(Object.entries(workspace.securitySettings).map(([k, v]) => [k, v]));
602
+ }
603
+ console.log();
604
+ }
605
+ function renderCostReport(report, format = "pretty") {
606
+ if (format !== "pretty") {
607
+ emitDetail(
608
+ {
609
+ name: "cost report",
610
+ columns: [],
611
+ pretty: { list() {
612
+ }, detail() {
613
+ } }
614
+ },
615
+ report,
616
+ format
617
+ );
618
+ return;
619
+ }
620
+ const dollars = (cents) => `$${(cents / 100).toFixed(2)}`;
621
+ ui.section(`Cost \u2014 ${report.workspaceSlug} (last ${report.days}d):`);
622
+ ui.keyValue([
623
+ ["Total", dollars(report.totalCents)],
624
+ ["Daily average", dollars(report.avgCents)]
625
+ ]);
626
+ if (report.quotaExceeded) ui.warn("Telemetry quota exceeded \u2014 data may be truncated");
627
+ if (report.records.length > 0) {
628
+ ui.section("Per day:");
629
+ ui.keyValue(report.records.map((r) => [r.date, dollars(r.costCents)]));
630
+ }
631
+ console.log();
632
+ }
633
+
214
634
  // src/cli/renderer/backup.ts
215
635
  function renderBackupHeader() {
216
636
  ui.header("Prisma AIRS \u2014 Backup & Restore");
@@ -252,17 +672,17 @@ function renderRestoreSummary(results) {
252
672
  }
253
673
 
254
674
  // src/cli/renderer/dlp.ts
255
- import chalk4 from "chalk";
256
- import { dump as yamlDump } from "js-yaml";
257
- function statusColor(status) {
675
+ import chalk5 from "chalk";
676
+ import { dump as yamlDump2 } from "js-yaml";
677
+ function statusColor2(status) {
258
678
  switch (status) {
259
679
  case "active":
260
- return chalk4.green(status);
680
+ return chalk5.green(status);
261
681
  case "deleted":
262
682
  case "disabled":
263
- return chalk4.yellow(status);
683
+ return chalk5.yellow(status);
264
684
  default:
265
- return status ? chalk4.dim(status) : chalk4.dim("\u2014");
685
+ return status ? chalk5.dim(status) : chalk5.dim("\u2014");
266
686
  }
267
687
  }
268
688
  function ts(ms) {
@@ -279,7 +699,7 @@ function emitStructured(payload, fmt) {
279
699
  return;
280
700
  }
281
701
  if (fmt === "yaml") {
282
- console.log(yamlDump(payload));
702
+ console.log(yamlDump2(payload));
283
703
  return;
284
704
  }
285
705
  console.log(JSON.stringify(payload, null, 2));
@@ -293,60 +713,67 @@ function pageMeta(page, returned) {
293
713
  returned
294
714
  };
295
715
  }
296
- function emitList(page, fmt, header, toRow, columns, prettyLine) {
297
- const content = Array.isArray(page?.content) ? page.content : [];
298
- const rows = content.map(toRow);
299
- if (fmt === "json" || fmt === "yaml") {
300
- emitStructured({ items: rows, page: pageMeta(page, content.length) }, fmt);
301
- return;
302
- }
303
- if (content.length === 0) {
304
- ui.emptyList(header.toLowerCase());
305
- return;
306
- }
307
- if (fmt === "pretty") {
308
- ui.section(`${header}:`);
309
- for (const item of content) console.log(prettyLine(item));
310
- const meta = pageMeta(page, content.length);
311
- console.log();
312
- ui.dim(
313
- `page=${meta.number} size=${meta.size} returned=${meta.returned} total=${meta.total ?? "?"}`
314
- );
315
- console.log();
316
- return;
317
- }
318
- console.log(formatOutput(rows, columns, fmt));
716
+ function camelKey(key) {
717
+ return key.replace(/[_-]([a-z0-9])/g, (_, char) => char.toUpperCase());
319
718
  }
320
- function fieldsToObject(fields) {
321
- const obj = {};
322
- for (const f of fields) {
323
- if (f.value === void 0 || f.value === null || f.value === "") continue;
324
- obj[toKey(f.label)] = f.value;
719
+ function camelize(value) {
720
+ if (Array.isArray(value)) return value.map(camelize);
721
+ if (value && typeof value === "object") {
722
+ return Object.fromEntries(
723
+ Object.entries(value).map(([key, child]) => [
724
+ camelKey(key),
725
+ camelize(child)
726
+ ])
727
+ );
325
728
  }
326
- return obj;
327
- }
328
- function toKey(label) {
329
- return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
729
+ return value;
330
730
  }
331
- function emitDetail(_item, fmt, fields, title) {
332
- if (fmt === "json" || fmt === "yaml") {
333
- emitStructured(fieldsToObject(fields), fmt);
334
- return;
335
- }
336
- if (fmt === "pretty") {
337
- ui.section(`${title}:`);
338
- const pairs = [];
339
- for (const f of fields) {
340
- if (f.value === void 0 || f.value === null || f.value === "") continue;
341
- pairs.push([f.label, f.value]);
731
+ function emitList2(page, fmt, header, toRow, columns, prettyLine) {
732
+ const content = Array.isArray(page?.content) ? page.content : [];
733
+ const meta = pageMeta(page, content.length);
734
+ const view = {
735
+ name: header.toLowerCase(),
736
+ columns: columns.map((column) => ({
737
+ ...column,
738
+ get: (item) => toRow(item)[column.key]
739
+ })),
740
+ structured: (item) => camelize(item),
741
+ pretty: {
742
+ list(items) {
743
+ ui.section(`${header}:`);
744
+ for (const item of items) console.log(prettyLine(item));
745
+ console.log();
746
+ },
747
+ detail() {
748
+ }
342
749
  }
343
- ui.keyValue(pairs);
344
- console.log();
345
- return;
346
- }
347
- const rows = [Object.fromEntries(fields.map((f) => [f.label, f.value ?? ""]))];
348
- const columns = fields.map((f) => ({ key: f.label, label: f.label }));
349
- console.log(formatOutput(rows, columns, fmt));
750
+ };
751
+ const total = typeof meta.total === "number" ? meta.total : void 0;
752
+ const number = Number(meta.number ?? 0);
753
+ const size = Number(meta.size ?? content.length);
754
+ const next = total !== void 0 && (number + 1) * size < total ? (number + 1) * size : void 0;
755
+ emitList(view, content, fmt, { page: { returned: content.length, total, next } });
756
+ }
757
+ function emitDetail2(item, fmt, fields, title) {
758
+ const view = {
759
+ name: title.toLowerCase(),
760
+ columns: [],
761
+ structured: (value) => camelize(value),
762
+ pretty: {
763
+ list() {
764
+ },
765
+ detail() {
766
+ ui.section(`${title}:`);
767
+ ui.keyValue(
768
+ fields.filter(
769
+ (field) => field.value !== void 0 && field.value !== null && field.value !== ""
770
+ ).map((field) => [field.label, field.value])
771
+ );
772
+ console.log();
773
+ }
774
+ }
775
+ };
776
+ emitDetail(view, item, fmt);
350
777
  }
351
778
  function ackObject(verb, item) {
352
779
  const out = { action: verb };
@@ -377,7 +804,7 @@ function emitIdAck(verb, id) {
377
804
  }
378
805
  var dlpFilteringProfiles = {
379
806
  renderList(page, fmt) {
380
- emitList(
807
+ emitList2(
381
808
  page,
382
809
  fmt,
383
810
  "Data Filtering Profiles",
@@ -398,16 +825,16 @@ var dlpFilteringProfiles = {
398
825
  { key: "version", label: "Version" }
399
826
  ],
400
827
  (it) => {
401
- const dir = it.direction ? chalk4.dim(` dir:${it.direction}`) : "";
402
- const sev = it.log_severity ? chalk4.dim(` sev:${it.log_severity}`) : "";
403
- const ver = it.version != null ? chalk4.dim(` v${it.version}`) : "";
404
- return ` ${chalk4.dim(it.id)}
405
- ${it.name} ${chalk4.cyan(it.type ?? "")}${dir}${sev}${ver}`;
828
+ const dir = it.direction ? chalk5.dim(` dir:${it.direction}`) : "";
829
+ const sev = it.log_severity ? chalk5.dim(` sev:${it.log_severity}`) : "";
830
+ const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
831
+ return ` ${chalk5.dim(it.id)}
832
+ ${it.name} ${chalk5.cyan(it.type ?? "")}${dir}${sev}${ver}`;
406
833
  }
407
834
  );
408
835
  },
409
836
  renderGet(item, fmt) {
410
- emitDetail(
837
+ emitDetail2(
411
838
  item,
412
839
  fmt,
413
840
  [
@@ -436,7 +863,7 @@ var dlpFilteringProfiles = {
436
863
  };
437
864
  var dlpPatterns = {
438
865
  renderList(page, fmt) {
439
- emitList(
866
+ emitList2(
440
867
  page,
441
868
  fmt,
442
869
  "Data Patterns",
@@ -457,15 +884,15 @@ var dlpPatterns = {
457
884
  { key: "version", label: "Version" }
458
885
  ],
459
886
  (it) => {
460
- const tech = it.detection_config?.technique ? chalk4.dim(` ${it.detection_config.technique}`) : "";
461
- const ver = it.version != null ? chalk4.dim(` v${it.version}`) : "";
462
- return ` ${chalk4.dim(it.id)}
463
- ${it.name} ${chalk4.cyan(it.type ?? "")} ${statusColor(it.status)}${tech}${ver}`;
887
+ const tech = it.detection_config?.technique ? chalk5.dim(` ${it.detection_config.technique}`) : "";
888
+ const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
889
+ return ` ${chalk5.dim(it.id)}
890
+ ${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.status)}${tech}${ver}`;
464
891
  }
465
892
  );
466
893
  },
467
894
  renderGet(item, fmt) {
468
- emitDetail(
895
+ emitDetail2(
469
896
  item,
470
897
  fmt,
471
898
  [
@@ -504,7 +931,7 @@ var dlpPatterns = {
504
931
  };
505
932
  var dlpProfiles = {
506
933
  renderList(page, fmt) {
507
- emitList(
934
+ emitList2(
508
935
  page,
509
936
  fmt,
510
937
  "Data Profiles",
@@ -525,15 +952,15 @@ var dlpProfiles = {
525
952
  { key: "version", label: "Version" }
526
953
  ],
527
954
  (it) => {
528
- const ptype = it.profile_type ? chalk4.dim(` ${it.profile_type}`) : "";
529
- const ver = it.version != null ? chalk4.dim(` v${it.version}`) : "";
530
- return ` ${chalk4.dim(it.id)}
531
- ${it.name} ${chalk4.cyan(it.type ?? "")} ${statusColor(it.profile_status)}${ptype}${ver}`;
955
+ const ptype = it.profile_type ? chalk5.dim(` ${it.profile_type}`) : "";
956
+ const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
957
+ return ` ${chalk5.dim(it.id)}
958
+ ${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.profile_status)}${ptype}${ver}`;
532
959
  }
533
960
  );
534
961
  },
535
962
  renderGet(item, fmt) {
536
- emitDetail(
963
+ emitDetail2(
537
964
  item,
538
965
  fmt,
539
966
  [
@@ -561,7 +988,7 @@ var dlpProfiles = {
561
988
  };
562
989
  var dlpDictionaries = {
563
990
  renderList(page, fmt) {
564
- emitList(
991
+ emitList2(
565
992
  page,
566
993
  fmt,
567
994
  "Data Dictionaries",
@@ -582,15 +1009,15 @@ var dlpDictionaries = {
582
1009
  { key: "version", label: "Version" }
583
1010
  ],
584
1011
  (it) => {
585
- const kw = Array.isArray(it.keywords) ? chalk4.dim(` ${it.keywords.length} kw`) : "";
586
- const ver = it.version != null ? chalk4.dim(` v${it.version}`) : "";
587
- return ` ${chalk4.dim(it.id)}
588
- ${it.name} ${chalk4.cyan(it.type ?? "")} ${statusColor(it.status)}${kw}${ver}`;
1012
+ const kw = Array.isArray(it.keywords) ? chalk5.dim(` ${it.keywords.length} kw`) : "";
1013
+ const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
1014
+ return ` ${chalk5.dim(it.id)}
1015
+ ${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.status)}${kw}${ver}`;
589
1016
  }
590
1017
  );
591
1018
  },
592
1019
  renderGet(item, fmt) {
593
- emitDetail(
1020
+ emitDetail2(
594
1021
  item,
595
1022
  fmt,
596
1023
  [
@@ -627,7 +1054,7 @@ var dlpDictionaries = {
627
1054
  };
628
1055
 
629
1056
  // src/cli/renderer/eval.ts
630
- import chalk5 from "chalk";
1057
+ import chalk6 from "chalk";
631
1058
  function buildEvalOutput(profile, topic, intent, metrics, results) {
632
1059
  const fps = results.filter((r) => !r.testCase.expectedTriggered && r.actualTriggered).map((r) => ({ prompt: r.testCase.prompt, expected: false, actual: true }));
633
1060
  const fns = results.filter((r) => r.testCase.expectedTriggered && !r.actualTriggered).map((r) => ({ prompt: r.testCase.prompt, expected: true, actual: false }));
@@ -651,7 +1078,7 @@ function buildEvalOutput(profile, topic, intent, metrics, results) {
651
1078
  };
652
1079
  }
653
1080
  function renderEvalTerminal(output) {
654
- const coverageColor = output.metrics.coverage >= 0.9 ? chalk5.green : output.metrics.coverage >= 0.7 ? chalk5.yellow : chalk5.red;
1081
+ const coverageColor = output.metrics.coverage >= 0.9 ? chalk6.green : output.metrics.coverage >= 0.7 ? chalk6.yellow : chalk6.red;
655
1082
  ui.header("Eval Results");
656
1083
  ui.keyValue([
657
1084
  ["Profile", output.profile],
@@ -684,8 +1111,16 @@ function renderEvalTerminal(output) {
684
1111
  }
685
1112
 
686
1113
  // src/cli/renderer/modelsecurity.ts
687
- import chalk6 from "chalk";
688
- import { dump as yamlDump2 } from "js-yaml";
1114
+ import chalk7 from "chalk";
1115
+ function resourceView(name, columns, pretty) {
1116
+ return { name, columns, pretty };
1117
+ }
1118
+ function structuredList(name, items, columns, format, pretty) {
1119
+ emitList(resourceView(name, columns, { list: pretty, detail: () => void 0 }), items, format);
1120
+ }
1121
+ function structuredDetail(name, item, format, pretty) {
1122
+ emitDetail(resourceView(name, [], { list: () => void 0, detail: pretty }), item, format);
1123
+ }
689
1124
  function renderModelSecurityHeader() {
690
1125
  ui.header("Prisma AIRS \u2014 Model Security", "ML model supply chain security");
691
1126
  }
@@ -696,66 +1131,56 @@ function stateColor(state) {
696
1131
  case "ALLOWING":
697
1132
  case "PASSED":
698
1133
  case "SUCCESS":
699
- return chalk6.green;
1134
+ return chalk7.green;
700
1135
  case "BLOCKED":
701
1136
  case "BLOCKING":
702
1137
  case "FAILED":
703
- return chalk6.red;
1138
+ return chalk7.red;
704
1139
  case "DISABLED":
705
- return chalk6.dim;
1140
+ return chalk7.dim;
706
1141
  default:
707
- return chalk6.yellow;
1142
+ return chalk7.yellow;
708
1143
  }
709
1144
  }
710
1145
  function renderGroupList(groups, format = "pretty") {
711
- if (groups.length === 0) {
712
- ui.emptyList("security groups");
713
- return;
714
- }
715
1146
  if (format !== "pretty") {
716
- const rows = groups.map((g) => ({
717
- id: g.uuid,
718
- name: g.name,
719
- state: g.state,
720
- sourceType: g.sourceType
721
- }));
722
- console.log(
723
- formatOutput(
724
- rows,
725
- [
726
- { key: "id", label: "ID" },
727
- { key: "name", label: "Name" },
728
- { key: "state", label: "State" },
729
- { key: "sourceType", label: "Source Type" }
730
- ],
731
- format
732
- )
1147
+ structuredList(
1148
+ "security groups",
1149
+ groups,
1150
+ [
1151
+ { key: "uuid", label: "ID" },
1152
+ { key: "name", label: "Name" },
1153
+ { key: "state", label: "State" },
1154
+ { key: "sourceType", label: "Source Type" }
1155
+ ],
1156
+ format,
1157
+ () => void 0
733
1158
  );
734
1159
  return;
735
1160
  }
1161
+ if (groups.length === 0) {
1162
+ ui.emptyList("security groups");
1163
+ return;
1164
+ }
736
1165
  ui.section("Security Groups:");
737
1166
  for (const g of groups) {
738
1167
  ui.dim(g.uuid);
739
- const color = g.state === "ACTIVE" ? chalk6.green : chalk6.yellow;
740
- console.log(` ${g.name} ${color(g.state)} source: ${chalk6.dim(g.sourceType)}`);
1168
+ const color = g.state === "ACTIVE" ? chalk7.green : chalk7.yellow;
1169
+ console.log(` ${g.name} ${color(g.state)} source: ${chalk7.dim(g.sourceType)}`);
741
1170
  }
742
1171
  console.log();
743
1172
  }
744
1173
  function renderGroupDetail(group, format = "pretty") {
745
- if (format === "json") {
746
- console.log(JSON.stringify(group, null, 2));
747
- return;
748
- }
749
- if (format === "yaml") {
750
- console.log(yamlDump2(group));
1174
+ if (format !== "pretty") {
1175
+ structuredDetail("security group", group, format, () => void 0);
751
1176
  return;
752
1177
  }
753
1178
  ui.section("Security Group Detail:");
754
- const color = group.state === "ACTIVE" ? chalk6.green : chalk6.yellow;
1179
+ const color = group.state === "ACTIVE" ? chalk7.green : chalk7.yellow;
755
1180
  ui.keyValue([
756
1181
  ["UUID", group.uuid],
757
1182
  ["Name", group.name],
758
- ["Description", group.description || chalk6.dim("(none)")],
1183
+ ["Description", group.description || chalk7.dim("(none)")],
759
1184
  ["Source Type", group.sourceType],
760
1185
  ["State", color(group.state)],
761
1186
  ["Created", group.createdAt],
@@ -764,45 +1189,46 @@ function renderGroupDetail(group, format = "pretty") {
764
1189
  console.log();
765
1190
  }
766
1191
  function renderRuleList(rules, format = "pretty") {
767
- if (rules.length === 0) {
768
- ui.emptyList("security rules");
769
- return;
770
- }
771
1192
  if (format !== "pretty") {
772
- const rows = rules.map((r) => ({
773
- id: r.uuid,
774
- name: r.name,
775
- type: r.ruleType,
776
- defaultState: r.defaultState,
777
- sources: r.compatibleSources.join(", ")
778
- }));
779
- console.log(
780
- formatOutput(
781
- rows,
782
- [
783
- { key: "id", label: "ID" },
784
- { key: "name", label: "Name" },
785
- { key: "type", label: "Type" },
786
- { key: "defaultState", label: "Default State" },
787
- { key: "sources", label: "Sources" }
788
- ],
789
- format
790
- )
1193
+ structuredList(
1194
+ "security rules",
1195
+ rules,
1196
+ [
1197
+ { key: "uuid", label: "ID" },
1198
+ { key: "name", label: "Name" },
1199
+ { key: "ruleType", label: "Type" },
1200
+ { key: "defaultState", label: "Default State" },
1201
+ {
1202
+ key: "compatibleSources",
1203
+ label: "Sources",
1204
+ get: (rule) => rule.compatibleSources.join(", ")
1205
+ }
1206
+ ],
1207
+ format,
1208
+ () => void 0
791
1209
  );
792
1210
  return;
793
1211
  }
1212
+ if (rules.length === 0) {
1213
+ ui.emptyList("security rules");
1214
+ return;
1215
+ }
794
1216
  ui.section("Security Rules:");
795
1217
  for (const r of rules) {
796
1218
  ui.dim(r.uuid);
797
1219
  console.log(
798
- ` ${r.name} type: ${chalk6.dim(r.ruleType)} default: ${chalk6.dim(r.defaultState)}`
1220
+ ` ${r.name} type: ${chalk7.dim(r.ruleType)} default: ${chalk7.dim(r.defaultState)}`
799
1221
  );
800
- console.log(` ${chalk6.dim(r.description)}`);
801
- console.log(` Sources: ${r.compatibleSources.map((s) => chalk6.dim(s)).join(", ")}`);
1222
+ console.log(` ${chalk7.dim(r.description)}`);
1223
+ console.log(` Sources: ${r.compatibleSources.map((s) => chalk7.dim(s)).join(", ")}`);
802
1224
  }
803
1225
  console.log();
804
1226
  }
805
- function renderRuleDetail(rule) {
1227
+ function renderRuleDetail(rule, format = "pretty") {
1228
+ if (format !== "pretty") {
1229
+ structuredDetail("security rule", rule, format, () => void 0);
1230
+ return;
1231
+ }
806
1232
  ui.section("Security Rule Detail:");
807
1233
  ui.keyValue([
808
1234
  ["UUID", rule.uuid],
@@ -827,13 +1253,27 @@ function renderRuleDetail(rule) {
827
1253
  if (rule.editableFields.length > 0) {
828
1254
  ui.section("Editable Fields:");
829
1255
  for (const f of rule.editableFields) {
830
- console.log(` ${f.displayName} (${chalk6.dim(f.attributeName)}): ${f.displayType}`);
831
- if (f.description) console.log(` ${chalk6.dim(f.description)}`);
1256
+ console.log(` ${f.displayName} (${chalk7.dim(f.attributeName)}): ${f.displayType}`);
1257
+ if (f.description) console.log(` ${chalk7.dim(f.description)}`);
832
1258
  }
833
1259
  }
834
1260
  console.log();
835
1261
  }
836
- function renderRuleInstanceList(instances) {
1262
+ function renderRuleInstanceList(instances, format = "pretty") {
1263
+ if (format !== "pretty") {
1264
+ structuredList(
1265
+ "rule instances",
1266
+ instances,
1267
+ [
1268
+ { key: "uuid", label: "ID" },
1269
+ { key: "securityRuleUuid", label: "Rule ID" },
1270
+ { key: "state", label: "State" }
1271
+ ],
1272
+ format,
1273
+ () => void 0
1274
+ );
1275
+ return;
1276
+ }
837
1277
  if (instances.length === 0) {
838
1278
  ui.emptyList("rule instances");
839
1279
  return;
@@ -846,7 +1286,11 @@ function renderRuleInstanceList(instances) {
846
1286
  }
847
1287
  console.log();
848
1288
  }
849
- function renderRuleInstanceDetail(instance) {
1289
+ function renderRuleInstanceDetail(instance, format = "pretty") {
1290
+ if (format !== "pretty") {
1291
+ structuredDetail("rule instance", instance, format, () => void 0);
1292
+ return;
1293
+ }
850
1294
  ui.section("Rule Instance Detail:");
851
1295
  const pairs = [
852
1296
  ["UUID", instance.uuid],
@@ -871,54 +1315,47 @@ function renderRuleInstanceDetail(instance) {
871
1315
  console.log();
872
1316
  }
873
1317
  function renderMsScanList(scans, format = "pretty") {
874
- if (scans.length === 0) {
875
- ui.emptyList("scans");
876
- return;
877
- }
878
1318
  if (format !== "pretty") {
879
- const rows = scans.map((s) => ({
880
- id: s.uuid,
881
- outcome: s.evalOutcome,
882
- origin: s.scanOrigin,
883
- modelUri: s.modelUri ?? "",
884
- createdAt: s.createdAt,
885
- passed: s.evalSummary?.rulesPassed ?? "",
886
- failed: s.evalSummary?.rulesFailed ?? ""
887
- }));
888
- console.log(
889
- formatOutput(
890
- rows,
891
- [
892
- { key: "id", label: "ID" },
893
- { key: "outcome", label: "Outcome" },
894
- { key: "origin", label: "Origin" },
895
- { key: "modelUri", label: "Model URI" },
896
- { key: "passed", label: "Passed" },
897
- { key: "failed", label: "Failed" },
898
- { key: "createdAt", label: "Created" }
899
- ],
900
- format
901
- )
1319
+ structuredList(
1320
+ "scans",
1321
+ scans,
1322
+ [
1323
+ { key: "uuid", label: "ID" },
1324
+ { key: "evalOutcome", label: "Outcome" },
1325
+ { key: "scanOrigin", label: "Origin" },
1326
+ { key: "modelUri", label: "Model URI" },
1327
+ { key: "createdAt", label: "Created" }
1328
+ ],
1329
+ format,
1330
+ () => void 0
902
1331
  );
903
1332
  return;
904
1333
  }
1334
+ if (scans.length === 0) {
1335
+ ui.emptyList("scans");
1336
+ return;
1337
+ }
905
1338
  ui.section("Model Security Scans:");
906
1339
  for (const s of scans) {
907
1340
  ui.dim(s.uuid);
908
1341
  console.log(
909
- ` ${stateColor(s.evalOutcome)(s.evalOutcome)} ${chalk6.dim(s.scanOrigin)} ${chalk6.dim(s.createdAt)}`
1342
+ ` ${stateColor(s.evalOutcome)(s.evalOutcome)} ${chalk7.dim(s.scanOrigin)} ${chalk7.dim(s.createdAt)}`
910
1343
  );
911
- if (s.modelUri) console.log(` ${chalk6.dim(s.modelUri)}`);
1344
+ if (s.modelUri) console.log(` ${chalk7.dim(s.modelUri)}`);
912
1345
  if (s.evalSummary) {
913
1346
  const { rulesPassed, rulesFailed, totalRules } = s.evalSummary;
914
1347
  console.log(
915
- ` Rules: ${chalk6.green(`${rulesPassed} passed`)} ${chalk6.red(`${rulesFailed} failed`)} / ${totalRules} total`
1348
+ ` Rules: ${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
916
1349
  );
917
1350
  }
918
1351
  }
919
1352
  console.log();
920
1353
  }
921
- function renderMsScanDetail(scan) {
1354
+ function renderMsScanDetail(scan, format = "pretty") {
1355
+ if (format !== "pretty") {
1356
+ structuredDetail("scan", scan, format, () => void 0);
1357
+ return;
1358
+ }
922
1359
  ui.section("Scan Detail:");
923
1360
  const pairs = [
924
1361
  ["UUID", scan.uuid],
@@ -934,7 +1371,7 @@ function renderMsScanDetail(scan) {
934
1371
  const { rulesPassed, rulesFailed, totalRules } = scan.evalSummary;
935
1372
  pairs.push([
936
1373
  "Rules",
937
- `${chalk6.green(`${rulesPassed} passed`)} ${chalk6.red(`${rulesFailed} failed`)} / ${totalRules} total`
1374
+ `${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
938
1375
  ]);
939
1376
  }
940
1377
  ui.keyValue(pairs);
@@ -944,7 +1381,22 @@ function renderMsScanDetail(scan) {
944
1381
  }
945
1382
  console.log();
946
1383
  }
947
- function renderEvaluationList(evaluations) {
1384
+ function renderEvaluationList(evaluations, format = "pretty") {
1385
+ if (format !== "pretty") {
1386
+ structuredList(
1387
+ "evaluations",
1388
+ evaluations,
1389
+ [
1390
+ { key: "uuid", label: "ID" },
1391
+ { key: "ruleName", label: "Rule" },
1392
+ { key: "result", label: "Result" },
1393
+ { key: "ruleInstanceState", label: "State" }
1394
+ ],
1395
+ format,
1396
+ () => void 0
1397
+ );
1398
+ return;
1399
+ }
948
1400
  if (evaluations.length === 0) {
949
1401
  ui.emptyList("evaluations");
950
1402
  return;
@@ -953,12 +1405,16 @@ function renderEvaluationList(evaluations) {
953
1405
  for (const e of evaluations) {
954
1406
  ui.dim(e.uuid);
955
1407
  console.log(
956
- ` ${e.ruleName} ${stateColor(e.result)(e.result)} ${chalk6.dim(e.ruleInstanceState)}`
1408
+ ` ${e.ruleName} ${stateColor(e.result)(e.result)} ${chalk7.dim(e.ruleInstanceState)}`
957
1409
  );
958
1410
  }
959
1411
  console.log();
960
1412
  }
961
- function renderEvaluationDetail(evaluation) {
1413
+ function renderEvaluationDetail(evaluation, format = "pretty") {
1414
+ if (format !== "pretty") {
1415
+ structuredDetail("evaluation", evaluation, format, () => void 0);
1416
+ return;
1417
+ }
962
1418
  ui.section("Evaluation Detail:");
963
1419
  ui.keyValue([
964
1420
  ["UUID", evaluation.uuid],
@@ -971,7 +1427,22 @@ function renderEvaluationDetail(evaluation) {
971
1427
  ]);
972
1428
  console.log();
973
1429
  }
974
- function renderViolationList(violations) {
1430
+ function renderViolationList(violations, format = "pretty") {
1431
+ if (format !== "pretty") {
1432
+ structuredList(
1433
+ "violations",
1434
+ violations,
1435
+ [
1436
+ { key: "uuid", label: "ID" },
1437
+ { key: "ruleName", label: "Rule" },
1438
+ { key: "file", label: "File" },
1439
+ { key: "threat", label: "Threat" }
1440
+ ],
1441
+ format,
1442
+ () => void 0
1443
+ );
1444
+ return;
1445
+ }
975
1446
  if (violations.length === 0) {
976
1447
  ui.emptyList("violations");
977
1448
  return;
@@ -979,17 +1450,21 @@ function renderViolationList(violations) {
979
1450
  ui.section("Violations:");
980
1451
  for (const v of violations) {
981
1452
  ui.dim(v.uuid);
982
- console.log(` ${chalk6.red(v.ruleName)} ${chalk6.dim(v.file)}`);
1453
+ console.log(` ${chalk7.red(v.ruleName)} ${chalk7.dim(v.file)}`);
983
1454
  console.log(` ${v.description}`);
984
- console.log(` Threat: ${chalk6.dim(v.threat)}`);
1455
+ console.log(` Threat: ${chalk7.dim(v.threat)}`);
985
1456
  }
986
1457
  console.log();
987
1458
  }
988
- function renderViolationDetail(violation) {
1459
+ function renderViolationDetail(violation, format = "pretty") {
1460
+ if (format !== "pretty") {
1461
+ structuredDetail("violation", violation, format, () => void 0);
1462
+ return;
1463
+ }
989
1464
  ui.section("Violation Detail:");
990
1465
  ui.keyValue([
991
1466
  ["UUID", violation.uuid],
992
- ["Rule", chalk6.red(violation.ruleName)],
1467
+ ["Rule", chalk7.red(violation.ruleName)],
993
1468
  ["Description", violation.ruleDescription],
994
1469
  ["State", violation.ruleInstanceState],
995
1470
  ["File", violation.file],
@@ -998,15 +1473,30 @@ function renderViolationDetail(violation) {
998
1473
  ]);
999
1474
  console.log();
1000
1475
  }
1001
- function renderFileList(files) {
1476
+ function renderFileList(files, format = "pretty") {
1477
+ if (format !== "pretty") {
1478
+ structuredList(
1479
+ "files",
1480
+ files,
1481
+ [
1482
+ { key: "uuid", label: "ID" },
1483
+ { key: "path", label: "Path" },
1484
+ { key: "type", label: "Type" },
1485
+ { key: "result", label: "Result" }
1486
+ ],
1487
+ format,
1488
+ () => void 0
1489
+ );
1490
+ return;
1491
+ }
1002
1492
  if (files.length === 0) {
1003
1493
  ui.emptyList("files");
1004
1494
  return;
1005
1495
  }
1006
1496
  ui.section("Scanned Files:");
1007
1497
  for (const f of files) {
1008
- const color = f.result === "SUCCESS" ? chalk6.green : f.result === "SKIPPED" ? chalk6.yellow : chalk6.red;
1009
- const formats = f.formats.length > 0 ? chalk6.dim(` [${f.formats.join(", ")}]`) : "";
1498
+ const color = f.result === "SUCCESS" ? chalk7.green : f.result === "SKIPPED" ? chalk7.yellow : chalk7.red;
1499
+ const formats = f.formats.length > 0 ? chalk7.dim(` [${f.formats.join(", ")}]`) : "";
1010
1500
  console.log(` ${color(f.result)} ${f.type} ${f.path}${formats}`);
1011
1501
  }
1012
1502
  console.log();
@@ -1034,45 +1524,42 @@ function renderLabelValues(key, values) {
1034
1524
  console.log();
1035
1525
  }
1036
1526
  function renderModelList(models, format = "pretty") {
1037
- if (models.length === 0) {
1038
- ui.emptyList("models");
1039
- return;
1040
- }
1041
1527
  if (format !== "pretty") {
1042
- const rows = models.map((m) => ({
1043
- id: m.uuid,
1044
- name: m.name,
1045
- outcome: m.latestVersionOutcome ?? "",
1046
- formats: (m.latestVersionFormats ?? []).join(", "),
1047
- scanned: m.latestVersionScanTime ?? ""
1048
- }));
1049
- console.log(
1050
- formatOutput(
1051
- rows,
1052
- [
1053
- { key: "id", label: "ID" },
1054
- { key: "name", label: "Name" },
1055
- { key: "outcome", label: "Outcome" },
1056
- { key: "formats", label: "Formats" },
1057
- { key: "scanned", label: "Last Scan" }
1058
- ],
1059
- format
1060
- )
1528
+ structuredList(
1529
+ "models",
1530
+ models,
1531
+ [
1532
+ { key: "uuid", label: "ID" },
1533
+ { key: "name", label: "Name" },
1534
+ { key: "latestVersionOutcome", label: "Outcome" },
1535
+ {
1536
+ key: "latestVersionFormats",
1537
+ label: "Formats",
1538
+ get: (model) => (model.latestVersionFormats ?? []).join(", ")
1539
+ },
1540
+ { key: "latestVersionScanTime", label: "Last Scan" }
1541
+ ],
1542
+ format,
1543
+ () => void 0
1061
1544
  );
1062
1545
  return;
1063
1546
  }
1547
+ if (models.length === 0) {
1548
+ ui.emptyList("models");
1549
+ return;
1550
+ }
1064
1551
  ui.section("Models:");
1065
1552
  for (const m of models) {
1066
1553
  ui.dim(m.uuid);
1067
- const outcome = m.latestVersionOutcome ? stateColor(m.latestVersionOutcome)(m.latestVersionOutcome) : chalk6.dim("unscanned");
1068
- const formats = m.latestVersionFormats && m.latestVersionFormats.length > 0 ? chalk6.dim(` [${m.latestVersionFormats.join(", ")}]`) : "";
1554
+ const outcome = m.latestVersionOutcome ? stateColor(m.latestVersionOutcome)(m.latestVersionOutcome) : chalk7.dim("unscanned");
1555
+ const formats = m.latestVersionFormats && m.latestVersionFormats.length > 0 ? chalk7.dim(` [${m.latestVersionFormats.join(", ")}]`) : "";
1069
1556
  console.log(` ${m.name} ${outcome}${formats}`);
1070
1557
  console.log();
1071
1558
  }
1072
1559
  }
1073
1560
  function renderModelDetail(model, format = "pretty") {
1074
1561
  if (format !== "pretty") {
1075
- console.log(format === "json" ? JSON.stringify(model, null, 2) : yamlDump2(model));
1562
+ structuredDetail("model", model, format, () => void 0);
1076
1563
  return;
1077
1564
  }
1078
1565
  ui.section("Model Detail:");
@@ -1099,37 +1586,30 @@ function renderModelDetail(model, format = "pretty") {
1099
1586
  console.log();
1100
1587
  }
1101
1588
  function renderModelVersionList(versions, format = "pretty") {
1102
- if (versions.length === 0) {
1103
- ui.emptyList("versions");
1104
- return;
1105
- }
1106
1589
  if (format !== "pretty") {
1107
- const rows = versions.map((v) => ({
1108
- id: v.uuid,
1109
- revision: v.revision,
1110
- files: v.fileCount ?? "",
1111
- outcome: v.lastEvalOutcome ?? "",
1112
- scanned: v.latestScanTime ?? ""
1113
- }));
1114
- console.log(
1115
- formatOutput(
1116
- rows,
1117
- [
1118
- { key: "id", label: "ID" },
1119
- { key: "revision", label: "Revision" },
1120
- { key: "files", label: "Files" },
1121
- { key: "outcome", label: "Outcome" },
1122
- { key: "scanned", label: "Last Scan" }
1123
- ],
1124
- format
1125
- )
1590
+ structuredList(
1591
+ "versions",
1592
+ versions,
1593
+ [
1594
+ { key: "uuid", label: "ID" },
1595
+ { key: "revision", label: "Revision" },
1596
+ { key: "fileCount", label: "Files" },
1597
+ { key: "lastEvalOutcome", label: "Outcome" },
1598
+ { key: "latestScanTime", label: "Last Scan" }
1599
+ ],
1600
+ format,
1601
+ () => void 0
1126
1602
  );
1127
1603
  return;
1128
1604
  }
1605
+ if (versions.length === 0) {
1606
+ ui.emptyList("versions");
1607
+ return;
1608
+ }
1129
1609
  ui.section("Model Versions:");
1130
1610
  for (const v of versions) {
1131
1611
  ui.dim(v.uuid);
1132
- const outcome = v.lastEvalOutcome ? stateColor(v.lastEvalOutcome)(v.lastEvalOutcome) : chalk6.dim("unscanned");
1612
+ const outcome = v.lastEvalOutcome ? stateColor(v.lastEvalOutcome)(v.lastEvalOutcome) : chalk7.dim("unscanned");
1133
1613
  const files = v.fileCount != null ? ` files: ${v.fileCount}` : "";
1134
1614
  console.log(` ${v.revision} ${outcome}${files}`);
1135
1615
  console.log();
@@ -1137,7 +1617,7 @@ function renderModelVersionList(versions, format = "pretty") {
1137
1617
  }
1138
1618
  function renderModelVersionDetail(version, format = "pretty") {
1139
1619
  if (format !== "pretty") {
1140
- console.log(format === "json" ? JSON.stringify(version, null, 2) : yamlDump2(version));
1620
+ structuredDetail("model version", version, format, () => void 0);
1141
1621
  return;
1142
1622
  }
1143
1623
  ui.section("Model Version Detail:");
@@ -1169,38 +1649,19 @@ function renderModelVersionDetail(version, format = "pretty") {
1169
1649
  console.log();
1170
1650
  }
1171
1651
  function renderModelFileList(files, format = "pretty") {
1172
- if (files.length === 0) {
1173
- ui.emptyList("files");
1652
+ if (format !== "pretty") {
1653
+ renderFileList(files, format);
1174
1654
  return;
1175
1655
  }
1176
- if (format !== "pretty") {
1177
- const rows = files.map((f) => ({
1178
- id: f.uuid,
1179
- path: f.path,
1180
- type: f.type,
1181
- formats: f.formats.join(", "),
1182
- result: f.result
1183
- }));
1184
- console.log(
1185
- formatOutput(
1186
- rows,
1187
- [
1188
- { key: "id", label: "ID" },
1189
- { key: "path", label: "Path" },
1190
- { key: "type", label: "Type" },
1191
- { key: "formats", label: "Formats" },
1192
- { key: "result", label: "Result" }
1193
- ],
1194
- format
1195
- )
1196
- );
1656
+ if (files.length === 0) {
1657
+ ui.emptyList("files");
1197
1658
  return;
1198
1659
  }
1199
1660
  renderFileList(files);
1200
1661
  }
1201
1662
 
1202
1663
  // src/cli/renderer/redteam.ts
1203
- import chalk7 from "chalk";
1664
+ import chalk8 from "chalk";
1204
1665
  import { dump as yamlDump3 } from "js-yaml";
1205
1666
  function renderRedteamHeader() {
1206
1667
  ui.header("Prisma AIRS \u2014 AI Red Team", "Adversarial scan operations");
@@ -1208,37 +1669,37 @@ function renderRedteamHeader() {
1208
1669
  function severityColor(severity) {
1209
1670
  switch (severity.toUpperCase()) {
1210
1671
  case "CRITICAL":
1211
- return chalk7.red;
1672
+ return chalk8.red;
1212
1673
  case "HIGH":
1213
- return chalk7.magenta;
1674
+ return chalk8.magenta;
1214
1675
  case "MEDIUM":
1215
- return chalk7.yellow;
1676
+ return chalk8.yellow;
1216
1677
  case "LOW":
1217
- return chalk7.cyan;
1678
+ return chalk8.cyan;
1218
1679
  default:
1219
- return chalk7.dim;
1680
+ return chalk8.dim;
1220
1681
  }
1221
1682
  }
1222
- function statusColor2(status) {
1683
+ function statusColor3(status) {
1223
1684
  switch (status) {
1224
1685
  case "COMPLETED":
1225
- return chalk7.green;
1686
+ return chalk8.green;
1226
1687
  case "RUNNING":
1227
- return chalk7.blue;
1688
+ return chalk8.blue;
1228
1689
  case "QUEUED":
1229
1690
  case "INIT":
1230
- return chalk7.yellow;
1691
+ return chalk8.yellow;
1231
1692
  case "FAILED":
1232
1693
  case "ABORTED":
1233
- return chalk7.red;
1694
+ return chalk8.red;
1234
1695
  case "PARTIALLY_COMPLETE":
1235
- return chalk7.yellow;
1696
+ return chalk8.yellow;
1236
1697
  default:
1237
- return chalk7.white;
1698
+ return chalk8.white;
1238
1699
  }
1239
1700
  }
1240
1701
  function activeState(active) {
1241
- return statusColor2(active ? "COMPLETED" : "FAILED")(active ? "active" : "inactive");
1702
+ return statusColor3(active ? "COMPLETED" : "FAILED")(active ? "active" : "inactive");
1242
1703
  }
1243
1704
  function renderScanStatus(job) {
1244
1705
  ui.section("Scan Status:");
@@ -1248,7 +1709,7 @@ function renderScanStatus(job) {
1248
1709
  ["Type", job.jobType]
1249
1710
  ];
1250
1711
  if (job.targetName) pairs.push(["Target", job.targetName]);
1251
- pairs.push(["Status", statusColor2(job.status)(job.status)]);
1712
+ pairs.push(["Status", statusColor3(job.status)(job.status)]);
1252
1713
  if (job.total != null && job.completed != null) {
1253
1714
  pairs.push(["Progress", `${job.completed}/${job.total}`]);
1254
1715
  }
@@ -1291,9 +1752,9 @@ function renderScanList(jobs, format = "pretty") {
1291
1752
  for (const job of jobs) {
1292
1753
  ui.dim(job.uuid);
1293
1754
  console.log(
1294
- ` ${job.name} ${statusColor2(job.status)(job.status)} ${job.jobType}${job.score != null ? ` score: ${job.score}` : ""}`
1755
+ ` ${job.name} ${statusColor3(job.status)(job.status)} ${job.jobType}${job.score != null ? ` score: ${job.score}` : ""}`
1295
1756
  );
1296
- if (job.createdAt) console.log(` ${chalk7.dim(job.createdAt)}`);
1757
+ if (job.createdAt) console.log(` ${chalk8.dim(job.createdAt)}`);
1297
1758
  console.log();
1298
1759
  }
1299
1760
  }
@@ -1308,7 +1769,7 @@ function renderStaticReport(report) {
1308
1769
  for (const s of report.severityBreakdown) {
1309
1770
  const color = severityColor(s.severity);
1310
1771
  console.log(
1311
- ` ${color(s.severity.padEnd(10))} ${chalk7.red(`${s.successful} bypassed`)} ${chalk7.green(`${s.failed} blocked`)}`
1772
+ ` ${color(s.severity.padEnd(10))} ${chalk8.red(`${s.successful} bypassed`)} ${chalk8.green(`${s.failed} blocked`)}`
1312
1773
  );
1313
1774
  }
1314
1775
  }
@@ -1386,10 +1847,10 @@ function renderAttackList(attacks, options) {
1386
1847
  }
1387
1848
  ui.section("Attacks:");
1388
1849
  for (const a of attacks) {
1389
- const sev = a.severity ? severityColor(a.severity)(a.severity.padEnd(10)) : chalk7.dim("N/A".padEnd(10));
1390
- const result = a.successful ? chalk7.red("BYPASSED") : chalk7.green("BLOCKED");
1850
+ const sev = a.severity ? severityColor(a.severity)(a.severity.padEnd(10)) : chalk8.dim("N/A".padEnd(10));
1851
+ const result = a.successful ? chalk8.red("BYPASSED") : chalk8.green("BLOCKED");
1391
1852
  const label = a.subCategoryDisplayName ?? a.subCategory ?? "\u2014";
1392
- console.log(` ${sev} ${result} ${label}${a.category ? chalk7.dim(` [${a.category}]`) : ""}`);
1853
+ console.log(` ${sev} ${result} ${label}${a.category ? chalk8.dim(` [${a.category}]`) : ""}`);
1393
1854
  }
1394
1855
  if (options?.footnote) ui.dim(options.footnote);
1395
1856
  console.log();
@@ -1409,11 +1870,11 @@ function renderCustomAttackList(attacks) {
1409
1870
  }
1410
1871
  ui.section("Custom Attacks:");
1411
1872
  for (const a of attacks) {
1412
- const result = a.threat ? chalk7.red("THREAT") : chalk7.green("SAFE");
1873
+ const result = a.threat ? chalk8.red("THREAT") : chalk8.green("SAFE");
1413
1874
  const prompt = a.promptText.length > 80 ? `${a.promptText.substring(0, 77)}...` : a.promptText;
1414
- const asrStr = a.asr != null ? chalk7.dim(` ASR: ${a.asr.toFixed(1)}%`) : "";
1875
+ const asrStr = a.asr != null ? chalk8.dim(` ASR: ${a.asr.toFixed(1)}%`) : "";
1415
1876
  console.log(` ${result}${asrStr} ${prompt}`);
1416
- if (a.goal) console.log(` ${chalk7.dim(a.goal)}`);
1877
+ if (a.goal) console.log(` ${chalk8.dim(a.goal)}`);
1417
1878
  }
1418
1879
  console.log();
1419
1880
  }
@@ -1460,11 +1921,11 @@ function renderCategories(categories) {
1460
1921
  ui.section("Attack Categories:");
1461
1922
  for (const c of categories) {
1462
1923
  console.log(
1463
- ` ${chalk7.bold(c.displayName)} ${chalk7.cyan(`(${c.id})`)}${c.description ? chalk7.dim(` \u2014 ${c.description}`) : ""}`
1924
+ ` ${chalk8.bold(c.displayName)} ${chalk8.cyan(`(${c.id})`)}${c.description ? chalk8.dim(` \u2014 ${c.description}`) : ""}`
1464
1925
  );
1465
1926
  for (const sc of c.subCategories) {
1466
1927
  console.log(
1467
- ` ${chalk7.dim("\u2022")} ${sc.displayName} ${chalk7.cyan(`(${sc.id})`)}${sc.description ? chalk7.dim(` \u2014 ${sc.description}`) : ""}`
1928
+ ` ${chalk8.dim("\u2022")} ${sc.displayName} ${chalk8.cyan(`(${sc.id})`)}${sc.description ? chalk8.dim(` \u2014 ${sc.description}`) : ""}`
1468
1929
  );
1469
1930
  }
1470
1931
  console.log();
@@ -1600,11 +2061,11 @@ function renderPromptList(prompts, format = "pretty") {
1600
2061
  }
1601
2062
  ui.section("Prompts:");
1602
2063
  for (const p of prompts) {
1603
- const status = p.active ? chalk7.green("active") : chalk7.dim("inactive");
2064
+ const status = p.active ? chalk8.green("active") : chalk8.dim("inactive");
1604
2065
  const text = p.prompt.length > 80 ? `${p.prompt.substring(0, 77)}...` : p.prompt;
1605
- console.log(` ${chalk7.dim(p.uuid)} ${status}`);
2066
+ console.log(` ${chalk8.dim(p.uuid)} ${status}`);
1606
2067
  console.log(` ${text}`);
1607
- if (p.goal) console.log(` ${chalk7.dim(`Goal: ${p.goal}`)}`);
2068
+ if (p.goal) console.log(` ${chalk8.dim(`Goal: ${p.goal}`)}`);
1608
2069
  }
1609
2070
  console.log();
1610
2071
  }
@@ -1621,7 +2082,7 @@ function renderPromptDetail(p, format = "pretty") {
1621
2082
  const pairs = [
1622
2083
  ["UUID", p.uuid],
1623
2084
  ["Set UUID", p.promptSetId],
1624
- ["Status", p.active ? chalk7.green("active") : chalk7.dim("inactive")],
2085
+ ["Status", p.active ? chalk8.green("active") : chalk8.dim("inactive")],
1625
2086
  ["Prompt", p.prompt]
1626
2087
  ];
1627
2088
  if (p.goal) pairs.push(["Goal", p.goal]);
@@ -1653,7 +2114,7 @@ function renderPropertyNames(names, format = "pretty") {
1653
2114
  function renderAuthValidation(result) {
1654
2115
  ui.section("Auth Validation:");
1655
2116
  const pairs = [
1656
- ["Validated", result.validated ? chalk7.green("yes") : chalk7.red("no")]
2117
+ ["Validated", result.validated ? chalk8.green("yes") : chalk8.red("no")]
1657
2118
  ];
1658
2119
  if (result.tokenPreview) pairs.push(["Token", result.tokenPreview]);
1659
2120
  if (result.expiresIn != null) pairs.push(["Expires In", `${result.expiresIn}s`]);
@@ -1671,7 +2132,7 @@ function renderTargetTemplates(templates) {
1671
2132
  function renderEulaStatus(status) {
1672
2133
  ui.section("EULA Status:");
1673
2134
  const pairs = [
1674
- ["Accepted", status.isAccepted ? chalk7.green("yes") : chalk7.red("no")]
2135
+ ["Accepted", status.isAccepted ? chalk8.green("yes") : chalk8.red("no")]
1675
2136
  ];
1676
2137
  if (status.acceptedAt) pairs.push(["Accepted At", status.acceptedAt]);
1677
2138
  if (status.acceptedByUserId) pairs.push(["Accepted By", status.acceptedByUserId]);
@@ -1709,7 +2170,7 @@ function renderInstanceResponse(resp) {
1709
2170
  if (resp.tenantId) pairs.push(["Tenant ID", resp.tenantId]);
1710
2171
  if (resp.appId) pairs.push(["App ID", resp.appId]);
1711
2172
  if (resp.isSuccess != null) {
1712
- pairs.push(["Success", resp.isSuccess ? chalk7.green("yes") : chalk7.red("no")]);
2173
+ pairs.push(["Success", resp.isSuccess ? chalk8.green("yes") : chalk8.red("no")]);
1713
2174
  }
1714
2175
  ui.keyValue(pairs);
1715
2176
  console.log();
@@ -1751,13 +2212,13 @@ function renderRegistryCredentials(creds, format = "pretty") {
1751
2212
  function channelStatusColor(status) {
1752
2213
  switch (status.toUpperCase()) {
1753
2214
  case "ONLINE":
1754
- return chalk7.green;
2215
+ return chalk8.green;
1755
2216
  case "DRAFT":
1756
- return chalk7.yellow;
2217
+ return chalk8.yellow;
1757
2218
  case "OFFLINE":
1758
- return chalk7.red;
2219
+ return chalk8.red;
1759
2220
  default:
1760
- return chalk7.white;
2221
+ return chalk8.white;
1761
2222
  }
1762
2223
  }
1763
2224
  function renderChannelList(channels, format = "pretty") {
@@ -1791,7 +2252,7 @@ function renderChannelList(channels, format = "pretty") {
1791
2252
  ui.section("Network Broker Channels:");
1792
2253
  for (const c of channels) {
1793
2254
  if (c.uuid) ui.dim(c.uuid);
1794
- const status = c.status ? channelStatusColor(c.status)(c.status) : chalk7.dim("unknown");
2255
+ const status = c.status ? channelStatusColor(c.status)(c.status) : chalk8.dim("unknown");
1795
2256
  const clients = c.connectedClientsCount != null ? ` clients: ${c.connectedClientsCount}` : "";
1796
2257
  console.log(` ${c.name ?? "(unnamed)"} ${status}${clients}`);
1797
2258
  console.log();
@@ -1869,7 +2330,7 @@ function renderLanguages(data, format = "pretty") {
1869
2330
  }
1870
2331
  ui.section("Languages:");
1871
2332
  for (const l of data.languages) {
1872
- console.log(` ${chalk7.dim(l.code)} ${l.name}`);
2333
+ console.log(` ${chalk8.dim(l.code)} ${l.name}`);
1873
2334
  }
1874
2335
  console.log();
1875
2336
  }
@@ -1903,45 +2364,140 @@ function renderErrorLogs(logs, format = "pretty") {
1903
2364
  }
1904
2365
  ui.section("Target-Profile Error Logs:");
1905
2366
  for (const l of logs) {
1906
- const type = l.errorType ? chalk7.red(l.errorType) : chalk7.dim("error");
2367
+ const type = l.errorType ? chalk8.red(l.errorType) : chalk8.dim("error");
1907
2368
  console.log(
1908
- ` ${chalk7.dim(l.createdAt)} ${type}${l.errorSource ? ` (${l.errorSource})` : ""}`
2369
+ ` ${chalk8.dim(l.createdAt)} ${type}${l.errorSource ? ` (${l.errorSource})` : ""}`
1909
2370
  );
1910
2371
  if (l.errorMessage) console.log(` ${l.errorMessage}`);
1911
- if (l.jobId) console.log(` ${chalk7.dim(`job: ${l.jobId}`)}`);
2372
+ if (l.jobId) console.log(` ${chalk8.dim(`job: ${l.jobId}`)}`);
2373
+ console.log();
2374
+ }
2375
+ }
2376
+ function renderAdapterList(adapters, format = "pretty", totalItems) {
2377
+ if (adapters.length === 0) {
2378
+ ui.emptyList("adapters");
2379
+ return;
2380
+ }
2381
+ if (format !== "pretty") {
2382
+ const rows = adapters.map((a) => ({
2383
+ uuid: a.uuid,
2384
+ name: a.name,
2385
+ status: a.status,
2386
+ targets: a.targetCount ?? "",
2387
+ updated: a.updatedAt ?? ""
2388
+ }));
2389
+ console.log(
2390
+ formatOutput(
2391
+ rows,
2392
+ [
2393
+ { key: "uuid", label: "UUID" },
2394
+ { key: "name", label: "Name" },
2395
+ { key: "status", label: "Status" },
2396
+ { key: "targets", label: "Targets" },
2397
+ { key: "updated", label: "Updated" }
2398
+ ],
2399
+ format
2400
+ )
2401
+ );
2402
+ return;
2403
+ }
2404
+ ui.section("Custom Target Adapters:");
2405
+ for (const a of adapters) {
2406
+ ui.dim(a.uuid);
2407
+ const status = a.status === "ACTIVE" ? chalk8.green(a.status) : chalk8.yellow(a.status);
2408
+ const targets = a.targetCount != null ? ` targets: ${a.targetCount}` : "";
2409
+ console.log(` ${a.name} ${status}${targets}`);
1912
2410
  console.log();
1913
2411
  }
2412
+ if (totalItems !== void 0) ui.dim(`${totalItems} total`);
2413
+ }
2414
+ function renderAdapterDetail(adapter, format = "pretty") {
2415
+ if (format !== "pretty") {
2416
+ console.log(format === "json" ? JSON.stringify(adapter, null, 2) : yamlDump3(adapter));
2417
+ return;
2418
+ }
2419
+ ui.section("Adapter Detail:");
2420
+ const pairs = [
2421
+ ["UUID", adapter.uuid],
2422
+ ["Name", adapter.name],
2423
+ [
2424
+ "Status",
2425
+ adapter.status === "ACTIVE" ? chalk8.green(adapter.status) : chalk8.yellow(adapter.status)
2426
+ ],
2427
+ ["Script", `${adapter.scriptB64.length} base64 chars`]
2428
+ ];
2429
+ if (adapter.description != null) pairs.push(["Description", adapter.description]);
2430
+ if (adapter.networkBrokerChannelUuid != null)
2431
+ pairs.push(["Broker Channel", adapter.networkBrokerChannelUuid]);
2432
+ if (adapter.targetCount != null) pairs.push(["Targets", adapter.targetCount]);
2433
+ if (adapter.createdAt != null) pairs.push(["Created", adapter.createdAt]);
2434
+ if (adapter.updatedAt != null) pairs.push(["Updated", adapter.updatedAt]);
2435
+ ui.keyValue(pairs);
2436
+ if (adapter.variables.length > 0) {
2437
+ ui.section("Variables:");
2438
+ ui.keyValue(
2439
+ adapter.variables.map((v) => [
2440
+ `${v.key} (${v.type})`,
2441
+ v.isRedacted ? chalk8.dim("(redacted)") : v.value ?? ""
2442
+ ])
2443
+ );
2444
+ }
2445
+ console.log();
2446
+ }
2447
+ function renderAdapterValidation(result, format = "pretty") {
2448
+ if (format !== "pretty") {
2449
+ console.log(format === "json" ? JSON.stringify(result, null, 2) : yamlDump3(result));
2450
+ return;
2451
+ }
2452
+ if (result.validated) {
2453
+ ui.success("Adapter script validated");
2454
+ } else {
2455
+ ui.error("Adapter script validation FAILED");
2456
+ }
2457
+ if (result.stdout) {
2458
+ ui.section("stdout:");
2459
+ console.log(result.stdout);
2460
+ }
2461
+ if (result.stderr) {
2462
+ ui.section("stderr:");
2463
+ console.log(chalk8.red(result.stderr));
2464
+ }
2465
+ if (result.traceback) {
2466
+ ui.section("traceback:");
2467
+ console.log(chalk8.red(result.traceback));
2468
+ }
2469
+ console.log();
1914
2470
  }
1915
2471
 
1916
2472
  // src/cli/renderer/runtime.ts
1917
- import chalk8 from "chalk";
2473
+ import chalk9 from "chalk";
1918
2474
  function renderScanProgress(job) {
1919
2475
  if (job.total != null && job.completed != null && job.total > 0) {
1920
2476
  const pct = Math.round(job.completed / job.total * 100);
1921
2477
  const bar = "\u2588".repeat(Math.round(pct / 5)) + "\u2591".repeat(20 - Math.round(pct / 5));
1922
2478
  process.stdout.write(
1923
- `\r ${statusColor3(job.status)(job.status)} ${bar} ${pct}% (${job.completed}/${job.total})`
2479
+ `\r ${statusColor4(job.status)(job.status)} ${bar} ${pct}% (${job.completed}/${job.total})`
1924
2480
  );
1925
2481
  } else {
1926
- process.stdout.write(`\r ${statusColor3(job.status)(job.status)}...`);
2482
+ process.stdout.write(`\r ${statusColor4(job.status)(job.status)}...`);
1927
2483
  }
1928
2484
  }
1929
- function statusColor3(status) {
2485
+ function statusColor4(status) {
1930
2486
  switch (status) {
1931
2487
  case "COMPLETED":
1932
- return chalk8.green;
2488
+ return chalk9.green;
1933
2489
  case "RUNNING":
1934
- return chalk8.blue;
2490
+ return chalk9.blue;
1935
2491
  case "QUEUED":
1936
2492
  case "INIT":
1937
- return chalk8.yellow;
2493
+ return chalk9.yellow;
1938
2494
  case "FAILED":
1939
2495
  case "ABORTED":
1940
- return chalk8.red;
2496
+ return chalk9.red;
1941
2497
  case "PARTIALLY_COMPLETE":
1942
- return chalk8.yellow;
2498
+ return chalk9.yellow;
1943
2499
  default:
1944
- return chalk8.white;
2500
+ return chalk9.white;
1945
2501
  }
1946
2502
  }
1947
2503
  function renderRuntimeConfigHeader() {
@@ -1971,8 +2527,8 @@ function renderProfileList(profiles, format = "pretty") {
1971
2527
  ui.section("Security Profiles:");
1972
2528
  for (const p of profiles) {
1973
2529
  ui.dim(p.profileId);
1974
- const status = p.active ? chalk8.green("active") : chalk8.yellow("inactive");
1975
- const rev = p.revision != null ? chalk8.dim(` rev:${p.revision}`) : "";
2530
+ const status = p.active ? chalk9.green("active") : chalk9.yellow("inactive");
2531
+ const rev = p.revision != null ? chalk9.dim(` rev:${p.revision}`) : "";
1976
2532
  console.log(` ${p.profileName} ${status}${rev}`);
1977
2533
  }
1978
2534
  console.log();
@@ -1982,7 +2538,7 @@ function renderProfileDetail(profile) {
1982
2538
  const pairs = [
1983
2539
  ["ID", profile.profileId],
1984
2540
  ["Name", profile.profileName],
1985
- ["Status", profile.active ? chalk8.green("active") : chalk8.yellow("inactive")]
2541
+ ["Status", profile.active ? chalk9.green("active") : chalk9.yellow("inactive")]
1986
2542
  ];
1987
2543
  if (profile.revision != null) pairs.push(["Revision", profile.revision]);
1988
2544
  if (profile.createdBy) pairs.push(["Created", profile.createdBy]);
@@ -2086,8 +2642,8 @@ function renderTopicList(topics, format = "pretty") {
2086
2642
  ui.section("Custom Topics:");
2087
2643
  for (const t of topics) {
2088
2644
  ui.dim(String(t.topic_id));
2089
- const rev = t.revision != null ? chalk8.dim(` rev:${t.revision}`) : "";
2090
- const desc = t.description ? chalk8.dim(` \u2014 ${t.description.slice(0, 80)}`) : "";
2645
+ const rev = t.revision != null ? chalk9.dim(` rev:${t.revision}`) : "";
2646
+ const desc = t.description ? chalk9.dim(` \u2014 ${t.description.slice(0, 80)}`) : "";
2091
2647
  console.log(` ${t.topic_name}${rev}${desc}`);
2092
2648
  }
2093
2649
  console.log();
@@ -2145,8 +2701,8 @@ function renderApiKeyList(keys, format = "pretty") {
2145
2701
  ui.section("API Keys:");
2146
2702
  for (const k of keys) {
2147
2703
  ui.dim(k.id);
2148
- const last8 = k.last8 ? chalk8.dim(` key: \u2026${k.last8}`) : "";
2149
- const expires = k.expiresAt ? chalk8.dim(` expires: ${k.expiresAt}`) : "";
2704
+ const last8 = k.last8 ? chalk9.dim(` key: \u2026${k.last8}`) : "";
2705
+ const expires = k.expiresAt ? chalk9.dim(` expires: ${k.expiresAt}`) : "";
2150
2706
  console.log(` ${k.name}${last8}${expires}`);
2151
2707
  }
2152
2708
  console.log();
@@ -2191,7 +2747,7 @@ function renderCustomerAppList(apps, format = "pretty") {
2191
2747
  ui.section("Customer Apps:");
2192
2748
  for (const a of apps) {
2193
2749
  if (a.id) ui.dim(a.id);
2194
- const desc = a.description ? chalk8.dim(` \u2014 ${a.description.slice(0, 80)}`) : "";
2750
+ const desc = a.description ? chalk9.dim(` \u2014 ${a.description.slice(0, 80)}`) : "";
2195
2751
  console.log(` ${a.name}${desc}`);
2196
2752
  }
2197
2753
  console.log();
@@ -2315,9 +2871,9 @@ function renderDeploymentProfileList(profiles, format = "pretty") {
2315
2871
  const name = p.raw.dp_name ?? p.raw.profile_name ?? p.raw.name ?? "unknown";
2316
2872
  const status = p.raw.status;
2317
2873
  const authCode = p.raw.auth_code;
2318
- const statusColor4 = status === "active" ? chalk8.green : chalk8.dim;
2874
+ const statusColor5 = status === "active" ? chalk9.green : chalk9.dim;
2319
2875
  console.log(
2320
- ` ${name}${status ? ` ${statusColor4(status)}` : ""}${authCode ? ` ${chalk8.dim(authCode)}` : ""}`
2876
+ ` ${name}${status ? ` ${statusColor5(status)}` : ""}${authCode ? ` ${chalk9.dim(authCode)}` : ""}`
2321
2877
  );
2322
2878
  }
2323
2879
  console.log();
@@ -2350,24 +2906,260 @@ function renderScanLogList(results, pageToken, format = "pretty") {
2350
2906
  );
2351
2907
  return;
2352
2908
  }
2353
- ui.section(`Scan Logs (${results.length} results):`);
2354
- for (const r of results) {
2355
- const action = r.action ?? r.verdict;
2356
- const app = r.app_name;
2357
- const profile = r.profile_name;
2358
- const ts2 = r.received_ts ?? r.timestamp;
2359
- const scanId = r.scan_id;
2360
- const actionColor = action === "block" ? chalk8.red : chalk8.green;
2361
- if (scanId) ui.dim(scanId);
2362
- console.log(
2363
- ` ${ts2 ? chalk8.dim(ts2) : ""} ${action ? actionColor(action) : ""} ${profile ? `[${profile}]` : ""} ${app ?? ""}`
2364
- );
2909
+ ui.section(`Scan Logs (${results.length} results):`);
2910
+ for (const r of results) {
2911
+ const action = r.action ?? r.verdict;
2912
+ const app = r.app_name;
2913
+ const profile = r.profile_name;
2914
+ const ts2 = r.received_ts ?? r.timestamp;
2915
+ const scanId = r.scan_id;
2916
+ const actionColor = action === "block" ? chalk9.red : chalk9.green;
2917
+ if (scanId) ui.dim(scanId);
2918
+ console.log(
2919
+ ` ${ts2 ? chalk9.dim(ts2) : ""} ${action ? actionColor(action) : ""} ${profile ? `[${profile}]` : ""} ${app ?? ""}`
2920
+ );
2921
+ }
2922
+ if (pageToken) {
2923
+ console.log();
2924
+ ui.dim(`Page token: ${pageToken}`);
2925
+ }
2926
+ console.log();
2927
+ }
2928
+
2929
+ // src/cli/confirm.ts
2930
+ async function confirmOrAbort(message, force, options = {}) {
2931
+ if (force) return;
2932
+ const interactive = options.isTTY ?? process.stdout.isTTY === true;
2933
+ if (!interactive) {
2934
+ usageError(
2935
+ `refusing to ${options.action ?? "proceed"} without --force in non-interactive mode`
2936
+ );
2937
+ }
2938
+ const prompt = options.promptFn ?? (await import("@inquirer/prompts")).confirm;
2939
+ const confirmed = await prompt({ message, default: false });
2940
+ if (!confirmed) {
2941
+ ui.info("Aborted");
2942
+ process.exit(0);
2943
+ }
2944
+ }
2945
+
2946
+ // src/cli/examples.ts
2947
+ function examples(...lines) {
2948
+ return `
2949
+ Examples:
2950
+ ${lines.map((l) => ` $ ${l}`).join("\n")}
2951
+ `;
2952
+ }
2953
+
2954
+ // src/cli/commands/aigateway.ts
2955
+ async function createService() {
2956
+ const config = await loadConfig();
2957
+ return new SdkAiGatewayService(aiGatewayClientOptions(config));
2958
+ }
2959
+ function failWithGrantHint(err) {
2960
+ const hint = aiGatewayGrantHint(err);
2961
+ if (hint) ui.warn(`403: ${hint}`);
2962
+ fail(err);
2963
+ }
2964
+ function parsePlane(value) {
2965
+ if (value === void 0) return void 0;
2966
+ if (value !== "data" && value !== "admin") {
2967
+ usageError(`Invalid --plane '${value}'. Valid planes: data, admin`);
2968
+ }
2969
+ return value;
2970
+ }
2971
+ function parseStatus(value) {
2972
+ if (value === void 0) return void 0;
2973
+ if (value !== "active" && value !== "archived") {
2974
+ usageError(`Invalid --status '${value}'. Valid statuses: active, archived`);
2975
+ }
2976
+ return value;
2977
+ }
2978
+ function parseJsonFlag(raw, flag) {
2979
+ if (raw === void 0) return void 0;
2980
+ try {
2981
+ return JSON.parse(raw);
2982
+ } catch {
2983
+ throw new Error(`${flag} must be valid JSON`);
2984
+ }
2985
+ }
2986
+ function buildWorkspaceWriteRequest(opts) {
2987
+ const out = {};
2988
+ for (const key of ["name", "description", "icon"]) {
2989
+ if (opts[key] !== void 0) out[key] = opts[key];
2990
+ }
2991
+ const defaults = parseJsonFlag(opts.defaults, "--defaults");
2992
+ const metadata = parseJsonFlag(opts.metadata, "--metadata");
2993
+ if (defaults !== void 0 || metadata !== void 0) {
2994
+ out.defaults = {
2995
+ ...typeof defaults === "object" && defaults !== null ? defaults : {},
2996
+ ...metadata !== void 0 ? { metadata } : {}
2997
+ };
2365
2998
  }
2366
- if (pageToken) {
2367
- console.log();
2368
- ui.dim(`Page token: ${pageToken}`);
2999
+ if (opts.users !== void 0) {
3000
+ out.users = opts.users.split(",").map((u) => u.trim()).filter(Boolean);
2369
3001
  }
2370
- console.log();
3002
+ const usage = parseJsonFlag(opts.usageLimits, "--usage-limits");
3003
+ if (usage !== void 0) out.usageLimits = usage;
3004
+ const rate = parseJsonFlag(opts.rateLimits, "--rate-limits");
3005
+ if (rate !== void 0) out.rateLimits = rate;
3006
+ return out;
3007
+ }
3008
+ function scopeNameLooksUnrelated(name, scopeName) {
3009
+ const nameToken = name.toLowerCase().replace(/[^a-z0-9]/g, "");
3010
+ if (nameToken.length < 4) return false;
3011
+ return !scopeName.toLowerCase().replace(/[^a-z0-9]/g, "").includes(nameToken);
3012
+ }
3013
+ function registerAiGatewayCommand(program) {
3014
+ const aigateway = program.command("aigateway").description("AI Gateway operations");
3015
+ const workspace = aigateway.command("workspace").description("Manage AI Gateway workspaces");
3016
+ 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, csv, json, yaml", "pretty").addHelpText(
3017
+ "after",
3018
+ examples(
3019
+ "airs aigateway workspace list",
3020
+ "airs aigateway workspace list --plane admin",
3021
+ "airs aigateway workspace list --plane admin --status archived",
3022
+ "airs aigateway workspace list --all --output json"
3023
+ )
3024
+ ).action(async (opts) => {
3025
+ try {
3026
+ const fmt = opts.output;
3027
+ if (fmt === "pretty") renderAiGatewayHeader();
3028
+ const plane = parsePlane(opts.plane);
3029
+ const status = parseStatus(opts.status);
3030
+ if (opts.all && (plane !== void 0 || status !== void 0)) {
3031
+ usageError("--all already merges admin-plane active + archived; drop --plane/--status");
3032
+ }
3033
+ const service = await createService();
3034
+ const workspaces = opts.all ? await service.listAllWorkspaces() : await service.listWorkspaces(
3035
+ plane !== void 0 || status !== void 0 ? { plane, status } : void 0
3036
+ );
3037
+ renderWorkspaceList(workspaces, fmt);
3038
+ if (fmt === "pretty" && !opts.all && plane !== "admin") {
3039
+ ui.status(
3040
+ "Data-plane list shows only active workspaces you are scoped to \u2014 use --plane admin or --all for the whole tenant."
3041
+ );
3042
+ }
3043
+ } catch (err) {
3044
+ failWithGrantHint(err);
3045
+ }
3046
+ });
3047
+ workspace.command("get <ref>").description("Get one workspace by UUID or slug (includes settings blocks)").option("--plane <plane>", "Plane to read from: data (scoped) or admin (whole tenant)").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
3048
+ "after",
3049
+ examples(
3050
+ "airs aigateway workspace get ws-main-a-349e0e",
3051
+ "airs aigateway workspace get 16f7e90d-382a-4e78-b577-1b01eb5f8297 --plane admin --output json"
3052
+ )
3053
+ ).action(async (ref, opts) => {
3054
+ try {
3055
+ const fmt = opts.output;
3056
+ if (fmt === "pretty") renderAiGatewayHeader();
3057
+ const plane = parsePlane(opts.plane);
3058
+ const service = await createService();
3059
+ const workspace2 = await service.getWorkspace(
3060
+ ref,
3061
+ plane !== void 0 ? { plane } : void 0
3062
+ );
3063
+ renderWorkspaceDetail(workspace2, fmt);
3064
+ } catch (err) {
3065
+ failWithGrantHint(err);
3066
+ }
3067
+ });
3068
+ workspace.command("create").description("Create a workspace (admin plane)").requiredOption("--name <name>", "Display name").requiredOption(
3069
+ "--scope-name <scope>",
3070
+ "SCM role scope granting data-plane access (e.g. ws_production_bx7qw0) \u2014 not derived from --name"
3071
+ ).option("--description <text>", "Workspace description").option("--icon <icon>", "Workspace icon").option("--metadata <json>", "Sugar for defaults.metadata (flat string map)").option("--defaults <json>", "Workspace defaults object").option("--users <ids>", "Comma-separated user ids to seed the workspace with").option("--usage-limits <json>", "Usage-limit policies \u2014 a JSON ARRAY of policy objects").option("--rate-limits <json>", "Rate-limit policies \u2014 a JSON ARRAY of policy objects").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
3072
+ "after",
3073
+ examples(
3074
+ "airs aigateway workspace create --name Production --scope-name ws_production_bx7qw0",
3075
+ `airs aigateway workspace create --name Production --scope-name ws_production_bx7qw0 --metadata '{"env":"production"}' --rate-limits '[{"type":"requests","unit":"rpm","value":100}]'`
3076
+ )
3077
+ ).action(async (opts) => {
3078
+ try {
3079
+ const fmt = opts.output;
3080
+ if (fmt === "pretty") renderAiGatewayHeader();
3081
+ if (scopeNameLooksUnrelated(opts.name, opts.scopeName)) {
3082
+ ui.warn(
3083
+ `--scope-name '${opts.scopeName}' shares no token with --name '${opts.name}'. A workspace created with a scope nobody holds will not appear in data-plane lists.`
3084
+ );
3085
+ }
3086
+ const service = await createService();
3087
+ const workspace2 = await service.createWorkspace({
3088
+ ...buildWorkspaceWriteRequest(opts),
3089
+ name: opts.name,
3090
+ scopeName: opts.scopeName
3091
+ });
3092
+ ui.success(`Workspace created: ${workspace2.id}`);
3093
+ renderWorkspaceDetail(workspace2, fmt);
3094
+ } catch (err) {
3095
+ failWithGrantHint(err);
3096
+ }
3097
+ });
3098
+ workspace.command("update <ref>").description("Update a workspace (admin plane, partial patch)").option("--name <name>", "New display name").option("--description <text>", "New description").option("--icon <icon>", "New icon").option("--metadata <json>", "Sugar for defaults.metadata (flat string map)").option("--defaults <json>", "Workspace defaults object").option("--usage-limits <json>", "Usage-limit policies \u2014 a JSON ARRAY of policy objects").option("--rate-limits <json>", "Rate-limit policies \u2014 a JSON ARRAY of policy objects").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
3099
+ "after",
3100
+ examples(
3101
+ `airs aigateway workspace update ws-produc-985697 --description 'Production workloads, us-east'`
3102
+ )
3103
+ ).action(async (ref, opts) => {
3104
+ try {
3105
+ const fmt = opts.output;
3106
+ if (fmt === "pretty") renderAiGatewayHeader();
3107
+ const request = buildWorkspaceWriteRequest(opts);
3108
+ if (Object.keys(request).length === 0) {
3109
+ usageError(
3110
+ "Specify at least one of --name --description --icon --metadata --defaults --usage-limits --rate-limits"
3111
+ );
3112
+ }
3113
+ const service = await createService();
3114
+ const workspace2 = await service.updateWorkspace(ref, request);
3115
+ ui.success(`Workspace updated: ${workspace2.id}`);
3116
+ renderWorkspaceDetail(workspace2, fmt);
3117
+ } catch (err) {
3118
+ failWithGrantHint(err);
3119
+ }
3120
+ });
3121
+ workspace.command("delete <ref>").description("Archive a workspace (soft delete \u2014 there is no hard delete)").option("--force", "Skip confirmation prompt").addHelpText("after", examples("airs aigateway workspace delete ws-produc-985697 --force")).action(async (ref, opts) => {
3122
+ try {
3123
+ renderAiGatewayHeader();
3124
+ await confirmOrAbort(
3125
+ `Archive workspace ${ref}? (soft delete \u2014 the row remains under --status archived)`,
3126
+ Boolean(opts.force),
3127
+ { action: `archive workspace ${ref}` }
3128
+ );
3129
+ const service = await createService();
3130
+ await service.deleteWorkspace(ref);
3131
+ ui.success(`Workspace archived: ${ref}`);
3132
+ ui.status(
3133
+ "This is a soft delete \u2014 the workspace remains visible via `workspace list --plane admin --status archived`. A `get` on it now answers 404; that is expected."
3134
+ );
3135
+ } catch (err) {
3136
+ failWithGrantHint(err);
3137
+ }
3138
+ });
3139
+ const telemetry = aigateway.command("telemetry").description("AI Gateway runtime telemetry (data plane)");
3140
+ const cost = telemetry.command("cost").description(
3141
+ "Total and per-day spend for a workspace (API reports cents; pretty output shows dollars)"
3142
+ ).requiredOption("--workspace <slug>", "Workspace slug (not UUID), e.g. ws-main-a-349e0e").option("--days <n>", "Rolling window in days, counted back from now", "7").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
3143
+ "after",
3144
+ examples(
3145
+ "airs aigateway telemetry cost --workspace ws-main-a-349e0e",
3146
+ "airs aigateway telemetry cost --workspace ws-main-a-349e0e --days 30 --output json"
3147
+ )
3148
+ ).action(async (opts) => {
3149
+ try {
3150
+ const fmt = await resolveOutput(cost, opts);
3151
+ if (fmt === "pretty") renderAiGatewayHeader();
3152
+ const days = Number.parseInt(opts.days, 10);
3153
+ if (!Number.isFinite(days) || days <= 0) {
3154
+ usageError(`Invalid --days '${opts.days}'. Expected a positive integer`);
3155
+ }
3156
+ const service = await createService();
3157
+ const report = await service.getTelemetryCost({ workspaceSlug: opts.workspace, days });
3158
+ renderCostReport(report, fmt);
3159
+ } catch (err) {
3160
+ failWithGrantHint(err);
3161
+ }
3162
+ });
2371
3163
  }
2372
3164
 
2373
3165
  // src/cli/commands/completion.ts
@@ -2559,13 +3351,6 @@ function assertKnownKey(key) {
2559
3351
  usageError(`Unknown config key '${key}'. Valid keys: ${CONFIG_KEYS.join(", ")}`);
2560
3352
  }
2561
3353
  }
2562
- var LIST_FORMATS = ["pretty", "json", "yaml"];
2563
- function parseListFormat(value) {
2564
- if (!LIST_FORMATS.includes(value)) {
2565
- usageError(`Invalid --output '${value}'. Valid formats: ${LIST_FORMATS.join(", ")}`);
2566
- }
2567
- return value;
2568
- }
2569
3354
  var COLUMNS = [
2570
3355
  { key: "key", label: "Key" },
2571
3356
  { key: "value", label: "Value" },
@@ -2580,9 +3365,9 @@ function registerConfigCommand(program) {
2580
3365
  "airs config get mgmtTsgId"
2581
3366
  )
2582
3367
  );
2583
- config.command("list").description("Show effective configuration with per-key source (env/file/default)").option("--output <format>", "Output format: pretty, json, or yaml", "pretty").option("--reveal", "Show secret values in full").action(async (opts) => {
3368
+ const configList = config.command("list").description("Show effective configuration with per-key source (env/file/default)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").option("--reveal", "Show secret values in full").action(async (opts) => {
2584
3369
  try {
2585
- const fmt = parseListFormat(opts.output);
3370
+ const fmt = await resolveOutput(configList, opts);
2586
3371
  const filePath = resolveConfigFilePath();
2587
3372
  const rows = buildConfigRows(await inspectConfig(), Boolean(opts.reveal));
2588
3373
  if (fmt === "pretty") {
@@ -2597,12 +3382,18 @@ function registerConfigCommand(program) {
2597
3382
  fail(err);
2598
3383
  }
2599
3384
  });
2600
- config.command("get <key>").description("Print a single effective config value").option("--reveal", "Show the real value of a secret key").action(async (key, opts) => {
3385
+ const configGet = config.command("get <key>").description("Print a single effective config value").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").option("--reveal", "Show the real value of a secret key").action(async (key, opts) => {
2601
3386
  try {
2602
3387
  assertKnownKey(key);
2603
3388
  const inspected = await inspectConfig();
2604
3389
  const entry = inspected[key];
2605
3390
  const raw = entry.value == null ? "" : String(entry.value);
3391
+ const fmt = await resolveOutput(configGet, opts);
3392
+ const value = raw !== "" && isSecretKey(key) && !opts.reveal ? maskSecret(raw) : raw;
3393
+ if (fmt !== "pretty") {
3394
+ console.log(formatOutput([{ key, value, source: entry.source }], COLUMNS, fmt));
3395
+ return;
3396
+ }
2606
3397
  if (raw !== "" && isSecretKey(key)) {
2607
3398
  if (opts.reveal) {
2608
3399
  ui.status(`Warning: printing secret value for '${key}'`);
@@ -2653,39 +3444,6 @@ function registerConfigCommand(program) {
2653
3444
  import { randomUUID } from "crypto";
2654
3445
  import { readFile as readFile2 } from "fs/promises";
2655
3446
  import { init, Scanner } from "@cdot65/prisma-airs-sdk";
2656
-
2657
- // src/config/client-options.ts
2658
- function runtimeInitOptions(config) {
2659
- return {
2660
- apiKey: config.airsApiKey,
2661
- apiToken: config.airsApiToken,
2662
- apiEndpoint: config.airsApiEndpoint,
2663
- numRetries: config.airsNumRetries
2664
- };
2665
- }
2666
- function redTeamClientOptions(config) {
2667
- return {
2668
- clientId: config.mgmtClientId,
2669
- clientSecret: config.mgmtClientSecret,
2670
- tsgId: config.mgmtTsgId,
2671
- dataEndpoint: config.redTeamDataEndpoint,
2672
- mgmtEndpoint: config.redTeamMgmtEndpoint,
2673
- tokenEndpoint: config.redTeamTokenEndpoint ?? config.mgmtTokenEndpoint,
2674
- networkBrokerEndpoint: config.redTeamNetworkBrokerEndpoint
2675
- };
2676
- }
2677
- function modelSecurityClientOptions(config) {
2678
- return {
2679
- clientId: config.mgmtClientId,
2680
- clientSecret: config.mgmtClientSecret,
2681
- tsgId: config.mgmtTsgId,
2682
- dataEndpoint: config.modelSecDataEndpoint,
2683
- mgmtEndpoint: config.modelSecMgmtEndpoint,
2684
- tokenEndpoint: config.modelSecTokenEndpoint ?? config.mgmtTokenEndpoint
2685
- };
2686
- }
2687
-
2688
- // src/cli/commands/doctor.ts
2689
3447
  var DOCTOR_TIMEOUT_MS = 5e3;
2690
3448
  var MIN_NODE_MAJOR = 20;
2691
3449
  function checkNodeVersion(version = process.version) {
@@ -2883,6 +3641,50 @@ async function checkManagementAuth(probe, hasCreds, timeoutMs = DOCTOR_TIMEOUT_M
2883
3641
  };
2884
3642
  }
2885
3643
  }
3644
+ async function checkAiGatewayApi(probe, hasCreds, timeoutMs = DOCTOR_TIMEOUT_MS) {
3645
+ const name = "AI Gateway API";
3646
+ if (!hasCreds) {
3647
+ return {
3648
+ name,
3649
+ status: "warn",
3650
+ detail: "skipped \u2014 management credentials not configured",
3651
+ hint: "Set PANW_MGMT_CLIENT_ID, PANW_MGMT_CLIENT_SECRET, PANW_MGMT_TSG_ID"
3652
+ };
3653
+ }
3654
+ try {
3655
+ const result = await withTimeout(probe(), timeoutMs);
3656
+ if (result === TIMED_OUT) {
3657
+ return {
3658
+ name,
3659
+ status: "fail",
3660
+ detail: `timed out after ${timeoutMs}ms \u2014 network unreachable or endpoint not responding`,
3661
+ hint: "Check network connectivity and PANW_AI_GW_DATA_ENDPOINT"
3662
+ };
3663
+ }
3664
+ return {
3665
+ name,
3666
+ status: "pass",
3667
+ detail: `endpoint reachable (${result} workspace${result === 1 ? "" : "s"} in scope)`
3668
+ };
3669
+ } catch (err) {
3670
+ const status = httpStatus(err);
3671
+ const message = errMessage(err);
3672
+ if (status === 403) {
3673
+ return {
3674
+ name,
3675
+ status: "warn",
3676
+ detail: `endpoint reachable, but access denied (HTTP 403): ${message}`,
3677
+ hint: aiGatewayGrantHint(err)
3678
+ };
3679
+ }
3680
+ return {
3681
+ name,
3682
+ status: "fail",
3683
+ detail: status !== void 0 ? `AI Gateway API error (HTTP ${status}): ${message}` : `network unreachable: ${message}`,
3684
+ hint: "Verify credentials and PANW_AI_GW_DATA_ENDPOINT"
3685
+ };
3686
+ }
3687
+ }
2886
3688
  async function defaultScannerProbe() {
2887
3689
  const config = await loadConfig();
2888
3690
  init(runtimeInitOptions(config));
@@ -2900,6 +3702,12 @@ async function defaultMgmtProbe() {
2900
3702
  const topics = await service.listTopics();
2901
3703
  return topics.length;
2902
3704
  }
3705
+ async function defaultAiGwProbe() {
3706
+ const config = await loadConfig();
3707
+ const service = new SdkAiGatewayService(aiGatewayClientOptions(config));
3708
+ const workspaces = await service.listWorkspaces();
3709
+ return workspaces.length;
3710
+ }
2903
3711
  async function runDoctor(deps = {}) {
2904
3712
  const configFilePath = deps.configFilePath ?? resolveConfigFilePath();
2905
3713
  const inspect = deps.inspect ?? (() => inspectConfig(configFilePath));
@@ -2923,7 +3731,12 @@ async function runDoctor(deps = {}) {
2923
3731
  mgmtCreds.status === "pass",
2924
3732
  timeoutMs
2925
3733
  );
2926
- return [node, configFile, scannerCreds, mgmtCreds, scannerApi, mgmtAuth];
3734
+ const aiGwApi = await checkAiGatewayApi(
3735
+ deps.aiGwProbe ?? defaultAiGwProbe,
3736
+ mgmtCreds.status === "pass",
3737
+ timeoutMs
3738
+ );
3739
+ return [node, configFile, scannerCreds, mgmtCreds, scannerApi, mgmtAuth, aiGwApi];
2927
3740
  }
2928
3741
  function hasFailure(checks) {
2929
3742
  return checks.some((c) => c.status === "fail");
@@ -2933,20 +3746,6 @@ var STATUS_KIND = {
2933
3746
  warn: "warn",
2934
3747
  fail: "error"
2935
3748
  };
2936
- var DOCTOR_FORMATS = ["pretty", "json", "yaml"];
2937
- function parseDoctorFormat(value) {
2938
- if (!DOCTOR_FORMATS.includes(value)) {
2939
- usageError(`Invalid --output '${value}'. Valid formats: ${DOCTOR_FORMATS.join(", ")}`);
2940
- }
2941
- return value;
2942
- }
2943
- function toYaml(checks) {
2944
- return checks.map((c) => {
2945
- const lines = [`name: ${c.name}`, `status: ${c.status}`, `detail: ${c.detail}`];
2946
- if (c.hint) lines.push(`hint: ${c.hint}`);
2947
- return lines.join("\n");
2948
- }).join("\n---\n");
2949
- }
2950
3749
  function renderPretty(checks) {
2951
3750
  ui.header("Doctor", "Prisma AIRS CLI preflight checks");
2952
3751
  for (const check of checks) {
@@ -2966,18 +3765,27 @@ function renderPretty(checks) {
2966
3765
  console.log("");
2967
3766
  }
2968
3767
  function registerDoctorCommand(program) {
2969
- program.command("doctor").description("Check credentials, config, and API connectivity (preflight)").option("--output <format>", "Output format: pretty, json, or yaml", "pretty").addHelpText(
3768
+ const doctor = program.command("doctor").description("Check credentials, config, and API connectivity (preflight)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
2970
3769
  "after",
2971
3770
  examples("airs doctor", `airs doctor --output json | jq '.[] | select(.status != "pass")'`)
2972
3771
  ).action(async (opts) => {
2973
- const fmt = parseDoctorFormat(opts.output);
3772
+ const fmt = await resolveOutput(doctor, opts);
2974
3773
  const checks = await runDoctor();
2975
- if (fmt === "json") {
2976
- console.log(JSON.stringify(checks, null, 2));
2977
- } else if (fmt === "yaml") {
2978
- console.log(toYaml(checks));
2979
- } else {
3774
+ if (fmt === "pretty") {
2980
3775
  renderPretty(checks);
3776
+ } else {
3777
+ console.log(
3778
+ formatOutput(
3779
+ checks.map((check) => ({ ...check })),
3780
+ [
3781
+ { key: "name", label: "Name" },
3782
+ { key: "status", label: "Status" },
3783
+ { key: "detail", label: "Detail" },
3784
+ { key: "hint", label: "Hint" }
3785
+ ],
3786
+ fmt
3787
+ )
3788
+ );
2981
3789
  }
2982
3790
  process.exit(hasFailure(checks) ? 1 : 0);
2983
3791
  });
@@ -3005,7 +3813,7 @@ function run(bin, args, label) {
3005
3813
  });
3006
3814
  });
3007
3815
  }
3008
- async function createService() {
3816
+ async function createService2() {
3009
3817
  const config = await loadConfig();
3010
3818
  return new SdkModelSecurityService(modelSecurityClientOptions(config));
3011
3819
  }
@@ -3016,16 +3824,18 @@ function registerModelSecurityCommand(program) {
3016
3824
  try {
3017
3825
  const fmt = opts.output;
3018
3826
  if (fmt === "pretty") renderModelSecurityHeader();
3019
- const service = await createService();
3020
- const result = await service.listGroups({
3827
+ const service = await createService2();
3828
+ const listOptions = {
3021
3829
  sourceTypes: opts.sourceTypes ? opts.sourceTypes.split(",").map((s) => s.trim()) : void 0,
3022
3830
  searchQuery: opts.search,
3023
3831
  sortField: opts.sortField,
3024
3832
  sortDir: opts.sortDir,
3025
3833
  enabledRules: opts.enabledRules ? opts.enabledRules.split(",").map((s) => s.trim()) : void 0,
3026
- limit: Number.parseInt(opts.limit, 10)
3027
- });
3028
- renderGroupList(result.groups, fmt);
3834
+ limit: Number.parseInt(opts.limit, 10),
3835
+ skip: Number(opts.offset ?? 0)
3836
+ };
3837
+ const rows = opts.all ? await service.listAllGroups({ ...listOptions, max: Number(opts.max) }) : (await service.listGroups(listOptions)).groups;
3838
+ renderGroupList(rows, fmt);
3029
3839
  } catch (err) {
3030
3840
  fail(err);
3031
3841
  }
@@ -3034,7 +3844,7 @@ function registerModelSecurityCommand(program) {
3034
3844
  try {
3035
3845
  const fmt = opts.output;
3036
3846
  if (fmt === "pretty") renderModelSecurityHeader();
3037
- const service = await createService();
3847
+ const service = await createService2();
3038
3848
  const group = await service.getGroup(uuid);
3039
3849
  renderGroupDetail(group, fmt);
3040
3850
  } catch (err) {
@@ -3044,7 +3854,7 @@ function registerModelSecurityCommand(program) {
3044
3854
  groups.command("create").description("Create a security group").requiredOption("--config <path>", "JSON file with group configuration").action(async (opts) => {
3045
3855
  try {
3046
3856
  renderModelSecurityHeader();
3047
- const service = await createService();
3857
+ const service = await createService2();
3048
3858
  const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
3049
3859
  const group = await service.createGroup({
3050
3860
  name: config.name,
@@ -3061,7 +3871,7 @@ function registerModelSecurityCommand(program) {
3061
3871
  groups.command("update <uuid>").description("Update a security group").option("--name <name>", "New name").option("--description <desc>", "New description").action(async (uuid, opts) => {
3062
3872
  try {
3063
3873
  renderModelSecurityHeader();
3064
- const service = await createService();
3874
+ const service = await createService2();
3065
3875
  const request = {};
3066
3876
  if (opts.name) request.name = opts.name;
3067
3877
  if (opts.description) request.description = opts.description;
@@ -3075,7 +3885,7 @@ function registerModelSecurityCommand(program) {
3075
3885
  groups.command("delete <uuid>").description("Delete a security group").action(async (uuid) => {
3076
3886
  try {
3077
3887
  renderModelSecurityHeader();
3078
- const service = await createService();
3888
+ const service = await createService2();
3079
3889
  const { confirmed, state } = await service.deleteGroupAndVerify(uuid);
3080
3890
  if (confirmed) {
3081
3891
  ui.success(`Group ${uuid} deleted.`);
@@ -3105,7 +3915,7 @@ function registerModelSecurityCommand(program) {
3105
3915
  if (!useUv && !hasBin("python3")) {
3106
3916
  fail(new Error("Neither uv nor python3 found on PATH. Install one first."));
3107
3917
  }
3108
- const service = await createService();
3918
+ const service = await createService2();
3109
3919
  const auth = await service.getPyPIAuth();
3110
3920
  const pkg = `model-security-client[${extras}]`;
3111
3921
  const steps = useUv ? [
@@ -3149,7 +3959,7 @@ function registerModelSecurityCommand(program) {
3149
3959
  labels.command("add <scanUuid>").description("Add labels to a scan").requiredOption("--labels <json>", "JSON array of {key, value} labels").action(async (scanUuid, opts) => {
3150
3960
  try {
3151
3961
  renderModelSecurityHeader();
3152
- const service = await createService();
3962
+ const service = await createService2();
3153
3963
  const parsed = JSON.parse(opts.labels);
3154
3964
  await service.addLabels(scanUuid, parsed);
3155
3965
  ui.success("Labels added.");
@@ -3160,7 +3970,7 @@ function registerModelSecurityCommand(program) {
3160
3970
  labels.command("set <scanUuid>").description("Replace all labels on a scan").requiredOption("--labels <json>", "JSON array of {key, value} labels").action(async (scanUuid, opts) => {
3161
3971
  try {
3162
3972
  renderModelSecurityHeader();
3163
- const service = await createService();
3973
+ const service = await createService2();
3164
3974
  const parsed = JSON.parse(opts.labels);
3165
3975
  await service.setLabels(scanUuid, parsed);
3166
3976
  ui.success("Labels set.");
@@ -3171,7 +3981,7 @@ function registerModelSecurityCommand(program) {
3171
3981
  labels.command("delete <scanUuid>").description("Delete labels from a scan by key").requiredOption("--keys <keys>", "Comma-separated label keys to delete").action(async (scanUuid, opts) => {
3172
3982
  try {
3173
3983
  renderModelSecurityHeader();
3174
- const service = await createService();
3984
+ const service = await createService2();
3175
3985
  const keys = opts.keys.split(",").map((k) => k.trim());
3176
3986
  await service.deleteLabels(scanUuid, keys);
3177
3987
  ui.success("Labels deleted.");
@@ -3182,7 +3992,7 @@ function registerModelSecurityCommand(program) {
3182
3992
  labels.command("keys").description("List available label keys").option("--limit <n>", "Max results", "20").action(async (opts) => {
3183
3993
  try {
3184
3994
  renderModelSecurityHeader();
3185
- const service = await createService();
3995
+ const service = await createService2();
3186
3996
  const result = await service.getLabelKeys({
3187
3997
  limit: Number.parseInt(opts.limit, 10)
3188
3998
  });
@@ -3194,7 +4004,7 @@ function registerModelSecurityCommand(program) {
3194
4004
  labels.command("values <key>").description("List values for a label key").option("--limit <n>", "Max results", "20").action(async (key, opts) => {
3195
4005
  try {
3196
4006
  renderModelSecurityHeader();
3197
- const service = await createService();
4007
+ const service = await createService2();
3198
4008
  const result = await service.getLabelValues(key, {
3199
4009
  limit: Number.parseInt(opts.limit, 10)
3200
4010
  });
@@ -3206,7 +4016,7 @@ function registerModelSecurityCommand(program) {
3206
4016
  ms.command("pypi-auth").description("Get PyPI authentication URL for Google Artifact Registry").action(async () => {
3207
4017
  try {
3208
4018
  renderModelSecurityHeader();
3209
- const service = await createService();
4019
+ const service = await createService2();
3210
4020
  const auth = await service.getPyPIAuth();
3211
4021
  ui.section("PyPI Authentication");
3212
4022
  ui.keyValue([
@@ -3220,24 +4030,26 @@ function registerModelSecurityCommand(program) {
3220
4030
  const ruleInstances = ms.command("rule-instances").description("Manage rule instances in groups");
3221
4031
  ruleInstances.command("list <groupUuid>").description("List rule instances in a security group").option("--security-rule-uuid <uuid>", "Filter by security rule UUID").option("--state <state>", "Filter by state (DISABLED, ALLOWING, BLOCKING)").option("--limit <n>", "Max results", "20").action(async (groupUuid, opts) => {
3222
4032
  try {
3223
- renderModelSecurityHeader();
3224
- const service = await createService();
4033
+ const fmt = opts.output;
4034
+ if (fmt === "pretty") renderModelSecurityHeader();
4035
+ const service = await createService2();
3225
4036
  const result = await service.listRuleInstances(groupUuid, {
3226
4037
  securityRuleUuid: opts.securityRuleUuid,
3227
4038
  state: opts.state,
3228
4039
  limit: Number.parseInt(opts.limit, 10)
3229
4040
  });
3230
- renderRuleInstanceList(result.ruleInstances);
4041
+ renderRuleInstanceList(result.ruleInstances, fmt);
3231
4042
  } catch (err) {
3232
4043
  fail(err);
3233
4044
  }
3234
4045
  });
3235
- ruleInstances.command("get <groupUuid> <instanceUuid>").description("Get rule instance details").action(async (groupUuid, instanceUuid) => {
4046
+ ruleInstances.command("get <groupUuid> <instanceUuid>").description("Get rule instance details").action(async (groupUuid, instanceUuid, opts) => {
3236
4047
  try {
3237
- renderModelSecurityHeader();
3238
- const service = await createService();
4048
+ const fmt = opts.output;
4049
+ if (fmt === "pretty") renderModelSecurityHeader();
4050
+ const service = await createService2();
3239
4051
  const instance = await service.getRuleInstance(groupUuid, instanceUuid);
3240
- renderRuleInstanceDetail(instance);
4052
+ renderRuleInstanceDetail(instance, fmt);
3241
4053
  } catch (err) {
3242
4054
  fail(err);
3243
4055
  }
@@ -3245,7 +4057,7 @@ function registerModelSecurityCommand(program) {
3245
4057
  ruleInstances.command("update <groupUuid> <instanceUuid>").description("Update a rule instance").requiredOption("--config <path>", "JSON file with rule instance updates").action(async (groupUuid, instanceUuid, opts) => {
3246
4058
  try {
3247
4059
  renderModelSecurityHeader();
3248
- const service = await createService();
4060
+ const service = await createService2();
3249
4061
  const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
3250
4062
  const instance = await service.updateRuleInstance(groupUuid, instanceUuid, {
3251
4063
  state: config.state,
@@ -3262,23 +4074,26 @@ function registerModelSecurityCommand(program) {
3262
4074
  try {
3263
4075
  const fmt = opts.output;
3264
4076
  if (fmt === "pretty") renderModelSecurityHeader();
3265
- const service = await createService();
3266
- const result = await service.listRules({
4077
+ const service = await createService2();
4078
+ const listOptions = {
3267
4079
  sourceType: opts.sourceType,
3268
4080
  searchQuery: opts.search,
3269
- limit: Number.parseInt(opts.limit, 10)
3270
- });
3271
- renderRuleList(result.rules, fmt);
4081
+ limit: Number.parseInt(opts.limit, 10),
4082
+ skip: Number(opts.offset ?? 0)
4083
+ };
4084
+ const rows = opts.all ? await service.listAllRules({ ...listOptions, max: Number(opts.max) }) : (await service.listRules(listOptions)).rules;
4085
+ renderRuleList(rows, fmt);
3272
4086
  } catch (err) {
3273
4087
  fail(err);
3274
4088
  }
3275
4089
  });
3276
- rules.command("get <uuid>").description("Get security rule details").action(async (uuid) => {
4090
+ rules.command("get <uuid>").description("Get security rule details").action(async (uuid, opts) => {
3277
4091
  try {
3278
- renderModelSecurityHeader();
3279
- const service = await createService();
4092
+ const fmt = opts.output;
4093
+ if (fmt === "pretty") renderModelSecurityHeader();
4094
+ const service = await createService2();
3280
4095
  const rule = await service.getRule(uuid);
3281
- renderRuleDetail(rule);
4096
+ renderRuleDetail(rule, fmt);
3282
4097
  } catch (err) {
3283
4098
  fail(err);
3284
4099
  }
@@ -3295,25 +4110,28 @@ function registerModelSecurityCommand(program) {
3295
4110
  try {
3296
4111
  const fmt = opts.output;
3297
4112
  if (fmt === "pretty") renderModelSecurityHeader();
3298
- const service = await createService();
3299
- const result = await service.listScans({
4113
+ const service = await createService2();
4114
+ const listOptions = {
3300
4115
  evalOutcome: opts.evalOutcome,
3301
4116
  sourceType: opts.sourceType,
3302
4117
  scanOrigin: opts.scanOrigin,
3303
4118
  search: opts.search,
3304
- limit: Number.parseInt(opts.limit, 10)
3305
- });
3306
- renderMsScanList(result.scans, fmt);
4119
+ limit: Number.parseInt(opts.limit, 10),
4120
+ skip: Number(opts.offset ?? 0)
4121
+ };
4122
+ const rows = opts.all ? await service.listAllScans({ ...listOptions, max: Number(opts.max) }) : (await service.listScans(listOptions)).scans;
4123
+ renderMsScanList(rows, fmt);
3307
4124
  } catch (err) {
3308
4125
  fail(err);
3309
4126
  }
3310
4127
  });
3311
- scans.command("get <uuid>").description("Get scan details").action(async (uuid) => {
4128
+ scans.command("get <uuid>").description("Get scan details").action(async (uuid, opts) => {
3312
4129
  try {
3313
- renderModelSecurityHeader();
3314
- const service = await createService();
4130
+ const fmt = opts.output;
4131
+ if (fmt === "pretty") renderModelSecurityHeader();
4132
+ const service = await createService2();
3315
4133
  const scan = await service.getScan(uuid);
3316
- renderMsScanDetail(scan);
4134
+ renderMsScanDetail(scan, fmt);
3317
4135
  } catch (err) {
3318
4136
  fail(err);
3319
4137
  }
@@ -3321,7 +4139,7 @@ function registerModelSecurityCommand(program) {
3321
4139
  scans.command("create").description("Create a model security scan").requiredOption("--config <path>", "JSON file with scan configuration").action(async (opts) => {
3322
4140
  try {
3323
4141
  renderModelSecurityHeader();
3324
- const service = await createService();
4142
+ const service = await createService2();
3325
4143
  const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
3326
4144
  const scan = await service.createScan(config);
3327
4145
  ui.success(`Scan created: ${scan.uuid}`);
@@ -3333,7 +4151,7 @@ function registerModelSecurityCommand(program) {
3333
4151
  scans.command("evaluations <scanUuid>").description("List rule evaluations for a scan").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
3334
4152
  try {
3335
4153
  renderModelSecurityHeader();
3336
- const service = await createService();
4154
+ const service = await createService2();
3337
4155
  const result = await service.getEvaluations(scanUuid, {
3338
4156
  limit: Number.parseInt(opts.limit, 10)
3339
4157
  });
@@ -3345,7 +4163,7 @@ function registerModelSecurityCommand(program) {
3345
4163
  scans.command("evaluation <uuid>").description("Get evaluation details").action(async (uuid) => {
3346
4164
  try {
3347
4165
  renderModelSecurityHeader();
3348
- const service = await createService();
4166
+ const service = await createService2();
3349
4167
  const evaluation = await service.getEvaluation(uuid);
3350
4168
  renderEvaluationDetail(evaluation);
3351
4169
  } catch (err) {
@@ -3355,7 +4173,7 @@ function registerModelSecurityCommand(program) {
3355
4173
  scans.command("violations <scanUuid>").description("List violations for a scan").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
3356
4174
  try {
3357
4175
  renderModelSecurityHeader();
3358
- const service = await createService();
4176
+ const service = await createService2();
3359
4177
  const result = await service.getViolations(scanUuid, {
3360
4178
  limit: Number.parseInt(opts.limit, 10)
3361
4179
  });
@@ -3367,7 +4185,7 @@ function registerModelSecurityCommand(program) {
3367
4185
  scans.command("violation <uuid>").description("Get violation details").action(async (uuid) => {
3368
4186
  try {
3369
4187
  renderModelSecurityHeader();
3370
- const service = await createService();
4188
+ const service = await createService2();
3371
4189
  const violation = await service.getViolation(uuid);
3372
4190
  renderViolationDetail(violation);
3373
4191
  } catch (err) {
@@ -3377,7 +4195,7 @@ function registerModelSecurityCommand(program) {
3377
4195
  scans.command("files <scanUuid>").description("List scanned files").option("--type <type>", "Filter by file type").option("--result <result>", "Filter by result").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
3378
4196
  try {
3379
4197
  renderModelSecurityHeader();
3380
- const service = await createService();
4198
+ const service = await createService2();
3381
4199
  const result = await service.getFiles(scanUuid, {
3382
4200
  type: opts.type,
3383
4201
  result: opts.result,
@@ -3393,16 +4211,17 @@ function registerModelSecurityCommand(program) {
3393
4211
  try {
3394
4212
  const fmt = opts.output;
3395
4213
  if (fmt === "pretty") renderModelSecurityHeader();
3396
- const service = await createService();
3397
- const result = await service.listModels({
4214
+ const service = await createService2();
4215
+ const listOptions = {
3398
4216
  search: opts.search,
3399
4217
  searchQuery: opts.searchQuery,
3400
4218
  sortField: opts.sortField,
3401
4219
  sortOrder: opts.sortOrder,
3402
4220
  limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
3403
4221
  skip: opts.offset ? Number.parseInt(opts.offset, 10) : void 0
3404
- });
3405
- renderModelList(result.models, fmt);
4222
+ };
4223
+ const rows = opts.all ? await service.listAllModels({ ...listOptions, max: Number(opts.max) }) : (await service.listModels(listOptions)).models;
4224
+ renderModelList(rows, fmt);
3406
4225
  } catch (err) {
3407
4226
  fail(err);
3408
4227
  }
@@ -3411,7 +4230,7 @@ function registerModelSecurityCommand(program) {
3411
4230
  try {
3412
4231
  const fmt = opts.output;
3413
4232
  if (fmt === "pretty") renderModelSecurityHeader();
3414
- const service = await createService();
4233
+ const service = await createService2();
3415
4234
  const model = await service.getModel(uuid);
3416
4235
  renderModelDetail(model, fmt);
3417
4236
  } catch (err) {
@@ -3422,7 +4241,7 @@ function registerModelSecurityCommand(program) {
3422
4241
  try {
3423
4242
  const fmt = opts.output;
3424
4243
  if (fmt === "pretty") renderModelSecurityHeader();
3425
- const service = await createService();
4244
+ const service = await createService2();
3426
4245
  const result = await service.listModelVersions(modelUuid, {
3427
4246
  sortOrder: opts.sortOrder,
3428
4247
  limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
@@ -3437,7 +4256,7 @@ function registerModelSecurityCommand(program) {
3437
4256
  try {
3438
4257
  const fmt = opts.output;
3439
4258
  if (fmt === "pretty") renderModelSecurityHeader();
3440
- const service = await createService();
4259
+ const service = await createService2();
3441
4260
  const version = await service.getModelVersion(uuid);
3442
4261
  renderModelVersionDetail(version, fmt);
3443
4262
  } catch (err) {
@@ -3448,7 +4267,7 @@ function registerModelSecurityCommand(program) {
3448
4267
  try {
3449
4268
  const fmt = opts.output;
3450
4269
  if (fmt === "pretty") renderModelSecurityHeader();
3451
- const service = await createService();
4270
+ const service = await createService2();
3452
4271
  const result = await service.listModelVersionFiles(modelVersionUuid, {
3453
4272
  limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
3454
4273
  skip: opts.offset ? Number.parseInt(opts.offset, 10) : void 0
@@ -3464,25 +4283,8 @@ function registerModelSecurityCommand(program) {
3464
4283
  import * as fs2 from "fs";
3465
4284
  import * as path from "path";
3466
4285
 
3467
- // src/cli/confirm.ts
3468
- async function confirmOrAbort(message, force, options = {}) {
3469
- if (force) return;
3470
- const interactive = options.isTTY ?? process.stdout.isTTY === true;
3471
- if (!interactive) {
3472
- usageError(
3473
- `refusing to ${options.action ?? "proceed"} without --force in non-interactive mode`
3474
- );
3475
- }
3476
- const prompt = options.promptFn ?? (await import("@inquirer/prompts")).confirm;
3477
- const confirmed = await prompt({ message, default: false });
3478
- if (!confirmed) {
3479
- ui.info("Aborted");
3480
- process.exit(0);
3481
- }
3482
- }
3483
-
3484
4286
  // src/cli/deprecated-flags.ts
3485
- import chalk9 from "chalk";
4287
+ import chalk10 from "chalk";
3486
4288
  import { Option } from "commander";
3487
4289
  var ALIASES = /* @__PURE__ */ new WeakMap();
3488
4290
  function registerDeprecatedAlias(cmd, alias) {
@@ -3497,7 +4299,7 @@ function resolveDeprecatedAliases(cmd, opts) {
3497
4299
  const oldValue = opts[alias.oldKey];
3498
4300
  if (oldValue === void 0) continue;
3499
4301
  console.error(
3500
- chalk9.yellow(
4302
+ chalk10.yellow(
3501
4303
  ` \u26A0 ${alias.oldFlag.split(" ")[0]} is deprecated and will be removed in v4 \u2014 use ${alias.canonicalFlag}`
3502
4304
  )
3503
4305
  );
@@ -3681,7 +4483,7 @@ async function restoreTargets(opts) {
3681
4483
  }
3682
4484
 
3683
4485
  // src/cli/commands/redteam.ts
3684
- async function createService2() {
4486
+ async function createService3() {
3685
4487
  const config = await loadConfig();
3686
4488
  return new SdkRedTeamService(redTeamClientOptions(config));
3687
4489
  }
@@ -3689,6 +4491,14 @@ async function createPromptSetService() {
3689
4491
  const config = await loadConfig();
3690
4492
  return new SdkPromptSetService(redTeamClientOptions(config));
3691
4493
  }
4494
+ function buildDefaultCategories(categories) {
4495
+ return Object.fromEntries(
4496
+ categories.map((category) => [
4497
+ category.id,
4498
+ category.subCategories.map((subCategory) => subCategory.id).filter((id) => id !== "MULTI_TURN")
4499
+ ])
4500
+ );
4501
+ }
3692
4502
  function parseAttackGoals(input) {
3693
4503
  const trimmed = input.trim();
3694
4504
  const raw = trimmed.startsWith("[") ? trimmed : fs2.readFileSync(trimmed, "utf-8");
@@ -3704,8 +4514,12 @@ function parseAttackGoals(input) {
3704
4514
  return parsed;
3705
4515
  }
3706
4516
  function sliceClientSide(items, opts) {
3707
- const offset = opts.offset !== void 0 ? Number.parseInt(opts.offset, 10) : 0;
3708
- const limit = opts.limit !== void 0 ? Number.parseInt(opts.limit, 10) : void 0;
4517
+ if (opts.all) {
4518
+ const max = opts.max === void 0 ? 1e4 : Number(opts.max);
4519
+ return max === 0 ? items : items.slice(0, max);
4520
+ }
4521
+ const offset = opts.offset !== void 0 ? Number.parseInt(String(opts.offset), 10) : 0;
4522
+ const limit = opts.limit !== void 0 ? Number.parseInt(String(opts.limit), 10) : void 0;
3709
4523
  return items.slice(offset, limit === void 0 ? void 0 : offset + limit);
3710
4524
  }
3711
4525
  function parsePositiveInt(input, flag) {
@@ -3721,8 +4535,11 @@ var VALID_TARGET_PROVIDERS = [
3721
4535
  "DATABRICKS",
3722
4536
  "BEDROCK",
3723
4537
  "REST",
3724
- "STREAMING"
4538
+ "STREAMING",
4539
+ "WEBSOCKET",
4540
+ "CUSTOM_TARGET_ADAPTER"
3725
4541
  ];
4542
+ var REST_PROVIDERS = /* @__PURE__ */ new Set(["REST", "STREAMING", "WEBSOCKET", "HUGGING_FACE"]);
3726
4543
  function buildTargetScaffold(provider, templates) {
3727
4544
  const key = provider.toUpperCase();
3728
4545
  if (!VALID_TARGET_PROVIDERS.includes(key)) {
@@ -3730,21 +4547,92 @@ function buildTargetScaffold(provider, templates) {
3730
4547
  `Unknown provider "${provider}". Valid providers: ${VALID_TARGET_PROVIDERS.join(", ")}`
3731
4548
  );
3732
4549
  }
4550
+ if (key === "CUSTOM_TARGET_ADAPTER") {
4551
+ return {
4552
+ name: "",
4553
+ target_type: "AGENT",
4554
+ connection_type: "CUSTOM_TARGET_ADAPTER",
4555
+ api_endpoint_type: "NETWORK_BROKER",
4556
+ network_broker_channel_uuid: "<channel-uuid>",
4557
+ adapter_uuid: "<adapter-uuid>",
4558
+ // adapter_variable_overrides is an ARRAY of {key, value, type} objects.
4559
+ adapter_variable_overrides: [],
4560
+ target_background: { use_case: "" },
4561
+ additional_context: {}
4562
+ };
4563
+ }
4564
+ if (REST_PROVIDERS.has(key)) {
4565
+ const tpl = templates[key] ?? {};
4566
+ return {
4567
+ name: "",
4568
+ target_type: "APPLICATION",
4569
+ connection_type: "CUSTOM",
4570
+ api_endpoint_type: "PUBLIC",
4571
+ response_mode: key === "STREAMING" ? "STREAMING" : key === "WEBSOCKET" ? "WEBSOCKET" : "REST",
4572
+ auth_type: "HEADERS",
4573
+ auth_config: {
4574
+ auth_header: { Authorization: "Bearer <token>" }
4575
+ },
4576
+ connection_params: {
4577
+ api_endpoint: tpl.url ?? "",
4578
+ request_headers: { "Content-Type": "application/json" },
4579
+ request_json: tpl.request_json ?? { messages: [{ role: "user", content: "{INPUT}" }] },
4580
+ response_json: tpl.response_json ?? { choices: [{ message: { content: "{RESPONSE}" } }] },
4581
+ response_key: "choices.0.message.content"
4582
+ },
4583
+ target_background: {},
4584
+ additional_context: {}
4585
+ };
4586
+ }
3733
4587
  return {
3734
4588
  name: "",
3735
4589
  target_type: "APPLICATION",
3736
- connection_params: templates[key] ?? {},
4590
+ connection_type: key,
4591
+ api_endpoint_type: "PUBLIC",
4592
+ response_mode: "REST",
4593
+ auth_type: "HEADERS",
4594
+ auth_config: {
4595
+ auth_header: { Authorization: "Bearer <token>" }
4596
+ },
4597
+ connection_params: {
4598
+ target_connection_config: templates[key] ?? {}
4599
+ },
3737
4600
  target_background: {},
3738
- additional_context: {},
3739
- target_metadata: {}
4601
+ additional_context: {}
3740
4602
  };
3741
4603
  }
4604
+ function resolveScriptB64(opts) {
4605
+ if (opts.scriptFile !== void 0 && opts.scriptB64 !== void 0) {
4606
+ throw new Error("--script-file and --script-b64 are mutually exclusive");
4607
+ }
4608
+ if (opts.scriptB64 !== void 0) return opts.scriptB64;
4609
+ if (opts.scriptFile !== void 0) {
4610
+ return Buffer.from(fs2.readFileSync(opts.scriptFile, "utf-8")).toString("base64");
4611
+ }
4612
+ throw new Error("one of --script-file or --script-b64 is required");
4613
+ }
4614
+ function parseAdapterVariables(input) {
4615
+ let parsed;
4616
+ try {
4617
+ parsed = JSON.parse(input);
4618
+ } catch (err) {
4619
+ throw new Error(`--variables: invalid JSON (${err instanceof Error ? err.message : err})`);
4620
+ }
4621
+ if (!Array.isArray(parsed) || !parsed.every(
4622
+ (v) => v !== null && typeof v === "object" && typeof v.key === "string" && (v.type === "VAR" || v.type === "SECRET")
4623
+ )) {
4624
+ throw new Error(
4625
+ '--variables: expected a JSON array of { "key": string, "value"?: string|null, "type": "VAR"|"SECRET" }'
4626
+ );
4627
+ }
4628
+ return parsed;
4629
+ }
3742
4630
  function registerRedteamCommand(program) {
3743
4631
  const redteam = program.command("redteam").description("AI Red Team scan operations");
3744
4632
  redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
3745
4633
  try {
3746
4634
  renderRedteamHeader();
3747
- const service = await createService2();
4635
+ const service = await createService3();
3748
4636
  await service.abortScan(jobId);
3749
4637
  ui.success(`Scan ${jobId} aborted.`);
3750
4638
  } catch (err) {
@@ -3754,7 +4642,7 @@ function registerRedteamCommand(program) {
3754
4642
  redteam.command("categories").description("List available attack categories").action(async () => {
3755
4643
  try {
3756
4644
  renderRedteamHeader();
3757
- const service = await createService2();
4645
+ const service = await createService3();
3758
4646
  const categories = await service.getCategories();
3759
4647
  renderCategories(categories);
3760
4648
  } catch (err) {
@@ -3765,7 +4653,7 @@ function registerRedteamCommand(program) {
3765
4653
  eula.command("status").description("Check EULA acceptance status").action(async () => {
3766
4654
  try {
3767
4655
  renderRedteamHeader();
3768
- const service = await createService2();
4656
+ const service = await createService3();
3769
4657
  const status = await service.getEulaStatus();
3770
4658
  renderEulaStatus(status);
3771
4659
  } catch (err) {
@@ -3775,7 +4663,7 @@ function registerRedteamCommand(program) {
3775
4663
  eula.command("content").description("Display EULA content").action(async () => {
3776
4664
  try {
3777
4665
  renderRedteamHeader();
3778
- const service = await createService2();
4666
+ const service = await createService3();
3779
4667
  const content = await service.getEulaContent();
3780
4668
  renderEulaContent(content);
3781
4669
  } catch (err) {
@@ -3793,7 +4681,7 @@ function registerRedteamCommand(program) {
3793
4681
  resolveDeprecatedAliases(eulaAccept, opts);
3794
4682
  try {
3795
4683
  renderRedteamHeader();
3796
- const service = await createService2();
4684
+ const service = await createService3();
3797
4685
  const content = await service.getEulaContent();
3798
4686
  if (!opts.force) {
3799
4687
  renderEulaContent(content);
@@ -3811,7 +4699,7 @@ function registerRedteamCommand(program) {
3811
4699
  instances.command("create").description("Create an instance").requiredOption("--tsg-id <id>", "TSG ID").requiredOption("--tenant-id <id>", "Tenant ID").requiredOption("--app-id <id>", "App ID").requiredOption("--region <region>", "Region").action(async (opts) => {
3812
4700
  try {
3813
4701
  renderRedteamHeader();
3814
- const service = await createService2();
4702
+ const service = await createService3();
3815
4703
  const result = await service.createInstance({
3816
4704
  tsgId: opts.tsgId,
3817
4705
  tenantId: opts.tenantId,
@@ -3827,7 +4715,7 @@ function registerRedteamCommand(program) {
3827
4715
  try {
3828
4716
  const fmt = opts.output;
3829
4717
  if (fmt === "pretty") renderRedteamHeader();
3830
- const service = await createService2();
4718
+ const service = await createService3();
3831
4719
  const result = await service.getInstance(tenantId);
3832
4720
  renderInstanceDetail(result, fmt);
3833
4721
  } catch (err) {
@@ -3837,7 +4725,7 @@ function registerRedteamCommand(program) {
3837
4725
  instances.command("update <tenantId>").description("Update an instance").requiredOption("--tsg-id <id>", "TSG ID").requiredOption("--app-id <id>", "App ID").requiredOption("--region <region>", "Region").action(async (tenantId, opts) => {
3838
4726
  try {
3839
4727
  renderRedteamHeader();
3840
- const service = await createService2();
4728
+ const service = await createService3();
3841
4729
  const result = await service.updateInstance(tenantId, {
3842
4730
  tsgId: opts.tsgId,
3843
4731
  tenantId,
@@ -3852,7 +4740,7 @@ function registerRedteamCommand(program) {
3852
4740
  instances.command("delete <tenantId>").description("Delete an instance").action(async (tenantId) => {
3853
4741
  try {
3854
4742
  renderRedteamHeader();
3855
- const service = await createService2();
4743
+ const service = await createService3();
3856
4744
  const result = await service.deleteInstance(tenantId);
3857
4745
  renderInstanceResponse(result);
3858
4746
  ui.success(`Instance ${tenantId} deleted.`);
@@ -3864,7 +4752,7 @@ function registerRedteamCommand(program) {
3864
4752
  devices.command("create <tenantId>").description("Create devices for an instance").requiredOption("--config <path>", "JSON file with device request").action(async (tenantId, opts) => {
3865
4753
  try {
3866
4754
  renderRedteamHeader();
3867
- const service = await createService2();
4755
+ const service = await createService3();
3868
4756
  const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
3869
4757
  const result = await service.createDevices(tenantId, config);
3870
4758
  ui.success("Devices created:");
@@ -3876,7 +4764,7 @@ function registerRedteamCommand(program) {
3876
4764
  devices.command("update <tenantId>").description("Update devices for an instance (PATCH)").requiredOption("--config <path>", "JSON file with device request").action(async (tenantId, opts) => {
3877
4765
  try {
3878
4766
  renderRedteamHeader();
3879
- const service = await createService2();
4767
+ const service = await createService3();
3880
4768
  const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
3881
4769
  const result = await service.updateDevices(tenantId, config);
3882
4770
  ui.success("Devices updated:");
@@ -3888,7 +4776,7 @@ function registerRedteamCommand(program) {
3888
4776
  devices.command("delete <tenantId>").description("Delete devices by serial numbers").requiredOption("--serial-numbers <list>", "Comma-separated serial numbers").action(async (tenantId, opts) => {
3889
4777
  try {
3890
4778
  renderRedteamHeader();
3891
- const service = await createService2();
4779
+ const service = await createService3();
3892
4780
  const result = await service.deleteDevices(tenantId, opts.serialNumbers);
3893
4781
  ui.success("Devices deleted:");
3894
4782
  console.log(JSON.stringify(result, null, 2));
@@ -3900,7 +4788,7 @@ function registerRedteamCommand(program) {
3900
4788
  try {
3901
4789
  const fmt = opts.output;
3902
4790
  if (fmt === "pretty") renderRedteamHeader();
3903
- const service = await createService2();
4791
+ const service = await createService3();
3904
4792
  const creds = await service.getRegistryCredentials();
3905
4793
  renderRegistryCredentials(creds, fmt);
3906
4794
  } catch (err) {
@@ -3911,13 +4799,15 @@ function registerRedteamCommand(program) {
3911
4799
  try {
3912
4800
  const fmt = opts.output;
3913
4801
  if (fmt === "pretty") renderRedteamHeader();
3914
- const service = await createService2();
3915
- const scans = await service.listScans({
4802
+ const service = await createService3();
4803
+ const listOptions = {
3916
4804
  status: opts.status,
3917
4805
  jobType: opts.type,
3918
4806
  targetId: opts.target,
3919
- limit: Number.parseInt(opts.limit, 10)
3920
- });
4807
+ limit: Number.parseInt(opts.limit, 10),
4808
+ offset: Number(opts.offset ?? 0)
4809
+ };
4810
+ const scans = opts.all ? await service.listAllScans({ ...listOptions, max: Number(opts.max) }) : await service.listScans(listOptions);
3921
4811
  renderScanList(scans, fmt);
3922
4812
  } catch (err) {
3923
4813
  fail(err);
@@ -4124,7 +5014,7 @@ function registerRedteamCommand(program) {
4124
5014
  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) => {
4125
5015
  try {
4126
5016
  renderRedteamHeader();
4127
- const service = await createService2();
5017
+ const service = await createService3();
4128
5018
  const job = await service.getScan(jobId);
4129
5019
  renderScanStatus(job);
4130
5020
  if (job.jobType === "CUSTOM") {
@@ -4184,7 +5074,18 @@ function registerRedteamCommand(program) {
4184
5074
  const customPromptSets = opts.promptSets ? opts.promptSets.split(",").map((s) => s.trim()) : void 0;
4185
5075
  try {
4186
5076
  renderRedteamHeader();
4187
- const service = await createService2();
5077
+ const service = await createService3();
5078
+ if (opts.type === "STATIC" && !categories) {
5079
+ const defaultCategories = buildDefaultCategories(await service.getCategories());
5080
+ const categoryCount = Object.values(defaultCategories).reduce(
5081
+ (total, subCategories) => total + subCategories.length,
5082
+ 0
5083
+ );
5084
+ categories = defaultCategories;
5085
+ ui.status(
5086
+ `No --categories given \u2014 defaulting to all ${categoryCount} categories (MULTI_TURN excluded). Pass --categories to narrow the scan.`
5087
+ );
5088
+ }
4188
5089
  ui.status(`Creating ${opts.type} scan "${opts.name}"...`);
4189
5090
  const job = await service.createScan({
4190
5091
  name: opts.name,
@@ -4218,7 +5119,7 @@ function registerRedteamCommand(program) {
4218
5119
  redteam.command("status <jobId>").description("Check scan status").action(async (jobId) => {
4219
5120
  try {
4220
5121
  renderRedteamHeader();
4221
- const service = await createService2();
5122
+ const service = await createService3();
4222
5123
  const job = await service.getScan(jobId);
4223
5124
  renderScanStatus(job);
4224
5125
  } catch (err) {
@@ -4237,7 +5138,7 @@ function registerRedteamCommand(program) {
4237
5138
  try {
4238
5139
  const fmt = opts.output;
4239
5140
  if (fmt === "pretty") renderRedteamHeader();
4240
- const service = await createService2();
5141
+ const service = await createService3();
4241
5142
  const list = await service.listTargets();
4242
5143
  renderTargetList(sliceClientSide(list, opts), fmt);
4243
5144
  } catch (err) {
@@ -4248,7 +5149,7 @@ function registerRedteamCommand(program) {
4248
5149
  try {
4249
5150
  const fmt = opts.output;
4250
5151
  if (fmt === "pretty") renderRedteamHeader();
4251
- const service = await createService2();
5152
+ const service = await createService3();
4252
5153
  const target = await service.getTarget(uuid);
4253
5154
  renderTargetDetail(target, fmt);
4254
5155
  } catch (err) {
@@ -4258,7 +5159,7 @@ function registerRedteamCommand(program) {
4258
5159
  targets.command("create").description("Create a new red team target").requiredOption("--config <path>", "JSON file with target configuration").option("--validate", "Validate target connection before saving").action(async (opts) => {
4259
5160
  try {
4260
5161
  renderRedteamHeader();
4261
- const service = await createService2();
5162
+ const service = await createService3();
4262
5163
  const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
4263
5164
  const target = await service.createTarget(
4264
5165
  config,
@@ -4273,7 +5174,7 @@ function registerRedteamCommand(program) {
4273
5174
  targets.command("update <uuid>").description("Update a red team target").requiredOption("--config <path>", "JSON file with target updates").option("--validate", "Validate target connection before saving").action(async (uuid, opts) => {
4274
5175
  try {
4275
5176
  renderRedteamHeader();
4276
- const service = await createService2();
5177
+ const service = await createService3();
4277
5178
  const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
4278
5179
  const target = await service.updateTarget(
4279
5180
  uuid,
@@ -4292,7 +5193,7 @@ function registerRedteamCommand(program) {
4292
5193
  action: `delete target ${uuid}`
4293
5194
  });
4294
5195
  renderRedteamHeader();
4295
- const service = await createService2();
5196
+ const service = await createService3();
4296
5197
  await service.deleteTarget(uuid);
4297
5198
  ui.success(`Target ${uuid} deleted.`);
4298
5199
  } catch (err) {
@@ -4302,7 +5203,7 @@ function registerRedteamCommand(program) {
4302
5203
  targets.command("probe").description("Test target connection without saving").requiredOption("--config <path>", "JSON file with connection params").action(async (opts) => {
4303
5204
  try {
4304
5205
  renderRedteamHeader();
4305
- const service = await createService2();
5206
+ const service = await createService3();
4306
5207
  const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
4307
5208
  const result = await service.probeTarget(config);
4308
5209
  ui.dim("Probe result:");
@@ -4314,7 +5215,7 @@ function registerRedteamCommand(program) {
4314
5215
  targets.command("profile <uuid>").description("View target profile").action(async (uuid) => {
4315
5216
  try {
4316
5217
  renderRedteamHeader();
4317
- const service = await createService2();
5218
+ const service = await createService3();
4318
5219
  const profile = await service.getTargetProfile(uuid);
4319
5220
  ui.dim("Target Profile:");
4320
5221
  console.log(JSON.stringify(profile, null, 2));
@@ -4325,7 +5226,7 @@ function registerRedteamCommand(program) {
4325
5226
  targets.command("update-profile <uuid>").description("Update target profile").requiredOption("--config <path>", "JSON file with profile updates").action(async (uuid, opts) => {
4326
5227
  try {
4327
5228
  renderRedteamHeader();
4328
- const service = await createService2();
5229
+ const service = await createService3();
4329
5230
  const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
4330
5231
  const result = await service.updateTargetProfile(uuid, config);
4331
5232
  ui.success("Profile updated:");
@@ -4337,7 +5238,7 @@ function registerRedteamCommand(program) {
4337
5238
  targets.command("validate-auth").description("Validate target auth credentials").requiredOption("--auth-type <type>", "Auth type: HEADERS, BASIC_AUTH, OAUTH2").requiredOption("--config <path>", "JSON file with auth_config").option("--target-id <uuid>", "Existing target UUID").action(async (opts) => {
4338
5239
  try {
4339
5240
  renderRedteamHeader();
4340
- const service = await createService2();
5241
+ const service = await createService3();
4341
5242
  const authConfig = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
4342
5243
  const result = await service.validateTargetAuth({
4343
5244
  authType: opts.authType,
@@ -4351,7 +5252,7 @@ function registerRedteamCommand(program) {
4351
5252
  });
4352
5253
  targets.command("metadata").description("Get target field metadata").action(async () => {
4353
5254
  try {
4354
- const service = await createService2();
5255
+ const service = await createService3();
4355
5256
  const metadata = await service.getTargetMetadata();
4356
5257
  console.log(JSON.stringify(metadata, null, 2));
4357
5258
  } catch (err) {
@@ -4383,7 +5284,7 @@ function registerRedteamCommand(program) {
4383
5284
  }
4384
5285
  try {
4385
5286
  renderRedteamHeader();
4386
- const service = await createService2();
5287
+ const service = await createService3();
4387
5288
  const templates = await service.getTargetTemplates();
4388
5289
  const scaffold = buildTargetScaffold(provider, templates);
4389
5290
  fs2.writeFileSync(outputPath, `${JSON.stringify(scaffold, null, 2)}
@@ -4402,19 +5303,25 @@ function registerRedteamCommand(program) {
4402
5303
  targets.command("templates").description("Get provider-specific target templates").action(async () => {
4403
5304
  try {
4404
5305
  renderRedteamHeader();
4405
- const service = await createService2();
5306
+ const service = await createService3();
4406
5307
  const templates = await service.getTargetTemplates();
4407
5308
  renderTargetTemplates(templates);
4408
5309
  } catch (err) {
4409
5310
  fail(err);
4410
5311
  }
4411
5312
  });
4412
- const targetsBackup = targets.command("backup").description("Backup red team targets to local JSON/YAML files").option("--output-dir <path>", "Output directory").option("--output <format>", "Output format: json or yaml", "json").option("--name <targetName>", "Backup a single target by name");
5313
+ const targetsBackup = targets.command("backup").description("Backup red team targets to local JSON/YAML files").option("--output-dir <path>", "Output directory").option("--file-format <format>", "Backup file format: json or yaml", "json").option("--name <targetName>", "Backup a single target by name");
5314
+ registerDeprecatedAlias(targetsBackup, {
5315
+ oldFlag: "--output <format>",
5316
+ oldKey: "output",
5317
+ canonicalFlag: "--file-format",
5318
+ canonicalKey: "fileFormat"
5319
+ });
4413
5320
  registerDeprecatedAlias(targetsBackup, {
4414
5321
  oldFlag: "--format <format>",
4415
5322
  oldKey: "format",
4416
- canonicalFlag: "--output",
4417
- canonicalKey: "output"
5323
+ canonicalFlag: "--file-format",
5324
+ canonicalKey: "fileFormat"
4418
5325
  });
4419
5326
  targetsBackup.action(async (opts) => {
4420
5327
  resolveDeprecatedAliases(targetsBackup, opts);
@@ -4423,7 +5330,7 @@ function registerRedteamCommand(program) {
4423
5330
  const outputDir = resolveOutputDir(opts.outputDir, "targets");
4424
5331
  const results = await backupTargets({
4425
5332
  outputDir,
4426
- format: opts.output ?? "json",
5333
+ format: opts.fileFormat ?? "json",
4427
5334
  name: opts.name
4428
5335
  });
4429
5336
  renderBackupSummary(results, outputDir);
@@ -4456,7 +5363,7 @@ function registerRedteamCommand(program) {
4456
5363
  try {
4457
5364
  const fmt = opts.output;
4458
5365
  if (fmt === "pretty") renderRedteamHeader();
4459
- const service = await createService2();
5366
+ const service = await createService3();
4460
5367
  const { logs } = await service.getTargetProfileErrorLogs(targetId, {
4461
5368
  limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
4462
5369
  offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
@@ -4467,19 +5374,182 @@ function registerRedteamCommand(program) {
4467
5374
  fail(err);
4468
5375
  }
4469
5376
  });
5377
+ const adapter = redteam.command("adapter").description("Manage custom target adapters (scripted targets run via the network broker)");
5378
+ async function assertChannelOnline(service, channelUuid) {
5379
+ let status;
5380
+ try {
5381
+ status = (await service.getChannel(channelUuid)).status;
5382
+ } catch {
5383
+ return;
5384
+ }
5385
+ if (status && status !== "ONLINE") {
5386
+ fail(
5387
+ new Error(
5388
+ `network broker channel ${channelUuid} is ${status} \u2014 adapter validation requires an ONLINE channel (network broker v1.4.0+). Check 'airs redteam network-broker channels list'.`
5389
+ )
5390
+ );
5391
+ }
5392
+ }
5393
+ adapter.command("list").description("List custom target adapters").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--search <text>", "Filter by search text").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").action(async (opts) => {
5394
+ try {
5395
+ const fmt = opts.output;
5396
+ if (fmt === "pretty") renderRedteamHeader();
5397
+ const service = await createService3();
5398
+ const listOptions = {
5399
+ limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
5400
+ offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
5401
+ search: opts.search
5402
+ };
5403
+ const result = opts.all ? { adapters: await service.listAllAdapters({ ...listOptions, max: Number(opts.max) }) } : await service.listAdapters(listOptions);
5404
+ const { adapters, totalItems } = result;
5405
+ renderAdapterList(adapters, fmt, totalItems);
5406
+ } catch (err) {
5407
+ fail(err);
5408
+ }
5409
+ });
5410
+ adapter.command("get <uuid>").description("Get a custom target adapter").option("--output <format>", "Output format: pretty, json, yaml", "pretty").action(async (uuid, opts) => {
5411
+ try {
5412
+ const fmt = opts.output;
5413
+ if (fmt === "pretty") renderRedteamHeader();
5414
+ const service = await createService3();
5415
+ renderAdapterDetail(await service.getAdapter(uuid), fmt);
5416
+ } catch (err) {
5417
+ fail(err);
5418
+ }
5419
+ });
5420
+ adapter.command("create").description("Create a custom target adapter").requiredOption("--name <name>", "Adapter name").requiredOption(
5421
+ "--prompt <text>",
5422
+ "Sample prompt used to exercise the adapter during validation (not stored)"
5423
+ ).option("--script-file <path>", "Path to the adapter script (encoded to base64 for you)").option("--script-b64 <b64>", "Adapter script, already base64-encoded").option("--description <text>", "Adapter description").option("--channel <uuid>", "Network broker channel UUID (required to activate)").option("--variables <json>", "JSON array of { key, value, type: VAR|SECRET }").option("--draft", "Save as DRAFT without running the validation script").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
5424
+ "after",
5425
+ examples(
5426
+ `airs redteam adapter create --name my-adapter --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --variables '[{"key":"endpoint","value":"http://agent.svc:8080","type":"VAR"}]'`,
5427
+ "airs redteam adapter create --name my-adapter --script-file ./adapter.py --prompt Hello --draft"
5428
+ )
5429
+ ).action(async (opts) => {
5430
+ try {
5431
+ const fmt = opts.output;
5432
+ if (fmt === "pretty") renderRedteamHeader();
5433
+ const scriptB64 = resolveScriptB64(opts);
5434
+ const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
5435
+ const service = await createService3();
5436
+ if (!opts.draft && opts.channel) await assertChannelOnline(service, opts.channel);
5437
+ const created = await service.createAdapter(
5438
+ {
5439
+ name: opts.name,
5440
+ scriptB64,
5441
+ prompt: opts.prompt,
5442
+ description: opts.description,
5443
+ networkBrokerChannelUuid: opts.channel,
5444
+ variables
5445
+ },
5446
+ opts.draft ? false : void 0
5447
+ );
5448
+ ui.success(`Adapter created: ${created.uuid}`);
5449
+ renderAdapterDetail(created, fmt);
5450
+ } catch (err) {
5451
+ fail(err);
5452
+ }
5453
+ });
5454
+ adapter.command("update <uuid>").description(
5455
+ "Update a custom target adapter (read-modify-write; variables preserved unless --variables)"
5456
+ ).requiredOption(
5457
+ "--prompt <text>",
5458
+ "Sample validation prompt \u2014 required on every update because upstream never stores it"
5459
+ ).option("--name <name>", "New adapter name").option("--script-file <path>", "New adapter script file (encoded to base64 for you)").option("--script-b64 <b64>", "New adapter script, already base64-encoded").option("--description <text>", "New description").option("--channel <uuid>", "New network broker channel UUID").option(
5460
+ "--variables <json>",
5461
+ "REPLACES the whole variable set \u2014 omitted keys are deleted upstream. Omit this flag to preserve stored variables."
5462
+ ).option("--draft", "Save as DRAFT without re-running the validation script").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
5463
+ "after",
5464
+ examples(
5465
+ `airs redteam adapter update 550e8400-... --description 'new description' --prompt 'Hello'`
5466
+ )
5467
+ ).action(async (uuid, opts) => {
5468
+ try {
5469
+ const fmt = opts.output;
5470
+ if (fmt === "pretty") renderRedteamHeader();
5471
+ const scriptB64 = opts.scriptFile !== void 0 || opts.scriptB64 !== void 0 ? resolveScriptB64(opts) : void 0;
5472
+ const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
5473
+ const service = await createService3();
5474
+ if (!opts.draft && opts.channel) await assertChannelOnline(service, opts.channel);
5475
+ const updated = await service.updateAdapter(
5476
+ uuid,
5477
+ {
5478
+ prompt: opts.prompt,
5479
+ name: opts.name,
5480
+ scriptB64,
5481
+ description: opts.description,
5482
+ networkBrokerChannelUuid: opts.channel,
5483
+ variables
5484
+ },
5485
+ opts.draft ? false : void 0
5486
+ );
5487
+ ui.success(`Adapter updated: ${updated.uuid}`);
5488
+ renderAdapterDetail(updated, fmt);
5489
+ } catch (err) {
5490
+ fail(err);
5491
+ }
5492
+ });
5493
+ adapter.command("delete <uuid>").description("Delete a custom target adapter").option("--force", "Skip confirmation prompt").action(async (uuid, opts) => {
5494
+ try {
5495
+ renderRedteamHeader();
5496
+ await confirmOrAbort(`Delete adapter ${uuid}?`, Boolean(opts.force), {
5497
+ action: `delete adapter ${uuid}`
5498
+ });
5499
+ const service = await createService3();
5500
+ await service.deleteAdapter(uuid);
5501
+ ui.success(`Adapter ${uuid} deleted.`);
5502
+ } catch (err) {
5503
+ fail(err);
5504
+ }
5505
+ });
5506
+ adapter.command("validate").description("Run an adapter script end-to-end through the broker channel without saving").requiredOption("--channel <uuid>", "Network broker channel UUID (must be ONLINE)").requiredOption("--prompt <text>", "Sample prompt to send through the adapter").option("--script-file <path>", "Path to the adapter script (encoded to base64 for you)").option("--script-b64 <b64>", "Adapter script, already base64-encoded").option(
5507
+ "--variables <json>",
5508
+ "JSON array of { key, value, type } \u2014 the FULL set the script needs"
5509
+ ).option(
5510
+ "--adapter <uuid>",
5511
+ "Existing adapter: resolves redacted/null variable values from its stored secrets (and supplies its variable set when --variables is omitted)"
5512
+ ).option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
5513
+ "after",
5514
+ examples(
5515
+ `airs redteam adapter validate --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --variables '[{"key":"endpoint","value":"http://agent.svc:8080","type":"VAR"}]'`,
5516
+ `airs redteam adapter validate --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --adapter 660e8400-...`
5517
+ )
5518
+ ).action(async (opts) => {
5519
+ try {
5520
+ const fmt = opts.output;
5521
+ if (fmt === "pretty") renderRedteamHeader();
5522
+ const scriptB64 = resolveScriptB64(opts);
5523
+ const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
5524
+ const service = await createService3();
5525
+ await assertChannelOnline(service, opts.channel);
5526
+ const result = await service.validateAdapter({
5527
+ scriptB64,
5528
+ networkBrokerChannelUuid: opts.channel,
5529
+ prompt: opts.prompt,
5530
+ variables,
5531
+ adapterUuid: opts.adapter
5532
+ });
5533
+ renderAdapterValidation(result, fmt);
5534
+ if (!result.validated) process.exitCode = 1;
5535
+ } catch (err) {
5536
+ fail(err);
5537
+ }
5538
+ });
4470
5539
  const networkBroker = redteam.command("network-broker").description("Manage red team network broker channels");
4471
5540
  const channels = networkBroker.command("channels").description("Manage network broker channels");
4472
5541
  channels.command("list").description("List network broker channels").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--search <text>", "Filter by search text").option("--status <status...>", "Filter by status (ONLINE, OFFLINE, DRAFT)").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").action(async (opts) => {
4473
5542
  try {
4474
5543
  const fmt = opts.output;
4475
5544
  if (fmt === "pretty") renderRedteamHeader();
4476
- const service = await createService2();
4477
- const { channels: list } = await service.listChannels({
5545
+ const service = await createService3();
5546
+ const listOptions = {
4478
5547
  limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
4479
5548
  offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
4480
5549
  search: opts.search,
4481
5550
  status: opts.status
4482
- });
5551
+ };
5552
+ const list = opts.all ? await service.listAllChannels({ ...listOptions, max: Number(opts.max) }) : (await service.listChannels(listOptions)).channels;
4483
5553
  renderChannelList(list, fmt);
4484
5554
  } catch (err) {
4485
5555
  fail(err);
@@ -4489,7 +5559,7 @@ function registerRedteamCommand(program) {
4489
5559
  try {
4490
5560
  const fmt = opts.output;
4491
5561
  if (fmt === "pretty") renderRedteamHeader();
4492
- const service = await createService2();
5562
+ const service = await createService3();
4493
5563
  const channel = await service.getChannel(channelId);
4494
5564
  renderChannelDetail(channel, fmt);
4495
5565
  } catch (err) {
@@ -4499,7 +5569,7 @@ function registerRedteamCommand(program) {
4499
5569
  channels.command("create").description("Create a network broker channel").requiredOption("--name <name>", "Channel name").option("--description <text>", "Channel description").action(async (opts) => {
4500
5570
  try {
4501
5571
  renderRedteamHeader();
4502
- const service = await createService2();
5572
+ const service = await createService3();
4503
5573
  const channel = await service.createChannel({
4504
5574
  name: opts.name,
4505
5575
  description: opts.description
@@ -4516,7 +5586,7 @@ function registerRedteamCommand(program) {
4516
5586
  usageError("Specify --name and/or --description to update");
4517
5587
  }
4518
5588
  renderRedteamHeader();
4519
- const service = await createService2();
5589
+ const service = await createService3();
4520
5590
  const channel = await service.updateChannel(channelId, {
4521
5591
  name: opts.name,
4522
5592
  description: opts.description
@@ -4531,7 +5601,7 @@ function registerRedteamCommand(program) {
4531
5601
  try {
4532
5602
  const fmt = opts.output;
4533
5603
  if (fmt === "pretty") renderRedteamHeader();
4534
- const service = await createService2();
5604
+ const service = await createService3();
4535
5605
  const stats = await service.getChannelStats();
4536
5606
  renderChannelStats(stats, fmt);
4537
5607
  } catch (err) {
@@ -4542,7 +5612,7 @@ function registerRedteamCommand(program) {
4542
5612
  try {
4543
5613
  const fmt = opts.output;
4544
5614
  if (fmt === "pretty") renderRedteamHeader();
4545
- const service = await createService2();
5615
+ const service = await createService3();
4546
5616
  const data = await service.getLanguages(Boolean(opts.management));
4547
5617
  renderLanguages(data, fmt);
4548
5618
  } catch (err) {
@@ -4556,7 +5626,7 @@ import { randomUUID as randomUUID4 } from "crypto";
4556
5626
  import * as fs5 from "fs";
4557
5627
  import { readFile as readFile8 } from "fs/promises";
4558
5628
  import { basename as basename3, dirname as dirname2, join as join2, resolve as resolvePath } from "path";
4559
- import chalk10 from "chalk";
5629
+ import chalk11 from "chalk";
4560
5630
 
4561
5631
  // src/cli/builders/profile-builder.ts
4562
5632
  function parseList(value) {
@@ -5021,6 +6091,31 @@ async function loadBulkScanState(filePath) {
5021
6091
 
5022
6092
  // src/cli/pagination.ts
5023
6093
  var DEFAULT_PAGE_SIZE = 50;
6094
+ var DEFAULT_OFFSET_LIMIT = 100;
6095
+ var DEFAULT_MAX_ITEMS = 1e4;
6096
+ function registerListFlags(command, options) {
6097
+ const defaultLimit = options.dialect === "offset" ? DEFAULT_OFFSET_LIMIT : DEFAULT_PAGE_SIZE;
6098
+ return command.option("--limit <n>", `Items per page (default: ${defaultLimit})`, Number, defaultLimit).option("--offset <n>", "Item offset (default: 0)", Number, 0).option("--all", "Walk all pages").option("--max <n>", "Maximum items with --all; 0 removes the cap", Number, DEFAULT_MAX_ITEMS);
6099
+ }
6100
+ function integerFlag(name, value, minimum) {
6101
+ const parsed = Number(value);
6102
+ if (!Number.isSafeInteger(parsed) || parsed < minimum) {
6103
+ const qualifier = minimum === 1 ? "a positive integer" : "a non-negative integer";
6104
+ throw new CliUsageError(`${name} must be ${qualifier}`);
6105
+ }
6106
+ return parsed;
6107
+ }
6108
+ function resolveListParams(command, opts, options) {
6109
+ const defaultLimit = options.dialect === "offset" ? DEFAULT_OFFSET_LIMIT : DEFAULT_PAGE_SIZE;
6110
+ const limit = integerFlag("--limit", opts.limit ?? defaultLimit, 1);
6111
+ const offset = integerFlag("--offset", opts.offset ?? 0, 0);
6112
+ const max = integerFlag("--max", opts.max ?? DEFAULT_MAX_ITEMS, 0);
6113
+ const all = Boolean(opts.all);
6114
+ if (all && command.getOptionValueSource?.("offset") === "cli") {
6115
+ throw new CliUsageError("--all cannot be combined with --offset");
6116
+ }
6117
+ return { limit, offset, all, max };
6118
+ }
5024
6119
  function registerPageAliases(cmd, opts) {
5025
6120
  registerDeprecatedAlias(cmd, {
5026
6121
  oldFlag: `${opts.sizeFlag} <n>`,
@@ -5142,6 +6237,71 @@ function parseQuotedField(content, start, len) {
5142
6237
  return { value, nextIndex: i };
5143
6238
  }
5144
6239
 
6240
+ // src/cli/renderer/views/runtime.ts
6241
+ var profilesView = {
6242
+ name: "profiles",
6243
+ columns: [
6244
+ { key: "profileId", label: "ID" },
6245
+ { key: "profileName", label: "Name" },
6246
+ { key: "active", label: "Active" },
6247
+ { key: "revision", label: "Revision" }
6248
+ ],
6249
+ pretty: {
6250
+ list: (items) => renderProfileList(items, "pretty"),
6251
+ detail: renderProfileDetail
6252
+ }
6253
+ };
6254
+ var apiKeysView = {
6255
+ name: "API keys",
6256
+ columns: [
6257
+ { key: "id", label: "ID" },
6258
+ { key: "name", label: "Name" },
6259
+ { key: "last8", label: "Last 8" },
6260
+ { key: "expiresAt", label: "Expires" }
6261
+ ],
6262
+ pretty: {
6263
+ list: (items) => renderApiKeyList(items, "pretty"),
6264
+ detail: renderApiKeyDetail
6265
+ }
6266
+ };
6267
+ var customerAppsView = {
6268
+ name: "customer apps",
6269
+ columns: [
6270
+ { key: "id", label: "ID" },
6271
+ { key: "name", label: "Name" },
6272
+ { key: "description", label: "Description" }
6273
+ ],
6274
+ pretty: {
6275
+ list: (items) => renderCustomerAppList(items, "pretty"),
6276
+ detail: renderCustomerAppDetail
6277
+ }
6278
+ };
6279
+ var topicsView = {
6280
+ name: "topics",
6281
+ columns: [
6282
+ { key: "topic_id", label: "ID" },
6283
+ { key: "topic_name", label: "Name" },
6284
+ { key: "revision", label: "Revision" },
6285
+ { key: "description", label: "Description" }
6286
+ ],
6287
+ structured: (topic) => ({
6288
+ topicId: topic.topic_id,
6289
+ topicName: topic.topic_name,
6290
+ revision: topic.revision,
6291
+ active: topic.active,
6292
+ description: topic.description,
6293
+ examples: topic.examples,
6294
+ createdBy: topic.created_by,
6295
+ updatedBy: topic.updated_by,
6296
+ lastModifiedTs: topic.last_modified_ts,
6297
+ createdTs: topic.created_ts
6298
+ }),
6299
+ pretty: {
6300
+ list: (items) => renderTopicList(items, "pretty"),
6301
+ detail: renderTopicDetail
6302
+ }
6303
+ };
6304
+
5145
6305
  // src/cli/commands/dlp/dictionaries.ts
5146
6306
  import { readFile as readFile6 } from "fs/promises";
5147
6307
  import { basename as basename2 } from "path";
@@ -5155,6 +6315,9 @@ var SdkDictionariesService = class {
5155
6315
  async list(params) {
5156
6316
  return this.client.list(params);
5157
6317
  }
6318
+ async listAll(params) {
6319
+ return this.client.listAll(params);
6320
+ }
5158
6321
  async create(params) {
5159
6322
  return this.client.create(params);
5160
6323
  }
@@ -5262,20 +6425,22 @@ function register(dlp) {
5262
6425
  "--offset <n>",
5263
6426
  "Starting offset \u2014 rounds down to a page boundary",
5264
6427
  (v) => Number.parseInt(v, 10)
5265
- ).option("--sort <field,dir>", "(repeatable)", (v, p = []) => [...p, v]).option("--keywords", "Include keyword list in response").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format", "pretty");
6428
+ ).option("--sort <field,dir>", "(repeatable)", (v, p = []) => [...p, v]).option("--keywords", "Include keyword list in response").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
5266
6429
  registerPageAliases(listCmd, { sizeFlag: "--size", sizeKey: "size" });
5267
6430
  listCmd.action(async (opts) => {
5268
6431
  try {
5269
6432
  const { page, size } = resolvePageParams(listCmd, opts);
5270
6433
  const includeKeywords = opts.keywords || opts.includeKeywords;
6434
+ const svc = new SdkDictionariesService();
6435
+ const params = {
6436
+ size,
6437
+ sort: opts.sort,
6438
+ keywords: includeKeywords ? true : void 0
6439
+ };
6440
+ const all = opts.all ? await svc.listAll({ ...params, max: Number(opts.max) }) : void 0;
5271
6441
  dlpDictionaries.renderList(
5272
- await new SdkDictionariesService().list({
5273
- page,
5274
- size,
5275
- sort: opts.sort,
5276
- keywords: includeKeywords ? true : void 0
5277
- }),
5278
- opts.output
6442
+ all ? { content: all, totalElements: all.length } : await svc.list({ ...params, page }),
6443
+ await resolveOutput(listCmd, opts)
5279
6444
  );
5280
6445
  } catch (err) {
5281
6446
  fail(err);
@@ -5296,12 +6461,12 @@ function register(dlp) {
5296
6461
  usageError(err instanceof Error ? err.message : String(err));
5297
6462
  }
5298
6463
  });
5299
- group.command("get <id>").option("--keywords", "").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format", "pretty").action(async (id, opts) => {
6464
+ const getCmd = group.command("get <id>").option("--keywords", "").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
5300
6465
  try {
5301
6466
  const includeKeywords = opts.keywords || opts.includeKeywords;
5302
6467
  dlpDictionaries.renderGet(
5303
6468
  await new SdkDictionariesService().get(id, { includeKeywords }),
5304
- opts.output
6469
+ await resolveOutput(getCmd, opts)
5305
6470
  );
5306
6471
  } catch (err) {
5307
6472
  fail(err);
@@ -5362,6 +6527,9 @@ var SdkDataFilteringProfilesService = class {
5362
6527
  async list(params) {
5363
6528
  return this.client.list(params);
5364
6529
  }
6530
+ async listAll(params) {
6531
+ return this.client.listAll(params);
6532
+ }
5365
6533
  async get(id) {
5366
6534
  return this.client.get(id);
5367
6535
  }
@@ -5495,7 +6663,7 @@ function listFlags(cmd) {
5495
6663
  "--offset <n>",
5496
6664
  "Starting offset \u2014 rounds down to a page boundary",
5497
6665
  (v) => Number.parseInt(v, 10)
5498
- ).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format", "pretty");
6666
+ ).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
5499
6667
  registerPageAliases(cmd, { sizeFlag: "--size", sizeKey: "size" });
5500
6668
  return cmd;
5501
6669
  }
@@ -5516,16 +6684,17 @@ function register2(dlp) {
5516
6684
  try {
5517
6685
  const { page, size } = resolvePageParams(listCmd, opts);
5518
6686
  const svc = new SdkDataFilteringProfilesService();
5519
- const r = await svc.list({ page, size, sort: opts.sort });
5520
- dlpFilteringProfiles.renderList(r, opts.output);
6687
+ const all = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
6688
+ const r = all ? { content: all, totalElements: all.length } : await svc.list({ page, size, sort: opts.sort });
6689
+ dlpFilteringProfiles.renderList(r, await resolveOutput(listCmd, opts));
5521
6690
  } catch (err) {
5522
6691
  fail(err);
5523
6692
  }
5524
6693
  });
5525
- group.command("get <id>").description("Get a filtering profile by id").option("--output <fmt>", "Output format", "pretty").action(async (id, opts) => {
6694
+ const getCmd = group.command("get <id>").description("Get a filtering profile by id").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
5526
6695
  try {
5527
6696
  const svc = new SdkDataFilteringProfilesService();
5528
- dlpFilteringProfiles.renderGet(await svc.get(id), opts.output);
6697
+ dlpFilteringProfiles.renderGet(await svc.get(id), await resolveOutput(getCmd, opts));
5529
6698
  } catch (err) {
5530
6699
  fail(err);
5531
6700
  }
@@ -5621,6 +6790,9 @@ var SdkDataPatternsService = class {
5621
6790
  async list(params) {
5622
6791
  return this.client.list(params);
5623
6792
  }
6793
+ async listAll(params) {
6794
+ return this.client.listAll(params);
6795
+ }
5624
6796
  async create(body) {
5625
6797
  return this.client.create(body);
5626
6798
  }
@@ -5644,7 +6816,7 @@ function listFlags2(cmd) {
5644
6816
  "--offset <n>",
5645
6817
  "Starting offset \u2014 rounds down to a page boundary",
5646
6818
  (v) => Number.parseInt(v, 10)
5647
- ).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format", "pretty");
6819
+ ).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
5648
6820
  registerPageAliases(cmd, { sizeFlag: "--size", sizeKey: "size" });
5649
6821
  return cmd;
5650
6822
  }
@@ -5666,9 +6838,10 @@ function register4(dlp) {
5666
6838
  try {
5667
6839
  const { page, size } = resolvePageParams(listCmd, opts);
5668
6840
  const svc = new SdkDataPatternsService();
6841
+ const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
5669
6842
  dlpPatterns.renderList(
5670
- await svc.list({ page, size, sort: opts.sort }),
5671
- opts.output
6843
+ result ? { content: result, totalElements: result.length } : await svc.list({ page, size, sort: opts.sort }),
6844
+ await resolveOutput(listCmd, opts)
5672
6845
  );
5673
6846
  } catch (err) {
5674
6847
  fail(err);
@@ -5686,11 +6859,11 @@ function register4(dlp) {
5686
6859
  usageError(err instanceof Error ? err.message : String(err));
5687
6860
  }
5688
6861
  });
5689
- group.command("get <id>").description("Get a data pattern by id").option("--output <fmt>", "Output format", "pretty").action(async (id, opts) => {
6862
+ const getCmd = group.command("get <id>").description("Get a data pattern by id").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
5690
6863
  try {
5691
6864
  dlpPatterns.renderGet(
5692
6865
  await new SdkDataPatternsService().get(id),
5693
- opts.output
6866
+ await resolveOutput(getCmd, opts)
5694
6867
  );
5695
6868
  } catch (err) {
5696
6869
  fail(err);
@@ -5746,6 +6919,9 @@ var SdkDataProfilesService = class {
5746
6919
  async list(params) {
5747
6920
  return this.client.list(params);
5748
6921
  }
6922
+ async listAll(params) {
6923
+ return this.client.listAll(params);
6924
+ }
5749
6925
  async create(body) {
5750
6926
  return this.client.create(body);
5751
6927
  }
@@ -5766,7 +6942,7 @@ function listFlags3(cmd) {
5766
6942
  "--offset <n>",
5767
6943
  "Starting offset \u2014 rounds down to a page boundary",
5768
6944
  (v) => Number.parseInt(v, 10)
5769
- ).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format", "pretty");
6945
+ ).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
5770
6946
  registerPageAliases(cmd, { sizeFlag: "--size", sizeKey: "size" });
5771
6947
  return cmd;
5772
6948
  }
@@ -5800,9 +6976,10 @@ function register5(dlp) {
5800
6976
  try {
5801
6977
  const { page, size } = resolvePageParams(listCmd, opts);
5802
6978
  const svc = new SdkDataProfilesService();
6979
+ const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
5803
6980
  dlpProfiles.renderList(
5804
- await svc.list({ page, size, sort: opts.sort }),
5805
- opts.output
6981
+ result ? { content: result, totalElements: result.length } : await svc.list({ page, size, sort: opts.sort }),
6982
+ await resolveOutput(listCmd, opts)
5806
6983
  );
5807
6984
  } catch (err) {
5808
6985
  fail(err);
@@ -5820,11 +6997,11 @@ function register5(dlp) {
5820
6997
  usageError(err instanceof Error ? err.message : String(err));
5821
6998
  }
5822
6999
  });
5823
- group.command("get <id>").description("Get a data profile by id").option("--output <fmt>", "Output format", "pretty").action(async (id, opts) => {
7000
+ const getCmd = group.command("get <id>").description("Get a data profile by id").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
5824
7001
  try {
5825
7002
  dlpProfiles.renderGet(
5826
7003
  await new SdkDataProfilesService().get(id),
5827
- opts.output
7004
+ await resolveOutput(getCmd, opts)
5828
7005
  );
5829
7006
  } catch (err) {
5830
7007
  fail(err);
@@ -6373,14 +7550,14 @@ function registerSampleCommand(parent) {
6373
7550
 
6374
7551
  // src/cli/commands/runtime.ts
6375
7552
  function renderScanResult(result) {
6376
- const actionColor = result.action === "block" ? chalk10.red : chalk10.green;
7553
+ const actionColor = result.action === "block" ? chalk11.red : chalk11.green;
6377
7554
  ui.header("Scan Result");
6378
7555
  ui.keyValue([
6379
7556
  ["Action", actionColor(result.action.toUpperCase())],
6380
7557
  ["Category", result.category],
6381
- ["Triggered", result.triggered ? chalk10.red("yes") : chalk10.green("no")],
6382
- ["Scan ID", chalk10.dim(result.scanId)],
6383
- ["Report ID", chalk10.dim(result.reportId)]
7558
+ ["Triggered", result.triggered ? chalk11.red("yes") : chalk11.green("no")],
7559
+ ["Scan ID", chalk11.dim(result.scanId)],
7560
+ ["Report ID", chalk11.dim(result.reportId)]
6384
7561
  ]);
6385
7562
  const flags = Object.entries(result.detections).filter(([, v]) => v);
6386
7563
  if (flags.length > 0) {
@@ -6472,15 +7649,26 @@ async function createMgmtService() {
6472
7649
  function registerRuntimeCommand(program) {
6473
7650
  const runtime = program.command("runtime").description("Runtime prompt scanning against AIRS profiles");
6474
7651
  const apiKeys = runtime.command("api-keys").description("Manage AIRS API keys");
6475
- apiKeys.command("list").description("List API keys").option("--limit <n>", "Max results", "100").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").action(async (opts) => {
7652
+ const apiKeysList = registerListFlags(apiKeys.command("list"), { dialect: "offset" }).description("List API keys").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
6476
7653
  try {
6477
- const fmt = opts.output;
7654
+ const fmt = await resolveOutput(apiKeysList, opts);
7655
+ const page = resolveListParams(apiKeysList, opts, { dialect: "offset" });
6478
7656
  if (fmt === "pretty") renderRuntimeConfigHeader();
6479
7657
  const service = await createMgmtService();
7658
+ if (page.all) {
7659
+ const items = await service.listAllApiKeys({ limit: page.limit, max: page.max });
7660
+ emitList(apiKeysView, items, fmt, {
7661
+ page: { returned: items.length, total: items.length, all: true }
7662
+ });
7663
+ return;
7664
+ }
6480
7665
  const result = await service.listApiKeys({
6481
- limit: Number.parseInt(opts.limit, 10)
7666
+ limit: page.limit,
7667
+ offset: page.offset
7668
+ });
7669
+ emitList(apiKeysView, result.apiKeys, fmt, {
7670
+ page: { returned: result.apiKeys.length, next: result.nextOffset }
6482
7671
  });
6483
- renderApiKeyList(result.apiKeys, fmt);
6484
7672
  } catch (err) {
6485
7673
  fail(err);
6486
7674
  }
@@ -6649,10 +7837,10 @@ function registerRuntimeCommand(program) {
6649
7837
  ui.header("Bulk Scan Complete");
6650
7838
  ui.keyValue([
6651
7839
  ["Total", results.length],
6652
- ["Blocked", chalk10.red(String(blocked))],
6653
- ["Allowed", chalk10.green(String(allowed))],
6654
- ["Failed", chalk10.red(String(failed))],
6655
- ["Output", chalk10.cyan(outputPath)]
7840
+ ["Blocked", chalk11.red(String(blocked))],
7841
+ ["Allowed", chalk11.green(String(allowed))],
7842
+ ["Failed", chalk11.red(String(failed))],
7843
+ ["Output", chalk11.cyan(outputPath)]
6656
7844
  ]);
6657
7845
  if (failed > 0) {
6658
7846
  ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
@@ -6667,25 +7855,37 @@ function registerRuntimeCommand(program) {
6667
7855
  }
6668
7856
  });
6669
7857
  const customerApps = runtime.command("customer-apps").description("Manage AIRS customer apps");
6670
- customerApps.command("list").description("List customer apps").option("--limit <n>", "Max results", "100").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").action(async (opts) => {
7858
+ const customerAppsList = registerListFlags(customerApps.command("list"), { dialect: "offset" }).description("List customer apps").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
6671
7859
  try {
6672
- const fmt = opts.output;
7860
+ const fmt = await resolveOutput(customerAppsList, opts);
7861
+ const page = resolveListParams(customerAppsList, opts, { dialect: "offset" });
6673
7862
  if (fmt === "pretty") renderRuntimeConfigHeader();
6674
7863
  const service = await createMgmtService();
7864
+ if (page.all) {
7865
+ const items = await service.listAllCustomerApps({ limit: page.limit, max: page.max });
7866
+ emitList(customerAppsView, items, fmt, {
7867
+ page: { returned: items.length, total: items.length, all: true }
7868
+ });
7869
+ return;
7870
+ }
6675
7871
  const result = await service.listCustomerApps({
6676
- limit: Number.parseInt(opts.limit, 10)
7872
+ limit: page.limit,
7873
+ offset: page.offset
7874
+ });
7875
+ emitList(customerAppsView, result.apps, fmt, {
7876
+ page: { returned: result.apps.length, next: result.nextOffset }
6677
7877
  });
6678
- renderCustomerAppList(result.apps, fmt);
6679
7878
  } catch (err) {
6680
7879
  fail(err);
6681
7880
  }
6682
7881
  });
6683
- customerApps.command("get <appName>").description("Get customer app details").action(async (appName) => {
7882
+ const customerAppsGet = customerApps.command("get <appName>").description("Get customer app details").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (appName, opts) => {
6684
7883
  try {
6685
- renderRuntimeConfigHeader();
7884
+ const fmt = await resolveOutput(customerAppsGet, opts);
7885
+ if (fmt === "pretty") renderRuntimeConfigHeader();
6686
7886
  const service = await createMgmtService();
6687
7887
  const app = await service.getCustomerApp(appName);
6688
- renderCustomerAppDetail(app);
7888
+ emitDetail(customerAppsView, app, fmt);
6689
7889
  } catch (err) {
6690
7890
  fail(err);
6691
7891
  }
@@ -6767,7 +7967,7 @@ function registerRuntimeCommand(program) {
6767
7967
  }
6768
7968
  });
6769
7969
  const profiles = runtime.command("profiles").description("Manage AIRS security profiles");
6770
- profiles.command("list").description("List security profiles").option("--limit <n>", "Max results", "100").option("--offset <n>", "Starting offset", "0").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").addHelpText(
7970
+ const profilesList = registerListFlags(profiles.command("list"), { dialect: "offset" }).description("List security profiles").option("--all-versions", "Include every profile revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
6771
7971
  "after",
6772
7972
  examples(
6773
7973
  "airs runtime profiles list",
@@ -6776,49 +7976,55 @@ function registerRuntimeCommand(program) {
6776
7976
  )
6777
7977
  ).action(async (opts) => {
6778
7978
  try {
6779
- const fmt = opts.output;
6780
- if (!OUTPUT_FORMATS.includes(fmt)) {
6781
- usageError(`Invalid output format "${fmt}". Valid: ${OUTPUT_FORMATS.join(", ")}`);
6782
- }
7979
+ const fmt = await resolveOutput(profilesList, opts);
7980
+ const page = resolveListParams(profilesList, opts, { dialect: "offset" });
6783
7981
  if (fmt === "pretty") renderRuntimeConfigHeader();
6784
7982
  const service = await createMgmtService();
7983
+ if (page.all) {
7984
+ const items = await service.listAllProfiles({
7985
+ limit: page.limit,
7986
+ latest: !opts.allVersions,
7987
+ max: page.max
7988
+ });
7989
+ emitList(profilesView, items, fmt, {
7990
+ page: { returned: items.length, total: items.length, all: true }
7991
+ });
7992
+ return;
7993
+ }
6785
7994
  const result = await service.listProfiles({
6786
- limit: Number.parseInt(opts.limit, 10),
6787
- offset: Number.parseInt(opts.offset, 10)
7995
+ limit: page.limit,
7996
+ offset: page.offset,
7997
+ latest: !opts.allVersions
7998
+ });
7999
+ emitList(profilesView, result.profiles, fmt, {
8000
+ page: {
8001
+ returned: result.profiles.length,
8002
+ next: result.nextOffset
8003
+ }
6788
8004
  });
6789
- renderProfileList(result.profiles, fmt);
6790
- if (fmt === "pretty" && result.nextOffset != null) {
6791
- ui.dim(`Next offset: ${result.nextOffset}`);
6792
- }
6793
8005
  } catch (err) {
6794
8006
  fail(err);
6795
8007
  }
6796
8008
  });
6797
- profiles.command("get <nameOrId>").description("Get a security profile by name or UUID").option("--output <format>", "Output format: pretty, json, yaml", "pretty").action(async (nameOrId, opts) => {
8009
+ const profilesGet = profiles.command("get <nameOrId>").description("Get a security profile by name or UUID").option("--revision <n>", "Select an exact revision", Number).option("--all-versions", "Return every matching revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (nameOrId, opts) => {
6798
8010
  try {
6799
- const fmt = opts.output;
6800
- if (fmt !== "pretty" && fmt !== "json" && fmt !== "yaml") {
6801
- usageError(`Invalid output format "${fmt}". Valid: pretty, json, yaml`);
6802
- }
8011
+ const fmt = await resolveOutput(profilesGet, opts);
6803
8012
  if (fmt === "pretty") renderRuntimeConfigHeader();
6804
8013
  const service = await createMgmtService();
6805
8014
  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
6806
8015
  nameOrId
6807
8016
  );
6808
- const profile = isUuid ? await service.getProfile(nameOrId) : await service.getProfileByName(nameOrId);
6809
- if (fmt === "json") {
6810
- console.log(JSON.stringify(profile, null, 2));
6811
- } else if (fmt === "yaml") {
6812
- const lines = [`profileId: ${profile.profileId}`, `profileName: ${profile.profileName}`];
6813
- if (profile.revision != null) lines.push(`revision: ${profile.revision}`);
6814
- if (profile.active != null) lines.push(`active: ${profile.active}`);
6815
- if (profile.createdBy) lines.push(`createdBy: ${profile.createdBy}`);
6816
- if (profile.updatedBy) lines.push(`updatedBy: ${profile.updatedBy}`);
6817
- if (profile.lastModifiedTs) lines.push(`lastModifiedTs: ${profile.lastModifiedTs}`);
6818
- if (profile.policy) lines.push(`policy: ${JSON.stringify(profile.policy, null, 2)}`);
6819
- console.log(lines.join("\n"));
8017
+ if (opts.revision !== void 0 || opts.allVersions) {
8018
+ const profiles2 = (await service.listAllProfiles({ latest: false })).filter(
8019
+ (profile) => isUuid ? profile.profileId === nameOrId : profile.profileName === nameOrId
8020
+ );
8021
+ const selected = opts.revision === void 0 ? profiles2 : profiles2.filter((profile) => profile.revision === opts.revision);
8022
+ if (selected.length === 0) throw new Error(`Profile ${nameOrId} not found`);
8023
+ if (opts.allVersions) emitList(profilesView, selected, fmt);
8024
+ else emitDetail(profilesView, selected[0], fmt);
6820
8025
  } else {
6821
- renderProfileDetail(profile);
8026
+ const profile = isUuid ? await service.getProfile(nameOrId) : await service.getProfileByName(nameOrId);
8027
+ emitDetail(profilesView, profile, fmt);
6822
8028
  }
6823
8029
  } catch (err) {
6824
8030
  fail(err);
@@ -7061,10 +8267,10 @@ function registerRuntimeCommand(program) {
7061
8267
  ui.header("Resume Poll Complete");
7062
8268
  ui.keyValue([
7063
8269
  ["Total", results.length],
7064
- ["Blocked", chalk10.red(String(blocked))],
7065
- ["Allowed", chalk10.green(String(allowed))],
7066
- ["Failed", chalk10.red(String(failed))],
7067
- ["Output", chalk10.cyan(outputPath)]
8270
+ ["Blocked", chalk11.red(String(blocked))],
8271
+ ["Allowed", chalk11.green(String(allowed))],
8272
+ ["Failed", chalk11.red(String(failed))],
8273
+ ["Output", chalk11.cyan(outputPath)]
7068
8274
  ]);
7069
8275
  if (failed > 0) {
7070
8276
  ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
@@ -7143,52 +8349,47 @@ function registerRuntimeCommand(program) {
7143
8349
  }
7144
8350
  });
7145
8351
  registerEvalCommand(topics);
7146
- topics.command("get <nameOrId>").description("Get a custom topic by name or UUID").option("--output <format>", "Output format: pretty, json, yaml", "pretty").action(async (nameOrId, opts) => {
8352
+ const topicsGet = topics.command("get <nameOrId>").description("Get a custom topic by name or UUID").option("--revision <n>", "Select an exact revision", Number).option("--all-versions", "Return every matching revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (nameOrId, opts) => {
7147
8353
  try {
7148
- const fmt = opts.output;
7149
- if (fmt !== "pretty" && fmt !== "json" && fmt !== "yaml") {
7150
- usageError(`Invalid output format "${fmt}". Valid: pretty, json, yaml`);
7151
- }
8354
+ const fmt = await resolveOutput(topicsGet, opts);
7152
8355
  if (fmt === "pretty") renderRuntimeConfigHeader();
7153
8356
  const service = await createMgmtService();
7154
8357
  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
7155
8358
  nameOrId
7156
8359
  );
7157
- const topic = isUuid ? await service.getTopic(nameOrId) : await service.getTopicByName(nameOrId);
7158
- if (fmt === "json") {
7159
- console.log(JSON.stringify(topic, null, 2));
7160
- } else if (fmt === "yaml") {
7161
- const lines = [`topic_id: ${topic.topic_id}`, `topic_name: ${topic.topic_name}`];
7162
- if (topic.revision != null) lines.push(`revision: ${topic.revision}`);
7163
- if (topic.description) lines.push(`description: ${topic.description}`);
7164
- if (topic.examples?.length) {
7165
- lines.push("examples:");
7166
- for (const ex of topic.examples) lines.push(` - ${ex}`);
7167
- }
7168
- if (topic.created_by) lines.push(`created_by: ${topic.created_by}`);
7169
- if (topic.updated_by) lines.push(`updated_by: ${topic.updated_by}`);
7170
- if (topic.last_modified_ts) lines.push(`last_modified_ts: ${topic.last_modified_ts}`);
7171
- console.log(lines.join("\n"));
8360
+ if (opts.revision !== void 0 || opts.allVersions) {
8361
+ const topics2 = (await service.listTopics()).filter(
8362
+ (topic) => isUuid ? topic.topic_id === nameOrId : topic.topic_name === nameOrId
8363
+ );
8364
+ const selected = opts.revision === void 0 ? topics2 : topics2.filter((topic) => topic.revision === opts.revision);
8365
+ if (selected.length === 0) throw new Error(`Topic ${nameOrId} not found`);
8366
+ if (opts.allVersions) emitList(topicsView, selected, fmt);
8367
+ else emitDetail(topicsView, selected[0], fmt);
7172
8368
  } else {
7173
- renderTopicDetail(topic);
8369
+ const topic = isUuid ? await service.getTopic(nameOrId) : await service.getTopicByName(nameOrId);
8370
+ emitDetail(topicsView, topic, fmt);
7174
8371
  }
7175
8372
  } catch (err) {
7176
8373
  fail(err);
7177
8374
  }
7178
8375
  });
7179
- topics.command("list").description("List custom topics").option("--limit <n>", "Max results", "100").option("--offset <n>", "Starting offset", "0").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").action(async (opts) => {
8376
+ const topicsList = registerListFlags(topics.command("list"), { dialect: "offset" }).description("List custom topics").option("--all-versions", "Include every topic revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
7180
8377
  try {
7181
- const fmt = opts.output;
8378
+ const fmt = await resolveOutput(topicsList, opts);
8379
+ const params = resolveListParams(topicsList, opts, { dialect: "offset" });
7182
8380
  if (fmt === "pretty") renderRuntimeConfigHeader();
7183
8381
  const service = await createMgmtService();
7184
- const allTopics = await service.listTopics();
7185
- const offset = Number.parseInt(opts.offset, 10);
7186
- const limit = Number.parseInt(opts.limit, 10);
7187
- const page = allTopics.slice(offset, offset + limit);
7188
- renderTopicList(page, fmt);
7189
- if (fmt === "pretty" && offset + limit < allTopics.length) {
7190
- ui.dim(`Showing ${page.length} of ${allTopics.length} topics`);
7191
- }
8382
+ const allTopics = opts.allVersions ? await service.listTopics() : await service.listLatestTopics(
8383
+ params.all ? { offset: 0, limit: params.max === 0 ? 1e4 : params.max } : { offset: params.offset, limit: params.limit }
8384
+ );
8385
+ const page = opts.allVersions && !params.all ? allTopics.slice(params.offset, params.offset + params.limit) : allTopics;
8386
+ emitList(topicsView, page, fmt, {
8387
+ page: params.all ? { returned: page.length, total: page.length, all: true } : {
8388
+ returned: page.length,
8389
+ total: opts.allVersions ? allTopics.length : void 0,
8390
+ next: opts.allVersions && params.offset + params.limit < allTopics.length ? params.offset + params.limit : void 0
8391
+ }
8392
+ });
7192
8393
  } catch (err) {
7193
8394
  fail(err);
7194
8395
  }
@@ -7397,16 +8598,45 @@ function applyListDeleteAliases(cmd) {
7397
8598
  applyListDeleteAliases(sub);
7398
8599
  }
7399
8600
  }
8601
+ function applyReadContractFlags(cmd) {
8602
+ for (const sub of cmd.commands) {
8603
+ const flags = () => sub.options.map((option) => option.long);
8604
+ if ((sub.name() === "list" || sub.name() === "get") && !flags().includes("--output")) {
8605
+ sub.option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml");
8606
+ }
8607
+ if (sub.name() === "list" && (flags().includes("--limit") || flags().includes("--offset"))) {
8608
+ if (!flags().includes("--limit")) sub.option("--limit <n>", "Items per page", Number, 50);
8609
+ if (!flags().includes("--offset")) sub.option("--offset <n>", "Item offset", Number, 0);
8610
+ if (!flags().includes("--all")) sub.option("--all", "Walk all pages");
8611
+ if (!flags().includes("--max")) {
8612
+ sub.option("--max <n>", "Maximum items with --all; 0 removes the cap", Number, 1e4);
8613
+ }
8614
+ }
8615
+ applyReadContractFlags(sub);
8616
+ }
8617
+ }
8618
+ function applySortedHelp(cmd) {
8619
+ cmd.configureHelp({ sortOptions: true, sortSubcommands: true });
8620
+ for (const sub of cmd.commands) applySortedHelp(sub);
8621
+ }
7400
8622
  function buildProgram() {
7401
8623
  const here = dirname4(fileURLToPath(import.meta.url));
7402
8624
  const pkg = JSON.parse(readFileSync4(join4(here, "../../package.json"), "utf-8"));
7403
8625
  const program = new Command();
7404
8626
  program.name("airs").description(
7405
8627
  "CLI and library for Palo Alto Prisma AIRS \u2014 guardrail refinement, AI red teaming, model security scanning, profile audits"
7406
- ).version(pkg.version).option("--debug", "Log all AIRS/SCM API requests and responses to a JSONL file").option("--quiet", "Suppress status and decorative output (data and errors still print)");
7407
- program.hook("preAction", (_thisCommand, actionCommand) => {
8628
+ ).version(pkg.version).option("--debug", "Log all AIRS/SCM API requests and responses to a JSONL file").option("--output <format>", "Default output format for read commands").option("--quiet", "Suppress status and decorative output (data and errors still print)");
8629
+ program.hook("preAction", async (_thisCommand, actionCommand) => {
7408
8630
  const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
7409
8631
  setQuiet(Boolean(root.quiet));
8632
+ if ((actionCommand.name() === "list" || actionCommand.name() === "get") && actionCommand.options.some((option) => option.long === "--output")) {
8633
+ try {
8634
+ const format = await resolveOutput(actionCommand, actionCommand.opts());
8635
+ actionCommand.setOptionValueWithSource("output", format, "implied");
8636
+ } catch (error) {
8637
+ fail(error);
8638
+ }
8639
+ }
7410
8640
  if (root.debug) {
7411
8641
  const logPath = join4(homedir(), ".prisma-airs", `debug-api-${Date.now()}.jsonl`);
7412
8642
  installDebugLogger(logPath);
@@ -7416,10 +8646,13 @@ function buildProgram() {
7416
8646
  registerRuntimeCommand(program);
7417
8647
  registerRedteamCommand(program);
7418
8648
  registerModelSecurityCommand(program);
8649
+ registerAiGatewayCommand(program);
7419
8650
  registerConfigCommand(program);
7420
8651
  registerDoctorCommand(program);
7421
8652
  registerCompletionCommand(program);
7422
8653
  applyListDeleteAliases(program);
8654
+ applyReadContractFlags(program);
8655
+ applySortedHelp(program);
7423
8656
  return program;
7424
8657
  }
7425
8658