@chainpatrol/cli 1.11.2 → 1.13.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 CHANGED
@@ -1,5 +1,46 @@
1
1
  # @chainpatrol/cli
2
2
 
3
+ ## 1.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2552904: Add Time to Detection: `getMetricsTimeToDetection` in the SDK,
8
+ `chainpatrol metrics time-to-detection` in the CLI, and the
9
+ `metrics_time_to_detection` MCP tool, all backed by the new
10
+ `POST /metrics/time-to-detection` endpoint. It reports how long threats were
11
+ already live before the earliest automated detection of the asset, for any
12
+ organization, per asset type. The clock runs from when the asset was first
13
+ seen live to its earliest automated detection; a human report does not stop
14
+ it. Each number is a minimum, published with how many blocked assets are in
15
+ the sample. Server-side work lives in @chainpatrol/external-trpc,
16
+ @chainpatrol/validation and @chainpatrol/metrics; those are private packages
17
+ and need no version bump.
18
+
19
+ ### Patch Changes
20
+
21
+ - 27bb1d9: Add Tumblr to the list of supported asset types.
22
+ - Updated dependencies [ac640ce]
23
+ - Updated dependencies [2552904]
24
+ - Updated dependencies [878241c]
25
+ - @chainpatrol/mcp@1.12.0
26
+ - @chainpatrol/sdk@1.12.0
27
+
28
+ ## 1.12.0
29
+
30
+ ### Minor Changes
31
+
32
+ - ebce4b9: Add Pump.fun as a first-class asset type in CLI asset type discovery.
33
+
34
+ ### Patch Changes
35
+
36
+ - 8056266: Add Glassdoor to the list of supported asset types.
37
+ - Updated dependencies [3895c2f]
38
+ - Updated dependencies [264c4f7]
39
+ - Updated dependencies [7812a3b]
40
+ - Updated dependencies [8a78f60]
41
+ - @chainpatrol/sdk@1.11.0
42
+ - @chainpatrol/mcp@1.11.0
43
+
3
44
  ## 1.11.2
4
45
 
5
46
  ### Patch Changes
@@ -96,6 +96,7 @@ _chainpatrol() {
96
96
  'breakdown:Show metrics breakdown'
97
97
  'takedown-time:Show median time to takedown by asset type'
98
98
  'takedown-time-trend:Show month-over-month median time-to-takedown trend'
99
+ 'time-to-detection:Show how long threats were live before first detection'
99
100
  'organization:Get organization metrics (official endpoint)'
100
101
  )
101
102
  _describe 'subcommand' metrics_subcommands
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  installCompletions,
6
6
  uninstallCompletions
7
- } from "./chunk-TI2HK3NM.js";
7
+ } from "./chunk-DY5VY6FO.js";
8
8
 
9
9
  // src/commands/setup-skill.ts
10
10
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
package/dist/cli.js CHANGED
@@ -11,12 +11,12 @@ import {
11
11
  getBundledSkillVersion,
12
12
  isSkillInstalled,
13
13
  readInstalledSkillVersion
14
- } from "./chunk-TGIVCSKT.js";
14
+ } from "./chunk-XLPLTBKX.js";
15
15
  import {
16
16
  compareVersions,
17
17
  getCliVersion
18
18
  } from "./chunk-OKIUEBUK.js";
19
- import "./chunk-TI2HK3NM.js";
19
+ import "./chunk-DY5VY6FO.js";
20
20
  import {
21
21
  DateTime
22
22
  } from "./chunk-TFCNKBRC.js";
@@ -282,7 +282,7 @@ var HELP = {
282
282
  },
283
283
  metrics: {
284
284
  description: "Query organization metrics and breakdowns.",
285
- usage: "chainpatrol metrics <summary|found|breakdown|organization>",
285
+ usage: "chainpatrol metrics <summary|found|breakdown|takedown-time|takedown-time-trend|time-to-detection|organization>",
286
286
  options: ["--org <slug> Organization slug"],
287
287
  examples: [
288
288
  "chainpatrol metrics summary --org acme --this-week",
@@ -290,6 +290,7 @@ var HELP = {
290
290
  "chainpatrol metrics breakdown --org acme --by day --this-week",
291
291
  "chainpatrol metrics takedown-time --org acme --from 2025-01-01 --to 2025-02-01",
292
292
  "chainpatrol metrics takedown-time-trend --org acme --months 12",
293
+ "chainpatrol metrics time-to-detection --org acme --from 2025-01-01 --to 2025-02-01",
293
294
  "chainpatrol metrics organization --org acme --from 2025-01-01 --to 2025-02-01"
294
295
  ]
295
296
  },
@@ -361,6 +362,17 @@ var HELP = {
361
362
  "--brand <ids> Comma-separated brand IDs"
362
363
  ]
363
364
  },
365
+ "metrics time-to-detection": {
366
+ description: "How long threats were already live before the earliest automated detection of the asset, for any organization, per asset type, for distinct assets this organization blocked in the range that are still blocked. The clock runs from when the asset was first seen live to its earliest automated detection; a human report does not stop it. Every number is a minimum and comes with how many blocked assets are measured. Defaults to the trailing 3 months if no date range is given.",
367
+ usage: "chainpatrol metrics time-to-detection --org <slug> [--from ISO --to ISO|--this-week]",
368
+ options: [
369
+ "--org <slug> Organization slug",
370
+ "--from <iso> Start date (ISO 8601)",
371
+ "--to <iso> End date (ISO 8601); a date-only value covers the whole day",
372
+ "--this-week Use the current ISO week (overrides default)",
373
+ "--brand <ids> Comma-separated brand IDs"
374
+ ]
375
+ },
364
376
  "metrics takedown-time-trend": {
365
377
  description: "Median time from takedown start to completion bucketed by month, over a trailing window, with the month-over-month change.",
366
378
  usage: "chainpatrol metrics takedown-time-trend --org <slug> [--months 12] [--asset-type TWITTER,URL]",
@@ -1360,7 +1372,7 @@ async function main() {
1360
1372
  break;
1361
1373
  }
1362
1374
  if (subcommand === "types") {
1363
- const { runAssetTypes } = await import("./types-KUPHV2V3.js");
1375
+ const { runAssetTypes } = await import("./types-ZO5KKMXK.js");
1364
1376
  runAssetTypes({
1365
1377
  json: jsonMode,
1366
1378
  outputFormat: cliContext.outputFormat
@@ -1573,6 +1585,19 @@ async function main() {
1573
1585
  });
1574
1586
  break;
1575
1587
  }
1588
+ if (subcommand === "time-to-detection" || subcommand === "timeToDetection") {
1589
+ const { runMetricsTimeToDetection } = await import("./time-to-detection-5L7ECKYU.js");
1590
+ await runMetricsTimeToDetection({
1591
+ org,
1592
+ from: cli.flags.from,
1593
+ to: cli.flags.to,
1594
+ thisWeek: cli.flags.thisWeek,
1595
+ brandIds: parseBrandIds(),
1596
+ json: jsonMode,
1597
+ outputFormat: cliContext.outputFormat
1598
+ });
1599
+ break;
1600
+ }
1576
1601
  if (subcommand === "takedown-time-trend" || subcommand === "takedownTimeTrend") {
1577
1602
  const { runMetricsTakedownTimeTrend } = await import("./takedown-time-trend-KYPA3W3U.js");
1578
1603
  await runMetricsTakedownTimeTrend({
@@ -1609,10 +1634,11 @@ async function main() {
1609
1634
  "breakdown",
1610
1635
  "takedown-time",
1611
1636
  "takedown-time-trend",
1637
+ "time-to-detection",
1612
1638
  "organization"
1613
1639
  ]) : null;
1614
1640
  throw new Error(
1615
- subcommand ? `Unknown subcommand: metrics ${subcommand}${hint ? `. Did you mean "metrics ${hint}"?` : ""}` : "Usage: chainpatrol metrics <summary|found|breakdown|takedown-time|takedown-time-trend|organization>"
1641
+ subcommand ? `Unknown subcommand: metrics ${subcommand}${hint ? `. Did you mean "metrics ${hint}"?` : ""}` : "Usage: chainpatrol metrics <summary|found|breakdown|takedown-time|takedown-time-trend|time-to-detection|organization>"
1616
1642
  );
1617
1643
  }
1618
1644
  case "reports": {
@@ -1953,17 +1979,17 @@ async function main() {
1953
1979
  case "setup":
1954
1980
  case "install":
1955
1981
  case "i": {
1956
- const { setupSkill } = await import("./setup-skill-TVJJD6NC.js");
1982
+ const { setupSkill } = await import("./setup-skill-RQG43DFT.js");
1957
1983
  setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
1958
1984
  break;
1959
1985
  }
1960
1986
  case "uninstall": {
1961
- const { uninstallSkill } = await import("./setup-skill-TVJJD6NC.js");
1987
+ const { uninstallSkill } = await import("./setup-skill-RQG43DFT.js");
1962
1988
  uninstallSkill({ json: jsonMode });
1963
1989
  break;
1964
1990
  }
1965
1991
  case "completions": {
1966
- const { printCompletions } = await import("./completions-7DD5E5HL.js");
1992
+ const { printCompletions } = await import("./completions-HYTAOGE4.js");
1967
1993
  printCompletions(subcommand);
1968
1994
  break;
1969
1995
  }
@@ -3,7 +3,7 @@ import {
3
3
  installCompletions,
4
4
  printCompletions,
5
5
  uninstallCompletions
6
- } from "./chunk-TI2HK3NM.js";
6
+ } from "./chunk-DY5VY6FO.js";
7
7
  export {
8
8
  getCompletionScript,
9
9
  installCompletions,
@@ -6,9 +6,9 @@ import {
6
6
  readInstalledSkillVersion,
7
7
  setupSkill,
8
8
  uninstallSkill
9
- } from "./chunk-TGIVCSKT.js";
9
+ } from "./chunk-XLPLTBKX.js";
10
10
  import "./chunk-OKIUEBUK.js";
11
- import "./chunk-TI2HK3NM.js";
11
+ import "./chunk-DY5VY6FO.js";
12
12
  export {
13
13
  getBundledSkillContent,
14
14
  getBundledSkillVersion,
@@ -0,0 +1,82 @@
1
+ import {
2
+ resolveDateRange
3
+ } from "./chunk-S37VFIZY.js";
4
+ import {
5
+ printOutput,
6
+ toCsvRows
7
+ } from "./chunk-VFT3TD3E.js";
8
+ import {
9
+ createApiClient
10
+ } from "./chunk-NSRQ62C4.js";
11
+ import "./chunk-4QZ7KD2Y.js";
12
+ import "./chunk-TFCNKBRC.js";
13
+ import "./chunk-ORAY4KMA.js";
14
+ import "./chunk-K4RAUZSD.js";
15
+
16
+ // src/commands/metrics/time-to-detection.ts
17
+ function formatTimeToDetectionRow(row) {
18
+ const measured = `${row.withEvidence}/${row.blocked} measured`;
19
+ if (row.medianDaysAtLeast == null) {
20
+ return `${row.assetType}: no assets with a live date and an automated detection (${measured})`;
21
+ }
22
+ return `${row.assetType}: live at least ${row.medianDaysAtLeast.toFixed(1)}d before first detection (${measured})`;
23
+ }
24
+ async function runMetricsTimeToDetection(options) {
25
+ const range = resolveDateRange({
26
+ from: options.from,
27
+ to: options.to,
28
+ thisWeek: options.thisWeek
29
+ });
30
+ const client = options.apiClient ?? createApiClient();
31
+ const result = await client.getMetricsTimeToDetection({
32
+ slug: options.org,
33
+ // The caller's own --from/--to go through untouched when given: the API
34
+ // reads a date-only value as the whole day, while `resolveDateRange` has
35
+ // already expanded it to a midnight timestamp, which would cut the final
36
+ // day off.
37
+ startDate: options.from ?? range.startDate,
38
+ endDate: options.to ?? range.endDate,
39
+ brandIds: options.brandIds
40
+ });
41
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
42
+ printOutput({
43
+ outputFormat,
44
+ json: { ...result, range },
45
+ markdown: [
46
+ `# Time to Detection (${options.org}) \u2014 ${range.label}`,
47
+ "",
48
+ `- Range: ${range.startDate} \u2192 ${range.endDate}`,
49
+ "",
50
+ ...result.byAssetType.map((row) => `- ${formatTimeToDetectionRow(row)}`)
51
+ ].join("\n"),
52
+ csv: toCsvRows(
53
+ result.byAssetType.map((row) => ({
54
+ rangeLabel: range.label,
55
+ startDate: range.startDate,
56
+ endDate: range.endDate,
57
+ assetType: row.assetType,
58
+ medianDaysAtLeast: row.medianDaysAtLeast,
59
+ withEvidence: row.withEvidence,
60
+ blocked: row.blocked
61
+ }))
62
+ ),
63
+ human: () => {
64
+ console.log(`Time to detection for ${options.org} \u2014 ${range.label}`);
65
+ console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
66
+ if (result.byAssetType.length === 0) {
67
+ console.log("No blocked assets in this range.");
68
+ return;
69
+ }
70
+ console.log(
71
+ "Every number is a minimum. The clock runs from when the asset was first seen live to its earliest automated detection; a human report does not stop it."
72
+ );
73
+ for (const row of result.byAssetType) {
74
+ console.log(formatTimeToDetectionRow(row));
75
+ }
76
+ }
77
+ });
78
+ }
79
+ export {
80
+ formatTimeToDetectionRow,
81
+ runMetricsTimeToDetection
82
+ };
@@ -15,6 +15,11 @@ var ASSET_TYPES = [
15
15
  label: "Page",
16
16
  description: "A standalone web page tracked independently of its parent URL (e.g. a hosted phishing kit deployed on a shared platform)."
17
17
  },
18
+ {
19
+ type: "PUMP_FUN",
20
+ label: "Pump.fun",
21
+ description: "A Pump.fun token, creator profile, or platform page."
22
+ },
18
23
  {
19
24
  type: "ADDRESS",
20
25
  label: "Address",
@@ -98,6 +103,7 @@ var ASSET_TYPES = [
98
103
  },
99
104
  { type: "TEACHABLE", label: "Teachable", description: "A Teachable course or school." },
100
105
  { type: "SUBSTACK", label: "Substack", description: "A Substack publication." },
106
+ { type: "TUMBLR", label: "Tumblr", description: "A Tumblr blog, post, or community." },
101
107
  { type: "DEBANK", label: "Debank", description: "A Debank profile." },
102
108
  { type: "TAWK_TO", label: "Tawk.to Chat", description: "A Tawk.to live-chat widget." },
103
109
  { type: "JOTFORM", label: "Jotform", description: "A Jotform form." },
@@ -223,6 +229,11 @@ var ASSET_TYPES = [
223
229
  type: "SMARTMONEYMATCH",
224
230
  label: "SmartMoneyMatch",
225
231
  description: "A SmartMoneyMatch article or event page."
232
+ },
233
+ {
234
+ type: "GLASSDOOR",
235
+ label: "Glassdoor",
236
+ description: "A Glassdoor employer profile or employee review page."
226
237
  }
227
238
  ];
228
239
  var ASSET_TYPE_LABELS = Object.freeze(
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@chainpatrol/cli",
3
3
  "description": "The official ChainPatrol CLI — terminal interface for threat detection",
4
4
  "author": "Umar Ahmed <umar@chainpatrol.io>",
5
- "version": "1.11.2",
5
+ "version": "1.13.0",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://chainpatrol.com/docs/cli",
8
8
  "keywords": [
@@ -33,8 +33,8 @@
33
33
  "lint": "npx oxlint ."
34
34
  },
35
35
  "dependencies": {
36
- "@chainpatrol/mcp": "1.10.2",
37
- "@chainpatrol/sdk": "1.10.0",
36
+ "@chainpatrol/mcp": "1.12.0",
37
+ "@chainpatrol/sdk": "1.12.0",
38
38
  "@modelcontextprotocol/sdk": "^1.30.0",
39
39
  "ink": "^7.0.1",
40
40
  "meow": "^14.1.0",