@cdot65/prisma-airs-cli 6.0.0 → 6.1.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.
Files changed (2) hide show
  1. package/dist/cli/index.js +160 -67
  2. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -3713,6 +3713,7 @@ function registerAiGatewayInference(parent) {
3713
3713
  }
3714
3714
 
3715
3715
  // src/cli/commands/aigateway/inventory.ts
3716
+ import { readFile as readFile3 } from "fs/promises";
3716
3717
  import {
3717
3718
  AI_GATEWAY_DEPLOYMENT_STATUSES,
3718
3719
  AI_GATEWAY_DEPLOYMENT_TYPES,
@@ -3720,6 +3721,7 @@ import {
3720
3721
  AI_GATEWAY_KNOWN_MCP_AUTH_TYPES,
3721
3722
  AI_GATEWAY_KNOWN_MCP_TRANSPORTS,
3722
3723
  AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES,
3724
+ customHostConfiguration,
3723
3725
  GatewayApiKeyRotateRequestSchema,
3724
3726
  GatewayApiKeyUpdateRequestSchema,
3725
3727
  GatewayConfigCreateRequestSchema,
@@ -3915,6 +3917,38 @@ function installDebugLogger(logPath, options = {}) {
3915
3917
  };
3916
3918
  }
3917
3919
 
3920
+ // src/cli/tenant-input.ts
3921
+ import { input, password } from "@inquirer/prompts";
3922
+ async function promptTenantValue(message, secret = false) {
3923
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
3924
+ throw new Error(
3925
+ "Interactive setup requires a terminal. Use --config, or --tsg-id and --client-id with --client-secret-stdin. For tenant set, pass a value or use --stdin."
3926
+ );
3927
+ const prompt = secret ? password : input;
3928
+ return prompt(
3929
+ {
3930
+ message,
3931
+ validate: (value) => Boolean(value.trim()) || "A nonempty value is required"
3932
+ },
3933
+ { input: process.stdin, output: process.stderr }
3934
+ );
3935
+ }
3936
+ async function readTenantStdin() {
3937
+ if (process.stdin.isTTY) throw new Error("--stdin requires piped input");
3938
+ const chunks = [];
3939
+ let size = 0;
3940
+ for await (const chunk of process.stdin) {
3941
+ const buffer = Buffer.from(chunk);
3942
+ size += buffer.length;
3943
+ if (size > 65536) throw new Error("Configuration input exceeds 64 KiB");
3944
+ chunks.push(buffer);
3945
+ }
3946
+ const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
3947
+ if (!value.trim() || /[\r\n\0]/.test(value))
3948
+ throw new Error("Provide one nonempty configuration value on stdin");
3949
+ return value;
3950
+ }
3951
+
3918
3952
  // src/cli/commands/aigateway/shared.ts
3919
3953
  import { open, readFile as readFile2, unlink } from "fs/promises";
3920
3954
  import { AIGatewayClient as AIGatewayClient2 } from "@cdot65/prisma-airs-sdk";
@@ -4663,13 +4697,33 @@ function registerIntegrations(root) {
4663
4697
  { option: "name", path: "name" },
4664
4698
  { option: "organisationId", path: "organisation_id" },
4665
4699
  { option: "secretMappings", path: "secret_mappings", parse: parseJsonOption },
4666
- { option: "slug", path: "slug" }
4700
+ { option: "slug", path: "slug" },
4701
+ // Derived by prepareIntegrationOptions() from --base-url / --header.
4702
+ { option: "providerAuthType", path: "configurations.provider_auth_type" },
4703
+ { option: "customHost", path: "configurations.custom_host" },
4704
+ { option: "customHeaders", path: "configurations.custom_headers", parse: parseStringMapOption }
4667
4705
  ];
4668
- const addIntegrationFields = (command) => command.option("--ai-provider-id <uuid>", "Provider catalog UUID").option("--configurations <json>", "Provider configuration object").option("--description <text>", "Integration description").option("--key <credential>", "Inline provider credential (prefer secret mappings)").option("--name <name>", "Integration name").option("--organisation-id <tsg>", "Numeric TSG id").option("--secret-mappings <json>", "Secret reference mapping array").option("--slug <slug>", "Stable integration slug");
4706
+ const addIntegrationSharedFields = (command) => command.option(
4707
+ "--base-url <url>",
4708
+ "Self-hosted or OpenAI-compatible endpoint (configurations.custom_host)"
4709
+ ).option("--configurations <json>", "Provider configuration object").option("--description <text>", "Integration description").option(
4710
+ "--header <name=value>",
4711
+ "Extra header sent to the custom host (repeatable)",
4712
+ collectOption
4713
+ ).option("--key <credential>", "Inline provider credential (visible in shell history)").option("--key-file <path>", "Read the provider credential from a file").option("--key-stdin", "Read the provider credential from piped stdin").option("--name <name>", "Integration name").option("--secret-mappings <json>", "Secret reference mapping array");
4714
+ const addIntegrationFields = (command) => addIntegrationSharedFields(command).option(
4715
+ "--ai-provider <slug-or-uuid>",
4716
+ "Provider catalog slug (see 'integrations providers') or UUID"
4717
+ ).option("--ai-provider-id <uuid>", "Provider catalog UUID").option("--organisation-id <tsg>", "Numeric TSG id").option("--slug <slug>", "Stable integration slug");
4669
4718
  const integrationUpdateFields = integrationFields.filter(
4670
- (field) => ["configurations", "description", "key", "name", "secretMappings"].includes(field.option)
4719
+ (field) => !["aiProviderId", "organisationId", "slug"].includes(field.option)
4720
+ );
4721
+ const providers = addReadOutput(
4722
+ group.command("providers").description("List the provider catalog: the slug or UUID that --ai-provider accepts")
4723
+ );
4724
+ providers.action(
4725
+ (opts) => runList(providers, opts, "catalog providers", (client) => client.integrations.catalog())
4671
4726
  );
4672
- const addIntegrationUpdateFields = (command) => command.option("--configurations <json>", "Provider configuration object").option("--description <text>", "Integration description").option("--key <credential>", "Inline provider credential (prefer secret mappings)").option("--name <name>", "Integration name").option("--secret-mappings <json>", "Secret reference mapping array");
4673
4727
  const create = addWriteOutput(
4674
4728
  addStructuredInputOptions(
4675
4729
  addIntegrationFields(
@@ -4678,31 +4732,38 @@ function registerIntegrations(root) {
4678
4732
  )
4679
4733
  );
4680
4734
  create.action(
4681
- (opts) => runWrite(
4682
- create,
4683
- opts,
4684
- () => buildStructuredRequest(opts, GatewayIntegrationCreateRequestSchema, integrationFields),
4685
- (client, body) => client.integrations.create(body)
4686
- )
4735
+ (opts) => runWrite(create, opts, async (client) => {
4736
+ const prepared = await prepareIntegrationOptions(opts, client, { promptWhenMissing: true });
4737
+ const body = await buildStructuredRequest(
4738
+ prepared,
4739
+ GatewayIntegrationCreateRequestSchema,
4740
+ integrationFields
4741
+ );
4742
+ if (!body.key && !(Array.isArray(body.secret_mappings) && body.secret_mappings.length)) {
4743
+ throw new CliUsageError(
4744
+ "A provider credential is required: run in a terminal to be prompted, or pass --key-file, --key-stdin (piped), --secret-mappings, or --key (the gateway rejects a credential-less integration with a generic AB01)"
4745
+ );
4746
+ }
4747
+ return client.integrations.create(body);
4748
+ })
4687
4749
  );
4688
4750
  const update = addWriteOutput(
4689
4751
  addStructuredInputOptions(
4690
- addIntegrationUpdateFields(
4752
+ addIntegrationSharedFields(
4691
4753
  group.command("update <id>").description("Update an integration with structured flags")
4692
4754
  )
4693
4755
  )
4694
4756
  );
4695
4757
  update.action(
4696
- (id, opts) => runWrite(
4697
- update,
4698
- opts,
4699
- () => buildStructuredRequest(
4700
- opts,
4758
+ (id, opts) => runWrite(update, opts, async (client) => {
4759
+ const prepared = await prepareIntegrationOptions(opts, client);
4760
+ const body = await buildStructuredRequest(
4761
+ prepared,
4701
4762
  GatewayIntegrationUpdateRequestSchema,
4702
4763
  integrationUpdateFields
4703
- ),
4704
- (client, body) => client.integrations.update(id, body)
4705
- )
4764
+ );
4765
+ return client.integrations.update(id, body);
4766
+ })
4706
4767
  );
4707
4768
  const remove = addWriteOutput(
4708
4769
  group.command("delete <id>").description("Permanently delete this integration").requiredOption("--organisation-id <tsg>", "Numeric TSG id").option("--force", "Skip confirmation prompt")
@@ -5032,6 +5093,70 @@ function registerPlugins(root) {
5032
5093
  )
5033
5094
  );
5034
5095
  }
5096
+ async function prepareIntegrationOptions(opts, client, settings = {}) {
5097
+ const sources = [opts.key !== void 0, Boolean(opts.keyStdin), opts.keyFile !== void 0];
5098
+ if (sources.filter(Boolean).length > 1) {
5099
+ throw new CliUsageError("Use only one of --key, --key-stdin, or --key-file");
5100
+ }
5101
+ const prepared = { ...opts };
5102
+ delete prepared.keyStdin;
5103
+ delete prepared.keyFile;
5104
+ delete prepared.baseUrl;
5105
+ delete prepared.header;
5106
+ delete prepared.aiProvider;
5107
+ const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
5108
+ if (opts.keyStdin) {
5109
+ if (interactive) {
5110
+ prepared.key = await promptTenantValue("Provider API key:", true);
5111
+ } else {
5112
+ try {
5113
+ prepared.key = await readTenantStdin();
5114
+ } catch {
5115
+ throw new CliUsageError(
5116
+ "--key-stdin reads one credential from piped stdin, e.g. `airs aigateway integrations create ... --key-stdin < provider.key`; in a terminal, omit every key flag to be prompted"
5117
+ );
5118
+ }
5119
+ }
5120
+ } else if (opts.keyFile !== void 0) {
5121
+ let raw;
5122
+ try {
5123
+ raw = await readFile3(opts.keyFile, "utf8");
5124
+ } catch {
5125
+ throw new CliUsageError(`Cannot read --key-file ${opts.keyFile}`);
5126
+ }
5127
+ const value = raw.replace(/\r?\n$/, "");
5128
+ if (!value.trim() || /[\r\n\0]/.test(value)) {
5129
+ throw new CliUsageError("--key-file must contain exactly one nonempty credential");
5130
+ }
5131
+ prepared.key = value;
5132
+ } else if (opts.key !== void 0) {
5133
+ ui.warning(
5134
+ "Inline --key is visible in shell history and process listings; prefer --key-stdin, --key-file, or --secret-mappings"
5135
+ );
5136
+ } else if (settings.promptWhenMissing && interactive && opts.secretMappings === void 0 && opts.file === void 0) {
5137
+ prepared.key = await promptTenantValue("Provider API key:", true);
5138
+ }
5139
+ if (opts.aiProvider !== void 0 && opts.aiProviderId !== void 0) {
5140
+ throw new CliUsageError("Use --ai-provider or --ai-provider-id, not both");
5141
+ }
5142
+ if (opts.aiProvider !== void 0) {
5143
+ prepared.aiProviderId = await client.integrations.resolveProviderId(opts.aiProvider);
5144
+ }
5145
+ if (opts.baseUrl !== void 0) {
5146
+ let configuration;
5147
+ try {
5148
+ configuration = customHostConfiguration({ host: opts.baseUrl });
5149
+ } catch (error) {
5150
+ throw new CliUsageError(
5151
+ `Invalid --base-url: ${error instanceof Error ? error.message : String(error)}`
5152
+ );
5153
+ }
5154
+ prepared.providerAuthType = configuration.provider_auth_type;
5155
+ prepared.customHost = configuration.custom_host;
5156
+ }
5157
+ if (opts.header?.length) prepared.customHeaders = opts.header;
5158
+ return prepared;
5159
+ }
5035
5160
  function registerAiGatewayInventory(root) {
5036
5161
  registerApiKeys(root);
5037
5162
  registerAuditLogs(root);
@@ -5875,7 +6000,7 @@ function registerCompletionCommand(program) {
5875
6000
 
5876
6001
  // src/cli/commands/doctor.ts
5877
6002
  import { randomUUID as randomUUID3 } from "crypto";
5878
- import { readFile as readFile3 } from "fs/promises";
6003
+ import { readFile as readFile4 } from "fs/promises";
5879
6004
  import { init, Scanner } from "@cdot65/prisma-airs-sdk";
5880
6005
 
5881
6006
  // src/config/env.ts
@@ -5979,7 +6104,7 @@ async function checkConfigFile(context3) {
5979
6104
  const { path: path3 } = context3;
5980
6105
  let raw;
5981
6106
  try {
5982
- raw = await readFile3(path3, "utf-8");
6107
+ raw = await readFile4(path3, "utf-8");
5983
6108
  } catch {
5984
6109
  return {
5985
6110
  name,
@@ -8316,7 +8441,7 @@ function registerRedteamCommand(program) {
8316
8441
  // src/cli/commands/runtime.ts
8317
8442
  import { randomUUID as randomUUID10 } from "crypto";
8318
8443
  import * as fs5 from "fs";
8319
- import { readFile as readFile11 } from "fs/promises";
8444
+ import { readFile as readFile12 } from "fs/promises";
8320
8445
  import { basename as basename3, dirname as dirname2, join as join3, resolve as resolvePath } from "path";
8321
8446
  import chalk11 from "chalk";
8322
8447
 
@@ -8995,7 +9120,7 @@ var topicsView = {
8995
9120
  };
8996
9121
 
8997
9122
  // src/cli/commands/dlp/dictionaries.ts
8998
- import { readFile as readFile7 } from "fs/promises";
9123
+ import { readFile as readFile8 } from "fs/promises";
8999
9124
  import { basename as basename2 } from "path";
9000
9125
  import { AISecSDKException as AISecSDKException2, ErrorType as ErrorType3 } from "@cdot65/prisma-airs-sdk";
9001
9126
 
@@ -9040,7 +9165,7 @@ async function loadDlpClientOptions() {
9040
9165
  }
9041
9166
 
9042
9167
  // src/cli/commands/dlp/patch.ts
9043
- import { readFile as readFile6 } from "fs/promises";
9168
+ import { readFile as readFile7 } from "fs/promises";
9044
9169
  function buildMergePatch(opts) {
9045
9170
  const out = {};
9046
9171
  for (const entry of opts.set ?? []) {
@@ -9082,7 +9207,7 @@ function coerceValue(raw) {
9082
9207
  async function parseBody(opts) {
9083
9208
  let raw;
9084
9209
  if (opts.bodyFile) {
9085
- raw = await readFile6(opts.bodyFile, "utf-8");
9210
+ raw = await readFile7(opts.bodyFile, "utf-8");
9086
9211
  } else if (opts.body === "-") {
9087
9212
  const chunks = [];
9088
9213
  for await (const chunk of opts.stdin ?? process.stdin) {
@@ -9113,7 +9238,7 @@ function visibleRecords(records, includePredefined) {
9113
9238
 
9114
9239
  // src/cli/commands/dlp/dictionaries.ts
9115
9240
  async function readMetadata(path3) {
9116
- const raw = await readFile7(path3, "utf-8");
9241
+ const raw = await readFile8(path3, "utf-8");
9117
9242
  let value;
9118
9243
  try {
9119
9244
  value = JSON.parse(raw);
@@ -9190,7 +9315,7 @@ function register(dlp) {
9190
9315
  const format = await resolveOutput(command, opts);
9191
9316
  const metadata = await buildMetadata(opts);
9192
9317
  if (!opts.file) throw new CliUsageError("--file is required (multipart upload)");
9193
- const file = await readFile7(opts.file);
9318
+ const file = await readFile8(opts.file);
9194
9319
  const r = await new SdkDictionariesService(await loadDlpClientOptions()).create({
9195
9320
  metadata,
9196
9321
  file,
@@ -9221,7 +9346,7 @@ function register(dlp) {
9221
9346
  const metadata = await buildMetadata(opts);
9222
9347
  const format = await resolveOutput(command, opts);
9223
9348
  if (!opts.file) throw new CliUsageError("--file is required (multipart upload)");
9224
- const file = await readFile7(opts.file);
9349
+ const file = await readFile8(opts.file);
9225
9350
  const r = await new SdkDictionariesService(await loadDlpClientOptions()).replace(id, {
9226
9351
  metadata,
9227
9352
  file,
@@ -9949,7 +10074,7 @@ function register5(dlp) {
9949
10074
 
9950
10075
  // src/cli/commands/dlp/transfer.ts
9951
10076
  import { randomUUID as randomUUID7 } from "crypto";
9952
- import { readFile as readFile8, stat } from "fs/promises";
10077
+ import { readFile as readFile9, stat } from "fs/promises";
9953
10078
  import { extname, resolve as resolve5 } from "path";
9954
10079
  import { AISecSDKException as AISecSDKException4, ManagementClient } from "@cdot65/prisma-airs-sdk";
9955
10080
  import { dump as dump6, JSON_SCHEMA, load as load2 } from "js-yaml";
@@ -11256,7 +11381,7 @@ function register6(dlp) {
11256
11381
  throw new CliUsageError("Backup must be a regular file no larger than 20 MiB");
11257
11382
  let input2;
11258
11383
  try {
11259
- const text2 = await readFile8(path3, "utf8");
11384
+ const text2 = await readFile9(path3, "utf8");
11260
11385
  if (Buffer.byteLength(text2) > MAX_BACKUP_BYTES) throw new Error("Backup too large");
11261
11386
  input2 = extension === ".json" ? JSON.parse(text2) : load2(text2, { schema: JSON_SCHEMA });
11262
11387
  } catch {
@@ -11348,7 +11473,7 @@ function registerDlpCommands(runtime) {
11348
11473
 
11349
11474
  // src/cli/commands/profile-transfer.ts
11350
11475
  import { randomUUID as randomUUID8 } from "crypto";
11351
- import { readFile as readFile9, stat as stat2 } from "fs/promises";
11476
+ import { readFile as readFile10, stat as stat2 } from "fs/promises";
11352
11477
  import { extname as extname2, resolve as resolve6 } from "path";
11353
11478
  import { AISecSDKException as AISecSDKException5, ManagementClient as ManagementClient2 } from "@cdot65/prisma-airs-sdk";
11354
11479
  import { dump as dump7, JSON_SCHEMA as JSON_SCHEMA2, load as load3 } from "js-yaml";
@@ -12193,7 +12318,7 @@ function registerProfileTransferCommands(profiles2) {
12193
12318
  throw new CliUsageError("Backup must be a regular file no larger than 20 MiB");
12194
12319
  let input2;
12195
12320
  try {
12196
- const text2 = await readFile9(path3, "utf8");
12321
+ const text2 = await readFile10(path3, "utf8");
12197
12322
  if (Buffer.byteLength(text2) > MAX_BACKUP_BYTES2) throw new Error("Backup too large");
12198
12323
  input2 = extension === ".json" ? JSON.parse(text2) : load3(text2, { schema: JSON_SCHEMA2 });
12199
12324
  } catch {
@@ -12837,7 +12962,7 @@ function registerCreateCommand(parent) {
12837
12962
  }
12838
12963
 
12839
12964
  // src/cli/commands/topics-eval.ts
12840
- import { readFile as readFile10 } from "fs/promises";
12965
+ import { readFile as readFile11 } from "fs/promises";
12841
12966
 
12842
12967
  // src/core/prompt-loader.ts
12843
12968
  function parseCsvLine(line) {
@@ -12980,7 +13105,7 @@ function registerEvalCommand(parent) {
12980
13105
  resolveDeprecatedAliases(cmd, opts);
12981
13106
  try {
12982
13107
  const config = await loadConfig();
12983
- const csvContent = await readFile10(opts.prompts, "utf-8");
13108
+ const csvContent = await readFile11(opts.prompts, "utf-8");
12984
13109
  const { cases, intent } = loadPrompts(csvContent, (msg) => ui.status(`Warning: ${msg}`));
12985
13110
  assertScannerCredentials(config);
12986
13111
  let scanner = new AirsScanService(runtimeInitOptions(config));
@@ -13275,7 +13400,7 @@ function registerRuntimeCommand(program) {
13275
13400
  try {
13276
13401
  const config = await loadConfig({});
13277
13402
  assertScannerCredentials(config);
13278
- const raw = await readFile11(opts.file, "utf-8");
13403
+ const raw = await readFile12(opts.file, "utf-8");
13279
13404
  const prompts = parseInputFile(raw, opts.file);
13280
13405
  if (prompts.length === 0) {
13281
13406
  usageError("No prompts found in input file");
@@ -14045,38 +14170,6 @@ async function rewriteTenantConfig(entry, change) {
14045
14170
  }
14046
14171
  }
14047
14172
 
14048
- // src/cli/tenant-input.ts
14049
- import { input, password } from "@inquirer/prompts";
14050
- async function promptTenantValue(message, secret = false) {
14051
- if (!process.stdin.isTTY || !process.stderr.isTTY)
14052
- throw new Error(
14053
- "Interactive setup requires a terminal. Use --config, or --tsg-id and --client-id with --client-secret-stdin. For tenant set, pass a value or use --stdin."
14054
- );
14055
- const prompt = secret ? password : input;
14056
- return prompt(
14057
- {
14058
- message,
14059
- validate: (value) => Boolean(value.trim()) || "A nonempty value is required"
14060
- },
14061
- { input: process.stdin, output: process.stderr }
14062
- );
14063
- }
14064
- async function readTenantStdin() {
14065
- if (process.stdin.isTTY) throw new Error("--stdin requires piped input");
14066
- const chunks = [];
14067
- let size = 0;
14068
- for await (const chunk of process.stdin) {
14069
- const buffer = Buffer.from(chunk);
14070
- size += buffer.length;
14071
- if (size > 65536) throw new Error("Configuration input exceeds 64 KiB");
14072
- chunks.push(buffer);
14073
- }
14074
- const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
14075
- if (!value.trim() || /[\r\n\0]/.test(value))
14076
- throw new Error("Provide one nonempty configuration value on stdin");
14077
- return value;
14078
- }
14079
-
14080
14173
  // src/cli/commands/tenant.ts
14081
14174
  function tenantInputFailure(error) {
14082
14175
  if (error instanceof Error && error.name === "ExitPromptError") {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cdot65/prisma-airs-cli",
3
3
  "packageManager": "pnpm@10.6.5",
4
- "version": "6.0.0",
4
+ "version": "6.1.1",
5
5
  "description": "CLI and library for Palo Alto Prisma AIRS — guardrail refinement, AI red teaming, model security scanning, profile audits",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "license": "MIT",
61
61
  "dependencies": {
62
- "@cdot65/prisma-airs-sdk": "0.32.0",
62
+ "@cdot65/prisma-airs-sdk": "0.33.0",
63
63
  "@inquirer/prompts": "^8.3.0",
64
64
  "chalk": "^5.6.2",
65
65
  "commander": "^14.0.3",