@delt/claude-alarm 1.1.1 → 1.3.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 +18 -10
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +1258 -180
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +660 -106
- package/dist/codex/main.js.map +1 -1
- package/dist/dashboard/index.html +176 -1
- package/dist/hub/server.js +308 -56
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +71 -3
- package/dist/index.js +309 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +176 -1
package/package.json
CHANGED
package/src/dashboard/index.html
CHANGED
|
@@ -143,6 +143,19 @@
|
|
|
143
143
|
color: var(--text-dim);
|
|
144
144
|
margin-bottom: 8px;
|
|
145
145
|
}
|
|
146
|
+
[hidden] { display: none !important; }
|
|
147
|
+
.codex-section { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); display: grid; gap: 8px; }
|
|
148
|
+
.codex-section .cmd-popup-title { margin-bottom: 0; color: var(--text); font-weight: 600; }
|
|
149
|
+
#codexAdapter, #codexFolder { box-sizing: border-box; width: 100%; min-width: 0; min-height: 36px; padding: 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--text); font: inherit; font-size: 12px; }
|
|
150
|
+
#codexFolder::placeholder { color: var(--text-dim); }
|
|
151
|
+
#codexAdapter:focus, #codexFolder:focus { outline: 1px solid var(--accent); border-color: var(--accent); }
|
|
152
|
+
.codex-warning { color: var(--text-dim); font-size: 11px; line-height: 1.5; }
|
|
153
|
+
#codexCreate { min-height: 36px; padding: 8px 12px; border: 1px solid var(--accent); border-radius: 6px; background: var(--surface); color: var(--text); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
|
|
154
|
+
#codexCreate:disabled { opacity: 0.6; cursor: wait; }
|
|
155
|
+
.codex-error { color: var(--red); font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
|
|
156
|
+
.session-close { flex-shrink: 0; align-self: flex-end; min-width: 32px; min-height: 32px; padding: 4px 6px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--text-dim); font-size: 16px; cursor: pointer; }
|
|
157
|
+
.session-close:hover, .session-close:focus-visible { border-color: var(--red); color: var(--red); }
|
|
158
|
+
.session-close.armed { border-color: var(--red); color: var(--red); font-size: 11px; font-weight: 600; }
|
|
146
159
|
.session-card {
|
|
147
160
|
background: var(--surface);
|
|
148
161
|
border: 1px solid var(--border);
|
|
@@ -863,6 +876,9 @@
|
|
|
863
876
|
|
|
864
877
|
/* Mobile responsive */
|
|
865
878
|
@media (max-width: 768px) {
|
|
879
|
+
.codex-section { gap: 10px; }
|
|
880
|
+
#codexAdapter, #codexFolder, #codexCreate { min-height: 40px; font-size: 13px; }
|
|
881
|
+
.session-close { min-width: 36px; min-height: 36px; }
|
|
866
882
|
.container {
|
|
867
883
|
grid-template-columns: 1fr;
|
|
868
884
|
height: calc(100vh - 97px);
|
|
@@ -1015,6 +1031,15 @@
|
|
|
1015
1031
|
<span class="cmd-text">claude --dangerously-load-development-channels server:claude-alarm --dangerously-skip-permissions</span>
|
|
1016
1032
|
<span class="cmd-icon">📋</span>
|
|
1017
1033
|
</div>
|
|
1034
|
+
<div class="codex-section" id="codexSection" hidden>
|
|
1035
|
+
<div class="cmd-popup-title">New Codex conversation:</div>
|
|
1036
|
+
<select id="codexAdapter" hidden></select>
|
|
1037
|
+
<input id="codexFolder" list="codexFolders" placeholder="Folder on that PC, e.g. C:\work\project" autocomplete="off" spellcheck="false">
|
|
1038
|
+
<datalist id="codexFolders"></datalist>
|
|
1039
|
+
<div class="codex-warning">Runs with full access: no approval prompts.</div>
|
|
1040
|
+
<button id="codexCreate" type="button">Create</button>
|
|
1041
|
+
<div class="codex-error" id="codexError" hidden></div>
|
|
1042
|
+
</div>
|
|
1018
1043
|
</div>
|
|
1019
1044
|
<div id="sessionsList"></div>
|
|
1020
1045
|
</div>
|
|
@@ -1066,6 +1091,9 @@
|
|
|
1066
1091
|
waitingReply: {},
|
|
1067
1092
|
permissionRequests: {}, // sessionId -> [{ requestId, toolName, description, inputPreview, timestamp, resolved, behavior }]
|
|
1068
1093
|
sessionMeta: { names: {}, order: [] }, // custom names + order, keyed by volatile sessionId
|
|
1094
|
+
codexAdapters: [],
|
|
1095
|
+
pendingSelect: null,
|
|
1096
|
+
closeArmed: null,
|
|
1069
1097
|
};
|
|
1070
1098
|
|
|
1071
1099
|
const $ = (sel) => document.querySelector(sel);
|
|
@@ -1257,6 +1285,17 @@
|
|
|
1257
1285
|
el.classList.toggle('show', !!msg);
|
|
1258
1286
|
}
|
|
1259
1287
|
|
|
1288
|
+
function showUploadRejected(msg) {
|
|
1289
|
+
state.waitingReply[msg.sessionId] = false;
|
|
1290
|
+
const label = msg.withText ? 'Image and message not delivered' : 'Image not delivered';
|
|
1291
|
+
if (state.selectedSession === msg.sessionId) {
|
|
1292
|
+
renderMessages();
|
|
1293
|
+
showMentionError(`${label}: ${msg.reason}`);
|
|
1294
|
+
}
|
|
1295
|
+
state.notifications.unshift({ sessionId: msg.sessionId, title: label, message: msg.reason, level: 'warning', time: Date.now() });
|
|
1296
|
+
renderNotifications();
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1260
1299
|
let mentionState = { open: false, items: [], index: 0, start: -1 };
|
|
1261
1300
|
|
|
1262
1301
|
function currentMentionQuery(input) {
|
|
@@ -1390,11 +1429,13 @@
|
|
|
1390
1429
|
msg.sessions.forEach(s => { state.sessions[s.id] = s; });
|
|
1391
1430
|
pruneSessionData();
|
|
1392
1431
|
renderSessions();
|
|
1432
|
+
if (state.pendingSelect && state.sessions[state.pendingSelect]) selectSession(state.pendingSelect);
|
|
1393
1433
|
break;
|
|
1394
1434
|
case 'session_connected':
|
|
1395
1435
|
state.sessions[msg.session.id] = msg.session;
|
|
1396
1436
|
if (!state.messages[msg.session.id]) state.messages[msg.session.id] = [];
|
|
1397
1437
|
renderSessions();
|
|
1438
|
+
if (state.pendingSelect === msg.session.id) selectSession(msg.session.id);
|
|
1398
1439
|
break;
|
|
1399
1440
|
case 'session_disconnected':
|
|
1400
1441
|
delete state.sessions[msg.sessionId];
|
|
@@ -1418,6 +1459,9 @@
|
|
|
1418
1459
|
state.notifications.unshift({ sessionId: msg.sessionId, title: 'Reply', message: msg.content.slice(0, 100), level: 'info', time: msg.timestamp });
|
|
1419
1460
|
renderNotifications();
|
|
1420
1461
|
break;
|
|
1462
|
+
case 'upload_rejected':
|
|
1463
|
+
showUploadRejected(msg);
|
|
1464
|
+
break;
|
|
1421
1465
|
case 'notification':
|
|
1422
1466
|
state.notifications.unshift({ sessionId: msg.sessionId, title: msg.title, message: msg.message, level: msg.level || 'info', time: msg.timestamp });
|
|
1423
1467
|
renderNotifications();
|
|
@@ -1463,6 +1507,15 @@
|
|
|
1463
1507
|
renderPermissionBar();
|
|
1464
1508
|
renderNotifications();
|
|
1465
1509
|
break;
|
|
1510
|
+
case 'codex_adapters': {
|
|
1511
|
+
state.codexAdapters = msg.adapters || [];
|
|
1512
|
+
if ($('#cmdPopup').classList.contains('active')) {
|
|
1513
|
+
const before = $('#codexAdapter').value;
|
|
1514
|
+
renderCodexForm();
|
|
1515
|
+
if ($('#codexAdapter').value !== before) loadCodexFolders();
|
|
1516
|
+
}
|
|
1517
|
+
break;
|
|
1518
|
+
}
|
|
1466
1519
|
case 'permission_resolved': {
|
|
1467
1520
|
const req = (state.permissionRequests[msg.sessionId] || []).find(r => r.requestId === msg.requestId);
|
|
1468
1521
|
if (req && !req.resolved) {
|
|
@@ -1491,6 +1544,10 @@
|
|
|
1491
1544
|
const unread = state.unread[id] || 0;
|
|
1492
1545
|
const dispName = sessionDisplayName(s);
|
|
1493
1546
|
const connTime = new Date(s.connectedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
1547
|
+
const armed = state.closeArmed && state.closeArmed.id === id && closeStep(state.closeArmed.at, Date.now()) !== 'arm';
|
|
1548
|
+
const closeBtn = s.closable
|
|
1549
|
+
? `<button class="session-close${armed ? ' armed' : ''}" type="button" title="Close — stays in Codex history">${armed ? 'Close?' : '×'}</button>`
|
|
1550
|
+
: '';
|
|
1494
1551
|
return `<div class="session-card${active}" data-id="${id}">
|
|
1495
1552
|
<span class="drag-handle" title="Drag to reorder">⠿</span>
|
|
1496
1553
|
<div class="session-body">
|
|
@@ -1498,6 +1555,7 @@
|
|
|
1498
1555
|
${cwdDisplay ? `<div class="session-cwd" title="${esc(s.cwd)}">${esc(cwdDisplay)} · ${connTime}</div>` : `<div class="session-cwd">${connTime}</div>`}
|
|
1499
1556
|
<span class="session-status ${s.status}">${s.status.replace('_', ' ')}</span>
|
|
1500
1557
|
</div>
|
|
1558
|
+
${closeBtn}
|
|
1501
1559
|
</div>`;
|
|
1502
1560
|
}).join('');
|
|
1503
1561
|
bindSessionCards(el);
|
|
@@ -1508,10 +1566,13 @@
|
|
|
1508
1566
|
el.querySelectorAll('.session-card').forEach(card => {
|
|
1509
1567
|
const id = card.dataset.id;
|
|
1510
1568
|
card.addEventListener('click', (e) => {
|
|
1569
|
+
if (e.target.closest('.session-close')) return;
|
|
1511
1570
|
if (e.target.closest('.drag-handle')) return;
|
|
1512
1571
|
if (e.target.classList.contains('name-edit-input')) return;
|
|
1513
1572
|
selectSession(id);
|
|
1514
1573
|
});
|
|
1574
|
+
const closeBtn = card.querySelector('.session-close');
|
|
1575
|
+
if (closeBtn) closeBtn.addEventListener('click', (e) => { e.stopPropagation(); onCloseClick(id); });
|
|
1515
1576
|
const nameEl = card.querySelector('.session-name');
|
|
1516
1577
|
nameEl.addEventListener('dblclick', (e) => {
|
|
1517
1578
|
e.stopPropagation();
|
|
@@ -1621,6 +1682,7 @@
|
|
|
1621
1682
|
}
|
|
1622
1683
|
|
|
1623
1684
|
function selectSession(id) {
|
|
1685
|
+
state.pendingSelect = null;
|
|
1624
1686
|
state.selectedSession = id;
|
|
1625
1687
|
state.unread[id] = 0;
|
|
1626
1688
|
if (!state.messages[id]) state.messages[id] = [];
|
|
@@ -1641,7 +1703,7 @@
|
|
|
1641
1703
|
|
|
1642
1704
|
function updateImageUI() {
|
|
1643
1705
|
const s = state.selectedSession ? state.sessions[state.selectedSession] : null;
|
|
1644
|
-
const canImage = s && s.isLocal
|
|
1706
|
+
const canImage = s && s.isLocal;
|
|
1645
1707
|
$('#attachBtn').disabled = !canImage;
|
|
1646
1708
|
}
|
|
1647
1709
|
|
|
@@ -2185,10 +2247,32 @@
|
|
|
2185
2247
|
msgList.scrollTo({ top: msgList.scrollHeight, behavior: 'smooth' });
|
|
2186
2248
|
});
|
|
2187
2249
|
|
|
2250
|
+
// --- Codex helpers ---
|
|
2251
|
+
function usableAdapters(adapters) {
|
|
2252
|
+
return adapters.filter(a => a.ready).sort((a, b) => (b.isLocal ? 1 : 0) - (a.isLocal ? 1 : 0));
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
function adapterLabels(adapters) {
|
|
2256
|
+
const counts = {};
|
|
2257
|
+
adapters.forEach(a => { counts[a.host] = (counts[a.host] || 0) + 1; });
|
|
2258
|
+
return adapters.map(a => counts[a.host] > 1 ? `${a.host} (${a.id.slice(0, 4)})` : a.host);
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// A second tap within 0.4 s is a phone double-tap, not a confirmation.
|
|
2262
|
+
function closeStep(armedAt, now) {
|
|
2263
|
+
if (armedAt === null || now - armedAt >= 3000) return 'arm';
|
|
2264
|
+
return now - armedAt < 400 ? 'wait' : 'close';
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2188
2267
|
// --- Add session popup ---
|
|
2189
2268
|
$('#addSessionBtn').addEventListener('click', (e) => {
|
|
2190
2269
|
e.stopPropagation();
|
|
2270
|
+
const opening = !$('#cmdPopup').classList.contains('active');
|
|
2191
2271
|
$('#cmdPopup').classList.toggle('active');
|
|
2272
|
+
if (opening) {
|
|
2273
|
+
renderCodexForm();
|
|
2274
|
+
loadCodexFolders();
|
|
2275
|
+
}
|
|
2192
2276
|
});
|
|
2193
2277
|
document.addEventListener('click', (e) => {
|
|
2194
2278
|
if (!e.target.closest('#cmdPopup') && !e.target.closest('#addSessionBtn')) {
|
|
@@ -2209,6 +2293,97 @@
|
|
|
2209
2293
|
});
|
|
2210
2294
|
});
|
|
2211
2295
|
|
|
2296
|
+
function renderCodexForm() {
|
|
2297
|
+
const usable = usableAdapters(state.codexAdapters);
|
|
2298
|
+
$('#codexSection').hidden = !usable.length;
|
|
2299
|
+
if (!usable.length) return;
|
|
2300
|
+
const select = $('#codexAdapter');
|
|
2301
|
+
const current = select.value;
|
|
2302
|
+
const labels = adapterLabels(usable);
|
|
2303
|
+
select.replaceChildren(...usable.map((a, i) => new Option(labels[i], a.id)));
|
|
2304
|
+
if (usable.some(a => a.id === current)) select.value = current;
|
|
2305
|
+
select.hidden = usable.length < 2;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
async function loadCodexFolders() {
|
|
2309
|
+
const list = $('#codexFolders');
|
|
2310
|
+
list.innerHTML = '';
|
|
2311
|
+
const adapterId = $('#codexAdapter').value;
|
|
2312
|
+
if (!adapterId) return;
|
|
2313
|
+
try {
|
|
2314
|
+
const res = await fetch(`/api/codex/folders?adapterId=${encodeURIComponent(adapterId)}`);
|
|
2315
|
+
if (!res.ok || $('#codexAdapter').value !== adapterId) return;
|
|
2316
|
+
const { folders } = await res.json();
|
|
2317
|
+
list.replaceChildren(...folders.map(f => new Option('', f)));
|
|
2318
|
+
} catch {}
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
async function createCodexConversation() {
|
|
2322
|
+
const btn = $('#codexCreate');
|
|
2323
|
+
const folder = $('#codexFolder').value.trim();
|
|
2324
|
+
const adapterId = $('#codexAdapter').value;
|
|
2325
|
+
if (!folder || !adapterId || btn.disabled) return;
|
|
2326
|
+
const error = $('#codexError');
|
|
2327
|
+
error.hidden = true;
|
|
2328
|
+
btn.disabled = true;
|
|
2329
|
+
btn.textContent = 'Creating…';
|
|
2330
|
+
try {
|
|
2331
|
+
const res = await fetch('/api/codex/threads', {
|
|
2332
|
+
method: 'POST',
|
|
2333
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2334
|
+
body: JSON.stringify({ adapterId, cwd: folder }),
|
|
2335
|
+
});
|
|
2336
|
+
const body = await res.json().catch(() => ({}));
|
|
2337
|
+
if (!res.ok) throw new Error(body.error || `Request failed (${res.status})`);
|
|
2338
|
+
$('#cmdPopup').classList.remove('active');
|
|
2339
|
+
$('#codexFolder').value = '';
|
|
2340
|
+
// The session can register before or after this answer arrives.
|
|
2341
|
+
if (state.sessions[body.sessionId]) selectSession(body.sessionId);
|
|
2342
|
+
else state.pendingSelect = body.sessionId;
|
|
2343
|
+
} catch (err) {
|
|
2344
|
+
error.textContent = err.message;
|
|
2345
|
+
error.hidden = false;
|
|
2346
|
+
} finally {
|
|
2347
|
+
btn.disabled = false;
|
|
2348
|
+
btn.textContent = 'Create';
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
async function onCloseClick(id) {
|
|
2353
|
+
const armedAt = state.closeArmed && state.closeArmed.id === id ? state.closeArmed.at : null;
|
|
2354
|
+
const step = closeStep(armedAt, Date.now());
|
|
2355
|
+
if (step === 'wait') return;
|
|
2356
|
+
if (step === 'arm') {
|
|
2357
|
+
const at = Date.now();
|
|
2358
|
+
state.closeArmed = { id, at };
|
|
2359
|
+
renderSessions();
|
|
2360
|
+
setTimeout(() => {
|
|
2361
|
+
if (state.closeArmed && state.closeArmed.at === at) {
|
|
2362
|
+
state.closeArmed = null;
|
|
2363
|
+
renderSessions();
|
|
2364
|
+
}
|
|
2365
|
+
}, 3000);
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
state.closeArmed = null;
|
|
2369
|
+
renderSessions();
|
|
2370
|
+
try {
|
|
2371
|
+
await fetch('/api/codex/threads/close', {
|
|
2372
|
+
method: 'POST',
|
|
2373
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2374
|
+
body: JSON.stringify({ sessionId: id }),
|
|
2375
|
+
});
|
|
2376
|
+
} catch {}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
$('#codexAdapter').addEventListener('change', loadCodexFolders);
|
|
2380
|
+
$('#codexCreate').addEventListener('click', createCodexConversation);
|
|
2381
|
+
$('#codexFolder').addEventListener('keydown', (e) => {
|
|
2382
|
+
if (e.key === 'Enter' && !e.isComposing) {
|
|
2383
|
+
e.preventDefault();
|
|
2384
|
+
createCodexConversation();
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2212
2387
|
// --- Settings modal (Webhook + Telegram) ---
|
|
2213
2388
|
let webhookData = [];
|
|
2214
2389
|
const closeSettings = () => $('#settingsOverlay').classList.add('hidden');
|