@chainpatrol/cli 0.12.0 → 0.14.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 +56 -0
- package/dist/{breakdown-KCTKQNDY.js → breakdown-K5Q6TDKE.js} +21 -6
- package/dist/{check-P5JJJNEX.js → check-NJH7BUIM.js} +1 -1
- package/dist/{chunk-W42ITB5J.js → chunk-6X5BFFXA.js} +77 -5
- package/dist/{chunk-DJVB73FL.js → chunk-BIKTBRER.js} +1 -1
- package/dist/chunk-S37VFIZY.js +66 -0
- package/dist/{chunk-OSRFCVBT.js → chunk-YZEMTNCX.js} +26 -2
- package/dist/cli.js +53 -43
- package/dist/{configs-update-3ZVXGADV.js → configs-update-ON67NMXA.js} +1 -1
- package/dist/{create-DFH2G7RH.js → create-W5S2ZPVC.js} +1 -1
- package/dist/{drift-T74V2C3J.js → drift-ZOLDE24A.js} +1 -1
- package/dist/found-M5MX4VXK.js +59 -0
- package/dist/{healthcheck-HZYEIIOR.js → healthcheck-PFYP6PNW.js} +1 -1
- package/dist/{list-PBAXQSL5.js → list-5TMGSHFC.js} +1 -1
- package/dist/{list-QUFY4IP7.js → list-AHWEE3YZ.js} +1 -1
- package/dist/{list-IYJBKE2F.js → list-B5KLEFHA.js} +1 -1
- package/dist/{list-ZFPML3ZR.js → list-FV5IYCG4.js} +1 -1
- package/dist/{list-23QQYAV5.js → list-GKC7PEU5.js} +1 -1
- package/dist/{list-YCT6DYDI.js → list-HGYBEYOV.js} +1 -1
- package/dist/{list-YRJPN4PI.js → list-ISP5JL4M.js} +1 -1
- package/dist/{list-WX5G3IOF.js → list-LNNIEVB4.js} +2 -2
- package/dist/{list-WJQT2XLE.js → list-TUMVDWIZ.js} +1 -1
- package/dist/{list-I7SBGA52.js → list-VP5IY2SZ.js} +1 -1
- package/dist/{list-EIVPGMWJ.js → list-Z7RJWZH5.js} +1 -1
- package/dist/{list-json-T2Z7NV7G.js → list-json-QC6X25LI.js} +1 -1
- package/dist/organization-VSQQAGBS.js +105 -0
- package/dist/{run-CJGN6TX4.js → run-HRFJORDS.js} +1 -1
- package/dist/{run-BHEXWBAS.js → run-KDNQIR5E.js} +2 -2
- package/dist/{run-MSO6IXEV.js → run-VHFXOCGS.js} +1 -1
- package/dist/{search-WPSKQGUS.js → search-NOSQW75S.js} +1 -1
- package/dist/{setup-skill-FA4STSGA.js → setup-skill-Q6PCFIDE.js} +1 -1
- package/dist/{snapshot-ZWAHEG64.js → snapshot-55UV26NQ.js} +1 -1
- package/dist/{summary-Y2LG6UJM.js → summary-QUCLZYX6.js} +19 -6
- package/dist/{validate-SM5DD7I2.js → validate-TPG47SYS.js} +1 -1
- package/dist/{whoami-DH5HVLFN.js → whoami-Y3ZJGN4O.js} +1 -1
- package/package.json +1 -1
- package/dist/found-EVXJZGYX.js +0 -95
- package/dist/organization-IIIOSTHF.js +0 -75
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @chainpatrol/cli
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 339729c: Add an `--include` filter to `chainpatrol metrics organization` (backed
|
|
8
|
+
by a new optional `include` field on `GET /organization/metrics`) so
|
|
9
|
+
callers can ask for just the metrics they care about instead of paying
|
|
10
|
+
for the full ~11-aggregate fan-out.
|
|
11
|
+
|
|
12
|
+
`--include` (or `include: [...]` in the JSON body) accepts a comma list
|
|
13
|
+
of any of: `reports`, `newThreats`, `threatsWatchlisted`,
|
|
14
|
+
`takedownsFiled`, `takedownsCompleted`, `domainThreats`,
|
|
15
|
+
`twitterThreats`, `telegramThreats`, `otherThreats`, `blockedByType`,
|
|
16
|
+
`blockedByDay`. The server runs only those Prisma aggregates;
|
|
17
|
+
unrequested fields come back as `null`. Omitting `--include` (or
|
|
18
|
+
passing an empty list) preserves the existing "compute everything"
|
|
19
|
+
behavior.
|
|
20
|
+
|
|
21
|
+
This is the targeted follow-up to the 3-month-default change for the
|
|
22
|
+
same 503 issue: agents that only need takedown counts can now skip the
|
|
23
|
+
nine other counts entirely, keeping each call well inside the platform
|
|
24
|
+
function cap and cheap enough to run repeatedly per org.
|
|
25
|
+
|
|
26
|
+
The response schema's metric fields and `blockedByType`/`blockedByDay`
|
|
27
|
+
arrays are now nullable to express "not included" — TypeScript callers
|
|
28
|
+
of the CLI's `OrganizationMetricsResult` that previously read
|
|
29
|
+
`result.metrics.reports` as `number` will need `?? 0` (or to opt in to
|
|
30
|
+
`--include` and check for `null` explicitly).
|
|
31
|
+
|
|
32
|
+
## 0.13.0
|
|
33
|
+
|
|
34
|
+
### Minor Changes
|
|
35
|
+
|
|
36
|
+
- 7cc2783: Default every `chainpatrol metrics …` subcommand
|
|
37
|
+
(`summary` / `found` / `breakdown` / `organization`) to a trailing
|
|
38
|
+
**3-month** window when no `--from`/`--to`/`--this-week` flag is
|
|
39
|
+
supplied, and echo the resolved range in every output format
|
|
40
|
+
(human, markdown, csv, json) so consumers can see exactly what
|
|
41
|
+
window the numbers describe.
|
|
42
|
+
|
|
43
|
+
Previously, omitting the date flags sent `undefined` for `startDate`
|
|
44
|
+
/ `endDate`, which caused the metrics handler to fan out ~15 parallel
|
|
45
|
+
unbounded Prisma aggregates per call. On large orgs that routinely
|
|
46
|
+
exceeded the platform's 30s function cap and surfaced to agents as
|
|
47
|
+
flaky `503` responses. The new default keeps the queries bounded
|
|
48
|
+
and produces a stable, labelled result.
|
|
49
|
+
|
|
50
|
+
`--this-week` is now also wired through to `metrics summary`,
|
|
51
|
+
`metrics breakdown`, and `metrics organization` (it was already
|
|
52
|
+
supported on `metrics found`).
|
|
53
|
+
|
|
54
|
+
JSON output now includes a `range: { startDate, endDate, label }`
|
|
55
|
+
field on every metrics subcommand. The label is one of
|
|
56
|
+
`"last 3 months"` (default), `"this week"`, or
|
|
57
|
+
`"custom range (<startIso> → <endIso>)"`.
|
|
58
|
+
|
|
3
59
|
## 0.12.0
|
|
4
60
|
|
|
5
61
|
### Minor Changes
|
|
@@ -1,31 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveDateRange
|
|
3
|
+
} from "./chunk-S37VFIZY.js";
|
|
1
4
|
import {
|
|
2
5
|
printOutput,
|
|
3
6
|
toCsvRows
|
|
4
7
|
} from "./chunk-VFT3TD3E.js";
|
|
5
8
|
import {
|
|
6
9
|
createApiClient
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-YZEMTNCX.js";
|
|
8
11
|
import "./chunk-EGWK6SRQ.js";
|
|
9
12
|
import "./chunk-TFCNKBRC.js";
|
|
10
13
|
import "./chunk-U73SABXK.js";
|
|
11
14
|
|
|
12
15
|
// src/commands/metrics/breakdown.ts
|
|
13
16
|
async function runMetricsBreakdown(options) {
|
|
17
|
+
const range = resolveDateRange({
|
|
18
|
+
from: options.from,
|
|
19
|
+
to: options.to,
|
|
20
|
+
thisWeek: options.thisWeek
|
|
21
|
+
});
|
|
14
22
|
const client = options.apiClient ?? createApiClient();
|
|
15
23
|
const result = await client.getMetricsBreakdown({
|
|
16
24
|
slug: options.org,
|
|
17
25
|
by: options.by,
|
|
18
|
-
startDate:
|
|
19
|
-
endDate:
|
|
26
|
+
startDate: range.startDate,
|
|
27
|
+
endDate: range.endDate,
|
|
20
28
|
brandIds: options.brandIds
|
|
21
29
|
});
|
|
22
30
|
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
23
31
|
printOutput({
|
|
24
32
|
outputFormat,
|
|
25
|
-
json: result,
|
|
33
|
+
json: { ...result, range },
|
|
26
34
|
markdown: [
|
|
27
|
-
`# Metrics Breakdown (${options.org})`,
|
|
35
|
+
`# Metrics Breakdown (${options.org}) \u2014 ${range.label}`,
|
|
28
36
|
"",
|
|
37
|
+
`- Range: ${range.startDate} \u2192 ${range.endDate}`,
|
|
29
38
|
`- Grouped by: ${result.by}`,
|
|
30
39
|
"",
|
|
31
40
|
...result.points.map((point) => {
|
|
@@ -40,6 +49,9 @@ async function runMetricsBreakdown(options) {
|
|
|
40
49
|
].join("\n"),
|
|
41
50
|
csv: toCsvRows(
|
|
42
51
|
result.points.map((point) => ({
|
|
52
|
+
rangeLabel: range.label,
|
|
53
|
+
startDate: range.startDate,
|
|
54
|
+
endDate: range.endDate,
|
|
43
55
|
key: point.key,
|
|
44
56
|
count: point.count,
|
|
45
57
|
date: point.date ?? null,
|
|
@@ -50,7 +62,10 @@ async function runMetricsBreakdown(options) {
|
|
|
50
62
|
}))
|
|
51
63
|
),
|
|
52
64
|
human: () => {
|
|
53
|
-
console.log(
|
|
65
|
+
console.log(
|
|
66
|
+
`Metrics breakdown for ${options.org} by ${result.by} \u2014 ${range.label}`
|
|
67
|
+
);
|
|
68
|
+
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
54
69
|
for (const point of result.points) {
|
|
55
70
|
if (result.by === "day") {
|
|
56
71
|
console.log(`${point.date ?? point.key}: ${point.count}`);
|
|
@@ -659,20 +659,92 @@ takedowns?", reach for \`orgs list\` \u2014 it's the only command that exposes
|
|
|
659
659
|
service flags across multiple orgs in one call. Run it in \`--json\` mode
|
|
660
660
|
and summarize patterns by service or by subscription tier.
|
|
661
661
|
|
|
662
|
-
### \`metrics summary | found | breakdown\` \u2014 Org metrics for spike/drop analysis
|
|
662
|
+
### \`metrics summary | found | breakdown | organization\` \u2014 Org metrics for spike/drop analysis
|
|
663
663
|
|
|
664
664
|
\`\`\`bash
|
|
665
|
-
chainpatrol --json metrics summary
|
|
666
|
-
chainpatrol --json metrics
|
|
667
|
-
chainpatrol --json metrics
|
|
665
|
+
chainpatrol --json metrics summary --org <slug> # defaults to last 3 months
|
|
666
|
+
chainpatrol --json metrics summary --org <slug> --this-week
|
|
667
|
+
chainpatrol --json metrics breakdown --org <slug> --by day --this-week
|
|
668
|
+
chainpatrol --json metrics found --org <slug> --from <YYYY-MM-DD> --to <YYYY-MM-DD>
|
|
669
|
+
chainpatrol --json metrics organization --org <slug> # defaults to last 3 months
|
|
668
670
|
\`\`\`
|
|
669
671
|
|
|
672
|
+
**Always operate on a bounded window.** When no \`--from\`/\`--to\`/\`--this-week\`
|
|
673
|
+
is passed, every metrics subcommand defaults to the trailing **last 3 months**
|
|
674
|
+
ending now. The default exists because unbounded org-wide aggregates over
|
|
675
|
+
multi-year history routinely time out at the platform layer (Vercel's
|
|
676
|
+
30s function cap), which surfaces to clients as a 503. Stick to the default,
|
|
677
|
+
or pass an explicit narrower window \u2014 don't try to bypass the default by
|
|
678
|
+
guessing wide \`--from\` values.
|
|
679
|
+
|
|
680
|
+
The resolved range is echoed back in every output format so the user can
|
|
681
|
+
see exactly what they got:
|
|
682
|
+
|
|
683
|
+
- JSON: \`{ "range": { "startDate": "...", "endDate": "...", "label": "last 3 months" } }\`
|
|
684
|
+
- Markdown / human: the header line includes the label, e.g.
|
|
685
|
+
\`Organization metrics for acme \u2014 last 3 months\`, followed by
|
|
686
|
+
\`Range: <startDate> \u2192 <endDate>\`.
|
|
687
|
+
|
|
688
|
+
When reporting numbers to the user (especially averages and rates),
|
|
689
|
+
**state the window explicitly** \u2014 say "averaged over the last 3 months"
|
|
690
|
+
rather than just quoting a number, since the same prompt phrased
|
|
691
|
+
differently can pick a different window.
|
|
692
|
+
|
|
670
693
|
\`breakdown\` is the one you usually want for healthchecks: it returns a
|
|
671
694
|
time series (by day or week) of reports, new threats, watchlisted threats,
|
|
672
695
|
and takedowns filed/completed. Compare the latest period against a prior
|
|
673
696
|
window to spot the **spike** or **drop** signals described in the manual
|
|
674
697
|
HealthCheck Guide. \`summary\` returns a single window total; \`found\` is
|
|
675
|
-
oriented around when threats were first discovered
|
|
698
|
+
oriented around when threats were first discovered; \`organization\` is
|
|
699
|
+
the full customer-facing dashboard slice (reports, new threats,
|
|
700
|
+
watchlisted, takedowns filed/completed, plus per-type and per-day
|
|
701
|
+
breakdowns).
|
|
702
|
+
|
|
703
|
+
#### \`--include\` \u2014 only compute the metrics you actually need
|
|
704
|
+
|
|
705
|
+
\`metrics organization\` runs one Prisma aggregate per requested field.
|
|
706
|
+
By default all 11 are computed in parallel. Pass \`--include\` (or
|
|
707
|
+
\`include: [\u2026]\` in the JSON body) with the comma-separated subset you
|
|
708
|
+
care about to skip the rest \u2014 the unrequested fields come back as
|
|
709
|
+
\`null\` instead of a number, and the server never runs those queries:
|
|
710
|
+
|
|
711
|
+
\`\`\`bash
|
|
712
|
+
# Only takedowns \u2014 one of the cheap shapes
|
|
713
|
+
chainpatrol --json metrics organization --org <slug> \\
|
|
714
|
+
--include takedownsFiled,takedownsCompleted
|
|
715
|
+
|
|
716
|
+
# Time series only, no scalar counts
|
|
717
|
+
chainpatrol --json metrics organization --org <slug> --include blockedByDay
|
|
718
|
+
\`\`\`
|
|
719
|
+
|
|
720
|
+
Allowed values: \`reports\`, \`newThreats\`, \`threatsWatchlisted\`,
|
|
721
|
+
\`takedownsFiled\`, \`takedownsCompleted\`, \`domainThreats\`,
|
|
722
|
+
\`twitterThreats\`, \`telegramThreats\`, \`otherThreats\`,
|
|
723
|
+
\`blockedByType\`, \`blockedByDay\`.
|
|
724
|
+
|
|
725
|
+
Use this whenever the user's question is specific ("how many takedowns
|
|
726
|
+
did we file last month?"). It is the lowest-effort way to make a
|
|
727
|
+
metrics call cheap enough to run repeatedly. Pair it with an explicit
|
|
728
|
+
date window for the best behavior.
|
|
729
|
+
|
|
730
|
+
#### "Across all orgs" via an API key \u2014 what's actually possible
|
|
731
|
+
|
|
732
|
+
A ChainPatrol API key is scoped to a single organization. The
|
|
733
|
+
\`/organization/metrics\` endpoint derives the org from the key, so
|
|
734
|
+
**there is no way to fetch metrics for multiple orgs with one API key**.
|
|
735
|
+
If the user asks for "averages across all orgs":
|
|
736
|
+
|
|
737
|
+
1. If they're authenticated with a user session, list accessible orgs
|
|
738
|
+
first (\`chainpatrol --json orgs list\`) and then call
|
|
739
|
+
\`metrics organization\` once per org **serially** (concurrent
|
|
740
|
+
fan-out across many orgs is what triggers 503/throttling on the
|
|
741
|
+
metrics endpoint).
|
|
742
|
+
2. If they're using a single API key, explain that the key only
|
|
743
|
+
covers one org \u2014 they need either session auth or one key per
|
|
744
|
+
org, not a loop with the same key.
|
|
745
|
+
3. Whatever window you choose, **make it explicit** in your reply
|
|
746
|
+
("last 3 months", "last 7 days", etc) \u2014 never report an average
|
|
747
|
+
without naming the window it was computed over.
|
|
676
748
|
|
|
677
749
|
### \`presets list | run\` \u2014 Packaged workflows for common jobs
|
|
678
750
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DateTime
|
|
3
|
+
} from "./chunk-TFCNKBRC.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/date-range.ts
|
|
6
|
+
var DEFAULT_RANGE_MONTHS = 3;
|
|
7
|
+
var DEFAULT_RANGE_LABEL = `last ${DEFAULT_RANGE_MONTHS} months`;
|
|
8
|
+
function customLabel(start, end) {
|
|
9
|
+
const startIso = start.toUTC().toISODate();
|
|
10
|
+
const endIso = end.toUTC().toISODate();
|
|
11
|
+
return `custom range (${startIso} \u2192 ${endIso})`;
|
|
12
|
+
}
|
|
13
|
+
function resolveDateRange({
|
|
14
|
+
from,
|
|
15
|
+
to,
|
|
16
|
+
thisWeek
|
|
17
|
+
}) {
|
|
18
|
+
if (from && to) {
|
|
19
|
+
const start2 = DateTime.fromISO(from).toUTC();
|
|
20
|
+
const end = DateTime.fromISO(to).toUTC();
|
|
21
|
+
return {
|
|
22
|
+
startDate: start2.toISO() ?? from,
|
|
23
|
+
endDate: end.toISO() ?? to,
|
|
24
|
+
label: customLabel(start2, end)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (from && !to) {
|
|
28
|
+
const now2 = DateTime.now().toUTC();
|
|
29
|
+
const start2 = DateTime.fromISO(from).toUTC();
|
|
30
|
+
return {
|
|
31
|
+
startDate: start2.toISO() ?? from,
|
|
32
|
+
endDate: now2.toISO() ?? "",
|
|
33
|
+
label: customLabel(start2, now2)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (!from && to) {
|
|
37
|
+
const end = DateTime.fromISO(to).toUTC();
|
|
38
|
+
const start2 = end.minus({ days: 7 });
|
|
39
|
+
return {
|
|
40
|
+
startDate: start2.toISO() ?? "",
|
|
41
|
+
endDate: end.toISO() ?? to,
|
|
42
|
+
label: customLabel(start2, end)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (thisWeek) {
|
|
46
|
+
const now2 = DateTime.now().toUTC();
|
|
47
|
+
const start2 = now2.startOf("week");
|
|
48
|
+
const end = now2.endOf("week");
|
|
49
|
+
return {
|
|
50
|
+
startDate: start2.toISO() ?? now2.minus({ days: 7 }).toISO() ?? "",
|
|
51
|
+
endDate: end.toISO() ?? now2.toISO() ?? "",
|
|
52
|
+
label: "this week"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const now = DateTime.now().toUTC();
|
|
56
|
+
const start = now.minus({ months: DEFAULT_RANGE_MONTHS });
|
|
57
|
+
return {
|
|
58
|
+
startDate: start.toISO() ?? "",
|
|
59
|
+
endDate: now.toISO() ?? "",
|
|
60
|
+
label: DEFAULT_RANGE_LABEL
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
resolveDateRange
|
|
66
|
+
};
|
|
@@ -45,6 +45,19 @@ var TAKEDOWN_SORT_KEYS = [
|
|
|
45
45
|
"assigneeId",
|
|
46
46
|
"brandId"
|
|
47
47
|
];
|
|
48
|
+
var ORGANIZATION_METRICS_INCLUDE_FIELDS = [
|
|
49
|
+
"reports",
|
|
50
|
+
"newThreats",
|
|
51
|
+
"threatsWatchlisted",
|
|
52
|
+
"takedownsFiled",
|
|
53
|
+
"takedownsCompleted",
|
|
54
|
+
"domainThreats",
|
|
55
|
+
"twitterThreats",
|
|
56
|
+
"telegramThreats",
|
|
57
|
+
"otherThreats",
|
|
58
|
+
"blockedByType",
|
|
59
|
+
"blockedByDay"
|
|
60
|
+
];
|
|
48
61
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
49
62
|
function defaultGetCredential() {
|
|
50
63
|
const envKey = process.env.CHAINPATROL_API_KEY;
|
|
@@ -71,7 +84,16 @@ function createApiClient(options) {
|
|
|
71
84
|
const headers = buildAuthHeaders();
|
|
72
85
|
const search = new URLSearchParams();
|
|
73
86
|
for (const [key, value] of Object.entries(query)) {
|
|
74
|
-
if (value
|
|
87
|
+
if (value === void 0) continue;
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
for (const item of value) {
|
|
90
|
+
if (item !== void 0 && item !== "") {
|
|
91
|
+
search.append(key, item);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (value !== "") {
|
|
75
97
|
search.set(key, value);
|
|
76
98
|
}
|
|
77
99
|
}
|
|
@@ -258,7 +280,8 @@ function createApiClient(options) {
|
|
|
258
280
|
organizationSlug: input.organizationSlug,
|
|
259
281
|
brandSlug: input.brandSlug,
|
|
260
282
|
startDate: parseIsoDateString(input.startDate),
|
|
261
|
-
endDate: parseIsoDateString(input.endDate)
|
|
283
|
+
endDate: parseIsoDateString(input.endDate),
|
|
284
|
+
include: input.include
|
|
262
285
|
});
|
|
263
286
|
},
|
|
264
287
|
listHealthchecks() {
|
|
@@ -378,5 +401,6 @@ export {
|
|
|
378
401
|
TAKEDOWN_STATUSES,
|
|
379
402
|
LIVENESS_STATUSES,
|
|
380
403
|
TAKEDOWN_SORT_KEYS,
|
|
404
|
+
ORGANIZATION_METRICS_INCLUDE_FIELDS,
|
|
381
405
|
createApiClient
|
|
382
406
|
};
|