@spencer-kit/coder-studio 0.3.1 → 0.3.2

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
@@ -115,8 +115,8 @@ var init_plugin = __esm({
115
115
  init_web_ui_routing();
116
116
  init_login_protection();
117
117
  AUTH_COOKIE_NAME = "coder_studio_auth";
118
- isPublicPath = (path8) => {
119
- const pathname = getRequestPathname(path8);
118
+ isPublicPath = (path9) => {
119
+ const pathname = getRequestPathname(path9);
120
120
  return pathname === "/" || pathname === "/login" || pathname === "/healthz" || pathname === "/auth/status" || pathname === "/auth/login" || pathname === "/auth/logout" || pathname.startsWith("/@") || isPublicStaticPath(pathname);
121
121
  };
122
122
  parseCookies = (cookieHeader) => {
@@ -307,6 +307,25 @@ function parseLogLevel(value) {
307
307
  return void 0;
308
308
  }
309
309
  }
310
+ function resolveDefaultAppVersion() {
311
+ if (cachedAppVersion) {
312
+ return cachedAppVersion;
313
+ }
314
+ const packageJsonPath = [new URL("../../cli/package.json", import.meta.url)].find(
315
+ (candidate) => fs.existsSync(candidate)
316
+ );
317
+ if (!packageJsonPath) {
318
+ cachedAppVersion = "0.0.0";
319
+ return cachedAppVersion;
320
+ }
321
+ try {
322
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
323
+ cachedAppVersion = typeof pkg.version === "string" ? pkg.version : "0.0.0";
324
+ } catch {
325
+ cachedAppVersion = "0.0.0";
326
+ }
327
+ return cachedAppVersion;
328
+ }
310
329
  function resolveDbPath(explicit) {
311
330
  if (explicit) return explicit;
312
331
  if (process.env.NODE_ENV !== "production") {
@@ -345,6 +364,7 @@ function parseServerConfig(overrides) {
345
364
  uploadsDir,
346
365
  logLevel: overrides?.logLevel ?? parseLogLevel(process.env.LOG_LEVEL) ?? "info",
347
366
  webRoot: overrides?.webRoot,
367
+ appVersion: overrides?.appVersion ?? process.env.CODER_STUDIO_APP_VERSION ?? resolveDefaultAppVersion(),
348
368
  auth: overrides?.auth || {
349
369
  enabled: !noAuth && !!password,
350
370
  password
@@ -357,7 +377,7 @@ function ensureDataDir(config) {
357
377
  }
358
378
  fs.mkdirSync(path.dirname(config.dataDir), { recursive: true });
359
379
  }
360
- var cachedTestUploadsDir;
380
+ var cachedTestUploadsDir, cachedAppVersion;
361
381
  var init_config = __esm({
362
382
  "packages/server/src/config.ts"() {
363
383
  "use strict";
@@ -787,6 +807,190 @@ var init_src = __esm({
787
807
  }
788
808
  });
789
809
 
810
+ // packages/utils/src/direct-execution.ts
811
+ import { posix, resolve } from "node:path";
812
+ function isWindowsDrivePath(path9) {
813
+ return /^[A-Za-z]:\//.test(path9);
814
+ }
815
+ function normalizeComparablePath(path9) {
816
+ let normalized = path9.replace(/\\/g, "/");
817
+ if (/^\/[A-Za-z]:\//.test(normalized)) {
818
+ normalized = normalized.slice(1);
819
+ }
820
+ if (normalized.startsWith("//")) {
821
+ normalized = `//${posix.normalize(normalized.slice(2))}`;
822
+ } else {
823
+ normalized = posix.normalize(normalized);
824
+ }
825
+ if (isWindowsDrivePath(normalized) || normalized.startsWith("//")) {
826
+ normalized = normalized.toLowerCase();
827
+ }
828
+ return normalized;
829
+ }
830
+ function normalizeModuleUrlPath(moduleUrl) {
831
+ let url;
832
+ try {
833
+ url = new URL(moduleUrl);
834
+ } catch {
835
+ return null;
836
+ }
837
+ if (url.protocol !== "file:") {
838
+ return null;
839
+ }
840
+ const path9 = `${url.host ? `//${url.host}` : ""}${decodeURIComponent(url.pathname)}`;
841
+ return normalizeComparablePath(path9);
842
+ }
843
+ function normalizeArgvPath(argv1) {
844
+ const isAbsoluteWindowsPath = /^[A-Za-z]:[\\/]/.test(argv1) || /^\\\\/.test(argv1);
845
+ return normalizeComparablePath(isAbsoluteWindowsPath ? argv1 : resolve(argv1));
846
+ }
847
+ function isDirectExecution(moduleUrl, argv1 = process.argv[1]) {
848
+ if (argv1 === void 0) {
849
+ return false;
850
+ }
851
+ const modulePath = normalizeModuleUrlPath(moduleUrl);
852
+ if (modulePath === null) {
853
+ return false;
854
+ }
855
+ return modulePath === normalizeArgvPath(argv1);
856
+ }
857
+ var init_direct_execution = __esm({
858
+ "packages/utils/src/direct-execution.ts"() {
859
+ "use strict";
860
+ }
861
+ });
862
+
863
+ // packages/utils/src/windows-shim.ts
864
+ function shouldUseShellForCommand(command, platform = process.platform) {
865
+ return platform === "win32" && WINDOWS_CMD_SHIMS.has(command.toLowerCase());
866
+ }
867
+ var WINDOWS_CMD_SHIMS;
868
+ var init_windows_shim = __esm({
869
+ "packages/utils/src/windows-shim.ts"() {
870
+ "use strict";
871
+ WINDOWS_CMD_SHIMS = /* @__PURE__ */ new Set(["pnpm", "npm", "npx"]);
872
+ }
873
+ });
874
+
875
+ // packages/utils/src/windows-shim-resolver.ts
876
+ import * as fs2 from "node:fs";
877
+ import * as path2 from "node:path";
878
+ function resolveSpawnArgv(argv, deps = {}) {
879
+ const platform = deps.platform ?? process.platform;
880
+ if (platform !== "win32") {
881
+ return [...argv];
882
+ }
883
+ const command = argv[0];
884
+ if (command === void 0) {
885
+ return [];
886
+ }
887
+ const restArgs = argv.slice(1);
888
+ const readFileSync9 = deps.readFileSync ?? ((file) => fs2.readFileSync(file, "utf8"));
889
+ const existsSync12 = deps.existsSync ?? fs2.existsSync;
890
+ const pathEnv = deps.pathEnv ?? process.env.Path ?? process.env.PATH ?? "";
891
+ const pathExt = deps.pathExt ?? process.env.PATHEXT ?? DEFAULT_PATHEXT;
892
+ const resolved = resolveExecutablePath(command, pathEnv, pathExt, existsSync12);
893
+ if (!resolved) {
894
+ return [...argv];
895
+ }
896
+ const ext = path2.win32.extname(resolved).toLowerCase();
897
+ if (ext === ".exe" || ext === ".com") {
898
+ return [resolved, ...restArgs];
899
+ }
900
+ if (ext === ".cmd" || ext === ".bat") {
901
+ let content;
902
+ try {
903
+ content = readFileSync9(resolved);
904
+ } catch {
905
+ return ["cmd.exe", "/d", "/s", "/c", resolved, ...restArgs];
906
+ }
907
+ const parsed = parseCmdShim(resolved, content);
908
+ if (parsed) {
909
+ return [parsed.node, parsed.entry, ...restArgs];
910
+ }
911
+ return ["cmd.exe", "/d", "/s", "/c", resolved, ...restArgs];
912
+ }
913
+ return [...argv];
914
+ }
915
+ function parseCmdShim(shimPath, content) {
916
+ const dp0Dir = path2.win32.dirname(shimPath);
917
+ const entryMatch = content.match(/"([^"\r\n]+\.js)"\s+%\*/i);
918
+ const entryRaw = entryMatch?.[1];
919
+ if (!entryRaw) {
920
+ return null;
921
+ }
922
+ const entry = expandShimVars(entryRaw, dp0Dir);
923
+ const nodeMatch = content.match(/"([^"\r\n]*node\.exe)"/i) ?? content.match(/"([^"\r\n]*node)"/i);
924
+ const nodeRaw = nodeMatch?.[1];
925
+ const node = nodeRaw ? expandShimVars(nodeRaw, dp0Dir) : "node";
926
+ return { node, entry };
927
+ }
928
+ function expandShimVars(value, dp0Dir) {
929
+ const dp0WithSlash = dp0Dir.endsWith("\\") ? dp0Dir : `${dp0Dir}\\`;
930
+ let expanded = value;
931
+ expanded = expanded.replace(/%~dp0/gi, dp0WithSlash);
932
+ expanded = expanded.replace(/%dp0%/gi, dp0WithSlash);
933
+ if (path2.win32.isAbsolute(expanded)) {
934
+ return path2.win32.normalize(expanded);
935
+ }
936
+ return path2.win32.resolve(dp0Dir, expanded);
937
+ }
938
+ function parsePathExt(pathExt) {
939
+ return pathExt.split(";").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
940
+ }
941
+ function resolveExecutablePath(command, pathEnv, pathExt, existsSync12) {
942
+ const hasExt = path2.win32.extname(command).length > 0;
943
+ const extensions = parsePathExt(pathExt);
944
+ if (path2.win32.isAbsolute(command)) {
945
+ if (existsSync12(command)) {
946
+ return command;
947
+ }
948
+ if (!hasExt) {
949
+ for (const ext of extensions) {
950
+ const candidate = command + ext;
951
+ if (existsSync12(candidate)) {
952
+ return candidate;
953
+ }
954
+ }
955
+ }
956
+ return null;
957
+ }
958
+ const dirs = pathEnv.split(";").map((dir) => dir.trim()).filter((dir) => dir.length > 0);
959
+ for (const dir of dirs) {
960
+ if (hasExt) {
961
+ const candidate = path2.win32.join(dir, command);
962
+ if (existsSync12(candidate)) {
963
+ return candidate;
964
+ }
965
+ continue;
966
+ }
967
+ for (const ext of extensions) {
968
+ const candidate = path2.win32.join(dir, command + ext);
969
+ if (existsSync12(candidate)) {
970
+ return candidate;
971
+ }
972
+ }
973
+ }
974
+ return null;
975
+ }
976
+ var DEFAULT_PATHEXT;
977
+ var init_windows_shim_resolver = __esm({
978
+ "packages/utils/src/windows-shim-resolver.ts"() {
979
+ "use strict";
980
+ DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC";
981
+ }
982
+ });
983
+
984
+ // packages/utils/src/index.ts
985
+ var init_src2 = __esm({
986
+ "packages/utils/src/index.ts"() {
987
+ "use strict";
988
+ init_direct_execution();
989
+ init_windows_shim();
990
+ init_windows_shim_resolver();
991
+ }
992
+ });
993
+
790
994
  // packages/server/src/fs/image.ts
791
995
  import { extname } from "path";
792
996
  function getImageTypeInfo(filePath) {
@@ -813,10 +1017,10 @@ var init_image = __esm({
813
1017
  // packages/server/src/fs/file-io.ts
814
1018
  import { createHash } from "crypto";
815
1019
  import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir, rm, stat } from "fs/promises";
816
- import { dirname as dirname2, resolve } from "path";
817
- async function statSafe(path8) {
1020
+ import { dirname as dirname2, resolve as resolve2 } from "path";
1021
+ async function statSafe(path9) {
818
1022
  try {
819
- return await stat(path8);
1023
+ return await stat(path9);
820
1024
  } catch {
821
1025
  return null;
822
1026
  }
@@ -847,8 +1051,8 @@ async function deleteEntry(rootPath, relPath) {
847
1051
  await rm(abs, { recursive: true });
848
1052
  }
849
1053
  function resolveSafe(root, relPath) {
850
- const absRoot = resolve(root);
851
- const abs = resolve(absRoot, relPath);
1054
+ const absRoot = resolve2(root);
1055
+ const abs = resolve2(absRoot, relPath);
852
1056
  if (!abs.startsWith(absRoot + "/") && abs !== absRoot) {
853
1057
  throw { code: "path_escape", message: "Path escapes workspace root" };
854
1058
  }
@@ -986,7 +1190,7 @@ var init_constants = __esm({
986
1190
  // packages/server/src/uploads/paths.ts
987
1191
  import { randomUUID } from "node:crypto";
988
1192
  import { lstat, mkdir as mkdir2 } from "node:fs/promises";
989
- import path2 from "node:path";
1193
+ import path3 from "node:path";
990
1194
  function sanitizeOriginalName(input2) {
991
1195
  let sanitized = "";
992
1196
  for (const char of input2.trim()) {
@@ -1022,9 +1226,9 @@ async function assertDirectorySegmentSafe(segmentPath) {
1022
1226
  }
1023
1227
  }
1024
1228
  async function ensureSafeUploadDir(rootDir, targetDir) {
1025
- const resolvedRoot = path2.resolve(rootDir);
1026
- const resolvedTarget = path2.resolve(targetDir);
1027
- if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path2.sep}`)) {
1229
+ const resolvedRoot = path3.resolve(rootDir);
1230
+ const resolvedTarget = path3.resolve(targetDir);
1231
+ if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path3.sep}`)) {
1028
1232
  throw new Error(`target dir escaped uploads root: ${resolvedTarget}`);
1029
1233
  }
1030
1234
  try {
@@ -1037,13 +1241,13 @@ async function ensureSafeUploadDir(rootDir, targetDir) {
1037
1241
  await mkdir2(resolvedRoot, { recursive: true });
1038
1242
  await assertDirectorySegmentSafe(resolvedRoot);
1039
1243
  }
1040
- const relative3 = path2.relative(resolvedRoot, resolvedTarget);
1244
+ const relative3 = path3.relative(resolvedRoot, resolvedTarget);
1041
1245
  if (!relative3) {
1042
1246
  return;
1043
1247
  }
1044
1248
  let current = resolvedRoot;
1045
- for (const segment of relative3.split(path2.sep)) {
1046
- current = path2.join(current, segment);
1249
+ for (const segment of relative3.split(path3.sep)) {
1250
+ current = path3.join(current, segment);
1047
1251
  try {
1048
1252
  await assertDirectorySegmentSafe(current);
1049
1253
  continue;
@@ -1068,11 +1272,11 @@ function generateBucketPath(input2) {
1068
1272
  validateWorkspaceId(input2.workspaceId);
1069
1273
  const now = input2.now ?? /* @__PURE__ */ new Date();
1070
1274
  const dateStr = now.toISOString().slice(0, 10);
1071
- const dir = path2.join(input2.uploadsDir, input2.workspaceId, dateStr);
1275
+ const dir = path3.join(input2.uploadsDir, input2.workspaceId, dateStr);
1072
1276
  const sanitizedName = sanitizeOriginalName(input2.originalName);
1073
1277
  const uuid8 = randomUUID().replace(/-/g, "").slice(0, 8);
1074
- const absolutePath = path2.resolve(dir, `${uuid8}-${sanitizedName}`);
1075
- const uploadsRoot = `${path2.resolve(input2.uploadsDir)}${path2.sep}`;
1278
+ const absolutePath = path3.resolve(dir, `${uuid8}-${sanitizedName}`);
1279
+ const uploadsRoot = `${path3.resolve(input2.uploadsDir)}${path3.sep}`;
1076
1280
  if (!absolutePath.startsWith(uploadsRoot)) {
1077
1281
  throw new Error(`generated upload path escaped uploads root: ${absolutePath}`);
1078
1282
  }
@@ -1095,7 +1299,7 @@ var init_paths = __esm({
1095
1299
 
1096
1300
  // packages/server/src/uploads/cleanup.ts
1097
1301
  import { readdir, rm as rm2, rmdir, stat as stat3, unlink } from "node:fs/promises";
1098
- import path3 from "node:path";
1302
+ import path4 from "node:path";
1099
1303
  async function listFilesRecursive(root) {
1100
1304
  let entries;
1101
1305
  try {
@@ -1108,7 +1312,7 @@ async function listFilesRecursive(root) {
1108
1312
  }
1109
1313
  const files = [];
1110
1314
  for (const entry of entries) {
1111
- const childPath = path3.join(root, entry.name);
1315
+ const childPath = path4.join(root, entry.name);
1112
1316
  if (entry.isDirectory()) {
1113
1317
  files.push(...await listFilesRecursive(childPath));
1114
1318
  continue;
@@ -1139,7 +1343,7 @@ async function pruneEmptyDirectories(root) {
1139
1343
  if (!entry.isDirectory()) {
1140
1344
  continue;
1141
1345
  }
1142
- await pruneEmptyDirectories(path3.join(root, entry.name));
1346
+ await pruneEmptyDirectories(path4.join(root, entry.name));
1143
1347
  }
1144
1348
  const remainingEntries = await readdir(root).catch(() => []);
1145
1349
  if (remainingEntries.length === 0) {
@@ -1148,12 +1352,12 @@ async function pruneEmptyDirectories(root) {
1148
1352
  }
1149
1353
  async function deleteWorkspaceUploads(uploadsDir, workspaceId) {
1150
1354
  validateWorkspaceId(workspaceId);
1151
- const bucket = path3.join(uploadsDir, workspaceId);
1355
+ const bucket = path4.join(uploadsDir, workspaceId);
1152
1356
  await rm2(bucket, { recursive: true, force: true });
1153
1357
  }
1154
1358
  async function enforceBucketCap(uploadsDir, workspaceId, capBytes, logger) {
1155
1359
  validateWorkspaceId(workspaceId);
1156
- const bucket = path3.join(uploadsDir, workspaceId);
1360
+ const bucket = path4.join(uploadsDir, workspaceId);
1157
1361
  const files = await listFilesRecursive(bucket);
1158
1362
  const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
1159
1363
  if (totalBytes <= capBytes) {
@@ -1193,7 +1397,7 @@ async function runStartupGc(uploadsDir, logger) {
1193
1397
  if (!workspaceEntry.isDirectory()) {
1194
1398
  continue;
1195
1399
  }
1196
- const workspaceDir = path3.join(uploadsDir, workspaceEntry.name);
1400
+ const workspaceDir = path4.join(uploadsDir, workspaceEntry.name);
1197
1401
  const dateEntries = await readdir(workspaceDir, { withFileTypes: true }).catch(
1198
1402
  () => []
1199
1403
  );
@@ -1201,7 +1405,7 @@ async function runStartupGc(uploadsDir, logger) {
1201
1405
  if (!dateEntry.isDirectory()) {
1202
1406
  continue;
1203
1407
  }
1204
- const dateDir = path3.join(workspaceDir, dateEntry.name);
1408
+ const dateDir = path4.join(workspaceDir, dateEntry.name);
1205
1409
  const files = await listFilesRecursive(dateDir);
1206
1410
  for (const file of files) {
1207
1411
  if (file.mtimeMs >= cutoffMs) {
@@ -1592,7 +1796,7 @@ var init_app = __esm({
1592
1796
  });
1593
1797
 
1594
1798
  // packages/server/src/config/codex-config-audit.ts
1595
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
1799
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
1596
1800
  import { homedir as homedir2 } from "node:os";
1597
1801
  import { dirname as dirname3, join as join2 } from "node:path";
1598
1802
  function resolveCodexConfigPath() {
@@ -1603,15 +1807,15 @@ function resolveCodexConfigPath() {
1603
1807
  return join2(homedir2(), ".codex", "config.toml");
1604
1808
  }
1605
1809
  function auditCodexConfigToml(configPath) {
1606
- const path8 = configPath ?? resolveCodexConfigPath();
1607
- if (!existsSync2(path8)) {
1608
- return { configPath: path8, exists: false, findings: [] };
1810
+ const path9 = configPath ?? resolveCodexConfigPath();
1811
+ if (!existsSync3(path9)) {
1812
+ return { configPath: path9, exists: false, findings: [] };
1609
1813
  }
1610
1814
  let content;
1611
1815
  try {
1612
- content = readFileSync2(path8, "utf-8");
1816
+ content = readFileSync3(path9, "utf-8");
1613
1817
  } catch {
1614
- return { configPath: path8, exists: false, findings: [] };
1818
+ return { configPath: path9, exists: false, findings: [] };
1615
1819
  }
1616
1820
  const lines = content.split(/\r?\n/);
1617
1821
  const findings = [];
@@ -1619,13 +1823,13 @@ function auditCodexConfigToml(configPath) {
1619
1823
  if (notifyFinding) findings.push(notifyFinding);
1620
1824
  const codexHooksFinding = detectCodexHooksFlag(lines);
1621
1825
  if (codexHooksFinding) findings.push(codexHooksFinding);
1622
- return { configPath: path8, exists: true, findings };
1826
+ return { configPath: path9, exists: true, findings };
1623
1827
  }
1624
1828
  function cleanupCodexConfigToml(configPath, opts) {
1625
1829
  if (opts.removeIds.length === 0) {
1626
1830
  return { removed: [], backupPath: null, noop: true };
1627
1831
  }
1628
- if (!existsSync2(configPath)) {
1832
+ if (!existsSync3(configPath)) {
1629
1833
  return { removed: [], backupPath: null, noop: true };
1630
1834
  }
1631
1835
  const audit = auditCodexConfigToml(configPath);
@@ -1633,7 +1837,7 @@ function cleanupCodexConfigToml(configPath, opts) {
1633
1837
  if (selected.length === 0) {
1634
1838
  return { removed: [], backupPath: null, noop: true };
1635
1839
  }
1636
- const original = readFileSync2(configPath, "utf-8");
1840
+ const original = readFileSync3(configPath, "utf-8");
1637
1841
  const backupPath = writeBackup(configPath, original, opts.backupDir);
1638
1842
  const linesToDrop = /* @__PURE__ */ new Set();
1639
1843
  for (const finding of selected) {
@@ -1735,7 +1939,7 @@ function countBrackets(s) {
1735
1939
  }
1736
1940
  function writeBackup(configPath, original, backupDir) {
1737
1941
  const dir = backupDir ?? dirname3(configPath);
1738
- if (!existsSync2(dir)) {
1942
+ if (!existsSync3(dir)) {
1739
1943
  mkdirSync2(dir, { recursive: true });
1740
1944
  }
1741
1945
  const ts = formatTimestamp(/* @__PURE__ */ new Date());
@@ -1776,35 +1980,78 @@ var init_codex_config_audit = __esm({
1776
1980
  }
1777
1981
  });
1778
1982
 
1983
+ // packages/server/src/provider-runtime/command-runner.ts
1984
+ import { spawn } from "node:child_process";
1985
+ async function runCommandAsString(file, args, options) {
1986
+ return new Promise((resolve4, reject) => {
1987
+ const child = spawn(file, args, {
1988
+ shell: shouldUseShellForCommand(file, process.platform),
1989
+ windowsHide: options?.windowsHide ?? true
1990
+ });
1991
+ const stdoutChunks = [];
1992
+ const stderrChunks = [];
1993
+ child.stdout?.on("data", (chunk) => {
1994
+ stdoutChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1995
+ });
1996
+ child.stderr?.on("data", (chunk) => {
1997
+ stderrChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1998
+ });
1999
+ child.on("error", (error) => {
2000
+ reject(
2001
+ Object.assign(error, {
2002
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
2003
+ stderr: Buffer.concat(stderrChunks).toString("utf8")
2004
+ })
2005
+ );
2006
+ });
2007
+ child.on("close", (code) => {
2008
+ const stdout = Buffer.concat(stdoutChunks).toString("utf8");
2009
+ const stderr = Buffer.concat(stderrChunks).toString("utf8");
2010
+ if (code === 0) {
2011
+ resolve4({ stdout, stderr });
2012
+ return;
2013
+ }
2014
+ reject(
2015
+ Object.assign(new Error(`Command failed with exit code ${code ?? "unknown"}`), {
2016
+ exitCode: code ?? void 0,
2017
+ stdout,
2018
+ stderr
2019
+ })
2020
+ );
2021
+ });
2022
+ });
2023
+ }
2024
+ var init_command_runner = __esm({
2025
+ "packages/server/src/provider-runtime/command-runner.ts"() {
2026
+ "use strict";
2027
+ init_src2();
2028
+ }
2029
+ });
2030
+
1779
2031
  // packages/server/src/provider-runtime/command-check.ts
1780
- import { execFile as nodeExecFile } from "node:child_process";
1781
- import { promisify } from "node:util";
1782
2032
  function getCommandLookupExecutable(platform) {
1783
2033
  return platform === "win32" ? "where" : "which";
1784
2034
  }
1785
2035
  async function checkCommandAvailable(command, deps = {}) {
1786
2036
  const platform = deps.platform ?? process.platform;
1787
- const execFile2 = deps.execFile ?? ((file, args) => execFileAsync(file, args));
2037
+ const runCommand2 = deps.runCommand ?? runCommandAsString;
1788
2038
  const lookup = getCommandLookupExecutable(platform);
1789
2039
  try {
1790
- await execFile2(lookup, [command]);
1791
- return true;
2040
+ const { stdout } = await runCommand2(lookup, [command], { windowsHide: true });
2041
+ return stdout.trim().length > 0;
1792
2042
  } catch {
1793
2043
  return false;
1794
2044
  }
1795
2045
  }
1796
- var execFileAsync;
1797
2046
  var init_command_check = __esm({
1798
2047
  "packages/server/src/provider-runtime/command-check.ts"() {
1799
2048
  "use strict";
1800
- execFileAsync = promisify(nodeExecFile);
2049
+ init_command_runner();
1801
2050
  }
1802
2051
  });
1803
2052
 
1804
2053
  // packages/server/src/provider-runtime/install-manager.ts
1805
- import { execFile as nodeExecFile2 } from "node:child_process";
1806
2054
  import { randomUUID as randomUUID2 } from "node:crypto";
1807
- import { promisify as promisify2 } from "node:util";
1808
2055
  function getErrorDetails(error) {
1809
2056
  if (error instanceof Error) {
1810
2057
  const record = error;
@@ -1879,12 +2126,12 @@ function cloneFailure(failure) {
1879
2126
  }
1880
2127
  };
1881
2128
  }
1882
- var execFileAsync2, EXCERPT_LIMIT, ProviderInstallManager;
2129
+ var EXCERPT_LIMIT, ProviderInstallManager;
1883
2130
  var init_install_manager = __esm({
1884
2131
  "packages/server/src/provider-runtime/install-manager.ts"() {
1885
2132
  "use strict";
1886
2133
  init_command_check();
1887
- execFileAsync2 = promisify2(nodeExecFile2);
2134
+ init_command_runner();
1888
2135
  EXCERPT_LIMIT = 400;
1889
2136
  ProviderInstallManager = class {
1890
2137
  providers = /* @__PURE__ */ new Map();
@@ -2069,7 +2316,7 @@ var init_install_manager = __esm({
2069
2316
  };
2070
2317
  }
2071
2318
  async runPreparedJob(provider, job) {
2072
- const execFile2 = this.deps.execFile ?? ((file, args) => execFileAsync2(file, args));
2319
+ const runCommand2 = this.deps.runCommand ?? runCommandAsString;
2073
2320
  job.status = "running";
2074
2321
  this.jobs.set(job.jobId, job);
2075
2322
  for (const step of job.steps) {
@@ -2096,7 +2343,7 @@ var init_install_manager = __esm({
2096
2343
  return;
2097
2344
  }
2098
2345
  } else {
2099
- const result = await execFile2(step.command, step.args);
2346
+ const result = await runCommand2(step.command, step.args, { windowsHide: true });
2100
2347
  step.stdoutExcerpt = excerpt(result.stdout);
2101
2348
  step.stderrExcerpt = excerpt(result.stderr);
2102
2349
  }
@@ -2432,7 +2679,7 @@ var init_idle_heuristics2 = __esm({
2432
2679
  });
2433
2680
 
2434
2681
  // packages/core/src/index.ts
2435
- var init_src2 = __esm({
2682
+ var init_src3 = __esm({
2436
2683
  "packages/core/src/index.ts"() {
2437
2684
  "use strict";
2438
2685
  init_events();
@@ -2469,14 +2716,16 @@ var init_provider_config = __esm({
2469
2716
  SUPPORTED_PROVIDER_IDS = ["claude", "codex"];
2470
2717
  supportedProviderIds = new Set(SUPPORTED_PROVIDER_IDS);
2471
2718
  ProviderLaunchConfigInputSchema = z4.object({
2472
- additionalArgs: z4.array(z4.string()).optional()
2719
+ additionalArgs: z4.array(z4.string()).optional(),
2720
+ envVars: z4.record(z4.string(), z4.string()).optional()
2473
2721
  }).strict();
2474
2722
  ProviderSettingsSchema = z4.object({
2475
2723
  claude: ProviderLaunchConfigInputSchema.optional(),
2476
2724
  codex: ProviderLaunchConfigInputSchema.optional()
2477
2725
  }).strict();
2478
2726
  ProviderLaunchConfigSchema = z4.object({
2479
- additionalArgs: z4.array(z4.string()).default([])
2727
+ additionalArgs: z4.array(z4.string()).default([]),
2728
+ envVars: z4.record(z4.string(), z4.string()).optional()
2480
2729
  });
2481
2730
  }
2482
2731
  });
@@ -2742,7 +2991,7 @@ var NOOP_SESSION_LOGGER, SessionManager, ActiveSession;
2742
2991
  var init_manager = __esm({
2743
2992
  "packages/server/src/session/manager.ts"() {
2744
2993
  "use strict";
2745
- init_src2();
2994
+ init_src3();
2746
2995
  init_provider_config();
2747
2996
  init_session_repo();
2748
2997
  init_pty_state_detector();
@@ -3283,7 +3532,7 @@ var init_database = __esm({
3283
3532
 
3284
3533
  // packages/server/src/storage/db.ts
3285
3534
  import { DatabaseSync } from "node:sqlite";
3286
- import { readFileSync as readFileSync3 } from "fs";
3535
+ import { readFileSync as readFileSync4 } from "fs";
3287
3536
  import { join as join3 } from "path";
3288
3537
  function normalizeSql(sql) {
3289
3538
  return (sql ?? "").replace(/\s+/g, " ").trim();
@@ -3432,7 +3681,7 @@ var init_db = __esm({
3432
3681
  "use strict";
3433
3682
  init_database();
3434
3683
  SCHEMA_PATH = join3(import.meta.dirname, "migrations", "001_init.sql");
3435
- SCHEMA_SQL = readFileSync3(SCHEMA_PATH, "utf-8");
3684
+ SCHEMA_SQL = readFileSync4(SCHEMA_PATH, "utf-8");
3436
3685
  LEGACY_TABLES = ["hook_registrations", "_migrations"];
3437
3686
  LEGACY_SESSION_COLUMNS = ["resume_id", "transcript_path"];
3438
3687
  EXPECTED_SCHEMA_ENTRIES = buildExpectedSchemaEntries();
@@ -3982,11 +4231,11 @@ function parseOrdinaryChangedEntry(record, staged, modified, deleted) {
3982
4231
  if (!xy) {
3983
4232
  return;
3984
4233
  }
3985
- const path8 = parts.slice(8).join(" ");
3986
- if (!path8) {
4234
+ const path9 = parts.slice(8).join(" ");
4235
+ if (!path9) {
3987
4236
  return;
3988
4237
  }
3989
- pushChange({ path: path8 }, xy, staged, modified, deleted);
4238
+ pushChange({ path: path9 }, xy, staged, modified, deleted);
3990
4239
  }
3991
4240
  function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
3992
4241
  const parts = record.split(" ");
@@ -3998,12 +4247,12 @@ function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
3998
4247
  const pathAndMaybeOldPath = pathTokens.join(" ");
3999
4248
  const inlinePathParts = pathAndMaybeOldPath.split(" ");
4000
4249
  const fallbackPath = !oldPathRecord && inlinePathParts.length === 1 && pathTokens.length > 1 ? pathTokens.slice(0, -1).join(" ") : void 0;
4001
- const path8 = fallbackPath ?? inlinePathParts[0];
4002
- if (!path8) {
4250
+ const path9 = fallbackPath ?? inlinePathParts[0];
4251
+ if (!path9) {
4003
4252
  return;
4004
4253
  }
4005
4254
  const oldPath = (oldPathRecord && !oldPathRecord.startsWith("#") ? oldPathRecord : void 0) ?? inlinePathParts[1] ?? (pathTokens.length > 1 ? pathTokens[pathTokens.length - 1] : void 0);
4006
- pushChange({ path: path8, oldPath }, xy, staged, modified, deleted);
4255
+ pushChange({ path: path9, oldPath }, xy, staged, modified, deleted);
4007
4256
  }
4008
4257
  function pushChange(change, xy, staged, modified, deleted) {
4009
4258
  const indexStatus = xy[0];
@@ -4030,9 +4279,9 @@ var init_status_parser = __esm({
4030
4279
  import { execFile } from "child_process";
4031
4280
  import { mkdir as mkdir3, mkdtemp, rm as rm4, writeFile as writeFile3 } from "fs/promises";
4032
4281
  import os2 from "os";
4033
- import path4 from "path";
4282
+ import path5 from "path";
4034
4283
  async function runGit(cwd, args, options = {}) {
4035
- return new Promise((resolve3, reject) => {
4284
+ return new Promise((resolve4, reject) => {
4036
4285
  const gitArgs = [
4037
4286
  ...options.config?.flatMap(([key, value]) => ["-c", `${key}=${value}`]) ?? [],
4038
4287
  ...args
@@ -4052,13 +4301,14 @@ async function runGit(cwd, args, options = {}) {
4052
4301
  ...options.env
4053
4302
  },
4054
4303
  maxBuffer: 10 * 1024 * 1024,
4055
- timeout: options.timeoutMs
4304
+ timeout: options.timeoutMs,
4305
+ windowsHide: true
4056
4306
  },
4057
4307
  (err, stdout, stderr) => {
4058
4308
  if (err) {
4059
4309
  reject(new GitError(err.message, stderr));
4060
4310
  } else {
4061
- resolve3({ stdout, stderr });
4311
+ resolve4({ stdout, stderr });
4062
4312
  }
4063
4313
  }
4064
4314
  );
@@ -4107,12 +4357,12 @@ async function discardChanges(cwd, paths) {
4107
4357
  if (paths.length === 0) return;
4108
4358
  const trackedPaths = [];
4109
4359
  const untrackedPaths = [];
4110
- for (const path8 of paths) {
4360
+ for (const path9 of paths) {
4111
4361
  try {
4112
- await runGit(cwd, ["ls-files", "--error-unmatch", "--", path8]);
4113
- trackedPaths.push(path8);
4362
+ await runGit(cwd, ["ls-files", "--error-unmatch", "--", path9]);
4363
+ trackedPaths.push(path9);
4114
4364
  } catch {
4115
- untrackedPaths.push(path8);
4365
+ untrackedPaths.push(path9);
4116
4366
  }
4117
4367
  }
4118
4368
  if (trackedPaths.length > 0) {
@@ -4380,9 +4630,9 @@ async function prepareGitAuthExecution(auth, remoteMetadata) {
4380
4630
  }
4381
4631
  };
4382
4632
  }
4383
- const tempDir = await mkdtemp(path4.join(os2.tmpdir(), "coder-studio-git-auth-"));
4384
- const hooksDir = path4.join(tempDir, "hooks");
4385
- const askPassPath = path4.join(tempDir, "askpass.sh");
4633
+ const tempDir = await mkdtemp(path5.join(os2.tmpdir(), "coder-studio-git-auth-"));
4634
+ const hooksDir = path5.join(tempDir, "hooks");
4635
+ const askPassPath = path5.join(tempDir, "askpass.sh");
4386
4636
  await mkdir3(hooksDir, { recursive: true, mode: 448 });
4387
4637
  await writeFile3(
4388
4638
  askPassPath,
@@ -4736,31 +4986,31 @@ var init_context_builder = __esm({
4736
4986
  });
4737
4987
 
4738
4988
  // packages/server/src/terminal/pty-host.ts
4739
- import { chmodSync, existsSync as existsSync3, statSync } from "node:fs";
4989
+ import { chmodSync, existsSync as existsSync4, statSync } from "node:fs";
4740
4990
  import { createRequire } from "node:module";
4741
- import path5 from "node:path";
4991
+ import path6 from "node:path";
4742
4992
  function ensureNodePtySpawnHelperExecutable(deps = {}) {
4743
4993
  const platform = deps.platform ?? process.platform;
4744
4994
  if (platform !== "darwin") {
4745
4995
  return;
4746
4996
  }
4747
4997
  const arch = deps.arch ?? process.arch;
4748
- const resolve3 = deps.resolve ?? ((id) => require2.resolve(id));
4749
- const fileExists = deps.existsSync ?? existsSync3;
4998
+ const resolve4 = deps.resolve ?? ((id) => require2.resolve(id));
4999
+ const fileExists = deps.existsSync ?? existsSync4;
4750
5000
  const stat7 = deps.statSync ?? statSync;
4751
5001
  const chmod = deps.chmodSync ?? chmodSync;
4752
5002
  let packageJsonPath;
4753
5003
  try {
4754
- packageJsonPath = resolve3(NODE_PTY_PKG);
5004
+ packageJsonPath = resolve4(NODE_PTY_PKG);
4755
5005
  } catch {
4756
5006
  return;
4757
5007
  }
4758
- const packageDir = path5.dirname(packageJsonPath);
5008
+ const packageDir = path6.dirname(packageJsonPath);
4759
5009
  const helperDir = arch === "arm64" ? "darwin-arm64" : arch === "x64" ? "darwin-x64" : null;
4760
5010
  if (!helperDir) {
4761
5011
  return;
4762
5012
  }
4763
- const helperPath = path5.join(packageDir, "prebuilds", helperDir, "spawn-helper");
5013
+ const helperPath = path6.join(packageDir, "prebuilds", helperDir, "spawn-helper");
4764
5014
  try {
4765
5015
  if (!fileExists(helperPath)) {
4766
5016
  return;
@@ -4816,7 +5066,7 @@ async function escalateKillWithPolling(pid, signal, options) {
4816
5066
  const startTime = Date.now();
4817
5067
  const deadline = startTime + timeoutMs;
4818
5068
  while (Date.now() < deadline) {
4819
- await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
5069
+ await new Promise((resolve4) => setTimeout(resolve4, pollIntervalMs));
4820
5070
  if (!isProcessAlive(pid)) {
4821
5071
  return true;
4822
5072
  }
@@ -4828,6 +5078,7 @@ var require2, NODE_PTY_PKG, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, NodePt
4828
5078
  var init_pty_host = __esm({
4829
5079
  "packages/server/src/terminal/pty-host.ts"() {
4830
5080
  "use strict";
5081
+ init_src2();
4831
5082
  require2 = createRequire(import.meta.url);
4832
5083
  NODE_PTY_PKG = "node-pty/package.json";
4833
5084
  DEFAULT_POLL_INTERVAL_MS = 50;
@@ -4842,7 +5093,13 @@ var init_pty_host = __esm({
4842
5093
  const message = err instanceof Error ? err.message : String(err);
4843
5094
  throw new Error(`node-pty native module not available. ${message}`);
4844
5095
  }
4845
- const [command, ...args] = argv;
5096
+ if (argv.length === 0) {
5097
+ throw new Error("PTY spawn requires a command");
5098
+ }
5099
+ const [command, ...args] = resolveSpawnArgv(argv, {
5100
+ pathEnv: options.env.Path ?? options.env.PATH,
5101
+ pathExt: options.env.PATHEXT
5102
+ });
4846
5103
  if (command === void 0) {
4847
5104
  throw new Error("PTY spawn requires a command");
4848
5105
  }
@@ -4903,13 +5160,13 @@ var SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY;
4903
5160
  var init_settings = __esm({
4904
5161
  "packages/server/src/supervisor/settings.ts"() {
4905
5162
  "use strict";
4906
- init_src2();
5163
+ init_src3();
4907
5164
  SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY = "supervisor.evaluationTimeoutSec";
4908
5165
  }
4909
5166
  });
4910
5167
 
4911
5168
  // packages/server/src/supervisor/evaluator.ts
4912
- import { spawn } from "node:child_process";
5169
+ import { spawn as spawn2 } from "node:child_process";
4913
5170
  function buildPrompt(context) {
4914
5171
  const agentOutput = context.transcriptExcerpt ?? context.terminalExcerpt ?? "";
4915
5172
  const userInput = context.latestUserInput?.trim() ?? "";
@@ -4940,12 +5197,13 @@ async function runCommand(command, timeoutMs, options = {}) {
4940
5197
  if (options.signal?.aborted) {
4941
5198
  throw createSupervisorEvalAbortedError();
4942
5199
  }
4943
- return await new Promise((resolve3, reject) => {
4944
- const child = spawn(command.argv[0], command.argv.slice(1), {
5200
+ return await new Promise((resolve4, reject) => {
5201
+ const child = spawn2(command.argv[0], command.argv.slice(1), {
4945
5202
  cwd: command.cwd,
4946
5203
  detached: process.platform !== "win32",
4947
5204
  env: { ...process.env, ...command.env },
4948
- stdio: ["ignore", "pipe", "pipe"]
5205
+ stdio: ["ignore", "pipe", "pipe"],
5206
+ windowsHide: true
4949
5207
  });
4950
5208
  const stdout = [];
4951
5209
  const stderr = [];
@@ -4969,7 +5227,7 @@ async function runCommand(command, timeoutMs, options = {}) {
4969
5227
  }
4970
5228
  settled = true;
4971
5229
  cleanup();
4972
- resolve3(value);
5230
+ resolve4(value);
4973
5231
  };
4974
5232
  const terminate = (error) => {
4975
5233
  if (terminationError) {
@@ -5176,7 +5434,7 @@ var NOOP_LOGGER2, SupervisorEvaluator;
5176
5434
  var init_evaluator = __esm({
5177
5435
  "packages/server/src/supervisor/evaluator.ts"() {
5178
5436
  "use strict";
5179
- init_src2();
5437
+ init_src3();
5180
5438
  init_provider_config();
5181
5439
  init_pty_host();
5182
5440
  init_settings();
@@ -5279,7 +5537,7 @@ var INJECTABLE_SESSION_STATES, SupervisorInjector;
5279
5537
  var init_injector = __esm({
5280
5538
  "packages/server/src/supervisor/injector.ts"() {
5281
5539
  "use strict";
5282
- init_src2();
5540
+ init_src3();
5283
5541
  INJECTABLE_SESSION_STATES = /* @__PURE__ */ new Set([
5284
5542
  "idle",
5285
5543
  "running"
@@ -5356,12 +5614,12 @@ var init_scheduler = __esm({
5356
5614
 
5357
5615
  // packages/server/src/supervisor/manager.ts
5358
5616
  function createDeferredCompletion() {
5359
- let resolve3 = () => {
5617
+ let resolve4 = () => {
5360
5618
  };
5361
5619
  const promise = new Promise((innerResolve) => {
5362
- resolve3 = innerResolve;
5620
+ resolve4 = innerResolve;
5363
5621
  });
5364
- return { promise, resolve: resolve3 };
5622
+ return { promise, resolve: resolve4 };
5365
5623
  }
5366
5624
  function generateSupervisorId() {
5367
5625
  return `sup_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
@@ -5391,7 +5649,7 @@ var NOOP_LOGGER3, SupervisorManager;
5391
5649
  var init_manager2 = __esm({
5392
5650
  "packages/server/src/supervisor/manager.ts"() {
5393
5651
  "use strict";
5394
- init_src2();
5652
+ init_src3();
5395
5653
  init_context_builder();
5396
5654
  init_evaluator();
5397
5655
  init_injector();
@@ -6300,8 +6558,8 @@ var init_terminal_snapshot_buffer = __esm({
6300
6558
  if (this.pendingWriteCount === 0) {
6301
6559
  return Promise.resolve();
6302
6560
  }
6303
- return new Promise((resolve3) => {
6304
- this.drainResolvers.push(resolve3);
6561
+ return new Promise((resolve4) => {
6562
+ this.drainResolvers.push(resolve4);
6305
6563
  });
6306
6564
  }
6307
6565
  resolveDrainIfIdle() {
@@ -6310,8 +6568,8 @@ var init_terminal_snapshot_buffer = __esm({
6310
6568
  }
6311
6569
  const resolvers = this.drainResolvers;
6312
6570
  this.drainResolvers = [];
6313
- for (const resolve3 of resolvers) {
6314
- resolve3();
6571
+ for (const resolve4 of resolvers) {
6572
+ resolve4();
6315
6573
  }
6316
6574
  }
6317
6575
  requireTerminal() {
@@ -6630,10 +6888,10 @@ var init_manager3 = __esm({
6630
6888
  }
6631
6889
  return existing.promise;
6632
6890
  }
6633
- let resolve3 = () => {
6891
+ let resolve4 = () => {
6634
6892
  };
6635
6893
  const promise = new Promise((innerResolve) => {
6636
- resolve3 = innerResolve;
6894
+ resolve4 = innerResolve;
6637
6895
  });
6638
6896
  let markKillCompleted = () => {
6639
6897
  };
@@ -6646,7 +6904,7 @@ var init_manager3 = __esm({
6646
6904
  markKillCompleted,
6647
6905
  finalized: false,
6648
6906
  promise,
6649
- resolve: resolve3
6907
+ resolve: resolve4
6650
6908
  });
6651
6909
  void terminal.pty.kill(signal).finally(() => {
6652
6910
  const waiter = this.explicitCloseWaiters.get(terminalId);
@@ -6775,14 +7033,14 @@ var init_manager3 = __esm({
6775
7033
  // packages/server/src/workspace/validator.ts
6776
7034
  import { constants } from "fs";
6777
7035
  import { access, stat as stat5 } from "fs/promises";
6778
- async function validatePath(path8) {
7036
+ async function validatePath(path9) {
6779
7037
  try {
6780
- const stats = await stat5(path8);
7038
+ const stats = await stat5(path9);
6781
7039
  if (!stats.isDirectory()) {
6782
7040
  return { valid: false, error: "Path is not a directory" };
6783
7041
  }
6784
- await access(path8, constants.R_OK);
6785
- await access(path8, constants.W_OK);
7042
+ await access(path9, constants.R_OK);
7043
+ await access(path9, constants.W_OK);
6786
7044
  return { valid: true };
6787
7045
  } catch (error) {
6788
7046
  if (error.code === "ENOENT") {
@@ -6799,8 +7057,8 @@ var init_validator = __esm({
6799
7057
  "packages/server/src/workspace/validator.ts"() {
6800
7058
  "use strict";
6801
7059
  WorkspaceValidator = class {
6802
- async validate(path8) {
6803
- const result = await validatePath(path8);
7060
+ async validate(path9) {
7061
+ const result = await validatePath(path9);
6804
7062
  if (!result.valid) {
6805
7063
  throw new Error(`Invalid workspace path: ${result.error}`);
6806
7064
  }
@@ -6810,14 +7068,14 @@ var init_validator = __esm({
6810
7068
  });
6811
7069
 
6812
7070
  // packages/server/src/fs/gitignore.ts
6813
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
7071
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
6814
7072
  import ignore from "ignore";
6815
7073
  import { join as join4, relative } from "path";
6816
- function normalizePath(path8) {
6817
- return path8.replace(/\\/g, "/");
7074
+ function normalizePath(path9) {
7075
+ return path9.replace(/\\/g, "/");
6818
7076
  }
6819
- function relativeToRoot(rootPath, path8) {
6820
- return normalizePath(relative(rootPath, path8));
7077
+ function relativeToRoot(rootPath, path9) {
7078
+ return normalizePath(relative(rootPath, path9));
6821
7079
  }
6822
7080
  function isDefaultTreeIgnored(name) {
6823
7081
  return name.startsWith(".") || name === "node_modules" || name === ".git";
@@ -6825,18 +7083,18 @@ function isDefaultTreeIgnored(name) {
6825
7083
  function isAlwaysTreeIgnored(name) {
6826
7084
  return name === "node_modules" || name === ".git";
6827
7085
  }
6828
- function isIgnoredByGitignore(ig, path8) {
6829
- if (!path8 || path8.startsWith("..")) {
7086
+ function isIgnoredByGitignore(ig, path9) {
7087
+ if (!path9 || path9.startsWith("..")) {
6830
7088
  return false;
6831
7089
  }
6832
- return ig.ignores(path8) || ig.ignores(`${path8}/`);
7090
+ return ig.ignores(path9) || ig.ignores(`${path9}/`);
6833
7091
  }
6834
7092
  function createGitignoreFilter(rootPath, dirPath) {
6835
7093
  const gitignorePath = join4(rootPath, ".gitignore");
6836
- if (!existsSync4(gitignorePath)) {
7094
+ if (!existsSync5(gitignorePath)) {
6837
7095
  return (name) => !isDefaultTreeIgnored(name);
6838
7096
  }
6839
- const gitignoreContent = readFileSync4(gitignorePath, "utf-8");
7097
+ const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
6840
7098
  const ig = ignore().add(gitignoreContent);
6841
7099
  return (name) => {
6842
7100
  if (isAlwaysTreeIgnored(name)) {
@@ -6848,17 +7106,17 @@ function createGitignoreFilter(rootPath, dirPath) {
6848
7106
  }
6849
7107
  function createWatcherIgnoreFilter(rootPath) {
6850
7108
  const gitignorePath = join4(rootPath, ".gitignore");
6851
- if (!existsSync4(gitignorePath)) {
6852
- return (path8) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path8)));
7109
+ if (!existsSync5(gitignorePath)) {
7110
+ return (path9) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path9)));
6853
7111
  }
6854
- const gitignoreContent = readFileSync4(gitignorePath, "utf-8");
7112
+ const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
6855
7113
  const ig = ignore().add(gitignoreContent);
6856
- return (path8) => {
6857
- const normalizedPath = normalizePath(path8);
7114
+ return (path9) => {
7115
+ const normalizedPath = normalizePath(path9);
6858
7116
  if (DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizedPath))) {
6859
7117
  return true;
6860
7118
  }
6861
- const relativePath = relativeToRoot(rootPath, path8);
7119
+ const relativePath = relativeToRoot(rootPath, path9);
6862
7120
  return isIgnoredByGitignore(ig, relativePath);
6863
7121
  };
6864
7122
  }
@@ -6881,7 +7139,7 @@ var WorkspaceWatcher;
6881
7139
  var init_watcher = __esm({
6882
7140
  "packages/server/src/fs/watcher.ts"() {
6883
7141
  "use strict";
6884
- init_src2();
7142
+ init_src3();
6885
7143
  init_gitignore();
6886
7144
  WorkspaceWatcher = class {
6887
7145
  constructor(workspaceId, rootPath, broadcaster) {
@@ -7133,12 +7391,12 @@ var init_manager4 = __esm({
7133
7391
  * @param path - Workspace path
7134
7392
  * @returns Workspace or undefined
7135
7393
  */
7136
- getByPath(path8) {
7394
+ getByPath(path9) {
7137
7395
  const row = this.deps.db.prepare(
7138
7396
  `SELECT id, path, target_runtime, wsl_distro, opened_at, last_active_at, ui_state
7139
7397
  FROM workspaces
7140
7398
  WHERE path = ?`
7141
- ).get(path8);
7399
+ ).get(path9);
7142
7400
  if (!row) return void 0;
7143
7401
  return {
7144
7402
  id: row.id,
@@ -7342,16 +7600,16 @@ async function debounce(key, op, windowMs) {
7342
7600
  clearTimeout(entry.timer);
7343
7601
  entry.op = op;
7344
7602
  } else {
7345
- let resolve3;
7603
+ let resolve4;
7346
7604
  let reject;
7347
7605
  const promise = new Promise((res, rej) => {
7348
- resolve3 = res;
7606
+ resolve4 = res;
7349
7607
  reject = rej;
7350
7608
  });
7351
7609
  entry = {
7352
7610
  timer: void 0,
7353
7611
  promise,
7354
- resolve: resolve3,
7612
+ resolve: resolve4,
7355
7613
  reject,
7356
7614
  op
7357
7615
  };
@@ -7527,7 +7785,7 @@ var TerminalInputActivitySchema, TerminalInputSchema, pendingTerminalInput, next
7527
7785
  var init_terminal = __esm({
7528
7786
  "packages/server/src/commands/terminal.ts"() {
7529
7787
  "use strict";
7530
- init_src2();
7788
+ init_src3();
7531
7789
  init_dispatch();
7532
7790
  TerminalInputActivitySchema = z5.enum(TERMINAL_INPUT_ACTIVITIES).optional();
7533
7791
  TerminalInputSchema = z5.union([
@@ -8135,7 +8393,7 @@ var BINARY_PAYLOAD_TIMEOUT_MS, isBinaryTerminalInputArgs, WsHub;
8135
8393
  var init_hub = __esm({
8136
8394
  "packages/server/src/ws/hub.ts"() {
8137
8395
  "use strict";
8138
- init_src2();
8396
+ init_src3();
8139
8397
  init_terminal();
8140
8398
  init_client();
8141
8399
  init_dispatch();
@@ -8174,7 +8432,10 @@ var init_hub = __esm({
8174
8432
  status: "connected",
8175
8433
  clientId: client.id,
8176
8434
  authEnabled: this.deps.config.auth.enabled,
8177
- binaryTerminalTransport: true
8435
+ binaryTerminalTransport: true,
8436
+ version: this.deps.config.appVersion ?? "0.0.0",
8437
+ serverInstanceId: `server-${process.pid}`,
8438
+ isWriter: false
8178
8439
  });
8179
8440
  client.onMessage((msg) => this.routeMessage(client, msg));
8180
8441
  client.onClose(() => this.handleClose(client));
@@ -8228,7 +8489,7 @@ var init_hub = __esm({
8228
8489
  }
8229
8490
  }
8230
8491
  awaitBinaryPayload(clientId) {
8231
- return new Promise((resolve3, reject) => {
8492
+ return new Promise((resolve4, reject) => {
8232
8493
  const timer = setTimeout(() => {
8233
8494
  const waiters = this.pendingBinaryWaiters.get(clientId);
8234
8495
  if (!waiters) return;
@@ -8240,7 +8501,7 @@ var init_hub = __esm({
8240
8501
  }
8241
8502
  reject(new Error("Timeout waiting for terminal input binary payload"));
8242
8503
  }, BINARY_PAYLOAD_TIMEOUT_MS);
8243
- const waiter = { resolve: resolve3, reject, timer };
8504
+ const waiter = { resolve: resolve4, reject, timer };
8244
8505
  const queue = this.pendingBinaryWaiters.get(clientId);
8245
8506
  if (queue) {
8246
8507
  queue.push(waiter);
@@ -9047,7 +9308,7 @@ var init_file = __esm({
9047
9308
  // packages/server/src/git/diff.ts
9048
9309
  import { mkdtemp as mkdtemp2, rm as rm5 } from "fs/promises";
9049
9310
  import os3 from "os";
9050
- import path6 from "path";
9311
+ import path7 from "path";
9051
9312
  async function isTrackedPath(cwd, filePath) {
9052
9313
  try {
9053
9314
  await runGit(cwd, ["ls-files", "--error-unmatch", "--", filePath]);
@@ -9057,8 +9318,8 @@ async function isTrackedPath(cwd, filePath) {
9057
9318
  }
9058
9319
  }
9059
9320
  async function getUntrackedFileDiff(cwd, filePath) {
9060
- const tempDir = await mkdtemp2(path6.join(os3.tmpdir(), "coder-studio-git-diff-"));
9061
- const tempIndex = path6.join(tempDir, "index");
9321
+ const tempDir = await mkdtemp2(path7.join(os3.tmpdir(), "coder-studio-git-diff-"));
9322
+ const tempIndex = path7.join(tempDir, "index");
9062
9323
  try {
9063
9324
  try {
9064
9325
  await runGit(cwd, ["read-tree", "HEAD"], {
@@ -9083,11 +9344,11 @@ async function getUntrackedFileDiff(cwd, filePath) {
9083
9344
  await rm5(tempDir, { recursive: true, force: true });
9084
9345
  }
9085
9346
  }
9086
- async function getFileDiff(cwd, path8, staged = false) {
9087
- if (!staged && !await isTrackedPath(cwd, path8)) {
9088
- return getUntrackedFileDiff(cwd, path8);
9347
+ async function getFileDiff(cwd, path9, staged = false) {
9348
+ if (!staged && !await isTrackedPath(cwd, path9)) {
9349
+ return getUntrackedFileDiff(cwd, path9);
9089
9350
  }
9090
- const args = staged ? ["diff", "--staged", "--", path8] : ["diff", "--", path8];
9351
+ const args = staged ? ["diff", "--staged", "--", path9] : ["diff", "--", path9];
9091
9352
  const result = await runGit(cwd, args);
9092
9353
  return result.stdout;
9093
9354
  }
@@ -9336,7 +9597,7 @@ var init_git2 = __esm({
9336
9597
  });
9337
9598
 
9338
9599
  // packages/server/src/config/config-io.ts
9339
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
9600
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
9340
9601
  import { homedir as homedir4 } from "node:os";
9341
9602
  import { basename as basename2, dirname as dirname4, join as join7 } from "node:path";
9342
9603
  function resolveConfigPath(configType) {
@@ -9358,11 +9619,11 @@ function resolveConfigPath(configType) {
9358
9619
  }
9359
9620
  function readConfigFile(configType) {
9360
9621
  const configPath = resolveConfigPath(configType);
9361
- if (!existsSync5(configPath)) {
9622
+ if (!existsSync6(configPath)) {
9362
9623
  return { configPath, content: "", exists: false };
9363
9624
  }
9364
9625
  try {
9365
- const content = readFileSync5(configPath, "utf-8");
9626
+ const content = readFileSync6(configPath, "utf-8");
9366
9627
  return { configPath, content, exists: true };
9367
9628
  } catch {
9368
9629
  return { configPath, content: "", exists: false };
@@ -9372,11 +9633,11 @@ function writeConfigFile(configType, content) {
9372
9633
  try {
9373
9634
  const configPath = resolveConfigPath(configType);
9374
9635
  const parentDir = dirname4(configPath);
9375
- if (!existsSync5(parentDir)) {
9636
+ if (!existsSync6(parentDir)) {
9376
9637
  mkdirSync3(parentDir, { recursive: true });
9377
9638
  }
9378
9639
  let backupPath = null;
9379
- if (existsSync5(configPath)) {
9640
+ if (existsSync6(configPath)) {
9380
9641
  backupPath = createBackup(configPath);
9381
9642
  }
9382
9643
  const tempPath = `${configPath}.tmp`;
@@ -9392,7 +9653,7 @@ function writeConfigFile(configType, content) {
9392
9653
  }
9393
9654
  }
9394
9655
  function createBackup(filePath) {
9395
- const original = readFileSync5(filePath, "utf-8");
9656
+ const original = readFileSync6(filePath, "utf-8");
9396
9657
  const ext = filePath.split(".").pop() ?? "";
9397
9658
  const base = basename2(filePath, `.${ext}`);
9398
9659
  const dir = dirname4(filePath);
@@ -9430,7 +9691,7 @@ var EMPTY_CODEX_AUDIT, SettingsSchema;
9430
9691
  var init_settings2 = __esm({
9431
9692
  "packages/server/src/commands/settings.ts"() {
9432
9693
  "use strict";
9433
- init_src2();
9694
+ init_src3();
9434
9695
  init_config_io();
9435
9696
  init_provider_config();
9436
9697
  init_provider_config_repo();
@@ -9710,9 +9971,9 @@ var init_supervisor2 = __esm({
9710
9971
  });
9711
9972
 
9712
9973
  // packages/server/src/git/worktree.ts
9713
- import path7 from "node:path";
9974
+ import path8 from "node:path";
9714
9975
  function normalizeWorktreePath(worktreePath) {
9715
- return path7.resolve(worktreePath);
9976
+ return path8.resolve(worktreePath);
9716
9977
  }
9717
9978
  async function resolveWorktreePath(repoPath, worktreePath) {
9718
9979
  const normalizedRequested = normalizeWorktreePath(worktreePath);
@@ -9799,19 +10060,19 @@ async function getWorktreeTree(worktreePath) {
9799
10060
  for (const line of lines) {
9800
10061
  const isDir = line.endsWith("/");
9801
10062
  const name = isDir ? line.slice(0, -1) : line;
9802
- const path8 = `${worktreePath}/${name}`;
10063
+ const path9 = `${worktreePath}/${name}`;
9803
10064
  nodes.push({
9804
10065
  name,
9805
- path: path8,
10066
+ path: path9,
9806
10067
  kind: isDir ? "dir" : "file"
9807
10068
  });
9808
10069
  }
9809
10070
  return nodes;
9810
10071
  }
9811
- async function createWorktree(repoPath, branch, path8) {
9812
- await runGit(repoPath, ["worktree", "add", path8, branch]);
10072
+ async function createWorktree(repoPath, branch, path9) {
10073
+ await runGit(repoPath, ["worktree", "add", path9, branch]);
9813
10074
  const worktrees = await listWorktrees(repoPath);
9814
- const created = worktrees.find((wt) => wt.path === path8);
10075
+ const created = worktrees.find((wt) => wt.path === path9);
9815
10076
  if (!created) {
9816
10077
  throw new Error("Failed to find created worktree");
9817
10078
  }
@@ -10009,8 +10270,6 @@ var init_commands = __esm({
10009
10270
  });
10010
10271
 
10011
10272
  // packages/server/src/server.ts
10012
- import { execFile as nodeExecFile3 } from "node:child_process";
10013
- import { promisify as promisify3 } from "node:util";
10014
10273
  function createCodexConfigAuditApi() {
10015
10274
  return {
10016
10275
  audit: () => ({ codex: auditCodexConfigToml() }),
@@ -10038,7 +10297,6 @@ async function logCodexConfigFindings(auditApi, logger) {
10038
10297
  }
10039
10298
  }
10040
10299
  async function createServer(configOverrides) {
10041
- const execFileAsync3 = promisify3(nodeExecFile3);
10042
10300
  const config = parseServerConfig(configOverrides);
10043
10301
  ensureDataDir(config);
10044
10302
  const db = openDatabase(config.dataDir);
@@ -10120,7 +10378,7 @@ async function createServer(configOverrides) {
10120
10378
  const providerRuntimeDeps = {};
10121
10379
  const providerInstallMgr = new ProviderInstallManager(providerRegistry, {
10122
10380
  ...providerRuntimeDeps,
10123
- execFile: (file, args) => execFileAsync3(file, args)
10381
+ runCommand: runCommandAsString
10124
10382
  });
10125
10383
  const commandContext = {
10126
10384
  workspaceMgr,
@@ -10283,10 +10541,12 @@ var init_server = __esm({
10283
10541
  "use strict";
10284
10542
  init_runtime();
10285
10543
  init_src();
10544
+ init_src2();
10286
10545
  init_app();
10287
10546
  init_event_bus();
10288
10547
  init_codex_config_audit();
10289
10548
  init_config();
10549
+ init_command_runner();
10290
10550
  init_install_manager();
10291
10551
  init_manager();
10292
10552
  init_db();
@@ -10306,7 +10566,7 @@ var init_server = __esm({
10306
10566
  init_fencing();
10307
10567
  init_hub();
10308
10568
  init_commands();
10309
- if (import.meta.url === `file://${process.argv[1]}`) {
10569
+ if (isDirectExecution(import.meta.url)) {
10310
10570
  const server = await createServer();
10311
10571
  process.on("SIGINT", async () => {
10312
10572
  console.log("\nShutting down...");
@@ -10456,8 +10716,8 @@ var init_workspace_repo = __esm({
10456
10716
  /**
10457
10717
  * Finds a workspace by path
10458
10718
  */
10459
- findByPath(path8) {
10460
- const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path8);
10719
+ findByPath(path9) {
10720
+ const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path9);
10461
10721
  return row ? this.rowToWorkspace(row) : void 0;
10462
10722
  }
10463
10723
  /**
@@ -10578,7 +10838,7 @@ __export(src_exports, {
10578
10838
  sessionToRow: () => sessionToRow,
10579
10839
  withTransaction: () => withTransaction
10580
10840
  });
10581
- var init_src3 = __esm({
10841
+ var init_src4 = __esm({
10582
10842
  async "packages/server/src/index.ts"() {
10583
10843
  "use strict";
10584
10844
  init_auth();
@@ -10592,15 +10852,15 @@ var init_src3 = __esm({
10592
10852
  });
10593
10853
 
10594
10854
  // packages/cli/src/cli.ts
10595
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
10855
+ import { existsSync as existsSync11 } from "fs";
10596
10856
  import { dirname as dirname6, join as join10 } from "path";
10597
10857
  import { fileURLToPath as fileURLToPath3 } from "url";
10598
10858
 
10599
10859
  // packages/cli/src/auth-control.ts
10600
- await init_src3();
10860
+ await init_src4();
10601
10861
 
10602
10862
  // packages/cli/src/config-store.ts
10603
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
10863
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
10604
10864
  import { homedir as homedir5 } from "os";
10605
10865
  import { basename as basename3, join as join8 } from "path";
10606
10866
  var DEFAULT_DB_FILE = "coder-studio.db";
@@ -10617,12 +10877,12 @@ function normalizeDataDir(input2) {
10617
10877
  return join8(input2, DEFAULT_DB_FILE);
10618
10878
  }
10619
10879
  function readCliConfig() {
10620
- const path8 = getCliConfigPath();
10621
- if (!existsSync6(path8)) {
10880
+ const path9 = getCliConfigPath();
10881
+ if (!existsSync7(path9)) {
10622
10882
  return null;
10623
10883
  }
10624
10884
  try {
10625
- const parsed = JSON.parse(readFileSync6(path8, "utf-8"));
10885
+ const parsed = JSON.parse(readFileSync7(path9, "utf-8"));
10626
10886
  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") {
10627
10887
  return null;
10628
10888
  }
@@ -10632,7 +10892,7 @@ function readCliConfig() {
10632
10892
  }
10633
10893
  }
10634
10894
  function writeCliConfig(config) {
10635
- const path8 = getCliConfigPath();
10895
+ const path9 = getCliConfigPath();
10636
10896
  const dir = join8(homedir5(), ".coder-studio");
10637
10897
  const normalizedConfig = {
10638
10898
  ...config.host !== void 0 ? { host: config.host } : {},
@@ -10640,10 +10900,10 @@ function writeCliConfig(config) {
10640
10900
  ...config.dataDir !== void 0 ? { dataDir: normalizeDataDir(config.dataDir) } : {},
10641
10901
  ...config.password !== void 0 ? { password: config.password } : {}
10642
10902
  };
10643
- if (!existsSync6(dir)) {
10903
+ if (!existsSync7(dir)) {
10644
10904
  mkdirSync4(dir, { recursive: true });
10645
10905
  }
10646
- writeFileSync4(path8, JSON.stringify(normalizedConfig, null, 2), "utf-8");
10906
+ writeFileSync4(path9, JSON.stringify(normalizedConfig, null, 2), "utf-8");
10647
10907
  }
10648
10908
 
10649
10909
  // packages/cli/src/auth-control.ts
@@ -10679,7 +10939,7 @@ async function clearAuthBlockByIp(ip) {
10679
10939
  }
10680
10940
 
10681
10941
  // packages/cli/src/browser.ts
10682
- import { spawn as spawn2 } from "node:child_process";
10942
+ import { spawn as spawn3 } from "node:child_process";
10683
10943
  function getOpenCommand(url) {
10684
10944
  switch (process.platform) {
10685
10945
  case "darwin":
@@ -10692,44 +10952,45 @@ function getOpenCommand(url) {
10692
10952
  }
10693
10953
  async function openBrowser(url) {
10694
10954
  const { command, args } = getOpenCommand(url);
10695
- await new Promise((resolve3, reject) => {
10696
- const child = spawn2(command, args, {
10955
+ await new Promise((resolve4, reject) => {
10956
+ const child = spawn3(command, args, {
10697
10957
  detached: true,
10698
- stdio: "ignore"
10958
+ stdio: "ignore",
10959
+ windowsHide: true
10699
10960
  });
10700
10961
  child.once("error", reject);
10701
10962
  child.once("spawn", () => {
10702
10963
  child.unref();
10703
- resolve3();
10964
+ resolve4();
10704
10965
  });
10705
10966
  });
10706
10967
  }
10707
10968
 
10708
10969
  // packages/cli/src/log-excerpt.ts
10709
- import { closeSync, existsSync as existsSync7, openSync, readSync, statSync as statSync2 } from "fs";
10970
+ import { closeSync, existsSync as existsSync8, openSync, readSync, statSync as statSync2 } from "fs";
10710
10971
  var DEFAULT_MAX_LINES = 40;
10711
10972
  var DEFAULT_MAX_CHARS = 4e3;
10712
10973
  var DEFAULT_MAX_BYTES = 16 * 1024;
10713
- var getFileSize = (path8) => {
10714
- if (!existsSync7(path8)) {
10974
+ var getFileSize = (path9) => {
10975
+ if (!existsSync8(path9)) {
10715
10976
  return 0;
10716
10977
  }
10717
10978
  try {
10718
- return statSync2(path8).size;
10979
+ return statSync2(path9).size;
10719
10980
  } catch {
10720
10981
  return 0;
10721
10982
  }
10722
10983
  };
10723
- var readLogExcerpt = (path8, {
10984
+ var readLogExcerpt = (path9, {
10724
10985
  startOffset = 0,
10725
10986
  maxBytes = DEFAULT_MAX_BYTES,
10726
10987
  maxLines = DEFAULT_MAX_LINES,
10727
10988
  maxChars = DEFAULT_MAX_CHARS
10728
10989
  } = {}) => {
10729
- if (!existsSync7(path8)) {
10990
+ if (!existsSync8(path9)) {
10730
10991
  return null;
10731
10992
  }
10732
- const fileSize = getFileSize(path8);
10993
+ const fileSize = getFileSize(path9);
10733
10994
  const safeOffset = startOffset > fileSize ? 0 : Math.max(0, startOffset);
10734
10995
  if (fileSize === safeOffset) {
10735
10996
  return null;
@@ -10737,7 +10998,7 @@ var readLogExcerpt = (path8, {
10737
10998
  const bytesToRead = Math.min(fileSize - safeOffset, maxBytes);
10738
10999
  const readStart = fileSize - bytesToRead;
10739
11000
  const buffer = Buffer.allocUnsafe(bytesToRead);
10740
- const fd = openSync(path8, "r");
11001
+ const fd = openSync(path9, "r");
10741
11002
  let content = "";
10742
11003
  let startsMidLine = false;
10743
11004
  try {
@@ -10804,6 +11065,27 @@ function assertSupportedNodeVersion(version = process.versions.node) {
10804
11065
  );
10805
11066
  }
10806
11067
 
11068
+ // packages/cli/src/package-manifest.ts
11069
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
11070
+ function resolveCliPackageManifestUrl(importMetaUrl) {
11071
+ const manifestUrl = [
11072
+ new URL("../package.json", importMetaUrl),
11073
+ new URL("../../package.json", importMetaUrl)
11074
+ ].find((candidate) => existsSync9(candidate));
11075
+ if (!manifestUrl) {
11076
+ throw new Error("Unable to locate CLI package.json");
11077
+ }
11078
+ return manifestUrl;
11079
+ }
11080
+ function getCliPackageManifest(importMetaUrl) {
11081
+ return JSON.parse(
11082
+ readFileSync8(resolveCliPackageManifestUrl(importMetaUrl), "utf-8")
11083
+ );
11084
+ }
11085
+ function getCliVersion(importMetaUrl) {
11086
+ return getCliPackageManifest(importMetaUrl).version ?? "0.0.0";
11087
+ }
11088
+
10807
11089
  // packages/cli/src/parse-args.ts
10808
11090
  var RUNTIME_CONFIG_ERROR = "Host, port, data-dir, password, and auth settings must be configured via the config command";
10809
11091
  function getActiveCommand(args) {
@@ -11013,6 +11295,8 @@ var MANAGED_SERVER_NAME = "coder-studio-server";
11013
11295
  var PM2_RESTART_DELAY_MS = 2e3;
11014
11296
  var PM2_MIN_UPTIME = "5s";
11015
11297
  var PM2_MAX_RESTARTS = 10;
11298
+ var PM2_DELETE_WAIT_MS = 5e3;
11299
+ var PM2_DISCONNECT_WAIT_MS = 1e3;
11016
11300
  var STARTUP_POLL_INTERVAL_MS = 100;
11017
11301
  var STARTUP_FAILURE_GUIDANCE = "Run `coder-studio logs` for details or `coder-studio serve --foreground` for interactive debugging.";
11018
11302
  var isMissingManagedServerError = (error) => {
@@ -11046,58 +11330,72 @@ async function loadPm2() {
11046
11330
  }
11047
11331
  var connectPm2 = async () => {
11048
11332
  const pm2 = await loadPm2();
11049
- return new Promise((resolve3, reject) => {
11333
+ return new Promise((resolve4, reject) => {
11050
11334
  pm2.connect((error) => {
11051
11335
  if (error) {
11052
11336
  reject(error);
11053
11337
  return;
11054
11338
  }
11055
- resolve3();
11339
+ resolve4();
11056
11340
  });
11057
11341
  });
11058
11342
  };
11059
- var sleep = async (ms) => new Promise((resolve3) => {
11060
- setTimeout(resolve3, ms);
11343
+ var sleep = async (ms) => new Promise((resolve4) => {
11344
+ setTimeout(resolve4, ms);
11061
11345
  });
11062
11346
  var disconnectPm2 = async () => {
11063
11347
  const pm2 = await loadPm2();
11064
- pm2.disconnect();
11065
- };
11066
- var describeManagedServer = async () => {
11067
- const pm2 = await loadPm2();
11068
- return new Promise((resolve3, reject) => {
11069
- pm2.describe(MANAGED_SERVER_NAME, (error, result) => {
11070
- if (error) {
11071
- reject(error);
11348
+ await new Promise((resolve4) => {
11349
+ let settled = false;
11350
+ const finish = () => {
11351
+ if (settled) {
11072
11352
  return;
11073
11353
  }
11074
- resolve3(result ?? []);
11075
- });
11354
+ settled = true;
11355
+ resolve4();
11356
+ };
11357
+ const timer = setTimeout(finish, PM2_DISCONNECT_WAIT_MS);
11358
+ try {
11359
+ pm2.disconnect(() => {
11360
+ clearTimeout(timer);
11361
+ finish();
11362
+ });
11363
+ } catch {
11364
+ clearTimeout(timer);
11365
+ finish();
11366
+ }
11076
11367
  });
11077
11368
  };
11078
- var removeManagedServer = async () => {
11079
- const pm2 = await loadPm2();
11080
- return new Promise((resolve3, reject) => {
11081
- pm2.delete(MANAGED_SERVER_NAME, (error) => {
11082
- if (error) {
11083
- reject(error);
11084
- return;
11085
- }
11086
- resolve3();
11087
- });
11369
+ var describeManagedServer = async (pm2) => new Promise((resolve4, reject) => {
11370
+ pm2.describe(MANAGED_SERVER_NAME, (error, result) => {
11371
+ if (error) {
11372
+ reject(error);
11373
+ return;
11374
+ }
11375
+ resolve4(result ?? []);
11088
11376
  });
11089
- };
11377
+ });
11378
+ var removeManagedServer = async (pm2) => new Promise((resolve4, reject) => {
11379
+ pm2.delete(MANAGED_SERVER_NAME, (error) => {
11380
+ if (error) {
11381
+ reject(error);
11382
+ return;
11383
+ }
11384
+ resolve4();
11385
+ });
11386
+ });
11090
11387
  var killPm2Daemon = async () => {
11091
11388
  const pm2 = await loadPm2();
11092
- return new Promise((resolve3) => {
11389
+ return new Promise((resolve4) => {
11093
11390
  pm2.kill(() => {
11094
- resolve3();
11391
+ resolve4();
11095
11392
  });
11096
11393
  });
11097
11394
  };
11098
11395
  var connectWithRecovery = async () => {
11099
11396
  try {
11100
11397
  await connectPm2();
11398
+ return loadPm2();
11101
11399
  } catch (error) {
11102
11400
  if (isPm2BrokenStateError(error)) {
11103
11401
  console.warn("PM2 daemon is in a stale state. Killing and reconnecting...");
@@ -11108,26 +11406,27 @@ var connectWithRecovery = async () => {
11108
11406
  await sleep(1e3);
11109
11407
  cachedPm2 = null;
11110
11408
  await connectPm2();
11409
+ return loadPm2();
11111
11410
  } else {
11112
11411
  throw error;
11113
11412
  }
11114
11413
  }
11115
11414
  };
11116
11415
  var withPm2Connection = async (operation) => {
11117
- await connectWithRecovery();
11416
+ const pm2 = await connectWithRecovery();
11118
11417
  try {
11119
- return await operation();
11418
+ return await operation(pm2);
11120
11419
  } finally {
11121
11420
  await disconnectPm2();
11122
11421
  }
11123
11422
  };
11124
- var waitForRuntimeReady = async (waitMs, logOffsets) => {
11423
+ var waitForRuntimeReady = async (pm2, waitMs, logOffsets) => {
11125
11424
  const deadline = Date.now() + waitMs;
11126
11425
  while (Date.now() <= deadline) {
11127
11426
  if (readRuntimeConfig()) {
11128
11427
  return;
11129
11428
  }
11130
- const processes = await describeManagedServer();
11429
+ const processes = await describeManagedServer(pm2);
11131
11430
  const process2 = processes[0];
11132
11431
  if (!process2) {
11133
11432
  throw createStartupError(
@@ -11147,14 +11446,39 @@ var waitForRuntimeReady = async (waitMs, logOffsets) => {
11147
11446
  }
11148
11447
  throw createStartupError(`runtime readiness timed out after ${waitMs}ms`, logOffsets);
11149
11448
  };
11150
- var waitForManagedServerExit = async () => {
11151
- while (true) {
11152
- const processes = await describeManagedServer();
11449
+ var waitForManagedServerDeletion = async (pm2, waitMs) => {
11450
+ const deadline = Date.now() + waitMs;
11451
+ while (Date.now() <= deadline) {
11452
+ const processes = await describeManagedServer(pm2);
11153
11453
  if (processes.length === 0) {
11154
11454
  return;
11155
11455
  }
11156
- await sleep(STARTUP_POLL_INTERVAL_MS);
11456
+ const remainingMs = deadline - Date.now();
11457
+ if (remainingMs <= 0) {
11458
+ break;
11459
+ }
11460
+ await sleep(Math.min(STARTUP_POLL_INTERVAL_MS, remainingMs));
11157
11461
  }
11462
+ throw new Error(`Timed out waiting for the managed server to stop after ${waitMs}ms.`);
11463
+ };
11464
+ var deleteManagedServerInSession = async (pm2, {
11465
+ ignoreMissing = false
11466
+ } = {}) => {
11467
+ const processes = await describeManagedServer(pm2);
11468
+ if (processes.length === 0) {
11469
+ return false;
11470
+ }
11471
+ try {
11472
+ await removeManagedServer(pm2);
11473
+ } catch (error) {
11474
+ if (ignoreMissing && isMissingManagedServerError(error)) {
11475
+ await waitForManagedServerDeletion(pm2, PM2_DELETE_WAIT_MS);
11476
+ return false;
11477
+ }
11478
+ throw error;
11479
+ }
11480
+ await waitForManagedServerDeletion(pm2, PM2_DELETE_WAIT_MS);
11481
+ return true;
11158
11482
  };
11159
11483
  var ensureLogDirectory = () => {
11160
11484
  mkdirSync5(join9(homedir6(), ".coder-studio", "logs"), { recursive: true });
@@ -11196,69 +11520,51 @@ var createStartupError = (reason, offsets) => {
11196
11520
  };
11197
11521
  var deleteManagedServer = async ({
11198
11522
  ignoreMissing = false
11199
- } = {}) => withPm2Connection(async () => {
11200
- const processes = await describeManagedServer();
11201
- if (processes.length === 0) {
11202
- return false;
11203
- }
11204
- try {
11205
- await removeManagedServer();
11206
- return true;
11207
- } catch (error) {
11208
- if (ignoreMissing && isMissingManagedServerError(error)) {
11209
- return false;
11210
- }
11211
- throw error;
11212
- }
11213
- });
11523
+ } = {}) => withPm2Connection((pm2) => deleteManagedServerInSession(pm2, { ignoreMissing }));
11214
11524
  var startManagedServer = async ({
11215
11525
  script,
11216
11526
  cwd,
11217
11527
  waitMs,
11218
11528
  args
11219
- }) => {
11220
- await deleteManagedServer({ ignoreMissing: true });
11221
- await withPm2Connection(waitForManagedServerExit);
11529
+ }) => withPm2Connection(async (pm2) => {
11530
+ await deleteManagedServerInSession(pm2, { ignoreMissing: true });
11222
11531
  if (readRuntimeConfig()) {
11223
11532
  deleteRuntimeConfig();
11224
11533
  }
11225
11534
  ensureLogDirectory();
11226
11535
  const { outFile, errFile } = getLogPaths();
11227
- const pm2 = await loadPm2();
11228
- await withPm2Connection(async () => {
11229
- const logOffsets = captureStartupLogOffsets();
11230
- await new Promise((resolve3, reject) => {
11231
- pm2.start(
11232
- {
11233
- name: MANAGED_SERVER_NAME,
11234
- script,
11235
- cwd,
11236
- ...args !== void 0 ? { args } : {},
11237
- env: {
11238
- ...process.env,
11239
- NODE_ENV: "production"
11240
- },
11241
- autorestart: true,
11242
- restart_delay: PM2_RESTART_DELAY_MS,
11243
- min_uptime: PM2_MIN_UPTIME,
11244
- max_restarts: PM2_MAX_RESTARTS,
11245
- out_file: outFile,
11246
- error_file: errFile
11536
+ const logOffsets = captureStartupLogOffsets();
11537
+ await new Promise((resolve4, reject) => {
11538
+ pm2.start(
11539
+ {
11540
+ name: MANAGED_SERVER_NAME,
11541
+ script,
11542
+ cwd,
11543
+ ...args !== void 0 ? { args } : {},
11544
+ env: {
11545
+ ...process.env,
11546
+ NODE_ENV: "production"
11247
11547
  },
11248
- (error) => {
11249
- if (error) {
11250
- reject(error);
11251
- return;
11252
- }
11253
- resolve3();
11548
+ autorestart: true,
11549
+ restart_delay: PM2_RESTART_DELAY_MS,
11550
+ min_uptime: PM2_MIN_UPTIME,
11551
+ max_restarts: PM2_MAX_RESTARTS,
11552
+ out_file: outFile,
11553
+ error_file: errFile
11554
+ },
11555
+ (error) => {
11556
+ if (error) {
11557
+ reject(error);
11558
+ return;
11254
11559
  }
11255
- );
11256
- });
11257
- await waitForRuntimeReady(waitMs, logOffsets);
11560
+ resolve4();
11561
+ }
11562
+ );
11258
11563
  });
11259
- };
11260
- var getManagedServerStatus = async () => withPm2Connection(async () => {
11261
- const processes = await describeManagedServer();
11564
+ await waitForRuntimeReady(pm2, waitMs, logOffsets);
11565
+ });
11566
+ var getManagedServerStatus = async () => withPm2Connection(async (pm2) => {
11567
+ const processes = await describeManagedServer(pm2);
11262
11568
  const process2 = processes[0];
11263
11569
  if (!process2) {
11264
11570
  return {
@@ -11291,6 +11597,13 @@ var getManagedServerStatus = async () => withPm2Connection(async () => {
11291
11597
  restartCount
11292
11598
  };
11293
11599
  }
11600
+ if (pm2Pid === null || pm2Pid === 0) {
11601
+ return {
11602
+ status: "stopped",
11603
+ pm2Pid: null,
11604
+ restartCount
11605
+ };
11606
+ }
11294
11607
  return {
11295
11608
  status: "errored",
11296
11609
  pm2Pid,
@@ -11327,7 +11640,7 @@ async function getServerStatus() {
11327
11640
  const managedStatus = await getManagedServerStatus();
11328
11641
  const runtime = readRuntimeConfig();
11329
11642
  const { outFile, errFile } = getLogPaths();
11330
- if (managedStatus.status === "stopped") {
11643
+ if (managedStatus.status === "stopped" || managedStatus.pm2Pid === null && runtime === null) {
11331
11644
  if (runtime) {
11332
11645
  deleteRuntimeConfig();
11333
11646
  }
@@ -11358,17 +11671,17 @@ async function getServerStatus() {
11358
11671
  import { fileURLToPath as fileURLToPath2 } from "url";
11359
11672
 
11360
11673
  // packages/cli/src/embed.ts
11361
- import { existsSync as existsSync8 } from "fs";
11362
- import { dirname as dirname5, resolve as resolve2 } from "path";
11674
+ import { existsSync as existsSync10 } from "fs";
11675
+ import { dirname as dirname5, resolve as resolve3 } from "path";
11363
11676
  import { fileURLToPath } from "url";
11364
11677
  var __filename = fileURLToPath(import.meta.url);
11365
11678
  var __dirname = dirname5(__filename);
11366
- var WEB_ASSETS_DIR = resolve2(__dirname, "../web");
11679
+ var WEB_ASSETS_DIR = resolve3(__dirname, "../web");
11367
11680
  function getStaticAssetsDir() {
11368
11681
  return WEB_ASSETS_DIR;
11369
11682
  }
11370
11683
  function hasWebAssets() {
11371
- return existsSync8(WEB_ASSETS_DIR);
11684
+ return existsSync10(WEB_ASSETS_DIR);
11372
11685
  }
11373
11686
 
11374
11687
  // packages/cli/src/server-runner.ts
@@ -11376,6 +11689,7 @@ var MISSING_WEB_ASSETS_WARNING = "Warning: Web assets not found. Frontend will n
11376
11689
  var buildServerConfig = () => {
11377
11690
  const savedConfig = readCliConfig();
11378
11691
  const config = {
11692
+ appVersion: getCliVersion(import.meta.url),
11379
11693
  ...savedConfig?.host !== void 0 ? { host: savedConfig.host } : {},
11380
11694
  ...savedConfig?.port !== void 0 && savedConfig.port > 0 ? { port: savedConfig.port } : {},
11381
11695
  ...savedConfig?.dataDir !== void 0 ? { dataDir: savedConfig.dataDir } : {},
@@ -11418,7 +11732,7 @@ var runServerEntrypoint = async (moduleUrl, argvEntry) => {
11418
11732
  };
11419
11733
  var startServer = async () => {
11420
11734
  assertSupportedNodeVersion();
11421
- const { createServer: createServer2 } = await init_src3().then(() => src_exports);
11735
+ const { createServer: createServer2 } = await init_src4().then(() => src_exports);
11422
11736
  const server = await createServer2(buildServerConfig());
11423
11737
  const shutdown = createShutdownHandler(server);
11424
11738
  process.on("SIGINT", shutdown);
@@ -11480,8 +11794,8 @@ function showLogs(status, {
11480
11794
  errorsOnly = false
11481
11795
  } = {}) {
11482
11796
  const paths = errorsOnly ? [status.errFile] : [status.outFile, status.errFile];
11483
- const contents = paths.filter((path8, index, paths2) => paths2.indexOf(path8) === index).flatMap((path8) => {
11484
- const content = readLogExcerpt(path8, { maxLines: tail, maxChars: null });
11797
+ const contents = paths.filter((path9, index, paths2) => paths2.indexOf(path9) === index).flatMap((path9) => {
11798
+ const content = readLogExcerpt(path9, { maxLines: tail, maxChars: null });
11485
11799
  return content ? [content] : [];
11486
11800
  });
11487
11801
  console.log(contents.length === 0 ? "No logs available." : contents.join("\n"));
@@ -11559,16 +11873,7 @@ EXAMPLES:
11559
11873
  `);
11560
11874
  }
11561
11875
  function showVersion() {
11562
- const manifestPath = [
11563
- new URL("../package.json", import.meta.url),
11564
- new URL("../../package.json", import.meta.url)
11565
- ].find((candidate) => existsSync9(candidate));
11566
- if (!manifestPath) {
11567
- throw new Error("Unable to locate CLI package.json");
11568
- }
11569
- const manifest = JSON.parse(readFileSync7(manifestPath, "utf-8"));
11570
- const version = manifest.version ?? "0.0.0";
11571
- console.log(`@spencer-kit/coder-studio v${version}`);
11876
+ console.log(`@spencer-kit/coder-studio v${getCliVersion(import.meta.url)}`);
11572
11877
  }
11573
11878
  function formatAuthBlocks(blocks) {
11574
11879
  if (blocks.length === 0) {
@@ -11584,7 +11889,7 @@ function resolveManagedScriptPath() {
11584
11889
  join10(currentDir, "server-runner.mjs"),
11585
11890
  join10(currentDir, "../src/server-runner.ts")
11586
11891
  ];
11587
- const scriptPath = candidates.find((candidate) => existsSync9(candidate));
11892
+ const scriptPath = candidates.find((candidate) => existsSync11(candidate));
11588
11893
  if (!scriptPath) {
11589
11894
  throw new Error("Unable to locate the managed server entry script");
11590
11895
  }