@chainpatrol/cli 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @chainpatrol/cli
2
2
 
3
+ ## 0.22.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e2b28a5: Expose `contactUrl` (main communication channel) on `orgs list` and `orgs get` output. Add `organization.missing-contact-url` healthcheck. Server-side work in @chainpatrol/external-trpc and @chainpatrol/validation supports the new field and healthcheck; both are private packages and don't need version bumps.
8
+ - b1e35f0: Surface pending Wallet Blocking / Takedown approvals on `orgs list` and `orgs get`. Each org now reports `pendingServiceApprovals` — open requests by lower-level members to enable an approval-gated service (Wallet Blocking / Takedowns) that a higher-up at the org has not yet approved. Adds `--pending-approval-active`, `--pending-approval-inactive`, and `--pending-approval-service <protection|takedowns>` filters to `orgs list` so you can track which organizations are awaiting sign-off, distinct from which have the service turned off. Server-side work in @chainpatrol/external-trpc and @chainpatrol/validation backs the new field and filters; both are private packages and don't need version bumps.
9
+
10
+ ## 0.21.0
11
+
12
+ ### Minor Changes
13
+
14
+ - b3e77eb: `orgs list` and `orgs get` now surface each organization's "Obligatory
15
+ Organization Admin Approval" state (`Organization.requiresCustomerReview`)
16
+ alongside its per-asset-type scope. The new response field is
17
+ `obligatoryAdminApproval: { active, assetTypes }` — an empty `assetTypes`
18
+ list with `active: true` means approval applies to every asset type
19
+ (matches the in-app UI's behaviour when no narrowing has been configured).
20
+
21
+ `orgs list` also accepts three new filters that compose with the
22
+ existing ones via AND:
23
+
24
+ - `--obligatory-approval-active` / `--obligatory-approval-inactive` —
25
+ filter on the toggle itself.
26
+ - `--obligatory-approval-asset-type <csv>` — return only orgs whose
27
+ approval scope covers every listed asset type (an org's empty
28
+ per-asset-type list still counts as "all types"). Passing this flag
29
+ implies the feature is on, so it can't be combined with
30
+ `--obligatory-approval-inactive`.
31
+
32
+ Server-side work in `@chainpatrol/external-trpc` and `@chainpatrol/validation`
33
+ extends the `/user/orgs` and `/user/orgs/{slug}` schemas accordingly; both
34
+ are private packages and don't need version bumps.
35
+
3
36
  ## 0.20.0
4
37
 
5
38
  ### Minor Changes
@@ -199,6 +199,18 @@ description: |
199
199
  "fetch org details", "org info",
200
200
  "orgs with takedowns off", "automation off across orgs",
201
201
  "which customers have X enabled", "service toggles by org",
202
+ "obligatory admin approval", "obligatory organization admin approval",
203
+ "obligatory approval", "admin approval enabled", "admin approval orgs",
204
+ "requires customer review", "requires admin approval",
205
+ "orgs that require admin approval", "orgs requiring approval",
206
+ "which orgs require approval for twitter", "approval scope",
207
+ "admin approval asset types", "approval per asset type",
208
+ "pending approval", "pending approvals", "pending service approval",
209
+ "orgs with pending approval", "pending wallet blocking approval",
210
+ "pending takedown approval", "awaiting approval", "waiting for approval",
211
+ "is protection active approval", "wallet blocking approval",
212
+ "who needs to approve wallet blocking", "service change request",
213
+ "track orgs with pending approval", "orgs awaiting sign-off",
202
214
  "is this URL blocked", "is this domain blocked", "is this address blocked",
203
215
  "check this asset", "asset check", "lookup asset status",
204
216
  "what is ARCHIVE_ORG", "what does PAGE mean", "list asset types",
@@ -656,14 +668,17 @@ Guide. Key signals in the response:
656
668
 
657
669
  Use \`--all\` to snapshot every org you have access to instead of a single slug.
658
670
 
659
- ### \`orgs list\` \u2014 List organizations with subscription status and service toggles
671
+ ### \`orgs list\` \u2014 List organizations with subscription status, service toggles, and admin-approval scope
660
672
 
661
673
  Returns every organization the caller can see, with each org's
662
- subscription status (\`PROSPECT\`, \`TRIAL\`, \`ACTIVE\`, \`INTEGRATION\`) and
663
- which services are active. Use it to answer questions like "which
664
- customers have takedowns enabled but automation off?" or "which prospects
665
- don't have detection turned on yet?" \u2014 filters compose with AND and are
666
- applied server-side, so one call returns the final list.
674
+ subscription status (\`PROSPECT\`, \`TRIAL\`, \`ACTIVE\`, \`INTEGRATION\`),
675
+ which services are active, and the per-org "Obligatory Organization
676
+ Admin Approval" toggle (with its asset-type scope). Use it to answer
677
+ questions like "which customers have takedowns enabled but automation
678
+ off?", "which prospects don't have detection turned on yet?", or "which
679
+ orgs require admin approval before adding Twitter assets to the
680
+ blocklist?" \u2014 filters compose with AND and are applied server-side, so
681
+ one call returns the final list.
667
682
 
668
683
  \`\`\`bash
669
684
  chainpatrol --json orgs list \\
@@ -686,6 +701,34 @@ misleading filters like "reporting.automated=true" that don't mean
686
701
  anything. \`detection\` and \`darkWebMonitoring\` have always been
687
702
  single-flag services.
688
703
 
704
+ #### Obligatory Organization Admin Approval
705
+
706
+ Each org also has a separate "Obligatory Organization Admin Approval"
707
+ toggle (\`Organization.requiresCustomerReview\` in the schema, surfaced
708
+ on the Services settings page in the app) that gates customer-side
709
+ review of proposals ChainPatrol staff have already approved before
710
+ they're added to the blocklist. It is NOT one of the operational
711
+ services above \u2014 it is an org-policy toggle with its own per-asset-type
712
+ scope.
713
+
714
+ Each org's response includes:
715
+
716
+ \`\`\`json
717
+ {
718
+ "obligatoryAdminApproval": {
719
+ "active": true,
720
+ "assetTypes": ["TWITTER", "URL"]
721
+ }
722
+ }
723
+ \`\`\`
724
+
725
+ - \`active=true\` + empty \`assetTypes\` means approval applies to
726
+ **all** asset types \u2014 the org has not narrowed the scope.
727
+ - \`active=true\` + a non-empty \`assetTypes\` list means approval is
728
+ required only for those asset types.
729
+ - \`active=false\` means no approval is required; \`assetTypes\` is
730
+ always empty in that case.
731
+
689
732
  So per-org JSON looks like:
690
733
 
691
734
  \`\`\`json
@@ -697,16 +740,55 @@ So per-org JSON looks like:
697
740
  "takedowns": { "active": true, "automated": false },
698
741
  "detection": { "active": true },
699
742
  "darkWebMonitoring": { "active": false }
700
- }
743
+ },
744
+ "obligatoryAdminApproval": { "active": true, "assetTypes": ["TWITTER"] },
745
+ "pendingServiceApprovals": [
746
+ {
747
+ "service": "protection",
748
+ "automated": false,
749
+ "serviceType": "isProtectionActive",
750
+ "serviceName": "Wallet Blocking",
751
+ "requestedAt": "2026-05-30T18:04:11.000Z"
752
+ }
753
+ ]
701
754
  }
702
755
  \`\`\`
703
756
 
757
+ #### Pending service approvals (Wallet Blocking / Takedowns)
758
+
759
+ Turning on **Wallet Blocking** (\`protection\`, the "is protection active"
760
+ toggle) or **Takedowns** is approval-gated: when a lower-level org member
761
+ tries to enable one, ChainPatrol records a pending request that a higher-up
762
+ at the org \u2014 an org OWNER, or ChainPatrol staff acting as owner \u2014 must
763
+ approve before the service actually turns on. \`pendingServiceApprovals\`
764
+ lists those open, awaiting-sign-off requests per org.
765
+
766
+ This is the answer to "which organizations have a pending approval for
767
+ Wallet blocking or Takedown services?" \u2014 it is NOT the same as "which orgs
768
+ have Wallet Blocking turned off". An org can have \`protection.active=false\`
769
+ with **no** pending approval (nobody has asked) OR **with** a pending
770
+ approval (someone asked and it's waiting on an owner). Only
771
+ \`pendingServiceApprovals\` distinguishes the two.
772
+
773
+ Each entry:
774
+
775
+ - \`service\` \u2014 \`protection\` (Wallet Blocking) or \`takedowns\`, matching the
776
+ \`services\` keys.
777
+ - \`automated\` \u2014 \`true\` when the request is for the automated variant
778
+ (e.g. "Automated Wallet Blocking").
779
+ - \`serviceType\` \u2014 raw enum, e.g. \`isProtectionActive\`.
780
+ - \`serviceName\` \u2014 human label, e.g. \`Wallet Blocking\`.
781
+ - \`requestedAt\` \u2014 when the enable request was submitted (ISO 8601, UTC).
782
+
783
+ An **empty array means nothing is awaiting approval** \u2014 read \`services\` for
784
+ the current on/off state, never infer it from this field.
785
+
704
786
  When a user asks about "automated reporting / reviewing / protection",
705
787
  explain that the flag exists in the DB but has no operational effect and
706
788
  isn't exposed by the public API \u2014 only \`takedowns.automated\` carries a
707
789
  real meaning.
708
790
 
709
- Filter flags (all optional, all comma-separated lists):
791
+ Filter flags (all optional, all comma-separated lists where noted):
710
792
 
711
793
  - \`--query <text>\` partial name match (substring, case-insensitive)
712
794
  - \`--subscription-status <list>\` one or more of \`PROSPECT\`, \`TRIAL\`,
@@ -719,6 +801,25 @@ Filter flags (all optional, all comma-separated lists):
719
801
  meaningful automation toggle. Passing any other service name errors out.
720
802
  - \`--service-manual <list>\` services whose automation must be OFF.
721
803
  Same restriction \u2014 only \`takedowns\`.
804
+ - \`--obligatory-approval-active\` \u2014 only orgs with admin approval on
805
+ - \`--obligatory-approval-inactive\` \u2014 only orgs with admin approval off
806
+ - \`--obligatory-approval-asset-type <list>\` \u2014 only orgs whose admin
807
+ approval scope covers EVERY listed asset type. The match treats an
808
+ org's empty per-asset-type list as "applies to all asset types", so a
809
+ fully-broad org matches every value passed here. Passing this flag
810
+ implies the feature is on, so it can't be combined with
811
+ \`--obligatory-approval-inactive\`. Use the canonical asset-type enum
812
+ names (\`TWITTER\`, \`URL\`, \`PAGE\`, \u2026); run \`chainpatrol asset types\`
813
+ to see them all.
814
+ - \`--pending-approval-active\` \u2014 only orgs with at least one open
815
+ service-enable approval awaiting a higher-up's sign-off.
816
+ - \`--pending-approval-inactive\` \u2014 only orgs with no pending approvals.
817
+ - \`--pending-approval-service <list>\` \u2014 only orgs with a pending approval
818
+ for one of the listed services. Accepts \`protection\` (Wallet Blocking)
819
+ and/or \`takedowns\` \u2014 those are the only approval-gated services.
820
+ Matches both the manual and automated variant. Passing this flag implies
821
+ a pending approval exists, so it can't be combined with
822
+ \`--pending-approval-inactive\`.
722
823
 
723
824
  Service names: \`reporting\`, \`reviewing\`, \`protection\`, \`takedowns\`,
724
825
  \`detection\`, \`darkWebMonitoring\`.
@@ -727,16 +828,58 @@ Customers see only orgs they're a member of. Staff/superuser sessions see
727
828
  every matching org. The response is the same in both cases; visibility is
728
829
  enforced server-side.
729
830
 
831
+ #### Use case: finding which orgs require admin approval for an asset type
832
+
833
+ When a user asks "which orgs require admin approval before blocking
834
+ Twitter assets?", reach straight for the filter \u2014 no client-side
835
+ post-processing needed:
836
+
837
+ \`\`\`bash
838
+ chainpatrol --json orgs list --obligatory-approval-asset-type TWITTER
839
+ \`\`\`
840
+
841
+ To audit just the broad opt-ins ("which orgs require approval for
842
+ everything?"), filter on the toggle and inspect \`assetTypes\` in the
843
+ output \u2014 an empty array means "all":
844
+
845
+ \`\`\`bash
846
+ chainpatrol --json orgs list --obligatory-approval-active \\
847
+ | jq '.organizations[] | select(.obligatoryAdminApproval.assetTypes | length == 0) | .slug'
848
+ \`\`\`
849
+
850
+ #### Use case: tracking orgs with a pending Wallet Blocking / Takedown approval
851
+
852
+ When a user asks "which organizations have a pending approval for Wallet
853
+ blocking or Takedown services?", do NOT reach for \`--service-inactive
854
+ protection\` \u2014 that lists orgs with the service turned OFF, which is a
855
+ different question. Use the pending-approval filter, which surfaces
856
+ staff-initiated enable requests still waiting on a higher-up:
857
+
858
+ \`\`\`bash
859
+ # Every org with a pending Wallet Blocking OR Takedown approval:
860
+ chainpatrol --json orgs list --pending-approval-service protection,takedowns
861
+
862
+ # Just Wallet Blocking:
863
+ chainpatrol --json orgs list --pending-approval-service protection
864
+
865
+ # Any pending approval at all, then read each org's pendingServiceApprovals:
866
+ chainpatrol --json orgs list --pending-approval-active \\
867
+ | jq '.organizations[] | {slug, pending: [.pendingServiceApprovals[].serviceName]}'
868
+ \`\`\`
869
+
730
870
  ### \`orgs get\` \u2014 Get a single organization by slug
731
871
 
732
872
  Look up one organization the caller has access to. Returns the same per-org
733
873
  shape as a single row from \`orgs list\` \u2014 \`active\` for every service,
734
874
  plus \`automated\` on \`takedowns\` only (see the note above on why other
735
- services don't expose an automation flag). Anything you could read from
736
- \`orgs list\` you can also read here without paging or filtering. Use it
737
- when the user names a specific customer ("show me acme's setup", "is
738
- takedowns automation on for morpho?") and you don't need the rest of the
739
- catalogue.
875
+ services don't expose an automation flag), plus \`obligatoryAdminApproval\`
876
+ (\`active\` + \`assetTypes\`) and \`pendingServiceApprovals\` (open Wallet
877
+ Blocking / Takedown enable requests awaiting a higher-up's sign-off).
878
+ Anything you could read from \`orgs list\` you can also read here without
879
+ paging or filtering. Use it when the user names a specific customer ("show
880
+ me acme's setup", "is takedowns automation on for morpho?", "does this org
881
+ require admin approval for Twitter?", "is morpho waiting on a Wallet
882
+ Blocking approval?") and you don't need the rest of the catalogue.
740
883
 
741
884
  \`\`\`bash
742
885
  chainpatrol orgs get <slug>
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  getCliVersion,
14
14
  isSkillInstalled,
15
15
  readInstalledSkillVersion
16
- } from "./chunk-3LJTWYT6.js";
16
+ } from "./chunk-LKITFBIG.js";
17
17
  import "./chunk-6VTWWGNV.js";
18
18
  import {
19
19
  DateTime
@@ -532,12 +532,16 @@ var HELP = {
532
532
  "--service-active <l> Comma-separated services that must be active",
533
533
  "--service-inactive <l> Comma-separated services that must be inactive",
534
534
  "--service-automated <l> Services whose automation must be ON (only `takedowns` is accepted \u2014 the other services have no meaningful automation toggle)",
535
- "--service-manual <l> Services whose automation must be OFF (only `takedowns` is accepted)"
535
+ "--service-manual <l> Services whose automation must be OFF (only `takedowns` is accepted)",
536
+ "--pending-approval-active Only orgs with an open service-enable approval awaiting a higher-up's sign-off",
537
+ "--pending-approval-inactive Only orgs with no pending service-enable approvals",
538
+ "--pending-approval-service <l> Only orgs with a pending approval for the listed service(s) \u2014 `protection` (Wallet Blocking) and/or `takedowns`"
536
539
  ],
537
540
  examples: [
538
541
  "chainpatrol orgs list --subscription-status ACTIVE",
539
542
  "chainpatrol orgs list --service-active takedowns --service-manual takedowns",
540
- "chainpatrol --json orgs list --subscription-status ACTIVE,TRIAL --service-active detection"
543
+ "chainpatrol --json orgs list --subscription-status ACTIVE,TRIAL --service-active detection",
544
+ "chainpatrol --json orgs list --pending-approval-service protection,takedowns"
541
545
  ]
542
546
  },
543
547
  "orgs get": {
@@ -916,6 +920,12 @@ ${getTopLevelHelp()}
916
920
  serviceInactive: { type: "string" },
917
921
  serviceAutomated: { type: "string" },
918
922
  serviceManual: { type: "string" },
923
+ obligatoryApprovalActive: { type: "boolean" },
924
+ obligatoryApprovalInactive: { type: "boolean" },
925
+ obligatoryApprovalAssetType: { type: "string" },
926
+ pendingApprovalActive: { type: "boolean" },
927
+ pendingApprovalInactive: { type: "boolean" },
928
+ pendingApprovalService: { type: "string" },
919
929
  query: { type: "string" },
920
930
  assetType: { type: "string" },
921
931
  takedownStatus: { type: "string" },
@@ -1558,7 +1568,7 @@ async function main() {
1558
1568
  }
1559
1569
  case "orgs": {
1560
1570
  if (subcommand === "list") {
1561
- const { runOrgsList } = await import("./list-QXA6GLCV.js");
1571
+ const { runOrgsList } = await import("./list-JK4SRMPL.js");
1562
1572
  await runOrgsList({
1563
1573
  query: cli.flags.query,
1564
1574
  subscriptionStatus: cli.flags.subscriptionStatus,
@@ -1566,6 +1576,12 @@ async function main() {
1566
1576
  serviceInactive: cli.flags.serviceInactive,
1567
1577
  serviceAutomated: cli.flags.serviceAutomated,
1568
1578
  serviceManual: cli.flags.serviceManual,
1579
+ obligatoryApprovalActive: cli.flags.obligatoryApprovalActive,
1580
+ obligatoryApprovalInactive: cli.flags.obligatoryApprovalInactive,
1581
+ obligatoryApprovalAssetType: cli.flags.obligatoryApprovalAssetType,
1582
+ pendingApprovalActive: cli.flags.pendingApprovalActive,
1583
+ pendingApprovalInactive: cli.flags.pendingApprovalInactive,
1584
+ pendingApprovalService: cli.flags.pendingApprovalService,
1569
1585
  json: jsonMode,
1570
1586
  outputFormat: cliContext.outputFormat
1571
1587
  });
@@ -1574,7 +1590,7 @@ async function main() {
1574
1590
  if (subcommand === "get") {
1575
1591
  const positionalSlug = action;
1576
1592
  const slug = positionalSlug ?? await tryResolveOrg();
1577
- const { runOrgsGet } = await import("./get-K7VHV5X2.js");
1593
+ const { runOrgsGet } = await import("./get-GTACVCLE.js");
1578
1594
  await runOrgsGet({
1579
1595
  slug,
1580
1596
  json: jsonMode,
@@ -1705,12 +1721,12 @@ async function main() {
1705
1721
  case "setup":
1706
1722
  case "install":
1707
1723
  case "i": {
1708
- const { setupSkill } = await import("./setup-skill-A36WQLMU.js");
1724
+ const { setupSkill } = await import("./setup-skill-V3HDWN7T.js");
1709
1725
  setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
1710
1726
  break;
1711
1727
  }
1712
1728
  case "uninstall": {
1713
- const { uninstallSkill } = await import("./setup-skill-A36WQLMU.js");
1729
+ const { uninstallSkill } = await import("./setup-skill-V3HDWN7T.js");
1714
1730
  uninstallSkill({ json: jsonMode });
1715
1731
  break;
1716
1732
  }
@@ -26,6 +26,18 @@ function summarizeServices(org) {
26
26
  if (org.services.darkWebMonitoring.active) parts.push("darkWebMonitoring");
27
27
  return parts.length > 0 ? parts.join(" ") : "(no services enabled)";
28
28
  }
29
+ function summarizeObligatoryAdminApproval(org) {
30
+ if (!org.obligatoryAdminApproval.active) return "off";
31
+ const types = org.obligatoryAdminApproval.assetTypes;
32
+ return types.length === 0 ? "on (all asset types)" : `on (${types.join(", ")})`;
33
+ }
34
+ function summarizePendingApprovals(org) {
35
+ if (org.pendingServiceApprovals.length === 0) return "none";
36
+ const labels = Array.from(
37
+ new Set(org.pendingServiceApprovals.map((approval) => approval.serviceName))
38
+ );
39
+ return labels.join(", ");
40
+ }
29
41
  async function runOrgsGet(options) {
30
42
  const slug = options.slug?.trim();
31
43
  if (!slug) {
@@ -44,13 +56,17 @@ async function runOrgsGet(options) {
44
56
  markdown: [
45
57
  `# ${organization.name} (\`${organization.slug}\`)`,
46
58
  "",
59
+ `- Contact URL: ${organization.contactUrl ?? "(not set)"}`,
47
60
  `- Subscription status: ${organization.subscriptionStatus}`,
48
- `- Services: ${summarizeServices(organization)}`
61
+ `- Services: ${summarizeServices(organization)}`,
62
+ `- Obligatory admin approval: ${summarizeObligatoryAdminApproval(organization)}`,
63
+ `- Pending service approvals: ${summarizePendingApprovals(organization)}`
49
64
  ].join("\n"),
50
65
  csv: toCsvRows([
51
66
  {
52
67
  slug: organization.slug,
53
68
  name: organization.name,
69
+ contactUrl: organization.contactUrl ?? "",
54
70
  subscriptionStatus: organization.subscriptionStatus,
55
71
  reportingActive: organization.services.reporting.active,
56
72
  reviewingActive: organization.services.reviewing.active,
@@ -59,13 +75,32 @@ async function runOrgsGet(options) {
59
75
  // `takedowns` is the only service with a meaningful automation flag.
60
76
  takedownsAutomated: organization.services.takedowns.automated,
61
77
  detectionActive: organization.services.detection.active,
62
- darkWebMonitoringActive: organization.services.darkWebMonitoring.active
78
+ darkWebMonitoringActive: organization.services.darkWebMonitoring.active,
79
+ obligatoryAdminApprovalActive: organization.obligatoryAdminApproval.active,
80
+ // Empty list + active=true means "applies to all asset types" — read
81
+ // the active column alongside this one to interpret.
82
+ obligatoryAdminApprovalAssetTypes: organization.obligatoryAdminApproval.assetTypes.join("|"),
83
+ // Pending staff requests to enable Wallet Blocking / Takedowns that a
84
+ // higher-up has not yet approved. Raw `serviceType` enum values,
85
+ // de-duplicated; an empty cell means nothing is awaiting approval.
86
+ pendingServiceApprovals: Array.from(
87
+ new Set(
88
+ organization.pendingServiceApprovals.map((approval) => approval.serviceType)
89
+ )
90
+ ).join("|")
63
91
  }
64
92
  ]),
65
93
  human: () => {
66
94
  console.log(`${organization.name} (slug: ${organization.slug})`);
95
+ console.log(` contactUrl: ${organization.contactUrl ?? "(not set)"}`);
67
96
  console.log(` subscriptionStatus: ${organization.subscriptionStatus}`);
68
97
  console.log(` services: ${summarizeServices(organization)}`);
98
+ console.log(
99
+ ` obligatoryAdminApproval: ${summarizeObligatoryAdminApproval(organization)}`
100
+ );
101
+ console.log(
102
+ ` pendingServiceApprovals: ${summarizePendingApprovals(organization)}`
103
+ );
69
104
  }
70
105
  });
71
106
  }
@@ -29,6 +29,7 @@ var SINGLE_SERVICES = [
29
29
  "darkWebMonitoring"
30
30
  ];
31
31
  var ALL_SERVICES = [...DUAL_SERVICES, ...SINGLE_SERVICES];
32
+ var PENDING_APPROVAL_SERVICES = ["protection", "takedowns"];
32
33
  function splitCsv(value) {
33
34
  if (!value) return [];
34
35
  return value.split(",").map((part) => part.trim()).filter(Boolean);
@@ -128,13 +129,83 @@ function summarizeServices(org) {
128
129
  }
129
130
  return parts.length > 0 ? parts.join(" ") : "(no services enabled)";
130
131
  }
132
+ function summarizeObligatoryAdminApproval(org) {
133
+ if (!org.obligatoryAdminApproval.active) return "off";
134
+ const types = org.obligatoryAdminApproval.assetTypes;
135
+ return types.length === 0 ? "on (all asset types)" : `on (${types.join(", ")})`;
136
+ }
137
+ function buildObligatoryAdminApprovalFilter(options) {
138
+ if (options.obligatoryApprovalActive && options.obligatoryApprovalInactive) {
139
+ throw new CliExitError(
140
+ "Conflicting flags: --obligatory-approval-active and --obligatory-approval-inactive can't both be passed.",
141
+ ExitCode.USAGE
142
+ );
143
+ }
144
+ const assetTypeStrings = splitCsv(options.obligatoryApprovalAssetType).map(
145
+ (value) => value.toUpperCase()
146
+ );
147
+ if (assetTypeStrings.length > 0 && options.obligatoryApprovalInactive) {
148
+ throw new CliExitError(
149
+ "Conflicting flags: --obligatory-approval-asset-type implies the feature is on, so it can't be combined with --obligatory-approval-inactive.",
150
+ ExitCode.USAGE
151
+ );
152
+ }
153
+ const filter = {};
154
+ if (options.obligatoryApprovalActive) filter.active = true;
155
+ if (options.obligatoryApprovalInactive) filter.active = false;
156
+ if (assetTypeStrings.length > 0) filter.assetTypes = assetTypeStrings;
157
+ return Object.keys(filter).length > 0 ? filter : void 0;
158
+ }
159
+ function parsePendingApprovalServices(raw) {
160
+ const parts = splitCsv(raw);
161
+ const result = [];
162
+ for (const part of parts) {
163
+ if (!PENDING_APPROVAL_SERVICES.includes(part)) {
164
+ throw new CliExitError(
165
+ `Unknown service '${part}' in --pending-approval-service. Only ${PENDING_APPROVAL_SERVICES.join(", ")} can require approval.`,
166
+ ExitCode.USAGE
167
+ );
168
+ }
169
+ result.push(part);
170
+ }
171
+ return result;
172
+ }
173
+ function buildPendingServiceApprovalFilter(options) {
174
+ if (options.pendingApprovalActive && options.pendingApprovalInactive) {
175
+ throw new CliExitError(
176
+ "Conflicting flags: --pending-approval-active and --pending-approval-inactive can't both be passed.",
177
+ ExitCode.USAGE
178
+ );
179
+ }
180
+ const services = parsePendingApprovalServices(options.pendingApprovalService);
181
+ if (services.length > 0 && options.pendingApprovalInactive) {
182
+ throw new CliExitError(
183
+ "Conflicting flags: --pending-approval-service implies a pending approval exists, so it can't be combined with --pending-approval-inactive.",
184
+ ExitCode.USAGE
185
+ );
186
+ }
187
+ const filter = {};
188
+ if (options.pendingApprovalActive) filter.active = true;
189
+ if (options.pendingApprovalInactive) filter.active = false;
190
+ if (services.length > 0) filter.services = services;
191
+ return Object.keys(filter).length > 0 ? filter : void 0;
192
+ }
193
+ function summarizePendingApprovals(org) {
194
+ if (org.pendingServiceApprovals.length === 0) return "none";
195
+ const labels = Array.from(
196
+ new Set(org.pendingServiceApprovals.map((approval) => approval.serviceName))
197
+ );
198
+ return labels.join(", ");
199
+ }
131
200
  async function runOrgsList(options) {
132
201
  const client = options.apiClient ?? createApiClient();
133
202
  const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
134
203
  const input = {
135
204
  query: options.query ?? "",
136
205
  subscriptionStatus: parseSubscriptionStatuses(options.subscriptionStatus),
137
- services: buildServicesFilter(options)
206
+ services: buildServicesFilter(options),
207
+ obligatoryAdminApproval: buildObligatoryAdminApprovalFilter(options),
208
+ pendingServiceApproval: buildPendingServiceApprovalFilter(options)
138
209
  };
139
210
  const result = await client.getUserOrgs(input);
140
211
  const organizations = result.organizations;
@@ -145,13 +216,14 @@ async function runOrgsList(options) {
145
216
  `# Organizations (${organizations.length})`,
146
217
  "",
147
218
  ...organizations.map(
148
- (org) => `- \`${org.slug}\` \u2014 ${org.name} \u2014 ${org.subscriptionStatus} \u2014 ${summarizeServices(org)}`
219
+ (org) => `- \`${org.slug}\` \u2014 ${org.name} \u2014 ${org.subscriptionStatus} \u2014 contact: ${org.contactUrl ?? "(not set)"} \u2014 ${summarizeServices(org)} \u2014 admin-approval: ${summarizeObligatoryAdminApproval(org)} \u2014 pending-approval: ${summarizePendingApprovals(org)}`
149
220
  )
150
221
  ].join("\n"),
151
222
  csv: toCsvRows(
152
223
  organizations.map((org) => ({
153
224
  slug: org.slug,
154
225
  name: org.name,
226
+ contactUrl: org.contactUrl ?? "",
155
227
  subscriptionStatus: org.subscriptionStatus,
156
228
  reportingActive: org.services.reporting.active,
157
229
  reviewingActive: org.services.reviewing.active,
@@ -160,7 +232,19 @@ async function runOrgsList(options) {
160
232
  // `takedowns` is the only service with a meaningful automation flag.
161
233
  takedownsAutomated: org.services.takedowns.automated,
162
234
  detectionActive: org.services.detection.active,
163
- darkWebMonitoringActive: org.services.darkWebMonitoring.active
235
+ darkWebMonitoringActive: org.services.darkWebMonitoring.active,
236
+ obligatoryAdminApprovalActive: org.obligatoryAdminApproval.active,
237
+ // Empty list + active means "applies to all asset types"; we keep
238
+ // the cell empty in CSV so spreadsheets render it as the natural
239
+ // "no narrowing" value — pair it with the active column.
240
+ obligatoryAdminApprovalAssetTypes: org.obligatoryAdminApproval.assetTypes.join("|"),
241
+ // Pending staff requests to enable Wallet Blocking / Takedowns that
242
+ // a higher-up has not yet approved. We emit the raw `serviceType`
243
+ // enum values (de-duplicated) so the column is machine-filterable;
244
+ // an empty cell means nothing is awaiting approval.
245
+ pendingServiceApprovals: Array.from(
246
+ new Set(org.pendingServiceApprovals.map((approval) => approval.serviceType))
247
+ ).join("|")
164
248
  }))
165
249
  ),
166
250
  human: () => {
@@ -172,8 +256,9 @@ async function runOrgsList(options) {
172
256
  const slugWidth = Math.max(...organizations.map((org) => org.slug.length), 4);
173
257
  const nameWidth = Math.max(...organizations.map((org) => org.name.length), 4);
174
258
  for (const org of organizations) {
259
+ const contact = org.contactUrl ?? "(no contact)";
175
260
  console.log(
176
- ` ${org.slug.padEnd(slugWidth)} ${org.name.padEnd(nameWidth)} ${org.subscriptionStatus.padEnd(11)} ${summarizeServices(org)}`
261
+ ` ${org.slug.padEnd(slugWidth)} ${org.name.padEnd(nameWidth)} ${org.subscriptionStatus.padEnd(11)} ${contact} ${summarizeServices(org)} admin-approval=${summarizeObligatoryAdminApproval(org)} pending-approval=${summarizePendingApprovals(org)}`
177
262
  );
178
263
  }
179
264
  }
@@ -6,7 +6,7 @@ import {
6
6
  readInstalledSkillVersion,
7
7
  setupSkill,
8
8
  uninstallSkill
9
- } from "./chunk-3LJTWYT6.js";
9
+ } from "./chunk-LKITFBIG.js";
10
10
  import "./chunk-6VTWWGNV.js";
11
11
  export {
12
12
  getBundledSkillContent,
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.20.0",
5
+ "version": "0.22.0",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://chainpatrol.com/docs/cli",
8
8
  "keywords": [