episoda 0.2.21 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -295,7 +295,7 @@ var require_git_executor = __commonJS({
295
295
  var git_validator_1 = require_git_validator();
296
296
  var git_parser_1 = require_git_parser();
297
297
  var execAsync = (0, util_1.promisify)(child_process_1.exec);
298
- var GitExecutor6 = class {
298
+ var GitExecutor4 = class {
299
299
  /**
300
300
  * Execute a git command
301
301
  * @param command - The git command to execute
@@ -1531,15 +1531,15 @@ var require_git_executor = __commonJS({
1531
1531
  try {
1532
1532
  const { stdout: gitDir } = await execAsync("git rev-parse --git-dir", { cwd, timeout: 5e3 });
1533
1533
  const gitDirPath = gitDir.trim();
1534
- const fs12 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1534
+ const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1535
1535
  const rebaseMergePath = `${gitDirPath}/rebase-merge`;
1536
1536
  const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
1537
1537
  try {
1538
- await fs12.access(rebaseMergePath);
1538
+ await fs11.access(rebaseMergePath);
1539
1539
  inRebase = true;
1540
1540
  } catch {
1541
1541
  try {
1542
- await fs12.access(rebaseApplyPath);
1542
+ await fs11.access(rebaseApplyPath);
1543
1543
  inRebase = true;
1544
1544
  } catch {
1545
1545
  inRebase = false;
@@ -1593,9 +1593,9 @@ var require_git_executor = __commonJS({
1593
1593
  error: validation.error || "UNKNOWN_ERROR"
1594
1594
  };
1595
1595
  }
1596
- const fs12 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1596
+ const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1597
1597
  try {
1598
- await fs12.access(command.path);
1598
+ await fs11.access(command.path);
1599
1599
  return {
1600
1600
  success: false,
1601
1601
  error: "WORKTREE_EXISTS",
@@ -1646,9 +1646,9 @@ var require_git_executor = __commonJS({
1646
1646
  */
1647
1647
  async executeWorktreeRemove(command, cwd, options) {
1648
1648
  try {
1649
- const fs12 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1649
+ const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1650
1650
  try {
1651
- await fs12.access(command.path);
1651
+ await fs11.access(command.path);
1652
1652
  } catch {
1653
1653
  return {
1654
1654
  success: false,
@@ -1801,10 +1801,10 @@ var require_git_executor = __commonJS({
1801
1801
  */
1802
1802
  async executeCloneBare(command, options) {
1803
1803
  try {
1804
- const fs12 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1805
- const path14 = await Promise.resolve().then(() => __importStar(require("path")));
1804
+ const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1805
+ const path13 = await Promise.resolve().then(() => __importStar(require("path")));
1806
1806
  try {
1807
- await fs12.access(command.path);
1807
+ await fs11.access(command.path);
1808
1808
  return {
1809
1809
  success: false,
1810
1810
  error: "BRANCH_ALREADY_EXISTS",
@@ -1813,9 +1813,9 @@ var require_git_executor = __commonJS({
1813
1813
  };
1814
1814
  } catch {
1815
1815
  }
1816
- const parentDir = path14.dirname(command.path);
1816
+ const parentDir = path13.dirname(command.path);
1817
1817
  try {
1818
- await fs12.mkdir(parentDir, { recursive: true });
1818
+ await fs11.mkdir(parentDir, { recursive: true });
1819
1819
  } catch {
1820
1820
  }
1821
1821
  const { stdout, stderr } = await execAsync(
@@ -1858,24 +1858,24 @@ var require_git_executor = __commonJS({
1858
1858
  */
1859
1859
  async executeProjectInfo(cwd, options) {
1860
1860
  try {
1861
- const fs12 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1862
- const path14 = await Promise.resolve().then(() => __importStar(require("path")));
1861
+ const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1862
+ const path13 = await Promise.resolve().then(() => __importStar(require("path")));
1863
1863
  let currentPath = cwd;
1864
1864
  let worktreeMode = false;
1865
1865
  let projectPath = cwd;
1866
1866
  let bareRepoPath;
1867
1867
  for (let i = 0; i < 10; i++) {
1868
- const bareDir = path14.join(currentPath, ".bare");
1869
- const episodaDir = path14.join(currentPath, ".episoda");
1868
+ const bareDir = path13.join(currentPath, ".bare");
1869
+ const episodaDir = path13.join(currentPath, ".episoda");
1870
1870
  try {
1871
- await fs12.access(bareDir);
1872
- await fs12.access(episodaDir);
1871
+ await fs11.access(bareDir);
1872
+ await fs11.access(episodaDir);
1873
1873
  worktreeMode = true;
1874
1874
  projectPath = currentPath;
1875
1875
  bareRepoPath = bareDir;
1876
1876
  break;
1877
1877
  } catch {
1878
- const parentPath = path14.dirname(currentPath);
1878
+ const parentPath = path13.dirname(currentPath);
1879
1879
  if (parentPath === currentPath) {
1880
1880
  break;
1881
1881
  }
@@ -1998,7 +1998,7 @@ var require_git_executor = __commonJS({
1998
1998
  }
1999
1999
  }
2000
2000
  };
2001
- exports2.GitExecutor = GitExecutor6;
2001
+ exports2.GitExecutor = GitExecutor4;
2002
2002
  }
2003
2003
  });
2004
2004
 
@@ -2467,34 +2467,34 @@ var require_auth = __commonJS({
2467
2467
  Object.defineProperty(exports2, "__esModule", { value: true });
2468
2468
  exports2.getConfigDir = getConfigDir5;
2469
2469
  exports2.getConfigPath = getConfigPath4;
2470
- exports2.loadConfig = loadConfig7;
2471
- exports2.saveConfig = saveConfig5;
2470
+ exports2.loadConfig = loadConfig6;
2471
+ exports2.saveConfig = saveConfig4;
2472
2472
  exports2.validateToken = validateToken;
2473
- var fs12 = __importStar(require("fs"));
2474
- var path14 = __importStar(require("path"));
2473
+ var fs11 = __importStar(require("fs"));
2474
+ var path13 = __importStar(require("path"));
2475
2475
  var os3 = __importStar(require("os"));
2476
2476
  var child_process_1 = require("child_process");
2477
2477
  var DEFAULT_CONFIG_FILE = "config.json";
2478
2478
  function getConfigDir5() {
2479
- return process.env.EPISODA_CONFIG_DIR || path14.join(os3.homedir(), ".episoda");
2479
+ return process.env.EPISODA_CONFIG_DIR || path13.join(os3.homedir(), ".episoda");
2480
2480
  }
2481
2481
  function getConfigPath4(configPath) {
2482
2482
  if (configPath) {
2483
2483
  return configPath;
2484
2484
  }
2485
- return path14.join(getConfigDir5(), DEFAULT_CONFIG_FILE);
2485
+ return path13.join(getConfigDir5(), DEFAULT_CONFIG_FILE);
2486
2486
  }
2487
2487
  function ensureConfigDir(configPath) {
2488
- const dir = path14.dirname(configPath);
2489
- const isNew = !fs12.existsSync(dir);
2488
+ const dir = path13.dirname(configPath);
2489
+ const isNew = !fs11.existsSync(dir);
2490
2490
  if (isNew) {
2491
- fs12.mkdirSync(dir, { recursive: true, mode: 448 });
2491
+ fs11.mkdirSync(dir, { recursive: true, mode: 448 });
2492
2492
  }
2493
2493
  if (process.platform === "darwin") {
2494
- const nosyncPath = path14.join(dir, ".nosync");
2495
- if (isNew || !fs12.existsSync(nosyncPath)) {
2494
+ const nosyncPath = path13.join(dir, ".nosync");
2495
+ if (isNew || !fs11.existsSync(nosyncPath)) {
2496
2496
  try {
2497
- fs12.writeFileSync(nosyncPath, "", { mode: 384 });
2497
+ fs11.writeFileSync(nosyncPath, "", { mode: 384 });
2498
2498
  (0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
2499
2499
  stdio: "ignore",
2500
2500
  timeout: 5e3
@@ -2504,13 +2504,13 @@ var require_auth = __commonJS({
2504
2504
  }
2505
2505
  }
2506
2506
  }
2507
- async function loadConfig7(configPath) {
2507
+ async function loadConfig6(configPath) {
2508
2508
  const fullPath = getConfigPath4(configPath);
2509
- if (!fs12.existsSync(fullPath)) {
2509
+ if (!fs11.existsSync(fullPath)) {
2510
2510
  return null;
2511
2511
  }
2512
2512
  try {
2513
- const content = fs12.readFileSync(fullPath, "utf8");
2513
+ const content = fs11.readFileSync(fullPath, "utf8");
2514
2514
  const config = JSON.parse(content);
2515
2515
  return config;
2516
2516
  } catch (error) {
@@ -2518,12 +2518,12 @@ var require_auth = __commonJS({
2518
2518
  return null;
2519
2519
  }
2520
2520
  }
2521
- async function saveConfig5(config, configPath) {
2521
+ async function saveConfig4(config, configPath) {
2522
2522
  const fullPath = getConfigPath4(configPath);
2523
2523
  ensureConfigDir(fullPath);
2524
2524
  try {
2525
2525
  const content = JSON.stringify(config, null, 2);
2526
- fs12.writeFileSync(fullPath, content, { mode: 384 });
2526
+ fs11.writeFileSync(fullPath, content, { mode: 384 });
2527
2527
  } catch (error) {
2528
2528
  throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
2529
2529
  }
@@ -2634,10 +2634,10 @@ var require_dist = __commonJS({
2634
2634
 
2635
2635
  // src/index.ts
2636
2636
  var import_commander = require("commander");
2637
- var import_core15 = __toESM(require_dist());
2637
+ var import_core14 = __toESM(require_dist());
2638
2638
 
2639
2639
  // src/commands/dev.ts
2640
- var import_core6 = __toESM(require_dist());
2640
+ var import_core4 = __toESM(require_dist());
2641
2641
 
2642
2642
  // src/framework-detector.ts
2643
2643
  var fs = __toESM(require("fs"));
@@ -3110,9 +3110,9 @@ async function getDevServerStatus() {
3110
3110
  }
3111
3111
 
3112
3112
  // src/commands/dev.ts
3113
- var import_child_process3 = require("child_process");
3114
- var path7 = __toESM(require("path"));
3115
- var fs6 = __toESM(require("fs"));
3113
+ var import_child_process2 = require("child_process");
3114
+ var path5 = __toESM(require("path"));
3115
+ var fs4 = __toESM(require("fs"));
3116
3116
 
3117
3117
  // src/utils/port-check.ts
3118
3118
  var net2 = __toESM(require("net"));
@@ -3564,6 +3564,148 @@ var WorktreeManager = class _WorktreeManager {
3564
3564
  this.releaseLock();
3565
3565
  }
3566
3566
  }
3567
+ // EP959-11: Worktree setup methods
3568
+ /**
3569
+ * Update the setup status of a worktree
3570
+ */
3571
+ async updateWorktreeStatus(moduleUid, status2, error) {
3572
+ return this.updateConfigSafe((config) => {
3573
+ const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
3574
+ if (worktree) {
3575
+ worktree.setupStatus = status2;
3576
+ if (status2 === "running") {
3577
+ worktree.setupStartedAt = (/* @__PURE__ */ new Date()).toISOString();
3578
+ } else if (status2 === "ready" || status2 === "error") {
3579
+ worktree.setupCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
3580
+ }
3581
+ if (error) {
3582
+ worktree.setupError = error;
3583
+ }
3584
+ }
3585
+ return config;
3586
+ });
3587
+ }
3588
+ /**
3589
+ * Get worktree info including setup status
3590
+ */
3591
+ getWorktreeStatus(moduleUid) {
3592
+ const config = this.readConfig();
3593
+ if (!config) return null;
3594
+ return config.worktrees.find((w) => w.moduleUid === moduleUid) || null;
3595
+ }
3596
+ /**
3597
+ * Copy files from main worktree to module worktree
3598
+ *
3599
+ * @deprecated EP964: This function is deprecated. Use worktree_env_vars for
3600
+ * environment variables or worktree_setup_script for other file operations.
3601
+ * This function now returns success (no-op) when main/ worktree doesn't exist.
3602
+ */
3603
+ async copyFilesFromMain(moduleUid, files) {
3604
+ console.warn(`[WorktreeManager] EP964: copyFilesFromMain is DEPRECATED.`);
3605
+ console.warn(`[WorktreeManager] EP964: Use worktree_env_vars for .env or worktree_setup_script for other files.`);
3606
+ const config = this.readConfig();
3607
+ if (!config) {
3608
+ return { success: false, error: "Config not found" };
3609
+ }
3610
+ const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
3611
+ if (!worktree) {
3612
+ return { success: false, error: `Worktree not found for ${moduleUid}` };
3613
+ }
3614
+ const mainWorktree = config.worktrees.find((w) => w.moduleUid === "main");
3615
+ if (!mainWorktree) {
3616
+ console.warn(`[WorktreeManager] EP964: No 'main' worktree - skipping file copy (this is expected).`);
3617
+ return { success: true };
3618
+ }
3619
+ try {
3620
+ for (const file of files) {
3621
+ const srcPath = path4.join(mainWorktree.worktreePath, file);
3622
+ const destPath = path4.join(worktree.worktreePath, file);
3623
+ if (fs3.existsSync(srcPath)) {
3624
+ const destDir = path4.dirname(destPath);
3625
+ if (!fs3.existsSync(destDir)) {
3626
+ fs3.mkdirSync(destDir, { recursive: true });
3627
+ }
3628
+ fs3.copyFileSync(srcPath, destPath);
3629
+ console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
3630
+ } else {
3631
+ console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
3632
+ }
3633
+ }
3634
+ return { success: true };
3635
+ } catch (error) {
3636
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
3637
+ }
3638
+ }
3639
+ /**
3640
+ * Run worktree setup script
3641
+ * EP959-M1: Enhanced logging with working directory, timeout info, and script preview
3642
+ */
3643
+ async runSetupScript(moduleUid, script) {
3644
+ const config = this.readConfig();
3645
+ if (!config) {
3646
+ return { success: false, error: "Config not found" };
3647
+ }
3648
+ const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
3649
+ if (!worktree) {
3650
+ return { success: false, error: `Worktree not found for ${moduleUid}` };
3651
+ }
3652
+ const TIMEOUT_MINUTES = 10;
3653
+ const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
3654
+ console.log(`[WorktreeManager] EP959: Running setup script for ${moduleUid}`);
3655
+ console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
3656
+ console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
3657
+ console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
3658
+ try {
3659
+ const { execSync: execSync7 } = require("child_process");
3660
+ execSync7(script, {
3661
+ cwd: worktree.worktreePath,
3662
+ stdio: "inherit",
3663
+ timeout: TIMEOUT_MINUTES * 60 * 1e3,
3664
+ env: { ...process.env, NODE_ENV: "development" }
3665
+ });
3666
+ console.log(`[WorktreeManager] EP959: Setup script completed successfully for ${moduleUid}`);
3667
+ return { success: true };
3668
+ } catch (error) {
3669
+ const errorMessage = error instanceof Error ? error.message : String(error);
3670
+ console.error(`[WorktreeManager] EP959: Setup script failed for ${moduleUid}:`, errorMessage);
3671
+ return { success: false, error: errorMessage };
3672
+ }
3673
+ }
3674
+ /**
3675
+ * Run worktree cleanup script before removal
3676
+ * EP959-m3: Execute cleanup script when worktree is being released
3677
+ */
3678
+ async runCleanupScript(moduleUid, script) {
3679
+ const config = this.readConfig();
3680
+ if (!config) {
3681
+ return { success: false, error: "Config not found" };
3682
+ }
3683
+ const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
3684
+ if (!worktree) {
3685
+ return { success: false, error: `Worktree not found for ${moduleUid}` };
3686
+ }
3687
+ const TIMEOUT_MINUTES = 5;
3688
+ const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
3689
+ console.log(`[WorktreeManager] EP959: Running cleanup script for ${moduleUid}`);
3690
+ console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
3691
+ console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
3692
+ console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
3693
+ try {
3694
+ const { execSync: execSync7 } = require("child_process");
3695
+ execSync7(script, {
3696
+ cwd: worktree.worktreePath,
3697
+ stdio: "inherit",
3698
+ timeout: TIMEOUT_MINUTES * 60 * 1e3,
3699
+ env: { ...process.env, NODE_ENV: "development" }
3700
+ });
3701
+ console.log(`[WorktreeManager] EP959: Cleanup script completed successfully for ${moduleUid}`);
3702
+ return { success: true };
3703
+ } catch (error) {
3704
+ const errorMessage = error instanceof Error ? error.message : String(error);
3705
+ console.warn(`[WorktreeManager] EP959: Cleanup script failed for ${moduleUid} (non-blocking):`, errorMessage);
3706
+ return { success: false, error: errorMessage };
3707
+ }
3708
+ }
3567
3709
  };
3568
3710
  function getEpisodaRoot() {
3569
3711
  return process.env.EPISODA_ROOT || path4.join(require("os").homedir(), "episoda");
@@ -3598,401 +3740,11 @@ async function findProjectRoot(startPath) {
3598
3740
  return null;
3599
3741
  }
3600
3742
 
3601
- // src/commands/migrate.ts
3602
- var fs5 = __toESM(require("fs"));
3603
- var path6 = __toESM(require("path"));
3604
- var import_child_process2 = require("child_process");
3605
- var import_core5 = __toESM(require_dist());
3606
-
3607
- // src/daemon/project-tracker.ts
3608
- var fs4 = __toESM(require("fs"));
3609
- var path5 = __toESM(require("path"));
3610
- var import_core4 = __toESM(require_dist());
3611
- function getProjectsFilePath() {
3612
- return path5.join((0, import_core4.getConfigDir)(), "projects.json");
3613
- }
3614
- function readProjects() {
3615
- const projectsPath = getProjectsFilePath();
3616
- try {
3617
- if (!fs4.existsSync(projectsPath)) {
3618
- return { projects: [] };
3619
- }
3620
- const content = fs4.readFileSync(projectsPath, "utf-8");
3621
- const data = JSON.parse(content);
3622
- if (!data.projects || !Array.isArray(data.projects)) {
3623
- console.warn("Invalid projects.json structure, resetting");
3624
- return { projects: [] };
3625
- }
3626
- return data;
3627
- } catch (error) {
3628
- console.error("Error reading projects.json:", error);
3629
- return { projects: [] };
3630
- }
3631
- }
3632
- function writeProjects(data) {
3633
- const projectsPath = getProjectsFilePath();
3634
- try {
3635
- const dir = path5.dirname(projectsPath);
3636
- if (!fs4.existsSync(dir)) {
3637
- fs4.mkdirSync(dir, { recursive: true });
3638
- }
3639
- fs4.writeFileSync(projectsPath, JSON.stringify(data, null, 2), "utf-8");
3640
- } catch (error) {
3641
- throw new Error(`Failed to write projects.json: ${error}`);
3642
- }
3643
- }
3644
- function addProject2(projectId, projectPath, options) {
3645
- const data = readProjects();
3646
- const now = (/* @__PURE__ */ new Date()).toISOString();
3647
- const existingByPath = data.projects.find((p) => p.path === projectPath);
3648
- if (existingByPath) {
3649
- existingByPath.id = projectId;
3650
- existingByPath.last_active = now;
3651
- if (options?.worktreeMode !== void 0) {
3652
- existingByPath.worktreeMode = options.worktreeMode;
3653
- }
3654
- if (options?.bareRepoPath) {
3655
- existingByPath.bareRepoPath = options.bareRepoPath;
3656
- }
3657
- writeProjects(data);
3658
- return existingByPath;
3659
- }
3660
- const existingByIdIndex = data.projects.findIndex((p) => p.id === projectId);
3661
- if (existingByIdIndex !== -1) {
3662
- const existingById = data.projects[existingByIdIndex];
3663
- console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`);
3664
- data.projects.splice(existingByIdIndex, 1);
3665
- }
3666
- const projectName = path5.basename(projectPath);
3667
- const newProject = {
3668
- id: projectId,
3669
- path: projectPath,
3670
- name: projectName,
3671
- added_at: now,
3672
- last_active: now,
3673
- // EP944: Worktree mode fields
3674
- worktreeMode: options?.worktreeMode,
3675
- bareRepoPath: options?.bareRepoPath
3676
- };
3677
- data.projects.push(newProject);
3678
- writeProjects(data);
3679
- return newProject;
3680
- }
3681
- function getProject(projectPath) {
3682
- const data = readProjects();
3683
- return data.projects.find((p) => p.path === projectPath) || null;
3684
- }
3685
-
3686
- // src/commands/migrate.ts
3687
- async function migrateCommand(options = {}) {
3688
- const cwd = options.cwd || process.cwd();
3689
- status.info("Checking current project...");
3690
- status.info("");
3691
- if (!fs5.existsSync(path6.join(cwd, ".git"))) {
3692
- throw new Error(
3693
- "Not a git repository.\nRun this command from the root of an existing git project."
3694
- );
3695
- }
3696
- const bareDir = path6.join(cwd, ".bare");
3697
- if (fs5.existsSync(bareDir)) {
3698
- throw new Error(
3699
- "This project is already in worktree mode.\nUse `episoda checkout {module}` to create worktrees."
3700
- );
3701
- }
3702
- const config = await (0, import_core5.loadConfig)();
3703
- if (!config || !config.access_token) {
3704
- throw new Error(
3705
- "Not authenticated. Please run `episoda auth` first."
3706
- );
3707
- }
3708
- const episodaConfigPath = path6.join(cwd, ".episoda", "config.json");
3709
- if (!fs5.existsSync(episodaConfigPath)) {
3710
- throw new Error(
3711
- "No .episoda/config.json found.\nThis project is not connected to episoda.dev.\nUse `episoda clone {workspace}/{project}` to clone a fresh copy."
3712
- );
3713
- }
3714
- let existingConfig;
3715
- try {
3716
- existingConfig = JSON.parse(fs5.readFileSync(episodaConfigPath, "utf-8"));
3717
- } catch (error) {
3718
- throw new Error("Failed to read .episoda/config.json");
3719
- }
3720
- const trackedProject = getProject(cwd);
3721
- const projectId = trackedProject?.id || existingConfig.project_id;
3722
- if (!projectId) {
3723
- throw new Error(
3724
- "Cannot determine project ID.\nUse `episoda clone {workspace}/{project}` for a fresh clone."
3725
- );
3726
- }
3727
- const gitExecutor = new import_core5.GitExecutor();
3728
- let currentBranch;
3729
- try {
3730
- currentBranch = (0, import_child_process2.execSync)("git branch --show-current", {
3731
- cwd,
3732
- encoding: "utf-8"
3733
- }).trim();
3734
- } catch {
3735
- throw new Error("Failed to determine current branch");
3736
- }
3737
- const statusResult = await gitExecutor.execute({ action: "status" }, { cwd });
3738
- const hasUncommitted = (statusResult.details?.uncommittedFiles?.length || 0) > 0;
3739
- if (hasUncommitted && !options.force) {
3740
- status.warning("You have uncommitted changes:");
3741
- for (const file of statusResult.details?.uncommittedFiles || []) {
3742
- status.info(` - ${file}`);
3743
- }
3744
- status.info("");
3745
- throw new Error(
3746
- "Please commit or stash your changes first.\nOr use --force to stash changes automatically."
3747
- );
3748
- }
3749
- let remoteUrl;
3750
- try {
3751
- remoteUrl = (0, import_child_process2.execSync)("git remote get-url origin", {
3752
- cwd,
3753
- encoding: "utf-8"
3754
- }).trim();
3755
- } catch {
3756
- throw new Error(
3757
- 'No git remote "origin" found.\nPlease configure a remote before migrating.'
3758
- );
3759
- }
3760
- let workspaceSlug = existingConfig.workspace_slug || "default";
3761
- let projectSlug = existingConfig.project_slug || path6.basename(cwd);
3762
- try {
3763
- const apiUrl = config.api_url || "https://episoda.dev";
3764
- const response = await fetch(`${apiUrl}/api/projects/${projectId}`, {
3765
- headers: {
3766
- "Authorization": `Bearer ${config.access_token}`,
3767
- "Content-Type": "application/json"
3768
- }
3769
- });
3770
- if (response.ok) {
3771
- const responseData = await response.json();
3772
- const projectData = responseData.data?.project;
3773
- if (projectData) {
3774
- workspaceSlug = projectData.workspace_slug || workspaceSlug;
3775
- projectSlug = projectData.slug || projectSlug;
3776
- }
3777
- }
3778
- } catch {
3779
- }
3780
- const targetPath = getProjectPath(workspaceSlug, projectSlug);
3781
- status.info("Migration Plan:");
3782
- status.info(` Current path: ${cwd}`);
3783
- status.info(` Target path: ${targetPath}`);
3784
- status.info(` Remote URL: ${remoteUrl}`);
3785
- status.info(` Current branch: ${currentBranch}`);
3786
- if (hasUncommitted) {
3787
- status.warning(` Uncommitted changes will be stashed`);
3788
- }
3789
- status.info("");
3790
- if (options.dryRun) {
3791
- status.info("Dry run complete. No changes made.");
3792
- status.info("Remove --dry-run to perform the migration.");
3793
- return;
3794
- }
3795
- const uncommittedFiles = statusResult.details?.uncommittedFiles || [];
3796
- if (hasUncommitted) {
3797
- status.info(`Found ${uncommittedFiles.length} uncommitted file(s) to preserve...`);
3798
- }
3799
- const targetExistedBefore = fs5.existsSync(targetPath);
3800
- try {
3801
- status.info("Creating target directory...");
3802
- const episodaRoot = getEpisodaRoot();
3803
- fs5.mkdirSync(targetPath, { recursive: true });
3804
- status.info("Cloning as bare repository...");
3805
- const bareRepoPath = path6.join(targetPath, ".bare");
3806
- const cloneResult = await gitExecutor.execute({
3807
- action: "clone_bare",
3808
- url: remoteUrl,
3809
- path: bareRepoPath
3810
- });
3811
- if (!cloneResult.success) {
3812
- throw new Error(`Failed to clone: ${cloneResult.output}`);
3813
- }
3814
- status.success("\u2713 Bare repository cloned");
3815
- status.info("Creating worktree configuration...");
3816
- const episodaDir = path6.join(targetPath, ".episoda");
3817
- fs5.mkdirSync(episodaDir, { recursive: true });
3818
- const worktreeConfig = {
3819
- projectId,
3820
- workspaceSlug,
3821
- projectSlug,
3822
- bareRepoPath,
3823
- worktreeMode: true,
3824
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3825
- worktrees: []
3826
- };
3827
- fs5.writeFileSync(
3828
- path6.join(episodaDir, "config.json"),
3829
- JSON.stringify(worktreeConfig, null, 2),
3830
- "utf-8"
3831
- );
3832
- status.info(`Creating worktree for branch "${currentBranch}"...`);
3833
- const moduleUid = extractModuleUid(currentBranch) || "main";
3834
- const worktreePath = path6.join(targetPath, moduleUid);
3835
- const worktreeResult = await gitExecutor.execute({
3836
- action: "worktree_add",
3837
- path: worktreePath,
3838
- branch: currentBranch,
3839
- create: false
3840
- }, { cwd: bareRepoPath });
3841
- if (!worktreeResult.success) {
3842
- throw new Error(`Failed to create worktree: ${worktreeResult.output}`);
3843
- }
3844
- status.success(`\u2713 Worktree created at ${worktreePath}`);
3845
- worktreeConfig.worktrees.push({
3846
- moduleUid,
3847
- branchName: currentBranch,
3848
- worktreePath,
3849
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3850
- lastAccessed: (/* @__PURE__ */ new Date()).toISOString()
3851
- });
3852
- fs5.writeFileSync(
3853
- path6.join(episodaDir, "config.json"),
3854
- JSON.stringify(worktreeConfig, null, 2),
3855
- "utf-8"
3856
- );
3857
- if (hasUncommitted && uncommittedFiles.length > 0) {
3858
- status.info("Copying uncommitted changes to new worktree...");
3859
- let copiedCount = 0;
3860
- let failedCount = 0;
3861
- for (const file of uncommittedFiles) {
3862
- const sourcePath = path6.join(cwd, file);
3863
- const destPath = path6.join(worktreePath, file);
3864
- try {
3865
- if (fs5.existsSync(sourcePath)) {
3866
- const destDir = path6.dirname(destPath);
3867
- if (!fs5.existsSync(destDir)) {
3868
- fs5.mkdirSync(destDir, { recursive: true });
3869
- }
3870
- fs5.copyFileSync(sourcePath, destPath);
3871
- copiedCount++;
3872
- }
3873
- } catch (copyError) {
3874
- failedCount++;
3875
- status.warning(`Could not copy: ${file}`);
3876
- }
3877
- }
3878
- if (copiedCount > 0) {
3879
- status.success(`\u2713 Copied ${copiedCount} uncommitted file(s)`);
3880
- }
3881
- if (failedCount > 0) {
3882
- status.warning(`${failedCount} file(s) could not be copied`);
3883
- status.info("You may need to copy some files manually from the original directory");
3884
- }
3885
- }
3886
- addProject2(projectId, targetPath, {
3887
- worktreeMode: true,
3888
- bareRepoPath
3889
- });
3890
- status.success("\u2713 Project registered with daemon");
3891
- try {
3892
- const apiUrl = config.api_url || "https://episoda.dev";
3893
- const settingsResponse = await fetch(`${apiUrl}/api/projects/${projectId}/settings`, {
3894
- method: "PATCH",
3895
- headers: {
3896
- "Authorization": `Bearer ${config.access_token}`,
3897
- "Content-Type": "application/json"
3898
- },
3899
- body: JSON.stringify({
3900
- local_project_path: worktreePath
3901
- })
3902
- });
3903
- if (settingsResponse.ok) {
3904
- status.success("\u2713 Project settings updated");
3905
- } else {
3906
- status.warning("Could not update project settings in database");
3907
- }
3908
- } catch {
3909
- status.warning("Could not update project settings in database");
3910
- }
3911
- try {
3912
- const updatedConfig = {
3913
- ...config,
3914
- project_settings: {
3915
- ...config.project_settings,
3916
- local_project_path: worktreePath,
3917
- cached_at: Date.now()
3918
- }
3919
- };
3920
- await (0, import_core5.saveConfig)(updatedConfig);
3921
- status.success("\u2713 Local config updated");
3922
- } catch {
3923
- status.warning("Could not update local config cache");
3924
- }
3925
- status.info("");
3926
- status.success("Migration complete!");
3927
- status.info("");
3928
- status.info("New project structure:");
3929
- status.info(` ${targetPath}/`);
3930
- status.info(" \u251C\u2500\u2500 .bare/ # Git repository");
3931
- status.info(" \u251C\u2500\u2500 .episoda/");
3932
- status.info(" \u2502 \u2514\u2500\u2500 config.json");
3933
- status.info(` \u2514\u2500\u2500 ${moduleUid}/ # Your current worktree`);
3934
- status.info("");
3935
- status.info("Next steps:");
3936
- status.info(` 1. cd ${worktreePath}`);
3937
- status.info(" 2. Continue working on your current branch");
3938
- status.info(" 3. Use `episoda checkout EP###` for other modules");
3939
- status.info("");
3940
- status.info("Your original repository at:");
3941
- status.info(` ${cwd}`);
3942
- status.info("can be safely removed after verifying the migration.");
3943
- status.info("");
3944
- } catch (error) {
3945
- status.error("Migration failed, cleaning up...");
3946
- if (!targetExistedBefore && fs5.existsSync(targetPath)) {
3947
- try {
3948
- fs5.rmSync(targetPath, { recursive: true, force: true });
3949
- status.info(`Removed ${targetPath}`);
3950
- } catch {
3951
- status.warning(`Could not remove ${targetPath}`);
3952
- }
3953
- } else if (targetExistedBefore) {
3954
- status.info("Target directory existed before migration, only removing migration artifacts...");
3955
- const bareRepoPath = path6.join(targetPath, ".bare");
3956
- const episodaDir = path6.join(targetPath, ".episoda");
3957
- if (fs5.existsSync(bareRepoPath)) {
3958
- try {
3959
- fs5.rmSync(bareRepoPath, { recursive: true, force: true });
3960
- status.info("Removed .bare directory");
3961
- } catch {
3962
- status.warning("Could not remove .bare directory");
3963
- }
3964
- }
3965
- if (fs5.existsSync(episodaDir)) {
3966
- try {
3967
- const configPath = path6.join(episodaDir, "config.json");
3968
- if (fs5.existsSync(configPath)) {
3969
- const config2 = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
3970
- if (config2.worktreeMode) {
3971
- fs5.rmSync(episodaDir, { recursive: true, force: true });
3972
- status.info("Removed .episoda directory");
3973
- }
3974
- }
3975
- } catch {
3976
- status.warning("Could not remove .episoda directory");
3977
- }
3978
- }
3979
- }
3980
- throw error;
3981
- }
3982
- }
3983
- function extractModuleUid(branchName) {
3984
- const match = branchName.match(/ep(\d+)/i);
3985
- if (match) {
3986
- return `EP${match[1]}`;
3987
- }
3988
- return null;
3989
- }
3990
-
3991
3743
  // src/commands/dev.ts
3992
3744
  var CONNECTION_MAX_RETRIES = 3;
3993
3745
  function findGitRoot(startDir) {
3994
3746
  try {
3995
- const result = (0, import_child_process3.execSync)("git rev-parse --show-toplevel", {
3747
+ const result = (0, import_child_process2.execSync)("git rev-parse --show-toplevel", {
3996
3748
  cwd: startDir,
3997
3749
  encoding: "utf-8",
3998
3750
  stdio: ["pipe", "pipe", "pipe"]
@@ -4004,7 +3756,7 @@ function findGitRoot(startDir) {
4004
3756
  }
4005
3757
  async function devCommand(options = {}) {
4006
3758
  try {
4007
- const config = await (0, import_core6.loadConfig)();
3759
+ const config = await (0, import_core4.loadConfig)();
4008
3760
  if (!config || !config.access_token || !config.project_id) {
4009
3761
  status.error("No authentication found. Please run:");
4010
3762
  status.info("");
@@ -4056,7 +3808,7 @@ async function devCommand(options = {}) {
4056
3808
  status.debug(`Using cached project path: ${projectPath}`);
4057
3809
  } else {
4058
3810
  const detectedRoot = findGitRoot(options.cwd || process.cwd());
4059
- projectPath = detectedRoot || path7.resolve(options.cwd || process.cwd());
3811
+ projectPath = detectedRoot || path5.resolve(options.cwd || process.cwd());
4060
3812
  if (detectedRoot) {
4061
3813
  status.debug(`Detected project root: ${projectPath}`);
4062
3814
  } else {
@@ -4070,7 +3822,7 @@ async function devCommand(options = {}) {
4070
3822
  cached_at: Date.now()
4071
3823
  }
4072
3824
  };
4073
- await (0, import_core6.saveConfig)(updatedConfig);
3825
+ await (0, import_core4.saveConfig)(updatedConfig);
4074
3826
  status.debug("Cached project settings locally");
4075
3827
  const settingsUrl = `${serverUrl}/api/projects/${config.project_id}/settings`;
4076
3828
  fetch(settingsUrl, {
@@ -4083,22 +3835,24 @@ async function devCommand(options = {}) {
4083
3835
  }).catch(() => {
4084
3836
  });
4085
3837
  }
4086
- const hasGitDir = fs6.existsSync(path7.join(projectPath, ".git"));
4087
3838
  const isWorktree = await isWorktreeProject(projectPath);
4088
- if (hasGitDir && !isWorktree) {
4089
- status.info("");
4090
- status.info("EP944: Migrating project to worktree mode...");
4091
- status.info("This is a one-time operation that enables multi-module development.");
4092
- status.info("");
4093
- try {
4094
- await migrateCommand({ cwd: projectPath, silent: false });
4095
- status.success("Project migrated to worktree mode!");
3839
+ if (!isWorktree) {
3840
+ const hasGitDir = fs4.existsSync(path5.join(projectPath, ".git"));
3841
+ if (hasGitDir) {
3842
+ status.error("This project is not in worktree mode.");
4096
3843
  status.info("");
4097
- } catch (error) {
4098
- status.warning(`Migration skipped: ${error.message}`);
4099
- status.info("You can run `episoda migrate` manually later.");
3844
+ status.info("Episoda requires projects to be cloned in worktree mode.");
3845
+ status.info("To set up this project:");
3846
+ status.info(" 1. Get the workspace/project slug from episoda.dev");
3847
+ status.info(" 2. Run: episoda clone {workspace}/{project}");
3848
+ status.info(" 3. Then: cd ~/episoda/{workspace}/{project}");
3849
+ status.info("");
3850
+ } else {
3851
+ status.error("Not a git repository or worktree project.");
3852
+ status.info("Run this command from within a project cloned with `episoda clone`.");
4100
3853
  status.info("");
4101
3854
  }
3855
+ process.exit(1);
4102
3856
  }
4103
3857
  let daemonPid = isDaemonRunning();
4104
3858
  if (!daemonPid) {
@@ -4192,7 +3946,7 @@ async function runDevServer(command, cwd, autoRestart) {
4192
3946
  let shuttingDown = false;
4193
3947
  const startServer = () => {
4194
3948
  status.info(`Starting dev server: ${command.join(" ")}`);
4195
- devProcess = (0, import_child_process3.spawn)(command[0], command.slice(1), {
3949
+ devProcess = (0, import_child_process2.spawn)(command[0], command.slice(1), {
4196
3950
  cwd,
4197
3951
  stdio: ["inherit", "inherit", "inherit"],
4198
3952
  shell: true
@@ -4245,33 +3999,33 @@ Received ${signal}, shutting down...`);
4245
3999
 
4246
4000
  // src/commands/auth.ts
4247
4001
  var os = __toESM(require("os"));
4248
- var fs8 = __toESM(require("fs"));
4249
- var path9 = __toESM(require("path"));
4250
- var import_child_process5 = require("child_process");
4251
- var import_core8 = __toESM(require_dist());
4002
+ var fs6 = __toESM(require("fs"));
4003
+ var path7 = __toESM(require("path"));
4004
+ var import_child_process4 = require("child_process");
4005
+ var import_core6 = __toESM(require_dist());
4252
4006
 
4253
4007
  // src/daemon/machine-id.ts
4254
- var fs7 = __toESM(require("fs"));
4255
- var path8 = __toESM(require("path"));
4008
+ var fs5 = __toESM(require("fs"));
4009
+ var path6 = __toESM(require("path"));
4256
4010
  var crypto2 = __toESM(require("crypto"));
4257
- var import_child_process4 = require("child_process");
4258
- var import_core7 = __toESM(require_dist());
4011
+ var import_child_process3 = require("child_process");
4012
+ var import_core5 = __toESM(require_dist());
4259
4013
  function isValidUUID(str) {
4260
4014
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4261
4015
  return uuidRegex.test(str);
4262
4016
  }
4263
4017
  async function getMachineId() {
4264
- const machineIdPath = path8.join((0, import_core7.getConfigDir)(), "machine-id");
4018
+ const machineIdPath = path6.join((0, import_core5.getConfigDir)(), "machine-id");
4265
4019
  try {
4266
- if (fs7.existsSync(machineIdPath)) {
4267
- const existingId = fs7.readFileSync(machineIdPath, "utf-8").trim();
4020
+ if (fs5.existsSync(machineIdPath)) {
4021
+ const existingId = fs5.readFileSync(machineIdPath, "utf-8").trim();
4268
4022
  if (existingId) {
4269
4023
  if (isValidUUID(existingId)) {
4270
4024
  return existingId;
4271
4025
  }
4272
4026
  console.log("[MachineId] Migrating legacy machine ID to UUID format...");
4273
4027
  const newUUID = generateMachineId();
4274
- fs7.writeFileSync(machineIdPath, newUUID, "utf-8");
4028
+ fs5.writeFileSync(machineIdPath, newUUID, "utf-8");
4275
4029
  console.log(`[MachineId] Migrated: ${existingId} \u2192 ${newUUID}`);
4276
4030
  return newUUID;
4277
4031
  }
@@ -4280,11 +4034,11 @@ async function getMachineId() {
4280
4034
  }
4281
4035
  const machineId = generateMachineId();
4282
4036
  try {
4283
- const dir = path8.dirname(machineIdPath);
4284
- if (!fs7.existsSync(dir)) {
4285
- fs7.mkdirSync(dir, { recursive: true });
4037
+ const dir = path6.dirname(machineIdPath);
4038
+ if (!fs5.existsSync(dir)) {
4039
+ fs5.mkdirSync(dir, { recursive: true });
4286
4040
  }
4287
- fs7.writeFileSync(machineIdPath, machineId, "utf-8");
4041
+ fs5.writeFileSync(machineIdPath, machineId, "utf-8");
4288
4042
  } catch (error) {
4289
4043
  console.error("Warning: Could not save machine ID to disk:", error);
4290
4044
  }
@@ -4293,7 +4047,7 @@ async function getMachineId() {
4293
4047
  function getHardwareUUID() {
4294
4048
  try {
4295
4049
  if (process.platform === "darwin") {
4296
- const output = (0, import_child_process4.execSync)(
4050
+ const output = (0, import_child_process3.execSync)(
4297
4051
  `ioreg -d2 -c IOPlatformExpertDevice | awk -F\\" '/IOPlatformUUID/{print $(NF-1)}'`,
4298
4052
  { encoding: "utf-8", timeout: 5e3 }
4299
4053
  ).trim();
@@ -4301,20 +4055,20 @@ function getHardwareUUID() {
4301
4055
  return output;
4302
4056
  }
4303
4057
  } else if (process.platform === "linux") {
4304
- if (fs7.existsSync("/etc/machine-id")) {
4305
- const machineId = fs7.readFileSync("/etc/machine-id", "utf-8").trim();
4058
+ if (fs5.existsSync("/etc/machine-id")) {
4059
+ const machineId = fs5.readFileSync("/etc/machine-id", "utf-8").trim();
4306
4060
  if (machineId && machineId.length > 0) {
4307
4061
  return machineId;
4308
4062
  }
4309
4063
  }
4310
- if (fs7.existsSync("/var/lib/dbus/machine-id")) {
4311
- const dbusId = fs7.readFileSync("/var/lib/dbus/machine-id", "utf-8").trim();
4064
+ if (fs5.existsSync("/var/lib/dbus/machine-id")) {
4065
+ const dbusId = fs5.readFileSync("/var/lib/dbus/machine-id", "utf-8").trim();
4312
4066
  if (dbusId && dbusId.length > 0) {
4313
4067
  return dbusId;
4314
4068
  }
4315
4069
  }
4316
4070
  } else if (process.platform === "win32") {
4317
- const output = (0, import_child_process4.execSync)("wmic csproduct get uuid", {
4071
+ const output = (0, import_child_process3.execSync)("wmic csproduct get uuid", {
4318
4072
  encoding: "utf-8",
4319
4073
  timeout: 5e3
4320
4074
  });
@@ -4695,7 +4449,7 @@ async function authCommand(options = {}) {
4695
4449
  status.info(` Project: ${tokenResponse.project_uid}`);
4696
4450
  status.info(` Workspace: ${tokenResponse.workspace_uid}`);
4697
4451
  status.info("");
4698
- await (0, import_core8.saveConfig)({
4452
+ await (0, import_core6.saveConfig)({
4699
4453
  project_id: tokenResponse.project_id,
4700
4454
  user_id: tokenResponse.user_id,
4701
4455
  workspace_id: tokenResponse.workspace_id,
@@ -4709,7 +4463,7 @@ async function authCommand(options = {}) {
4709
4463
  // Convert to Unix timestamp
4710
4464
  api_url: apiUrl
4711
4465
  });
4712
- status.success(`\u2713 Configuration saved to ${(0, import_core8.getConfigPath)()}`);
4466
+ status.success(`\u2713 Configuration saved to ${(0, import_core6.getConfigPath)()}`);
4713
4467
  status.info("");
4714
4468
  status.info("Installing git credential helper...");
4715
4469
  const credentialHelperInstalled = await installGitCredentialHelper(apiUrl);
@@ -4753,7 +4507,7 @@ async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
4753
4507
  resolve4(false);
4754
4508
  }, expiresIn * 1e3);
4755
4509
  const url = `${apiUrl}/api/oauth/authorize-stream?device_code=${deviceCode}`;
4756
- const curlProcess = (0, import_child_process5.spawn)("curl", ["-N", url]);
4510
+ const curlProcess = (0, import_child_process4.spawn)("curl", ["-N", url]);
4757
4511
  let buffer = "";
4758
4512
  curlProcess.stdout.on("data", (chunk) => {
4759
4513
  buffer += chunk.toString();
@@ -4857,7 +4611,7 @@ function openBrowser(url) {
4857
4611
  break;
4858
4612
  }
4859
4613
  try {
4860
- (0, import_child_process5.spawn)(command, args, {
4614
+ (0, import_child_process4.spawn)(command, args, {
4861
4615
  detached: true,
4862
4616
  stdio: "ignore"
4863
4617
  }).unref();
@@ -4868,17 +4622,17 @@ function openBrowser(url) {
4868
4622
  async function installGitCredentialHelper(apiUrl) {
4869
4623
  try {
4870
4624
  const homeDir = os.homedir();
4871
- const episodaBinDir = path9.join(homeDir, ".episoda", "bin");
4872
- const helperPath = path9.join(episodaBinDir, "git-credential-episoda");
4873
- fs8.mkdirSync(episodaBinDir, { recursive: true });
4625
+ const episodaBinDir = path7.join(homeDir, ".episoda", "bin");
4626
+ const helperPath = path7.join(episodaBinDir, "git-credential-episoda");
4627
+ fs6.mkdirSync(episodaBinDir, { recursive: true });
4874
4628
  const scriptContent = generateCredentialHelperScript(apiUrl);
4875
- fs8.writeFileSync(helperPath, scriptContent, { mode: 493 });
4629
+ fs6.writeFileSync(helperPath, scriptContent, { mode: 493 });
4876
4630
  try {
4877
- fs8.accessSync(helperPath, fs8.constants.X_OK);
4631
+ fs6.accessSync(helperPath, fs6.constants.X_OK);
4878
4632
  } catch {
4879
4633
  }
4880
4634
  try {
4881
- const allHelpers = (0, import_child_process5.execSync)("git config --global --get-all credential.helper", {
4635
+ const allHelpers = (0, import_child_process4.execSync)("git config --global --get-all credential.helper", {
4882
4636
  encoding: "utf8",
4883
4637
  stdio: ["pipe", "pipe", "pipe"]
4884
4638
  }).trim().split("\n");
@@ -4887,7 +4641,7 @@ async function installGitCredentialHelper(apiUrl) {
4887
4641
  }
4888
4642
  } catch {
4889
4643
  }
4890
- (0, import_child_process5.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
4644
+ (0, import_child_process4.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
4891
4645
  encoding: "utf8",
4892
4646
  stdio: ["pipe", "pipe", "pipe"]
4893
4647
  });
@@ -4905,19 +4659,19 @@ function updateShellProfile(binDir) {
4905
4659
  }
4906
4660
  const homeDir = os.homedir();
4907
4661
  const profiles = [
4908
- path9.join(homeDir, ".bashrc"),
4909
- path9.join(homeDir, ".zshrc"),
4910
- path9.join(homeDir, ".profile")
4662
+ path7.join(homeDir, ".bashrc"),
4663
+ path7.join(homeDir, ".zshrc"),
4664
+ path7.join(homeDir, ".profile")
4911
4665
  ];
4912
4666
  const exportLine = `export PATH="${binDir}:$PATH" # Added by episoda auth`;
4913
4667
  for (const profile of profiles) {
4914
4668
  try {
4915
- if (fs8.existsSync(profile)) {
4916
- const content = fs8.readFileSync(profile, "utf8");
4669
+ if (fs6.existsSync(profile)) {
4670
+ const content = fs6.readFileSync(profile, "utf8");
4917
4671
  if (content.includes(".episoda/bin")) {
4918
4672
  continue;
4919
4673
  }
4920
- fs8.appendFileSync(profile, `
4674
+ fs6.appendFileSync(profile, `
4921
4675
  # Episoda CLI
4922
4676
  ${exportLine}
4923
4677
  `);
@@ -4929,10 +4683,10 @@ ${exportLine}
4929
4683
 
4930
4684
  // src/commands/connect.ts
4931
4685
  var os2 = __toESM(require("os"));
4932
- var fs9 = __toESM(require("fs"));
4933
- var path10 = __toESM(require("path"));
4934
- var import_child_process6 = require("child_process");
4935
- var import_core9 = __toESM(require_dist());
4686
+ var fs7 = __toESM(require("fs"));
4687
+ var path8 = __toESM(require("path"));
4688
+ var import_child_process5 = require("child_process");
4689
+ var import_core7 = __toESM(require_dist());
4936
4690
  async function connectCommand(options) {
4937
4691
  const { code } = options;
4938
4692
  const apiUrl = options.apiUrl || process.env.EPISODA_API_URL || "https://episoda.dev";
@@ -4948,14 +4702,14 @@ async function connectCommand(options) {
4948
4702
  status.info(` Project: ${tokenResponse.project_uid}`);
4949
4703
  status.info(` Workspace: ${tokenResponse.workspace_uid}`);
4950
4704
  status.info("");
4951
- await (0, import_core9.saveConfig)({
4705
+ await (0, import_core7.saveConfig)({
4952
4706
  project_id: tokenResponse.project_id,
4953
4707
  user_id: tokenResponse.user_id,
4954
4708
  workspace_id: tokenResponse.workspace_id,
4955
4709
  access_token: tokenResponse.access_token,
4956
4710
  api_url: apiUrl
4957
4711
  });
4958
- status.success(`\u2713 Configuration saved to ${(0, import_core9.getConfigPath)()}`);
4712
+ status.success(`\u2713 Configuration saved to ${(0, import_core7.getConfigPath)()}`);
4959
4713
  status.info("");
4960
4714
  status.info("Installing git credential helper...");
4961
4715
  const credentialHelperInstalled = await installGitCredentialHelper2(apiUrl);
@@ -5005,13 +4759,13 @@ async function exchangeUserCode(apiUrl, userCode, machineId) {
5005
4759
  async function installGitCredentialHelper2(apiUrl) {
5006
4760
  try {
5007
4761
  const homeDir = os2.homedir();
5008
- const episodaBinDir = path10.join(homeDir, ".episoda", "bin");
5009
- const helperPath = path10.join(episodaBinDir, "git-credential-episoda");
5010
- fs9.mkdirSync(episodaBinDir, { recursive: true });
4762
+ const episodaBinDir = path8.join(homeDir, ".episoda", "bin");
4763
+ const helperPath = path8.join(episodaBinDir, "git-credential-episoda");
4764
+ fs7.mkdirSync(episodaBinDir, { recursive: true });
5011
4765
  const scriptContent = generateCredentialHelperScript(apiUrl);
5012
- fs9.writeFileSync(helperPath, scriptContent, { mode: 493 });
4766
+ fs7.writeFileSync(helperPath, scriptContent, { mode: 493 });
5013
4767
  try {
5014
- const allHelpers = (0, import_child_process6.execSync)("git config --global --get-all credential.helper", {
4768
+ const allHelpers = (0, import_child_process5.execSync)("git config --global --get-all credential.helper", {
5015
4769
  encoding: "utf8",
5016
4770
  stdio: ["pipe", "pipe", "pipe"]
5017
4771
  }).trim().split("\n");
@@ -5020,7 +4774,7 @@ async function installGitCredentialHelper2(apiUrl) {
5020
4774
  }
5021
4775
  } catch {
5022
4776
  }
5023
- (0, import_child_process6.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
4777
+ (0, import_child_process5.execSync)(`git config --global --add credential.helper "${helperPath}"`, {
5024
4778
  encoding: "utf8",
5025
4779
  stdio: ["pipe", "pipe", "pipe"]
5026
4780
  });
@@ -5038,19 +4792,19 @@ function updateShellProfile2(binDir) {
5038
4792
  }
5039
4793
  const homeDir = os2.homedir();
5040
4794
  const profiles = [
5041
- path10.join(homeDir, ".bashrc"),
5042
- path10.join(homeDir, ".zshrc"),
5043
- path10.join(homeDir, ".profile")
4795
+ path8.join(homeDir, ".bashrc"),
4796
+ path8.join(homeDir, ".zshrc"),
4797
+ path8.join(homeDir, ".profile")
5044
4798
  ];
5045
4799
  const exportLine = `export PATH="${binDir}:$PATH" # Added by episoda`;
5046
4800
  for (const profile of profiles) {
5047
4801
  try {
5048
- if (fs9.existsSync(profile)) {
5049
- const content = fs9.readFileSync(profile, "utf8");
4802
+ if (fs7.existsSync(profile)) {
4803
+ const content = fs7.readFileSync(profile, "utf8");
5050
4804
  if (content.includes(".episoda/bin")) {
5051
4805
  continue;
5052
4806
  }
5053
- fs9.appendFileSync(profile, `
4807
+ fs7.appendFileSync(profile, `
5054
4808
  # Episoda CLI
5055
4809
  ${exportLine}
5056
4810
  `);
@@ -5061,11 +4815,11 @@ ${exportLine}
5061
4815
  }
5062
4816
 
5063
4817
  // src/commands/status.ts
5064
- var import_core10 = __toESM(require_dist());
4818
+ var import_core8 = __toESM(require_dist());
5065
4819
  async function statusCommand(options = {}) {
5066
4820
  status.info("Checking CLI status...");
5067
4821
  status.info("");
5068
- const config = await (0, import_core10.loadConfig)();
4822
+ const config = await (0, import_core8.loadConfig)();
5069
4823
  if (!config) {
5070
4824
  status.error("\u2717 CLI not initialized");
5071
4825
  status.info("");
@@ -5080,8 +4834,8 @@ async function statusCommand(options = {}) {
5080
4834
  status.info("Configuration:");
5081
4835
  status.info(` Project ID: ${config.project_id}`);
5082
4836
  status.info(` API URL: ${config.api_url}`);
5083
- status.info(` CLI Version: ${import_core10.VERSION}`);
5084
- status.info(` Config file: ${(0, import_core10.getConfigPath)()}`);
4837
+ status.info(` CLI Version: ${import_core8.VERSION}`);
4838
+ status.info(` Config file: ${(0, import_core8.getConfigPath)()}`);
5085
4839
  status.info("");
5086
4840
  if (!config.access_token || config.access_token === "") {
5087
4841
  status.warning("\u26A0 Not authenticated");
@@ -5241,9 +4995,87 @@ async function stopCommand(options = {}) {
5241
4995
  }
5242
4996
 
5243
4997
  // src/commands/clone.ts
5244
- var fs10 = __toESM(require("fs"));
5245
- var path11 = __toESM(require("path"));
5246
- var import_core11 = __toESM(require_dist());
4998
+ var fs9 = __toESM(require("fs"));
4999
+ var path10 = __toESM(require("path"));
5000
+ var import_child_process6 = require("child_process");
5001
+ var import_core10 = __toESM(require_dist());
5002
+
5003
+ // src/daemon/project-tracker.ts
5004
+ var fs8 = __toESM(require("fs"));
5005
+ var path9 = __toESM(require("path"));
5006
+ var import_core9 = __toESM(require_dist());
5007
+ function getProjectsFilePath() {
5008
+ return path9.join((0, import_core9.getConfigDir)(), "projects.json");
5009
+ }
5010
+ function readProjects() {
5011
+ const projectsPath = getProjectsFilePath();
5012
+ try {
5013
+ if (!fs8.existsSync(projectsPath)) {
5014
+ return { projects: [] };
5015
+ }
5016
+ const content = fs8.readFileSync(projectsPath, "utf-8");
5017
+ const data = JSON.parse(content);
5018
+ if (!data.projects || !Array.isArray(data.projects)) {
5019
+ console.warn("Invalid projects.json structure, resetting");
5020
+ return { projects: [] };
5021
+ }
5022
+ return data;
5023
+ } catch (error) {
5024
+ console.error("Error reading projects.json:", error);
5025
+ return { projects: [] };
5026
+ }
5027
+ }
5028
+ function writeProjects(data) {
5029
+ const projectsPath = getProjectsFilePath();
5030
+ try {
5031
+ const dir = path9.dirname(projectsPath);
5032
+ if (!fs8.existsSync(dir)) {
5033
+ fs8.mkdirSync(dir, { recursive: true });
5034
+ }
5035
+ fs8.writeFileSync(projectsPath, JSON.stringify(data, null, 2), "utf-8");
5036
+ } catch (error) {
5037
+ throw new Error(`Failed to write projects.json: ${error}`);
5038
+ }
5039
+ }
5040
+ function addProject2(projectId, projectPath, options) {
5041
+ const data = readProjects();
5042
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5043
+ const existingByPath = data.projects.find((p) => p.path === projectPath);
5044
+ if (existingByPath) {
5045
+ existingByPath.id = projectId;
5046
+ existingByPath.last_active = now;
5047
+ if (options?.worktreeMode !== void 0) {
5048
+ existingByPath.worktreeMode = options.worktreeMode;
5049
+ }
5050
+ if (options?.bareRepoPath) {
5051
+ existingByPath.bareRepoPath = options.bareRepoPath;
5052
+ }
5053
+ writeProjects(data);
5054
+ return existingByPath;
5055
+ }
5056
+ const existingByIdIndex = data.projects.findIndex((p) => p.id === projectId);
5057
+ if (existingByIdIndex !== -1) {
5058
+ const existingById = data.projects[existingByIdIndex];
5059
+ console.log(`[ProjectTracker] Replacing project entry: ${existingById.path} -> ${projectPath}`);
5060
+ data.projects.splice(existingByIdIndex, 1);
5061
+ }
5062
+ const projectName = path9.basename(projectPath);
5063
+ const newProject = {
5064
+ id: projectId,
5065
+ path: projectPath,
5066
+ name: projectName,
5067
+ added_at: now,
5068
+ last_active: now,
5069
+ // EP944: Worktree mode fields
5070
+ worktreeMode: options?.worktreeMode,
5071
+ bareRepoPath: options?.bareRepoPath
5072
+ };
5073
+ data.projects.push(newProject);
5074
+ writeProjects(data);
5075
+ return newProject;
5076
+ }
5077
+
5078
+ // src/commands/clone.ts
5247
5079
  async function cloneCommand(slugArg, options = {}) {
5248
5080
  const slugParts = slugArg.split("/");
5249
5081
  if (slugParts.length !== 2 || !slugParts[0] || !slugParts[1]) {
@@ -5254,7 +5086,7 @@ async function cloneCommand(slugArg, options = {}) {
5254
5086
  const [workspaceSlug, projectSlug] = slugParts;
5255
5087
  status.info(`Cloning ${workspaceSlug}/${projectSlug}...`);
5256
5088
  status.info("");
5257
- const config = await (0, import_core11.loadConfig)();
5089
+ const config = await (0, import_core10.loadConfig)();
5258
5090
  if (!config || !config.access_token) {
5259
5091
  throw new Error(
5260
5092
  "Not authenticated. Please run `episoda auth` first."
@@ -5262,9 +5094,9 @@ async function cloneCommand(slugArg, options = {}) {
5262
5094
  }
5263
5095
  const apiUrl = options.apiUrl || config.api_url || "https://episoda.dev";
5264
5096
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
5265
- if (fs10.existsSync(projectPath)) {
5266
- const bareRepoPath = path11.join(projectPath, ".bare");
5267
- if (fs10.existsSync(bareRepoPath)) {
5097
+ if (fs9.existsSync(projectPath)) {
5098
+ const bareRepoPath = path10.join(projectPath, ".bare");
5099
+ if (fs9.existsSync(bareRepoPath)) {
5268
5100
  status.warning(`Project already cloned at ${projectPath}`);
5269
5101
  status.info("");
5270
5102
  status.info("Next steps:");
@@ -5296,7 +5128,7 @@ Please configure a repository in the project settings on episoda.dev.`
5296
5128
  status.info("");
5297
5129
  status.info("Creating project directory...");
5298
5130
  const episodaRoot = getEpisodaRoot();
5299
- fs10.mkdirSync(projectPath, { recursive: true });
5131
+ fs9.mkdirSync(projectPath, { recursive: true });
5300
5132
  status.success(`\u2713 Created ${projectPath}`);
5301
5133
  status.info("Cloning repository (bare)...");
5302
5134
  try {
@@ -5310,6 +5142,9 @@ Please configure a repository in the project settings on episoda.dev.`
5310
5142
  status.success("\u2713 Repository cloned");
5311
5143
  status.info("");
5312
5144
  const bareRepoPath = worktreeManager.getBareRepoPath();
5145
+ await extractBootstrapScripts(bareRepoPath, projectPath);
5146
+ status.success("\u2713 Bootstrap scripts extracted");
5147
+ status.info("");
5313
5148
  addProject2(projectDetails.id, projectPath, {
5314
5149
  worktreeMode: true,
5315
5150
  bareRepoPath
@@ -5320,18 +5155,21 @@ Please configure a repository in the project settings on episoda.dev.`
5320
5155
  status.info("");
5321
5156
  status.info("Project structure:");
5322
5157
  status.info(` ${projectPath}/`);
5323
- status.info(" \u251C\u2500\u2500 .bare/ # Git repository");
5158
+ status.info(" \u251C\u2500\u2500 .bare/ # Git repository");
5324
5159
  status.info(" \u2514\u2500\u2500 .episoda/");
5325
- status.info(" \u2514\u2500\u2500 config.json # Project config");
5160
+ status.info(" \u251C\u2500\u2500 config.json # Project config");
5161
+ status.info(" \u2514\u2500\u2500 scripts/");
5162
+ status.info(" \u2514\u2500\u2500 api-helper.sh # API authentication");
5326
5163
  status.info("");
5327
5164
  status.info("Next steps:");
5328
5165
  status.info(` 1. cd ${projectPath}`);
5329
- status.info(" 2. episoda checkout {moduleUid} # e.g., episoda checkout EP100");
5166
+ status.info(" 2. source .episoda/scripts/api-helper.sh # Load API helpers");
5167
+ status.info(" 3. episoda checkout {moduleUid} # e.g., episoda checkout EP100");
5330
5168
  status.info("");
5331
5169
  } catch (error) {
5332
- if (fs10.existsSync(projectPath)) {
5170
+ if (fs9.existsSync(projectPath)) {
5333
5171
  try {
5334
- fs10.rmSync(projectPath, { recursive: true, force: true });
5172
+ fs9.rmSync(projectPath, { recursive: true, force: true });
5335
5173
  } catch {
5336
5174
  }
5337
5175
  }
@@ -5404,9 +5242,23 @@ ${errorBody}`
5404
5242
  }
5405
5243
  return data;
5406
5244
  }
5245
+ async function extractBootstrapScripts(bareRepoPath, projectPath) {
5246
+ const scriptsDir = path10.join(projectPath, ".episoda", "scripts");
5247
+ fs9.mkdirSync(scriptsDir, { recursive: true });
5248
+ try {
5249
+ const scriptContent = (0, import_child_process6.execSync)(
5250
+ `git --git-dir="${bareRepoPath}" show main:scripts/api-helper.sh`,
5251
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5252
+ );
5253
+ const scriptPath = path10.join(scriptsDir, "api-helper.sh");
5254
+ fs9.writeFileSync(scriptPath, scriptContent, { mode: 493 });
5255
+ } catch (error) {
5256
+ console.log("[clone] Could not extract api-helper.sh:", error);
5257
+ }
5258
+ }
5407
5259
 
5408
5260
  // src/commands/checkout.ts
5409
- var import_core12 = __toESM(require_dist());
5261
+ var import_core11 = __toESM(require_dist());
5410
5262
 
5411
5263
  // src/utils/http.ts
5412
5264
  async function fetchWithRetry(url, options, maxRetries = 3) {
@@ -5446,7 +5298,7 @@ async function checkoutCommand(moduleUid, options = {}) {
5446
5298
  "Not in a worktree project.\nRun this command from within a project cloned with `episoda clone`.\nOr cd to ~/episoda/{workspace}/{project}/"
5447
5299
  );
5448
5300
  }
5449
- const config = await (0, import_core12.loadConfig)();
5301
+ const config = await (0, import_core11.loadConfig)();
5450
5302
  if (!config || !config.access_token) {
5451
5303
  throw new Error(
5452
5304
  "Not authenticated. Please run `episoda auth` first."
@@ -5485,8 +5337,8 @@ The .bare directory or .episoda/config.json may be missing.`
5485
5337
  const branchName = moduleDetails.branch_name || `feature/${moduleUid.toLowerCase()}`;
5486
5338
  let createBranch = !moduleDetails.branch_name || options.create;
5487
5339
  if (createBranch && !moduleDetails.branch_name) {
5488
- const { GitExecutor: GitExecutor6 } = await Promise.resolve().then(() => __toESM(require_dist()));
5489
- const gitExecutor = new GitExecutor6();
5340
+ const { GitExecutor: GitExecutor4 } = await Promise.resolve().then(() => __toESM(require_dist()));
5341
+ const gitExecutor = new GitExecutor4();
5490
5342
  const branchCheck = await gitExecutor.execute(
5491
5343
  { action: "branch_exists", branch: branchName },
5492
5344
  { cwd: worktreeManager.getBareRepoPath() }
@@ -5583,8 +5435,8 @@ async function updateModuleCheckout(apiUrl, moduleId, accessToken, branchName) {
5583
5435
  }
5584
5436
 
5585
5437
  // src/commands/release.ts
5586
- var path12 = __toESM(require("path"));
5587
- var import_core13 = __toESM(require_dist());
5438
+ var path11 = __toESM(require("path"));
5439
+ var import_core12 = __toESM(require_dist());
5588
5440
  async function releaseCommand(moduleUid, options = {}) {
5589
5441
  if (!moduleUid || !moduleUid.match(/^EP\d+$/)) {
5590
5442
  throw new Error(
@@ -5630,12 +5482,24 @@ Commit or stash your changes first, or use --force to discard them.`
5630
5482
  );
5631
5483
  }
5632
5484
  }
5633
- const currentPath = path12.resolve(process.cwd());
5485
+ const currentPath = path11.resolve(process.cwd());
5634
5486
  if (currentPath.startsWith(existing.worktreePath)) {
5635
5487
  status.warning("You are inside the worktree being released.");
5636
5488
  status.info(`Please cd to ${projectRoot} first.`);
5637
5489
  throw new Error("Cannot release worktree while inside it");
5638
5490
  }
5491
+ const config = await (0, import_core12.loadConfig)();
5492
+ const cleanupScript = config?.project_settings?.worktree_cleanup_script;
5493
+ if (cleanupScript) {
5494
+ status.info("Running cleanup script...");
5495
+ const cleanupResult = await worktreeManager.runCleanupScript(moduleUid, cleanupScript);
5496
+ if (!cleanupResult.success) {
5497
+ status.warning(`Cleanup script failed: ${cleanupResult.error}`);
5498
+ status.info("Continuing with worktree removal...");
5499
+ } else {
5500
+ status.success("\u2713 Cleanup script completed");
5501
+ }
5502
+ }
5639
5503
  status.info("Removing worktree...");
5640
5504
  const result = await worktreeManager.removeWorktree(moduleUid, options.force);
5641
5505
  if (!result.success) {
@@ -5644,7 +5508,6 @@ Commit or stash your changes first, or use --force to discard them.`
5644
5508
  status.success("\u2713 Worktree removed");
5645
5509
  status.info("");
5646
5510
  try {
5647
- const config = await (0, import_core13.loadConfig)();
5648
5511
  if (config?.access_token) {
5649
5512
  const worktreeConfig = worktreeManager.getConfig();
5650
5513
  if (worktreeConfig) {
@@ -5668,7 +5531,7 @@ Commit or stash your changes first, or use --force to discard them.`
5668
5531
  status.info("");
5669
5532
  }
5670
5533
  async function checkUncommittedChanges(worktreePath) {
5671
- const gitExecutor = new import_core13.GitExecutor();
5534
+ const gitExecutor = new import_core12.GitExecutor();
5672
5535
  const result = await gitExecutor.execute(
5673
5536
  { action: "status" },
5674
5537
  { cwd: worktreePath }
@@ -5693,10 +5556,10 @@ async function updateModuleRelease(apiUrl, projectId, workspaceSlug, moduleUid,
5693
5556
  }
5694
5557
 
5695
5558
  // src/commands/list.ts
5696
- var fs11 = __toESM(require("fs"));
5697
- var path13 = __toESM(require("path"));
5559
+ var fs10 = __toESM(require("fs"));
5560
+ var path12 = __toESM(require("path"));
5698
5561
  var import_chalk2 = __toESM(require("chalk"));
5699
- var import_core14 = __toESM(require_dist());
5562
+ var import_core13 = __toESM(require_dist());
5700
5563
  async function listCommand(subcommand, options = {}) {
5701
5564
  if (subcommand === "worktrees" || subcommand === "wt") {
5702
5565
  await listWorktrees(options);
@@ -5706,7 +5569,7 @@ async function listCommand(subcommand, options = {}) {
5706
5569
  }
5707
5570
  async function listProjects(options) {
5708
5571
  const episodaRoot = getEpisodaRoot();
5709
- if (!fs11.existsSync(episodaRoot)) {
5572
+ if (!fs10.existsSync(episodaRoot)) {
5710
5573
  status.info("No projects cloned yet.");
5711
5574
  status.info("");
5712
5575
  status.info("Clone a project with:");
@@ -5714,12 +5577,12 @@ async function listProjects(options) {
5714
5577
  return;
5715
5578
  }
5716
5579
  const projects = [];
5717
- const workspaces = fs11.readdirSync(episodaRoot, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
5580
+ const workspaces = fs10.readdirSync(episodaRoot, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
5718
5581
  for (const workspace of workspaces) {
5719
- const workspacePath = path13.join(episodaRoot, workspace.name);
5720
- const projectDirs = fs11.readdirSync(workspacePath, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
5582
+ const workspacePath = path12.join(episodaRoot, workspace.name);
5583
+ const projectDirs = fs10.readdirSync(workspacePath, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
5721
5584
  for (const projectDir of projectDirs) {
5722
- const projectPath = path13.join(workspacePath, projectDir.name);
5585
+ const projectPath = path12.join(workspacePath, projectDir.name);
5723
5586
  if (await isWorktreeProject(projectPath)) {
5724
5587
  const manager = new WorktreeManager(projectPath);
5725
5588
  await manager.initialize();
@@ -5784,7 +5647,7 @@ async function listWorktrees(options) {
5784
5647
  status.info(" episoda checkout {moduleUid}");
5785
5648
  return;
5786
5649
  }
5787
- const gitExecutor = new import_core14.GitExecutor();
5650
+ const gitExecutor = new import_core13.GitExecutor();
5788
5651
  for (const wt of worktrees) {
5789
5652
  console.log("");
5790
5653
  let statusIndicator = import_chalk2.default.green("\u25CF");
@@ -5821,7 +5684,7 @@ async function listWorktrees(options) {
5821
5684
  }
5822
5685
 
5823
5686
  // src/index.ts
5824
- import_commander.program.name("episoda").description("Episoda local development workflow orchestration").version(import_core15.VERSION);
5687
+ import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core14.VERSION);
5825
5688
  import_commander.program.command("auth").description("Authenticate to Episoda via OAuth and configure CLI").option("--api-url <url>", "API URL (default: https://episoda.dev)").action(async (options) => {
5826
5689
  try {
5827
5690
  await authCommand(options);
@@ -5838,7 +5701,7 @@ import_commander.program.command("connect").description("Connect using a pre-aut
5838
5701
  process.exit(1);
5839
5702
  }
5840
5703
  });
5841
- import_commander.program.command("dev").description("Start dev server with Episoda orchestration").option("--auto-restart", "Auto-restart dev server if it crashes").option("--connection-only", "Connection-only mode (don't start dev server)").allowUnknownOption().action(async (options, cmd) => {
5704
+ import_commander.program.command("dev").description("Connect to Episoda and start dev server (worktree mode required)").option("--auto-restart", "Auto-restart dev server if it crashes").option("--connection-only", "Connection-only mode (don't start dev server)").allowUnknownOption().action(async (options, cmd) => {
5842
5705
  try {
5843
5706
  const args = process.argv.slice(process.argv.indexOf("dev") + 1);
5844
5707
  const separatorIndex = args.indexOf("--");
@@ -5909,14 +5772,6 @@ import_commander.program.command("list").description("List cloned projects and w
5909
5772
  process.exit(1);
5910
5773
  }
5911
5774
  });
5912
- import_commander.program.command("migrate").description("Convert existing project to worktree mode").option("--force", "Force migration even with uncommitted changes (auto-stash)").option("--dry-run", "Show what would be done without making changes").action(async (options) => {
5913
- try {
5914
- await migrateCommand(options);
5915
- } catch (error) {
5916
- status.error(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
5917
- process.exit(1);
5918
- }
5919
- });
5920
5775
  import_commander.program.command("dev:restart").description("Restart the dev server for a module").argument("[moduleUid]", "Module UID (required)").action(async (moduleUid) => {
5921
5776
  try {
5922
5777
  if (!moduleUid) {