@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.
@@ -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";
@@ -365,7 +385,7 @@ var init_config = __esm({
365
385
  });
366
386
 
367
387
  // packages/core/src/runtime.ts
368
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
388
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
369
389
  import { homedir as homedir2 } from "node:os";
370
390
  import { dirname as dirname2, join as join2 } from "node:path";
371
391
  function getRuntimeDir() {
@@ -385,14 +405,14 @@ function getRuntimePath() {
385
405
  function writeRuntimeConfig(config) {
386
406
  const runtimePath = getRuntimePath();
387
407
  const runtimeDir = dirname2(runtimePath);
388
- if (!existsSync3(runtimeDir)) {
408
+ if (!existsSync4(runtimeDir)) {
389
409
  mkdirSync2(runtimeDir, { recursive: true });
390
410
  }
391
411
  writeFileSync2(runtimePath, JSON.stringify(config, null, 2), "utf-8");
392
412
  }
393
413
  function deleteRuntimeConfig() {
394
414
  const runtimePath = getRuntimePath();
395
- if (existsSync3(runtimePath)) {
415
+ if (existsSync4(runtimePath)) {
396
416
  unlinkSync(runtimePath);
397
417
  }
398
418
  }
@@ -765,6 +785,190 @@ var init_src = __esm({
765
785
  }
766
786
  });
767
787
 
788
+ // packages/utils/src/direct-execution.ts
789
+ import { posix, resolve as resolve2 } from "node:path";
790
+ function isWindowsDrivePath(path9) {
791
+ return /^[A-Za-z]:\//.test(path9);
792
+ }
793
+ function normalizeComparablePath(path9) {
794
+ let normalized = path9.replace(/\\/g, "/");
795
+ if (/^\/[A-Za-z]:\//.test(normalized)) {
796
+ normalized = normalized.slice(1);
797
+ }
798
+ if (normalized.startsWith("//")) {
799
+ normalized = `//${posix.normalize(normalized.slice(2))}`;
800
+ } else {
801
+ normalized = posix.normalize(normalized);
802
+ }
803
+ if (isWindowsDrivePath(normalized) || normalized.startsWith("//")) {
804
+ normalized = normalized.toLowerCase();
805
+ }
806
+ return normalized;
807
+ }
808
+ function normalizeModuleUrlPath(moduleUrl) {
809
+ let url;
810
+ try {
811
+ url = new URL(moduleUrl);
812
+ } catch {
813
+ return null;
814
+ }
815
+ if (url.protocol !== "file:") {
816
+ return null;
817
+ }
818
+ const path9 = `${url.host ? `//${url.host}` : ""}${decodeURIComponent(url.pathname)}`;
819
+ return normalizeComparablePath(path9);
820
+ }
821
+ function normalizeArgvPath(argv1) {
822
+ const isAbsoluteWindowsPath = /^[A-Za-z]:[\\/]/.test(argv1) || /^\\\\/.test(argv1);
823
+ return normalizeComparablePath(isAbsoluteWindowsPath ? argv1 : resolve2(argv1));
824
+ }
825
+ function isDirectExecution(moduleUrl, argv1 = process.argv[1]) {
826
+ if (argv1 === void 0) {
827
+ return false;
828
+ }
829
+ const modulePath = normalizeModuleUrlPath(moduleUrl);
830
+ if (modulePath === null) {
831
+ return false;
832
+ }
833
+ return modulePath === normalizeArgvPath(argv1);
834
+ }
835
+ var init_direct_execution = __esm({
836
+ "packages/utils/src/direct-execution.ts"() {
837
+ "use strict";
838
+ }
839
+ });
840
+
841
+ // packages/utils/src/windows-shim.ts
842
+ function shouldUseShellForCommand(command, platform = process.platform) {
843
+ return platform === "win32" && WINDOWS_CMD_SHIMS.has(command.toLowerCase());
844
+ }
845
+ var WINDOWS_CMD_SHIMS;
846
+ var init_windows_shim = __esm({
847
+ "packages/utils/src/windows-shim.ts"() {
848
+ "use strict";
849
+ WINDOWS_CMD_SHIMS = /* @__PURE__ */ new Set(["pnpm", "npm", "npx"]);
850
+ }
851
+ });
852
+
853
+ // packages/utils/src/windows-shim-resolver.ts
854
+ import * as fs2 from "node:fs";
855
+ import * as path2 from "node:path";
856
+ function resolveSpawnArgv(argv, deps = {}) {
857
+ const platform = deps.platform ?? process.platform;
858
+ if (platform !== "win32") {
859
+ return [...argv];
860
+ }
861
+ const command = argv[0];
862
+ if (command === void 0) {
863
+ return [];
864
+ }
865
+ const restArgs = argv.slice(1);
866
+ const readFileSync9 = deps.readFileSync ?? ((file) => fs2.readFileSync(file, "utf8"));
867
+ const existsSync10 = deps.existsSync ?? fs2.existsSync;
868
+ const pathEnv = deps.pathEnv ?? process.env.Path ?? process.env.PATH ?? "";
869
+ const pathExt = deps.pathExt ?? process.env.PATHEXT ?? DEFAULT_PATHEXT;
870
+ const resolved = resolveExecutablePath(command, pathEnv, pathExt, existsSync10);
871
+ if (!resolved) {
872
+ return [...argv];
873
+ }
874
+ const ext = path2.win32.extname(resolved).toLowerCase();
875
+ if (ext === ".exe" || ext === ".com") {
876
+ return [resolved, ...restArgs];
877
+ }
878
+ if (ext === ".cmd" || ext === ".bat") {
879
+ let content;
880
+ try {
881
+ content = readFileSync9(resolved);
882
+ } catch {
883
+ return ["cmd.exe", "/d", "/s", "/c", resolved, ...restArgs];
884
+ }
885
+ const parsed = parseCmdShim(resolved, content);
886
+ if (parsed) {
887
+ return [parsed.node, parsed.entry, ...restArgs];
888
+ }
889
+ return ["cmd.exe", "/d", "/s", "/c", resolved, ...restArgs];
890
+ }
891
+ return [...argv];
892
+ }
893
+ function parseCmdShim(shimPath, content) {
894
+ const dp0Dir = path2.win32.dirname(shimPath);
895
+ const entryMatch = content.match(/"([^"\r\n]+\.js)"\s+%\*/i);
896
+ const entryRaw = entryMatch?.[1];
897
+ if (!entryRaw) {
898
+ return null;
899
+ }
900
+ const entry = expandShimVars(entryRaw, dp0Dir);
901
+ const nodeMatch = content.match(/"([^"\r\n]*node\.exe)"/i) ?? content.match(/"([^"\r\n]*node)"/i);
902
+ const nodeRaw = nodeMatch?.[1];
903
+ const node = nodeRaw ? expandShimVars(nodeRaw, dp0Dir) : "node";
904
+ return { node, entry };
905
+ }
906
+ function expandShimVars(value, dp0Dir) {
907
+ const dp0WithSlash = dp0Dir.endsWith("\\") ? dp0Dir : `${dp0Dir}\\`;
908
+ let expanded = value;
909
+ expanded = expanded.replace(/%~dp0/gi, dp0WithSlash);
910
+ expanded = expanded.replace(/%dp0%/gi, dp0WithSlash);
911
+ if (path2.win32.isAbsolute(expanded)) {
912
+ return path2.win32.normalize(expanded);
913
+ }
914
+ return path2.win32.resolve(dp0Dir, expanded);
915
+ }
916
+ function parsePathExt(pathExt) {
917
+ return pathExt.split(";").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
918
+ }
919
+ function resolveExecutablePath(command, pathEnv, pathExt, existsSync10) {
920
+ const hasExt = path2.win32.extname(command).length > 0;
921
+ const extensions = parsePathExt(pathExt);
922
+ if (path2.win32.isAbsolute(command)) {
923
+ if (existsSync10(command)) {
924
+ return command;
925
+ }
926
+ if (!hasExt) {
927
+ for (const ext of extensions) {
928
+ const candidate = command + ext;
929
+ if (existsSync10(candidate)) {
930
+ return candidate;
931
+ }
932
+ }
933
+ }
934
+ return null;
935
+ }
936
+ const dirs = pathEnv.split(";").map((dir) => dir.trim()).filter((dir) => dir.length > 0);
937
+ for (const dir of dirs) {
938
+ if (hasExt) {
939
+ const candidate = path2.win32.join(dir, command);
940
+ if (existsSync10(candidate)) {
941
+ return candidate;
942
+ }
943
+ continue;
944
+ }
945
+ for (const ext of extensions) {
946
+ const candidate = path2.win32.join(dir, command + ext);
947
+ if (existsSync10(candidate)) {
948
+ return candidate;
949
+ }
950
+ }
951
+ }
952
+ return null;
953
+ }
954
+ var DEFAULT_PATHEXT;
955
+ var init_windows_shim_resolver = __esm({
956
+ "packages/utils/src/windows-shim-resolver.ts"() {
957
+ "use strict";
958
+ DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC";
959
+ }
960
+ });
961
+
962
+ // packages/utils/src/index.ts
963
+ var init_src2 = __esm({
964
+ "packages/utils/src/index.ts"() {
965
+ "use strict";
966
+ init_direct_execution();
967
+ init_windows_shim();
968
+ init_windows_shim_resolver();
969
+ }
970
+ });
971
+
768
972
  // packages/server/src/fs/image.ts
769
973
  import { extname } from "path";
770
974
  function getImageTypeInfo(filePath) {
@@ -791,10 +995,10 @@ var init_image = __esm({
791
995
  // packages/server/src/fs/file-io.ts
792
996
  import { createHash } from "crypto";
793
997
  import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir, rm, stat } from "fs/promises";
794
- import { dirname as dirname3, resolve as resolve2 } from "path";
795
- async function statSafe(path8) {
998
+ import { dirname as dirname3, resolve as resolve3 } from "path";
999
+ async function statSafe(path9) {
796
1000
  try {
797
- return await stat(path8);
1001
+ return await stat(path9);
798
1002
  } catch {
799
1003
  return null;
800
1004
  }
@@ -825,8 +1029,8 @@ async function deleteEntry(rootPath, relPath) {
825
1029
  await rm(abs, { recursive: true });
826
1030
  }
827
1031
  function resolveSafe(root, relPath) {
828
- const absRoot = resolve2(root);
829
- const abs = resolve2(absRoot, relPath);
1032
+ const absRoot = resolve3(root);
1033
+ const abs = resolve3(absRoot, relPath);
830
1034
  if (!abs.startsWith(absRoot + "/") && abs !== absRoot) {
831
1035
  throw { code: "path_escape", message: "Path escapes workspace root" };
832
1036
  }
@@ -964,7 +1168,7 @@ var init_constants = __esm({
964
1168
  // packages/server/src/uploads/paths.ts
965
1169
  import { randomUUID } from "node:crypto";
966
1170
  import { lstat, mkdir as mkdir2 } from "node:fs/promises";
967
- import path2 from "node:path";
1171
+ import path3 from "node:path";
968
1172
  function sanitizeOriginalName(input) {
969
1173
  let sanitized = "";
970
1174
  for (const char of input.trim()) {
@@ -1000,9 +1204,9 @@ async function assertDirectorySegmentSafe(segmentPath) {
1000
1204
  }
1001
1205
  }
1002
1206
  async function ensureSafeUploadDir(rootDir, targetDir) {
1003
- const resolvedRoot = path2.resolve(rootDir);
1004
- const resolvedTarget = path2.resolve(targetDir);
1005
- if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path2.sep}`)) {
1207
+ const resolvedRoot = path3.resolve(rootDir);
1208
+ const resolvedTarget = path3.resolve(targetDir);
1209
+ if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path3.sep}`)) {
1006
1210
  throw new Error(`target dir escaped uploads root: ${resolvedTarget}`);
1007
1211
  }
1008
1212
  try {
@@ -1015,13 +1219,13 @@ async function ensureSafeUploadDir(rootDir, targetDir) {
1015
1219
  await mkdir2(resolvedRoot, { recursive: true });
1016
1220
  await assertDirectorySegmentSafe(resolvedRoot);
1017
1221
  }
1018
- const relative3 = path2.relative(resolvedRoot, resolvedTarget);
1222
+ const relative3 = path3.relative(resolvedRoot, resolvedTarget);
1019
1223
  if (!relative3) {
1020
1224
  return;
1021
1225
  }
1022
1226
  let current = resolvedRoot;
1023
- for (const segment of relative3.split(path2.sep)) {
1024
- current = path2.join(current, segment);
1227
+ for (const segment of relative3.split(path3.sep)) {
1228
+ current = path3.join(current, segment);
1025
1229
  try {
1026
1230
  await assertDirectorySegmentSafe(current);
1027
1231
  continue;
@@ -1046,11 +1250,11 @@ function generateBucketPath(input) {
1046
1250
  validateWorkspaceId(input.workspaceId);
1047
1251
  const now = input.now ?? /* @__PURE__ */ new Date();
1048
1252
  const dateStr = now.toISOString().slice(0, 10);
1049
- const dir = path2.join(input.uploadsDir, input.workspaceId, dateStr);
1253
+ const dir = path3.join(input.uploadsDir, input.workspaceId, dateStr);
1050
1254
  const sanitizedName = sanitizeOriginalName(input.originalName);
1051
1255
  const uuid8 = randomUUID().replace(/-/g, "").slice(0, 8);
1052
- const absolutePath = path2.resolve(dir, `${uuid8}-${sanitizedName}`);
1053
- const uploadsRoot = `${path2.resolve(input.uploadsDir)}${path2.sep}`;
1256
+ const absolutePath = path3.resolve(dir, `${uuid8}-${sanitizedName}`);
1257
+ const uploadsRoot = `${path3.resolve(input.uploadsDir)}${path3.sep}`;
1054
1258
  if (!absolutePath.startsWith(uploadsRoot)) {
1055
1259
  throw new Error(`generated upload path escaped uploads root: ${absolutePath}`);
1056
1260
  }
@@ -1073,7 +1277,7 @@ var init_paths = __esm({
1073
1277
 
1074
1278
  // packages/server/src/uploads/cleanup.ts
1075
1279
  import { readdir, rm as rm2, rmdir, stat as stat3, unlink } from "node:fs/promises";
1076
- import path3 from "node:path";
1280
+ import path4 from "node:path";
1077
1281
  async function listFilesRecursive(root) {
1078
1282
  let entries;
1079
1283
  try {
@@ -1086,7 +1290,7 @@ async function listFilesRecursive(root) {
1086
1290
  }
1087
1291
  const files = [];
1088
1292
  for (const entry of entries) {
1089
- const childPath = path3.join(root, entry.name);
1293
+ const childPath = path4.join(root, entry.name);
1090
1294
  if (entry.isDirectory()) {
1091
1295
  files.push(...await listFilesRecursive(childPath));
1092
1296
  continue;
@@ -1117,7 +1321,7 @@ async function pruneEmptyDirectories(root) {
1117
1321
  if (!entry.isDirectory()) {
1118
1322
  continue;
1119
1323
  }
1120
- await pruneEmptyDirectories(path3.join(root, entry.name));
1324
+ await pruneEmptyDirectories(path4.join(root, entry.name));
1121
1325
  }
1122
1326
  const remainingEntries = await readdir(root).catch(() => []);
1123
1327
  if (remainingEntries.length === 0) {
@@ -1126,12 +1330,12 @@ async function pruneEmptyDirectories(root) {
1126
1330
  }
1127
1331
  async function deleteWorkspaceUploads(uploadsDir, workspaceId) {
1128
1332
  validateWorkspaceId(workspaceId);
1129
- const bucket = path3.join(uploadsDir, workspaceId);
1333
+ const bucket = path4.join(uploadsDir, workspaceId);
1130
1334
  await rm2(bucket, { recursive: true, force: true });
1131
1335
  }
1132
1336
  async function enforceBucketCap(uploadsDir, workspaceId, capBytes, logger) {
1133
1337
  validateWorkspaceId(workspaceId);
1134
- const bucket = path3.join(uploadsDir, workspaceId);
1338
+ const bucket = path4.join(uploadsDir, workspaceId);
1135
1339
  const files = await listFilesRecursive(bucket);
1136
1340
  const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
1137
1341
  if (totalBytes <= capBytes) {
@@ -1171,7 +1375,7 @@ async function runStartupGc(uploadsDir, logger) {
1171
1375
  if (!workspaceEntry.isDirectory()) {
1172
1376
  continue;
1173
1377
  }
1174
- const workspaceDir = path3.join(uploadsDir, workspaceEntry.name);
1378
+ const workspaceDir = path4.join(uploadsDir, workspaceEntry.name);
1175
1379
  const dateEntries = await readdir(workspaceDir, { withFileTypes: true }).catch(
1176
1380
  () => []
1177
1381
  );
@@ -1179,7 +1383,7 @@ async function runStartupGc(uploadsDir, logger) {
1179
1383
  if (!dateEntry.isDirectory()) {
1180
1384
  continue;
1181
1385
  }
1182
- const dateDir = path3.join(workspaceDir, dateEntry.name);
1386
+ const dateDir = path4.join(workspaceDir, dateEntry.name);
1183
1387
  const files = await listFilesRecursive(dateDir);
1184
1388
  for (const file of files) {
1185
1389
  if (file.mtimeMs >= cutoffMs) {
@@ -1570,7 +1774,7 @@ var init_app = __esm({
1570
1774
  });
1571
1775
 
1572
1776
  // packages/server/src/config/codex-config-audit.ts
1573
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
1777
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
1574
1778
  import { homedir as homedir3 } from "node:os";
1575
1779
  import { dirname as dirname4, join as join3 } from "node:path";
1576
1780
  function resolveCodexConfigPath() {
@@ -1581,15 +1785,15 @@ function resolveCodexConfigPath() {
1581
1785
  return join3(homedir3(), ".codex", "config.toml");
1582
1786
  }
1583
1787
  function auditCodexConfigToml(configPath) {
1584
- const path8 = configPath ?? resolveCodexConfigPath();
1585
- if (!existsSync4(path8)) {
1586
- return { configPath: path8, exists: false, findings: [] };
1788
+ const path9 = configPath ?? resolveCodexConfigPath();
1789
+ if (!existsSync6(path9)) {
1790
+ return { configPath: path9, exists: false, findings: [] };
1587
1791
  }
1588
1792
  let content;
1589
1793
  try {
1590
- content = readFileSync3(path8, "utf-8");
1794
+ content = readFileSync5(path9, "utf-8");
1591
1795
  } catch {
1592
- return { configPath: path8, exists: false, findings: [] };
1796
+ return { configPath: path9, exists: false, findings: [] };
1593
1797
  }
1594
1798
  const lines = content.split(/\r?\n/);
1595
1799
  const findings = [];
@@ -1597,13 +1801,13 @@ function auditCodexConfigToml(configPath) {
1597
1801
  if (notifyFinding) findings.push(notifyFinding);
1598
1802
  const codexHooksFinding = detectCodexHooksFlag(lines);
1599
1803
  if (codexHooksFinding) findings.push(codexHooksFinding);
1600
- return { configPath: path8, exists: true, findings };
1804
+ return { configPath: path9, exists: true, findings };
1601
1805
  }
1602
1806
  function cleanupCodexConfigToml(configPath, opts) {
1603
1807
  if (opts.removeIds.length === 0) {
1604
1808
  return { removed: [], backupPath: null, noop: true };
1605
1809
  }
1606
- if (!existsSync4(configPath)) {
1810
+ if (!existsSync6(configPath)) {
1607
1811
  return { removed: [], backupPath: null, noop: true };
1608
1812
  }
1609
1813
  const audit = auditCodexConfigToml(configPath);
@@ -1611,7 +1815,7 @@ function cleanupCodexConfigToml(configPath, opts) {
1611
1815
  if (selected.length === 0) {
1612
1816
  return { removed: [], backupPath: null, noop: true };
1613
1817
  }
1614
- const original = readFileSync3(configPath, "utf-8");
1818
+ const original = readFileSync5(configPath, "utf-8");
1615
1819
  const backupPath = writeBackup(configPath, original, opts.backupDir);
1616
1820
  const linesToDrop = /* @__PURE__ */ new Set();
1617
1821
  for (const finding of selected) {
@@ -1713,7 +1917,7 @@ function countBrackets(s) {
1713
1917
  }
1714
1918
  function writeBackup(configPath, original, backupDir) {
1715
1919
  const dir = backupDir ?? dirname4(configPath);
1716
- if (!existsSync4(dir)) {
1920
+ if (!existsSync6(dir)) {
1717
1921
  mkdirSync3(dir, { recursive: true });
1718
1922
  }
1719
1923
  const ts = formatTimestamp(/* @__PURE__ */ new Date());
@@ -1754,35 +1958,78 @@ var init_codex_config_audit = __esm({
1754
1958
  }
1755
1959
  });
1756
1960
 
1961
+ // packages/server/src/provider-runtime/command-runner.ts
1962
+ import { spawn } from "node:child_process";
1963
+ async function runCommandAsString(file, args, options) {
1964
+ return new Promise((resolve4, reject) => {
1965
+ const child = spawn(file, args, {
1966
+ shell: shouldUseShellForCommand(file, process.platform),
1967
+ windowsHide: options?.windowsHide ?? true
1968
+ });
1969
+ const stdoutChunks = [];
1970
+ const stderrChunks = [];
1971
+ child.stdout?.on("data", (chunk) => {
1972
+ stdoutChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1973
+ });
1974
+ child.stderr?.on("data", (chunk) => {
1975
+ stderrChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1976
+ });
1977
+ child.on("error", (error) => {
1978
+ reject(
1979
+ Object.assign(error, {
1980
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
1981
+ stderr: Buffer.concat(stderrChunks).toString("utf8")
1982
+ })
1983
+ );
1984
+ });
1985
+ child.on("close", (code) => {
1986
+ const stdout = Buffer.concat(stdoutChunks).toString("utf8");
1987
+ const stderr = Buffer.concat(stderrChunks).toString("utf8");
1988
+ if (code === 0) {
1989
+ resolve4({ stdout, stderr });
1990
+ return;
1991
+ }
1992
+ reject(
1993
+ Object.assign(new Error(`Command failed with exit code ${code ?? "unknown"}`), {
1994
+ exitCode: code ?? void 0,
1995
+ stdout,
1996
+ stderr
1997
+ })
1998
+ );
1999
+ });
2000
+ });
2001
+ }
2002
+ var init_command_runner = __esm({
2003
+ "packages/server/src/provider-runtime/command-runner.ts"() {
2004
+ "use strict";
2005
+ init_src2();
2006
+ }
2007
+ });
2008
+
1757
2009
  // packages/server/src/provider-runtime/command-check.ts
1758
- import { execFile as nodeExecFile } from "node:child_process";
1759
- import { promisify } from "node:util";
1760
2010
  function getCommandLookupExecutable(platform) {
1761
2011
  return platform === "win32" ? "where" : "which";
1762
2012
  }
1763
2013
  async function checkCommandAvailable(command, deps = {}) {
1764
2014
  const platform = deps.platform ?? process.platform;
1765
- const execFile2 = deps.execFile ?? ((file, args) => execFileAsync(file, args));
2015
+ const runCommand2 = deps.runCommand ?? runCommandAsString;
1766
2016
  const lookup = getCommandLookupExecutable(platform);
1767
2017
  try {
1768
- await execFile2(lookup, [command]);
1769
- return true;
2018
+ const { stdout } = await runCommand2(lookup, [command], { windowsHide: true });
2019
+ return stdout.trim().length > 0;
1770
2020
  } catch {
1771
2021
  return false;
1772
2022
  }
1773
2023
  }
1774
- var execFileAsync;
1775
2024
  var init_command_check = __esm({
1776
2025
  "packages/server/src/provider-runtime/command-check.ts"() {
1777
2026
  "use strict";
1778
- execFileAsync = promisify(nodeExecFile);
2027
+ init_command_runner();
1779
2028
  }
1780
2029
  });
1781
2030
 
1782
2031
  // packages/server/src/provider-runtime/install-manager.ts
1783
- import { execFile as nodeExecFile2 } from "node:child_process";
1784
2032
  import { randomUUID as randomUUID2 } from "node:crypto";
1785
- import { promisify as promisify2 } from "node:util";
1786
2033
  function getErrorDetails(error) {
1787
2034
  if (error instanceof Error) {
1788
2035
  const record = error;
@@ -1857,12 +2104,12 @@ function cloneFailure(failure) {
1857
2104
  }
1858
2105
  };
1859
2106
  }
1860
- var execFileAsync2, EXCERPT_LIMIT, ProviderInstallManager;
2107
+ var EXCERPT_LIMIT, ProviderInstallManager;
1861
2108
  var init_install_manager = __esm({
1862
2109
  "packages/server/src/provider-runtime/install-manager.ts"() {
1863
2110
  "use strict";
1864
2111
  init_command_check();
1865
- execFileAsync2 = promisify2(nodeExecFile2);
2112
+ init_command_runner();
1866
2113
  EXCERPT_LIMIT = 400;
1867
2114
  ProviderInstallManager = class {
1868
2115
  providers = /* @__PURE__ */ new Map();
@@ -2047,7 +2294,7 @@ var init_install_manager = __esm({
2047
2294
  };
2048
2295
  }
2049
2296
  async runPreparedJob(provider, job) {
2050
- const execFile2 = this.deps.execFile ?? ((file, args) => execFileAsync2(file, args));
2297
+ const runCommand2 = this.deps.runCommand ?? runCommandAsString;
2051
2298
  job.status = "running";
2052
2299
  this.jobs.set(job.jobId, job);
2053
2300
  for (const step of job.steps) {
@@ -2074,7 +2321,7 @@ var init_install_manager = __esm({
2074
2321
  return;
2075
2322
  }
2076
2323
  } else {
2077
- const result = await execFile2(step.command, step.args);
2324
+ const result = await runCommand2(step.command, step.args, { windowsHide: true });
2078
2325
  step.stdoutExcerpt = excerpt(result.stdout);
2079
2326
  step.stderrExcerpt = excerpt(result.stderr);
2080
2327
  }
@@ -2410,7 +2657,7 @@ var init_idle_heuristics2 = __esm({
2410
2657
  });
2411
2658
 
2412
2659
  // packages/core/src/index.ts
2413
- var init_src2 = __esm({
2660
+ var init_src3 = __esm({
2414
2661
  "packages/core/src/index.ts"() {
2415
2662
  "use strict";
2416
2663
  init_events();
@@ -2447,14 +2694,16 @@ var init_provider_config = __esm({
2447
2694
  SUPPORTED_PROVIDER_IDS = ["claude", "codex"];
2448
2695
  supportedProviderIds = new Set(SUPPORTED_PROVIDER_IDS);
2449
2696
  ProviderLaunchConfigInputSchema = z4.object({
2450
- additionalArgs: z4.array(z4.string()).optional()
2697
+ additionalArgs: z4.array(z4.string()).optional(),
2698
+ envVars: z4.record(z4.string(), z4.string()).optional()
2451
2699
  }).strict();
2452
2700
  ProviderSettingsSchema = z4.object({
2453
2701
  claude: ProviderLaunchConfigInputSchema.optional(),
2454
2702
  codex: ProviderLaunchConfigInputSchema.optional()
2455
2703
  }).strict();
2456
2704
  ProviderLaunchConfigSchema = z4.object({
2457
- additionalArgs: z4.array(z4.string()).default([])
2705
+ additionalArgs: z4.array(z4.string()).default([]),
2706
+ envVars: z4.record(z4.string(), z4.string()).optional()
2458
2707
  });
2459
2708
  }
2460
2709
  });
@@ -2720,7 +2969,7 @@ var NOOP_SESSION_LOGGER, SessionManager, ActiveSession;
2720
2969
  var init_manager = __esm({
2721
2970
  "packages/server/src/session/manager.ts"() {
2722
2971
  "use strict";
2723
- init_src2();
2972
+ init_src3();
2724
2973
  init_provider_config();
2725
2974
  init_session_repo();
2726
2975
  init_pty_state_detector();
@@ -3261,7 +3510,7 @@ var init_database = __esm({
3261
3510
 
3262
3511
  // packages/server/src/storage/db.ts
3263
3512
  import { DatabaseSync } from "node:sqlite";
3264
- import { readFileSync as readFileSync4 } from "fs";
3513
+ import { readFileSync as readFileSync6 } from "fs";
3265
3514
  import { join as join4 } from "path";
3266
3515
  function normalizeSql(sql) {
3267
3516
  return (sql ?? "").replace(/\s+/g, " ").trim();
@@ -3410,7 +3659,7 @@ var init_db = __esm({
3410
3659
  "use strict";
3411
3660
  init_database();
3412
3661
  SCHEMA_PATH = join4(import.meta.dirname, "migrations", "001_init.sql");
3413
- SCHEMA_SQL = readFileSync4(SCHEMA_PATH, "utf-8");
3662
+ SCHEMA_SQL = readFileSync6(SCHEMA_PATH, "utf-8");
3414
3663
  LEGACY_TABLES = ["hook_registrations", "_migrations"];
3415
3664
  LEGACY_SESSION_COLUMNS = ["resume_id", "transcript_path"];
3416
3665
  EXPECTED_SCHEMA_ENTRIES = buildExpectedSchemaEntries();
@@ -3960,11 +4209,11 @@ function parseOrdinaryChangedEntry(record, staged, modified, deleted) {
3960
4209
  if (!xy) {
3961
4210
  return;
3962
4211
  }
3963
- const path8 = parts.slice(8).join(" ");
3964
- if (!path8) {
4212
+ const path9 = parts.slice(8).join(" ");
4213
+ if (!path9) {
3965
4214
  return;
3966
4215
  }
3967
- pushChange({ path: path8 }, xy, staged, modified, deleted);
4216
+ pushChange({ path: path9 }, xy, staged, modified, deleted);
3968
4217
  }
3969
4218
  function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
3970
4219
  const parts = record.split(" ");
@@ -3976,12 +4225,12 @@ function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
3976
4225
  const pathAndMaybeOldPath = pathTokens.join(" ");
3977
4226
  const inlinePathParts = pathAndMaybeOldPath.split(" ");
3978
4227
  const fallbackPath = !oldPathRecord && inlinePathParts.length === 1 && pathTokens.length > 1 ? pathTokens.slice(0, -1).join(" ") : void 0;
3979
- const path8 = fallbackPath ?? inlinePathParts[0];
3980
- if (!path8) {
4228
+ const path9 = fallbackPath ?? inlinePathParts[0];
4229
+ if (!path9) {
3981
4230
  return;
3982
4231
  }
3983
4232
  const oldPath = (oldPathRecord && !oldPathRecord.startsWith("#") ? oldPathRecord : void 0) ?? inlinePathParts[1] ?? (pathTokens.length > 1 ? pathTokens[pathTokens.length - 1] : void 0);
3984
- pushChange({ path: path8, oldPath }, xy, staged, modified, deleted);
4233
+ pushChange({ path: path9, oldPath }, xy, staged, modified, deleted);
3985
4234
  }
3986
4235
  function pushChange(change, xy, staged, modified, deleted) {
3987
4236
  const indexStatus = xy[0];
@@ -4008,9 +4257,9 @@ var init_status_parser = __esm({
4008
4257
  import { execFile } from "child_process";
4009
4258
  import { mkdir as mkdir3, mkdtemp, rm as rm4, writeFile as writeFile3 } from "fs/promises";
4010
4259
  import os2 from "os";
4011
- import path4 from "path";
4260
+ import path5 from "path";
4012
4261
  async function runGit(cwd, args, options = {}) {
4013
- return new Promise((resolve3, reject) => {
4262
+ return new Promise((resolve4, reject) => {
4014
4263
  const gitArgs = [
4015
4264
  ...options.config?.flatMap(([key, value]) => ["-c", `${key}=${value}`]) ?? [],
4016
4265
  ...args
@@ -4030,13 +4279,14 @@ async function runGit(cwd, args, options = {}) {
4030
4279
  ...options.env
4031
4280
  },
4032
4281
  maxBuffer: 10 * 1024 * 1024,
4033
- timeout: options.timeoutMs
4282
+ timeout: options.timeoutMs,
4283
+ windowsHide: true
4034
4284
  },
4035
4285
  (err, stdout, stderr) => {
4036
4286
  if (err) {
4037
4287
  reject(new GitError(err.message, stderr));
4038
4288
  } else {
4039
- resolve3({ stdout, stderr });
4289
+ resolve4({ stdout, stderr });
4040
4290
  }
4041
4291
  }
4042
4292
  );
@@ -4085,12 +4335,12 @@ async function discardChanges(cwd, paths) {
4085
4335
  if (paths.length === 0) return;
4086
4336
  const trackedPaths = [];
4087
4337
  const untrackedPaths = [];
4088
- for (const path8 of paths) {
4338
+ for (const path9 of paths) {
4089
4339
  try {
4090
- await runGit(cwd, ["ls-files", "--error-unmatch", "--", path8]);
4091
- trackedPaths.push(path8);
4340
+ await runGit(cwd, ["ls-files", "--error-unmatch", "--", path9]);
4341
+ trackedPaths.push(path9);
4092
4342
  } catch {
4093
- untrackedPaths.push(path8);
4343
+ untrackedPaths.push(path9);
4094
4344
  }
4095
4345
  }
4096
4346
  if (trackedPaths.length > 0) {
@@ -4358,9 +4608,9 @@ async function prepareGitAuthExecution(auth, remoteMetadata) {
4358
4608
  }
4359
4609
  };
4360
4610
  }
4361
- const tempDir = await mkdtemp(path4.join(os2.tmpdir(), "coder-studio-git-auth-"));
4362
- const hooksDir = path4.join(tempDir, "hooks");
4363
- const askPassPath = path4.join(tempDir, "askpass.sh");
4611
+ const tempDir = await mkdtemp(path5.join(os2.tmpdir(), "coder-studio-git-auth-"));
4612
+ const hooksDir = path5.join(tempDir, "hooks");
4613
+ const askPassPath = path5.join(tempDir, "askpass.sh");
4364
4614
  await mkdir3(hooksDir, { recursive: true, mode: 448 });
4365
4615
  await writeFile3(
4366
4616
  askPassPath,
@@ -4714,31 +4964,31 @@ var init_context_builder = __esm({
4714
4964
  });
4715
4965
 
4716
4966
  // packages/server/src/terminal/pty-host.ts
4717
- import { chmodSync, existsSync as existsSync5, statSync } from "node:fs";
4967
+ import { chmodSync, existsSync as existsSync7, statSync } from "node:fs";
4718
4968
  import { createRequire } from "node:module";
4719
- import path5 from "node:path";
4969
+ import path6 from "node:path";
4720
4970
  function ensureNodePtySpawnHelperExecutable(deps = {}) {
4721
4971
  const platform = deps.platform ?? process.platform;
4722
4972
  if (platform !== "darwin") {
4723
4973
  return;
4724
4974
  }
4725
4975
  const arch = deps.arch ?? process.arch;
4726
- const resolve3 = deps.resolve ?? ((id) => require2.resolve(id));
4727
- const fileExists = deps.existsSync ?? existsSync5;
4976
+ const resolve4 = deps.resolve ?? ((id) => require2.resolve(id));
4977
+ const fileExists = deps.existsSync ?? existsSync7;
4728
4978
  const stat7 = deps.statSync ?? statSync;
4729
4979
  const chmod = deps.chmodSync ?? chmodSync;
4730
4980
  let packageJsonPath;
4731
4981
  try {
4732
- packageJsonPath = resolve3(NODE_PTY_PKG);
4982
+ packageJsonPath = resolve4(NODE_PTY_PKG);
4733
4983
  } catch {
4734
4984
  return;
4735
4985
  }
4736
- const packageDir = path5.dirname(packageJsonPath);
4986
+ const packageDir = path6.dirname(packageJsonPath);
4737
4987
  const helperDir = arch === "arm64" ? "darwin-arm64" : arch === "x64" ? "darwin-x64" : null;
4738
4988
  if (!helperDir) {
4739
4989
  return;
4740
4990
  }
4741
- const helperPath = path5.join(packageDir, "prebuilds", helperDir, "spawn-helper");
4991
+ const helperPath = path6.join(packageDir, "prebuilds", helperDir, "spawn-helper");
4742
4992
  try {
4743
4993
  if (!fileExists(helperPath)) {
4744
4994
  return;
@@ -4794,7 +5044,7 @@ async function escalateKillWithPolling(pid, signal, options) {
4794
5044
  const startTime = Date.now();
4795
5045
  const deadline = startTime + timeoutMs;
4796
5046
  while (Date.now() < deadline) {
4797
- await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
5047
+ await new Promise((resolve4) => setTimeout(resolve4, pollIntervalMs));
4798
5048
  if (!isProcessAlive(pid)) {
4799
5049
  return true;
4800
5050
  }
@@ -4806,6 +5056,7 @@ var require2, NODE_PTY_PKG, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, NodePt
4806
5056
  var init_pty_host = __esm({
4807
5057
  "packages/server/src/terminal/pty-host.ts"() {
4808
5058
  "use strict";
5059
+ init_src2();
4809
5060
  require2 = createRequire(import.meta.url);
4810
5061
  NODE_PTY_PKG = "node-pty/package.json";
4811
5062
  DEFAULT_POLL_INTERVAL_MS = 50;
@@ -4820,7 +5071,13 @@ var init_pty_host = __esm({
4820
5071
  const message = err instanceof Error ? err.message : String(err);
4821
5072
  throw new Error(`node-pty native module not available. ${message}`);
4822
5073
  }
4823
- const [command, ...args] = argv;
5074
+ if (argv.length === 0) {
5075
+ throw new Error("PTY spawn requires a command");
5076
+ }
5077
+ const [command, ...args] = resolveSpawnArgv(argv, {
5078
+ pathEnv: options.env.Path ?? options.env.PATH,
5079
+ pathExt: options.env.PATHEXT
5080
+ });
4824
5081
  if (command === void 0) {
4825
5082
  throw new Error("PTY spawn requires a command");
4826
5083
  }
@@ -4881,13 +5138,13 @@ var SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY;
4881
5138
  var init_settings = __esm({
4882
5139
  "packages/server/src/supervisor/settings.ts"() {
4883
5140
  "use strict";
4884
- init_src2();
5141
+ init_src3();
4885
5142
  SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY = "supervisor.evaluationTimeoutSec";
4886
5143
  }
4887
5144
  });
4888
5145
 
4889
5146
  // packages/server/src/supervisor/evaluator.ts
4890
- import { spawn } from "node:child_process";
5147
+ import { spawn as spawn2 } from "node:child_process";
4891
5148
  function buildPrompt(context) {
4892
5149
  const agentOutput = context.transcriptExcerpt ?? context.terminalExcerpt ?? "";
4893
5150
  const userInput = context.latestUserInput?.trim() ?? "";
@@ -4918,12 +5175,13 @@ async function runCommand(command, timeoutMs, options = {}) {
4918
5175
  if (options.signal?.aborted) {
4919
5176
  throw createSupervisorEvalAbortedError();
4920
5177
  }
4921
- return await new Promise((resolve3, reject) => {
4922
- const child = spawn(command.argv[0], command.argv.slice(1), {
5178
+ return await new Promise((resolve4, reject) => {
5179
+ const child = spawn2(command.argv[0], command.argv.slice(1), {
4923
5180
  cwd: command.cwd,
4924
5181
  detached: process.platform !== "win32",
4925
5182
  env: { ...process.env, ...command.env },
4926
- stdio: ["ignore", "pipe", "pipe"]
5183
+ stdio: ["ignore", "pipe", "pipe"],
5184
+ windowsHide: true
4927
5185
  });
4928
5186
  const stdout = [];
4929
5187
  const stderr = [];
@@ -4947,7 +5205,7 @@ async function runCommand(command, timeoutMs, options = {}) {
4947
5205
  }
4948
5206
  settled = true;
4949
5207
  cleanup();
4950
- resolve3(value);
5208
+ resolve4(value);
4951
5209
  };
4952
5210
  const terminate = (error) => {
4953
5211
  if (terminationError) {
@@ -5154,7 +5412,7 @@ var NOOP_LOGGER2, SupervisorEvaluator;
5154
5412
  var init_evaluator = __esm({
5155
5413
  "packages/server/src/supervisor/evaluator.ts"() {
5156
5414
  "use strict";
5157
- init_src2();
5415
+ init_src3();
5158
5416
  init_provider_config();
5159
5417
  init_pty_host();
5160
5418
  init_settings();
@@ -5257,7 +5515,7 @@ var INJECTABLE_SESSION_STATES, SupervisorInjector;
5257
5515
  var init_injector = __esm({
5258
5516
  "packages/server/src/supervisor/injector.ts"() {
5259
5517
  "use strict";
5260
- init_src2();
5518
+ init_src3();
5261
5519
  INJECTABLE_SESSION_STATES = /* @__PURE__ */ new Set([
5262
5520
  "idle",
5263
5521
  "running"
@@ -5334,12 +5592,12 @@ var init_scheduler = __esm({
5334
5592
 
5335
5593
  // packages/server/src/supervisor/manager.ts
5336
5594
  function createDeferredCompletion() {
5337
- let resolve3 = () => {
5595
+ let resolve4 = () => {
5338
5596
  };
5339
5597
  const promise = new Promise((innerResolve) => {
5340
- resolve3 = innerResolve;
5598
+ resolve4 = innerResolve;
5341
5599
  });
5342
- return { promise, resolve: resolve3 };
5600
+ return { promise, resolve: resolve4 };
5343
5601
  }
5344
5602
  function generateSupervisorId() {
5345
5603
  return `sup_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
@@ -5369,7 +5627,7 @@ var NOOP_LOGGER3, SupervisorManager;
5369
5627
  var init_manager2 = __esm({
5370
5628
  "packages/server/src/supervisor/manager.ts"() {
5371
5629
  "use strict";
5372
- init_src2();
5630
+ init_src3();
5373
5631
  init_context_builder();
5374
5632
  init_evaluator();
5375
5633
  init_injector();
@@ -6278,8 +6536,8 @@ var init_terminal_snapshot_buffer = __esm({
6278
6536
  if (this.pendingWriteCount === 0) {
6279
6537
  return Promise.resolve();
6280
6538
  }
6281
- return new Promise((resolve3) => {
6282
- this.drainResolvers.push(resolve3);
6539
+ return new Promise((resolve4) => {
6540
+ this.drainResolvers.push(resolve4);
6283
6541
  });
6284
6542
  }
6285
6543
  resolveDrainIfIdle() {
@@ -6288,8 +6546,8 @@ var init_terminal_snapshot_buffer = __esm({
6288
6546
  }
6289
6547
  const resolvers = this.drainResolvers;
6290
6548
  this.drainResolvers = [];
6291
- for (const resolve3 of resolvers) {
6292
- resolve3();
6549
+ for (const resolve4 of resolvers) {
6550
+ resolve4();
6293
6551
  }
6294
6552
  }
6295
6553
  requireTerminal() {
@@ -6608,10 +6866,10 @@ var init_manager3 = __esm({
6608
6866
  }
6609
6867
  return existing.promise;
6610
6868
  }
6611
- let resolve3 = () => {
6869
+ let resolve4 = () => {
6612
6870
  };
6613
6871
  const promise = new Promise((innerResolve) => {
6614
- resolve3 = innerResolve;
6872
+ resolve4 = innerResolve;
6615
6873
  });
6616
6874
  let markKillCompleted = () => {
6617
6875
  };
@@ -6624,7 +6882,7 @@ var init_manager3 = __esm({
6624
6882
  markKillCompleted,
6625
6883
  finalized: false,
6626
6884
  promise,
6627
- resolve: resolve3
6885
+ resolve: resolve4
6628
6886
  });
6629
6887
  void terminal.pty.kill(signal).finally(() => {
6630
6888
  const waiter = this.explicitCloseWaiters.get(terminalId);
@@ -6753,14 +7011,14 @@ var init_manager3 = __esm({
6753
7011
  // packages/server/src/workspace/validator.ts
6754
7012
  import { constants } from "fs";
6755
7013
  import { access, stat as stat5 } from "fs/promises";
6756
- async function validatePath(path8) {
7014
+ async function validatePath(path9) {
6757
7015
  try {
6758
- const stats = await stat5(path8);
7016
+ const stats = await stat5(path9);
6759
7017
  if (!stats.isDirectory()) {
6760
7018
  return { valid: false, error: "Path is not a directory" };
6761
7019
  }
6762
- await access(path8, constants.R_OK);
6763
- await access(path8, constants.W_OK);
7020
+ await access(path9, constants.R_OK);
7021
+ await access(path9, constants.W_OK);
6764
7022
  return { valid: true };
6765
7023
  } catch (error) {
6766
7024
  if (error.code === "ENOENT") {
@@ -6777,8 +7035,8 @@ var init_validator = __esm({
6777
7035
  "packages/server/src/workspace/validator.ts"() {
6778
7036
  "use strict";
6779
7037
  WorkspaceValidator = class {
6780
- async validate(path8) {
6781
- const result = await validatePath(path8);
7038
+ async validate(path9) {
7039
+ const result = await validatePath(path9);
6782
7040
  if (!result.valid) {
6783
7041
  throw new Error(`Invalid workspace path: ${result.error}`);
6784
7042
  }
@@ -6788,14 +7046,14 @@ var init_validator = __esm({
6788
7046
  });
6789
7047
 
6790
7048
  // packages/server/src/fs/gitignore.ts
6791
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
7049
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
6792
7050
  import ignore from "ignore";
6793
7051
  import { join as join5, relative } from "path";
6794
- function normalizePath(path8) {
6795
- return path8.replace(/\\/g, "/");
7052
+ function normalizePath(path9) {
7053
+ return path9.replace(/\\/g, "/");
6796
7054
  }
6797
- function relativeToRoot(rootPath, path8) {
6798
- return normalizePath(relative(rootPath, path8));
7055
+ function relativeToRoot(rootPath, path9) {
7056
+ return normalizePath(relative(rootPath, path9));
6799
7057
  }
6800
7058
  function isDefaultTreeIgnored(name) {
6801
7059
  return name.startsWith(".") || name === "node_modules" || name === ".git";
@@ -6803,18 +7061,18 @@ function isDefaultTreeIgnored(name) {
6803
7061
  function isAlwaysTreeIgnored(name) {
6804
7062
  return name === "node_modules" || name === ".git";
6805
7063
  }
6806
- function isIgnoredByGitignore(ig, path8) {
6807
- if (!path8 || path8.startsWith("..")) {
7064
+ function isIgnoredByGitignore(ig, path9) {
7065
+ if (!path9 || path9.startsWith("..")) {
6808
7066
  return false;
6809
7067
  }
6810
- return ig.ignores(path8) || ig.ignores(`${path8}/`);
7068
+ return ig.ignores(path9) || ig.ignores(`${path9}/`);
6811
7069
  }
6812
7070
  function createGitignoreFilter(rootPath, dirPath) {
6813
7071
  const gitignorePath = join5(rootPath, ".gitignore");
6814
- if (!existsSync6(gitignorePath)) {
7072
+ if (!existsSync8(gitignorePath)) {
6815
7073
  return (name) => !isDefaultTreeIgnored(name);
6816
7074
  }
6817
- const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
7075
+ const gitignoreContent = readFileSync7(gitignorePath, "utf-8");
6818
7076
  const ig = ignore().add(gitignoreContent);
6819
7077
  return (name) => {
6820
7078
  if (isAlwaysTreeIgnored(name)) {
@@ -6826,17 +7084,17 @@ function createGitignoreFilter(rootPath, dirPath) {
6826
7084
  }
6827
7085
  function createWatcherIgnoreFilter(rootPath) {
6828
7086
  const gitignorePath = join5(rootPath, ".gitignore");
6829
- if (!existsSync6(gitignorePath)) {
6830
- return (path8) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path8)));
7087
+ if (!existsSync8(gitignorePath)) {
7088
+ return (path9) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path9)));
6831
7089
  }
6832
- const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
7090
+ const gitignoreContent = readFileSync7(gitignorePath, "utf-8");
6833
7091
  const ig = ignore().add(gitignoreContent);
6834
- return (path8) => {
6835
- const normalizedPath = normalizePath(path8);
7092
+ return (path9) => {
7093
+ const normalizedPath = normalizePath(path9);
6836
7094
  if (DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizedPath))) {
6837
7095
  return true;
6838
7096
  }
6839
- const relativePath = relativeToRoot(rootPath, path8);
7097
+ const relativePath = relativeToRoot(rootPath, path9);
6840
7098
  return isIgnoredByGitignore(ig, relativePath);
6841
7099
  };
6842
7100
  }
@@ -6859,7 +7117,7 @@ var WorkspaceWatcher;
6859
7117
  var init_watcher = __esm({
6860
7118
  "packages/server/src/fs/watcher.ts"() {
6861
7119
  "use strict";
6862
- init_src2();
7120
+ init_src3();
6863
7121
  init_gitignore();
6864
7122
  WorkspaceWatcher = class {
6865
7123
  constructor(workspaceId, rootPath, broadcaster) {
@@ -7111,12 +7369,12 @@ var init_manager4 = __esm({
7111
7369
  * @param path - Workspace path
7112
7370
  * @returns Workspace or undefined
7113
7371
  */
7114
- getByPath(path8) {
7372
+ getByPath(path9) {
7115
7373
  const row = this.deps.db.prepare(
7116
7374
  `SELECT id, path, target_runtime, wsl_distro, opened_at, last_active_at, ui_state
7117
7375
  FROM workspaces
7118
7376
  WHERE path = ?`
7119
- ).get(path8);
7377
+ ).get(path9);
7120
7378
  if (!row) return void 0;
7121
7379
  return {
7122
7380
  id: row.id,
@@ -7320,16 +7578,16 @@ async function debounce(key, op, windowMs) {
7320
7578
  clearTimeout(entry.timer);
7321
7579
  entry.op = op;
7322
7580
  } else {
7323
- let resolve3;
7581
+ let resolve4;
7324
7582
  let reject;
7325
7583
  const promise = new Promise((res, rej) => {
7326
- resolve3 = res;
7584
+ resolve4 = res;
7327
7585
  reject = rej;
7328
7586
  });
7329
7587
  entry = {
7330
7588
  timer: void 0,
7331
7589
  promise,
7332
- resolve: resolve3,
7590
+ resolve: resolve4,
7333
7591
  reject,
7334
7592
  op
7335
7593
  };
@@ -7505,7 +7763,7 @@ var TerminalInputActivitySchema, TerminalInputSchema, pendingTerminalInput, next
7505
7763
  var init_terminal = __esm({
7506
7764
  "packages/server/src/commands/terminal.ts"() {
7507
7765
  "use strict";
7508
- init_src2();
7766
+ init_src3();
7509
7767
  init_dispatch();
7510
7768
  TerminalInputActivitySchema = z5.enum(TERMINAL_INPUT_ACTIVITIES).optional();
7511
7769
  TerminalInputSchema = z5.union([
@@ -8113,7 +8371,7 @@ var BINARY_PAYLOAD_TIMEOUT_MS, isBinaryTerminalInputArgs, WsHub;
8113
8371
  var init_hub = __esm({
8114
8372
  "packages/server/src/ws/hub.ts"() {
8115
8373
  "use strict";
8116
- init_src2();
8374
+ init_src3();
8117
8375
  init_terminal();
8118
8376
  init_client();
8119
8377
  init_dispatch();
@@ -8152,7 +8410,10 @@ var init_hub = __esm({
8152
8410
  status: "connected",
8153
8411
  clientId: client.id,
8154
8412
  authEnabled: this.deps.config.auth.enabled,
8155
- binaryTerminalTransport: true
8413
+ binaryTerminalTransport: true,
8414
+ version: this.deps.config.appVersion ?? "0.0.0",
8415
+ serverInstanceId: `server-${process.pid}`,
8416
+ isWriter: false
8156
8417
  });
8157
8418
  client.onMessage((msg) => this.routeMessage(client, msg));
8158
8419
  client.onClose(() => this.handleClose(client));
@@ -8206,7 +8467,7 @@ var init_hub = __esm({
8206
8467
  }
8207
8468
  }
8208
8469
  awaitBinaryPayload(clientId) {
8209
- return new Promise((resolve3, reject) => {
8470
+ return new Promise((resolve4, reject) => {
8210
8471
  const timer = setTimeout(() => {
8211
8472
  const waiters = this.pendingBinaryWaiters.get(clientId);
8212
8473
  if (!waiters) return;
@@ -8218,7 +8479,7 @@ var init_hub = __esm({
8218
8479
  }
8219
8480
  reject(new Error("Timeout waiting for terminal input binary payload"));
8220
8481
  }, BINARY_PAYLOAD_TIMEOUT_MS);
8221
- const waiter = { resolve: resolve3, reject, timer };
8482
+ const waiter = { resolve: resolve4, reject, timer };
8222
8483
  const queue = this.pendingBinaryWaiters.get(clientId);
8223
8484
  if (queue) {
8224
8485
  queue.push(waiter);
@@ -9025,7 +9286,7 @@ var init_file = __esm({
9025
9286
  // packages/server/src/git/diff.ts
9026
9287
  import { mkdtemp as mkdtemp2, rm as rm5 } from "fs/promises";
9027
9288
  import os3 from "os";
9028
- import path6 from "path";
9289
+ import path7 from "path";
9029
9290
  async function isTrackedPath(cwd, filePath) {
9030
9291
  try {
9031
9292
  await runGit(cwd, ["ls-files", "--error-unmatch", "--", filePath]);
@@ -9035,8 +9296,8 @@ async function isTrackedPath(cwd, filePath) {
9035
9296
  }
9036
9297
  }
9037
9298
  async function getUntrackedFileDiff(cwd, filePath) {
9038
- const tempDir = await mkdtemp2(path6.join(os3.tmpdir(), "coder-studio-git-diff-"));
9039
- const tempIndex = path6.join(tempDir, "index");
9299
+ const tempDir = await mkdtemp2(path7.join(os3.tmpdir(), "coder-studio-git-diff-"));
9300
+ const tempIndex = path7.join(tempDir, "index");
9040
9301
  try {
9041
9302
  try {
9042
9303
  await runGit(cwd, ["read-tree", "HEAD"], {
@@ -9061,11 +9322,11 @@ async function getUntrackedFileDiff(cwd, filePath) {
9061
9322
  await rm5(tempDir, { recursive: true, force: true });
9062
9323
  }
9063
9324
  }
9064
- async function getFileDiff(cwd, path8, staged = false) {
9065
- if (!staged && !await isTrackedPath(cwd, path8)) {
9066
- return getUntrackedFileDiff(cwd, path8);
9325
+ async function getFileDiff(cwd, path9, staged = false) {
9326
+ if (!staged && !await isTrackedPath(cwd, path9)) {
9327
+ return getUntrackedFileDiff(cwd, path9);
9067
9328
  }
9068
- const args = staged ? ["diff", "--staged", "--", path8] : ["diff", "--", path8];
9329
+ const args = staged ? ["diff", "--staged", "--", path9] : ["diff", "--", path9];
9069
9330
  const result = await runGit(cwd, args);
9070
9331
  return result.stdout;
9071
9332
  }
@@ -9314,7 +9575,7 @@ var init_git2 = __esm({
9314
9575
  });
9315
9576
 
9316
9577
  // packages/server/src/config/config-io.ts
9317
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
9578
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
9318
9579
  import { homedir as homedir5 } from "node:os";
9319
9580
  import { basename as basename3, dirname as dirname5, join as join8 } from "node:path";
9320
9581
  function resolveConfigPath(configType) {
@@ -9336,11 +9597,11 @@ function resolveConfigPath(configType) {
9336
9597
  }
9337
9598
  function readConfigFile(configType) {
9338
9599
  const configPath = resolveConfigPath(configType);
9339
- if (!existsSync7(configPath)) {
9600
+ if (!existsSync9(configPath)) {
9340
9601
  return { configPath, content: "", exists: false };
9341
9602
  }
9342
9603
  try {
9343
- const content = readFileSync6(configPath, "utf-8");
9604
+ const content = readFileSync8(configPath, "utf-8");
9344
9605
  return { configPath, content, exists: true };
9345
9606
  } catch {
9346
9607
  return { configPath, content: "", exists: false };
@@ -9350,11 +9611,11 @@ function writeConfigFile(configType, content) {
9350
9611
  try {
9351
9612
  const configPath = resolveConfigPath(configType);
9352
9613
  const parentDir = dirname5(configPath);
9353
- if (!existsSync7(parentDir)) {
9614
+ if (!existsSync9(parentDir)) {
9354
9615
  mkdirSync4(parentDir, { recursive: true });
9355
9616
  }
9356
9617
  let backupPath = null;
9357
- if (existsSync7(configPath)) {
9618
+ if (existsSync9(configPath)) {
9358
9619
  backupPath = createBackup(configPath);
9359
9620
  }
9360
9621
  const tempPath = `${configPath}.tmp`;
@@ -9370,7 +9631,7 @@ function writeConfigFile(configType, content) {
9370
9631
  }
9371
9632
  }
9372
9633
  function createBackup(filePath) {
9373
- const original = readFileSync6(filePath, "utf-8");
9634
+ const original = readFileSync8(filePath, "utf-8");
9374
9635
  const ext = filePath.split(".").pop() ?? "";
9375
9636
  const base = basename3(filePath, `.${ext}`);
9376
9637
  const dir = dirname5(filePath);
@@ -9408,7 +9669,7 @@ var EMPTY_CODEX_AUDIT, SettingsSchema;
9408
9669
  var init_settings2 = __esm({
9409
9670
  "packages/server/src/commands/settings.ts"() {
9410
9671
  "use strict";
9411
- init_src2();
9672
+ init_src3();
9412
9673
  init_config_io();
9413
9674
  init_provider_config();
9414
9675
  init_provider_config_repo();
@@ -9688,9 +9949,9 @@ var init_supervisor2 = __esm({
9688
9949
  });
9689
9950
 
9690
9951
  // packages/server/src/git/worktree.ts
9691
- import path7 from "node:path";
9952
+ import path8 from "node:path";
9692
9953
  function normalizeWorktreePath(worktreePath) {
9693
- return path7.resolve(worktreePath);
9954
+ return path8.resolve(worktreePath);
9694
9955
  }
9695
9956
  async function resolveWorktreePath(repoPath, worktreePath) {
9696
9957
  const normalizedRequested = normalizeWorktreePath(worktreePath);
@@ -9777,19 +10038,19 @@ async function getWorktreeTree(worktreePath) {
9777
10038
  for (const line of lines) {
9778
10039
  const isDir = line.endsWith("/");
9779
10040
  const name = isDir ? line.slice(0, -1) : line;
9780
- const path8 = `${worktreePath}/${name}`;
10041
+ const path9 = `${worktreePath}/${name}`;
9781
10042
  nodes.push({
9782
10043
  name,
9783
- path: path8,
10044
+ path: path9,
9784
10045
  kind: isDir ? "dir" : "file"
9785
10046
  });
9786
10047
  }
9787
10048
  return nodes;
9788
10049
  }
9789
- async function createWorktree(repoPath, branch, path8) {
9790
- await runGit(repoPath, ["worktree", "add", path8, branch]);
10050
+ async function createWorktree(repoPath, branch, path9) {
10051
+ await runGit(repoPath, ["worktree", "add", path9, branch]);
9791
10052
  const worktrees = await listWorktrees(repoPath);
9792
- const created = worktrees.find((wt) => wt.path === path8);
10053
+ const created = worktrees.find((wt) => wt.path === path9);
9793
10054
  if (!created) {
9794
10055
  throw new Error("Failed to find created worktree");
9795
10056
  }
@@ -9987,8 +10248,6 @@ var init_commands = __esm({
9987
10248
  });
9988
10249
 
9989
10250
  // packages/server/src/server.ts
9990
- import { execFile as nodeExecFile3 } from "node:child_process";
9991
- import { promisify as promisify3 } from "node:util";
9992
10251
  function createCodexConfigAuditApi() {
9993
10252
  return {
9994
10253
  audit: () => ({ codex: auditCodexConfigToml() }),
@@ -10016,7 +10275,6 @@ async function logCodexConfigFindings(auditApi, logger) {
10016
10275
  }
10017
10276
  }
10018
10277
  async function createServer(configOverrides) {
10019
- const execFileAsync3 = promisify3(nodeExecFile3);
10020
10278
  const config = parseServerConfig(configOverrides);
10021
10279
  ensureDataDir(config);
10022
10280
  const db = openDatabase(config.dataDir);
@@ -10098,7 +10356,7 @@ async function createServer(configOverrides) {
10098
10356
  const providerRuntimeDeps = {};
10099
10357
  const providerInstallMgr = new ProviderInstallManager(providerRegistry, {
10100
10358
  ...providerRuntimeDeps,
10101
- execFile: (file, args) => execFileAsync3(file, args)
10359
+ runCommand: runCommandAsString
10102
10360
  });
10103
10361
  const commandContext = {
10104
10362
  workspaceMgr,
@@ -10261,10 +10519,12 @@ var init_server = __esm({
10261
10519
  "use strict";
10262
10520
  init_runtime();
10263
10521
  init_src();
10522
+ init_src2();
10264
10523
  init_app();
10265
10524
  init_event_bus();
10266
10525
  init_codex_config_audit();
10267
10526
  init_config();
10527
+ init_command_runner();
10268
10528
  init_install_manager();
10269
10529
  init_manager();
10270
10530
  init_db();
@@ -10284,7 +10544,7 @@ var init_server = __esm({
10284
10544
  init_fencing();
10285
10545
  init_hub();
10286
10546
  init_commands();
10287
- if (import.meta.url === `file://${process.argv[1]}`) {
10547
+ if (isDirectExecution(import.meta.url)) {
10288
10548
  const server = await createServer();
10289
10549
  process.on("SIGINT", async () => {
10290
10550
  console.log("\nShutting down...");
@@ -10434,8 +10694,8 @@ var init_workspace_repo = __esm({
10434
10694
  /**
10435
10695
  * Finds a workspace by path
10436
10696
  */
10437
- findByPath(path8) {
10438
- const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path8);
10697
+ findByPath(path9) {
10698
+ const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path9);
10439
10699
  return row ? this.rowToWorkspace(row) : void 0;
10440
10700
  }
10441
10701
  /**
@@ -10556,7 +10816,7 @@ __export(src_exports, {
10556
10816
  sessionToRow: () => sessionToRow,
10557
10817
  withTransaction: () => withTransaction
10558
10818
  });
10559
- var init_src3 = __esm({
10819
+ var init_src4 = __esm({
10560
10820
  async "packages/server/src/index.ts"() {
10561
10821
  "use strict";
10562
10822
  init_auth();
@@ -10580,12 +10840,12 @@ function getCliConfigPath() {
10580
10840
  return join(homedir(), ".coder-studio", "config.json");
10581
10841
  }
10582
10842
  function readCliConfig() {
10583
- const path8 = getCliConfigPath();
10584
- if (!existsSync(path8)) {
10843
+ const path9 = getCliConfigPath();
10844
+ if (!existsSync(path9)) {
10585
10845
  return null;
10586
10846
  }
10587
10847
  try {
10588
- const parsed = JSON.parse(readFileSync(path8, "utf-8"));
10848
+ const parsed = JSON.parse(readFileSync(path9, "utf-8"));
10589
10849
  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") {
10590
10850
  return null;
10591
10851
  }
@@ -10639,11 +10899,33 @@ function assertSupportedNodeVersion(version = process.versions.node) {
10639
10899
  );
10640
10900
  }
10641
10901
 
10902
+ // packages/cli/src/package-manifest.ts
10903
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
10904
+ function resolveCliPackageManifestUrl(importMetaUrl) {
10905
+ const manifestUrl = [
10906
+ new URL("../package.json", importMetaUrl),
10907
+ new URL("../../package.json", importMetaUrl)
10908
+ ].find((candidate) => existsSync3(candidate));
10909
+ if (!manifestUrl) {
10910
+ throw new Error("Unable to locate CLI package.json");
10911
+ }
10912
+ return manifestUrl;
10913
+ }
10914
+ function getCliPackageManifest(importMetaUrl) {
10915
+ return JSON.parse(
10916
+ readFileSync2(resolveCliPackageManifestUrl(importMetaUrl), "utf-8")
10917
+ );
10918
+ }
10919
+ function getCliVersion(importMetaUrl) {
10920
+ return getCliPackageManifest(importMetaUrl).version ?? "0.0.0";
10921
+ }
10922
+
10642
10923
  // packages/cli/src/server-runner.ts
10643
10924
  var MISSING_WEB_ASSETS_WARNING = "Warning: Web assets not found. Frontend will not be available.";
10644
10925
  var buildServerConfig = () => {
10645
10926
  const savedConfig = readCliConfig();
10646
10927
  const config = {
10928
+ appVersion: getCliVersion(import.meta.url),
10647
10929
  ...savedConfig?.host !== void 0 ? { host: savedConfig.host } : {},
10648
10930
  ...savedConfig?.port !== void 0 && savedConfig.port > 0 ? { port: savedConfig.port } : {},
10649
10931
  ...savedConfig?.dataDir !== void 0 ? { dataDir: savedConfig.dataDir } : {},
@@ -10686,7 +10968,7 @@ var runServerEntrypoint = async (moduleUrl, argvEntry) => {
10686
10968
  };
10687
10969
  var startServer = async () => {
10688
10970
  assertSupportedNodeVersion();
10689
- const { createServer: createServer2 } = await init_src3().then(() => src_exports);
10971
+ const { createServer: createServer2 } = await init_src4().then(() => src_exports);
10690
10972
  const server = await createServer2(buildServerConfig());
10691
10973
  const shutdown = createShutdownHandler(server);
10692
10974
  process.on("SIGINT", shutdown);