episoda 0.2.66 → 0.2.68

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.
@@ -2592,9 +2592,12 @@ var require_auth = __commonJS({
2592
2592
  }
2593
2593
  }
2594
2594
  if (process.env.EPISODA_ACCESS_TOKEN) {
2595
+ const expiresAtEnv = process.env.EPISODA_ACCESS_TOKEN_EXPIRES_AT;
2596
+ const expires_at = expiresAtEnv ? parseInt(expiresAtEnv, 10) : void 0;
2595
2597
  return {
2596
2598
  access_token: process.env.EPISODA_ACCESS_TOKEN,
2597
2599
  refresh_token: process.env.EPISODA_REFRESH_TOKEN,
2600
+ expires_at,
2598
2601
  api_url: process.env.EPISODA_API_URL || "https://episoda.dev",
2599
2602
  project_id: process.env.EPISODA_PROJECT_ID || "",
2600
2603
  user_id: process.env.EPISODA_USER_ID || "",
@@ -2727,7 +2730,7 @@ var require_package = __commonJS({
2727
2730
  "package.json"(exports2, module2) {
2728
2731
  module2.exports = {
2729
2732
  name: "episoda",
2730
- version: "0.2.66",
2733
+ version: "0.2.68",
2731
2734
  description: "CLI tool for Episoda local development workflow orchestration",
2732
2735
  main: "dist/index.js",
2733
2736
  types: "dist/index.d.ts",
@@ -8899,6 +8902,23 @@ var Daemon = class _Daemon {
8899
8902
  console.log(`[Daemon] EP949: Received token refresh for ${projectId}`);
8900
8903
  client.updateToken(tokenMsg.accessToken);
8901
8904
  });
8905
+ client.on("machine_uuid_update", async (message) => {
8906
+ const uuidMsg = message;
8907
+ if (uuidMsg.machineUuid) {
8908
+ this.machineUuid = uuidMsg.machineUuid;
8909
+ console.log(`[Daemon] EP1095: Machine UUID updated: ${this.machineUuid}`);
8910
+ await this.cacheMachineUuid(uuidMsg.machineUuid);
8911
+ this.syncMachineProjectPath(projectId, projectPath).catch((err) => {
8912
+ console.warn("[Daemon] EP1095: Deferred project path sync failed:", err.message);
8913
+ });
8914
+ const connection2 = this.connections.get(projectPath);
8915
+ if (connection2) {
8916
+ this.reconcileWorktrees(projectId, projectPath, connection2.client).catch((err) => {
8917
+ console.warn("[Daemon] EP1095: Deferred reconciliation failed:", err.message);
8918
+ });
8919
+ }
8920
+ }
8921
+ });
8902
8922
  client.on("disconnected", (event) => {
8903
8923
  const disconnectEvent = event;
8904
8924
  console.log(`[Daemon] Connection closed for ${projectId}: code=${disconnectEvent.code}, willReconnect=${disconnectEvent.willReconnect}`);
@@ -8981,12 +9001,47 @@ var Daemon = class _Daemon {
8981
9001
  process.on("SIGTERM", () => shutdownHandler("SIGTERM"));
8982
9002
  process.on("SIGINT", () => shutdownHandler("SIGINT"));
8983
9003
  }
9004
+ /**
9005
+ * EP1095: Get git directory and working directory for a project path
9006
+ * Handles both traditional (.git directory) and worktree (.bare directory) structures
9007
+ * Returns { gitDir, workDir } where:
9008
+ * - gitDir: The --git-dir value to use with git commands
9009
+ * - workDir: The directory to run git commands in (cwd)
9010
+ */
9011
+ getGitDirs(projectPath) {
9012
+ const bareDir = path19.join(projectPath, ".bare");
9013
+ const gitPath = path19.join(projectPath, ".git");
9014
+ if (fs18.existsSync(bareDir) && fs18.statSync(bareDir).isDirectory()) {
9015
+ return { gitDir: bareDir, workDir: projectPath };
9016
+ }
9017
+ if (fs18.existsSync(gitPath) && fs18.statSync(gitPath).isDirectory()) {
9018
+ return { gitDir: null, workDir: projectPath };
9019
+ }
9020
+ if (fs18.existsSync(gitPath) && fs18.statSync(gitPath).isFile()) {
9021
+ return { gitDir: null, workDir: projectPath };
9022
+ }
9023
+ const entries = fs18.readdirSync(projectPath, { withFileTypes: true });
9024
+ for (const entry of entries) {
9025
+ if (entry.isDirectory() && entry.name.startsWith("EP")) {
9026
+ const worktreePath = path19.join(projectPath, entry.name);
9027
+ const worktreeGit = path19.join(worktreePath, ".git");
9028
+ if (fs18.existsSync(worktreeGit)) {
9029
+ return { gitDir: null, workDir: worktreePath };
9030
+ }
9031
+ }
9032
+ }
9033
+ if (fs18.existsSync(bareDir)) {
9034
+ return { gitDir: bareDir, workDir: projectPath };
9035
+ }
9036
+ return { gitDir: null, workDir: projectPath };
9037
+ }
8984
9038
  /**
8985
9039
  * EP595: Configure git with user and workspace ID for post-checkout hook
8986
9040
  * EP655: Added machineId for device isolation in multi-device environments
8987
9041
  * EP725: Added projectId for main branch badge tracking
8988
9042
  * EP726: Added machineUuid (UUID) for unified device identification
8989
9043
  * EP1091: Renamed deviceId param to machineUuid for consistency
9044
+ * EP1095: Added worktree structure support (uses .bare as gitDir)
8990
9045
  *
8991
9046
  * This stores the IDs in .git/config so the post-checkout hook can
8992
9047
  * update module.checkout_* fields when git operations happen from terminal.
@@ -8994,29 +9049,31 @@ var Daemon = class _Daemon {
8994
9049
  async configureGitUser(projectPath, userId, workspaceId, machineId, projectId, machineUuid) {
8995
9050
  try {
8996
9051
  const { execSync: execSync9 } = await import("child_process");
8997
- execSync9(`git config episoda.userId ${userId}`, {
8998
- cwd: projectPath,
9052
+ const { gitDir, workDir } = this.getGitDirs(projectPath);
9053
+ const gitCmd = gitDir ? `git --git-dir="${gitDir}"` : "git";
9054
+ execSync9(`${gitCmd} config episoda.userId ${userId}`, {
9055
+ cwd: workDir,
8999
9056
  encoding: "utf8",
9000
9057
  stdio: "pipe"
9001
9058
  });
9002
- execSync9(`git config episoda.workspaceId ${workspaceId}`, {
9003
- cwd: projectPath,
9059
+ execSync9(`${gitCmd} config episoda.workspaceId ${workspaceId}`, {
9060
+ cwd: workDir,
9004
9061
  encoding: "utf8",
9005
9062
  stdio: "pipe"
9006
9063
  });
9007
- execSync9(`git config episoda.machineId ${machineId}`, {
9008
- cwd: projectPath,
9064
+ execSync9(`${gitCmd} config episoda.machineId ${machineId}`, {
9065
+ cwd: workDir,
9009
9066
  encoding: "utf8",
9010
9067
  stdio: "pipe"
9011
9068
  });
9012
- execSync9(`git config episoda.projectId ${projectId}`, {
9013
- cwd: projectPath,
9069
+ execSync9(`${gitCmd} config episoda.projectId ${projectId}`, {
9070
+ cwd: workDir,
9014
9071
  encoding: "utf8",
9015
9072
  stdio: "pipe"
9016
9073
  });
9017
9074
  if (machineUuid) {
9018
- execSync9(`git config episoda.deviceId ${machineUuid}`, {
9019
- cwd: projectPath,
9075
+ execSync9(`${gitCmd} config episoda.deviceId ${machineUuid}`, {
9076
+ cwd: workDir,
9020
9077
  encoding: "utf8",
9021
9078
  stdio: "pipe"
9022
9079
  });
@@ -9028,6 +9085,7 @@ var Daemon = class _Daemon {
9028
9085
  }
9029
9086
  /**
9030
9087
  * EP610: Install git hooks from bundled files
9088
+ * EP1095: Added worktree structure support (hooks in .bare/hooks)
9031
9089
  *
9032
9090
  * Installs post-checkout and pre-commit hooks to enable:
9033
9091
  * - Branch tracking (post-checkout updates module.checkout_* fields)
@@ -9035,10 +9093,29 @@ var Daemon = class _Daemon {
9035
9093
  */
9036
9094
  async installGitHooks(projectPath) {
9037
9095
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
9038
- const hooksDir = path19.join(projectPath, ".git", "hooks");
9096
+ let hooksDir;
9097
+ const bareHooksDir = path19.join(projectPath, ".bare", "hooks");
9098
+ const gitHooksDir = path19.join(projectPath, ".git", "hooks");
9099
+ if (fs18.existsSync(bareHooksDir)) {
9100
+ hooksDir = bareHooksDir;
9101
+ } else if (fs18.existsSync(gitHooksDir) && fs18.statSync(path19.join(projectPath, ".git")).isDirectory()) {
9102
+ hooksDir = gitHooksDir;
9103
+ } else {
9104
+ const parentBareHooks = path19.join(projectPath, "..", ".bare", "hooks");
9105
+ if (fs18.existsSync(parentBareHooks)) {
9106
+ hooksDir = parentBareHooks;
9107
+ } else {
9108
+ console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
9109
+ return;
9110
+ }
9111
+ }
9039
9112
  if (!fs18.existsSync(hooksDir)) {
9040
- console.warn(`[Daemon] Hooks directory not found: ${hooksDir}`);
9041
- return;
9113
+ try {
9114
+ fs18.mkdirSync(hooksDir, { recursive: true });
9115
+ } catch (error) {
9116
+ console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
9117
+ return;
9118
+ }
9042
9119
  }
9043
9120
  for (const hookName of hooks) {
9044
9121
  try {