vibora 1.9.1 → 1.11.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,8 +6,8 @@
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-CCtJOkVu.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-BxbgLbxS.css">
9
+ <script type="module" crossorigin src="/assets/index-C4ahV9ZS.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-BiapsD8F.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibora",
3
- "version": "1.9.1",
3
+ "version": "1.11.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
@@ -3512,7 +3512,8 @@ var DEFAULT_SETTINGS = {
3512
3512
  basicAuthUsername: null,
3513
3513
  basicAuthPassword: null,
3514
3514
  linearApiKey: null,
3515
- githubPat: null
3515
+ githubPat: null,
3516
+ language: null
3516
3517
  };
3517
3518
  function expandPath(p) {
3518
3519
  if (p.startsWith("~/")) {
@@ -3586,7 +3587,8 @@ function getSettings() {
3586
3587
  basicAuthUsername: parsed.basicAuthUsername ?? null,
3587
3588
  basicAuthPassword: parsed.basicAuthPassword ?? null,
3588
3589
  linearApiKey: parsed.linearApiKey ?? null,
3589
- githubPat: parsed.githubPat ?? null
3590
+ githubPat: parsed.githubPat ?? null,
3591
+ language: parsed.language ?? null
3590
3592
  };
3591
3593
  if (hasMissingKeys) {
3592
3594
  fs.writeFileSync(settingsPath, JSON.stringify(fileSettings, null, 2), "utf-8");
@@ -3602,7 +3604,8 @@ function getSettings() {
3602
3604
  basicAuthUsername: process.env.VIBORA_BASIC_AUTH_USERNAME ?? fileSettings.basicAuthUsername,
3603
3605
  basicAuthPassword: process.env.VIBORA_BASIC_AUTH_PASSWORD ?? fileSettings.basicAuthPassword,
3604
3606
  linearApiKey: process.env.LINEAR_API_KEY ?? fileSettings.linearApiKey,
3605
- githubPat: process.env.GITHUB_PAT ?? fileSettings.githubPat
3607
+ githubPat: process.env.GITHUB_PAT ?? fileSettings.githubPat,
3608
+ language: fileSettings.language
3606
3609
  };
3607
3610
  }
3608
3611
  function getSetting(key) {
@@ -3679,6 +3682,80 @@ function updateNotificationSettings(updates) {
3679
3682
  fs.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2), "utf-8");
3680
3683
  return updated;
3681
3684
  }
3685
+ function getClaudeSettingsPath() {
3686
+ return path.join(os.homedir(), ".claude", "settings.json");
3687
+ }
3688
+ function getClaudeSettings() {
3689
+ const settingsPath = getClaudeSettingsPath();
3690
+ if (!fs.existsSync(settingsPath))
3691
+ return {};
3692
+ try {
3693
+ return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
3694
+ } catch {
3695
+ return {};
3696
+ }
3697
+ }
3698
+ function updateClaudeSettings(updates) {
3699
+ const settingsPath = getClaudeSettingsPath();
3700
+ const dir = path.dirname(settingsPath);
3701
+ if (!fs.existsSync(dir))
3702
+ fs.mkdirSync(dir, { recursive: true });
3703
+ const current = getClaudeSettings();
3704
+ const merged = { ...current, ...updates };
3705
+ fs.writeFileSync(settingsPath, JSON.stringify(merged, null, 2), "utf-8");
3706
+ }
3707
+ var DEFAULT_ZAI_SETTINGS = {
3708
+ enabled: false,
3709
+ apiKey: null,
3710
+ haikuModel: "glm-4.5-air",
3711
+ sonnetModel: "glm-4.7",
3712
+ opusModel: "glm-4.7"
3713
+ };
3714
+ function getZAiSettings() {
3715
+ ensureViboraDir();
3716
+ const settingsPath = getSettingsPath();
3717
+ if (!fs.existsSync(settingsPath)) {
3718
+ return DEFAULT_ZAI_SETTINGS;
3719
+ }
3720
+ try {
3721
+ const content = fs.readFileSync(settingsPath, "utf-8");
3722
+ const parsed = JSON.parse(content);
3723
+ const zai = parsed.zai;
3724
+ if (!zai) {
3725
+ return DEFAULT_ZAI_SETTINGS;
3726
+ }
3727
+ return {
3728
+ enabled: zai.enabled ?? false,
3729
+ apiKey: zai.apiKey ?? null,
3730
+ haikuModel: zai.haikuModel ?? DEFAULT_ZAI_SETTINGS.haikuModel,
3731
+ sonnetModel: zai.sonnetModel ?? DEFAULT_ZAI_SETTINGS.sonnetModel,
3732
+ opusModel: zai.opusModel ?? DEFAULT_ZAI_SETTINGS.opusModel
3733
+ };
3734
+ } catch {
3735
+ return DEFAULT_ZAI_SETTINGS;
3736
+ }
3737
+ }
3738
+ function updateZAiSettings(updates) {
3739
+ ensureViboraDir();
3740
+ const settingsPath = getSettingsPath();
3741
+ let parsed = {};
3742
+ if (fs.existsSync(settingsPath)) {
3743
+ try {
3744
+ parsed = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
3745
+ } catch {}
3746
+ }
3747
+ const current = getZAiSettings();
3748
+ const updated = {
3749
+ enabled: updates.enabled ?? current.enabled,
3750
+ apiKey: updates.apiKey !== undefined ? updates.apiKey : current.apiKey,
3751
+ haikuModel: updates.haikuModel ?? current.haikuModel,
3752
+ sonnetModel: updates.sonnetModel ?? current.sonnetModel,
3753
+ opusModel: updates.opusModel ?? current.opusModel
3754
+ };
3755
+ parsed.zai = updated;
3756
+ fs.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2), "utf-8");
3757
+ return updated;
3758
+ }
3682
3759
 
3683
3760
  // server/middleware/auth.ts
3684
3761
  var SESSION_COOKIE_NAME = "vibora_session";
@@ -3723,8 +3800,8 @@ var sessionAuthMiddleware = createMiddleware(async (c, next) => {
3723
3800
 
3724
3801
  // server/app.ts
3725
3802
  import { readFile as readFile2 } from "fs/promises";
3726
- import { join as join11 } from "path";
3727
- import { existsSync as existsSync10 } from "fs";
3803
+ import { join as join12 } from "path";
3804
+ import { existsSync as existsSync11 } from "fs";
3728
3805
 
3729
3806
  // server/routes/health.ts
3730
3807
  var app = new Hono2;
@@ -13320,7 +13397,7 @@ glob.glob = glob;
13320
13397
  // node_modules/bun-pty/dist/index.js
13321
13398
  import { dlopen, FFIType, ptr } from "bun:ffi";
13322
13399
  import { Buffer as Buffer2 } from "buffer";
13323
- import { join as join3, dirname, basename } from "path";
13400
+ import { join as join3, dirname as dirname2, basename } from "path";
13324
13401
  import { existsSync as existsSync2 } from "fs";
13325
13402
 
13326
13403
  class EventEmitter2 {
@@ -13359,9 +13436,9 @@ function resolveLibPath() {
13359
13436
  const arch = process.arch;
13360
13437
  const filenames = platform === "darwin" ? arch === "arm64" ? ["librust_pty_arm64.dylib", "librust_pty.dylib"] : ["librust_pty.dylib"] : platform === "win32" ? ["rust_pty.dll"] : arch === "arm64" ? ["librust_pty_arm64.so", "librust_pty.so"] : ["librust_pty.so"];
13361
13438
  const base = Bun.fileURLToPath(import.meta.url);
13362
- const fileDir = dirname(base);
13439
+ const fileDir = dirname2(base);
13363
13440
  const dirName = basename(fileDir);
13364
- const here = dirName === "src" || dirName === "dist" ? dirname(fileDir) : fileDir;
13441
+ const here = dirName === "src" || dirName === "dist" ? dirname2(fileDir) : fileDir;
13365
13442
  const basePaths = [
13366
13443
  join3(here, "rust-pty", "target", "release"),
13367
13444
  join3(here, "..", "bun-pty", "rust-pty", "target", "release"),
@@ -13735,6 +13812,30 @@ class BufferManager {
13735
13812
  }
13736
13813
 
13737
13814
  // server/terminal/terminal-session.ts
13815
+ var ZAI_ENV_VARS = [
13816
+ "ANTHROPIC_AUTH_TOKEN",
13817
+ "ANTHROPIC_BASE_URL",
13818
+ "API_TIMEOUT_MS",
13819
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
13820
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
13821
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
13822
+ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"
13823
+ ];
13824
+ function getTerminalEnv() {
13825
+ const { PORT: _PORT, ...envWithoutPort } = process.env;
13826
+ const zaiSettings = getZAiSettings();
13827
+ if (zaiSettings.enabled) {
13828
+ return envWithoutPort;
13829
+ }
13830
+ const filtered = {};
13831
+ for (const [key, value] of Object.entries(envWithoutPort)) {
13832
+ if (!ZAI_ENV_VARS.includes(key) && value !== undefined) {
13833
+ filtered[key] = value;
13834
+ }
13835
+ }
13836
+ return filtered;
13837
+ }
13838
+
13738
13839
  class TerminalSession {
13739
13840
  id;
13740
13841
  _name;
@@ -13787,7 +13888,6 @@ class TerminalSession {
13787
13888
  start() {
13788
13889
  const dtach = getDtachService();
13789
13890
  const [cmd, ...args] = dtach.getCreateCommand(this.id);
13790
- const { PORT: _PORT, ...envWithoutPort } = process.env;
13791
13891
  try {
13792
13892
  this.pty = spawn(cmd, args, {
13793
13893
  name: "xterm-256color",
@@ -13795,7 +13895,7 @@ class TerminalSession {
13795
13895
  rows: this.rows,
13796
13896
  cwd: this.cwd,
13797
13897
  env: {
13798
- ...envWithoutPort,
13898
+ ...getTerminalEnv(),
13799
13899
  TERM: "xterm-256color",
13800
13900
  COLORTERM: "truecolor"
13801
13901
  }
@@ -13822,7 +13922,6 @@ class TerminalSession {
13822
13922
  }
13823
13923
  this.buffer.loadFromDisk();
13824
13924
  const [cmd, ...args] = dtach.getAttachCommand(this.id);
13825
- const { PORT: _PORT, ...envWithoutPort } = process.env;
13826
13925
  try {
13827
13926
  this.pty = spawn(cmd, args, {
13828
13927
  name: "xterm-256color",
@@ -13830,7 +13929,7 @@ class TerminalSession {
13830
13929
  rows: this.rows,
13831
13930
  cwd: this.cwd,
13832
13931
  env: {
13833
- ...envWithoutPort,
13932
+ ...getTerminalEnv(),
13834
13933
  TERM: "xterm-256color",
13835
13934
  COLORTERM: "truecolor"
13836
13935
  }
@@ -137570,24 +137669,6 @@ async function updateLinearTicketStatus(identifier, viboraStatus) {
137570
137669
  }
137571
137670
  }
137572
137671
 
137573
- // server/services/task-status.ts
137574
- async function updateTaskStatus(taskId, newStatus) {
137575
- const existing = db.select().from(tasks).where(eq(tasks.id, taskId)).get();
137576
- if (!existing)
137577
- return;
137578
- const oldStatus = existing.status;
137579
- if (oldStatus === newStatus)
137580
- return;
137581
- const now = new Date().toISOString();
137582
- db.update(tasks).set({ status: newStatus, updatedAt: now }).where(eq(tasks.id, taskId)).run();
137583
- broadcast({ type: "task:updated", payload: { taskId } });
137584
- if (existing.linearTicketId) {
137585
- updateLinearTicketStatus(existing.linearTicketId, newStatus).catch((err) => {
137586
- console.error(`Failed to update Linear ticket ${existing.linearTicketId}:`, err);
137587
- });
137588
- }
137589
- }
137590
-
137591
137672
  // server/services/notification-service.ts
137592
137673
  import { spawn as spawn2 } from "child_process";
137593
137674
  async function sendSoundNotification(config) {
@@ -137761,7 +137842,52 @@ async function testNotificationChannel(channel, settings) {
137761
137842
  }
137762
137843
  }
137763
137844
 
137845
+ // server/services/task-status.ts
137846
+ async function updateTaskStatus(taskId, newStatus, newPosition) {
137847
+ const existing = db.select().from(tasks).where(eq(tasks.id, taskId)).get();
137848
+ if (!existing)
137849
+ return null;
137850
+ const oldStatus = existing.status;
137851
+ const statusChanged = oldStatus !== newStatus;
137852
+ const now = new Date().toISOString();
137853
+ const updateData = {
137854
+ status: newStatus,
137855
+ updatedAt: now
137856
+ };
137857
+ if (newPosition !== undefined) {
137858
+ updateData.position = newPosition;
137859
+ }
137860
+ db.update(tasks).set(updateData).where(eq(tasks.id, taskId)).run();
137861
+ const updated = db.select().from(tasks).where(eq(tasks.id, taskId)).get();
137862
+ broadcast({ type: "task:updated", payload: { taskId } });
137863
+ if (statusChanged && updated) {
137864
+ if (existing.linearTicketId) {
137865
+ updateLinearTicketStatus(existing.linearTicketId, newStatus).catch((err) => {
137866
+ console.error(`Failed to update Linear ticket ${existing.linearTicketId}:`, err);
137867
+ });
137868
+ }
137869
+ if (newStatus === "IN_REVIEW") {
137870
+ sendNotification({
137871
+ title: "Task Ready for Review",
137872
+ message: `Task "${updated.title}" moved to review`,
137873
+ taskId: updated.id,
137874
+ taskTitle: updated.title,
137875
+ type: "task_status_change"
137876
+ });
137877
+ }
137878
+ if ((newStatus === "DONE" || newStatus === "CANCELED") && updated.worktreePath) {
137879
+ try {
137880
+ killClaudeInTerminalsForWorktree(updated.worktreePath);
137881
+ } catch (err) {
137882
+ console.error(`Failed to kill Claude in worktree ${updated.worktreePath}:`, err);
137883
+ }
137884
+ }
137885
+ }
137886
+ return updated ?? null;
137887
+ }
137888
+
137764
137889
  // server/routes/tasks.ts
137890
+ var __dirname = "/home/runner/work/vibora/vibora/server/routes";
137765
137891
  function createGitWorktree(repoPath, worktreePath, branch, baseBranch) {
137766
137892
  try {
137767
137893
  const worktreeParent = path5.dirname(worktreePath);
@@ -137834,29 +137960,8 @@ function copyFilesToWorktree(repoPath, worktreePath, patterns) {
137834
137960
  function initializeWorktreeForVibora(worktreePath) {
137835
137961
  const claudeLocalPath = path5.join(worktreePath, "CLAUDE.local.md");
137836
137962
  const gitignorePath = path5.join(worktreePath, ".gitignore");
137837
- const viboraSection = `
137838
- ## Vibora Task Management
137839
-
137840
- You are working inside a Vibora task worktree. Use the \`vibora\` CLI to manage this task:
137841
-
137842
- \`\`\`bash
137843
- # View current task info
137844
- vibora current-task
137845
-
137846
- # Associate a PR with this task (enables auto-completion when merged)
137847
- vibora current-task pr https://github.com/owner/repo/pull/123
137848
-
137849
- # Associate a Linear ticket with this task
137850
- vibora current-task linear https://linear.app/team/issue/TEAM-123
137851
-
137852
- # Update task status when work is complete
137853
- vibora current-task review # Ready for review
137854
- vibora current-task done # Task complete
137855
- \`\`\`
137856
-
137857
- When you create a PR for this work, run \`vibora current-task pr <url>\` to link it.
137858
- The task will automatically complete when the PR is merged.
137859
- `;
137963
+ const templatePath = path5.join(__dirname, "../templates/CLAUDE.local.template.md");
137964
+ const viboraSection = fs3.readFileSync(templatePath, "utf-8");
137860
137965
  let claudeContent = "";
137861
137966
  if (fs3.existsSync(claudeLocalPath)) {
137862
137967
  claudeContent = fs3.readFileSync(claudeLocalPath, "utf-8");
@@ -138070,37 +138175,7 @@ app2.patch("/:id/status", async (c) => {
138070
138175
  }
138071
138176
  }
138072
138177
  }
138073
- db.update(tasks).set({
138074
- status: newStatus,
138075
- position: newPosition,
138076
- updatedAt: now
138077
- }).where(eq(tasks.id, id)).run();
138078
- const updated = db.select().from(tasks).where(eq(tasks.id, id)).get();
138079
- broadcast({ type: "task:updated", payload: { taskId: id } });
138080
- if (oldStatus !== newStatus && existing.linearTicketId) {
138081
- updateLinearTicketStatus(existing.linearTicketId, newStatus).catch((err) => {
138082
- console.error("Failed to update Linear ticket status:", err);
138083
- });
138084
- }
138085
- if (oldStatus !== newStatus && updated) {
138086
- if (newStatus === "IN_REVIEW") {
138087
- sendNotification({
138088
- title: "Task Ready for Review",
138089
- message: `Task "${updated.title}" moved to review`,
138090
- taskId: updated.id,
138091
- taskTitle: updated.title,
138092
- type: "task_status_change"
138093
- });
138094
- } else if (newStatus === "DONE") {
138095
- sendNotification({
138096
- title: "Task Completed",
138097
- message: `Task "${updated.title}" marked as done`,
138098
- taskId: updated.id,
138099
- taskTitle: updated.title,
138100
- type: "task_status_change"
138101
- });
138102
- }
138103
- }
138178
+ const updated = await updateTaskStatus(id, newStatus, newPosition);
138104
138179
  return c.json(updated ? parseViewState(updated) : null);
138105
138180
  } catch (err) {
138106
138181
  return c.json({ error: err instanceof Error ? err.message : "Failed to update task status" }, 400);
@@ -138993,7 +139068,8 @@ var CONFIG_KEYS = {
138993
139068
  HOSTNAME: "hostname",
138994
139069
  SSH_PORT: "sshPort",
138995
139070
  LINEAR_API_KEY: "linearApiKey",
138996
- GITHUB_PAT: "githubPat"
139071
+ GITHUB_PAT: "githubPat",
139072
+ LANGUAGE: "language"
138997
139073
  };
138998
139074
  var app5 = new Hono2;
138999
139075
  app5.get("/notifications", (c) => {
@@ -139018,6 +139094,63 @@ app5.post("/notifications/test/:channel", async (c) => {
139018
139094
  const result = await testNotificationChannel(channel);
139019
139095
  return c.json(result);
139020
139096
  });
139097
+ app5.post("/notifications/send", async (c) => {
139098
+ try {
139099
+ const body = await c.req.json();
139100
+ if (!body.title || !body.message) {
139101
+ return c.json({ error: "title and message are required" }, 400);
139102
+ }
139103
+ const payload = {
139104
+ title: body.title,
139105
+ message: body.message,
139106
+ type: "task_status_change"
139107
+ };
139108
+ const results = await sendNotification(payload);
139109
+ return c.json({ success: true, results });
139110
+ } catch (err) {
139111
+ return c.json({ error: err instanceof Error ? err.message : "Failed to send notification" }, 400);
139112
+ }
139113
+ });
139114
+ app5.get("/z-ai", (c) => {
139115
+ const settings = getZAiSettings();
139116
+ return c.json(settings);
139117
+ });
139118
+ app5.put("/z-ai", async (c) => {
139119
+ try {
139120
+ const body = await c.req.json();
139121
+ const updated = updateZAiSettings(body);
139122
+ if (updated.enabled && updated.apiKey) {
139123
+ const claudeSettings = getClaudeSettings();
139124
+ const currentEnv = claudeSettings.env || {};
139125
+ updateClaudeSettings({
139126
+ env: {
139127
+ ...currentEnv,
139128
+ ANTHROPIC_AUTH_TOKEN: updated.apiKey,
139129
+ ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
139130
+ API_TIMEOUT_MS: "3000000",
139131
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: updated.haikuModel,
139132
+ ANTHROPIC_DEFAULT_SONNET_MODEL: updated.sonnetModel,
139133
+ ANTHROPIC_DEFAULT_OPUS_MODEL: updated.opusModel
139134
+ }
139135
+ });
139136
+ } else {
139137
+ const claudeSettings = getClaudeSettings();
139138
+ if (claudeSettings.env) {
139139
+ const env = { ...claudeSettings.env };
139140
+ delete env.ANTHROPIC_AUTH_TOKEN;
139141
+ delete env.ANTHROPIC_BASE_URL;
139142
+ delete env.API_TIMEOUT_MS;
139143
+ delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
139144
+ delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;
139145
+ delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;
139146
+ updateClaudeSettings({ env: Object.keys(env).length > 0 ? env : undefined });
139147
+ }
139148
+ }
139149
+ return c.json(updated);
139150
+ } catch (err) {
139151
+ return c.json({ error: err instanceof Error ? err.message : "Failed to update z.ai settings" }, 400);
139152
+ }
139153
+ });
139021
139154
  app5.get("/:key", (c) => {
139022
139155
  const key = c.req.param("key");
139023
139156
  const settings = getSettings();
@@ -139036,6 +139169,8 @@ app5.get("/:key", (c) => {
139036
139169
  value = settings.linearApiKey;
139037
139170
  } else if (key === "github_pat" || key === CONFIG_KEYS.GITHUB_PAT) {
139038
139171
  value = settings.githubPat;
139172
+ } else if (key === "language" || key === CONFIG_KEYS.LANGUAGE) {
139173
+ return c.json({ key, value: settings.language, isDefault: settings.language === null });
139039
139174
  } else if (key === "worktree_base_path") {
139040
139175
  return c.json({ key, value: getWorktreeBasePath(), isDefault: true });
139041
139176
  }
@@ -139092,6 +139227,13 @@ app5.put("/:key", async (c) => {
139092
139227
  }
139093
139228
  updateSettings({ githubPat: body.value || null });
139094
139229
  return c.json({ key, value: body.value });
139230
+ } else if (key === "language" || key === CONFIG_KEYS.LANGUAGE) {
139231
+ const langValue = body.value === "" || body.value === null ? null : body.value;
139232
+ if (langValue !== null && langValue !== "en" && langValue !== "zh") {
139233
+ return c.json({ error: 'Language must be "en", "zh", or null' }, 400);
139234
+ }
139235
+ updateSettings({ language: langValue });
139236
+ return c.json({ key, value: langValue });
139095
139237
  } else {
139096
139238
  return c.json({ error: `Unknown or read-only config key: ${key}` }, 400);
139097
139239
  }
@@ -139117,6 +139259,8 @@ app5.delete("/:key", (c) => {
139117
139259
  defaultValue = defaults2.linearApiKey;
139118
139260
  } else if (key === "github_pat" || key === CONFIG_KEYS.GITHUB_PAT) {
139119
139261
  defaultValue = defaults2.githubPat;
139262
+ } else if (key === "language" || key === CONFIG_KEYS.LANGUAGE) {
139263
+ defaultValue = defaults2.language;
139120
139264
  }
139121
139265
  return c.json({ key, value: defaultValue, isDefault: true });
139122
139266
  });
@@ -143451,8 +143595,10 @@ app12.get("/check", async (c) => {
143451
143595
  var auth_default = app12;
143452
143596
 
143453
143597
  // server/routes/monitoring.ts
143454
- import { readdirSync as readdirSync7, readFileSync as readFileSync7, readlinkSync as readlinkSync2 } from "fs";
143598
+ import { readdirSync as readdirSync7, readFileSync as readFileSync7, readlinkSync as readlinkSync2, existsSync as existsSync10 } from "fs";
143455
143599
  import { execSync as execSync5 } from "child_process";
143600
+ import { homedir as homedir5 } from "os";
143601
+ import { join as join11 } from "path";
143456
143602
 
143457
143603
  // server/services/metrics-collector.ts
143458
143604
  import os5 from "os";
@@ -144023,8 +144169,8 @@ function findViboraInstances() {
144023
144169
  const parentPid = getParentPid(pid);
144024
144170
  const cmdParts = cmdline.trim().split(/\s+/);
144025
144171
  const isBunProcess = cmdParts[0]?.includes("bun") ?? false;
144026
- const isDevBackend = isBunProcess && cmdline.includes("server/index.ts");
144027
- const isProdBackend = isBunProcess && !!env.VIBORA_PACKAGE_ROOT;
144172
+ const isDevBackend = isBunProcess && cmdline.includes("server/index.ts") && env.NODE_ENV !== "production";
144173
+ const isProdBackend = isBunProcess && (!!env.VIBORA_PACKAGE_ROOT || cmdline.includes("server/index.ts") && env.NODE_ENV === "production");
144028
144174
  if (isDevBackend || isProdBackend) {
144029
144175
  const port = parseInt(env.PORT || "3333", 10);
144030
144176
  const cwd = getProcessCwd(pid);
@@ -144127,13 +144273,141 @@ monitoringRoutes.post("/vibora-instances/:pid/kill", async (c) => {
144127
144273
  port: group.port
144128
144274
  });
144129
144275
  });
144276
+ var cachedUsage = null;
144277
+ var usageCacheTimestamp = 0;
144278
+ var USAGE_CACHE_MS = 15 * 1000;
144279
+ async function getClaudeOAuthToken() {
144280
+ const primaryPath = join11(homedir5(), ".claude", ".credentials.json");
144281
+ try {
144282
+ if (existsSync10(primaryPath)) {
144283
+ const content = readFileSync7(primaryPath, "utf-8");
144284
+ const config = JSON.parse(content);
144285
+ if (config.claudeAiOauth && typeof config.claudeAiOauth === "object") {
144286
+ const token = config.claudeAiOauth.accessToken;
144287
+ if (token && typeof token === "string" && token.startsWith("sk-ant-oat")) {
144288
+ return token;
144289
+ }
144290
+ }
144291
+ }
144292
+ } catch {}
144293
+ try {
144294
+ const result = execSync5('secret-tool lookup service "Claude Code"', {
144295
+ encoding: "utf-8",
144296
+ timeout: 5000,
144297
+ stdio: ["pipe", "pipe", "pipe"]
144298
+ });
144299
+ const token = result.trim();
144300
+ if (token && token.startsWith("sk-ant-oat")) {
144301
+ return token;
144302
+ }
144303
+ } catch {}
144304
+ return null;
144305
+ }
144306
+ async function fetchClaudeUsage(token) {
144307
+ try {
144308
+ const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
144309
+ method: "GET",
144310
+ headers: {
144311
+ "Content-Type": "application/json",
144312
+ "User-Agent": "vibora/1.0.0",
144313
+ Authorization: `Bearer ${token}`,
144314
+ "anthropic-beta": "oauth-2025-04-20"
144315
+ }
144316
+ });
144317
+ if (!response.ok) {
144318
+ return { available: false, fiveHour: null, sevenDay: null, sevenDayOpus: null, sevenDaySonnet: null, error: `API returned ${response.status}` };
144319
+ }
144320
+ const data = await response.json();
144321
+ const parseBlock = (block) => {
144322
+ if (!block)
144323
+ return null;
144324
+ return {
144325
+ percentUsed: block.utilization ?? 0,
144326
+ resetAt: block.resets_at || new Date().toISOString(),
144327
+ isOverLimit: (block.utilization ?? 0) >= 100
144328
+ };
144329
+ };
144330
+ const fiveHour = parseBlock(data.five_hour);
144331
+ const sevenDay = parseBlock(data.seven_day);
144332
+ let fiveHourWithTime = null;
144333
+ if (fiveHour) {
144334
+ const now = new Date;
144335
+ const resetAt = new Date(fiveHour.resetAt);
144336
+ const timeRemainingMinutes = Math.max(0, Math.round((resetAt.getTime() - now.getTime()) / (1000 * 60)));
144337
+ fiveHourWithTime = { ...fiveHour, timeRemainingMinutes };
144338
+ }
144339
+ let sevenDayWithProgress = null;
144340
+ if (sevenDay) {
144341
+ const now = new Date;
144342
+ const resetAt = new Date(sevenDay.resetAt);
144343
+ const periodStart = new Date(resetAt);
144344
+ periodStart.setDate(periodStart.getDate() - 7);
144345
+ let weekProgressPercent;
144346
+ if (now > resetAt) {
144347
+ const newResetAt = new Date(resetAt);
144348
+ newResetAt.setDate(newResetAt.getDate() + 7);
144349
+ const totalMs = newResetAt.getTime() - resetAt.getTime();
144350
+ const elapsedMs = now.getTime() - resetAt.getTime();
144351
+ weekProgressPercent = Math.round(elapsedMs / totalMs * 100);
144352
+ } else {
144353
+ const totalMs = resetAt.getTime() - periodStart.getTime();
144354
+ const elapsedMs = now.getTime() - periodStart.getTime();
144355
+ weekProgressPercent = Math.max(0, Math.min(100, Math.round(elapsedMs / totalMs * 100)));
144356
+ }
144357
+ sevenDayWithProgress = { ...sevenDay, weekProgressPercent };
144358
+ }
144359
+ return {
144360
+ available: true,
144361
+ fiveHour: fiveHourWithTime,
144362
+ sevenDay: sevenDayWithProgress,
144363
+ sevenDayOpus: parseBlock(data.seven_day_opus ?? undefined),
144364
+ sevenDaySonnet: parseBlock(data.seven_day_sonnet ?? undefined)
144365
+ };
144366
+ } catch (err) {
144367
+ const message = err instanceof Error ? err.message : String(err);
144368
+ return { available: false, fiveHour: null, sevenDay: null, sevenDayOpus: null, sevenDaySonnet: null, error: message };
144369
+ }
144370
+ }
144371
+ monitoringRoutes.get("/claude-usage", async (c) => {
144372
+ const now = Date.now();
144373
+ const zaiSettings = getZAiSettings();
144374
+ if (zaiSettings.enabled && zaiSettings.apiKey) {
144375
+ const response = {
144376
+ available: false,
144377
+ fiveHour: null,
144378
+ sevenDay: null,
144379
+ sevenDayOpus: null,
144380
+ sevenDaySonnet: null,
144381
+ error: "Usage tracking is not available when using z.ai proxy. Check your z.ai dashboard at https://z.ai for usage statistics."
144382
+ };
144383
+ cachedUsage = response;
144384
+ usageCacheTimestamp = now;
144385
+ return c.json(response);
144386
+ }
144387
+ const token = await getClaudeOAuthToken();
144388
+ if (!token) {
144389
+ const response = {
144390
+ available: false,
144391
+ fiveHour: null,
144392
+ sevenDay: null,
144393
+ sevenDayOpus: null,
144394
+ sevenDaySonnet: null,
144395
+ error: "No Claude Code OAuth token found"
144396
+ };
144397
+ return c.json(response);
144398
+ }
144399
+ const usage = await fetchClaudeUsage(token);
144400
+ cachedUsage = usage;
144401
+ usageCacheTimestamp = now;
144402
+ return c.json(usage);
144403
+ });
144130
144404
 
144131
144405
  // server/app.ts
144132
144406
  function getDistPath() {
144133
144407
  if (process.env.VIBORA_PACKAGE_ROOT) {
144134
- return join11(process.env.VIBORA_PACKAGE_ROOT, "dist");
144408
+ return join12(process.env.VIBORA_PACKAGE_ROOT, "dist");
144135
144409
  }
144136
- return join11(process.cwd(), "dist");
144410
+ return join12(process.cwd(), "dist");
144137
144411
  }
144138
144412
  function createApp() {
144139
144413
  const app13 = new Hono2;
@@ -144180,8 +144454,8 @@ function createApp() {
144180
144454
  });
144181
144455
  };
144182
144456
  app13.get("/assets/*", async (c) => {
144183
- const assetPath = join11(distPath, c.req.path);
144184
- if (existsSync10(assetPath)) {
144457
+ const assetPath = join12(distPath, c.req.path);
144458
+ if (existsSync11(assetPath)) {
144185
144459
  return serveFile(assetPath);
144186
144460
  }
144187
144461
  return c.notFound();
@@ -144189,8 +144463,8 @@ function createApp() {
144189
144463
  const staticFiles = ["favicon.ico", "vibora-icon.png", "vibora-logo.jpeg", "vite.svg"];
144190
144464
  for (const file of staticFiles) {
144191
144465
  app13.get(`/${file}`, async () => {
144192
- const filePath = join11(distPath, file);
144193
- if (existsSync10(filePath)) {
144466
+ const filePath = join12(distPath, file);
144467
+ if (existsSync11(filePath)) {
144194
144468
  return serveFile(filePath);
144195
144469
  }
144196
144470
  return new Response("Not Found", { status: 404 });
@@ -144201,7 +144475,7 @@ function createApp() {
144201
144475
  if (path9.startsWith("/api/") || path9.startsWith("/ws/") || path9 === "/health") {
144202
144476
  return next();
144203
144477
  }
144204
- const html = await readFile2(join11(distPath, "index.html"), "utf-8");
144478
+ const html = await readFile2(join12(distPath, "index.html"), "utf-8");
144205
144479
  return c.html(html);
144206
144480
  });
144207
144481
  }