episoda 0.2.105 → 0.2.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1571,15 +1571,15 @@ var require_git_executor = __commonJS({
1571
1571
  try {
1572
1572
  const { stdout: gitDir } = await execAsync3("git rev-parse --git-dir", { cwd, timeout: 5e3 });
1573
1573
  const gitDirPath = gitDir.trim();
1574
- const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1574
+ const fs24 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1575
1575
  const rebaseMergePath = `${gitDirPath}/rebase-merge`;
1576
1576
  const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
1577
1577
  try {
1578
- await fs22.access(rebaseMergePath);
1578
+ await fs24.access(rebaseMergePath);
1579
1579
  inRebase = true;
1580
1580
  } catch {
1581
1581
  try {
1582
- await fs22.access(rebaseApplyPath);
1582
+ await fs24.access(rebaseApplyPath);
1583
1583
  inRebase = true;
1584
1584
  } catch {
1585
1585
  inRebase = false;
@@ -1633,9 +1633,9 @@ var require_git_executor = __commonJS({
1633
1633
  error: validation.error || "UNKNOWN_ERROR"
1634
1634
  };
1635
1635
  }
1636
- const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1636
+ const fs24 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1637
1637
  try {
1638
- await fs22.access(command.path);
1638
+ await fs24.access(command.path);
1639
1639
  return {
1640
1640
  success: false,
1641
1641
  error: "WORKTREE_EXISTS",
@@ -1689,9 +1689,9 @@ var require_git_executor = __commonJS({
1689
1689
  */
1690
1690
  async executeWorktreeRemove(command, cwd, options) {
1691
1691
  try {
1692
- const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1692
+ const fs24 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1693
1693
  try {
1694
- await fs22.access(command.path);
1694
+ await fs24.access(command.path);
1695
1695
  } catch {
1696
1696
  return {
1697
1697
  success: false,
@@ -1726,7 +1726,7 @@ var require_git_executor = __commonJS({
1726
1726
  const result = await this.runGitCommand(args, cwd, options);
1727
1727
  if (result.success) {
1728
1728
  try {
1729
- await fs22.rm(command.path, { recursive: true, force: true });
1729
+ await fs24.rm(command.path, { recursive: true, force: true });
1730
1730
  } catch {
1731
1731
  }
1732
1732
  return {
@@ -1860,10 +1860,10 @@ var require_git_executor = __commonJS({
1860
1860
  */
1861
1861
  async executeCloneBare(command, options) {
1862
1862
  try {
1863
- const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1864
- const path23 = await Promise.resolve().then(() => __importStar(require("path")));
1863
+ const fs24 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1864
+ const path25 = await Promise.resolve().then(() => __importStar(require("path")));
1865
1865
  try {
1866
- await fs22.access(command.path);
1866
+ await fs24.access(command.path);
1867
1867
  return {
1868
1868
  success: false,
1869
1869
  error: "BRANCH_ALREADY_EXISTS",
@@ -1872,9 +1872,9 @@ var require_git_executor = __commonJS({
1872
1872
  };
1873
1873
  } catch {
1874
1874
  }
1875
- const parentDir = path23.dirname(command.path);
1875
+ const parentDir = path25.dirname(command.path);
1876
1876
  try {
1877
- await fs22.mkdir(parentDir, { recursive: true });
1877
+ await fs24.mkdir(parentDir, { recursive: true });
1878
1878
  } catch {
1879
1879
  }
1880
1880
  const { stdout, stderr } = await execAsync3(
@@ -1922,22 +1922,22 @@ var require_git_executor = __commonJS({
1922
1922
  */
1923
1923
  async executeProjectInfo(cwd, options) {
1924
1924
  try {
1925
- const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1926
- const path23 = await Promise.resolve().then(() => __importStar(require("path")));
1925
+ const fs24 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1926
+ const path25 = await Promise.resolve().then(() => __importStar(require("path")));
1927
1927
  let currentPath = cwd;
1928
1928
  let projectPath = cwd;
1929
1929
  let bareRepoPath;
1930
1930
  for (let i = 0; i < 10; i++) {
1931
- const bareDir = path23.join(currentPath, ".bare");
1932
- const episodaDir = path23.join(currentPath, ".episoda");
1931
+ const bareDir = path25.join(currentPath, ".bare");
1932
+ const episodaDir = path25.join(currentPath, ".episoda");
1933
1933
  try {
1934
- await fs22.access(bareDir);
1935
- await fs22.access(episodaDir);
1934
+ await fs24.access(bareDir);
1935
+ await fs24.access(episodaDir);
1936
1936
  projectPath = currentPath;
1937
1937
  bareRepoPath = bareDir;
1938
1938
  break;
1939
1939
  } catch {
1940
- const parentPath = path23.dirname(currentPath);
1940
+ const parentPath = path25.dirname(currentPath);
1941
1941
  if (parentPath === currentPath) {
1942
1942
  break;
1943
1943
  }
@@ -2590,31 +2590,31 @@ var require_auth = __commonJS({
2590
2590
  exports2.loadConfig = loadConfig8;
2591
2591
  exports2.saveConfig = saveConfig2;
2592
2592
  exports2.validateToken = validateToken;
2593
- var fs22 = __importStar(require("fs"));
2594
- var path23 = __importStar(require("path"));
2593
+ var fs24 = __importStar(require("fs"));
2594
+ var path25 = __importStar(require("path"));
2595
2595
  var os9 = __importStar(require("os"));
2596
2596
  var child_process_1 = require("child_process");
2597
2597
  var DEFAULT_CONFIG_FILE = "config.json";
2598
2598
  function getConfigDir8() {
2599
- return process.env.EPISODA_CONFIG_DIR || path23.join(os9.homedir(), ".episoda");
2599
+ return process.env.EPISODA_CONFIG_DIR || path25.join(os9.homedir(), ".episoda");
2600
2600
  }
2601
2601
  function getConfigPath(configPath) {
2602
2602
  if (configPath) {
2603
2603
  return configPath;
2604
2604
  }
2605
- return path23.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
2605
+ return path25.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
2606
2606
  }
2607
2607
  function ensureConfigDir(configPath) {
2608
- const dir = path23.dirname(configPath);
2609
- const isNew = !fs22.existsSync(dir);
2608
+ const dir = path25.dirname(configPath);
2609
+ const isNew = !fs24.existsSync(dir);
2610
2610
  if (isNew) {
2611
- fs22.mkdirSync(dir, { recursive: true, mode: 448 });
2611
+ fs24.mkdirSync(dir, { recursive: true, mode: 448 });
2612
2612
  }
2613
2613
  if (process.platform === "darwin") {
2614
- const nosyncPath = path23.join(dir, ".nosync");
2615
- if (isNew || !fs22.existsSync(nosyncPath)) {
2614
+ const nosyncPath = path25.join(dir, ".nosync");
2615
+ if (isNew || !fs24.existsSync(nosyncPath)) {
2616
2616
  try {
2617
- fs22.writeFileSync(nosyncPath, "", { mode: 384 });
2617
+ fs24.writeFileSync(nosyncPath, "", { mode: 384 });
2618
2618
  (0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
2619
2619
  stdio: "ignore",
2620
2620
  timeout: 5e3
@@ -2626,9 +2626,9 @@ var require_auth = __commonJS({
2626
2626
  }
2627
2627
  async function loadConfig8(configPath) {
2628
2628
  const fullPath = getConfigPath(configPath);
2629
- if (fs22.existsSync(fullPath)) {
2629
+ if (fs24.existsSync(fullPath)) {
2630
2630
  try {
2631
- const content = fs22.readFileSync(fullPath, "utf8");
2631
+ const content = fs24.readFileSync(fullPath, "utf8");
2632
2632
  const config = JSON.parse(content);
2633
2633
  return config;
2634
2634
  } catch (error) {
@@ -2638,9 +2638,9 @@ var require_auth = __commonJS({
2638
2638
  if (process.env.EPISODA_MODE === "cloud" && process.env.EPISODA_WORKSPACE) {
2639
2639
  const homeDir = process.env.HOME || require("os").homedir();
2640
2640
  const workspaceConfigPath = require("path").join(homeDir, "episoda", process.env.EPISODA_WORKSPACE, ".episoda", "config.json");
2641
- if (fs22.existsSync(workspaceConfigPath)) {
2641
+ if (fs24.existsSync(workspaceConfigPath)) {
2642
2642
  try {
2643
- const content = fs22.readFileSync(workspaceConfigPath, "utf8");
2643
+ const content = fs24.readFileSync(workspaceConfigPath, "utf8");
2644
2644
  const workspaceConfig = JSON.parse(content);
2645
2645
  return {
2646
2646
  access_token: process.env.EPISODA_ACCESS_TOKEN || workspaceConfig.accessToken,
@@ -2687,7 +2687,7 @@ var require_auth = __commonJS({
2687
2687
  ensureConfigDir(fullPath);
2688
2688
  try {
2689
2689
  const content = JSON.stringify(config, null, 2);
2690
- fs22.writeFileSync(fullPath, content, { mode: 384 });
2690
+ fs24.writeFileSync(fullPath, content, { mode: 384 });
2691
2691
  } catch (error) {
2692
2692
  throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
2693
2693
  }
@@ -2804,7 +2804,7 @@ var require_package = __commonJS({
2804
2804
  "package.json"(exports2, module2) {
2805
2805
  module2.exports = {
2806
2806
  name: "episoda",
2807
- version: "0.2.105",
2807
+ version: "0.2.106",
2808
2808
  description: "CLI tool for Episoda local development workflow orchestration",
2809
2809
  main: "dist/index.js",
2810
2810
  types: "dist/index.d.ts",
@@ -2843,7 +2843,7 @@ var require_package = __commonJS({
2843
2843
  "@modelcontextprotocol/server-github": "^0.6.0"
2844
2844
  },
2845
2845
  devDependencies: {
2846
- "@episoda/core": "*",
2846
+ "@episoda/core": "workspace:*",
2847
2847
  "@types/node": "^20.11.24",
2848
2848
  "@types/semver": "7.7.1",
2849
2849
  "@types/tar": "6.1.13",
@@ -4058,8 +4058,8 @@ async function handleExec(command, projectPath) {
4058
4058
  }
4059
4059
 
4060
4060
  // src/daemon/handlers/worktree-handlers.ts
4061
- var path15 = __toESM(require("path"));
4062
- var fs14 = __toESM(require("fs"));
4061
+ var path16 = __toESM(require("path"));
4062
+ var fs15 = __toESM(require("fs"));
4063
4063
  var import_child_process8 = require("child_process");
4064
4064
  var import_util = require("util");
4065
4065
 
@@ -4781,7 +4781,13 @@ var WorktreeManager = class _WorktreeManager {
4781
4781
  }
4782
4782
  };
4783
4783
  function getEpisodaRoot() {
4784
- return process.env.EPISODA_ROOT || path7.join(require("os").homedir(), "episoda");
4784
+ if (process.env.EPISODA_ROOT) {
4785
+ return process.env.EPISODA_ROOT;
4786
+ }
4787
+ if (process.env.EPISODA_MODE === "cloud") {
4788
+ return process.env.HOME || "/home/episoda";
4789
+ }
4790
+ return path7.join(require("os").homedir(), "episoda");
4785
4791
  }
4786
4792
  function getProjectPath(workspaceSlug, projectSlug) {
4787
4793
  return path7.join(getEpisodaRoot(), workspaceSlug, projectSlug);
@@ -7326,6 +7332,144 @@ async function deleteWorktree(config, moduleUid) {
7326
7332
  }
7327
7333
  }
7328
7334
 
7335
+ // src/daemon/package-manager.ts
7336
+ var fs14 = __toESM(require("fs"));
7337
+ var path15 = __toESM(require("path"));
7338
+ var PACKAGE_MANAGERS = {
7339
+ javascript: [
7340
+ {
7341
+ name: "pnpm",
7342
+ detector: (p) => {
7343
+ if (fs14.existsSync(path15.join(p, "pnpm-lock.yaml"))) {
7344
+ return "pnpm-lock.yaml";
7345
+ }
7346
+ const pkgJsonPath = path15.join(p, "package.json");
7347
+ if (fs14.existsSync(pkgJsonPath)) {
7348
+ try {
7349
+ const pkg = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf-8"));
7350
+ if (pkg.packageManager?.startsWith("pnpm")) {
7351
+ return "package.json (packageManager field)";
7352
+ }
7353
+ } catch {
7354
+ }
7355
+ }
7356
+ return null;
7357
+ },
7358
+ config: {
7359
+ installCmd: "pnpm install --frozen-lockfile",
7360
+ cacheEnvVar: "PNPM_HOME"
7361
+ }
7362
+ },
7363
+ {
7364
+ name: "yarn",
7365
+ detector: (p) => fs14.existsSync(path15.join(p, "yarn.lock")) ? "yarn.lock" : null,
7366
+ config: {
7367
+ installCmd: "yarn install --frozen-lockfile",
7368
+ cacheEnvVar: "YARN_CACHE_FOLDER"
7369
+ }
7370
+ },
7371
+ {
7372
+ name: "bun",
7373
+ detector: (p) => fs14.existsSync(path15.join(p, "bun.lockb")) ? "bun.lockb" : null,
7374
+ config: {
7375
+ installCmd: "bun install --frozen-lockfile"
7376
+ }
7377
+ },
7378
+ {
7379
+ name: "npm",
7380
+ detector: (p) => fs14.existsSync(path15.join(p, "package-lock.json")) ? "package-lock.json" : null,
7381
+ config: {
7382
+ installCmd: "npm ci"
7383
+ }
7384
+ },
7385
+ {
7386
+ // EP1222: Default to pnpm for new projects without lockfile
7387
+ // This encourages standardization on pnpm across Episoda projects
7388
+ name: "pnpm",
7389
+ detector: (p) => fs14.existsSync(path15.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
7390
+ config: {
7391
+ installCmd: "pnpm install",
7392
+ cacheEnvVar: "PNPM_HOME"
7393
+ }
7394
+ }
7395
+ ],
7396
+ python: [
7397
+ {
7398
+ name: "uv",
7399
+ detector: (p) => {
7400
+ const pyprojectPath = path15.join(p, "pyproject.toml");
7401
+ if (fs14.existsSync(path15.join(p, "uv.lock"))) {
7402
+ return "uv.lock";
7403
+ }
7404
+ if (fs14.existsSync(pyprojectPath)) {
7405
+ try {
7406
+ const content = fs14.readFileSync(pyprojectPath, "utf-8");
7407
+ if (content.includes("[tool.uv]") || content.includes("[project]")) {
7408
+ return "pyproject.toml";
7409
+ }
7410
+ } catch {
7411
+ }
7412
+ }
7413
+ return null;
7414
+ },
7415
+ config: {
7416
+ installCmd: "uv sync",
7417
+ cacheEnvVar: "UV_CACHE_DIR"
7418
+ }
7419
+ },
7420
+ {
7421
+ name: "pip",
7422
+ detector: (p) => fs14.existsSync(path15.join(p, "requirements.txt")) ? "requirements.txt" : null,
7423
+ config: {
7424
+ installCmd: "pip install -r requirements.txt"
7425
+ }
7426
+ }
7427
+ ]
7428
+ // Future: Add more languages here
7429
+ // rust: [
7430
+ // {
7431
+ // name: 'cargo',
7432
+ // detector: (p) => fs.existsSync(path.join(p, 'Cargo.toml')) ? 'Cargo.toml' : null,
7433
+ // config: { installCmd: 'cargo build' }
7434
+ // }
7435
+ // ],
7436
+ // go: [
7437
+ // {
7438
+ // name: 'go',
7439
+ // detector: (p) => fs.existsSync(path.join(p, 'go.mod')) ? 'go.mod' : null,
7440
+ // config: { installCmd: 'go mod download' }
7441
+ // }
7442
+ // ],
7443
+ };
7444
+ function detectPackageManager(worktreePath, preferredLanguages) {
7445
+ const checkedFiles = [];
7446
+ const languages = preferredLanguages || Object.keys(PACKAGE_MANAGERS);
7447
+ for (const language of languages) {
7448
+ const managers = PACKAGE_MANAGERS[language];
7449
+ if (!managers) continue;
7450
+ for (const { name, detector, config } of managers) {
7451
+ const matchedFile = detector(worktreePath);
7452
+ if (matchedFile) {
7453
+ checkedFiles.push(matchedFile);
7454
+ return {
7455
+ detected: true,
7456
+ packageManager: {
7457
+ name,
7458
+ language,
7459
+ ...config
7460
+ },
7461
+ checkedFiles,
7462
+ matchedFile
7463
+ };
7464
+ }
7465
+ }
7466
+ }
7467
+ return {
7468
+ detected: false,
7469
+ checkedFiles
7470
+ };
7471
+ }
7472
+
7329
7473
  // src/daemon/handlers/worktree-handlers.ts
7330
7474
  async function getConfigForApi() {
7331
7475
  if (process.env.EPISODA_MODE === "cloud" && process.env.EPISODA_ACCESS_TOKEN) {
@@ -7344,6 +7488,16 @@ async function getConfigForApi() {
7344
7488
  return (0, import_core9.loadConfig)();
7345
7489
  }
7346
7490
  var execAsync = (0, import_util.promisify)(import_child_process8.exec);
7491
+ async function autoDetectSetupScript(worktreePath) {
7492
+ const detection = detectPackageManager(worktreePath);
7493
+ if (!detection.detected || !detection.packageManager) {
7494
+ console.log(`[Worktree] EP1222: No package manager detected in ${worktreePath}`);
7495
+ return null;
7496
+ }
7497
+ const { name, language, installCmd } = detection.packageManager;
7498
+ console.log(`[Worktree] EP1222: Detected ${name} (${language}) via ${detection.matchedFile}`);
7499
+ return installCmd;
7500
+ }
7347
7501
  async function handleWorktreeCreate(request2) {
7348
7502
  const {
7349
7503
  workspaceSlug,
@@ -7368,18 +7522,18 @@ async function handleWorktreeCreate(request2) {
7368
7522
  }
7369
7523
  try {
7370
7524
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
7371
- const bareRepoPath = path15.join(projectPath, ".bare");
7372
- if (!fs14.existsSync(bareRepoPath)) {
7525
+ const bareRepoPath = path16.join(projectPath, ".bare");
7526
+ if (!fs15.existsSync(bareRepoPath)) {
7373
7527
  console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
7374
7528
  console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
7375
- const episodaDir = path15.join(projectPath, ".episoda");
7376
- fs14.mkdirSync(episodaDir, { recursive: true });
7529
+ const episodaDir = path16.join(projectPath, ".episoda");
7530
+ fs15.mkdirSync(episodaDir, { recursive: true });
7377
7531
  try {
7378
7532
  console.log(`[Worktree] K1273: Starting git clone...`);
7379
7533
  await execAsync(`git clone --bare "${repoUrl}" "${bareRepoPath}"`);
7380
7534
  console.log(`[Worktree] K1273: Clone successful`);
7381
7535
  await execAsync(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`);
7382
- const configPath = path15.join(episodaDir, "config.json");
7536
+ const configPath = path16.join(episodaDir, "config.json");
7383
7537
  const config2 = {
7384
7538
  projectId,
7385
7539
  workspaceSlug,
@@ -7388,7 +7542,7 @@ async function handleWorktreeCreate(request2) {
7388
7542
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7389
7543
  worktrees: []
7390
7544
  };
7391
- fs14.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
7545
+ fs15.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
7392
7546
  console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
7393
7547
  } catch (cloneError) {
7394
7548
  console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
@@ -7436,11 +7590,12 @@ async function handleWorktreeCreate(request2) {
7436
7590
  }
7437
7591
  if (envVars && Object.keys(envVars).length > 0) {
7438
7592
  const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
7439
- const envPath = path15.join(worktreePath, ".env");
7440
- fs14.writeFileSync(envPath, envContent + "\n", "utf-8");
7593
+ const envPath = path16.join(worktreePath, ".env");
7594
+ fs15.writeFileSync(envPath, envContent + "\n", "utf-8");
7441
7595
  console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
7442
7596
  }
7443
- if (setupScript) {
7597
+ const effectiveSetupScript = setupScript || await autoDetectSetupScript(worktreePath);
7598
+ if (effectiveSetupScript) {
7444
7599
  await manager.updateWorktreeStatus(moduleUid, "setup");
7445
7600
  if (config?.access_token) {
7446
7601
  await updateWorktree(config, {
@@ -7452,7 +7607,7 @@ async function handleWorktreeCreate(request2) {
7452
7607
  });
7453
7608
  }
7454
7609
  console.log(`[Worktree] EP1143: Running setup script...`);
7455
- const scriptResult = await manager.runSetupScript(moduleUid, setupScript);
7610
+ const scriptResult = await manager.runSetupScript(moduleUid, effectiveSetupScript);
7456
7611
  if (!scriptResult.success) {
7457
7612
  console.error(`[Worktree] EP1143: Setup script failed: ${scriptResult.error}`);
7458
7613
  await manager.updateWorktreeStatus(moduleUid, "error", scriptResult.error);
@@ -7610,12 +7765,12 @@ async function handleProjectEject(request2) {
7610
7765
  console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
7611
7766
  try {
7612
7767
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
7613
- const bareRepoPath = path15.join(projectPath, ".bare");
7614
- if (!fs14.existsSync(projectPath)) {
7768
+ const bareRepoPath = path16.join(projectPath, ".bare");
7769
+ if (!fs15.existsSync(projectPath)) {
7615
7770
  console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
7616
7771
  return { success: true };
7617
7772
  }
7618
- if (!fs14.existsSync(bareRepoPath)) {
7773
+ if (!fs15.existsSync(bareRepoPath)) {
7619
7774
  console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
7620
7775
  return { success: true };
7621
7776
  }
@@ -7630,12 +7785,12 @@ async function handleProjectEject(request2) {
7630
7785
  };
7631
7786
  }
7632
7787
  }
7633
- const artifactsPath = path15.join(projectPath, "artifacts");
7634
- if (fs14.existsSync(artifactsPath)) {
7788
+ const artifactsPath = path16.join(projectPath, "artifacts");
7789
+ if (fs15.existsSync(artifactsPath)) {
7635
7790
  await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
7636
7791
  }
7637
7792
  console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
7638
- await fs14.promises.rm(projectPath, { recursive: true, force: true });
7793
+ await fs15.promises.rm(projectPath, { recursive: true, force: true });
7639
7794
  console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
7640
7795
  return { success: true };
7641
7796
  } catch (error) {
@@ -7649,7 +7804,7 @@ async function handleProjectEject(request2) {
7649
7804
  async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
7650
7805
  const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
7651
7806
  try {
7652
- const files = await fs14.promises.readdir(artifactsPath);
7807
+ const files = await fs15.promises.readdir(artifactsPath);
7653
7808
  if (files.length === 0) {
7654
7809
  return;
7655
7810
  }
@@ -7663,8 +7818,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
7663
7818
  }
7664
7819
  const artifacts = [];
7665
7820
  for (const fileName of files) {
7666
- const filePath = path15.join(artifactsPath, fileName);
7667
- const stat = await fs14.promises.stat(filePath);
7821
+ const filePath = path16.join(artifactsPath, fileName);
7822
+ const stat = await fs15.promises.stat(filePath);
7668
7823
  if (stat.isDirectory()) {
7669
7824
  continue;
7670
7825
  }
@@ -7673,9 +7828,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
7673
7828
  continue;
7674
7829
  }
7675
7830
  try {
7676
- const content = await fs14.promises.readFile(filePath);
7831
+ const content = await fs15.promises.readFile(filePath);
7677
7832
  const base64Content = content.toString("base64");
7678
- const ext = path15.extname(fileName).toLowerCase();
7833
+ const ext = path16.extname(fileName).toLowerCase();
7679
7834
  const mimeTypes = {
7680
7835
  ".json": "application/json",
7681
7836
  ".txt": "text/plain",
@@ -7730,6 +7885,85 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
7730
7885
  }
7731
7886
  }
7732
7887
 
7888
+ // src/daemon/handlers/project-handlers.ts
7889
+ var path17 = __toESM(require("path"));
7890
+ var fs16 = __toESM(require("fs"));
7891
+ function validateSlug(slug, fieldName) {
7892
+ if (!slug || typeof slug !== "string") {
7893
+ return `${fieldName} is required`;
7894
+ }
7895
+ const trimmed = slug.trim();
7896
+ if (!trimmed) {
7897
+ return `${fieldName} cannot be empty`;
7898
+ }
7899
+ if (trimmed.includes("..") || trimmed.includes("/") || trimmed.includes("\\")) {
7900
+ return `${fieldName} contains invalid characters`;
7901
+ }
7902
+ if (trimmed.includes("\0")) {
7903
+ return `${fieldName} contains invalid characters`;
7904
+ }
7905
+ if (trimmed.startsWith(".")) {
7906
+ return `${fieldName} cannot start with a dot`;
7907
+ }
7908
+ return null;
7909
+ }
7910
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7911
+ async function handleProjectSetup(params) {
7912
+ const { workspaceSlug, projectSlug, projectId } = params;
7913
+ const workspaceError = validateSlug(workspaceSlug, "workspaceSlug");
7914
+ if (workspaceError) {
7915
+ console.error(`[ProjectSetup] EP1199: Validation failed: ${workspaceError}`);
7916
+ return { success: false, error: workspaceError };
7917
+ }
7918
+ const projectError = validateSlug(projectSlug, "projectSlug");
7919
+ if (projectError) {
7920
+ console.error(`[ProjectSetup] EP1199: Validation failed: ${projectError}`);
7921
+ return { success: false, error: projectError };
7922
+ }
7923
+ if (!UUID_REGEX.test(projectId)) {
7924
+ console.error(`[ProjectSetup] EP1199: Invalid projectId format: ${projectId}`);
7925
+ return { success: false, error: "Invalid projectId format" };
7926
+ }
7927
+ console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
7928
+ try {
7929
+ const projectPath = getProjectPath(workspaceSlug, projectSlug);
7930
+ const artifactsPath = path17.join(projectPath, "artifacts");
7931
+ const configDir = path17.join(projectPath, ".episoda");
7932
+ const configPath = path17.join(configDir, "config.json");
7933
+ await fs16.promises.mkdir(artifactsPath, { recursive: true });
7934
+ await fs16.promises.mkdir(configDir, { recursive: true });
7935
+ let existingConfig = {};
7936
+ try {
7937
+ const existing = await fs16.promises.readFile(configPath, "utf-8");
7938
+ existingConfig = JSON.parse(existing);
7939
+ } catch {
7940
+ }
7941
+ const config = {
7942
+ ...existingConfig,
7943
+ project_id: projectId,
7944
+ workspace_slug: workspaceSlug,
7945
+ project_slug: projectSlug,
7946
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
7947
+ // Only set created_at if not already present
7948
+ created_at: existingConfig.created_at || (/* @__PURE__ */ new Date()).toISOString()
7949
+ };
7950
+ await fs16.promises.writeFile(configPath, JSON.stringify(config, null, 2));
7951
+ console.log(`[ProjectSetup] EP1199: Project setup complete at ${projectPath}`);
7952
+ return {
7953
+ success: true,
7954
+ projectPath,
7955
+ artifactsPath
7956
+ };
7957
+ } catch (error) {
7958
+ const errorMessage = error instanceof Error ? error.message : String(error);
7959
+ console.error(`[ProjectSetup] EP1199: Setup failed:`, errorMessage);
7960
+ return {
7961
+ success: false,
7962
+ error: errorMessage
7963
+ };
7964
+ }
7965
+ }
7966
+
7733
7967
  // src/daemon/handlers/stale-commit-cleanup.ts
7734
7968
  var import_child_process9 = require("child_process");
7735
7969
  var import_util2 = require("util");
@@ -7827,12 +8061,12 @@ async function cleanupStaleCommits(projectPath) {
7827
8061
 
7828
8062
  // src/agent/claude-binary.ts
7829
8063
  var import_child_process10 = require("child_process");
7830
- var path16 = __toESM(require("path"));
7831
- var fs15 = __toESM(require("fs"));
8064
+ var path18 = __toESM(require("path"));
8065
+ var fs17 = __toESM(require("fs"));
7832
8066
  var cachedBinaryPath = null;
7833
8067
  function isValidClaudeBinary(binaryPath) {
7834
8068
  try {
7835
- fs15.accessSync(binaryPath, fs15.constants.X_OK);
8069
+ fs17.accessSync(binaryPath, fs17.constants.X_OK);
7836
8070
  const version = (0, import_child_process10.execSync)(`"${binaryPath}" --version`, {
7837
8071
  encoding: "utf-8",
7838
8072
  timeout: 5e3,
@@ -7865,14 +8099,14 @@ async function ensureClaudeBinary() {
7865
8099
  }
7866
8100
  const bundledPaths = [
7867
8101
  // In production: node_modules/.bin/claude
7868
- path16.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
8102
+ path18.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
7869
8103
  // In monorepo development: packages/episoda/node_modules/.bin/claude
7870
- path16.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
8104
+ path18.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
7871
8105
  // Root monorepo node_modules
7872
- path16.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
8106
+ path18.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
7873
8107
  ];
7874
8108
  for (const bundledPath of bundledPaths) {
7875
- if (fs15.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
8109
+ if (fs17.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
7876
8110
  cachedBinaryPath = bundledPath;
7877
8111
  return cachedBinaryPath;
7878
8112
  }
@@ -7898,12 +8132,12 @@ async function ensureClaudeBinary() {
7898
8132
 
7899
8133
  // src/agent/codex-binary.ts
7900
8134
  var import_child_process11 = require("child_process");
7901
- var path17 = __toESM(require("path"));
7902
- var fs16 = __toESM(require("fs"));
8135
+ var path19 = __toESM(require("path"));
8136
+ var fs18 = __toESM(require("fs"));
7903
8137
  var cachedBinaryPath2 = null;
7904
8138
  function isValidCodexBinary(binaryPath) {
7905
8139
  try {
7906
- fs16.accessSync(binaryPath, fs16.constants.X_OK);
8140
+ fs18.accessSync(binaryPath, fs18.constants.X_OK);
7907
8141
  const version = (0, import_child_process11.execSync)(`"${binaryPath}" --version`, {
7908
8142
  encoding: "utf-8",
7909
8143
  timeout: 5e3,
@@ -7936,14 +8170,14 @@ async function ensureCodexBinary() {
7936
8170
  }
7937
8171
  const bundledPaths = [
7938
8172
  // In production: node_modules/.bin/codex
7939
- path17.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
8173
+ path19.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
7940
8174
  // In monorepo development: packages/episoda/node_modules/.bin/codex
7941
- path17.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
8175
+ path19.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
7942
8176
  // Root monorepo node_modules
7943
- path17.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
8177
+ path19.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
7944
8178
  ];
7945
8179
  for (const bundledPath of bundledPaths) {
7946
- if (fs16.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
8180
+ if (fs18.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
7947
8181
  cachedBinaryPath2 = bundledPath;
7948
8182
  return cachedBinaryPath2;
7949
8183
  }
@@ -8010,8 +8244,8 @@ function generateCodexConfig(credentials, projectPath) {
8010
8244
 
8011
8245
  // src/agent/agent-manager.ts
8012
8246
  var import_child_process12 = require("child_process");
8013
- var path18 = __toESM(require("path"));
8014
- var fs17 = __toESM(require("fs"));
8247
+ var path20 = __toESM(require("path"));
8248
+ var fs19 = __toESM(require("fs"));
8015
8249
  var os6 = __toESM(require("os"));
8016
8250
 
8017
8251
  // src/agent/claude-config.ts
@@ -8334,7 +8568,7 @@ var AgentManager = class {
8334
8568
  this.initialized = false;
8335
8569
  // EP1133: Lock for config file writes to prevent race conditions
8336
8570
  this.configWriteLock = Promise.resolve();
8337
- this.pidDir = path18.join(os6.homedir(), ".episoda", "agent-pids");
8571
+ this.pidDir = path20.join(os6.homedir(), ".episoda", "agent-pids");
8338
8572
  }
8339
8573
  /**
8340
8574
  * EP1133: Acquire lock for config file writes
@@ -8363,8 +8597,8 @@ var AgentManager = class {
8363
8597
  return;
8364
8598
  }
8365
8599
  console.log("[AgentManager] Initializing...");
8366
- if (!fs17.existsSync(this.pidDir)) {
8367
- fs17.mkdirSync(this.pidDir, { recursive: true });
8600
+ if (!fs19.existsSync(this.pidDir)) {
8601
+ fs19.mkdirSync(this.pidDir, { recursive: true });
8368
8602
  }
8369
8603
  await this.cleanupOrphanedProcesses();
8370
8604
  try {
@@ -8611,9 +8845,9 @@ If changes are needed, explain what needs to be done.`;
8611
8845
  const useApiKey = !useOAuth && !!session.credentials.apiKey;
8612
8846
  if (provider === "codex") {
8613
8847
  await this.withConfigLock(async () => {
8614
- const codexDir = path18.join(os6.homedir(), ".codex");
8615
- if (!fs17.existsSync(codexDir)) {
8616
- fs17.mkdirSync(codexDir, { recursive: true });
8848
+ const codexDir = path20.join(os6.homedir(), ".codex");
8849
+ if (!fs19.existsSync(codexDir)) {
8850
+ fs19.mkdirSync(codexDir, { recursive: true });
8617
8851
  }
8618
8852
  if (useOAuth) {
8619
8853
  const codexConfig = generateCodexConfig({
@@ -8623,21 +8857,21 @@ If changes are needed, explain what needs to be done.`;
8623
8857
  accountId: session.credentials.accountId,
8624
8858
  expiresAt: session.credentials.expiresAt
8625
8859
  }, session.projectPath);
8626
- const authJsonPath = path18.join(codexDir, "auth.json");
8627
- fs17.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
8860
+ const authJsonPath = path20.join(codexDir, "auth.json");
8861
+ fs19.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
8628
8862
  console.log("[AgentManager] EP1133: Wrote Codex auth.json to ~/.codex/auth.json");
8629
8863
  if (codexConfig["config.toml"]) {
8630
- const configTomlPath = path18.join(codexDir, "config.toml");
8864
+ const configTomlPath = path20.join(codexDir, "config.toml");
8631
8865
  let existingConfig = "";
8632
8866
  try {
8633
- existingConfig = fs17.readFileSync(configTomlPath, "utf-8");
8867
+ existingConfig = fs19.readFileSync(configTomlPath, "utf-8");
8634
8868
  } catch {
8635
8869
  }
8636
8870
  const escapedPathForRegex = session.projectPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8637
8871
  const projectKeyPattern = new RegExp(`\\[projects\\."${escapedPathForRegex}"\\]`);
8638
8872
  const projectAlreadyTrusted = projectKeyPattern.test(existingConfig);
8639
8873
  if (!projectAlreadyTrusted) {
8640
- fs17.writeFileSync(configTomlPath, existingConfig + "\n" + codexConfig["config.toml"], { mode: 420 });
8874
+ fs19.writeFileSync(configTomlPath, existingConfig + "\n" + codexConfig["config.toml"], { mode: 420 });
8641
8875
  console.log("[AgentManager] EP1133: Updated Codex config.toml with project trust");
8642
8876
  }
8643
8877
  }
@@ -8647,14 +8881,14 @@ If changes are needed, explain what needs to be done.`;
8647
8881
  });
8648
8882
  } else {
8649
8883
  await this.withConfigLock(async () => {
8650
- const claudeDir = path18.join(os6.homedir(), ".claude");
8651
- const credentialsPath = path18.join(claudeDir, ".credentials.json");
8652
- const statsigDir = path18.join(claudeDir, "statsig");
8653
- if (!fs17.existsSync(claudeDir)) {
8654
- fs17.mkdirSync(claudeDir, { recursive: true });
8884
+ const claudeDir = path20.join(os6.homedir(), ".claude");
8885
+ const credentialsPath = path20.join(claudeDir, ".credentials.json");
8886
+ const statsigDir = path20.join(claudeDir, "statsig");
8887
+ if (!fs19.existsSync(claudeDir)) {
8888
+ fs19.mkdirSync(claudeDir, { recursive: true });
8655
8889
  }
8656
- if (!fs17.existsSync(statsigDir)) {
8657
- fs17.mkdirSync(statsigDir, { recursive: true });
8890
+ if (!fs19.existsSync(statsigDir)) {
8891
+ fs19.mkdirSync(statsigDir, { recursive: true });
8658
8892
  }
8659
8893
  if (useOAuth) {
8660
8894
  const oauthCredentials = {
@@ -8672,7 +8906,7 @@ If changes are needed, explain what needs to be done.`;
8672
8906
  const credentialsContent = JSON.stringify({
8673
8907
  claudeAiOauth: oauthCredentials
8674
8908
  }, null, 2);
8675
- fs17.writeFileSync(credentialsPath, credentialsContent, { mode: 384 });
8909
+ fs19.writeFileSync(credentialsPath, credentialsContent, { mode: 384 });
8676
8910
  console.log("[AgentManager] Wrote OAuth credentials to ~/.claude/.credentials.json");
8677
8911
  try {
8678
8912
  const claudeConfig = generateClaudeConfig({
@@ -8684,11 +8918,11 @@ If changes are needed, explain what needs to be done.`;
8684
8918
  if (!hasEvaluations || !hasStableId) {
8685
8919
  throw new Error(`Invalid statsig config: missing required files`);
8686
8920
  }
8687
- const settingsPath = path18.join(claudeDir, "settings.json");
8688
- fs17.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
8921
+ const settingsPath = path20.join(claudeDir, "settings.json");
8922
+ fs19.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
8689
8923
  for (const [filename, content] of Object.entries(claudeConfig.statsig)) {
8690
- const filePath = path18.join(statsigDir, filename);
8691
- fs17.writeFileSync(filePath, content, { mode: 420 });
8924
+ const filePath = path20.join(statsigDir, filename);
8925
+ fs19.writeFileSync(filePath, content, { mode: 420 });
8692
8926
  }
8693
8927
  if (session.credentials.githubToken) {
8694
8928
  console.log("[AgentManager] EP1146: GitHub MCP server enabled with installation token");
@@ -8958,14 +9192,14 @@ If changes are needed, explain what needs to be done.`;
8958
9192
  */
8959
9193
  async cleanupOrphanedProcesses() {
8960
9194
  let cleaned = 0;
8961
- if (!fs17.existsSync(this.pidDir)) {
9195
+ if (!fs19.existsSync(this.pidDir)) {
8962
9196
  return { cleaned };
8963
9197
  }
8964
- const pidFiles = fs17.readdirSync(this.pidDir).filter((f) => f.endsWith(".pid"));
9198
+ const pidFiles = fs19.readdirSync(this.pidDir).filter((f) => f.endsWith(".pid"));
8965
9199
  for (const pidFile of pidFiles) {
8966
- const pidPath = path18.join(this.pidDir, pidFile);
9200
+ const pidPath = path20.join(this.pidDir, pidFile);
8967
9201
  try {
8968
- const pidStr = fs17.readFileSync(pidPath, "utf-8").trim();
9202
+ const pidStr = fs19.readFileSync(pidPath, "utf-8").trim();
8969
9203
  const pid = parseInt(pidStr, 10);
8970
9204
  if (!isNaN(pid)) {
8971
9205
  try {
@@ -8976,7 +9210,7 @@ If changes are needed, explain what needs to be done.`;
8976
9210
  } catch {
8977
9211
  }
8978
9212
  }
8979
- fs17.unlinkSync(pidPath);
9213
+ fs19.unlinkSync(pidPath);
8980
9214
  } catch (error) {
8981
9215
  console.warn(`[AgentManager] Error cleaning PID file ${pidFile}:`, error);
8982
9216
  }
@@ -8990,17 +9224,17 @@ If changes are needed, explain what needs to be done.`;
8990
9224
  * Write PID file for session tracking
8991
9225
  */
8992
9226
  writePidFile(sessionId, pid) {
8993
- const pidPath = path18.join(this.pidDir, `${sessionId}.pid`);
8994
- fs17.writeFileSync(pidPath, pid.toString());
9227
+ const pidPath = path20.join(this.pidDir, `${sessionId}.pid`);
9228
+ fs19.writeFileSync(pidPath, pid.toString());
8995
9229
  }
8996
9230
  /**
8997
9231
  * Remove PID file for session
8998
9232
  */
8999
9233
  removePidFile(sessionId) {
9000
- const pidPath = path18.join(this.pidDir, `${sessionId}.pid`);
9234
+ const pidPath = path20.join(this.pidDir, `${sessionId}.pid`);
9001
9235
  try {
9002
- if (fs17.existsSync(pidPath)) {
9003
- fs17.unlinkSync(pidPath);
9236
+ if (fs19.existsSync(pidPath)) {
9237
+ fs19.unlinkSync(pidPath);
9004
9238
  }
9005
9239
  } catch {
9006
9240
  }
@@ -9010,8 +9244,8 @@ If changes are needed, explain what needs to be done.`;
9010
9244
  // src/utils/dev-server.ts
9011
9245
  var import_child_process13 = require("child_process");
9012
9246
  var import_core11 = __toESM(require_dist());
9013
- var fs18 = __toESM(require("fs"));
9014
- var path19 = __toESM(require("path"));
9247
+ var fs20 = __toESM(require("fs"));
9248
+ var path21 = __toESM(require("path"));
9015
9249
  var MAX_RESTART_ATTEMPTS = 5;
9016
9250
  var INITIAL_RESTART_DELAY_MS = 2e3;
9017
9251
  var MAX_RESTART_DELAY_MS = 3e4;
@@ -9019,26 +9253,26 @@ var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
9019
9253
  var NODE_MEMORY_LIMIT_MB = 2048;
9020
9254
  var activeServers = /* @__PURE__ */ new Map();
9021
9255
  function getLogsDir() {
9022
- const logsDir = path19.join((0, import_core11.getConfigDir)(), "logs");
9023
- if (!fs18.existsSync(logsDir)) {
9024
- fs18.mkdirSync(logsDir, { recursive: true });
9256
+ const logsDir = path21.join((0, import_core11.getConfigDir)(), "logs");
9257
+ if (!fs20.existsSync(logsDir)) {
9258
+ fs20.mkdirSync(logsDir, { recursive: true });
9025
9259
  }
9026
9260
  return logsDir;
9027
9261
  }
9028
9262
  function getLogFilePath(moduleUid) {
9029
- return path19.join(getLogsDir(), `dev-${moduleUid}.log`);
9263
+ return path21.join(getLogsDir(), `dev-${moduleUid}.log`);
9030
9264
  }
9031
9265
  function rotateLogIfNeeded(logPath) {
9032
9266
  try {
9033
- if (fs18.existsSync(logPath)) {
9034
- const stats = fs18.statSync(logPath);
9267
+ if (fs20.existsSync(logPath)) {
9268
+ const stats = fs20.statSync(logPath);
9035
9269
  if (stats.size > MAX_LOG_SIZE_BYTES) {
9036
9270
  const backupPath = `${logPath}.1`;
9037
- if (fs18.existsSync(backupPath)) {
9038
- fs18.unlinkSync(backupPath);
9271
+ if (fs20.existsSync(backupPath)) {
9272
+ fs20.unlinkSync(backupPath);
9039
9273
  }
9040
- fs18.renameSync(logPath, backupPath);
9041
- console.log(`[DevServer] EP932: Rotated log file for ${path19.basename(logPath)}`);
9274
+ fs20.renameSync(logPath, backupPath);
9275
+ console.log(`[DevServer] EP932: Rotated log file for ${path21.basename(logPath)}`);
9042
9276
  }
9043
9277
  }
9044
9278
  } catch (error) {
@@ -9051,7 +9285,7 @@ function writeToLog(logPath, line, isError = false) {
9051
9285
  const prefix = isError ? "ERR" : "OUT";
9052
9286
  const logLine = `[${timestamp}] [${prefix}] ${line}
9053
9287
  `;
9054
- fs18.appendFileSync(logPath, logLine);
9288
+ fs20.appendFileSync(logPath, logLine);
9055
9289
  } catch {
9056
9290
  }
9057
9291
  }
@@ -9230,8 +9464,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
9230
9464
  });
9231
9465
  injectedEnvVars = result.envVars;
9232
9466
  console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
9233
- const envFilePath = path19.join(projectPath, ".env");
9234
- if (!fs18.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
9467
+ const envFilePath = path21.join(projectPath, ".env");
9468
+ if (!fs20.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
9235
9469
  console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
9236
9470
  writeEnvFile(projectPath, injectedEnvVars);
9237
9471
  }
@@ -9337,19 +9571,19 @@ function getDevServerStatus() {
9337
9571
  }
9338
9572
 
9339
9573
  // src/utils/worktree.ts
9340
- var path20 = __toESM(require("path"));
9341
- var fs19 = __toESM(require("fs"));
9574
+ var path22 = __toESM(require("path"));
9575
+ var fs21 = __toESM(require("fs"));
9342
9576
  var os7 = __toESM(require("os"));
9343
9577
  var import_core12 = __toESM(require_dist());
9344
9578
  function getEpisodaRoot2() {
9345
- return process.env.EPISODA_ROOT || path20.join(os7.homedir(), "episoda");
9579
+ return process.env.EPISODA_ROOT || path22.join(os7.homedir(), "episoda");
9346
9580
  }
9347
9581
  function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
9348
9582
  const root = getEpisodaRoot2();
9349
- const worktreePath = path20.join(root, workspaceSlug, projectSlug, moduleUid);
9583
+ const worktreePath = path22.join(root, workspaceSlug, projectSlug, moduleUid);
9350
9584
  return {
9351
9585
  path: worktreePath,
9352
- exists: fs19.existsSync(worktreePath),
9586
+ exists: fs21.existsSync(worktreePath),
9353
9587
  moduleUid
9354
9588
  };
9355
9589
  }
@@ -9363,15 +9597,15 @@ async function getWorktreeInfoForModule(moduleUid) {
9363
9597
  return null;
9364
9598
  }
9365
9599
  const root = getEpisodaRoot2();
9366
- const workspaceRoot = path20.join(root, config.workspace_slug);
9600
+ const workspaceRoot = path22.join(root, config.workspace_slug);
9367
9601
  try {
9368
- const entries = fs19.readdirSync(workspaceRoot, { withFileTypes: true });
9602
+ const entries = fs21.readdirSync(workspaceRoot, { withFileTypes: true });
9369
9603
  for (const entry of entries) {
9370
9604
  if (!entry.isDirectory()) {
9371
9605
  continue;
9372
9606
  }
9373
- const worktreePath = path20.join(workspaceRoot, entry.name, moduleUid);
9374
- if (fs19.existsSync(worktreePath)) {
9607
+ const worktreePath = path22.join(workspaceRoot, entry.name, moduleUid);
9608
+ if (fs21.existsSync(worktreePath)) {
9375
9609
  return {
9376
9610
  path: worktreePath,
9377
9611
  exists: true,
@@ -9388,61 +9622,61 @@ async function getWorktreeInfoForModule(moduleUid) {
9388
9622
  }
9389
9623
 
9390
9624
  // src/framework-detector.ts
9391
- var fs20 = __toESM(require("fs"));
9392
- var path21 = __toESM(require("path"));
9393
- function getInstallCommand(cwd) {
9394
- if (fs20.existsSync(path21.join(cwd, "bun.lockb"))) {
9625
+ var fs22 = __toESM(require("fs"));
9626
+ var path23 = __toESM(require("path"));
9627
+ function getInstallCommand2(cwd) {
9628
+ if (fs22.existsSync(path23.join(cwd, "bun.lockb"))) {
9395
9629
  return {
9396
9630
  command: ["bun", "install"],
9397
9631
  description: "Installing dependencies with bun",
9398
9632
  detectedFrom: "bun.lockb"
9399
9633
  };
9400
9634
  }
9401
- if (fs20.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
9635
+ if (fs22.existsSync(path23.join(cwd, "pnpm-lock.yaml"))) {
9402
9636
  return {
9403
9637
  command: ["pnpm", "install"],
9404
9638
  description: "Installing dependencies with pnpm",
9405
9639
  detectedFrom: "pnpm-lock.yaml"
9406
9640
  };
9407
9641
  }
9408
- if (fs20.existsSync(path21.join(cwd, "yarn.lock"))) {
9642
+ if (fs22.existsSync(path23.join(cwd, "yarn.lock"))) {
9409
9643
  return {
9410
9644
  command: ["yarn", "install"],
9411
9645
  description: "Installing dependencies with yarn",
9412
9646
  detectedFrom: "yarn.lock"
9413
9647
  };
9414
9648
  }
9415
- if (fs20.existsSync(path21.join(cwd, "package-lock.json"))) {
9649
+ if (fs22.existsSync(path23.join(cwd, "package-lock.json"))) {
9416
9650
  return {
9417
9651
  command: ["npm", "ci"],
9418
9652
  description: "Installing dependencies with npm ci",
9419
9653
  detectedFrom: "package-lock.json"
9420
9654
  };
9421
9655
  }
9422
- if (fs20.existsSync(path21.join(cwd, "package.json"))) {
9656
+ if (fs22.existsSync(path23.join(cwd, "package.json"))) {
9423
9657
  return {
9424
9658
  command: ["npm", "install"],
9425
9659
  description: "Installing dependencies with npm",
9426
9660
  detectedFrom: "package.json"
9427
9661
  };
9428
9662
  }
9429
- if (fs20.existsSync(path21.join(cwd, "Pipfile.lock")) || fs20.existsSync(path21.join(cwd, "Pipfile"))) {
9663
+ if (fs22.existsSync(path23.join(cwd, "Pipfile.lock")) || fs22.existsSync(path23.join(cwd, "Pipfile"))) {
9430
9664
  return {
9431
9665
  command: ["pipenv", "install"],
9432
9666
  description: "Installing dependencies with pipenv",
9433
- detectedFrom: fs20.existsSync(path21.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9667
+ detectedFrom: fs22.existsSync(path23.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9434
9668
  };
9435
9669
  }
9436
- if (fs20.existsSync(path21.join(cwd, "poetry.lock"))) {
9670
+ if (fs22.existsSync(path23.join(cwd, "poetry.lock"))) {
9437
9671
  return {
9438
9672
  command: ["poetry", "install"],
9439
9673
  description: "Installing dependencies with poetry",
9440
9674
  detectedFrom: "poetry.lock"
9441
9675
  };
9442
9676
  }
9443
- if (fs20.existsSync(path21.join(cwd, "pyproject.toml"))) {
9444
- const pyprojectPath = path21.join(cwd, "pyproject.toml");
9445
- const content = fs20.readFileSync(pyprojectPath, "utf-8");
9677
+ if (fs22.existsSync(path23.join(cwd, "pyproject.toml"))) {
9678
+ const pyprojectPath = path23.join(cwd, "pyproject.toml");
9679
+ const content = fs22.readFileSync(pyprojectPath, "utf-8");
9446
9680
  if (content.includes("[tool.poetry]")) {
9447
9681
  return {
9448
9682
  command: ["poetry", "install"],
@@ -9451,42 +9685,42 @@ function getInstallCommand(cwd) {
9451
9685
  };
9452
9686
  }
9453
9687
  }
9454
- if (fs20.existsSync(path21.join(cwd, "requirements.txt"))) {
9688
+ if (fs22.existsSync(path23.join(cwd, "requirements.txt"))) {
9455
9689
  return {
9456
9690
  command: ["pip", "install", "-r", "requirements.txt"],
9457
9691
  description: "Installing dependencies with pip",
9458
9692
  detectedFrom: "requirements.txt"
9459
9693
  };
9460
9694
  }
9461
- if (fs20.existsSync(path21.join(cwd, "Gemfile.lock")) || fs20.existsSync(path21.join(cwd, "Gemfile"))) {
9695
+ if (fs22.existsSync(path23.join(cwd, "Gemfile.lock")) || fs22.existsSync(path23.join(cwd, "Gemfile"))) {
9462
9696
  return {
9463
9697
  command: ["bundle", "install"],
9464
9698
  description: "Installing dependencies with bundler",
9465
- detectedFrom: fs20.existsSync(path21.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9699
+ detectedFrom: fs22.existsSync(path23.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9466
9700
  };
9467
9701
  }
9468
- if (fs20.existsSync(path21.join(cwd, "go.sum")) || fs20.existsSync(path21.join(cwd, "go.mod"))) {
9702
+ if (fs22.existsSync(path23.join(cwd, "go.sum")) || fs22.existsSync(path23.join(cwd, "go.mod"))) {
9469
9703
  return {
9470
9704
  command: ["go", "mod", "download"],
9471
9705
  description: "Downloading Go modules",
9472
- detectedFrom: fs20.existsSync(path21.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9706
+ detectedFrom: fs22.existsSync(path23.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9473
9707
  };
9474
9708
  }
9475
- if (fs20.existsSync(path21.join(cwd, "Cargo.lock")) || fs20.existsSync(path21.join(cwd, "Cargo.toml"))) {
9709
+ if (fs22.existsSync(path23.join(cwd, "Cargo.lock")) || fs22.existsSync(path23.join(cwd, "Cargo.toml"))) {
9476
9710
  return {
9477
9711
  command: ["cargo", "build"],
9478
9712
  description: "Building Rust project (downloads dependencies)",
9479
- detectedFrom: fs20.existsSync(path21.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9713
+ detectedFrom: fs22.existsSync(path23.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9480
9714
  };
9481
9715
  }
9482
9716
  return null;
9483
9717
  }
9484
9718
 
9485
9719
  // src/daemon/daemon-process.ts
9486
- var fs21 = __toESM(require("fs"));
9720
+ var fs23 = __toESM(require("fs"));
9487
9721
  var http2 = __toESM(require("http"));
9488
9722
  var os8 = __toESM(require("os"));
9489
- var path22 = __toESM(require("path"));
9723
+ var path24 = __toESM(require("path"));
9490
9724
  var packageJson = require_package();
9491
9725
  async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
9492
9726
  const now = Date.now();
@@ -9707,9 +9941,9 @@ var Daemon = class _Daemon {
9707
9941
  this.healthServer = http2.createServer((req, res) => {
9708
9942
  if (req.url === "/health" || req.url === "/") {
9709
9943
  const isConnected = this.liveConnections.size > 0;
9710
- const projects = Array.from(this.connections.entries()).map(([path23, conn]) => ({
9711
- path: path23,
9712
- connected: this.liveConnections.has(path23)
9944
+ const projects = Array.from(this.connections.entries()).map(([path25, conn]) => ({
9945
+ path: path25,
9946
+ connected: this.liveConnections.has(path25)
9713
9947
  }));
9714
9948
  const status = {
9715
9949
  status: isConnected ? "healthy" : "degraded",
@@ -9939,9 +10173,12 @@ var Daemon = class _Daemon {
9939
10173
  this.ipcServer.on("worktree-list", async (params) => {
9940
10174
  return handleWorktreeList(params.workspaceSlug, params.projectSlug);
9941
10175
  });
9942
- this.ipcServer.on("project-eject", async (params) => {
10176
+ this.ipcServer.on("project:eject", async (params) => {
9943
10177
  return handleProjectEject(params);
9944
10178
  });
10179
+ this.ipcServer.on("project:setup", async (params) => {
10180
+ return handleProjectSetup(params);
10181
+ });
9945
10182
  }
9946
10183
  /**
9947
10184
  * Restore WebSocket connections for tracked projects
@@ -10074,7 +10311,7 @@ var Daemon = class _Daemon {
10074
10311
  client.updateActivity();
10075
10312
  try {
10076
10313
  const gitCmd = message.command;
10077
- const bareRepoPath = path22.join(projectPath, ".bare");
10314
+ const bareRepoPath = path24.join(projectPath, ".bare");
10078
10315
  const cwd = gitCmd.worktreePath || bareRepoPath;
10079
10316
  if (gitCmd.worktreePath) {
10080
10317
  console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
@@ -10188,9 +10425,14 @@ var Daemon = class _Daemon {
10188
10425
  );
10189
10426
  break;
10190
10427
  // EP1144: Project ejection for Tier 1 cleanup
10191
- case "eject_project":
10428
+ // EP1199: Renamed from eject_project to project:eject for naming consistency
10429
+ case "project:eject":
10192
10430
  result = await handleProjectEject(cmd);
10193
10431
  break;
10432
+ // EP1199: Project setup for cloud containers
10433
+ case "project:setup":
10434
+ result = await handleProjectSetup(cmd);
10435
+ break;
10194
10436
  default:
10195
10437
  result = {
10196
10438
  success: false,
@@ -10654,8 +10896,8 @@ var Daemon = class _Daemon {
10654
10896
  let daemonPid;
10655
10897
  try {
10656
10898
  const pidPath = getPidFilePath();
10657
- if (fs21.existsSync(pidPath)) {
10658
- const pidStr = fs21.readFileSync(pidPath, "utf-8").trim();
10899
+ if (fs23.existsSync(pidPath)) {
10900
+ const pidStr = fs23.readFileSync(pidPath, "utf-8").trim();
10659
10901
  daemonPid = parseInt(pidStr, 10);
10660
10902
  }
10661
10903
  } catch (pidError) {
@@ -10736,28 +10978,28 @@ var Daemon = class _Daemon {
10736
10978
  * - workDir: The directory to run git commands in (cwd)
10737
10979
  */
10738
10980
  getGitDirs(projectPath) {
10739
- const bareDir = path22.join(projectPath, ".bare");
10740
- const gitPath = path22.join(projectPath, ".git");
10741
- if (fs21.existsSync(bareDir) && fs21.statSync(bareDir).isDirectory()) {
10981
+ const bareDir = path24.join(projectPath, ".bare");
10982
+ const gitPath = path24.join(projectPath, ".git");
10983
+ if (fs23.existsSync(bareDir) && fs23.statSync(bareDir).isDirectory()) {
10742
10984
  return { gitDir: bareDir, workDir: projectPath };
10743
10985
  }
10744
- if (fs21.existsSync(gitPath) && fs21.statSync(gitPath).isDirectory()) {
10986
+ if (fs23.existsSync(gitPath) && fs23.statSync(gitPath).isDirectory()) {
10745
10987
  return { gitDir: null, workDir: projectPath };
10746
10988
  }
10747
- if (fs21.existsSync(gitPath) && fs21.statSync(gitPath).isFile()) {
10989
+ if (fs23.existsSync(gitPath) && fs23.statSync(gitPath).isFile()) {
10748
10990
  return { gitDir: null, workDir: projectPath };
10749
10991
  }
10750
- const entries = fs21.readdirSync(projectPath, { withFileTypes: true });
10992
+ const entries = fs23.readdirSync(projectPath, { withFileTypes: true });
10751
10993
  for (const entry of entries) {
10752
10994
  if (entry.isDirectory() && entry.name.startsWith("EP")) {
10753
- const worktreePath = path22.join(projectPath, entry.name);
10754
- const worktreeGit = path22.join(worktreePath, ".git");
10755
- if (fs21.existsSync(worktreeGit)) {
10995
+ const worktreePath = path24.join(projectPath, entry.name);
10996
+ const worktreeGit = path24.join(worktreePath, ".git");
10997
+ if (fs23.existsSync(worktreeGit)) {
10756
10998
  return { gitDir: null, workDir: worktreePath };
10757
10999
  }
10758
11000
  }
10759
11001
  }
10760
- if (fs21.existsSync(bareDir)) {
11002
+ if (fs23.existsSync(bareDir)) {
10761
11003
  return { gitDir: bareDir, workDir: projectPath };
10762
11004
  }
10763
11005
  return { gitDir: null, workDir: projectPath };
@@ -10821,24 +11063,24 @@ var Daemon = class _Daemon {
10821
11063
  async installGitHooks(projectPath) {
10822
11064
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
10823
11065
  let hooksDir;
10824
- const bareHooksDir = path22.join(projectPath, ".bare", "hooks");
10825
- const gitHooksDir = path22.join(projectPath, ".git", "hooks");
10826
- if (fs21.existsSync(bareHooksDir)) {
11066
+ const bareHooksDir = path24.join(projectPath, ".bare", "hooks");
11067
+ const gitHooksDir = path24.join(projectPath, ".git", "hooks");
11068
+ if (fs23.existsSync(bareHooksDir)) {
10827
11069
  hooksDir = bareHooksDir;
10828
- } else if (fs21.existsSync(gitHooksDir) && fs21.statSync(path22.join(projectPath, ".git")).isDirectory()) {
11070
+ } else if (fs23.existsSync(gitHooksDir) && fs23.statSync(path24.join(projectPath, ".git")).isDirectory()) {
10829
11071
  hooksDir = gitHooksDir;
10830
11072
  } else {
10831
- const parentBareHooks = path22.join(projectPath, "..", ".bare", "hooks");
10832
- if (fs21.existsSync(parentBareHooks)) {
11073
+ const parentBareHooks = path24.join(projectPath, "..", ".bare", "hooks");
11074
+ if (fs23.existsSync(parentBareHooks)) {
10833
11075
  hooksDir = parentBareHooks;
10834
11076
  } else {
10835
11077
  console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
10836
11078
  return;
10837
11079
  }
10838
11080
  }
10839
- if (!fs21.existsSync(hooksDir)) {
11081
+ if (!fs23.existsSync(hooksDir)) {
10840
11082
  try {
10841
- fs21.mkdirSync(hooksDir, { recursive: true });
11083
+ fs23.mkdirSync(hooksDir, { recursive: true });
10842
11084
  } catch (error) {
10843
11085
  console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
10844
11086
  return;
@@ -10846,20 +11088,20 @@ var Daemon = class _Daemon {
10846
11088
  }
10847
11089
  for (const hookName of hooks) {
10848
11090
  try {
10849
- const hookPath = path22.join(hooksDir, hookName);
10850
- const bundledHookPath = path22.join(__dirname, "..", "hooks", hookName);
10851
- if (!fs21.existsSync(bundledHookPath)) {
11091
+ const hookPath = path24.join(hooksDir, hookName);
11092
+ const bundledHookPath = path24.join(__dirname, "..", "hooks", hookName);
11093
+ if (!fs23.existsSync(bundledHookPath)) {
10852
11094
  console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
10853
11095
  continue;
10854
11096
  }
10855
- const hookContent = fs21.readFileSync(bundledHookPath, "utf-8");
10856
- if (fs21.existsSync(hookPath)) {
10857
- const existingContent = fs21.readFileSync(hookPath, "utf-8");
11097
+ const hookContent = fs23.readFileSync(bundledHookPath, "utf-8");
11098
+ if (fs23.existsSync(hookPath)) {
11099
+ const existingContent = fs23.readFileSync(hookPath, "utf-8");
10858
11100
  if (existingContent === hookContent) {
10859
11101
  continue;
10860
11102
  }
10861
11103
  }
10862
- fs21.writeFileSync(hookPath, hookContent, { mode: 493 });
11104
+ fs23.writeFileSync(hookPath, hookContent, { mode: 493 });
10863
11105
  console.log(`[Daemon] Installed git hook: ${hookName}`);
10864
11106
  } catch (error) {
10865
11107
  console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
@@ -11197,7 +11439,7 @@ var Daemon = class _Daemon {
11197
11439
  console.log(`[Daemon] EP1002: Writing .env with ${Object.keys(envVars).length} variables`);
11198
11440
  writeEnvFile(worktreePath, envVars);
11199
11441
  }
11200
- const installCmd = getInstallCommand(worktreePath);
11442
+ const installCmd = getInstallCommand2(worktreePath);
11201
11443
  if (installCmd) {
11202
11444
  console.log(`[Daemon] EP1002: ${installCmd.description} (detected from ${installCmd.detectedFrom})`);
11203
11445
  console.log(`[Daemon] EP1002: Running: ${installCmd.command.join(" ")}`);
@@ -11251,7 +11493,7 @@ var Daemon = class _Daemon {
11251
11493
  console.warn(`[Daemon] EP964: File copy failed (non-fatal): ${copyResult.error}`);
11252
11494
  }
11253
11495
  }
11254
- const installCmd = getInstallCommand(worktreePath);
11496
+ const installCmd = getInstallCommand2(worktreePath);
11255
11497
  if (installCmd) {
11256
11498
  console.log(`[Daemon] EP986: ${installCmd.description} (detected from ${installCmd.detectedFrom})`);
11257
11499
  console.log(`[Daemon] EP986: Running: ${installCmd.command.join(" ")}`);
@@ -11863,8 +12105,8 @@ var Daemon = class _Daemon {
11863
12105
  await this.shutdown();
11864
12106
  try {
11865
12107
  const pidPath = getPidFilePath();
11866
- if (fs21.existsSync(pidPath)) {
11867
- fs21.unlinkSync(pidPath);
12108
+ if (fs23.existsSync(pidPath)) {
12109
+ fs23.unlinkSync(pidPath);
11868
12110
  console.log("[Daemon] PID file cleaned up");
11869
12111
  }
11870
12112
  } catch (error) {