hookstack-cli 0.1.8 → 0.1.10
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 +26 -15
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -21,7 +21,9 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
|
21
21
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8')).version
|
|
22
22
|
|
|
23
23
|
async function fetchHooks(slugs) {
|
|
24
|
-
const url =
|
|
24
|
+
const url = slugs.length === 0
|
|
25
|
+
? `${API_BASE}/api/hooks`
|
|
26
|
+
: `${API_BASE}/api/hooks?slugs=${slugs.map(encodeURIComponent).join(',')}`
|
|
25
27
|
const res = await fetch(url)
|
|
26
28
|
if (!res.ok) {
|
|
27
29
|
const body = await res.text().catch(() => '')
|
|
@@ -125,7 +127,8 @@ async function interactiveInstall(slugs, args) {
|
|
|
125
127
|
p.intro(pc.bgCyan(pc.black(' hookstack-cli ')))
|
|
126
128
|
|
|
127
129
|
const s = p.spinner()
|
|
128
|
-
|
|
130
|
+
const isDefault = slugs.length === 0
|
|
131
|
+
s.start(isDefault ? 'Fetching default HookStack…' : `Fetching ${plural(slugs.length, 'hook')}`)
|
|
129
132
|
let data
|
|
130
133
|
try {
|
|
131
134
|
data = await fetchHooks(slugs)
|
|
@@ -136,33 +139,39 @@ async function interactiveInstall(slugs, args) {
|
|
|
136
139
|
}
|
|
137
140
|
const { hooks } = data
|
|
138
141
|
const notFound = slugs.filter(slug => !hooks.find(h => h.slug === slug))
|
|
139
|
-
s.stop(
|
|
142
|
+
s.stop(isDefault
|
|
143
|
+
? `Default HookStack — ${plural(hooks.length, 'hook')}`
|
|
144
|
+
: `Fetched ${plural(hooks.length, 'hook')}`)
|
|
140
145
|
if (notFound.length) p.log.warn(`Unknown slugs skipped: ${notFound.join(', ')}`)
|
|
141
146
|
if (hooks.length === 0) {
|
|
142
147
|
p.cancel('No hooks to install.')
|
|
143
148
|
process.exit(1)
|
|
144
149
|
}
|
|
145
150
|
|
|
151
|
+
if (isDefault) {
|
|
152
|
+
p.log.info(`The default HookStack gives your Claude Code setup ${plural(hooks.length, 'battle-tested hook')} covering security, context, validation and workflow.`)
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
const scope = await p.select({
|
|
147
|
-
message: '
|
|
156
|
+
message: 'Where do you want to install?',
|
|
148
157
|
initialValue: args.scope,
|
|
149
158
|
options: [
|
|
150
|
-
{ value: 'project', label: '
|
|
151
|
-
{ value: 'global', label: '
|
|
159
|
+
{ value: 'project', label: 'This project', hint: './.claude — committed with your repo' },
|
|
160
|
+
{ value: 'global', label: 'All my projects', hint: '~/.claude — every project on this machine' },
|
|
152
161
|
],
|
|
153
162
|
})
|
|
154
163
|
if (p.isCancel(scope)) { p.cancel('Cancelled.'); process.exit(0) }
|
|
155
164
|
|
|
156
165
|
const dirs = resolveScopeRoot(scope, { cwd: process.cwd(), home: homedir() })
|
|
157
166
|
|
|
158
|
-
p.note(summaryPanel(buildSummaryRows(hooks, { root: dirs.root })),
|
|
167
|
+
p.note(summaryPanel(buildSummaryRows(hooks, { root: dirs.root })), `${plural(hooks.length, 'Hook')} to install`)
|
|
159
168
|
p.note(securityPanel(buildSecurityRows(hooks)), 'Security')
|
|
160
169
|
|
|
161
170
|
const ok = await p.confirm({ message: `Install ${plural(hooks.length, 'hook')} into ${scope === 'global' ? '~/.claude' : './.claude'}?` })
|
|
162
171
|
if (p.isCancel(ok) || !ok) { p.cancel('Cancelled.'); process.exit(0) }
|
|
163
172
|
|
|
164
173
|
const s2 = p.spinner()
|
|
165
|
-
s2.start('Installing')
|
|
174
|
+
s2.start('Installing…')
|
|
166
175
|
let result
|
|
167
176
|
try {
|
|
168
177
|
result = doInstall(hooks, dirs, scope, p.log)
|
|
@@ -173,12 +182,14 @@ async function interactiveInstall(slugs, args) {
|
|
|
173
182
|
}
|
|
174
183
|
s2.stop(`Wrote ${plural(result.scriptCount, 'script')} + patched settings.json`)
|
|
175
184
|
|
|
185
|
+
p.log.info(`Browse more hooks → ${pc.cyan(`${API_BASE}/#catalogue`)}`)
|
|
176
186
|
p.log.info(`⭐ star us → ${pc.cyan(REPO_URL)}`)
|
|
177
|
-
p.outro(pc.green(`✓
|
|
187
|
+
p.outro(pc.green(`✓ ${plural(result.hookCount, 'hook')} installed — restart Claude Code to activate.`))
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
async function directInstall(slugs, args) {
|
|
181
|
-
|
|
191
|
+
const isDefault = slugs.length === 0
|
|
192
|
+
console.log(isDefault ? '\nInstalling default HookStack…' : `\nFetching ${plural(slugs.length, 'hook')}…`)
|
|
182
193
|
let data
|
|
183
194
|
try {
|
|
184
195
|
data = await fetchHooks(slugs)
|
|
@@ -205,25 +216,25 @@ const HELP = `
|
|
|
205
216
|
hookstack — Claude Code hook installer
|
|
206
217
|
|
|
207
218
|
Usage:
|
|
208
|
-
npx hookstack-cli@latest install
|
|
219
|
+
npx hookstack-cli@latest install # install the default HookStack
|
|
220
|
+
npx hookstack-cli@latest install --hooks=<slug1>,<slug2>,... # custom selection
|
|
209
221
|
|
|
210
222
|
Options:
|
|
211
|
-
--hooks <slugs> Comma-separated list of hook slugs
|
|
223
|
+
--hooks <slugs> Comma-separated list of hook slugs (omit for default set)
|
|
212
224
|
--global, -g Install into ~/.claude instead of ./.claude
|
|
213
225
|
--scope <s> "project" (default) or "global"
|
|
214
226
|
--yes, -y Skip prompts (non-interactive install)
|
|
215
227
|
--version, -v Show version
|
|
216
228
|
--help, -h Show this help
|
|
217
229
|
|
|
218
|
-
|
|
219
|
-
or when --yes is passed. Browse hooks at https://hookstack.vercel.app
|
|
230
|
+
Browse hooks at https://hookstack.vercel.app
|
|
220
231
|
`
|
|
221
232
|
|
|
222
233
|
async function main() {
|
|
223
234
|
const args = parseArgs(process.argv)
|
|
224
235
|
|
|
225
236
|
if (args.version) { console.log(VERSION); return }
|
|
226
|
-
if (args.help
|
|
237
|
+
if (args.help) { console.log(HELP); return }
|
|
227
238
|
|
|
228
239
|
const command = args.command ?? 'install'
|
|
229
240
|
if (command !== 'install') {
|