@cdot65/prisma-airs-cli 5.0.1 → 5.2.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,8 @@ import {
9
9
  SdkPromptSetService,
10
10
  SdkRedTeamService,
11
11
  SdkRuntimeService,
12
+ collectGatewayEnvironmentReport,
13
+ collectRedTeamEnvironmentReport,
12
14
  collectRuntimeDailyReport,
13
15
  computeMetrics,
14
16
  getOrCreateManagementClient,
@@ -16,6 +18,10 @@ import {
16
18
  loadConfig,
17
19
  readBackupDir,
18
20
  readBackupFile,
21
+ renderEnvironmentReportHtml,
22
+ renderEnvironmentReportMarkdown,
23
+ renderRedTeamReportHtml,
24
+ renderRedTeamReportMarkdown,
19
25
  renderRuntimeReportHtml,
20
26
  renderRuntimeReportMarkdown,
21
27
  resolveConfigFilePath,
@@ -24,7 +30,7 @@ import {
24
30
  validateTopic,
25
31
  writeBackupFile,
26
32
  writeReportFile
27
- } from "../chunk-IRLIFMGQ.js";
33
+ } from "../chunk-7Z76VCB3.js";
28
34
 
29
35
  // src/cli/index.ts
30
36
  import "dotenv/config";
@@ -43,7 +49,7 @@ function installProcessGuards() {
43
49
  }
44
50
 
45
51
  // src/cli/program.ts
46
- import { randomUUID as randomUUID6 } from "crypto";
52
+ import { randomUUID as randomUUID8 } from "crypto";
47
53
  import { readFileSync as readFileSync4 } from "fs";
48
54
  import { dirname as dirname4, join as join4 } from "path";
49
55
  import { fileURLToPath } from "url";
@@ -3052,8 +3058,8 @@ function registerAiGatewayInference(parent) {
3052
3058
  process.once("SIGINT", interrupt);
3053
3059
  process.once("SIGTERM", interrupt);
3054
3060
  process.stdout.on("error", outputError);
3055
- const write = (text2) => new Promise((resolve4, reject) => {
3056
- process.stdout.write(text2, (error) => error ? reject(error) : resolve4());
3061
+ const write = (text2) => new Promise((resolve6, reject) => {
3062
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve6());
3057
3063
  });
3058
3064
  const requestOptions = { signal: controller.signal };
3059
3065
  const result = kind === "chat" ? await client.createChatCompletion(
@@ -4338,6 +4344,20 @@ function registerOrganisations(root) {
4338
4344
  const self = showHelpOnEmpty(
4339
4345
  group.command("self").description("Manage the current organisation")
4340
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
+ );
4341
4361
  const selfGet = addReadOutput(self.command("get").description("Get the current organisation"));
4342
4362
  selfGet.action((opts) => runDetail(selfGet, opts, (client) => client.organisations.getSelf()));
4343
4363
  const selfUpdate = addWriteOutput(
@@ -4428,6 +4448,10 @@ function registerAiGatewayInventory(root) {
4428
4448
  { option: "name", path: "name" },
4429
4449
  { option: "workspace", path: "workspace_id" }
4430
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()));
4431
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");
4432
4456
  const guardrailUpdateFields = guardrailFields.filter((field) => field.option !== "workspace");
4433
4457
  const addGuardrailUpdateFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name");
@@ -4498,7 +4522,8 @@ function registerAiGatewayInventory(root) {
4498
4522
  // src/cli/commands/aigateway/telemetry.ts
4499
4523
  import {
4500
4524
  AI_GW_GROUP_COLUMNS,
4501
- AI_GW_GROUP_DIMENSIONS
4525
+ AI_GW_GROUP_DIMENSIONS,
4526
+ redactAIGatewaySecrets as redactAIGatewaySecrets2
4502
4527
  } from "@cdot65/prisma-airs-sdk";
4503
4528
 
4504
4529
  // src/cli/commands/aigateway/telemetry-filters.ts
@@ -4583,7 +4608,10 @@ function parseNamedDate2(value, flag) {
4583
4608
  function registerMetric(telemetry, name, description, method) {
4584
4609
  const command = addWindowOptions(telemetry.command(name).description(description));
4585
4610
  command.action(
4586
- (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
+ })
4587
4615
  );
4588
4616
  }
4589
4617
  function registerFilteredMetric(telemetry, name, description) {
@@ -4631,6 +4659,24 @@ function registerAiGatewayTelemetryReads(telemetry) {
4631
4659
  );
4632
4660
  registerMetric(telemetry, "error-trends", "Get error trends", "errorTrends");
4633
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
+ );
4634
4680
  const feedback = showHelpOnEmpty(
4635
4681
  telemetry.command("feedback").description("Inspect model feedback telemetry")
4636
4682
  );
@@ -4673,7 +4719,7 @@ function registerAiGatewayTelemetryReads(telemetry) {
4673
4719
  registerFilteredMetric(telemetry, "latency", "Get latency telemetry");
4674
4720
  const logs = showHelpOnEmpty(telemetry.command("logs").description("Inspect request logs"));
4675
4721
  const logsList = addWindowOptions(
4676
- 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")
4677
4723
  );
4678
4724
  logsList.action(
4679
4725
  (opts) => runDetail(
@@ -4682,6 +4728,7 @@ function registerAiGatewayTelemetryReads(telemetry) {
4682
4728
  (client) => client.telemetry.logs({
4683
4729
  ...windowFrom(opts),
4684
4730
  pageSize: parsePositiveInteger2(opts.pageSize ?? "50", "--page-size"),
4731
+ currentPage: parseNonnegativePage(opts.currentPage ?? "0"),
4685
4732
  ...opts.statusCode ? { statusCode: parsePositiveInteger2(opts.statusCode, "--status-code") } : {},
4686
4733
  ...opts.traceId ? { traceId: opts.traceId } : {}
4687
4734
  })
@@ -4693,6 +4740,99 @@ function registerAiGatewayTelemetryReads(telemetry) {
4693
4740
  registerMetric(telemetry, "user-trends", "Get user trends", "userTrends");
4694
4741
  registerMetric(telemetry, "users", "Get unique-user count", "users");
4695
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-dashboard.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 registerAiGatewayDashboardCommand(aigateway) {
4756
+ const command = aigateway.command("dashboard").description("Generate an AI Gateway environment dashboard (read-only)").requiredOption("--workspace <ref>", "Workspace slug, UUID, or unique display name").option("--days <n>", "Rolling window in days (default: 1)", "1").option("--start <iso>", "Explicit ISO-8601 start; requires --end").option("--end <iso>", "Explicit ISO-8601 end; requires --start").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
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 dashboard --workspace ws-develo-71f8d8 --output-file ./airs-daily.html",
4763
+ "airs aigateway dashboard --workspace ws-develo-71f8d8 --output markdown --output-file ./airs-daily.md",
4764
+ "airs aigateway dashboard --workspace ws-develo-71f8d8",
4765
+ "airs aigateway dashboard --workspace ws-develo-71f8d8 --strict --max-pages 20 --output-file - > daily.html"
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
+ }
4696
4836
 
4697
4837
  // src/cli/commands/aigateway.ts
4698
4838
  async function createService() {
@@ -4756,6 +4896,7 @@ function scopeNameLooksUnrelated(name, scopeName) {
4756
4896
  function registerAiGatewayCommand(program) {
4757
4897
  const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
4758
4898
  registerAiGatewayInventory(aigateway);
4899
+ registerAiGatewayDashboardCommand(aigateway);
4759
4900
  registerAiGatewayInference(aigateway);
4760
4901
  const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
4761
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(
@@ -5198,7 +5339,7 @@ function registerConfigCommand(program) {
5198
5339
  }
5199
5340
 
5200
5341
  // src/cli/commands/doctor.ts
5201
- import { randomUUID } from "crypto";
5342
+ import { randomUUID as randomUUID2 } from "crypto";
5202
5343
  import { readFile as readFile4 } from "fs/promises";
5203
5344
  import { init, Scanner } from "@cdot65/prisma-airs-sdk";
5204
5345
  var DOCTOR_TIMEOUT_MS = 5e3;
@@ -5296,8 +5437,8 @@ async function withTimeout(promise, ms) {
5296
5437
  try {
5297
5438
  return await Promise.race([
5298
5439
  promise,
5299
- new Promise((resolve4) => {
5300
- timer = setTimeout(() => resolve4(TIMED_OUT), ms);
5440
+ new Promise((resolve6) => {
5441
+ timer = setTimeout(() => resolve6(TIMED_OUT), ms);
5301
5442
  })
5302
5443
  ]);
5303
5444
  } finally {
@@ -5452,7 +5593,7 @@ async function defaultScannerProbe() {
5452
5593
  const config = await loadConfig();
5453
5594
  init(runtimeInitOptions(config));
5454
5595
  const scanner = new Scanner();
5455
- return scanner.queryByScanIds([randomUUID()]);
5596
+ return scanner.queryByScanIds([randomUUID2()]);
5456
5597
  }
5457
5598
  async function defaultMgmtProbe() {
5458
5599
  const config = await loadConfig();
@@ -5567,12 +5708,12 @@ function hasBin(bin) {
5567
5708
  }
5568
5709
  }
5569
5710
  function run(bin, args, label) {
5570
- return new Promise((resolve4, reject) => {
5711
+ return new Promise((resolve6, reject) => {
5571
5712
  const child = spawn(bin, args, { stdio: "inherit" });
5572
5713
  child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
5573
5714
  child.on("close", (code) => {
5574
5715
  if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
5575
- else resolve4();
5716
+ else resolve6();
5576
5717
  });
5577
5718
  });
5578
5719
  }
@@ -6150,6 +6291,76 @@ async function backupTargets(opts) {
6150
6291
  return results;
6151
6292
  }
6152
6293
 
6294
+ // src/cli/commands/redteam-dashboard.ts
6295
+ import { lstat as lstat2 } from "fs/promises";
6296
+ import { resolve as resolve2 } from "path";
6297
+ import { RedTeamClient } from "@cdot65/prisma-airs-sdk";
6298
+ import { randomUUID as randomUUID3 } from "crypto";
6299
+ function registerRedTeamDashboardCommand(redteam) {
6300
+ const command = redteam.command("dashboard").description("Generate a Red Team environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
6301
+ "--output-file <path>",
6302
+ "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
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(
6304
+ "after",
6305
+ examples(
6306
+ "airs redteam dashboard --output-file ./airs-daily.html",
6307
+ "airs redteam dashboard --output markdown --output-file ./airs-daily.md",
6308
+ "airs redteam dashboard",
6309
+ "airs redteam dashboard --strict --max-pages 20 --output-file - > daily.html"
6310
+ )
6311
+ ).action(async (opts) => {
6312
+ const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
6313
+ if (format !== "html" && format !== "markdown")
6314
+ usageError("Report output must be html or markdown");
6315
+ const maxPages = Number(opts.maxPages);
6316
+ if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages) || maxPages > 100)
6317
+ usageError("--max-pages must be an integer from 1 to 100");
6318
+ if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
6319
+ usageError("--title must contain 1\u2013240 characters");
6320
+ if (opts.outputFile !== void 0 && !opts.outputFile.trim())
6321
+ usageError("--output-file cannot be empty");
6322
+ const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID3().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
6323
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve2(opts.outputFile ?? defaultName);
6324
+ try {
6325
+ if (outputPath) {
6326
+ const existing = await lstat2(outputPath).catch((error) => {
6327
+ if (error.code !== "ENOENT") throw error;
6328
+ return void 0;
6329
+ });
6330
+ if (existing) throw new Error("Output already exists");
6331
+ }
6332
+ const config = await loadConfig();
6333
+ const client = new RedTeamClient({
6334
+ ...redTeamClientOptions(config),
6335
+ numRetries: 0
6336
+ });
6337
+ ui.status("Collecting Red Team statistics and current configuration (read-only)...");
6338
+ const report = await collectRedTeamEnvironmentReport(client, {
6339
+ title: opts.title,
6340
+ maxPages
6341
+ });
6342
+ const rendered = format === "html" ? renderRedTeamReportHtml(report) : renderRedTeamReportMarkdown(report);
6343
+ if (outputPath) {
6344
+ await writeReportFile(outputPath, rendered);
6345
+ ui.status(`Report \u2192 ${outputPath.replace(/[\p{Cc}\p{Cf}]/gu, "_")} (mode 0600)`);
6346
+ } else {
6347
+ await new Promise(
6348
+ (done, reject) => process.stdout.write(rendered, (error) => error ? reject(error) : done())
6349
+ );
6350
+ }
6351
+ const incomplete = report.sources.filter((source) => source.status !== "complete").length;
6352
+ if (incomplete)
6353
+ ui.status(`${incomplete} source(s) incomplete; review the evidence section.`);
6354
+ if (report.health === "unknown" || opts.strict && incomplete > 0) process.exitCode = 1;
6355
+ } catch {
6356
+ ui.error(
6357
+ "Report generation failed. Verify Management API configuration and a writable, new output path. Existing files are never overwritten."
6358
+ );
6359
+ process.exitCode = 1;
6360
+ }
6361
+ });
6362
+ }
6363
+
6153
6364
  // src/cli/commands/restore.ts
6154
6365
  var SERVER_DERIVED_FIELDS = [
6155
6366
  "uuid",
@@ -6392,6 +6603,7 @@ function parseAdapterVariables(input) {
6392
6603
  }
6393
6604
  function registerRedteamCommand(program) {
6394
6605
  const redteam = program.command("redteam").description("AI Red Team scan operations");
6606
+ registerRedTeamDashboardCommand(redteam);
6395
6607
  redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
6396
6608
  try {
6397
6609
  renderRedteamHeader();
@@ -7385,7 +7597,7 @@ function registerRedteamCommand(program) {
7385
7597
  }
7386
7598
 
7387
7599
  // src/cli/commands/runtime.ts
7388
- import { randomUUID as randomUUID5 } from "crypto";
7600
+ import { randomUUID as randomUUID7 } from "crypto";
7389
7601
  import * as fs5 from "fs";
7390
7602
  import { readFile as readFile10 } from "fs/promises";
7391
7603
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
@@ -7633,7 +7845,7 @@ function mergeProfilePolicy(existing, overrides) {
7633
7845
  }
7634
7846
 
7635
7847
  // src/cli/bulk-scan-lock.ts
7636
- import { randomUUID as randomUUID2 } from "crypto";
7848
+ import { randomUUID as randomUUID4 } from "crypto";
7637
7849
  import * as fs3 from "fs/promises";
7638
7850
  function errorCode(error) {
7639
7851
  return error?.code;
@@ -7664,7 +7876,7 @@ function processIsAlive(pid) {
7664
7876
  }
7665
7877
  }
7666
7878
  async function installLock(lockPath, record) {
7667
- const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID2()}`;
7879
+ const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID4()}`;
7668
7880
  try {
7669
7881
  await fs3.writeFile(candidate, JSON.stringify(record), {
7670
7882
  encoding: "utf-8",
@@ -7682,7 +7894,7 @@ async function acquireBulkScanLock(statePath) {
7682
7894
  version: 1,
7683
7895
  pid: process.pid,
7684
7896
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7685
- token: randomUUID2()
7897
+ token: randomUUID4()
7686
7898
  };
7687
7899
  for (let attempt = 0; attempt < 2; attempt++) {
7688
7900
  try {
@@ -7718,32 +7930,32 @@ async function acquireBulkScanLock(statePath) {
7718
7930
  }
7719
7931
 
7720
7932
  // src/cli/bulk-scan-state.ts
7721
- import { randomUUID as randomUUID3 } from "crypto";
7933
+ import { randomUUID as randomUUID5 } from "crypto";
7722
7934
  import * as fs4 from "fs/promises";
7723
7935
  import * as path2 from "path";
7724
- import { z } from "zod";
7725
- var BulkScanResultSchema = z.object({
7726
- index: z.number().int().nonnegative(),
7727
- reqId: z.number().int().nonnegative(),
7728
- prompt: z.string(),
7729
- response: z.string().optional(),
7730
- scanId: z.string(),
7731
- reportId: z.string(),
7732
- action: z.enum(["allow", "block", "failed"]),
7733
- category: z.string(),
7734
- triggered: z.boolean(),
7735
- detections: z.record(z.boolean()),
7736
- error: z.string().optional()
7936
+ import { z as z2 } from "zod";
7937
+ var BulkScanResultSchema = z2.object({
7938
+ index: z2.number().int().nonnegative(),
7939
+ reqId: z2.number().int().nonnegative(),
7940
+ prompt: z2.string(),
7941
+ response: z2.string().optional(),
7942
+ scanId: z2.string(),
7943
+ reportId: z2.string(),
7944
+ action: z2.enum(["allow", "block", "failed"]),
7945
+ category: z2.string(),
7946
+ triggered: z2.boolean(),
7947
+ detections: z2.record(z2.boolean()),
7948
+ error: z2.string().optional()
7737
7949
  });
7738
- var BulkScanItemSchema = z.object({
7739
- index: z.number().int().nonnegative(),
7740
- reqId: z.number().int().nonnegative(),
7741
- prompt: z.string(),
7742
- status: z.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
7743
- scanId: z.string().min(1).optional(),
7744
- receiptReportId: z.string().optional(),
7950
+ var BulkScanItemSchema = z2.object({
7951
+ index: z2.number().int().nonnegative(),
7952
+ reqId: z2.number().int().nonnegative(),
7953
+ prompt: z2.string(),
7954
+ status: z2.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
7955
+ scanId: z2.string().min(1).optional(),
7956
+ receiptReportId: z2.string().optional(),
7745
7957
  result: BulkScanResultSchema.optional(),
7746
- error: z.string().optional()
7958
+ error: z2.string().optional()
7747
7959
  }).superRefine((item, ctx) => {
7748
7960
  if (item.reqId !== item.index) {
7749
7961
  ctx.addIssue({ code: "custom", message: "reqId must match the stable input index" });
@@ -7781,15 +7993,15 @@ var BulkScanItemSchema = z.object({
7781
7993
  }
7782
7994
  }
7783
7995
  });
7784
- var BulkScanStateSchema = z.object({
7785
- version: z.literal(2),
7786
- profile: z.string().min(1),
7787
- sessionId: z.string().optional(),
7788
- outputFile: z.string().min(1),
7789
- batchSize: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
7790
- createdAt: z.string().datetime(),
7791
- updatedAt: z.string().datetime(),
7792
- items: z.array(BulkScanItemSchema).min(1)
7996
+ var BulkScanStateSchema = z2.object({
7997
+ version: z2.literal(2),
7998
+ profile: z2.string().min(1),
7999
+ sessionId: z2.string().optional(),
8000
+ outputFile: z2.string().min(1),
8001
+ batchSize: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
8002
+ createdAt: z2.string().datetime(),
8003
+ updatedAt: z2.string().datetime(),
8004
+ items: z2.array(BulkScanItemSchema).min(1)
7793
8005
  }).superRefine((state, ctx) => {
7794
8006
  const indices = /* @__PURE__ */ new Set();
7795
8007
  for (const [position, item] of state.items.entries()) {
@@ -7815,8 +8027,8 @@ async function saveBulkScanState(state, dir, filePath) {
7815
8027
  }
7816
8028
  await fs4.mkdir(dir, { recursive: true, mode: 448 });
7817
8029
  if (!filePath) await fs4.chmod(dir, 448);
7818
- const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID3()}.bulk-scan.json`);
7819
- const temporary = `${target}.tmp-${process.pid}-${randomUUID3()}`;
8030
+ const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID5()}.bulk-scan.json`);
8031
+ const temporary = `${target}.tmp-${process.pid}-${randomUUID5()}`;
7820
8032
  try {
7821
8033
  await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
7822
8034
  encoding: "utf-8",
@@ -8941,8 +9153,8 @@ function registerCleanupCommand(parent) {
8941
9153
  }
8942
9154
 
8943
9155
  // src/cli/commands/runtime-dashboard.ts
8944
- import { lstat } from "fs/promises";
8945
- import { resolve as resolve2 } from "path";
9156
+ import { lstat as lstat3 } from "fs/promises";
9157
+ import { resolve as resolve4 } from "path";
8946
9158
  import { dump as dump5 } from "js-yaml";
8947
9159
  var TIME_UNITS = ["hour", "hours", "day", "days"];
8948
9160
  function integer2(value, name, minimum = 0) {
@@ -9018,8 +9230,8 @@ function registerRuntimeDashboardCommands(runtime) {
9018
9230
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
9019
9231
  );
9020
9232
  if (opts.outputFile) {
9021
- const path3 = resolve2(text(opts.outputFile, "--output-file"));
9022
- const existing = await lstat(path3).catch((error) => {
9233
+ const path3 = resolve4(text(opts.outputFile, "--output-file"));
9234
+ const existing = await lstat3(path3).catch((error) => {
9023
9235
  if (error.code !== "ENOENT") throw error;
9024
9236
  return void 0;
9025
9237
  });
@@ -9034,7 +9246,7 @@ function registerRuntimeDashboardCommands(runtime) {
9034
9246
  const result = await read(client.dashboard);
9035
9247
  const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
9036
9248
  if (command.name() === "scan-content" && opts.outputFile) {
9037
- await writeReportFile(resolve2(String(opts.outputFile)), `${output}
9249
+ await writeReportFile(resolve4(String(opts.outputFile)), `${output}
9038
9250
  `);
9039
9251
  ui.status("Scan content exported to the requested private file (0600).");
9040
9252
  } else console.log(output);
@@ -9205,9 +9417,9 @@ function registerRuntimeDashboardCommands(runtime) {
9205
9417
  }
9206
9418
 
9207
9419
  // src/cli/commands/runtime-report.ts
9208
- import { lstat as lstat2 } from "fs/promises";
9209
- import { resolve as resolve3 } from "path";
9210
- import { randomUUID as randomUUID4 } from "crypto";
9420
+ import { lstat as lstat4 } from "fs/promises";
9421
+ import { resolve as resolve5 } from "path";
9422
+ import { randomUUID as randomUUID6 } from "crypto";
9211
9423
  function registerRuntimeReportCommand(runtime) {
9212
9424
  const command = runtime.command("report").description("Generate a daily AI Runtime Security environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
9213
9425
  "--output-file <path>",
@@ -9231,11 +9443,11 @@ function registerRuntimeReportCommand(runtime) {
9231
9443
  usageError("--title must contain 1\u2013240 characters");
9232
9444
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
9233
9445
  usageError("--output-file cannot be empty");
9234
- const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID4().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9235
- const outputPath = opts.outputFile === "-" ? void 0 : resolve3(opts.outputFile ?? defaultName);
9446
+ const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID6().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9447
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve5(opts.outputFile ?? defaultName);
9236
9448
  try {
9237
9449
  if (outputPath) {
9238
- const existing = await lstat2(outputPath).catch((error) => {
9450
+ const existing = await lstat4(outputPath).catch((error) => {
9239
9451
  if (error.code !== "ENOENT") throw error;
9240
9452
  return void 0;
9241
9453
  });
@@ -9722,7 +9934,7 @@ function completedBulkResults(state) {
9722
9934
  }
9723
9935
  async function writeBulkResults(outputPath, results) {
9724
9936
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
9725
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID5()}`;
9937
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID7()}`;
9726
9938
  try {
9727
9939
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
9728
9940
  encoding: "utf-8",
@@ -10567,12 +10779,12 @@ function buildProgram() {
10567
10779
  program.hook("preAction", async (_thisCommand, actionCommand) => {
10568
10780
  const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
10569
10781
  setQuiet(Boolean(root.quiet));
10570
- const isRuntimeReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime";
10571
- if (isRuntimeReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10782
+ const isEnvironmentReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime" || actionCommand.name() === "dashboard" && ["redteam", "aigateway"].includes(actionCommand.parent?.name() ?? "");
10783
+ if (isEnvironmentReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10572
10784
  usageError(
10573
10785
  "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
10574
10786
  );
10575
- if (!isRuntimeReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10787
+ if (!isEnvironmentReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10576
10788
  try {
10577
10789
  const format = await resolveOutput(actionCommand, actionCommand.opts());
10578
10790
  actionCommand.setOptionValueWithSource("output", format, "implied");
@@ -10583,7 +10795,7 @@ function buildProgram() {
10583
10795
  if (root.debug) {
10584
10796
  const logPath = join4(
10585
10797
  process.cwd(),
10586
- `debug-api-${Date.now()}-${randomUUID6().slice(0, 8)}.jsonl`
10798
+ `debug-api-${Date.now()}-${randomUUID8().slice(0, 8)}.jsonl`
10587
10799
  );
10588
10800
  try {
10589
10801
  installDebugLogger(logPath);