@episoda/cli 0.2.145 → 0.2.147

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.
@@ -1880,7 +1880,7 @@ var require_git_executor = __commonJS({
1880
1880
  async executeCloneBare(command, options) {
1881
1881
  try {
1882
1882
  const fs24 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1883
- const path26 = await Promise.resolve().then(() => __importStar(require("path")));
1883
+ const path25 = await Promise.resolve().then(() => __importStar(require("path")));
1884
1884
  try {
1885
1885
  await fs24.access(command.path);
1886
1886
  return {
@@ -1891,7 +1891,7 @@ var require_git_executor = __commonJS({
1891
1891
  };
1892
1892
  } catch {
1893
1893
  }
1894
- const parentDir = path26.dirname(command.path);
1894
+ const parentDir = path25.dirname(command.path);
1895
1895
  try {
1896
1896
  await fs24.mkdir(parentDir, { recursive: true });
1897
1897
  } catch {
@@ -1942,13 +1942,13 @@ var require_git_executor = __commonJS({
1942
1942
  async executeProjectInfo(cwd, options) {
1943
1943
  try {
1944
1944
  const fs24 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1945
- const path26 = await Promise.resolve().then(() => __importStar(require("path")));
1945
+ const path25 = await Promise.resolve().then(() => __importStar(require("path")));
1946
1946
  let currentPath = cwd;
1947
1947
  let projectPath = cwd;
1948
1948
  let bareRepoPath;
1949
1949
  for (let i = 0; i < 10; i++) {
1950
- const bareDir = path26.join(currentPath, ".bare");
1951
- const episodaDir = path26.join(currentPath, ".episoda");
1950
+ const bareDir = path25.join(currentPath, ".bare");
1951
+ const episodaDir = path25.join(currentPath, ".episoda");
1952
1952
  try {
1953
1953
  await fs24.access(bareDir);
1954
1954
  await fs24.access(episodaDir);
@@ -1956,7 +1956,7 @@ var require_git_executor = __commonJS({
1956
1956
  bareRepoPath = bareDir;
1957
1957
  break;
1958
1958
  } catch {
1959
- const parentPath = path26.dirname(currentPath);
1959
+ const parentPath = path25.dirname(currentPath);
1960
1960
  if (parentPath === currentPath) {
1961
1961
  break;
1962
1962
  }
@@ -2610,29 +2610,29 @@ var require_auth = __commonJS({
2610
2610
  exports2.saveConfig = saveConfig2;
2611
2611
  exports2.validateToken = validateToken;
2612
2612
  var fs24 = __importStar(require("fs"));
2613
- var path26 = __importStar(require("path"));
2613
+ var path25 = __importStar(require("path"));
2614
2614
  var os10 = __importStar(require("os"));
2615
2615
  var child_process_1 = require("child_process");
2616
2616
  var DEFAULT_CONFIG_FILE = "config.json";
2617
2617
  var hasWarnedMissingProjectId = false;
2618
2618
  var hasWarnedMissingRequiredFields = false;
2619
2619
  function getConfigDir8() {
2620
- return process.env.EPISODA_CONFIG_DIR || path26.join(os10.homedir(), ".episoda");
2620
+ return process.env.EPISODA_CONFIG_DIR || path25.join(os10.homedir(), ".episoda");
2621
2621
  }
2622
2622
  function getConfigPath(configPath) {
2623
2623
  if (configPath) {
2624
2624
  return configPath;
2625
2625
  }
2626
- return path26.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
2626
+ return path25.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
2627
2627
  }
2628
2628
  function ensureConfigDir(configPath) {
2629
- const dir = path26.dirname(configPath);
2629
+ const dir = path25.dirname(configPath);
2630
2630
  const isNew = !fs24.existsSync(dir);
2631
2631
  if (isNew) {
2632
2632
  fs24.mkdirSync(dir, { recursive: true, mode: 448 });
2633
2633
  }
2634
2634
  if (process.platform === "darwin") {
2635
- const nosyncPath = path26.join(dir, ".nosync");
2635
+ const nosyncPath = path25.join(dir, ".nosync");
2636
2636
  if (isNew || !fs24.existsSync(nosyncPath)) {
2637
2637
  try {
2638
2638
  fs24.writeFileSync(nosyncPath, "", { mode: 384 });
@@ -2913,7 +2913,7 @@ var require_package = __commonJS({
2913
2913
  "package.json"(exports2, module2) {
2914
2914
  module2.exports = {
2915
2915
  name: "@episoda/cli",
2916
- version: "0.2.144",
2916
+ version: "0.2.146",
2917
2917
  description: "CLI tool for Episoda local development workflow orchestration",
2918
2918
  main: "dist/index.js",
2919
2919
  types: "dist/index.d.ts",
@@ -3304,77 +3304,7 @@ var import_child_process2 = require("child_process");
3304
3304
  var semver = __toESM(require("semver"));
3305
3305
 
3306
3306
  // src/ipc/ipc-client.ts
3307
- var net2 = __toESM(require("net"));
3308
- var path5 = __toESM(require("path"));
3309
- var crypto2 = __toESM(require("crypto"));
3310
3307
  var import_core5 = __toESM(require_dist());
3311
- var getSocketPath2 = () => path5.join((0, import_core5.getConfigDir)(), "daemon.sock");
3312
- var DEFAULT_TIMEOUT = 15e3;
3313
- async function sendCommand(command, params, timeout = DEFAULT_TIMEOUT) {
3314
- return new Promise((resolve4, reject) => {
3315
- const socket = net2.createConnection(getSocketPath2());
3316
- const requestId = crypto2.randomUUID();
3317
- let buffer = "";
3318
- let timeoutHandle;
3319
- timeoutHandle = setTimeout(() => {
3320
- socket.destroy();
3321
- reject(new Error(`Command timed out after ${timeout}ms`));
3322
- }, timeout);
3323
- socket.on("connect", () => {
3324
- const request2 = {
3325
- id: requestId,
3326
- command,
3327
- params
3328
- };
3329
- socket.write(JSON.stringify(request2) + "\n");
3330
- });
3331
- socket.on("data", (chunk) => {
3332
- buffer += chunk.toString();
3333
- const newlineIndex = buffer.indexOf("\n");
3334
- if (newlineIndex === -1) return;
3335
- const message = buffer.slice(0, newlineIndex);
3336
- try {
3337
- const response = JSON.parse(message);
3338
- if (response.id !== requestId) {
3339
- reject(new Error("Response ID mismatch"));
3340
- return;
3341
- }
3342
- clearTimeout(timeoutHandle);
3343
- socket.end();
3344
- if (response.success) {
3345
- resolve4(response.data);
3346
- } else {
3347
- reject(new Error(response.error || "Command failed"));
3348
- }
3349
- } catch (error) {
3350
- clearTimeout(timeoutHandle);
3351
- socket.end();
3352
- reject(new Error("Invalid response from daemon"));
3353
- }
3354
- });
3355
- socket.on("error", (error) => {
3356
- clearTimeout(timeoutHandle);
3357
- if (error.code === "ENOENT" || error.code === "ECONNREFUSED") {
3358
- reject(new Error("Daemon is not running. Start it with: episoda dev"));
3359
- } else {
3360
- reject(error);
3361
- }
3362
- });
3363
- socket.on("timeout", () => {
3364
- clearTimeout(timeoutHandle);
3365
- socket.destroy();
3366
- reject(new Error("Connection timeout"));
3367
- });
3368
- });
3369
- }
3370
- async function isDaemonReachable() {
3371
- try {
3372
- await sendCommand("ping", {}, 1e3);
3373
- return true;
3374
- } catch (error) {
3375
- return false;
3376
- }
3377
- }
3378
3308
 
3379
3309
  // src/utils/update-checker.ts
3380
3310
  var PACKAGE_NAME = "@episoda/cli";
@@ -3438,67 +3368,6 @@ function performSyncUpdate(targetVersion) {
3438
3368
  };
3439
3369
  }
3440
3370
  }
3441
- async function restartDaemon() {
3442
- try {
3443
- (0, import_child_process2.execSync)("episoda stop", {
3444
- stdio: ["pipe", "pipe", "pipe"],
3445
- timeout: 1e4
3446
- });
3447
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
3448
- const child = (0, import_child_process2.spawn)("episoda", ["dev"], {
3449
- detached: true,
3450
- stdio: "ignore",
3451
- shell: true
3452
- });
3453
- child.unref();
3454
- const maxWaitMs = 1e4;
3455
- const pollIntervalMs = 500;
3456
- const startTime = Date.now();
3457
- while (Date.now() - startTime < maxWaitMs) {
3458
- await new Promise((resolve4) => setTimeout(resolve4, pollIntervalMs));
3459
- try {
3460
- const reachable = await isDaemonReachable();
3461
- if (reachable) {
3462
- return { success: true };
3463
- }
3464
- } catch {
3465
- }
3466
- }
3467
- return {
3468
- success: false,
3469
- error: "Daemon did not respond within 10 seconds"
3470
- };
3471
- } catch (error) {
3472
- return {
3473
- success: false,
3474
- error: error instanceof Error ? error.message : String(error)
3475
- };
3476
- }
3477
- }
3478
- async function updateAndRestartDaemon(targetVersion) {
3479
- const updateResult = performSyncUpdate(targetVersion);
3480
- if (!updateResult.success) {
3481
- return {
3482
- updateSuccess: false,
3483
- restartSuccess: false,
3484
- error: `Update failed: ${updateResult.error}`
3485
- };
3486
- }
3487
- const installedVersion = getInstalledVersion();
3488
- if (installedVersion !== targetVersion) {
3489
- return {
3490
- updateSuccess: false,
3491
- restartSuccess: false,
3492
- error: `Version mismatch: expected ${targetVersion}, got ${installedVersion || "unknown"}`
3493
- };
3494
- }
3495
- const restartResult = await restartDaemon();
3496
- return {
3497
- updateSuccess: true,
3498
- restartSuccess: restartResult.success,
3499
- error: restartResult.success ? void 0 : `Restart failed: ${restartResult.error}`
3500
- };
3501
- }
3502
3371
  function getInstalledVersion() {
3503
3372
  try {
3504
3373
  const output = (0, import_child_process2.execSync)(`npm list -g ${PACKAGE_NAME} --json`, {
@@ -3514,26 +3383,26 @@ function getInstalledVersion() {
3514
3383
 
3515
3384
  // src/daemon/handlers/file-handlers.ts
3516
3385
  var fs5 = __toESM(require("fs"));
3517
- var path7 = __toESM(require("path"));
3386
+ var path6 = __toESM(require("path"));
3518
3387
  var readline = __toESM(require("readline"));
3519
3388
 
3520
3389
  // src/daemon/permissions/path-classifier.ts
3521
- var path6 = __toESM(require("path"));
3390
+ var path5 = __toESM(require("path"));
3522
3391
  var fs4 = __toESM(require("fs"));
3523
3392
  function classifyPath(targetPath, options) {
3524
3393
  const { workspaceRoot, projectRoot } = options;
3525
- const normalizedWorkspace = path6.resolve(workspaceRoot);
3526
- const normalizedProject = path6.resolve(projectRoot);
3527
- const resolvedPath = path6.isAbsolute(targetPath) ? path6.resolve(targetPath) : path6.resolve(projectRoot, targetPath);
3528
- const artifactsDir = path6.join(normalizedWorkspace, "artifacts");
3529
- if (resolvedPath.startsWith(artifactsDir + path6.sep) || resolvedPath === artifactsDir) {
3394
+ const normalizedWorkspace = path5.resolve(workspaceRoot);
3395
+ const normalizedProject = path5.resolve(projectRoot);
3396
+ const resolvedPath = path5.isAbsolute(targetPath) ? path5.resolve(targetPath) : path5.resolve(projectRoot, targetPath);
3397
+ const artifactsDir = path5.join(normalizedWorkspace, "artifacts");
3398
+ if (resolvedPath.startsWith(artifactsDir + path5.sep) || resolvedPath === artifactsDir) {
3530
3399
  return {
3531
3400
  classification: "artifacts",
3532
3401
  resolvedPath
3533
3402
  };
3534
3403
  }
3535
- if (!resolvedPath.startsWith(normalizedProject + path6.sep) && resolvedPath !== normalizedProject) {
3536
- if (resolvedPath.startsWith(normalizedWorkspace + path6.sep)) {
3404
+ if (!resolvedPath.startsWith(normalizedProject + path5.sep) && resolvedPath !== normalizedProject) {
3405
+ if (resolvedPath.startsWith(normalizedWorkspace + path5.sep)) {
3537
3406
  return {
3538
3407
  classification: "unknown",
3539
3408
  resolvedPath
@@ -3544,8 +3413,8 @@ function classifyPath(targetPath, options) {
3544
3413
  resolvedPath
3545
3414
  };
3546
3415
  }
3547
- const relativePath = path6.relative(normalizedProject, resolvedPath);
3548
- const pathParts = relativePath.split(path6.sep);
3416
+ const relativePath = path5.relative(normalizedProject, resolvedPath);
3417
+ const pathParts = relativePath.split(path5.sep);
3549
3418
  if (pathParts[0] === ".bare") {
3550
3419
  return {
3551
3420
  classification: "bare_repo",
@@ -3560,8 +3429,8 @@ function classifyPath(targetPath, options) {
3560
3429
  }
3561
3430
  const firstPart = pathParts[0];
3562
3431
  if (firstPart && isModuleUidPattern(firstPart)) {
3563
- const worktreeDir = path6.join(normalizedProject, firstPart);
3564
- const gitFile = path6.join(worktreeDir, ".git");
3432
+ const worktreeDir = path5.join(normalizedProject, firstPart);
3433
+ const gitFile = path5.join(worktreeDir, ".git");
3565
3434
  const worktreeExists = fs4.existsSync(gitFile) && fs4.statSync(gitFile).isFile();
3566
3435
  return {
3567
3436
  classification: "worktree",
@@ -3579,19 +3448,19 @@ function isModuleUidPattern(str) {
3579
3448
  return /^EP\d+$/.test(str);
3580
3449
  }
3581
3450
  function deriveWorkspaceRoot(projectPath) {
3582
- return path6.dirname(path6.resolve(projectPath));
3451
+ return path5.dirname(path5.resolve(projectPath));
3583
3452
  }
3584
3453
  function validateWorkspacePath(filePath, projectPath) {
3585
- const normalizedProjectPath = path6.resolve(projectPath);
3454
+ const normalizedProjectPath = path5.resolve(projectPath);
3586
3455
  const workspaceRoot = deriveWorkspaceRoot(projectPath);
3587
- const normalizedWorkspace = path6.resolve(workspaceRoot);
3588
- const artifactsDir = path6.join(normalizedWorkspace, "artifacts");
3589
- const absolutePath = path6.isAbsolute(filePath) ? path6.resolve(filePath) : path6.resolve(projectPath, filePath);
3590
- const normalizedPath = path6.normalize(absolutePath);
3591
- if (normalizedPath.startsWith(normalizedProjectPath + path6.sep) || normalizedPath === normalizedProjectPath) {
3456
+ const normalizedWorkspace = path5.resolve(workspaceRoot);
3457
+ const artifactsDir = path5.join(normalizedWorkspace, "artifacts");
3458
+ const absolutePath = path5.isAbsolute(filePath) ? path5.resolve(filePath) : path5.resolve(projectPath, filePath);
3459
+ const normalizedPath = path5.normalize(absolutePath);
3460
+ if (normalizedPath.startsWith(normalizedProjectPath + path5.sep) || normalizedPath === normalizedProjectPath) {
3592
3461
  return normalizedPath;
3593
3462
  }
3594
- if (normalizedPath.startsWith(artifactsDir + path6.sep) || normalizedPath === artifactsDir) {
3463
+ if (normalizedPath.startsWith(artifactsDir + path5.sep) || normalizedPath === artifactsDir) {
3595
3464
  return normalizedPath;
3596
3465
  }
3597
3466
  return null;
@@ -3748,7 +3617,7 @@ async function handleFileWrite(command, projectPath) {
3748
3617
  }
3749
3618
  try {
3750
3619
  if (createDirs) {
3751
- const dirPath = path7.dirname(validPath);
3620
+ const dirPath = path6.dirname(validPath);
3752
3621
  if (!fs5.existsSync(dirPath)) {
3753
3622
  fs5.mkdirSync(dirPath, { recursive: true });
3754
3623
  }
@@ -3806,7 +3675,7 @@ async function handleFileList(command, projectPath) {
3806
3675
  const dirEntries = await fs5.promises.readdir(validPath, { withFileTypes: true });
3807
3676
  for (const entry of dirEntries) {
3808
3677
  if (!includeHidden && entry.name.startsWith(".")) continue;
3809
- const entryPath = path7.join(validPath, entry.name);
3678
+ const entryPath = path6.join(validPath, entry.name);
3810
3679
  const entryStats = await fs5.promises.stat(entryPath);
3811
3680
  entries.push({
3812
3681
  name: entry.name,
@@ -3832,8 +3701,8 @@ async function listDirectoryRecursive(basePath, currentPath, entries, includeHid
3832
3701
  const dirEntries = await fs5.promises.readdir(currentPath, { withFileTypes: true });
3833
3702
  for (const entry of dirEntries) {
3834
3703
  if (!includeHidden && entry.name.startsWith(".")) continue;
3835
- const entryPath = path7.join(currentPath, entry.name);
3836
- const relativePath = path7.relative(basePath, entryPath);
3704
+ const entryPath = path6.join(currentPath, entry.name);
3705
+ const relativePath = path6.relative(basePath, entryPath);
3837
3706
  try {
3838
3707
  const entryStats = await fs5.promises.stat(entryPath);
3839
3708
  entries.push({
@@ -3953,8 +3822,8 @@ async function searchFilesRecursive(basePath, currentPath, pattern, files, maxRe
3953
3822
  for (const entry of entries) {
3954
3823
  if (files.length >= maxResults) return;
3955
3824
  if (entry.name.startsWith(".")) continue;
3956
- const entryPath = path7.join(currentPath, entry.name);
3957
- const relativePath = path7.relative(basePath, entryPath);
3825
+ const entryPath = path6.join(currentPath, entry.name);
3826
+ const relativePath = path6.relative(basePath, entryPath);
3958
3827
  try {
3959
3828
  if (entry.isDirectory()) {
3960
3829
  await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults);
@@ -4012,7 +3881,7 @@ async function handleFileGrep(command, projectPath) {
4012
3881
  }
4013
3882
  async function grepFile(basePath, filePath, pattern, matches, maxResults) {
4014
3883
  if (matches.length >= maxResults) return;
4015
- const relativePath = path7.relative(basePath, filePath);
3884
+ const relativePath = path6.relative(basePath, filePath);
4016
3885
  const fileStream = fs5.createReadStream(filePath, { encoding: "utf8" });
4017
3886
  const rl = readline.createInterface({
4018
3887
  input: fileStream,
@@ -4027,7 +3896,7 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
4027
3896
  }
4028
3897
  if (pattern.test(line)) {
4029
3898
  matches.push({
4030
- file: relativePath || path7.basename(filePath),
3899
+ file: relativePath || path6.basename(filePath),
4031
3900
  line: lineNumber,
4032
3901
  content: line.slice(0, 500)
4033
3902
  // Truncate long lines
@@ -4041,7 +3910,7 @@ async function grepDirectoryRecursive(basePath, currentPath, searchPattern, file
4041
3910
  for (const entry of entries) {
4042
3911
  if (matches.length >= maxResults) return;
4043
3912
  if (entry.name.startsWith(".")) continue;
4044
- const entryPath = path7.join(currentPath, entry.name);
3913
+ const entryPath = path6.join(currentPath, entry.name);
4045
3914
  try {
4046
3915
  if (entry.isDirectory()) {
4047
3916
  await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults);
@@ -4132,7 +4001,7 @@ async function handleFileDelete(command, projectPath) {
4132
4001
  error: "Invalid path: directory traversal not allowed"
4133
4002
  };
4134
4003
  }
4135
- const normalizedProjectPath = path7.resolve(projectPath);
4004
+ const normalizedProjectPath = path6.resolve(projectPath);
4136
4005
  if (validPath === normalizedProjectPath) {
4137
4006
  return {
4138
4007
  success: false,
@@ -4230,13 +4099,13 @@ async function handleFileMkdir(command, projectPath) {
4230
4099
 
4231
4100
  // src/daemon/handlers/exec-handler.ts
4232
4101
  var import_child_process3 = require("child_process");
4233
- var DEFAULT_TIMEOUT2 = 3e4;
4102
+ var DEFAULT_TIMEOUT = 3e4;
4234
4103
  var MAX_TIMEOUT = 3e5;
4235
4104
  async function handleExec(command, projectPath) {
4236
4105
  const {
4237
4106
  command: cmd,
4238
4107
  cwd = projectPath,
4239
- timeout = DEFAULT_TIMEOUT2,
4108
+ timeout = DEFAULT_TIMEOUT,
4240
4109
  env = {}
4241
4110
  } = command;
4242
4111
  const effectiveTimeout = Math.min(Math.max(timeout, 1e3), MAX_TIMEOUT);
@@ -4312,7 +4181,7 @@ async function handleExec(command, projectPath) {
4312
4181
  }
4313
4182
 
4314
4183
  // src/daemon/handlers/worktree-handlers.ts
4315
- var path17 = __toESM(require("path"));
4184
+ var path16 = __toESM(require("path"));
4316
4185
  var fs15 = __toESM(require("fs"));
4317
4186
  var os6 = __toESM(require("os"));
4318
4187
  var import_child_process8 = require("child_process");
@@ -4320,7 +4189,7 @@ var import_util = require("util");
4320
4189
 
4321
4190
  // src/daemon/worktree-manager.ts
4322
4191
  var fs6 = __toESM(require("fs"));
4323
- var path8 = __toESM(require("path"));
4192
+ var path7 = __toESM(require("path"));
4324
4193
  var import_core6 = __toESM(require_dist());
4325
4194
  function validateModuleUid(moduleUid) {
4326
4195
  if (!moduleUid || typeof moduleUid !== "string" || !moduleUid.trim()) {
@@ -4344,8 +4213,8 @@ var WorktreeManager = class _WorktreeManager {
4344
4213
  // ============================================================
4345
4214
  this.lockPath = "";
4346
4215
  this.projectRoot = projectRoot;
4347
- this.bareRepoPath = path8.join(projectRoot, ".bare");
4348
- this.configPath = path8.join(projectRoot, ".episoda", "config.json");
4216
+ this.bareRepoPath = path7.join(projectRoot, ".bare");
4217
+ this.configPath = path7.join(projectRoot, ".episoda", "config.json");
4349
4218
  this.gitExecutor = new import_core6.GitExecutor();
4350
4219
  }
4351
4220
  /**
@@ -4421,7 +4290,7 @@ var WorktreeManager = class _WorktreeManager {
4421
4290
  */
4422
4291
  static async createProject(projectRoot, repoUrl, projectId, workspaceSlug, projectSlug) {
4423
4292
  const manager = new _WorktreeManager(projectRoot);
4424
- const episodaDir = path8.join(projectRoot, ".episoda");
4293
+ const episodaDir = path7.join(projectRoot, ".episoda");
4425
4294
  fs6.mkdirSync(episodaDir, { recursive: true });
4426
4295
  const cloneResult = await manager.gitExecutor.execute({
4427
4296
  action: "clone_bare",
@@ -4453,8 +4322,8 @@ var WorktreeManager = class _WorktreeManager {
4453
4322
  error: `Invalid module UID: "${moduleUid}" - contains disallowed characters`
4454
4323
  };
4455
4324
  }
4456
- const worktreePath = path8.join(this.projectRoot, moduleUid);
4457
- const normalizedWorktreePath = path8.resolve(worktreePath);
4325
+ const worktreePath = path7.join(this.projectRoot, moduleUid);
4326
+ const normalizedWorktreePath = path7.resolve(worktreePath);
4458
4327
  const lockAcquired = await this.acquireLock();
4459
4328
  if (!lockAcquired) {
4460
4329
  return {
@@ -4476,7 +4345,7 @@ var WorktreeManager = class _WorktreeManager {
4476
4345
  action: "worktree_list"
4477
4346
  }, { cwd: this.bareRepoPath });
4478
4347
  const worktrees = listResult.details?.worktrees || [];
4479
- const matching = worktrees.find((w) => path8.resolve(w.path) === normalizedWorktreePath);
4348
+ const matching = worktrees.find((w) => path7.resolve(w.path) === normalizedWorktreePath);
4480
4349
  if (matching) {
4481
4350
  const adoptedWorktree = {
4482
4351
  moduleUid,
@@ -4736,7 +4605,7 @@ var WorktreeManager = class _WorktreeManager {
4736
4605
  try {
4737
4606
  const validation = await this.validateWorktrees();
4738
4607
  for (const orphanPath of validation.orphaned) {
4739
- const dirName = path8.basename(orphanPath);
4608
+ const dirName = path7.basename(orphanPath);
4740
4609
  const isModuleWorktree = /^EP\d+$/.test(dirName);
4741
4610
  if (!isModuleWorktree) {
4742
4611
  console.log(`[WorktreeManager] EP1190: Found non-module worktree: ${dirName}`);
@@ -4872,7 +4741,7 @@ var WorktreeManager = class _WorktreeManager {
4872
4741
  // Turborepo cache
4873
4742
  ];
4874
4743
  for (const cacheDir of cacheDirs) {
4875
- const cachePath = path8.join(worktreePath, cacheDir);
4744
+ const cachePath = path7.join(worktreePath, cacheDir);
4876
4745
  try {
4877
4746
  if (fs6.existsSync(cachePath)) {
4878
4747
  console.log(`[WorktreeManager] EP1070: Cleaning build cache: ${cacheDir}`);
@@ -4902,7 +4771,7 @@ var WorktreeManager = class _WorktreeManager {
4902
4771
  }
4903
4772
  writeConfig(config) {
4904
4773
  try {
4905
- const dir = path8.dirname(this.configPath);
4774
+ const dir = path7.dirname(this.configPath);
4906
4775
  if (!fs6.existsSync(dir)) {
4907
4776
  fs6.mkdirSync(dir, { recursive: true });
4908
4777
  }
@@ -4988,10 +4857,10 @@ var WorktreeManager = class _WorktreeManager {
4988
4857
  }
4989
4858
  try {
4990
4859
  for (const file of files) {
4991
- const srcPath = path8.join(mainWorktree.worktreePath, file);
4992
- const destPath = path8.join(worktree.worktreePath, file);
4860
+ const srcPath = path7.join(mainWorktree.worktreePath, file);
4861
+ const destPath = path7.join(worktree.worktreePath, file);
4993
4862
  if (fs6.existsSync(srcPath)) {
4994
- const destDir = path8.dirname(destPath);
4863
+ const destDir = path7.dirname(destPath);
4995
4864
  if (!fs6.existsSync(destDir)) {
4996
4865
  fs6.mkdirSync(destDir, { recursive: true });
4997
4866
  }
@@ -5084,10 +4953,10 @@ function getEpisodaRoot() {
5084
4953
  if (process.env.EPISODA_MODE === "cloud") {
5085
4954
  return process.env.HOME || "/home/episoda";
5086
4955
  }
5087
- return path8.join(require("os").homedir(), "episoda");
4956
+ return path7.join(require("os").homedir(), "episoda");
5088
4957
  }
5089
4958
  function getProjectPath(workspaceSlug, projectSlug) {
5090
- return path8.join(getEpisodaRoot(), workspaceSlug, projectSlug);
4959
+ return path7.join(getEpisodaRoot(), workspaceSlug, projectSlug);
5091
4960
  }
5092
4961
  async function isWorktreeProject(projectRoot) {
5093
4962
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
@@ -5102,7 +4971,7 @@ async function isWorktreeProject(projectRoot) {
5102
4971
  return result;
5103
4972
  }
5104
4973
  async function findProjectRoot(startPath) {
5105
- let current = path8.resolve(startPath);
4974
+ let current = path7.resolve(startPath);
5106
4975
  const episodaRoot = getEpisodaRoot();
5107
4976
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
5108
4977
  if (debug) {
@@ -5115,8 +4984,8 @@ async function findProjectRoot(startPath) {
5115
4984
  return null;
5116
4985
  }
5117
4986
  for (let i = 0; i < 10; i++) {
5118
- const bareDir = path8.join(current, ".bare");
5119
- const episodaDir = path8.join(current, ".episoda");
4987
+ const bareDir = path7.join(current, ".bare");
4988
+ const episodaDir = path7.join(current, ".episoda");
5120
4989
  if (debug) {
5121
4990
  const bareExists = fs6.existsSync(bareDir);
5122
4991
  const episodaExists = fs6.existsSync(episodaDir);
@@ -5130,7 +4999,7 @@ async function findProjectRoot(startPath) {
5130
4999
  return current;
5131
5000
  }
5132
5001
  }
5133
- const parent = path8.dirname(current);
5002
+ const parent = path7.dirname(current);
5134
5003
  if (parent === current) {
5135
5004
  break;
5136
5005
  }
@@ -5169,15 +5038,15 @@ var import_fs = require("fs");
5169
5038
  var import_child_process5 = require("child_process");
5170
5039
  var http = __toESM(require("http"));
5171
5040
  var fs10 = __toESM(require("fs"));
5172
- var path12 = __toESM(require("path"));
5041
+ var path11 = __toESM(require("path"));
5173
5042
  var import_events = require("events");
5174
5043
  var import_core7 = __toESM(require_dist());
5175
5044
 
5176
5045
  // src/utils/port-check.ts
5177
- var net3 = __toESM(require("net"));
5046
+ var net2 = __toESM(require("net"));
5178
5047
  async function isPortInUse(port) {
5179
5048
  return new Promise((resolve4) => {
5180
- const server = net3.createServer();
5049
+ const server = net2.createServer();
5181
5050
  server.once("error", (err) => {
5182
5051
  if (err.code === "EADDRINUSE") {
5183
5052
  resolve4(true);
@@ -5195,12 +5064,12 @@ async function isPortInUse(port) {
5195
5064
 
5196
5065
  // src/utils/env-cache.ts
5197
5066
  var fs8 = __toESM(require("fs"));
5198
- var path10 = __toESM(require("path"));
5067
+ var path9 = __toESM(require("path"));
5199
5068
  var os = __toESM(require("os"));
5200
5069
 
5201
5070
  // src/utils/env-setup.ts
5202
5071
  var fs7 = __toESM(require("fs"));
5203
- var path9 = __toESM(require("path"));
5072
+ var path8 = __toESM(require("path"));
5204
5073
  async function fetchEnvVars(apiUrl, accessToken) {
5205
5074
  try {
5206
5075
  const url = `${apiUrl}/api/cli/env-vars`;
@@ -5235,16 +5104,16 @@ function writeEnvFile(targetPath, envVars) {
5235
5104
  }
5236
5105
  return `${key}=${value}`;
5237
5106
  }).join("\n") + "\n";
5238
- const envPath = path9.join(targetPath, ".env");
5107
+ const envPath = path8.join(targetPath, ".env");
5239
5108
  fs7.writeFileSync(envPath, envContent, { mode: 384 });
5240
5109
  console.log(`[env-setup] Wrote ${Object.keys(envVars).length} env vars to ${envPath}`);
5241
5110
  }
5242
5111
 
5243
5112
  // src/utils/env-cache.ts
5244
5113
  var DEFAULT_CACHE_TTL = 60;
5245
- var CACHE_DIR = path10.join(os.homedir(), ".episoda", "cache");
5114
+ var CACHE_DIR = path9.join(os.homedir(), ".episoda", "cache");
5246
5115
  function getCacheFilePath(projectId) {
5247
- return path10.join(CACHE_DIR, `env-vars-${projectId}.json`);
5116
+ return path9.join(CACHE_DIR, `env-vars-${projectId}.json`);
5248
5117
  }
5249
5118
  function ensureCacheDir() {
5250
5119
  if (!fs8.existsSync(CACHE_DIR)) {
@@ -5346,10 +5215,10 @@ No cached values available as fallback.`
5346
5215
 
5347
5216
  // src/preview/dev-server-registry.ts
5348
5217
  var fs9 = __toESM(require("fs"));
5349
- var path11 = __toESM(require("path"));
5218
+ var path10 = __toESM(require("path"));
5350
5219
  var os2 = __toESM(require("os"));
5351
5220
  var import_child_process4 = require("child_process");
5352
- var DEV_SERVER_REGISTRY_DIR = path11.join(os2.homedir(), ".episoda", "dev-servers");
5221
+ var DEV_SERVER_REGISTRY_DIR = path10.join(os2.homedir(), ".episoda", "dev-servers");
5353
5222
  var DevServerRegistry = class {
5354
5223
  constructor() {
5355
5224
  this.ensureRegistryDir();
@@ -5372,7 +5241,7 @@ var DevServerRegistry = class {
5372
5241
  * Get the registry file path for a module
5373
5242
  */
5374
5243
  getEntryPath(moduleUid) {
5375
- return path11.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
5244
+ return path10.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
5376
5245
  }
5377
5246
  /**
5378
5247
  * Register a dev server
@@ -5883,7 +5752,7 @@ var DevServerRunner = class extends import_events.EventEmitter {
5883
5752
  cacheTtl: 300
5884
5753
  });
5885
5754
  console.log(`[DevServerRunner] Loaded ${Object.keys(result.envVars).length} env vars`);
5886
- const envFilePath = path12.join(projectPath, ".env");
5755
+ const envFilePath = path11.join(projectPath, ".env");
5887
5756
  if (!fs10.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
5888
5757
  console.log(`[DevServerRunner] Writing .env file`);
5889
5758
  writeEnvFile(projectPath, result.envVars);
@@ -6033,14 +5902,14 @@ var DevServerRunner = class extends import_events.EventEmitter {
6033
5902
  return new Promise((resolve4) => setTimeout(resolve4, ms));
6034
5903
  }
6035
5904
  getLogsDir() {
6036
- const logsDir = path12.join((0, import_core7.getConfigDir)(), "logs");
5905
+ const logsDir = path11.join((0, import_core7.getConfigDir)(), "logs");
6037
5906
  if (!fs10.existsSync(logsDir)) {
6038
5907
  fs10.mkdirSync(logsDir, { recursive: true });
6039
5908
  }
6040
5909
  return logsDir;
6041
5910
  }
6042
5911
  getLogFilePath(moduleUid) {
6043
- return path12.join(this.getLogsDir(), `dev-${moduleUid}.log`);
5912
+ return path11.join(this.getLogsDir(), `dev-${moduleUid}.log`);
6044
5913
  }
6045
5914
  rotateLogIfNeeded(logPath) {
6046
5915
  try {
@@ -6093,13 +5962,13 @@ function getDevServerRunner() {
6093
5962
  var import_child_process7 = require("child_process");
6094
5963
  var import_events2 = require("events");
6095
5964
  var fs12 = __toESM(require("fs"));
6096
- var path14 = __toESM(require("path"));
5965
+ var path13 = __toESM(require("path"));
6097
5966
  var os4 = __toESM(require("os"));
6098
5967
 
6099
5968
  // src/tunnel/cloudflared-manager.ts
6100
5969
  var import_child_process6 = require("child_process");
6101
5970
  var fs11 = __toESM(require("fs"));
6102
- var path13 = __toESM(require("path"));
5971
+ var path12 = __toESM(require("path"));
6103
5972
  var os3 = __toESM(require("os"));
6104
5973
  var https = __toESM(require("https"));
6105
5974
  var tar = __toESM(require("tar"));
@@ -6118,11 +5987,11 @@ var DOWNLOAD_URLS = {
6118
5987
  }
6119
5988
  };
6120
5989
  function getEpisodaBinDir() {
6121
- return path13.join(os3.homedir(), ".episoda", "bin");
5990
+ return path12.join(os3.homedir(), ".episoda", "bin");
6122
5991
  }
6123
5992
  function getCloudflaredPath() {
6124
5993
  const binaryName = os3.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
6125
- return path13.join(getEpisodaBinDir(), binaryName);
5994
+ return path12.join(getEpisodaBinDir(), binaryName);
6126
5995
  }
6127
5996
  function isCloudflaredInPath() {
6128
5997
  try {
@@ -6220,7 +6089,7 @@ async function downloadCloudflared() {
6220
6089
  fs11.mkdirSync(binDir, { recursive: true });
6221
6090
  const isTgz = url.endsWith(".tgz");
6222
6091
  if (isTgz) {
6223
- const tempFile = path13.join(binDir, "cloudflared.tgz");
6092
+ const tempFile = path12.join(binDir, "cloudflared.tgz");
6224
6093
  console.log(`[Tunnel] Downloading cloudflared from ${url}...`);
6225
6094
  await downloadFile(url, tempFile);
6226
6095
  console.log("[Tunnel] Extracting cloudflared...");
@@ -6424,7 +6293,7 @@ function getDaemonModeConfig() {
6424
6293
  }
6425
6294
 
6426
6295
  // src/tunnel/tunnel-manager.ts
6427
- var TUNNEL_PID_DIR = path14.join(os4.homedir(), ".episoda", "tunnels");
6296
+ var TUNNEL_PID_DIR = path13.join(os4.homedir(), ".episoda", "tunnels");
6428
6297
  var TUNNEL_TIMEOUTS = {
6429
6298
  /** Time to wait for Named Tunnel connection (includes API token fetch + connect) */
6430
6299
  NAMED_TUNNEL_CONNECT: 6e4,
@@ -6470,7 +6339,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
6470
6339
  * EP877: Get PID file path for a module
6471
6340
  */
6472
6341
  getPidFilePath(moduleUid) {
6473
- return path14.join(TUNNEL_PID_DIR, `${moduleUid}.pid`);
6342
+ return path13.join(TUNNEL_PID_DIR, `${moduleUid}.pid`);
6474
6343
  }
6475
6344
  /**
6476
6345
  * EP877: Write PID to file for tracking across restarts
@@ -7086,12 +6955,12 @@ function getTunnelManager() {
7086
6955
 
7087
6956
  // src/utils/port-allocator.ts
7088
6957
  var fs13 = __toESM(require("fs"));
7089
- var path15 = __toESM(require("path"));
6958
+ var path14 = __toESM(require("path"));
7090
6959
  var os5 = __toESM(require("os"));
7091
6960
  var PORT_RANGE_START = 3100;
7092
6961
  var PORT_RANGE_END = 3199;
7093
6962
  var PORT_WARNING_THRESHOLD = 80;
7094
- var PORTS_FILE = path15.join(os5.homedir(), ".episoda", "ports.json");
6963
+ var PORTS_FILE = path14.join(os5.homedir(), ".episoda", "ports.json");
7095
6964
  var portAssignments = /* @__PURE__ */ new Map();
7096
6965
  var initialized = false;
7097
6966
  function loadFromDisk() {
@@ -7116,7 +6985,7 @@ function loadFromDisk() {
7116
6985
  }
7117
6986
  function saveToDisk() {
7118
6987
  try {
7119
- const dir = path15.dirname(PORTS_FILE);
6988
+ const dir = path14.dirname(PORTS_FILE);
7120
6989
  if (!fs13.existsSync(dir)) {
7121
6990
  fs13.mkdirSync(dir, { recursive: true });
7122
6991
  }
@@ -7660,16 +7529,16 @@ async function deleteWorktree(config, moduleUid) {
7660
7529
 
7661
7530
  // src/daemon/package-manager.ts
7662
7531
  var fs14 = __toESM(require("fs"));
7663
- var path16 = __toESM(require("path"));
7532
+ var path15 = __toESM(require("path"));
7664
7533
  var PACKAGE_MANAGERS = {
7665
7534
  javascript: [
7666
7535
  {
7667
7536
  name: "pnpm",
7668
7537
  detector: (p) => {
7669
- if (fs14.existsSync(path16.join(p, "pnpm-lock.yaml"))) {
7538
+ if (fs14.existsSync(path15.join(p, "pnpm-lock.yaml"))) {
7670
7539
  return "pnpm-lock.yaml";
7671
7540
  }
7672
- const pkgJsonPath = path16.join(p, "package.json");
7541
+ const pkgJsonPath = path15.join(p, "package.json");
7673
7542
  if (fs14.existsSync(pkgJsonPath)) {
7674
7543
  try {
7675
7544
  const pkg = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
@@ -7688,7 +7557,7 @@ var PACKAGE_MANAGERS = {
7688
7557
  },
7689
7558
  {
7690
7559
  name: "yarn",
7691
- detector: (p) => fs14.existsSync(path16.join(p, "yarn.lock")) ? "yarn.lock" : null,
7560
+ detector: (p) => fs14.existsSync(path15.join(p, "yarn.lock")) ? "yarn.lock" : null,
7692
7561
  config: {
7693
7562
  installCmd: "yarn install --frozen-lockfile",
7694
7563
  cacheEnvVar: "YARN_CACHE_FOLDER"
@@ -7696,14 +7565,14 @@ var PACKAGE_MANAGERS = {
7696
7565
  },
7697
7566
  {
7698
7567
  name: "bun",
7699
- detector: (p) => fs14.existsSync(path16.join(p, "bun.lockb")) ? "bun.lockb" : null,
7568
+ detector: (p) => fs14.existsSync(path15.join(p, "bun.lockb")) ? "bun.lockb" : null,
7700
7569
  config: {
7701
7570
  installCmd: "bun install --frozen-lockfile"
7702
7571
  }
7703
7572
  },
7704
7573
  {
7705
7574
  name: "npm",
7706
- detector: (p) => fs14.existsSync(path16.join(p, "package-lock.json")) ? "package-lock.json" : null,
7575
+ detector: (p) => fs14.existsSync(path15.join(p, "package-lock.json")) ? "package-lock.json" : null,
7707
7576
  config: {
7708
7577
  installCmd: "npm ci"
7709
7578
  }
@@ -7712,7 +7581,7 @@ var PACKAGE_MANAGERS = {
7712
7581
  // EP1222: Default to pnpm for new projects without lockfile
7713
7582
  // This encourages standardization on pnpm across Episoda projects
7714
7583
  name: "pnpm",
7715
- detector: (p) => fs14.existsSync(path16.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
7584
+ detector: (p) => fs14.existsSync(path15.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
7716
7585
  config: {
7717
7586
  installCmd: "pnpm install",
7718
7587
  cacheEnvVar: "PNPM_HOME"
@@ -7723,8 +7592,8 @@ var PACKAGE_MANAGERS = {
7723
7592
  {
7724
7593
  name: "uv",
7725
7594
  detector: (p) => {
7726
- const pyprojectPath = path16.join(p, "pyproject.toml");
7727
- if (fs14.existsSync(path16.join(p, "uv.lock"))) {
7595
+ const pyprojectPath = path15.join(p, "pyproject.toml");
7596
+ if (fs14.existsSync(path15.join(p, "uv.lock"))) {
7728
7597
  return "uv.lock";
7729
7598
  }
7730
7599
  if (fs14.existsSync(pyprojectPath)) {
@@ -7745,7 +7614,7 @@ var PACKAGE_MANAGERS = {
7745
7614
  },
7746
7615
  {
7747
7616
  name: "pip",
7748
- detector: (p) => fs14.existsSync(path16.join(p, "requirements.txt")) ? "requirements.txt" : null,
7617
+ detector: (p) => fs14.existsSync(path15.join(p, "requirements.txt")) ? "requirements.txt" : null,
7749
7618
  config: {
7750
7619
  installCmd: "pip install -r requirements.txt"
7751
7620
  }
@@ -7819,7 +7688,7 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
7819
7688
  return;
7820
7689
  }
7821
7690
  const homeDir = process.env.HOME || os6.homedir();
7822
- const workspaceConfigPath = path17.join(
7691
+ const workspaceConfigPath = path16.join(
7823
7692
  homeDir,
7824
7693
  "episoda",
7825
7694
  workspaceSlug,
@@ -7899,19 +7768,19 @@ async function handleWorktreeCreate(request2) {
7899
7768
  }
7900
7769
  try {
7901
7770
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
7902
- const bareRepoPath = path17.join(projectPath, ".bare");
7771
+ const bareRepoPath = path16.join(projectPath, ".bare");
7903
7772
  const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
7904
7773
  if (!fs15.existsSync(bareRepoPath)) {
7905
7774
  console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
7906
7775
  console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
7907
- const episodaDir = path17.join(projectPath, ".episoda");
7776
+ const episodaDir = path16.join(projectPath, ".episoda");
7908
7777
  fs15.mkdirSync(episodaDir, { recursive: true });
7909
7778
  try {
7910
7779
  console.log(`[Worktree] K1273: Starting git clone...`);
7911
7780
  await execAsync(`git clone --bare "${repoUrl}" "${bareRepoPath}"`, { env: gitEnv });
7912
7781
  console.log(`[Worktree] K1273: Clone successful`);
7913
7782
  await execAsync(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
7914
- const configPath = path17.join(episodaDir, "config.json");
7783
+ const configPath = path16.join(episodaDir, "config.json");
7915
7784
  const config2 = {
7916
7785
  projectId,
7917
7786
  workspaceSlug,
@@ -7968,7 +7837,7 @@ async function handleWorktreeCreate(request2) {
7968
7837
  }
7969
7838
  if (envVars && Object.keys(envVars).length > 0) {
7970
7839
  const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
7971
- const envPath = path17.join(worktreePath, ".env");
7840
+ const envPath = path16.join(worktreePath, ".env");
7972
7841
  fs15.writeFileSync(envPath, envContent + "\n", "utf-8");
7973
7842
  console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
7974
7843
  }
@@ -8155,7 +8024,7 @@ async function handleProjectEject(request2) {
8155
8024
  console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
8156
8025
  try {
8157
8026
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
8158
- const bareRepoPath = path17.join(projectPath, ".bare");
8027
+ const bareRepoPath = path16.join(projectPath, ".bare");
8159
8028
  if (!fs15.existsSync(projectPath)) {
8160
8029
  console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
8161
8030
  return { success: true };
@@ -8175,7 +8044,7 @@ async function handleProjectEject(request2) {
8175
8044
  };
8176
8045
  }
8177
8046
  }
8178
- const artifactsPath = path17.join(projectPath, "artifacts");
8047
+ const artifactsPath = path16.join(projectPath, "artifacts");
8179
8048
  if (fs15.existsSync(artifactsPath)) {
8180
8049
  await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
8181
8050
  }
@@ -8208,7 +8077,7 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
8208
8077
  }
8209
8078
  const artifacts = [];
8210
8079
  for (const fileName of files) {
8211
- const filePath = path17.join(artifactsPath, fileName);
8080
+ const filePath = path16.join(artifactsPath, fileName);
8212
8081
  const stat = await fs15.promises.stat(filePath);
8213
8082
  if (stat.isDirectory()) {
8214
8083
  continue;
@@ -8220,7 +8089,7 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
8220
8089
  try {
8221
8090
  const content = await fs15.promises.readFile(filePath);
8222
8091
  const base64Content = content.toString("base64");
8223
- const ext = path17.extname(fileName).toLowerCase();
8092
+ const ext = path16.extname(fileName).toLowerCase();
8224
8093
  const mimeTypes = {
8225
8094
  ".json": "application/json",
8226
8095
  ".txt": "text/plain",
@@ -8276,7 +8145,7 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
8276
8145
  }
8277
8146
 
8278
8147
  // src/daemon/handlers/project-handlers.ts
8279
- var path18 = __toESM(require("path"));
8148
+ var path17 = __toESM(require("path"));
8280
8149
  var fs16 = __toESM(require("fs"));
8281
8150
  function validateSlug(slug, fieldName) {
8282
8151
  if (!slug || typeof slug !== "string") {
@@ -8317,9 +8186,9 @@ async function handleProjectSetup(params) {
8317
8186
  console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
8318
8187
  try {
8319
8188
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
8320
- const artifactsPath = path18.join(projectPath, "artifacts");
8321
- const configDir = path18.join(projectPath, ".episoda");
8322
- const configPath = path18.join(configDir, "config.json");
8189
+ const artifactsPath = path17.join(projectPath, "artifacts");
8190
+ const configDir = path17.join(projectPath, ".episoda");
8191
+ const configPath = path17.join(configDir, "config.json");
8323
8192
  await fs16.promises.mkdir(artifactsPath, { recursive: true });
8324
8193
  await fs16.promises.mkdir(configDir, { recursive: true });
8325
8194
  let existingConfig = {};
@@ -8451,7 +8320,7 @@ async function cleanupStaleCommits(projectPath) {
8451
8320
 
8452
8321
  // src/agent/claude-binary.ts
8453
8322
  var import_child_process10 = require("child_process");
8454
- var path19 = __toESM(require("path"));
8323
+ var path18 = __toESM(require("path"));
8455
8324
  var fs17 = __toESM(require("fs"));
8456
8325
  var cachedBinaryPath = null;
8457
8326
  function isValidClaudeBinary(binaryPath) {
@@ -8489,11 +8358,11 @@ async function ensureClaudeBinary() {
8489
8358
  }
8490
8359
  const bundledPaths = [
8491
8360
  // In production: node_modules/.bin/claude
8492
- path19.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
8361
+ path18.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
8493
8362
  // In monorepo development: packages/episoda/node_modules/.bin/claude
8494
- path19.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
8363
+ path18.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
8495
8364
  // Root monorepo node_modules
8496
- path19.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
8365
+ path18.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
8497
8366
  ];
8498
8367
  for (const bundledPath of bundledPaths) {
8499
8368
  if (fs17.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
@@ -8522,7 +8391,7 @@ async function ensureClaudeBinary() {
8522
8391
 
8523
8392
  // src/agent/codex-binary.ts
8524
8393
  var import_child_process11 = require("child_process");
8525
- var path20 = __toESM(require("path"));
8394
+ var path19 = __toESM(require("path"));
8526
8395
  var fs18 = __toESM(require("fs"));
8527
8396
  var cachedBinaryPath2 = null;
8528
8397
  function isValidCodexBinary(binaryPath) {
@@ -8560,11 +8429,11 @@ async function ensureCodexBinary() {
8560
8429
  }
8561
8430
  const bundledPaths = [
8562
8431
  // In production: node_modules/.bin/codex
8563
- path20.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
8432
+ path19.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
8564
8433
  // In monorepo development: packages/episoda/node_modules/.bin/codex
8565
- path20.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
8434
+ path19.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
8566
8435
  // Root monorepo node_modules
8567
- path20.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
8436
+ path19.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
8568
8437
  ];
8569
8438
  for (const bundledPath of bundledPaths) {
8570
8439
  if (fs18.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
@@ -8663,7 +8532,7 @@ function generateCodexMcpConfigToml(servers, projectPath) {
8663
8532
 
8664
8533
  // src/agent/agent-manager.ts
8665
8534
  var import_child_process12 = require("child_process");
8666
- var path21 = __toESM(require("path"));
8535
+ var path20 = __toESM(require("path"));
8667
8536
  var fs19 = __toESM(require("fs"));
8668
8537
  var os7 = __toESM(require("os"));
8669
8538
 
@@ -8983,7 +8852,7 @@ var AgentManager = class {
8983
8852
  this.initialized = false;
8984
8853
  // EP1133: Lock for config file writes to prevent race conditions
8985
8854
  this.configWriteLock = Promise.resolve();
8986
- this.pidDir = path21.join(os7.homedir(), ".episoda", "agent-pids");
8855
+ this.pidDir = path20.join(os7.homedir(), ".episoda", "agent-pids");
8987
8856
  }
8988
8857
  /**
8989
8858
  * EP1133: Acquire lock for config file writes
@@ -9040,12 +8909,12 @@ var AgentManager = class {
9040
8909
  }
9041
8910
  loadProviderTokens(provider, sessionDir, legacyDir) {
9042
8911
  if (provider === "codex") {
9043
- const sessionAuthPath = path21.join(sessionDir, "auth.json");
9044
- const legacyAuthPath = path21.join(legacyDir, "auth.json");
8912
+ const sessionAuthPath = path20.join(sessionDir, "auth.json");
8913
+ const legacyAuthPath = path20.join(legacyDir, "auth.json");
9045
8914
  return this.readCodexTokensFromPath(sessionAuthPath) || this.readCodexTokensFromPath(legacyAuthPath);
9046
8915
  }
9047
- const sessionCredentialsPath = path21.join(sessionDir, ".credentials.json");
9048
- const legacyCredentialsPath = path21.join(legacyDir, ".credentials.json");
8916
+ const sessionCredentialsPath = path20.join(sessionDir, ".credentials.json");
8917
+ const legacyCredentialsPath = path20.join(legacyDir, ".credentials.json");
9049
8918
  return this.readClaudeTokensFromPath(sessionCredentialsPath) || this.readClaudeTokensFromPath(legacyCredentialsPath);
9050
8919
  }
9051
8920
  applyTokensToSession(session, tokens) {
@@ -9123,10 +8992,10 @@ var AgentManager = class {
9123
8992
  findCodexRequirementsToml(sessionProjectPath) {
9124
8993
  const candidates = [];
9125
8994
  if (sessionProjectPath) {
9126
- candidates.push(path21.join(sessionProjectPath, ".codex", "requirements.toml"));
8995
+ candidates.push(path20.join(sessionProjectPath, ".codex", "requirements.toml"));
9127
8996
  }
9128
- const codexHome = process.env.CODEX_HOME || path21.join(os7.homedir(), ".codex");
9129
- candidates.push(path21.join(codexHome, "requirements.toml"));
8997
+ const codexHome = process.env.CODEX_HOME || path20.join(os7.homedir(), ".codex");
8998
+ candidates.push(path20.join(codexHome, "requirements.toml"));
9130
8999
  candidates.push("/etc/codex/requirements.toml");
9131
9000
  for (const p of candidates) {
9132
9001
  if (fs19.existsSync(p)) return p;
@@ -9600,10 +9469,10 @@ If changes are needed, explain what needs to be done.`;
9600
9469
  }
9601
9470
  const useOAuth = !!session.credentials.oauthToken;
9602
9471
  const useApiKey = !useOAuth && !!session.credentials.apiKey;
9603
- const sessionCodexDir = path21.join(os7.homedir(), ".codex", "sessions", sessionId);
9604
- const sessionClaudeDir = path21.join(os7.homedir(), ".claude", "sessions", sessionId);
9605
- const legacyCodexDir = path21.join(os7.homedir(), ".codex");
9606
- const legacyClaudeDir = path21.join(os7.homedir(), ".claude");
9472
+ const sessionCodexDir = path20.join(os7.homedir(), ".codex", "sessions", sessionId);
9473
+ const sessionClaudeDir = path20.join(os7.homedir(), ".claude", "sessions", sessionId);
9474
+ const legacyCodexDir = path20.join(os7.homedir(), ".codex");
9475
+ const legacyClaudeDir = path20.join(os7.homedir(), ".claude");
9607
9476
  const sessionConfigDir = provider === "codex" ? sessionCodexDir : sessionClaudeDir;
9608
9477
  const legacyConfigDir = provider === "codex" ? legacyCodexDir : legacyClaudeDir;
9609
9478
  if (useOAuth) {
@@ -9627,7 +9496,7 @@ If changes are needed, explain what needs to be done.`;
9627
9496
  accountId: session.credentials.accountId,
9628
9497
  expiresAt: session.credentials.expiresAt
9629
9498
  });
9630
- const authJsonPath = path21.join(sessionCodexDir, "auth.json");
9499
+ const authJsonPath = path20.join(sessionCodexDir, "auth.json");
9631
9500
  fs19.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
9632
9501
  console.log(`[AgentManager] EP1260: Wrote Codex auth.json to ${authJsonPath}`);
9633
9502
  } else if (useApiKey) {
@@ -9719,13 +9588,13 @@ If changes are needed, explain what needs to be done.`;
9719
9588
  };
9720
9589
  });
9721
9590
  const configTomlContent = generateCodexMcpConfigToml(codexMcpServers, session.projectPath);
9722
- const configTomlPath = path21.join(sessionCodexDir, "config.toml");
9591
+ const configTomlPath = path20.join(sessionCodexDir, "config.toml");
9723
9592
  fs19.writeFileSync(configTomlPath, configTomlContent, { mode: 384 });
9724
9593
  console.log(`[AgentManager] EP1287: Wrote Codex config.toml at ${configTomlPath} with ${codexMcpServers.length} MCP server(s): ${codexMcpServers.map((s) => s.name).join(", ")}`);
9725
9594
  } else {
9726
9595
  const configTomlContent = generateCodexMcpConfigToml([], session.projectPath);
9727
9596
  if (configTomlContent.trim()) {
9728
- const configTomlPath = path21.join(sessionCodexDir, "config.toml");
9597
+ const configTomlPath = path20.join(sessionCodexDir, "config.toml");
9729
9598
  fs19.writeFileSync(configTomlPath, configTomlContent, { mode: 420 });
9730
9599
  console.log(`[AgentManager] EP1287: Wrote Codex config.toml (project trust only) at ${configTomlPath}`);
9731
9600
  }
@@ -9733,11 +9602,11 @@ If changes are needed, explain what needs to be done.`;
9733
9602
  });
9734
9603
  } else {
9735
9604
  await this.withConfigLock(async () => {
9736
- const statsigDir = path21.join(sessionClaudeDir, "statsig");
9605
+ const statsigDir = path20.join(sessionClaudeDir, "statsig");
9737
9606
  fs19.mkdirSync(statsigDir, { recursive: true });
9738
9607
  console.log(`[AgentManager] EP1260: Created session-specific Claude dir: ${sessionClaudeDir}`);
9739
9608
  if (useOAuth) {
9740
- const credentialsPath = path21.join(sessionClaudeDir, ".credentials.json");
9609
+ const credentialsPath = path20.join(sessionClaudeDir, ".credentials.json");
9741
9610
  const oauthCredentials = {
9742
9611
  accessToken: session.credentials.oauthToken
9743
9612
  };
@@ -9765,10 +9634,10 @@ If changes are needed, explain what needs to be done.`;
9765
9634
  if (!hasEvaluations || !hasStableId) {
9766
9635
  throw new Error(`Invalid statsig config: missing required files`);
9767
9636
  }
9768
- const settingsPath = path21.join(sessionClaudeDir, "settings.json");
9637
+ const settingsPath = path20.join(sessionClaudeDir, "settings.json");
9769
9638
  fs19.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
9770
9639
  for (const [filename, content] of Object.entries(claudeConfig.statsig)) {
9771
- const filePath = path21.join(statsigDir, filename);
9640
+ const filePath = path20.join(statsigDir, filename);
9772
9641
  fs19.writeFileSync(filePath, content, { mode: 420 });
9773
9642
  }
9774
9643
  console.log("[AgentManager] EP1260: Wrote Claude config files to session directory");
@@ -9842,6 +9711,10 @@ If changes are needed, explain what needs to be done.`;
9842
9711
  });
9843
9712
  let stdoutBuffer = "";
9844
9713
  let extractedSessionId;
9714
+ let resultModel;
9715
+ let resultCostUsd;
9716
+ let resultUsage;
9717
+ let resultNumTurns;
9845
9718
  let stdoutEventCount = 0;
9846
9719
  let chunksSent = 0;
9847
9720
  const streamStartTime = Date.now();
@@ -9876,6 +9749,18 @@ If changes are needed, explain what needs to be done.`;
9876
9749
  } else if (parsed.item?.type === "reasoning" && parsed.item.summary) {
9877
9750
  onChunk(`[Thinking: ${parsed.item.summary}]
9878
9751
  `);
9752
+ } else if (parsed.item?.type === "contextCompaction" && options.onCompaction) {
9753
+ const compactionEvent = {
9754
+ seq: stdoutEventCount,
9755
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9756
+ provider: "codex",
9757
+ type: "session.compaction",
9758
+ raw: parsed,
9759
+ trigger: "auto",
9760
+ summary: parsed.item.summary
9761
+ };
9762
+ console.log(`[AgentManager] EP1321: Codex compaction completed`);
9763
+ options.onCompaction(compactionEvent);
9879
9764
  }
9880
9765
  break;
9881
9766
  case "item.failed":
@@ -9886,6 +9771,9 @@ If changes are needed, explain what needs to be done.`;
9886
9771
  onError(parsed.message || parsed.error?.message || "Unknown error from Codex");
9887
9772
  break;
9888
9773
  case "item.started":
9774
+ if (parsed.item?.type === "contextCompaction") {
9775
+ console.log(`[AgentManager] EP1321: Codex compaction started`);
9776
+ }
9889
9777
  if (parsed.item?.type === "command_execution" && options.onToolUse) {
9890
9778
  const toolEvent = {
9891
9779
  id: parsed.item.id || `codex-cmd-${Date.now()}`,
@@ -9897,9 +9785,23 @@ If changes are needed, explain what needs to be done.`;
9897
9785
  options.onToolUse(toolEvent);
9898
9786
  }
9899
9787
  break;
9900
- // Ignore: turn.started, turn.completed (usage stats)
9788
+ case "turn.completed":
9789
+ if (parsed.usage) {
9790
+ const u = parsed.usage;
9791
+ const inputTokens = u.input_tokens ?? u.prompt_tokens ?? 0;
9792
+ const outputTokens = u.output_tokens ?? u.completion_tokens ?? 0;
9793
+ resultUsage = {
9794
+ inputTokens,
9795
+ outputTokens,
9796
+ totalTokens: inputTokens + outputTokens
9797
+ };
9798
+ }
9799
+ resultModel = parsed.model;
9800
+ resultNumTurns = typeof parsed.num_turns === "number" ? parsed.num_turns : void 0;
9801
+ break;
9802
+ // Ignore: turn.started
9901
9803
  default:
9902
- if (!["turn.started", "turn.completed"].includes(parsed.type)) {
9804
+ if (parsed.type !== "turn.started") {
9903
9805
  console.log(`[AgentManager] Codex event: ${parsed.type}`);
9904
9806
  }
9905
9807
  }
@@ -9969,6 +9871,25 @@ If changes are needed, explain what needs to be done.`;
9969
9871
  session.agentSessionId = extractedSessionId;
9970
9872
  session.claudeSessionId = extractedSessionId;
9971
9873
  }
9874
+ resultModel = parsed.model;
9875
+ if (typeof parsed.total_cost_usd === "number") {
9876
+ resultCostUsd = parsed.total_cost_usd;
9877
+ } else if (typeof parsed.cost_usd === "number") {
9878
+ resultCostUsd = parsed.cost_usd;
9879
+ }
9880
+ resultNumTurns = typeof parsed.num_turns === "number" ? parsed.num_turns : void 0;
9881
+ if (parsed.usage) {
9882
+ const u = parsed.usage;
9883
+ const inputTokens = u.input_tokens ?? u.prompt_tokens ?? 0;
9884
+ const outputTokens = u.output_tokens ?? u.completion_tokens ?? 0;
9885
+ resultUsage = {
9886
+ inputTokens,
9887
+ outputTokens,
9888
+ cacheReadTokens: u.cache_read_input_tokens ?? u.cache_read_tokens,
9889
+ cacheWriteTokens: u.cache_creation_input_tokens ?? u.cache_write_tokens,
9890
+ totalTokens: inputTokens + outputTokens
9891
+ };
9892
+ }
9972
9893
  break;
9973
9894
  case "system":
9974
9895
  if (parsed.session_id) {
@@ -9976,6 +9897,20 @@ If changes are needed, explain what needs to be done.`;
9976
9897
  session.agentSessionId = extractedSessionId;
9977
9898
  session.claudeSessionId = extractedSessionId;
9978
9899
  }
9900
+ if (parsed.subtype === "compact_boundary" && options.onCompaction) {
9901
+ const metadata = parsed.compactMetadata;
9902
+ const compactionEvent = {
9903
+ seq: stdoutEventCount,
9904
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9905
+ provider: "claude",
9906
+ type: "session.compaction",
9907
+ raw: parsed,
9908
+ trigger: metadata?.trigger ?? "auto",
9909
+ preCompactionTokens: metadata?.preCompactTokenCount
9910
+ };
9911
+ console.log(`[AgentManager] EP1321: Claude compaction detected - trigger: ${compactionEvent.trigger}, tokens: ${compactionEvent.preCompactionTokens ?? "unknown"}`);
9912
+ options.onCompaction(compactionEvent);
9913
+ }
9979
9914
  break;
9980
9915
  case "error":
9981
9916
  onError(parsed.error?.message || parsed.message || "Unknown error from Claude Code");
@@ -10011,12 +9946,13 @@ If changes are needed, explain what needs to be done.`;
10011
9946
  }
10012
9947
  })();
10013
9948
  }
9949
+ const resultMeta = resultModel || resultCostUsd !== void 0 || resultUsage ? { model: resultModel, costUsd: resultCostUsd, durationMs: duration, numTurns: resultNumTurns, usage: resultUsage } : void 0;
10014
9950
  if (code === 0) {
10015
9951
  session.status = "stopped";
10016
- onComplete(extractedSessionId || session.agentSessionId || session.claudeSessionId);
9952
+ onComplete(extractedSessionId || session.agentSessionId || session.claudeSessionId, resultMeta);
10017
9953
  } else if (signal === "SIGINT") {
10018
9954
  session.status = "stopped";
10019
- onComplete(extractedSessionId || session.agentSessionId || session.claudeSessionId);
9955
+ onComplete(extractedSessionId || session.agentSessionId || session.claudeSessionId, resultMeta);
10020
9956
  } else {
10021
9957
  session.status = "error";
10022
9958
  const errorMsg = stderrBuffer.trim() || `Process exited with code ${code}`;
@@ -10096,7 +10032,7 @@ If changes are needed, explain what needs to be done.`;
10096
10032
  cleanupSessionCredentials(sessionId, provider) {
10097
10033
  const providers = provider ? [provider] : ["claude", "codex"];
10098
10034
  for (const p of providers) {
10099
- const sessionDir = p === "codex" ? path21.join(os7.homedir(), ".codex", "sessions", sessionId) : path21.join(os7.homedir(), ".claude", "sessions", sessionId);
10035
+ const sessionDir = p === "codex" ? path20.join(os7.homedir(), ".codex", "sessions", sessionId) : path20.join(os7.homedir(), ".claude", "sessions", sessionId);
10100
10036
  try {
10101
10037
  if (fs19.existsSync(sessionDir)) {
10102
10038
  fs19.rmSync(sessionDir, { recursive: true, force: true });
@@ -10187,7 +10123,7 @@ If changes are needed, explain what needs to be done.`;
10187
10123
  }
10188
10124
  const pidFiles = fs19.readdirSync(this.pidDir).filter((f) => f.endsWith(".pid"));
10189
10125
  for (const pidFile of pidFiles) {
10190
- const pidPath = path21.join(this.pidDir, pidFile);
10126
+ const pidPath = path20.join(this.pidDir, pidFile);
10191
10127
  try {
10192
10128
  const pidStr = fs19.readFileSync(pidPath, "utf-8").trim();
10193
10129
  const pid = parseInt(pidStr, 10);
@@ -10214,14 +10150,14 @@ If changes are needed, explain what needs to be done.`;
10214
10150
  * Write PID file for session tracking
10215
10151
  */
10216
10152
  writePidFile(sessionId, pid) {
10217
- const pidPath = path21.join(this.pidDir, `${sessionId}.pid`);
10153
+ const pidPath = path20.join(this.pidDir, `${sessionId}.pid`);
10218
10154
  fs19.writeFileSync(pidPath, pid.toString());
10219
10155
  }
10220
10156
  /**
10221
10157
  * Remove PID file for session
10222
10158
  */
10223
10159
  removePidFile(sessionId) {
10224
- const pidPath = path21.join(this.pidDir, `${sessionId}.pid`);
10160
+ const pidPath = path20.join(this.pidDir, `${sessionId}.pid`);
10225
10161
  try {
10226
10162
  if (fs19.existsSync(pidPath)) {
10227
10163
  fs19.unlinkSync(pidPath);
@@ -10437,7 +10373,7 @@ var AgentCommandQueue = class {
10437
10373
  var import_child_process13 = require("child_process");
10438
10374
  var import_core12 = __toESM(require_dist());
10439
10375
  var fs20 = __toESM(require("fs"));
10440
- var path22 = __toESM(require("path"));
10376
+ var path21 = __toESM(require("path"));
10441
10377
  var MAX_RESTART_ATTEMPTS = 5;
10442
10378
  var INITIAL_RESTART_DELAY_MS = 2e3;
10443
10379
  var MAX_RESTART_DELAY_MS = 3e4;
@@ -10445,14 +10381,14 @@ var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
10445
10381
  var NODE_MEMORY_LIMIT_MB = 2048;
10446
10382
  var activeServers = /* @__PURE__ */ new Map();
10447
10383
  function getLogsDir() {
10448
- const logsDir = path22.join((0, import_core12.getConfigDir)(), "logs");
10384
+ const logsDir = path21.join((0, import_core12.getConfigDir)(), "logs");
10449
10385
  if (!fs20.existsSync(logsDir)) {
10450
10386
  fs20.mkdirSync(logsDir, { recursive: true });
10451
10387
  }
10452
10388
  return logsDir;
10453
10389
  }
10454
10390
  function getLogFilePath(moduleUid) {
10455
- return path22.join(getLogsDir(), `dev-${moduleUid}.log`);
10391
+ return path21.join(getLogsDir(), `dev-${moduleUid}.log`);
10456
10392
  }
10457
10393
  function rotateLogIfNeeded(logPath) {
10458
10394
  try {
@@ -10464,7 +10400,7 @@ function rotateLogIfNeeded(logPath) {
10464
10400
  fs20.unlinkSync(backupPath);
10465
10401
  }
10466
10402
  fs20.renameSync(logPath, backupPath);
10467
- console.log(`[DevServer] EP932: Rotated log file for ${path22.basename(logPath)}`);
10403
+ console.log(`[DevServer] EP932: Rotated log file for ${path21.basename(logPath)}`);
10468
10404
  }
10469
10405
  }
10470
10406
  } catch (error) {
@@ -10656,7 +10592,7 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
10656
10592
  });
10657
10593
  injectedEnvVars = result.envVars;
10658
10594
  console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
10659
- const envFilePath = path22.join(projectPath, ".env");
10595
+ const envFilePath = path21.join(projectPath, ".env");
10660
10596
  if (!fs20.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
10661
10597
  console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
10662
10598
  writeEnvFile(projectPath, injectedEnvVars);
@@ -10763,7 +10699,7 @@ function getDevServerStatus() {
10763
10699
  }
10764
10700
 
10765
10701
  // src/utils/worktree.ts
10766
- var path23 = __toESM(require("path"));
10702
+ var path22 = __toESM(require("path"));
10767
10703
  var fs21 = __toESM(require("fs"));
10768
10704
  var os8 = __toESM(require("os"));
10769
10705
  var import_core13 = __toESM(require_dist());
@@ -10774,11 +10710,11 @@ function getEpisodaRoot2() {
10774
10710
  if (process.env.EPISODA_MODE === "cloud") {
10775
10711
  return process.env.HOME || "/home/episoda";
10776
10712
  }
10777
- return path23.join(os8.homedir(), "episoda");
10713
+ return path22.join(os8.homedir(), "episoda");
10778
10714
  }
10779
10715
  function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
10780
10716
  const root = getEpisodaRoot2();
10781
- const worktreePath = path23.join(root, workspaceSlug, projectSlug, moduleUid);
10717
+ const worktreePath = path22.join(root, workspaceSlug, projectSlug, moduleUid);
10782
10718
  return {
10783
10719
  path: worktreePath,
10784
10720
  exists: fs21.existsSync(worktreePath),
@@ -10795,14 +10731,14 @@ async function getWorktreeInfoForModule(moduleUid) {
10795
10731
  return null;
10796
10732
  }
10797
10733
  const root = getEpisodaRoot2();
10798
- const workspaceRoot = path23.join(root, config.workspace_slug);
10734
+ const workspaceRoot = path22.join(root, config.workspace_slug);
10799
10735
  try {
10800
10736
  const entries = fs21.readdirSync(workspaceRoot, { withFileTypes: true });
10801
10737
  for (const entry of entries) {
10802
10738
  if (!entry.isDirectory()) {
10803
10739
  continue;
10804
10740
  }
10805
- const worktreePath = path23.join(workspaceRoot, entry.name, moduleUid);
10741
+ const worktreePath = path22.join(workspaceRoot, entry.name, moduleUid);
10806
10742
  if (fs21.existsSync(worktreePath)) {
10807
10743
  return {
10808
10744
  path: worktreePath,
@@ -10821,59 +10757,59 @@ async function getWorktreeInfoForModule(moduleUid) {
10821
10757
 
10822
10758
  // src/framework-detector.ts
10823
10759
  var fs22 = __toESM(require("fs"));
10824
- var path24 = __toESM(require("path"));
10760
+ var path23 = __toESM(require("path"));
10825
10761
  function getInstallCommand2(cwd) {
10826
- if (fs22.existsSync(path24.join(cwd, "bun.lockb"))) {
10762
+ if (fs22.existsSync(path23.join(cwd, "bun.lockb"))) {
10827
10763
  return {
10828
10764
  command: ["bun", "install"],
10829
10765
  description: "Installing dependencies with bun",
10830
10766
  detectedFrom: "bun.lockb"
10831
10767
  };
10832
10768
  }
10833
- if (fs22.existsSync(path24.join(cwd, "pnpm-lock.yaml"))) {
10769
+ if (fs22.existsSync(path23.join(cwd, "pnpm-lock.yaml"))) {
10834
10770
  return {
10835
10771
  command: ["pnpm", "install"],
10836
10772
  description: "Installing dependencies with pnpm",
10837
10773
  detectedFrom: "pnpm-lock.yaml"
10838
10774
  };
10839
10775
  }
10840
- if (fs22.existsSync(path24.join(cwd, "yarn.lock"))) {
10776
+ if (fs22.existsSync(path23.join(cwd, "yarn.lock"))) {
10841
10777
  return {
10842
10778
  command: ["yarn", "install"],
10843
10779
  description: "Installing dependencies with yarn",
10844
10780
  detectedFrom: "yarn.lock"
10845
10781
  };
10846
10782
  }
10847
- if (fs22.existsSync(path24.join(cwd, "package-lock.json"))) {
10783
+ if (fs22.existsSync(path23.join(cwd, "package-lock.json"))) {
10848
10784
  return {
10849
10785
  command: ["npm", "ci"],
10850
10786
  description: "Installing dependencies with npm ci",
10851
10787
  detectedFrom: "package-lock.json"
10852
10788
  };
10853
10789
  }
10854
- if (fs22.existsSync(path24.join(cwd, "package.json"))) {
10790
+ if (fs22.existsSync(path23.join(cwd, "package.json"))) {
10855
10791
  return {
10856
10792
  command: ["npm", "install"],
10857
10793
  description: "Installing dependencies with npm",
10858
10794
  detectedFrom: "package.json"
10859
10795
  };
10860
10796
  }
10861
- if (fs22.existsSync(path24.join(cwd, "Pipfile.lock")) || fs22.existsSync(path24.join(cwd, "Pipfile"))) {
10797
+ if (fs22.existsSync(path23.join(cwd, "Pipfile.lock")) || fs22.existsSync(path23.join(cwd, "Pipfile"))) {
10862
10798
  return {
10863
10799
  command: ["pipenv", "install"],
10864
10800
  description: "Installing dependencies with pipenv",
10865
- detectedFrom: fs22.existsSync(path24.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
10801
+ detectedFrom: fs22.existsSync(path23.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
10866
10802
  };
10867
10803
  }
10868
- if (fs22.existsSync(path24.join(cwd, "poetry.lock"))) {
10804
+ if (fs22.existsSync(path23.join(cwd, "poetry.lock"))) {
10869
10805
  return {
10870
10806
  command: ["poetry", "install"],
10871
10807
  description: "Installing dependencies with poetry",
10872
10808
  detectedFrom: "poetry.lock"
10873
10809
  };
10874
10810
  }
10875
- if (fs22.existsSync(path24.join(cwd, "pyproject.toml"))) {
10876
- const pyprojectPath = path24.join(cwd, "pyproject.toml");
10811
+ if (fs22.existsSync(path23.join(cwd, "pyproject.toml"))) {
10812
+ const pyprojectPath = path23.join(cwd, "pyproject.toml");
10877
10813
  const content = fs22.readFileSync(pyprojectPath, "utf-8");
10878
10814
  if (content.includes("[tool.poetry]")) {
10879
10815
  return {
@@ -10883,42 +10819,43 @@ function getInstallCommand2(cwd) {
10883
10819
  };
10884
10820
  }
10885
10821
  }
10886
- if (fs22.existsSync(path24.join(cwd, "requirements.txt"))) {
10822
+ if (fs22.existsSync(path23.join(cwd, "requirements.txt"))) {
10887
10823
  return {
10888
10824
  command: ["pip", "install", "-r", "requirements.txt"],
10889
10825
  description: "Installing dependencies with pip",
10890
10826
  detectedFrom: "requirements.txt"
10891
10827
  };
10892
10828
  }
10893
- if (fs22.existsSync(path24.join(cwd, "Gemfile.lock")) || fs22.existsSync(path24.join(cwd, "Gemfile"))) {
10829
+ if (fs22.existsSync(path23.join(cwd, "Gemfile.lock")) || fs22.existsSync(path23.join(cwd, "Gemfile"))) {
10894
10830
  return {
10895
10831
  command: ["bundle", "install"],
10896
10832
  description: "Installing dependencies with bundler",
10897
- detectedFrom: fs22.existsSync(path24.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
10833
+ detectedFrom: fs22.existsSync(path23.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
10898
10834
  };
10899
10835
  }
10900
- if (fs22.existsSync(path24.join(cwd, "go.sum")) || fs22.existsSync(path24.join(cwd, "go.mod"))) {
10836
+ if (fs22.existsSync(path23.join(cwd, "go.sum")) || fs22.existsSync(path23.join(cwd, "go.mod"))) {
10901
10837
  return {
10902
10838
  command: ["go", "mod", "download"],
10903
10839
  description: "Downloading Go modules",
10904
- detectedFrom: fs22.existsSync(path24.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
10840
+ detectedFrom: fs22.existsSync(path23.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
10905
10841
  };
10906
10842
  }
10907
- if (fs22.existsSync(path24.join(cwd, "Cargo.lock")) || fs22.existsSync(path24.join(cwd, "Cargo.toml"))) {
10843
+ if (fs22.existsSync(path23.join(cwd, "Cargo.lock")) || fs22.existsSync(path23.join(cwd, "Cargo.toml"))) {
10908
10844
  return {
10909
10845
  command: ["cargo", "build"],
10910
10846
  description: "Building Rust project (downloads dependencies)",
10911
- detectedFrom: fs22.existsSync(path24.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
10847
+ detectedFrom: fs22.existsSync(path23.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
10912
10848
  };
10913
10849
  }
10914
10850
  return null;
10915
10851
  }
10916
10852
 
10917
10853
  // src/daemon/daemon-process.ts
10854
+ var import_child_process14 = require("child_process");
10918
10855
  var fs23 = __toESM(require("fs"));
10919
10856
  var http2 = __toESM(require("http"));
10920
10857
  var os9 = __toESM(require("os"));
10921
- var path25 = __toESM(require("path"));
10858
+ var path24 = __toESM(require("path"));
10922
10859
  var packageJson = require_package();
10923
10860
  function getBuildPackagesCommand2(installCmd) {
10924
10861
  const runner = installCmd?.command?.[0];
@@ -11086,6 +11023,9 @@ var Daemon = class _Daemon {
11086
11023
  this.pendingUpdateVersion = null;
11087
11024
  // Deferred update when sessions are active
11088
11025
  this.updateInProgress = false;
11026
+ // EP1324: Retry limiting for failed update attempts
11027
+ this.lastFailedUpdateVersion = null;
11028
+ this.updateFailedAttempts = 0;
11089
11029
  this.ipcServer = new IPCServer();
11090
11030
  }
11091
11031
  static {
@@ -11122,6 +11062,9 @@ var Daemon = class _Daemon {
11122
11062
  static {
11123
11063
  this.UPDATE_CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
11124
11064
  }
11065
+ static {
11066
+ this.MAX_UPDATE_ATTEMPTS = 3;
11067
+ }
11125
11068
  /**
11126
11069
  * Start the daemon
11127
11070
  */
@@ -11197,6 +11140,14 @@ var Daemon = class _Daemon {
11197
11140
  }
11198
11141
  async applyUpdateIfIdle(targetVersion, source) {
11199
11142
  if (this.updateInProgress) return;
11143
+ if (targetVersion === packageJson.version) {
11144
+ console.log(`[Daemon] EP1324: Already running version ${targetVersion}, skipping update`);
11145
+ return;
11146
+ }
11147
+ if (this.lastFailedUpdateVersion === targetVersion && this.updateFailedAttempts >= _Daemon.MAX_UPDATE_ATTEMPTS) {
11148
+ console.log(`[Daemon] EP1324: Skipping update to ${targetVersion} after ${this.updateFailedAttempts} failed attempts`);
11149
+ return;
11150
+ }
11200
11151
  const activeAgentSessions = this.getActiveAgentSessionCount();
11201
11152
  if (activeAgentSessions > 0) {
11202
11153
  this.pendingUpdateVersion = targetVersion;
@@ -11206,18 +11157,52 @@ var Daemon = class _Daemon {
11206
11157
  this.updateInProgress = true;
11207
11158
  console.log(`[Daemon] EP1319: Applying CLI update (${source}) to ${targetVersion} (idle)`);
11208
11159
  try {
11209
- const result = await updateAndRestartDaemon(targetVersion);
11210
- if (!result.updateSuccess || !result.restartSuccess) {
11211
- console.warn(`[Daemon] EP1319: Update to ${targetVersion} failed (${result.error || "unknown error"}), will retry later`);
11212
- this.pendingUpdateVersion = targetVersion;
11160
+ const installResult = performSyncUpdate(targetVersion);
11161
+ if (!installResult.success) {
11162
+ this.recordUpdateFailure(targetVersion, `Install failed: ${installResult.error}`);
11163
+ return;
11213
11164
  }
11165
+ const installedVersion = getInstalledVersion();
11166
+ if (installedVersion !== targetVersion) {
11167
+ this.recordUpdateFailure(targetVersion, `Version mismatch: expected ${targetVersion}, got ${installedVersion || "unknown"}`);
11168
+ return;
11169
+ }
11170
+ console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
11171
+ await this.shutdown();
11172
+ const { getConfigDir: getConfigDir8 } = require_dist();
11173
+ const configDir = getConfigDir8();
11174
+ const logPath = path24.join(configDir, "daemon.log");
11175
+ const logFd = fs23.openSync(logPath, "a");
11176
+ const child = (0, import_child_process14.spawn)("node", [__filename], {
11177
+ detached: true,
11178
+ stdio: ["ignore", logFd, logFd],
11179
+ env: { ...process.env, EPISODA_DAEMON_MODE: "1" }
11180
+ });
11181
+ child.unref();
11182
+ const pidPath = getPidFilePath();
11183
+ fs23.writeFileSync(pidPath, child.pid.toString(), "utf-8");
11184
+ console.log(`[Daemon] EP1324: New daemon spawned (PID: ${child.pid}), exiting old process`);
11185
+ process.exit(0);
11214
11186
  } catch (error) {
11215
- console.warn(`[Daemon] EP1319: Update to ${targetVersion} failed, will retry later`);
11216
- this.pendingUpdateVersion = targetVersion;
11187
+ this.recordUpdateFailure(targetVersion, error instanceof Error ? error.message : String(error));
11217
11188
  } finally {
11218
11189
  this.updateInProgress = false;
11219
11190
  }
11220
11191
  }
11192
+ /**
11193
+ * EP1324: Record a failed update attempt and defer for retry.
11194
+ * After MAX_UPDATE_ATTEMPTS for the same version, further attempts are blocked.
11195
+ */
11196
+ recordUpdateFailure(version, reason) {
11197
+ if (this.lastFailedUpdateVersion === version) {
11198
+ this.updateFailedAttempts++;
11199
+ } else {
11200
+ this.lastFailedUpdateVersion = version;
11201
+ this.updateFailedAttempts = 1;
11202
+ }
11203
+ this.pendingUpdateVersion = version;
11204
+ console.warn(`[Daemon] EP1324: Update to ${version} failed (attempt ${this.updateFailedAttempts}/${_Daemon.MAX_UPDATE_ATTEMPTS}): ${reason}`);
11205
+ }
11221
11206
  /**
11222
11207
  * EP1319: Periodic update check for long-lived containers
11223
11208
  * Checks npm registry for newer version. If found:
@@ -11256,9 +11241,9 @@ var Daemon = class _Daemon {
11256
11241
  this.healthServer = http2.createServer((req, res) => {
11257
11242
  if (req.url === "/health" || req.url === "/") {
11258
11243
  const isConnected = this.liveConnections.size > 0;
11259
- const projects = Array.from(this.connections.entries()).map(([path26, conn]) => ({
11260
- path: path26,
11261
- connected: this.liveConnections.has(path26)
11244
+ const projects = Array.from(this.connections.entries()).map(([path25, conn]) => ({
11245
+ path: path25,
11246
+ connected: this.liveConnections.has(path25)
11262
11247
  }));
11263
11248
  const status = {
11264
11249
  status: isConnected ? "healthy" : "degraded",
@@ -11686,7 +11671,7 @@ var Daemon = class _Daemon {
11686
11671
  client.updateActivity();
11687
11672
  try {
11688
11673
  const gitCmd = message.command;
11689
- const bareRepoPath = path25.join(projectPath, ".bare");
11674
+ const bareRepoPath = path24.join(projectPath, ".bare");
11690
11675
  const cwd = gitCmd.worktreePath || bareRepoPath;
11691
11676
  if (gitCmd.worktreePath) {
11692
11677
  console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
@@ -11987,7 +11972,25 @@ var Daemon = class _Daemon {
11987
11972
  console.error(`[Daemon] EP1311: Failed to send tool_result (WebSocket may be disconnected):`, sendError);
11988
11973
  }
11989
11974
  },
11990
- onComplete: async (claudeSessionId) => {
11975
+ // EP1321: Forward compaction events for UI visibility
11976
+ onCompaction: async (event) => {
11977
+ console.log(`[Daemon] EP1321: Forwarding compaction via WebSocket - trigger: ${event.trigger}`);
11978
+ try {
11979
+ await client.send({
11980
+ type: "agent_result",
11981
+ commandId,
11982
+ result: {
11983
+ success: true,
11984
+ status: "compaction",
11985
+ sessionId,
11986
+ compaction: event
11987
+ }
11988
+ });
11989
+ } catch (sendError) {
11990
+ console.error(`[Daemon] EP1321: Failed to send compaction (WebSocket may be disconnected):`, sendError);
11991
+ }
11992
+ },
11993
+ onComplete: async (claudeSessionId, resultMetadata) => {
11991
11994
  const duration = Date.now() - daemonStreamStart;
11992
11995
  console.log(`[Daemon] EP1191: Stream complete - ${daemonChunkCount} chunks, ${daemonToolUseCount} tool uses forwarded in ${duration}ms`);
11993
11996
  commandQueue.complete(sessionId);
@@ -11995,7 +11998,7 @@ var Daemon = class _Daemon {
11995
11998
  await client.send({
11996
11999
  type: "agent_result",
11997
12000
  commandId,
11998
- result: { success: true, status: "complete", sessionId, claudeSessionId }
12001
+ result: { success: true, status: "complete", sessionId, claudeSessionId, resultMetadata }
11999
12002
  });
12000
12003
  } catch (sendError) {
12001
12004
  console.error(`[Daemon] EP912: Failed to send complete (WebSocket may be disconnected):`, sendError);
@@ -12448,8 +12451,8 @@ var Daemon = class _Daemon {
12448
12451
  * - workDir: The directory to run git commands in (cwd)
12449
12452
  */
12450
12453
  getGitDirs(projectPath) {
12451
- const bareDir = path25.join(projectPath, ".bare");
12452
- const gitPath = path25.join(projectPath, ".git");
12454
+ const bareDir = path24.join(projectPath, ".bare");
12455
+ const gitPath = path24.join(projectPath, ".git");
12453
12456
  if (fs23.existsSync(bareDir) && fs23.statSync(bareDir).isDirectory()) {
12454
12457
  return { gitDir: bareDir, workDir: projectPath };
12455
12458
  }
@@ -12462,8 +12465,8 @@ var Daemon = class _Daemon {
12462
12465
  const entries = fs23.readdirSync(projectPath, { withFileTypes: true });
12463
12466
  for (const entry of entries) {
12464
12467
  if (entry.isDirectory() && entry.name.startsWith("EP")) {
12465
- const worktreePath = path25.join(projectPath, entry.name);
12466
- const worktreeGit = path25.join(worktreePath, ".git");
12468
+ const worktreePath = path24.join(projectPath, entry.name);
12469
+ const worktreeGit = path24.join(worktreePath, ".git");
12467
12470
  if (fs23.existsSync(worktreeGit)) {
12468
12471
  return { gitDir: null, workDir: worktreePath };
12469
12472
  }
@@ -12533,14 +12536,14 @@ var Daemon = class _Daemon {
12533
12536
  async installGitHooks(projectPath) {
12534
12537
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
12535
12538
  let hooksDir;
12536
- const bareHooksDir = path25.join(projectPath, ".bare", "hooks");
12537
- const gitHooksDir = path25.join(projectPath, ".git", "hooks");
12539
+ const bareHooksDir = path24.join(projectPath, ".bare", "hooks");
12540
+ const gitHooksDir = path24.join(projectPath, ".git", "hooks");
12538
12541
  if (fs23.existsSync(bareHooksDir)) {
12539
12542
  hooksDir = bareHooksDir;
12540
- } else if (fs23.existsSync(gitHooksDir) && fs23.statSync(path25.join(projectPath, ".git")).isDirectory()) {
12543
+ } else if (fs23.existsSync(gitHooksDir) && fs23.statSync(path24.join(projectPath, ".git")).isDirectory()) {
12541
12544
  hooksDir = gitHooksDir;
12542
12545
  } else {
12543
- const parentBareHooks = path25.join(projectPath, "..", ".bare", "hooks");
12546
+ const parentBareHooks = path24.join(projectPath, "..", ".bare", "hooks");
12544
12547
  if (fs23.existsSync(parentBareHooks)) {
12545
12548
  hooksDir = parentBareHooks;
12546
12549
  } else {
@@ -12558,8 +12561,8 @@ var Daemon = class _Daemon {
12558
12561
  }
12559
12562
  for (const hookName of hooks) {
12560
12563
  try {
12561
- const hookPath = path25.join(hooksDir, hookName);
12562
- const bundledHookPath = path25.join(__dirname, "..", "hooks", hookName);
12564
+ const hookPath = path24.join(hooksDir, hookName);
12565
+ const bundledHookPath = path24.join(__dirname, "..", "hooks", hookName);
12563
12566
  if (!fs23.existsSync(bundledHookPath)) {
12564
12567
  console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
12565
12568
  continue;