@cdot65/prisma-airs-cli 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,8 @@
21
21
  - **AI Red Teaming** — adversarial scanning with static, dynamic, and custom prompt set attack modes
22
22
  - **[AI Gateway](https://cdot65.github.io/prisma-airs-cli/cli/aigateway/workspaces/)** — scoped/admin workspace management and workspace cost telemetry
23
23
  - **Model Security** — ML model supply chain scanning with security groups, rules, and violation tracking
24
+ - **Unified automation output** — every read command supports `pretty`, `table`, `markdown`, `csv`, `json`, and `yaml`, with pipe-safe stdout
25
+ - **Complete pagination** — consistent `--limit`, `--offset`, and `--all` traversal with a configurable safety cap
24
26
  - **`airs doctor`** — one-command diagnostics for environment, credentials, and API connectivity
25
27
  - **`airs config`** — manage `~/.prisma-airs/config.json` from the CLI (`list`, `get`, `set`, `unset`, `path`)
26
28
 
@@ -65,9 +67,31 @@ airs aigateway telemetry cost --workspace <workspace-slug> --days 30
65
67
 
66
68
  # Model security
67
69
  airs model-security scans create --config scan-config.json
70
+
71
+ # Pipe-safe read output and complete traversal
72
+ airs runtime profiles list --all --output json | jq '.[].profileName'
73
+ airs runtime topics list --all-versions --output markdown
68
74
  ```
69
75
 
70
- Bulk scans preserve one output row per input prompt in input order, including all eight runtime detector flags. Work is processed as sequential logical batches (`--batch-size 25` by default), with SDK requests capped at 20 prompts. Item-level state makes accepted and pending work resumable without duplicating CSV rows, and active jobs are locked against overlapping resumes. Runtime actions are exactly `allow`, `block`, or `failed`; failed or timed-out prompts make the command exit 1. Bulk scanning requires `@cdot65/prisma-airs-sdk` 0.13.2 or later.
76
+ Bulk scans preserve one output row per input prompt in input order, including all eight runtime detector flags. Work is processed as sequential logical batches (`--batch-size 25` by default), with SDK requests capped at 20 prompts. Item-level state makes accepted and pending work resumable without duplicating CSV rows, and active jobs are locked against overlapping resumes. Runtime actions are exactly `allow`, `block`, or `failed`; failed or timed-out prompts make the command exit 1. Version 4 uses `@cdot65/prisma-airs-sdk` 0.18.0 or later.
77
+
78
+ ## Read Output and Pagination
79
+
80
+ Read commands share one contract:
81
+
82
+ - Formats: `pretty`, `table`, `markdown`, `csv`, `json`, and `yaml`.
83
+ - JSON/YAML lists are bare arrays of complete normalized records; detail reads are complete objects.
84
+ - Table, Markdown, and CSV are stable human-oriented projections. CSV uses RFC 4180 quoting.
85
+ - Data is written to stdout; status, paging hints, warnings, and errors are written to stderr.
86
+ - Output precedence is command `--output`, global `--output`, `defaultOutput`/`PANW_CLI_OUTPUT`, then `pretty`.
87
+ - Paginated lists use `--limit`, `--offset`, and `--all`. Complete traversal is capped at 10,000 records by default; change it with `--max`, or use `--max 0` for no cap.
88
+ - Profile and topic lists return only the latest revision by default. Use `--all-versions` or `--revision` when historical revisions are needed.
89
+
90
+ ```bash
91
+ airs --output json runtime profiles list --all | jq '.[].profileName'
92
+ PANW_CLI_OUTPUT=yaml airs runtime topics get "My Topic"
93
+ airs model-security scans list --all --max 25000 --output csv > scans.csv
94
+ ```
71
95
 
72
96
  ## Documentation
73
97
 
@@ -83,7 +107,7 @@ The full guides, complete CLI reference, configuration, and architecture live on
83
107
 
84
108
  ## Configuration
85
109
 
86
- Credentials come from environment variables or `~/.prisma-airs/config.json`. At minimum: `PANW_AI_SEC_API_KEY` (scanning) and `PANW_MGMT_CLIENT_ID` / `PANW_MGMT_CLIENT_SECRET` / `PANW_MGMT_TSG_ID` (management). See [`.env.example`](.env.example) and the [configuration guide](https://cdot65.github.io/prisma-airs-cli/getting-started/configuration/) for the full list.
110
+ Credentials come from environment variables or `~/.prisma-airs/config.json`. At minimum: `PANW_AI_SEC_API_KEY` (scanning) and `PANW_MGMT_CLIENT_ID` / `PANW_MGMT_CLIENT_SECRET` / `PANW_MGMT_TSG_ID` (management). Set `defaultOutput` in the config file or `PANW_CLI_OUTPUT` in the environment to choose a default read format. See [`.env.example`](.env.example) and the [configuration guide](https://cdot65.github.io/prisma-airs-cli/getting-started/configuration/) for the full list.
87
111
 
88
112
  ## License
89
113
 
@@ -21,7 +21,14 @@ var SdkManagementService = class {
21
21
  return { message: response.message };
22
22
  }
23
23
  async listTopics() {
24
- const response = await this.client.topics.list();
24
+ return this.client.topics.listAll();
25
+ }
26
+ async listLatestTopics(opts) {
27
+ const response = await this.client.topics.list({
28
+ latestOnly: true,
29
+ limit: opts?.limit,
30
+ offset: opts?.offset
31
+ });
25
32
  return response.custom_topics;
26
33
  }
27
34
  async getTopic(topicId) {
@@ -32,9 +39,9 @@ var SdkManagementService = class {
32
39
  }
33
40
  async getTopicByName(topicName) {
34
41
  const topics = await this.listTopics();
35
- const topic = topics.find((t) => t.topic_name === topicName);
36
- if (!topic) throw new Error(`Topic "${topicName}" not found`);
37
- return topic;
42
+ const matches = topics.filter((topic) => topic.topic_name === topicName);
43
+ if (matches.length === 0) throw new Error(`Topic "${topicName}" not found`);
44
+ return matches.reduce((latest, topic) => topic.revision > latest.revision ? topic : latest);
38
45
  }
39
46
  /**
40
47
  * Sets a single custom topic on a profile's topic-guardrails config.
@@ -53,7 +60,7 @@ var SdkManagementService = class {
53
60
  * it defaults to revision 0 (original content), not the latest.
54
61
  */
55
62
  async assignTopicsToProfile(profileName, topics, guardrailAction) {
56
- const { ai_profiles } = await this.client.profiles.list();
63
+ const ai_profiles = await this.client.profiles.listAll();
57
64
  const profile = ai_profiles.find((p) => p.profile_name === profileName);
58
65
  if (!profile?.profile_id) {
59
66
  throw new Error(`Profile "${profileName}" not found`);
@@ -102,7 +109,7 @@ var SdkManagementService = class {
102
109
  });
103
110
  }
104
111
  async getProfileTopics(profileName) {
105
- const { ai_profiles } = await this.client.profiles.list();
112
+ const ai_profiles = await this.client.profiles.listAll();
106
113
  const profile = ai_profiles.find((p) => p.profile_name === profileName);
107
114
  if (!profile?.profile_id) {
108
115
  throw new Error(`Profile "${profileName}" not found`);
@@ -170,6 +177,12 @@ var SdkManagementService = class {
170
177
  nextOffset: response.next_offset
171
178
  };
172
179
  }
180
+ async listAllProfiles(opts) {
181
+ const profiles = await this.client.profiles.listAll(opts);
182
+ return profiles.map(
183
+ (profile) => this.normalizeProfile(profile)
184
+ );
185
+ }
173
186
  async createProfile(request) {
174
187
  const response = await this.client.profiles.create(request);
175
188
  return this.normalizeProfile(response);
@@ -208,6 +221,11 @@ var SdkManagementService = class {
208
221
  nextOffset: raw.next_offset
209
222
  };
210
223
  }
224
+ async listAllApiKeys(opts) {
225
+ return (await this.client.apiKeys.listAll(opts)).map(
226
+ (key) => this.normalizeApiKey(key)
227
+ );
228
+ }
211
229
  async createApiKey(request) {
212
230
  const response = await this.client.apiKeys.create(request);
213
231
  return this.normalizeApiKey(response);
@@ -240,6 +258,11 @@ var SdkManagementService = class {
240
258
  nextOffset: raw.next_offset
241
259
  };
242
260
  }
261
+ async listAllCustomerApps(opts) {
262
+ return (await this.client.customerApps.listAll(opts)).map(
263
+ (app) => this.normalizeCustomerApp(app)
264
+ );
265
+ }
243
266
  async getCustomerApp(appName) {
244
267
  const response = await this.client.customerApps.get(appName);
245
268
  return this.normalizeCustomerApp(response);
@@ -253,11 +276,18 @@ var SdkManagementService = class {
253
276
  return this.normalizeCustomerApp(response);
254
277
  }
255
278
  async listConsumptionApps(opts) {
256
- const response = await this.client.dashboard.applicationsOverview({
257
- limit: opts?.limit ?? 100,
258
- offset: opts?.offset ?? 0
259
- });
260
- return (response.items ?? []).filter(
279
+ const limit = opts?.limit ?? 100;
280
+ let offset = opts?.offset ?? 0;
281
+ const items = [];
282
+ for (; ; ) {
283
+ const response = await this.client.dashboard.applicationsOverview({ limit, offset });
284
+ const page = response.items ?? [];
285
+ items.push(...page);
286
+ const total = response.pagination?.total_items;
287
+ offset += page.length;
288
+ if (page.length === 0 || page.length < limit || total != null && offset >= total) break;
289
+ }
290
+ return items.filter(
261
291
  (item) => typeof item.id === "string" && typeof item.name === "string"
262
292
  ).map((item) => ({
263
293
  appId: item.id,
@@ -525,6 +555,18 @@ var SdkModelSecurityService = class {
525
555
  groups: raw.security_groups.map(normalizeGroup)
526
556
  };
527
557
  }
558
+ async listAllGroups(opts = {}) {
559
+ const rows = await this.client.securityGroups.listAll({
560
+ source_types: opts.sourceTypes,
561
+ search_query: opts.searchQuery,
562
+ sort_field: opts.sortField,
563
+ sort_dir: opts.sortDir,
564
+ enabled_rules: opts.enabledRules,
565
+ limit: opts.limit,
566
+ max: opts.max
567
+ });
568
+ return rows.map((row) => normalizeGroup(row));
569
+ }
528
570
  async getGroup(uuid) {
529
571
  const response = await this.client.securityGroups.get(uuid);
530
572
  return normalizeGroup(response);
@@ -615,6 +657,15 @@ var SdkModelSecurityService = class {
615
657
  rules: raw.rules.map(normalizeRule)
616
658
  };
617
659
  }
660
+ async listAllRules(opts = {}) {
661
+ const rows = await this.client.securityRules.listAll({
662
+ source_type: opts.sourceType,
663
+ search_query: opts.searchQuery,
664
+ limit: opts.limit,
665
+ max: opts.max
666
+ });
667
+ return rows.map((row) => normalizeRule(row));
668
+ }
618
669
  async getRule(uuid) {
619
670
  const response = await this.client.securityRules.get(uuid);
620
671
  return normalizeRule(response);
@@ -641,6 +692,16 @@ var SdkModelSecurityService = class {
641
692
  scans: raw.scans.map(normalizeScan)
642
693
  };
643
694
  }
695
+ async listAllScans(opts = {}) {
696
+ const rows = await this.client.scans.listAll({
697
+ eval_outcomes: opts.evalOutcome === void 0 ? void 0 : [opts.evalOutcome],
698
+ source_types: opts.sourceType === void 0 ? void 0 : [opts.sourceType],
699
+ search_query: opts.search,
700
+ limit: opts.limit,
701
+ max: opts.max
702
+ });
703
+ return rows.map((row) => normalizeScan(row));
704
+ }
644
705
  async getScan(uuid) {
645
706
  const response = await this.client.scans.get(uuid);
646
707
  return normalizeScan(response);
@@ -743,6 +804,17 @@ var SdkModelSecurityService = class {
743
804
  models: raw.models.map(normalizeModel)
744
805
  };
745
806
  }
807
+ async listAllModels(opts = {}) {
808
+ const rows = await this.client.models.listAllModels({
809
+ search: opts.search,
810
+ search_query: opts.searchQuery,
811
+ sort_field: opts.sortField,
812
+ sort_order: opts.sortOrder,
813
+ limit: opts.limit,
814
+ max: opts.max
815
+ });
816
+ return rows.map((row) => normalizeModel(row));
817
+ }
746
818
  async getModel(uuid) {
747
819
  const response = await this.client.models.getModel(uuid);
748
820
  return normalizeModel(response);
@@ -1168,29 +1240,14 @@ var SdkRedTeamService = class {
1168
1240
  return await this.client.targets.getTargetTemplates();
1169
1241
  }
1170
1242
  async listTargets() {
1171
- const all = [];
1172
- let skip = 0;
1173
- const limit = 100;
1174
- for (; ; ) {
1175
- const response = this.client.targets.list({ skip, limit });
1176
- const body = await response;
1177
- const data = body.data;
1178
- for (const t of data) {
1179
- all.push({
1180
- uuid: t.uuid,
1181
- name: t.name,
1182
- status: t.status,
1183
- targetType: t.target_type,
1184
- active: t.active
1185
- });
1186
- }
1187
- const pagination = body.pagination;
1188
- if (!pagination || all.length >= (pagination.total ?? data.length) || data.length < limit) {
1189
- break;
1190
- }
1191
- skip += limit;
1192
- }
1193
- return all;
1243
+ const targets = await this.client.targets.listAll({ limit: 100 });
1244
+ return targets.map((target) => ({
1245
+ uuid: target.uuid,
1246
+ name: target.name,
1247
+ status: String(target.status ?? ""),
1248
+ targetType: target.target_type == null ? void 0 : String(target.target_type),
1249
+ active: target.active
1250
+ }));
1194
1251
  }
1195
1252
  async getTarget(uuid) {
1196
1253
  const response = await this.client.targets.get(uuid);
@@ -1262,11 +1319,22 @@ var SdkRedTeamService = class {
1262
1319
  if (opts?.jobType) sdkOpts.job_type = opts.jobType;
1263
1320
  if (opts?.targetId) sdkOpts.target_id = opts.targetId;
1264
1321
  if (opts?.limit) sdkOpts.limit = opts.limit;
1322
+ if (opts?.offset !== void 0) sdkOpts.skip = opts.offset;
1265
1323
  const response = await this.client.scans.list(sdkOpts);
1266
1324
  return response.data.map(
1267
1325
  normalizeJob
1268
1326
  );
1269
1327
  }
1328
+ async listAllScans(opts) {
1329
+ const sdkOpts = {};
1330
+ if (opts?.status) sdkOpts.status = opts.status;
1331
+ if (opts?.jobType) sdkOpts.job_type = opts.jobType;
1332
+ if (opts?.targetId) sdkOpts.target_id = opts.targetId;
1333
+ if (opts?.limit) sdkOpts.limit = opts.limit;
1334
+ if (opts?.max !== void 0) sdkOpts.max = opts.max;
1335
+ const rows = await this.client.scans.listAll(sdkOpts);
1336
+ return rows.map((row) => normalizeJob(row));
1337
+ }
1270
1338
  async abortScan(jobId) {
1271
1339
  await this.client.scans.abort(jobId);
1272
1340
  }
@@ -1402,6 +1470,18 @@ var SdkRedTeamService = class {
1402
1470
  totalItems: pagination?.total_items
1403
1471
  };
1404
1472
  }
1473
+ async listAllChannels(opts = {}) {
1474
+ const limit = opts.limit ?? 100;
1475
+ const cap = opts.max === 0 ? Number.POSITIVE_INFINITY : opts.max ?? 1e4;
1476
+ const rows = [];
1477
+ for (let offset = 0; rows.length < cap; offset += limit) {
1478
+ const page = await this.listChannels({ ...opts, limit, offset });
1479
+ rows.push(...page.channels.slice(0, cap - rows.length));
1480
+ if (page.channels.length < limit || rows.length >= (page.totalItems ?? Number.POSITIVE_INFINITY))
1481
+ break;
1482
+ }
1483
+ return rows;
1484
+ }
1405
1485
  async getChannel(channelId) {
1406
1486
  const raw = await this.client.networkBroker.getChannel(channelId);
1407
1487
  return normalizeChannel(raw);
@@ -1474,6 +1554,14 @@ var SdkRedTeamService = class {
1474
1554
  totalItems: pagination?.total_items
1475
1555
  };
1476
1556
  }
1557
+ async listAllAdapters(opts = {}) {
1558
+ const sdkOpts = {};
1559
+ if (opts.limit != null) sdkOpts.limit = opts.limit;
1560
+ if (opts.search) sdkOpts.search = opts.search;
1561
+ if (opts.max !== void 0) sdkOpts.max = opts.max;
1562
+ const rows = await this.client.adapters.listAll(sdkOpts);
1563
+ return rows.map((row) => normalizeAdapterListItem(row));
1564
+ }
1477
1565
  async getAdapter(uuid) {
1478
1566
  const raw = await this.client.adapters.get(uuid);
1479
1567
  return normalizeAdapterDetail(raw);
@@ -2168,6 +2256,7 @@ var ConfigSchema = z.object({
2168
2256
  aiGwTokenEndpoint: z.string().optional(),
2169
2257
  // Tuning
2170
2258
  scanConcurrency: z.coerce.number().int().min(1).max(20).default(5),
2259
+ defaultOutput: z.enum(["pretty", "table", "markdown", "csv", "json", "yaml"]).optional(),
2171
2260
  // Persistence
2172
2261
  dataDir: z.string().default("~/.prisma-airs/runs")
2173
2262
  });
@@ -2200,6 +2289,7 @@ function fromEnv() {
2200
2289
  aiGwAdminEndpoint: env.PANW_AI_GW_ADMIN_ENDPOINT,
2201
2290
  aiGwTokenEndpoint: env.PANW_AI_GW_TOKEN_ENDPOINT,
2202
2291
  scanConcurrency: env.SCAN_CONCURRENCY,
2292
+ defaultOutput: env.PANW_CLI_OUTPUT,
2203
2293
  dataDir: env.DATA_DIR
2204
2294
  };
2205
2295
  }