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/README.md +12 -15
- package/bin/vibora.js +95 -208
- package/dist/assets/index-BiapsD8F.css +1 -0
- package/dist/assets/index-C4ahV9ZS.js +116 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/index.js +369 -95
- package/dist/assets/index-BxbgLbxS.css +0 -1
- package/dist/assets/index-C7bo3ECQ.css +0 -1
- package/dist/assets/index-CCtJOkVu.js +0 -116
- package/dist/assets/index-QutiQrzr.js +0 -45
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-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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
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
|
|
3727
|
-
import { existsSync as
|
|
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 =
|
|
13439
|
+
const fileDir = dirname2(base);
|
|
13363
13440
|
const dirName = basename(fileDir);
|
|
13364
|
-
const here = dirName === "src" || dirName === "dist" ?
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
|
137838
|
-
|
|
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
|
-
|
|
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
|
|
144408
|
+
return join12(process.env.VIBORA_PACKAGE_ROOT, "dist");
|
|
144135
144409
|
}
|
|
144136
|
-
return
|
|
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 =
|
|
144184
|
-
if (
|
|
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 =
|
|
144193
|
-
if (
|
|
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(
|
|
144478
|
+
const html = await readFile2(join12(distPath, "index.html"), "utf-8");
|
|
144205
144479
|
return c.html(html);
|
|
144206
144480
|
});
|
|
144207
144481
|
}
|