@cdot65/prisma-airs-cli 5.1.0 → 5.3.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
@@ -9,6 +9,7 @@ import {
9
9
  SdkPromptSetService,
10
10
  SdkRedTeamService,
11
11
  SdkRuntimeService,
12
+ collectGatewayEnvironmentReport,
12
13
  collectRedTeamEnvironmentReport,
13
14
  collectRuntimeDailyReport,
14
15
  computeMetrics,
@@ -17,6 +18,8 @@ import {
17
18
  loadConfig,
18
19
  readBackupDir,
19
20
  readBackupFile,
21
+ renderEnvironmentReportHtml,
22
+ renderEnvironmentReportMarkdown,
20
23
  renderRedTeamReportHtml,
21
24
  renderRedTeamReportMarkdown,
22
25
  renderRuntimeReportHtml,
@@ -27,7 +30,7 @@ import {
27
30
  validateTopic,
28
31
  writeBackupFile,
29
32
  writeReportFile
30
- } from "../chunk-DR6MZG4E.js";
33
+ } from "../chunk-7Z76VCB3.js";
31
34
 
32
35
  // src/cli/index.ts
33
36
  import "dotenv/config";
@@ -46,7 +49,7 @@ function installProcessGuards() {
46
49
  }
47
50
 
48
51
  // src/cli/program.ts
49
- import { randomUUID as randomUUID7 } from "crypto";
52
+ import { randomUUID as randomUUID8 } from "crypto";
50
53
  import { readFileSync as readFileSync4 } from "fs";
51
54
  import { dirname as dirname4, join as join4 } from "path";
52
55
  import { fileURLToPath } from "url";
@@ -3055,8 +3058,8 @@ function registerAiGatewayInference(parent) {
3055
3058
  process.once("SIGINT", interrupt);
3056
3059
  process.once("SIGTERM", interrupt);
3057
3060
  process.stdout.on("error", outputError);
3058
- const write = (text2) => new Promise((resolve5, reject) => {
3059
- process.stdout.write(text2, (error) => error ? reject(error) : resolve5());
3061
+ const write = (text2) => new Promise((resolve6, reject) => {
3062
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve6());
3060
3063
  });
3061
3064
  const requestOptions = { signal: controller.signal };
3062
3065
  const result = kind === "chat" ? await client.createChatCompletion(
@@ -4341,6 +4344,20 @@ function registerOrganisations(root) {
4341
4344
  const self = showHelpOnEmpty(
4342
4345
  group.command("self").description("Manage the current organisation")
4343
4346
  );
4347
+ const info = addReadOutput(
4348
+ group.command("info").description("Get organisation dashboard info (settings redacted)").requiredOption("--tsg-id <id>", "Tenant service group ID")
4349
+ );
4350
+ info.action(
4351
+ (opts) => runDetail(
4352
+ info,
4353
+ opts,
4354
+ async (client) => redactAIGatewaySecrets(
4355
+ "organisations.getInfo",
4356
+ await client.organisations.getInfo(opts.tsgId),
4357
+ "response"
4358
+ )
4359
+ )
4360
+ );
4344
4361
  const selfGet = addReadOutput(self.command("get").description("Get the current organisation"));
4345
4362
  selfGet.action((opts) => runDetail(selfGet, opts, (client) => client.organisations.getSelf()));
4346
4363
  const selfUpdate = addWriteOutput(
@@ -4431,6 +4448,10 @@ function registerAiGatewayInventory(root) {
4431
4448
  { option: "name", path: "name" },
4432
4449
  { option: "workspace", path: "workspace_id" }
4433
4450
  ];
4451
+ const catalog = addReadOutput(
4452
+ guardrails.command("catalog").description("Get available guardrail evaluator schemas (not enabled policies)")
4453
+ );
4454
+ catalog.action((opts) => runDetail(catalog, opts, (client) => client.guardrails.getCatalog()));
4434
4455
  const addGuardrailFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name").option("--workspace <uuid>", "Workspace UUID");
4435
4456
  const guardrailUpdateFields = guardrailFields.filter((field) => field.option !== "workspace");
4436
4457
  const addGuardrailUpdateFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name");
@@ -4501,7 +4522,8 @@ function registerAiGatewayInventory(root) {
4501
4522
  // src/cli/commands/aigateway/telemetry.ts
4502
4523
  import {
4503
4524
  AI_GW_GROUP_COLUMNS,
4504
- AI_GW_GROUP_DIMENSIONS
4525
+ AI_GW_GROUP_DIMENSIONS,
4526
+ redactAIGatewaySecrets as redactAIGatewaySecrets2
4505
4527
  } from "@cdot65/prisma-airs-sdk";
4506
4528
 
4507
4529
  // src/cli/commands/aigateway/telemetry-filters.ts
@@ -4586,7 +4608,10 @@ function parseNamedDate2(value, flag) {
4586
4608
  function registerMetric(telemetry, name, description, method) {
4587
4609
  const command = addWindowOptions(telemetry.command(name).description(description));
4588
4610
  command.action(
4589
- (opts) => runDetail(command, opts, (client) => client.telemetry[method](windowFrom(opts)))
4611
+ (opts) => runDetail(command, opts, async (client) => {
4612
+ const result = await client.telemetry[method](windowFrom(opts));
4613
+ return method === "filterBoundaries" ? redactAIGatewaySecrets2("telemetry.filterBoundaries", result, "response") : result;
4614
+ })
4590
4615
  );
4591
4616
  }
4592
4617
  function registerFilteredMetric(telemetry, name, description) {
@@ -4634,6 +4659,24 @@ function registerAiGatewayTelemetryReads(telemetry) {
4634
4659
  );
4635
4660
  registerMetric(telemetry, "error-trends", "Get error trends", "errorTrends");
4636
4661
  registerMetric(telemetry, "errors", "Get error count", "errors");
4662
+ registerMetric(
4663
+ telemetry,
4664
+ "error-category-trends",
4665
+ "Get errors by HTTP status",
4666
+ "errorCategoryTrends"
4667
+ );
4668
+ registerMetric(
4669
+ telemetry,
4670
+ "grouped-errors",
4671
+ "Get HTTP error counts by time bucket",
4672
+ "groupedErrors"
4673
+ );
4674
+ registerMetric(
4675
+ telemetry,
4676
+ "filter-boundaries",
4677
+ "Get available analytics filters and bounds",
4678
+ "filterBoundaries"
4679
+ );
4637
4680
  const feedback = showHelpOnEmpty(
4638
4681
  telemetry.command("feedback").description("Inspect model feedback telemetry")
4639
4682
  );
@@ -4676,7 +4719,7 @@ function registerAiGatewayTelemetryReads(telemetry) {
4676
4719
  registerFilteredMetric(telemetry, "latency", "Get latency telemetry");
4677
4720
  const logs = showHelpOnEmpty(telemetry.command("logs").description("Inspect request logs"));
4678
4721
  const logsList = addWindowOptions(
4679
- logs.command("list").description("List request logs").option("--page-size <n>", "Rows per response", "50").option("--status-code <code>", "Filter by HTTP status").option("--trace-id <id>", "Return one trace id")
4722
+ logs.command("list").description("List request logs").option("--page-size <n>", "Rows per response", "50").option("--current-page <n>", "Zero-based transaction page", "0").option("--status-code <code>", "Filter by HTTP status").option("--trace-id <id>", "Return one trace id")
4680
4723
  );
4681
4724
  logsList.action(
4682
4725
  (opts) => runDetail(
@@ -4685,6 +4728,7 @@ function registerAiGatewayTelemetryReads(telemetry) {
4685
4728
  (client) => client.telemetry.logs({
4686
4729
  ...windowFrom(opts),
4687
4730
  pageSize: parsePositiveInteger2(opts.pageSize ?? "50", "--page-size"),
4731
+ currentPage: parseNonnegativePage(opts.currentPage ?? "0"),
4688
4732
  ...opts.statusCode ? { statusCode: parsePositiveInteger2(opts.statusCode, "--status-code") } : {},
4689
4733
  ...opts.traceId ? { traceId: opts.traceId } : {}
4690
4734
  })
@@ -4696,6 +4740,99 @@ function registerAiGatewayTelemetryReads(telemetry) {
4696
4740
  registerMetric(telemetry, "user-trends", "Get user trends", "userTrends");
4697
4741
  registerMetric(telemetry, "users", "Get unique-user count", "users");
4698
4742
  }
4743
+ function parseNonnegativePage(value) {
4744
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
4745
+ throw new CliUsageError("--current-page must be a nonnegative safe integer");
4746
+ return Number(value);
4747
+ }
4748
+
4749
+ // src/cli/commands/aigateway-report.ts
4750
+ import { lstat } from "fs/promises";
4751
+ import { resolve } from "path";
4752
+ import { AIGatewayClient as AIGatewayClient3 } from "@cdot65/prisma-airs-sdk";
4753
+ import { z } from "zod";
4754
+ import { randomUUID } from "crypto";
4755
+ function registerAiGatewayReportCommand(aigateway) {
4756
+ const command = aigateway.command("report").alias("dashboard").description("Generate an AI Gateway environment report (read-only)").requiredOption("--workspace <ref>", "Workspace slug, UUID, or unique display name").option("--days <n>", "Rolling window in days (default: 1)", "1").option("--start <iso>", "Explicit ISO-8601 start; requires --end").option("--end <iso>", "Explicit ISO-8601 end; requires --start").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
4757
+ "--output-file <path>",
4758
+ "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
4759
+ ).option("--title <text>", "Report title", "AI Gateway environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (50 transactions/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
4760
+ "after",
4761
+ examples(
4762
+ "airs aigateway report --workspace ws-develo-71f8d8 --output-file ./airs-daily.html",
4763
+ "airs aigateway report --workspace ws-develo-71f8d8 --output markdown --output-file ./airs-daily.md",
4764
+ "airs aigateway report --workspace ws-develo-71f8d8",
4765
+ "airs aigateway report --workspace ws-develo-71f8d8 --strict --max-pages 20 --output-file - > daily.html"
4766
+ )
4767
+ ).action(async (opts) => {
4768
+ const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
4769
+ if (format !== "html" && format !== "markdown")
4770
+ usageError("Report output must be html or markdown");
4771
+ if (typeof opts.workspace !== "string" || !opts.workspace.trim())
4772
+ usageError("--workspace cannot be empty");
4773
+ if (!/^[1-9]\d*$/.test(opts.days) || !Number.isSafeInteger(Number(opts.days)))
4774
+ usageError("--days must be a positive integer");
4775
+ if (Boolean(opts.start) !== Boolean(opts.end))
4776
+ usageError("--start and --end must be supplied together");
4777
+ if (opts.start && command.getOptionValueSource("days") !== "default")
4778
+ usageError("Use --days or --start/--end, not both");
4779
+ const end = opts.end ? new Date(opts.end) : /* @__PURE__ */ new Date();
4780
+ const start = opts.start ? new Date(opts.start) : new Date(end.getTime() - Number(opts.days) * 864e5);
4781
+ const iso = z.string().datetime({ offset: true });
4782
+ if (opts.start && (!iso.safeParse(opts.start).success || !iso.safeParse(opts.end).success) || !Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || start >= end)
4783
+ usageError("Provide a valid increasing ISO-8601 window with timezone");
4784
+ const maxPages = Number(opts.maxPages);
4785
+ if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages) || maxPages > 100)
4786
+ usageError("--max-pages must be an integer from 1 to 100");
4787
+ if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
4788
+ usageError("--title must contain 1\u2013240 characters");
4789
+ if (opts.outputFile !== void 0 && !opts.outputFile.trim())
4790
+ usageError("--output-file cannot be empty");
4791
+ const defaultName = `airs-aigateway-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
4792
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve(opts.outputFile ?? defaultName);
4793
+ try {
4794
+ if (outputPath) {
4795
+ const existing = await lstat(outputPath).catch((error) => {
4796
+ if (error.code !== "ENOENT") throw error;
4797
+ return void 0;
4798
+ });
4799
+ if (existing) throw new Error("Output already exists");
4800
+ }
4801
+ const config = await loadConfig();
4802
+ const client = new AIGatewayClient3({
4803
+ ...aiGatewayClientOptions(config),
4804
+ numRetries: 0
4805
+ });
4806
+ ui.status("Collecting AI Gateway statistics and current configuration (read-only)...");
4807
+ const report = await collectGatewayEnvironmentReport(client, {
4808
+ title: opts.title,
4809
+ maxPages,
4810
+ workspace: opts.workspace,
4811
+ tsgId: config.mgmtTsgId ?? process.env.PANW_AI_GW_TSG_ID ?? "",
4812
+ start,
4813
+ end
4814
+ });
4815
+ const rendered = format === "html" ? renderEnvironmentReportHtml(report) : renderEnvironmentReportMarkdown(report);
4816
+ if (outputPath) {
4817
+ await writeReportFile(outputPath, rendered);
4818
+ ui.status(`Report \u2192 ${outputPath.replace(/[\p{Cc}\p{Cf}]/gu, "_")} (mode 0600)`);
4819
+ } else {
4820
+ await new Promise(
4821
+ (done, reject) => process.stdout.write(rendered, (error) => error ? reject(error) : done())
4822
+ );
4823
+ }
4824
+ const incomplete = report.sources.filter((source) => source.status !== "complete").length;
4825
+ if (incomplete)
4826
+ ui.status(`${incomplete} source(s) incomplete; review the evidence section.`);
4827
+ if (report.health === "unknown" || opts.strict && incomplete > 0) process.exitCode = 1;
4828
+ } catch {
4829
+ ui.error(
4830
+ "Report generation failed. Verify Management API configuration and a writable, new output path. Existing files are never overwritten."
4831
+ );
4832
+ process.exitCode = 1;
4833
+ }
4834
+ });
4835
+ }
4699
4836
 
4700
4837
  // src/cli/commands/aigateway.ts
4701
4838
  async function createService() {
@@ -4759,6 +4896,7 @@ function scopeNameLooksUnrelated(name, scopeName) {
4759
4896
  function registerAiGatewayCommand(program) {
4760
4897
  const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
4761
4898
  registerAiGatewayInventory(aigateway);
4899
+ registerAiGatewayReportCommand(aigateway);
4762
4900
  registerAiGatewayInference(aigateway);
4763
4901
  const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
4764
4902
  const workspaceList = workspace.command("list").description("List workspaces (default: active workspaces you are scoped to)").option("--plane <plane>", "Plane to read from: data (scoped) or admin (whole tenant)").option("--status <status>", "Filter by lifecycle state: active or archived").option("--all", "Merge active + archived admin-plane reads (whole tenant, both states)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
@@ -5201,7 +5339,7 @@ function registerConfigCommand(program) {
5201
5339
  }
5202
5340
 
5203
5341
  // src/cli/commands/doctor.ts
5204
- import { randomUUID } from "crypto";
5342
+ import { randomUUID as randomUUID2 } from "crypto";
5205
5343
  import { readFile as readFile4 } from "fs/promises";
5206
5344
  import { init, Scanner } from "@cdot65/prisma-airs-sdk";
5207
5345
  var DOCTOR_TIMEOUT_MS = 5e3;
@@ -5299,8 +5437,8 @@ async function withTimeout(promise, ms) {
5299
5437
  try {
5300
5438
  return await Promise.race([
5301
5439
  promise,
5302
- new Promise((resolve5) => {
5303
- timer = setTimeout(() => resolve5(TIMED_OUT), ms);
5440
+ new Promise((resolve6) => {
5441
+ timer = setTimeout(() => resolve6(TIMED_OUT), ms);
5304
5442
  })
5305
5443
  ]);
5306
5444
  } finally {
@@ -5455,7 +5593,7 @@ async function defaultScannerProbe() {
5455
5593
  const config = await loadConfig();
5456
5594
  init(runtimeInitOptions(config));
5457
5595
  const scanner = new Scanner();
5458
- return scanner.queryByScanIds([randomUUID()]);
5596
+ return scanner.queryByScanIds([randomUUID2()]);
5459
5597
  }
5460
5598
  async function defaultMgmtProbe() {
5461
5599
  const config = await loadConfig();
@@ -5570,12 +5708,12 @@ function hasBin(bin) {
5570
5708
  }
5571
5709
  }
5572
5710
  function run(bin, args, label) {
5573
- return new Promise((resolve5, reject) => {
5711
+ return new Promise((resolve6, reject) => {
5574
5712
  const child = spawn(bin, args, { stdio: "inherit" });
5575
5713
  child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
5576
5714
  child.on("close", (code) => {
5577
5715
  if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
5578
- else resolve5();
5716
+ else resolve6();
5579
5717
  });
5580
5718
  });
5581
5719
  }
@@ -6153,24 +6291,33 @@ async function backupTargets(opts) {
6153
6291
  return results;
6154
6292
  }
6155
6293
 
6156
- // src/cli/commands/redteam-dashboard.ts
6157
- import { lstat } from "fs/promises";
6158
- import { resolve } from "path";
6294
+ // src/cli/commands/redteam-report.ts
6295
+ import { lstat as lstat2 } from "fs/promises";
6296
+ import { resolve as resolve2 } from "path";
6159
6297
  import { RedTeamClient } from "@cdot65/prisma-airs-sdk";
6160
- import { randomUUID as randomUUID2 } from "crypto";
6161
- function registerRedTeamDashboardCommand(redteam) {
6162
- const command = redteam.command("dashboard").description("Generate a Red Team environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
6298
+ import { randomUUID as randomUUID3 } from "crypto";
6299
+ function registerRedTeamReportCommand(redteam, scanReport) {
6300
+ const command = redteam.command("report [jobId]").alias("dashboard").description("Generate an environment report, or view an individual scan report by job ID").option("--attacks", "Individual scan only: include attack list", false).option("--severity <level>", "Individual scan only: filter attacks by severity").option("--limit <n>", "Individual scan only: max attacks to show", "20").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
6163
6301
  "--output-file <path>",
6164
6302
  "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
6165
6303
  ).option("--title <text>", "Report title", "Red Team environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (15 records/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
6166
6304
  "after",
6167
6305
  examples(
6168
- "airs redteam dashboard --output-file ./airs-daily.html",
6169
- "airs redteam dashboard --output markdown --output-file ./airs-daily.md",
6170
- "airs redteam dashboard",
6171
- "airs redteam dashboard --strict --max-pages 20 --output-file - > daily.html"
6306
+ "airs redteam report --output-file ./airs-daily.html",
6307
+ "airs redteam report --output markdown --output-file ./airs-daily.md",
6308
+ "airs redteam report",
6309
+ "airs redteam report --strict --max-pages 20 --output-file - > daily.html"
6172
6310
  )
6173
- ).action(async (opts) => {
6311
+ ).action(async (jobId, opts) => {
6312
+ const explicit = (name) => command.getOptionValueSource(name) === "cli";
6313
+ if (jobId !== void 0) {
6314
+ if (["output", "outputFile", "title", "maxPages", "strict"].some(explicit) || ["html", "markdown"].includes(command.parent?.parent?.opts().output))
6315
+ usageError("Environment report options cannot be combined with a scan job ID");
6316
+ await scanReport(jobId, opts);
6317
+ return;
6318
+ }
6319
+ if (["attacks", "severity", "limit"].some(explicit))
6320
+ usageError("--attacks, --severity and --limit require a scan job ID");
6174
6321
  const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
6175
6322
  if (format !== "html" && format !== "markdown")
6176
6323
  usageError("Report output must be html or markdown");
@@ -6181,11 +6328,11 @@ function registerRedTeamDashboardCommand(redteam) {
6181
6328
  usageError("--title must contain 1\u2013240 characters");
6182
6329
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
6183
6330
  usageError("--output-file cannot be empty");
6184
- const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID2().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
6185
- const outputPath = opts.outputFile === "-" ? void 0 : resolve(opts.outputFile ?? defaultName);
6331
+ const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID3().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
6332
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve2(opts.outputFile ?? defaultName);
6186
6333
  try {
6187
6334
  if (outputPath) {
6188
- const existing = await lstat(outputPath).catch((error) => {
6335
+ const existing = await lstat2(outputPath).catch((error) => {
6189
6336
  if (error.code !== "ENOENT") throw error;
6190
6337
  return void 0;
6191
6338
  });
@@ -6465,7 +6612,6 @@ function parseAdapterVariables(input) {
6465
6612
  }
6466
6613
  function registerRedteamCommand(program) {
6467
6614
  const redteam = program.command("redteam").description("AI Red Team scan operations");
6468
- registerRedTeamDashboardCommand(redteam);
6469
6615
  redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
6470
6616
  try {
6471
6617
  renderRedteamHeader();
@@ -6848,7 +6994,7 @@ function registerRedteamCommand(program) {
6848
6994
  fail(err);
6849
6995
  }
6850
6996
  });
6851
- 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) => {
6997
+ registerRedTeamReportCommand(redteam, async (jobId, opts) => {
6852
6998
  try {
6853
6999
  renderRedteamHeader();
6854
7000
  const service = await createService3();
@@ -7459,7 +7605,7 @@ function registerRedteamCommand(program) {
7459
7605
  }
7460
7606
 
7461
7607
  // src/cli/commands/runtime.ts
7462
- import { randomUUID as randomUUID6 } from "crypto";
7608
+ import { randomUUID as randomUUID7 } from "crypto";
7463
7609
  import * as fs5 from "fs";
7464
7610
  import { readFile as readFile10 } from "fs/promises";
7465
7611
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
@@ -7707,7 +7853,7 @@ function mergeProfilePolicy(existing, overrides) {
7707
7853
  }
7708
7854
 
7709
7855
  // src/cli/bulk-scan-lock.ts
7710
- import { randomUUID as randomUUID3 } from "crypto";
7856
+ import { randomUUID as randomUUID4 } from "crypto";
7711
7857
  import * as fs3 from "fs/promises";
7712
7858
  function errorCode(error) {
7713
7859
  return error?.code;
@@ -7738,7 +7884,7 @@ function processIsAlive(pid) {
7738
7884
  }
7739
7885
  }
7740
7886
  async function installLock(lockPath, record) {
7741
- const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID3()}`;
7887
+ const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID4()}`;
7742
7888
  try {
7743
7889
  await fs3.writeFile(candidate, JSON.stringify(record), {
7744
7890
  encoding: "utf-8",
@@ -7756,7 +7902,7 @@ async function acquireBulkScanLock(statePath) {
7756
7902
  version: 1,
7757
7903
  pid: process.pid,
7758
7904
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7759
- token: randomUUID3()
7905
+ token: randomUUID4()
7760
7906
  };
7761
7907
  for (let attempt = 0; attempt < 2; attempt++) {
7762
7908
  try {
@@ -7792,32 +7938,32 @@ async function acquireBulkScanLock(statePath) {
7792
7938
  }
7793
7939
 
7794
7940
  // src/cli/bulk-scan-state.ts
7795
- import { randomUUID as randomUUID4 } from "crypto";
7941
+ import { randomUUID as randomUUID5 } from "crypto";
7796
7942
  import * as fs4 from "fs/promises";
7797
7943
  import * as path2 from "path";
7798
- import { z } from "zod";
7799
- var BulkScanResultSchema = z.object({
7800
- index: z.number().int().nonnegative(),
7801
- reqId: z.number().int().nonnegative(),
7802
- prompt: z.string(),
7803
- response: z.string().optional(),
7804
- scanId: z.string(),
7805
- reportId: z.string(),
7806
- action: z.enum(["allow", "block", "failed"]),
7807
- category: z.string(),
7808
- triggered: z.boolean(),
7809
- detections: z.record(z.boolean()),
7810
- error: z.string().optional()
7944
+ import { z as z2 } from "zod";
7945
+ var BulkScanResultSchema = z2.object({
7946
+ index: z2.number().int().nonnegative(),
7947
+ reqId: z2.number().int().nonnegative(),
7948
+ prompt: z2.string(),
7949
+ response: z2.string().optional(),
7950
+ scanId: z2.string(),
7951
+ reportId: z2.string(),
7952
+ action: z2.enum(["allow", "block", "failed"]),
7953
+ category: z2.string(),
7954
+ triggered: z2.boolean(),
7955
+ detections: z2.record(z2.boolean()),
7956
+ error: z2.string().optional()
7811
7957
  });
7812
- var BulkScanItemSchema = z.object({
7813
- index: z.number().int().nonnegative(),
7814
- reqId: z.number().int().nonnegative(),
7815
- prompt: z.string(),
7816
- status: z.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
7817
- scanId: z.string().min(1).optional(),
7818
- receiptReportId: z.string().optional(),
7958
+ var BulkScanItemSchema = z2.object({
7959
+ index: z2.number().int().nonnegative(),
7960
+ reqId: z2.number().int().nonnegative(),
7961
+ prompt: z2.string(),
7962
+ status: z2.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
7963
+ scanId: z2.string().min(1).optional(),
7964
+ receiptReportId: z2.string().optional(),
7819
7965
  result: BulkScanResultSchema.optional(),
7820
- error: z.string().optional()
7966
+ error: z2.string().optional()
7821
7967
  }).superRefine((item, ctx) => {
7822
7968
  if (item.reqId !== item.index) {
7823
7969
  ctx.addIssue({ code: "custom", message: "reqId must match the stable input index" });
@@ -7855,15 +8001,15 @@ var BulkScanItemSchema = z.object({
7855
8001
  }
7856
8002
  }
7857
8003
  });
7858
- var BulkScanStateSchema = z.object({
7859
- version: z.literal(2),
7860
- profile: z.string().min(1),
7861
- sessionId: z.string().optional(),
7862
- outputFile: z.string().min(1),
7863
- batchSize: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
7864
- createdAt: z.string().datetime(),
7865
- updatedAt: z.string().datetime(),
7866
- items: z.array(BulkScanItemSchema).min(1)
8004
+ var BulkScanStateSchema = z2.object({
8005
+ version: z2.literal(2),
8006
+ profile: z2.string().min(1),
8007
+ sessionId: z2.string().optional(),
8008
+ outputFile: z2.string().min(1),
8009
+ batchSize: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8010
+ createdAt: z2.string().datetime(),
8011
+ updatedAt: z2.string().datetime(),
8012
+ items: z2.array(BulkScanItemSchema).min(1)
7867
8013
  }).superRefine((state, ctx) => {
7868
8014
  const indices = /* @__PURE__ */ new Set();
7869
8015
  for (const [position, item] of state.items.entries()) {
@@ -7889,8 +8035,8 @@ async function saveBulkScanState(state, dir, filePath) {
7889
8035
  }
7890
8036
  await fs4.mkdir(dir, { recursive: true, mode: 448 });
7891
8037
  if (!filePath) await fs4.chmod(dir, 448);
7892
- const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID4()}.bulk-scan.json`);
7893
- const temporary = `${target}.tmp-${process.pid}-${randomUUID4()}`;
8038
+ const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID5()}.bulk-scan.json`);
8039
+ const temporary = `${target}.tmp-${process.pid}-${randomUUID5()}`;
7894
8040
  try {
7895
8041
  await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
7896
8042
  encoding: "utf-8",
@@ -9015,8 +9161,8 @@ function registerCleanupCommand(parent) {
9015
9161
  }
9016
9162
 
9017
9163
  // src/cli/commands/runtime-dashboard.ts
9018
- import { lstat as lstat2 } from "fs/promises";
9019
- import { resolve as resolve3 } from "path";
9164
+ import { lstat as lstat3 } from "fs/promises";
9165
+ import { resolve as resolve4 } from "path";
9020
9166
  import { dump as dump5 } from "js-yaml";
9021
9167
  var TIME_UNITS = ["hour", "hours", "day", "days"];
9022
9168
  function integer2(value, name, minimum = 0) {
@@ -9092,8 +9238,8 @@ function registerRuntimeDashboardCommands(runtime) {
9092
9238
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
9093
9239
  );
9094
9240
  if (opts.outputFile) {
9095
- const path3 = resolve3(text(opts.outputFile, "--output-file"));
9096
- const existing = await lstat2(path3).catch((error) => {
9241
+ const path3 = resolve4(text(opts.outputFile, "--output-file"));
9242
+ const existing = await lstat3(path3).catch((error) => {
9097
9243
  if (error.code !== "ENOENT") throw error;
9098
9244
  return void 0;
9099
9245
  });
@@ -9108,7 +9254,7 @@ function registerRuntimeDashboardCommands(runtime) {
9108
9254
  const result = await read(client.dashboard);
9109
9255
  const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
9110
9256
  if (command.name() === "scan-content" && opts.outputFile) {
9111
- await writeReportFile(resolve3(String(opts.outputFile)), `${output}
9257
+ await writeReportFile(resolve4(String(opts.outputFile)), `${output}
9112
9258
  `);
9113
9259
  ui.status("Scan content exported to the requested private file (0600).");
9114
9260
  } else console.log(output);
@@ -9279,11 +9425,11 @@ function registerRuntimeDashboardCommands(runtime) {
9279
9425
  }
9280
9426
 
9281
9427
  // src/cli/commands/runtime-report.ts
9282
- import { lstat as lstat3 } from "fs/promises";
9283
- import { resolve as resolve4 } from "path";
9284
- import { randomUUID as randomUUID5 } from "crypto";
9428
+ import { lstat as lstat4 } from "fs/promises";
9429
+ import { resolve as resolve5 } from "path";
9430
+ import { randomUUID as randomUUID6 } from "crypto";
9285
9431
  function registerRuntimeReportCommand(runtime) {
9286
- const command = runtime.command("report").description("Generate a daily AI Runtime Security environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
9432
+ const command = runtime.command("report").description("Generate a daily AI Runtime Security environment report (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
9287
9433
  "--output-file <path>",
9288
9434
  "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
9289
9435
  ).option("--title <text>", "Report title", "Daily environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (25 sessions/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
@@ -9305,11 +9451,11 @@ function registerRuntimeReportCommand(runtime) {
9305
9451
  usageError("--title must contain 1\u2013240 characters");
9306
9452
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
9307
9453
  usageError("--output-file cannot be empty");
9308
- const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID5().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9309
- const outputPath = opts.outputFile === "-" ? void 0 : resolve4(opts.outputFile ?? defaultName);
9454
+ const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID6().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9455
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve5(opts.outputFile ?? defaultName);
9310
9456
  try {
9311
9457
  if (outputPath) {
9312
- const existing = await lstat3(outputPath).catch((error) => {
9458
+ const existing = await lstat4(outputPath).catch((error) => {
9313
9459
  if (error.code !== "ENOENT") throw error;
9314
9460
  return void 0;
9315
9461
  });
@@ -9796,7 +9942,7 @@ function completedBulkResults(state) {
9796
9942
  }
9797
9943
  async function writeBulkResults(outputPath, results) {
9798
9944
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
9799
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID6()}`;
9945
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID7()}`;
9800
9946
  try {
9801
9947
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
9802
9948
  encoding: "utf-8",
@@ -10641,12 +10787,12 @@ function buildProgram() {
10641
10787
  program.hook("preAction", async (_thisCommand, actionCommand) => {
10642
10788
  const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
10643
10789
  setQuiet(Boolean(root.quiet));
10644
- const isEnvironmentReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime" || actionCommand.name() === "dashboard" && actionCommand.parent?.name() === "redteam";
10790
+ const isEnvironmentReport = actionCommand.name() === "report" && ["runtime", "redteam", "aigateway"].includes(actionCommand.parent?.name() ?? "") && !(actionCommand.parent?.name() === "redteam" && actionCommand.args.length > 0);
10645
10791
  if (isEnvironmentReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10646
10792
  usageError(
10647
10793
  "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
10648
10794
  );
10649
- if (!isEnvironmentReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10795
+ if (!isEnvironmentReport && !(actionCommand.name() === "report" && actionCommand.parent?.name() === "redteam") && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10650
10796
  try {
10651
10797
  const format = await resolveOutput(actionCommand, actionCommand.opts());
10652
10798
  actionCommand.setOptionValueWithSource("output", format, "implied");
@@ -10657,7 +10803,7 @@ function buildProgram() {
10657
10803
  if (root.debug) {
10658
10804
  const logPath = join4(
10659
10805
  process.cwd(),
10660
- `debug-api-${Date.now()}-${randomUUID7().slice(0, 8)}.jsonl`
10806
+ `debug-api-${Date.now()}-${randomUUID8().slice(0, 8)}.jsonl`
10661
10807
  );
10662
10808
  try {
10663
10809
  installDebugLogger(logPath);