claude-notification-plugin 1.0.82 → 1.0.88

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/install.js CHANGED
@@ -4,17 +4,246 @@ import fs from 'fs';
4
4
  import os from 'os';
5
5
  import path from 'path';
6
6
  import readline from 'readline';
7
+ import { execSync } from 'child_process';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
7
12
 
8
13
  const home = os.homedir();
9
14
  const claudeDir = path.join(home, '.claude');
15
+ const pluginsDir = path.join(claudeDir, 'plugins');
10
16
  const configPath = path.join(claudeDir, 'notifier.config.json');
11
17
  const settingsPath = path.join(claudeDir, 'settings.json');
18
+ const installedPluginsPath = path.join(pluginsDir, 'installed_plugins.json');
19
+ const knownMarketplacesPath = path.join(pluginsDir, 'known_marketplaces.json');
20
+ const marketplacesDir = path.join(pluginsDir, 'marketplaces');
21
+ const RESOLVER_PATH = path.join(claudeDir, 'claude-notify-resolve.js');
12
22
 
13
23
  const HOOK_COMMAND = 'claude-notifier';
24
+ const MARKETPLACE_KEY = 'bazilio-plugins';
25
+ const PLUGIN_KEY = 'claude-notification-plugin@bazilio-plugins';
26
+ const MARKETPLACE_REPO = 'https://github.com/Bazilio-san/claude-plugins.git';
27
+ const MARKETPLACE_GITHUB = 'Bazilio-san/claude-plugins';
28
+
29
+ const CLI_BINS = {
30
+ 'claude-notify-listener': 'bin/listener-cli.js',
31
+ 'claude-notify-install': 'bin/install.js',
32
+ 'claude-notify-uninstall': 'bin/uninstall.js',
33
+ };
34
+
35
+ // ──────────────────────────────────────
36
+ // Plugin registration
37
+ // ──────────────────────────────────────
38
+
39
+ function getVersion () {
40
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
41
+ return pkg.version;
42
+ }
43
+
44
+ function copyToCache (version) {
45
+ const cacheBase = path.join(pluginsDir, 'cache', MARKETPLACE_KEY, 'claude-notification-plugin');
46
+ const dest = path.join(cacheBase, version);
47
+
48
+ if (fs.existsSync(dest)) {
49
+ fs.rmSync(dest, { recursive: true });
50
+ }
51
+ fs.mkdirSync(dest, { recursive: true });
52
+
53
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
54
+ const entries = [...(pkg.files || []), 'package.json', 'package-lock.json'];
55
+
56
+ for (const entry of entries) {
57
+ const src = path.join(PACKAGE_ROOT, entry);
58
+ if (!fs.existsSync(src)) {
59
+ continue;
60
+ }
61
+ const target = path.join(dest, entry);
62
+ const stat = fs.statSync(src);
63
+ if (stat.isDirectory()) {
64
+ fs.cpSync(src, target, { recursive: true });
65
+ } else {
66
+ fs.mkdirSync(path.dirname(target), { recursive: true });
67
+ fs.copyFileSync(src, target);
68
+ }
69
+ }
70
+
71
+ try {
72
+ execSync('npm install --production --ignore-scripts', {
73
+ cwd: dest,
74
+ stdio: 'pipe',
75
+ windowsHide: true,
76
+ });
77
+ } catch {
78
+ console.warn(' Warning: could not install dependencies in plugin cache.');
79
+ }
80
+
81
+ return dest;
82
+ }
83
+
84
+ function updateInstalledPlugins (version, installPath) {
85
+ fs.mkdirSync(pluginsDir, { recursive: true });
86
+
87
+ let data = { version: 2, plugins: {} };
88
+ if (fs.existsSync(installedPluginsPath)) {
89
+ try {
90
+ data = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
91
+ } catch {
92
+ // ignore malformed file
93
+ }
94
+ }
95
+
96
+ const now = new Date().toISOString();
97
+ const existing = data.plugins[PLUGIN_KEY]?.[0];
98
+
99
+ data.plugins[PLUGIN_KEY] = [{
100
+ scope: 'user',
101
+ installPath,
102
+ version,
103
+ installedAt: existing?.installedAt || now,
104
+ lastUpdated: now,
105
+ gitCommitSha: existing?.gitCommitSha || '',
106
+ }];
107
+
108
+ fs.writeFileSync(installedPluginsPath, JSON.stringify(data, null, 2));
109
+ }
110
+
111
+ function updateKnownMarketplaces () {
112
+ let data = {};
113
+ if (fs.existsSync(knownMarketplacesPath)) {
114
+ try {
115
+ data = JSON.parse(fs.readFileSync(knownMarketplacesPath, 'utf-8'));
116
+ } catch {
117
+ // ignore malformed file
118
+ }
119
+ }
120
+
121
+ const installLocation = path.join(marketplacesDir, MARKETPLACE_KEY);
122
+
123
+ data[MARKETPLACE_KEY] = {
124
+ ...data[MARKETPLACE_KEY],
125
+ source: {
126
+ source: 'github',
127
+ repo: MARKETPLACE_GITHUB,
128
+ },
129
+ installLocation,
130
+ lastUpdated: data[MARKETPLACE_KEY]?.lastUpdated || new Date().toISOString(),
131
+ autoUpdate: true,
132
+ };
133
+
134
+ fs.writeFileSync(knownMarketplacesPath, JSON.stringify(data, null, 2));
135
+ }
136
+
137
+ function cloneOrUpdateMarketplace () {
138
+ const dest = path.join(marketplacesDir, MARKETPLACE_KEY);
139
+
140
+ if (fs.existsSync(path.join(dest, '.git'))) {
141
+ try {
142
+ execSync('git pull --ff-only', { cwd: dest, stdio: 'pipe', windowsHide: true });
143
+ } catch {
144
+ // offline or conflict — not fatal
145
+ }
146
+ } else {
147
+ fs.mkdirSync(marketplacesDir, { recursive: true });
148
+ try {
149
+ execSync(`git clone "${MARKETPLACE_REPO}" "${dest}"`, {
150
+ stdio: 'pipe',
151
+ windowsHide: true,
152
+ });
153
+ } catch {
154
+ console.warn(' Warning: could not clone marketplace repo (offline?).');
155
+ }
156
+ }
157
+ }
158
+
159
+ // ──────────────────────────────────────
160
+ // CLI wrapper registration
161
+ // ──────────────────────────────────────
162
+
163
+ function writeResolver () {
164
+ const content = `#!/usr/bin/env node
165
+ 'use strict';
166
+ const fs = require('fs');
167
+ const path = require('path');
168
+ const { execFileSync } = require('child_process');
169
+
170
+ const pluginKey = 'claude-notification-plugin@bazilio-plugins';
171
+ const home = process.env.USERPROFILE || process.env.HOME;
172
+ const regPath = path.join(home, '.claude', 'plugins', 'installed_plugins.json');
173
+
174
+ let installPath;
175
+ try {
176
+ const reg = JSON.parse(fs.readFileSync(regPath, 'utf-8'));
177
+ const entries = reg.plugins[pluginKey];
178
+ if (entries && entries.length) installPath = entries[0].installPath;
179
+ } catch {}
180
+
181
+ if (!installPath) {
182
+ console.error('claude-notification-plugin is not installed.');
183
+ process.exit(1);
184
+ }
14
185
 
15
- // ----------------------
16
- // HELPERS
17
- // ----------------------
186
+ const target = path.join(installPath, process.argv[2]);
187
+ const args = process.argv.slice(3);
188
+
189
+ try {
190
+ execFileSync('node', [target, ...args], { stdio: 'inherit' });
191
+ } catch (e) {
192
+ process.exit(e.status || 1);
193
+ }
194
+ `;
195
+ fs.writeFileSync(RESOLVER_PATH, content, { mode: 0o755 });
196
+ }
197
+
198
+ function findClaudeDir () {
199
+ try {
200
+ const cmd = process.platform === 'win32' ? 'where claude' : 'which claude';
201
+ const result = execSync(cmd, { encoding: 'utf-8', windowsHide: true }).trim();
202
+ const first = result.split('\n')[0].trim();
203
+ return path.dirname(first);
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+
209
+ function registerCli () {
210
+ const binDir = findClaudeDir();
211
+ if (!binDir) {
212
+ console.warn(' Warning: "claude" not found in PATH — CLI wrappers not registered.');
213
+ return;
214
+ }
215
+
216
+ writeResolver();
217
+
218
+ const isWindows = process.platform === 'win32';
219
+ const resolverNative = RESOLVER_PATH.replace(/\//g, path.sep);
220
+ const created = [];
221
+
222
+ for (const [name, relPath] of Object.entries(CLI_BINS)) {
223
+ if (isWindows) {
224
+ const cmdFile = path.join(binDir, `${name}.cmd`);
225
+ const content = `@echo off\r\nnode "${resolverNative}" ${relPath} %*\r\n`;
226
+ fs.writeFileSync(cmdFile, content);
227
+ created.push(cmdFile);
228
+ } else {
229
+ const shFile = path.join(binDir, name);
230
+ const content = `#!/bin/sh\nexec node "${RESOLVER_PATH}" "${relPath}" "$@"\n`;
231
+ fs.writeFileSync(shFile, content, { mode: 0o755 });
232
+ created.push(shFile);
233
+ }
234
+ }
235
+
236
+ if (created.length > 0) {
237
+ console.log(' CLI commands registered:');
238
+ for (const f of created) {
239
+ console.log(` ${f}`);
240
+ }
241
+ }
242
+ }
243
+
244
+ // ──────────────────────────────────────
245
+ // Helpers
246
+ // ──────────────────────────────────────
18
247
 
19
248
  function ask (rl, question) {
20
249
  return new Promise((resolve) => {
@@ -65,11 +294,34 @@ function addHook (settings, event) {
65
294
  }
66
295
  }
67
296
 
68
- // ----------------------
69
- // MAIN
70
- // ----------------------
297
+ // ──────────────────────────────────────
298
+ // Main
299
+ // ──────────────────────────────────────
71
300
 
72
301
  async function main () {
302
+ // 1. Register plugin in Claude Code
303
+ const version = getVersion();
304
+
305
+ console.log('');
306
+ console.log(`Registering plugin v${version} in Claude Code...`);
307
+
308
+ const installPath = copyToCache(version);
309
+ console.log(` Cached: ${installPath}`);
310
+
311
+ updateInstalledPlugins(version, installPath);
312
+ console.log(' Updated installed_plugins.json');
313
+
314
+ updateKnownMarketplaces();
315
+ console.log(' Updated known_marketplaces.json');
316
+
317
+ cloneOrUpdateMarketplace();
318
+ console.log(' Marketplace synced');
319
+
320
+ registerCli();
321
+
322
+ console.log(' Plugin registered.');
323
+
324
+ // 2. Interactive Telegram setup
73
325
  const rl = readline.createInterface({
74
326
  input: process.stdin,
75
327
  output: process.stdout,
@@ -80,7 +332,6 @@ async function main () {
80
332
  console.log('==================================');
81
333
  console.log('');
82
334
 
83
- // Load existing config
84
335
  let existing = {};
85
336
  if (fs.existsSync(configPath)) {
86
337
  try {
@@ -93,7 +344,6 @@ async function main () {
93
344
  const existingToken = existing.telegram?.token || '';
94
345
  const existingChatId = existing.telegram?.chatId || '';
95
346
 
96
- // Telegram setup
97
347
  let token = existingToken;
98
348
  let chatId = existingChatId;
99
349
 
@@ -103,7 +353,7 @@ async function main () {
103
353
  const reuse = await ask(rl, 'Keep existing token? (Y/n): ');
104
354
  if (reuse.toLowerCase() === 'n') {
105
355
  token = await ask(rl, 'New Bot Token: ');
106
- chatId = ''; // reset chatId when token changes
356
+ chatId = '';
107
357
  }
108
358
  } else {
109
359
  const useTelegram = await ask(rl, 'Configure Telegram? (y/N): ');
@@ -112,7 +362,6 @@ async function main () {
112
362
  }
113
363
  }
114
364
 
115
- // Fetch chatId if we have a token but no chatId
116
365
  if (token && !chatId) {
117
366
  console.log('');
118
367
  console.log('Send any message to your bot in Telegram, then press Enter.');
@@ -133,7 +382,7 @@ async function main () {
133
382
 
134
383
  rl.close();
135
384
 
136
- // Create / update config
385
+ // 3. Write config
137
386
  fs.mkdirSync(claudeDir, { recursive: true });
138
387
 
139
388
  const platform = process.platform;
@@ -178,16 +427,13 @@ async function main () {
178
427
  },
179
428
  };
180
429
 
181
- // Merge defaults with existing config (user values take priority)
182
430
  const config = { ...defaults, ...existing };
183
- // Deep-merge nested objects
184
431
  for (const key of Object.keys(defaults)) {
185
432
  if (defaults[key] && typeof defaults[key] === 'object' && !Array.isArray(defaults[key])) {
186
433
  config[key] = { ...defaults[key], ...(existing[key] || {}) };
187
434
  }
188
435
  }
189
436
 
190
- // Apply Telegram credentials from this run (if provided)
191
437
  if (token) {
192
438
  config.telegram.token = token;
193
439
  }
@@ -197,7 +443,7 @@ async function main () {
197
443
 
198
444
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
199
445
 
200
- // Patch settings.json
446
+ // 4. Register hooks
201
447
  let settings = {};
202
448
 
203
449
  if (fs.existsSync(settingsPath)) {
@@ -216,7 +462,7 @@ async function main () {
216
462
 
217
463
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
218
464
 
219
- // Output
465
+ // 5. Summary
220
466
  console.log('');
221
467
  console.log('Installed!');
222
468
  console.log('');
package/bin/uninstall.js CHANGED
@@ -40,8 +40,9 @@ if (fs.existsSync(settingsPath)) {
40
40
  }
41
41
  }
42
42
 
43
- // Remove config and state files
44
- for (const file of [configPath, statePath]) {
43
+ // Remove config, state, and resolver files
44
+ const resolverPath = path.join(claudeDir, 'claude-notify-resolve.js');
45
+ for (const file of [configPath, statePath, resolverPath]) {
45
46
  if (fs.existsSync(file)) {
46
47
  fs.unlinkSync(file);
47
48
  }
@@ -397,7 +397,7 @@ If the worktree doesn't exist, it will be created automatically.
397
397
  5. When Claude finishes → sends the result to Telegram
398
398
  6. If there's a next task in the queue → starts it
399
399
 
400
- ---RFr
400
+ ---
401
401
 
402
402
  ## Projects and worktrees
403
403
 
@@ -847,7 +847,7 @@ exec(`claude -p "${userText}"`)
847
847
 
848
848
  - 10 tasks in the queue per workDir (spam protection)
849
849
  - 50 tasks total (overload protection)
850
- - 10-minute timeout per task (hang protection)
850
+ - 30-minute timeout per task (hang protection)
851
851
 
852
852
  ---
853
853
 
@@ -863,7 +863,7 @@ claude-notify-listener status
863
863
  Check:
864
864
 
865
865
  1. Does the config exist? `cat ~/.claude/notifier.config.json`
866
- 2. Are `telegramToken` and `telegramChatId` present?
866
+ 2. Are `telegram.token` and `telegram.chatId` present?
867
867
  3. Is there a `listener.projects` section?
868
868
  4. Logs: `claude-notify-listener logs`
869
869
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // noinspection UnnecessaryLocalVariableJS
2
3
 
3
4
  import fs from 'fs';
4
5
  import os from 'os';
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "claude-notification-plugin",
3
- "productName": "claude-notification-plugin",
4
- "version": "1.0.82",
5
- "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
- "type": "module",
7
- "engines": {
8
- "node": ">=18.0.0"
9
- },
10
- "files": [
11
- ".claude-plugin/",
12
- "bin/",
13
- "commands/",
14
- "hooks/",
15
- "listener/",
16
- "notifier/",
17
- "README.md",
18
- "LICENSE"
19
- ],
20
- "bin": {
21
- "claude-notify-install": "bin/install.js",
22
- "claude-notify-uninstall": "bin/uninstall.js",
23
- "claude-notifier": "notifier/notifier.js",
24
- "claude-notify-listener": "bin/listener-cli.js",
25
- "claude-notify-register": "bin/register-cli.js"
26
- },
27
- "scripts": {
28
- "lint": "eslint .",
29
- "lint:fix": "eslint --fix ."
30
- },
31
- "keywords": [
32
- "claude",
33
- "claude-code",
34
- "notifications",
35
- "telegram",
36
- "hooks",
37
- "macos",
38
- "linux",
39
- "cross-platform"
40
- ],
41
- "author": {
42
- "name": "Viacheslav Makarov",
43
- "email": "npmjs@bazilio.ru"
44
- },
45
- "license": "MIT",
46
- "repository": {
47
- "type": "git",
48
- "url": "git+https://github.com/Bazilio-san/claude-notification-plugin.git"
49
- },
50
- "homepage": "https://github.com/Bazilio-san/claude-notification-plugin#readme",
51
- "publishConfig": {
52
- "access": "public"
53
- },
54
- "dependencies": {
55
- "node-notifier": "^10.0.1"
56
- },
57
- "devDependencies": {
58
- "eslint-plugin-import": "^2.31.0"
59
- }
60
- }
1
+ {
2
+ "name": "claude-notification-plugin",
3
+ "productName": "claude-notification-plugin",
4
+ "version": "1.0.88",
5
+ "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=18.0.0"
9
+ },
10
+ "files": [
11
+ ".claude-plugin/",
12
+ "bin/",
13
+ "hooks/",
14
+ "listener/",
15
+ "notifier/",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "bin": {
20
+ "claude-notify-install": "bin/install.js",
21
+ "claude-notify-uninstall": "bin/uninstall.js",
22
+ "claude-notifier": "notifier/notifier.js",
23
+ "claude-notify-listener": "bin/listener-cli.js"
24
+ },
25
+ "scripts": {
26
+ "preuninstall": "node bin/uninstall.js",
27
+ "postinstall": "node bin/install.js",
28
+ "lint": "eslint .",
29
+ "lint:fix": "eslint --fix ."
30
+ },
31
+ "keywords": [
32
+ "claude",
33
+ "claude-code",
34
+ "notifications",
35
+ "telegram",
36
+ "hooks",
37
+ "macos",
38
+ "linux",
39
+ "cross-platform"
40
+ ],
41
+ "author": {
42
+ "name": "Viacheslav Makarov",
43
+ "email": "npmjs@bazilio.ru"
44
+ },
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/Bazilio-san/claude-notification-plugin.git"
49
+ },
50
+ "homepage": "https://github.com/Bazilio-san/claude-notification-plugin#readme",
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "dependencies": {
55
+ "node-notifier": "^10.0.1"
56
+ },
57
+ "devDependencies": {
58
+ "eslint-plugin-import": "^2.31.0"
59
+ }
60
+ }
@@ -1,142 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from 'fs';
4
- import path from 'path';
5
- import { execSync } from 'child_process';
6
- import { fileURLToPath } from 'url';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
-
11
- const PLUGIN_ROOT = path.resolve(__dirname, '..');
12
-
13
- const BINS = {
14
- 'claude-notify-listener': 'bin/listener-cli.js',
15
- 'claude-notify-install': 'bin/install.js',
16
- 'claude-notify-uninstall': 'bin/uninstall.js',
17
- };
18
-
19
- function findClaudeDir () {
20
- try {
21
- const cmd = process.platform === 'win32' ? 'where claude' : 'which claude';
22
- const result = execSync(cmd, { encoding: 'utf-8', windowsHide: true }).trim();
23
- // 'where' on Windows may return multiple lines
24
- const first = result.split('\n')[0].trim();
25
- return path.dirname(first);
26
- } catch {
27
- return null;
28
- }
29
- }
30
-
31
- function register () {
32
- const claudeDir = findClaudeDir();
33
- if (!claudeDir) {
34
- console.error('Could not find "claude" in PATH.');
35
- console.error('Make sure Claude Code CLI is installed and available.');
36
- process.exit(1);
37
- }
38
-
39
- const isWindows = process.platform === 'win32';
40
- const created = [];
41
-
42
- for (const [name, relPath] of Object.entries(BINS)) {
43
- const scriptPath = path.join(PLUGIN_ROOT, relPath);
44
-
45
- if (!fs.existsSync(scriptPath)) {
46
- console.warn(` Skip ${name}: ${scriptPath} not found`);
47
- continue;
48
- }
49
-
50
- if (isWindows) {
51
- const cmdFile = path.join(claudeDir, `${name}.cmd`);
52
- const content = `@echo off\r\nnode "${scriptPath}" %*\r\n`;
53
- fs.writeFileSync(cmdFile, content);
54
- created.push(cmdFile);
55
- } else {
56
- const shFile = path.join(claudeDir, name);
57
- const content = `#!/bin/sh\nexec node "${scriptPath}" "$@"\n`;
58
- fs.writeFileSync(shFile, content, { mode: 0o755 });
59
- created.push(shFile);
60
- }
61
- }
62
-
63
- if (created.length > 0) {
64
- console.log('Registered CLI commands:');
65
- for (const f of created) {
66
- console.log(` ${f}`);
67
- }
68
- } else {
69
- console.log('No commands were registered.');
70
- }
71
- }
72
-
73
- function list () {
74
- const claudeDir = findClaudeDir();
75
- if (!claudeDir) {
76
- console.log('Claude CLI not found in PATH.');
77
- return;
78
- }
79
-
80
- const isWindows = process.platform === 'win32';
81
- const ext = isWindows ? '.cmd' : '';
82
- let found = 0;
83
-
84
- for (const name of Object.keys(BINS)) {
85
- const filePath = path.join(claudeDir, `${name}${ext}`);
86
- const exists = fs.existsSync(filePath);
87
- console.log(` ${exists ? '+' : '-'} ${name}${exists ? ` (${filePath})` : ''}`);
88
- if (exists) {
89
- found++;
90
- }
91
- }
92
-
93
- console.log('');
94
- console.log(`${found}/${Object.keys(BINS).length} commands registered in ${claudeDir}`);
95
- }
96
-
97
- function unregister () {
98
- const claudeDir = findClaudeDir();
99
- if (!claudeDir) {
100
- console.log('Claude CLI not found in PATH — nothing to unregister.');
101
- return;
102
- }
103
-
104
- const isWindows = process.platform === 'win32';
105
- const removed = [];
106
-
107
- for (const name of Object.keys(BINS)) {
108
- const ext = isWindows ? '.cmd' : '';
109
- const filePath = path.join(claudeDir, `${name}${ext}`);
110
- if (fs.existsSync(filePath)) {
111
- fs.unlinkSync(filePath);
112
- removed.push(filePath);
113
- }
114
- }
115
-
116
- if (removed.length > 0) {
117
- console.log('Removed CLI commands:');
118
- for (const f of removed) {
119
- console.log(` ${f}`);
120
- }
121
- } else {
122
- console.log('No CLI commands found to remove.');
123
- }
124
- }
125
-
126
- const action = process.argv[2];
127
-
128
- if (action === 'unregister' || action === 'remove') {
129
- unregister();
130
- } else if (action === 'list' || action === 'status') {
131
- list();
132
- } else if (action === 'register' || !action) {
133
- register();
134
- } else {
135
- console.log('Usage: claude-notify-register <register|unregister|list>');
136
- console.log('');
137
- console.log('Commands:');
138
- console.log(' register Register CLI commands next to claude binary (default)');
139
- console.log(' unregister Remove registered CLI commands');
140
- console.log(' list Show registration status of CLI commands');
141
- process.exit(1);
142
- }