vibora 1.6.0 → 1.7.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/vibora.js +11 -8
- package/dist/assets/index-DDBvX7Ij.css +1 -0
- package/dist/assets/index-DS5B_viR.js +116 -0
- package/dist/index.html +2 -2
- package/drizzle/0004_dear_beyonder.sql +9 -0
- package/drizzle/meta/0004_snapshot.json +464 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/server/index.js +388 -20
package/server/index.js
CHANGED
|
@@ -7667,6 +7667,7 @@ __export(exports_schema, {
|
|
|
7667
7667
|
terminalViewState: () => terminalViewState,
|
|
7668
7668
|
terminalTabs: () => terminalTabs,
|
|
7669
7669
|
tasks: () => tasks,
|
|
7670
|
+
systemMetrics: () => systemMetrics,
|
|
7670
7671
|
repositories: () => repositories
|
|
7671
7672
|
});
|
|
7672
7673
|
var tasks = sqliteTable("tasks", {
|
|
@@ -7725,6 +7726,15 @@ var repositories = sqliteTable("repositories", {
|
|
|
7725
7726
|
createdAt: text("created_at").notNull(),
|
|
7726
7727
|
updatedAt: text("updated_at").notNull()
|
|
7727
7728
|
});
|
|
7729
|
+
var systemMetrics = sqliteTable("system_metrics", {
|
|
7730
|
+
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
7731
|
+
timestamp: integer("timestamp").notNull(),
|
|
7732
|
+
cpuPercent: real("cpu_percent").notNull(),
|
|
7733
|
+
memoryUsedBytes: integer("memory_used_bytes").notNull(),
|
|
7734
|
+
memoryTotalBytes: integer("memory_total_bytes").notNull(),
|
|
7735
|
+
diskUsedBytes: integer("disk_used_bytes").notNull(),
|
|
7736
|
+
diskTotalBytes: integer("disk_total_bytes").notNull()
|
|
7737
|
+
});
|
|
7728
7738
|
|
|
7729
7739
|
// server/db/index.ts
|
|
7730
7740
|
initializeViboraDirectories();
|
|
@@ -137940,9 +137950,9 @@ app2.delete("/bulk", async (c) => {
|
|
|
137940
137950
|
continue;
|
|
137941
137951
|
if (existing.worktreePath) {
|
|
137942
137952
|
destroyTerminalsForWorktree(existing.worktreePath);
|
|
137943
|
-
|
|
137944
|
-
|
|
137945
|
-
|
|
137953
|
+
if (body.deleteLinkedWorktrees && existing.repoPath) {
|
|
137954
|
+
deleteGitWorktree(existing.repoPath, existing.worktreePath);
|
|
137955
|
+
}
|
|
137946
137956
|
}
|
|
137947
137957
|
const columnTasks = db.select().from(tasks).where(eq(tasks.status, existing.status)).all();
|
|
137948
137958
|
for (const t of columnTasks) {
|
|
@@ -137996,15 +138006,16 @@ app2.patch("/:id", async (c) => {
|
|
|
137996
138006
|
});
|
|
137997
138007
|
app2.delete("/:id", (c) => {
|
|
137998
138008
|
const id = c.req.param("id");
|
|
138009
|
+
const deleteLinkedWorktree = c.req.query("deleteLinkedWorktree") === "true";
|
|
137999
138010
|
const existing = db.select().from(tasks).where(eq(tasks.id, id)).get();
|
|
138000
138011
|
if (!existing) {
|
|
138001
138012
|
return c.json({ error: "Task not found" }, 404);
|
|
138002
138013
|
}
|
|
138003
138014
|
if (existing.worktreePath) {
|
|
138004
138015
|
destroyTerminalsForWorktree(existing.worktreePath);
|
|
138005
|
-
|
|
138006
|
-
|
|
138007
|
-
|
|
138016
|
+
if (deleteLinkedWorktree && existing.repoPath) {
|
|
138017
|
+
deleteGitWorktree(existing.repoPath, existing.worktreePath);
|
|
138018
|
+
}
|
|
138008
138019
|
}
|
|
138009
138020
|
const columnTasks = db.select().from(tasks).where(eq(tasks.status, existing.status)).all();
|
|
138010
138021
|
const now = new Date().toISOString();
|
|
@@ -139504,15 +139515,19 @@ app7.delete("/", async (c) => {
|
|
|
139504
139515
|
await deleteWorktree(body.worktreePath, body.repoPath || linkedTask?.repoPath);
|
|
139505
139516
|
let deletedTaskId;
|
|
139506
139517
|
if (linkedTask) {
|
|
139507
|
-
const columnTasks = db.select().from(tasks).where(eq(tasks.status, linkedTask.status)).all();
|
|
139508
139518
|
const now = new Date().toISOString();
|
|
139509
|
-
|
|
139510
|
-
|
|
139511
|
-
|
|
139519
|
+
if (body.deleteLinkedTask) {
|
|
139520
|
+
const columnTasks = db.select().from(tasks).where(eq(tasks.status, linkedTask.status)).all();
|
|
139521
|
+
for (const t of columnTasks) {
|
|
139522
|
+
if (t.position > linkedTask.position) {
|
|
139523
|
+
db.update(tasks).set({ position: t.position - 1, updatedAt: now }).where(eq(tasks.id, t.id)).run();
|
|
139524
|
+
}
|
|
139512
139525
|
}
|
|
139526
|
+
db.delete(tasks).where(eq(tasks.id, linkedTask.id)).run();
|
|
139527
|
+
deletedTaskId = linkedTask.id;
|
|
139528
|
+
} else {
|
|
139529
|
+
db.update(tasks).set({ worktreePath: null, updatedAt: now }).where(eq(tasks.id, linkedTask.id)).run();
|
|
139513
139530
|
}
|
|
139514
|
-
db.delete(tasks).where(eq(tasks.id, linkedTask.id)).run();
|
|
139515
|
-
deletedTaskId = linkedTask.id;
|
|
139516
139531
|
}
|
|
139517
139532
|
return c.json({ success: true, path: body.worktreePath, deletedTaskId });
|
|
139518
139533
|
} catch (err) {
|
|
@@ -143434,6 +143449,355 @@ app12.get("/check", async (c) => {
|
|
|
143434
143449
|
});
|
|
143435
143450
|
var auth_default = app12;
|
|
143436
143451
|
|
|
143452
|
+
// server/routes/monitoring.ts
|
|
143453
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync7, readlinkSync as readlinkSync2 } from "fs";
|
|
143454
|
+
import { execSync as execSync5 } from "child_process";
|
|
143455
|
+
|
|
143456
|
+
// server/services/metrics-collector.ts
|
|
143457
|
+
import os5 from "os";
|
|
143458
|
+
import { execSync as execSync4 } from "child_process";
|
|
143459
|
+
var COLLECT_INTERVAL = 5000;
|
|
143460
|
+
var RETENTION_HOURS = 24;
|
|
143461
|
+
var intervalId = null;
|
|
143462
|
+
var previousCpu = null;
|
|
143463
|
+
function getCpuSnapshot() {
|
|
143464
|
+
const cpus = os5.cpus();
|
|
143465
|
+
let idle = 0;
|
|
143466
|
+
let total = 0;
|
|
143467
|
+
for (const cpu of cpus) {
|
|
143468
|
+
idle += cpu.times.idle;
|
|
143469
|
+
total += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq;
|
|
143470
|
+
}
|
|
143471
|
+
return { idle, total };
|
|
143472
|
+
}
|
|
143473
|
+
function calculateCpuPercent() {
|
|
143474
|
+
const current = getCpuSnapshot();
|
|
143475
|
+
if (!previousCpu) {
|
|
143476
|
+
previousCpu = current;
|
|
143477
|
+
return 0;
|
|
143478
|
+
}
|
|
143479
|
+
const idleDiff = current.idle - previousCpu.idle;
|
|
143480
|
+
const totalDiff = current.total - previousCpu.total;
|
|
143481
|
+
previousCpu = current;
|
|
143482
|
+
if (totalDiff === 0)
|
|
143483
|
+
return 0;
|
|
143484
|
+
const usedPercent = (totalDiff - idleDiff) / totalDiff * 100;
|
|
143485
|
+
return Math.round(usedPercent * 100) / 100;
|
|
143486
|
+
}
|
|
143487
|
+
function getDiskUsage() {
|
|
143488
|
+
try {
|
|
143489
|
+
const output = execSync4("df -B1 / | tail -1", {
|
|
143490
|
+
encoding: "utf-8",
|
|
143491
|
+
timeout: 5000,
|
|
143492
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
143493
|
+
});
|
|
143494
|
+
const parts = output.trim().split(/\s+/);
|
|
143495
|
+
if (parts.length >= 4) {
|
|
143496
|
+
const total = parseInt(parts[1], 10) || 0;
|
|
143497
|
+
const used = parseInt(parts[2], 10) || 0;
|
|
143498
|
+
return { used, total };
|
|
143499
|
+
}
|
|
143500
|
+
} catch (err) {
|
|
143501
|
+
console.error("Failed to get disk usage:", err);
|
|
143502
|
+
}
|
|
143503
|
+
return { used: 0, total: 0 };
|
|
143504
|
+
}
|
|
143505
|
+
function collectMetrics() {
|
|
143506
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
143507
|
+
const cpuPercent = calculateCpuPercent();
|
|
143508
|
+
const memoryTotal = os5.totalmem();
|
|
143509
|
+
const memoryFree = os5.freemem();
|
|
143510
|
+
const memoryUsed = memoryTotal - memoryFree;
|
|
143511
|
+
const disk = getDiskUsage();
|
|
143512
|
+
db.insert(systemMetrics).values({
|
|
143513
|
+
timestamp,
|
|
143514
|
+
cpuPercent,
|
|
143515
|
+
memoryUsedBytes: memoryUsed,
|
|
143516
|
+
memoryTotalBytes: memoryTotal,
|
|
143517
|
+
diskUsedBytes: disk.used,
|
|
143518
|
+
diskTotalBytes: disk.total
|
|
143519
|
+
}).run();
|
|
143520
|
+
}
|
|
143521
|
+
function pruneOldMetrics() {
|
|
143522
|
+
const cutoff = Math.floor(Date.now() / 1000) - RETENTION_HOURS * 60 * 60;
|
|
143523
|
+
const result = db.delete(systemMetrics).where(lt(systemMetrics.timestamp, cutoff)).run();
|
|
143524
|
+
if (result.changes > 0) {
|
|
143525
|
+
console.log(`[MetricsCollector] Pruned ${result.changes} old metrics records`);
|
|
143526
|
+
}
|
|
143527
|
+
}
|
|
143528
|
+
function startMetricsCollector() {
|
|
143529
|
+
if (intervalId)
|
|
143530
|
+
return;
|
|
143531
|
+
console.log(`Metrics collector started (${COLLECT_INTERVAL / 1000}s interval)`);
|
|
143532
|
+
previousCpu = getCpuSnapshot();
|
|
143533
|
+
setTimeout(() => {
|
|
143534
|
+
collectMetrics();
|
|
143535
|
+
}, 1000);
|
|
143536
|
+
intervalId = setInterval(() => {
|
|
143537
|
+
collectMetrics();
|
|
143538
|
+
}, COLLECT_INTERVAL);
|
|
143539
|
+
setInterval(() => {
|
|
143540
|
+
pruneOldMetrics();
|
|
143541
|
+
}, 60 * 60 * 1000);
|
|
143542
|
+
pruneOldMetrics();
|
|
143543
|
+
}
|
|
143544
|
+
function stopMetricsCollector() {
|
|
143545
|
+
if (intervalId) {
|
|
143546
|
+
clearInterval(intervalId);
|
|
143547
|
+
intervalId = null;
|
|
143548
|
+
console.log("Metrics collector stopped");
|
|
143549
|
+
}
|
|
143550
|
+
}
|
|
143551
|
+
function getMetrics(windowSeconds) {
|
|
143552
|
+
const cutoff = Math.floor(Date.now() / 1000) - windowSeconds;
|
|
143553
|
+
const rows = db.select().from(systemMetrics).where(lt(systemMetrics.timestamp, cutoff) ? undefined : undefined).all().filter((r) => r.timestamp >= cutoff);
|
|
143554
|
+
return rows.map((row) => ({
|
|
143555
|
+
timestamp: row.timestamp,
|
|
143556
|
+
cpuPercent: row.cpuPercent,
|
|
143557
|
+
memoryUsedPercent: row.memoryTotalBytes > 0 ? row.memoryUsedBytes / row.memoryTotalBytes * 100 : 0,
|
|
143558
|
+
diskUsedPercent: row.diskTotalBytes > 0 ? row.diskUsedBytes / row.diskTotalBytes * 100 : 0
|
|
143559
|
+
}));
|
|
143560
|
+
}
|
|
143561
|
+
function getCurrentMetrics() {
|
|
143562
|
+
const memoryTotal = os5.totalmem();
|
|
143563
|
+
const memoryFree = os5.freemem();
|
|
143564
|
+
const memoryUsed = memoryTotal - memoryFree;
|
|
143565
|
+
const disk = getDiskUsage();
|
|
143566
|
+
const latest = db.select().from(systemMetrics).orderBy(systemMetrics.timestamp).limit(1).all();
|
|
143567
|
+
return {
|
|
143568
|
+
cpu: latest.length > 0 ? latest[0].cpuPercent : 0,
|
|
143569
|
+
memory: {
|
|
143570
|
+
total: memoryTotal,
|
|
143571
|
+
used: memoryUsed,
|
|
143572
|
+
usedPercent: memoryUsed / memoryTotal * 100
|
|
143573
|
+
},
|
|
143574
|
+
disk: {
|
|
143575
|
+
total: disk.total,
|
|
143576
|
+
used: disk.used,
|
|
143577
|
+
usedPercent: disk.total > 0 ? disk.used / disk.total * 100 : 0,
|
|
143578
|
+
path: "/"
|
|
143579
|
+
}
|
|
143580
|
+
};
|
|
143581
|
+
}
|
|
143582
|
+
|
|
143583
|
+
// server/routes/monitoring.ts
|
|
143584
|
+
function parseWindow(window) {
|
|
143585
|
+
const match3 = window.match(/^(\d+)(m|h)$/);
|
|
143586
|
+
if (!match3)
|
|
143587
|
+
return 3600;
|
|
143588
|
+
const value = parseInt(match3[1], 10);
|
|
143589
|
+
const unit = match3[2];
|
|
143590
|
+
if (unit === "m")
|
|
143591
|
+
return value * 60;
|
|
143592
|
+
if (unit === "h")
|
|
143593
|
+
return value * 3600;
|
|
143594
|
+
return 3600;
|
|
143595
|
+
}
|
|
143596
|
+
function findAllClaudeProcesses() {
|
|
143597
|
+
const claudeProcesses = [];
|
|
143598
|
+
try {
|
|
143599
|
+
const procDirs = readdirSync7("/proc").filter((d) => /^\d+$/.test(d));
|
|
143600
|
+
for (const pidStr of procDirs) {
|
|
143601
|
+
const pid = parseInt(pidStr, 10);
|
|
143602
|
+
try {
|
|
143603
|
+
const cmdline = readFileSync7(`/proc/${pid}/cmdline`, "utf-8");
|
|
143604
|
+
if (/\bclaude\b/i.test(cmdline)) {
|
|
143605
|
+
claudeProcesses.push({ pid, cmdline });
|
|
143606
|
+
}
|
|
143607
|
+
} catch {}
|
|
143608
|
+
}
|
|
143609
|
+
} catch {
|
|
143610
|
+
try {
|
|
143611
|
+
const result = execSync5("pgrep -f claude", { encoding: "utf-8" });
|
|
143612
|
+
for (const line of result.trim().split(`
|
|
143613
|
+
`)) {
|
|
143614
|
+
const pid = parseInt(line, 10);
|
|
143615
|
+
if (!isNaN(pid)) {
|
|
143616
|
+
try {
|
|
143617
|
+
const cmdline = execSync5(`ps -p ${pid} -o args=`, { encoding: "utf-8" }).trim();
|
|
143618
|
+
claudeProcesses.push({ pid, cmdline });
|
|
143619
|
+
} catch {
|
|
143620
|
+
claudeProcesses.push({ pid, cmdline: "claude" });
|
|
143621
|
+
}
|
|
143622
|
+
}
|
|
143623
|
+
}
|
|
143624
|
+
} catch {}
|
|
143625
|
+
}
|
|
143626
|
+
return claudeProcesses;
|
|
143627
|
+
}
|
|
143628
|
+
function getProcessCwd(pid) {
|
|
143629
|
+
try {
|
|
143630
|
+
return readlinkSync2(`/proc/${pid}/cwd`);
|
|
143631
|
+
} catch {
|
|
143632
|
+
try {
|
|
143633
|
+
const result = execSync5(`lsof -p ${pid} -d cwd -Fn 2>/dev/null | grep ^n | cut -c2-`, {
|
|
143634
|
+
encoding: "utf-8"
|
|
143635
|
+
});
|
|
143636
|
+
return result.trim() || "(unknown)";
|
|
143637
|
+
} catch {
|
|
143638
|
+
return "(unknown)";
|
|
143639
|
+
}
|
|
143640
|
+
}
|
|
143641
|
+
}
|
|
143642
|
+
function getProcessMemoryMB(pid) {
|
|
143643
|
+
try {
|
|
143644
|
+
const status = readFileSync7(`/proc/${pid}/status`, "utf-8");
|
|
143645
|
+
const match3 = status.match(/VmRSS:\s+(\d+)\s+kB/);
|
|
143646
|
+
return match3 ? parseInt(match3[1], 10) / 1024 : 0;
|
|
143647
|
+
} catch {
|
|
143648
|
+
try {
|
|
143649
|
+
const result = execSync5(`ps -o rss= -p ${pid}`, { encoding: "utf-8" });
|
|
143650
|
+
return parseInt(result.trim(), 10) / 1024;
|
|
143651
|
+
} catch {
|
|
143652
|
+
return 0;
|
|
143653
|
+
}
|
|
143654
|
+
}
|
|
143655
|
+
}
|
|
143656
|
+
function getProcessStartTime(pid) {
|
|
143657
|
+
try {
|
|
143658
|
+
const stat = readFileSync7(`/proc/${pid}/stat`, "utf-8");
|
|
143659
|
+
const fields = stat.split(" ");
|
|
143660
|
+
const starttime = parseInt(fields[21], 10);
|
|
143661
|
+
const uptime = parseFloat(readFileSync7("/proc/uptime", "utf-8").split(" ")[0]);
|
|
143662
|
+
const clockTicks = 100;
|
|
143663
|
+
const bootTime = Math.floor(Date.now() / 1000) - uptime;
|
|
143664
|
+
return Math.floor(bootTime + starttime / clockTicks);
|
|
143665
|
+
} catch {
|
|
143666
|
+
return null;
|
|
143667
|
+
}
|
|
143668
|
+
}
|
|
143669
|
+
function getDescendantPids2(pid) {
|
|
143670
|
+
const descendants = [];
|
|
143671
|
+
try {
|
|
143672
|
+
const result = execSync5(`ps --ppid ${pid} -o pid= 2>/dev/null || true`, { encoding: "utf-8" });
|
|
143673
|
+
for (const line of result.trim().split(`
|
|
143674
|
+
`)) {
|
|
143675
|
+
const childPid = parseInt(line.trim(), 10);
|
|
143676
|
+
if (!isNaN(childPid)) {
|
|
143677
|
+
descendants.push(childPid);
|
|
143678
|
+
descendants.push(...getDescendantPids2(childPid));
|
|
143679
|
+
}
|
|
143680
|
+
}
|
|
143681
|
+
} catch {}
|
|
143682
|
+
return descendants;
|
|
143683
|
+
}
|
|
143684
|
+
var monitoringRoutes = new Hono2;
|
|
143685
|
+
monitoringRoutes.get("/claude-instances", (c) => {
|
|
143686
|
+
const filter2 = c.req.query("filter") || "vibora";
|
|
143687
|
+
const allClaudeProcesses = findAllClaudeProcesses();
|
|
143688
|
+
const viboraManagedPids = new Map;
|
|
143689
|
+
try {
|
|
143690
|
+
const ptyManager2 = getPTYManager();
|
|
143691
|
+
const terminals2 = ptyManager2.listTerminals();
|
|
143692
|
+
const dtachService2 = getDtachService();
|
|
143693
|
+
for (const terminal of terminals2) {
|
|
143694
|
+
const socketPath = dtachService2.getSocketPath(terminal.id);
|
|
143695
|
+
try {
|
|
143696
|
+
const procDirs = readdirSync7("/proc").filter((d) => /^\d+$/.test(d));
|
|
143697
|
+
for (const pidStr of procDirs) {
|
|
143698
|
+
const pid = parseInt(pidStr, 10);
|
|
143699
|
+
try {
|
|
143700
|
+
const cmdline = readFileSync7(`/proc/${pid}/cmdline`, "utf-8");
|
|
143701
|
+
if (cmdline.includes(socketPath)) {
|
|
143702
|
+
const descendants = getDescendantPids2(pid);
|
|
143703
|
+
for (const descendantPid of [...descendants, pid]) {
|
|
143704
|
+
viboraManagedPids.set(descendantPid, {
|
|
143705
|
+
terminalId: terminal.id,
|
|
143706
|
+
terminalName: terminal.name,
|
|
143707
|
+
cwd: terminal.cwd
|
|
143708
|
+
});
|
|
143709
|
+
}
|
|
143710
|
+
}
|
|
143711
|
+
} catch {}
|
|
143712
|
+
}
|
|
143713
|
+
} catch {}
|
|
143714
|
+
}
|
|
143715
|
+
} catch {}
|
|
143716
|
+
const allTasks = db.select().from(tasks).all();
|
|
143717
|
+
const tasksByWorktree = new Map(allTasks.filter((t) => t.worktreePath).map((t) => [t.worktreePath, t]));
|
|
143718
|
+
const instances = [];
|
|
143719
|
+
for (const { pid } of allClaudeProcesses) {
|
|
143720
|
+
const viboraInfo = viboraManagedPids.get(pid);
|
|
143721
|
+
const isViboraManaged = !!viboraInfo;
|
|
143722
|
+
if (filter2 === "vibora" && !isViboraManaged) {
|
|
143723
|
+
continue;
|
|
143724
|
+
}
|
|
143725
|
+
const cwd = viboraInfo?.cwd || getProcessCwd(pid);
|
|
143726
|
+
const ramMB = Math.round(getProcessMemoryMB(pid) * 10) / 10;
|
|
143727
|
+
const startedAt = getProcessStartTime(pid);
|
|
143728
|
+
let taskId = null;
|
|
143729
|
+
let taskTitle = null;
|
|
143730
|
+
let worktreePath = null;
|
|
143731
|
+
if (viboraInfo) {
|
|
143732
|
+
const task = tasksByWorktree.get(viboraInfo.cwd);
|
|
143733
|
+
if (task) {
|
|
143734
|
+
taskId = task.id;
|
|
143735
|
+
taskTitle = task.title;
|
|
143736
|
+
worktreePath = viboraInfo.cwd;
|
|
143737
|
+
}
|
|
143738
|
+
}
|
|
143739
|
+
instances.push({
|
|
143740
|
+
pid,
|
|
143741
|
+
cwd,
|
|
143742
|
+
ramMB,
|
|
143743
|
+
startedAt,
|
|
143744
|
+
terminalId: viboraInfo?.terminalId || null,
|
|
143745
|
+
terminalName: viboraInfo?.terminalName || null,
|
|
143746
|
+
taskId,
|
|
143747
|
+
taskTitle,
|
|
143748
|
+
worktreePath,
|
|
143749
|
+
isViboraManaged
|
|
143750
|
+
});
|
|
143751
|
+
}
|
|
143752
|
+
instances.sort((a, b) => {
|
|
143753
|
+
if (a.isViboraManaged !== b.isViboraManaged) {
|
|
143754
|
+
return a.isViboraManaged ? -1 : 1;
|
|
143755
|
+
}
|
|
143756
|
+
return b.ramMB - a.ramMB;
|
|
143757
|
+
});
|
|
143758
|
+
return c.json(instances);
|
|
143759
|
+
});
|
|
143760
|
+
monitoringRoutes.get("/system-metrics", (c) => {
|
|
143761
|
+
const windowStr = c.req.query("window") || "1h";
|
|
143762
|
+
const windowSeconds = parseWindow(windowStr);
|
|
143763
|
+
const dataPoints = getMetrics(windowSeconds);
|
|
143764
|
+
const current = getCurrentMetrics();
|
|
143765
|
+
return c.json({
|
|
143766
|
+
window: windowStr,
|
|
143767
|
+
dataPoints,
|
|
143768
|
+
current
|
|
143769
|
+
});
|
|
143770
|
+
});
|
|
143771
|
+
monitoringRoutes.post("/claude-instances/:terminalId/kill", (c) => {
|
|
143772
|
+
const terminalId = c.req.param("terminalId");
|
|
143773
|
+
try {
|
|
143774
|
+
const dtachService2 = getDtachService();
|
|
143775
|
+
const killed = dtachService2.killClaudeInSession(terminalId);
|
|
143776
|
+
return c.json({ success: true, killed });
|
|
143777
|
+
} catch (err) {
|
|
143778
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
143779
|
+
return c.json({ error: message }, 500);
|
|
143780
|
+
}
|
|
143781
|
+
});
|
|
143782
|
+
monitoringRoutes.post("/claude-instances/:pid/kill-pid", (c) => {
|
|
143783
|
+
const pidStr = c.req.param("pid");
|
|
143784
|
+
const pid = parseInt(pidStr, 10);
|
|
143785
|
+
if (isNaN(pid)) {
|
|
143786
|
+
return c.json({ error: "Invalid PID" }, 400);
|
|
143787
|
+
}
|
|
143788
|
+
try {
|
|
143789
|
+
const cmdline = readFileSync7(`/proc/${pid}/cmdline`, "utf-8");
|
|
143790
|
+
if (!/\bclaude\b/i.test(cmdline)) {
|
|
143791
|
+
return c.json({ error: "Process is not a Claude instance" }, 400);
|
|
143792
|
+
}
|
|
143793
|
+
process.kill(pid, "SIGTERM");
|
|
143794
|
+
return c.json({ success: true, killed: true });
|
|
143795
|
+
} catch (err) {
|
|
143796
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
143797
|
+
return c.json({ error: message }, 500);
|
|
143798
|
+
}
|
|
143799
|
+
});
|
|
143800
|
+
|
|
143437
143801
|
// server/app.ts
|
|
143438
143802
|
function getDistPath() {
|
|
143439
143803
|
if (process.env.VIBORA_PACKAGE_ROOT) {
|
|
@@ -143462,6 +143826,7 @@ function createApp() {
|
|
|
143462
143826
|
app13.route("/api/linear", linear_default);
|
|
143463
143827
|
app13.route("/api/github", github_default);
|
|
143464
143828
|
app13.route("/api/auth", auth_default);
|
|
143829
|
+
app13.route("/api/monitoring", monitoringRoutes);
|
|
143465
143830
|
if (process.env.VIBORA_PACKAGE_ROOT) {
|
|
143466
143831
|
const distPath = getDistPath();
|
|
143467
143832
|
const serveFile = async (filePath) => {
|
|
@@ -143514,7 +143879,7 @@ function createApp() {
|
|
|
143514
143879
|
}
|
|
143515
143880
|
|
|
143516
143881
|
// server/services/pr-monitor.ts
|
|
143517
|
-
import { execSync as
|
|
143882
|
+
import { execSync as execSync6 } from "child_process";
|
|
143518
143883
|
var POLL_INTERVAL = 60000;
|
|
143519
143884
|
function parsePrUrl(url) {
|
|
143520
143885
|
const match3 = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
@@ -143529,7 +143894,7 @@ function checkPrStatus(prUrl) {
|
|
|
143529
143894
|
return null;
|
|
143530
143895
|
}
|
|
143531
143896
|
try {
|
|
143532
|
-
const output =
|
|
143897
|
+
const output = execSync6(`gh pr view ${parsed.number} --repo ${parsed.owner}/${parsed.repo} --json state,mergedAt`, { encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] });
|
|
143533
143898
|
const data = JSON.parse(output);
|
|
143534
143899
|
return {
|
|
143535
143900
|
state: data.state,
|
|
@@ -143555,20 +143920,20 @@ async function pollPRs() {
|
|
|
143555
143920
|
}
|
|
143556
143921
|
}
|
|
143557
143922
|
}
|
|
143558
|
-
var
|
|
143923
|
+
var intervalId2 = null;
|
|
143559
143924
|
function startPRMonitor() {
|
|
143560
|
-
if (
|
|
143925
|
+
if (intervalId2)
|
|
143561
143926
|
return;
|
|
143562
143927
|
console.log("PR Monitor started (60s interval)");
|
|
143563
143928
|
pollPRs().catch(console.error);
|
|
143564
|
-
|
|
143929
|
+
intervalId2 = setInterval(() => {
|
|
143565
143930
|
pollPRs().catch(console.error);
|
|
143566
143931
|
}, POLL_INTERVAL);
|
|
143567
143932
|
}
|
|
143568
143933
|
function stopPRMonitor() {
|
|
143569
|
-
if (
|
|
143570
|
-
clearInterval(
|
|
143571
|
-
|
|
143934
|
+
if (intervalId2) {
|
|
143935
|
+
clearInterval(intervalId2);
|
|
143936
|
+
intervalId2 = null;
|
|
143572
143937
|
console.log("PR Monitor stopped");
|
|
143573
143938
|
}
|
|
143574
143939
|
}
|
|
@@ -143611,10 +143976,12 @@ var server = serve({
|
|
|
143611
143976
|
});
|
|
143612
143977
|
injectWebSocket(server);
|
|
143613
143978
|
startPRMonitor();
|
|
143979
|
+
startMetricsCollector();
|
|
143614
143980
|
process.on("SIGINT", () => {
|
|
143615
143981
|
console.log(`
|
|
143616
143982
|
Shutting down (terminals will persist)...`);
|
|
143617
143983
|
stopPRMonitor();
|
|
143984
|
+
stopMetricsCollector();
|
|
143618
143985
|
ptyManager2.detachAll();
|
|
143619
143986
|
server.close();
|
|
143620
143987
|
process.exit(0);
|
|
@@ -143623,6 +143990,7 @@ process.on("SIGTERM", () => {
|
|
|
143623
143990
|
console.log(`
|
|
143624
143991
|
Shutting down (terminals will persist)...`);
|
|
143625
143992
|
stopPRMonitor();
|
|
143993
|
+
stopMetricsCollector();
|
|
143626
143994
|
ptyManager2.detachAll();
|
|
143627
143995
|
server.close();
|
|
143628
143996
|
process.exit(0);
|