@cdot65/prisma-airs-cli 3.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/dist/{chunk-TTBN7YHC.js → chunk-W5YDJS7H.js} +264 -35
- package/dist/cli/index.js +1923 -690
- package/dist/index.d.ts +189 -5
- package/dist/index.js +1 -1
- package/package.json +15 -3
package/dist/cli/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
sanitizeFilename,
|
|
21
21
|
validateTopic,
|
|
22
22
|
writeBackupFile
|
|
23
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-W5YDJS7H.js";
|
|
24
24
|
|
|
25
25
|
// src/cli/index.ts
|
|
26
26
|
import "dotenv/config";
|
|
@@ -45,20 +45,221 @@ 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/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
// src/airs/aigateway.ts
|
|
49
|
+
import {
|
|
50
|
+
AIGatewayClient
|
|
51
|
+
} from "@cdot65/prisma-airs-sdk";
|
|
52
|
+
function toLimitArray(value) {
|
|
53
|
+
if (Array.isArray(value)) return value;
|
|
54
|
+
if (value !== null && typeof value === "object") return [value];
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
function normalizeWorkspace(raw) {
|
|
58
|
+
return {
|
|
59
|
+
id: raw.id,
|
|
60
|
+
slug: raw.slug,
|
|
61
|
+
name: raw.name,
|
|
62
|
+
icon: raw.icon,
|
|
63
|
+
description: raw.description,
|
|
64
|
+
createdAt: raw.created_at,
|
|
65
|
+
lastUpdatedAt: raw.last_updated_at,
|
|
66
|
+
isDefault: Boolean(raw.is_default),
|
|
67
|
+
status: raw.status,
|
|
68
|
+
scopeName: raw.scope_name
|
|
69
|
+
};
|
|
54
70
|
}
|
|
71
|
+
function normalizeWorkspaceDetail(raw) {
|
|
72
|
+
return {
|
|
73
|
+
...normalizeWorkspace(raw),
|
|
74
|
+
defaults: raw.defaults,
|
|
75
|
+
usageLimits: toLimitArray(raw.usage_limits),
|
|
76
|
+
rateLimits: toLimitArray(raw.rate_limits),
|
|
77
|
+
securitySettings: raw.security_settings,
|
|
78
|
+
dataPlaneSecuritySettings: raw.data_plane_security_settings,
|
|
79
|
+
settings: raw.settings
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function aiGatewayGrantHint(err) {
|
|
83
|
+
const status = err?.status ?? err?.statusCode;
|
|
84
|
+
if (status !== 403) return void 0;
|
|
85
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
86
|
+
const grant = message.includes("AB03") ? "the service account is missing a workspace-scope grant (data plane, /ai_gw/v2)" : "the service account is missing a tenant-root admin grant (admin plane, /ai_gw/admin/v2)";
|
|
87
|
+
return `${grant}. SCM Access Management edits the existing role row by default \u2014 use "Add Role" so the account ends up with both role rows, not one row moved.`;
|
|
88
|
+
}
|
|
89
|
+
var SdkAiGatewayService = class {
|
|
90
|
+
client;
|
|
91
|
+
constructor(opts) {
|
|
92
|
+
this.client = new AIGatewayClient(opts);
|
|
93
|
+
}
|
|
94
|
+
async listWorkspaces(options) {
|
|
95
|
+
const response = await this.client.workspaces.list(options);
|
|
96
|
+
return response.data.map(normalizeWorkspace);
|
|
97
|
+
}
|
|
98
|
+
async listAllWorkspaces() {
|
|
99
|
+
const [active, archived] = await Promise.all([
|
|
100
|
+
this.client.workspaces.list({ plane: "admin" }),
|
|
101
|
+
this.client.workspaces.list({ plane: "admin", status: "archived" })
|
|
102
|
+
]);
|
|
103
|
+
return [
|
|
104
|
+
...active.data,
|
|
105
|
+
...archived.data
|
|
106
|
+
].map(normalizeWorkspace);
|
|
107
|
+
}
|
|
108
|
+
async getWorkspace(workspaceRef, options) {
|
|
109
|
+
try {
|
|
110
|
+
const raw = await this.client.workspaces.get(workspaceRef, options);
|
|
111
|
+
return normalizeWorkspaceDetail(raw);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
const status = err.statusCode;
|
|
114
|
+
if (status !== 404) throw err;
|
|
115
|
+
const resolved = await this.resolveWorkspaceRef(workspaceRef, [
|
|
116
|
+
options?.plane ?? "data",
|
|
117
|
+
"admin"
|
|
118
|
+
]);
|
|
119
|
+
if (resolved === workspaceRef) throw err;
|
|
120
|
+
const raw = await this.client.workspaces.get(resolved, options);
|
|
121
|
+
return normalizeWorkspaceDetail(raw);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async createWorkspace(request) {
|
|
125
|
+
const body = {
|
|
126
|
+
name: request.name,
|
|
127
|
+
scope_name: request.scopeName
|
|
128
|
+
};
|
|
129
|
+
if (request.description !== void 0) body.description = request.description;
|
|
130
|
+
if (request.icon !== void 0) body.icon = request.icon;
|
|
131
|
+
if (request.defaults !== void 0) body.defaults = request.defaults;
|
|
132
|
+
if (request.users !== void 0) body.users = request.users;
|
|
133
|
+
if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
|
|
134
|
+
if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
|
|
135
|
+
const created = await this.client.workspaces.create(body);
|
|
136
|
+
return this.refetchAfterWrite(created.id, created);
|
|
137
|
+
}
|
|
138
|
+
async updateWorkspace(workspaceRef, request) {
|
|
139
|
+
const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
|
|
140
|
+
const body = {};
|
|
141
|
+
if (request.name !== void 0) body.name = request.name;
|
|
142
|
+
if (request.description !== void 0) body.description = request.description;
|
|
143
|
+
if (request.icon !== void 0) body.icon = request.icon;
|
|
144
|
+
if (request.defaults !== void 0) body.defaults = request.defaults;
|
|
145
|
+
if (request.usageLimits !== void 0) body.usage_limits = request.usageLimits;
|
|
146
|
+
if (request.rateLimits !== void 0) body.rate_limits = request.rateLimits;
|
|
147
|
+
await this.client.workspaces.update(ref, body);
|
|
148
|
+
return this.getWorkspace(ref, { plane: "admin" });
|
|
149
|
+
}
|
|
150
|
+
async deleteWorkspace(workspaceRef) {
|
|
151
|
+
const ref = await this.resolveWorkspaceRef(workspaceRef, ["admin"]);
|
|
152
|
+
await this.client.workspaces.delete(ref);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The API accepts only a UUID or slug as a workspace ref — a display name
|
|
156
|
+
* gets a misleading 400 AB01 ("No update fields provided") on writes.
|
|
157
|
+
* Match a user-supplied ref against the workspace list so name | slug |
|
|
158
|
+
* uuid all work. Unmatched refs pass through so the API's own error stands.
|
|
159
|
+
*/
|
|
160
|
+
async resolveWorkspaceRef(ref, planes) {
|
|
161
|
+
for (const plane of planes) {
|
|
162
|
+
let rows;
|
|
163
|
+
try {
|
|
164
|
+
rows = await this.listWorkspaces({ plane });
|
|
165
|
+
} catch {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (rows.some((w) => w.id === ref || w.slug === ref)) return ref;
|
|
169
|
+
const byName = rows.filter((w) => w.name === ref);
|
|
170
|
+
if (byName.length > 1) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`workspace name '${ref}' is ambiguous (${byName.map((w) => w.slug).join(", ")}) \u2014 use a slug or UUID`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (byName.length === 1) return byName[0].slug;
|
|
176
|
+
}
|
|
177
|
+
return ref;
|
|
178
|
+
}
|
|
179
|
+
async getTelemetryCost(opts) {
|
|
180
|
+
const days = opts.days ?? 7;
|
|
181
|
+
const workspaceSlug = await this.resolveWorkspaceRef(opts.workspaceSlug, ["data", "admin"]);
|
|
182
|
+
const raw = await this.client.telemetry.cost({
|
|
183
|
+
workspaceSlug,
|
|
184
|
+
days
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
workspaceSlug,
|
|
188
|
+
days,
|
|
189
|
+
totalCents: raw.data.total,
|
|
190
|
+
totalUsd: raw.data.total / 100,
|
|
191
|
+
avgCents: raw.data.avg,
|
|
192
|
+
avgUsd: raw.data.avg / 100,
|
|
193
|
+
quotaExceeded: raw.data.isQuotaExceeded,
|
|
194
|
+
records: raw.data.records.map((r) => ({
|
|
195
|
+
date: r.x,
|
|
196
|
+
costCents: r.y,
|
|
197
|
+
costUsd: r.y / 100
|
|
198
|
+
}))
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/** Re-read after a write, falling back to the (partial) write response if the get fails. */
|
|
202
|
+
async refetchAfterWrite(workspaceRef, writeResponse) {
|
|
203
|
+
try {
|
|
204
|
+
return await this.getWorkspace(workspaceRef, { plane: "admin" });
|
|
205
|
+
} catch {
|
|
206
|
+
return normalizeWorkspaceDetail(writeResponse);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
55
210
|
|
|
56
|
-
// src/
|
|
57
|
-
|
|
211
|
+
// src/config/client-options.ts
|
|
212
|
+
function runtimeInitOptions(config) {
|
|
213
|
+
return {
|
|
214
|
+
apiKey: config.airsApiKey,
|
|
215
|
+
apiToken: config.airsApiToken,
|
|
216
|
+
apiEndpoint: config.airsApiEndpoint,
|
|
217
|
+
numRetries: config.airsNumRetries
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function redTeamClientOptions(config) {
|
|
221
|
+
return {
|
|
222
|
+
clientId: config.mgmtClientId,
|
|
223
|
+
clientSecret: config.mgmtClientSecret,
|
|
224
|
+
tsgId: config.mgmtTsgId,
|
|
225
|
+
dataEndpoint: config.redTeamDataEndpoint,
|
|
226
|
+
mgmtEndpoint: config.redTeamMgmtEndpoint,
|
|
227
|
+
tokenEndpoint: config.redTeamTokenEndpoint ?? config.mgmtTokenEndpoint,
|
|
228
|
+
networkBrokerEndpoint: config.redTeamNetworkBrokerEndpoint
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function aiGatewayClientOptions(config) {
|
|
232
|
+
return {
|
|
233
|
+
clientId: config.mgmtClientId,
|
|
234
|
+
clientSecret: config.mgmtClientSecret,
|
|
235
|
+
tsgId: config.mgmtTsgId,
|
|
236
|
+
dataEndpoint: config.aiGwDataEndpoint,
|
|
237
|
+
adminEndpoint: config.aiGwAdminEndpoint,
|
|
238
|
+
tokenEndpoint: config.aiGwTokenEndpoint ?? config.mgmtTokenEndpoint
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function modelSecurityClientOptions(config) {
|
|
242
|
+
return {
|
|
243
|
+
clientId: config.mgmtClientId,
|
|
244
|
+
clientSecret: config.mgmtClientSecret,
|
|
245
|
+
tsgId: config.mgmtTsgId,
|
|
246
|
+
dataEndpoint: config.modelSecDataEndpoint,
|
|
247
|
+
mgmtEndpoint: config.modelSecMgmtEndpoint,
|
|
248
|
+
tokenEndpoint: config.modelSecTokenEndpoint ?? config.mgmtTokenEndpoint
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/cli/renderer/aigateway.ts
|
|
253
|
+
import chalk4 from "chalk";
|
|
254
|
+
import { dump as yamlDump } from "js-yaml";
|
|
58
255
|
|
|
59
256
|
// src/cli/renderer/common.ts
|
|
60
257
|
import chalk2 from "chalk";
|
|
258
|
+
import { dump } from "js-yaml";
|
|
259
|
+
var CliUsageError = class extends Error {
|
|
260
|
+
};
|
|
61
261
|
function fail(err) {
|
|
262
|
+
if (err instanceof CliUsageError) usageError(err.message);
|
|
62
263
|
const message = err instanceof Error ? err.message : String(err);
|
|
63
264
|
const status = err?.status ?? err?.statusCode;
|
|
64
265
|
console.error(chalk2.red(`
|
|
@@ -76,44 +277,81 @@ function usageError(message) {
|
|
|
76
277
|
`));
|
|
77
278
|
process.exit(2);
|
|
78
279
|
}
|
|
79
|
-
var OUTPUT_FORMATS = [
|
|
280
|
+
var OUTPUT_FORMATS = [
|
|
281
|
+
"pretty",
|
|
282
|
+
"table",
|
|
283
|
+
"markdown",
|
|
284
|
+
"csv",
|
|
285
|
+
"json",
|
|
286
|
+
"yaml"
|
|
287
|
+
];
|
|
288
|
+
async function resolveOutput(command, opts, resolution = {}) {
|
|
289
|
+
const localIsExplicit = command.getOptionValueSource?.("output") === "cli";
|
|
290
|
+
const root = command.parent ? command.optsWithGlobals() : command.opts();
|
|
291
|
+
let configured;
|
|
292
|
+
try {
|
|
293
|
+
configured = (await loadConfig()).defaultOutput;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (process.env.PANW_CLI_OUTPUT !== void 0) configured = process.env.PANW_CLI_OUTPUT;
|
|
296
|
+
else throw error;
|
|
297
|
+
}
|
|
298
|
+
const candidate = String(localIsExplicit ? opts.output : root.output ?? configured ?? "pretty");
|
|
299
|
+
if (!OUTPUT_FORMATS.includes(candidate))
|
|
300
|
+
throw new CliUsageError(
|
|
301
|
+
`Invalid output format '${candidate}'. Expected: ${OUTPUT_FORMATS.join(", ")}`
|
|
302
|
+
);
|
|
303
|
+
const format = candidate;
|
|
304
|
+
const allowed = resolution.allowed ?? OUTPUT_FORMATS;
|
|
305
|
+
if (!allowed.includes(format))
|
|
306
|
+
throw new CliUsageError(
|
|
307
|
+
`Output format '${format}' is not supported here. Expected: ${allowed.join(", ")}`
|
|
308
|
+
);
|
|
309
|
+
return format;
|
|
310
|
+
}
|
|
311
|
+
function displayValue(value) {
|
|
312
|
+
if (value == null) return "";
|
|
313
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
314
|
+
}
|
|
315
|
+
function csvCell(value) {
|
|
316
|
+
const text = displayValue(value);
|
|
317
|
+
return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
318
|
+
}
|
|
319
|
+
function markdownCell(value) {
|
|
320
|
+
return displayValue(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
321
|
+
}
|
|
80
322
|
function formatOutput(rows, columns, format) {
|
|
81
|
-
if (rows.length === 0) return "";
|
|
82
|
-
const
|
|
323
|
+
if (rows.length === 0) return format === "json" ? "[]" : "";
|
|
324
|
+
const projected = rows.map((row) => columns.map((column) => row[column.key]));
|
|
83
325
|
switch (format) {
|
|
84
326
|
case "json":
|
|
85
|
-
return JSON.stringify(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
102
|
-
return [header, ...lines].join("\n");
|
|
103
|
-
}
|
|
104
|
-
case "yaml": {
|
|
105
|
-
return rows.map((r) => columns.map((c) => `${c.key}: ${vals(r, c.key)}`).join("\n")).join("\n---\n");
|
|
327
|
+
return JSON.stringify(rows, null, 2);
|
|
328
|
+
case "yaml":
|
|
329
|
+
return dump(rows, { noRefs: true, lineWidth: -1 }).trimEnd();
|
|
330
|
+
case "csv":
|
|
331
|
+
return [
|
|
332
|
+
columns.map((column) => csvCell(column.label)).join(","),
|
|
333
|
+
...projected.map((values) => values.map(csvCell).join(","))
|
|
334
|
+
].join("\n");
|
|
335
|
+
case "markdown": {
|
|
336
|
+
const header = `| ${columns.map((column) => markdownCell(column.label)).join(" | ")} |`;
|
|
337
|
+
const divider = `| ${columns.map(() => "---").join(" | ")} |`;
|
|
338
|
+
return [
|
|
339
|
+
header,
|
|
340
|
+
divider,
|
|
341
|
+
...projected.map((values) => `| ${values.map(markdownCell).join(" | ")} |`)
|
|
342
|
+
].join("\n");
|
|
106
343
|
}
|
|
107
344
|
case "table": {
|
|
345
|
+
const values = projected.map((row) => row.map(displayValue));
|
|
108
346
|
const widths = columns.map(
|
|
109
|
-
(
|
|
347
|
+
(column, index) => Math.max(column.label.length, ...values.map((row) => row[index].length))
|
|
110
348
|
);
|
|
111
|
-
const
|
|
112
|
-
const header = columns.map((
|
|
113
|
-
const body =
|
|
114
|
-
(
|
|
349
|
+
const separator = widths.map((width) => "\u2500".repeat(width + 2)).join("\u253C");
|
|
350
|
+
const header = columns.map((column, index) => ` ${column.label.padEnd(widths[index])} `).join("\u2502");
|
|
351
|
+
const body = values.map(
|
|
352
|
+
(row) => row.map((value, index) => ` ${value.padEnd(widths[index])} `).join("\u2502")
|
|
115
353
|
);
|
|
116
|
-
return [header,
|
|
354
|
+
return [header, separator, ...body].join("\n");
|
|
117
355
|
}
|
|
118
356
|
default:
|
|
119
357
|
return "";
|
|
@@ -121,6 +359,7 @@ function formatOutput(rows, columns, format) {
|
|
|
121
359
|
}
|
|
122
360
|
|
|
123
361
|
// src/cli/renderer/ui.ts
|
|
362
|
+
import chalk3 from "chalk";
|
|
124
363
|
var INDENT = " ";
|
|
125
364
|
var quietMode = false;
|
|
126
365
|
function setQuiet(quiet) {
|
|
@@ -211,6 +450,187 @@ ${INDENT}${chalk3.bold(label)}
|
|
|
211
450
|
}
|
|
212
451
|
};
|
|
213
452
|
|
|
453
|
+
// src/cli/renderer/view.ts
|
|
454
|
+
import { dump as dump2 } from "js-yaml";
|
|
455
|
+
function asRecord(item) {
|
|
456
|
+
return item;
|
|
457
|
+
}
|
|
458
|
+
function project(view, item) {
|
|
459
|
+
const source = asRecord(item);
|
|
460
|
+
return Object.fromEntries(
|
|
461
|
+
view.columns.map((column) => [column.key, column.get ? column.get(item) : source[column.key]])
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
function renderPageStatus(page) {
|
|
465
|
+
if (!page) return;
|
|
466
|
+
if (page.all) ui.status(`Showing all ${page.total ?? page.returned}`);
|
|
467
|
+
else if (page.total !== void 0)
|
|
468
|
+
ui.status(
|
|
469
|
+
`Showing ${page.returned} of ${page.total}${page.next !== void 0 ? ` (next --offset ${page.next})` : ""}`
|
|
470
|
+
);
|
|
471
|
+
else if (page.next !== void 0) ui.status(`Showing ${page.returned} (more available)`);
|
|
472
|
+
else ui.status(`Showing ${page.returned}`);
|
|
473
|
+
}
|
|
474
|
+
function emitList(view, items, format, opts = {}) {
|
|
475
|
+
if (format === "pretty") {
|
|
476
|
+
if (items.length === 0) ui.emptyList(view.name);
|
|
477
|
+
else view.pretty.list(items);
|
|
478
|
+
} else {
|
|
479
|
+
const rows = format === "json" || format === "yaml" ? items.map((item) => view.structured?.(item) ?? asRecord(item)) : items.map((item) => project(view, item));
|
|
480
|
+
const rendered = formatOutput(rows, view.columns, format);
|
|
481
|
+
if (rendered) console.log(rendered);
|
|
482
|
+
}
|
|
483
|
+
renderPageStatus(opts.page);
|
|
484
|
+
}
|
|
485
|
+
function emitDetail(view, item, format) {
|
|
486
|
+
if (format === "pretty") {
|
|
487
|
+
view.pretty.detail(item);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const structured = view.structured?.(item) ?? asRecord(item);
|
|
491
|
+
if (format === "json") console.log(JSON.stringify(structured, null, 2));
|
|
492
|
+
else if (format === "yaml")
|
|
493
|
+
console.log(dump2(structured, { noRefs: true, lineWidth: -1 }).trimEnd());
|
|
494
|
+
else {
|
|
495
|
+
const rows = Object.entries(structured).map(([key, value]) => ({
|
|
496
|
+
key,
|
|
497
|
+
value: value != null && typeof value === "object" ? JSON.stringify(value) : value
|
|
498
|
+
}));
|
|
499
|
+
console.log(
|
|
500
|
+
formatOutput(
|
|
501
|
+
rows,
|
|
502
|
+
[
|
|
503
|
+
{ key: "key", label: "Key" },
|
|
504
|
+
{ key: "value", label: "Value" }
|
|
505
|
+
],
|
|
506
|
+
format
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/cli/renderer/aigateway.ts
|
|
513
|
+
function renderAiGatewayHeader() {
|
|
514
|
+
ui.header("Prisma AIRS \u2014 AI Gateway", "Gateway workspace operations");
|
|
515
|
+
}
|
|
516
|
+
function statusColor(status) {
|
|
517
|
+
switch (status.toLowerCase()) {
|
|
518
|
+
case "active":
|
|
519
|
+
return chalk4.green;
|
|
520
|
+
case "archived":
|
|
521
|
+
return chalk4.yellow;
|
|
522
|
+
default:
|
|
523
|
+
return chalk4.dim;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function statusLabel(status) {
|
|
527
|
+
return status ?? "unknown";
|
|
528
|
+
}
|
|
529
|
+
function renderWorkspaceList(workspaces, format = "pretty") {
|
|
530
|
+
if (workspaces.length === 0) {
|
|
531
|
+
ui.emptyList("workspaces");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (format !== "pretty") {
|
|
535
|
+
const rows = workspaces.map((w) => ({
|
|
536
|
+
id: w.id,
|
|
537
|
+
slug: w.slug,
|
|
538
|
+
name: w.name,
|
|
539
|
+
status: statusLabel(w.status),
|
|
540
|
+
isDefault: w.isDefault,
|
|
541
|
+
scopeName: w.scopeName ?? ""
|
|
542
|
+
}));
|
|
543
|
+
console.log(
|
|
544
|
+
formatOutput(
|
|
545
|
+
rows,
|
|
546
|
+
[
|
|
547
|
+
{ key: "id", label: "ID" },
|
|
548
|
+
{ key: "slug", label: "Slug" },
|
|
549
|
+
{ key: "name", label: "Name" },
|
|
550
|
+
{ key: "status", label: "Status" },
|
|
551
|
+
{ key: "isDefault", label: "Default" },
|
|
552
|
+
{ key: "scopeName", label: "Scope" }
|
|
553
|
+
],
|
|
554
|
+
format
|
|
555
|
+
)
|
|
556
|
+
);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
ui.section("AI Gateway Workspaces:");
|
|
560
|
+
for (const w of workspaces) {
|
|
561
|
+
ui.dim(w.id);
|
|
562
|
+
const status = statusColor(statusLabel(w.status))(statusLabel(w.status));
|
|
563
|
+
const dflt = w.isDefault ? chalk4.cyan(" default") : "";
|
|
564
|
+
console.log(` ${w.name} ${chalk4.dim(w.slug)} ${status}${dflt}`);
|
|
565
|
+
if (w.scopeName) console.log(` ${chalk4.dim(`scope: ${w.scopeName}`)}`);
|
|
566
|
+
console.log();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function renderWorkspaceDetail(workspace, format = "pretty") {
|
|
570
|
+
if (format !== "pretty") {
|
|
571
|
+
console.log(format === "json" ? JSON.stringify(workspace, null, 2) : yamlDump(workspace));
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
ui.section("Workspace Detail:");
|
|
575
|
+
const pairs = [
|
|
576
|
+
["ID", workspace.id],
|
|
577
|
+
["Slug", workspace.slug],
|
|
578
|
+
["Name", workspace.name],
|
|
579
|
+
["Status", statusColor(statusLabel(workspace.status))(statusLabel(workspace.status))],
|
|
580
|
+
["Default", workspace.isDefault ? "yes" : "no"]
|
|
581
|
+
];
|
|
582
|
+
if (workspace.description != null) pairs.push(["Description", workspace.description]);
|
|
583
|
+
if (workspace.scopeName != null) pairs.push(["Scope", workspace.scopeName]);
|
|
584
|
+
if (workspace.createdAt != null) pairs.push(["Created", workspace.createdAt]);
|
|
585
|
+
if (workspace.lastUpdatedAt != null) pairs.push(["Updated", workspace.lastUpdatedAt]);
|
|
586
|
+
ui.keyValue(pairs);
|
|
587
|
+
if (workspace.defaults && Object.keys(workspace.defaults).length > 0) {
|
|
588
|
+
ui.section("Defaults:");
|
|
589
|
+
console.log(chalk4.dim(JSON.stringify(workspace.defaults, null, 2)));
|
|
590
|
+
}
|
|
591
|
+
if (workspace.usageLimits.length > 0) {
|
|
592
|
+
ui.section("Usage Limits:");
|
|
593
|
+
console.log(chalk4.dim(JSON.stringify(workspace.usageLimits, null, 2)));
|
|
594
|
+
}
|
|
595
|
+
if (workspace.rateLimits.length > 0) {
|
|
596
|
+
ui.section("Rate Limits:");
|
|
597
|
+
console.log(chalk4.dim(JSON.stringify(workspace.rateLimits, null, 2)));
|
|
598
|
+
}
|
|
599
|
+
if (workspace.securitySettings && Object.keys(workspace.securitySettings).length > 0) {
|
|
600
|
+
ui.section("Security Settings:");
|
|
601
|
+
ui.keyValue(Object.entries(workspace.securitySettings).map(([k, v]) => [k, v]));
|
|
602
|
+
}
|
|
603
|
+
console.log();
|
|
604
|
+
}
|
|
605
|
+
function renderCostReport(report, format = "pretty") {
|
|
606
|
+
if (format !== "pretty") {
|
|
607
|
+
emitDetail(
|
|
608
|
+
{
|
|
609
|
+
name: "cost report",
|
|
610
|
+
columns: [],
|
|
611
|
+
pretty: { list() {
|
|
612
|
+
}, detail() {
|
|
613
|
+
} }
|
|
614
|
+
},
|
|
615
|
+
report,
|
|
616
|
+
format
|
|
617
|
+
);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const dollars = (cents) => `$${(cents / 100).toFixed(2)}`;
|
|
621
|
+
ui.section(`Cost \u2014 ${report.workspaceSlug} (last ${report.days}d):`);
|
|
622
|
+
ui.keyValue([
|
|
623
|
+
["Total", dollars(report.totalCents)],
|
|
624
|
+
["Daily average", dollars(report.avgCents)]
|
|
625
|
+
]);
|
|
626
|
+
if (report.quotaExceeded) ui.warn("Telemetry quota exceeded \u2014 data may be truncated");
|
|
627
|
+
if (report.records.length > 0) {
|
|
628
|
+
ui.section("Per day:");
|
|
629
|
+
ui.keyValue(report.records.map((r) => [r.date, dollars(r.costCents)]));
|
|
630
|
+
}
|
|
631
|
+
console.log();
|
|
632
|
+
}
|
|
633
|
+
|
|
214
634
|
// src/cli/renderer/backup.ts
|
|
215
635
|
function renderBackupHeader() {
|
|
216
636
|
ui.header("Prisma AIRS \u2014 Backup & Restore");
|
|
@@ -252,17 +672,17 @@ function renderRestoreSummary(results) {
|
|
|
252
672
|
}
|
|
253
673
|
|
|
254
674
|
// src/cli/renderer/dlp.ts
|
|
255
|
-
import
|
|
256
|
-
import { dump as
|
|
257
|
-
function
|
|
675
|
+
import chalk5 from "chalk";
|
|
676
|
+
import { dump as yamlDump2 } from "js-yaml";
|
|
677
|
+
function statusColor2(status) {
|
|
258
678
|
switch (status) {
|
|
259
679
|
case "active":
|
|
260
|
-
return
|
|
680
|
+
return chalk5.green(status);
|
|
261
681
|
case "deleted":
|
|
262
682
|
case "disabled":
|
|
263
|
-
return
|
|
683
|
+
return chalk5.yellow(status);
|
|
264
684
|
default:
|
|
265
|
-
return status ?
|
|
685
|
+
return status ? chalk5.dim(status) : chalk5.dim("\u2014");
|
|
266
686
|
}
|
|
267
687
|
}
|
|
268
688
|
function ts(ms) {
|
|
@@ -279,7 +699,7 @@ function emitStructured(payload, fmt) {
|
|
|
279
699
|
return;
|
|
280
700
|
}
|
|
281
701
|
if (fmt === "yaml") {
|
|
282
|
-
console.log(
|
|
702
|
+
console.log(yamlDump2(payload));
|
|
283
703
|
return;
|
|
284
704
|
}
|
|
285
705
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -293,60 +713,67 @@ function pageMeta(page, returned) {
|
|
|
293
713
|
returned
|
|
294
714
|
};
|
|
295
715
|
}
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
const rows = content.map(toRow);
|
|
299
|
-
if (fmt === "json" || fmt === "yaml") {
|
|
300
|
-
emitStructured({ items: rows, page: pageMeta(page, content.length) }, fmt);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
if (content.length === 0) {
|
|
304
|
-
ui.emptyList(header.toLowerCase());
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
if (fmt === "pretty") {
|
|
308
|
-
ui.section(`${header}:`);
|
|
309
|
-
for (const item of content) console.log(prettyLine(item));
|
|
310
|
-
const meta = pageMeta(page, content.length);
|
|
311
|
-
console.log();
|
|
312
|
-
ui.dim(
|
|
313
|
-
`page=${meta.number} size=${meta.size} returned=${meta.returned} total=${meta.total ?? "?"}`
|
|
314
|
-
);
|
|
315
|
-
console.log();
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
console.log(formatOutput(rows, columns, fmt));
|
|
716
|
+
function camelKey(key) {
|
|
717
|
+
return key.replace(/[_-]([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
319
718
|
}
|
|
320
|
-
function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
719
|
+
function camelize(value) {
|
|
720
|
+
if (Array.isArray(value)) return value.map(camelize);
|
|
721
|
+
if (value && typeof value === "object") {
|
|
722
|
+
return Object.fromEntries(
|
|
723
|
+
Object.entries(value).map(([key, child]) => [
|
|
724
|
+
camelKey(key),
|
|
725
|
+
camelize(child)
|
|
726
|
+
])
|
|
727
|
+
);
|
|
325
728
|
}
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
function toKey(label) {
|
|
329
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
729
|
+
return value;
|
|
330
730
|
}
|
|
331
|
-
function
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
731
|
+
function emitList2(page, fmt, header, toRow, columns, prettyLine) {
|
|
732
|
+
const content = Array.isArray(page?.content) ? page.content : [];
|
|
733
|
+
const meta = pageMeta(page, content.length);
|
|
734
|
+
const view = {
|
|
735
|
+
name: header.toLowerCase(),
|
|
736
|
+
columns: columns.map((column) => ({
|
|
737
|
+
...column,
|
|
738
|
+
get: (item) => toRow(item)[column.key]
|
|
739
|
+
})),
|
|
740
|
+
structured: (item) => camelize(item),
|
|
741
|
+
pretty: {
|
|
742
|
+
list(items) {
|
|
743
|
+
ui.section(`${header}:`);
|
|
744
|
+
for (const item of items) console.log(prettyLine(item));
|
|
745
|
+
console.log();
|
|
746
|
+
},
|
|
747
|
+
detail() {
|
|
748
|
+
}
|
|
342
749
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
750
|
+
};
|
|
751
|
+
const total = typeof meta.total === "number" ? meta.total : void 0;
|
|
752
|
+
const number = Number(meta.number ?? 0);
|
|
753
|
+
const size = Number(meta.size ?? content.length);
|
|
754
|
+
const next = total !== void 0 && (number + 1) * size < total ? (number + 1) * size : void 0;
|
|
755
|
+
emitList(view, content, fmt, { page: { returned: content.length, total, next } });
|
|
756
|
+
}
|
|
757
|
+
function emitDetail2(item, fmt, fields, title) {
|
|
758
|
+
const view = {
|
|
759
|
+
name: title.toLowerCase(),
|
|
760
|
+
columns: [],
|
|
761
|
+
structured: (value) => camelize(value),
|
|
762
|
+
pretty: {
|
|
763
|
+
list() {
|
|
764
|
+
},
|
|
765
|
+
detail() {
|
|
766
|
+
ui.section(`${title}:`);
|
|
767
|
+
ui.keyValue(
|
|
768
|
+
fields.filter(
|
|
769
|
+
(field) => field.value !== void 0 && field.value !== null && field.value !== ""
|
|
770
|
+
).map((field) => [field.label, field.value])
|
|
771
|
+
);
|
|
772
|
+
console.log();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
emitDetail(view, item, fmt);
|
|
350
777
|
}
|
|
351
778
|
function ackObject(verb, item) {
|
|
352
779
|
const out = { action: verb };
|
|
@@ -377,7 +804,7 @@ function emitIdAck(verb, id) {
|
|
|
377
804
|
}
|
|
378
805
|
var dlpFilteringProfiles = {
|
|
379
806
|
renderList(page, fmt) {
|
|
380
|
-
|
|
807
|
+
emitList2(
|
|
381
808
|
page,
|
|
382
809
|
fmt,
|
|
383
810
|
"Data Filtering Profiles",
|
|
@@ -398,16 +825,16 @@ var dlpFilteringProfiles = {
|
|
|
398
825
|
{ key: "version", label: "Version" }
|
|
399
826
|
],
|
|
400
827
|
(it) => {
|
|
401
|
-
const dir = it.direction ?
|
|
402
|
-
const sev = it.log_severity ?
|
|
403
|
-
const ver = it.version != null ?
|
|
404
|
-
return ` ${
|
|
405
|
-
${it.name} ${
|
|
828
|
+
const dir = it.direction ? chalk5.dim(` dir:${it.direction}`) : "";
|
|
829
|
+
const sev = it.log_severity ? chalk5.dim(` sev:${it.log_severity}`) : "";
|
|
830
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
831
|
+
return ` ${chalk5.dim(it.id)}
|
|
832
|
+
${it.name} ${chalk5.cyan(it.type ?? "")}${dir}${sev}${ver}`;
|
|
406
833
|
}
|
|
407
834
|
);
|
|
408
835
|
},
|
|
409
836
|
renderGet(item, fmt) {
|
|
410
|
-
|
|
837
|
+
emitDetail2(
|
|
411
838
|
item,
|
|
412
839
|
fmt,
|
|
413
840
|
[
|
|
@@ -436,7 +863,7 @@ var dlpFilteringProfiles = {
|
|
|
436
863
|
};
|
|
437
864
|
var dlpPatterns = {
|
|
438
865
|
renderList(page, fmt) {
|
|
439
|
-
|
|
866
|
+
emitList2(
|
|
440
867
|
page,
|
|
441
868
|
fmt,
|
|
442
869
|
"Data Patterns",
|
|
@@ -457,15 +884,15 @@ var dlpPatterns = {
|
|
|
457
884
|
{ key: "version", label: "Version" }
|
|
458
885
|
],
|
|
459
886
|
(it) => {
|
|
460
|
-
const tech = it.detection_config?.technique ?
|
|
461
|
-
const ver = it.version != null ?
|
|
462
|
-
return ` ${
|
|
463
|
-
${it.name} ${
|
|
887
|
+
const tech = it.detection_config?.technique ? chalk5.dim(` ${it.detection_config.technique}`) : "";
|
|
888
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
889
|
+
return ` ${chalk5.dim(it.id)}
|
|
890
|
+
${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.status)}${tech}${ver}`;
|
|
464
891
|
}
|
|
465
892
|
);
|
|
466
893
|
},
|
|
467
894
|
renderGet(item, fmt) {
|
|
468
|
-
|
|
895
|
+
emitDetail2(
|
|
469
896
|
item,
|
|
470
897
|
fmt,
|
|
471
898
|
[
|
|
@@ -504,7 +931,7 @@ var dlpPatterns = {
|
|
|
504
931
|
};
|
|
505
932
|
var dlpProfiles = {
|
|
506
933
|
renderList(page, fmt) {
|
|
507
|
-
|
|
934
|
+
emitList2(
|
|
508
935
|
page,
|
|
509
936
|
fmt,
|
|
510
937
|
"Data Profiles",
|
|
@@ -525,15 +952,15 @@ var dlpProfiles = {
|
|
|
525
952
|
{ key: "version", label: "Version" }
|
|
526
953
|
],
|
|
527
954
|
(it) => {
|
|
528
|
-
const ptype = it.profile_type ?
|
|
529
|
-
const ver = it.version != null ?
|
|
530
|
-
return ` ${
|
|
531
|
-
${it.name} ${
|
|
955
|
+
const ptype = it.profile_type ? chalk5.dim(` ${it.profile_type}`) : "";
|
|
956
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
957
|
+
return ` ${chalk5.dim(it.id)}
|
|
958
|
+
${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.profile_status)}${ptype}${ver}`;
|
|
532
959
|
}
|
|
533
960
|
);
|
|
534
961
|
},
|
|
535
962
|
renderGet(item, fmt) {
|
|
536
|
-
|
|
963
|
+
emitDetail2(
|
|
537
964
|
item,
|
|
538
965
|
fmt,
|
|
539
966
|
[
|
|
@@ -561,7 +988,7 @@ var dlpProfiles = {
|
|
|
561
988
|
};
|
|
562
989
|
var dlpDictionaries = {
|
|
563
990
|
renderList(page, fmt) {
|
|
564
|
-
|
|
991
|
+
emitList2(
|
|
565
992
|
page,
|
|
566
993
|
fmt,
|
|
567
994
|
"Data Dictionaries",
|
|
@@ -582,15 +1009,15 @@ var dlpDictionaries = {
|
|
|
582
1009
|
{ key: "version", label: "Version" }
|
|
583
1010
|
],
|
|
584
1011
|
(it) => {
|
|
585
|
-
const kw = Array.isArray(it.keywords) ?
|
|
586
|
-
const ver = it.version != null ?
|
|
587
|
-
return ` ${
|
|
588
|
-
${it.name} ${
|
|
1012
|
+
const kw = Array.isArray(it.keywords) ? chalk5.dim(` ${it.keywords.length} kw`) : "";
|
|
1013
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
1014
|
+
return ` ${chalk5.dim(it.id)}
|
|
1015
|
+
${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.status)}${kw}${ver}`;
|
|
589
1016
|
}
|
|
590
1017
|
);
|
|
591
1018
|
},
|
|
592
1019
|
renderGet(item, fmt) {
|
|
593
|
-
|
|
1020
|
+
emitDetail2(
|
|
594
1021
|
item,
|
|
595
1022
|
fmt,
|
|
596
1023
|
[
|
|
@@ -627,7 +1054,7 @@ var dlpDictionaries = {
|
|
|
627
1054
|
};
|
|
628
1055
|
|
|
629
1056
|
// src/cli/renderer/eval.ts
|
|
630
|
-
import
|
|
1057
|
+
import chalk6 from "chalk";
|
|
631
1058
|
function buildEvalOutput(profile, topic, intent, metrics, results) {
|
|
632
1059
|
const fps = results.filter((r) => !r.testCase.expectedTriggered && r.actualTriggered).map((r) => ({ prompt: r.testCase.prompt, expected: false, actual: true }));
|
|
633
1060
|
const fns = results.filter((r) => r.testCase.expectedTriggered && !r.actualTriggered).map((r) => ({ prompt: r.testCase.prompt, expected: true, actual: false }));
|
|
@@ -651,7 +1078,7 @@ function buildEvalOutput(profile, topic, intent, metrics, results) {
|
|
|
651
1078
|
};
|
|
652
1079
|
}
|
|
653
1080
|
function renderEvalTerminal(output) {
|
|
654
|
-
const coverageColor = output.metrics.coverage >= 0.9 ?
|
|
1081
|
+
const coverageColor = output.metrics.coverage >= 0.9 ? chalk6.green : output.metrics.coverage >= 0.7 ? chalk6.yellow : chalk6.red;
|
|
655
1082
|
ui.header("Eval Results");
|
|
656
1083
|
ui.keyValue([
|
|
657
1084
|
["Profile", output.profile],
|
|
@@ -684,8 +1111,16 @@ function renderEvalTerminal(output) {
|
|
|
684
1111
|
}
|
|
685
1112
|
|
|
686
1113
|
// src/cli/renderer/modelsecurity.ts
|
|
687
|
-
import
|
|
688
|
-
|
|
1114
|
+
import chalk7 from "chalk";
|
|
1115
|
+
function resourceView(name, columns, pretty) {
|
|
1116
|
+
return { name, columns, pretty };
|
|
1117
|
+
}
|
|
1118
|
+
function structuredList(name, items, columns, format, pretty) {
|
|
1119
|
+
emitList(resourceView(name, columns, { list: pretty, detail: () => void 0 }), items, format);
|
|
1120
|
+
}
|
|
1121
|
+
function structuredDetail(name, item, format, pretty) {
|
|
1122
|
+
emitDetail(resourceView(name, [], { list: () => void 0, detail: pretty }), item, format);
|
|
1123
|
+
}
|
|
689
1124
|
function renderModelSecurityHeader() {
|
|
690
1125
|
ui.header("Prisma AIRS \u2014 Model Security", "ML model supply chain security");
|
|
691
1126
|
}
|
|
@@ -696,66 +1131,56 @@ function stateColor(state) {
|
|
|
696
1131
|
case "ALLOWING":
|
|
697
1132
|
case "PASSED":
|
|
698
1133
|
case "SUCCESS":
|
|
699
|
-
return
|
|
1134
|
+
return chalk7.green;
|
|
700
1135
|
case "BLOCKED":
|
|
701
1136
|
case "BLOCKING":
|
|
702
1137
|
case "FAILED":
|
|
703
|
-
return
|
|
1138
|
+
return chalk7.red;
|
|
704
1139
|
case "DISABLED":
|
|
705
|
-
return
|
|
1140
|
+
return chalk7.dim;
|
|
706
1141
|
default:
|
|
707
|
-
return
|
|
1142
|
+
return chalk7.yellow;
|
|
708
1143
|
}
|
|
709
1144
|
}
|
|
710
1145
|
function renderGroupList(groups, format = "pretty") {
|
|
711
|
-
if (groups.length === 0) {
|
|
712
|
-
ui.emptyList("security groups");
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
1146
|
if (format !== "pretty") {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
{ key: "name", label: "Name" },
|
|
728
|
-
{ key: "state", label: "State" },
|
|
729
|
-
{ key: "sourceType", label: "Source Type" }
|
|
730
|
-
],
|
|
731
|
-
format
|
|
732
|
-
)
|
|
1147
|
+
structuredList(
|
|
1148
|
+
"security groups",
|
|
1149
|
+
groups,
|
|
1150
|
+
[
|
|
1151
|
+
{ key: "uuid", label: "ID" },
|
|
1152
|
+
{ key: "name", label: "Name" },
|
|
1153
|
+
{ key: "state", label: "State" },
|
|
1154
|
+
{ key: "sourceType", label: "Source Type" }
|
|
1155
|
+
],
|
|
1156
|
+
format,
|
|
1157
|
+
() => void 0
|
|
733
1158
|
);
|
|
734
1159
|
return;
|
|
735
1160
|
}
|
|
1161
|
+
if (groups.length === 0) {
|
|
1162
|
+
ui.emptyList("security groups");
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
736
1165
|
ui.section("Security Groups:");
|
|
737
1166
|
for (const g of groups) {
|
|
738
1167
|
ui.dim(g.uuid);
|
|
739
|
-
const color = g.state === "ACTIVE" ?
|
|
740
|
-
console.log(` ${g.name} ${color(g.state)} source: ${
|
|
1168
|
+
const color = g.state === "ACTIVE" ? chalk7.green : chalk7.yellow;
|
|
1169
|
+
console.log(` ${g.name} ${color(g.state)} source: ${chalk7.dim(g.sourceType)}`);
|
|
741
1170
|
}
|
|
742
1171
|
console.log();
|
|
743
1172
|
}
|
|
744
1173
|
function renderGroupDetail(group, format = "pretty") {
|
|
745
|
-
if (format
|
|
746
|
-
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
if (format === "yaml") {
|
|
750
|
-
console.log(yamlDump2(group));
|
|
1174
|
+
if (format !== "pretty") {
|
|
1175
|
+
structuredDetail("security group", group, format, () => void 0);
|
|
751
1176
|
return;
|
|
752
1177
|
}
|
|
753
1178
|
ui.section("Security Group Detail:");
|
|
754
|
-
const color = group.state === "ACTIVE" ?
|
|
1179
|
+
const color = group.state === "ACTIVE" ? chalk7.green : chalk7.yellow;
|
|
755
1180
|
ui.keyValue([
|
|
756
1181
|
["UUID", group.uuid],
|
|
757
1182
|
["Name", group.name],
|
|
758
|
-
["Description", group.description ||
|
|
1183
|
+
["Description", group.description || chalk7.dim("(none)")],
|
|
759
1184
|
["Source Type", group.sourceType],
|
|
760
1185
|
["State", color(group.state)],
|
|
761
1186
|
["Created", group.createdAt],
|
|
@@ -764,45 +1189,46 @@ function renderGroupDetail(group, format = "pretty") {
|
|
|
764
1189
|
console.log();
|
|
765
1190
|
}
|
|
766
1191
|
function renderRuleList(rules, format = "pretty") {
|
|
767
|
-
if (rules.length === 0) {
|
|
768
|
-
ui.emptyList("security rules");
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
1192
|
if (format !== "pretty") {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
],
|
|
789
|
-
format
|
|
790
|
-
)
|
|
1193
|
+
structuredList(
|
|
1194
|
+
"security rules",
|
|
1195
|
+
rules,
|
|
1196
|
+
[
|
|
1197
|
+
{ key: "uuid", label: "ID" },
|
|
1198
|
+
{ key: "name", label: "Name" },
|
|
1199
|
+
{ key: "ruleType", label: "Type" },
|
|
1200
|
+
{ key: "defaultState", label: "Default State" },
|
|
1201
|
+
{
|
|
1202
|
+
key: "compatibleSources",
|
|
1203
|
+
label: "Sources",
|
|
1204
|
+
get: (rule) => rule.compatibleSources.join(", ")
|
|
1205
|
+
}
|
|
1206
|
+
],
|
|
1207
|
+
format,
|
|
1208
|
+
() => void 0
|
|
791
1209
|
);
|
|
792
1210
|
return;
|
|
793
1211
|
}
|
|
1212
|
+
if (rules.length === 0) {
|
|
1213
|
+
ui.emptyList("security rules");
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
794
1216
|
ui.section("Security Rules:");
|
|
795
1217
|
for (const r of rules) {
|
|
796
1218
|
ui.dim(r.uuid);
|
|
797
1219
|
console.log(
|
|
798
|
-
` ${r.name} type: ${
|
|
1220
|
+
` ${r.name} type: ${chalk7.dim(r.ruleType)} default: ${chalk7.dim(r.defaultState)}`
|
|
799
1221
|
);
|
|
800
|
-
console.log(` ${
|
|
801
|
-
console.log(` Sources: ${r.compatibleSources.map((s) =>
|
|
1222
|
+
console.log(` ${chalk7.dim(r.description)}`);
|
|
1223
|
+
console.log(` Sources: ${r.compatibleSources.map((s) => chalk7.dim(s)).join(", ")}`);
|
|
802
1224
|
}
|
|
803
1225
|
console.log();
|
|
804
1226
|
}
|
|
805
|
-
function renderRuleDetail(rule) {
|
|
1227
|
+
function renderRuleDetail(rule, format = "pretty") {
|
|
1228
|
+
if (format !== "pretty") {
|
|
1229
|
+
structuredDetail("security rule", rule, format, () => void 0);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
806
1232
|
ui.section("Security Rule Detail:");
|
|
807
1233
|
ui.keyValue([
|
|
808
1234
|
["UUID", rule.uuid],
|
|
@@ -827,13 +1253,27 @@ function renderRuleDetail(rule) {
|
|
|
827
1253
|
if (rule.editableFields.length > 0) {
|
|
828
1254
|
ui.section("Editable Fields:");
|
|
829
1255
|
for (const f of rule.editableFields) {
|
|
830
|
-
console.log(` ${f.displayName} (${
|
|
831
|
-
if (f.description) console.log(` ${
|
|
1256
|
+
console.log(` ${f.displayName} (${chalk7.dim(f.attributeName)}): ${f.displayType}`);
|
|
1257
|
+
if (f.description) console.log(` ${chalk7.dim(f.description)}`);
|
|
832
1258
|
}
|
|
833
1259
|
}
|
|
834
1260
|
console.log();
|
|
835
1261
|
}
|
|
836
|
-
function renderRuleInstanceList(instances) {
|
|
1262
|
+
function renderRuleInstanceList(instances, format = "pretty") {
|
|
1263
|
+
if (format !== "pretty") {
|
|
1264
|
+
structuredList(
|
|
1265
|
+
"rule instances",
|
|
1266
|
+
instances,
|
|
1267
|
+
[
|
|
1268
|
+
{ key: "uuid", label: "ID" },
|
|
1269
|
+
{ key: "securityRuleUuid", label: "Rule ID" },
|
|
1270
|
+
{ key: "state", label: "State" }
|
|
1271
|
+
],
|
|
1272
|
+
format,
|
|
1273
|
+
() => void 0
|
|
1274
|
+
);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
837
1277
|
if (instances.length === 0) {
|
|
838
1278
|
ui.emptyList("rule instances");
|
|
839
1279
|
return;
|
|
@@ -846,7 +1286,11 @@ function renderRuleInstanceList(instances) {
|
|
|
846
1286
|
}
|
|
847
1287
|
console.log();
|
|
848
1288
|
}
|
|
849
|
-
function renderRuleInstanceDetail(instance) {
|
|
1289
|
+
function renderRuleInstanceDetail(instance, format = "pretty") {
|
|
1290
|
+
if (format !== "pretty") {
|
|
1291
|
+
structuredDetail("rule instance", instance, format, () => void 0);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
850
1294
|
ui.section("Rule Instance Detail:");
|
|
851
1295
|
const pairs = [
|
|
852
1296
|
["UUID", instance.uuid],
|
|
@@ -871,54 +1315,47 @@ function renderRuleInstanceDetail(instance) {
|
|
|
871
1315
|
console.log();
|
|
872
1316
|
}
|
|
873
1317
|
function renderMsScanList(scans, format = "pretty") {
|
|
874
|
-
if (scans.length === 0) {
|
|
875
|
-
ui.emptyList("scans");
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
1318
|
if (format !== "pretty") {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
[
|
|
892
|
-
{ key: "id", label: "ID" },
|
|
893
|
-
{ key: "outcome", label: "Outcome" },
|
|
894
|
-
{ key: "origin", label: "Origin" },
|
|
895
|
-
{ key: "modelUri", label: "Model URI" },
|
|
896
|
-
{ key: "passed", label: "Passed" },
|
|
897
|
-
{ key: "failed", label: "Failed" },
|
|
898
|
-
{ key: "createdAt", label: "Created" }
|
|
899
|
-
],
|
|
900
|
-
format
|
|
901
|
-
)
|
|
1319
|
+
structuredList(
|
|
1320
|
+
"scans",
|
|
1321
|
+
scans,
|
|
1322
|
+
[
|
|
1323
|
+
{ key: "uuid", label: "ID" },
|
|
1324
|
+
{ key: "evalOutcome", label: "Outcome" },
|
|
1325
|
+
{ key: "scanOrigin", label: "Origin" },
|
|
1326
|
+
{ key: "modelUri", label: "Model URI" },
|
|
1327
|
+
{ key: "createdAt", label: "Created" }
|
|
1328
|
+
],
|
|
1329
|
+
format,
|
|
1330
|
+
() => void 0
|
|
902
1331
|
);
|
|
903
1332
|
return;
|
|
904
1333
|
}
|
|
1334
|
+
if (scans.length === 0) {
|
|
1335
|
+
ui.emptyList("scans");
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
905
1338
|
ui.section("Model Security Scans:");
|
|
906
1339
|
for (const s of scans) {
|
|
907
1340
|
ui.dim(s.uuid);
|
|
908
1341
|
console.log(
|
|
909
|
-
` ${stateColor(s.evalOutcome)(s.evalOutcome)} ${
|
|
1342
|
+
` ${stateColor(s.evalOutcome)(s.evalOutcome)} ${chalk7.dim(s.scanOrigin)} ${chalk7.dim(s.createdAt)}`
|
|
910
1343
|
);
|
|
911
|
-
if (s.modelUri) console.log(` ${
|
|
1344
|
+
if (s.modelUri) console.log(` ${chalk7.dim(s.modelUri)}`);
|
|
912
1345
|
if (s.evalSummary) {
|
|
913
1346
|
const { rulesPassed, rulesFailed, totalRules } = s.evalSummary;
|
|
914
1347
|
console.log(
|
|
915
|
-
` Rules: ${
|
|
1348
|
+
` Rules: ${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
|
|
916
1349
|
);
|
|
917
1350
|
}
|
|
918
1351
|
}
|
|
919
1352
|
console.log();
|
|
920
1353
|
}
|
|
921
|
-
function renderMsScanDetail(scan) {
|
|
1354
|
+
function renderMsScanDetail(scan, format = "pretty") {
|
|
1355
|
+
if (format !== "pretty") {
|
|
1356
|
+
structuredDetail("scan", scan, format, () => void 0);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
922
1359
|
ui.section("Scan Detail:");
|
|
923
1360
|
const pairs = [
|
|
924
1361
|
["UUID", scan.uuid],
|
|
@@ -934,7 +1371,7 @@ function renderMsScanDetail(scan) {
|
|
|
934
1371
|
const { rulesPassed, rulesFailed, totalRules } = scan.evalSummary;
|
|
935
1372
|
pairs.push([
|
|
936
1373
|
"Rules",
|
|
937
|
-
`${
|
|
1374
|
+
`${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
|
|
938
1375
|
]);
|
|
939
1376
|
}
|
|
940
1377
|
ui.keyValue(pairs);
|
|
@@ -944,7 +1381,22 @@ function renderMsScanDetail(scan) {
|
|
|
944
1381
|
}
|
|
945
1382
|
console.log();
|
|
946
1383
|
}
|
|
947
|
-
function renderEvaluationList(evaluations) {
|
|
1384
|
+
function renderEvaluationList(evaluations, format = "pretty") {
|
|
1385
|
+
if (format !== "pretty") {
|
|
1386
|
+
structuredList(
|
|
1387
|
+
"evaluations",
|
|
1388
|
+
evaluations,
|
|
1389
|
+
[
|
|
1390
|
+
{ key: "uuid", label: "ID" },
|
|
1391
|
+
{ key: "ruleName", label: "Rule" },
|
|
1392
|
+
{ key: "result", label: "Result" },
|
|
1393
|
+
{ key: "ruleInstanceState", label: "State" }
|
|
1394
|
+
],
|
|
1395
|
+
format,
|
|
1396
|
+
() => void 0
|
|
1397
|
+
);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
948
1400
|
if (evaluations.length === 0) {
|
|
949
1401
|
ui.emptyList("evaluations");
|
|
950
1402
|
return;
|
|
@@ -953,12 +1405,16 @@ function renderEvaluationList(evaluations) {
|
|
|
953
1405
|
for (const e of evaluations) {
|
|
954
1406
|
ui.dim(e.uuid);
|
|
955
1407
|
console.log(
|
|
956
|
-
` ${e.ruleName} ${stateColor(e.result)(e.result)} ${
|
|
1408
|
+
` ${e.ruleName} ${stateColor(e.result)(e.result)} ${chalk7.dim(e.ruleInstanceState)}`
|
|
957
1409
|
);
|
|
958
1410
|
}
|
|
959
1411
|
console.log();
|
|
960
1412
|
}
|
|
961
|
-
function renderEvaluationDetail(evaluation) {
|
|
1413
|
+
function renderEvaluationDetail(evaluation, format = "pretty") {
|
|
1414
|
+
if (format !== "pretty") {
|
|
1415
|
+
structuredDetail("evaluation", evaluation, format, () => void 0);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
962
1418
|
ui.section("Evaluation Detail:");
|
|
963
1419
|
ui.keyValue([
|
|
964
1420
|
["UUID", evaluation.uuid],
|
|
@@ -971,7 +1427,22 @@ function renderEvaluationDetail(evaluation) {
|
|
|
971
1427
|
]);
|
|
972
1428
|
console.log();
|
|
973
1429
|
}
|
|
974
|
-
function renderViolationList(violations) {
|
|
1430
|
+
function renderViolationList(violations, format = "pretty") {
|
|
1431
|
+
if (format !== "pretty") {
|
|
1432
|
+
structuredList(
|
|
1433
|
+
"violations",
|
|
1434
|
+
violations,
|
|
1435
|
+
[
|
|
1436
|
+
{ key: "uuid", label: "ID" },
|
|
1437
|
+
{ key: "ruleName", label: "Rule" },
|
|
1438
|
+
{ key: "file", label: "File" },
|
|
1439
|
+
{ key: "threat", label: "Threat" }
|
|
1440
|
+
],
|
|
1441
|
+
format,
|
|
1442
|
+
() => void 0
|
|
1443
|
+
);
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
975
1446
|
if (violations.length === 0) {
|
|
976
1447
|
ui.emptyList("violations");
|
|
977
1448
|
return;
|
|
@@ -979,17 +1450,21 @@ function renderViolationList(violations) {
|
|
|
979
1450
|
ui.section("Violations:");
|
|
980
1451
|
for (const v of violations) {
|
|
981
1452
|
ui.dim(v.uuid);
|
|
982
|
-
console.log(` ${
|
|
1453
|
+
console.log(` ${chalk7.red(v.ruleName)} ${chalk7.dim(v.file)}`);
|
|
983
1454
|
console.log(` ${v.description}`);
|
|
984
|
-
console.log(` Threat: ${
|
|
1455
|
+
console.log(` Threat: ${chalk7.dim(v.threat)}`);
|
|
985
1456
|
}
|
|
986
1457
|
console.log();
|
|
987
1458
|
}
|
|
988
|
-
function renderViolationDetail(violation) {
|
|
1459
|
+
function renderViolationDetail(violation, format = "pretty") {
|
|
1460
|
+
if (format !== "pretty") {
|
|
1461
|
+
structuredDetail("violation", violation, format, () => void 0);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
989
1464
|
ui.section("Violation Detail:");
|
|
990
1465
|
ui.keyValue([
|
|
991
1466
|
["UUID", violation.uuid],
|
|
992
|
-
["Rule",
|
|
1467
|
+
["Rule", chalk7.red(violation.ruleName)],
|
|
993
1468
|
["Description", violation.ruleDescription],
|
|
994
1469
|
["State", violation.ruleInstanceState],
|
|
995
1470
|
["File", violation.file],
|
|
@@ -998,15 +1473,30 @@ function renderViolationDetail(violation) {
|
|
|
998
1473
|
]);
|
|
999
1474
|
console.log();
|
|
1000
1475
|
}
|
|
1001
|
-
function renderFileList(files) {
|
|
1476
|
+
function renderFileList(files, format = "pretty") {
|
|
1477
|
+
if (format !== "pretty") {
|
|
1478
|
+
structuredList(
|
|
1479
|
+
"files",
|
|
1480
|
+
files,
|
|
1481
|
+
[
|
|
1482
|
+
{ key: "uuid", label: "ID" },
|
|
1483
|
+
{ key: "path", label: "Path" },
|
|
1484
|
+
{ key: "type", label: "Type" },
|
|
1485
|
+
{ key: "result", label: "Result" }
|
|
1486
|
+
],
|
|
1487
|
+
format,
|
|
1488
|
+
() => void 0
|
|
1489
|
+
);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1002
1492
|
if (files.length === 0) {
|
|
1003
1493
|
ui.emptyList("files");
|
|
1004
1494
|
return;
|
|
1005
1495
|
}
|
|
1006
1496
|
ui.section("Scanned Files:");
|
|
1007
1497
|
for (const f of files) {
|
|
1008
|
-
const color = f.result === "SUCCESS" ?
|
|
1009
|
-
const formats = f.formats.length > 0 ?
|
|
1498
|
+
const color = f.result === "SUCCESS" ? chalk7.green : f.result === "SKIPPED" ? chalk7.yellow : chalk7.red;
|
|
1499
|
+
const formats = f.formats.length > 0 ? chalk7.dim(` [${f.formats.join(", ")}]`) : "";
|
|
1010
1500
|
console.log(` ${color(f.result)} ${f.type} ${f.path}${formats}`);
|
|
1011
1501
|
}
|
|
1012
1502
|
console.log();
|
|
@@ -1034,45 +1524,42 @@ function renderLabelValues(key, values) {
|
|
|
1034
1524
|
console.log();
|
|
1035
1525
|
}
|
|
1036
1526
|
function renderModelList(models, format = "pretty") {
|
|
1037
|
-
if (models.length === 0) {
|
|
1038
|
-
ui.emptyList("models");
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
1527
|
if (format !== "pretty") {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
],
|
|
1059
|
-
format
|
|
1060
|
-
)
|
|
1528
|
+
structuredList(
|
|
1529
|
+
"models",
|
|
1530
|
+
models,
|
|
1531
|
+
[
|
|
1532
|
+
{ key: "uuid", label: "ID" },
|
|
1533
|
+
{ key: "name", label: "Name" },
|
|
1534
|
+
{ key: "latestVersionOutcome", label: "Outcome" },
|
|
1535
|
+
{
|
|
1536
|
+
key: "latestVersionFormats",
|
|
1537
|
+
label: "Formats",
|
|
1538
|
+
get: (model) => (model.latestVersionFormats ?? []).join(", ")
|
|
1539
|
+
},
|
|
1540
|
+
{ key: "latestVersionScanTime", label: "Last Scan" }
|
|
1541
|
+
],
|
|
1542
|
+
format,
|
|
1543
|
+
() => void 0
|
|
1061
1544
|
);
|
|
1062
1545
|
return;
|
|
1063
1546
|
}
|
|
1547
|
+
if (models.length === 0) {
|
|
1548
|
+
ui.emptyList("models");
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1064
1551
|
ui.section("Models:");
|
|
1065
1552
|
for (const m of models) {
|
|
1066
1553
|
ui.dim(m.uuid);
|
|
1067
|
-
const outcome = m.latestVersionOutcome ? stateColor(m.latestVersionOutcome)(m.latestVersionOutcome) :
|
|
1068
|
-
const formats = m.latestVersionFormats && m.latestVersionFormats.length > 0 ?
|
|
1554
|
+
const outcome = m.latestVersionOutcome ? stateColor(m.latestVersionOutcome)(m.latestVersionOutcome) : chalk7.dim("unscanned");
|
|
1555
|
+
const formats = m.latestVersionFormats && m.latestVersionFormats.length > 0 ? chalk7.dim(` [${m.latestVersionFormats.join(", ")}]`) : "";
|
|
1069
1556
|
console.log(` ${m.name} ${outcome}${formats}`);
|
|
1070
1557
|
console.log();
|
|
1071
1558
|
}
|
|
1072
1559
|
}
|
|
1073
1560
|
function renderModelDetail(model, format = "pretty") {
|
|
1074
1561
|
if (format !== "pretty") {
|
|
1075
|
-
|
|
1562
|
+
structuredDetail("model", model, format, () => void 0);
|
|
1076
1563
|
return;
|
|
1077
1564
|
}
|
|
1078
1565
|
ui.section("Model Detail:");
|
|
@@ -1099,37 +1586,30 @@ function renderModelDetail(model, format = "pretty") {
|
|
|
1099
1586
|
console.log();
|
|
1100
1587
|
}
|
|
1101
1588
|
function renderModelVersionList(versions, format = "pretty") {
|
|
1102
|
-
if (versions.length === 0) {
|
|
1103
|
-
ui.emptyList("versions");
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
1589
|
if (format !== "pretty") {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
{ key: "revision", label: "Revision" },
|
|
1120
|
-
{ key: "files", label: "Files" },
|
|
1121
|
-
{ key: "outcome", label: "Outcome" },
|
|
1122
|
-
{ key: "scanned", label: "Last Scan" }
|
|
1123
|
-
],
|
|
1124
|
-
format
|
|
1125
|
-
)
|
|
1590
|
+
structuredList(
|
|
1591
|
+
"versions",
|
|
1592
|
+
versions,
|
|
1593
|
+
[
|
|
1594
|
+
{ key: "uuid", label: "ID" },
|
|
1595
|
+
{ key: "revision", label: "Revision" },
|
|
1596
|
+
{ key: "fileCount", label: "Files" },
|
|
1597
|
+
{ key: "lastEvalOutcome", label: "Outcome" },
|
|
1598
|
+
{ key: "latestScanTime", label: "Last Scan" }
|
|
1599
|
+
],
|
|
1600
|
+
format,
|
|
1601
|
+
() => void 0
|
|
1126
1602
|
);
|
|
1127
1603
|
return;
|
|
1128
1604
|
}
|
|
1605
|
+
if (versions.length === 0) {
|
|
1606
|
+
ui.emptyList("versions");
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1129
1609
|
ui.section("Model Versions:");
|
|
1130
1610
|
for (const v of versions) {
|
|
1131
1611
|
ui.dim(v.uuid);
|
|
1132
|
-
const outcome = v.lastEvalOutcome ? stateColor(v.lastEvalOutcome)(v.lastEvalOutcome) :
|
|
1612
|
+
const outcome = v.lastEvalOutcome ? stateColor(v.lastEvalOutcome)(v.lastEvalOutcome) : chalk7.dim("unscanned");
|
|
1133
1613
|
const files = v.fileCount != null ? ` files: ${v.fileCount}` : "";
|
|
1134
1614
|
console.log(` ${v.revision} ${outcome}${files}`);
|
|
1135
1615
|
console.log();
|
|
@@ -1137,7 +1617,7 @@ function renderModelVersionList(versions, format = "pretty") {
|
|
|
1137
1617
|
}
|
|
1138
1618
|
function renderModelVersionDetail(version, format = "pretty") {
|
|
1139
1619
|
if (format !== "pretty") {
|
|
1140
|
-
|
|
1620
|
+
structuredDetail("model version", version, format, () => void 0);
|
|
1141
1621
|
return;
|
|
1142
1622
|
}
|
|
1143
1623
|
ui.section("Model Version Detail:");
|
|
@@ -1169,38 +1649,19 @@ function renderModelVersionDetail(version, format = "pretty") {
|
|
|
1169
1649
|
console.log();
|
|
1170
1650
|
}
|
|
1171
1651
|
function renderModelFileList(files, format = "pretty") {
|
|
1172
|
-
if (
|
|
1173
|
-
|
|
1652
|
+
if (format !== "pretty") {
|
|
1653
|
+
renderFileList(files, format);
|
|
1174
1654
|
return;
|
|
1175
1655
|
}
|
|
1176
|
-
if (
|
|
1177
|
-
|
|
1178
|
-
id: f.uuid,
|
|
1179
|
-
path: f.path,
|
|
1180
|
-
type: f.type,
|
|
1181
|
-
formats: f.formats.join(", "),
|
|
1182
|
-
result: f.result
|
|
1183
|
-
}));
|
|
1184
|
-
console.log(
|
|
1185
|
-
formatOutput(
|
|
1186
|
-
rows,
|
|
1187
|
-
[
|
|
1188
|
-
{ key: "id", label: "ID" },
|
|
1189
|
-
{ key: "path", label: "Path" },
|
|
1190
|
-
{ key: "type", label: "Type" },
|
|
1191
|
-
{ key: "formats", label: "Formats" },
|
|
1192
|
-
{ key: "result", label: "Result" }
|
|
1193
|
-
],
|
|
1194
|
-
format
|
|
1195
|
-
)
|
|
1196
|
-
);
|
|
1656
|
+
if (files.length === 0) {
|
|
1657
|
+
ui.emptyList("files");
|
|
1197
1658
|
return;
|
|
1198
1659
|
}
|
|
1199
1660
|
renderFileList(files);
|
|
1200
1661
|
}
|
|
1201
1662
|
|
|
1202
1663
|
// src/cli/renderer/redteam.ts
|
|
1203
|
-
import
|
|
1664
|
+
import chalk8 from "chalk";
|
|
1204
1665
|
import { dump as yamlDump3 } from "js-yaml";
|
|
1205
1666
|
function renderRedteamHeader() {
|
|
1206
1667
|
ui.header("Prisma AIRS \u2014 AI Red Team", "Adversarial scan operations");
|
|
@@ -1208,37 +1669,37 @@ function renderRedteamHeader() {
|
|
|
1208
1669
|
function severityColor(severity) {
|
|
1209
1670
|
switch (severity.toUpperCase()) {
|
|
1210
1671
|
case "CRITICAL":
|
|
1211
|
-
return
|
|
1672
|
+
return chalk8.red;
|
|
1212
1673
|
case "HIGH":
|
|
1213
|
-
return
|
|
1674
|
+
return chalk8.magenta;
|
|
1214
1675
|
case "MEDIUM":
|
|
1215
|
-
return
|
|
1676
|
+
return chalk8.yellow;
|
|
1216
1677
|
case "LOW":
|
|
1217
|
-
return
|
|
1678
|
+
return chalk8.cyan;
|
|
1218
1679
|
default:
|
|
1219
|
-
return
|
|
1680
|
+
return chalk8.dim;
|
|
1220
1681
|
}
|
|
1221
1682
|
}
|
|
1222
|
-
function
|
|
1683
|
+
function statusColor3(status) {
|
|
1223
1684
|
switch (status) {
|
|
1224
1685
|
case "COMPLETED":
|
|
1225
|
-
return
|
|
1686
|
+
return chalk8.green;
|
|
1226
1687
|
case "RUNNING":
|
|
1227
|
-
return
|
|
1688
|
+
return chalk8.blue;
|
|
1228
1689
|
case "QUEUED":
|
|
1229
1690
|
case "INIT":
|
|
1230
|
-
return
|
|
1691
|
+
return chalk8.yellow;
|
|
1231
1692
|
case "FAILED":
|
|
1232
1693
|
case "ABORTED":
|
|
1233
|
-
return
|
|
1694
|
+
return chalk8.red;
|
|
1234
1695
|
case "PARTIALLY_COMPLETE":
|
|
1235
|
-
return
|
|
1696
|
+
return chalk8.yellow;
|
|
1236
1697
|
default:
|
|
1237
|
-
return
|
|
1698
|
+
return chalk8.white;
|
|
1238
1699
|
}
|
|
1239
1700
|
}
|
|
1240
1701
|
function activeState(active) {
|
|
1241
|
-
return
|
|
1702
|
+
return statusColor3(active ? "COMPLETED" : "FAILED")(active ? "active" : "inactive");
|
|
1242
1703
|
}
|
|
1243
1704
|
function renderScanStatus(job) {
|
|
1244
1705
|
ui.section("Scan Status:");
|
|
@@ -1248,7 +1709,7 @@ function renderScanStatus(job) {
|
|
|
1248
1709
|
["Type", job.jobType]
|
|
1249
1710
|
];
|
|
1250
1711
|
if (job.targetName) pairs.push(["Target", job.targetName]);
|
|
1251
|
-
pairs.push(["Status",
|
|
1712
|
+
pairs.push(["Status", statusColor3(job.status)(job.status)]);
|
|
1252
1713
|
if (job.total != null && job.completed != null) {
|
|
1253
1714
|
pairs.push(["Progress", `${job.completed}/${job.total}`]);
|
|
1254
1715
|
}
|
|
@@ -1291,9 +1752,9 @@ function renderScanList(jobs, format = "pretty") {
|
|
|
1291
1752
|
for (const job of jobs) {
|
|
1292
1753
|
ui.dim(job.uuid);
|
|
1293
1754
|
console.log(
|
|
1294
|
-
` ${job.name} ${
|
|
1755
|
+
` ${job.name} ${statusColor3(job.status)(job.status)} ${job.jobType}${job.score != null ? ` score: ${job.score}` : ""}`
|
|
1295
1756
|
);
|
|
1296
|
-
if (job.createdAt) console.log(` ${
|
|
1757
|
+
if (job.createdAt) console.log(` ${chalk8.dim(job.createdAt)}`);
|
|
1297
1758
|
console.log();
|
|
1298
1759
|
}
|
|
1299
1760
|
}
|
|
@@ -1308,7 +1769,7 @@ function renderStaticReport(report) {
|
|
|
1308
1769
|
for (const s of report.severityBreakdown) {
|
|
1309
1770
|
const color = severityColor(s.severity);
|
|
1310
1771
|
console.log(
|
|
1311
|
-
` ${color(s.severity.padEnd(10))} ${
|
|
1772
|
+
` ${color(s.severity.padEnd(10))} ${chalk8.red(`${s.successful} bypassed`)} ${chalk8.green(`${s.failed} blocked`)}`
|
|
1312
1773
|
);
|
|
1313
1774
|
}
|
|
1314
1775
|
}
|
|
@@ -1386,10 +1847,10 @@ function renderAttackList(attacks, options) {
|
|
|
1386
1847
|
}
|
|
1387
1848
|
ui.section("Attacks:");
|
|
1388
1849
|
for (const a of attacks) {
|
|
1389
|
-
const sev = a.severity ? severityColor(a.severity)(a.severity.padEnd(10)) :
|
|
1390
|
-
const result = a.successful ?
|
|
1850
|
+
const sev = a.severity ? severityColor(a.severity)(a.severity.padEnd(10)) : chalk8.dim("N/A".padEnd(10));
|
|
1851
|
+
const result = a.successful ? chalk8.red("BYPASSED") : chalk8.green("BLOCKED");
|
|
1391
1852
|
const label = a.subCategoryDisplayName ?? a.subCategory ?? "\u2014";
|
|
1392
|
-
console.log(` ${sev} ${result} ${label}${a.category ?
|
|
1853
|
+
console.log(` ${sev} ${result} ${label}${a.category ? chalk8.dim(` [${a.category}]`) : ""}`);
|
|
1393
1854
|
}
|
|
1394
1855
|
if (options?.footnote) ui.dim(options.footnote);
|
|
1395
1856
|
console.log();
|
|
@@ -1409,11 +1870,11 @@ function renderCustomAttackList(attacks) {
|
|
|
1409
1870
|
}
|
|
1410
1871
|
ui.section("Custom Attacks:");
|
|
1411
1872
|
for (const a of attacks) {
|
|
1412
|
-
const result = a.threat ?
|
|
1873
|
+
const result = a.threat ? chalk8.red("THREAT") : chalk8.green("SAFE");
|
|
1413
1874
|
const prompt = a.promptText.length > 80 ? `${a.promptText.substring(0, 77)}...` : a.promptText;
|
|
1414
|
-
const asrStr = a.asr != null ?
|
|
1875
|
+
const asrStr = a.asr != null ? chalk8.dim(` ASR: ${a.asr.toFixed(1)}%`) : "";
|
|
1415
1876
|
console.log(` ${result}${asrStr} ${prompt}`);
|
|
1416
|
-
if (a.goal) console.log(` ${
|
|
1877
|
+
if (a.goal) console.log(` ${chalk8.dim(a.goal)}`);
|
|
1417
1878
|
}
|
|
1418
1879
|
console.log();
|
|
1419
1880
|
}
|
|
@@ -1460,11 +1921,11 @@ function renderCategories(categories) {
|
|
|
1460
1921
|
ui.section("Attack Categories:");
|
|
1461
1922
|
for (const c of categories) {
|
|
1462
1923
|
console.log(
|
|
1463
|
-
` ${
|
|
1924
|
+
` ${chalk8.bold(c.displayName)} ${chalk8.cyan(`(${c.id})`)}${c.description ? chalk8.dim(` \u2014 ${c.description}`) : ""}`
|
|
1464
1925
|
);
|
|
1465
1926
|
for (const sc of c.subCategories) {
|
|
1466
1927
|
console.log(
|
|
1467
|
-
` ${
|
|
1928
|
+
` ${chalk8.dim("\u2022")} ${sc.displayName} ${chalk8.cyan(`(${sc.id})`)}${sc.description ? chalk8.dim(` \u2014 ${sc.description}`) : ""}`
|
|
1468
1929
|
);
|
|
1469
1930
|
}
|
|
1470
1931
|
console.log();
|
|
@@ -1600,11 +2061,11 @@ function renderPromptList(prompts, format = "pretty") {
|
|
|
1600
2061
|
}
|
|
1601
2062
|
ui.section("Prompts:");
|
|
1602
2063
|
for (const p of prompts) {
|
|
1603
|
-
const status = p.active ?
|
|
2064
|
+
const status = p.active ? chalk8.green("active") : chalk8.dim("inactive");
|
|
1604
2065
|
const text = p.prompt.length > 80 ? `${p.prompt.substring(0, 77)}...` : p.prompt;
|
|
1605
|
-
console.log(` ${
|
|
2066
|
+
console.log(` ${chalk8.dim(p.uuid)} ${status}`);
|
|
1606
2067
|
console.log(` ${text}`);
|
|
1607
|
-
if (p.goal) console.log(` ${
|
|
2068
|
+
if (p.goal) console.log(` ${chalk8.dim(`Goal: ${p.goal}`)}`);
|
|
1608
2069
|
}
|
|
1609
2070
|
console.log();
|
|
1610
2071
|
}
|
|
@@ -1621,7 +2082,7 @@ function renderPromptDetail(p, format = "pretty") {
|
|
|
1621
2082
|
const pairs = [
|
|
1622
2083
|
["UUID", p.uuid],
|
|
1623
2084
|
["Set UUID", p.promptSetId],
|
|
1624
|
-
["Status", p.active ?
|
|
2085
|
+
["Status", p.active ? chalk8.green("active") : chalk8.dim("inactive")],
|
|
1625
2086
|
["Prompt", p.prompt]
|
|
1626
2087
|
];
|
|
1627
2088
|
if (p.goal) pairs.push(["Goal", p.goal]);
|
|
@@ -1653,7 +2114,7 @@ function renderPropertyNames(names, format = "pretty") {
|
|
|
1653
2114
|
function renderAuthValidation(result) {
|
|
1654
2115
|
ui.section("Auth Validation:");
|
|
1655
2116
|
const pairs = [
|
|
1656
|
-
["Validated", result.validated ?
|
|
2117
|
+
["Validated", result.validated ? chalk8.green("yes") : chalk8.red("no")]
|
|
1657
2118
|
];
|
|
1658
2119
|
if (result.tokenPreview) pairs.push(["Token", result.tokenPreview]);
|
|
1659
2120
|
if (result.expiresIn != null) pairs.push(["Expires In", `${result.expiresIn}s`]);
|
|
@@ -1671,7 +2132,7 @@ function renderTargetTemplates(templates) {
|
|
|
1671
2132
|
function renderEulaStatus(status) {
|
|
1672
2133
|
ui.section("EULA Status:");
|
|
1673
2134
|
const pairs = [
|
|
1674
|
-
["Accepted", status.isAccepted ?
|
|
2135
|
+
["Accepted", status.isAccepted ? chalk8.green("yes") : chalk8.red("no")]
|
|
1675
2136
|
];
|
|
1676
2137
|
if (status.acceptedAt) pairs.push(["Accepted At", status.acceptedAt]);
|
|
1677
2138
|
if (status.acceptedByUserId) pairs.push(["Accepted By", status.acceptedByUserId]);
|
|
@@ -1709,7 +2170,7 @@ function renderInstanceResponse(resp) {
|
|
|
1709
2170
|
if (resp.tenantId) pairs.push(["Tenant ID", resp.tenantId]);
|
|
1710
2171
|
if (resp.appId) pairs.push(["App ID", resp.appId]);
|
|
1711
2172
|
if (resp.isSuccess != null) {
|
|
1712
|
-
pairs.push(["Success", resp.isSuccess ?
|
|
2173
|
+
pairs.push(["Success", resp.isSuccess ? chalk8.green("yes") : chalk8.red("no")]);
|
|
1713
2174
|
}
|
|
1714
2175
|
ui.keyValue(pairs);
|
|
1715
2176
|
console.log();
|
|
@@ -1751,13 +2212,13 @@ function renderRegistryCredentials(creds, format = "pretty") {
|
|
|
1751
2212
|
function channelStatusColor(status) {
|
|
1752
2213
|
switch (status.toUpperCase()) {
|
|
1753
2214
|
case "ONLINE":
|
|
1754
|
-
return
|
|
2215
|
+
return chalk8.green;
|
|
1755
2216
|
case "DRAFT":
|
|
1756
|
-
return
|
|
2217
|
+
return chalk8.yellow;
|
|
1757
2218
|
case "OFFLINE":
|
|
1758
|
-
return
|
|
2219
|
+
return chalk8.red;
|
|
1759
2220
|
default:
|
|
1760
|
-
return
|
|
2221
|
+
return chalk8.white;
|
|
1761
2222
|
}
|
|
1762
2223
|
}
|
|
1763
2224
|
function renderChannelList(channels, format = "pretty") {
|
|
@@ -1791,7 +2252,7 @@ function renderChannelList(channels, format = "pretty") {
|
|
|
1791
2252
|
ui.section("Network Broker Channels:");
|
|
1792
2253
|
for (const c of channels) {
|
|
1793
2254
|
if (c.uuid) ui.dim(c.uuid);
|
|
1794
|
-
const status = c.status ? channelStatusColor(c.status)(c.status) :
|
|
2255
|
+
const status = c.status ? channelStatusColor(c.status)(c.status) : chalk8.dim("unknown");
|
|
1795
2256
|
const clients = c.connectedClientsCount != null ? ` clients: ${c.connectedClientsCount}` : "";
|
|
1796
2257
|
console.log(` ${c.name ?? "(unnamed)"} ${status}${clients}`);
|
|
1797
2258
|
console.log();
|
|
@@ -1869,7 +2330,7 @@ function renderLanguages(data, format = "pretty") {
|
|
|
1869
2330
|
}
|
|
1870
2331
|
ui.section("Languages:");
|
|
1871
2332
|
for (const l of data.languages) {
|
|
1872
|
-
console.log(` ${
|
|
2333
|
+
console.log(` ${chalk8.dim(l.code)} ${l.name}`);
|
|
1873
2334
|
}
|
|
1874
2335
|
console.log();
|
|
1875
2336
|
}
|
|
@@ -1903,45 +2364,140 @@ function renderErrorLogs(logs, format = "pretty") {
|
|
|
1903
2364
|
}
|
|
1904
2365
|
ui.section("Target-Profile Error Logs:");
|
|
1905
2366
|
for (const l of logs) {
|
|
1906
|
-
const type = l.errorType ?
|
|
2367
|
+
const type = l.errorType ? chalk8.red(l.errorType) : chalk8.dim("error");
|
|
1907
2368
|
console.log(
|
|
1908
|
-
` ${
|
|
2369
|
+
` ${chalk8.dim(l.createdAt)} ${type}${l.errorSource ? ` (${l.errorSource})` : ""}`
|
|
1909
2370
|
);
|
|
1910
2371
|
if (l.errorMessage) console.log(` ${l.errorMessage}`);
|
|
1911
|
-
if (l.jobId) console.log(` ${
|
|
2372
|
+
if (l.jobId) console.log(` ${chalk8.dim(`job: ${l.jobId}`)}`);
|
|
2373
|
+
console.log();
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function renderAdapterList(adapters, format = "pretty", totalItems) {
|
|
2377
|
+
if (adapters.length === 0) {
|
|
2378
|
+
ui.emptyList("adapters");
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
if (format !== "pretty") {
|
|
2382
|
+
const rows = adapters.map((a) => ({
|
|
2383
|
+
uuid: a.uuid,
|
|
2384
|
+
name: a.name,
|
|
2385
|
+
status: a.status,
|
|
2386
|
+
targets: a.targetCount ?? "",
|
|
2387
|
+
updated: a.updatedAt ?? ""
|
|
2388
|
+
}));
|
|
2389
|
+
console.log(
|
|
2390
|
+
formatOutput(
|
|
2391
|
+
rows,
|
|
2392
|
+
[
|
|
2393
|
+
{ key: "uuid", label: "UUID" },
|
|
2394
|
+
{ key: "name", label: "Name" },
|
|
2395
|
+
{ key: "status", label: "Status" },
|
|
2396
|
+
{ key: "targets", label: "Targets" },
|
|
2397
|
+
{ key: "updated", label: "Updated" }
|
|
2398
|
+
],
|
|
2399
|
+
format
|
|
2400
|
+
)
|
|
2401
|
+
);
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
ui.section("Custom Target Adapters:");
|
|
2405
|
+
for (const a of adapters) {
|
|
2406
|
+
ui.dim(a.uuid);
|
|
2407
|
+
const status = a.status === "ACTIVE" ? chalk8.green(a.status) : chalk8.yellow(a.status);
|
|
2408
|
+
const targets = a.targetCount != null ? ` targets: ${a.targetCount}` : "";
|
|
2409
|
+
console.log(` ${a.name} ${status}${targets}`);
|
|
1912
2410
|
console.log();
|
|
1913
2411
|
}
|
|
2412
|
+
if (totalItems !== void 0) ui.dim(`${totalItems} total`);
|
|
2413
|
+
}
|
|
2414
|
+
function renderAdapterDetail(adapter, format = "pretty") {
|
|
2415
|
+
if (format !== "pretty") {
|
|
2416
|
+
console.log(format === "json" ? JSON.stringify(adapter, null, 2) : yamlDump3(adapter));
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
ui.section("Adapter Detail:");
|
|
2420
|
+
const pairs = [
|
|
2421
|
+
["UUID", adapter.uuid],
|
|
2422
|
+
["Name", adapter.name],
|
|
2423
|
+
[
|
|
2424
|
+
"Status",
|
|
2425
|
+
adapter.status === "ACTIVE" ? chalk8.green(adapter.status) : chalk8.yellow(adapter.status)
|
|
2426
|
+
],
|
|
2427
|
+
["Script", `${adapter.scriptB64.length} base64 chars`]
|
|
2428
|
+
];
|
|
2429
|
+
if (adapter.description != null) pairs.push(["Description", adapter.description]);
|
|
2430
|
+
if (adapter.networkBrokerChannelUuid != null)
|
|
2431
|
+
pairs.push(["Broker Channel", adapter.networkBrokerChannelUuid]);
|
|
2432
|
+
if (adapter.targetCount != null) pairs.push(["Targets", adapter.targetCount]);
|
|
2433
|
+
if (adapter.createdAt != null) pairs.push(["Created", adapter.createdAt]);
|
|
2434
|
+
if (adapter.updatedAt != null) pairs.push(["Updated", adapter.updatedAt]);
|
|
2435
|
+
ui.keyValue(pairs);
|
|
2436
|
+
if (adapter.variables.length > 0) {
|
|
2437
|
+
ui.section("Variables:");
|
|
2438
|
+
ui.keyValue(
|
|
2439
|
+
adapter.variables.map((v) => [
|
|
2440
|
+
`${v.key} (${v.type})`,
|
|
2441
|
+
v.isRedacted ? chalk8.dim("(redacted)") : v.value ?? ""
|
|
2442
|
+
])
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
console.log();
|
|
2446
|
+
}
|
|
2447
|
+
function renderAdapterValidation(result, format = "pretty") {
|
|
2448
|
+
if (format !== "pretty") {
|
|
2449
|
+
console.log(format === "json" ? JSON.stringify(result, null, 2) : yamlDump3(result));
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (result.validated) {
|
|
2453
|
+
ui.success("Adapter script validated");
|
|
2454
|
+
} else {
|
|
2455
|
+
ui.error("Adapter script validation FAILED");
|
|
2456
|
+
}
|
|
2457
|
+
if (result.stdout) {
|
|
2458
|
+
ui.section("stdout:");
|
|
2459
|
+
console.log(result.stdout);
|
|
2460
|
+
}
|
|
2461
|
+
if (result.stderr) {
|
|
2462
|
+
ui.section("stderr:");
|
|
2463
|
+
console.log(chalk8.red(result.stderr));
|
|
2464
|
+
}
|
|
2465
|
+
if (result.traceback) {
|
|
2466
|
+
ui.section("traceback:");
|
|
2467
|
+
console.log(chalk8.red(result.traceback));
|
|
2468
|
+
}
|
|
2469
|
+
console.log();
|
|
1914
2470
|
}
|
|
1915
2471
|
|
|
1916
2472
|
// src/cli/renderer/runtime.ts
|
|
1917
|
-
import
|
|
2473
|
+
import chalk9 from "chalk";
|
|
1918
2474
|
function renderScanProgress(job) {
|
|
1919
2475
|
if (job.total != null && job.completed != null && job.total > 0) {
|
|
1920
2476
|
const pct = Math.round(job.completed / job.total * 100);
|
|
1921
2477
|
const bar = "\u2588".repeat(Math.round(pct / 5)) + "\u2591".repeat(20 - Math.round(pct / 5));
|
|
1922
2478
|
process.stdout.write(
|
|
1923
|
-
`\r ${
|
|
2479
|
+
`\r ${statusColor4(job.status)(job.status)} ${bar} ${pct}% (${job.completed}/${job.total})`
|
|
1924
2480
|
);
|
|
1925
2481
|
} else {
|
|
1926
|
-
process.stdout.write(`\r ${
|
|
2482
|
+
process.stdout.write(`\r ${statusColor4(job.status)(job.status)}...`);
|
|
1927
2483
|
}
|
|
1928
2484
|
}
|
|
1929
|
-
function
|
|
2485
|
+
function statusColor4(status) {
|
|
1930
2486
|
switch (status) {
|
|
1931
2487
|
case "COMPLETED":
|
|
1932
|
-
return
|
|
2488
|
+
return chalk9.green;
|
|
1933
2489
|
case "RUNNING":
|
|
1934
|
-
return
|
|
2490
|
+
return chalk9.blue;
|
|
1935
2491
|
case "QUEUED":
|
|
1936
2492
|
case "INIT":
|
|
1937
|
-
return
|
|
2493
|
+
return chalk9.yellow;
|
|
1938
2494
|
case "FAILED":
|
|
1939
2495
|
case "ABORTED":
|
|
1940
|
-
return
|
|
2496
|
+
return chalk9.red;
|
|
1941
2497
|
case "PARTIALLY_COMPLETE":
|
|
1942
|
-
return
|
|
2498
|
+
return chalk9.yellow;
|
|
1943
2499
|
default:
|
|
1944
|
-
return
|
|
2500
|
+
return chalk9.white;
|
|
1945
2501
|
}
|
|
1946
2502
|
}
|
|
1947
2503
|
function renderRuntimeConfigHeader() {
|
|
@@ -1971,8 +2527,8 @@ function renderProfileList(profiles, format = "pretty") {
|
|
|
1971
2527
|
ui.section("Security Profiles:");
|
|
1972
2528
|
for (const p of profiles) {
|
|
1973
2529
|
ui.dim(p.profileId);
|
|
1974
|
-
const status = p.active ?
|
|
1975
|
-
const rev = p.revision != null ?
|
|
2530
|
+
const status = p.active ? chalk9.green("active") : chalk9.yellow("inactive");
|
|
2531
|
+
const rev = p.revision != null ? chalk9.dim(` rev:${p.revision}`) : "";
|
|
1976
2532
|
console.log(` ${p.profileName} ${status}${rev}`);
|
|
1977
2533
|
}
|
|
1978
2534
|
console.log();
|
|
@@ -1982,7 +2538,7 @@ function renderProfileDetail(profile) {
|
|
|
1982
2538
|
const pairs = [
|
|
1983
2539
|
["ID", profile.profileId],
|
|
1984
2540
|
["Name", profile.profileName],
|
|
1985
|
-
["Status", profile.active ?
|
|
2541
|
+
["Status", profile.active ? chalk9.green("active") : chalk9.yellow("inactive")]
|
|
1986
2542
|
];
|
|
1987
2543
|
if (profile.revision != null) pairs.push(["Revision", profile.revision]);
|
|
1988
2544
|
if (profile.createdBy) pairs.push(["Created", profile.createdBy]);
|
|
@@ -2086,8 +2642,8 @@ function renderTopicList(topics, format = "pretty") {
|
|
|
2086
2642
|
ui.section("Custom Topics:");
|
|
2087
2643
|
for (const t of topics) {
|
|
2088
2644
|
ui.dim(String(t.topic_id));
|
|
2089
|
-
const rev = t.revision != null ?
|
|
2090
|
-
const desc = t.description ?
|
|
2645
|
+
const rev = t.revision != null ? chalk9.dim(` rev:${t.revision}`) : "";
|
|
2646
|
+
const desc = t.description ? chalk9.dim(` \u2014 ${t.description.slice(0, 80)}`) : "";
|
|
2091
2647
|
console.log(` ${t.topic_name}${rev}${desc}`);
|
|
2092
2648
|
}
|
|
2093
2649
|
console.log();
|
|
@@ -2145,8 +2701,8 @@ function renderApiKeyList(keys, format = "pretty") {
|
|
|
2145
2701
|
ui.section("API Keys:");
|
|
2146
2702
|
for (const k of keys) {
|
|
2147
2703
|
ui.dim(k.id);
|
|
2148
|
-
const last8 = k.last8 ?
|
|
2149
|
-
const expires = k.expiresAt ?
|
|
2704
|
+
const last8 = k.last8 ? chalk9.dim(` key: \u2026${k.last8}`) : "";
|
|
2705
|
+
const expires = k.expiresAt ? chalk9.dim(` expires: ${k.expiresAt}`) : "";
|
|
2150
2706
|
console.log(` ${k.name}${last8}${expires}`);
|
|
2151
2707
|
}
|
|
2152
2708
|
console.log();
|
|
@@ -2191,7 +2747,7 @@ function renderCustomerAppList(apps, format = "pretty") {
|
|
|
2191
2747
|
ui.section("Customer Apps:");
|
|
2192
2748
|
for (const a of apps) {
|
|
2193
2749
|
if (a.id) ui.dim(a.id);
|
|
2194
|
-
const desc = a.description ?
|
|
2750
|
+
const desc = a.description ? chalk9.dim(` \u2014 ${a.description.slice(0, 80)}`) : "";
|
|
2195
2751
|
console.log(` ${a.name}${desc}`);
|
|
2196
2752
|
}
|
|
2197
2753
|
console.log();
|
|
@@ -2315,9 +2871,9 @@ function renderDeploymentProfileList(profiles, format = "pretty") {
|
|
|
2315
2871
|
const name = p.raw.dp_name ?? p.raw.profile_name ?? p.raw.name ?? "unknown";
|
|
2316
2872
|
const status = p.raw.status;
|
|
2317
2873
|
const authCode = p.raw.auth_code;
|
|
2318
|
-
const
|
|
2874
|
+
const statusColor5 = status === "active" ? chalk9.green : chalk9.dim;
|
|
2319
2875
|
console.log(
|
|
2320
|
-
` ${name}${status ? ` ${
|
|
2876
|
+
` ${name}${status ? ` ${statusColor5(status)}` : ""}${authCode ? ` ${chalk9.dim(authCode)}` : ""}`
|
|
2321
2877
|
);
|
|
2322
2878
|
}
|
|
2323
2879
|
console.log();
|
|
@@ -2350,24 +2906,260 @@ function renderScanLogList(results, pageToken, format = "pretty") {
|
|
|
2350
2906
|
);
|
|
2351
2907
|
return;
|
|
2352
2908
|
}
|
|
2353
|
-
ui.section(`Scan Logs (${results.length} results):`);
|
|
2354
|
-
for (const r of results) {
|
|
2355
|
-
const action = r.action ?? r.verdict;
|
|
2356
|
-
const app = r.app_name;
|
|
2357
|
-
const profile = r.profile_name;
|
|
2358
|
-
const ts2 = r.received_ts ?? r.timestamp;
|
|
2359
|
-
const scanId = r.scan_id;
|
|
2360
|
-
const actionColor = action === "block" ?
|
|
2361
|
-
if (scanId) ui.dim(scanId);
|
|
2362
|
-
console.log(
|
|
2363
|
-
` ${ts2 ?
|
|
2364
|
-
);
|
|
2909
|
+
ui.section(`Scan Logs (${results.length} results):`);
|
|
2910
|
+
for (const r of results) {
|
|
2911
|
+
const action = r.action ?? r.verdict;
|
|
2912
|
+
const app = r.app_name;
|
|
2913
|
+
const profile = r.profile_name;
|
|
2914
|
+
const ts2 = r.received_ts ?? r.timestamp;
|
|
2915
|
+
const scanId = r.scan_id;
|
|
2916
|
+
const actionColor = action === "block" ? chalk9.red : chalk9.green;
|
|
2917
|
+
if (scanId) ui.dim(scanId);
|
|
2918
|
+
console.log(
|
|
2919
|
+
` ${ts2 ? chalk9.dim(ts2) : ""} ${action ? actionColor(action) : ""} ${profile ? `[${profile}]` : ""} ${app ?? ""}`
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
if (pageToken) {
|
|
2923
|
+
console.log();
|
|
2924
|
+
ui.dim(`Page token: ${pageToken}`);
|
|
2925
|
+
}
|
|
2926
|
+
console.log();
|
|
2927
|
+
}
|
|
2928
|
+
|
|
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
|
+
usageError(
|
|
2935
|
+
`refusing to ${options.action ?? "proceed"} without --force in non-interactive mode`
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
const prompt = options.promptFn ?? (await import("@inquirer/prompts")).confirm;
|
|
2939
|
+
const confirmed = await prompt({ message, default: false });
|
|
2940
|
+
if (!confirmed) {
|
|
2941
|
+
ui.info("Aborted");
|
|
2942
|
+
process.exit(0);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
// src/cli/examples.ts
|
|
2947
|
+
function examples(...lines) {
|
|
2948
|
+
return `
|
|
2949
|
+
Examples:
|
|
2950
|
+
${lines.map((l) => ` $ ${l}`).join("\n")}
|
|
2951
|
+
`;
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// src/cli/commands/aigateway.ts
|
|
2955
|
+
async function createService() {
|
|
2956
|
+
const config = await loadConfig();
|
|
2957
|
+
return new SdkAiGatewayService(aiGatewayClientOptions(config));
|
|
2958
|
+
}
|
|
2959
|
+
function failWithGrantHint(err) {
|
|
2960
|
+
const hint = aiGatewayGrantHint(err);
|
|
2961
|
+
if (hint) ui.warn(`403: ${hint}`);
|
|
2962
|
+
fail(err);
|
|
2963
|
+
}
|
|
2964
|
+
function parsePlane(value) {
|
|
2965
|
+
if (value === void 0) return void 0;
|
|
2966
|
+
if (value !== "data" && value !== "admin") {
|
|
2967
|
+
usageError(`Invalid --plane '${value}'. Valid planes: data, admin`);
|
|
2968
|
+
}
|
|
2969
|
+
return value;
|
|
2970
|
+
}
|
|
2971
|
+
function parseStatus(value) {
|
|
2972
|
+
if (value === void 0) return void 0;
|
|
2973
|
+
if (value !== "active" && value !== "archived") {
|
|
2974
|
+
usageError(`Invalid --status '${value}'. Valid statuses: active, archived`);
|
|
2975
|
+
}
|
|
2976
|
+
return value;
|
|
2977
|
+
}
|
|
2978
|
+
function parseJsonFlag(raw, flag) {
|
|
2979
|
+
if (raw === void 0) return void 0;
|
|
2980
|
+
try {
|
|
2981
|
+
return JSON.parse(raw);
|
|
2982
|
+
} catch {
|
|
2983
|
+
throw new Error(`${flag} must be valid JSON`);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
function buildWorkspaceWriteRequest(opts) {
|
|
2987
|
+
const out = {};
|
|
2988
|
+
for (const key of ["name", "description", "icon"]) {
|
|
2989
|
+
if (opts[key] !== void 0) out[key] = opts[key];
|
|
2990
|
+
}
|
|
2991
|
+
const defaults = parseJsonFlag(opts.defaults, "--defaults");
|
|
2992
|
+
const metadata = parseJsonFlag(opts.metadata, "--metadata");
|
|
2993
|
+
if (defaults !== void 0 || metadata !== void 0) {
|
|
2994
|
+
out.defaults = {
|
|
2995
|
+
...typeof defaults === "object" && defaults !== null ? defaults : {},
|
|
2996
|
+
...metadata !== void 0 ? { metadata } : {}
|
|
2997
|
+
};
|
|
2365
2998
|
}
|
|
2366
|
-
if (
|
|
2367
|
-
|
|
2368
|
-
ui.dim(`Page token: ${pageToken}`);
|
|
2999
|
+
if (opts.users !== void 0) {
|
|
3000
|
+
out.users = opts.users.split(",").map((u) => u.trim()).filter(Boolean);
|
|
2369
3001
|
}
|
|
2370
|
-
|
|
3002
|
+
const usage = parseJsonFlag(opts.usageLimits, "--usage-limits");
|
|
3003
|
+
if (usage !== void 0) out.usageLimits = usage;
|
|
3004
|
+
const rate = parseJsonFlag(opts.rateLimits, "--rate-limits");
|
|
3005
|
+
if (rate !== void 0) out.rateLimits = rate;
|
|
3006
|
+
return out;
|
|
3007
|
+
}
|
|
3008
|
+
function scopeNameLooksUnrelated(name, scopeName) {
|
|
3009
|
+
const nameToken = name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3010
|
+
if (nameToken.length < 4) return false;
|
|
3011
|
+
return !scopeName.toLowerCase().replace(/[^a-z0-9]/g, "").includes(nameToken);
|
|
3012
|
+
}
|
|
3013
|
+
function registerAiGatewayCommand(program) {
|
|
3014
|
+
const aigateway = program.command("aigateway").description("AI Gateway operations");
|
|
3015
|
+
const workspace = aigateway.command("workspace").description("Manage AI Gateway workspaces");
|
|
3016
|
+
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, csv, json, yaml", "pretty").addHelpText(
|
|
3017
|
+
"after",
|
|
3018
|
+
examples(
|
|
3019
|
+
"airs aigateway workspace list",
|
|
3020
|
+
"airs aigateway workspace list --plane admin",
|
|
3021
|
+
"airs aigateway workspace list --plane admin --status archived",
|
|
3022
|
+
"airs aigateway workspace list --all --output json"
|
|
3023
|
+
)
|
|
3024
|
+
).action(async (opts) => {
|
|
3025
|
+
try {
|
|
3026
|
+
const fmt = opts.output;
|
|
3027
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
3028
|
+
const plane = parsePlane(opts.plane);
|
|
3029
|
+
const status = parseStatus(opts.status);
|
|
3030
|
+
if (opts.all && (plane !== void 0 || status !== void 0)) {
|
|
3031
|
+
usageError("--all already merges admin-plane active + archived; drop --plane/--status");
|
|
3032
|
+
}
|
|
3033
|
+
const service = await createService();
|
|
3034
|
+
const workspaces = opts.all ? await service.listAllWorkspaces() : await service.listWorkspaces(
|
|
3035
|
+
plane !== void 0 || status !== void 0 ? { plane, status } : void 0
|
|
3036
|
+
);
|
|
3037
|
+
renderWorkspaceList(workspaces, fmt);
|
|
3038
|
+
if (fmt === "pretty" && !opts.all && plane !== "admin") {
|
|
3039
|
+
ui.status(
|
|
3040
|
+
"Data-plane list shows only active workspaces you are scoped to \u2014 use --plane admin or --all for the whole tenant."
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
} catch (err) {
|
|
3044
|
+
failWithGrantHint(err);
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
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", "pretty").addHelpText(
|
|
3048
|
+
"after",
|
|
3049
|
+
examples(
|
|
3050
|
+
"airs aigateway workspace get ws-main-a-349e0e",
|
|
3051
|
+
"airs aigateway workspace get 16f7e90d-382a-4e78-b577-1b01eb5f8297 --plane admin --output json"
|
|
3052
|
+
)
|
|
3053
|
+
).action(async (ref, opts) => {
|
|
3054
|
+
try {
|
|
3055
|
+
const fmt = opts.output;
|
|
3056
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
3057
|
+
const plane = parsePlane(opts.plane);
|
|
3058
|
+
const service = await createService();
|
|
3059
|
+
const workspace2 = await service.getWorkspace(
|
|
3060
|
+
ref,
|
|
3061
|
+
plane !== void 0 ? { plane } : void 0
|
|
3062
|
+
);
|
|
3063
|
+
renderWorkspaceDetail(workspace2, fmt);
|
|
3064
|
+
} catch (err) {
|
|
3065
|
+
failWithGrantHint(err);
|
|
3066
|
+
}
|
|
3067
|
+
});
|
|
3068
|
+
workspace.command("create").description("Create a workspace (admin plane)").requiredOption("--name <name>", "Display name").requiredOption(
|
|
3069
|
+
"--scope-name <scope>",
|
|
3070
|
+
"SCM role scope granting data-plane access (e.g. ws_production_bx7qw0) \u2014 not derived from --name"
|
|
3071
|
+
).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
|
+
"after",
|
|
3073
|
+
examples(
|
|
3074
|
+
"airs aigateway workspace create --name Production --scope-name ws_production_bx7qw0",
|
|
3075
|
+
`airs aigateway workspace create --name Production --scope-name ws_production_bx7qw0 --metadata '{"env":"production"}' --rate-limits '[{"type":"requests","unit":"rpm","value":100}]'`
|
|
3076
|
+
)
|
|
3077
|
+
).action(async (opts) => {
|
|
3078
|
+
try {
|
|
3079
|
+
const fmt = opts.output;
|
|
3080
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
3081
|
+
if (scopeNameLooksUnrelated(opts.name, opts.scopeName)) {
|
|
3082
|
+
ui.warn(
|
|
3083
|
+
`--scope-name '${opts.scopeName}' shares no token with --name '${opts.name}'. A workspace created with a scope nobody holds will not appear in data-plane lists.`
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
const service = await createService();
|
|
3087
|
+
const workspace2 = await service.createWorkspace({
|
|
3088
|
+
...buildWorkspaceWriteRequest(opts),
|
|
3089
|
+
name: opts.name,
|
|
3090
|
+
scopeName: opts.scopeName
|
|
3091
|
+
});
|
|
3092
|
+
ui.success(`Workspace created: ${workspace2.id}`);
|
|
3093
|
+
renderWorkspaceDetail(workspace2, fmt);
|
|
3094
|
+
} catch (err) {
|
|
3095
|
+
failWithGrantHint(err);
|
|
3096
|
+
}
|
|
3097
|
+
});
|
|
3098
|
+
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
|
+
"after",
|
|
3100
|
+
examples(
|
|
3101
|
+
`airs aigateway workspace update ws-produc-985697 --description 'Production workloads, us-east'`
|
|
3102
|
+
)
|
|
3103
|
+
).action(async (ref, opts) => {
|
|
3104
|
+
try {
|
|
3105
|
+
const fmt = opts.output;
|
|
3106
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
3107
|
+
const request = buildWorkspaceWriteRequest(opts);
|
|
3108
|
+
if (Object.keys(request).length === 0) {
|
|
3109
|
+
usageError(
|
|
3110
|
+
"Specify at least one of --name --description --icon --metadata --defaults --usage-limits --rate-limits"
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
const service = await createService();
|
|
3114
|
+
const workspace2 = await service.updateWorkspace(ref, request);
|
|
3115
|
+
ui.success(`Workspace updated: ${workspace2.id}`);
|
|
3116
|
+
renderWorkspaceDetail(workspace2, fmt);
|
|
3117
|
+
} catch (err) {
|
|
3118
|
+
failWithGrantHint(err);
|
|
3119
|
+
}
|
|
3120
|
+
});
|
|
3121
|
+
workspace.command("delete <ref>").description("Archive a workspace (soft delete \u2014 there is no hard delete)").option("--force", "Skip confirmation prompt").addHelpText("after", examples("airs aigateway workspace delete ws-produc-985697 --force")).action(async (ref, opts) => {
|
|
3122
|
+
try {
|
|
3123
|
+
renderAiGatewayHeader();
|
|
3124
|
+
await confirmOrAbort(
|
|
3125
|
+
`Archive workspace ${ref}? (soft delete \u2014 the row remains under --status archived)`,
|
|
3126
|
+
Boolean(opts.force),
|
|
3127
|
+
{ action: `archive workspace ${ref}` }
|
|
3128
|
+
);
|
|
3129
|
+
const service = await createService();
|
|
3130
|
+
await service.deleteWorkspace(ref);
|
|
3131
|
+
ui.success(`Workspace archived: ${ref}`);
|
|
3132
|
+
ui.status(
|
|
3133
|
+
"This is a soft delete \u2014 the workspace remains visible via `workspace list --plane admin --status archived`. A `get` on it now answers 404; that is expected."
|
|
3134
|
+
);
|
|
3135
|
+
} catch (err) {
|
|
3136
|
+
failWithGrantHint(err);
|
|
3137
|
+
}
|
|
3138
|
+
});
|
|
3139
|
+
const telemetry = aigateway.command("telemetry").description("AI Gateway runtime telemetry (data plane)");
|
|
3140
|
+
const cost = telemetry.command("cost").description(
|
|
3141
|
+
"Total and per-day spend for a workspace (API reports cents; pretty output shows dollars)"
|
|
3142
|
+
).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(
|
|
3143
|
+
"after",
|
|
3144
|
+
examples(
|
|
3145
|
+
"airs aigateway telemetry cost --workspace ws-main-a-349e0e",
|
|
3146
|
+
"airs aigateway telemetry cost --workspace ws-main-a-349e0e --days 30 --output json"
|
|
3147
|
+
)
|
|
3148
|
+
).action(async (opts) => {
|
|
3149
|
+
try {
|
|
3150
|
+
const fmt = await resolveOutput(cost, opts);
|
|
3151
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
3152
|
+
const days = Number.parseInt(opts.days, 10);
|
|
3153
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
3154
|
+
usageError(`Invalid --days '${opts.days}'. Expected a positive integer`);
|
|
3155
|
+
}
|
|
3156
|
+
const service = await createService();
|
|
3157
|
+
const report = await service.getTelemetryCost({ workspaceSlug: opts.workspace, days });
|
|
3158
|
+
renderCostReport(report, fmt);
|
|
3159
|
+
} catch (err) {
|
|
3160
|
+
failWithGrantHint(err);
|
|
3161
|
+
}
|
|
3162
|
+
});
|
|
2371
3163
|
}
|
|
2372
3164
|
|
|
2373
3165
|
// src/cli/commands/completion.ts
|
|
@@ -2559,13 +3351,6 @@ function assertKnownKey(key) {
|
|
|
2559
3351
|
usageError(`Unknown config key '${key}'. Valid keys: ${CONFIG_KEYS.join(", ")}`);
|
|
2560
3352
|
}
|
|
2561
3353
|
}
|
|
2562
|
-
var LIST_FORMATS = ["pretty", "json", "yaml"];
|
|
2563
|
-
function parseListFormat(value) {
|
|
2564
|
-
if (!LIST_FORMATS.includes(value)) {
|
|
2565
|
-
usageError(`Invalid --output '${value}'. Valid formats: ${LIST_FORMATS.join(", ")}`);
|
|
2566
|
-
}
|
|
2567
|
-
return value;
|
|
2568
|
-
}
|
|
2569
3354
|
var COLUMNS = [
|
|
2570
3355
|
{ key: "key", label: "Key" },
|
|
2571
3356
|
{ key: "value", label: "Value" },
|
|
@@ -2580,9 +3365,9 @@ function registerConfigCommand(program) {
|
|
|
2580
3365
|
"airs config get mgmtTsgId"
|
|
2581
3366
|
)
|
|
2582
3367
|
);
|
|
2583
|
-
config.command("list").description("Show effective configuration with per-key source (env/file/default)").option("--output <format>", "Output format: pretty,
|
|
3368
|
+
const configList = config.command("list").description("Show effective configuration with per-key source (env/file/default)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").option("--reveal", "Show secret values in full").action(async (opts) => {
|
|
2584
3369
|
try {
|
|
2585
|
-
const fmt =
|
|
3370
|
+
const fmt = await resolveOutput(configList, opts);
|
|
2586
3371
|
const filePath = resolveConfigFilePath();
|
|
2587
3372
|
const rows = buildConfigRows(await inspectConfig(), Boolean(opts.reveal));
|
|
2588
3373
|
if (fmt === "pretty") {
|
|
@@ -2597,12 +3382,18 @@ function registerConfigCommand(program) {
|
|
|
2597
3382
|
fail(err);
|
|
2598
3383
|
}
|
|
2599
3384
|
});
|
|
2600
|
-
config.command("get <key>").description("Print a single effective config value").option("--reveal", "Show the real value of a secret key").action(async (key, opts) => {
|
|
3385
|
+
const configGet = config.command("get <key>").description("Print a single effective config value").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").option("--reveal", "Show the real value of a secret key").action(async (key, opts) => {
|
|
2601
3386
|
try {
|
|
2602
3387
|
assertKnownKey(key);
|
|
2603
3388
|
const inspected = await inspectConfig();
|
|
2604
3389
|
const entry = inspected[key];
|
|
2605
3390
|
const raw = entry.value == null ? "" : String(entry.value);
|
|
3391
|
+
const fmt = await resolveOutput(configGet, opts);
|
|
3392
|
+
const value = raw !== "" && isSecretKey(key) && !opts.reveal ? maskSecret(raw) : raw;
|
|
3393
|
+
if (fmt !== "pretty") {
|
|
3394
|
+
console.log(formatOutput([{ key, value, source: entry.source }], COLUMNS, fmt));
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
2606
3397
|
if (raw !== "" && isSecretKey(key)) {
|
|
2607
3398
|
if (opts.reveal) {
|
|
2608
3399
|
ui.status(`Warning: printing secret value for '${key}'`);
|
|
@@ -2653,39 +3444,6 @@ function registerConfigCommand(program) {
|
|
|
2653
3444
|
import { randomUUID } from "crypto";
|
|
2654
3445
|
import { readFile as readFile2 } from "fs/promises";
|
|
2655
3446
|
import { init, Scanner } from "@cdot65/prisma-airs-sdk";
|
|
2656
|
-
|
|
2657
|
-
// src/config/client-options.ts
|
|
2658
|
-
function runtimeInitOptions(config) {
|
|
2659
|
-
return {
|
|
2660
|
-
apiKey: config.airsApiKey,
|
|
2661
|
-
apiToken: config.airsApiToken,
|
|
2662
|
-
apiEndpoint: config.airsApiEndpoint,
|
|
2663
|
-
numRetries: config.airsNumRetries
|
|
2664
|
-
};
|
|
2665
|
-
}
|
|
2666
|
-
function redTeamClientOptions(config) {
|
|
2667
|
-
return {
|
|
2668
|
-
clientId: config.mgmtClientId,
|
|
2669
|
-
clientSecret: config.mgmtClientSecret,
|
|
2670
|
-
tsgId: config.mgmtTsgId,
|
|
2671
|
-
dataEndpoint: config.redTeamDataEndpoint,
|
|
2672
|
-
mgmtEndpoint: config.redTeamMgmtEndpoint,
|
|
2673
|
-
tokenEndpoint: config.redTeamTokenEndpoint ?? config.mgmtTokenEndpoint,
|
|
2674
|
-
networkBrokerEndpoint: config.redTeamNetworkBrokerEndpoint
|
|
2675
|
-
};
|
|
2676
|
-
}
|
|
2677
|
-
function modelSecurityClientOptions(config) {
|
|
2678
|
-
return {
|
|
2679
|
-
clientId: config.mgmtClientId,
|
|
2680
|
-
clientSecret: config.mgmtClientSecret,
|
|
2681
|
-
tsgId: config.mgmtTsgId,
|
|
2682
|
-
dataEndpoint: config.modelSecDataEndpoint,
|
|
2683
|
-
mgmtEndpoint: config.modelSecMgmtEndpoint,
|
|
2684
|
-
tokenEndpoint: config.modelSecTokenEndpoint ?? config.mgmtTokenEndpoint
|
|
2685
|
-
};
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
// src/cli/commands/doctor.ts
|
|
2689
3447
|
var DOCTOR_TIMEOUT_MS = 5e3;
|
|
2690
3448
|
var MIN_NODE_MAJOR = 20;
|
|
2691
3449
|
function checkNodeVersion(version = process.version) {
|
|
@@ -2883,6 +3641,50 @@ async function checkManagementAuth(probe, hasCreds, timeoutMs = DOCTOR_TIMEOUT_M
|
|
|
2883
3641
|
};
|
|
2884
3642
|
}
|
|
2885
3643
|
}
|
|
3644
|
+
async function checkAiGatewayApi(probe, hasCreds, timeoutMs = DOCTOR_TIMEOUT_MS) {
|
|
3645
|
+
const name = "AI Gateway API";
|
|
3646
|
+
if (!hasCreds) {
|
|
3647
|
+
return {
|
|
3648
|
+
name,
|
|
3649
|
+
status: "warn",
|
|
3650
|
+
detail: "skipped \u2014 management credentials not configured",
|
|
3651
|
+
hint: "Set PANW_MGMT_CLIENT_ID, PANW_MGMT_CLIENT_SECRET, PANW_MGMT_TSG_ID"
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
try {
|
|
3655
|
+
const result = await withTimeout(probe(), timeoutMs);
|
|
3656
|
+
if (result === TIMED_OUT) {
|
|
3657
|
+
return {
|
|
3658
|
+
name,
|
|
3659
|
+
status: "fail",
|
|
3660
|
+
detail: `timed out after ${timeoutMs}ms \u2014 network unreachable or endpoint not responding`,
|
|
3661
|
+
hint: "Check network connectivity and PANW_AI_GW_DATA_ENDPOINT"
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
return {
|
|
3665
|
+
name,
|
|
3666
|
+
status: "pass",
|
|
3667
|
+
detail: `endpoint reachable (${result} workspace${result === 1 ? "" : "s"} in scope)`
|
|
3668
|
+
};
|
|
3669
|
+
} catch (err) {
|
|
3670
|
+
const status = httpStatus(err);
|
|
3671
|
+
const message = errMessage(err);
|
|
3672
|
+
if (status === 403) {
|
|
3673
|
+
return {
|
|
3674
|
+
name,
|
|
3675
|
+
status: "warn",
|
|
3676
|
+
detail: `endpoint reachable, but access denied (HTTP 403): ${message}`,
|
|
3677
|
+
hint: aiGatewayGrantHint(err)
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
return {
|
|
3681
|
+
name,
|
|
3682
|
+
status: "fail",
|
|
3683
|
+
detail: status !== void 0 ? `AI Gateway API error (HTTP ${status}): ${message}` : `network unreachable: ${message}`,
|
|
3684
|
+
hint: "Verify credentials and PANW_AI_GW_DATA_ENDPOINT"
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
2886
3688
|
async function defaultScannerProbe() {
|
|
2887
3689
|
const config = await loadConfig();
|
|
2888
3690
|
init(runtimeInitOptions(config));
|
|
@@ -2900,6 +3702,12 @@ async function defaultMgmtProbe() {
|
|
|
2900
3702
|
const topics = await service.listTopics();
|
|
2901
3703
|
return topics.length;
|
|
2902
3704
|
}
|
|
3705
|
+
async function defaultAiGwProbe() {
|
|
3706
|
+
const config = await loadConfig();
|
|
3707
|
+
const service = new SdkAiGatewayService(aiGatewayClientOptions(config));
|
|
3708
|
+
const workspaces = await service.listWorkspaces();
|
|
3709
|
+
return workspaces.length;
|
|
3710
|
+
}
|
|
2903
3711
|
async function runDoctor(deps = {}) {
|
|
2904
3712
|
const configFilePath = deps.configFilePath ?? resolveConfigFilePath();
|
|
2905
3713
|
const inspect = deps.inspect ?? (() => inspectConfig(configFilePath));
|
|
@@ -2923,7 +3731,12 @@ async function runDoctor(deps = {}) {
|
|
|
2923
3731
|
mgmtCreds.status === "pass",
|
|
2924
3732
|
timeoutMs
|
|
2925
3733
|
);
|
|
2926
|
-
|
|
3734
|
+
const aiGwApi = await checkAiGatewayApi(
|
|
3735
|
+
deps.aiGwProbe ?? defaultAiGwProbe,
|
|
3736
|
+
mgmtCreds.status === "pass",
|
|
3737
|
+
timeoutMs
|
|
3738
|
+
);
|
|
3739
|
+
return [node, configFile, scannerCreds, mgmtCreds, scannerApi, mgmtAuth, aiGwApi];
|
|
2927
3740
|
}
|
|
2928
3741
|
function hasFailure(checks) {
|
|
2929
3742
|
return checks.some((c) => c.status === "fail");
|
|
@@ -2933,20 +3746,6 @@ var STATUS_KIND = {
|
|
|
2933
3746
|
warn: "warn",
|
|
2934
3747
|
fail: "error"
|
|
2935
3748
|
};
|
|
2936
|
-
var DOCTOR_FORMATS = ["pretty", "json", "yaml"];
|
|
2937
|
-
function parseDoctorFormat(value) {
|
|
2938
|
-
if (!DOCTOR_FORMATS.includes(value)) {
|
|
2939
|
-
usageError(`Invalid --output '${value}'. Valid formats: ${DOCTOR_FORMATS.join(", ")}`);
|
|
2940
|
-
}
|
|
2941
|
-
return value;
|
|
2942
|
-
}
|
|
2943
|
-
function toYaml(checks) {
|
|
2944
|
-
return checks.map((c) => {
|
|
2945
|
-
const lines = [`name: ${c.name}`, `status: ${c.status}`, `detail: ${c.detail}`];
|
|
2946
|
-
if (c.hint) lines.push(`hint: ${c.hint}`);
|
|
2947
|
-
return lines.join("\n");
|
|
2948
|
-
}).join("\n---\n");
|
|
2949
|
-
}
|
|
2950
3749
|
function renderPretty(checks) {
|
|
2951
3750
|
ui.header("Doctor", "Prisma AIRS CLI preflight checks");
|
|
2952
3751
|
for (const check of checks) {
|
|
@@ -2966,18 +3765,27 @@ function renderPretty(checks) {
|
|
|
2966
3765
|
console.log("");
|
|
2967
3766
|
}
|
|
2968
3767
|
function registerDoctorCommand(program) {
|
|
2969
|
-
program.command("doctor").description("Check credentials, config, and API connectivity (preflight)").option("--output <format>", "Output format: pretty,
|
|
3768
|
+
const doctor = program.command("doctor").description("Check credentials, config, and API connectivity (preflight)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
|
|
2970
3769
|
"after",
|
|
2971
3770
|
examples("airs doctor", `airs doctor --output json | jq '.[] | select(.status != "pass")'`)
|
|
2972
3771
|
).action(async (opts) => {
|
|
2973
|
-
const fmt =
|
|
3772
|
+
const fmt = await resolveOutput(doctor, opts);
|
|
2974
3773
|
const checks = await runDoctor();
|
|
2975
|
-
if (fmt === "
|
|
2976
|
-
console.log(JSON.stringify(checks, null, 2));
|
|
2977
|
-
} else if (fmt === "yaml") {
|
|
2978
|
-
console.log(toYaml(checks));
|
|
2979
|
-
} else {
|
|
3774
|
+
if (fmt === "pretty") {
|
|
2980
3775
|
renderPretty(checks);
|
|
3776
|
+
} else {
|
|
3777
|
+
console.log(
|
|
3778
|
+
formatOutput(
|
|
3779
|
+
checks.map((check) => ({ ...check })),
|
|
3780
|
+
[
|
|
3781
|
+
{ key: "name", label: "Name" },
|
|
3782
|
+
{ key: "status", label: "Status" },
|
|
3783
|
+
{ key: "detail", label: "Detail" },
|
|
3784
|
+
{ key: "hint", label: "Hint" }
|
|
3785
|
+
],
|
|
3786
|
+
fmt
|
|
3787
|
+
)
|
|
3788
|
+
);
|
|
2981
3789
|
}
|
|
2982
3790
|
process.exit(hasFailure(checks) ? 1 : 0);
|
|
2983
3791
|
});
|
|
@@ -3005,7 +3813,7 @@ function run(bin, args, label) {
|
|
|
3005
3813
|
});
|
|
3006
3814
|
});
|
|
3007
3815
|
}
|
|
3008
|
-
async function
|
|
3816
|
+
async function createService2() {
|
|
3009
3817
|
const config = await loadConfig();
|
|
3010
3818
|
return new SdkModelSecurityService(modelSecurityClientOptions(config));
|
|
3011
3819
|
}
|
|
@@ -3016,16 +3824,18 @@ function registerModelSecurityCommand(program) {
|
|
|
3016
3824
|
try {
|
|
3017
3825
|
const fmt = opts.output;
|
|
3018
3826
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3019
|
-
const service = await
|
|
3020
|
-
const
|
|
3827
|
+
const service = await createService2();
|
|
3828
|
+
const listOptions = {
|
|
3021
3829
|
sourceTypes: opts.sourceTypes ? opts.sourceTypes.split(",").map((s) => s.trim()) : void 0,
|
|
3022
3830
|
searchQuery: opts.search,
|
|
3023
3831
|
sortField: opts.sortField,
|
|
3024
3832
|
sortDir: opts.sortDir,
|
|
3025
3833
|
enabledRules: opts.enabledRules ? opts.enabledRules.split(",").map((s) => s.trim()) : void 0,
|
|
3026
|
-
limit: Number.parseInt(opts.limit, 10)
|
|
3027
|
-
|
|
3028
|
-
|
|
3834
|
+
limit: Number.parseInt(opts.limit, 10),
|
|
3835
|
+
skip: Number(opts.offset ?? 0)
|
|
3836
|
+
};
|
|
3837
|
+
const rows = opts.all ? await service.listAllGroups({ ...listOptions, max: Number(opts.max) }) : (await service.listGroups(listOptions)).groups;
|
|
3838
|
+
renderGroupList(rows, fmt);
|
|
3029
3839
|
} catch (err) {
|
|
3030
3840
|
fail(err);
|
|
3031
3841
|
}
|
|
@@ -3034,7 +3844,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3034
3844
|
try {
|
|
3035
3845
|
const fmt = opts.output;
|
|
3036
3846
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3037
|
-
const service = await
|
|
3847
|
+
const service = await createService2();
|
|
3038
3848
|
const group = await service.getGroup(uuid);
|
|
3039
3849
|
renderGroupDetail(group, fmt);
|
|
3040
3850
|
} catch (err) {
|
|
@@ -3044,7 +3854,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3044
3854
|
groups.command("create").description("Create a security group").requiredOption("--config <path>", "JSON file with group configuration").action(async (opts) => {
|
|
3045
3855
|
try {
|
|
3046
3856
|
renderModelSecurityHeader();
|
|
3047
|
-
const service = await
|
|
3857
|
+
const service = await createService2();
|
|
3048
3858
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
3049
3859
|
const group = await service.createGroup({
|
|
3050
3860
|
name: config.name,
|
|
@@ -3061,7 +3871,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3061
3871
|
groups.command("update <uuid>").description("Update a security group").option("--name <name>", "New name").option("--description <desc>", "New description").action(async (uuid, opts) => {
|
|
3062
3872
|
try {
|
|
3063
3873
|
renderModelSecurityHeader();
|
|
3064
|
-
const service = await
|
|
3874
|
+
const service = await createService2();
|
|
3065
3875
|
const request = {};
|
|
3066
3876
|
if (opts.name) request.name = opts.name;
|
|
3067
3877
|
if (opts.description) request.description = opts.description;
|
|
@@ -3075,7 +3885,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3075
3885
|
groups.command("delete <uuid>").description("Delete a security group").action(async (uuid) => {
|
|
3076
3886
|
try {
|
|
3077
3887
|
renderModelSecurityHeader();
|
|
3078
|
-
const service = await
|
|
3888
|
+
const service = await createService2();
|
|
3079
3889
|
const { confirmed, state } = await service.deleteGroupAndVerify(uuid);
|
|
3080
3890
|
if (confirmed) {
|
|
3081
3891
|
ui.success(`Group ${uuid} deleted.`);
|
|
@@ -3105,7 +3915,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3105
3915
|
if (!useUv && !hasBin("python3")) {
|
|
3106
3916
|
fail(new Error("Neither uv nor python3 found on PATH. Install one first."));
|
|
3107
3917
|
}
|
|
3108
|
-
const service = await
|
|
3918
|
+
const service = await createService2();
|
|
3109
3919
|
const auth = await service.getPyPIAuth();
|
|
3110
3920
|
const pkg = `model-security-client[${extras}]`;
|
|
3111
3921
|
const steps = useUv ? [
|
|
@@ -3149,7 +3959,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3149
3959
|
labels.command("add <scanUuid>").description("Add labels to a scan").requiredOption("--labels <json>", "JSON array of {key, value} labels").action(async (scanUuid, opts) => {
|
|
3150
3960
|
try {
|
|
3151
3961
|
renderModelSecurityHeader();
|
|
3152
|
-
const service = await
|
|
3962
|
+
const service = await createService2();
|
|
3153
3963
|
const parsed = JSON.parse(opts.labels);
|
|
3154
3964
|
await service.addLabels(scanUuid, parsed);
|
|
3155
3965
|
ui.success("Labels added.");
|
|
@@ -3160,7 +3970,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3160
3970
|
labels.command("set <scanUuid>").description("Replace all labels on a scan").requiredOption("--labels <json>", "JSON array of {key, value} labels").action(async (scanUuid, opts) => {
|
|
3161
3971
|
try {
|
|
3162
3972
|
renderModelSecurityHeader();
|
|
3163
|
-
const service = await
|
|
3973
|
+
const service = await createService2();
|
|
3164
3974
|
const parsed = JSON.parse(opts.labels);
|
|
3165
3975
|
await service.setLabels(scanUuid, parsed);
|
|
3166
3976
|
ui.success("Labels set.");
|
|
@@ -3171,7 +3981,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3171
3981
|
labels.command("delete <scanUuid>").description("Delete labels from a scan by key").requiredOption("--keys <keys>", "Comma-separated label keys to delete").action(async (scanUuid, opts) => {
|
|
3172
3982
|
try {
|
|
3173
3983
|
renderModelSecurityHeader();
|
|
3174
|
-
const service = await
|
|
3984
|
+
const service = await createService2();
|
|
3175
3985
|
const keys = opts.keys.split(",").map((k) => k.trim());
|
|
3176
3986
|
await service.deleteLabels(scanUuid, keys);
|
|
3177
3987
|
ui.success("Labels deleted.");
|
|
@@ -3182,7 +3992,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3182
3992
|
labels.command("keys").description("List available label keys").option("--limit <n>", "Max results", "20").action(async (opts) => {
|
|
3183
3993
|
try {
|
|
3184
3994
|
renderModelSecurityHeader();
|
|
3185
|
-
const service = await
|
|
3995
|
+
const service = await createService2();
|
|
3186
3996
|
const result = await service.getLabelKeys({
|
|
3187
3997
|
limit: Number.parseInt(opts.limit, 10)
|
|
3188
3998
|
});
|
|
@@ -3194,7 +4004,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3194
4004
|
labels.command("values <key>").description("List values for a label key").option("--limit <n>", "Max results", "20").action(async (key, opts) => {
|
|
3195
4005
|
try {
|
|
3196
4006
|
renderModelSecurityHeader();
|
|
3197
|
-
const service = await
|
|
4007
|
+
const service = await createService2();
|
|
3198
4008
|
const result = await service.getLabelValues(key, {
|
|
3199
4009
|
limit: Number.parseInt(opts.limit, 10)
|
|
3200
4010
|
});
|
|
@@ -3206,7 +4016,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3206
4016
|
ms.command("pypi-auth").description("Get PyPI authentication URL for Google Artifact Registry").action(async () => {
|
|
3207
4017
|
try {
|
|
3208
4018
|
renderModelSecurityHeader();
|
|
3209
|
-
const service = await
|
|
4019
|
+
const service = await createService2();
|
|
3210
4020
|
const auth = await service.getPyPIAuth();
|
|
3211
4021
|
ui.section("PyPI Authentication");
|
|
3212
4022
|
ui.keyValue([
|
|
@@ -3220,24 +4030,26 @@ function registerModelSecurityCommand(program) {
|
|
|
3220
4030
|
const ruleInstances = ms.command("rule-instances").description("Manage rule instances in groups");
|
|
3221
4031
|
ruleInstances.command("list <groupUuid>").description("List rule instances in a security group").option("--security-rule-uuid <uuid>", "Filter by security rule UUID").option("--state <state>", "Filter by state (DISABLED, ALLOWING, BLOCKING)").option("--limit <n>", "Max results", "20").action(async (groupUuid, opts) => {
|
|
3222
4032
|
try {
|
|
3223
|
-
|
|
3224
|
-
|
|
4033
|
+
const fmt = opts.output;
|
|
4034
|
+
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4035
|
+
const service = await createService2();
|
|
3225
4036
|
const result = await service.listRuleInstances(groupUuid, {
|
|
3226
4037
|
securityRuleUuid: opts.securityRuleUuid,
|
|
3227
4038
|
state: opts.state,
|
|
3228
4039
|
limit: Number.parseInt(opts.limit, 10)
|
|
3229
4040
|
});
|
|
3230
|
-
renderRuleInstanceList(result.ruleInstances);
|
|
4041
|
+
renderRuleInstanceList(result.ruleInstances, fmt);
|
|
3231
4042
|
} catch (err) {
|
|
3232
4043
|
fail(err);
|
|
3233
4044
|
}
|
|
3234
4045
|
});
|
|
3235
|
-
ruleInstances.command("get <groupUuid> <instanceUuid>").description("Get rule instance details").action(async (groupUuid, instanceUuid) => {
|
|
4046
|
+
ruleInstances.command("get <groupUuid> <instanceUuid>").description("Get rule instance details").action(async (groupUuid, instanceUuid, opts) => {
|
|
3236
4047
|
try {
|
|
3237
|
-
|
|
3238
|
-
|
|
4048
|
+
const fmt = opts.output;
|
|
4049
|
+
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4050
|
+
const service = await createService2();
|
|
3239
4051
|
const instance = await service.getRuleInstance(groupUuid, instanceUuid);
|
|
3240
|
-
renderRuleInstanceDetail(instance);
|
|
4052
|
+
renderRuleInstanceDetail(instance, fmt);
|
|
3241
4053
|
} catch (err) {
|
|
3242
4054
|
fail(err);
|
|
3243
4055
|
}
|
|
@@ -3245,7 +4057,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3245
4057
|
ruleInstances.command("update <groupUuid> <instanceUuid>").description("Update a rule instance").requiredOption("--config <path>", "JSON file with rule instance updates").action(async (groupUuid, instanceUuid, opts) => {
|
|
3246
4058
|
try {
|
|
3247
4059
|
renderModelSecurityHeader();
|
|
3248
|
-
const service = await
|
|
4060
|
+
const service = await createService2();
|
|
3249
4061
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
3250
4062
|
const instance = await service.updateRuleInstance(groupUuid, instanceUuid, {
|
|
3251
4063
|
state: config.state,
|
|
@@ -3262,23 +4074,26 @@ function registerModelSecurityCommand(program) {
|
|
|
3262
4074
|
try {
|
|
3263
4075
|
const fmt = opts.output;
|
|
3264
4076
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3265
|
-
const service = await
|
|
3266
|
-
const
|
|
4077
|
+
const service = await createService2();
|
|
4078
|
+
const listOptions = {
|
|
3267
4079
|
sourceType: opts.sourceType,
|
|
3268
4080
|
searchQuery: opts.search,
|
|
3269
|
-
limit: Number.parseInt(opts.limit, 10)
|
|
3270
|
-
|
|
3271
|
-
|
|
4081
|
+
limit: Number.parseInt(opts.limit, 10),
|
|
4082
|
+
skip: Number(opts.offset ?? 0)
|
|
4083
|
+
};
|
|
4084
|
+
const rows = opts.all ? await service.listAllRules({ ...listOptions, max: Number(opts.max) }) : (await service.listRules(listOptions)).rules;
|
|
4085
|
+
renderRuleList(rows, fmt);
|
|
3272
4086
|
} catch (err) {
|
|
3273
4087
|
fail(err);
|
|
3274
4088
|
}
|
|
3275
4089
|
});
|
|
3276
|
-
rules.command("get <uuid>").description("Get security rule details").action(async (uuid) => {
|
|
4090
|
+
rules.command("get <uuid>").description("Get security rule details").action(async (uuid, opts) => {
|
|
3277
4091
|
try {
|
|
3278
|
-
|
|
3279
|
-
|
|
4092
|
+
const fmt = opts.output;
|
|
4093
|
+
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4094
|
+
const service = await createService2();
|
|
3280
4095
|
const rule = await service.getRule(uuid);
|
|
3281
|
-
renderRuleDetail(rule);
|
|
4096
|
+
renderRuleDetail(rule, fmt);
|
|
3282
4097
|
} catch (err) {
|
|
3283
4098
|
fail(err);
|
|
3284
4099
|
}
|
|
@@ -3295,25 +4110,28 @@ function registerModelSecurityCommand(program) {
|
|
|
3295
4110
|
try {
|
|
3296
4111
|
const fmt = opts.output;
|
|
3297
4112
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3298
|
-
const service = await
|
|
3299
|
-
const
|
|
4113
|
+
const service = await createService2();
|
|
4114
|
+
const listOptions = {
|
|
3300
4115
|
evalOutcome: opts.evalOutcome,
|
|
3301
4116
|
sourceType: opts.sourceType,
|
|
3302
4117
|
scanOrigin: opts.scanOrigin,
|
|
3303
4118
|
search: opts.search,
|
|
3304
|
-
limit: Number.parseInt(opts.limit, 10)
|
|
3305
|
-
|
|
3306
|
-
|
|
4119
|
+
limit: Number.parseInt(opts.limit, 10),
|
|
4120
|
+
skip: Number(opts.offset ?? 0)
|
|
4121
|
+
};
|
|
4122
|
+
const rows = opts.all ? await service.listAllScans({ ...listOptions, max: Number(opts.max) }) : (await service.listScans(listOptions)).scans;
|
|
4123
|
+
renderMsScanList(rows, fmt);
|
|
3307
4124
|
} catch (err) {
|
|
3308
4125
|
fail(err);
|
|
3309
4126
|
}
|
|
3310
4127
|
});
|
|
3311
|
-
scans.command("get <uuid>").description("Get scan details").action(async (uuid) => {
|
|
4128
|
+
scans.command("get <uuid>").description("Get scan details").action(async (uuid, opts) => {
|
|
3312
4129
|
try {
|
|
3313
|
-
|
|
3314
|
-
|
|
4130
|
+
const fmt = opts.output;
|
|
4131
|
+
if (fmt === "pretty") renderModelSecurityHeader();
|
|
4132
|
+
const service = await createService2();
|
|
3315
4133
|
const scan = await service.getScan(uuid);
|
|
3316
|
-
renderMsScanDetail(scan);
|
|
4134
|
+
renderMsScanDetail(scan, fmt);
|
|
3317
4135
|
} catch (err) {
|
|
3318
4136
|
fail(err);
|
|
3319
4137
|
}
|
|
@@ -3321,7 +4139,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3321
4139
|
scans.command("create").description("Create a model security scan").requiredOption("--config <path>", "JSON file with scan configuration").action(async (opts) => {
|
|
3322
4140
|
try {
|
|
3323
4141
|
renderModelSecurityHeader();
|
|
3324
|
-
const service = await
|
|
4142
|
+
const service = await createService2();
|
|
3325
4143
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
3326
4144
|
const scan = await service.createScan(config);
|
|
3327
4145
|
ui.success(`Scan created: ${scan.uuid}`);
|
|
@@ -3333,7 +4151,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3333
4151
|
scans.command("evaluations <scanUuid>").description("List rule evaluations for a scan").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
|
|
3334
4152
|
try {
|
|
3335
4153
|
renderModelSecurityHeader();
|
|
3336
|
-
const service = await
|
|
4154
|
+
const service = await createService2();
|
|
3337
4155
|
const result = await service.getEvaluations(scanUuid, {
|
|
3338
4156
|
limit: Number.parseInt(opts.limit, 10)
|
|
3339
4157
|
});
|
|
@@ -3345,7 +4163,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3345
4163
|
scans.command("evaluation <uuid>").description("Get evaluation details").action(async (uuid) => {
|
|
3346
4164
|
try {
|
|
3347
4165
|
renderModelSecurityHeader();
|
|
3348
|
-
const service = await
|
|
4166
|
+
const service = await createService2();
|
|
3349
4167
|
const evaluation = await service.getEvaluation(uuid);
|
|
3350
4168
|
renderEvaluationDetail(evaluation);
|
|
3351
4169
|
} catch (err) {
|
|
@@ -3355,7 +4173,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3355
4173
|
scans.command("violations <scanUuid>").description("List violations for a scan").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
|
|
3356
4174
|
try {
|
|
3357
4175
|
renderModelSecurityHeader();
|
|
3358
|
-
const service = await
|
|
4176
|
+
const service = await createService2();
|
|
3359
4177
|
const result = await service.getViolations(scanUuid, {
|
|
3360
4178
|
limit: Number.parseInt(opts.limit, 10)
|
|
3361
4179
|
});
|
|
@@ -3367,7 +4185,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3367
4185
|
scans.command("violation <uuid>").description("Get violation details").action(async (uuid) => {
|
|
3368
4186
|
try {
|
|
3369
4187
|
renderModelSecurityHeader();
|
|
3370
|
-
const service = await
|
|
4188
|
+
const service = await createService2();
|
|
3371
4189
|
const violation = await service.getViolation(uuid);
|
|
3372
4190
|
renderViolationDetail(violation);
|
|
3373
4191
|
} catch (err) {
|
|
@@ -3377,7 +4195,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3377
4195
|
scans.command("files <scanUuid>").description("List scanned files").option("--type <type>", "Filter by file type").option("--result <result>", "Filter by result").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
|
|
3378
4196
|
try {
|
|
3379
4197
|
renderModelSecurityHeader();
|
|
3380
|
-
const service = await
|
|
4198
|
+
const service = await createService2();
|
|
3381
4199
|
const result = await service.getFiles(scanUuid, {
|
|
3382
4200
|
type: opts.type,
|
|
3383
4201
|
result: opts.result,
|
|
@@ -3393,16 +4211,17 @@ function registerModelSecurityCommand(program) {
|
|
|
3393
4211
|
try {
|
|
3394
4212
|
const fmt = opts.output;
|
|
3395
4213
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3396
|
-
const service = await
|
|
3397
|
-
const
|
|
4214
|
+
const service = await createService2();
|
|
4215
|
+
const listOptions = {
|
|
3398
4216
|
search: opts.search,
|
|
3399
4217
|
searchQuery: opts.searchQuery,
|
|
3400
4218
|
sortField: opts.sortField,
|
|
3401
4219
|
sortOrder: opts.sortOrder,
|
|
3402
4220
|
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
3403
4221
|
skip: opts.offset ? Number.parseInt(opts.offset, 10) : void 0
|
|
3404
|
-
}
|
|
3405
|
-
|
|
4222
|
+
};
|
|
4223
|
+
const rows = opts.all ? await service.listAllModels({ ...listOptions, max: Number(opts.max) }) : (await service.listModels(listOptions)).models;
|
|
4224
|
+
renderModelList(rows, fmt);
|
|
3406
4225
|
} catch (err) {
|
|
3407
4226
|
fail(err);
|
|
3408
4227
|
}
|
|
@@ -3411,7 +4230,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3411
4230
|
try {
|
|
3412
4231
|
const fmt = opts.output;
|
|
3413
4232
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3414
|
-
const service = await
|
|
4233
|
+
const service = await createService2();
|
|
3415
4234
|
const model = await service.getModel(uuid);
|
|
3416
4235
|
renderModelDetail(model, fmt);
|
|
3417
4236
|
} catch (err) {
|
|
@@ -3422,7 +4241,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3422
4241
|
try {
|
|
3423
4242
|
const fmt = opts.output;
|
|
3424
4243
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3425
|
-
const service = await
|
|
4244
|
+
const service = await createService2();
|
|
3426
4245
|
const result = await service.listModelVersions(modelUuid, {
|
|
3427
4246
|
sortOrder: opts.sortOrder,
|
|
3428
4247
|
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
@@ -3437,7 +4256,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3437
4256
|
try {
|
|
3438
4257
|
const fmt = opts.output;
|
|
3439
4258
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3440
|
-
const service = await
|
|
4259
|
+
const service = await createService2();
|
|
3441
4260
|
const version = await service.getModelVersion(uuid);
|
|
3442
4261
|
renderModelVersionDetail(version, fmt);
|
|
3443
4262
|
} catch (err) {
|
|
@@ -3448,7 +4267,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3448
4267
|
try {
|
|
3449
4268
|
const fmt = opts.output;
|
|
3450
4269
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3451
|
-
const service = await
|
|
4270
|
+
const service = await createService2();
|
|
3452
4271
|
const result = await service.listModelVersionFiles(modelVersionUuid, {
|
|
3453
4272
|
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
3454
4273
|
skip: opts.offset ? Number.parseInt(opts.offset, 10) : void 0
|
|
@@ -3464,25 +4283,8 @@ function registerModelSecurityCommand(program) {
|
|
|
3464
4283
|
import * as fs2 from "fs";
|
|
3465
4284
|
import * as path from "path";
|
|
3466
4285
|
|
|
3467
|
-
// src/cli/confirm.ts
|
|
3468
|
-
async function confirmOrAbort(message, force, options = {}) {
|
|
3469
|
-
if (force) return;
|
|
3470
|
-
const interactive = options.isTTY ?? process.stdout.isTTY === true;
|
|
3471
|
-
if (!interactive) {
|
|
3472
|
-
usageError(
|
|
3473
|
-
`refusing to ${options.action ?? "proceed"} without --force in non-interactive mode`
|
|
3474
|
-
);
|
|
3475
|
-
}
|
|
3476
|
-
const prompt = options.promptFn ?? (await import("@inquirer/prompts")).confirm;
|
|
3477
|
-
const confirmed = await prompt({ message, default: false });
|
|
3478
|
-
if (!confirmed) {
|
|
3479
|
-
ui.info("Aborted");
|
|
3480
|
-
process.exit(0);
|
|
3481
|
-
}
|
|
3482
|
-
}
|
|
3483
|
-
|
|
3484
4286
|
// src/cli/deprecated-flags.ts
|
|
3485
|
-
import
|
|
4287
|
+
import chalk10 from "chalk";
|
|
3486
4288
|
import { Option } from "commander";
|
|
3487
4289
|
var ALIASES = /* @__PURE__ */ new WeakMap();
|
|
3488
4290
|
function registerDeprecatedAlias(cmd, alias) {
|
|
@@ -3497,7 +4299,7 @@ function resolveDeprecatedAliases(cmd, opts) {
|
|
|
3497
4299
|
const oldValue = opts[alias.oldKey];
|
|
3498
4300
|
if (oldValue === void 0) continue;
|
|
3499
4301
|
console.error(
|
|
3500
|
-
|
|
4302
|
+
chalk10.yellow(
|
|
3501
4303
|
` \u26A0 ${alias.oldFlag.split(" ")[0]} is deprecated and will be removed in v4 \u2014 use ${alias.canonicalFlag}`
|
|
3502
4304
|
)
|
|
3503
4305
|
);
|
|
@@ -3681,7 +4483,7 @@ async function restoreTargets(opts) {
|
|
|
3681
4483
|
}
|
|
3682
4484
|
|
|
3683
4485
|
// src/cli/commands/redteam.ts
|
|
3684
|
-
async function
|
|
4486
|
+
async function createService3() {
|
|
3685
4487
|
const config = await loadConfig();
|
|
3686
4488
|
return new SdkRedTeamService(redTeamClientOptions(config));
|
|
3687
4489
|
}
|
|
@@ -3689,6 +4491,14 @@ async function createPromptSetService() {
|
|
|
3689
4491
|
const config = await loadConfig();
|
|
3690
4492
|
return new SdkPromptSetService(redTeamClientOptions(config));
|
|
3691
4493
|
}
|
|
4494
|
+
function buildDefaultCategories(categories) {
|
|
4495
|
+
return Object.fromEntries(
|
|
4496
|
+
categories.map((category) => [
|
|
4497
|
+
category.id,
|
|
4498
|
+
category.subCategories.map((subCategory) => subCategory.id).filter((id) => id !== "MULTI_TURN")
|
|
4499
|
+
])
|
|
4500
|
+
);
|
|
4501
|
+
}
|
|
3692
4502
|
function parseAttackGoals(input) {
|
|
3693
4503
|
const trimmed = input.trim();
|
|
3694
4504
|
const raw = trimmed.startsWith("[") ? trimmed : fs2.readFileSync(trimmed, "utf-8");
|
|
@@ -3704,8 +4514,12 @@ function parseAttackGoals(input) {
|
|
|
3704
4514
|
return parsed;
|
|
3705
4515
|
}
|
|
3706
4516
|
function sliceClientSide(items, opts) {
|
|
3707
|
-
|
|
3708
|
-
|
|
4517
|
+
if (opts.all) {
|
|
4518
|
+
const max = opts.max === void 0 ? 1e4 : Number(opts.max);
|
|
4519
|
+
return max === 0 ? items : items.slice(0, max);
|
|
4520
|
+
}
|
|
4521
|
+
const offset = opts.offset !== void 0 ? Number.parseInt(String(opts.offset), 10) : 0;
|
|
4522
|
+
const limit = opts.limit !== void 0 ? Number.parseInt(String(opts.limit), 10) : void 0;
|
|
3709
4523
|
return items.slice(offset, limit === void 0 ? void 0 : offset + limit);
|
|
3710
4524
|
}
|
|
3711
4525
|
function parsePositiveInt(input, flag) {
|
|
@@ -3721,8 +4535,11 @@ var VALID_TARGET_PROVIDERS = [
|
|
|
3721
4535
|
"DATABRICKS",
|
|
3722
4536
|
"BEDROCK",
|
|
3723
4537
|
"REST",
|
|
3724
|
-
"STREAMING"
|
|
4538
|
+
"STREAMING",
|
|
4539
|
+
"WEBSOCKET",
|
|
4540
|
+
"CUSTOM_TARGET_ADAPTER"
|
|
3725
4541
|
];
|
|
4542
|
+
var REST_PROVIDERS = /* @__PURE__ */ new Set(["REST", "STREAMING", "WEBSOCKET", "HUGGING_FACE"]);
|
|
3726
4543
|
function buildTargetScaffold(provider, templates) {
|
|
3727
4544
|
const key = provider.toUpperCase();
|
|
3728
4545
|
if (!VALID_TARGET_PROVIDERS.includes(key)) {
|
|
@@ -3730,21 +4547,92 @@ function buildTargetScaffold(provider, templates) {
|
|
|
3730
4547
|
`Unknown provider "${provider}". Valid providers: ${VALID_TARGET_PROVIDERS.join(", ")}`
|
|
3731
4548
|
);
|
|
3732
4549
|
}
|
|
4550
|
+
if (key === "CUSTOM_TARGET_ADAPTER") {
|
|
4551
|
+
return {
|
|
4552
|
+
name: "",
|
|
4553
|
+
target_type: "AGENT",
|
|
4554
|
+
connection_type: "CUSTOM_TARGET_ADAPTER",
|
|
4555
|
+
api_endpoint_type: "NETWORK_BROKER",
|
|
4556
|
+
network_broker_channel_uuid: "<channel-uuid>",
|
|
4557
|
+
adapter_uuid: "<adapter-uuid>",
|
|
4558
|
+
// adapter_variable_overrides is an ARRAY of {key, value, type} objects.
|
|
4559
|
+
adapter_variable_overrides: [],
|
|
4560
|
+
target_background: { use_case: "" },
|
|
4561
|
+
additional_context: {}
|
|
4562
|
+
};
|
|
4563
|
+
}
|
|
4564
|
+
if (REST_PROVIDERS.has(key)) {
|
|
4565
|
+
const tpl = templates[key] ?? {};
|
|
4566
|
+
return {
|
|
4567
|
+
name: "",
|
|
4568
|
+
target_type: "APPLICATION",
|
|
4569
|
+
connection_type: "CUSTOM",
|
|
4570
|
+
api_endpoint_type: "PUBLIC",
|
|
4571
|
+
response_mode: key === "STREAMING" ? "STREAMING" : key === "WEBSOCKET" ? "WEBSOCKET" : "REST",
|
|
4572
|
+
auth_type: "HEADERS",
|
|
4573
|
+
auth_config: {
|
|
4574
|
+
auth_header: { Authorization: "Bearer <token>" }
|
|
4575
|
+
},
|
|
4576
|
+
connection_params: {
|
|
4577
|
+
api_endpoint: tpl.url ?? "",
|
|
4578
|
+
request_headers: { "Content-Type": "application/json" },
|
|
4579
|
+
request_json: tpl.request_json ?? { messages: [{ role: "user", content: "{INPUT}" }] },
|
|
4580
|
+
response_json: tpl.response_json ?? { choices: [{ message: { content: "{RESPONSE}" } }] },
|
|
4581
|
+
response_key: "choices.0.message.content"
|
|
4582
|
+
},
|
|
4583
|
+
target_background: {},
|
|
4584
|
+
additional_context: {}
|
|
4585
|
+
};
|
|
4586
|
+
}
|
|
3733
4587
|
return {
|
|
3734
4588
|
name: "",
|
|
3735
4589
|
target_type: "APPLICATION",
|
|
3736
|
-
|
|
4590
|
+
connection_type: key,
|
|
4591
|
+
api_endpoint_type: "PUBLIC",
|
|
4592
|
+
response_mode: "REST",
|
|
4593
|
+
auth_type: "HEADERS",
|
|
4594
|
+
auth_config: {
|
|
4595
|
+
auth_header: { Authorization: "Bearer <token>" }
|
|
4596
|
+
},
|
|
4597
|
+
connection_params: {
|
|
4598
|
+
target_connection_config: templates[key] ?? {}
|
|
4599
|
+
},
|
|
3737
4600
|
target_background: {},
|
|
3738
|
-
additional_context: {}
|
|
3739
|
-
target_metadata: {}
|
|
4601
|
+
additional_context: {}
|
|
3740
4602
|
};
|
|
3741
4603
|
}
|
|
4604
|
+
function resolveScriptB64(opts) {
|
|
4605
|
+
if (opts.scriptFile !== void 0 && opts.scriptB64 !== void 0) {
|
|
4606
|
+
throw new Error("--script-file and --script-b64 are mutually exclusive");
|
|
4607
|
+
}
|
|
4608
|
+
if (opts.scriptB64 !== void 0) return opts.scriptB64;
|
|
4609
|
+
if (opts.scriptFile !== void 0) {
|
|
4610
|
+
return Buffer.from(fs2.readFileSync(opts.scriptFile, "utf-8")).toString("base64");
|
|
4611
|
+
}
|
|
4612
|
+
throw new Error("one of --script-file or --script-b64 is required");
|
|
4613
|
+
}
|
|
4614
|
+
function parseAdapterVariables(input) {
|
|
4615
|
+
let parsed;
|
|
4616
|
+
try {
|
|
4617
|
+
parsed = JSON.parse(input);
|
|
4618
|
+
} catch (err) {
|
|
4619
|
+
throw new Error(`--variables: invalid JSON (${err instanceof Error ? err.message : err})`);
|
|
4620
|
+
}
|
|
4621
|
+
if (!Array.isArray(parsed) || !parsed.every(
|
|
4622
|
+
(v) => v !== null && typeof v === "object" && typeof v.key === "string" && (v.type === "VAR" || v.type === "SECRET")
|
|
4623
|
+
)) {
|
|
4624
|
+
throw new Error(
|
|
4625
|
+
'--variables: expected a JSON array of { "key": string, "value"?: string|null, "type": "VAR"|"SECRET" }'
|
|
4626
|
+
);
|
|
4627
|
+
}
|
|
4628
|
+
return parsed;
|
|
4629
|
+
}
|
|
3742
4630
|
function registerRedteamCommand(program) {
|
|
3743
4631
|
const redteam = program.command("redteam").description("AI Red Team scan operations");
|
|
3744
4632
|
redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
|
|
3745
4633
|
try {
|
|
3746
4634
|
renderRedteamHeader();
|
|
3747
|
-
const service = await
|
|
4635
|
+
const service = await createService3();
|
|
3748
4636
|
await service.abortScan(jobId);
|
|
3749
4637
|
ui.success(`Scan ${jobId} aborted.`);
|
|
3750
4638
|
} catch (err) {
|
|
@@ -3754,7 +4642,7 @@ function registerRedteamCommand(program) {
|
|
|
3754
4642
|
redteam.command("categories").description("List available attack categories").action(async () => {
|
|
3755
4643
|
try {
|
|
3756
4644
|
renderRedteamHeader();
|
|
3757
|
-
const service = await
|
|
4645
|
+
const service = await createService3();
|
|
3758
4646
|
const categories = await service.getCategories();
|
|
3759
4647
|
renderCategories(categories);
|
|
3760
4648
|
} catch (err) {
|
|
@@ -3765,7 +4653,7 @@ function registerRedteamCommand(program) {
|
|
|
3765
4653
|
eula.command("status").description("Check EULA acceptance status").action(async () => {
|
|
3766
4654
|
try {
|
|
3767
4655
|
renderRedteamHeader();
|
|
3768
|
-
const service = await
|
|
4656
|
+
const service = await createService3();
|
|
3769
4657
|
const status = await service.getEulaStatus();
|
|
3770
4658
|
renderEulaStatus(status);
|
|
3771
4659
|
} catch (err) {
|
|
@@ -3775,7 +4663,7 @@ function registerRedteamCommand(program) {
|
|
|
3775
4663
|
eula.command("content").description("Display EULA content").action(async () => {
|
|
3776
4664
|
try {
|
|
3777
4665
|
renderRedteamHeader();
|
|
3778
|
-
const service = await
|
|
4666
|
+
const service = await createService3();
|
|
3779
4667
|
const content = await service.getEulaContent();
|
|
3780
4668
|
renderEulaContent(content);
|
|
3781
4669
|
} catch (err) {
|
|
@@ -3793,7 +4681,7 @@ function registerRedteamCommand(program) {
|
|
|
3793
4681
|
resolveDeprecatedAliases(eulaAccept, opts);
|
|
3794
4682
|
try {
|
|
3795
4683
|
renderRedteamHeader();
|
|
3796
|
-
const service = await
|
|
4684
|
+
const service = await createService3();
|
|
3797
4685
|
const content = await service.getEulaContent();
|
|
3798
4686
|
if (!opts.force) {
|
|
3799
4687
|
renderEulaContent(content);
|
|
@@ -3811,7 +4699,7 @@ function registerRedteamCommand(program) {
|
|
|
3811
4699
|
instances.command("create").description("Create an instance").requiredOption("--tsg-id <id>", "TSG ID").requiredOption("--tenant-id <id>", "Tenant ID").requiredOption("--app-id <id>", "App ID").requiredOption("--region <region>", "Region").action(async (opts) => {
|
|
3812
4700
|
try {
|
|
3813
4701
|
renderRedteamHeader();
|
|
3814
|
-
const service = await
|
|
4702
|
+
const service = await createService3();
|
|
3815
4703
|
const result = await service.createInstance({
|
|
3816
4704
|
tsgId: opts.tsgId,
|
|
3817
4705
|
tenantId: opts.tenantId,
|
|
@@ -3827,7 +4715,7 @@ function registerRedteamCommand(program) {
|
|
|
3827
4715
|
try {
|
|
3828
4716
|
const fmt = opts.output;
|
|
3829
4717
|
if (fmt === "pretty") renderRedteamHeader();
|
|
3830
|
-
const service = await
|
|
4718
|
+
const service = await createService3();
|
|
3831
4719
|
const result = await service.getInstance(tenantId);
|
|
3832
4720
|
renderInstanceDetail(result, fmt);
|
|
3833
4721
|
} catch (err) {
|
|
@@ -3837,7 +4725,7 @@ function registerRedteamCommand(program) {
|
|
|
3837
4725
|
instances.command("update <tenantId>").description("Update an instance").requiredOption("--tsg-id <id>", "TSG ID").requiredOption("--app-id <id>", "App ID").requiredOption("--region <region>", "Region").action(async (tenantId, opts) => {
|
|
3838
4726
|
try {
|
|
3839
4727
|
renderRedteamHeader();
|
|
3840
|
-
const service = await
|
|
4728
|
+
const service = await createService3();
|
|
3841
4729
|
const result = await service.updateInstance(tenantId, {
|
|
3842
4730
|
tsgId: opts.tsgId,
|
|
3843
4731
|
tenantId,
|
|
@@ -3852,7 +4740,7 @@ function registerRedteamCommand(program) {
|
|
|
3852
4740
|
instances.command("delete <tenantId>").description("Delete an instance").action(async (tenantId) => {
|
|
3853
4741
|
try {
|
|
3854
4742
|
renderRedteamHeader();
|
|
3855
|
-
const service = await
|
|
4743
|
+
const service = await createService3();
|
|
3856
4744
|
const result = await service.deleteInstance(tenantId);
|
|
3857
4745
|
renderInstanceResponse(result);
|
|
3858
4746
|
ui.success(`Instance ${tenantId} deleted.`);
|
|
@@ -3864,7 +4752,7 @@ function registerRedteamCommand(program) {
|
|
|
3864
4752
|
devices.command("create <tenantId>").description("Create devices for an instance").requiredOption("--config <path>", "JSON file with device request").action(async (tenantId, opts) => {
|
|
3865
4753
|
try {
|
|
3866
4754
|
renderRedteamHeader();
|
|
3867
|
-
const service = await
|
|
4755
|
+
const service = await createService3();
|
|
3868
4756
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
3869
4757
|
const result = await service.createDevices(tenantId, config);
|
|
3870
4758
|
ui.success("Devices created:");
|
|
@@ -3876,7 +4764,7 @@ function registerRedteamCommand(program) {
|
|
|
3876
4764
|
devices.command("update <tenantId>").description("Update devices for an instance (PATCH)").requiredOption("--config <path>", "JSON file with device request").action(async (tenantId, opts) => {
|
|
3877
4765
|
try {
|
|
3878
4766
|
renderRedteamHeader();
|
|
3879
|
-
const service = await
|
|
4767
|
+
const service = await createService3();
|
|
3880
4768
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
3881
4769
|
const result = await service.updateDevices(tenantId, config);
|
|
3882
4770
|
ui.success("Devices updated:");
|
|
@@ -3888,7 +4776,7 @@ function registerRedteamCommand(program) {
|
|
|
3888
4776
|
devices.command("delete <tenantId>").description("Delete devices by serial numbers").requiredOption("--serial-numbers <list>", "Comma-separated serial numbers").action(async (tenantId, opts) => {
|
|
3889
4777
|
try {
|
|
3890
4778
|
renderRedteamHeader();
|
|
3891
|
-
const service = await
|
|
4779
|
+
const service = await createService3();
|
|
3892
4780
|
const result = await service.deleteDevices(tenantId, opts.serialNumbers);
|
|
3893
4781
|
ui.success("Devices deleted:");
|
|
3894
4782
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -3900,7 +4788,7 @@ function registerRedteamCommand(program) {
|
|
|
3900
4788
|
try {
|
|
3901
4789
|
const fmt = opts.output;
|
|
3902
4790
|
if (fmt === "pretty") renderRedteamHeader();
|
|
3903
|
-
const service = await
|
|
4791
|
+
const service = await createService3();
|
|
3904
4792
|
const creds = await service.getRegistryCredentials();
|
|
3905
4793
|
renderRegistryCredentials(creds, fmt);
|
|
3906
4794
|
} catch (err) {
|
|
@@ -3911,13 +4799,15 @@ function registerRedteamCommand(program) {
|
|
|
3911
4799
|
try {
|
|
3912
4800
|
const fmt = opts.output;
|
|
3913
4801
|
if (fmt === "pretty") renderRedteamHeader();
|
|
3914
|
-
const service = await
|
|
3915
|
-
const
|
|
4802
|
+
const service = await createService3();
|
|
4803
|
+
const listOptions = {
|
|
3916
4804
|
status: opts.status,
|
|
3917
4805
|
jobType: opts.type,
|
|
3918
4806
|
targetId: opts.target,
|
|
3919
|
-
limit: Number.parseInt(opts.limit, 10)
|
|
3920
|
-
|
|
4807
|
+
limit: Number.parseInt(opts.limit, 10),
|
|
4808
|
+
offset: Number(opts.offset ?? 0)
|
|
4809
|
+
};
|
|
4810
|
+
const scans = opts.all ? await service.listAllScans({ ...listOptions, max: Number(opts.max) }) : await service.listScans(listOptions);
|
|
3921
4811
|
renderScanList(scans, fmt);
|
|
3922
4812
|
} catch (err) {
|
|
3923
4813
|
fail(err);
|
|
@@ -4124,7 +5014,7 @@ function registerRedteamCommand(program) {
|
|
|
4124
5014
|
redteam.command("report <jobId>").description("View scan report").option("--attacks", "Include attack list", false).option("--severity <level>", "Filter attacks by severity").option("--limit <n>", "Max attacks to show", "20").action(async (jobId, opts) => {
|
|
4125
5015
|
try {
|
|
4126
5016
|
renderRedteamHeader();
|
|
4127
|
-
const service = await
|
|
5017
|
+
const service = await createService3();
|
|
4128
5018
|
const job = await service.getScan(jobId);
|
|
4129
5019
|
renderScanStatus(job);
|
|
4130
5020
|
if (job.jobType === "CUSTOM") {
|
|
@@ -4184,7 +5074,18 @@ function registerRedteamCommand(program) {
|
|
|
4184
5074
|
const customPromptSets = opts.promptSets ? opts.promptSets.split(",").map((s) => s.trim()) : void 0;
|
|
4185
5075
|
try {
|
|
4186
5076
|
renderRedteamHeader();
|
|
4187
|
-
const service = await
|
|
5077
|
+
const service = await createService3();
|
|
5078
|
+
if (opts.type === "STATIC" && !categories) {
|
|
5079
|
+
const defaultCategories = buildDefaultCategories(await service.getCategories());
|
|
5080
|
+
const categoryCount = Object.values(defaultCategories).reduce(
|
|
5081
|
+
(total, subCategories) => total + subCategories.length,
|
|
5082
|
+
0
|
|
5083
|
+
);
|
|
5084
|
+
categories = defaultCategories;
|
|
5085
|
+
ui.status(
|
|
5086
|
+
`No --categories given \u2014 defaulting to all ${categoryCount} categories (MULTI_TURN excluded). Pass --categories to narrow the scan.`
|
|
5087
|
+
);
|
|
5088
|
+
}
|
|
4188
5089
|
ui.status(`Creating ${opts.type} scan "${opts.name}"...`);
|
|
4189
5090
|
const job = await service.createScan({
|
|
4190
5091
|
name: opts.name,
|
|
@@ -4218,7 +5119,7 @@ function registerRedteamCommand(program) {
|
|
|
4218
5119
|
redteam.command("status <jobId>").description("Check scan status").action(async (jobId) => {
|
|
4219
5120
|
try {
|
|
4220
5121
|
renderRedteamHeader();
|
|
4221
|
-
const service = await
|
|
5122
|
+
const service = await createService3();
|
|
4222
5123
|
const job = await service.getScan(jobId);
|
|
4223
5124
|
renderScanStatus(job);
|
|
4224
5125
|
} catch (err) {
|
|
@@ -4237,7 +5138,7 @@ function registerRedteamCommand(program) {
|
|
|
4237
5138
|
try {
|
|
4238
5139
|
const fmt = opts.output;
|
|
4239
5140
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4240
|
-
const service = await
|
|
5141
|
+
const service = await createService3();
|
|
4241
5142
|
const list = await service.listTargets();
|
|
4242
5143
|
renderTargetList(sliceClientSide(list, opts), fmt);
|
|
4243
5144
|
} catch (err) {
|
|
@@ -4248,7 +5149,7 @@ function registerRedteamCommand(program) {
|
|
|
4248
5149
|
try {
|
|
4249
5150
|
const fmt = opts.output;
|
|
4250
5151
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4251
|
-
const service = await
|
|
5152
|
+
const service = await createService3();
|
|
4252
5153
|
const target = await service.getTarget(uuid);
|
|
4253
5154
|
renderTargetDetail(target, fmt);
|
|
4254
5155
|
} catch (err) {
|
|
@@ -4258,7 +5159,7 @@ function registerRedteamCommand(program) {
|
|
|
4258
5159
|
targets.command("create").description("Create a new red team target").requiredOption("--config <path>", "JSON file with target configuration").option("--validate", "Validate target connection before saving").action(async (opts) => {
|
|
4259
5160
|
try {
|
|
4260
5161
|
renderRedteamHeader();
|
|
4261
|
-
const service = await
|
|
5162
|
+
const service = await createService3();
|
|
4262
5163
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4263
5164
|
const target = await service.createTarget(
|
|
4264
5165
|
config,
|
|
@@ -4273,7 +5174,7 @@ function registerRedteamCommand(program) {
|
|
|
4273
5174
|
targets.command("update <uuid>").description("Update a red team target").requiredOption("--config <path>", "JSON file with target updates").option("--validate", "Validate target connection before saving").action(async (uuid, opts) => {
|
|
4274
5175
|
try {
|
|
4275
5176
|
renderRedteamHeader();
|
|
4276
|
-
const service = await
|
|
5177
|
+
const service = await createService3();
|
|
4277
5178
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4278
5179
|
const target = await service.updateTarget(
|
|
4279
5180
|
uuid,
|
|
@@ -4292,7 +5193,7 @@ function registerRedteamCommand(program) {
|
|
|
4292
5193
|
action: `delete target ${uuid}`
|
|
4293
5194
|
});
|
|
4294
5195
|
renderRedteamHeader();
|
|
4295
|
-
const service = await
|
|
5196
|
+
const service = await createService3();
|
|
4296
5197
|
await service.deleteTarget(uuid);
|
|
4297
5198
|
ui.success(`Target ${uuid} deleted.`);
|
|
4298
5199
|
} catch (err) {
|
|
@@ -4302,7 +5203,7 @@ function registerRedteamCommand(program) {
|
|
|
4302
5203
|
targets.command("probe").description("Test target connection without saving").requiredOption("--config <path>", "JSON file with connection params").action(async (opts) => {
|
|
4303
5204
|
try {
|
|
4304
5205
|
renderRedteamHeader();
|
|
4305
|
-
const service = await
|
|
5206
|
+
const service = await createService3();
|
|
4306
5207
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4307
5208
|
const result = await service.probeTarget(config);
|
|
4308
5209
|
ui.dim("Probe result:");
|
|
@@ -4314,7 +5215,7 @@ function registerRedteamCommand(program) {
|
|
|
4314
5215
|
targets.command("profile <uuid>").description("View target profile").action(async (uuid) => {
|
|
4315
5216
|
try {
|
|
4316
5217
|
renderRedteamHeader();
|
|
4317
|
-
const service = await
|
|
5218
|
+
const service = await createService3();
|
|
4318
5219
|
const profile = await service.getTargetProfile(uuid);
|
|
4319
5220
|
ui.dim("Target Profile:");
|
|
4320
5221
|
console.log(JSON.stringify(profile, null, 2));
|
|
@@ -4325,7 +5226,7 @@ function registerRedteamCommand(program) {
|
|
|
4325
5226
|
targets.command("update-profile <uuid>").description("Update target profile").requiredOption("--config <path>", "JSON file with profile updates").action(async (uuid, opts) => {
|
|
4326
5227
|
try {
|
|
4327
5228
|
renderRedteamHeader();
|
|
4328
|
-
const service = await
|
|
5229
|
+
const service = await createService3();
|
|
4329
5230
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4330
5231
|
const result = await service.updateTargetProfile(uuid, config);
|
|
4331
5232
|
ui.success("Profile updated:");
|
|
@@ -4337,7 +5238,7 @@ function registerRedteamCommand(program) {
|
|
|
4337
5238
|
targets.command("validate-auth").description("Validate target auth credentials").requiredOption("--auth-type <type>", "Auth type: HEADERS, BASIC_AUTH, OAUTH2").requiredOption("--config <path>", "JSON file with auth_config").option("--target-id <uuid>", "Existing target UUID").action(async (opts) => {
|
|
4338
5239
|
try {
|
|
4339
5240
|
renderRedteamHeader();
|
|
4340
|
-
const service = await
|
|
5241
|
+
const service = await createService3();
|
|
4341
5242
|
const authConfig = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4342
5243
|
const result = await service.validateTargetAuth({
|
|
4343
5244
|
authType: opts.authType,
|
|
@@ -4351,7 +5252,7 @@ function registerRedteamCommand(program) {
|
|
|
4351
5252
|
});
|
|
4352
5253
|
targets.command("metadata").description("Get target field metadata").action(async () => {
|
|
4353
5254
|
try {
|
|
4354
|
-
const service = await
|
|
5255
|
+
const service = await createService3();
|
|
4355
5256
|
const metadata = await service.getTargetMetadata();
|
|
4356
5257
|
console.log(JSON.stringify(metadata, null, 2));
|
|
4357
5258
|
} catch (err) {
|
|
@@ -4383,7 +5284,7 @@ function registerRedteamCommand(program) {
|
|
|
4383
5284
|
}
|
|
4384
5285
|
try {
|
|
4385
5286
|
renderRedteamHeader();
|
|
4386
|
-
const service = await
|
|
5287
|
+
const service = await createService3();
|
|
4387
5288
|
const templates = await service.getTargetTemplates();
|
|
4388
5289
|
const scaffold = buildTargetScaffold(provider, templates);
|
|
4389
5290
|
fs2.writeFileSync(outputPath, `${JSON.stringify(scaffold, null, 2)}
|
|
@@ -4402,19 +5303,25 @@ function registerRedteamCommand(program) {
|
|
|
4402
5303
|
targets.command("templates").description("Get provider-specific target templates").action(async () => {
|
|
4403
5304
|
try {
|
|
4404
5305
|
renderRedteamHeader();
|
|
4405
|
-
const service = await
|
|
5306
|
+
const service = await createService3();
|
|
4406
5307
|
const templates = await service.getTargetTemplates();
|
|
4407
5308
|
renderTargetTemplates(templates);
|
|
4408
5309
|
} catch (err) {
|
|
4409
5310
|
fail(err);
|
|
4410
5311
|
}
|
|
4411
5312
|
});
|
|
4412
|
-
const targetsBackup = targets.command("backup").description("Backup red team targets to local JSON/YAML files").option("--output-dir <path>", "Output directory").option("--
|
|
5313
|
+
const targetsBackup = targets.command("backup").description("Backup red team targets to local JSON/YAML files").option("--output-dir <path>", "Output directory").option("--file-format <format>", "Backup file format: json or yaml", "json").option("--name <targetName>", "Backup a single target by name");
|
|
5314
|
+
registerDeprecatedAlias(targetsBackup, {
|
|
5315
|
+
oldFlag: "--output <format>",
|
|
5316
|
+
oldKey: "output",
|
|
5317
|
+
canonicalFlag: "--file-format",
|
|
5318
|
+
canonicalKey: "fileFormat"
|
|
5319
|
+
});
|
|
4413
5320
|
registerDeprecatedAlias(targetsBackup, {
|
|
4414
5321
|
oldFlag: "--format <format>",
|
|
4415
5322
|
oldKey: "format",
|
|
4416
|
-
canonicalFlag: "--
|
|
4417
|
-
canonicalKey: "
|
|
5323
|
+
canonicalFlag: "--file-format",
|
|
5324
|
+
canonicalKey: "fileFormat"
|
|
4418
5325
|
});
|
|
4419
5326
|
targetsBackup.action(async (opts) => {
|
|
4420
5327
|
resolveDeprecatedAliases(targetsBackup, opts);
|
|
@@ -4423,7 +5330,7 @@ function registerRedteamCommand(program) {
|
|
|
4423
5330
|
const outputDir = resolveOutputDir(opts.outputDir, "targets");
|
|
4424
5331
|
const results = await backupTargets({
|
|
4425
5332
|
outputDir,
|
|
4426
|
-
format: opts.
|
|
5333
|
+
format: opts.fileFormat ?? "json",
|
|
4427
5334
|
name: opts.name
|
|
4428
5335
|
});
|
|
4429
5336
|
renderBackupSummary(results, outputDir);
|
|
@@ -4456,7 +5363,7 @@ function registerRedteamCommand(program) {
|
|
|
4456
5363
|
try {
|
|
4457
5364
|
const fmt = opts.output;
|
|
4458
5365
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4459
|
-
const service = await
|
|
5366
|
+
const service = await createService3();
|
|
4460
5367
|
const { logs } = await service.getTargetProfileErrorLogs(targetId, {
|
|
4461
5368
|
limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
|
|
4462
5369
|
offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
|
|
@@ -4467,19 +5374,182 @@ function registerRedteamCommand(program) {
|
|
|
4467
5374
|
fail(err);
|
|
4468
5375
|
}
|
|
4469
5376
|
});
|
|
5377
|
+
const adapter = redteam.command("adapter").description("Manage custom target adapters (scripted targets run via the network broker)");
|
|
5378
|
+
async function assertChannelOnline(service, channelUuid) {
|
|
5379
|
+
let status;
|
|
5380
|
+
try {
|
|
5381
|
+
status = (await service.getChannel(channelUuid)).status;
|
|
5382
|
+
} catch {
|
|
5383
|
+
return;
|
|
5384
|
+
}
|
|
5385
|
+
if (status && status !== "ONLINE") {
|
|
5386
|
+
fail(
|
|
5387
|
+
new Error(
|
|
5388
|
+
`network broker channel ${channelUuid} is ${status} \u2014 adapter validation requires an ONLINE channel (network broker v1.4.0+). Check 'airs redteam network-broker channels list'.`
|
|
5389
|
+
)
|
|
5390
|
+
);
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
adapter.command("list").description("List custom target adapters").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--search <text>", "Filter by search text").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").action(async (opts) => {
|
|
5394
|
+
try {
|
|
5395
|
+
const fmt = opts.output;
|
|
5396
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5397
|
+
const service = await createService3();
|
|
5398
|
+
const listOptions = {
|
|
5399
|
+
limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
|
|
5400
|
+
offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
|
|
5401
|
+
search: opts.search
|
|
5402
|
+
};
|
|
5403
|
+
const result = opts.all ? { adapters: await service.listAllAdapters({ ...listOptions, max: Number(opts.max) }) } : await service.listAdapters(listOptions);
|
|
5404
|
+
const { adapters, totalItems } = result;
|
|
5405
|
+
renderAdapterList(adapters, fmt, totalItems);
|
|
5406
|
+
} catch (err) {
|
|
5407
|
+
fail(err);
|
|
5408
|
+
}
|
|
5409
|
+
});
|
|
5410
|
+
adapter.command("get <uuid>").description("Get a custom target adapter").option("--output <format>", "Output format: pretty, json, yaml", "pretty").action(async (uuid, opts) => {
|
|
5411
|
+
try {
|
|
5412
|
+
const fmt = opts.output;
|
|
5413
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5414
|
+
const service = await createService3();
|
|
5415
|
+
renderAdapterDetail(await service.getAdapter(uuid), fmt);
|
|
5416
|
+
} catch (err) {
|
|
5417
|
+
fail(err);
|
|
5418
|
+
}
|
|
5419
|
+
});
|
|
5420
|
+
adapter.command("create").description("Create a custom target adapter").requiredOption("--name <name>", "Adapter name").requiredOption(
|
|
5421
|
+
"--prompt <text>",
|
|
5422
|
+
"Sample prompt used to exercise the adapter during validation (not stored)"
|
|
5423
|
+
).option("--script-file <path>", "Path to the adapter script (encoded to base64 for you)").option("--script-b64 <b64>", "Adapter script, already base64-encoded").option("--description <text>", "Adapter description").option("--channel <uuid>", "Network broker channel UUID (required to activate)").option("--variables <json>", "JSON array of { key, value, type: VAR|SECRET }").option("--draft", "Save as DRAFT without running the validation script").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
|
|
5424
|
+
"after",
|
|
5425
|
+
examples(
|
|
5426
|
+
`airs redteam adapter create --name my-adapter --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --variables '[{"key":"endpoint","value":"http://agent.svc:8080","type":"VAR"}]'`,
|
|
5427
|
+
"airs redteam adapter create --name my-adapter --script-file ./adapter.py --prompt Hello --draft"
|
|
5428
|
+
)
|
|
5429
|
+
).action(async (opts) => {
|
|
5430
|
+
try {
|
|
5431
|
+
const fmt = opts.output;
|
|
5432
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5433
|
+
const scriptB64 = resolveScriptB64(opts);
|
|
5434
|
+
const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
|
|
5435
|
+
const service = await createService3();
|
|
5436
|
+
if (!opts.draft && opts.channel) await assertChannelOnline(service, opts.channel);
|
|
5437
|
+
const created = await service.createAdapter(
|
|
5438
|
+
{
|
|
5439
|
+
name: opts.name,
|
|
5440
|
+
scriptB64,
|
|
5441
|
+
prompt: opts.prompt,
|
|
5442
|
+
description: opts.description,
|
|
5443
|
+
networkBrokerChannelUuid: opts.channel,
|
|
5444
|
+
variables
|
|
5445
|
+
},
|
|
5446
|
+
opts.draft ? false : void 0
|
|
5447
|
+
);
|
|
5448
|
+
ui.success(`Adapter created: ${created.uuid}`);
|
|
5449
|
+
renderAdapterDetail(created, fmt);
|
|
5450
|
+
} catch (err) {
|
|
5451
|
+
fail(err);
|
|
5452
|
+
}
|
|
5453
|
+
});
|
|
5454
|
+
adapter.command("update <uuid>").description(
|
|
5455
|
+
"Update a custom target adapter (read-modify-write; variables preserved unless --variables)"
|
|
5456
|
+
).requiredOption(
|
|
5457
|
+
"--prompt <text>",
|
|
5458
|
+
"Sample validation prompt \u2014 required on every update because upstream never stores it"
|
|
5459
|
+
).option("--name <name>", "New adapter name").option("--script-file <path>", "New adapter script file (encoded to base64 for you)").option("--script-b64 <b64>", "New adapter script, already base64-encoded").option("--description <text>", "New description").option("--channel <uuid>", "New network broker channel UUID").option(
|
|
5460
|
+
"--variables <json>",
|
|
5461
|
+
"REPLACES the whole variable set \u2014 omitted keys are deleted upstream. Omit this flag to preserve stored variables."
|
|
5462
|
+
).option("--draft", "Save as DRAFT without re-running the validation script").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
|
|
5463
|
+
"after",
|
|
5464
|
+
examples(
|
|
5465
|
+
`airs redteam adapter update 550e8400-... --description 'new description' --prompt 'Hello'`
|
|
5466
|
+
)
|
|
5467
|
+
).action(async (uuid, opts) => {
|
|
5468
|
+
try {
|
|
5469
|
+
const fmt = opts.output;
|
|
5470
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5471
|
+
const scriptB64 = opts.scriptFile !== void 0 || opts.scriptB64 !== void 0 ? resolveScriptB64(opts) : void 0;
|
|
5472
|
+
const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
|
|
5473
|
+
const service = await createService3();
|
|
5474
|
+
if (!opts.draft && opts.channel) await assertChannelOnline(service, opts.channel);
|
|
5475
|
+
const updated = await service.updateAdapter(
|
|
5476
|
+
uuid,
|
|
5477
|
+
{
|
|
5478
|
+
prompt: opts.prompt,
|
|
5479
|
+
name: opts.name,
|
|
5480
|
+
scriptB64,
|
|
5481
|
+
description: opts.description,
|
|
5482
|
+
networkBrokerChannelUuid: opts.channel,
|
|
5483
|
+
variables
|
|
5484
|
+
},
|
|
5485
|
+
opts.draft ? false : void 0
|
|
5486
|
+
);
|
|
5487
|
+
ui.success(`Adapter updated: ${updated.uuid}`);
|
|
5488
|
+
renderAdapterDetail(updated, fmt);
|
|
5489
|
+
} catch (err) {
|
|
5490
|
+
fail(err);
|
|
5491
|
+
}
|
|
5492
|
+
});
|
|
5493
|
+
adapter.command("delete <uuid>").description("Delete a custom target adapter").option("--force", "Skip confirmation prompt").action(async (uuid, opts) => {
|
|
5494
|
+
try {
|
|
5495
|
+
renderRedteamHeader();
|
|
5496
|
+
await confirmOrAbort(`Delete adapter ${uuid}?`, Boolean(opts.force), {
|
|
5497
|
+
action: `delete adapter ${uuid}`
|
|
5498
|
+
});
|
|
5499
|
+
const service = await createService3();
|
|
5500
|
+
await service.deleteAdapter(uuid);
|
|
5501
|
+
ui.success(`Adapter ${uuid} deleted.`);
|
|
5502
|
+
} catch (err) {
|
|
5503
|
+
fail(err);
|
|
5504
|
+
}
|
|
5505
|
+
});
|
|
5506
|
+
adapter.command("validate").description("Run an adapter script end-to-end through the broker channel without saving").requiredOption("--channel <uuid>", "Network broker channel UUID (must be ONLINE)").requiredOption("--prompt <text>", "Sample prompt to send through the adapter").option("--script-file <path>", "Path to the adapter script (encoded to base64 for you)").option("--script-b64 <b64>", "Adapter script, already base64-encoded").option(
|
|
5507
|
+
"--variables <json>",
|
|
5508
|
+
"JSON array of { key, value, type } \u2014 the FULL set the script needs"
|
|
5509
|
+
).option(
|
|
5510
|
+
"--adapter <uuid>",
|
|
5511
|
+
"Existing adapter: resolves redacted/null variable values from its stored secrets (and supplies its variable set when --variables is omitted)"
|
|
5512
|
+
).option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
|
|
5513
|
+
"after",
|
|
5514
|
+
examples(
|
|
5515
|
+
`airs redteam adapter validate --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --variables '[{"key":"endpoint","value":"http://agent.svc:8080","type":"VAR"}]'`,
|
|
5516
|
+
`airs redteam adapter validate --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --adapter 660e8400-...`
|
|
5517
|
+
)
|
|
5518
|
+
).action(async (opts) => {
|
|
5519
|
+
try {
|
|
5520
|
+
const fmt = opts.output;
|
|
5521
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5522
|
+
const scriptB64 = resolveScriptB64(opts);
|
|
5523
|
+
const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
|
|
5524
|
+
const service = await createService3();
|
|
5525
|
+
await assertChannelOnline(service, opts.channel);
|
|
5526
|
+
const result = await service.validateAdapter({
|
|
5527
|
+
scriptB64,
|
|
5528
|
+
networkBrokerChannelUuid: opts.channel,
|
|
5529
|
+
prompt: opts.prompt,
|
|
5530
|
+
variables,
|
|
5531
|
+
adapterUuid: opts.adapter
|
|
5532
|
+
});
|
|
5533
|
+
renderAdapterValidation(result, fmt);
|
|
5534
|
+
if (!result.validated) process.exitCode = 1;
|
|
5535
|
+
} catch (err) {
|
|
5536
|
+
fail(err);
|
|
5537
|
+
}
|
|
5538
|
+
});
|
|
4470
5539
|
const networkBroker = redteam.command("network-broker").description("Manage red team network broker channels");
|
|
4471
5540
|
const channels = networkBroker.command("channels").description("Manage network broker channels");
|
|
4472
5541
|
channels.command("list").description("List network broker channels").option("--limit <n>", "Max results").option("--offset <n>", "Starting offset").option("--search <text>", "Filter by search text").option("--status <status...>", "Filter by status (ONLINE, OFFLINE, DRAFT)").option("--output <format>", "Output format: pretty, table, csv, json, yaml", "pretty").action(async (opts) => {
|
|
4473
5542
|
try {
|
|
4474
5543
|
const fmt = opts.output;
|
|
4475
5544
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4476
|
-
const service = await
|
|
4477
|
-
const
|
|
5545
|
+
const service = await createService3();
|
|
5546
|
+
const listOptions = {
|
|
4478
5547
|
limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
|
|
4479
5548
|
offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
|
|
4480
5549
|
search: opts.search,
|
|
4481
5550
|
status: opts.status
|
|
4482
|
-
}
|
|
5551
|
+
};
|
|
5552
|
+
const list = opts.all ? await service.listAllChannels({ ...listOptions, max: Number(opts.max) }) : (await service.listChannels(listOptions)).channels;
|
|
4483
5553
|
renderChannelList(list, fmt);
|
|
4484
5554
|
} catch (err) {
|
|
4485
5555
|
fail(err);
|
|
@@ -4489,7 +5559,7 @@ function registerRedteamCommand(program) {
|
|
|
4489
5559
|
try {
|
|
4490
5560
|
const fmt = opts.output;
|
|
4491
5561
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4492
|
-
const service = await
|
|
5562
|
+
const service = await createService3();
|
|
4493
5563
|
const channel = await service.getChannel(channelId);
|
|
4494
5564
|
renderChannelDetail(channel, fmt);
|
|
4495
5565
|
} catch (err) {
|
|
@@ -4499,7 +5569,7 @@ function registerRedteamCommand(program) {
|
|
|
4499
5569
|
channels.command("create").description("Create a network broker channel").requiredOption("--name <name>", "Channel name").option("--description <text>", "Channel description").action(async (opts) => {
|
|
4500
5570
|
try {
|
|
4501
5571
|
renderRedteamHeader();
|
|
4502
|
-
const service = await
|
|
5572
|
+
const service = await createService3();
|
|
4503
5573
|
const channel = await service.createChannel({
|
|
4504
5574
|
name: opts.name,
|
|
4505
5575
|
description: opts.description
|
|
@@ -4516,7 +5586,7 @@ function registerRedteamCommand(program) {
|
|
|
4516
5586
|
usageError("Specify --name and/or --description to update");
|
|
4517
5587
|
}
|
|
4518
5588
|
renderRedteamHeader();
|
|
4519
|
-
const service = await
|
|
5589
|
+
const service = await createService3();
|
|
4520
5590
|
const channel = await service.updateChannel(channelId, {
|
|
4521
5591
|
name: opts.name,
|
|
4522
5592
|
description: opts.description
|
|
@@ -4531,7 +5601,7 @@ function registerRedteamCommand(program) {
|
|
|
4531
5601
|
try {
|
|
4532
5602
|
const fmt = opts.output;
|
|
4533
5603
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4534
|
-
const service = await
|
|
5604
|
+
const service = await createService3();
|
|
4535
5605
|
const stats = await service.getChannelStats();
|
|
4536
5606
|
renderChannelStats(stats, fmt);
|
|
4537
5607
|
} catch (err) {
|
|
@@ -4542,7 +5612,7 @@ function registerRedteamCommand(program) {
|
|
|
4542
5612
|
try {
|
|
4543
5613
|
const fmt = opts.output;
|
|
4544
5614
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4545
|
-
const service = await
|
|
5615
|
+
const service = await createService3();
|
|
4546
5616
|
const data = await service.getLanguages(Boolean(opts.management));
|
|
4547
5617
|
renderLanguages(data, fmt);
|
|
4548
5618
|
} catch (err) {
|
|
@@ -4556,7 +5626,7 @@ import { randomUUID as randomUUID4 } from "crypto";
|
|
|
4556
5626
|
import * as fs5 from "fs";
|
|
4557
5627
|
import { readFile as readFile8 } from "fs/promises";
|
|
4558
5628
|
import { basename as basename3, dirname as dirname2, join as join2, resolve as resolvePath } from "path";
|
|
4559
|
-
import
|
|
5629
|
+
import chalk11 from "chalk";
|
|
4560
5630
|
|
|
4561
5631
|
// src/cli/builders/profile-builder.ts
|
|
4562
5632
|
function parseList(value) {
|
|
@@ -5021,6 +6091,31 @@ async function loadBulkScanState(filePath) {
|
|
|
5021
6091
|
|
|
5022
6092
|
// src/cli/pagination.ts
|
|
5023
6093
|
var DEFAULT_PAGE_SIZE = 50;
|
|
6094
|
+
var DEFAULT_OFFSET_LIMIT = 100;
|
|
6095
|
+
var DEFAULT_MAX_ITEMS = 1e4;
|
|
6096
|
+
function registerListFlags(command, options) {
|
|
6097
|
+
const defaultLimit = options.dialect === "offset" ? DEFAULT_OFFSET_LIMIT : DEFAULT_PAGE_SIZE;
|
|
6098
|
+
return command.option("--limit <n>", `Items per page (default: ${defaultLimit})`, Number, defaultLimit).option("--offset <n>", "Item offset (default: 0)", Number, 0).option("--all", "Walk all pages").option("--max <n>", "Maximum items with --all; 0 removes the cap", Number, DEFAULT_MAX_ITEMS);
|
|
6099
|
+
}
|
|
6100
|
+
function integerFlag(name, value, minimum) {
|
|
6101
|
+
const parsed = Number(value);
|
|
6102
|
+
if (!Number.isSafeInteger(parsed) || parsed < minimum) {
|
|
6103
|
+
const qualifier = minimum === 1 ? "a positive integer" : "a non-negative integer";
|
|
6104
|
+
throw new CliUsageError(`${name} must be ${qualifier}`);
|
|
6105
|
+
}
|
|
6106
|
+
return parsed;
|
|
6107
|
+
}
|
|
6108
|
+
function resolveListParams(command, opts, options) {
|
|
6109
|
+
const defaultLimit = options.dialect === "offset" ? DEFAULT_OFFSET_LIMIT : DEFAULT_PAGE_SIZE;
|
|
6110
|
+
const limit = integerFlag("--limit", opts.limit ?? defaultLimit, 1);
|
|
6111
|
+
const offset = integerFlag("--offset", opts.offset ?? 0, 0);
|
|
6112
|
+
const max = integerFlag("--max", opts.max ?? DEFAULT_MAX_ITEMS, 0);
|
|
6113
|
+
const all = Boolean(opts.all);
|
|
6114
|
+
if (all && command.getOptionValueSource?.("offset") === "cli") {
|
|
6115
|
+
throw new CliUsageError("--all cannot be combined with --offset");
|
|
6116
|
+
}
|
|
6117
|
+
return { limit, offset, all, max };
|
|
6118
|
+
}
|
|
5024
6119
|
function registerPageAliases(cmd, opts) {
|
|
5025
6120
|
registerDeprecatedAlias(cmd, {
|
|
5026
6121
|
oldFlag: `${opts.sizeFlag} <n>`,
|
|
@@ -5142,6 +6237,71 @@ function parseQuotedField(content, start, len) {
|
|
|
5142
6237
|
return { value, nextIndex: i };
|
|
5143
6238
|
}
|
|
5144
6239
|
|
|
6240
|
+
// src/cli/renderer/views/runtime.ts
|
|
6241
|
+
var profilesView = {
|
|
6242
|
+
name: "profiles",
|
|
6243
|
+
columns: [
|
|
6244
|
+
{ key: "profileId", label: "ID" },
|
|
6245
|
+
{ key: "profileName", label: "Name" },
|
|
6246
|
+
{ key: "active", label: "Active" },
|
|
6247
|
+
{ key: "revision", label: "Revision" }
|
|
6248
|
+
],
|
|
6249
|
+
pretty: {
|
|
6250
|
+
list: (items) => renderProfileList(items, "pretty"),
|
|
6251
|
+
detail: renderProfileDetail
|
|
6252
|
+
}
|
|
6253
|
+
};
|
|
6254
|
+
var apiKeysView = {
|
|
6255
|
+
name: "API keys",
|
|
6256
|
+
columns: [
|
|
6257
|
+
{ key: "id", label: "ID" },
|
|
6258
|
+
{ key: "name", label: "Name" },
|
|
6259
|
+
{ key: "last8", label: "Last 8" },
|
|
6260
|
+
{ key: "expiresAt", label: "Expires" }
|
|
6261
|
+
],
|
|
6262
|
+
pretty: {
|
|
6263
|
+
list: (items) => renderApiKeyList(items, "pretty"),
|
|
6264
|
+
detail: renderApiKeyDetail
|
|
6265
|
+
}
|
|
6266
|
+
};
|
|
6267
|
+
var customerAppsView = {
|
|
6268
|
+
name: "customer apps",
|
|
6269
|
+
columns: [
|
|
6270
|
+
{ key: "id", label: "ID" },
|
|
6271
|
+
{ key: "name", label: "Name" },
|
|
6272
|
+
{ key: "description", label: "Description" }
|
|
6273
|
+
],
|
|
6274
|
+
pretty: {
|
|
6275
|
+
list: (items) => renderCustomerAppList(items, "pretty"),
|
|
6276
|
+
detail: renderCustomerAppDetail
|
|
6277
|
+
}
|
|
6278
|
+
};
|
|
6279
|
+
var topicsView = {
|
|
6280
|
+
name: "topics",
|
|
6281
|
+
columns: [
|
|
6282
|
+
{ key: "topic_id", label: "ID" },
|
|
6283
|
+
{ key: "topic_name", label: "Name" },
|
|
6284
|
+
{ key: "revision", label: "Revision" },
|
|
6285
|
+
{ key: "description", label: "Description" }
|
|
6286
|
+
],
|
|
6287
|
+
structured: (topic) => ({
|
|
6288
|
+
topicId: topic.topic_id,
|
|
6289
|
+
topicName: topic.topic_name,
|
|
6290
|
+
revision: topic.revision,
|
|
6291
|
+
active: topic.active,
|
|
6292
|
+
description: topic.description,
|
|
6293
|
+
examples: topic.examples,
|
|
6294
|
+
createdBy: topic.created_by,
|
|
6295
|
+
updatedBy: topic.updated_by,
|
|
6296
|
+
lastModifiedTs: topic.last_modified_ts,
|
|
6297
|
+
createdTs: topic.created_ts
|
|
6298
|
+
}),
|
|
6299
|
+
pretty: {
|
|
6300
|
+
list: (items) => renderTopicList(items, "pretty"),
|
|
6301
|
+
detail: renderTopicDetail
|
|
6302
|
+
}
|
|
6303
|
+
};
|
|
6304
|
+
|
|
5145
6305
|
// src/cli/commands/dlp/dictionaries.ts
|
|
5146
6306
|
import { readFile as readFile6 } from "fs/promises";
|
|
5147
6307
|
import { basename as basename2 } from "path";
|
|
@@ -5155,6 +6315,9 @@ var SdkDictionariesService = class {
|
|
|
5155
6315
|
async list(params) {
|
|
5156
6316
|
return this.client.list(params);
|
|
5157
6317
|
}
|
|
6318
|
+
async listAll(params) {
|
|
6319
|
+
return this.client.listAll(params);
|
|
6320
|
+
}
|
|
5158
6321
|
async create(params) {
|
|
5159
6322
|
return this.client.create(params);
|
|
5160
6323
|
}
|
|
@@ -5262,20 +6425,22 @@ function register(dlp) {
|
|
|
5262
6425
|
"--offset <n>",
|
|
5263
6426
|
"Starting offset \u2014 rounds down to a page boundary",
|
|
5264
6427
|
(v) => Number.parseInt(v, 10)
|
|
5265
|
-
).option("--sort <field,dir>", "(repeatable)", (v, p = []) => [...p, v]).option("--keywords", "Include keyword list in response").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format
|
|
6428
|
+
).option("--sort <field,dir>", "(repeatable)", (v, p = []) => [...p, v]).option("--keywords", "Include keyword list in response").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
|
|
5266
6429
|
registerPageAliases(listCmd, { sizeFlag: "--size", sizeKey: "size" });
|
|
5267
6430
|
listCmd.action(async (opts) => {
|
|
5268
6431
|
try {
|
|
5269
6432
|
const { page, size } = resolvePageParams(listCmd, opts);
|
|
5270
6433
|
const includeKeywords = opts.keywords || opts.includeKeywords;
|
|
6434
|
+
const svc = new SdkDictionariesService();
|
|
6435
|
+
const params = {
|
|
6436
|
+
size,
|
|
6437
|
+
sort: opts.sort,
|
|
6438
|
+
keywords: includeKeywords ? true : void 0
|
|
6439
|
+
};
|
|
6440
|
+
const all = opts.all ? await svc.listAll({ ...params, max: Number(opts.max) }) : void 0;
|
|
5271
6441
|
dlpDictionaries.renderList(
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
size,
|
|
5275
|
-
sort: opts.sort,
|
|
5276
|
-
keywords: includeKeywords ? true : void 0
|
|
5277
|
-
}),
|
|
5278
|
-
opts.output
|
|
6442
|
+
all ? { content: all, totalElements: all.length } : await svc.list({ ...params, page }),
|
|
6443
|
+
await resolveOutput(listCmd, opts)
|
|
5279
6444
|
);
|
|
5280
6445
|
} catch (err) {
|
|
5281
6446
|
fail(err);
|
|
@@ -5296,12 +6461,12 @@ function register(dlp) {
|
|
|
5296
6461
|
usageError(err instanceof Error ? err.message : String(err));
|
|
5297
6462
|
}
|
|
5298
6463
|
});
|
|
5299
|
-
group.command("get <id>").option("--keywords", "").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format
|
|
6464
|
+
const getCmd = group.command("get <id>").option("--keywords", "").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
|
|
5300
6465
|
try {
|
|
5301
6466
|
const includeKeywords = opts.keywords || opts.includeKeywords;
|
|
5302
6467
|
dlpDictionaries.renderGet(
|
|
5303
6468
|
await new SdkDictionariesService().get(id, { includeKeywords }),
|
|
5304
|
-
opts
|
|
6469
|
+
await resolveOutput(getCmd, opts)
|
|
5305
6470
|
);
|
|
5306
6471
|
} catch (err) {
|
|
5307
6472
|
fail(err);
|
|
@@ -5362,6 +6527,9 @@ var SdkDataFilteringProfilesService = class {
|
|
|
5362
6527
|
async list(params) {
|
|
5363
6528
|
return this.client.list(params);
|
|
5364
6529
|
}
|
|
6530
|
+
async listAll(params) {
|
|
6531
|
+
return this.client.listAll(params);
|
|
6532
|
+
}
|
|
5365
6533
|
async get(id) {
|
|
5366
6534
|
return this.client.get(id);
|
|
5367
6535
|
}
|
|
@@ -5495,7 +6663,7 @@ function listFlags(cmd) {
|
|
|
5495
6663
|
"--offset <n>",
|
|
5496
6664
|
"Starting offset \u2014 rounds down to a page boundary",
|
|
5497
6665
|
(v) => Number.parseInt(v, 10)
|
|
5498
|
-
).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format
|
|
6666
|
+
).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
|
|
5499
6667
|
registerPageAliases(cmd, { sizeFlag: "--size", sizeKey: "size" });
|
|
5500
6668
|
return cmd;
|
|
5501
6669
|
}
|
|
@@ -5516,16 +6684,17 @@ function register2(dlp) {
|
|
|
5516
6684
|
try {
|
|
5517
6685
|
const { page, size } = resolvePageParams(listCmd, opts);
|
|
5518
6686
|
const svc = new SdkDataFilteringProfilesService();
|
|
5519
|
-
const
|
|
5520
|
-
|
|
6687
|
+
const all = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
|
|
6688
|
+
const r = all ? { content: all, totalElements: all.length } : await svc.list({ page, size, sort: opts.sort });
|
|
6689
|
+
dlpFilteringProfiles.renderList(r, await resolveOutput(listCmd, opts));
|
|
5521
6690
|
} catch (err) {
|
|
5522
6691
|
fail(err);
|
|
5523
6692
|
}
|
|
5524
6693
|
});
|
|
5525
|
-
group.command("get <id>").description("Get a filtering profile by id").option("--output <fmt>", "Output format
|
|
6694
|
+
const getCmd = group.command("get <id>").description("Get a filtering profile by id").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
|
|
5526
6695
|
try {
|
|
5527
6696
|
const svc = new SdkDataFilteringProfilesService();
|
|
5528
|
-
dlpFilteringProfiles.renderGet(await svc.get(id), opts
|
|
6697
|
+
dlpFilteringProfiles.renderGet(await svc.get(id), await resolveOutput(getCmd, opts));
|
|
5529
6698
|
} catch (err) {
|
|
5530
6699
|
fail(err);
|
|
5531
6700
|
}
|
|
@@ -5621,6 +6790,9 @@ var SdkDataPatternsService = class {
|
|
|
5621
6790
|
async list(params) {
|
|
5622
6791
|
return this.client.list(params);
|
|
5623
6792
|
}
|
|
6793
|
+
async listAll(params) {
|
|
6794
|
+
return this.client.listAll(params);
|
|
6795
|
+
}
|
|
5624
6796
|
async create(body) {
|
|
5625
6797
|
return this.client.create(body);
|
|
5626
6798
|
}
|
|
@@ -5644,7 +6816,7 @@ function listFlags2(cmd) {
|
|
|
5644
6816
|
"--offset <n>",
|
|
5645
6817
|
"Starting offset \u2014 rounds down to a page boundary",
|
|
5646
6818
|
(v) => Number.parseInt(v, 10)
|
|
5647
|
-
).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format
|
|
6819
|
+
).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
|
|
5648
6820
|
registerPageAliases(cmd, { sizeFlag: "--size", sizeKey: "size" });
|
|
5649
6821
|
return cmd;
|
|
5650
6822
|
}
|
|
@@ -5666,9 +6838,10 @@ function register4(dlp) {
|
|
|
5666
6838
|
try {
|
|
5667
6839
|
const { page, size } = resolvePageParams(listCmd, opts);
|
|
5668
6840
|
const svc = new SdkDataPatternsService();
|
|
6841
|
+
const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
|
|
5669
6842
|
dlpPatterns.renderList(
|
|
5670
|
-
await svc.list({ page, size, sort: opts.sort }),
|
|
5671
|
-
opts
|
|
6843
|
+
result ? { content: result, totalElements: result.length } : await svc.list({ page, size, sort: opts.sort }),
|
|
6844
|
+
await resolveOutput(listCmd, opts)
|
|
5672
6845
|
);
|
|
5673
6846
|
} catch (err) {
|
|
5674
6847
|
fail(err);
|
|
@@ -5686,11 +6859,11 @@ function register4(dlp) {
|
|
|
5686
6859
|
usageError(err instanceof Error ? err.message : String(err));
|
|
5687
6860
|
}
|
|
5688
6861
|
});
|
|
5689
|
-
group.command("get <id>").description("Get a data pattern by id").option("--output <fmt>", "Output format
|
|
6862
|
+
const getCmd = group.command("get <id>").description("Get a data pattern by id").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
|
|
5690
6863
|
try {
|
|
5691
6864
|
dlpPatterns.renderGet(
|
|
5692
6865
|
await new SdkDataPatternsService().get(id),
|
|
5693
|
-
opts
|
|
6866
|
+
await resolveOutput(getCmd, opts)
|
|
5694
6867
|
);
|
|
5695
6868
|
} catch (err) {
|
|
5696
6869
|
fail(err);
|
|
@@ -5746,6 +6919,9 @@ var SdkDataProfilesService = class {
|
|
|
5746
6919
|
async list(params) {
|
|
5747
6920
|
return this.client.list(params);
|
|
5748
6921
|
}
|
|
6922
|
+
async listAll(params) {
|
|
6923
|
+
return this.client.listAll(params);
|
|
6924
|
+
}
|
|
5749
6925
|
async create(body) {
|
|
5750
6926
|
return this.client.create(body);
|
|
5751
6927
|
}
|
|
@@ -5766,7 +6942,7 @@ function listFlags3(cmd) {
|
|
|
5766
6942
|
"--offset <n>",
|
|
5767
6943
|
"Starting offset \u2014 rounds down to a page boundary",
|
|
5768
6944
|
(v) => Number.parseInt(v, 10)
|
|
5769
|
-
).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format
|
|
6945
|
+
).option("--sort <field,dir>", "Sort criteria (repeatable)", repeatable).option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
|
|
5770
6946
|
registerPageAliases(cmd, { sizeFlag: "--size", sizeKey: "size" });
|
|
5771
6947
|
return cmd;
|
|
5772
6948
|
}
|
|
@@ -5800,9 +6976,10 @@ function register5(dlp) {
|
|
|
5800
6976
|
try {
|
|
5801
6977
|
const { page, size } = resolvePageParams(listCmd, opts);
|
|
5802
6978
|
const svc = new SdkDataProfilesService();
|
|
6979
|
+
const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
|
|
5803
6980
|
dlpProfiles.renderList(
|
|
5804
|
-
await svc.list({ page, size, sort: opts.sort }),
|
|
5805
|
-
opts
|
|
6981
|
+
result ? { content: result, totalElements: result.length } : await svc.list({ page, size, sort: opts.sort }),
|
|
6982
|
+
await resolveOutput(listCmd, opts)
|
|
5806
6983
|
);
|
|
5807
6984
|
} catch (err) {
|
|
5808
6985
|
fail(err);
|
|
@@ -5820,11 +6997,11 @@ function register5(dlp) {
|
|
|
5820
6997
|
usageError(err instanceof Error ? err.message : String(err));
|
|
5821
6998
|
}
|
|
5822
6999
|
});
|
|
5823
|
-
group.command("get <id>").description("Get a data profile by id").option("--output <fmt>", "Output format
|
|
7000
|
+
const getCmd = group.command("get <id>").description("Get a data profile by id").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (id, opts) => {
|
|
5824
7001
|
try {
|
|
5825
7002
|
dlpProfiles.renderGet(
|
|
5826
7003
|
await new SdkDataProfilesService().get(id),
|
|
5827
|
-
opts
|
|
7004
|
+
await resolveOutput(getCmd, opts)
|
|
5828
7005
|
);
|
|
5829
7006
|
} catch (err) {
|
|
5830
7007
|
fail(err);
|
|
@@ -6373,14 +7550,14 @@ function registerSampleCommand(parent) {
|
|
|
6373
7550
|
|
|
6374
7551
|
// src/cli/commands/runtime.ts
|
|
6375
7552
|
function renderScanResult(result) {
|
|
6376
|
-
const actionColor = result.action === "block" ?
|
|
7553
|
+
const actionColor = result.action === "block" ? chalk11.red : chalk11.green;
|
|
6377
7554
|
ui.header("Scan Result");
|
|
6378
7555
|
ui.keyValue([
|
|
6379
7556
|
["Action", actionColor(result.action.toUpperCase())],
|
|
6380
7557
|
["Category", result.category],
|
|
6381
|
-
["Triggered", result.triggered ?
|
|
6382
|
-
["Scan ID",
|
|
6383
|
-
["Report ID",
|
|
7558
|
+
["Triggered", result.triggered ? chalk11.red("yes") : chalk11.green("no")],
|
|
7559
|
+
["Scan ID", chalk11.dim(result.scanId)],
|
|
7560
|
+
["Report ID", chalk11.dim(result.reportId)]
|
|
6384
7561
|
]);
|
|
6385
7562
|
const flags = Object.entries(result.detections).filter(([, v]) => v);
|
|
6386
7563
|
if (flags.length > 0) {
|
|
@@ -6472,15 +7649,26 @@ async function createMgmtService() {
|
|
|
6472
7649
|
function registerRuntimeCommand(program) {
|
|
6473
7650
|
const runtime = program.command("runtime").description("Runtime prompt scanning against AIRS profiles");
|
|
6474
7651
|
const apiKeys = runtime.command("api-keys").description("Manage AIRS API keys");
|
|
6475
|
-
apiKeys.command("list").description("List API keys").option("--
|
|
7652
|
+
const apiKeysList = registerListFlags(apiKeys.command("list"), { dialect: "offset" }).description("List API keys").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
|
|
6476
7653
|
try {
|
|
6477
|
-
const fmt = opts
|
|
7654
|
+
const fmt = await resolveOutput(apiKeysList, opts);
|
|
7655
|
+
const page = resolveListParams(apiKeysList, opts, { dialect: "offset" });
|
|
6478
7656
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
6479
7657
|
const service = await createMgmtService();
|
|
7658
|
+
if (page.all) {
|
|
7659
|
+
const items = await service.listAllApiKeys({ limit: page.limit, max: page.max });
|
|
7660
|
+
emitList(apiKeysView, items, fmt, {
|
|
7661
|
+
page: { returned: items.length, total: items.length, all: true }
|
|
7662
|
+
});
|
|
7663
|
+
return;
|
|
7664
|
+
}
|
|
6480
7665
|
const result = await service.listApiKeys({
|
|
6481
|
-
limit:
|
|
7666
|
+
limit: page.limit,
|
|
7667
|
+
offset: page.offset
|
|
7668
|
+
});
|
|
7669
|
+
emitList(apiKeysView, result.apiKeys, fmt, {
|
|
7670
|
+
page: { returned: result.apiKeys.length, next: result.nextOffset }
|
|
6482
7671
|
});
|
|
6483
|
-
renderApiKeyList(result.apiKeys, fmt);
|
|
6484
7672
|
} catch (err) {
|
|
6485
7673
|
fail(err);
|
|
6486
7674
|
}
|
|
@@ -6649,10 +7837,10 @@ function registerRuntimeCommand(program) {
|
|
|
6649
7837
|
ui.header("Bulk Scan Complete");
|
|
6650
7838
|
ui.keyValue([
|
|
6651
7839
|
["Total", results.length],
|
|
6652
|
-
["Blocked",
|
|
6653
|
-
["Allowed",
|
|
6654
|
-
["Failed",
|
|
6655
|
-
["Output",
|
|
7840
|
+
["Blocked", chalk11.red(String(blocked))],
|
|
7841
|
+
["Allowed", chalk11.green(String(allowed))],
|
|
7842
|
+
["Failed", chalk11.red(String(failed))],
|
|
7843
|
+
["Output", chalk11.cyan(outputPath)]
|
|
6656
7844
|
]);
|
|
6657
7845
|
if (failed > 0) {
|
|
6658
7846
|
ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
|
|
@@ -6667,25 +7855,37 @@ function registerRuntimeCommand(program) {
|
|
|
6667
7855
|
}
|
|
6668
7856
|
});
|
|
6669
7857
|
const customerApps = runtime.command("customer-apps").description("Manage AIRS customer apps");
|
|
6670
|
-
customerApps.command("list").description("List customer apps").option("--
|
|
7858
|
+
const customerAppsList = registerListFlags(customerApps.command("list"), { dialect: "offset" }).description("List customer apps").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
|
|
6671
7859
|
try {
|
|
6672
|
-
const fmt = opts
|
|
7860
|
+
const fmt = await resolveOutput(customerAppsList, opts);
|
|
7861
|
+
const page = resolveListParams(customerAppsList, opts, { dialect: "offset" });
|
|
6673
7862
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
6674
7863
|
const service = await createMgmtService();
|
|
7864
|
+
if (page.all) {
|
|
7865
|
+
const items = await service.listAllCustomerApps({ limit: page.limit, max: page.max });
|
|
7866
|
+
emitList(customerAppsView, items, fmt, {
|
|
7867
|
+
page: { returned: items.length, total: items.length, all: true }
|
|
7868
|
+
});
|
|
7869
|
+
return;
|
|
7870
|
+
}
|
|
6675
7871
|
const result = await service.listCustomerApps({
|
|
6676
|
-
limit:
|
|
7872
|
+
limit: page.limit,
|
|
7873
|
+
offset: page.offset
|
|
7874
|
+
});
|
|
7875
|
+
emitList(customerAppsView, result.apps, fmt, {
|
|
7876
|
+
page: { returned: result.apps.length, next: result.nextOffset }
|
|
6677
7877
|
});
|
|
6678
|
-
renderCustomerAppList(result.apps, fmt);
|
|
6679
7878
|
} catch (err) {
|
|
6680
7879
|
fail(err);
|
|
6681
7880
|
}
|
|
6682
7881
|
});
|
|
6683
|
-
customerApps.command("get <appName>").description("Get customer app details").action(async (appName) => {
|
|
7882
|
+
const customerAppsGet = customerApps.command("get <appName>").description("Get customer app details").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (appName, opts) => {
|
|
6684
7883
|
try {
|
|
6685
|
-
|
|
7884
|
+
const fmt = await resolveOutput(customerAppsGet, opts);
|
|
7885
|
+
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
6686
7886
|
const service = await createMgmtService();
|
|
6687
7887
|
const app = await service.getCustomerApp(appName);
|
|
6688
|
-
|
|
7888
|
+
emitDetail(customerAppsView, app, fmt);
|
|
6689
7889
|
} catch (err) {
|
|
6690
7890
|
fail(err);
|
|
6691
7891
|
}
|
|
@@ -6767,7 +7967,7 @@ function registerRuntimeCommand(program) {
|
|
|
6767
7967
|
}
|
|
6768
7968
|
});
|
|
6769
7969
|
const profiles = runtime.command("profiles").description("Manage AIRS security profiles");
|
|
6770
|
-
profiles.command("list").description("List security profiles").option("--
|
|
7970
|
+
const profilesList = registerListFlags(profiles.command("list"), { dialect: "offset" }).description("List security profiles").option("--all-versions", "Include every profile revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
|
|
6771
7971
|
"after",
|
|
6772
7972
|
examples(
|
|
6773
7973
|
"airs runtime profiles list",
|
|
@@ -6776,49 +7976,55 @@ function registerRuntimeCommand(program) {
|
|
|
6776
7976
|
)
|
|
6777
7977
|
).action(async (opts) => {
|
|
6778
7978
|
try {
|
|
6779
|
-
const fmt = opts
|
|
6780
|
-
|
|
6781
|
-
usageError(`Invalid output format "${fmt}". Valid: ${OUTPUT_FORMATS.join(", ")}`);
|
|
6782
|
-
}
|
|
7979
|
+
const fmt = await resolveOutput(profilesList, opts);
|
|
7980
|
+
const page = resolveListParams(profilesList, opts, { dialect: "offset" });
|
|
6783
7981
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
6784
7982
|
const service = await createMgmtService();
|
|
7983
|
+
if (page.all) {
|
|
7984
|
+
const items = await service.listAllProfiles({
|
|
7985
|
+
limit: page.limit,
|
|
7986
|
+
latest: !opts.allVersions,
|
|
7987
|
+
max: page.max
|
|
7988
|
+
});
|
|
7989
|
+
emitList(profilesView, items, fmt, {
|
|
7990
|
+
page: { returned: items.length, total: items.length, all: true }
|
|
7991
|
+
});
|
|
7992
|
+
return;
|
|
7993
|
+
}
|
|
6785
7994
|
const result = await service.listProfiles({
|
|
6786
|
-
limit:
|
|
6787
|
-
offset:
|
|
7995
|
+
limit: page.limit,
|
|
7996
|
+
offset: page.offset,
|
|
7997
|
+
latest: !opts.allVersions
|
|
7998
|
+
});
|
|
7999
|
+
emitList(profilesView, result.profiles, fmt, {
|
|
8000
|
+
page: {
|
|
8001
|
+
returned: result.profiles.length,
|
|
8002
|
+
next: result.nextOffset
|
|
8003
|
+
}
|
|
6788
8004
|
});
|
|
6789
|
-
renderProfileList(result.profiles, fmt);
|
|
6790
|
-
if (fmt === "pretty" && result.nextOffset != null) {
|
|
6791
|
-
ui.dim(`Next offset: ${result.nextOffset}`);
|
|
6792
|
-
}
|
|
6793
8005
|
} catch (err) {
|
|
6794
8006
|
fail(err);
|
|
6795
8007
|
}
|
|
6796
8008
|
});
|
|
6797
|
-
profiles.command("get <nameOrId>").description("Get a security profile by name or UUID").option("--output <format>", "Output format: pretty, json, yaml"
|
|
8009
|
+
const profilesGet = profiles.command("get <nameOrId>").description("Get a security profile by name or UUID").option("--revision <n>", "Select an exact revision", Number).option("--all-versions", "Return every matching revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (nameOrId, opts) => {
|
|
6798
8010
|
try {
|
|
6799
|
-
const fmt = opts
|
|
6800
|
-
if (fmt !== "pretty" && fmt !== "json" && fmt !== "yaml") {
|
|
6801
|
-
usageError(`Invalid output format "${fmt}". Valid: pretty, json, yaml`);
|
|
6802
|
-
}
|
|
8011
|
+
const fmt = await resolveOutput(profilesGet, opts);
|
|
6803
8012
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
6804
8013
|
const service = await createMgmtService();
|
|
6805
8014
|
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
6806
8015
|
nameOrId
|
|
6807
8016
|
);
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
const
|
|
6813
|
-
if (
|
|
6814
|
-
if (
|
|
6815
|
-
|
|
6816
|
-
if (profile.updatedBy) lines.push(`updatedBy: ${profile.updatedBy}`);
|
|
6817
|
-
if (profile.lastModifiedTs) lines.push(`lastModifiedTs: ${profile.lastModifiedTs}`);
|
|
6818
|
-
if (profile.policy) lines.push(`policy: ${JSON.stringify(profile.policy, null, 2)}`);
|
|
6819
|
-
console.log(lines.join("\n"));
|
|
8017
|
+
if (opts.revision !== void 0 || opts.allVersions) {
|
|
8018
|
+
const profiles2 = (await service.listAllProfiles({ latest: false })).filter(
|
|
8019
|
+
(profile) => isUuid ? profile.profileId === nameOrId : profile.profileName === nameOrId
|
|
8020
|
+
);
|
|
8021
|
+
const selected = opts.revision === void 0 ? profiles2 : profiles2.filter((profile) => profile.revision === opts.revision);
|
|
8022
|
+
if (selected.length === 0) throw new Error(`Profile ${nameOrId} not found`);
|
|
8023
|
+
if (opts.allVersions) emitList(profilesView, selected, fmt);
|
|
8024
|
+
else emitDetail(profilesView, selected[0], fmt);
|
|
6820
8025
|
} else {
|
|
6821
|
-
|
|
8026
|
+
const profile = isUuid ? await service.getProfile(nameOrId) : await service.getProfileByName(nameOrId);
|
|
8027
|
+
emitDetail(profilesView, profile, fmt);
|
|
6822
8028
|
}
|
|
6823
8029
|
} catch (err) {
|
|
6824
8030
|
fail(err);
|
|
@@ -7061,10 +8267,10 @@ function registerRuntimeCommand(program) {
|
|
|
7061
8267
|
ui.header("Resume Poll Complete");
|
|
7062
8268
|
ui.keyValue([
|
|
7063
8269
|
["Total", results.length],
|
|
7064
|
-
["Blocked",
|
|
7065
|
-
["Allowed",
|
|
7066
|
-
["Failed",
|
|
7067
|
-
["Output",
|
|
8270
|
+
["Blocked", chalk11.red(String(blocked))],
|
|
8271
|
+
["Allowed", chalk11.green(String(allowed))],
|
|
8272
|
+
["Failed", chalk11.red(String(failed))],
|
|
8273
|
+
["Output", chalk11.cyan(outputPath)]
|
|
7068
8274
|
]);
|
|
7069
8275
|
if (failed > 0) {
|
|
7070
8276
|
ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
|
|
@@ -7143,52 +8349,47 @@ function registerRuntimeCommand(program) {
|
|
|
7143
8349
|
}
|
|
7144
8350
|
});
|
|
7145
8351
|
registerEvalCommand(topics);
|
|
7146
|
-
topics.command("get <nameOrId>").description("Get a custom topic by name or UUID").option("--output <format>", "Output format: pretty, json, yaml"
|
|
8352
|
+
const topicsGet = topics.command("get <nameOrId>").description("Get a custom topic by name or UUID").option("--revision <n>", "Select an exact revision", Number).option("--all-versions", "Return every matching revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (nameOrId, opts) => {
|
|
7147
8353
|
try {
|
|
7148
|
-
const fmt = opts
|
|
7149
|
-
if (fmt !== "pretty" && fmt !== "json" && fmt !== "yaml") {
|
|
7150
|
-
usageError(`Invalid output format "${fmt}". Valid: pretty, json, yaml`);
|
|
7151
|
-
}
|
|
8354
|
+
const fmt = await resolveOutput(topicsGet, opts);
|
|
7152
8355
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
7153
8356
|
const service = await createMgmtService();
|
|
7154
8357
|
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
7155
8358
|
nameOrId
|
|
7156
8359
|
);
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
const
|
|
7162
|
-
if (
|
|
7163
|
-
if (
|
|
7164
|
-
|
|
7165
|
-
lines.push("examples:");
|
|
7166
|
-
for (const ex of topic.examples) lines.push(` - ${ex}`);
|
|
7167
|
-
}
|
|
7168
|
-
if (topic.created_by) lines.push(`created_by: ${topic.created_by}`);
|
|
7169
|
-
if (topic.updated_by) lines.push(`updated_by: ${topic.updated_by}`);
|
|
7170
|
-
if (topic.last_modified_ts) lines.push(`last_modified_ts: ${topic.last_modified_ts}`);
|
|
7171
|
-
console.log(lines.join("\n"));
|
|
8360
|
+
if (opts.revision !== void 0 || opts.allVersions) {
|
|
8361
|
+
const topics2 = (await service.listTopics()).filter(
|
|
8362
|
+
(topic) => isUuid ? topic.topic_id === nameOrId : topic.topic_name === nameOrId
|
|
8363
|
+
);
|
|
8364
|
+
const selected = opts.revision === void 0 ? topics2 : topics2.filter((topic) => topic.revision === opts.revision);
|
|
8365
|
+
if (selected.length === 0) throw new Error(`Topic ${nameOrId} not found`);
|
|
8366
|
+
if (opts.allVersions) emitList(topicsView, selected, fmt);
|
|
8367
|
+
else emitDetail(topicsView, selected[0], fmt);
|
|
7172
8368
|
} else {
|
|
7173
|
-
|
|
8369
|
+
const topic = isUuid ? await service.getTopic(nameOrId) : await service.getTopicByName(nameOrId);
|
|
8370
|
+
emitDetail(topicsView, topic, fmt);
|
|
7174
8371
|
}
|
|
7175
8372
|
} catch (err) {
|
|
7176
8373
|
fail(err);
|
|
7177
8374
|
}
|
|
7178
8375
|
});
|
|
7179
|
-
topics.command("list").description("List custom topics").option("--
|
|
8376
|
+
const topicsList = registerListFlags(topics.command("list"), { dialect: "offset" }).description("List custom topics").option("--all-versions", "Include every topic revision").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
|
|
7180
8377
|
try {
|
|
7181
|
-
const fmt = opts
|
|
8378
|
+
const fmt = await resolveOutput(topicsList, opts);
|
|
8379
|
+
const params = resolveListParams(topicsList, opts, { dialect: "offset" });
|
|
7182
8380
|
if (fmt === "pretty") renderRuntimeConfigHeader();
|
|
7183
8381
|
const service = await createMgmtService();
|
|
7184
|
-
const allTopics = await service.listTopics()
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
const page = allTopics.slice(offset, offset + limit);
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
8382
|
+
const allTopics = opts.allVersions ? await service.listTopics() : await service.listLatestTopics(
|
|
8383
|
+
params.all ? { offset: 0, limit: params.max === 0 ? 1e4 : params.max } : { offset: params.offset, limit: params.limit }
|
|
8384
|
+
);
|
|
8385
|
+
const page = opts.allVersions && !params.all ? allTopics.slice(params.offset, params.offset + params.limit) : allTopics;
|
|
8386
|
+
emitList(topicsView, page, fmt, {
|
|
8387
|
+
page: params.all ? { returned: page.length, total: page.length, all: true } : {
|
|
8388
|
+
returned: page.length,
|
|
8389
|
+
total: opts.allVersions ? allTopics.length : void 0,
|
|
8390
|
+
next: opts.allVersions && params.offset + params.limit < allTopics.length ? params.offset + params.limit : void 0
|
|
8391
|
+
}
|
|
8392
|
+
});
|
|
7192
8393
|
} catch (err) {
|
|
7193
8394
|
fail(err);
|
|
7194
8395
|
}
|
|
@@ -7397,16 +8598,45 @@ function applyListDeleteAliases(cmd) {
|
|
|
7397
8598
|
applyListDeleteAliases(sub);
|
|
7398
8599
|
}
|
|
7399
8600
|
}
|
|
8601
|
+
function applyReadContractFlags(cmd) {
|
|
8602
|
+
for (const sub of cmd.commands) {
|
|
8603
|
+
const flags = () => sub.options.map((option) => option.long);
|
|
8604
|
+
if ((sub.name() === "list" || sub.name() === "get") && !flags().includes("--output")) {
|
|
8605
|
+
sub.option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml");
|
|
8606
|
+
}
|
|
8607
|
+
if (sub.name() === "list" && (flags().includes("--limit") || flags().includes("--offset"))) {
|
|
8608
|
+
if (!flags().includes("--limit")) sub.option("--limit <n>", "Items per page", Number, 50);
|
|
8609
|
+
if (!flags().includes("--offset")) sub.option("--offset <n>", "Item offset", Number, 0);
|
|
8610
|
+
if (!flags().includes("--all")) sub.option("--all", "Walk all pages");
|
|
8611
|
+
if (!flags().includes("--max")) {
|
|
8612
|
+
sub.option("--max <n>", "Maximum items with --all; 0 removes the cap", Number, 1e4);
|
|
8613
|
+
}
|
|
8614
|
+
}
|
|
8615
|
+
applyReadContractFlags(sub);
|
|
8616
|
+
}
|
|
8617
|
+
}
|
|
8618
|
+
function applySortedHelp(cmd) {
|
|
8619
|
+
cmd.configureHelp({ sortOptions: true, sortSubcommands: true });
|
|
8620
|
+
for (const sub of cmd.commands) applySortedHelp(sub);
|
|
8621
|
+
}
|
|
7400
8622
|
function buildProgram() {
|
|
7401
8623
|
const here = dirname4(fileURLToPath(import.meta.url));
|
|
7402
8624
|
const pkg = JSON.parse(readFileSync4(join4(here, "../../package.json"), "utf-8"));
|
|
7403
8625
|
const program = new Command();
|
|
7404
8626
|
program.name("airs").description(
|
|
7405
8627
|
"CLI and library for Palo Alto Prisma AIRS \u2014 guardrail refinement, AI red teaming, model security scanning, profile audits"
|
|
7406
|
-
).version(pkg.version).option("--debug", "Log all AIRS/SCM API requests and responses to a JSONL file").option("--quiet", "Suppress status and decorative output (data and errors still print)");
|
|
7407
|
-
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
8628
|
+
).version(pkg.version).option("--debug", "Log all AIRS/SCM API requests and responses to a JSONL file").option("--output <format>", "Default output format for read commands").option("--quiet", "Suppress status and decorative output (data and errors still print)");
|
|
8629
|
+
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
7408
8630
|
const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
|
|
7409
8631
|
setQuiet(Boolean(root.quiet));
|
|
8632
|
+
if ((actionCommand.name() === "list" || actionCommand.name() === "get") && actionCommand.options.some((option) => option.long === "--output")) {
|
|
8633
|
+
try {
|
|
8634
|
+
const format = await resolveOutput(actionCommand, actionCommand.opts());
|
|
8635
|
+
actionCommand.setOptionValueWithSource("output", format, "implied");
|
|
8636
|
+
} catch (error) {
|
|
8637
|
+
fail(error);
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
7410
8640
|
if (root.debug) {
|
|
7411
8641
|
const logPath = join4(homedir(), ".prisma-airs", `debug-api-${Date.now()}.jsonl`);
|
|
7412
8642
|
installDebugLogger(logPath);
|
|
@@ -7416,10 +8646,13 @@ function buildProgram() {
|
|
|
7416
8646
|
registerRuntimeCommand(program);
|
|
7417
8647
|
registerRedteamCommand(program);
|
|
7418
8648
|
registerModelSecurityCommand(program);
|
|
8649
|
+
registerAiGatewayCommand(program);
|
|
7419
8650
|
registerConfigCommand(program);
|
|
7420
8651
|
registerDoctorCommand(program);
|
|
7421
8652
|
registerCompletionCommand(program);
|
|
7422
8653
|
applyListDeleteAliases(program);
|
|
8654
|
+
applyReadContractFlags(program);
|
|
8655
|
+
applySortedHelp(program);
|
|
7423
8656
|
return program;
|
|
7424
8657
|
}
|
|
7425
8658
|
|