@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.
@@ -0,0 +1,12 @@
1
+ import {
2
+ getCompletionScript,
3
+ installCompletions,
4
+ printCompletions,
5
+ uninstallCompletions
6
+ } from "./chunk-IUZB3DQW.js";
7
+ export {
8
+ getCompletionScript,
9
+ installCompletions,
10
+ printCompletions,
11
+ uninstallCompletions
12
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ getConfig,
3
+ getConfigDir,
4
+ saveConfig
5
+ } from "./chunk-U73SABXK.js";
6
+ export {
7
+ getConfig,
8
+ getConfigDir,
9
+ saveConfig
10
+ };
@@ -0,0 +1,101 @@
1
+ import {
2
+ CliExitError,
3
+ ExitCode
4
+ } from "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput
7
+ } from "./chunk-VFT3TD3E.js";
8
+ import {
9
+ createApiClient
10
+ } from "./chunk-H7UKKLCV.js";
11
+ import "./chunk-EEG7T6WT.js";
12
+ import "./chunk-U73SABXK.js";
13
+
14
+ // src/commands/detections/configs-update.ts
15
+ async function runDetectionsConfigsUpdate(options) {
16
+ if (options.enable && options.disable) {
17
+ throw new CliExitError("Use either --enable or --disable, not both.", ExitCode.USAGE);
18
+ }
19
+ const status = options.enable ? "ENABLED" : options.disable ? "DISABLED" : void 0;
20
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
21
+ const payload = {
22
+ slug: options.org,
23
+ configId: options.configId,
24
+ status,
25
+ title: options.title,
26
+ description: options.description,
27
+ cron: options.cron,
28
+ config: options.configPatch,
29
+ mergeConfig: options.mergeConfig ?? true
30
+ };
31
+ if (options.dryRun) {
32
+ printOutput({
33
+ outputFormat,
34
+ json: {
35
+ dryRun: true,
36
+ mutation: "detection.configs.update",
37
+ payload,
38
+ explanation: options.explain ? "This command updates status/metadata/config values on a detection config." : void 0
39
+ },
40
+ markdown: [
41
+ "# Dry Run: Detection Config Update",
42
+ "",
43
+ `- Org: ${payload.slug}`,
44
+ `- Config ID: ${payload.configId}`,
45
+ `- Status update: ${payload.status ?? "none"}`
46
+ ].join("\n"),
47
+ human: () => {
48
+ console.log("Dry run only. No config changes were saved.");
49
+ console.log(
50
+ `Would update config ${payload.configId} for org=${payload.slug}${payload.status ? ` status=${payload.status}` : ""}`
51
+ );
52
+ }
53
+ });
54
+ return;
55
+ }
56
+ const client = options.apiClient ?? createApiClient();
57
+ const result = await client.updateDetectionConfig(payload);
58
+ printOutput({
59
+ outputFormat,
60
+ json: {
61
+ ...result,
62
+ explanation: options.explain ? "Applied mutation to update detection config metadata and/or configuration payload." : void 0
63
+ },
64
+ markdown: [
65
+ `# Detection Config Updated (${options.org})`,
66
+ "",
67
+ `- Config: ${result.config.id}`,
68
+ `- Source: ${result.config.source}`,
69
+ `- Status: ${result.config.status}`,
70
+ `- Updated at: ${result.config.updatedAt}`
71
+ ].join("\n"),
72
+ human: () => {
73
+ console.log(
74
+ `Updated config ${result.config.id} [${result.config.source}] status=${result.config.status}`
75
+ );
76
+ if (result.config.title) {
77
+ console.log(`Title: ${result.config.title}`);
78
+ }
79
+ if (result.config.cron) {
80
+ console.log(`Cron: ${result.config.cron}`);
81
+ }
82
+ if (Object.keys(result.config.config).length > 0) {
83
+ console.log(`Config keys: ${Object.keys(result.config.config).join(", ")}`);
84
+ }
85
+ if (options.explain) {
86
+ console.log(
87
+ `Changed fields: ${[
88
+ status ? "status" : null,
89
+ options.title !== void 0 ? "title" : null,
90
+ options.description !== void 0 ? "description" : null,
91
+ options.cron !== void 0 ? "cron" : null,
92
+ options.configPatch ? "config" : null
93
+ ].filter(Boolean).join(", ") || "none"}`
94
+ );
95
+ }
96
+ }
97
+ });
98
+ }
99
+ export {
100
+ runDetectionsConfigsUpdate
101
+ };
@@ -0,0 +1,128 @@
1
+ import {
2
+ CliExitError,
3
+ ExitCode
4
+ } from "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput,
7
+ toCsvRows
8
+ } from "./chunk-VFT3TD3E.js";
9
+ import {
10
+ createApiClient
11
+ } from "./chunk-H7UKKLCV.js";
12
+ import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-U73SABXK.js";
14
+
15
+ // src/commands/reports/create.ts
16
+ import { readFileSync } from "fs";
17
+ function parseAssetStatus(value) {
18
+ const normalized = value.trim().toUpperCase();
19
+ if (normalized === "BLOCKED" || normalized === "ALLOWED" || normalized === "UNKNOWN") {
20
+ return normalized;
21
+ }
22
+ throw new Error(`Invalid asset status '${value}'. Use BLOCKED, ALLOWED, or UNKNOWN.`);
23
+ }
24
+ var ASSET_STATUS_SUFFIX = /:(BLOCKED|ALLOWED|UNKNOWN)$/i;
25
+ function parseAssetValue(value) {
26
+ const trimmed = value.trim();
27
+ const match = trimmed.match(ASSET_STATUS_SUFFIX);
28
+ if (!match) {
29
+ return { content: trimmed, status: "BLOCKED" };
30
+ }
31
+ const content = trimmed.slice(0, match.index).trim();
32
+ if (!content) {
33
+ throw new Error(`Invalid --asset value '${value}'.`);
34
+ }
35
+ return { content, status: parseAssetStatus(match[1]) };
36
+ }
37
+ function readPayloadFile(payloadFile) {
38
+ const content = readFileSync(payloadFile, "utf-8");
39
+ const parsed = JSON.parse(content);
40
+ return parsed;
41
+ }
42
+ async function runReportsCreate(options) {
43
+ const filePayload = options.payloadFile ? readPayloadFile(options.payloadFile) : void 0;
44
+ const cliAssets = options.assets?.map(parseAssetValue) ?? [];
45
+ const payload = {
46
+ ...filePayload ?? {},
47
+ organizationSlug: options.org ?? filePayload?.organizationSlug,
48
+ title: options.title ?? filePayload?.title,
49
+ description: options.description ?? filePayload?.description,
50
+ contactInfo: options.contactInfo ?? filePayload?.contactInfo,
51
+ attachmentUrls: options.attachmentUrls ?? filePayload?.attachmentUrls,
52
+ externalSubmissionLink: options.externalSubmissionLink ?? filePayload?.externalSubmissionLink,
53
+ userAgent: options.userAgent ?? filePayload?.userAgent,
54
+ referrer: options.referrer ?? filePayload?.referrer,
55
+ assets: cliAssets.length > 0 ? cliAssets : filePayload?.assets ?? [],
56
+ rawAssetsInput: filePayload?.rawAssetsInput,
57
+ externalReporter: filePayload?.externalReporter
58
+ };
59
+ if (!payload.assets || payload.assets.length === 0) {
60
+ throw new CliExitError(
61
+ "At least one asset is required. Use --asset content[:status] or provide assets in --payload-file.",
62
+ ExitCode.USAGE
63
+ );
64
+ }
65
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
66
+ if (options.dryRun) {
67
+ printOutput({
68
+ outputFormat,
69
+ json: {
70
+ dryRun: true,
71
+ mutation: "reports.create",
72
+ payload,
73
+ explanation: options.explain ? "This command creates a report and proposal assets for triage." : void 0
74
+ },
75
+ markdown: [
76
+ "# Dry Run: Report Create",
77
+ "",
78
+ `- Org: ${payload.organizationSlug ?? "from token context"}`,
79
+ `- Title: ${payload.title ?? "(none)"}`,
80
+ `- Assets: ${payload.assets.length}`
81
+ ].join("\n"),
82
+ csv: toCsvRows(
83
+ payload.assets.map((asset) => ({
84
+ content: asset.content,
85
+ status: asset.status ?? "BLOCKED"
86
+ }))
87
+ ),
88
+ human: () => {
89
+ console.log("Dry run only. No report was created.");
90
+ console.log(
91
+ `Would create report for org=${payload.organizationSlug ?? "default"} with ${payload.assets.length} asset(s).`
92
+ );
93
+ }
94
+ });
95
+ return;
96
+ }
97
+ const client = options.apiClient ?? createApiClient();
98
+ const result = await client.createReport(payload);
99
+ printOutput({
100
+ outputFormat,
101
+ json: {
102
+ ...result,
103
+ explanation: options.explain ? "Report creation succeeds after payload validation and report service processing." : void 0
104
+ },
105
+ markdown: [
106
+ "# Report Created",
107
+ "",
108
+ `- Report ID: ${result.id}`,
109
+ `- Organization: ${result.organization?.slug ?? "unknown"}`,
110
+ `- Created at: ${result.createdAt}`
111
+ ].join("\n"),
112
+ human: () => {
113
+ console.log(`Created report ${result.id}`);
114
+ if (result.organization) {
115
+ console.log(`Organization: ${result.organization.slug}`);
116
+ }
117
+ console.log(`Created at: ${result.createdAt}`);
118
+ if (options.explain) {
119
+ console.log(
120
+ `Submitted ${payload.assets.length} asset(s) for report triage and classification.`
121
+ );
122
+ }
123
+ }
124
+ });
125
+ }
126
+ export {
127
+ runReportsCreate
128
+ };
@@ -0,0 +1,80 @@
1
+ import {
2
+ CliExitError,
3
+ ExitCode
4
+ } from "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput,
7
+ toCsvRows
8
+ } from "./chunk-VFT3TD3E.js";
9
+ import {
10
+ createApiClient
11
+ } from "./chunk-H7UKKLCV.js";
12
+ import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-U73SABXK.js";
14
+
15
+ // src/commands/detections/drift.ts
16
+ async function runDetectionsDrift(options) {
17
+ const client = options.apiClient ?? createApiClient();
18
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
19
+ const result = await client.getDetectionDrift({
20
+ slug: options.org,
21
+ source: options.source,
22
+ configIds: options.configId ? [options.configId] : void 0,
23
+ includeDisabled: options.includeDisabled ?? false,
24
+ lookbackHours: options.lookbackHours
25
+ });
26
+ printOutput({
27
+ outputFormat,
28
+ json: {
29
+ ...result,
30
+ explanation: options.explain ? {
31
+ signalTypes: ["zero_results_too_long", "noisy_source", "stale_query"],
32
+ failureCondition: "summary.signalCount > 0"
33
+ } : void 0
34
+ },
35
+ markdown: [
36
+ `# Detection Drift (${options.org})`,
37
+ "",
38
+ `- Checked configs: ${result.summary.checkedConfigs}`,
39
+ `- Signals: ${result.summary.signalCount}`,
40
+ `- Zero results too long: ${result.summary.zeroResultsCount}`,
41
+ `- Noisy source: ${result.summary.noisyCount}`,
42
+ `- Stale query: ${result.summary.staleCount}`,
43
+ "",
44
+ ...result.signals.map(
45
+ (signal) => `- ${signal.severity.toUpperCase()} ${signal.signal} [${signal.source}] #${signal.configId}`
46
+ )
47
+ ].join("\n"),
48
+ csv: toCsvRows(
49
+ result.signals.map((signal) => ({
50
+ signal: signal.signal,
51
+ severity: signal.severity,
52
+ configId: signal.configId,
53
+ source: signal.source,
54
+ title: signal.title,
55
+ status: signal.status
56
+ }))
57
+ ),
58
+ human: () => {
59
+ console.log(
60
+ `Detection drift for ${options.org}: ${result.summary.signalCount} signal(s) across ${result.summary.checkedConfigs} config(s)`
61
+ );
62
+ for (const signal of result.signals) {
63
+ console.log(
64
+ `- ${signal.severity.toUpperCase()} ${signal.signal} [${signal.source}] config ${signal.configId}`
65
+ );
66
+ }
67
+ if (options.explain) {
68
+ console.log(
69
+ "Signals are based on zero results over threshold, noisy result/allow ratios, and stale query updates."
70
+ );
71
+ }
72
+ }
73
+ });
74
+ if (result.summary.signalCount > 0) {
75
+ throw new CliExitError("Detection drift signals found.", ExitCode.CHECK_FAILED);
76
+ }
77
+ }
78
+ export {
79
+ runDetectionsDrift
80
+ };
@@ -0,0 +1,93 @@
1
+ import {
2
+ printOutput,
3
+ toCsvRows
4
+ } from "./chunk-VFT3TD3E.js";
5
+ import {
6
+ DateTime,
7
+ createApiClient
8
+ } from "./chunk-H7UKKLCV.js";
9
+ import "./chunk-EEG7T6WT.js";
10
+ import "./chunk-U73SABXK.js";
11
+
12
+ // src/lib/date-range.ts
13
+ function resolveDateRange({
14
+ from,
15
+ to,
16
+ thisWeek
17
+ }) {
18
+ if (from && to) {
19
+ return {
20
+ startDate: DateTime.fromISO(from).toUTC().toISO() ?? from,
21
+ endDate: DateTime.fromISO(to).toUTC().toISO() ?? to
22
+ };
23
+ }
24
+ if (from && !to) {
25
+ const now2 = DateTime.now().toUTC();
26
+ return {
27
+ startDate: DateTime.fromISO(from).toUTC().toISO() ?? from,
28
+ endDate: now2.toISO() ?? ""
29
+ };
30
+ }
31
+ if (!from && to) {
32
+ const end = DateTime.fromISO(to).toUTC();
33
+ return {
34
+ startDate: end.minus({ days: 7 }).toISO() ?? "",
35
+ endDate: end.toISO() ?? to
36
+ };
37
+ }
38
+ if (thisWeek) {
39
+ const now2 = DateTime.now().toUTC();
40
+ return {
41
+ startDate: now2.startOf("week").toISO() ?? now2.minus({ days: 7 }).toISO() ?? "",
42
+ endDate: now2.endOf("week").toISO() ?? now2.toISO() ?? ""
43
+ };
44
+ }
45
+ const now = DateTime.now().toUTC();
46
+ return {
47
+ startDate: now.minus({ days: 7 }).toISO() ?? "",
48
+ endDate: now.toISO() ?? ""
49
+ };
50
+ }
51
+
52
+ // src/commands/metrics/found.ts
53
+ async function runMetricsFound(options) {
54
+ const range = resolveDateRange({
55
+ from: options.from,
56
+ to: options.to,
57
+ thisWeek: options.thisWeek
58
+ });
59
+ const client = options.apiClient ?? createApiClient();
60
+ const result = await client.getMetricsFound({
61
+ slug: options.org,
62
+ startDate: range.startDate,
63
+ endDate: range.endDate,
64
+ brandIds: options.brandIds
65
+ });
66
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
67
+ printOutput({
68
+ outputFormat,
69
+ json: result,
70
+ markdown: [
71
+ `# Found Threats (${options.org})`,
72
+ "",
73
+ `- Metric: ${result.metric}`,
74
+ `- Found: ${result.found}`,
75
+ `- Range: ${range.startDate} -> ${range.endDate}`
76
+ ].join("\n"),
77
+ csv: toCsvRows([
78
+ {
79
+ metric: result.metric,
80
+ found: result.found,
81
+ startDate: range.startDate,
82
+ endDate: range.endDate
83
+ }
84
+ ]),
85
+ human: () => {
86
+ console.log(`Found threats (${result.metric}) for ${options.org}: ${result.found}`);
87
+ console.log(`Range: ${range.startDate} -> ${range.endDate}`);
88
+ }
89
+ });
90
+ }
91
+ export {
92
+ runMetricsFound
93
+ };
@@ -0,0 +1,94 @@
1
+ import {
2
+ CliExitError,
3
+ ExitCode
4
+ } from "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput,
7
+ toCsvRows
8
+ } from "./chunk-VFT3TD3E.js";
9
+ import {
10
+ createApiClient
11
+ } from "./chunk-H7UKKLCV.js";
12
+ import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-U73SABXK.js";
14
+
15
+ // src/commands/detections/healthcheck.ts
16
+ async function runDetectionsHealthcheck(options) {
17
+ const client = options.apiClient ?? createApiClient();
18
+ const result = await client.validateDetectionConfigs({
19
+ slug: options.org,
20
+ source: options.source,
21
+ minResults: options.minResults,
22
+ lookbackHours: options.lookbackHours,
23
+ runBeforeValidate: options.run ?? false,
24
+ includeDisabled: options.includeDisabled ?? false
25
+ });
26
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
27
+ const csv = toCsvRows(
28
+ result.validations.map((validation) => ({
29
+ configId: validation.configId,
30
+ source: validation.source,
31
+ status: validation.status,
32
+ valid: validation.valid,
33
+ ran: validation.ran,
34
+ runOk: validation.runOk,
35
+ recentResultCount: validation.recentResultCount,
36
+ runMessage: validation.runMessage
37
+ }))
38
+ );
39
+ const markdown = [
40
+ `# Detection Healthcheck (${options.org})`,
41
+ "",
42
+ `- Checked: ${result.summary.checkedConfigs}`,
43
+ `- Passing: ${result.summary.passingConfigs}`,
44
+ `- Failing: ${result.summary.failingConfigs}`,
45
+ `- Lookback hours: ${result.summary.lookbackHours}`,
46
+ `- Min results: ${result.summary.minResults}`,
47
+ "",
48
+ ...result.validations.map((validation) => {
49
+ const runState = validation.ran ? validation.runOk ? "run-ok" : "run-failed" : "not-run";
50
+ return `- ${validation.valid ? "PASS" : "FAIL"} [${validation.source}] #${validation.configId} results=${validation.recentResultCount} ${runState}`;
51
+ })
52
+ ].join("\n");
53
+ printOutput({
54
+ outputFormat,
55
+ json: {
56
+ ...result,
57
+ explanation: options.explain ? {
58
+ checkType: "detection_healthcheck",
59
+ failureCondition: "result.ok === false"
60
+ } : void 0
61
+ },
62
+ markdown,
63
+ csv,
64
+ human: () => {
65
+ console.log(
66
+ `Checked ${result.summary.checkedConfigs} config(s), ${result.summary.passingConfigs} passing, ${result.summary.failingConfigs} failing`
67
+ );
68
+ console.log(
69
+ `Lookback: ${result.summary.lookbackHours}h, minimum results per config: ${result.summary.minResults}`
70
+ );
71
+ for (const validation of result.validations) {
72
+ const icon = validation.valid ? "\u2713" : "\u2717";
73
+ const runState = validation.ran ? validation.runOk ? "run-ok" : "run-failed" : "not-run";
74
+ console.log(
75
+ `${icon} [${validation.source}] config ${validation.configId} (${validation.status}) results=${validation.recentResultCount} ${runState}`
76
+ );
77
+ if (validation.runMessage) {
78
+ console.log(` ${validation.runMessage}`);
79
+ }
80
+ }
81
+ if (options.explain) {
82
+ console.log(
83
+ "Healthcheck fails when any config is below min-results or run check fails."
84
+ );
85
+ }
86
+ }
87
+ });
88
+ if (!result.ok) {
89
+ throw new CliExitError("Detection healthcheck failed.", ExitCode.CHECK_FAILED);
90
+ }
91
+ }
92
+ export {
93
+ runDetectionsHealthcheck
94
+ };
@@ -0,0 +1,94 @@
1
+ import {
2
+ CliExitError,
3
+ ExitCode
4
+ } from "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput,
7
+ toCsvRows
8
+ } from "./chunk-VFT3TD3E.js";
9
+ import {
10
+ createApiClient
11
+ } from "./chunk-H7UKKLCV.js";
12
+ import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-U73SABXK.js";
14
+
15
+ // src/commands/reports/list.ts
16
+ function toReportRow(report) {
17
+ return {
18
+ id: report.id,
19
+ title: report.title,
20
+ status: report.status ?? "TODO",
21
+ createdAt: report.createdAt,
22
+ updatedAt: report.updatedAt,
23
+ assets: report.proposals.length,
24
+ reporter: report.externalReporter?.displayName ?? report.reporter?.fullName ?? report.externalReporter?.platform ?? "Unknown"
25
+ };
26
+ }
27
+ async function runReportsList(options) {
28
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
29
+ const limit = options.limit ?? 10;
30
+ if (limit < 1 || limit > 20) {
31
+ throw new CliExitError(
32
+ "reports list requires --limit between 1 and 20.",
33
+ ExitCode.USAGE
34
+ );
35
+ }
36
+ const client = options.apiClient ?? createApiClient();
37
+ const result = await client.listOrganizationReports({
38
+ slug: options.org,
39
+ limit,
40
+ cursor: options.cursor,
41
+ status: options.status,
42
+ searchQuery: options.searchQuery
43
+ });
44
+ const rows = result.reports.map(toReportRow);
45
+ printOutput({
46
+ outputFormat,
47
+ json: {
48
+ ...result,
49
+ explanation: options.explain ? {
50
+ listMode: "organization_reports",
51
+ filters: {
52
+ limit,
53
+ cursor: options.cursor,
54
+ status: options.status,
55
+ searchQuery: options.searchQuery
56
+ }
57
+ } : void 0
58
+ },
59
+ markdown: [
60
+ `# Reports (${options.org})`,
61
+ "",
62
+ `- Returned: ${result.reports.length}`,
63
+ `- Next cursor: ${result.nextCursor ?? "none"}`,
64
+ "",
65
+ ...rows.map(
66
+ (row) => `- #${row.id} ${row.status} | ${row.title} | assets=${row.assets} | created=${row.createdAt}`
67
+ )
68
+ ].join("\n"),
69
+ csv: toCsvRows(rows),
70
+ human: () => {
71
+ if (result.reports.length === 0) {
72
+ console.log("No reports found.");
73
+ return;
74
+ }
75
+ console.log(`Reports for ${options.org} (returned ${result.reports.length})`);
76
+ for (const row of rows) {
77
+ console.log(
78
+ `#${row.id} [${row.status}] ${row.title} assets=${row.assets} created=${row.createdAt}`
79
+ );
80
+ }
81
+ if (result.nextCursor !== null) {
82
+ console.log(`Next cursor: ${result.nextCursor}`);
83
+ }
84
+ if (options.explain) {
85
+ console.log(
86
+ "Use --cursor <nextCursor> to paginate older reports and confirm creation history."
87
+ );
88
+ }
89
+ }
90
+ });
91
+ }
92
+ export {
93
+ runReportsList
94
+ };
@@ -0,0 +1,40 @@
1
+ import {
2
+ PRESETS
3
+ } from "./chunk-D2QGXYXZ.js";
4
+ import "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput,
7
+ toCsvRows
8
+ } from "./chunk-VFT3TD3E.js";
9
+ import "./chunk-H7UKKLCV.js";
10
+ import "./chunk-EEG7T6WT.js";
11
+ import "./chunk-U73SABXK.js";
12
+
13
+ // src/commands/presets/list.ts
14
+ async function runPresetsList({ outputFormat }) {
15
+ printOutput({
16
+ outputFormat,
17
+ json: { presets: PRESETS },
18
+ markdown: [
19
+ "# Available Presets",
20
+ "",
21
+ ...PRESETS.map((preset) => `- \`${preset.id}\`: ${preset.description}`)
22
+ ].join("\n"),
23
+ csv: toCsvRows(
24
+ PRESETS.map((preset) => ({
25
+ id: preset.id,
26
+ title: preset.title,
27
+ description: preset.description
28
+ }))
29
+ ),
30
+ human: () => {
31
+ console.log("Available presets:");
32
+ for (const preset of PRESETS) {
33
+ console.log(`- ${preset.id}: ${preset.description}`);
34
+ }
35
+ }
36
+ });
37
+ }
38
+ export {
39
+ runPresetsList
40
+ };