@lovenyberg/ove 0.7.0 → 0.9.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/bin/ove.ts +37 -1
- package/package.json +1 -1
- package/public/index.html +164 -3
- package/public/metrics.html +716 -0
- package/public/status.html +43 -0
- package/public/trace.html +127 -0
- package/src/adapters/github.test.ts +8 -0
- package/src/adapters/github.ts +3 -2
- package/src/adapters/http.test.ts +597 -1
- package/src/adapters/http.ts +222 -3
- package/src/adapters/slack.test.ts +233 -0
- package/src/adapters/types.ts +1 -1
- package/src/adapters/whatsapp.test.ts +102 -0
- package/src/adapters/wiring.test.ts +2 -0
- package/src/diagnostics.test.ts +375 -0
- package/src/handlers.test.ts +553 -0
- package/src/handlers.ts +46 -7
- package/src/index.ts +1 -0
- package/src/queue.test.ts +174 -0
- package/src/queue.ts +85 -7
- package/src/router.test.ts +151 -1
- package/src/router.ts +95 -34
- package/src/sessions.test.ts +41 -0
- package/src/sessions.ts +27 -0
- package/src/setup.ts +160 -1
- package/src/smoke.test.ts +31 -1
package/bin/ove.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
|
-
import { validateConfig, runSetup } from "../src/setup";
|
|
6
|
+
import { validateConfig, runSetup, runDiagnostics, printDiagnostics } from "../src/setup";
|
|
7
|
+
import { loadConfig } from "../src/config";
|
|
7
8
|
|
|
8
9
|
async function checkForUpdate(): Promise<void> {
|
|
9
10
|
try {
|
|
@@ -37,9 +38,43 @@ if (command === "init") {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
await runSetup();
|
|
41
|
+
|
|
42
|
+
// Run diagnostics after init
|
|
43
|
+
process.stdout.write("\n Checking environment...\n");
|
|
44
|
+
const config = loadConfig();
|
|
45
|
+
const results = await runDiagnostics(config);
|
|
46
|
+
printDiagnostics(results);
|
|
47
|
+
const failures = results.filter(r => r.status === "fail");
|
|
48
|
+
if (failures.length > 0) {
|
|
49
|
+
process.stdout.write(`\n ${failures.length} issue(s) found. Run 'ove doctor' for details.\n`);
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write("\n");
|
|
40
52
|
process.exit(0);
|
|
41
53
|
}
|
|
42
54
|
|
|
55
|
+
if (command === "doctor") {
|
|
56
|
+
process.stdout.write("\n Checking environment...\n");
|
|
57
|
+
const config = loadConfig();
|
|
58
|
+
const results = await runDiagnostics(config);
|
|
59
|
+
printDiagnostics(results);
|
|
60
|
+
|
|
61
|
+
const failures = results.filter(r => r.status === "fail");
|
|
62
|
+
const warnings = results.filter(r => r.status === "warn");
|
|
63
|
+
process.stdout.write("\n");
|
|
64
|
+
if (failures.length === 0 && warnings.length === 0) {
|
|
65
|
+
process.stdout.write(" All checks passed.\n\n");
|
|
66
|
+
} else {
|
|
67
|
+
if (failures.length > 0) {
|
|
68
|
+
process.stdout.write(` ${failures.length} error(s)`);
|
|
69
|
+
}
|
|
70
|
+
if (warnings.length > 0) {
|
|
71
|
+
process.stdout.write(`${failures.length > 0 ? ", " : " "}${warnings.length} warning(s)`);
|
|
72
|
+
}
|
|
73
|
+
process.stdout.write("\n\n");
|
|
74
|
+
}
|
|
75
|
+
process.exit(failures.length > 0 ? 1 : 0);
|
|
76
|
+
}
|
|
77
|
+
|
|
43
78
|
if (command === "start" || !command) {
|
|
44
79
|
await checkForUpdate();
|
|
45
80
|
process.stdout.write(" Checking config...\n");
|
|
@@ -74,6 +109,7 @@ Usage:
|
|
|
74
109
|
ove Start Ove (auto-detects Slack/CLI from env)
|
|
75
110
|
ove start Same as above
|
|
76
111
|
ove init Interactive setup — creates config.json and .env
|
|
112
|
+
ove doctor Check environment, tools, and connections
|
|
77
113
|
ove help Show this message
|
|
78
114
|
|
|
79
115
|
Environment:
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -225,8 +225,23 @@
|
|
|
225
225
|
.msg.ove { color: var(--text); display: flex; gap: 0.5rem; align-items: flex-start; }
|
|
226
226
|
.msg.ove .ove-avatar { width: 20px; height: 20px; border-radius: 3px; flex-shrink: 0; margin-top: 2px; }
|
|
227
227
|
.msg.ove .ove-text { flex: 1; }
|
|
228
|
-
.msg.status { color: var(--text-muted); font-size: 0.75rem; }
|
|
228
|
+
.msg.status { color: var(--text-muted); font-size: 0.75rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
229
229
|
.msg.error { color: var(--red); }
|
|
230
|
+
|
|
231
|
+
.cancel-inline-btn {
|
|
232
|
+
background: none;
|
|
233
|
+
border: 1px solid var(--red-dim);
|
|
234
|
+
color: var(--red);
|
|
235
|
+
font-family: inherit;
|
|
236
|
+
font-size: 0.65rem;
|
|
237
|
+
padding: 0.15rem 0.5rem;
|
|
238
|
+
border-radius: 3px;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
transition: all 0.15s;
|
|
241
|
+
flex-shrink: 0;
|
|
242
|
+
}
|
|
243
|
+
.cancel-inline-btn:hover { background: var(--red-dim); }
|
|
244
|
+
.cancel-inline-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
230
245
|
.msg.system {
|
|
231
246
|
color: var(--text-muted);
|
|
232
247
|
font-size: 0.7rem;
|
|
@@ -274,6 +289,69 @@
|
|
|
274
289
|
::-webkit-scrollbar-track { background: transparent; }
|
|
275
290
|
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
276
291
|
::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
|
|
292
|
+
|
|
293
|
+
/* ── Mobile layout ── */
|
|
294
|
+
@media (max-width: 768px) {
|
|
295
|
+
header {
|
|
296
|
+
padding: 0.4rem 0.5rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.header-left {
|
|
300
|
+
gap: 0.5rem;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.header-left h1 {
|
|
304
|
+
font-size: 0.75rem;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.nav-link {
|
|
308
|
+
font-size: 0.6rem;
|
|
309
|
+
padding: 0.15rem 0.35rem;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.sidebar {
|
|
313
|
+
position: fixed;
|
|
314
|
+
top: 0;
|
|
315
|
+
left: 0;
|
|
316
|
+
bottom: 0;
|
|
317
|
+
width: 85vw;
|
|
318
|
+
max-width: 320px;
|
|
319
|
+
z-index: 100;
|
|
320
|
+
transform: translateX(0);
|
|
321
|
+
transition: transform 0.25s ease;
|
|
322
|
+
border-right: 1px solid var(--border);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.sidebar.collapsed {
|
|
326
|
+
transform: translateX(-100%);
|
|
327
|
+
width: 85vw;
|
|
328
|
+
min-width: 0;
|
|
329
|
+
border-right: none;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.sidebar-overlay {
|
|
333
|
+
display: none;
|
|
334
|
+
position: fixed;
|
|
335
|
+
top: 0;
|
|
336
|
+
left: 0;
|
|
337
|
+
right: 0;
|
|
338
|
+
bottom: 0;
|
|
339
|
+
background: rgba(0, 0, 0, 0.5);
|
|
340
|
+
z-index: 99;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.sidebar-overlay.visible {
|
|
344
|
+
display: block;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.layout {
|
|
348
|
+
position: relative;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#input-bar input {
|
|
352
|
+
font-size: 16px; /* Prevent iOS zoom */
|
|
353
|
+
}
|
|
354
|
+
}
|
|
277
355
|
</style>
|
|
278
356
|
</head>
|
|
279
357
|
<body>
|
|
@@ -283,6 +361,7 @@
|
|
|
283
361
|
<h1>ove</h1>
|
|
284
362
|
<a href="/trace" class="nav-link">traces</a>
|
|
285
363
|
<a href="/status" class="nav-link">status</a>
|
|
364
|
+
<a href="/metrics" class="nav-link">metrics</a>
|
|
286
365
|
<a href="https://github.com/jacksoncage/ove" target="_blank" class="nav-link">github</a>
|
|
287
366
|
</div>
|
|
288
367
|
<div class="header-right">
|
|
@@ -291,6 +370,7 @@
|
|
|
291
370
|
</header>
|
|
292
371
|
|
|
293
372
|
<div class="layout">
|
|
373
|
+
<div class="sidebar-overlay" id="sidebarOverlay"></div>
|
|
294
374
|
<div class="sidebar" id="sidebar">
|
|
295
375
|
<div class="sidebar-section" style="flex:1;display:flex;flex-direction:column">
|
|
296
376
|
<div class="sidebar-section-header">
|
|
@@ -347,15 +427,36 @@
|
|
|
347
427
|
|
|
348
428
|
function clear(node) { while (node.firstChild) node.removeChild(node.firstChild); }
|
|
349
429
|
|
|
430
|
+
var sidebarOverlayEl = document.getElementById("sidebarOverlay");
|
|
431
|
+
|
|
350
432
|
// ── Sidebar toggle ──
|
|
351
|
-
var
|
|
433
|
+
var isMobile = window.matchMedia("(max-width: 768px)").matches;
|
|
434
|
+
var sidebarHidden = isMobile ? true : localStorage.getItem("ove-sidebar") === "hidden";
|
|
352
435
|
if (sidebarHidden) sidebarEl.classList.add("collapsed");
|
|
353
436
|
|
|
437
|
+
function updateSidebarOverlay() {
|
|
438
|
+
var mobile = window.matchMedia("(max-width: 768px)").matches;
|
|
439
|
+
if (mobile && !sidebarEl.classList.contains("collapsed")) {
|
|
440
|
+
sidebarOverlayEl.classList.add("visible");
|
|
441
|
+
} else {
|
|
442
|
+
sidebarOverlayEl.classList.remove("visible");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
354
446
|
sidebarToggleEl.addEventListener("click", function () {
|
|
355
447
|
sidebarEl.classList.toggle("collapsed");
|
|
356
448
|
localStorage.setItem("ove-sidebar", sidebarEl.classList.contains("collapsed") ? "hidden" : "visible");
|
|
449
|
+
updateSidebarOverlay();
|
|
357
450
|
});
|
|
358
451
|
|
|
452
|
+
sidebarOverlayEl.addEventListener("click", function () {
|
|
453
|
+
sidebarEl.classList.add("collapsed");
|
|
454
|
+
localStorage.setItem("ove-sidebar", "hidden");
|
|
455
|
+
updateSidebarOverlay();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
window.addEventListener("resize", updateSidebarOverlay);
|
|
459
|
+
|
|
359
460
|
// ── Chat messages ──
|
|
360
461
|
function addMsg(text, cls) {
|
|
361
462
|
var div = el("div", "msg " + cls);
|
|
@@ -392,6 +493,60 @@
|
|
|
392
493
|
var eventId = data.eventId;
|
|
393
494
|
|
|
394
495
|
var statusDiv = addMsg("Working...", "status");
|
|
496
|
+
var cancelBtn = document.createElement("button");
|
|
497
|
+
cancelBtn.className = "cancel-inline-btn";
|
|
498
|
+
cancelBtn.textContent = "cancel";
|
|
499
|
+
statusDiv.appendChild(cancelBtn);
|
|
500
|
+
|
|
501
|
+
// We need to find the task ID from the most recent tasks
|
|
502
|
+
var currentTaskId = null;
|
|
503
|
+
cancelBtn.addEventListener("click", async function () {
|
|
504
|
+
cancelBtn.disabled = true;
|
|
505
|
+
cancelBtn.textContent = "cancelling...";
|
|
506
|
+
// Try to find the running task for this user
|
|
507
|
+
if (!currentTaskId) {
|
|
508
|
+
try {
|
|
509
|
+
var tasksRes = await fetch("/api/tasks?status=running&limit=5&key=" + encodeURIComponent(API_KEY), { headers: apiHeaders() });
|
|
510
|
+
if (tasksRes.ok) {
|
|
511
|
+
var tasks = await tasksRes.json();
|
|
512
|
+
var userTask = tasks.find(function (t) { return t.userId === "http:web"; });
|
|
513
|
+
if (userTask) currentTaskId = userTask.id;
|
|
514
|
+
}
|
|
515
|
+
} catch (e) {}
|
|
516
|
+
}
|
|
517
|
+
if (!currentTaskId) {
|
|
518
|
+
try {
|
|
519
|
+
var tasksRes = await fetch("/api/tasks?status=pending&limit=5&key=" + encodeURIComponent(API_KEY), { headers: apiHeaders() });
|
|
520
|
+
if (tasksRes.ok) {
|
|
521
|
+
var tasks = await tasksRes.json();
|
|
522
|
+
var userTask = tasks.find(function (t) { return t.userId === "http:web"; });
|
|
523
|
+
if (userTask) currentTaskId = userTask.id;
|
|
524
|
+
}
|
|
525
|
+
} catch (e) {}
|
|
526
|
+
}
|
|
527
|
+
if (currentTaskId) {
|
|
528
|
+
try {
|
|
529
|
+
var cancelRes = await fetch("/api/tasks/" + encodeURIComponent(currentTaskId) + "/cancel", {
|
|
530
|
+
method: "POST",
|
|
531
|
+
headers: apiHeaders(),
|
|
532
|
+
});
|
|
533
|
+
if (cancelRes.ok) {
|
|
534
|
+
statusDiv.remove();
|
|
535
|
+
addMsg("Task cancelled.", "status");
|
|
536
|
+
sse.close();
|
|
537
|
+
} else {
|
|
538
|
+
cancelBtn.textContent = "failed";
|
|
539
|
+
setTimeout(function () { cancelBtn.textContent = "cancel"; cancelBtn.disabled = false; }, 2000);
|
|
540
|
+
}
|
|
541
|
+
} catch (e) {
|
|
542
|
+
cancelBtn.textContent = "error";
|
|
543
|
+
setTimeout(function () { cancelBtn.textContent = "cancel"; cancelBtn.disabled = false; }, 2000);
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
cancelBtn.textContent = "no task found";
|
|
547
|
+
setTimeout(function () { cancelBtn.textContent = "cancel"; cancelBtn.disabled = false; }, 2000);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
395
550
|
|
|
396
551
|
var sse = new EventSource("/api/message/" + eventId + "/stream?key=" + encodeURIComponent(API_KEY));
|
|
397
552
|
sse.onmessage = function (e) {
|
|
@@ -403,7 +558,13 @@
|
|
|
403
558
|
// Refresh sidebar
|
|
404
559
|
fetchHistory();
|
|
405
560
|
} else if (d.statusText) {
|
|
406
|
-
|
|
561
|
+
// Update status text but keep cancel button
|
|
562
|
+
var statusText = statusDiv.firstChild;
|
|
563
|
+
if (statusText && statusText.nodeType === Node.TEXT_NODE) {
|
|
564
|
+
statusText.textContent = d.statusText;
|
|
565
|
+
} else {
|
|
566
|
+
statusDiv.insertBefore(document.createTextNode(d.statusText), statusDiv.firstChild);
|
|
567
|
+
}
|
|
407
568
|
}
|
|
408
569
|
};
|
|
409
570
|
sse.onerror = function () { sse.close(); };
|