@chainpatrol/cli 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/{breakdown-BECXDJIS.js → breakdown-FFNYSXRQ.js} +2 -2
  3. package/dist/{chunk-S7PQNG4E.js → chunk-DOL35U2S.js} +286 -7
  4. package/dist/chunk-EBJMOX3Q.js +83 -0
  5. package/dist/{chunk-B3CCMSG2.js → chunk-WBKCXGLV.js} +1 -1
  6. package/dist/cli.js +24 -21
  7. package/dist/{configs-update-AHI7T6S4.js → configs-update-VOWH674W.js} +2 -2
  8. package/dist/{create-RS37JAMM.js → create-ASGUBKZ7.js} +2 -2
  9. package/dist/{drift-2JRZK2MC.js → drift-PASGDEEX.js} +2 -2
  10. package/dist/{found-YYYFF4FD.js → found-TIW7T4VX.js} +2 -2
  11. package/dist/{healthcheck-6RHZBEVE.js → healthcheck-SIE5EXN3.js} +2 -2
  12. package/dist/{list-MHEIY7LQ.js → list-CEWBMKJ3.js} +2 -2
  13. package/dist/{list-M2UQCXIO.js → list-KTIZ3UHA.js} +3 -3
  14. package/dist/{list-56BS25X4.js → list-TMFLCS5U.js} +4 -4
  15. package/dist/{list-json-GANUT7SB.js → list-json-SUZL2GBJ.js} +2 -2
  16. package/dist/{login-ML2EKF44.js → login-C66GRR3Y.js} +4 -2
  17. package/dist/login-json-XGMXT5VJ.js +50 -0
  18. package/dist/login-plain-CWKOUZKE.js +60 -0
  19. package/dist/{run-7NTCD7JI.js → run-3TTLZ6HA.js} +3 -3
  20. package/dist/{run-JZNZSCDJ.js → run-MGOASUON.js} +2 -2
  21. package/dist/{setup-skill-XZRLJE3A.js → setup-skill-PCUBJJYU.js} +1 -1
  22. package/dist/{snapshot-KGMO44YB.js → snapshot-MQ6DWDFG.js} +2 -2
  23. package/dist/{summary-PA2CCZVO.js → summary-QORQFCMW.js} +2 -2
  24. package/dist/{validate-NIBVL7X5.js → validate-II6GH6H6.js} +2 -2
  25. package/package.json +1 -1
  26. package/dist/chunk-ZVM45CTB.js +0 -19
  27. package/dist/login-json-Y3AIQIIB.js +0 -75
  28. package/dist/{chunk-DSOM6TZX.js → chunk-44FSS3CZ.js} +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # @chainpatrol/cli
2
2
 
3
+ ## 0.3.4
4
+
5
+ ### Patch Changes
6
+
7
+ - e30617f: Update the bundled Claude Code skill to document the `detections healthcheck`,
8
+ `queues snapshot`, `metrics summary | found | breakdown`, and `presets`
9
+ subcommands, plus the `--no-input` / `--no-color` / `--output` / `--quiet`
10
+ global flags. The existing soft / manual signals in the Organization
11
+ HealthCheck Guide are preserved verbatim; each subsection now also points at
12
+ the matching CLI command via a "Run via CLI" note, and a new "Quick Path"
13
+ block lists the commands an agent should reach for first when asked to run
14
+ a health check.
15
+
16
+ ## 0.3.3
17
+
18
+ ### Patch Changes
19
+
20
+ - 415dbcd: `chainpatrol login`:
21
+
22
+ - Auto-detect non-TTY stdout (Claude Code on the web, CI runners, piped
23
+ invocations) and fall back to a plain-text login flow that prints the
24
+ device code and verification URL immediately. Previously the
25
+ interactive Ink-based UI rendered nothing in those environments,
26
+ making the command appear to hang with no output until the device
27
+ code expired.
28
+ - `chainpatrol --json login` now emits the device-code line on **stdout**
29
+ instead of stderr, matching the `{"status":"success",...}` line. This
30
+ makes the URL visible without callers needing to remember `2>&1`.
31
+ Consumers should treat the output as JSON-lines and read the last
32
+ line for the terminal result.
33
+
34
+ Claude Code skill bundled with the CLI:
35
+
36
+ - New "Running `login` from an agent (headless / non-TTY)" section
37
+ documenting the background+tail pattern so agents surface the
38
+ verification URL to the user immediately instead of blocking on the
39
+ polling step.
40
+
3
41
  ## 0.3.2
4
42
 
5
43
  ### Patch Changes
@@ -4,9 +4,9 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-DSOM6TZX.js";
8
- import "./chunk-TFCNKBRC.js";
7
+ } from "./chunk-44FSS3CZ.js";
9
8
  import "./chunk-EEG7T6WT.js";
9
+ import "./chunk-TFCNKBRC.js";
10
10
  import "./chunk-U73SABXK.js";
11
11
 
12
12
  // src/commands/metrics/breakdown.ts
@@ -150,6 +150,46 @@ JSON mode (for automation):
150
150
  chainpatrol --json login
151
151
  \`\`\`
152
152
 
153
+ #### Running \`login\` from an agent (headless / non-TTY)
154
+
155
+ The login flow blocks for up to 30 minutes while polling for the user to
156
+ authorize in their browser. If you (the agent) run it as a foreground
157
+ command and wait for it to exit before reading output, you will appear
158
+ stuck \u2014 the verification URL will never be shown because the process is
159
+ still polling.
160
+
161
+ **Always run \`login\` in the background and stream its output**, then
162
+ surface the verification URL to the user as soon as it appears:
163
+
164
+ \`\`\`bash
165
+ # 1. Kick off login in the background (do NOT wait for it to exit)
166
+ chainpatrol --json login > /tmp/cp-login.out 2>&1 &
167
+
168
+ # 2. Wait briefly for the first line, which contains the URL
169
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
170
+ test -s /tmp/cp-login.out && break
171
+ sleep 1
172
+ done
173
+ cat /tmp/cp-login.out
174
+ \`\`\`
175
+
176
+ The first line emitted is JSON describing the device code, e.g.:
177
+
178
+ \`\`\`json
179
+ {"action":"open_url","user_code":"ABCD-1234","verification_uri":"https://app.chainpatrol.io/auth/verify-device","verification_uri_complete":"https://app.chainpatrol.io/auth/verify-device?user_code=ABCD-1234","expires_in":1800,"headless":true}
180
+ \`\`\`
181
+
182
+ Show the user the \`verification_uri_complete\` link (or
183
+ \`verification_uri\` + \`user_code\` as a fallback) and explain that the
184
+ CLI will pick up the token automatically once they authorize. Then keep
185
+ the background process running and tail it for the final
186
+ \`{"status":"success",...}\` or \`{"error":...}\` line.
187
+
188
+ CLI v0.3.3+ also auto-detects non-TTY stdout and prints the URL as plain
189
+ text immediately when you run \`chainpatrol login\` without \`--json\`,
190
+ so the same background+tail pattern works without \`--json\`. Prefer
191
+ \`--json\` so the output is structured and machine-parseable.
192
+
153
193
  ### \`logout\` \u2014 Clear stored credentials
154
194
 
155
195
  \`\`\`bash
@@ -224,6 +264,101 @@ You can also compare with the non-customer set using
224
264
  \`--no-reported-by-customer\` to gauge detection coverage on the same time
225
265
  window.
226
266
 
267
+ ### \`detections healthcheck\` \u2014 Validate enabled detection configs produce recent results
268
+
269
+ Server-side check. The CLI calls the ChainPatrol API; the server fetches each
270
+ enabled detection config for the org, counts results produced in the lookback
271
+ window, and FAILs configs that fall under \`--min-results\` (or whose run
272
+ errored when \`--run\` is set).
273
+
274
+ \`\`\`bash
275
+ chainpatrol --json detections healthcheck --org <slug>
276
+ \`\`\`
277
+
278
+ Flags:
279
+ - \`--source <key>\` only validate one source (e.g. \`twitter_search\`)
280
+ - \`--min-results <n>\` minimum results required in the window to pass
281
+ - \`--lookback-hours <n>\` size of the lookback window
282
+ - \`--run\` ask the server to run each config first, then validate the fresh output
283
+ - \`--include-disabled\` also validate disabled configs
284
+
285
+ What this command covers:
286
+ - Configs that have gone silent (recentResultCount below threshold)
287
+ - Configs that error when run (runOk=false) when \`--run\` is set
288
+
289
+ What it does NOT cover:
290
+ - Reviewing backlog / SLA breaches \u2192 use \`queues snapshot\`
291
+ - Takedown ToDo / In Progress / Cancelled volumes \u2192 use \`queues snapshot\`
292
+ - Spikes or drops in detection volume over time \u2192 use \`metrics breakdown\`
293
+ - Customer-reported gaps \u2192 use \`reports list --reported-by-customer\`
294
+ - Google Safe Browsing submission errors (not yet exposed in CLI)
295
+
296
+ Use it as the first signal in the Detection part of an org healthcheck, then
297
+ fall back to the manual checks in the HealthCheck Guide below for everything
298
+ else.
299
+
300
+ ### \`queues snapshot\` \u2014 Operations review/takedown queue snapshot
301
+
302
+ Server-side aggregation of the operations review queue (pending proposals,
303
+ SLA breaches, age buckets) and the takedown queue (open, in-progress, stale).
304
+
305
+ \`\`\`bash
306
+ chainpatrol --json queues snapshot --org <slug>
307
+ \`\`\`
308
+
309
+ Useful for the **Reviewing** and **Takedowns** sections of the HealthCheck
310
+ Guide. Key signals in the response:
311
+ - \`reviewQueue.totalPendingProposals\` and \`reviewQueue.distinctReports\` \u2014
312
+ backlog size
313
+ - \`reviewQueue.slaBuckets.breached\` \u2014 SLA breaches (treat any breach as a
314
+ finding)
315
+ - \`reviewQueue.ageBuckets.gte168h\` \u2014 proposals older than 7 days (anything
316
+ >14 days from the manual guide should always be in here)
317
+ - \`takedownQueue.totalOpen\` and \`takedownQueue.staleInProgress\` \u2014 open
318
+ and stuck takedowns
319
+
320
+ Use \`--all\` to snapshot every org you have access to instead of a single slug.
321
+
322
+ ### \`metrics summary | found | breakdown\` \u2014 Org metrics for spike/drop analysis
323
+
324
+ \`\`\`bash
325
+ chainpatrol --json metrics summary --org <slug> --this-week
326
+ chainpatrol --json metrics breakdown --org <slug> --by day --this-week
327
+ chainpatrol --json metrics found --org <slug> --from <YYYY-MM-DD> --to <YYYY-MM-DD>
328
+ \`\`\`
329
+
330
+ \`breakdown\` is the one you usually want for healthchecks: it returns a
331
+ time series (by day or week) of reports, new threats, watchlisted threats,
332
+ and takedowns filed/completed. Compare the latest period against a prior
333
+ window to spot the **spike** or **drop** signals described in the manual
334
+ HealthCheck Guide. \`summary\` returns a single window total; \`found\` is
335
+ oriented around when threats were first discovered.
336
+
337
+ ### \`presets list | run\` \u2014 Packaged workflows for common jobs
338
+
339
+ \`\`\`bash
340
+ chainpatrol presets list
341
+ chainpatrol presets run cs-weekly-health --org <slug>
342
+ \`\`\`
343
+
344
+ Use \`presets list\` to discover packaged multi-step workflows. The bundled
345
+ \`cs-weekly-health\` preset runs the standard customer-success weekly health
346
+ sweep; prefer it over hand-rolling the same sequence of commands.
347
+
348
+ ## Headless / agent-mode tips
349
+
350
+ When running these commands from an agent or CI:
351
+
352
+ - Prefer \`--json\` (or \`--output json\`) so output is structured and the
353
+ update-check stderr nudge is suppressed automatically.
354
+ - Pass \`--no-input\` to forbid any interactive prompt (the CLI will error
355
+ out instead of waiting on a TTY).
356
+ - Pass \`--no-color\` or set \`NO_COLOR=1\` if your sink can't render ANSI.
357
+ - Set \`CHAINPATROL_NO_UPDATE_CHECK=1\` to silence the skill/npm freshness
358
+ nudge entirely.
359
+ - For \`login\` specifically, see the headless runbook in the login section
360
+ above \u2014 login is the one command that needs background + tail handling.
361
+
227
362
  ## Checking Auth Status
228
363
 
229
364
  To check if the user is logged in, read the credentials file:
@@ -263,12 +398,16 @@ machine-readable output is never polluted.
263
398
 
264
399
  ## Global Flags
265
400
 
266
- | Flag | Description |
267
- |-------------|--------------------------------------|
268
- | \`--json\` | Machine-readable JSON output |
269
- | \`--org\` | Organization slug (saved for later) |
270
- | \`--help\` | Show help |
271
- | \`--version\` | Show version |
401
+ | Flag | Description |
402
+ |------------------|--------------------------------------------------------|
403
+ | \`--json\` | Machine-readable JSON output (shortcut for \`--output json\`) |
404
+ | \`--output <fmt>\` | Output format: \`human\` (default), \`json\`, \`markdown\`, \`csv\` |
405
+ | \`--quiet\`, \`-q\` | Suppress non-essential output and the update-check nudge |
406
+ | \`--no-color\` | Disable ANSI colors (also respects the \`NO_COLOR\` env var) |
407
+ | \`--no-input\` | Disable interactive prompts (use in scripts and agents) |
408
+ | \`--org <slug>\` | Organization slug (saved as the default for later commands) |
409
+ | \`--help\`, \`-h\` | Show help |
410
+ | \`--version\` | Show version |
272
411
 
273
412
  ## Workflow
274
413
 
@@ -296,7 +435,85 @@ When the user asks for a "health check", "audit", "what's wrong with org X",
296
435
  "review org X's setup", or similar, walk through each section below and surface
297
436
  findings.
298
437
 
299
- In each section there are things you can look for that may be wrong.
438
+ In each section there are things you can look for that may be wrong. Some
439
+ checks have a dedicated CLI command that does most of the work server-side;
440
+ others are soft / qualitative signals that still need you to fetch data with
441
+ \`configs list\` or \`reports list\` and reason about it manually.
442
+
443
+ ### Quick Path: CLI commands that automate parts of this guide
444
+
445
+ Run these first to get cheap, structured signals before falling back to the
446
+ manual checks in each subsection:
447
+
448
+ \`\`\`bash
449
+ # Detection: configs that have gone silent or are erroring
450
+ chainpatrol --json detections healthcheck --org <slug>
451
+
452
+ # Reviewing & Takedowns: backlog, SLA breaches, stuck takedowns
453
+ chainpatrol --json queues snapshot --org <slug>
454
+
455
+ # Spikes / drops in detection volume over time (compare windows)
456
+ chainpatrol --json metrics breakdown --org <slug> --by day --this-week
457
+
458
+ # Customer-reported threats \u2014 gaps in our own detection
459
+ chainpatrol --json reports list --org <slug> --reported-by-customer
460
+
461
+ # What's enabled vs disabled vs not configured for the org
462
+ chainpatrol --json configs list --org <slug>
463
+
464
+ # Packaged weekly customer-success sweep (preferred when it covers the ask)
465
+ chainpatrol presets run cs-weekly-health --org <slug>
466
+ \`\`\`
467
+
468
+ Treat each command's output as one input to the healthcheck. The manual
469
+ checks below still apply \u2014 especially for signals the CLI cannot infer on
470
+ its own (e.g. "lots of Twitter assets are blocked but Twitter Post Search
471
+ is disabled", or "this drop is fine because the config isn't relevant
472
+ to this org").
473
+
474
+ ### Reporting progress while running a healthcheck
475
+
476
+ When the user asks for a healthcheck, narrate each step in real time so they
477
+ can watch the run unfold. Do NOT batch results and dump everything at the
478
+ end. For every check you run (Quick Path CLI command, or a manual signal
479
+ where you're fetching data with \`configs list\` / \`reports list\` to
480
+ reason about):
481
+
482
+ 1. **Before** you call the command, emit ONE short sentence stating what
483
+ you're about to check, including the org slug. Examples:
484
+ - "Running detection config healthcheck for morpho\u2026"
485
+ - "Snapshotting review and takedown queues for morpho\u2026"
486
+ - "Fetching customer-reported reports for morpho to look for detection gaps\u2026"
487
+
488
+ 2. **As soon as** the command returns, emit ONE short result line that
489
+ starts with a status word and ends with a key number or finding:
490
+ - \`DONE\` \u2014 ran cleanly, nothing to flag
491
+ - \`WARN\` \u2014 soft signal worth surfacing (e.g. a borderline backlog,
492
+ mild spike or drop, a config you'd want a human to confirm)
493
+ - \`FAIL\` \u2014 concrete failure that the user should act on
494
+ - Examples:
495
+ - "DONE \u2014 14/14 detection configs passing."
496
+ - "WARN \u2014 23 proposals in the review queue; 4 are older than 7 days."
497
+ - "FAIL \u2014 twitter_post_search returned 0 results in the last 24h with --run."
498
+
499
+ 3. Run **independent** checks in **parallel** (single message, multiple
500
+ Bash tool calls) so progress lines arrive quickly. \`detections
501
+ healthcheck\`, \`queues snapshot\`, \`metrics breakdown\`,
502
+ \`reports list --reported-by-customer\`, and \`configs list\` are all
503
+ independent of each other and safe to fire concurrently. Dependent
504
+ follow-ups (e.g. paginating \`reports list\` with a returned cursor,
505
+ or fetching extra detail on a single failing config) run after the
506
+ first round.
507
+
508
+ 4. After every check is reported, emit a short final **Summary** section:
509
+ - A one-line status per check (\u2713 / \u26A0 / \u2717 + name + key number).
510
+ - A short "Top issues" list of the highest-priority FAIL / WARN
511
+ findings, in priority order, with the concrete next action for each.
512
+
513
+ Keep each progress line to one sentence. The goal is for the user to see
514
+ the healthcheck happening, not to read a wall of text mid-run \u2014 full
515
+ detail belongs in the final Summary or in a follow-up when the user asks
516
+ about a specific finding.
300
517
 
301
518
  ### Detection
302
519
 
@@ -307,18 +524,37 @@ turned off. For example: lots of Twitter assets are on the blocklist, but
307
524
  detection sources like "Twitter / X User Search" or "Twitter Post Search" are
308
525
  disabled. Those should be turned on.
309
526
 
527
+ **Run via CLI:** there is no single command for this \u2014 it's a manual
528
+ correlation. Fetch the org's enabled vs disabled configs with
529
+ \`chainpatrol --json configs list --org <slug>\`, then look at recent reports
530
+ (\`chainpatrol --json reports list --org <slug>\`) and the asset types of any
531
+ blocked items. Flag any asset type where blocked items exist but the matching
532
+ detection source has \`status: "disabled"\` (or appears in the "Not configured"
533
+ group).
534
+
310
535
  #### Spike in Detections
311
536
 
312
537
  A spike in recent detections is worth investigating. It could be a bad config
313
538
  change, or it could be a legitimate new attack push in this area \u2014 useful
314
539
  intel to surface to the security team as a targeted spike.
315
540
 
541
+ **Run via CLI:** \`chainpatrol --json metrics breakdown --org <slug> --by day --this-week\`
542
+ (and a comparison window via \`--from\`/\`--to\`) to see daily detection
543
+ volume. Anything notably above the recent baseline is a spike \u2014 cross-reference
544
+ against recent config changes for that source.
545
+
316
546
  #### Drop in Detections
317
547
 
318
548
  A drop is also worth looking into. It may be a bad config change, or it may
319
549
  mean the config is not really relevant and can be safely turned off (not all
320
550
  default-on configs are relevant to every org).
321
551
 
552
+ **Run via CLI:** the same \`metrics breakdown\` time series catches drops.
553
+ Additionally, \`chainpatrol --json detections healthcheck --org <slug>\`
554
+ fails configs whose \`recentResultCount\` is below \`--min-results\` in the
555
+ \`--lookback-hours\` window \u2014 that's exactly a "this source went silent"
556
+ signal. Use \`--run\` to also catch configs that error when executed.
557
+
322
558
  ### Reviewing
323
559
 
324
560
  #### Pile Up / Backlog of Unreviewed Proposals
@@ -328,6 +564,13 @@ reports, but really the threshold is relative to the average number of
328
564
  confirmed threats per week. Example: if an org only adds 5 blocked threats per
329
565
  week, then a 7-day backlog of even 10 proposals is a really big deal.
330
566
 
567
+ **Run via CLI:** \`chainpatrol --json queues snapshot --org <slug>\` returns
568
+ \`reviewQueue.totalPendingProposals\` and \`reviewQueue.distinctReports\`.
569
+ Compare against the org's typical weekly throughput (use
570
+ \`metrics summary --this-week\` for that baseline) \u2014 a backlog that exceeds
571
+ a week of typical confirmed-threat volume is a finding regardless of the
572
+ absolute number.
573
+
331
574
  #### Really Old Proposals
332
575
 
333
576
  Any proposal waiting for review longer than 14 days is a sign something has
@@ -335,6 +578,12 @@ gone wrong. Even complex investigations rarely take longer than this. Except
335
578
  for rare cases, these should be rejected or approved to prevent a backlog
336
579
  from building.
337
580
 
581
+ **Run via CLI:** \`queues snapshot\` returns \`reviewQueue.ageBuckets.gte168h\`
582
+ (proposals older than 7 days). Anything in that bucket is at least halfway
583
+ to the 14-day threshold; investigate. \`reviewQueue.slaBuckets.breached\`
584
+ captures the strictest SLA breaches separately \u2014 any non-zero value is
585
+ worth raising.
586
+
338
587
  #### Spike in Auto Approved Reports
339
588
 
340
589
  If suddenly a lot of proposals in an org are being approved by automation,
@@ -345,6 +594,12 @@ cases, notify an engineer at ChainPatrol and check any detection configs you
345
594
  adjusted recently, since those may be the cause of spam combined with a weak
346
595
  rule.
347
596
 
597
+ **Run via CLI:** there is no dedicated subcommand for the auto-approval
598
+ rate yet. As a proxy, use \`chainpatrol --json metrics breakdown --org <slug> --by day --this-week\`
599
+ and look for a sudden surge in \`newThreats\` / \`threatsWatchlisted\` that
600
+ isn't matched by a parallel rise in reviewer activity \u2014 that gap usually
601
+ points at automation doing the approving.
602
+
348
603
  ### Blocklisting
349
604
 
350
605
  #### Google Safe Browsing (Coming Soon)
@@ -358,6 +613,10 @@ also take a look at the org's custom detection sources \u2014 it's possible ther
358
613
  are too many false positives landing on the blocklist, indicating issues with
359
614
  detection and reviewing rules.
360
615
 
616
+ **Run via CLI:** not yet \u2014 the CLI does not expose Google Safe Browsing
617
+ submission state today. Until the new public API lands, this remains a
618
+ manual / engineering-team check.
619
+
361
620
  ### Takedowns
362
621
 
363
622
  #### Too Many Takedowns in ToDo
@@ -366,12 +625,22 @@ Can mean a gap in automated takedowns not being implemented for some new area
366
625
  of threats. It can also mean the areas that require manual takedowns are
367
626
  being missed by the takedown team.
368
627
 
628
+ **Run via CLI:** \`chainpatrol --json queues snapshot --org <slug>\` returns
629
+ \`takedownQueue.totalOpen\` and breakdowns by status. A persistently large
630
+ ToDo bucket relative to the org's typical takedown rate (use
631
+ \`metrics summary\` for that baseline) is the finding.
632
+
369
633
  #### Too Many Takedowns In Progress
370
634
 
371
635
  Typically means something is wrong with the submission itself. The takedown
372
636
  may need to be resubmitted, the vendor asked for more evidence, or we may
373
637
  have submitted it in the wrong place.
374
638
 
639
+ **Run via CLI:** \`queues snapshot\` returns \`takedownQueue.staleInProgress\`
640
+ \u2014 takedowns that have sat in In Progress past the expected vendor turnaround.
641
+ Any non-zero value is worth investigating; a growing number across snapshots
642
+ strongly suggests a vendor-side or submission-format problem.
643
+
375
644
  #### Too Many Cancelled Takedowns
376
645
 
377
646
  Takedowns should rarely be cancelled. A cancelled takedown means "we will not
@@ -379,11 +648,21 @@ do this takedown" for some reason. Cases like adding an item to the blocklist
379
648
  when it's already taken down are treated as completed, not cancelled. So even
380
649
  3 cancelled takedowns in a 7-day period is too many.
381
650
 
651
+ **Run via CLI:** no first-class command yet. As an interim signal, use
652
+ \`chainpatrol --json metrics breakdown --org <slug> --by day --this-week\`
653
+ and compare \`takedownsFiled\` vs \`takedownsCompleted\` for a sudden
654
+ divergence \u2014 a widening gap with no In-Progress growth often shows up as
655
+ cancellations.
656
+
382
657
  #### Automated Takedowns Turned Off for Over 30 Days
383
658
 
384
659
  Automated takedowns should be on by default for nearly every organization.
385
660
  Any issue that would make you want to turn off automated takedowns should be
386
661
  resolved within 30 days.
662
+
663
+ **Run via CLI:** not yet exposed in a single command. Check this manually
664
+ with the org's takedown automation settings; the CLI's role here is mostly
665
+ to highlight stale state in \`queues snapshot\` so you know to ask.
387
666
  `;
388
667
  }
389
668
  function getBundledSkillVersion() {
@@ -0,0 +1,83 @@
1
+ import {
2
+ fetchUserEmail,
3
+ getCredentials,
4
+ isLoggedIn,
5
+ pollForToken,
6
+ requestDeviceCode,
7
+ storeCredentials
8
+ } from "./chunk-EEG7T6WT.js";
9
+ import {
10
+ DateTime
11
+ } from "./chunk-TFCNKBRC.js";
12
+
13
+ // src/lib/login-flow.ts
14
+ async function runLoginFlow(emit) {
15
+ if (isLoggedIn()) {
16
+ const creds = getCredentials();
17
+ emit({ type: "already_logged_in", email: creds.email ?? null });
18
+ return true;
19
+ }
20
+ const deviceCode = await requestDeviceCode();
21
+ emit({ type: "device_code", code: deviceCode });
22
+ let interval = deviceCode.interval * 1e3;
23
+ while (true) {
24
+ await new Promise((resolve) => setTimeout(resolve, interval));
25
+ const result = await pollForToken(deviceCode.device_code);
26
+ switch (result.status) {
27
+ case "success": {
28
+ const expiresAt = DateTime.now().toUTC().plus({ seconds: result.expiresIn }).toISO();
29
+ if (!expiresAt) {
30
+ emit({ type: "error", message: "Failed to compute credential expiry." });
31
+ return false;
32
+ }
33
+ const email = await fetchUserEmail(result.accessToken);
34
+ storeCredentials({
35
+ accessToken: result.accessToken,
36
+ expiresAt,
37
+ ...email ? { email } : {}
38
+ });
39
+ emit({ type: "success", email: email ?? null });
40
+ return true;
41
+ }
42
+ case "pending":
43
+ continue;
44
+ case "slow_down":
45
+ interval += result.addSeconds * 1e3;
46
+ continue;
47
+ case "expired":
48
+ emit({ type: "expired" });
49
+ return false;
50
+ case "denied":
51
+ emit({ type: "denied" });
52
+ return false;
53
+ case "error":
54
+ emit({ type: "error", message: result.message });
55
+ return false;
56
+ }
57
+ }
58
+ }
59
+ function formatUserCode(code) {
60
+ return code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;
61
+ }
62
+
63
+ // src/lib/headless.ts
64
+ function isHeadlessEnv(env = process.env, platform = process.platform) {
65
+ if (env.CHAINPATROL_HEADLESS === "1") return true;
66
+ if (env.CHAINPATROL_HEADLESS === "0") return false;
67
+ if (env.CLAUDE_CODE_REMOTE === "true") return true;
68
+ if (env.CODESPACES === "true") return true;
69
+ if (env.GITPOD_WORKSPACE_ID) return true;
70
+ if (env.REPL_ID) return true;
71
+ if (env.CI === "true") return true;
72
+ if (env.SSH_CONNECTION || env.SSH_TTY) return true;
73
+ if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY && !env.MIR_SOCKET) {
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+
79
+ export {
80
+ runLoginFlow,
81
+ formatUserCode,
82
+ isHeadlessEnv
83
+ };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
11
+ } from "./chunk-44FSS3CZ.js";
12
12
  import {
13
13
  DateTime
14
14
  } from "./chunk-TFCNKBRC.js";
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  getCliVersion,
14
14
  isSkillInstalled,
15
15
  readInstalledSkillVersion
16
- } from "./chunk-S7PQNG4E.js";
16
+ } from "./chunk-DOL35U2S.js";
17
17
  import "./chunk-IUZB3DQW.js";
18
18
  import {
19
19
  DateTime
@@ -686,12 +686,12 @@ function parseAttachmentUrls() {
686
686
  }
687
687
  async function handleConfigsList(org) {
688
688
  if (jsonMode) {
689
- const { listConfigsJson } = await import("./list-json-GANUT7SB.js");
689
+ const { listConfigsJson } = await import("./list-json-SUZL2GBJ.js");
690
690
  await listConfigsJson({ org });
691
691
  return;
692
692
  }
693
693
  const { render } = await import("ink");
694
- const { default: ConfigsList } = await import("./list-56BS25X4.js");
694
+ const { default: ConfigsList } = await import("./list-TMFLCS5U.js");
695
695
  const { default: React } = await import("react");
696
696
  render(React.createElement(ConfigsList, { org }));
697
697
  }
@@ -746,11 +746,14 @@ async function main() {
746
746
  );
747
747
  }
748
748
  if (jsonMode) {
749
- const { loginJson } = await import("./login-json-Y3AIQIIB.js");
749
+ const { loginJson } = await import("./login-json-XGMXT5VJ.js");
750
750
  await loginJson();
751
+ } else if (!process.stdout.isTTY) {
752
+ const { loginPlain } = await import("./login-plain-CWKOUZKE.js");
753
+ await loginPlain();
751
754
  } else {
752
755
  const { render } = await import("ink");
753
- const { default: Login } = await import("./login-ML2EKF44.js");
756
+ const { default: Login } = await import("./login-C66GRR3Y.js");
754
757
  const { default: React } = await import("react");
755
758
  render(React.createElement(Login));
756
759
  }
@@ -786,7 +789,7 @@ async function main() {
786
789
  case "detections": {
787
790
  const org = await resolveOrg();
788
791
  if (subcommand === "healthcheck") {
789
- const { runDetectionsHealthcheck } = await import("./healthcheck-6RHZBEVE.js");
792
+ const { runDetectionsHealthcheck } = await import("./healthcheck-SIE5EXN3.js");
790
793
  await runDetectionsHealthcheck({
791
794
  org,
792
795
  source: cli.flags.source,
@@ -801,7 +804,7 @@ async function main() {
801
804
  break;
802
805
  }
803
806
  if (subcommand === "validate") {
804
- const { runDetectionsValidate } = await import("./validate-NIBVL7X5.js");
807
+ const { runDetectionsValidate } = await import("./validate-II6GH6H6.js");
805
808
  await runDetectionsValidate({
806
809
  org,
807
810
  source: cli.flags.source,
@@ -816,7 +819,7 @@ async function main() {
816
819
  break;
817
820
  }
818
821
  if (subcommand === "drift") {
819
- const { runDetectionsDrift } = await import("./drift-2JRZK2MC.js");
822
+ const { runDetectionsDrift } = await import("./drift-PASGDEEX.js");
820
823
  await runDetectionsDrift({
821
824
  org,
822
825
  source: cli.flags.source,
@@ -830,7 +833,7 @@ async function main() {
830
833
  break;
831
834
  }
832
835
  if (subcommand === "run") {
833
- const { runDetectionsRun } = await import("./run-JZNZSCDJ.js");
836
+ const { runDetectionsRun } = await import("./run-MGOASUON.js");
834
837
  await runDetectionsRun({
835
838
  org,
836
839
  configId: cli.flags.configId,
@@ -849,7 +852,7 @@ async function main() {
849
852
  break;
850
853
  }
851
854
  if (action === "run") {
852
- const { runDetectionsRun } = await import("./run-JZNZSCDJ.js");
855
+ const { runDetectionsRun } = await import("./run-MGOASUON.js");
853
856
  await runDetectionsRun({
854
857
  org,
855
858
  configId: cli.flags.configId,
@@ -867,7 +870,7 @@ async function main() {
867
870
  throw new Error("detections configs update requires --config-id");
868
871
  }
869
872
  const configPatch = getConfigPatchFromSetFlags();
870
- const { runDetectionsConfigsUpdate } = await import("./configs-update-AHI7T6S4.js");
873
+ const { runDetectionsConfigsUpdate } = await import("./configs-update-VOWH674W.js");
871
874
  await runDetectionsConfigsUpdate({
872
875
  org,
873
876
  configId: cli.flags.configId,
@@ -894,7 +897,7 @@ async function main() {
894
897
  case "metrics": {
895
898
  const org = await resolveOrg();
896
899
  if (subcommand === "summary") {
897
- const { runMetricsSummary } = await import("./summary-PA2CCZVO.js");
900
+ const { runMetricsSummary } = await import("./summary-QORQFCMW.js");
898
901
  await runMetricsSummary({
899
902
  org,
900
903
  from: cli.flags.from,
@@ -906,7 +909,7 @@ async function main() {
906
909
  break;
907
910
  }
908
911
  if (subcommand === "found") {
909
- const { runMetricsFound } = await import("./found-YYYFF4FD.js");
912
+ const { runMetricsFound } = await import("./found-TIW7T4VX.js");
910
913
  await runMetricsFound({
911
914
  org,
912
915
  from: cli.flags.from,
@@ -923,7 +926,7 @@ async function main() {
923
926
  if (!by || !["day", "type", "brand"].includes(by)) {
924
927
  throw new Error("metrics breakdown requires --by <day|type|brand>");
925
928
  }
926
- const { runMetricsBreakdown } = await import("./breakdown-BECXDJIS.js");
929
+ const { runMetricsBreakdown } = await import("./breakdown-FFNYSXRQ.js");
927
930
  await runMetricsBreakdown({
928
931
  org,
929
932
  by,
@@ -943,7 +946,7 @@ async function main() {
943
946
  case "reports": {
944
947
  if (subcommand === "list") {
945
948
  const org = await resolveOrg();
946
- const { runReportsList } = await import("./list-MHEIY7LQ.js");
949
+ const { runReportsList } = await import("./list-CEWBMKJ3.js");
947
950
  await runReportsList({
948
951
  org,
949
952
  limit: cli.flags.limit,
@@ -959,7 +962,7 @@ async function main() {
959
962
  }
960
963
  if (subcommand === "create") {
961
964
  const org = await tryResolveOrg();
962
- const { runReportsCreate } = await import("./create-RS37JAMM.js");
965
+ const { runReportsCreate } = await import("./create-ASGUBKZ7.js");
963
966
  await runReportsCreate({
964
967
  org,
965
968
  title: cli.flags.title,
@@ -983,7 +986,7 @@ async function main() {
983
986
  }
984
987
  case "queues": {
985
988
  if (subcommand === "snapshot") {
986
- const { runQueuesSnapshot } = await import("./snapshot-KGMO44YB.js");
989
+ const { runQueuesSnapshot } = await import("./snapshot-MQ6DWDFG.js");
987
990
  await runQueuesSnapshot({
988
991
  org: cli.flags.org,
989
992
  all: cli.flags.all,
@@ -1001,7 +1004,7 @@ async function main() {
1001
1004
  }
1002
1005
  case "presets": {
1003
1006
  if (subcommand === "list") {
1004
- const { runPresetsList } = await import("./list-M2UQCXIO.js");
1007
+ const { runPresetsList } = await import("./list-KTIZ3UHA.js");
1005
1008
  await runPresetsList({ outputFormat: cliContext.outputFormat });
1006
1009
  break;
1007
1010
  }
@@ -1012,7 +1015,7 @@ async function main() {
1012
1015
  );
1013
1016
  }
1014
1017
  const org = await resolveOrg();
1015
- const { runPresetsRun } = await import("./run-7NTCD7JI.js");
1018
+ const { runPresetsRun } = await import("./run-3TTLZ6HA.js");
1016
1019
  await runPresetsRun({
1017
1020
  presetId: action,
1018
1021
  org,
@@ -1029,12 +1032,12 @@ async function main() {
1029
1032
  case "setup":
1030
1033
  case "install":
1031
1034
  case "i": {
1032
- const { setupSkill } = await import("./setup-skill-XZRLJE3A.js");
1035
+ const { setupSkill } = await import("./setup-skill-PCUBJJYU.js");
1033
1036
  setupSkill({ json: jsonMode });
1034
1037
  break;
1035
1038
  }
1036
1039
  case "uninstall": {
1037
- const { uninstallSkill } = await import("./setup-skill-XZRLJE3A.js");
1040
+ const { uninstallSkill } = await import("./setup-skill-PCUBJJYU.js");
1038
1041
  uninstallSkill({ json: jsonMode });
1039
1042
  break;
1040
1043
  }
@@ -7,9 +7,9 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-DSOM6TZX.js";
11
- import "./chunk-TFCNKBRC.js";
10
+ } from "./chunk-44FSS3CZ.js";
12
11
  import "./chunk-EEG7T6WT.js";
12
+ import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
15
15
  // src/commands/detections/configs-update.ts
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
12
- import "./chunk-TFCNKBRC.js";
11
+ } from "./chunk-44FSS3CZ.js";
13
12
  import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/reports/create.ts
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
12
- import "./chunk-TFCNKBRC.js";
11
+ } from "./chunk-44FSS3CZ.js";
13
12
  import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/detections/drift.ts
@@ -4,11 +4,11 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-DSOM6TZX.js";
7
+ } from "./chunk-44FSS3CZ.js";
8
+ import "./chunk-EEG7T6WT.js";
8
9
  import {
9
10
  DateTime
10
11
  } from "./chunk-TFCNKBRC.js";
11
- import "./chunk-EEG7T6WT.js";
12
12
  import "./chunk-U73SABXK.js";
13
13
 
14
14
  // src/lib/date-range.ts
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
12
- import "./chunk-TFCNKBRC.js";
11
+ } from "./chunk-44FSS3CZ.js";
13
12
  import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/detections/healthcheck.ts
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
12
- import "./chunk-TFCNKBRC.js";
11
+ } from "./chunk-44FSS3CZ.js";
13
12
  import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/reports/list.ts
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  PRESETS
3
- } from "./chunk-B3CCMSG2.js";
3
+ } from "./chunk-WBKCXGLV.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-DSOM6TZX.js";
10
- import "./chunk-TFCNKBRC.js";
9
+ import "./chunk-44FSS3CZ.js";
11
10
  import "./chunk-EEG7T6WT.js";
11
+ import "./chunk-TFCNKBRC.js";
12
12
  import "./chunk-U73SABXK.js";
13
13
 
14
14
  // src/commands/presets/list.ts
@@ -1,16 +1,16 @@
1
- import {
2
- createApiClient
3
- } from "./chunk-DSOM6TZX.js";
4
- import "./chunk-TFCNKBRC.js";
5
1
  import {
6
2
  ErrorDisplay,
7
3
  Spinner
8
4
  } from "./chunk-JCMWDZYY.js";
5
+ import {
6
+ createApiClient
7
+ } from "./chunk-44FSS3CZ.js";
9
8
  import {
10
9
  AuthCorruptedError,
11
10
  AuthExpiredError,
12
11
  AuthNotLoggedInError
13
12
  } from "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/configs/list.tsx
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-DSOM6TZX.js";
4
- import "./chunk-TFCNKBRC.js";
3
+ } from "./chunk-44FSS3CZ.js";
5
4
  import "./chunk-EEG7T6WT.js";
5
+ import "./chunk-TFCNKBRC.js";
6
6
  import "./chunk-U73SABXK.js";
7
7
 
8
8
  // src/commands/configs/list-json.ts
@@ -3,8 +3,9 @@ import {
3
3
  Spinner
4
4
  } from "./chunk-JCMWDZYY.js";
5
5
  import {
6
+ formatUserCode,
6
7
  isHeadlessEnv
7
- } from "./chunk-ZVM45CTB.js";
8
+ } from "./chunk-EBJMOX3Q.js";
8
9
  import {
9
10
  fetchUserEmail,
10
11
  getCredentials,
@@ -13,6 +14,7 @@ import {
13
14
  requestDeviceCode,
14
15
  storeCredentials
15
16
  } from "./chunk-EEG7T6WT.js";
17
+ import "./chunk-TFCNKBRC.js";
16
18
  import "./chunk-U73SABXK.js";
17
19
 
18
20
  // src/commands/login.tsx
@@ -140,7 +142,7 @@ function Login() {
140
142
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
141
143
  /* @__PURE__ */ jsxs(Box, { children: [
142
144
  /* @__PURE__ */ jsx(Text, { children: "Your code: " }),
143
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: state.deviceCode.user_code.length === 8 ? `${state.deviceCode.user_code.slice(0, 4)}-${state.deviceCode.user_code.slice(4)}` : state.deviceCode.user_code })
145
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: formatUserCode(state.deviceCode.user_code) })
144
146
  ] }),
145
147
  completeUri ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
146
148
  /* @__PURE__ */ jsx(Text, { children: "Open this URL on any device to approve in one step:" }),
@@ -0,0 +1,50 @@
1
+ import {
2
+ isHeadlessEnv,
3
+ runLoginFlow
4
+ } from "./chunk-EBJMOX3Q.js";
5
+ import "./chunk-EEG7T6WT.js";
6
+ import "./chunk-TFCNKBRC.js";
7
+ import "./chunk-U73SABXK.js";
8
+
9
+ // src/commands/login-json.ts
10
+ async function loginJson() {
11
+ const ok = await runLoginFlow((event) => {
12
+ switch (event.type) {
13
+ case "already_logged_in":
14
+ console.log(JSON.stringify({ status: "already_logged_in", email: event.email }));
15
+ return;
16
+ case "device_code":
17
+ console.log(
18
+ JSON.stringify({
19
+ action: "open_url",
20
+ user_code: event.code.user_code,
21
+ verification_uri: event.code.verification_uri,
22
+ verification_uri_complete: event.code.verification_uri_complete ?? null,
23
+ expires_in: event.code.expires_in,
24
+ headless: isHeadlessEnv()
25
+ })
26
+ );
27
+ return;
28
+ case "success":
29
+ console.log(JSON.stringify({ status: "success", email: event.email }));
30
+ return;
31
+ case "expired":
32
+ console.error(
33
+ JSON.stringify({ error: "Code expired. Run `chainpatrol login` again." })
34
+ );
35
+ return;
36
+ case "denied":
37
+ console.error(JSON.stringify({ error: "Authorization denied." }));
38
+ return;
39
+ case "error":
40
+ console.error(JSON.stringify({ error: event.message }));
41
+ return;
42
+ }
43
+ });
44
+ if (!ok) {
45
+ process.exit(1);
46
+ }
47
+ }
48
+ export {
49
+ loginJson
50
+ };
@@ -0,0 +1,60 @@
1
+ import {
2
+ formatUserCode,
3
+ isHeadlessEnv,
4
+ runLoginFlow
5
+ } from "./chunk-EBJMOX3Q.js";
6
+ import "./chunk-EEG7T6WT.js";
7
+ import "./chunk-TFCNKBRC.js";
8
+ import "./chunk-U73SABXK.js";
9
+
10
+ // src/commands/login-plain.ts
11
+ async function loginPlain() {
12
+ const ok = await runLoginFlow((event) => {
13
+ switch (event.type) {
14
+ case "already_logged_in":
15
+ console.log(
16
+ event.email ? `Already logged in as ${event.email}` : "Already logged in"
17
+ );
18
+ return;
19
+ case "device_code": {
20
+ const completeUri = event.code.verification_uri_complete;
21
+ const userCode = formatUserCode(event.code.user_code);
22
+ if (completeUri) {
23
+ console.log("Open this URL on any device to approve in one step:");
24
+ console.log(` ${completeUri}`);
25
+ console.log(
26
+ `Or visit ${event.code.verification_uri} and enter the code: ${userCode}`
27
+ );
28
+ } else {
29
+ console.log(`Open this URL in your browser: ${event.code.verification_uri}`);
30
+ console.log(`Enter the code: ${userCode}`);
31
+ }
32
+ if (isHeadlessEnv()) {
33
+ console.log("(headless environment detected \u2014 browser will not auto-open)");
34
+ }
35
+ console.log("Waiting for approval...");
36
+ return;
37
+ }
38
+ case "success":
39
+ console.log(
40
+ event.email ? `Logged in successfully as ${event.email}` : "Logged in successfully"
41
+ );
42
+ return;
43
+ case "expired":
44
+ console.error("Code expired. Run `chainpatrol login` again.");
45
+ return;
46
+ case "denied":
47
+ console.error("Authorization denied.");
48
+ return;
49
+ case "error":
50
+ console.error(event.message);
51
+ return;
52
+ }
53
+ });
54
+ if (!ok) {
55
+ process.exit(1);
56
+ }
57
+ }
58
+ export {
59
+ loginPlain
60
+ };
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  getPresetDefinition,
3
3
  runPreset
4
- } from "./chunk-B3CCMSG2.js";
4
+ } from "./chunk-WBKCXGLV.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-DSOM6TZX.js";
11
- import "./chunk-TFCNKBRC.js";
10
+ import "./chunk-44FSS3CZ.js";
12
11
  import "./chunk-EEG7T6WT.js";
12
+ import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
15
15
  // src/commands/presets/run.ts
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
12
- import "./chunk-TFCNKBRC.js";
11
+ } from "./chunk-44FSS3CZ.js";
13
12
  import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/detections/run.ts
@@ -6,7 +6,7 @@ import {
6
6
  readInstalledSkillVersion,
7
7
  setupSkill,
8
8
  uninstallSkill
9
- } from "./chunk-S7PQNG4E.js";
9
+ } from "./chunk-DOL35U2S.js";
10
10
  import "./chunk-IUZB3DQW.js";
11
11
  export {
12
12
  getBundledSkillContent,
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
12
- import "./chunk-TFCNKBRC.js";
11
+ } from "./chunk-44FSS3CZ.js";
13
12
  import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/queues/snapshot.ts
@@ -4,9 +4,9 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-DSOM6TZX.js";
8
- import "./chunk-TFCNKBRC.js";
7
+ } from "./chunk-44FSS3CZ.js";
9
8
  import "./chunk-EEG7T6WT.js";
9
+ import "./chunk-TFCNKBRC.js";
10
10
  import "./chunk-U73SABXK.js";
11
11
 
12
12
  // src/commands/metrics/summary.ts
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-DSOM6TZX.js";
12
- import "./chunk-TFCNKBRC.js";
11
+ } from "./chunk-44FSS3CZ.js";
13
12
  import "./chunk-EEG7T6WT.js";
13
+ import "./chunk-TFCNKBRC.js";
14
14
  import "./chunk-U73SABXK.js";
15
15
 
16
16
  // src/commands/detections/validate.ts
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.3.2",
5
+ "version": "0.3.4",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://chainpatrol.com/docs/cli",
8
8
  "keywords": [
@@ -1,19 +0,0 @@
1
- // src/lib/headless.ts
2
- function isHeadlessEnv(env = process.env, platform = process.platform) {
3
- if (env.CHAINPATROL_HEADLESS === "1") return true;
4
- if (env.CHAINPATROL_HEADLESS === "0") return false;
5
- if (env.CLAUDE_CODE_REMOTE === "true") return true;
6
- if (env.CODESPACES === "true") return true;
7
- if (env.GITPOD_WORKSPACE_ID) return true;
8
- if (env.REPL_ID) return true;
9
- if (env.CI === "true") return true;
10
- if (env.SSH_CONNECTION || env.SSH_TTY) return true;
11
- if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY && !env.MIR_SOCKET) {
12
- return true;
13
- }
14
- return false;
15
- }
16
-
17
- export {
18
- isHeadlessEnv
19
- };
@@ -1,75 +0,0 @@
1
- import {
2
- isHeadlessEnv
3
- } from "./chunk-ZVM45CTB.js";
4
- import {
5
- fetchUserEmail,
6
- getCredentials,
7
- isLoggedIn,
8
- pollForToken,
9
- requestDeviceCode,
10
- storeCredentials
11
- } from "./chunk-EEG7T6WT.js";
12
- import "./chunk-U73SABXK.js";
13
-
14
- // src/commands/login-json.ts
15
- async function loginJson() {
16
- if (isLoggedIn()) {
17
- const creds = getCredentials();
18
- console.log(
19
- JSON.stringify({ status: "already_logged_in", email: creds.email ?? null })
20
- );
21
- return;
22
- }
23
- const deviceCode = await requestDeviceCode();
24
- console.error(
25
- JSON.stringify({
26
- action: "open_url",
27
- user_code: deviceCode.user_code,
28
- verification_uri: deviceCode.verification_uri,
29
- verification_uri_complete: deviceCode.verification_uri_complete ?? null,
30
- expires_in: deviceCode.expires_in,
31
- headless: isHeadlessEnv()
32
- })
33
- );
34
- let interval = deviceCode.interval * 1e3;
35
- while (true) {
36
- await new Promise((r) => setTimeout(r, interval));
37
- const result = await pollForToken(deviceCode.device_code);
38
- switch (result.status) {
39
- case "success": {
40
- const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
41
- const email = await fetchUserEmail(result.accessToken);
42
- const creds = {
43
- accessToken: result.accessToken,
44
- expiresAt,
45
- ...email ? { email } : {}
46
- };
47
- storeCredentials(creds);
48
- console.log(JSON.stringify({ status: "success", email: email ?? null }));
49
- return;
50
- }
51
- case "pending":
52
- continue;
53
- case "slow_down":
54
- interval += result.addSeconds * 1e3;
55
- continue;
56
- case "expired":
57
- console.error(
58
- JSON.stringify({ error: "Code expired. Run `chainpatrol login` again." })
59
- );
60
- process.exit(1);
61
- break;
62
- case "denied":
63
- console.error(JSON.stringify({ error: "Authorization denied." }));
64
- process.exit(1);
65
- break;
66
- case "error":
67
- console.error(JSON.stringify({ error: result.message }));
68
- process.exit(1);
69
- break;
70
- }
71
- }
72
- }
73
- export {
74
- loginJson
75
- };
@@ -1,9 +1,9 @@
1
- import {
2
- DateTime
3
- } from "./chunk-TFCNKBRC.js";
4
1
  import {
5
2
  getValidCredentials
6
3
  } from "./chunk-EEG7T6WT.js";
4
+ import {
5
+ DateTime
6
+ } from "./chunk-TFCNKBRC.js";
7
7
  import {
8
8
  getConfig
9
9
  } from "./chunk-U73SABXK.js";