@rafter-security/cli 0.5.3 → 0.5.9

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.
Files changed (40) hide show
  1. package/README.md +15 -3
  2. package/dist/commands/agent/audit-skill.js +2 -2
  3. package/dist/commands/agent/audit.js +96 -0
  4. package/dist/commands/agent/baseline.js +213 -0
  5. package/dist/commands/agent/exec.js +1 -1
  6. package/dist/commands/agent/index.js +4 -0
  7. package/dist/commands/agent/init.js +371 -29
  8. package/dist/commands/agent/install-hook.js +41 -47
  9. package/dist/commands/agent/scan.js +196 -23
  10. package/dist/commands/agent/status.js +65 -4
  11. package/dist/commands/agent/update-gitleaks.js +40 -0
  12. package/dist/commands/agent/verify.js +18 -4
  13. package/dist/commands/backend/run.js +69 -61
  14. package/dist/commands/ci/init.js +10 -3
  15. package/dist/commands/completion.js +320 -110
  16. package/dist/commands/hook/posttool.js +21 -7
  17. package/dist/commands/hook/pretool.js +50 -13
  18. package/dist/commands/issues/dedup.js +39 -0
  19. package/dist/commands/issues/from-scan.js +143 -0
  20. package/dist/commands/issues/from-text.js +185 -0
  21. package/dist/commands/issues/github-client.js +85 -0
  22. package/dist/commands/issues/index.js +25 -0
  23. package/dist/commands/issues/issue-builder.js +101 -0
  24. package/dist/commands/policy/export.js +7 -2
  25. package/dist/commands/scan/index.js +44 -0
  26. package/dist/core/audit-logger.js +41 -0
  27. package/dist/core/config-defaults.js +28 -0
  28. package/dist/core/config-manager.js +19 -2
  29. package/dist/core/pattern-engine.js +26 -1
  30. package/dist/core/risk-rules.js +5 -3
  31. package/dist/index.js +8 -2
  32. package/dist/scanners/gitleaks.js +5 -5
  33. package/dist/scanners/regex-scanner.js +12 -1
  34. package/dist/scanners/secret-patterns.js +3 -3
  35. package/dist/utils/binary-manager.js +59 -20
  36. package/dist/utils/skill-manager.js +5 -3
  37. package/package.json +2 -1
  38. package/resources/pre-commit-hook.sh +2 -2
  39. package/resources/pre-push-hook.sh +60 -0
  40. package/resources/rafter-security-skill.md +7 -11
@@ -1,106 +1,242 @@
1
1
  import { Command } from "commander";
2
- const BASH_COMPLETION = `
3
- # rafter bash completion
4
- # Add to ~/.bashrc: eval "$(rafter completion bash)"
5
- _rafter_completion() {
6
- local cur prev words
2
+ const BASH_COMPLETION = `# rafter bash completion
3
+ _rafter_completions() {
4
+ local cur prev commands
7
5
  COMPREPLY=()
8
6
  cur="\${COMP_WORDS[COMP_CWORD]}"
9
7
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
10
- words="\${COMP_WORDS[*]}"
11
8
 
12
- local top_cmds="run scan get usage agent ci hook mcp policy completion --help --version"
13
- local agent_cmds="init scan exec config audit audit-skill install-hook verify status"
14
- local ci_cmds="init"
15
- local hook_cmds="pretool posttool"
16
- local policy_cmds="export"
9
+ # Top-level commands
10
+ commands="run scan get usage agent ci hook mcp policy completion help"
17
11
 
18
- if [[ \${COMP_CWORD} -eq 1 ]]; then
19
- COMPREPLY=( \$(compgen -W "\${top_cmds}" -- "\${cur}") )
20
- return 0
21
- fi
22
-
23
- case "\${COMP_WORDS[1]}" in
12
+ case "\${prev}" in
13
+ rafter)
14
+ COMPREPLY=( $(compgen -W "\${commands} --agent --version --help" -- "\${cur}") )
15
+ return 0
16
+ ;;
24
17
  agent)
25
- if [[ \${COMP_CWORD} -eq 2 ]]; then
26
- COMPREPLY=( \$(compgen -W "\${agent_cmds}" -- "\${cur}") )
18
+ COMPREPLY=( $(compgen -W "scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline --help" -- "\${cur}") )
19
+ return 0
20
+ ;;
21
+ config)
22
+ if [[ "\${COMP_WORDS[1]}" == "agent" ]]; then
23
+ COMPREPLY=( $(compgen -W "show get set --help" -- "\${cur}") )
27
24
  fi
28
- case "\${COMP_WORDS[2]}" in
29
- scan) COMPREPLY=( \$(compgen -W "--quiet --json --format --staged --diff --engine" -- "\${cur}") ) ;;
30
- init) COMPREPLY=( \$(compgen -W "--risk-level --skip-gitleaks --skip-openclaw --skip-claude-code --force" -- "\${cur}") ) ;;
31
- verify) COMPREPLY=() ;;
32
- status) COMPREPLY=() ;;
33
- audit-skill) COMPREPLY=( \$(compgen -W "--skip-openclaw --json" -- "\${cur}") ) ;;
34
- install-hook) COMPREPLY=( \$(compgen -W "--global" -- "\${cur}") ) ;;
35
- config) COMPREPLY=( \$(compgen -W "show get set" -- "\${cur}") ) ;;
36
- audit) COMPREPLY=( \$(compgen -W "--last --event --agent --since" -- "\${cur}") ) ;;
37
- esac
25
+ return 0
26
+ ;;
27
+ ci)
28
+ COMPREPLY=( $(compgen -W "init --help" -- "\${cur}") )
29
+ return 0
38
30
  ;;
39
31
  hook)
40
- COMPREPLY=( \$(compgen -W "\${hook_cmds}" -- "\${cur}") )
32
+ COMPREPLY=( $(compgen -W "pretool --help" -- "\${cur}") )
33
+ return 0
41
34
  ;;
42
- ci)
43
- if [[ \${COMP_CWORD} -eq 2 ]]; then
44
- COMPREPLY=( \$(compgen -W "\${ci_cmds}" -- "\${cur}") )
45
- fi
35
+ mcp)
36
+ COMPREPLY=( $(compgen -W "serve --help" -- "\${cur}") )
37
+ return 0
46
38
  ;;
47
39
  policy)
48
- COMPREPLY=( \$(compgen -W "\${policy_cmds}" -- "\${cur}") )
49
- ;;
50
- run|scan)
51
- COMPREPLY=( \$(compgen -W "--api-key --format --quiet" -- "\${cur}") )
40
+ COMPREPLY=( $(compgen -W "export --help" -- "\${cur}") )
41
+ return 0
52
42
  ;;
53
43
  completion)
54
- COMPREPLY=( \$(compgen -W "bash zsh fish" -- "\${cur}") )
44
+ COMPREPLY=( $(compgen -W "bash zsh fish" -- "\${cur}") )
45
+ return 0
46
+ ;;
47
+ scan)
48
+ if [[ "\${COMP_WORDS[1]}" == "agent" ]]; then
49
+ COMPREPLY=( $(compgen -W "--quiet --json --staged --diff --engine --help" -- "\${cur}") )
50
+ else
51
+ COMPREPLY=( $(compgen -W "--repo --branch --api-key --format --skip-interactive --quiet --help" -- "\${cur}") )
52
+ fi
53
+ return 0
54
+ ;;
55
+ run)
56
+ COMPREPLY=( $(compgen -W "--repo --branch --api-key --format --skip-interactive --quiet --help" -- "\${cur}") )
57
+ return 0
58
+ ;;
59
+ get)
60
+ COMPREPLY=( $(compgen -W "--api-key --format --interactive --quiet --help" -- "\${cur}") )
61
+ return 0
62
+ ;;
63
+ init)
64
+ if [[ "\${COMP_WORDS[1]}" == "agent" ]]; then
65
+ COMPREPLY=( $(compgen -W "--risk-level --with-openclaw --with-claude-code --with-codex --with-gemini --with-aider --with-cursor --with-windsurf --with-continue --with-gitleaks --all --help" -- "\${cur}") )
66
+ elif [[ "\${COMP_WORDS[1]}" == "ci" ]]; then
67
+ COMPREPLY=( $(compgen -W "--platform --output --with-backend --help" -- "\${cur}") )
68
+ fi
69
+ return 0
55
70
  ;;
56
71
  esac
57
72
  }
58
- complete -F _rafter_completion rafter
73
+ complete -F _rafter_completions rafter
59
74
  `;
60
- const ZSH_COMPLETION = `
61
- # rafter zsh completion
62
- # Add to ~/.zshrc: eval "$(rafter completion zsh)"
63
- #compdef rafter
75
+ const ZSH_COMPLETION = `#compdef rafter
64
76
 
65
77
  _rafter() {
66
- local state
67
- typeset -A opt_args
68
-
69
- _arguments \\
70
- '1: :->cmd' \\
71
- '*: :->args'
72
-
73
- case \$state in
74
- cmd)
75
- _values 'command' \\
76
- 'run[Run a security scan via backend]' \\
77
- 'scan[Alias for run]' \\
78
- 'agent[Agent security features]' \\
79
- 'ci[CI/CD integration]' \\
80
- 'hook[Hook handlers]' \\
81
- 'mcp[MCP server]' \\
82
- 'policy[Policy management]' \\
83
- 'completion[Shell completion scripts]'
78
+ local -a commands
79
+ commands=(
80
+ 'run:Submit a security scan to the Rafter backend'
81
+ 'scan:Alias for run'
82
+ 'get:Retrieve scan results'
83
+ 'usage:Check API usage quota'
84
+ 'agent:Agent security commands'
85
+ 'ci:CI/CD pipeline setup'
86
+ 'hook:Git hook handlers'
87
+ 'mcp:MCP server'
88
+ 'policy:Policy management'
89
+ 'completion:Generate shell completions'
90
+ 'help:Display help'
91
+ )
92
+
93
+ local -a agent_commands
94
+ agent_commands=(
95
+ 'scan:Scan files for secrets locally'
96
+ 'init:Initialize agent security'
97
+ 'audit:View audit log'
98
+ 'config:Manage configuration'
99
+ 'exec:Execute command with security'
100
+ 'audit-skill:Audit a Claude Code skill'
101
+ 'install-hook:Install git hook (pre-commit or pre-push)'
102
+ 'verify:Check integration status'
103
+ 'status:Show agent status'
104
+ 'update-gitleaks:Update gitleaks binary'
105
+ 'baseline:Manage findings baseline'
106
+ )
107
+
108
+ local -a config_commands
109
+ config_commands=(
110
+ 'show:Show current configuration'
111
+ 'get:Get a configuration value'
112
+ 'set:Set a configuration value'
113
+ )
114
+
115
+ _arguments -C \\
116
+ '(-a --agent)'{-a,--agent}'[Plain output for AI agents]' \\
117
+ '(-V --version)'{-V,--version}'[Show version]' \\
118
+ '(-h --help)'{-h,--help}'[Show help]' \\
119
+ '1:command:->command' \\
120
+ '*::arg:->args'
121
+
122
+ case "\$state" in
123
+ command)
124
+ _describe 'command' commands
84
125
  ;;
85
126
  args)
86
- case \$words[2] in
127
+ case "\$words[1]" in
87
128
  agent)
88
- _values 'subcommand' \\
89
- 'init[Initialize agent security]' \\
90
- 'scan[Scan for secrets]' \\
91
- 'exec[Execute command with security validation]' \\
92
- 'config[Manage configuration]' \\
93
- 'audit[View audit logs]' \\
94
- 'audit-skill[Audit a skill file]' \\
95
- 'install-hook[Install git pre-commit hook]' \\
96
- 'verify[Health check]' \\
97
- 'status[Status dashboard]'
129
+ _arguments -C \\
130
+ '1:subcommand:->subcmd' \\
131
+ '*::arg:->subargs'
132
+ case "\$state" in
133
+ subcmd)
134
+ _describe 'agent command' agent_commands
135
+ ;;
136
+ subargs)
137
+ case "\$words[1]" in
138
+ scan)
139
+ _arguments \\
140
+ '(-q --quiet)'{-q,--quiet}'[Only output if secrets found]' \\
141
+ '--json[Output as JSON]' \\
142
+ '--staged[Scan only staged files]' \\
143
+ '--diff[Scan files changed since ref]:ref:' \\
144
+ '--engine[Scanner engine]:engine:(gitleaks patterns)' \\
145
+ '1:path:_files'
146
+ ;;
147
+ init)
148
+ _arguments \\
149
+ '--risk-level[Risk level]:level:(minimal moderate aggressive)' \\
150
+ '--with-openclaw[Install OpenClaw integration]' \\
151
+ '--with-claude-code[Install Claude Code integration]' \\
152
+ '--with-codex[Install Codex CLI integration]' \\
153
+ '--with-gemini[Install Gemini CLI integration]' \\
154
+ '--with-aider[Install Aider integration]' \\
155
+ '--with-cursor[Install Cursor integration]' \\
156
+ '--with-windsurf[Install Windsurf integration]' \\
157
+ '--with-continue[Install Continue.dev integration]' \\
158
+ '--with-gitleaks[Download Gitleaks binary]' \\
159
+ '--all[Install all detected integrations]'
160
+ ;;
161
+ audit)
162
+ _arguments \\
163
+ '--last[Show last N entries]:count:' \\
164
+ '--event[Filter by event type]:type:' \\
165
+ '--agent[Filter by agent type]:agent:(openclaw claude-code)' \\
166
+ '--since[Show entries since date]:date:'
167
+ ;;
168
+ config)
169
+ _arguments -C '1:subcommand:->cfgcmd'
170
+ case "\$state" in
171
+ cfgcmd)
172
+ _describe 'config command' config_commands
173
+ ;;
174
+ esac
175
+ ;;
176
+ exec)
177
+ _arguments \\
178
+ '--skip-scan[Skip pre-execution scanning]' \\
179
+ '--force[Skip approval prompts]' \\
180
+ '1:command:'
181
+ ;;
182
+ audit-skill)
183
+ _arguments \\
184
+ '--skip-openclaw[Skip OpenClaw integration]' \\
185
+ '--json[Output as JSON]' \\
186
+ '1:skill-path:_files'
187
+ ;;
188
+ install-hook)
189
+ _arguments \\
190
+ '--global[Install globally]'
191
+ ;;
192
+ esac
193
+ ;;
194
+ esac
195
+ ;;
196
+ run|scan)
197
+ _arguments \\
198
+ '(-r --repo)'{-r,--repo}'[Repository]:repo:' \\
199
+ '(-b --branch)'{-b,--branch}'[Branch]:branch:' \\
200
+ '(-k --api-key)'{-k,--api-key}'[API key]:key:' \\
201
+ '(-f --format)'{-f,--format}'[Output format]:format:(json md)' \\
202
+ '--skip-interactive[Do not wait for scan]' \\
203
+ '--quiet[Suppress status messages]'
204
+ ;;
205
+ get)
206
+ _arguments \\
207
+ '(-k --api-key)'{-k,--api-key}'[API key]:key:' \\
208
+ '(-f --format)'{-f,--format}'[Output format]:format:(json md)' \\
209
+ '--interactive[Poll until done]' \\
210
+ '--quiet[Suppress status messages]' \\
211
+ '1:scan_id:'
212
+ ;;
213
+ usage)
214
+ _arguments \\
215
+ '(-k --api-key)'{-k,--api-key}'[API key]:key:'
216
+ ;;
217
+ ci)
218
+ _arguments -C '1:subcommand:(init)'
98
219
  ;;
99
220
  hook)
100
- _values 'subcommand' 'pretool[PreToolUse handler]' 'posttool[PostToolUse handler]'
221
+ _arguments -C '1:subcommand:(pretool)'
222
+ ;;
223
+ mcp)
224
+ _arguments -C '1:subcommand:(serve)'
225
+ ;;
226
+ policy)
227
+ _arguments -C \\
228
+ '1:subcommand:(export)' \\
229
+ '*::arg:->policyargs'
230
+ case "\$state" in
231
+ policyargs)
232
+ _arguments \\
233
+ '--format[Target format]:format:(claude codex)' \\
234
+ '--output[Output file]:path:_files'
235
+ ;;
236
+ esac
101
237
  ;;
102
238
  completion)
103
- _values 'shell' 'bash' 'zsh' 'fish'
239
+ _arguments '1:shell:(bash zsh fish)'
104
240
  ;;
105
241
  esac
106
242
  ;;
@@ -109,61 +245,135 @@ _rafter() {
109
245
 
110
246
  _rafter
111
247
  `;
112
- const FISH_COMPLETION = `
113
- # rafter fish completion
114
- # Save to ~/.config/fish/completions/rafter.fish
115
- # Or: rafter completion fish > ~/.config/fish/completions/rafter.fish
248
+ const FISH_COMPLETION = `# rafter fish completion
116
249
 
250
+ # Disable file completions by default
117
251
  complete -c rafter -f
118
- complete -c rafter -n '__fish_use_subcommand' -a 'run' -d 'Run a security scan via backend'
119
- complete -c rafter -n '__fish_use_subcommand' -a 'agent' -d 'Agent security features'
120
- complete -c rafter -n '__fish_use_subcommand' -a 'ci' -d 'CI/CD integration'
121
- complete -c rafter -n '__fish_use_subcommand' -a 'hook' -d 'Hook handlers'
122
- complete -c rafter -n '__fish_use_subcommand' -a 'mcp' -d 'MCP server'
123
- complete -c rafter -n '__fish_use_subcommand' -a 'completion' -d 'Shell completion scripts'
252
+
253
+ # Global options
254
+ complete -c rafter -s a -l agent -d 'Plain output for AI agents'
255
+ complete -c rafter -s V -l version -d 'Show version'
256
+ complete -c rafter -s h -l help -d 'Show help'
257
+
258
+ # Top-level commands
259
+ complete -c rafter -n '__fish_use_subcommand' -a run -d 'Submit a security scan'
260
+ complete -c rafter -n '__fish_use_subcommand' -a scan -d 'Alias for run'
261
+ complete -c rafter -n '__fish_use_subcommand' -a get -d 'Retrieve scan results'
262
+ complete -c rafter -n '__fish_use_subcommand' -a usage -d 'Check API usage quota'
263
+ complete -c rafter -n '__fish_use_subcommand' -a agent -d 'Agent security commands'
264
+ complete -c rafter -n '__fish_use_subcommand' -a ci -d 'CI/CD pipeline setup'
265
+ complete -c rafter -n '__fish_use_subcommand' -a hook -d 'Git hook handlers'
266
+ complete -c rafter -n '__fish_use_subcommand' -a mcp -d 'MCP server'
267
+ complete -c rafter -n '__fish_use_subcommand' -a policy -d 'Policy management'
268
+ complete -c rafter -n '__fish_use_subcommand' -a completion -d 'Generate shell completions'
269
+
270
+ # run / scan options
271
+ complete -c rafter -n '__fish_seen_subcommand_from run scan' -s r -l repo -d 'Repository (org/repo)' -r
272
+ complete -c rafter -n '__fish_seen_subcommand_from run scan' -s b -l branch -d 'Branch' -r
273
+ complete -c rafter -n '__fish_seen_subcommand_from run scan' -s k -l api-key -d 'API key' -r
274
+ complete -c rafter -n '__fish_seen_subcommand_from run scan' -s f -l format -d 'Output format' -ra 'json md'
275
+ complete -c rafter -n '__fish_seen_subcommand_from run scan' -l skip-interactive -d 'Do not wait for scan'
276
+ complete -c rafter -n '__fish_seen_subcommand_from run scan' -l quiet -d 'Suppress status messages'
277
+
278
+ # get options
279
+ complete -c rafter -n '__fish_seen_subcommand_from get' -s k -l api-key -d 'API key' -r
280
+ complete -c rafter -n '__fish_seen_subcommand_from get' -s f -l format -d 'Output format' -ra 'json md'
281
+ complete -c rafter -n '__fish_seen_subcommand_from get' -l interactive -d 'Poll until done'
282
+ complete -c rafter -n '__fish_seen_subcommand_from get' -l quiet -d 'Suppress status messages'
283
+
284
+ # usage options
285
+ complete -c rafter -n '__fish_seen_subcommand_from usage' -s k -l api-key -d 'API key' -r
124
286
 
125
287
  # agent subcommands
126
- complete -c rafter -n '__fish_seen_subcommand_from agent' -a 'init scan exec config audit audit-skill install-hook verify status'
127
- complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l quiet -s q -d 'Only output if secrets found'
128
- complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l json -d 'JSON output'
129
- complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l format -d 'Output format: text, json, sarif'
130
- complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l staged -d 'Scan staged files'
131
- complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l engine -d 'Engine: gitleaks, patterns, auto'
288
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a scan -d 'Scan files for secrets'
289
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a init -d 'Initialize agent security'
290
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a audit -d 'View audit log'
291
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a config -d 'Manage configuration'
292
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a exec -d 'Execute with security'
293
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a audit-skill -d 'Audit a skill file'
294
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a install-hook -d 'Install pre-commit hook'
295
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a verify -d 'Check integration status'
296
+
297
+ # agent scan options
298
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -s q -l quiet -d 'Only output if secrets found'
299
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l json -d 'Output as JSON'
300
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l staged -d 'Scan only staged files'
301
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l diff -d 'Scan changed since ref' -r
302
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l engine -d 'Scanner engine' -ra 'gitleaks patterns'
303
+
304
+ # agent init options
305
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l risk-level -d 'Risk level' -ra 'minimal moderate aggressive'
306
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-openclaw -d 'Install OpenClaw'
307
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-claude-code -d 'Install Claude Code'
308
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-codex -d 'Install Codex CLI'
309
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-gemini -d 'Install Gemini CLI'
310
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-aider -d 'Install Aider'
311
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-cursor -d 'Install Cursor'
312
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-windsurf -d 'Install Windsurf'
313
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-continue -d 'Install Continue.dev'
314
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l with-gitleaks -d 'Install Gitleaks'
315
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l all -d 'Install all detected'
316
+
317
+ # agent audit options
318
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l last -d 'Show last N entries' -r
319
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l event -d 'Filter by event type' -r
320
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l agent -d 'Filter by agent type' -ra 'openclaw claude-code'
321
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l since -d 'Since date' -r
322
+
323
+ # agent config subcommands
324
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from config' -a show -d 'Show configuration'
325
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from config' -a get -d 'Get a config value'
326
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from config' -a set -d 'Set a config value'
327
+
328
+ # agent exec options
329
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from exec' -l skip-scan -d 'Skip pre-execution scanning'
330
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from exec' -l force -d 'Skip approval prompts'
331
+
332
+ # agent audit-skill options
333
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit-skill' -l skip-openclaw -d 'Skip OpenClaw integration'
334
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit-skill' -l json -d 'Output as JSON'
335
+
336
+ # agent install-hook options
337
+ complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from install-hook' -l global -d 'Install globally'
338
+
339
+ # ci subcommands
340
+ complete -c rafter -n '__fish_seen_subcommand_from ci' -a init -d 'Initialize CI pipeline'
341
+ complete -c rafter -n '__fish_seen_subcommand_from ci; and __fish_seen_subcommand_from init' -l platform -d 'CI platform' -ra 'github gitlab circleci'
342
+ complete -c rafter -n '__fish_seen_subcommand_from ci; and __fish_seen_subcommand_from init' -l output -d 'Output path' -r
343
+ complete -c rafter -n '__fish_seen_subcommand_from ci; and __fish_seen_subcommand_from init' -l with-backend -d 'Include backend audit'
132
344
 
133
345
  # hook subcommands
134
- complete -c rafter -n '__fish_seen_subcommand_from hook' -a 'pretool posttool'
346
+ complete -c rafter -n '__fish_seen_subcommand_from hook' -a pretool -d 'PreToolUse hook handler'
347
+
348
+ # mcp subcommands
349
+ complete -c rafter -n '__fish_seen_subcommand_from mcp' -a serve -d 'Start MCP server'
350
+ complete -c rafter -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from serve' -l transport -d 'Transport type' -r
351
+
352
+ # policy subcommands
353
+ complete -c rafter -n '__fish_seen_subcommand_from policy' -a export -d 'Export policy'
354
+ complete -c rafter -n '__fish_seen_subcommand_from policy; and __fish_seen_subcommand_from export' -l format -d 'Target format' -ra 'claude codex'
355
+ complete -c rafter -n '__fish_seen_subcommand_from policy; and __fish_seen_subcommand_from export' -l output -d 'Output file' -r
135
356
 
136
- # completion subcommands
137
- complete -c rafter -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'
357
+ # completion subcommand
358
+ complete -c rafter -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell type'
138
359
  `;
139
360
  export function createCompletionCommand() {
140
361
  return new Command("completion")
141
362
  .description("Generate shell completion scripts")
142
363
  .argument("<shell>", "Shell type: bash, zsh, or fish")
143
- .addHelpText("after", `
144
- Examples:
145
- # bash — add to ~/.bashrc
146
- eval "$(rafter completion bash)"
147
-
148
- # zsh — add to ~/.zshrc
149
- eval "$(rafter completion zsh)"
150
-
151
- # fish — save to completions dir
152
- rafter completion fish > ~/.config/fish/completions/rafter.fish
153
- `)
154
364
  .action((shell) => {
155
- switch (shell.toLowerCase()) {
365
+ switch (shell) {
156
366
  case "bash":
157
- process.stdout.write(BASH_COMPLETION.trimStart());
367
+ process.stdout.write(BASH_COMPLETION);
158
368
  break;
159
369
  case "zsh":
160
- process.stdout.write(ZSH_COMPLETION.trimStart());
370
+ process.stdout.write(ZSH_COMPLETION);
161
371
  break;
162
372
  case "fish":
163
- process.stdout.write(FISH_COMPLETION.trimStart());
373
+ process.stdout.write(FISH_COMPLETION);
164
374
  break;
165
375
  default:
166
- console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
376
+ process.stderr.write(`Unknown shell: ${shell}. Supported: bash, zsh, fish\n`);
167
377
  process.exit(1);
168
378
  }
169
379
  });
@@ -5,17 +5,28 @@ export function createHookPosttoolCommand() {
5
5
  return new Command("posttool")
6
6
  .description("PostToolUse hook handler (reads stdin, redacts secrets in output, writes JSON to stdout)")
7
7
  .action(async () => {
8
- const input = await readStdin();
9
- let payload;
10
8
  try {
11
- payload = JSON.parse(input);
9
+ const input = await readStdin();
10
+ let payload;
11
+ try {
12
+ payload = JSON.parse(input);
13
+ }
14
+ catch {
15
+ writeOutput({ action: "continue" });
16
+ return;
17
+ }
18
+ // Validate payload is an object with expected shape
19
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
20
+ writeOutput({ action: "continue" });
21
+ return;
22
+ }
23
+ const output = evaluateToolResponse(payload);
24
+ writeOutput(output);
12
25
  }
13
26
  catch {
27
+ // Any unexpected error → fail open
14
28
  writeOutput({ action: "continue" });
15
- return;
16
29
  }
17
- const output = evaluateToolResponse(payload);
18
- writeOutput(output);
19
30
  });
20
31
  }
21
32
  function evaluateToolResponse(payload) {
@@ -59,12 +70,15 @@ function countMatches(scanner, tool_response) {
59
70
  }
60
71
  return count;
61
72
  }
73
+ const STDIN_TIMEOUT_MS = 5000;
62
74
  function readStdin() {
63
75
  return new Promise((resolve) => {
64
76
  let data = "";
77
+ const timeout = setTimeout(() => { resolve(data); }, STDIN_TIMEOUT_MS);
65
78
  process.stdin.setEncoding("utf-8");
66
79
  process.stdin.on("data", (chunk) => { data += chunk; });
67
- process.stdin.on("end", () => { resolve(data); });
80
+ process.stdin.on("end", () => { clearTimeout(timeout); resolve(data); });
81
+ process.stdin.on("error", () => { clearTimeout(timeout); resolve(data); });
68
82
  process.stdin.resume();
69
83
  });
70
84
  }