@lexq/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +117 -0
- package/CONTEXT.md +158 -0
- package/README.md +153 -0
- package/dist/index.js +1850 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/skills/lexq-execution/SKILL.md +211 -0
- package/skills/lexq-groups/SKILL.md +181 -0
- package/skills/lexq-recipes/SKILL.md +407 -0
- package/skills/lexq-rules/SKILL.md +264 -0
- package/skills/lexq-shared/SKILL.md +139 -0
- package/skills/lexq-simulation/SKILL.md +279 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1850 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { dirname, join as join2 } from "path";
|
|
8
|
+
|
|
9
|
+
// src/commands/auth.ts
|
|
10
|
+
import "commander";
|
|
11
|
+
import { createInterface } from "readline/promises";
|
|
12
|
+
import { stdin, stdout } from "process";
|
|
13
|
+
|
|
14
|
+
// src/lib/config.ts
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
16
|
+
import { homedir } from "os";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
var CONFIG_DIR = join(homedir(), ".lexq");
|
|
19
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
20
|
+
var DEFAULT_CONFIG = {
|
|
21
|
+
baseUrl: "https://api.lexq.io/api/v1/partners",
|
|
22
|
+
format: "json"
|
|
23
|
+
};
|
|
24
|
+
function loadConfig() {
|
|
25
|
+
if (!existsSync(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
|
|
26
|
+
try {
|
|
27
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
28
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
29
|
+
} catch {
|
|
30
|
+
return { ...DEFAULT_CONFIG };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function saveConfig(config) {
|
|
34
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
35
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
const current = loadConfig();
|
|
38
|
+
const merged = { ...current, ...config };
|
|
39
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf-8");
|
|
40
|
+
}
|
|
41
|
+
function deleteConfig() {
|
|
42
|
+
if (existsSync(CONFIG_FILE)) {
|
|
43
|
+
unlinkSync(CONFIG_FILE);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getConfigPath() {
|
|
47
|
+
return CONFIG_FILE;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/lib/api-client.ts
|
|
51
|
+
var ApiError = class extends Error {
|
|
52
|
+
constructor(statusCode, errorCode, message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.statusCode = statusCode;
|
|
55
|
+
this.errorCode = errorCode;
|
|
56
|
+
this.name = "ApiError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
async function apiRequest(method, path, options = {}) {
|
|
60
|
+
const config = loadConfig();
|
|
61
|
+
const baseUrl = options.baseUrl ?? config.baseUrl;
|
|
62
|
+
const apiKey = options.apiKey ?? config.apiKey;
|
|
63
|
+
if (!apiKey) {
|
|
64
|
+
throw new ApiError(401, "AUTH", 'Not authenticated. Run "lexq auth login" first.');
|
|
65
|
+
}
|
|
66
|
+
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : baseUrl + "/");
|
|
67
|
+
if (options.params) {
|
|
68
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
69
|
+
if (value !== void 0 && value !== "") {
|
|
70
|
+
url.searchParams.set(key, value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const headers = {
|
|
75
|
+
"X-API-KEY": apiKey,
|
|
76
|
+
"Accept": "application/json"
|
|
77
|
+
};
|
|
78
|
+
if (options.body) {
|
|
79
|
+
headers["Content-Type"] = "application/json";
|
|
80
|
+
}
|
|
81
|
+
if (options.dryRun) {
|
|
82
|
+
const masked = apiKey.length > 8 ? apiKey.substring(0, 4) + "****" + apiKey.substring(apiKey.length - 4) : "****";
|
|
83
|
+
console.log(`${method} ${url.toString()}`);
|
|
84
|
+
console.log("Headers:");
|
|
85
|
+
console.log(` X-API-KEY: ${masked}`);
|
|
86
|
+
console.log(` Content-Type: application/json`);
|
|
87
|
+
if (options.body) {
|
|
88
|
+
console.log("Body:");
|
|
89
|
+
console.log(` ${JSON.stringify(options.body, null, 2)}`);
|
|
90
|
+
}
|
|
91
|
+
console.log("\n(Use without --dry-run to execute)");
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
if (options.verbose) {
|
|
95
|
+
console.error(`\u2192 ${method} ${url.toString()}`);
|
|
96
|
+
}
|
|
97
|
+
const startTime = Date.now();
|
|
98
|
+
const response = await fetch(url.toString(), {
|
|
99
|
+
method,
|
|
100
|
+
headers,
|
|
101
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
102
|
+
});
|
|
103
|
+
if (options.verbose) {
|
|
104
|
+
console.error(`\u2190 ${response.status} ${response.statusText} (${Date.now() - startTime}ms)`);
|
|
105
|
+
}
|
|
106
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
107
|
+
if (contentType.includes("text/csv") || contentType.includes("application/octet-stream")) {
|
|
108
|
+
return response;
|
|
109
|
+
}
|
|
110
|
+
if (response.status === 204 || contentType === "") {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
const json = await response.json();
|
|
114
|
+
if (!response.ok || json.result !== "SUCCESS") {
|
|
115
|
+
throw new ApiError(
|
|
116
|
+
response.status,
|
|
117
|
+
json.errorCode,
|
|
118
|
+
json.message ?? `Request failed with status ${response.status}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return json.data;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/lib/output.ts
|
|
125
|
+
import Table from "cli-table3";
|
|
126
|
+
function printJson(data) {
|
|
127
|
+
console.log(JSON.stringify(data, null, 2));
|
|
128
|
+
}
|
|
129
|
+
function printTable(headers, rows, options) {
|
|
130
|
+
const table = new Table({
|
|
131
|
+
head: headers,
|
|
132
|
+
style: { head: ["cyan"] },
|
|
133
|
+
wordWrap: true
|
|
134
|
+
});
|
|
135
|
+
for (const row of rows) {
|
|
136
|
+
if (options?.truncate) {
|
|
137
|
+
table.push(
|
|
138
|
+
row.map(
|
|
139
|
+
(cell) => cell.length > options.truncate ? cell.substring(0, options.truncate) + "\u2026" : cell
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
table.push(row);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
console.log(table.toString());
|
|
147
|
+
}
|
|
148
|
+
function printError(error) {
|
|
149
|
+
if (error instanceof Error && "errorCode" in error) {
|
|
150
|
+
const apiErr = error;
|
|
151
|
+
const output = {
|
|
152
|
+
error: apiErr.errorCode ?? "UNKNOWN",
|
|
153
|
+
message: apiErr.message,
|
|
154
|
+
status: apiErr.statusCode
|
|
155
|
+
};
|
|
156
|
+
console.error(JSON.stringify(output, null, 2));
|
|
157
|
+
} else if (error instanceof Error) {
|
|
158
|
+
console.error(JSON.stringify({ error: "CLI_ERROR", message: error.message }, null, 2));
|
|
159
|
+
} else {
|
|
160
|
+
console.error(JSON.stringify({ error: "UNKNOWN", message: String(error) }, null, 2));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/commands/auth.ts
|
|
165
|
+
function registerAuthCommands(program) {
|
|
166
|
+
const auth = program.command("auth").description("Manage authentication");
|
|
167
|
+
auth.command("login").description("Authenticate with your LexQ API key").action(async () => {
|
|
168
|
+
try {
|
|
169
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
170
|
+
const apiKey = await rl.question("Enter your API Key: ");
|
|
171
|
+
rl.close();
|
|
172
|
+
if (!apiKey.trim()) {
|
|
173
|
+
console.error("API key cannot be empty.");
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
saveConfig({ apiKey: apiKey.trim() });
|
|
177
|
+
console.log(`\u2713 API key saved to ${getConfigPath()}`);
|
|
178
|
+
console.log(' Run "lexq auth whoami" to verify.');
|
|
179
|
+
} catch (error) {
|
|
180
|
+
printError(error);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
auth.command("logout").description("Remove stored credentials").action(() => {
|
|
185
|
+
deleteConfig();
|
|
186
|
+
console.log("\u2713 Credentials removed.");
|
|
187
|
+
});
|
|
188
|
+
auth.command("whoami").description("Show current authentication info").action(async () => {
|
|
189
|
+
try {
|
|
190
|
+
const config = loadConfig();
|
|
191
|
+
if (!config.apiKey) {
|
|
192
|
+
console.error('Not authenticated. Run "lexq auth login" first.');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
const info = await apiRequest(
|
|
196
|
+
"GET",
|
|
197
|
+
"whoami",
|
|
198
|
+
{ apiKey: config.apiKey, baseUrl: config.baseUrl }
|
|
199
|
+
);
|
|
200
|
+
const masked = config.apiKey.length > 8 ? config.apiKey.substring(0, 4) + "****" + config.apiKey.substring(config.apiKey.length - 4) : "****";
|
|
201
|
+
printJson({
|
|
202
|
+
...info,
|
|
203
|
+
apiKey: masked,
|
|
204
|
+
baseUrl: config.baseUrl
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
printError(error);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/commands/status.ts
|
|
214
|
+
import "commander";
|
|
215
|
+
function registerStatusCommand(program) {
|
|
216
|
+
program.command("status").description("Check LexQ API server status").action(async () => {
|
|
217
|
+
const config = loadConfig();
|
|
218
|
+
const baseUrl = config.baseUrl.replace(/\/v1\/partners\/?$/, "");
|
|
219
|
+
try {
|
|
220
|
+
const start = Date.now();
|
|
221
|
+
const response = await fetch(`${baseUrl}/health`);
|
|
222
|
+
const latency = Date.now() - start;
|
|
223
|
+
printJson({
|
|
224
|
+
status: response.ok ? "ok" : "degraded",
|
|
225
|
+
httpStatus: response.status,
|
|
226
|
+
latencyMs: latency,
|
|
227
|
+
endpoint: `${baseUrl}/health`
|
|
228
|
+
});
|
|
229
|
+
} catch {
|
|
230
|
+
printError(new Error(`Cannot reach LexQ API at ${baseUrl}/health`));
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/commands/groups.ts
|
|
237
|
+
import "commander";
|
|
238
|
+
function registerGroupCommands(program) {
|
|
239
|
+
const groups = program.command("groups").description("Manage policy groups");
|
|
240
|
+
groups.command("list").description("List all policy groups").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
241
|
+
try {
|
|
242
|
+
const globalOpts = program.opts();
|
|
243
|
+
const format = globalOpts.format ?? "json";
|
|
244
|
+
const data = await apiRequest(
|
|
245
|
+
"GET",
|
|
246
|
+
"policy-groups",
|
|
247
|
+
{
|
|
248
|
+
apiKey: globalOpts.apiKey,
|
|
249
|
+
baseUrl: globalOpts.baseUrl,
|
|
250
|
+
dryRun: globalOpts.dryRun,
|
|
251
|
+
verbose: globalOpts.verbose,
|
|
252
|
+
params: { page: opts.page, size: opts.size }
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
if (format === "table") {
|
|
256
|
+
printTable(
|
|
257
|
+
["ID", "Name", "Status", "Priority", "Version", "Updated"],
|
|
258
|
+
data.content.map((g) => [
|
|
259
|
+
g.id,
|
|
260
|
+
g.name,
|
|
261
|
+
g.status,
|
|
262
|
+
String(g.priority),
|
|
263
|
+
g.currentVersionName ?? "\u2013",
|
|
264
|
+
g.updatedAt.substring(0, 10)
|
|
265
|
+
]),
|
|
266
|
+
{ truncate: 24 }
|
|
267
|
+
);
|
|
268
|
+
console.log(`
|
|
269
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
270
|
+
} else {
|
|
271
|
+
printJson(data);
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
printError(error);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
groups.command("get").description("Get a policy group by ID").requiredOption("--id <groupId>", "Policy group ID").action(async (opts) => {
|
|
279
|
+
try {
|
|
280
|
+
const globalOpts = program.opts();
|
|
281
|
+
const data = await apiRequest(
|
|
282
|
+
"GET",
|
|
283
|
+
`policy-groups/${opts.id}`,
|
|
284
|
+
{
|
|
285
|
+
apiKey: globalOpts.apiKey,
|
|
286
|
+
baseUrl: globalOpts.baseUrl,
|
|
287
|
+
dryRun: globalOpts.dryRun,
|
|
288
|
+
verbose: globalOpts.verbose
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
printJson(data);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
printError(error);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
groups.command("create").description("Create a new policy group").requiredOption("--json <body>", "Request body as JSON string").action(async (opts) => {
|
|
298
|
+
try {
|
|
299
|
+
const globalOpts = program.opts();
|
|
300
|
+
const body = JSON.parse(opts.json);
|
|
301
|
+
const data = await apiRequest(
|
|
302
|
+
"POST",
|
|
303
|
+
"policy-groups",
|
|
304
|
+
{
|
|
305
|
+
apiKey: globalOpts.apiKey,
|
|
306
|
+
baseUrl: globalOpts.baseUrl,
|
|
307
|
+
dryRun: globalOpts.dryRun,
|
|
308
|
+
verbose: globalOpts.verbose,
|
|
309
|
+
body
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
printJson(data);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
printError(error);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
groups.command("update").description("Update a policy group").requiredOption("--id <groupId>", "Policy group ID").requiredOption("--json <body>", "Request body as JSON string").action(async (opts) => {
|
|
319
|
+
try {
|
|
320
|
+
const globalOpts = program.opts();
|
|
321
|
+
const body = JSON.parse(opts.json);
|
|
322
|
+
const data = await apiRequest(
|
|
323
|
+
"PUT",
|
|
324
|
+
`policy-groups/${opts.id}`,
|
|
325
|
+
{
|
|
326
|
+
apiKey: globalOpts.apiKey,
|
|
327
|
+
baseUrl: globalOpts.baseUrl,
|
|
328
|
+
dryRun: globalOpts.dryRun,
|
|
329
|
+
verbose: globalOpts.verbose,
|
|
330
|
+
body
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
printJson(data);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
printError(error);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
groups.command("delete").description("Delete a policy group").requiredOption("--id <groupId>", "Policy group ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
340
|
+
try {
|
|
341
|
+
const globalOpts = program.opts();
|
|
342
|
+
if (!opts.force) {
|
|
343
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
344
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
345
|
+
const answer = await rl.question(`Delete group ${opts.id}? [y/N] `);
|
|
346
|
+
rl.close();
|
|
347
|
+
if (answer.toLowerCase() !== "y") {
|
|
348
|
+
console.log("Cancelled.");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
await apiRequest(
|
|
353
|
+
"DELETE",
|
|
354
|
+
`policy-groups/${opts.id}`,
|
|
355
|
+
{
|
|
356
|
+
apiKey: globalOpts.apiKey,
|
|
357
|
+
baseUrl: globalOpts.baseUrl,
|
|
358
|
+
dryRun: globalOpts.dryRun,
|
|
359
|
+
verbose: globalOpts.verbose
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
console.log(`\u2713 Group ${opts.id} deleted.`);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
printError(error);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
const abTest = groups.command("ab-test").description("A/B test management");
|
|
369
|
+
abTest.command("start").description("Start an A/B test").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Challenger version ID").requiredOption("--traffic-rate <rate>", "Traffic rate for challenger (1-99)").action(async (opts) => {
|
|
370
|
+
try {
|
|
371
|
+
const globalOpts = program.opts();
|
|
372
|
+
const body = {
|
|
373
|
+
testVersionId: opts.versionId,
|
|
374
|
+
trafficRate: Number(opts.trafficRate)
|
|
375
|
+
};
|
|
376
|
+
const data = await apiRequest(
|
|
377
|
+
"POST",
|
|
378
|
+
`policy-groups/${opts.groupId}/ab-test`,
|
|
379
|
+
{
|
|
380
|
+
apiKey: globalOpts.apiKey,
|
|
381
|
+
baseUrl: globalOpts.baseUrl,
|
|
382
|
+
dryRun: globalOpts.dryRun,
|
|
383
|
+
verbose: globalOpts.verbose,
|
|
384
|
+
body
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
printJson(data);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
printError(error);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
abTest.command("stop").description("Stop an A/B test").requiredOption("--group-id <groupId>", "Policy group ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
394
|
+
try {
|
|
395
|
+
const globalOpts = program.opts();
|
|
396
|
+
if (!opts.force) {
|
|
397
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
398
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
399
|
+
const answer = await rl.question(`Stop A/B test for group ${opts.groupId}? [y/N] `);
|
|
400
|
+
rl.close();
|
|
401
|
+
if (answer.toLowerCase() !== "y") {
|
|
402
|
+
console.log("Cancelled.");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const data = await apiRequest(
|
|
407
|
+
"DELETE",
|
|
408
|
+
`policy-groups/${opts.groupId}/ab-test`,
|
|
409
|
+
{
|
|
410
|
+
apiKey: globalOpts.apiKey,
|
|
411
|
+
baseUrl: globalOpts.baseUrl,
|
|
412
|
+
dryRun: globalOpts.dryRun,
|
|
413
|
+
verbose: globalOpts.verbose
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
printJson(data);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
printError(error);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
abTest.command("adjust").description("Adjust A/B test traffic rate").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--traffic-rate <rate>", "New traffic rate (1-99)").action(async (opts) => {
|
|
423
|
+
try {
|
|
424
|
+
const globalOpts = program.opts();
|
|
425
|
+
const body = {
|
|
426
|
+
trafficRate: Number(opts.trafficRate)
|
|
427
|
+
};
|
|
428
|
+
const data = await apiRequest(
|
|
429
|
+
"PATCH",
|
|
430
|
+
`policy-groups/${opts.groupId}/ab-test/traffic-rate`,
|
|
431
|
+
{
|
|
432
|
+
apiKey: globalOpts.apiKey,
|
|
433
|
+
baseUrl: globalOpts.baseUrl,
|
|
434
|
+
dryRun: globalOpts.dryRun,
|
|
435
|
+
verbose: globalOpts.verbose,
|
|
436
|
+
body
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
printJson(data);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
printError(error);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/commands/versions.ts
|
|
448
|
+
import "commander";
|
|
449
|
+
function registerVersionCommands(program) {
|
|
450
|
+
const versions = program.command("versions").description("Manage policy versions");
|
|
451
|
+
versions.command("list").description("List versions for a policy group").requiredOption("--group-id <groupId>", "Policy group ID").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
452
|
+
try {
|
|
453
|
+
const globalOpts = program.opts();
|
|
454
|
+
const format = globalOpts.format ?? "json";
|
|
455
|
+
const data = await apiRequest(
|
|
456
|
+
"GET",
|
|
457
|
+
`policy-groups/${opts.groupId}/versions`,
|
|
458
|
+
{
|
|
459
|
+
apiKey: globalOpts.apiKey,
|
|
460
|
+
baseUrl: globalOpts.baseUrl,
|
|
461
|
+
dryRun: globalOpts.dryRun,
|
|
462
|
+
verbose: globalOpts.verbose,
|
|
463
|
+
params: { page: opts.page, size: opts.size }
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
if (format === "table") {
|
|
467
|
+
printTable(
|
|
468
|
+
["ID", "v#", "Status", "Commit", "Hash", "Created"],
|
|
469
|
+
data.content.map((v) => [
|
|
470
|
+
v.id,
|
|
471
|
+
`v${v.versionNo}`,
|
|
472
|
+
v.status,
|
|
473
|
+
v.commitMessage ?? "\u2013",
|
|
474
|
+
v.snapshotHash ? v.snapshotHash.substring(0, 8) : "\u2013",
|
|
475
|
+
v.createdAt.substring(0, 10)
|
|
476
|
+
]),
|
|
477
|
+
{ truncate: 24 }
|
|
478
|
+
);
|
|
479
|
+
console.log(`
|
|
480
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
481
|
+
} else {
|
|
482
|
+
printJson(data);
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
printError(error);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
versions.command("get").description("Get a policy version by ID").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").action(async (opts) => {
|
|
490
|
+
try {
|
|
491
|
+
const globalOpts = program.opts();
|
|
492
|
+
const data = await apiRequest(
|
|
493
|
+
"GET",
|
|
494
|
+
`policy-groups/${opts.groupId}/versions/${opts.id}`,
|
|
495
|
+
{
|
|
496
|
+
apiKey: globalOpts.apiKey,
|
|
497
|
+
baseUrl: globalOpts.baseUrl,
|
|
498
|
+
dryRun: globalOpts.dryRun,
|
|
499
|
+
verbose: globalOpts.verbose
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
printJson(data);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
printError(error);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
versions.command("create").description("Create a new draft version").requiredOption("--group-id <groupId>", "Policy group ID").option("--commit-message <message>", "Commit message").option("--effective-from <date>", "Effective from (ISO datetime)").option("--effective-to <date>", "Effective to (ISO datetime)").option("--json <body>", "Full request body as JSON (overrides other options)").action(async (opts) => {
|
|
509
|
+
try {
|
|
510
|
+
const globalOpts = program.opts();
|
|
511
|
+
const body = opts.json ? JSON.parse(opts.json) : buildCreateBody(opts);
|
|
512
|
+
const data = await apiRequest(
|
|
513
|
+
"POST",
|
|
514
|
+
`policy-groups/${opts.groupId}/versions`,
|
|
515
|
+
{
|
|
516
|
+
apiKey: globalOpts.apiKey,
|
|
517
|
+
baseUrl: globalOpts.baseUrl,
|
|
518
|
+
dryRun: globalOpts.dryRun,
|
|
519
|
+
verbose: globalOpts.verbose,
|
|
520
|
+
body
|
|
521
|
+
}
|
|
522
|
+
);
|
|
523
|
+
printJson(data);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
printError(error);
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
versions.command("update").description("Update a draft version metadata").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--commit-message <message>", "Commit message").option("--effective-from <date>", "Effective from (ISO datetime)").option("--effective-to <date>", "Effective to (ISO datetime)").option("--json <body>", "Full request body as JSON (overrides other options)").action(async (opts) => {
|
|
530
|
+
try {
|
|
531
|
+
const globalOpts = program.opts();
|
|
532
|
+
const body = opts.json ? JSON.parse(opts.json) : buildUpdateBody(opts);
|
|
533
|
+
const data = await apiRequest(
|
|
534
|
+
"PUT",
|
|
535
|
+
`policy-groups/${opts.groupId}/versions/${opts.id}`,
|
|
536
|
+
{
|
|
537
|
+
apiKey: globalOpts.apiKey,
|
|
538
|
+
baseUrl: globalOpts.baseUrl,
|
|
539
|
+
dryRun: globalOpts.dryRun,
|
|
540
|
+
verbose: globalOpts.verbose,
|
|
541
|
+
body
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
printJson(data);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
printError(error);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
versions.command("delete").description("Delete a policy version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
551
|
+
try {
|
|
552
|
+
const globalOpts = program.opts();
|
|
553
|
+
if (!opts.force) {
|
|
554
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
555
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
556
|
+
const answer = await rl.question(`Delete version ${opts.id}? [y/N] `);
|
|
557
|
+
rl.close();
|
|
558
|
+
if (answer.toLowerCase() !== "y") {
|
|
559
|
+
console.log("Cancelled.");
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
await apiRequest(
|
|
564
|
+
"DELETE",
|
|
565
|
+
`policy-groups/${opts.groupId}/versions/${opts.id}`,
|
|
566
|
+
{
|
|
567
|
+
apiKey: globalOpts.apiKey,
|
|
568
|
+
baseUrl: globalOpts.baseUrl,
|
|
569
|
+
dryRun: globalOpts.dryRun,
|
|
570
|
+
verbose: globalOpts.verbose
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
console.log(`\u2713 Version ${opts.id} deleted.`);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
printError(error);
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
versions.command("clone").description("Clone (duplicate) a policy version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Source version ID to clone").action(async (opts) => {
|
|
580
|
+
try {
|
|
581
|
+
const globalOpts = program.opts();
|
|
582
|
+
const data = await apiRequest(
|
|
583
|
+
"POST",
|
|
584
|
+
`policy-groups/${opts.groupId}/versions/${opts.id}/clone`,
|
|
585
|
+
{
|
|
586
|
+
apiKey: globalOpts.apiKey,
|
|
587
|
+
baseUrl: globalOpts.baseUrl,
|
|
588
|
+
dryRun: globalOpts.dryRun,
|
|
589
|
+
verbose: globalOpts.verbose
|
|
590
|
+
}
|
|
591
|
+
);
|
|
592
|
+
printJson(data);
|
|
593
|
+
} catch (error) {
|
|
594
|
+
printError(error);
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
function buildCreateBody(opts) {
|
|
600
|
+
const body = {};
|
|
601
|
+
if (opts.commitMessage) body.commitMessage = opts.commitMessage;
|
|
602
|
+
if (opts.effectiveFrom) body.effectiveFrom = opts.effectiveFrom;
|
|
603
|
+
if (opts.effectiveTo) body.effectiveTo = opts.effectiveTo;
|
|
604
|
+
return body;
|
|
605
|
+
}
|
|
606
|
+
function buildUpdateBody(opts) {
|
|
607
|
+
const body = {};
|
|
608
|
+
if (opts.commitMessage) body.commitMessage = opts.commitMessage;
|
|
609
|
+
if (opts.effectiveFrom) body.effectiveFrom = opts.effectiveFrom;
|
|
610
|
+
if (opts.effectiveTo) body.effectiveTo = opts.effectiveTo;
|
|
611
|
+
return body;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/commands/rules.ts
|
|
615
|
+
import "commander";
|
|
616
|
+
function registerRuleCommands(program) {
|
|
617
|
+
const rules = program.command("rules").description("Manage policy rules");
|
|
618
|
+
rules.command("list").description("List rules for a policy version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
619
|
+
try {
|
|
620
|
+
const globalOpts = program.opts();
|
|
621
|
+
const format = globalOpts.format ?? "json";
|
|
622
|
+
const data = await apiRequest(
|
|
623
|
+
"GET",
|
|
624
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/rules`,
|
|
625
|
+
{
|
|
626
|
+
apiKey: globalOpts.apiKey,
|
|
627
|
+
baseUrl: globalOpts.baseUrl,
|
|
628
|
+
dryRun: globalOpts.dryRun,
|
|
629
|
+
verbose: globalOpts.verbose,
|
|
630
|
+
params: { page: opts.page, size: opts.size }
|
|
631
|
+
}
|
|
632
|
+
);
|
|
633
|
+
if (format === "table") {
|
|
634
|
+
printTable(
|
|
635
|
+
["ID", "Name", "Priority", "Conditions", "Actions", "Enabled"],
|
|
636
|
+
data.content.map((r) => [
|
|
637
|
+
r.id,
|
|
638
|
+
r.name,
|
|
639
|
+
String(r.priority),
|
|
640
|
+
String(r.totalConditionCount),
|
|
641
|
+
String(r.totalActionCount),
|
|
642
|
+
r.isEnabled ? "\u2713" : "\u2717"
|
|
643
|
+
]),
|
|
644
|
+
{ truncate: 24 }
|
|
645
|
+
);
|
|
646
|
+
console.log(`
|
|
647
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
648
|
+
} else {
|
|
649
|
+
printJson(data);
|
|
650
|
+
}
|
|
651
|
+
} catch (error) {
|
|
652
|
+
printError(error);
|
|
653
|
+
process.exit(1);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
rules.command("get").description("Get a rule by ID").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").requiredOption("--id <ruleId>", "Rule ID").action(async (opts) => {
|
|
657
|
+
try {
|
|
658
|
+
const globalOpts = program.opts();
|
|
659
|
+
const data = await apiRequest(
|
|
660
|
+
"GET",
|
|
661
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/rules/${opts.id}`,
|
|
662
|
+
{
|
|
663
|
+
apiKey: globalOpts.apiKey,
|
|
664
|
+
baseUrl: globalOpts.baseUrl,
|
|
665
|
+
dryRun: globalOpts.dryRun,
|
|
666
|
+
verbose: globalOpts.verbose
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
printJson(data);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
printError(error);
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
rules.command("create").description("Create a new rule").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").requiredOption("--json <body>", "Request body as JSON string").action(async (opts) => {
|
|
676
|
+
try {
|
|
677
|
+
const globalOpts = program.opts();
|
|
678
|
+
const body = JSON.parse(opts.json);
|
|
679
|
+
const data = await apiRequest(
|
|
680
|
+
"POST",
|
|
681
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/rules`,
|
|
682
|
+
{
|
|
683
|
+
apiKey: globalOpts.apiKey,
|
|
684
|
+
baseUrl: globalOpts.baseUrl,
|
|
685
|
+
dryRun: globalOpts.dryRun,
|
|
686
|
+
verbose: globalOpts.verbose,
|
|
687
|
+
body
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
printJson(data);
|
|
691
|
+
} catch (error) {
|
|
692
|
+
printError(error);
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
rules.command("update").description("Update a rule").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").requiredOption("--id <ruleId>", "Rule ID").requiredOption("--json <body>", "Request body as JSON string").action(async (opts) => {
|
|
697
|
+
try {
|
|
698
|
+
const globalOpts = program.opts();
|
|
699
|
+
const body = JSON.parse(opts.json);
|
|
700
|
+
const data = await apiRequest(
|
|
701
|
+
"PUT",
|
|
702
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/rules/${opts.id}`,
|
|
703
|
+
{
|
|
704
|
+
apiKey: globalOpts.apiKey,
|
|
705
|
+
baseUrl: globalOpts.baseUrl,
|
|
706
|
+
dryRun: globalOpts.dryRun,
|
|
707
|
+
verbose: globalOpts.verbose,
|
|
708
|
+
body
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
printJson(data);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
printError(error);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
rules.command("delete").description("Delete a rule").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").requiredOption("--id <ruleId>", "Rule ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
718
|
+
try {
|
|
719
|
+
const globalOpts = program.opts();
|
|
720
|
+
if (!opts.force) {
|
|
721
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
722
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
723
|
+
const answer = await rl.question(`Delete rule ${opts.id}? [y/N] `);
|
|
724
|
+
rl.close();
|
|
725
|
+
if (answer.toLowerCase() !== "y") {
|
|
726
|
+
console.log("Cancelled.");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
await apiRequest(
|
|
731
|
+
"DELETE",
|
|
732
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/rules/${opts.id}`,
|
|
733
|
+
{
|
|
734
|
+
apiKey: globalOpts.apiKey,
|
|
735
|
+
baseUrl: globalOpts.baseUrl,
|
|
736
|
+
dryRun: globalOpts.dryRun,
|
|
737
|
+
verbose: globalOpts.verbose
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
console.log(`\u2713 Rule ${opts.id} deleted.`);
|
|
741
|
+
} catch (error) {
|
|
742
|
+
printError(error);
|
|
743
|
+
process.exit(1);
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
rules.command("reorder").description("Reorder rules by priority (drag & drop equivalent)").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").requiredOption("--rule-ids <ids>", "Comma-separated rule IDs in desired order").action(async (opts) => {
|
|
747
|
+
try {
|
|
748
|
+
const globalOpts = program.opts();
|
|
749
|
+
const ruleIds = opts.ruleIds.split(",").map((id) => id.trim());
|
|
750
|
+
const body = {
|
|
751
|
+
rules: ruleIds.map((ruleId, index) => ({
|
|
752
|
+
ruleId,
|
|
753
|
+
priority: index
|
|
754
|
+
}))
|
|
755
|
+
};
|
|
756
|
+
await apiRequest(
|
|
757
|
+
"PATCH",
|
|
758
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/rules/reorder`,
|
|
759
|
+
{
|
|
760
|
+
apiKey: globalOpts.apiKey,
|
|
761
|
+
baseUrl: globalOpts.baseUrl,
|
|
762
|
+
dryRun: globalOpts.dryRun,
|
|
763
|
+
verbose: globalOpts.verbose,
|
|
764
|
+
body
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
console.log(`\u2713 ${ruleIds.length} rules reordered.`);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
printError(error);
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
rules.command("toggle").description("Enable or disable a rule").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").requiredOption("--id <ruleId>", "Rule ID").requiredOption("--enabled <boolean>", "true or false").action(async (opts) => {
|
|
774
|
+
try {
|
|
775
|
+
const globalOpts = program.opts();
|
|
776
|
+
const isEnabled = opts.enabled === "true";
|
|
777
|
+
await apiRequest(
|
|
778
|
+
"PATCH",
|
|
779
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/rules/${opts.id}/enabled`,
|
|
780
|
+
{
|
|
781
|
+
apiKey: globalOpts.apiKey,
|
|
782
|
+
baseUrl: globalOpts.baseUrl,
|
|
783
|
+
dryRun: globalOpts.dryRun,
|
|
784
|
+
verbose: globalOpts.verbose,
|
|
785
|
+
body: { isEnabled }
|
|
786
|
+
}
|
|
787
|
+
);
|
|
788
|
+
console.log(`\u2713 Rule ${opts.id} ${isEnabled ? "enabled" : "disabled"}.`);
|
|
789
|
+
} catch (error) {
|
|
790
|
+
printError(error);
|
|
791
|
+
process.exit(1);
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/commands/facts.ts
|
|
797
|
+
import "commander";
|
|
798
|
+
function registerFactCommands(program) {
|
|
799
|
+
const facts = program.command("facts").description("Manage fact definitions (schema)");
|
|
800
|
+
facts.command("list").description("List fact definitions").option("--keyword <keyword>", "Filter by keyword").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
801
|
+
try {
|
|
802
|
+
const globalOpts = program.opts();
|
|
803
|
+
const format = globalOpts.format ?? "json";
|
|
804
|
+
const params = {
|
|
805
|
+
page: opts.page,
|
|
806
|
+
size: opts.size
|
|
807
|
+
};
|
|
808
|
+
if (opts.keyword) params.keyword = opts.keyword;
|
|
809
|
+
const data = await apiRequest(
|
|
810
|
+
"GET",
|
|
811
|
+
"schema/facts",
|
|
812
|
+
{
|
|
813
|
+
apiKey: globalOpts.apiKey,
|
|
814
|
+
baseUrl: globalOpts.baseUrl,
|
|
815
|
+
dryRun: globalOpts.dryRun,
|
|
816
|
+
verbose: globalOpts.verbose,
|
|
817
|
+
params
|
|
818
|
+
}
|
|
819
|
+
);
|
|
820
|
+
if (format === "table") {
|
|
821
|
+
printTable(
|
|
822
|
+
["ID", "Key", "Name", "Type", "System", "Required"],
|
|
823
|
+
data.content.map((f) => [
|
|
824
|
+
f.id,
|
|
825
|
+
f.key,
|
|
826
|
+
f.name,
|
|
827
|
+
f.type,
|
|
828
|
+
f.isSystem ? "\u2713" : "\u2013",
|
|
829
|
+
f.isRequired ? "\u2713" : "\u2013"
|
|
830
|
+
]),
|
|
831
|
+
{ truncate: 28 }
|
|
832
|
+
);
|
|
833
|
+
console.log(`
|
|
834
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
835
|
+
} else {
|
|
836
|
+
printJson(data);
|
|
837
|
+
}
|
|
838
|
+
} catch (error) {
|
|
839
|
+
printError(error);
|
|
840
|
+
process.exit(1);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
facts.command("create").description("Create a new fact definition").option("--key <key>", "Fact key (lowercase, underscores)").option("--name <name>", "Display name").option("--type <type>", "Value type: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER").option("--description <desc>", "Description").option("--required", "Mark as required", false).option("--json <body>", "Full request body as JSON (overrides other options)").action(async (opts) => {
|
|
844
|
+
try {
|
|
845
|
+
const globalOpts = program.opts();
|
|
846
|
+
const body = opts.json ? JSON.parse(opts.json) : buildCreateBody2(opts);
|
|
847
|
+
const data = await apiRequest(
|
|
848
|
+
"POST",
|
|
849
|
+
"schema/facts",
|
|
850
|
+
{
|
|
851
|
+
apiKey: globalOpts.apiKey,
|
|
852
|
+
baseUrl: globalOpts.baseUrl,
|
|
853
|
+
dryRun: globalOpts.dryRun,
|
|
854
|
+
verbose: globalOpts.verbose,
|
|
855
|
+
body
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
printJson(data);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
printError(error);
|
|
861
|
+
process.exit(1);
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
facts.command("update").description("Update a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--name <name>", "Display name").option("--description <desc>", "Description").option("--required", "Mark as required").option("--no-required", "Mark as not required").option("--json <body>", "Full request body as JSON (overrides other options)").action(async (opts) => {
|
|
865
|
+
try {
|
|
866
|
+
const globalOpts = program.opts();
|
|
867
|
+
const body = opts.json ? JSON.parse(opts.json) : buildUpdateBody2(opts);
|
|
868
|
+
const data = await apiRequest(
|
|
869
|
+
"PUT",
|
|
870
|
+
`schema/facts/${opts.id}`,
|
|
871
|
+
{
|
|
872
|
+
apiKey: globalOpts.apiKey,
|
|
873
|
+
baseUrl: globalOpts.baseUrl,
|
|
874
|
+
dryRun: globalOpts.dryRun,
|
|
875
|
+
verbose: globalOpts.verbose,
|
|
876
|
+
body
|
|
877
|
+
}
|
|
878
|
+
);
|
|
879
|
+
printJson(data);
|
|
880
|
+
} catch (error) {
|
|
881
|
+
printError(error);
|
|
882
|
+
process.exit(1);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
facts.command("delete").description("Delete a fact definition").requiredOption("--id <factId>", "Fact definition ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
886
|
+
try {
|
|
887
|
+
const globalOpts = program.opts();
|
|
888
|
+
if (!opts.force) {
|
|
889
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
890
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
891
|
+
const answer = await rl.question(`Delete fact ${opts.id}? [y/N] `);
|
|
892
|
+
rl.close();
|
|
893
|
+
if (answer.toLowerCase() !== "y") {
|
|
894
|
+
console.log("Cancelled.");
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
await apiRequest(
|
|
899
|
+
"DELETE",
|
|
900
|
+
`schema/facts/${opts.id}`,
|
|
901
|
+
{
|
|
902
|
+
apiKey: globalOpts.apiKey,
|
|
903
|
+
baseUrl: globalOpts.baseUrl,
|
|
904
|
+
dryRun: globalOpts.dryRun,
|
|
905
|
+
verbose: globalOpts.verbose
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
console.log(`\u2713 Fact ${opts.id} deleted.`);
|
|
909
|
+
} catch (error) {
|
|
910
|
+
printError(error);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
facts.command("action-metadata").description("Get action runtime fact metadata").action(async () => {
|
|
915
|
+
try {
|
|
916
|
+
const globalOpts = program.opts();
|
|
917
|
+
const data = await apiRequest(
|
|
918
|
+
"GET",
|
|
919
|
+
"schema/action-metadata",
|
|
920
|
+
{
|
|
921
|
+
apiKey: globalOpts.apiKey,
|
|
922
|
+
baseUrl: globalOpts.baseUrl,
|
|
923
|
+
dryRun: globalOpts.dryRun,
|
|
924
|
+
verbose: globalOpts.verbose
|
|
925
|
+
}
|
|
926
|
+
);
|
|
927
|
+
printJson(data);
|
|
928
|
+
} catch (error) {
|
|
929
|
+
printError(error);
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
function buildCreateBody2(opts) {
|
|
935
|
+
if (!opts.key || !opts.name || !opts.type) {
|
|
936
|
+
throw new Error("--key, --name, and --type are required (or use --json).");
|
|
937
|
+
}
|
|
938
|
+
const body = {
|
|
939
|
+
key: opts.key,
|
|
940
|
+
name: opts.name,
|
|
941
|
+
type: opts.type,
|
|
942
|
+
isRequired: opts.required === true
|
|
943
|
+
};
|
|
944
|
+
if (opts.description) body.description = opts.description;
|
|
945
|
+
return body;
|
|
946
|
+
}
|
|
947
|
+
function buildUpdateBody2(opts) {
|
|
948
|
+
const body = {};
|
|
949
|
+
if (opts.name) body.name = opts.name;
|
|
950
|
+
if (opts.description !== void 0) body.description = opts.description;
|
|
951
|
+
if (typeof opts.required === "boolean") body.isRequired = opts.required;
|
|
952
|
+
return body;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// src/commands/deploy.ts
|
|
956
|
+
import "commander";
|
|
957
|
+
function registerDeployCommands(program) {
|
|
958
|
+
const deploy = program.command("deploy").description("Deployment lifecycle and history");
|
|
959
|
+
deploy.command("publish").description("Publish a DRAFT version (DRAFT \u2192 ACTIVE)").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Version ID to publish").option("--memo <memo>", "Deployment memo").action(async (opts) => {
|
|
960
|
+
try {
|
|
961
|
+
const globalOpts = program.opts();
|
|
962
|
+
await apiRequest(
|
|
963
|
+
"POST",
|
|
964
|
+
`policy-groups/${opts.groupId}/versions/${opts.versionId}/publish`,
|
|
965
|
+
{
|
|
966
|
+
apiKey: globalOpts.apiKey,
|
|
967
|
+
baseUrl: globalOpts.baseUrl,
|
|
968
|
+
dryRun: globalOpts.dryRun,
|
|
969
|
+
verbose: globalOpts.verbose,
|
|
970
|
+
body: { memo: opts.memo ?? "" }
|
|
971
|
+
}
|
|
972
|
+
);
|
|
973
|
+
console.log(`\u2713 Version ${opts.versionId} published.`);
|
|
974
|
+
} catch (error) {
|
|
975
|
+
printError(error);
|
|
976
|
+
process.exit(1);
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
deploy.command("live").description("Deploy an ACTIVE version to live traffic").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Version ID to deploy").option("--memo <memo>", "Deployment memo").action(async (opts) => {
|
|
980
|
+
try {
|
|
981
|
+
const globalOpts = program.opts();
|
|
982
|
+
await apiRequest(
|
|
983
|
+
"POST",
|
|
984
|
+
`policy-groups/${opts.groupId}/deploy`,
|
|
985
|
+
{
|
|
986
|
+
apiKey: globalOpts.apiKey,
|
|
987
|
+
baseUrl: globalOpts.baseUrl,
|
|
988
|
+
dryRun: globalOpts.dryRun,
|
|
989
|
+
verbose: globalOpts.verbose,
|
|
990
|
+
body: { versionId: opts.versionId, memo: opts.memo ?? "" }
|
|
991
|
+
}
|
|
992
|
+
);
|
|
993
|
+
console.log(`\u2713 Version ${opts.versionId} deployed to live.`);
|
|
994
|
+
} catch (error) {
|
|
995
|
+
printError(error);
|
|
996
|
+
process.exit(1);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
deploy.command("rollback").description("Rollback to the previous deployed version").requiredOption("--group-id <groupId>", "Policy group ID").option("--memo <memo>", "Rollback reason").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
1000
|
+
try {
|
|
1001
|
+
const globalOpts = program.opts();
|
|
1002
|
+
if (!opts.force) {
|
|
1003
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
1004
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1005
|
+
const answer = await rl.question(`Rollback group ${opts.groupId}? [y/N] `);
|
|
1006
|
+
rl.close();
|
|
1007
|
+
if (answer.toLowerCase() !== "y") {
|
|
1008
|
+
console.log("Cancelled.");
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
await apiRequest(
|
|
1013
|
+
"POST",
|
|
1014
|
+
`policy-groups/${opts.groupId}/rollback`,
|
|
1015
|
+
{
|
|
1016
|
+
apiKey: globalOpts.apiKey,
|
|
1017
|
+
baseUrl: globalOpts.baseUrl,
|
|
1018
|
+
dryRun: globalOpts.dryRun,
|
|
1019
|
+
verbose: globalOpts.verbose,
|
|
1020
|
+
body: { memo: opts.memo ?? "" }
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
1023
|
+
console.log(`\u2713 Group ${opts.groupId} rolled back.`);
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
printError(error);
|
|
1026
|
+
process.exit(1);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
deploy.command("undeploy").description("Remove the live version from a group").requiredOption("--group-id <groupId>", "Policy group ID").option("--memo <memo>", "Undeploy reason").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
1030
|
+
try {
|
|
1031
|
+
const globalOpts = program.opts();
|
|
1032
|
+
if (!opts.force) {
|
|
1033
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
1034
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1035
|
+
const answer = await rl.question(`Undeploy group ${opts.groupId}? [y/N] `);
|
|
1036
|
+
rl.close();
|
|
1037
|
+
if (answer.toLowerCase() !== "y") {
|
|
1038
|
+
console.log("Cancelled.");
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
await apiRequest(
|
|
1043
|
+
"POST",
|
|
1044
|
+
`policy-groups/${opts.groupId}/undeploy`,
|
|
1045
|
+
{
|
|
1046
|
+
apiKey: globalOpts.apiKey,
|
|
1047
|
+
baseUrl: globalOpts.baseUrl,
|
|
1048
|
+
dryRun: globalOpts.dryRun,
|
|
1049
|
+
verbose: globalOpts.verbose,
|
|
1050
|
+
body: { memo: opts.memo ?? "" }
|
|
1051
|
+
}
|
|
1052
|
+
);
|
|
1053
|
+
console.log(`\u2713 Group ${opts.groupId} undeployed.`);
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
printError(error);
|
|
1056
|
+
process.exit(1);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", "Filter by types (comma-separated: PUBLISH,DEPLOY,ROLLBACK,UNDEPLOY)").option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
1060
|
+
try {
|
|
1061
|
+
const globalOpts = program.opts();
|
|
1062
|
+
const format = globalOpts.format ?? "json";
|
|
1063
|
+
const params = {
|
|
1064
|
+
page: opts.page,
|
|
1065
|
+
size: opts.size
|
|
1066
|
+
};
|
|
1067
|
+
if (opts.groupId) params.groupId = opts.groupId;
|
|
1068
|
+
if (opts.types) params.types = opts.types;
|
|
1069
|
+
if (opts.startDate) params.startDate = opts.startDate;
|
|
1070
|
+
if (opts.endDate) params.endDate = opts.endDate;
|
|
1071
|
+
const data = await apiRequest(
|
|
1072
|
+
"GET",
|
|
1073
|
+
"deployments",
|
|
1074
|
+
{
|
|
1075
|
+
apiKey: globalOpts.apiKey,
|
|
1076
|
+
baseUrl: globalOpts.baseUrl,
|
|
1077
|
+
dryRun: globalOpts.dryRun,
|
|
1078
|
+
verbose: globalOpts.verbose,
|
|
1079
|
+
params
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
if (format === "table") {
|
|
1083
|
+
printTable(
|
|
1084
|
+
["ID", "Type", "Group", "Version", "By", "At"],
|
|
1085
|
+
data.content.map((d) => [
|
|
1086
|
+
d.id.substring(0, 8),
|
|
1087
|
+
d.deploymentType,
|
|
1088
|
+
d.policyGroupName,
|
|
1089
|
+
d.versionNo != null ? `v${d.versionNo}` : "\u2013",
|
|
1090
|
+
d.deployedByName,
|
|
1091
|
+
d.deployedAt.substring(0, 16)
|
|
1092
|
+
]),
|
|
1093
|
+
{ truncate: 20 }
|
|
1094
|
+
);
|
|
1095
|
+
console.log(`
|
|
1096
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
1097
|
+
} else {
|
|
1098
|
+
printJson(data);
|
|
1099
|
+
}
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
printError(error);
|
|
1102
|
+
process.exit(1);
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
deploy.command("detail").description("Get deployment detail").requiredOption("--id <deploymentId>", "Deployment ID").action(async (opts) => {
|
|
1106
|
+
try {
|
|
1107
|
+
const globalOpts = program.opts();
|
|
1108
|
+
const data = await apiRequest(
|
|
1109
|
+
"GET",
|
|
1110
|
+
`deployments/${opts.id}`,
|
|
1111
|
+
{
|
|
1112
|
+
apiKey: globalOpts.apiKey,
|
|
1113
|
+
baseUrl: globalOpts.baseUrl,
|
|
1114
|
+
dryRun: globalOpts.dryRun,
|
|
1115
|
+
verbose: globalOpts.verbose
|
|
1116
|
+
}
|
|
1117
|
+
);
|
|
1118
|
+
printJson(data);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
printError(error);
|
|
1121
|
+
process.exit(1);
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
deploy.command("overview").description("Show deployment status overview for all groups").action(async () => {
|
|
1125
|
+
try {
|
|
1126
|
+
const globalOpts = program.opts();
|
|
1127
|
+
const format = globalOpts.format ?? "json";
|
|
1128
|
+
const data = await apiRequest(
|
|
1129
|
+
"GET",
|
|
1130
|
+
"deployments/overview",
|
|
1131
|
+
{
|
|
1132
|
+
apiKey: globalOpts.apiKey,
|
|
1133
|
+
baseUrl: globalOpts.baseUrl,
|
|
1134
|
+
dryRun: globalOpts.dryRun,
|
|
1135
|
+
verbose: globalOpts.verbose
|
|
1136
|
+
}
|
|
1137
|
+
);
|
|
1138
|
+
if (format === "table") {
|
|
1139
|
+
printTable(
|
|
1140
|
+
["Group", "Name", "Status", "Current Version", "Last Deploy"],
|
|
1141
|
+
data.map((d) => [
|
|
1142
|
+
d.groupId.substring(0, 8),
|
|
1143
|
+
d.groupName,
|
|
1144
|
+
d.groupStatus,
|
|
1145
|
+
d.currentVersionName ?? "\u2013",
|
|
1146
|
+
d.lastDeployedAt?.substring(0, 16) ?? "\u2013"
|
|
1147
|
+
])
|
|
1148
|
+
);
|
|
1149
|
+
} else {
|
|
1150
|
+
printJson(data);
|
|
1151
|
+
}
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
printError(error);
|
|
1154
|
+
process.exit(1);
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
deploy.command("deployable").description("List deployable (ACTIVE) versions for a group").requiredOption("--group-id <groupId>", "Policy group ID").action(async (opts) => {
|
|
1158
|
+
try {
|
|
1159
|
+
const globalOpts = program.opts();
|
|
1160
|
+
const data = await apiRequest(
|
|
1161
|
+
"GET",
|
|
1162
|
+
`deployments/groups/${opts.groupId}/deployable-versions`,
|
|
1163
|
+
{
|
|
1164
|
+
apiKey: globalOpts.apiKey,
|
|
1165
|
+
baseUrl: globalOpts.baseUrl,
|
|
1166
|
+
dryRun: globalOpts.dryRun,
|
|
1167
|
+
verbose: globalOpts.verbose
|
|
1168
|
+
}
|
|
1169
|
+
);
|
|
1170
|
+
printJson(data);
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
printError(error);
|
|
1173
|
+
process.exit(1);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
deploy.command("diff").description("Compare snapshot diff between two versions").requiredOption("--base <versionId>", "Base version ID").requiredOption("--target <versionId>", "Target version ID").action(async (opts) => {
|
|
1177
|
+
try {
|
|
1178
|
+
const globalOpts = program.opts();
|
|
1179
|
+
const data = await apiRequest(
|
|
1180
|
+
"GET",
|
|
1181
|
+
"deployments/diff",
|
|
1182
|
+
{
|
|
1183
|
+
apiKey: globalOpts.apiKey,
|
|
1184
|
+
baseUrl: globalOpts.baseUrl,
|
|
1185
|
+
dryRun: globalOpts.dryRun,
|
|
1186
|
+
verbose: globalOpts.verbose,
|
|
1187
|
+
params: {
|
|
1188
|
+
baseVersionId: opts.base,
|
|
1189
|
+
targetVersionId: opts.target
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
printJson(data);
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
printError(error);
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/commands/analytics.ts
|
|
1202
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1203
|
+
import "commander";
|
|
1204
|
+
function registerAnalyticsCommands(program) {
|
|
1205
|
+
const analytics = program.command("analytics").description("Dry run, simulation, and requirements");
|
|
1206
|
+
analytics.command("dry-run").description("Execute a single dry run against a version").requiredOption("--version-id <versionId>", "Policy version ID").option("--json <body>", "Request body as JSON string").option("--file <path>", "Read request body from a JSON file").option("--debug", "Include debug traces", false).option("--mock", "Mock external calls", false).action(async (opts) => {
|
|
1207
|
+
try {
|
|
1208
|
+
const globalOpts = program.opts();
|
|
1209
|
+
const body = resolveBody(opts);
|
|
1210
|
+
if (!body.facts) {
|
|
1211
|
+
throw new Error('Request body must include "facts". Use --json or --file.');
|
|
1212
|
+
}
|
|
1213
|
+
body.includeDebugInfo = opts.debug;
|
|
1214
|
+
body.mockExternalCalls = opts.mock;
|
|
1215
|
+
const data = await apiRequest(
|
|
1216
|
+
"POST",
|
|
1217
|
+
`analytics/dry-run/versions/${opts.versionId}`,
|
|
1218
|
+
{
|
|
1219
|
+
apiKey: globalOpts.apiKey,
|
|
1220
|
+
baseUrl: globalOpts.baseUrl,
|
|
1221
|
+
dryRun: globalOpts.dryRun,
|
|
1222
|
+
verbose: globalOpts.verbose,
|
|
1223
|
+
body
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
printJson(data);
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
printError(error);
|
|
1229
|
+
process.exit(1);
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
analytics.command("dry-run-compare").description("Compare dry run results between two versions").option("--json <body>", "Request body as JSON string").option("--file <path>", "Read request body from a JSON file").action(async (opts) => {
|
|
1233
|
+
try {
|
|
1234
|
+
const globalOpts = program.opts();
|
|
1235
|
+
const body = resolveBody(opts);
|
|
1236
|
+
if (!body.facts || !body.versionIdA || !body.versionIdB) {
|
|
1237
|
+
throw new Error('Request body must include "facts", "versionIdA", "versionIdB". Use --json or --file.');
|
|
1238
|
+
}
|
|
1239
|
+
const data = await apiRequest(
|
|
1240
|
+
"POST",
|
|
1241
|
+
"analytics/dry-run/compare",
|
|
1242
|
+
{
|
|
1243
|
+
apiKey: globalOpts.apiKey,
|
|
1244
|
+
baseUrl: globalOpts.baseUrl,
|
|
1245
|
+
dryRun: globalOpts.dryRun,
|
|
1246
|
+
verbose: globalOpts.verbose,
|
|
1247
|
+
body
|
|
1248
|
+
}
|
|
1249
|
+
);
|
|
1250
|
+
printJson(data);
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
printError(error);
|
|
1253
|
+
process.exit(1);
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
analytics.command("requirements").description("Analyze required input facts for a version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").action(async (opts) => {
|
|
1257
|
+
try {
|
|
1258
|
+
const globalOpts = program.opts();
|
|
1259
|
+
const format = globalOpts.format ?? "json";
|
|
1260
|
+
const data = await apiRequest(
|
|
1261
|
+
"GET",
|
|
1262
|
+
`analytics/groups/${opts.groupId}/versions/${opts.versionId}/requirements`,
|
|
1263
|
+
{
|
|
1264
|
+
apiKey: globalOpts.apiKey,
|
|
1265
|
+
baseUrl: globalOpts.baseUrl,
|
|
1266
|
+
dryRun: globalOpts.dryRun,
|
|
1267
|
+
verbose: globalOpts.verbose
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
if (format === "table") {
|
|
1271
|
+
printTable(
|
|
1272
|
+
["Key", "Type", "Name", "Required", "Used By"],
|
|
1273
|
+
data.requiredFacts.map((f) => [
|
|
1274
|
+
f.key,
|
|
1275
|
+
f.type ?? "\u2013",
|
|
1276
|
+
f.displayName ?? "\u2013",
|
|
1277
|
+
f.required ? "\u2713" : "\u2013",
|
|
1278
|
+
f.usedBy.join(", ") || "\u2013"
|
|
1279
|
+
])
|
|
1280
|
+
);
|
|
1281
|
+
console.log("\nExample request:");
|
|
1282
|
+
console.log(JSON.stringify(data.exampleRequest, null, 2));
|
|
1283
|
+
} else {
|
|
1284
|
+
printJson(data);
|
|
1285
|
+
}
|
|
1286
|
+
} catch (error) {
|
|
1287
|
+
printError(error);
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
const sim = analytics.command("simulation").description("Manage batch simulations");
|
|
1292
|
+
sim.command("start").description("Start a new batch simulation").requiredOption("--json <body>", "Simulation request body as JSON").option("--file <path>", "Read request body from a JSON file").action(async (opts) => {
|
|
1293
|
+
try {
|
|
1294
|
+
const globalOpts = program.opts();
|
|
1295
|
+
const body = resolveBody(opts);
|
|
1296
|
+
const data = await apiRequest(
|
|
1297
|
+
"POST",
|
|
1298
|
+
"analytics/simulations",
|
|
1299
|
+
{
|
|
1300
|
+
apiKey: globalOpts.apiKey,
|
|
1301
|
+
baseUrl: globalOpts.baseUrl,
|
|
1302
|
+
dryRun: globalOpts.dryRun,
|
|
1303
|
+
verbose: globalOpts.verbose,
|
|
1304
|
+
body
|
|
1305
|
+
}
|
|
1306
|
+
);
|
|
1307
|
+
console.log(`\u2713 Simulation started: ${data.simulationId} (${data.status})`);
|
|
1308
|
+
printJson(data);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
printError(error);
|
|
1311
|
+
process.exit(1);
|
|
1312
|
+
}
|
|
1313
|
+
});
|
|
1314
|
+
sim.command("status").description("Get simulation status and results").requiredOption("--id <simulationId>", "Simulation ID").action(async (opts) => {
|
|
1315
|
+
try {
|
|
1316
|
+
const globalOpts = program.opts();
|
|
1317
|
+
const data = await apiRequest(
|
|
1318
|
+
"GET",
|
|
1319
|
+
`analytics/simulations/${opts.id}`,
|
|
1320
|
+
{
|
|
1321
|
+
apiKey: globalOpts.apiKey,
|
|
1322
|
+
baseUrl: globalOpts.baseUrl,
|
|
1323
|
+
dryRun: globalOpts.dryRun,
|
|
1324
|
+
verbose: globalOpts.verbose
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
printJson(data);
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
printError(error);
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
sim.command("list").description("List simulation history").option("--status <status>", "Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED)").option("--from <date>", "Start date (yyyy-MM-dd)").option("--to <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
1334
|
+
try {
|
|
1335
|
+
const globalOpts = program.opts();
|
|
1336
|
+
const format = globalOpts.format ?? "json";
|
|
1337
|
+
const params = {
|
|
1338
|
+
page: opts.page,
|
|
1339
|
+
size: opts.size
|
|
1340
|
+
};
|
|
1341
|
+
if (opts.status) params.status = opts.status;
|
|
1342
|
+
if (opts.from) params.from = opts.from;
|
|
1343
|
+
if (opts.to) params.to = opts.to;
|
|
1344
|
+
const data = await apiRequest(
|
|
1345
|
+
"GET",
|
|
1346
|
+
"analytics/simulations",
|
|
1347
|
+
{
|
|
1348
|
+
apiKey: globalOpts.apiKey,
|
|
1349
|
+
baseUrl: globalOpts.baseUrl,
|
|
1350
|
+
dryRun: globalOpts.dryRun,
|
|
1351
|
+
verbose: globalOpts.verbose,
|
|
1352
|
+
params
|
|
1353
|
+
}
|
|
1354
|
+
);
|
|
1355
|
+
if (format === "table") {
|
|
1356
|
+
printTable(
|
|
1357
|
+
["ID", "Group", "Target", "Status", "Match", "Records", "Created"],
|
|
1358
|
+
data.content.map((s) => [
|
|
1359
|
+
s.simulationId.substring(0, 8),
|
|
1360
|
+
s.policyGroupName,
|
|
1361
|
+
s.targetVersionName ?? "\u2013",
|
|
1362
|
+
s.status,
|
|
1363
|
+
`${(s.matchRate * 100).toFixed(1)}%`,
|
|
1364
|
+
String(s.totalRecords),
|
|
1365
|
+
s.createdAt.substring(0, 16)
|
|
1366
|
+
]),
|
|
1367
|
+
{ truncate: 20 }
|
|
1368
|
+
);
|
|
1369
|
+
console.log(`
|
|
1370
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
1371
|
+
} else {
|
|
1372
|
+
printJson(data);
|
|
1373
|
+
}
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
printError(error);
|
|
1376
|
+
process.exit(1);
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
sim.command("cancel").description("Cancel a running simulation").requiredOption("--id <simulationId>", "Simulation ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
1380
|
+
try {
|
|
1381
|
+
const globalOpts = program.opts();
|
|
1382
|
+
if (!opts.force) {
|
|
1383
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
1384
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1385
|
+
const answer = await rl.question(`Cancel simulation ${opts.id}? [y/N] `);
|
|
1386
|
+
rl.close();
|
|
1387
|
+
if (answer.toLowerCase() !== "y") {
|
|
1388
|
+
console.log("Cancelled.");
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
await apiRequest(
|
|
1393
|
+
"DELETE",
|
|
1394
|
+
`analytics/simulations/${opts.id}`,
|
|
1395
|
+
{
|
|
1396
|
+
apiKey: globalOpts.apiKey,
|
|
1397
|
+
baseUrl: globalOpts.baseUrl,
|
|
1398
|
+
dryRun: globalOpts.dryRun,
|
|
1399
|
+
verbose: globalOpts.verbose
|
|
1400
|
+
}
|
|
1401
|
+
);
|
|
1402
|
+
console.log(`\u2713 Simulation ${opts.id} cancelled.`);
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
printError(error);
|
|
1405
|
+
process.exit(1);
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
sim.command("export").description("Export simulation results").requiredOption("--id <simulationId>", "Simulation ID").option("--format <fmt>", "Export format: csv or json", "json").option("--output <path>", "Output file path").action(async (opts) => {
|
|
1409
|
+
try {
|
|
1410
|
+
const globalOpts = program.opts();
|
|
1411
|
+
const exportFormat = opts.format === "csv" ? "csv" : "json";
|
|
1412
|
+
const response = await apiRequest(
|
|
1413
|
+
"GET",
|
|
1414
|
+
`analytics/simulations/${opts.id}/export`,
|
|
1415
|
+
{
|
|
1416
|
+
apiKey: globalOpts.apiKey,
|
|
1417
|
+
baseUrl: globalOpts.baseUrl,
|
|
1418
|
+
dryRun: globalOpts.dryRun,
|
|
1419
|
+
verbose: globalOpts.verbose,
|
|
1420
|
+
params: { format: exportFormat }
|
|
1421
|
+
}
|
|
1422
|
+
);
|
|
1423
|
+
if (opts.output) {
|
|
1424
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
1425
|
+
const text = typeof response === "string" ? response : JSON.stringify(response, null, 2);
|
|
1426
|
+
writeFileSync2(opts.output, text, "utf-8");
|
|
1427
|
+
console.log(`\u2713 Exported to ${opts.output}`);
|
|
1428
|
+
} else {
|
|
1429
|
+
printJson(response);
|
|
1430
|
+
}
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
printError(error);
|
|
1433
|
+
process.exit(1);
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
function resolveBody(opts) {
|
|
1438
|
+
if (opts.file) {
|
|
1439
|
+
const raw = readFileSync2(opts.file, "utf-8");
|
|
1440
|
+
return JSON.parse(raw);
|
|
1441
|
+
}
|
|
1442
|
+
if (opts.json) {
|
|
1443
|
+
return JSON.parse(opts.json);
|
|
1444
|
+
}
|
|
1445
|
+
return {};
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/commands/history.ts
|
|
1449
|
+
import "commander";
|
|
1450
|
+
function registerHistoryCommands(program) {
|
|
1451
|
+
const history = program.command("history").description("Execution history");
|
|
1452
|
+
history.command("list").description("List execution history").option("--trace-id <traceId>", "Filter by trace ID").option("--group-id <groupId>", "Filter by policy group").option("--version-id <versionId>", "Filter by version").option("--status <status>", "Filter by status (SUCCESS, NO_MATCH, ERROR, TIMEOUT)").option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
1453
|
+
try {
|
|
1454
|
+
const globalOpts = program.opts();
|
|
1455
|
+
const format = globalOpts.format ?? "json";
|
|
1456
|
+
const params = {
|
|
1457
|
+
page: opts.page,
|
|
1458
|
+
size: opts.size
|
|
1459
|
+
};
|
|
1460
|
+
if (opts.traceId) params.traceId = opts.traceId;
|
|
1461
|
+
if (opts.groupId) params.policyGroupId = opts.groupId;
|
|
1462
|
+
if (opts.versionId) params.versionId = opts.versionId;
|
|
1463
|
+
if (opts.status) params.status = opts.status;
|
|
1464
|
+
if (opts.startDate) params.startDate = opts.startDate;
|
|
1465
|
+
if (opts.endDate) params.endDate = opts.endDate;
|
|
1466
|
+
const data = await apiRequest(
|
|
1467
|
+
"GET",
|
|
1468
|
+
"execution/history",
|
|
1469
|
+
{
|
|
1470
|
+
apiKey: globalOpts.apiKey,
|
|
1471
|
+
baseUrl: globalOpts.baseUrl,
|
|
1472
|
+
dryRun: globalOpts.dryRun,
|
|
1473
|
+
verbose: globalOpts.verbose,
|
|
1474
|
+
params
|
|
1475
|
+
}
|
|
1476
|
+
);
|
|
1477
|
+
if (format === "table") {
|
|
1478
|
+
printTable(
|
|
1479
|
+
["Trace", "Group", "Version", "Status", "Matched", "Latency", "At"],
|
|
1480
|
+
data.content.map((h) => [
|
|
1481
|
+
h.traceId.substring(0, 12),
|
|
1482
|
+
h.policyGroupName ?? "\u2013",
|
|
1483
|
+
h.policyVersionNo != null ? `v${h.policyVersionNo}` : "\u2013",
|
|
1484
|
+
h.status,
|
|
1485
|
+
h.isMatched ? "\u2713" : "\u2717",
|
|
1486
|
+
`${h.latencyMs}ms`,
|
|
1487
|
+
h.createdAt.substring(0, 16)
|
|
1488
|
+
]),
|
|
1489
|
+
{ truncate: 20 }
|
|
1490
|
+
);
|
|
1491
|
+
console.log(`
|
|
1492
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
1493
|
+
} else {
|
|
1494
|
+
printJson(data);
|
|
1495
|
+
}
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
printError(error);
|
|
1498
|
+
process.exit(1);
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
history.command("get").description("Get execution detail").requiredOption("--id <executionId>", "Execution history ID").action(async (opts) => {
|
|
1502
|
+
try {
|
|
1503
|
+
const globalOpts = program.opts();
|
|
1504
|
+
const data = await apiRequest(
|
|
1505
|
+
"GET",
|
|
1506
|
+
`execution/history/${opts.id}`,
|
|
1507
|
+
{
|
|
1508
|
+
apiKey: globalOpts.apiKey,
|
|
1509
|
+
baseUrl: globalOpts.baseUrl,
|
|
1510
|
+
dryRun: globalOpts.dryRun,
|
|
1511
|
+
verbose: globalOpts.verbose
|
|
1512
|
+
}
|
|
1513
|
+
);
|
|
1514
|
+
printJson(data);
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
printError(error);
|
|
1517
|
+
process.exit(1);
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
history.command("stats").description("Get execution statistics").option("--group-id <groupId>", "Filter by policy group").option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").action(async (opts) => {
|
|
1521
|
+
try {
|
|
1522
|
+
const globalOpts = program.opts();
|
|
1523
|
+
const format = globalOpts.format ?? "json";
|
|
1524
|
+
const params = {};
|
|
1525
|
+
if (opts.groupId) params.policyGroupId = opts.groupId;
|
|
1526
|
+
if (opts.startDate) params.startDate = opts.startDate;
|
|
1527
|
+
if (opts.endDate) params.endDate = opts.endDate;
|
|
1528
|
+
const data = await apiRequest(
|
|
1529
|
+
"GET",
|
|
1530
|
+
"execution/history/stats",
|
|
1531
|
+
{
|
|
1532
|
+
apiKey: globalOpts.apiKey,
|
|
1533
|
+
baseUrl: globalOpts.baseUrl,
|
|
1534
|
+
dryRun: globalOpts.dryRun,
|
|
1535
|
+
verbose: globalOpts.verbose,
|
|
1536
|
+
params
|
|
1537
|
+
}
|
|
1538
|
+
);
|
|
1539
|
+
if (format === "table") {
|
|
1540
|
+
printTable(
|
|
1541
|
+
["Total", "Success", "No Match", "Failures", "Success Rate", "Avg Latency"],
|
|
1542
|
+
[[
|
|
1543
|
+
String(data.totalExecutions),
|
|
1544
|
+
String(data.successCount),
|
|
1545
|
+
String(data.noMatchCount),
|
|
1546
|
+
String(data.failureCount),
|
|
1547
|
+
`${data.successRate}%`,
|
|
1548
|
+
`${data.avgLatencyMs}ms`
|
|
1549
|
+
]]
|
|
1550
|
+
);
|
|
1551
|
+
} else {
|
|
1552
|
+
printJson(data);
|
|
1553
|
+
}
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
printError(error);
|
|
1556
|
+
process.exit(1);
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// src/commands/integrations.ts
|
|
1562
|
+
import "commander";
|
|
1563
|
+
function registerIntegrationCommands(program) {
|
|
1564
|
+
const integrations = program.command("integrations").description("Manage external integrations");
|
|
1565
|
+
integrations.command("list").description("List integrations").option("--type <type>", "Filter by type (COUPON, POINT, NOTIFICATION, CRM, MESSENGER, WEBHOOK)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
1566
|
+
try {
|
|
1567
|
+
const globalOpts = program.opts();
|
|
1568
|
+
const format = globalOpts.format ?? "json";
|
|
1569
|
+
const params = {
|
|
1570
|
+
page: opts.page,
|
|
1571
|
+
size: opts.size
|
|
1572
|
+
};
|
|
1573
|
+
if (opts.type) params.type = opts.type;
|
|
1574
|
+
const data = await apiRequest(
|
|
1575
|
+
"GET",
|
|
1576
|
+
"integrations",
|
|
1577
|
+
{
|
|
1578
|
+
apiKey: globalOpts.apiKey,
|
|
1579
|
+
baseUrl: globalOpts.baseUrl,
|
|
1580
|
+
dryRun: globalOpts.dryRun,
|
|
1581
|
+
verbose: globalOpts.verbose,
|
|
1582
|
+
params
|
|
1583
|
+
}
|
|
1584
|
+
);
|
|
1585
|
+
if (format === "table") {
|
|
1586
|
+
printTable(
|
|
1587
|
+
["ID", "Name", "Type", "URL", "Active"],
|
|
1588
|
+
data.content.map((i) => [
|
|
1589
|
+
i.id.substring(0, 8),
|
|
1590
|
+
i.name,
|
|
1591
|
+
i.type,
|
|
1592
|
+
i.baseUrl,
|
|
1593
|
+
i.isActive ? "\u2713" : "\u2717"
|
|
1594
|
+
]),
|
|
1595
|
+
{ truncate: 28 }
|
|
1596
|
+
);
|
|
1597
|
+
console.log(`
|
|
1598
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
1599
|
+
} else {
|
|
1600
|
+
printJson(data);
|
|
1601
|
+
}
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
printError(error);
|
|
1604
|
+
process.exit(1);
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
integrations.command("get").description("Get integration detail").requiredOption("--id <integrationId>", "Integration ID").action(async (opts) => {
|
|
1608
|
+
try {
|
|
1609
|
+
const globalOpts = program.opts();
|
|
1610
|
+
const data = await apiRequest(
|
|
1611
|
+
"GET",
|
|
1612
|
+
`integrations/${opts.id}`,
|
|
1613
|
+
{
|
|
1614
|
+
apiKey: globalOpts.apiKey,
|
|
1615
|
+
baseUrl: globalOpts.baseUrl,
|
|
1616
|
+
dryRun: globalOpts.dryRun,
|
|
1617
|
+
verbose: globalOpts.verbose
|
|
1618
|
+
}
|
|
1619
|
+
);
|
|
1620
|
+
printJson(data);
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
printError(error);
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
integrations.command("save").description("Create or update an integration").requiredOption("--json <body>", "Request body as JSON string").action(async (opts) => {
|
|
1627
|
+
try {
|
|
1628
|
+
const globalOpts = program.opts();
|
|
1629
|
+
const body = JSON.parse(opts.json);
|
|
1630
|
+
const data = await apiRequest(
|
|
1631
|
+
"POST",
|
|
1632
|
+
"integrations",
|
|
1633
|
+
{
|
|
1634
|
+
apiKey: globalOpts.apiKey,
|
|
1635
|
+
baseUrl: globalOpts.baseUrl,
|
|
1636
|
+
dryRun: globalOpts.dryRun,
|
|
1637
|
+
verbose: globalOpts.verbose,
|
|
1638
|
+
body
|
|
1639
|
+
}
|
|
1640
|
+
);
|
|
1641
|
+
printJson(data);
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
printError(error);
|
|
1644
|
+
process.exit(1);
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
integrations.command("delete").description("Delete an integration").requiredOption("--id <integrationId>", "Integration ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
1648
|
+
try {
|
|
1649
|
+
const globalOpts = program.opts();
|
|
1650
|
+
if (!opts.force) {
|
|
1651
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
1652
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1653
|
+
const answer = await rl.question(`Delete integration ${opts.id}? [y/N] `);
|
|
1654
|
+
rl.close();
|
|
1655
|
+
if (answer.toLowerCase() !== "y") {
|
|
1656
|
+
console.log("Cancelled.");
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
await apiRequest(
|
|
1661
|
+
"DELETE",
|
|
1662
|
+
`integrations/${opts.id}`,
|
|
1663
|
+
{
|
|
1664
|
+
apiKey: globalOpts.apiKey,
|
|
1665
|
+
baseUrl: globalOpts.baseUrl,
|
|
1666
|
+
dryRun: globalOpts.dryRun,
|
|
1667
|
+
verbose: globalOpts.verbose
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
console.log(`\u2713 Integration ${opts.id} deleted.`);
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
printError(error);
|
|
1673
|
+
process.exit(1);
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
integrations.command("config-spec").description("Get integration configuration field specs").action(async () => {
|
|
1677
|
+
try {
|
|
1678
|
+
const globalOpts = program.opts();
|
|
1679
|
+
const data = await apiRequest(
|
|
1680
|
+
"GET",
|
|
1681
|
+
"integrations/config-spec",
|
|
1682
|
+
{
|
|
1683
|
+
apiKey: globalOpts.apiKey,
|
|
1684
|
+
baseUrl: globalOpts.baseUrl,
|
|
1685
|
+
dryRun: globalOpts.dryRun,
|
|
1686
|
+
verbose: globalOpts.verbose
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1689
|
+
printJson(data);
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
printError(error);
|
|
1692
|
+
process.exit(1);
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// src/commands/logs.ts
|
|
1698
|
+
import "commander";
|
|
1699
|
+
function registerLogCommands(program) {
|
|
1700
|
+
const logs = program.command("logs").description("Failure logs");
|
|
1701
|
+
logs.command("list").description("List failure logs").option("--category <category>", "Filter by category (INTEGRATION, INTERNAL)").option("--task-type <taskType>", "Filter by task type (COUPON_ISSUE, POINT_EARN, NOTIFICATION_SEND, WEBHOOK_EXECUTE)").option("--status <status>", "Filter by status (PENDING, RESOLVED, IGNORED)").option("--keyword <keyword>", "Search keyword").option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
1702
|
+
try {
|
|
1703
|
+
const globalOpts = program.opts();
|
|
1704
|
+
const format = globalOpts.format ?? "json";
|
|
1705
|
+
const params = {
|
|
1706
|
+
page: opts.page,
|
|
1707
|
+
size: opts.size
|
|
1708
|
+
};
|
|
1709
|
+
if (opts.category) params.category = opts.category;
|
|
1710
|
+
if (opts.taskType) params.taskType = opts.taskType;
|
|
1711
|
+
if (opts.status) params.status = opts.status;
|
|
1712
|
+
if (opts.keyword) params.searchKeyword = opts.keyword;
|
|
1713
|
+
if (opts.startDate) params.startDate = opts.startDate;
|
|
1714
|
+
if (opts.endDate) params.endDate = opts.endDate;
|
|
1715
|
+
const data = await apiRequest(
|
|
1716
|
+
"GET",
|
|
1717
|
+
"failure-logs",
|
|
1718
|
+
{
|
|
1719
|
+
apiKey: globalOpts.apiKey,
|
|
1720
|
+
baseUrl: globalOpts.baseUrl,
|
|
1721
|
+
dryRun: globalOpts.dryRun,
|
|
1722
|
+
verbose: globalOpts.verbose,
|
|
1723
|
+
params
|
|
1724
|
+
}
|
|
1725
|
+
);
|
|
1726
|
+
if (format === "table") {
|
|
1727
|
+
printTable(
|
|
1728
|
+
["ID", "Category", "Task", "Status", "Retries", "Error", "Created"],
|
|
1729
|
+
data.content.map((l) => [
|
|
1730
|
+
l.id.substring(0, 8),
|
|
1731
|
+
l.category,
|
|
1732
|
+
l.taskType,
|
|
1733
|
+
l.status,
|
|
1734
|
+
String(l.retryCount),
|
|
1735
|
+
l.errorMessage?.substring(0, 24) ?? "\u2013",
|
|
1736
|
+
l.createdAt.substring(0, 16)
|
|
1737
|
+
]),
|
|
1738
|
+
{ truncate: 24 }
|
|
1739
|
+
);
|
|
1740
|
+
console.log(`
|
|
1741
|
+
${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
1742
|
+
} else {
|
|
1743
|
+
printJson(data);
|
|
1744
|
+
}
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
printError(error);
|
|
1747
|
+
process.exit(1);
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
logs.command("get").description("Get failure log detail").requiredOption("--id <logId>", "Log ID").action(async (opts) => {
|
|
1751
|
+
try {
|
|
1752
|
+
const globalOpts = program.opts();
|
|
1753
|
+
const data = await apiRequest(
|
|
1754
|
+
"GET",
|
|
1755
|
+
`failure-logs/${opts.id}`,
|
|
1756
|
+
{
|
|
1757
|
+
apiKey: globalOpts.apiKey,
|
|
1758
|
+
baseUrl: globalOpts.baseUrl,
|
|
1759
|
+
dryRun: globalOpts.dryRun,
|
|
1760
|
+
verbose: globalOpts.verbose
|
|
1761
|
+
}
|
|
1762
|
+
);
|
|
1763
|
+
printJson(data);
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
printError(error);
|
|
1766
|
+
process.exit(1);
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
logs.command("action").description("Process a failure log action (RETRY, IGNORE, RESOLVE)").requiredOption("--id <logId>", "Log ID").requiredOption("--action <action>", "Action: RETRY, IGNORE, or RESOLVE").action(async (opts) => {
|
|
1770
|
+
try {
|
|
1771
|
+
const globalOpts = program.opts();
|
|
1772
|
+
const data = await apiRequest(
|
|
1773
|
+
"POST",
|
|
1774
|
+
`failure-logs/${opts.id}/actions`,
|
|
1775
|
+
{
|
|
1776
|
+
apiKey: globalOpts.apiKey,
|
|
1777
|
+
baseUrl: globalOpts.baseUrl,
|
|
1778
|
+
dryRun: globalOpts.dryRun,
|
|
1779
|
+
verbose: globalOpts.verbose,
|
|
1780
|
+
params: { action: opts.action }
|
|
1781
|
+
}
|
|
1782
|
+
);
|
|
1783
|
+
printJson(data);
|
|
1784
|
+
} catch (error) {
|
|
1785
|
+
printError(error);
|
|
1786
|
+
process.exit(1);
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
logs.command("bulk-action").description("Bulk process failure logs").requiredOption("--ids <logIds>", "Comma-separated log IDs").requiredOption("--action <action>", "Action: RETRY, IGNORE, or RESOLVE").action(async (opts) => {
|
|
1790
|
+
try {
|
|
1791
|
+
const globalOpts = program.opts();
|
|
1792
|
+
const logIds = opts.ids.split(",").map((id) => id.trim());
|
|
1793
|
+
const data = await apiRequest(
|
|
1794
|
+
"POST",
|
|
1795
|
+
"failure-logs/bulk-actions",
|
|
1796
|
+
{
|
|
1797
|
+
apiKey: globalOpts.apiKey,
|
|
1798
|
+
baseUrl: globalOpts.baseUrl,
|
|
1799
|
+
dryRun: globalOpts.dryRun,
|
|
1800
|
+
verbose: globalOpts.verbose,
|
|
1801
|
+
body: { logIds, action: opts.action }
|
|
1802
|
+
}
|
|
1803
|
+
);
|
|
1804
|
+
printJson(data);
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
printError(error);
|
|
1807
|
+
process.exit(1);
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/cli.ts
|
|
1813
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1814
|
+
function getVersion() {
|
|
1815
|
+
try {
|
|
1816
|
+
const pkg = JSON.parse(readFileSync3(join2(__dirname, "..", "package.json"), "utf-8"));
|
|
1817
|
+
return pkg.version;
|
|
1818
|
+
} catch {
|
|
1819
|
+
return "0.0.0";
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function createCli() {
|
|
1823
|
+
const program = new Command();
|
|
1824
|
+
program.name("lexq").description("LexQ CLI \u2014 manage policies, simulate rules, and deploy from the terminal.").version(getVersion(), "-V, --version").option("--format <format>", "Output format: json or table", "json").option("--api-key <key>", "Override stored API key").option("--base-url <url>", "Override API base URL").option("--dry-run", "Preview the HTTP request without executing").option("--verbose", "Show request/response details").option("--no-color", "Disable colored output");
|
|
1825
|
+
registerAuthCommands(program);
|
|
1826
|
+
registerStatusCommand(program);
|
|
1827
|
+
registerGroupCommands(program);
|
|
1828
|
+
registerVersionCommands(program);
|
|
1829
|
+
registerRuleCommands(program);
|
|
1830
|
+
registerFactCommands(program);
|
|
1831
|
+
registerDeployCommands(program);
|
|
1832
|
+
registerAnalyticsCommands(program);
|
|
1833
|
+
registerHistoryCommands(program);
|
|
1834
|
+
registerIntegrationCommands(program);
|
|
1835
|
+
registerLogCommands(program);
|
|
1836
|
+
return program;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/index.ts
|
|
1840
|
+
async function main() {
|
|
1841
|
+
const program = createCli();
|
|
1842
|
+
try {
|
|
1843
|
+
await program.parseAsync(process.argv);
|
|
1844
|
+
} catch (error) {
|
|
1845
|
+
printError(error);
|
|
1846
|
+
process.exit(1);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
main();
|
|
1850
|
+
//# sourceMappingURL=index.js.map
|