hookstack-cli 0.1.18 → 0.1.19

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 (3) hide show
  1. package/bin/cli.mjs +24 -20
  2. package/bin/core.mjs +13 -1
  3. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -11,7 +11,6 @@ import {
11
11
  mergeHooks,
12
12
  assertSafeTarget,
13
13
  collectIncomingHooks,
14
- buildSummaryRows,
15
14
  buildSecurityRows,
16
15
  } from './core.mjs'
17
16
 
@@ -71,29 +70,36 @@ function capCell(on, width) {
71
70
  return on ? pc.yellow(text) : pc.dim(text)
72
71
  }
73
72
 
74
- const SNYK_COLOR = { high: pc.red, medium: pc.yellow, low: pc.cyan, safe: pc.green, unknown: pc.dim }
73
+ const VERDICT_COLOR = { high: pc.red, medium: pc.yellow, low: pc.cyan, safe: pc.green, unknown: pc.dim }
75
74
 
76
75
  export function securityPanel(rows) {
77
- const W = { name: 28, cap: 8, snyk: 10 }
76
+ const W = { name: 24, benefit: 34, cap: 7, snyk: 9, codeql: 8 }
78
77
  const header = pc.dim(
79
- ''.padEnd(W.name) + 'Shell'.padEnd(W.cap) + 'Net'.padEnd(W.cap) +
80
- 'Writes'.padEnd(W.cap) + 'Snyk',
78
+ ''.padEnd(W.name) +
79
+ ''.padEnd(W.benefit) +
80
+ 'Shell'.padEnd(W.cap) +
81
+ 'Net'.padEnd(W.cap) +
82
+ 'Writes'.padEnd(W.cap) +
83
+ 'Snyk'.padEnd(W.snyk) +
84
+ 'CodeQL',
81
85
  )
82
- const body = rows.map(r =>
83
- truncPad(r.name, W.name) +
84
- capCell(r.shell, W.cap) +
85
- capCell(r.network, W.cap) +
86
- capCell(r.fsWrite, W.cap) +
87
- (SNYK_COLOR[r.snyk.level] ?? pc.dim)(truncPad(r.snyk.label, W.snyk)),
88
- )
89
- const anyUnknown = rows.some(r => r.snyk.level === 'unknown')
86
+ const body = rows.flatMap(r => {
87
+ const benefitText = r.benefit ? truncPad(r.benefit, W.benefit) : ''.padEnd(W.benefit)
88
+ const row = truncPad(r.name, W.name) +
89
+ pc.dim(benefitText) +
90
+ capCell(r.shell, W.cap) +
91
+ capCell(r.network, W.cap) +
92
+ capCell(r.fsWrite, W.cap) +
93
+ (VERDICT_COLOR[r.snyk.level] ?? pc.dim)(truncPad(r.snyk.label, W.snyk)) +
94
+ (VERDICT_COLOR[r.codeql.level] ?? pc.dim)(r.codeql.label)
95
+ return [row]
96
+ })
90
97
  const footer = [
91
98
  '',
92
99
  pc.dim('Shell = runs commands · Net = network access · Writes = filesystem writes'),
93
- anyUnknown ? pc.dim('Snyk "—" = not scanned yet') : null,
94
100
  pc.dim(`Details: ${API_BASE}/hook/<slug>`),
95
- ].filter(v => v !== null).join('\n')
96
- return [header, ...body, footer].join('\n')
101
+ ].join('\n')
102
+ return [header, '', ...body, footer].join('\n')
97
103
  }
98
104
 
99
105
  // ── install ───────────────────────────────────────────────────────────────────
@@ -184,12 +190,10 @@ async function interactiveInstall(slugs, args) {
184
190
  scope = scopeResult
185
191
 
186
192
  const dirs = resolveScopeRoot(scope, { cwd: process.cwd(), home: homedir() })
187
- const summaryRows = buildSummaryRows(hooks, { root: dirs.root })
188
193
  const secRows = buildSecurityRows(hooks)
189
194
 
190
- // Panels
191
- p.note(summaryPanel(summaryRows), 'Installation Summary')
192
- p.note(securityPanel(secRows), 'Security Assessment')
195
+ // Panel
196
+ p.note(securityPanel(secRows), 'Installation Summary')
193
197
 
194
198
  // Confirm
195
199
  const scopeLabel = scope === 'global' ? '~/.claude' : scope === 'copilot' ? './.claude (Copilot mode)' : './.claude'
package/bin/core.mjs CHANGED
@@ -159,6 +159,16 @@ export function snykVerdict(snyk) {
159
159
  return { label: 'Safe', level: 'safe' }
160
160
  }
161
161
 
162
+ // Maps a stored CodeQL scan to a short verdict label.
163
+ export function codeqlVerdict(codeql) {
164
+ if (!codeql || typeof codeql !== 'object') return { label: '—', level: 'unknown' }
165
+ const { critical = 0, high = 0, medium = 0, low = 0 } = codeql
166
+ if (critical > 0 || high > 0) return { label: 'High Risk', level: 'high' }
167
+ if (medium > 0) return { label: 'Med Risk', level: 'medium' }
168
+ if (low > 0) return { label: 'Low Risk', level: 'low' }
169
+ return { label: 'Safe', level: 'safe' }
170
+ }
171
+
162
172
  export function shortRepo(url) {
163
173
  if (!url) return null
164
174
  return String(url)
@@ -184,12 +194,14 @@ export function buildSummaryRows(hooks, { root }) {
184
194
  })
185
195
  }
186
196
 
187
- // Display rows for the "Security" panel: local static capabilities + Snyk verdict.
197
+ // Display rows for the "Installation Summary" panel: description + static capabilities + verdicts.
188
198
  export function buildSecurityRows(hooks) {
189
199
  return hooks.map(h => ({
190
200
  slug: h.slug,
191
201
  name: h.name ?? h.slug,
202
+ benefit: h.benefit ?? null,
192
203
  ...analyzeSecurity(h.code_snippet),
193
204
  snyk: snykVerdict(h.security?.snyk),
205
+ codeql: codeqlVerdict(h.security?.codeql),
194
206
  }))
195
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hookstack-cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "CLI installer for the Hookstack catalogue of Claude Code hooks",
5
5
  "type": "module",
6
6
  "bin": {