@spencer-kit/coder-studio 0.3.3 → 0.3.4

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/esm/bin.mjs CHANGED
@@ -885,11 +885,11 @@ function resolveSpawnArgv(argv, deps = {}) {
885
885
  return [];
886
886
  }
887
887
  const restArgs = argv.slice(1);
888
- const readFileSync8 = deps.readFileSync ?? ((file) => fs2.readFileSync(file, "utf8"));
889
- const existsSync11 = deps.existsSync ?? fs2.existsSync;
888
+ const readFileSync9 = deps.readFileSync ?? ((file) => fs2.readFileSync(file, "utf8"));
889
+ const existsSync12 = deps.existsSync ?? fs2.existsSync;
890
890
  const pathEnv = deps.pathEnv ?? process.env.Path ?? process.env.PATH ?? "";
891
891
  const pathExt = deps.pathExt ?? process.env.PATHEXT ?? DEFAULT_PATHEXT;
892
- const resolved = resolveExecutablePath(command, pathEnv, pathExt, existsSync11);
892
+ const resolved = resolveExecutablePath(command, pathEnv, pathExt, existsSync12);
893
893
  if (!resolved) {
894
894
  return [...argv];
895
895
  }
@@ -900,7 +900,7 @@ function resolveSpawnArgv(argv, deps = {}) {
900
900
  if (ext === ".cmd" || ext === ".bat") {
901
901
  let content;
902
902
  try {
903
- content = readFileSync8(resolved);
903
+ content = readFileSync9(resolved);
904
904
  } catch {
905
905
  return ["cmd.exe", "/d", "/s", "/c", resolved, ...restArgs];
906
906
  }
@@ -938,17 +938,17 @@ function expandShimVars(value, dp0Dir) {
938
938
  function parsePathExt(pathExt) {
939
939
  return pathExt.split(";").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
940
940
  }
941
- function resolveExecutablePath(command, pathEnv, pathExt, existsSync11) {
941
+ function resolveExecutablePath(command, pathEnv, pathExt, existsSync12) {
942
942
  const hasExt = path2.win32.extname(command).length > 0;
943
943
  const extensions = parsePathExt(pathExt);
944
944
  if (path2.win32.isAbsolute(command)) {
945
- if (existsSync11(command)) {
945
+ if (existsSync12(command)) {
946
946
  return command;
947
947
  }
948
948
  if (!hasExt) {
949
949
  for (const ext of extensions) {
950
950
  const candidate = command + ext;
951
- if (existsSync11(candidate)) {
951
+ if (existsSync12(candidate)) {
952
952
  return candidate;
953
953
  }
954
954
  }
@@ -959,14 +959,14 @@ function resolveExecutablePath(command, pathEnv, pathExt, existsSync11) {
959
959
  for (const dir of dirs) {
960
960
  if (hasExt) {
961
961
  const candidate = path2.win32.join(dir, command);
962
- if (existsSync11(candidate)) {
962
+ if (existsSync12(candidate)) {
963
963
  return candidate;
964
964
  }
965
965
  continue;
966
966
  }
967
967
  for (const ext of extensions) {
968
968
  const candidate = path2.win32.join(dir, command + ext);
969
- if (existsSync11(candidate)) {
969
+ if (existsSync12(candidate)) {
970
970
  return candidate;
971
971
  }
972
972
  }
@@ -2129,6 +2129,157 @@ var init_command_check = __esm({
2129
2129
  }
2130
2130
  });
2131
2131
 
2132
+ // packages/server/src/provider-runtime/e2e-provider-mock.ts
2133
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2134
+ import { dirname as dirname3, join as join2 } from "node:path";
2135
+ function createE2EProviderMockOverrides(env = process.env) {
2136
+ const statePath = env.CODER_STUDIO_E2E_PROVIDER_STATE_PATH;
2137
+ if (!statePath) {
2138
+ return null;
2139
+ }
2140
+ const binDir = env.CODER_STUDIO_E2E_PROVIDER_BIN_DIR;
2141
+ const debugLogPath = env.CODER_STUDIO_E2E_PROVIDER_DEBUG_LOG_PATH;
2142
+ appendDebugLog(debugLogPath, `init statePath=${statePath} binDir=${binDir ?? ""}`);
2143
+ const commandExists = async (command) => {
2144
+ const state = readMockState(statePath);
2145
+ const override = state.commands?.[command];
2146
+ appendDebugLog(
2147
+ debugLogPath,
2148
+ `commandExists ${command} override=${String(override)} state=${JSON.stringify(state.commands ?? {})}`
2149
+ );
2150
+ if (typeof override === "boolean") {
2151
+ return override;
2152
+ }
2153
+ return checkCommandAvailable(command);
2154
+ };
2155
+ const runCommand2 = async (file, args, options) => {
2156
+ const providerId = getInstallProviderId(file, args);
2157
+ appendDebugLog(
2158
+ debugLogPath,
2159
+ `runCommand ${file} ${args.join(" ")} provider=${providerId ?? "none"}`
2160
+ );
2161
+ if (!providerId) {
2162
+ return runCommandAsString(file, args, options);
2163
+ }
2164
+ const state = readMockState(statePath);
2165
+ const behavior = state.installBehavior?.[providerId];
2166
+ appendDebugLog(
2167
+ debugLogPath,
2168
+ `behavior ${providerId} ${JSON.stringify(behavior)} state=${JSON.stringify(state)}`
2169
+ );
2170
+ if (!behavior) {
2171
+ return runCommandAsString(file, args, options);
2172
+ }
2173
+ if (behavior.result === "success") {
2174
+ writeMockState(statePath, (draft) => {
2175
+ draft.commands ??= {};
2176
+ draft.commands[providerId] = true;
2177
+ });
2178
+ if (binDir) {
2179
+ ensureProviderCommand(binDir, providerId);
2180
+ }
2181
+ appendDebugLog(debugLogPath, `install success ${providerId}`);
2182
+ return {
2183
+ stdout: `installed ${providerId}`,
2184
+ stderr: ""
2185
+ };
2186
+ }
2187
+ const message = behavior.message ?? (behavior.result === "permission_denied" ? "permission denied" : "command not found");
2188
+ throw Object.assign(new Error(message), {
2189
+ exitCode: 1,
2190
+ stdout: "",
2191
+ stderr: message
2192
+ });
2193
+ };
2194
+ return {
2195
+ commandExists,
2196
+ runCommand: runCommand2
2197
+ };
2198
+ }
2199
+ function getInstallProviderId(file, args) {
2200
+ if (file !== "npm" || args.length !== 3) {
2201
+ return null;
2202
+ }
2203
+ if (args[0] !== "install" || args[1] !== "-g") {
2204
+ return null;
2205
+ }
2206
+ const packageName = args[2];
2207
+ if (packageName === PROVIDER_INSTALL_PACKAGES.claude) {
2208
+ return "claude";
2209
+ }
2210
+ if (packageName === PROVIDER_INSTALL_PACKAGES.codex) {
2211
+ return "codex";
2212
+ }
2213
+ return null;
2214
+ }
2215
+ function readMockState(statePath) {
2216
+ if (!existsSync3(statePath)) {
2217
+ return {};
2218
+ }
2219
+ const raw = readFileSync3(statePath, "utf8");
2220
+ if (!raw.trim()) {
2221
+ return {};
2222
+ }
2223
+ try {
2224
+ return JSON.parse(raw);
2225
+ } catch (error) {
2226
+ throw new Error(
2227
+ `Invalid provider mock state at ${statePath}: ${error instanceof Error ? error.message : String(error)}`
2228
+ );
2229
+ }
2230
+ }
2231
+ function writeMockState(statePath, updater) {
2232
+ const nextState = readMockState(statePath);
2233
+ updater(nextState);
2234
+ mkdirSync2(dirname3(statePath), { recursive: true });
2235
+ writeFileSync2(statePath, JSON.stringify(nextState, null, 2));
2236
+ return nextState;
2237
+ }
2238
+ function ensureProviderCommand(binDir, providerId) {
2239
+ mkdirSync2(binDir, { recursive: true });
2240
+ const scriptPath = join2(binDir, providerId);
2241
+ writeFileSync2(scriptPath, PROVIDER_COMMAND_SCRIPTS[providerId], "utf8");
2242
+ chmodSync(scriptPath, 493);
2243
+ }
2244
+ function appendDebugLog(path10, line) {
2245
+ if (!path10) {
2246
+ return;
2247
+ }
2248
+ mkdirSync2(dirname3(path10), { recursive: true });
2249
+ writeFileSync2(path10, `${line}
2250
+ `, { flag: "a" });
2251
+ }
2252
+ var PROVIDER_INSTALL_PACKAGES, PROVIDER_COMMAND_SCRIPTS;
2253
+ var init_e2e_provider_mock = __esm({
2254
+ "packages/server/src/provider-runtime/e2e-provider-mock.ts"() {
2255
+ "use strict";
2256
+ init_command_check();
2257
+ init_command_runner();
2258
+ PROVIDER_INSTALL_PACKAGES = {
2259
+ claude: "@anthropic-ai/claude-code",
2260
+ codex: "@openai/codex"
2261
+ };
2262
+ PROVIDER_COMMAND_SCRIPTS = {
2263
+ claude: `#!/usr/bin/env bash
2264
+ set -euo pipefail
2265
+ trap 'exit 0' TERM INT
2266
+ printf 'Mock Claude ready\\n'
2267
+ while true; do
2268
+ sleep 1
2269
+ done
2270
+ `,
2271
+ codex: `#!/usr/bin/env bash
2272
+ set -euo pipefail
2273
+ trap 'exit 0' TERM INT
2274
+ printf 'Session ID: abcdef-123456\\n> '
2275
+ while true; do
2276
+ sleep 1
2277
+ done
2278
+ `
2279
+ };
2280
+ }
2281
+ });
2282
+
2132
2283
  // packages/server/src/provider-runtime/install-manager.ts
2133
2284
  import { randomUUID as randomUUID2 } from "node:crypto";
2134
2285
  function getErrorDetails(error) {
@@ -3611,8 +3762,8 @@ var init_database = __esm({
3611
3762
 
3612
3763
  // packages/server/src/storage/db.ts
3613
3764
  import { DatabaseSync } from "node:sqlite";
3614
- import { readFileSync as readFileSync3 } from "fs";
3615
- import { join as join2 } from "path";
3765
+ import { readFileSync as readFileSync4 } from "fs";
3766
+ import { join as join3 } from "path";
3616
3767
  function normalizeSql(sql) {
3617
3768
  return (sql ?? "").replace(/\s+/g, " ").trim();
3618
3769
  }
@@ -3759,8 +3910,8 @@ var init_db = __esm({
3759
3910
  "packages/server/src/storage/db.ts"() {
3760
3911
  "use strict";
3761
3912
  init_database();
3762
- SCHEMA_PATH = join2(import.meta.dirname, "migrations", "001_init.sql");
3763
- SCHEMA_SQL = readFileSync3(SCHEMA_PATH, "utf-8");
3913
+ SCHEMA_PATH = join3(import.meta.dirname, "migrations", "001_init.sql");
3914
+ SCHEMA_SQL = readFileSync4(SCHEMA_PATH, "utf-8");
3764
3915
  LEGACY_TABLES = ["hook_registrations", "_migrations"];
3765
3916
  LEGACY_SESSION_COLUMNS = ["resume_id", "transcript_path"];
3766
3917
  EXPECTED_SCHEMA_ENTRIES = buildExpectedSchemaEntries();
@@ -4692,6 +4843,7 @@ function parseFetchUpdatedRefs(stderr) {
4692
4843
  }
4693
4844
  async function runGitCheckout(cwd, ref, options) {
4694
4845
  const args = ["checkout"];
4846
+ const formatCheckoutError = (error, fallbackMessage) => error instanceof GitError ? error.stderr.trim() || error.message || fallbackMessage : fallbackMessage;
4695
4847
  let isRemoteRef = false;
4696
4848
  try {
4697
4849
  const { stdout: remoteList } = await runGit(cwd, ["remote"]);
@@ -4703,15 +4855,22 @@ async function runGitCheckout(cwd, ref, options) {
4703
4855
  if (isRemoteRef && !options?.createBranch) {
4704
4856
  const remoteSeparatorIndex = ref.indexOf("/");
4705
4857
  const branchName = remoteSeparatorIndex >= 0 ? ref.slice(remoteSeparatorIndex + 1) : ref;
4706
- args.push("-b", branchName, ref);
4858
+ try {
4859
+ await runGit(cwd, ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`]);
4860
+ const { stdout, stderr } = await runGit(cwd, ["checkout", branchName]);
4861
+ const message = stdout || stderr || `Checkout to ${branchName} completed`;
4862
+ return { success: true, message, branch: branchName };
4863
+ } catch {
4864
+ args.push("-b", branchName, ref);
4865
+ }
4707
4866
  try {
4708
4867
  const { stdout, stderr } = await runGit(cwd, args);
4709
4868
  const message = stdout || stderr || `Checkout to ${ref} completed`;
4710
4869
  return { success: true, message, branch: branchName };
4711
- } catch {
4870
+ } catch (error) {
4712
4871
  return {
4713
4872
  success: false,
4714
- message: `Failed to checkout remote branch '${ref}'`
4873
+ message: formatCheckoutError(error, `Failed to checkout remote branch '${ref}'`)
4715
4874
  };
4716
4875
  }
4717
4876
  } else {
@@ -4725,10 +4884,10 @@ async function runGitCheckout(cwd, ref, options) {
4725
4884
  const branch = branchMatch?.[1] ?? ref;
4726
4885
  const message = stdout || stderr || `Checkout to ${ref} completed`;
4727
4886
  return { success: true, message, branch };
4728
- } catch {
4887
+ } catch (error) {
4729
4888
  return {
4730
4889
  success: false,
4731
- message: `Failed to checkout '${ref}'`
4890
+ message: formatCheckoutError(error, `Failed to checkout '${ref}'`)
4732
4891
  };
4733
4892
  }
4734
4893
  }
@@ -4743,23 +4902,42 @@ async function runGitCreateBranch(cwd, branchName, options) {
4743
4902
  }
4744
4903
  async function runGitListBranches(cwd) {
4745
4904
  const { stdout: localOutput } = await runGit(cwd, ["branch", "--list"]);
4905
+ const { stdout: localVerboseOutput } = await runGit(cwd, ["branch", "--list", "-vv"]);
4746
4906
  const { stdout: remoteOutput } = await runGit(cwd, ["branch", "-r"]);
4747
4907
  const branches = [];
4748
4908
  let current = "";
4909
+ const linkedWorktreePathsByBranch = /* @__PURE__ */ new Map();
4910
+ const localVerboseLines = localVerboseOutput.split("\n").filter((line) => line.trim());
4911
+ for (const line of localVerboseLines) {
4912
+ const normalizedLine = line.replace(/^[*+ ]\s+/, "");
4913
+ const branchMatch = normalizedLine.match(/^([^\s]+)\s+/);
4914
+ const worktreeMatch = line.match(/\((.+?)\)\s/);
4915
+ if (!branchMatch?.[1] || !worktreeMatch?.[1]) {
4916
+ continue;
4917
+ }
4918
+ const worktreePath = worktreeMatch[1];
4919
+ if (worktreePath.startsWith("/") || worktreePath.startsWith("~")) {
4920
+ linkedWorktreePathsByBranch.set(branchMatch[1], worktreePath);
4921
+ }
4922
+ }
4749
4923
  const localLines = localOutput.split("\n").filter((line) => line.trim());
4750
4924
  for (const line of localLines) {
4751
4925
  const isCurrent = line.startsWith("*");
4752
- const name = line.replace(/^\*?\s+/, "").trim();
4926
+ const name = line.replace(/^[*+ ]\s+/, "").trim();
4753
4927
  if (name.startsWith("(HEAD detached")) {
4754
4928
  if (isCurrent) {
4755
4929
  current = "";
4756
4930
  }
4757
4931
  continue;
4758
4932
  }
4933
+ if (linkedWorktreePathsByBranch.has(name) && !isCurrent) {
4934
+ continue;
4935
+ }
4759
4936
  branches.push({
4760
4937
  name,
4761
4938
  isRemote: false,
4762
- isCurrent
4939
+ isCurrent,
4940
+ linkedWorktreePath: linkedWorktreePathsByBranch.get(name)
4763
4941
  });
4764
4942
  if (isCurrent) {
4765
4943
  current = name;
@@ -5208,7 +5386,7 @@ var init_context_builder = __esm({
5208
5386
  });
5209
5387
 
5210
5388
  // packages/server/src/terminal/pty-host.ts
5211
- import { chmodSync, existsSync as existsSync3, statSync } from "node:fs";
5389
+ import { chmodSync as chmodSync2, existsSync as existsSync4, statSync } from "node:fs";
5212
5390
  import { createRequire } from "node:module";
5213
5391
  import path6 from "node:path";
5214
5392
  function ensureNodePtySpawnHelperExecutable(deps = {}) {
@@ -5218,9 +5396,9 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
5218
5396
  }
5219
5397
  const arch = deps.arch ?? process.arch;
5220
5398
  const resolve4 = deps.resolve ?? ((id) => require2.resolve(id));
5221
- const fileExists = deps.existsSync ?? existsSync3;
5399
+ const fileExists = deps.existsSync ?? existsSync4;
5222
5400
  const stat7 = deps.statSync ?? statSync;
5223
- const chmod = deps.chmodSync ?? chmodSync;
5401
+ const chmod = deps.chmodSync ?? chmodSync2;
5224
5402
  let packageJsonPath;
5225
5403
  try {
5226
5404
  packageJsonPath = resolve4(NODE_PTY_PKG);
@@ -7290,9 +7468,9 @@ var init_validator = __esm({
7290
7468
  });
7291
7469
 
7292
7470
  // packages/server/src/fs/gitignore.ts
7293
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
7471
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
7294
7472
  import ignore from "ignore";
7295
- import { join as join3, relative as relative3 } from "path";
7473
+ import { join as join4, relative as relative3 } from "path";
7296
7474
  function normalizePath(path10) {
7297
7475
  return path10.replace(/\\/g, "/");
7298
7476
  }
@@ -7312,26 +7490,26 @@ function isIgnoredByGitignore(ig, path10) {
7312
7490
  return ig.ignores(path10) || ig.ignores(`${path10}/`);
7313
7491
  }
7314
7492
  function createGitignoreFilter(rootPath, dirPath) {
7315
- const gitignorePath = join3(rootPath, ".gitignore");
7316
- if (!existsSync4(gitignorePath)) {
7493
+ const gitignorePath = join4(rootPath, ".gitignore");
7494
+ if (!existsSync5(gitignorePath)) {
7317
7495
  return (name) => !isDefaultTreeIgnored(name);
7318
7496
  }
7319
- const gitignoreContent = readFileSync4(gitignorePath, "utf-8");
7497
+ const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
7320
7498
  const ig = ignore().add(gitignoreContent);
7321
7499
  return (name) => {
7322
7500
  if (isAlwaysTreeIgnored(name)) {
7323
7501
  return false;
7324
7502
  }
7325
- const relativePath = relativeToRoot(rootPath, join3(dirPath, name));
7503
+ const relativePath = relativeToRoot(rootPath, join4(dirPath, name));
7326
7504
  return !isIgnoredByGitignore(ig, relativePath);
7327
7505
  };
7328
7506
  }
7329
7507
  function createWatcherIgnoreFilter(rootPath) {
7330
- const gitignorePath = join3(rootPath, ".gitignore");
7331
- if (!existsSync4(gitignorePath)) {
7508
+ const gitignorePath = join4(rootPath, ".gitignore");
7509
+ if (!existsSync5(gitignorePath)) {
7332
7510
  return (path10) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path10)));
7333
7511
  }
7334
- const gitignoreContent = readFileSync4(gitignorePath, "utf-8");
7512
+ const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
7335
7513
  const ig = ignore().add(gitignoreContent);
7336
7514
  return (path10) => {
7337
7515
  const normalizedPath = normalizePath(path10);
@@ -7458,6 +7636,11 @@ var init_manager4 = __esm({
7458
7636
  new WorkspaceWatcher(workspaceId, rootPath, this.deps.broadcaster)
7459
7637
  );
7460
7638
  }
7639
+ hydrateWatchers() {
7640
+ for (const workspace of this.list()) {
7641
+ this.startWatcher(workspace.id, workspace.path);
7642
+ }
7643
+ }
7461
7644
  updateUiState(workspaceId, uiState) {
7462
7645
  const workspace = this.get(workspaceId);
7463
7646
  if (!workspace) {
@@ -8933,7 +9116,7 @@ var init_hub = __esm({
8933
9116
  // packages/server/src/commands/workspace.ts
8934
9117
  import { readdir as readdir2 } from "node:fs/promises";
8935
9118
  import { homedir as homedir2 } from "node:os";
8936
- import { join as join4 } from "node:path";
9119
+ import { join as join5 } from "node:path";
8937
9120
  import { z as z6 } from "zod";
8938
9121
  var init_workspace = __esm({
8939
9122
  "packages/server/src/commands/workspace.ts"() {
@@ -8952,11 +9135,11 @@ var init_workspace = __esm({
8952
9135
  const entries = await readdir2(basePath, { withFileTypes: true });
8953
9136
  const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
8954
9137
  name: entry.name,
8955
- path: join4(basePath, entry.name)
9138
+ path: join5(basePath, entry.name)
8956
9139
  })).sort((a, b) => a.name.localeCompare(b.name));
8957
9140
  return {
8958
9141
  currentPath: basePath,
8959
- parentPath: basePath !== "/" ? join4(basePath, "..") : null,
9142
+ parentPath: basePath !== "/" ? join5(basePath, "..") : null,
8960
9143
  directories
8961
9144
  };
8962
9145
  }
@@ -9229,9 +9412,9 @@ var init_session = __esm({
9229
9412
 
9230
9413
  // packages/server/src/fs/tree.ts
9231
9414
  import { readdir as readdir3, stat as stat6 } from "fs/promises";
9232
- import { join as join5, relative as relative4 } from "path";
9415
+ import { join as join6, relative as relative4 } from "path";
9233
9416
  async function readTree(rootPath, subdir) {
9234
- const targetPath = subdir ? join5(rootPath, subdir) : rootPath;
9417
+ const targetPath = subdir ? join6(rootPath, subdir) : rootPath;
9235
9418
  const filter = createGitignoreFilter(rootPath, targetPath);
9236
9419
  const entries = await readdir3(targetPath, { withFileTypes: true });
9237
9420
  const nodes = [];
@@ -9239,7 +9422,7 @@ async function readTree(rootPath, subdir) {
9239
9422
  if (!filter(entry.name)) {
9240
9423
  continue;
9241
9424
  }
9242
- const fullPath = join5(targetPath, entry.name);
9425
+ const fullPath = join6(targetPath, entry.name);
9243
9426
  const relPath = relative4(rootPath, fullPath);
9244
9427
  if (entry.isDirectory()) {
9245
9428
  nodes.push({
@@ -9283,7 +9466,7 @@ async function searchFiles(rootPath, query, limit = 10) {
9283
9466
  const filteredEntries = entries.filter((entry) => filter(entry.name));
9284
9467
  filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
9285
9468
  for (const entry of filteredEntries) {
9286
- const fullPath = join5(dirPath, entry.name);
9469
+ const fullPath = join6(dirPath, entry.name);
9287
9470
  const relPath = relative4(rootPath, fullPath);
9288
9471
  if (entry.isDirectory()) {
9289
9472
  await walk(fullPath);
@@ -9895,37 +10078,37 @@ var init_git2 = __esm({
9895
10078
  });
9896
10079
 
9897
10080
  // packages/server/src/config/config-io.ts
9898
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
10081
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
9899
10082
  import { homedir as homedir3 } from "node:os";
9900
- import { basename as basename2, dirname as dirname3, join as join6 } from "node:path";
10083
+ import { basename as basename2, dirname as dirname4, join as join7 } from "node:path";
9901
10084
  function resolveConfigPath(configType) {
9902
10085
  if (configType === "codex") {
9903
10086
  const testHome = process.env.CODER_STUDIO_CODEX_HOME;
9904
10087
  if (testHome && testHome.trim()) {
9905
- return join6(testHome, "config.toml");
10088
+ return join7(testHome, "config.toml");
9906
10089
  }
9907
10090
  const codexHome = process.env.CODEX_HOME;
9908
10091
  if (codexHome && codexHome.trim()) {
9909
- return join6(codexHome, "config.toml");
10092
+ return join7(codexHome, "config.toml");
9910
10093
  }
9911
- return join6(homedir3(), ".codex", "config.toml");
10094
+ return join7(homedir3(), ".codex", "config.toml");
9912
10095
  }
9913
10096
  if (configType === "claude") {
9914
10097
  const testHome = process.env.CODER_STUDIO_CLAUDE_HOME;
9915
10098
  if (testHome && testHome.trim()) {
9916
- return join6(testHome, "settings.json");
10099
+ return join7(testHome, "settings.json");
9917
10100
  }
9918
- return join6(homedir3(), ".claude", "settings.json");
10101
+ return join7(homedir3(), ".claude", "settings.json");
9919
10102
  }
9920
10103
  throw new Error(`Unknown config type: ${configType}`);
9921
10104
  }
9922
10105
  function readConfigFile(configType) {
9923
10106
  const configPath = resolveConfigPath(configType);
9924
- if (!existsSync5(configPath)) {
10107
+ if (!existsSync6(configPath)) {
9925
10108
  return { configPath, content: "", exists: false };
9926
10109
  }
9927
10110
  try {
9928
- const content = readFileSync5(configPath, "utf-8");
10111
+ const content = readFileSync6(configPath, "utf-8");
9929
10112
  return { configPath, content, exists: true };
9930
10113
  } catch {
9931
10114
  return { configPath, content: "", exists: false };
@@ -9934,16 +10117,16 @@ function readConfigFile(configType) {
9934
10117
  function writeConfigFile(configType, content) {
9935
10118
  try {
9936
10119
  const configPath = resolveConfigPath(configType);
9937
- const parentDir = dirname3(configPath);
9938
- if (!existsSync5(parentDir)) {
9939
- mkdirSync2(parentDir, { recursive: true });
10120
+ const parentDir = dirname4(configPath);
10121
+ if (!existsSync6(parentDir)) {
10122
+ mkdirSync3(parentDir, { recursive: true });
9940
10123
  }
9941
10124
  let backupPath = null;
9942
- if (existsSync5(configPath)) {
10125
+ if (existsSync6(configPath)) {
9943
10126
  backupPath = createBackup(configPath);
9944
10127
  }
9945
10128
  const tempPath = `${configPath}.tmp`;
9946
- writeFileSync2(tempPath, content, "utf-8");
10129
+ writeFileSync3(tempPath, content, "utf-8");
9947
10130
  renameSync(tempPath, configPath);
9948
10131
  return { success: true, backupPath };
9949
10132
  } catch (error) {
@@ -9955,13 +10138,13 @@ function writeConfigFile(configType, content) {
9955
10138
  }
9956
10139
  }
9957
10140
  function createBackup(filePath) {
9958
- const original = readFileSync5(filePath, "utf-8");
10141
+ const original = readFileSync6(filePath, "utf-8");
9959
10142
  const ext = filePath.split(".").pop() ?? "";
9960
10143
  const base = basename2(filePath, `.${ext}`);
9961
- const dir = dirname3(filePath);
10144
+ const dir = dirname4(filePath);
9962
10145
  const ts = formatTimestamp(/* @__PURE__ */ new Date());
9963
- const backupPath = join6(dir, `${base}.bak.${ts}.${ext}`);
9964
- writeFileSync2(backupPath, original, "utf-8");
10146
+ const backupPath = join7(dir, `${base}.bak.${ts}.${ext}`);
10147
+ writeFileSync3(backupPath, original, "utf-8");
9965
10148
  return backupPath;
9966
10149
  }
9967
10150
  function formatTimestamp(d) {
@@ -10665,6 +10848,7 @@ async function createServer(configOverrides) {
10665
10848
  (err) => console.warn("[uploads] cascade cleanup failed", { wsId: workspaceId, err })
10666
10849
  )
10667
10850
  });
10851
+ workspaceMgr.hydrateWatchers();
10668
10852
  const authSessionRepo = new AuthSessionRepo(db);
10669
10853
  const authLoginBlockRepo = new AuthLoginBlockRepo(db);
10670
10854
  const app = await buildFastifyApp({
@@ -10704,10 +10888,13 @@ async function createServer(configOverrides) {
10704
10888
  });
10705
10889
  await sessionMgr.hydrate();
10706
10890
  await supervisorMgr.hydrate();
10707
- const providerRuntimeDeps = {};
10891
+ const providerMockOverrides = createE2EProviderMockOverrides();
10892
+ const providerRuntimeDeps = providerMockOverrides ? {
10893
+ commandExists: providerMockOverrides.commandExists
10894
+ } : {};
10708
10895
  const providerInstallMgr = new ProviderInstallManager(providerRegistry, {
10709
10896
  ...providerRuntimeDeps,
10710
- runCommand: runCommandAsString
10897
+ runCommand: providerMockOverrides?.runCommand ?? runCommandAsString
10711
10898
  });
10712
10899
  commandContext = {
10713
10900
  workspaceMgr,
@@ -10877,6 +11064,7 @@ var init_server = __esm({
10877
11064
  init_config();
10878
11065
  init_auto_fetch();
10879
11066
  init_command_runner();
11067
+ init_e2e_provider_mock();
10880
11068
  init_install_manager();
10881
11069
  init_manager();
10882
11070
  init_db();
@@ -11183,20 +11371,20 @@ var init_src4 = __esm({
11183
11371
  });
11184
11372
 
11185
11373
  // packages/cli/src/cli.ts
11186
- import { existsSync as existsSync10 } from "fs";
11187
- import { dirname as dirname5, join as join9 } from "path";
11374
+ import { existsSync as existsSync11 } from "fs";
11375
+ import { dirname as dirname6, join as join10 } from "path";
11188
11376
  import { fileURLToPath as fileURLToPath3 } from "url";
11189
11377
 
11190
11378
  // packages/cli/src/auth-control.ts
11191
11379
  await init_src4();
11192
11380
 
11193
11381
  // packages/cli/src/config-store.ts
11194
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
11382
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
11195
11383
  import { homedir as homedir4 } from "os";
11196
- import { basename as basename3, join as join7 } from "path";
11384
+ import { basename as basename3, join as join8 } from "path";
11197
11385
  var DEFAULT_DB_FILE = "coder-studio.db";
11198
11386
  function getCliConfigPath() {
11199
- return join7(homedir4(), ".coder-studio", "config.json");
11387
+ return join8(homedir4(), ".coder-studio", "config.json");
11200
11388
  }
11201
11389
  function normalizeDataDir(input2) {
11202
11390
  if (input2.endsWith(".db")) {
@@ -11205,15 +11393,15 @@ function normalizeDataDir(input2) {
11205
11393
  if (basename3(input2).includes(".")) {
11206
11394
  return input2;
11207
11395
  }
11208
- return join7(input2, DEFAULT_DB_FILE);
11396
+ return join8(input2, DEFAULT_DB_FILE);
11209
11397
  }
11210
11398
  function readCliConfig() {
11211
11399
  const path10 = getCliConfigPath();
11212
- if (!existsSync6(path10)) {
11400
+ if (!existsSync7(path10)) {
11213
11401
  return null;
11214
11402
  }
11215
11403
  try {
11216
- const parsed = JSON.parse(readFileSync6(path10, "utf-8"));
11404
+ const parsed = JSON.parse(readFileSync7(path10, "utf-8"));
11217
11405
  if (parsed.host !== void 0 && typeof parsed.host !== "string" || parsed.port !== void 0 && typeof parsed.port !== "number" || parsed.dataDir !== void 0 && typeof parsed.dataDir !== "string" || parsed.password !== void 0 && typeof parsed.password !== "string") {
11218
11406
  return null;
11219
11407
  }
@@ -11224,17 +11412,17 @@ function readCliConfig() {
11224
11412
  }
11225
11413
  function writeCliConfig(config) {
11226
11414
  const path10 = getCliConfigPath();
11227
- const dir = join7(homedir4(), ".coder-studio");
11415
+ const dir = join8(homedir4(), ".coder-studio");
11228
11416
  const normalizedConfig = {
11229
11417
  ...config.host !== void 0 ? { host: config.host } : {},
11230
11418
  ...config.port !== void 0 && config.port > 0 ? { port: config.port } : {},
11231
11419
  ...config.dataDir !== void 0 ? { dataDir: normalizeDataDir(config.dataDir) } : {},
11232
11420
  ...config.password !== void 0 ? { password: config.password } : {}
11233
11421
  };
11234
- if (!existsSync6(dir)) {
11235
- mkdirSync3(dir, { recursive: true });
11422
+ if (!existsSync7(dir)) {
11423
+ mkdirSync4(dir, { recursive: true });
11236
11424
  }
11237
- writeFileSync3(path10, JSON.stringify(normalizedConfig, null, 2), "utf-8");
11425
+ writeFileSync4(path10, JSON.stringify(normalizedConfig, null, 2), "utf-8");
11238
11426
  }
11239
11427
 
11240
11428
  // packages/cli/src/auth-control.ts
@@ -11298,12 +11486,12 @@ async function openBrowser(url) {
11298
11486
  }
11299
11487
 
11300
11488
  // packages/cli/src/log-excerpt.ts
11301
- import { closeSync, existsSync as existsSync7, openSync, readSync, statSync as statSync2 } from "fs";
11489
+ import { closeSync, existsSync as existsSync8, openSync, readSync, statSync as statSync2 } from "fs";
11302
11490
  var DEFAULT_MAX_LINES = 40;
11303
11491
  var DEFAULT_MAX_CHARS = 4e3;
11304
11492
  var DEFAULT_MAX_BYTES = 16 * 1024;
11305
11493
  var getFileSize = (path10) => {
11306
- if (!existsSync7(path10)) {
11494
+ if (!existsSync8(path10)) {
11307
11495
  return 0;
11308
11496
  }
11309
11497
  try {
@@ -11318,7 +11506,7 @@ var readLogExcerpt = (path10, {
11318
11506
  maxLines = DEFAULT_MAX_LINES,
11319
11507
  maxChars = DEFAULT_MAX_CHARS
11320
11508
  } = {}) => {
11321
- if (!existsSync7(path10)) {
11509
+ if (!existsSync8(path10)) {
11322
11510
  return null;
11323
11511
  }
11324
11512
  const fileSize = getFileSize(path10);
@@ -11397,12 +11585,12 @@ function assertSupportedNodeVersion(version = process.versions.node) {
11397
11585
  }
11398
11586
 
11399
11587
  // packages/cli/src/package-manifest.ts
11400
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
11588
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
11401
11589
  function resolveCliPackageManifestUrl(importMetaUrl) {
11402
11590
  const manifestUrl = [
11403
11591
  new URL("../package.json", importMetaUrl),
11404
11592
  new URL("../../package.json", importMetaUrl)
11405
- ].find((candidate) => existsSync8(candidate));
11593
+ ].find((candidate) => existsSync9(candidate));
11406
11594
  if (!manifestUrl) {
11407
11595
  throw new Error("Unable to locate CLI package.json");
11408
11596
  }
@@ -11410,7 +11598,7 @@ function resolveCliPackageManifestUrl(importMetaUrl) {
11410
11598
  }
11411
11599
  function getCliPackageManifest(importMetaUrl) {
11412
11600
  return JSON.parse(
11413
- readFileSync7(resolveCliPackageManifestUrl(importMetaUrl), "utf-8")
11601
+ readFileSync8(resolveCliPackageManifestUrl(importMetaUrl), "utf-8")
11414
11602
  );
11415
11603
  }
11416
11604
  function getCliVersion(importMetaUrl) {
@@ -11619,9 +11807,9 @@ function parseArgs(argv) {
11619
11807
 
11620
11808
  // packages/cli/src/pm2-control.ts
11621
11809
  init_runtime();
11622
- import { mkdirSync as mkdirSync4 } from "fs";
11810
+ import { mkdirSync as mkdirSync5 } from "fs";
11623
11811
  import { homedir as homedir5 } from "os";
11624
- import { join as join8 } from "path";
11812
+ import { join as join9 } from "path";
11625
11813
  var MANAGED_SERVER_NAME = "coder-studio-server";
11626
11814
  var PM2_RESTART_DELAY_MS = 2e3;
11627
11815
  var PM2_MIN_UPTIME = "5s";
@@ -11812,11 +12000,11 @@ var deleteManagedServerInSession = async (pm2, {
11812
12000
  return true;
11813
12001
  };
11814
12002
  var ensureLogDirectory = () => {
11815
- mkdirSync4(join8(homedir5(), ".coder-studio", "logs"), { recursive: true });
12003
+ mkdirSync5(join9(homedir5(), ".coder-studio", "logs"), { recursive: true });
11816
12004
  };
11817
12005
  var getLogPaths = () => ({
11818
- outFile: join8(homedir5(), ".coder-studio", "logs", "server.out.log"),
11819
- errFile: join8(homedir5(), ".coder-studio", "logs", "server.err.log")
12006
+ outFile: join9(homedir5(), ".coder-studio", "logs", "server.out.log"),
12007
+ errFile: join9(homedir5(), ".coder-studio", "logs", "server.err.log")
11820
12008
  });
11821
12009
  var captureStartupLogOffsets = () => {
11822
12010
  const { outFile, errFile } = getLogPaths();
@@ -12002,17 +12190,17 @@ async function getServerStatus() {
12002
12190
  import { fileURLToPath as fileURLToPath2 } from "url";
12003
12191
 
12004
12192
  // packages/cli/src/embed.ts
12005
- import { existsSync as existsSync9 } from "fs";
12006
- import { dirname as dirname4, resolve as resolve3 } from "path";
12193
+ import { existsSync as existsSync10 } from "fs";
12194
+ import { dirname as dirname5, resolve as resolve3 } from "path";
12007
12195
  import { fileURLToPath } from "url";
12008
12196
  var __filename = fileURLToPath(import.meta.url);
12009
- var __dirname = dirname4(__filename);
12197
+ var __dirname = dirname5(__filename);
12010
12198
  var WEB_ASSETS_DIR = resolve3(__dirname, "../web");
12011
12199
  function getStaticAssetsDir() {
12012
12200
  return WEB_ASSETS_DIR;
12013
12201
  }
12014
12202
  function hasWebAssets() {
12015
- return existsSync9(WEB_ASSETS_DIR);
12203
+ return existsSync10(WEB_ASSETS_DIR);
12016
12204
  }
12017
12205
 
12018
12206
  // packages/cli/src/server-runner.ts
@@ -12214,13 +12402,13 @@ function formatAuthBlocks(blocks) {
12214
12402
  }
12215
12403
  function resolveManagedScriptPath() {
12216
12404
  const currentFile = fileURLToPath3(import.meta.url);
12217
- const currentDir = dirname5(currentFile);
12405
+ const currentDir = dirname6(currentFile);
12218
12406
  const candidates = [
12219
- join9(currentDir, "server-runner.js"),
12220
- join9(currentDir, "server-runner.mjs"),
12221
- join9(currentDir, "../src/server-runner.ts")
12407
+ join10(currentDir, "server-runner.js"),
12408
+ join10(currentDir, "server-runner.mjs"),
12409
+ join10(currentDir, "../src/server-runner.ts")
12222
12410
  ];
12223
- const scriptPath = candidates.find((candidate) => existsSync10(candidate));
12411
+ const scriptPath = candidates.find((candidate) => existsSync11(candidate));
12224
12412
  if (!scriptPath) {
12225
12413
  throw new Error("Unable to locate the managed server entry script");
12226
12414
  }