@vibe80/vibe80 0.1.3 → 0.1.8

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
@@ -14,12 +14,14 @@ const monoAuthUrlFile = path.join(
14
14
  );
15
15
  const defaultEnv = {
16
16
  DEPLOYMENT_MODE: "mono_user",
17
- JWT_KEY_PATH: path.join(homeDir, ".vibe80", "jwt.key"),
17
+ VIBE80_DATA_DIRECTORY: path.join(homeDir, ".vibe80"),
18
18
  STORAGE_BACKEND: "sqlite",
19
- SQLITE_PATH: path.join(homeDir, ".vibe80", "data.sqlite"),
20
19
  };
21
20
  const deploymentMode = process.env.DEPLOYMENT_MODE || defaultEnv.DEPLOYMENT_MODE;
22
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");
23
25
 
24
26
  const spawnProcess = (cmd, args, label, extraEnv = {}) => {
25
27
  const child = spawn(cmd, args, {
@@ -128,11 +130,21 @@ const shutdown = (code = 0) => {
128
130
 
129
131
  const startServer = () => {
130
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
+ }
131
140
  server = spawnProcess(
132
141
  process.execPath,
133
142
  ["server/src/index.js"],
134
143
  "server",
135
- { VIBE80_MONO_AUTH_URL_FILE: monoAuthUrlFile }
144
+ {
145
+ VIBE80_MONO_AUTH_URL_FILE: monoAuthUrlFile,
146
+ ...monoProviderEnv,
147
+ }
136
148
  );
137
149
  void maybeOpenMonoAuthUrl();
138
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe80/vibe80",
3
- "version": "0.1.3",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "server",
@@ -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?.();
@@ -45,6 +45,7 @@ import {
45
45
  } from "./services/auth.js";
46
46
  import {
47
47
  ensureDefaultMonoWorkspace,
48
+ applyMonoUserProviderOverridesFromEnv,
48
49
  isMonoUser,
49
50
  getWorkspacePaths,
50
51
  getWorkspaceSshPaths,
@@ -154,6 +155,7 @@ const sudoPath = process.env.VIBE80_SUDO_PATH || "sudo";
154
155
 
155
156
  await storage.init();
156
157
  await ensureDefaultMonoWorkspace();
158
+ await applyMonoUserProviderOverridesFromEnv();
157
159
 
158
160
  // ---------------------------------------------------------------------------
159
161
  // Middleware pipeline
@@ -660,9 +662,20 @@ wss.on("connection", (socket, req) => {
660
662
  return;
661
663
  }
662
664
  const isMainWorktree = worktreeId === "main";
663
- const client = isMainWorktree
665
+ let client = isMainWorktree
664
666
  ? getActiveClient(session)
665
667
  : runtime.worktreeClients.get(worktreeId);
668
+ if (isMainWorktree && !client) {
669
+ const provider = session.activeProvider === "claude" ? "claude" : "codex";
670
+ client = await getOrCreateClient(session, provider);
671
+ if (!client.listenerCount("ready")) {
672
+ if (provider === "claude") {
673
+ attachClaudeEvents(sessionId, client, provider);
674
+ } else {
675
+ attachClientEvents(sessionId, client, provider);
676
+ }
677
+ }
678
+ }
666
679
  if (!client?.ready) {
667
680
  const label = isMainWorktree
668
681
  ? getProviderLabel(session)
@@ -1144,6 +1157,20 @@ wss.on("connection", (socket, req) => {
1144
1157
  });
1145
1158
  });
1146
1159
  }
1160
+ } else if (session.activeProvider === "claude") {
1161
+ const client = await getOrCreateClient(session, "claude");
1162
+ if (!client.listenerCount("ready")) {
1163
+ attachClaudeEvents(sessionId, client, "claude");
1164
+ }
1165
+ if (!client.ready && !client.activeProcess) {
1166
+ client.start().catch((error) => {
1167
+ console.error("Failed to restart Claude CLI:", error);
1168
+ broadcastToSession(sessionId, {
1169
+ type: "error",
1170
+ message: "Claude CLI failed to start.",
1171
+ });
1172
+ });
1173
+ }
1147
1174
  }
1148
1175
 
1149
1176
  authenticated = true;
@@ -1,9 +1,16 @@
1
1
  import crypto from "crypto";
2
2
  import fs from "fs";
3
+ import os from "os";
3
4
  import path from "path";
4
5
  import jwt from "jsonwebtoken";
5
6
 
6
- const jwtKeyPath = process.env.JWT_KEY_PATH || "/var/lib/vibe80/jwt.key";
7
+ const homeDir = process.env.HOME || os.homedir();
8
+ const isMonoUser = process.env.DEPLOYMENT_MODE === "mono_user";
9
+ const defaultDataDirectory = isMonoUser
10
+ ? path.join(homeDir, ".vibe80")
11
+ : "/var/lib/vibe80";
12
+ const dataDirectory = process.env.VIBE80_DATA_DIRECTORY || defaultDataDirectory;
13
+ const jwtKeyPath = process.env.JWT_KEY_PATH || path.join(dataDirectory, "jwt.key");
7
14
  const jwtIssuer = process.env.JWT_ISSUER || "vibe80";
8
15
  const jwtAudience = process.env.JWT_AUDIENCE || "workspace";
9
16
  const accessTokenTtlSeconds =
@@ -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"
@@ -8,6 +8,8 @@ const SUDO_PATH = process.env.VIBE80_SUDO_PATH || "sudo";
8
8
  const DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE;
9
9
  const IS_MONO_USER = DEPLOYMENT_MODE === "mono_user";
10
10
  const WORKSPACE_ROOT_DIRECTORY = process.env.WORKSPACE_ROOT_DIRECTORY || "/workspaces";
11
+ const MONO_USER_WORKSPACE_DIR =
12
+ process.env.MONO_USER_WORKSPACE_DIR || path.join(os.homedir(), "vibe80_workspace");
11
13
  const ALLOWED_ENV_KEYS = new Set([
12
14
  "GIT_SSH_COMMAND",
13
15
  "GIT_CONFIG_GLOBAL",
@@ -151,15 +153,21 @@ export const getWorkspaceHome = (workspaceId) => {
151
153
 
152
154
  export const getWorkspaceRoot = (workspaceId) =>
153
155
  (IS_MONO_USER
154
- ? path.join(os.homedir(), "vibe80_workspace")
156
+ ? MONO_USER_WORKSPACE_DIR
155
157
  : path.join(WORKSPACE_ROOT_DIRECTORY, workspaceId));
156
158
 
157
159
  const validateCwd = (workspaceId, cwd) => {
158
160
  const resolved = path.resolve(cwd);
159
161
  const homeDir = getWorkspaceHome(workspaceId);
162
+ const workspaceRootDir = getWorkspaceRoot(workspaceId);
163
+ const inHome =
164
+ resolved === homeDir || resolved.startsWith(homeDir + path.sep);
165
+ const inWorkspaceRoot =
166
+ resolved === workspaceRootDir ||
167
+ resolved.startsWith(workspaceRootDir + path.sep);
160
168
  if (
161
- resolved !== homeDir &&
162
- !resolved.startsWith(homeDir + path.sep)
169
+ !inHome &&
170
+ !(IS_MONO_USER && inWorkspaceRoot)
163
171
  ) {
164
172
  throw new Error("cwd outside workspace");
165
173
  }
@@ -16,6 +16,8 @@ const isMonoUser = deploymentMode === "mono_user";
16
16
  const workspaceHomeBase = process.env.WORKSPACE_HOME_BASE || "/home";
17
17
  const workspaceRootBase = process.env.WORKSPACE_ROOT_DIRECTORY || "/workspaces";
18
18
  const workspaceRootName = "vibe80_workspace";
19
+ const monoUserWorkspaceDir =
20
+ process.env.MONO_USER_WORKSPACE_DIR || path.join(os.homedir(), workspaceRootName);
19
21
  const workspaceSessionsDirName = "sessions";
20
22
  const rootHelperPath = process.env.VIBE80_ROOT_HELPER || "/usr/local/bin/vibe80-root";
21
23
  const sudoPath = process.env.VIBE80_SUDO_PATH || "sudo";
@@ -37,7 +39,7 @@ const runRootCommand = (args, options = {}) => {
37
39
  export const getWorkspacePaths = (workspaceId) => {
38
40
  const home = isMonoUser ? os.homedir() : path.join(workspaceHomeBase, workspaceId);
39
41
  const root = isMonoUser
40
- ? path.join(home, workspaceRootName)
42
+ ? monoUserWorkspaceDir
41
43
  : path.join(workspaceRootBase, workspaceId);
42
44
  const sessionsDir = path.join(root, workspaceSessionsDirName);
43
45
  return {
@@ -763,14 +765,12 @@ export const ensureDefaultMonoWorkspace = async () => {
763
765
  return;
764
766
  }
765
767
  const workspaceId = "default";
768
+ const enabledProviders = getMonoEnabledProvidersFromEnv();
766
769
  await ensureWorkspaceDirs(workspaceId);
767
770
  const ids = await getWorkspaceUserIds(workspaceId);
768
771
  const existing = await storage.getWorkspace(workspaceId);
769
772
  if (!existing) {
770
- const providers = {
771
- codex: { enabled: true, auth: null },
772
- claude: { enabled: true, auth: null },
773
- };
773
+ const providers = applyMonoEnabledProviders({}, enabledProviders);
774
774
  await persistWorkspaceRecord({
775
775
  workspaceId,
776
776
  providers,
@@ -779,7 +779,177 @@ export const ensureDefaultMonoWorkspace = async () => {
779
779
  existing: null,
780
780
  });
781
781
  await appendAuditLog(workspaceId, "workspace_created");
782
+ return;
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
+ });
795
+ };
796
+
797
+ const readEnvValue = (name) =>
798
+ typeof process.env[name] === "string" ? process.env[name].trim() : "";
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
+
845
+ const buildMonoUserProviderOverridesFromEnv = () => {
846
+ const codexApiKey = readEnvValue("CODEX_API_KEY");
847
+ const codexAuthJsonB64Raw = process.env.CODEX_AUTH_JSON_B64;
848
+ const hasCodexAuthJsonB64 = typeof codexAuthJsonB64Raw === "string";
849
+ const codexAuthJsonB64 = hasCodexAuthJsonB64 ? codexAuthJsonB64Raw.trim() : "";
850
+ const claudeApiKey = readEnvValue("CLAUDE_API_KEY");
851
+ const claudeSetupTokenRaw = process.env.CLAUDE_SETUP_TOKEN;
852
+ const hasClaudeSetupToken = typeof claudeSetupTokenRaw === "string";
853
+ const claudeSetupToken = hasClaudeSetupToken ? claudeSetupTokenRaw.trim() : "";
854
+
855
+ const overrides = {};
856
+
857
+ if (codexApiKey && hasCodexAuthJsonB64) {
858
+ console.warn(
859
+ "[warn] Both CODEX_API_KEY and CODEX_AUTH_JSON_B64 are set; using CODEX_AUTH_JSON_B64."
860
+ );
861
+ }
862
+ if (hasCodexAuthJsonB64) {
863
+ if (!codexAuthJsonB64) {
864
+ console.warn(
865
+ "[warn] Invalid CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
866
+ );
867
+ } else {
868
+ try {
869
+ const decoded = decodeBase64(codexAuthJsonB64);
870
+ validateCodexAuthJson(decoded);
871
+ overrides.codex = {
872
+ auth: { type: "auth_json_b64", value: codexAuthJsonB64 },
873
+ };
874
+ } catch {
875
+ console.warn(
876
+ "[warn] Invalid CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
877
+ );
878
+ }
879
+ }
880
+ } else if (codexApiKey) {
881
+ overrides.codex = {
882
+ auth: { type: "api_key", value: codexApiKey },
883
+ };
884
+ }
885
+
886
+ if (claudeApiKey && hasClaudeSetupToken) {
887
+ console.warn(
888
+ "[warn] Both CLAUDE_API_KEY and CLAUDE_SETUP_TOKEN are set; using CLAUDE_SETUP_TOKEN."
889
+ );
890
+ }
891
+ if (hasClaudeSetupToken) {
892
+ if (!claudeSetupToken) {
893
+ console.warn(
894
+ "[warn] Invalid CLAUDE_SETUP_TOKEN detected; ignoring claude preprovisioning."
895
+ );
896
+ } else {
897
+ overrides.claude = {
898
+ auth: { type: "setup_token", value: claudeSetupToken },
899
+ };
900
+ }
901
+ } else if (claudeApiKey) {
902
+ overrides.claude = {
903
+ auth: { type: "api_key", value: claudeApiKey },
904
+ };
905
+ }
906
+
907
+ return overrides;
908
+ };
909
+
910
+ export const applyMonoUserProviderOverridesFromEnv = async () => {
911
+ if (!isMonoUser) {
912
+ return;
913
+ }
914
+ const overrides = buildMonoUserProviderOverridesFromEnv();
915
+ if (Object.keys(overrides).length === 0) {
916
+ return;
917
+ }
918
+ const workspaceId = "default";
919
+ const existing = await storage.getWorkspace(workspaceId);
920
+ if (!existing) {
921
+ return;
782
922
  }
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
+ });
942
+ const ids = await getWorkspaceUserIds(workspaceId);
943
+ await writeWorkspaceProviderAuth(workspaceId, mergedProviders);
944
+ await persistWorkspaceRecord({
945
+ workspaceId,
946
+ providers: mergedProviders,
947
+ ids,
948
+ existing,
949
+ });
950
+ await appendAuditLog(workspaceId, "workspace_providers_preprovisioned_from_env", {
951
+ providers: Object.keys(overrides),
952
+ });
783
953
  };
784
954
 
785
955
  export const createWorkspace = async (providers) => {
@@ -790,13 +960,18 @@ export const createWorkspace = async (providers) => {
790
960
  if (isMonoUser) {
791
961
  const workspaceId = "default";
792
962
  await ensureWorkspaceDirs(workspaceId);
963
+ const enabledProviders = getMonoEnabledProvidersFromEnv();
793
964
  const secret = "default";
794
965
  const ids = await getWorkspaceUserIds(workspaceId);
795
966
  const existing = await storage.getWorkspace(workspaceId);
796
- await writeWorkspaceProviderAuth(workspaceId, providers);
967
+ const providersWithActivation = applyMonoEnabledProviders(
968
+ providers || existing?.providers || {},
969
+ enabledProviders
970
+ );
971
+ await writeWorkspaceProviderAuth(workspaceId, providersWithActivation);
797
972
  await persistWorkspaceRecord({
798
973
  workspaceId,
799
- providers,
974
+ providers: providersWithActivation,
800
975
  ids,
801
976
  workspaceSecretHash: hashWorkspaceSecret(secret),
802
977
  existing,
@@ -1,4 +1,5 @@
1
1
  import fs from "fs";
2
+ import os from "os";
2
3
  import path from "path";
3
4
  import sqlite3 from "sqlite3";
4
5
 
@@ -70,7 +71,13 @@ const all = (db, sql, params = []) =>
70
71
  });
71
72
 
72
73
  export const createSqliteStorage = () => {
73
- const dbPath = process.env.SQLITE_PATH || "/var/lib/vibe80/base.sqlite";
74
+ const homeDir = process.env.HOME || os.homedir();
75
+ const isMonoUser = process.env.DEPLOYMENT_MODE === "mono_user";
76
+ const defaultDataDirectory = isMonoUser
77
+ ? path.join(homeDir, ".vibe80")
78
+ : "/var/lib/vibe80";
79
+ const dataDirectory = process.env.VIBE80_DATA_DIRECTORY || defaultDataDirectory;
80
+ const dbPath = process.env.SQLITE_PATH || path.join(dataDirectory, "base.sqlite");
74
81
  const resolvedPath = path.resolve(dbPath);
75
82
  const dir = path.dirname(resolvedPath);
76
83
  fs.mkdirSync(dir, { recursive: true, mode: 0o750 });