@cdot65/prisma-airs-cli 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/dist/cli/index.js +1648 -282
- 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
|
|
@@ -287,7 +294,10 @@ var OUTPUT_FORMATS = [
|
|
|
287
294
|
];
|
|
288
295
|
async function resolveOutput(command, opts, resolution = {}) {
|
|
289
296
|
const localIsExplicit = command.getOptionValueSource?.("output") === "cli";
|
|
290
|
-
|
|
297
|
+
let rootCommand = command;
|
|
298
|
+
while (rootCommand.parent) rootCommand = rootCommand.parent;
|
|
299
|
+
const globalIsExplicit = rootCommand.getOptionValueSource?.("output") === "cli";
|
|
300
|
+
const globalOutput = globalIsExplicit ? rootCommand.opts().output : void 0;
|
|
291
301
|
let configured;
|
|
292
302
|
try {
|
|
293
303
|
configured = (await loadConfig()).defaultOutput;
|
|
@@ -295,7 +305,9 @@ async function resolveOutput(command, opts, resolution = {}) {
|
|
|
295
305
|
if (process.env.PANW_CLI_OUTPUT !== void 0) configured = process.env.PANW_CLI_OUTPUT;
|
|
296
306
|
else throw error;
|
|
297
307
|
}
|
|
298
|
-
const candidate = String(
|
|
308
|
+
const candidate = String(
|
|
309
|
+
localIsExplicit ? opts.output : globalOutput ?? configured ?? "pretty"
|
|
310
|
+
);
|
|
299
311
|
if (!OUTPUT_FORMATS.includes(candidate))
|
|
300
312
|
throw new CliUsageError(
|
|
301
313
|
`Invalid output format '${candidate}'. Expected: ${OUTPUT_FORMATS.join(", ")}`
|
|
@@ -320,7 +332,11 @@ function markdownCell(value) {
|
|
|
320
332
|
return displayValue(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
321
333
|
}
|
|
322
334
|
function formatOutput(rows, columns, format) {
|
|
323
|
-
if (rows.length === 0)
|
|
335
|
+
if (rows.length === 0) {
|
|
336
|
+
if (format === "json") return "[]";
|
|
337
|
+
if (format === "yaml") return "[]\n";
|
|
338
|
+
return "";
|
|
339
|
+
}
|
|
324
340
|
const projected = rows.map((row) => columns.map((column) => row[column.key]));
|
|
325
341
|
switch (format) {
|
|
326
342
|
case "json":
|
|
@@ -2880,7 +2896,8 @@ function renderDeploymentProfileList(profiles, format = "pretty") {
|
|
|
2880
2896
|
}
|
|
2881
2897
|
function renderScanLogList(results, pageToken, format = "pretty") {
|
|
2882
2898
|
if (results.length === 0) {
|
|
2883
|
-
ui.emptyList("scan logs");
|
|
2899
|
+
if (format === "pretty") ui.emptyList("scan logs");
|
|
2900
|
+
else console.log(formatOutput([], [], format));
|
|
2884
2901
|
return;
|
|
2885
2902
|
}
|
|
2886
2903
|
if (format !== "pretty") {
|
|
@@ -2926,29 +2943,1525 @@ function renderScanLogList(results, pageToken, format = "pretty") {
|
|
|
2926
2943
|
console.log();
|
|
2927
2944
|
}
|
|
2928
2945
|
|
|
2929
|
-
// src/cli/confirm.ts
|
|
2930
|
-
async function confirmOrAbort(message, force, options = {}) {
|
|
2931
|
-
if (force) return;
|
|
2932
|
-
const interactive = options.isTTY ?? process.stdout.isTTY === true;
|
|
2933
|
-
if (!interactive) {
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
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 parseNamedDate(value, flag) {
|
|
3608
|
+
try {
|
|
3609
|
+
return parseDateOption(value);
|
|
3610
|
+
} catch (error) {
|
|
3611
|
+
throw new CliUsageError(
|
|
3612
|
+
`Invalid ${flag}: ${error instanceof Error ? error.message : String(error)}`
|
|
3613
|
+
);
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
function parsePositiveInteger(value, flag) {
|
|
3617
|
+
try {
|
|
3618
|
+
const parsed = parseIntegerOption(value);
|
|
3619
|
+
if (parsed <= 0) throw new CliUsageError("Expected a positive integer");
|
|
3620
|
+
return parsed;
|
|
3621
|
+
} catch (error) {
|
|
3622
|
+
throw new CliUsageError(
|
|
3623
|
+
`Invalid ${flag}: ${error instanceof Error ? error.message : String(error)}`
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
function addCrudMutationNodes(group, resource, request, operations) {
|
|
3628
|
+
let create = group.command("create").description(`Create a ${resource} from structured flags`);
|
|
3629
|
+
if (request.createOptions) create = request.createOptions(create);
|
|
3630
|
+
create = addWriteOutput(addStructuredInputOptions(create));
|
|
3631
|
+
create.action(
|
|
3632
|
+
(opts) => runWrite(
|
|
3633
|
+
create,
|
|
3634
|
+
opts,
|
|
3635
|
+
() => buildStructuredRequest(opts, request.createSchema, request.createFields),
|
|
3636
|
+
(client, body) => operations.create(client, body)
|
|
3637
|
+
)
|
|
3638
|
+
);
|
|
3639
|
+
let update = group.command("update <id>").description(`Update a ${resource} with structured flags`);
|
|
3640
|
+
if (request.updateOptions) update = request.updateOptions(update);
|
|
3641
|
+
update = addWriteOutput(addStructuredInputOptions(update));
|
|
3642
|
+
update.action(
|
|
3643
|
+
(id, opts) => runWrite(
|
|
3644
|
+
update,
|
|
3645
|
+
opts,
|
|
3646
|
+
() => buildStructuredRequest(opts, request.updateSchema, request.updateFields),
|
|
3647
|
+
(client, body) => operations.update(client, id, body)
|
|
3648
|
+
)
|
|
3649
|
+
);
|
|
3650
|
+
const remove = addWriteOutput(
|
|
3651
|
+
group.command("delete <id>").description("Permanently delete this resource").option("--force", "Skip confirmation prompt")
|
|
3652
|
+
);
|
|
3653
|
+
remove.action(
|
|
3654
|
+
(id, opts) => runConfirmedWrite(
|
|
3655
|
+
remove,
|
|
3656
|
+
opts,
|
|
3657
|
+
`Permanently delete ${resource} ${id}?`,
|
|
3658
|
+
(client) => operations.delete(client, id)
|
|
3659
|
+
)
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
function registerApiKeys(root) {
|
|
3663
|
+
const apiKeys = showHelpOnEmpty(
|
|
3664
|
+
root.command("api-keys").description("Manage service and user gateway credentials")
|
|
3665
|
+
);
|
|
3666
|
+
for (const kind of ["service", "user"]) {
|
|
3667
|
+
const group = showHelpOnEmpty(apiKeys.command(kind).description(`Manage ${kind} API keys`));
|
|
3668
|
+
const list = addReadOutput(
|
|
3669
|
+
group.command("list").description(`List ${kind} API keys in a workspace (data plane)`).requiredOption("--workspace <uuid>", "Workspace UUID")
|
|
3670
|
+
);
|
|
3671
|
+
list.action(
|
|
3672
|
+
(opts) => runList(
|
|
3673
|
+
list,
|
|
3674
|
+
opts,
|
|
3675
|
+
`${kind} API keys`,
|
|
3676
|
+
(client) => kind === "service" ? client.apiKeys.listService({ workspaceId: opts.workspace }) : client.apiKeys.listUser({ workspaceId: opts.workspace })
|
|
3677
|
+
)
|
|
3678
|
+
);
|
|
3679
|
+
const get = addReadOutput(group.command("get <id>").description(`Get one ${kind} API key`));
|
|
3680
|
+
get.action(
|
|
3681
|
+
(id, opts) => runDetail(
|
|
3682
|
+
get,
|
|
3683
|
+
opts,
|
|
3684
|
+
(client) => kind === "service" ? client.apiKeys.getService(id) : client.apiKeys.getUser(id)
|
|
3685
|
+
)
|
|
3686
|
+
);
|
|
3687
|
+
const createFields = [
|
|
3688
|
+
{ option: "alertEmails", path: "alert_emails", parse: parseCsvOption },
|
|
3689
|
+
{ option: "description", path: "description" },
|
|
3690
|
+
{ option: "expiresAt", path: "expires_at" },
|
|
3691
|
+
{ option: "name", path: "name" },
|
|
3692
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
3693
|
+
{ option: "scopes", path: "scopes", parse: parseCsvOption },
|
|
3694
|
+
{ option: "type", path: "type" },
|
|
3695
|
+
{ option: "workspace", path: "workspace_id" },
|
|
3696
|
+
...kind === "user" ? [{ option: "userId", path: "user_id" }] : []
|
|
3697
|
+
];
|
|
3698
|
+
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(
|
|
3699
|
+
"--scopes <scopes>",
|
|
3700
|
+
`Comma-separated scopes (known: ${knownValues(AI_GATEWAY_KNOWN_API_KEY_SCOPES)})`
|
|
3701
|
+
).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");
|
|
3702
|
+
if (kind === "user") createCommand = createCommand.option("--user-id <uuid>", "User UUID");
|
|
3703
|
+
const create = addWriteOutput(addStructuredInputOptions(createCommand));
|
|
3704
|
+
create.action((opts) => {
|
|
3705
|
+
if (kind === "service") {
|
|
3706
|
+
return runSecretWrite(
|
|
3707
|
+
create,
|
|
3708
|
+
opts,
|
|
3709
|
+
() => buildStructuredRequest(opts, GatewayServiceApiKeyCreateRequestSchema, createFields),
|
|
3710
|
+
(client, body) => client.apiKeys.createService(body)
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
return runSecretWrite(
|
|
3714
|
+
create,
|
|
3715
|
+
opts,
|
|
3716
|
+
() => buildStructuredRequest(opts, GatewayUserApiKeyCreateRequestSchema, createFields),
|
|
3717
|
+
(client, body) => client.apiKeys.createUser(body)
|
|
3718
|
+
);
|
|
3719
|
+
});
|
|
3720
|
+
const update = addWriteOutput(
|
|
3721
|
+
addStructuredInputOptions(
|
|
3722
|
+
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(
|
|
3723
|
+
"--scopes <scopes>",
|
|
3724
|
+
`Comma-separated scopes (known: ${knownValues(AI_GATEWAY_KNOWN_API_KEY_SCOPES)})`
|
|
3725
|
+
)
|
|
3726
|
+
)
|
|
3727
|
+
);
|
|
3728
|
+
update.action(
|
|
3729
|
+
(id, opts) => runWrite(
|
|
3730
|
+
update,
|
|
3731
|
+
opts,
|
|
3732
|
+
() => buildStructuredRequest(opts, GatewayApiKeyUpdateRequestSchema, [
|
|
3733
|
+
{ option: "alertEmails", path: "alert_emails", parse: parseCsvOption },
|
|
3734
|
+
{ option: "description", path: "description" },
|
|
3735
|
+
{ option: "expiresAt", path: "expires_at" },
|
|
3736
|
+
{ option: "name", path: "name" },
|
|
3737
|
+
{ option: "resetUsage", path: "reset_usage", parse: parseBooleanOption },
|
|
3738
|
+
{ option: "scopes", path: "scopes", parse: parseCsvOption }
|
|
3739
|
+
]),
|
|
3740
|
+
(client, body) => kind === "service" ? client.apiKeys.updateService(id, body) : client.apiKeys.updateUser(id, body)
|
|
3741
|
+
)
|
|
3742
|
+
);
|
|
3743
|
+
const remove = addWriteOutput(
|
|
3744
|
+
group.command("delete <id>").description(`Revoke a ${kind} API key`).option("--force", "Skip confirmation prompt")
|
|
3745
|
+
);
|
|
3746
|
+
remove.action(
|
|
3747
|
+
(id, opts) => runConfirmedWrite(
|
|
3748
|
+
remove,
|
|
3749
|
+
opts,
|
|
3750
|
+
`Revoke ${kind} API key ${id}?`,
|
|
3751
|
+
(client) => kind === "service" ? client.apiKeys.deleteService(id) : client.apiKeys.deleteUser(id)
|
|
3752
|
+
)
|
|
3753
|
+
);
|
|
3754
|
+
const rotate = addWriteOutput(
|
|
3755
|
+
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")
|
|
3756
|
+
);
|
|
3757
|
+
rotate.action(
|
|
3758
|
+
(id, opts) => runSecretWrite(
|
|
3759
|
+
rotate,
|
|
3760
|
+
opts,
|
|
3761
|
+
() => buildStructuredRequest(opts, GatewayApiKeyRotateRequestSchema, [
|
|
3762
|
+
{
|
|
3763
|
+
option: "transitionMs",
|
|
3764
|
+
path: "key_transition_period_ms",
|
|
3765
|
+
parse: parseIntegerOption
|
|
3766
|
+
}
|
|
3767
|
+
]),
|
|
3768
|
+
(client, body) => kind === "service" ? client.apiKeys.rotateService(id, body) : client.apiKeys.rotateUser(id, body),
|
|
3769
|
+
`Rotate ${kind} API key ${id}?`
|
|
3770
|
+
)
|
|
3771
|
+
);
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
function registerAuditLogs(root) {
|
|
3775
|
+
const group = showHelpOnEmpty(
|
|
3776
|
+
root.command("audit-logs").description("Inspect organisation audit activity")
|
|
3777
|
+
);
|
|
3778
|
+
const list = addReadOutput(
|
|
3779
|
+
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")
|
|
3780
|
+
);
|
|
3781
|
+
list.action((opts) => {
|
|
3782
|
+
return runList(list, opts, "audit logs", async (client) => {
|
|
3783
|
+
const end = opts.end ? parseNamedDate(opts.end, "--end") : /* @__PURE__ */ new Date();
|
|
3784
|
+
const start = opts.start ? parseNamedDate(opts.start, "--start") : new Date(end.getTime() - parsePositiveInteger(opts.days, "--days") * 864e5);
|
|
3785
|
+
const result = await client.auditLogs.list({ start, end });
|
|
3786
|
+
return opts.revealSensitive ? result : redactDeep(result);
|
|
3787
|
+
});
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
function registerConfigs(root) {
|
|
3791
|
+
const group = registerScopedReads(
|
|
3792
|
+
root,
|
|
3793
|
+
"configs",
|
|
3794
|
+
"Manage routing configurations",
|
|
3795
|
+
(client) => client.configs
|
|
3796
|
+
);
|
|
3797
|
+
const versions = addReadOutput(
|
|
3798
|
+
group.command("versions <id>").description("List immutable config versions")
|
|
3799
|
+
);
|
|
3800
|
+
versions.action(
|
|
3801
|
+
(id, opts) => runList(versions, opts, "config versions", (client) => client.configs.listVersions(id))
|
|
3802
|
+
);
|
|
3803
|
+
const commonFields = [
|
|
3804
|
+
{ option: "name", path: "name" },
|
|
3805
|
+
{ option: "workspace", path: "workspace_id" },
|
|
3806
|
+
{ option: "status", path: "status" }
|
|
3807
|
+
];
|
|
3808
|
+
addCrudMutationNodes(
|
|
3809
|
+
group,
|
|
3810
|
+
"config",
|
|
3811
|
+
{
|
|
3812
|
+
createFields: commonFields,
|
|
3813
|
+
createOptions: (command) => command.option("--name <name>", "Config name").option("--workspace <uuid>", "Workspace UUID"),
|
|
3814
|
+
createSchema: GatewayConfigCreateRequestSchema,
|
|
3815
|
+
updateFields: commonFields,
|
|
3816
|
+
updateOptions: (command) => command.option("--name <name>", "New config name").option("--status <status>", "New config status").option("--workspace <uuid>", "New workspace UUID"),
|
|
3817
|
+
updateSchema: GatewayConfigUpdateRequestSchema
|
|
3818
|
+
},
|
|
3819
|
+
{
|
|
3820
|
+
create: (client, body) => client.configs.create(body),
|
|
3821
|
+
delete: (client, id) => client.configs.delete(id),
|
|
3822
|
+
update: (client, id, body) => client.configs.update(id, body)
|
|
3823
|
+
}
|
|
3824
|
+
);
|
|
3825
|
+
}
|
|
3826
|
+
function registerDeployments(root) {
|
|
3827
|
+
const group = showHelpOnEmpty(
|
|
3828
|
+
root.command("deployments").description("Manage self-hosted gateway registrations")
|
|
3829
|
+
);
|
|
3830
|
+
const list = addReadOutput(group.command("list").description("List deployments (admin plane)"));
|
|
3831
|
+
list.action((opts) => runList(list, opts, "deployments", (client) => client.deployments.list()));
|
|
3832
|
+
const get = addReadOutput(
|
|
3833
|
+
group.command("get <id>").description("Get a deployment by UUID (admin plane)")
|
|
3834
|
+
);
|
|
3835
|
+
get.action((id, opts) => runDetail(get, opts, (client) => client.deployments.get(id)));
|
|
3836
|
+
const ping = addReadOutput(
|
|
3837
|
+
group.command("ping <id>").description("Run the optional control-plane ingress diagnostic")
|
|
3838
|
+
);
|
|
3839
|
+
ping.action((id, opts) => runDetail(ping, opts, (client) => client.deployments.ping(id)));
|
|
3840
|
+
const archive = addWriteOutput(
|
|
3841
|
+
group.command("archive <id>").description("Archive a deployment registration").requiredOption("--organisation-id <tsg>", "Numeric TSG id").option("--force", "Skip confirmation prompt")
|
|
3842
|
+
);
|
|
3843
|
+
archive.action(
|
|
3844
|
+
(id, opts) => runConfirmedWrite(
|
|
3845
|
+
archive,
|
|
3846
|
+
opts,
|
|
3847
|
+
`Archive deployment ${id}?`,
|
|
3848
|
+
(client) => client.deployments.delete(id, opts.organisationId)
|
|
3849
|
+
)
|
|
3850
|
+
);
|
|
3851
|
+
const create = addWriteOutput(
|
|
3852
|
+
addStructuredInputOptions(
|
|
3853
|
+
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")
|
|
3854
|
+
)
|
|
3855
|
+
);
|
|
3856
|
+
create.action(
|
|
3857
|
+
(opts) => runSecretWrite(
|
|
3858
|
+
create,
|
|
3859
|
+
opts,
|
|
3860
|
+
() => buildStructuredRequest(opts, GatewayDeploymentCreateRequestSchema, [
|
|
3861
|
+
{ option: "authSettings", path: "auth_settings", parse: parseJsonOption },
|
|
3862
|
+
{ option: "deploymentConfig", path: "deployment_config", parse: parseJsonOption },
|
|
3863
|
+
{ option: "isDefault", path: "is_default", parse: parseBooleanOption },
|
|
3864
|
+
{ option: "name", path: "name" },
|
|
3865
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
3866
|
+
{ option: "slug", path: "slug" },
|
|
3867
|
+
{ option: "type", path: "type" }
|
|
3868
|
+
]),
|
|
3869
|
+
(client, body) => client.deployments.create(body)
|
|
3870
|
+
)
|
|
3871
|
+
);
|
|
3872
|
+
const update = addWriteOutput(
|
|
3873
|
+
addStructuredInputOptions(
|
|
3874
|
+
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(
|
|
3875
|
+
"--status <status>",
|
|
3876
|
+
`Deployment status: ${knownValues(AI_GATEWAY_DEPLOYMENT_STATUSES)}`
|
|
3877
|
+
).option("--type <type>", `Deployment type: ${knownValues(AI_GATEWAY_DEPLOYMENT_TYPES)}`)
|
|
3878
|
+
)
|
|
3879
|
+
);
|
|
3880
|
+
update.action(
|
|
3881
|
+
(id, opts) => runSecretWrite(
|
|
3882
|
+
update,
|
|
3883
|
+
opts,
|
|
3884
|
+
() => buildStructuredRequest(opts, GatewayDeploymentUpdateRequestSchema, [
|
|
3885
|
+
{ option: "authSettings", path: "auth_settings", parse: parseJsonOption },
|
|
3886
|
+
{ option: "deploymentConfig", path: "deployment_config", parse: parseJsonOption },
|
|
3887
|
+
{ option: "isDefault", path: "is_default", parse: parseBooleanOption },
|
|
3888
|
+
{ option: "name", path: "name" },
|
|
3889
|
+
{ option: "overrideExisting", path: "override_existing", parse: parseBooleanOption },
|
|
3890
|
+
{ option: "rotateAuth", path: "rotate_auth", parse: parseBooleanOption },
|
|
3891
|
+
{ option: "status", path: "status" },
|
|
3892
|
+
{ option: "type", path: "type" }
|
|
3893
|
+
]),
|
|
3894
|
+
(client, body) => client.deployments.update(id, body),
|
|
3895
|
+
void 0,
|
|
3896
|
+
{
|
|
3897
|
+
requiresDestination: (body) => body.rotate_auth === true,
|
|
3898
|
+
redactResponse: (result) => redactAIGatewaySecrets("deployments.update", result, "response")
|
|
3899
|
+
}
|
|
3900
|
+
)
|
|
3901
|
+
);
|
|
3902
|
+
}
|
|
3903
|
+
function registerIntegrations(root) {
|
|
3904
|
+
const group = showHelpOnEmpty(
|
|
3905
|
+
root.command("integrations").description("Manage organisation provider integrations")
|
|
3906
|
+
);
|
|
3907
|
+
const list = addReadOutput(
|
|
3908
|
+
group.command("list").description("List provider integrations (admin plane)")
|
|
3909
|
+
);
|
|
3910
|
+
list.action(
|
|
3911
|
+
(opts) => runList(list, opts, "integrations", (client) => client.integrations.list())
|
|
3912
|
+
);
|
|
3913
|
+
const get = addReadOutput(
|
|
3914
|
+
group.command("get <id>").description("Get a provider integration by UUID")
|
|
3915
|
+
);
|
|
3916
|
+
get.action((id, opts) => runDetail(get, opts, (client) => client.integrations.get(id)));
|
|
3917
|
+
const integrationFields = [
|
|
3918
|
+
{ option: "aiProviderId", path: "ai_provider_id" },
|
|
3919
|
+
{ option: "configurations", path: "configurations", parse: parseJsonOption },
|
|
3920
|
+
{ option: "description", path: "description" },
|
|
3921
|
+
{ option: "key", path: "key" },
|
|
3922
|
+
{ option: "name", path: "name" },
|
|
3923
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
3924
|
+
{ option: "secretMappings", path: "secret_mappings", parse: parseJsonOption },
|
|
3925
|
+
{ option: "slug", path: "slug" }
|
|
3926
|
+
];
|
|
3927
|
+
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");
|
|
3928
|
+
const integrationUpdateFields = integrationFields.filter(
|
|
3929
|
+
(field) => ["configurations", "description", "key", "name", "secretMappings"].includes(field.option)
|
|
3930
|
+
);
|
|
3931
|
+
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");
|
|
3932
|
+
const create = addWriteOutput(
|
|
3933
|
+
addStructuredInputOptions(
|
|
3934
|
+
addIntegrationFields(
|
|
3935
|
+
group.command("create").description("Create an integration from structured flags")
|
|
3936
|
+
)
|
|
3937
|
+
)
|
|
3938
|
+
);
|
|
3939
|
+
create.action(
|
|
3940
|
+
(opts) => runWrite(
|
|
3941
|
+
create,
|
|
3942
|
+
opts,
|
|
3943
|
+
() => buildStructuredRequest(opts, GatewayIntegrationCreateRequestSchema, integrationFields),
|
|
3944
|
+
(client, body) => client.integrations.create(body)
|
|
3945
|
+
)
|
|
3946
|
+
);
|
|
3947
|
+
const update = addWriteOutput(
|
|
3948
|
+
addStructuredInputOptions(
|
|
3949
|
+
addIntegrationUpdateFields(
|
|
3950
|
+
group.command("update <id>").description("Update an integration with structured flags")
|
|
3951
|
+
)
|
|
3952
|
+
)
|
|
3953
|
+
);
|
|
3954
|
+
update.action(
|
|
3955
|
+
(id, opts) => runWrite(
|
|
3956
|
+
update,
|
|
3957
|
+
opts,
|
|
3958
|
+
() => buildStructuredRequest(
|
|
3959
|
+
opts,
|
|
3960
|
+
GatewayIntegrationUpdateRequestSchema,
|
|
3961
|
+
integrationUpdateFields
|
|
3962
|
+
),
|
|
3963
|
+
(client, body) => client.integrations.update(id, body)
|
|
3964
|
+
)
|
|
3965
|
+
);
|
|
3966
|
+
const remove = addWriteOutput(
|
|
3967
|
+
group.command("delete <id>").description("Permanently delete this integration").requiredOption("--organisation-id <tsg>", "Numeric TSG id").option("--force", "Skip confirmation prompt")
|
|
3968
|
+
);
|
|
3969
|
+
remove.action(
|
|
3970
|
+
(id, opts) => runConfirmedWrite(
|
|
3971
|
+
remove,
|
|
3972
|
+
opts,
|
|
3973
|
+
`Permanently delete integration ${id}?`,
|
|
3974
|
+
(client) => client.integrations.delete(id, opts.organisationId)
|
|
3975
|
+
)
|
|
3976
|
+
);
|
|
3977
|
+
const models = showHelpOnEmpty(
|
|
3978
|
+
group.command("models").description("Inspect or replace model bindings")
|
|
3979
|
+
);
|
|
3980
|
+
const modelsList = addReadOutput(
|
|
3981
|
+
models.command("list <id>").description("List models for an integration")
|
|
3982
|
+
);
|
|
3983
|
+
modelsList.action(
|
|
3984
|
+
(id, opts) => runList(modelsList, opts, "integration models", (client) => client.integrations.getModels(id))
|
|
3985
|
+
);
|
|
3986
|
+
const modelsSet = addWriteOutput(
|
|
3987
|
+
addStructuredInputOptions(
|
|
3988
|
+
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")
|
|
3989
|
+
)
|
|
3990
|
+
);
|
|
3991
|
+
modelsSet.action(
|
|
3992
|
+
(id, opts) => runConfirmedWrite(
|
|
3993
|
+
modelsSet,
|
|
3994
|
+
opts,
|
|
3995
|
+
`Replace model bindings on ${id}?`,
|
|
3996
|
+
() => buildStructuredRequest(opts, GatewayIntegrationModelsBulkUpdateRequestSchema, [
|
|
3997
|
+
{ option: "allowAllModels", path: "allow_all_models", parse: parseBooleanOption },
|
|
3998
|
+
{ option: "model", path: "models", parse: parseModelBindingsOption }
|
|
3999
|
+
]),
|
|
4000
|
+
(client, body) => client.integrations.setModels(id, body)
|
|
4001
|
+
)
|
|
4002
|
+
);
|
|
4003
|
+
const workspaces = showHelpOnEmpty(
|
|
4004
|
+
group.command("workspaces").description("Inspect or replace workspace bindings")
|
|
4005
|
+
);
|
|
4006
|
+
const workspacesList = addReadOutput(
|
|
4007
|
+
workspaces.command("list <id>").description("List workspace bindings")
|
|
4008
|
+
);
|
|
4009
|
+
workspacesList.action(
|
|
4010
|
+
(id, opts) => runDetail(workspacesList, opts, (client) => client.integrations.getWorkspaces(id))
|
|
4011
|
+
);
|
|
4012
|
+
const workspacesSet = addWriteOutput(
|
|
4013
|
+
addStructuredInputOptions(
|
|
4014
|
+
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")
|
|
4015
|
+
)
|
|
4016
|
+
);
|
|
4017
|
+
workspacesSet.action(
|
|
4018
|
+
(id, opts) => runConfirmedWrite(
|
|
4019
|
+
workspacesSet,
|
|
4020
|
+
opts,
|
|
4021
|
+
`Replace workspace bindings on ${id}?`,
|
|
4022
|
+
() => buildStructuredRequest(
|
|
4023
|
+
{
|
|
4024
|
+
...opts,
|
|
4025
|
+
overrideExistingWorkspaceAccess: !opts.preserveExisting
|
|
4026
|
+
},
|
|
4027
|
+
GatewayIntegrationWorkspacesBulkUpdateRequestSchema,
|
|
4028
|
+
[
|
|
4029
|
+
{
|
|
4030
|
+
option: "createDefaultProvider",
|
|
4031
|
+
path: "create_default_provider",
|
|
4032
|
+
parse: parseBooleanOption
|
|
4033
|
+
},
|
|
4034
|
+
{ option: "defaultProviderSlug", path: "default_provider_slug" },
|
|
4035
|
+
{
|
|
4036
|
+
option: "globalAccess",
|
|
4037
|
+
path: "global_workspace_access.enabled",
|
|
4038
|
+
parse: parseBooleanOption
|
|
4039
|
+
},
|
|
4040
|
+
{
|
|
4041
|
+
option: "overrideExistingWorkspaceAccess",
|
|
4042
|
+
path: "override_existing_workspace_access",
|
|
4043
|
+
parse: parseBooleanOption
|
|
4044
|
+
},
|
|
4045
|
+
{ option: "workspaceBinding", path: "workspaces", parse: parseBooleanBindingsOption }
|
|
4046
|
+
]
|
|
4047
|
+
),
|
|
4048
|
+
(client, body) => client.integrations.setWorkspaces(id, body)
|
|
4049
|
+
)
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
4052
|
+
function registerMcp(root) {
|
|
4053
|
+
const mcp = showHelpOnEmpty(
|
|
4054
|
+
root.command("mcp").description("Manage MCP integrations and servers")
|
|
4055
|
+
);
|
|
4056
|
+
const group = showHelpOnEmpty(
|
|
4057
|
+
mcp.command("integrations").description("Manage MCP server integrations")
|
|
4058
|
+
);
|
|
4059
|
+
const list = addReadOutput(
|
|
4060
|
+
group.command("list").description("List MCP integrations (admin plane)")
|
|
4061
|
+
);
|
|
4062
|
+
list.action(
|
|
4063
|
+
(opts) => runList(list, opts, "MCP integrations", (client) => client.mcpIntegrations.list())
|
|
4064
|
+
);
|
|
4065
|
+
const get = addReadOutput(
|
|
4066
|
+
group.command("get <id>").description("Get an MCP integration by UUID")
|
|
4067
|
+
);
|
|
4068
|
+
get.action((id, opts) => runDetail(get, opts, (client) => client.mcpIntegrations.get(id)));
|
|
4069
|
+
const mcpFields = [
|
|
4070
|
+
{ option: "authType", path: "auth_type" },
|
|
4071
|
+
{ option: "configurations", path: "configurations", parse: parseJsonOption },
|
|
4072
|
+
{ option: "description", path: "description" },
|
|
4073
|
+
{ option: "name", path: "name" },
|
|
4074
|
+
{ option: "organisationId", path: "organisation_id" },
|
|
4075
|
+
{ option: "secretMappings", path: "secret_mappings", parse: parseJsonOption },
|
|
4076
|
+
{ option: "slug", path: "slug" },
|
|
4077
|
+
{ option: "transport", path: "transport" },
|
|
4078
|
+
{ option: "url", path: "url" }
|
|
4079
|
+
];
|
|
4080
|
+
const addMcpFields = (command) => command.option(
|
|
4081
|
+
"--auth-type <type>",
|
|
4082
|
+
`Authentication type (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_AUTH_TYPES)})`
|
|
4083
|
+
).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(
|
|
4084
|
+
"--transport <transport>",
|
|
4085
|
+
`Transport (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_TRANSPORTS)})`
|
|
4086
|
+
).option("--url <url>", "MCP server URL");
|
|
4087
|
+
const mcpUpdateFields = mcpFields.filter(
|
|
4088
|
+
(field) => [
|
|
4089
|
+
"authType",
|
|
4090
|
+
"configurations",
|
|
4091
|
+
"description",
|
|
4092
|
+
"name",
|
|
4093
|
+
"secretMappings",
|
|
4094
|
+
"transport",
|
|
4095
|
+
"url"
|
|
4096
|
+
].includes(field.option)
|
|
4097
|
+
);
|
|
4098
|
+
const addMcpUpdateFields = (command) => command.option(
|
|
4099
|
+
"--auth-type <type>",
|
|
4100
|
+
`Authentication type (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_AUTH_TYPES)})`
|
|
4101
|
+
).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(
|
|
4102
|
+
"--transport <transport>",
|
|
4103
|
+
`Transport (known: ${knownValues(AI_GATEWAY_KNOWN_MCP_TRANSPORTS)})`
|
|
4104
|
+
).option("--url <url>", "MCP server URL");
|
|
4105
|
+
addCrudMutationNodes(
|
|
4106
|
+
group,
|
|
4107
|
+
"MCP integration",
|
|
4108
|
+
{
|
|
4109
|
+
createFields: mcpFields,
|
|
4110
|
+
createOptions: addMcpFields,
|
|
4111
|
+
createSchema: McpIntegrationCreateRequestSchema,
|
|
4112
|
+
updateFields: mcpUpdateFields,
|
|
4113
|
+
updateOptions: addMcpUpdateFields,
|
|
4114
|
+
updateSchema: McpIntegrationUpdateRequestSchema
|
|
4115
|
+
},
|
|
4116
|
+
{
|
|
4117
|
+
create: (client, body) => client.mcpIntegrations.create(body),
|
|
4118
|
+
delete: (client, id) => client.mcpIntegrations.delete(id),
|
|
4119
|
+
update: (client, id, body) => client.mcpIntegrations.update(id, body)
|
|
4120
|
+
}
|
|
4121
|
+
);
|
|
4122
|
+
const capabilities = showHelpOnEmpty(
|
|
4123
|
+
group.command("capabilities").description("Inspect or replace MCP capabilities")
|
|
4124
|
+
);
|
|
4125
|
+
const capabilitiesList = addReadOutput(
|
|
4126
|
+
capabilities.command("list <id>").description("List discovered MCP capabilities")
|
|
4127
|
+
);
|
|
4128
|
+
capabilitiesList.action(
|
|
4129
|
+
(id, opts) => runDetail(capabilitiesList, opts, (client) => client.mcpIntegrations.getCapabilities(id))
|
|
4130
|
+
);
|
|
4131
|
+
const capabilitiesSet = addWriteOutput(
|
|
4132
|
+
addStructuredInputOptions(
|
|
4133
|
+
capabilities.command("set <id>").description("Replace enabled MCP capabilities").option(
|
|
4134
|
+
"--capability <type:name=enabled>",
|
|
4135
|
+
`Capability binding (repeatable; type: ${knownValues(AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES)})`,
|
|
4136
|
+
collectOption
|
|
4137
|
+
).option("--force", "Skip confirmation prompt")
|
|
4138
|
+
)
|
|
4139
|
+
);
|
|
4140
|
+
capabilitiesSet.action(
|
|
4141
|
+
(id, opts) => runConfirmedWrite(
|
|
4142
|
+
capabilitiesSet,
|
|
4143
|
+
opts,
|
|
4144
|
+
`Replace enabled MCP capabilities on ${id}?`,
|
|
4145
|
+
() => buildStructuredRequest(opts, McpIntegrationCapabilitiesBulkUpdateRequestSchema, [
|
|
4146
|
+
{ option: "capability", path: "capabilities", parse: parseCapabilityBindingsOption }
|
|
4147
|
+
]),
|
|
4148
|
+
(client, body) => client.mcpIntegrations.setCapabilities(id, body)
|
|
4149
|
+
)
|
|
4150
|
+
);
|
|
4151
|
+
const metadata = addReadOutput(
|
|
4152
|
+
group.command("metadata <id>").description("Get discovered MCP metadata")
|
|
4153
|
+
);
|
|
4154
|
+
metadata.action(
|
|
4155
|
+
(id, opts) => runDetail(metadata, opts, (client) => client.mcpIntegrations.getMetadata(id))
|
|
4156
|
+
);
|
|
4157
|
+
const workspaces = showHelpOnEmpty(
|
|
4158
|
+
group.command("workspaces").description("Inspect or replace MCP workspace access")
|
|
4159
|
+
);
|
|
4160
|
+
const workspacesList = addReadOutput(
|
|
4161
|
+
workspaces.command("list <id>").description("Read workspace access from integration detail")
|
|
4162
|
+
);
|
|
4163
|
+
workspacesList.action(
|
|
4164
|
+
(id, opts) => runDetail(workspacesList, opts, (client) => client.mcpIntegrations.get(id))
|
|
4165
|
+
);
|
|
4166
|
+
const workspacesSet = addWriteOutput(
|
|
4167
|
+
addStructuredInputOptions(
|
|
4168
|
+
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")
|
|
4169
|
+
)
|
|
4170
|
+
);
|
|
4171
|
+
workspacesSet.action(
|
|
4172
|
+
(id, opts) => runConfirmedWrite(
|
|
4173
|
+
workspacesSet,
|
|
4174
|
+
opts,
|
|
4175
|
+
`Replace MCP workspace access on ${id}?`,
|
|
4176
|
+
() => buildStructuredRequest(
|
|
4177
|
+
{
|
|
4178
|
+
...opts,
|
|
4179
|
+
overrideExistingWorkspaceAccess: !opts.preserveExisting
|
|
4180
|
+
},
|
|
4181
|
+
McpIntegrationWorkspacesBulkUpdateRequestSchema,
|
|
4182
|
+
[
|
|
4183
|
+
{
|
|
4184
|
+
option: "globalAccess",
|
|
4185
|
+
path: "global_workspace_access.enabled",
|
|
4186
|
+
parse: parseBooleanOption
|
|
4187
|
+
},
|
|
4188
|
+
{
|
|
4189
|
+
option: "overrideExistingWorkspaceAccess",
|
|
4190
|
+
path: "override_existing_workspace_access",
|
|
4191
|
+
parse: parseBooleanOption
|
|
4192
|
+
},
|
|
4193
|
+
{ option: "workspaceBinding", path: "workspaces", parse: parseBooleanBindingsOption }
|
|
4194
|
+
]
|
|
4195
|
+
),
|
|
4196
|
+
(client, body) => client.mcpIntegrations.setWorkspaces(id, body)
|
|
4197
|
+
)
|
|
4198
|
+
);
|
|
4199
|
+
}
|
|
4200
|
+
function registerOrganisations(root) {
|
|
4201
|
+
const group = showHelpOnEmpty(
|
|
4202
|
+
root.command("organisations").description("Manage organisation and authentication settings")
|
|
4203
|
+
);
|
|
4204
|
+
const self = showHelpOnEmpty(
|
|
4205
|
+
group.command("self").description("Manage the current organisation")
|
|
4206
|
+
);
|
|
4207
|
+
const selfGet = addReadOutput(self.command("get").description("Get the current organisation"));
|
|
4208
|
+
selfGet.action((opts) => runDetail(selfGet, opts, (client) => client.organisations.getSelf()));
|
|
4209
|
+
const selfUpdate = addWriteOutput(
|
|
4210
|
+
addStructuredInputOptions(
|
|
4211
|
+
self.command("update").description("Update the current organisation with structured flags").option("--name <name>", "Organisation name")
|
|
4212
|
+
)
|
|
4213
|
+
);
|
|
4214
|
+
selfUpdate.action(
|
|
4215
|
+
(opts) => runWrite(
|
|
4216
|
+
selfUpdate,
|
|
4217
|
+
opts,
|
|
4218
|
+
() => buildStructuredRequest(opts, GatewayOrganisationUpdateRequestSchema, [
|
|
4219
|
+
{ option: "name", path: "name" }
|
|
4220
|
+
]),
|
|
4221
|
+
(client, body) => client.organisations.updateSelf(body)
|
|
4222
|
+
)
|
|
4223
|
+
);
|
|
4224
|
+
const auth = showHelpOnEmpty(
|
|
4225
|
+
group.command("auth-settings").description("Manage organisation authentication settings")
|
|
4226
|
+
);
|
|
4227
|
+
const authGet = addReadOutput(
|
|
4228
|
+
auth.command("get").description("Get authentication settings").requiredOption("--tsg-id <tsg>", "Numeric TSG id").option("--reveal-sensitive", "Show SCIM and authentication secrets")
|
|
4229
|
+
);
|
|
4230
|
+
authGet.action(
|
|
4231
|
+
(opts) => runDetail(authGet, opts, async (client) => {
|
|
4232
|
+
const result = await client.organisations.getAuthSettings(opts.tsgId);
|
|
4233
|
+
return opts.revealSensitive ? result : redactAIGatewaySecrets("organisations.getAuthSettings", result, "response");
|
|
4234
|
+
})
|
|
4235
|
+
);
|
|
4236
|
+
const authUpdate = addWriteOutput(
|
|
4237
|
+
addStructuredInputOptions(
|
|
4238
|
+
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")
|
|
4239
|
+
)
|
|
4240
|
+
);
|
|
4241
|
+
authUpdate.action(
|
|
4242
|
+
(opts) => runWrite(
|
|
4243
|
+
authUpdate,
|
|
4244
|
+
opts,
|
|
4245
|
+
() => buildStructuredRequest(opts, GatewayOrganisationAuthSettingsUpdateRequestSchema, [
|
|
4246
|
+
{ option: "authSettings", path: "auth_settings", parse: parseJsonOption },
|
|
4247
|
+
{ option: "domains", path: "domains", parse: parseCsvOption },
|
|
4248
|
+
{ option: "scimToken", path: "scim_token" }
|
|
4249
|
+
]),
|
|
4250
|
+
(client, body) => client.organisations.updateAuthSettings(opts.tsgId, body)
|
|
4251
|
+
)
|
|
4252
|
+
);
|
|
4253
|
+
}
|
|
4254
|
+
function registerPlugins(root) {
|
|
4255
|
+
const group = showHelpOnEmpty(root.command("plugins").description("Manage gateway plugins"));
|
|
4256
|
+
const list = addReadOutput(group.command("list").description("List installed gateway plugins"));
|
|
4257
|
+
list.action((opts) => runList(list, opts, "plugins", (client) => client.plugins.list()));
|
|
4258
|
+
const create = addWriteOutput(
|
|
4259
|
+
addStructuredInputOptions(
|
|
4260
|
+
group.command("create").description("Install a gateway plugin from structured flags").option(
|
|
4261
|
+
"--credential <key=value>",
|
|
4262
|
+
"Plugin credential (repeatable; treated as sensitive)",
|
|
4263
|
+
collectOption
|
|
4264
|
+
).option("--integration-id <uuid>", "Integration UUID").option("--organisation-id <tsg>", "Numeric TSG id")
|
|
4265
|
+
)
|
|
4266
|
+
);
|
|
4267
|
+
create.action(
|
|
4268
|
+
(opts) => runWrite(
|
|
4269
|
+
create,
|
|
4270
|
+
opts,
|
|
4271
|
+
() => buildStructuredRequest(opts, GatewayPluginCreateRequestSchema, [
|
|
4272
|
+
{ option: "credential", path: "credentials", parse: parseStringMapOption },
|
|
4273
|
+
{ option: "integrationId", path: "integration_id" },
|
|
4274
|
+
{ option: "organisationId", path: "organisation_id" }
|
|
4275
|
+
]),
|
|
4276
|
+
(client, body) => client.plugins.create(body)
|
|
4277
|
+
)
|
|
4278
|
+
);
|
|
4279
|
+
}
|
|
4280
|
+
function registerAiGatewayInventory(root) {
|
|
4281
|
+
registerApiKeys(root);
|
|
4282
|
+
registerAuditLogs(root);
|
|
4283
|
+
registerConfigs(root);
|
|
4284
|
+
registerDeployments(root);
|
|
4285
|
+
const guardrails = registerScopedReads(
|
|
4286
|
+
root,
|
|
4287
|
+
"guardrails",
|
|
4288
|
+
"Manage workspace guardrails",
|
|
4289
|
+
(client) => client.guardrails
|
|
4290
|
+
);
|
|
4291
|
+
const guardrailFields = [
|
|
4292
|
+
{ option: "actions", path: "actions", parse: parseJsonOption },
|
|
4293
|
+
{ option: "checks", path: "checks", parse: parseJsonOption },
|
|
4294
|
+
{ option: "name", path: "name" },
|
|
4295
|
+
{ option: "workspace", path: "workspace_id" }
|
|
4296
|
+
];
|
|
4297
|
+
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");
|
|
4298
|
+
const guardrailUpdateFields = guardrailFields.filter((field) => field.option !== "workspace");
|
|
4299
|
+
const addGuardrailUpdateFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name");
|
|
4300
|
+
addCrudMutationNodes(
|
|
4301
|
+
guardrails,
|
|
4302
|
+
"guardrail",
|
|
4303
|
+
{
|
|
4304
|
+
createFields: guardrailFields,
|
|
4305
|
+
createOptions: addGuardrailFields,
|
|
4306
|
+
createSchema: GatewayGuardrailCreateRequestSchema,
|
|
4307
|
+
updateFields: guardrailUpdateFields,
|
|
4308
|
+
updateOptions: addGuardrailUpdateFields,
|
|
4309
|
+
updateSchema: GatewayGuardrailUpdateRequestSchema
|
|
4310
|
+
},
|
|
4311
|
+
{
|
|
4312
|
+
create: (client, body) => client.guardrails.create(body),
|
|
4313
|
+
delete: (client, id) => client.guardrails.delete(id),
|
|
4314
|
+
update: (client, id, body) => client.guardrails.update(id, body)
|
|
4315
|
+
}
|
|
4316
|
+
);
|
|
4317
|
+
registerIntegrations(root);
|
|
4318
|
+
registerMcp(root);
|
|
4319
|
+
registerOrganisations(root);
|
|
4320
|
+
registerPlugins(root);
|
|
4321
|
+
const providers = registerScopedReads(
|
|
4322
|
+
root,
|
|
4323
|
+
"providers",
|
|
4324
|
+
"Manage workspace provider bindings",
|
|
4325
|
+
(client) => client.providers,
|
|
4326
|
+
{ sensitiveDetail: true, sensitiveOperation: "providers.get" }
|
|
4327
|
+
);
|
|
4328
|
+
const providerFields = [
|
|
4329
|
+
{ option: "aiProviderId", path: "ai_provider_id" },
|
|
4330
|
+
{ option: "expiresAt", path: "expires_at" },
|
|
4331
|
+
{ option: "integrationId", path: "integration_id" },
|
|
4332
|
+
{ option: "name", path: "name" },
|
|
4333
|
+
{ option: "note", path: "note" },
|
|
4334
|
+
{ option: "rateLimit", path: "rate_limits", parse: parseJsonOption },
|
|
4335
|
+
{ option: "resetUsage", path: "reset_usage", parse: parseBooleanOption },
|
|
4336
|
+
{ option: "slug", path: "slug" },
|
|
4337
|
+
{ option: "usageLimit", path: "usage_limits", parse: parseJsonOption },
|
|
4338
|
+
{ option: "workspace", path: "workspace_id" }
|
|
4339
|
+
];
|
|
4340
|
+
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");
|
|
4341
|
+
const providerUpdateFields = providerFields.filter(
|
|
4342
|
+
(field) => ["expiresAt", "name", "note", "rateLimit", "resetUsage", "usageLimit"].includes(field.option)
|
|
4343
|
+
);
|
|
4344
|
+
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");
|
|
4345
|
+
addCrudMutationNodes(
|
|
4346
|
+
providers,
|
|
4347
|
+
"provider",
|
|
4348
|
+
{
|
|
4349
|
+
createFields: providerFields,
|
|
4350
|
+
createOptions: addProviderFields,
|
|
4351
|
+
createSchema: GatewayProviderCreateRequestSchema,
|
|
4352
|
+
updateFields: providerUpdateFields,
|
|
4353
|
+
updateOptions: addProviderUpdateFields,
|
|
4354
|
+
updateSchema: GatewayProviderUpdateRequestSchema
|
|
4355
|
+
},
|
|
4356
|
+
{
|
|
4357
|
+
create: (client, body) => client.providers.create(body),
|
|
4358
|
+
delete: (client, id) => client.providers.delete(id),
|
|
4359
|
+
update: (client, id, body) => client.providers.update(id, body)
|
|
4360
|
+
}
|
|
4361
|
+
);
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
// src/cli/commands/aigateway/telemetry.ts
|
|
4365
|
+
function addWindowOptions(command) {
|
|
4366
|
+
return addReadOutput(
|
|
4367
|
+
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")
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
4370
|
+
function windowFrom(opts) {
|
|
4371
|
+
const window = { workspaceSlug: opts.workspace };
|
|
4372
|
+
if (opts.start) window.start = parseNamedDate2(opts.start, "--start");
|
|
4373
|
+
else window.days = parsePositiveInteger2(opts.days ?? "7", "--days");
|
|
4374
|
+
if (opts.end) window.end = parseNamedDate2(opts.end, "--end");
|
|
4375
|
+
return window;
|
|
4376
|
+
}
|
|
4377
|
+
function parsePositiveInteger2(value, flag) {
|
|
4378
|
+
try {
|
|
4379
|
+
const parsed = parseIntegerOption(value);
|
|
4380
|
+
if (parsed <= 0) throw new CliUsageError("Expected a positive integer");
|
|
4381
|
+
return parsed;
|
|
4382
|
+
} catch (error) {
|
|
4383
|
+
if (error instanceof CliUsageError)
|
|
4384
|
+
throw new CliUsageError(`Invalid ${flag}: ${error.message}`);
|
|
4385
|
+
throw error;
|
|
2937
4386
|
}
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
4387
|
+
}
|
|
4388
|
+
function parseNamedDate2(value, flag) {
|
|
4389
|
+
try {
|
|
4390
|
+
return parseDateOption(value);
|
|
4391
|
+
} catch (error) {
|
|
4392
|
+
if (error instanceof CliUsageError)
|
|
4393
|
+
throw new CliUsageError(`Invalid ${flag}: ${error.message}`);
|
|
4394
|
+
throw error;
|
|
2943
4395
|
}
|
|
2944
4396
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
4397
|
+
function registerMetric(telemetry, name, description, method) {
|
|
4398
|
+
const command = addWindowOptions(telemetry.command(name).description(description));
|
|
4399
|
+
command.action(
|
|
4400
|
+
(opts) => runDetail(command, opts, (client) => client.telemetry[method](windowFrom(opts)))
|
|
4401
|
+
);
|
|
4402
|
+
}
|
|
4403
|
+
function registerAiGatewayTelemetryReads(telemetry) {
|
|
4404
|
+
const cache = showHelpOnEmpty(telemetry.command("cache").description("Inspect cache telemetry"));
|
|
4405
|
+
const cacheSummary = addWindowOptions(cache.command("summary").description("Get cache totals"));
|
|
4406
|
+
cacheSummary.action(
|
|
4407
|
+
(opts) => runDetail(cacheSummary, opts, (client) => client.telemetry.cacheSummary(windowFrom(opts)))
|
|
4408
|
+
);
|
|
4409
|
+
const cacheTrend = addWindowOptions(cache.command("trend").description("Get cache-hit trend"));
|
|
4410
|
+
cacheTrend.action(
|
|
4411
|
+
(opts) => runDetail(cacheTrend, opts, (client) => client.telemetry.cacheHitTrend(windowFrom(opts)))
|
|
4412
|
+
);
|
|
4413
|
+
registerMetric(telemetry, "error-trends", "Get error trends", "errorTrends");
|
|
4414
|
+
registerMetric(telemetry, "errors", "Get error count", "errors");
|
|
4415
|
+
const feedback = showHelpOnEmpty(
|
|
4416
|
+
telemetry.command("feedback").description("Inspect model feedback telemetry")
|
|
4417
|
+
);
|
|
4418
|
+
const feedbackMethods = {
|
|
4419
|
+
distribution: "feedbackScoreDistribution",
|
|
4420
|
+
models: "feedbackModels",
|
|
4421
|
+
trend: "feedbackTrend",
|
|
4422
|
+
weighted: "feedbackWeighted"
|
|
4423
|
+
};
|
|
4424
|
+
for (const [name, method] of Object.entries(feedbackMethods)) {
|
|
4425
|
+
const command = addWindowOptions(feedback.command(name).description(`Get feedback ${name}`));
|
|
4426
|
+
command.action(
|
|
4427
|
+
(opts) => runDetail(command, opts, (client) => client.telemetry[method](windowFrom(opts)))
|
|
4428
|
+
);
|
|
4429
|
+
}
|
|
4430
|
+
const groupBy = addWindowOptions(
|
|
4431
|
+
telemetry.command("group-by <dimension>").description("Aggregate telemetry by provider, model, status, or another SDK dimension").option("--columns <names>", "Comma-separated aggregate columns")
|
|
4432
|
+
);
|
|
4433
|
+
groupBy.action(
|
|
4434
|
+
(dimension, opts) => runDetail(
|
|
4435
|
+
groupBy,
|
|
4436
|
+
opts,
|
|
4437
|
+
(client) => client.telemetry.groupBy(dimension, {
|
|
4438
|
+
...windowFrom(opts),
|
|
4439
|
+
...opts.columns ? { columns: opts.columns.split(",").map((value) => value.trim()) } : {}
|
|
4440
|
+
})
|
|
4441
|
+
)
|
|
4442
|
+
);
|
|
4443
|
+
registerMetric(telemetry, "latency", "Get latency telemetry", "latency");
|
|
4444
|
+
const logs = showHelpOnEmpty(telemetry.command("logs").description("Inspect request logs"));
|
|
4445
|
+
const logsList = addWindowOptions(
|
|
4446
|
+
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")
|
|
4447
|
+
);
|
|
4448
|
+
logsList.action(
|
|
4449
|
+
(opts) => runDetail(
|
|
4450
|
+
logsList,
|
|
4451
|
+
opts,
|
|
4452
|
+
(client) => client.telemetry.logs({
|
|
4453
|
+
...windowFrom(opts),
|
|
4454
|
+
pageSize: parsePositiveInteger2(opts.pageSize ?? "50", "--page-size"),
|
|
4455
|
+
...opts.statusCode ? { statusCode: parsePositiveInteger2(opts.statusCode, "--status-code") } : {},
|
|
4456
|
+
...opts.traceId ? { traceId: opts.traceId } : {}
|
|
4457
|
+
})
|
|
4458
|
+
)
|
|
4459
|
+
);
|
|
4460
|
+
registerMetric(telemetry, "requests", "Get request count", "requests");
|
|
4461
|
+
registerMetric(telemetry, "rescued-retries", "Get rescued retry telemetry", "rescuedRetries");
|
|
4462
|
+
registerMetric(telemetry, "tokens", "Get token usage", "tokens");
|
|
4463
|
+
registerMetric(telemetry, "user-trends", "Get user trends", "userTrends");
|
|
4464
|
+
registerMetric(telemetry, "users", "Get unique-user count", "users");
|
|
2952
4465
|
}
|
|
2953
4466
|
|
|
2954
4467
|
// src/cli/commands/aigateway.ts
|
|
@@ -2991,18 +4504,18 @@ function buildWorkspaceWriteRequest(opts) {
|
|
|
2991
4504
|
const defaults = parseJsonFlag(opts.defaults, "--defaults");
|
|
2992
4505
|
const metadata = parseJsonFlag(opts.metadata, "--metadata");
|
|
2993
4506
|
if (defaults !== void 0 || metadata !== void 0) {
|
|
2994
|
-
out.defaults = {
|
|
4507
|
+
out.defaults = GatewayDefaultsInputSchema.parse({
|
|
2995
4508
|
...typeof defaults === "object" && defaults !== null ? defaults : {},
|
|
2996
4509
|
...metadata !== void 0 ? { metadata } : {}
|
|
2997
|
-
};
|
|
4510
|
+
});
|
|
2998
4511
|
}
|
|
2999
4512
|
if (opts.users !== void 0) {
|
|
3000
4513
|
out.users = opts.users.split(",").map((u) => u.trim()).filter(Boolean);
|
|
3001
4514
|
}
|
|
3002
4515
|
const usage = parseJsonFlag(opts.usageLimits, "--usage-limits");
|
|
3003
|
-
if (usage !== void 0) out.usageLimits = usage;
|
|
4516
|
+
if (usage !== void 0) out.usageLimits = GatewayUsageLimitInputSchema.array().parse(usage);
|
|
3004
4517
|
const rate = parseJsonFlag(opts.rateLimits, "--rate-limits");
|
|
3005
|
-
if (rate !== void 0) out.rateLimits = rate;
|
|
4518
|
+
if (rate !== void 0) out.rateLimits = GatewayRateLimitInputSchema.array().parse(rate);
|
|
3006
4519
|
return out;
|
|
3007
4520
|
}
|
|
3008
4521
|
function scopeNameLooksUnrelated(name, scopeName) {
|
|
@@ -3011,19 +4524,20 @@ function scopeNameLooksUnrelated(name, scopeName) {
|
|
|
3011
4524
|
return !scopeName.toLowerCase().replace(/[^a-z0-9]/g, "").includes(nameToken);
|
|
3012
4525
|
}
|
|
3013
4526
|
function registerAiGatewayCommand(program) {
|
|
3014
|
-
const aigateway = program.command("aigateway").description("AI Gateway
|
|
3015
|
-
|
|
3016
|
-
workspace.command("
|
|
4527
|
+
const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
|
|
4528
|
+
registerAiGatewayInventory(aigateway);
|
|
4529
|
+
const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
|
|
4530
|
+
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(
|
|
3017
4531
|
"after",
|
|
3018
4532
|
examples(
|
|
3019
|
-
"airs aigateway
|
|
3020
|
-
"airs aigateway
|
|
3021
|
-
"airs aigateway
|
|
3022
|
-
"airs aigateway
|
|
4533
|
+
"airs aigateway workspaces list",
|
|
4534
|
+
"airs aigateway workspaces list --plane admin",
|
|
4535
|
+
"airs aigateway workspaces list --plane admin --status archived",
|
|
4536
|
+
"airs aigateway workspaces list --all --output json"
|
|
3023
4537
|
)
|
|
3024
4538
|
).action(async (opts) => {
|
|
3025
4539
|
try {
|
|
3026
|
-
const fmt = opts
|
|
4540
|
+
const fmt = await resolveOutput(workspaceList, opts);
|
|
3027
4541
|
if (fmt === "pretty") renderAiGatewayHeader();
|
|
3028
4542
|
const plane = parsePlane(opts.plane);
|
|
3029
4543
|
const status = parseStatus(opts.status);
|
|
@@ -3044,15 +4558,15 @@ function registerAiGatewayCommand(program) {
|
|
|
3044
4558
|
failWithGrantHint(err);
|
|
3045
4559
|
}
|
|
3046
4560
|
});
|
|
3047
|
-
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, json, yaml"
|
|
4561
|
+
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(
|
|
3048
4562
|
"after",
|
|
3049
4563
|
examples(
|
|
3050
|
-
"airs aigateway
|
|
3051
|
-
"airs aigateway
|
|
4564
|
+
"airs aigateway workspaces get ws-main-a-349e0e",
|
|
4565
|
+
"airs aigateway workspaces get 16f7e90d-382a-4e78-b577-1b01eb5f8297 --plane admin --output json"
|
|
3052
4566
|
)
|
|
3053
4567
|
).action(async (ref, opts) => {
|
|
3054
4568
|
try {
|
|
3055
|
-
const fmt = opts
|
|
4569
|
+
const fmt = await resolveOutput(workspaceGet, opts);
|
|
3056
4570
|
if (fmt === "pretty") renderAiGatewayHeader();
|
|
3057
4571
|
const plane = parsePlane(opts.plane);
|
|
3058
4572
|
const service = await createService();
|
|
@@ -3071,8 +4585,8 @@ function registerAiGatewayCommand(program) {
|
|
|
3071
4585
|
).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(
|
|
3072
4586
|
"after",
|
|
3073
4587
|
examples(
|
|
3074
|
-
"airs aigateway
|
|
3075
|
-
`airs aigateway
|
|
4588
|
+
"airs aigateway workspaces create --name Production --scope-name ws_production_bx7qw0",
|
|
4589
|
+
`airs aigateway workspaces create --name Production --scope-name ws_production_bx7qw0 --metadata '{"env":"production"}' --rate-limits '[{"type":"requests","unit":"rpm","value":100}]'`
|
|
3076
4590
|
)
|
|
3077
4591
|
).action(async (opts) => {
|
|
3078
4592
|
try {
|
|
@@ -3098,7 +4612,7 @@ function registerAiGatewayCommand(program) {
|
|
|
3098
4612
|
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(
|
|
3099
4613
|
"after",
|
|
3100
4614
|
examples(
|
|
3101
|
-
`airs aigateway
|
|
4615
|
+
`airs aigateway workspaces update ws-produc-985697 --description 'Production workloads, us-east'`
|
|
3102
4616
|
)
|
|
3103
4617
|
).action(async (ref, opts) => {
|
|
3104
4618
|
try {
|
|
@@ -3118,9 +4632,14 @@ function registerAiGatewayCommand(program) {
|
|
|
3118
4632
|
failWithGrantHint(err);
|
|
3119
4633
|
}
|
|
3120
4634
|
});
|
|
3121
|
-
|
|
4635
|
+
const archiveWorkspace = async (ref, opts, deprecated) => {
|
|
3122
4636
|
try {
|
|
3123
4637
|
renderAiGatewayHeader();
|
|
4638
|
+
if (deprecated) {
|
|
4639
|
+
ui.warn(
|
|
4640
|
+
"`aigateway workspace delete` is deprecated because this operation archives; use `aigateway workspaces archive`."
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
3124
4643
|
await confirmOrAbort(
|
|
3125
4644
|
`Archive workspace ${ref}? (soft delete \u2014 the row remains under --status archived)`,
|
|
3126
4645
|
Boolean(opts.force),
|
|
@@ -3130,13 +4649,16 @@ function registerAiGatewayCommand(program) {
|
|
|
3130
4649
|
await service.deleteWorkspace(ref);
|
|
3131
4650
|
ui.success(`Workspace archived: ${ref}`);
|
|
3132
4651
|
ui.status(
|
|
3133
|
-
"This is a soft delete \u2014 the workspace remains visible via `
|
|
4652
|
+
"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."
|
|
3134
4653
|
);
|
|
3135
4654
|
} catch (err) {
|
|
3136
4655
|
failWithGrantHint(err);
|
|
3137
4656
|
}
|
|
3138
|
-
}
|
|
3139
|
-
|
|
4657
|
+
};
|
|
4658
|
+
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));
|
|
4659
|
+
workspace.command("delete <ref>", { hidden: true }).description("Deprecated compatibility command for archive").option("--force", "Skip confirmation prompt").action((ref, opts) => archiveWorkspace(ref, opts, true));
|
|
4660
|
+
const telemetry = aigateway.command("telemetry").description("AI Gateway runtime telemetry (data plane)").action(() => telemetry.outputHelp());
|
|
4661
|
+
registerAiGatewayTelemetryReads(telemetry);
|
|
3140
4662
|
const cost = telemetry.command("cost").description(
|
|
3141
4663
|
"Total and per-day spend for a workspace (API reports cents; pretty output shows dollars)"
|
|
3142
4664
|
).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(
|
|
@@ -3282,8 +4804,8 @@ function registerCompletionCommand(program) {
|
|
|
3282
4804
|
}
|
|
3283
4805
|
|
|
3284
4806
|
// src/cli/commands/config.ts
|
|
3285
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3286
|
-
import { dirname } from "path";
|
|
4807
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
4808
|
+
import { dirname as dirname2 } from "path";
|
|
3287
4809
|
var CONFIG_KEYS = Object.keys(ConfigSchema.shape);
|
|
3288
4810
|
var SECRET_PATTERN = /key|secret|token|password/i;
|
|
3289
4811
|
function isKnownKey(key) {
|
|
@@ -3306,7 +4828,7 @@ function buildConfigRows(inspected, reveal) {
|
|
|
3306
4828
|
async function readConfigFileStrict(filePath) {
|
|
3307
4829
|
let raw;
|
|
3308
4830
|
try {
|
|
3309
|
-
raw = await
|
|
4831
|
+
raw = await readFile2(filePath, "utf-8");
|
|
3310
4832
|
} catch {
|
|
3311
4833
|
return { ok: true, data: {} };
|
|
3312
4834
|
}
|
|
@@ -3331,7 +4853,7 @@ async function setConfigValue(filePath, key, value) {
|
|
|
3331
4853
|
}
|
|
3332
4854
|
const coerced = result.data[key];
|
|
3333
4855
|
const next = { ...read.data, [key]: coerced };
|
|
3334
|
-
await mkdir(
|
|
4856
|
+
await mkdir(dirname2(filePath), { recursive: true });
|
|
3335
4857
|
await writeFile(filePath, `${JSON.stringify(next, null, 2)}
|
|
3336
4858
|
`, "utf-8");
|
|
3337
4859
|
return { ok: true, value: coerced };
|
|
@@ -3442,7 +4964,7 @@ function registerConfigCommand(program) {
|
|
|
3442
4964
|
|
|
3443
4965
|
// src/cli/commands/doctor.ts
|
|
3444
4966
|
import { randomUUID } from "crypto";
|
|
3445
|
-
import { readFile as
|
|
4967
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
3446
4968
|
import { init, Scanner } from "@cdot65/prisma-airs-sdk";
|
|
3447
4969
|
var DOCTOR_TIMEOUT_MS = 5e3;
|
|
3448
4970
|
var MIN_NODE_MAJOR = 20;
|
|
@@ -3462,7 +4984,7 @@ async function checkConfigFile(filePath) {
|
|
|
3462
4984
|
const name = "Config file";
|
|
3463
4985
|
let raw;
|
|
3464
4986
|
try {
|
|
3465
|
-
raw = await
|
|
4987
|
+
raw = await readFile3(filePath, "utf-8");
|
|
3466
4988
|
} catch {
|
|
3467
4989
|
return {
|
|
3468
4990
|
name,
|
|
@@ -3820,9 +5342,9 @@ async function createService2() {
|
|
|
3820
5342
|
function registerModelSecurityCommand(program) {
|
|
3821
5343
|
const ms = program.command("model-security").description("AI Model Security operations \u2014 groups, rules, scans");
|
|
3822
5344
|
const groups = ms.command("groups").description("Manage security groups");
|
|
3823
|
-
groups.command("list").description("List security groups").option("--source-types <types>", "Filter by source types (comma-separated)").option("--search <query>", "Search by name or UUID").option("--sort-field <field>", "Sort field (created_at, updated_at)").option("--sort-dir <dir>", "Sort direction (asc, desc)").option("--enabled-rules <uuids>", "Filter by enabled rule UUIDs (comma-separated)").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
5345
|
+
const groupsList = groups.command("list").description("List security groups").option("--source-types <types>", "Filter by source types (comma-separated)").option("--search <query>", "Search by name or UUID").option("--sort-field <field>", "Sort field (created_at, updated_at)").option("--sort-dir <dir>", "Sort direction (asc, desc)").option("--enabled-rules <uuids>", "Filter by enabled rule UUIDs (comma-separated)").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
|
|
3824
5346
|
try {
|
|
3825
|
-
const fmt = opts
|
|
5347
|
+
const fmt = await resolveOutput(groupsList, opts);
|
|
3826
5348
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3827
5349
|
const service = await createService2();
|
|
3828
5350
|
const listOptions = {
|
|
@@ -3840,9 +5362,9 @@ function registerModelSecurityCommand(program) {
|
|
|
3840
5362
|
fail(err);
|
|
3841
5363
|
}
|
|
3842
5364
|
});
|
|
3843
|
-
groups.command("get <uuid>").description("Get security group details").option("--output <format>", "Output format: pretty, json, yaml"
|
|
5365
|
+
const groupsGet = groups.command("get <uuid>").description("Get security group details").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (uuid, opts) => {
|
|
3844
5366
|
try {
|
|
3845
|
-
const fmt = opts
|
|
5367
|
+
const fmt = await resolveOutput(groupsGet, opts);
|
|
3846
5368
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3847
5369
|
const service = await createService2();
|
|
3848
5370
|
const group = await service.getGroup(uuid);
|
|
@@ -4070,9 +5592,9 @@ function registerModelSecurityCommand(program) {
|
|
|
4070
5592
|
}
|
|
4071
5593
|
});
|
|
4072
5594
|
const rules = ms.command("rules").description("Browse security rules");
|
|
4073
|
-
rules.command("list").description("List available security rules").option("--source-type <type>", "Filter by source type").option("--search <query>", "Search by name or UUID").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
5595
|
+
const rulesList = rules.command("list").description("List available security rules").option("--source-type <type>", "Filter by source type").option("--search <query>", "Search by name or UUID").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
|
|
4074
5596
|
try {
|
|
4075
|
-
const fmt = opts
|
|
5597
|
+
const fmt = await resolveOutput(rulesList, opts);
|
|
4076
5598
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4077
5599
|
const service = await createService2();
|
|
4078
5600
|
const listOptions = {
|
|
@@ -4099,7 +5621,7 @@ function registerModelSecurityCommand(program) {
|
|
|
4099
5621
|
}
|
|
4100
5622
|
});
|
|
4101
5623
|
const scans = ms.command("scans").description("Model security scan operations");
|
|
4102
|
-
scans.command("list").description("List model security scans").option("--eval-outcome <outcome>", "Filter by eval outcome").option("--source-type <type>", "Filter by source type").option("--scan-origin <origin>", "Filter by scan origin").option("--search <query>", "Search scans").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
5624
|
+
const scansList = scans.command("list").description("List model security scans").option("--eval-outcome <outcome>", "Filter by eval outcome").option("--source-type <type>", "Filter by source type").option("--scan-origin <origin>", "Filter by scan origin").option("--search <query>", "Search scans").option("--limit <n>", "Max results", "20").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
|
|
4103
5625
|
"after",
|
|
4104
5626
|
examples(
|
|
4105
5627
|
"airs model-security scans list",
|
|
@@ -4108,7 +5630,7 @@ function registerModelSecurityCommand(program) {
|
|
|
4108
5630
|
)
|
|
4109
5631
|
).action(async (opts) => {
|
|
4110
5632
|
try {
|
|
4111
|
-
const fmt = opts
|
|
5633
|
+
const fmt = await resolveOutput(scansList, opts);
|
|
4112
5634
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4113
5635
|
const service = await createService2();
|
|
4114
5636
|
const listOptions = {
|
|
@@ -4207,9 +5729,9 @@ function registerModelSecurityCommand(program) {
|
|
|
4207
5729
|
}
|
|
4208
5730
|
});
|
|
4209
5731
|
const models = ms.command("models").description("Browse the scanned model catalog (read-only)");
|
|
4210
|
-
models.command("list").description("List models in the catalog").option("--search <text>", "Filter by search text").option("--search-query <text>", "Filter by model UUID or name").option("--sort-field <field>", "Sort field: created_at, updated_at").option("--sort-order <order>", "Sort order: asc, desc").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
5732
|
+
const modelsList = models.command("list").description("List models in the catalog").option("--search <text>", "Filter by search text").option("--search-query <text>", "Filter by model UUID or name").option("--sort-field <field>", "Sort field: created_at, updated_at").option("--sort-order <order>", "Sort order: asc, desc").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText("after", examples("airs model-security models list")).action(async (opts) => {
|
|
4211
5733
|
try {
|
|
4212
|
-
const fmt = opts
|
|
5734
|
+
const fmt = await resolveOutput(modelsList, opts);
|
|
4213
5735
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4214
5736
|
const service = await createService2();
|
|
4215
5737
|
const listOptions = {
|
|
@@ -4226,9 +5748,9 @@ function registerModelSecurityCommand(program) {
|
|
|
4226
5748
|
fail(err);
|
|
4227
5749
|
}
|
|
4228
5750
|
});
|
|
4229
|
-
models.command("get <uuid>").description("Get a model by UUID").option("--output <format>", "Output format: pretty, json, yaml"
|
|
5751
|
+
const modelsGet = models.command("get <uuid>").description("Get a model by UUID").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (uuid, opts) => {
|
|
4230
5752
|
try {
|
|
4231
|
-
const fmt = opts
|
|
5753
|
+
const fmt = await resolveOutput(modelsGet, opts);
|
|
4232
5754
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4233
5755
|
const service = await createService2();
|
|
4234
5756
|
const model = await service.getModel(uuid);
|
|
@@ -4237,9 +5759,9 @@ function registerModelSecurityCommand(program) {
|
|
|
4237
5759
|
fail(err);
|
|
4238
5760
|
}
|
|
4239
5761
|
});
|
|
4240
|
-
models.command("versions <modelUuid>").description("List versions of a model").option("--sort-order <order>", "Sort order: asc, desc").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
5762
|
+
const modelVersions = models.command("versions <modelUuid>").description("List versions of a model").option("--sort-order <order>", "Sort order: asc, desc").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (modelUuid, opts) => {
|
|
4241
5763
|
try {
|
|
4242
|
-
const fmt = opts
|
|
5764
|
+
const fmt = await resolveOutput(modelVersions, opts);
|
|
4243
5765
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4244
5766
|
const service = await createService2();
|
|
4245
5767
|
const result = await service.listModelVersions(modelUuid, {
|
|
@@ -4252,9 +5774,9 @@ function registerModelSecurityCommand(program) {
|
|
|
4252
5774
|
fail(err);
|
|
4253
5775
|
}
|
|
4254
5776
|
});
|
|
4255
|
-
models.command("version <uuid>").description("Get a model version by UUID").option("--output <format>", "Output format: pretty, json, yaml"
|
|
5777
|
+
const modelVersion = models.command("version <uuid>").description("Get a model version by UUID").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (uuid, opts) => {
|
|
4256
5778
|
try {
|
|
4257
|
-
const fmt = opts
|
|
5779
|
+
const fmt = await resolveOutput(modelVersion, opts);
|
|
4258
5780
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4259
5781
|
const service = await createService2();
|
|
4260
5782
|
const version = await service.getModelVersion(uuid);
|
|
@@ -4263,9 +5785,9 @@ function registerModelSecurityCommand(program) {
|
|
|
4263
5785
|
fail(err);
|
|
4264
5786
|
}
|
|
4265
5787
|
});
|
|
4266
|
-
models.command("files <modelVersionUuid>").description("List files in a model version").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
5788
|
+
const modelFiles = models.command("files <modelVersionUuid>").description("List files in a model version").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (modelVersionUuid, opts) => {
|
|
4267
5789
|
try {
|
|
4268
|
-
const fmt = opts
|
|
5790
|
+
const fmt = await resolveOutput(modelFiles, opts);
|
|
4269
5791
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4270
5792
|
const service = await createService2();
|
|
4271
5793
|
const result = await service.listModelVersionFiles(modelVersionUuid, {
|
|
@@ -5624,8 +7146,8 @@ function registerRedteamCommand(program) {
|
|
|
5624
7146
|
// src/cli/commands/runtime.ts
|
|
5625
7147
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
5626
7148
|
import * as fs5 from "fs";
|
|
5627
|
-
import { readFile as
|
|
5628
|
-
import { basename as basename3, dirname as
|
|
7149
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
7150
|
+
import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
|
|
5629
7151
|
import chalk11 from "chalk";
|
|
5630
7152
|
|
|
5631
7153
|
// src/cli/builders/profile-builder.ts
|
|
@@ -6303,7 +7825,7 @@ var topicsView = {
|
|
|
6303
7825
|
};
|
|
6304
7826
|
|
|
6305
7827
|
// src/cli/commands/dlp/dictionaries.ts
|
|
6306
|
-
import { readFile as
|
|
7828
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
6307
7829
|
import { basename as basename2 } from "path";
|
|
6308
7830
|
|
|
6309
7831
|
// src/airs/dlp/dictionaries.ts
|
|
@@ -6342,7 +7864,7 @@ var SdkDictionariesService = class {
|
|
|
6342
7864
|
};
|
|
6343
7865
|
|
|
6344
7866
|
// src/cli/commands/dlp/patch.ts
|
|
6345
|
-
import { readFile as
|
|
7867
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
6346
7868
|
function buildMergePatch(opts) {
|
|
6347
7869
|
const out = {};
|
|
6348
7870
|
for (const entry of opts.set ?? []) {
|
|
@@ -6384,7 +7906,7 @@ function coerceValue(raw) {
|
|
|
6384
7906
|
async function parseBody(opts) {
|
|
6385
7907
|
let raw;
|
|
6386
7908
|
if (opts.bodyFile) {
|
|
6387
|
-
raw = await
|
|
7909
|
+
raw = await readFile6(opts.bodyFile, "utf-8");
|
|
6388
7910
|
} else if (opts.body === "-") {
|
|
6389
7911
|
const chunks = [];
|
|
6390
7912
|
for await (const chunk of opts.stdin ?? process.stdin) {
|
|
@@ -6405,7 +7927,7 @@ async function parseBody(opts) {
|
|
|
6405
7927
|
// src/cli/commands/dlp/dictionaries.ts
|
|
6406
7928
|
async function buildMetadata(opts) {
|
|
6407
7929
|
if (opts.metadataFile) {
|
|
6408
|
-
return JSON.parse(await
|
|
7930
|
+
return JSON.parse(await readFile7(opts.metadataFile, "utf-8"));
|
|
6409
7931
|
}
|
|
6410
7932
|
if (!opts.name || !opts.category || !opts.region || !opts.file) {
|
|
6411
7933
|
throw new Error("--name, --category, --region, and --file are required");
|
|
@@ -6450,7 +7972,7 @@ function register(dlp) {
|
|
|
6450
7972
|
try {
|
|
6451
7973
|
const metadata = await buildMetadata(opts);
|
|
6452
7974
|
if (!opts.file) throw new Error("--file is required (multipart upload)");
|
|
6453
|
-
const file = await
|
|
7975
|
+
const file = await readFile7(opts.file);
|
|
6454
7976
|
const r = await new SdkDictionariesService().create({
|
|
6455
7977
|
metadata,
|
|
6456
7978
|
file,
|
|
@@ -6478,7 +8000,7 @@ function register(dlp) {
|
|
|
6478
8000
|
try {
|
|
6479
8001
|
const metadata = await buildMetadata(opts);
|
|
6480
8002
|
if (!opts.file) throw new Error("--file is required (multipart upload)");
|
|
6481
|
-
const file = await
|
|
8003
|
+
const file = await readFile7(opts.file);
|
|
6482
8004
|
const r = await new SdkDictionariesService().replace(id, {
|
|
6483
8005
|
metadata,
|
|
6484
8006
|
file,
|
|
@@ -7298,7 +8820,7 @@ function registerCreateCommand(parent) {
|
|
|
7298
8820
|
}
|
|
7299
8821
|
|
|
7300
8822
|
// src/cli/commands/topics-eval.ts
|
|
7301
|
-
import { readFile as
|
|
8823
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
7302
8824
|
|
|
7303
8825
|
// src/core/prompt-loader.ts
|
|
7304
8826
|
function parseCsvLine(line) {
|
|
@@ -7441,7 +8963,7 @@ function registerEvalCommand(parent) {
|
|
|
7441
8963
|
resolveDeprecatedAliases(cmd, opts);
|
|
7442
8964
|
try {
|
|
7443
8965
|
const config = await loadConfig();
|
|
7444
|
-
const csvContent = await
|
|
8966
|
+
const csvContent = await readFile8(opts.prompts, "utf-8");
|
|
7445
8967
|
const { cases, intent } = loadPrompts(csvContent, (msg) => ui.status(`Warning: ${msg}`));
|
|
7446
8968
|
if (!config.airsApiKey && !config.airsApiToken) {
|
|
7447
8969
|
fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
|
|
@@ -7612,7 +9134,7 @@ function completedBulkResults(state) {
|
|
|
7612
9134
|
return state.items.flatMap((item) => item.result ? [item.result] : []).sort((left, right) => left.index - right.index);
|
|
7613
9135
|
}
|
|
7614
9136
|
async function writeBulkResults(outputPath, results) {
|
|
7615
|
-
await fs5.promises.mkdir(
|
|
9137
|
+
await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
|
|
7616
9138
|
const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID4()}`;
|
|
7617
9139
|
try {
|
|
7618
9140
|
await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
|
|
@@ -7630,10 +9152,10 @@ function isDefiniteSubmissionRejection(error) {
|
|
|
7630
9152
|
const metadata = error;
|
|
7631
9153
|
return metadata?.failureKind === "http" && typeof metadata.statusCode === "number" && metadata.statusCode >= 400 && metadata.statusCode < 500;
|
|
7632
9154
|
}
|
|
7633
|
-
function
|
|
9155
|
+
function parsePositiveInteger3(value, optionName2) {
|
|
7634
9156
|
const parsed = Number(value);
|
|
7635
9157
|
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(parsed)) {
|
|
7636
|
-
usageError(`${
|
|
9158
|
+
usageError(`${optionName2} must be a positive integer`);
|
|
7637
9159
|
}
|
|
7638
9160
|
return parsed;
|
|
7639
9161
|
}
|
|
@@ -7736,14 +9258,14 @@ function registerRuntimeCommand(program) {
|
|
|
7736
9258
|
if (!opts.file) {
|
|
7737
9259
|
usageError("--file <file> is required");
|
|
7738
9260
|
}
|
|
7739
|
-
const batchSize =
|
|
9261
|
+
const batchSize = parsePositiveInteger3(opts.batchSize, "--batch-size");
|
|
7740
9262
|
let releaseJobLock;
|
|
7741
9263
|
try {
|
|
7742
9264
|
const config = await loadConfig({});
|
|
7743
9265
|
if (!config.airsApiKey && !config.airsApiToken) {
|
|
7744
9266
|
fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
|
|
7745
9267
|
}
|
|
7746
|
-
const raw = await
|
|
9268
|
+
const raw = await readFile9(opts.file, "utf-8");
|
|
7747
9269
|
const prompts = parseInputFile(raw, opts.file);
|
|
7748
9270
|
if (prompts.length === 0) {
|
|
7749
9271
|
usageError("No prompts found in input file");
|
|
@@ -7753,7 +9275,7 @@ function registerRuntimeCommand(program) {
|
|
|
7753
9275
|
opts.outputFile ?? `${opts.profile.replace(/\s+/g, "-")}-bulk-scan.csv`
|
|
7754
9276
|
);
|
|
7755
9277
|
const stateDir = resolvePath(
|
|
7756
|
-
basename3(config.dataDir) === "runs" ?
|
|
9278
|
+
basename3(config.dataDir) === "runs" ? join3(dirname3(config.dataDir), "bulk-scans") : join3(config.dataDir, "bulk-scans")
|
|
7757
9279
|
);
|
|
7758
9280
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7759
9281
|
const state = {
|
|
@@ -7912,14 +9434,14 @@ function registerRuntimeCommand(program) {
|
|
|
7912
9434
|
fail(err);
|
|
7913
9435
|
}
|
|
7914
9436
|
});
|
|
7915
|
-
customerApps.command("consumption").argument(
|
|
9437
|
+
const customerAppsConsumption = customerApps.command("consumption").argument(
|
|
7916
9438
|
"[appName]",
|
|
7917
9439
|
"Dashboard application name \u2014 the literal scan-payload metadata.app_name, as shown in the SCM AI Applications view (may differ from the SCM-registered customer-app name). Omit to report every dashboard bucket."
|
|
7918
9440
|
).description(
|
|
7919
9441
|
"Show per-app token consumption + violation breakdown (SCM dashboard). Omit appName to scan all apps."
|
|
7920
|
-
).option("--time-interval <n>", "Window in days: 7, 30, or 60", "30").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
9442
|
+
).option("--time-interval <n>", "Window in days: 7, 30, or 60", "30").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (appName, opts) => {
|
|
7921
9443
|
try {
|
|
7922
|
-
const fmt = opts
|
|
9444
|
+
const fmt = await resolveOutput(customerAppsConsumption, opts);
|
|
7923
9445
|
const interval = Number.parseInt(opts.timeInterval, 10);
|
|
7924
9446
|
if (interval !== 7 && interval !== 30 && interval !== 60) {
|
|
7925
9447
|
usageError("--time-interval must be 7, 30, or 60 (the API rejects other values)");
|
|
@@ -7953,9 +9475,9 @@ function registerRuntimeCommand(program) {
|
|
|
7953
9475
|
}
|
|
7954
9476
|
});
|
|
7955
9477
|
const deploymentProfiles = runtime.command("deployment-profiles").description("List AIRS deployment profiles");
|
|
7956
|
-
deploymentProfiles.command("list").description("List deployment profiles").option("--unactivated", "Include unactivated profiles").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
9478
|
+
const deploymentProfilesList = deploymentProfiles.command("list").description("List deployment profiles").option("--unactivated", "Include unactivated profiles").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
|
|
7957
9479
|
try {
|
|
7958
|
-
const fmt = opts
|
|
9480
|
+
const fmt = await resolveOutput(deploymentProfilesList, opts);
|
|
7959
9481
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
7960
9482
|
const service = await createMgmtService();
|
|
7961
9483
|
const profiles2 = await service.listDeploymentProfiles({
|
|
@@ -8192,7 +9714,7 @@ function registerRuntimeCommand(program) {
|
|
|
8192
9714
|
);
|
|
8193
9715
|
const outputPath = resolvePath(opts.outputFile ?? state.outputFile);
|
|
8194
9716
|
state.outputFile = outputPath;
|
|
8195
|
-
await saveBulkScanState(state,
|
|
9717
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8196
9718
|
const pollSubmitted = async (items) => {
|
|
8197
9719
|
for (const batch of submittedBatches(items)) {
|
|
8198
9720
|
const results2 = await service.pollBatch(batch, void 0, {
|
|
@@ -8201,12 +9723,12 @@ function registerRuntimeCommand(program) {
|
|
|
8201
9723
|
},
|
|
8202
9724
|
onProgress: async (progress) => {
|
|
8203
9725
|
recordBulkResults(state, progress);
|
|
8204
|
-
await saveBulkScanState(state,
|
|
9726
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8205
9727
|
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
8206
9728
|
}
|
|
8207
9729
|
});
|
|
8208
9730
|
recordBulkResults(state, results2);
|
|
8209
|
-
await saveBulkScanState(state,
|
|
9731
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8210
9732
|
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
8211
9733
|
}
|
|
8212
9734
|
};
|
|
@@ -8230,7 +9752,7 @@ function registerRuntimeCommand(program) {
|
|
|
8230
9752
|
for (let start = 0; start < pendingItems.length; start += SDK_ASYNC_BATCH_SIZE) {
|
|
8231
9753
|
const chunk = pendingItems.slice(start, start + SDK_ASYNC_BATCH_SIZE);
|
|
8232
9754
|
for (const item of chunk) item.status = "submitting";
|
|
8233
|
-
await saveBulkScanState(state,
|
|
9755
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8234
9756
|
try {
|
|
8235
9757
|
const batch = await service.submitBatch(state.profile, chunk, state.sessionId, {
|
|
8236
9758
|
onRetry: (attempt, delayMs) => {
|
|
@@ -8246,19 +9768,19 @@ function registerRuntimeCommand(program) {
|
|
|
8246
9768
|
item.receiptReportId = batch.reportId;
|
|
8247
9769
|
item.error = void 0;
|
|
8248
9770
|
}
|
|
8249
|
-
await saveBulkScanState(state,
|
|
9771
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8250
9772
|
} catch (error) {
|
|
8251
9773
|
for (const item of chunk) {
|
|
8252
9774
|
item.status = isDefiniteSubmissionRejection(error) ? "pending" : "ambiguous";
|
|
8253
9775
|
item.error = error instanceof Error ? error.message : String(error);
|
|
8254
9776
|
}
|
|
8255
|
-
await saveBulkScanState(state,
|
|
9777
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8256
9778
|
throw error;
|
|
8257
9779
|
}
|
|
8258
9780
|
}
|
|
8259
9781
|
await pollSubmitted(logicalBatch);
|
|
8260
9782
|
}
|
|
8261
|
-
await saveBulkScanState(state,
|
|
9783
|
+
await saveBulkScanState(state, dirname3(stateFile), stateFile);
|
|
8262
9784
|
const results = completedBulkResults(state);
|
|
8263
9785
|
await writeBulkResults(outputPath, results);
|
|
8264
9786
|
const blocked = results.filter((r) => r.action === "block").length;
|
|
@@ -8307,12 +9829,12 @@ function registerRuntimeCommand(program) {
|
|
|
8307
9829
|
}
|
|
8308
9830
|
});
|
|
8309
9831
|
const scanLogs = runtime.command("scan-logs").description("Query AIRS scan logs");
|
|
8310
|
-
const scanLogsQuery = scanLogs.command("query").description("Query scan logs").requiredOption("--interval <n>", "Time interval").requiredOption("--unit <unit>", "Time unit (hours)").option("--filter <filter>", "Filter: all, benign, threat", "all").option("--limit <n>", "Max results per page (API page size)", "50").option("--offset <n>", "Starting offset \u2014 rounds down to a page boundary", "0").option("--output <format>", "Output format: pretty, table, csv, json, yaml"
|
|
9832
|
+
const scanLogsQuery = scanLogs.command("query").description("Query scan logs").requiredOption("--interval <n>", "Time interval").requiredOption("--unit <unit>", "Time unit (hours)").option("--filter <filter>", "Filter: all, benign, threat", "all").option("--limit <n>", "Max results per page (API page size)", "50").option("--offset <n>", "Starting offset \u2014 rounds down to a page boundary", "0").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml");
|
|
8311
9833
|
registerPageAliases(scanLogsQuery, { sizeFlag: "--page-size", sizeKey: "pageSize" });
|
|
8312
9834
|
scanLogsQuery.action(async (opts) => {
|
|
8313
9835
|
try {
|
|
8314
9836
|
const { page, size } = resolvePageParams(scanLogsQuery, opts, { indexBase: 1 });
|
|
8315
|
-
const fmt = opts
|
|
9837
|
+
const fmt = await resolveOutput(scanLogsQuery, opts);
|
|
8316
9838
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
8317
9839
|
const service = await createMgmtService();
|
|
8318
9840
|
const result = await service.queryScanLogs({
|
|
@@ -8411,190 +9933,34 @@ function registerRuntimeCommand(program) {
|
|
|
8411
9933
|
registerDlpCommands(runtime);
|
|
8412
9934
|
}
|
|
8413
9935
|
|
|
8414
|
-
// src/cli/debug-logger.ts
|
|
8415
|
-
import {
|
|
8416
|
-
appendFileSync,
|
|
8417
|
-
mkdirSync,
|
|
8418
|
-
readdirSync,
|
|
8419
|
-
statSync,
|
|
8420
|
-
unlinkSync,
|
|
8421
|
-
writeFileSync as writeFileSync2
|
|
8422
|
-
} from "fs";
|
|
8423
|
-
import { dirname as dirname3, join as join3 } from "path";
|
|
8424
|
-
var AIRS_DOMAINS = [
|
|
8425
|
-
"api.sase.paloaltonetworks.com",
|
|
8426
|
-
"service.api.aisecurity.paloaltonetworks.com",
|
|
8427
|
-
"auth.apps.paloaltonetworks.com",
|
|
8428
|
-
"api.dlp.paloaltonetworks.com"
|
|
8429
|
-
];
|
|
8430
|
-
function isAirsUrl(url) {
|
|
8431
|
-
try {
|
|
8432
|
-
const parsed = new URL(url);
|
|
8433
|
-
return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
|
|
8434
|
-
} catch {
|
|
8435
|
-
return false;
|
|
8436
|
-
}
|
|
8437
|
-
}
|
|
8438
|
-
var MASK = "***";
|
|
8439
|
-
var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key/i;
|
|
8440
|
-
function isSensitiveKey(key) {
|
|
8441
|
-
return SENSITIVE_KEY_PATTERN.test(key);
|
|
8442
|
-
}
|
|
8443
|
-
function redactHeaders(headers) {
|
|
8444
|
-
const out = {};
|
|
8445
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
8446
|
-
out[k] = isSensitiveKey(k) ? MASK : v;
|
|
8447
|
-
}
|
|
8448
|
-
return out;
|
|
8449
|
-
}
|
|
8450
|
-
function redactDeep(value) {
|
|
8451
|
-
if (Array.isArray(value)) {
|
|
8452
|
-
return value.map(redactDeep);
|
|
8453
|
-
}
|
|
8454
|
-
if (value !== null && typeof value === "object") {
|
|
8455
|
-
const out = {};
|
|
8456
|
-
for (const [k, v] of Object.entries(value)) {
|
|
8457
|
-
out[k] = isSensitiveKey(k) ? MASK : redactDeep(v);
|
|
8458
|
-
}
|
|
8459
|
-
return out;
|
|
8460
|
-
}
|
|
8461
|
-
return value;
|
|
8462
|
-
}
|
|
8463
|
-
function redactUrl(url) {
|
|
8464
|
-
try {
|
|
8465
|
-
const parsed = new URL(url);
|
|
8466
|
-
let touched = false;
|
|
8467
|
-
for (const key of parsed.searchParams.keys()) {
|
|
8468
|
-
if (isSensitiveKey(key)) {
|
|
8469
|
-
parsed.searchParams.set(key, MASK);
|
|
8470
|
-
touched = true;
|
|
8471
|
-
}
|
|
8472
|
-
}
|
|
8473
|
-
return touched ? parsed.toString() : url;
|
|
8474
|
-
} catch {
|
|
8475
|
-
return url;
|
|
8476
|
-
}
|
|
8477
|
-
}
|
|
8478
|
-
function pruneDebugLogs(dir, keep) {
|
|
8479
|
-
let files;
|
|
8480
|
-
try {
|
|
8481
|
-
files = readdirSync(dir).filter((f) => f.startsWith("debug-api-") && f.endsWith(".jsonl"));
|
|
8482
|
-
} catch {
|
|
8483
|
-
return;
|
|
8484
|
-
}
|
|
8485
|
-
const byAge = files.map((f) => {
|
|
8486
|
-
const path3 = join3(dir, f);
|
|
8487
|
-
try {
|
|
8488
|
-
return { path: path3, mtime: statSync(path3).mtimeMs };
|
|
8489
|
-
} catch {
|
|
8490
|
-
return null;
|
|
8491
|
-
}
|
|
8492
|
-
}).filter((e) => e !== null).sort((a, b) => b.mtime - a.mtime);
|
|
8493
|
-
for (const { path: path3 } of byAge.slice(keep)) {
|
|
8494
|
-
try {
|
|
8495
|
-
unlinkSync(path3);
|
|
8496
|
-
} catch {
|
|
8497
|
-
}
|
|
8498
|
-
}
|
|
8499
|
-
}
|
|
8500
|
-
function headersToRecord(headers) {
|
|
8501
|
-
if (!headers) return {};
|
|
8502
|
-
if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
|
|
8503
|
-
const out = {};
|
|
8504
|
-
headers.forEach((v, k) => {
|
|
8505
|
-
out[k] = v;
|
|
8506
|
-
});
|
|
8507
|
-
return out;
|
|
8508
|
-
}
|
|
8509
|
-
if (Array.isArray(headers)) {
|
|
8510
|
-
return Object.fromEntries(headers);
|
|
8511
|
-
}
|
|
8512
|
-
return headers;
|
|
8513
|
-
}
|
|
8514
|
-
var KEEP_DEBUG_LOGS = 10;
|
|
8515
|
-
function installDebugLogger(logPath) {
|
|
8516
|
-
mkdirSync(dirname3(logPath), { recursive: true });
|
|
8517
|
-
writeFileSync2(logPath, "", "utf-8");
|
|
8518
|
-
pruneDebugLogs(dirname3(logPath), KEEP_DEBUG_LOGS);
|
|
8519
|
-
const originalFetch = globalThis.fetch;
|
|
8520
|
-
globalThis.fetch = async function debugFetch(input, init2) {
|
|
8521
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
8522
|
-
if (!isAirsUrl(url)) {
|
|
8523
|
-
return originalFetch(input, init2);
|
|
8524
|
-
}
|
|
8525
|
-
const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
|
|
8526
|
-
const reqHeaders = redactHeaders(headersToRecord(init2?.headers));
|
|
8527
|
-
const loggedUrl = redactUrl(url);
|
|
8528
|
-
let reqBody;
|
|
8529
|
-
if (init2?.body) {
|
|
8530
|
-
try {
|
|
8531
|
-
reqBody = redactDeep(JSON.parse(String(init2.body)));
|
|
8532
|
-
} catch {
|
|
8533
|
-
reqBody = String(init2.body);
|
|
8534
|
-
}
|
|
8535
|
-
}
|
|
8536
|
-
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
8537
|
-
const startMs = Date.now();
|
|
8538
|
-
let response;
|
|
8539
|
-
let resBody;
|
|
8540
|
-
let error;
|
|
8541
|
-
try {
|
|
8542
|
-
response = await originalFetch(input, init2);
|
|
8543
|
-
} catch (err) {
|
|
8544
|
-
error = err instanceof Error ? err.message : String(err);
|
|
8545
|
-
const entry2 = JSON.stringify({
|
|
8546
|
-
timestamp: ts2,
|
|
8547
|
-
durationMs: Date.now() - startMs,
|
|
8548
|
-
request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
|
|
8549
|
-
error
|
|
8550
|
-
});
|
|
8551
|
-
appendFileSync(logPath, `${entry2}
|
|
8552
|
-
`);
|
|
8553
|
-
throw err;
|
|
8554
|
-
}
|
|
8555
|
-
const durationMs = Date.now() - startMs;
|
|
8556
|
-
const resHeaders = {};
|
|
8557
|
-
response.headers.forEach((v, k) => {
|
|
8558
|
-
resHeaders[k] = v;
|
|
8559
|
-
});
|
|
8560
|
-
const clone = response.clone();
|
|
8561
|
-
try {
|
|
8562
|
-
const text = await clone.text();
|
|
8563
|
-
try {
|
|
8564
|
-
resBody = redactDeep(JSON.parse(text));
|
|
8565
|
-
} catch {
|
|
8566
|
-
resBody = text;
|
|
8567
|
-
}
|
|
8568
|
-
} catch {
|
|
8569
|
-
resBody = "<unreadable>";
|
|
8570
|
-
}
|
|
8571
|
-
const entry = JSON.stringify({
|
|
8572
|
-
timestamp: ts2,
|
|
8573
|
-
durationMs,
|
|
8574
|
-
request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
|
|
8575
|
-
response: {
|
|
8576
|
-
status: response.status,
|
|
8577
|
-
statusText: response.statusText,
|
|
8578
|
-
headers: redactHeaders(resHeaders),
|
|
8579
|
-
body: resBody
|
|
8580
|
-
}
|
|
8581
|
-
});
|
|
8582
|
-
appendFileSync(logPath, `${entry}
|
|
8583
|
-
`);
|
|
8584
|
-
return response;
|
|
8585
|
-
};
|
|
8586
|
-
return {
|
|
8587
|
-
teardown() {
|
|
8588
|
-
globalThis.fetch = originalFetch;
|
|
8589
|
-
}
|
|
8590
|
-
};
|
|
8591
|
-
}
|
|
8592
|
-
|
|
8593
9936
|
// src/cli/program.ts
|
|
9937
|
+
var READ_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
9938
|
+
"categories",
|
|
9939
|
+
"consumption",
|
|
9940
|
+
"evaluation",
|
|
9941
|
+
"evaluations",
|
|
9942
|
+
"files",
|
|
9943
|
+
"get",
|
|
9944
|
+
"languages",
|
|
9945
|
+
"list",
|
|
9946
|
+
"pypi-auth",
|
|
9947
|
+
"query",
|
|
9948
|
+
"registry-credentials",
|
|
9949
|
+
"report",
|
|
9950
|
+
"stats",
|
|
9951
|
+
"status",
|
|
9952
|
+
"values",
|
|
9953
|
+
"version",
|
|
9954
|
+
"versions",
|
|
9955
|
+
"violation",
|
|
9956
|
+
"violations"
|
|
9957
|
+
]);
|
|
8594
9958
|
function applyListDeleteAliases(cmd) {
|
|
8595
9959
|
for (const sub of cmd.commands) {
|
|
8596
9960
|
if (sub.name() === "list" && !sub.aliases().includes("ls")) sub.alias("ls");
|
|
8597
|
-
|
|
9961
|
+
const isHiddenCompatibilityCommand = sub.name() === "delete" && Boolean(sub._hidden);
|
|
9962
|
+
if (sub.name() === "delete" && !isHiddenCompatibilityCommand && !sub.aliases().includes("rm"))
|
|
9963
|
+
sub.alias("rm");
|
|
8598
9964
|
applyListDeleteAliases(sub);
|
|
8599
9965
|
}
|
|
8600
9966
|
}
|
|
@@ -8629,7 +9995,7 @@ function buildProgram() {
|
|
|
8629
9995
|
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
8630
9996
|
const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
|
|
8631
9997
|
setQuiet(Boolean(root.quiet));
|
|
8632
|
-
if (
|
|
9998
|
+
if (READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
|
|
8633
9999
|
try {
|
|
8634
10000
|
const format = await resolveOutput(actionCommand, actionCommand.opts());
|
|
8635
10001
|
actionCommand.setOptionValueWithSource("output", format, "implied");
|