@cdot65/prisma-airs-cli 5.1.0 → 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,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-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
+ }
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
+ registerAiGatewayDashboardCommand(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
  }
@@ -6154,10 +6292,10 @@ async function backupTargets(opts) {
6154
6292
  }
6155
6293
 
6156
6294
  // src/cli/commands/redteam-dashboard.ts
6157
- import { lstat } from "fs/promises";
6158
- import { resolve } from "path";
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";
6298
+ import { randomUUID as randomUUID3 } from "crypto";
6161
6299
  function registerRedTeamDashboardCommand(redteam) {
6162
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(
6163
6301
  "--output-file <path>",
@@ -6181,11 +6319,11 @@ function registerRedTeamDashboardCommand(redteam) {
6181
6319
  usageError("--title must contain 1\u2013240 characters");
6182
6320
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
6183
6321
  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);
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);
6186
6324
  try {
6187
6325
  if (outputPath) {
6188
- const existing = await lstat(outputPath).catch((error) => {
6326
+ const existing = await lstat2(outputPath).catch((error) => {
6189
6327
  if (error.code !== "ENOENT") throw error;
6190
6328
  return void 0;
6191
6329
  });
@@ -7459,7 +7597,7 @@ function registerRedteamCommand(program) {
7459
7597
  }
7460
7598
 
7461
7599
  // src/cli/commands/runtime.ts
7462
- import { randomUUID as randomUUID6 } from "crypto";
7600
+ import { randomUUID as randomUUID7 } from "crypto";
7463
7601
  import * as fs5 from "fs";
7464
7602
  import { readFile as readFile10 } from "fs/promises";
7465
7603
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
@@ -7707,7 +7845,7 @@ function mergeProfilePolicy(existing, overrides) {
7707
7845
  }
7708
7846
 
7709
7847
  // src/cli/bulk-scan-lock.ts
7710
- import { randomUUID as randomUUID3 } from "crypto";
7848
+ import { randomUUID as randomUUID4 } from "crypto";
7711
7849
  import * as fs3 from "fs/promises";
7712
7850
  function errorCode(error) {
7713
7851
  return error?.code;
@@ -7738,7 +7876,7 @@ function processIsAlive(pid) {
7738
7876
  }
7739
7877
  }
7740
7878
  async function installLock(lockPath, record) {
7741
- const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID3()}`;
7879
+ const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID4()}`;
7742
7880
  try {
7743
7881
  await fs3.writeFile(candidate, JSON.stringify(record), {
7744
7882
  encoding: "utf-8",
@@ -7756,7 +7894,7 @@ async function acquireBulkScanLock(statePath) {
7756
7894
  version: 1,
7757
7895
  pid: process.pid,
7758
7896
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7759
- token: randomUUID3()
7897
+ token: randomUUID4()
7760
7898
  };
7761
7899
  for (let attempt = 0; attempt < 2; attempt++) {
7762
7900
  try {
@@ -7792,32 +7930,32 @@ async function acquireBulkScanLock(statePath) {
7792
7930
  }
7793
7931
 
7794
7932
  // src/cli/bulk-scan-state.ts
7795
- import { randomUUID as randomUUID4 } from "crypto";
7933
+ import { randomUUID as randomUUID5 } from "crypto";
7796
7934
  import * as fs4 from "fs/promises";
7797
7935
  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()
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()
7811
7949
  });
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(),
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(),
7819
7957
  result: BulkScanResultSchema.optional(),
7820
- error: z.string().optional()
7958
+ error: z2.string().optional()
7821
7959
  }).superRefine((item, ctx) => {
7822
7960
  if (item.reqId !== item.index) {
7823
7961
  ctx.addIssue({ code: "custom", message: "reqId must match the stable input index" });
@@ -7855,15 +7993,15 @@ var BulkScanItemSchema = z.object({
7855
7993
  }
7856
7994
  }
7857
7995
  });
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)
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)
7867
8005
  }).superRefine((state, ctx) => {
7868
8006
  const indices = /* @__PURE__ */ new Set();
7869
8007
  for (const [position, item] of state.items.entries()) {
@@ -7889,8 +8027,8 @@ async function saveBulkScanState(state, dir, filePath) {
7889
8027
  }
7890
8028
  await fs4.mkdir(dir, { recursive: true, mode: 448 });
7891
8029
  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()}`;
8030
+ const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID5()}.bulk-scan.json`);
8031
+ const temporary = `${target}.tmp-${process.pid}-${randomUUID5()}`;
7894
8032
  try {
7895
8033
  await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
7896
8034
  encoding: "utf-8",
@@ -9015,8 +9153,8 @@ function registerCleanupCommand(parent) {
9015
9153
  }
9016
9154
 
9017
9155
  // src/cli/commands/runtime-dashboard.ts
9018
- import { lstat as lstat2 } from "fs/promises";
9019
- import { resolve as resolve3 } from "path";
9156
+ import { lstat as lstat3 } from "fs/promises";
9157
+ import { resolve as resolve4 } from "path";
9020
9158
  import { dump as dump5 } from "js-yaml";
9021
9159
  var TIME_UNITS = ["hour", "hours", "day", "days"];
9022
9160
  function integer2(value, name, minimum = 0) {
@@ -9092,8 +9230,8 @@ function registerRuntimeDashboardCommands(runtime) {
9092
9230
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
9093
9231
  );
9094
9232
  if (opts.outputFile) {
9095
- const path3 = resolve3(text(opts.outputFile, "--output-file"));
9096
- const existing = await lstat2(path3).catch((error) => {
9233
+ const path3 = resolve4(text(opts.outputFile, "--output-file"));
9234
+ const existing = await lstat3(path3).catch((error) => {
9097
9235
  if (error.code !== "ENOENT") throw error;
9098
9236
  return void 0;
9099
9237
  });
@@ -9108,7 +9246,7 @@ function registerRuntimeDashboardCommands(runtime) {
9108
9246
  const result = await read(client.dashboard);
9109
9247
  const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
9110
9248
  if (command.name() === "scan-content" && opts.outputFile) {
9111
- await writeReportFile(resolve3(String(opts.outputFile)), `${output}
9249
+ await writeReportFile(resolve4(String(opts.outputFile)), `${output}
9112
9250
  `);
9113
9251
  ui.status("Scan content exported to the requested private file (0600).");
9114
9252
  } else console.log(output);
@@ -9279,9 +9417,9 @@ function registerRuntimeDashboardCommands(runtime) {
9279
9417
  }
9280
9418
 
9281
9419
  // 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";
9420
+ import { lstat as lstat4 } from "fs/promises";
9421
+ import { resolve as resolve5 } from "path";
9422
+ import { randomUUID as randomUUID6 } from "crypto";
9285
9423
  function registerRuntimeReportCommand(runtime) {
9286
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(
9287
9425
  "--output-file <path>",
@@ -9305,11 +9443,11 @@ function registerRuntimeReportCommand(runtime) {
9305
9443
  usageError("--title must contain 1\u2013240 characters");
9306
9444
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
9307
9445
  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);
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);
9310
9448
  try {
9311
9449
  if (outputPath) {
9312
- const existing = await lstat3(outputPath).catch((error) => {
9450
+ const existing = await lstat4(outputPath).catch((error) => {
9313
9451
  if (error.code !== "ENOENT") throw error;
9314
9452
  return void 0;
9315
9453
  });
@@ -9796,7 +9934,7 @@ function completedBulkResults(state) {
9796
9934
  }
9797
9935
  async function writeBulkResults(outputPath, results) {
9798
9936
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
9799
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID6()}`;
9937
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID7()}`;
9800
9938
  try {
9801
9939
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
9802
9940
  encoding: "utf-8",
@@ -10641,7 +10779,7 @@ function buildProgram() {
10641
10779
  program.hook("preAction", async (_thisCommand, actionCommand) => {
10642
10780
  const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
10643
10781
  setQuiet(Boolean(root.quiet));
10644
- const isEnvironmentReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime" || actionCommand.name() === "dashboard" && actionCommand.parent?.name() === "redteam";
10782
+ const isEnvironmentReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime" || actionCommand.name() === "dashboard" && ["redteam", "aigateway"].includes(actionCommand.parent?.name() ?? "");
10645
10783
  if (isEnvironmentReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10646
10784
  usageError(
10647
10785
  "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
@@ -10657,7 +10795,7 @@ function buildProgram() {
10657
10795
  if (root.debug) {
10658
10796
  const logPath = join4(
10659
10797
  process.cwd(),
10660
- `debug-api-${Date.now()}-${randomUUID7().slice(0, 8)}.jsonl`
10798
+ `debug-api-${Date.now()}-${randomUUID8().slice(0, 8)}.jsonl`
10661
10799
  );
10662
10800
  try {
10663
10801
  installDebugLogger(logPath);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CreateCustomTopicRequest, CustomTopic as CustomTopic$1, CreateSecurityProfileRequest, ManagementClientOptions, ModelSecurityClientOptions, RedTeamClientOptions, InitOptions, ManagementClient, RedTeamClient, QuotaSummary } from '@cdot65/prisma-airs-sdk';
1
+ import { CreateCustomTopicRequest, CustomTopic as CustomTopic$1, CreateSecurityProfileRequest, ManagementClientOptions, ModelSecurityClientOptions, RedTeamClientOptions, InitOptions, ManagementClient, AIGatewayClient, RedTeamClient, QuotaSummary } from '@cdot65/prisma-airs-sdk';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
@@ -1845,9 +1845,6 @@ declare function computeMetrics(results: TestResult[]): EfficacyMetrics;
1845
1845
  /** Compute per-category error breakdown from test results. Sorted by error rate descending. */
1846
1846
  declare function computeCategoryBreakdown(results: TestResult[]): CategoryBreakdown[];
1847
1847
 
1848
- /** Atomically publish a private report without overwriting a file or following a symlink. */
1849
- declare function writeReportFile(path: string, content: string): Promise<void>;
1850
-
1851
1848
  /** Read-only SDK surface needed by the first product report. */
1852
1849
  interface RuntimeReportClient {
1853
1850
  dashboard: Pick<ManagementClient['dashboard'], 'applicationsOverview' | 'sessionsOverview' | 'sessionsChart' | 'topApplicationsViolations' | 'applicationsViolationsTrend'>;
@@ -1857,6 +1854,31 @@ interface RuntimeReportClient {
1857
1854
  type ReportSourceStatus = 'complete' | 'partial' | 'unavailable';
1858
1855
  type ReportPriority = 'attention' | 'review' | 'info';
1859
1856
  type ReportFormat = 'html' | 'markdown';
1857
+ interface EnvironmentReportTable {
1858
+ title: string;
1859
+ note: string;
1860
+ headers: string[];
1861
+ rows: Array<Array<string | number | null>>;
1862
+ }
1863
+ /** Aggregate-only view shared by portable product dashboards. */
1864
+ interface EnvironmentReport {
1865
+ schemaVersion: 1;
1866
+ product: string;
1867
+ title: string;
1868
+ collectionStartedAt: string;
1869
+ generatedAt: string;
1870
+ window: {
1871
+ start: string;
1872
+ end: string;
1873
+ };
1874
+ windowLabel?: string;
1875
+ schemaLabel?: string;
1876
+ health: 'attention' | 'review' | 'no-findings' | 'unknown';
1877
+ sources: ReportSource[];
1878
+ findings: ReportFinding[];
1879
+ tables: EnvironmentReportTable[];
1880
+ limitations: string[];
1881
+ }
1860
1882
  interface ReportSource {
1861
1883
  name: string;
1862
1884
  method: string;
@@ -1970,6 +1992,27 @@ interface RuntimeReportOptions {
1970
1992
  now?: () => Date;
1971
1993
  }
1972
1994
 
1995
+ type GatewayReportClient = Pick<AIGatewayClient, 'telemetry' | 'workspaces' | 'configs' | 'apiKeys' | 'organisations' | 'guardrails'>;
1996
+ interface GatewayReportOptions {
1997
+ workspace: string;
1998
+ tsgId: string;
1999
+ start: Date;
2000
+ end: Date;
2001
+ title?: string;
2002
+ maxPages?: number;
2003
+ now?: () => Date;
2004
+ }
2005
+ /** Collect all 25 supplied read feeds; retain only allowlisted aggregate evidence. */
2006
+ declare function collectGatewayEnvironmentReport(client: GatewayReportClient, options: GatewayReportOptions): Promise<EnvironmentReport>;
2007
+
2008
+ /** Portable Markdown using the same allowlisted evidence as the HTML dashboard. */
2009
+ declare function renderEnvironmentReportMarkdown(r: EnvironmentReport): string;
2010
+ /** Self-contained HTML with hash-based CSP, static inline JS/CSS and no remote resources. */
2011
+ declare function renderEnvironmentReportHtml(r: EnvironmentReport): string;
2012
+
2013
+ /** Atomically publish a private report without overwriting a file or following a symlink. */
2014
+ declare function writeReportFile(path: string, content: string): Promise<void>;
2015
+
1973
2016
  /** Read-only SDK surface; older SDK installations explicitly report quota as unavailable. */
1974
2017
  type RedTeamReportClient = Pick<RedTeamClient, 'getDashboardOverview' | 'getScanStatistics'> & {
1975
2018
  targets: Pick<RedTeamClient['targets'], 'list'>;
@@ -2005,9 +2048,9 @@ interface RedTeamEnvironmentReport {
2005
2048
  declare function collectRedTeamEnvironmentReport(client: RedTeamReportClient, options?: RuntimeReportOptions): Promise<RedTeamEnvironmentReport>;
2006
2049
 
2007
2050
  /** Portable Markdown using the same allowlisted evidence as the HTML dashboard. */
2008
- declare function renderRedTeamReportMarkdown(r: RedTeamEnvironmentReport): string;
2009
- /** Self-contained HTML with hash-based CSP, static inline JS/CSS and no remote resources. */
2010
- declare function renderRedTeamReportHtml(r: RedTeamEnvironmentReport): string;
2051
+ declare function renderRedTeamReportMarkdown(report: RedTeamEnvironmentReport): string;
2052
+ /** Self-contained HTML with hash-based CSP and no remote resources. */
2053
+ declare function renderRedTeamReportHtml(report: RedTeamEnvironmentReport): string;
2011
2054
 
2012
2055
  /** Render the same evidence model as a portable, inert Markdown deliverable. */
2013
2056
  declare function renderRuntimeReportMarkdown(report: RuntimeDailyReport): string;
@@ -2020,4 +2063,4 @@ declare function renderRuntimeReportHtml(report: RuntimeDailyReport): string;
2020
2063
  */
2021
2064
  declare function collectRuntimeDailyReport(client: RuntimeReportClient, options?: RuntimeReportOptions): Promise<RuntimeDailyReport>;
2022
2065
 
2023
- export { AirsScanService, type AnalysisReport, type ApiKeyInfo, type ApiKeyListResult, type BackupEnvelope, type BackupFormat, type BackupResult, type BatchEntry, type BulkScanAction, type BulkScanResult, type CategoryBreakdown, type ConsumptionAppListEntry, type ConsumptionQueryOptions, type ConsumptionTimeInterval, type CustomTopic, type CustomerAppConsumption, type CustomerAppInfo, type CustomerAppListResult, type DeleteResponse, type DeploymentProfileInfo, type EfficacyMetrics, type EulaContent, type EulaStatus, type IndexedPrompt, type InstanceDetail, type InstanceRequest, type InstanceResponse, type IterationResult, type ManagementService, type ModelSecurityEvaluation, type ModelSecurityFile, type ModelSecurityFileListOptions, type ModelSecurityGroup, type ModelSecurityGroupCreateRequest, type ModelSecurityGroupListOptions, type ModelSecurityGroupUpdateRequest, type ModelSecurityLabel, type ModelSecurityModel, type ModelSecurityModelListOptions, type ModelSecurityModelVersion, type ModelSecurityModelVersionListOptions, type ModelSecurityPyPIAuth, type ModelSecurityRule, type ModelSecurityRuleEditableField, type ModelSecurityRuleInstance, type ModelSecurityRuleInstanceListOptions, type ModelSecurityRuleInstanceUpdateRequest, type ModelSecurityRuleListOptions, type ModelSecurityScan, type ModelSecurityScanListOptions, type ModelSecurityService, type ModelSecurityViolation, type MutationResponse, type PaginationOptions, type PollRetryOptions, type ProfileTopic, type PromptDetail, type PromptSetDetail, type PromptSetService, type PromptSetVersionInfo, type PropertyValueList, type RedTeamAdapterCreateRequest, type RedTeamAdapterDetail, type RedTeamAdapterListItem, type RedTeamAdapterListOptions, type RedTeamAdapterUpdateOverrides, type RedTeamAdapterValidateRequest, type RedTeamAdapterValidationResult, type RedTeamAdapterVar, type RedTeamAttack, type RedTeamCategory, type RedTeamChannel, type RedTeamChannelCreateRequest, type RedTeamChannelListOptions, type RedTeamChannelStats, type RedTeamChannelUpdateRequest, type RedTeamCustomAttack, type RedTeamCustomReport, type RedTeamDynamicReport, type RedTeamEnvironmentReport, type RedTeamErrorLog, type RedTeamJob, type RedTeamLanguages, type RedTeamReportClient, type RedTeamReportTable, type RedTeamService, type RedTeamStaticReport, type RedTeamTarget, type RedTeamTargetCreateRequest, type RedTeamTargetDetail, type RedTeamTargetUpdateRequest, type RegistryCredentials, type ReliableRuntimeService, type ReportApplication, type ReportDailyTelemetry, type ReportFinding, type ReportFormat, type ReportPriority, type ReportProfile, type ReportRegisteredApp, type ReportSessionSummary, type ReportSeverity, type ReportSource, type ReportSourceStatus, type ResourceType, type RestoreResult, type RunState, type RuntimeDailyReport, type RuntimeReportClient, type RuntimeReportOptions, type RuntimeScanResult, type RuntimeService, SDK_ASYNC_BATCH_SIZE, type ScanLogQueryOptions, type ScanLogQueryResult, type ScanResult, type ScanService, SdkManagementService, SdkModelSecurityService, SdkPromptSetService, SdkRedTeamService, SdkRuntimeService, type SecurityProfileInfo, type SecurityProfileListResult, type SubmittedBatch, type TargetAuthValidationRequest, type TargetAuthValidationResult, type TargetOperationOptions, type TestCase, type TestResult, type UserInput, type ValidationError, collectRedTeamEnvironmentReport, collectRuntimeDailyReport, computeCategoryBreakdown, computeMetrics, loadConfig, readBackupDir, readBackupFile, renderRedTeamReportHtml, renderRedTeamReportMarkdown, renderRuntimeReportHtml, renderRuntimeReportMarkdown, resolveOutputDir, sanitizeFilename, validateDescription, validateExamples, validateName, validateTopic, writeBackupFile, writeReportFile };
2066
+ export { AirsScanService, type AnalysisReport, type ApiKeyInfo, type ApiKeyListResult, type BackupEnvelope, type BackupFormat, type BackupResult, type BatchEntry, type BulkScanAction, type BulkScanResult, type CategoryBreakdown, type ConsumptionAppListEntry, type ConsumptionQueryOptions, type ConsumptionTimeInterval, type CustomTopic, type CustomerAppConsumption, type CustomerAppInfo, type CustomerAppListResult, type DeleteResponse, type DeploymentProfileInfo, type EfficacyMetrics, type EnvironmentReport, type EnvironmentReportTable, type EulaContent, type EulaStatus, type GatewayReportClient, type GatewayReportOptions, type IndexedPrompt, type InstanceDetail, type InstanceRequest, type InstanceResponse, type IterationResult, type ManagementService, type ModelSecurityEvaluation, type ModelSecurityFile, type ModelSecurityFileListOptions, type ModelSecurityGroup, type ModelSecurityGroupCreateRequest, type ModelSecurityGroupListOptions, type ModelSecurityGroupUpdateRequest, type ModelSecurityLabel, type ModelSecurityModel, type ModelSecurityModelListOptions, type ModelSecurityModelVersion, type ModelSecurityModelVersionListOptions, type ModelSecurityPyPIAuth, type ModelSecurityRule, type ModelSecurityRuleEditableField, type ModelSecurityRuleInstance, type ModelSecurityRuleInstanceListOptions, type ModelSecurityRuleInstanceUpdateRequest, type ModelSecurityRuleListOptions, type ModelSecurityScan, type ModelSecurityScanListOptions, type ModelSecurityService, type ModelSecurityViolation, type MutationResponse, type PaginationOptions, type PollRetryOptions, type ProfileTopic, type PromptDetail, type PromptSetDetail, type PromptSetService, type PromptSetVersionInfo, type PropertyValueList, type RedTeamAdapterCreateRequest, type RedTeamAdapterDetail, type RedTeamAdapterListItem, type RedTeamAdapterListOptions, type RedTeamAdapterUpdateOverrides, type RedTeamAdapterValidateRequest, type RedTeamAdapterValidationResult, type RedTeamAdapterVar, type RedTeamAttack, type RedTeamCategory, type RedTeamChannel, type RedTeamChannelCreateRequest, type RedTeamChannelListOptions, type RedTeamChannelStats, type RedTeamChannelUpdateRequest, type RedTeamCustomAttack, type RedTeamCustomReport, type RedTeamDynamicReport, type RedTeamEnvironmentReport, type RedTeamErrorLog, type RedTeamJob, type RedTeamLanguages, type RedTeamReportClient, type RedTeamReportTable, type RedTeamService, type RedTeamStaticReport, type RedTeamTarget, type RedTeamTargetCreateRequest, type RedTeamTargetDetail, type RedTeamTargetUpdateRequest, type RegistryCredentials, type ReliableRuntimeService, type ReportApplication, type ReportDailyTelemetry, type ReportFinding, type ReportFormat, type ReportPriority, type ReportProfile, type ReportRegisteredApp, type ReportSessionSummary, type ReportSeverity, type ReportSource, type ReportSourceStatus, type ResourceType, type RestoreResult, type RunState, type RuntimeDailyReport, type RuntimeReportClient, type RuntimeReportOptions, type RuntimeScanResult, type RuntimeService, SDK_ASYNC_BATCH_SIZE, type ScanLogQueryOptions, type ScanLogQueryResult, type ScanResult, type ScanService, SdkManagementService, SdkModelSecurityService, SdkPromptSetService, SdkRedTeamService, SdkRuntimeService, type SecurityProfileInfo, type SecurityProfileListResult, type SubmittedBatch, type TargetAuthValidationRequest, type TargetAuthValidationResult, type TargetOperationOptions, type TestCase, type TestResult, type UserInput, type ValidationError, collectGatewayEnvironmentReport, collectRedTeamEnvironmentReport, collectRuntimeDailyReport, computeCategoryBreakdown, computeMetrics, loadConfig, readBackupDir, readBackupFile, renderEnvironmentReportHtml, renderEnvironmentReportMarkdown, renderRedTeamReportHtml, renderRedTeamReportMarkdown, renderRuntimeReportHtml, renderRuntimeReportMarkdown, resolveOutputDir, sanitizeFilename, validateDescription, validateExamples, validateName, validateTopic, writeBackupFile, writeReportFile };