@chainpatrol/cli 0.13.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 +63 -0
- package/dist/{breakdown-B6QUGYI6.js → breakdown-27PMFEWQ.js} +1 -1
- package/dist/{check-P5JJJNEX.js → check-M5U2CSWK.js} +1 -1
- package/dist/{chunk-DJVB73FL.js → chunk-FOTFWUOX.js} +1 -1
- package/dist/{chunk-BQDMAEQU.js → chunk-QGKR7EPH.js} +102 -18
- package/dist/{chunk-OSRFCVBT.js → chunk-UZDSNWGL.js} +27 -2
- package/dist/cli.js +49 -40
- package/dist/{configs-update-3ZVXGADV.js → configs-update-YMPRYXKZ.js} +1 -1
- package/dist/{create-DFH2G7RH.js → create-DQYNYD6G.js} +1 -1
- package/dist/{drift-T74V2C3J.js → drift-T4AF6Y3X.js} +1 -1
- package/dist/{found-NXAWEIEK.js → found-XEGMO6YY.js} +1 -1
- package/dist/{healthcheck-HZYEIIOR.js → healthcheck-FORGIJWP.js} +1 -1
- package/dist/{list-EIVPGMWJ.js → list-63HPS2RE.js} +1 -1
- package/dist/{list-WX5G3IOF.js → list-BJMP3QEV.js} +2 -2
- package/dist/{list-YRJPN4PI.js → list-COJ5BLQV.js} +1 -1
- package/dist/{list-WJQT2XLE.js → list-DVPEMSVH.js} +1 -1
- package/dist/{list-ZFPML3ZR.js → list-N4TTNR6N.js} +1 -1
- package/dist/{list-23QQYAV5.js → list-OWJLAWMP.js} +1 -1
- package/dist/{list-QUFY4IP7.js → list-RMD4NKVB.js} +1 -1
- package/dist/{list-PBAXQSL5.js → list-VOWXWXHY.js} +1 -1
- package/dist/{list-I7SBGA52.js → list-WPXNCBT3.js} +1 -1
- package/dist/{list-YCT6DYDI.js → list-XHBQMP6P.js} +1 -1
- package/dist/{list-IYJBKE2F.js → list-ZTMCEG56.js} +1 -1
- package/dist/{list-json-T2Z7NV7G.js → list-json-X6QHIRE5.js} +1 -1
- package/dist/organization-HD6LRAZP.js +251 -0
- package/dist/{run-BHEXWBAS.js → run-BHAH7DUC.js} +2 -2
- package/dist/{run-CJGN6TX4.js → run-G5J77GZD.js} +1 -1
- package/dist/{run-MSO6IXEV.js → run-V7QJBNLF.js} +1 -1
- package/dist/{search-WPSKQGUS.js → search-ME6KFCEV.js} +1 -1
- package/dist/{setup-skill-TPMTULHJ.js → setup-skill-ENJSVGLR.js} +1 -1
- package/dist/{snapshot-ZWAHEG64.js → snapshot-B6TQCFQ3.js} +1 -1
- package/dist/{summary-CV657J5V.js → summary-NYHIQ5DP.js} +1 -1
- package/dist/{validate-SM5DD7I2.js → validate-I3YHZDZR.js} +1 -1
- package/dist/{whoami-DH5HVLFN.js → whoami-6KU4LOJS.js} +1 -1
- package/package.json +1 -1
- package/dist/organization-GS5LFIWO.js +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
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
|
+
|
|
37
|
+
## 0.14.0
|
|
38
|
+
|
|
39
|
+
### Minor Changes
|
|
40
|
+
|
|
41
|
+
- 339729c: Add an `--include` filter to `chainpatrol metrics organization` (backed
|
|
42
|
+
by a new optional `include` field on `GET /organization/metrics`) so
|
|
43
|
+
callers can ask for just the metrics they care about instead of paying
|
|
44
|
+
for the full ~11-aggregate fan-out.
|
|
45
|
+
|
|
46
|
+
`--include` (or `include: [...]` in the JSON body) accepts a comma list
|
|
47
|
+
of any of: `reports`, `newThreats`, `threatsWatchlisted`,
|
|
48
|
+
`takedownsFiled`, `takedownsCompleted`, `domainThreats`,
|
|
49
|
+
`twitterThreats`, `telegramThreats`, `otherThreats`, `blockedByType`,
|
|
50
|
+
`blockedByDay`. The server runs only those Prisma aggregates;
|
|
51
|
+
unrequested fields come back as `null`. Omitting `--include` (or
|
|
52
|
+
passing an empty list) preserves the existing "compute everything"
|
|
53
|
+
behavior.
|
|
54
|
+
|
|
55
|
+
This is the targeted follow-up to the 3-month-default change for the
|
|
56
|
+
same 503 issue: agents that only need takedown counts can now skip the
|
|
57
|
+
nine other counts entirely, keeping each call well inside the platform
|
|
58
|
+
function cap and cheap enough to run repeatedly per org.
|
|
59
|
+
|
|
60
|
+
The response schema's metric fields and `blockedByType`/`blockedByDay`
|
|
61
|
+
arrays are now nullable to express "not included" — TypeScript callers
|
|
62
|
+
of the CLI's `OrganizationMetricsResult` that previously read
|
|
63
|
+
`result.metrics.reports` as `number` will need `?? 0` (or to opt in to
|
|
64
|
+
`--include` and check for `null` explicitly).
|
|
65
|
+
|
|
3
66
|
## 0.13.0
|
|
4
67
|
|
|
5
68
|
### Minor Changes
|
|
@@ -700,24 +700,108 @@ the full customer-facing dashboard slice (reports, new threats,
|
|
|
700
700
|
watchlisted, takedowns filed/completed, plus per-type and per-day
|
|
701
701
|
breakdowns).
|
|
702
702
|
|
|
703
|
-
####
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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
|
+
#### \`--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.
|
|
721
805
|
|
|
722
806
|
### \`presets list | run\` \u2014 Packaged workflows for common jobs
|
|
723
807
|
|
|
@@ -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
|
}
|
|
@@ -256,9 +278,11 @@ function createApiClient(options) {
|
|
|
256
278
|
getOrganizationMetrics(input) {
|
|
257
279
|
return requestGet("/organization/metrics", {
|
|
258
280
|
organizationSlug: input.organizationSlug,
|
|
281
|
+
slugs: input.slugs,
|
|
259
282
|
brandSlug: input.brandSlug,
|
|
260
283
|
startDate: parseIsoDateString(input.startDate),
|
|
261
|
-
endDate: parseIsoDateString(input.endDate)
|
|
284
|
+
endDate: parseIsoDateString(input.endDate),
|
|
285
|
+
include: input.include
|
|
262
286
|
});
|
|
263
287
|
},
|
|
264
288
|
listHealthchecks() {
|
|
@@ -378,5 +402,6 @@ export {
|
|
|
378
402
|
TAKEDOWN_STATUSES,
|
|
379
403
|
LIVENESS_STATUSES,
|
|
380
404
|
TAKEDOWN_SORT_KEYS,
|
|
405
|
+
ORGANIZATION_METRICS_INCLUDE_FIELDS,
|
|
381
406
|
createApiClient
|
|
382
407
|
};
|
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,19 +263,23 @@ 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]",
|
|
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
|
-
"--this-week Use the current ISO week (overrides default)"
|
|
274
|
+
"--this-week Use the current ISO week (overrides default)",
|
|
275
|
+
"--include <list> Comma list of fields to compute: reports,newThreats,threatsWatchlisted,takedownsFiled,takedownsCompleted,domainThreats,twitterThreats,telegramThreats,otherThreats,blockedByType,blockedByDay (omit for all)"
|
|
274
276
|
],
|
|
275
277
|
examples: [
|
|
276
|
-
"chainpatrol metrics organization --org acme
|
|
278
|
+
"chainpatrol metrics organization --org acme # defaults to last 3 months, all metrics",
|
|
279
|
+
"chainpatrol metrics organization --org acme --include takedownsFiled,takedownsCompleted",
|
|
277
280
|
"chainpatrol metrics organization --org acme --from 2026-01-01 --to 2026-02-01",
|
|
278
|
-
"
|
|
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",
|
|
282
|
+
"CHAINPATROL_API_KEY=... chainpatrol metrics organization --json --include reports"
|
|
279
283
|
]
|
|
280
284
|
},
|
|
281
285
|
"metrics summary": {
|
|
@@ -858,6 +862,8 @@ ${getTopLevelHelp()}
|
|
|
858
862
|
to: { type: "string" },
|
|
859
863
|
thisWeek: { type: "boolean", default: false },
|
|
860
864
|
by: { type: "string" },
|
|
865
|
+
include: { type: "string" },
|
|
866
|
+
slugs: { type: "string" },
|
|
861
867
|
brand: { type: "string" },
|
|
862
868
|
enable: { type: "boolean", default: false },
|
|
863
869
|
disable: { type: "boolean", default: false },
|
|
@@ -1031,12 +1037,12 @@ function parseAttachmentUrls() {
|
|
|
1031
1037
|
}
|
|
1032
1038
|
async function handleConfigsList(org) {
|
|
1033
1039
|
if (jsonMode) {
|
|
1034
|
-
const { listConfigsJson } = await import("./list-json-
|
|
1040
|
+
const { listConfigsJson } = await import("./list-json-X6QHIRE5.js");
|
|
1035
1041
|
await listConfigsJson({ org });
|
|
1036
1042
|
return;
|
|
1037
1043
|
}
|
|
1038
1044
|
const { render } = await import("ink");
|
|
1039
|
-
const { default: ConfigsList } = await import("./list-
|
|
1045
|
+
const { default: ConfigsList } = await import("./list-ZTMCEG56.js");
|
|
1040
1046
|
const { default: React } = await import("react");
|
|
1041
1047
|
render(React.createElement(ConfigsList, { org }));
|
|
1042
1048
|
}
|
|
@@ -1136,7 +1142,7 @@ async function main() {
|
|
|
1136
1142
|
const positionals = cli.input.slice(2);
|
|
1137
1143
|
const flagAssets = parseAssetInputs();
|
|
1138
1144
|
const contents = [...positionals, ...flagAssets];
|
|
1139
|
-
const { runAssetCheck } = await import("./check-
|
|
1145
|
+
const { runAssetCheck } = await import("./check-M5U2CSWK.js");
|
|
1140
1146
|
await runAssetCheck({
|
|
1141
1147
|
contents,
|
|
1142
1148
|
json: jsonMode,
|
|
@@ -1146,7 +1152,7 @@ async function main() {
|
|
|
1146
1152
|
break;
|
|
1147
1153
|
}
|
|
1148
1154
|
if (subcommand === "list") {
|
|
1149
|
-
const { runAssetList } = await import("./list-
|
|
1155
|
+
const { runAssetList } = await import("./list-VOWXWXHY.js");
|
|
1150
1156
|
await runAssetList({
|
|
1151
1157
|
type: cli.flags.type ?? cli.flags.assetType,
|
|
1152
1158
|
status: cli.flags.assetStatus ?? cli.flags.status,
|
|
@@ -1167,7 +1173,7 @@ async function main() {
|
|
|
1167
1173
|
case "detections": {
|
|
1168
1174
|
const org = await resolveOrg();
|
|
1169
1175
|
if (subcommand === "healthcheck") {
|
|
1170
|
-
const { runDetectionsHealthcheck } = await import("./healthcheck-
|
|
1176
|
+
const { runDetectionsHealthcheck } = await import("./healthcheck-FORGIJWP.js");
|
|
1171
1177
|
await runDetectionsHealthcheck({
|
|
1172
1178
|
org,
|
|
1173
1179
|
source: cli.flags.source,
|
|
@@ -1182,7 +1188,7 @@ async function main() {
|
|
|
1182
1188
|
break;
|
|
1183
1189
|
}
|
|
1184
1190
|
if (subcommand === "validate") {
|
|
1185
|
-
const { runDetectionsValidate } = await import("./validate-
|
|
1191
|
+
const { runDetectionsValidate } = await import("./validate-I3YHZDZR.js");
|
|
1186
1192
|
await runDetectionsValidate({
|
|
1187
1193
|
org,
|
|
1188
1194
|
source: cli.flags.source,
|
|
@@ -1197,7 +1203,7 @@ async function main() {
|
|
|
1197
1203
|
break;
|
|
1198
1204
|
}
|
|
1199
1205
|
if (subcommand === "drift") {
|
|
1200
|
-
const { runDetectionsDrift } = await import("./drift-
|
|
1206
|
+
const { runDetectionsDrift } = await import("./drift-T4AF6Y3X.js");
|
|
1201
1207
|
await runDetectionsDrift({
|
|
1202
1208
|
org,
|
|
1203
1209
|
source: cli.flags.source,
|
|
@@ -1211,7 +1217,7 @@ async function main() {
|
|
|
1211
1217
|
break;
|
|
1212
1218
|
}
|
|
1213
1219
|
if (subcommand === "run") {
|
|
1214
|
-
const { runDetectionsRun } = await import("./run-
|
|
1220
|
+
const { runDetectionsRun } = await import("./run-V7QJBNLF.js");
|
|
1215
1221
|
await runDetectionsRun({
|
|
1216
1222
|
org,
|
|
1217
1223
|
configId: cli.flags.configId,
|
|
@@ -1225,7 +1231,7 @@ async function main() {
|
|
|
1225
1231
|
break;
|
|
1226
1232
|
}
|
|
1227
1233
|
if (subcommand === "list") {
|
|
1228
|
-
const { runDetectionsList } = await import("./list-
|
|
1234
|
+
const { runDetectionsList } = await import("./list-RMD4NKVB.js");
|
|
1229
1235
|
await runDetectionsList({
|
|
1230
1236
|
org,
|
|
1231
1237
|
cursor: cli.flags.cursor,
|
|
@@ -1254,7 +1260,7 @@ async function main() {
|
|
|
1254
1260
|
break;
|
|
1255
1261
|
}
|
|
1256
1262
|
if (action === "run") {
|
|
1257
|
-
const { runDetectionsRun } = await import("./run-
|
|
1263
|
+
const { runDetectionsRun } = await import("./run-V7QJBNLF.js");
|
|
1258
1264
|
await runDetectionsRun({
|
|
1259
1265
|
org,
|
|
1260
1266
|
configId: cli.flags.configId,
|
|
@@ -1272,7 +1278,7 @@ async function main() {
|
|
|
1272
1278
|
throw new Error("detections configs update requires --config-id");
|
|
1273
1279
|
}
|
|
1274
1280
|
const configPatch = getConfigPatchFromSetFlags();
|
|
1275
|
-
const { runDetectionsConfigsUpdate } = await import("./configs-update-
|
|
1281
|
+
const { runDetectionsConfigsUpdate } = await import("./configs-update-YMPRYXKZ.js");
|
|
1276
1282
|
await runDetectionsConfigsUpdate({
|
|
1277
1283
|
org,
|
|
1278
1284
|
configId: cli.flags.configId,
|
|
@@ -1305,14 +1311,17 @@ async function main() {
|
|
|
1305
1311
|
}
|
|
1306
1312
|
case "metrics": {
|
|
1307
1313
|
if (subcommand === "organization" || subcommand === "org") {
|
|
1308
|
-
const
|
|
1309
|
-
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");
|
|
1310
1317
|
await runMetricsOrganization({
|
|
1311
1318
|
org: org2,
|
|
1319
|
+
slugs,
|
|
1312
1320
|
brandSlug: cli.flags.brandSlug,
|
|
1313
1321
|
from: cli.flags.from,
|
|
1314
1322
|
to: cli.flags.to,
|
|
1315
1323
|
thisWeek: cli.flags.thisWeek,
|
|
1324
|
+
include: parseCsvList(cli.flags.include),
|
|
1316
1325
|
json: jsonMode,
|
|
1317
1326
|
outputFormat: cliContext.outputFormat
|
|
1318
1327
|
});
|
|
@@ -1320,7 +1329,7 @@ async function main() {
|
|
|
1320
1329
|
}
|
|
1321
1330
|
const org = await resolveOrg();
|
|
1322
1331
|
if (subcommand === "summary") {
|
|
1323
|
-
const { runMetricsSummary } = await import("./summary-
|
|
1332
|
+
const { runMetricsSummary } = await import("./summary-NYHIQ5DP.js");
|
|
1324
1333
|
await runMetricsSummary({
|
|
1325
1334
|
org,
|
|
1326
1335
|
from: cli.flags.from,
|
|
@@ -1333,7 +1342,7 @@ async function main() {
|
|
|
1333
1342
|
break;
|
|
1334
1343
|
}
|
|
1335
1344
|
if (subcommand === "found") {
|
|
1336
|
-
const { runMetricsFound } = await import("./found-
|
|
1345
|
+
const { runMetricsFound } = await import("./found-XEGMO6YY.js");
|
|
1337
1346
|
await runMetricsFound({
|
|
1338
1347
|
org,
|
|
1339
1348
|
from: cli.flags.from,
|
|
@@ -1350,7 +1359,7 @@ async function main() {
|
|
|
1350
1359
|
if (!by || !["day", "type", "brand"].includes(by)) {
|
|
1351
1360
|
throw new Error("metrics breakdown requires --by <day|type|brand>");
|
|
1352
1361
|
}
|
|
1353
|
-
const { runMetricsBreakdown } = await import("./breakdown-
|
|
1362
|
+
const { runMetricsBreakdown } = await import("./breakdown-27PMFEWQ.js");
|
|
1354
1363
|
await runMetricsBreakdown({
|
|
1355
1364
|
org,
|
|
1356
1365
|
by,
|
|
@@ -1371,7 +1380,7 @@ async function main() {
|
|
|
1371
1380
|
case "reports": {
|
|
1372
1381
|
if (subcommand === "list") {
|
|
1373
1382
|
const org = await resolveOrg();
|
|
1374
|
-
const { runReportsList } = await import("./list-
|
|
1383
|
+
const { runReportsList } = await import("./list-N4TTNR6N.js");
|
|
1375
1384
|
await runReportsList({
|
|
1376
1385
|
org,
|
|
1377
1386
|
limit: cli.flags.limit,
|
|
@@ -1399,7 +1408,7 @@ async function main() {
|
|
|
1399
1408
|
}
|
|
1400
1409
|
if (subcommand === "create") {
|
|
1401
1410
|
const org = await tryResolveOrg();
|
|
1402
|
-
const { runReportsCreate } = await import("./create-
|
|
1411
|
+
const { runReportsCreate } = await import("./create-DQYNYD6G.js");
|
|
1403
1412
|
await runReportsCreate({
|
|
1404
1413
|
org,
|
|
1405
1414
|
title: cli.flags.title,
|
|
@@ -1420,7 +1429,7 @@ async function main() {
|
|
|
1420
1429
|
const positionals = cli.input.slice(2);
|
|
1421
1430
|
const flagAssets = parseAssetInputs();
|
|
1422
1431
|
const assetContents = [...positionals, ...flagAssets];
|
|
1423
|
-
const { runReportsSearch } = await import("./search-
|
|
1432
|
+
const { runReportsSearch } = await import("./search-ME6KFCEV.js");
|
|
1424
1433
|
await runReportsSearch({
|
|
1425
1434
|
assetContents,
|
|
1426
1435
|
reportedByCustomer: cli.flags.reportedByCustomer,
|
|
@@ -1436,7 +1445,7 @@ async function main() {
|
|
|
1436
1445
|
}
|
|
1437
1446
|
case "threats": {
|
|
1438
1447
|
if (subcommand === "list") {
|
|
1439
|
-
const { runThreatsList } = await import("./list-
|
|
1448
|
+
const { runThreatsList } = await import("./list-XHBQMP6P.js");
|
|
1440
1449
|
await runThreatsList({
|
|
1441
1450
|
query: cli.flags.query,
|
|
1442
1451
|
from: cli.flags.from,
|
|
@@ -1459,7 +1468,7 @@ async function main() {
|
|
|
1459
1468
|
case "takedowns": {
|
|
1460
1469
|
if (subcommand === "list") {
|
|
1461
1470
|
const org = await tryResolveOrg();
|
|
1462
|
-
const { runTakedownsList } = await import("./list-
|
|
1471
|
+
const { runTakedownsList } = await import("./list-DVPEMSVH.js");
|
|
1463
1472
|
await runTakedownsList({
|
|
1464
1473
|
org,
|
|
1465
1474
|
query: cli.flags.query,
|
|
@@ -1490,7 +1499,7 @@ async function main() {
|
|
|
1490
1499
|
}
|
|
1491
1500
|
case "queues": {
|
|
1492
1501
|
if (subcommand === "snapshot") {
|
|
1493
|
-
const { runQueuesSnapshot } = await import("./snapshot-
|
|
1502
|
+
const { runQueuesSnapshot } = await import("./snapshot-B6TQCFQ3.js");
|
|
1494
1503
|
await runQueuesSnapshot({
|
|
1495
1504
|
org: cli.flags.org,
|
|
1496
1505
|
all: cli.flags.all,
|
|
@@ -1508,7 +1517,7 @@ async function main() {
|
|
|
1508
1517
|
}
|
|
1509
1518
|
case "orgs": {
|
|
1510
1519
|
if (subcommand === "list") {
|
|
1511
|
-
const { runOrgsList } = await import("./list-
|
|
1520
|
+
const { runOrgsList } = await import("./list-COJ5BLQV.js");
|
|
1512
1521
|
await runOrgsList({
|
|
1513
1522
|
query: cli.flags.query,
|
|
1514
1523
|
subscriptionStatus: cli.flags.subscriptionStatus,
|
|
@@ -1523,7 +1532,7 @@ async function main() {
|
|
|
1523
1532
|
}
|
|
1524
1533
|
if (subcommand === "assets") {
|
|
1525
1534
|
if (action === "list") {
|
|
1526
|
-
const { runOrgsAssetsList } = await import("./list-
|
|
1535
|
+
const { runOrgsAssetsList } = await import("./list-63HPS2RE.js");
|
|
1527
1536
|
await runOrgsAssetsList({
|
|
1528
1537
|
type: cli.flags.type ?? cli.flags.assetType,
|
|
1529
1538
|
groupId: cli.flags.groupId,
|
|
@@ -1539,7 +1548,7 @@ async function main() {
|
|
|
1539
1548
|
}
|
|
1540
1549
|
if (subcommand === "asset-groups") {
|
|
1541
1550
|
if (action === "list") {
|
|
1542
|
-
const { runOrgsAssetGroupsList } = await import("./list-
|
|
1551
|
+
const { runOrgsAssetGroupsList } = await import("./list-WPXNCBT3.js");
|
|
1543
1552
|
await runOrgsAssetGroupsList({
|
|
1544
1553
|
json: jsonMode,
|
|
1545
1554
|
outputFormat: cliContext.outputFormat
|
|
@@ -1554,7 +1563,7 @@ async function main() {
|
|
|
1554
1563
|
);
|
|
1555
1564
|
}
|
|
1556
1565
|
case "whoami": {
|
|
1557
|
-
const { runWhoami } = await import("./whoami-
|
|
1566
|
+
const { runWhoami } = await import("./whoami-6KU4LOJS.js");
|
|
1558
1567
|
await runWhoami({
|
|
1559
1568
|
json: jsonMode,
|
|
1560
1569
|
outputFormat: cliContext.outputFormat
|
|
@@ -1563,7 +1572,7 @@ async function main() {
|
|
|
1563
1572
|
}
|
|
1564
1573
|
case "healthchecks": {
|
|
1565
1574
|
if (subcommand === "list") {
|
|
1566
|
-
const { runHealthchecksList } = await import("./list-
|
|
1575
|
+
const { runHealthchecksList } = await import("./list-OWJLAWMP.js");
|
|
1567
1576
|
await runHealthchecksList({
|
|
1568
1577
|
json: jsonMode,
|
|
1569
1578
|
outputFormat: cliContext.outputFormat
|
|
@@ -1577,7 +1586,7 @@ async function main() {
|
|
|
1577
1586
|
thresholds.minResults = cli.flags.minResults;
|
|
1578
1587
|
if (cli.flags.lookbackHours !== void 0)
|
|
1579
1588
|
thresholds.lookbackHours = cli.flags.lookbackHours;
|
|
1580
|
-
const { runHealthchecksRun } = await import("./run-
|
|
1589
|
+
const { runHealthchecksRun } = await import("./run-G5J77GZD.js");
|
|
1581
1590
|
await runHealthchecksRun({
|
|
1582
1591
|
org,
|
|
1583
1592
|
id: action,
|
|
@@ -1595,7 +1604,7 @@ async function main() {
|
|
|
1595
1604
|
}
|
|
1596
1605
|
case "presets": {
|
|
1597
1606
|
if (subcommand === "list") {
|
|
1598
|
-
const { runPresetsList } = await import("./list-
|
|
1607
|
+
const { runPresetsList } = await import("./list-BJMP3QEV.js");
|
|
1599
1608
|
await runPresetsList({ outputFormat: cliContext.outputFormat });
|
|
1600
1609
|
break;
|
|
1601
1610
|
}
|
|
@@ -1606,7 +1615,7 @@ async function main() {
|
|
|
1606
1615
|
);
|
|
1607
1616
|
}
|
|
1608
1617
|
const org = await resolveOrg();
|
|
1609
|
-
const { runPresetsRun } = await import("./run-
|
|
1618
|
+
const { runPresetsRun } = await import("./run-BHAH7DUC.js");
|
|
1610
1619
|
await runPresetsRun({
|
|
1611
1620
|
presetId: action,
|
|
1612
1621
|
org,
|
|
@@ -1623,12 +1632,12 @@ async function main() {
|
|
|
1623
1632
|
case "setup":
|
|
1624
1633
|
case "install":
|
|
1625
1634
|
case "i": {
|
|
1626
|
-
const { setupSkill } = await import("./setup-skill-
|
|
1635
|
+
const { setupSkill } = await import("./setup-skill-ENJSVGLR.js");
|
|
1627
1636
|
setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
|
|
1628
1637
|
break;
|
|
1629
1638
|
}
|
|
1630
1639
|
case "uninstall": {
|
|
1631
|
-
const { uninstallSkill } = await import("./setup-skill-
|
|
1640
|
+
const { uninstallSkill } = await import("./setup-skill-ENJSVGLR.js");
|
|
1632
1641
|
uninstallSkill({ json: jsonMode });
|
|
1633
1642
|
break;
|
|
1634
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,88 +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
|
-
createApiClient
|
|
10
|
-
} from "./chunk-OSRFCVBT.js";
|
|
11
|
-
import "./chunk-EGWK6SRQ.js";
|
|
12
|
-
import "./chunk-TFCNKBRC.js";
|
|
13
|
-
import "./chunk-U73SABXK.js";
|
|
14
|
-
|
|
15
|
-
// src/commands/metrics/organization.ts
|
|
16
|
-
async function runMetricsOrganization(options) {
|
|
17
|
-
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
18
|
-
const client = options.apiClient ?? createApiClient();
|
|
19
|
-
const range = resolveDateRange({
|
|
20
|
-
from: options.from,
|
|
21
|
-
to: options.to,
|
|
22
|
-
thisWeek: options.thisWeek
|
|
23
|
-
});
|
|
24
|
-
const input = {
|
|
25
|
-
organizationSlug: options.org,
|
|
26
|
-
brandSlug: options.brandSlug,
|
|
27
|
-
startDate: range.startDate,
|
|
28
|
-
endDate: range.endDate
|
|
29
|
-
};
|
|
30
|
-
const result = await client.getOrganizationMetrics(input);
|
|
31
|
-
const label = options.org ?? "your organization";
|
|
32
|
-
printOutput({
|
|
33
|
-
outputFormat,
|
|
34
|
-
json: { ...result, range },
|
|
35
|
-
markdown: [
|
|
36
|
-
`# Organization Metrics (${label}) \u2014 ${range.label}`,
|
|
37
|
-
"",
|
|
38
|
-
`- Range: ${range.startDate} \u2192 ${range.endDate}`,
|
|
39
|
-
`- Reports: ${result.metrics.reports}`,
|
|
40
|
-
`- New threats: ${result.metrics.newThreats}`,
|
|
41
|
-
`- Watchlisted threats: ${result.metrics.threatsWatchlisted}`,
|
|
42
|
-
`- Takedowns filed: ${result.metrics.takedownsFiled}`,
|
|
43
|
-
`- Takedowns completed: ${result.metrics.takedownsCompleted}`,
|
|
44
|
-
`- Domain threats: ${result.metrics.domainThreats}`,
|
|
45
|
-
`- Twitter threats: ${result.metrics.twitterThreats}`,
|
|
46
|
-
`- Telegram threats: ${result.metrics.telegramThreats}`,
|
|
47
|
-
`- Other threats: ${result.metrics.otherThreats}`
|
|
48
|
-
].join("\n"),
|
|
49
|
-
csv: toCsvRows([
|
|
50
|
-
{
|
|
51
|
-
rangeLabel: range.label,
|
|
52
|
-
startDate: range.startDate,
|
|
53
|
-
endDate: range.endDate,
|
|
54
|
-
reports: result.metrics.reports,
|
|
55
|
-
newThreats: result.metrics.newThreats,
|
|
56
|
-
threatsWatchlisted: result.metrics.threatsWatchlisted,
|
|
57
|
-
takedownsFiled: result.metrics.takedownsFiled,
|
|
58
|
-
takedownsCompleted: result.metrics.takedownsCompleted,
|
|
59
|
-
domainThreats: result.metrics.domainThreats,
|
|
60
|
-
twitterThreats: result.metrics.twitterThreats,
|
|
61
|
-
telegramThreats: result.metrics.telegramThreats,
|
|
62
|
-
otherThreats: result.metrics.otherThreats
|
|
63
|
-
}
|
|
64
|
-
]),
|
|
65
|
-
human: () => {
|
|
66
|
-
console.log(`Organization metrics for ${label} \u2014 ${range.label}`);
|
|
67
|
-
console.log(`Range: ${range.startDate} \u2192 ${range.endDate}`);
|
|
68
|
-
console.log(`Reports: ${result.metrics.reports}`);
|
|
69
|
-
console.log(`New threats: ${result.metrics.newThreats}`);
|
|
70
|
-
console.log(`Watchlisted threats: ${result.metrics.threatsWatchlisted}`);
|
|
71
|
-
console.log(`Takedowns filed: ${result.metrics.takedownsFiled}`);
|
|
72
|
-
console.log(`Takedowns completed: ${result.metrics.takedownsCompleted}`);
|
|
73
|
-
console.log(
|
|
74
|
-
`Threat breakdown: domains=${result.metrics.domainThreats}, twitter=${result.metrics.twitterThreats}, telegram=${result.metrics.telegramThreats}, other=${result.metrics.otherThreats}`
|
|
75
|
-
);
|
|
76
|
-
if (result.blockedByDay.length > 0) {
|
|
77
|
-
console.log("");
|
|
78
|
-
console.log(`Blocked by day (${result.blockedByDay.length} entries):`);
|
|
79
|
-
for (const entry of result.blockedByDay) {
|
|
80
|
-
console.log(` ${entry.date}: ${entry.count}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
export {
|
|
87
|
-
runMetricsOrganization
|
|
88
|
-
};
|