@cdot65/prisma-airs-cli 4.5.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  validateTopic,
25
25
  writeBackupFile,
26
26
  writeReportFile
27
- } from "../chunk-NWK7RG2N.js";
27
+ } from "../chunk-IRLIFMGQ.js";
28
28
 
29
29
  // src/cli/index.ts
30
30
  import "dotenv/config";
@@ -226,6 +226,17 @@ var SdkAiGatewayService = class {
226
226
  };
227
227
 
228
228
  // src/config/client-options.ts
229
+ function managementClientOptions(config) {
230
+ return {
231
+ clientId: config.mgmtClientId,
232
+ clientSecret: config.mgmtClientSecret,
233
+ tsgId: config.mgmtTsgId,
234
+ apiEndpoint: config.mgmtEndpoint,
235
+ dashboardEndpoint: config.mgmtDashboardEndpoint ?? "https://api.apps.paloaltonetworks.com/aisec",
236
+ tokenEndpoint: config.mgmtTokenEndpoint,
237
+ dlpEndpoint: config.dlpEndpoint
238
+ };
239
+ }
229
240
  function runtimeInitOptions(config) {
230
241
  return {
231
242
  apiKey: config.airsApiKey,
@@ -335,8 +346,8 @@ function displayValue(value) {
335
346
  return typeof value === "object" ? JSON.stringify(value) : String(value);
336
347
  }
337
348
  function csvCell(value) {
338
- const text = displayValue(value);
339
- return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
349
+ const text2 = displayValue(value);
350
+ return /[",\r\n]/.test(text2) ? `"${text2.replace(/"/g, '""')}"` : text2;
340
351
  }
341
352
  function markdownCell(value) {
342
353
  return displayValue(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
@@ -1377,33 +1388,33 @@ function renderMsScanList(scans, format = "pretty") {
1377
1388
  }
1378
1389
  console.log();
1379
1390
  }
1380
- function renderMsScanDetail(scan, format = "pretty") {
1391
+ function renderMsScanDetail(scan2, format = "pretty") {
1381
1392
  if (format !== "pretty") {
1382
- structuredDetail("scan", scan, format, () => void 0);
1393
+ structuredDetail("scan", scan2, format, () => void 0);
1383
1394
  return;
1384
1395
  }
1385
1396
  ui.section("Scan Detail:");
1386
1397
  const pairs = [
1387
- ["UUID", scan.uuid],
1388
- ["Outcome", stateColor(scan.evalOutcome)(scan.evalOutcome)]
1398
+ ["UUID", scan2.uuid],
1399
+ ["Outcome", stateColor(scan2.evalOutcome)(scan2.evalOutcome)]
1389
1400
  ];
1390
- if (scan.modelUri) pairs.push(["Model URI", scan.modelUri]);
1391
- pairs.push(["Origin", scan.scanOrigin]);
1392
- pairs.push(["Source", scan.sourceType]);
1393
- pairs.push(["Group", scan.securityGroupName]);
1394
- pairs.push(["Created", scan.createdAt]);
1395
- pairs.push(["Updated", scan.updatedAt]);
1396
- if (scan.evalSummary) {
1397
- const { rulesPassed, rulesFailed, totalRules } = scan.evalSummary;
1401
+ if (scan2.modelUri) pairs.push(["Model URI", scan2.modelUri]);
1402
+ pairs.push(["Origin", scan2.scanOrigin]);
1403
+ pairs.push(["Source", scan2.sourceType]);
1404
+ pairs.push(["Group", scan2.securityGroupName]);
1405
+ pairs.push(["Created", scan2.createdAt]);
1406
+ pairs.push(["Updated", scan2.updatedAt]);
1407
+ if (scan2.evalSummary) {
1408
+ const { rulesPassed, rulesFailed, totalRules } = scan2.evalSummary;
1398
1409
  pairs.push([
1399
1410
  "Rules",
1400
1411
  `${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
1401
1412
  ]);
1402
1413
  }
1403
1414
  ui.keyValue(pairs);
1404
- if (scan.labels.length > 0) {
1415
+ if (scan2.labels.length > 0) {
1405
1416
  ui.section("Labels:");
1406
- ui.keyValue(scan.labels.map((l) => [l.key, l.value]));
1417
+ ui.keyValue(scan2.labels.map((l) => [l.key, l.value]));
1407
1418
  }
1408
1419
  console.log();
1409
1420
  }
@@ -2088,9 +2099,9 @@ function renderPromptList(prompts, format = "pretty") {
2088
2099
  ui.section("Prompts:");
2089
2100
  for (const p of prompts) {
2090
2101
  const status = p.active ? chalk8.green("active") : chalk8.dim("inactive");
2091
- const text = p.prompt.length > 80 ? `${p.prompt.substring(0, 77)}...` : p.prompt;
2102
+ const text2 = p.prompt.length > 80 ? `${p.prompt.substring(0, 77)}...` : p.prompt;
2092
2103
  console.log(` ${chalk8.dim(p.uuid)} ${status}`);
2093
- console.log(` ${text}`);
2104
+ console.log(` ${text2}`);
2094
2105
  if (p.goal) console.log(` ${chalk8.dim(`Goal: ${p.goal}`)}`);
2095
2106
  }
2096
2107
  console.log();
@@ -2904,54 +2915,6 @@ function renderDeploymentProfileList(profiles, format = "pretty") {
2904
2915
  }
2905
2916
  console.log();
2906
2917
  }
2907
- function renderScanLogList(results, pageToken, format = "pretty") {
2908
- if (results.length === 0) {
2909
- if (format === "pretty") ui.emptyList("scan logs");
2910
- else console.log(formatOutput([], [], format));
2911
- return;
2912
- }
2913
- if (format !== "pretty") {
2914
- const rows = results.map((r) => ({
2915
- scanId: r.scan_id ?? "",
2916
- timestamp: r.received_ts ?? r.timestamp ?? "",
2917
- action: r.action ?? r.verdict ?? "",
2918
- profile: r.profile_name ?? "",
2919
- app: r.app_name ?? ""
2920
- }));
2921
- console.log(
2922
- formatOutput(
2923
- rows,
2924
- [
2925
- { key: "scanId", label: "Scan ID" },
2926
- { key: "timestamp", label: "Timestamp" },
2927
- { key: "action", label: "Action" },
2928
- { key: "profile", label: "Profile" },
2929
- { key: "app", label: "App" }
2930
- ],
2931
- format
2932
- )
2933
- );
2934
- return;
2935
- }
2936
- ui.section(`Scan Logs (${results.length} results):`);
2937
- for (const r of results) {
2938
- const action = r.action ?? r.verdict;
2939
- const app = r.app_name;
2940
- const profile = r.profile_name;
2941
- const ts2 = r.received_ts ?? r.timestamp;
2942
- const scanId = r.scan_id;
2943
- const actionColor = action === "block" ? chalk9.red : chalk9.green;
2944
- if (scanId) ui.dim(scanId);
2945
- console.log(
2946
- ` ${ts2 ? chalk9.dim(ts2) : ""} ${action ? actionColor(action) : ""} ${profile ? `[${profile}]` : ""} ${app ?? ""}`
2947
- );
2948
- }
2949
- if (pageToken) {
2950
- console.log();
2951
- ui.dim(`Page token: ${pageToken}`);
2952
- }
2953
- console.log();
2954
- }
2955
2918
 
2956
2919
  // src/cli/confirm.ts
2957
2920
  async function confirmOrAbort(message, force, options = {}) {
@@ -3089,8 +3052,8 @@ function registerAiGatewayInference(parent) {
3089
3052
  process.once("SIGINT", interrupt);
3090
3053
  process.once("SIGTERM", interrupt);
3091
3054
  process.stdout.on("error", outputError);
3092
- const write = (text) => new Promise((resolve3, reject) => {
3093
- process.stdout.write(text, (error) => error ? reject(error) : resolve3());
3055
+ const write = (text2) => new Promise((resolve4, reject) => {
3056
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve4());
3094
3057
  });
3095
3058
  const requestOptions = { signal: controller.signal };
3096
3059
  const result = kind === "chat" ? await client.createChatCompletion(
@@ -3109,16 +3072,16 @@ function registerAiGatewayInference(parent) {
3109
3072
  if (format === "json") await write(`${JSON.stringify(event)}
3110
3073
  `);
3111
3074
  else {
3112
- const text = responseText(event);
3113
- if (text) await write(text);
3075
+ const text2 = responseText(event);
3076
+ if (text2) await write(text2);
3114
3077
  }
3115
3078
  if ("type" in event && (event.type === "response.failed" || event.type === "response.incomplete"))
3116
3079
  throw new Error(`Gateway generation ended with ${event.type}`);
3117
3080
  }
3118
3081
  if (format === "pretty") await write("\n");
3119
3082
  } else {
3120
- const text = format === "yaml" ? dump3(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
3121
- await write(`${text}
3083
+ const text2 = format === "yaml" ? dump3(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
3084
+ await write(`${text2}
3122
3085
  `);
3123
3086
  if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
3124
3087
  throw new Error(`Gateway generation ended with ${result.status}`);
@@ -3188,6 +3151,7 @@ import {
3188
3151
  import { dirname, join } from "path";
3189
3152
  var AIRS_DOMAINS = [
3190
3153
  "api.sase.paloaltonetworks.com",
3154
+ "api.apps.paloaltonetworks.com",
3191
3155
  "service.api.aisecurity.paloaltonetworks.com",
3192
3156
  "auth.apps.paloaltonetworks.com",
3193
3157
  "api.dlp.paloaltonetworks.com"
@@ -3269,11 +3233,12 @@ function installDebugLogger(logPath) {
3269
3233
  if (!isAirsUrl(url) && !inference) {
3270
3234
  return originalFetch(input, init2);
3271
3235
  }
3236
+ const dashboard = /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url);
3272
3237
  const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
3273
3238
  const reqHeaders = redactHeaders(rawHeaders);
3274
3239
  const loggedUrl = redactUrl(url);
3275
3240
  let reqBody;
3276
- if (inference) reqBody = "[BODY OMITTED]";
3241
+ if (inference || dashboard) reqBody = "[BODY OMITTED]";
3277
3242
  else if (init2?.body) {
3278
3243
  try {
3279
3244
  reqBody = redactDeep(JSON.parse(String(init2.body)));
@@ -3306,14 +3271,14 @@ function installDebugLogger(logPath) {
3306
3271
  response.headers.forEach((v, k) => {
3307
3272
  resHeaders[k] = v;
3308
3273
  });
3309
- if (inference || response.headers.get("content-type")?.includes("text/event-stream")) {
3274
+ if (inference || dashboard || response.headers.get("content-type")?.includes("text/event-stream")) {
3310
3275
  resBody = "[BODY OMITTED]";
3311
3276
  } else {
3312
3277
  const clone = response.clone();
3313
3278
  try {
3314
- const text = await clone.text();
3279
+ const text2 = await clone.text();
3315
3280
  try {
3316
- resBody = redactDeep(JSON.parse(text));
3281
+ resBody = redactDeep(JSON.parse(text2));
3317
3282
  } catch {
3318
3283
  resBody = "[NON-JSON BODY OMITTED]";
3319
3284
  }
@@ -3370,8 +3335,8 @@ function addWriteOutput(command) {
3370
3335
  async function readGatewayRequest(path3) {
3371
3336
  let parsed;
3372
3337
  try {
3373
- const text = await readFile2(path3, "utf8");
3374
- parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(text) : JSON.parse(text);
3338
+ const text2 = await readFile2(path3, "utf8");
3339
+ parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(text2) : JSON.parse(text2);
3375
3340
  } catch (error) {
3376
3341
  throw new CliUsageError(
3377
3342
  `Unable to parse --file '${path3}' as JSON or YAML: ${error instanceof Error ? error.message : String(error)}`
@@ -3586,9 +3551,9 @@ function parseBooleanOption(value) {
3586
3551
  throw new CliUsageError("Expected true or false");
3587
3552
  }
3588
3553
  function parseIntegerOption(value) {
3589
- const text = String(value);
3590
- if (!/^-?(0|[1-9]\d*)$/.test(text)) throw new CliUsageError("Expected an integer");
3591
- const parsed = Number(text);
3554
+ const text2 = String(value);
3555
+ if (!/^-?(0|[1-9]\d*)$/.test(text2)) throw new CliUsageError("Expected an integer");
3556
+ const parsed = Number(text2);
3592
3557
  if (!Number.isSafeInteger(parsed)) throw new CliUsageError("Expected a safe integer");
3593
3558
  return parsed;
3594
3559
  }
@@ -3632,14 +3597,14 @@ function parseModelBindingsOption(value) {
3632
3597
  }
3633
3598
  function parseCapabilityBindingsOption(value) {
3634
3599
  return repeatableValues(value).map((raw) => {
3635
- const [identity, enabled] = splitKeyValue(raw, "type:name");
3636
- const separator = identity.indexOf(":");
3637
- if (separator <= 0 || separator === identity.length - 1) {
3600
+ const [identity2, enabled] = splitKeyValue(raw, "type:name");
3601
+ const separator = identity2.indexOf(":");
3602
+ if (separator <= 0 || separator === identity2.length - 1) {
3638
3603
  throw new CliUsageError("Expected type:name=enabled");
3639
3604
  }
3640
3605
  return {
3641
- type: identity.slice(0, separator),
3642
- name: identity.slice(separator + 1),
3606
+ type: identity2.slice(0, separator),
3607
+ name: identity2.slice(separator + 1),
3643
3608
  enabled: parseBooleanOption(enabled)
3644
3609
  };
3645
3610
  });
@@ -3660,12 +3625,12 @@ function parseAssignment(raw, forceString) {
3660
3625
  const separator = raw.indexOf("=");
3661
3626
  if (separator <= 0) throw new CliUsageError("Expected path=value");
3662
3627
  const path3 = raw.slice(0, separator);
3663
- const text = raw.slice(separator + 1);
3664
- if (forceString) return [path3, text];
3628
+ const text2 = raw.slice(separator + 1);
3629
+ if (forceString) return [path3, text2];
3665
3630
  try {
3666
- return [path3, JSON.parse(text)];
3631
+ return [path3, JSON.parse(text2)];
3667
3632
  } catch {
3668
- return [path3, text];
3633
+ return [path3, text2];
3669
3634
  }
3670
3635
  }
3671
3636
  function schemaMessage(error) {
@@ -4589,11 +4554,11 @@ function addWindowOptions(command) {
4589
4554
  );
4590
4555
  }
4591
4556
  function windowFrom(opts) {
4592
- const window = { workspaceSlug: opts.workspace };
4593
- if (opts.start) window.start = parseNamedDate2(opts.start, "--start");
4594
- else window.days = parsePositiveInteger2(opts.days ?? "7", "--days");
4595
- if (opts.end) window.end = parseNamedDate2(opts.end, "--end");
4596
- return window;
4557
+ const window2 = { workspaceSlug: opts.workspace };
4558
+ if (opts.start) window2.start = parseNamedDate2(opts.start, "--start");
4559
+ else window2.days = parsePositiveInteger2(opts.days ?? "7", "--days");
4560
+ if (opts.end) window2.end = parseNamedDate2(opts.end, "--end");
4561
+ return window2;
4597
4562
  }
4598
4563
  function parsePositiveInteger2(value, flag) {
4599
4564
  try {
@@ -5331,8 +5296,8 @@ async function withTimeout(promise, ms) {
5331
5296
  try {
5332
5297
  return await Promise.race([
5333
5298
  promise,
5334
- new Promise((resolve3) => {
5335
- timer = setTimeout(() => resolve3(TIMED_OUT), ms);
5299
+ new Promise((resolve4) => {
5300
+ timer = setTimeout(() => resolve4(TIMED_OUT), ms);
5336
5301
  })
5337
5302
  ]);
5338
5303
  } finally {
@@ -5602,12 +5567,12 @@ function hasBin(bin) {
5602
5567
  }
5603
5568
  }
5604
5569
  function run(bin, args, label) {
5605
- return new Promise((resolve3, reject) => {
5570
+ return new Promise((resolve4, reject) => {
5606
5571
  const child = spawn(bin, args, { stdio: "inherit" });
5607
5572
  child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
5608
5573
  child.on("close", (code) => {
5609
5574
  if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
5610
- else resolve3();
5575
+ else resolve4();
5611
5576
  });
5612
5577
  });
5613
5578
  }
@@ -5928,8 +5893,8 @@ function registerModelSecurityCommand(program) {
5928
5893
  const fmt = opts.output;
5929
5894
  if (fmt === "pretty") renderModelSecurityHeader();
5930
5895
  const service = await createService2();
5931
- const scan = await service.getScan(uuid);
5932
- renderMsScanDetail(scan, fmt);
5896
+ const scan2 = await service.getScan(uuid);
5897
+ renderMsScanDetail(scan2, fmt);
5933
5898
  } catch (err) {
5934
5899
  fail(err);
5935
5900
  }
@@ -5939,9 +5904,9 @@ function registerModelSecurityCommand(program) {
5939
5904
  renderModelSecurityHeader();
5940
5905
  const service = await createService2();
5941
5906
  const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
5942
- const scan = await service.createScan(config);
5943
- ui.success(`Scan created: ${scan.uuid}`);
5944
- renderMsScanDetail(scan);
5907
+ const scan2 = await service.createScan(config);
5908
+ ui.success(`Scan created: ${scan2.uuid}`);
5909
+ renderMsScanDetail(scan2);
5945
5910
  } catch (err) {
5946
5911
  fail(err);
5947
5912
  }
@@ -7488,7 +7453,7 @@ function buildDataProtection(flags) {
7488
7453
  const dld = { action: flags.dlpAction };
7489
7454
  const profiles = parseList(flags.dlpProfiles);
7490
7455
  if (profiles) {
7491
- dld.member = profiles.map((text) => ({ text }));
7456
+ dld.member = profiles.map((text2) => ({ text: text2 }));
7492
7457
  }
7493
7458
  if (flags.maskDataInline != null) {
7494
7459
  dld["mask-data-inline"] = flags.maskDataInline;
@@ -8975,15 +8940,279 @@ function registerCleanupCommand(parent) {
8975
8940
  });
8976
8941
  }
8977
8942
 
8978
- // src/cli/commands/runtime-report.ts
8943
+ // src/cli/commands/runtime-dashboard.ts
8979
8944
  import { lstat } from "fs/promises";
8980
8945
  import { resolve as resolve2 } from "path";
8946
+ import { dump as dump5 } from "js-yaml";
8947
+ var TIME_UNITS = ["hour", "hours", "day", "days"];
8948
+ function integer2(value, name, minimum = 0) {
8949
+ const n = Number(value);
8950
+ if (!/^\d+$/.test(String(value)) || !Number.isSafeInteger(n) || n < minimum)
8951
+ throw new CliUsageError(`${name} must be a safe integer >= ${minimum}`);
8952
+ return n;
8953
+ }
8954
+ function text(value, name) {
8955
+ if (typeof value !== "string" || !value.trim()) throw new CliUsageError(`${name} is required`);
8956
+ return value;
8957
+ }
8958
+ function window(opts) {
8959
+ return {
8960
+ timeInterval: integer2(opts.interval, "--interval", 1),
8961
+ timeUnit: text(opts.unit, "--unit")
8962
+ };
8963
+ }
8964
+ function identity(opts) {
8965
+ return {
8966
+ sessionId: text(opts.sessionId, "--session-id"),
8967
+ appId: text(opts.appId, "--app-id"),
8968
+ appName: text(opts.appName, "--app-name"),
8969
+ ...window(opts)
8970
+ };
8971
+ }
8972
+ function scan(opts) {
8973
+ return {
8974
+ scanId: text(opts.scanId, "--scan-id"),
8975
+ scanSubReqId: integer2(opts.scanSubReqId, "--scan-sub-req-id")
8976
+ };
8977
+ }
8978
+ function timeOptions(command, interval = "1", unit = "day", allowedUnits = TIME_UNITS) {
8979
+ return command.option("--interval <n>", "Look-back interval", interval).option(
8980
+ "--unit <unit>",
8981
+ `Time unit: ${allowedUnits.join(", ")}`,
8982
+ (value) => {
8983
+ if (!allowedUnits.includes(value))
8984
+ usageError(
8985
+ `Unsupported --unit. Supported units: ${allowedUnits.join(", ")}. For one week, use --interval 7 --unit days.`
8986
+ );
8987
+ return value;
8988
+ },
8989
+ unit
8990
+ );
8991
+ }
8992
+ function pageOptions(command) {
8993
+ return command.option("--limit <n>", "Items per page", "25").option("--offset <n>", "Item offset", "0");
8994
+ }
8995
+ function sessionOptions(command) {
8996
+ return timeOptions(
8997
+ command.requiredOption("--session-id <id>", "Session identity").requiredOption("--app-id <id>", "Application identity").requiredOption("--app-name <name>", "Exact dashboard application name"),
8998
+ "30",
8999
+ "days"
9000
+ );
9001
+ }
9002
+ function scanOptions(command) {
9003
+ return command.requiredOption("--scan-id <id>", "Scan identity").requiredOption("--scan-sub-req-id <n>", "Sub-request index, including zero");
9004
+ }
9005
+ function registerRuntimeDashboardCommands(runtime) {
9006
+ const dashboard = runtime.command("dashboard").description("SCM application summaries and daily telemetry (OAuth)");
9007
+ const sessions = runtime.command("sessions").description("Verified SCM session inventory and drill-down (OAuth)");
9008
+ function register6(command, prepare) {
9009
+ command.option("--output <format>", "Output format: pretty, json, yaml").action(async (opts) => {
9010
+ try {
9011
+ const read = prepare(opts);
9012
+ const format = await resolveOutput(command, opts, {
9013
+ allowed: ["pretty", "json", "yaml"]
9014
+ });
9015
+ if (command.name() === "scan-content") {
9016
+ if (Boolean(opts.showContent) === Boolean(opts.outputFile))
9017
+ throw new CliUsageError(
9018
+ "Scan content requires exactly one of --show-content or --output-file <new-path>"
9019
+ );
9020
+ if (opts.outputFile) {
9021
+ const path3 = resolve2(text(opts.outputFile, "--output-file"));
9022
+ const existing = await lstat(path3).catch((error) => {
9023
+ if (error.code !== "ENOENT") throw error;
9024
+ return void 0;
9025
+ });
9026
+ if (existing) throw new Error("Destination exists");
9027
+ }
9028
+ }
9029
+ const config = await loadConfig();
9030
+ const client = getOrCreateManagementClient({
9031
+ ...managementClientOptions(config),
9032
+ numRetries: 0
9033
+ });
9034
+ const result = await read(client.dashboard);
9035
+ const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
9036
+ if (command.name() === "scan-content" && opts.outputFile) {
9037
+ await writeReportFile(resolve2(String(opts.outputFile)), `${output}
9038
+ `);
9039
+ ui.status("Scan content exported to the requested private file (0600).");
9040
+ } else console.log(output);
9041
+ } catch (error) {
9042
+ if (error instanceof CliUsageError) usageError(error.message);
9043
+ const status = error?.statusCode;
9044
+ ui.error(
9045
+ `Dashboard operation failed${typeof status === "number" && status >= 400 && status <= 599 ? ` (HTTP ${status})` : ""}. Check credentials, query options, and any output destination. No existing file is overwritten.`
9046
+ );
9047
+ process.exitCode = 1;
9048
+ }
9049
+ });
9050
+ }
9051
+ register6(
9052
+ pageOptions(
9053
+ timeOptions(
9054
+ dashboard.command("applications").description("One page of application activity"),
9055
+ "1",
9056
+ "day",
9057
+ ["day", "days", "hour"]
9058
+ )
9059
+ ),
9060
+ (opts) => {
9061
+ const interval = integer2(opts.interval, "--interval", 1);
9062
+ const unit = text(opts.unit, "--unit");
9063
+ if (![1, 7, 30, 60].includes(interval) || !["day", "days", "hour"].includes(unit))
9064
+ throw new CliUsageError(
9065
+ "Application overview supports interval 1/7/30/60 and unit day/days/hour"
9066
+ );
9067
+ const query = {
9068
+ timeInterval: interval,
9069
+ timeUnit: unit,
9070
+ limit: integer2(opts.limit, "--limit", 1),
9071
+ offset: integer2(opts.offset, "--offset")
9072
+ };
9073
+ return (client) => client.applicationsOverview(query);
9074
+ }
9075
+ );
9076
+ for (const [name, method] of [
9077
+ ["application", "application"],
9078
+ ["application-violations", "applicationViolationBreakdown"]
9079
+ ]) {
9080
+ register6(
9081
+ timeOptions(
9082
+ dashboard.command(name).requiredOption("--app-id <id>", "Application identity").requiredOption("--app-name <name>", "Exact dashboard application name"),
9083
+ "30",
9084
+ "days",
9085
+ ["days"]
9086
+ ),
9087
+ (opts) => {
9088
+ const interval = integer2(opts.interval, "--interval", 1);
9089
+ if (![7, 30, 60].includes(interval) || opts.unit !== "days")
9090
+ throw new CliUsageError("Application detail requires 7, 30, or 60 days");
9091
+ const query = {
9092
+ appId: text(opts.appId, "--app-id"),
9093
+ appName: text(opts.appName, "--app-name"),
9094
+ timeInterval: interval,
9095
+ timeUnit: "days"
9096
+ };
9097
+ return (client) => client[method](query);
9098
+ }
9099
+ );
9100
+ }
9101
+ for (const [name, method, days] of [
9102
+ ["top-applications", "topApplicationsViolations", false],
9103
+ ["violations-trend", "applicationsViolationsTrend", false],
9104
+ ["apps-list", "appsList", true]
9105
+ ]) {
9106
+ register6(
9107
+ timeOptions(dashboard.command(name), days ? "30" : "1", days ? "days" : "day"),
9108
+ (opts) => {
9109
+ const query = window(opts);
9110
+ return (client) => client[method](query);
9111
+ }
9112
+ );
9113
+ }
9114
+ register6(timeOptions(sessions.command("chart")), (opts) => {
9115
+ const query = window(opts);
9116
+ return (client) => client.sessionsChart(query);
9117
+ });
9118
+ register6(
9119
+ pageOptions(
9120
+ timeOptions(
9121
+ sessions.command("list").description("Session entries; --all walks pages with completeness checks")
9122
+ )
9123
+ ).option("--all", "Walk all pages (up to 100 pages)").option("--max <n>", "Item cap with --all; 0 removes item cap", "10000"),
9124
+ (opts) => {
9125
+ const query = {
9126
+ ...window(opts),
9127
+ limit: integer2(opts.limit, "--limit", 1),
9128
+ offset: integer2(opts.offset, "--offset")
9129
+ };
9130
+ const maximum = integer2(opts.max, "--max");
9131
+ return async (client) => {
9132
+ const items = [];
9133
+ const seen = /* @__PURE__ */ new Set();
9134
+ let expectedTotal;
9135
+ let offset = query.offset;
9136
+ for (let pageIndex = 0; pageIndex < 100; pageIndex++) {
9137
+ const page = await client.sessionsOverview({ ...query, offset });
9138
+ if (!opts.all) {
9139
+ ui.status(
9140
+ `Returned ${page.items.length} of ${page.pagination.total_items ?? "unknown"} sessions; this is one page.`
9141
+ );
9142
+ return page.items;
9143
+ }
9144
+ const total = page.pagination.total_items;
9145
+ if (!Number.isSafeInteger(total) || total < 0 || page.pagination.skip !== offset || page.pagination.limit !== query.limit || page.items.length > query.limit || expectedTotal !== void 0 && total !== expectedTotal)
9146
+ throw new Error("Unstable pagination");
9147
+ expectedTotal = total;
9148
+ for (const item of page.items) {
9149
+ const key = JSON.stringify([
9150
+ item.application_id,
9151
+ item.application_name,
9152
+ item.session_id
9153
+ ]);
9154
+ if (seen.has(key)) throw new Error("Overlapping session pages");
9155
+ seen.add(key);
9156
+ items.push(item);
9157
+ }
9158
+ offset += page.items.length;
9159
+ if (offset > total || page.items.length === 0 && offset < total)
9160
+ throw new Error("Incomplete inventory");
9161
+ if (maximum > 0 && items.length > maximum) {
9162
+ ui.status("Session item cap reached; output is partial.");
9163
+ process.exitCode = 1;
9164
+ return items.slice(0, maximum);
9165
+ }
9166
+ if (offset === total) {
9167
+ ui.status(
9168
+ `Retrieved ${items.length} sessions from offset ${query.offset}; pagination complete.`
9169
+ );
9170
+ return items;
9171
+ }
9172
+ if (maximum > 0 && items.length === maximum) {
9173
+ ui.status("Session item cap reached; output is partial.");
9174
+ process.exitCode = 1;
9175
+ return items;
9176
+ }
9177
+ }
9178
+ ui.status("Session page cap reached; output is partial.");
9179
+ process.exitCode = 1;
9180
+ return items;
9181
+ };
9182
+ }
9183
+ );
9184
+ register6(pageOptions(sessionOptions(sessions.command("get"))), (opts) => {
9185
+ const query = {
9186
+ ...identity(opts),
9187
+ limit: integer2(opts.limit, "--limit", 1),
9188
+ offset: integer2(opts.offset, "--offset")
9189
+ };
9190
+ return (client) => client.session(query);
9191
+ });
9192
+ register6(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
9193
+ const query = { ...identity(opts), ...scan(opts) };
9194
+ return (client) => client.sessionTransaction(query);
9195
+ });
9196
+ register6(
9197
+ scanOptions(
9198
+ sessions.command("scan-content").description("Explicit sensitive scan-content read; never automatically fetched")
9199
+ ).option("--show-content", "Explicitly display stored content on stdout").option("--output-file <path>", "Private new file for stored content (no overwrite)"),
9200
+ (opts) => {
9201
+ const query = scan(opts);
9202
+ return (client) => client.scanContent(query);
9203
+ }
9204
+ );
9205
+ }
9206
+
9207
+ // src/cli/commands/runtime-report.ts
9208
+ import { lstat as lstat2 } from "fs/promises";
9209
+ import { resolve as resolve3 } from "path";
8981
9210
  import { randomUUID as randomUUID4 } from "crypto";
8982
9211
  function registerRuntimeReportCommand(runtime) {
8983
9212
  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(
8984
9213
  "--output-file <path>",
8985
9214
  "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
8986
- ).option("--title <text>", "Report title", "Daily environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (100 records/page)", "10").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
9215
+ ).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(
8987
9216
  "after",
8988
9217
  examples(
8989
9218
  "airs runtime report --output-file ./airs-daily.html",
@@ -9003,10 +9232,10 @@ function registerRuntimeReportCommand(runtime) {
9003
9232
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
9004
9233
  usageError("--output-file cannot be empty");
9005
9234
  const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID4().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9006
- const outputPath = opts.outputFile === "-" ? void 0 : resolve2(opts.outputFile ?? defaultName);
9235
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve3(opts.outputFile ?? defaultName);
9007
9236
  try {
9008
9237
  if (outputPath) {
9009
- const existing = await lstat(outputPath).catch((error) => {
9238
+ const existing = await lstat2(outputPath).catch((error) => {
9010
9239
  if (error.code !== "ENOENT") throw error;
9011
9240
  return void 0;
9012
9241
  });
@@ -9014,11 +9243,7 @@ function registerRuntimeReportCommand(runtime) {
9014
9243
  }
9015
9244
  const config = await loadConfig();
9016
9245
  const client = getOrCreateManagementClient({
9017
- clientId: config.mgmtClientId,
9018
- clientSecret: config.mgmtClientSecret,
9019
- tsgId: config.mgmtTsgId,
9020
- apiEndpoint: config.mgmtEndpoint,
9021
- tokenEndpoint: config.mgmtTokenEndpoint,
9246
+ ...managementClientOptions(config),
9022
9247
  numRetries: 0
9023
9248
  });
9024
9249
  ui.status("Collecting daily runtime activity and current configuration (read-only)...");
@@ -9293,14 +9518,14 @@ async function evalTopic(scanner, profileName, topicName, cases, concurrency = 5
9293
9518
  throw new Error(`scanBatch returned ${scanResults.length} results for ${cases.length} prompts`);
9294
9519
  }
9295
9520
  const testResults = cases.map((testCase, i) => {
9296
- const scan = scanResults[i];
9521
+ const scan2 = scanResults[i];
9297
9522
  return {
9298
9523
  testCase,
9299
- actualTriggered: scan.triggered,
9300
- scanAction: scan.action,
9301
- scanId: scan.scanId,
9302
- reportId: scan.reportId,
9303
- correct: testCase.expectedTriggered === scan.triggered
9524
+ actualTriggered: scan2.triggered,
9525
+ scanAction: scan2.action,
9526
+ scanId: scan2.scanId,
9527
+ reportId: scan2.reportId,
9528
+ correct: testCase.expectedTriggered === scan2.triggered
9304
9529
  };
9305
9530
  });
9306
9531
  const metrics = computeMetrics(testResults);
@@ -9523,16 +9748,12 @@ function parsePositiveInteger3(value, optionName2) {
9523
9748
  }
9524
9749
  async function createMgmtService() {
9525
9750
  const config = await loadConfig();
9526
- return new SdkManagementService({
9527
- clientId: config.mgmtClientId,
9528
- clientSecret: config.mgmtClientSecret,
9529
- tsgId: config.mgmtTsgId,
9530
- tokenEndpoint: config.mgmtTokenEndpoint
9531
- });
9751
+ return new SdkManagementService(managementClientOptions(config));
9532
9752
  }
9533
9753
  function registerRuntimeCommand(program) {
9534
9754
  const runtime = program.command("runtime").description("Runtime prompt scanning against AIRS profiles");
9535
9755
  registerRuntimeReportCommand(runtime);
9756
+ registerRuntimeDashboardCommands(runtime);
9536
9757
  const apiKeys = runtime.command("api-keys").description("Manage AIRS API keys");
9537
9758
  const apiKeysList = registerListFlags(apiKeys.command("list"), { dialect: "offset" }).description("List API keys").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
9538
9759
  try {
@@ -10191,26 +10412,14 @@ function registerRuntimeCommand(program) {
10191
10412
  fail(err);
10192
10413
  }
10193
10414
  });
10194
- const scanLogs = runtime.command("scan-logs").description("Query AIRS scan logs");
10195
- const scanLogsQuery = scanLogs.command("query").description("Query scan logs").requiredOption("--interval <n>", "Time interval").requiredOption("--unit <unit>", "Time unit (hours)").option("--filter <filter>", "Filter: all, benign, threat", "all").option("--limit <n>", "Max results per page (API page size)", "50").option("--offset <n>", "Starting offset \u2014 rounds down to a page boundary", "0").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml");
10415
+ const scanLogs = runtime.command("scan-logs").description("BROKEN legacy retrieval \u2014 under refactor; use runtime sessions");
10416
+ const scanLogsQuery = scanLogs.command("query").description("Unavailable legacy query; use runtime sessions list").requiredOption("--interval <n>", "Time interval").requiredOption("--unit <unit>", "Time unit (hours)").option("--filter <filter>", "Filter: all, benign, threat", "all").option("--limit <n>", "Max results per page (API page size)", "50").option("--offset <n>", "Starting offset \u2014 rounds down to a page boundary", "0").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml");
10196
10417
  registerPageAliases(scanLogsQuery, { sizeFlag: "--page-size", sizeKey: "pageSize" });
10197
- scanLogsQuery.action(async (opts) => {
10198
- try {
10199
- const { page, size } = resolvePageParams(scanLogsQuery, opts, { indexBase: 1 });
10200
- const fmt = await resolveOutput(scanLogsQuery, opts);
10201
- if (fmt === "pretty") renderRuntimeConfigHeader();
10202
- const service = await createMgmtService();
10203
- const result = await service.queryScanLogs({
10204
- timeInterval: Number.parseInt(opts.interval, 10),
10205
- timeUnit: opts.unit,
10206
- pageNumber: page ?? 1,
10207
- pageSize: size ?? 50,
10208
- filter: opts.filter
10209
- });
10210
- renderScanLogList(result.results, result.pageToken, fmt);
10211
- } catch (err) {
10212
- fail(err);
10213
- }
10418
+ scanLogsQuery.action(() => {
10419
+ ui.error(
10420
+ "Legacy scan-logs retrieval is broken and under refactor. Empty results do not mean no activity. Use airs runtime sessions list --interval 1 --unit day --output json, or airs runtime report."
10421
+ );
10422
+ process.exitCode = 1;
10214
10423
  });
10215
10424
  const topics = runtime.command("topics").description("Manage AIRS custom topics and guardrail generation");
10216
10425
  registerApplyCommand(topics);
@@ -10361,7 +10570,7 @@ function buildProgram() {
10361
10570
  const isRuntimeReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime";
10362
10571
  if (isRuntimeReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10363
10572
  usageError(
10364
- "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting scan-log content"
10573
+ "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
10365
10574
  );
10366
10575
  if (!isRuntimeReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10367
10576
  try {