@chainpatrol/sdk 0.11.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/skill/content.ts","../../src/skill/parse.ts"],"names":[],"mappings":";;;AAAO,SAAS,kBAAkB,OAAA,EAAyB;AACzD,EAAA,OAAO,CAAA;AAAA;AAAA,SAAA,EAEE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAumDlB;;;AC1mDO,SAAS,kBAAkB,OAAA,EAAqC;AACrE,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,6BAA6B,CAAA;AAC3D,EAAA,IAAI,CAAC,SAAS,OAAO,MAAA;AACrB,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,CAAC,CAAA,CAAE,MAAM,wBAAwB,CAAA;AAC9D,EAAA,OAAO,YAAA,GAAe,YAAA,CAAa,CAAC,CAAA,CAAE,MAAK,GAAI,MAAA;AACjD","file":"index.js","sourcesContent":["export function buildSkillContent(version: string): string {\n return `---\nname: chainpatrol\nversion: ${version}\ndescription: |\n ChainPatrol CLI assistant. Helps use the chainpatrol CLI tool: login via device\n code flow, check auth status, list detection configs, list reports (including\n customer-reported ones), run CLI commands, and run an organization\n healthcheck across the detection / reviewing / blocklisting / takedown\n pipeline.\n Use when: \"chainpatrol cli\", \"login to chainpatrol\", \"check detection configs\",\n \"am I logged in\", \"list configs\", \"use the cli\", \"list reports\",\n \"customer reports\", \"reports reported by customer\", \"find detection gaps\",\n \"org healthcheck\", \"organization health check\", \"audit my org\",\n \"what's wrong with org\", \"review org setup\", \"list orgs\", \"list organizations\",\n \"get org\", \"get organization\", \"show org details\", \"org by slug\",\n \"fetch org details\", \"org info\",\n \"orgs with takedowns off\", \"automation off across orgs\",\n \"which customers have X enabled\", \"service toggles by org\",\n \"obligatory admin approval\", \"obligatory organization admin approval\",\n \"obligatory approval\", \"admin approval enabled\", \"admin approval orgs\",\n \"requires customer review\", \"requires admin approval\",\n \"orgs that require admin approval\", \"orgs requiring approval\",\n \"which orgs require approval for twitter\", \"approval scope\",\n \"admin approval asset types\", \"approval per asset type\",\n \"pending approval\", \"pending approvals\", \"pending service approval\",\n \"orgs with pending approval\", \"pending wallet blocking approval\",\n \"pending takedown approval\", \"awaiting approval\", \"waiting for approval\",\n \"is protection active approval\", \"wallet blocking approval\",\n \"who needs to approve wallet blocking\", \"service change request\",\n \"track orgs with pending approval\", \"orgs awaiting sign-off\",\n \"is this URL blocked\", \"is this domain blocked\", \"is this address blocked\",\n \"check this asset\", \"asset check\", \"lookup asset status\",\n \"what is ARCHIVE_ORG\", \"what does PAGE mean\", \"list asset types\",\n \"supported asset types\", \"asset type mapping\", \"asset type enum\",\n \"what asset types are there\", \"human readable asset type\",\n \"how many takedowns\", \"takedowns in the last\", \"threats taken down\",\n \"across all clients\", \"across all customers\", \"across all orgs\",\n \"across all brands\", \"company-wide\", \"total takedowns\", \"total threats\",\n \"total reports\", \"average takedowns\", \"average threats\", \"average per day\",\n \"average per customer\", \"average per org\", \"rollup across customers\",\n \"sum across orgs\", \"sum across customers\",\n \"trends in org\", \"search for trends\", \"trend search\", \"look for trends\",\n \"any trends\", \"trending threats\", \"spike in asset type\",\n \"spike in threat volume\", \"coordinated attack\", \"spike check\",\n \"anything unusual\", \"anything new for\", \"what's new for\",\n \"employee being targeted\", \"spike on a sub-brand\", \"spike on a brand\",\n \"sub-brand spike\",\n \"list brands\", \"brands for org\", \"list sub-brands\", \"employee brands\",\n \"individual brands\", \"brand list\", \"all brands in org\".\nallowed-tools:\n - Bash\n - Read\n - Grep\n - Glob\n---\n\n# ChainPatrol CLI Skill\n\nYou are a ChainPatrol CLI assistant. Help the user interact with the ChainPatrol\nplatform using the CLI tool.\n\n## Running the CLI\n\nIMPORTANT: Claude Code's sandbox shell often has a minimal PATH\n(\\`/usr/bin:/bin:/usr/sbin:/sbin\\`) that may not include the directory where\n\\`chainpatrol\\` is installed, so bare \\`chainpatrol\\` calls may fail with\n\"command not found\". Always invoke the CLI by its full path.\n\nThe install location depends on the environment:\n\n- **Local installs** typically land at \\`/usr/local/bin/chainpatrol\\` (when\n installed globally via \\`npm install -g @chainpatrol/cli\\`).\n- **Cloud / sandboxed environments** (e.g. Claude Code on the web, Cursor\n Cloud) often install Node into \\`/opt\\` and the binary ends up under a\n Node-version-specific path like \\`/opt/node22/bin/chainpatrol\\`. Variants\n such as \\`/opt/node20/bin/chainpatrol\\` or \\`/opt/node21/bin/chainpatrol\\`\n are also possible depending on which Node version is active.\n\nTo find the binary, try (in order):\n\n\\`\\`\\`bash\ncommand -v chainpatrol \\\\\n || ls /usr/local/bin/chainpatrol /opt/node*/bin/chainpatrol 2>/dev/null \\\\\n | head -n 1\n\\`\\`\\`\n\nThen use that full path for every subsequent command, e.g.:\n\n\\`\\`\\`bash\n/opt/node22/bin/chainpatrol <command> [options]\n# or\n/usr/local/bin/chainpatrol <command> [options]\n\\`\\`\\`\n\nAll examples below use the short name \\`chainpatrol\\` for readability, but you\nMUST substitute the full resolved path in your Bash commands.\n\n## Available Commands\n\n### \\`login\\` — Authenticate with ChainPatrol\n\nUses the OAuth Device Code flow (RFC 8628):\n1. CLI requests a device code from the server\n2. User is shown a code and a URL to visit\n3. User authorizes in the browser\n4. CLI polls for the token\n\n\\`\\`\\`bash\nchainpatrol login\n\\`\\`\\`\n\nJSON mode (for automation):\n\\`\\`\\`bash\nchainpatrol --json login\n\\`\\`\\`\n\n#### Running \\`login\\` from an agent (headless / non-TTY)\n\nThe login flow blocks for up to 30 minutes while polling for the user to\nauthorize in their browser. If you (the agent) run it as a foreground\ncommand and wait for it to exit before reading output, you will appear\nstuck — the verification URL will never be shown because the process is\nstill polling.\n\n**Always run \\`login\\` in the background and stream its output**, then\nsurface the verification URL to the user as soon as it appears:\n\n\\`\\`\\`bash\n# 1. Kick off login in the background (do NOT wait for it to exit)\nchainpatrol --json login > /tmp/cp-login.out 2>&1 &\n\n# 2. Wait briefly for the first line, which contains the URL\nfor _ in 1 2 3 4 5 6 7 8 9 10; do\n test -s /tmp/cp-login.out && break\n sleep 1\ndone\ncat /tmp/cp-login.out\n\\`\\`\\`\n\nThe first line emitted is JSON describing the device code, e.g.:\n\n\\`\\`\\`json\n{\"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}\n\\`\\`\\`\n\nShow the user the \\`verification_uri_complete\\` link (or\n\\`verification_uri\\` + \\`user_code\\` as a fallback) and explain that the\nCLI will pick up the token automatically once they authorize. Then keep\nthe background process running and tail it for the final\n\\`{\"status\":\"success\",...}\\` or \\`{\"error\":...}\\` line.\n\nCLI v0.3.3+ also auto-detects non-TTY stdout and prints the URL as plain\ntext immediately when you run \\`chainpatrol login\\` without \\`--json\\`,\nso the same background+tail pattern works without \\`--json\\`. Prefer\n\\`--json\\` so the output is structured and machine-parseable.\n\n### \\`logout\\` — Clear stored credentials\n\n\\`\\`\\`bash\nchainpatrol logout\n\\`\\`\\`\n\n### \\`asset check\\` — Check one or many assets against the blocklist\n\nLook up a URL, domain, or crypto address and return its aggregated status\n(\\`BLOCKED\\`, \\`ALLOWED\\`, or \\`UNKNOWN\\`) plus a per-source breakdown\n(ChainPatrol + external feeds like eth-phishing-detect, phishfort, seal,\npolkadot-phishing). Works whether you're authenticated via device-code\nlogin or via a \\`CHAINPATROL_API_KEY\\` env var.\n\nSingle asset:\n\n\\`\\`\\`bash\nchainpatrol asset check https://phish.example\nchainpatrol asset check 0xabc123...\n\\`\\`\\`\n\n#### Bulk checks (preferred for >1 asset)\n\nPass multiple assets in a single invocation — the CLI runs them in\nparallel (concurrency 10) and returns one row per asset. **Do this\ninstead of looping the CLI in a shell \\`for\\` loop**: one process, one\nauth handshake, parallel HTTP. Use either positional args or repeated\n\\`--asset\\`:\n\n\\`\\`\\`bash\n# positional form\nchainpatrol asset check a.example b.example c.example\n\n# repeated --asset (handy when content has spaces or special chars)\nchainpatrol asset check --asset a.example --asset b.example\n\n# from a file of one-asset-per-line (use xargs to splat into one call)\nxargs -a domains.txt chainpatrol asset check\n\\`\\`\\`\n\nJSON mode is the agent-friendly default — single-asset JSON keeps the\nflat \\`{ content, status, source, reason?, sources[], watchStatus? }\\`\nshape; multi-asset JSON returns \\`{ results: [...], summary: { checked,\nblocked, allowed, unknown, errored } }\\`:\n\n\\`\\`\\`bash\nchainpatrol --json asset check https://phish.example\nchainpatrol --json asset check a.example b.example c.example\n\\`\\`\\`\n\nMarkdown / CSV are also available for sharing in docs / chat:\n\n\\`\\`\\`bash\nchainpatrol asset check phish.example --output markdown\nchainpatrol asset check a.example b.example --output csv\n\\`\\`\\`\n\nIf any individual lookup fails, the CLI still prints results for the\nsuccessful ones, then exits non-zero so failures aren't silently\nswallowed.\n\n### \\`asset types\\` — List every supported asset type and what it means\n\nWhen a user asks \"what is \\`ARCHIVE_ORG\\`?\", \"what does \\`PAGE\\` mean?\",\nor \"what asset types does ChainPatrol support?\" — don't guess. Run:\n\n\\`\\`\\`bash\nchainpatrol asset types\nchainpatrol --json asset types\n\\`\\`\\`\n\nThe mapping is bundled with the CLI (no API or auth required), so this\nis safe to run anywhere. Each row is \\`{ type, label, description }\\`:\n\\`type\\` is the canonical enum value used by every \\`--asset-type\\` flag\n(\\`asset list\\`, \\`threats list\\`, \\`takedowns list\\`, \\`detections list\\`,\n\\`reports list\\`, \\`orgs assets list\\`); \\`label\\` is the human-friendly\ndisplay name (\\`ARCHIVE_ORG\\` → \\`Archive.org\\`, \\`PAGE\\` → \\`Page\\`,\n\\`FIVE_HUNDRED_PX\\` → \\`500px\\`); \\`description\\` is a one-line note for\nunobvious entries.\n\nUse this whenever you need to translate between enum and display name,\nvalidate that a type the user mentioned is real, or enumerate options\nbefore constructing a filter.\n\n### \\`configs list\\` — List detection configurations\n\nRequires authentication and an organization slug.\n\n\\`\\`\\`bash\nchainpatrol configs list --org <slug>\n\\`\\`\\`\n\nJSON mode:\n\\`\\`\\`bash\nchainpatrol --json configs list --org <slug>\n\\`\\`\\`\n\nThe \\`--org\\` flag is saved for future commands. Once set, you can omit it:\n\\`\\`\\`bash\nchainpatrol configs list\n\\`\\`\\`\n\n### \\`reports list\\` — List recent reports for an organization\n\nReturns the most recent reports submitted for an organization. Each report\nincludes a \\`reportedByCustomer\\` boolean indicating whether the report was\nsubmitted by a customer of ChainPatrol (e.g. via API key, Slack/Telegram bot,\nor another external integration) rather than by ChainPatrol's automated\ndetections or staff reviewers.\n\n\\`\\`\\`bash\nchainpatrol reports list --org <slug>\n\\`\\`\\`\n\nCommon flags:\n- \\`--limit <n>\\` page size (1-20)\n- \\`--cursor <id>\\` pagination cursor (use \\`nextCursor\\` from a previous response)\n- \\`--status <s>\\` filter by report status (e.g. \\`TODO\\`, \\`IN_PROGRESS\\`, \\`DONE\\`)\n- \\`--search <q>\\` search query across title/description/asset content\n- \\`--reported-by-customer\\` only show reports submitted by a customer\n- \\`--no-reported-by-customer\\` only show reports NOT submitted by a customer\n (i.e. found by ChainPatrol's automation or staff)\n\nJSON mode is recommended when you want to analyze the data programmatically:\n\\`\\`\\`bash\nchainpatrol --json reports list --org <slug> --reported-by-customer\n\\`\\`\\`\n\nThe response includes \\`totalCount\\`: the number of reports matching the same\nfilters as the query, independent of \\`--limit\\`/\\`--cursor\\`. Use it to:\n- Report a denominator when sampling (e.g. \"sampled 200 of ~4,235\").\n- Decide up front whether to page through everything or narrow filters first —\n if \\`totalCount\\` is huge, add more filters instead of paginating blindly.\n- Drive a progress indicator while paginating with \\`nextCursor\\`.\n\n\\`totalCount\\` honors every list filter (\\`--status\\`, \\`--reported-by-customer\\` /\n\\`--no-reported-by-customer\\`, \\`--exclude-automation\\`, \\`--review-status\\` /\n\\`--only-rejected\\`, \\`--asset-type\\`, brand, \\`--country-code\\`, \\`--from\\`/\\`--to\\`,\n\\`--updated-from\\`/\\`--updated-to\\`, \\`--search\\`, \\`--reporter-query\\`), so changing\nfilters changes the count. \\`reports\\` (the returned page) is capped by\n\\`--limit\\` (max 20); \\`totalCount\\` is not.\n\n#### Use case: finding gaps in ChainPatrol detection\n\nCustomer-reported reports (\\`reportedByCustomer=true\\`) are a valuable signal\nfor finding gaps in ChainPatrol's automated detections and staff triage: each\none is a threat the customer found before ChainPatrol's own systems did. When\nthe user asks something like:\n\n- \"show me the threats our customers are reporting\"\n- \"what is X's customer reporting?\"\n- \"where are we missing detections for org Y?\"\n- \"summarize recent reports submitted by customers\"\n\n…use \\`chainpatrol --json reports list --org <slug> --reported-by-customer\\`\nto fetch them, then highlight patterns (asset types, domains, common\nkeywords, recurring brands) so the user can:\n1. Spot detection coverage gaps to fix in the org's detection configs.\n2. Improve staff triage runbooks for recurring scams.\n3. Work directly with the customer to close the loop and prevent future\n misses.\n\nYou can also compare with the non-customer set using\n\\`--no-reported-by-customer\\` to gauge detection coverage on the same time\nwindow.\n\n### \\`detections healthcheck\\` — Validate enabled detection configs produce recent results\n\nServer-side check. The CLI calls the ChainPatrol API; the server fetches each\nenabled detection config for the org, counts results produced in the lookback\nwindow, and FAILs configs that fall under \\`--min-results\\` (or whose run\nerrored when \\`--run\\` is set).\n\n\\`\\`\\`bash\nchainpatrol --json detections healthcheck --org <slug>\n\\`\\`\\`\n\nFlags:\n- \\`--source <key>\\` only validate one source (e.g. \\`twitter_search\\`)\n- \\`--min-results <n>\\` minimum results required in the window to pass\n- \\`--lookback-hours <n>\\` size of the lookback window\n- \\`--run\\` ask the server to run each config first, then validate the fresh output\n- \\`--include-disabled\\` also validate disabled configs\n\nWhat this command covers:\n- Configs that have gone silent (recentResultCount below threshold)\n- Configs that error when run (runOk=false) when \\`--run\\` is set\n\nWhat it does NOT cover:\n- Reviewing backlog / SLA breaches → use \\`queues snapshot\\`\n- Takedown ToDo / In Progress / Cancelled volumes → use \\`queues snapshot\\`\n- Spikes or drops in detection volume over time → use \\`metrics breakdown\\`\n- Customer-reported gaps → use \\`reports list --reported-by-customer\\`\n- Google Safe Browsing submission errors (not yet exposed in CLI)\n\nUse it as the first signal in the Detection part of an org healthcheck, then\nfall back to the manual checks in the HealthCheck Guide below for everything\nelse.\n\n> Prefer the newer \\`healthchecks\\` namespace below. \\`detections healthcheck\\`\n> is the original single-purpose command; the \\`healthchecks\\` namespace is\n> the canonical place to discover and run every check we expose.\n\n### \\`healthchecks list | run\\` — Run uniform org healthchecks via the public API\n\nThe \\`healthchecks\\` namespace is the canonical way to run the named checks\nfrom the Organization HealthCheck Guide below. Each implemented endpoint\nreturns the same uniform shape — \\`{ id, ok, severity, observed, threshold,\nfindings, suggestedAction }\\` — so the CLI / agent can render every check the\nsame way regardless of category.\n\n\\`\\`\\`bash\n# Discover every check the platform exposes today, including planned checks\n# that are not yet implemented on the backend.\nchainpatrol --json healthchecks list\n\n# Run a single named check.\nchainpatrol --json healthchecks run reviewing.backlog --org <slug>\n\n# Run every implemented check in parallel and aggregate the results.\nchainpatrol --json healthchecks run --all --org <slug>\n\\`\\`\\`\n\nEach implemented check has a stable id of the form \\`category.name\\`. Implemented\nids today: \\`detections.silent-configs\\`, \\`reviewing.backlog\\`,\n\\`reviewing.old-proposals\\`, \\`reviewing.watchlist-backlog\\`,\n\\`reviewing.watchlist-old\\`, \\`takedowns.todo-volume\\`,\n\\`takedowns.in-progress-volume\\`, \\`takedowns.stale-in-progress\\`,\n\\`takedowns.cancelled-count\\`, \\`takedowns.automation-off\\`,\n\\`assets.dead-asset-spike\\`.\n\n### Pending proposals: \"Needs Review\" vs \"Watchlisted\"\n\nPENDING proposals split into two operationally distinct buckets, and we\ngrade them with separate checks:\n\n- **Needs Review** — pending proposals the reviewing UI shows by default\n (\\`excludeWatchlisted=true\\`): assets that are NOT on a watchlist, OR\n reports submitted by a customer (those stay visible even when the asset\n is watchlisted). This is the actionable queue reviewers work from, so\n pile-ups and old items here are high-priority signals (\\`fail\\` severity\n is reachable).\n- **Watchlisted** — pending proposals on watchlisted assets (excluding\n customer-reported reports). The reviewing UI hides these by default\n because watchlisting is the act of intentionally deferring an asset.\n Pile-ups and aged items here are worth surfacing as cleanup work, but\n severity is **capped at warn** so they never block on the same SLA as\n Needs Review. When reporting findings, treat these as lower-priority.\n\nEach healthcheck result includes an \\`appUrl\\` field (string or null) that\ndeep-links to the relevant filtered admin page in the web app — e.g. the\ntakedowns page filtered to IN_PROGRESS for \\`takedowns.stale-in-progress\\`,\nor the review page filtered to oldest pending for \\`reviewing.old-proposals\\`.\n**When reporting a non-OK healthcheck to the user, always surface the\n\\`appUrl\\` so they can jump straight to the right view.** Some checks\n(\\`detections.silent-configs\\`, \\`assets.dead-asset-spike\\`) emit \\`null\\`\nbecause no filterable list page exists for that signal yet.\n\nImplemented checks today:\n\n- **detections.silent-configs** — equivalent to \\`detections healthcheck\\`,\n exposed under the uniform shape.\n- **reviewing.backlog** — counts Needs Review pending proposals and grades\n severity against per-org thresholds (default warn=50, fail=100).\n- **reviewing.old-proposals** — counts Needs Review proposals older than\n the warn / fail age thresholds (default 7 / 14 days) and lists the\n oldest offenders.\n- **reviewing.watchlist-backlog** — counts watchlisted pending proposals\n (default warn=200). Severity capped at warn.\n- **reviewing.watchlist-old** — counts watchlisted pending proposals older\n than the warn-age threshold (default 30 days) and lists the oldest.\n Severity capped at warn.\n- **takedowns.todo-volume** — counts takedowns in TODO (default warn=50,\n fail=100). Pile-ups here usually mean an automation gap on a new threat\n surface, or manual-filing capacity issues.\n- **takedowns.in-progress-volume** — counts takedowns currently IN_PROGRESS\n regardless of age (default warn=30, fail=75). Complements\n \\`stale-in-progress\\` — a high count signals vendor-side or\n submission-format problems even before items go stale.\n- **takedowns.stale-in-progress** — counts takedowns sitting in IN_PROGRESS\n past the staleness threshold (default 7 days) and lists the oldest.\n- **takedowns.cancelled-count** — counts CANCELLED transitions from the\n TakedownEvent log over a rolling window (default 7d, warn=3, fail=10).\n Cancellations should be rare; a spike usually means a proposal-funnel\n quality problem or misuse of the CANCELLED status.\n- **takedowns.automation-off** — flags orgs with takedown service enabled\n but \\`isAutomatedTakedownsActive\\` off for too long (default warn=30d,\n fail=60d). Skipped for orgs with takedown service entirely disabled.\n- **assets.dead-asset-spike** — compares DEAD-detection events in the\n current window against the prior baseline rate; warns on a multiplier\n exceeding the threshold (default 24h vs 7d, ×2 warn / ×4 fail) once the\n current count clears the \\`minSpikeCount\\` floor. Catches liveness-checker\n regressions after platform changes.\n\nThe following checks are listed by \\`healthchecks list\\` (\\`implemented: false\\`)\nbut **not yet implemented on the backend** — when the agent surfaces them in\na healthcheck report, mark them explicitly as \"manual check, no API yet\":\n\n- **detections.coverage-gaps** — blocked assets vs. enabled-source correlation.\n Still requires manual reasoning with \\`configs list\\` + \\`reports list\\`.\n- **detections.spike** / **detections.drop** — require server-side baseline\n modeling. Use \\`metrics breakdown --by day\\` as an interim signal.\n- **reviewing.auto-approval-spike** — needs distinguishing automation vs.\n human approvers in the review history. Use \\`metrics breakdown\\` as a proxy.\n- **blocklisting.gsb-cancelled-rate** — Google Safe Browsing submission state\n is not yet exposed in the public API.\n- **assets.dead-but-alive** / **assets.alive-but-marked-dead** — require live\n HTTP probes against asset URLs, which is not a synchronous-healthcheck\n shape. Until a dedicated probe command exists, sample a handful manually\n from \\`assets list --status DEAD\\` (or ALIVE) and verify in a browser. Notify\n ChainPatrol engineering if the liveness checker looks miscalibrated.\n\nWhen the user asks to \"run a healthcheck on org X\", the canonical command is:\n\n\\`\\`\\`bash\nchainpatrol --json healthchecks run --all --org X\n\\`\\`\\`\n\nThis iterates the implemented entries in \\`healthchecks list\\`, runs them in\nparallel, and aggregates the uniform results. Combine with the manual checks\nin the HealthCheck Guide below for everything still marked\n\\`implemented: false\\`.\n\n### \\`queues snapshot\\` — Operations review/takedown queue snapshot\n\nServer-side aggregation of the operations review queue (pending proposals,\nSLA breaches, age buckets) and the takedown queue (open, in-progress, stale).\n\n\\`\\`\\`bash\nchainpatrol --json queues snapshot --org <slug>\n\\`\\`\\`\n\nUseful for the **Reviewing** and **Takedowns** sections of the HealthCheck\nGuide. Key signals in the response:\n- \\`reviewQueue.totalPendingProposals\\` and \\`reviewQueue.distinctReports\\` —\n backlog size\n- \\`reviewQueue.slaBuckets.breached\\` — SLA breaches (treat any breach as a\n finding)\n- \\`reviewQueue.ageBuckets.gte168h\\` — proposals older than 7 days (anything\n >14 days from the manual guide should always be in here)\n- \\`takedownQueue.totalOpen\\` and \\`takedownQueue.staleInProgress\\` — open\n and stuck takedowns\n\nUse \\`--all\\` to snapshot every org you have access to instead of a single slug.\n\n### \\`orgs list\\` — List organizations with subscription status, service toggles, and admin-approval scope\n\nReturns every organization the caller can see, with each org's\nsubscription status (\\`PROSPECT\\`, \\`TRIAL\\`, \\`ACTIVE\\`, \\`INTEGRATION\\`),\nwhich services are active, and the per-org \"Obligatory Organization\nAdmin Approval\" toggle (with its asset-type scope). Use it to answer\nquestions like \"which customers have takedowns enabled but automation\noff?\", \"which prospects don't have detection turned on yet?\", or \"which\norgs require admin approval before adding Twitter assets to the\nblocklist?\" — filters compose with AND and are applied server-side, so\none call returns the final list.\n\n\\`\\`\\`bash\nchainpatrol --json orgs list \\\\\n --subscription-status ACTIVE \\\\\n --service-active takedowns \\\\\n --service-manual takedowns\n\\`\\`\\`\n\n#### Per-service response shape — \\`automated\\` is takedowns-only\n\nEvery service exposes an \\`active\\` boolean. **Only \\`takedowns\\`\nadditionally exposes \\`automated\\`** — that is the one service where the\nmanual-vs-automated distinction changes real platform behavior (whether\nChainPatrol files the takedown on its own, or queues it for a human to\nfile). \\`reporting\\`, \\`reviewing\\`, and \\`protection\\` have backing\n\\`isAutomated*Active\\` columns in the database, but those flags have no\noperational effect today, so the API deliberately omits them from both\nthe response and the \\`services\\` filter — surfacing them would invite\nmisleading filters like \"reporting.automated=true\" that don't mean\nanything. \\`detection\\` and \\`darkWebMonitoring\\` have always been\nsingle-flag services.\n\n#### Obligatory Organization Admin Approval\n\nEach org also has a separate \"Obligatory Organization Admin Approval\"\ntoggle (\\`Organization.requiresCustomerReview\\` in the schema, surfaced\non the Services settings page in the app) that gates customer-side\nreview of proposals ChainPatrol staff have already approved before\nthey're added to the blocklist. It is NOT one of the operational\nservices above — it is an org-policy toggle with its own per-asset-type\nscope.\n\nEach org's response includes:\n\n\\`\\`\\`json\n{\n \"obligatoryAdminApproval\": {\n \"active\": true,\n \"assetTypes\": [\"TWITTER\", \"URL\"]\n }\n}\n\\`\\`\\`\n\n- \\`active=true\\` + empty \\`assetTypes\\` means approval applies to\n **all** asset types — the org has not narrowed the scope.\n- \\`active=true\\` + a non-empty \\`assetTypes\\` list means approval is\n required only for those asset types.\n- \\`active=false\\` means no approval is required; \\`assetTypes\\` is\n always empty in that case.\n\nSo per-org JSON looks like:\n\n\\`\\`\\`json\n{\n \"services\": {\n \"reporting\": { \"active\": true },\n \"reviewing\": { \"active\": true },\n \"protection\": { \"active\": false },\n \"takedowns\": { \"active\": true, \"automated\": false },\n \"detection\": { \"active\": true },\n \"darkWebMonitoring\": { \"active\": false }\n },\n \"obligatoryAdminApproval\": { \"active\": true, \"assetTypes\": [\"TWITTER\"] },\n \"pendingServiceApprovals\": [\n {\n \"service\": \"protection\",\n \"automated\": false,\n \"serviceType\": \"isProtectionActive\",\n \"serviceName\": \"Wallet Blocking\",\n \"requestedAt\": \"2026-05-30T18:04:11.000Z\"\n }\n ]\n}\n\\`\\`\\`\n\n#### Pending service approvals (Wallet Blocking / Takedowns)\n\nTurning on **Wallet Blocking** (\\`protection\\`, the \"is protection active\"\ntoggle) or **Takedowns** is approval-gated: when a lower-level org member\ntries to enable one, ChainPatrol records a pending request that a higher-up\nat the org — an org OWNER, or ChainPatrol staff acting as owner — must\napprove before the service actually turns on. \\`pendingServiceApprovals\\`\nlists those open, awaiting-sign-off requests per org.\n\nThis is the answer to \"which organizations have a pending approval for\nWallet blocking or Takedown services?\" — it is NOT the same as \"which orgs\nhave Wallet Blocking turned off\". An org can have \\`protection.active=false\\`\nwith **no** pending approval (nobody has asked) OR **with** a pending\napproval (someone asked and it's waiting on an owner). Only\n\\`pendingServiceApprovals\\` distinguishes the two.\n\nEach entry:\n\n- \\`service\\` — \\`protection\\` (Wallet Blocking) or \\`takedowns\\`, matching the\n \\`services\\` keys.\n- \\`automated\\` — \\`true\\` when the request is for the automated variant\n (e.g. \"Automated Wallet Blocking\").\n- \\`serviceType\\` — raw enum, e.g. \\`isProtectionActive\\`.\n- \\`serviceName\\` — human label, e.g. \\`Wallet Blocking\\`.\n- \\`requestedAt\\` — when the enable request was submitted (ISO 8601, UTC).\n\nAn **empty array means nothing is awaiting approval** — read \\`services\\` for\nthe current on/off state, never infer it from this field.\n\nWhen a user asks about \"automated reporting / reviewing / protection\",\nexplain that the flag exists in the DB but has no operational effect and\nisn't exposed by the public API — only \\`takedowns.automated\\` carries a\nreal meaning.\n\nFilter flags (all optional, all comma-separated lists where noted):\n\n- \\`--query <text>\\` partial name match (substring, case-insensitive)\n- \\`--subscription-status <list>\\` one or more of \\`PROSPECT\\`, \\`TRIAL\\`,\n \\`ACTIVE\\`, \\`INTEGRATION\\`. \\`INACTIVE\\` is intentionally not reachable\n through this filter — \\`orgs list\\` only ever returns live customers.\n- \\`--service-active <list>\\` services that must be active\n- \\`--service-inactive <list>\\` services that must be inactive\n- \\`--service-automated <list>\\` services whose automation must be ON.\n **Only \\`takedowns\\` is accepted** — the other services don't have a\n meaningful automation toggle. Passing any other service name errors out.\n- \\`--service-manual <list>\\` services whose automation must be OFF.\n Same restriction — only \\`takedowns\\`.\n- \\`--obligatory-approval-active\\` — only orgs with admin approval on\n- \\`--obligatory-approval-inactive\\` — only orgs with admin approval off\n- \\`--obligatory-approval-asset-type <list>\\` — only orgs whose admin\n approval scope covers EVERY listed asset type. The match treats an\n org's empty per-asset-type list as \"applies to all asset types\", so a\n fully-broad org matches every value passed here. Passing this flag\n implies the feature is on, so it can't be combined with\n \\`--obligatory-approval-inactive\\`. Use the canonical asset-type enum\n names (\\`TWITTER\\`, \\`URL\\`, \\`PAGE\\`, …); run \\`chainpatrol asset types\\`\n to see them all.\n- \\`--pending-approval-active\\` — only orgs with at least one open\n service-enable approval awaiting a higher-up's sign-off.\n- \\`--pending-approval-inactive\\` — only orgs with no pending approvals.\n- \\`--pending-approval-service <list>\\` — only orgs with a pending approval\n for one of the listed services. Accepts \\`protection\\` (Wallet Blocking)\n and/or \\`takedowns\\` — those are the only approval-gated services.\n Matches both the manual and automated variant. Passing this flag implies\n a pending approval exists, so it can't be combined with\n \\`--pending-approval-inactive\\`.\n\nService names: \\`reporting\\`, \\`reviewing\\`, \\`protection\\`, \\`takedowns\\`,\n\\`detection\\`, \\`darkWebMonitoring\\`.\n\nCustomers see only orgs they're a member of. Staff/superuser sessions see\nevery matching org. The response is the same in both cases; visibility is\nenforced server-side.\n\n#### Use case: finding which orgs require admin approval for an asset type\n\nWhen a user asks \"which orgs require admin approval before blocking\nTwitter assets?\", reach straight for the filter — no client-side\npost-processing needed:\n\n\\`\\`\\`bash\nchainpatrol --json orgs list --obligatory-approval-asset-type TWITTER\n\\`\\`\\`\n\nTo audit just the broad opt-ins (\"which orgs require approval for\neverything?\"), filter on the toggle and inspect \\`assetTypes\\` in the\noutput — an empty array means \"all\":\n\n\\`\\`\\`bash\nchainpatrol --json orgs list --obligatory-approval-active \\\\\n | jq '.organizations[] | select(.obligatoryAdminApproval.assetTypes | length == 0) | .slug'\n\\`\\`\\`\n\n#### Use case: tracking orgs with a pending Wallet Blocking / Takedown approval\n\nWhen a user asks \"which organizations have a pending approval for Wallet\nblocking or Takedown services?\", do NOT reach for \\`--service-inactive\nprotection\\` — that lists orgs with the service turned OFF, which is a\ndifferent question. Use the pending-approval filter, which surfaces\nstaff-initiated enable requests still waiting on a higher-up:\n\n\\`\\`\\`bash\n# Every org with a pending Wallet Blocking OR Takedown approval:\nchainpatrol --json orgs list --pending-approval-service protection,takedowns\n\n# Just Wallet Blocking:\nchainpatrol --json orgs list --pending-approval-service protection\n\n# Any pending approval at all, then read each org's pendingServiceApprovals:\nchainpatrol --json orgs list --pending-approval-active \\\\\n | jq '.organizations[] | {slug, pending: [.pendingServiceApprovals[].serviceName]}'\n\\`\\`\\`\n\n### \\`orgs get\\` — Get a single organization by slug\n\nLook up one organization the caller has access to. Returns the same per-org\nshape as a single row from \\`orgs list\\` — \\`active\\` for every service,\nplus \\`automated\\` on \\`takedowns\\` only (see the note above on why other\nservices don't expose an automation flag), plus \\`obligatoryAdminApproval\\`\n(\\`active\\` + \\`assetTypes\\`) and \\`pendingServiceApprovals\\` (open Wallet\nBlocking / Takedown enable requests awaiting a higher-up's sign-off).\nAnything you could read from \\`orgs list\\` you can also read here without\npaging or filtering. Use it when the user names a specific customer (\"show\nme acme's setup\", \"is takedowns automation on for morpho?\", \"does this org\nrequire admin approval for Twitter?\", \"is morpho waiting on a Wallet\nBlocking approval?\") and you don't need the rest of the catalogue.\n\n\\`\\`\\`bash\nchainpatrol orgs get <slug>\nchainpatrol --json orgs get <slug>\n\\`\\`\\`\n\nPermission rules match \\`orgs list\\`:\n\n- Org-scoped API keys: must match the slug — querying another org returns\n 403.\n- User sessions / user-scoped API keys: need an active OrganizationMembership\n on the slug. Staff/superusers can read any org.\n- Soft-deleted orgs are not returned (404). A 404 also fires for orgs the\n caller would not be authorized to see — the endpoint deliberately does\n not distinguish \"doesn't exist\" from \"you can't see this\" beyond the\n generic 403/404 envelope.\n\nPrefer \\`orgs get\\` over \\`orgs list\\` + client-side filter whenever the\ncaller already knows the slug; it's one round-trip and side-steps the\nlist filters entirely.\n\n#### Use case: finding service configuration gaps across the customer base\n\nWhen the user asks something like \"which customers are paying us but don't\nhave takedowns automated yet?\" or \"any orgs running detection without\ntakedowns?\", reach for \\`orgs list\\` — it's the only command that exposes\nservice flags across multiple orgs in one call. Run it in \\`--json\\` mode\nand summarize patterns by service or by subscription tier.\n\n### \\`brands list\\` — List the brands (sub-brands) belonging to an org\n\nReturns every brand for the org the caller is authenticated as, both\nparent / product brands (\\`type: \"ORGANIZATION\"\\`) and individual /\nemployee brands (\\`type: \"INDIVIDUAL\"\\`). Soft-deleted brands are excluded.\nThe endpoint is org-scoped and inferred from auth — there's no \\`--org\\`\nflag — so org-scoped API keys, user API keys, and user sessions all work\nwithout extra arguments.\n\n\\`\\`\\`bash\nchainpatrol brands list # all brands\nchainpatrol brands list --type INDIVIDUAL # employee / person brands only\nchainpatrol brands list --type ORGANIZATION # product / parent brands only\nchainpatrol --json brands list # machine-readable\n\\`\\`\\`\n\nEach entry has \\`{ id, slug, name, type, description, brandGroupId, createdAt }\\`.\nUse this when you need to:\n\n- Distinguish employee vs. product brands ahead of time — e.g. while\n running a trend search and you want to highlight which spiking\n sub-brands are employees so the user gets a direct heads-up to the\n person involved.\n- Resolve a brand name the user mentions to its \\`slug\\` for use with\n \\`metrics organization --brand-slug <slug>\\` or as a \\`brandId\\` for\n \\`takedowns list --brand <id>\\`.\n- Audit a customer's setup — \"how many employee brands does this org\n protect?\" or \"are there brands the org set up but never wired into a\n detection config?\"\n\n### \\`metrics summary | found | breakdown | organization\\` — Org metrics for spike/drop analysis\n\n#### Decision rule — read this before picking a metrics subcommand\n\nWhen the user asks for a number that **spans more than one customer/org/brand**\n— phrases like \"across all clients\", \"across all customers\", \"across all orgs\",\n\"across all brands\", \"company-wide\", \"total takedowns\", \"total threats\",\n\"average takedowns per day across customers\", \"rollup across customers\",\n\"how many takedowns in the last 7 days?\" (no org named) — the answer is\n**\\`chainpatrol metrics organization --all-my-orgs\\`** (or \\`--slugs\\`\nif you want a specific subset). \\`summary\\`, \\`found\\`, and \\`breakdown\\`\nare single-org commands and have **no** \\`--slugs\\`/\\`--all-my-orgs\\`\nform; only \\`organization\\` does. The multi-org form rolls totals +\nper-day / per-org-per-day averages + a per-org breakdown server-side\nin **one** HTTP call.\n\n**Anti-patterns to avoid:**\n\n- ❌ \"There's no cross-org aggregation in the CLI.\" There is —\n \\`metrics organization --all-my-orgs\\` (or \\`--slugs\\`). Don't bail\n out citing the docs without trying these flags.\n- ❌ Looping \\`metrics summary --org X\\` once per customer to sum\n client-side. The multi-org form is one round-trip; the loop is what\n made the endpoint 503-prone in the first place.\n- ❌ Calling \\`orgs list\\` to build a slug list when you just want\n \"everything.\" Use \\`--all-my-orgs\\` and skip the enumeration entirely —\n the server resolves the same set via shared logic. Save \\`orgs list\\`\n + \\`--slugs\\` for cases where the user asked for a specific subset.\n- ❌ Routing the user to Metabase / the data warehouse for a question\n the CLI can answer. Reach for Metabase only when the metric isn't\n in the \\`--include\\` list (\\`reports\\`, \\`newThreats\\`,\n \\`threatsWatchlisted\\`, \\`takedownsFiled\\`, \\`takedownsCompleted\\`,\n \\`domainThreats\\`, \\`twitterThreats\\`, \\`telegramThreats\\`,\n \\`otherThreats\\`, \\`blockedByType\\`, \\`blockedByDay\\`).\n- ❌ \"Iterating 100+ orgs one-by-one isn't practical here.\" Right —\n that's why you don't iterate. \\`--all-my-orgs\\` covers up to 500\n orgs in a single call; for larger fleets, narrow with\n \\`--subscription-status\\` / \\`--service-active\\`, or split an\n explicit \\`--slugs\\` list into batches and sum.\n\n#### Common cross-org recipe (copy and run)\n\nThe shortest answer to \"how many takedowns across all customers in the\nlast 7 days?\" is a single call — no \\`orgs list\\` step needed:\n\n\\`\\`\\`bash\nchainpatrol --json metrics organization \\\\\n --all-my-orgs \\\\\n --subscription-status ACTIVE \\\\\n --service-active takedowns \\\\\n --include takedownsCompleted \\\\\n --from <YYYY-MM-DD 7 days ago> --to <YYYY-MM-DD today>\n\n# Read these fields from the JSON:\n# .scope.orgs ← which orgs the server resolved\n# .metrics.takedownsCompleted ← grand total across all orgs\n# .averages.perDay.takedownsCompleted ← total / windowDays\n# .averages.perOrgPerDay.takedownsCompleted ← total / numOrgs / windowDays\n# .perOrg[slug].metrics.takedownsCompleted ← per-customer breakdown\n\\`\\`\\`\n\nReplace \\`takedownsCompleted\\` with whatever metric the user asked about.\nReplace the date range with whatever window they asked about (default\n3 months if they didn't say).\n\nIf the user *asked* for a specific subset of customers (\"how many for\nacme, beta, gamma combined?\"), use \\`--slugs acme,beta,gamma\\` instead\nof \\`--all-my-orgs\\`. \\`--slugs\\` accepts up to 500 entries.\n\n**Auth note for \\`--all-my-orgs\\`:** requires a user session or a\nuser-scoped API key (it inherits your memberships). Org-scoped API\nkeys are rejected — they're pinned to a single org by design. If\n\\`whoami\\` shows an org-scoped key, use a Bearer session instead or\nask the user to provision a user API key.\n\n#### Single-org examples\n\n\\`\\`\\`bash\nchainpatrol --json metrics summary --org <slug> # defaults to last 3 months\nchainpatrol --json metrics summary --org <slug> --this-week\nchainpatrol --json metrics breakdown --org <slug> --by day --this-week\nchainpatrol --json metrics found --org <slug> --from <YYYY-MM-DD> --to <YYYY-MM-DD>\nchainpatrol --json metrics organization --org <slug> # defaults to last 3 months\n\\`\\`\\`\n\n**Always operate on a bounded window.** When no \\`--from\\`/\\`--to\\`/\\`--this-week\\`\nis passed, every metrics subcommand defaults to the trailing **last 3 months**\nending now. The default exists because unbounded org-wide aggregates over\nmulti-year history routinely time out at the platform layer (Vercel's\n30s function cap), which surfaces to clients as a 503. Stick to the default,\nor pass an explicit narrower window — don't try to bypass the default by\nguessing wide \\`--from\\` values.\n\nThe resolved range is echoed back in every output format so the user can\nsee exactly what they got:\n\n- JSON: \\`{ \"range\": { \"startDate\": \"...\", \"endDate\": \"...\", \"label\": \"last 3 months\" } }\\`\n- Markdown / human: the header line includes the label, e.g.\n \\`Organization metrics for acme — last 3 months\\`, followed by\n \\`Range: <startDate> → <endDate>\\`.\n\nWhen reporting numbers to the user (especially averages and rates),\n**state the window explicitly** — say \"averaged over the last 3 months\"\nrather than just quoting a number, since the same prompt phrased\ndifferently can pick a different window.\n\n\\`breakdown\\` is the one you usually want for healthchecks: it returns a\ntime series (by day or week) of reports, new threats, watchlisted threats,\nand takedowns filed/completed. Compare the latest period against a prior\nwindow to spot the **spike** or **drop** signals described in the manual\nHealthCheck Guide. \\`summary\\` returns a single window total; \\`found\\` is\noriented around when threats were first discovered; \\`organization\\` is\nthe full customer-facing dashboard slice (reports, new threats,\nwatchlisted, takedowns filed/completed, plus per-type and per-day\nbreakdowns).\n\n#### \\`--include\\` — only compute the metrics you actually need\n\n\\`metrics organization\\` runs one Prisma aggregate per requested field.\nBy default all 11 are computed in parallel. Pass \\`--include\\` (or\n\\`include: […]\\` in the JSON body) with the comma-separated subset you\ncare about to skip the rest — the unrequested fields come back as\n\\`null\\` instead of a number, and the server never runs those queries:\n\n\\`\\`\\`bash\n# Only takedowns — one of the cheap shapes\nchainpatrol --json metrics organization --org <slug> \\\\\n --include takedownsFiled,takedownsCompleted\n\n# Time series only, no scalar counts\nchainpatrol --json metrics organization --org <slug> --include blockedByDay\n\\`\\`\\`\n\nAllowed values: \\`reports\\`, \\`newThreats\\`, \\`threatsWatchlisted\\`,\n\\`takedownsFiled\\`, \\`takedownsCompleted\\`, \\`domainThreats\\`,\n\\`twitterThreats\\`, \\`telegramThreats\\`, \\`otherThreats\\`,\n\\`blockedByType\\`, \\`blockedByDay\\`.\n\nUse this whenever the user's question is specific (\"how many takedowns\ndid we file last month?\"). It is the lowest-effort way to make a\nmetrics call cheap enough to run repeatedly. Pair it with an explicit\ndate window for the best behavior.\n\n#### \\`--slugs\\` — multi-org rollups in one call (use this for \"across all customers/clients\")\n\nWhen the user asks for a total or an average **across more than one\norg** — e.g. \"how many takedowns did we complete across all clients\nin the last 7 days?\", \"average reports per org per day this month\",\n\"top 5 customers by new threats\" — reach for \\`--slugs\\` instead of\nlooping. The server fans out the per-org aggregates internally,\nsums into totals, computes per-day and per-org-per-day averages,\nand returns a per-org breakdown in a **single HTTP round trip**:\n\n\\`\\`\\`bash\n# Total takedowns completed across three customers, last 7 days\nchainpatrol --json metrics organization \\\\\n --slugs acme,beta,gamma \\\\\n --include takedownsCompleted \\\\\n --from 2026-05-12 --to 2026-05-19\n\\`\\`\\`\n\nThe response shape switches to multi-org:\n\n\\`\\`\\`json\n{\n \"scope\": { \"mode\": \"multi\", \"orgs\": [\"acme\", \"beta\", \"gamma\"] },\n \"metrics\": { \"takedownsCompleted\": 117, ... }, // sum across orgs\n \"averages\": {\n \"perDay\": { \"takedownsCompleted\": 16.71, ... }, // total / windowDays\n \"perOrgPerDay\": { \"takedownsCompleted\": 5.57, ... }, // total / numOrgs / windowDays\n \"windowDays\": 7\n },\n \"perOrg\": { \"acme\": { \"metrics\": { ... } }, \"beta\": { ... }, \"gamma\": { ... } }\n}\n\\`\\`\\`\n\nTo answer **\"across all of our customers\"**, prefer \\`--all-my-orgs\\` —\nit lets the server resolve the org set in one round-trip, no \\`orgs\nlist\\` step needed:\n\n\\`\\`\\`bash\nchainpatrol --json metrics organization \\\\\n --all-my-orgs \\\\\n --subscription-status ACTIVE \\\\\n --service-active takedowns \\\\\n --include takedownsCompleted \\\\\n --from 2026-05-12 --to 2026-05-19\n\\`\\`\\`\n\nUse \\`--slugs <comma-list>\\` only when the user asked for a *specific*\nsubset of customers (\"how many for acme, beta, gamma combined?\"); the\ntwo flags are mutually exclusive. Both forms cap at 500 orgs per call;\nif a real fleet exceeds that, narrow with\n\\`--subscription-status\\`/\\`--service-active\\` filters or split an\nexplicit \\`--slugs\\` list into batches and sum. **Do not loop\n\\`metrics organization\\` once per org** — N HTTP round-trips is what\nmade the old code 503.\n\nAlways pair the multi-org form with \\`--include\\` to narrow the metric\nset to what the user actually asked about. \"How many takedowns did\nwe complete?\" → \\`--include takedownsCompleted\\`; \"average reports\nper day per customer?\" → \\`--include reports\\`. Skipping \\`--include\\`\nruns ~11 aggregates per org which is rarely necessary.\n\n#### Auth: which credential can use \\`--slugs\\` / \\`--all-my-orgs\\`\n\n- **Org-scoped API key** (the most common production key): can only\n query its own org. If \\`--slugs\\` contains foreign orgs the server\n returns 403; \\`--all-my-orgs\\` is also rejected. Use the \\`--org\\`\n form (or omit both) instead.\n- **User API key**: inherits the user's org memberships, so it can\n query any org the user is a member of (or any org for staff\n users) via \\`--slugs\\` or \\`--all-my-orgs\\`. This is the credential\n you want for cross-org rollups from automated agents.\n- **Session auth** (Bearer token, e.g. \\`chainpatrol login\\`): same\n membership rules as the user. Staff users can query any org.\n\n#### Reporting numbers to the user\n\nWhatever window and scope you choose, **make both explicit in your\nreply** — say \"averaged across 12 customers over the last 3 months\"\nor \"summed across all paying orgs for the last 7 days.\" The same\nprompt phrased slightly differently can pick a different window or\norg set, and the response already echoes\n\\`scope.mode\\` / \\`scope.orgs\\` / \\`averages.windowDays\\` /\n\\`range.label\\` so you don't have to guess.\n\n### \\`presets list | run\\` — Packaged workflows for common jobs\n\n\\`\\`\\`bash\nchainpatrol presets list\nchainpatrol presets run cs-weekly-health --org <slug>\n\\`\\`\\`\n\nUse \\`presets list\\` to discover packaged multi-step workflows. The bundled\n\\`cs-weekly-health\\` preset runs the standard customer-success weekly health\nsweep; prefer it over hand-rolling the same sequence of commands.\n\n## Headless / agent-mode tips\n\nWhen running these commands from an agent or CI:\n\n- Prefer \\`--json\\` (or \\`--output json\\`) so output is structured and the\n update-check stderr nudge is suppressed automatically.\n- Pass \\`--no-input\\` to forbid any interactive prompt (the CLI will error\n out instead of waiting on a TTY).\n- Pass \\`--no-color\\` or set \\`NO_COLOR=1\\` if your sink can't render ANSI.\n- Set \\`CHAINPATROL_NO_UPDATE_CHECK=1\\` to silence the skill/npm freshness\n nudge entirely.\n- For \\`login\\` specifically, see the headless runbook in the login section\n above — login is the one command that needs background + tail handling.\n\n## Checking Auth Status\n\nTo check if the user is logged in, read the credentials file:\n\n\\`\\`\\`bash\ncat ~/.chainpatrol/credentials.json 2>/dev/null && echo \"Logged in\" || echo \"Not logged in\"\n\\`\\`\\`\n\nOr start the login flow which will detect existing sessions:\n\\`\\`\\`bash\nchainpatrol --json login\n\\`\\`\\`\n\n## Configuration\n\nConfig is stored at \\`~/.chainpatrol/config.json\\`:\n- \\`apiUrl\\` — API base URL (default: \\`https://app.chainpatrol.io\\`)\n- \\`defaultOrg\\` — Saved organization slug\n\nOverride config dir with \\`CHAINPATROL_CONFIG_DIR\\` env var.\n\n## Version Checks\n\nThe CLI runs two lightweight version checks alongside each command and prints\na nudge to stderr if anything is out of date:\n\n- **Skill freshness**: compares the installed skill (\\`~/.claude/skills/chainpatrol/SKILL.md\\`)\n to the version bundled with the CLI. If it's missing or older, the user is\n asked to run \\`chainpatrol setup\\`.\n- **NPM freshness**: compares the running CLI version to the latest published\n on the npm registry. The check is throttled to once per 24 hours and capped\n at a 1.5s timeout, with the result cached at \\`<configDir>/version-check.json\\`.\n\nSet \\`CHAINPATROL_NO_UPDATE_CHECK=1\\` to silence both checks. JSON mode\n(\\`--json\\`) and quiet mode (\\`-q\\` / \\`--quiet\\`) also suppress the nudges so\nmachine-readable output is never polluted.\n\n## Global Flags\n\n| Flag | Description |\n|------------------|--------------------------------------------------------|\n| \\`--json\\` | Machine-readable JSON output (shortcut for \\`--output json\\`) |\n| \\`--output <fmt>\\` | Output format: \\`human\\` (default), \\`json\\`, \\`markdown\\`, \\`csv\\` |\n| \\`--quiet\\`, \\`-q\\` | Suppress non-essential output and the update-check nudge |\n| \\`--no-color\\` | Disable ANSI colors (also respects the \\`NO_COLOR\\` env var) |\n| \\`--no-input\\` | Disable interactive prompts (use in scripts and agents) |\n| \\`--org <slug>\\` | Organization slug (saved as the default for later commands) |\n| \\`--help\\`, \\`-h\\` | Show help |\n| \\`--version\\` | Show version |\n\n## Workflow\n\nWhen the user asks to use the CLI, follow this order:\n\n1. **Check login status** — Read \\`~/.chainpatrol/credentials.json\\` to see if they're logged in\n2. **Login if needed** — Run the login command and guide them through the device code flow\n3. **Set org if needed** — Ensure \\`--org\\` is provided or already saved in config\n4. **Run the requested command** — Execute the CLI command and show results\n\n## Detection Config Output\n\nThe \\`configs list\\` command shows detection sources grouped by:\n- **Configured** — Sources with org-level configs (enabled/disabled with details)\n- **Global** — Sources that run globally for all orgs (CERTSTREAM, ASSET_CHECK, BLOCKLIST, etc.)\n- **Not Configured** — Sources available but not yet set up for the org\n\nEach config entry includes: title, status, cron schedule, and configuration parameters.\n\n## Organization HealthCheck Guide\n\nThis guide explains how to look for things that may be wrong for a given org\nacross the full pipeline: **detection → reviewing → blocklisting → takedowns**.\nWhen the user asks for a \"health check\", \"audit\", \"what's wrong with org X\",\n\"review org X's setup\", or similar, walk through each section below and surface\nfindings.\n\nIn each section there are things you can look for that may be wrong. Some\nchecks have a dedicated CLI command that does most of the work server-side;\nothers are soft / qualitative signals that still need you to fetch data with\n\\`configs list\\` or \\`reports list\\` and reason about it manually.\n\n### Quick Path: CLI commands that automate parts of this guide\n\nThe canonical first step is now the \\`healthchecks\\` namespace, which runs\nevery implemented check via the public API and returns a uniform shape per\ncheck (\\`id\\`, \\`severity\\`, \\`observed\\`, \\`threshold\\`, \\`findings\\`,\n\\`suggestedAction\\`):\n\n\\`\\`\\`bash\n# Discover every check the platform exposes, implemented or planned.\nchainpatrol --json healthchecks list\n\n# Run every implemented healthcheck in parallel and aggregate the results.\nchainpatrol --json healthchecks run --all --org <slug>\n\n# Run a single named check.\nchainpatrol --json healthchecks run reviewing.backlog --org <slug>\n\\`\\`\\`\n\nAfter \\`healthchecks run --all\\`, use these complementary commands to cover\nthe signals that are not yet exposed as a uniform healthcheck endpoint:\n\n\\`\\`\\`bash\n# Spikes / drops in detection volume over time (compare windows)\nchainpatrol --json metrics breakdown --org <slug> --by day --this-week\n\n# Customer-reported threats — gaps in our own detection\nchainpatrol --json reports list --org <slug> --reported-by-customer\n\n# What's enabled vs disabled vs not configured for the org\nchainpatrol --json configs list --org <slug>\n\n# Snapshot of review/takedown queues — raw counts behind several healthchecks\nchainpatrol --json queues snapshot --org <slug>\n\n# Packaged weekly customer-success sweep (preferred when it covers the ask)\nchainpatrol presets run cs-weekly-health --org <slug>\n\\`\\`\\`\n\nTreat each command's output as one input to the healthcheck. The manual\nchecks below still apply — especially for signals the CLI cannot infer on\nits own (e.g. \"lots of Twitter assets are blocked but Twitter Post Search\nis disabled\", or \"this drop is fine because the config isn't relevant\nto this org\"). Each subsection of the guide notes whether a healthcheck\nendpoint exists today and what to fall back on when it doesn't.\n\n### Reporting progress while running a healthcheck\n\nWhen the user asks for a healthcheck, narrate each step in real time so they\ncan watch the run unfold. Do NOT batch results and dump everything at the\nend. For every check you run (Quick Path CLI command, or a manual signal\nwhere you're fetching data with \\`configs list\\` / \\`reports list\\` to\nreason about):\n\n1. **Before** you call the command, emit ONE short sentence stating what\n you're about to check, including the org slug. Examples:\n - \"Running detection config healthcheck for morpho…\"\n - \"Snapshotting review and takedown queues for morpho…\"\n - \"Fetching customer-reported reports for morpho to look for detection gaps…\"\n\n2. **As soon as** the command returns, emit ONE short result line that\n starts with a status word and ends with a key number or finding:\n - \\`DONE\\` — ran cleanly, nothing to flag\n - \\`WARN\\` — soft signal worth surfacing (e.g. a borderline backlog,\n mild spike or drop, a config you'd want a human to confirm)\n - \\`FAIL\\` — concrete failure that the user should act on\n - Examples:\n - \"DONE — 14/14 detection configs passing.\"\n - \"WARN — 23 proposals in the review queue; 4 are older than 7 days.\"\n - \"FAIL — twitter_post_search returned 0 results in the last 24h with --run.\"\n\n3. Run **independent** checks in **parallel** (single message, multiple\n Bash tool calls) so progress lines arrive quickly. \\`detections\n healthcheck\\`, \\`queues snapshot\\`, \\`metrics breakdown\\`,\n \\`reports list --reported-by-customer\\`, and \\`configs list\\` are all\n independent of each other and safe to fire concurrently. Dependent\n follow-ups (e.g. paginating \\`reports list\\` with a returned cursor,\n or fetching extra detail on a single failing config) run after the\n first round.\n\n4. After every check is reported, emit a short final **Summary** section:\n - A one-line status per check (✓ / ⚠ / ✗ + name + key number).\n - A short \"Top issues\" list of the highest-priority FAIL / WARN\n findings, in priority order, with the concrete next action for each.\n\nKeep each progress line to one sentence. The goal is for the user to see\nthe healthcheck happening, not to read a wall of text mid-run — full\ndetail belongs in the final Summary or in a follow-up when the user asks\nabout a specific finding.\n\n### Detection\n\n#### Enable Config That May Be Turned Off for Current Threats\n\nBlocked threats exist in an asset type but the matching detection source is\nturned off. For example: lots of Twitter assets are on the blocklist, but\ndetection sources like \"Twitter / X User Search\" or \"Twitter Post Search\" are\ndisabled. Those should be turned on.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed in\n\\`healthchecks list\\` as \\`detections.coverage-gaps\\` with\n\\`implemented: false\\` — when reporting this signal in a healthcheck, note\n\"manual check, no API yet\". Until the endpoint lands, do the correlation\nmanually: \\`chainpatrol --json configs list --org <slug>\\` for enabled vs\ndisabled configs, then \\`chainpatrol --json reports list --org <slug>\\` for\nrecent blocked-item asset types. Flag any asset type where blocked items\nexist but the matching detection source has \\`status: \"disabled\"\\` (or\nappears in the \"Not configured\" group).\n\n#### Spike in Detections\n\nA spike in recent detections is worth investigating. It could be a bad config\nchange, or it could be a legitimate new attack push in this area — useful\nintel to surface to the security team as a targeted spike.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed\nin \\`healthchecks list\\` as \\`detections.spike\\` with \\`implemented: false\\`.\nUntil the endpoint lands, use \\`chainpatrol --json metrics breakdown --org <slug> --by day --this-week\\`\n(and a comparison window via \\`--from\\`/\\`--to\\`) to see daily detection\nvolume. Anything notably above the recent baseline is a spike — cross-reference\nagainst recent config changes for that source.\n\n#### Drop in Detections\n\nA drop is also worth looking into. It may be a bad config change, or it may\nmean the config is not really relevant and can be safely turned off (not all\ndefault-on configs are relevant to every org).\n\n**Run via CLI:** the extreme case (\"this source went silent\") is covered\ntoday by \\`chainpatrol --json healthchecks run detections.silent-configs --org <slug>\\`,\nwhich is the canonical replacement for the older \\`detections healthcheck\\`.\nIt fails any config whose \\`recentResultCount\\` is below \\`--min-results\\`\nin the \\`--lookback-hours\\` window; pass \\`--run\\` (via the lower-level\n\\`detections healthcheck --run\\`) to also catch configs that error when\nexecuted. For soft drops (still producing results but below baseline),\n\\`detections.drop\\` is marked \\`implemented: false\\` in \\`healthchecks list\\`\n— use \\`metrics breakdown --by day\\` and compare windows manually.\n\n### Reviewing\n\nPENDING proposals split into two operationally distinct buckets:\n\n- **Needs Review** — assets not on a watchlist, or reports submitted by a\n customer. This is the reviewing UI's default view (\\`excludeWatchlisted=true\\`)\n and the actionable queue reviewers work from. Pile-ups and aged items\n here are high priority — \\`fail\\` severity is reachable.\n- **Watchlisted** — pending proposals on watchlisted assets (excluding\n customer-reported reports). The UI hides these by default because\n watchlisting is the act of intentionally deferring the asset. Pile-ups\n and aged items here are worth surfacing as cleanup work but **severity\n is capped at warn** — they should never block on the same SLA as Needs\n Review.\n\nEach bucket has its own pile-up and age check, so you can grade them\nindependently and tune thresholds without one drowning the other.\n\n#### Pile Up / Backlog of Needs-Review Proposals\n\nToo many proposals waiting in review. For most organizations this is over 100\nreports, but really the threshold is relative to the average number of\nconfirmed threats per week. Example: if an org only adds 5 blocked threats per\nweek, then a 7-day backlog of even 10 proposals is a really big deal.\n\n**Run via CLI:** **Implemented as \\`healthchecks run reviewing.backlog\\`.**\nThe endpoint counts the **Needs Review** subset only (assets not\nwatchlisted, or reports marked \\`reportedByCustomer\\`) and grades severity\nagainst per-org thresholds (default warn=50, fail=100; override with\n\\`--warn-threshold\\` / \\`--fail-threshold\\` via the run payload). This is\nthe number the reviewing page in the app shows by default, so the\nhealthcheck output matches what reviewers see.\n\nFor raw counts plus SLA / age breakdowns,\n\\`chainpatrol --json queues snapshot --org <slug>\\` remains useful and\nexposes \\`reviewQueue.totalPendingProposals\\` and\n\\`reviewQueue.distinctReports\\` (note: \\`queues snapshot\\` does NOT apply\nthe watchlist filter, so its number is the sum of Needs Review +\nWatchlisted). Compare against the org's typical weekly throughput\n(use \\`metrics summary --this-week\\` for that baseline) — a backlog that\nexceeds a week of typical confirmed-threat volume is a finding regardless\nof the absolute number.\n\n#### Really Old Needs-Review Proposals\n\nAny Needs-Review proposal waiting longer than 14 days is a sign something\nhas gone wrong. Even complex investigations rarely take longer than this.\nExcept for rare cases, these should be rejected or approved to prevent a\nbacklog from building.\n\n**Run via CLI:** **Implemented as \\`healthchecks run reviewing.old-proposals\\`.**\nThe endpoint counts Needs-Review proposals older than the warn / fail age\nthresholds (default 7 / 14 days) and lists the oldest offenders in\n\\`findings\\`. \\`queues snapshot\\` (\\`reviewQueue.ageBuckets.gte168h\\`) still\nworks as a raw view, and \\`reviewQueue.slaBuckets.breached\\` captures the\nstrictest SLA breaches separately — any non-zero value is worth raising.\n\n#### Watchlist Pile-Up / Old Watchlisted Proposals\n\nWatchlisted-pending proposals are deferred on purpose, but they shouldn't\ngrow unbounded — a huge pile or very-old items signal that the watchlist\nneeds a cleanup pass. These don't block on the same SLA as Needs Review.\n\n**Run via CLI:** **Implemented as \\`healthchecks run reviewing.watchlist-backlog\\`**\n(pile-up count, default warn=200) and\n**\\`healthchecks run reviewing.watchlist-old\\`** (default warn-age 30\ndays). Both cap severity at warn. When reporting findings during an org\nhealthcheck, group them under \"watchlist cleanup\" rather than mixing with\nNeeds-Review findings — they're operationally different concerns.\n\n#### Spike in Auto Approved Reports\n\nIf suddenly a lot of proposals in an org are being approved by automation,\nthat can be a sign of a bad rule approving too much, or a break in auto\nconfidences. Sometimes detection is spamming things that uniquely combine\nwith a weakness of a rule — which is effectively a bad rule. In all these\ncases, notify an engineer at ChainPatrol and check any detection configs you\nadjusted recently, since those may be the cause of spam combined with a weak\nrule.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed\nin \\`healthchecks list\\` as \\`reviewing.auto-approval-spike\\` with\n\\`implemented: false\\`. As a proxy until the endpoint lands, use\n\\`chainpatrol --json metrics breakdown --org <slug> --by day --this-week\\`\nand look for a sudden surge in \\`newThreats\\` / \\`threatsWatchlisted\\` that\nisn't matched by a parallel rise in reviewer activity — that gap usually\npoints at automation doing the approving.\n\n### Blocklisting\n\n#### Google Safe Browsing (Coming Soon)\n\n(Needs new public API added before this works.)\n\nHigh error rate in Google Safe Browsing submission tracker. Each submission\nhas a status. If too many are in \\`CANCELLED\\`, that means Google's engine\ndenied our submission. Contact ChainPatrol's eng team to investigate why, and\nalso take a look at the org's custom detection sources — it's possible there\nare too many false positives landing on the blocklist, indicating issues with\ndetection and reviewing rules.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed\nin \\`healthchecks list\\` as \\`blocklisting.gsb-cancelled-rate\\` with\n\\`implemented: false\\`. Until Google Safe Browsing submission state is\nexposed in the public API, this remains a manual / engineering-team check.\n\n### Takedowns\n\nThe takedown pipeline has three stages — TODO (queued, not yet filed),\nIN_PROGRESS (filed, waiting on vendor / customer / refile), and a terminal\nstate (COMPLETED or CANCELLED). Healthchecks cover pile-ups at each stage,\nplus quality/configuration issues.\n\n#### Too Many Takedowns in ToDo\n\nCan mean a gap in automated takedowns not being implemented for some new area\nof threats. It can also mean the areas that require manual takedowns are\nbeing missed by the takedown team.\n\n**Run via CLI:** **Implemented as \\`healthchecks run takedowns.todo-volume\\`.**\nCounts takedowns sitting in TODO (default warn=50, fail=100). For raw\nbreakdowns by type, cross-reference with\n\\`chainpatrol --json metrics breakdown --org <slug> --by assetType\\` —\nitems piled up on a specific platform usually point at an automation\ngap there.\n\n#### Too Many Takedowns In Progress\n\nTypically means something is wrong with the submission itself. The takedown\nmay need to be resubmitted, the vendor asked for more evidence, or we may\nhave submitted it in the wrong place.\n\n**Run via CLI:** Two checks, complementary:\n\n- **\\`healthchecks run takedowns.in-progress-volume\\`** — counts all\n IN_PROGRESS takedowns regardless of age (default warn=30, fail=75).\n Catches a vendor-side or submission-format problem before items go\n stale.\n- **\\`healthchecks run takedowns.stale-in-progress\\`** — counts IN_PROGRESS\n takedowns past a staleness threshold (default 7 days), lists the oldest\n offenders. Any non-zero value is worth investigating; a growing count\n across snapshots strongly suggests vendor-side or format issues.\n\n#### Too Many Cancelled Takedowns\n\nTakedowns should rarely be cancelled. A cancelled takedown means \"we will not\ndo this takedown\" for some reason. Cases like adding an item to the blocklist\nwhen it's already taken down are treated as completed, not cancelled. So even\n3 cancelled takedowns in a 7-day period is too many.\n\n**Run via CLI:** **Implemented as \\`healthchecks run takedowns.cancelled-count\\`.**\nCounts transitions into the CANCELLED status from the TakedownEvent log\nwithin the lookback window (default 7 days, warn=3, fail=10). The check\nuses the event log rather than \\`Takedown.updatedAt\\` so it correctly\nattributes the cancellation date even if the takedown has since been\nedited. A spike usually means a quality problem in the proposal funnel\nor the CANCELLED status being used as a catch-all.\n\n#### Automated Takedowns Turned Off for Over 30 Days\n\nAutomated takedowns should be on by default for nearly every organization.\nAny issue that would make you want to turn off automated takedowns should be\nresolved within 30 days.\n\n**Run via CLI:** **Implemented as \\`healthchecks run takedowns.automation-off\\`.**\nChecks \\`Organization.isAutomatedTakedownsActive\\` and derives the\noff-duration from the most recent \\`SERVICES_AUTOMATED_TAKEDOWNS_UPDATED\\`\nentry in \\`OrganizationEvent\\` (default warn 30 days, fail 60 days). Orgs\nwith takedown service entirely disabled (\\`isTakedownsActive=0\\`) are\nskipped — automation being off is implied in that case.\n\n### Assets\n\nHealthchecks on the asset model — specifically asset liveness state, which\nthe takedown team depends on for follow-up.\n\n#### Spike in Recently Dead Assets\n\nA sudden spike in DEAD detections can be a good signal (takedowns or platform\nmoderation working) but it can also mean the liveness checker is\nmisclassifying assets after a platform change, captcha rollout, or anti-bot\nupdate. If many assets become dead at once, sample a few manually.\n\n**Run via CLI:** **Implemented as \\`healthchecks run assets.dead-asset-spike\\`.**\nCompares \\`DETECTED_AS_DEAD\\` events in the current window (default 24h)\nagainst the baseline rate from the prior \\`baselineDays\\` (default 7d).\nSeverity fires only when the current count clears \\`minSpikeCount\\` (default\n10) AND exceeds the multiplier (default warn ×2, fail ×4). The\n\\`minSpikeCount\\` floor suppresses noise on orgs with near-zero baseline\nactivity. When this fires, pull a sample of recent DEAD assets, verify a\nfew in a browser, and notify ChainPatrol engineering if the sample is\nclearly still live.\n\n#### Assets Marked Dead but Still Online / Assets Not Marked Dead Even Though They Are Down\n\nThese two opposite failure modes are **not implemented as healthchecks**\n(listed as \\`assets.dead-but-alive\\` and \\`assets.alive-but-marked-dead\\` with\n\\`implemented: false\\`). Both require live HTTP probes against asset URLs,\nwhich is not a synchronous-healthcheck shape.\n\nUntil a dedicated probe command exists:\n\n- For \"marked dead but still alive\": sample a handful of recently-DEAD\n assets, open them in a browser, and watch for any that load. Common\n causes: bot protection, geo-blocking, rate limits, or liveness logic\n that does not handle the asset type correctly.\n- For \"alive but marked dead\": after a known takedown event, sample\n assets that *should* be dead but are still marked alive. Common causes:\n cached responses, soft-404 pages, parked-domain redirects, platform\n suspension pages still returning 200.\n\nIn both cases, if liveness looks miscalibrated for a class of assets,\nnotify ChainPatrol engineering — the checker likely needs a tuning pass for\nthat platform.\n\n## Organization Trend Search Guide\n\nTrend search asks \"what's changed recently for this org?\" — the answer can\nsurface coordinated attacks, new attack channels, or campaigns targeting\nspecific people, **before** they show up as a healthcheck failure. Unlike\nthe HealthCheck Guide above (which grades single signals against\nthresholds), trend search compares a recent window to a baseline window\nand flags ratios, not absolute counts. A trend is interesting even when\nthe pipeline is keeping up with it — the user usually wants to know.\n\nWhen the user asks something like:\n\n- \"are there any trends in org X?\"\n- \"search for trends in <org>\"\n- \"anything unusual happening with <org> lately?\"\n- \"any spikes for <org>?\"\n- \"what's new for <org> this week?\"\n- \"is anyone targeting <org>'s employees more than usual?\"\n- \"are we seeing more YouTube/Telegram/etc. threats for <org>?\"\n\n…run the three trend checks below in parallel (single message, multiple\nBash calls) and surface anything where the current rate is ≥ 2× the\nbaseline AND clears a small absolute floor.\n\n### How to compute \"current vs. baseline\" rates\n\nPick two non-overlapping windows: a **current** window the user cares about\n(default last 7 days) and a **baseline** window ending where the current\none starts (default the prior ~90 days, i.e. \\`current_start - 90d\\` →\n\\`current_start - 1d\\`). For any breakdown bucket, compute:\n\n\\`\\`\\`\ncurrent_per_day = current_count / current_window_days\nbaseline_per_day = baseline_count / baseline_window_days\nratio = current_per_day / max(baseline_per_day, epsilon)\n\\`\\`\\`\n\nFlag a bucket if \\`ratio >= 2\\` AND \\`current_count >= 5\\`. The floor\nsuppresses noise on orgs with near-zero baseline activity (a single new\nYouTube report jumping the rate from 0 to 1/day shouldn't trigger). If\nthe user gives a different window (\"last 3 days\", \"this month\"), adjust\nboth windows proportionally and keep the same ratio threshold.\n\n### Trend 1 — Spike in a specific asset type\n\nA sudden jump in threats on a platform the org doesn't usually see\ntraffic on (\"normally you don't get targeted on YouTube much, but now\nthere's a spike there\") is worth flagging — it usually means an attacker\nhas discovered a new channel that works for them. Even when the absolute\nnumber is small, the *ratio* against the org's normal mix is what\nmatters.\n\nUse \\`metrics breakdown --by type\\` over both windows:\n\n\\`\\`\\`bash\n# Current window — last 7 days, broken out by asset type\nchainpatrol --json metrics breakdown --org <slug> --by type \\\\\n --from <YYYY-MM-DD 7d ago> --to <YYYY-MM-DD today>\n\n# Baseline window — prior ~90 days ending where the current window starts\nchainpatrol --json metrics breakdown --org <slug> --by type \\\\\n --from <YYYY-MM-DD 97d ago> --to <YYYY-MM-DD 8d ago>\n\\`\\`\\`\n\nEach entry in \\`.points\\` has \\`{ type, count }\\`. Join the two windows on\n\\`type\\`, apply the ratio rule above, and report each flagged type.\n\nCross-reference any flagged type with \\`configs list --org <slug>\\` — if\nthe spike is on Twitter / X but \\`twitter_post_search\\` is disabled, the\ndetection source for that channel isn't even running for this org and\nthe spike is being caught by something else (likely customer reports);\nsuggest turning it on.\n\n### Trend 2 — Spike in overall threat volume\n\nA sharp rise in total threats across the org — regardless of type or\nbrand — usually means a coordinated attack or campaign is underway. This\nisn't a healthcheck (reviewing and takedowns may be keeping up just\nfine), but the security team still wants to know so they can warn\ncustomers / employees / partners.\n\n\\`\\`\\`bash\n# Daily volume for the last ~6 weeks — enough to eyeball a baseline AND\n# see the spike on the right edge of the series.\nchainpatrol --json metrics breakdown --org <slug> --by day \\\\\n --from <YYYY-MM-DD 42d ago> --to <YYYY-MM-DD today>\n\\`\\`\\`\n\nRead \\`.points\\` (one entry per day). Take the trailing 7-day average and\ncompare against the prior ~5 weeks (the same 2× / floor rule). For round\ntotals to quote to the user, also pull:\n\n\\`\\`\\`bash\n# Two calls — current and baseline windows — using --include to skip\n# the per-type / per-day series and keep the call cheap.\nchainpatrol --json metrics organization --org <slug> \\\\\n --include reports,newThreats,threatsWatchlisted \\\\\n --from <current_from> --to <current_to>\n\nchainpatrol --json metrics organization --org <slug> \\\\\n --include reports,newThreats,threatsWatchlisted \\\\\n --from <baseline_from> --to <baseline_to>\n\\`\\`\\`\n\nWhen volume spikes broadly, also skim \\`reports list --reported-by-customer\\`\nfor the same window — a wave of customer reports often arrives a few hours\nahead of automated detection on a real coordinated push.\n\n### Trend 3 — Spike on a specific sub-brand (especially employee brands)\n\nSub-brands the org has set up for individual people — employees,\nexecutives, public figures — are high-signal targets. A sudden spike on\none usually means an attacker is impersonating that person specifically,\nwhich is materially different from a generic phishing wave and usually\nwarrants a direct heads-up to the person involved. In the database these\nare \\`Brand\\` rows with \\`type: INDIVIDUAL\\` (vs. \\`ORGANIZATION\\` for\nproduct / parent brands).\n\n**Step 1: Pull the brand roster** so you can label each spike as\n\"employee\" or \"product\" without asking the user.\n\n\\`\\`\\`bash\nchainpatrol --json brands list\n\\`\\`\\`\n\nBuild a map of \\`slug → type\\` from the response so the next step's\nfindings can be tagged \\`(employee)\\` or \\`(product)\\` automatically.\n\n**Step 2: Run the breakdown over both windows.**\n\n\\`\\`\\`bash\n# Current window — last 7 days, broken out by sub-brand\nchainpatrol --json metrics breakdown --org <slug> --by brand \\\\\n --from <YYYY-MM-DD 7d ago> --to <YYYY-MM-DD today>\n\n# Baseline window — prior ~90 days\nchainpatrol --json metrics breakdown --org <slug> --by brand \\\\\n --from <YYYY-MM-DD 97d ago> --to <YYYY-MM-DD 8d ago>\n\\`\\`\\`\n\nEach entry in \\`.points\\` has \\`{ brandId, brandSlug, brandName, count }\\`.\nApply the ratio rule, join against the \\`slug → type\\` map from Step 1,\nand report each spiking sub-brand by name plus its type.\n\n**Step 3: Drill into the flagged brand** with \\`metrics organization\n--brand-slug <slug>\\` for a per-day / per-type breakdown scoped to that\nbrand only. The server applies the brand filter to the scalar metrics\n(\\`newThreats\\`, \\`takedownsFiled\\`, etc.) AND to \\`blockedByType\\` /\n\\`blockedByDay\\`, so the time series and per-asset-type counts are real\nbrand-only data:\n\n\\`\\`\\`bash\nchainpatrol --json metrics organization --org <slug> \\\\\n --brand-slug <brand-slug> \\\\\n --include newThreats,blockedByType,blockedByDay \\\\\n --from <YYYY-MM-DD 7d ago> --to <YYYY-MM-DD today>\n\\`\\`\\`\n\nIf multiple \\`INDIVIDUAL\\` brands are spiking at the same time, treat that\nas a stronger signal than a single one — likely a campaign targeting the\ncompany's people rather than a single impersonation. Prioritize those\nfindings over single-brand spikes when summarizing.\n\n### Reporting trend findings\n\nReport each spiking bucket as its own finding with: the trend it falls\nunder (asset type / overall volume / sub-brand), the current vs. baseline\nrate (e.g. \"youtube: 12/day last 7d vs. 1.2/day prior 90d, ×10\nbaseline\"), and a one-line follow-up:\n\n- Asset-type spike → check \\`configs list\\` for the matching detection\n source and turn it on if it's off.\n- Overall-volume spike → flag a possible coordinated campaign; suggest\n the security team look at the recent \\`reports list\\` for shared\n infrastructure (sender, registrar, hosting).\n- Sub-brand spike → look up \\`type\\` via \\`brands list\\`; if\n \\`INDIVIDUAL\\`, suggest a direct heads-up to that person; if\n \\`ORGANIZATION\\`, suggest a product-team alert.\n\nIf none of the three trends fire above the 2× / floor thresholds, say so\nexplicitly (\"no significant trends in the last 7 days vs. the prior 90\")\nrather than dumping every breakdown number — the absence is the\nfinding.\n`;\n}\n","export function parseSkillVersion(content: string): string | undefined {\n const fmMatch = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!fmMatch) return undefined;\n const versionMatch = fmMatch[1].match(/^version:\\s*(.+?)\\s*$/m);\n return versionMatch ? versionMatch[1].trim() : undefined;\n}\n"]}
1
+ {"version":3,"sources":["../../src/skill/content.ts","../../src/skill/parse.ts"],"names":[],"mappings":";;;AAAO,SAAS,kBAAkB,OAAA,EAAyB;AACzD,EAAA,OAAO,CAAA;AAAA;AAAA,SAAA,EAEE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAumDlB;;;AC1mDO,SAAS,kBAAkB,OAAA,EAAqC;AACrE,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,6BAA6B,CAAA;AAC3D,EAAA,IAAI,CAAC,SAAS,OAAO,MAAA;AACrB,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,CAAC,CAAA,CAAE,MAAM,wBAAwB,CAAA;AAC9D,EAAA,OAAO,YAAA,GAAe,YAAA,CAAa,CAAC,CAAA,CAAE,MAAK,GAAI,MAAA;AACjD","file":"index.js","sourcesContent":["export function buildSkillContent(version: string): string {\n return `---\nname: chainpatrol\nversion: ${version}\ndescription: |\n ChainPatrol CLI assistant. Helps use the chainpatrol CLI tool: login via device\n code flow, check auth status, list detection configs, list reports (including\n customer-reported ones), run CLI commands, and run an organization\n healthcheck across the detection / reviewing / blocklisting / takedown\n pipeline.\n Use when: \"chainpatrol cli\", \"login to chainpatrol\", \"check detection configs\",\n \"am I logged in\", \"list configs\", \"use the cli\", \"list reports\",\n \"customer reports\", \"reports reported by customer\", \"find detection gaps\",\n \"org healthcheck\", \"organization health check\", \"audit my org\",\n \"what's wrong with org\", \"review org setup\", \"list orgs\", \"list organizations\",\n \"get org\", \"get organization\", \"show org details\", \"org by slug\",\n \"fetch org details\", \"org info\",\n \"orgs with takedowns off\", \"automation off across orgs\",\n \"which customers have X enabled\", \"service toggles by org\",\n \"obligatory admin approval\", \"obligatory organization admin approval\",\n \"obligatory approval\", \"admin approval enabled\", \"admin approval orgs\",\n \"requires customer review\", \"requires admin approval\",\n \"orgs that require admin approval\", \"orgs requiring approval\",\n \"which orgs require approval for twitter\", \"approval scope\",\n \"admin approval asset types\", \"approval per asset type\",\n \"pending approval\", \"pending approvals\", \"pending service approval\",\n \"orgs with pending approval\", \"pending wallet blocking approval\",\n \"pending takedown approval\", \"awaiting approval\", \"waiting for approval\",\n \"is protection active approval\", \"wallet blocking approval\",\n \"who needs to approve wallet blocking\", \"service change request\",\n \"track orgs with pending approval\", \"orgs awaiting sign-off\",\n \"is this URL blocked\", \"is this domain blocked\", \"is this address blocked\",\n \"check this asset\", \"asset check\", \"lookup asset status\",\n \"what is ARCHIVE_ORG\", \"what does PAGE mean\", \"list asset types\",\n \"supported asset types\", \"asset type mapping\", \"asset type enum\",\n \"what asset types are there\", \"human readable asset type\",\n \"how many takedowns\", \"takedowns in the last\", \"threats taken down\",\n \"across all clients\", \"across all customers\", \"across all orgs\",\n \"across all brands\", \"company-wide\", \"total takedowns\", \"total threats\",\n \"total reports\", \"average takedowns\", \"average threats\", \"average per day\",\n \"average per customer\", \"average per org\", \"rollup across customers\",\n \"sum across orgs\", \"sum across customers\",\n \"trends in org\", \"search for trends\", \"trend search\", \"look for trends\",\n \"any trends\", \"trending threats\", \"spike in asset type\",\n \"spike in threat volume\", \"coordinated attack\", \"spike check\",\n \"anything unusual\", \"anything new for\", \"what's new for\",\n \"employee being targeted\", \"spike on a sub-brand\", \"spike on a brand\",\n \"sub-brand spike\",\n \"list brands\", \"brands for org\", \"list sub-brands\", \"employee brands\",\n \"individual brands\", \"brand list\", \"all brands in org\".\nallowed-tools:\n - Bash\n - Read\n - Grep\n - Glob\n---\n\n# ChainPatrol CLI Skill\n\nYou are a ChainPatrol CLI assistant. Help the user interact with the ChainPatrol\nplatform using the CLI tool.\n\n## Running the CLI\n\nIMPORTANT: Claude Code's sandbox shell often has a minimal PATH\n(\\`/usr/bin:/bin:/usr/sbin:/sbin\\`) that may not include the directory where\n\\`chainpatrol\\` is installed, so bare \\`chainpatrol\\` calls may fail with\n\"command not found\". Always invoke the CLI by its full path.\n\nThe install location depends on the environment:\n\n- **Local installs** typically land at \\`/usr/local/bin/chainpatrol\\` (when\n installed globally via \\`npm install -g @chainpatrol/cli\\`).\n- **Cloud / sandboxed environments** (e.g. Claude Code on the web, Cursor\n Cloud) often install Node into \\`/opt\\` and the binary ends up under a\n Node-version-specific path like \\`/opt/node22/bin/chainpatrol\\`. Variants\n such as \\`/opt/node20/bin/chainpatrol\\` or \\`/opt/node21/bin/chainpatrol\\`\n are also possible depending on which Node version is active.\n\nTo find the binary, try (in order):\n\n\\`\\`\\`bash\ncommand -v chainpatrol \\\\\n || ls /usr/local/bin/chainpatrol /opt/node*/bin/chainpatrol 2>/dev/null \\\\\n | head -n 1\n\\`\\`\\`\n\nThen use that full path for every subsequent command, e.g.:\n\n\\`\\`\\`bash\n/opt/node22/bin/chainpatrol <command> [options]\n# or\n/usr/local/bin/chainpatrol <command> [options]\n\\`\\`\\`\n\nAll examples below use the short name \\`chainpatrol\\` for readability, but you\nMUST substitute the full resolved path in your Bash commands.\n\n## Available Commands\n\n### \\`login\\` — Authenticate with ChainPatrol\n\nUses the OAuth Device Code flow (RFC 8628):\n1. CLI requests a device code from the server\n2. User is shown a code and a URL to visit\n3. User authorizes in the browser\n4. CLI polls for the token\n\n\\`\\`\\`bash\nchainpatrol login\n\\`\\`\\`\n\nJSON mode (for automation):\n\\`\\`\\`bash\nchainpatrol --json login\n\\`\\`\\`\n\n#### Running \\`login\\` from an agent (headless / non-TTY)\n\nThe login flow blocks for up to 30 minutes while polling for the user to\nauthorize in their browser. If you (the agent) run it as a foreground\ncommand and wait for it to exit before reading output, you will appear\nstuck — the verification URL will never be shown because the process is\nstill polling.\n\n**Always run \\`login\\` in the background and stream its output**, then\nsurface the verification URL to the user as soon as it appears:\n\n\\`\\`\\`bash\n# 1. Kick off login in the background (do NOT wait for it to exit)\nchainpatrol --json login > /tmp/cp-login.out 2>&1 &\n\n# 2. Wait briefly for the first line, which contains the URL\nfor _ in 1 2 3 4 5 6 7 8 9 10; do\n test -s /tmp/cp-login.out && break\n sleep 1\ndone\ncat /tmp/cp-login.out\n\\`\\`\\`\n\nThe first line emitted is JSON describing the device code, e.g.:\n\n\\`\\`\\`json\n{\"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}\n\\`\\`\\`\n\nShow the user the \\`verification_uri_complete\\` link (or\n\\`verification_uri\\` + \\`user_code\\` as a fallback) and explain that the\nCLI will pick up the token automatically once they authorize. Then keep\nthe background process running and tail it for the final\n\\`{\"status\":\"success\",...}\\` or \\`{\"error\":...}\\` line.\n\nCLI v0.3.3+ also auto-detects non-TTY stdout and prints the URL as plain\ntext immediately when you run \\`chainpatrol login\\` without \\`--json\\`,\nso the same background+tail pattern works without \\`--json\\`. Prefer\n\\`--json\\` so the output is structured and machine-parseable.\n\n### \\`logout\\` — Clear stored credentials\n\n\\`\\`\\`bash\nchainpatrol logout\n\\`\\`\\`\n\n### \\`asset check\\` — Check one or many assets against the blocklist\n\nLook up a URL, domain, or crypto address and return its aggregated status\n(\\`BLOCKED\\`, \\`ALLOWED\\`, or \\`UNKNOWN\\`) plus a per-source breakdown\n(ChainPatrol + external feeds like eth-phishing-detect, phishfort, seal,\npolkadot-phishing). Works whether you're authenticated via device-code\nlogin or via a \\`CHAINPATROL_API_KEY\\` env var.\n\nSingle asset:\n\n\\`\\`\\`bash\nchainpatrol asset check https://phish.example\nchainpatrol asset check 0xabc123...\n\\`\\`\\`\n\n#### Bulk checks (preferred for >1 asset)\n\nPass multiple assets in a single invocation — the CLI runs them in\nparallel (concurrency 10) and returns one row per asset. **Do this\ninstead of looping the CLI in a shell \\`for\\` loop**: one process, one\nauth handshake, parallel HTTP. Use either positional args or repeated\n\\`--asset\\`:\n\n\\`\\`\\`bash\n# positional form\nchainpatrol asset check a.example b.example c.example\n\n# repeated --asset (handy when content has spaces or special chars)\nchainpatrol asset check --asset a.example --asset b.example\n\n# from a file of one-asset-per-line (use xargs to splat into one call)\nxargs -a domains.txt chainpatrol asset check\n\\`\\`\\`\n\nJSON mode is the agent-friendly default — single-asset JSON keeps the\nflat \\`{ content, status, source, reason?, sources[], watchStatus? }\\`\nshape; multi-asset JSON returns \\`{ results: [...], summary: { checked,\nblocked, allowed, unknown, errored } }\\`:\n\n\\`\\`\\`bash\nchainpatrol --json asset check https://phish.example\nchainpatrol --json asset check a.example b.example c.example\n\\`\\`\\`\n\nMarkdown / CSV are also available for sharing in docs / chat:\n\n\\`\\`\\`bash\nchainpatrol asset check phish.example --output markdown\nchainpatrol asset check a.example b.example --output csv\n\\`\\`\\`\n\nIf any individual lookup fails, the CLI still prints results for the\nsuccessful ones, then exits non-zero so failures aren't silently\nswallowed.\n\n### \\`asset types\\` — List every supported asset type and what it means\n\nWhen a user asks \"what is \\`ARCHIVE_ORG\\`?\", \"what does \\`PAGE\\` mean?\",\nor \"what asset types does ChainPatrol support?\" — don't guess. Run:\n\n\\`\\`\\`bash\nchainpatrol asset types\nchainpatrol --json asset types\n\\`\\`\\`\n\nThe mapping is bundled with the CLI (no API or auth required), so this\nis safe to run anywhere. Each row is \\`{ type, label, description }\\`:\n\\`type\\` is the canonical enum value used by every \\`--asset-type\\` flag\n(\\`asset list\\`, \\`threats list\\`, \\`takedowns list\\`, \\`detections list\\`,\n\\`reports list\\`, \\`orgs assets list\\`); \\`label\\` is the human-friendly\ndisplay name (\\`ARCHIVE_ORG\\` → \\`Archive.org\\`, \\`PAGE\\` → \\`Page\\`,\n\\`FIVE_HUNDRED_PX\\` → \\`500px\\`); \\`description\\` is a one-line note for\nunobvious entries.\n\nUse this whenever you need to translate between enum and display name,\nvalidate that a type the user mentioned is real, or enumerate options\nbefore constructing a filter.\n\n### \\`configs list\\` — List detection configurations\n\nRequires authentication and an organization slug.\n\n\\`\\`\\`bash\nchainpatrol configs list --org <slug>\n\\`\\`\\`\n\nJSON mode:\n\\`\\`\\`bash\nchainpatrol --json configs list --org <slug>\n\\`\\`\\`\n\nThe \\`--org\\` flag is saved for future commands. Once set, you can omit it:\n\\`\\`\\`bash\nchainpatrol configs list\n\\`\\`\\`\n\n### \\`reports list\\` — List recent reports for an organization\n\nReturns the most recent reports submitted for an organization. Each report\nincludes a \\`reportedByCustomer\\` boolean indicating whether the report was\nsubmitted by a customer of ChainPatrol (e.g. via API key, Slack/Telegram bot,\nor another external integration) rather than by ChainPatrol's automated\ndetections or staff reviewers.\n\n\\`\\`\\`bash\nchainpatrol reports list --org <slug>\n\\`\\`\\`\n\nCommon flags:\n- \\`--limit <n>\\` page size (1-20)\n- \\`--cursor <id>\\` pagination cursor (use \\`nextCursor\\` from a previous response)\n- \\`--status <s>\\` filter by report status (e.g. \\`TODO\\`, \\`IN_PROGRESS\\`, \\`DONE\\`)\n- \\`--search <q>\\` search query across title/description/asset content\n- \\`--reported-by-customer\\` only show reports submitted by a customer\n- \\`--no-reported-by-customer\\` only show reports NOT submitted by a customer\n (i.e. found by ChainPatrol's automation or staff)\n\nJSON mode is recommended when you want to analyze the data programmatically:\n\\`\\`\\`bash\nchainpatrol --json reports list --org <slug> --reported-by-customer\n\\`\\`\\`\n\nThe response includes \\`totalCount\\`: the number of reports matching the same\nfilters as the query, independent of \\`--limit\\`/\\`--cursor\\`. Use it to:\n- Report a denominator when sampling (e.g. \"sampled 200 of ~4,235\").\n- Decide up front whether to page through everything or narrow filters first —\n if \\`totalCount\\` is huge, add more filters instead of paginating blindly.\n- Drive a progress indicator while paginating with \\`nextCursor\\`.\n\n\\`totalCount\\` honors every list filter (\\`--status\\`, \\`--reported-by-customer\\` /\n\\`--no-reported-by-customer\\`, \\`--exclude-automation\\`, \\`--review-status\\` /\n\\`--only-rejected\\`, \\`--asset-type\\`, brand, \\`--country-code\\`, \\`--from\\`/\\`--to\\`,\n\\`--updated-from\\`/\\`--updated-to\\`, \\`--search\\`, \\`--reporter-query\\`), so changing\nfilters changes the count. \\`reports\\` (the returned page) is capped by\n\\`--limit\\` (max 20); \\`totalCount\\` is not.\n\n#### Use case: finding gaps in ChainPatrol detection\n\nCustomer-reported reports (\\`reportedByCustomer=true\\`) are a valuable signal\nfor finding gaps in ChainPatrol's automated detections and staff triage: each\none is a threat the customer found before ChainPatrol's own systems did. When\nthe user asks something like:\n\n- \"show me the threats our customers are reporting\"\n- \"what is X's customer reporting?\"\n- \"where are we missing detections for org Y?\"\n- \"summarize recent reports submitted by customers\"\n\n…use \\`chainpatrol --json reports list --org <slug> --reported-by-customer\\`\nto fetch them, then highlight patterns (asset types, domains, common\nkeywords, recurring brands) so the user can:\n1. Spot detection coverage gaps to fix in the org's detection configs.\n2. Improve staff triage runbooks for recurring scams.\n3. Work directly with the customer to close the loop and prevent future\n misses.\n\nYou can also compare with the non-customer set using\n\\`--no-reported-by-customer\\` to gauge detection coverage on the same time\nwindow.\n\n### \\`detections healthcheck\\` — Validate enabled detection configs produce recent results\n\nServer-side check. The CLI calls the ChainPatrol API; the server fetches each\nenabled detection config for the org, counts results produced in the lookback\nwindow, and FAILs configs that fall under \\`--min-results\\` (or whose run\nerrored when \\`--run\\` is set).\n\n\\`\\`\\`bash\nchainpatrol --json detections healthcheck --org <slug>\n\\`\\`\\`\n\nFlags:\n- \\`--source <key>\\` only validate one source (e.g. \\`twitter_search\\`)\n- \\`--min-results <n>\\` minimum results required in the window to pass\n- \\`--lookback-hours <n>\\` size of the lookback window\n- \\`--run\\` ask the server to run each config first, then validate the fresh output\n- \\`--include-disabled\\` also validate disabled configs\n\nWhat this command covers:\n- Configs that have gone silent (recentResultCount below threshold)\n- Configs that error when run (runOk=false) when \\`--run\\` is set\n\nWhat it does NOT cover:\n- Reviewing backlog / SLA breaches → use \\`queues snapshot\\`\n- Takedown ToDo / In Progress / Cancelled volumes → use \\`queues snapshot\\`\n- Spikes or drops in detection volume over time → use \\`metrics breakdown\\`\n- Customer-reported gaps → use \\`reports list --reported-by-customer\\`\n- Google Safe Browsing submission errors (not yet exposed in CLI)\n\nUse it as the first signal in the Detection part of an org healthcheck, then\nfall back to the manual checks in the HealthCheck Guide below for everything\nelse.\n\n> Prefer the newer \\`healthchecks\\` namespace below. \\`detections healthcheck\\`\n> is the original single-purpose command; the \\`healthchecks\\` namespace is\n> the canonical place to discover and run every check we expose.\n\n### \\`healthchecks list | run\\` — Run uniform org healthchecks via the public API\n\nThe \\`healthchecks\\` namespace is the canonical way to run the named checks\nfrom the Organization HealthCheck Guide below. Each implemented endpoint\nreturns the same uniform shape — \\`{ id, ok, severity, observed, threshold,\nfindings, suggestedAction }\\` — so the CLI / agent can render every check the\nsame way regardless of category.\n\n\\`\\`\\`bash\n# Discover every check the platform exposes today, including planned checks\n# that are not yet implemented on the backend.\nchainpatrol --json healthchecks list\n\n# Run a single named check.\nchainpatrol --json healthchecks run reviewing.backlog --org <slug>\n\n# Run every implemented check in parallel and aggregate the results.\nchainpatrol --json healthchecks run --all --org <slug>\n\\`\\`\\`\n\nEach implemented check has a stable id of the form \\`category.name\\`. Implemented\nids today: \\`detections.silent-configs\\`, \\`reviewing.backlog\\`,\n\\`reviewing.old-proposals\\`, \\`reviewing.watchlist-backlog\\`,\n\\`reviewing.watchlist-old\\`, \\`takedowns.todo-volume\\`,\n\\`takedowns.in-progress-volume\\`, \\`takedowns.stale-in-progress\\`,\n\\`takedowns.cancelled-count\\`, \\`takedowns.automation-off\\`,\n\\`assets.dead-asset-spike\\`.\n\n### Pending proposals: \"Needs Review\" vs \"Watchlisted\"\n\nPENDING proposals split into two operationally distinct buckets, and we\ngrade them with separate checks:\n\n- **Needs Review** — pending proposals the reviewing UI shows by default\n (\\`excludeWatchlisted=true\\`): assets that are NOT on a watchlist, OR\n reports submitted by a customer (those stay visible even when the asset\n is watchlisted). This is the actionable queue reviewers work from, so\n pile-ups and old items here are high-priority signals (\\`fail\\` severity\n is reachable).\n- **Watchlisted** — pending proposals on watchlisted assets (excluding\n customer-reported reports). The reviewing UI hides these by default\n because watchlisting is the act of intentionally deferring an asset.\n Pile-ups and aged items here are worth surfacing as cleanup work, but\n severity is **capped at warn** so they never block on the same SLA as\n Needs Review. When reporting findings, treat these as lower-priority.\n\nEach healthcheck result includes an \\`appUrl\\` field (string or null) that\ndeep-links to the relevant filtered admin page in the web app — e.g. the\ntakedowns page filtered to IN_PROGRESS for \\`takedowns.stale-in-progress\\`,\nor the review page filtered to oldest pending for \\`reviewing.old-proposals\\`.\n**When reporting a non-OK healthcheck to the user, always surface the\n\\`appUrl\\` so they can jump straight to the right view.** Some checks\n(\\`detections.silent-configs\\`, \\`assets.dead-asset-spike\\`) emit \\`null\\`\nbecause no filterable list page exists for that signal yet.\n\nImplemented checks today:\n\n- **detections.silent-configs** — equivalent to \\`detections healthcheck\\`,\n exposed under the uniform shape.\n- **reviewing.backlog** — counts Needs Review pending proposals and grades\n severity against per-org thresholds (default warn=50, fail=100).\n- **reviewing.old-proposals** — counts Needs Review proposals older than\n the warn / fail age thresholds (default 7 / 14 days) and lists the\n oldest offenders.\n- **reviewing.watchlist-backlog** — counts watchlisted pending proposals\n (default warn=200). Severity capped at warn.\n- **reviewing.watchlist-old** — counts watchlisted pending proposals older\n than the warn-age threshold (default 30 days) and lists the oldest.\n Severity capped at warn.\n- **takedowns.todo-volume** — counts takedowns in TODO (default warn=50,\n fail=100). Pile-ups here usually mean an automation gap on a new threat\n surface, or manual-filing capacity issues.\n- **takedowns.in-progress-volume** — counts takedowns currently IN_PROGRESS\n regardless of age (default warn=30, fail=75). Complements\n \\`stale-in-progress\\` — a high count signals vendor-side or\n submission-format problems even before items go stale.\n- **takedowns.stale-in-progress** — counts takedowns sitting in IN_PROGRESS\n past the staleness threshold (default 7 days) and lists the oldest.\n- **takedowns.cancelled-count** — counts CANCELLED transitions from the\n TakedownEvent log over a rolling window (default 7d, warn=3, fail=10).\n Cancellations should be rare; a spike usually means a proposal-funnel\n quality problem or misuse of the CANCELLED status.\n- **takedowns.automation-off** — flags orgs with takedown service enabled\n but \\`isAutomatedTakedownsActive\\` off for too long (default warn=30d,\n fail=60d). Skipped for orgs with takedown service entirely disabled.\n- **assets.dead-asset-spike** — compares DEAD-detection events in the\n current window against the prior baseline rate; warns on a multiplier\n exceeding the threshold (default 24h vs 7d, ×2 warn / ×4 fail) once the\n current count clears the \\`minSpikeCount\\` floor. Catches liveness-checker\n regressions after platform changes.\n\nThe following checks are listed by \\`healthchecks list\\` (\\`implemented: false\\`)\nbut **not yet implemented on the backend** — when the agent surfaces them in\na healthcheck report, mark them explicitly as \"manual check, no API yet\":\n\n- **detections.coverage-gaps** — blocked assets vs. enabled-source correlation.\n Still requires manual reasoning with \\`configs list\\` + \\`reports list\\`.\n- **detections.spike** / **detections.drop** — require server-side baseline\n modeling. Use \\`metrics breakdown --by day\\` as an interim signal.\n- **reviewing.auto-approval-spike** — needs distinguishing automation vs.\n human approvers in the review history. Use \\`metrics breakdown\\` as a proxy.\n- **blocklisting.gsb-cancelled-rate** — Google Safe Browsing submission state\n is not yet exposed in the public API.\n- **assets.dead-but-alive** / **assets.alive-but-marked-dead** — require live\n HTTP probes against asset URLs, which is not a synchronous-healthcheck\n shape. Until a dedicated probe command exists, sample a handful manually\n from \\`assets list --status DEAD\\` (or ALIVE) and verify in a browser. Notify\n ChainPatrol engineering if the liveness checker looks miscalibrated.\n\nWhen the user asks to \"run a healthcheck on org X\", the canonical command is:\n\n\\`\\`\\`bash\nchainpatrol --json healthchecks run --all --org X\n\\`\\`\\`\n\nThis iterates the implemented entries in \\`healthchecks list\\`, runs them in\nparallel, and aggregates the uniform results. Combine with the manual checks\nin the HealthCheck Guide below for everything still marked\n\\`implemented: false\\`.\n\n### \\`queues snapshot\\` — Operations review/takedown queue snapshot\n\nServer-side aggregation of the operations review queue (pending proposals,\nSLA breaches, age buckets) and the takedown queue (open, in-progress, stale).\n\n\\`\\`\\`bash\nchainpatrol --json queues snapshot --org <slug>\n\\`\\`\\`\n\nUseful for the **Reviewing** and **Takedowns** sections of the HealthCheck\nGuide. Key signals in the response:\n- \\`reviewQueue.totalPendingProposals\\` and \\`reviewQueue.distinctReports\\` —\n backlog size\n- \\`reviewQueue.slaBuckets.breached\\` — SLA breaches (treat any breach as a\n finding)\n- \\`reviewQueue.ageBuckets.gte168h\\` — proposals older than 7 days (anything\n >14 days from the manual guide should always be in here)\n- \\`takedownQueue.totalOpen\\` and \\`takedownQueue.staleInProgress\\` — open\n and stuck takedowns\n\nUse \\`--all\\` to snapshot every org you have access to instead of a single slug.\n\n### \\`orgs list\\` — List organizations with subscription status, service toggles, and admin-approval scope\n\nReturns every organization the caller can see, with each org's\nsubscription status (\\`PROSPECT\\`, \\`POC\\`, \\`ACTIVE\\`, \\`INTEGRATION\\`),\nwhich services are active, and the per-org \"Obligatory Organization\nAdmin Approval\" toggle (with its asset-type scope). Use it to answer\nquestions like \"which customers have takedowns enabled but automation\noff?\", \"which prospects don't have detection turned on yet?\", or \"which\norgs require admin approval before adding Twitter assets to the\nblocklist?\" — filters compose with AND and are applied server-side, so\none call returns the final list.\n\n\\`\\`\\`bash\nchainpatrol --json orgs list \\\\\n --subscription-status ACTIVE \\\\\n --service-active takedowns \\\\\n --service-manual takedowns\n\\`\\`\\`\n\n#### Per-service response shape — \\`automated\\` is takedowns-only\n\nEvery service exposes an \\`active\\` boolean. **Only \\`takedowns\\`\nadditionally exposes \\`automated\\`** — that is the one service where the\nmanual-vs-automated distinction changes real platform behavior (whether\nChainPatrol files the takedown on its own, or queues it for a human to\nfile). \\`reporting\\`, \\`reviewing\\`, and \\`protection\\` have backing\n\\`isAutomated*Active\\` columns in the database, but those flags have no\noperational effect today, so the API deliberately omits them from both\nthe response and the \\`services\\` filter — surfacing them would invite\nmisleading filters like \"reporting.automated=true\" that don't mean\nanything. \\`detection\\` and \\`darkWebMonitoring\\` have always been\nsingle-flag services.\n\n#### Obligatory Organization Admin Approval\n\nEach org also has a separate \"Obligatory Organization Admin Approval\"\ntoggle (\\`Organization.requiresCustomerReview\\` in the schema, surfaced\non the Services settings page in the app) that gates customer-side\nreview of proposals ChainPatrol staff have already approved before\nthey're added to the blocklist. It is NOT one of the operational\nservices above — it is an org-policy toggle with its own per-asset-type\nscope.\n\nEach org's response includes:\n\n\\`\\`\\`json\n{\n \"obligatoryAdminApproval\": {\n \"active\": true,\n \"assetTypes\": [\"TWITTER\", \"URL\"]\n }\n}\n\\`\\`\\`\n\n- \\`active=true\\` + empty \\`assetTypes\\` means approval applies to\n **all** asset types — the org has not narrowed the scope.\n- \\`active=true\\` + a non-empty \\`assetTypes\\` list means approval is\n required only for those asset types.\n- \\`active=false\\` means no approval is required; \\`assetTypes\\` is\n always empty in that case.\n\nSo per-org JSON looks like:\n\n\\`\\`\\`json\n{\n \"services\": {\n \"reporting\": { \"active\": true },\n \"reviewing\": { \"active\": true },\n \"protection\": { \"active\": false },\n \"takedowns\": { \"active\": true, \"automated\": false },\n \"detection\": { \"active\": true },\n \"darkWebMonitoring\": { \"active\": false }\n },\n \"obligatoryAdminApproval\": { \"active\": true, \"assetTypes\": [\"TWITTER\"] },\n \"pendingServiceApprovals\": [\n {\n \"service\": \"protection\",\n \"automated\": false,\n \"serviceType\": \"isProtectionActive\",\n \"serviceName\": \"Wallet Blocking\",\n \"requestedAt\": \"2026-05-30T18:04:11.000Z\"\n }\n ]\n}\n\\`\\`\\`\n\n#### Pending service approvals (Wallet Blocking / Takedowns)\n\nTurning on **Wallet Blocking** (\\`protection\\`, the \"is protection active\"\ntoggle) or **Takedowns** is approval-gated: when a lower-level org member\ntries to enable one, ChainPatrol records a pending request that a higher-up\nat the org — an org OWNER, or ChainPatrol staff acting as owner — must\napprove before the service actually turns on. \\`pendingServiceApprovals\\`\nlists those open, awaiting-sign-off requests per org.\n\nThis is the answer to \"which organizations have a pending approval for\nWallet blocking or Takedown services?\" — it is NOT the same as \"which orgs\nhave Wallet Blocking turned off\". An org can have \\`protection.active=false\\`\nwith **no** pending approval (nobody has asked) OR **with** a pending\napproval (someone asked and it's waiting on an owner). Only\n\\`pendingServiceApprovals\\` distinguishes the two.\n\nEach entry:\n\n- \\`service\\` — \\`protection\\` (Wallet Blocking) or \\`takedowns\\`, matching the\n \\`services\\` keys.\n- \\`automated\\` — \\`true\\` when the request is for the automated variant\n (e.g. \"Automated Wallet Blocking\").\n- \\`serviceType\\` — raw enum, e.g. \\`isProtectionActive\\`.\n- \\`serviceName\\` — human label, e.g. \\`Wallet Blocking\\`.\n- \\`requestedAt\\` — when the enable request was submitted (ISO 8601, UTC).\n\nAn **empty array means nothing is awaiting approval** — read \\`services\\` for\nthe current on/off state, never infer it from this field.\n\nWhen a user asks about \"automated reporting / reviewing / protection\",\nexplain that the flag exists in the DB but has no operational effect and\nisn't exposed by the public API — only \\`takedowns.automated\\` carries a\nreal meaning.\n\nFilter flags (all optional, all comma-separated lists where noted):\n\n- \\`--query <text>\\` partial name match (substring, case-insensitive)\n- \\`--subscription-status <list>\\` one or more of \\`PROSPECT\\`, \\`POC\\`,\n \\`ACTIVE\\`, \\`INTEGRATION\\`. \\`INACTIVE\\` is intentionally not reachable\n through this filter — \\`orgs list\\` only ever returns live customers.\n- \\`--service-active <list>\\` services that must be active\n- \\`--service-inactive <list>\\` services that must be inactive\n- \\`--service-automated <list>\\` services whose automation must be ON.\n **Only \\`takedowns\\` is accepted** — the other services don't have a\n meaningful automation toggle. Passing any other service name errors out.\n- \\`--service-manual <list>\\` services whose automation must be OFF.\n Same restriction — only \\`takedowns\\`.\n- \\`--obligatory-approval-active\\` — only orgs with admin approval on\n- \\`--obligatory-approval-inactive\\` — only orgs with admin approval off\n- \\`--obligatory-approval-asset-type <list>\\` — only orgs whose admin\n approval scope covers EVERY listed asset type. The match treats an\n org's empty per-asset-type list as \"applies to all asset types\", so a\n fully-broad org matches every value passed here. Passing this flag\n implies the feature is on, so it can't be combined with\n \\`--obligatory-approval-inactive\\`. Use the canonical asset-type enum\n names (\\`TWITTER\\`, \\`URL\\`, \\`PAGE\\`, …); run \\`chainpatrol asset types\\`\n to see them all.\n- \\`--pending-approval-active\\` — only orgs with at least one open\n service-enable approval awaiting a higher-up's sign-off.\n- \\`--pending-approval-inactive\\` — only orgs with no pending approvals.\n- \\`--pending-approval-service <list>\\` — only orgs with a pending approval\n for one of the listed services. Accepts \\`protection\\` (Wallet Blocking)\n and/or \\`takedowns\\` — those are the only approval-gated services.\n Matches both the manual and automated variant. Passing this flag implies\n a pending approval exists, so it can't be combined with\n \\`--pending-approval-inactive\\`.\n\nService names: \\`reporting\\`, \\`reviewing\\`, \\`protection\\`, \\`takedowns\\`,\n\\`detection\\`, \\`darkWebMonitoring\\`.\n\nCustomers see only orgs they're a member of. Staff/superuser sessions see\nevery matching org. The response is the same in both cases; visibility is\nenforced server-side.\n\n#### Use case: finding which orgs require admin approval for an asset type\n\nWhen a user asks \"which orgs require admin approval before blocking\nTwitter assets?\", reach straight for the filter — no client-side\npost-processing needed:\n\n\\`\\`\\`bash\nchainpatrol --json orgs list --obligatory-approval-asset-type TWITTER\n\\`\\`\\`\n\nTo audit just the broad opt-ins (\"which orgs require approval for\neverything?\"), filter on the toggle and inspect \\`assetTypes\\` in the\noutput — an empty array means \"all\":\n\n\\`\\`\\`bash\nchainpatrol --json orgs list --obligatory-approval-active \\\\\n | jq '.organizations[] | select(.obligatoryAdminApproval.assetTypes | length == 0) | .slug'\n\\`\\`\\`\n\n#### Use case: tracking orgs with a pending Wallet Blocking / Takedown approval\n\nWhen a user asks \"which organizations have a pending approval for Wallet\nblocking or Takedown services?\", do NOT reach for \\`--service-inactive\nprotection\\` — that lists orgs with the service turned OFF, which is a\ndifferent question. Use the pending-approval filter, which surfaces\nstaff-initiated enable requests still waiting on a higher-up:\n\n\\`\\`\\`bash\n# Every org with a pending Wallet Blocking OR Takedown approval:\nchainpatrol --json orgs list --pending-approval-service protection,takedowns\n\n# Just Wallet Blocking:\nchainpatrol --json orgs list --pending-approval-service protection\n\n# Any pending approval at all, then read each org's pendingServiceApprovals:\nchainpatrol --json orgs list --pending-approval-active \\\\\n | jq '.organizations[] | {slug, pending: [.pendingServiceApprovals[].serviceName]}'\n\\`\\`\\`\n\n### \\`orgs get\\` — Get a single organization by slug\n\nLook up one organization the caller has access to. Returns the same per-org\nshape as a single row from \\`orgs list\\` — \\`active\\` for every service,\nplus \\`automated\\` on \\`takedowns\\` only (see the note above on why other\nservices don't expose an automation flag), plus \\`obligatoryAdminApproval\\`\n(\\`active\\` + \\`assetTypes\\`) and \\`pendingServiceApprovals\\` (open Wallet\nBlocking / Takedown enable requests awaiting a higher-up's sign-off).\nAnything you could read from \\`orgs list\\` you can also read here without\npaging or filtering. Use it when the user names a specific customer (\"show\nme acme's setup\", \"is takedowns automation on for morpho?\", \"does this org\nrequire admin approval for Twitter?\", \"is morpho waiting on a Wallet\nBlocking approval?\") and you don't need the rest of the catalogue.\n\n\\`\\`\\`bash\nchainpatrol orgs get <slug>\nchainpatrol --json orgs get <slug>\n\\`\\`\\`\n\nPermission rules match \\`orgs list\\`:\n\n- Org-scoped API keys: must match the slug — querying another org returns\n 403.\n- User sessions / user-scoped API keys: need an active OrganizationMembership\n on the slug. Staff/superusers can read any org.\n- Soft-deleted orgs are not returned (404). A 404 also fires for orgs the\n caller would not be authorized to see — the endpoint deliberately does\n not distinguish \"doesn't exist\" from \"you can't see this\" beyond the\n generic 403/404 envelope.\n\nPrefer \\`orgs get\\` over \\`orgs list\\` + client-side filter whenever the\ncaller already knows the slug; it's one round-trip and side-steps the\nlist filters entirely.\n\n#### Use case: finding service configuration gaps across the customer base\n\nWhen the user asks something like \"which customers are paying us but don't\nhave takedowns automated yet?\" or \"any orgs running detection without\ntakedowns?\", reach for \\`orgs list\\` — it's the only command that exposes\nservice flags across multiple orgs in one call. Run it in \\`--json\\` mode\nand summarize patterns by service or by subscription tier.\n\n### \\`brands list\\` — List the brands (sub-brands) belonging to an org\n\nReturns every brand for the org the caller is authenticated as, both\nparent / product brands (\\`type: \"ORGANIZATION\"\\`) and individual /\nemployee brands (\\`type: \"INDIVIDUAL\"\\`). Soft-deleted brands are excluded.\nThe endpoint is org-scoped and inferred from auth — there's no \\`--org\\`\nflag — so org-scoped API keys, user API keys, and user sessions all work\nwithout extra arguments.\n\n\\`\\`\\`bash\nchainpatrol brands list # all brands\nchainpatrol brands list --type INDIVIDUAL # employee / person brands only\nchainpatrol brands list --type ORGANIZATION # product / parent brands only\nchainpatrol --json brands list # machine-readable\n\\`\\`\\`\n\nEach entry has \\`{ id, slug, name, type, description, brandGroupId, createdAt }\\`.\nUse this when you need to:\n\n- Distinguish employee vs. product brands ahead of time — e.g. while\n running a trend search and you want to highlight which spiking\n sub-brands are employees so the user gets a direct heads-up to the\n person involved.\n- Resolve a brand name the user mentions to its \\`slug\\` for use with\n \\`metrics organization --brand-slug <slug>\\` or as a \\`brandId\\` for\n \\`takedowns list --brand <id>\\`.\n- Audit a customer's setup — \"how many employee brands does this org\n protect?\" or \"are there brands the org set up but never wired into a\n detection config?\"\n\n### \\`metrics summary | found | breakdown | organization\\` — Org metrics for spike/drop analysis\n\n#### Decision rule — read this before picking a metrics subcommand\n\nWhen the user asks for a number that **spans more than one customer/org/brand**\n— phrases like \"across all clients\", \"across all customers\", \"across all orgs\",\n\"across all brands\", \"company-wide\", \"total takedowns\", \"total threats\",\n\"average takedowns per day across customers\", \"rollup across customers\",\n\"how many takedowns in the last 7 days?\" (no org named) — the answer is\n**\\`chainpatrol metrics organization --all-my-orgs\\`** (or \\`--slugs\\`\nif you want a specific subset). \\`summary\\`, \\`found\\`, and \\`breakdown\\`\nare single-org commands and have **no** \\`--slugs\\`/\\`--all-my-orgs\\`\nform; only \\`organization\\` does. The multi-org form rolls totals +\nper-day / per-org-per-day averages + a per-org breakdown server-side\nin **one** HTTP call.\n\n**Anti-patterns to avoid:**\n\n- ❌ \"There's no cross-org aggregation in the CLI.\" There is —\n \\`metrics organization --all-my-orgs\\` (or \\`--slugs\\`). Don't bail\n out citing the docs without trying these flags.\n- ❌ Looping \\`metrics summary --org X\\` once per customer to sum\n client-side. The multi-org form is one round-trip; the loop is what\n made the endpoint 503-prone in the first place.\n- ❌ Calling \\`orgs list\\` to build a slug list when you just want\n \"everything.\" Use \\`--all-my-orgs\\` and skip the enumeration entirely —\n the server resolves the same set via shared logic. Save \\`orgs list\\`\n + \\`--slugs\\` for cases where the user asked for a specific subset.\n- ❌ Routing the user to Metabase / the data warehouse for a question\n the CLI can answer. Reach for Metabase only when the metric isn't\n in the \\`--include\\` list (\\`reports\\`, \\`newThreats\\`,\n \\`threatsWatchlisted\\`, \\`takedownsFiled\\`, \\`takedownsCompleted\\`,\n \\`domainThreats\\`, \\`twitterThreats\\`, \\`telegramThreats\\`,\n \\`otherThreats\\`, \\`blockedByType\\`, \\`blockedByDay\\`).\n- ❌ \"Iterating 100+ orgs one-by-one isn't practical here.\" Right —\n that's why you don't iterate. \\`--all-my-orgs\\` covers up to 500\n orgs in a single call; for larger fleets, narrow with\n \\`--subscription-status\\` / \\`--service-active\\`, or split an\n explicit \\`--slugs\\` list into batches and sum.\n\n#### Common cross-org recipe (copy and run)\n\nThe shortest answer to \"how many takedowns across all customers in the\nlast 7 days?\" is a single call — no \\`orgs list\\` step needed:\n\n\\`\\`\\`bash\nchainpatrol --json metrics organization \\\\\n --all-my-orgs \\\\\n --subscription-status ACTIVE \\\\\n --service-active takedowns \\\\\n --include takedownsCompleted \\\\\n --from <YYYY-MM-DD 7 days ago> --to <YYYY-MM-DD today>\n\n# Read these fields from the JSON:\n# .scope.orgs ← which orgs the server resolved\n# .metrics.takedownsCompleted ← grand total across all orgs\n# .averages.perDay.takedownsCompleted ← total / windowDays\n# .averages.perOrgPerDay.takedownsCompleted ← total / numOrgs / windowDays\n# .perOrg[slug].metrics.takedownsCompleted ← per-customer breakdown\n\\`\\`\\`\n\nReplace \\`takedownsCompleted\\` with whatever metric the user asked about.\nReplace the date range with whatever window they asked about (default\n3 months if they didn't say).\n\nIf the user *asked* for a specific subset of customers (\"how many for\nacme, beta, gamma combined?\"), use \\`--slugs acme,beta,gamma\\` instead\nof \\`--all-my-orgs\\`. \\`--slugs\\` accepts up to 500 entries.\n\n**Auth note for \\`--all-my-orgs\\`:** requires a user session or a\nuser-scoped API key (it inherits your memberships). Org-scoped API\nkeys are rejected — they're pinned to a single org by design. If\n\\`whoami\\` shows an org-scoped key, use a Bearer session instead or\nask the user to provision a user API key.\n\n#### Single-org examples\n\n\\`\\`\\`bash\nchainpatrol --json metrics summary --org <slug> # defaults to last 3 months\nchainpatrol --json metrics summary --org <slug> --this-week\nchainpatrol --json metrics breakdown --org <slug> --by day --this-week\nchainpatrol --json metrics found --org <slug> --from <YYYY-MM-DD> --to <YYYY-MM-DD>\nchainpatrol --json metrics organization --org <slug> # defaults to last 3 months\n\\`\\`\\`\n\n**Always operate on a bounded window.** When no \\`--from\\`/\\`--to\\`/\\`--this-week\\`\nis passed, every metrics subcommand defaults to the trailing **last 3 months**\nending now. The default exists because unbounded org-wide aggregates over\nmulti-year history routinely time out at the platform layer (Vercel's\n30s function cap), which surfaces to clients as a 503. Stick to the default,\nor pass an explicit narrower window — don't try to bypass the default by\nguessing wide \\`--from\\` values.\n\nThe resolved range is echoed back in every output format so the user can\nsee exactly what they got:\n\n- JSON: \\`{ \"range\": { \"startDate\": \"...\", \"endDate\": \"...\", \"label\": \"last 3 months\" } }\\`\n- Markdown / human: the header line includes the label, e.g.\n \\`Organization metrics for acme — last 3 months\\`, followed by\n \\`Range: <startDate> → <endDate>\\`.\n\nWhen reporting numbers to the user (especially averages and rates),\n**state the window explicitly** — say \"averaged over the last 3 months\"\nrather than just quoting a number, since the same prompt phrased\ndifferently can pick a different window.\n\n\\`breakdown\\` is the one you usually want for healthchecks: it returns a\ntime series (by day or week) of reports, new threats, watchlisted threats,\nand takedowns filed/completed. Compare the latest period against a prior\nwindow to spot the **spike** or **drop** signals described in the manual\nHealthCheck Guide. \\`summary\\` returns a single window total; \\`found\\` is\noriented around when threats were first discovered; \\`organization\\` is\nthe full customer-facing dashboard slice (reports, new threats,\nwatchlisted, takedowns filed/completed, plus per-type and per-day\nbreakdowns).\n\n#### \\`--include\\` — only compute the metrics you actually need\n\n\\`metrics organization\\` runs one Prisma aggregate per requested field.\nBy default all 11 are computed in parallel. Pass \\`--include\\` (or\n\\`include: […]\\` in the JSON body) with the comma-separated subset you\ncare about to skip the rest — the unrequested fields come back as\n\\`null\\` instead of a number, and the server never runs those queries:\n\n\\`\\`\\`bash\n# Only takedowns — one of the cheap shapes\nchainpatrol --json metrics organization --org <slug> \\\\\n --include takedownsFiled,takedownsCompleted\n\n# Time series only, no scalar counts\nchainpatrol --json metrics organization --org <slug> --include blockedByDay\n\\`\\`\\`\n\nAllowed values: \\`reports\\`, \\`newThreats\\`, \\`threatsWatchlisted\\`,\n\\`takedownsFiled\\`, \\`takedownsCompleted\\`, \\`domainThreats\\`,\n\\`twitterThreats\\`, \\`telegramThreats\\`, \\`otherThreats\\`,\n\\`blockedByType\\`, \\`blockedByDay\\`.\n\nUse this whenever the user's question is specific (\"how many takedowns\ndid we file last month?\"). It is the lowest-effort way to make a\nmetrics call cheap enough to run repeatedly. Pair it with an explicit\ndate window for the best behavior.\n\n#### \\`--slugs\\` — multi-org rollups in one call (use this for \"across all customers/clients\")\n\nWhen the user asks for a total or an average **across more than one\norg** — e.g. \"how many takedowns did we complete across all clients\nin the last 7 days?\", \"average reports per org per day this month\",\n\"top 5 customers by new threats\" — reach for \\`--slugs\\` instead of\nlooping. The server fans out the per-org aggregates internally,\nsums into totals, computes per-day and per-org-per-day averages,\nand returns a per-org breakdown in a **single HTTP round trip**:\n\n\\`\\`\\`bash\n# Total takedowns completed across three customers, last 7 days\nchainpatrol --json metrics organization \\\\\n --slugs acme,beta,gamma \\\\\n --include takedownsCompleted \\\\\n --from 2026-05-12 --to 2026-05-19\n\\`\\`\\`\n\nThe response shape switches to multi-org:\n\n\\`\\`\\`json\n{\n \"scope\": { \"mode\": \"multi\", \"orgs\": [\"acme\", \"beta\", \"gamma\"] },\n \"metrics\": { \"takedownsCompleted\": 117, ... }, // sum across orgs\n \"averages\": {\n \"perDay\": { \"takedownsCompleted\": 16.71, ... }, // total / windowDays\n \"perOrgPerDay\": { \"takedownsCompleted\": 5.57, ... }, // total / numOrgs / windowDays\n \"windowDays\": 7\n },\n \"perOrg\": { \"acme\": { \"metrics\": { ... } }, \"beta\": { ... }, \"gamma\": { ... } }\n}\n\\`\\`\\`\n\nTo answer **\"across all of our customers\"**, prefer \\`--all-my-orgs\\` —\nit lets the server resolve the org set in one round-trip, no \\`orgs\nlist\\` step needed:\n\n\\`\\`\\`bash\nchainpatrol --json metrics organization \\\\\n --all-my-orgs \\\\\n --subscription-status ACTIVE \\\\\n --service-active takedowns \\\\\n --include takedownsCompleted \\\\\n --from 2026-05-12 --to 2026-05-19\n\\`\\`\\`\n\nUse \\`--slugs <comma-list>\\` only when the user asked for a *specific*\nsubset of customers (\"how many for acme, beta, gamma combined?\"); the\ntwo flags are mutually exclusive. Both forms cap at 500 orgs per call;\nif a real fleet exceeds that, narrow with\n\\`--subscription-status\\`/\\`--service-active\\` filters or split an\nexplicit \\`--slugs\\` list into batches and sum. **Do not loop\n\\`metrics organization\\` once per org** — N HTTP round-trips is what\nmade the old code 503.\n\nAlways pair the multi-org form with \\`--include\\` to narrow the metric\nset to what the user actually asked about. \"How many takedowns did\nwe complete?\" → \\`--include takedownsCompleted\\`; \"average reports\nper day per customer?\" → \\`--include reports\\`. Skipping \\`--include\\`\nruns ~11 aggregates per org which is rarely necessary.\n\n#### Auth: which credential can use \\`--slugs\\` / \\`--all-my-orgs\\`\n\n- **Org-scoped API key** (the most common production key): can only\n query its own org. If \\`--slugs\\` contains foreign orgs the server\n returns 403; \\`--all-my-orgs\\` is also rejected. Use the \\`--org\\`\n form (or omit both) instead.\n- **User API key**: inherits the user's org memberships, so it can\n query any org the user is a member of (or any org for staff\n users) via \\`--slugs\\` or \\`--all-my-orgs\\`. This is the credential\n you want for cross-org rollups from automated agents.\n- **Session auth** (Bearer token, e.g. \\`chainpatrol login\\`): same\n membership rules as the user. Staff users can query any org.\n\n#### Reporting numbers to the user\n\nWhatever window and scope you choose, **make both explicit in your\nreply** — say \"averaged across 12 customers over the last 3 months\"\nor \"summed across all paying orgs for the last 7 days.\" The same\nprompt phrased slightly differently can pick a different window or\norg set, and the response already echoes\n\\`scope.mode\\` / \\`scope.orgs\\` / \\`averages.windowDays\\` /\n\\`range.label\\` so you don't have to guess.\n\n### \\`presets list | run\\` — Packaged workflows for common jobs\n\n\\`\\`\\`bash\nchainpatrol presets list\nchainpatrol presets run cs-weekly-health --org <slug>\n\\`\\`\\`\n\nUse \\`presets list\\` to discover packaged multi-step workflows. The bundled\n\\`cs-weekly-health\\` preset runs the standard customer-success weekly health\nsweep; prefer it over hand-rolling the same sequence of commands.\n\n## Headless / agent-mode tips\n\nWhen running these commands from an agent or CI:\n\n- Prefer \\`--json\\` (or \\`--output json\\`) so output is structured and the\n update-check stderr nudge is suppressed automatically.\n- Pass \\`--no-input\\` to forbid any interactive prompt (the CLI will error\n out instead of waiting on a TTY).\n- Pass \\`--no-color\\` or set \\`NO_COLOR=1\\` if your sink can't render ANSI.\n- Set \\`CHAINPATROL_NO_UPDATE_CHECK=1\\` to silence the skill/npm freshness\n nudge entirely.\n- For \\`login\\` specifically, see the headless runbook in the login section\n above — login is the one command that needs background + tail handling.\n\n## Checking Auth Status\n\nTo check if the user is logged in, read the credentials file:\n\n\\`\\`\\`bash\ncat ~/.chainpatrol/credentials.json 2>/dev/null && echo \"Logged in\" || echo \"Not logged in\"\n\\`\\`\\`\n\nOr start the login flow which will detect existing sessions:\n\\`\\`\\`bash\nchainpatrol --json login\n\\`\\`\\`\n\n## Configuration\n\nConfig is stored at \\`~/.chainpatrol/config.json\\`:\n- \\`apiUrl\\` — API base URL (default: \\`https://app.chainpatrol.io\\`)\n- \\`defaultOrg\\` — Saved organization slug\n\nOverride config dir with \\`CHAINPATROL_CONFIG_DIR\\` env var.\n\n## Version Checks\n\nThe CLI runs two lightweight version checks alongside each command and prints\na nudge to stderr if anything is out of date:\n\n- **Skill freshness**: compares the installed skill (\\`~/.claude/skills/chainpatrol/SKILL.md\\`)\n to the version bundled with the CLI. If it's missing or older, the user is\n asked to run \\`chainpatrol setup\\`.\n- **NPM freshness**: compares the running CLI version to the latest published\n on the npm registry. The check is throttled to once per 24 hours and capped\n at a 1.5s timeout, with the result cached at \\`<configDir>/version-check.json\\`.\n\nSet \\`CHAINPATROL_NO_UPDATE_CHECK=1\\` to silence both checks. JSON mode\n(\\`--json\\`) and quiet mode (\\`-q\\` / \\`--quiet\\`) also suppress the nudges so\nmachine-readable output is never polluted.\n\n## Global Flags\n\n| Flag | Description |\n|------------------|--------------------------------------------------------|\n| \\`--json\\` | Machine-readable JSON output (shortcut for \\`--output json\\`) |\n| \\`--output <fmt>\\` | Output format: \\`human\\` (default), \\`json\\`, \\`markdown\\`, \\`csv\\` |\n| \\`--quiet\\`, \\`-q\\` | Suppress non-essential output and the update-check nudge |\n| \\`--no-color\\` | Disable ANSI colors (also respects the \\`NO_COLOR\\` env var) |\n| \\`--no-input\\` | Disable interactive prompts (use in scripts and agents) |\n| \\`--org <slug>\\` | Organization slug (saved as the default for later commands) |\n| \\`--help\\`, \\`-h\\` | Show help |\n| \\`--version\\` | Show version |\n\n## Workflow\n\nWhen the user asks to use the CLI, follow this order:\n\n1. **Check login status** — Read \\`~/.chainpatrol/credentials.json\\` to see if they're logged in\n2. **Login if needed** — Run the login command and guide them through the device code flow\n3. **Set org if needed** — Ensure \\`--org\\` is provided or already saved in config\n4. **Run the requested command** — Execute the CLI command and show results\n\n## Detection Config Output\n\nThe \\`configs list\\` command shows detection sources grouped by:\n- **Configured** — Sources with org-level configs (enabled/disabled with details)\n- **Global** — Sources that run globally for all orgs (CERTSTREAM, ASSET_CHECK, BLOCKLIST, etc.)\n- **Not Configured** — Sources available but not yet set up for the org\n\nEach config entry includes: title, status, cron schedule, and configuration parameters.\n\n## Organization HealthCheck Guide\n\nThis guide explains how to look for things that may be wrong for a given org\nacross the full pipeline: **detection → reviewing → blocklisting → takedowns**.\nWhen the user asks for a \"health check\", \"audit\", \"what's wrong with org X\",\n\"review org X's setup\", or similar, walk through each section below and surface\nfindings.\n\nIn each section there are things you can look for that may be wrong. Some\nchecks have a dedicated CLI command that does most of the work server-side;\nothers are soft / qualitative signals that still need you to fetch data with\n\\`configs list\\` or \\`reports list\\` and reason about it manually.\n\n### Quick Path: CLI commands that automate parts of this guide\n\nThe canonical first step is now the \\`healthchecks\\` namespace, which runs\nevery implemented check via the public API and returns a uniform shape per\ncheck (\\`id\\`, \\`severity\\`, \\`observed\\`, \\`threshold\\`, \\`findings\\`,\n\\`suggestedAction\\`):\n\n\\`\\`\\`bash\n# Discover every check the platform exposes, implemented or planned.\nchainpatrol --json healthchecks list\n\n# Run every implemented healthcheck in parallel and aggregate the results.\nchainpatrol --json healthchecks run --all --org <slug>\n\n# Run a single named check.\nchainpatrol --json healthchecks run reviewing.backlog --org <slug>\n\\`\\`\\`\n\nAfter \\`healthchecks run --all\\`, use these complementary commands to cover\nthe signals that are not yet exposed as a uniform healthcheck endpoint:\n\n\\`\\`\\`bash\n# Spikes / drops in detection volume over time (compare windows)\nchainpatrol --json metrics breakdown --org <slug> --by day --this-week\n\n# Customer-reported threats — gaps in our own detection\nchainpatrol --json reports list --org <slug> --reported-by-customer\n\n# What's enabled vs disabled vs not configured for the org\nchainpatrol --json configs list --org <slug>\n\n# Snapshot of review/takedown queues — raw counts behind several healthchecks\nchainpatrol --json queues snapshot --org <slug>\n\n# Packaged weekly customer-success sweep (preferred when it covers the ask)\nchainpatrol presets run cs-weekly-health --org <slug>\n\\`\\`\\`\n\nTreat each command's output as one input to the healthcheck. The manual\nchecks below still apply — especially for signals the CLI cannot infer on\nits own (e.g. \"lots of Twitter assets are blocked but Twitter Post Search\nis disabled\", or \"this drop is fine because the config isn't relevant\nto this org\"). Each subsection of the guide notes whether a healthcheck\nendpoint exists today and what to fall back on when it doesn't.\n\n### Reporting progress while running a healthcheck\n\nWhen the user asks for a healthcheck, narrate each step in real time so they\ncan watch the run unfold. Do NOT batch results and dump everything at the\nend. For every check you run (Quick Path CLI command, or a manual signal\nwhere you're fetching data with \\`configs list\\` / \\`reports list\\` to\nreason about):\n\n1. **Before** you call the command, emit ONE short sentence stating what\n you're about to check, including the org slug. Examples:\n - \"Running detection config healthcheck for morpho…\"\n - \"Snapshotting review and takedown queues for morpho…\"\n - \"Fetching customer-reported reports for morpho to look for detection gaps…\"\n\n2. **As soon as** the command returns, emit ONE short result line that\n starts with a status word and ends with a key number or finding:\n - \\`DONE\\` — ran cleanly, nothing to flag\n - \\`WARN\\` — soft signal worth surfacing (e.g. a borderline backlog,\n mild spike or drop, a config you'd want a human to confirm)\n - \\`FAIL\\` — concrete failure that the user should act on\n - Examples:\n - \"DONE — 14/14 detection configs passing.\"\n - \"WARN — 23 proposals in the review queue; 4 are older than 7 days.\"\n - \"FAIL — twitter_post_search returned 0 results in the last 24h with --run.\"\n\n3. Run **independent** checks in **parallel** (single message, multiple\n Bash tool calls) so progress lines arrive quickly. \\`detections\n healthcheck\\`, \\`queues snapshot\\`, \\`metrics breakdown\\`,\n \\`reports list --reported-by-customer\\`, and \\`configs list\\` are all\n independent of each other and safe to fire concurrently. Dependent\n follow-ups (e.g. paginating \\`reports list\\` with a returned cursor,\n or fetching extra detail on a single failing config) run after the\n first round.\n\n4. After every check is reported, emit a short final **Summary** section:\n - A one-line status per check (✓ / ⚠ / ✗ + name + key number).\n - A short \"Top issues\" list of the highest-priority FAIL / WARN\n findings, in priority order, with the concrete next action for each.\n\nKeep each progress line to one sentence. The goal is for the user to see\nthe healthcheck happening, not to read a wall of text mid-run — full\ndetail belongs in the final Summary or in a follow-up when the user asks\nabout a specific finding.\n\n### Detection\n\n#### Enable Config That May Be Turned Off for Current Threats\n\nBlocked threats exist in an asset type but the matching detection source is\nturned off. For example: lots of Twitter assets are on the blocklist, but\ndetection sources like \"Twitter / X User Search\" or \"Twitter Post Search\" are\ndisabled. Those should be turned on.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed in\n\\`healthchecks list\\` as \\`detections.coverage-gaps\\` with\n\\`implemented: false\\` — when reporting this signal in a healthcheck, note\n\"manual check, no API yet\". Until the endpoint lands, do the correlation\nmanually: \\`chainpatrol --json configs list --org <slug>\\` for enabled vs\ndisabled configs, then \\`chainpatrol --json reports list --org <slug>\\` for\nrecent blocked-item asset types. Flag any asset type where blocked items\nexist but the matching detection source has \\`status: \"disabled\"\\` (or\nappears in the \"Not configured\" group).\n\n#### Spike in Detections\n\nA spike in recent detections is worth investigating. It could be a bad config\nchange, or it could be a legitimate new attack push in this area — useful\nintel to surface to the security team as a targeted spike.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed\nin \\`healthchecks list\\` as \\`detections.spike\\` with \\`implemented: false\\`.\nUntil the endpoint lands, use \\`chainpatrol --json metrics breakdown --org <slug> --by day --this-week\\`\n(and a comparison window via \\`--from\\`/\\`--to\\`) to see daily detection\nvolume. Anything notably above the recent baseline is a spike — cross-reference\nagainst recent config changes for that source.\n\n#### Drop in Detections\n\nA drop is also worth looking into. It may be a bad config change, or it may\nmean the config is not really relevant and can be safely turned off (not all\ndefault-on configs are relevant to every org).\n\n**Run via CLI:** the extreme case (\"this source went silent\") is covered\ntoday by \\`chainpatrol --json healthchecks run detections.silent-configs --org <slug>\\`,\nwhich is the canonical replacement for the older \\`detections healthcheck\\`.\nIt fails any config whose \\`recentResultCount\\` is below \\`--min-results\\`\nin the \\`--lookback-hours\\` window; pass \\`--run\\` (via the lower-level\n\\`detections healthcheck --run\\`) to also catch configs that error when\nexecuted. For soft drops (still producing results but below baseline),\n\\`detections.drop\\` is marked \\`implemented: false\\` in \\`healthchecks list\\`\n— use \\`metrics breakdown --by day\\` and compare windows manually.\n\n### Reviewing\n\nPENDING proposals split into two operationally distinct buckets:\n\n- **Needs Review** — assets not on a watchlist, or reports submitted by a\n customer. This is the reviewing UI's default view (\\`excludeWatchlisted=true\\`)\n and the actionable queue reviewers work from. Pile-ups and aged items\n here are high priority — \\`fail\\` severity is reachable.\n- **Watchlisted** — pending proposals on watchlisted assets (excluding\n customer-reported reports). The UI hides these by default because\n watchlisting is the act of intentionally deferring the asset. Pile-ups\n and aged items here are worth surfacing as cleanup work but **severity\n is capped at warn** — they should never block on the same SLA as Needs\n Review.\n\nEach bucket has its own pile-up and age check, so you can grade them\nindependently and tune thresholds without one drowning the other.\n\n#### Pile Up / Backlog of Needs-Review Proposals\n\nToo many proposals waiting in review. For most organizations this is over 100\nreports, but really the threshold is relative to the average number of\nconfirmed threats per week. Example: if an org only adds 5 blocked threats per\nweek, then a 7-day backlog of even 10 proposals is a really big deal.\n\n**Run via CLI:** **Implemented as \\`healthchecks run reviewing.backlog\\`.**\nThe endpoint counts the **Needs Review** subset only (assets not\nwatchlisted, or reports marked \\`reportedByCustomer\\`) and grades severity\nagainst per-org thresholds (default warn=50, fail=100; override with\n\\`--warn-threshold\\` / \\`--fail-threshold\\` via the run payload). This is\nthe number the reviewing page in the app shows by default, so the\nhealthcheck output matches what reviewers see.\n\nFor raw counts plus SLA / age breakdowns,\n\\`chainpatrol --json queues snapshot --org <slug>\\` remains useful and\nexposes \\`reviewQueue.totalPendingProposals\\` and\n\\`reviewQueue.distinctReports\\` (note: \\`queues snapshot\\` does NOT apply\nthe watchlist filter, so its number is the sum of Needs Review +\nWatchlisted). Compare against the org's typical weekly throughput\n(use \\`metrics summary --this-week\\` for that baseline) — a backlog that\nexceeds a week of typical confirmed-threat volume is a finding regardless\nof the absolute number.\n\n#### Really Old Needs-Review Proposals\n\nAny Needs-Review proposal waiting longer than 14 days is a sign something\nhas gone wrong. Even complex investigations rarely take longer than this.\nExcept for rare cases, these should be rejected or approved to prevent a\nbacklog from building.\n\n**Run via CLI:** **Implemented as \\`healthchecks run reviewing.old-proposals\\`.**\nThe endpoint counts Needs-Review proposals older than the warn / fail age\nthresholds (default 7 / 14 days) and lists the oldest offenders in\n\\`findings\\`. \\`queues snapshot\\` (\\`reviewQueue.ageBuckets.gte168h\\`) still\nworks as a raw view, and \\`reviewQueue.slaBuckets.breached\\` captures the\nstrictest SLA breaches separately — any non-zero value is worth raising.\n\n#### Watchlist Pile-Up / Old Watchlisted Proposals\n\nWatchlisted-pending proposals are deferred on purpose, but they shouldn't\ngrow unbounded — a huge pile or very-old items signal that the watchlist\nneeds a cleanup pass. These don't block on the same SLA as Needs Review.\n\n**Run via CLI:** **Implemented as \\`healthchecks run reviewing.watchlist-backlog\\`**\n(pile-up count, default warn=200) and\n**\\`healthchecks run reviewing.watchlist-old\\`** (default warn-age 30\ndays). Both cap severity at warn. When reporting findings during an org\nhealthcheck, group them under \"watchlist cleanup\" rather than mixing with\nNeeds-Review findings — they're operationally different concerns.\n\n#### Spike in Auto Approved Reports\n\nIf suddenly a lot of proposals in an org are being approved by automation,\nthat can be a sign of a bad rule approving too much, or a break in auto\nconfidences. Sometimes detection is spamming things that uniquely combine\nwith a weakness of a rule — which is effectively a bad rule. In all these\ncases, notify an engineer at ChainPatrol and check any detection configs you\nadjusted recently, since those may be the cause of spam combined with a weak\nrule.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed\nin \\`healthchecks list\\` as \\`reviewing.auto-approval-spike\\` with\n\\`implemented: false\\`. As a proxy until the endpoint lands, use\n\\`chainpatrol --json metrics breakdown --org <slug> --by day --this-week\\`\nand look for a sudden surge in \\`newThreats\\` / \\`threatsWatchlisted\\` that\nisn't matched by a parallel rise in reviewer activity — that gap usually\npoints at automation doing the approving.\n\n### Blocklisting\n\n#### Google Safe Browsing (Coming Soon)\n\n(Needs new public API added before this works.)\n\nHigh error rate in Google Safe Browsing submission tracker. Each submission\nhas a status. If too many are in \\`CANCELLED\\`, that means Google's engine\ndenied our submission. Contact ChainPatrol's eng team to investigate why, and\nalso take a look at the org's custom detection sources — it's possible there\nare too many false positives landing on the blocklist, indicating issues with\ndetection and reviewing rules.\n\n**Run via CLI:** **Not yet implemented as a healthcheck endpoint.** Listed\nin \\`healthchecks list\\` as \\`blocklisting.gsb-cancelled-rate\\` with\n\\`implemented: false\\`. Until Google Safe Browsing submission state is\nexposed in the public API, this remains a manual / engineering-team check.\n\n### Takedowns\n\nThe takedown pipeline has three stages — TODO (queued, not yet filed),\nIN_PROGRESS (filed, waiting on vendor / customer / refile), and a terminal\nstate (COMPLETED or CANCELLED). Healthchecks cover pile-ups at each stage,\nplus quality/configuration issues.\n\n#### Too Many Takedowns in ToDo\n\nCan mean a gap in automated takedowns not being implemented for some new area\nof threats. It can also mean the areas that require manual takedowns are\nbeing missed by the takedown team.\n\n**Run via CLI:** **Implemented as \\`healthchecks run takedowns.todo-volume\\`.**\nCounts takedowns sitting in TODO (default warn=50, fail=100). For raw\nbreakdowns by type, cross-reference with\n\\`chainpatrol --json metrics breakdown --org <slug> --by assetType\\` —\nitems piled up on a specific platform usually point at an automation\ngap there.\n\n#### Too Many Takedowns In Progress\n\nTypically means something is wrong with the submission itself. The takedown\nmay need to be resubmitted, the vendor asked for more evidence, or we may\nhave submitted it in the wrong place.\n\n**Run via CLI:** Two checks, complementary:\n\n- **\\`healthchecks run takedowns.in-progress-volume\\`** — counts all\n IN_PROGRESS takedowns regardless of age (default warn=30, fail=75).\n Catches a vendor-side or submission-format problem before items go\n stale.\n- **\\`healthchecks run takedowns.stale-in-progress\\`** — counts IN_PROGRESS\n takedowns past a staleness threshold (default 7 days), lists the oldest\n offenders. Any non-zero value is worth investigating; a growing count\n across snapshots strongly suggests vendor-side or format issues.\n\n#### Too Many Cancelled Takedowns\n\nTakedowns should rarely be cancelled. A cancelled takedown means \"we will not\ndo this takedown\" for some reason. Cases like adding an item to the blocklist\nwhen it's already taken down are treated as completed, not cancelled. So even\n3 cancelled takedowns in a 7-day period is too many.\n\n**Run via CLI:** **Implemented as \\`healthchecks run takedowns.cancelled-count\\`.**\nCounts transitions into the CANCELLED status from the TakedownEvent log\nwithin the lookback window (default 7 days, warn=3, fail=10). The check\nuses the event log rather than \\`Takedown.updatedAt\\` so it correctly\nattributes the cancellation date even if the takedown has since been\nedited. A spike usually means a quality problem in the proposal funnel\nor the CANCELLED status being used as a catch-all.\n\n#### Automated Takedowns Turned Off for Over 30 Days\n\nAutomated takedowns should be on by default for nearly every organization.\nAny issue that would make you want to turn off automated takedowns should be\nresolved within 30 days.\n\n**Run via CLI:** **Implemented as \\`healthchecks run takedowns.automation-off\\`.**\nChecks \\`Organization.isAutomatedTakedownsActive\\` and derives the\noff-duration from the most recent \\`SERVICES_AUTOMATED_TAKEDOWNS_UPDATED\\`\nentry in \\`OrganizationEvent\\` (default warn 30 days, fail 60 days). Orgs\nwith takedown service entirely disabled (\\`isTakedownsActive=0\\`) are\nskipped — automation being off is implied in that case.\n\n### Assets\n\nHealthchecks on the asset model — specifically asset liveness state, which\nthe takedown team depends on for follow-up.\n\n#### Spike in Recently Dead Assets\n\nA sudden spike in DEAD detections can be a good signal (takedowns or platform\nmoderation working) but it can also mean the liveness checker is\nmisclassifying assets after a platform change, captcha rollout, or anti-bot\nupdate. If many assets become dead at once, sample a few manually.\n\n**Run via CLI:** **Implemented as \\`healthchecks run assets.dead-asset-spike\\`.**\nCompares \\`DETECTED_AS_DEAD\\` events in the current window (default 24h)\nagainst the baseline rate from the prior \\`baselineDays\\` (default 7d).\nSeverity fires only when the current count clears \\`minSpikeCount\\` (default\n10) AND exceeds the multiplier (default warn ×2, fail ×4). The\n\\`minSpikeCount\\` floor suppresses noise on orgs with near-zero baseline\nactivity. When this fires, pull a sample of recent DEAD assets, verify a\nfew in a browser, and notify ChainPatrol engineering if the sample is\nclearly still live.\n\n#### Assets Marked Dead but Still Online / Assets Not Marked Dead Even Though They Are Down\n\nThese two opposite failure modes are **not implemented as healthchecks**\n(listed as \\`assets.dead-but-alive\\` and \\`assets.alive-but-marked-dead\\` with\n\\`implemented: false\\`). Both require live HTTP probes against asset URLs,\nwhich is not a synchronous-healthcheck shape.\n\nUntil a dedicated probe command exists:\n\n- For \"marked dead but still alive\": sample a handful of recently-DEAD\n assets, open them in a browser, and watch for any that load. Common\n causes: bot protection, geo-blocking, rate limits, or liveness logic\n that does not handle the asset type correctly.\n- For \"alive but marked dead\": after a known takedown event, sample\n assets that *should* be dead but are still marked alive. Common causes:\n cached responses, soft-404 pages, parked-domain redirects, platform\n suspension pages still returning 200.\n\nIn both cases, if liveness looks miscalibrated for a class of assets,\nnotify ChainPatrol engineering — the checker likely needs a tuning pass for\nthat platform.\n\n## Organization Trend Search Guide\n\nTrend search asks \"what's changed recently for this org?\" — the answer can\nsurface coordinated attacks, new attack channels, or campaigns targeting\nspecific people, **before** they show up as a healthcheck failure. Unlike\nthe HealthCheck Guide above (which grades single signals against\nthresholds), trend search compares a recent window to a baseline window\nand flags ratios, not absolute counts. A trend is interesting even when\nthe pipeline is keeping up with it — the user usually wants to know.\n\nWhen the user asks something like:\n\n- \"are there any trends in org X?\"\n- \"search for trends in <org>\"\n- \"anything unusual happening with <org> lately?\"\n- \"any spikes for <org>?\"\n- \"what's new for <org> this week?\"\n- \"is anyone targeting <org>'s employees more than usual?\"\n- \"are we seeing more YouTube/Telegram/etc. threats for <org>?\"\n\n…run the three trend checks below in parallel (single message, multiple\nBash calls) and surface anything where the current rate is ≥ 2× the\nbaseline AND clears a small absolute floor.\n\n### How to compute \"current vs. baseline\" rates\n\nPick two non-overlapping windows: a **current** window the user cares about\n(default last 7 days) and a **baseline** window ending where the current\none starts (default the prior ~90 days, i.e. \\`current_start - 90d\\` →\n\\`current_start - 1d\\`). For any breakdown bucket, compute:\n\n\\`\\`\\`\ncurrent_per_day = current_count / current_window_days\nbaseline_per_day = baseline_count / baseline_window_days\nratio = current_per_day / max(baseline_per_day, epsilon)\n\\`\\`\\`\n\nFlag a bucket if \\`ratio >= 2\\` AND \\`current_count >= 5\\`. The floor\nsuppresses noise on orgs with near-zero baseline activity (a single new\nYouTube report jumping the rate from 0 to 1/day shouldn't trigger). If\nthe user gives a different window (\"last 3 days\", \"this month\"), adjust\nboth windows proportionally and keep the same ratio threshold.\n\n### Trend 1 — Spike in a specific asset type\n\nA sudden jump in threats on a platform the org doesn't usually see\ntraffic on (\"normally you don't get targeted on YouTube much, but now\nthere's a spike there\") is worth flagging — it usually means an attacker\nhas discovered a new channel that works for them. Even when the absolute\nnumber is small, the *ratio* against the org's normal mix is what\nmatters.\n\nUse \\`metrics breakdown --by type\\` over both windows:\n\n\\`\\`\\`bash\n# Current window — last 7 days, broken out by asset type\nchainpatrol --json metrics breakdown --org <slug> --by type \\\\\n --from <YYYY-MM-DD 7d ago> --to <YYYY-MM-DD today>\n\n# Baseline window — prior ~90 days ending where the current window starts\nchainpatrol --json metrics breakdown --org <slug> --by type \\\\\n --from <YYYY-MM-DD 97d ago> --to <YYYY-MM-DD 8d ago>\n\\`\\`\\`\n\nEach entry in \\`.points\\` has \\`{ type, count }\\`. Join the two windows on\n\\`type\\`, apply the ratio rule above, and report each flagged type.\n\nCross-reference any flagged type with \\`configs list --org <slug>\\` — if\nthe spike is on Twitter / X but \\`twitter_post_search\\` is disabled, the\ndetection source for that channel isn't even running for this org and\nthe spike is being caught by something else (likely customer reports);\nsuggest turning it on.\n\n### Trend 2 — Spike in overall threat volume\n\nA sharp rise in total threats across the org — regardless of type or\nbrand — usually means a coordinated attack or campaign is underway. This\nisn't a healthcheck (reviewing and takedowns may be keeping up just\nfine), but the security team still wants to know so they can warn\ncustomers / employees / partners.\n\n\\`\\`\\`bash\n# Daily volume for the last ~6 weeks — enough to eyeball a baseline AND\n# see the spike on the right edge of the series.\nchainpatrol --json metrics breakdown --org <slug> --by day \\\\\n --from <YYYY-MM-DD 42d ago> --to <YYYY-MM-DD today>\n\\`\\`\\`\n\nRead \\`.points\\` (one entry per day). Take the trailing 7-day average and\ncompare against the prior ~5 weeks (the same 2× / floor rule). For round\ntotals to quote to the user, also pull:\n\n\\`\\`\\`bash\n# Two calls — current and baseline windows — using --include to skip\n# the per-type / per-day series and keep the call cheap.\nchainpatrol --json metrics organization --org <slug> \\\\\n --include reports,newThreats,threatsWatchlisted \\\\\n --from <current_from> --to <current_to>\n\nchainpatrol --json metrics organization --org <slug> \\\\\n --include reports,newThreats,threatsWatchlisted \\\\\n --from <baseline_from> --to <baseline_to>\n\\`\\`\\`\n\nWhen volume spikes broadly, also skim \\`reports list --reported-by-customer\\`\nfor the same window — a wave of customer reports often arrives a few hours\nahead of automated detection on a real coordinated push.\n\n### Trend 3 — Spike on a specific sub-brand (especially employee brands)\n\nSub-brands the org has set up for individual people — employees,\nexecutives, public figures — are high-signal targets. A sudden spike on\none usually means an attacker is impersonating that person specifically,\nwhich is materially different from a generic phishing wave and usually\nwarrants a direct heads-up to the person involved. In the database these\nare \\`Brand\\` rows with \\`type: INDIVIDUAL\\` (vs. \\`ORGANIZATION\\` for\nproduct / parent brands).\n\n**Step 1: Pull the brand roster** so you can label each spike as\n\"employee\" or \"product\" without asking the user.\n\n\\`\\`\\`bash\nchainpatrol --json brands list\n\\`\\`\\`\n\nBuild a map of \\`slug → type\\` from the response so the next step's\nfindings can be tagged \\`(employee)\\` or \\`(product)\\` automatically.\n\n**Step 2: Run the breakdown over both windows.**\n\n\\`\\`\\`bash\n# Current window — last 7 days, broken out by sub-brand\nchainpatrol --json metrics breakdown --org <slug> --by brand \\\\\n --from <YYYY-MM-DD 7d ago> --to <YYYY-MM-DD today>\n\n# Baseline window — prior ~90 days\nchainpatrol --json metrics breakdown --org <slug> --by brand \\\\\n --from <YYYY-MM-DD 97d ago> --to <YYYY-MM-DD 8d ago>\n\\`\\`\\`\n\nEach entry in \\`.points\\` has \\`{ brandId, brandSlug, brandName, count }\\`.\nApply the ratio rule, join against the \\`slug → type\\` map from Step 1,\nand report each spiking sub-brand by name plus its type.\n\n**Step 3: Drill into the flagged brand** with \\`metrics organization\n--brand-slug <slug>\\` for a per-day / per-type breakdown scoped to that\nbrand only. The server applies the brand filter to the scalar metrics\n(\\`newThreats\\`, \\`takedownsFiled\\`, etc.) AND to \\`blockedByType\\` /\n\\`blockedByDay\\`, so the time series and per-asset-type counts are real\nbrand-only data:\n\n\\`\\`\\`bash\nchainpatrol --json metrics organization --org <slug> \\\\\n --brand-slug <brand-slug> \\\\\n --include newThreats,blockedByType,blockedByDay \\\\\n --from <YYYY-MM-DD 7d ago> --to <YYYY-MM-DD today>\n\\`\\`\\`\n\nIf multiple \\`INDIVIDUAL\\` brands are spiking at the same time, treat that\nas a stronger signal than a single one — likely a campaign targeting the\ncompany's people rather than a single impersonation. Prioritize those\nfindings over single-brand spikes when summarizing.\n\n### Reporting trend findings\n\nReport each spiking bucket as its own finding with: the trend it falls\nunder (asset type / overall volume / sub-brand), the current vs. baseline\nrate (e.g. \"youtube: 12/day last 7d vs. 1.2/day prior 90d, ×10\nbaseline\"), and a one-line follow-up:\n\n- Asset-type spike → check \\`configs list\\` for the matching detection\n source and turn it on if it's off.\n- Overall-volume spike → flag a possible coordinated campaign; suggest\n the security team look at the recent \\`reports list\\` for shared\n infrastructure (sender, registrar, hosting).\n- Sub-brand spike → look up \\`type\\` via \\`brands list\\`; if\n \\`INDIVIDUAL\\`, suggest a direct heads-up to that person; if\n \\`ORGANIZATION\\`, suggest a product-team alert.\n\nIf none of the three trends fire above the 2× / floor thresholds, say so\nexplicitly (\"no significant trends in the last 7 days vs. the prior 90\")\nrather than dumping every breakdown number — the absence is the\nfinding.\n`;\n}\n","export function parseSkillVersion(content: string): string | undefined {\n const fmMatch = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!fmMatch) return undefined;\n const versionMatch = fmMatch[1].match(/^version:\\s*(.+?)\\s*$/m);\n return versionMatch ? versionMatch[1].trim() : undefined;\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { buildSkillContent } from '../chunk-UTEZF4EZ.mjs';
1
+ export { buildSkillContent } from '../chunk-S5BCAUOT.mjs';
2
2
  import '../chunk-4U7ZT42S.mjs';
3
3
 
4
4
  // src/skill/parse.ts
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@chainpatrol/sdk",
3
3
  "description": "The official ChainPatrol SDK",
4
4
  "author": "Umar Ahmed <umar@chainpatrol.io>",
5
- "version": "0.11.0",
5
+ "version": "1.0.0",
6
6
  "license": "MIT",
7
7
  "homepage": "https://chainpatrol.com/docs/sdk",
8
8
  "keywords": [