@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/README.md +224 -0
- package/dist/breakdown-JVN66HY3.js +69 -0
- package/dist/chunk-D2QGXYXZ.js +126 -0
- package/dist/chunk-E2LAMILJ.js +48 -0
- package/dist/chunk-EEG7T6WT.js +287 -0
- package/dist/chunk-H7UKKLCV.js +6572 -0
- package/dist/chunk-IUZB3DQW.js +237 -0
- package/dist/chunk-JCMWDZYY.js +39 -0
- package/dist/chunk-U73SABXK.js +46 -0
- package/dist/chunk-VFT3TD3E.js +82 -0
- package/dist/cli.js +895 -0
- package/dist/completions-EGQIARFC.js +12 -0
- package/dist/config-Z3TASRME.js +10 -0
- package/dist/configs-update-BK2S6AZ6.js +101 -0
- package/dist/create-4SQUBQI7.js +128 -0
- package/dist/drift-VRZKQC4P.js +80 -0
- package/dist/found-4O3AISNI.js +93 -0
- package/dist/healthcheck-7DR5MGEQ.js +94 -0
- package/dist/list-6L7XR4SZ.js +94 -0
- package/dist/list-HZAHEHDM.js +40 -0
- package/dist/list-IBMM562A.js +139 -0
- package/dist/list-json-TPBLJBD3.js +24 -0
- package/dist/login-G7LPHKDR.js +162 -0
- package/dist/login-json-LKB72OFY.js +71 -0
- package/dist/logout-LA7VEKON.js +25 -0
- package/dist/logout-json-4GIJZJ46.js +18 -0
- package/dist/run-PABQKATZ.js +112 -0
- package/dist/run-U62KVNTH.js +34 -0
- package/dist/setup-skill-U24CJZ6T.js +211 -0
- package/dist/snapshot-JEVDTE74.js +88 -0
- package/dist/summary-YG5NYIOA.js +61 -0
- package/dist/validate-PI7GPT5I.js +79 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# ChainPatrol CLI
|
|
2
|
+
|
|
3
|
+
Terminal interface for ChainPatrol threat detection.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g chainpatrol
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
chainpatrol login
|
|
15
|
+
chainpatrol configs list --org my-org
|
|
16
|
+
chainpatrol --json configs list --org my-org
|
|
17
|
+
chainpatrol detections healthcheck --org my-org --run
|
|
18
|
+
chainpatrol detections drift --org my-org --lookback-hours 168
|
|
19
|
+
chainpatrol detections configs update --org my-org --config-id 42 --enable --set query=brand-phishing
|
|
20
|
+
chainpatrol metrics found --org my-org --this-week
|
|
21
|
+
chainpatrol reports create --org my-org --title "Phishing report" --asset "https://phish.example:BLOCKED"
|
|
22
|
+
chainpatrol reports list --org my-org --limit 10
|
|
23
|
+
chainpatrol metrics breakdown --org my-org --by type --from 2026-01-01 --to 2026-01-08
|
|
24
|
+
chainpatrol queues snapshot --all --output markdown
|
|
25
|
+
chainpatrol presets run cs-weekly-health --org my-org --output json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Global agent-first flags
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# preview mutations without sending API write requests
|
|
32
|
+
chainpatrol reports create --org my-org --title "Preview" --asset "https://phish.example:BLOCKED" --dry-run
|
|
33
|
+
|
|
34
|
+
# include recommendation context and emit markdown
|
|
35
|
+
chainpatrol detections drift --org my-org --explain --output markdown
|
|
36
|
+
|
|
37
|
+
# CSV handoff for docs/slides
|
|
38
|
+
chainpatrol queues snapshot --all --output csv
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Detection commands
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# run a full healthcheck with source validation
|
|
45
|
+
chainpatrol detections healthcheck --org my-org --run --min-results 1 --lookback-hours 168
|
|
46
|
+
|
|
47
|
+
# validate only
|
|
48
|
+
chainpatrol detections validate --org my-org --source urlscan
|
|
49
|
+
|
|
50
|
+
# run configs on demand
|
|
51
|
+
chainpatrol detections run --org my-org --source google_search
|
|
52
|
+
|
|
53
|
+
# detect noisy/stale/zero-result drift
|
|
54
|
+
chainpatrol detections drift --org my-org --lookback-hours 168
|
|
55
|
+
|
|
56
|
+
# update one config
|
|
57
|
+
chainpatrol detections configs update --org my-org --config-id 42 --disable
|
|
58
|
+
chainpatrol detections configs update --org my-org --config-id 42 --set query=brand-phishing --set maxResults=100
|
|
59
|
+
|
|
60
|
+
# dry-run config update
|
|
61
|
+
chainpatrol detections configs update --org my-org --config-id 42 --set query=brand-phishing --dry-run --output json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Metrics commands
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# summary metrics
|
|
68
|
+
chainpatrol metrics summary --org my-org --from 2026-01-01 --to 2026-01-08
|
|
69
|
+
|
|
70
|
+
# default "found" metric = customer-facing new threats
|
|
71
|
+
chainpatrol metrics found --org my-org --this-week
|
|
72
|
+
|
|
73
|
+
# grouped metrics
|
|
74
|
+
chainpatrol metrics breakdown --org my-org --by day --from 2026-01-01 --to 2026-01-08
|
|
75
|
+
chainpatrol metrics breakdown --org my-org --by type --from 2026-01-01 --to 2026-01-08
|
|
76
|
+
chainpatrol metrics breakdown --org my-org --by brand --from 2026-01-01 --to 2026-01-08
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Reports commands
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# create report with inline assets
|
|
83
|
+
chainpatrol reports create --org my-org --title "New phishing campaign" --description "Detected from CLI" --asset "https://phish.example:BLOCKED"
|
|
84
|
+
|
|
85
|
+
# add multiple assets and attachments
|
|
86
|
+
chainpatrol reports create --org my-org --title "Batch report" --asset "https://a.example:BLOCKED" --asset "https://b.example:ALLOWED" --attachment-url "https://cdn.example/screenshot1.png"
|
|
87
|
+
|
|
88
|
+
# use full payload for advanced fields (externalReporter, enrichments, rawAssetsInput)
|
|
89
|
+
chainpatrol reports create --payload-file ./report-payload.json --json
|
|
90
|
+
|
|
91
|
+
# dry-run then apply
|
|
92
|
+
chainpatrol reports create --org my-org --title "Batch report" --asset "https://a.example:BLOCKED" --dry-run --output markdown
|
|
93
|
+
chainpatrol reports create --org my-org --title "Batch report" --asset "https://a.example:BLOCKED"
|
|
94
|
+
|
|
95
|
+
# confirm recently created reports
|
|
96
|
+
chainpatrol reports list --org my-org --limit 5
|
|
97
|
+
chainpatrol reports list --org my-org --status TODO --search "Phishing report"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Operations commands
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# org-level queue snapshot
|
|
104
|
+
chainpatrol queues snapshot --org my-org
|
|
105
|
+
|
|
106
|
+
# all-org queue snapshot
|
|
107
|
+
chainpatrol queues snapshot --all --window-hours 168 --output markdown
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Presets
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# list available presets
|
|
114
|
+
chainpatrol presets list
|
|
115
|
+
|
|
116
|
+
# run CS weekly health workflow
|
|
117
|
+
chainpatrol presets run cs-weekly-health --org my-org --output json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### AI-friendly workflows
|
|
121
|
+
|
|
122
|
+
Use `--json` in automations and agent prompts:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
chainpatrol --json detections healthcheck --org my-org --run --min-results 1
|
|
126
|
+
chainpatrol --json metrics found --org my-org --this-week
|
|
127
|
+
chainpatrol --json metrics breakdown --org my-org --by type --from 2026-01-01 --to 2026-01-08
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Deterministic exit codes:
|
|
131
|
+
|
|
132
|
+
- `0` success
|
|
133
|
+
- `2` auth errors
|
|
134
|
+
- `3` usage/argument errors
|
|
135
|
+
- `4` not found
|
|
136
|
+
- `5` forbidden
|
|
137
|
+
- `6` check failure (health/drift/queue thresholds)
|
|
138
|
+
- `7` mutation partial failure
|
|
139
|
+
- `8` network/API unavailable
|
|
140
|
+
- `1` unknown fallback
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
### Build and install locally
|
|
145
|
+
|
|
146
|
+
From the repo root:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
yarn cli:local:install
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
And set up the config:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
mkdir -p ~/.chainpatrol
|
|
156
|
+
echo '{"apiUrl": "https://api.chainpatrol.dev"}' > ~/.chainpatrol/config.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Install the Claude Code skill
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
chainpatrol setup
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
This copies the `/chainpatrol` skill to `~/.claude/skills/chainpatrol/SKILL.md` so Claude Code can help you use the CLI in any project.
|
|
166
|
+
|
|
167
|
+
### Testing locally
|
|
168
|
+
|
|
169
|
+
Install the CLI and point it at your local dev server:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
yarn cli:local:install
|
|
173
|
+
chainpatrol setup
|
|
174
|
+
|
|
175
|
+
mkdir -p ~/.chainpatrol
|
|
176
|
+
echo '{"apiUrl": "http://localhost:3000"}' > ~/.chainpatrol/config.json
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
XDG path is also supported:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
mkdir -p ~/.config/chainpatrol
|
|
183
|
+
echo '{"apiUrl": "http://localhost:3000"}' > ~/.config/chainpatrol/config.json
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Start the dev server (`yarn dev`), then test:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# authenticate
|
|
190
|
+
chainpatrol login
|
|
191
|
+
|
|
192
|
+
# list configs (sets default org)
|
|
193
|
+
chainpatrol configs list --org chainpatrol
|
|
194
|
+
|
|
195
|
+
# uses saved default org
|
|
196
|
+
chainpatrol configs list
|
|
197
|
+
|
|
198
|
+
# JSON output
|
|
199
|
+
chainpatrol configs list --json
|
|
200
|
+
|
|
201
|
+
# detection healthcheck
|
|
202
|
+
chainpatrol detections healthcheck --org chainpatrol --run
|
|
203
|
+
|
|
204
|
+
# metrics checks
|
|
205
|
+
chainpatrol metrics found --org chainpatrol --this-week
|
|
206
|
+
chainpatrol metrics breakdown --org chainpatrol --by type --from 2026-01-01 --to 2026-01-08
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Uninstall
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
chainpatrol uninstall # remove Claude Code skill
|
|
213
|
+
npm unlink chainpatrol # remove global CLI link
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Scripts
|
|
217
|
+
|
|
218
|
+
| Script | Description |
|
|
219
|
+
| ---------------- | ----------------------------- |
|
|
220
|
+
| `yarn dev` | Run CLI from source via tsx |
|
|
221
|
+
| `yarn dev:setup` | Run setup command from source |
|
|
222
|
+
| `yarn build` | Build with tsup |
|
|
223
|
+
| `yarn typecheck` | Type check |
|
|
224
|
+
| `yarn test` | Run unit tests |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
printOutput,
|
|
3
|
+
toCsvRows
|
|
4
|
+
} from "./chunk-VFT3TD3E.js";
|
|
5
|
+
import {
|
|
6
|
+
createApiClient
|
|
7
|
+
} from "./chunk-H7UKKLCV.js";
|
|
8
|
+
import "./chunk-EEG7T6WT.js";
|
|
9
|
+
import "./chunk-U73SABXK.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/metrics/breakdown.ts
|
|
12
|
+
async function runMetricsBreakdown(options) {
|
|
13
|
+
const client = options.apiClient ?? createApiClient();
|
|
14
|
+
const result = await client.getMetricsBreakdown({
|
|
15
|
+
slug: options.org,
|
|
16
|
+
by: options.by,
|
|
17
|
+
startDate: options.from,
|
|
18
|
+
endDate: options.to,
|
|
19
|
+
brandIds: options.brandIds
|
|
20
|
+
});
|
|
21
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
22
|
+
printOutput({
|
|
23
|
+
outputFormat,
|
|
24
|
+
json: result,
|
|
25
|
+
markdown: [
|
|
26
|
+
`# Metrics Breakdown (${options.org})`,
|
|
27
|
+
"",
|
|
28
|
+
`- Grouped by: ${result.by}`,
|
|
29
|
+
"",
|
|
30
|
+
...result.points.map((point) => {
|
|
31
|
+
if (result.by === "day") {
|
|
32
|
+
return `- ${point.date ?? point.key}: ${point.count}`;
|
|
33
|
+
}
|
|
34
|
+
if (result.by === "type") {
|
|
35
|
+
return `- ${point.type ?? point.key}: ${point.count}`;
|
|
36
|
+
}
|
|
37
|
+
return `- ${point.brandName ?? point.brandSlug ?? point.key}: ${point.count}`;
|
|
38
|
+
})
|
|
39
|
+
].join("\n"),
|
|
40
|
+
csv: toCsvRows(
|
|
41
|
+
result.points.map((point) => ({
|
|
42
|
+
key: point.key,
|
|
43
|
+
count: point.count,
|
|
44
|
+
date: point.date ?? null,
|
|
45
|
+
type: point.type ?? null,
|
|
46
|
+
brandId: point.brandId ?? null,
|
|
47
|
+
brandSlug: point.brandSlug ?? null,
|
|
48
|
+
brandName: point.brandName ?? null
|
|
49
|
+
}))
|
|
50
|
+
),
|
|
51
|
+
human: () => {
|
|
52
|
+
console.log(`Metrics breakdown for ${options.org} by ${result.by}`);
|
|
53
|
+
for (const point of result.points) {
|
|
54
|
+
if (result.by === "day") {
|
|
55
|
+
console.log(`${point.date ?? point.key}: ${point.count}`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (result.by === "type") {
|
|
59
|
+
console.log(`${point.type ?? point.key}: ${point.count}`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
console.log(`${point.brandName ?? point.brandSlug ?? point.key}: ${point.count}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
runMetricsBreakdown
|
|
69
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
DateTime,
|
|
11
|
+
createApiClient
|
|
12
|
+
} from "./chunk-H7UKKLCV.js";
|
|
13
|
+
|
|
14
|
+
// src/presets/index.ts
|
|
15
|
+
var PRESETS = [
|
|
16
|
+
{
|
|
17
|
+
id: "cs-weekly-health",
|
|
18
|
+
title: "CS Weekly Health",
|
|
19
|
+
description: "Weekly customer health package with found threats, summary metrics, and detection validation."
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
function getPresetDefinition(id) {
|
|
23
|
+
return PRESETS.find((preset) => preset.id === id);
|
|
24
|
+
}
|
|
25
|
+
async function runPreset({
|
|
26
|
+
presetId,
|
|
27
|
+
org,
|
|
28
|
+
outputFormat,
|
|
29
|
+
explain,
|
|
30
|
+
apiClient
|
|
31
|
+
}) {
|
|
32
|
+
const client = apiClient ?? createApiClient();
|
|
33
|
+
if (presetId !== "cs-weekly-health") {
|
|
34
|
+
throw new CliExitError(`Unknown preset '${presetId}'.`, ExitCode.USAGE);
|
|
35
|
+
}
|
|
36
|
+
const now = DateTime.now().toUTC();
|
|
37
|
+
const startDate = now.startOf("week").toISO();
|
|
38
|
+
const endDate = now.toISO();
|
|
39
|
+
if (!startDate || !endDate) {
|
|
40
|
+
throw new CliExitError("Failed to resolve preset date range.", ExitCode.UNKNOWN);
|
|
41
|
+
}
|
|
42
|
+
const [summary, found, validation] = await Promise.all([
|
|
43
|
+
client.getMetricsSummary({
|
|
44
|
+
slug: org,
|
|
45
|
+
startDate,
|
|
46
|
+
endDate
|
|
47
|
+
}),
|
|
48
|
+
client.getMetricsFound({
|
|
49
|
+
slug: org,
|
|
50
|
+
startDate,
|
|
51
|
+
endDate
|
|
52
|
+
}),
|
|
53
|
+
client.validateDetectionConfigs({
|
|
54
|
+
slug: org,
|
|
55
|
+
lookbackHours: 168,
|
|
56
|
+
minResults: 1,
|
|
57
|
+
runBeforeValidate: false,
|
|
58
|
+
includeDisabled: false
|
|
59
|
+
})
|
|
60
|
+
]);
|
|
61
|
+
const payload = {
|
|
62
|
+
presetId,
|
|
63
|
+
org,
|
|
64
|
+
range: { startDate, endDate },
|
|
65
|
+
summary: summary.metrics,
|
|
66
|
+
found,
|
|
67
|
+
detectionValidation: validation.summary,
|
|
68
|
+
failedDetectionConfigs: validation.validations.filter((item) => !item.valid)
|
|
69
|
+
};
|
|
70
|
+
printOutput({
|
|
71
|
+
outputFormat,
|
|
72
|
+
json: {
|
|
73
|
+
...payload,
|
|
74
|
+
explanation: explain ? "Combines customer-facing weekly threat metrics with detector healthcheck coverage." : void 0
|
|
75
|
+
},
|
|
76
|
+
markdown: [
|
|
77
|
+
`# Preset: CS Weekly Health (${org})`,
|
|
78
|
+
"",
|
|
79
|
+
`- Range: ${startDate} -> ${endDate}`,
|
|
80
|
+
`- Reports: ${summary.metrics.reports}`,
|
|
81
|
+
`- New threats: ${summary.metrics.newThreats}`,
|
|
82
|
+
`- Found metric: ${found.found}`,
|
|
83
|
+
`- Detection configs checked: ${validation.summary.checkedConfigs}`,
|
|
84
|
+
`- Detection configs failing: ${validation.summary.failingConfigs}`
|
|
85
|
+
].join("\n"),
|
|
86
|
+
csv: toCsvRows([
|
|
87
|
+
{
|
|
88
|
+
org,
|
|
89
|
+
startDate,
|
|
90
|
+
endDate,
|
|
91
|
+
reports: summary.metrics.reports,
|
|
92
|
+
newThreats: summary.metrics.newThreats,
|
|
93
|
+
found: found.found,
|
|
94
|
+
checkedConfigs: validation.summary.checkedConfigs,
|
|
95
|
+
failingConfigs: validation.summary.failingConfigs
|
|
96
|
+
}
|
|
97
|
+
]),
|
|
98
|
+
human: () => {
|
|
99
|
+
console.log(`Preset "${presetId}" for ${org}`);
|
|
100
|
+
console.log(`Range: ${startDate} -> ${endDate}`);
|
|
101
|
+
console.log(
|
|
102
|
+
`Metrics: reports=${summary.metrics.reports} newThreats=${summary.metrics.newThreats} watchlisted=${summary.metrics.threatsWatchlisted}`
|
|
103
|
+
);
|
|
104
|
+
console.log(
|
|
105
|
+
`Found threats this week: ${found.found} | Detector health failures: ${validation.summary.failingConfigs}`
|
|
106
|
+
);
|
|
107
|
+
if (explain) {
|
|
108
|
+
console.log(
|
|
109
|
+
"This preset highlights weekly CS health outcomes plus detector reliability checks."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
if (!validation.ok) {
|
|
115
|
+
throw new CliExitError(
|
|
116
|
+
"Preset completed with detector health failures.",
|
|
117
|
+
ExitCode.CHECK_FAILED
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
PRESETS,
|
|
124
|
+
getPresetDefinition,
|
|
125
|
+
runPreset
|
|
126
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/lib/exit-codes.ts
|
|
2
|
+
var ExitCode = {
|
|
3
|
+
SUCCESS: 0,
|
|
4
|
+
UNKNOWN: 1,
|
|
5
|
+
AUTH: 2,
|
|
6
|
+
USAGE: 3,
|
|
7
|
+
NOT_FOUND: 4,
|
|
8
|
+
FORBIDDEN: 5,
|
|
9
|
+
CHECK_FAILED: 6,
|
|
10
|
+
MUTATION_PARTIAL_FAILURE: 7,
|
|
11
|
+
NETWORK_UNAVAILABLE: 8
|
|
12
|
+
};
|
|
13
|
+
var CliExitError = class extends Error {
|
|
14
|
+
exitCode;
|
|
15
|
+
constructor(message, exitCode) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "CliExitError";
|
|
18
|
+
this.exitCode = exitCode;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function mapErrorToExitCode(error) {
|
|
22
|
+
if (error instanceof CliExitError) {
|
|
23
|
+
return error.exitCode;
|
|
24
|
+
}
|
|
25
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
26
|
+
if (message.includes("authentication failed") || message.includes("run `chainpatrol login`")) {
|
|
27
|
+
return ExitCode.AUTH;
|
|
28
|
+
}
|
|
29
|
+
if (message.includes("permission") || message.includes("forbidden")) {
|
|
30
|
+
return ExitCode.FORBIDDEN;
|
|
31
|
+
}
|
|
32
|
+
if (message.includes("not found")) {
|
|
33
|
+
return ExitCode.NOT_FOUND;
|
|
34
|
+
}
|
|
35
|
+
if (message.includes("cannot connect") || message.includes("network error") || message.includes("timed out") || message.includes("temporarily unavailable")) {
|
|
36
|
+
return ExitCode.NETWORK_UNAVAILABLE;
|
|
37
|
+
}
|
|
38
|
+
if (message.includes("usage") || message.includes("requires --") || message.includes("unknown command")) {
|
|
39
|
+
return ExitCode.USAGE;
|
|
40
|
+
}
|
|
41
|
+
return ExitCode.UNKNOWN;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
ExitCode,
|
|
46
|
+
CliExitError,
|
|
47
|
+
mapErrorToExitCode
|
|
48
|
+
};
|