@chainpatrol/cli 1.7.0 → 1.8.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 +27 -0
- package/dist/{chunk-YMO4QK6Q.js → chunk-LE5ZOOUI.js} +1 -1
- package/dist/{chunk-7BZUQSRO.js → chunk-TI2HK3NM.js} +1 -0
- package/dist/cli.js +31 -6
- package/dist/{completions-3ESPMO65.js → completions-7DD5E5HL.js} +1 -1
- package/dist/{setup-skill-LP5Z3Z5T.js → setup-skill-ESJ4HIKR.js} +2 -2
- package/dist/takedown-time-trend-LL3ZCCSD.js +103 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @chainpatrol/cli
|
|
2
2
|
|
|
3
|
+
## 1.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 33ba556: Adds time-to-takedown trends: `client.getMetricsTakedownTimeTrend()` in the SDK
|
|
8
|
+
and `chainpatrol metrics takedown-time-trend --org <slug>` in the CLI, both
|
|
9
|
+
backed by the new `POST /metrics/takedown-time/trend` endpoint. Returns the
|
|
10
|
+
median time from takedown start to completion bucketed by the month a takedown
|
|
11
|
+
completed, over a trailing window of whole months, plus the month-over-month
|
|
12
|
+
change for each series. Pass `assetTypes` (max 5) to get one series per asset
|
|
13
|
+
type instead of a single pooled series.
|
|
14
|
+
|
|
15
|
+
The month-over-month comparison uses the two most recent months that actually
|
|
16
|
+
had completed takedowns, so a quiet month in between doesn't read as "no
|
|
17
|
+
change", and `direction` reads lower-is-better: `"improved"` means the median
|
|
18
|
+
fell. Months with no completed takedowns carry a `null` median rather than zero.
|
|
19
|
+
|
|
20
|
+
Server-side work lives in @chainpatrol/external-trpc, @chainpatrol/validation
|
|
21
|
+
and @chainpatrol/metrics; all three are private packages and don't need version
|
|
22
|
+
bumps. The endpoint is gated on the new `median-time-to-takedown-trends-api`
|
|
23
|
+
feature flag, separate from the flag on `POST /metrics/takedown-time`.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [33ba556]
|
|
28
|
+
- @chainpatrol/sdk@1.8.0
|
|
29
|
+
|
|
3
30
|
## 1.7.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installCompletions,
|
|
3
3
|
uninstallCompletions
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TI2HK3NM.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";
|
|
@@ -95,6 +95,7 @@ _chainpatrol() {
|
|
|
95
95
|
'found:Show found threats count'
|
|
96
96
|
'breakdown:Show metrics breakdown'
|
|
97
97
|
'takedown-time:Show median time to takedown by asset type'
|
|
98
|
+
'takedown-time-trend:Show month-over-month median time-to-takedown trend'
|
|
98
99
|
'organization:Get organization metrics (official endpoint)'
|
|
99
100
|
)
|
|
100
101
|
_describe 'subcommand' metrics_subcommands
|
package/dist/cli.js
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
getCliVersion,
|
|
14
14
|
isSkillInstalled,
|
|
15
15
|
readInstalledSkillVersion
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-LE5ZOOUI.js";
|
|
17
|
+
import "./chunk-TI2HK3NM.js";
|
|
18
18
|
import {
|
|
19
19
|
DateTime
|
|
20
20
|
} from "./chunk-TFCNKBRC.js";
|
|
@@ -287,6 +287,7 @@ var HELP = {
|
|
|
287
287
|
"chainpatrol metrics found --org acme --from 2025-01-01 --to 2025-02-01",
|
|
288
288
|
"chainpatrol metrics breakdown --org acme --by day --this-week",
|
|
289
289
|
"chainpatrol metrics takedown-time --org acme --from 2025-01-01 --to 2025-02-01",
|
|
290
|
+
"chainpatrol metrics takedown-time-trend --org acme --months 12",
|
|
290
291
|
"chainpatrol metrics organization --org acme --from 2025-01-01 --to 2025-02-01"
|
|
291
292
|
]
|
|
292
293
|
},
|
|
@@ -358,6 +359,16 @@ var HELP = {
|
|
|
358
359
|
"--brand <ids> Comma-separated brand IDs"
|
|
359
360
|
]
|
|
360
361
|
},
|
|
362
|
+
"metrics takedown-time-trend": {
|
|
363
|
+
description: "Median time from takedown start to completion bucketed by month, over a trailing window, with the month-over-month change.",
|
|
364
|
+
usage: "chainpatrol metrics takedown-time-trend --org <slug> [--months 12] [--asset-type TWITTER,URL]",
|
|
365
|
+
options: [
|
|
366
|
+
"--org <slug> Organization slug",
|
|
367
|
+
"--months <n> Trailing window in whole months (2-36, default 12)",
|
|
368
|
+
"--asset-type <types> Comma-separated asset types to break the trend down by (max 5)",
|
|
369
|
+
"--brand <ids> Comma-separated brand IDs"
|
|
370
|
+
]
|
|
371
|
+
},
|
|
361
372
|
reports: {
|
|
362
373
|
description: "Create, list, and search reports from the terminal.",
|
|
363
374
|
usage: "chainpatrol reports <create|list|search>",
|
|
@@ -967,6 +978,7 @@ ${getTopLevelHelp()}
|
|
|
967
978
|
to: { type: "string" },
|
|
968
979
|
thisWeek: { type: "boolean", default: false },
|
|
969
980
|
by: { type: "string" },
|
|
981
|
+
months: { type: "number" },
|
|
970
982
|
include: { type: "string" },
|
|
971
983
|
slugs: { type: "string" },
|
|
972
984
|
allMyOrgs: { type: "boolean", default: false },
|
|
@@ -1536,6 +1548,18 @@ async function main() {
|
|
|
1536
1548
|
});
|
|
1537
1549
|
break;
|
|
1538
1550
|
}
|
|
1551
|
+
if (subcommand === "takedown-time-trend" || subcommand === "takedownTimeTrend") {
|
|
1552
|
+
const { runMetricsTakedownTimeTrend } = await import("./takedown-time-trend-LL3ZCCSD.js");
|
|
1553
|
+
await runMetricsTakedownTimeTrend({
|
|
1554
|
+
org,
|
|
1555
|
+
months: cli.flags.months,
|
|
1556
|
+
assetTypes: parseCsvList(cli.flags.assetType),
|
|
1557
|
+
brandIds: parseBrandIds(),
|
|
1558
|
+
json: jsonMode,
|
|
1559
|
+
outputFormat: cliContext.outputFormat
|
|
1560
|
+
});
|
|
1561
|
+
break;
|
|
1562
|
+
}
|
|
1539
1563
|
if (subcommand === "breakdown") {
|
|
1540
1564
|
const by = cli.flags.by;
|
|
1541
1565
|
if (!by || !["day", "type", "brand"].includes(by)) {
|
|
@@ -1559,10 +1583,11 @@ async function main() {
|
|
|
1559
1583
|
"found",
|
|
1560
1584
|
"breakdown",
|
|
1561
1585
|
"takedown-time",
|
|
1586
|
+
"takedown-time-trend",
|
|
1562
1587
|
"organization"
|
|
1563
1588
|
]) : null;
|
|
1564
1589
|
throw new Error(
|
|
1565
|
-
subcommand ? `Unknown subcommand: metrics ${subcommand}${hint ? `. Did you mean "metrics ${hint}"?` : ""}` : "Usage: chainpatrol metrics <summary|found|breakdown|takedown-time|organization>"
|
|
1590
|
+
subcommand ? `Unknown subcommand: metrics ${subcommand}${hint ? `. Did you mean "metrics ${hint}"?` : ""}` : "Usage: chainpatrol metrics <summary|found|breakdown|takedown-time|takedown-time-trend|organization>"
|
|
1566
1591
|
);
|
|
1567
1592
|
}
|
|
1568
1593
|
case "reports": {
|
|
@@ -1896,17 +1921,17 @@ async function main() {
|
|
|
1896
1921
|
case "setup":
|
|
1897
1922
|
case "install":
|
|
1898
1923
|
case "i": {
|
|
1899
|
-
const { setupSkill } = await import("./setup-skill-
|
|
1924
|
+
const { setupSkill } = await import("./setup-skill-ESJ4HIKR.js");
|
|
1900
1925
|
setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
|
|
1901
1926
|
break;
|
|
1902
1927
|
}
|
|
1903
1928
|
case "uninstall": {
|
|
1904
|
-
const { uninstallSkill } = await import("./setup-skill-
|
|
1929
|
+
const { uninstallSkill } = await import("./setup-skill-ESJ4HIKR.js");
|
|
1905
1930
|
uninstallSkill({ json: jsonMode });
|
|
1906
1931
|
break;
|
|
1907
1932
|
}
|
|
1908
1933
|
case "completions": {
|
|
1909
|
-
const { printCompletions } = await import("./completions-
|
|
1934
|
+
const { printCompletions } = await import("./completions-7DD5E5HL.js");
|
|
1910
1935
|
printCompletions(subcommand);
|
|
1911
1936
|
break;
|
|
1912
1937
|
}
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
readInstalledSkillVersion,
|
|
7
7
|
setupSkill,
|
|
8
8
|
uninstallSkill
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-LE5ZOOUI.js";
|
|
10
|
+
import "./chunk-TI2HK3NM.js";
|
|
11
11
|
export {
|
|
12
12
|
getBundledSkillContent,
|
|
13
13
|
getBundledSkillVersion,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
printOutput,
|
|
3
|
+
toCsvRows
|
|
4
|
+
} from "./chunk-VFT3TD3E.js";
|
|
5
|
+
import {
|
|
6
|
+
createApiClient
|
|
7
|
+
} from "./chunk-322AVMUH.js";
|
|
8
|
+
import "./chunk-EGWK6SRQ.js";
|
|
9
|
+
import "./chunk-U73SABXK.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/metrics/takedown-time-trend.ts
|
|
12
|
+
function formatDuration(hours) {
|
|
13
|
+
if (hours === null) {
|
|
14
|
+
return "\u2014";
|
|
15
|
+
}
|
|
16
|
+
if (hours < 1 / 60) {
|
|
17
|
+
return `${Math.round(hours * 3600)}s`;
|
|
18
|
+
}
|
|
19
|
+
if (hours < 1) {
|
|
20
|
+
return `${Math.round(hours * 60)}m`;
|
|
21
|
+
}
|
|
22
|
+
if (hours < 24) {
|
|
23
|
+
return `${hours.toFixed(1)}h`;
|
|
24
|
+
}
|
|
25
|
+
return `${(hours / 24).toFixed(1)}d`;
|
|
26
|
+
}
|
|
27
|
+
function formatPercentChange(percentChange) {
|
|
28
|
+
if (percentChange === null) {
|
|
29
|
+
return "no comparable earlier month";
|
|
30
|
+
}
|
|
31
|
+
const sign = percentChange > 0 ? "+" : "";
|
|
32
|
+
return `${sign}${percentChange.toFixed(1)}%`;
|
|
33
|
+
}
|
|
34
|
+
async function runMetricsTakedownTimeTrend(options) {
|
|
35
|
+
const client = options.apiClient ?? createApiClient();
|
|
36
|
+
const result = await client.getMetricsTakedownTimeTrend({
|
|
37
|
+
slug: options.org,
|
|
38
|
+
months: options.months,
|
|
39
|
+
assetTypes: options.assetTypes,
|
|
40
|
+
brandIds: options.brandIds
|
|
41
|
+
});
|
|
42
|
+
const seriesLabel = (assetType) => assetType ?? "All asset types";
|
|
43
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
44
|
+
printOutput({
|
|
45
|
+
outputFormat,
|
|
46
|
+
json: result,
|
|
47
|
+
markdown: [
|
|
48
|
+
`# Median Time to Takedown Trend (${options.org}) \u2014 last ${result.months} months`,
|
|
49
|
+
"",
|
|
50
|
+
...result.series.flatMap((series) => [
|
|
51
|
+
`## ${seriesLabel(series.assetType)}`,
|
|
52
|
+
"",
|
|
53
|
+
`- Month over month: ${formatPercentChange(series.change.percentChange)}${series.change.direction ? ` (${series.change.direction})` : ""}`,
|
|
54
|
+
"",
|
|
55
|
+
"| Month | Median | Takedowns |",
|
|
56
|
+
"| --- | --- | --- |",
|
|
57
|
+
...series.points.map(
|
|
58
|
+
(point) => `| ${point.period} | ${formatDuration(point.medianTakedownHours)} | ${point.count} |`
|
|
59
|
+
),
|
|
60
|
+
""
|
|
61
|
+
])
|
|
62
|
+
].join("\n"),
|
|
63
|
+
csv: toCsvRows(
|
|
64
|
+
result.series.flatMap(
|
|
65
|
+
(series) => series.points.map((point) => ({
|
|
66
|
+
assetType: series.assetType ?? "",
|
|
67
|
+
period: point.period,
|
|
68
|
+
medianTakedownMinutes: point.medianTakedownMinutes,
|
|
69
|
+
medianTakedownHours: point.medianTakedownHours,
|
|
70
|
+
medianTakedownDays: point.medianTakedownDays,
|
|
71
|
+
count: point.count,
|
|
72
|
+
percentChange: series.change.percentChange,
|
|
73
|
+
direction: series.change.direction
|
|
74
|
+
}))
|
|
75
|
+
)
|
|
76
|
+
),
|
|
77
|
+
human: () => {
|
|
78
|
+
console.log(
|
|
79
|
+
`Median time to takedown trend for ${options.org} \u2014 last ${result.months} months`
|
|
80
|
+
);
|
|
81
|
+
for (const series of result.series) {
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(seriesLabel(series.assetType));
|
|
84
|
+
for (const point of series.points) {
|
|
85
|
+
console.log(
|
|
86
|
+
` ${point.period}: ${formatDuration(point.medianTakedownHours)} (${point.count} takedowns)`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const { latest, previous, percentChange, direction } = series.change;
|
|
90
|
+
if (latest && previous && percentChange !== null) {
|
|
91
|
+
console.log(
|
|
92
|
+
` Month over month (${previous.period} \u2192 ${latest.period}): ${formatPercentChange(percentChange)} \u2014 ${direction}`
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
console.log(` Month over month: ${formatPercentChange(percentChange)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
runMetricsTakedownTimeTrend
|
|
103
|
+
};
|
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.8.0",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"homepage": "https://chainpatrol.com/docs/cli",
|
|
8
8
|
"keywords": [
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"lint": "npx oxlint ."
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@chainpatrol/sdk": "1.
|
|
36
|
+
"@chainpatrol/sdk": "1.8.0",
|
|
37
37
|
"ink": "^7.0.1",
|
|
38
38
|
"meow": "^14.1.0",
|
|
39
39
|
"open": "^11.0.0",
|