episoda 0.2.93 → 0.2.95

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.
@@ -1571,15 +1571,15 @@ var require_git_executor = __commonJS({
1571
1571
  try {
1572
1572
  const { stdout: gitDir } = await execAsync3("git rev-parse --git-dir", { cwd, timeout: 5e3 });
1573
1573
  const gitDirPath = gitDir.trim();
1574
- const fs21 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1574
+ const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1575
1575
  const rebaseMergePath = `${gitDirPath}/rebase-merge`;
1576
1576
  const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
1577
1577
  try {
1578
- await fs21.access(rebaseMergePath);
1578
+ await fs22.access(rebaseMergePath);
1579
1579
  inRebase = true;
1580
1580
  } catch {
1581
1581
  try {
1582
- await fs21.access(rebaseApplyPath);
1582
+ await fs22.access(rebaseApplyPath);
1583
1583
  inRebase = true;
1584
1584
  } catch {
1585
1585
  inRebase = false;
@@ -1633,9 +1633,9 @@ var require_git_executor = __commonJS({
1633
1633
  error: validation.error || "UNKNOWN_ERROR"
1634
1634
  };
1635
1635
  }
1636
- const fs21 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1636
+ const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1637
1637
  try {
1638
- await fs21.access(command.path);
1638
+ await fs22.access(command.path);
1639
1639
  return {
1640
1640
  success: false,
1641
1641
  error: "WORKTREE_EXISTS",
@@ -1689,9 +1689,9 @@ var require_git_executor = __commonJS({
1689
1689
  */
1690
1690
  async executeWorktreeRemove(command, cwd, options) {
1691
1691
  try {
1692
- const fs21 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1692
+ const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1693
1693
  try {
1694
- await fs21.access(command.path);
1694
+ await fs22.access(command.path);
1695
1695
  } catch {
1696
1696
  return {
1697
1697
  success: false,
@@ -1726,7 +1726,7 @@ var require_git_executor = __commonJS({
1726
1726
  const result = await this.runGitCommand(args, cwd, options);
1727
1727
  if (result.success) {
1728
1728
  try {
1729
- await fs21.rm(command.path, { recursive: true, force: true });
1729
+ await fs22.rm(command.path, { recursive: true, force: true });
1730
1730
  } catch {
1731
1731
  }
1732
1732
  return {
@@ -1860,10 +1860,10 @@ var require_git_executor = __commonJS({
1860
1860
  */
1861
1861
  async executeCloneBare(command, options) {
1862
1862
  try {
1863
- const fs21 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1864
- const path22 = await Promise.resolve().then(() => __importStar(require("path")));
1863
+ const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1864
+ const path23 = await Promise.resolve().then(() => __importStar(require("path")));
1865
1865
  try {
1866
- await fs21.access(command.path);
1866
+ await fs22.access(command.path);
1867
1867
  return {
1868
1868
  success: false,
1869
1869
  error: "BRANCH_ALREADY_EXISTS",
@@ -1872,9 +1872,9 @@ var require_git_executor = __commonJS({
1872
1872
  };
1873
1873
  } catch {
1874
1874
  }
1875
- const parentDir = path22.dirname(command.path);
1875
+ const parentDir = path23.dirname(command.path);
1876
1876
  try {
1877
- await fs21.mkdir(parentDir, { recursive: true });
1877
+ await fs22.mkdir(parentDir, { recursive: true });
1878
1878
  } catch {
1879
1879
  }
1880
1880
  const { stdout, stderr } = await execAsync3(
@@ -1922,22 +1922,22 @@ var require_git_executor = __commonJS({
1922
1922
  */
1923
1923
  async executeProjectInfo(cwd, options) {
1924
1924
  try {
1925
- const fs21 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1926
- const path22 = await Promise.resolve().then(() => __importStar(require("path")));
1925
+ const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1926
+ const path23 = await Promise.resolve().then(() => __importStar(require("path")));
1927
1927
  let currentPath = cwd;
1928
1928
  let projectPath = cwd;
1929
1929
  let bareRepoPath;
1930
1930
  for (let i = 0; i < 10; i++) {
1931
- const bareDir = path22.join(currentPath, ".bare");
1932
- const episodaDir = path22.join(currentPath, ".episoda");
1931
+ const bareDir = path23.join(currentPath, ".bare");
1932
+ const episodaDir = path23.join(currentPath, ".episoda");
1933
1933
  try {
1934
- await fs21.access(bareDir);
1935
- await fs21.access(episodaDir);
1934
+ await fs22.access(bareDir);
1935
+ await fs22.access(episodaDir);
1936
1936
  projectPath = currentPath;
1937
1937
  bareRepoPath = bareDir;
1938
1938
  break;
1939
1939
  } catch {
1940
- const parentPath = path22.dirname(currentPath);
1940
+ const parentPath = path23.dirname(currentPath);
1941
1941
  if (parentPath === currentPath) {
1942
1942
  break;
1943
1943
  }
@@ -2153,7 +2153,7 @@ var require_websocket_client = __commonJS({
2153
2153
  clearTimeout(this.reconnectTimeout);
2154
2154
  this.reconnectTimeout = void 0;
2155
2155
  }
2156
- return new Promise((resolve3, reject) => {
2156
+ return new Promise((resolve4, reject) => {
2157
2157
  const connectionTimeout = setTimeout(() => {
2158
2158
  if (this.ws) {
2159
2159
  this.ws.terminate();
@@ -2182,7 +2182,7 @@ var require_websocket_client = __commonJS({
2182
2182
  daemonPid: this.daemonPid
2183
2183
  });
2184
2184
  this.startHeartbeat();
2185
- resolve3();
2185
+ resolve4();
2186
2186
  });
2187
2187
  this.ws.on("pong", () => {
2188
2188
  if (this.heartbeatTimeoutTimer) {
@@ -2313,13 +2313,13 @@ var require_websocket_client = __commonJS({
2313
2313
  console.warn("[EpisodaClient] Cannot send - WebSocket not connected");
2314
2314
  return false;
2315
2315
  }
2316
- return new Promise((resolve3) => {
2316
+ return new Promise((resolve4) => {
2317
2317
  this.ws.send(JSON.stringify(message), (error) => {
2318
2318
  if (error) {
2319
2319
  console.error("[EpisodaClient] Failed to send message:", error);
2320
- resolve3(false);
2320
+ resolve4(false);
2321
2321
  } else {
2322
- resolve3(true);
2322
+ resolve4(true);
2323
2323
  }
2324
2324
  });
2325
2325
  });
@@ -2572,31 +2572,31 @@ var require_auth = __commonJS({
2572
2572
  exports2.loadConfig = loadConfig8;
2573
2573
  exports2.saveConfig = saveConfig2;
2574
2574
  exports2.validateToken = validateToken;
2575
- var fs21 = __importStar(require("fs"));
2576
- var path22 = __importStar(require("path"));
2575
+ var fs22 = __importStar(require("fs"));
2576
+ var path23 = __importStar(require("path"));
2577
2577
  var os9 = __importStar(require("os"));
2578
2578
  var child_process_1 = require("child_process");
2579
2579
  var DEFAULT_CONFIG_FILE = "config.json";
2580
2580
  function getConfigDir8() {
2581
- return process.env.EPISODA_CONFIG_DIR || path22.join(os9.homedir(), ".episoda");
2581
+ return process.env.EPISODA_CONFIG_DIR || path23.join(os9.homedir(), ".episoda");
2582
2582
  }
2583
2583
  function getConfigPath(configPath) {
2584
2584
  if (configPath) {
2585
2585
  return configPath;
2586
2586
  }
2587
- return path22.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
2587
+ return path23.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
2588
2588
  }
2589
2589
  function ensureConfigDir(configPath) {
2590
- const dir = path22.dirname(configPath);
2591
- const isNew = !fs21.existsSync(dir);
2590
+ const dir = path23.dirname(configPath);
2591
+ const isNew = !fs22.existsSync(dir);
2592
2592
  if (isNew) {
2593
- fs21.mkdirSync(dir, { recursive: true, mode: 448 });
2593
+ fs22.mkdirSync(dir, { recursive: true, mode: 448 });
2594
2594
  }
2595
2595
  if (process.platform === "darwin") {
2596
- const nosyncPath = path22.join(dir, ".nosync");
2597
- if (isNew || !fs21.existsSync(nosyncPath)) {
2596
+ const nosyncPath = path23.join(dir, ".nosync");
2597
+ if (isNew || !fs22.existsSync(nosyncPath)) {
2598
2598
  try {
2599
- fs21.writeFileSync(nosyncPath, "", { mode: 384 });
2599
+ fs22.writeFileSync(nosyncPath, "", { mode: 384 });
2600
2600
  (0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
2601
2601
  stdio: "ignore",
2602
2602
  timeout: 5e3
@@ -2608,9 +2608,9 @@ var require_auth = __commonJS({
2608
2608
  }
2609
2609
  async function loadConfig8(configPath) {
2610
2610
  const fullPath = getConfigPath(configPath);
2611
- if (fs21.existsSync(fullPath)) {
2611
+ if (fs22.existsSync(fullPath)) {
2612
2612
  try {
2613
- const content = fs21.readFileSync(fullPath, "utf8");
2613
+ const content = fs22.readFileSync(fullPath, "utf8");
2614
2614
  const config = JSON.parse(content);
2615
2615
  return config;
2616
2616
  } catch (error) {
@@ -2620,9 +2620,9 @@ var require_auth = __commonJS({
2620
2620
  if (process.env.EPISODA_MODE === "cloud" && process.env.EPISODA_WORKSPACE) {
2621
2621
  const homeDir = process.env.HOME || require("os").homedir();
2622
2622
  const workspaceConfigPath = require("path").join(homeDir, "episoda", process.env.EPISODA_WORKSPACE, ".episoda", "config.json");
2623
- if (fs21.existsSync(workspaceConfigPath)) {
2623
+ if (fs22.existsSync(workspaceConfigPath)) {
2624
2624
  try {
2625
- const content = fs21.readFileSync(workspaceConfigPath, "utf8");
2625
+ const content = fs22.readFileSync(workspaceConfigPath, "utf8");
2626
2626
  const workspaceConfig = JSON.parse(content);
2627
2627
  return {
2628
2628
  access_token: process.env.EPISODA_ACCESS_TOKEN || workspaceConfig.accessToken,
@@ -2669,7 +2669,7 @@ var require_auth = __commonJS({
2669
2669
  ensureConfigDir(fullPath);
2670
2670
  try {
2671
2671
  const content = JSON.stringify(config, null, 2);
2672
- fs21.writeFileSync(fullPath, content, { mode: 384 });
2672
+ fs22.writeFileSync(fullPath, content, { mode: 384 });
2673
2673
  } catch (error) {
2674
2674
  throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
2675
2675
  }
@@ -2786,7 +2786,7 @@ var require_package = __commonJS({
2786
2786
  "package.json"(exports2, module2) {
2787
2787
  module2.exports = {
2788
2788
  name: "episoda",
2789
- version: "0.2.93",
2789
+ version: "0.2.95",
2790
2790
  description: "CLI tool for Episoda local development workflow orchestration",
2791
2791
  main: "dist/index.js",
2792
2792
  types: "dist/index.d.ts",
@@ -2821,7 +2821,8 @@ var require_package = __commonJS({
2821
2821
  },
2822
2822
  optionalDependencies: {
2823
2823
  "@anthropic-ai/claude-code": "^2.0.0",
2824
- "@openai/codex": "^0.86.0"
2824
+ "@openai/codex": "^0.86.0",
2825
+ "@modelcontextprotocol/server-github": "^0.6.0"
2825
2826
  },
2826
2827
  devDependencies: {
2827
2828
  "@episoda/core": "*",
@@ -3088,10 +3089,10 @@ var IPCServer = class {
3088
3089
  this.server = net.createServer((socket) => {
3089
3090
  this.handleConnection(socket);
3090
3091
  });
3091
- return new Promise((resolve3, reject) => {
3092
+ return new Promise((resolve4, reject) => {
3092
3093
  this.server.listen(socketPath, () => {
3093
3094
  fs3.chmodSync(socketPath, 384);
3094
- resolve3();
3095
+ resolve4();
3095
3096
  });
3096
3097
  this.server.on("error", reject);
3097
3098
  });
@@ -3102,12 +3103,12 @@ var IPCServer = class {
3102
3103
  async stop() {
3103
3104
  if (!this.server) return;
3104
3105
  const socketPath = getSocketPath();
3105
- return new Promise((resolve3) => {
3106
+ return new Promise((resolve4) => {
3106
3107
  this.server.close(() => {
3107
3108
  if (fs3.existsSync(socketPath)) {
3108
3109
  fs3.unlinkSync(socketPath);
3109
3110
  }
3110
- resolve3();
3111
+ resolve4();
3111
3112
  });
3112
3113
  });
3113
3114
  }
@@ -3240,18 +3241,171 @@ function performBackgroundUpdate(targetVersion) {
3240
3241
  }
3241
3242
 
3242
3243
  // src/daemon/handlers/file-handlers.ts
3243
- var fs4 = __toESM(require("fs"));
3244
- var path5 = __toESM(require("path"));
3244
+ var fs5 = __toESM(require("fs"));
3245
+ var path6 = __toESM(require("path"));
3245
3246
  var readline = __toESM(require("readline"));
3246
- var DEFAULT_MAX_FILE_SIZE = 20 * 1024 * 1024;
3247
- function validatePath(filePath, projectPath) {
3247
+
3248
+ // src/daemon/permissions/path-classifier.ts
3249
+ var path5 = __toESM(require("path"));
3250
+ var fs4 = __toESM(require("fs"));
3251
+ function classifyPath(targetPath, options) {
3252
+ const { workspaceRoot, projectRoot } = options;
3253
+ const normalizedWorkspace = path5.resolve(workspaceRoot);
3254
+ const normalizedProject = path5.resolve(projectRoot);
3255
+ const resolvedPath = path5.isAbsolute(targetPath) ? path5.resolve(targetPath) : path5.resolve(projectRoot, targetPath);
3256
+ const artifactsDir = path5.join(normalizedWorkspace, "artifacts");
3257
+ if (resolvedPath.startsWith(artifactsDir + path5.sep) || resolvedPath === artifactsDir) {
3258
+ return {
3259
+ classification: "artifacts",
3260
+ resolvedPath
3261
+ };
3262
+ }
3263
+ if (!resolvedPath.startsWith(normalizedProject + path5.sep) && resolvedPath !== normalizedProject) {
3264
+ if (resolvedPath.startsWith(normalizedWorkspace + path5.sep)) {
3265
+ return {
3266
+ classification: "unknown",
3267
+ resolvedPath
3268
+ };
3269
+ }
3270
+ return {
3271
+ classification: "unknown",
3272
+ resolvedPath
3273
+ };
3274
+ }
3275
+ const relativePath = path5.relative(normalizedProject, resolvedPath);
3276
+ const pathParts = relativePath.split(path5.sep);
3277
+ if (pathParts[0] === ".bare") {
3278
+ return {
3279
+ classification: "bare_repo",
3280
+ resolvedPath
3281
+ };
3282
+ }
3283
+ if (pathParts[0] === ".episoda") {
3284
+ return {
3285
+ classification: "config",
3286
+ resolvedPath
3287
+ };
3288
+ }
3289
+ const firstPart = pathParts[0];
3290
+ if (firstPart && isModuleUidPattern(firstPart)) {
3291
+ const worktreeDir = path5.join(normalizedProject, firstPart);
3292
+ const gitFile = path5.join(worktreeDir, ".git");
3293
+ const worktreeExists = fs4.existsSync(gitFile) && fs4.statSync(gitFile).isFile();
3294
+ return {
3295
+ classification: "worktree",
3296
+ moduleUid: firstPart,
3297
+ worktreeExists,
3298
+ resolvedPath
3299
+ };
3300
+ }
3301
+ return {
3302
+ classification: "project_root",
3303
+ resolvedPath
3304
+ };
3305
+ }
3306
+ function isModuleUidPattern(str) {
3307
+ return /^EP\d+$/.test(str);
3308
+ }
3309
+ function deriveWorkspaceRoot(projectPath) {
3310
+ return path5.dirname(path5.resolve(projectPath));
3311
+ }
3312
+ function validateWorkspacePath(filePath, projectPath) {
3248
3313
  const normalizedProjectPath = path5.resolve(projectPath);
3314
+ const workspaceRoot = deriveWorkspaceRoot(projectPath);
3315
+ const normalizedWorkspace = path5.resolve(workspaceRoot);
3316
+ const artifactsDir = path5.join(normalizedWorkspace, "artifacts");
3249
3317
  const absolutePath = path5.isAbsolute(filePath) ? path5.resolve(filePath) : path5.resolve(projectPath, filePath);
3250
3318
  const normalizedPath = path5.normalize(absolutePath);
3251
- if (!normalizedPath.startsWith(normalizedProjectPath + path5.sep) && normalizedPath !== normalizedProjectPath) {
3252
- return null;
3319
+ if (normalizedPath.startsWith(normalizedProjectPath + path5.sep) || normalizedPath === normalizedProjectPath) {
3320
+ return normalizedPath;
3253
3321
  }
3254
- return normalizedPath;
3322
+ if (normalizedPath.startsWith(artifactsDir + path5.sep) || normalizedPath === artifactsDir) {
3323
+ return normalizedPath;
3324
+ }
3325
+ return null;
3326
+ }
3327
+
3328
+ // src/daemon/permissions/write-permission.ts
3329
+ function checkWritePermission(targetPath, projectPath) {
3330
+ const workspaceRoot = deriveWorkspaceRoot(projectPath);
3331
+ const classification = classifyPath(targetPath, {
3332
+ workspaceRoot,
3333
+ projectRoot: projectPath
3334
+ });
3335
+ return evaluatePermission(classification);
3336
+ }
3337
+ function evaluatePermission(classification) {
3338
+ const { classification: pathType, moduleUid, worktreeExists, resolvedPath } = classification;
3339
+ switch (pathType) {
3340
+ case "artifacts":
3341
+ return {
3342
+ allowed: true,
3343
+ classification: pathType
3344
+ };
3345
+ case "bare_repo":
3346
+ return {
3347
+ allowed: false,
3348
+ reason: "Cannot write to .bare/ directory - this is the protected git repository",
3349
+ classification: pathType
3350
+ };
3351
+ case "config":
3352
+ return {
3353
+ allowed: false,
3354
+ reason: "Cannot write to .episoda/ directory - this is protected configuration",
3355
+ classification: pathType
3356
+ };
3357
+ case "worktree":
3358
+ if (worktreeExists) {
3359
+ return {
3360
+ allowed: true,
3361
+ classification: pathType,
3362
+ moduleUid
3363
+ };
3364
+ } else {
3365
+ return {
3366
+ allowed: false,
3367
+ reason: `Cannot write to ${moduleUid}/ - worktree does not exist. Module must be in 'doing' or 'review' state with an active worktree.`,
3368
+ classification: pathType,
3369
+ moduleUid
3370
+ };
3371
+ }
3372
+ case "project_root":
3373
+ return {
3374
+ allowed: false,
3375
+ reason: "Cannot write directly to project root. Files must be in a worktree directory or artifacts/",
3376
+ classification: pathType
3377
+ };
3378
+ case "unknown":
3379
+ default:
3380
+ return {
3381
+ allowed: false,
3382
+ reason: "Path is outside the allowed project directory structure",
3383
+ classification: pathType
3384
+ };
3385
+ }
3386
+ }
3387
+ function formatPermissionDenied(result) {
3388
+ if (result.allowed) {
3389
+ return "Write allowed";
3390
+ }
3391
+ const baseMessage = result.reason || "Write permission denied";
3392
+ switch (result.classification) {
3393
+ case "worktree":
3394
+ return `${baseMessage} Use the platform to move the module to 'doing' state first.`;
3395
+ case "bare_repo":
3396
+ case "config":
3397
+ return `${baseMessage} These directories are managed by the system.`;
3398
+ case "project_root":
3399
+ return `${baseMessage} Create files within your worktree (e.g., ${result.moduleUid || "EPxxx"}/src/...) or use artifacts/ for outputs.`;
3400
+ default:
3401
+ return baseMessage;
3402
+ }
3403
+ }
3404
+
3405
+ // src/daemon/handlers/file-handlers.ts
3406
+ var DEFAULT_MAX_FILE_SIZE = 20 * 1024 * 1024;
3407
+ function validatePath(filePath, projectPath) {
3408
+ return validateWorkspacePath(filePath, projectPath);
3255
3409
  }
3256
3410
  async function handleFileRead(command, projectPath) {
3257
3411
  const { path: filePath, encoding = "base64", maxSize = DEFAULT_MAX_FILE_SIZE } = command;
@@ -3263,13 +3417,13 @@ async function handleFileRead(command, projectPath) {
3263
3417
  };
3264
3418
  }
3265
3419
  try {
3266
- if (!fs4.existsSync(validPath)) {
3420
+ if (!fs5.existsSync(validPath)) {
3267
3421
  return {
3268
3422
  success: false,
3269
3423
  error: "File not found"
3270
3424
  };
3271
3425
  }
3272
- const stats = fs4.statSync(validPath);
3426
+ const stats = fs5.statSync(validPath);
3273
3427
  if (stats.isDirectory()) {
3274
3428
  return {
3275
3429
  success: false,
@@ -3282,7 +3436,7 @@ async function handleFileRead(command, projectPath) {
3282
3436
  error: `File too large: ${stats.size} bytes exceeds limit of ${maxSize} bytes`
3283
3437
  };
3284
3438
  }
3285
- const buffer = fs4.readFileSync(validPath);
3439
+ const buffer = fs5.readFileSync(validPath);
3286
3440
  let content;
3287
3441
  if (encoding === "base64") {
3288
3442
  content = buffer.toString("base64");
@@ -3313,11 +3467,18 @@ async function handleFileWrite(command, projectPath) {
3313
3467
  error: "Invalid path: directory traversal not allowed"
3314
3468
  };
3315
3469
  }
3470
+ const permission = checkWritePermission(validPath, projectPath);
3471
+ if (!permission.allowed) {
3472
+ return {
3473
+ success: false,
3474
+ error: formatPermissionDenied(permission)
3475
+ };
3476
+ }
3316
3477
  try {
3317
3478
  if (createDirs) {
3318
- const dirPath = path5.dirname(validPath);
3319
- if (!fs4.existsSync(dirPath)) {
3320
- fs4.mkdirSync(dirPath, { recursive: true });
3479
+ const dirPath = path6.dirname(validPath);
3480
+ if (!fs5.existsSync(dirPath)) {
3481
+ fs5.mkdirSync(dirPath, { recursive: true });
3321
3482
  }
3322
3483
  }
3323
3484
  let buffer;
@@ -3327,8 +3488,8 @@ async function handleFileWrite(command, projectPath) {
3327
3488
  buffer = Buffer.from(content, "utf8");
3328
3489
  }
3329
3490
  const tempPath = `${validPath}.tmp.${Date.now()}`;
3330
- fs4.writeFileSync(tempPath, buffer);
3331
- fs4.renameSync(tempPath, validPath);
3491
+ fs5.writeFileSync(tempPath, buffer);
3492
+ fs5.renameSync(tempPath, validPath);
3332
3493
  return {
3333
3494
  success: true,
3334
3495
  bytesWritten: buffer.length
@@ -3353,13 +3514,13 @@ async function handleFileList(command, projectPath) {
3353
3514
  };
3354
3515
  }
3355
3516
  try {
3356
- if (!fs4.existsSync(validPath)) {
3517
+ if (!fs5.existsSync(validPath)) {
3357
3518
  return {
3358
3519
  success: false,
3359
3520
  error: "Directory not found"
3360
3521
  };
3361
3522
  }
3362
- const stats = fs4.statSync(validPath);
3523
+ const stats = fs5.statSync(validPath);
3363
3524
  if (!stats.isDirectory()) {
3364
3525
  return {
3365
3526
  success: false,
@@ -3370,11 +3531,11 @@ async function handleFileList(command, projectPath) {
3370
3531
  if (recursive) {
3371
3532
  await listDirectoryRecursive(validPath, validPath, entries, includeHidden);
3372
3533
  } else {
3373
- const dirEntries = await fs4.promises.readdir(validPath, { withFileTypes: true });
3534
+ const dirEntries = await fs5.promises.readdir(validPath, { withFileTypes: true });
3374
3535
  for (const entry of dirEntries) {
3375
3536
  if (!includeHidden && entry.name.startsWith(".")) continue;
3376
- const entryPath = path5.join(validPath, entry.name);
3377
- const entryStats = await fs4.promises.stat(entryPath);
3537
+ const entryPath = path6.join(validPath, entry.name);
3538
+ const entryStats = await fs5.promises.stat(entryPath);
3378
3539
  entries.push({
3379
3540
  name: entry.name,
3380
3541
  type: entry.isDirectory() ? "directory" : "file",
@@ -3396,13 +3557,13 @@ async function handleFileList(command, projectPath) {
3396
3557
  }
3397
3558
  }
3398
3559
  async function listDirectoryRecursive(basePath, currentPath, entries, includeHidden) {
3399
- const dirEntries = await fs4.promises.readdir(currentPath, { withFileTypes: true });
3560
+ const dirEntries = await fs5.promises.readdir(currentPath, { withFileTypes: true });
3400
3561
  for (const entry of dirEntries) {
3401
3562
  if (!includeHidden && entry.name.startsWith(".")) continue;
3402
- const entryPath = path5.join(currentPath, entry.name);
3403
- const relativePath = path5.relative(basePath, entryPath);
3563
+ const entryPath = path6.join(currentPath, entry.name);
3564
+ const relativePath = path6.relative(basePath, entryPath);
3404
3565
  try {
3405
- const entryStats = await fs4.promises.stat(entryPath);
3566
+ const entryStats = await fs5.promises.stat(entryPath);
3406
3567
  entries.push({
3407
3568
  name: relativePath,
3408
3569
  type: entry.isDirectory() ? "directory" : "file",
@@ -3426,7 +3587,7 @@ async function handleFileSearch(command, projectPath) {
3426
3587
  };
3427
3588
  }
3428
3589
  try {
3429
- if (!fs4.existsSync(validPath)) {
3590
+ if (!fs5.existsSync(validPath)) {
3430
3591
  return {
3431
3592
  success: false,
3432
3593
  error: "Directory not found"
@@ -3516,12 +3677,12 @@ function findClosingBracket(pattern, start) {
3516
3677
  }
3517
3678
  async function searchFilesRecursive(basePath, currentPath, pattern, files, maxResults) {
3518
3679
  if (files.length >= maxResults) return;
3519
- const entries = await fs4.promises.readdir(currentPath, { withFileTypes: true });
3680
+ const entries = await fs5.promises.readdir(currentPath, { withFileTypes: true });
3520
3681
  for (const entry of entries) {
3521
3682
  if (files.length >= maxResults) return;
3522
3683
  if (entry.name.startsWith(".")) continue;
3523
- const entryPath = path5.join(currentPath, entry.name);
3524
- const relativePath = path5.relative(basePath, entryPath);
3684
+ const entryPath = path6.join(currentPath, entry.name);
3685
+ const relativePath = path6.relative(basePath, entryPath);
3525
3686
  try {
3526
3687
  if (entry.isDirectory()) {
3527
3688
  await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults);
@@ -3549,7 +3710,7 @@ async function handleFileGrep(command, projectPath) {
3549
3710
  };
3550
3711
  }
3551
3712
  try {
3552
- if (!fs4.existsSync(validPath)) {
3713
+ if (!fs5.existsSync(validPath)) {
3553
3714
  return {
3554
3715
  success: false,
3555
3716
  error: "Path not found"
@@ -3558,7 +3719,7 @@ async function handleFileGrep(command, projectPath) {
3558
3719
  const matches = [];
3559
3720
  const searchRegex = new RegExp(pattern, caseSensitive ? "" : "i");
3560
3721
  const fileGlobRegex = globToRegex(filePattern);
3561
- const stats = fs4.statSync(validPath);
3722
+ const stats = fs5.statSync(validPath);
3562
3723
  if (stats.isFile()) {
3563
3724
  await grepFile(validPath, validPath, searchRegex, matches, effectiveMaxResults);
3564
3725
  } else {
@@ -3579,8 +3740,8 @@ async function handleFileGrep(command, projectPath) {
3579
3740
  }
3580
3741
  async function grepFile(basePath, filePath, pattern, matches, maxResults) {
3581
3742
  if (matches.length >= maxResults) return;
3582
- const relativePath = path5.relative(basePath, filePath);
3583
- const fileStream = fs4.createReadStream(filePath, { encoding: "utf8" });
3743
+ const relativePath = path6.relative(basePath, filePath);
3744
+ const fileStream = fs5.createReadStream(filePath, { encoding: "utf8" });
3584
3745
  const rl = readline.createInterface({
3585
3746
  input: fileStream,
3586
3747
  crlfDelay: Infinity
@@ -3594,7 +3755,7 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
3594
3755
  }
3595
3756
  if (pattern.test(line)) {
3596
3757
  matches.push({
3597
- file: relativePath || path5.basename(filePath),
3758
+ file: relativePath || path6.basename(filePath),
3598
3759
  line: lineNumber,
3599
3760
  content: line.slice(0, 500)
3600
3761
  // Truncate long lines
@@ -3604,11 +3765,11 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
3604
3765
  }
3605
3766
  async function grepDirectoryRecursive(basePath, currentPath, searchPattern, filePattern, matches, maxResults) {
3606
3767
  if (matches.length >= maxResults) return;
3607
- const entries = await fs4.promises.readdir(currentPath, { withFileTypes: true });
3768
+ const entries = await fs5.promises.readdir(currentPath, { withFileTypes: true });
3608
3769
  for (const entry of entries) {
3609
3770
  if (matches.length >= maxResults) return;
3610
3771
  if (entry.name.startsWith(".")) continue;
3611
- const entryPath = path5.join(currentPath, entry.name);
3772
+ const entryPath = path6.join(currentPath, entry.name);
3612
3773
  try {
3613
3774
  if (entry.isDirectory()) {
3614
3775
  await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults);
@@ -3628,14 +3789,21 @@ async function handleFileEdit(command, projectPath) {
3628
3789
  error: "Invalid path: directory traversal not allowed"
3629
3790
  };
3630
3791
  }
3792
+ const permission = checkWritePermission(validPath, projectPath);
3793
+ if (!permission.allowed) {
3794
+ return {
3795
+ success: false,
3796
+ error: formatPermissionDenied(permission)
3797
+ };
3798
+ }
3631
3799
  try {
3632
- if (!fs4.existsSync(validPath)) {
3800
+ if (!fs5.existsSync(validPath)) {
3633
3801
  return {
3634
3802
  success: false,
3635
3803
  error: "File not found"
3636
3804
  };
3637
3805
  }
3638
- const stats = fs4.statSync(validPath);
3806
+ const stats = fs5.statSync(validPath);
3639
3807
  if (stats.isDirectory()) {
3640
3808
  return {
3641
3809
  success: false,
@@ -3649,7 +3817,7 @@ async function handleFileEdit(command, projectPath) {
3649
3817
  error: `File too large for edit operation: ${stats.size} bytes exceeds limit of ${MAX_EDIT_SIZE} bytes (10MB). Use write-file for large files.`
3650
3818
  };
3651
3819
  }
3652
- const content = fs4.readFileSync(validPath, "utf8");
3820
+ const content = fs5.readFileSync(validPath, "utf8");
3653
3821
  const occurrences = content.split(oldString).length - 1;
3654
3822
  if (occurrences === 0) {
3655
3823
  return {
@@ -3666,8 +3834,8 @@ async function handleFileEdit(command, projectPath) {
3666
3834
  const newContent = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
3667
3835
  const replacements = replaceAll ? occurrences : 1;
3668
3836
  const tempPath = `${validPath}.tmp.${Date.now()}`;
3669
- fs4.writeFileSync(tempPath, newContent, "utf8");
3670
- fs4.renameSync(tempPath, validPath);
3837
+ fs5.writeFileSync(tempPath, newContent, "utf8");
3838
+ fs5.renameSync(tempPath, validPath);
3671
3839
  const newSize = Buffer.byteLength(newContent, "utf8");
3672
3840
  return {
3673
3841
  success: true,
@@ -3692,21 +3860,28 @@ async function handleFileDelete(command, projectPath) {
3692
3860
  error: "Invalid path: directory traversal not allowed"
3693
3861
  };
3694
3862
  }
3695
- const normalizedProjectPath = path5.resolve(projectPath);
3863
+ const normalizedProjectPath = path6.resolve(projectPath);
3696
3864
  if (validPath === normalizedProjectPath) {
3697
3865
  return {
3698
3866
  success: false,
3699
3867
  error: "Cannot delete project root directory"
3700
3868
  };
3701
3869
  }
3870
+ const permission = checkWritePermission(validPath, projectPath);
3871
+ if (!permission.allowed) {
3872
+ return {
3873
+ success: false,
3874
+ error: formatPermissionDenied(permission)
3875
+ };
3876
+ }
3702
3877
  try {
3703
- if (!fs4.existsSync(validPath)) {
3878
+ if (!fs5.existsSync(validPath)) {
3704
3879
  return {
3705
3880
  success: false,
3706
3881
  error: "Path not found"
3707
3882
  };
3708
3883
  }
3709
- const stats = fs4.statSync(validPath);
3884
+ const stats = fs5.statSync(validPath);
3710
3885
  const isDirectory = stats.isDirectory();
3711
3886
  if (isDirectory && !recursive) {
3712
3887
  return {
@@ -3715,9 +3890,9 @@ async function handleFileDelete(command, projectPath) {
3715
3890
  };
3716
3891
  }
3717
3892
  if (isDirectory) {
3718
- fs4.rmSync(validPath, { recursive: true, force: true });
3893
+ fs5.rmSync(validPath, { recursive: true, force: true });
3719
3894
  } else {
3720
- fs4.unlinkSync(validPath);
3895
+ fs5.unlinkSync(validPath);
3721
3896
  }
3722
3897
  return {
3723
3898
  success: true,
@@ -3742,9 +3917,16 @@ async function handleFileMkdir(command, projectPath) {
3742
3917
  error: "Invalid path: directory traversal not allowed"
3743
3918
  };
3744
3919
  }
3920
+ const permission = checkWritePermission(validPath, projectPath);
3921
+ if (!permission.allowed) {
3922
+ return {
3923
+ success: false,
3924
+ error: formatPermissionDenied(permission)
3925
+ };
3926
+ }
3745
3927
  try {
3746
- if (fs4.existsSync(validPath)) {
3747
- const stats = fs4.statSync(validPath);
3928
+ if (fs5.existsSync(validPath)) {
3929
+ const stats = fs5.statSync(validPath);
3748
3930
  if (stats.isDirectory()) {
3749
3931
  return {
3750
3932
  success: true,
@@ -3759,7 +3941,7 @@ async function handleFileMkdir(command, projectPath) {
3759
3941
  }
3760
3942
  }
3761
3943
  const modeNum = parseInt(mode, 8);
3762
- fs4.mkdirSync(validPath, { recursive: true, mode: modeNum });
3944
+ fs5.mkdirSync(validPath, { recursive: true, mode: modeNum });
3763
3945
  return {
3764
3946
  success: true,
3765
3947
  created: true
@@ -3786,7 +3968,7 @@ async function handleExec(command, projectPath) {
3786
3968
  env = {}
3787
3969
  } = command;
3788
3970
  const effectiveTimeout = Math.min(Math.max(timeout, 1e3), MAX_TIMEOUT);
3789
- return new Promise((resolve3) => {
3971
+ return new Promise((resolve4) => {
3790
3972
  let stdout = "";
3791
3973
  let stderr = "";
3792
3974
  let timedOut = false;
@@ -3794,7 +3976,7 @@ async function handleExec(command, projectPath) {
3794
3976
  const done = (result) => {
3795
3977
  if (resolved) return;
3796
3978
  resolved = true;
3797
- resolve3(result);
3979
+ resolve4(result);
3798
3980
  };
3799
3981
  try {
3800
3982
  const proc = (0, import_child_process3.spawn)(cmd, {
@@ -3858,14 +4040,14 @@ async function handleExec(command, projectPath) {
3858
4040
  }
3859
4041
 
3860
4042
  // src/daemon/handlers/worktree-handlers.ts
3861
- var path14 = __toESM(require("path"));
3862
- var fs13 = __toESM(require("fs"));
4043
+ var path15 = __toESM(require("path"));
4044
+ var fs14 = __toESM(require("fs"));
3863
4045
  var import_child_process8 = require("child_process");
3864
4046
  var import_util = require("util");
3865
4047
 
3866
4048
  // src/daemon/worktree-manager.ts
3867
- var fs5 = __toESM(require("fs"));
3868
- var path6 = __toESM(require("path"));
4049
+ var fs6 = __toESM(require("fs"));
4050
+ var path7 = __toESM(require("path"));
3869
4051
  var import_core6 = __toESM(require_dist());
3870
4052
  function validateModuleUid(moduleUid) {
3871
4053
  if (!moduleUid || typeof moduleUid !== "string" || !moduleUid.trim()) {
@@ -3889,8 +4071,8 @@ var WorktreeManager = class _WorktreeManager {
3889
4071
  // ============================================================
3890
4072
  this.lockPath = "";
3891
4073
  this.projectRoot = projectRoot;
3892
- this.bareRepoPath = path6.join(projectRoot, ".bare");
3893
- this.configPath = path6.join(projectRoot, ".episoda", "config.json");
4074
+ this.bareRepoPath = path7.join(projectRoot, ".bare");
4075
+ this.configPath = path7.join(projectRoot, ".episoda", "config.json");
3894
4076
  this.gitExecutor = new import_core6.GitExecutor();
3895
4077
  }
3896
4078
  /**
@@ -3901,13 +4083,13 @@ var WorktreeManager = class _WorktreeManager {
3901
4083
  */
3902
4084
  async initialize() {
3903
4085
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
3904
- if (!fs5.existsSync(this.bareRepoPath)) {
4086
+ if (!fs6.existsSync(this.bareRepoPath)) {
3905
4087
  if (debug) {
3906
4088
  console.log(`[WorktreeManager] initialize: .bare not found at ${this.bareRepoPath}`);
3907
4089
  }
3908
4090
  return false;
3909
4091
  }
3910
- if (!fs5.existsSync(this.configPath)) {
4092
+ if (!fs6.existsSync(this.configPath)) {
3911
4093
  if (debug) {
3912
4094
  console.log(`[WorktreeManager] initialize: config not found at ${this.configPath}`);
3913
4095
  }
@@ -3966,8 +4148,8 @@ var WorktreeManager = class _WorktreeManager {
3966
4148
  */
3967
4149
  static async createProject(projectRoot, repoUrl, projectId, workspaceSlug, projectSlug) {
3968
4150
  const manager = new _WorktreeManager(projectRoot);
3969
- const episodaDir = path6.join(projectRoot, ".episoda");
3970
- fs5.mkdirSync(episodaDir, { recursive: true });
4151
+ const episodaDir = path7.join(projectRoot, ".episoda");
4152
+ fs6.mkdirSync(episodaDir, { recursive: true });
3971
4153
  const cloneResult = await manager.gitExecutor.execute({
3972
4154
  action: "clone_bare",
3973
4155
  url: repoUrl,
@@ -3998,7 +4180,7 @@ var WorktreeManager = class _WorktreeManager {
3998
4180
  error: `Invalid module UID: "${moduleUid}" - contains disallowed characters`
3999
4181
  };
4000
4182
  }
4001
- const worktreePath = path6.join(this.projectRoot, moduleUid);
4183
+ const worktreePath = path7.join(this.projectRoot, moduleUid);
4002
4184
  const lockAcquired = await this.acquireLock();
4003
4185
  if (!lockAcquired) {
4004
4186
  return {
@@ -4189,7 +4371,7 @@ var WorktreeManager = class _WorktreeManager {
4189
4371
  let prunedCount = 0;
4190
4372
  await this.updateConfigSafe((config) => {
4191
4373
  const initialCount = config.worktrees.length;
4192
- config.worktrees = config.worktrees.filter((w) => fs5.existsSync(w.worktreePath));
4374
+ config.worktrees = config.worktrees.filter((w) => fs6.existsSync(w.worktreePath));
4193
4375
  prunedCount = initialCount - config.worktrees.length;
4194
4376
  return config;
4195
4377
  });
@@ -4270,25 +4452,25 @@ var WorktreeManager = class _WorktreeManager {
4270
4452
  const retryInterval = 50;
4271
4453
  while (Date.now() - startTime < timeoutMs) {
4272
4454
  try {
4273
- fs5.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
4455
+ fs6.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
4274
4456
  return true;
4275
4457
  } catch (err) {
4276
4458
  if (err.code === "EEXIST") {
4277
4459
  try {
4278
- const stats = fs5.statSync(lockPath);
4460
+ const stats = fs6.statSync(lockPath);
4279
4461
  const lockAge = Date.now() - stats.mtimeMs;
4280
4462
  if (lockAge > 3e4) {
4281
4463
  try {
4282
- const lockContent = fs5.readFileSync(lockPath, "utf-8").trim();
4464
+ const lockContent = fs6.readFileSync(lockPath, "utf-8").trim();
4283
4465
  const lockPid = parseInt(lockContent, 10);
4284
4466
  if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
4285
- await new Promise((resolve3) => setTimeout(resolve3, retryInterval));
4467
+ await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
4286
4468
  continue;
4287
4469
  }
4288
4470
  } catch {
4289
4471
  }
4290
4472
  try {
4291
- fs5.unlinkSync(lockPath);
4473
+ fs6.unlinkSync(lockPath);
4292
4474
  } catch {
4293
4475
  }
4294
4476
  continue;
@@ -4296,7 +4478,7 @@ var WorktreeManager = class _WorktreeManager {
4296
4478
  } catch {
4297
4479
  continue;
4298
4480
  }
4299
- await new Promise((resolve3) => setTimeout(resolve3, retryInterval));
4481
+ await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
4300
4482
  continue;
4301
4483
  }
4302
4484
  throw err;
@@ -4309,7 +4491,7 @@ var WorktreeManager = class _WorktreeManager {
4309
4491
  */
4310
4492
  releaseLock() {
4311
4493
  try {
4312
- fs5.unlinkSync(this.getLockPath());
4494
+ fs6.unlinkSync(this.getLockPath());
4313
4495
  } catch {
4314
4496
  }
4315
4497
  }
@@ -4333,11 +4515,11 @@ var WorktreeManager = class _WorktreeManager {
4333
4515
  // Turborepo cache
4334
4516
  ];
4335
4517
  for (const cacheDir of cacheDirs) {
4336
- const cachePath = path6.join(worktreePath, cacheDir);
4518
+ const cachePath = path7.join(worktreePath, cacheDir);
4337
4519
  try {
4338
- if (fs5.existsSync(cachePath)) {
4520
+ if (fs6.existsSync(cachePath)) {
4339
4521
  console.log(`[WorktreeManager] EP1070: Cleaning build cache: ${cacheDir}`);
4340
- fs5.rmSync(cachePath, { recursive: true, force: true });
4522
+ fs6.rmSync(cachePath, { recursive: true, force: true });
4341
4523
  }
4342
4524
  } catch (error) {
4343
4525
  console.warn(`[WorktreeManager] EP1070: Failed to clean ${cacheDir} (non-blocking):`, error.message);
@@ -4346,10 +4528,10 @@ var WorktreeManager = class _WorktreeManager {
4346
4528
  }
4347
4529
  readConfig() {
4348
4530
  try {
4349
- if (!fs5.existsSync(this.configPath)) {
4531
+ if (!fs6.existsSync(this.configPath)) {
4350
4532
  return null;
4351
4533
  }
4352
- const content = fs5.readFileSync(this.configPath, "utf-8");
4534
+ const content = fs6.readFileSync(this.configPath, "utf-8");
4353
4535
  return JSON.parse(content);
4354
4536
  } catch (error) {
4355
4537
  if (error instanceof SyntaxError) {
@@ -4363,11 +4545,11 @@ var WorktreeManager = class _WorktreeManager {
4363
4545
  }
4364
4546
  writeConfig(config) {
4365
4547
  try {
4366
- const dir = path6.dirname(this.configPath);
4367
- if (!fs5.existsSync(dir)) {
4368
- fs5.mkdirSync(dir, { recursive: true });
4548
+ const dir = path7.dirname(this.configPath);
4549
+ if (!fs6.existsSync(dir)) {
4550
+ fs6.mkdirSync(dir, { recursive: true });
4369
4551
  }
4370
- fs5.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
4552
+ fs6.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
4371
4553
  } catch (error) {
4372
4554
  console.error("[WorktreeManager] Failed to write config:", error);
4373
4555
  throw error;
@@ -4449,14 +4631,14 @@ var WorktreeManager = class _WorktreeManager {
4449
4631
  }
4450
4632
  try {
4451
4633
  for (const file of files) {
4452
- const srcPath = path6.join(mainWorktree.worktreePath, file);
4453
- const destPath = path6.join(worktree.worktreePath, file);
4454
- if (fs5.existsSync(srcPath)) {
4455
- const destDir = path6.dirname(destPath);
4456
- if (!fs5.existsSync(destDir)) {
4457
- fs5.mkdirSync(destDir, { recursive: true });
4458
- }
4459
- fs5.copyFileSync(srcPath, destPath);
4634
+ const srcPath = path7.join(mainWorktree.worktreePath, file);
4635
+ const destPath = path7.join(worktree.worktreePath, file);
4636
+ if (fs6.existsSync(srcPath)) {
4637
+ const destDir = path7.dirname(destPath);
4638
+ if (!fs6.existsSync(destDir)) {
4639
+ fs6.mkdirSync(destDir, { recursive: true });
4640
+ }
4641
+ fs6.copyFileSync(srcPath, destPath);
4460
4642
  console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
4461
4643
  } else {
4462
4644
  console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
@@ -4539,10 +4721,10 @@ var WorktreeManager = class _WorktreeManager {
4539
4721
  }
4540
4722
  };
4541
4723
  function getEpisodaRoot() {
4542
- return process.env.EPISODA_ROOT || path6.join(require("os").homedir(), "episoda");
4724
+ return process.env.EPISODA_ROOT || path7.join(require("os").homedir(), "episoda");
4543
4725
  }
4544
4726
  function getProjectPath(workspaceSlug, projectSlug) {
4545
- return path6.join(getEpisodaRoot(), workspaceSlug, projectSlug);
4727
+ return path7.join(getEpisodaRoot(), workspaceSlug, projectSlug);
4546
4728
  }
4547
4729
  async function isWorktreeProject(projectRoot) {
4548
4730
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
@@ -4557,7 +4739,7 @@ async function isWorktreeProject(projectRoot) {
4557
4739
  return result;
4558
4740
  }
4559
4741
  async function findProjectRoot(startPath) {
4560
- let current = path6.resolve(startPath);
4742
+ let current = path7.resolve(startPath);
4561
4743
  const episodaRoot = getEpisodaRoot();
4562
4744
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
4563
4745
  if (debug) {
@@ -4570,14 +4752,14 @@ async function findProjectRoot(startPath) {
4570
4752
  return null;
4571
4753
  }
4572
4754
  for (let i = 0; i < 10; i++) {
4573
- const bareDir = path6.join(current, ".bare");
4574
- const episodaDir = path6.join(current, ".episoda");
4755
+ const bareDir = path7.join(current, ".bare");
4756
+ const episodaDir = path7.join(current, ".episoda");
4575
4757
  if (debug) {
4576
- const bareExists = fs5.existsSync(bareDir);
4577
- const episodaExists = fs5.existsSync(episodaDir);
4758
+ const bareExists = fs6.existsSync(bareDir);
4759
+ const episodaExists = fs6.existsSync(episodaDir);
4578
4760
  console.log(`[WorktreeManager] findProjectRoot: checking ${current} (.bare=${bareExists}, .episoda=${episodaExists})`);
4579
4761
  }
4580
- if (fs5.existsSync(bareDir) && fs5.existsSync(episodaDir)) {
4762
+ if (fs6.existsSync(bareDir) && fs6.existsSync(episodaDir)) {
4581
4763
  if (await isWorktreeProject(current)) {
4582
4764
  if (debug) {
4583
4765
  console.log(`[WorktreeManager] findProjectRoot: found valid project at ${current}`);
@@ -4585,7 +4767,7 @@ async function findProjectRoot(startPath) {
4585
4767
  return current;
4586
4768
  }
4587
4769
  }
4588
- const parent = path6.dirname(current);
4770
+ const parent = path7.dirname(current);
4589
4771
  if (parent === current) {
4590
4772
  break;
4591
4773
  }
@@ -4623,39 +4805,39 @@ var import_fs = require("fs");
4623
4805
  // src/preview/dev-server-runner.ts
4624
4806
  var import_child_process5 = require("child_process");
4625
4807
  var http = __toESM(require("http"));
4626
- var fs9 = __toESM(require("fs"));
4627
- var path10 = __toESM(require("path"));
4808
+ var fs10 = __toESM(require("fs"));
4809
+ var path11 = __toESM(require("path"));
4628
4810
  var import_events = require("events");
4629
4811
  var import_core7 = __toESM(require_dist());
4630
4812
 
4631
4813
  // src/utils/port-check.ts
4632
4814
  var net2 = __toESM(require("net"));
4633
4815
  async function isPortInUse(port) {
4634
- return new Promise((resolve3) => {
4816
+ return new Promise((resolve4) => {
4635
4817
  const server = net2.createServer();
4636
4818
  server.once("error", (err) => {
4637
4819
  if (err.code === "EADDRINUSE") {
4638
- resolve3(true);
4820
+ resolve4(true);
4639
4821
  } else {
4640
- resolve3(false);
4822
+ resolve4(false);
4641
4823
  }
4642
4824
  });
4643
4825
  server.once("listening", () => {
4644
4826
  server.close();
4645
- resolve3(false);
4827
+ resolve4(false);
4646
4828
  });
4647
4829
  server.listen(port);
4648
4830
  });
4649
4831
  }
4650
4832
 
4651
4833
  // src/utils/env-cache.ts
4652
- var fs7 = __toESM(require("fs"));
4653
- var path8 = __toESM(require("path"));
4834
+ var fs8 = __toESM(require("fs"));
4835
+ var path9 = __toESM(require("path"));
4654
4836
  var os = __toESM(require("os"));
4655
4837
 
4656
4838
  // src/utils/env-setup.ts
4657
- var fs6 = __toESM(require("fs"));
4658
- var path7 = __toESM(require("path"));
4839
+ var fs7 = __toESM(require("fs"));
4840
+ var path8 = __toESM(require("path"));
4659
4841
  async function fetchEnvVars(apiUrl, accessToken) {
4660
4842
  try {
4661
4843
  const url = `${apiUrl}/api/cli/env-vars`;
@@ -4690,29 +4872,29 @@ function writeEnvFile(targetPath, envVars) {
4690
4872
  }
4691
4873
  return `${key}=${value}`;
4692
4874
  }).join("\n") + "\n";
4693
- const envPath = path7.join(targetPath, ".env");
4694
- fs6.writeFileSync(envPath, envContent, { mode: 384 });
4875
+ const envPath = path8.join(targetPath, ".env");
4876
+ fs7.writeFileSync(envPath, envContent, { mode: 384 });
4695
4877
  console.log(`[env-setup] Wrote ${Object.keys(envVars).length} env vars to ${envPath}`);
4696
4878
  }
4697
4879
 
4698
4880
  // src/utils/env-cache.ts
4699
4881
  var DEFAULT_CACHE_TTL = 60;
4700
- var CACHE_DIR = path8.join(os.homedir(), ".episoda", "cache");
4882
+ var CACHE_DIR = path9.join(os.homedir(), ".episoda", "cache");
4701
4883
  function getCacheFilePath(projectId) {
4702
- return path8.join(CACHE_DIR, `env-vars-${projectId}.json`);
4884
+ return path9.join(CACHE_DIR, `env-vars-${projectId}.json`);
4703
4885
  }
4704
4886
  function ensureCacheDir() {
4705
- if (!fs7.existsSync(CACHE_DIR)) {
4706
- fs7.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
4887
+ if (!fs8.existsSync(CACHE_DIR)) {
4888
+ fs8.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
4707
4889
  }
4708
4890
  }
4709
4891
  function readCache(projectId) {
4710
4892
  try {
4711
4893
  const cacheFile = getCacheFilePath(projectId);
4712
- if (!fs7.existsSync(cacheFile)) {
4894
+ if (!fs8.existsSync(cacheFile)) {
4713
4895
  return null;
4714
4896
  }
4715
- const content = fs7.readFileSync(cacheFile, "utf-8");
4897
+ const content = fs8.readFileSync(cacheFile, "utf-8");
4716
4898
  const data = JSON.parse(content);
4717
4899
  if (!data.vars || typeof data.vars !== "object" || !data.fetchedAt) {
4718
4900
  return null;
@@ -4731,7 +4913,7 @@ function writeCache(projectId, vars) {
4731
4913
  fetchedAt: Date.now(),
4732
4914
  projectId
4733
4915
  };
4734
- fs7.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
4916
+ fs8.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
4735
4917
  } catch (error) {
4736
4918
  console.warn("[env-cache] Failed to write cache:", error instanceof Error ? error.message : error);
4737
4919
  }
@@ -4800,11 +4982,11 @@ No cached values available as fallback.`
4800
4982
  }
4801
4983
 
4802
4984
  // src/preview/dev-server-registry.ts
4803
- var fs8 = __toESM(require("fs"));
4804
- var path9 = __toESM(require("path"));
4985
+ var fs9 = __toESM(require("fs"));
4986
+ var path10 = __toESM(require("path"));
4805
4987
  var os2 = __toESM(require("os"));
4806
4988
  var import_child_process4 = require("child_process");
4807
- var DEV_SERVER_REGISTRY_DIR = path9.join(os2.homedir(), ".episoda", "dev-servers");
4989
+ var DEV_SERVER_REGISTRY_DIR = path10.join(os2.homedir(), ".episoda", "dev-servers");
4808
4990
  var DevServerRegistry = class {
4809
4991
  constructor() {
4810
4992
  this.ensureRegistryDir();
@@ -4814,9 +4996,9 @@ var DevServerRegistry = class {
4814
4996
  */
4815
4997
  ensureRegistryDir() {
4816
4998
  try {
4817
- if (!fs8.existsSync(DEV_SERVER_REGISTRY_DIR)) {
4999
+ if (!fs9.existsSync(DEV_SERVER_REGISTRY_DIR)) {
4818
5000
  console.log(`[DevServerRegistry] EP1042: Creating registry directory: ${DEV_SERVER_REGISTRY_DIR}`);
4819
- fs8.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
5001
+ fs9.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
4820
5002
  }
4821
5003
  } catch (error) {
4822
5004
  console.error(`[DevServerRegistry] EP1042: Failed to create registry directory:`, error);
@@ -4827,7 +5009,7 @@ var DevServerRegistry = class {
4827
5009
  * Get the registry file path for a module
4828
5010
  */
4829
5011
  getEntryPath(moduleUid) {
4830
- return path9.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
5012
+ return path10.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
4831
5013
  }
4832
5014
  /**
4833
5015
  * Register a dev server
@@ -4838,7 +5020,7 @@ var DevServerRegistry = class {
4838
5020
  try {
4839
5021
  this.ensureRegistryDir();
4840
5022
  const entryPath = this.getEntryPath(entry.moduleUid);
4841
- fs8.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
5023
+ fs9.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
4842
5024
  console.log(`[DevServerRegistry] EP1042: Registered ${entry.moduleUid} (PID ${entry.pid}, port ${entry.port})`);
4843
5025
  } catch (error) {
4844
5026
  console.error(`[DevServerRegistry] EP1042: Failed to register ${entry.moduleUid}:`, error);
@@ -4852,8 +5034,8 @@ var DevServerRegistry = class {
4852
5034
  unregister(moduleUid) {
4853
5035
  try {
4854
5036
  const entryPath = this.getEntryPath(moduleUid);
4855
- if (fs8.existsSync(entryPath)) {
4856
- fs8.unlinkSync(entryPath);
5037
+ if (fs9.existsSync(entryPath)) {
5038
+ fs9.unlinkSync(entryPath);
4857
5039
  console.log(`[DevServerRegistry] EP1042: Unregistered ${moduleUid}`);
4858
5040
  }
4859
5041
  } catch (error) {
@@ -4869,10 +5051,10 @@ var DevServerRegistry = class {
4869
5051
  getByModule(moduleUid) {
4870
5052
  try {
4871
5053
  const entryPath = this.getEntryPath(moduleUid);
4872
- if (!fs8.existsSync(entryPath)) {
5054
+ if (!fs9.existsSync(entryPath)) {
4873
5055
  return null;
4874
5056
  }
4875
- const content = fs8.readFileSync(entryPath, "utf8");
5057
+ const content = fs9.readFileSync(entryPath, "utf8");
4876
5058
  const entry = JSON.parse(content);
4877
5059
  if (!entry.pid || !entry.port || !entry.worktreePath) {
4878
5060
  console.warn(`[DevServerRegistry] EP1042: Invalid entry for ${moduleUid}, removing`);
@@ -4910,7 +5092,7 @@ var DevServerRegistry = class {
4910
5092
  const entries = [];
4911
5093
  try {
4912
5094
  this.ensureRegistryDir();
4913
- const files = fs8.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
5095
+ const files = fs9.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
4914
5096
  for (const file of files) {
4915
5097
  const moduleUid = file.replace(".json", "");
4916
5098
  const entry = this.getByModule(moduleUid);
@@ -4968,7 +5150,7 @@ var DevServerRegistry = class {
4968
5150
  return null;
4969
5151
  } catch {
4970
5152
  try {
4971
- return fs8.readlinkSync(`/proc/${pid}/cwd`);
5153
+ return fs9.readlinkSync(`/proc/${pid}/cwd`);
4972
5154
  } catch {
4973
5155
  return null;
4974
5156
  }
@@ -5078,7 +5260,7 @@ var DevServerRegistry = class {
5078
5260
  return killed;
5079
5261
  }
5080
5262
  wait(ms) {
5081
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5263
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
5082
5264
  }
5083
5265
  };
5084
5266
  var registryInstance = null;
@@ -5338,8 +5520,8 @@ var DevServerRunner = class extends import_events.EventEmitter {
5338
5520
  cacheTtl: 300
5339
5521
  });
5340
5522
  console.log(`[DevServerRunner] Loaded ${Object.keys(result.envVars).length} env vars`);
5341
- const envFilePath = path10.join(projectPath, ".env");
5342
- if (!fs9.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
5523
+ const envFilePath = path11.join(projectPath, ".env");
5524
+ if (!fs10.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
5343
5525
  console.log(`[DevServerRunner] Writing .env file`);
5344
5526
  writeEnvFile(projectPath, result.envVars);
5345
5527
  }
@@ -5454,7 +5636,7 @@ var DevServerRunner = class extends import_events.EventEmitter {
5454
5636
  return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
5455
5637
  }
5456
5638
  async checkHealth(port) {
5457
- return new Promise((resolve3) => {
5639
+ return new Promise((resolve4) => {
5458
5640
  const req = http.request(
5459
5641
  {
5460
5642
  hostname: "localhost",
@@ -5463,12 +5645,12 @@ var DevServerRunner = class extends import_events.EventEmitter {
5463
5645
  method: "HEAD",
5464
5646
  timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
5465
5647
  },
5466
- () => resolve3(true)
5648
+ () => resolve4(true)
5467
5649
  );
5468
- req.on("error", () => resolve3(false));
5650
+ req.on("error", () => resolve4(false));
5469
5651
  req.on("timeout", () => {
5470
5652
  req.destroy();
5471
- resolve3(false);
5653
+ resolve4(false);
5472
5654
  });
5473
5655
  req.end();
5474
5656
  });
@@ -5485,28 +5667,28 @@ var DevServerRunner = class extends import_events.EventEmitter {
5485
5667
  return false;
5486
5668
  }
5487
5669
  wait(ms) {
5488
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5670
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
5489
5671
  }
5490
5672
  getLogsDir() {
5491
- const logsDir = path10.join((0, import_core7.getConfigDir)(), "logs");
5492
- if (!fs9.existsSync(logsDir)) {
5493
- fs9.mkdirSync(logsDir, { recursive: true });
5673
+ const logsDir = path11.join((0, import_core7.getConfigDir)(), "logs");
5674
+ if (!fs10.existsSync(logsDir)) {
5675
+ fs10.mkdirSync(logsDir, { recursive: true });
5494
5676
  }
5495
5677
  return logsDir;
5496
5678
  }
5497
5679
  getLogFilePath(moduleUid) {
5498
- return path10.join(this.getLogsDir(), `dev-${moduleUid}.log`);
5680
+ return path11.join(this.getLogsDir(), `dev-${moduleUid}.log`);
5499
5681
  }
5500
5682
  rotateLogIfNeeded(logPath) {
5501
5683
  try {
5502
- if (fs9.existsSync(logPath)) {
5503
- const stats = fs9.statSync(logPath);
5684
+ if (fs10.existsSync(logPath)) {
5685
+ const stats = fs10.statSync(logPath);
5504
5686
  if (stats.size > DEV_SERVER_CONSTANTS.MAX_LOG_SIZE_BYTES) {
5505
5687
  const backupPath = `${logPath}.1`;
5506
- if (fs9.existsSync(backupPath)) {
5507
- fs9.unlinkSync(backupPath);
5688
+ if (fs10.existsSync(backupPath)) {
5689
+ fs10.unlinkSync(backupPath);
5508
5690
  }
5509
- fs9.renameSync(logPath, backupPath);
5691
+ fs10.renameSync(logPath, backupPath);
5510
5692
  }
5511
5693
  }
5512
5694
  } catch {
@@ -5517,7 +5699,7 @@ var DevServerRunner = class extends import_events.EventEmitter {
5517
5699
  try {
5518
5700
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
5519
5701
  const prefix = isError ? "ERR" : "OUT";
5520
- fs9.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
5702
+ fs10.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
5521
5703
  `);
5522
5704
  } catch {
5523
5705
  }
@@ -5547,14 +5729,14 @@ function getDevServerRunner() {
5547
5729
  // src/tunnel/tunnel-manager.ts
5548
5730
  var import_child_process7 = require("child_process");
5549
5731
  var import_events2 = require("events");
5550
- var fs11 = __toESM(require("fs"));
5551
- var path12 = __toESM(require("path"));
5732
+ var fs12 = __toESM(require("fs"));
5733
+ var path13 = __toESM(require("path"));
5552
5734
  var os4 = __toESM(require("os"));
5553
5735
 
5554
5736
  // src/tunnel/cloudflared-manager.ts
5555
5737
  var import_child_process6 = require("child_process");
5556
- var fs10 = __toESM(require("fs"));
5557
- var path11 = __toESM(require("path"));
5738
+ var fs11 = __toESM(require("fs"));
5739
+ var path12 = __toESM(require("path"));
5558
5740
  var os3 = __toESM(require("os"));
5559
5741
  var https = __toESM(require("https"));
5560
5742
  var tar = __toESM(require("tar"));
@@ -5573,11 +5755,11 @@ var DOWNLOAD_URLS = {
5573
5755
  }
5574
5756
  };
5575
5757
  function getEpisodaBinDir() {
5576
- return path11.join(os3.homedir(), ".episoda", "bin");
5758
+ return path12.join(os3.homedir(), ".episoda", "bin");
5577
5759
  }
5578
5760
  function getCloudflaredPath() {
5579
5761
  const binaryName = os3.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
5580
- return path11.join(getEpisodaBinDir(), binaryName);
5762
+ return path12.join(getEpisodaBinDir(), binaryName);
5581
5763
  }
5582
5764
  function isCloudflaredInPath() {
5583
5765
  try {
@@ -5594,7 +5776,7 @@ function isCloudflaredInPath() {
5594
5776
  function isCloudflaredInstalled() {
5595
5777
  const cloudflaredPath = getCloudflaredPath();
5596
5778
  try {
5597
- fs10.accessSync(cloudflaredPath, fs10.constants.X_OK);
5779
+ fs11.accessSync(cloudflaredPath, fs11.constants.X_OK);
5598
5780
  return true;
5599
5781
  } catch {
5600
5782
  return false;
@@ -5618,7 +5800,7 @@ function getDownloadUrl() {
5618
5800
  return platformUrls[arch3] || null;
5619
5801
  }
5620
5802
  async function downloadFile(url, destPath) {
5621
- return new Promise((resolve3, reject) => {
5803
+ return new Promise((resolve4, reject) => {
5622
5804
  const followRedirect = (currentUrl, redirectCount = 0) => {
5623
5805
  if (redirectCount > 5) {
5624
5806
  reject(new Error("Too many redirects"));
@@ -5644,14 +5826,14 @@ async function downloadFile(url, destPath) {
5644
5826
  reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
5645
5827
  return;
5646
5828
  }
5647
- const file = fs10.createWriteStream(destPath);
5829
+ const file = fs11.createWriteStream(destPath);
5648
5830
  response.pipe(file);
5649
5831
  file.on("finish", () => {
5650
5832
  file.close();
5651
- resolve3();
5833
+ resolve4();
5652
5834
  });
5653
5835
  file.on("error", (err) => {
5654
- fs10.unlinkSync(destPath);
5836
+ fs11.unlinkSync(destPath);
5655
5837
  reject(err);
5656
5838
  });
5657
5839
  }).on("error", reject);
@@ -5672,21 +5854,21 @@ async function downloadCloudflared() {
5672
5854
  }
5673
5855
  const binDir = getEpisodaBinDir();
5674
5856
  const cloudflaredPath = getCloudflaredPath();
5675
- fs10.mkdirSync(binDir, { recursive: true });
5857
+ fs11.mkdirSync(binDir, { recursive: true });
5676
5858
  const isTgz = url.endsWith(".tgz");
5677
5859
  if (isTgz) {
5678
- const tempFile = path11.join(binDir, "cloudflared.tgz");
5860
+ const tempFile = path12.join(binDir, "cloudflared.tgz");
5679
5861
  console.log(`[Tunnel] Downloading cloudflared from ${url}...`);
5680
5862
  await downloadFile(url, tempFile);
5681
5863
  console.log("[Tunnel] Extracting cloudflared...");
5682
5864
  await extractTgz(tempFile, binDir);
5683
- fs10.unlinkSync(tempFile);
5865
+ fs11.unlinkSync(tempFile);
5684
5866
  } else {
5685
5867
  console.log(`[Tunnel] Downloading cloudflared from ${url}...`);
5686
5868
  await downloadFile(url, cloudflaredPath);
5687
5869
  }
5688
5870
  if (os3.platform() !== "win32") {
5689
- fs10.chmodSync(cloudflaredPath, 493);
5871
+ fs11.chmodSync(cloudflaredPath, 493);
5690
5872
  }
5691
5873
  if (!verifyCloudflared(cloudflaredPath)) {
5692
5874
  throw new Error("Downloaded cloudflared binary failed verification");
@@ -5850,7 +6032,7 @@ function getDaemonModeConfig() {
5850
6032
  }
5851
6033
 
5852
6034
  // src/tunnel/tunnel-manager.ts
5853
- var TUNNEL_PID_DIR = path12.join(os4.homedir(), ".episoda", "tunnels");
6035
+ var TUNNEL_PID_DIR = path13.join(os4.homedir(), ".episoda", "tunnels");
5854
6036
  var TUNNEL_TIMEOUTS = {
5855
6037
  /** Time to wait for Named Tunnel connection (includes API token fetch + connect) */
5856
6038
  NAMED_TUNNEL_CONNECT: 6e4,
@@ -5882,9 +6064,9 @@ var TunnelManager = class extends import_events2.EventEmitter {
5882
6064
  */
5883
6065
  ensurePidDir() {
5884
6066
  try {
5885
- if (!fs11.existsSync(TUNNEL_PID_DIR)) {
6067
+ if (!fs12.existsSync(TUNNEL_PID_DIR)) {
5886
6068
  console.log(`[Tunnel] EP904: Creating PID directory: ${TUNNEL_PID_DIR}`);
5887
- fs11.mkdirSync(TUNNEL_PID_DIR, { recursive: true });
6069
+ fs12.mkdirSync(TUNNEL_PID_DIR, { recursive: true });
5888
6070
  console.log(`[Tunnel] EP904: PID directory created successfully`);
5889
6071
  }
5890
6072
  } catch (error) {
@@ -5896,7 +6078,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
5896
6078
  * EP877: Get PID file path for a module
5897
6079
  */
5898
6080
  getPidFilePath(moduleUid) {
5899
- return path12.join(TUNNEL_PID_DIR, `${moduleUid}.pid`);
6081
+ return path13.join(TUNNEL_PID_DIR, `${moduleUid}.pid`);
5900
6082
  }
5901
6083
  /**
5902
6084
  * EP877: Write PID to file for tracking across restarts
@@ -5906,7 +6088,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
5906
6088
  try {
5907
6089
  this.ensurePidDir();
5908
6090
  const pidPath = this.getPidFilePath(moduleUid);
5909
- fs11.writeFileSync(pidPath, pid.toString(), "utf8");
6091
+ fs12.writeFileSync(pidPath, pid.toString(), "utf8");
5910
6092
  console.log(`[Tunnel] EP904: Wrote PID ${pid} for ${moduleUid} to ${pidPath}`);
5911
6093
  } catch (error) {
5912
6094
  console.error(`[Tunnel] EP904: Failed to write PID file for ${moduleUid}:`, error);
@@ -5919,10 +6101,10 @@ var TunnelManager = class extends import_events2.EventEmitter {
5919
6101
  readPidFile(moduleUid) {
5920
6102
  try {
5921
6103
  const pidPath = this.getPidFilePath(moduleUid);
5922
- if (!fs11.existsSync(pidPath)) {
6104
+ if (!fs12.existsSync(pidPath)) {
5923
6105
  return null;
5924
6106
  }
5925
- const content = fs11.readFileSync(pidPath, "utf8").trim();
6107
+ const content = fs12.readFileSync(pidPath, "utf8").trim();
5926
6108
  const pid = parseInt(content, 10);
5927
6109
  if (isNaN(pid) || pid <= 0) {
5928
6110
  console.warn(`[Tunnel] EP948: Invalid PID file content for ${moduleUid}: "${content}", removing stale file`);
@@ -5950,8 +6132,8 @@ var TunnelManager = class extends import_events2.EventEmitter {
5950
6132
  removePidFile(moduleUid) {
5951
6133
  try {
5952
6134
  const pidPath = this.getPidFilePath(moduleUid);
5953
- if (fs11.existsSync(pidPath)) {
5954
- fs11.unlinkSync(pidPath);
6135
+ if (fs12.existsSync(pidPath)) {
6136
+ fs12.unlinkSync(pidPath);
5955
6137
  console.log(`[Tunnel] EP877: Removed PID file for ${moduleUid}`);
5956
6138
  }
5957
6139
  } catch (error) {
@@ -6026,10 +6208,10 @@ var TunnelManager = class extends import_events2.EventEmitter {
6026
6208
  const isTracked = Array.from(this.tunnelStates.values()).some((s) => s.info.pid === pid);
6027
6209
  console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`);
6028
6210
  this.killByPid(pid, "SIGTERM");
6029
- await new Promise((resolve3) => setTimeout(resolve3, 500));
6211
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
6030
6212
  if (this.isProcessRunning(pid)) {
6031
6213
  this.killByPid(pid, "SIGKILL");
6032
- await new Promise((resolve3) => setTimeout(resolve3, 200));
6214
+ await new Promise((resolve4) => setTimeout(resolve4, 200));
6033
6215
  }
6034
6216
  killed.push(pid);
6035
6217
  }
@@ -6055,7 +6237,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6055
6237
  }
6056
6238
  try {
6057
6239
  this.ensurePidDir();
6058
- const pidFiles = fs11.readdirSync(TUNNEL_PID_DIR).filter((f) => f.endsWith(".pid"));
6240
+ const pidFiles = fs12.readdirSync(TUNNEL_PID_DIR).filter((f) => f.endsWith(".pid"));
6059
6241
  for (const pidFile of pidFiles) {
6060
6242
  const moduleUid = pidFile.replace(".pid", "");
6061
6243
  const pid = this.readPidFile(moduleUid);
@@ -6063,7 +6245,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6063
6245
  if (!this.tunnelStates.has(moduleUid)) {
6064
6246
  console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`);
6065
6247
  this.killByPid(pid, "SIGTERM");
6066
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
6248
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
6067
6249
  if (this.isProcessRunning(pid)) {
6068
6250
  this.killByPid(pid, "SIGKILL");
6069
6251
  }
@@ -6078,7 +6260,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6078
6260
  if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {
6079
6261
  console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`);
6080
6262
  this.killByPid(pid, "SIGTERM");
6081
- await new Promise((resolve3) => setTimeout(resolve3, 500));
6263
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
6082
6264
  if (this.isProcessRunning(pid)) {
6083
6265
  this.killByPid(pid, "SIGKILL");
6084
6266
  }
@@ -6168,7 +6350,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6168
6350
  return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
6169
6351
  }
6170
6352
  }
6171
- return new Promise((resolve3) => {
6353
+ return new Promise((resolve4) => {
6172
6354
  const tunnelInfo = {
6173
6355
  moduleUid,
6174
6356
  url: previewUrl || "",
@@ -6234,7 +6416,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6234
6416
  moduleUid,
6235
6417
  url: tunnelInfo.url
6236
6418
  });
6237
- resolve3({ success: true, url: tunnelInfo.url });
6419
+ resolve4({ success: true, url: tunnelInfo.url });
6238
6420
  }
6239
6421
  };
6240
6422
  process2.stderr?.on("data", (data) => {
@@ -6261,7 +6443,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6261
6443
  onStatusChange?.("error", errorMsg);
6262
6444
  this.emitEvent({ type: "error", moduleUid, error: errorMsg });
6263
6445
  }
6264
- resolve3({ success: false, error: errorMsg });
6446
+ resolve4({ success: false, error: errorMsg });
6265
6447
  } else if (wasConnected) {
6266
6448
  if (currentState && !currentState.intentionallyStopped) {
6267
6449
  console.log(`[Tunnel] EP948: Named tunnel ${moduleUid} crashed unexpectedly, attempting reconnect...`);
@@ -6292,7 +6474,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6292
6474
  this.emitEvent({ type: "error", moduleUid, error: error.message });
6293
6475
  }
6294
6476
  if (!connected) {
6295
- resolve3({ success: false, error: error.message });
6477
+ resolve4({ success: false, error: error.message });
6296
6478
  }
6297
6479
  });
6298
6480
  setTimeout(() => {
@@ -6315,7 +6497,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6315
6497
  onStatusChange?.("error", errorMsg);
6316
6498
  this.emitEvent({ type: "error", moduleUid, error: errorMsg });
6317
6499
  }
6318
- resolve3({ success: false, error: errorMsg });
6500
+ resolve4({ success: false, error: errorMsg });
6319
6501
  }
6320
6502
  }, TUNNEL_TIMEOUTS.NAMED_TUNNEL_CONNECT);
6321
6503
  });
@@ -6376,7 +6558,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6376
6558
  if (orphanPid && this.isProcessRunning(orphanPid)) {
6377
6559
  console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`);
6378
6560
  this.killByPid(orphanPid, "SIGTERM");
6379
- await new Promise((resolve3) => setTimeout(resolve3, 500));
6561
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
6380
6562
  if (this.isProcessRunning(orphanPid)) {
6381
6563
  this.killByPid(orphanPid, "SIGKILL");
6382
6564
  }
@@ -6385,7 +6567,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6385
6567
  const killedOnPort = await this.killCloudflaredOnPort(port);
6386
6568
  if (killedOnPort.length > 0) {
6387
6569
  console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`);
6388
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
6570
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
6389
6571
  }
6390
6572
  const cleanup = await this.cleanupOrphanedProcesses();
6391
6573
  if (cleanup.cleaned > 0) {
@@ -6425,7 +6607,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6425
6607
  if (orphanPid && this.isProcessRunning(orphanPid)) {
6426
6608
  console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`);
6427
6609
  this.killByPid(orphanPid, "SIGTERM");
6428
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
6610
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
6429
6611
  if (this.isProcessRunning(orphanPid)) {
6430
6612
  this.killByPid(orphanPid, "SIGKILL");
6431
6613
  }
@@ -6441,16 +6623,16 @@ var TunnelManager = class extends import_events2.EventEmitter {
6441
6623
  const tunnel = state.info;
6442
6624
  if (tunnel.process && !tunnel.process.killed) {
6443
6625
  tunnel.process.kill("SIGTERM");
6444
- await new Promise((resolve3) => {
6626
+ await new Promise((resolve4) => {
6445
6627
  const timeout = setTimeout(() => {
6446
6628
  if (tunnel.process && !tunnel.process.killed) {
6447
6629
  tunnel.process.kill("SIGKILL");
6448
6630
  }
6449
- resolve3();
6631
+ resolve4();
6450
6632
  }, 3e3);
6451
6633
  tunnel.process.once("exit", () => {
6452
6634
  clearTimeout(timeout);
6453
- resolve3();
6635
+ resolve4();
6454
6636
  });
6455
6637
  });
6456
6638
  }
@@ -6511,21 +6693,21 @@ function getTunnelManager() {
6511
6693
  }
6512
6694
 
6513
6695
  // src/utils/port-allocator.ts
6514
- var fs12 = __toESM(require("fs"));
6515
- var path13 = __toESM(require("path"));
6696
+ var fs13 = __toESM(require("fs"));
6697
+ var path14 = __toESM(require("path"));
6516
6698
  var os5 = __toESM(require("os"));
6517
6699
  var PORT_RANGE_START = 3100;
6518
6700
  var PORT_RANGE_END = 3199;
6519
6701
  var PORT_WARNING_THRESHOLD = 80;
6520
- var PORTS_FILE = path13.join(os5.homedir(), ".episoda", "ports.json");
6702
+ var PORTS_FILE = path14.join(os5.homedir(), ".episoda", "ports.json");
6521
6703
  var portAssignments = /* @__PURE__ */ new Map();
6522
6704
  var initialized = false;
6523
6705
  function loadFromDisk() {
6524
6706
  if (initialized) return;
6525
6707
  initialized = true;
6526
6708
  try {
6527
- if (fs12.existsSync(PORTS_FILE)) {
6528
- const content = fs12.readFileSync(PORTS_FILE, "utf8");
6709
+ if (fs13.existsSync(PORTS_FILE)) {
6710
+ const content = fs13.readFileSync(PORTS_FILE, "utf8");
6529
6711
  const data = JSON.parse(content);
6530
6712
  for (const [moduleUid, port] of Object.entries(data)) {
6531
6713
  if (typeof port === "number" && port >= PORT_RANGE_START && port <= PORT_RANGE_END) {
@@ -6542,15 +6724,15 @@ function loadFromDisk() {
6542
6724
  }
6543
6725
  function saveToDisk() {
6544
6726
  try {
6545
- const dir = path13.dirname(PORTS_FILE);
6546
- if (!fs12.existsSync(dir)) {
6547
- fs12.mkdirSync(dir, { recursive: true });
6727
+ const dir = path14.dirname(PORTS_FILE);
6728
+ if (!fs13.existsSync(dir)) {
6729
+ fs13.mkdirSync(dir, { recursive: true });
6548
6730
  }
6549
6731
  const data = {};
6550
6732
  for (const [moduleUid, port] of portAssignments) {
6551
6733
  data[moduleUid] = port;
6552
6734
  }
6553
- fs12.writeFileSync(PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
6735
+ fs13.writeFileSync(PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
6554
6736
  } catch (error) {
6555
6737
  console.warn(`[PortAllocator] EP1042: Failed to save ports.json:`, error);
6556
6738
  }
@@ -6734,7 +6916,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
6734
6916
  for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
6735
6917
  if (attempt > 1) {
6736
6918
  console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
6737
- await new Promise((resolve3) => setTimeout(resolve3, 2e3));
6919
+ await new Promise((resolve4) => setTimeout(resolve4, 2e3));
6738
6920
  }
6739
6921
  tunnelResult = await this.tunnel.startTunnel({
6740
6922
  moduleUid,
@@ -6855,7 +7037,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
6855
7037
  }
6856
7038
  console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
6857
7039
  await this.stopPreview(moduleUid);
6858
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
7040
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
6859
7041
  return this.startPreview({
6860
7042
  moduleUid,
6861
7043
  worktreePath: state.worktreePath,
@@ -7126,18 +7308,18 @@ async function handleWorktreeCreate(request2) {
7126
7308
  }
7127
7309
  try {
7128
7310
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
7129
- const bareRepoPath = path14.join(projectPath, ".bare");
7130
- if (!fs13.existsSync(bareRepoPath)) {
7311
+ const bareRepoPath = path15.join(projectPath, ".bare");
7312
+ if (!fs14.existsSync(bareRepoPath)) {
7131
7313
  console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
7132
7314
  console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
7133
- const episodaDir = path14.join(projectPath, ".episoda");
7134
- fs13.mkdirSync(episodaDir, { recursive: true });
7315
+ const episodaDir = path15.join(projectPath, ".episoda");
7316
+ fs14.mkdirSync(episodaDir, { recursive: true });
7135
7317
  try {
7136
7318
  console.log(`[Worktree] K1273: Starting git clone...`);
7137
7319
  await execAsync(`git clone --bare "${repoUrl}" "${bareRepoPath}"`);
7138
7320
  console.log(`[Worktree] K1273: Clone successful`);
7139
7321
  await execAsync(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`);
7140
- const configPath = path14.join(episodaDir, "config.json");
7322
+ const configPath = path15.join(episodaDir, "config.json");
7141
7323
  const config2 = {
7142
7324
  projectId,
7143
7325
  workspaceSlug,
@@ -7146,7 +7328,7 @@ async function handleWorktreeCreate(request2) {
7146
7328
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7147
7329
  worktrees: []
7148
7330
  };
7149
- fs13.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
7331
+ fs14.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
7150
7332
  console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
7151
7333
  } catch (cloneError) {
7152
7334
  console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
@@ -7194,8 +7376,8 @@ async function handleWorktreeCreate(request2) {
7194
7376
  }
7195
7377
  if (envVars && Object.keys(envVars).length > 0) {
7196
7378
  const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
7197
- const envPath = path14.join(worktreePath, ".env");
7198
- fs13.writeFileSync(envPath, envContent + "\n", "utf-8");
7379
+ const envPath = path15.join(worktreePath, ".env");
7380
+ fs14.writeFileSync(envPath, envContent + "\n", "utf-8");
7199
7381
  console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
7200
7382
  }
7201
7383
  if (setupScript) {
@@ -7368,12 +7550,12 @@ async function handleProjectEject(request2) {
7368
7550
  console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
7369
7551
  try {
7370
7552
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
7371
- const bareRepoPath = path14.join(projectPath, ".bare");
7372
- if (!fs13.existsSync(projectPath)) {
7553
+ const bareRepoPath = path15.join(projectPath, ".bare");
7554
+ if (!fs14.existsSync(projectPath)) {
7373
7555
  console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
7374
7556
  return { success: true };
7375
7557
  }
7376
- if (!fs13.existsSync(bareRepoPath)) {
7558
+ if (!fs14.existsSync(bareRepoPath)) {
7377
7559
  console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
7378
7560
  return { success: true };
7379
7561
  }
@@ -7388,12 +7570,12 @@ async function handleProjectEject(request2) {
7388
7570
  };
7389
7571
  }
7390
7572
  }
7391
- const artifactsPath = path14.join(projectPath, "artifacts");
7392
- if (fs13.existsSync(artifactsPath)) {
7573
+ const artifactsPath = path15.join(projectPath, "artifacts");
7574
+ if (fs14.existsSync(artifactsPath)) {
7393
7575
  await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
7394
7576
  }
7395
7577
  console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
7396
- await fs13.promises.rm(projectPath, { recursive: true, force: true });
7578
+ await fs14.promises.rm(projectPath, { recursive: true, force: true });
7397
7579
  console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
7398
7580
  return { success: true };
7399
7581
  } catch (error) {
@@ -7407,7 +7589,7 @@ async function handleProjectEject(request2) {
7407
7589
  async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
7408
7590
  const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
7409
7591
  try {
7410
- const files = await fs13.promises.readdir(artifactsPath);
7592
+ const files = await fs14.promises.readdir(artifactsPath);
7411
7593
  if (files.length === 0) {
7412
7594
  return;
7413
7595
  }
@@ -7421,8 +7603,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
7421
7603
  }
7422
7604
  const artifacts = [];
7423
7605
  for (const fileName of files) {
7424
- const filePath = path14.join(artifactsPath, fileName);
7425
- const stat = await fs13.promises.stat(filePath);
7606
+ const filePath = path15.join(artifactsPath, fileName);
7607
+ const stat = await fs14.promises.stat(filePath);
7426
7608
  if (stat.isDirectory()) {
7427
7609
  continue;
7428
7610
  }
@@ -7431,9 +7613,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
7431
7613
  continue;
7432
7614
  }
7433
7615
  try {
7434
- const content = await fs13.promises.readFile(filePath);
7616
+ const content = await fs14.promises.readFile(filePath);
7435
7617
  const base64Content = content.toString("base64");
7436
- const ext = path14.extname(fileName).toLowerCase();
7618
+ const ext = path15.extname(fileName).toLowerCase();
7437
7619
  const mimeTypes = {
7438
7620
  ".json": "application/json",
7439
7621
  ".txt": "text/plain",
@@ -7585,12 +7767,12 @@ async function cleanupStaleCommits(projectPath) {
7585
7767
 
7586
7768
  // src/agent/claude-binary.ts
7587
7769
  var import_child_process10 = require("child_process");
7588
- var path15 = __toESM(require("path"));
7589
- var fs14 = __toESM(require("fs"));
7770
+ var path16 = __toESM(require("path"));
7771
+ var fs15 = __toESM(require("fs"));
7590
7772
  var cachedBinaryPath = null;
7591
7773
  function isValidClaudeBinary(binaryPath) {
7592
7774
  try {
7593
- fs14.accessSync(binaryPath, fs14.constants.X_OK);
7775
+ fs15.accessSync(binaryPath, fs15.constants.X_OK);
7594
7776
  const version = (0, import_child_process10.execSync)(`"${binaryPath}" --version`, {
7595
7777
  encoding: "utf-8",
7596
7778
  timeout: 5e3,
@@ -7623,14 +7805,14 @@ async function ensureClaudeBinary() {
7623
7805
  }
7624
7806
  const bundledPaths = [
7625
7807
  // In production: node_modules/.bin/claude
7626
- path15.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
7808
+ path16.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
7627
7809
  // In monorepo development: packages/episoda/node_modules/.bin/claude
7628
- path15.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
7810
+ path16.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
7629
7811
  // Root monorepo node_modules
7630
- path15.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
7812
+ path16.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
7631
7813
  ];
7632
7814
  for (const bundledPath of bundledPaths) {
7633
- if (fs14.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
7815
+ if (fs15.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
7634
7816
  cachedBinaryPath = bundledPath;
7635
7817
  return cachedBinaryPath;
7636
7818
  }
@@ -7656,12 +7838,12 @@ async function ensureClaudeBinary() {
7656
7838
 
7657
7839
  // src/agent/codex-binary.ts
7658
7840
  var import_child_process11 = require("child_process");
7659
- var path16 = __toESM(require("path"));
7660
- var fs15 = __toESM(require("fs"));
7841
+ var path17 = __toESM(require("path"));
7842
+ var fs16 = __toESM(require("fs"));
7661
7843
  var cachedBinaryPath2 = null;
7662
7844
  function isValidCodexBinary(binaryPath) {
7663
7845
  try {
7664
- fs15.accessSync(binaryPath, fs15.constants.X_OK);
7846
+ fs16.accessSync(binaryPath, fs16.constants.X_OK);
7665
7847
  const version = (0, import_child_process11.execSync)(`"${binaryPath}" --version`, {
7666
7848
  encoding: "utf-8",
7667
7849
  timeout: 5e3,
@@ -7694,14 +7876,14 @@ async function ensureCodexBinary() {
7694
7876
  }
7695
7877
  const bundledPaths = [
7696
7878
  // In production: node_modules/.bin/codex
7697
- path16.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
7879
+ path17.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
7698
7880
  // In monorepo development: packages/episoda/node_modules/.bin/codex
7699
- path16.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
7881
+ path17.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
7700
7882
  // Root monorepo node_modules
7701
- path16.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
7883
+ path17.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
7702
7884
  ];
7703
7885
  for (const bundledPath of bundledPaths) {
7704
- if (fs15.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
7886
+ if (fs16.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
7705
7887
  cachedBinaryPath2 = bundledPath;
7706
7888
  return cachedBinaryPath2;
7707
7889
  }
@@ -7768,8 +7950,8 @@ function generateCodexConfig(credentials, projectPath) {
7768
7950
 
7769
7951
  // src/agent/agent-manager.ts
7770
7952
  var import_child_process12 = require("child_process");
7771
- var path17 = __toESM(require("path"));
7772
- var fs16 = __toESM(require("fs"));
7953
+ var path18 = __toESM(require("path"));
7954
+ var fs17 = __toESM(require("fs"));
7773
7955
  var os6 = __toESM(require("os"));
7774
7956
 
7775
7957
  // src/agent/claude-config.ts
@@ -7780,8 +7962,8 @@ function generateStatsigHash() {
7780
7962
  function generateNumericHash() {
7781
7963
  return Math.floor(1e9 + Math.random() * 9e9).toString();
7782
7964
  }
7783
- function generateSettings() {
7784
- return {
7965
+ function generateSettings(options = {}) {
7966
+ const settings = {
7785
7967
  permissions: {
7786
7968
  allow: [
7787
7969
  "Bash",
@@ -7790,7 +7972,9 @@ function generateSettings() {
7790
7972
  "Edit",
7791
7973
  "Glob",
7792
7974
  "Grep",
7793
- "WebFetch"
7975
+ "WebFetch",
7976
+ "mcp__github"
7977
+ // EP1146: Allow GitHub MCP tools
7794
7978
  ],
7795
7979
  deny: [],
7796
7980
  ask: [],
@@ -7798,6 +7982,20 @@ function generateSettings() {
7798
7982
  },
7799
7983
  alwaysThinkingEnabled: true
7800
7984
  };
7985
+ if (options.githubToken) {
7986
+ const mcpEnv = {
7987
+ GITHUB_PERSONAL_ACCESS_TOKEN: options.githubToken
7988
+ };
7989
+ settings.mcpServers = {
7990
+ github: {
7991
+ command: "npx",
7992
+ args: ["-y", "@modelcontextprotocol/server-github"],
7993
+ env: mcpEnv
7994
+ }
7995
+ };
7996
+ console.log("[ClaudeConfig] EP1146: GitHub MCP server configured");
7997
+ }
7998
+ return settings;
7801
7999
  }
7802
8000
  function generateStableId() {
7803
8001
  return JSON.stringify((0, import_crypto.randomUUID)());
@@ -8039,13 +8237,13 @@ function generateCachedEvaluations(stableId, sessionId) {
8039
8237
  fullUserHash: Math.floor(Math.random() * 1e10).toString()
8040
8238
  };
8041
8239
  }
8042
- function generateClaudeConfig() {
8240
+ function generateClaudeConfig(options = {}) {
8043
8241
  const hash = generateStatsigHash();
8044
8242
  const numericHash = generateNumericHash();
8045
8243
  const stableId = (0, import_crypto.randomUUID)();
8046
8244
  const sessionId = (0, import_crypto.randomUUID)();
8047
8245
  return {
8048
- "settings.json": JSON.stringify(generateSettings(), null, 2),
8246
+ "settings.json": JSON.stringify(generateSettings(options), null, 2),
8049
8247
  statsig: {
8050
8248
  [`statsig.stable_id.${numericHash}`]: generateStableId(),
8051
8249
  [`statsig.session_id.${numericHash}`]: JSON.stringify(
@@ -8076,7 +8274,7 @@ var AgentManager = class {
8076
8274
  this.initialized = false;
8077
8275
  // EP1133: Lock for config file writes to prevent race conditions
8078
8276
  this.configWriteLock = Promise.resolve();
8079
- this.pidDir = path17.join(os6.homedir(), ".episoda", "agent-pids");
8277
+ this.pidDir = path18.join(os6.homedir(), ".episoda", "agent-pids");
8080
8278
  }
8081
8279
  /**
8082
8280
  * EP1133: Acquire lock for config file writes
@@ -8085,8 +8283,8 @@ var AgentManager = class {
8085
8283
  async withConfigLock(fn) {
8086
8284
  const previousLock = this.configWriteLock;
8087
8285
  let releaseLock;
8088
- this.configWriteLock = new Promise((resolve3) => {
8089
- releaseLock = resolve3;
8286
+ this.configWriteLock = new Promise((resolve4) => {
8287
+ releaseLock = resolve4;
8090
8288
  });
8091
8289
  try {
8092
8290
  await previousLock;
@@ -8105,8 +8303,8 @@ var AgentManager = class {
8105
8303
  return;
8106
8304
  }
8107
8305
  console.log("[AgentManager] Initializing...");
8108
- if (!fs16.existsSync(this.pidDir)) {
8109
- fs16.mkdirSync(this.pidDir, { recursive: true });
8306
+ if (!fs17.existsSync(this.pidDir)) {
8307
+ fs17.mkdirSync(this.pidDir, { recursive: true });
8110
8308
  }
8111
8309
  await this.cleanupOrphanedProcesses();
8112
8310
  try {
@@ -8272,9 +8470,9 @@ ${message}`;
8272
8470
  const useApiKey = !useOAuth && !!session.credentials.apiKey;
8273
8471
  if (provider === "codex") {
8274
8472
  await this.withConfigLock(async () => {
8275
- const codexDir = path17.join(os6.homedir(), ".codex");
8276
- if (!fs16.existsSync(codexDir)) {
8277
- fs16.mkdirSync(codexDir, { recursive: true });
8473
+ const codexDir = path18.join(os6.homedir(), ".codex");
8474
+ if (!fs17.existsSync(codexDir)) {
8475
+ fs17.mkdirSync(codexDir, { recursive: true });
8278
8476
  }
8279
8477
  if (useOAuth) {
8280
8478
  const codexConfig = generateCodexConfig({
@@ -8284,21 +8482,21 @@ ${message}`;
8284
8482
  accountId: session.credentials.accountId,
8285
8483
  expiresAt: session.credentials.expiresAt
8286
8484
  }, session.projectPath);
8287
- const authJsonPath = path17.join(codexDir, "auth.json");
8288
- fs16.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
8485
+ const authJsonPath = path18.join(codexDir, "auth.json");
8486
+ fs17.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
8289
8487
  console.log("[AgentManager] EP1133: Wrote Codex auth.json to ~/.codex/auth.json");
8290
8488
  if (codexConfig["config.toml"]) {
8291
- const configTomlPath = path17.join(codexDir, "config.toml");
8489
+ const configTomlPath = path18.join(codexDir, "config.toml");
8292
8490
  let existingConfig = "";
8293
8491
  try {
8294
- existingConfig = fs16.readFileSync(configTomlPath, "utf-8");
8492
+ existingConfig = fs17.readFileSync(configTomlPath, "utf-8");
8295
8493
  } catch {
8296
8494
  }
8297
8495
  const escapedPathForRegex = session.projectPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8298
8496
  const projectKeyPattern = new RegExp(`\\[projects\\."${escapedPathForRegex}"\\]`);
8299
8497
  const projectAlreadyTrusted = projectKeyPattern.test(existingConfig);
8300
8498
  if (!projectAlreadyTrusted) {
8301
- fs16.writeFileSync(configTomlPath, existingConfig + "\n" + codexConfig["config.toml"], { mode: 420 });
8499
+ fs17.writeFileSync(configTomlPath, existingConfig + "\n" + codexConfig["config.toml"], { mode: 420 });
8302
8500
  console.log("[AgentManager] EP1133: Updated Codex config.toml with project trust");
8303
8501
  }
8304
8502
  }
@@ -8308,14 +8506,14 @@ ${message}`;
8308
8506
  });
8309
8507
  } else {
8310
8508
  await this.withConfigLock(async () => {
8311
- const claudeDir = path17.join(os6.homedir(), ".claude");
8312
- const credentialsPath = path17.join(claudeDir, ".credentials.json");
8313
- const statsigDir = path17.join(claudeDir, "statsig");
8314
- if (!fs16.existsSync(claudeDir)) {
8315
- fs16.mkdirSync(claudeDir, { recursive: true });
8509
+ const claudeDir = path18.join(os6.homedir(), ".claude");
8510
+ const credentialsPath = path18.join(claudeDir, ".credentials.json");
8511
+ const statsigDir = path18.join(claudeDir, "statsig");
8512
+ if (!fs17.existsSync(claudeDir)) {
8513
+ fs17.mkdirSync(claudeDir, { recursive: true });
8316
8514
  }
8317
- if (!fs16.existsSync(statsigDir)) {
8318
- fs16.mkdirSync(statsigDir, { recursive: true });
8515
+ if (!fs17.existsSync(statsigDir)) {
8516
+ fs17.mkdirSync(statsigDir, { recursive: true });
8319
8517
  }
8320
8518
  if (useOAuth) {
8321
8519
  const oauthCredentials = {
@@ -8333,21 +8531,26 @@ ${message}`;
8333
8531
  const credentialsContent = JSON.stringify({
8334
8532
  claudeAiOauth: oauthCredentials
8335
8533
  }, null, 2);
8336
- fs16.writeFileSync(credentialsPath, credentialsContent, { mode: 384 });
8534
+ fs17.writeFileSync(credentialsPath, credentialsContent, { mode: 384 });
8337
8535
  console.log("[AgentManager] Wrote OAuth credentials to ~/.claude/.credentials.json");
8338
8536
  try {
8339
- const claudeConfig = generateClaudeConfig();
8537
+ const claudeConfig = generateClaudeConfig({
8538
+ githubToken: session.credentials.githubToken
8539
+ });
8340
8540
  const statsigFiles = Object.keys(claudeConfig.statsig);
8341
8541
  const hasEvaluations = statsigFiles.some((f) => f.includes("cached.evaluations"));
8342
8542
  const hasStableId = statsigFiles.some((f) => f.includes("stable_id"));
8343
8543
  if (!hasEvaluations || !hasStableId) {
8344
8544
  throw new Error(`Invalid statsig config: missing required files`);
8345
8545
  }
8346
- const settingsPath = path17.join(claudeDir, "settings.json");
8347
- fs16.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
8546
+ const settingsPath = path18.join(claudeDir, "settings.json");
8547
+ fs17.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
8348
8548
  for (const [filename, content] of Object.entries(claudeConfig.statsig)) {
8349
- const filePath = path17.join(statsigDir, filename);
8350
- fs16.writeFileSync(filePath, content, { mode: 420 });
8549
+ const filePath = path18.join(statsigDir, filename);
8550
+ fs17.writeFileSync(filePath, content, { mode: 420 });
8551
+ }
8552
+ if (session.credentials.githubToken) {
8553
+ console.log("[AgentManager] EP1146: GitHub MCP server enabled with installation token");
8351
8554
  }
8352
8555
  console.log("[AgentManager] Wrote Claude config files");
8353
8556
  } catch (configError) {
@@ -8542,17 +8745,17 @@ ${message}`;
8542
8745
  if (agentProcess && !agentProcess.killed) {
8543
8746
  console.log(`[AgentManager] Stopping session ${sessionId}`);
8544
8747
  agentProcess.kill("SIGINT");
8545
- await new Promise((resolve3) => {
8748
+ await new Promise((resolve4) => {
8546
8749
  const timeout = setTimeout(() => {
8547
8750
  if (!agentProcess.killed) {
8548
8751
  console.log(`[AgentManager] Force killing session ${sessionId}`);
8549
8752
  agentProcess.kill("SIGTERM");
8550
8753
  }
8551
- resolve3();
8754
+ resolve4();
8552
8755
  }, 5e3);
8553
8756
  agentProcess.once("exit", () => {
8554
8757
  clearTimeout(timeout);
8555
- resolve3();
8758
+ resolve4();
8556
8759
  });
8557
8760
  });
8558
8761
  }
@@ -8594,14 +8797,14 @@ ${message}`;
8594
8797
  */
8595
8798
  async cleanupOrphanedProcesses() {
8596
8799
  let cleaned = 0;
8597
- if (!fs16.existsSync(this.pidDir)) {
8800
+ if (!fs17.existsSync(this.pidDir)) {
8598
8801
  return { cleaned };
8599
8802
  }
8600
- const pidFiles = fs16.readdirSync(this.pidDir).filter((f) => f.endsWith(".pid"));
8803
+ const pidFiles = fs17.readdirSync(this.pidDir).filter((f) => f.endsWith(".pid"));
8601
8804
  for (const pidFile of pidFiles) {
8602
- const pidPath = path17.join(this.pidDir, pidFile);
8805
+ const pidPath = path18.join(this.pidDir, pidFile);
8603
8806
  try {
8604
- const pidStr = fs16.readFileSync(pidPath, "utf-8").trim();
8807
+ const pidStr = fs17.readFileSync(pidPath, "utf-8").trim();
8605
8808
  const pid = parseInt(pidStr, 10);
8606
8809
  if (!isNaN(pid)) {
8607
8810
  try {
@@ -8612,7 +8815,7 @@ ${message}`;
8612
8815
  } catch {
8613
8816
  }
8614
8817
  }
8615
- fs16.unlinkSync(pidPath);
8818
+ fs17.unlinkSync(pidPath);
8616
8819
  } catch (error) {
8617
8820
  console.warn(`[AgentManager] Error cleaning PID file ${pidFile}:`, error);
8618
8821
  }
@@ -8626,17 +8829,17 @@ ${message}`;
8626
8829
  * Write PID file for session tracking
8627
8830
  */
8628
8831
  writePidFile(sessionId, pid) {
8629
- const pidPath = path17.join(this.pidDir, `${sessionId}.pid`);
8630
- fs16.writeFileSync(pidPath, pid.toString());
8832
+ const pidPath = path18.join(this.pidDir, `${sessionId}.pid`);
8833
+ fs17.writeFileSync(pidPath, pid.toString());
8631
8834
  }
8632
8835
  /**
8633
8836
  * Remove PID file for session
8634
8837
  */
8635
8838
  removePidFile(sessionId) {
8636
- const pidPath = path17.join(this.pidDir, `${sessionId}.pid`);
8839
+ const pidPath = path18.join(this.pidDir, `${sessionId}.pid`);
8637
8840
  try {
8638
- if (fs16.existsSync(pidPath)) {
8639
- fs16.unlinkSync(pidPath);
8841
+ if (fs17.existsSync(pidPath)) {
8842
+ fs17.unlinkSync(pidPath);
8640
8843
  }
8641
8844
  } catch {
8642
8845
  }
@@ -8646,8 +8849,8 @@ ${message}`;
8646
8849
  // src/utils/dev-server.ts
8647
8850
  var import_child_process13 = require("child_process");
8648
8851
  var import_core11 = __toESM(require_dist());
8649
- var fs17 = __toESM(require("fs"));
8650
- var path18 = __toESM(require("path"));
8852
+ var fs18 = __toESM(require("fs"));
8853
+ var path19 = __toESM(require("path"));
8651
8854
  var MAX_RESTART_ATTEMPTS = 5;
8652
8855
  var INITIAL_RESTART_DELAY_MS = 2e3;
8653
8856
  var MAX_RESTART_DELAY_MS = 3e4;
@@ -8655,26 +8858,26 @@ var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
8655
8858
  var NODE_MEMORY_LIMIT_MB = 2048;
8656
8859
  var activeServers = /* @__PURE__ */ new Map();
8657
8860
  function getLogsDir() {
8658
- const logsDir = path18.join((0, import_core11.getConfigDir)(), "logs");
8659
- if (!fs17.existsSync(logsDir)) {
8660
- fs17.mkdirSync(logsDir, { recursive: true });
8861
+ const logsDir = path19.join((0, import_core11.getConfigDir)(), "logs");
8862
+ if (!fs18.existsSync(logsDir)) {
8863
+ fs18.mkdirSync(logsDir, { recursive: true });
8661
8864
  }
8662
8865
  return logsDir;
8663
8866
  }
8664
8867
  function getLogFilePath(moduleUid) {
8665
- return path18.join(getLogsDir(), `dev-${moduleUid}.log`);
8868
+ return path19.join(getLogsDir(), `dev-${moduleUid}.log`);
8666
8869
  }
8667
8870
  function rotateLogIfNeeded(logPath) {
8668
8871
  try {
8669
- if (fs17.existsSync(logPath)) {
8670
- const stats = fs17.statSync(logPath);
8872
+ if (fs18.existsSync(logPath)) {
8873
+ const stats = fs18.statSync(logPath);
8671
8874
  if (stats.size > MAX_LOG_SIZE_BYTES) {
8672
8875
  const backupPath = `${logPath}.1`;
8673
- if (fs17.existsSync(backupPath)) {
8674
- fs17.unlinkSync(backupPath);
8876
+ if (fs18.existsSync(backupPath)) {
8877
+ fs18.unlinkSync(backupPath);
8675
8878
  }
8676
- fs17.renameSync(logPath, backupPath);
8677
- console.log(`[DevServer] EP932: Rotated log file for ${path18.basename(logPath)}`);
8879
+ fs18.renameSync(logPath, backupPath);
8880
+ console.log(`[DevServer] EP932: Rotated log file for ${path19.basename(logPath)}`);
8678
8881
  }
8679
8882
  }
8680
8883
  } catch (error) {
@@ -8687,7 +8890,7 @@ function writeToLog(logPath, line, isError = false) {
8687
8890
  const prefix = isError ? "ERR" : "OUT";
8688
8891
  const logLine = `[${timestamp}] [${prefix}] ${line}
8689
8892
  `;
8690
- fs17.appendFileSync(logPath, logLine);
8893
+ fs18.appendFileSync(logPath, logLine);
8691
8894
  } catch {
8692
8895
  }
8693
8896
  }
@@ -8707,7 +8910,7 @@ async function killProcessOnPort(port) {
8707
8910
  } catch {
8708
8911
  }
8709
8912
  }
8710
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
8913
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
8711
8914
  for (const pid of pids) {
8712
8915
  try {
8713
8916
  (0, import_child_process13.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
@@ -8716,7 +8919,7 @@ async function killProcessOnPort(port) {
8716
8919
  } catch {
8717
8920
  }
8718
8921
  }
8719
- await new Promise((resolve3) => setTimeout(resolve3, 500));
8922
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
8720
8923
  const stillInUse = await isPortInUse(port);
8721
8924
  if (stillInUse) {
8722
8925
  console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
@@ -8736,7 +8939,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
8736
8939
  if (await isPortInUse(port)) {
8737
8940
  return true;
8738
8941
  }
8739
- await new Promise((resolve3) => setTimeout(resolve3, checkInterval));
8942
+ await new Promise((resolve4) => setTimeout(resolve4, checkInterval));
8740
8943
  }
8741
8944
  return false;
8742
8945
  }
@@ -8808,7 +9011,7 @@ async function handleProcessExit(moduleUid, code, signal) {
8808
9011
  const delay = calculateRestartDelay(serverInfo.restartCount);
8809
9012
  console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
8810
9013
  writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
8811
- await new Promise((resolve3) => setTimeout(resolve3, delay));
9014
+ await new Promise((resolve4) => setTimeout(resolve4, delay));
8812
9015
  if (!activeServers.has(moduleUid)) {
8813
9016
  console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
8814
9017
  return;
@@ -8866,8 +9069,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
8866
9069
  });
8867
9070
  injectedEnvVars = result.envVars;
8868
9071
  console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
8869
- const envFilePath = path18.join(projectPath, ".env");
8870
- if (!fs17.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
9072
+ const envFilePath = path19.join(projectPath, ".env");
9073
+ if (!fs18.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
8871
9074
  console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
8872
9075
  writeEnvFile(projectPath, injectedEnvVars);
8873
9076
  }
@@ -8933,7 +9136,7 @@ async function stopDevServer(moduleUid) {
8933
9136
  writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
8934
9137
  }
8935
9138
  serverInfo.process.kill("SIGTERM");
8936
- await new Promise((resolve3) => setTimeout(resolve3, 2e3));
9139
+ await new Promise((resolve4) => setTimeout(resolve4, 2e3));
8937
9140
  if (!serverInfo.process.killed) {
8938
9141
  serverInfo.process.kill("SIGKILL");
8939
9142
  }
@@ -8951,7 +9154,7 @@ async function restartDevServer(moduleUid) {
8951
9154
  writeToLog(logFile, `Manual restart requested`, false);
8952
9155
  }
8953
9156
  await stopDevServer(moduleUid);
8954
- await new Promise((resolve3) => setTimeout(resolve3, 1e3));
9157
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
8955
9158
  if (await isPortInUse(port)) {
8956
9159
  await killProcessOnPort(port);
8957
9160
  }
@@ -8973,19 +9176,19 @@ function getDevServerStatus() {
8973
9176
  }
8974
9177
 
8975
9178
  // src/utils/worktree.ts
8976
- var path19 = __toESM(require("path"));
8977
- var fs18 = __toESM(require("fs"));
9179
+ var path20 = __toESM(require("path"));
9180
+ var fs19 = __toESM(require("fs"));
8978
9181
  var os7 = __toESM(require("os"));
8979
9182
  var import_core12 = __toESM(require_dist());
8980
9183
  function getEpisodaRoot2() {
8981
- return process.env.EPISODA_ROOT || path19.join(os7.homedir(), "episoda");
9184
+ return process.env.EPISODA_ROOT || path20.join(os7.homedir(), "episoda");
8982
9185
  }
8983
9186
  function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
8984
9187
  const root = getEpisodaRoot2();
8985
- const worktreePath = path19.join(root, workspaceSlug, projectSlug, moduleUid);
9188
+ const worktreePath = path20.join(root, workspaceSlug, projectSlug, moduleUid);
8986
9189
  return {
8987
9190
  path: worktreePath,
8988
- exists: fs18.existsSync(worktreePath),
9191
+ exists: fs19.existsSync(worktreePath),
8989
9192
  moduleUid
8990
9193
  };
8991
9194
  }
@@ -8999,15 +9202,15 @@ async function getWorktreeInfoForModule(moduleUid) {
8999
9202
  return null;
9000
9203
  }
9001
9204
  const root = getEpisodaRoot2();
9002
- const workspaceRoot = path19.join(root, config.workspace_slug);
9205
+ const workspaceRoot = path20.join(root, config.workspace_slug);
9003
9206
  try {
9004
- const entries = fs18.readdirSync(workspaceRoot, { withFileTypes: true });
9207
+ const entries = fs19.readdirSync(workspaceRoot, { withFileTypes: true });
9005
9208
  for (const entry of entries) {
9006
9209
  if (!entry.isDirectory()) {
9007
9210
  continue;
9008
9211
  }
9009
- const worktreePath = path19.join(workspaceRoot, entry.name, moduleUid);
9010
- if (fs18.existsSync(worktreePath)) {
9212
+ const worktreePath = path20.join(workspaceRoot, entry.name, moduleUid);
9213
+ if (fs19.existsSync(worktreePath)) {
9011
9214
  return {
9012
9215
  path: worktreePath,
9013
9216
  exists: true,
@@ -9024,61 +9227,61 @@ async function getWorktreeInfoForModule(moduleUid) {
9024
9227
  }
9025
9228
 
9026
9229
  // src/framework-detector.ts
9027
- var fs19 = __toESM(require("fs"));
9028
- var path20 = __toESM(require("path"));
9230
+ var fs20 = __toESM(require("fs"));
9231
+ var path21 = __toESM(require("path"));
9029
9232
  function getInstallCommand(cwd) {
9030
- if (fs19.existsSync(path20.join(cwd, "bun.lockb"))) {
9233
+ if (fs20.existsSync(path21.join(cwd, "bun.lockb"))) {
9031
9234
  return {
9032
9235
  command: ["bun", "install"],
9033
9236
  description: "Installing dependencies with bun",
9034
9237
  detectedFrom: "bun.lockb"
9035
9238
  };
9036
9239
  }
9037
- if (fs19.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
9240
+ if (fs20.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
9038
9241
  return {
9039
9242
  command: ["pnpm", "install"],
9040
9243
  description: "Installing dependencies with pnpm",
9041
9244
  detectedFrom: "pnpm-lock.yaml"
9042
9245
  };
9043
9246
  }
9044
- if (fs19.existsSync(path20.join(cwd, "yarn.lock"))) {
9247
+ if (fs20.existsSync(path21.join(cwd, "yarn.lock"))) {
9045
9248
  return {
9046
9249
  command: ["yarn", "install"],
9047
9250
  description: "Installing dependencies with yarn",
9048
9251
  detectedFrom: "yarn.lock"
9049
9252
  };
9050
9253
  }
9051
- if (fs19.existsSync(path20.join(cwd, "package-lock.json"))) {
9254
+ if (fs20.existsSync(path21.join(cwd, "package-lock.json"))) {
9052
9255
  return {
9053
9256
  command: ["npm", "ci"],
9054
9257
  description: "Installing dependencies with npm ci",
9055
9258
  detectedFrom: "package-lock.json"
9056
9259
  };
9057
9260
  }
9058
- if (fs19.existsSync(path20.join(cwd, "package.json"))) {
9261
+ if (fs20.existsSync(path21.join(cwd, "package.json"))) {
9059
9262
  return {
9060
9263
  command: ["npm", "install"],
9061
9264
  description: "Installing dependencies with npm",
9062
9265
  detectedFrom: "package.json"
9063
9266
  };
9064
9267
  }
9065
- if (fs19.existsSync(path20.join(cwd, "Pipfile.lock")) || fs19.existsSync(path20.join(cwd, "Pipfile"))) {
9268
+ if (fs20.existsSync(path21.join(cwd, "Pipfile.lock")) || fs20.existsSync(path21.join(cwd, "Pipfile"))) {
9066
9269
  return {
9067
9270
  command: ["pipenv", "install"],
9068
9271
  description: "Installing dependencies with pipenv",
9069
- detectedFrom: fs19.existsSync(path20.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9272
+ detectedFrom: fs20.existsSync(path21.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9070
9273
  };
9071
9274
  }
9072
- if (fs19.existsSync(path20.join(cwd, "poetry.lock"))) {
9275
+ if (fs20.existsSync(path21.join(cwd, "poetry.lock"))) {
9073
9276
  return {
9074
9277
  command: ["poetry", "install"],
9075
9278
  description: "Installing dependencies with poetry",
9076
9279
  detectedFrom: "poetry.lock"
9077
9280
  };
9078
9281
  }
9079
- if (fs19.existsSync(path20.join(cwd, "pyproject.toml"))) {
9080
- const pyprojectPath = path20.join(cwd, "pyproject.toml");
9081
- const content = fs19.readFileSync(pyprojectPath, "utf-8");
9282
+ if (fs20.existsSync(path21.join(cwd, "pyproject.toml"))) {
9283
+ const pyprojectPath = path21.join(cwd, "pyproject.toml");
9284
+ const content = fs20.readFileSync(pyprojectPath, "utf-8");
9082
9285
  if (content.includes("[tool.poetry]")) {
9083
9286
  return {
9084
9287
  command: ["poetry", "install"],
@@ -9087,41 +9290,41 @@ function getInstallCommand(cwd) {
9087
9290
  };
9088
9291
  }
9089
9292
  }
9090
- if (fs19.existsSync(path20.join(cwd, "requirements.txt"))) {
9293
+ if (fs20.existsSync(path21.join(cwd, "requirements.txt"))) {
9091
9294
  return {
9092
9295
  command: ["pip", "install", "-r", "requirements.txt"],
9093
9296
  description: "Installing dependencies with pip",
9094
9297
  detectedFrom: "requirements.txt"
9095
9298
  };
9096
9299
  }
9097
- if (fs19.existsSync(path20.join(cwd, "Gemfile.lock")) || fs19.existsSync(path20.join(cwd, "Gemfile"))) {
9300
+ if (fs20.existsSync(path21.join(cwd, "Gemfile.lock")) || fs20.existsSync(path21.join(cwd, "Gemfile"))) {
9098
9301
  return {
9099
9302
  command: ["bundle", "install"],
9100
9303
  description: "Installing dependencies with bundler",
9101
- detectedFrom: fs19.existsSync(path20.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9304
+ detectedFrom: fs20.existsSync(path21.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9102
9305
  };
9103
9306
  }
9104
- if (fs19.existsSync(path20.join(cwd, "go.sum")) || fs19.existsSync(path20.join(cwd, "go.mod"))) {
9307
+ if (fs20.existsSync(path21.join(cwd, "go.sum")) || fs20.existsSync(path21.join(cwd, "go.mod"))) {
9105
9308
  return {
9106
9309
  command: ["go", "mod", "download"],
9107
9310
  description: "Downloading Go modules",
9108
- detectedFrom: fs19.existsSync(path20.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9311
+ detectedFrom: fs20.existsSync(path21.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9109
9312
  };
9110
9313
  }
9111
- if (fs19.existsSync(path20.join(cwd, "Cargo.lock")) || fs19.existsSync(path20.join(cwd, "Cargo.toml"))) {
9314
+ if (fs20.existsSync(path21.join(cwd, "Cargo.lock")) || fs20.existsSync(path21.join(cwd, "Cargo.toml"))) {
9112
9315
  return {
9113
9316
  command: ["cargo", "build"],
9114
9317
  description: "Building Rust project (downloads dependencies)",
9115
- detectedFrom: fs19.existsSync(path20.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9318
+ detectedFrom: fs20.existsSync(path21.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9116
9319
  };
9117
9320
  }
9118
9321
  return null;
9119
9322
  }
9120
9323
 
9121
9324
  // src/daemon/daemon-process.ts
9122
- var fs20 = __toESM(require("fs"));
9325
+ var fs21 = __toESM(require("fs"));
9123
9326
  var os8 = __toESM(require("os"));
9124
- var path21 = __toESM(require("path"));
9327
+ var path22 = __toESM(require("path"));
9125
9328
  var packageJson = require_package();
9126
9329
  async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
9127
9330
  const now = Date.now();
@@ -9371,7 +9574,7 @@ var Daemon = class _Daemon {
9371
9574
  if (attempt < MAX_RETRIES) {
9372
9575
  const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
9373
9576
  console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
9374
- await new Promise((resolve3) => setTimeout(resolve3, delay));
9577
+ await new Promise((resolve4) => setTimeout(resolve4, delay));
9375
9578
  await this.disconnectProject(projectPath);
9376
9579
  }
9377
9580
  }
@@ -9572,8 +9775,8 @@ var Daemon = class _Daemon {
9572
9775
  }
9573
9776
  }
9574
9777
  let releaseLock;
9575
- const lockPromise = new Promise((resolve3) => {
9576
- releaseLock = resolve3;
9778
+ const lockPromise = new Promise((resolve4) => {
9779
+ releaseLock = resolve4;
9577
9780
  });
9578
9781
  this.tunnelOperationLocks.set(moduleUid, lockPromise);
9579
9782
  try {
@@ -9599,7 +9802,7 @@ var Daemon = class _Daemon {
9599
9802
  const maxWait = 35e3;
9600
9803
  const startTime = Date.now();
9601
9804
  while (this.pendingConnections.has(projectPath) && Date.now() - startTime < maxWait) {
9602
- await new Promise((resolve3) => setTimeout(resolve3, 500));
9805
+ await new Promise((resolve4) => setTimeout(resolve4, 500));
9603
9806
  }
9604
9807
  if (this.liveConnections.has(projectPath)) {
9605
9808
  console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
@@ -9647,7 +9850,7 @@ var Daemon = class _Daemon {
9647
9850
  client.updateActivity();
9648
9851
  try {
9649
9852
  const gitCmd = message.command;
9650
- const bareRepoPath = path21.join(projectPath, ".bare");
9853
+ const bareRepoPath = path22.join(projectPath, ".bare");
9651
9854
  const cwd = gitCmd.worktreePath || bareRepoPath;
9652
9855
  if (gitCmd.worktreePath) {
9653
9856
  console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
@@ -10191,21 +10394,21 @@ var Daemon = class _Daemon {
10191
10394
  let daemonPid;
10192
10395
  try {
10193
10396
  const pidPath = getPidFilePath();
10194
- if (fs20.existsSync(pidPath)) {
10195
- const pidStr = fs20.readFileSync(pidPath, "utf-8").trim();
10397
+ if (fs21.existsSync(pidPath)) {
10398
+ const pidStr = fs21.readFileSync(pidPath, "utf-8").trim();
10196
10399
  daemonPid = parseInt(pidStr, 10);
10197
10400
  }
10198
10401
  } catch (pidError) {
10199
10402
  console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError);
10200
10403
  }
10201
- const authSuccessPromise = new Promise((resolve3, reject) => {
10404
+ const authSuccessPromise = new Promise((resolve4, reject) => {
10202
10405
  const AUTH_TIMEOUT = 3e4;
10203
10406
  const timeout = setTimeout(() => {
10204
10407
  reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
10205
10408
  }, AUTH_TIMEOUT);
10206
10409
  const authHandler = () => {
10207
10410
  clearTimeout(timeout);
10208
- resolve3();
10411
+ resolve4();
10209
10412
  };
10210
10413
  client.once("auth_success", authHandler);
10211
10414
  const errorHandler = (message) => {
@@ -10273,28 +10476,28 @@ var Daemon = class _Daemon {
10273
10476
  * - workDir: The directory to run git commands in (cwd)
10274
10477
  */
10275
10478
  getGitDirs(projectPath) {
10276
- const bareDir = path21.join(projectPath, ".bare");
10277
- const gitPath = path21.join(projectPath, ".git");
10278
- if (fs20.existsSync(bareDir) && fs20.statSync(bareDir).isDirectory()) {
10479
+ const bareDir = path22.join(projectPath, ".bare");
10480
+ const gitPath = path22.join(projectPath, ".git");
10481
+ if (fs21.existsSync(bareDir) && fs21.statSync(bareDir).isDirectory()) {
10279
10482
  return { gitDir: bareDir, workDir: projectPath };
10280
10483
  }
10281
- if (fs20.existsSync(gitPath) && fs20.statSync(gitPath).isDirectory()) {
10484
+ if (fs21.existsSync(gitPath) && fs21.statSync(gitPath).isDirectory()) {
10282
10485
  return { gitDir: null, workDir: projectPath };
10283
10486
  }
10284
- if (fs20.existsSync(gitPath) && fs20.statSync(gitPath).isFile()) {
10487
+ if (fs21.existsSync(gitPath) && fs21.statSync(gitPath).isFile()) {
10285
10488
  return { gitDir: null, workDir: projectPath };
10286
10489
  }
10287
- const entries = fs20.readdirSync(projectPath, { withFileTypes: true });
10490
+ const entries = fs21.readdirSync(projectPath, { withFileTypes: true });
10288
10491
  for (const entry of entries) {
10289
10492
  if (entry.isDirectory() && entry.name.startsWith("EP")) {
10290
- const worktreePath = path21.join(projectPath, entry.name);
10291
- const worktreeGit = path21.join(worktreePath, ".git");
10292
- if (fs20.existsSync(worktreeGit)) {
10493
+ const worktreePath = path22.join(projectPath, entry.name);
10494
+ const worktreeGit = path22.join(worktreePath, ".git");
10495
+ if (fs21.existsSync(worktreeGit)) {
10293
10496
  return { gitDir: null, workDir: worktreePath };
10294
10497
  }
10295
10498
  }
10296
10499
  }
10297
- if (fs20.existsSync(bareDir)) {
10500
+ if (fs21.existsSync(bareDir)) {
10298
10501
  return { gitDir: bareDir, workDir: projectPath };
10299
10502
  }
10300
10503
  return { gitDir: null, workDir: projectPath };
@@ -10358,24 +10561,24 @@ var Daemon = class _Daemon {
10358
10561
  async installGitHooks(projectPath) {
10359
10562
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
10360
10563
  let hooksDir;
10361
- const bareHooksDir = path21.join(projectPath, ".bare", "hooks");
10362
- const gitHooksDir = path21.join(projectPath, ".git", "hooks");
10363
- if (fs20.existsSync(bareHooksDir)) {
10564
+ const bareHooksDir = path22.join(projectPath, ".bare", "hooks");
10565
+ const gitHooksDir = path22.join(projectPath, ".git", "hooks");
10566
+ if (fs21.existsSync(bareHooksDir)) {
10364
10567
  hooksDir = bareHooksDir;
10365
- } else if (fs20.existsSync(gitHooksDir) && fs20.statSync(path21.join(projectPath, ".git")).isDirectory()) {
10568
+ } else if (fs21.existsSync(gitHooksDir) && fs21.statSync(path22.join(projectPath, ".git")).isDirectory()) {
10366
10569
  hooksDir = gitHooksDir;
10367
10570
  } else {
10368
- const parentBareHooks = path21.join(projectPath, "..", ".bare", "hooks");
10369
- if (fs20.existsSync(parentBareHooks)) {
10571
+ const parentBareHooks = path22.join(projectPath, "..", ".bare", "hooks");
10572
+ if (fs21.existsSync(parentBareHooks)) {
10370
10573
  hooksDir = parentBareHooks;
10371
10574
  } else {
10372
10575
  console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
10373
10576
  return;
10374
10577
  }
10375
10578
  }
10376
- if (!fs20.existsSync(hooksDir)) {
10579
+ if (!fs21.existsSync(hooksDir)) {
10377
10580
  try {
10378
- fs20.mkdirSync(hooksDir, { recursive: true });
10581
+ fs21.mkdirSync(hooksDir, { recursive: true });
10379
10582
  } catch (error) {
10380
10583
  console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
10381
10584
  return;
@@ -10383,20 +10586,20 @@ var Daemon = class _Daemon {
10383
10586
  }
10384
10587
  for (const hookName of hooks) {
10385
10588
  try {
10386
- const hookPath = path21.join(hooksDir, hookName);
10387
- const bundledHookPath = path21.join(__dirname, "..", "hooks", hookName);
10388
- if (!fs20.existsSync(bundledHookPath)) {
10589
+ const hookPath = path22.join(hooksDir, hookName);
10590
+ const bundledHookPath = path22.join(__dirname, "..", "hooks", hookName);
10591
+ if (!fs21.existsSync(bundledHookPath)) {
10389
10592
  console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
10390
10593
  continue;
10391
10594
  }
10392
- const hookContent = fs20.readFileSync(bundledHookPath, "utf-8");
10393
- if (fs20.existsSync(hookPath)) {
10394
- const existingContent = fs20.readFileSync(hookPath, "utf-8");
10595
+ const hookContent = fs21.readFileSync(bundledHookPath, "utf-8");
10596
+ if (fs21.existsSync(hookPath)) {
10597
+ const existingContent = fs21.readFileSync(hookPath, "utf-8");
10395
10598
  if (existingContent === hookContent) {
10396
10599
  continue;
10397
10600
  }
10398
10601
  }
10399
- fs20.writeFileSync(hookPath, hookContent, { mode: 493 });
10602
+ fs21.writeFileSync(hookPath, hookContent, { mode: 493 });
10400
10603
  console.log(`[Daemon] Installed git hook: ${hookName}`);
10401
10604
  } catch (error) {
10402
10605
  console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
@@ -11500,8 +11703,8 @@ var Daemon = class _Daemon {
11500
11703
  await this.shutdown();
11501
11704
  try {
11502
11705
  const pidPath = getPidFilePath();
11503
- if (fs20.existsSync(pidPath)) {
11504
- fs20.unlinkSync(pidPath);
11706
+ if (fs21.existsSync(pidPath)) {
11707
+ fs21.unlinkSync(pidPath);
11505
11708
  console.log("[Daemon] PID file cleaned up");
11506
11709
  }
11507
11710
  } catch (error) {