@chainpatrol/cli 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/{breakdown-EBSACUST.js → breakdown-AX6QNTQH.js} +1 -1
- package/dist/{chunk-PIYOWGBZ.js → chunk-AGXMZFUU.js} +1 -1
- package/dist/{chunk-MXUZR2BV.js → chunk-LLWKCA3H.js} +3 -0
- package/dist/{chunk-BSK4YHFA.js → chunk-ZN3VMRWG.js} +239 -52
- package/dist/cli.js +81 -24
- package/dist/{configs-update-RPN32YTL.js → configs-update-VROBC2HI.js} +1 -1
- package/dist/{create-QP3M7EZM.js → create-XTCUNT2C.js} +1 -1
- package/dist/{drift-DZ6A7JL5.js → drift-VOKQJ36G.js} +1 -1
- package/dist/{found-AOPBSLRD.js → found-A5HRTJCJ.js} +1 -1
- package/dist/{healthcheck-KAONRGSS.js → healthcheck-AQUXVKAO.js} +1 -1
- package/dist/{list-GEMCFDD5.js → list-5ENZAOFL.js} +1 -1
- package/dist/list-CGRHTFAS.js +182 -0
- package/dist/{list-MWDFCHMJ.js → list-CVFXTKNX.js} +1 -1
- package/dist/{list-LN6NOZIJ.js → list-EYRN5JYC.js} +2 -2
- package/dist/{list-UW63DIKX.js → list-PLZ67PNY.js} +1 -1
- package/dist/{list-json-WTMYLZGY.js → list-json-LEKCCWQU.js} +1 -1
- package/dist/{run-64SBCL4R.js → run-MS5SA5YL.js} +1 -1
- package/dist/{run-43CC5AXR.js → run-OT2X46GT.js} +1 -1
- package/dist/{run-MH5RYPWA.js → run-YHDUUP66.js} +2 -2
- package/dist/{setup-skill-NQIZBJMR.js → setup-skill-J7PZYVCE.js} +1 -1
- package/dist/{snapshot-E3TPZOKT.js → snapshot-4QR4I67P.js} +1 -1
- package/dist/{summary-6NCA7PDP.js → summary-JOCABBCO.js} +1 -1
- package/dist/{validate-BJFEKI2N.js → validate-27RUCN7R.js} +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @chainpatrol/cli
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c170343: Add `chainpatrol setup --cloud` flag that also installs a Claude Code
|
|
8
|
+
SessionStart hook at `~/.claude/hooks/chainpatrol-login.sh` and registers
|
|
9
|
+
it in `~/.claude/settings.json`. When the user has no chainpatrol
|
|
10
|
+
credentials, the hook emits SessionStart `additionalContext` so Claude
|
|
11
|
+
surfaces the device-code login URL on the first user turn — useful for
|
|
12
|
+
cloud Claude Code environments that can't run the interactive login from
|
|
13
|
+
a container startup script. The hook is a silent no-op once the user is
|
|
14
|
+
authenticated. Local installs (without `--cloud`) leave Claude Code
|
|
15
|
+
hooks untouched. `chainpatrol uninstall` always removes the hook if
|
|
16
|
+
present, without disturbing unrelated hooks or settings.
|
|
17
|
+
|
|
18
|
+
## 0.6.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 575b671: Add `chainpatrol orgs list` — lists organizations accessible to the caller along with each org's subscription status and active/automated service flags (reporting, reviewing, protection, takedowns, detection, dark web monitoring). Filter flags compose with AND and are applied server-side:
|
|
23
|
+
|
|
24
|
+
- `--subscription-status <list>` — comma-separated `PROSPECT`, `TRIAL`, `ACTIVE`, `INTEGRATION` (`INACTIVE` is intentionally not reachable through this filter)
|
|
25
|
+
- `--service-active <list>` / `--service-inactive <list>` — services that must be active / inactive
|
|
26
|
+
- `--service-automated <list>` / `--service-manual <list>` — services whose automation must be on / off (`reporting`, `reviewing`, `protection`, `takedowns`)
|
|
27
|
+
- `--query <text>` — partial name match
|
|
28
|
+
|
|
29
|
+
Use cases: "which ACTIVE customers have takedowns enabled but automation off", "which prospects don't have detection turned on yet". The bundled CLI skill gets a new `orgs list` section under the `queues snapshot` heading.
|
|
30
|
+
|
|
3
31
|
## 0.5.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
|
@@ -4,13 +4,125 @@ import {
|
|
|
4
4
|
} from "./chunk-IUZB3DQW.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/setup-skill.ts
|
|
7
|
-
import { mkdirSync, writeFileSync, existsSync, readFileSync as
|
|
8
|
-
import { join } from "path";
|
|
7
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync3, rmSync } from "fs";
|
|
8
|
+
import { join as join2 } from "path";
|
|
9
|
+
import { homedir as homedir2 } from "os";
|
|
10
|
+
|
|
11
|
+
// src/commands/login-hook.ts
|
|
12
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, unlinkSync } from "fs";
|
|
9
13
|
import { homedir } from "os";
|
|
14
|
+
import { join, dirname } from "path";
|
|
15
|
+
var CLAUDE_DIR = join(homedir(), ".claude");
|
|
16
|
+
var HOOK_DIR = join(CLAUDE_DIR, "hooks");
|
|
17
|
+
var HOOK_FILE = join(HOOK_DIR, "chainpatrol-login.sh");
|
|
18
|
+
var SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
|
|
19
|
+
var HOOK_SCRIPT = `#!/bin/bash
|
|
20
|
+
# chainpatrol auto-login hook \u2014 installed by \`chainpatrol setup\`.
|
|
21
|
+
# On Claude Code session start, if the user has no chainpatrol credentials,
|
|
22
|
+
# emit additionalContext so Claude surfaces the device-code login URL on
|
|
23
|
+
# the first user turn. Safe to run repeatedly: no-ops once authenticated.
|
|
24
|
+
set -uo pipefail
|
|
25
|
+
|
|
26
|
+
CONFIG_DIR="\${CHAINPATROL_CONFIG_DIR:-$HOME/.chainpatrol}"
|
|
27
|
+
if [ -s "$CONFIG_DIR/credentials.json" ]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Only nudge when the CLI is reachable \u2014 otherwise we'd ask Claude to run
|
|
32
|
+
# a command that isn't installed.
|
|
33
|
+
if ! command -v chainpatrol >/dev/null 2>&1 \\
|
|
34
|
+
&& [ ! -x /usr/local/bin/chainpatrol ] \\
|
|
35
|
+
&& ! ls /opt/node*/bin/chainpatrol >/dev/null 2>&1; then
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
cat <<'JSON'
|
|
40
|
+
{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"chainpatrol auto-login: the user is not authenticated with ChainPatrol (no credentials.json found). If the user's task involves ChainPatrol or the chainpatrol CLI, start \`chainpatrol login\` in the background per the /chainpatrol skill's headless runbook (background + tail /tmp/cp-login.out) and surface the verification URL before doing anything else. If the user's task is unrelated, ignore this notice."}}
|
|
41
|
+
JSON
|
|
42
|
+
exit 0
|
|
43
|
+
`;
|
|
44
|
+
function installLoginHook() {
|
|
45
|
+
mkdirSync(HOOK_DIR, { recursive: true });
|
|
46
|
+
const hookWritten = !existsSync(HOOK_FILE) || readFileSync(HOOK_FILE, "utf-8") !== HOOK_SCRIPT;
|
|
47
|
+
if (hookWritten) {
|
|
48
|
+
writeFileSync(HOOK_FILE, HOOK_SCRIPT, { mode: 493 });
|
|
49
|
+
}
|
|
50
|
+
const settingsUpdated = registerHookInSettings();
|
|
51
|
+
return {
|
|
52
|
+
hookPath: HOOK_FILE,
|
|
53
|
+
hookWritten,
|
|
54
|
+
settingsPath: SETTINGS_FILE,
|
|
55
|
+
settingsUpdated
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function uninstallLoginHook() {
|
|
59
|
+
let hookRemoved = false;
|
|
60
|
+
if (existsSync(HOOK_FILE)) {
|
|
61
|
+
unlinkSync(HOOK_FILE);
|
|
62
|
+
hookRemoved = true;
|
|
63
|
+
}
|
|
64
|
+
const settingsUpdated = unregisterHookFromSettings();
|
|
65
|
+
return { hookRemoved, settingsUpdated };
|
|
66
|
+
}
|
|
67
|
+
function readSettings() {
|
|
68
|
+
if (!existsSync(SETTINGS_FILE)) return {};
|
|
69
|
+
const raw = readFileSync(SETTINGS_FILE, "utf-8");
|
|
70
|
+
if (raw.trim() === "") return {};
|
|
71
|
+
return JSON.parse(raw);
|
|
72
|
+
}
|
|
73
|
+
function writeSettings(settings) {
|
|
74
|
+
mkdirSync(dirname(SETTINGS_FILE), { recursive: true });
|
|
75
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n", {
|
|
76
|
+
mode: 420
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function hasHookEntry(settings) {
|
|
80
|
+
const matchers = settings.hooks?.SessionStart;
|
|
81
|
+
if (!Array.isArray(matchers)) return false;
|
|
82
|
+
return matchers.some(
|
|
83
|
+
(matcher) => Array.isArray(matcher?.hooks) && matcher.hooks.some((h) => h?.command === HOOK_FILE)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
function registerHookInSettings() {
|
|
87
|
+
const settings = readSettings();
|
|
88
|
+
if (hasHookEntry(settings)) return false;
|
|
89
|
+
const hooks = settings.hooks ??= {};
|
|
90
|
+
const sessionStart = hooks.SessionStart ??= [];
|
|
91
|
+
sessionStart.push({
|
|
92
|
+
hooks: [{ type: "command", command: HOOK_FILE }]
|
|
93
|
+
});
|
|
94
|
+
writeSettings(settings);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
function unregisterHookFromSettings() {
|
|
98
|
+
if (!existsSync(SETTINGS_FILE)) return false;
|
|
99
|
+
const settings = readSettings();
|
|
100
|
+
const sessionStart = settings.hooks?.SessionStart;
|
|
101
|
+
if (!Array.isArray(sessionStart)) return false;
|
|
102
|
+
let changed = false;
|
|
103
|
+
const filtered = sessionStart.map((matcher) => {
|
|
104
|
+
if (!Array.isArray(matcher?.hooks)) return matcher;
|
|
105
|
+
const remaining = matcher.hooks.filter((h) => h?.command !== HOOK_FILE);
|
|
106
|
+
if (remaining.length === matcher.hooks.length) return matcher;
|
|
107
|
+
changed = true;
|
|
108
|
+
return remaining.length > 0 ? { ...matcher, hooks: remaining } : null;
|
|
109
|
+
}).filter((m) => m !== null);
|
|
110
|
+
if (!changed) return false;
|
|
111
|
+
if (filtered.length === 0) {
|
|
112
|
+
delete settings.hooks.SessionStart;
|
|
113
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
114
|
+
delete settings.hooks;
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
settings.hooks.SessionStart = filtered;
|
|
118
|
+
}
|
|
119
|
+
writeSettings(settings);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
10
122
|
|
|
11
123
|
// src/lib/version.ts
|
|
12
|
-
import { readFileSync } from "fs";
|
|
13
|
-
import { dirname, resolve } from "path";
|
|
124
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
125
|
+
import { dirname as dirname2, resolve } from "path";
|
|
14
126
|
import { fileURLToPath } from "url";
|
|
15
127
|
var PACKAGE_NAME = "@chainpatrol/cli";
|
|
16
128
|
var cached;
|
|
@@ -21,7 +133,7 @@ function getCliVersion() {
|
|
|
21
133
|
}
|
|
22
134
|
function resolveCliVersion() {
|
|
23
135
|
try {
|
|
24
|
-
const here =
|
|
136
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
25
137
|
const candidates = [
|
|
26
138
|
resolve(here, "..", "package.json"),
|
|
27
139
|
resolve(here, "..", "..", "package.json")
|
|
@@ -36,7 +148,7 @@ function resolveCliVersion() {
|
|
|
36
148
|
}
|
|
37
149
|
function tryReadVersion(path) {
|
|
38
150
|
try {
|
|
39
|
-
const raw =
|
|
151
|
+
const raw = readFileSync2(path, "utf-8");
|
|
40
152
|
const pkg = JSON.parse(raw);
|
|
41
153
|
if (pkg.name === PACKAGE_NAME && typeof pkg.version === "string") {
|
|
42
154
|
return pkg.version;
|
|
@@ -66,8 +178,8 @@ function compareVersions(a, b) {
|
|
|
66
178
|
}
|
|
67
179
|
|
|
68
180
|
// src/commands/setup-skill.ts
|
|
69
|
-
var SKILL_DIR =
|
|
70
|
-
var SKILL_FILE =
|
|
181
|
+
var SKILL_DIR = join2(homedir2(), ".claude", "skills", "chainpatrol");
|
|
182
|
+
var SKILL_FILE = join2(SKILL_DIR, "SKILL.md");
|
|
71
183
|
function buildSkillContent(version) {
|
|
72
184
|
return `---
|
|
73
185
|
name: chainpatrol
|
|
@@ -82,7 +194,9 @@ description: |
|
|
|
82
194
|
"am I logged in", "list configs", "use the cli", "list reports",
|
|
83
195
|
"customer reports", "reports reported by customer", "find detection gaps",
|
|
84
196
|
"org healthcheck", "organization health check", "audit my org",
|
|
85
|
-
"what's wrong with org", "review org setup"
|
|
197
|
+
"what's wrong with org", "review org setup", "list orgs", "list organizations",
|
|
198
|
+
"orgs with takedowns off", "automation off across orgs",
|
|
199
|
+
"which customers have X enabled", "service toggles by org".
|
|
86
200
|
allowed-tools:
|
|
87
201
|
- Bash
|
|
88
202
|
- Read
|
|
@@ -443,6 +557,51 @@ Guide. Key signals in the response:
|
|
|
443
557
|
|
|
444
558
|
Use \`--all\` to snapshot every org you have access to instead of a single slug.
|
|
445
559
|
|
|
560
|
+
### \`orgs list\` \u2014 List organizations with subscription status and service toggles
|
|
561
|
+
|
|
562
|
+
Returns every organization the caller can see, with each org's
|
|
563
|
+
subscription status (\`PROSPECT\`, \`TRIAL\`, \`ACTIVE\`, \`INTEGRATION\`) and
|
|
564
|
+
which services are active and automated. Use it to answer questions like
|
|
565
|
+
"which customers have takedowns enabled but automation off?" or "which
|
|
566
|
+
prospects don't have detection turned on yet?" \u2014 filters compose with AND
|
|
567
|
+
and are applied server-side, so one call returns the final list.
|
|
568
|
+
|
|
569
|
+
\`\`\`bash
|
|
570
|
+
chainpatrol --json orgs list \\
|
|
571
|
+
--subscription-status ACTIVE \\
|
|
572
|
+
--service-active takedowns \\
|
|
573
|
+
--service-manual takedowns
|
|
574
|
+
\`\`\`
|
|
575
|
+
|
|
576
|
+
Filter flags (all optional, all comma-separated lists):
|
|
577
|
+
|
|
578
|
+
- \`--query <text>\` partial name match (substring, case-insensitive)
|
|
579
|
+
- \`--subscription-status <list>\` one or more of \`PROSPECT\`, \`TRIAL\`,
|
|
580
|
+
\`ACTIVE\`, \`INTEGRATION\`. \`INACTIVE\` is intentionally not reachable
|
|
581
|
+
through this filter \u2014 \`orgs list\` only ever returns live customers.
|
|
582
|
+
- \`--service-active <list>\` services that must be active
|
|
583
|
+
- \`--service-inactive <list>\` services that must be inactive
|
|
584
|
+
- \`--service-automated <list>\` services whose automation must be ON
|
|
585
|
+
(\`reporting\`, \`reviewing\`, \`protection\`, \`takedowns\` \u2014 the four
|
|
586
|
+
with an automation toggle)
|
|
587
|
+
- \`--service-manual <list>\` services whose automation must be OFF
|
|
588
|
+
(same four)
|
|
589
|
+
|
|
590
|
+
Service names: \`reporting\`, \`reviewing\`, \`protection\`, \`takedowns\`,
|
|
591
|
+
\`detection\`, \`darkWebMonitoring\`.
|
|
592
|
+
|
|
593
|
+
Customers see only orgs they're a member of. Staff/superuser sessions see
|
|
594
|
+
every matching org. The response is the same in both cases; visibility is
|
|
595
|
+
enforced server-side.
|
|
596
|
+
|
|
597
|
+
#### Use case: finding service configuration gaps across the customer base
|
|
598
|
+
|
|
599
|
+
When the user asks something like "which customers are paying us but don't
|
|
600
|
+
have takedowns automated yet?" or "any orgs running detection without
|
|
601
|
+
takedowns?", reach for \`orgs list\` \u2014 it's the only command that exposes
|
|
602
|
+
service flags across multiple orgs in one call. Run it in \`--json\` mode
|
|
603
|
+
and summarize patterns by service or by subscription tier.
|
|
604
|
+
|
|
446
605
|
### \`metrics summary | found | breakdown\` \u2014 Org metrics for spike/drop analysis
|
|
447
606
|
|
|
448
607
|
\`\`\`bash
|
|
@@ -924,16 +1083,16 @@ function getBundledSkillContent() {
|
|
|
924
1083
|
return buildSkillContent(getCliVersion());
|
|
925
1084
|
}
|
|
926
1085
|
function readInstalledSkillVersion() {
|
|
927
|
-
if (!
|
|
1086
|
+
if (!existsSync2(SKILL_FILE)) return void 0;
|
|
928
1087
|
try {
|
|
929
|
-
const raw =
|
|
1088
|
+
const raw = readFileSync3(SKILL_FILE, "utf-8");
|
|
930
1089
|
return parseSkillVersion(raw);
|
|
931
1090
|
} catch {
|
|
932
1091
|
return void 0;
|
|
933
1092
|
}
|
|
934
1093
|
}
|
|
935
1094
|
function isSkillInstalled() {
|
|
936
|
-
return
|
|
1095
|
+
return existsSync2(SKILL_FILE);
|
|
937
1096
|
}
|
|
938
1097
|
function parseSkillVersion(content) {
|
|
939
1098
|
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
@@ -954,66 +1113,94 @@ var LOGO = `
|
|
|
954
1113
|
`;
|
|
955
1114
|
function setupSkill(options) {
|
|
956
1115
|
const skillContent = buildSkillContent(getCliVersion());
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1116
|
+
const skillAlreadyExists = existsSync2(SKILL_FILE);
|
|
1117
|
+
const skillUpToDate = skillAlreadyExists && readFileSync3(SKILL_FILE, "utf-8") === skillContent;
|
|
1118
|
+
let skillStatus;
|
|
1119
|
+
if (!skillAlreadyExists) {
|
|
1120
|
+
mkdirSync2(SKILL_DIR, { recursive: true });
|
|
1121
|
+
writeFileSync2(SKILL_FILE, skillContent, { mode: 420 });
|
|
1122
|
+
skillStatus = "installed";
|
|
1123
|
+
} else if (!skillUpToDate) {
|
|
1124
|
+
writeFileSync2(SKILL_FILE, skillContent, { mode: 420 });
|
|
1125
|
+
skillStatus = "updated";
|
|
1126
|
+
} else {
|
|
1127
|
+
skillStatus = "up-to-date";
|
|
968
1128
|
}
|
|
969
|
-
mkdirSync(SKILL_DIR, { recursive: true });
|
|
970
|
-
writeFileSync(SKILL_FILE, skillContent, { mode: 420 });
|
|
971
1129
|
const completionResult = installCompletions();
|
|
1130
|
+
const loginHookResult = options.cloud ? installLoginHook() : null;
|
|
1131
|
+
const completionsChanged = completionResult.installed || completionResult.configuredShellRc;
|
|
1132
|
+
const loginHookChanged = loginHookResult != null && (loginHookResult.hookWritten || loginHookResult.settingsUpdated);
|
|
1133
|
+
const overallStatus = skillStatus !== "up-to-date" ? skillStatus : completionsChanged || loginHookChanged ? "updated" : "up-to-date";
|
|
972
1134
|
if (options.json) {
|
|
973
1135
|
console.log(
|
|
974
1136
|
JSON.stringify({
|
|
975
|
-
status:
|
|
1137
|
+
status: overallStatus,
|
|
976
1138
|
path: SKILL_FILE,
|
|
977
|
-
|
|
1139
|
+
skill: { status: skillStatus, path: SKILL_FILE },
|
|
1140
|
+
completions: completionResult,
|
|
1141
|
+
loginHook: loginHookResult
|
|
978
1142
|
})
|
|
979
1143
|
);
|
|
980
|
-
|
|
981
|
-
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (overallStatus === "up-to-date") {
|
|
1147
|
+
console.log("Claude Code skill, completions, and login hook are already up to date.");
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
console.log(LOGO);
|
|
1151
|
+
if (skillStatus === "installed") {
|
|
1152
|
+
console.log(`Installed Claude Code skill at ${SKILL_FILE}`);
|
|
1153
|
+
} else if (skillStatus === "updated") {
|
|
1154
|
+
console.log(`Updated Claude Code skill at ${SKILL_FILE}`);
|
|
1155
|
+
}
|
|
1156
|
+
console.log("You can now use /chainpatrol in Claude Code from any project.");
|
|
1157
|
+
if (loginHookChanged && loginHookResult) {
|
|
1158
|
+
console.log(`Installed auto-login hook at ${loginHookResult.hookPath}`);
|
|
1159
|
+
if (loginHookResult.settingsUpdated) {
|
|
1160
|
+
console.log(`Registered SessionStart hook in ${loginHookResult.settingsPath}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (completionResult.installed) {
|
|
982
1164
|
console.log(
|
|
983
|
-
|
|
1165
|
+
`Installed ${completionResult.shell} completions at ${completionResult.path}`
|
|
984
1166
|
);
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
console.log(
|
|
988
|
-
`Installed ${completionResult.shell} completions at ${completionResult.path}`
|
|
989
|
-
);
|
|
990
|
-
if (completionResult.configuredShellRc) {
|
|
991
|
-
console.log("Added completion config to ~/.zshrc");
|
|
992
|
-
}
|
|
993
|
-
console.log('Run "exec zsh" or open a new terminal to enable tab completion.');
|
|
1167
|
+
if (completionResult.configuredShellRc) {
|
|
1168
|
+
console.log("Added completion config to ~/.zshrc");
|
|
994
1169
|
}
|
|
1170
|
+
console.log('Run "exec zsh" or open a new terminal to enable tab completion.');
|
|
995
1171
|
}
|
|
996
1172
|
}
|
|
997
1173
|
function uninstallSkill(options) {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
} else {
|
|
1002
|
-
console.log("Claude Code skill is not installed.");
|
|
1003
|
-
}
|
|
1004
|
-
return;
|
|
1174
|
+
const skillInstalled = existsSync2(SKILL_FILE);
|
|
1175
|
+
if (skillInstalled) {
|
|
1176
|
+
rmSync(SKILL_DIR, { recursive: true });
|
|
1005
1177
|
}
|
|
1006
|
-
rmSync(SKILL_DIR, { recursive: true });
|
|
1007
1178
|
const removedCompletions = uninstallCompletions();
|
|
1179
|
+
const loginHookResult = uninstallLoginHook();
|
|
1180
|
+
const removedAnything = skillInstalled || removedCompletions || loginHookResult.hookRemoved || loginHookResult.settingsUpdated;
|
|
1008
1181
|
if (options.json) {
|
|
1009
1182
|
console.log(
|
|
1010
|
-
JSON.stringify({
|
|
1183
|
+
JSON.stringify({
|
|
1184
|
+
status: removedAnything ? "uninstalled" : "not-installed",
|
|
1185
|
+
path: SKILL_FILE,
|
|
1186
|
+
removedCompletions,
|
|
1187
|
+
loginHook: loginHookResult
|
|
1188
|
+
})
|
|
1011
1189
|
);
|
|
1012
|
-
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (!removedAnything) {
|
|
1193
|
+
console.log("Claude Code skill is not installed.");
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (skillInstalled) {
|
|
1013
1197
|
console.log("Removed Claude Code skill from " + SKILL_DIR);
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1198
|
+
}
|
|
1199
|
+
if (loginHookResult.hookRemoved || loginHookResult.settingsUpdated) {
|
|
1200
|
+
console.log("Removed chainpatrol auto-login hook.");
|
|
1201
|
+
}
|
|
1202
|
+
if (removedCompletions) {
|
|
1203
|
+
console.log("Removed shell completions.");
|
|
1017
1204
|
}
|
|
1018
1205
|
}
|
|
1019
1206
|
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getCliVersion,
|
|
14
14
|
isSkillInstalled,
|
|
15
15
|
readInstalledSkillVersion
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ZN3VMRWG.js";
|
|
17
17
|
import "./chunk-IUZB3DQW.js";
|
|
18
18
|
import {
|
|
19
19
|
DateTime
|
|
@@ -281,6 +281,31 @@ var HELP = {
|
|
|
281
281
|
"--window-hours <n> Hours used for staleness windows"
|
|
282
282
|
]
|
|
283
283
|
},
|
|
284
|
+
orgs: {
|
|
285
|
+
description: "List organizations accessible to you with subscription status and service toggles.",
|
|
286
|
+
usage: "chainpatrol orgs list [filters]",
|
|
287
|
+
examples: [
|
|
288
|
+
"chainpatrol orgs list",
|
|
289
|
+
"chainpatrol orgs list --subscription-status ACTIVE --service-active takedowns --service-manual takedowns"
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
"orgs list": {
|
|
293
|
+
description: "List organizations with their subscription status and per-service active/automated flags. All filters compose with AND.",
|
|
294
|
+
usage: "chainpatrol orgs list [filters]",
|
|
295
|
+
options: [
|
|
296
|
+
"--query <text> Partial name match (substring, case-insensitive)",
|
|
297
|
+
"--subscription-status <l> Comma-separated list (PROSPECT,TRIAL,ACTIVE,INTEGRATION)",
|
|
298
|
+
"--service-active <l> Comma-separated services that must be active",
|
|
299
|
+
"--service-inactive <l> Comma-separated services that must be inactive",
|
|
300
|
+
"--service-automated <l> Services whose automation must be ON (reporting,reviewing,protection,takedowns)",
|
|
301
|
+
"--service-manual <l> Services whose automation must be OFF (reporting,reviewing,protection,takedowns)"
|
|
302
|
+
],
|
|
303
|
+
examples: [
|
|
304
|
+
"chainpatrol orgs list --subscription-status ACTIVE",
|
|
305
|
+
"chainpatrol orgs list --service-active takedowns --service-manual takedowns",
|
|
306
|
+
"chainpatrol --json orgs list --subscription-status ACTIVE,TRIAL --service-active detection"
|
|
307
|
+
]
|
|
308
|
+
},
|
|
284
309
|
healthchecks: {
|
|
285
310
|
description: "Run organization healthchecks via the public API. Use `list` to see every available check (including planned ones not yet implemented on the backend) and `run` to execute one or all implemented checks.",
|
|
286
311
|
usage: "chainpatrol healthchecks <list|run <id>|run --all>",
|
|
@@ -329,7 +354,10 @@ var HELP = {
|
|
|
329
354
|
},
|
|
330
355
|
setup: {
|
|
331
356
|
description: "Install Claude Code skill and shell completions.",
|
|
332
|
-
usage: "chainpatrol setup"
|
|
357
|
+
usage: "chainpatrol setup [--cloud]",
|
|
358
|
+
options: [
|
|
359
|
+
"--cloud Also install a SessionStart hook that surfaces the device-code login URL when the user is not authenticated. Intended for Claude Code cloud / headless environments; omit for local installs."
|
|
360
|
+
]
|
|
333
361
|
},
|
|
334
362
|
uninstall: {
|
|
335
363
|
description: "Remove Claude Code skill and shell completions.",
|
|
@@ -369,6 +397,7 @@ function getTopLevelHelp() {
|
|
|
369
397
|
" metrics Query organization metrics and breakdowns",
|
|
370
398
|
" reports Create and list reports from terminal",
|
|
371
399
|
" queues Snapshot operations review/takedown queues",
|
|
400
|
+
" orgs List organizations and filter by status/services",
|
|
372
401
|
" presets Run saved workflows for common jobs",
|
|
373
402
|
" setup Install Claude Code skill and shell completions",
|
|
374
403
|
" uninstall Remove Claude Code skill and shell completions",
|
|
@@ -538,6 +567,7 @@ var COMMANDS = [
|
|
|
538
567
|
"metrics",
|
|
539
568
|
"reports",
|
|
540
569
|
"queues",
|
|
570
|
+
"orgs",
|
|
541
571
|
"presets",
|
|
542
572
|
"setup",
|
|
543
573
|
"uninstall",
|
|
@@ -614,11 +644,18 @@ ${getTopLevelHelp()}
|
|
|
614
644
|
contactInfo: { type: "string" },
|
|
615
645
|
externalSubmissionLink: { type: "string" },
|
|
616
646
|
payloadFile: { type: "string" },
|
|
647
|
+
subscriptionStatus: { type: "string" },
|
|
648
|
+
serviceActive: { type: "string" },
|
|
649
|
+
serviceInactive: { type: "string" },
|
|
650
|
+
serviceAutomated: { type: "string" },
|
|
651
|
+
serviceManual: { type: "string" },
|
|
652
|
+
query: { type: "string" },
|
|
617
653
|
help: { type: "boolean", shortFlag: "h" },
|
|
618
654
|
version: { type: "boolean", shortFlag: "V" },
|
|
619
655
|
quiet: { type: "boolean", default: false, shortFlag: "q" },
|
|
620
656
|
noInput: { type: "boolean", default: false },
|
|
621
|
-
noColor: { type: "boolean", default: false }
|
|
657
|
+
noColor: { type: "boolean", default: false },
|
|
658
|
+
cloud: { type: "boolean", default: false }
|
|
622
659
|
}
|
|
623
660
|
});
|
|
624
661
|
var [command, subcommand, action] = cli.input;
|
|
@@ -717,12 +754,12 @@ function parseAttachmentUrls() {
|
|
|
717
754
|
}
|
|
718
755
|
async function handleConfigsList(org) {
|
|
719
756
|
if (jsonMode) {
|
|
720
|
-
const { listConfigsJson } = await import("./list-json-
|
|
757
|
+
const { listConfigsJson } = await import("./list-json-LEKCCWQU.js");
|
|
721
758
|
await listConfigsJson({ org });
|
|
722
759
|
return;
|
|
723
760
|
}
|
|
724
761
|
const { render } = await import("ink");
|
|
725
|
-
const { default: ConfigsList } = await import("./list-
|
|
762
|
+
const { default: ConfigsList } = await import("./list-5ENZAOFL.js");
|
|
726
763
|
const { default: React } = await import("react");
|
|
727
764
|
render(React.createElement(ConfigsList, { org }));
|
|
728
765
|
}
|
|
@@ -820,7 +857,7 @@ async function main() {
|
|
|
820
857
|
case "detections": {
|
|
821
858
|
const org = await resolveOrg();
|
|
822
859
|
if (subcommand === "healthcheck") {
|
|
823
|
-
const { runDetectionsHealthcheck } = await import("./healthcheck-
|
|
860
|
+
const { runDetectionsHealthcheck } = await import("./healthcheck-AQUXVKAO.js");
|
|
824
861
|
await runDetectionsHealthcheck({
|
|
825
862
|
org,
|
|
826
863
|
source: cli.flags.source,
|
|
@@ -835,7 +872,7 @@ async function main() {
|
|
|
835
872
|
break;
|
|
836
873
|
}
|
|
837
874
|
if (subcommand === "validate") {
|
|
838
|
-
const { runDetectionsValidate } = await import("./validate-
|
|
875
|
+
const { runDetectionsValidate } = await import("./validate-27RUCN7R.js");
|
|
839
876
|
await runDetectionsValidate({
|
|
840
877
|
org,
|
|
841
878
|
source: cli.flags.source,
|
|
@@ -850,7 +887,7 @@ async function main() {
|
|
|
850
887
|
break;
|
|
851
888
|
}
|
|
852
889
|
if (subcommand === "drift") {
|
|
853
|
-
const { runDetectionsDrift } = await import("./drift-
|
|
890
|
+
const { runDetectionsDrift } = await import("./drift-VOKQJ36G.js");
|
|
854
891
|
await runDetectionsDrift({
|
|
855
892
|
org,
|
|
856
893
|
source: cli.flags.source,
|
|
@@ -864,7 +901,7 @@ async function main() {
|
|
|
864
901
|
break;
|
|
865
902
|
}
|
|
866
903
|
if (subcommand === "run") {
|
|
867
|
-
const { runDetectionsRun } = await import("./run-
|
|
904
|
+
const { runDetectionsRun } = await import("./run-OT2X46GT.js");
|
|
868
905
|
await runDetectionsRun({
|
|
869
906
|
org,
|
|
870
907
|
configId: cli.flags.configId,
|
|
@@ -883,7 +920,7 @@ async function main() {
|
|
|
883
920
|
break;
|
|
884
921
|
}
|
|
885
922
|
if (action === "run") {
|
|
886
|
-
const { runDetectionsRun } = await import("./run-
|
|
923
|
+
const { runDetectionsRun } = await import("./run-OT2X46GT.js");
|
|
887
924
|
await runDetectionsRun({
|
|
888
925
|
org,
|
|
889
926
|
configId: cli.flags.configId,
|
|
@@ -901,7 +938,7 @@ async function main() {
|
|
|
901
938
|
throw new Error("detections configs update requires --config-id");
|
|
902
939
|
}
|
|
903
940
|
const configPatch = getConfigPatchFromSetFlags();
|
|
904
|
-
const { runDetectionsConfigsUpdate } = await import("./configs-update-
|
|
941
|
+
const { runDetectionsConfigsUpdate } = await import("./configs-update-VROBC2HI.js");
|
|
905
942
|
await runDetectionsConfigsUpdate({
|
|
906
943
|
org,
|
|
907
944
|
configId: cli.flags.configId,
|
|
@@ -928,7 +965,7 @@ async function main() {
|
|
|
928
965
|
case "metrics": {
|
|
929
966
|
const org = await resolveOrg();
|
|
930
967
|
if (subcommand === "summary") {
|
|
931
|
-
const { runMetricsSummary } = await import("./summary-
|
|
968
|
+
const { runMetricsSummary } = await import("./summary-JOCABBCO.js");
|
|
932
969
|
await runMetricsSummary({
|
|
933
970
|
org,
|
|
934
971
|
from: cli.flags.from,
|
|
@@ -940,7 +977,7 @@ async function main() {
|
|
|
940
977
|
break;
|
|
941
978
|
}
|
|
942
979
|
if (subcommand === "found") {
|
|
943
|
-
const { runMetricsFound } = await import("./found-
|
|
980
|
+
const { runMetricsFound } = await import("./found-A5HRTJCJ.js");
|
|
944
981
|
await runMetricsFound({
|
|
945
982
|
org,
|
|
946
983
|
from: cli.flags.from,
|
|
@@ -957,7 +994,7 @@ async function main() {
|
|
|
957
994
|
if (!by || !["day", "type", "brand"].includes(by)) {
|
|
958
995
|
throw new Error("metrics breakdown requires --by <day|type|brand>");
|
|
959
996
|
}
|
|
960
|
-
const { runMetricsBreakdown } = await import("./breakdown-
|
|
997
|
+
const { runMetricsBreakdown } = await import("./breakdown-AX6QNTQH.js");
|
|
961
998
|
await runMetricsBreakdown({
|
|
962
999
|
org,
|
|
963
1000
|
by,
|
|
@@ -977,7 +1014,7 @@ async function main() {
|
|
|
977
1014
|
case "reports": {
|
|
978
1015
|
if (subcommand === "list") {
|
|
979
1016
|
const org = await resolveOrg();
|
|
980
|
-
const { runReportsList } = await import("./list-
|
|
1017
|
+
const { runReportsList } = await import("./list-CVFXTKNX.js");
|
|
981
1018
|
await runReportsList({
|
|
982
1019
|
org,
|
|
983
1020
|
limit: cli.flags.limit,
|
|
@@ -993,7 +1030,7 @@ async function main() {
|
|
|
993
1030
|
}
|
|
994
1031
|
if (subcommand === "create") {
|
|
995
1032
|
const org = await tryResolveOrg();
|
|
996
|
-
const { runReportsCreate } = await import("./create-
|
|
1033
|
+
const { runReportsCreate } = await import("./create-XTCUNT2C.js");
|
|
997
1034
|
await runReportsCreate({
|
|
998
1035
|
org,
|
|
999
1036
|
title: cli.flags.title,
|
|
@@ -1017,7 +1054,7 @@ async function main() {
|
|
|
1017
1054
|
}
|
|
1018
1055
|
case "queues": {
|
|
1019
1056
|
if (subcommand === "snapshot") {
|
|
1020
|
-
const { runQueuesSnapshot } = await import("./snapshot-
|
|
1057
|
+
const { runQueuesSnapshot } = await import("./snapshot-4QR4I67P.js");
|
|
1021
1058
|
await runQueuesSnapshot({
|
|
1022
1059
|
org: cli.flags.org,
|
|
1023
1060
|
all: cli.flags.all,
|
|
@@ -1033,9 +1070,29 @@ async function main() {
|
|
|
1033
1070
|
subcommand ? `Unknown subcommand: queues ${subcommand}${hint ? `. Did you mean "queues ${hint}"?` : ""}` : "Usage: chainpatrol queues snapshot [--org <slug>|--all]"
|
|
1034
1071
|
);
|
|
1035
1072
|
}
|
|
1073
|
+
case "orgs": {
|
|
1074
|
+
if (subcommand === "list") {
|
|
1075
|
+
const { runOrgsList } = await import("./list-CGRHTFAS.js");
|
|
1076
|
+
await runOrgsList({
|
|
1077
|
+
query: cli.flags.query,
|
|
1078
|
+
subscriptionStatus: cli.flags.subscriptionStatus,
|
|
1079
|
+
serviceActive: cli.flags.serviceActive,
|
|
1080
|
+
serviceInactive: cli.flags.serviceInactive,
|
|
1081
|
+
serviceAutomated: cli.flags.serviceAutomated,
|
|
1082
|
+
serviceManual: cli.flags.serviceManual,
|
|
1083
|
+
json: jsonMode,
|
|
1084
|
+
outputFormat: cliContext.outputFormat
|
|
1085
|
+
});
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
const hint = subcommand ? suggest(subcommand, ["list"]) : null;
|
|
1089
|
+
throw new Error(
|
|
1090
|
+
subcommand ? `Unknown subcommand: orgs ${subcommand}${hint ? `. Did you mean "orgs ${hint}"?` : ""}` : "Usage: chainpatrol orgs list [--subscription-status <list>] [--service-active <list>] [--service-automated <list>] ..."
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1036
1093
|
case "healthchecks": {
|
|
1037
1094
|
if (subcommand === "list") {
|
|
1038
|
-
const { runHealthchecksList } = await import("./list-
|
|
1095
|
+
const { runHealthchecksList } = await import("./list-PLZ67PNY.js");
|
|
1039
1096
|
await runHealthchecksList({
|
|
1040
1097
|
json: jsonMode,
|
|
1041
1098
|
outputFormat: cliContext.outputFormat
|
|
@@ -1049,7 +1106,7 @@ async function main() {
|
|
|
1049
1106
|
thresholds.minResults = cli.flags.minResults;
|
|
1050
1107
|
if (cli.flags.lookbackHours !== void 0)
|
|
1051
1108
|
thresholds.lookbackHours = cli.flags.lookbackHours;
|
|
1052
|
-
const { runHealthchecksRun } = await import("./run-
|
|
1109
|
+
const { runHealthchecksRun } = await import("./run-MS5SA5YL.js");
|
|
1053
1110
|
await runHealthchecksRun({
|
|
1054
1111
|
org,
|
|
1055
1112
|
id: action,
|
|
@@ -1067,7 +1124,7 @@ async function main() {
|
|
|
1067
1124
|
}
|
|
1068
1125
|
case "presets": {
|
|
1069
1126
|
if (subcommand === "list") {
|
|
1070
|
-
const { runPresetsList } = await import("./list-
|
|
1127
|
+
const { runPresetsList } = await import("./list-EYRN5JYC.js");
|
|
1071
1128
|
await runPresetsList({ outputFormat: cliContext.outputFormat });
|
|
1072
1129
|
break;
|
|
1073
1130
|
}
|
|
@@ -1078,7 +1135,7 @@ async function main() {
|
|
|
1078
1135
|
);
|
|
1079
1136
|
}
|
|
1080
1137
|
const org = await resolveOrg();
|
|
1081
|
-
const { runPresetsRun } = await import("./run-
|
|
1138
|
+
const { runPresetsRun } = await import("./run-YHDUUP66.js");
|
|
1082
1139
|
await runPresetsRun({
|
|
1083
1140
|
presetId: action,
|
|
1084
1141
|
org,
|
|
@@ -1095,12 +1152,12 @@ async function main() {
|
|
|
1095
1152
|
case "setup":
|
|
1096
1153
|
case "install":
|
|
1097
1154
|
case "i": {
|
|
1098
|
-
const { setupSkill } = await import("./setup-skill-
|
|
1099
|
-
setupSkill({ json: jsonMode });
|
|
1155
|
+
const { setupSkill } = await import("./setup-skill-J7PZYVCE.js");
|
|
1156
|
+
setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
|
|
1100
1157
|
break;
|
|
1101
1158
|
}
|
|
1102
1159
|
case "uninstall": {
|
|
1103
|
-
const { uninstallSkill } = await import("./setup-skill-
|
|
1160
|
+
const { uninstallSkill } = await import("./setup-skill-J7PZYVCE.js");
|
|
1104
1161
|
uninstallSkill({ json: jsonMode });
|
|
1105
1162
|
break;
|
|
1106
1163
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliExitError,
|
|
3
|
+
ExitCode
|
|
4
|
+
} from "./chunk-E2LAMILJ.js";
|
|
5
|
+
import {
|
|
6
|
+
printOutput,
|
|
7
|
+
toCsvRows
|
|
8
|
+
} from "./chunk-VFT3TD3E.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient
|
|
11
|
+
} from "./chunk-LLWKCA3H.js";
|
|
12
|
+
import "./chunk-EEG7T6WT.js";
|
|
13
|
+
import "./chunk-TFCNKBRC.js";
|
|
14
|
+
import "./chunk-U73SABXK.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/orgs/list.ts
|
|
17
|
+
var VALID_SUBSCRIPTION_STATUSES = /* @__PURE__ */ new Set([
|
|
18
|
+
"PROSPECT",
|
|
19
|
+
"TRIAL",
|
|
20
|
+
"ACTIVE",
|
|
21
|
+
"INTEGRATION"
|
|
22
|
+
]);
|
|
23
|
+
var DUAL_SERVICES = ["reporting", "reviewing", "protection", "takedowns"];
|
|
24
|
+
var SINGLE_SERVICES = ["detection", "darkWebMonitoring"];
|
|
25
|
+
var ALL_SERVICES = [...DUAL_SERVICES, ...SINGLE_SERVICES];
|
|
26
|
+
function splitCsv(value) {
|
|
27
|
+
if (!value) return [];
|
|
28
|
+
return value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
function parseSubscriptionStatuses(raw) {
|
|
31
|
+
const parts = splitCsv(raw);
|
|
32
|
+
if (parts.length === 0) return void 0;
|
|
33
|
+
const result = [];
|
|
34
|
+
for (const part of parts) {
|
|
35
|
+
const upper = part.toUpperCase();
|
|
36
|
+
if (!VALID_SUBSCRIPTION_STATUSES.has(upper)) {
|
|
37
|
+
throw new CliExitError(
|
|
38
|
+
`Unknown subscription status: '${part}'. Valid values: ${Array.from(VALID_SUBSCRIPTION_STATUSES).join(", ")}.`,
|
|
39
|
+
ExitCode.USAGE
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
result.push(upper);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
function parseServiceList(raw, flagName) {
|
|
47
|
+
const parts = splitCsv(raw);
|
|
48
|
+
const result = [];
|
|
49
|
+
for (const part of parts) {
|
|
50
|
+
if (!ALL_SERVICES.includes(part)) {
|
|
51
|
+
throw new CliExitError(
|
|
52
|
+
`Unknown service '${part}' in --${flagName}. Valid services: ${ALL_SERVICES.join(", ")}.`,
|
|
53
|
+
ExitCode.USAGE
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
result.push(part);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function requireDualService(service, flagName) {
|
|
61
|
+
if (!DUAL_SERVICES.includes(service)) {
|
|
62
|
+
throw new CliExitError(
|
|
63
|
+
`Service '${service}' does not have an automation toggle and can't be used with --${flagName}. Use --service-active / --service-inactive instead.`,
|
|
64
|
+
ExitCode.USAGE
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return service;
|
|
68
|
+
}
|
|
69
|
+
function buildServicesFilter(options) {
|
|
70
|
+
const filters = {};
|
|
71
|
+
const activeSetBy = /* @__PURE__ */ new Map();
|
|
72
|
+
const automatedSetBy = /* @__PURE__ */ new Map();
|
|
73
|
+
function setActive(service, active, flagName) {
|
|
74
|
+
const previous = activeSetBy.get(service);
|
|
75
|
+
if (previous && previous !== flagName) {
|
|
76
|
+
throw new CliExitError(
|
|
77
|
+
`Conflicting flags for service '${service}': --${previous} and --${flagName} can't both be passed.`,
|
|
78
|
+
ExitCode.USAGE
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
activeSetBy.set(service, flagName);
|
|
82
|
+
const existing = filters[service] ?? {};
|
|
83
|
+
filters[service] = { ...existing, active };
|
|
84
|
+
}
|
|
85
|
+
function setAutomated(service, automated, flagName) {
|
|
86
|
+
const previous = automatedSetBy.get(service);
|
|
87
|
+
if (previous && previous !== flagName) {
|
|
88
|
+
throw new CliExitError(
|
|
89
|
+
`Conflicting flags for service '${service}': --${previous} and --${flagName} can't both be passed.`,
|
|
90
|
+
ExitCode.USAGE
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
automatedSetBy.set(service, flagName);
|
|
94
|
+
const existing = filters[service] ?? {};
|
|
95
|
+
filters[service] = { ...existing, automated };
|
|
96
|
+
}
|
|
97
|
+
for (const service of parseServiceList(options.serviceActive, "service-active")) {
|
|
98
|
+
setActive(service, true, "service-active");
|
|
99
|
+
}
|
|
100
|
+
for (const service of parseServiceList(options.serviceInactive, "service-inactive")) {
|
|
101
|
+
setActive(service, false, "service-inactive");
|
|
102
|
+
}
|
|
103
|
+
for (const service of parseServiceList(options.serviceAutomated, "service-automated")) {
|
|
104
|
+
setAutomated(
|
|
105
|
+
requireDualService(service, "service-automated"),
|
|
106
|
+
true,
|
|
107
|
+
"service-automated"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
for (const service of parseServiceList(options.serviceManual, "service-manual")) {
|
|
111
|
+
setAutomated(requireDualService(service, "service-manual"), false, "service-manual");
|
|
112
|
+
}
|
|
113
|
+
return Object.keys(filters).length > 0 ? filters : void 0;
|
|
114
|
+
}
|
|
115
|
+
function summarizeServices(org) {
|
|
116
|
+
const parts = [];
|
|
117
|
+
for (const service of DUAL_SERVICES) {
|
|
118
|
+
const entry = org.services[service];
|
|
119
|
+
if (!entry.active) continue;
|
|
120
|
+
parts.push(`${service}=${entry.automated ? "auto" : "manual"}`);
|
|
121
|
+
}
|
|
122
|
+
for (const service of SINGLE_SERVICES) {
|
|
123
|
+
if (org.services[service].active) parts.push(service);
|
|
124
|
+
}
|
|
125
|
+
return parts.length > 0 ? parts.join(" ") : "(no services enabled)";
|
|
126
|
+
}
|
|
127
|
+
async function runOrgsList(options) {
|
|
128
|
+
const client = options.apiClient ?? createApiClient();
|
|
129
|
+
const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
|
|
130
|
+
const input = {
|
|
131
|
+
query: options.query ?? "",
|
|
132
|
+
subscriptionStatus: parseSubscriptionStatuses(options.subscriptionStatus),
|
|
133
|
+
services: buildServicesFilter(options)
|
|
134
|
+
};
|
|
135
|
+
const result = await client.getUserOrgs(input);
|
|
136
|
+
const organizations = result.organizations;
|
|
137
|
+
printOutput({
|
|
138
|
+
outputFormat,
|
|
139
|
+
json: result,
|
|
140
|
+
markdown: [
|
|
141
|
+
`# Organizations (${organizations.length})`,
|
|
142
|
+
"",
|
|
143
|
+
...organizations.map(
|
|
144
|
+
(org) => `- \`${org.slug}\` \u2014 ${org.name} \u2014 ${org.subscriptionStatus} \u2014 ${summarizeServices(org)}`
|
|
145
|
+
)
|
|
146
|
+
].join("\n"),
|
|
147
|
+
csv: toCsvRows(
|
|
148
|
+
organizations.map((org) => ({
|
|
149
|
+
slug: org.slug,
|
|
150
|
+
name: org.name,
|
|
151
|
+
subscriptionStatus: org.subscriptionStatus,
|
|
152
|
+
reportingActive: org.services.reporting.active,
|
|
153
|
+
reportingAutomated: org.services.reporting.automated,
|
|
154
|
+
reviewingActive: org.services.reviewing.active,
|
|
155
|
+
reviewingAutomated: org.services.reviewing.automated,
|
|
156
|
+
protectionActive: org.services.protection.active,
|
|
157
|
+
protectionAutomated: org.services.protection.automated,
|
|
158
|
+
takedownsActive: org.services.takedowns.active,
|
|
159
|
+
takedownsAutomated: org.services.takedowns.automated,
|
|
160
|
+
detectionActive: org.services.detection.active,
|
|
161
|
+
darkWebMonitoringActive: org.services.darkWebMonitoring.active
|
|
162
|
+
}))
|
|
163
|
+
),
|
|
164
|
+
human: () => {
|
|
165
|
+
if (organizations.length === 0) {
|
|
166
|
+
console.log("No organizations matched the filter.");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
console.log(`Found ${organizations.length} organization(s):`);
|
|
170
|
+
const slugWidth = Math.max(...organizations.map((org) => org.slug.length), 4);
|
|
171
|
+
const nameWidth = Math.max(...organizations.map((org) => org.name.length), 4);
|
|
172
|
+
for (const org of organizations) {
|
|
173
|
+
console.log(
|
|
174
|
+
` ${org.slug.padEnd(slugWidth)} ${org.name.padEnd(nameWidth)} ${org.subscriptionStatus.padEnd(11)} ${summarizeServices(org)}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
export {
|
|
181
|
+
runOrgsList
|
|
182
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRESETS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AGXMZFUU.js";
|
|
4
4
|
import "./chunk-E2LAMILJ.js";
|
|
5
5
|
import {
|
|
6
6
|
printOutput,
|
|
7
7
|
toCsvRows
|
|
8
8
|
} from "./chunk-VFT3TD3E.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-LLWKCA3H.js";
|
|
10
10
|
import "./chunk-EEG7T6WT.js";
|
|
11
11
|
import "./chunk-TFCNKBRC.js";
|
|
12
12
|
import "./chunk-U73SABXK.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPresetDefinition,
|
|
3
3
|
runPreset
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AGXMZFUU.js";
|
|
5
5
|
import {
|
|
6
6
|
CliExitError,
|
|
7
7
|
ExitCode
|
|
8
8
|
} from "./chunk-E2LAMILJ.js";
|
|
9
9
|
import "./chunk-VFT3TD3E.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-LLWKCA3H.js";
|
|
11
11
|
import "./chunk-EEG7T6WT.js";
|
|
12
12
|
import "./chunk-TFCNKBRC.js";
|
|
13
13
|
import "./chunk-U73SABXK.js";
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@chainpatrol/cli",
|
|
3
3
|
"description": "The official ChainPatrol CLI — terminal interface for threat detection",
|
|
4
4
|
"author": "Umar Ahmed <umar@chainpatrol.io>",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.7.0",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"homepage": "https://chainpatrol.com/docs/cli",
|
|
8
8
|
"keywords": [
|