@claude-code-hooks/cli 0.1.9 → 0.1.11
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/package.json +5 -5
- package/src/cli.js +58 -51
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-code-hooks/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Wizard CLI to set up and manage @claude-code-hooks packages for Claude Code.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/beefiker/claude-code-hooks/tree/main/packages/cli",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@clack/prompts": "^1.0.0",
|
|
30
|
-
"@claude-code-hooks/core": "0.1.
|
|
31
|
-
"@claude-code-hooks/security": "0.1.
|
|
32
|
-
"@claude-code-hooks/secrets": "0.1.
|
|
33
|
-
"@claude-code-hooks/sound": "0.2.
|
|
30
|
+
"@claude-code-hooks/core": "^0.1.4",
|
|
31
|
+
"@claude-code-hooks/security": "^0.1.8",
|
|
32
|
+
"@claude-code-hooks/secrets": "^0.1.9",
|
|
33
|
+
"@claude-code-hooks/sound": "^0.2.12",
|
|
34
34
|
"@claude-code-hooks/notification": "0.1.5"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
package/src/cli.js
CHANGED
|
@@ -31,7 +31,9 @@ import {
|
|
|
31
31
|
configFilePath,
|
|
32
32
|
readProjectConfig,
|
|
33
33
|
writeProjectConfig,
|
|
34
|
-
removeLegacyClaudeSoundHooks
|
|
34
|
+
removeLegacyClaudeSoundHooks,
|
|
35
|
+
t,
|
|
36
|
+
detectLanguage
|
|
35
37
|
} from '@claude-code-hooks/core';
|
|
36
38
|
|
|
37
39
|
import { buildSettingsSnippet } from './snippet.js';
|
|
@@ -43,8 +45,8 @@ import { planInteractiveSetup as planSecretsSetup } from '@claude-code-hooks/sec
|
|
|
43
45
|
import { planInteractiveSetup as planSoundSetup } from '@claude-code-hooks/sound/src/plan.js';
|
|
44
46
|
import { planInteractiveSetup as planNotificationSetup } from '@claude-code-hooks/notification/src/plan.js';
|
|
45
47
|
|
|
46
|
-
function dieCancelled(msg
|
|
47
|
-
cancel(msg);
|
|
48
|
+
function dieCancelled(msg) {
|
|
49
|
+
cancel(msg ?? t('cli.cancelled'));
|
|
48
50
|
process.exit(0);
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -73,8 +75,7 @@ async function withBackspaceBack(controller, runPrompt) {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
function usage(exitCode = 0) {
|
|
76
|
-
process.stdout.write(
|
|
77
|
-
claude-code-hooks\n\nUsage:\n claude-code-hooks\n npx @claude-code-hooks/cli@latest\n\nWhat it does:\n - Update Claude Code settings (global), or generate a project-only config + pasteable snippet.\n`);
|
|
78
|
+
process.stdout.write(t('cli.usage') + '\n');
|
|
78
79
|
process.exit(exitCode);
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -97,7 +98,7 @@ async function ensureProjectOnlyConfig(projectDir, selected, perPackageConfig) {
|
|
|
97
98
|
|
|
98
99
|
async function maybeWriteSnippet(projectDir, snippetObj) {
|
|
99
100
|
const ok = await confirm({
|
|
100
|
-
message:
|
|
101
|
+
message: t('cli.writeSnippet'),
|
|
101
102
|
initialValue: false
|
|
102
103
|
});
|
|
103
104
|
if (isCancel(ok)) return;
|
|
@@ -105,17 +106,23 @@ async function maybeWriteSnippet(projectDir, snippetObj) {
|
|
|
105
106
|
|
|
106
107
|
const filePath = path.join(projectDir, 'claude-code-hooks.snippet.json');
|
|
107
108
|
await fs.writeFile(filePath, JSON.stringify(snippetObj, null, 2) + '\n');
|
|
108
|
-
note(filePath, '
|
|
109
|
+
note(filePath, t('cli.wroteSnippet'));
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
async function main() {
|
|
112
113
|
const args = process.argv.slice(2);
|
|
113
114
|
if (args.includes('-h') || args.includes('--help')) usage(0);
|
|
114
115
|
|
|
116
|
+
// i18n: --lang ko or env CLAUDE_CODE_HOOKS_LANG
|
|
117
|
+
const langIdx = args.indexOf('--lang');
|
|
118
|
+
if (langIdx !== -1 && args[langIdx + 1]) {
|
|
119
|
+
process.env.CLAUDE_CODE_HOOKS_LANG = args[langIdx + 1].toLowerCase().slice(0, 2);
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
const projectDir = process.cwd();
|
|
116
123
|
|
|
117
124
|
intro('claude-code-hooks');
|
|
118
|
-
note(
|
|
125
|
+
note(t('cli.navHint'), t('cli.navTitle'));
|
|
119
126
|
|
|
120
127
|
// ── Step 1–3: action, target, packages (Backspace = go to previous step) ──
|
|
121
128
|
let action;
|
|
@@ -126,23 +133,23 @@ async function main() {
|
|
|
126
133
|
let perPackage = { security: null, secrets: null, sound: null, notification: null };
|
|
127
134
|
|
|
128
135
|
const packageOptions = [
|
|
129
|
-
{ value: 'security', label: '
|
|
130
|
-
{ value: 'secrets', label: '
|
|
131
|
-
{ value: 'sound', label: '
|
|
132
|
-
{ value: 'notification', label: '
|
|
136
|
+
{ value: 'security', label: t('cli.pkgSecurity'), hint: t('cli.pkgSecurityHint') },
|
|
137
|
+
{ value: 'secrets', label: t('cli.pkgSecrets'), hint: t('cli.pkgSecretsHint') },
|
|
138
|
+
{ value: 'sound', label: t('cli.pkgSound'), hint: t('cli.pkgSoundHint') },
|
|
139
|
+
{ value: 'notification', label: t('cli.pkgNotification'), hint: t('cli.pkgNotificationHint') }
|
|
133
140
|
];
|
|
134
141
|
|
|
135
142
|
while (true) {
|
|
136
143
|
if (step === 1) {
|
|
137
144
|
action = await select({
|
|
138
|
-
message: `${pc.dim('
|
|
145
|
+
message: `${pc.dim(t('cli.stepFormat', { n: 1 }))} ${t('cli.step1ChooseAction')}`,
|
|
139
146
|
options: [
|
|
140
|
-
{ value: 'setup', label: '
|
|
141
|
-
{ value: 'uninstall', label: '
|
|
142
|
-
{ value: 'exit', label: '
|
|
147
|
+
{ value: 'setup', label: t('cli.actionSetup') },
|
|
148
|
+
{ value: 'uninstall', label: t('cli.actionUninstall') },
|
|
149
|
+
{ value: 'exit', label: t('cli.actionExit') }
|
|
143
150
|
]
|
|
144
151
|
});
|
|
145
|
-
if (isCancel(action) || action === 'exit') dieCancelled('
|
|
152
|
+
if (isCancel(action) || action === 'exit') dieCancelled(t('cli.bye'));
|
|
146
153
|
step = 2;
|
|
147
154
|
}
|
|
148
155
|
|
|
@@ -150,10 +157,10 @@ async function main() {
|
|
|
150
157
|
const targetCtrl = new AbortController();
|
|
151
158
|
const { result: targetResult, wentBack: targetBack } = await withBackspaceBack(targetCtrl, () =>
|
|
152
159
|
select({
|
|
153
|
-
message: `${pc.dim('
|
|
160
|
+
message: `${pc.dim(t('cli.stepFormat', { n: 2 }))} ${t('cli.step2ChooseTarget')}`,
|
|
154
161
|
options: [
|
|
155
|
-
{ value: 'global', label:
|
|
156
|
-
{ value: 'projectOnly', label:
|
|
162
|
+
{ value: 'global', label: t('cli.targetGlobal') },
|
|
163
|
+
{ value: 'projectOnly', label: t('cli.targetProjectOnly').replace(CONFIG_FILENAME, pc.bold(CONFIG_FILENAME)) }
|
|
157
164
|
],
|
|
158
165
|
signal: targetCtrl.signal
|
|
159
166
|
})
|
|
@@ -171,7 +178,7 @@ async function main() {
|
|
|
171
178
|
const pkgsCtrl = new AbortController();
|
|
172
179
|
const { result: pkgsResult, wentBack: pkgsBack } = await withBackspaceBack(pkgsCtrl, () =>
|
|
173
180
|
multiselect({
|
|
174
|
-
message: `${pc.dim('
|
|
181
|
+
message: `${pc.dim(t('cli.stepFormat', { n: 3 }))} ${t('cli.step3SelectPackages')}`,
|
|
175
182
|
options: packageOptions,
|
|
176
183
|
required: true,
|
|
177
184
|
signal: pkgsCtrl.signal
|
|
@@ -189,7 +196,7 @@ async function main() {
|
|
|
189
196
|
if (step === 4) {
|
|
190
197
|
const proceedCtrl = new AbortController();
|
|
191
198
|
const { result: proceedResult, wentBack: proceedBack } = await withBackspaceBack(proceedCtrl, () =>
|
|
192
|
-
confirm({ message: '
|
|
199
|
+
confirm({ message: t('cli.configureNow'), initialValue: true, signal: proceedCtrl.signal })
|
|
193
200
|
);
|
|
194
201
|
if (proceedBack) {
|
|
195
202
|
step = 3;
|
|
@@ -209,10 +216,10 @@ async function main() {
|
|
|
209
216
|
perPackage = { security: null, secrets: null, sound: null, notification: null };
|
|
210
217
|
|
|
211
218
|
const packageDescs = {
|
|
212
|
-
security: '
|
|
213
|
-
secrets: '
|
|
214
|
-
sound: '
|
|
215
|
-
notification: '
|
|
219
|
+
security: t('cli.pkgSecurityHint'),
|
|
220
|
+
secrets: t('cli.pkgSecretsHint'),
|
|
221
|
+
sound: t('cli.pkgSoundHint'),
|
|
222
|
+
notification: t('cli.pkgNotificationHint')
|
|
216
223
|
};
|
|
217
224
|
|
|
218
225
|
const formatPackageList = (/** @type {string | null} */ highlightKey) =>
|
|
@@ -228,19 +235,19 @@ async function main() {
|
|
|
228
235
|
|
|
229
236
|
// ── Step 4/5: configure (highlight current package) ──
|
|
230
237
|
if (selected.includes('security')) {
|
|
231
|
-
note(formatPackageList('security'), `${pc.dim('
|
|
238
|
+
note(formatPackageList('security'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
|
|
232
239
|
perPackage.security = await planSecuritySetup({ action, projectDir, ui: 'umbrella' });
|
|
233
240
|
}
|
|
234
241
|
if (selected.includes('secrets')) {
|
|
235
|
-
note(formatPackageList('secrets'), `${pc.dim('
|
|
242
|
+
note(formatPackageList('secrets'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
|
|
236
243
|
perPackage.secrets = await planSecretsSetup({ action, projectDir, ui: 'umbrella' });
|
|
237
244
|
}
|
|
238
245
|
if (selected.includes('sound')) {
|
|
239
|
-
note(formatPackageList('sound'), `${pc.dim('
|
|
246
|
+
note(formatPackageList('sound'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
|
|
240
247
|
perPackage.sound = await planSoundSetup({ action, projectDir, ui: 'umbrella' });
|
|
241
248
|
}
|
|
242
249
|
if (selected.includes('notification')) {
|
|
243
|
-
note(formatPackageList('notification'), `${pc.dim('
|
|
250
|
+
note(formatPackageList('notification'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
|
|
244
251
|
perPackage.notification = await planNotificationSetup({ action, projectDir, ui: 'umbrella' });
|
|
245
252
|
}
|
|
246
253
|
|
|
@@ -253,38 +260,38 @@ async function main() {
|
|
|
253
260
|
}
|
|
254
261
|
|
|
255
262
|
function summarizePlan(key, plan) {
|
|
256
|
-
if (!plan) return `${key}: (
|
|
257
|
-
if (action === 'uninstall') return `${key}:
|
|
263
|
+
if (!plan) return `${key}: ${t('cli.summarySkipped')}`;
|
|
264
|
+
if (action === 'uninstall') return `${key}: ${t('cli.summaryRemoveHooks')}`;
|
|
258
265
|
|
|
259
266
|
const events = plan.snippetHooks ? Object.keys(plan.snippetHooks) : [];
|
|
260
267
|
const list = events.slice(0, 5);
|
|
261
268
|
const tail = events.length > 5 ? ` +${events.length - 5} more` : '';
|
|
262
|
-
return `${key}: ${events.length}
|
|
269
|
+
return `${key}: ${t('cli.summaryEvents', { count: events.length })}${events.length ? ` (${list.join(', ')}${tail})` : ''}`;
|
|
263
270
|
}
|
|
264
271
|
|
|
265
272
|
note(
|
|
266
273
|
[
|
|
267
|
-
`${pc.dim('
|
|
274
|
+
`${pc.dim(t('cli.stepFormat', { n: 5 }))} ${t('cli.step5Review')}`,
|
|
268
275
|
'',
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
`${t('cli.reviewAction')}: ${pc.bold(action)}`,
|
|
277
|
+
`${t('cli.reviewTarget')}: ${pc.bold(target === 'global' ? t('cli.reviewTargetGlobal') : t('cli.reviewTargetProjectOnly'))}`,
|
|
271
278
|
'',
|
|
272
|
-
`${pc.bold('
|
|
279
|
+
`${pc.bold(t('cli.reviewPackages'))}`,
|
|
273
280
|
...selected.map((k) => ` - ${summarizePlan(k, perPackage[k])}`),
|
|
274
281
|
'',
|
|
275
|
-
`${pc.bold('
|
|
282
|
+
`${pc.bold(t('cli.reviewFiles'))}`,
|
|
276
283
|
...files.map((f) => ` - ${f}`)
|
|
277
284
|
].join('\n'),
|
|
278
|
-
'
|
|
285
|
+
t('cli.step5Review')
|
|
279
286
|
);
|
|
280
287
|
|
|
281
288
|
const applyCtrl = new AbortController();
|
|
282
289
|
const { result: applyResult, wentBack: applyBack } = await withBackspaceBack(applyCtrl, () =>
|
|
283
290
|
select({
|
|
284
|
-
message: '
|
|
291
|
+
message: t('cli.applyChanges'),
|
|
285
292
|
options: [
|
|
286
|
-
{ value: 'yes', label: '
|
|
287
|
-
{ value: 'cancel', label: '
|
|
293
|
+
{ value: 'yes', label: t('cli.applyYes') },
|
|
294
|
+
{ value: 'cancel', label: t('cli.applyCancel') }
|
|
288
295
|
],
|
|
289
296
|
signal: applyCtrl.signal
|
|
290
297
|
})
|
|
@@ -293,7 +300,7 @@ async function main() {
|
|
|
293
300
|
step = 3;
|
|
294
301
|
continue;
|
|
295
302
|
}
|
|
296
|
-
if (isCancel(applyResult) || applyResult === 'cancel') dieCancelled('
|
|
303
|
+
if (isCancel(applyResult) || applyResult === 'cancel') dieCancelled(t('cli.noChanges'));
|
|
297
304
|
|
|
298
305
|
break;
|
|
299
306
|
}
|
|
@@ -301,7 +308,7 @@ async function main() {
|
|
|
301
308
|
if (target === 'projectOnly') {
|
|
302
309
|
// Write project config
|
|
303
310
|
const s = spinner();
|
|
304
|
-
s.start('
|
|
311
|
+
s.start(t('cli.writingProjectConfig'));
|
|
305
312
|
|
|
306
313
|
// perPackage.*.projectConfigSection is shaped for claude-code-hooks.config.json sections.
|
|
307
314
|
const projectCfg = await ensureProjectOnlyConfig(projectDir, selected, {
|
|
@@ -323,12 +330,12 @@ async function main() {
|
|
|
323
330
|
}
|
|
324
331
|
});
|
|
325
332
|
|
|
326
|
-
s.stop('
|
|
333
|
+
s.stop(t('cli.done'));
|
|
327
334
|
|
|
328
|
-
note(JSON.stringify(snippetObj, null, 2), '
|
|
335
|
+
note(JSON.stringify(snippetObj, null, 2), t('cli.pasteSnippet'));
|
|
329
336
|
await maybeWriteSnippet(projectDir, snippetObj);
|
|
330
337
|
|
|
331
|
-
outro(
|
|
338
|
+
outro(`${t('cli.projectConfigWritten')}: ${pc.bold(configFilePath(projectDir))}`);
|
|
332
339
|
return;
|
|
333
340
|
}
|
|
334
341
|
|
|
@@ -336,14 +343,14 @@ async function main() {
|
|
|
336
343
|
const settingsPath = configPathForScope('global', projectDir);
|
|
337
344
|
const res = await readJsonIfExists(settingsPath);
|
|
338
345
|
if (!res.ok) {
|
|
339
|
-
cancel(
|
|
346
|
+
cancel(t('cli.couldNotReadJson', { path: settingsPath }));
|
|
340
347
|
process.exit(1);
|
|
341
348
|
}
|
|
342
349
|
|
|
343
350
|
let settings = res.value;
|
|
344
351
|
|
|
345
352
|
const s = spinner();
|
|
346
|
-
s.start('
|
|
353
|
+
s.start(t('cli.applyingChanges'));
|
|
347
354
|
|
|
348
355
|
for (const key of selected) {
|
|
349
356
|
const plan = perPackage[key];
|
|
@@ -366,8 +373,8 @@ async function main() {
|
|
|
366
373
|
});
|
|
367
374
|
}
|
|
368
375
|
|
|
369
|
-
s.stop('
|
|
370
|
-
outro(
|
|
376
|
+
s.stop(t('cli.done'));
|
|
377
|
+
outro(`${t('cli.saved')}: ${pc.bold(settingsPath)}`);
|
|
371
378
|
}
|
|
372
379
|
|
|
373
380
|
main().catch((err) => {
|