@stackmemoryai/stackmemory 0.5.6 → 0.5.8
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/dist/cli/commands/auto-background.js +180 -0
- package/dist/cli/commands/auto-background.js.map +7 -0
- package/dist/cli/commands/sms-notify.js +276 -0
- package/dist/cli/commands/sms-notify.js.map +7 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +2 -2
- package/dist/hooks/auto-background.js +146 -0
- package/dist/hooks/auto-background.js.map +7 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +2 -2
- package/dist/hooks/sms-notify.js +286 -0
- package/dist/hooks/sms-notify.js.map +7 -0
- package/dist/hooks/sms-webhook.js +122 -0
- package/dist/hooks/sms-webhook.js.map +7 -0
- package/package.json +1 -1
- package/scripts/install-auto-background-hook.sh +144 -0
- package/scripts/install-notify-hook.sh +84 -0
- package/templates/claude-hooks/auto-background-hook.js +156 -0
- package/templates/claude-hooks/notify-review-hook.js +171 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code hook for SMS notifications on review-ready events
|
|
4
|
+
*
|
|
5
|
+
* Triggers notifications when:
|
|
6
|
+
* - PR is created
|
|
7
|
+
* - Task is marked complete
|
|
8
|
+
* - User explicitly requests notification
|
|
9
|
+
*
|
|
10
|
+
* Install: stackmemory notify install-hook
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const https = require('https');
|
|
17
|
+
|
|
18
|
+
const CONFIG_PATH = path.join(os.homedir(), '.stackmemory', 'sms-notify.json');
|
|
19
|
+
|
|
20
|
+
function loadConfig() {
|
|
21
|
+
try {
|
|
22
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
23
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
} catch {}
|
|
26
|
+
return { enabled: false };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function shouldNotify(toolName, toolInput, output) {
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
if (!config.enabled) return null;
|
|
32
|
+
|
|
33
|
+
// Check for PR creation
|
|
34
|
+
if (toolName === 'Bash') {
|
|
35
|
+
const cmd = toolInput?.command || '';
|
|
36
|
+
const out = output || '';
|
|
37
|
+
|
|
38
|
+
// gh pr create
|
|
39
|
+
if (cmd.includes('gh pr create') && out.includes('github.com')) {
|
|
40
|
+
const prUrl = out.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/)?.[0];
|
|
41
|
+
return {
|
|
42
|
+
type: 'review_ready',
|
|
43
|
+
title: 'PR Ready for Review',
|
|
44
|
+
message: prUrl || 'Pull request created successfully',
|
|
45
|
+
options: ['Approve', 'Review', 'Skip'],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// npm publish
|
|
50
|
+
if (cmd.includes('npm publish') && out.includes('+')) {
|
|
51
|
+
const pkg = out.match(/\+ ([^\s]+)/)?.[1];
|
|
52
|
+
return {
|
|
53
|
+
type: 'task_complete',
|
|
54
|
+
title: 'Package Published',
|
|
55
|
+
message: pkg ? `Published ${pkg}` : 'Package published successfully',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Deployment
|
|
60
|
+
if (
|
|
61
|
+
(cmd.includes('deploy') || cmd.includes('railway up')) &&
|
|
62
|
+
(out.includes('deployed') || out.includes('success'))
|
|
63
|
+
) {
|
|
64
|
+
return {
|
|
65
|
+
type: 'review_ready',
|
|
66
|
+
title: 'Deployment Complete',
|
|
67
|
+
message: 'Ready for verification',
|
|
68
|
+
options: ['Verify', 'Rollback', 'Skip'],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sendNotification(notification) {
|
|
77
|
+
const config = loadConfig();
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
!config.accountSid ||
|
|
81
|
+
!config.authToken ||
|
|
82
|
+
!config.fromNumber ||
|
|
83
|
+
!config.toNumber
|
|
84
|
+
) {
|
|
85
|
+
// Try env vars
|
|
86
|
+
const sid = process.env.TWILIO_ACCOUNT_SID;
|
|
87
|
+
const token = process.env.TWILIO_AUTH_TOKEN;
|
|
88
|
+
const from = process.env.TWILIO_FROM_NUMBER;
|
|
89
|
+
const to = process.env.TWILIO_TO_NUMBER;
|
|
90
|
+
|
|
91
|
+
if (!sid || !token || !from || !to) {
|
|
92
|
+
console.error('[notify-hook] Missing Twilio credentials');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
config.accountSid = sid;
|
|
97
|
+
config.authToken = token;
|
|
98
|
+
config.fromNumber = from;
|
|
99
|
+
config.toNumber = to;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let message = `${notification.title}\n\n${notification.message}`;
|
|
103
|
+
|
|
104
|
+
if (notification.options) {
|
|
105
|
+
message += '\n\n';
|
|
106
|
+
notification.options.forEach((opt, i) => {
|
|
107
|
+
message += `${i + 1}. ${opt}\n`;
|
|
108
|
+
});
|
|
109
|
+
message += '\nReply with number to select';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const postData = new URLSearchParams({
|
|
113
|
+
From: config.fromNumber,
|
|
114
|
+
To: config.toNumber,
|
|
115
|
+
Body: message,
|
|
116
|
+
}).toString();
|
|
117
|
+
|
|
118
|
+
const options = {
|
|
119
|
+
hostname: 'api.twilio.com',
|
|
120
|
+
port: 443,
|
|
121
|
+
path: `/2010-04-01/Accounts/${config.accountSid}/Messages.json`,
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: {
|
|
124
|
+
Authorization:
|
|
125
|
+
'Basic ' +
|
|
126
|
+
Buffer.from(`${config.accountSid}:${config.authToken}`).toString(
|
|
127
|
+
'base64'
|
|
128
|
+
),
|
|
129
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
130
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const req = https.request(options, (res) => {
|
|
135
|
+
if (res.statusCode === 201) {
|
|
136
|
+
console.error(`[notify-hook] Sent: ${notification.title}`);
|
|
137
|
+
} else {
|
|
138
|
+
console.error(`[notify-hook] Failed: ${res.statusCode}`);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
req.on('error', (e) => {
|
|
143
|
+
console.error(`[notify-hook] Error: ${e.message}`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
req.write(postData);
|
|
147
|
+
req.end();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Read hook input from stdin (post-tool-use hook)
|
|
151
|
+
let input = '';
|
|
152
|
+
process.stdin.setEncoding('utf8');
|
|
153
|
+
process.stdin.on('data', (chunk) => (input += chunk));
|
|
154
|
+
process.stdin.on('end', () => {
|
|
155
|
+
try {
|
|
156
|
+
const hookData = JSON.parse(input);
|
|
157
|
+
const { tool_name, tool_input, tool_output } = hookData;
|
|
158
|
+
|
|
159
|
+
const notification = shouldNotify(tool_name, tool_input, tool_output);
|
|
160
|
+
|
|
161
|
+
if (notification) {
|
|
162
|
+
sendNotification(notification);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Always allow (post-tool hooks don't block)
|
|
166
|
+
console.log(JSON.stringify({ status: 'ok' }));
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error('[notify-hook] Error:', err.message);
|
|
169
|
+
console.log(JSON.stringify({ status: 'ok' }));
|
|
170
|
+
}
|
|
171
|
+
});
|