@chainpatrol/cli 0.4.1 → 0.6.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 +31 -0
- package/dist/{breakdown-EBSACUST.js → breakdown-AX6QNTQH.js} +1 -1
- package/dist/{chunk-PIYOWGBZ.js → chunk-AGXMZFUU.js} +1 -1
- package/dist/{chunk-MXUZR2BV.js → chunk-LLWKCA3H.js} +3 -0
- package/dist/{chunk-F4GU6AII.js → chunk-XOXQPUR6.js} +165 -33
- package/dist/cli.js +74 -21
- package/dist/{configs-update-RPN32YTL.js → configs-update-VROBC2HI.js} +1 -1
- package/dist/{create-QP3M7EZM.js → create-XTCUNT2C.js} +1 -1
- package/dist/{drift-DZ6A7JL5.js → drift-VOKQJ36G.js} +1 -1
- package/dist/{found-AOPBSLRD.js → found-A5HRTJCJ.js} +1 -1
- package/dist/{healthcheck-KAONRGSS.js → healthcheck-AQUXVKAO.js} +1 -1
- package/dist/{list-GEMCFDD5.js → list-5ENZAOFL.js} +1 -1
- package/dist/list-CGRHTFAS.js +182 -0
- package/dist/{list-MWDFCHMJ.js → list-CVFXTKNX.js} +1 -1
- package/dist/{list-LN6NOZIJ.js → list-EYRN5JYC.js} +2 -2
- package/dist/{list-UW63DIKX.js → list-PLZ67PNY.js} +1 -1
- package/dist/{list-json-WTMYLZGY.js → list-json-LEKCCWQU.js} +1 -1
- package/dist/{run-7THXM7GF.js → run-MS5SA5YL.js} +14 -2
- package/dist/{run-43CC5AXR.js → run-OT2X46GT.js} +1 -1
- package/dist/{run-MH5RYPWA.js → run-YHDUUP66.js} +2 -2
- package/dist/{setup-skill-2SEVH3M6.js → setup-skill-BTR2IZ4E.js} +1 -1
- package/dist/{snapshot-E3TPZOKT.js → snapshot-4QR4I67P.js} +1 -1
- package/dist/{summary-6NCA7PDP.js → summary-JOCABBCO.js} +1 -1
- package/dist/{validate-BJFEKI2N.js → validate-27RUCN7R.js} +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @chainpatrol/cli
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 575b671: Add `chainpatrol orgs list` — lists organizations accessible to the caller along with each org's subscription status and active/automated service flags (reporting, reviewing, protection, takedowns, detection, dark web monitoring). Filter flags compose with AND and are applied server-side:
|
|
8
|
+
|
|
9
|
+
- `--subscription-status <list>` — comma-separated `PROSPECT`, `TRIAL`, `ACTIVE`, `INTEGRATION` (`INACTIVE` is intentionally not reachable through this filter)
|
|
10
|
+
- `--service-active <list>` / `--service-inactive <list>` — services that must be active / inactive
|
|
11
|
+
- `--service-automated <list>` / `--service-manual <list>` — services whose automation must be on / off (`reporting`, `reviewing`, `protection`, `takedowns`)
|
|
12
|
+
- `--query <text>` — partial name match
|
|
13
|
+
|
|
14
|
+
Use cases: "which ACTIVE customers have takedowns enabled but automation off", "which prospects don't have detection turned on yet". The bundled CLI skill gets a new `orgs list` section under the `queues snapshot` heading.
|
|
15
|
+
|
|
16
|
+
## 0.5.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- f8d9454: Add five new healthchecks covering the takedown pipeline and asset liveness, expanding the runnable surface for `chainpatrol healthchecks run`:
|
|
21
|
+
|
|
22
|
+
- **`takedowns.todo-volume`** — counts takedowns sitting in TODO. Default warn=50, fail=100. Catches automation gaps on new threat surfaces or manual-filing capacity issues.
|
|
23
|
+
- **`takedowns.in-progress-volume`** — counts takedowns currently IN*PROGRESS regardless of age. Default warn=30, fail=75. Complements `takedowns.stale-in-progress` (age-based) by catching submission-format / vendor-side issues \_before* items go stale.
|
|
24
|
+
- **`takedowns.cancelled-count`** — counts transitions into CANCELLED from the `TakedownEvent` log over a rolling window. Default 7d / warn=3 / fail=10. Replaces the placeholder `takedowns.cancelled-rate` registry entry. Uses the event log rather than `Takedown.updatedAt` so the cancellation date is correct even if the takedown has since been edited.
|
|
25
|
+
- **`takedowns.automation-off`** — flags orgs with takedown service enabled but `isAutomatedTakedownsActive` off for too long. Default warn=30d, fail=60d. Age is derived from the most recent `SERVICES_AUTOMATED_TAKEDOWNS_UPDATED` `OrganizationEvent`, falling back to `Organization.updatedAt`. Orgs with takedown service entirely disabled are skipped.
|
|
26
|
+
- **`assets.dead-asset-spike`** — compares `DETECTED_AS_DEAD` events in the current window against the prior baseline rate; warns on a multiplier exceeding the threshold (default 24h vs 7d, ×2 warn / ×4 fail) once the current count clears the `minSpikeCount` floor. Catches liveness-checker regressions after platform changes.
|
|
27
|
+
|
|
28
|
+
The bundled CLI skill gets a rewritten **Takedowns** section covering all four takedown checks and a new top-level **Assets** section covering `dead-asset-spike` plus guidance on the still-unimplemented `assets.dead-but-alive` / `assets.alive-but-marked-dead` (which require live HTTP probes and are not a fit for synchronous healthchecks).
|
|
29
|
+
|
|
30
|
+
The public `HealthcheckResult.category` enum gains `"assets"`. `observed` and `threshold` records now also accept booleans and null (for fields like `automatedTakedownsActive` and the nullable `ratio` on the spike check).
|
|
31
|
+
|
|
32
|
+
`HealthcheckResult` also gains an `appUrl` field (string or null) that deep-links to the relevant filtered admin page in the web app. For example, `takedowns.stale-in-progress` returns `https://app.chainpatrol.io/admin/<slug>/takedowns?takedownStatus=IN_PROGRESS&sortBy=oldest` and `reviewing.backlog` returns `https://app.chainpatrol.io/admin/<slug>/review?excludeWatchlisted=true`. The CLI prints `View in app: <url>` after each result. Checks without a sensible filtered view (`detections.silent-configs`, `assets.dead-asset-spike`) return `null`. The base URL respects `BETTER_AUTH_URL` and falls back to `https://app.chainpatrol.io`.
|
|
33
|
+
|
|
3
34
|
## 0.4.1
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
|
@@ -82,7 +82,9 @@ description: |
|
|
|
82
82
|
"am I logged in", "list configs", "use the cli", "list reports",
|
|
83
83
|
"customer reports", "reports reported by customer", "find detection gaps",
|
|
84
84
|
"org healthcheck", "organization health check", "audit my org",
|
|
85
|
-
"what's wrong with org", "review org setup"
|
|
85
|
+
"what's wrong with org", "review org setup", "list orgs", "list organizations",
|
|
86
|
+
"orgs with takedowns off", "automation off across orgs",
|
|
87
|
+
"which customers have X enabled", "service toggles by org".
|
|
86
88
|
allowed-tools:
|
|
87
89
|
- Bash
|
|
88
90
|
- Read
|
|
@@ -321,10 +323,13 @@ chainpatrol --json healthchecks run reviewing.backlog --org <slug>
|
|
|
321
323
|
chainpatrol --json healthchecks run --all --org <slug>
|
|
322
324
|
\`\`\`
|
|
323
325
|
|
|
324
|
-
Each implemented check has a stable id of the form \`category.name
|
|
325
|
-
\`detections.silent-configs\`, \`reviewing.backlog\`,
|
|
326
|
+
Each implemented check has a stable id of the form \`category.name\`. Implemented
|
|
327
|
+
ids today: \`detections.silent-configs\`, \`reviewing.backlog\`,
|
|
326
328
|
\`reviewing.old-proposals\`, \`reviewing.watchlist-backlog\`,
|
|
327
|
-
\`reviewing.watchlist-old\`, \`takedowns.
|
|
329
|
+
\`reviewing.watchlist-old\`, \`takedowns.todo-volume\`,
|
|
330
|
+
\`takedowns.in-progress-volume\`, \`takedowns.stale-in-progress\`,
|
|
331
|
+
\`takedowns.cancelled-count\`, \`takedowns.automation-off\`,
|
|
332
|
+
\`assets.dead-asset-spike\`.
|
|
328
333
|
|
|
329
334
|
### Pending proposals: "Needs Review" vs "Watchlisted"
|
|
330
335
|
|
|
@@ -344,6 +349,15 @@ grade them with separate checks:
|
|
|
344
349
|
severity is **capped at warn** so they never block on the same SLA as
|
|
345
350
|
Needs Review. When reporting findings, treat these as lower-priority.
|
|
346
351
|
|
|
352
|
+
Each healthcheck result includes an \`appUrl\` field (string or null) that
|
|
353
|
+
deep-links to the relevant filtered admin page in the web app \u2014 e.g. the
|
|
354
|
+
takedowns page filtered to IN_PROGRESS for \`takedowns.stale-in-progress\`,
|
|
355
|
+
or the review page filtered to oldest pending for \`reviewing.old-proposals\`.
|
|
356
|
+
**When reporting a non-OK healthcheck to the user, always surface the
|
|
357
|
+
\`appUrl\` so they can jump straight to the right view.** Some checks
|
|
358
|
+
(\`detections.silent-configs\`, \`assets.dead-asset-spike\`) emit \`null\`
|
|
359
|
+
because no filterable list page exists for that signal yet.
|
|
360
|
+
|
|
347
361
|
Implemented checks today:
|
|
348
362
|
|
|
349
363
|
- **detections.silent-configs** \u2014 equivalent to \`detections healthcheck\`,
|
|
@@ -358,8 +372,27 @@ Implemented checks today:
|
|
|
358
372
|
- **reviewing.watchlist-old** \u2014 counts watchlisted pending proposals older
|
|
359
373
|
than the warn-age threshold (default 30 days) and lists the oldest.
|
|
360
374
|
Severity capped at warn.
|
|
375
|
+
- **takedowns.todo-volume** \u2014 counts takedowns in TODO (default warn=50,
|
|
376
|
+
fail=100). Pile-ups here usually mean an automation gap on a new threat
|
|
377
|
+
surface, or manual-filing capacity issues.
|
|
378
|
+
- **takedowns.in-progress-volume** \u2014 counts takedowns currently IN_PROGRESS
|
|
379
|
+
regardless of age (default warn=30, fail=75). Complements
|
|
380
|
+
\`stale-in-progress\` \u2014 a high count signals vendor-side or
|
|
381
|
+
submission-format problems even before items go stale.
|
|
361
382
|
- **takedowns.stale-in-progress** \u2014 counts takedowns sitting in IN_PROGRESS
|
|
362
383
|
past the staleness threshold (default 7 days) and lists the oldest.
|
|
384
|
+
- **takedowns.cancelled-count** \u2014 counts CANCELLED transitions from the
|
|
385
|
+
TakedownEvent log over a rolling window (default 7d, warn=3, fail=10).
|
|
386
|
+
Cancellations should be rare; a spike usually means a proposal-funnel
|
|
387
|
+
quality problem or misuse of the CANCELLED status.
|
|
388
|
+
- **takedowns.automation-off** \u2014 flags orgs with takedown service enabled
|
|
389
|
+
but \`isAutomatedTakedownsActive\` off for too long (default warn=30d,
|
|
390
|
+
fail=60d). Skipped for orgs with takedown service entirely disabled.
|
|
391
|
+
- **assets.dead-asset-spike** \u2014 compares DEAD-detection events in the
|
|
392
|
+
current window against the prior baseline rate; warns on a multiplier
|
|
393
|
+
exceeding the threshold (default 24h vs 7d, \xD72 warn / \xD74 fail) once the
|
|
394
|
+
current count clears the \`minSpikeCount\` floor. Catches liveness-checker
|
|
395
|
+
regressions after platform changes.
|
|
363
396
|
|
|
364
397
|
The following checks are listed by \`healthchecks list\` (\`implemented: false\`)
|
|
365
398
|
but **not yet implemented on the backend** \u2014 when the agent surfaces them in
|
|
@@ -373,8 +406,11 @@ a healthcheck report, mark them explicitly as "manual check, no API yet":
|
|
|
373
406
|
human approvers in the review history. Use \`metrics breakdown\` as a proxy.
|
|
374
407
|
- **blocklisting.gsb-cancelled-rate** \u2014 Google Safe Browsing submission state
|
|
375
408
|
is not yet exposed in the public API.
|
|
376
|
-
- **
|
|
377
|
-
|
|
409
|
+
- **assets.dead-but-alive** / **assets.alive-but-marked-dead** \u2014 require live
|
|
410
|
+
HTTP probes against asset URLs, which is not a synchronous-healthcheck
|
|
411
|
+
shape. Until a dedicated probe command exists, sample a handful manually
|
|
412
|
+
from \`assets list --status DEAD\` (or ALIVE) and verify in a browser. Notify
|
|
413
|
+
ChainPatrol engineering if the liveness checker looks miscalibrated.
|
|
378
414
|
|
|
379
415
|
When the user asks to "run a healthcheck on org X", the canonical command is:
|
|
380
416
|
|
|
@@ -409,6 +445,51 @@ Guide. Key signals in the response:
|
|
|
409
445
|
|
|
410
446
|
Use \`--all\` to snapshot every org you have access to instead of a single slug.
|
|
411
447
|
|
|
448
|
+
### \`orgs list\` \u2014 List organizations with subscription status and service toggles
|
|
449
|
+
|
|
450
|
+
Returns every organization the caller can see, with each org's
|
|
451
|
+
subscription status (\`PROSPECT\`, \`TRIAL\`, \`ACTIVE\`, \`INTEGRATION\`) and
|
|
452
|
+
which services are active and automated. Use it to answer questions like
|
|
453
|
+
"which customers have takedowns enabled but automation off?" or "which
|
|
454
|
+
prospects don't have detection turned on yet?" \u2014 filters compose with AND
|
|
455
|
+
and are applied server-side, so one call returns the final list.
|
|
456
|
+
|
|
457
|
+
\`\`\`bash
|
|
458
|
+
chainpatrol --json orgs list \\
|
|
459
|
+
--subscription-status ACTIVE \\
|
|
460
|
+
--service-active takedowns \\
|
|
461
|
+
--service-manual takedowns
|
|
462
|
+
\`\`\`
|
|
463
|
+
|
|
464
|
+
Filter flags (all optional, all comma-separated lists):
|
|
465
|
+
|
|
466
|
+
- \`--query <text>\` partial name match (substring, case-insensitive)
|
|
467
|
+
- \`--subscription-status <list>\` one or more of \`PROSPECT\`, \`TRIAL\`,
|
|
468
|
+
\`ACTIVE\`, \`INTEGRATION\`. \`INACTIVE\` is intentionally not reachable
|
|
469
|
+
through this filter \u2014 \`orgs list\` only ever returns live customers.
|
|
470
|
+
- \`--service-active <list>\` services that must be active
|
|
471
|
+
- \`--service-inactive <list>\` services that must be inactive
|
|
472
|
+
- \`--service-automated <list>\` services whose automation must be ON
|
|
473
|
+
(\`reporting\`, \`reviewing\`, \`protection\`, \`takedowns\` \u2014 the four
|
|
474
|
+
with an automation toggle)
|
|
475
|
+
- \`--service-manual <list>\` services whose automation must be OFF
|
|
476
|
+
(same four)
|
|
477
|
+
|
|
478
|
+
Service names: \`reporting\`, \`reviewing\`, \`protection\`, \`takedowns\`,
|
|
479
|
+
\`detection\`, \`darkWebMonitoring\`.
|
|
480
|
+
|
|
481
|
+
Customers see only orgs they're a member of. Staff/superuser sessions see
|
|
482
|
+
every matching org. The response is the same in both cases; visibility is
|
|
483
|
+
enforced server-side.
|
|
484
|
+
|
|
485
|
+
#### Use case: finding service configuration gaps across the customer base
|
|
486
|
+
|
|
487
|
+
When the user asks something like "which customers are paying us but don't
|
|
488
|
+
have takedowns automated yet?" or "any orgs running detection without
|
|
489
|
+
takedowns?", reach for \`orgs list\` \u2014 it's the only command that exposes
|
|
490
|
+
service flags across multiple orgs in one call. Run it in \`--json\` mode
|
|
491
|
+
and summarize patterns by service or by subscription tier.
|
|
492
|
+
|
|
412
493
|
### \`metrics summary | found | breakdown\` \u2014 Org metrics for spike/drop analysis
|
|
413
494
|
|
|
414
495
|
\`\`\`bash
|
|
@@ -775,19 +856,23 @@ exposed in the public API, this remains a manual / engineering-team check.
|
|
|
775
856
|
|
|
776
857
|
### Takedowns
|
|
777
858
|
|
|
859
|
+
The takedown pipeline has three stages \u2014 TODO (queued, not yet filed),
|
|
860
|
+
IN_PROGRESS (filed, waiting on vendor / customer / refile), and a terminal
|
|
861
|
+
state (COMPLETED or CANCELLED). Healthchecks cover pile-ups at each stage,
|
|
862
|
+
plus quality/configuration issues.
|
|
863
|
+
|
|
778
864
|
#### Too Many Takedowns in ToDo
|
|
779
865
|
|
|
780
866
|
Can mean a gap in automated takedowns not being implemented for some new area
|
|
781
867
|
of threats. It can also mean the areas that require manual takedowns are
|
|
782
868
|
being missed by the takedown team.
|
|
783
869
|
|
|
784
|
-
**Run via CLI:** **
|
|
785
|
-
in
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
\`metrics summary\` for that baseline) is the finding.
|
|
870
|
+
**Run via CLI:** **Implemented as \`healthchecks run takedowns.todo-volume\`.**
|
|
871
|
+
Counts takedowns sitting in TODO (default warn=50, fail=100). For raw
|
|
872
|
+
breakdowns by type, cross-reference with
|
|
873
|
+
\`chainpatrol --json metrics breakdown --org <slug> --by assetType\` \u2014
|
|
874
|
+
items piled up on a specific platform usually point at an automation
|
|
875
|
+
gap there.
|
|
791
876
|
|
|
792
877
|
#### Too Many Takedowns In Progress
|
|
793
878
|
|
|
@@ -795,12 +880,16 @@ Typically means something is wrong with the submission itself. The takedown
|
|
|
795
880
|
may need to be resubmitted, the vendor asked for more evidence, or we may
|
|
796
881
|
have submitted it in the wrong place.
|
|
797
882
|
|
|
798
|
-
**Run via CLI:**
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
883
|
+
**Run via CLI:** Two checks, complementary:
|
|
884
|
+
|
|
885
|
+
- **\`healthchecks run takedowns.in-progress-volume\`** \u2014 counts all
|
|
886
|
+
IN_PROGRESS takedowns regardless of age (default warn=30, fail=75).
|
|
887
|
+
Catches a vendor-side or submission-format problem before items go
|
|
888
|
+
stale.
|
|
889
|
+
- **\`healthchecks run takedowns.stale-in-progress\`** \u2014 counts IN_PROGRESS
|
|
890
|
+
takedowns past a staleness threshold (default 7 days), lists the oldest
|
|
891
|
+
offenders. Any non-zero value is worth investigating; a growing count
|
|
892
|
+
across snapshots strongly suggests vendor-side or format issues.
|
|
804
893
|
|
|
805
894
|
#### Too Many Cancelled Takedowns
|
|
806
895
|
|
|
@@ -809,14 +898,13 @@ do this takedown" for some reason. Cases like adding an item to the blocklist
|
|
|
809
898
|
when it's already taken down are treated as completed, not cancelled. So even
|
|
810
899
|
3 cancelled takedowns in a 7-day period is too many.
|
|
811
900
|
|
|
812
|
-
**Run via CLI:** **
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
cancellations.
|
|
901
|
+
**Run via CLI:** **Implemented as \`healthchecks run takedowns.cancelled-count\`.**
|
|
902
|
+
Counts transitions into the CANCELLED status from the TakedownEvent log
|
|
903
|
+
within the lookback window (default 7 days, warn=3, fail=10). The check
|
|
904
|
+
uses the event log rather than \`Takedown.updatedAt\` so it correctly
|
|
905
|
+
attributes the cancellation date even if the takedown has since been
|
|
906
|
+
edited. A spike usually means a quality problem in the proposal funnel
|
|
907
|
+
or the CANCELLED status being used as a catch-all.
|
|
820
908
|
|
|
821
909
|
#### Automated Takedowns Turned Off for Over 30 Days
|
|
822
910
|
|
|
@@ -824,12 +912,56 @@ Automated takedowns should be on by default for nearly every organization.
|
|
|
824
912
|
Any issue that would make you want to turn off automated takedowns should be
|
|
825
913
|
resolved within 30 days.
|
|
826
914
|
|
|
827
|
-
**Run via CLI:** **
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
915
|
+
**Run via CLI:** **Implemented as \`healthchecks run takedowns.automation-off\`.**
|
|
916
|
+
Checks \`Organization.isAutomatedTakedownsActive\` and derives the
|
|
917
|
+
off-duration from the most recent \`SERVICES_AUTOMATED_TAKEDOWNS_UPDATED\`
|
|
918
|
+
entry in \`OrganizationEvent\` (default warn 30 days, fail 60 days). Orgs
|
|
919
|
+
with takedown service entirely disabled (\`isTakedownsActive=0\`) are
|
|
920
|
+
skipped \u2014 automation being off is implied in that case.
|
|
921
|
+
|
|
922
|
+
### Assets
|
|
923
|
+
|
|
924
|
+
Healthchecks on the asset model \u2014 specifically asset liveness state, which
|
|
925
|
+
the takedown team depends on for follow-up.
|
|
926
|
+
|
|
927
|
+
#### Spike in Recently Dead Assets
|
|
928
|
+
|
|
929
|
+
A sudden spike in DEAD detections can be a good signal (takedowns or platform
|
|
930
|
+
moderation working) but it can also mean the liveness checker is
|
|
931
|
+
misclassifying assets after a platform change, captcha rollout, or anti-bot
|
|
932
|
+
update. If many assets become dead at once, sample a few manually.
|
|
933
|
+
|
|
934
|
+
**Run via CLI:** **Implemented as \`healthchecks run assets.dead-asset-spike\`.**
|
|
935
|
+
Compares \`DETECTED_AS_DEAD\` events in the current window (default 24h)
|
|
936
|
+
against the baseline rate from the prior \`baselineDays\` (default 7d).
|
|
937
|
+
Severity fires only when the current count clears \`minSpikeCount\` (default
|
|
938
|
+
10) AND exceeds the multiplier (default warn \xD72, fail \xD74). The
|
|
939
|
+
\`minSpikeCount\` floor suppresses noise on orgs with near-zero baseline
|
|
940
|
+
activity. When this fires, pull a sample of recent DEAD assets, verify a
|
|
941
|
+
few in a browser, and notify ChainPatrol engineering if the sample is
|
|
942
|
+
clearly still live.
|
|
943
|
+
|
|
944
|
+
#### Assets Marked Dead but Still Online / Assets Not Marked Dead Even Though They Are Down
|
|
945
|
+
|
|
946
|
+
These two opposite failure modes are **not implemented as healthchecks**
|
|
947
|
+
(listed as \`assets.dead-but-alive\` and \`assets.alive-but-marked-dead\` with
|
|
948
|
+
\`implemented: false\`). Both require live HTTP probes against asset URLs,
|
|
949
|
+
which is not a synchronous-healthcheck shape.
|
|
950
|
+
|
|
951
|
+
Until a dedicated probe command exists:
|
|
952
|
+
|
|
953
|
+
- For "marked dead but still alive": sample a handful of recently-DEAD
|
|
954
|
+
assets, open them in a browser, and watch for any that load. Common
|
|
955
|
+
causes: bot protection, geo-blocking, rate limits, or liveness logic
|
|
956
|
+
that does not handle the asset type correctly.
|
|
957
|
+
- For "alive but marked dead": after a known takedown event, sample
|
|
958
|
+
assets that *should* be dead but are still marked alive. Common causes:
|
|
959
|
+
cached responses, soft-404 pages, parked-domain redirects, platform
|
|
960
|
+
suspension pages still returning 200.
|
|
961
|
+
|
|
962
|
+
In both cases, if liveness looks miscalibrated for a class of assets,
|
|
963
|
+
notify ChainPatrol engineering \u2014 the checker likely needs a tuning pass for
|
|
964
|
+
that platform.
|
|
833
965
|
`;
|
|
834
966
|
}
|
|
835
967
|
function getBundledSkillVersion() {
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getCliVersion,
|
|
14
14
|
isSkillInstalled,
|
|
15
15
|
readInstalledSkillVersion
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XOXQPUR6.js";
|
|
17
17
|
import "./chunk-IUZB3DQW.js";
|
|
18
18
|
import {
|
|
19
19
|
DateTime
|
|
@@ -281,6 +281,31 @@ var HELP = {
|
|
|
281
281
|
"--window-hours <n> Hours used for staleness windows"
|
|
282
282
|
]
|
|
283
283
|
},
|
|
284
|
+
orgs: {
|
|
285
|
+
description: "List organizations accessible to you with subscription status and service toggles.",
|
|
286
|
+
usage: "chainpatrol orgs list [filters]",
|
|
287
|
+
examples: [
|
|
288
|
+
"chainpatrol orgs list",
|
|
289
|
+
"chainpatrol orgs list --subscription-status ACTIVE --service-active takedowns --service-manual takedowns"
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
"orgs list": {
|
|
293
|
+
description: "List organizations with their subscription status and per-service active/automated flags. All filters compose with AND.",
|
|
294
|
+
usage: "chainpatrol orgs list [filters]",
|
|
295
|
+
options: [
|
|
296
|
+
"--query <text> Partial name match (substring, case-insensitive)",
|
|
297
|
+
"--subscription-status <l> Comma-separated list (PROSPECT,TRIAL,ACTIVE,INTEGRATION)",
|
|
298
|
+
"--service-active <l> Comma-separated services that must be active",
|
|
299
|
+
"--service-inactive <l> Comma-separated services that must be inactive",
|
|
300
|
+
"--service-automated <l> Services whose automation must be ON (reporting,reviewing,protection,takedowns)",
|
|
301
|
+
"--service-manual <l> Services whose automation must be OFF (reporting,reviewing,protection,takedowns)"
|
|
302
|
+
],
|
|
303
|
+
examples: [
|
|
304
|
+
"chainpatrol orgs list --subscription-status ACTIVE",
|
|
305
|
+
"chainpatrol orgs list --service-active takedowns --service-manual takedowns",
|
|
306
|
+
"chainpatrol --json orgs list --subscription-status ACTIVE,TRIAL --service-active detection"
|
|
307
|
+
]
|
|
308
|
+
},
|
|
284
309
|
healthchecks: {
|
|
285
310
|
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.",
|
|
286
311
|
usage: "chainpatrol healthchecks <list|run <id>|run --all>",
|
|
@@ -369,6 +394,7 @@ function getTopLevelHelp() {
|
|
|
369
394
|
" metrics Query organization metrics and breakdowns",
|
|
370
395
|
" reports Create and list reports from terminal",
|
|
371
396
|
" queues Snapshot operations review/takedown queues",
|
|
397
|
+
" orgs List organizations and filter by status/services",
|
|
372
398
|
" presets Run saved workflows for common jobs",
|
|
373
399
|
" setup Install Claude Code skill and shell completions",
|
|
374
400
|
" uninstall Remove Claude Code skill and shell completions",
|
|
@@ -538,6 +564,7 @@ var COMMANDS = [
|
|
|
538
564
|
"metrics",
|
|
539
565
|
"reports",
|
|
540
566
|
"queues",
|
|
567
|
+
"orgs",
|
|
541
568
|
"presets",
|
|
542
569
|
"setup",
|
|
543
570
|
"uninstall",
|
|
@@ -614,6 +641,12 @@ ${getTopLevelHelp()}
|
|
|
614
641
|
contactInfo: { type: "string" },
|
|
615
642
|
externalSubmissionLink: { type: "string" },
|
|
616
643
|
payloadFile: { type: "string" },
|
|
644
|
+
subscriptionStatus: { type: "string" },
|
|
645
|
+
serviceActive: { type: "string" },
|
|
646
|
+
serviceInactive: { type: "string" },
|
|
647
|
+
serviceAutomated: { type: "string" },
|
|
648
|
+
serviceManual: { type: "string" },
|
|
649
|
+
query: { type: "string" },
|
|
617
650
|
help: { type: "boolean", shortFlag: "h" },
|
|
618
651
|
version: { type: "boolean", shortFlag: "V" },
|
|
619
652
|
quiet: { type: "boolean", default: false, shortFlag: "q" },
|
|
@@ -717,12 +750,12 @@ function parseAttachmentUrls() {
|
|
|
717
750
|
}
|
|
718
751
|
async function handleConfigsList(org) {
|
|
719
752
|
if (jsonMode) {
|
|
720
|
-
const { listConfigsJson } = await import("./list-json-
|
|
753
|
+
const { listConfigsJson } = await import("./list-json-LEKCCWQU.js");
|
|
721
754
|
await listConfigsJson({ org });
|
|
722
755
|
return;
|
|
723
756
|
}
|
|
724
757
|
const { render } = await import("ink");
|
|
725
|
-
const { default: ConfigsList } = await import("./list-
|
|
758
|
+
const { default: ConfigsList } = await import("./list-5ENZAOFL.js");
|
|
726
759
|
const { default: React } = await import("react");
|
|
727
760
|
render(React.createElement(ConfigsList, { org }));
|
|
728
761
|
}
|
|
@@ -820,7 +853,7 @@ async function main() {
|
|
|
820
853
|
case "detections": {
|
|
821
854
|
const org = await resolveOrg();
|
|
822
855
|
if (subcommand === "healthcheck") {
|
|
823
|
-
const { runDetectionsHealthcheck } = await import("./healthcheck-
|
|
856
|
+
const { runDetectionsHealthcheck } = await import("./healthcheck-AQUXVKAO.js");
|
|
824
857
|
await runDetectionsHealthcheck({
|
|
825
858
|
org,
|
|
826
859
|
source: cli.flags.source,
|
|
@@ -835,7 +868,7 @@ async function main() {
|
|
|
835
868
|
break;
|
|
836
869
|
}
|
|
837
870
|
if (subcommand === "validate") {
|
|
838
|
-
const { runDetectionsValidate } = await import("./validate-
|
|
871
|
+
const { runDetectionsValidate } = await import("./validate-27RUCN7R.js");
|
|
839
872
|
await runDetectionsValidate({
|
|
840
873
|
org,
|
|
841
874
|
source: cli.flags.source,
|
|
@@ -850,7 +883,7 @@ async function main() {
|
|
|
850
883
|
break;
|
|
851
884
|
}
|
|
852
885
|
if (subcommand === "drift") {
|
|
853
|
-
const { runDetectionsDrift } = await import("./drift-
|
|
886
|
+
const { runDetectionsDrift } = await import("./drift-VOKQJ36G.js");
|
|
854
887
|
await runDetectionsDrift({
|
|
855
888
|
org,
|
|
856
889
|
source: cli.flags.source,
|
|
@@ -864,7 +897,7 @@ async function main() {
|
|
|
864
897
|
break;
|
|
865
898
|
}
|
|
866
899
|
if (subcommand === "run") {
|
|
867
|
-
const { runDetectionsRun } = await import("./run-
|
|
900
|
+
const { runDetectionsRun } = await import("./run-OT2X46GT.js");
|
|
868
901
|
await runDetectionsRun({
|
|
869
902
|
org,
|
|
870
903
|
configId: cli.flags.configId,
|
|
@@ -883,7 +916,7 @@ async function main() {
|
|
|
883
916
|
break;
|
|
884
917
|
}
|
|
885
918
|
if (action === "run") {
|
|
886
|
-
const { runDetectionsRun } = await import("./run-
|
|
919
|
+
const { runDetectionsRun } = await import("./run-OT2X46GT.js");
|
|
887
920
|
await runDetectionsRun({
|
|
888
921
|
org,
|
|
889
922
|
configId: cli.flags.configId,
|
|
@@ -901,7 +934,7 @@ async function main() {
|
|
|
901
934
|
throw new Error("detections configs update requires --config-id");
|
|
902
935
|
}
|
|
903
936
|
const configPatch = getConfigPatchFromSetFlags();
|
|
904
|
-
const { runDetectionsConfigsUpdate } = await import("./configs-update-
|
|
937
|
+
const { runDetectionsConfigsUpdate } = await import("./configs-update-VROBC2HI.js");
|
|
905
938
|
await runDetectionsConfigsUpdate({
|
|
906
939
|
org,
|
|
907
940
|
configId: cli.flags.configId,
|
|
@@ -928,7 +961,7 @@ async function main() {
|
|
|
928
961
|
case "metrics": {
|
|
929
962
|
const org = await resolveOrg();
|
|
930
963
|
if (subcommand === "summary") {
|
|
931
|
-
const { runMetricsSummary } = await import("./summary-
|
|
964
|
+
const { runMetricsSummary } = await import("./summary-JOCABBCO.js");
|
|
932
965
|
await runMetricsSummary({
|
|
933
966
|
org,
|
|
934
967
|
from: cli.flags.from,
|
|
@@ -940,7 +973,7 @@ async function main() {
|
|
|
940
973
|
break;
|
|
941
974
|
}
|
|
942
975
|
if (subcommand === "found") {
|
|
943
|
-
const { runMetricsFound } = await import("./found-
|
|
976
|
+
const { runMetricsFound } = await import("./found-A5HRTJCJ.js");
|
|
944
977
|
await runMetricsFound({
|
|
945
978
|
org,
|
|
946
979
|
from: cli.flags.from,
|
|
@@ -957,7 +990,7 @@ async function main() {
|
|
|
957
990
|
if (!by || !["day", "type", "brand"].includes(by)) {
|
|
958
991
|
throw new Error("metrics breakdown requires --by <day|type|brand>");
|
|
959
992
|
}
|
|
960
|
-
const { runMetricsBreakdown } = await import("./breakdown-
|
|
993
|
+
const { runMetricsBreakdown } = await import("./breakdown-AX6QNTQH.js");
|
|
961
994
|
await runMetricsBreakdown({
|
|
962
995
|
org,
|
|
963
996
|
by,
|
|
@@ -977,7 +1010,7 @@ async function main() {
|
|
|
977
1010
|
case "reports": {
|
|
978
1011
|
if (subcommand === "list") {
|
|
979
1012
|
const org = await resolveOrg();
|
|
980
|
-
const { runReportsList } = await import("./list-
|
|
1013
|
+
const { runReportsList } = await import("./list-CVFXTKNX.js");
|
|
981
1014
|
await runReportsList({
|
|
982
1015
|
org,
|
|
983
1016
|
limit: cli.flags.limit,
|
|
@@ -993,7 +1026,7 @@ async function main() {
|
|
|
993
1026
|
}
|
|
994
1027
|
if (subcommand === "create") {
|
|
995
1028
|
const org = await tryResolveOrg();
|
|
996
|
-
const { runReportsCreate } = await import("./create-
|
|
1029
|
+
const { runReportsCreate } = await import("./create-XTCUNT2C.js");
|
|
997
1030
|
await runReportsCreate({
|
|
998
1031
|
org,
|
|
999
1032
|
title: cli.flags.title,
|
|
@@ -1017,7 +1050,7 @@ async function main() {
|
|
|
1017
1050
|
}
|
|
1018
1051
|
case "queues": {
|
|
1019
1052
|
if (subcommand === "snapshot") {
|
|
1020
|
-
const { runQueuesSnapshot } = await import("./snapshot-
|
|
1053
|
+
const { runQueuesSnapshot } = await import("./snapshot-4QR4I67P.js");
|
|
1021
1054
|
await runQueuesSnapshot({
|
|
1022
1055
|
org: cli.flags.org,
|
|
1023
1056
|
all: cli.flags.all,
|
|
@@ -1033,9 +1066,29 @@ async function main() {
|
|
|
1033
1066
|
subcommand ? `Unknown subcommand: queues ${subcommand}${hint ? `. Did you mean "queues ${hint}"?` : ""}` : "Usage: chainpatrol queues snapshot [--org <slug>|--all]"
|
|
1034
1067
|
);
|
|
1035
1068
|
}
|
|
1069
|
+
case "orgs": {
|
|
1070
|
+
if (subcommand === "list") {
|
|
1071
|
+
const { runOrgsList } = await import("./list-CGRHTFAS.js");
|
|
1072
|
+
await runOrgsList({
|
|
1073
|
+
query: cli.flags.query,
|
|
1074
|
+
subscriptionStatus: cli.flags.subscriptionStatus,
|
|
1075
|
+
serviceActive: cli.flags.serviceActive,
|
|
1076
|
+
serviceInactive: cli.flags.serviceInactive,
|
|
1077
|
+
serviceAutomated: cli.flags.serviceAutomated,
|
|
1078
|
+
serviceManual: cli.flags.serviceManual,
|
|
1079
|
+
json: jsonMode,
|
|
1080
|
+
outputFormat: cliContext.outputFormat
|
|
1081
|
+
});
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
const hint = subcommand ? suggest(subcommand, ["list"]) : null;
|
|
1085
|
+
throw new Error(
|
|
1086
|
+
subcommand ? `Unknown subcommand: orgs ${subcommand}${hint ? `. Did you mean "orgs ${hint}"?` : ""}` : "Usage: chainpatrol orgs list [--subscription-status <list>] [--service-active <list>] [--service-automated <list>] ..."
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1036
1089
|
case "healthchecks": {
|
|
1037
1090
|
if (subcommand === "list") {
|
|
1038
|
-
const { runHealthchecksList } = await import("./list-
|
|
1091
|
+
const { runHealthchecksList } = await import("./list-PLZ67PNY.js");
|
|
1039
1092
|
await runHealthchecksList({
|
|
1040
1093
|
json: jsonMode,
|
|
1041
1094
|
outputFormat: cliContext.outputFormat
|
|
@@ -1049,7 +1102,7 @@ async function main() {
|
|
|
1049
1102
|
thresholds.minResults = cli.flags.minResults;
|
|
1050
1103
|
if (cli.flags.lookbackHours !== void 0)
|
|
1051
1104
|
thresholds.lookbackHours = cli.flags.lookbackHours;
|
|
1052
|
-
const { runHealthchecksRun } = await import("./run-
|
|
1105
|
+
const { runHealthchecksRun } = await import("./run-MS5SA5YL.js");
|
|
1053
1106
|
await runHealthchecksRun({
|
|
1054
1107
|
org,
|
|
1055
1108
|
id: action,
|
|
@@ -1067,7 +1120,7 @@ async function main() {
|
|
|
1067
1120
|
}
|
|
1068
1121
|
case "presets": {
|
|
1069
1122
|
if (subcommand === "list") {
|
|
1070
|
-
const { runPresetsList } = await import("./list-
|
|
1123
|
+
const { runPresetsList } = await import("./list-EYRN5JYC.js");
|
|
1071
1124
|
await runPresetsList({ outputFormat: cliContext.outputFormat });
|
|
1072
1125
|
break;
|
|
1073
1126
|
}
|
|
@@ -1078,7 +1131,7 @@ async function main() {
|
|
|
1078
1131
|
);
|
|
1079
1132
|
}
|
|
1080
1133
|
const org = await resolveOrg();
|
|
1081
|
-
const { runPresetsRun } = await import("./run-
|
|
1134
|
+
const { runPresetsRun } = await import("./run-YHDUUP66.js");
|
|
1082
1135
|
await runPresetsRun({
|
|
1083
1136
|
presetId: action,
|
|
1084
1137
|
org,
|
|
@@ -1095,12 +1148,12 @@ async function main() {
|
|
|
1095
1148
|
case "setup":
|
|
1096
1149
|
case "install":
|
|
1097
1150
|
case "i": {
|
|
1098
|
-
const { setupSkill } = await import("./setup-skill-
|
|
1151
|
+
const { setupSkill } = await import("./setup-skill-BTR2IZ4E.js");
|
|
1099
1152
|
setupSkill({ json: jsonMode });
|
|
1100
1153
|
break;
|
|
1101
1154
|
}
|
|
1102
1155
|
case "uninstall": {
|
|
1103
|
-
const { uninstallSkill } = await import("./setup-skill-
|
|
1156
|
+
const { uninstallSkill } = await import("./setup-skill-BTR2IZ4E.js");
|
|
1104
1157
|
uninstallSkill({ json: jsonMode });
|
|
1105
1158
|
break;
|
|
1106
1159
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
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-LLWKCA3H.js";
|
|
12
|
+
import "./chunk-EEG7T6WT.js";
|
|
13
|
+
import "./chunk-TFCNKBRC.js";
|
|
14
|
+
import "./chunk-U73SABXK.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/orgs/list.ts
|
|
17
|
+
var VALID_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
|
|
18
|
+
"PROSPECT",
|
|
19
|
+
"TRIAL",
|
|
20
|
+
"ACTIVE",
|
|
21
|
+
"INTEGRATION"
|
|
22
|
+
]);
|
|
23
|
+
var DUAL_SERVICES = ["reporting", "reviewing", "protection", "takedowns"];
|
|
24
|
+
var SINGLE_SERVICES = ["detection", "darkWebMonitoring"];
|
|
25
|
+
var ALL_SERVICES = [...DUAL_SERVICES, ...SINGLE_SERVICES];
|
|
26
|
+
function splitCsv(value) {
|
|
27
|
+
if (!value) return [];
|
|
28
|
+
return value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
function parseSubscriptionStatuses(raw) {
|
|
31
|
+
const parts = splitCsv(raw);
|
|
32
|
+
if (parts.length === 0) return void 0;
|
|
33
|
+
const result = [];
|
|
34
|
+
for (const part of parts) {
|
|
35
|
+
const upper = part.toUpperCase();
|
|
36
|
+
if (!VALID_SUBSCRIPTION_STATUSES.has(upper)) {
|
|
37
|
+
throw new CliExitError(
|
|
38
|
+
`Unknown subscription status: '${part}'. Valid values: ${Array.from(VALID_SUBSCRIPTION_STATUSES).join(", ")}.`,
|
|
39
|
+
ExitCode.USAGE
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
result.push(upper);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
function parseServiceList(raw, flagName) {
|
|
47
|
+
const parts = splitCsv(raw);
|
|
48
|
+
const result = [];
|
|
49
|
+
for (const part of parts) {
|
|
50
|
+
if (!ALL_SERVICES.includes(part)) {
|
|
51
|
+
throw new CliExitError(
|
|
52
|
+
`Unknown service '${part}' in --${flagName}. Valid services: ${ALL_SERVICES.join(", ")}.`,
|
|
53
|
+
ExitCode.USAGE
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
result.push(part);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function requireDualService(service, flagName) {
|
|
61
|
+
if (!DUAL_SERVICES.includes(service)) {
|
|
62
|
+
throw new CliExitError(
|
|
63
|
+
`Service '${service}' does not have an automation toggle and can't be used with --${flagName}. Use --service-active / --service-inactive instead.`,
|
|
64
|
+
ExitCode.USAGE
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return service;
|
|
68
|
+
}
|
|
69
|
+
function buildServicesFilter(options) {
|
|
70
|
+
const filters = {};
|
|
71
|
+
const activeSetBy = /* @__PURE__ */ new Map();
|
|
72
|
+
const automatedSetBy = /* @__PURE__ */ new Map();
|
|
73
|
+
function setActive(service, active, flagName) {
|
|
74
|
+
const previous = activeSetBy.get(service);
|
|
75
|
+
if (previous && previous !== flagName) {
|
|
76
|
+
throw new CliExitError(
|
|
77
|
+
`Conflicting flags for service '${service}': --${previous} and --${flagName} can't both be passed.`,
|
|
78
|
+
ExitCode.USAGE
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
activeSetBy.set(service, flagName);
|
|
82
|
+
const existing = filters[service] ?? {};
|
|
83
|
+
filters[service] = { ...existing, active };
|
|
84
|
+
}
|
|
85
|
+
function setAutomated(service, automated, flagName) {
|
|
86
|
+
const previous = automatedSetBy.get(service);
|
|
87
|
+
if (previous && previous !== flagName) {
|
|
88
|
+
throw new CliExitError(
|
|
89
|
+
`Conflicting flags for service '${service}': --${previous} and --${flagName} can't both be passed.`,
|
|
90
|
+
ExitCode.USAGE
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
automatedSetBy.set(service, flagName);
|
|
94
|
+
const existing = filters[service] ?? {};
|
|
95
|
+
filters[service] = { ...existing, automated };
|
|
96
|
+
}
|
|
97
|
+
for (const service of parseServiceList(options.serviceActive, "service-active")) {
|
|
98
|
+
setActive(service, true, "service-active");
|
|
99
|
+
}
|
|
100
|
+
for (const service of parseServiceList(options.serviceInactive, "service-inactive")) {
|
|
101
|
+
setActive(service, false, "service-inactive");
|
|
102
|
+
}
|
|
103
|
+
for (const service of parseServiceList(options.serviceAutomated, "service-automated")) {
|
|
104
|
+
setAutomated(
|
|
105
|
+
requireDualService(service, "service-automated"),
|
|
106
|
+
true,
|
|
107
|
+
"service-automated"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
for (const service of parseServiceList(options.serviceManual, "service-manual")) {
|
|
111
|
+
setAutomated(requireDualService(service, "service-manual"), false, "service-manual");
|
|
112
|
+
}
|
|
113
|
+
return Object.keys(filters).length > 0 ? filters : void 0;
|
|
114
|
+
}
|
|
115
|
+
function summarizeServices(org) {
|
|
116
|
+
const parts = [];
|
|
117
|
+
for (const service of DUAL_SERVICES) {
|
|
118
|
+
const entry = org.services[service];
|
|
119
|
+
if (!entry.active) continue;
|
|
120
|
+
parts.push(`${service}=${entry.automated ? "auto" : "manual"}`);
|
|
121
|
+
}
|
|
122
|
+
for (const service of SINGLE_SERVICES) {
|
|
123
|
+
if (org.services[service].active) parts.push(service);
|
|
124
|
+
}
|
|
125
|
+
return parts.length > 0 ? parts.join(" ") : "(no services enabled)";
|
|
126
|
+
}
|
|
127
|
+
async function runOrgsList(options) {
|
|
128
|
+
const client = options.apiClient ?? createApiClient();
|
|
129
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
130
|
+
const input = {
|
|
131
|
+
query: options.query ?? "",
|
|
132
|
+
subscriptionStatus: parseSubscriptionStatuses(options.subscriptionStatus),
|
|
133
|
+
services: buildServicesFilter(options)
|
|
134
|
+
};
|
|
135
|
+
const result = await client.getUserOrgs(input);
|
|
136
|
+
const organizations = result.organizations;
|
|
137
|
+
printOutput({
|
|
138
|
+
outputFormat,
|
|
139
|
+
json: result,
|
|
140
|
+
markdown: [
|
|
141
|
+
`# Organizations (${organizations.length})`,
|
|
142
|
+
"",
|
|
143
|
+
...organizations.map(
|
|
144
|
+
(org) => `- \`${org.slug}\` \u2014 ${org.name} \u2014 ${org.subscriptionStatus} \u2014 ${summarizeServices(org)}`
|
|
145
|
+
)
|
|
146
|
+
].join("\n"),
|
|
147
|
+
csv: toCsvRows(
|
|
148
|
+
organizations.map((org) => ({
|
|
149
|
+
slug: org.slug,
|
|
150
|
+
name: org.name,
|
|
151
|
+
subscriptionStatus: org.subscriptionStatus,
|
|
152
|
+
reportingActive: org.services.reporting.active,
|
|
153
|
+
reportingAutomated: org.services.reporting.automated,
|
|
154
|
+
reviewingActive: org.services.reviewing.active,
|
|
155
|
+
reviewingAutomated: org.services.reviewing.automated,
|
|
156
|
+
protectionActive: org.services.protection.active,
|
|
157
|
+
protectionAutomated: org.services.protection.automated,
|
|
158
|
+
takedownsActive: org.services.takedowns.active,
|
|
159
|
+
takedownsAutomated: org.services.takedowns.automated,
|
|
160
|
+
detectionActive: org.services.detection.active,
|
|
161
|
+
darkWebMonitoringActive: org.services.darkWebMonitoring.active
|
|
162
|
+
}))
|
|
163
|
+
),
|
|
164
|
+
human: () => {
|
|
165
|
+
if (organizations.length === 0) {
|
|
166
|
+
console.log("No organizations matched the filter.");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
console.log(`Found ${organizations.length} organization(s):`);
|
|
170
|
+
const slugWidth = Math.max(...organizations.map((org) => org.slug.length), 4);
|
|
171
|
+
const nameWidth = Math.max(...organizations.map((org) => org.name.length), 4);
|
|
172
|
+
for (const org of organizations) {
|
|
173
|
+
console.log(
|
|
174
|
+
` ${org.slug.padEnd(slugWidth)} ${org.name.padEnd(nameWidth)} ${org.subscriptionStatus.padEnd(11)} ${summarizeServices(org)}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
export {
|
|
181
|
+
runOrgsList
|
|
182
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRESETS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AGXMZFUU.js";
|
|
4
4
|
import "./chunk-E2LAMILJ.js";
|
|
5
5
|
import {
|
|
6
6
|
printOutput,
|
|
7
7
|
toCsvRows
|
|
8
8
|
} from "./chunk-VFT3TD3E.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-LLWKCA3H.js";
|
|
10
10
|
import "./chunk-EEG7T6WT.js";
|
|
11
11
|
import "./chunk-TFCNKBRC.js";
|
|
12
12
|
import "./chunk-U73SABXK.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-VFT3TD3E.js";
|
|
9
9
|
import {
|
|
10
10
|
createApiClient
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-LLWKCA3H.js";
|
|
12
12
|
import "./chunk-EEG7T6WT.js";
|
|
13
13
|
import "./chunk-TFCNKBRC.js";
|
|
14
14
|
import "./chunk-U73SABXK.js";
|
|
@@ -27,7 +27,12 @@ function buildPayload(entry, org, thresholds) {
|
|
|
27
27
|
"failThreshold",
|
|
28
28
|
"warnAgeHours",
|
|
29
29
|
"failAgeHours",
|
|
30
|
-
"staleThresholdHours"
|
|
30
|
+
"staleThresholdHours",
|
|
31
|
+
"windowHours",
|
|
32
|
+
"baselineDays",
|
|
33
|
+
"warnMultiplier",
|
|
34
|
+
"failMultiplier",
|
|
35
|
+
"minSpikeCount"
|
|
31
36
|
]);
|
|
32
37
|
for (const [key, value] of Object.entries(thresholds)) {
|
|
33
38
|
if (allowedKeys.has(key)) {
|
|
@@ -150,6 +155,9 @@ async function runHealthchecksRun(options) {
|
|
|
150
155
|
if (entry.result.suggestedAction) {
|
|
151
156
|
lines.push(`- Suggested action: ${entry.result.suggestedAction}`);
|
|
152
157
|
}
|
|
158
|
+
if (entry.result.appUrl) {
|
|
159
|
+
lines.push(`- View in app: ${entry.result.appUrl}`);
|
|
160
|
+
}
|
|
153
161
|
return lines.join("\n");
|
|
154
162
|
}),
|
|
155
163
|
...errors.map((entry) => `## ${entry.entry.id} \u2014 ERROR
|
|
@@ -194,6 +202,10 @@ async function runHealthchecksRun(options) {
|
|
|
194
202
|
console.log(`Suggested action for ${entry.result.id}:`);
|
|
195
203
|
console.log(` ${entry.result.suggestedAction}`);
|
|
196
204
|
}
|
|
205
|
+
if (entry.result.appUrl) {
|
|
206
|
+
console.log("");
|
|
207
|
+
console.log(`View in app: ${entry.result.appUrl}`);
|
|
208
|
+
}
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
211
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPresetDefinition,
|
|
3
3
|
runPreset
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AGXMZFUU.js";
|
|
5
5
|
import {
|
|
6
6
|
CliExitError,
|
|
7
7
|
ExitCode
|
|
8
8
|
} from "./chunk-E2LAMILJ.js";
|
|
9
9
|
import "./chunk-VFT3TD3E.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-LLWKCA3H.js";
|
|
11
11
|
import "./chunk-EEG7T6WT.js";
|
|
12
12
|
import "./chunk-TFCNKBRC.js";
|
|
13
13
|
import "./chunk-U73SABXK.js";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@chainpatrol/cli",
|
|
3
3
|
"description": "The official ChainPatrol CLI — terminal interface for threat detection",
|
|
4
4
|
"author": "Umar Ahmed <umar@chainpatrol.io>",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.6.0",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"homepage": "https://chainpatrol.com/docs/cli",
|
|
8
8
|
"keywords": [
|