@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovenyberg/ove",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Your grumpy but meticulous dev companion. AI coding agent for Slack, WhatsApp, Telegram, Discord, GitHub, HTTP API, and CLI.",
5
5
  "type": "module",
6
6
  "bin": {
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 sidebarHidden = localStorage.getItem("ove-sidebar") === "hidden";
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
- statusDiv.textContent = d.statusText;
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(); };