@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 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
- { VIBE80_MONO_AUTH_URL_FILE: monoAuthUrlFile }
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.7",
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
  }
@@ -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 = false;
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?.();
@@ -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 terminalEnabled = !/^(0|false|no|off)$/i.test(
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
- const client = isMainWorktree
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
- const client = isMainWorktree
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 mergedProviders = mergeProvidersForUpdate(existing.providers || {}, overrides);
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
- await writeWorkspaceProviderAuth(workspaceId, providers);
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,