@chainpatrol/cli 0.15.1 → 0.17.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 +67 -0
  2. package/dist/{breakdown-27PMFEWQ.js → breakdown-ETSF2BZ3.js} +1 -1
  3. package/dist/{check-M5U2CSWK.js → check-EMGFDXUO.js} +1 -1
  4. package/dist/{chunk-IPN6O3N4.js → chunk-75H4KTIQ.js} +96 -42
  5. package/dist/{chunk-UZDSNWGL.js → chunk-DPLUSURP.js} +9 -0
  6. package/dist/{chunk-FOTFWUOX.js → chunk-HGAQBGGL.js} +1 -1
  7. package/dist/cli.js +81 -49
  8. package/dist/{configs-update-YMPRYXKZ.js → configs-update-EKNLIRA6.js} +1 -1
  9. package/dist/{create-DQYNYD6G.js → create-M4PV5VVU.js} +1 -1
  10. package/dist/{drift-T4AF6Y3X.js → drift-RQKTGJWB.js} +1 -1
  11. package/dist/{found-XEGMO6YY.js → found-LVYBEOS6.js} +1 -1
  12. package/dist/get-BPTWCJ6H.js +75 -0
  13. package/dist/{healthcheck-FORGIJWP.js → healthcheck-J6BIZEG7.js} +1 -1
  14. package/dist/{list-VOWXWXHY.js → list-2ENAEZVP.js} +1 -1
  15. package/dist/{list-N4TTNR6N.js → list-44BQZHJD.js} +1 -1
  16. package/dist/{list-ZTMCEG56.js → list-AEM2KSO4.js} +1 -1
  17. package/dist/{list-OWJLAWMP.js → list-CNF2FGLN.js} +1 -1
  18. package/dist/{list-XHBQMP6P.js → list-E2JVJNHV.js} +1 -1
  19. package/dist/{list-BJMP3QEV.js → list-JCJHIJM4.js} +2 -2
  20. package/dist/{list-RMD4NKVB.js → list-LKNCICLT.js} +1 -1
  21. package/dist/{list-WPXNCBT3.js → list-O36VWTWR.js} +1 -1
  22. package/dist/{list-63HPS2RE.js → list-O62BZKQ5.js} +1 -1
  23. package/dist/{list-COJ5BLQV.js → list-OEJV4UNQ.js} +1 -1
  24. package/dist/{list-DVPEMSVH.js → list-S5Y6GW43.js} +1 -1
  25. package/dist/{list-json-X6QHIRE5.js → list-json-ZTS5GXNA.js} +1 -1
  26. package/dist/{organization-HD6LRAZP.js → organization-PFPBZXJZ.js} +35 -4
  27. package/dist/{run-V7QJBNLF.js → run-HS2EEOM6.js} +1 -1
  28. package/dist/{run-BHAH7DUC.js → run-LKPKTH3W.js} +2 -2
  29. package/dist/{run-G5J77GZD.js → run-R3RR6XQF.js} +1 -1
  30. package/dist/{search-ME6KFCEV.js → search-EBXEP7XC.js} +1 -1
  31. package/dist/{setup-skill-6SDQXFTR.js → setup-skill-RPVW2CCQ.js} +1 -1
  32. package/dist/{snapshot-B6TQCFQ3.js → snapshot-GNZMYTXT.js} +1 -1
  33. package/dist/{summary-NYHIQ5DP.js → summary-POVQC6B7.js} +1 -1
  34. package/dist/{validate-I3YHZDZR.js → validate-QE2F4PYY.js} +1 -1
  35. package/dist/{whoami-6KU4LOJS.js → whoami-24KUJUN4.js} +1 -1
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # @chainpatrol/cli
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 27b18af: Adds `chainpatrol orgs get <slug>` for fetching a single accessible
8
+ organization, backed by a new public `GET /user/orgs/{slug}` endpoint.
9
+ Returns the same per-org shape as one row of `orgs list` (subscription
10
+ status + per-service active/automated flags) and reuses the same access
11
+ gating: org-scoped API keys must match the slug, user sessions / user-scoped
12
+ API keys need an active membership unless the caller is staff. The bundled
13
+ Claude Code skill (`/chainpatrol`) is updated to document the new command.
14
+ Server-side procedure + validation live in the private `@chainpatrol/external-trpc`
15
+ and `@chainpatrol/validation` packages and ship together with this CLI
16
+ release, but are not listed in the frontmatter because they are private.
17
+
18
+ ## 0.16.0
19
+
20
+ ### Minor Changes
21
+
22
+ - c1af664: Add `--all-my-orgs` to `chainpatrol metrics organization` and raise
23
+ the `--slugs` cap from 50 → 500.
24
+
25
+ `--all-my-orgs` is the shortest path to answer "how many takedowns
26
+ across all clients in the last 7 days?" — no `orgs list` round-trip,
27
+ no client-side slug stitching. The server resolves the org set using
28
+ the same logic as `/user/orgs` (so an agent already familiar with
29
+ that filter shape can reuse it), runs the per-org metric fan-out
30
+ with bounded concurrency, and rolls up totals + per-day /
31
+ per-org-per-day averages + the per-org breakdown in one HTTP call.
32
+
33
+ Pair with `--subscription-status` and/or `--service-active` to
34
+ narrow to the org set the question is actually about — e.g.
35
+
36
+ ```bash
37
+ chainpatrol --json metrics organization \
38
+ --all-my-orgs \
39
+ --subscription-status ACTIVE \
40
+ --service-active takedowns \
41
+ --include takedownsCompleted \
42
+ --from 2026-05-12 --to 2026-05-19
43
+ ```
44
+
45
+ The `--slugs` form still works for cases where the caller wants a
46
+ specific named subset; both forms cap at 500 orgs per call (previously
47
+ 50). The cap remains a backstop — `--all-my-orgs` returns a 413 with
48
+ a clear "narrow with filters" hint if the resolved set exceeds it.
49
+
50
+ Auth:
51
+
52
+ - Org-scoped API keys are rejected for `--all-my-orgs` (they're pinned
53
+ to a single org by design — the default form already covers that
54
+ org).
55
+ - User API keys (inherit owning user's memberships) and Bearer
56
+ sessions can use it; the resolved org set matches what `orgs list`
57
+ would return for the same caller.
58
+ - Staff users still get every active org (subject to the filters).
59
+
60
+ Response now includes `scope.resolvedFrom` (`"api-key" |
61
+ "organizationSlug" | "slugs" | "allMyOrgs"`) so agents can see how
62
+ the server interpreted their request and report it accurately to the
63
+ end user.
64
+
65
+ Skill content is rewritten to lead with `--all-my-orgs` for cross-org
66
+ questions, with `--slugs` repositioned as "use this for an explicit
67
+ subset." Existing `--slugs` examples and the `orgs list` + `--slugs`
68
+ recipe are updated to call out the new single-call alternative.
69
+
3
70
  ## 0.15.1
4
71
 
5
72
  ### Patch Changes
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-UZDSNWGL.js";
10
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -195,6 +195,8 @@ description: |
195
195
  "customer reports", "reports reported by customer", "find detection gaps",
196
196
  "org healthcheck", "organization health check", "audit my org",
197
197
  "what's wrong with org", "review org setup", "list orgs", "list organizations",
198
+ "get org", "get organization", "show org details", "org by slug",
199
+ "fetch org details", "org info",
198
200
  "orgs with takedowns off", "automation off across orgs",
199
201
  "which customers have X enabled", "service toggles by org",
200
202
  "is this URL blocked", "is this domain blocked", "is this address blocked",
@@ -657,6 +659,35 @@ Customers see only orgs they're a member of. Staff/superuser sessions see
657
659
  every matching org. The response is the same in both cases; visibility is
658
660
  enforced server-side.
659
661
 
662
+ ### \`orgs get\` \u2014 Get a single organization by slug
663
+
664
+ Look up one organization the caller has access to. Returns the same per-org
665
+ shape as a single row from \`orgs list\` (subscription status + per-service
666
+ active/automated flags), so anything you could read from \`orgs list\` you
667
+ can also read here without paging or filtering. Use it when the user names
668
+ a specific customer ("show me acme's setup", "is takedowns automation on
669
+ for morpho?") and you don't need the rest of the catalogue.
670
+
671
+ \`\`\`bash
672
+ chainpatrol orgs get <slug>
673
+ chainpatrol --json orgs get <slug>
674
+ \`\`\`
675
+
676
+ Permission rules match \`orgs list\`:
677
+
678
+ - Org-scoped API keys: must match the slug \u2014 querying another org returns
679
+ 403.
680
+ - User sessions / user-scoped API keys: need an active OrganizationMembership
681
+ on the slug. Staff/superusers can read any org.
682
+ - Soft-deleted orgs are not returned (404). A 404 also fires for orgs the
683
+ caller would not be authorized to see \u2014 the endpoint deliberately does
684
+ not distinguish "doesn't exist" from "you can't see this" beyond the
685
+ generic 403/404 envelope.
686
+
687
+ Prefer \`orgs get\` over \`orgs list\` + client-side filter whenever the
688
+ caller already knows the slug; it's one round-trip and side-steps the
689
+ list filters entirely.
690
+
660
691
  #### Use case: finding service configuration gaps across the customer base
661
692
 
662
693
  When the user asks something like "which customers are paying us but don't
@@ -674,21 +705,25 @@ When the user asks for a number that **spans more than one customer/org/brand**
674
705
  "across all brands", "company-wide", "total takedowns", "total threats",
675
706
  "average takedowns per day across customers", "rollup across customers",
676
707
  "how many takedowns in the last 7 days?" (no org named) \u2014 the answer is
677
- **\`chainpatrol metrics organization --slugs <comma-list>\`**, *not*
678
- \`metrics summary\`. \`summary\`, \`found\`, and \`breakdown\` are
679
- single-org commands and have no \`--slugs\`; only \`organization\` does.
680
- The \`--slugs\` form rolls totals + per-day / per-org-per-day averages
681
- server-side in **one** HTTP call. Full recipe is in the \`--slugs\`
682
- section below.
708
+ **\`chainpatrol metrics organization --all-my-orgs\`** (or \`--slugs\`
709
+ if you want a specific subset). \`summary\`, \`found\`, and \`breakdown\`
710
+ are single-org commands and have **no** \`--slugs\`/\`--all-my-orgs\`
711
+ form; only \`organization\` does. The multi-org form rolls totals +
712
+ per-day / per-org-per-day averages + a per-org breakdown server-side
713
+ in **one** HTTP call.
683
714
 
684
715
  **Anti-patterns to avoid:**
685
716
 
686
- - \u274C "There's no cross-org aggregation in the CLI." There is \u2014 \`metrics
687
- organization --slugs\`. Don't bail out citing the docs without
688
- searching for \`--slugs\`.
717
+ - \u274C "There's no cross-org aggregation in the CLI." There is \u2014
718
+ \`metrics organization --all-my-orgs\` (or \`--slugs\`). Don't bail
719
+ out citing the docs without trying these flags.
689
720
  - \u274C Looping \`metrics summary --org X\` once per customer to sum
690
- client-side. \`--slugs\` is one round-trip; the loop is what made
691
- the endpoint 503-prone in the first place.
721
+ client-side. The multi-org form is one round-trip; the loop is what
722
+ made the endpoint 503-prone in the first place.
723
+ - \u274C Calling \`orgs list\` to build a slug list when you just want
724
+ "everything." Use \`--all-my-orgs\` and skip the enumeration entirely \u2014
725
+ the server resolves the same set via shared logic. Save \`orgs list\`
726
+ + \`--slugs\` for cases where the user asked for a specific subset.
692
727
  - \u274C Routing the user to Metabase / the data warehouse for a question
693
728
  the CLI can answer. Reach for Metabase only when the metric isn't
694
729
  in the \`--include\` list (\`reports\`, \`newThreats\`,
@@ -696,35 +731,46 @@ section below.
696
731
  \`domainThreats\`, \`twitterThreats\`, \`telegramThreats\`,
697
732
  \`otherThreats\`, \`blockedByType\`, \`blockedByDay\`).
698
733
  - \u274C "Iterating 100+ orgs one-by-one isn't practical here." Right \u2014
699
- that's why you don't iterate. You pass \`--slugs\` (cap 50; if more,
700
- split the list into batches and sum) with \`--include <metric>\`
701
- to keep each per-org aggregate cheap.
734
+ that's why you don't iterate. \`--all-my-orgs\` covers up to 500
735
+ orgs in a single call; for larger fleets, narrow with
736
+ \`--subscription-status\` / \`--service-active\`, or split an
737
+ explicit \`--slugs\` list into batches and sum.
702
738
 
703
739
  #### Common cross-org recipe (copy and run)
704
740
 
705
- \`\`\`bash
706
- # Step 1: get the slugs of every active customer with takedowns enabled
707
- chainpatrol --json orgs list \\
708
- --subscription-status ACTIVE \\
709
- --service-active takedowns
741
+ The shortest answer to "how many takedowns across all customers in the
742
+ last 7 days?" is a single call \u2014 no \`orgs list\` step needed:
710
743
 
711
- # Step 2: feed the slugs into one metrics call. Server fans out and rolls up.
744
+ \`\`\`bash
712
745
  chainpatrol --json metrics organization \\
713
- --slugs <comma-list from step 1> \\
746
+ --all-my-orgs \\
747
+ --subscription-status ACTIVE \\
748
+ --service-active takedowns \\
714
749
  --include takedownsCompleted \\
715
750
  --from <YYYY-MM-DD 7 days ago> --to <YYYY-MM-DD today>
716
751
 
717
752
  # Read these fields from the JSON:
718
- # .metrics.takedownsCompleted \u2190 grand total across all orgs
719
- # .averages.perDay.takedownsCompleted \u2190 total / windowDays
720
- # .averages.perOrgPerDay.takedownsCompleted \u2190 total / numOrgs / windowDays
721
- # .perOrg[slug].metrics.takedownsCompleted \u2190 per-customer breakdown
753
+ # .scope.orgs \u2190 which orgs the server resolved
754
+ # .metrics.takedownsCompleted \u2190 grand total across all orgs
755
+ # .averages.perDay.takedownsCompleted \u2190 total / windowDays
756
+ # .averages.perOrgPerDay.takedownsCompleted \u2190 total / numOrgs / windowDays
757
+ # .perOrg[slug].metrics.takedownsCompleted \u2190 per-customer breakdown
722
758
  \`\`\`
723
759
 
724
760
  Replace \`takedownsCompleted\` with whatever metric the user asked about.
725
761
  Replace the date range with whatever window they asked about (default
726
762
  3 months if they didn't say).
727
763
 
764
+ If the user *asked* for a specific subset of customers ("how many for
765
+ acme, beta, gamma combined?"), use \`--slugs acme,beta,gamma\` instead
766
+ of \`--all-my-orgs\`. \`--slugs\` accepts up to 500 entries.
767
+
768
+ **Auth note for \`--all-my-orgs\`:** requires a user session or a
769
+ user-scoped API key (it inherits your memberships). Org-scoped API
770
+ keys are rejected \u2014 they're pinned to a single org by design. If
771
+ \`whoami\` shows an org-scoped key, use a Bearer session instead or
772
+ ask the user to provision a user API key.
773
+
728
774
  #### Single-org examples
729
775
 
730
776
  \`\`\`bash
@@ -826,36 +872,44 @@ The response shape switches to multi-org:
826
872
  }
827
873
  \`\`\`
828
874
 
829
- To answer **"across all of our customers"**, list the orgs first:
875
+ To answer **"across all of our customers"**, prefer \`--all-my-orgs\` \u2014
876
+ it lets the server resolve the org set in one round-trip, no \`orgs
877
+ list\` step needed:
830
878
 
831
879
  \`\`\`bash
832
- chainpatrol --json orgs list --subscription-status ACTIVE # \u2190 get slugs
833
880
  chainpatrol --json metrics organization \\
834
- --slugs <comma-list of all the slugs from above> \\
881
+ --all-my-orgs \\
882
+ --subscription-status ACTIVE \\
883
+ --service-active takedowns \\
835
884
  --include takedownsCompleted \\
836
885
  --from 2026-05-12 --to 2026-05-19
837
886
  \`\`\`
838
887
 
839
- The endpoint caps \`slugs\` at 50 per call; if you need more, split
840
- the list and sum the totals client-side. **Do not loop \`metrics
841
- organization\` once per org** when \`--slugs\` will do \u2014 N HTTP
842
- round-trips is what made the old code 503.
888
+ Use \`--slugs <comma-list>\` only when the user asked for a *specific*
889
+ subset of customers ("how many for acme, beta, gamma combined?"); the
890
+ two flags are mutually exclusive. Both forms cap at 500 orgs per call;
891
+ if a real fleet exceeds that, narrow with
892
+ \`--subscription-status\`/\`--service-active\` filters or split an
893
+ explicit \`--slugs\` list into batches and sum. **Do not loop
894
+ \`metrics organization\` once per org** \u2014 N HTTP round-trips is what
895
+ made the old code 503.
843
896
 
844
- Always pair \`--slugs\` with \`--include\` to narrow the metric set to
845
- what the user actually asked about. "How many takedowns did we
846
- complete?" \u2192 \`--include takedownsCompleted\`; "average reports per
847
- day per customer?" \u2192 \`--include reports\`. Skipping \`--include\` runs
848
- ~11 aggregates per org which is rarely necessary.
897
+ Always pair the multi-org form with \`--include\` to narrow the metric
898
+ set to what the user actually asked about. "How many takedowns did
899
+ we complete?" \u2192 \`--include takedownsCompleted\`; "average reports
900
+ per day per customer?" \u2192 \`--include reports\`. Skipping \`--include\`
901
+ runs ~11 aggregates per org which is rarely necessary.
849
902
 
850
- #### Auth: which credential can use \`--slugs\`
903
+ #### Auth: which credential can use \`--slugs\` / \`--all-my-orgs\`
851
904
 
852
905
  - **Org-scoped API key** (the most common production key): can only
853
- query its own org. If \`--slugs\` contains anything else the server
854
- returns 403. Use the \`--org\` form (or omit both) instead.
906
+ query its own org. If \`--slugs\` contains foreign orgs the server
907
+ returns 403; \`--all-my-orgs\` is also rejected. Use the \`--org\`
908
+ form (or omit both) instead.
855
909
  - **User API key**: inherits the user's org memberships, so it can
856
910
  query any org the user is a member of (or any org for staff
857
- users). This is the credential you want for cross-org rollups
858
- from automated agents.
911
+ users) via \`--slugs\` or \`--all-my-orgs\`. This is the credential
912
+ you want for cross-org rollups from automated agents.
859
913
  - **Session auth** (Bearer token, e.g. \`chainpatrol login\`): same
860
914
  membership rules as the user. Staff users can query any org.
861
915
 
@@ -279,6 +279,9 @@ function createApiClient(options) {
279
279
  return requestGet("/organization/metrics", {
280
280
  organizationSlug: input.organizationSlug,
281
281
  slugs: input.slugs,
282
+ allMyOrgs: input.allMyOrgs ? "true" : void 0,
283
+ subscriptionStatus: input.subscriptionStatus,
284
+ services: input.services ? JSON.stringify(input.services) : void 0,
282
285
  brandSlug: input.brandSlug,
283
286
  startDate: parseIsoDateString(input.startDate),
284
287
  endDate: parseIsoDateString(input.endDate),
@@ -294,6 +297,12 @@ function createApiClient(options) {
294
297
  getUserOrgs(input) {
295
298
  return request("/user/orgs", input);
296
299
  },
300
+ getUserOrg(input) {
301
+ return requestGet(
302
+ `/user/orgs/${encodeURIComponent(input.slug)}`,
303
+ {}
304
+ );
305
+ },
297
306
  listAssets(input) {
298
307
  return request("/asset/list", {
299
308
  type: input.type,
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.js";
12
12
  import {
13
13
  DateTime
14
14
  } from "./chunk-TFCNKBRC.js";
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  getCliVersion,
14
14
  isSkillInstalled,
15
15
  readInstalledSkillVersion
16
- } from "./chunk-IPN6O3N4.js";
16
+ } from "./chunk-75H4KTIQ.js";
17
17
  import "./chunk-NQT77IB7.js";
18
18
  import {
19
19
  DateTime
@@ -263,22 +263,25 @@ var HELP = {
263
263
  ]
264
264
  },
265
265
  "metrics organization": {
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>]",
266
+ description: "Get organization metrics via the documented GET /organization/metrics endpoint. Works with org-scoped API keys (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; use --slugs OR --all-my-orgs for multi-org rollups (totals + per-day / per-org-per-day averages + per-org breakdown). Fields not included come back as null.",
267
+ usage: "chainpatrol metrics organization [--org <slug>|--slugs <list>|--all-my-orgs] [--from ISO --to ISO|--this-week] [--include <list>]",
268
268
  options: [
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)",
272
- "--from <iso> Start date (ISO 8601, e.g. 2026-01-01)",
273
- "--to <iso> End date (ISO 8601)",
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)"
269
+ "--org <slug> Single org. Mutually exclusive with --slugs and --all-my-orgs.",
270
+ "--slugs <list> Comma list of org slugs (cap 500). Response is the rollup across these orgs.",
271
+ "--all-my-orgs Shortcut for 'every org I can see'. Server resolves via the same logic as `orgs list` (org-scoped API keys are rejected). Cap 500 \u2014 narrow with --subscription-status / --service-active if you have more.",
272
+ "--subscription-status <list> With --all-my-orgs: only ACTIVE / TRIAL / PROSPECT / INTEGRATION (default: same set as `orgs list`).",
273
+ "--service-active <list> With --all-my-orgs: only orgs with the listed service(s) active. Allowed: reporting, reviewing, protection, takedowns, detection, darkWebMonitoring.",
274
+ "--brand-slug <slug> Filter by brand slug (requires brands-metrics flag; single-org only).",
275
+ "--from <iso> Start date (ISO 8601, e.g. 2026-01-01)",
276
+ "--to <iso> End date (ISO 8601)",
277
+ "--this-week Use the current ISO week (overrides default)",
278
+ "--include <list> Comma list of fields to compute: reports,newThreats,threatsWatchlisted,takedownsFiled,takedownsCompleted,domainThreats,twitterThreats,telegramThreats,otherThreats,blockedByType,blockedByDay (omit for all)"
276
279
  ],
277
280
  examples: [
278
- "chainpatrol metrics organization --org acme # defaults to last 3 months, all metrics",
281
+ "chainpatrol metrics organization --org acme # defaults to last 3 months",
279
282
  "chainpatrol metrics organization --org acme --include takedownsFiled,takedownsCompleted",
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",
283
+ "chainpatrol metrics organization --slugs acme,beta,gamma --include takedownsCompleted --from 2026-05-12 --to 2026-05-19",
284
+ "chainpatrol metrics organization --all-my-orgs --service-active takedowns --subscription-status ACTIVE --include takedownsCompleted --from 2026-05-12 --to 2026-05-19 # cross-customer total in one call",
282
285
  "CHAINPATROL_API_KEY=... chainpatrol metrics organization --json --include reports"
283
286
  ]
284
287
  },
@@ -471,10 +474,11 @@ var HELP = {
471
474
  ]
472
475
  },
473
476
  orgs: {
474
- description: "List organizations, their assets, and asset groups. Top-level `orgs list` lists organizations accessible to you; the nested subcommands operate on the org tied to your API key.",
475
- usage: "chainpatrol orgs <list|assets list|asset-groups list>",
477
+ description: "List organizations, their assets, and asset groups. Top-level `orgs list` lists organizations accessible to you; `orgs get <slug>` returns details for a single accessible org; the nested subcommands operate on the org tied to your API key.",
478
+ usage: "chainpatrol orgs <list|get <slug>|assets list|asset-groups list>",
476
479
  examples: [
477
480
  "chainpatrol orgs list",
481
+ "chainpatrol orgs get acme",
478
482
  "chainpatrol orgs assets list --asset-type URL",
479
483
  "chainpatrol orgs asset-groups list"
480
484
  ]
@@ -525,6 +529,18 @@ var HELP = {
525
529
  "chainpatrol --json orgs list --subscription-status ACTIVE,TRIAL --service-active detection"
526
530
  ]
527
531
  },
532
+ "orgs get": {
533
+ description: "Get a single organization by slug. Returns the same shape as one row of `orgs list` (subscription status + per-service active/automated flags). Caller must have access: org-scoped API keys must match the slug, user sessions/keys need an active membership unless the user is staff.",
534
+ usage: "chainpatrol orgs get <slug>",
535
+ options: [
536
+ "--org <slug> Use the saved default org instead of a positional argument."
537
+ ],
538
+ examples: [
539
+ "chainpatrol orgs get acme",
540
+ "chainpatrol --json orgs get acme",
541
+ "chainpatrol orgs get acme --output markdown"
542
+ ]
543
+ },
528
544
  healthchecks: {
529
545
  description: "Run organization healthchecks via the public API. Use `list` to see every available check (including planned ones not yet implemented on the backend) and `run` to execute one or all implemented checks.",
530
546
  usage: "chainpatrol healthchecks <list|run <id>|run --all>",
@@ -864,6 +880,7 @@ ${getTopLevelHelp()}
864
880
  by: { type: "string" },
865
881
  include: { type: "string" },
866
882
  slugs: { type: "string" },
883
+ allMyOrgs: { type: "boolean", default: false },
867
884
  brand: { type: "string" },
868
885
  enable: { type: "boolean", default: false },
869
886
  disable: { type: "boolean", default: false },
@@ -1037,12 +1054,12 @@ function parseAttachmentUrls() {
1037
1054
  }
1038
1055
  async function handleConfigsList(org) {
1039
1056
  if (jsonMode) {
1040
- const { listConfigsJson } = await import("./list-json-X6QHIRE5.js");
1057
+ const { listConfigsJson } = await import("./list-json-ZTS5GXNA.js");
1041
1058
  await listConfigsJson({ org });
1042
1059
  return;
1043
1060
  }
1044
1061
  const { render } = await import("ink");
1045
- const { default: ConfigsList } = await import("./list-ZTMCEG56.js");
1062
+ const { default: ConfigsList } = await import("./list-AEM2KSO4.js");
1046
1063
  const { default: React } = await import("react");
1047
1064
  render(React.createElement(ConfigsList, { org }));
1048
1065
  }
@@ -1142,7 +1159,7 @@ async function main() {
1142
1159
  const positionals = cli.input.slice(2);
1143
1160
  const flagAssets = parseAssetInputs();
1144
1161
  const contents = [...positionals, ...flagAssets];
1145
- const { runAssetCheck } = await import("./check-M5U2CSWK.js");
1162
+ const { runAssetCheck } = await import("./check-EMGFDXUO.js");
1146
1163
  await runAssetCheck({
1147
1164
  contents,
1148
1165
  json: jsonMode,
@@ -1152,7 +1169,7 @@ async function main() {
1152
1169
  break;
1153
1170
  }
1154
1171
  if (subcommand === "list") {
1155
- const { runAssetList } = await import("./list-VOWXWXHY.js");
1172
+ const { runAssetList } = await import("./list-2ENAEZVP.js");
1156
1173
  await runAssetList({
1157
1174
  type: cli.flags.type ?? cli.flags.assetType,
1158
1175
  status: cli.flags.assetStatus ?? cli.flags.status,
@@ -1173,7 +1190,7 @@ async function main() {
1173
1190
  case "detections": {
1174
1191
  const org = await resolveOrg();
1175
1192
  if (subcommand === "healthcheck") {
1176
- const { runDetectionsHealthcheck } = await import("./healthcheck-FORGIJWP.js");
1193
+ const { runDetectionsHealthcheck } = await import("./healthcheck-J6BIZEG7.js");
1177
1194
  await runDetectionsHealthcheck({
1178
1195
  org,
1179
1196
  source: cli.flags.source,
@@ -1188,7 +1205,7 @@ async function main() {
1188
1205
  break;
1189
1206
  }
1190
1207
  if (subcommand === "validate") {
1191
- const { runDetectionsValidate } = await import("./validate-I3YHZDZR.js");
1208
+ const { runDetectionsValidate } = await import("./validate-QE2F4PYY.js");
1192
1209
  await runDetectionsValidate({
1193
1210
  org,
1194
1211
  source: cli.flags.source,
@@ -1203,7 +1220,7 @@ async function main() {
1203
1220
  break;
1204
1221
  }
1205
1222
  if (subcommand === "drift") {
1206
- const { runDetectionsDrift } = await import("./drift-T4AF6Y3X.js");
1223
+ const { runDetectionsDrift } = await import("./drift-RQKTGJWB.js");
1207
1224
  await runDetectionsDrift({
1208
1225
  org,
1209
1226
  source: cli.flags.source,
@@ -1217,7 +1234,7 @@ async function main() {
1217
1234
  break;
1218
1235
  }
1219
1236
  if (subcommand === "run") {
1220
- const { runDetectionsRun } = await import("./run-V7QJBNLF.js");
1237
+ const { runDetectionsRun } = await import("./run-HS2EEOM6.js");
1221
1238
  await runDetectionsRun({
1222
1239
  org,
1223
1240
  configId: cli.flags.configId,
@@ -1231,7 +1248,7 @@ async function main() {
1231
1248
  break;
1232
1249
  }
1233
1250
  if (subcommand === "list") {
1234
- const { runDetectionsList } = await import("./list-RMD4NKVB.js");
1251
+ const { runDetectionsList } = await import("./list-LKNCICLT.js");
1235
1252
  await runDetectionsList({
1236
1253
  org,
1237
1254
  cursor: cli.flags.cursor,
@@ -1260,7 +1277,7 @@ async function main() {
1260
1277
  break;
1261
1278
  }
1262
1279
  if (action === "run") {
1263
- const { runDetectionsRun } = await import("./run-V7QJBNLF.js");
1280
+ const { runDetectionsRun } = await import("./run-HS2EEOM6.js");
1264
1281
  await runDetectionsRun({
1265
1282
  org,
1266
1283
  configId: cli.flags.configId,
@@ -1278,7 +1295,7 @@ async function main() {
1278
1295
  throw new Error("detections configs update requires --config-id");
1279
1296
  }
1280
1297
  const configPatch = getConfigPatchFromSetFlags();
1281
- const { runDetectionsConfigsUpdate } = await import("./configs-update-YMPRYXKZ.js");
1298
+ const { runDetectionsConfigsUpdate } = await import("./configs-update-EKNLIRA6.js");
1282
1299
  await runDetectionsConfigsUpdate({
1283
1300
  org,
1284
1301
  configId: cli.flags.configId,
@@ -1312,11 +1329,15 @@ async function main() {
1312
1329
  case "metrics": {
1313
1330
  if (subcommand === "organization" || subcommand === "org") {
1314
1331
  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");
1332
+ const allMyOrgs = cli.flags.allMyOrgs;
1333
+ const org2 = slugs && slugs.length > 0 || allMyOrgs ? void 0 : await tryResolveOrg();
1334
+ const { runMetricsOrganization } = await import("./organization-PFPBZXJZ.js");
1317
1335
  await runMetricsOrganization({
1318
1336
  org: org2,
1319
1337
  slugs,
1338
+ allMyOrgs: allMyOrgs || void 0,
1339
+ subscriptionStatus: parseCsvList(cli.flags.subscriptionStatus),
1340
+ serviceActive: parseCsvList(cli.flags.serviceActive),
1320
1341
  brandSlug: cli.flags.brandSlug,
1321
1342
  from: cli.flags.from,
1322
1343
  to: cli.flags.to,
@@ -1329,7 +1350,7 @@ async function main() {
1329
1350
  }
1330
1351
  const org = await resolveOrg();
1331
1352
  if (subcommand === "summary") {
1332
- const { runMetricsSummary } = await import("./summary-NYHIQ5DP.js");
1353
+ const { runMetricsSummary } = await import("./summary-POVQC6B7.js");
1333
1354
  await runMetricsSummary({
1334
1355
  org,
1335
1356
  from: cli.flags.from,
@@ -1342,7 +1363,7 @@ async function main() {
1342
1363
  break;
1343
1364
  }
1344
1365
  if (subcommand === "found") {
1345
- const { runMetricsFound } = await import("./found-XEGMO6YY.js");
1366
+ const { runMetricsFound } = await import("./found-LVYBEOS6.js");
1346
1367
  await runMetricsFound({
1347
1368
  org,
1348
1369
  from: cli.flags.from,
@@ -1359,7 +1380,7 @@ async function main() {
1359
1380
  if (!by || !["day", "type", "brand"].includes(by)) {
1360
1381
  throw new Error("metrics breakdown requires --by <day|type|brand>");
1361
1382
  }
1362
- const { runMetricsBreakdown } = await import("./breakdown-27PMFEWQ.js");
1383
+ const { runMetricsBreakdown } = await import("./breakdown-ETSF2BZ3.js");
1363
1384
  await runMetricsBreakdown({
1364
1385
  org,
1365
1386
  by,
@@ -1380,7 +1401,7 @@ async function main() {
1380
1401
  case "reports": {
1381
1402
  if (subcommand === "list") {
1382
1403
  const org = await resolveOrg();
1383
- const { runReportsList } = await import("./list-N4TTNR6N.js");
1404
+ const { runReportsList } = await import("./list-44BQZHJD.js");
1384
1405
  await runReportsList({
1385
1406
  org,
1386
1407
  limit: cli.flags.limit,
@@ -1408,7 +1429,7 @@ async function main() {
1408
1429
  }
1409
1430
  if (subcommand === "create") {
1410
1431
  const org = await tryResolveOrg();
1411
- const { runReportsCreate } = await import("./create-DQYNYD6G.js");
1432
+ const { runReportsCreate } = await import("./create-M4PV5VVU.js");
1412
1433
  await runReportsCreate({
1413
1434
  org,
1414
1435
  title: cli.flags.title,
@@ -1429,7 +1450,7 @@ async function main() {
1429
1450
  const positionals = cli.input.slice(2);
1430
1451
  const flagAssets = parseAssetInputs();
1431
1452
  const assetContents = [...positionals, ...flagAssets];
1432
- const { runReportsSearch } = await import("./search-ME6KFCEV.js");
1453
+ const { runReportsSearch } = await import("./search-EBXEP7XC.js");
1433
1454
  await runReportsSearch({
1434
1455
  assetContents,
1435
1456
  reportedByCustomer: cli.flags.reportedByCustomer,
@@ -1445,7 +1466,7 @@ async function main() {
1445
1466
  }
1446
1467
  case "threats": {
1447
1468
  if (subcommand === "list") {
1448
- const { runThreatsList } = await import("./list-XHBQMP6P.js");
1469
+ const { runThreatsList } = await import("./list-E2JVJNHV.js");
1449
1470
  await runThreatsList({
1450
1471
  query: cli.flags.query,
1451
1472
  from: cli.flags.from,
@@ -1468,7 +1489,7 @@ async function main() {
1468
1489
  case "takedowns": {
1469
1490
  if (subcommand === "list") {
1470
1491
  const org = await tryResolveOrg();
1471
- const { runTakedownsList } = await import("./list-DVPEMSVH.js");
1492
+ const { runTakedownsList } = await import("./list-S5Y6GW43.js");
1472
1493
  await runTakedownsList({
1473
1494
  org,
1474
1495
  query: cli.flags.query,
@@ -1499,7 +1520,7 @@ async function main() {
1499
1520
  }
1500
1521
  case "queues": {
1501
1522
  if (subcommand === "snapshot") {
1502
- const { runQueuesSnapshot } = await import("./snapshot-B6TQCFQ3.js");
1523
+ const { runQueuesSnapshot } = await import("./snapshot-GNZMYTXT.js");
1503
1524
  await runQueuesSnapshot({
1504
1525
  org: cli.flags.org,
1505
1526
  all: cli.flags.all,
@@ -1517,7 +1538,7 @@ async function main() {
1517
1538
  }
1518
1539
  case "orgs": {
1519
1540
  if (subcommand === "list") {
1520
- const { runOrgsList } = await import("./list-COJ5BLQV.js");
1541
+ const { runOrgsList } = await import("./list-OEJV4UNQ.js");
1521
1542
  await runOrgsList({
1522
1543
  query: cli.flags.query,
1523
1544
  subscriptionStatus: cli.flags.subscriptionStatus,
@@ -1530,9 +1551,20 @@ async function main() {
1530
1551
  });
1531
1552
  break;
1532
1553
  }
1554
+ if (subcommand === "get") {
1555
+ const positionalSlug = action;
1556
+ const slug = positionalSlug ?? await tryResolveOrg();
1557
+ const { runOrgsGet } = await import("./get-BPTWCJ6H.js");
1558
+ await runOrgsGet({
1559
+ slug,
1560
+ json: jsonMode,
1561
+ outputFormat: cliContext.outputFormat
1562
+ });
1563
+ break;
1564
+ }
1533
1565
  if (subcommand === "assets") {
1534
1566
  if (action === "list") {
1535
- const { runOrgsAssetsList } = await import("./list-63HPS2RE.js");
1567
+ const { runOrgsAssetsList } = await import("./list-O62BZKQ5.js");
1536
1568
  await runOrgsAssetsList({
1537
1569
  type: cli.flags.type ?? cli.flags.assetType,
1538
1570
  groupId: cli.flags.groupId,
@@ -1548,7 +1580,7 @@ async function main() {
1548
1580
  }
1549
1581
  if (subcommand === "asset-groups") {
1550
1582
  if (action === "list") {
1551
- const { runOrgsAssetGroupsList } = await import("./list-WPXNCBT3.js");
1583
+ const { runOrgsAssetGroupsList } = await import("./list-O36VWTWR.js");
1552
1584
  await runOrgsAssetGroupsList({
1553
1585
  json: jsonMode,
1554
1586
  outputFormat: cliContext.outputFormat
@@ -1557,13 +1589,13 @@ async function main() {
1557
1589
  }
1558
1590
  throw new Error("Usage: chainpatrol orgs asset-groups list");
1559
1591
  }
1560
- const hint = subcommand ? suggest(subcommand, ["list", "assets", "asset-groups"]) : null;
1592
+ const hint = subcommand ? suggest(subcommand, ["list", "get", "assets", "asset-groups"]) : null;
1561
1593
  throw new Error(
1562
- subcommand ? `Unknown subcommand: orgs ${subcommand}${hint ? `. Did you mean "orgs ${hint}"?` : ""}` : "Usage: chainpatrol orgs <list|assets list|asset-groups list> [filters]"
1594
+ subcommand ? `Unknown subcommand: orgs ${subcommand}${hint ? `. Did you mean "orgs ${hint}"?` : ""}` : "Usage: chainpatrol orgs <list|get <slug>|assets list|asset-groups list> [filters]"
1563
1595
  );
1564
1596
  }
1565
1597
  case "whoami": {
1566
- const { runWhoami } = await import("./whoami-6KU4LOJS.js");
1598
+ const { runWhoami } = await import("./whoami-24KUJUN4.js");
1567
1599
  await runWhoami({
1568
1600
  json: jsonMode,
1569
1601
  outputFormat: cliContext.outputFormat
@@ -1572,7 +1604,7 @@ async function main() {
1572
1604
  }
1573
1605
  case "healthchecks": {
1574
1606
  if (subcommand === "list") {
1575
- const { runHealthchecksList } = await import("./list-OWJLAWMP.js");
1607
+ const { runHealthchecksList } = await import("./list-CNF2FGLN.js");
1576
1608
  await runHealthchecksList({
1577
1609
  json: jsonMode,
1578
1610
  outputFormat: cliContext.outputFormat
@@ -1586,7 +1618,7 @@ async function main() {
1586
1618
  thresholds.minResults = cli.flags.minResults;
1587
1619
  if (cli.flags.lookbackHours !== void 0)
1588
1620
  thresholds.lookbackHours = cli.flags.lookbackHours;
1589
- const { runHealthchecksRun } = await import("./run-G5J77GZD.js");
1621
+ const { runHealthchecksRun } = await import("./run-R3RR6XQF.js");
1590
1622
  await runHealthchecksRun({
1591
1623
  org,
1592
1624
  id: action,
@@ -1604,7 +1636,7 @@ async function main() {
1604
1636
  }
1605
1637
  case "presets": {
1606
1638
  if (subcommand === "list") {
1607
- const { runPresetsList } = await import("./list-BJMP3QEV.js");
1639
+ const { runPresetsList } = await import("./list-JCJHIJM4.js");
1608
1640
  await runPresetsList({ outputFormat: cliContext.outputFormat });
1609
1641
  break;
1610
1642
  }
@@ -1615,7 +1647,7 @@ async function main() {
1615
1647
  );
1616
1648
  }
1617
1649
  const org = await resolveOrg();
1618
- const { runPresetsRun } = await import("./run-BHAH7DUC.js");
1650
+ const { runPresetsRun } = await import("./run-LKPKTH3W.js");
1619
1651
  await runPresetsRun({
1620
1652
  presetId: action,
1621
1653
  org,
@@ -1632,12 +1664,12 @@ async function main() {
1632
1664
  case "setup":
1633
1665
  case "install":
1634
1666
  case "i": {
1635
- const { setupSkill } = await import("./setup-skill-6SDQXFTR.js");
1667
+ const { setupSkill } = await import("./setup-skill-RPVW2CCQ.js");
1636
1668
  setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
1637
1669
  break;
1638
1670
  }
1639
1671
  case "uninstall": {
1640
- const { uninstallSkill } = await import("./setup-skill-6SDQXFTR.js");
1672
+ const { uninstallSkill } = await import("./setup-skill-RPVW2CCQ.js");
1641
1673
  uninstallSkill({ json: jsonMode });
1642
1674
  break;
1643
1675
  }
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-UZDSNWGL.js";
10
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
10
+ } from "./chunk-DPLUSURP.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -0,0 +1,75 @@
1
+ import {
2
+ CliExitError,
3
+ ExitCode
4
+ } from "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput,
7
+ toCsvRows
8
+ } from "./chunk-VFT3TD3E.js";
9
+ import {
10
+ createApiClient
11
+ } from "./chunk-DPLUSURP.js";
12
+ import "./chunk-EGWK6SRQ.js";
13
+ import "./chunk-TFCNKBRC.js";
14
+ import "./chunk-U73SABXK.js";
15
+
16
+ // src/commands/orgs/get.ts
17
+ function summarizeServices(org) {
18
+ const parts = [];
19
+ for (const service of ["reporting", "reviewing", "protection", "takedowns"]) {
20
+ const entry = org.services[service];
21
+ if (!entry.active) continue;
22
+ parts.push(`${service}=${entry.automated ? "auto" : "manual"}`);
23
+ }
24
+ if (org.services.detection.active) parts.push("detection");
25
+ if (org.services.darkWebMonitoring.active) parts.push("darkWebMonitoring");
26
+ return parts.length > 0 ? parts.join(" ") : "(no services enabled)";
27
+ }
28
+ async function runOrgsGet(options) {
29
+ const slug = options.slug?.trim();
30
+ if (!slug) {
31
+ throw new CliExitError(
32
+ "orgs get requires an organization slug. Example: chainpatrol orgs get acme",
33
+ ExitCode.USAGE
34
+ );
35
+ }
36
+ const client = options.apiClient ?? createApiClient();
37
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
38
+ const result = await client.getUserOrg({ slug });
39
+ const { organization } = result;
40
+ printOutput({
41
+ outputFormat,
42
+ json: result,
43
+ markdown: [
44
+ `# ${organization.name} (\`${organization.slug}\`)`,
45
+ "",
46
+ `- Subscription status: ${organization.subscriptionStatus}`,
47
+ `- Services: ${summarizeServices(organization)}`
48
+ ].join("\n"),
49
+ csv: toCsvRows([
50
+ {
51
+ slug: organization.slug,
52
+ name: organization.name,
53
+ subscriptionStatus: organization.subscriptionStatus,
54
+ reportingActive: organization.services.reporting.active,
55
+ reportingAutomated: organization.services.reporting.automated,
56
+ reviewingActive: organization.services.reviewing.active,
57
+ reviewingAutomated: organization.services.reviewing.automated,
58
+ protectionActive: organization.services.protection.active,
59
+ protectionAutomated: organization.services.protection.automated,
60
+ takedownsActive: organization.services.takedowns.active,
61
+ takedownsAutomated: organization.services.takedowns.automated,
62
+ detectionActive: organization.services.detection.active,
63
+ darkWebMonitoringActive: organization.services.darkWebMonitoring.active
64
+ }
65
+ ]),
66
+ human: () => {
67
+ console.log(`${organization.name} (slug: ${organization.slug})`);
68
+ console.log(` subscriptionStatus: ${organization.subscriptionStatus}`);
69
+ console.log(` services: ${summarizeServices(organization)}`);
70
+ }
71
+ });
72
+ }
73
+ export {
74
+ runOrgsGet
75
+ };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -10,7 +10,7 @@ import {
10
10
  REPORT_REVIEW_DECISIONS,
11
11
  REPORT_STATUSES,
12
12
  createApiClient
13
- } from "./chunk-UZDSNWGL.js";
13
+ } from "./chunk-DPLUSURP.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-JCMWDZYY.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-UZDSNWGL.js";
7
+ } from "./chunk-DPLUSURP.js";
8
8
  import {
9
9
  AuthCorruptedError,
10
10
  AuthExpiredError,
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-UZDSNWGL.js";
7
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-FOTFWUOX.js";
3
+ } from "./chunk-HGAQBGGL.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-UZDSNWGL.js";
9
+ import "./chunk-DPLUSURP.js";
10
10
  import "./chunk-EGWK6SRQ.js";
11
11
  import "./chunk-TFCNKBRC.js";
12
12
  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-UZDSNWGL.js";
15
+ } from "./chunk-DPLUSURP.js";
16
16
  import "./chunk-EGWK6SRQ.js";
17
17
  import "./chunk-TFCNKBRC.js";
18
18
  import "./chunk-U73SABXK.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-UZDSNWGL.js";
7
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
14
+ } from "./chunk-DPLUSURP.js";
15
15
  import "./chunk-EGWK6SRQ.js";
16
16
  import "./chunk-TFCNKBRC.js";
17
17
  import "./chunk-U73SABXK.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-UZDSNWGL.js";
3
+ } from "./chunk-DPLUSURP.js";
4
4
  import "./chunk-EGWK6SRQ.js";
5
5
  import "./chunk-TFCNKBRC.js";
6
6
  import "./chunk-U73SABXK.js";
@@ -8,12 +8,34 @@ import {
8
8
  import {
9
9
  ORGANIZATION_METRICS_INCLUDE_FIELDS,
10
10
  createApiClient
11
- } from "./chunk-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/metrics/organization.ts
17
+ var SERVICE_KEYS = [
18
+ "reporting",
19
+ "reviewing",
20
+ "protection",
21
+ "takedowns",
22
+ "detection",
23
+ "darkWebMonitoring"
24
+ ];
25
+ var SERVICE_KEY_SET = new Set(SERVICE_KEYS);
26
+ function buildServicesFilter(raw) {
27
+ if (!raw || raw.length === 0) return void 0;
28
+ const filter = {};
29
+ for (const value of raw) {
30
+ if (!SERVICE_KEY_SET.has(value)) {
31
+ throw new Error(
32
+ `Unknown --service-active value: '${value}'. Allowed: ${SERVICE_KEYS.join(", ")}`
33
+ );
34
+ }
35
+ filter[value] = { active: true };
36
+ }
37
+ return filter;
38
+ }
17
39
  var INCLUDE_VALUES = new Set(ORGANIZATION_METRICS_INCLUDE_FIELDS);
18
40
  var METRIC_LABELS = [
19
41
  ["reports", "Reports"],
@@ -50,8 +72,13 @@ function populatedMetricRows(metrics, format = (v) => String(v)) {
50
72
  ]);
51
73
  }
52
74
  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.");
75
+ const explicitForms = [
76
+ Boolean(options.org),
77
+ Boolean(options.slugs && options.slugs.length > 0),
78
+ Boolean(options.allMyOrgs)
79
+ ].filter(Boolean).length;
80
+ if (explicitForms > 1) {
81
+ throw new Error("Pass at most one of --org, --slugs, or --all-my-orgs.");
55
82
  }
56
83
  const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
57
84
  const client = options.apiClient ?? createApiClient();
@@ -61,9 +88,13 @@ async function runMetricsOrganization(options) {
61
88
  thisWeek: options.thisWeek
62
89
  });
63
90
  const include = parseIncludeList(options.include);
91
+ const services = buildServicesFilter(options.serviceActive);
64
92
  const input = {
65
93
  organizationSlug: options.org,
66
94
  slugs: options.slugs && options.slugs.length > 0 ? options.slugs : void 0,
95
+ allMyOrgs: options.allMyOrgs || void 0,
96
+ subscriptionStatus: options.subscriptionStatus && options.subscriptionStatus.length > 0 ? options.subscriptionStatus : void 0,
97
+ services,
67
98
  brandSlug: options.brandSlug,
68
99
  startDate: range.startDate,
69
100
  endDate: range.endDate,
@@ -71,7 +102,7 @@ async function runMetricsOrganization(options) {
71
102
  };
72
103
  const result = await client.getOrganizationMetrics(input);
73
104
  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";
105
+ const scopeLabel = isMulti ? `${result.scope.orgs.length} orgs${result.scope.resolvedFrom === "allMyOrgs" ? " (--all-my-orgs)" : ` (${result.scope.orgs.slice(0, 10).join(", ")}${result.scope.orgs.length > 10 ? ", \u2026" : ""})`}` : result.scope.orgs[0] ?? options.org ?? "your organization";
75
106
  const includeLabel = include ? ` (include: ${include.join(", ")})` : "";
76
107
  const totalsRows = populatedMetricRows(result.metrics);
77
108
  const perDayRows = result.averages.perDay ? populatedMetricRows(result.averages.perDay, formatAverage) : [];
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  getPresetDefinition,
3
3
  runPreset
4
- } from "./chunk-FOTFWUOX.js";
4
+ } from "./chunk-HGAQBGGL.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-UZDSNWGL.js";
10
+ import "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-IPN6O3N4.js";
9
+ } from "./chunk-75H4KTIQ.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
10
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
11
+ } from "./chunk-DPLUSURP.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-UZDSNWGL.js";
6
+ } from "./chunk-DPLUSURP.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.15.1",
5
+ "version": "0.17.0",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://chainpatrol.com/docs/cli",
8
8
  "keywords": [