@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 +41 -0
- package/dist/{chunk-TI2HK3NM.js → chunk-DY5VY6FO.js} +1 -0
- package/dist/{chunk-TGIVCSKT.js → chunk-XLPLTBKX.js} +1 -1
- package/dist/cli.js +34 -8
- package/dist/{completions-7DD5E5HL.js → completions-HYTAOGE4.js} +1 -1
- package/dist/{setup-skill-TVJJD6NC.js → setup-skill-RQG43DFT.js} +2 -2
- package/dist/time-to-detection-5L7ECKYU.js +82 -0
- package/dist/{types-KUPHV2V3.js → types-ZO5KKMXK.js} +11 -0
- package/package.json +3 -3
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-
|
|
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-
|
|
14
|
+
} from "./chunk-XLPLTBKX.js";
|
|
15
15
|
import {
|
|
16
16
|
compareVersions,
|
|
17
17
|
getCliVersion
|
|
18
18
|
} from "./chunk-OKIUEBUK.js";
|
|
19
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
1992
|
+
const { printCompletions } = await import("./completions-HYTAOGE4.js");
|
|
1967
1993
|
printCompletions(subcommand);
|
|
1968
1994
|
break;
|
|
1969
1995
|
}
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
readInstalledSkillVersion,
|
|
7
7
|
setupSkill,
|
|
8
8
|
uninstallSkill
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XLPLTBKX.js";
|
|
10
10
|
import "./chunk-OKIUEBUK.js";
|
|
11
|
-
import "./chunk-
|
|
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.
|
|
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.
|
|
37
|
-
"@chainpatrol/sdk": "1.
|
|
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",
|