@chainpatrol/cli 0.9.0 → 0.11.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/CHANGELOG.md +36 -0
- package/README.md +16 -2
- package/dist/{breakdown-63FAOVL7.js → breakdown-MM5GSZKV.js} +1 -1
- package/dist/check-GDKUHVQI.js +203 -0
- package/dist/{chunk-BJISZ3CY.js → chunk-37KYJWB3.js} +1 -1
- package/dist/{chunk-P4L4N5LM.js → chunk-EPKNZRX6.js} +34 -5
- package/dist/{chunk-RIKR2WFT.js → chunk-SX3L6NKR.js} +85 -4
- package/dist/{chunk-Z76CUWSS.js → chunk-YXRFUYA6.js} +13 -2
- package/dist/cli.js +146 -37
- package/dist/{completions-D6SOLU2D.js → completions-62OW3B3Y.js} +1 -1
- package/dist/{configs-update-2IOSKZH5.js → configs-update-7X7657PB.js} +1 -1
- package/dist/{create-EWS3SFCH.js → create-FL4QQTPN.js} +1 -1
- package/dist/{drift-Q3VG3XG3.js → drift-JKBHWWFU.js} +1 -1
- package/dist/{found-22B7RZT5.js → found-3WI4XQG4.js} +1 -1
- package/dist/{healthcheck-C3AIMUJT.js → healthcheck-EVQVPGWW.js} +1 -1
- package/dist/{list-IA4CSOIY.js → list-5DN6X3DP.js} +1 -1
- package/dist/{list-DNRWKM5O.js → list-BLNIE4GL.js} +1 -1
- package/dist/{list-AYHDFSQG.js → list-E5XNR2YG.js} +1 -1
- package/dist/list-JPG2ZSR4.js +137 -0
- package/dist/{list-HFWSMLGJ.js → list-ULHPOUJV.js} +2 -2
- package/dist/{list-SDBLGBVW.js → list-XDGU6SJQ.js} +1 -1
- package/dist/{list-json-CEPGVUGF.js → list-json-R2MIXADX.js} +1 -1
- package/dist/organization-MJRKWC4Z.js +75 -0
- package/dist/{run-AQMJRFGL.js → run-2YZZGZYN.js} +1 -1
- package/dist/{run-YTWNQD5X.js → run-5E5EC3MP.js} +1 -1
- package/dist/{run-WJGHJPXN.js → run-EUVRC72M.js} +2 -2
- package/dist/{setup-skill-DR4KGQMG.js → setup-skill-HAENFIFQ.js} +2 -2
- package/dist/{snapshot-C5MZWJTW.js → snapshot-3FUJICJJ.js} +1 -1
- package/dist/{summary-NQDZQEOD.js → summary-RVYQUKWV.js} +1 -1
- package/dist/{validate-5DVPSXJP.js → validate-UOVKEAJM.js} +1 -1
- package/package.json +1 -1
- package/dist/check-YZRIAUOK.js +0 -85
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @chainpatrol/cli
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 21e4455: Add `chainpatrol takedowns list` and `chainpatrol metrics organization`
|
|
8
|
+
commands so agents can pull per-takedown detail and org-scoped metrics
|
|
9
|
+
from the CLI instead of stopping at `queues snapshot`'s aggregate
|
|
10
|
+
counts.
|
|
11
|
+
|
|
12
|
+
`takedowns list` wraps `POST /takedowns/list` with filters for status,
|
|
13
|
+
asset type, liveness, date range, search, sort, and pagination, and
|
|
14
|
+
emits the standard human/json/markdown/csv shapes. `metrics
|
|
15
|
+
organization` wraps the documented `GET /organization/metrics`
|
|
16
|
+
endpoint, complementing the existing `metrics summary`/`found`/
|
|
17
|
+
`breakdown` subcommands.
|
|
18
|
+
|
|
19
|
+
Both endpoints now also accept a user-session bearer token (pass
|
|
20
|
+
`--org <slug>`) in addition to an API key, so customer-success operators
|
|
21
|
+
logged in via `chainpatrol login` can use them without provisioning a
|
|
22
|
+
key.
|
|
23
|
+
|
|
24
|
+
## 0.10.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- 034096d: `chainpatrol asset check` now accepts multiple assets in a single
|
|
29
|
+
invocation — either as positional arguments
|
|
30
|
+
(`chainpatrol asset check a.example b.example c.example`) or via
|
|
31
|
+
repeated `--asset` flags. Lookups run in parallel (concurrency 10) and
|
|
32
|
+
the JSON output for bulk calls is
|
|
33
|
+
`{ results: [...], summary: { checked, blocked, allowed, unknown, errored } }`.
|
|
34
|
+
Single-asset JSON keeps the existing flat shape for back-compat. The
|
|
35
|
+
bundled `/chainpatrol` Claude Code skill is updated to call out the bulk
|
|
36
|
+
form so agents stop falling back to per-asset shell loops (or refusing
|
|
37
|
+
the request) when asked to check many domains at once.
|
|
38
|
+
|
|
3
39
|
## 0.9.0
|
|
4
40
|
|
|
5
41
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ chainpatrol metrics found --org my-org --this-week
|
|
|
22
22
|
chainpatrol reports create --org my-org --title "Phishing report" --asset "https://phish.example:BLOCKED"
|
|
23
23
|
chainpatrol reports list --org my-org --limit 10
|
|
24
24
|
chainpatrol metrics breakdown --org my-org --by type --from 2026-01-01 --to 2026-01-08
|
|
25
|
+
chainpatrol metrics organization --org my-org --from 2026-01-01 --to 2026-02-01
|
|
25
26
|
chainpatrol queues snapshot --all --output markdown
|
|
27
|
+
chainpatrol takedowns list --org my-org --takedown-status TODO,IN_PROGRESS --per-page 50
|
|
26
28
|
chainpatrol presets run cs-weekly-health --org my-org --output json
|
|
27
29
|
```
|
|
28
30
|
|
|
@@ -45,11 +47,23 @@ chainpatrol queues snapshot --all --output csv
|
|
|
45
47
|
# look up a single asset against the ChainPatrol blocklist and external feeds
|
|
46
48
|
chainpatrol asset check https://phish.example
|
|
47
49
|
|
|
48
|
-
#
|
|
50
|
+
# bulk: check many assets in one call (parallel, concurrency=10)
|
|
51
|
+
chainpatrol asset check a.example b.example c.example
|
|
52
|
+
|
|
53
|
+
# repeated --asset is equivalent to positional args
|
|
54
|
+
chainpatrol asset check --asset a.example --asset b.example
|
|
55
|
+
|
|
56
|
+
# pipe a file of one-domain-per-line via xargs into a single CLI call
|
|
57
|
+
xargs -a domains.txt chainpatrol --json asset check
|
|
58
|
+
|
|
59
|
+
# JSON output for automation (single-asset returns flat shape; bulk returns
|
|
60
|
+
# { results: [...], summary: { checked, blocked, allowed, unknown, errored } })
|
|
49
61
|
chainpatrol --json asset check 0xabc123...
|
|
62
|
+
chainpatrol --json asset check a.example b.example c.example
|
|
50
63
|
|
|
51
|
-
# markdown summary
|
|
64
|
+
# markdown / csv summary tables
|
|
52
65
|
chainpatrol asset check phish.example --output markdown
|
|
66
|
+
chainpatrol asset check a.example b.example --output csv
|
|
53
67
|
```
|
|
54
68
|
|
|
55
69
|
### Detection commands
|
|
@@ -0,0 +1,203 @@
|
|
|
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-SX3L6NKR.js";
|
|
12
|
+
import "./chunk-EGWK6SRQ.js";
|
|
13
|
+
import "./chunk-TFCNKBRC.js";
|
|
14
|
+
import "./chunk-U73SABXK.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/asset/check.ts
|
|
17
|
+
var DEFAULT_CONCURRENCY = 10;
|
|
18
|
+
function statusLine(result) {
|
|
19
|
+
const watchTag = result.watchStatus ? ` watch=${result.watchStatus}` : "";
|
|
20
|
+
const reasonTag = result.reason ? ` reason=${result.reason}` : "";
|
|
21
|
+
return `${result.status} (source=${result.source}${reasonTag}${watchTag})`;
|
|
22
|
+
}
|
|
23
|
+
async function runWithConcurrency(items, worker, concurrency) {
|
|
24
|
+
const results = new Array(items.length);
|
|
25
|
+
let cursor = 0;
|
|
26
|
+
const workers = new Array(Math.min(concurrency, items.length)).fill(null).map(async () => {
|
|
27
|
+
while (true) {
|
|
28
|
+
const index = cursor;
|
|
29
|
+
cursor += 1;
|
|
30
|
+
if (index >= items.length) return;
|
|
31
|
+
results[index] = await worker(items[index]);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
await Promise.all(workers);
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
async function runAssetCheck(options) {
|
|
38
|
+
const contents = (options.contents ?? []).map((value) => value.trim()).filter((value) => value.length > 0);
|
|
39
|
+
if (contents.length === 0) {
|
|
40
|
+
throw new CliExitError(
|
|
41
|
+
"asset check requires at least one asset. Example: chainpatrol asset check https://example.com",
|
|
42
|
+
ExitCode.USAGE
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
46
|
+
const client = options.apiClient ?? createApiClient();
|
|
47
|
+
const perAssetResults = await runWithConcurrency(
|
|
48
|
+
contents,
|
|
49
|
+
async (content) => {
|
|
50
|
+
try {
|
|
51
|
+
const result = await client.assetCheck({ content });
|
|
52
|
+
return { content, ok: true, result };
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return {
|
|
55
|
+
content,
|
|
56
|
+
ok: false,
|
|
57
|
+
error: err instanceof Error ? err.message : String(err)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
DEFAULT_CONCURRENCY
|
|
62
|
+
);
|
|
63
|
+
const summary = {
|
|
64
|
+
checked: perAssetResults.length,
|
|
65
|
+
blocked: perAssetResults.filter((r) => r.ok && r.result.status === "BLOCKED").length,
|
|
66
|
+
allowed: perAssetResults.filter((r) => r.ok && r.result.status === "ALLOWED").length,
|
|
67
|
+
unknown: perAssetResults.filter((r) => r.ok && r.result.status === "UNKNOWN").length,
|
|
68
|
+
errored: perAssetResults.filter((r) => !r.ok).length
|
|
69
|
+
};
|
|
70
|
+
const isSingle = contents.length === 1;
|
|
71
|
+
const jsonPayload = isSingle ? buildSingleJsonPayload(perAssetResults[0], options.explain) : {
|
|
72
|
+
results: perAssetResults.map(toJsonRow),
|
|
73
|
+
summary,
|
|
74
|
+
explanation: options.explain ? "Bulk asset check ran the public asset/check endpoint for each input in parallel (concurrency=10). Each row reports the aggregated ChainPatrol status plus the per-source breakdown." : void 0
|
|
75
|
+
};
|
|
76
|
+
const markdown = isSingle ? buildSingleMarkdown(perAssetResults[0]) : [
|
|
77
|
+
`# Asset Check (${summary.checked})`,
|
|
78
|
+
"",
|
|
79
|
+
`- Blocked: ${summary.blocked}`,
|
|
80
|
+
`- Allowed: ${summary.allowed}`,
|
|
81
|
+
`- Unknown: ${summary.unknown}`,
|
|
82
|
+
...summary.errored > 0 ? [`- Errored: ${summary.errored}`] : [],
|
|
83
|
+
"",
|
|
84
|
+
"| Content | Status | Source | Reason | Watch |",
|
|
85
|
+
"| --- | --- | --- | --- | --- |",
|
|
86
|
+
...perAssetResults.map(toMarkdownRow)
|
|
87
|
+
].join("\n");
|
|
88
|
+
const csv = toCsvRows(perAssetResults.map(toCsvRow));
|
|
89
|
+
printOutput({
|
|
90
|
+
outputFormat,
|
|
91
|
+
json: jsonPayload,
|
|
92
|
+
markdown,
|
|
93
|
+
csv,
|
|
94
|
+
human: () => {
|
|
95
|
+
for (const entry of perAssetResults) {
|
|
96
|
+
if (entry.ok) {
|
|
97
|
+
console.log(`${entry.content} -> ${statusLine(entry.result)}`);
|
|
98
|
+
} else {
|
|
99
|
+
console.log(`${entry.content} -> ERROR: ${entry.error}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (isSingle && perAssetResults[0].ok) {
|
|
103
|
+
const sources = perAssetResults[0].result.sources;
|
|
104
|
+
if (sources.length > 0) {
|
|
105
|
+
console.log("Sources:");
|
|
106
|
+
for (const entry of sources) {
|
|
107
|
+
console.log(` - ${entry.source}: ${entry.status}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (perAssetResults[0].result.message) {
|
|
111
|
+
console.log(`Message: ${perAssetResults[0].result.message}`);
|
|
112
|
+
}
|
|
113
|
+
if (perAssetResults[0].result.code) {
|
|
114
|
+
console.log(`Code: ${perAssetResults[0].result.code}`);
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
console.log(
|
|
118
|
+
`Summary: ${summary.checked} checked \u2014 blocked=${summary.blocked} allowed=${summary.allowed} unknown=${summary.unknown}${summary.errored > 0 ? ` errored=${summary.errored}` : ""}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (options.explain) {
|
|
122
|
+
console.log(
|
|
123
|
+
"Status is the aggregated verdict across ChainPatrol and external feeds; see the per-source rows for the underlying signals."
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
if (summary.errored > 0) {
|
|
129
|
+
throw new CliExitError(
|
|
130
|
+
`asset check: ${summary.errored} of ${summary.checked} request(s) failed.`,
|
|
131
|
+
ExitCode.UNKNOWN
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function buildSingleJsonPayload(entry, explain) {
|
|
136
|
+
if (!entry.ok) {
|
|
137
|
+
return {
|
|
138
|
+
content: entry.content,
|
|
139
|
+
error: entry.error,
|
|
140
|
+
explanation: explain ? "asset check failed for this asset. Inspect the error message and retry." : void 0
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
content: entry.content,
|
|
145
|
+
...entry.result,
|
|
146
|
+
explanation: explain ? "Asset check aggregates ChainPatrol records and external sources to classify a domain, URL, or crypto address as BLOCKED, ALLOWED, or UNKNOWN." : void 0
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function buildSingleMarkdown(entry) {
|
|
150
|
+
if (!entry.ok) {
|
|
151
|
+
return [`# Asset Check: ${entry.content}`, "", `- Error: ${entry.error}`].join("\n");
|
|
152
|
+
}
|
|
153
|
+
const result = entry.result;
|
|
154
|
+
return [
|
|
155
|
+
`# Asset Check: ${entry.content}`,
|
|
156
|
+
"",
|
|
157
|
+
`- Status: **${result.status}**`,
|
|
158
|
+
`- Source: ${result.source}`,
|
|
159
|
+
...result.reason ? [`- Reason: ${result.reason}`] : [],
|
|
160
|
+
...result.watchStatus ? [`- Watch status: ${result.watchStatus}`] : [],
|
|
161
|
+
...result.message ? [`- Message: ${result.message}`] : [],
|
|
162
|
+
...result.code ? [`- Code: ${result.code}`] : [],
|
|
163
|
+
"",
|
|
164
|
+
"## Per-source results",
|
|
165
|
+
"",
|
|
166
|
+
...result.sources.map((entry2) => `- ${entry2.source}: ${entry2.status}`)
|
|
167
|
+
].join("\n");
|
|
168
|
+
}
|
|
169
|
+
function toJsonRow(entry) {
|
|
170
|
+
if (!entry.ok) {
|
|
171
|
+
return { content: entry.content, error: entry.error };
|
|
172
|
+
}
|
|
173
|
+
return { content: entry.content, ...entry.result };
|
|
174
|
+
}
|
|
175
|
+
function toMarkdownRow(entry) {
|
|
176
|
+
if (!entry.ok) {
|
|
177
|
+
return `| ${entry.content} | ERROR | \u2014 | ${entry.error.replace(/\|/g, "\\|")} | \u2014 |`;
|
|
178
|
+
}
|
|
179
|
+
const r = entry.result;
|
|
180
|
+
return `| ${entry.content} | ${r.status} | ${r.source} | ${r.reason ?? ""} | ${r.watchStatus ?? ""} |`;
|
|
181
|
+
}
|
|
182
|
+
function toCsvRow(entry) {
|
|
183
|
+
if (!entry.ok) {
|
|
184
|
+
return {
|
|
185
|
+
content: entry.content,
|
|
186
|
+
status: "ERROR",
|
|
187
|
+
source: "",
|
|
188
|
+
reason: entry.error,
|
|
189
|
+
watchStatus: ""
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const r = entry.result;
|
|
193
|
+
return {
|
|
194
|
+
content: entry.content,
|
|
195
|
+
status: r.status,
|
|
196
|
+
source: r.source,
|
|
197
|
+
reason: r.reason ?? "",
|
|
198
|
+
watchStatus: r.watchStatus ?? ""
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
runAssetCheck
|
|
203
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installCompletions,
|
|
3
3
|
uninstallCompletions
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YXRFUYA6.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/setup-skill.ts
|
|
7
7
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync3, rmSync } from "fs";
|
|
@@ -312,7 +312,7 @@ so the same background+tail pattern works without \`--json\`. Prefer
|
|
|
312
312
|
chainpatrol logout
|
|
313
313
|
\`\`\`
|
|
314
314
|
|
|
315
|
-
### \`asset check\` \u2014 Check
|
|
315
|
+
### \`asset check\` \u2014 Check one or many assets against the blocklist
|
|
316
316
|
|
|
317
317
|
Look up a URL, domain, or crypto address and return its aggregated status
|
|
318
318
|
(\`BLOCKED\`, \`ALLOWED\`, or \`UNKNOWN\`) plus a per-source breakdown
|
|
@@ -320,24 +320,53 @@ Look up a URL, domain, or crypto address and return its aggregated status
|
|
|
320
320
|
polkadot-phishing). Works whether you're authenticated via device-code
|
|
321
321
|
login or via a \`CHAINPATROL_API_KEY\` env var.
|
|
322
322
|
|
|
323
|
+
Single asset:
|
|
324
|
+
|
|
323
325
|
\`\`\`bash
|
|
324
326
|
chainpatrol asset check https://phish.example
|
|
325
327
|
chainpatrol asset check 0xabc123...
|
|
326
328
|
\`\`\`
|
|
327
329
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
+
#### Bulk checks (preferred for >1 asset)
|
|
331
|
+
|
|
332
|
+
Pass multiple assets in a single invocation \u2014 the CLI runs them in
|
|
333
|
+
parallel (concurrency 10) and returns one row per asset. **Do this
|
|
334
|
+
instead of looping the CLI in a shell \`for\` loop**: one process, one
|
|
335
|
+
auth handshake, parallel HTTP. Use either positional args or repeated
|
|
336
|
+
\`--asset\`:
|
|
337
|
+
|
|
338
|
+
\`\`\`bash
|
|
339
|
+
# positional form
|
|
340
|
+
chainpatrol asset check a.example b.example c.example
|
|
341
|
+
|
|
342
|
+
# repeated --asset (handy when content has spaces or special chars)
|
|
343
|
+
chainpatrol asset check --asset a.example --asset b.example
|
|
344
|
+
|
|
345
|
+
# from a file of one-asset-per-line (use xargs to splat into one call)
|
|
346
|
+
xargs -a domains.txt chainpatrol asset check
|
|
347
|
+
\`\`\`
|
|
348
|
+
|
|
349
|
+
JSON mode is the agent-friendly default \u2014 single-asset JSON keeps the
|
|
350
|
+
flat \`{ content, status, source, reason?, sources[], watchStatus? }\`
|
|
351
|
+
shape; multi-asset JSON returns \`{ results: [...], summary: { checked,
|
|
352
|
+
blocked, allowed, unknown, errored } }\`:
|
|
330
353
|
|
|
331
354
|
\`\`\`bash
|
|
332
355
|
chainpatrol --json asset check https://phish.example
|
|
356
|
+
chainpatrol --json asset check a.example b.example c.example
|
|
333
357
|
\`\`\`
|
|
334
358
|
|
|
335
|
-
Markdown
|
|
359
|
+
Markdown / CSV are also available for sharing in docs / chat:
|
|
336
360
|
|
|
337
361
|
\`\`\`bash
|
|
338
362
|
chainpatrol asset check phish.example --output markdown
|
|
363
|
+
chainpatrol asset check a.example b.example --output csv
|
|
339
364
|
\`\`\`
|
|
340
365
|
|
|
366
|
+
If any individual lookup fails, the CLI still prints results for the
|
|
367
|
+
successful ones, then exits non-zero so failures aren't silently
|
|
368
|
+
swallowed.
|
|
369
|
+
|
|
341
370
|
### \`configs list\` \u2014 List detection configurations
|
|
342
371
|
|
|
343
372
|
Requires authentication and an organization slug.
|
|
@@ -17,6 +17,26 @@ function parseIsoDate(value) {
|
|
|
17
17
|
}
|
|
18
18
|
return dt.toJSDate();
|
|
19
19
|
}
|
|
20
|
+
function parseIsoDateString(value) {
|
|
21
|
+
return parseIsoDate(value)?.toISOString();
|
|
22
|
+
}
|
|
23
|
+
var TAKEDOWN_STATUSES = [
|
|
24
|
+
"TODO",
|
|
25
|
+
"IN_PROGRESS",
|
|
26
|
+
"COMPLETED",
|
|
27
|
+
"CANCELLED",
|
|
28
|
+
"PENDING_RETRACTION",
|
|
29
|
+
"RETRACTION_SENT",
|
|
30
|
+
"RETRACTED",
|
|
31
|
+
"PENDING_INPUT"
|
|
32
|
+
];
|
|
33
|
+
var LIVENESS_STATUSES = ["UNKNOWN", "ALIVE", "DEAD"];
|
|
34
|
+
var TAKEDOWN_SORT_KEYS = [
|
|
35
|
+
"updatedAt",
|
|
36
|
+
"createdAt",
|
|
37
|
+
"takedownStatus",
|
|
38
|
+
"takedownUpdatedAt"
|
|
39
|
+
];
|
|
20
40
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
21
41
|
function defaultGetCredential() {
|
|
22
42
|
const envKey = process.env.CHAINPATROL_API_KEY;
|
|
@@ -29,16 +49,49 @@ function createApiClient(options) {
|
|
|
29
49
|
const config = getConfig();
|
|
30
50
|
const apiUrl = options?.apiUrl ?? config.apiUrl;
|
|
31
51
|
const getCredential = options?.getCredential ?? (options?.getToken ? () => ({ kind: "bearer", value: options.getToken() }) : defaultGetCredential);
|
|
32
|
-
|
|
52
|
+
function buildAuthHeaders() {
|
|
33
53
|
const credential = getCredential();
|
|
34
|
-
const headers = {
|
|
35
|
-
"Content-Type": "application/json"
|
|
36
|
-
};
|
|
54
|
+
const headers = {};
|
|
37
55
|
if (credential.kind === "api-key") {
|
|
38
56
|
headers["x-api-key"] = credential.value;
|
|
39
57
|
} else {
|
|
40
58
|
headers["Authorization"] = `Bearer ${credential.value}`;
|
|
41
59
|
}
|
|
60
|
+
return headers;
|
|
61
|
+
}
|
|
62
|
+
async function requestGet(path, query) {
|
|
63
|
+
const headers = buildAuthHeaders();
|
|
64
|
+
const search = new URLSearchParams();
|
|
65
|
+
for (const [key, value] of Object.entries(query)) {
|
|
66
|
+
if (value !== void 0 && value !== "") {
|
|
67
|
+
search.set(key, value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const queryString = search.toString();
|
|
71
|
+
const url = `${apiUrl}/api/v2${path}${queryString ? `?${queryString}` : ""}`;
|
|
72
|
+
let res;
|
|
73
|
+
try {
|
|
74
|
+
res = await fetch(url, {
|
|
75
|
+
method: "GET",
|
|
76
|
+
headers,
|
|
77
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
81
|
+
throw new Error(
|
|
82
|
+
"Request timed out. Check your network connection and try again."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (err instanceof TypeError && err.code === "ECONNREFUSED") {
|
|
86
|
+
throw new Error(`Cannot connect to ${apiUrl}. Is the server running?`);
|
|
87
|
+
}
|
|
88
|
+
throw new Error("Network error. Check your internet connection and try again.");
|
|
89
|
+
}
|
|
90
|
+
return handleResponse(res);
|
|
91
|
+
}
|
|
92
|
+
async function request(path, body) {
|
|
93
|
+
const headers = buildAuthHeaders();
|
|
94
|
+
headers["Content-Type"] = "application/json";
|
|
42
95
|
let res;
|
|
43
96
|
try {
|
|
44
97
|
res = await fetch(`${apiUrl}/api/v2${path}`, {
|
|
@@ -58,6 +111,9 @@ function createApiClient(options) {
|
|
|
58
111
|
}
|
|
59
112
|
throw new Error("Network error. Check your internet connection and try again.");
|
|
60
113
|
}
|
|
114
|
+
return handleResponse(res);
|
|
115
|
+
}
|
|
116
|
+
async function handleResponse(res) {
|
|
61
117
|
if (!res.ok) {
|
|
62
118
|
let errorMessageFromResponse = null;
|
|
63
119
|
try {
|
|
@@ -157,6 +213,28 @@ function createApiClient(options) {
|
|
|
157
213
|
reportedByCustomer: input.reportedByCustomer
|
|
158
214
|
});
|
|
159
215
|
},
|
|
216
|
+
listTakedowns(input) {
|
|
217
|
+
return request("/takedowns/list", {
|
|
218
|
+
organizationSlug: input.organizationSlug,
|
|
219
|
+
query: input.query,
|
|
220
|
+
startDate: parseIsoDateString(input.startDate),
|
|
221
|
+
endDate: parseIsoDateString(input.endDate),
|
|
222
|
+
assetType: input.assetType,
|
|
223
|
+
takedownStatus: input.takedownStatus,
|
|
224
|
+
livenessStatus: input.livenessStatus,
|
|
225
|
+
sorting: input.sorting,
|
|
226
|
+
per_page: input.perPage,
|
|
227
|
+
next_page: input.nextPage
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
getOrganizationMetrics(input) {
|
|
231
|
+
return requestGet("/organization/metrics", {
|
|
232
|
+
organizationSlug: input.organizationSlug,
|
|
233
|
+
brandSlug: input.brandSlug,
|
|
234
|
+
startDate: parseIsoDateString(input.startDate),
|
|
235
|
+
endDate: parseIsoDateString(input.endDate)
|
|
236
|
+
});
|
|
237
|
+
},
|
|
160
238
|
listHealthchecks() {
|
|
161
239
|
return request("/healthchecks/list", {});
|
|
162
240
|
},
|
|
@@ -170,5 +248,8 @@ function createApiClient(options) {
|
|
|
170
248
|
}
|
|
171
249
|
|
|
172
250
|
export {
|
|
251
|
+
TAKEDOWN_STATUSES,
|
|
252
|
+
LIVENESS_STATUSES,
|
|
253
|
+
TAKEDOWN_SORT_KEYS,
|
|
173
254
|
createApiClient
|
|
174
255
|
};
|
|
@@ -22,6 +22,7 @@ _chainpatrol() {
|
|
|
22
22
|
'metrics:Query organization metrics'
|
|
23
23
|
'reports:Create and list reports from terminal'
|
|
24
24
|
'queues:Snapshot operations queues'
|
|
25
|
+
'takedowns:List individual takedown records'
|
|
25
26
|
'presets:Run saved workflows'
|
|
26
27
|
'setup:Install Claude Code skill and shell completions'
|
|
27
28
|
'uninstall:Remove Claude Code skill and shell completions'
|
|
@@ -84,6 +85,7 @@ _chainpatrol() {
|
|
|
84
85
|
'summary:Show metrics summary'
|
|
85
86
|
'found:Show found threats count'
|
|
86
87
|
'breakdown:Show metrics breakdown'
|
|
88
|
+
'organization:Get organization metrics (official endpoint)'
|
|
87
89
|
)
|
|
88
90
|
_describe 'subcommand' metrics_subcommands
|
|
89
91
|
;;
|
|
@@ -100,6 +102,11 @@ _chainpatrol() {
|
|
|
100
102
|
queues_subcommands=('snapshot:Show queue snapshot')
|
|
101
103
|
_describe 'subcommand' queues_subcommands
|
|
102
104
|
;;
|
|
105
|
+
takedowns)
|
|
106
|
+
local -a takedowns_subcommands
|
|
107
|
+
takedowns_subcommands=('list:List individual takedown records')
|
|
108
|
+
_describe 'subcommand' takedowns_subcommands
|
|
109
|
+
;;
|
|
103
110
|
presets)
|
|
104
111
|
local -a presets_subcommands
|
|
105
112
|
presets_subcommands=(
|
|
@@ -124,7 +131,7 @@ var BASH_COMPLETION = `_chainpatrol() {
|
|
|
124
131
|
COMPREPLY=()
|
|
125
132
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
126
133
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
127
|
-
commands="login logout asset configs detections metrics reports queues presets setup uninstall completions help"
|
|
134
|
+
commands="login logout asset configs detections metrics reports queues takedowns presets setup uninstall completions help"
|
|
128
135
|
|
|
129
136
|
case "\${prev}" in
|
|
130
137
|
chainpatrol)
|
|
@@ -144,7 +151,7 @@ var BASH_COMPLETION = `_chainpatrol() {
|
|
|
144
151
|
return 0
|
|
145
152
|
;;
|
|
146
153
|
metrics)
|
|
147
|
-
COMPREPLY=( $(compgen -W "summary found breakdown" -- "\${cur}") )
|
|
154
|
+
COMPREPLY=( $(compgen -W "summary found breakdown organization" -- "\${cur}") )
|
|
148
155
|
return 0
|
|
149
156
|
;;
|
|
150
157
|
reports)
|
|
@@ -155,6 +162,10 @@ var BASH_COMPLETION = `_chainpatrol() {
|
|
|
155
162
|
COMPREPLY=( $(compgen -W "snapshot" -- "\${cur}") )
|
|
156
163
|
return 0
|
|
157
164
|
;;
|
|
165
|
+
takedowns)
|
|
166
|
+
COMPREPLY=( $(compgen -W "list" -- "\${cur}") )
|
|
167
|
+
return 0
|
|
168
|
+
;;
|
|
158
169
|
presets)
|
|
159
170
|
COMPREPLY=( $(compgen -W "list run" -- "\${cur}") )
|
|
160
171
|
return 0
|