episoda 0.2.22 → 0.2.24

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
@@ -530,6 +530,24 @@ var require_git_executor = __commonJS({
530
530
  * Execute status command
531
531
  */
532
532
  async executeStatus(cwd, options) {
533
+ try {
534
+ const isBareResult = await execAsync("git rev-parse --is-bare-repository", { cwd, timeout: 5e3 });
535
+ if (isBareResult.stdout.trim() === "true") {
536
+ const headResult = await execAsync("git symbolic-ref --short HEAD", { cwd, timeout: 5e3 });
537
+ const branchName = headResult.stdout.trim();
538
+ return {
539
+ success: true,
540
+ output: `## ${branchName}`,
541
+ details: {
542
+ uncommittedFiles: [],
543
+ // No working tree in bare repo
544
+ branchName,
545
+ currentBranch: branchName
546
+ }
547
+ };
548
+ }
549
+ } catch {
550
+ }
533
551
  const result = await this.runGitCommand(["status", "--porcelain", "-b"], cwd, options);
534
552
  if (result.success && result.output) {
535
553
  const statusInfo = (0, git_parser_1.parseGitStatus)(result.output);
@@ -1861,7 +1879,6 @@ var require_git_executor = __commonJS({
1861
1879
  const fs11 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1862
1880
  const path13 = await Promise.resolve().then(() => __importStar(require("path")));
1863
1881
  let currentPath = cwd;
1864
- let worktreeMode = false;
1865
1882
  let projectPath = cwd;
1866
1883
  let bareRepoPath;
1867
1884
  for (let i = 0; i < 10; i++) {
@@ -1870,7 +1887,6 @@ var require_git_executor = __commonJS({
1870
1887
  try {
1871
1888
  await fs11.access(bareDir);
1872
1889
  await fs11.access(episodaDir);
1873
- worktreeMode = true;
1874
1890
  projectPath = currentPath;
1875
1891
  bareRepoPath = bareDir;
1876
1892
  break;
@@ -1884,9 +1900,8 @@ var require_git_executor = __commonJS({
1884
1900
  }
1885
1901
  return {
1886
1902
  success: true,
1887
- output: worktreeMode ? "Worktree mode project" : "Standard git project",
1903
+ output: bareRepoPath ? "Episoda project" : "Git repository",
1888
1904
  details: {
1889
- worktreeMode,
1890
1905
  projectPath,
1891
1906
  bareRepoPath
1892
1907
  }
@@ -2468,7 +2483,7 @@ var require_auth = __commonJS({
2468
2483
  exports2.getConfigDir = getConfigDir5;
2469
2484
  exports2.getConfigPath = getConfigPath4;
2470
2485
  exports2.loadConfig = loadConfig6;
2471
- exports2.saveConfig = saveConfig4;
2486
+ exports2.saveConfig = saveConfig3;
2472
2487
  exports2.validateToken = validateToken;
2473
2488
  var fs11 = __importStar(require("fs"));
2474
2489
  var path13 = __importStar(require("path"));
@@ -2518,7 +2533,7 @@ var require_auth = __commonJS({
2518
2533
  return null;
2519
2534
  }
2520
2535
  }
2521
- async function saveConfig4(config, configPath) {
2536
+ async function saveConfig3(config, configPath) {
2522
2537
  const fullPath = getConfigPath4(configPath);
2523
2538
  ensureConfigDir(fullPath);
2524
2539
  try {
@@ -3172,7 +3187,8 @@ var WorktreeManager = class _WorktreeManager {
3172
3187
  }
3173
3188
  /**
3174
3189
  * Initialize worktree manager from existing project root
3175
- * @returns true if valid worktree project, false otherwise
3190
+ * EP971: All projects use worktree architecture
3191
+ * @returns true if valid project, false otherwise
3176
3192
  */
3177
3193
  async initialize() {
3178
3194
  if (!fs3.existsSync(this.bareRepoPath)) {
@@ -3183,7 +3199,7 @@ var WorktreeManager = class _WorktreeManager {
3183
3199
  }
3184
3200
  try {
3185
3201
  const config = this.readConfig();
3186
- return config?.worktreeMode === true;
3202
+ return config !== null;
3187
3203
  } catch {
3188
3204
  return false;
3189
3205
  }
@@ -3208,7 +3224,6 @@ var WorktreeManager = class _WorktreeManager {
3208
3224
  workspaceSlug,
3209
3225
  projectSlug,
3210
3226
  bareRepoPath: manager.bareRepoPath,
3211
- worktreeMode: true,
3212
3227
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3213
3228
  worktrees: []
3214
3229
  };
@@ -3740,6 +3755,60 @@ async function findProjectRoot(startPath) {
3740
3755
  return null;
3741
3756
  }
3742
3757
 
3758
+ // src/api/machine-settings.ts
3759
+ async function fetchProjectPath(config, projectId) {
3760
+ if (!config.device_id || !config.access_token) {
3761
+ return null;
3762
+ }
3763
+ const serverUrl = config.api_url || "https://episoda.dev";
3764
+ const url = `${serverUrl}/api/dev/machines/${config.device_id}/project-path/${projectId}`;
3765
+ try {
3766
+ const response = await fetch(url, {
3767
+ method: "GET",
3768
+ headers: {
3769
+ "Authorization": `Bearer ${config.access_token}`,
3770
+ "Content-Type": "application/json"
3771
+ }
3772
+ });
3773
+ if (!response.ok) {
3774
+ if (response.status === 404) {
3775
+ return null;
3776
+ }
3777
+ console.debug(`[MachineSettings] fetchProjectPath failed: ${response.status} ${response.statusText}`);
3778
+ return null;
3779
+ }
3780
+ const data = await response.json();
3781
+ return data.data?.path || null;
3782
+ } catch (error) {
3783
+ console.debug(`[MachineSettings] fetchProjectPath network error:`, error);
3784
+ return null;
3785
+ }
3786
+ }
3787
+ async function syncProjectPath(config, projectId, path13) {
3788
+ if (!config.device_id || !config.access_token) {
3789
+ return false;
3790
+ }
3791
+ const serverUrl = config.api_url || "https://episoda.dev";
3792
+ const url = `${serverUrl}/api/dev/machines/${config.device_id}/project-path/${projectId}`;
3793
+ try {
3794
+ const response = await fetch(url, {
3795
+ method: "PUT",
3796
+ headers: {
3797
+ "Authorization": `Bearer ${config.access_token}`,
3798
+ "Content-Type": "application/json"
3799
+ },
3800
+ body: JSON.stringify({ path: path13 })
3801
+ });
3802
+ if (!response.ok) {
3803
+ console.debug(`[MachineSettings] syncProjectPath failed: ${response.status} ${response.statusText}`);
3804
+ }
3805
+ return response.ok;
3806
+ } catch (error) {
3807
+ console.debug(`[MachineSettings] syncProjectPath network error:`, error);
3808
+ return false;
3809
+ }
3810
+ }
3811
+
3743
3812
  // src/commands/dev.ts
3744
3813
  var CONNECTION_MAX_RETRIES = 3;
3745
3814
  function findGitRoot(startDir) {
@@ -3798,14 +3867,11 @@ async function devCommand(options = {}) {
3798
3867
  await new Promise((resolve4) => setTimeout(resolve4, 2e3));
3799
3868
  }
3800
3869
  }
3801
- const serverUrl = config.api_url || process.env.EPISODA_API_URL || "https://episoda.dev";
3802
3870
  let projectPath;
3803
- const SETTINGS_CACHE_TTL = 24 * 60 * 60 * 1e3;
3804
- const cachedSettings = config.project_settings;
3805
- const isCacheFresh = cachedSettings?.cached_at && Date.now() - cachedSettings.cached_at < SETTINGS_CACHE_TTL;
3806
- if (isCacheFresh && cachedSettings?.local_project_path) {
3807
- projectPath = cachedSettings.local_project_path;
3808
- status.debug(`Using cached project path: ${projectPath}`);
3871
+ const serverPath = await fetchProjectPath(config, config.project_id);
3872
+ if (serverPath && fs4.existsSync(serverPath)) {
3873
+ projectPath = serverPath;
3874
+ status.debug(`Using server-synced project path: ${projectPath}`);
3809
3875
  } else {
3810
3876
  const detectedRoot = findGitRoot(options.cwd || process.cwd());
3811
3877
  projectPath = detectedRoot || path5.resolve(options.cwd || process.cwd());
@@ -3814,44 +3880,19 @@ async function devCommand(options = {}) {
3814
3880
  } else {
3815
3881
  status.warning(`Could not detect git root, using: ${projectPath}`);
3816
3882
  }
3817
- const updatedConfig = {
3818
- ...config,
3819
- project_settings: {
3820
- ...cachedSettings,
3821
- local_project_path: projectPath,
3822
- cached_at: Date.now()
3883
+ syncProjectPath(config, config.project_id, projectPath).then((synced) => {
3884
+ if (synced) {
3885
+ status.debug("Project path synced to server");
3823
3886
  }
3824
- };
3825
- await (0, import_core4.saveConfig)(updatedConfig);
3826
- status.debug("Cached project settings locally");
3827
- const settingsUrl = `${serverUrl}/api/projects/${config.project_id}/settings`;
3828
- fetch(settingsUrl, {
3829
- method: "PATCH",
3830
- headers: {
3831
- "Authorization": `Bearer ${config.access_token}`,
3832
- "Content-Type": "application/json"
3833
- },
3834
- body: JSON.stringify({ local_project_path: projectPath })
3835
3887
  }).catch(() => {
3836
3888
  });
3837
3889
  }
3838
3890
  const isWorktree = await isWorktreeProject(projectPath);
3839
3891
  if (!isWorktree) {
3840
- const hasGitDir = fs4.existsSync(path5.join(projectPath, ".git"));
3841
- if (hasGitDir) {
3842
- status.error("This project is not in worktree mode.");
3843
- status.info("");
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`.");
3853
- status.info("");
3854
- }
3892
+ status.error("Not an Episoda project.");
3893
+ status.info("");
3894
+ status.info("Use `episoda clone {workspace}/{project}` to set up a project.");
3895
+ status.info("");
3855
3896
  process.exit(1);
3856
3897
  }
3857
3898
  let daemonPid = isDaemonRunning();
@@ -5044,9 +5085,6 @@ function addProject2(projectId, projectPath, options) {
5044
5085
  if (existingByPath) {
5045
5086
  existingByPath.id = projectId;
5046
5087
  existingByPath.last_active = now;
5047
- if (options?.worktreeMode !== void 0) {
5048
- existingByPath.worktreeMode = options.worktreeMode;
5049
- }
5050
5088
  if (options?.bareRepoPath) {
5051
5089
  existingByPath.bareRepoPath = options.bareRepoPath;
5052
5090
  }
@@ -5066,8 +5104,7 @@ function addProject2(projectId, projectPath, options) {
5066
5104
  name: projectName,
5067
5105
  added_at: now,
5068
5106
  last_active: now,
5069
- // EP944: Worktree mode fields
5070
- worktreeMode: options?.worktreeMode,
5107
+ // EP971: Bare repo path for worktree architecture
5071
5108
  bareRepoPath: options?.bareRepoPath
5072
5109
  };
5073
5110
  data.projects.push(newProject);
@@ -5105,7 +5142,7 @@ async function cloneCommand(slugArg, options = {}) {
5105
5142
  return;
5106
5143
  }
5107
5144
  throw new Error(
5108
- `Directory exists but is not a worktree project: ${projectPath}
5145
+ `Directory exists but is not an Episoda project: ${projectPath}
5109
5146
  Please remove it manually or use a different location.`
5110
5147
  );
5111
5148
  }
@@ -5146,7 +5183,6 @@ Please configure a repository in the project settings on episoda.dev.`
5146
5183
  status.success("\u2713 Bootstrap scripts extracted");
5147
5184
  status.info("");
5148
5185
  addProject2(projectDetails.id, projectPath, {
5149
- worktreeMode: true,
5150
5186
  bareRepoPath
5151
5187
  });
5152
5188
  status.success("\u2713 Project registered with daemon");
@@ -5701,7 +5737,7 @@ import_commander.program.command("connect").description("Connect using a pre-aut
5701
5737
  process.exit(1);
5702
5738
  }
5703
5739
  });
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) => {
5740
+ import_commander.program.command("dev").description("Connect to Episoda and start dev server").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) => {
5705
5741
  try {
5706
5742
  const args = process.argv.slice(process.argv.indexOf("dev") + 1);
5707
5743
  const separatorIndex = args.indexOf("--");
@@ -5740,7 +5776,7 @@ import_commander.program.command("disconnect").description("Disconnect from epis
5740
5776
  process.exit(1);
5741
5777
  }
5742
5778
  });
5743
- import_commander.program.command("clone").description("Clone a project in worktree mode for multi-module development").argument("<workspace/project>", "Workspace and project slug (e.g., my-team/my-project)").option("--api-url <url>", "API URL (default: https://episoda.dev)").action(async (slug, options) => {
5779
+ import_commander.program.command("clone").description("Clone a project for multi-module development").argument("<workspace/project>", "Workspace and project slug (e.g., my-team/my-project)").option("--api-url <url>", "API URL (default: https://episoda.dev)").action(async (slug, options) => {
5744
5780
  try {
5745
5781
  await cloneCommand(slug, options);
5746
5782
  } catch (error) {