@serviceme/devtools-core 0.3.1 → 0.3.3

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/index.js CHANGED
@@ -96,6 +96,7 @@ __export(src_exports, {
96
96
  SCHEDULER_LOCK_FILENAME: () => SCHEDULER_LOCK_FILENAME,
97
97
  SCHEDULER_LOG_FILENAME: () => SCHEDULER_LOG_FILENAME,
98
98
  SCHEDULER_PID_FILENAME: () => SCHEDULER_PID_FILENAME,
99
+ SERVER_PROXY_GLOBAL_FILENAME: () => SERVER_PROXY_GLOBAL_FILENAME,
99
100
  SERVICEME_DIR_NAME: () => SERVICEME_DIR_NAME,
100
101
  SERVICEME_HOME_ENV: () => SERVICEME_HOME_ENV,
101
102
  SKILL_DRAFTS_SUBDIR: () => SKILL_DRAFTS_SUBDIR,
@@ -163,6 +164,7 @@ __export(src_exports, {
163
164
  getSchedulerLockPath: () => getSchedulerLockPath,
164
165
  getSchedulerLogPath: () => getSchedulerLogPath,
165
166
  getSchedulerPidPath: () => getSchedulerPidPath,
167
+ getServerProxyGlobalPath: () => getServerProxyGlobalPath,
166
168
  getServicemeHome: () => getServicemeHome,
167
169
  getSkillDraftsDir: () => getSkillDraftsDir,
168
170
  getToolboxJsonPath: () => getToolboxJsonPath,
@@ -173,6 +175,7 @@ __export(src_exports, {
173
175
  isUserRepo: () => isUserRepo,
174
176
  matchesCron: () => matchesCron,
175
177
  mergeWithDefaults: () => mergeWithDefaults,
178
+ migrateLegacyServerProxyEnabled: () => migrateLegacyServerProxyEnabled,
176
179
  migrateToGlobal: () => migrateToGlobal,
177
180
  moveFiles: () => moveFiles,
178
181
  narrowRepoConfig: () => narrowRepoConfig,
@@ -180,6 +183,7 @@ __export(src_exports, {
180
183
  parseAgentToolPermissions: () => parseAgentToolPermissions,
181
184
  parseIntervalMs: () => parseIntervalMs,
182
185
  randomInstallationId: () => randomInstallationId,
186
+ readServerProxyGlobal: () => readServerProxyGlobal,
183
187
  reindexOrder: () => reindexOrder,
184
188
  reposFileSchema: () => reposFileSchema,
185
189
  resetUserHomeOverrides: () => resetUserHomeOverrides,
@@ -194,7 +198,8 @@ __export(src_exports, {
194
198
  unzipFile: () => unzipFile,
195
199
  userRepoConfigSchema: () => userRepoSchema,
196
200
  validateReposFile: () => validateReposFile,
197
- validateTaskPayload: () => validateTaskPayload
201
+ validateTaskPayload: () => validateTaskPayload,
202
+ writeServerProxyGlobal: () => writeServerProxyGlobal
198
203
  });
199
204
  module.exports = __toCommonJS(src_exports);
200
205
 
@@ -647,6 +652,50 @@ var AccessControl = class {
647
652
  }
648
653
  };
649
654
 
655
+ // src/logger.ts
656
+ var noopLogger = {
657
+ debug() {
658
+ },
659
+ info() {
660
+ },
661
+ warn() {
662
+ },
663
+ error() {
664
+ }
665
+ };
666
+ function formatArgs(args) {
667
+ return args.map((arg) => {
668
+ if (typeof arg === "string") {
669
+ return arg;
670
+ }
671
+ try {
672
+ return JSON.stringify(arg);
673
+ } catch {
674
+ return String(arg);
675
+ }
676
+ }).join(" ");
677
+ }
678
+ function createConsoleLogger(prefix = "serviceme") {
679
+ return {
680
+ debug(message, ...args) {
681
+ process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
682
+ `);
683
+ },
684
+ info(message, ...args) {
685
+ process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
686
+ `);
687
+ },
688
+ warn(message, ...args) {
689
+ process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
690
+ `);
691
+ },
692
+ error(message, ...args) {
693
+ process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
694
+ `);
695
+ }
696
+ };
697
+ }
698
+
650
699
  // src/auth/AuthStateManager.ts
651
700
  var import_node_events = require("events");
652
701
  var AuthStateManager = class {
@@ -809,6 +858,7 @@ var AuthCore = class {
809
858
  this.state = opts.stateManager ?? new AuthStateManager();
810
859
  this.tokenStore = opts.tokenStore;
811
860
  this.accessControl = opts.accessControl;
861
+ this.logger = opts.logger ?? noopLogger;
812
862
  }
813
863
  /** Snapshot of every account, the active provider, and the last error. */
814
864
  status() {
@@ -830,16 +880,28 @@ var AuthCore = class {
830
880
  */
831
881
  async login(provider, ui, shouldContinue) {
832
882
  const providerImpl = this.registry.get(provider);
883
+ this.logger.info("[AuthCore] Starting device-flow login", { provider });
833
884
  try {
834
885
  const initial = await providerImpl.requestDeviceFlow();
835
886
  await ui(initial);
887
+ if (!initial.deviceCode) {
888
+ this.logger.error("[AuthCore] Provider did not return deviceCode", provider);
889
+ throw new Error("Auth provider did not return deviceCode for device flow completion");
890
+ }
836
891
  const session = await providerImpl.completeDeviceFlow(
837
- initial.userCode ? initial.deviceCode ?? "" : "",
838
- shouldContinue
892
+ initial.deviceCode,
893
+ shouldContinue,
894
+ initial.pollIntervalMs
839
895
  );
840
- return await this.persistSession(providerImpl, session);
896
+ const result = await this.persistSession(providerImpl, session);
897
+ this.logger.info("[AuthCore] Device-flow login completed", { provider });
898
+ return result;
841
899
  } catch (err) {
842
- this.state.recordError(err instanceof Error ? err.message : String(err));
900
+ const message = err instanceof Error ? err.message : String(err);
901
+ this.logger.error("[AuthCore] Device-flow login failed", message, {
902
+ provider
903
+ });
904
+ this.state.recordError(message);
843
905
  throw err;
844
906
  }
845
907
  }
@@ -864,7 +926,10 @@ var AuthCore = class {
864
926
  if (!provider) return null;
865
927
  const account = this.state.getActiveAccount();
866
928
  if (!account) return null;
867
- const envelope = await this.tokenStore.get({ provider, accountId: account.id });
929
+ const envelope = await this.tokenStore.get({
930
+ provider,
931
+ accountId: account.id
932
+ });
868
933
  if (!envelope) return null;
869
934
  return { provider, account, token: envelope.token };
870
935
  }
@@ -874,9 +939,15 @@ var AuthCore = class {
874
939
  * every account.
875
940
  */
876
941
  async logout(provider) {
942
+ this.logger.info("[AuthCore] Logout requested", {
943
+ provider: provider ?? "all"
944
+ });
877
945
  if (!provider) {
878
946
  for (const account of this.state.listAccounts()) {
879
- await this.tokenStore.delete({ provider: account.provider, accountId: account.id });
947
+ await this.tokenStore.delete({
948
+ provider: account.provider,
949
+ accountId: account.id
950
+ });
880
951
  this.state.removeAccount(account.provider, account.id);
881
952
  }
882
953
  this.state.clearAll();
@@ -951,6 +1022,10 @@ var AuthCore = class {
951
1022
  avatarUrl: meta.avatarUrl,
952
1023
  expiresAt: meta.expiresAt ?? expiresAt
953
1024
  };
1025
+ this.logger.debug("[AuthCore] Persisting session", {
1026
+ provider: meta.provider,
1027
+ accountId: meta.id
1028
+ });
954
1029
  await this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {
955
1030
  expiresAt
956
1031
  });
@@ -1053,11 +1128,13 @@ var GitHubAuthProvider = class {
1053
1128
  userUrl: config.userUrl ?? DEFAULT_USER_URL,
1054
1129
  scope: config.scope ?? DEFAULT_SCOPE,
1055
1130
  minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
1056
- maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
1131
+ maxPollIntervalMs: config.maxPollIntervalMs ?? 6e4,
1057
1132
  maxWaitMs: config.maxWaitMs,
1058
1133
  fetchImpl: config.fetchImpl ?? fetch,
1059
1134
  deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
1060
1135
  };
1136
+ this.logger = config.logger ?? noopLogger;
1137
+ this.sleepImpl = config.sleepImpl ?? sleep;
1061
1138
  }
1062
1139
  async requestDeviceFlow(opts) {
1063
1140
  const body = JSON.stringify({
@@ -1066,6 +1143,10 @@ var GitHubAuthProvider = class {
1066
1143
  });
1067
1144
  let lastNetworkError;
1068
1145
  for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
1146
+ this.logger.debug("[GitHubAuthProvider] Requesting device code", {
1147
+ attempt,
1148
+ maxAttempts: DEVICE_CODE_MAX_ATTEMPTS
1149
+ });
1069
1150
  let resp;
1070
1151
  try {
1071
1152
  resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
@@ -1079,45 +1160,99 @@ var GitHubAuthProvider = class {
1079
1160
  });
1080
1161
  } catch (error) {
1081
1162
  if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
1163
+ this.logger.error(
1164
+ "[GitHubAuthProvider] Device-code request failed (non-retryable)",
1165
+ error instanceof Error ? error.message : String(error),
1166
+ { attempt }
1167
+ );
1082
1168
  throw error;
1083
1169
  }
1170
+ const delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);
1171
+ this.logger.warn(
1172
+ "[GitHubAuthProvider] Transient network error requesting device code, retrying",
1173
+ {
1174
+ attempt,
1175
+ delayMs,
1176
+ error: error instanceof Error ? error.message : String(error)
1177
+ }
1178
+ );
1084
1179
  lastNetworkError = error;
1085
- await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
1180
+ await this.sleepImpl(delayMs);
1086
1181
  continue;
1087
1182
  }
1088
1183
  if (!resp.ok) {
1184
+ this.logger.error(
1185
+ "[GitHubAuthProvider] Device-code request rejected by GitHub",
1186
+ `HTTP ${resp.status}`
1187
+ );
1089
1188
  throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
1090
1189
  }
1091
1190
  const data = await resp.json();
1092
1191
  if (!data.device_code || !data.user_code || !data.verification_uri) {
1192
+ this.logger.error(
1193
+ "[GitHubAuthProvider] Device-code response missing required fields",
1194
+ JSON.stringify(Object.keys(data))
1195
+ );
1093
1196
  throw new Error("GitHub device-code response missing required fields");
1094
1197
  }
1198
+ this.logger.info("[GitHubAuthProvider] Device code obtained", {
1199
+ userCode: data.user_code,
1200
+ verificationUri: data.verification_uri,
1201
+ expiresInSec: data.expires_in,
1202
+ pollIntervalSec: data.interval
1203
+ });
1095
1204
  return {
1096
1205
  provider: this.providerId,
1206
+ deviceCode: data.device_code,
1097
1207
  userCode: data.user_code,
1098
1208
  verificationUrl: data.verification_uri,
1099
1209
  expiresAt: Date.now() + data.expires_in * 1e3,
1210
+ pollIntervalMs: data.interval * 1e3,
1100
1211
  message: `Open ${data.verification_uri} and enter ${data.user_code}`
1101
1212
  };
1102
1213
  }
1103
1214
  throw lastNetworkError ?? new Error("GitHub device-code request failed");
1104
1215
  }
1105
- async completeDeviceFlow(deviceCode, shouldContinue) {
1216
+ async completeDeviceFlow(deviceCode, shouldContinue, initialPollIntervalMs) {
1106
1217
  const start = Date.now();
1107
- let pollIntervalMs = this.cfg.minPollIntervalMs;
1218
+ let pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);
1108
1219
  let consecutiveSlowDown = 0;
1220
+ let pollCount = 0;
1221
+ this.logger.info("[GitHubAuthProvider] Starting device-flow polling", {
1222
+ initialPollIntervalMs: pollIntervalMs,
1223
+ maxWaitMs: this.cfg.maxWaitMs
1224
+ });
1109
1225
  while (true) {
1110
1226
  if (shouldContinue && !shouldContinue()) {
1227
+ this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
1228
+ elapsedMs: Date.now() - start,
1229
+ pollCount
1230
+ });
1111
1231
  throw new Error("GitHub device flow cancelled by caller");
1112
1232
  }
1113
1233
  const elapsed = Date.now() - start;
1114
1234
  if (this.cfg.maxWaitMs !== void 0 && elapsed > this.cfg.maxWaitMs) {
1235
+ this.logger.warn("[GitHubAuthProvider] Device flow exceeded max wait time", {
1236
+ elapsedMs: elapsed,
1237
+ maxWaitMs: this.cfg.maxWaitMs,
1238
+ pollCount
1239
+ });
1115
1240
  throw new Error("GitHub device flow exceeded max wait time");
1116
1241
  }
1117
- await sleep(pollIntervalMs);
1242
+ await this.sleepImpl(pollIntervalMs);
1118
1243
  if (shouldContinue && !shouldContinue()) {
1244
+ this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
1245
+ elapsedMs: Date.now() - start,
1246
+ pollCount
1247
+ });
1119
1248
  throw new Error("GitHub device flow cancelled by caller");
1120
1249
  }
1250
+ pollCount++;
1251
+ this.logger.debug("[GitHubAuthProvider] Polling for authorization", {
1252
+ pollCount,
1253
+ elapsedMs: Date.now() - start,
1254
+ pollIntervalMs
1255
+ });
1121
1256
  let resp;
1122
1257
  try {
1123
1258
  resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
@@ -1135,19 +1270,59 @@ var GitHubAuthProvider = class {
1135
1270
  });
1136
1271
  } catch (error) {
1137
1272
  if (!isTransientNetworkError(error)) {
1273
+ this.logger.error(
1274
+ "[GitHubAuthProvider] Non-retryable error while polling token endpoint",
1275
+ error instanceof Error ? error.message : String(error),
1276
+ { pollCount }
1277
+ );
1138
1278
  throw error;
1139
1279
  }
1140
- pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
1280
+ const nextPollIntervalMs = Math.min(
1281
+ Math.round(pollIntervalMs * 1.5),
1282
+ this.cfg.maxPollIntervalMs
1283
+ );
1284
+ this.logger.warn(
1285
+ "[GitHubAuthProvider] Transient network error while polling, backing off",
1286
+ {
1287
+ pollCount,
1288
+ error: error instanceof Error ? error.message : String(error),
1289
+ previousPollIntervalMs: pollIntervalMs,
1290
+ nextPollIntervalMs
1291
+ }
1292
+ );
1293
+ pollIntervalMs = nextPollIntervalMs;
1141
1294
  continue;
1142
1295
  }
1143
1296
  if (!resp.ok) {
1144
- pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
1297
+ const nextPollIntervalMs = Math.min(
1298
+ Math.round(pollIntervalMs * 1.5),
1299
+ this.cfg.maxPollIntervalMs
1300
+ );
1301
+ this.logger.warn(
1302
+ "[GitHubAuthProvider] Token endpoint returned non-OK status, backing off",
1303
+ {
1304
+ pollCount,
1305
+ status: resp.status,
1306
+ previousPollIntervalMs: pollIntervalMs,
1307
+ nextPollIntervalMs
1308
+ }
1309
+ );
1310
+ pollIntervalMs = nextPollIntervalMs;
1145
1311
  continue;
1146
1312
  }
1147
1313
  const data = await resp.json();
1148
1314
  if (data.access_token) {
1315
+ this.logger.info("[GitHubAuthProvider] Authorization granted, fetching user profile", {
1316
+ pollCount,
1317
+ elapsedMs: Date.now() - start
1318
+ });
1149
1319
  const rawUser = await this.fetchGitHubUser(data.access_token);
1150
1320
  const email = await this.resolveEmail(data.access_token, rawUser);
1321
+ this.logger.info("[GitHubAuthProvider] Device-flow login completed", {
1322
+ pollCount,
1323
+ elapsedMs: Date.now() - start,
1324
+ login: rawUser.login
1325
+ });
1151
1326
  return {
1152
1327
  token: data.access_token,
1153
1328
  refreshToken: data.refresh_token,
@@ -1162,22 +1337,42 @@ var GitHubAuthProvider = class {
1162
1337
  };
1163
1338
  }
1164
1339
  if (data.error === "authorization_pending") {
1340
+ this.logger.debug("[GitHubAuthProvider] Authorization still pending", {
1341
+ pollCount,
1342
+ elapsedMs: Date.now() - start
1343
+ });
1165
1344
  continue;
1166
1345
  }
1167
1346
  if (data.error === "slow_down") {
1168
1347
  consecutiveSlowDown++;
1169
- pollIntervalMs = Math.min(
1170
- Math.round(pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2e3)),
1171
- this.cfg.maxPollIntervalMs
1172
- );
1348
+ const nextPollIntervalMs = Math.min(pollIntervalMs + 5e3, this.cfg.maxPollIntervalMs);
1349
+ this.logger.warn("[GitHubAuthProvider] GitHub requested slower polling (slow_down)", {
1350
+ pollCount,
1351
+ consecutiveSlowDown,
1352
+ previousPollIntervalMs: pollIntervalMs,
1353
+ nextPollIntervalMs
1354
+ });
1355
+ pollIntervalMs = nextPollIntervalMs;
1173
1356
  continue;
1174
1357
  }
1175
1358
  if (data.error === "access_denied") {
1359
+ this.logger.warn("[GitHubAuthProvider] User denied authorization", {
1360
+ pollCount
1361
+ });
1176
1362
  throw new Error("User denied authorization");
1177
1363
  }
1178
1364
  if (data.error === "expired_token") {
1365
+ this.logger.warn(
1366
+ "[GitHubAuthProvider] Device code expired before authorization completed",
1367
+ { pollCount, elapsedMs: Date.now() - start }
1368
+ );
1179
1369
  throw new Error("GitHub device code expired \u2014 restart the flow");
1180
1370
  }
1371
+ this.logger.error(
1372
+ "[GitHubAuthProvider] Unexpected device-flow error from token endpoint",
1373
+ data.error ?? "unknown",
1374
+ { pollCount }
1375
+ );
1181
1376
  throw new Error(`GitHub device flow error: ${data.error ?? "unknown"}`);
1182
1377
  }
1183
1378
  }
@@ -1857,6 +2052,7 @@ var DRAFTS_SUBDIR = "drafts";
1857
2052
  var SKILL_DRAFTS_SUBDIR = "skills";
1858
2053
  var AGENT_DRAFTS_SUBDIR = "agents";
1859
2054
  var REPOS_CONFIG_FILENAME = "repos.json";
2055
+ var SERVER_PROXY_GLOBAL_FILENAME = "server-proxy.json";
1860
2056
  var SAFE_REPO_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
1861
2057
  var SERVICEME_HOME_ENV = "SERVICEME_HOME";
1862
2058
  var activeOverrides = {};
@@ -1957,6 +2153,9 @@ function getMigrationFailuresPath() {
1957
2153
  function getKnownWorkspacesPath() {
1958
2154
  return path2.join(getServicemeHome(), KNOWN_WORKSPACES_FILENAME);
1959
2155
  }
2156
+ function getServerProxyGlobalPath() {
2157
+ return path2.join(getServicemeHome(), SERVER_PROXY_GLOBAL_FILENAME);
2158
+ }
1960
2159
  var CREDENTIALS_CONFIG_FILENAME = "credentials.json";
1961
2160
  var DEVICE_JSON_FILENAME = "device.json";
1962
2161
  var TOOLBOX_JSON_FILENAME = "toolbox.json";
@@ -2048,10 +2247,20 @@ function isRecord(value) {
2048
2247
  function isNodeError(value) {
2049
2248
  return value instanceof Error && typeof value.code === "string";
2050
2249
  }
2250
+ var LOCK_PID_FILE = "pid";
2251
+ function isProcessAlive(pid) {
2252
+ try {
2253
+ process.kill(pid, 0);
2254
+ return true;
2255
+ } catch {
2256
+ return false;
2257
+ }
2258
+ }
2051
2259
  var FileLock = class {
2052
2260
  constructor(filePath, timeoutMs, retryMs) {
2053
2261
  this.acquired = false;
2054
2262
  this.dirPath = `${filePath}.lock`;
2263
+ this.pidFilePath = path3.join(this.dirPath, LOCK_PID_FILE);
2055
2264
  this.timeoutMs = timeoutMs;
2056
2265
  this.retryMs = retryMs;
2057
2266
  }
@@ -2060,12 +2269,18 @@ var FileLock = class {
2060
2269
  while (true) {
2061
2270
  try {
2062
2271
  await fsp.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
2272
+ await fsp.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
2063
2273
  this.acquired = true;
2064
2274
  return;
2065
2275
  } catch (err) {
2066
2276
  if (!isNodeError(err) || err.code !== "EEXIST") {
2067
2277
  throw err;
2068
2278
  }
2279
+ const stale = await this.isStaleLock();
2280
+ if (stale) {
2281
+ await fsp.rm(this.dirPath, { recursive: true, force: true });
2282
+ continue;
2283
+ }
2069
2284
  if (Date.now() - start >= this.timeoutMs) {
2070
2285
  throw new Error(`IdentityStore lock acquisition timed out for ${this.dirPath}`);
2071
2286
  }
@@ -2073,6 +2288,16 @@ var FileLock = class {
2073
2288
  }
2074
2289
  }
2075
2290
  }
2291
+ async isStaleLock() {
2292
+ try {
2293
+ const pidStr = await fsp.readFile(this.pidFilePath, "utf8");
2294
+ const pid = Number.parseInt(pidStr.trim(), 10);
2295
+ if (!Number.isFinite(pid) || pid <= 0) return true;
2296
+ return !isProcessAlive(pid);
2297
+ } catch {
2298
+ return true;
2299
+ }
2300
+ }
2076
2301
  async release() {
2077
2302
  if (!this.acquired) return;
2078
2303
  this.acquired = false;
@@ -2498,6 +2723,14 @@ var TOOL_CHECK_TIMEOUT_MS = {
2498
2723
  pnpm: 15e3,
2499
2724
  nrm: 15e3
2500
2725
  };
2726
+ var POSIX_LOGIN_SHELL_FALLBACK_TOOLS = /* @__PURE__ */ new Set([
2727
+ "npm",
2728
+ "pnpm",
2729
+ "nrm",
2730
+ "node",
2731
+ "dotnet",
2732
+ "rtk"
2733
+ ]);
2501
2734
  var ERROR_CODE_NOT_FOUND = 127;
2502
2735
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
2503
2736
  var EnvironmentInspector = class {
@@ -2541,6 +2774,23 @@ var EnvironmentInspector = class {
2541
2774
  timeoutMs: this.getToolTimeout(toolName)
2542
2775
  });
2543
2776
  return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2777
+ } catch {
2778
+ if (this.shouldUsePosixLoginShellFallback(toolName)) {
2779
+ return this.getToolPathFromLoginShell(toolName);
2780
+ }
2781
+ return void 0;
2782
+ }
2783
+ }
2784
+ async getToolPathFromLoginShell(toolName) {
2785
+ try {
2786
+ const result = await this.runCommandFn("/bin/bash", {
2787
+ args: [
2788
+ "-lc",
2789
+ 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; command -v ' + toolName
2790
+ ],
2791
+ timeoutMs: this.getToolTimeout(toolName)
2792
+ });
2793
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2544
2794
  } catch {
2545
2795
  return void 0;
2546
2796
  }
@@ -2565,12 +2815,33 @@ var EnvironmentInspector = class {
2565
2815
  }
2566
2816
  }
2567
2817
  async getToolVersion(toolName) {
2568
- const invocation = await this.getVersionInvocation(toolName);
2569
- const result = await this.runCommandFn(invocation.command, {
2570
- args: invocation.args,
2571
- timeoutMs: this.getToolTimeout(toolName)
2572
- });
2573
- return this.parseVersion(toolName, result.stdout || result.stderr);
2818
+ try {
2819
+ const invocation = await this.getVersionInvocation(toolName);
2820
+ const result = await this.runCommandFn(invocation.command, {
2821
+ args: invocation.args,
2822
+ timeoutMs: this.getToolTimeout(toolName)
2823
+ });
2824
+ return this.parseVersion(toolName, result.stdout || result.stderr);
2825
+ } catch (error) {
2826
+ if (!this.shouldUsePosixLoginShellFallback(toolName)) {
2827
+ throw error;
2828
+ }
2829
+ const fallbackResult = await this.runCommandFn("/bin/bash", {
2830
+ args: [
2831
+ "-lc",
2832
+ `export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; ${toolName} --version`
2833
+ ],
2834
+ timeoutMs: this.getToolTimeout(toolName)
2835
+ });
2836
+ const fallbackOutput = (fallbackResult.stdout || fallbackResult.stderr).trim();
2837
+ if (!fallbackOutput) {
2838
+ throw error;
2839
+ }
2840
+ return this.parseVersion(toolName, fallbackOutput);
2841
+ }
2842
+ }
2843
+ shouldUsePosixLoginShellFallback(toolName) {
2844
+ return this.platform !== "win32" && POSIX_LOGIN_SHELL_FALLBACK_TOOLS.has(toolName);
2574
2845
  }
2575
2846
  async checkNvm() {
2576
2847
  if (this.platform === "win32") {
@@ -2620,7 +2891,7 @@ var EnvironmentInspector = class {
2620
2891
  };
2621
2892
  }
2622
2893
  try {
2623
- const result = await this.runCommandFn("dotnet", {
2894
+ const result = await this.runCommandFn(dotnetPath, {
2624
2895
  args: ["nuget", "list", "source"],
2625
2896
  timeoutMs: this.getToolTimeout("nuget")
2626
2897
  });
@@ -3151,54 +3422,85 @@ function detectIndent(text) {
3151
3422
  return 2;
3152
3423
  }
3153
3424
 
3154
- // src/logger.ts
3155
- var noopLogger = {
3156
- debug() {
3157
- },
3158
- info() {
3159
- },
3160
- warn() {
3161
- },
3162
- error() {
3163
- }
3164
- };
3165
- function formatArgs(args) {
3166
- return args.map((arg) => {
3167
- if (typeof arg === "string") {
3168
- return arg;
3169
- }
3170
- try {
3171
- return JSON.stringify(arg);
3172
- } catch {
3173
- return String(arg);
3174
- }
3175
- }).join(" ");
3176
- }
3177
- function createConsoleLogger(prefix = "serviceme") {
3178
- return {
3179
- debug(message, ...args) {
3180
- process.stderr.write(`[${prefix}] DEBUG ${message} ${formatArgs(args)}
3181
- `);
3182
- },
3183
- info(message, ...args) {
3184
- process.stderr.write(`[${prefix}] INFO ${message} ${formatArgs(args)}
3185
- `);
3186
- },
3187
- warn(message, ...args) {
3188
- process.stderr.write(`[${prefix}] WARN ${message} ${formatArgs(args)}
3189
- `);
3190
- },
3191
- error(message, ...args) {
3192
- process.stderr.write(`[${prefix}] ERROR ${message} ${formatArgs(args)}
3193
- `);
3425
+ // src/paths/serverProxyGlobal.ts
3426
+ var import_node_crypto4 = require("crypto");
3427
+ var fs6 = __toESM(require("fs/promises"));
3428
+ var import_promises2 = require("fs/promises");
3429
+ var path9 = __toESM(require("path"));
3430
+ async function readServerProxyGlobal() {
3431
+ const filePath = getServerProxyGlobalPath();
3432
+ try {
3433
+ const raw = await fs6.readFile(filePath, "utf8");
3434
+ const parsed = JSON.parse(raw);
3435
+ if (!isServerProxyGlobalState(parsed)) {
3436
+ throw new Error(
3437
+ `Invalid ${SERVER_PROXY_GLOBAL_FILENAME}: expected {enabled: boolean, lastServerUrl?: string, updatedAt: string}, got ${JSON.stringify(parsed).slice(0, 80)}`
3438
+ );
3194
3439
  }
3440
+ return parsed;
3441
+ } catch (err) {
3442
+ if (isENOENT(err)) return null;
3443
+ throw err;
3444
+ }
3445
+ }
3446
+ async function writeServerProxyGlobal(patch) {
3447
+ const filePath = getServerProxyGlobalPath();
3448
+ const dirPath = path9.dirname(filePath);
3449
+ await fs6.mkdir(dirPath, { recursive: true });
3450
+ const current = await readServerProxyGlobal() ?? {
3451
+ enabled: false,
3452
+ allowOverride: false,
3453
+ lastServerUrl: void 0,
3454
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
3195
3455
  };
3456
+ const next = {
3457
+ enabled: patch.enabled !== void 0 ? patch.enabled : current.enabled,
3458
+ allowOverride: patch.allowOverride !== void 0 ? patch.allowOverride : current.allowOverride,
3459
+ lastServerUrl: patch.lastServerUrl === void 0 ? current.lastServerUrl : patch.lastServerUrl === null ? void 0 : patch.lastServerUrl,
3460
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3461
+ };
3462
+ const tmpPath = `${filePath}.tmp-${(0, import_node_crypto4.randomUUID)()}`;
3463
+ const fh = await (0, import_promises2.open)(tmpPath, "w");
3464
+ try {
3465
+ await fh.writeFile(JSON.stringify(next, null, " "), "utf8");
3466
+ await fh.sync();
3467
+ } finally {
3468
+ await fh.close();
3469
+ }
3470
+ await fs6.rename(tmpPath, filePath);
3471
+ return next;
3472
+ }
3473
+ async function migrateLegacyServerProxyEnabled(readLegacy, clearLegacy) {
3474
+ const legacyEnabled = readLegacy();
3475
+ if (legacyEnabled !== true) return null;
3476
+ const existing = await readServerProxyGlobal();
3477
+ if (existing?.enabled === true) {
3478
+ await clearLegacy();
3479
+ return null;
3480
+ }
3481
+ const next = await writeServerProxyGlobal({ enabled: true });
3482
+ await clearLegacy();
3483
+ return next;
3484
+ }
3485
+ function isServerProxyGlobalState(v) {
3486
+ if (!v || typeof v !== "object") return false;
3487
+ const obj = v;
3488
+ if (typeof obj.enabled !== "boolean") return false;
3489
+ if (typeof obj.allowOverride !== "boolean") return false;
3490
+ if (typeof obj.updatedAt !== "string") return false;
3491
+ if (obj.lastServerUrl !== void 0 && obj.lastServerUrl !== null && typeof obj.lastServerUrl !== "string") {
3492
+ return false;
3493
+ }
3494
+ return true;
3495
+ }
3496
+ function isENOENT(err) {
3497
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
3196
3498
  }
3197
3499
 
3198
3500
  // src/phase5/bootstrap.ts
3199
- var import_node_crypto4 = require("crypto");
3200
- var fs6 = __toESM(require("fs/promises"));
3201
- var path9 = __toESM(require("path"));
3501
+ var import_node_crypto5 = require("crypto");
3502
+ var fs7 = __toESM(require("fs/promises"));
3503
+ var path10 = __toESM(require("path"));
3202
3504
  function getPhase5FileSpecs() {
3203
3505
  return [
3204
3506
  {
@@ -3229,7 +3531,7 @@ function getPhase5FileSpecs() {
3229
3531
  path: getMachineIdPath(),
3230
3532
  // Random uuid, written as a bare string. Subsequent
3231
3533
  // activations see the file and skip re-randomizing.
3232
- defaultContent: (0, import_node_crypto4.randomUUID)()
3534
+ defaultContent: (0, import_node_crypto5.randomUUID)()
3233
3535
  },
3234
3536
  {
3235
3537
  path: getProfilesJsonPath(),
@@ -3243,19 +3545,19 @@ async function bootstrapPhase5Placeholders() {
3243
3545
  const result = { created: [], skipped: [], failed: [] };
3244
3546
  const home = getServicemeHome();
3245
3547
  try {
3246
- await fs6.mkdir(home, { recursive: true });
3548
+ await fs7.mkdir(home, { recursive: true });
3247
3549
  } catch (err) {
3248
3550
  result.failed.push({ path: home, reason: err.message });
3249
3551
  return result;
3250
3552
  }
3251
3553
  for (const spec of getPhase5FileSpecs()) {
3252
3554
  try {
3253
- await fs6.access(spec.path);
3555
+ await fs7.access(spec.path);
3254
3556
  result.skipped.push(spec.path);
3255
3557
  } catch {
3256
3558
  try {
3257
- await fs6.mkdir(path9.dirname(spec.path), { recursive: true });
3258
- await fs6.writeFile(spec.path, spec.defaultContent, "utf8");
3559
+ await fs7.mkdir(path10.dirname(spec.path), { recursive: true });
3560
+ await fs7.writeFile(spec.path, spec.defaultContent, "utf8");
3259
3561
  result.created.push(spec.path);
3260
3562
  } catch (writeErr) {
3261
3563
  result.failed.push({ path: spec.path, reason: writeErr.message });
@@ -3266,13 +3568,13 @@ async function bootstrapPhase5Placeholders() {
3266
3568
  }
3267
3569
 
3268
3570
  // src/project/projectTools.ts
3269
- var fs7 = __toESM(require("fs/promises"));
3270
- var path10 = __toESM(require("path"));
3571
+ var fs8 = __toESM(require("fs/promises"));
3572
+ var path11 = __toESM(require("path"));
3271
3573
  var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
3272
3574
 
3273
3575
  // src/utils/fileUtils.ts
3274
3576
  var import_node_fs = require("fs");
3275
- var import_promises2 = require("fs/promises");
3577
+ var import_promises3 = require("fs/promises");
3276
3578
  var import_node_path = require("path");
3277
3579
  var import_yauzl = __toESM(require("yauzl"));
3278
3580
  var unzipFile = (zipPath, dest) => {
@@ -3283,12 +3585,12 @@ var unzipFile = (zipPath, dest) => {
3283
3585
  zipfile.readEntry();
3284
3586
  zipfile.on("entry", (entry) => {
3285
3587
  if (/\/$/.test(entry.fileName)) {
3286
- void (0, import_promises2.mkdir)((0, import_node_path.join)(dest, entry.fileName), { recursive: true }).then(() => {
3588
+ void (0, import_promises3.mkdir)((0, import_node_path.join)(dest, entry.fileName), { recursive: true }).then(() => {
3287
3589
  zipfile.readEntry();
3288
3590
  }).catch(reject);
3289
3591
  } else {
3290
3592
  const outputPath = (0, import_node_path.join)(dest, entry.fileName);
3291
- void (0, import_promises2.mkdir)((0, import_node_path.dirname)(outputPath), { recursive: true }).then(() => {
3593
+ void (0, import_promises3.mkdir)((0, import_node_path.dirname)(outputPath), { recursive: true }).then(() => {
3292
3594
  zipfile.openReadStream(
3293
3595
  entry,
3294
3596
  (streamError, readStream) => {
@@ -3317,54 +3619,54 @@ var unzipFile = (zipPath, dest) => {
3317
3619
  };
3318
3620
  var tryLstat = async (targetPath) => {
3319
3621
  try {
3320
- return await (0, import_promises2.lstat)(targetPath);
3622
+ return await (0, import_promises3.lstat)(targetPath);
3321
3623
  } catch {
3322
3624
  return null;
3323
3625
  }
3324
3626
  };
3325
3627
  var mergeEntry = async (sourcePath, destPath, overwrite) => {
3326
- const sourceStat = await (0, import_promises2.lstat)(sourcePath);
3628
+ const sourceStat = await (0, import_promises3.lstat)(sourcePath);
3327
3629
  const destStat = await tryLstat(destPath);
3328
3630
  if (sourceStat.isDirectory()) {
3329
3631
  if (destStat && !destStat.isDirectory()) {
3330
3632
  if (!overwrite) {
3331
- await (0, import_promises2.rm)(sourcePath, { recursive: true, force: true });
3633
+ await (0, import_promises3.rm)(sourcePath, { recursive: true, force: true });
3332
3634
  return;
3333
3635
  }
3334
- await (0, import_promises2.rm)(destPath, { recursive: true, force: true });
3636
+ await (0, import_promises3.rm)(destPath, { recursive: true, force: true });
3335
3637
  }
3336
- await (0, import_promises2.mkdir)(destPath, { recursive: true });
3337
- const children = await (0, import_promises2.readdir)(sourcePath);
3638
+ await (0, import_promises3.mkdir)(destPath, { recursive: true });
3639
+ const children = await (0, import_promises3.readdir)(sourcePath);
3338
3640
  for (const child of children) {
3339
3641
  await mergeEntry((0, import_node_path.join)(sourcePath, child), (0, import_node_path.join)(destPath, child), overwrite);
3340
3642
  }
3341
- await (0, import_promises2.rm)(sourcePath, { recursive: true, force: true });
3643
+ await (0, import_promises3.rm)(sourcePath, { recursive: true, force: true });
3342
3644
  return;
3343
3645
  }
3344
3646
  if (destStat) {
3345
3647
  if (!overwrite) {
3346
- await (0, import_promises2.rm)(sourcePath, { recursive: true, force: true });
3648
+ await (0, import_promises3.rm)(sourcePath, { recursive: true, force: true });
3347
3649
  return;
3348
3650
  }
3349
- await (0, import_promises2.rm)(destPath, { recursive: true, force: true });
3651
+ await (0, import_promises3.rm)(destPath, { recursive: true, force: true });
3350
3652
  }
3351
3653
  try {
3352
- await (0, import_promises2.rename)(sourcePath, destPath);
3654
+ await (0, import_promises3.rename)(sourcePath, destPath);
3353
3655
  } catch {
3354
- await (0, import_promises2.copyFile)(sourcePath, destPath);
3355
- await (0, import_promises2.rm)(sourcePath, { recursive: true, force: true });
3656
+ await (0, import_promises3.copyFile)(sourcePath, destPath);
3657
+ await (0, import_promises3.rm)(sourcePath, { recursive: true, force: true });
3356
3658
  }
3357
3659
  };
3358
3660
  var moveFiles = async (sourceDir, destDir, overwrite = false) => {
3359
- await (0, import_promises2.mkdir)(destDir, { recursive: true });
3360
- const files = await (0, import_promises2.readdir)(sourceDir);
3661
+ await (0, import_promises3.mkdir)(destDir, { recursive: true });
3662
+ const files = await (0, import_promises3.readdir)(sourceDir);
3361
3663
  for (const file of files) {
3362
3664
  const sourceFile = (0, import_node_path.join)(sourceDir, file);
3363
3665
  const destFile = (0, import_node_path.join)(destDir, file);
3364
3666
  if (!overwrite) {
3365
3667
  try {
3366
- await (0, import_promises2.access)(destFile, import_node_fs.constants.F_OK);
3367
- await (0, import_promises2.rm)(sourceFile, { recursive: true, force: true });
3668
+ await (0, import_promises3.access)(destFile, import_node_fs.constants.F_OK);
3669
+ await (0, import_promises3.rm)(sourceFile, { recursive: true, force: true });
3368
3670
  continue;
3369
3671
  } catch {
3370
3672
  }
@@ -3377,10 +3679,10 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
3377
3679
  var ProjectTools = class {
3378
3680
  async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
3379
3681
  await unzipFile(zipPath, tempExtractDir);
3380
- let sourceDir = path10.join(tempExtractDir, input.extractedDirName);
3682
+ let sourceDir = path11.join(tempExtractDir, input.extractedDirName);
3381
3683
  let actualDirName = input.extractedDirName;
3382
3684
  if (!await this.pathExists(sourceDir)) {
3383
- const entries = await fs7.readdir(tempExtractDir, { withFileTypes: true });
3685
+ const entries = await fs8.readdir(tempExtractDir, { withFileTypes: true });
3384
3686
  const directories = entries.filter(
3385
3687
  (entry) => entry.isDirectory() && !entry.name.startsWith(".")
3386
3688
  );
@@ -3392,7 +3694,7 @@ var ProjectTools = class {
3392
3694
  );
3393
3695
  if (selectedDirectory) {
3394
3696
  actualDirName = selectedDirectory;
3395
- sourceDir = path10.join(tempExtractDir, actualDirName);
3697
+ sourceDir = path11.join(tempExtractDir, actualDirName);
3396
3698
  } else if (directories.length === 0) {
3397
3699
  throw new Error(
3398
3700
  `No directory found after extraction. Expected directory: ${input.extractedDirName}`
@@ -3442,7 +3744,7 @@ var ProjectTools = class {
3442
3744
  } else {
3443
3745
  for (const scriptPath of scripts) {
3444
3746
  try {
3445
- await fs7.chmod(scriptPath, 493);
3747
+ await fs8.chmod(scriptPath, 493);
3446
3748
  updatedCount += 1;
3447
3749
  } catch {
3448
3750
  }
@@ -3490,7 +3792,7 @@ var ProjectTools = class {
3490
3792
  };
3491
3793
  }
3492
3794
  async ensurePresetManifest(workspacePath, preset) {
3493
- const presetManifestPath = path10.join(
3795
+ const presetManifestPath = path11.join(
3494
3796
  workspacePath,
3495
3797
  ".ms-scaffold",
3496
3798
  "presets",
@@ -3499,11 +3801,11 @@ var ProjectTools = class {
3499
3801
  if (await this.pathExists(presetManifestPath)) {
3500
3802
  return;
3501
3803
  }
3502
- const projectModePath = path10.join(workspacePath, ".ms-scaffold", "project-mode.json");
3804
+ const projectModePath = path11.join(workspacePath, ".ms-scaffold", "project-mode.json");
3503
3805
  if (!await this.pathExists(projectModePath)) {
3504
3806
  return;
3505
3807
  }
3506
- const projectModeRaw = await fs7.readFile(projectModePath, "utf8");
3808
+ const projectModeRaw = await fs8.readFile(projectModePath, "utf8");
3507
3809
  const projectMode = JSON.parse(projectModeRaw);
3508
3810
  const synthesizedPreset = {
3509
3811
  preset,
@@ -3515,8 +3817,8 @@ var ProjectTools = class {
3515
3817
  mergeManagedFiles: [],
3516
3818
  userOwnedPaths: []
3517
3819
  };
3518
- await fs7.mkdir(path10.dirname(presetManifestPath), { recursive: true });
3519
- await fs7.writeFile(
3820
+ await fs8.mkdir(path11.dirname(presetManifestPath), { recursive: true });
3821
+ await fs8.writeFile(
3520
3822
  presetManifestPath,
3521
3823
  `${JSON.stringify(synthesizedPreset, null, 2)}
3522
3824
  `,
@@ -3527,12 +3829,12 @@ var ProjectTools = class {
3527
3829
  const results = [];
3528
3830
  let entries;
3529
3831
  try {
3530
- entries = await fs7.readdir(dir, { withFileTypes: true });
3832
+ entries = await fs8.readdir(dir, { withFileTypes: true });
3531
3833
  } catch {
3532
3834
  return results;
3533
3835
  }
3534
3836
  for (const entry of entries) {
3535
- const fullPath = path10.join(dir, entry.name);
3837
+ const fullPath = path11.join(dir, entry.name);
3536
3838
  if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
3537
3839
  results.push(...await this.findScripts(fullPath, extensions));
3538
3840
  } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
@@ -3543,7 +3845,7 @@ var ProjectTools = class {
3543
3845
  }
3544
3846
  async pathExists(targetPath) {
3545
3847
  try {
3546
- await fs7.access(targetPath);
3848
+ await fs8.access(targetPath);
3547
3849
  return true;
3548
3850
  } catch {
3549
3851
  return false;
@@ -3560,7 +3862,7 @@ var ProjectTools = class {
3560
3862
  const matches = [];
3561
3863
  for (const directoryName of directoryNames) {
3562
3864
  if (await this.directoryMatchesProjectPattern(
3563
- path10.join(tempExtractDir, directoryName),
3865
+ path11.join(tempExtractDir, directoryName),
3564
3866
  projectFilePattern
3565
3867
  )) {
3566
3868
  matches.push(directoryName);
@@ -3572,7 +3874,7 @@ var ProjectTools = class {
3572
3874
  return null;
3573
3875
  }
3574
3876
  async directoryMatchesProjectPattern(directoryPath, projectFilePattern) {
3575
- const entries = await fs7.readdir(directoryPath);
3877
+ const entries = await fs8.readdir(directoryPath);
3576
3878
  if (projectFilePattern.includes("*")) {
3577
3879
  const regex = new RegExp(`^${projectFilePattern.replace("*", ".*")}$`);
3578
3880
  return entries.some((entry) => regex.test(entry));
@@ -3585,8 +3887,8 @@ function createProjectTools() {
3585
3887
  }
3586
3888
 
3587
3889
  // src/repo-manager/index.ts
3588
- var fs8 = __toESM(require("fs/promises"));
3589
- var path11 = __toESM(require("path"));
3890
+ var fs9 = __toESM(require("fs/promises"));
3891
+ var path12 = __toESM(require("path"));
3590
3892
 
3591
3893
  // src/repos/types.ts
3592
3894
  function isDefaultRepo(repo) {
@@ -3668,11 +3970,11 @@ var RepoManager = class {
3668
3970
  const exists = await this.pathExists(localPath);
3669
3971
  if (exists) {
3670
3972
  if (await this.isValidGitRepo(localPath)) continue;
3671
- await fs8.rm(localPath, { recursive: true, force: true });
3973
+ await fs9.rm(localPath, { recursive: true, force: true });
3672
3974
  }
3673
3975
  try {
3674
3976
  if (!this.skipClone) {
3675
- await fs8.mkdir(path11.dirname(localPath), { recursive: true });
3977
+ await fs9.mkdir(path12.dirname(localPath), { recursive: true });
3676
3978
  await this.git.clone(repo.id, repo.url, localPath, repo.branch, true);
3677
3979
  }
3678
3980
  await this.store.updateRepo(repo.id, {
@@ -3707,10 +4009,10 @@ var RepoManager = class {
3707
4009
  const localPath = getRepoDir(repoId);
3708
4010
  const exists = await this.pathExists(localPath);
3709
4011
  if (exists && !await this.isValidGitRepo(localPath)) {
3710
- await fs8.rm(localPath, { recursive: true, force: true });
4012
+ await fs9.rm(localPath, { recursive: true, force: true });
3711
4013
  }
3712
4014
  if (!exists || !await this.pathExists(localPath)) {
3713
- await fs8.mkdir(path11.dirname(localPath), { recursive: true });
4015
+ await fs9.mkdir(path12.dirname(localPath), { recursive: true });
3714
4016
  if (!this.skipClone) {
3715
4017
  await this.git.clone(proxyId, repo.url, localPath, repo.branch, useProxy);
3716
4018
  }
@@ -3810,7 +4112,7 @@ var RepoManager = class {
3810
4112
  let cloned = false;
3811
4113
  if (!this.skipClone) {
3812
4114
  const localPath = getRepoDir(id);
3813
- await fs8.mkdir(path11.dirname(localPath), { recursive: true });
4115
+ await fs9.mkdir(path12.dirname(localPath), { recursive: true });
3814
4116
  await this.git.clone(userProxyId, url, localPath, branch, useProxy);
3815
4117
  cloned = true;
3816
4118
  }
@@ -3835,7 +4137,7 @@ var RepoManager = class {
3835
4137
  await this.store.removeUserRepo(repoId);
3836
4138
  const localPath = getRepoDir(repoId);
3837
4139
  try {
3838
- await fs8.rm(localPath, { recursive: true, force: true });
4140
+ await fs9.rm(localPath, { recursive: true, force: true });
3839
4141
  } catch (err) {
3840
4142
  if (err.code !== "ENOENT") throw err;
3841
4143
  }
@@ -3858,7 +4160,7 @@ var RepoManager = class {
3858
4160
  }
3859
4161
  /** Force-create the SERVICEME home directory tree (idempotent). */
3860
4162
  async ensureHome() {
3861
- await fs8.mkdir(getServicemeHome(), { recursive: true });
4163
+ await fs9.mkdir(getServicemeHome(), { recursive: true });
3862
4164
  }
3863
4165
  /**
3864
4166
  * Returns `true` when `p` contains a `.git` entry — i.e. it is an
@@ -3866,11 +4168,11 @@ var RepoManager = class {
3866
4168
  * (e.g. from an interrupted clone) return `false`.
3867
4169
  */
3868
4170
  async isValidGitRepo(p) {
3869
- return this.pathExists(path11.join(p, ".git"));
4171
+ return this.pathExists(path12.join(p, ".git"));
3870
4172
  }
3871
4173
  async pathExists(p) {
3872
4174
  try {
3873
- await fs8.stat(p);
4175
+ await fs9.stat(p);
3874
4176
  return true;
3875
4177
  } catch {
3876
4178
  return false;
@@ -3995,8 +4297,8 @@ function resolveDefaultRepoId(existing, existingIds) {
3995
4297
  }
3996
4298
 
3997
4299
  // src/repos/loader.ts
3998
- var fs9 = __toESM(require("fs/promises"));
3999
- var path12 = __toESM(require("path"));
4300
+ var fs10 = __toESM(require("fs/promises"));
4301
+ var path13 = __toESM(require("path"));
4000
4302
  var import_zod = require("zod");
4001
4303
  var ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
4002
4304
  var repoIdSchema = import_zod.z.string().min(1).max(64).regex(SAFE_REPO_ID_PATTERN, "repo id must match /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/");
@@ -4079,7 +4381,7 @@ var ReposLoader = class {
4079
4381
  this.configPath = options.configPath;
4080
4382
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
4081
4383
  this.randomSuffix = options.randomSuffix ?? defaultRandomSuffix;
4082
- this.fileSystem = options.fileSystem ?? fs9;
4384
+ this.fileSystem = options.fileSystem ?? fs10;
4083
4385
  }
4084
4386
  /** Absolute path of the file this loader reads/writes. */
4085
4387
  getConfigPath() {
@@ -4132,7 +4434,7 @@ var ReposLoader = class {
4132
4434
  */
4133
4435
  async save(config) {
4134
4436
  const validated = reposFileSchema.parse(config);
4135
- const dir = path12.dirname(this.configPath);
4437
+ const dir = path13.dirname(this.configPath);
4136
4438
  await this.fileSystem.mkdir(dir, { recursive: true });
4137
4439
  const serialized = `${JSON.stringify(validated, null, 2)}
4138
4440
  `;
@@ -4141,7 +4443,7 @@ var ReposLoader = class {
4141
4443
  try {
4142
4444
  await this.fileSystem.rename(tempPath, this.configPath);
4143
4445
  } catch (error) {
4144
- const unlink2 = this.fileSystem.unlink ?? fs9.unlink;
4446
+ const unlink2 = this.fileSystem.unlink ?? fs10.unlink;
4145
4447
  await unlink2(tempPath).catch(() => void 0);
4146
4448
  throw error;
4147
4449
  }
@@ -4201,7 +4503,7 @@ function narrowRepoConfig(repo) {
4201
4503
 
4202
4504
  // src/repos/store.ts
4203
4505
  var import_node_events2 = require("events");
4204
- var fs10 = __toESM(require("fs"));
4506
+ var fs11 = __toESM(require("fs"));
4205
4507
  var ReposStore = class {
4206
4508
  constructor(options = {}) {
4207
4509
  this.config = null;
@@ -4210,7 +4512,7 @@ var ReposStore = class {
4210
4512
  this.reloadTimer = null;
4211
4513
  this.lastLoadResult = null;
4212
4514
  this.loader = options.loader ?? new ReposLoader({ configPath: "" });
4213
- this.fileSystem = options.fileSystem ?? { watch: fs10.watch };
4515
+ this.fileSystem = options.fileSystem ?? { watch: fs11.watch };
4214
4516
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
4215
4517
  this.debounceMs = options.debounceMs ?? 50;
4216
4518
  this.createFsWatcher = options.createFsWatcher ?? ((p, cb) => this.defaultCreateFsWatcher(p, cb));
@@ -4492,17 +4794,17 @@ async function bootstrapDefaults(store) {
4492
4794
  }
4493
4795
 
4494
4796
  // src/scheduled-tasks/daemon/DaemonLogger.ts
4495
- var fs11 = __toESM(require("fs"));
4496
- var path13 = __toESM(require("path"));
4797
+ var fs12 = __toESM(require("fs"));
4798
+ var path14 = __toESM(require("path"));
4497
4799
  var CONFIG_DIR = ".serviceme";
4498
4800
  var LOG_FILE = "scheduler.log";
4499
4801
  var MAX_LOG_SIZE = 1024 * 1024;
4500
4802
  var DaemonLogger = class {
4501
4803
  constructor(workspacePath, options = {}) {
4502
- this.logPath = options.logPath ?? path13.join(workspacePath, CONFIG_DIR, LOG_FILE);
4503
- const dir = path13.dirname(this.logPath);
4504
- if (!fs11.existsSync(dir)) {
4505
- fs11.mkdirSync(dir, { recursive: true });
4804
+ this.logPath = options.logPath ?? path14.join(workspacePath, CONFIG_DIR, LOG_FILE);
4805
+ const dir = path14.dirname(this.logPath);
4806
+ if (!fs12.existsSync(dir)) {
4807
+ fs12.mkdirSync(dir, { recursive: true });
4506
4808
  }
4507
4809
  }
4508
4810
  getLogPath() {
@@ -4513,16 +4815,16 @@ var DaemonLogger = class {
4513
4815
  const line = `[${ts}] [${level.toUpperCase()}] ${message}
4514
4816
  `;
4515
4817
  this.rotateIfNeeded();
4516
- fs11.appendFileSync(this.logPath, line, "utf-8");
4818
+ fs12.appendFileSync(this.logPath, line, "utf-8");
4517
4819
  }
4518
4820
  rotateIfNeeded() {
4519
4821
  try {
4520
- const stats = fs11.statSync(this.logPath);
4822
+ const stats = fs12.statSync(this.logPath);
4521
4823
  if (stats.size > MAX_LOG_SIZE) {
4522
- const content = fs11.readFileSync(this.logPath, "utf-8");
4824
+ const content = fs12.readFileSync(this.logPath, "utf-8");
4523
4825
  const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
4524
4826
  if (halfIdx > 0) {
4525
- fs11.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
4827
+ fs12.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
4526
4828
  }
4527
4829
  }
4528
4830
  } catch {
@@ -4531,39 +4833,39 @@ var DaemonLogger = class {
4531
4833
  };
4532
4834
 
4533
4835
  // src/scheduled-tasks/daemon/PidManager.ts
4534
- var fs12 = __toESM(require("fs"));
4535
- var path14 = __toESM(require("path"));
4836
+ var fs13 = __toESM(require("fs"));
4837
+ var path15 = __toESM(require("path"));
4536
4838
  var CONFIG_DIR2 = ".serviceme";
4537
4839
  var PID_FILE = "scheduler.pid";
4538
4840
  var PidManager = class {
4539
4841
  constructor(workspacePath, options = {}) {
4540
- this.pidPath = options.pidPath ?? path14.join(workspacePath, CONFIG_DIR2, PID_FILE);
4842
+ this.pidPath = options.pidPath ?? path15.join(workspacePath, CONFIG_DIR2, PID_FILE);
4541
4843
  }
4542
4844
  getPidPath() {
4543
4845
  return this.pidPath;
4544
4846
  }
4545
4847
  writePid(pid) {
4546
- const dir = path14.dirname(this.pidPath);
4547
- if (!fs12.existsSync(dir)) {
4548
- fs12.mkdirSync(dir, { recursive: true });
4848
+ const dir = path15.dirname(this.pidPath);
4849
+ if (!fs13.existsSync(dir)) {
4850
+ fs13.mkdirSync(dir, { recursive: true });
4549
4851
  }
4550
- fs12.writeFileSync(this.pidPath, String(pid), "utf-8");
4852
+ fs13.writeFileSync(this.pidPath, String(pid), "utf-8");
4551
4853
  }
4552
4854
  readPid() {
4553
4855
  let stat5;
4554
4856
  try {
4555
- stat5 = fs12.statSync(this.pidPath);
4857
+ stat5 = fs13.statSync(this.pidPath);
4556
4858
  } catch {
4557
4859
  return null;
4558
4860
  }
4559
4861
  if (!stat5.isFile()) return null;
4560
- const raw = fs12.readFileSync(this.pidPath, "utf-8").trim();
4862
+ const raw = fs13.readFileSync(this.pidPath, "utf-8").trim();
4561
4863
  const pid = Number.parseInt(raw, 10);
4562
4864
  return Number.isNaN(pid) ? null : pid;
4563
4865
  }
4564
4866
  removePid() {
4565
- if (fs12.existsSync(this.pidPath)) {
4566
- fs12.unlinkSync(this.pidPath);
4867
+ if (fs13.existsSync(this.pidPath)) {
4868
+ fs13.unlinkSync(this.pidPath);
4567
4869
  }
4568
4870
  }
4569
4871
  isProcessRunning(pid) {
@@ -4584,13 +4886,13 @@ var PidManager = class {
4584
4886
  };
4585
4887
 
4586
4888
  // src/scheduled-tasks/daemon/SchedulerDaemon.ts
4587
- var fs17 = __toESM(require("fs"));
4889
+ var fs18 = __toESM(require("fs"));
4588
4890
  var os5 = __toESM(require("os"));
4589
- var path18 = __toESM(require("path"));
4891
+ var path19 = __toESM(require("path"));
4590
4892
 
4591
4893
  // src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
4592
4894
  var import_node_child_process3 = require("child_process");
4593
- var fs13 = __toESM(require("fs"));
4895
+ var fs14 = __toESM(require("fs"));
4594
4896
 
4595
4897
  // src/scheduled-tasks/executors/timeout.ts
4596
4898
  function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
@@ -4625,7 +4927,7 @@ function redactArgs(args) {
4625
4927
  function writeDiagnostic(message) {
4626
4928
  const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
4627
4929
  if (logPath) {
4628
- fs13.appendFileSync(logPath, message);
4930
+ fs14.appendFileSync(logPath, message);
4629
4931
  return;
4630
4932
  }
4631
4933
  process.stderr.write(message);
@@ -4831,15 +5133,15 @@ ${body}`.trim()
4831
5133
 
4832
5134
  // src/scheduled-tasks/executors/ShellExecutor.ts
4833
5135
  var import_node_child_process4 = require("child_process");
4834
- var fs14 = __toESM(require("fs"));
4835
- var path15 = __toESM(require("path"));
5136
+ var fs15 = __toESM(require("fs"));
5137
+ var path16 = __toESM(require("path"));
4836
5138
  var MAX_OUTPUT_BYTES2 = 1024 * 1024;
4837
5139
  var DEFAULT_TIMEOUT_MS4 = 6e4;
4838
5140
  var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
4839
5141
  function resolveShellExecution(script, options = {}) {
4840
5142
  const platform3 = options.platform ?? process.platform;
4841
5143
  const env = options.env ?? process.env;
4842
- const fileExists = options.fileExists ?? fs14.existsSync;
5144
+ const fileExists = options.fileExists ?? fs15.existsSync;
4843
5145
  if (platform3 === "win32") {
4844
5146
  const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
4845
5147
  if (posixShell) {
@@ -4884,10 +5186,10 @@ function findWindowsPosixShell(env, fileExists) {
4884
5186
  if (fileExists(candidate)) return candidate;
4885
5187
  }
4886
5188
  const pathValue = env.Path ?? env.PATH ?? "";
4887
- for (const dir of pathValue.split(path15.win32.delimiter)) {
5189
+ for (const dir of pathValue.split(path16.win32.delimiter)) {
4888
5190
  if (!dir) continue;
4889
5191
  for (const executable of POSIX_SHELL_CANDIDATES) {
4890
- const candidate = path15.win32.join(dir, executable);
5192
+ const candidate = path16.win32.join(dir, executable);
4891
5193
  if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
4892
5194
  return candidate;
4893
5195
  }
@@ -4896,13 +5198,13 @@ function findWindowsPosixShell(env, fileExists) {
4896
5198
  return null;
4897
5199
  }
4898
5200
  function isWindowsWslLauncher(candidate) {
4899
- const normalized = path15.win32.normalize(candidate).toLowerCase();
5201
+ const normalized = path16.win32.normalize(candidate).toLowerCase();
4900
5202
  return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
4901
5203
  }
4902
5204
  function writeDiagnostic2(message) {
4903
5205
  const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
4904
5206
  if (logPath) {
4905
- fs14.appendFileSync(logPath, message);
5207
+ fs15.appendFileSync(logPath, message);
4906
5208
  return;
4907
5209
  }
4908
5210
  process.stderr.write(message);
@@ -5044,10 +5346,10 @@ function getExecutor(taskType) {
5044
5346
  }
5045
5347
 
5046
5348
  // src/scheduled-tasks/TaskConfigManager.ts
5047
- var import_node_crypto5 = require("crypto");
5048
- var fs15 = __toESM(require("fs"));
5349
+ var import_node_crypto6 = require("crypto");
5350
+ var fs16 = __toESM(require("fs"));
5049
5351
  var os4 = __toESM(require("os"));
5050
- var path16 = __toESM(require("path"));
5352
+ var path17 = __toESM(require("path"));
5051
5353
  var import_devtools_protocol8 = require("@serviceme/devtools-protocol");
5052
5354
  function emptyConfig() {
5053
5355
  return { version: 2, tasks: [] };
@@ -5071,7 +5373,7 @@ function v1ContainerShape(value) {
5071
5373
  }
5072
5374
  function defaultWorkspaceContext() {
5073
5375
  const home = os4.homedir() || "/";
5074
- return { path: home, name: path16.basename(home) || home };
5376
+ return { path: home, name: path17.basename(home) || home };
5075
5377
  }
5076
5378
  function requireNonEmptyString(payload, field, taskType) {
5077
5379
  if (!isRecord2(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
@@ -5106,10 +5408,10 @@ var TaskConfigManager = class {
5106
5408
  return this.configPath;
5107
5409
  }
5108
5410
  readConfig() {
5109
- if (!fs15.existsSync(this.configPath)) {
5411
+ if (!fs16.existsSync(this.configPath)) {
5110
5412
  return emptyConfig();
5111
5413
  }
5112
- const raw = fs15.readFileSync(this.configPath, "utf-8");
5414
+ const raw = fs16.readFileSync(this.configPath, "utf-8");
5113
5415
  let parsed;
5114
5416
  try {
5115
5417
  parsed = JSON.parse(raw);
@@ -5162,7 +5464,7 @@ var TaskConfigManager = class {
5162
5464
  const target = this.migrationFailuresPath ?? getMigrationFailuresPath();
5163
5465
  const prior = (() => {
5164
5466
  try {
5165
- return JSON.parse(fs15.readFileSync(target, "utf-8"));
5467
+ return JSON.parse(fs16.readFileSync(target, "utf-8"));
5166
5468
  } catch {
5167
5469
  return [];
5168
5470
  }
@@ -5175,8 +5477,8 @@ var TaskConfigManager = class {
5175
5477
  snippet: raw.slice(0, 500),
5176
5478
  recordedAt: (/* @__PURE__ */ new Date()).toISOString()
5177
5479
  });
5178
- fs15.mkdirSync(path16.dirname(target), { recursive: true });
5179
- fs15.writeFileSync(target, JSON.stringify(failures, null, " "), "utf-8");
5480
+ fs16.mkdirSync(path17.dirname(target), { recursive: true });
5481
+ fs16.writeFileSync(target, JSON.stringify(failures, null, " "), "utf-8");
5180
5482
  } catch (writeError) {
5181
5483
  this.warn(
5182
5484
  `TaskConfigManager: also failed to write migration-failures log: ${String(writeError)}`
@@ -5184,13 +5486,13 @@ var TaskConfigManager = class {
5184
5486
  }
5185
5487
  }
5186
5488
  writeConfig(config) {
5187
- const dir = path16.dirname(this.configPath);
5188
- if (!fs15.existsSync(dir)) {
5189
- fs15.mkdirSync(dir, { recursive: true });
5489
+ const dir = path17.dirname(this.configPath);
5490
+ if (!fs16.existsSync(dir)) {
5491
+ fs16.mkdirSync(dir, { recursive: true });
5190
5492
  }
5191
5493
  const tmp = `${this.configPath}.tmp`;
5192
- fs15.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
5193
- fs15.renameSync(tmp, this.configPath);
5494
+ fs16.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
5495
+ fs16.renameSync(tmp, this.configPath);
5194
5496
  }
5195
5497
  listTasks() {
5196
5498
  return this.readConfig().tasks;
@@ -5212,7 +5514,7 @@ var TaskConfigManager = class {
5212
5514
  const config = this.readConfig();
5213
5515
  const now = (/* @__PURE__ */ new Date()).toISOString();
5214
5516
  const task = {
5215
- id: (0, import_node_crypto5.randomUUID)(),
5517
+ id: (0, import_node_crypto6.randomUUID)(),
5216
5518
  name: input.name,
5217
5519
  description: input.description,
5218
5520
  enabled: input.enabled ?? true,
@@ -5467,9 +5769,9 @@ var TaskExecutionEngine = class {
5467
5769
  };
5468
5770
 
5469
5771
  // src/scheduled-tasks/TaskLogManager.ts
5470
- var import_node_crypto6 = require("crypto");
5471
- var fs16 = __toESM(require("fs"));
5472
- var path17 = __toESM(require("path"));
5772
+ var import_node_crypto7 = require("crypto");
5773
+ var fs17 = __toESM(require("fs"));
5774
+ var path18 = __toESM(require("path"));
5473
5775
  var MAX_LOGS = 200;
5474
5776
  function emptyLogFile() {
5475
5777
  return { logs: [] };
@@ -5498,11 +5800,11 @@ var TaskLogManager = class {
5498
5800
  return this.logPath;
5499
5801
  }
5500
5802
  readLogFile() {
5501
- if (!fs16.existsSync(this.logPath)) {
5803
+ if (!fs17.existsSync(this.logPath)) {
5502
5804
  return emptyLogFile();
5503
5805
  }
5504
5806
  try {
5505
- const raw = fs16.readFileSync(this.logPath, "utf-8");
5807
+ const raw = fs17.readFileSync(this.logPath, "utf-8");
5506
5808
  const parsed = JSON.parse(raw);
5507
5809
  const file = validateAndRepairLogFile(parsed);
5508
5810
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
@@ -5518,26 +5820,26 @@ var TaskLogManager = class {
5518
5820
  }
5519
5821
  backupCorruptedFile() {
5520
5822
  try {
5521
- if (fs16.existsSync(this.logPath)) {
5823
+ if (fs17.existsSync(this.logPath)) {
5522
5824
  const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
5523
- fs16.copyFileSync(this.logPath, backupPath);
5825
+ fs17.copyFileSync(this.logPath, backupPath);
5524
5826
  }
5525
5827
  } catch {
5526
5828
  }
5527
5829
  }
5528
5830
  writeLogFile(file) {
5529
- const dir = path17.dirname(this.logPath);
5530
- if (!fs16.existsSync(dir)) {
5531
- fs16.mkdirSync(dir, { recursive: true });
5831
+ const dir = path18.dirname(this.logPath);
5832
+ if (!fs17.existsSync(dir)) {
5833
+ fs17.mkdirSync(dir, { recursive: true });
5532
5834
  }
5533
5835
  const tmp = `${this.logPath}.tmp`;
5534
- fs16.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
5535
- fs16.renameSync(tmp, this.logPath);
5836
+ fs17.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
5837
+ fs17.renameSync(tmp, this.logPath);
5536
5838
  }
5537
5839
  appendLog(input) {
5538
5840
  const file = this.readLogFile();
5539
5841
  const log = {
5540
- id: (0, import_node_crypto6.randomUUID)(),
5842
+ id: (0, import_node_crypto7.randomUUID)(),
5541
5843
  taskId: input.taskId,
5542
5844
  taskName: input.taskName,
5543
5845
  startedAt: input.startedAt,
@@ -5592,12 +5894,12 @@ var SchedulerDaemon = class {
5592
5894
  this.lastRun = /* @__PURE__ */ new Map();
5593
5895
  this.taskRunning = /* @__PURE__ */ new Set();
5594
5896
  this.workspacePath = workspacePath;
5595
- const configDir = path18.join(workspacePath, ".serviceme");
5897
+ const configDir = path19.join(workspacePath, ".serviceme");
5596
5898
  this.configManager = new TaskConfigManager({
5597
- configPath: path18.join(configDir, "scheduled-tasks.json")
5899
+ configPath: path19.join(configDir, "scheduled-tasks.json")
5598
5900
  });
5599
5901
  this.logManager = new TaskLogManager({
5600
- logPath: path18.join(configDir, "scheduled-tasks-log.json")
5902
+ logPath: path19.join(configDir, "scheduled-tasks-log.json")
5601
5903
  });
5602
5904
  this.pidManager = new PidManager(workspacePath);
5603
5905
  this.logger = new DaemonLogger(workspacePath);
@@ -5649,8 +5951,8 @@ var SchedulerDaemon = class {
5649
5951
  const configPath = this.configManager.getConfigPath();
5650
5952
  const dir = configPath.substring(0, configPath.lastIndexOf("/"));
5651
5953
  try {
5652
- if (fs17.existsSync(dir)) {
5653
- this.watcher = fs17.watch(dir, (_eventType, filename) => {
5954
+ if (fs18.existsSync(dir)) {
5955
+ this.watcher = fs18.watch(dir, (_eventType, filename) => {
5654
5956
  if (filename === "scheduled-tasks.json") {
5655
5957
  this.logger.log("info", "Config file changed, reconciling...");
5656
5958
  }
@@ -5803,9 +6105,9 @@ function matchCronField(field, value) {
5803
6105
  }
5804
6106
 
5805
6107
  // src/scheduled-tasks/daemon/SchedulerDaemonV2.ts
5806
- var fs18 = __toESM(require("fs"));
6108
+ var fs19 = __toESM(require("fs"));
5807
6109
  var os6 = __toESM(require("os"));
5808
- var path19 = __toESM(require("path"));
6110
+ var path20 = __toESM(require("path"));
5809
6111
  var TICK_INTERVAL2 = 1e3;
5810
6112
  var MIN_SCHEDULE_INTERVAL2 = 1e3;
5811
6113
  var SCHEDULER_LOG_FILENAME2 = "scheduler.log";
@@ -5825,7 +6127,7 @@ var SchedulerDaemonV2 = class {
5825
6127
  this.logManager = options.logManager ?? new TaskLogManager();
5826
6128
  this.pidManager = options.pidManager ?? new PidManager("", { pidPath: getSchedulerPidPath() });
5827
6129
  this.logger = options.logger ?? new DaemonLogger(os6.homedir(), {
5828
- logPath: path19.join(path19.dirname(this.pidManager.getPidPath()), SCHEDULER_LOG_FILENAME2)
6130
+ logPath: path20.join(path20.dirname(this.pidManager.getPidPath()), SCHEDULER_LOG_FILENAME2)
5829
6131
  });
5830
6132
  this.getExecutor = options.getExecutor ?? getExecutor;
5831
6133
  this.tryAcquireLock = options.tryAcquireLock ?? (() => true);
@@ -5883,7 +6185,7 @@ var SchedulerDaemonV2 = class {
5883
6185
  const now = Date.now();
5884
6186
  for (const task of config.tasks) {
5885
6187
  if (!task.enabled) continue;
5886
- if (!fs18.existsSync(task.workspace.path)) {
6188
+ if (!fs19.existsSync(task.workspace.path)) {
5887
6189
  this.disableTaskForMissingWorkspace(task, config);
5888
6190
  continue;
5889
6191
  }
@@ -6040,21 +6342,21 @@ function matchCronField2(field, value) {
6040
6342
  }
6041
6343
 
6042
6344
  // src/scheduled-tasks/migration/MigrateToGlobal.ts
6043
- var fs19 = __toESM(require("fs"));
6044
- var path20 = __toESM(require("path"));
6345
+ var fs20 = __toESM(require("fs"));
6346
+ var path21 = __toESM(require("path"));
6045
6347
  var import_devtools_protocol9 = require("@serviceme/devtools-protocol");
6046
6348
  var WORKSPACE_DIR = ".serviceme";
6047
6349
  var V1_FILENAME = "scheduled-tasks.json";
6048
6350
  function defaultProbe(workspacePath) {
6049
6351
  return {
6050
6352
  path: workspacePath,
6051
- name: path20.basename(workspacePath) || workspacePath
6353
+ name: path21.basename(workspacePath) || workspacePath
6052
6354
  };
6053
6355
  }
6054
6356
  function readV1Config(v1Path) {
6055
6357
  let raw;
6056
6358
  try {
6057
- raw = fs19.readFileSync(v1Path, "utf-8");
6359
+ raw = fs20.readFileSync(v1Path, "utf-8");
6058
6360
  } catch (err) {
6059
6361
  return {
6060
6362
  ok: false,
@@ -6077,27 +6379,27 @@ function readV1Config(v1Path) {
6077
6379
  }
6078
6380
  function safeDelete(filePath) {
6079
6381
  try {
6080
- fs19.unlinkSync(filePath);
6382
+ fs20.unlinkSync(filePath);
6081
6383
  } catch {
6082
6384
  }
6083
6385
  }
6084
6386
  function ensureDir(filePath) {
6085
- const dir = path20.dirname(filePath);
6086
- if (!fs19.existsSync(dir)) {
6087
- fs19.mkdirSync(dir, { recursive: true });
6387
+ const dir = path21.dirname(filePath);
6388
+ if (!fs20.existsSync(dir)) {
6389
+ fs20.mkdirSync(dir, { recursive: true });
6088
6390
  }
6089
6391
  }
6090
6392
  function readJsonFile(filePath) {
6091
- if (!fs19.existsSync(filePath)) return null;
6393
+ if (!fs20.existsSync(filePath)) return null;
6092
6394
  try {
6093
- return JSON.parse(fs19.readFileSync(filePath, "utf-8"));
6395
+ return JSON.parse(fs20.readFileSync(filePath, "utf-8"));
6094
6396
  } catch {
6095
6397
  return null;
6096
6398
  }
6097
6399
  }
6098
6400
  function writeJsonFile(filePath, data) {
6099
6401
  ensureDir(filePath);
6100
- fs19.writeFileSync(filePath, JSON.stringify(data, null, " "), "utf-8");
6402
+ fs20.writeFileSync(filePath, JSON.stringify(data, null, " "), "utf-8");
6101
6403
  }
6102
6404
  function disambiguateName(task, existingNames, workspaceName) {
6103
6405
  if (!existingNames.has(task.name)) {
@@ -6127,8 +6429,8 @@ async function migrateToGlobal(options) {
6127
6429
  const conflicts = [];
6128
6430
  const issues = [];
6129
6431
  for (const workspacePath of options.workspacePaths) {
6130
- const v1Path = path20.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
6131
- if (!fs19.existsSync(v1Path)) continue;
6432
+ const v1Path = path21.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
6433
+ if (!fs20.existsSync(v1Path)) continue;
6132
6434
  const v1 = readV1Config(v1Path);
6133
6435
  if (!v1.ok) {
6134
6436
  failures.push({
@@ -6170,8 +6472,8 @@ async function migrateToGlobal(options) {
6170
6472
  if (migrated > 0) {
6171
6473
  ensureDir(globalConfigPath);
6172
6474
  const tmp = `${globalConfigPath}.tmp`;
6173
- fs19.writeFileSync(tmp, JSON.stringify(baseConfig, null, " "), "utf-8");
6174
- fs19.renameSync(tmp, globalConfigPath);
6475
+ fs20.writeFileSync(tmp, JSON.stringify(baseConfig, null, " "), "utf-8");
6476
+ fs20.renameSync(tmp, globalConfigPath);
6175
6477
  }
6176
6478
  if (failures.length > priorFailures.length) {
6177
6479
  writeJsonFile(migrationFailuresPath, failures);
@@ -6188,8 +6490,8 @@ async function migrateToGlobal(options) {
6188
6490
 
6189
6491
  // src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts
6190
6492
  var import_node_child_process5 = require("child_process");
6191
- var fs20 = __toESM(require("fs"));
6192
- var path21 = __toESM(require("path"));
6493
+ var fs21 = __toESM(require("fs"));
6494
+ var path22 = __toESM(require("path"));
6193
6495
  var DEFAULT_TIMEOUT_MS5 = 2e3;
6194
6496
  var GitTimeoutError = class extends Error {
6195
6497
  constructor() {
@@ -6252,8 +6554,8 @@ var WorkspaceProbe = class {
6252
6554
  }
6253
6555
  }
6254
6556
  async probe(workspacePath) {
6255
- const name = path21.basename(workspacePath) || workspacePath;
6256
- if (!workspacePath || !fs20.existsSync(workspacePath)) {
6557
+ const name = path22.basename(workspacePath) || workspacePath;
6558
+ if (!workspacePath || !fs21.existsSync(workspacePath)) {
6257
6559
  return {
6258
6560
  workspace: { path: workspacePath, name },
6259
6561
  error: "path-not-found"
@@ -6405,8 +6707,8 @@ var SkillReconciler = class {
6405
6707
  };
6406
6708
 
6407
6709
  // src/skills/SkillStore.ts
6408
- var fs21 = __toESM(require("fs/promises"));
6409
- var path22 = __toESM(require("path"));
6710
+ var fs22 = __toESM(require("fs/promises"));
6711
+ var path23 = __toESM(require("path"));
6410
6712
  var USER_SKILL_MARKER_FILE = ".serviceme-skill.json";
6411
6713
  var LEGACY_USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
6412
6714
  var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
@@ -6422,7 +6724,7 @@ var SkillStore = class {
6422
6724
  constructor(options) {
6423
6725
  this.workspacePath = options.workspacePath;
6424
6726
  this.userSkillsRoot = options.userSkillsRoot;
6425
- this.fileSystem = options.fileSystem ?? fs21;
6727
+ this.fileSystem = options.fileSystem ?? fs22;
6426
6728
  }
6427
6729
  normalizeRemoteSkillId(remoteId) {
6428
6730
  if (remoteId.startsWith("official/")) {
@@ -6441,10 +6743,10 @@ var SkillStore = class {
6441
6743
  return WORKSPACE_SKILLS_MARKER_RELATIVE;
6442
6744
  }
6443
6745
  getUserSkillPath(skillId) {
6444
- return path22.join(this.userSkillsRoot, skillId);
6746
+ return path23.join(this.userSkillsRoot, skillId);
6445
6747
  }
6446
6748
  async listWorkspaceSkillIds() {
6447
- const skillsRootPath = path22.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE);
6749
+ const skillsRootPath = path23.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE);
6448
6750
  try {
6449
6751
  const entries = await this.fileSystem.readdir(skillsRootPath, {
6450
6752
  withFileTypes: true
@@ -6468,7 +6770,7 @@ var SkillStore = class {
6468
6770
  const targetDir = this.getUserSkillPath(skillId);
6469
6771
  await this.fileSystem.mkdir(targetDir, { recursive: true });
6470
6772
  await this.fileSystem.writeFile(
6471
- path22.join(targetDir, USER_SKILL_MARKER_FILE),
6773
+ path23.join(targetDir, USER_SKILL_MARKER_FILE),
6472
6774
  JSON.stringify({ skillId, installedBy: "serviceme" }, null, 2),
6473
6775
  "utf-8"
6474
6776
  );
@@ -6477,7 +6779,7 @@ var SkillStore = class {
6477
6779
  await this.migrateLegacyUserSkillMarker(skillId);
6478
6780
  try {
6479
6781
  const marker = await this.fileSystem.readFile(
6480
- path22.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
6782
+ path23.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
6481
6783
  "utf-8"
6482
6784
  );
6483
6785
  const parsed = JSON.parse(marker);
@@ -6494,8 +6796,8 @@ var SkillStore = class {
6494
6796
  */
6495
6797
  async migrateLegacyUserSkillMarker(skillId) {
6496
6798
  const targetDir = this.getUserSkillPath(skillId);
6497
- const newPath = path22.join(targetDir, USER_SKILL_MARKER_FILE);
6498
- const legacyPath = path22.join(targetDir, LEGACY_USER_SKILL_MARKER_FILE);
6799
+ const newPath = path23.join(targetDir, USER_SKILL_MARKER_FILE);
6800
+ const legacyPath = path23.join(targetDir, LEGACY_USER_SKILL_MARKER_FILE);
6499
6801
  try {
6500
6802
  await this.fileSystem.readFile(newPath, "utf-8");
6501
6803
  return;
@@ -6508,12 +6810,12 @@ var SkillStore = class {
6508
6810
  }
6509
6811
  }
6510
6812
  async writeSkillFiles(skillId, scope, files) {
6511
- const root = scope === "workspace" ? path22.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
6512
- const targetDir = path22.join(root, skillId);
6813
+ const root = scope === "workspace" ? path23.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
6814
+ const targetDir = path23.join(root, skillId);
6513
6815
  await this.fileSystem.mkdir(targetDir, { recursive: true });
6514
6816
  for (const file of files) {
6515
- const filePath = path22.join(targetDir, file.path);
6516
- await this.fileSystem.mkdir(path22.dirname(filePath), { recursive: true });
6817
+ const filePath = path23.join(targetDir, file.path);
6818
+ await this.fileSystem.mkdir(path23.dirname(filePath), { recursive: true });
6517
6819
  await this.fileSystem.writeFile(filePath, file.content, "utf-8");
6518
6820
  if (file.executable) {
6519
6821
  try {
@@ -6526,8 +6828,8 @@ var SkillStore = class {
6526
6828
  };
6527
6829
 
6528
6830
  // src/submit/index.ts
6529
- var fs22 = __toESM(require("fs/promises"));
6530
- var path23 = __toESM(require("path"));
6831
+ var fs23 = __toESM(require("fs/promises"));
6832
+ var path24 = __toESM(require("path"));
6531
6833
 
6532
6834
  // src/submit/types.ts
6533
6835
  var SubmitError = class extends Error {
@@ -6577,14 +6879,14 @@ var SubmitClient = class {
6577
6879
  throw new SubmitError(v.reason ?? "unknown", v.detail ?? "validation denied");
6578
6880
  }
6579
6881
  const localRepoPath = getRepoDir(repoId);
6580
- const targetDir = path23.join(localRepoPath, "skills", skillName);
6581
- await fs22.mkdir(targetDir, { recursive: true });
6882
+ const targetDir = path24.join(localRepoPath, "skills", skillName);
6883
+ await fs23.mkdir(targetDir, { recursive: true });
6582
6884
  for (const f of files) {
6583
- const full = path23.join(targetDir, f.path);
6584
- await fs22.mkdir(path23.dirname(full), { recursive: true });
6885
+ const full = path24.join(targetDir, f.path);
6886
+ await fs23.mkdir(path24.dirname(full), { recursive: true });
6585
6887
  const tmp = `${full}.${process.pid}.${Date.now()}.tmp`;
6586
- await fs22.writeFile(tmp, f.content, "utf8");
6587
- await fs22.rename(tmp, full);
6888
+ await fs23.writeFile(tmp, f.content, "utf8");
6889
+ await fs23.rename(tmp, full);
6588
6890
  }
6589
6891
  const commitMessage = `feat(skills): add ${skillName}`;
6590
6892
  const { commitSha } = await this.git.commit(localRepoPath, commitMessage);
@@ -6653,8 +6955,8 @@ function touchLastUsedAt(tools, id, when = /* @__PURE__ */ new Date()) {
6653
6955
 
6654
6956
  // src/toolbox/ToolboxStore.ts
6655
6957
  var fsp2 = __toESM(require("fs/promises"));
6656
- var path24 = __toESM(require("path"));
6657
- var import_promises3 = require("timers/promises");
6958
+ var path25 = __toESM(require("path"));
6959
+ var import_promises4 = require("timers/promises");
6658
6960
 
6659
6961
  // src/toolbox/types.ts
6660
6962
  var TOOLBOX_JSON_SCHEMA_VERSION = 1;
@@ -6688,11 +6990,11 @@ var LOCK_DIR_MODE2 = 448;
6688
6990
  var DEFAULT_LOCK_TIMEOUT_MS2 = 5e3;
6689
6991
  var DEFAULT_LOCK_RETRY_MS2 = 25;
6690
6992
  var TMP_SUFFIX2 = ".tmp";
6691
- var WORKSPACE_TOOLBOX_RELATIVE_PATH = path24.join(".github", ".serviceme-toolbox.json");
6993
+ var WORKSPACE_TOOLBOX_RELATIVE_PATH = path25.join(".github", ".serviceme-toolbox.json");
6692
6994
  var LEGACY_WORKSPACE_TOOLBOX_FILENAME = ".ms-devtools-toolbox.json";
6693
6995
  async function migrateLegacyWorkspaceToolboxFile(filePath) {
6694
6996
  if (!filePath) return;
6695
- const legacyPath = path24.join(path24.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);
6997
+ const legacyPath = path25.join(path25.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);
6696
6998
  if (legacyPath === filePath) return;
6697
6999
  try {
6698
7000
  await fsp2.access(filePath);
@@ -6737,7 +7039,7 @@ var FsToolboxFileBackend = class {
6737
7039
  }
6738
7040
  }
6739
7041
  async write(filePath, payload) {
6740
- await fsp2.mkdir(path24.dirname(filePath), { recursive: true });
7042
+ await fsp2.mkdir(path25.dirname(filePath), { recursive: true });
6741
7043
  const tmpPath = `${filePath}${TMP_SUFFIX2}`;
6742
7044
  const bytes = Buffer.from(JSON.stringify(payload, null, " "), "utf8");
6743
7045
  await fsp2.rm(tmpPath, { force: true });
@@ -6769,10 +7071,19 @@ function coercePersistedToolbox(parsed) {
6769
7071
  function isNodeError2(value) {
6770
7072
  return value instanceof Error && typeof value.code === "string";
6771
7073
  }
7074
+ function isProcessAlive2(pid) {
7075
+ try {
7076
+ process.kill(pid, 0);
7077
+ return true;
7078
+ } catch {
7079
+ return false;
7080
+ }
7081
+ }
6772
7082
  var ToolboxFileLock = class {
6773
7083
  constructor(filePath, timeoutMs, retryMs) {
6774
7084
  this.acquired = false;
6775
7085
  this.dirPath = `${filePath}.lock`;
7086
+ this.pidFilePath = path25.join(this.dirPath, "pid");
6776
7087
  this.timeoutMs = timeoutMs;
6777
7088
  this.retryMs = retryMs;
6778
7089
  }
@@ -6781,17 +7092,33 @@ var ToolboxFileLock = class {
6781
7092
  while (true) {
6782
7093
  try {
6783
7094
  await fsp2.mkdir(this.dirPath, { mode: LOCK_DIR_MODE2 });
7095
+ await fsp2.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
6784
7096
  this.acquired = true;
6785
7097
  return;
6786
7098
  } catch (err) {
6787
7099
  if (!isNodeError2(err) || err.code !== "EEXIST") throw err;
7100
+ const stale = await this.isStaleLock();
7101
+ if (stale) {
7102
+ await fsp2.rm(this.dirPath, { recursive: true, force: true });
7103
+ continue;
7104
+ }
6788
7105
  if (Date.now() - start >= this.timeoutMs) {
6789
7106
  throw new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);
6790
7107
  }
6791
- await (0, import_promises3.setTimeout)(this.retryMs);
7108
+ await (0, import_promises4.setTimeout)(this.retryMs);
6792
7109
  }
6793
7110
  }
6794
7111
  }
7112
+ async isStaleLock() {
7113
+ try {
7114
+ const pidStr = await fsp2.readFile(this.pidFilePath, "utf8");
7115
+ const pid = Number.parseInt(pidStr.trim(), 10);
7116
+ if (!Number.isFinite(pid) || pid <= 0) return true;
7117
+ return !isProcessAlive2(pid);
7118
+ } catch {
7119
+ return true;
7120
+ }
7121
+ }
6795
7122
  async release() {
6796
7123
  if (!this.acquired) return;
6797
7124
  this.acquired = false;
@@ -6800,7 +7127,7 @@ var ToolboxFileLock = class {
6800
7127
  };
6801
7128
  function defaultWorkspacePath() {
6802
7129
  if (process.env.SERVICEME_NO_WORKSPACE_TOOLBOX === "1") return null;
6803
- return path24.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);
7130
+ return path25.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);
6804
7131
  }
6805
7132
  var ToolboxStore = class {
6806
7133
  constructor(opts = {}) {
@@ -7108,6 +7435,7 @@ var ToolboxCore = class {
7108
7435
  SCHEDULER_LOCK_FILENAME,
7109
7436
  SCHEDULER_LOG_FILENAME,
7110
7437
  SCHEDULER_PID_FILENAME,
7438
+ SERVER_PROXY_GLOBAL_FILENAME,
7111
7439
  SERVICEME_DIR_NAME,
7112
7440
  SERVICEME_HOME_ENV,
7113
7441
  SKILL_DRAFTS_SUBDIR,
@@ -7175,6 +7503,7 @@ var ToolboxCore = class {
7175
7503
  getSchedulerLockPath,
7176
7504
  getSchedulerLogPath,
7177
7505
  getSchedulerPidPath,
7506
+ getServerProxyGlobalPath,
7178
7507
  getServicemeHome,
7179
7508
  getSkillDraftsDir,
7180
7509
  getToolboxJsonPath,
@@ -7185,6 +7514,7 @@ var ToolboxCore = class {
7185
7514
  isUserRepo,
7186
7515
  matchesCron,
7187
7516
  mergeWithDefaults,
7517
+ migrateLegacyServerProxyEnabled,
7188
7518
  migrateToGlobal,
7189
7519
  moveFiles,
7190
7520
  narrowRepoConfig,
@@ -7192,6 +7522,7 @@ var ToolboxCore = class {
7192
7522
  parseAgentToolPermissions,
7193
7523
  parseIntervalMs,
7194
7524
  randomInstallationId,
7525
+ readServerProxyGlobal,
7195
7526
  reindexOrder,
7196
7527
  reposFileSchema,
7197
7528
  resetUserHomeOverrides,
@@ -7206,6 +7537,7 @@ var ToolboxCore = class {
7206
7537
  unzipFile,
7207
7538
  userRepoConfigSchema,
7208
7539
  validateReposFile,
7209
- validateTaskPayload
7540
+ validateTaskPayload,
7541
+ writeServerProxyGlobal
7210
7542
  });
7211
7543
  //# sourceMappingURL=index.js.map