@claude-code-hooks/cli 0.1.16 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +54 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-code-hooks/cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
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",
package/src/cli.js CHANGED
@@ -97,7 +97,7 @@ function showWelcome() {
97
97
  process.stdout.write(particleLine(0));
98
98
  process.stdout.write(line + pad);
99
99
  process.stdout.write(pad);
100
- process.stdout.write(` ${icon} ${pc.blue(pc.bold('claude-code-hooks'))}${version ? pc.gray(version) : ''}\n`);
100
+ process.stdout.write(` ${icon} ${pc.blue(pc.bold(t('cli.welcomeTitle')))}${version ? pc.gray(version) : ''}\n`);
101
101
  process.stdout.write(pad);
102
102
  process.stdout.write(` ${pc.brightCyan('Customize Claude Code with zero dependencies')}\n`);
103
103
  process.stdout.write(pad);
@@ -206,9 +206,12 @@ async function main() {
206
206
  const projectDir = process.cwd();
207
207
 
208
208
  showWelcome();
209
- note(t('cli.navHint'), t('cli.navTitle'));
210
209
 
211
- // ── Step 1–3: action, target, packages (Backspace = go to previous step) ──
210
+ /** Build step header: "Step N/5 <title> (ESC exit · Backspace back)" */
211
+ const stepHeader = (n, title) =>
212
+ pc.dim(t('cli.stepHeader', { n, title, suffix: t('cli.stepHeaderSuffix') }));
213
+
214
+ // ── Step 1–5: action, target, packages, configure, review (Backspace = go to previous step) ──
212
215
  let action;
213
216
  let target;
214
217
  let selected;
@@ -226,7 +229,7 @@ async function main() {
226
229
  while (true) {
227
230
  if (step === 1) {
228
231
  action = await select({
229
- message: `${pc.dim(t('cli.stepFormat', { n: 1 }))} ${t('cli.step1ChooseAction')}`,
232
+ message: stepHeader(1, t('cli.step1ChooseAction')),
230
233
  options: [
231
234
  { value: 'setup', label: t('cli.actionSetup') },
232
235
  { value: 'uninstall', label: t('cli.actionUninstall') },
@@ -241,7 +244,7 @@ async function main() {
241
244
  const targetCtrl = new AbortController();
242
245
  const { result: targetResult, wentBack: targetBack } = await withBackspaceBack(targetCtrl, () =>
243
246
  select({
244
- message: `${pc.dim(t('cli.stepFormat', { n: 2 }))} ${t('cli.step2ChooseTarget')}`,
247
+ message: stepHeader(2, t('cli.step2ChooseTarget')),
245
248
  options: [
246
249
  { value: 'global', label: t('cli.targetGlobal') },
247
250
  { value: 'project', label: t('common.scopeProject') },
@@ -263,7 +266,7 @@ async function main() {
263
266
  const pkgsCtrl = new AbortController();
264
267
  const { result: pkgsResult, wentBack: pkgsBack } = await withBackspaceBack(pkgsCtrl, () =>
265
268
  multiselect({
266
- message: `${pc.dim(t('cli.stepFormat', { n: 3 }))} ${t('cli.step3SelectPackages')}`,
269
+ message: stepHeader(3, t('cli.step3SelectPackages')),
267
270
  options: packageOptions,
268
271
  required: true,
269
272
  validate: (v) => (!v || v.length === 0) ? t('common.selectAtLeastOne') : true,
@@ -282,7 +285,7 @@ async function main() {
282
285
  if (step === 4) {
283
286
  const proceedCtrl = new AbortController();
284
287
  const { result: proceedResult, wentBack: proceedBack } = await withBackspaceBack(proceedCtrl, () =>
285
- confirm({ message: t('cli.configureNow'), initialValue: true, active: t('common.yes'), inactive: t('common.no'), signal: proceedCtrl.signal })
288
+ confirm({ message: stepHeader(4, t('cli.configureNow')), initialValue: true, active: t('common.yes'), inactive: t('common.no'), signal: proceedCtrl.signal })
286
289
  );
287
290
  if (proceedBack) {
288
291
  step = 3;
@@ -321,19 +324,19 @@ async function main() {
321
324
 
322
325
  // ── Step 4/5: configure (highlight current package) ──
323
326
  if (selected.includes('security')) {
324
- note(formatPackageList('security'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
327
+ note(formatPackageList('security'), stepHeader(4, t('cli.step4Configure')));
325
328
  perPackage.security = await planSecuritySetup({ action, projectDir, ui: 'umbrella' });
326
329
  }
327
330
  if (selected.includes('secrets')) {
328
- note(formatPackageList('secrets'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
331
+ note(formatPackageList('secrets'), stepHeader(4, t('cli.step4Configure')));
329
332
  perPackage.secrets = await planSecretsSetup({ action, projectDir, ui: 'umbrella' });
330
333
  }
331
334
  if (selected.includes('sound')) {
332
- note(formatPackageList('sound'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
335
+ note(formatPackageList('sound'), stepHeader(4, t('cli.step4Configure')));
333
336
  perPackage.sound = await planSoundSetup({ action, projectDir, ui: 'umbrella' });
334
337
  }
335
338
  if (selected.includes('notification')) {
336
- note(formatPackageList('notification'), `${pc.dim(t('cli.stepFormat', { n: 4 }))} ${t('cli.step4Configure')}`);
339
+ note(formatPackageList('notification'), stepHeader(4, t('cli.step4Configure')));
337
340
  perPackage.notification = await planNotificationSetup({ action, projectDir, ui: 'umbrella' });
338
341
  }
339
342
 
@@ -355,10 +358,11 @@ async function main() {
355
358
 
356
359
  note(
357
360
  [
358
- `${pc.dim(t('cli.stepFormat', { n: 5 }))} ${t('cli.step5Review')}`,
361
+ stepHeader(5, t('cli.step5Review')),
359
362
  '',
360
- `${t('cli.reviewAction')}: ${pc.bold(action)}`,
361
- `${t('cli.reviewTarget')}: ${pc.bold(
363
+ `${pc.bold(t('cli.reviewSectionActionTarget'))}`,
364
+ ` ${t('cli.reviewAction')}: ${pc.bold(action)}`,
365
+ ` ${t('cli.reviewTarget')}: ${pc.bold(
362
366
  target === 'global' ? t('cli.reviewTargetGlobal') :
363
367
  target === 'project' ? t('cli.reviewTargetProject') :
364
368
  t('cli.reviewTargetProjectLocal')
@@ -373,10 +377,10 @@ async function main() {
373
377
  t('cli.step5Review')
374
378
  );
375
379
 
376
- const applyCtrl = new AbortController();
380
+ const applyCtrl = new AbortController();
377
381
  const { result: applyResult, wentBack: applyBack } = await withBackspaceBack(applyCtrl, () =>
378
382
  select({
379
- message: t('cli.applyChanges'),
383
+ message: stepHeader(5, t('cli.applyChanges')),
380
384
  options: [
381
385
  { value: 'yes', label: t('cli.applyYes') },
382
386
  { value: 'cancel', label: t('cli.applyCancel') }
@@ -395,34 +399,40 @@ async function main() {
395
399
 
396
400
  // Global / project / projectLocal apply: read settings.json, apply transforms, write once.
397
401
  const settingsPath = configPathForScope(target, projectDir);
402
+ const pkgLabels = {
403
+ security: t('cli.pkgSecurity'),
404
+ secrets: t('cli.pkgSecrets'),
405
+ sound: t('cli.pkgSound'),
406
+ notification: t('cli.pkgNotification')
407
+ };
408
+ const s = spinner();
409
+ s.start(t('cli.applyingChanges'));
410
+
398
411
  const res = await readJsonIfExists(settingsPath);
399
412
  if (!res.ok) {
413
+ s.stop(t('cli.done'));
400
414
  cancel(t('cli.couldNotReadJson', { path: settingsPath }));
401
415
  process.exit(1);
402
416
  }
403
-
404
417
  let settings = res.value;
405
418
 
406
- const s = spinner();
407
- s.start(t('cli.applyingChanges'));
408
-
409
419
  for (const key of selected) {
410
420
  const plan = perPackage[key];
411
421
  if (!plan) continue;
422
+ const pkgName = pkgLabels[key] ?? key;
423
+ s.message(t('cli.applyStepApplyPackage', { packageName: pkgName }));
412
424
  settings = await plan.applyToSettings(settings);
413
425
  }
414
426
 
415
- // Remove legacy claude-sound hooks (from old standalone package) to avoid duplicates
427
+ s.message(t('cli.applyStepWriteSettings'));
416
428
  settings = removeLegacyClaudeSoundHooks(settings);
417
-
418
429
  await writeJson(settingsPath, settings);
419
-
420
430
  if (target === 'projectLocal') {
421
431
  await ensureGitignoreLocalEntry(projectDir);
422
432
  }
423
433
 
424
- // Update project config only on setup.
425
434
  if (action === 'setup') {
435
+ s.message(t('cli.applyStepWriteProjectConfig'));
426
436
  await ensureProjectOnlyConfig(projectDir, selected, {
427
437
  security: perPackage.security?.projectConfigSection,
428
438
  secrets: perPackage.secrets?.projectConfigSection,
@@ -430,9 +440,27 @@ async function main() {
430
440
  notification: perPackage.notification?.projectConfigSection
431
441
  });
432
442
  }
443
+ s.stop('');
444
+
445
+ // Build contextual completion note
446
+ const traits = [];
447
+ if (selected.includes('security') || selected.includes('secrets')) traits.push(t('cli.doneTraitSafer'));
448
+ if (selected.includes('sound')) traits.push(t('cli.doneTraitLouder'));
449
+ if (selected.includes('notification')) traits.push(t('cli.doneTraitAttentive'));
450
+
451
+ const lines = [];
452
+ lines.push(`${t('cli.saved')}: ${pc.cyan(settingsPath)}`);
453
+ lines.push(`${t('cli.reviewPackages')}: ${selected.map((k) => pc.green(pkgLabels[k] ?? k)).join(', ')}`);
454
+
455
+ if (traits.length > 0) {
456
+ const joined = traits.length === 1
457
+ ? traits[0]
458
+ : traits.slice(0, -1).join(', ') + ' & ' + traits[traits.length - 1];
459
+ lines.push('');
460
+ lines.push(pc.bold(t('cli.doneWithTraits', { traits: joined })));
461
+ }
433
462
 
434
- s.stop(t('cli.done'));
435
- outro(`${t('cli.saved')}: ${pc.bold(settingsPath)}`);
463
+ note(lines.join('\n'), t('cli.doneNoteTitle'));
436
464
  }
437
465
 
438
466
  main().catch((err) => {