@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.
Files changed (32) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +16 -2
  3. package/dist/{breakdown-63FAOVL7.js → breakdown-MM5GSZKV.js} +1 -1
  4. package/dist/check-GDKUHVQI.js +203 -0
  5. package/dist/{chunk-BJISZ3CY.js → chunk-37KYJWB3.js} +1 -1
  6. package/dist/{chunk-P4L4N5LM.js → chunk-EPKNZRX6.js} +34 -5
  7. package/dist/{chunk-RIKR2WFT.js → chunk-SX3L6NKR.js} +85 -4
  8. package/dist/{chunk-Z76CUWSS.js → chunk-YXRFUYA6.js} +13 -2
  9. package/dist/cli.js +146 -37
  10. package/dist/{completions-D6SOLU2D.js → completions-62OW3B3Y.js} +1 -1
  11. package/dist/{configs-update-2IOSKZH5.js → configs-update-7X7657PB.js} +1 -1
  12. package/dist/{create-EWS3SFCH.js → create-FL4QQTPN.js} +1 -1
  13. package/dist/{drift-Q3VG3XG3.js → drift-JKBHWWFU.js} +1 -1
  14. package/dist/{found-22B7RZT5.js → found-3WI4XQG4.js} +1 -1
  15. package/dist/{healthcheck-C3AIMUJT.js → healthcheck-EVQVPGWW.js} +1 -1
  16. package/dist/{list-IA4CSOIY.js → list-5DN6X3DP.js} +1 -1
  17. package/dist/{list-DNRWKM5O.js → list-BLNIE4GL.js} +1 -1
  18. package/dist/{list-AYHDFSQG.js → list-E5XNR2YG.js} +1 -1
  19. package/dist/list-JPG2ZSR4.js +137 -0
  20. package/dist/{list-HFWSMLGJ.js → list-ULHPOUJV.js} +2 -2
  21. package/dist/{list-SDBLGBVW.js → list-XDGU6SJQ.js} +1 -1
  22. package/dist/{list-json-CEPGVUGF.js → list-json-R2MIXADX.js} +1 -1
  23. package/dist/organization-MJRKWC4Z.js +75 -0
  24. package/dist/{run-AQMJRFGL.js → run-2YZZGZYN.js} +1 -1
  25. package/dist/{run-YTWNQD5X.js → run-5E5EC3MP.js} +1 -1
  26. package/dist/{run-WJGHJPXN.js → run-EUVRC72M.js} +2 -2
  27. package/dist/{setup-skill-DR4KGQMG.js → setup-skill-HAENFIFQ.js} +2 -2
  28. package/dist/{snapshot-C5MZWJTW.js → snapshot-3FUJICJJ.js} +1 -1
  29. package/dist/{summary-NQDZQEOD.js → summary-RVYQUKWV.js} +1 -1
  30. package/dist/{validate-5DVPSXJP.js → validate-UOVKEAJM.js} +1 -1
  31. package/package.json +1 -1
  32. 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
- # JSON output for automation (returns status, per-source breakdown, watchStatus)
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
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-RIKR2WFT.js";
7
+ } from "./chunk-SX3L6NKR.js";
8
8
  import "./chunk-EGWK6SRQ.js";
9
9
  import "./chunk-TFCNKBRC.js";
10
10
  import "./chunk-U73SABXK.js";
@@ -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
+ };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-RIKR2WFT.js";
11
+ } from "./chunk-SX3L6NKR.js";
12
12
  import {
13
13
  DateTime
14
14
  } from "./chunk-TFCNKBRC.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  installCompletions,
3
3
  uninstallCompletions
4
- } from "./chunk-Z76CUWSS.js";
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 a single asset against the blocklist
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
- JSON mode (recommended for automations and agents \u2014 returns full
329
- \`{ status, source, reason?, sources[], watchStatus? }\`):
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 is also supported for sharing in docs / chat:
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
- async function request(path, body) {
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