@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.
- package/CHANGELOG.md +38 -0
- package/dist/{breakdown-BECXDJIS.js → breakdown-FFNYSXRQ.js} +2 -2
- package/dist/{chunk-S7PQNG4E.js → chunk-DOL35U2S.js} +286 -7
- package/dist/chunk-EBJMOX3Q.js +83 -0
- package/dist/{chunk-B3CCMSG2.js → chunk-WBKCXGLV.js} +1 -1
- package/dist/cli.js +24 -21
- package/dist/{configs-update-AHI7T6S4.js → configs-update-VOWH674W.js} +2 -2
- package/dist/{create-RS37JAMM.js → create-ASGUBKZ7.js} +2 -2
- package/dist/{drift-2JRZK2MC.js → drift-PASGDEEX.js} +2 -2
- package/dist/{found-YYYFF4FD.js → found-TIW7T4VX.js} +2 -2
- package/dist/{healthcheck-6RHZBEVE.js → healthcheck-SIE5EXN3.js} +2 -2
- package/dist/{list-MHEIY7LQ.js → list-CEWBMKJ3.js} +2 -2
- package/dist/{list-M2UQCXIO.js → list-KTIZ3UHA.js} +3 -3
- package/dist/{list-56BS25X4.js → list-TMFLCS5U.js} +4 -4
- package/dist/{list-json-GANUT7SB.js → list-json-SUZL2GBJ.js} +2 -2
- package/dist/{login-ML2EKF44.js → login-C66GRR3Y.js} +4 -2
- package/dist/login-json-XGMXT5VJ.js +50 -0
- package/dist/login-plain-CWKOUZKE.js +60 -0
- package/dist/{run-7NTCD7JI.js → run-3TTLZ6HA.js} +3 -3
- package/dist/{run-JZNZSCDJ.js → run-MGOASUON.js} +2 -2
- package/dist/{setup-skill-XZRLJE3A.js → setup-skill-PCUBJJYU.js} +1 -1
- package/dist/{snapshot-KGMO44YB.js → snapshot-MQ6DWDFG.js} +2 -2
- package/dist/{summary-PA2CCZVO.js → summary-QORQFCMW.js} +2 -2
- package/dist/{validate-NIBVL7X5.js → validate-II6GH6H6.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-ZVM45CTB.js +0 -19
- package/dist/login-json-Y3AIQIIB.js +0 -75
- 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-
|
|
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
|
|
267
|
-
|
|
268
|
-
| \`--json\`
|
|
269
|
-
| \`--
|
|
270
|
-
| \`--
|
|
271
|
-
| \`--
|
|
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
|
+
};
|
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-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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
@@ -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-
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
} from "./chunk-VFT3TD3E.js";
|
|
9
9
|
import {
|
|
10
10
|
createApiClient
|
|
11
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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.
|
|
5
|
+
"version": "0.3.4",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"homepage": "https://chainpatrol.com/docs/cli",
|
|
8
8
|
"keywords": [
|
package/dist/chunk-ZVM45CTB.js
DELETED
|
@@ -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
|
-
};
|