@chainpatrol/cli 0.1.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/README.md +224 -0
- package/dist/breakdown-JVN66HY3.js +69 -0
- package/dist/chunk-D2QGXYXZ.js +126 -0
- package/dist/chunk-E2LAMILJ.js +48 -0
- package/dist/chunk-EEG7T6WT.js +287 -0
- package/dist/chunk-H7UKKLCV.js +6572 -0
- package/dist/chunk-IUZB3DQW.js +237 -0
- package/dist/chunk-JCMWDZYY.js +39 -0
- package/dist/chunk-U73SABXK.js +46 -0
- package/dist/chunk-VFT3TD3E.js +82 -0
- package/dist/cli.js +895 -0
- package/dist/completions-EGQIARFC.js +12 -0
- package/dist/config-Z3TASRME.js +10 -0
- package/dist/configs-update-BK2S6AZ6.js +101 -0
- package/dist/create-4SQUBQI7.js +128 -0
- package/dist/drift-VRZKQC4P.js +80 -0
- package/dist/found-4O3AISNI.js +93 -0
- package/dist/healthcheck-7DR5MGEQ.js +94 -0
- package/dist/list-6L7XR4SZ.js +94 -0
- package/dist/list-HZAHEHDM.js +40 -0
- package/dist/list-IBMM562A.js +139 -0
- package/dist/list-json-TPBLJBD3.js +24 -0
- package/dist/login-G7LPHKDR.js +162 -0
- package/dist/login-json-LKB72OFY.js +71 -0
- package/dist/logout-LA7VEKON.js +25 -0
- package/dist/logout-json-4GIJZJ46.js +18 -0
- package/dist/run-PABQKATZ.js +112 -0
- package/dist/run-U62KVNTH.js +34 -0
- package/dist/setup-skill-U24CJZ6T.js +211 -0
- package/dist/snapshot-JEVDTE74.js +88 -0
- package/dist/summary-YG5NYIOA.js +61 -0
- package/dist/validate-PI7GPT5I.js +79 -0
- package/package.json +50 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// src/commands/completions.ts
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import {
|
|
5
|
+
mkdirSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
appendFileSync
|
|
11
|
+
} from "fs";
|
|
12
|
+
var ZSH_COMPLETION = `#compdef chainpatrol
|
|
13
|
+
|
|
14
|
+
_chainpatrol() {
|
|
15
|
+
local -a commands
|
|
16
|
+
commands=(
|
|
17
|
+
'login:Authenticate with ChainPatrol'
|
|
18
|
+
'logout:Clear stored credentials'
|
|
19
|
+
'configs:Manage detection configs'
|
|
20
|
+
'detections:Validate and run detection configs'
|
|
21
|
+
'metrics:Query organization metrics'
|
|
22
|
+
'reports:Create and list reports from terminal'
|
|
23
|
+
'queues:Snapshot operations queues'
|
|
24
|
+
'presets:Run saved workflows'
|
|
25
|
+
'setup:Install Claude Code skill and shell completions'
|
|
26
|
+
'uninstall:Remove Claude Code skill and shell completions'
|
|
27
|
+
'completions:Output shell completion script'
|
|
28
|
+
'help:Show help for a command'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
local -a global_flags
|
|
32
|
+
global_flags=(
|
|
33
|
+
'--json[Output as machine-readable JSON]'
|
|
34
|
+
'--output[Output format]:format:(human json markdown csv)'
|
|
35
|
+
'--dry-run[Preview mutation payloads without applying]'
|
|
36
|
+
'--explain[Include reasoning metadata]'
|
|
37
|
+
'--all[Run across all organizations]'
|
|
38
|
+
'--window-hours[Queue staleness window in hours]:hours:'
|
|
39
|
+
'--org[Organization slug]:org slug:'
|
|
40
|
+
'--limit[Result page size for list commands]:limit:'
|
|
41
|
+
'--cursor[Pagination cursor for list commands]:cursor:'
|
|
42
|
+
'--status[Filter list by report status]:status:'
|
|
43
|
+
'--search[Search query for report list]:query:'
|
|
44
|
+
'--help[Show help]'
|
|
45
|
+
'-h[Show help]'
|
|
46
|
+
'--version[Show version]'
|
|
47
|
+
'-V[Show version]'
|
|
48
|
+
'--quiet[Suppress non-essential output]'
|
|
49
|
+
'-q[Suppress non-essential output]'
|
|
50
|
+
'--no-input[Disable interactive prompts]'
|
|
51
|
+
'--no-color[Disable colored output]'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (( CURRENT == 2 )); then
|
|
55
|
+
_describe 'command' commands
|
|
56
|
+
_arguments -s $global_flags
|
|
57
|
+
elif (( CURRENT == 3 )); then
|
|
58
|
+
case "\${words[2]}" in
|
|
59
|
+
configs)
|
|
60
|
+
local -a subcommands
|
|
61
|
+
subcommands=('list:List detection configurations')
|
|
62
|
+
_describe 'subcommand' subcommands
|
|
63
|
+
;;
|
|
64
|
+
detections)
|
|
65
|
+
local -a detections_subcommands
|
|
66
|
+
detections_subcommands=(
|
|
67
|
+
'healthcheck:Run detection healthcheck'
|
|
68
|
+
'validate:Validate detection configs'
|
|
69
|
+
'drift:Show detection drift signals'
|
|
70
|
+
'run:Run detection configs'
|
|
71
|
+
'configs:Manage detection configs'
|
|
72
|
+
)
|
|
73
|
+
_describe 'subcommand' detections_subcommands
|
|
74
|
+
;;
|
|
75
|
+
metrics)
|
|
76
|
+
local -a metrics_subcommands
|
|
77
|
+
metrics_subcommands=(
|
|
78
|
+
'summary:Show metrics summary'
|
|
79
|
+
'found:Show found threats count'
|
|
80
|
+
'breakdown:Show metrics breakdown'
|
|
81
|
+
)
|
|
82
|
+
_describe 'subcommand' metrics_subcommands
|
|
83
|
+
;;
|
|
84
|
+
reports)
|
|
85
|
+
local -a reports_subcommands
|
|
86
|
+
reports_subcommands=(
|
|
87
|
+
'create:Create a new report'
|
|
88
|
+
'list:List organization reports'
|
|
89
|
+
)
|
|
90
|
+
_describe 'subcommand' reports_subcommands
|
|
91
|
+
;;
|
|
92
|
+
queues)
|
|
93
|
+
local -a queues_subcommands
|
|
94
|
+
queues_subcommands=('snapshot:Show queue snapshot')
|
|
95
|
+
_describe 'subcommand' queues_subcommands
|
|
96
|
+
;;
|
|
97
|
+
presets)
|
|
98
|
+
local -a presets_subcommands
|
|
99
|
+
presets_subcommands=(
|
|
100
|
+
'list:List available presets'
|
|
101
|
+
'run:Run a preset workflow'
|
|
102
|
+
)
|
|
103
|
+
_describe 'subcommand' presets_subcommands
|
|
104
|
+
;;
|
|
105
|
+
completions)
|
|
106
|
+
local -a shells
|
|
107
|
+
shells=('zsh:Zsh completion script' 'bash:Bash completion script')
|
|
108
|
+
_describe 'shell' shells
|
|
109
|
+
;;
|
|
110
|
+
esac
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_chainpatrol
|
|
115
|
+
`;
|
|
116
|
+
var BASH_COMPLETION = `_chainpatrol() {
|
|
117
|
+
local cur prev commands
|
|
118
|
+
COMPREPLY=()
|
|
119
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
120
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
121
|
+
commands="login logout configs detections metrics reports queues presets setup uninstall completions help"
|
|
122
|
+
|
|
123
|
+
case "\${prev}" in
|
|
124
|
+
chainpatrol)
|
|
125
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
126
|
+
return 0
|
|
127
|
+
;;
|
|
128
|
+
configs)
|
|
129
|
+
COMPREPLY=( $(compgen -W "list" -- "\${cur}") )
|
|
130
|
+
return 0
|
|
131
|
+
;;
|
|
132
|
+
detections)
|
|
133
|
+
COMPREPLY=( $(compgen -W "healthcheck validate drift run configs" -- "\${cur}") )
|
|
134
|
+
return 0
|
|
135
|
+
;;
|
|
136
|
+
metrics)
|
|
137
|
+
COMPREPLY=( $(compgen -W "summary found breakdown" -- "\${cur}") )
|
|
138
|
+
return 0
|
|
139
|
+
;;
|
|
140
|
+
reports)
|
|
141
|
+
COMPREPLY=( $(compgen -W "create list" -- "\${cur}") )
|
|
142
|
+
return 0
|
|
143
|
+
;;
|
|
144
|
+
queues)
|
|
145
|
+
COMPREPLY=( $(compgen -W "snapshot" -- "\${cur}") )
|
|
146
|
+
return 0
|
|
147
|
+
;;
|
|
148
|
+
presets)
|
|
149
|
+
COMPREPLY=( $(compgen -W "list run" -- "\${cur}") )
|
|
150
|
+
return 0
|
|
151
|
+
;;
|
|
152
|
+
run)
|
|
153
|
+
if [[ "\${COMP_WORDS[COMP_CWORD-2]}" == "presets" ]]; then
|
|
154
|
+
COMPREPLY=( $(compgen -W "cs-weekly-health" -- "\${cur}") )
|
|
155
|
+
return 0
|
|
156
|
+
fi
|
|
157
|
+
;;
|
|
158
|
+
completions)
|
|
159
|
+
COMPREPLY=( $(compgen -W "zsh bash" -- "\${cur}") )
|
|
160
|
+
return 0
|
|
161
|
+
;;
|
|
162
|
+
esac
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
complete -F _chainpatrol chainpatrol
|
|
166
|
+
`;
|
|
167
|
+
function getCompletionScript(shell) {
|
|
168
|
+
switch (shell) {
|
|
169
|
+
case "bash":
|
|
170
|
+
return BASH_COMPLETION;
|
|
171
|
+
case "zsh":
|
|
172
|
+
default:
|
|
173
|
+
return ZSH_COMPLETION;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function printCompletions(shell) {
|
|
177
|
+
const resolved = shell ?? (process.env.SHELL?.includes("zsh") ? "zsh" : "bash");
|
|
178
|
+
console.log(getCompletionScript(resolved));
|
|
179
|
+
}
|
|
180
|
+
var ZSH_COMPLETIONS_DIR = join(homedir(), ".zsh", "completions");
|
|
181
|
+
var ZSH_COMPLETION_FILE = join(ZSH_COMPLETIONS_DIR, "_chainpatrol");
|
|
182
|
+
var ZSHRC = join(homedir(), ".zshrc");
|
|
183
|
+
var SHELL_MARKER = "# Added by chainpatrol setup";
|
|
184
|
+
function ensureZshrcCompletions() {
|
|
185
|
+
if (!existsSync(ZSHRC)) return false;
|
|
186
|
+
const content = readFileSync(ZSHRC, "utf-8");
|
|
187
|
+
if (content.includes(SHELL_MARKER)) return false;
|
|
188
|
+
const snippet = [
|
|
189
|
+
"",
|
|
190
|
+
`${SHELL_MARKER} (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`,
|
|
191
|
+
"fpath=(~/.zsh/completions $fpath)",
|
|
192
|
+
"autoload -Uz compinit && compinit",
|
|
193
|
+
""
|
|
194
|
+
].join("\n");
|
|
195
|
+
appendFileSync(ZSHRC, snippet);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function removeZshrcCompletions() {
|
|
199
|
+
if (!existsSync(ZSHRC)) return false;
|
|
200
|
+
const content = readFileSync(ZSHRC, "utf-8");
|
|
201
|
+
if (!content.includes(SHELL_MARKER)) return false;
|
|
202
|
+
const lines = content.split("\n");
|
|
203
|
+
const filtered = lines.filter((line, i) => {
|
|
204
|
+
if (line.includes(SHELL_MARKER)) return false;
|
|
205
|
+
if (line === "fpath=(~/.zsh/completions $fpath)") return false;
|
|
206
|
+
if (line === "autoload -Uz compinit && compinit") return false;
|
|
207
|
+
return true;
|
|
208
|
+
});
|
|
209
|
+
writeFileSync(ZSHRC, filtered.join("\n"));
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
function installCompletions() {
|
|
213
|
+
const shell = process.env.SHELL ?? "";
|
|
214
|
+
if (!shell.includes("zsh")) {
|
|
215
|
+
return { installed: false, path: "", shell: "bash", configuredShellRc: false };
|
|
216
|
+
}
|
|
217
|
+
mkdirSync(ZSH_COMPLETIONS_DIR, { recursive: true });
|
|
218
|
+
writeFileSync(ZSH_COMPLETION_FILE, ZSH_COMPLETION, { mode: 420 });
|
|
219
|
+
const configuredShellRc = ensureZshrcCompletions();
|
|
220
|
+
return { installed: true, path: ZSH_COMPLETION_FILE, shell: "zsh", configuredShellRc };
|
|
221
|
+
}
|
|
222
|
+
function uninstallCompletions() {
|
|
223
|
+
let removed = false;
|
|
224
|
+
if (existsSync(ZSH_COMPLETION_FILE)) {
|
|
225
|
+
unlinkSync(ZSH_COMPLETION_FILE);
|
|
226
|
+
removed = true;
|
|
227
|
+
}
|
|
228
|
+
removeZshrcCompletions();
|
|
229
|
+
return removed;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export {
|
|
233
|
+
getCompletionScript,
|
|
234
|
+
printCompletions,
|
|
235
|
+
installCompletions,
|
|
236
|
+
uninstallCompletions
|
|
237
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/components/spinner.tsx
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Text } from "ink";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
6
|
+
function Spinner({ label }) {
|
|
7
|
+
const [frame, setFrame] = useState(0);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const timer = setInterval(() => {
|
|
10
|
+
setFrame((prev) => (prev + 1) % FRAMES.length);
|
|
11
|
+
}, 80);
|
|
12
|
+
return () => clearInterval(timer);
|
|
13
|
+
}, []);
|
|
14
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
15
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: FRAMES[frame] }),
|
|
16
|
+
label ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
17
|
+
" ",
|
|
18
|
+
label
|
|
19
|
+
] }) : null
|
|
20
|
+
] });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/components/error.tsx
|
|
24
|
+
import { Text as Text2 } from "ink";
|
|
25
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
26
|
+
function ErrorDisplay({ message }) {
|
|
27
|
+
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
28
|
+
/* @__PURE__ */ jsx2(Text2, { color: "red", bold: true, children: "\u2717" }),
|
|
29
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
30
|
+
" ",
|
|
31
|
+
message
|
|
32
|
+
] })
|
|
33
|
+
] });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
Spinner,
|
|
38
|
+
ErrorDisplay
|
|
39
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/lib/config.ts
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
var DEFAULTS = {
|
|
6
|
+
apiUrl: process.env.CHAINPATROL_API_URL ?? "https://app.chainpatrol.io"
|
|
7
|
+
};
|
|
8
|
+
function getConfigDir() {
|
|
9
|
+
if (process.env.CHAINPATROL_CONFIG_DIR) {
|
|
10
|
+
return process.env.CHAINPATROL_CONFIG_DIR;
|
|
11
|
+
}
|
|
12
|
+
const legacy = join(homedir(), ".chainpatrol");
|
|
13
|
+
const xdg = join(
|
|
14
|
+
process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"),
|
|
15
|
+
"chainpatrol"
|
|
16
|
+
);
|
|
17
|
+
if (existsSync(legacy) && !existsSync(xdg)) {
|
|
18
|
+
return legacy;
|
|
19
|
+
}
|
|
20
|
+
return xdg;
|
|
21
|
+
}
|
|
22
|
+
function getConfig() {
|
|
23
|
+
const configFile = join(getConfigDir(), "config.json");
|
|
24
|
+
if (!existsSync(configFile)) {
|
|
25
|
+
return { ...DEFAULTS };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const raw = readFileSync(configFile, "utf-8");
|
|
29
|
+
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
30
|
+
} catch {
|
|
31
|
+
return { ...DEFAULTS };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function saveConfig(config) {
|
|
35
|
+
const configDir = getConfigDir();
|
|
36
|
+
mkdirSync(configDir, { recursive: true });
|
|
37
|
+
writeFileSync(join(configDir, "config.json"), JSON.stringify(config, null, 2), {
|
|
38
|
+
mode: 420
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
getConfigDir,
|
|
44
|
+
getConfig,
|
|
45
|
+
saveConfig
|
|
46
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/lib/output-format.ts
|
|
2
|
+
var SUPPORTED_OUTPUT_FORMATS = ["human", "json", "markdown", "csv"];
|
|
3
|
+
function resolveOutputFormat({
|
|
4
|
+
json,
|
|
5
|
+
output
|
|
6
|
+
}) {
|
|
7
|
+
if (json && output && output !== "json") {
|
|
8
|
+
throw new Error("Use either --json or --output, not both.");
|
|
9
|
+
}
|
|
10
|
+
if (json) {
|
|
11
|
+
return "json";
|
|
12
|
+
}
|
|
13
|
+
if (!output) {
|
|
14
|
+
return "human";
|
|
15
|
+
}
|
|
16
|
+
if (output === "human" || output === "json" || output === "markdown" || output === "csv") {
|
|
17
|
+
return output;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Unsupported output format '${output}'. Use one of: ${SUPPORTED_OUTPUT_FORMATS.join(", ")}`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function toCsvRows(rows) {
|
|
24
|
+
if (rows.length === 0) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
const headers = Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
|
|
28
|
+
const escapeValue = (value) => {
|
|
29
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
30
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
const lines = [
|
|
35
|
+
headers.join(","),
|
|
36
|
+
...rows.map(
|
|
37
|
+
(row) => headers.map((header) => {
|
|
38
|
+
const raw = row[header];
|
|
39
|
+
if (raw === null || raw === void 0) {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
return escapeValue(String(raw));
|
|
43
|
+
}).join(",")
|
|
44
|
+
)
|
|
45
|
+
];
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
48
|
+
function printOutput({
|
|
49
|
+
outputFormat,
|
|
50
|
+
human,
|
|
51
|
+
json,
|
|
52
|
+
markdown,
|
|
53
|
+
csv
|
|
54
|
+
}) {
|
|
55
|
+
if (outputFormat === "json") {
|
|
56
|
+
console.log(JSON.stringify(json, null, 2));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (outputFormat === "markdown") {
|
|
60
|
+
if (markdown === void 0) {
|
|
61
|
+
console.log(JSON.stringify(json, null, 2));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log(markdown);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (outputFormat === "csv") {
|
|
68
|
+
if (csv === void 0) {
|
|
69
|
+
console.log(JSON.stringify(json, null, 2));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log(csv);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
human();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
resolveOutputFormat,
|
|
80
|
+
toCsvRows,
|
|
81
|
+
printOutput
|
|
82
|
+
};
|