@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{breakdown-K5Q6TDKE.js → breakdown-27PMFEWQ.js} +1 -1
  3. package/dist/{check-NJH7BUIM.js → check-M5U2CSWK.js} +1 -1
  4. package/dist/{chunk-BIKTBRER.js → chunk-FOTFWUOX.js} +1 -1
  5. package/dist/{chunk-6X5BFFXA.js → chunk-QGKR7EPH.js} +75 -18
  6. package/dist/{chunk-YZEMTNCX.js → chunk-UZDSNWGL.js} +1 -0
  7. package/dist/cli.js +42 -37
  8. package/dist/{configs-update-ON67NMXA.js → configs-update-YMPRYXKZ.js} +1 -1
  9. package/dist/{create-W5S2ZPVC.js → create-DQYNYD6G.js} +1 -1
  10. package/dist/{drift-ZOLDE24A.js → drift-T4AF6Y3X.js} +1 -1
  11. package/dist/{found-M5MX4VXK.js → found-XEGMO6YY.js} +1 -1
  12. package/dist/{healthcheck-PFYP6PNW.js → healthcheck-FORGIJWP.js} +1 -1
  13. package/dist/{list-Z7RJWZH5.js → list-63HPS2RE.js} +1 -1
  14. package/dist/{list-LNNIEVB4.js → list-BJMP3QEV.js} +2 -2
  15. package/dist/{list-ISP5JL4M.js → list-COJ5BLQV.js} +1 -1
  16. package/dist/{list-TUMVDWIZ.js → list-DVPEMSVH.js} +1 -1
  17. package/dist/{list-FV5IYCG4.js → list-N4TTNR6N.js} +1 -1
  18. package/dist/{list-GKC7PEU5.js → list-OWJLAWMP.js} +1 -1
  19. package/dist/{list-AHWEE3YZ.js → list-RMD4NKVB.js} +1 -1
  20. package/dist/{list-5TMGSHFC.js → list-VOWXWXHY.js} +1 -1
  21. package/dist/{list-VP5IY2SZ.js → list-WPXNCBT3.js} +1 -1
  22. package/dist/{list-HGYBEYOV.js → list-XHBQMP6P.js} +1 -1
  23. package/dist/{list-B5KLEFHA.js → list-ZTMCEG56.js} +1 -1
  24. package/dist/{list-json-QC6X25LI.js → list-json-X6QHIRE5.js} +1 -1
  25. package/dist/organization-HD6LRAZP.js +251 -0
  26. package/dist/{run-KDNQIR5E.js → run-BHAH7DUC.js} +2 -2
  27. package/dist/{run-HRFJORDS.js → run-G5J77GZD.js} +1 -1
  28. package/dist/{run-VHFXOCGS.js → run-V7QJBNLF.js} +1 -1
  29. package/dist/{search-NOSQW75S.js → search-ME6KFCEV.js} +1 -1
  30. package/dist/{setup-skill-Q6PCFIDE.js → setup-skill-ENJSVGLR.js} +1 -1
  31. package/dist/{snapshot-55UV26NQ.js → snapshot-B6TQCFQ3.js} +1 -1
  32. package/dist/{summary-QUCLZYX6.js → summary-NYHIQ5DP.js} +1 -1
  33. package/dist/{validate-TPG47SYS.js → validate-I3YHZDZR.js} +1 -1
  34. package/dist/{whoami-Y3ZJGN4O.js → whoami-6KU4LOJS.js} +1 -1
  35. package/package.json +1 -1
  36. 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
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-YZEMTNCX.js";
10
+ } from "./chunk-UZDSNWGL.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import {
13
13
  DateTime
14
14
  } from "./chunk-TFCNKBRC.js";
@@ -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
- #### "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.
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-6X5BFFXA.js";
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 both API key auth (org derived from key) and session auth (pass --org). Defaults to the trailing 3 months if no date range is given. Use --include to skip aggregates the caller doesn't need \u2014 fields not included come back as null.",
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> Organization slug (omit when using an API key)",
270
- "--brand-slug <slug> Filter by brand slug (requires brands-metrics flag)",
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-QC6X25LI.js");
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-B5KLEFHA.js");
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-NJH7BUIM.js");
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-5TMGSHFC.js");
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-PFYP6PNW.js");
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-TPG47SYS.js");
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-ZOLDE24A.js");
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-VHFXOCGS.js");
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-AHWEE3YZ.js");
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-VHFXOCGS.js");
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-ON67NMXA.js");
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 org2 = await tryResolveOrg();
1312
- const { runMetricsOrganization } = await import("./organization-VSQQAGBS.js");
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-QUCLZYX6.js");
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-M5MX4VXK.js");
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-K5Q6TDKE.js");
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-FV5IYCG4.js");
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-W5S2ZPVC.js");
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-NOSQW75S.js");
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-HGYBEYOV.js");
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-TUMVDWIZ.js");
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-55UV26NQ.js");
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-ISP5JL4M.js");
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-Z7RJWZH5.js");
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-VP5IY2SZ.js");
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-Y3ZJGN4O.js");
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-GKC7PEU5.js");
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-HRFJORDS.js");
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-LNNIEVB4.js");
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-KDNQIR5E.js");
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-Q6PCFIDE.js");
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-Q6PCFIDE.js");
1640
+ const { uninstallSkill } = await import("./setup-skill-ENJSVGLR.js");
1636
1641
  uninstallSkill({ json: jsonMode });
1637
1642
  break;
1638
1643
  }
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-YZEMTNCX.js";
10
+ } from "./chunk-UZDSNWGL.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-YZEMTNCX.js";
10
+ } from "./chunk-UZDSNWGL.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  PRESETS
3
- } from "./chunk-BIKTBRER.js";
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-YZEMTNCX.js";
9
+ import "./chunk-UZDSNWGL.js";
10
10
  import "./chunk-EGWK6SRQ.js";
11
11
  import "./chunk-TFCNKBRC.js";
12
12
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -11,7 +11,7 @@ import {
11
11
  TAKEDOWN_SORT_KEYS,
12
12
  TAKEDOWN_STATUSES,
13
13
  createApiClient
14
- } from "./chunk-YZEMTNCX.js";
14
+ } from "./chunk-UZDSNWGL.js";
15
15
  import "./chunk-EGWK6SRQ.js";
16
16
  import "./chunk-TFCNKBRC.js";
17
17
  import "./chunk-U73SABXK.js";
@@ -10,7 +10,7 @@ import {
10
10
  REPORT_REVIEW_DECISIONS,
11
11
  REPORT_STATUSES,
12
12
  createApiClient
13
- } from "./chunk-YZEMTNCX.js";
13
+ } from "./chunk-UZDSNWGL.js";
14
14
  import "./chunk-EGWK6SRQ.js";
15
15
  import "./chunk-TFCNKBRC.js";
16
16
  import "./chunk-U73SABXK.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-YZEMTNCX.js";
7
+ } from "./chunk-UZDSNWGL.js";
8
8
  import "./chunk-EGWK6SRQ.js";
9
9
  import "./chunk-TFCNKBRC.js";
10
10
  import "./chunk-U73SABXK.js";
@@ -12,7 +12,7 @@ import {
12
12
  LIVENESS_FILTER_VALUES,
13
13
  WATCHLIST_FILTER_VALUES,
14
14
  createApiClient
15
- } from "./chunk-YZEMTNCX.js";
15
+ } from "./chunk-UZDSNWGL.js";
16
16
  import "./chunk-EGWK6SRQ.js";
17
17
  import "./chunk-TFCNKBRC.js";
18
18
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-YZEMTNCX.js";
7
+ } from "./chunk-UZDSNWGL.js";
8
8
  import "./chunk-EGWK6SRQ.js";
9
9
  import "./chunk-TFCNKBRC.js";
10
10
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-JCMWDZYY.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-YZEMTNCX.js";
7
+ } from "./chunk-UZDSNWGL.js";
8
8
  import {
9
9
  AuthCorruptedError,
10
10
  AuthExpiredError,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-YZEMTNCX.js";
3
+ } from "./chunk-UZDSNWGL.js";
4
4
  import "./chunk-EGWK6SRQ.js";
5
5
  import "./chunk-TFCNKBRC.js";
6
6
  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-BIKTBRER.js";
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-YZEMTNCX.js";
10
+ import "./chunk-UZDSNWGL.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -6,7 +6,7 @@ import {
6
6
  readInstalledSkillVersion,
7
7
  setupSkill,
8
8
  uninstallSkill
9
- } from "./chunk-6X5BFFXA.js";
9
+ } from "./chunk-QGKR7EPH.js";
10
10
  import "./chunk-NQT77IB7.js";
11
11
  export {
12
12
  getBundledSkillContent,
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-YZEMTNCX.js";
10
+ } from "./chunk-UZDSNWGL.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-YZEMTNCX.js";
11
+ } from "./chunk-UZDSNWGL.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-VFT3TD3E.js";
4
4
  import {
5
5
  createApiClient
6
- } from "./chunk-YZEMTNCX.js";
6
+ } from "./chunk-UZDSNWGL.js";
7
7
  import "./chunk-EGWK6SRQ.js";
8
8
  import "./chunk-TFCNKBRC.js";
9
9
  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.14.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
- };