@chainpatrol/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/dist/cli.js ADDED
@@ -0,0 +1,895 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CliExitError,
4
+ ExitCode,
5
+ mapErrorToExitCode
6
+ } from "./chunk-E2LAMILJ.js";
7
+ import {
8
+ resolveOutputFormat
9
+ } from "./chunk-VFT3TD3E.js";
10
+
11
+ // src/cli.tsx
12
+ import meow from "meow";
13
+
14
+ // src/lib/cli-context.ts
15
+ function createCliContext({
16
+ json,
17
+ output,
18
+ dryRun,
19
+ explain
20
+ }) {
21
+ try {
22
+ return {
23
+ outputFormat: resolveOutputFormat({ json, output }),
24
+ dryRun,
25
+ explain
26
+ };
27
+ } catch (error) {
28
+ const message = error instanceof Error ? error.message : "Invalid output format provided.";
29
+ throw new CliExitError(message, ExitCode.USAGE);
30
+ }
31
+ }
32
+
33
+ // src/lib/command-help.ts
34
+ var COMMON_OPTIONS = [
35
+ "--json Output as machine-readable JSON",
36
+ "--output <fmt> Output format: human|json|markdown|csv",
37
+ "--quiet, -q Suppress non-essential output",
38
+ "--no-color Disable colored output (also respects NO_COLOR)",
39
+ "--no-input Disable interactive prompts (for scripting)",
40
+ "--help, -h Show this help"
41
+ ];
42
+ function formatEntry(name, entry) {
43
+ const lines = [];
44
+ lines.push(` ${name}`);
45
+ lines.push(` ${entry.description}`);
46
+ lines.push("");
47
+ lines.push(" Usage");
48
+ lines.push(` $ ${entry.usage}`);
49
+ if (entry.options && entry.options.length > 0) {
50
+ lines.push("");
51
+ lines.push(" Options");
52
+ for (const option of entry.options) {
53
+ lines.push(` ${option}`);
54
+ }
55
+ }
56
+ lines.push("");
57
+ lines.push(" Common options");
58
+ for (const option of COMMON_OPTIONS) {
59
+ lines.push(` ${option}`);
60
+ }
61
+ if (entry.examples && entry.examples.length > 0) {
62
+ lines.push("");
63
+ lines.push(" Examples");
64
+ for (const example of entry.examples) {
65
+ lines.push(` $ ${example}`);
66
+ }
67
+ }
68
+ return lines.join("\n");
69
+ }
70
+ var HELP = {
71
+ login: {
72
+ description: "Authenticate with ChainPatrol via device-code flow.",
73
+ usage: "chainpatrol login",
74
+ examples: ["chainpatrol login", "chainpatrol login --json"]
75
+ },
76
+ logout: {
77
+ description: "Clear stored credentials from this machine.",
78
+ usage: "chainpatrol logout"
79
+ },
80
+ configs: {
81
+ description: "Manage detection configs for an organization.",
82
+ usage: "chainpatrol configs <list>",
83
+ options: ["--org <slug> Organization slug (saved as default)"],
84
+ examples: ["chainpatrol configs list --org acme"]
85
+ },
86
+ "configs list": {
87
+ description: "List detection configs available to your organization.",
88
+ usage: "chainpatrol configs list --org <slug>",
89
+ options: ["--org <slug> Organization slug"],
90
+ examples: ["chainpatrol configs list --org acme"]
91
+ },
92
+ detections: {
93
+ description: "Validate, run, and update detection configs.",
94
+ usage: "chainpatrol detections <healthcheck|validate|drift|run|configs>",
95
+ options: ["--org <slug> Organization slug"],
96
+ examples: [
97
+ "chainpatrol detections healthcheck --org acme --run",
98
+ "chainpatrol detections validate --org acme --min-results 5",
99
+ "chainpatrol detections drift --org acme --lookback-hours 168",
100
+ "chainpatrol detections run --org acme --source twitter",
101
+ "chainpatrol detections configs update --org acme --config-id 1 --enable"
102
+ ]
103
+ },
104
+ "detections healthcheck": {
105
+ description: "Run a health check across all detection configs for an org.",
106
+ usage: "chainpatrol detections healthcheck --org <slug> [options]",
107
+ options: [
108
+ "--org <slug> Organization slug",
109
+ "--source <key> Filter by detection source key",
110
+ "--min-results <n> Minimum recent results required",
111
+ "--lookback-hours <n> Lookback window in hours",
112
+ "--run Run detections before validating",
113
+ "--include-disabled Include disabled configs"
114
+ ],
115
+ examples: ["chainpatrol detections healthcheck --org acme --run"]
116
+ },
117
+ "detections validate": {
118
+ description: "Validate that detection configs produce expected results.",
119
+ usage: "chainpatrol detections validate --org <slug> [options]",
120
+ options: [
121
+ "--org <slug> Organization slug",
122
+ "--source <key> Filter by detection source key",
123
+ "--min-results <n> Minimum recent results required",
124
+ "--lookback-hours <n> Lookback window in hours",
125
+ "--run Run detections before validating",
126
+ "--include-disabled Include disabled configs"
127
+ ]
128
+ },
129
+ "detections drift": {
130
+ description: "Detect drift signals (zero-results, noisy, stale queries).",
131
+ usage: "chainpatrol detections drift --org <slug> [options]",
132
+ options: [
133
+ "--org <slug> Organization slug",
134
+ "--source <key> Filter by detection source key",
135
+ "--config-id <n> Filter by config id",
136
+ "--lookback-hours <n> Lookback window in hours",
137
+ "--include-disabled Include disabled configs"
138
+ ]
139
+ },
140
+ "detections run": {
141
+ description: "Run one or more detection configs on demand.",
142
+ usage: "chainpatrol detections run --org <slug> [options]",
143
+ options: [
144
+ "--org <slug> Organization slug",
145
+ "--config-id <n> Run a specific config",
146
+ "--source <key> Filter by detection source key",
147
+ "--include-disabled Include disabled configs",
148
+ "--dry-run Preview without executing"
149
+ ]
150
+ },
151
+ "detections configs update": {
152
+ description: "Update an existing detection config's status, title, cron, or query.",
153
+ usage: "chainpatrol detections configs update --org <slug> --config-id <n> [options]",
154
+ options: [
155
+ "--org <slug> Organization slug",
156
+ "--config-id <n> Config id (required)",
157
+ "--enable Enable the config",
158
+ "--disable Disable the config",
159
+ "--title <text> New title",
160
+ "--description <text> New description",
161
+ "--cron <expr> New CRON expression",
162
+ "--set key=value Patch a config value (repeatable)",
163
+ "--merge-config Merge --set keys into existing config (default true)",
164
+ "--dry-run Preview without applying"
165
+ ],
166
+ examples: [
167
+ "chainpatrol detections configs update --org acme --config-id 12 --enable",
168
+ 'chainpatrol detections configs update --org acme --config-id 12 --set query="phishing site"'
169
+ ]
170
+ },
171
+ metrics: {
172
+ description: "Query organization metrics and breakdowns.",
173
+ usage: "chainpatrol metrics <summary|found|breakdown>",
174
+ options: ["--org <slug> Organization slug"],
175
+ examples: [
176
+ "chainpatrol metrics summary --org acme --this-week",
177
+ "chainpatrol metrics found --org acme --from 2025-01-01 --to 2025-02-01",
178
+ "chainpatrol metrics breakdown --org acme --by day --this-week"
179
+ ]
180
+ },
181
+ "metrics summary": {
182
+ description: "Summary metrics for an org over a date range.",
183
+ usage: "chainpatrol metrics summary --org <slug> [--from ISO --to ISO]",
184
+ options: [
185
+ "--org <slug> Organization slug",
186
+ "--from <iso> Start date (ISO 8601)",
187
+ "--to <iso> End date (ISO 8601)",
188
+ "--brand <ids> Comma-separated brand IDs"
189
+ ]
190
+ },
191
+ "metrics found": {
192
+ description: "Count of newly found threats over a date range.",
193
+ usage: "chainpatrol metrics found --org <slug> [--from ISO --to ISO|--this-week]",
194
+ options: [
195
+ "--org <slug> Organization slug",
196
+ "--from <iso> Start date (ISO 8601)",
197
+ "--to <iso> End date (ISO 8601)",
198
+ "--this-week Use the current week",
199
+ "--brand <ids> Comma-separated brand IDs"
200
+ ]
201
+ },
202
+ "metrics breakdown": {
203
+ description: "Threat counts grouped by axis (day, type, brand).",
204
+ usage: "chainpatrol metrics breakdown --org <slug> --by <day|type|brand>",
205
+ options: [
206
+ "--org <slug> Organization slug",
207
+ "--by <axis> day|type|brand (required)",
208
+ "--from <iso> Start date",
209
+ "--to <iso> End date",
210
+ "--brand <ids> Comma-separated brand IDs"
211
+ ]
212
+ },
213
+ reports: {
214
+ description: "Create and list reports from the terminal.",
215
+ usage: "chainpatrol reports <create|list>",
216
+ examples: [
217
+ 'chainpatrol reports create --org acme --title "Phish" --asset "https://bad.site:BLOCKED"',
218
+ "chainpatrol reports list --org acme --limit 5"
219
+ ]
220
+ },
221
+ "reports create": {
222
+ description: "Create a new report with one or more assets.",
223
+ usage: "chainpatrol reports create [--org <slug>] [options]",
224
+ options: [
225
+ "--org <slug> Organization slug (defaults to saved/env)",
226
+ "--title <text> Report title",
227
+ "--description <text> Report description",
228
+ "--contact-info <text> Reporter contact info",
229
+ "--asset <c[:status]> Asset (repeatable). status=BLOCKED|ALLOWED|UNKNOWN",
230
+ "--attachment-url <u> Attachment URL (repeatable)",
231
+ "--external-submission-link <u> External submission link",
232
+ "--payload-file <path> JSON file with full report payload",
233
+ "--dry-run Preview without submitting"
234
+ ],
235
+ examples: [
236
+ 'chainpatrol reports create --org acme --title "Phish" --asset "https://bad.site:BLOCKED"',
237
+ "chainpatrol reports create --payload-file ./report.json --dry-run"
238
+ ]
239
+ },
240
+ "reports list": {
241
+ description: "List recent reports for an organization.",
242
+ usage: "chainpatrol reports list --org <slug> [options]",
243
+ options: [
244
+ "--org <slug> Organization slug",
245
+ "--limit <n> Page size (1-20)",
246
+ "--cursor <id> Pagination cursor",
247
+ "--status <s> Filter by report status",
248
+ "--search <q> Search query"
249
+ ]
250
+ },
251
+ queues: {
252
+ description: "Snapshot operations review/takedown queues.",
253
+ usage: "chainpatrol queues snapshot [--org <slug>|--all]",
254
+ examples: [
255
+ "chainpatrol queues snapshot --org acme",
256
+ "chainpatrol queues snapshot --all"
257
+ ]
258
+ },
259
+ "queues snapshot": {
260
+ description: "Snapshot pending review proposals and open takedowns.",
261
+ usage: "chainpatrol queues snapshot [--org <slug>|--all] [--window-hours <n>]",
262
+ options: [
263
+ "--org <slug> Organization slug",
264
+ "--all All organizations (staff-only)",
265
+ "--window-hours <n> Hours used for staleness windows"
266
+ ]
267
+ },
268
+ presets: {
269
+ description: "Run saved workflows for common jobs.",
270
+ usage: "chainpatrol presets <list|run <id>>",
271
+ examples: [
272
+ "chainpatrol presets list",
273
+ "chainpatrol presets run cs-weekly-health --org acme"
274
+ ]
275
+ },
276
+ "presets list": {
277
+ description: "List available preset workflows.",
278
+ usage: "chainpatrol presets list"
279
+ },
280
+ "presets run": {
281
+ description: "Run a preset workflow by id.",
282
+ usage: "chainpatrol presets run <preset-id> --org <slug>",
283
+ options: ["--org <slug> Organization slug"]
284
+ },
285
+ setup: {
286
+ description: "Install Claude Code skill and shell completions.",
287
+ usage: "chainpatrol setup"
288
+ },
289
+ uninstall: {
290
+ description: "Remove Claude Code skill and shell completions.",
291
+ usage: "chainpatrol uninstall"
292
+ },
293
+ completions: {
294
+ description: "Output a shell completion script.",
295
+ usage: "chainpatrol completions <zsh|bash>",
296
+ examples: ["chainpatrol completions zsh > _chainpatrol"]
297
+ }
298
+ };
299
+ function getCommandHelp(command2, subcommand2) {
300
+ if (subcommand2) {
301
+ const combined = `${command2} ${subcommand2}`;
302
+ if (HELP[combined]) {
303
+ return formatEntry(`chainpatrol ${combined}`, HELP[combined]);
304
+ }
305
+ }
306
+ if (HELP[command2]) {
307
+ return formatEntry(`chainpatrol ${command2}`, HELP[command2]);
308
+ }
309
+ return null;
310
+ }
311
+ function getTopLevelHelp() {
312
+ return [
313
+ " ChainPatrol CLI",
314
+ "",
315
+ " Usage",
316
+ " $ chainpatrol <command> [options]",
317
+ "",
318
+ " Commands",
319
+ " login Authenticate with ChainPatrol",
320
+ " logout Clear stored credentials",
321
+ " configs Manage detection configs",
322
+ " detections Validate, run, update detection configs",
323
+ " metrics Query organization metrics and breakdowns",
324
+ " reports Create and list reports from terminal",
325
+ " queues Snapshot operations review/takedown queues",
326
+ " presets Run saved workflows for common jobs",
327
+ " setup Install Claude Code skill and shell completions",
328
+ " uninstall Remove Claude Code skill and shell completions",
329
+ " completions Output shell completion script (zsh/bash)",
330
+ " help <cmd> Show help for a specific command",
331
+ "",
332
+ " Common options (use --help on a command for full options)",
333
+ ...COMMON_OPTIONS.map((option) => ` ${option}`),
334
+ "",
335
+ " Examples",
336
+ " $ chainpatrol login",
337
+ " $ chainpatrol configs list --org acme",
338
+ " $ chainpatrol detections healthcheck --help",
339
+ " $ chainpatrol reports create --help",
340
+ "",
341
+ " Support",
342
+ " https://github.com/chainpatrol/chainpatrol-cli/issues"
343
+ ].join("\n");
344
+ }
345
+
346
+ // src/cli.tsx
347
+ var COMMANDS = [
348
+ "login",
349
+ "logout",
350
+ "configs",
351
+ "detections",
352
+ "metrics",
353
+ "reports",
354
+ "queues",
355
+ "presets",
356
+ "setup",
357
+ "uninstall",
358
+ "completions",
359
+ "help"
360
+ ];
361
+ function levenshtein(a, b) {
362
+ const m = a.length;
363
+ const n = b.length;
364
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
365
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
366
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
367
+ for (let i = 1; i <= m; i++) {
368
+ for (let j = 1; j <= n; j++) {
369
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
370
+ }
371
+ }
372
+ return dp[m][n];
373
+ }
374
+ function suggest(input, candidates) {
375
+ let best = null;
376
+ let bestDist = 3;
377
+ for (const candidate of candidates) {
378
+ const d = levenshtein(input, candidate);
379
+ if (d < bestDist) {
380
+ bestDist = d;
381
+ best = candidate;
382
+ }
383
+ }
384
+ return best;
385
+ }
386
+ var cli = meow(`
387
+ ${getTopLevelHelp()}
388
+ `, {
389
+ importMeta: import.meta,
390
+ version: "0.1.0",
391
+ flags: {
392
+ json: { type: "boolean", default: false },
393
+ output: { type: "string" },
394
+ dryRun: { type: "boolean", default: false },
395
+ explain: { type: "boolean", default: false },
396
+ all: { type: "boolean", default: false },
397
+ windowHours: { type: "number" },
398
+ org: { type: "string" },
399
+ source: { type: "string" },
400
+ configId: { type: "number" },
401
+ run: { type: "boolean", default: false },
402
+ minResults: { type: "number" },
403
+ lookbackHours: { type: "number" },
404
+ from: { type: "string" },
405
+ to: { type: "string" },
406
+ thisWeek: { type: "boolean", default: false },
407
+ by: { type: "string" },
408
+ brand: { type: "string" },
409
+ enable: { type: "boolean", default: false },
410
+ disable: { type: "boolean", default: false },
411
+ includeDisabled: { type: "boolean", default: false },
412
+ title: { type: "string" },
413
+ description: { type: "string" },
414
+ cron: { type: "string" },
415
+ mergeConfig: { type: "boolean", default: true },
416
+ asset: { type: "string" },
417
+ limit: { type: "number" },
418
+ cursor: { type: "number" },
419
+ status: { type: "string" },
420
+ search: { type: "string" },
421
+ attachmentUrl: { type: "string" },
422
+ contactInfo: { type: "string" },
423
+ externalSubmissionLink: { type: "string" },
424
+ payloadFile: { type: "string" },
425
+ help: { type: "boolean", shortFlag: "h" },
426
+ version: { type: "boolean", shortFlag: "V" },
427
+ quiet: { type: "boolean", default: false, shortFlag: "q" },
428
+ noInput: { type: "boolean", default: false },
429
+ noColor: { type: "boolean", default: false }
430
+ }
431
+ });
432
+ var [command, subcommand, action] = cli.input;
433
+ var cliContext = createCliContext({
434
+ json: cli.flags.json,
435
+ output: cli.flags.output,
436
+ dryRun: cli.flags.dryRun,
437
+ explain: cli.flags.explain
438
+ });
439
+ var jsonMode = cliContext.outputFormat === "json";
440
+ var quiet = cli.flags.quiet;
441
+ function jsonError(message, code = ExitCode.UNKNOWN) {
442
+ console.error(JSON.stringify({ error: message, exitCode: code }, null, 2));
443
+ process.exit(code);
444
+ }
445
+ async function tryResolveOrg() {
446
+ const { getConfig, saveConfig } = await import("./config-Z3TASRME.js");
447
+ const config = getConfig();
448
+ if (cli.flags.org) {
449
+ if (config.defaultOrg !== cli.flags.org) {
450
+ saveConfig({ ...config, defaultOrg: cli.flags.org });
451
+ }
452
+ return cli.flags.org;
453
+ }
454
+ if (config.defaultOrg) {
455
+ return config.defaultOrg;
456
+ }
457
+ const envOrg = process.env.CHAINPATROL_ORG;
458
+ if (envOrg) {
459
+ return envOrg;
460
+ }
461
+ return void 0;
462
+ }
463
+ async function resolveOrg() {
464
+ const org = await tryResolveOrg();
465
+ if (org) return org;
466
+ if (jsonMode) jsonError("Organization required. Use --org <slug> to specify one.");
467
+ throw new Error("Organization required. Use --org <slug> to specify one.");
468
+ }
469
+ function getRepeatedFlag(flagName) {
470
+ const values = [];
471
+ const argv = process.argv.slice(2);
472
+ for (let i = 0; i < argv.length; i += 1) {
473
+ if (argv[i] === `--${flagName}`) {
474
+ const nextValue = argv[i + 1];
475
+ if (nextValue && !nextValue.startsWith("-")) {
476
+ values.push(nextValue);
477
+ }
478
+ }
479
+ if (argv[i].startsWith(`--${flagName}=`)) {
480
+ values.push(argv[i].slice(flagName.length + 3));
481
+ }
482
+ }
483
+ return values;
484
+ }
485
+ function parseSetValue(value) {
486
+ if (value === "true") return true;
487
+ if (value === "false") return false;
488
+ if (value === "null") return null;
489
+ const parsedNumber = Number(value);
490
+ if (!Number.isNaN(parsedNumber) && value.trim() !== "") return parsedNumber;
491
+ if (value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]")) {
492
+ try {
493
+ return JSON.parse(value);
494
+ } catch {
495
+ return value;
496
+ }
497
+ }
498
+ return value;
499
+ }
500
+ function getConfigPatchFromSetFlags() {
501
+ const setFlags = getRepeatedFlag("set");
502
+ const patch = {};
503
+ for (const flag of setFlags) {
504
+ const equalsIndex = flag.indexOf("=");
505
+ if (equalsIndex <= 0) continue;
506
+ const key = flag.slice(0, equalsIndex).trim();
507
+ const value = flag.slice(equalsIndex + 1).trim();
508
+ if (!key) continue;
509
+ patch[key] = parseSetValue(value);
510
+ }
511
+ return patch;
512
+ }
513
+ function parseBrandIds() {
514
+ if (!cli.flags.brand) return void 0;
515
+ const values = cli.flags.brand.split(",").map((item) => item.trim()).filter(Boolean);
516
+ const ids = values.map((value) => Number(value)).filter((value) => !Number.isNaN(value));
517
+ return ids.length > 0 ? ids : void 0;
518
+ }
519
+ function parseAssetInputs() {
520
+ return getRepeatedFlag("asset");
521
+ }
522
+ function parseAttachmentUrls() {
523
+ const values = getRepeatedFlag("attachment-url").filter(Boolean);
524
+ return values.length > 0 ? values : void 0;
525
+ }
526
+ async function handleConfigsList(org) {
527
+ if (jsonMode) {
528
+ const { listConfigsJson } = await import("./list-json-TPBLJBD3.js");
529
+ await listConfigsJson({ org });
530
+ return;
531
+ }
532
+ const { render } = await import("ink");
533
+ const { default: ConfigsList } = await import("./list-IBMM562A.js");
534
+ const { default: React } = await import("react");
535
+ render(React.createElement(ConfigsList, { org }));
536
+ }
537
+ function maybeShowScopedHelp() {
538
+ const wantsHelp = cli.flags.help === true || command === "help";
539
+ if (command === "help") {
540
+ if (subcommand) {
541
+ const text = getCommandHelp(subcommand, action);
542
+ if (text) {
543
+ console.log(text);
544
+ return true;
545
+ }
546
+ }
547
+ console.log(getTopLevelHelp());
548
+ return true;
549
+ }
550
+ if (wantsHelp && command) {
551
+ const text = getCommandHelp(command, subcommand);
552
+ if (text) {
553
+ console.log(text);
554
+ return true;
555
+ }
556
+ }
557
+ return false;
558
+ }
559
+ async function main() {
560
+ if (maybeShowScopedHelp()) {
561
+ return;
562
+ }
563
+ if (cliContext.dryRun) {
564
+ const dryRunSupported = command === "reports" && subcommand === "create" || command === "detections" && subcommand === "run" || command === "detections" && subcommand === "configs" && action === "run" || command === "detections" && subcommand === "configs" && action === "update";
565
+ if (!dryRunSupported) {
566
+ throw new CliExitError(
567
+ "--dry-run is only supported for mutation commands.",
568
+ ExitCode.USAGE
569
+ );
570
+ }
571
+ }
572
+ switch (command) {
573
+ case "login": {
574
+ if (cli.flags.noInput && !jsonMode) {
575
+ throw new Error(
576
+ "Login requires interaction. Use --json to get the device code URL for non-interactive auth."
577
+ );
578
+ }
579
+ if (jsonMode) {
580
+ const { loginJson } = await import("./login-json-LKB72OFY.js");
581
+ await loginJson();
582
+ } else {
583
+ const { render } = await import("ink");
584
+ const { default: Login } = await import("./login-G7LPHKDR.js");
585
+ const { default: React } = await import("react");
586
+ render(React.createElement(Login));
587
+ }
588
+ break;
589
+ }
590
+ case "logout": {
591
+ if (jsonMode) {
592
+ const { logoutJson } = await import("./logout-json-4GIJZJ46.js");
593
+ await logoutJson();
594
+ } else {
595
+ const { render } = await import("ink");
596
+ const { default: Logout } = await import("./logout-LA7VEKON.js");
597
+ const { default: React } = await import("react");
598
+ render(React.createElement(Logout));
599
+ }
600
+ break;
601
+ }
602
+ case "configs": {
603
+ if (subcommand === "list") {
604
+ const org = await resolveOrg();
605
+ await handleConfigsList(org);
606
+ break;
607
+ }
608
+ const sub = subcommand;
609
+ if (sub) {
610
+ const hint = suggest(sub, ["list"]);
611
+ throw new Error(
612
+ `Unknown subcommand: configs ${sub}${hint ? `. Did you mean "configs ${hint}"?` : ""}`
613
+ );
614
+ }
615
+ throw new Error("Usage: chainpatrol configs list");
616
+ }
617
+ case "detections": {
618
+ const org = await resolveOrg();
619
+ if (subcommand === "healthcheck") {
620
+ const { runDetectionsHealthcheck } = await import("./healthcheck-7DR5MGEQ.js");
621
+ await runDetectionsHealthcheck({
622
+ org,
623
+ source: cli.flags.source,
624
+ minResults: cli.flags.minResults,
625
+ lookbackHours: cli.flags.lookbackHours,
626
+ run: cli.flags.run,
627
+ includeDisabled: cli.flags.includeDisabled,
628
+ json: jsonMode,
629
+ outputFormat: cliContext.outputFormat,
630
+ explain: cliContext.explain
631
+ });
632
+ break;
633
+ }
634
+ if (subcommand === "validate") {
635
+ const { runDetectionsValidate } = await import("./validate-PI7GPT5I.js");
636
+ await runDetectionsValidate({
637
+ org,
638
+ source: cli.flags.source,
639
+ minResults: cli.flags.minResults,
640
+ lookbackHours: cli.flags.lookbackHours,
641
+ runBeforeValidate: cli.flags.run,
642
+ includeDisabled: cli.flags.includeDisabled,
643
+ json: jsonMode,
644
+ outputFormat: cliContext.outputFormat,
645
+ explain: cliContext.explain
646
+ });
647
+ break;
648
+ }
649
+ if (subcommand === "drift") {
650
+ const { runDetectionsDrift } = await import("./drift-VRZKQC4P.js");
651
+ await runDetectionsDrift({
652
+ org,
653
+ source: cli.flags.source,
654
+ configId: cli.flags.configId,
655
+ lookbackHours: cli.flags.lookbackHours,
656
+ includeDisabled: cli.flags.includeDisabled,
657
+ json: jsonMode,
658
+ outputFormat: cliContext.outputFormat,
659
+ explain: cliContext.explain
660
+ });
661
+ break;
662
+ }
663
+ if (subcommand === "run") {
664
+ const { runDetectionsRun } = await import("./run-PABQKATZ.js");
665
+ await runDetectionsRun({
666
+ org,
667
+ configId: cli.flags.configId,
668
+ source: cli.flags.source,
669
+ includeDisabled: cli.flags.includeDisabled,
670
+ json: jsonMode,
671
+ outputFormat: cliContext.outputFormat,
672
+ explain: cliContext.explain,
673
+ dryRun: cliContext.dryRun
674
+ });
675
+ break;
676
+ }
677
+ if (subcommand === "configs") {
678
+ if (action === "list") {
679
+ await handleConfigsList(org);
680
+ break;
681
+ }
682
+ if (action === "run") {
683
+ const { runDetectionsRun } = await import("./run-PABQKATZ.js");
684
+ await runDetectionsRun({
685
+ org,
686
+ configId: cli.flags.configId,
687
+ source: cli.flags.source,
688
+ includeDisabled: cli.flags.includeDisabled,
689
+ json: jsonMode,
690
+ outputFormat: cliContext.outputFormat,
691
+ explain: cliContext.explain,
692
+ dryRun: cliContext.dryRun
693
+ });
694
+ break;
695
+ }
696
+ if (action === "update") {
697
+ if (!cli.flags.configId) {
698
+ throw new Error("detections configs update requires --config-id");
699
+ }
700
+ const configPatch = getConfigPatchFromSetFlags();
701
+ const { runDetectionsConfigsUpdate } = await import("./configs-update-BK2S6AZ6.js");
702
+ await runDetectionsConfigsUpdate({
703
+ org,
704
+ configId: cli.flags.configId,
705
+ enable: cli.flags.enable,
706
+ disable: cli.flags.disable,
707
+ title: cli.flags.title,
708
+ description: cli.flags.description,
709
+ cron: cli.flags.cron,
710
+ configPatch: Object.keys(configPatch).length > 0 ? configPatch : void 0,
711
+ mergeConfig: cli.flags.mergeConfig,
712
+ json: jsonMode,
713
+ outputFormat: cliContext.outputFormat,
714
+ explain: cliContext.explain,
715
+ dryRun: cliContext.dryRun
716
+ });
717
+ break;
718
+ }
719
+ }
720
+ const hint = subcommand ? suggest(subcommand, ["healthcheck", "validate", "drift", "run", "configs"]) : null;
721
+ throw new Error(
722
+ subcommand ? `Unknown subcommand: detections ${subcommand}${hint ? `. Did you mean "detections ${hint}"?` : ""}` : "Usage: chainpatrol detections <healthcheck|validate|drift|run|configs>"
723
+ );
724
+ }
725
+ case "metrics": {
726
+ const org = await resolveOrg();
727
+ if (subcommand === "summary") {
728
+ const { runMetricsSummary } = await import("./summary-YG5NYIOA.js");
729
+ await runMetricsSummary({
730
+ org,
731
+ from: cli.flags.from,
732
+ to: cli.flags.to,
733
+ brandIds: parseBrandIds(),
734
+ json: jsonMode,
735
+ outputFormat: cliContext.outputFormat
736
+ });
737
+ break;
738
+ }
739
+ if (subcommand === "found") {
740
+ const { runMetricsFound } = await import("./found-4O3AISNI.js");
741
+ await runMetricsFound({
742
+ org,
743
+ from: cli.flags.from,
744
+ to: cli.flags.to,
745
+ thisWeek: cli.flags.thisWeek,
746
+ brandIds: parseBrandIds(),
747
+ json: jsonMode,
748
+ outputFormat: cliContext.outputFormat
749
+ });
750
+ break;
751
+ }
752
+ if (subcommand === "breakdown") {
753
+ const by = cli.flags.by;
754
+ if (!by || !["day", "type", "brand"].includes(by)) {
755
+ throw new Error("metrics breakdown requires --by <day|type|brand>");
756
+ }
757
+ const { runMetricsBreakdown } = await import("./breakdown-JVN66HY3.js");
758
+ await runMetricsBreakdown({
759
+ org,
760
+ by,
761
+ from: cli.flags.from,
762
+ to: cli.flags.to,
763
+ brandIds: parseBrandIds(),
764
+ json: jsonMode,
765
+ outputFormat: cliContext.outputFormat
766
+ });
767
+ break;
768
+ }
769
+ const hint = subcommand ? suggest(subcommand, ["summary", "found", "breakdown"]) : null;
770
+ throw new Error(
771
+ subcommand ? `Unknown subcommand: metrics ${subcommand}${hint ? `. Did you mean "metrics ${hint}"?` : ""}` : "Usage: chainpatrol metrics <summary|found|breakdown>"
772
+ );
773
+ }
774
+ case "reports": {
775
+ if (subcommand === "list") {
776
+ const org = await resolveOrg();
777
+ const { runReportsList } = await import("./list-6L7XR4SZ.js");
778
+ await runReportsList({
779
+ org,
780
+ limit: cli.flags.limit,
781
+ cursor: cli.flags.cursor,
782
+ status: cli.flags.status,
783
+ searchQuery: cli.flags.search,
784
+ json: jsonMode,
785
+ outputFormat: cliContext.outputFormat,
786
+ explain: cliContext.explain
787
+ });
788
+ break;
789
+ }
790
+ if (subcommand === "create") {
791
+ const org = await tryResolveOrg();
792
+ const { runReportsCreate } = await import("./create-4SQUBQI7.js");
793
+ await runReportsCreate({
794
+ org,
795
+ title: cli.flags.title,
796
+ description: cli.flags.description,
797
+ contactInfo: cli.flags.contactInfo,
798
+ attachmentUrls: parseAttachmentUrls(),
799
+ externalSubmissionLink: cli.flags.externalSubmissionLink,
800
+ assets: parseAssetInputs(),
801
+ payloadFile: cli.flags.payloadFile,
802
+ json: jsonMode,
803
+ outputFormat: cliContext.outputFormat,
804
+ explain: cliContext.explain,
805
+ dryRun: cliContext.dryRun
806
+ });
807
+ break;
808
+ }
809
+ const hint = subcommand ? suggest(subcommand, ["create", "list"]) : null;
810
+ throw new Error(
811
+ subcommand ? `Unknown subcommand: reports ${subcommand}${hint ? `. Did you mean "reports ${hint}"?` : ""}` : "Usage: chainpatrol reports <create|list>"
812
+ );
813
+ }
814
+ case "queues": {
815
+ if (subcommand === "snapshot") {
816
+ const { runQueuesSnapshot } = await import("./snapshot-JEVDTE74.js");
817
+ await runQueuesSnapshot({
818
+ org: cli.flags.org,
819
+ all: cli.flags.all,
820
+ windowHours: cli.flags.windowHours,
821
+ json: jsonMode,
822
+ outputFormat: cliContext.outputFormat,
823
+ explain: cliContext.explain
824
+ });
825
+ break;
826
+ }
827
+ const hint = subcommand ? suggest(subcommand, ["snapshot"]) : null;
828
+ throw new Error(
829
+ subcommand ? `Unknown subcommand: queues ${subcommand}${hint ? `. Did you mean "queues ${hint}"?` : ""}` : "Usage: chainpatrol queues snapshot [--org <slug>|--all]"
830
+ );
831
+ }
832
+ case "presets": {
833
+ if (subcommand === "list") {
834
+ const { runPresetsList } = await import("./list-HZAHEHDM.js");
835
+ await runPresetsList({ outputFormat: cliContext.outputFormat });
836
+ break;
837
+ }
838
+ if (subcommand === "run") {
839
+ if (!action) {
840
+ throw new Error(
841
+ "presets run requires a preset id. Example: presets run cs-weekly-health"
842
+ );
843
+ }
844
+ const org = await resolveOrg();
845
+ const { runPresetsRun } = await import("./run-U62KVNTH.js");
846
+ await runPresetsRun({
847
+ presetId: action,
848
+ org,
849
+ outputFormat: cliContext.outputFormat,
850
+ explain: cliContext.explain
851
+ });
852
+ break;
853
+ }
854
+ const hint = subcommand ? suggest(subcommand, ["list", "run"]) : null;
855
+ throw new Error(
856
+ subcommand ? `Unknown subcommand: presets ${subcommand}${hint ? `. Did you mean "presets ${hint}"?` : ""}` : "Usage: chainpatrol presets <list|run>"
857
+ );
858
+ }
859
+ case "setup":
860
+ case "install":
861
+ case "i": {
862
+ const { setupSkill } = await import("./setup-skill-U24CJZ6T.js");
863
+ setupSkill({ json: jsonMode });
864
+ break;
865
+ }
866
+ case "uninstall": {
867
+ const { uninstallSkill } = await import("./setup-skill-U24CJZ6T.js");
868
+ uninstallSkill({ json: jsonMode });
869
+ break;
870
+ }
871
+ case "completions": {
872
+ const { printCompletions } = await import("./completions-EGQIARFC.js");
873
+ printCompletions(subcommand);
874
+ break;
875
+ }
876
+ default: {
877
+ if (command) {
878
+ const hint = suggest(command, COMMANDS);
879
+ const suffix = hint ? ` Did you mean "${hint}"?` : "";
880
+ throw new Error(`Unknown command: ${command}.${suffix} Use -h for help.`);
881
+ }
882
+ if (jsonMode) jsonError("No command specified. Use --help for usage.");
883
+ if (!quiet) console.log(getTopLevelHelp());
884
+ }
885
+ }
886
+ }
887
+ main().catch((err) => {
888
+ const message = err instanceof Error ? err.message : String(err);
889
+ const exitCode = mapErrorToExitCode(err);
890
+ if (jsonMode) {
891
+ jsonError(message, exitCode);
892
+ }
893
+ console.error(message);
894
+ process.exit(exitCode);
895
+ });