@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.
@@ -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
+ };