@delt/claude-alarm 1.1.1 → 1.2.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 +17 -10
- package/dist/channel/server.js.map +1 -1
- package/dist/cli.js +906 -67
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +380 -63
- package/dist/codex/main.js.map +1 -1
- package/dist/dashboard/index.html +162 -1
- package/dist/hub/server.js +165 -8
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +60 -2
- package/dist/index.js +166 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +162 -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);
|
|
@@ -1390,11 +1418,13 @@
|
|
|
1390
1418
|
msg.sessions.forEach(s => { state.sessions[s.id] = s; });
|
|
1391
1419
|
pruneSessionData();
|
|
1392
1420
|
renderSessions();
|
|
1421
|
+
if (state.pendingSelect && state.sessions[state.pendingSelect]) selectSession(state.pendingSelect);
|
|
1393
1422
|
break;
|
|
1394
1423
|
case 'session_connected':
|
|
1395
1424
|
state.sessions[msg.session.id] = msg.session;
|
|
1396
1425
|
if (!state.messages[msg.session.id]) state.messages[msg.session.id] = [];
|
|
1397
1426
|
renderSessions();
|
|
1427
|
+
if (state.pendingSelect === msg.session.id) selectSession(msg.session.id);
|
|
1398
1428
|
break;
|
|
1399
1429
|
case 'session_disconnected':
|
|
1400
1430
|
delete state.sessions[msg.sessionId];
|
|
@@ -1463,6 +1493,15 @@
|
|
|
1463
1493
|
renderPermissionBar();
|
|
1464
1494
|
renderNotifications();
|
|
1465
1495
|
break;
|
|
1496
|
+
case 'codex_adapters': {
|
|
1497
|
+
state.codexAdapters = msg.adapters || [];
|
|
1498
|
+
if ($('#cmdPopup').classList.contains('active')) {
|
|
1499
|
+
const before = $('#codexAdapter').value;
|
|
1500
|
+
renderCodexForm();
|
|
1501
|
+
if ($('#codexAdapter').value !== before) loadCodexFolders();
|
|
1502
|
+
}
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1466
1505
|
case 'permission_resolved': {
|
|
1467
1506
|
const req = (state.permissionRequests[msg.sessionId] || []).find(r => r.requestId === msg.requestId);
|
|
1468
1507
|
if (req && !req.resolved) {
|
|
@@ -1491,6 +1530,10 @@
|
|
|
1491
1530
|
const unread = state.unread[id] || 0;
|
|
1492
1531
|
const dispName = sessionDisplayName(s);
|
|
1493
1532
|
const connTime = new Date(s.connectedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
1533
|
+
const armed = state.closeArmed && state.closeArmed.id === id && closeStep(state.closeArmed.at, Date.now()) !== 'arm';
|
|
1534
|
+
const closeBtn = s.closable
|
|
1535
|
+
? `<button class="session-close${armed ? ' armed' : ''}" type="button" title="Close — stays in Codex history">${armed ? 'Close?' : '×'}</button>`
|
|
1536
|
+
: '';
|
|
1494
1537
|
return `<div class="session-card${active}" data-id="${id}">
|
|
1495
1538
|
<span class="drag-handle" title="Drag to reorder">⠿</span>
|
|
1496
1539
|
<div class="session-body">
|
|
@@ -1498,6 +1541,7 @@
|
|
|
1498
1541
|
${cwdDisplay ? `<div class="session-cwd" title="${esc(s.cwd)}">${esc(cwdDisplay)} · ${connTime}</div>` : `<div class="session-cwd">${connTime}</div>`}
|
|
1499
1542
|
<span class="session-status ${s.status}">${s.status.replace('_', ' ')}</span>
|
|
1500
1543
|
</div>
|
|
1544
|
+
${closeBtn}
|
|
1501
1545
|
</div>`;
|
|
1502
1546
|
}).join('');
|
|
1503
1547
|
bindSessionCards(el);
|
|
@@ -1508,10 +1552,13 @@
|
|
|
1508
1552
|
el.querySelectorAll('.session-card').forEach(card => {
|
|
1509
1553
|
const id = card.dataset.id;
|
|
1510
1554
|
card.addEventListener('click', (e) => {
|
|
1555
|
+
if (e.target.closest('.session-close')) return;
|
|
1511
1556
|
if (e.target.closest('.drag-handle')) return;
|
|
1512
1557
|
if (e.target.classList.contains('name-edit-input')) return;
|
|
1513
1558
|
selectSession(id);
|
|
1514
1559
|
});
|
|
1560
|
+
const closeBtn = card.querySelector('.session-close');
|
|
1561
|
+
if (closeBtn) closeBtn.addEventListener('click', (e) => { e.stopPropagation(); onCloseClick(id); });
|
|
1515
1562
|
const nameEl = card.querySelector('.session-name');
|
|
1516
1563
|
nameEl.addEventListener('dblclick', (e) => {
|
|
1517
1564
|
e.stopPropagation();
|
|
@@ -1621,6 +1668,7 @@
|
|
|
1621
1668
|
}
|
|
1622
1669
|
|
|
1623
1670
|
function selectSession(id) {
|
|
1671
|
+
state.pendingSelect = null;
|
|
1624
1672
|
state.selectedSession = id;
|
|
1625
1673
|
state.unread[id] = 0;
|
|
1626
1674
|
if (!state.messages[id]) state.messages[id] = [];
|
|
@@ -1641,7 +1689,7 @@
|
|
|
1641
1689
|
|
|
1642
1690
|
function updateImageUI() {
|
|
1643
1691
|
const s = state.selectedSession ? state.sessions[state.selectedSession] : null;
|
|
1644
|
-
const canImage = s && s.isLocal
|
|
1692
|
+
const canImage = s && s.isLocal;
|
|
1645
1693
|
$('#attachBtn').disabled = !canImage;
|
|
1646
1694
|
}
|
|
1647
1695
|
|
|
@@ -2185,10 +2233,32 @@
|
|
|
2185
2233
|
msgList.scrollTo({ top: msgList.scrollHeight, behavior: 'smooth' });
|
|
2186
2234
|
});
|
|
2187
2235
|
|
|
2236
|
+
// --- Codex helpers ---
|
|
2237
|
+
function usableAdapters(adapters) {
|
|
2238
|
+
return adapters.filter(a => a.ready).sort((a, b) => (b.isLocal ? 1 : 0) - (a.isLocal ? 1 : 0));
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
function adapterLabels(adapters) {
|
|
2242
|
+
const counts = {};
|
|
2243
|
+
adapters.forEach(a => { counts[a.host] = (counts[a.host] || 0) + 1; });
|
|
2244
|
+
return adapters.map(a => counts[a.host] > 1 ? `${a.host} (${a.id.slice(0, 4)})` : a.host);
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
// A second tap within 0.4 s is a phone double-tap, not a confirmation.
|
|
2248
|
+
function closeStep(armedAt, now) {
|
|
2249
|
+
if (armedAt === null || now - armedAt >= 3000) return 'arm';
|
|
2250
|
+
return now - armedAt < 400 ? 'wait' : 'close';
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2188
2253
|
// --- Add session popup ---
|
|
2189
2254
|
$('#addSessionBtn').addEventListener('click', (e) => {
|
|
2190
2255
|
e.stopPropagation();
|
|
2256
|
+
const opening = !$('#cmdPopup').classList.contains('active');
|
|
2191
2257
|
$('#cmdPopup').classList.toggle('active');
|
|
2258
|
+
if (opening) {
|
|
2259
|
+
renderCodexForm();
|
|
2260
|
+
loadCodexFolders();
|
|
2261
|
+
}
|
|
2192
2262
|
});
|
|
2193
2263
|
document.addEventListener('click', (e) => {
|
|
2194
2264
|
if (!e.target.closest('#cmdPopup') && !e.target.closest('#addSessionBtn')) {
|
|
@@ -2209,6 +2279,97 @@
|
|
|
2209
2279
|
});
|
|
2210
2280
|
});
|
|
2211
2281
|
|
|
2282
|
+
function renderCodexForm() {
|
|
2283
|
+
const usable = usableAdapters(state.codexAdapters);
|
|
2284
|
+
$('#codexSection').hidden = !usable.length;
|
|
2285
|
+
if (!usable.length) return;
|
|
2286
|
+
const select = $('#codexAdapter');
|
|
2287
|
+
const current = select.value;
|
|
2288
|
+
const labels = adapterLabels(usable);
|
|
2289
|
+
select.replaceChildren(...usable.map((a, i) => new Option(labels[i], a.id)));
|
|
2290
|
+
if (usable.some(a => a.id === current)) select.value = current;
|
|
2291
|
+
select.hidden = usable.length < 2;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
async function loadCodexFolders() {
|
|
2295
|
+
const list = $('#codexFolders');
|
|
2296
|
+
list.innerHTML = '';
|
|
2297
|
+
const adapterId = $('#codexAdapter').value;
|
|
2298
|
+
if (!adapterId) return;
|
|
2299
|
+
try {
|
|
2300
|
+
const res = await fetch(`/api/codex/folders?adapterId=${encodeURIComponent(adapterId)}`);
|
|
2301
|
+
if (!res.ok || $('#codexAdapter').value !== adapterId) return;
|
|
2302
|
+
const { folders } = await res.json();
|
|
2303
|
+
list.replaceChildren(...folders.map(f => new Option('', f)));
|
|
2304
|
+
} catch {}
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
async function createCodexConversation() {
|
|
2308
|
+
const btn = $('#codexCreate');
|
|
2309
|
+
const folder = $('#codexFolder').value.trim();
|
|
2310
|
+
const adapterId = $('#codexAdapter').value;
|
|
2311
|
+
if (!folder || !adapterId || btn.disabled) return;
|
|
2312
|
+
const error = $('#codexError');
|
|
2313
|
+
error.hidden = true;
|
|
2314
|
+
btn.disabled = true;
|
|
2315
|
+
btn.textContent = 'Creating…';
|
|
2316
|
+
try {
|
|
2317
|
+
const res = await fetch('/api/codex/threads', {
|
|
2318
|
+
method: 'POST',
|
|
2319
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2320
|
+
body: JSON.stringify({ adapterId, cwd: folder }),
|
|
2321
|
+
});
|
|
2322
|
+
const body = await res.json().catch(() => ({}));
|
|
2323
|
+
if (!res.ok) throw new Error(body.error || `Request failed (${res.status})`);
|
|
2324
|
+
$('#cmdPopup').classList.remove('active');
|
|
2325
|
+
$('#codexFolder').value = '';
|
|
2326
|
+
// The session can register before or after this answer arrives.
|
|
2327
|
+
if (state.sessions[body.sessionId]) selectSession(body.sessionId);
|
|
2328
|
+
else state.pendingSelect = body.sessionId;
|
|
2329
|
+
} catch (err) {
|
|
2330
|
+
error.textContent = err.message;
|
|
2331
|
+
error.hidden = false;
|
|
2332
|
+
} finally {
|
|
2333
|
+
btn.disabled = false;
|
|
2334
|
+
btn.textContent = 'Create';
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
async function onCloseClick(id) {
|
|
2339
|
+
const armedAt = state.closeArmed && state.closeArmed.id === id ? state.closeArmed.at : null;
|
|
2340
|
+
const step = closeStep(armedAt, Date.now());
|
|
2341
|
+
if (step === 'wait') return;
|
|
2342
|
+
if (step === 'arm') {
|
|
2343
|
+
const at = Date.now();
|
|
2344
|
+
state.closeArmed = { id, at };
|
|
2345
|
+
renderSessions();
|
|
2346
|
+
setTimeout(() => {
|
|
2347
|
+
if (state.closeArmed && state.closeArmed.at === at) {
|
|
2348
|
+
state.closeArmed = null;
|
|
2349
|
+
renderSessions();
|
|
2350
|
+
}
|
|
2351
|
+
}, 3000);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
state.closeArmed = null;
|
|
2355
|
+
renderSessions();
|
|
2356
|
+
try {
|
|
2357
|
+
await fetch('/api/codex/threads/close', {
|
|
2358
|
+
method: 'POST',
|
|
2359
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2360
|
+
body: JSON.stringify({ sessionId: id }),
|
|
2361
|
+
});
|
|
2362
|
+
} catch {}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
$('#codexAdapter').addEventListener('change', loadCodexFolders);
|
|
2366
|
+
$('#codexCreate').addEventListener('click', createCodexConversation);
|
|
2367
|
+
$('#codexFolder').addEventListener('keydown', (e) => {
|
|
2368
|
+
if (e.key === 'Enter' && !e.isComposing) {
|
|
2369
|
+
e.preventDefault();
|
|
2370
|
+
createCodexConversation();
|
|
2371
|
+
}
|
|
2372
|
+
});
|
|
2212
2373
|
// --- Settings modal (Webhook + Telegram) ---
|
|
2213
2374
|
let webhookData = [];
|
|
2214
2375
|
const closeSettings = () => $('#settingsOverlay').classList.add('hidden');
|