vibora 1.1.0 → 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/dist/index.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <link rel="icon" type="image/png" sizes="512x512" href="/vibora-icon.png" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>Vibora</title>
9
- <script type="module" crossorigin src="/assets/index-DJxCuTf-.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-ClQ5DUcr.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-DCMXlbkM.css">
11
11
  </head>
12
12
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibora",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "The Vibe Engineer's Cockpit",
5
5
  "license": "PolyForm-Shield-1.0.0",
6
6
  "repository": {
package/server/index.js CHANGED
@@ -599,7 +599,7 @@ var require_printer = __commonJS((exports) => {
599
599
  leave: printDocASTReducer
600
600
  });
601
601
  }
602
- var MAX_LINE_LENGTH2 = 80;
602
+ var MAX_LINE_LENGTH = 80;
603
603
  var printDocASTReducer = {
604
604
  Name: function Name(node) {
605
605
  return node.value;
@@ -633,7 +633,7 @@ var require_printer = __commonJS((exports) => {
633
633
  var { alias, name, arguments: args, directives, selectionSet } = _ref3;
634
634
  var prefix = wrap("", alias, ": ") + name;
635
635
  var argsLine = prefix + wrap("(", join6(args, ", "), ")");
636
- if (argsLine.length > MAX_LINE_LENGTH2) {
636
+ if (argsLine.length > MAX_LINE_LENGTH) {
637
637
  argsLine = prefix + wrap(`(
638
638
  `, indent(join6(args, `
639
639
  `)), `
@@ -13540,6 +13540,21 @@ function getDescendantPids(pid) {
13540
13540
  } catch {}
13541
13541
  return descendants;
13542
13542
  }
13543
+ function isClaudeProcess(pid) {
13544
+ try {
13545
+ const cmdline = readFileSync2(`/proc/${pid}/cmdline`, "utf-8");
13546
+ return /\bclaude\b/i.test(cmdline);
13547
+ } catch {
13548
+ try {
13549
+ const result = execSync(`ps -p ${pid} -o args= 2>/dev/null || true`, {
13550
+ encoding: "utf-8"
13551
+ });
13552
+ return /\bclaude\b/i.test(result);
13553
+ } catch {
13554
+ return false;
13555
+ }
13556
+ }
13557
+ }
13543
13558
  function killProcessTree(pid) {
13544
13559
  const descendants = getDescendantPids(pid);
13545
13560
  for (const childPid of descendants.reverse()) {
@@ -13582,6 +13597,21 @@ class DtachService {
13582
13597
  killProcessTree(pid);
13583
13598
  }
13584
13599
  }
13600
+ killClaudeInSession(terminalId) {
13601
+ const socketPath = this.getSocketPath(terminalId);
13602
+ const dtachPids = findProcessesByArg(socketPath);
13603
+ let killedAny = false;
13604
+ for (const dtachPid of dtachPids) {
13605
+ const descendants = getDescendantPids(dtachPid);
13606
+ for (const pid of descendants) {
13607
+ if (isClaudeProcess(pid)) {
13608
+ killProcessTree(pid);
13609
+ killedAny = true;
13610
+ }
13611
+ }
13612
+ }
13613
+ return killedAny;
13614
+ }
13585
13615
  static isAvailable() {
13586
13616
  try {
13587
13617
  execSync("which dtach", { encoding: "utf-8" });
@@ -13602,8 +13632,7 @@ function getDtachService() {
13602
13632
  // server/terminal/buffer-manager.ts
13603
13633
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync } from "fs";
13604
13634
  import * as path4 from "path";
13605
- var MAX_BUFFER_LINES = 1e4;
13606
- var MAX_LINE_LENGTH = 2000;
13635
+ var MAX_BUFFER_BYTES = 1e6;
13607
13636
  function getBuffersDir() {
13608
13637
  const dir = path4.join(getViboraDir(), "buffers");
13609
13638
  if (!existsSync4(dir)) {
@@ -13613,47 +13642,43 @@ function getBuffersDir() {
13613
13642
  }
13614
13643
 
13615
13644
  class BufferManager {
13616
- lines = [];
13617
- partialLine = "";
13645
+ chunks = [];
13646
+ totalBytes = 0;
13618
13647
  terminalId = null;
13619
13648
  setTerminalId(id) {
13620
13649
  this.terminalId = id;
13621
13650
  }
13622
13651
  append(data) {
13623
- const combined = this.partialLine + data;
13624
- const parts = combined.split(`
13625
- `);
13626
- this.partialLine = parts.pop() || "";
13627
- for (const line of parts) {
13628
- const truncated = line.length > MAX_LINE_LENGTH ? line.slice(0, MAX_LINE_LENGTH) + "..." : line;
13629
- this.lines.push(truncated);
13630
- }
13631
- if (this.lines.length > MAX_BUFFER_LINES) {
13632
- this.lines = this.lines.slice(-MAX_BUFFER_LINES);
13652
+ this.chunks.push({ data, timestamp: Date.now() });
13653
+ this.totalBytes += data.length;
13654
+ while (this.totalBytes > MAX_BUFFER_BYTES && this.chunks.length > 1) {
13655
+ const removed = this.chunks.shift();
13656
+ this.totalBytes -= removed.data.length;
13633
13657
  }
13634
13658
  }
13635
13659
  getContents() {
13636
- const content = this.lines.join(`
13637
- `);
13638
- if (this.partialLine) {
13639
- return content + `
13640
- ` + this.partialLine;
13641
- }
13642
- return content;
13660
+ return this.chunks.map((c) => c.data).join("");
13643
13661
  }
13644
13662
  clear() {
13645
- this.lines = [];
13646
- this.partialLine = "";
13663
+ this.chunks = [];
13664
+ this.totalBytes = 0;
13647
13665
  }
13648
13666
  getLineCount() {
13649
- return this.lines.length + (this.partialLine ? 1 : 0);
13667
+ const content = this.getContents();
13668
+ return content.split(`
13669
+ `).length;
13650
13670
  }
13651
13671
  saveToDisk() {
13652
13672
  if (!this.terminalId)
13653
13673
  return;
13654
13674
  const filePath = path4.join(getBuffersDir(), `${this.terminalId}.buf`);
13655
13675
  try {
13656
- writeFileSync2(filePath, this.getContents(), "utf-8");
13676
+ const content = this.getContents();
13677
+ const fileData = {
13678
+ version: 2,
13679
+ content: Buffer.from(content).toString("base64")
13680
+ };
13681
+ writeFileSync2(filePath, JSON.stringify(fileData), "utf-8");
13657
13682
  } catch (err) {
13658
13683
  console.error(`[BufferManager] Failed to save buffer for ${this.terminalId}:`, err);
13659
13684
  }
@@ -13664,11 +13689,21 @@ class BufferManager {
13664
13689
  const filePath = path4.join(getBuffersDir(), `${this.terminalId}.buf`);
13665
13690
  try {
13666
13691
  if (existsSync4(filePath)) {
13667
- const content = readFileSync3(filePath, "utf-8");
13668
- this.lines = content.split(`
13669
- `);
13670
- this.partialLine = "";
13671
- console.log(`[BufferManager] Loaded ${this.lines.length} lines for ${this.terminalId}`);
13692
+ const raw2 = readFileSync3(filePath, "utf-8");
13693
+ let content;
13694
+ try {
13695
+ const parsed = JSON.parse(raw2);
13696
+ if (parsed.version === 2 && typeof parsed.content === "string") {
13697
+ content = Buffer.from(parsed.content, "base64").toString();
13698
+ } else {
13699
+ content = raw2;
13700
+ }
13701
+ } catch {
13702
+ content = raw2;
13703
+ }
13704
+ this.chunks = [{ data: content, timestamp: Date.now() }];
13705
+ this.totalBytes = content.length;
13706
+ console.log(`[BufferManager] Loaded ${this.totalBytes} bytes for ${this.terminalId}`);
13672
13707
  } else {
13673
13708
  console.log(`[BufferManager] No buffer file for ${this.terminalId}`);
13674
13709
  }
@@ -13838,6 +13873,10 @@ class TerminalSession {
13838
13873
  getBuffer() {
13839
13874
  return this.buffer.getContents();
13840
13875
  }
13876
+ clearBuffer() {
13877
+ this.buffer.clear();
13878
+ this.buffer.saveToDisk();
13879
+ }
13841
13880
  getInfo() {
13842
13881
  return {
13843
13882
  id: this.id,
@@ -14011,6 +14050,14 @@ class PTYManager {
14011
14050
  }
14012
14051
  return session.getBuffer();
14013
14052
  }
14053
+ clearBuffer(terminalId) {
14054
+ const session = this.sessions.get(terminalId);
14055
+ if (!session) {
14056
+ return false;
14057
+ }
14058
+ session.clearBuffer();
14059
+ return true;
14060
+ }
14014
14061
  getInfo(terminalId) {
14015
14062
  const session = this.sessions.get(terminalId);
14016
14063
  if (!session) {
@@ -14021,6 +14068,14 @@ class PTYManager {
14021
14068
  listTerminals() {
14022
14069
  return Array.from(this.sessions.values()).map((s) => s.getInfo());
14023
14070
  }
14071
+ killClaudeInTerminal(terminalId) {
14072
+ const session = this.sessions.get(terminalId);
14073
+ if (!session) {
14074
+ return false;
14075
+ }
14076
+ const dtach = getDtachService();
14077
+ return dtach.killClaudeInSession(terminalId);
14078
+ }
14024
14079
  detachAll() {
14025
14080
  for (const session of this.sessions.values()) {
14026
14081
  session.detach();
@@ -14059,6 +14114,19 @@ function destroyTerminalAndBroadcast(terminalId) {
14059
14114
  }
14060
14115
  return success;
14061
14116
  }
14117
+ function killClaudeInTerminalsForWorktree(worktreePath) {
14118
+ const manager = getPTYManager();
14119
+ const terminals2 = manager.listTerminals();
14120
+ let count = 0;
14121
+ for (const terminal of terminals2) {
14122
+ if (terminal.cwd === worktreePath) {
14123
+ if (manager.killClaudeInTerminal(terminal.id)) {
14124
+ count++;
14125
+ }
14126
+ }
14127
+ }
14128
+ return count;
14129
+ }
14062
14130
 
14063
14131
  // server/terminal/tab-manager.ts
14064
14132
  var VIEW_STATE_ID = "singleton";
@@ -14308,6 +14376,17 @@ var terminalWebSocketHandlers = {
14308
14376
  }
14309
14377
  break;
14310
14378
  }
14379
+ case "terminal:clearBuffer": {
14380
+ const { terminalId } = message.payload;
14381
+ const success = ptyManager2.clearBuffer(terminalId);
14382
+ if (success) {
14383
+ broadcastToTerminal(terminalId, {
14384
+ type: "terminal:bufferCleared",
14385
+ payload: { terminalId }
14386
+ });
14387
+ }
14388
+ break;
14389
+ }
14311
14390
  case "tab:create": {
14312
14391
  const { name, position } = message.payload;
14313
14392
  const tab = tabManager2.create({ name, position });
@@ -138015,6 +138094,22 @@ app2.patch("/:id/status", async (c) => {
138015
138094
  return c.json({ error: err instanceof Error ? err.message : "Failed to update task status" }, 400);
138016
138095
  }
138017
138096
  });
138097
+ app2.post("/:id/kill-claude", (c) => {
138098
+ const id = c.req.param("id");
138099
+ const task = db.select().from(tasks).where(eq(tasks.id, id)).get();
138100
+ if (!task) {
138101
+ return c.json({ error: "Task not found" }, 404);
138102
+ }
138103
+ if (!task.worktreePath) {
138104
+ return c.json({ success: true, terminalsAffected: 0 });
138105
+ }
138106
+ try {
138107
+ const count = killClaudeInTerminalsForWorktree(task.worktreePath);
138108
+ return c.json({ success: true, terminalsAffected: count });
138109
+ } catch {
138110
+ return c.json({ success: true, terminalsAffected: 0 });
138111
+ }
138112
+ });
138018
138113
  var tasks_default = app2;
138019
138114
 
138020
138115
  // server/routes/git.ts