hookstack-cli 0.1.15 → 0.1.17

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 CHANGED
@@ -151,10 +151,13 @@ async function interactiveInstall(slugs, args) {
151
151
  // Scope selection
152
152
  let scope = args.scope
153
153
  console.log('\n Where to install?')
154
- console.log(` ${pc.cyan('1')} This project ${pc.dim('./.claude — committed with your repo')}`)
155
- console.log(` ${pc.cyan('2')} All my projects ${pc.dim('~/.claude — every project on this machine')}`)
156
- const scopeAnswer = await ask(` → [${scope === 'global' ? '2' : '1'}]: `)
154
+ console.log(` ${pc.cyan('1')} This project ${pc.dim('./.claude — committed with your repo')}`)
155
+ console.log(` ${pc.cyan('2')} All my projects ${pc.dim('~/.claude — every project on this machine')}`)
156
+ console.log(` ${pc.cyan('3')} This GitHub Copilot project ${pc.dim('./.claude settings.json adapted, committed with your repo')}`)
157
+ const defaultChoice = scope === 'global' ? '2' : scope === 'copilot' ? '3' : '1'
158
+ const scopeAnswer = await ask(` → [${defaultChoice}]: `)
157
159
  if (scopeAnswer === '2' || scopeAnswer === 'global') scope = 'global'
160
+ else if (scopeAnswer === '3' || scopeAnswer === 'copilot') scope = 'copilot'
158
161
  else if (scopeAnswer === 'q') { console.log('Cancelled.'); process.exit(0) }
159
162
  else scope = 'project'
160
163
 
@@ -165,7 +168,8 @@ async function interactiveInstall(slugs, args) {
165
168
  console.log(`\n ${pc.bold('Security')}`)
166
169
  console.log(securityPanel(buildSecurityRows(hooks)))
167
170
 
168
- const confirmAnswer = await ask(`\n Install ${plural(hooks.length, 'hook')} into ${scope === 'global' ? '~/.claude' : './.claude'}? [Y/n]: `)
171
+ const scopeLabel = scope === 'global' ? '~/.claude' : scope === 'copilot' ? './.claude (GitHub Copilot mode)' : './.claude'
172
+ const confirmAnswer = await ask(`\n Install ${plural(hooks.length, 'hook')} into ${scopeLabel}? [Y/n]: `)
169
173
  if (confirmAnswer.toLowerCase() === 'n' || confirmAnswer.toLowerCase() === 'no') {
170
174
  console.log('Cancelled.')
171
175
  process.exit(0)
@@ -221,7 +225,8 @@ const HELP = `
221
225
  Options:
222
226
  --hooks <slugs> Comma-separated list of hook slugs (omit for default set)
223
227
  --global, -g Install into ~/.claude instead of ./.claude
224
- --scope <s> "project" (default) or "global"
228
+ --copilot Install into ./.claude with paths adapted for GitHub Copilot
229
+ --scope <s> "project" (default), "global", or "copilot"
225
230
  --yes, -y Skip prompts (non-interactive install)
226
231
  --version, -v Show version
227
232
  --help, -h Show this help
package/bin/core.mjs CHANGED
@@ -36,6 +36,7 @@ export function parseArgs(argv) {
36
36
  if (arg === '--yes' || arg === '-y') { result.yes = true; continue }
37
37
  if (arg === '--global' || arg === '-g') { result.scope = 'global'; continue }
38
38
  if (arg === '--project') { result.scope = 'project'; continue }
39
+ if (arg === '--copilot') { result.scope = 'copilot'; continue }
39
40
  if (arg.startsWith('--scope=')) {
40
41
  const v = arg.slice('--scope='.length)
41
42
  if (v === 'global' || v === 'project') result.scope = v
@@ -98,7 +99,8 @@ export function mergeHooks(existing, incoming) {
98
99
 
99
100
  // Gathers the hook fragments from an API hook list into a single event→entries
100
101
  // map. For global scope, rewrites $CLAUDE_PROJECT_DIR to the absolute global
101
- // root so commands resolve outside any project.
102
+ // root so commands resolve outside any project. For copilot scope, strips
103
+ // $CLAUDE_PROJECT_DIR/ so paths become relative (GitHub Copilot compatible).
102
104
  export function collectIncomingHooks(hooks, { scope = 'project', globalRoot } = {}) {
103
105
  const incoming = {}
104
106
  for (const hook of hooks) {
@@ -107,14 +109,16 @@ export function collectIncomingHooks(hooks, { scope = 'project', globalRoot } =
107
109
  for (const [event, entries] of Object.entries(fragment)) {
108
110
  incoming[event] ??= []
109
111
  for (const entry of entries) {
110
- const rewrite = scope === 'global' && globalRoot
111
112
  incoming[event].push({
112
113
  ...entry,
113
- hooks: entry.hooks.map(h =>
114
- rewrite && typeof h.command === 'string'
115
- ? { ...h, command: h.command.replace(PROJECT_DIR_RE, globalRoot) }
116
- : h,
117
- ),
114
+ hooks: entry.hooks.map(h => {
115
+ if (!h.command || typeof h.command !== 'string') return h
116
+ if (scope === 'global' && globalRoot)
117
+ return { ...h, command: h.command.replace(PROJECT_DIR_RE, globalRoot) }
118
+ if (scope === 'copilot')
119
+ return { ...h, command: h.command.replace(/\$\{?CLAUDE_PROJECT_DIR\}?\//g, '') }
120
+ return h
121
+ }),
118
122
  })
119
123
  }
120
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hookstack-cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "CLI installer for the Hookstack catalogue of Claude Code hooks",
5
5
  "type": "module",
6
6
  "bin": {