claude-notification-plugin 1.1.74 → 1.1.76
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/.claude-plugin/plugin.json +1 -1
- package/bin/constants.js +26 -0
- package/bin/listener-cli.js +26 -16
- package/commit-sha +1 -1
- package/listener/jsonl-reader.js +18 -6
- package/listener/listener.js +72 -7
- package/listener/message-parser.js +6 -3
- package/notifier/notifier.js +26 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-notification-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.76",
|
|
4
4
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Viacheslav Makarov",
|
package/bin/constants.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
1
2
|
import os from 'os';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
|
|
@@ -39,6 +40,31 @@ export const SHORTCUT_DIR = path.join(
|
|
|
39
40
|
export const SHORTCUT_PATH = path.join(SHORTCUT_DIR, SHORTCUT_NAME);
|
|
40
41
|
export const APP_ID = 'Claude Notify';
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Find the alias of the project marked as default (isDefault: true).
|
|
45
|
+
* Falls back to the first project key if none is marked.
|
|
46
|
+
*/
|
|
47
|
+
export function getDefaultProject (projects) {
|
|
48
|
+
if (!projects || typeof projects !== 'object') {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
for (const [alias, proj] of Object.entries(projects)) {
|
|
52
|
+
if (typeof proj === 'object' && proj.isDefault) {
|
|
53
|
+
return alias;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Fallback: first project
|
|
57
|
+
const keys = Object.keys(projects);
|
|
58
|
+
return keys.length > 0 ? keys[0] : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Save config object back to CONFIG_PATH.
|
|
63
|
+
*/
|
|
64
|
+
export function saveConfig (config) {
|
|
65
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
66
|
+
}
|
|
67
|
+
|
|
42
68
|
// Plugin identity
|
|
43
69
|
export const HOOK_COMMAND = 'claude-notify';
|
|
44
70
|
export const MARKETPLACE_KEY = 'bazilio-plugins';
|
package/bin/listener-cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import readline from 'readline';
|
|
|
6
6
|
import { spawn, execSync } from 'child_process';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import {
|
|
9
|
-
HOME, CLAUDE_DIR, CONFIG_PATH, PID_PATH, LISTENER_LOG_FILENAME,
|
|
9
|
+
HOME, CLAUDE_DIR, CONFIG_PATH, PID_PATH, LISTENER_LOG_FILENAME, getDefaultProject,
|
|
10
10
|
} from './constants.js';
|
|
11
11
|
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -116,7 +116,7 @@ async function startDaemon () {
|
|
|
116
116
|
console.error(JSON.stringify({
|
|
117
117
|
listener: {
|
|
118
118
|
projects: {
|
|
119
|
-
|
|
119
|
+
myproject: { path: '/path/to/your/project', isDefault: true },
|
|
120
120
|
},
|
|
121
121
|
},
|
|
122
122
|
}, null, 2));
|
|
@@ -453,7 +453,10 @@ async function setupListener () {
|
|
|
453
453
|
maxTotalTasks: L.maxTotalTasks ?? 50,
|
|
454
454
|
logDir: L.logDir || path.join(HOME, '.claude'),
|
|
455
455
|
taskLogDir: L.taskLogDir || path.join(HOME, '.claude'),
|
|
456
|
-
projectPath:
|
|
456
|
+
projectPath: (() => {
|
|
457
|
+
const defAlias = getDefaultProject(L.projects);
|
|
458
|
+
return defAlias ? (L.projects[defAlias]?.path || '') : '';
|
|
459
|
+
})(),
|
|
457
460
|
};
|
|
458
461
|
|
|
459
462
|
const rl = readline.createInterface({
|
|
@@ -520,30 +523,41 @@ Permission mode for claude -p (tools access):
|
|
|
520
523
|
|
|
521
524
|
// --- Default project ---
|
|
522
525
|
console.log('');
|
|
523
|
-
const projectInput = await ask(rl, `Default project path [${defaults.projectPath || '(none)'}]: `);
|
|
524
|
-
const rawProjectPath = projectInput || defaults.projectPath;
|
|
525
|
-
|
|
526
526
|
L.projects = L.projects || {};
|
|
527
|
+
const currentDefaultAlias = getDefaultProject(L.projects);
|
|
528
|
+
const currentDefaultPath = currentDefaultAlias ? (L.projects[currentDefaultAlias]?.path || '') : '';
|
|
529
|
+
|
|
530
|
+
const projectInput = await ask(rl, `Default project path [${currentDefaultPath || '(none)'}]: `);
|
|
531
|
+
const rawProjectPath = projectInput || currentDefaultPath;
|
|
527
532
|
let hasValidProject = false;
|
|
528
533
|
|
|
529
534
|
if (rawProjectPath) {
|
|
530
535
|
const validatedPath = await validateProjectPath(rl, rawProjectPath);
|
|
531
536
|
if (validatedPath) {
|
|
532
|
-
|
|
533
|
-
|
|
537
|
+
let aliasForDefault = currentDefaultAlias;
|
|
538
|
+
if (!aliasForDefault) {
|
|
539
|
+
const aliasInput = await ask(rl, 'Alias for default project: ');
|
|
540
|
+
aliasForDefault = aliasInput && isValidAlias(aliasInput) ? aliasInput : 'main';
|
|
541
|
+
}
|
|
542
|
+
L.projects[aliasForDefault] = L.projects[aliasForDefault] || {};
|
|
543
|
+
L.projects[aliasForDefault].path = validatedPath;
|
|
544
|
+
// Set isDefault on this project, clear from others
|
|
545
|
+
for (const proj of Object.values(L.projects)) {
|
|
546
|
+
if (typeof proj === 'object') {
|
|
547
|
+
delete proj.isDefault;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
L.projects[aliasForDefault].isDefault = true;
|
|
534
551
|
hasValidProject = true;
|
|
535
552
|
} else {
|
|
536
|
-
delete L.projects.default;
|
|
537
553
|
console.log(' \u26a0 Default project will not be set. Listener will not start without at least one project.');
|
|
538
554
|
}
|
|
539
555
|
} else {
|
|
540
|
-
delete L.projects.default;
|
|
541
556
|
console.log(' \u26a0 No default project configured. Listener will not start without at least one project.');
|
|
542
557
|
}
|
|
543
558
|
|
|
544
559
|
// --- Additional projects loop ---
|
|
545
|
-
|
|
546
|
-
const existingAliases = Object.keys(L.projects).filter(a => a !== 'default');
|
|
560
|
+
const existingAliases = Object.keys(L.projects);
|
|
547
561
|
if (existingAliases.length > 0) {
|
|
548
562
|
console.log(`\nExisting projects: ${existingAliases.join(', ')}`);
|
|
549
563
|
}
|
|
@@ -563,10 +577,6 @@ Permission mode for claude -p (tools access):
|
|
|
563
577
|
console.log(' \u26a0 Alias cannot be empty.');
|
|
564
578
|
continue;
|
|
565
579
|
}
|
|
566
|
-
if (alias === 'default') {
|
|
567
|
-
console.log(' \u26a0 "default" is reserved. Choose a different name.');
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
580
|
if (!isValidAlias(alias)) {
|
|
571
581
|
console.log(' \u26a0 Invalid alias. Allowed characters: a-z, A-Z, 0-9, -, _');
|
|
572
582
|
continue;
|
package/commit-sha
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8d2fc918805c6a42eb0158fbc60e35c34761ed82
|
package/listener/jsonl-reader.js
CHANGED
|
@@ -229,15 +229,27 @@ function formatToolUse (tool) {
|
|
|
229
229
|
: (typeof input.glob === 'string' ? input.glob : '');
|
|
230
230
|
|
|
231
231
|
const flags = [];
|
|
232
|
-
if (input['-n'])
|
|
233
|
-
|
|
232
|
+
if (input['-n']) {
|
|
233
|
+
flags.push('-n');
|
|
234
|
+
}
|
|
235
|
+
if (input['-C']) {
|
|
236
|
+
flags.push(`-C ${input['-C']}`);
|
|
237
|
+
}
|
|
234
238
|
if (!input['-C'] && (typeof input.context === 'number' || typeof input.context === 'string')) {
|
|
235
239
|
flags.push(`-C ${input.context}`);
|
|
236
240
|
}
|
|
237
|
-
if (input['-i'])
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (input
|
|
241
|
+
if (input['-i']) {
|
|
242
|
+
flags.push('-i');
|
|
243
|
+
}
|
|
244
|
+
if (input['-A']) {
|
|
245
|
+
flags.push(`-A ${input['-A']}`);
|
|
246
|
+
}
|
|
247
|
+
if (input['-B']) {
|
|
248
|
+
flags.push(`-B ${input['-B']}`);
|
|
249
|
+
}
|
|
250
|
+
if (input.head_limit) {
|
|
251
|
+
flags.push(`head ${input.head_limit}`);
|
|
252
|
+
}
|
|
241
253
|
const flagStr = flags.length ? ` ${flags.join(' ')}` : '';
|
|
242
254
|
|
|
243
255
|
return where
|
package/listener/listener.js
CHANGED
|
@@ -11,7 +11,7 @@ import { WorkQueue } from './work-queue.js';
|
|
|
11
11
|
import { PtyRunner } from './pty-runner.js';
|
|
12
12
|
import { WorktreeManager } from './worktree-manager.js';
|
|
13
13
|
import { parseMessage, parseTarget } from './message-parser.js';
|
|
14
|
-
import { CLAUDE_DIR, CONFIG_PATH, LISTENER_LOG_FILENAME } from '../bin/constants.js';
|
|
14
|
+
import { CLAUDE_DIR, CONFIG_PATH, LISTENER_LOG_FILENAME, getDefaultProject, saveConfig } from '../bin/constants.js';
|
|
15
15
|
import { JsonlReader, resolveJsonlPath, resolveJsonlByMtime } from './jsonl-reader.js';
|
|
16
16
|
|
|
17
17
|
// ----------------------
|
|
@@ -514,6 +514,8 @@ async function handleCommand (cmd, args) {
|
|
|
514
514
|
return handleNewSession(args);
|
|
515
515
|
case '/projects':
|
|
516
516
|
return handleProjects();
|
|
517
|
+
case '/setdefault':
|
|
518
|
+
return handleSetDefault(args);
|
|
517
519
|
case '/worktrees':
|
|
518
520
|
return handleWorktrees(args);
|
|
519
521
|
case '/worktree':
|
|
@@ -652,7 +654,7 @@ function handleQueue () {
|
|
|
652
654
|
|
|
653
655
|
async function handleCancel (args) {
|
|
654
656
|
const target = parseTarget(args);
|
|
655
|
-
const projectAlias = target?.project ||
|
|
657
|
+
const projectAlias = target?.project || getDefaultProject(listenerConfig.projects);
|
|
656
658
|
const branch = target?.branch || null;
|
|
657
659
|
|
|
658
660
|
let workDir;
|
|
@@ -703,7 +705,7 @@ function handleDrop (args) {
|
|
|
703
705
|
|
|
704
706
|
function handleClear (args) {
|
|
705
707
|
const target = parseTarget(args);
|
|
706
|
-
const projectAlias = target?.project ||
|
|
708
|
+
const projectAlias = target?.project || getDefaultProject(listenerConfig.projects);
|
|
707
709
|
const branch = target?.branch || null;
|
|
708
710
|
|
|
709
711
|
let workDir;
|
|
@@ -726,7 +728,7 @@ function handleClear (args) {
|
|
|
726
728
|
|
|
727
729
|
function handleNewSession (args) {
|
|
728
730
|
const target = parseTarget(args);
|
|
729
|
-
const projectAlias = target?.project ||
|
|
731
|
+
const projectAlias = target?.project || getDefaultProject(listenerConfig.projects);
|
|
730
732
|
const branch = target?.branch || null;
|
|
731
733
|
|
|
732
734
|
let workDir;
|
|
@@ -751,10 +753,12 @@ function handleNewSession (args) {
|
|
|
751
753
|
|
|
752
754
|
function handleProjects () {
|
|
753
755
|
const projects = listenerConfig.projects;
|
|
756
|
+
const defaultAlias = getDefaultProject(projects);
|
|
754
757
|
let text = '📂 <b>Projects:</b>\n';
|
|
755
758
|
for (const [alias, proj] of Object.entries(projects)) {
|
|
756
759
|
const projPath = typeof proj === 'string' ? proj : proj.path;
|
|
757
|
-
|
|
760
|
+
const icon = alias === defaultAlias ? '🏠 ' : '';
|
|
761
|
+
text += `\n${icon}<b>&${escapeHtml(alias)}</b> → <code>${escapeHtml(projPath)}</code>`;
|
|
758
762
|
const worktrees = typeof proj === 'object' ? proj.worktrees : null;
|
|
759
763
|
if (worktrees && Object.keys(worktrees).length > 0) {
|
|
760
764
|
for (const [branch, wtPath] of Object.entries(worktrees)) {
|
|
@@ -762,7 +766,63 @@ function handleProjects () {
|
|
|
762
766
|
}
|
|
763
767
|
}
|
|
764
768
|
}
|
|
765
|
-
|
|
769
|
+
|
|
770
|
+
const buttons = [];
|
|
771
|
+
// "Set Default" button
|
|
772
|
+
buttons.push([{ text: '🏠 Set Default', callback_data: '/setdefault' }]);
|
|
773
|
+
|
|
774
|
+
return { text, replyMarkup: { inline_keyboard: buttons } };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function handleSetDefault (args) {
|
|
778
|
+
const projects = listenerConfig.projects;
|
|
779
|
+
|
|
780
|
+
// No args — show inline keyboard with project list
|
|
781
|
+
if (!args || !args.trim()) {
|
|
782
|
+
const defaultAlias = getDefaultProject(projects);
|
|
783
|
+
const buttons = [];
|
|
784
|
+
for (const [alias, proj] of Object.entries(projects)) {
|
|
785
|
+
const projPath = typeof proj === 'string' ? proj : proj.path;
|
|
786
|
+
const icon = alias === defaultAlias ? '🏠 ' : '';
|
|
787
|
+
buttons.push([{
|
|
788
|
+
text: `${icon}${alias} — ${projPath}`,
|
|
789
|
+
callback_data: `/setdefault ${alias}`,
|
|
790
|
+
}]);
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
text: '🏠 <b>Select default project:</b>',
|
|
794
|
+
replyMarkup: { inline_keyboard: buttons },
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Args provided — set the default
|
|
799
|
+
const alias = args.trim();
|
|
800
|
+
if (!projects[alias]) {
|
|
801
|
+
return `❌ Project "<b>${escapeHtml(alias)}</b>" not found. Use /projects to list.`;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Clear isDefault from all projects, set on chosen
|
|
805
|
+
for (const proj of Object.values(projects)) {
|
|
806
|
+
if (typeof proj === 'object') {
|
|
807
|
+
delete proj.isDefault;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
const proj = projects[alias];
|
|
811
|
+
if (typeof proj === 'object') {
|
|
812
|
+
proj.isDefault = true;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Persist to config file
|
|
816
|
+
try {
|
|
817
|
+
saveConfig(config);
|
|
818
|
+
logger.info(`Default project changed to "${alias}"`);
|
|
819
|
+
} catch (err) {
|
|
820
|
+
logger.error(`Failed to save config: ${err.message}`);
|
|
821
|
+
return `❌ Failed to save config: ${escapeHtml(err.message)}`;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const projPath = typeof proj === 'string' ? proj : proj.path;
|
|
825
|
+
return `✅ Default project: <b>&${escapeHtml(alias)}</b> → <code>${escapeHtml(projPath)}</code>`;
|
|
766
826
|
}
|
|
767
827
|
|
|
768
828
|
function handleWorktrees (args) {
|
|
@@ -931,6 +991,9 @@ const MENU_KEYBOARD = {
|
|
|
931
991
|
[
|
|
932
992
|
{ text: '📜 History', callback_data: '/history' },
|
|
933
993
|
{ text: '🖥 PTY', callback_data: '/pty' },
|
|
994
|
+
{ text: '🏠 Default', callback_data: '/setdefault' },
|
|
995
|
+
],
|
|
996
|
+
[
|
|
934
997
|
{ text: '📖 Help', callback_data: '/help' },
|
|
935
998
|
],
|
|
936
999
|
],
|
|
@@ -948,6 +1011,7 @@ function handleHelp () {
|
|
|
948
1011
|
/clear &project[/branch] — clear queue + reset session
|
|
949
1012
|
/newsession [&project[/branch]] — reset session (keep queue)
|
|
950
1013
|
/projects — list projects
|
|
1014
|
+
/setdefault — change default project
|
|
951
1015
|
/worktrees &project — project worktrees
|
|
952
1016
|
/worktree &project/branch — create worktree
|
|
953
1017
|
/rmworktree &project/branch — remove worktree
|
|
@@ -1065,7 +1129,7 @@ async function mainLoop () {
|
|
|
1065
1129
|
await poller.answerCallbackQuery(msg.callbackQueryId);
|
|
1066
1130
|
}
|
|
1067
1131
|
|
|
1068
|
-
const parsed = parseMessage(msg.text);
|
|
1132
|
+
const parsed = parseMessage(msg.text, getDefaultProject(listenerConfig.projects));
|
|
1069
1133
|
if (!parsed) {
|
|
1070
1134
|
continue;
|
|
1071
1135
|
}
|
|
@@ -1099,6 +1163,7 @@ async function mainLoop () {
|
|
|
1099
1163
|
{ command: 'status', description: 'Status of all projects' },
|
|
1100
1164
|
{ command: 'queue', description: 'Show all queues' },
|
|
1101
1165
|
{ command: 'projects', description: 'List projects' },
|
|
1166
|
+
{ command: 'setdefault', description: 'Change default project' },
|
|
1102
1167
|
{ command: 'history', description: 'Recent task history' },
|
|
1103
1168
|
{ command: 'pty', description: 'PTY session diagnostics' },
|
|
1104
1169
|
{ command: 'help', description: 'Show all commands' },
|
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* /command args → { type: 'command', cmd, args }
|
|
8
8
|
* &project/branch text → { type: 'task', project, branch, text }
|
|
9
9
|
* &project text → { type: 'task', project, branch: null, text }
|
|
10
|
-
* text → { type: 'task', project:
|
|
10
|
+
* text → { type: 'task', project: <defaultProject>, branch: null, text }
|
|
11
11
|
*
|
|
12
12
|
* Any /word is treated as a command (known or unknown).
|
|
13
13
|
* Project designation uses & prefix: &project or &project/branch.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} text - The message text.
|
|
16
|
+
* @param {string} [defaultProject] - Alias of the default project (used for plain text tasks).
|
|
14
17
|
*/
|
|
15
|
-
export function parseMessage (text) {
|
|
18
|
+
export function parseMessage (text, defaultProject) {
|
|
16
19
|
if (!text || typeof text !== 'string') {
|
|
17
20
|
return null;
|
|
18
21
|
}
|
|
@@ -61,7 +64,7 @@ export function parseMessage (text) {
|
|
|
61
64
|
// Plain text → default project
|
|
62
65
|
return {
|
|
63
66
|
type: 'task',
|
|
64
|
-
project: 'default',
|
|
67
|
+
project: defaultProject || 'default',
|
|
65
68
|
branch: null,
|
|
66
69
|
text: trimmed,
|
|
67
70
|
};
|
package/notifier/notifier.js
CHANGED
|
@@ -26,6 +26,28 @@ function debugLog (config, ...args) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function normalizePath (p) {
|
|
30
|
+
return path.resolve(p).replace(/\\/g, '/').toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveProjectName (cwd, config) {
|
|
34
|
+
const fallback = path.basename(cwd);
|
|
35
|
+
const projects = config?.listenerProjects;
|
|
36
|
+
if (!projects || typeof projects !== 'object') {
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
const normalizedCwd = normalizePath(cwd);
|
|
40
|
+
for (const entry of Object.values(projects)) {
|
|
41
|
+
if (!entry?.path) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (normalizedCwd === normalizePath(entry.path) && entry.name) {
|
|
45
|
+
return entry.name;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return fallback;
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
function getBranch (cwd) {
|
|
30
52
|
try {
|
|
31
53
|
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
@@ -98,6 +120,9 @@ function loadConfig () {
|
|
|
98
120
|
if (typeof user.webhookUrl === 'string') {
|
|
99
121
|
config.webhookUrl = user.webhookUrl;
|
|
100
122
|
}
|
|
123
|
+
if (user.listener?.projects) {
|
|
124
|
+
config.listenerProjects = user.listener.projects;
|
|
125
|
+
}
|
|
101
126
|
} catch {
|
|
102
127
|
// ignore malformed config
|
|
103
128
|
}
|
|
@@ -720,7 +745,7 @@ process.stdin.on('end', async () => {
|
|
|
720
745
|
|
|
721
746
|
const eventType = event.hook_event_name || 'unknown';
|
|
722
747
|
const cwd = event.cwd || process.cwd();
|
|
723
|
-
const project =
|
|
748
|
+
const project = resolveProjectName(cwd, config);
|
|
724
749
|
const sessionId = event.session_id || 'default';
|
|
725
750
|
|
|
726
751
|
const disabled = isNotifierDisabled();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-notification-plugin",
|
|
3
3
|
"productName": "claude-notification-plugin",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.76",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|