@delt/claude-alarm 0.9.0 → 1.0.0
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/README.md +47 -4
- package/dist/channel/server.js +41 -22
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +460 -145
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +774 -0
- package/dist/codex/main.js.map +1 -0
- package/dist/dashboard/index.html +44 -37
- package/dist/hub/server.js +292 -93
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +28 -2
- package/dist/index.js +338 -120
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/dashboard/index.html +44 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delt/claude-alarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Monitor and get notifications from multiple Claude Code sessions via MCP Channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"dev": "tsup --watch",
|
|
29
29
|
"start:hub": "node dist/cli.js hub start",
|
|
30
30
|
"start:channel": "node dist/channel/server.js",
|
|
31
|
-
"test": "node --import tsx --test \"test/**/*.test.ts\""
|
|
31
|
+
"test": "node --import tsx --import ./test/isolate-home.ts --test --test-timeout=30000 \"test/**/*.test.ts\""
|
|
32
32
|
},
|
|
33
33
|
"keywords": [
|
|
34
34
|
"claude",
|
package/src/dashboard/index.html
CHANGED
|
@@ -209,6 +209,14 @@
|
|
|
209
209
|
justify-content: center;
|
|
210
210
|
padding: 0 5px;
|
|
211
211
|
}
|
|
212
|
+
.agent-badge {
|
|
213
|
+
font-size: 10px;
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
padding: 1px 6px;
|
|
216
|
+
border-radius: 8px;
|
|
217
|
+
border: 1px solid var(--green);
|
|
218
|
+
color: var(--green);
|
|
219
|
+
}
|
|
212
220
|
.session-status {
|
|
213
221
|
display: inline-block;
|
|
214
222
|
font-size: 11px;
|
|
@@ -871,7 +879,7 @@
|
|
|
871
879
|
<div class="token-overlay hidden" id="tokenOverlay">
|
|
872
880
|
<div class="token-form">
|
|
873
881
|
<h2>Authentication Required</h2>
|
|
874
|
-
<p>Enter the hub token to connect.
|
|
882
|
+
<p>Enter the hub token to connect. Run <code>claude-alarm token</code>, or open the login link printed by <code>claude-alarm hub start</code>.</p>
|
|
875
883
|
<input type="text" id="tokenInput" placeholder="Paste token here..." autocomplete="off">
|
|
876
884
|
<button id="tokenSubmit">Connect</button>
|
|
877
885
|
<div class="token-error" id="tokenError">Connection failed. Check your token.</div>
|
|
@@ -1026,7 +1034,6 @@
|
|
|
1026
1034
|
selectedSession: null,
|
|
1027
1035
|
messages: {},
|
|
1028
1036
|
notifications: [],
|
|
1029
|
-
token: null,
|
|
1030
1037
|
pendingImage: null,
|
|
1031
1038
|
unread: {},
|
|
1032
1039
|
waitingReply: {},
|
|
@@ -1131,6 +1138,10 @@
|
|
|
1131
1138
|
return state.sessionMeta.names[s.id] || s.displayName || s.name;
|
|
1132
1139
|
}
|
|
1133
1140
|
|
|
1141
|
+
function agentName(s) {
|
|
1142
|
+
return s && s.agentKind === 'codex' ? 'Codex' : 'Claude';
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1134
1145
|
function normName(s) {
|
|
1135
1146
|
return String(s).normalize('NFC').trim().toLowerCase();
|
|
1136
1147
|
}
|
|
@@ -1154,7 +1165,7 @@
|
|
|
1154
1165
|
// Built-in SendMessage only reaches sessions on the hub's own machine.
|
|
1155
1166
|
function mentionTargets() {
|
|
1156
1167
|
const selected = state.sessions[state.selectedSession];
|
|
1157
|
-
if (!selected || !selected.isLocal) return [];
|
|
1168
|
+
if (!selected || !selected.isLocal || selected.agentKind === 'codex') return [];
|
|
1158
1169
|
return Object.values(state.sessions).filter(s => s.id !== selected.id && s.isLocal && s.peerName);
|
|
1159
1170
|
}
|
|
1160
1171
|
|
|
@@ -1191,6 +1202,8 @@
|
|
|
1191
1202
|
// The routing line is the contract with the channel instructions in
|
|
1192
1203
|
// src/channel/server.ts (ROUTING) — change both together.
|
|
1193
1204
|
function buildRouting(text) {
|
|
1205
|
+
const target = state.sessions[state.selectedSession];
|
|
1206
|
+
if (target && target.agentKind === 'codex') return { ok: true, content: text };
|
|
1194
1207
|
const mentions = extractMentions(text);
|
|
1195
1208
|
if (!mentions.length) return { ok: true, content: text };
|
|
1196
1209
|
const unknown = [];
|
|
@@ -1291,25 +1304,28 @@
|
|
|
1291
1304
|
initTheme();
|
|
1292
1305
|
|
|
1293
1306
|
// --- Token handling ---
|
|
1294
|
-
function
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
return urlToken;
|
|
1307
|
+
async function checkAuth() {
|
|
1308
|
+
try {
|
|
1309
|
+
return (await fetch('/api/status')).status !== 401;
|
|
1310
|
+
} catch {
|
|
1311
|
+
return true;
|
|
1300
1312
|
}
|
|
1301
|
-
return sessionStorage.getItem('claude-alarm-token');
|
|
1302
1313
|
}
|
|
1303
1314
|
|
|
1304
1315
|
function showTokenForm() { $('#tokenOverlay').classList.remove('hidden'); }
|
|
1305
1316
|
function hideTokenForm() { $('#tokenOverlay').classList.add('hidden'); }
|
|
1306
1317
|
|
|
1307
|
-
$('#tokenSubmit').addEventListener('click', () => {
|
|
1318
|
+
$('#tokenSubmit').addEventListener('click', async () => {
|
|
1308
1319
|
const token = $('#tokenInput').value.trim();
|
|
1309
1320
|
if (!token) return;
|
|
1310
|
-
|
|
1311
|
-
|
|
1321
|
+
const res = await fetch('/api/login', {
|
|
1322
|
+
method: 'POST',
|
|
1323
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1324
|
+
body: JSON.stringify({ token }),
|
|
1325
|
+
}).catch(() => null);
|
|
1326
|
+
if (!res || !res.ok) { $('#tokenError').style.display = 'block'; return; }
|
|
1312
1327
|
$('#tokenError').style.display = 'none';
|
|
1328
|
+
$('#tokenInput').value = '';
|
|
1313
1329
|
hideTokenForm();
|
|
1314
1330
|
connect();
|
|
1315
1331
|
});
|
|
@@ -1320,25 +1336,23 @@
|
|
|
1320
1336
|
// --- WebSocket ---
|
|
1321
1337
|
function connect() {
|
|
1322
1338
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1339
|
+
const ws = new WebSocket(`${proto}://${location.host}/ws/dashboard`);
|
|
1340
|
+
let opened = false;
|
|
1325
1341
|
|
|
1326
1342
|
ws.onopen = () => {
|
|
1343
|
+
opened = true;
|
|
1327
1344
|
state.ws = ws;
|
|
1328
1345
|
hideTokenForm();
|
|
1329
1346
|
$('#connDot').classList.add('connected');
|
|
1330
1347
|
$('#connLabel').textContent = 'Connected';
|
|
1331
1348
|
};
|
|
1332
|
-
ws.onclose = () => {
|
|
1349
|
+
ws.onclose = async () => {
|
|
1333
1350
|
state.ws = null;
|
|
1334
1351
|
$('#connDot').classList.remove('connected');
|
|
1335
1352
|
$('#connLabel').textContent = 'Disconnected';
|
|
1353
|
+
if (!opened && !(await checkAuth())) { showTokenForm(); return; }
|
|
1336
1354
|
setTimeout(connect, 3000);
|
|
1337
1355
|
};
|
|
1338
|
-
ws.onerror = () => {
|
|
1339
|
-
if (!state.token) { showTokenForm(); } else { $('#tokenError').style.display = 'block'; showTokenForm(); }
|
|
1340
|
-
ws.close();
|
|
1341
|
-
};
|
|
1342
1356
|
ws.onmessage = (e) => { try { handleMessage(JSON.parse(e.data)); } catch {} };
|
|
1343
1357
|
}
|
|
1344
1358
|
|
|
@@ -1433,7 +1447,7 @@
|
|
|
1433
1447
|
return `<div class="session-card${active}" data-id="${id}">
|
|
1434
1448
|
<span class="drag-handle" title="Drag to reorder">⠿</span>
|
|
1435
1449
|
<div class="session-body">
|
|
1436
|
-
<div class="session-name" title="Double-click to rename">${esc(dispName)}${unread ? `<span class="unread-badge">${unread}</span>` : ''}</div>
|
|
1450
|
+
<div class="session-name" title="Double-click to rename">${esc(dispName)}${s.agentKind === 'codex' ? '<span class="agent-badge">Codex</span>' : ''}${unread ? `<span class="unread-badge">${unread}</span>` : ''}</div>
|
|
1437
1451
|
${cwdDisplay ? `<div class="session-cwd" title="${esc(s.cwd)}">${esc(cwdDisplay)} · ${connTime}</div>` : `<div class="session-cwd">${connTime}</div>`}
|
|
1438
1452
|
<span class="session-status ${s.status}">${s.status.replace('_', ' ')}</span>
|
|
1439
1453
|
</div>
|
|
@@ -1580,7 +1594,7 @@
|
|
|
1580
1594
|
|
|
1581
1595
|
function updateImageUI() {
|
|
1582
1596
|
const s = state.selectedSession ? state.sessions[state.selectedSession] : null;
|
|
1583
|
-
const canImage = s && s.isLocal;
|
|
1597
|
+
const canImage = s && s.isLocal && s.agentKind !== 'codex';
|
|
1584
1598
|
$('#attachBtn').disabled = !canImage;
|
|
1585
1599
|
}
|
|
1586
1600
|
|
|
@@ -1616,7 +1630,7 @@
|
|
|
1616
1630
|
content = m.from === 'session' ? renderMarkdown(m.content) : esc(m.content).replace(/\n/g, '<br>');
|
|
1617
1631
|
}
|
|
1618
1632
|
return `<div class="message ${cls}">
|
|
1619
|
-
<div class="message-meta">${m.from === 'session' ?
|
|
1633
|
+
<div class="message-meta">${m.from === 'session' ? agentName(s) : 'You'} · ${timeStr}</div>
|
|
1620
1634
|
<div class="message-body">${content}</div>
|
|
1621
1635
|
</div>`;
|
|
1622
1636
|
}).join('');
|
|
@@ -2128,16 +2142,14 @@
|
|
|
2128
2142
|
$('#settingsToggle').addEventListener('click', async () => {
|
|
2129
2143
|
// Load webhook data
|
|
2130
2144
|
try {
|
|
2131
|
-
const
|
|
2132
|
-
const res = await fetch(`/api/webhooks${tokenQuery}`);
|
|
2145
|
+
const res = await fetch(`/api/webhooks`);
|
|
2133
2146
|
const data = await res.json();
|
|
2134
2147
|
webhookData = data.webhooks || [];
|
|
2135
2148
|
} catch { webhookData = []; }
|
|
2136
2149
|
renderWebhooks();
|
|
2137
2150
|
// Load telegram data
|
|
2138
2151
|
try {
|
|
2139
|
-
const
|
|
2140
|
-
const res = await fetch(`/api/telegram${tokenQuery}`);
|
|
2152
|
+
const res = await fetch(`/api/telegram`);
|
|
2141
2153
|
const data = await res.json();
|
|
2142
2154
|
const tg = data.telegram || {};
|
|
2143
2155
|
if (!$('#tgBotToken').value || $('#tgBotToken').value.includes('...')) {
|
|
@@ -2172,8 +2184,7 @@
|
|
|
2172
2184
|
$('#webhookSave').addEventListener('click', async () => {
|
|
2173
2185
|
const valid = webhookData.filter(w => w.url.trim());
|
|
2174
2186
|
try {
|
|
2175
|
-
|
|
2176
|
-
await fetch(`/api/webhooks${tokenQuery}`, {
|
|
2187
|
+
await fetch(`/api/webhooks`, {
|
|
2177
2188
|
method: 'POST',
|
|
2178
2189
|
headers: { 'Content-Type': 'application/json' },
|
|
2179
2190
|
body: JSON.stringify({ webhooks: valid }),
|
|
@@ -2248,8 +2259,7 @@
|
|
|
2248
2259
|
$('#tgDetectStatus').textContent = 'Detecting...';
|
|
2249
2260
|
$('#tgChatList').innerHTML = '';
|
|
2250
2261
|
try {
|
|
2251
|
-
const
|
|
2252
|
-
const res = await fetch(`/api/telegram/detect${tokenQuery}`, {
|
|
2262
|
+
const res = await fetch(`/api/telegram/detect`, {
|
|
2253
2263
|
method: 'POST',
|
|
2254
2264
|
headers: { 'Content-Type': 'application/json' },
|
|
2255
2265
|
body: JSON.stringify({ botToken }),
|
|
@@ -2286,8 +2296,7 @@
|
|
|
2286
2296
|
const chatId = $('#tgChatId').value.trim();
|
|
2287
2297
|
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
2288
2298
|
try {
|
|
2289
|
-
const
|
|
2290
|
-
const res = await fetch(`/api/telegram/test${tokenQuery}`, {
|
|
2299
|
+
const res = await fetch(`/api/telegram/test`, {
|
|
2291
2300
|
method: 'POST',
|
|
2292
2301
|
headers: { 'Content-Type': 'application/json' },
|
|
2293
2302
|
body: JSON.stringify({ botToken, chatId }),
|
|
@@ -2306,8 +2315,7 @@
|
|
|
2306
2315
|
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
2307
2316
|
if (botToken.includes('...')) { showTgStatus('Bot Token is masked. Please enter the full token.', false); return; }
|
|
2308
2317
|
try {
|
|
2309
|
-
|
|
2310
|
-
await fetch(`/api/telegram${tokenQuery}`, {
|
|
2318
|
+
await fetch(`/api/telegram`, {
|
|
2311
2319
|
method: 'POST',
|
|
2312
2320
|
headers: { 'Content-Type': 'application/json' },
|
|
2313
2321
|
body: JSON.stringify({ telegram: { botToken, chatId, enabled } }),
|
|
@@ -2335,8 +2343,7 @@
|
|
|
2335
2343
|
document.querySelector('.sessions-panel').classList.add('mobile-active');
|
|
2336
2344
|
}
|
|
2337
2345
|
|
|
2338
|
-
|
|
2339
|
-
connect();
|
|
2346
|
+
checkAuth().then((ok) => (ok ? connect() : showTokenForm()));
|
|
2340
2347
|
})();
|
|
2341
2348
|
</script>
|
|
2342
2349
|
</body>
|