@cdot65/prisma-airs-cli 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/{chunk-TTBN7YHC.js → chunk-2VIUZRPB.js} +140 -1
- package/dist/cli/index.js +1186 -292
- package/dist/index.d.ts +106 -4
- package/dist/index.js +1 -1
- package/package.json +2 -2
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-2VIUZRPB.js";
|
|
24
24
|
|
|
25
25
|
// src/cli/index.ts
|
|
26
26
|
import "dotenv/config";
|
|
@@ -45,16 +45,207 @@ 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
|
+
};
|
|
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
|
+
};
|
|
54
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
|
+
avgCents: raw.data.avg,
|
|
191
|
+
quotaExceeded: raw.data.isQuotaExceeded,
|
|
192
|
+
records: raw.data.records.map((r) => ({ date: r.x, costCents: r.y }))
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Re-read after a write, falling back to the (partial) write response if the get fails. */
|
|
196
|
+
async refetchAfterWrite(workspaceRef, writeResponse) {
|
|
197
|
+
try {
|
|
198
|
+
return await this.getWorkspace(workspaceRef, { plane: "admin" });
|
|
199
|
+
} catch {
|
|
200
|
+
return normalizeWorkspaceDetail(writeResponse);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
55
204
|
|
|
56
|
-
// src/
|
|
57
|
-
|
|
205
|
+
// src/config/client-options.ts
|
|
206
|
+
function runtimeInitOptions(config) {
|
|
207
|
+
return {
|
|
208
|
+
apiKey: config.airsApiKey,
|
|
209
|
+
apiToken: config.airsApiToken,
|
|
210
|
+
apiEndpoint: config.airsApiEndpoint,
|
|
211
|
+
numRetries: config.airsNumRetries
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function redTeamClientOptions(config) {
|
|
215
|
+
return {
|
|
216
|
+
clientId: config.mgmtClientId,
|
|
217
|
+
clientSecret: config.mgmtClientSecret,
|
|
218
|
+
tsgId: config.mgmtTsgId,
|
|
219
|
+
dataEndpoint: config.redTeamDataEndpoint,
|
|
220
|
+
mgmtEndpoint: config.redTeamMgmtEndpoint,
|
|
221
|
+
tokenEndpoint: config.redTeamTokenEndpoint ?? config.mgmtTokenEndpoint,
|
|
222
|
+
networkBrokerEndpoint: config.redTeamNetworkBrokerEndpoint
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function aiGatewayClientOptions(config) {
|
|
226
|
+
return {
|
|
227
|
+
clientId: config.mgmtClientId,
|
|
228
|
+
clientSecret: config.mgmtClientSecret,
|
|
229
|
+
tsgId: config.mgmtTsgId,
|
|
230
|
+
dataEndpoint: config.aiGwDataEndpoint,
|
|
231
|
+
adminEndpoint: config.aiGwAdminEndpoint,
|
|
232
|
+
tokenEndpoint: config.aiGwTokenEndpoint ?? config.mgmtTokenEndpoint
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function modelSecurityClientOptions(config) {
|
|
236
|
+
return {
|
|
237
|
+
clientId: config.mgmtClientId,
|
|
238
|
+
clientSecret: config.mgmtClientSecret,
|
|
239
|
+
tsgId: config.mgmtTsgId,
|
|
240
|
+
dataEndpoint: config.modelSecDataEndpoint,
|
|
241
|
+
mgmtEndpoint: config.modelSecMgmtEndpoint,
|
|
242
|
+
tokenEndpoint: config.modelSecTokenEndpoint ?? config.mgmtTokenEndpoint
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/cli/renderer/aigateway.ts
|
|
247
|
+
import chalk4 from "chalk";
|
|
248
|
+
import { dump as yamlDump } from "js-yaml";
|
|
58
249
|
|
|
59
250
|
// src/cli/renderer/common.ts
|
|
60
251
|
import chalk2 from "chalk";
|
|
@@ -121,6 +312,7 @@ function formatOutput(rows, columns, format) {
|
|
|
121
312
|
}
|
|
122
313
|
|
|
123
314
|
// src/cli/renderer/ui.ts
|
|
315
|
+
import chalk3 from "chalk";
|
|
124
316
|
var INDENT = " ";
|
|
125
317
|
var quietMode = false;
|
|
126
318
|
function setQuiet(quiet) {
|
|
@@ -211,6 +403,118 @@ ${INDENT}${chalk3.bold(label)}
|
|
|
211
403
|
}
|
|
212
404
|
};
|
|
213
405
|
|
|
406
|
+
// src/cli/renderer/aigateway.ts
|
|
407
|
+
function renderAiGatewayHeader() {
|
|
408
|
+
ui.header("Prisma AIRS \u2014 AI Gateway", "Gateway workspace operations");
|
|
409
|
+
}
|
|
410
|
+
function statusColor(status) {
|
|
411
|
+
switch (status.toLowerCase()) {
|
|
412
|
+
case "active":
|
|
413
|
+
return chalk4.green;
|
|
414
|
+
case "archived":
|
|
415
|
+
return chalk4.yellow;
|
|
416
|
+
default:
|
|
417
|
+
return chalk4.dim;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function statusLabel(status) {
|
|
421
|
+
return status ?? "unknown";
|
|
422
|
+
}
|
|
423
|
+
function renderWorkspaceList(workspaces, format = "pretty") {
|
|
424
|
+
if (workspaces.length === 0) {
|
|
425
|
+
ui.emptyList("workspaces");
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (format !== "pretty") {
|
|
429
|
+
const rows = workspaces.map((w) => ({
|
|
430
|
+
id: w.id,
|
|
431
|
+
slug: w.slug,
|
|
432
|
+
name: w.name,
|
|
433
|
+
status: statusLabel(w.status),
|
|
434
|
+
isDefault: w.isDefault,
|
|
435
|
+
scopeName: w.scopeName ?? ""
|
|
436
|
+
}));
|
|
437
|
+
console.log(
|
|
438
|
+
formatOutput(
|
|
439
|
+
rows,
|
|
440
|
+
[
|
|
441
|
+
{ key: "id", label: "ID" },
|
|
442
|
+
{ key: "slug", label: "Slug" },
|
|
443
|
+
{ key: "name", label: "Name" },
|
|
444
|
+
{ key: "status", label: "Status" },
|
|
445
|
+
{ key: "isDefault", label: "Default" },
|
|
446
|
+
{ key: "scopeName", label: "Scope" }
|
|
447
|
+
],
|
|
448
|
+
format
|
|
449
|
+
)
|
|
450
|
+
);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
ui.section("AI Gateway Workspaces:");
|
|
454
|
+
for (const w of workspaces) {
|
|
455
|
+
ui.dim(w.id);
|
|
456
|
+
const status = statusColor(statusLabel(w.status))(statusLabel(w.status));
|
|
457
|
+
const dflt = w.isDefault ? chalk4.cyan(" default") : "";
|
|
458
|
+
console.log(` ${w.name} ${chalk4.dim(w.slug)} ${status}${dflt}`);
|
|
459
|
+
if (w.scopeName) console.log(` ${chalk4.dim(`scope: ${w.scopeName}`)}`);
|
|
460
|
+
console.log();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function renderWorkspaceDetail(workspace, format = "pretty") {
|
|
464
|
+
if (format !== "pretty") {
|
|
465
|
+
console.log(format === "json" ? JSON.stringify(workspace, null, 2) : yamlDump(workspace));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
ui.section("Workspace Detail:");
|
|
469
|
+
const pairs = [
|
|
470
|
+
["ID", workspace.id],
|
|
471
|
+
["Slug", workspace.slug],
|
|
472
|
+
["Name", workspace.name],
|
|
473
|
+
["Status", statusColor(statusLabel(workspace.status))(statusLabel(workspace.status))],
|
|
474
|
+
["Default", workspace.isDefault ? "yes" : "no"]
|
|
475
|
+
];
|
|
476
|
+
if (workspace.description != null) pairs.push(["Description", workspace.description]);
|
|
477
|
+
if (workspace.scopeName != null) pairs.push(["Scope", workspace.scopeName]);
|
|
478
|
+
if (workspace.createdAt != null) pairs.push(["Created", workspace.createdAt]);
|
|
479
|
+
if (workspace.lastUpdatedAt != null) pairs.push(["Updated", workspace.lastUpdatedAt]);
|
|
480
|
+
ui.keyValue(pairs);
|
|
481
|
+
if (workspace.defaults && Object.keys(workspace.defaults).length > 0) {
|
|
482
|
+
ui.section("Defaults:");
|
|
483
|
+
console.log(chalk4.dim(JSON.stringify(workspace.defaults, null, 2)));
|
|
484
|
+
}
|
|
485
|
+
if (workspace.usageLimits.length > 0) {
|
|
486
|
+
ui.section("Usage Limits:");
|
|
487
|
+
console.log(chalk4.dim(JSON.stringify(workspace.usageLimits, null, 2)));
|
|
488
|
+
}
|
|
489
|
+
if (workspace.rateLimits.length > 0) {
|
|
490
|
+
ui.section("Rate Limits:");
|
|
491
|
+
console.log(chalk4.dim(JSON.stringify(workspace.rateLimits, null, 2)));
|
|
492
|
+
}
|
|
493
|
+
if (workspace.securitySettings && Object.keys(workspace.securitySettings).length > 0) {
|
|
494
|
+
ui.section("Security Settings:");
|
|
495
|
+
ui.keyValue(Object.entries(workspace.securitySettings).map(([k, v]) => [k, v]));
|
|
496
|
+
}
|
|
497
|
+
console.log();
|
|
498
|
+
}
|
|
499
|
+
function renderCostReport(report, format = "pretty") {
|
|
500
|
+
if (format !== "pretty") {
|
|
501
|
+
console.log(format === "json" ? JSON.stringify(report, null, 2) : yamlDump(report));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const dollars = (cents) => `$${(cents / 100).toFixed(2)}`;
|
|
505
|
+
ui.section(`Cost \u2014 ${report.workspaceSlug} (last ${report.days}d):`);
|
|
506
|
+
ui.keyValue([
|
|
507
|
+
["Total", dollars(report.totalCents)],
|
|
508
|
+
["Daily average", dollars(report.avgCents)]
|
|
509
|
+
]);
|
|
510
|
+
if (report.quotaExceeded) ui.warn("Telemetry quota exceeded \u2014 data may be truncated");
|
|
511
|
+
if (report.records.length > 0) {
|
|
512
|
+
ui.section("Per day:");
|
|
513
|
+
ui.keyValue(report.records.map((r) => [r.date, dollars(r.costCents)]));
|
|
514
|
+
}
|
|
515
|
+
console.log();
|
|
516
|
+
}
|
|
517
|
+
|
|
214
518
|
// src/cli/renderer/backup.ts
|
|
215
519
|
function renderBackupHeader() {
|
|
216
520
|
ui.header("Prisma AIRS \u2014 Backup & Restore");
|
|
@@ -252,17 +556,17 @@ function renderRestoreSummary(results) {
|
|
|
252
556
|
}
|
|
253
557
|
|
|
254
558
|
// src/cli/renderer/dlp.ts
|
|
255
|
-
import
|
|
256
|
-
import { dump as
|
|
257
|
-
function
|
|
559
|
+
import chalk5 from "chalk";
|
|
560
|
+
import { dump as yamlDump2 } from "js-yaml";
|
|
561
|
+
function statusColor2(status) {
|
|
258
562
|
switch (status) {
|
|
259
563
|
case "active":
|
|
260
|
-
return
|
|
564
|
+
return chalk5.green(status);
|
|
261
565
|
case "deleted":
|
|
262
566
|
case "disabled":
|
|
263
|
-
return
|
|
567
|
+
return chalk5.yellow(status);
|
|
264
568
|
default:
|
|
265
|
-
return status ?
|
|
569
|
+
return status ? chalk5.dim(status) : chalk5.dim("\u2014");
|
|
266
570
|
}
|
|
267
571
|
}
|
|
268
572
|
function ts(ms) {
|
|
@@ -279,7 +583,7 @@ function emitStructured(payload, fmt) {
|
|
|
279
583
|
return;
|
|
280
584
|
}
|
|
281
585
|
if (fmt === "yaml") {
|
|
282
|
-
console.log(
|
|
586
|
+
console.log(yamlDump2(payload));
|
|
283
587
|
return;
|
|
284
588
|
}
|
|
285
589
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -398,11 +702,11 @@ var dlpFilteringProfiles = {
|
|
|
398
702
|
{ key: "version", label: "Version" }
|
|
399
703
|
],
|
|
400
704
|
(it) => {
|
|
401
|
-
const dir = it.direction ?
|
|
402
|
-
const sev = it.log_severity ?
|
|
403
|
-
const ver = it.version != null ?
|
|
404
|
-
return ` ${
|
|
405
|
-
${it.name} ${
|
|
705
|
+
const dir = it.direction ? chalk5.dim(` dir:${it.direction}`) : "";
|
|
706
|
+
const sev = it.log_severity ? chalk5.dim(` sev:${it.log_severity}`) : "";
|
|
707
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
708
|
+
return ` ${chalk5.dim(it.id)}
|
|
709
|
+
${it.name} ${chalk5.cyan(it.type ?? "")}${dir}${sev}${ver}`;
|
|
406
710
|
}
|
|
407
711
|
);
|
|
408
712
|
},
|
|
@@ -457,10 +761,10 @@ var dlpPatterns = {
|
|
|
457
761
|
{ key: "version", label: "Version" }
|
|
458
762
|
],
|
|
459
763
|
(it) => {
|
|
460
|
-
const tech = it.detection_config?.technique ?
|
|
461
|
-
const ver = it.version != null ?
|
|
462
|
-
return ` ${
|
|
463
|
-
${it.name} ${
|
|
764
|
+
const tech = it.detection_config?.technique ? chalk5.dim(` ${it.detection_config.technique}`) : "";
|
|
765
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
766
|
+
return ` ${chalk5.dim(it.id)}
|
|
767
|
+
${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.status)}${tech}${ver}`;
|
|
464
768
|
}
|
|
465
769
|
);
|
|
466
770
|
},
|
|
@@ -525,10 +829,10 @@ var dlpProfiles = {
|
|
|
525
829
|
{ key: "version", label: "Version" }
|
|
526
830
|
],
|
|
527
831
|
(it) => {
|
|
528
|
-
const ptype = it.profile_type ?
|
|
529
|
-
const ver = it.version != null ?
|
|
530
|
-
return ` ${
|
|
531
|
-
${it.name} ${
|
|
832
|
+
const ptype = it.profile_type ? chalk5.dim(` ${it.profile_type}`) : "";
|
|
833
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
834
|
+
return ` ${chalk5.dim(it.id)}
|
|
835
|
+
${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.profile_status)}${ptype}${ver}`;
|
|
532
836
|
}
|
|
533
837
|
);
|
|
534
838
|
},
|
|
@@ -582,10 +886,10 @@ var dlpDictionaries = {
|
|
|
582
886
|
{ key: "version", label: "Version" }
|
|
583
887
|
],
|
|
584
888
|
(it) => {
|
|
585
|
-
const kw = Array.isArray(it.keywords) ?
|
|
586
|
-
const ver = it.version != null ?
|
|
587
|
-
return ` ${
|
|
588
|
-
${it.name} ${
|
|
889
|
+
const kw = Array.isArray(it.keywords) ? chalk5.dim(` ${it.keywords.length} kw`) : "";
|
|
890
|
+
const ver = it.version != null ? chalk5.dim(` v${it.version}`) : "";
|
|
891
|
+
return ` ${chalk5.dim(it.id)}
|
|
892
|
+
${it.name} ${chalk5.cyan(it.type ?? "")} ${statusColor2(it.status)}${kw}${ver}`;
|
|
589
893
|
}
|
|
590
894
|
);
|
|
591
895
|
},
|
|
@@ -627,7 +931,7 @@ var dlpDictionaries = {
|
|
|
627
931
|
};
|
|
628
932
|
|
|
629
933
|
// src/cli/renderer/eval.ts
|
|
630
|
-
import
|
|
934
|
+
import chalk6 from "chalk";
|
|
631
935
|
function buildEvalOutput(profile, topic, intent, metrics, results) {
|
|
632
936
|
const fps = results.filter((r) => !r.testCase.expectedTriggered && r.actualTriggered).map((r) => ({ prompt: r.testCase.prompt, expected: false, actual: true }));
|
|
633
937
|
const fns = results.filter((r) => r.testCase.expectedTriggered && !r.actualTriggered).map((r) => ({ prompt: r.testCase.prompt, expected: true, actual: false }));
|
|
@@ -651,7 +955,7 @@ function buildEvalOutput(profile, topic, intent, metrics, results) {
|
|
|
651
955
|
};
|
|
652
956
|
}
|
|
653
957
|
function renderEvalTerminal(output) {
|
|
654
|
-
const coverageColor = output.metrics.coverage >= 0.9 ?
|
|
958
|
+
const coverageColor = output.metrics.coverage >= 0.9 ? chalk6.green : output.metrics.coverage >= 0.7 ? chalk6.yellow : chalk6.red;
|
|
655
959
|
ui.header("Eval Results");
|
|
656
960
|
ui.keyValue([
|
|
657
961
|
["Profile", output.profile],
|
|
@@ -684,8 +988,8 @@ function renderEvalTerminal(output) {
|
|
|
684
988
|
}
|
|
685
989
|
|
|
686
990
|
// src/cli/renderer/modelsecurity.ts
|
|
687
|
-
import
|
|
688
|
-
import { dump as
|
|
991
|
+
import chalk7 from "chalk";
|
|
992
|
+
import { dump as yamlDump3 } from "js-yaml";
|
|
689
993
|
function renderModelSecurityHeader() {
|
|
690
994
|
ui.header("Prisma AIRS \u2014 Model Security", "ML model supply chain security");
|
|
691
995
|
}
|
|
@@ -696,15 +1000,15 @@ function stateColor(state) {
|
|
|
696
1000
|
case "ALLOWING":
|
|
697
1001
|
case "PASSED":
|
|
698
1002
|
case "SUCCESS":
|
|
699
|
-
return
|
|
1003
|
+
return chalk7.green;
|
|
700
1004
|
case "BLOCKED":
|
|
701
1005
|
case "BLOCKING":
|
|
702
1006
|
case "FAILED":
|
|
703
|
-
return
|
|
1007
|
+
return chalk7.red;
|
|
704
1008
|
case "DISABLED":
|
|
705
|
-
return
|
|
1009
|
+
return chalk7.dim;
|
|
706
1010
|
default:
|
|
707
|
-
return
|
|
1011
|
+
return chalk7.yellow;
|
|
708
1012
|
}
|
|
709
1013
|
}
|
|
710
1014
|
function renderGroupList(groups, format = "pretty") {
|
|
@@ -736,8 +1040,8 @@ function renderGroupList(groups, format = "pretty") {
|
|
|
736
1040
|
ui.section("Security Groups:");
|
|
737
1041
|
for (const g of groups) {
|
|
738
1042
|
ui.dim(g.uuid);
|
|
739
|
-
const color = g.state === "ACTIVE" ?
|
|
740
|
-
console.log(` ${g.name} ${color(g.state)} source: ${
|
|
1043
|
+
const color = g.state === "ACTIVE" ? chalk7.green : chalk7.yellow;
|
|
1044
|
+
console.log(` ${g.name} ${color(g.state)} source: ${chalk7.dim(g.sourceType)}`);
|
|
741
1045
|
}
|
|
742
1046
|
console.log();
|
|
743
1047
|
}
|
|
@@ -747,15 +1051,15 @@ function renderGroupDetail(group, format = "pretty") {
|
|
|
747
1051
|
return;
|
|
748
1052
|
}
|
|
749
1053
|
if (format === "yaml") {
|
|
750
|
-
console.log(
|
|
1054
|
+
console.log(yamlDump3(group));
|
|
751
1055
|
return;
|
|
752
1056
|
}
|
|
753
1057
|
ui.section("Security Group Detail:");
|
|
754
|
-
const color = group.state === "ACTIVE" ?
|
|
1058
|
+
const color = group.state === "ACTIVE" ? chalk7.green : chalk7.yellow;
|
|
755
1059
|
ui.keyValue([
|
|
756
1060
|
["UUID", group.uuid],
|
|
757
1061
|
["Name", group.name],
|
|
758
|
-
["Description", group.description ||
|
|
1062
|
+
["Description", group.description || chalk7.dim("(none)")],
|
|
759
1063
|
["Source Type", group.sourceType],
|
|
760
1064
|
["State", color(group.state)],
|
|
761
1065
|
["Created", group.createdAt],
|
|
@@ -795,10 +1099,10 @@ function renderRuleList(rules, format = "pretty") {
|
|
|
795
1099
|
for (const r of rules) {
|
|
796
1100
|
ui.dim(r.uuid);
|
|
797
1101
|
console.log(
|
|
798
|
-
` ${r.name} type: ${
|
|
1102
|
+
` ${r.name} type: ${chalk7.dim(r.ruleType)} default: ${chalk7.dim(r.defaultState)}`
|
|
799
1103
|
);
|
|
800
|
-
console.log(` ${
|
|
801
|
-
console.log(` Sources: ${r.compatibleSources.map((s) =>
|
|
1104
|
+
console.log(` ${chalk7.dim(r.description)}`);
|
|
1105
|
+
console.log(` Sources: ${r.compatibleSources.map((s) => chalk7.dim(s)).join(", ")}`);
|
|
802
1106
|
}
|
|
803
1107
|
console.log();
|
|
804
1108
|
}
|
|
@@ -827,8 +1131,8 @@ function renderRuleDetail(rule) {
|
|
|
827
1131
|
if (rule.editableFields.length > 0) {
|
|
828
1132
|
ui.section("Editable Fields:");
|
|
829
1133
|
for (const f of rule.editableFields) {
|
|
830
|
-
console.log(` ${f.displayName} (${
|
|
831
|
-
if (f.description) console.log(` ${
|
|
1134
|
+
console.log(` ${f.displayName} (${chalk7.dim(f.attributeName)}): ${f.displayType}`);
|
|
1135
|
+
if (f.description) console.log(` ${chalk7.dim(f.description)}`);
|
|
832
1136
|
}
|
|
833
1137
|
}
|
|
834
1138
|
console.log();
|
|
@@ -906,13 +1210,13 @@ function renderMsScanList(scans, format = "pretty") {
|
|
|
906
1210
|
for (const s of scans) {
|
|
907
1211
|
ui.dim(s.uuid);
|
|
908
1212
|
console.log(
|
|
909
|
-
` ${stateColor(s.evalOutcome)(s.evalOutcome)} ${
|
|
1213
|
+
` ${stateColor(s.evalOutcome)(s.evalOutcome)} ${chalk7.dim(s.scanOrigin)} ${chalk7.dim(s.createdAt)}`
|
|
910
1214
|
);
|
|
911
|
-
if (s.modelUri) console.log(` ${
|
|
1215
|
+
if (s.modelUri) console.log(` ${chalk7.dim(s.modelUri)}`);
|
|
912
1216
|
if (s.evalSummary) {
|
|
913
1217
|
const { rulesPassed, rulesFailed, totalRules } = s.evalSummary;
|
|
914
1218
|
console.log(
|
|
915
|
-
` Rules: ${
|
|
1219
|
+
` Rules: ${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
|
|
916
1220
|
);
|
|
917
1221
|
}
|
|
918
1222
|
}
|
|
@@ -934,7 +1238,7 @@ function renderMsScanDetail(scan) {
|
|
|
934
1238
|
const { rulesPassed, rulesFailed, totalRules } = scan.evalSummary;
|
|
935
1239
|
pairs.push([
|
|
936
1240
|
"Rules",
|
|
937
|
-
`${
|
|
1241
|
+
`${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
|
|
938
1242
|
]);
|
|
939
1243
|
}
|
|
940
1244
|
ui.keyValue(pairs);
|
|
@@ -953,7 +1257,7 @@ function renderEvaluationList(evaluations) {
|
|
|
953
1257
|
for (const e of evaluations) {
|
|
954
1258
|
ui.dim(e.uuid);
|
|
955
1259
|
console.log(
|
|
956
|
-
` ${e.ruleName} ${stateColor(e.result)(e.result)} ${
|
|
1260
|
+
` ${e.ruleName} ${stateColor(e.result)(e.result)} ${chalk7.dim(e.ruleInstanceState)}`
|
|
957
1261
|
);
|
|
958
1262
|
}
|
|
959
1263
|
console.log();
|
|
@@ -979,9 +1283,9 @@ function renderViolationList(violations) {
|
|
|
979
1283
|
ui.section("Violations:");
|
|
980
1284
|
for (const v of violations) {
|
|
981
1285
|
ui.dim(v.uuid);
|
|
982
|
-
console.log(` ${
|
|
1286
|
+
console.log(` ${chalk7.red(v.ruleName)} ${chalk7.dim(v.file)}`);
|
|
983
1287
|
console.log(` ${v.description}`);
|
|
984
|
-
console.log(` Threat: ${
|
|
1288
|
+
console.log(` Threat: ${chalk7.dim(v.threat)}`);
|
|
985
1289
|
}
|
|
986
1290
|
console.log();
|
|
987
1291
|
}
|
|
@@ -989,7 +1293,7 @@ function renderViolationDetail(violation) {
|
|
|
989
1293
|
ui.section("Violation Detail:");
|
|
990
1294
|
ui.keyValue([
|
|
991
1295
|
["UUID", violation.uuid],
|
|
992
|
-
["Rule",
|
|
1296
|
+
["Rule", chalk7.red(violation.ruleName)],
|
|
993
1297
|
["Description", violation.ruleDescription],
|
|
994
1298
|
["State", violation.ruleInstanceState],
|
|
995
1299
|
["File", violation.file],
|
|
@@ -1005,8 +1309,8 @@ function renderFileList(files) {
|
|
|
1005
1309
|
}
|
|
1006
1310
|
ui.section("Scanned Files:");
|
|
1007
1311
|
for (const f of files) {
|
|
1008
|
-
const color = f.result === "SUCCESS" ?
|
|
1009
|
-
const formats = f.formats.length > 0 ?
|
|
1312
|
+
const color = f.result === "SUCCESS" ? chalk7.green : f.result === "SKIPPED" ? chalk7.yellow : chalk7.red;
|
|
1313
|
+
const formats = f.formats.length > 0 ? chalk7.dim(` [${f.formats.join(", ")}]`) : "";
|
|
1010
1314
|
console.log(` ${color(f.result)} ${f.type} ${f.path}${formats}`);
|
|
1011
1315
|
}
|
|
1012
1316
|
console.log();
|
|
@@ -1064,15 +1368,15 @@ function renderModelList(models, format = "pretty") {
|
|
|
1064
1368
|
ui.section("Models:");
|
|
1065
1369
|
for (const m of models) {
|
|
1066
1370
|
ui.dim(m.uuid);
|
|
1067
|
-
const outcome = m.latestVersionOutcome ? stateColor(m.latestVersionOutcome)(m.latestVersionOutcome) :
|
|
1068
|
-
const formats = m.latestVersionFormats && m.latestVersionFormats.length > 0 ?
|
|
1371
|
+
const outcome = m.latestVersionOutcome ? stateColor(m.latestVersionOutcome)(m.latestVersionOutcome) : chalk7.dim("unscanned");
|
|
1372
|
+
const formats = m.latestVersionFormats && m.latestVersionFormats.length > 0 ? chalk7.dim(` [${m.latestVersionFormats.join(", ")}]`) : "";
|
|
1069
1373
|
console.log(` ${m.name} ${outcome}${formats}`);
|
|
1070
1374
|
console.log();
|
|
1071
1375
|
}
|
|
1072
1376
|
}
|
|
1073
1377
|
function renderModelDetail(model, format = "pretty") {
|
|
1074
1378
|
if (format !== "pretty") {
|
|
1075
|
-
console.log(format === "json" ? JSON.stringify(model, null, 2) :
|
|
1379
|
+
console.log(format === "json" ? JSON.stringify(model, null, 2) : yamlDump3(model));
|
|
1076
1380
|
return;
|
|
1077
1381
|
}
|
|
1078
1382
|
ui.section("Model Detail:");
|
|
@@ -1129,7 +1433,7 @@ function renderModelVersionList(versions, format = "pretty") {
|
|
|
1129
1433
|
ui.section("Model Versions:");
|
|
1130
1434
|
for (const v of versions) {
|
|
1131
1435
|
ui.dim(v.uuid);
|
|
1132
|
-
const outcome = v.lastEvalOutcome ? stateColor(v.lastEvalOutcome)(v.lastEvalOutcome) :
|
|
1436
|
+
const outcome = v.lastEvalOutcome ? stateColor(v.lastEvalOutcome)(v.lastEvalOutcome) : chalk7.dim("unscanned");
|
|
1133
1437
|
const files = v.fileCount != null ? ` files: ${v.fileCount}` : "";
|
|
1134
1438
|
console.log(` ${v.revision} ${outcome}${files}`);
|
|
1135
1439
|
console.log();
|
|
@@ -1137,7 +1441,7 @@ function renderModelVersionList(versions, format = "pretty") {
|
|
|
1137
1441
|
}
|
|
1138
1442
|
function renderModelVersionDetail(version, format = "pretty") {
|
|
1139
1443
|
if (format !== "pretty") {
|
|
1140
|
-
console.log(format === "json" ? JSON.stringify(version, null, 2) :
|
|
1444
|
+
console.log(format === "json" ? JSON.stringify(version, null, 2) : yamlDump3(version));
|
|
1141
1445
|
return;
|
|
1142
1446
|
}
|
|
1143
1447
|
ui.section("Model Version Detail:");
|
|
@@ -1200,45 +1504,45 @@ function renderModelFileList(files, format = "pretty") {
|
|
|
1200
1504
|
}
|
|
1201
1505
|
|
|
1202
1506
|
// src/cli/renderer/redteam.ts
|
|
1203
|
-
import
|
|
1204
|
-
import { dump as
|
|
1507
|
+
import chalk8 from "chalk";
|
|
1508
|
+
import { dump as yamlDump4 } from "js-yaml";
|
|
1205
1509
|
function renderRedteamHeader() {
|
|
1206
1510
|
ui.header("Prisma AIRS \u2014 AI Red Team", "Adversarial scan operations");
|
|
1207
1511
|
}
|
|
1208
1512
|
function severityColor(severity) {
|
|
1209
1513
|
switch (severity.toUpperCase()) {
|
|
1210
1514
|
case "CRITICAL":
|
|
1211
|
-
return
|
|
1515
|
+
return chalk8.red;
|
|
1212
1516
|
case "HIGH":
|
|
1213
|
-
return
|
|
1517
|
+
return chalk8.magenta;
|
|
1214
1518
|
case "MEDIUM":
|
|
1215
|
-
return
|
|
1519
|
+
return chalk8.yellow;
|
|
1216
1520
|
case "LOW":
|
|
1217
|
-
return
|
|
1521
|
+
return chalk8.cyan;
|
|
1218
1522
|
default:
|
|
1219
|
-
return
|
|
1523
|
+
return chalk8.dim;
|
|
1220
1524
|
}
|
|
1221
1525
|
}
|
|
1222
|
-
function
|
|
1526
|
+
function statusColor3(status) {
|
|
1223
1527
|
switch (status) {
|
|
1224
1528
|
case "COMPLETED":
|
|
1225
|
-
return
|
|
1529
|
+
return chalk8.green;
|
|
1226
1530
|
case "RUNNING":
|
|
1227
|
-
return
|
|
1531
|
+
return chalk8.blue;
|
|
1228
1532
|
case "QUEUED":
|
|
1229
1533
|
case "INIT":
|
|
1230
|
-
return
|
|
1534
|
+
return chalk8.yellow;
|
|
1231
1535
|
case "FAILED":
|
|
1232
1536
|
case "ABORTED":
|
|
1233
|
-
return
|
|
1537
|
+
return chalk8.red;
|
|
1234
1538
|
case "PARTIALLY_COMPLETE":
|
|
1235
|
-
return
|
|
1539
|
+
return chalk8.yellow;
|
|
1236
1540
|
default:
|
|
1237
|
-
return
|
|
1541
|
+
return chalk8.white;
|
|
1238
1542
|
}
|
|
1239
1543
|
}
|
|
1240
1544
|
function activeState(active) {
|
|
1241
|
-
return
|
|
1545
|
+
return statusColor3(active ? "COMPLETED" : "FAILED")(active ? "active" : "inactive");
|
|
1242
1546
|
}
|
|
1243
1547
|
function renderScanStatus(job) {
|
|
1244
1548
|
ui.section("Scan Status:");
|
|
@@ -1248,7 +1552,7 @@ function renderScanStatus(job) {
|
|
|
1248
1552
|
["Type", job.jobType]
|
|
1249
1553
|
];
|
|
1250
1554
|
if (job.targetName) pairs.push(["Target", job.targetName]);
|
|
1251
|
-
pairs.push(["Status",
|
|
1555
|
+
pairs.push(["Status", statusColor3(job.status)(job.status)]);
|
|
1252
1556
|
if (job.total != null && job.completed != null) {
|
|
1253
1557
|
pairs.push(["Progress", `${job.completed}/${job.total}`]);
|
|
1254
1558
|
}
|
|
@@ -1291,9 +1595,9 @@ function renderScanList(jobs, format = "pretty") {
|
|
|
1291
1595
|
for (const job of jobs) {
|
|
1292
1596
|
ui.dim(job.uuid);
|
|
1293
1597
|
console.log(
|
|
1294
|
-
` ${job.name} ${
|
|
1598
|
+
` ${job.name} ${statusColor3(job.status)(job.status)} ${job.jobType}${job.score != null ? ` score: ${job.score}` : ""}`
|
|
1295
1599
|
);
|
|
1296
|
-
if (job.createdAt) console.log(` ${
|
|
1600
|
+
if (job.createdAt) console.log(` ${chalk8.dim(job.createdAt)}`);
|
|
1297
1601
|
console.log();
|
|
1298
1602
|
}
|
|
1299
1603
|
}
|
|
@@ -1308,7 +1612,7 @@ function renderStaticReport(report) {
|
|
|
1308
1612
|
for (const s of report.severityBreakdown) {
|
|
1309
1613
|
const color = severityColor(s.severity);
|
|
1310
1614
|
console.log(
|
|
1311
|
-
` ${color(s.severity.padEnd(10))} ${
|
|
1615
|
+
` ${color(s.severity.padEnd(10))} ${chalk8.red(`${s.successful} bypassed`)} ${chalk8.green(`${s.failed} blocked`)}`
|
|
1312
1616
|
);
|
|
1313
1617
|
}
|
|
1314
1618
|
}
|
|
@@ -1386,10 +1690,10 @@ function renderAttackList(attacks, options) {
|
|
|
1386
1690
|
}
|
|
1387
1691
|
ui.section("Attacks:");
|
|
1388
1692
|
for (const a of attacks) {
|
|
1389
|
-
const sev = a.severity ? severityColor(a.severity)(a.severity.padEnd(10)) :
|
|
1390
|
-
const result = a.successful ?
|
|
1693
|
+
const sev = a.severity ? severityColor(a.severity)(a.severity.padEnd(10)) : chalk8.dim("N/A".padEnd(10));
|
|
1694
|
+
const result = a.successful ? chalk8.red("BYPASSED") : chalk8.green("BLOCKED");
|
|
1391
1695
|
const label = a.subCategoryDisplayName ?? a.subCategory ?? "\u2014";
|
|
1392
|
-
console.log(` ${sev} ${result} ${label}${a.category ?
|
|
1696
|
+
console.log(` ${sev} ${result} ${label}${a.category ? chalk8.dim(` [${a.category}]`) : ""}`);
|
|
1393
1697
|
}
|
|
1394
1698
|
if (options?.footnote) ui.dim(options.footnote);
|
|
1395
1699
|
console.log();
|
|
@@ -1409,11 +1713,11 @@ function renderCustomAttackList(attacks) {
|
|
|
1409
1713
|
}
|
|
1410
1714
|
ui.section("Custom Attacks:");
|
|
1411
1715
|
for (const a of attacks) {
|
|
1412
|
-
const result = a.threat ?
|
|
1716
|
+
const result = a.threat ? chalk8.red("THREAT") : chalk8.green("SAFE");
|
|
1413
1717
|
const prompt = a.promptText.length > 80 ? `${a.promptText.substring(0, 77)}...` : a.promptText;
|
|
1414
|
-
const asrStr = a.asr != null ?
|
|
1718
|
+
const asrStr = a.asr != null ? chalk8.dim(` ASR: ${a.asr.toFixed(1)}%`) : "";
|
|
1415
1719
|
console.log(` ${result}${asrStr} ${prompt}`);
|
|
1416
|
-
if (a.goal) console.log(` ${
|
|
1720
|
+
if (a.goal) console.log(` ${chalk8.dim(a.goal)}`);
|
|
1417
1721
|
}
|
|
1418
1722
|
console.log();
|
|
1419
1723
|
}
|
|
@@ -1460,11 +1764,11 @@ function renderCategories(categories) {
|
|
|
1460
1764
|
ui.section("Attack Categories:");
|
|
1461
1765
|
for (const c of categories) {
|
|
1462
1766
|
console.log(
|
|
1463
|
-
` ${
|
|
1767
|
+
` ${chalk8.bold(c.displayName)} ${chalk8.cyan(`(${c.id})`)}${c.description ? chalk8.dim(` \u2014 ${c.description}`) : ""}`
|
|
1464
1768
|
);
|
|
1465
1769
|
for (const sc of c.subCategories) {
|
|
1466
1770
|
console.log(
|
|
1467
|
-
` ${
|
|
1771
|
+
` ${chalk8.dim("\u2022")} ${sc.displayName} ${chalk8.cyan(`(${sc.id})`)}${sc.description ? chalk8.dim(` \u2014 ${sc.description}`) : ""}`
|
|
1468
1772
|
);
|
|
1469
1773
|
}
|
|
1470
1774
|
console.log();
|
|
@@ -1521,7 +1825,7 @@ function renderTargetDetail(target, format = "pretty") {
|
|
|
1521
1825
|
if (format === "json") {
|
|
1522
1826
|
console.log(JSON.stringify(target, null, 2));
|
|
1523
1827
|
} else if (format === "yaml") {
|
|
1524
|
-
console.log(
|
|
1828
|
+
console.log(yamlDump4(target));
|
|
1525
1829
|
}
|
|
1526
1830
|
return;
|
|
1527
1831
|
}
|
|
@@ -1553,7 +1857,7 @@ function renderPromptSetDetail(ps, format = "pretty", info) {
|
|
|
1553
1857
|
if (format === "json") {
|
|
1554
1858
|
console.log(JSON.stringify(payload, null, 2));
|
|
1555
1859
|
} else if (format === "yaml") {
|
|
1556
|
-
console.log(
|
|
1860
|
+
console.log(yamlDump4(payload));
|
|
1557
1861
|
}
|
|
1558
1862
|
return;
|
|
1559
1863
|
}
|
|
@@ -1590,7 +1894,7 @@ function renderPromptList(prompts, format = "pretty") {
|
|
|
1590
1894
|
if (format === "json") {
|
|
1591
1895
|
console.log(JSON.stringify(prompts, null, 2));
|
|
1592
1896
|
} else if (format === "yaml") {
|
|
1593
|
-
console.log(
|
|
1897
|
+
console.log(yamlDump4(prompts));
|
|
1594
1898
|
}
|
|
1595
1899
|
return;
|
|
1596
1900
|
}
|
|
@@ -1600,11 +1904,11 @@ function renderPromptList(prompts, format = "pretty") {
|
|
|
1600
1904
|
}
|
|
1601
1905
|
ui.section("Prompts:");
|
|
1602
1906
|
for (const p of prompts) {
|
|
1603
|
-
const status = p.active ?
|
|
1907
|
+
const status = p.active ? chalk8.green("active") : chalk8.dim("inactive");
|
|
1604
1908
|
const text = p.prompt.length > 80 ? `${p.prompt.substring(0, 77)}...` : p.prompt;
|
|
1605
|
-
console.log(` ${
|
|
1909
|
+
console.log(` ${chalk8.dim(p.uuid)} ${status}`);
|
|
1606
1910
|
console.log(` ${text}`);
|
|
1607
|
-
if (p.goal) console.log(` ${
|
|
1911
|
+
if (p.goal) console.log(` ${chalk8.dim(`Goal: ${p.goal}`)}`);
|
|
1608
1912
|
}
|
|
1609
1913
|
console.log();
|
|
1610
1914
|
}
|
|
@@ -1613,7 +1917,7 @@ function renderPromptDetail(p, format = "pretty") {
|
|
|
1613
1917
|
if (format === "json") {
|
|
1614
1918
|
console.log(JSON.stringify(p, null, 2));
|
|
1615
1919
|
} else if (format === "yaml") {
|
|
1616
|
-
console.log(
|
|
1920
|
+
console.log(yamlDump4(p));
|
|
1617
1921
|
}
|
|
1618
1922
|
return;
|
|
1619
1923
|
}
|
|
@@ -1621,7 +1925,7 @@ function renderPromptDetail(p, format = "pretty") {
|
|
|
1621
1925
|
const pairs = [
|
|
1622
1926
|
["UUID", p.uuid],
|
|
1623
1927
|
["Set UUID", p.promptSetId],
|
|
1624
|
-
["Status", p.active ?
|
|
1928
|
+
["Status", p.active ? chalk8.green("active") : chalk8.dim("inactive")],
|
|
1625
1929
|
["Prompt", p.prompt]
|
|
1626
1930
|
];
|
|
1627
1931
|
if (p.goal) pairs.push(["Goal", p.goal]);
|
|
@@ -1633,7 +1937,7 @@ function renderPropertyNames(names, format = "pretty") {
|
|
|
1633
1937
|
if (format === "json") {
|
|
1634
1938
|
console.log(JSON.stringify(names, null, 2));
|
|
1635
1939
|
} else if (format === "yaml") {
|
|
1636
|
-
console.log(
|
|
1940
|
+
console.log(yamlDump4(names));
|
|
1637
1941
|
} else {
|
|
1638
1942
|
const rows = names.map((n) => ({ name: n }));
|
|
1639
1943
|
console.log(formatOutput(rows, [{ key: "name", label: "Name" }], format));
|
|
@@ -1653,7 +1957,7 @@ function renderPropertyNames(names, format = "pretty") {
|
|
|
1653
1957
|
function renderAuthValidation(result) {
|
|
1654
1958
|
ui.section("Auth Validation:");
|
|
1655
1959
|
const pairs = [
|
|
1656
|
-
["Validated", result.validated ?
|
|
1960
|
+
["Validated", result.validated ? chalk8.green("yes") : chalk8.red("no")]
|
|
1657
1961
|
];
|
|
1658
1962
|
if (result.tokenPreview) pairs.push(["Token", result.tokenPreview]);
|
|
1659
1963
|
if (result.expiresIn != null) pairs.push(["Expires In", `${result.expiresIn}s`]);
|
|
@@ -1671,7 +1975,7 @@ function renderTargetTemplates(templates) {
|
|
|
1671
1975
|
function renderEulaStatus(status) {
|
|
1672
1976
|
ui.section("EULA Status:");
|
|
1673
1977
|
const pairs = [
|
|
1674
|
-
["Accepted", status.isAccepted ?
|
|
1978
|
+
["Accepted", status.isAccepted ? chalk8.green("yes") : chalk8.red("no")]
|
|
1675
1979
|
];
|
|
1676
1980
|
if (status.acceptedAt) pairs.push(["Accepted At", status.acceptedAt]);
|
|
1677
1981
|
if (status.acceptedByUserId) pairs.push(["Accepted By", status.acceptedByUserId]);
|
|
@@ -1688,7 +1992,7 @@ function renderPropertyValues(payload, format = "pretty") {
|
|
|
1688
1992
|
if (format === "json") {
|
|
1689
1993
|
console.log(JSON.stringify(payload, null, 2));
|
|
1690
1994
|
} else if (format === "yaml") {
|
|
1691
|
-
console.log(
|
|
1995
|
+
console.log(yamlDump4(payload));
|
|
1692
1996
|
}
|
|
1693
1997
|
return;
|
|
1694
1998
|
}
|
|
@@ -1709,7 +2013,7 @@ function renderInstanceResponse(resp) {
|
|
|
1709
2013
|
if (resp.tenantId) pairs.push(["Tenant ID", resp.tenantId]);
|
|
1710
2014
|
if (resp.appId) pairs.push(["App ID", resp.appId]);
|
|
1711
2015
|
if (resp.isSuccess != null) {
|
|
1712
|
-
pairs.push(["Success", resp.isSuccess ?
|
|
2016
|
+
pairs.push(["Success", resp.isSuccess ? chalk8.green("yes") : chalk8.red("no")]);
|
|
1713
2017
|
}
|
|
1714
2018
|
ui.keyValue(pairs);
|
|
1715
2019
|
console.log();
|
|
@@ -1719,7 +2023,7 @@ function renderInstanceDetail(inst, format = "pretty") {
|
|
|
1719
2023
|
if (format === "json") {
|
|
1720
2024
|
console.log(JSON.stringify(inst, null, 2));
|
|
1721
2025
|
} else if (format === "yaml") {
|
|
1722
|
-
console.log(
|
|
2026
|
+
console.log(yamlDump4(inst));
|
|
1723
2027
|
}
|
|
1724
2028
|
return;
|
|
1725
2029
|
}
|
|
@@ -1737,7 +2041,7 @@ function renderRegistryCredentials(creds, format = "pretty") {
|
|
|
1737
2041
|
if (format === "json") {
|
|
1738
2042
|
console.log(JSON.stringify(creds, null, 2));
|
|
1739
2043
|
} else if (format === "yaml") {
|
|
1740
|
-
console.log(
|
|
2044
|
+
console.log(yamlDump4(creds));
|
|
1741
2045
|
}
|
|
1742
2046
|
return;
|
|
1743
2047
|
}
|
|
@@ -1751,13 +2055,13 @@ function renderRegistryCredentials(creds, format = "pretty") {
|
|
|
1751
2055
|
function channelStatusColor(status) {
|
|
1752
2056
|
switch (status.toUpperCase()) {
|
|
1753
2057
|
case "ONLINE":
|
|
1754
|
-
return
|
|
2058
|
+
return chalk8.green;
|
|
1755
2059
|
case "DRAFT":
|
|
1756
|
-
return
|
|
2060
|
+
return chalk8.yellow;
|
|
1757
2061
|
case "OFFLINE":
|
|
1758
|
-
return
|
|
2062
|
+
return chalk8.red;
|
|
1759
2063
|
default:
|
|
1760
|
-
return
|
|
2064
|
+
return chalk8.white;
|
|
1761
2065
|
}
|
|
1762
2066
|
}
|
|
1763
2067
|
function renderChannelList(channels, format = "pretty") {
|
|
@@ -1791,7 +2095,7 @@ function renderChannelList(channels, format = "pretty") {
|
|
|
1791
2095
|
ui.section("Network Broker Channels:");
|
|
1792
2096
|
for (const c of channels) {
|
|
1793
2097
|
if (c.uuid) ui.dim(c.uuid);
|
|
1794
|
-
const status = c.status ? channelStatusColor(c.status)(c.status) :
|
|
2098
|
+
const status = c.status ? channelStatusColor(c.status)(c.status) : chalk8.dim("unknown");
|
|
1795
2099
|
const clients = c.connectedClientsCount != null ? ` clients: ${c.connectedClientsCount}` : "";
|
|
1796
2100
|
console.log(` ${c.name ?? "(unnamed)"} ${status}${clients}`);
|
|
1797
2101
|
console.log();
|
|
@@ -1799,7 +2103,7 @@ function renderChannelList(channels, format = "pretty") {
|
|
|
1799
2103
|
}
|
|
1800
2104
|
function renderChannelDetail(channel, format = "pretty") {
|
|
1801
2105
|
if (format !== "pretty") {
|
|
1802
|
-
console.log(format === "json" ? JSON.stringify(channel, null, 2) :
|
|
2106
|
+
console.log(format === "json" ? JSON.stringify(channel, null, 2) : yamlDump4(channel));
|
|
1803
2107
|
return;
|
|
1804
2108
|
}
|
|
1805
2109
|
ui.section("Channel Detail:");
|
|
@@ -1825,7 +2129,7 @@ function renderChannelDetail(channel, format = "pretty") {
|
|
|
1825
2129
|
}
|
|
1826
2130
|
function renderChannelStats(stats, format = "pretty") {
|
|
1827
2131
|
if (format !== "pretty") {
|
|
1828
|
-
console.log(format === "json" ? JSON.stringify(stats, null, 2) :
|
|
2132
|
+
console.log(format === "json" ? JSON.stringify(stats, null, 2) : yamlDump4(stats));
|
|
1829
2133
|
return;
|
|
1830
2134
|
}
|
|
1831
2135
|
ui.section("Network Broker Stats:");
|
|
@@ -1843,7 +2147,7 @@ function renderChannelStats(stats, format = "pretty") {
|
|
|
1843
2147
|
function renderLanguages(data, format = "pretty") {
|
|
1844
2148
|
if (format !== "pretty") {
|
|
1845
2149
|
if (format === "json" || format === "yaml") {
|
|
1846
|
-
console.log(format === "json" ? JSON.stringify(data, null, 2) :
|
|
2150
|
+
console.log(format === "json" ? JSON.stringify(data, null, 2) : yamlDump4(data));
|
|
1847
2151
|
return;
|
|
1848
2152
|
}
|
|
1849
2153
|
console.log(
|
|
@@ -1869,7 +2173,7 @@ function renderLanguages(data, format = "pretty") {
|
|
|
1869
2173
|
}
|
|
1870
2174
|
ui.section("Languages:");
|
|
1871
2175
|
for (const l of data.languages) {
|
|
1872
|
-
console.log(` ${
|
|
2176
|
+
console.log(` ${chalk8.dim(l.code)} ${l.name}`);
|
|
1873
2177
|
}
|
|
1874
2178
|
console.log();
|
|
1875
2179
|
}
|
|
@@ -1903,45 +2207,140 @@ function renderErrorLogs(logs, format = "pretty") {
|
|
|
1903
2207
|
}
|
|
1904
2208
|
ui.section("Target-Profile Error Logs:");
|
|
1905
2209
|
for (const l of logs) {
|
|
1906
|
-
const type = l.errorType ?
|
|
2210
|
+
const type = l.errorType ? chalk8.red(l.errorType) : chalk8.dim("error");
|
|
1907
2211
|
console.log(
|
|
1908
|
-
` ${
|
|
2212
|
+
` ${chalk8.dim(l.createdAt)} ${type}${l.errorSource ? ` (${l.errorSource})` : ""}`
|
|
1909
2213
|
);
|
|
1910
2214
|
if (l.errorMessage) console.log(` ${l.errorMessage}`);
|
|
1911
|
-
if (l.jobId) console.log(` ${
|
|
2215
|
+
if (l.jobId) console.log(` ${chalk8.dim(`job: ${l.jobId}`)}`);
|
|
2216
|
+
console.log();
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
function renderAdapterList(adapters, format = "pretty", totalItems) {
|
|
2220
|
+
if (adapters.length === 0) {
|
|
2221
|
+
ui.emptyList("adapters");
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
if (format !== "pretty") {
|
|
2225
|
+
const rows = adapters.map((a) => ({
|
|
2226
|
+
uuid: a.uuid,
|
|
2227
|
+
name: a.name,
|
|
2228
|
+
status: a.status,
|
|
2229
|
+
targets: a.targetCount ?? "",
|
|
2230
|
+
updated: a.updatedAt ?? ""
|
|
2231
|
+
}));
|
|
2232
|
+
console.log(
|
|
2233
|
+
formatOutput(
|
|
2234
|
+
rows,
|
|
2235
|
+
[
|
|
2236
|
+
{ key: "uuid", label: "UUID" },
|
|
2237
|
+
{ key: "name", label: "Name" },
|
|
2238
|
+
{ key: "status", label: "Status" },
|
|
2239
|
+
{ key: "targets", label: "Targets" },
|
|
2240
|
+
{ key: "updated", label: "Updated" }
|
|
2241
|
+
],
|
|
2242
|
+
format
|
|
2243
|
+
)
|
|
2244
|
+
);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
ui.section("Custom Target Adapters:");
|
|
2248
|
+
for (const a of adapters) {
|
|
2249
|
+
ui.dim(a.uuid);
|
|
2250
|
+
const status = a.status === "ACTIVE" ? chalk8.green(a.status) : chalk8.yellow(a.status);
|
|
2251
|
+
const targets = a.targetCount != null ? ` targets: ${a.targetCount}` : "";
|
|
2252
|
+
console.log(` ${a.name} ${status}${targets}`);
|
|
1912
2253
|
console.log();
|
|
1913
2254
|
}
|
|
2255
|
+
if (totalItems !== void 0) ui.dim(`${totalItems} total`);
|
|
2256
|
+
}
|
|
2257
|
+
function renderAdapterDetail(adapter, format = "pretty") {
|
|
2258
|
+
if (format !== "pretty") {
|
|
2259
|
+
console.log(format === "json" ? JSON.stringify(adapter, null, 2) : yamlDump4(adapter));
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
ui.section("Adapter Detail:");
|
|
2263
|
+
const pairs = [
|
|
2264
|
+
["UUID", adapter.uuid],
|
|
2265
|
+
["Name", adapter.name],
|
|
2266
|
+
[
|
|
2267
|
+
"Status",
|
|
2268
|
+
adapter.status === "ACTIVE" ? chalk8.green(adapter.status) : chalk8.yellow(adapter.status)
|
|
2269
|
+
],
|
|
2270
|
+
["Script", `${adapter.scriptB64.length} base64 chars`]
|
|
2271
|
+
];
|
|
2272
|
+
if (adapter.description != null) pairs.push(["Description", adapter.description]);
|
|
2273
|
+
if (adapter.networkBrokerChannelUuid != null)
|
|
2274
|
+
pairs.push(["Broker Channel", adapter.networkBrokerChannelUuid]);
|
|
2275
|
+
if (adapter.targetCount != null) pairs.push(["Targets", adapter.targetCount]);
|
|
2276
|
+
if (adapter.createdAt != null) pairs.push(["Created", adapter.createdAt]);
|
|
2277
|
+
if (adapter.updatedAt != null) pairs.push(["Updated", adapter.updatedAt]);
|
|
2278
|
+
ui.keyValue(pairs);
|
|
2279
|
+
if (adapter.variables.length > 0) {
|
|
2280
|
+
ui.section("Variables:");
|
|
2281
|
+
ui.keyValue(
|
|
2282
|
+
adapter.variables.map((v) => [
|
|
2283
|
+
`${v.key} (${v.type})`,
|
|
2284
|
+
v.isRedacted ? chalk8.dim("(redacted)") : v.value ?? ""
|
|
2285
|
+
])
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
console.log();
|
|
2289
|
+
}
|
|
2290
|
+
function renderAdapterValidation(result, format = "pretty") {
|
|
2291
|
+
if (format !== "pretty") {
|
|
2292
|
+
console.log(format === "json" ? JSON.stringify(result, null, 2) : yamlDump4(result));
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
if (result.validated) {
|
|
2296
|
+
ui.success("Adapter script validated");
|
|
2297
|
+
} else {
|
|
2298
|
+
ui.error("Adapter script validation FAILED");
|
|
2299
|
+
}
|
|
2300
|
+
if (result.stdout) {
|
|
2301
|
+
ui.section("stdout:");
|
|
2302
|
+
console.log(result.stdout);
|
|
2303
|
+
}
|
|
2304
|
+
if (result.stderr) {
|
|
2305
|
+
ui.section("stderr:");
|
|
2306
|
+
console.log(chalk8.red(result.stderr));
|
|
2307
|
+
}
|
|
2308
|
+
if (result.traceback) {
|
|
2309
|
+
ui.section("traceback:");
|
|
2310
|
+
console.log(chalk8.red(result.traceback));
|
|
2311
|
+
}
|
|
2312
|
+
console.log();
|
|
1914
2313
|
}
|
|
1915
2314
|
|
|
1916
2315
|
// src/cli/renderer/runtime.ts
|
|
1917
|
-
import
|
|
2316
|
+
import chalk9 from "chalk";
|
|
1918
2317
|
function renderScanProgress(job) {
|
|
1919
2318
|
if (job.total != null && job.completed != null && job.total > 0) {
|
|
1920
2319
|
const pct = Math.round(job.completed / job.total * 100);
|
|
1921
2320
|
const bar = "\u2588".repeat(Math.round(pct / 5)) + "\u2591".repeat(20 - Math.round(pct / 5));
|
|
1922
2321
|
process.stdout.write(
|
|
1923
|
-
`\r ${
|
|
2322
|
+
`\r ${statusColor4(job.status)(job.status)} ${bar} ${pct}% (${job.completed}/${job.total})`
|
|
1924
2323
|
);
|
|
1925
2324
|
} else {
|
|
1926
|
-
process.stdout.write(`\r ${
|
|
2325
|
+
process.stdout.write(`\r ${statusColor4(job.status)(job.status)}...`);
|
|
1927
2326
|
}
|
|
1928
2327
|
}
|
|
1929
|
-
function
|
|
2328
|
+
function statusColor4(status) {
|
|
1930
2329
|
switch (status) {
|
|
1931
2330
|
case "COMPLETED":
|
|
1932
|
-
return
|
|
2331
|
+
return chalk9.green;
|
|
1933
2332
|
case "RUNNING":
|
|
1934
|
-
return
|
|
2333
|
+
return chalk9.blue;
|
|
1935
2334
|
case "QUEUED":
|
|
1936
2335
|
case "INIT":
|
|
1937
|
-
return
|
|
2336
|
+
return chalk9.yellow;
|
|
1938
2337
|
case "FAILED":
|
|
1939
2338
|
case "ABORTED":
|
|
1940
|
-
return
|
|
2339
|
+
return chalk9.red;
|
|
1941
2340
|
case "PARTIALLY_COMPLETE":
|
|
1942
|
-
return
|
|
2341
|
+
return chalk9.yellow;
|
|
1943
2342
|
default:
|
|
1944
|
-
return
|
|
2343
|
+
return chalk9.white;
|
|
1945
2344
|
}
|
|
1946
2345
|
}
|
|
1947
2346
|
function renderRuntimeConfigHeader() {
|
|
@@ -1971,8 +2370,8 @@ function renderProfileList(profiles, format = "pretty") {
|
|
|
1971
2370
|
ui.section("Security Profiles:");
|
|
1972
2371
|
for (const p of profiles) {
|
|
1973
2372
|
ui.dim(p.profileId);
|
|
1974
|
-
const status = p.active ?
|
|
1975
|
-
const rev = p.revision != null ?
|
|
2373
|
+
const status = p.active ? chalk9.green("active") : chalk9.yellow("inactive");
|
|
2374
|
+
const rev = p.revision != null ? chalk9.dim(` rev:${p.revision}`) : "";
|
|
1976
2375
|
console.log(` ${p.profileName} ${status}${rev}`);
|
|
1977
2376
|
}
|
|
1978
2377
|
console.log();
|
|
@@ -1982,7 +2381,7 @@ function renderProfileDetail(profile) {
|
|
|
1982
2381
|
const pairs = [
|
|
1983
2382
|
["ID", profile.profileId],
|
|
1984
2383
|
["Name", profile.profileName],
|
|
1985
|
-
["Status", profile.active ?
|
|
2384
|
+
["Status", profile.active ? chalk9.green("active") : chalk9.yellow("inactive")]
|
|
1986
2385
|
];
|
|
1987
2386
|
if (profile.revision != null) pairs.push(["Revision", profile.revision]);
|
|
1988
2387
|
if (profile.createdBy) pairs.push(["Created", profile.createdBy]);
|
|
@@ -2086,8 +2485,8 @@ function renderTopicList(topics, format = "pretty") {
|
|
|
2086
2485
|
ui.section("Custom Topics:");
|
|
2087
2486
|
for (const t of topics) {
|
|
2088
2487
|
ui.dim(String(t.topic_id));
|
|
2089
|
-
const rev = t.revision != null ?
|
|
2090
|
-
const desc = t.description ?
|
|
2488
|
+
const rev = t.revision != null ? chalk9.dim(` rev:${t.revision}`) : "";
|
|
2489
|
+
const desc = t.description ? chalk9.dim(` \u2014 ${t.description.slice(0, 80)}`) : "";
|
|
2091
2490
|
console.log(` ${t.topic_name}${rev}${desc}`);
|
|
2092
2491
|
}
|
|
2093
2492
|
console.log();
|
|
@@ -2145,8 +2544,8 @@ function renderApiKeyList(keys, format = "pretty") {
|
|
|
2145
2544
|
ui.section("API Keys:");
|
|
2146
2545
|
for (const k of keys) {
|
|
2147
2546
|
ui.dim(k.id);
|
|
2148
|
-
const last8 = k.last8 ?
|
|
2149
|
-
const expires = k.expiresAt ?
|
|
2547
|
+
const last8 = k.last8 ? chalk9.dim(` key: \u2026${k.last8}`) : "";
|
|
2548
|
+
const expires = k.expiresAt ? chalk9.dim(` expires: ${k.expiresAt}`) : "";
|
|
2150
2549
|
console.log(` ${k.name}${last8}${expires}`);
|
|
2151
2550
|
}
|
|
2152
2551
|
console.log();
|
|
@@ -2191,7 +2590,7 @@ function renderCustomerAppList(apps, format = "pretty") {
|
|
|
2191
2590
|
ui.section("Customer Apps:");
|
|
2192
2591
|
for (const a of apps) {
|
|
2193
2592
|
if (a.id) ui.dim(a.id);
|
|
2194
|
-
const desc = a.description ?
|
|
2593
|
+
const desc = a.description ? chalk9.dim(` \u2014 ${a.description.slice(0, 80)}`) : "";
|
|
2195
2594
|
console.log(` ${a.name}${desc}`);
|
|
2196
2595
|
}
|
|
2197
2596
|
console.log();
|
|
@@ -2315,9 +2714,9 @@ function renderDeploymentProfileList(profiles, format = "pretty") {
|
|
|
2315
2714
|
const name = p.raw.dp_name ?? p.raw.profile_name ?? p.raw.name ?? "unknown";
|
|
2316
2715
|
const status = p.raw.status;
|
|
2317
2716
|
const authCode = p.raw.auth_code;
|
|
2318
|
-
const
|
|
2717
|
+
const statusColor5 = status === "active" ? chalk9.green : chalk9.dim;
|
|
2319
2718
|
console.log(
|
|
2320
|
-
` ${name}${status ? ` ${
|
|
2719
|
+
` ${name}${status ? ` ${statusColor5(status)}` : ""}${authCode ? ` ${chalk9.dim(authCode)}` : ""}`
|
|
2321
2720
|
);
|
|
2322
2721
|
}
|
|
2323
2722
|
console.log();
|
|
@@ -2357,10 +2756,10 @@ function renderScanLogList(results, pageToken, format = "pretty") {
|
|
|
2357
2756
|
const profile = r.profile_name;
|
|
2358
2757
|
const ts2 = r.received_ts ?? r.timestamp;
|
|
2359
2758
|
const scanId = r.scan_id;
|
|
2360
|
-
const actionColor = action === "block" ?
|
|
2759
|
+
const actionColor = action === "block" ? chalk9.red : chalk9.green;
|
|
2361
2760
|
if (scanId) ui.dim(scanId);
|
|
2362
2761
|
console.log(
|
|
2363
|
-
` ${ts2 ?
|
|
2762
|
+
` ${ts2 ? chalk9.dim(ts2) : ""} ${action ? actionColor(action) : ""} ${profile ? `[${profile}]` : ""} ${app ?? ""}`
|
|
2364
2763
|
);
|
|
2365
2764
|
}
|
|
2366
2765
|
if (pageToken) {
|
|
@@ -2370,20 +2769,256 @@ function renderScanLogList(results, pageToken, format = "pretty") {
|
|
|
2370
2769
|
console.log();
|
|
2371
2770
|
}
|
|
2372
2771
|
|
|
2373
|
-
// src/cli/
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
const
|
|
2377
|
-
|
|
2378
|
-
|
|
2772
|
+
// src/cli/confirm.ts
|
|
2773
|
+
async function confirmOrAbort(message, force, options = {}) {
|
|
2774
|
+
if (force) return;
|
|
2775
|
+
const interactive = options.isTTY ?? process.stdout.isTTY === true;
|
|
2776
|
+
if (!interactive) {
|
|
2777
|
+
usageError(
|
|
2778
|
+
`refusing to ${options.action ?? "proceed"} without --force in non-interactive mode`
|
|
2779
|
+
);
|
|
2379
2780
|
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2781
|
+
const prompt = options.promptFn ?? (await import("@inquirer/prompts")).confirm;
|
|
2782
|
+
const confirmed = await prompt({ message, default: false });
|
|
2783
|
+
if (!confirmed) {
|
|
2784
|
+
ui.info("Aborted");
|
|
2785
|
+
process.exit(0);
|
|
2382
2786
|
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
// src/cli/examples.ts
|
|
2790
|
+
function examples(...lines) {
|
|
2791
|
+
return `
|
|
2792
|
+
Examples:
|
|
2793
|
+
${lines.map((l) => ` $ ${l}`).join("\n")}
|
|
2794
|
+
`;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
// src/cli/commands/aigateway.ts
|
|
2798
|
+
async function createService() {
|
|
2799
|
+
const config = await loadConfig();
|
|
2800
|
+
return new SdkAiGatewayService(aiGatewayClientOptions(config));
|
|
2801
|
+
}
|
|
2802
|
+
function failWithGrantHint(err) {
|
|
2803
|
+
const hint = aiGatewayGrantHint(err);
|
|
2804
|
+
if (hint) ui.warn(`403: ${hint}`);
|
|
2805
|
+
fail(err);
|
|
2806
|
+
}
|
|
2807
|
+
function parsePlane(value) {
|
|
2808
|
+
if (value === void 0) return void 0;
|
|
2809
|
+
if (value !== "data" && value !== "admin") {
|
|
2810
|
+
usageError(`Invalid --plane '${value}'. Valid planes: data, admin`);
|
|
2811
|
+
}
|
|
2812
|
+
return value;
|
|
2813
|
+
}
|
|
2814
|
+
function parseStatus(value) {
|
|
2815
|
+
if (value === void 0) return void 0;
|
|
2816
|
+
if (value !== "active" && value !== "archived") {
|
|
2817
|
+
usageError(`Invalid --status '${value}'. Valid statuses: active, archived`);
|
|
2818
|
+
}
|
|
2819
|
+
return value;
|
|
2820
|
+
}
|
|
2821
|
+
function parseJsonFlag(raw, flag) {
|
|
2822
|
+
if (raw === void 0) return void 0;
|
|
2823
|
+
try {
|
|
2824
|
+
return JSON.parse(raw);
|
|
2825
|
+
} catch {
|
|
2826
|
+
throw new Error(`${flag} must be valid JSON`);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
function buildWorkspaceWriteRequest(opts) {
|
|
2830
|
+
const out = {};
|
|
2831
|
+
for (const key of ["name", "description", "icon"]) {
|
|
2832
|
+
if (opts[key] !== void 0) out[key] = opts[key];
|
|
2833
|
+
}
|
|
2834
|
+
const defaults = parseJsonFlag(opts.defaults, "--defaults");
|
|
2835
|
+
const metadata = parseJsonFlag(opts.metadata, "--metadata");
|
|
2836
|
+
if (defaults !== void 0 || metadata !== void 0) {
|
|
2837
|
+
out.defaults = {
|
|
2838
|
+
...typeof defaults === "object" && defaults !== null ? defaults : {},
|
|
2839
|
+
...metadata !== void 0 ? { metadata } : {}
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
if (opts.users !== void 0) {
|
|
2843
|
+
out.users = opts.users.split(",").map((u) => u.trim()).filter(Boolean);
|
|
2844
|
+
}
|
|
2845
|
+
const usage = parseJsonFlag(opts.usageLimits, "--usage-limits");
|
|
2846
|
+
if (usage !== void 0) out.usageLimits = usage;
|
|
2847
|
+
const rate = parseJsonFlag(opts.rateLimits, "--rate-limits");
|
|
2848
|
+
if (rate !== void 0) out.rateLimits = rate;
|
|
2849
|
+
return out;
|
|
2850
|
+
}
|
|
2851
|
+
function scopeNameLooksUnrelated(name, scopeName) {
|
|
2852
|
+
const nameToken = name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2853
|
+
if (nameToken.length < 4) return false;
|
|
2854
|
+
return !scopeName.toLowerCase().replace(/[^a-z0-9]/g, "").includes(nameToken);
|
|
2855
|
+
}
|
|
2856
|
+
function registerAiGatewayCommand(program) {
|
|
2857
|
+
const aigateway = program.command("aigateway").description("AI Gateway operations");
|
|
2858
|
+
const workspace = aigateway.command("workspace").description("Manage AI Gateway workspaces");
|
|
2859
|
+
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(
|
|
2860
|
+
"after",
|
|
2861
|
+
examples(
|
|
2862
|
+
"airs aigateway workspace list",
|
|
2863
|
+
"airs aigateway workspace list --plane admin",
|
|
2864
|
+
"airs aigateway workspace list --plane admin --status archived",
|
|
2865
|
+
"airs aigateway workspace list --all --output json"
|
|
2866
|
+
)
|
|
2867
|
+
).action(async (opts) => {
|
|
2868
|
+
try {
|
|
2869
|
+
const fmt = opts.output;
|
|
2870
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
2871
|
+
const plane = parsePlane(opts.plane);
|
|
2872
|
+
const status = parseStatus(opts.status);
|
|
2873
|
+
if (opts.all && (plane !== void 0 || status !== void 0)) {
|
|
2874
|
+
usageError("--all already merges admin-plane active + archived; drop --plane/--status");
|
|
2875
|
+
}
|
|
2876
|
+
const service = await createService();
|
|
2877
|
+
const workspaces = opts.all ? await service.listAllWorkspaces() : await service.listWorkspaces(
|
|
2878
|
+
plane !== void 0 || status !== void 0 ? { plane, status } : void 0
|
|
2879
|
+
);
|
|
2880
|
+
renderWorkspaceList(workspaces, fmt);
|
|
2881
|
+
if (fmt === "pretty" && !opts.all && plane !== "admin") {
|
|
2882
|
+
ui.status(
|
|
2883
|
+
"Data-plane list shows only active workspaces you are scoped to \u2014 use --plane admin or --all for the whole tenant."
|
|
2884
|
+
);
|
|
2885
|
+
}
|
|
2886
|
+
} catch (err) {
|
|
2887
|
+
failWithGrantHint(err);
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
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(
|
|
2891
|
+
"after",
|
|
2892
|
+
examples(
|
|
2893
|
+
"airs aigateway workspace get ws-main-a-349e0e",
|
|
2894
|
+
"airs aigateway workspace get 16f7e90d-382a-4e78-b577-1b01eb5f8297 --plane admin --output json"
|
|
2895
|
+
)
|
|
2896
|
+
).action(async (ref, opts) => {
|
|
2897
|
+
try {
|
|
2898
|
+
const fmt = opts.output;
|
|
2899
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
2900
|
+
const plane = parsePlane(opts.plane);
|
|
2901
|
+
const service = await createService();
|
|
2902
|
+
const workspace2 = await service.getWorkspace(
|
|
2903
|
+
ref,
|
|
2904
|
+
plane !== void 0 ? { plane } : void 0
|
|
2905
|
+
);
|
|
2906
|
+
renderWorkspaceDetail(workspace2, fmt);
|
|
2907
|
+
} catch (err) {
|
|
2908
|
+
failWithGrantHint(err);
|
|
2909
|
+
}
|
|
2910
|
+
});
|
|
2911
|
+
workspace.command("create").description("Create a workspace (admin plane)").requiredOption("--name <name>", "Display name").requiredOption(
|
|
2912
|
+
"--scope-name <scope>",
|
|
2913
|
+
"SCM role scope granting data-plane access (e.g. ws_production_bx7qw0) \u2014 not derived from --name"
|
|
2914
|
+
).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(
|
|
2915
|
+
"after",
|
|
2916
|
+
examples(
|
|
2917
|
+
"airs aigateway workspace create --name Production --scope-name ws_production_bx7qw0",
|
|
2918
|
+
`airs aigateway workspace create --name Production --scope-name ws_production_bx7qw0 --metadata '{"env":"production"}' --rate-limits '[{"type":"requests","unit":"rpm","value":100}]'`
|
|
2919
|
+
)
|
|
2920
|
+
).action(async (opts) => {
|
|
2921
|
+
try {
|
|
2922
|
+
const fmt = opts.output;
|
|
2923
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
2924
|
+
if (scopeNameLooksUnrelated(opts.name, opts.scopeName)) {
|
|
2925
|
+
ui.warn(
|
|
2926
|
+
`--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.`
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
const service = await createService();
|
|
2930
|
+
const workspace2 = await service.createWorkspace({
|
|
2931
|
+
...buildWorkspaceWriteRequest(opts),
|
|
2932
|
+
name: opts.name,
|
|
2933
|
+
scopeName: opts.scopeName
|
|
2934
|
+
});
|
|
2935
|
+
ui.success(`Workspace created: ${workspace2.id}`);
|
|
2936
|
+
renderWorkspaceDetail(workspace2, fmt);
|
|
2937
|
+
} catch (err) {
|
|
2938
|
+
failWithGrantHint(err);
|
|
2939
|
+
}
|
|
2940
|
+
});
|
|
2941
|
+
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(
|
|
2942
|
+
"after",
|
|
2943
|
+
examples(
|
|
2944
|
+
`airs aigateway workspace update ws-produc-985697 --description 'Production workloads, us-east'`
|
|
2945
|
+
)
|
|
2946
|
+
).action(async (ref, opts) => {
|
|
2947
|
+
try {
|
|
2948
|
+
const fmt = opts.output;
|
|
2949
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
2950
|
+
const request = buildWorkspaceWriteRequest(opts);
|
|
2951
|
+
if (Object.keys(request).length === 0) {
|
|
2952
|
+
usageError(
|
|
2953
|
+
"Specify at least one of --name --description --icon --metadata --defaults --usage-limits --rate-limits"
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
const service = await createService();
|
|
2957
|
+
const workspace2 = await service.updateWorkspace(ref, request);
|
|
2958
|
+
ui.success(`Workspace updated: ${workspace2.id}`);
|
|
2959
|
+
renderWorkspaceDetail(workspace2, fmt);
|
|
2960
|
+
} catch (err) {
|
|
2961
|
+
failWithGrantHint(err);
|
|
2962
|
+
}
|
|
2963
|
+
});
|
|
2964
|
+
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) => {
|
|
2965
|
+
try {
|
|
2966
|
+
renderAiGatewayHeader();
|
|
2967
|
+
await confirmOrAbort(
|
|
2968
|
+
`Archive workspace ${ref}? (soft delete \u2014 the row remains under --status archived)`,
|
|
2969
|
+
Boolean(opts.force),
|
|
2970
|
+
{ action: `archive workspace ${ref}` }
|
|
2971
|
+
);
|
|
2972
|
+
const service = await createService();
|
|
2973
|
+
await service.deleteWorkspace(ref);
|
|
2974
|
+
ui.success(`Workspace archived: ${ref}`);
|
|
2975
|
+
ui.status(
|
|
2976
|
+
"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."
|
|
2977
|
+
);
|
|
2978
|
+
} catch (err) {
|
|
2979
|
+
failWithGrantHint(err);
|
|
2980
|
+
}
|
|
2981
|
+
});
|
|
2982
|
+
const telemetry = aigateway.command("telemetry").description("AI Gateway runtime telemetry (data plane)");
|
|
2983
|
+
telemetry.command("cost").description(
|
|
2984
|
+
"Total and per-day spend for a workspace (API reports cents; pretty output shows dollars)"
|
|
2985
|
+
).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, json, yaml", "pretty").addHelpText(
|
|
2986
|
+
"after",
|
|
2987
|
+
examples(
|
|
2988
|
+
"airs aigateway telemetry cost --workspace ws-main-a-349e0e",
|
|
2989
|
+
"airs aigateway telemetry cost --workspace ws-main-a-349e0e --days 30 --output json"
|
|
2990
|
+
)
|
|
2991
|
+
).action(async (opts) => {
|
|
2992
|
+
try {
|
|
2993
|
+
const fmt = opts.output;
|
|
2994
|
+
if (fmt === "pretty") renderAiGatewayHeader();
|
|
2995
|
+
const days = Number.parseInt(opts.days, 10);
|
|
2996
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
2997
|
+
usageError(`Invalid --days '${opts.days}'. Expected a positive integer`);
|
|
2998
|
+
}
|
|
2999
|
+
const service = await createService();
|
|
3000
|
+
const report = await service.getTelemetryCost({ workspaceSlug: opts.workspace, days });
|
|
3001
|
+
renderCostReport(report, fmt);
|
|
3002
|
+
} catch (err) {
|
|
3003
|
+
failWithGrantHint(err);
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
// src/cli/commands/completion.ts
|
|
3009
|
+
var COMPLETION_SHELLS = ["bash", "zsh", "fish"];
|
|
3010
|
+
function collectCompletionNodes(root, path3 = []) {
|
|
3011
|
+
const words = [];
|
|
3012
|
+
for (const sub of root.commands) {
|
|
3013
|
+
words.push(sub.name(), ...sub.aliases());
|
|
3014
|
+
}
|
|
3015
|
+
for (const opt of root.options) {
|
|
3016
|
+
if (!opt.hidden && opt.long) words.push(opt.long);
|
|
3017
|
+
}
|
|
3018
|
+
words.push("--help");
|
|
3019
|
+
const node = { path: path3.join(" "), words: [...new Set(words)] };
|
|
3020
|
+
const children = root.commands.flatMap(
|
|
3021
|
+
(sub) => collectCompletionNodes(sub, [...path3, sub.name()])
|
|
2387
3022
|
);
|
|
2388
3023
|
return [node, ...children];
|
|
2389
3024
|
}
|
|
@@ -2653,39 +3288,6 @@ function registerConfigCommand(program) {
|
|
|
2653
3288
|
import { randomUUID } from "crypto";
|
|
2654
3289
|
import { readFile as readFile2 } from "fs/promises";
|
|
2655
3290
|
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
3291
|
var DOCTOR_TIMEOUT_MS = 5e3;
|
|
2690
3292
|
var MIN_NODE_MAJOR = 20;
|
|
2691
3293
|
function checkNodeVersion(version = process.version) {
|
|
@@ -2883,6 +3485,50 @@ async function checkManagementAuth(probe, hasCreds, timeoutMs = DOCTOR_TIMEOUT_M
|
|
|
2883
3485
|
};
|
|
2884
3486
|
}
|
|
2885
3487
|
}
|
|
3488
|
+
async function checkAiGatewayApi(probe, hasCreds, timeoutMs = DOCTOR_TIMEOUT_MS) {
|
|
3489
|
+
const name = "AI Gateway API";
|
|
3490
|
+
if (!hasCreds) {
|
|
3491
|
+
return {
|
|
3492
|
+
name,
|
|
3493
|
+
status: "warn",
|
|
3494
|
+
detail: "skipped \u2014 management credentials not configured",
|
|
3495
|
+
hint: "Set PANW_MGMT_CLIENT_ID, PANW_MGMT_CLIENT_SECRET, PANW_MGMT_TSG_ID"
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
try {
|
|
3499
|
+
const result = await withTimeout(probe(), timeoutMs);
|
|
3500
|
+
if (result === TIMED_OUT) {
|
|
3501
|
+
return {
|
|
3502
|
+
name,
|
|
3503
|
+
status: "fail",
|
|
3504
|
+
detail: `timed out after ${timeoutMs}ms \u2014 network unreachable or endpoint not responding`,
|
|
3505
|
+
hint: "Check network connectivity and PANW_AI_GW_DATA_ENDPOINT"
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
return {
|
|
3509
|
+
name,
|
|
3510
|
+
status: "pass",
|
|
3511
|
+
detail: `endpoint reachable (${result} workspace${result === 1 ? "" : "s"} in scope)`
|
|
3512
|
+
};
|
|
3513
|
+
} catch (err) {
|
|
3514
|
+
const status = httpStatus(err);
|
|
3515
|
+
const message = errMessage(err);
|
|
3516
|
+
if (status === 403) {
|
|
3517
|
+
return {
|
|
3518
|
+
name,
|
|
3519
|
+
status: "warn",
|
|
3520
|
+
detail: `endpoint reachable, but access denied (HTTP 403): ${message}`,
|
|
3521
|
+
hint: aiGatewayGrantHint(err)
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
return {
|
|
3525
|
+
name,
|
|
3526
|
+
status: "fail",
|
|
3527
|
+
detail: status !== void 0 ? `AI Gateway API error (HTTP ${status}): ${message}` : `network unreachable: ${message}`,
|
|
3528
|
+
hint: "Verify credentials and PANW_AI_GW_DATA_ENDPOINT"
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
2886
3532
|
async function defaultScannerProbe() {
|
|
2887
3533
|
const config = await loadConfig();
|
|
2888
3534
|
init(runtimeInitOptions(config));
|
|
@@ -2900,6 +3546,12 @@ async function defaultMgmtProbe() {
|
|
|
2900
3546
|
const topics = await service.listTopics();
|
|
2901
3547
|
return topics.length;
|
|
2902
3548
|
}
|
|
3549
|
+
async function defaultAiGwProbe() {
|
|
3550
|
+
const config = await loadConfig();
|
|
3551
|
+
const service = new SdkAiGatewayService(aiGatewayClientOptions(config));
|
|
3552
|
+
const workspaces = await service.listWorkspaces();
|
|
3553
|
+
return workspaces.length;
|
|
3554
|
+
}
|
|
2903
3555
|
async function runDoctor(deps = {}) {
|
|
2904
3556
|
const configFilePath = deps.configFilePath ?? resolveConfigFilePath();
|
|
2905
3557
|
const inspect = deps.inspect ?? (() => inspectConfig(configFilePath));
|
|
@@ -2923,7 +3575,12 @@ async function runDoctor(deps = {}) {
|
|
|
2923
3575
|
mgmtCreds.status === "pass",
|
|
2924
3576
|
timeoutMs
|
|
2925
3577
|
);
|
|
2926
|
-
|
|
3578
|
+
const aiGwApi = await checkAiGatewayApi(
|
|
3579
|
+
deps.aiGwProbe ?? defaultAiGwProbe,
|
|
3580
|
+
mgmtCreds.status === "pass",
|
|
3581
|
+
timeoutMs
|
|
3582
|
+
);
|
|
3583
|
+
return [node, configFile, scannerCreds, mgmtCreds, scannerApi, mgmtAuth, aiGwApi];
|
|
2927
3584
|
}
|
|
2928
3585
|
function hasFailure(checks) {
|
|
2929
3586
|
return checks.some((c) => c.status === "fail");
|
|
@@ -3005,7 +3662,7 @@ function run(bin, args, label) {
|
|
|
3005
3662
|
});
|
|
3006
3663
|
});
|
|
3007
3664
|
}
|
|
3008
|
-
async function
|
|
3665
|
+
async function createService2() {
|
|
3009
3666
|
const config = await loadConfig();
|
|
3010
3667
|
return new SdkModelSecurityService(modelSecurityClientOptions(config));
|
|
3011
3668
|
}
|
|
@@ -3016,7 +3673,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3016
3673
|
try {
|
|
3017
3674
|
const fmt = opts.output;
|
|
3018
3675
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3019
|
-
const service = await
|
|
3676
|
+
const service = await createService2();
|
|
3020
3677
|
const result = await service.listGroups({
|
|
3021
3678
|
sourceTypes: opts.sourceTypes ? opts.sourceTypes.split(",").map((s) => s.trim()) : void 0,
|
|
3022
3679
|
searchQuery: opts.search,
|
|
@@ -3034,7 +3691,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3034
3691
|
try {
|
|
3035
3692
|
const fmt = opts.output;
|
|
3036
3693
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3037
|
-
const service = await
|
|
3694
|
+
const service = await createService2();
|
|
3038
3695
|
const group = await service.getGroup(uuid);
|
|
3039
3696
|
renderGroupDetail(group, fmt);
|
|
3040
3697
|
} catch (err) {
|
|
@@ -3044,7 +3701,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3044
3701
|
groups.command("create").description("Create a security group").requiredOption("--config <path>", "JSON file with group configuration").action(async (opts) => {
|
|
3045
3702
|
try {
|
|
3046
3703
|
renderModelSecurityHeader();
|
|
3047
|
-
const service = await
|
|
3704
|
+
const service = await createService2();
|
|
3048
3705
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
3049
3706
|
const group = await service.createGroup({
|
|
3050
3707
|
name: config.name,
|
|
@@ -3061,7 +3718,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3061
3718
|
groups.command("update <uuid>").description("Update a security group").option("--name <name>", "New name").option("--description <desc>", "New description").action(async (uuid, opts) => {
|
|
3062
3719
|
try {
|
|
3063
3720
|
renderModelSecurityHeader();
|
|
3064
|
-
const service = await
|
|
3721
|
+
const service = await createService2();
|
|
3065
3722
|
const request = {};
|
|
3066
3723
|
if (opts.name) request.name = opts.name;
|
|
3067
3724
|
if (opts.description) request.description = opts.description;
|
|
@@ -3075,7 +3732,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3075
3732
|
groups.command("delete <uuid>").description("Delete a security group").action(async (uuid) => {
|
|
3076
3733
|
try {
|
|
3077
3734
|
renderModelSecurityHeader();
|
|
3078
|
-
const service = await
|
|
3735
|
+
const service = await createService2();
|
|
3079
3736
|
const { confirmed, state } = await service.deleteGroupAndVerify(uuid);
|
|
3080
3737
|
if (confirmed) {
|
|
3081
3738
|
ui.success(`Group ${uuid} deleted.`);
|
|
@@ -3105,7 +3762,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3105
3762
|
if (!useUv && !hasBin("python3")) {
|
|
3106
3763
|
fail(new Error("Neither uv nor python3 found on PATH. Install one first."));
|
|
3107
3764
|
}
|
|
3108
|
-
const service = await
|
|
3765
|
+
const service = await createService2();
|
|
3109
3766
|
const auth = await service.getPyPIAuth();
|
|
3110
3767
|
const pkg = `model-security-client[${extras}]`;
|
|
3111
3768
|
const steps = useUv ? [
|
|
@@ -3149,7 +3806,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3149
3806
|
labels.command("add <scanUuid>").description("Add labels to a scan").requiredOption("--labels <json>", "JSON array of {key, value} labels").action(async (scanUuid, opts) => {
|
|
3150
3807
|
try {
|
|
3151
3808
|
renderModelSecurityHeader();
|
|
3152
|
-
const service = await
|
|
3809
|
+
const service = await createService2();
|
|
3153
3810
|
const parsed = JSON.parse(opts.labels);
|
|
3154
3811
|
await service.addLabels(scanUuid, parsed);
|
|
3155
3812
|
ui.success("Labels added.");
|
|
@@ -3160,7 +3817,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3160
3817
|
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
3818
|
try {
|
|
3162
3819
|
renderModelSecurityHeader();
|
|
3163
|
-
const service = await
|
|
3820
|
+
const service = await createService2();
|
|
3164
3821
|
const parsed = JSON.parse(opts.labels);
|
|
3165
3822
|
await service.setLabels(scanUuid, parsed);
|
|
3166
3823
|
ui.success("Labels set.");
|
|
@@ -3171,7 +3828,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3171
3828
|
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
3829
|
try {
|
|
3173
3830
|
renderModelSecurityHeader();
|
|
3174
|
-
const service = await
|
|
3831
|
+
const service = await createService2();
|
|
3175
3832
|
const keys = opts.keys.split(",").map((k) => k.trim());
|
|
3176
3833
|
await service.deleteLabels(scanUuid, keys);
|
|
3177
3834
|
ui.success("Labels deleted.");
|
|
@@ -3182,7 +3839,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3182
3839
|
labels.command("keys").description("List available label keys").option("--limit <n>", "Max results", "20").action(async (opts) => {
|
|
3183
3840
|
try {
|
|
3184
3841
|
renderModelSecurityHeader();
|
|
3185
|
-
const service = await
|
|
3842
|
+
const service = await createService2();
|
|
3186
3843
|
const result = await service.getLabelKeys({
|
|
3187
3844
|
limit: Number.parseInt(opts.limit, 10)
|
|
3188
3845
|
});
|
|
@@ -3194,7 +3851,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3194
3851
|
labels.command("values <key>").description("List values for a label key").option("--limit <n>", "Max results", "20").action(async (key, opts) => {
|
|
3195
3852
|
try {
|
|
3196
3853
|
renderModelSecurityHeader();
|
|
3197
|
-
const service = await
|
|
3854
|
+
const service = await createService2();
|
|
3198
3855
|
const result = await service.getLabelValues(key, {
|
|
3199
3856
|
limit: Number.parseInt(opts.limit, 10)
|
|
3200
3857
|
});
|
|
@@ -3206,7 +3863,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3206
3863
|
ms.command("pypi-auth").description("Get PyPI authentication URL for Google Artifact Registry").action(async () => {
|
|
3207
3864
|
try {
|
|
3208
3865
|
renderModelSecurityHeader();
|
|
3209
|
-
const service = await
|
|
3866
|
+
const service = await createService2();
|
|
3210
3867
|
const auth = await service.getPyPIAuth();
|
|
3211
3868
|
ui.section("PyPI Authentication");
|
|
3212
3869
|
ui.keyValue([
|
|
@@ -3221,7 +3878,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3221
3878
|
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
3879
|
try {
|
|
3223
3880
|
renderModelSecurityHeader();
|
|
3224
|
-
const service = await
|
|
3881
|
+
const service = await createService2();
|
|
3225
3882
|
const result = await service.listRuleInstances(groupUuid, {
|
|
3226
3883
|
securityRuleUuid: opts.securityRuleUuid,
|
|
3227
3884
|
state: opts.state,
|
|
@@ -3235,7 +3892,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3235
3892
|
ruleInstances.command("get <groupUuid> <instanceUuid>").description("Get rule instance details").action(async (groupUuid, instanceUuid) => {
|
|
3236
3893
|
try {
|
|
3237
3894
|
renderModelSecurityHeader();
|
|
3238
|
-
const service = await
|
|
3895
|
+
const service = await createService2();
|
|
3239
3896
|
const instance = await service.getRuleInstance(groupUuid, instanceUuid);
|
|
3240
3897
|
renderRuleInstanceDetail(instance);
|
|
3241
3898
|
} catch (err) {
|
|
@@ -3245,7 +3902,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3245
3902
|
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
3903
|
try {
|
|
3247
3904
|
renderModelSecurityHeader();
|
|
3248
|
-
const service = await
|
|
3905
|
+
const service = await createService2();
|
|
3249
3906
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
3250
3907
|
const instance = await service.updateRuleInstance(groupUuid, instanceUuid, {
|
|
3251
3908
|
state: config.state,
|
|
@@ -3262,7 +3919,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3262
3919
|
try {
|
|
3263
3920
|
const fmt = opts.output;
|
|
3264
3921
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3265
|
-
const service = await
|
|
3922
|
+
const service = await createService2();
|
|
3266
3923
|
const result = await service.listRules({
|
|
3267
3924
|
sourceType: opts.sourceType,
|
|
3268
3925
|
searchQuery: opts.search,
|
|
@@ -3276,7 +3933,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3276
3933
|
rules.command("get <uuid>").description("Get security rule details").action(async (uuid) => {
|
|
3277
3934
|
try {
|
|
3278
3935
|
renderModelSecurityHeader();
|
|
3279
|
-
const service = await
|
|
3936
|
+
const service = await createService2();
|
|
3280
3937
|
const rule = await service.getRule(uuid);
|
|
3281
3938
|
renderRuleDetail(rule);
|
|
3282
3939
|
} catch (err) {
|
|
@@ -3295,7 +3952,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3295
3952
|
try {
|
|
3296
3953
|
const fmt = opts.output;
|
|
3297
3954
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3298
|
-
const service = await
|
|
3955
|
+
const service = await createService2();
|
|
3299
3956
|
const result = await service.listScans({
|
|
3300
3957
|
evalOutcome: opts.evalOutcome,
|
|
3301
3958
|
sourceType: opts.sourceType,
|
|
@@ -3311,7 +3968,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3311
3968
|
scans.command("get <uuid>").description("Get scan details").action(async (uuid) => {
|
|
3312
3969
|
try {
|
|
3313
3970
|
renderModelSecurityHeader();
|
|
3314
|
-
const service = await
|
|
3971
|
+
const service = await createService2();
|
|
3315
3972
|
const scan = await service.getScan(uuid);
|
|
3316
3973
|
renderMsScanDetail(scan);
|
|
3317
3974
|
} catch (err) {
|
|
@@ -3321,7 +3978,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3321
3978
|
scans.command("create").description("Create a model security scan").requiredOption("--config <path>", "JSON file with scan configuration").action(async (opts) => {
|
|
3322
3979
|
try {
|
|
3323
3980
|
renderModelSecurityHeader();
|
|
3324
|
-
const service = await
|
|
3981
|
+
const service = await createService2();
|
|
3325
3982
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
3326
3983
|
const scan = await service.createScan(config);
|
|
3327
3984
|
ui.success(`Scan created: ${scan.uuid}`);
|
|
@@ -3333,7 +3990,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3333
3990
|
scans.command("evaluations <scanUuid>").description("List rule evaluations for a scan").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
|
|
3334
3991
|
try {
|
|
3335
3992
|
renderModelSecurityHeader();
|
|
3336
|
-
const service = await
|
|
3993
|
+
const service = await createService2();
|
|
3337
3994
|
const result = await service.getEvaluations(scanUuid, {
|
|
3338
3995
|
limit: Number.parseInt(opts.limit, 10)
|
|
3339
3996
|
});
|
|
@@ -3345,7 +4002,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3345
4002
|
scans.command("evaluation <uuid>").description("Get evaluation details").action(async (uuid) => {
|
|
3346
4003
|
try {
|
|
3347
4004
|
renderModelSecurityHeader();
|
|
3348
|
-
const service = await
|
|
4005
|
+
const service = await createService2();
|
|
3349
4006
|
const evaluation = await service.getEvaluation(uuid);
|
|
3350
4007
|
renderEvaluationDetail(evaluation);
|
|
3351
4008
|
} catch (err) {
|
|
@@ -3355,7 +4012,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3355
4012
|
scans.command("violations <scanUuid>").description("List violations for a scan").option("--limit <n>", "Max results", "20").action(async (scanUuid, opts) => {
|
|
3356
4013
|
try {
|
|
3357
4014
|
renderModelSecurityHeader();
|
|
3358
|
-
const service = await
|
|
4015
|
+
const service = await createService2();
|
|
3359
4016
|
const result = await service.getViolations(scanUuid, {
|
|
3360
4017
|
limit: Number.parseInt(opts.limit, 10)
|
|
3361
4018
|
});
|
|
@@ -3367,7 +4024,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3367
4024
|
scans.command("violation <uuid>").description("Get violation details").action(async (uuid) => {
|
|
3368
4025
|
try {
|
|
3369
4026
|
renderModelSecurityHeader();
|
|
3370
|
-
const service = await
|
|
4027
|
+
const service = await createService2();
|
|
3371
4028
|
const violation = await service.getViolation(uuid);
|
|
3372
4029
|
renderViolationDetail(violation);
|
|
3373
4030
|
} catch (err) {
|
|
@@ -3377,7 +4034,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3377
4034
|
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
4035
|
try {
|
|
3379
4036
|
renderModelSecurityHeader();
|
|
3380
|
-
const service = await
|
|
4037
|
+
const service = await createService2();
|
|
3381
4038
|
const result = await service.getFiles(scanUuid, {
|
|
3382
4039
|
type: opts.type,
|
|
3383
4040
|
result: opts.result,
|
|
@@ -3393,7 +4050,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3393
4050
|
try {
|
|
3394
4051
|
const fmt = opts.output;
|
|
3395
4052
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3396
|
-
const service = await
|
|
4053
|
+
const service = await createService2();
|
|
3397
4054
|
const result = await service.listModels({
|
|
3398
4055
|
search: opts.search,
|
|
3399
4056
|
searchQuery: opts.searchQuery,
|
|
@@ -3411,7 +4068,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3411
4068
|
try {
|
|
3412
4069
|
const fmt = opts.output;
|
|
3413
4070
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3414
|
-
const service = await
|
|
4071
|
+
const service = await createService2();
|
|
3415
4072
|
const model = await service.getModel(uuid);
|
|
3416
4073
|
renderModelDetail(model, fmt);
|
|
3417
4074
|
} catch (err) {
|
|
@@ -3422,7 +4079,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3422
4079
|
try {
|
|
3423
4080
|
const fmt = opts.output;
|
|
3424
4081
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3425
|
-
const service = await
|
|
4082
|
+
const service = await createService2();
|
|
3426
4083
|
const result = await service.listModelVersions(modelUuid, {
|
|
3427
4084
|
sortOrder: opts.sortOrder,
|
|
3428
4085
|
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
@@ -3437,7 +4094,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3437
4094
|
try {
|
|
3438
4095
|
const fmt = opts.output;
|
|
3439
4096
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3440
|
-
const service = await
|
|
4097
|
+
const service = await createService2();
|
|
3441
4098
|
const version = await service.getModelVersion(uuid);
|
|
3442
4099
|
renderModelVersionDetail(version, fmt);
|
|
3443
4100
|
} catch (err) {
|
|
@@ -3448,7 +4105,7 @@ function registerModelSecurityCommand(program) {
|
|
|
3448
4105
|
try {
|
|
3449
4106
|
const fmt = opts.output;
|
|
3450
4107
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
3451
|
-
const service = await
|
|
4108
|
+
const service = await createService2();
|
|
3452
4109
|
const result = await service.listModelVersionFiles(modelVersionUuid, {
|
|
3453
4110
|
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
3454
4111
|
skip: opts.offset ? Number.parseInt(opts.offset, 10) : void 0
|
|
@@ -3464,25 +4121,8 @@ function registerModelSecurityCommand(program) {
|
|
|
3464
4121
|
import * as fs2 from "fs";
|
|
3465
4122
|
import * as path from "path";
|
|
3466
4123
|
|
|
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
4124
|
// src/cli/deprecated-flags.ts
|
|
3485
|
-
import
|
|
4125
|
+
import chalk10 from "chalk";
|
|
3486
4126
|
import { Option } from "commander";
|
|
3487
4127
|
var ALIASES = /* @__PURE__ */ new WeakMap();
|
|
3488
4128
|
function registerDeprecatedAlias(cmd, alias) {
|
|
@@ -3497,7 +4137,7 @@ function resolveDeprecatedAliases(cmd, opts) {
|
|
|
3497
4137
|
const oldValue = opts[alias.oldKey];
|
|
3498
4138
|
if (oldValue === void 0) continue;
|
|
3499
4139
|
console.error(
|
|
3500
|
-
|
|
4140
|
+
chalk10.yellow(
|
|
3501
4141
|
` \u26A0 ${alias.oldFlag.split(" ")[0]} is deprecated and will be removed in v4 \u2014 use ${alias.canonicalFlag}`
|
|
3502
4142
|
)
|
|
3503
4143
|
);
|
|
@@ -3681,7 +4321,7 @@ async function restoreTargets(opts) {
|
|
|
3681
4321
|
}
|
|
3682
4322
|
|
|
3683
4323
|
// src/cli/commands/redteam.ts
|
|
3684
|
-
async function
|
|
4324
|
+
async function createService3() {
|
|
3685
4325
|
const config = await loadConfig();
|
|
3686
4326
|
return new SdkRedTeamService(redTeamClientOptions(config));
|
|
3687
4327
|
}
|
|
@@ -3689,6 +4329,14 @@ async function createPromptSetService() {
|
|
|
3689
4329
|
const config = await loadConfig();
|
|
3690
4330
|
return new SdkPromptSetService(redTeamClientOptions(config));
|
|
3691
4331
|
}
|
|
4332
|
+
function buildDefaultCategories(categories) {
|
|
4333
|
+
return Object.fromEntries(
|
|
4334
|
+
categories.map((category) => [
|
|
4335
|
+
category.id,
|
|
4336
|
+
category.subCategories.map((subCategory) => subCategory.id).filter((id) => id !== "MULTI_TURN")
|
|
4337
|
+
])
|
|
4338
|
+
);
|
|
4339
|
+
}
|
|
3692
4340
|
function parseAttackGoals(input) {
|
|
3693
4341
|
const trimmed = input.trim();
|
|
3694
4342
|
const raw = trimmed.startsWith("[") ? trimmed : fs2.readFileSync(trimmed, "utf-8");
|
|
@@ -3721,8 +4369,11 @@ var VALID_TARGET_PROVIDERS = [
|
|
|
3721
4369
|
"DATABRICKS",
|
|
3722
4370
|
"BEDROCK",
|
|
3723
4371
|
"REST",
|
|
3724
|
-
"STREAMING"
|
|
4372
|
+
"STREAMING",
|
|
4373
|
+
"WEBSOCKET",
|
|
4374
|
+
"CUSTOM_TARGET_ADAPTER"
|
|
3725
4375
|
];
|
|
4376
|
+
var REST_PROVIDERS = /* @__PURE__ */ new Set(["REST", "STREAMING", "WEBSOCKET", "HUGGING_FACE"]);
|
|
3726
4377
|
function buildTargetScaffold(provider, templates) {
|
|
3727
4378
|
const key = provider.toUpperCase();
|
|
3728
4379
|
if (!VALID_TARGET_PROVIDERS.includes(key)) {
|
|
@@ -3730,21 +4381,92 @@ function buildTargetScaffold(provider, templates) {
|
|
|
3730
4381
|
`Unknown provider "${provider}". Valid providers: ${VALID_TARGET_PROVIDERS.join(", ")}`
|
|
3731
4382
|
);
|
|
3732
4383
|
}
|
|
4384
|
+
if (key === "CUSTOM_TARGET_ADAPTER") {
|
|
4385
|
+
return {
|
|
4386
|
+
name: "",
|
|
4387
|
+
target_type: "AGENT",
|
|
4388
|
+
connection_type: "CUSTOM_TARGET_ADAPTER",
|
|
4389
|
+
api_endpoint_type: "NETWORK_BROKER",
|
|
4390
|
+
network_broker_channel_uuid: "<channel-uuid>",
|
|
4391
|
+
adapter_uuid: "<adapter-uuid>",
|
|
4392
|
+
// adapter_variable_overrides is an ARRAY of {key, value, type} objects.
|
|
4393
|
+
adapter_variable_overrides: [],
|
|
4394
|
+
target_background: { use_case: "" },
|
|
4395
|
+
additional_context: {}
|
|
4396
|
+
};
|
|
4397
|
+
}
|
|
4398
|
+
if (REST_PROVIDERS.has(key)) {
|
|
4399
|
+
const tpl = templates[key] ?? {};
|
|
4400
|
+
return {
|
|
4401
|
+
name: "",
|
|
4402
|
+
target_type: "APPLICATION",
|
|
4403
|
+
connection_type: "CUSTOM",
|
|
4404
|
+
api_endpoint_type: "PUBLIC",
|
|
4405
|
+
response_mode: key === "STREAMING" ? "STREAMING" : key === "WEBSOCKET" ? "WEBSOCKET" : "REST",
|
|
4406
|
+
auth_type: "HEADERS",
|
|
4407
|
+
auth_config: {
|
|
4408
|
+
auth_header: { Authorization: "Bearer <token>" }
|
|
4409
|
+
},
|
|
4410
|
+
connection_params: {
|
|
4411
|
+
api_endpoint: tpl.url ?? "",
|
|
4412
|
+
request_headers: { "Content-Type": "application/json" },
|
|
4413
|
+
request_json: tpl.request_json ?? { messages: [{ role: "user", content: "{INPUT}" }] },
|
|
4414
|
+
response_json: tpl.response_json ?? { choices: [{ message: { content: "{RESPONSE}" } }] },
|
|
4415
|
+
response_key: "choices.0.message.content"
|
|
4416
|
+
},
|
|
4417
|
+
target_background: {},
|
|
4418
|
+
additional_context: {}
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
3733
4421
|
return {
|
|
3734
4422
|
name: "",
|
|
3735
4423
|
target_type: "APPLICATION",
|
|
3736
|
-
|
|
4424
|
+
connection_type: key,
|
|
4425
|
+
api_endpoint_type: "PUBLIC",
|
|
4426
|
+
response_mode: "REST",
|
|
4427
|
+
auth_type: "HEADERS",
|
|
4428
|
+
auth_config: {
|
|
4429
|
+
auth_header: { Authorization: "Bearer <token>" }
|
|
4430
|
+
},
|
|
4431
|
+
connection_params: {
|
|
4432
|
+
target_connection_config: templates[key] ?? {}
|
|
4433
|
+
},
|
|
3737
4434
|
target_background: {},
|
|
3738
|
-
additional_context: {}
|
|
3739
|
-
target_metadata: {}
|
|
4435
|
+
additional_context: {}
|
|
3740
4436
|
};
|
|
3741
4437
|
}
|
|
4438
|
+
function resolveScriptB64(opts) {
|
|
4439
|
+
if (opts.scriptFile !== void 0 && opts.scriptB64 !== void 0) {
|
|
4440
|
+
throw new Error("--script-file and --script-b64 are mutually exclusive");
|
|
4441
|
+
}
|
|
4442
|
+
if (opts.scriptB64 !== void 0) return opts.scriptB64;
|
|
4443
|
+
if (opts.scriptFile !== void 0) {
|
|
4444
|
+
return Buffer.from(fs2.readFileSync(opts.scriptFile, "utf-8")).toString("base64");
|
|
4445
|
+
}
|
|
4446
|
+
throw new Error("one of --script-file or --script-b64 is required");
|
|
4447
|
+
}
|
|
4448
|
+
function parseAdapterVariables(input) {
|
|
4449
|
+
let parsed;
|
|
4450
|
+
try {
|
|
4451
|
+
parsed = JSON.parse(input);
|
|
4452
|
+
} catch (err) {
|
|
4453
|
+
throw new Error(`--variables: invalid JSON (${err instanceof Error ? err.message : err})`);
|
|
4454
|
+
}
|
|
4455
|
+
if (!Array.isArray(parsed) || !parsed.every(
|
|
4456
|
+
(v) => v !== null && typeof v === "object" && typeof v.key === "string" && (v.type === "VAR" || v.type === "SECRET")
|
|
4457
|
+
)) {
|
|
4458
|
+
throw new Error(
|
|
4459
|
+
'--variables: expected a JSON array of { "key": string, "value"?: string|null, "type": "VAR"|"SECRET" }'
|
|
4460
|
+
);
|
|
4461
|
+
}
|
|
4462
|
+
return parsed;
|
|
4463
|
+
}
|
|
3742
4464
|
function registerRedteamCommand(program) {
|
|
3743
4465
|
const redteam = program.command("redteam").description("AI Red Team scan operations");
|
|
3744
4466
|
redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
|
|
3745
4467
|
try {
|
|
3746
4468
|
renderRedteamHeader();
|
|
3747
|
-
const service = await
|
|
4469
|
+
const service = await createService3();
|
|
3748
4470
|
await service.abortScan(jobId);
|
|
3749
4471
|
ui.success(`Scan ${jobId} aborted.`);
|
|
3750
4472
|
} catch (err) {
|
|
@@ -3754,7 +4476,7 @@ function registerRedteamCommand(program) {
|
|
|
3754
4476
|
redteam.command("categories").description("List available attack categories").action(async () => {
|
|
3755
4477
|
try {
|
|
3756
4478
|
renderRedteamHeader();
|
|
3757
|
-
const service = await
|
|
4479
|
+
const service = await createService3();
|
|
3758
4480
|
const categories = await service.getCategories();
|
|
3759
4481
|
renderCategories(categories);
|
|
3760
4482
|
} catch (err) {
|
|
@@ -3765,7 +4487,7 @@ function registerRedteamCommand(program) {
|
|
|
3765
4487
|
eula.command("status").description("Check EULA acceptance status").action(async () => {
|
|
3766
4488
|
try {
|
|
3767
4489
|
renderRedteamHeader();
|
|
3768
|
-
const service = await
|
|
4490
|
+
const service = await createService3();
|
|
3769
4491
|
const status = await service.getEulaStatus();
|
|
3770
4492
|
renderEulaStatus(status);
|
|
3771
4493
|
} catch (err) {
|
|
@@ -3775,7 +4497,7 @@ function registerRedteamCommand(program) {
|
|
|
3775
4497
|
eula.command("content").description("Display EULA content").action(async () => {
|
|
3776
4498
|
try {
|
|
3777
4499
|
renderRedteamHeader();
|
|
3778
|
-
const service = await
|
|
4500
|
+
const service = await createService3();
|
|
3779
4501
|
const content = await service.getEulaContent();
|
|
3780
4502
|
renderEulaContent(content);
|
|
3781
4503
|
} catch (err) {
|
|
@@ -3793,7 +4515,7 @@ function registerRedteamCommand(program) {
|
|
|
3793
4515
|
resolveDeprecatedAliases(eulaAccept, opts);
|
|
3794
4516
|
try {
|
|
3795
4517
|
renderRedteamHeader();
|
|
3796
|
-
const service = await
|
|
4518
|
+
const service = await createService3();
|
|
3797
4519
|
const content = await service.getEulaContent();
|
|
3798
4520
|
if (!opts.force) {
|
|
3799
4521
|
renderEulaContent(content);
|
|
@@ -3811,7 +4533,7 @@ function registerRedteamCommand(program) {
|
|
|
3811
4533
|
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
4534
|
try {
|
|
3813
4535
|
renderRedteamHeader();
|
|
3814
|
-
const service = await
|
|
4536
|
+
const service = await createService3();
|
|
3815
4537
|
const result = await service.createInstance({
|
|
3816
4538
|
tsgId: opts.tsgId,
|
|
3817
4539
|
tenantId: opts.tenantId,
|
|
@@ -3827,7 +4549,7 @@ function registerRedteamCommand(program) {
|
|
|
3827
4549
|
try {
|
|
3828
4550
|
const fmt = opts.output;
|
|
3829
4551
|
if (fmt === "pretty") renderRedteamHeader();
|
|
3830
|
-
const service = await
|
|
4552
|
+
const service = await createService3();
|
|
3831
4553
|
const result = await service.getInstance(tenantId);
|
|
3832
4554
|
renderInstanceDetail(result, fmt);
|
|
3833
4555
|
} catch (err) {
|
|
@@ -3837,7 +4559,7 @@ function registerRedteamCommand(program) {
|
|
|
3837
4559
|
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
4560
|
try {
|
|
3839
4561
|
renderRedteamHeader();
|
|
3840
|
-
const service = await
|
|
4562
|
+
const service = await createService3();
|
|
3841
4563
|
const result = await service.updateInstance(tenantId, {
|
|
3842
4564
|
tsgId: opts.tsgId,
|
|
3843
4565
|
tenantId,
|
|
@@ -3852,7 +4574,7 @@ function registerRedteamCommand(program) {
|
|
|
3852
4574
|
instances.command("delete <tenantId>").description("Delete an instance").action(async (tenantId) => {
|
|
3853
4575
|
try {
|
|
3854
4576
|
renderRedteamHeader();
|
|
3855
|
-
const service = await
|
|
4577
|
+
const service = await createService3();
|
|
3856
4578
|
const result = await service.deleteInstance(tenantId);
|
|
3857
4579
|
renderInstanceResponse(result);
|
|
3858
4580
|
ui.success(`Instance ${tenantId} deleted.`);
|
|
@@ -3864,7 +4586,7 @@ function registerRedteamCommand(program) {
|
|
|
3864
4586
|
devices.command("create <tenantId>").description("Create devices for an instance").requiredOption("--config <path>", "JSON file with device request").action(async (tenantId, opts) => {
|
|
3865
4587
|
try {
|
|
3866
4588
|
renderRedteamHeader();
|
|
3867
|
-
const service = await
|
|
4589
|
+
const service = await createService3();
|
|
3868
4590
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
3869
4591
|
const result = await service.createDevices(tenantId, config);
|
|
3870
4592
|
ui.success("Devices created:");
|
|
@@ -3876,7 +4598,7 @@ function registerRedteamCommand(program) {
|
|
|
3876
4598
|
devices.command("update <tenantId>").description("Update devices for an instance (PATCH)").requiredOption("--config <path>", "JSON file with device request").action(async (tenantId, opts) => {
|
|
3877
4599
|
try {
|
|
3878
4600
|
renderRedteamHeader();
|
|
3879
|
-
const service = await
|
|
4601
|
+
const service = await createService3();
|
|
3880
4602
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
3881
4603
|
const result = await service.updateDevices(tenantId, config);
|
|
3882
4604
|
ui.success("Devices updated:");
|
|
@@ -3888,7 +4610,7 @@ function registerRedteamCommand(program) {
|
|
|
3888
4610
|
devices.command("delete <tenantId>").description("Delete devices by serial numbers").requiredOption("--serial-numbers <list>", "Comma-separated serial numbers").action(async (tenantId, opts) => {
|
|
3889
4611
|
try {
|
|
3890
4612
|
renderRedteamHeader();
|
|
3891
|
-
const service = await
|
|
4613
|
+
const service = await createService3();
|
|
3892
4614
|
const result = await service.deleteDevices(tenantId, opts.serialNumbers);
|
|
3893
4615
|
ui.success("Devices deleted:");
|
|
3894
4616
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -3900,7 +4622,7 @@ function registerRedteamCommand(program) {
|
|
|
3900
4622
|
try {
|
|
3901
4623
|
const fmt = opts.output;
|
|
3902
4624
|
if (fmt === "pretty") renderRedteamHeader();
|
|
3903
|
-
const service = await
|
|
4625
|
+
const service = await createService3();
|
|
3904
4626
|
const creds = await service.getRegistryCredentials();
|
|
3905
4627
|
renderRegistryCredentials(creds, fmt);
|
|
3906
4628
|
} catch (err) {
|
|
@@ -3911,7 +4633,7 @@ function registerRedteamCommand(program) {
|
|
|
3911
4633
|
try {
|
|
3912
4634
|
const fmt = opts.output;
|
|
3913
4635
|
if (fmt === "pretty") renderRedteamHeader();
|
|
3914
|
-
const service = await
|
|
4636
|
+
const service = await createService3();
|
|
3915
4637
|
const scans = await service.listScans({
|
|
3916
4638
|
status: opts.status,
|
|
3917
4639
|
jobType: opts.type,
|
|
@@ -4124,7 +4846,7 @@ function registerRedteamCommand(program) {
|
|
|
4124
4846
|
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
4847
|
try {
|
|
4126
4848
|
renderRedteamHeader();
|
|
4127
|
-
const service = await
|
|
4849
|
+
const service = await createService3();
|
|
4128
4850
|
const job = await service.getScan(jobId);
|
|
4129
4851
|
renderScanStatus(job);
|
|
4130
4852
|
if (job.jobType === "CUSTOM") {
|
|
@@ -4184,7 +4906,18 @@ function registerRedteamCommand(program) {
|
|
|
4184
4906
|
const customPromptSets = opts.promptSets ? opts.promptSets.split(",").map((s) => s.trim()) : void 0;
|
|
4185
4907
|
try {
|
|
4186
4908
|
renderRedteamHeader();
|
|
4187
|
-
const service = await
|
|
4909
|
+
const service = await createService3();
|
|
4910
|
+
if (opts.type === "STATIC" && !categories) {
|
|
4911
|
+
const defaultCategories = buildDefaultCategories(await service.getCategories());
|
|
4912
|
+
const categoryCount = Object.values(defaultCategories).reduce(
|
|
4913
|
+
(total, subCategories) => total + subCategories.length,
|
|
4914
|
+
0
|
|
4915
|
+
);
|
|
4916
|
+
categories = defaultCategories;
|
|
4917
|
+
ui.status(
|
|
4918
|
+
`No --categories given \u2014 defaulting to all ${categoryCount} categories (MULTI_TURN excluded). Pass --categories to narrow the scan.`
|
|
4919
|
+
);
|
|
4920
|
+
}
|
|
4188
4921
|
ui.status(`Creating ${opts.type} scan "${opts.name}"...`);
|
|
4189
4922
|
const job = await service.createScan({
|
|
4190
4923
|
name: opts.name,
|
|
@@ -4218,7 +4951,7 @@ function registerRedteamCommand(program) {
|
|
|
4218
4951
|
redteam.command("status <jobId>").description("Check scan status").action(async (jobId) => {
|
|
4219
4952
|
try {
|
|
4220
4953
|
renderRedteamHeader();
|
|
4221
|
-
const service = await
|
|
4954
|
+
const service = await createService3();
|
|
4222
4955
|
const job = await service.getScan(jobId);
|
|
4223
4956
|
renderScanStatus(job);
|
|
4224
4957
|
} catch (err) {
|
|
@@ -4237,7 +4970,7 @@ function registerRedteamCommand(program) {
|
|
|
4237
4970
|
try {
|
|
4238
4971
|
const fmt = opts.output;
|
|
4239
4972
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4240
|
-
const service = await
|
|
4973
|
+
const service = await createService3();
|
|
4241
4974
|
const list = await service.listTargets();
|
|
4242
4975
|
renderTargetList(sliceClientSide(list, opts), fmt);
|
|
4243
4976
|
} catch (err) {
|
|
@@ -4248,7 +4981,7 @@ function registerRedteamCommand(program) {
|
|
|
4248
4981
|
try {
|
|
4249
4982
|
const fmt = opts.output;
|
|
4250
4983
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4251
|
-
const service = await
|
|
4984
|
+
const service = await createService3();
|
|
4252
4985
|
const target = await service.getTarget(uuid);
|
|
4253
4986
|
renderTargetDetail(target, fmt);
|
|
4254
4987
|
} catch (err) {
|
|
@@ -4258,7 +4991,7 @@ function registerRedteamCommand(program) {
|
|
|
4258
4991
|
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
4992
|
try {
|
|
4260
4993
|
renderRedteamHeader();
|
|
4261
|
-
const service = await
|
|
4994
|
+
const service = await createService3();
|
|
4262
4995
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4263
4996
|
const target = await service.createTarget(
|
|
4264
4997
|
config,
|
|
@@ -4273,7 +5006,7 @@ function registerRedteamCommand(program) {
|
|
|
4273
5006
|
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
5007
|
try {
|
|
4275
5008
|
renderRedteamHeader();
|
|
4276
|
-
const service = await
|
|
5009
|
+
const service = await createService3();
|
|
4277
5010
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4278
5011
|
const target = await service.updateTarget(
|
|
4279
5012
|
uuid,
|
|
@@ -4292,7 +5025,7 @@ function registerRedteamCommand(program) {
|
|
|
4292
5025
|
action: `delete target ${uuid}`
|
|
4293
5026
|
});
|
|
4294
5027
|
renderRedteamHeader();
|
|
4295
|
-
const service = await
|
|
5028
|
+
const service = await createService3();
|
|
4296
5029
|
await service.deleteTarget(uuid);
|
|
4297
5030
|
ui.success(`Target ${uuid} deleted.`);
|
|
4298
5031
|
} catch (err) {
|
|
@@ -4302,7 +5035,7 @@ function registerRedteamCommand(program) {
|
|
|
4302
5035
|
targets.command("probe").description("Test target connection without saving").requiredOption("--config <path>", "JSON file with connection params").action(async (opts) => {
|
|
4303
5036
|
try {
|
|
4304
5037
|
renderRedteamHeader();
|
|
4305
|
-
const service = await
|
|
5038
|
+
const service = await createService3();
|
|
4306
5039
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4307
5040
|
const result = await service.probeTarget(config);
|
|
4308
5041
|
ui.dim("Probe result:");
|
|
@@ -4314,7 +5047,7 @@ function registerRedteamCommand(program) {
|
|
|
4314
5047
|
targets.command("profile <uuid>").description("View target profile").action(async (uuid) => {
|
|
4315
5048
|
try {
|
|
4316
5049
|
renderRedteamHeader();
|
|
4317
|
-
const service = await
|
|
5050
|
+
const service = await createService3();
|
|
4318
5051
|
const profile = await service.getTargetProfile(uuid);
|
|
4319
5052
|
ui.dim("Target Profile:");
|
|
4320
5053
|
console.log(JSON.stringify(profile, null, 2));
|
|
@@ -4325,7 +5058,7 @@ function registerRedteamCommand(program) {
|
|
|
4325
5058
|
targets.command("update-profile <uuid>").description("Update target profile").requiredOption("--config <path>", "JSON file with profile updates").action(async (uuid, opts) => {
|
|
4326
5059
|
try {
|
|
4327
5060
|
renderRedteamHeader();
|
|
4328
|
-
const service = await
|
|
5061
|
+
const service = await createService3();
|
|
4329
5062
|
const config = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4330
5063
|
const result = await service.updateTargetProfile(uuid, config);
|
|
4331
5064
|
ui.success("Profile updated:");
|
|
@@ -4337,7 +5070,7 @@ function registerRedteamCommand(program) {
|
|
|
4337
5070
|
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
5071
|
try {
|
|
4339
5072
|
renderRedteamHeader();
|
|
4340
|
-
const service = await
|
|
5073
|
+
const service = await createService3();
|
|
4341
5074
|
const authConfig = JSON.parse(fs2.readFileSync(opts.config, "utf-8"));
|
|
4342
5075
|
const result = await service.validateTargetAuth({
|
|
4343
5076
|
authType: opts.authType,
|
|
@@ -4351,7 +5084,7 @@ function registerRedteamCommand(program) {
|
|
|
4351
5084
|
});
|
|
4352
5085
|
targets.command("metadata").description("Get target field metadata").action(async () => {
|
|
4353
5086
|
try {
|
|
4354
|
-
const service = await
|
|
5087
|
+
const service = await createService3();
|
|
4355
5088
|
const metadata = await service.getTargetMetadata();
|
|
4356
5089
|
console.log(JSON.stringify(metadata, null, 2));
|
|
4357
5090
|
} catch (err) {
|
|
@@ -4383,7 +5116,7 @@ function registerRedteamCommand(program) {
|
|
|
4383
5116
|
}
|
|
4384
5117
|
try {
|
|
4385
5118
|
renderRedteamHeader();
|
|
4386
|
-
const service = await
|
|
5119
|
+
const service = await createService3();
|
|
4387
5120
|
const templates = await service.getTargetTemplates();
|
|
4388
5121
|
const scaffold = buildTargetScaffold(provider, templates);
|
|
4389
5122
|
fs2.writeFileSync(outputPath, `${JSON.stringify(scaffold, null, 2)}
|
|
@@ -4402,7 +5135,7 @@ function registerRedteamCommand(program) {
|
|
|
4402
5135
|
targets.command("templates").description("Get provider-specific target templates").action(async () => {
|
|
4403
5136
|
try {
|
|
4404
5137
|
renderRedteamHeader();
|
|
4405
|
-
const service = await
|
|
5138
|
+
const service = await createService3();
|
|
4406
5139
|
const templates = await service.getTargetTemplates();
|
|
4407
5140
|
renderTargetTemplates(templates);
|
|
4408
5141
|
} catch (err) {
|
|
@@ -4456,7 +5189,7 @@ function registerRedteamCommand(program) {
|
|
|
4456
5189
|
try {
|
|
4457
5190
|
const fmt = opts.output;
|
|
4458
5191
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4459
|
-
const service = await
|
|
5192
|
+
const service = await createService3();
|
|
4460
5193
|
const { logs } = await service.getTargetProfileErrorLogs(targetId, {
|
|
4461
5194
|
limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
|
|
4462
5195
|
offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
|
|
@@ -4467,13 +5200,173 @@ function registerRedteamCommand(program) {
|
|
|
4467
5200
|
fail(err);
|
|
4468
5201
|
}
|
|
4469
5202
|
});
|
|
5203
|
+
const adapter = redteam.command("adapter").description("Manage custom target adapters (scripted targets run via the network broker)");
|
|
5204
|
+
async function assertChannelOnline(service, channelUuid) {
|
|
5205
|
+
let status;
|
|
5206
|
+
try {
|
|
5207
|
+
status = (await service.getChannel(channelUuid)).status;
|
|
5208
|
+
} catch {
|
|
5209
|
+
return;
|
|
5210
|
+
}
|
|
5211
|
+
if (status && status !== "ONLINE") {
|
|
5212
|
+
fail(
|
|
5213
|
+
new Error(
|
|
5214
|
+
`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'.`
|
|
5215
|
+
)
|
|
5216
|
+
);
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
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) => {
|
|
5220
|
+
try {
|
|
5221
|
+
const fmt = opts.output;
|
|
5222
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5223
|
+
const service = await createService3();
|
|
5224
|
+
const { adapters, totalItems } = await service.listAdapters({
|
|
5225
|
+
limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
|
|
5226
|
+
offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
|
|
5227
|
+
search: opts.search
|
|
5228
|
+
});
|
|
5229
|
+
renderAdapterList(adapters, fmt, totalItems);
|
|
5230
|
+
} catch (err) {
|
|
5231
|
+
fail(err);
|
|
5232
|
+
}
|
|
5233
|
+
});
|
|
5234
|
+
adapter.command("get <uuid>").description("Get a custom target adapter").option("--output <format>", "Output format: pretty, json, yaml", "pretty").action(async (uuid, opts) => {
|
|
5235
|
+
try {
|
|
5236
|
+
const fmt = opts.output;
|
|
5237
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5238
|
+
const service = await createService3();
|
|
5239
|
+
renderAdapterDetail(await service.getAdapter(uuid), fmt);
|
|
5240
|
+
} catch (err) {
|
|
5241
|
+
fail(err);
|
|
5242
|
+
}
|
|
5243
|
+
});
|
|
5244
|
+
adapter.command("create").description("Create a custom target adapter").requiredOption("--name <name>", "Adapter name").requiredOption(
|
|
5245
|
+
"--prompt <text>",
|
|
5246
|
+
"Sample prompt used to exercise the adapter during validation (not stored)"
|
|
5247
|
+
).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(
|
|
5248
|
+
"after",
|
|
5249
|
+
examples(
|
|
5250
|
+
`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"}]'`,
|
|
5251
|
+
"airs redteam adapter create --name my-adapter --script-file ./adapter.py --prompt Hello --draft"
|
|
5252
|
+
)
|
|
5253
|
+
).action(async (opts) => {
|
|
5254
|
+
try {
|
|
5255
|
+
const fmt = opts.output;
|
|
5256
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5257
|
+
const scriptB64 = resolveScriptB64(opts);
|
|
5258
|
+
const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
|
|
5259
|
+
const service = await createService3();
|
|
5260
|
+
if (!opts.draft && opts.channel) await assertChannelOnline(service, opts.channel);
|
|
5261
|
+
const created = await service.createAdapter(
|
|
5262
|
+
{
|
|
5263
|
+
name: opts.name,
|
|
5264
|
+
scriptB64,
|
|
5265
|
+
prompt: opts.prompt,
|
|
5266
|
+
description: opts.description,
|
|
5267
|
+
networkBrokerChannelUuid: opts.channel,
|
|
5268
|
+
variables
|
|
5269
|
+
},
|
|
5270
|
+
opts.draft ? false : void 0
|
|
5271
|
+
);
|
|
5272
|
+
ui.success(`Adapter created: ${created.uuid}`);
|
|
5273
|
+
renderAdapterDetail(created, fmt);
|
|
5274
|
+
} catch (err) {
|
|
5275
|
+
fail(err);
|
|
5276
|
+
}
|
|
5277
|
+
});
|
|
5278
|
+
adapter.command("update <uuid>").description(
|
|
5279
|
+
"Update a custom target adapter (read-modify-write; variables preserved unless --variables)"
|
|
5280
|
+
).requiredOption(
|
|
5281
|
+
"--prompt <text>",
|
|
5282
|
+
"Sample validation prompt \u2014 required on every update because upstream never stores it"
|
|
5283
|
+
).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(
|
|
5284
|
+
"--variables <json>",
|
|
5285
|
+
"REPLACES the whole variable set \u2014 omitted keys are deleted upstream. Omit this flag to preserve stored variables."
|
|
5286
|
+
).option("--draft", "Save as DRAFT without re-running the validation script").option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
|
|
5287
|
+
"after",
|
|
5288
|
+
examples(
|
|
5289
|
+
`airs redteam adapter update 550e8400-... --description 'new description' --prompt 'Hello'`
|
|
5290
|
+
)
|
|
5291
|
+
).action(async (uuid, opts) => {
|
|
5292
|
+
try {
|
|
5293
|
+
const fmt = opts.output;
|
|
5294
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5295
|
+
const scriptB64 = opts.scriptFile !== void 0 || opts.scriptB64 !== void 0 ? resolveScriptB64(opts) : void 0;
|
|
5296
|
+
const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
|
|
5297
|
+
const service = await createService3();
|
|
5298
|
+
if (!opts.draft && opts.channel) await assertChannelOnline(service, opts.channel);
|
|
5299
|
+
const updated = await service.updateAdapter(
|
|
5300
|
+
uuid,
|
|
5301
|
+
{
|
|
5302
|
+
prompt: opts.prompt,
|
|
5303
|
+
name: opts.name,
|
|
5304
|
+
scriptB64,
|
|
5305
|
+
description: opts.description,
|
|
5306
|
+
networkBrokerChannelUuid: opts.channel,
|
|
5307
|
+
variables
|
|
5308
|
+
},
|
|
5309
|
+
opts.draft ? false : void 0
|
|
5310
|
+
);
|
|
5311
|
+
ui.success(`Adapter updated: ${updated.uuid}`);
|
|
5312
|
+
renderAdapterDetail(updated, fmt);
|
|
5313
|
+
} catch (err) {
|
|
5314
|
+
fail(err);
|
|
5315
|
+
}
|
|
5316
|
+
});
|
|
5317
|
+
adapter.command("delete <uuid>").description("Delete a custom target adapter").option("--force", "Skip confirmation prompt").action(async (uuid, opts) => {
|
|
5318
|
+
try {
|
|
5319
|
+
renderRedteamHeader();
|
|
5320
|
+
await confirmOrAbort(`Delete adapter ${uuid}?`, Boolean(opts.force), {
|
|
5321
|
+
action: `delete adapter ${uuid}`
|
|
5322
|
+
});
|
|
5323
|
+
const service = await createService3();
|
|
5324
|
+
await service.deleteAdapter(uuid);
|
|
5325
|
+
ui.success(`Adapter ${uuid} deleted.`);
|
|
5326
|
+
} catch (err) {
|
|
5327
|
+
fail(err);
|
|
5328
|
+
}
|
|
5329
|
+
});
|
|
5330
|
+
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(
|
|
5331
|
+
"--variables <json>",
|
|
5332
|
+
"JSON array of { key, value, type } \u2014 the FULL set the script needs"
|
|
5333
|
+
).option(
|
|
5334
|
+
"--adapter <uuid>",
|
|
5335
|
+
"Existing adapter: resolves redacted/null variable values from its stored secrets (and supplies its variable set when --variables is omitted)"
|
|
5336
|
+
).option("--output <format>", "Output format: pretty, json, yaml", "pretty").addHelpText(
|
|
5337
|
+
"after",
|
|
5338
|
+
examples(
|
|
5339
|
+
`airs redteam adapter validate --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --variables '[{"key":"endpoint","value":"http://agent.svc:8080","type":"VAR"}]'`,
|
|
5340
|
+
`airs redteam adapter validate --script-file ./adapter.py --channel 550e8400-... --prompt 'Hello' --adapter 660e8400-...`
|
|
5341
|
+
)
|
|
5342
|
+
).action(async (opts) => {
|
|
5343
|
+
try {
|
|
5344
|
+
const fmt = opts.output;
|
|
5345
|
+
if (fmt === "pretty") renderRedteamHeader();
|
|
5346
|
+
const scriptB64 = resolveScriptB64(opts);
|
|
5347
|
+
const variables = opts.variables ? parseAdapterVariables(opts.variables) : void 0;
|
|
5348
|
+
const service = await createService3();
|
|
5349
|
+
await assertChannelOnline(service, opts.channel);
|
|
5350
|
+
const result = await service.validateAdapter({
|
|
5351
|
+
scriptB64,
|
|
5352
|
+
networkBrokerChannelUuid: opts.channel,
|
|
5353
|
+
prompt: opts.prompt,
|
|
5354
|
+
variables,
|
|
5355
|
+
adapterUuid: opts.adapter
|
|
5356
|
+
});
|
|
5357
|
+
renderAdapterValidation(result, fmt);
|
|
5358
|
+
if (!result.validated) process.exitCode = 1;
|
|
5359
|
+
} catch (err) {
|
|
5360
|
+
fail(err);
|
|
5361
|
+
}
|
|
5362
|
+
});
|
|
4470
5363
|
const networkBroker = redteam.command("network-broker").description("Manage red team network broker channels");
|
|
4471
5364
|
const channels = networkBroker.command("channels").description("Manage network broker channels");
|
|
4472
5365
|
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
5366
|
try {
|
|
4474
5367
|
const fmt = opts.output;
|
|
4475
5368
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4476
|
-
const service = await
|
|
5369
|
+
const service = await createService3();
|
|
4477
5370
|
const { channels: list } = await service.listChannels({
|
|
4478
5371
|
limit: opts.limit ? parsePositiveInt(opts.limit, "--limit") : void 0,
|
|
4479
5372
|
offset: opts.offset ? Number.parseInt(opts.offset, 10) : void 0,
|
|
@@ -4489,7 +5382,7 @@ function registerRedteamCommand(program) {
|
|
|
4489
5382
|
try {
|
|
4490
5383
|
const fmt = opts.output;
|
|
4491
5384
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4492
|
-
const service = await
|
|
5385
|
+
const service = await createService3();
|
|
4493
5386
|
const channel = await service.getChannel(channelId);
|
|
4494
5387
|
renderChannelDetail(channel, fmt);
|
|
4495
5388
|
} catch (err) {
|
|
@@ -4499,7 +5392,7 @@ function registerRedteamCommand(program) {
|
|
|
4499
5392
|
channels.command("create").description("Create a network broker channel").requiredOption("--name <name>", "Channel name").option("--description <text>", "Channel description").action(async (opts) => {
|
|
4500
5393
|
try {
|
|
4501
5394
|
renderRedteamHeader();
|
|
4502
|
-
const service = await
|
|
5395
|
+
const service = await createService3();
|
|
4503
5396
|
const channel = await service.createChannel({
|
|
4504
5397
|
name: opts.name,
|
|
4505
5398
|
description: opts.description
|
|
@@ -4516,7 +5409,7 @@ function registerRedteamCommand(program) {
|
|
|
4516
5409
|
usageError("Specify --name and/or --description to update");
|
|
4517
5410
|
}
|
|
4518
5411
|
renderRedteamHeader();
|
|
4519
|
-
const service = await
|
|
5412
|
+
const service = await createService3();
|
|
4520
5413
|
const channel = await service.updateChannel(channelId, {
|
|
4521
5414
|
name: opts.name,
|
|
4522
5415
|
description: opts.description
|
|
@@ -4531,7 +5424,7 @@ function registerRedteamCommand(program) {
|
|
|
4531
5424
|
try {
|
|
4532
5425
|
const fmt = opts.output;
|
|
4533
5426
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4534
|
-
const service = await
|
|
5427
|
+
const service = await createService3();
|
|
4535
5428
|
const stats = await service.getChannelStats();
|
|
4536
5429
|
renderChannelStats(stats, fmt);
|
|
4537
5430
|
} catch (err) {
|
|
@@ -4542,7 +5435,7 @@ function registerRedteamCommand(program) {
|
|
|
4542
5435
|
try {
|
|
4543
5436
|
const fmt = opts.output;
|
|
4544
5437
|
if (fmt === "pretty") renderRedteamHeader();
|
|
4545
|
-
const service = await
|
|
5438
|
+
const service = await createService3();
|
|
4546
5439
|
const data = await service.getLanguages(Boolean(opts.management));
|
|
4547
5440
|
renderLanguages(data, fmt);
|
|
4548
5441
|
} catch (err) {
|
|
@@ -4556,7 +5449,7 @@ import { randomUUID as randomUUID4 } from "crypto";
|
|
|
4556
5449
|
import * as fs5 from "fs";
|
|
4557
5450
|
import { readFile as readFile8 } from "fs/promises";
|
|
4558
5451
|
import { basename as basename3, dirname as dirname2, join as join2, resolve as resolvePath } from "path";
|
|
4559
|
-
import
|
|
5452
|
+
import chalk11 from "chalk";
|
|
4560
5453
|
|
|
4561
5454
|
// src/cli/builders/profile-builder.ts
|
|
4562
5455
|
function parseList(value) {
|
|
@@ -6373,14 +7266,14 @@ function registerSampleCommand(parent) {
|
|
|
6373
7266
|
|
|
6374
7267
|
// src/cli/commands/runtime.ts
|
|
6375
7268
|
function renderScanResult(result) {
|
|
6376
|
-
const actionColor = result.action === "block" ?
|
|
7269
|
+
const actionColor = result.action === "block" ? chalk11.red : chalk11.green;
|
|
6377
7270
|
ui.header("Scan Result");
|
|
6378
7271
|
ui.keyValue([
|
|
6379
7272
|
["Action", actionColor(result.action.toUpperCase())],
|
|
6380
7273
|
["Category", result.category],
|
|
6381
|
-
["Triggered", result.triggered ?
|
|
6382
|
-
["Scan ID",
|
|
6383
|
-
["Report ID",
|
|
7274
|
+
["Triggered", result.triggered ? chalk11.red("yes") : chalk11.green("no")],
|
|
7275
|
+
["Scan ID", chalk11.dim(result.scanId)],
|
|
7276
|
+
["Report ID", chalk11.dim(result.reportId)]
|
|
6384
7277
|
]);
|
|
6385
7278
|
const flags = Object.entries(result.detections).filter(([, v]) => v);
|
|
6386
7279
|
if (flags.length > 0) {
|
|
@@ -6649,10 +7542,10 @@ function registerRuntimeCommand(program) {
|
|
|
6649
7542
|
ui.header("Bulk Scan Complete");
|
|
6650
7543
|
ui.keyValue([
|
|
6651
7544
|
["Total", results.length],
|
|
6652
|
-
["Blocked",
|
|
6653
|
-
["Allowed",
|
|
6654
|
-
["Failed",
|
|
6655
|
-
["Output",
|
|
7545
|
+
["Blocked", chalk11.red(String(blocked))],
|
|
7546
|
+
["Allowed", chalk11.green(String(allowed))],
|
|
7547
|
+
["Failed", chalk11.red(String(failed))],
|
|
7548
|
+
["Output", chalk11.cyan(outputPath)]
|
|
6656
7549
|
]);
|
|
6657
7550
|
if (failed > 0) {
|
|
6658
7551
|
ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
|
|
@@ -7061,10 +7954,10 @@ function registerRuntimeCommand(program) {
|
|
|
7061
7954
|
ui.header("Resume Poll Complete");
|
|
7062
7955
|
ui.keyValue([
|
|
7063
7956
|
["Total", results.length],
|
|
7064
|
-
["Blocked",
|
|
7065
|
-
["Allowed",
|
|
7066
|
-
["Failed",
|
|
7067
|
-
["Output",
|
|
7957
|
+
["Blocked", chalk11.red(String(blocked))],
|
|
7958
|
+
["Allowed", chalk11.green(String(allowed))],
|
|
7959
|
+
["Failed", chalk11.red(String(failed))],
|
|
7960
|
+
["Output", chalk11.cyan(outputPath)]
|
|
7068
7961
|
]);
|
|
7069
7962
|
if (failed > 0) {
|
|
7070
7963
|
ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
|
|
@@ -7416,6 +8309,7 @@ function buildProgram() {
|
|
|
7416
8309
|
registerRuntimeCommand(program);
|
|
7417
8310
|
registerRedteamCommand(program);
|
|
7418
8311
|
registerModelSecurityCommand(program);
|
|
8312
|
+
registerAiGatewayCommand(program);
|
|
7419
8313
|
registerConfigCommand(program);
|
|
7420
8314
|
registerDoctorCommand(program);
|
|
7421
8315
|
registerCompletionCommand(program);
|