@chainpatrol/cli 0.14.0 → 0.15.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 +34 -0
- package/dist/{breakdown-K5Q6TDKE.js → breakdown-27PMFEWQ.js} +1 -1
- package/dist/{check-NJH7BUIM.js → check-M5U2CSWK.js} +1 -1
- package/dist/{chunk-BIKTBRER.js → chunk-FOTFWUOX.js} +1 -1
- package/dist/{chunk-6X5BFFXA.js → chunk-QGKR7EPH.js} +75 -18
- package/dist/{chunk-YZEMTNCX.js → chunk-UZDSNWGL.js} +1 -0
- package/dist/cli.js +42 -37
- package/dist/{configs-update-ON67NMXA.js → configs-update-YMPRYXKZ.js} +1 -1
- package/dist/{create-W5S2ZPVC.js → create-DQYNYD6G.js} +1 -1
- package/dist/{drift-ZOLDE24A.js → drift-T4AF6Y3X.js} +1 -1
- package/dist/{found-M5MX4VXK.js → found-XEGMO6YY.js} +1 -1
- package/dist/{healthcheck-PFYP6PNW.js → healthcheck-FORGIJWP.js} +1 -1
- package/dist/{list-Z7RJWZH5.js → list-63HPS2RE.js} +1 -1
- package/dist/{list-LNNIEVB4.js → list-BJMP3QEV.js} +2 -2
- package/dist/{list-ISP5JL4M.js → list-COJ5BLQV.js} +1 -1
- package/dist/{list-TUMVDWIZ.js → list-DVPEMSVH.js} +1 -1
- package/dist/{list-FV5IYCG4.js → list-N4TTNR6N.js} +1 -1
- package/dist/{list-GKC7PEU5.js → list-OWJLAWMP.js} +1 -1
- package/dist/{list-AHWEE3YZ.js → list-RMD4NKVB.js} +1 -1
- package/dist/{list-5TMGSHFC.js → list-VOWXWXHY.js} +1 -1
- package/dist/{list-VP5IY2SZ.js → list-WPXNCBT3.js} +1 -1
- package/dist/{list-HGYBEYOV.js → list-XHBQMP6P.js} +1 -1
- package/dist/{list-B5KLEFHA.js → list-ZTMCEG56.js} +1 -1
- package/dist/{list-json-QC6X25LI.js → list-json-X6QHIRE5.js} +1 -1
- package/dist/organization-HD6LRAZP.js +251 -0
- package/dist/{run-KDNQIR5E.js → run-BHAH7DUC.js} +2 -2
- package/dist/{run-HRFJORDS.js → run-G5J77GZD.js} +1 -1
- package/dist/{run-VHFXOCGS.js → run-V7QJBNLF.js} +1 -1
- package/dist/{search-NOSQW75S.js → search-ME6KFCEV.js} +1 -1
- package/dist/{setup-skill-Q6PCFIDE.js → setup-skill-ENJSVGLR.js} +1 -1
- package/dist/{snapshot-55UV26NQ.js → snapshot-B6TQCFQ3.js} +1 -1
- package/dist/{summary-QUCLZYX6.js → summary-NYHIQ5DP.js} +1 -1
- package/dist/{validate-TPG47SYS.js → validate-I3YHZDZR.js} +1 -1
- package/dist/{whoami-Y3ZJGN4O.js → whoami-6KU4LOJS.js} +1 -1
- package/package.json +1 -1
- package/dist/organization-VSQQAGBS.js +0 -105
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @chainpatrol/cli
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e24afdc: `GET /organization/metrics` (and the matching `chainpatrol metrics
|
|
8
|
+
organization --slugs` CLI flag) now accept a list of org slugs and
|
|
9
|
+
return a single rolled-up response: `metrics` becomes the sum across
|
|
10
|
+
the queried orgs, `perOrg` holds the per-org breakdown, and a new
|
|
11
|
+
`averages` field provides `perDay` (total / windowDays) plus
|
|
12
|
+
`perOrgPerDay` (total / numOrgs / windowDays) so callers don't have
|
|
13
|
+
to do the math themselves. The single-org form is unchanged on the
|
|
14
|
+
wire except for the new `averages.perDay` and `scope: { mode, orgs }`
|
|
15
|
+
additions, and `averages.perOrgPerDay` is `null` for single-org
|
|
16
|
+
callers.
|
|
17
|
+
|
|
18
|
+
Lets an agent answer "how many threats did we take down across all
|
|
19
|
+
clients in the last 7 days?" — or "what's our average takedowns per
|
|
20
|
+
customer per day this month?" — in **one** HTTP call with one
|
|
21
|
+
`--include`-narrowed Prisma aggregate per org (run server-side with
|
|
22
|
+
bounded concurrency), instead of the N-round-trip fan-out that was
|
|
23
|
+
previously the only path. `slugs` is capped at 50 per call, mutually
|
|
24
|
+
exclusive with `organizationSlug`, and disallowed with `brandSlug`.
|
|
25
|
+
|
|
26
|
+
Auth: org-scoped API keys remain single-org and are rejected if
|
|
27
|
+
`slugs` contains any foreign org; user API keys (which inherit the
|
|
28
|
+
owning user's memberships) and Bearer sessions can pass any orgs
|
|
29
|
+
the principal has access to. Staff users can query any org, as
|
|
30
|
+
before.
|
|
31
|
+
|
|
32
|
+
The CLI's `metrics organization` command picks up a `--slugs`
|
|
33
|
+
flag, prints totals + per-day / per-org-per-day averages + a
|
|
34
|
+
per-org breakdown in human/markdown/csv output, and surfaces a
|
|
35
|
+
`scope` and `averages` block in the JSON envelope.
|
|
36
|
+
|
|
3
37
|
## 0.14.0
|
|
4
38
|
|
|
5
39
|
### Minor Changes
|
|
@@ -727,24 +727,81 @@ did we file last month?"). It is the lowest-effort way to make a
|
|
|
727
727
|
metrics call cheap enough to run repeatedly. Pair it with an explicit
|
|
728
728
|
date window for the best behavior.
|
|
729
729
|
|
|
730
|
-
####
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
730
|
+
#### \`--slugs\` \u2014 multi-org rollups in one call (use this for "across all customers/clients")
|
|
731
|
+
|
|
732
|
+
When the user asks for a total or an average **across more than one
|
|
733
|
+
org** \u2014 e.g. "how many takedowns did we complete across all clients
|
|
734
|
+
in the last 7 days?", "average reports per org per day this month",
|
|
735
|
+
"top 5 customers by new threats" \u2014 reach for \`--slugs\` instead of
|
|
736
|
+
looping. The server fans out the per-org aggregates internally,
|
|
737
|
+
sums into totals, computes per-day and per-org-per-day averages,
|
|
738
|
+
and returns a per-org breakdown in a **single HTTP round trip**:
|
|
739
|
+
|
|
740
|
+
\`\`\`bash
|
|
741
|
+
# Total takedowns completed across three customers, last 7 days
|
|
742
|
+
chainpatrol --json metrics organization \\
|
|
743
|
+
--slugs acme,beta,gamma \\
|
|
744
|
+
--include takedownsCompleted \\
|
|
745
|
+
--from 2026-05-12 --to 2026-05-19
|
|
746
|
+
\`\`\`
|
|
747
|
+
|
|
748
|
+
The response shape switches to multi-org:
|
|
749
|
+
|
|
750
|
+
\`\`\`json
|
|
751
|
+
{
|
|
752
|
+
"scope": { "mode": "multi", "orgs": ["acme", "beta", "gamma"] },
|
|
753
|
+
"metrics": { "takedownsCompleted": 117, ... }, // sum across orgs
|
|
754
|
+
"averages": {
|
|
755
|
+
"perDay": { "takedownsCompleted": 16.71, ... }, // total / windowDays
|
|
756
|
+
"perOrgPerDay": { "takedownsCompleted": 5.57, ... }, // total / numOrgs / windowDays
|
|
757
|
+
"windowDays": 7
|
|
758
|
+
},
|
|
759
|
+
"perOrg": { "acme": { "metrics": { ... } }, "beta": { ... }, "gamma": { ... } }
|
|
760
|
+
}
|
|
761
|
+
\`\`\`
|
|
762
|
+
|
|
763
|
+
To answer **"across all of our customers"**, list the orgs first:
|
|
764
|
+
|
|
765
|
+
\`\`\`bash
|
|
766
|
+
chainpatrol --json orgs list --subscription-status ACTIVE # \u2190 get slugs
|
|
767
|
+
chainpatrol --json metrics organization \\
|
|
768
|
+
--slugs <comma-list of all the slugs from above> \\
|
|
769
|
+
--include takedownsCompleted \\
|
|
770
|
+
--from 2026-05-12 --to 2026-05-19
|
|
771
|
+
\`\`\`
|
|
772
|
+
|
|
773
|
+
The endpoint caps \`slugs\` at 50 per call; if you need more, split
|
|
774
|
+
the list and sum the totals client-side. **Do not loop \`metrics
|
|
775
|
+
organization\` once per org** when \`--slugs\` will do \u2014 N HTTP
|
|
776
|
+
round-trips is what made the old code 503.
|
|
777
|
+
|
|
778
|
+
Always pair \`--slugs\` with \`--include\` to narrow the metric set to
|
|
779
|
+
what the user actually asked about. "How many takedowns did we
|
|
780
|
+
complete?" \u2192 \`--include takedownsCompleted\`; "average reports per
|
|
781
|
+
day per customer?" \u2192 \`--include reports\`. Skipping \`--include\` runs
|
|
782
|
+
~11 aggregates per org which is rarely necessary.
|
|
783
|
+
|
|
784
|
+
#### Auth: which credential can use \`--slugs\`
|
|
785
|
+
|
|
786
|
+
- **Org-scoped API key** (the most common production key): can only
|
|
787
|
+
query its own org. If \`--slugs\` contains anything else the server
|
|
788
|
+
returns 403. Use the \`--org\` form (or omit both) instead.
|
|
789
|
+
- **User API key**: inherits the user's org memberships, so it can
|
|
790
|
+
query any org the user is a member of (or any org for staff
|
|
791
|
+
users). This is the credential you want for cross-org rollups
|
|
792
|
+
from automated agents.
|
|
793
|
+
- **Session auth** (Bearer token, e.g. \`chainpatrol login\`): same
|
|
794
|
+
membership rules as the user. Staff users can query any org.
|
|
795
|
+
|
|
796
|
+
#### Reporting numbers to the user
|
|
797
|
+
|
|
798
|
+
Whatever window and scope you choose, **make both explicit in your
|
|
799
|
+
reply** \u2014 say "averaged across 12 customers over the last 3 months"
|
|
800
|
+
or "summed across all paying orgs for the last 7 days." The same
|
|
801
|
+
prompt phrased slightly differently can pick a different window or
|
|
802
|
+
org set, and the response already echoes
|
|
803
|
+
\`scope.mode\` / \`scope.orgs\` / \`averages.windowDays\` /
|
|
804
|
+
\`range.label\` so you don't have to guess.
|
|
748
805
|
|
|
749
806
|
### \`presets list | run\` \u2014 Packaged workflows for common jobs
|
|
750
807
|
|
|
@@ -278,6 +278,7 @@ function createApiClient(options) {
|
|
|
278
278
|
getOrganizationMetrics(input) {
|
|
279
279
|
return requestGet("/organization/metrics", {
|
|
280
280
|
organizationSlug: input.organizationSlug,
|
|
281
|
+
slugs: input.slugs,
|
|
281
282
|
brandSlug: input.brandSlug,
|
|
282
283
|
startDate: parseIsoDateString(input.startDate),
|
|
283
284
|
endDate: parseIsoDateString(input.endDate),
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getCliVersion,
|
|
14
14
|
isSkillInstalled,
|
|
15
15
|
readInstalledSkillVersion
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-QGKR7EPH.js";
|
|
17
17
|
import "./chunk-NQT77IB7.js";
|
|
18
18
|
import {
|
|
19
19
|
DateTime
|
|
@@ -263,11 +263,12 @@ var HELP = {
|
|
|
263
263
|
]
|
|
264
264
|
},
|
|
265
265
|
"metrics organization": {
|
|
266
|
-
description: "Get organization metrics via the documented GET /organization/metrics endpoint. Works with
|
|
267
|
-
usage: "chainpatrol metrics organization [--org <slug>] [--from ISO --to ISO|--this-week] [--include <list>]",
|
|
266
|
+
description: "Get organization metrics via the documented GET /organization/metrics endpoint. Works with org-scoped API keys (org derived from key, single-org only), user API keys (inherits user permissions, multi-org allowed), and session auth. Defaults to the trailing 3 months if no date range is given. Use --include to skip aggregates the caller doesn't need; use --slugs to query several orgs in one call and get totals + per-org breakdown + per-day / per-org-per-day averages. Fields not included come back as null.",
|
|
267
|
+
usage: "chainpatrol metrics organization [--org <slug>|--slugs <list>] [--from ISO --to ISO|--this-week] [--include <list>]",
|
|
268
268
|
options: [
|
|
269
|
-
"--org <slug>
|
|
270
|
-
"--
|
|
269
|
+
"--org <slug> Single organization slug (omit when using an org-scoped API key). Mutually exclusive with --slugs.",
|
|
270
|
+
"--slugs <list> Comma list of org slugs (max 50). Response includes totals across these orgs and a per-org breakdown plus per-day / per-org-per-day averages. Not allowed for org-scoped API keys with foreign slugs.",
|
|
271
|
+
"--brand-slug <slug> Filter by brand slug (requires brands-metrics flag; only valid with a single org)",
|
|
271
272
|
"--from <iso> Start date (ISO 8601, e.g. 2026-01-01)",
|
|
272
273
|
"--to <iso> End date (ISO 8601)",
|
|
273
274
|
"--this-week Use the current ISO week (overrides default)",
|
|
@@ -277,6 +278,7 @@ var HELP = {
|
|
|
277
278
|
"chainpatrol metrics organization --org acme # defaults to last 3 months, all metrics",
|
|
278
279
|
"chainpatrol metrics organization --org acme --include takedownsFiled,takedownsCompleted",
|
|
279
280
|
"chainpatrol metrics organization --org acme --from 2026-01-01 --to 2026-02-01",
|
|
281
|
+
"chainpatrol metrics organization --slugs acme,beta,gamma --include takedownsCompleted --from 2026-05-12 --to 2026-05-19 # 7-day takedown total + per-day / per-org-per-day averages",
|
|
280
282
|
"CHAINPATROL_API_KEY=... chainpatrol metrics organization --json --include reports"
|
|
281
283
|
]
|
|
282
284
|
},
|
|
@@ -861,6 +863,7 @@ ${getTopLevelHelp()}
|
|
|
861
863
|
thisWeek: { type: "boolean", default: false },
|
|
862
864
|
by: { type: "string" },
|
|
863
865
|
include: { type: "string" },
|
|
866
|
+
slugs: { type: "string" },
|
|
864
867
|
brand: { type: "string" },
|
|
865
868
|
enable: { type: "boolean", default: false },
|
|
866
869
|
disable: { type: "boolean", default: false },
|
|
@@ -1034,12 +1037,12 @@ function parseAttachmentUrls() {
|
|
|
1034
1037
|
}
|
|
1035
1038
|
async function handleConfigsList(org) {
|
|
1036
1039
|
if (jsonMode) {
|
|
1037
|
-
const { listConfigsJson } = await import("./list-json-
|
|
1040
|
+
const { listConfigsJson } = await import("./list-json-X6QHIRE5.js");
|
|
1038
1041
|
await listConfigsJson({ org });
|
|
1039
1042
|
return;
|
|
1040
1043
|
}
|
|
1041
1044
|
const { render } = await import("ink");
|
|
1042
|
-
const { default: ConfigsList } = await import("./list-
|
|
1045
|
+
const { default: ConfigsList } = await import("./list-ZTMCEG56.js");
|
|
1043
1046
|
const { default: React } = await import("react");
|
|
1044
1047
|
render(React.createElement(ConfigsList, { org }));
|
|
1045
1048
|
}
|
|
@@ -1139,7 +1142,7 @@ async function main() {
|
|
|
1139
1142
|
const positionals = cli.input.slice(2);
|
|
1140
1143
|
const flagAssets = parseAssetInputs();
|
|
1141
1144
|
const contents = [...positionals, ...flagAssets];
|
|
1142
|
-
const { runAssetCheck } = await import("./check-
|
|
1145
|
+
const { runAssetCheck } = await import("./check-M5U2CSWK.js");
|
|
1143
1146
|
await runAssetCheck({
|
|
1144
1147
|
contents,
|
|
1145
1148
|
json: jsonMode,
|
|
@@ -1149,7 +1152,7 @@ async function main() {
|
|
|
1149
1152
|
break;
|
|
1150
1153
|
}
|
|
1151
1154
|
if (subcommand === "list") {
|
|
1152
|
-
const { runAssetList } = await import("./list-
|
|
1155
|
+
const { runAssetList } = await import("./list-VOWXWXHY.js");
|
|
1153
1156
|
await runAssetList({
|
|
1154
1157
|
type: cli.flags.type ?? cli.flags.assetType,
|
|
1155
1158
|
status: cli.flags.assetStatus ?? cli.flags.status,
|
|
@@ -1170,7 +1173,7 @@ async function main() {
|
|
|
1170
1173
|
case "detections": {
|
|
1171
1174
|
const org = await resolveOrg();
|
|
1172
1175
|
if (subcommand === "healthcheck") {
|
|
1173
|
-
const { runDetectionsHealthcheck } = await import("./healthcheck-
|
|
1176
|
+
const { runDetectionsHealthcheck } = await import("./healthcheck-FORGIJWP.js");
|
|
1174
1177
|
await runDetectionsHealthcheck({
|
|
1175
1178
|
org,
|
|
1176
1179
|
source: cli.flags.source,
|
|
@@ -1185,7 +1188,7 @@ async function main() {
|
|
|
1185
1188
|
break;
|
|
1186
1189
|
}
|
|
1187
1190
|
if (subcommand === "validate") {
|
|
1188
|
-
const { runDetectionsValidate } = await import("./validate-
|
|
1191
|
+
const { runDetectionsValidate } = await import("./validate-I3YHZDZR.js");
|
|
1189
1192
|
await runDetectionsValidate({
|
|
1190
1193
|
org,
|
|
1191
1194
|
source: cli.flags.source,
|
|
@@ -1200,7 +1203,7 @@ async function main() {
|
|
|
1200
1203
|
break;
|
|
1201
1204
|
}
|
|
1202
1205
|
if (subcommand === "drift") {
|
|
1203
|
-
const { runDetectionsDrift } = await import("./drift-
|
|
1206
|
+
const { runDetectionsDrift } = await import("./drift-T4AF6Y3X.js");
|
|
1204
1207
|
await runDetectionsDrift({
|
|
1205
1208
|
org,
|
|
1206
1209
|
source: cli.flags.source,
|
|
@@ -1214,7 +1217,7 @@ async function main() {
|
|
|
1214
1217
|
break;
|
|
1215
1218
|
}
|
|
1216
1219
|
if (subcommand === "run") {
|
|
1217
|
-
const { runDetectionsRun } = await import("./run-
|
|
1220
|
+
const { runDetectionsRun } = await import("./run-V7QJBNLF.js");
|
|
1218
1221
|
await runDetectionsRun({
|
|
1219
1222
|
org,
|
|
1220
1223
|
configId: cli.flags.configId,
|
|
@@ -1228,7 +1231,7 @@ async function main() {
|
|
|
1228
1231
|
break;
|
|
1229
1232
|
}
|
|
1230
1233
|
if (subcommand === "list") {
|
|
1231
|
-
const { runDetectionsList } = await import("./list-
|
|
1234
|
+
const { runDetectionsList } = await import("./list-RMD4NKVB.js");
|
|
1232
1235
|
await runDetectionsList({
|
|
1233
1236
|
org,
|
|
1234
1237
|
cursor: cli.flags.cursor,
|
|
@@ -1257,7 +1260,7 @@ async function main() {
|
|
|
1257
1260
|
break;
|
|
1258
1261
|
}
|
|
1259
1262
|
if (action === "run") {
|
|
1260
|
-
const { runDetectionsRun } = await import("./run-
|
|
1263
|
+
const { runDetectionsRun } = await import("./run-V7QJBNLF.js");
|
|
1261
1264
|
await runDetectionsRun({
|
|
1262
1265
|
org,
|
|
1263
1266
|
configId: cli.flags.configId,
|
|
@@ -1275,7 +1278,7 @@ async function main() {
|
|
|
1275
1278
|
throw new Error("detections configs update requires --config-id");
|
|
1276
1279
|
}
|
|
1277
1280
|
const configPatch = getConfigPatchFromSetFlags();
|
|
1278
|
-
const { runDetectionsConfigsUpdate } = await import("./configs-update-
|
|
1281
|
+
const { runDetectionsConfigsUpdate } = await import("./configs-update-YMPRYXKZ.js");
|
|
1279
1282
|
await runDetectionsConfigsUpdate({
|
|
1280
1283
|
org,
|
|
1281
1284
|
configId: cli.flags.configId,
|
|
@@ -1308,10 +1311,12 @@ async function main() {
|
|
|
1308
1311
|
}
|
|
1309
1312
|
case "metrics": {
|
|
1310
1313
|
if (subcommand === "organization" || subcommand === "org") {
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1314
|
+
const slugs = parseCsvList(cli.flags.slugs);
|
|
1315
|
+
const org2 = slugs && slugs.length > 0 ? void 0 : await tryResolveOrg();
|
|
1316
|
+
const { runMetricsOrganization } = await import("./organization-HD6LRAZP.js");
|
|
1313
1317
|
await runMetricsOrganization({
|
|
1314
1318
|
org: org2,
|
|
1319
|
+
slugs,
|
|
1315
1320
|
brandSlug: cli.flags.brandSlug,
|
|
1316
1321
|
from: cli.flags.from,
|
|
1317
1322
|
to: cli.flags.to,
|
|
@@ -1324,7 +1329,7 @@ async function main() {
|
|
|
1324
1329
|
}
|
|
1325
1330
|
const org = await resolveOrg();
|
|
1326
1331
|
if (subcommand === "summary") {
|
|
1327
|
-
const { runMetricsSummary } = await import("./summary-
|
|
1332
|
+
const { runMetricsSummary } = await import("./summary-NYHIQ5DP.js");
|
|
1328
1333
|
await runMetricsSummary({
|
|
1329
1334
|
org,
|
|
1330
1335
|
from: cli.flags.from,
|
|
@@ -1337,7 +1342,7 @@ async function main() {
|
|
|
1337
1342
|
break;
|
|
1338
1343
|
}
|
|
1339
1344
|
if (subcommand === "found") {
|
|
1340
|
-
const { runMetricsFound } = await import("./found-
|
|
1345
|
+
const { runMetricsFound } = await import("./found-XEGMO6YY.js");
|
|
1341
1346
|
await runMetricsFound({
|
|
1342
1347
|
org,
|
|
1343
1348
|
from: cli.flags.from,
|
|
@@ -1354,7 +1359,7 @@ async function main() {
|
|
|
1354
1359
|
if (!by || !["day", "type", "brand"].includes(by)) {
|
|
1355
1360
|
throw new Error("metrics breakdown requires --by <day|type|brand>");
|
|
1356
1361
|
}
|
|
1357
|
-
const { runMetricsBreakdown } = await import("./breakdown-
|
|
1362
|
+
const { runMetricsBreakdown } = await import("./breakdown-27PMFEWQ.js");
|
|
1358
1363
|
await runMetricsBreakdown({
|
|
1359
1364
|
org,
|
|
1360
1365
|
by,
|
|
@@ -1375,7 +1380,7 @@ async function main() {
|
|
|
1375
1380
|
case "reports": {
|
|
1376
1381
|
if (subcommand === "list") {
|
|
1377
1382
|
const org = await resolveOrg();
|
|
1378
|
-
const { runReportsList } = await import("./list-
|
|
1383
|
+
const { runReportsList } = await import("./list-N4TTNR6N.js");
|
|
1379
1384
|
await runReportsList({
|
|
1380
1385
|
org,
|
|
1381
1386
|
limit: cli.flags.limit,
|
|
@@ -1403,7 +1408,7 @@ async function main() {
|
|
|
1403
1408
|
}
|
|
1404
1409
|
if (subcommand === "create") {
|
|
1405
1410
|
const org = await tryResolveOrg();
|
|
1406
|
-
const { runReportsCreate } = await import("./create-
|
|
1411
|
+
const { runReportsCreate } = await import("./create-DQYNYD6G.js");
|
|
1407
1412
|
await runReportsCreate({
|
|
1408
1413
|
org,
|
|
1409
1414
|
title: cli.flags.title,
|
|
@@ -1424,7 +1429,7 @@ async function main() {
|
|
|
1424
1429
|
const positionals = cli.input.slice(2);
|
|
1425
1430
|
const flagAssets = parseAssetInputs();
|
|
1426
1431
|
const assetContents = [...positionals, ...flagAssets];
|
|
1427
|
-
const { runReportsSearch } = await import("./search-
|
|
1432
|
+
const { runReportsSearch } = await import("./search-ME6KFCEV.js");
|
|
1428
1433
|
await runReportsSearch({
|
|
1429
1434
|
assetContents,
|
|
1430
1435
|
reportedByCustomer: cli.flags.reportedByCustomer,
|
|
@@ -1440,7 +1445,7 @@ async function main() {
|
|
|
1440
1445
|
}
|
|
1441
1446
|
case "threats": {
|
|
1442
1447
|
if (subcommand === "list") {
|
|
1443
|
-
const { runThreatsList } = await import("./list-
|
|
1448
|
+
const { runThreatsList } = await import("./list-XHBQMP6P.js");
|
|
1444
1449
|
await runThreatsList({
|
|
1445
1450
|
query: cli.flags.query,
|
|
1446
1451
|
from: cli.flags.from,
|
|
@@ -1463,7 +1468,7 @@ async function main() {
|
|
|
1463
1468
|
case "takedowns": {
|
|
1464
1469
|
if (subcommand === "list") {
|
|
1465
1470
|
const org = await tryResolveOrg();
|
|
1466
|
-
const { runTakedownsList } = await import("./list-
|
|
1471
|
+
const { runTakedownsList } = await import("./list-DVPEMSVH.js");
|
|
1467
1472
|
await runTakedownsList({
|
|
1468
1473
|
org,
|
|
1469
1474
|
query: cli.flags.query,
|
|
@@ -1494,7 +1499,7 @@ async function main() {
|
|
|
1494
1499
|
}
|
|
1495
1500
|
case "queues": {
|
|
1496
1501
|
if (subcommand === "snapshot") {
|
|
1497
|
-
const { runQueuesSnapshot } = await import("./snapshot-
|
|
1502
|
+
const { runQueuesSnapshot } = await import("./snapshot-B6TQCFQ3.js");
|
|
1498
1503
|
await runQueuesSnapshot({
|
|
1499
1504
|
org: cli.flags.org,
|
|
1500
1505
|
all: cli.flags.all,
|
|
@@ -1512,7 +1517,7 @@ async function main() {
|
|
|
1512
1517
|
}
|
|
1513
1518
|
case "orgs": {
|
|
1514
1519
|
if (subcommand === "list") {
|
|
1515
|
-
const { runOrgsList } = await import("./list-
|
|
1520
|
+
const { runOrgsList } = await import("./list-COJ5BLQV.js");
|
|
1516
1521
|
await runOrgsList({
|
|
1517
1522
|
query: cli.flags.query,
|
|
1518
1523
|
subscriptionStatus: cli.flags.subscriptionStatus,
|
|
@@ -1527,7 +1532,7 @@ async function main() {
|
|
|
1527
1532
|
}
|
|
1528
1533
|
if (subcommand === "assets") {
|
|
1529
1534
|
if (action === "list") {
|
|
1530
|
-
const { runOrgsAssetsList } = await import("./list-
|
|
1535
|
+
const { runOrgsAssetsList } = await import("./list-63HPS2RE.js");
|
|
1531
1536
|
await runOrgsAssetsList({
|
|
1532
1537
|
type: cli.flags.type ?? cli.flags.assetType,
|
|
1533
1538
|
groupId: cli.flags.groupId,
|
|
@@ -1543,7 +1548,7 @@ async function main() {
|
|
|
1543
1548
|
}
|
|
1544
1549
|
if (subcommand === "asset-groups") {
|
|
1545
1550
|
if (action === "list") {
|
|
1546
|
-
const { runOrgsAssetGroupsList } = await import("./list-
|
|
1551
|
+
const { runOrgsAssetGroupsList } = await import("./list-WPXNCBT3.js");
|
|
1547
1552
|
await runOrgsAssetGroupsList({
|
|
1548
1553
|
json: jsonMode,
|
|
1549
1554
|
outputFormat: cliContext.outputFormat
|
|
@@ -1558,7 +1563,7 @@ async function main() {
|
|
|
1558
1563
|
);
|
|
1559
1564
|
}
|
|
1560
1565
|
case "whoami": {
|
|
1561
|
-
const { runWhoami } = await import("./whoami-
|
|
1566
|
+
const { runWhoami } = await import("./whoami-6KU4LOJS.js");
|
|
1562
1567
|
await runWhoami({
|
|
1563
1568
|
json: jsonMode,
|
|
1564
1569
|
outputFormat: cliContext.outputFormat
|
|
@@ -1567,7 +1572,7 @@ async function main() {
|
|
|
1567
1572
|
}
|
|
1568
1573
|
case "healthchecks": {
|
|
1569
1574
|
if (subcommand === "list") {
|
|
1570
|
-
const { runHealthchecksList } = await import("./list-
|
|
1575
|
+
const { runHealthchecksList } = await import("./list-OWJLAWMP.js");
|
|
1571
1576
|
await runHealthchecksList({
|
|
1572
1577
|
json: jsonMode,
|
|
1573
1578
|
outputFormat: cliContext.outputFormat
|
|
@@ -1581,7 +1586,7 @@ async function main() {
|
|
|
1581
1586
|
thresholds.minResults = cli.flags.minResults;
|
|
1582
1587
|
if (cli.flags.lookbackHours !== void 0)
|
|
1583
1588
|
thresholds.lookbackHours = cli.flags.lookbackHours;
|
|
1584
|
-
const { runHealthchecksRun } = await import("./run-
|
|
1589
|
+
const { runHealthchecksRun } = await import("./run-G5J77GZD.js");
|
|
1585
1590
|
await runHealthchecksRun({
|
|
1586
1591
|
org,
|
|
1587
1592
|
id: action,
|
|
@@ -1599,7 +1604,7 @@ async function main() {
|
|
|
1599
1604
|
}
|
|
1600
1605
|
case "presets": {
|
|
1601
1606
|
if (subcommand === "list") {
|
|
1602
|
-
const { runPresetsList } = await import("./list-
|
|
1607
|
+
const { runPresetsList } = await import("./list-BJMP3QEV.js");
|
|
1603
1608
|
await runPresetsList({ outputFormat: cliContext.outputFormat });
|
|
1604
1609
|
break;
|
|
1605
1610
|
}
|
|
@@ -1610,7 +1615,7 @@ async function main() {
|
|
|
1610
1615
|
);
|
|
1611
1616
|
}
|
|
1612
1617
|
const org = await resolveOrg();
|
|
1613
|
-
const { runPresetsRun } = await import("./run-
|
|
1618
|
+
const { runPresetsRun } = await import("./run-BHAH7DUC.js");
|
|
1614
1619
|
await runPresetsRun({
|
|
1615
1620
|
presetId: action,
|
|
1616
1621
|
org,
|
|
@@ -1627,12 +1632,12 @@ async function main() {
|
|
|
1627
1632
|
case "setup":
|
|
1628
1633
|
case "install":
|
|
1629
1634
|
case "i": {
|
|
1630
|
-
const { setupSkill } = await import("./setup-skill-
|
|
1635
|
+
const { setupSkill } = await import("./setup-skill-ENJSVGLR.js");
|
|
1631
1636
|
setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
|
|
1632
1637
|
break;
|
|
1633
1638
|
}
|
|
1634
1639
|
case "uninstall": {
|
|
1635
|
-
const { uninstallSkill } = await import("./setup-skill-
|
|
1640
|
+
const { uninstallSkill } = await import("./setup-skill-ENJSVGLR.js");
|
|
1636
1641
|
uninstallSkill({ json: jsonMode });
|
|
1637
1642
|
break;
|
|
1638
1643
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRESETS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FOTFWUOX.js";
|
|
4
4
|
import "./chunk-E2LAMILJ.js";
|
|
5
5
|
import {
|
|
6
6
|
printOutput,
|
|
7
7
|
toCsvRows
|
|
8
8
|
} from "./chunk-VFT3TD3E.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-UZDSNWGL.js";
|
|
10
10
|
import "./chunk-EGWK6SRQ.js";
|
|
11
11
|
import "./chunk-TFCNKBRC.js";
|
|
12
12
|
import "./chunk-U73SABXK.js";
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveDateRange
|
|
3
|
+
} from "./chunk-S37VFIZY.js";
|
|
4
|
+
import {
|
|
5
|
+
printOutput,
|
|
6
|
+
toCsvRows
|
|
7
|
+
} from "./chunk-VFT3TD3E.js";
|
|
8
|
+
import {
|
|
9
|
+
ORGANIZATION_METRICS_INCLUDE_FIELDS,
|
|
10
|
+
createApiClient
|
|
11
|
+
} from "./chunk-UZDSNWGL.js";
|
|
12
|
+
import "./chunk-EGWK6SRQ.js";
|
|
13
|
+
import "./chunk-TFCNKBRC.js";
|
|
14
|
+
import "./chunk-U73SABXK.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/metrics/organization.ts
|
|
17
|
+
var INCLUDE_VALUES = new Set(ORGANIZATION_METRICS_INCLUDE_FIELDS);
|
|
18
|
+
var METRIC_LABELS = [
|
|
19
|
+
["reports", "Reports"],
|
|
20
|
+
["newThreats", "New threats"],
|
|
21
|
+
["threatsWatchlisted", "Watchlisted threats"],
|
|
22
|
+
["takedownsFiled", "Takedowns filed"],
|
|
23
|
+
["takedownsCompleted", "Takedowns completed"],
|
|
24
|
+
["domainThreats", "Domain threats"],
|
|
25
|
+
["twitterThreats", "Twitter threats"],
|
|
26
|
+
["telegramThreats", "Telegram threats"],
|
|
27
|
+
["otherThreats", "Other threats"]
|
|
28
|
+
];
|
|
29
|
+
function parseIncludeList(raw) {
|
|
30
|
+
if (!raw || raw.length === 0) return void 0;
|
|
31
|
+
const fields = [];
|
|
32
|
+
for (const value of raw) {
|
|
33
|
+
if (!INCLUDE_VALUES.has(value)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Unknown --include value: '${value}'. Allowed: ${ORGANIZATION_METRICS_INCLUDE_FIELDS.join(", ")}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
fields.push(value);
|
|
39
|
+
}
|
|
40
|
+
return fields;
|
|
41
|
+
}
|
|
42
|
+
function formatAverage(value) {
|
|
43
|
+
if (value === null) return "n/a";
|
|
44
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(2);
|
|
45
|
+
}
|
|
46
|
+
function populatedMetricRows(metrics, format = (v) => String(v)) {
|
|
47
|
+
return METRIC_LABELS.filter(([key]) => metrics[key] !== null).map(([key, label]) => [
|
|
48
|
+
label,
|
|
49
|
+
format(metrics[key])
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
async function runMetricsOrganization(options) {
|
|
53
|
+
if (options.org && options.slugs && options.slugs.length > 0) {
|
|
54
|
+
throw new Error("Pass either --org or --slugs, not both.");
|
|
55
|
+
}
|
|
56
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
57
|
+
const client = options.apiClient ?? createApiClient();
|
|
58
|
+
const range = resolveDateRange({
|
|
59
|
+
from: options.from,
|
|
60
|
+
to: options.to,
|
|
61
|
+
thisWeek: options.thisWeek
|
|
62
|
+
});
|
|
63
|
+
const include = parseIncludeList(options.include);
|
|
64
|
+
const input = {
|
|
65
|
+
organizationSlug: options.org,
|
|
66
|
+
slugs: options.slugs && options.slugs.length > 0 ? options.slugs : void 0,
|
|
67
|
+
brandSlug: options.brandSlug,
|
|
68
|
+
startDate: range.startDate,
|
|
69
|
+
endDate: range.endDate,
|
|
70
|
+
include
|
|
71
|
+
};
|
|
72
|
+
const result = await client.getOrganizationMetrics(input);
|
|
73
|
+
const isMulti = result.scope.mode === "multi";
|
|
74
|
+
const scopeLabel = isMulti ? `${result.scope.orgs.length} orgs (${result.scope.orgs.join(", ")})` : result.scope.orgs[0] ?? options.org ?? "your organization";
|
|
75
|
+
const includeLabel = include ? ` (include: ${include.join(", ")})` : "";
|
|
76
|
+
const totalsRows = populatedMetricRows(result.metrics);
|
|
77
|
+
const perDayRows = result.averages.perDay ? populatedMetricRows(result.averages.perDay, formatAverage) : [];
|
|
78
|
+
const perOrgPerDayRows = result.averages.perOrgPerDay ? populatedMetricRows(result.averages.perOrgPerDay, formatAverage) : [];
|
|
79
|
+
printOutput({
|
|
80
|
+
outputFormat,
|
|
81
|
+
json: { ...result, range, include: include ?? null },
|
|
82
|
+
markdown: buildMarkdown({
|
|
83
|
+
scopeLabel,
|
|
84
|
+
rangeLabel: range.label,
|
|
85
|
+
includeLabel,
|
|
86
|
+
isMulti,
|
|
87
|
+
range,
|
|
88
|
+
result,
|
|
89
|
+
totalsRows,
|
|
90
|
+
perDayRows,
|
|
91
|
+
perOrgPerDayRows
|
|
92
|
+
}),
|
|
93
|
+
csv: toCsvRows(buildCsvRows({ result, range, isMulti })),
|
|
94
|
+
human: () => {
|
|
95
|
+
const headline = isMulti ? `Multi-org metrics \u2014 ${scopeLabel} \u2014 ${range.label}${includeLabel}` : `Organization metrics for ${scopeLabel} \u2014 ${range.label}${includeLabel}`;
|
|
96
|
+
console.log(headline);
|
|
97
|
+
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
98
|
+
const totalsHeader = isMulti ? "Totals (sum across orgs):" : "Totals:";
|
|
99
|
+
console.log(totalsHeader);
|
|
100
|
+
for (const [name, value] of totalsRows) {
|
|
101
|
+
console.log(` ${name}: ${value}`);
|
|
102
|
+
}
|
|
103
|
+
if (result.averages.windowDays !== null && perDayRows.length > 0) {
|
|
104
|
+
console.log("");
|
|
105
|
+
console.log(`Averages per day (window = ${result.averages.windowDays} days):`);
|
|
106
|
+
for (const [name, value] of perDayRows) {
|
|
107
|
+
console.log(` ${name}: ${value}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (perOrgPerDayRows.length > 0) {
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(`Averages per org per day:`);
|
|
113
|
+
for (const [name, value] of perOrgPerDayRows) {
|
|
114
|
+
console.log(` ${name}: ${value}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (isMulti && result.perOrg) {
|
|
118
|
+
console.log("");
|
|
119
|
+
console.log("Per-org breakdown:");
|
|
120
|
+
for (const slug of result.scope.orgs) {
|
|
121
|
+
const entry = result.perOrg[slug];
|
|
122
|
+
if (!entry) continue;
|
|
123
|
+
const rows = populatedMetricRows(entry.metrics);
|
|
124
|
+
if (rows.length === 0) continue;
|
|
125
|
+
console.log(` ${slug}:`);
|
|
126
|
+
for (const [name, value] of rows) {
|
|
127
|
+
console.log(` ${name}: ${value}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!isMulti && result.blockedByDay && result.blockedByDay.length > 0) {
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(`Blocked by day (${result.blockedByDay.length} entries):`);
|
|
134
|
+
for (const entry of result.blockedByDay) {
|
|
135
|
+
console.log(` ${entry.date}: ${entry.count}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function buildMarkdown(args) {
|
|
142
|
+
const {
|
|
143
|
+
scopeLabel,
|
|
144
|
+
rangeLabel,
|
|
145
|
+
includeLabel,
|
|
146
|
+
isMulti,
|
|
147
|
+
range,
|
|
148
|
+
result,
|
|
149
|
+
totalsRows,
|
|
150
|
+
perDayRows,
|
|
151
|
+
perOrgPerDayRows
|
|
152
|
+
} = args;
|
|
153
|
+
const lines = [];
|
|
154
|
+
const heading = isMulti ? "Multi-Org Metrics" : "Organization Metrics";
|
|
155
|
+
lines.push(`# ${heading} (${scopeLabel}) \u2014 ${rangeLabel}${includeLabel}`);
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push(`- Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push(isMulti ? `## Totals (sum across orgs)` : `## Totals`);
|
|
160
|
+
for (const [name, value] of totalsRows) {
|
|
161
|
+
lines.push(`- ${name}: ${value}`);
|
|
162
|
+
}
|
|
163
|
+
if (result.averages.windowDays !== null && perDayRows.length > 0) {
|
|
164
|
+
lines.push("");
|
|
165
|
+
lines.push(`## Averages per day (window = ${result.averages.windowDays} days)`);
|
|
166
|
+
for (const [name, value] of perDayRows) {
|
|
167
|
+
lines.push(`- ${name}: ${value}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (perOrgPerDayRows.length > 0) {
|
|
171
|
+
lines.push("");
|
|
172
|
+
lines.push(`## Averages per org per day`);
|
|
173
|
+
for (const [name, value] of perOrgPerDayRows) {
|
|
174
|
+
lines.push(`- ${name}: ${value}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (isMulti && result.perOrg) {
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push(`## Per-org breakdown`);
|
|
180
|
+
for (const slug of result.scope.orgs) {
|
|
181
|
+
const entry = result.perOrg[slug];
|
|
182
|
+
if (!entry) continue;
|
|
183
|
+
const rows = populatedMetricRows(entry.metrics);
|
|
184
|
+
if (rows.length === 0) continue;
|
|
185
|
+
lines.push(`### ${slug}`);
|
|
186
|
+
for (const [name, value] of rows) {
|
|
187
|
+
lines.push(`- ${name}: ${value}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return lines.join("\n");
|
|
192
|
+
}
|
|
193
|
+
function buildCsvRows({
|
|
194
|
+
result,
|
|
195
|
+
range,
|
|
196
|
+
isMulti
|
|
197
|
+
}) {
|
|
198
|
+
const baseFields = (metrics) => ({
|
|
199
|
+
reports: metrics.reports,
|
|
200
|
+
newThreats: metrics.newThreats,
|
|
201
|
+
threatsWatchlisted: metrics.threatsWatchlisted,
|
|
202
|
+
takedownsFiled: metrics.takedownsFiled,
|
|
203
|
+
takedownsCompleted: metrics.takedownsCompleted,
|
|
204
|
+
domainThreats: metrics.domainThreats,
|
|
205
|
+
twitterThreats: metrics.twitterThreats,
|
|
206
|
+
telegramThreats: metrics.telegramThreats,
|
|
207
|
+
otherThreats: metrics.otherThreats
|
|
208
|
+
});
|
|
209
|
+
if (!isMulti) {
|
|
210
|
+
return [
|
|
211
|
+
{
|
|
212
|
+
scope: "single",
|
|
213
|
+
org: result.scope.orgs[0] ?? "",
|
|
214
|
+
rangeLabel: range.label,
|
|
215
|
+
startDate: range.startDate,
|
|
216
|
+
endDate: range.endDate,
|
|
217
|
+
windowDays: result.averages.windowDays,
|
|
218
|
+
...baseFields(result.metrics)
|
|
219
|
+
}
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
const rows = [];
|
|
223
|
+
rows.push({
|
|
224
|
+
scope: "totals",
|
|
225
|
+
org: "",
|
|
226
|
+
rangeLabel: range.label,
|
|
227
|
+
startDate: range.startDate,
|
|
228
|
+
endDate: range.endDate,
|
|
229
|
+
windowDays: result.averages.windowDays,
|
|
230
|
+
...baseFields(result.metrics)
|
|
231
|
+
});
|
|
232
|
+
if (result.perOrg) {
|
|
233
|
+
for (const slug of result.scope.orgs) {
|
|
234
|
+
const entry = result.perOrg[slug];
|
|
235
|
+
if (!entry) continue;
|
|
236
|
+
rows.push({
|
|
237
|
+
scope: "per-org",
|
|
238
|
+
org: slug,
|
|
239
|
+
rangeLabel: range.label,
|
|
240
|
+
startDate: range.startDate,
|
|
241
|
+
endDate: range.endDate,
|
|
242
|
+
windowDays: result.averages.windowDays,
|
|
243
|
+
...baseFields(entry.metrics)
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return rows;
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
runMetricsOrganization
|
|
251
|
+
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPresetDefinition,
|
|
3
3
|
runPreset
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FOTFWUOX.js";
|
|
5
5
|
import {
|
|
6
6
|
CliExitError,
|
|
7
7
|
ExitCode
|
|
8
8
|
} from "./chunk-E2LAMILJ.js";
|
|
9
9
|
import "./chunk-VFT3TD3E.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-UZDSNWGL.js";
|
|
11
11
|
import "./chunk-EGWK6SRQ.js";
|
|
12
12
|
import "./chunk-TFCNKBRC.js";
|
|
13
13
|
import "./chunk-U73SABXK.js";
|
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": "0.
|
|
5
|
+
"version": "0.15.0",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"homepage": "https://chainpatrol.com/docs/cli",
|
|
8
8
|
"keywords": [
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveDateRange
|
|
3
|
-
} from "./chunk-S37VFIZY.js";
|
|
4
|
-
import {
|
|
5
|
-
printOutput,
|
|
6
|
-
toCsvRows
|
|
7
|
-
} from "./chunk-VFT3TD3E.js";
|
|
8
|
-
import {
|
|
9
|
-
ORGANIZATION_METRICS_INCLUDE_FIELDS,
|
|
10
|
-
createApiClient
|
|
11
|
-
} from "./chunk-YZEMTNCX.js";
|
|
12
|
-
import "./chunk-EGWK6SRQ.js";
|
|
13
|
-
import "./chunk-TFCNKBRC.js";
|
|
14
|
-
import "./chunk-U73SABXK.js";
|
|
15
|
-
|
|
16
|
-
// src/commands/metrics/organization.ts
|
|
17
|
-
var INCLUDE_VALUES = new Set(ORGANIZATION_METRICS_INCLUDE_FIELDS);
|
|
18
|
-
function parseIncludeList(raw) {
|
|
19
|
-
if (!raw || raw.length === 0) return void 0;
|
|
20
|
-
const fields = [];
|
|
21
|
-
for (const value of raw) {
|
|
22
|
-
if (!INCLUDE_VALUES.has(value)) {
|
|
23
|
-
throw new Error(
|
|
24
|
-
`Unknown --include value: '${value}'. Allowed: ${ORGANIZATION_METRICS_INCLUDE_FIELDS.join(", ")}`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
fields.push(value);
|
|
28
|
-
}
|
|
29
|
-
return fields;
|
|
30
|
-
}
|
|
31
|
-
async function runMetricsOrganization(options) {
|
|
32
|
-
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
33
|
-
const client = options.apiClient ?? createApiClient();
|
|
34
|
-
const range = resolveDateRange({
|
|
35
|
-
from: options.from,
|
|
36
|
-
to: options.to,
|
|
37
|
-
thisWeek: options.thisWeek
|
|
38
|
-
});
|
|
39
|
-
const include = parseIncludeList(options.include);
|
|
40
|
-
const input = {
|
|
41
|
-
organizationSlug: options.org,
|
|
42
|
-
brandSlug: options.brandSlug,
|
|
43
|
-
startDate: range.startDate,
|
|
44
|
-
endDate: range.endDate,
|
|
45
|
-
include
|
|
46
|
-
};
|
|
47
|
-
const result = await client.getOrganizationMetrics(input);
|
|
48
|
-
const label = options.org ?? "your organization";
|
|
49
|
-
const includeLabel = include ? ` (include: ${include.join(", ")})` : "";
|
|
50
|
-
const metricLines = [
|
|
51
|
-
["Reports", result.metrics.reports],
|
|
52
|
-
["New threats", result.metrics.newThreats],
|
|
53
|
-
["Watchlisted threats", result.metrics.threatsWatchlisted],
|
|
54
|
-
["Takedowns filed", result.metrics.takedownsFiled],
|
|
55
|
-
["Takedowns completed", result.metrics.takedownsCompleted],
|
|
56
|
-
["Domain threats", result.metrics.domainThreats],
|
|
57
|
-
["Twitter threats", result.metrics.twitterThreats],
|
|
58
|
-
["Telegram threats", result.metrics.telegramThreats],
|
|
59
|
-
["Other threats", result.metrics.otherThreats]
|
|
60
|
-
];
|
|
61
|
-
const populatedMetricLines = metricLines.filter(([, value]) => value !== null);
|
|
62
|
-
printOutput({
|
|
63
|
-
outputFormat,
|
|
64
|
-
json: { ...result, range, include: include ?? null },
|
|
65
|
-
markdown: [
|
|
66
|
-
`# Organization Metrics (${label}) \u2014 ${range.label}${includeLabel}`,
|
|
67
|
-
"",
|
|
68
|
-
`- Range: ${range.startDate} \u2192 ${range.endDate}`,
|
|
69
|
-
...populatedMetricLines.map(([name, value]) => `- ${name}: ${value}`)
|
|
70
|
-
].join("\n"),
|
|
71
|
-
csv: toCsvRows([
|
|
72
|
-
{
|
|
73
|
-
rangeLabel: range.label,
|
|
74
|
-
startDate: range.startDate,
|
|
75
|
-
endDate: range.endDate,
|
|
76
|
-
reports: result.metrics.reports,
|
|
77
|
-
newThreats: result.metrics.newThreats,
|
|
78
|
-
threatsWatchlisted: result.metrics.threatsWatchlisted,
|
|
79
|
-
takedownsFiled: result.metrics.takedownsFiled,
|
|
80
|
-
takedownsCompleted: result.metrics.takedownsCompleted,
|
|
81
|
-
domainThreats: result.metrics.domainThreats,
|
|
82
|
-
twitterThreats: result.metrics.twitterThreats,
|
|
83
|
-
telegramThreats: result.metrics.telegramThreats,
|
|
84
|
-
otherThreats: result.metrics.otherThreats
|
|
85
|
-
}
|
|
86
|
-
]),
|
|
87
|
-
human: () => {
|
|
88
|
-
console.log(`Organization metrics for ${label} \u2014 ${range.label}${includeLabel}`);
|
|
89
|
-
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
90
|
-
for (const [name, value] of populatedMetricLines) {
|
|
91
|
-
console.log(`${name}: ${value}`);
|
|
92
|
-
}
|
|
93
|
-
if (result.blockedByDay && result.blockedByDay.length > 0) {
|
|
94
|
-
console.log("");
|
|
95
|
-
console.log(`Blocked by day (${result.blockedByDay.length} entries):`);
|
|
96
|
-
for (const entry of result.blockedByDay) {
|
|
97
|
-
console.log(` ${entry.date}: ${entry.count}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
export {
|
|
104
|
-
runMetricsOrganization
|
|
105
|
-
};
|