@vibe80/vibe80 0.1.7 → 0.1.9
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/vibe80.js +14 -1
- package/package.json +4 -2
- package/server/package.json +3 -1
- package/server/src/claudeClient.js +3 -2
- package/server/src/index.js +42 -3
- package/server/src/routes/sessions.js +1 -1
- package/server/src/routes/worktrees.js +12 -1
- package/server/src/services/workspace.js +85 -11
package/bin/vibe80.js
CHANGED
|
@@ -19,6 +19,9 @@ const defaultEnv = {
|
|
|
19
19
|
};
|
|
20
20
|
const deploymentMode = process.env.DEPLOYMENT_MODE || defaultEnv.DEPLOYMENT_MODE;
|
|
21
21
|
const serverPort = process.env.PORT || "5179";
|
|
22
|
+
const cliArgs = process.argv.slice(2);
|
|
23
|
+
const enableCodexFromCli = cliArgs.includes("--codex");
|
|
24
|
+
const enableClaudeFromCli = cliArgs.includes("--claude");
|
|
22
25
|
|
|
23
26
|
const spawnProcess = (cmd, args, label, extraEnv = {}) => {
|
|
24
27
|
const child = spawn(cmd, args, {
|
|
@@ -127,11 +130,21 @@ const shutdown = (code = 0) => {
|
|
|
127
130
|
|
|
128
131
|
const startServer = () => {
|
|
129
132
|
unlinkMonoAuthUrlFile();
|
|
133
|
+
const monoProviderEnv = {};
|
|
134
|
+
if (enableCodexFromCli) {
|
|
135
|
+
monoProviderEnv.VIBE80_MONO_ENABLE_CODEX = "true";
|
|
136
|
+
}
|
|
137
|
+
if (enableClaudeFromCli) {
|
|
138
|
+
monoProviderEnv.VIBE80_MONO_ENABLE_CLAUDE = "true";
|
|
139
|
+
}
|
|
130
140
|
server = spawnProcess(
|
|
131
141
|
process.execPath,
|
|
132
142
|
["server/src/index.js"],
|
|
133
143
|
"server",
|
|
134
|
-
{
|
|
144
|
+
{
|
|
145
|
+
VIBE80_MONO_AUTH_URL_FILE: monoAuthUrlFile,
|
|
146
|
+
...monoProviderEnv,
|
|
147
|
+
}
|
|
135
148
|
);
|
|
136
149
|
void maybeOpenMonoAuthUrl();
|
|
137
150
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe80/vibe80",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"server",
|
|
@@ -37,10 +37,12 @@
|
|
|
37
37
|
"express-rate-limit": "^7.4.0",
|
|
38
38
|
"jsonwebtoken": "^9.0.3",
|
|
39
39
|
"multer": "^1.4.5-lts.1",
|
|
40
|
-
"node-pty": "^1.0.0",
|
|
41
40
|
"redis": "^4.7.1",
|
|
42
41
|
"sqlite3": "^5.1.7",
|
|
43
42
|
"ws": "^8.17.1"
|
|
44
43
|
},
|
|
44
|
+
"optionalDependencies": {
|
|
45
|
+
"node-pty": "^1.0.0"
|
|
46
|
+
},
|
|
45
47
|
"license": "Apache-2.0"
|
|
46
48
|
}
|
package/server/package.json
CHANGED
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
"express-rate-limit": "^7.4.0",
|
|
17
17
|
"jsonwebtoken": "^9.0.3",
|
|
18
18
|
"multer": "^1.4.5-lts.1",
|
|
19
|
-
"node-pty": "^1.0.0",
|
|
20
19
|
"redis": "^4.7.1",
|
|
21
20
|
"sqlite3": "^5.1.7",
|
|
22
21
|
"ws": "^8.17.1"
|
|
23
22
|
},
|
|
23
|
+
"optionalDependencies": {
|
|
24
|
+
"node-pty": "^1.0.0"
|
|
25
|
+
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@vitest/coverage-v8": "^4.0.18",
|
|
26
28
|
"supertest": "^7.2.2",
|
|
@@ -48,7 +48,7 @@ export class ClaudeCliClient extends EventEmitter {
|
|
|
48
48
|
this.tmpDir = tmpDir || null;
|
|
49
49
|
this.sessionId = sessionId || null;
|
|
50
50
|
this.worktreeId = worktreeId || "main";
|
|
51
|
-
this.ready =
|
|
51
|
+
this.ready = true;
|
|
52
52
|
this.threadId = threadId || null;
|
|
53
53
|
this.pendingForkFromThreadId = forkFromThreadId || null;
|
|
54
54
|
this.modelInfo = null;
|
|
@@ -77,7 +77,6 @@ export class ClaudeCliClient extends EventEmitter {
|
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
this.activeProcess = null;
|
|
80
|
-
this.ready = false;
|
|
81
80
|
const exitPromise = new Promise((resolve) => {
|
|
82
81
|
proc.once("exit", resolve);
|
|
83
82
|
proc.once("close", resolve);
|
|
@@ -223,6 +222,7 @@ export class ClaudeCliClient extends EventEmitter {
|
|
|
223
222
|
cwd: isMonoUser ? this.cwd : undefined,
|
|
224
223
|
});
|
|
225
224
|
this.activeProcess = proc;
|
|
225
|
+
this.ready = false;
|
|
226
226
|
|
|
227
227
|
proc.stdout.setEncoding("utf8");
|
|
228
228
|
proc.stdout.on("data", (chunk) => {
|
|
@@ -264,6 +264,7 @@ export class ClaudeCliClient extends EventEmitter {
|
|
|
264
264
|
if (this.activeProcess === proc) {
|
|
265
265
|
this.activeProcess = null;
|
|
266
266
|
}
|
|
267
|
+
this.ready = true;
|
|
267
268
|
this.#flushLogBuffer("OUT", "stdoutLogBuffer");
|
|
268
269
|
this.#flushLogBuffer("ERR", "stderrLogBuffer");
|
|
269
270
|
this.providerLogger?.close?.();
|
package/server/src/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import path from "path";
|
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { WebSocketServer } from "ws";
|
|
7
|
-
import * as pty from "node-pty";
|
|
8
7
|
import rateLimit from "express-rate-limit";
|
|
9
8
|
import storage from "./storage/index.js";
|
|
10
9
|
import {
|
|
@@ -102,9 +101,23 @@ if (trustProxySetting !== undefined) {
|
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
|
-
const
|
|
104
|
+
const terminalRequested = !/^(0|false|no|off)$/i.test(
|
|
106
105
|
process.env.TERMINAL_ENABLED || ""
|
|
107
106
|
);
|
|
107
|
+
let pty = null;
|
|
108
|
+
let terminalAvailable = false;
|
|
109
|
+
if (terminalRequested) {
|
|
110
|
+
try {
|
|
111
|
+
const ptyModule = await import("node-pty");
|
|
112
|
+
pty = ptyModule;
|
|
113
|
+
terminalAvailable = true;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.warn(
|
|
116
|
+
`[warn] Terminal disabled: node-pty is unavailable (${error?.message || error}).`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const terminalEnabled = terminalRequested && terminalAvailable;
|
|
108
121
|
const allowRunSlashCommand = !/^(0|false|no|off)$/i.test(
|
|
109
122
|
process.env.ALLOW_RUN_SLASH_COMMAND || ""
|
|
110
123
|
);
|
|
@@ -205,6 +218,7 @@ const routeDeps = {
|
|
|
205
218
|
attachClaudeEventsForWorktree,
|
|
206
219
|
deploymentMode,
|
|
207
220
|
debugApiWsLog,
|
|
221
|
+
terminalEnabled,
|
|
208
222
|
};
|
|
209
223
|
|
|
210
224
|
app.use("/api/v1", healthRoutes({
|
|
@@ -662,9 +676,20 @@ wss.on("connection", (socket, req) => {
|
|
|
662
676
|
return;
|
|
663
677
|
}
|
|
664
678
|
const isMainWorktree = worktreeId === "main";
|
|
665
|
-
|
|
679
|
+
let client = isMainWorktree
|
|
666
680
|
? getActiveClient(session)
|
|
667
681
|
: runtime.worktreeClients.get(worktreeId);
|
|
682
|
+
if (isMainWorktree && !client) {
|
|
683
|
+
const provider = session.activeProvider === "claude" ? "claude" : "codex";
|
|
684
|
+
client = await getOrCreateClient(session, provider);
|
|
685
|
+
if (!client.listenerCount("ready")) {
|
|
686
|
+
if (provider === "claude") {
|
|
687
|
+
attachClaudeEvents(sessionId, client, provider);
|
|
688
|
+
} else {
|
|
689
|
+
attachClientEvents(sessionId, client, provider);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
668
693
|
if (!client?.ready) {
|
|
669
694
|
const label = isMainWorktree
|
|
670
695
|
? getProviderLabel(session)
|
|
@@ -1146,6 +1171,20 @@ wss.on("connection", (socket, req) => {
|
|
|
1146
1171
|
});
|
|
1147
1172
|
});
|
|
1148
1173
|
}
|
|
1174
|
+
} else if (session.activeProvider === "claude") {
|
|
1175
|
+
const client = await getOrCreateClient(session, "claude");
|
|
1176
|
+
if (!client.listenerCount("ready")) {
|
|
1177
|
+
attachClaudeEvents(sessionId, client, "claude");
|
|
1178
|
+
}
|
|
1179
|
+
if (!client.ready && !client.activeProcess) {
|
|
1180
|
+
client.start().catch((error) => {
|
|
1181
|
+
console.error("Failed to restart Claude CLI:", error);
|
|
1182
|
+
broadcastToSession(sessionId, {
|
|
1183
|
+
type: "error",
|
|
1184
|
+
message: "Claude CLI failed to start.",
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1149
1188
|
}
|
|
1150
1189
|
|
|
1151
1190
|
authenticated = true;
|
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
clearWorktreeMessages,
|
|
34
34
|
} from "../worktreeManager.js";
|
|
35
35
|
|
|
36
|
-
const terminalEnabled = /^(1|true|yes|on)$/i.test(process.env.TERMINAL_ENABLED || "1");
|
|
37
36
|
const instanceHostname = process.env.HOSTNAME || os.hostname();
|
|
38
37
|
|
|
39
38
|
export default function sessionRoutes(deps) {
|
|
@@ -43,6 +42,7 @@ export default function sessionRoutes(deps) {
|
|
|
43
42
|
attachClaudeEvents,
|
|
44
43
|
getActiveClient,
|
|
45
44
|
deploymentMode,
|
|
45
|
+
terminalEnabled,
|
|
46
46
|
} = deps;
|
|
47
47
|
|
|
48
48
|
const router = Router();
|
|
@@ -430,9 +430,20 @@ export default function worktreeRoutes(deps) {
|
|
|
430
430
|
|
|
431
431
|
const isMainWorktree = worktreeId === "main";
|
|
432
432
|
const runtime = getSessionRuntime(sessionId);
|
|
433
|
-
|
|
433
|
+
let client = isMainWorktree
|
|
434
434
|
? getActiveClient(session)
|
|
435
435
|
: runtime?.worktreeClients?.get(worktreeId);
|
|
436
|
+
if (isMainWorktree && !client) {
|
|
437
|
+
const provider = session.activeProvider === "claude" ? "claude" : "codex";
|
|
438
|
+
client = await getOrCreateClient(session, provider);
|
|
439
|
+
if (!client.listenerCount("ready")) {
|
|
440
|
+
if (provider === "claude") {
|
|
441
|
+
attachClaudeEvents(sessionId, client, provider);
|
|
442
|
+
} else {
|
|
443
|
+
attachClientEvents(sessionId, client, provider);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
436
447
|
if (!client?.ready) {
|
|
437
448
|
const label = isMainWorktree
|
|
438
449
|
? worktree.provider === "claude"
|
|
@@ -765,14 +765,12 @@ export const ensureDefaultMonoWorkspace = async () => {
|
|
|
765
765
|
return;
|
|
766
766
|
}
|
|
767
767
|
const workspaceId = "default";
|
|
768
|
+
const enabledProviders = getMonoEnabledProvidersFromEnv();
|
|
768
769
|
await ensureWorkspaceDirs(workspaceId);
|
|
769
770
|
const ids = await getWorkspaceUserIds(workspaceId);
|
|
770
771
|
const existing = await storage.getWorkspace(workspaceId);
|
|
771
772
|
if (!existing) {
|
|
772
|
-
const providers = {
|
|
773
|
-
codex: { enabled: true, auth: null },
|
|
774
|
-
claude: { enabled: true, auth: null },
|
|
775
|
-
};
|
|
773
|
+
const providers = applyMonoEnabledProviders({}, enabledProviders);
|
|
776
774
|
await persistWorkspaceRecord({
|
|
777
775
|
workspaceId,
|
|
778
776
|
providers,
|
|
@@ -781,12 +779,69 @@ export const ensureDefaultMonoWorkspace = async () => {
|
|
|
781
779
|
existing: null,
|
|
782
780
|
});
|
|
783
781
|
await appendAuditLog(workspaceId, "workspace_created");
|
|
782
|
+
return;
|
|
784
783
|
}
|
|
784
|
+
const providers = applyMonoEnabledProviders(existing.providers || {}, enabledProviders);
|
|
785
|
+
await persistWorkspaceRecord({
|
|
786
|
+
workspaceId,
|
|
787
|
+
providers,
|
|
788
|
+
ids,
|
|
789
|
+
existing,
|
|
790
|
+
});
|
|
791
|
+
await appendAuditLog(workspaceId, "workspace_providers_activation_updated", {
|
|
792
|
+
codexEnabled: providers.codex?.enabled ?? false,
|
|
793
|
+
claudeEnabled: providers.claude?.enabled ?? false,
|
|
794
|
+
});
|
|
785
795
|
};
|
|
786
796
|
|
|
787
797
|
const readEnvValue = (name) =>
|
|
788
798
|
typeof process.env[name] === "string" ? process.env[name].trim() : "";
|
|
789
799
|
|
|
800
|
+
const parseMonoEnableEnv = (name) => {
|
|
801
|
+
const raw = process.env[name];
|
|
802
|
+
if (typeof raw !== "string") {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
const value = raw.trim().toLowerCase();
|
|
806
|
+
if (["1", "true", "yes", "on"].includes(value)) {
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
if (["0", "false", "no", "off", ""].includes(value)) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
throw new Error(
|
|
813
|
+
`Invalid ${name} value \"${raw}\". Use one of: true/false, 1/0, yes/no, on/off.`
|
|
814
|
+
);
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
const getMonoEnabledProvidersFromEnv = () => {
|
|
818
|
+
const codexEnabled = parseMonoEnableEnv("VIBE80_MONO_ENABLE_CODEX");
|
|
819
|
+
const claudeEnabled = parseMonoEnableEnv("VIBE80_MONO_ENABLE_CLAUDE");
|
|
820
|
+
if (!codexEnabled && !claudeEnabled) {
|
|
821
|
+
throw new Error(
|
|
822
|
+
"In mono_user mode, enable at least one provider via --codex/--claude or VIBE80_MONO_ENABLE_CODEX/VIBE80_MONO_ENABLE_CLAUDE."
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
return { codexEnabled, claudeEnabled };
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
const applyMonoEnabledProviders = (providers = {}, enabledConfig) => {
|
|
829
|
+
const { codexEnabled, claudeEnabled } = enabledConfig;
|
|
830
|
+
const codexPrev = providers?.codex && typeof providers.codex === "object" ? providers.codex : {};
|
|
831
|
+
const claudePrev = providers?.claude && typeof providers.claude === "object" ? providers.claude : {};
|
|
832
|
+
return {
|
|
833
|
+
...providers,
|
|
834
|
+
codex: {
|
|
835
|
+
enabled: Boolean(codexEnabled),
|
|
836
|
+
auth: codexPrev.auth || null,
|
|
837
|
+
},
|
|
838
|
+
claude: {
|
|
839
|
+
enabled: Boolean(claudeEnabled),
|
|
840
|
+
auth: claudePrev.auth || null,
|
|
841
|
+
},
|
|
842
|
+
};
|
|
843
|
+
};
|
|
844
|
+
|
|
790
845
|
const buildMonoUserProviderOverridesFromEnv = () => {
|
|
791
846
|
const codexApiKey = readEnvValue("CODEX_API_KEY");
|
|
792
847
|
const codexAuthJsonB64Raw = process.env.CODEX_AUTH_JSON_B64;
|
|
@@ -814,7 +869,6 @@ const buildMonoUserProviderOverridesFromEnv = () => {
|
|
|
814
869
|
const decoded = decodeBase64(codexAuthJsonB64);
|
|
815
870
|
validateCodexAuthJson(decoded);
|
|
816
871
|
overrides.codex = {
|
|
817
|
-
enabled: true,
|
|
818
872
|
auth: { type: "auth_json_b64", value: codexAuthJsonB64 },
|
|
819
873
|
};
|
|
820
874
|
} catch {
|
|
@@ -825,7 +879,6 @@ const buildMonoUserProviderOverridesFromEnv = () => {
|
|
|
825
879
|
}
|
|
826
880
|
} else if (codexApiKey) {
|
|
827
881
|
overrides.codex = {
|
|
828
|
-
enabled: true,
|
|
829
882
|
auth: { type: "api_key", value: codexApiKey },
|
|
830
883
|
};
|
|
831
884
|
}
|
|
@@ -842,13 +895,11 @@ const buildMonoUserProviderOverridesFromEnv = () => {
|
|
|
842
895
|
);
|
|
843
896
|
} else {
|
|
844
897
|
overrides.claude = {
|
|
845
|
-
enabled: true,
|
|
846
898
|
auth: { type: "setup_token", value: claudeSetupToken },
|
|
847
899
|
};
|
|
848
900
|
}
|
|
849
901
|
} else if (claudeApiKey) {
|
|
850
902
|
overrides.claude = {
|
|
851
|
-
enabled: true,
|
|
852
903
|
auth: { type: "api_key", value: claudeApiKey },
|
|
853
904
|
};
|
|
854
905
|
}
|
|
@@ -869,7 +920,25 @@ export const applyMonoUserProviderOverridesFromEnv = async () => {
|
|
|
869
920
|
if (!existing) {
|
|
870
921
|
return;
|
|
871
922
|
}
|
|
872
|
-
const
|
|
923
|
+
const activationConfig = getMonoEnabledProvidersFromEnv();
|
|
924
|
+
const mergedProviders = mergeProvidersForUpdate(existing.providers || {}, {
|
|
925
|
+
...(overrides.codex
|
|
926
|
+
? {
|
|
927
|
+
codex: {
|
|
928
|
+
enabled: activationConfig.codexEnabled,
|
|
929
|
+
auth: overrides.codex.auth,
|
|
930
|
+
},
|
|
931
|
+
}
|
|
932
|
+
: {}),
|
|
933
|
+
...(overrides.claude
|
|
934
|
+
? {
|
|
935
|
+
claude: {
|
|
936
|
+
enabled: activationConfig.claudeEnabled,
|
|
937
|
+
auth: overrides.claude.auth,
|
|
938
|
+
},
|
|
939
|
+
}
|
|
940
|
+
: {}),
|
|
941
|
+
});
|
|
873
942
|
const ids = await getWorkspaceUserIds(workspaceId);
|
|
874
943
|
await writeWorkspaceProviderAuth(workspaceId, mergedProviders);
|
|
875
944
|
await persistWorkspaceRecord({
|
|
@@ -891,13 +960,18 @@ export const createWorkspace = async (providers) => {
|
|
|
891
960
|
if (isMonoUser) {
|
|
892
961
|
const workspaceId = "default";
|
|
893
962
|
await ensureWorkspaceDirs(workspaceId);
|
|
963
|
+
const enabledProviders = getMonoEnabledProvidersFromEnv();
|
|
894
964
|
const secret = "default";
|
|
895
965
|
const ids = await getWorkspaceUserIds(workspaceId);
|
|
896
966
|
const existing = await storage.getWorkspace(workspaceId);
|
|
897
|
-
|
|
967
|
+
const providersWithActivation = applyMonoEnabledProviders(
|
|
968
|
+
providers || existing?.providers || {},
|
|
969
|
+
enabledProviders
|
|
970
|
+
);
|
|
971
|
+
await writeWorkspaceProviderAuth(workspaceId, providersWithActivation);
|
|
898
972
|
await persistWorkspaceRecord({
|
|
899
973
|
workspaceId,
|
|
900
|
-
providers,
|
|
974
|
+
providers: providersWithActivation,
|
|
901
975
|
ids,
|
|
902
976
|
workspaceSecretHash: hashWorkspaceSecret(secret),
|
|
903
977
|
existing,
|