@cdot65/prisma-airs-cli 4.0.1 → 4.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.
- package/README.md +10 -6
- package/dist/cli/index.js +1592 -248
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -45,6 +45,13 @@ import { dirname as dirname4, join as join4 } from "path";
|
|
|
45
45
|
import { fileURLToPath } from "url";
|
|
46
46
|
import { Command } from "commander";
|
|
47
47
|
|
|
48
|
+
// src/cli/commands/aigateway.ts
|
|
49
|
+
import {
|
|
50
|
+
GatewayDefaultsInputSchema,
|
|
51
|
+
GatewayRateLimitInputSchema,
|
|
52
|
+
GatewayUsageLimitInputSchema
|
|
53
|
+
} from "@cdot65/prisma-airs-sdk";
|
|
54
|
+
|
|
48
55
|
// src/airs/aigateway.ts
|
|
49
56
|
import {
|
|
50
57
|
AIGatewayClient
|
|
@@ -2936,29 +2943,1534 @@ function renderScanLogList(results, pageToken, format = "pretty") {
|
|
|
2936
2943
|
console.log();
|
|
2937
2944
|
}
|
|
2938
2945
|
|
|
2939
|
-
// src/cli/confirm.ts
|
|
2940
|
-
async function confirmOrAbort(message, force, options = {}) {
|
|
2941
|
-
if (force) return;
|
|
2942
|
-
const interactive = options.isTTY ?? process.stdout.isTTY === true;
|
|
2943
|
-
if (!interactive) {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2946
|
+
// src/cli/confirm.ts
|
|
2947
|
+
async function confirmOrAbort(message, force, options = {}) {
|
|
2948
|
+
if (force) return;
|
|
2949
|
+
const interactive = options.isTTY ?? process.stdout.isTTY === true;
|
|
2950
|
+
if (!interactive) {
|
|
2951
|
+
await options.onAbort?.(2);
|
|
2952
|
+
usageError(
|
|
2953
|
+
`refusing to ${options.action ?? "proceed"} without --force in non-interactive mode`
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
const prompt = options.promptFn ?? (await import("@inquirer/prompts")).confirm;
|
|
2957
|
+
const confirmed = await prompt({ message, default: false });
|
|
2958
|
+
if (!confirmed) {
|
|
2959
|
+
await options.onAbort?.(0);
|
|
2960
|
+
ui.info("Aborted");
|
|
2961
|
+
process.exit(0);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// src/cli/examples.ts
|
|
2966
|
+
function examples(...lines) {
|
|
2967
|
+
return `
|
|
2968
|
+
Examples:
|
|
2969
|
+
${lines.map((l) => ` $ ${l}`).join("\n")}
|
|
2970
|
+
`;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
// src/cli/commands/aigateway/inventory.ts
|
|
2974
|
+
import {
|
|
2975
|
+
AI_GATEWAY_DEPLOYMENT_STATUSES,
|
|
2976
|
+
AI_GATEWAY_DEPLOYMENT_TYPES,
|
|
2977
|
+
AI_GATEWAY_KNOWN_API_KEY_SCOPES,
|
|
2978
|
+
AI_GATEWAY_KNOWN_MCP_AUTH_TYPES,
|
|
2979
|
+
AI_GATEWAY_KNOWN_MCP_TRANSPORTS,
|
|
2980
|
+
AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES,
|
|
2981
|
+
GatewayApiKeyRotateRequestSchema,
|
|
2982
|
+
GatewayApiKeyUpdateRequestSchema,
|
|
2983
|
+
GatewayConfigCreateRequestSchema,
|
|
2984
|
+
GatewayConfigUpdateRequestSchema,
|
|
2985
|
+
GatewayDeploymentCreateRequestSchema,
|
|
2986
|
+
GatewayDeploymentUpdateRequestSchema,
|
|
2987
|
+
GatewayGuardrailCreateRequestSchema,
|
|
2988
|
+
GatewayGuardrailUpdateRequestSchema,
|
|
2989
|
+
GatewayIntegrationCreateRequestSchema,
|
|
2990
|
+
GatewayIntegrationModelsBulkUpdateRequestSchema,
|
|
2991
|
+
GatewayIntegrationUpdateRequestSchema,
|
|
2992
|
+
GatewayIntegrationWorkspacesBulkUpdateRequestSchema,
|
|
2993
|
+
GatewayOrganisationAuthSettingsUpdateRequestSchema,
|
|
2994
|
+
GatewayOrganisationUpdateRequestSchema,
|
|
2995
|
+
GatewayPluginCreateRequestSchema,
|
|
2996
|
+
GatewayProviderCreateRequestSchema,
|
|
2997
|
+
GatewayProviderUpdateRequestSchema,
|
|
2998
|
+
GatewayServiceApiKeyCreateRequestSchema,
|
|
2999
|
+
GatewayUserApiKeyCreateRequestSchema,
|
|
3000
|
+
McpIntegrationCapabilitiesBulkUpdateRequestSchema,
|
|
3001
|
+
McpIntegrationCreateRequestSchema,
|
|
3002
|
+
McpIntegrationUpdateRequestSchema,
|
|
3003
|
+
McpIntegrationWorkspacesBulkUpdateRequestSchema,
|
|
3004
|
+
redactAIGatewaySecrets
|
|
3005
|
+
} from "@cdot65/prisma-airs-sdk";
|
|
3006
|
+
|
|
3007
|
+
// src/cli/debug-logger.ts
|
|
3008
|
+
import {
|
|
3009
|
+
appendFileSync,
|
|
3010
|
+
mkdirSync,
|
|
3011
|
+
readdirSync,
|
|
3012
|
+
statSync,
|
|
3013
|
+
unlinkSync,
|
|
3014
|
+
writeFileSync
|
|
3015
|
+
} from "fs";
|
|
3016
|
+
import { dirname, join } from "path";
|
|
3017
|
+
var AIRS_DOMAINS = [
|
|
3018
|
+
"api.sase.paloaltonetworks.com",
|
|
3019
|
+
"service.api.aisecurity.paloaltonetworks.com",
|
|
3020
|
+
"auth.apps.paloaltonetworks.com",
|
|
3021
|
+
"api.dlp.paloaltonetworks.com"
|
|
3022
|
+
];
|
|
3023
|
+
function isAirsUrl(url) {
|
|
3024
|
+
try {
|
|
3025
|
+
const parsed = new URL(url);
|
|
3026
|
+
return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
|
|
3027
|
+
} catch {
|
|
3028
|
+
return false;
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
var MASK = "***";
|
|
3032
|
+
var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key|client[-_]?auth|^key$/i;
|
|
3033
|
+
function isSensitiveKey(key) {
|
|
3034
|
+
return SENSITIVE_KEY_PATTERN.test(key);
|
|
3035
|
+
}
|
|
3036
|
+
function redactHeaders(headers) {
|
|
3037
|
+
const out = {};
|
|
3038
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
3039
|
+
out[k] = isSensitiveKey(k) ? MASK : v;
|
|
3040
|
+
}
|
|
3041
|
+
return out;
|
|
3042
|
+
}
|
|
3043
|
+
function redactDeep(value) {
|
|
3044
|
+
if (Array.isArray(value)) {
|
|
3045
|
+
return value.map(redactDeep);
|
|
3046
|
+
}
|
|
3047
|
+
if (value !== null && typeof value === "object") {
|
|
3048
|
+
const out = {};
|
|
3049
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3050
|
+
out[k] = isSensitiveKey(k) ? MASK : redactDeep(v);
|
|
3051
|
+
}
|
|
3052
|
+
return out;
|
|
3053
|
+
}
|
|
3054
|
+
return value;
|
|
3055
|
+
}
|
|
3056
|
+
function redactUrl(url) {
|
|
3057
|
+
try {
|
|
3058
|
+
const parsed = new URL(url);
|
|
3059
|
+
let touched = false;
|
|
3060
|
+
for (const key of parsed.searchParams.keys()) {
|
|
3061
|
+
if (isSensitiveKey(key)) {
|
|
3062
|
+
parsed.searchParams.set(key, MASK);
|
|
3063
|
+
touched = true;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return touched ? parsed.toString() : url;
|
|
3067
|
+
} catch {
|
|
3068
|
+
return url;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
function pruneDebugLogs(dir, keep) {
|
|
3072
|
+
let files;
|
|
3073
|
+
try {
|
|
3074
|
+
files = readdirSync(dir).filter((f) => f.startsWith("debug-api-") && f.endsWith(".jsonl"));
|
|
3075
|
+
} catch {
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
const byAge = files.map((f) => {
|
|
3079
|
+
const path3 = join(dir, f);
|
|
3080
|
+
try {
|
|
3081
|
+
return { path: path3, mtime: statSync(path3).mtimeMs };
|
|
3082
|
+
} catch {
|
|
3083
|
+
return null;
|
|
3084
|
+
}
|
|
3085
|
+
}).filter((e) => e !== null).sort((a, b) => b.mtime - a.mtime);
|
|
3086
|
+
for (const { path: path3 } of byAge.slice(keep)) {
|
|
3087
|
+
try {
|
|
3088
|
+
unlinkSync(path3);
|
|
3089
|
+
} catch {
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
function headersToRecord(headers) {
|
|
3094
|
+
if (!headers) return {};
|
|
3095
|
+
if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
|
|
3096
|
+
const out = {};
|
|
3097
|
+
headers.forEach((v, k) => {
|
|
3098
|
+
out[k] = v;
|
|
3099
|
+
});
|
|
3100
|
+
return out;
|
|
3101
|
+
}
|
|
3102
|
+
if (Array.isArray(headers)) {
|
|
3103
|
+
return Object.fromEntries(headers);
|
|
3104
|
+
}
|
|
3105
|
+
return headers;
|
|
3106
|
+
}
|
|
3107
|
+
var KEEP_DEBUG_LOGS = 10;
|
|
3108
|
+
function installDebugLogger(logPath) {
|
|
3109
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
3110
|
+
writeFileSync(logPath, "", "utf-8");
|
|
3111
|
+
pruneDebugLogs(dirname(logPath), KEEP_DEBUG_LOGS);
|
|
3112
|
+
const originalFetch = globalThis.fetch;
|
|
3113
|
+
globalThis.fetch = async function debugFetch(input, init2) {
|
|
3114
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
3115
|
+
if (!isAirsUrl(url)) {
|
|
3116
|
+
return originalFetch(input, init2);
|
|
3117
|
+
}
|
|
3118
|
+
const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
|
|
3119
|
+
const reqHeaders = redactHeaders(headersToRecord(init2?.headers));
|
|
3120
|
+
const loggedUrl = redactUrl(url);
|
|
3121
|
+
let reqBody;
|
|
3122
|
+
if (init2?.body) {
|
|
3123
|
+
try {
|
|
3124
|
+
reqBody = redactDeep(JSON.parse(String(init2.body)));
|
|
3125
|
+
} catch {
|
|
3126
|
+
reqBody = String(init2.body);
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
3130
|
+
const startMs = Date.now();
|
|
3131
|
+
let response;
|
|
3132
|
+
let resBody;
|
|
3133
|
+
let error;
|
|
3134
|
+
try {
|
|
3135
|
+
response = await originalFetch(input, init2);
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
error = err instanceof Error ? err.message : String(err);
|
|
3138
|
+
const entry2 = JSON.stringify({
|
|
3139
|
+
timestamp: ts2,
|
|
3140
|
+
durationMs: Date.now() - startMs,
|
|
3141
|
+
request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
|
|
3142
|
+
error
|
|
3143
|
+
});
|
|
3144
|
+
appendFileSync(logPath, `${entry2}
|
|
3145
|
+
`);
|
|
3146
|
+
throw err;
|
|
3147
|
+
}
|
|
3148
|
+
const durationMs = Date.now() - startMs;
|
|
3149
|
+
const resHeaders = {};
|
|
3150
|
+
response.headers.forEach((v, k) => {
|
|
3151
|
+
resHeaders[k] = v;
|
|
3152
|
+
});
|
|
3153
|
+
const clone = response.clone();
|
|
3154
|
+
try {
|
|
3155
|
+
const text = await clone.text();
|
|
3156
|
+
try {
|
|
3157
|
+
resBody = redactDeep(JSON.parse(text));
|
|
3158
|
+
} catch {
|
|
3159
|
+
resBody = text;
|
|
3160
|
+
}
|
|
3161
|
+
} catch {
|
|
3162
|
+
resBody = "<unreadable>";
|
|
3163
|
+
}
|
|
3164
|
+
const entry = JSON.stringify({
|
|
3165
|
+
timestamp: ts2,
|
|
3166
|
+
durationMs,
|
|
3167
|
+
request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
|
|
3168
|
+
response: {
|
|
3169
|
+
status: response.status,
|
|
3170
|
+
statusText: response.statusText,
|
|
3171
|
+
headers: redactHeaders(resHeaders),
|
|
3172
|
+
body: resBody
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
appendFileSync(logPath, `${entry}
|
|
3176
|
+
`);
|
|
3177
|
+
return response;
|
|
3178
|
+
};
|
|
3179
|
+
return {
|
|
3180
|
+
teardown() {
|
|
3181
|
+
globalThis.fetch = originalFetch;
|
|
3182
|
+
}
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
// src/cli/commands/aigateway/shared.ts
|
|
3187
|
+
import { open, readFile, unlink } from "fs/promises";
|
|
3188
|
+
import { AIGatewayClient as AIGatewayClient2 } from "@cdot65/prisma-airs-sdk";
|
|
3189
|
+
import { dump as dump3, load } from "js-yaml";
|
|
3190
|
+
async function defaultClientFactory() {
|
|
3191
|
+
const config = await loadConfig();
|
|
3192
|
+
return new AIGatewayClient2(aiGatewayClientOptions(config));
|
|
3193
|
+
}
|
|
3194
|
+
var clientFactory = defaultClientFactory;
|
|
3195
|
+
async function createAiGatewayClient() {
|
|
3196
|
+
return clientFactory();
|
|
3197
|
+
}
|
|
3198
|
+
function showHelpOnEmpty(command) {
|
|
3199
|
+
return command.action(() => command.outputHelp());
|
|
3200
|
+
}
|
|
3201
|
+
function addReadOutput(command) {
|
|
3202
|
+
return command.option(
|
|
3203
|
+
"--output <format>",
|
|
3204
|
+
"Output format: pretty, table, markdown, csv, json, yaml"
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
function addWriteOutput(command) {
|
|
3208
|
+
return command.option("--output <format>", "Output format: pretty, json, yaml");
|
|
3209
|
+
}
|
|
3210
|
+
async function readGatewayRequest(path3) {
|
|
3211
|
+
let parsed;
|
|
3212
|
+
try {
|
|
3213
|
+
const text = await readFile(path3, "utf8");
|
|
3214
|
+
parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(text) : JSON.parse(text);
|
|
3215
|
+
} catch (error) {
|
|
3216
|
+
throw new CliUsageError(
|
|
3217
|
+
`Unable to parse --file '${path3}' as JSON or YAML: ${error instanceof Error ? error.message : String(error)}`
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
3220
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3221
|
+
throw new CliUsageError(`--file '${path3}' must contain an object`);
|
|
3222
|
+
}
|
|
3223
|
+
return parsed;
|
|
3224
|
+
}
|
|
3225
|
+
function failAiGateway(error) {
|
|
3226
|
+
const hint = aiGatewayGrantHint(error);
|
|
3227
|
+
if (hint) ui.warn(`403: ${hint}`);
|
|
3228
|
+
fail(error);
|
|
3229
|
+
}
|
|
3230
|
+
function responseItems(value) {
|
|
3231
|
+
const record = asRecord2(value);
|
|
3232
|
+
if (Array.isArray(value)) return value.map(asRecord2);
|
|
3233
|
+
if (Array.isArray(record.data)) return record.data.map(asRecord2);
|
|
3234
|
+
const data = asRecord2(record.data);
|
|
3235
|
+
if (Array.isArray(data.records)) return data.records.map(asRecord2);
|
|
3236
|
+
if (Array.isArray(record.records)) return record.records.map(asRecord2);
|
|
3237
|
+
return [];
|
|
3238
|
+
}
|
|
3239
|
+
function asRecord2(value) {
|
|
3240
|
+
return value !== null && typeof value === "object" ? value : { value };
|
|
3241
|
+
}
|
|
3242
|
+
function displayColumns(items) {
|
|
3243
|
+
const preferred = [
|
|
3244
|
+
"id",
|
|
3245
|
+
"slug",
|
|
3246
|
+
"name",
|
|
3247
|
+
"type",
|
|
3248
|
+
"status",
|
|
3249
|
+
"enabled",
|
|
3250
|
+
"created_at",
|
|
3251
|
+
"last_updated_at"
|
|
3252
|
+
];
|
|
3253
|
+
const keys = new Set(items.flatMap((item) => Object.keys(item)));
|
|
3254
|
+
const scalar = (key) => items.every((item) => item[key] == null || typeof item[key] !== "object");
|
|
3255
|
+
const chosen = [
|
|
3256
|
+
...preferred.filter((key) => keys.has(key) && scalar(key)),
|
|
3257
|
+
...[...keys].filter((key) => !preferred.includes(key) && scalar(key)).sort()
|
|
3258
|
+
].slice(0, 8);
|
|
3259
|
+
return chosen.map((key) => ({
|
|
3260
|
+
key,
|
|
3261
|
+
label: key.replaceAll("_", " ").replace(/\b\w/g, (c) => c.toUpperCase())
|
|
3262
|
+
}));
|
|
3263
|
+
}
|
|
3264
|
+
function renderGatewayList(items, format, label) {
|
|
3265
|
+
if (format === "json") {
|
|
3266
|
+
console.log(JSON.stringify(items, null, 2));
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
if (format === "yaml") {
|
|
3270
|
+
console.log(dump3(items, { noRefs: true, lineWidth: -1 }).trimEnd());
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
if (items.length === 0) {
|
|
3274
|
+
ui.emptyList(label);
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
const columns = displayColumns(items);
|
|
3278
|
+
const rendered = formatOutput(items, columns, format === "pretty" ? "table" : format);
|
|
3279
|
+
if (rendered) console.log(rendered);
|
|
3280
|
+
}
|
|
3281
|
+
function renderGatewayDetail(value, format) {
|
|
3282
|
+
const item = asRecord2(value);
|
|
3283
|
+
if (format === "json") {
|
|
3284
|
+
console.log(JSON.stringify(item, null, 2));
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
if (format === "yaml") {
|
|
3288
|
+
console.log(dump3(item, { noRefs: true, lineWidth: -1 }).trimEnd());
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
const rows = Object.entries(item).map(([key, raw]) => ({
|
|
3292
|
+
key,
|
|
3293
|
+
value: raw !== null && typeof raw === "object" ? JSON.stringify(raw) : raw
|
|
3294
|
+
}));
|
|
3295
|
+
const rendered = formatOutput(
|
|
3296
|
+
rows,
|
|
3297
|
+
[
|
|
3298
|
+
{ key: "key", label: "Field" },
|
|
3299
|
+
{ key: "value", label: "Value" }
|
|
3300
|
+
],
|
|
3301
|
+
format === "pretty" ? "table" : format
|
|
3302
|
+
);
|
|
3303
|
+
if (rendered) console.log(rendered);
|
|
3304
|
+
}
|
|
3305
|
+
async function runList(command, opts, label, load2) {
|
|
3306
|
+
try {
|
|
3307
|
+
const format = await resolveOutput(command, opts);
|
|
3308
|
+
const client = await createAiGatewayClient();
|
|
3309
|
+
renderGatewayList(responseItems(await load2(client)), format, label);
|
|
3310
|
+
} catch (error) {
|
|
3311
|
+
failAiGateway(error);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
async function runDetail(command, opts, load2) {
|
|
3315
|
+
try {
|
|
3316
|
+
const format = await resolveOutput(command, opts);
|
|
3317
|
+
const client = await createAiGatewayClient();
|
|
3318
|
+
renderGatewayDetail(await load2(client), format);
|
|
3319
|
+
} catch (error) {
|
|
3320
|
+
failAiGateway(error);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
async function runWrite(command, opts, prepareOrWrite, preparedWrite) {
|
|
3324
|
+
try {
|
|
3325
|
+
const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
|
|
3326
|
+
const prepared = preparedWrite ? await prepareOrWrite() : void 0;
|
|
3327
|
+
const client = await createAiGatewayClient();
|
|
3328
|
+
const result = preparedWrite ? await preparedWrite(client, prepared) : await prepareOrWrite(client);
|
|
3329
|
+
renderGatewayDetail(result, format);
|
|
3330
|
+
} catch (error) {
|
|
3331
|
+
failAiGateway(error);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
async function runConfirmedWrite(command, opts, prompt, prepareOrWrite, preparedWrite) {
|
|
3335
|
+
try {
|
|
3336
|
+
const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
|
|
3337
|
+
const prepared = preparedWrite ? await prepareOrWrite() : void 0;
|
|
3338
|
+
const action = prompt.replace(/\?$/, "").replace(/^./, (character) => character.toLowerCase());
|
|
3339
|
+
await confirmOrAbort(prompt, Boolean(opts.force), { action });
|
|
3340
|
+
const client = await createAiGatewayClient();
|
|
3341
|
+
const result = preparedWrite ? await preparedWrite(client, prepared) : await prepareOrWrite(client);
|
|
3342
|
+
renderGatewayDetail(result ?? { success: true }, format);
|
|
3343
|
+
} catch (error) {
|
|
3344
|
+
failAiGateway(error);
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
async function runSecretWrite(command, opts, prepare, write, prompt, settings = {}) {
|
|
3348
|
+
let destinationHandle;
|
|
3349
|
+
let destinationReserved = false;
|
|
3350
|
+
let secretPersisted = false;
|
|
3351
|
+
const cleanupReservedDestination = async () => {
|
|
3352
|
+
if (destinationHandle) await destinationHandle.close().catch(() => void 0);
|
|
3353
|
+
destinationHandle = void 0;
|
|
3354
|
+
if (opts.secretOutput && destinationReserved && !secretPersisted) {
|
|
3355
|
+
await unlink(opts.secretOutput).catch(() => void 0);
|
|
3356
|
+
}
|
|
3357
|
+
destinationReserved = false;
|
|
3358
|
+
};
|
|
3359
|
+
try {
|
|
3360
|
+
const prepared = await prepare();
|
|
3361
|
+
const requiresDestination = settings.requiresDestination?.(prepared) ?? true;
|
|
3362
|
+
if (requiresDestination && !opts.secretOutput && !opts.showSecret) {
|
|
3363
|
+
throw new CliUsageError(
|
|
3364
|
+
"Choose --secret-output <path> (recommended) or --show-secret before requesting a one-time credential"
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
|
|
3368
|
+
if (requiresDestination && opts.secretOutput) {
|
|
3369
|
+
destinationHandle = await open(opts.secretOutput, "wx", 384);
|
|
3370
|
+
destinationReserved = true;
|
|
3371
|
+
}
|
|
3372
|
+
if (prompt) {
|
|
3373
|
+
const action = prompt.replace(/\?$/, "").replace(/^./, (character) => character.toLowerCase());
|
|
3374
|
+
await confirmOrAbort(prompt, Boolean(opts.force), {
|
|
3375
|
+
action,
|
|
3376
|
+
onAbort: cleanupReservedDestination
|
|
3377
|
+
});
|
|
3378
|
+
}
|
|
3379
|
+
const client = await createAiGatewayClient();
|
|
3380
|
+
const result = await write(client, prepared);
|
|
3381
|
+
if (requiresDestination && opts.secretOutput && destinationHandle) {
|
|
3382
|
+
await destinationHandle.writeFile(`${JSON.stringify(result, null, 2)}
|
|
3383
|
+
`, "utf8");
|
|
3384
|
+
secretPersisted = true;
|
|
3385
|
+
await destinationHandle.close();
|
|
3386
|
+
destinationHandle = void 0;
|
|
3387
|
+
renderGatewayDetail({ saved_to: opts.secretOutput }, format);
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
renderGatewayDetail(
|
|
3391
|
+
requiresDestination ? result : settings.redactResponse?.(result) ?? result,
|
|
3392
|
+
format
|
|
3393
|
+
);
|
|
3394
|
+
} catch (error) {
|
|
3395
|
+
await cleanupReservedDestination();
|
|
3396
|
+
failAiGateway(error);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// src/cli/commands/aigateway/structured-input.ts
|
|
3401
|
+
import {
|
|
3402
|
+
AISecSDKException,
|
|
3403
|
+
buildDottedObject,
|
|
3404
|
+
ErrorType,
|
|
3405
|
+
GatewayJsonObjectSchema,
|
|
3406
|
+
setDottedValue
|
|
3407
|
+
} from "@cdot65/prisma-airs-sdk";
|
|
3408
|
+
function collectOption(value, previous = []) {
|
|
3409
|
+
return [...previous, value];
|
|
3410
|
+
}
|
|
3411
|
+
function addStructuredInputOptions(command) {
|
|
3412
|
+
return command.option("--file <path>", "Advanced JSON/YAML request base; named flags override it").option(
|
|
3413
|
+
"--set <path=value>",
|
|
3414
|
+
"Set a typed JSON value at a request path (repeatable)",
|
|
3415
|
+
collectOption
|
|
3416
|
+
).option(
|
|
3417
|
+
"--set-string <path=value>",
|
|
3418
|
+
"Set a literal string at a request path (repeatable)",
|
|
3419
|
+
collectOption
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
3422
|
+
function parseBooleanOption(value) {
|
|
3423
|
+
if (value === true || value === false) return value;
|
|
3424
|
+
if (value === "true") return true;
|
|
3425
|
+
if (value === "false") return false;
|
|
3426
|
+
throw new CliUsageError("Expected true or false");
|
|
3427
|
+
}
|
|
3428
|
+
function parseIntegerOption(value) {
|
|
3429
|
+
const text = String(value);
|
|
3430
|
+
if (!/^-?(0|[1-9]\d*)$/.test(text)) throw new CliUsageError("Expected an integer");
|
|
3431
|
+
const parsed = Number(text);
|
|
3432
|
+
if (!Number.isSafeInteger(parsed)) throw new CliUsageError("Expected a safe integer");
|
|
3433
|
+
return parsed;
|
|
3434
|
+
}
|
|
3435
|
+
function parseDateOption(value) {
|
|
3436
|
+
const parsed = new Date(String(value));
|
|
3437
|
+
if (Number.isNaN(parsed.getTime())) throw new CliUsageError("Expected an ISO-8601 timestamp");
|
|
3438
|
+
return parsed;
|
|
3439
|
+
}
|
|
3440
|
+
function parseCsvOption(value) {
|
|
3441
|
+
const values = String(value).split(",").map((item) => item.trim()).filter(Boolean);
|
|
3442
|
+
if (values.length === 0) throw new CliUsageError("Expected a non-empty comma-separated list");
|
|
3443
|
+
return values;
|
|
3444
|
+
}
|
|
3445
|
+
function parseJsonOption(value) {
|
|
3446
|
+
try {
|
|
3447
|
+
return JSON.parse(String(value));
|
|
3448
|
+
} catch {
|
|
3449
|
+
throw new CliUsageError("Expected valid JSON");
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
function repeatableValues(value) {
|
|
3453
|
+
if (Array.isArray(value)) return value.map(String);
|
|
3454
|
+
return [String(value)];
|
|
3455
|
+
}
|
|
3456
|
+
function splitKeyValue(raw, label) {
|
|
3457
|
+
const separator = raw.indexOf("=");
|
|
3458
|
+
if (separator <= 0) throw new CliUsageError(`Expected ${label}=value`);
|
|
3459
|
+
return [raw.slice(0, separator), raw.slice(separator + 1)];
|
|
3460
|
+
}
|
|
3461
|
+
function parseBooleanBindingsOption(value) {
|
|
3462
|
+
return repeatableValues(value).map((raw) => {
|
|
3463
|
+
const [id, enabled] = splitKeyValue(raw, "id");
|
|
3464
|
+
return { id, enabled: parseBooleanOption(enabled) };
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3467
|
+
function parseModelBindingsOption(value) {
|
|
3468
|
+
return repeatableValues(value).map((raw) => {
|
|
3469
|
+
const [slug, enabled] = splitKeyValue(raw, "slug");
|
|
3470
|
+
return { slug, enabled: parseBooleanOption(enabled) };
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
function parseCapabilityBindingsOption(value) {
|
|
3474
|
+
return repeatableValues(value).map((raw) => {
|
|
3475
|
+
const [identity, enabled] = splitKeyValue(raw, "type:name");
|
|
3476
|
+
const separator = identity.indexOf(":");
|
|
3477
|
+
if (separator <= 0 || separator === identity.length - 1) {
|
|
3478
|
+
throw new CliUsageError("Expected type:name=enabled");
|
|
3479
|
+
}
|
|
3480
|
+
return {
|
|
3481
|
+
type: identity.slice(0, separator),
|
|
3482
|
+
name: identity.slice(separator + 1),
|
|
3483
|
+
enabled: parseBooleanOption(enabled)
|
|
3484
|
+
};
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
function parseStringMapOption(value) {
|
|
3488
|
+
const result = {};
|
|
3489
|
+
for (const raw of repeatableValues(value)) {
|
|
3490
|
+
const [key, item] = splitKeyValue(raw, "key");
|
|
3491
|
+
if (["__proto__", "constructor", "prototype"].includes(key)) {
|
|
3492
|
+
throw new CliUsageError(`Unsafe key: ${key}`);
|
|
3493
|
+
}
|
|
3494
|
+
if (Object.hasOwn(result, key)) throw new CliUsageError(`Duplicate key: ${key}`);
|
|
3495
|
+
result[key] = item;
|
|
3496
|
+
}
|
|
3497
|
+
return result;
|
|
3498
|
+
}
|
|
3499
|
+
function parseAssignment(raw, forceString) {
|
|
3500
|
+
const separator = raw.indexOf("=");
|
|
3501
|
+
if (separator <= 0) throw new CliUsageError("Expected path=value");
|
|
3502
|
+
const path3 = raw.slice(0, separator);
|
|
3503
|
+
const text = raw.slice(separator + 1);
|
|
3504
|
+
if (forceString) return [path3, text];
|
|
3505
|
+
try {
|
|
3506
|
+
return [path3, JSON.parse(text)];
|
|
3507
|
+
} catch {
|
|
3508
|
+
return [path3, text];
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
function schemaMessage(error) {
|
|
3512
|
+
const issues = error?.issues;
|
|
3513
|
+
if (!Array.isArray(issues) || issues.length === 0)
|
|
3514
|
+
return "request body does not match its schema";
|
|
3515
|
+
return issues.map((issue) => {
|
|
3516
|
+
const path3 = issue.path?.length ? issue.path.map(String).join(".") : "<root>";
|
|
3517
|
+
return `${path3}: ${issue.message ?? "invalid value"}`;
|
|
3518
|
+
}).join("; ");
|
|
3519
|
+
}
|
|
3520
|
+
function optionName(option) {
|
|
3521
|
+
return `--${option.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`;
|
|
3522
|
+
}
|
|
3523
|
+
function asUsageError(error, prefix = "Invalid AI Gateway request") {
|
|
3524
|
+
if (error instanceof CliUsageError) return error;
|
|
3525
|
+
if (error instanceof AISecSDKException && error.errorType === ErrorType.USER_REQUEST_PAYLOAD_ERROR) {
|
|
3526
|
+
return new CliUsageError(`${prefix}: ${error.message}`);
|
|
3527
|
+
}
|
|
3528
|
+
return new CliUsageError(`${prefix}: ${schemaMessage(error)}`);
|
|
3529
|
+
}
|
|
3530
|
+
async function buildStructuredRequest(options, schema, fields = []) {
|
|
3531
|
+
try {
|
|
3532
|
+
let body = {};
|
|
3533
|
+
if (options.file) {
|
|
3534
|
+
body = GatewayJsonObjectSchema.parse(await readGatewayRequest(options.file));
|
|
3535
|
+
}
|
|
3536
|
+
for (const field of fields) {
|
|
3537
|
+
const raw = options[field.option];
|
|
3538
|
+
if (raw === void 0 || Array.isArray(raw) && raw.length === 0) continue;
|
|
3539
|
+
let value;
|
|
3540
|
+
try {
|
|
3541
|
+
value = field.parse ? field.parse(raw) : raw;
|
|
3542
|
+
} catch (error) {
|
|
3543
|
+
if (error instanceof CliUsageError) {
|
|
3544
|
+
throw new CliUsageError(`Invalid ${optionName(field.option)}: ${error.message}`);
|
|
3545
|
+
}
|
|
3546
|
+
throw error;
|
|
3547
|
+
}
|
|
3548
|
+
body = setDottedValue(body, field.path, value);
|
|
3549
|
+
}
|
|
3550
|
+
const assignments = [
|
|
3551
|
+
...(options.set ?? []).map((raw) => {
|
|
3552
|
+
try {
|
|
3553
|
+
const [path3, value] = parseAssignment(raw, false);
|
|
3554
|
+
return { path: path3, value };
|
|
3555
|
+
} catch (error) {
|
|
3556
|
+
if (error instanceof CliUsageError) {
|
|
3557
|
+
throw new CliUsageError(`Invalid --set '${raw}': ${error.message}`);
|
|
3558
|
+
}
|
|
3559
|
+
throw error;
|
|
3560
|
+
}
|
|
3561
|
+
}),
|
|
3562
|
+
...(options.setString ?? []).map((raw) => {
|
|
3563
|
+
try {
|
|
3564
|
+
const [path3, value] = parseAssignment(raw, true);
|
|
3565
|
+
return { path: path3, value };
|
|
3566
|
+
} catch (error) {
|
|
3567
|
+
if (error instanceof CliUsageError) {
|
|
3568
|
+
throw new CliUsageError(`Invalid --set-string '${raw}': ${error.message}`);
|
|
3569
|
+
}
|
|
3570
|
+
throw error;
|
|
3571
|
+
}
|
|
3572
|
+
})
|
|
3573
|
+
];
|
|
3574
|
+
buildDottedObject(assignments);
|
|
3575
|
+
for (const { path: path3, value } of assignments) body = setDottedValue(body, path3, value);
|
|
3576
|
+
return schema.parse(body);
|
|
3577
|
+
} catch (error) {
|
|
3578
|
+
throw asUsageError(error);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
// src/cli/commands/aigateway/inventory.ts
|
|
3583
|
+
function registerScopedReads(root, name, description, select, options = {}) {
|
|
3584
|
+
const group = showHelpOnEmpty(root.command(name).description(description));
|
|
3585
|
+
const list = addReadOutput(
|
|
3586
|
+
group.command("list").description(`List ${name} in a workspace (data plane)`).requiredOption("--workspace <uuid>", "Workspace UUID")
|
|
3587
|
+
);
|
|
3588
|
+
list.action(
|
|
3589
|
+
(opts) => runList(list, opts, name, (client) => select(client).list({ workspaceId: opts.workspace }))
|
|
3590
|
+
);
|
|
3591
|
+
let get = group.command("get <id>").description(`Get one ${name.replace(/s$/, "")} by UUID (data plane)`);
|
|
3592
|
+
if (options.sensitiveDetail) {
|
|
3593
|
+
get = get.option("--reveal-sensitive", "Show credential-bearing fields");
|
|
3594
|
+
}
|
|
3595
|
+
addReadOutput(get);
|
|
3596
|
+
get.action(
|
|
3597
|
+
(id, opts) => runDetail(get, opts, async (client) => {
|
|
3598
|
+
const result = await select(client).get(id);
|
|
3599
|
+
if (!options.sensitiveDetail || opts.revealSensitive) return result;
|
|
3600
|
+
const metadataRedacted = options.sensitiveOperation ? redactAIGatewaySecrets(options.sensitiveOperation, result, "response") : result;
|
|
3601
|
+
return redactDeep(metadataRedacted);
|
|
3602
|
+
})
|
|
3603
|
+
);
|
|
3604
|
+
return group;
|
|
3605
|
+
}
|
|
3606
|
+
var knownValues = (values) => values.join(", ");
|
|
3607
|
+
function redactApiKeyMaterial(value) {
|
|
3608
|
+
if (Array.isArray(value)) return value.map(redactApiKeyMaterial);
|
|
3609
|
+
if (value === null || typeof value !== "object") return value;
|
|
3610
|
+
return Object.fromEntries(
|
|
3611
|
+
Object.entries(value).map(([key, entry]) => [
|
|
3612
|
+
key,
|
|
3613
|
+
key === "key" ? "***" : redactApiKeyMaterial(entry)
|
|
3614
|
+
])
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
function parseNamedDate(value, flag) {
|
|
3618
|
+
try {
|
|
3619
|
+
return parseDateOption(value);
|
|
3620
|
+
} catch (error) {
|
|
3621
|
+
throw new CliUsageError(
|
|
3622
|
+
`Invalid ${flag}: ${error instanceof Error ? error.message : String(error)}`
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
function parsePositiveInteger(value, flag) {
|
|
3627
|
+
try {
|
|
3628
|
+
const parsed = parseIntegerOption(value);
|
|
3629
|
+
if (parsed <= 0) throw new CliUsageError("Expected a positive integer");
|
|
3630
|
+
return parsed;
|
|
3631
|
+
} catch (error) {
|
|
3632
|
+
throw new CliUsageError(
|
|
3633
|
+
`Invalid ${flag}: ${error instanceof Error ? error.message : String(error)}`
|
|
3634
|
+
);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
function addCrudMutationNodes(group, resource, request, operations) {
|
|
3638
|
+
let create = group.command("create").description(`Create a ${resource} from structured flags`);
|
|
3639
|
+
if (request.createOptions) create = request.createOptions(create);
|
|
3640
|
+
create = addWriteOutput(addStructuredInputOptions(create));
|
|
3641
|
+
create.action(
|
|
3642
|
+
(opts) => runWrite(
|
|
3643
|
+
create,
|
|
3644
|
+
opts,
|
|
3645
|
+
() => buildStructuredRequest(opts, request.createSchema, request.createFields),
|
|
3646
|
+
(client, body) => operations.create(client, body)
|
|
3647
|
+
)
|
|
3648
|
+
);
|
|
3649
|
+
let update = group.command("update <id>").description(`Update a ${resource} with structured flags`);
|
|
3650
|
+
if (request.updateOptions) update = request.updateOptions(update);
|
|
3651
|
+
update = addWriteOutput(addStructuredInputOptions(update));
|
|
3652
|
+
update.action(
|
|
3653
|
+
(id, opts) => runWrite(
|
|
3654
|
+
update,
|
|
3655
|
+
opts,
|
|
3656
|
+
() => buildStructuredRequest(opts, request.updateSchema, request.updateFields),
|
|
3657
|
+
(client, body) => operations.update(client, id, body)
|
|
3658
|
+
)
|
|
3659
|
+
);
|
|
3660
|
+
const remove = addWriteOutput(
|
|
3661
|
+
group.command("delete <id>").description("Permanently delete this resource").option("--force", "Skip confirmation prompt")
|
|
3662
|
+
);
|
|
3663
|
+
remove.action(
|
|
3664
|
+
(id, opts) => runConfirmedWrite(
|
|
3665
|
+
remove,
|
|
3666
|
+
opts,
|
|
3667
|
+
`Permanently delete ${resource} ${id}?`,
|
|
3668
|
+
(client) => operations.delete(client, id)
|
|
3669
|
+
)
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
function registerApiKeys(root) {
|
|
3673
|
+
const apiKeys = showHelpOnEmpty(
|
|
3674
|
+
root.command("api-keys").description("Manage service and user gateway credentials")
|
|
3675
|
+
);
|
|
3676
|
+
for (const kind of ["service", "user"]) {
|
|
3677
|
+
const group = showHelpOnEmpty(apiKeys.command(kind).description(`Manage ${kind} API keys`));
|
|
3678
|
+
const list = addReadOutput(
|
|
3679
|
+
group.command("list").description(`List ${kind} API keys in a workspace (data plane)`).requiredOption("--workspace <uuid>", "Workspace UUID").option("--reveal-sensitive", "Show API key material")
|
|
3680
|
+
);
|
|
3681
|
+
list.action(
|
|
3682
|
+
(opts) => runList(list, opts, `${kind} API keys`, async (client) => {
|
|
3683
|
+
const result = await (kind === "service" ? client.apiKeys.listService({ workspaceId: opts.workspace }) : client.apiKeys.listUser({ workspaceId: opts.workspace }));
|
|
3684
|
+
return opts.revealSensitive ? result : redactApiKeyMaterial(result);
|
|
3685
|
+
})
|
|
3686
|
+
);
|
|
3687
|
+
const get = addReadOutput(
|
|
3688
|
+
group.command("get <id>").description(`Get one ${kind} API key`).option("--reveal-sensitive", "Show API key material")
|
|
3689
|
+
);
|
|
3690
|
+
get.action(
|
|
3691
|
+
(id, opts) => runDetail(get, opts, async (client) => {
|
|
3692
|
+
const result = await (kind === "service" ? client.apiKeys.getService(id) : client.apiKeys.getUser(id));
|
|
3693
|
+
return opts.revealSensitive ? result : redactApiKeyMaterial(result);
|
|
3694
|
+
})
|
|
3695
|
+
);
|
|
3696
|
+
const createFields = [
|
|
3697
|
+
{ option: "alertEmails", path: "alert_emails", parse: parseCsvOption },
|
|
3698
|
+
{ option: "description", path: "description" },
|
|
3699
|
+
{ option: "expiresAt", path: "expires_at" },
|
|
3700
|
+
{ option: "name", path: "name" },
|
|
3701
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
3702
|
+
{ option: "scopes", path: "scopes", parse: parseCsvOption },
|
|
3703
|
+
{ option: "type", path: "type" },
|
|
3704
|
+
{ option: "workspace", path: "workspace_id" },
|
|
3705
|
+
...kind === "user" ? [{ option: "userId", path: "user_id" }] : []
|
|
3706
|
+
];
|
|
3707
|
+
let createCommand = group.command("create").description(`Create a ${kind} API key from structured flags`).option("--alert-emails <emails>", "Comma-separated alert email addresses").option("--description <text>", "Credential description").option("--expires-at <iso>", "Expiration as ISO-8601").option("--name <name>", "Credential name").option("--organisation-id <tsg>", "Numeric TSG id").option(
|
|
3708
|
+
"--scopes <scopes>",
|
|
3709
|
+
`Comma-separated scopes (known: ${knownValues(AI_GATEWAY_KNOWN_API_KEY_SCOPES)})`
|
|
3710
|
+
).option("--type <type>", "Credential type").option("--workspace <uuid>", "Workspace UUID").option("--secret-output <path>", "Write the one-time credential to a new 0600 file").option("--show-secret", "Print the one-time credential to stdout");
|
|
3711
|
+
if (kind === "user") createCommand = createCommand.option("--user-id <uuid>", "User UUID");
|
|
3712
|
+
const create = addWriteOutput(addStructuredInputOptions(createCommand));
|
|
3713
|
+
create.action((opts) => {
|
|
3714
|
+
if (kind === "service") {
|
|
3715
|
+
return runSecretWrite(
|
|
3716
|
+
create,
|
|
3717
|
+
opts,
|
|
3718
|
+
() => buildStructuredRequest(opts, GatewayServiceApiKeyCreateRequestSchema, createFields),
|
|
3719
|
+
(client, body) => client.apiKeys.createService(body)
|
|
3720
|
+
);
|
|
3721
|
+
}
|
|
3722
|
+
return runSecretWrite(
|
|
3723
|
+
create,
|
|
3724
|
+
opts,
|
|
3725
|
+
() => buildStructuredRequest(opts, GatewayUserApiKeyCreateRequestSchema, createFields),
|
|
3726
|
+
(client, body) => client.apiKeys.createUser(body)
|
|
3727
|
+
);
|
|
3728
|
+
});
|
|
3729
|
+
const update = addWriteOutput(
|
|
3730
|
+
addStructuredInputOptions(
|
|
3731
|
+
group.command("update <id>").description(`Update a ${kind} API key with structured flags`).option("--alert-emails <emails>", "Comma-separated alert email addresses").option("--description <text>", "Credential description").option("--expires-at <iso>", "Expiration as ISO-8601").option("--name <name>", "Credential name").option("--reset-usage <boolean>", "Reset accumulated usage: true or false").option(
|
|
3732
|
+
"--scopes <scopes>",
|
|
3733
|
+
`Comma-separated scopes (known: ${knownValues(AI_GATEWAY_KNOWN_API_KEY_SCOPES)})`
|
|
3734
|
+
)
|
|
3735
|
+
)
|
|
3736
|
+
);
|
|
3737
|
+
update.action(
|
|
3738
|
+
(id, opts) => runWrite(
|
|
3739
|
+
update,
|
|
3740
|
+
opts,
|
|
3741
|
+
() => buildStructuredRequest(opts, GatewayApiKeyUpdateRequestSchema, [
|
|
3742
|
+
{ option: "alertEmails", path: "alert_emails", parse: parseCsvOption },
|
|
3743
|
+
{ option: "description", path: "description" },
|
|
3744
|
+
{ option: "expiresAt", path: "expires_at" },
|
|
3745
|
+
{ option: "name", path: "name" },
|
|
3746
|
+
{ option: "resetUsage", path: "reset_usage", parse: parseBooleanOption },
|
|
3747
|
+
{ option: "scopes", path: "scopes", parse: parseCsvOption }
|
|
3748
|
+
]),
|
|
3749
|
+
(client, body) => kind === "service" ? client.apiKeys.updateService(id, body) : client.apiKeys.updateUser(id, body)
|
|
3750
|
+
)
|
|
3751
|
+
);
|
|
3752
|
+
const remove = addWriteOutput(
|
|
3753
|
+
group.command("delete <id>").description(`Revoke a ${kind} API key`).option("--force", "Skip confirmation prompt")
|
|
3754
|
+
);
|
|
3755
|
+
remove.action(
|
|
3756
|
+
(id, opts) => runConfirmedWrite(
|
|
3757
|
+
remove,
|
|
3758
|
+
opts,
|
|
3759
|
+
`Revoke ${kind} API key ${id}?`,
|
|
3760
|
+
(client) => kind === "service" ? client.apiKeys.deleteService(id) : client.apiKeys.deleteUser(id)
|
|
3761
|
+
)
|
|
3762
|
+
);
|
|
3763
|
+
const rotate = addWriteOutput(
|
|
3764
|
+
group.command("rotate <id>").description(`Rotate a ${kind} API key`).option("--force", "Skip confirmation prompt").option("--secret-output <path>", "Write the one-time credential to a new 0600 file").option("--show-secret", "Print the one-time credential to stdout").option("--transition-ms <ms>", "Credential overlap in milliseconds")
|
|
3765
|
+
);
|
|
3766
|
+
rotate.action(
|
|
3767
|
+
(id, opts) => runSecretWrite(
|
|
3768
|
+
rotate,
|
|
3769
|
+
opts,
|
|
3770
|
+
() => buildStructuredRequest(opts, GatewayApiKeyRotateRequestSchema, [
|
|
3771
|
+
{
|
|
3772
|
+
option: "transitionMs",
|
|
3773
|
+
path: "key_transition_period_ms",
|
|
3774
|
+
parse: parseIntegerOption
|
|
3775
|
+
}
|
|
3776
|
+
]),
|
|
3777
|
+
(client, body) => kind === "service" ? client.apiKeys.rotateService(id, body) : client.apiKeys.rotateUser(id, body),
|
|
3778
|
+
`Rotate ${kind} API key ${id}?`
|
|
3779
|
+
)
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
function registerAuditLogs(root) {
|
|
3784
|
+
const group = showHelpOnEmpty(
|
|
3785
|
+
root.command("audit-logs").description("Inspect organisation audit activity")
|
|
3786
|
+
);
|
|
3787
|
+
const list = addReadOutput(
|
|
3788
|
+
group.command("list").description("List audit activity for a UTC time window (admin plane)").option("--days <n>", "Rolling window in days", "7").option("--end <iso>", "Window end as ISO-8601").option("--reveal-sensitive", "Show sensitive request fields").option("--start <iso>", "Window start as ISO-8601")
|
|
3789
|
+
);
|
|
3790
|
+
list.action((opts) => {
|
|
3791
|
+
return runList(list, opts, "audit logs", async (client) => {
|
|
3792
|
+
const end = opts.end ? parseNamedDate(opts.end, "--end") : /* @__PURE__ */ new Date();
|
|
3793
|
+
const start = opts.start ? parseNamedDate(opts.start, "--start") : new Date(end.getTime() - parsePositiveInteger(opts.days, "--days") * 864e5);
|
|
3794
|
+
const result = await client.auditLogs.list({ start, end });
|
|
3795
|
+
return opts.revealSensitive ? result : redactDeep(result);
|
|
3796
|
+
});
|
|
3797
|
+
});
|
|
3798
|
+
}
|
|
3799
|
+
function registerConfigs(root) {
|
|
3800
|
+
const group = registerScopedReads(
|
|
3801
|
+
root,
|
|
3802
|
+
"configs",
|
|
3803
|
+
"Manage routing configurations",
|
|
3804
|
+
(client) => client.configs
|
|
3805
|
+
);
|
|
3806
|
+
const versions = addReadOutput(
|
|
3807
|
+
group.command("versions <id>").description("List immutable config versions")
|
|
3808
|
+
);
|
|
3809
|
+
versions.action(
|
|
3810
|
+
(id, opts) => runList(versions, opts, "config versions", (client) => client.configs.listVersions(id))
|
|
3811
|
+
);
|
|
3812
|
+
const commonFields = [
|
|
3813
|
+
{ option: "name", path: "name" },
|
|
3814
|
+
{ option: "workspace", path: "workspace_id" },
|
|
3815
|
+
{ option: "status", path: "status" }
|
|
3816
|
+
];
|
|
3817
|
+
addCrudMutationNodes(
|
|
3818
|
+
group,
|
|
3819
|
+
"config",
|
|
3820
|
+
{
|
|
3821
|
+
createFields: commonFields,
|
|
3822
|
+
createOptions: (command) => command.option("--name <name>", "Config name").option("--workspace <uuid>", "Workspace UUID"),
|
|
3823
|
+
createSchema: GatewayConfigCreateRequestSchema,
|
|
3824
|
+
updateFields: commonFields,
|
|
3825
|
+
updateOptions: (command) => command.option("--name <name>", "New config name").option("--status <status>", "New config status").option("--workspace <uuid>", "New workspace UUID"),
|
|
3826
|
+
updateSchema: GatewayConfigUpdateRequestSchema
|
|
3827
|
+
},
|
|
3828
|
+
{
|
|
3829
|
+
create: (client, body) => client.configs.create(body),
|
|
3830
|
+
delete: (client, id) => client.configs.delete(id),
|
|
3831
|
+
update: (client, id, body) => client.configs.update(id, body)
|
|
3832
|
+
}
|
|
3833
|
+
);
|
|
3834
|
+
}
|
|
3835
|
+
function registerDeployments(root) {
|
|
3836
|
+
const group = showHelpOnEmpty(
|
|
3837
|
+
root.command("deployments").description("Manage self-hosted gateway registrations")
|
|
3838
|
+
);
|
|
3839
|
+
const list = addReadOutput(group.command("list").description("List deployments (admin plane)"));
|
|
3840
|
+
list.action((opts) => runList(list, opts, "deployments", (client) => client.deployments.list()));
|
|
3841
|
+
const get = addReadOutput(
|
|
3842
|
+
group.command("get <id>").description("Get a deployment by UUID (admin plane)")
|
|
3843
|
+
);
|
|
3844
|
+
get.action((id, opts) => runDetail(get, opts, (client) => client.deployments.get(id)));
|
|
3845
|
+
const ping = addReadOutput(
|
|
3846
|
+
group.command("ping <id>").description("Run the optional control-plane ingress diagnostic")
|
|
3847
|
+
);
|
|
3848
|
+
ping.action((id, opts) => runDetail(ping, opts, (client) => client.deployments.ping(id)));
|
|
3849
|
+
const archive = addWriteOutput(
|
|
3850
|
+
group.command("archive <id>").description("Archive a deployment registration").requiredOption("--organisation-id <tsg>", "Numeric TSG id").option("--force", "Skip confirmation prompt")
|
|
3851
|
+
);
|
|
3852
|
+
archive.action(
|
|
3853
|
+
(id, opts) => runConfirmedWrite(
|
|
3854
|
+
archive,
|
|
3855
|
+
opts,
|
|
3856
|
+
`Archive deployment ${id}?`,
|
|
3857
|
+
(client) => client.deployments.delete(id, opts.organisationId)
|
|
3858
|
+
)
|
|
3859
|
+
);
|
|
3860
|
+
const create = addWriteOutput(
|
|
3861
|
+
addStructuredInputOptions(
|
|
3862
|
+
group.command("create").description("Register a self-hosted deployment from structured flags").option("--auth-settings <json>", "Deployment authentication settings object").option("--deployment-config <json>", "Deployment configuration object").option("--is-default <boolean>", "Make this the default deployment").option("--name <name>", "Deployment name").option("--organisation-id <tsg>", "Numeric TSG id").option("--slug <slug>", "Stable deployment slug").option("--type <type>", `Deployment type: ${knownValues(AI_GATEWAY_DEPLOYMENT_TYPES)}`).option("--secret-output <path>", "Write registration credentials to a new 0600 file").option("--show-secret", "Print registration credentials to stdout")
|
|
3863
|
+
)
|
|
3864
|
+
);
|
|
3865
|
+
create.action(
|
|
3866
|
+
(opts) => runSecretWrite(
|
|
3867
|
+
create,
|
|
3868
|
+
opts,
|
|
3869
|
+
() => buildStructuredRequest(opts, GatewayDeploymentCreateRequestSchema, [
|
|
3870
|
+
{ option: "authSettings", path: "auth_settings", parse: parseJsonOption },
|
|
3871
|
+
{ option: "deploymentConfig", path: "deployment_config", parse: parseJsonOption },
|
|
3872
|
+
{ option: "isDefault", path: "is_default", parse: parseBooleanOption },
|
|
3873
|
+
{ option: "name", path: "name" },
|
|
3874
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
3875
|
+
{ option: "slug", path: "slug" },
|
|
3876
|
+
{ option: "type", path: "type" }
|
|
3877
|
+
]),
|
|
3878
|
+
(client, body) => client.deployments.create(body)
|
|
3879
|
+
)
|
|
3880
|
+
);
|
|
3881
|
+
const update = addWriteOutput(
|
|
3882
|
+
addStructuredInputOptions(
|
|
3883
|
+
group.command("update <id>").description("Update a deployment registration with structured flags").option("--auth-settings <json>", "Deployment authentication settings object").option("--deployment-config <json>", "Deployment configuration object").option("--is-default <boolean>", "Make this the default deployment").option("--name <name>", "Deployment name").option("--override-existing <boolean>", "Override an existing registration").option("--rotate-auth <boolean>", "Rotate deployment authentication").option("--secret-output <path>", "Write rotated credentials to a new 0600 file").option("--show-secret", "Print rotated credentials to stdout").option(
|
|
3884
|
+
"--status <status>",
|
|
3885
|
+
`Deployment status: ${knownValues(AI_GATEWAY_DEPLOYMENT_STATUSES)}`
|
|
3886
|
+
).option("--type <type>", `Deployment type: ${knownValues(AI_GATEWAY_DEPLOYMENT_TYPES)}`)
|
|
3887
|
+
)
|
|
3888
|
+
);
|
|
3889
|
+
update.action(
|
|
3890
|
+
(id, opts) => runSecretWrite(
|
|
3891
|
+
update,
|
|
3892
|
+
opts,
|
|
3893
|
+
() => buildStructuredRequest(opts, GatewayDeploymentUpdateRequestSchema, [
|
|
3894
|
+
{ option: "authSettings", path: "auth_settings", parse: parseJsonOption },
|
|
3895
|
+
{ option: "deploymentConfig", path: "deployment_config", parse: parseJsonOption },
|
|
3896
|
+
{ option: "isDefault", path: "is_default", parse: parseBooleanOption },
|
|
3897
|
+
{ option: "name", path: "name" },
|
|
3898
|
+
{ option: "overrideExisting", path: "override_existing", parse: parseBooleanOption },
|
|
3899
|
+
{ option: "rotateAuth", path: "rotate_auth", parse: parseBooleanOption },
|
|
3900
|
+
{ option: "status", path: "status" },
|
|
3901
|
+
{ option: "type", path: "type" }
|
|
3902
|
+
]),
|
|
3903
|
+
(client, body) => client.deployments.update(id, body),
|
|
3904
|
+
void 0,
|
|
3905
|
+
{
|
|
3906
|
+
requiresDestination: (body) => body.rotate_auth === true,
|
|
3907
|
+
redactResponse: (result) => redactAIGatewaySecrets("deployments.update", result, "response")
|
|
3908
|
+
}
|
|
3909
|
+
)
|
|
3910
|
+
);
|
|
3911
|
+
}
|
|
3912
|
+
function registerIntegrations(root) {
|
|
3913
|
+
const group = showHelpOnEmpty(
|
|
3914
|
+
root.command("integrations").description("Manage organisation provider integrations")
|
|
3915
|
+
);
|
|
3916
|
+
const list = addReadOutput(
|
|
3917
|
+
group.command("list").description("List provider integrations (admin plane)")
|
|
3918
|
+
);
|
|
3919
|
+
list.action(
|
|
3920
|
+
(opts) => runList(list, opts, "integrations", (client) => client.integrations.list())
|
|
3921
|
+
);
|
|
3922
|
+
const get = addReadOutput(
|
|
3923
|
+
group.command("get <id>").description("Get a provider integration by UUID")
|
|
3924
|
+
);
|
|
3925
|
+
get.action((id, opts) => runDetail(get, opts, (client) => client.integrations.get(id)));
|
|
3926
|
+
const integrationFields = [
|
|
3927
|
+
{ option: "aiProviderId", path: "ai_provider_id" },
|
|
3928
|
+
{ option: "configurations", path: "configurations", parse: parseJsonOption },
|
|
3929
|
+
{ option: "description", path: "description" },
|
|
3930
|
+
{ option: "key", path: "key" },
|
|
3931
|
+
{ option: "name", path: "name" },
|
|
3932
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
3933
|
+
{ option: "secretMappings", path: "secret_mappings", parse: parseJsonOption },
|
|
3934
|
+
{ option: "slug", path: "slug" }
|
|
3935
|
+
];
|
|
3936
|
+
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");
|
|
3937
|
+
const integrationUpdateFields = integrationFields.filter(
|
|
3938
|
+
(field) => ["configurations", "description", "key", "name", "secretMappings"].includes(field.option)
|
|
3939
|
+
);
|
|
3940
|
+
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");
|
|
3941
|
+
const create = addWriteOutput(
|
|
3942
|
+
addStructuredInputOptions(
|
|
3943
|
+
addIntegrationFields(
|
|
3944
|
+
group.command("create").description("Create an integration from structured flags")
|
|
3945
|
+
)
|
|
3946
|
+
)
|
|
3947
|
+
);
|
|
3948
|
+
create.action(
|
|
3949
|
+
(opts) => runWrite(
|
|
3950
|
+
create,
|
|
3951
|
+
opts,
|
|
3952
|
+
() => buildStructuredRequest(opts, GatewayIntegrationCreateRequestSchema, integrationFields),
|
|
3953
|
+
(client, body) => client.integrations.create(body)
|
|
3954
|
+
)
|
|
3955
|
+
);
|
|
3956
|
+
const update = addWriteOutput(
|
|
3957
|
+
addStructuredInputOptions(
|
|
3958
|
+
addIntegrationUpdateFields(
|
|
3959
|
+
group.command("update <id>").description("Update an integration with structured flags")
|
|
3960
|
+
)
|
|
3961
|
+
)
|
|
3962
|
+
);
|
|
3963
|
+
update.action(
|
|
3964
|
+
(id, opts) => runWrite(
|
|
3965
|
+
update,
|
|
3966
|
+
opts,
|
|
3967
|
+
() => buildStructuredRequest(
|
|
3968
|
+
opts,
|
|
3969
|
+
GatewayIntegrationUpdateRequestSchema,
|
|
3970
|
+
integrationUpdateFields
|
|
3971
|
+
),
|
|
3972
|
+
(client, body) => client.integrations.update(id, body)
|
|
3973
|
+
)
|
|
3974
|
+
);
|
|
3975
|
+
const remove = addWriteOutput(
|
|
3976
|
+
group.command("delete <id>").description("Permanently delete this integration").requiredOption("--organisation-id <tsg>", "Numeric TSG id").option("--force", "Skip confirmation prompt")
|
|
3977
|
+
);
|
|
3978
|
+
remove.action(
|
|
3979
|
+
(id, opts) => runConfirmedWrite(
|
|
3980
|
+
remove,
|
|
3981
|
+
opts,
|
|
3982
|
+
`Permanently delete integration ${id}?`,
|
|
3983
|
+
(client) => client.integrations.delete(id, opts.organisationId)
|
|
3984
|
+
)
|
|
3985
|
+
);
|
|
3986
|
+
const models = showHelpOnEmpty(
|
|
3987
|
+
group.command("models").description("Inspect or replace model bindings")
|
|
3988
|
+
);
|
|
3989
|
+
const modelsList = addReadOutput(
|
|
3990
|
+
models.command("list <id>").description("List models for an integration")
|
|
3991
|
+
);
|
|
3992
|
+
modelsList.action(
|
|
3993
|
+
(id, opts) => runList(modelsList, opts, "integration models", (client) => client.integrations.getModels(id))
|
|
3994
|
+
);
|
|
3995
|
+
const modelsSet = addWriteOutput(
|
|
3996
|
+
addStructuredInputOptions(
|
|
3997
|
+
models.command("set <id>").description("Replace model bindings").option("--allow-all-models <boolean>", "Allow every model: true or false").option("--model <slug=enabled>", "Model binding (repeatable)", collectOption).option("--force", "Skip confirmation prompt")
|
|
3998
|
+
)
|
|
3999
|
+
);
|
|
4000
|
+
modelsSet.action(
|
|
4001
|
+
(id, opts) => runConfirmedWrite(
|
|
4002
|
+
modelsSet,
|
|
4003
|
+
opts,
|
|
4004
|
+
`Replace model bindings on ${id}?`,
|
|
4005
|
+
() => buildStructuredRequest(opts, GatewayIntegrationModelsBulkUpdateRequestSchema, [
|
|
4006
|
+
{ option: "allowAllModels", path: "allow_all_models", parse: parseBooleanOption },
|
|
4007
|
+
{ option: "model", path: "models", parse: parseModelBindingsOption }
|
|
4008
|
+
]),
|
|
4009
|
+
(client, body) => client.integrations.setModels(id, body)
|
|
4010
|
+
)
|
|
4011
|
+
);
|
|
4012
|
+
const workspaces = showHelpOnEmpty(
|
|
4013
|
+
group.command("workspaces").description("Inspect or replace workspace bindings")
|
|
4014
|
+
);
|
|
4015
|
+
const workspacesList = addReadOutput(
|
|
4016
|
+
workspaces.command("list <id>").description("List workspace bindings")
|
|
4017
|
+
);
|
|
4018
|
+
workspacesList.action(
|
|
4019
|
+
(id, opts) => runDetail(workspacesList, opts, (client) => client.integrations.getWorkspaces(id))
|
|
4020
|
+
);
|
|
4021
|
+
const workspacesSet = addWriteOutput(
|
|
4022
|
+
addStructuredInputOptions(
|
|
4023
|
+
workspaces.command("set <id>").description("Replace workspace bindings").option("--create-default-provider <boolean>", "Create defaults for new bindings").option("--default-provider-slug <slug>", "Default provider slug").option("--global-access <boolean>", "Enable or disable access to every workspace").option("--preserve-existing", "Preserve bindings not named by this command").option("--workspace-binding <id=enabled>", "Workspace binding (repeatable)", collectOption).option("--force", "Skip confirmation prompt")
|
|
4024
|
+
)
|
|
4025
|
+
);
|
|
4026
|
+
workspacesSet.action(
|
|
4027
|
+
(id, opts) => runConfirmedWrite(
|
|
4028
|
+
workspacesSet,
|
|
4029
|
+
opts,
|
|
4030
|
+
`Replace workspace bindings on ${id}?`,
|
|
4031
|
+
() => buildStructuredRequest(
|
|
4032
|
+
{
|
|
4033
|
+
...opts,
|
|
4034
|
+
overrideExistingWorkspaceAccess: !opts.preserveExisting
|
|
4035
|
+
},
|
|
4036
|
+
GatewayIntegrationWorkspacesBulkUpdateRequestSchema,
|
|
4037
|
+
[
|
|
4038
|
+
{
|
|
4039
|
+
option: "createDefaultProvider",
|
|
4040
|
+
path: "create_default_provider",
|
|
4041
|
+
parse: parseBooleanOption
|
|
4042
|
+
},
|
|
4043
|
+
{ option: "defaultProviderSlug", path: "default_provider_slug" },
|
|
4044
|
+
{
|
|
4045
|
+
option: "globalAccess",
|
|
4046
|
+
path: "global_workspace_access.enabled",
|
|
4047
|
+
parse: parseBooleanOption
|
|
4048
|
+
},
|
|
4049
|
+
{
|
|
4050
|
+
option: "overrideExistingWorkspaceAccess",
|
|
4051
|
+
path: "override_existing_workspace_access",
|
|
4052
|
+
parse: parseBooleanOption
|
|
4053
|
+
},
|
|
4054
|
+
{ option: "workspaceBinding", path: "workspaces", parse: parseBooleanBindingsOption }
|
|
4055
|
+
]
|
|
4056
|
+
),
|
|
4057
|
+
(client, body) => client.integrations.setWorkspaces(id, body)
|
|
4058
|
+
)
|
|
4059
|
+
);
|
|
4060
|
+
}
|
|
4061
|
+
function registerMcp(root) {
|
|
4062
|
+
const mcp = showHelpOnEmpty(
|
|
4063
|
+
root.command("mcp").description("Manage MCP integrations and servers")
|
|
4064
|
+
);
|
|
4065
|
+
const group = showHelpOnEmpty(
|
|
4066
|
+
mcp.command("integrations").description("Manage MCP server integrations")
|
|
4067
|
+
);
|
|
4068
|
+
const list = addReadOutput(
|
|
4069
|
+
group.command("list").description("List MCP integrations (admin plane)")
|
|
4070
|
+
);
|
|
4071
|
+
list.action(
|
|
4072
|
+
(opts) => runList(list, opts, "MCP integrations", (client) => client.mcpIntegrations.list())
|
|
4073
|
+
);
|
|
4074
|
+
const get = addReadOutput(
|
|
4075
|
+
group.command("get <id>").description("Get an MCP integration by UUID")
|
|
4076
|
+
);
|
|
4077
|
+
get.action((id, opts) => runDetail(get, opts, (client) => client.mcpIntegrations.get(id)));
|
|
4078
|
+
const mcpFields = [
|
|
4079
|
+
{ option: "authType", path: "auth_type" },
|
|
4080
|
+
{ option: "configurations", path: "configurations", parse: parseJsonOption },
|
|
4081
|
+
{ option: "description", path: "description" },
|
|
4082
|
+
{ option: "name", path: "name" },
|
|
4083
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
4084
|
+
{ option: "secretMappings", path: "secret_mappings", parse: parseJsonOption },
|
|
4085
|
+
{ option: "slug", path: "slug" },
|
|
4086
|
+
{ option: "transport", path: "transport" },
|
|
4087
|
+
{ option: "url", path: "url" }
|
|
4088
|
+
];
|
|
4089
|
+
const addMcpFields = (command) => command.option(
|
|
4090
|
+
"--auth-type <type>",
|
|
4091
|
+
`Authentication type (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_AUTH_TYPES)})`
|
|
4092
|
+
).option("--configurations <json>", "MCP authentication/configuration object").option("--description <text>", "Integration description").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").option(
|
|
4093
|
+
"--transport <transport>",
|
|
4094
|
+
`Transport (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_TRANSPORTS)})`
|
|
4095
|
+
).option("--url <url>", "MCP server URL");
|
|
4096
|
+
const mcpUpdateFields = mcpFields.filter(
|
|
4097
|
+
(field) => [
|
|
4098
|
+
"authType",
|
|
4099
|
+
"configurations",
|
|
4100
|
+
"description",
|
|
4101
|
+
"name",
|
|
4102
|
+
"secretMappings",
|
|
4103
|
+
"transport",
|
|
4104
|
+
"url"
|
|
4105
|
+
].includes(field.option)
|
|
4106
|
+
);
|
|
4107
|
+
const addMcpUpdateFields = (command) => command.option(
|
|
4108
|
+
"--auth-type <type>",
|
|
4109
|
+
`Authentication type (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_AUTH_TYPES)})`
|
|
4110
|
+
).option("--configurations <json>", "MCP authentication/configuration object").option("--description <text>", "Integration description").option("--name <name>", "Integration name").option("--secret-mappings <json>", "Secret reference mapping array").option(
|
|
4111
|
+
"--transport <transport>",
|
|
4112
|
+
`Transport (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_TRANSPORTS)})`
|
|
4113
|
+
).option("--url <url>", "MCP server URL");
|
|
4114
|
+
addCrudMutationNodes(
|
|
4115
|
+
group,
|
|
4116
|
+
"MCP integration",
|
|
4117
|
+
{
|
|
4118
|
+
createFields: mcpFields,
|
|
4119
|
+
createOptions: addMcpFields,
|
|
4120
|
+
createSchema: McpIntegrationCreateRequestSchema,
|
|
4121
|
+
updateFields: mcpUpdateFields,
|
|
4122
|
+
updateOptions: addMcpUpdateFields,
|
|
4123
|
+
updateSchema: McpIntegrationUpdateRequestSchema
|
|
4124
|
+
},
|
|
4125
|
+
{
|
|
4126
|
+
create: (client, body) => client.mcpIntegrations.create(body),
|
|
4127
|
+
delete: (client, id) => client.mcpIntegrations.delete(id),
|
|
4128
|
+
update: (client, id, body) => client.mcpIntegrations.update(id, body)
|
|
4129
|
+
}
|
|
4130
|
+
);
|
|
4131
|
+
const capabilities = showHelpOnEmpty(
|
|
4132
|
+
group.command("capabilities").description("Inspect or replace MCP capabilities")
|
|
4133
|
+
);
|
|
4134
|
+
const capabilitiesList = addReadOutput(
|
|
4135
|
+
capabilities.command("list <id>").description("List discovered MCP capabilities")
|
|
4136
|
+
);
|
|
4137
|
+
capabilitiesList.action(
|
|
4138
|
+
(id, opts) => runDetail(capabilitiesList, opts, (client) => client.mcpIntegrations.getCapabilities(id))
|
|
4139
|
+
);
|
|
4140
|
+
const capabilitiesSet = addWriteOutput(
|
|
4141
|
+
addStructuredInputOptions(
|
|
4142
|
+
capabilities.command("set <id>").description("Replace enabled MCP capabilities").option(
|
|
4143
|
+
"--capability <type:name=enabled>",
|
|
4144
|
+
`Capability binding (repeatable; type: ${knownValues(AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES)})`,
|
|
4145
|
+
collectOption
|
|
4146
|
+
).option("--force", "Skip confirmation prompt")
|
|
4147
|
+
)
|
|
4148
|
+
);
|
|
4149
|
+
capabilitiesSet.action(
|
|
4150
|
+
(id, opts) => runConfirmedWrite(
|
|
4151
|
+
capabilitiesSet,
|
|
4152
|
+
opts,
|
|
4153
|
+
`Replace enabled MCP capabilities on ${id}?`,
|
|
4154
|
+
() => buildStructuredRequest(opts, McpIntegrationCapabilitiesBulkUpdateRequestSchema, [
|
|
4155
|
+
{ option: "capability", path: "capabilities", parse: parseCapabilityBindingsOption }
|
|
4156
|
+
]),
|
|
4157
|
+
(client, body) => client.mcpIntegrations.setCapabilities(id, body)
|
|
4158
|
+
)
|
|
4159
|
+
);
|
|
4160
|
+
const metadata = addReadOutput(
|
|
4161
|
+
group.command("metadata <id>").description("Get discovered MCP metadata")
|
|
4162
|
+
);
|
|
4163
|
+
metadata.action(
|
|
4164
|
+
(id, opts) => runDetail(metadata, opts, (client) => client.mcpIntegrations.getMetadata(id))
|
|
4165
|
+
);
|
|
4166
|
+
const workspaces = showHelpOnEmpty(
|
|
4167
|
+
group.command("workspaces").description("Inspect or replace MCP workspace access")
|
|
4168
|
+
);
|
|
4169
|
+
const workspacesList = addReadOutput(
|
|
4170
|
+
workspaces.command("list <id>").description("Read workspace access from integration detail")
|
|
4171
|
+
);
|
|
4172
|
+
workspacesList.action(
|
|
4173
|
+
(id, opts) => runDetail(workspacesList, opts, (client) => client.mcpIntegrations.get(id))
|
|
4174
|
+
);
|
|
4175
|
+
const workspacesSet = addWriteOutput(
|
|
4176
|
+
addStructuredInputOptions(
|
|
4177
|
+
workspaces.command("set <id>").description("Replace MCP workspace access").option("--global-access <boolean>", "Enable or disable access to every workspace").option("--preserve-existing", "Preserve bindings not named by this command").option("--workspace-binding <id=enabled>", "Workspace binding (repeatable)", collectOption).option("--force", "Skip confirmation prompt")
|
|
4178
|
+
)
|
|
4179
|
+
);
|
|
4180
|
+
workspacesSet.action(
|
|
4181
|
+
(id, opts) => runConfirmedWrite(
|
|
4182
|
+
workspacesSet,
|
|
4183
|
+
opts,
|
|
4184
|
+
`Replace MCP workspace access on ${id}?`,
|
|
4185
|
+
() => buildStructuredRequest(
|
|
4186
|
+
{
|
|
4187
|
+
...opts,
|
|
4188
|
+
overrideExistingWorkspaceAccess: !opts.preserveExisting
|
|
4189
|
+
},
|
|
4190
|
+
McpIntegrationWorkspacesBulkUpdateRequestSchema,
|
|
4191
|
+
[
|
|
4192
|
+
{
|
|
4193
|
+
option: "globalAccess",
|
|
4194
|
+
path: "global_workspace_access.enabled",
|
|
4195
|
+
parse: parseBooleanOption
|
|
4196
|
+
},
|
|
4197
|
+
{
|
|
4198
|
+
option: "overrideExistingWorkspaceAccess",
|
|
4199
|
+
path: "override_existing_workspace_access",
|
|
4200
|
+
parse: parseBooleanOption
|
|
4201
|
+
},
|
|
4202
|
+
{ option: "workspaceBinding", path: "workspaces", parse: parseBooleanBindingsOption }
|
|
4203
|
+
]
|
|
4204
|
+
),
|
|
4205
|
+
(client, body) => client.mcpIntegrations.setWorkspaces(id, body)
|
|
4206
|
+
)
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
function registerOrganisations(root) {
|
|
4210
|
+
const group = showHelpOnEmpty(
|
|
4211
|
+
root.command("organisations").description("Manage organisation and authentication settings")
|
|
4212
|
+
);
|
|
4213
|
+
const self = showHelpOnEmpty(
|
|
4214
|
+
group.command("self").description("Manage the current organisation")
|
|
4215
|
+
);
|
|
4216
|
+
const selfGet = addReadOutput(self.command("get").description("Get the current organisation"));
|
|
4217
|
+
selfGet.action((opts) => runDetail(selfGet, opts, (client) => client.organisations.getSelf()));
|
|
4218
|
+
const selfUpdate = addWriteOutput(
|
|
4219
|
+
addStructuredInputOptions(
|
|
4220
|
+
self.command("update").description("Update the current organisation with structured flags").option("--name <name>", "Organisation name")
|
|
4221
|
+
)
|
|
4222
|
+
);
|
|
4223
|
+
selfUpdate.action(
|
|
4224
|
+
(opts) => runWrite(
|
|
4225
|
+
selfUpdate,
|
|
4226
|
+
opts,
|
|
4227
|
+
() => buildStructuredRequest(opts, GatewayOrganisationUpdateRequestSchema, [
|
|
4228
|
+
{ option: "name", path: "name" }
|
|
4229
|
+
]),
|
|
4230
|
+
(client, body) => client.organisations.updateSelf(body)
|
|
4231
|
+
)
|
|
4232
|
+
);
|
|
4233
|
+
const auth = showHelpOnEmpty(
|
|
4234
|
+
group.command("auth-settings").description("Manage organisation authentication settings")
|
|
4235
|
+
);
|
|
4236
|
+
const authGet = addReadOutput(
|
|
4237
|
+
auth.command("get").description("Get authentication settings").requiredOption("--tsg-id <tsg>", "Numeric TSG id").option("--reveal-sensitive", "Show SCIM and authentication secrets")
|
|
4238
|
+
);
|
|
4239
|
+
authGet.action(
|
|
4240
|
+
(opts) => runDetail(authGet, opts, async (client) => {
|
|
4241
|
+
const result = await client.organisations.getAuthSettings(opts.tsgId);
|
|
4242
|
+
return opts.revealSensitive ? result : redactAIGatewaySecrets("organisations.getAuthSettings", result, "response");
|
|
4243
|
+
})
|
|
4244
|
+
);
|
|
4245
|
+
const authUpdate = addWriteOutput(
|
|
4246
|
+
addStructuredInputOptions(
|
|
4247
|
+
auth.command("update").description("Update authentication settings with structured flags").requiredOption("--tsg-id <tsg>", "Numeric TSG id").option("--auth-settings <json>", "Authentication settings object").option("--domains <domains>", "Comma-separated allowed domains").option("--scim-token <token>", "SCIM token")
|
|
4248
|
+
)
|
|
4249
|
+
);
|
|
4250
|
+
authUpdate.action(
|
|
4251
|
+
(opts) => runWrite(
|
|
4252
|
+
authUpdate,
|
|
4253
|
+
opts,
|
|
4254
|
+
() => buildStructuredRequest(opts, GatewayOrganisationAuthSettingsUpdateRequestSchema, [
|
|
4255
|
+
{ option: "authSettings", path: "auth_settings", parse: parseJsonOption },
|
|
4256
|
+
{ option: "domains", path: "domains", parse: parseCsvOption },
|
|
4257
|
+
{ option: "scimToken", path: "scim_token" }
|
|
4258
|
+
]),
|
|
4259
|
+
(client, body) => client.organisations.updateAuthSettings(opts.tsgId, body)
|
|
4260
|
+
)
|
|
4261
|
+
);
|
|
4262
|
+
}
|
|
4263
|
+
function registerPlugins(root) {
|
|
4264
|
+
const group = showHelpOnEmpty(root.command("plugins").description("Manage gateway plugins"));
|
|
4265
|
+
const list = addReadOutput(group.command("list").description("List installed gateway plugins"));
|
|
4266
|
+
list.action((opts) => runList(list, opts, "plugins", (client) => client.plugins.list()));
|
|
4267
|
+
const create = addWriteOutput(
|
|
4268
|
+
addStructuredInputOptions(
|
|
4269
|
+
group.command("create").description("Install a gateway plugin from structured flags").option(
|
|
4270
|
+
"--credential <key=value>",
|
|
4271
|
+
"Plugin credential (repeatable; treated as sensitive)",
|
|
4272
|
+
collectOption
|
|
4273
|
+
).option("--integration-id <uuid>", "Integration UUID").option("--organisation-id <tsg>", "Numeric TSG id")
|
|
4274
|
+
)
|
|
4275
|
+
);
|
|
4276
|
+
create.action(
|
|
4277
|
+
(opts) => runWrite(
|
|
4278
|
+
create,
|
|
4279
|
+
opts,
|
|
4280
|
+
() => buildStructuredRequest(opts, GatewayPluginCreateRequestSchema, [
|
|
4281
|
+
{ option: "credential", path: "credentials", parse: parseStringMapOption },
|
|
4282
|
+
{ option: "integrationId", path: "integration_id" },
|
|
4283
|
+
{ option: "organisationId", path: "organisation_id" }
|
|
4284
|
+
]),
|
|
4285
|
+
(client, body) => client.plugins.create(body)
|
|
4286
|
+
)
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
4289
|
+
function registerAiGatewayInventory(root) {
|
|
4290
|
+
registerApiKeys(root);
|
|
4291
|
+
registerAuditLogs(root);
|
|
4292
|
+
registerConfigs(root);
|
|
4293
|
+
registerDeployments(root);
|
|
4294
|
+
const guardrails = registerScopedReads(
|
|
4295
|
+
root,
|
|
4296
|
+
"guardrails",
|
|
4297
|
+
"Manage workspace guardrails",
|
|
4298
|
+
(client) => client.guardrails
|
|
4299
|
+
);
|
|
4300
|
+
const guardrailFields = [
|
|
4301
|
+
{ option: "actions", path: "actions", parse: parseJsonOption },
|
|
4302
|
+
{ option: "checks", path: "checks", parse: parseJsonOption },
|
|
4303
|
+
{ option: "name", path: "name" },
|
|
4304
|
+
{ option: "workspace", path: "workspace_id" }
|
|
4305
|
+
];
|
|
4306
|
+
const addGuardrailFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name").option("--workspace <uuid>", "Workspace UUID");
|
|
4307
|
+
const guardrailUpdateFields = guardrailFields.filter((field) => field.option !== "workspace");
|
|
4308
|
+
const addGuardrailUpdateFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name");
|
|
4309
|
+
addCrudMutationNodes(
|
|
4310
|
+
guardrails,
|
|
4311
|
+
"guardrail",
|
|
4312
|
+
{
|
|
4313
|
+
createFields: guardrailFields,
|
|
4314
|
+
createOptions: addGuardrailFields,
|
|
4315
|
+
createSchema: GatewayGuardrailCreateRequestSchema,
|
|
4316
|
+
updateFields: guardrailUpdateFields,
|
|
4317
|
+
updateOptions: addGuardrailUpdateFields,
|
|
4318
|
+
updateSchema: GatewayGuardrailUpdateRequestSchema
|
|
4319
|
+
},
|
|
4320
|
+
{
|
|
4321
|
+
create: (client, body) => client.guardrails.create(body),
|
|
4322
|
+
delete: (client, id) => client.guardrails.delete(id),
|
|
4323
|
+
update: (client, id, body) => client.guardrails.update(id, body)
|
|
4324
|
+
}
|
|
4325
|
+
);
|
|
4326
|
+
registerIntegrations(root);
|
|
4327
|
+
registerMcp(root);
|
|
4328
|
+
registerOrganisations(root);
|
|
4329
|
+
registerPlugins(root);
|
|
4330
|
+
const providers = registerScopedReads(
|
|
4331
|
+
root,
|
|
4332
|
+
"providers",
|
|
4333
|
+
"Manage workspace provider bindings",
|
|
4334
|
+
(client) => client.providers,
|
|
4335
|
+
{ sensitiveDetail: true, sensitiveOperation: "providers.get" }
|
|
4336
|
+
);
|
|
4337
|
+
const providerFields = [
|
|
4338
|
+
{ option: "aiProviderId", path: "ai_provider_id" },
|
|
4339
|
+
{ option: "expiresAt", path: "expires_at" },
|
|
4340
|
+
{ option: "integrationId", path: "integration_id" },
|
|
4341
|
+
{ option: "name", path: "name" },
|
|
4342
|
+
{ option: "note", path: "note" },
|
|
4343
|
+
{ option: "rateLimit", path: "rate_limits", parse: parseJsonOption },
|
|
4344
|
+
{ option: "resetUsage", path: "reset_usage", parse: parseBooleanOption },
|
|
4345
|
+
{ option: "slug", path: "slug" },
|
|
4346
|
+
{ option: "usageLimit", path: "usage_limits", parse: parseJsonOption },
|
|
4347
|
+
{ option: "workspace", path: "workspace_id" }
|
|
4348
|
+
];
|
|
4349
|
+
const addProviderFields = (command) => command.option("--ai-provider-id <uuid>", "Provider catalog UUID").option("--expires-at <iso>", "Expiration as ISO-8601").option("--integration-id <uuid>", "Organisation integration UUID").option("--name <name>", "Provider binding name").option("--note <text>", "Operator note").option("--rate-limit <json>", "Rate-limit object").option("--reset-usage <boolean>", "Reset accumulated usage: true or false").option("--slug <slug>", "Provider binding slug").option("--usage-limit <json>", "Usage-limit object").option("--workspace <uuid>", "Workspace UUID");
|
|
4350
|
+
const providerUpdateFields = providerFields.filter(
|
|
4351
|
+
(field) => ["expiresAt", "name", "note", "rateLimit", "resetUsage", "usageLimit"].includes(field.option)
|
|
4352
|
+
);
|
|
4353
|
+
const addProviderUpdateFields = (command) => command.option("--expires-at <iso>", "Expiration as ISO-8601").option("--name <name>", "Provider binding name").option("--note <text>", "Operator note").option("--rate-limit <json>", "Rate-limit object").option("--reset-usage <boolean>", "Reset accumulated usage: true or false").option("--usage-limit <json>", "Usage-limit object");
|
|
4354
|
+
addCrudMutationNodes(
|
|
4355
|
+
providers,
|
|
4356
|
+
"provider",
|
|
4357
|
+
{
|
|
4358
|
+
createFields: providerFields,
|
|
4359
|
+
createOptions: addProviderFields,
|
|
4360
|
+
createSchema: GatewayProviderCreateRequestSchema,
|
|
4361
|
+
updateFields: providerUpdateFields,
|
|
4362
|
+
updateOptions: addProviderUpdateFields,
|
|
4363
|
+
updateSchema: GatewayProviderUpdateRequestSchema
|
|
4364
|
+
},
|
|
4365
|
+
{
|
|
4366
|
+
create: (client, body) => client.providers.create(body),
|
|
4367
|
+
delete: (client, id) => client.providers.delete(id),
|
|
4368
|
+
update: (client, id, body) => client.providers.update(id, body)
|
|
4369
|
+
}
|
|
4370
|
+
);
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
// src/cli/commands/aigateway/telemetry.ts
|
|
4374
|
+
function addWindowOptions(command) {
|
|
4375
|
+
return addReadOutput(
|
|
4376
|
+
command.requiredOption("--workspace <slug>", "Workspace slug").option("--days <n>", "Rolling window in days", "7").option("--end <iso>", "Window end as ISO-8601").option("--start <iso>", "Window start as ISO-8601")
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
function windowFrom(opts) {
|
|
4380
|
+
const window = { workspaceSlug: opts.workspace };
|
|
4381
|
+
if (opts.start) window.start = parseNamedDate2(opts.start, "--start");
|
|
4382
|
+
else window.days = parsePositiveInteger2(opts.days ?? "7", "--days");
|
|
4383
|
+
if (opts.end) window.end = parseNamedDate2(opts.end, "--end");
|
|
4384
|
+
return window;
|
|
4385
|
+
}
|
|
4386
|
+
function parsePositiveInteger2(value, flag) {
|
|
4387
|
+
try {
|
|
4388
|
+
const parsed = parseIntegerOption(value);
|
|
4389
|
+
if (parsed <= 0) throw new CliUsageError("Expected a positive integer");
|
|
4390
|
+
return parsed;
|
|
4391
|
+
} catch (error) {
|
|
4392
|
+
if (error instanceof CliUsageError)
|
|
4393
|
+
throw new CliUsageError(`Invalid ${flag}: ${error.message}`);
|
|
4394
|
+
throw error;
|
|
2947
4395
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
4396
|
+
}
|
|
4397
|
+
function parseNamedDate2(value, flag) {
|
|
4398
|
+
try {
|
|
4399
|
+
return parseDateOption(value);
|
|
4400
|
+
} catch (error) {
|
|
4401
|
+
if (error instanceof CliUsageError)
|
|
4402
|
+
throw new CliUsageError(`Invalid ${flag}: ${error.message}`);
|
|
4403
|
+
throw error;
|
|
2953
4404
|
}
|
|
2954
4405
|
}
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
4406
|
+
function registerMetric(telemetry, name, description, method) {
|
|
4407
|
+
const command = addWindowOptions(telemetry.command(name).description(description));
|
|
4408
|
+
command.action(
|
|
4409
|
+
(opts) => runDetail(command, opts, (client) => client.telemetry[method](windowFrom(opts)))
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4412
|
+
function registerAiGatewayTelemetryReads(telemetry) {
|
|
4413
|
+
const cache = showHelpOnEmpty(telemetry.command("cache").description("Inspect cache telemetry"));
|
|
4414
|
+
const cacheSummary = addWindowOptions(cache.command("summary").description("Get cache totals"));
|
|
4415
|
+
cacheSummary.action(
|
|
4416
|
+
(opts) => runDetail(cacheSummary, opts, (client) => client.telemetry.cacheSummary(windowFrom(opts)))
|
|
4417
|
+
);
|
|
4418
|
+
const cacheTrend = addWindowOptions(cache.command("trend").description("Get cache-hit trend"));
|
|
4419
|
+
cacheTrend.action(
|
|
4420
|
+
(opts) => runDetail(cacheTrend, opts, (client) => client.telemetry.cacheHitTrend(windowFrom(opts)))
|
|
4421
|
+
);
|
|
4422
|
+
registerMetric(telemetry, "error-trends", "Get error trends", "errorTrends");
|
|
4423
|
+
registerMetric(telemetry, "errors", "Get error count", "errors");
|
|
4424
|
+
const feedback = showHelpOnEmpty(
|
|
4425
|
+
telemetry.command("feedback").description("Inspect model feedback telemetry")
|
|
4426
|
+
);
|
|
4427
|
+
const feedbackMethods = {
|
|
4428
|
+
distribution: "feedbackScoreDistribution",
|
|
4429
|
+
models: "feedbackModels",
|
|
4430
|
+
trend: "feedbackTrend",
|
|
4431
|
+
weighted: "feedbackWeighted"
|
|
4432
|
+
};
|
|
4433
|
+
for (const [name, method] of Object.entries(feedbackMethods)) {
|
|
4434
|
+
const command = addWindowOptions(feedback.command(name).description(`Get feedback ${name}`));
|
|
4435
|
+
command.action(
|
|
4436
|
+
(opts) => runDetail(command, opts, (client) => client.telemetry[method](windowFrom(opts)))
|
|
4437
|
+
);
|
|
4438
|
+
}
|
|
4439
|
+
const groupBy = addWindowOptions(
|
|
4440
|
+
telemetry.command("group-by <dimension>").description("Aggregate telemetry by provider, model, status, or another SDK dimension").option("--columns <names>", "Comma-separated aggregate columns")
|
|
4441
|
+
);
|
|
4442
|
+
groupBy.action(
|
|
4443
|
+
(dimension, opts) => runDetail(
|
|
4444
|
+
groupBy,
|
|
4445
|
+
opts,
|
|
4446
|
+
(client) => client.telemetry.groupBy(dimension, {
|
|
4447
|
+
...windowFrom(opts),
|
|
4448
|
+
...opts.columns ? { columns: opts.columns.split(",").map((value) => value.trim()) } : {}
|
|
4449
|
+
})
|
|
4450
|
+
)
|
|
4451
|
+
);
|
|
4452
|
+
registerMetric(telemetry, "latency", "Get latency telemetry", "latency");
|
|
4453
|
+
const logs = showHelpOnEmpty(telemetry.command("logs").description("Inspect request logs"));
|
|
4454
|
+
const logsList = addWindowOptions(
|
|
4455
|
+
logs.command("list").description("List request logs").option("--page-size <n>", "Rows per response", "50").option("--status-code <code>", "Filter by HTTP status").option("--trace-id <id>", "Return one trace id")
|
|
4456
|
+
);
|
|
4457
|
+
logsList.action(
|
|
4458
|
+
(opts) => runDetail(
|
|
4459
|
+
logsList,
|
|
4460
|
+
opts,
|
|
4461
|
+
(client) => client.telemetry.logs({
|
|
4462
|
+
...windowFrom(opts),
|
|
4463
|
+
pageSize: parsePositiveInteger2(opts.pageSize ?? "50", "--page-size"),
|
|
4464
|
+
...opts.statusCode ? { statusCode: parsePositiveInteger2(opts.statusCode, "--status-code") } : {},
|
|
4465
|
+
...opts.traceId ? { traceId: opts.traceId } : {}
|
|
4466
|
+
})
|
|
4467
|
+
)
|
|
4468
|
+
);
|
|
4469
|
+
registerMetric(telemetry, "requests", "Get request count", "requests");
|
|
4470
|
+
registerMetric(telemetry, "rescued-retries", "Get rescued retry telemetry", "rescuedRetries");
|
|
4471
|
+
registerMetric(telemetry, "tokens", "Get token usage", "tokens");
|
|
4472
|
+
registerMetric(telemetry, "user-trends", "Get user trends", "userTrends");
|
|
4473
|
+
registerMetric(telemetry, "users", "Get unique-user count", "users");
|
|
2962
4474
|
}
|
|
2963
4475
|
|
|
2964
4476
|
// src/cli/commands/aigateway.ts
|
|
@@ -3001,18 +4513,18 @@ function buildWorkspaceWriteRequest(opts) {
|
|
|
3001
4513
|
const defaults = parseJsonFlag(opts.defaults, "--defaults");
|
|
3002
4514
|
const metadata = parseJsonFlag(opts.metadata, "--metadata");
|
|
3003
4515
|
if (defaults !== void 0 || metadata !== void 0) {
|
|
3004
|
-
out.defaults = {
|
|
4516
|
+
out.defaults = GatewayDefaultsInputSchema.parse({
|
|
3005
4517
|
...typeof defaults === "object" && defaults !== null ? defaults : {},
|
|
3006
4518
|
...metadata !== void 0 ? { metadata } : {}
|
|
3007
|
-
};
|
|
4519
|
+
});
|
|
3008
4520
|
}
|
|
3009
4521
|
if (opts.users !== void 0) {
|
|
3010
4522
|
out.users = opts.users.split(",").map((u) => u.trim()).filter(Boolean);
|
|
3011
4523
|
}
|
|
3012
4524
|
const usage = parseJsonFlag(opts.usageLimits, "--usage-limits");
|
|
3013
|
-
if (usage !== void 0) out.usageLimits = usage;
|
|
4525
|
+
if (usage !== void 0) out.usageLimits = GatewayUsageLimitInputSchema.array().parse(usage);
|
|
3014
4526
|
const rate = parseJsonFlag(opts.rateLimits, "--rate-limits");
|
|
3015
|
-
if (rate !== void 0) out.rateLimits = rate;
|
|
4527
|
+
if (rate !== void 0) out.rateLimits = GatewayRateLimitInputSchema.array().parse(rate);
|
|
3016
4528
|
return out;
|
|
3017
4529
|
}
|
|
3018
4530
|
function scopeNameLooksUnrelated(name, scopeName) {
|
|
@@ -3021,15 +4533,16 @@ function scopeNameLooksUnrelated(name, scopeName) {
|
|
|
3021
4533
|
return !scopeName.toLowerCase().replace(/[^a-z0-9]/g, "").includes(nameToken);
|
|
3022
4534
|
}
|
|
3023
4535
|
function registerAiGatewayCommand(program) {
|
|
3024
|
-
const aigateway = program.command("aigateway").description("AI Gateway
|
|
3025
|
-
|
|
4536
|
+
const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
|
|
4537
|
+
registerAiGatewayInventory(aigateway);
|
|
4538
|
+
const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
|
|
3026
4539
|
const workspaceList = workspace.command("list").description("List workspaces (default: active workspaces you are scoped to)").option("--plane <plane>", "Plane to read from: data (scoped) or admin (whole tenant)").option("--status <status>", "Filter by lifecycle state: active or archived").option("--all", "Merge active + archived admin-plane reads (whole tenant, both states)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
|
|
3027
4540
|
"after",
|
|
3028
4541
|
examples(
|
|
3029
|
-
"airs aigateway
|
|
3030
|
-
"airs aigateway
|
|
3031
|
-
"airs aigateway
|
|
3032
|
-
"airs aigateway
|
|
4542
|
+
"airs aigateway workspaces list",
|
|
4543
|
+
"airs aigateway workspaces list --plane admin",
|
|
4544
|
+
"airs aigateway workspaces list --plane admin --status archived",
|
|
4545
|
+
"airs aigateway workspaces list --all --output json"
|
|
3033
4546
|
)
|
|
3034
4547
|
).action(async (opts) => {
|
|
3035
4548
|
try {
|
|
@@ -3057,8 +4570,8 @@ function registerAiGatewayCommand(program) {
|
|
|
3057
4570
|
const workspaceGet = workspace.command("get <ref>").description("Get one workspace by UUID or slug (includes settings blocks)").option("--plane <plane>", "Plane to read from: data (scoped) or admin (whole tenant)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
|
|
3058
4571
|
"after",
|
|
3059
4572
|
examples(
|
|
3060
|
-
"airs aigateway
|
|
3061
|
-
"airs aigateway
|
|
4573
|
+
"airs aigateway workspaces get ws-main-a-349e0e",
|
|
4574
|
+
"airs aigateway workspaces get 16f7e90d-382a-4e78-b577-1b01eb5f8297 --plane admin --output json"
|
|
3062
4575
|
)
|
|
3063
4576
|
).action(async (ref, opts) => {
|
|
3064
4577
|
try {
|
|
@@ -3081,8 +4594,8 @@ function registerAiGatewayCommand(program) {
|
|
|
3081
4594
|
).option("--description <text>", "Workspace description").option("--icon <icon>", "Workspace icon").option("--metadata <json>", "Sugar for defaults.metadata (flat string map)").option("--defaults <json>", "Workspace defaults object").option("--users <ids>", "Comma-separated user ids to seed the workspace with").option("--usage-limits <json>", "Usage-limit policies \u2014 a JSON ARRAY of policy objects").option("--rate-limits <json>", "Rate-limit policies \u2014 a JSON ARRAY of policy objects").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
|
|
3082
4595
|
"after",
|
|
3083
4596
|
examples(
|
|
3084
|
-
"airs aigateway
|
|
3085
|
-
`airs aigateway
|
|
4597
|
+
"airs aigateway workspaces create --name Production --scope-name ws_production_bx7qw0",
|
|
4598
|
+
`airs aigateway workspaces create --name Production --scope-name ws_production_bx7qw0 --metadata '{"env":"production"}' --rate-limits '[{"type":"requests","unit":"rpm","value":100}]'`
|
|
3086
4599
|
)
|
|
3087
4600
|
).action(async (opts) => {
|
|
3088
4601
|
try {
|
|
@@ -3108,7 +4621,7 @@ function registerAiGatewayCommand(program) {
|
|
|
3108
4621
|
workspace.command("update <ref>").description("Update a workspace (admin plane, partial patch)").option("--name <name>", "New display name").option("--description <text>", "New description").option("--icon <icon>", "New icon").option("--metadata <json>", "Sugar for defaults.metadata (flat string map)").option("--defaults <json>", "Workspace defaults object").option("--usage-limits <json>", "Usage-limit policies \u2014 a JSON ARRAY of policy objects").option("--rate-limits <json>", "Rate-limit policies \u2014 a JSON ARRAY of policy objects").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
|
|
3109
4622
|
"after",
|
|
3110
4623
|
examples(
|
|
3111
|
-
`airs aigateway
|
|
4624
|
+
`airs aigateway workspaces update ws-produc-985697 --description 'Production workloads, us-east'`
|
|
3112
4625
|
)
|
|
3113
4626
|
).action(async (ref, opts) => {
|
|
3114
4627
|
try {
|
|
@@ -3128,9 +4641,14 @@ function registerAiGatewayCommand(program) {
|
|
|
3128
4641
|
failWithGrantHint(err);
|
|
3129
4642
|
}
|
|
3130
4643
|
});
|
|
3131
|
-
|
|
4644
|
+
const archiveWorkspace = async (ref, opts, deprecated) => {
|
|
3132
4645
|
try {
|
|
3133
4646
|
renderAiGatewayHeader();
|
|
4647
|
+
if (deprecated) {
|
|
4648
|
+
ui.warn(
|
|
4649
|
+
"`aigateway workspace delete` is deprecated because this operation archives; use `aigateway workspaces archive`."
|
|
4650
|
+
);
|
|
4651
|
+
}
|
|
3134
4652
|
await confirmOrAbort(
|
|
3135
4653
|
`Archive workspace ${ref}? (soft delete \u2014 the row remains under --status archived)`,
|
|
3136
4654
|
Boolean(opts.force),
|
|
@@ -3140,13 +4658,16 @@ function registerAiGatewayCommand(program) {
|
|
|
3140
4658
|
await service.deleteWorkspace(ref);
|
|
3141
4659
|
ui.success(`Workspace archived: ${ref}`);
|
|
3142
4660
|
ui.status(
|
|
3143
|
-
"This is a soft delete \u2014 the workspace remains visible via `
|
|
4661
|
+
"This is a soft delete \u2014 the workspace remains visible via `workspaces list --plane admin --status archived`. A `get` on it now answers 404; that is expected."
|
|
3144
4662
|
);
|
|
3145
4663
|
} catch (err) {
|
|
3146
4664
|
failWithGrantHint(err);
|
|
3147
4665
|
}
|
|
3148
|
-
}
|
|
3149
|
-
|
|
4666
|
+
};
|
|
4667
|
+
workspace.command("archive <ref>").description("Archive a workspace (soft delete \u2014 there is no hard delete)").option("--force", "Skip confirmation prompt").addHelpText("after", examples("airs aigateway workspaces archive ws-produc-985697 --force")).action((ref, opts) => archiveWorkspace(ref, opts, false));
|
|
4668
|
+
workspace.command("delete <ref>", { hidden: true }).description("Deprecated compatibility command for archive").option("--force", "Skip confirmation prompt").action((ref, opts) => archiveWorkspace(ref, opts, true));
|
|
4669
|
+
const telemetry = aigateway.command("telemetry").description("AI Gateway runtime telemetry (data plane)").action(() => telemetry.outputHelp());
|
|
4670
|
+
registerAiGatewayTelemetryReads(telemetry);
|
|
3150
4671
|
const cost = telemetry.command("cost").description(
|
|
3151
4672
|
"Total and per-day spend for a workspace (API reports cents; pretty output shows dollars)"
|
|
3152
4673
|
).requiredOption("--workspace <slug>", "Workspace slug (not UUID), e.g. ws-main-a-349e0e").option("--days <n>", "Rolling window in days, counted back from now", "7").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
|
|
@@ -3292,8 +4813,8 @@ function registerCompletionCommand(program) {
|
|
|
3292
4813
|
}
|
|
3293
4814
|
|
|
3294
4815
|
// src/cli/commands/config.ts
|
|
3295
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3296
|
-
import { dirname } from "path";
|
|
4816
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
4817
|
+
import { dirname as dirname2 } from "path";
|
|
3297
4818
|
var CONFIG_KEYS = Object.keys(ConfigSchema.shape);
|
|
3298
4819
|
var SECRET_PATTERN = /key|secret|token|password/i;
|
|
3299
4820
|
function isKnownKey(key) {
|
|
@@ -3316,7 +4837,7 @@ function buildConfigRows(inspected, reveal) {
|
|
|
3316
4837
|
async function readConfigFileStrict(filePath) {
|
|
3317
4838
|
let raw;
|
|
3318
4839
|
try {
|
|
3319
|
-
raw = await
|
|
4840
|
+
raw = await readFile2(filePath, "utf-8");
|
|
3320
4841
|
} catch {
|
|
3321
4842
|
return { ok: true, data: {} };
|
|
3322
4843
|
}
|
|
@@ -3341,7 +4862,7 @@ async function setConfigValue(filePath, key, value) {
|
|
|
3341
4862
|
}
|
|
3342
4863
|
const coerced = result.data[key];
|
|
3343
4864
|
const next = { ...read.data, [key]: coerced };
|
|
3344
|
-
await mkdir(
|
|
4865
|
+
await mkdir(dirname2(filePath), { recursive: true });
|
|
3345
4866
|
await writeFile(filePath, `${JSON.stringify(next, null, 2)}
|
|
3346
4867
|
`, "utf-8");
|
|
3347
4868
|
return { ok: true, value: coerced };
|
|
@@ -3452,7 +4973,7 @@ function registerConfigCommand(program) {
|
|
|
3452
4973
|
|
|
3453
4974
|
// src/cli/commands/doctor.ts
|
|
3454
4975
|
import { randomUUID } from "crypto";
|
|
3455
|
-
import { readFile as
|
|
4976
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
3456
4977
|
import { init, Scanner } from "@cdot65/prisma-airs-sdk";
|
|
3457
4978
|
var DOCTOR_TIMEOUT_MS = 5e3;
|
|
3458
4979
|
var MIN_NODE_MAJOR = 20;
|
|
@@ -3472,7 +4993,7 @@ async function checkConfigFile(filePath) {
|
|
|
3472
4993
|
const name = "Config file";
|
|
3473
4994
|
let raw;
|
|
3474
4995
|
try {
|
|
3475
|
-
raw = await
|
|
4996
|
+
raw = await readFile3(filePath, "utf-8");
|
|
3476
4997
|
} catch {
|
|
3477
4998
|
return {
|
|
3478
4999
|
name,
|
|
@@ -5634,8 +7155,8 @@ function registerRedteamCommand(program) {
|
|
|
5634
7155
|
// src/cli/commands/runtime.ts
|
|
5635
7156
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5636
7157
|
import * as fs5 from "fs";
|
|
5637
|
-
import { readFile as
|
|
5638
|
-
import { basename as basename3, dirname as
|
|
7158
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
7159
|
+
import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
|
|
5639
7160
|
import chalk11 from "chalk";
|
|
5640
7161
|
|
|
5641
7162
|
// src/cli/builders/profile-builder.ts
|
|
@@ -6313,7 +7834,7 @@ var topicsView = {
|
|
|
6313
7834
|
};
|
|
6314
7835
|
|
|
6315
7836
|
// src/cli/commands/dlp/dictionaries.ts
|
|
6316
|
-
import { readFile as
|
|
7837
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
6317
7838
|
import { basename as basename2 } from "path";
|
|
6318
7839
|
|
|
6319
7840
|
// src/airs/dlp/dictionaries.ts
|
|
@@ -6352,7 +7873,7 @@ var SdkDictionariesService = class {
|
|
|
6352
7873
|
};
|
|
6353
7874
|
|
|
6354
7875
|
// src/cli/commands/dlp/patch.ts
|
|
6355
|
-
import { readFile as
|
|
7876
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
6356
7877
|
function buildMergePatch(opts) {
|
|
6357
7878
|
const out = {};
|
|
6358
7879
|
for (const entry of opts.set ?? []) {
|
|
@@ -6394,7 +7915,7 @@ function coerceValue(raw) {
|
|
|
6394
7915
|
async function parseBody(opts) {
|
|
6395
7916
|
let raw;
|
|
6396
7917
|
if (opts.bodyFile) {
|
|
6397
|
-
raw = await
|
|
7918
|
+
raw = await readFile6(opts.bodyFile, "utf-8");
|
|
6398
7919
|
} else if (opts.body === "-") {
|
|
6399
7920
|
const chunks = [];
|
|
6400
7921
|
for await (const chunk of opts.stdin ?? process.stdin) {
|
|
@@ -6415,7 +7936,7 @@ async function parseBody(opts) {
|
|
|
6415
7936
|
// src/cli/commands/dlp/dictionaries.ts
|
|
6416
7937
|
async function buildMetadata(opts) {
|
|
6417
7938
|
if (opts.metadataFile) {
|
|
6418
|
-
return JSON.parse(await
|
|
7939
|
+
return JSON.parse(await readFile7(opts.metadataFile, "utf-8"));
|
|
6419
7940
|
}
|
|
6420
7941
|
if (!opts.name || !opts.category || !opts.region || !opts.file) {
|
|
6421
7942
|
throw new Error("--name, --category, --region, and --file are required");
|
|
@@ -6460,7 +7981,7 @@ function register(dlp) {
|
|
|
6460
7981
|
try {
|
|
6461
7982
|
const metadata = await buildMetadata(opts);
|
|
6462
7983
|
if (!opts.file) throw new Error("--file is required (multipart upload)");
|
|
6463
|
-
const file = await
|
|
7984
|
+
const file = await readFile7(opts.file);
|
|
6464
7985
|
const r = await new SdkDictionariesService().create({
|
|
6465
7986
|
metadata,
|
|
6466
7987
|
file,
|
|
@@ -6488,7 +8009,7 @@ function register(dlp) {
|
|
|
6488
8009
|
try {
|
|
6489
8010
|
const metadata = await buildMetadata(opts);
|
|
6490
8011
|
if (!opts.file) throw new Error("--file is required (multipart upload)");
|
|
6491
|
-
const file = await
|
|
8012
|
+
const file = await readFile7(opts.file);
|
|
6492
8013
|
const r = await new SdkDictionariesService().replace(id, {
|
|
6493
8014
|
metadata,
|
|
6494
8015
|
file,
|
|
@@ -7308,7 +8829,7 @@ function registerCreateCommand(parent) {
|
|
|
7308
8829
|
}
|
|
7309
8830
|
|
|
7310
8831
|
// src/cli/commands/topics-eval.ts
|
|
7311
|
-
import { readFile as
|
|
8832
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
7312
8833
|
|
|
7313
8834
|
// src/core/prompt-loader.ts
|
|
7314
8835
|
function parseCsvLine(line) {
|
|
@@ -7451,7 +8972,7 @@ function registerEvalCommand(parent) {
|
|
|
7451
8972
|
resolveDeprecatedAliases(cmd, opts);
|
|
7452
8973
|
try {
|
|
7453
8974
|
const config = await loadConfig();
|
|
7454
|
-
const csvContent = await
|
|
8975
|
+
const csvContent = await readFile8(opts.prompts, "utf-8");
|
|
7455
8976
|
const { cases, intent } = loadPrompts(csvContent, (msg) => ui.status(`Warning: ${msg}`));
|
|
7456
8977
|
if (!config.airsApiKey && !config.airsApiToken) {
|
|
7457
8978
|
fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
|
|
@@ -7622,7 +9143,7 @@ function completedBulkResults(state) {
|
|
|
7622
9143
|
return state.items.flatMap((item) => item.result ? [item.result] : []).sort((left, right) => left.index - right.index);
|
|
7623
9144
|
}
|
|
7624
9145
|
async function writeBulkResults(outputPath, results) {
|
|
7625
|
-
await fs5.promises.mkdir(
|
|
9146
|
+
await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
|
|
7626
9147
|
const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID4()}`;
|
|
7627
9148
|
try {
|
|
7628
9149
|
await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
|
|
@@ -7640,10 +9161,10 @@ function isDefiniteSubmissionRejection(error) {
|
|
|
7640
9161
|
const metadata = error;
|
|
7641
9162
|
return metadata?.failureKind === "http" && typeof metadata.statusCode === "number" && metadata.statusCode >= 400 && metadata.statusCode < 500;
|
|
7642
9163
|
}
|
|
7643
|
-
function
|
|
9164
|
+
function parsePositiveInteger3(value, optionName2) {
|
|
7644
9165
|
const parsed = Number(value);
|
|
7645
9166
|
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(parsed)) {
|
|
7646
|
-
usageError(`${
|
|
9167
|
+
usageError(`${optionName2} must be a positive integer`);
|
|
7647
9168
|
}
|
|
7648
9169
|
return parsed;
|
|
7649
9170
|
}
|
|
@@ -7746,14 +9267,14 @@ function registerRuntimeCommand(program) {
|
|
|
7746
9267
|
if (!opts.file) {
|
|
7747
9268
|
usageError("--file <file> is required");
|
|
7748
9269
|
}
|
|
7749
|
-
const batchSize =
|
|
9270
|
+
const batchSize = parsePositiveInteger3(opts.batchSize, "--batch-size");
|
|
7750
9271
|
let releaseJobLock;
|
|
7751
9272
|
try {
|
|
7752
9273
|
const config = await loadConfig({});
|
|
7753
9274
|
if (!config.airsApiKey && !config.airsApiToken) {
|
|
7754
9275
|
fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
|
|
7755
9276
|
}
|
|
7756
|
-
const raw = await
|
|
9277
|
+
const raw = await readFile9(opts.file, "utf-8");
|
|
7757
9278
|
const prompts = parseInputFile(raw, opts.file);
|
|
7758
9279
|
if (prompts.length === 0) {
|
|
7759
9280
|
usageError("No prompts found in input file");
|
|
@@ -7763,7 +9284,7 @@ function registerRuntimeCommand(program) {
|
|
|
7763
9284
|
opts.outputFile ?? `${opts.profile.replace(/\s+/g, "-")}-bulk-scan.csv`
|
|
7764
9285
|
);
|
|
7765
9286
|
const stateDir = resolvePath(
|
|
7766
|
-
basename3(config.dataDir) === "runs" ?
|
|
9287
|
+
basename3(config.dataDir) === "runs" ? join3(dirname3(config.dataDir), "bulk-scans") : join3(config.dataDir, "bulk-scans")
|
|
7767
9288
|
);
|
|
7768
9289
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7769
9290
|
const state = {
|
|
@@ -8202,7 +9723,7 @@ function registerRuntimeCommand(program) {
|
|
|
8202
9723
|
);
|
|
8203
9724
|
const outputPath = resolvePath(opts.outputFile ?? state.outputFile);
|
|
8204
9725
|
state.outputFile = outputPath;
|
|
8205
|
-
await saveBulkScanState(state,
|
|
9726
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8206
9727
|
const pollSubmitted = async (items) => {
|
|
8207
9728
|
for (const batch of submittedBatches(items)) {
|
|
8208
9729
|
const results2 = await service.pollBatch(batch, void 0, {
|
|
@@ -8211,12 +9732,12 @@ function registerRuntimeCommand(program) {
|
|
|
8211
9732
|
},
|
|
8212
9733
|
onProgress: async (progress) => {
|
|
8213
9734
|
recordBulkResults(state, progress);
|
|
8214
|
-
await saveBulkScanState(state,
|
|
9735
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8215
9736
|
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
8216
9737
|
}
|
|
8217
9738
|
});
|
|
8218
9739
|
recordBulkResults(state, results2);
|
|
8219
|
-
await saveBulkScanState(state,
|
|
9740
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8220
9741
|
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
8221
9742
|
}
|
|
8222
9743
|
};
|
|
@@ -8240,7 +9761,7 @@ function registerRuntimeCommand(program) {
|
|
|
8240
9761
|
for (let start = 0; start < pendingItems.length; start += SDK_ASYNC_BATCH_SIZE) {
|
|
8241
9762
|
const chunk = pendingItems.slice(start, start + SDK_ASYNC_BATCH_SIZE);
|
|
8242
9763
|
for (const item of chunk) item.status = "submitting";
|
|
8243
|
-
await saveBulkScanState(state,
|
|
9764
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8244
9765
|
try {
|
|
8245
9766
|
const batch = await service.submitBatch(state.profile, chunk, state.sessionId, {
|
|
8246
9767
|
onRetry: (attempt, delayMs) => {
|
|
@@ -8256,19 +9777,19 @@ function registerRuntimeCommand(program) {
|
|
|
8256
9777
|
item.receiptReportId = batch.reportId;
|
|
8257
9778
|
item.error = void 0;
|
|
8258
9779
|
}
|
|
8259
|
-
await saveBulkScanState(state,
|
|
9780
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8260
9781
|
} catch (error) {
|
|
8261
9782
|
for (const item of chunk) {
|
|
8262
9783
|
item.status = isDefiniteSubmissionRejection(error) ? "pending" : "ambiguous";
|
|
8263
9784
|
item.error = error instanceof Error ? error.message : String(error);
|
|
8264
9785
|
}
|
|
8265
|
-
await saveBulkScanState(state,
|
|
9786
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8266
9787
|
throw error;
|
|
8267
9788
|
}
|
|
8268
9789
|
}
|
|
8269
9790
|
await pollSubmitted(logicalBatch);
|
|
8270
9791
|
}
|
|
8271
|
-
await saveBulkScanState(state,
|
|
9792
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8272
9793
|
const results = completedBulkResults(state);
|
|
8273
9794
|
await writeBulkResults(outputPath, results);
|
|
8274
9795
|
const blocked = results.filter((r) => r.action === "block").length;
|
|
@@ -8421,185 +9942,6 @@ function registerRuntimeCommand(program) {
|
|
|
8421
9942
|
registerDlpCommands(runtime);
|
|
8422
9943
|
}
|
|
8423
9944
|
|
|
8424
|
-
// src/cli/debug-logger.ts
|
|
8425
|
-
import {
|
|
8426
|
-
appendFileSync,
|
|
8427
|
-
mkdirSync,
|
|
8428
|
-
readdirSync,
|
|
8429
|
-
statSync,
|
|
8430
|
-
unlinkSync,
|
|
8431
|
-
writeFileSync as writeFileSync2
|
|
8432
|
-
} from "fs";
|
|
8433
|
-
import { dirname as dirname3, join as join3 } from "path";
|
|
8434
|
-
var AIRS_DOMAINS = [
|
|
8435
|
-
"api.sase.paloaltonetworks.com",
|
|
8436
|
-
"service.api.aisecurity.paloaltonetworks.com",
|
|
8437
|
-
"auth.apps.paloaltonetworks.com",
|
|
8438
|
-
"api.dlp.paloaltonetworks.com"
|
|
8439
|
-
];
|
|
8440
|
-
function isAirsUrl(url) {
|
|
8441
|
-
try {
|
|
8442
|
-
const parsed = new URL(url);
|
|
8443
|
-
return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
|
|
8444
|
-
} catch {
|
|
8445
|
-
return false;
|
|
8446
|
-
}
|
|
8447
|
-
}
|
|
8448
|
-
var MASK = "***";
|
|
8449
|
-
var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key/i;
|
|
8450
|
-
function isSensitiveKey(key) {
|
|
8451
|
-
return SENSITIVE_KEY_PATTERN.test(key);
|
|
8452
|
-
}
|
|
8453
|
-
function redactHeaders(headers) {
|
|
8454
|
-
const out = {};
|
|
8455
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
8456
|
-
out[k] = isSensitiveKey(k) ? MASK : v;
|
|
8457
|
-
}
|
|
8458
|
-
return out;
|
|
8459
|
-
}
|
|
8460
|
-
function redactDeep(value) {
|
|
8461
|
-
if (Array.isArray(value)) {
|
|
8462
|
-
return value.map(redactDeep);
|
|
8463
|
-
}
|
|
8464
|
-
if (value !== null && typeof value === "object") {
|
|
8465
|
-
const out = {};
|
|
8466
|
-
for (const [k, v] of Object.entries(value)) {
|
|
8467
|
-
out[k] = isSensitiveKey(k) ? MASK : redactDeep(v);
|
|
8468
|
-
}
|
|
8469
|
-
return out;
|
|
8470
|
-
}
|
|
8471
|
-
return value;
|
|
8472
|
-
}
|
|
8473
|
-
function redactUrl(url) {
|
|
8474
|
-
try {
|
|
8475
|
-
const parsed = new URL(url);
|
|
8476
|
-
let touched = false;
|
|
8477
|
-
for (const key of parsed.searchParams.keys()) {
|
|
8478
|
-
if (isSensitiveKey(key)) {
|
|
8479
|
-
parsed.searchParams.set(key, MASK);
|
|
8480
|
-
touched = true;
|
|
8481
|
-
}
|
|
8482
|
-
}
|
|
8483
|
-
return touched ? parsed.toString() : url;
|
|
8484
|
-
} catch {
|
|
8485
|
-
return url;
|
|
8486
|
-
}
|
|
8487
|
-
}
|
|
8488
|
-
function pruneDebugLogs(dir, keep) {
|
|
8489
|
-
let files;
|
|
8490
|
-
try {
|
|
8491
|
-
files = readdirSync(dir).filter((f) => f.startsWith("debug-api-") && f.endsWith(".jsonl"));
|
|
8492
|
-
} catch {
|
|
8493
|
-
return;
|
|
8494
|
-
}
|
|
8495
|
-
const byAge = files.map((f) => {
|
|
8496
|
-
const path3 = join3(dir, f);
|
|
8497
|
-
try {
|
|
8498
|
-
return { path: path3, mtime: statSync(path3).mtimeMs };
|
|
8499
|
-
} catch {
|
|
8500
|
-
return null;
|
|
8501
|
-
}
|
|
8502
|
-
}).filter((e) => e !== null).sort((a, b) => b.mtime - a.mtime);
|
|
8503
|
-
for (const { path: path3 } of byAge.slice(keep)) {
|
|
8504
|
-
try {
|
|
8505
|
-
unlinkSync(path3);
|
|
8506
|
-
} catch {
|
|
8507
|
-
}
|
|
8508
|
-
}
|
|
8509
|
-
}
|
|
8510
|
-
function headersToRecord(headers) {
|
|
8511
|
-
if (!headers) return {};
|
|
8512
|
-
if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
|
|
8513
|
-
const out = {};
|
|
8514
|
-
headers.forEach((v, k) => {
|
|
8515
|
-
out[k] = v;
|
|
8516
|
-
});
|
|
8517
|
-
return out;
|
|
8518
|
-
}
|
|
8519
|
-
if (Array.isArray(headers)) {
|
|
8520
|
-
return Object.fromEntries(headers);
|
|
8521
|
-
}
|
|
8522
|
-
return headers;
|
|
8523
|
-
}
|
|
8524
|
-
var KEEP_DEBUG_LOGS = 10;
|
|
8525
|
-
function installDebugLogger(logPath) {
|
|
8526
|
-
mkdirSync(dirname3(logPath), { recursive: true });
|
|
8527
|
-
writeFileSync2(logPath, "", "utf-8");
|
|
8528
|
-
pruneDebugLogs(dirname3(logPath), KEEP_DEBUG_LOGS);
|
|
8529
|
-
const originalFetch = globalThis.fetch;
|
|
8530
|
-
globalThis.fetch = async function debugFetch(input, init2) {
|
|
8531
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
8532
|
-
if (!isAirsUrl(url)) {
|
|
8533
|
-
return originalFetch(input, init2);
|
|
8534
|
-
}
|
|
8535
|
-
const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
|
|
8536
|
-
const reqHeaders = redactHeaders(headersToRecord(init2?.headers));
|
|
8537
|
-
const loggedUrl = redactUrl(url);
|
|
8538
|
-
let reqBody;
|
|
8539
|
-
if (init2?.body) {
|
|
8540
|
-
try {
|
|
8541
|
-
reqBody = redactDeep(JSON.parse(String(init2.body)));
|
|
8542
|
-
} catch {
|
|
8543
|
-
reqBody = String(init2.body);
|
|
8544
|
-
}
|
|
8545
|
-
}
|
|
8546
|
-
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
8547
|
-
const startMs = Date.now();
|
|
8548
|
-
let response;
|
|
8549
|
-
let resBody;
|
|
8550
|
-
let error;
|
|
8551
|
-
try {
|
|
8552
|
-
response = await originalFetch(input, init2);
|
|
8553
|
-
} catch (err) {
|
|
8554
|
-
error = err instanceof Error ? err.message : String(err);
|
|
8555
|
-
const entry2 = JSON.stringify({
|
|
8556
|
-
timestamp: ts2,
|
|
8557
|
-
durationMs: Date.now() - startMs,
|
|
8558
|
-
request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
|
|
8559
|
-
error
|
|
8560
|
-
});
|
|
8561
|
-
appendFileSync(logPath, `${entry2}
|
|
8562
|
-
`);
|
|
8563
|
-
throw err;
|
|
8564
|
-
}
|
|
8565
|
-
const durationMs = Date.now() - startMs;
|
|
8566
|
-
const resHeaders = {};
|
|
8567
|
-
response.headers.forEach((v, k) => {
|
|
8568
|
-
resHeaders[k] = v;
|
|
8569
|
-
});
|
|
8570
|
-
const clone = response.clone();
|
|
8571
|
-
try {
|
|
8572
|
-
const text = await clone.text();
|
|
8573
|
-
try {
|
|
8574
|
-
resBody = redactDeep(JSON.parse(text));
|
|
8575
|
-
} catch {
|
|
8576
|
-
resBody = text;
|
|
8577
|
-
}
|
|
8578
|
-
} catch {
|
|
8579
|
-
resBody = "<unreadable>";
|
|
8580
|
-
}
|
|
8581
|
-
const entry = JSON.stringify({
|
|
8582
|
-
timestamp: ts2,
|
|
8583
|
-
durationMs,
|
|
8584
|
-
request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
|
|
8585
|
-
response: {
|
|
8586
|
-
status: response.status,
|
|
8587
|
-
statusText: response.statusText,
|
|
8588
|
-
headers: redactHeaders(resHeaders),
|
|
8589
|
-
body: resBody
|
|
8590
|
-
}
|
|
8591
|
-
});
|
|
8592
|
-
appendFileSync(logPath, `${entry}
|
|
8593
|
-
`);
|
|
8594
|
-
return response;
|
|
8595
|
-
};
|
|
8596
|
-
return {
|
|
8597
|
-
teardown() {
|
|
8598
|
-
globalThis.fetch = originalFetch;
|
|
8599
|
-
}
|
|
8600
|
-
};
|
|
8601
|
-
}
|
|
8602
|
-
|
|
8603
9945
|
// src/cli/program.ts
|
|
8604
9946
|
var READ_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
8605
9947
|
"categories",
|
|
@@ -8625,7 +9967,9 @@ var READ_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
|
8625
9967
|
function applyListDeleteAliases(cmd) {
|
|
8626
9968
|
for (const sub of cmd.commands) {
|
|
8627
9969
|
if (sub.name() === "list" && !sub.aliases().includes("ls")) sub.alias("ls");
|
|
8628
|
-
|
|
9970
|
+
const isHiddenCompatibilityCommand = sub.name() === "delete" && Boolean(sub._hidden);
|
|
9971
|
+
if (sub.name() === "delete" && !isHiddenCompatibilityCommand && !sub.aliases().includes("rm"))
|
|
9972
|
+
sub.alias("rm");
|
|
8629
9973
|
applyListDeleteAliases(sub);
|
|
8630
9974
|
}
|
|
8631
9975
|
}
|