@cdot65/prisma-airs-cli 7.1.5 → 7.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.
Files changed (2) hide show
  1. package/dist/cli/index.js +669 -527
  2. package/package.json +3 -3
package/dist/cli/index.js CHANGED
@@ -3562,443 +3562,52 @@ ${lines.map((l) => ` $ ${commandHint(l)}`).join("\n")}
3562
3562
  `;
3563
3563
  }
3564
3564
 
3565
- // src/cli/commands/aigateway/inference.ts
3566
- import { readFile } from "fs/promises";
3565
+ // src/cli/commands/aigateway/admin-guardrails.ts
3567
3566
  import {
3568
- AIGatewayInferenceClient,
3569
- ErrorType,
3570
- GatewayInferenceInputCreateChatCompletionRequestSchema,
3571
- GatewayInferenceInputCreateEmbeddingRequestSchema,
3572
- GatewayInferenceInputCreateResponseSchema
3567
+ GatewayAdminGuardrailCreateRequestSchema,
3568
+ GatewayAdminGuardrailMcpMappingRequestSchema,
3569
+ GatewayAdminGuardrailMcpSyncRequestSchema,
3570
+ GatewayGuardrailUpdateRequestSchema
3573
3571
  } from "@cdot65/prisma-airs-sdk";
3574
- import { dump as dump4 } from "js-yaml";
3575
- function integer2(raw, flag) {
3576
- const value = Number(raw);
3577
- if (!Number.isSafeInteger(value) || value < 1)
3578
- throw new CliUsageError(`${flag} must be a positive integer`);
3579
- return value;
3572
+
3573
+ // src/cli/commands/aigateway/shared.ts
3574
+ import { open, readFile, unlink } from "fs/promises";
3575
+ import { AIGatewayClient as AIGatewayClient2 } from "@cdot65/prisma-airs-sdk";
3576
+ import { dump as dump4, load } from "js-yaml";
3577
+ async function defaultClientFactory() {
3578
+ const config = await loadConfig();
3579
+ return new AIGatewayClient2(aiGatewayClientOptions(config));
3580
3580
  }
3581
- async function inferenceBody(kind, prompt, opts, defaultModel) {
3582
- if (prompt !== void 0 && opts.file)
3583
- throw new CliUsageError("Pass a prompt or --file, not both");
3584
- if (prompt === void 0 && !opts.file) throw new CliUsageError("A prompt or --file is required");
3585
- let body;
3586
- if (opts.file) {
3587
- let value;
3588
- try {
3589
- value = JSON.parse(await readFile(opts.file, "utf8"));
3590
- } catch {
3591
- throw new CliUsageError("--file must name a readable JSON request file");
3592
- }
3593
- if (!value || typeof value !== "object" || Array.isArray(value))
3594
- throw new CliUsageError("--file must contain a JSON object");
3595
- body = value;
3596
- } else {
3597
- body = kind === "chat" ? { messages: [{ role: "user", content: prompt }] } : { input: prompt };
3598
- }
3599
- body = { ...body };
3600
- const model = opts.model ?? body.model ?? defaultModel;
3601
- if (typeof model !== "string" || !model.trim())
3602
- throw new CliUsageError(
3603
- "Set --model, the request model, or the matching inference model config"
3604
- );
3605
- body.model = model;
3606
- if (opts.stream !== void 0) body.stream = opts.stream;
3607
- if (opts.maxTokens !== void 0)
3608
- body[kind === "responses" ? "max_output_tokens" : "max_completion_tokens"] = integer2(
3609
- opts.maxTokens,
3610
- "--max-tokens"
3611
- );
3612
- if (opts.dimensions !== void 0) body.dimensions = integer2(opts.dimensions, "--dimensions");
3613
- if (opts.encoding !== void 0) body.encoding_format = opts.encoding;
3614
- if (kind === "responses" && body.store === void 0) body.store = false;
3615
- return body;
3581
+ var clientFactory = defaultClientFactory;
3582
+ async function createAiGatewayClient() {
3583
+ return clientFactory();
3616
3584
  }
3617
- function responseText(value) {
3618
- if (!value || typeof value !== "object") return "";
3619
- const record = value;
3620
- if (record.type === "response.output_text.delta" && typeof record.delta === "string")
3621
- return record.delta;
3622
- if (Array.isArray(record.choices))
3623
- return record.choices.map((choice) => choice.message?.content ?? choice.delta?.content ?? "").join("");
3624
- if (Array.isArray(record.output))
3625
- return record.output.flatMap((item) => Array.isArray(item.content) ? item.content : []).filter((part) => part.type === "output_text").map((part) => part.text).join("");
3626
- return "";
3585
+ function showHelpOnEmpty(command) {
3586
+ return command.action(() => command.outputHelp());
3627
3587
  }
3628
- function registerAiGatewayInference(parent) {
3629
- const group = parent.command("inference").description("Call the gateway runtime (separate API key; no SCM OAuth required)");
3630
- for (const kind of ["chat", "responses", "embeddings"]) {
3631
- const command = group.command(`${kind} [prompt]`).description(
3632
- `${kind === "embeddings" ? "Create embeddings" : "Generate a model response"} through your runtime gateway`
3633
- ).option("--model <id>", "Provider-prefixed model ID; overrides request file and config").option("--endpoint <url>", "Runtime base URL including /v1; overrides config").option("--file <path>", "JSON request file (mutually exclusive with prompt)").option("--timeout <ms>", "Total deadline including streaming, in milliseconds", "60000").option(
3634
- "--output <format>",
3635
- "pretty, json or yaml; streaming json emits one chunk per line (JSONL)"
3636
- );
3637
- if (kind === "embeddings")
3638
- command.option("--dimensions <n>", "Embedding dimensions").option("--encoding <format>", "float or base64");
3639
- else
3640
- command.option("--stream", "Stream text (pretty) or validated events (json/JSONL)").option("--max-tokens <n>", "Maximum completion/output tokens");
3641
- command.action(async (prompt, opts) => {
3642
- const controller = new AbortController();
3643
- const interrupt = () => controller.abort(new Error("Inference interrupted"));
3644
- const outputError = (error) => controller.abort(error);
3645
- let stream;
3646
- let failed = false;
3647
- let failure3;
3648
- try {
3649
- const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
3650
- const config = await loadConfig({ aiGwInferenceEndpoint: opts.endpoint });
3651
- const body = await inferenceBody(
3652
- kind,
3653
- prompt,
3654
- opts,
3655
- kind === "embeddings" ? config.aiGwEmbeddingModel : config.aiGwInferenceModel
3656
- );
3657
- if (body.stream === true && format === "yaml")
3658
- throw new CliUsageError("Streaming supports --output pretty or json (JSONL), not yaml");
3659
- const schema = kind === "chat" ? GatewayInferenceInputCreateChatCompletionRequestSchema : kind === "responses" ? GatewayInferenceInputCreateResponseSchema : GatewayInferenceInputCreateEmbeddingRequestSchema;
3660
- const parsed = schema.safeParse(body);
3661
- if (!parsed.success)
3662
- throw new CliUsageError(
3663
- `Invalid inference request: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.code}`).join("; ")}`
3664
- );
3665
- const client = new AIGatewayInferenceClient({
3666
- endpoint: config.aiGwInferenceEndpoint,
3667
- apiKey: config.aiGwInferenceApiKey,
3668
- timeoutMs: integer2(opts.timeout ?? "60000", "--timeout"),
3669
- numRetries: 0
3670
- });
3671
- process.once("SIGINT", interrupt);
3672
- process.once("SIGTERM", interrupt);
3673
- process.stdout.on("error", outputError);
3674
- const write = (text2) => new Promise((resolve10, reject) => {
3675
- process.stdout.write(text2, (error) => error ? reject(error) : resolve10());
3676
- });
3677
- const requestOptions = { signal: controller.signal };
3678
- const result = kind === "chat" ? await client.createChatCompletion(
3679
- GatewayInferenceInputCreateChatCompletionRequestSchema.parse(body),
3680
- requestOptions
3681
- ) : kind === "responses" ? await client.createResponse(
3682
- GatewayInferenceInputCreateResponseSchema.parse(body),
3683
- requestOptions
3684
- ) : await client.createEmbedding(
3685
- GatewayInferenceInputCreateEmbeddingRequestSchema.parse(body),
3686
- requestOptions
3687
- );
3688
- if (Symbol.asyncIterator in result) {
3689
- stream = result;
3690
- for await (const event of result) {
3691
- if (format === "json") await write(`${JSON.stringify(event)}
3692
- `);
3693
- else {
3694
- const text2 = responseText(event);
3695
- if (text2) await write(text2);
3696
- }
3697
- if ("type" in event && (event.type === "response.failed" || event.type === "response.incomplete"))
3698
- throw new Error(`Gateway generation ended with ${event.type}`);
3699
- }
3700
- if (format === "pretty") await write("\n");
3701
- } else {
3702
- const text2 = format === "yaml" ? dump4(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
3703
- await write(`${text2}
3704
- `);
3705
- if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
3706
- throw new Error(`Gateway generation ended with ${result.status}`);
3707
- }
3708
- } catch (error) {
3709
- failed = true;
3710
- failure3 = error;
3711
- } finally {
3712
- await stream?.cancel();
3713
- controller.abort();
3714
- process.removeListener("SIGINT", interrupt);
3715
- process.removeListener("SIGTERM", interrupt);
3716
- process.stdout.removeListener("error", outputError);
3717
- }
3718
- if (failed && failure3?.code !== "EPIPE") {
3719
- if (failure3?.errorType === ErrorType.USER_REQUEST_PAYLOAD_ERROR)
3720
- usageError(failure3 instanceof Error ? failure3.message : "Invalid inference options");
3721
- fail(failure3);
3722
- }
3723
- });
3724
- }
3588
+ function addReadOutput(command) {
3589
+ return command.option(
3590
+ "--output <format>",
3591
+ "Output format: pretty, table, markdown, csv, json, yaml"
3592
+ );
3725
3593
  }
3726
-
3727
- // src/cli/commands/aigateway/inventory.ts
3728
- import { readFile as readFile3 } from "fs/promises";
3729
- import {
3730
- AI_GATEWAY_DEPLOYMENT_STATUSES,
3731
- AI_GATEWAY_DEPLOYMENT_TYPES,
3732
- AI_GATEWAY_KNOWN_API_KEY_SCOPES,
3733
- AI_GATEWAY_KNOWN_MCP_AUTH_TYPES,
3734
- AI_GATEWAY_KNOWN_MCP_TRANSPORTS,
3735
- AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES,
3736
- customHostConfiguration,
3737
- GatewayApiKeyRotateRequestSchema,
3738
- GatewayApiKeyUpdateRequestSchema,
3739
- GatewayConfigCreateRequestSchema,
3740
- GatewayConfigUpdateRequestSchema,
3741
- GatewayDeploymentCreateRequestSchema,
3742
- GatewayDeploymentUpdateRequestSchema,
3743
- GatewayGuardrailCreateRequestSchema,
3744
- GatewayGuardrailUpdateRequestSchema,
3745
- GatewayIntegrationCreateRequestSchema,
3746
- GatewayIntegrationModelsBulkUpdateRequestSchema,
3747
- GatewayIntegrationUpdateRequestSchema,
3748
- GatewayIntegrationWorkspacesBulkUpdateRequestSchema,
3749
- GatewayOrganisationAuthSettingsUpdateRequestSchema,
3750
- GatewayOrganisationUpdateRequestSchema,
3751
- GatewayPluginCreateRequestSchema,
3752
- GatewayProviderCreateRequestSchema,
3753
- GatewayProviderUpdateRequestSchema,
3754
- GatewayServiceApiKeyCreateRequestSchema,
3755
- GatewayUserApiKeyCreateRequestSchema,
3756
- McpIntegrationCapabilitiesBulkUpdateRequestSchema,
3757
- McpIntegrationCreateRequestSchema,
3758
- McpIntegrationUpdateRequestSchema,
3759
- McpIntegrationWorkspacesBulkUpdateRequestSchema,
3760
- redactAIGatewaySecrets
3761
- } from "@cdot65/prisma-airs-sdk";
3762
-
3763
- // src/cli/debug-logger.ts
3764
- import {
3765
- appendFileSync,
3766
- mkdirSync,
3767
- readdirSync,
3768
- statSync,
3769
- unlinkSync,
3770
- writeFileSync
3771
- } from "fs";
3772
- import { dirname, join } from "path";
3773
- var AIRS_DOMAINS = [
3774
- "api.sase.paloaltonetworks.com",
3775
- "api.apps.paloaltonetworks.com",
3776
- "service.api.aisecurity.paloaltonetworks.com",
3777
- "auth.apps.paloaltonetworks.com",
3778
- "api.dlp.paloaltonetworks.com"
3779
- ];
3780
- function isAirsUrl(url) {
3594
+ function addWriteOutput(command) {
3595
+ return command.option("--output <format>", "Output format: pretty, json, yaml");
3596
+ }
3597
+ async function readGatewayRequest(path3) {
3598
+ let parsed;
3781
3599
  try {
3782
- const parsed = new URL(url);
3783
- return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
3784
- } catch {
3785
- return false;
3600
+ const text2 = await readFile(path3, "utf8");
3601
+ parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(text2) : JSON.parse(text2);
3602
+ } catch (error) {
3603
+ throw new CliUsageError(
3604
+ `Unable to parse --file '${path3}' as JSON or YAML: ${error instanceof Error ? error.message : String(error)}`
3605
+ );
3786
3606
  }
3787
- }
3788
- var MASK = "***";
3789
- var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key|client[-_]?auth|auth[-_]?code|^key$/i;
3790
- function isSensitiveKey(key) {
3791
- return SENSITIVE_KEY_PATTERN.test(key);
3792
- }
3793
- function redactHeaders(headers) {
3794
- const out = {};
3795
- for (const [k, v] of Object.entries(headers)) {
3796
- out[k] = isSensitiveKey(k) || /^x-portkey-(config|metadata|forward-headers)$/i.test(k) ? MASK : v;
3607
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
3608
+ throw new CliUsageError(`--file '${path3}' must contain an object`);
3797
3609
  }
3798
- return out;
3799
- }
3800
- function redactDeep(value) {
3801
- if (Array.isArray(value)) {
3802
- return value.map(redactDeep);
3803
- }
3804
- if (value !== null && typeof value === "object") {
3805
- const out = {};
3806
- for (const [k, v] of Object.entries(value)) {
3807
- out[k] = isSensitiveKey(k) ? MASK : redactDeep(v);
3808
- }
3809
- return out;
3810
- }
3811
- return value;
3812
- }
3813
- function redactUrl(url) {
3814
- try {
3815
- const parsed = new URL(url);
3816
- let touched = false;
3817
- for (const key of parsed.searchParams.keys()) {
3818
- if (isSensitiveKey(key)) {
3819
- parsed.searchParams.set(key, MASK);
3820
- touched = true;
3821
- }
3822
- }
3823
- return touched ? parsed.toString() : url;
3824
- } catch {
3825
- return url;
3826
- }
3827
- }
3828
- function headersToRecord(headers) {
3829
- if (!headers) return {};
3830
- if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
3831
- const out = {};
3832
- headers.forEach((v, k) => {
3833
- out[k] = v;
3834
- });
3835
- return out;
3836
- }
3837
- if (Array.isArray(headers)) {
3838
- return Object.fromEntries(headers);
3839
- }
3840
- return headers;
3841
- }
3842
- function installDebugLogger(logPath, options = {}) {
3843
- mkdirSync(dirname(logPath), { recursive: true, mode: 448 });
3844
- writeFileSync(logPath, "", { encoding: "utf8", mode: 384, flag: "wx" });
3845
- const originalFetch = globalThis.fetch;
3846
- globalThis.fetch = async function debugFetch(input2, init2) {
3847
- const url = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
3848
- const rawHeaders = headersToRecord(
3849
- init2?.headers ?? (input2 instanceof Request ? input2.headers : void 0)
3850
- );
3851
- const inference = Object.keys(rawHeaders).some(
3852
- (name) => name.toLowerCase() === "x-portkey-api-key"
3853
- );
3854
- if (!isAirsUrl(url) && !inference) {
3855
- return originalFetch(input2, init2);
3856
- }
3857
- const dashboard = options.omitBodies || /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url) || /\/aiag\//.test(new URL(url).pathname);
3858
- const method = init2?.method ?? (input2 instanceof Request ? input2.method : "GET");
3859
- const reqHeaders = redactHeaders(rawHeaders);
3860
- const loggedUrl = redactUrl(url);
3861
- let reqBody;
3862
- if (inference || dashboard) reqBody = "[BODY OMITTED]";
3863
- else if (init2?.body) {
3864
- try {
3865
- reqBody = redactDeep(JSON.parse(String(init2.body)));
3866
- } catch {
3867
- const contentType = new Headers(rawHeaders).get("content-type") ?? "";
3868
- reqBody = contentType.includes("application/x-www-form-urlencoded") ? redactDeep(Object.fromEntries(new URLSearchParams(String(init2.body)))) : "[NON-JSON BODY OMITTED]";
3869
- }
3870
- }
3871
- const ts2 = (/* @__PURE__ */ new Date()).toISOString();
3872
- const startMs = Date.now();
3873
- let response;
3874
- let resBody;
3875
- let error;
3876
- try {
3877
- response = await originalFetch(input2, init2);
3878
- } catch (err) {
3879
- error = inference ? "Runtime request failed" : "Request failed";
3880
- const entry2 = JSON.stringify({
3881
- timestamp: ts2,
3882
- durationMs: Date.now() - startMs,
3883
- request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
3884
- error
3885
- });
3886
- appendFileSync(logPath, `${entry2}
3887
- `);
3888
- throw err;
3889
- }
3890
- const durationMs = Date.now() - startMs;
3891
- const resHeaders = {};
3892
- response.headers.forEach((v, k) => {
3893
- resHeaders[k] = v;
3894
- });
3895
- if (inference || dashboard || response.headers.get("content-type")?.includes("text/event-stream")) {
3896
- resBody = "[BODY OMITTED]";
3897
- } else {
3898
- const clone = response.clone();
3899
- try {
3900
- const text2 = await clone.text();
3901
- try {
3902
- resBody = redactDeep(JSON.parse(text2));
3903
- } catch {
3904
- resBody = "[NON-JSON BODY OMITTED]";
3905
- }
3906
- } catch {
3907
- resBody = "<unreadable>";
3908
- }
3909
- }
3910
- const entry = JSON.stringify({
3911
- timestamp: ts2,
3912
- durationMs,
3913
- request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
3914
- response: {
3915
- status: response.status,
3916
- statusText: response.statusText,
3917
- headers: redactHeaders(resHeaders),
3918
- body: resBody
3919
- }
3920
- });
3921
- appendFileSync(logPath, `${entry}
3922
- `);
3923
- return response;
3924
- };
3925
- return {
3926
- teardown() {
3927
- globalThis.fetch = originalFetch;
3928
- }
3929
- };
3930
- }
3931
-
3932
- // src/cli/tenant-input.ts
3933
- import { input, password } from "@inquirer/prompts";
3934
- async function promptTenantValue(message, secret = false) {
3935
- if (!process.stdin.isTTY || !process.stderr.isTTY)
3936
- throw new Error(
3937
- "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."
3938
- );
3939
- const prompt = secret ? password : input;
3940
- return prompt(
3941
- {
3942
- message,
3943
- validate: (value) => Boolean(value.trim()) || "A nonempty value is required"
3944
- },
3945
- { input: process.stdin, output: process.stderr }
3946
- );
3947
- }
3948
- async function readTenantStdin() {
3949
- if (process.stdin.isTTY) throw new Error("--stdin requires piped input");
3950
- const chunks = [];
3951
- let size = 0;
3952
- for await (const chunk of process.stdin) {
3953
- const buffer = Buffer.from(chunk);
3954
- size += buffer.length;
3955
- if (size > 65536) throw new Error("Configuration input exceeds 64 KiB");
3956
- chunks.push(buffer);
3957
- }
3958
- const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
3959
- if (!value.trim() || /[\r\n\0]/.test(value))
3960
- throw new Error("Provide one nonempty configuration value on stdin");
3961
- return value;
3962
- }
3963
-
3964
- // src/cli/commands/aigateway/shared.ts
3965
- import { open, readFile as readFile2, unlink } from "fs/promises";
3966
- import { AIGatewayClient as AIGatewayClient2 } from "@cdot65/prisma-airs-sdk";
3967
- import { dump as dump5, load } from "js-yaml";
3968
- async function defaultClientFactory() {
3969
- const config = await loadConfig();
3970
- return new AIGatewayClient2(aiGatewayClientOptions(config));
3971
- }
3972
- var clientFactory = defaultClientFactory;
3973
- async function createAiGatewayClient() {
3974
- return clientFactory();
3975
- }
3976
- function showHelpOnEmpty(command) {
3977
- return command.action(() => command.outputHelp());
3978
- }
3979
- function addReadOutput(command) {
3980
- return command.option(
3981
- "--output <format>",
3982
- "Output format: pretty, table, markdown, csv, json, yaml"
3983
- );
3984
- }
3985
- function addWriteOutput(command) {
3986
- return command.option("--output <format>", "Output format: pretty, json, yaml");
3987
- }
3988
- async function readGatewayRequest(path3) {
3989
- let parsed;
3990
- try {
3991
- const text2 = await readFile2(path3, "utf8");
3992
- parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(text2) : JSON.parse(text2);
3993
- } catch (error) {
3994
- throw new CliUsageError(
3995
- `Unable to parse --file '${path3}' as JSON or YAML: ${error instanceof Error ? error.message : String(error)}`
3996
- );
3997
- }
3998
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
3999
- throw new CliUsageError(`--file '${path3}' must contain an object`);
4000
- }
4001
- return parsed;
3610
+ return parsed;
4002
3611
  }
4003
3612
  function failAiGateway(error) {
4004
3613
  const hint = aiGatewayGrantHint(error);
@@ -4045,7 +3654,7 @@ function renderGatewayList(items, format, label2) {
4045
3654
  return;
4046
3655
  }
4047
3656
  if (format === "yaml") {
4048
- console.log(dump5(items, { noRefs: true, lineWidth: -1 }).trimEnd());
3657
+ console.log(dump4(items, { noRefs: true, lineWidth: -1 }).trimEnd());
4049
3658
  return;
4050
3659
  }
4051
3660
  if (items.length === 0) {
@@ -4063,7 +3672,7 @@ function renderGatewayDetail(value, format) {
4063
3672
  return;
4064
3673
  }
4065
3674
  if (format === "yaml") {
4066
- console.log(dump5(item, { noRefs: true, lineWidth: -1 }).trimEnd());
3675
+ console.log(dump4(item, { noRefs: true, lineWidth: -1 }).trimEnd());
4067
3676
  return;
4068
3677
  }
4069
3678
  const rows = Object.entries(item).map(([key, raw]) => ({
@@ -4179,7 +3788,7 @@ async function runSecretWrite(command, opts, prepare, write, prompt, settings =
4179
3788
  import {
4180
3789
  AISecSDKException,
4181
3790
  buildDottedObject,
4182
- ErrorType as ErrorType2,
3791
+ ErrorType,
4183
3792
  GatewayJsonObjectSchema,
4184
3793
  setDottedValue
4185
3794
  } from "@cdot65/prisma-airs-sdk";
@@ -4242,119 +3851,651 @@ function parseBooleanBindingsOption(value) {
4242
3851
  return { id, enabled: parseBooleanOption(enabled) };
4243
3852
  });
4244
3853
  }
4245
- function parseModelBindingsOption(value) {
4246
- return repeatableValues(value).map((raw) => {
4247
- const [slug, enabled] = splitKeyValue(raw, "slug");
4248
- return { slug, enabled: parseBooleanOption(enabled) };
4249
- });
3854
+ function parseModelBindingsOption(value) {
3855
+ return repeatableValues(value).map((raw) => {
3856
+ const [slug, enabled] = splitKeyValue(raw, "slug");
3857
+ return { slug, enabled: parseBooleanOption(enabled) };
3858
+ });
3859
+ }
3860
+ function parseCapabilityBindingsOption(value) {
3861
+ return repeatableValues(value).map((raw) => {
3862
+ const [identity2, enabled] = splitKeyValue(raw, "type:name");
3863
+ const separator = identity2.indexOf(":");
3864
+ if (separator <= 0 || separator === identity2.length - 1) {
3865
+ throw new CliUsageError("Expected type:name=enabled");
3866
+ }
3867
+ return {
3868
+ type: identity2.slice(0, separator),
3869
+ name: identity2.slice(separator + 1),
3870
+ enabled: parseBooleanOption(enabled)
3871
+ };
3872
+ });
3873
+ }
3874
+ function parseStringMapOption(value) {
3875
+ const result = {};
3876
+ for (const raw of repeatableValues(value)) {
3877
+ const [key, item] = splitKeyValue(raw, "key");
3878
+ if (["__proto__", "constructor", "prototype"].includes(key)) {
3879
+ throw new CliUsageError(`Unsafe key: ${key}`);
3880
+ }
3881
+ if (Object.hasOwn(result, key)) throw new CliUsageError(`Duplicate key: ${key}`);
3882
+ result[key] = item;
3883
+ }
3884
+ return result;
3885
+ }
3886
+ function parseAssignment(raw, forceString) {
3887
+ const separator = raw.indexOf("=");
3888
+ if (separator <= 0) throw new CliUsageError("Expected path=value");
3889
+ const path3 = raw.slice(0, separator);
3890
+ const text2 = raw.slice(separator + 1);
3891
+ if (forceString) return [path3, text2];
3892
+ try {
3893
+ return [path3, JSON.parse(text2)];
3894
+ } catch {
3895
+ return [path3, text2];
3896
+ }
3897
+ }
3898
+ function schemaMessage(error) {
3899
+ const issues = error?.issues;
3900
+ if (!Array.isArray(issues) || issues.length === 0)
3901
+ return "request body does not match its schema";
3902
+ return issues.map((issue) => {
3903
+ const path3 = issue.path?.length ? issue.path.map(String).join(".") : "<root>";
3904
+ return `${path3}: ${issue.message ?? "invalid value"}`;
3905
+ }).join("; ");
3906
+ }
3907
+ function optionName(option) {
3908
+ return `--${option.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`;
3909
+ }
3910
+ function asUsageError(error, prefix = "Invalid AI Gateway request") {
3911
+ if (error instanceof CliUsageError) return error;
3912
+ if (error instanceof AISecSDKException && error.errorType === ErrorType.USER_REQUEST_PAYLOAD_ERROR) {
3913
+ return new CliUsageError(`${prefix}: ${error.message}`);
3914
+ }
3915
+ return new CliUsageError(`${prefix}: ${schemaMessage(error)}`);
3916
+ }
3917
+ async function buildStructuredRequest(options, schema, fields = []) {
3918
+ try {
3919
+ let body = {};
3920
+ if (options.file) {
3921
+ body = GatewayJsonObjectSchema.parse(await readGatewayRequest(options.file));
3922
+ }
3923
+ for (const field of fields) {
3924
+ const raw = options[field.option];
3925
+ if (raw === void 0 || Array.isArray(raw) && raw.length === 0) continue;
3926
+ let value;
3927
+ try {
3928
+ value = field.parse ? field.parse(raw) : raw;
3929
+ } catch (error) {
3930
+ if (error instanceof CliUsageError) {
3931
+ throw new CliUsageError(`Invalid ${optionName(field.option)}: ${error.message}`);
3932
+ }
3933
+ throw error;
3934
+ }
3935
+ body = setDottedValue(body, field.path, value);
3936
+ }
3937
+ const assignments = [
3938
+ ...(options.set ?? []).map((raw) => {
3939
+ try {
3940
+ const [path3, value] = parseAssignment(raw, false);
3941
+ return { path: path3, value };
3942
+ } catch (error) {
3943
+ if (error instanceof CliUsageError) {
3944
+ throw new CliUsageError(`Invalid --set '${raw}': ${error.message}`);
3945
+ }
3946
+ throw error;
3947
+ }
3948
+ }),
3949
+ ...(options.setString ?? []).map((raw) => {
3950
+ try {
3951
+ const [path3, value] = parseAssignment(raw, true);
3952
+ return { path: path3, value };
3953
+ } catch (error) {
3954
+ if (error instanceof CliUsageError) {
3955
+ throw new CliUsageError(`Invalid --set-string '${raw}': ${error.message}`);
3956
+ }
3957
+ throw error;
3958
+ }
3959
+ })
3960
+ ];
3961
+ buildDottedObject(assignments);
3962
+ for (const { path: path3, value } of assignments) body = setDottedValue(body, path3, value);
3963
+ return schema.parse(body);
3964
+ } catch (error) {
3965
+ throw asUsageError(error);
3966
+ }
3967
+ }
3968
+
3969
+ // src/cli/commands/aigateway/admin-guardrails.ts
3970
+ function paginationInteger(value, flag, minimum, maximum) {
3971
+ try {
3972
+ const parsed = parseIntegerOption(value);
3973
+ if (parsed < minimum || parsed > maximum) {
3974
+ throw new CliUsageError(`${flag} must be an integer from ${minimum} to ${maximum}`);
3975
+ }
3976
+ return parsed;
3977
+ } catch (error) {
3978
+ fail(error);
3979
+ }
3980
+ }
3981
+ function registerAdminGuardrails(root) {
3982
+ const group = showHelpOnEmpty(
3983
+ root.command("admin-guardrails").description(
3984
+ "Manage organisation guardrails on the admin plane (mutations specification-tested)"
3985
+ )
3986
+ );
3987
+ const list = addReadOutput(
3988
+ group.command("list").alias("ls").description(
3989
+ "List organisation guardrails; optional workspace filtering may be denied by policy"
3990
+ ).option("--workspace <uuid>", "Optional workspace UUID filter").option(
3991
+ "--page-size <number>",
3992
+ "Page size, 1\u20131000",
3993
+ (value) => paginationInteger(value, "--page-size", 1, 1e3)
3994
+ ).option(
3995
+ "--current-page <number>",
3996
+ "Zero-based page number",
3997
+ (value) => paginationInteger(value, "--current-page", 0, Number.MAX_SAFE_INTEGER)
3998
+ )
3999
+ );
4000
+ list.action(
4001
+ (opts) => runList(
4002
+ list,
4003
+ opts,
4004
+ "Admin guardrails",
4005
+ (c) => c.adminGuardrails.list({
4006
+ workspaceId: opts.workspace,
4007
+ pageSize: opts.pageSize,
4008
+ currentPage: opts.currentPage
4009
+ })
4010
+ )
4011
+ );
4012
+ const get = addReadOutput(
4013
+ group.command("get <id>").description("Read an admin guardrail by UUID")
4014
+ );
4015
+ get.action((id, opts) => runDetail(get, opts, (c) => c.adminGuardrails.get(id)));
4016
+ const fields = [
4017
+ { option: "name", path: "name" },
4018
+ { option: "checks", path: "checks", parse: parseJsonOption },
4019
+ { option: "actions", path: "actions", parse: parseJsonOption }
4020
+ ];
4021
+ const common = (cmd) => addWriteOutput(
4022
+ addStructuredInputOptions(
4023
+ cmd.option("--name <name>", "Guardrail name").option("--checks <json>", "Checks array").option("--actions <json>", "Actions object")
4024
+ )
4025
+ );
4026
+ const create = common(
4027
+ group.command("create").description("Create an admin guardrail (specification-tested; no workspace fallback)")
4028
+ ).option("--target <target>", "llm or mcp_tools").option("--workspace <uuid>", "Optional workspace UUID").option("--organisation <uuid>", "Optional organisation UUID, not the numeric TSG");
4029
+ create.action(
4030
+ (opts) => runWrite(
4031
+ create,
4032
+ opts,
4033
+ () => buildStructuredRequest(opts, GatewayAdminGuardrailCreateRequestSchema, [
4034
+ ...fields,
4035
+ { option: "target", path: "target" },
4036
+ { option: "workspace", path: "workspace_id" },
4037
+ { option: "organisation", path: "organisation_id" }
4038
+ ]),
4039
+ (c, body) => c.adminGuardrails.create(body)
4040
+ )
4041
+ );
4042
+ const update = common(
4043
+ group.command("update <id>").description("Update an admin guardrail (specification-tested)")
4044
+ );
4045
+ update.action(
4046
+ (id, opts) => runWrite(
4047
+ update,
4048
+ opts,
4049
+ () => buildStructuredRequest(opts, GatewayGuardrailUpdateRequestSchema, fields),
4050
+ (c, body) => c.adminGuardrails.update(id, body)
4051
+ )
4052
+ );
4053
+ const remove = addWriteOutput(
4054
+ group.command("delete <id>").alias("rm").description("Permanently delete an admin guardrail").option("--force", "Skip confirmation prompt")
4055
+ );
4056
+ remove.action(
4057
+ (id, opts) => runConfirmedWrite(
4058
+ remove,
4059
+ opts,
4060
+ `Permanently delete admin guardrail ${id}?`,
4061
+ (c) => c.adminGuardrails.delete(id)
4062
+ )
4063
+ );
4064
+ const mappings = showHelpOnEmpty(
4065
+ group.command("mcp-servers").description("Manage admin guardrail MCP mappings; does not sign in to MCP")
4066
+ );
4067
+ const mappingsList = addReadOutput(
4068
+ mappings.command("list <guardrail-id>").alias("ls").description("List MCP mappings")
4069
+ );
4070
+ mappingsList.action(
4071
+ (id, opts) => runList(mappingsList, opts, "MCP mappings", (c) => c.adminGuardrails.getMcpServers(id))
4072
+ );
4073
+ const sync = addWriteOutput(
4074
+ addStructuredInputOptions(
4075
+ mappings.command("sync <guardrail-id>").description("Replace all mappings (specification-tested)").option("--force", "Skip replacement confirmation")
4076
+ )
4077
+ );
4078
+ sync.action(
4079
+ (id, opts) => runConfirmedWrite(
4080
+ sync,
4081
+ opts,
4082
+ `Replace all MCP mappings on admin guardrail ${id}?`,
4083
+ () => buildStructuredRequest(opts, GatewayAdminGuardrailMcpSyncRequestSchema),
4084
+ (c, body) => c.adminGuardrails.syncMcpServers(id, body)
4085
+ )
4086
+ );
4087
+ const upsert = addWriteOutput(
4088
+ addStructuredInputOptions(
4089
+ mappings.command("upsert <guardrail-id> <server-id>").description("Set one mapping (specification-tested)")
4090
+ )
4091
+ );
4092
+ upsert.action(
4093
+ (id, serverId, opts) => runWrite(
4094
+ upsert,
4095
+ opts,
4096
+ () => buildStructuredRequest(opts, GatewayAdminGuardrailMcpMappingRequestSchema),
4097
+ (c, body) => c.adminGuardrails.upsertMcpServer(id, serverId, body)
4098
+ )
4099
+ );
4100
+ }
4101
+
4102
+ // src/cli/commands/aigateway/inference.ts
4103
+ import { readFile as readFile2 } from "fs/promises";
4104
+ import {
4105
+ AIGatewayInferenceClient,
4106
+ ErrorType as ErrorType2,
4107
+ GatewayInferenceInputCreateChatCompletionRequestSchema,
4108
+ GatewayInferenceInputCreateEmbeddingRequestSchema,
4109
+ GatewayInferenceInputCreateResponseSchema
4110
+ } from "@cdot65/prisma-airs-sdk";
4111
+ import { dump as dump5 } from "js-yaml";
4112
+ function integer2(raw, flag) {
4113
+ const value = Number(raw);
4114
+ if (!Number.isSafeInteger(value) || value < 1)
4115
+ throw new CliUsageError(`${flag} must be a positive integer`);
4116
+ return value;
4117
+ }
4118
+ async function inferenceBody(kind, prompt, opts, defaultModel) {
4119
+ if (prompt !== void 0 && opts.file)
4120
+ throw new CliUsageError("Pass a prompt or --file, not both");
4121
+ if (prompt === void 0 && !opts.file) throw new CliUsageError("A prompt or --file is required");
4122
+ let body;
4123
+ if (opts.file) {
4124
+ let value;
4125
+ try {
4126
+ value = JSON.parse(await readFile2(opts.file, "utf8"));
4127
+ } catch {
4128
+ throw new CliUsageError("--file must name a readable JSON request file");
4129
+ }
4130
+ if (!value || typeof value !== "object" || Array.isArray(value))
4131
+ throw new CliUsageError("--file must contain a JSON object");
4132
+ body = value;
4133
+ } else {
4134
+ body = kind === "chat" ? { messages: [{ role: "user", content: prompt }] } : { input: prompt };
4135
+ }
4136
+ body = { ...body };
4137
+ const model = opts.model ?? body.model ?? defaultModel;
4138
+ if (typeof model !== "string" || !model.trim())
4139
+ throw new CliUsageError(
4140
+ "Set --model, the request model, or the matching inference model config"
4141
+ );
4142
+ body.model = model;
4143
+ if (opts.stream !== void 0) body.stream = opts.stream;
4144
+ if (opts.maxTokens !== void 0)
4145
+ body[kind === "responses" ? "max_output_tokens" : "max_completion_tokens"] = integer2(
4146
+ opts.maxTokens,
4147
+ "--max-tokens"
4148
+ );
4149
+ if (opts.dimensions !== void 0) body.dimensions = integer2(opts.dimensions, "--dimensions");
4150
+ if (opts.encoding !== void 0) body.encoding_format = opts.encoding;
4151
+ if (kind === "responses" && body.store === void 0) body.store = false;
4152
+ return body;
4153
+ }
4154
+ function responseText(value) {
4155
+ if (!value || typeof value !== "object") return "";
4156
+ const record = value;
4157
+ if (record.type === "response.output_text.delta" && typeof record.delta === "string")
4158
+ return record.delta;
4159
+ if (Array.isArray(record.choices))
4160
+ return record.choices.map((choice) => choice.message?.content ?? choice.delta?.content ?? "").join("");
4161
+ if (Array.isArray(record.output))
4162
+ return record.output.flatMap((item) => Array.isArray(item.content) ? item.content : []).filter((part) => part.type === "output_text").map((part) => part.text).join("");
4163
+ return "";
4164
+ }
4165
+ function registerAiGatewayInference(parent) {
4166
+ const group = parent.command("inference").description("Call the gateway runtime (separate API key; no SCM OAuth required)");
4167
+ for (const kind of ["chat", "responses", "embeddings"]) {
4168
+ const command = group.command(`${kind} [prompt]`).description(
4169
+ `${kind === "embeddings" ? "Create embeddings" : "Generate a model response"} through your runtime gateway`
4170
+ ).option("--model <id>", "Provider-prefixed model ID; overrides request file and config").option("--endpoint <url>", "Runtime base URL including /v1; overrides config").option("--file <path>", "JSON request file (mutually exclusive with prompt)").option("--timeout <ms>", "Total deadline including streaming, in milliseconds", "60000").option(
4171
+ "--output <format>",
4172
+ "pretty, json or yaml; streaming json emits one chunk per line (JSONL)"
4173
+ );
4174
+ if (kind === "embeddings")
4175
+ command.option("--dimensions <n>", "Embedding dimensions").option("--encoding <format>", "float or base64");
4176
+ else
4177
+ command.option("--stream", "Stream text (pretty) or validated events (json/JSONL)").option("--max-tokens <n>", "Maximum completion/output tokens");
4178
+ command.action(async (prompt, opts) => {
4179
+ const controller = new AbortController();
4180
+ const interrupt = () => controller.abort(new Error("Inference interrupted"));
4181
+ const outputError = (error) => controller.abort(error);
4182
+ let stream;
4183
+ let failed = false;
4184
+ let failure3;
4185
+ try {
4186
+ const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
4187
+ const config = await loadConfig({ aiGwInferenceEndpoint: opts.endpoint });
4188
+ const body = await inferenceBody(
4189
+ kind,
4190
+ prompt,
4191
+ opts,
4192
+ kind === "embeddings" ? config.aiGwEmbeddingModel : config.aiGwInferenceModel
4193
+ );
4194
+ if (body.stream === true && format === "yaml")
4195
+ throw new CliUsageError("Streaming supports --output pretty or json (JSONL), not yaml");
4196
+ const schema = kind === "chat" ? GatewayInferenceInputCreateChatCompletionRequestSchema : kind === "responses" ? GatewayInferenceInputCreateResponseSchema : GatewayInferenceInputCreateEmbeddingRequestSchema;
4197
+ const parsed = schema.safeParse(body);
4198
+ if (!parsed.success)
4199
+ throw new CliUsageError(
4200
+ `Invalid inference request: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.code}`).join("; ")}`
4201
+ );
4202
+ const client = new AIGatewayInferenceClient({
4203
+ endpoint: config.aiGwInferenceEndpoint,
4204
+ apiKey: config.aiGwInferenceApiKey,
4205
+ timeoutMs: integer2(opts.timeout ?? "60000", "--timeout"),
4206
+ numRetries: 0
4207
+ });
4208
+ process.once("SIGINT", interrupt);
4209
+ process.once("SIGTERM", interrupt);
4210
+ process.stdout.on("error", outputError);
4211
+ const write = (text2) => new Promise((resolve10, reject) => {
4212
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve10());
4213
+ });
4214
+ const requestOptions = { signal: controller.signal };
4215
+ const result = kind === "chat" ? await client.createChatCompletion(
4216
+ GatewayInferenceInputCreateChatCompletionRequestSchema.parse(body),
4217
+ requestOptions
4218
+ ) : kind === "responses" ? await client.createResponse(
4219
+ GatewayInferenceInputCreateResponseSchema.parse(body),
4220
+ requestOptions
4221
+ ) : await client.createEmbedding(
4222
+ GatewayInferenceInputCreateEmbeddingRequestSchema.parse(body),
4223
+ requestOptions
4224
+ );
4225
+ if (Symbol.asyncIterator in result) {
4226
+ stream = result;
4227
+ for await (const event of result) {
4228
+ if (format === "json") await write(`${JSON.stringify(event)}
4229
+ `);
4230
+ else {
4231
+ const text2 = responseText(event);
4232
+ if (text2) await write(text2);
4233
+ }
4234
+ if ("type" in event && (event.type === "response.failed" || event.type === "response.incomplete"))
4235
+ throw new Error(`Gateway generation ended with ${event.type}`);
4236
+ }
4237
+ if (format === "pretty") await write("\n");
4238
+ } else {
4239
+ const text2 = format === "yaml" ? dump5(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
4240
+ await write(`${text2}
4241
+ `);
4242
+ if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
4243
+ throw new Error(`Gateway generation ended with ${result.status}`);
4244
+ }
4245
+ } catch (error) {
4246
+ failed = true;
4247
+ failure3 = error;
4248
+ } finally {
4249
+ await stream?.cancel();
4250
+ controller.abort();
4251
+ process.removeListener("SIGINT", interrupt);
4252
+ process.removeListener("SIGTERM", interrupt);
4253
+ process.stdout.removeListener("error", outputError);
4254
+ }
4255
+ if (failed && failure3?.code !== "EPIPE") {
4256
+ if (failure3?.errorType === ErrorType2.USER_REQUEST_PAYLOAD_ERROR)
4257
+ usageError(failure3 instanceof Error ? failure3.message : "Invalid inference options");
4258
+ fail(failure3);
4259
+ }
4260
+ });
4261
+ }
4262
+ }
4263
+
4264
+ // src/cli/commands/aigateway/inventory.ts
4265
+ import { readFile as readFile3 } from "fs/promises";
4266
+ import {
4267
+ AI_GATEWAY_DEPLOYMENT_STATUSES,
4268
+ AI_GATEWAY_DEPLOYMENT_TYPES,
4269
+ AI_GATEWAY_KNOWN_API_KEY_SCOPES,
4270
+ AI_GATEWAY_KNOWN_MCP_AUTH_TYPES,
4271
+ AI_GATEWAY_KNOWN_MCP_TRANSPORTS,
4272
+ AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES,
4273
+ customHostConfiguration,
4274
+ GatewayApiKeyRotateRequestSchema,
4275
+ GatewayApiKeyUpdateRequestSchema,
4276
+ GatewayConfigCreateRequestSchema,
4277
+ GatewayConfigUpdateRequestSchema,
4278
+ GatewayDeploymentCreateRequestSchema,
4279
+ GatewayDeploymentUpdateRequestSchema,
4280
+ GatewayGuardrailCreateRequestSchema,
4281
+ GatewayGuardrailUpdateRequestSchema as GatewayGuardrailUpdateRequestSchema2,
4282
+ GatewayIntegrationCreateRequestSchema,
4283
+ GatewayIntegrationModelsBulkUpdateRequestSchema,
4284
+ GatewayIntegrationUpdateRequestSchema,
4285
+ GatewayIntegrationWorkspacesBulkUpdateRequestSchema,
4286
+ GatewayOrganisationAuthSettingsUpdateRequestSchema,
4287
+ GatewayOrganisationUpdateRequestSchema,
4288
+ GatewayPluginCreateRequestSchema,
4289
+ GatewayProviderCreateRequestSchema,
4290
+ GatewayProviderUpdateRequestSchema,
4291
+ GatewayServiceApiKeyCreateRequestSchema,
4292
+ GatewayUserApiKeyCreateRequestSchema,
4293
+ McpIntegrationCapabilitiesBulkUpdateRequestSchema,
4294
+ McpIntegrationCreateRequestSchema,
4295
+ McpIntegrationUpdateRequestSchema,
4296
+ McpIntegrationWorkspacesBulkUpdateRequestSchema,
4297
+ redactAIGatewaySecrets
4298
+ } from "@cdot65/prisma-airs-sdk";
4299
+
4300
+ // src/cli/debug-logger.ts
4301
+ import {
4302
+ appendFileSync,
4303
+ mkdirSync,
4304
+ readdirSync,
4305
+ statSync,
4306
+ unlinkSync,
4307
+ writeFileSync
4308
+ } from "fs";
4309
+ import { dirname, join } from "path";
4310
+ var AIRS_DOMAINS = [
4311
+ "api.sase.paloaltonetworks.com",
4312
+ "api.apps.paloaltonetworks.com",
4313
+ "service.api.aisecurity.paloaltonetworks.com",
4314
+ "auth.apps.paloaltonetworks.com",
4315
+ "api.dlp.paloaltonetworks.com"
4316
+ ];
4317
+ function isAirsUrl(url) {
4318
+ try {
4319
+ const parsed = new URL(url);
4320
+ return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
4321
+ } catch {
4322
+ return false;
4323
+ }
4324
+ }
4325
+ var MASK = "***";
4326
+ var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key|client[-_]?auth|auth[-_]?code|^key$/i;
4327
+ function isSensitiveKey(key) {
4328
+ return SENSITIVE_KEY_PATTERN.test(key);
4250
4329
  }
4251
- function parseCapabilityBindingsOption(value) {
4252
- return repeatableValues(value).map((raw) => {
4253
- const [identity2, enabled] = splitKeyValue(raw, "type:name");
4254
- const separator = identity2.indexOf(":");
4255
- if (separator <= 0 || separator === identity2.length - 1) {
4256
- throw new CliUsageError("Expected type:name=enabled");
4257
- }
4258
- return {
4259
- type: identity2.slice(0, separator),
4260
- name: identity2.slice(separator + 1),
4261
- enabled: parseBooleanOption(enabled)
4262
- };
4263
- });
4330
+ function redactHeaders(headers) {
4331
+ const out = {};
4332
+ for (const [k, v] of Object.entries(headers)) {
4333
+ out[k] = isSensitiveKey(k) || /^x-portkey-(config|metadata|forward-headers)$/i.test(k) ? MASK : v;
4334
+ }
4335
+ return out;
4264
4336
  }
4265
- function parseStringMapOption(value) {
4266
- const result = {};
4267
- for (const raw of repeatableValues(value)) {
4268
- const [key, item] = splitKeyValue(raw, "key");
4269
- if (["__proto__", "constructor", "prototype"].includes(key)) {
4270
- throw new CliUsageError(`Unsafe key: ${key}`);
4337
+ function redactDeep(value) {
4338
+ if (Array.isArray(value)) {
4339
+ return value.map(redactDeep);
4340
+ }
4341
+ if (value !== null && typeof value === "object") {
4342
+ const out = {};
4343
+ for (const [k, v] of Object.entries(value)) {
4344
+ out[k] = isSensitiveKey(k) ? MASK : redactDeep(v);
4271
4345
  }
4272
- if (Object.hasOwn(result, key)) throw new CliUsageError(`Duplicate key: ${key}`);
4273
- result[key] = item;
4346
+ return out;
4274
4347
  }
4275
- return result;
4348
+ return value;
4276
4349
  }
4277
- function parseAssignment(raw, forceString) {
4278
- const separator = raw.indexOf("=");
4279
- if (separator <= 0) throw new CliUsageError("Expected path=value");
4280
- const path3 = raw.slice(0, separator);
4281
- const text2 = raw.slice(separator + 1);
4282
- if (forceString) return [path3, text2];
4350
+ function redactUrl(url) {
4283
4351
  try {
4284
- return [path3, JSON.parse(text2)];
4352
+ const parsed = new URL(url);
4353
+ let touched = false;
4354
+ for (const key of parsed.searchParams.keys()) {
4355
+ if (isSensitiveKey(key)) {
4356
+ parsed.searchParams.set(key, MASK);
4357
+ touched = true;
4358
+ }
4359
+ }
4360
+ return touched ? parsed.toString() : url;
4285
4361
  } catch {
4286
- return [path3, text2];
4362
+ return url;
4287
4363
  }
4288
4364
  }
4289
- function schemaMessage(error) {
4290
- const issues = error?.issues;
4291
- if (!Array.isArray(issues) || issues.length === 0)
4292
- return "request body does not match its schema";
4293
- return issues.map((issue) => {
4294
- const path3 = issue.path?.length ? issue.path.map(String).join(".") : "<root>";
4295
- return `${path3}: ${issue.message ?? "invalid value"}`;
4296
- }).join("; ");
4297
- }
4298
- function optionName(option) {
4299
- return `--${option.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`;
4300
- }
4301
- function asUsageError(error, prefix = "Invalid AI Gateway request") {
4302
- if (error instanceof CliUsageError) return error;
4303
- if (error instanceof AISecSDKException && error.errorType === ErrorType2.USER_REQUEST_PAYLOAD_ERROR) {
4304
- return new CliUsageError(`${prefix}: ${error.message}`);
4365
+ function headersToRecord(headers) {
4366
+ if (!headers) return {};
4367
+ if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
4368
+ const out = {};
4369
+ headers.forEach((v, k) => {
4370
+ out[k] = v;
4371
+ });
4372
+ return out;
4305
4373
  }
4306
- return new CliUsageError(`${prefix}: ${schemaMessage(error)}`);
4374
+ if (Array.isArray(headers)) {
4375
+ return Object.fromEntries(headers);
4376
+ }
4377
+ return headers;
4307
4378
  }
4308
- async function buildStructuredRequest(options, schema, fields = []) {
4309
- try {
4310
- let body = {};
4311
- if (options.file) {
4312
- body = GatewayJsonObjectSchema.parse(await readGatewayRequest(options.file));
4379
+ function installDebugLogger(logPath, options = {}) {
4380
+ mkdirSync(dirname(logPath), { recursive: true, mode: 448 });
4381
+ writeFileSync(logPath, "", { encoding: "utf8", mode: 384, flag: "wx" });
4382
+ const originalFetch = globalThis.fetch;
4383
+ globalThis.fetch = async function debugFetch(input2, init2) {
4384
+ const url = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
4385
+ const rawHeaders = headersToRecord(
4386
+ init2?.headers ?? (input2 instanceof Request ? input2.headers : void 0)
4387
+ );
4388
+ const inference = Object.keys(rawHeaders).some(
4389
+ (name) => name.toLowerCase() === "x-portkey-api-key"
4390
+ );
4391
+ if (!isAirsUrl(url) && !inference) {
4392
+ return originalFetch(input2, init2);
4313
4393
  }
4314
- for (const field of fields) {
4315
- const raw = options[field.option];
4316
- if (raw === void 0 || Array.isArray(raw) && raw.length === 0) continue;
4317
- let value;
4394
+ const dashboard = options.omitBodies || /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url) || /\/aiag\//.test(new URL(url).pathname);
4395
+ const method = init2?.method ?? (input2 instanceof Request ? input2.method : "GET");
4396
+ const reqHeaders = redactHeaders(rawHeaders);
4397
+ const loggedUrl = redactUrl(url);
4398
+ let reqBody;
4399
+ if (inference || dashboard) reqBody = "[BODY OMITTED]";
4400
+ else if (init2?.body) {
4318
4401
  try {
4319
- value = field.parse ? field.parse(raw) : raw;
4320
- } catch (error) {
4321
- if (error instanceof CliUsageError) {
4322
- throw new CliUsageError(`Invalid ${optionName(field.option)}: ${error.message}`);
4323
- }
4324
- throw error;
4402
+ reqBody = redactDeep(JSON.parse(String(init2.body)));
4403
+ } catch {
4404
+ const contentType = new Headers(rawHeaders).get("content-type") ?? "";
4405
+ reqBody = contentType.includes("application/x-www-form-urlencoded") ? redactDeep(Object.fromEntries(new URLSearchParams(String(init2.body)))) : "[NON-JSON BODY OMITTED]";
4325
4406
  }
4326
- body = setDottedValue(body, field.path, value);
4327
4407
  }
4328
- const assignments = [
4329
- ...(options.set ?? []).map((raw) => {
4330
- try {
4331
- const [path3, value] = parseAssignment(raw, false);
4332
- return { path: path3, value };
4333
- } catch (error) {
4334
- if (error instanceof CliUsageError) {
4335
- throw new CliUsageError(`Invalid --set '${raw}': ${error.message}`);
4336
- }
4337
- throw error;
4338
- }
4339
- }),
4340
- ...(options.setString ?? []).map((raw) => {
4408
+ const ts2 = (/* @__PURE__ */ new Date()).toISOString();
4409
+ const startMs = Date.now();
4410
+ let response;
4411
+ let resBody;
4412
+ let error;
4413
+ try {
4414
+ response = await originalFetch(input2, init2);
4415
+ } catch (err) {
4416
+ error = inference ? "Runtime request failed" : "Request failed";
4417
+ const entry2 = JSON.stringify({
4418
+ timestamp: ts2,
4419
+ durationMs: Date.now() - startMs,
4420
+ request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
4421
+ error
4422
+ });
4423
+ appendFileSync(logPath, `${entry2}
4424
+ `);
4425
+ throw err;
4426
+ }
4427
+ const durationMs = Date.now() - startMs;
4428
+ const resHeaders = {};
4429
+ response.headers.forEach((v, k) => {
4430
+ resHeaders[k] = v;
4431
+ });
4432
+ if (inference || dashboard || response.headers.get("content-type")?.includes("text/event-stream")) {
4433
+ resBody = "[BODY OMITTED]";
4434
+ } else {
4435
+ const clone = response.clone();
4436
+ try {
4437
+ const text2 = await clone.text();
4341
4438
  try {
4342
- const [path3, value] = parseAssignment(raw, true);
4343
- return { path: path3, value };
4344
- } catch (error) {
4345
- if (error instanceof CliUsageError) {
4346
- throw new CliUsageError(`Invalid --set-string '${raw}': ${error.message}`);
4347
- }
4348
- throw error;
4439
+ resBody = redactDeep(JSON.parse(text2));
4440
+ } catch {
4441
+ resBody = "[NON-JSON BODY OMITTED]";
4349
4442
  }
4350
- })
4351
- ];
4352
- buildDottedObject(assignments);
4353
- for (const { path: path3, value } of assignments) body = setDottedValue(body, path3, value);
4354
- return schema.parse(body);
4355
- } catch (error) {
4356
- throw asUsageError(error);
4443
+ } catch {
4444
+ resBody = "<unreadable>";
4445
+ }
4446
+ }
4447
+ const entry = JSON.stringify({
4448
+ timestamp: ts2,
4449
+ durationMs,
4450
+ request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
4451
+ response: {
4452
+ status: response.status,
4453
+ statusText: response.statusText,
4454
+ headers: redactHeaders(resHeaders),
4455
+ body: resBody
4456
+ }
4457
+ });
4458
+ appendFileSync(logPath, `${entry}
4459
+ `);
4460
+ return response;
4461
+ };
4462
+ return {
4463
+ teardown() {
4464
+ globalThis.fetch = originalFetch;
4465
+ }
4466
+ };
4467
+ }
4468
+
4469
+ // src/cli/tenant-input.ts
4470
+ import { input, password } from "@inquirer/prompts";
4471
+ async function promptTenantValue(message, secret = false) {
4472
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
4473
+ throw new Error(
4474
+ "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."
4475
+ );
4476
+ const prompt = secret ? password : input;
4477
+ return prompt(
4478
+ {
4479
+ message,
4480
+ validate: (value) => Boolean(value.trim()) || "A nonempty value is required"
4481
+ },
4482
+ { input: process.stdin, output: process.stderr }
4483
+ );
4484
+ }
4485
+ async function readTenantStdin() {
4486
+ if (process.stdin.isTTY) throw new Error("--stdin requires piped input");
4487
+ const chunks = [];
4488
+ let size = 0;
4489
+ for await (const chunk of process.stdin) {
4490
+ const buffer = Buffer.from(chunk);
4491
+ size += buffer.length;
4492
+ if (size > 65536) throw new Error("Configuration input exceeds 64 KiB");
4493
+ chunks.push(buffer);
4357
4494
  }
4495
+ const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
4496
+ if (!value.trim() || /[\r\n\0]/.test(value))
4497
+ throw new Error("Provide one nonempty configuration value on stdin");
4498
+ return value;
4358
4499
  }
4359
4500
 
4360
4501
  // src/cli/commands/aigateway/inventory.ts
@@ -5202,7 +5343,7 @@ function registerAiGatewayInventory(root) {
5202
5343
  createSchema: GatewayGuardrailCreateRequestSchema,
5203
5344
  updateFields: guardrailUpdateFields,
5204
5345
  updateOptions: addGuardrailUpdateFields,
5205
- updateSchema: GatewayGuardrailUpdateRequestSchema
5346
+ updateSchema: GatewayGuardrailUpdateRequestSchema2
5206
5347
  },
5207
5348
  {
5208
5349
  create: (client, body) => client.guardrails.create(body),
@@ -5634,6 +5775,7 @@ function scopeNameLooksUnrelated(name, scopeName) {
5634
5775
  function registerAiGatewayCommand(program) {
5635
5776
  const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
5636
5777
  registerAiGatewayInventory(aigateway);
5778
+ registerAdminGuardrails(aigateway);
5637
5779
  registerAiGatewayReportCommand(aigateway);
5638
5780
  registerAiGatewayInference(aigateway);
5639
5781
  const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@cdot65/prisma-airs-cli",
3
3
  "packageManager": "pnpm@10.6.5",
4
- "version": "7.1.5",
5
- "description": "CLI and library for Palo Alto Prisma AIRS \u2014 guardrail refinement, AI red teaming, model security scanning, profile audits",
4
+ "version": "7.2.0",
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",
8
8
  "types": "dist/index.d.ts",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "license": "MIT",
61
61
  "dependencies": {
62
- "@cdot65/prisma-airs-sdk": "0.33.0",
62
+ "@cdot65/prisma-airs-sdk": "0.34.0",
63
63
  "@inquirer/prompts": "^8.3.0",
64
64
  "@typesafe-ai/sdk": "0.6.0",
65
65
  "chalk": "^5.6.2",