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.
- package/bin/cli.mjs +24 -20
- package/bin/core.mjs +13 -1
- 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
|
|
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:
|
|
76
|
+
const W = { name: 24, benefit: 34, cap: 7, snyk: 9, codeql: 8 }
|
|
78
77
|
const header = pc.dim(
|
|
79
|
-
''.padEnd(W.name) +
|
|
80
|
-
'
|
|
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.
|
|
83
|
-
truncPad(r.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
].
|
|
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
|
-
//
|
|
191
|
-
p.note(
|
|
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 "
|
|
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
|
}
|