@chainpatrol/cli 0.20.0 → 0.21.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,31 @@
1
1
  # @chainpatrol/cli
2
2
 
3
+ ## 0.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b3e77eb: `orgs list` and `orgs get` now surface each organization's "Obligatory
8
+ Organization Admin Approval" state (`Organization.requiresCustomerReview`)
9
+ alongside its per-asset-type scope. The new response field is
10
+ `obligatoryAdminApproval: { active, assetTypes }` — an empty `assetTypes`
11
+ list with `active: true` means approval applies to every asset type
12
+ (matches the in-app UI's behaviour when no narrowing has been configured).
13
+
14
+ `orgs list` also accepts three new filters that compose with the
15
+ existing ones via AND:
16
+
17
+ - `--obligatory-approval-active` / `--obligatory-approval-inactive` —
18
+ filter on the toggle itself.
19
+ - `--obligatory-approval-asset-type <csv>` — return only orgs whose
20
+ approval scope covers every listed asset type (an org's empty
21
+ per-asset-type list still counts as "all types"). Passing this flag
22
+ implies the feature is on, so it can't be combined with
23
+ `--obligatory-approval-inactive`.
24
+
25
+ Server-side work in `@chainpatrol/external-trpc` and `@chainpatrol/validation`
26
+ extends the `/user/orgs` and `/user/orgs/{slug}` schemas accordingly; both
27
+ are private packages and don't need version bumps.
28
+
3
29
  ## 0.20.0
4
30
 
5
31
  ### Minor Changes
@@ -199,6 +199,12 @@ 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",
202
208
  "is this URL blocked", "is this domain blocked", "is this address blocked",
203
209
  "check this asset", "asset check", "lookup asset status",
204
210
  "what is ARCHIVE_ORG", "what does PAGE mean", "list asset types",
@@ -656,14 +662,17 @@ Guide. Key signals in the response:
656
662
 
657
663
  Use \`--all\` to snapshot every org you have access to instead of a single slug.
658
664
 
659
- ### \`orgs list\` \u2014 List organizations with subscription status and service toggles
665
+ ### \`orgs list\` \u2014 List organizations with subscription status, service toggles, and admin-approval scope
660
666
 
661
667
  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.
668
+ subscription status (\`PROSPECT\`, \`TRIAL\`, \`ACTIVE\`, \`INTEGRATION\`),
669
+ which services are active, and the per-org "Obligatory Organization
670
+ Admin Approval" toggle (with its asset-type scope). Use it to answer
671
+ questions like "which customers have takedowns enabled but automation
672
+ off?", "which prospects don't have detection turned on yet?", or "which
673
+ orgs require admin approval before adding Twitter assets to the
674
+ blocklist?" \u2014 filters compose with AND and are applied server-side, so
675
+ one call returns the final list.
667
676
 
668
677
  \`\`\`bash
669
678
  chainpatrol --json orgs list \\
@@ -686,6 +695,34 @@ misleading filters like "reporting.automated=true" that don't mean
686
695
  anything. \`detection\` and \`darkWebMonitoring\` have always been
687
696
  single-flag services.
688
697
 
698
+ #### Obligatory Organization Admin Approval
699
+
700
+ Each org also has a separate "Obligatory Organization Admin Approval"
701
+ toggle (\`Organization.requiresCustomerReview\` in the schema, surfaced
702
+ on the Services settings page in the app) that gates customer-side
703
+ review of proposals ChainPatrol staff have already approved before
704
+ they're added to the blocklist. It is NOT one of the operational
705
+ services above \u2014 it is an org-policy toggle with its own per-asset-type
706
+ scope.
707
+
708
+ Each org's response includes:
709
+
710
+ \`\`\`json
711
+ {
712
+ "obligatoryAdminApproval": {
713
+ "active": true,
714
+ "assetTypes": ["TWITTER", "URL"]
715
+ }
716
+ }
717
+ \`\`\`
718
+
719
+ - \`active=true\` + empty \`assetTypes\` means approval applies to
720
+ **all** asset types \u2014 the org has not narrowed the scope.
721
+ - \`active=true\` + a non-empty \`assetTypes\` list means approval is
722
+ required only for those asset types.
723
+ - \`active=false\` means no approval is required; \`assetTypes\` is
724
+ always empty in that case.
725
+
689
726
  So per-org JSON looks like:
690
727
 
691
728
  \`\`\`json
@@ -697,7 +734,8 @@ So per-org JSON looks like:
697
734
  "takedowns": { "active": true, "automated": false },
698
735
  "detection": { "active": true },
699
736
  "darkWebMonitoring": { "active": false }
700
- }
737
+ },
738
+ "obligatoryAdminApproval": { "active": true, "assetTypes": ["TWITTER"] }
701
739
  }
702
740
  \`\`\`
703
741
 
@@ -706,7 +744,7 @@ explain that the flag exists in the DB but has no operational effect and
706
744
  isn't exposed by the public API \u2014 only \`takedowns.automated\` carries a
707
745
  real meaning.
708
746
 
709
- Filter flags (all optional, all comma-separated lists):
747
+ Filter flags (all optional, all comma-separated lists where noted):
710
748
 
711
749
  - \`--query <text>\` partial name match (substring, case-insensitive)
712
750
  - \`--subscription-status <list>\` one or more of \`PROSPECT\`, \`TRIAL\`,
@@ -719,6 +757,16 @@ Filter flags (all optional, all comma-separated lists):
719
757
  meaningful automation toggle. Passing any other service name errors out.
720
758
  - \`--service-manual <list>\` services whose automation must be OFF.
721
759
  Same restriction \u2014 only \`takedowns\`.
760
+ - \`--obligatory-approval-active\` \u2014 only orgs with admin approval on
761
+ - \`--obligatory-approval-inactive\` \u2014 only orgs with admin approval off
762
+ - \`--obligatory-approval-asset-type <list>\` \u2014 only orgs whose admin
763
+ approval scope covers EVERY listed asset type. The match treats an
764
+ org's empty per-asset-type list as "applies to all asset types", so a
765
+ fully-broad org matches every value passed here. Passing this flag
766
+ implies the feature is on, so it can't be combined with
767
+ \`--obligatory-approval-inactive\`. Use the canonical asset-type enum
768
+ names (\`TWITTER\`, \`URL\`, \`PAGE\`, \u2026); run \`chainpatrol asset types\`
769
+ to see them all.
722
770
 
723
771
  Service names: \`reporting\`, \`reviewing\`, \`protection\`, \`takedowns\`,
724
772
  \`detection\`, \`darkWebMonitoring\`.
@@ -727,16 +775,36 @@ Customers see only orgs they're a member of. Staff/superuser sessions see
727
775
  every matching org. The response is the same in both cases; visibility is
728
776
  enforced server-side.
729
777
 
778
+ #### Use case: finding which orgs require admin approval for an asset type
779
+
780
+ When a user asks "which orgs require admin approval before blocking
781
+ Twitter assets?", reach straight for the filter \u2014 no client-side
782
+ post-processing needed:
783
+
784
+ \`\`\`bash
785
+ chainpatrol --json orgs list --obligatory-approval-asset-type TWITTER
786
+ \`\`\`
787
+
788
+ To audit just the broad opt-ins ("which orgs require approval for
789
+ everything?"), filter on the toggle and inspect \`assetTypes\` in the
790
+ output \u2014 an empty array means "all":
791
+
792
+ \`\`\`bash
793
+ chainpatrol --json orgs list --obligatory-approval-active \\
794
+ | jq '.organizations[] | select(.obligatoryAdminApproval.assetTypes | length == 0) | .slug'
795
+ \`\`\`
796
+
730
797
  ### \`orgs get\` \u2014 Get a single organization by slug
731
798
 
732
799
  Look up one organization the caller has access to. Returns the same per-org
733
800
  shape as a single row from \`orgs list\` \u2014 \`active\` for every service,
734
801
  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.
802
+ services don't expose an automation flag), plus \`obligatoryAdminApproval\`
803
+ (\`active\` + \`assetTypes\`). Anything you could read from \`orgs list\` you
804
+ can also read here without paging or filtering. Use it when the user
805
+ names a specific customer ("show me acme's setup", "is takedowns
806
+ automation on for morpho?", "does this org require admin approval for
807
+ Twitter?") and you don't need the rest of the catalogue.
740
808
 
741
809
  \`\`\`bash
742
810
  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-4N2YKTJ5.js";
17
17
  import "./chunk-6VTWWGNV.js";
18
18
  import {
19
19
  DateTime
@@ -916,6 +916,9 @@ ${getTopLevelHelp()}
916
916
  serviceInactive: { type: "string" },
917
917
  serviceAutomated: { type: "string" },
918
918
  serviceManual: { type: "string" },
919
+ obligatoryApprovalActive: { type: "boolean" },
920
+ obligatoryApprovalInactive: { type: "boolean" },
921
+ obligatoryApprovalAssetType: { type: "string" },
919
922
  query: { type: "string" },
920
923
  assetType: { type: "string" },
921
924
  takedownStatus: { type: "string" },
@@ -1558,7 +1561,7 @@ async function main() {
1558
1561
  }
1559
1562
  case "orgs": {
1560
1563
  if (subcommand === "list") {
1561
- const { runOrgsList } = await import("./list-QXA6GLCV.js");
1564
+ const { runOrgsList } = await import("./list-MHICJ2TK.js");
1562
1565
  await runOrgsList({
1563
1566
  query: cli.flags.query,
1564
1567
  subscriptionStatus: cli.flags.subscriptionStatus,
@@ -1566,6 +1569,9 @@ async function main() {
1566
1569
  serviceInactive: cli.flags.serviceInactive,
1567
1570
  serviceAutomated: cli.flags.serviceAutomated,
1568
1571
  serviceManual: cli.flags.serviceManual,
1572
+ obligatoryApprovalActive: cli.flags.obligatoryApprovalActive,
1573
+ obligatoryApprovalInactive: cli.flags.obligatoryApprovalInactive,
1574
+ obligatoryApprovalAssetType: cli.flags.obligatoryApprovalAssetType,
1569
1575
  json: jsonMode,
1570
1576
  outputFormat: cliContext.outputFormat
1571
1577
  });
@@ -1574,7 +1580,7 @@ async function main() {
1574
1580
  if (subcommand === "get") {
1575
1581
  const positionalSlug = action;
1576
1582
  const slug = positionalSlug ?? await tryResolveOrg();
1577
- const { runOrgsGet } = await import("./get-K7VHV5X2.js");
1583
+ const { runOrgsGet } = await import("./get-XN752KSL.js");
1578
1584
  await runOrgsGet({
1579
1585
  slug,
1580
1586
  json: jsonMode,
@@ -1705,12 +1711,12 @@ async function main() {
1705
1711
  case "setup":
1706
1712
  case "install":
1707
1713
  case "i": {
1708
- const { setupSkill } = await import("./setup-skill-A36WQLMU.js");
1714
+ const { setupSkill } = await import("./setup-skill-UEOROLCF.js");
1709
1715
  setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
1710
1716
  break;
1711
1717
  }
1712
1718
  case "uninstall": {
1713
- const { uninstallSkill } = await import("./setup-skill-A36WQLMU.js");
1719
+ const { uninstallSkill } = await import("./setup-skill-UEOROLCF.js");
1714
1720
  uninstallSkill({ json: jsonMode });
1715
1721
  break;
1716
1722
  }
@@ -26,6 +26,11 @@ 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
+ }
29
34
  async function runOrgsGet(options) {
30
35
  const slug = options.slug?.trim();
31
36
  if (!slug) {
@@ -45,7 +50,8 @@ async function runOrgsGet(options) {
45
50
  `# ${organization.name} (\`${organization.slug}\`)`,
46
51
  "",
47
52
  `- Subscription status: ${organization.subscriptionStatus}`,
48
- `- Services: ${summarizeServices(organization)}`
53
+ `- Services: ${summarizeServices(organization)}`,
54
+ `- Obligatory admin approval: ${summarizeObligatoryAdminApproval(organization)}`
49
55
  ].join("\n"),
50
56
  csv: toCsvRows([
51
57
  {
@@ -59,13 +65,20 @@ async function runOrgsGet(options) {
59
65
  // `takedowns` is the only service with a meaningful automation flag.
60
66
  takedownsAutomated: organization.services.takedowns.automated,
61
67
  detectionActive: organization.services.detection.active,
62
- darkWebMonitoringActive: organization.services.darkWebMonitoring.active
68
+ darkWebMonitoringActive: organization.services.darkWebMonitoring.active,
69
+ obligatoryAdminApprovalActive: organization.obligatoryAdminApproval.active,
70
+ // Empty list + active=true means "applies to all asset types" — read
71
+ // the active column alongside this one to interpret.
72
+ obligatoryAdminApprovalAssetTypes: organization.obligatoryAdminApproval.assetTypes.join("|")
63
73
  }
64
74
  ]),
65
75
  human: () => {
66
76
  console.log(`${organization.name} (slug: ${organization.slug})`);
67
77
  console.log(` subscriptionStatus: ${organization.subscriptionStatus}`);
68
78
  console.log(` services: ${summarizeServices(organization)}`);
79
+ console.log(
80
+ ` obligatoryAdminApproval: ${summarizeObligatoryAdminApproval(organization)}`
81
+ );
69
82
  }
70
83
  });
71
84
  }
@@ -128,13 +128,41 @@ function summarizeServices(org) {
128
128
  }
129
129
  return parts.length > 0 ? parts.join(" ") : "(no services enabled)";
130
130
  }
131
+ function summarizeObligatoryAdminApproval(org) {
132
+ if (!org.obligatoryAdminApproval.active) return "off";
133
+ const types = org.obligatoryAdminApproval.assetTypes;
134
+ return types.length === 0 ? "on (all asset types)" : `on (${types.join(", ")})`;
135
+ }
136
+ function buildObligatoryAdminApprovalFilter(options) {
137
+ if (options.obligatoryApprovalActive && options.obligatoryApprovalInactive) {
138
+ throw new CliExitError(
139
+ "Conflicting flags: --obligatory-approval-active and --obligatory-approval-inactive can't both be passed.",
140
+ ExitCode.USAGE
141
+ );
142
+ }
143
+ const assetTypeStrings = splitCsv(options.obligatoryApprovalAssetType).map(
144
+ (value) => value.toUpperCase()
145
+ );
146
+ if (assetTypeStrings.length > 0 && options.obligatoryApprovalInactive) {
147
+ throw new CliExitError(
148
+ "Conflicting flags: --obligatory-approval-asset-type implies the feature is on, so it can't be combined with --obligatory-approval-inactive.",
149
+ ExitCode.USAGE
150
+ );
151
+ }
152
+ const filter = {};
153
+ if (options.obligatoryApprovalActive) filter.active = true;
154
+ if (options.obligatoryApprovalInactive) filter.active = false;
155
+ if (assetTypeStrings.length > 0) filter.assetTypes = assetTypeStrings;
156
+ return Object.keys(filter).length > 0 ? filter : void 0;
157
+ }
131
158
  async function runOrgsList(options) {
132
159
  const client = options.apiClient ?? createApiClient();
133
160
  const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
134
161
  const input = {
135
162
  query: options.query ?? "",
136
163
  subscriptionStatus: parseSubscriptionStatuses(options.subscriptionStatus),
137
- services: buildServicesFilter(options)
164
+ services: buildServicesFilter(options),
165
+ obligatoryAdminApproval: buildObligatoryAdminApprovalFilter(options)
138
166
  };
139
167
  const result = await client.getUserOrgs(input);
140
168
  const organizations = result.organizations;
@@ -145,7 +173,7 @@ async function runOrgsList(options) {
145
173
  `# Organizations (${organizations.length})`,
146
174
  "",
147
175
  ...organizations.map(
148
- (org) => `- \`${org.slug}\` \u2014 ${org.name} \u2014 ${org.subscriptionStatus} \u2014 ${summarizeServices(org)}`
176
+ (org) => `- \`${org.slug}\` \u2014 ${org.name} \u2014 ${org.subscriptionStatus} \u2014 ${summarizeServices(org)} \u2014 admin-approval: ${summarizeObligatoryAdminApproval(org)}`
149
177
  )
150
178
  ].join("\n"),
151
179
  csv: toCsvRows(
@@ -160,7 +188,12 @@ async function runOrgsList(options) {
160
188
  // `takedowns` is the only service with a meaningful automation flag.
161
189
  takedownsAutomated: org.services.takedowns.automated,
162
190
  detectionActive: org.services.detection.active,
163
- darkWebMonitoringActive: org.services.darkWebMonitoring.active
191
+ darkWebMonitoringActive: org.services.darkWebMonitoring.active,
192
+ obligatoryAdminApprovalActive: org.obligatoryAdminApproval.active,
193
+ // Empty list + active means "applies to all asset types"; we keep
194
+ // the cell empty in CSV so spreadsheets render it as the natural
195
+ // "no narrowing" value — pair it with the active column.
196
+ obligatoryAdminApprovalAssetTypes: org.obligatoryAdminApproval.assetTypes.join("|")
164
197
  }))
165
198
  ),
166
199
  human: () => {
@@ -173,7 +206,7 @@ async function runOrgsList(options) {
173
206
  const nameWidth = Math.max(...organizations.map((org) => org.name.length), 4);
174
207
  for (const org of organizations) {
175
208
  console.log(
176
- ` ${org.slug.padEnd(slugWidth)} ${org.name.padEnd(nameWidth)} ${org.subscriptionStatus.padEnd(11)} ${summarizeServices(org)}`
209
+ ` ${org.slug.padEnd(slugWidth)} ${org.name.padEnd(nameWidth)} ${org.subscriptionStatus.padEnd(11)} ${summarizeServices(org)} admin-approval=${summarizeObligatoryAdminApproval(org)}`
177
210
  );
178
211
  }
179
212
  }
@@ -6,7 +6,7 @@ import {
6
6
  readInstalledSkillVersion,
7
7
  setupSkill,
8
8
  uninstallSkill
9
- } from "./chunk-3LJTWYT6.js";
9
+ } from "./chunk-4N2YKTJ5.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.21.0",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://chainpatrol.com/docs/cli",
8
8
  "keywords": [