episoda 0.2.62 → 0.2.64

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.
@@ -2726,7 +2726,7 @@ var require_package = __commonJS({
2726
2726
  "package.json"(exports2, module2) {
2727
2727
  module2.exports = {
2728
2728
  name: "episoda",
2729
- version: "0.2.62",
2729
+ version: "0.2.64",
2730
2730
  description: "CLI tool for Episoda local development workflow orchestration",
2731
2731
  main: "dist/index.js",
2732
2732
  types: "dist/index.d.ts",
@@ -7091,23 +7091,40 @@ var WorktreeManager = class _WorktreeManager {
7091
7091
  /**
7092
7092
  * Initialize worktree manager from existing project root
7093
7093
  * EP971: All projects use worktree architecture
7094
+ * EP1093: Added debug logging for cloud container diagnostics
7094
7095
  * @returns true if valid project, false otherwise
7095
7096
  */
7096
7097
  async initialize() {
7098
+ const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
7097
7099
  if (!fs15.existsSync(this.bareRepoPath)) {
7100
+ if (debug) {
7101
+ console.log(`[WorktreeManager] initialize: .bare not found at ${this.bareRepoPath}`);
7102
+ }
7098
7103
  return false;
7099
7104
  }
7100
7105
  if (!fs15.existsSync(this.configPath)) {
7106
+ if (debug) {
7107
+ console.log(`[WorktreeManager] initialize: config not found at ${this.configPath}`);
7108
+ }
7101
7109
  return false;
7102
7110
  }
7103
7111
  try {
7104
7112
  const config = this.readConfig();
7105
7113
  if (config === null) {
7114
+ if (debug) {
7115
+ console.log(`[WorktreeManager] initialize: readConfig returned null`);
7116
+ }
7106
7117
  return false;
7107
7118
  }
7119
+ if (debug) {
7120
+ console.log(`[WorktreeManager] initialize: config loaded, projectId=${config.projectId}`);
7121
+ }
7108
7122
  await this.ensureFetchRefspecConfigured();
7109
7123
  return true;
7110
- } catch {
7124
+ } catch (error) {
7125
+ if (debug) {
7126
+ console.log(`[WorktreeManager] initialize: exception - ${error.message}`);
7127
+ }
7111
7128
  return false;
7112
7129
  }
7113
7130
  }
@@ -7719,20 +7736,43 @@ function getEpisodaRoot() {
7719
7736
  return process.env.EPISODA_ROOT || path16.join(require("os").homedir(), "episoda");
7720
7737
  }
7721
7738
  async function isWorktreeProject(projectRoot) {
7739
+ const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
7740
+ if (debug) {
7741
+ console.log(`[WorktreeManager] isWorktreeProject: checking ${projectRoot}`);
7742
+ }
7722
7743
  const manager = new WorktreeManager(projectRoot);
7723
- return manager.initialize();
7744
+ const result = await manager.initialize();
7745
+ if (debug) {
7746
+ console.log(`[WorktreeManager] isWorktreeProject: ${projectRoot} -> ${result}`);
7747
+ }
7748
+ return result;
7724
7749
  }
7725
7750
  async function findProjectRoot(startPath) {
7726
7751
  let current = path16.resolve(startPath);
7727
7752
  const episodaRoot = getEpisodaRoot();
7753
+ const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
7754
+ if (debug) {
7755
+ console.log(`[WorktreeManager] findProjectRoot: start=${startPath}, episodaRoot=${episodaRoot}`);
7756
+ }
7728
7757
  if (!current.startsWith(episodaRoot)) {
7758
+ if (debug) {
7759
+ console.log(`[WorktreeManager] findProjectRoot: ${current} is not under ${episodaRoot}`);
7760
+ }
7729
7761
  return null;
7730
7762
  }
7731
7763
  for (let i = 0; i < 10; i++) {
7732
7764
  const bareDir = path16.join(current, ".bare");
7733
7765
  const episodaDir = path16.join(current, ".episoda");
7766
+ if (debug) {
7767
+ const bareExists = fs15.existsSync(bareDir);
7768
+ const episodaExists = fs15.existsSync(episodaDir);
7769
+ console.log(`[WorktreeManager] findProjectRoot: checking ${current} (.bare=${bareExists}, .episoda=${episodaExists})`);
7770
+ }
7734
7771
  if (fs15.existsSync(bareDir) && fs15.existsSync(episodaDir)) {
7735
7772
  if (await isWorktreeProject(current)) {
7773
+ if (debug) {
7774
+ console.log(`[WorktreeManager] findProjectRoot: found valid project at ${current}`);
7775
+ }
7736
7776
  return current;
7737
7777
  }
7738
7778
  }
@@ -7742,6 +7782,9 @@ async function findProjectRoot(startPath) {
7742
7782
  }
7743
7783
  current = parent;
7744
7784
  }
7785
+ if (debug) {
7786
+ console.log(`[WorktreeManager] findProjectRoot: no project found`);
7787
+ }
7745
7788
  return null;
7746
7789
  }
7747
7790
 
@@ -7964,8 +8007,8 @@ var Daemon = class _Daemon {
7964
8007
  // 60 seconds
7965
8008
  constructor() {
7966
8009
  this.machineId = "";
7967
- this.deviceId = null;
7968
- // EP726: Cached device UUID from server
8010
+ this.machineUuid = null;
8011
+ // EP1091: Renamed from deviceId for platform terminology alignment
7969
8012
  this.deviceName = null;
7970
8013
  // EP661: Cached device name from server
7971
8014
  this.flyMachineId = null;
@@ -8024,9 +8067,9 @@ var Daemon = class _Daemon {
8024
8067
  this.machineId = await getMachineId();
8025
8068
  console.log(`[Daemon] Machine ID: ${this.machineId}`);
8026
8069
  const config = await (0, import_core12.loadConfig)();
8027
- if (config?.device_id) {
8028
- this.deviceId = config.device_id;
8029
- console.log(`[Daemon] Loaded cached Device ID (UUID): ${this.deviceId}`);
8070
+ if (config?.machine_uuid || config?.device_id) {
8071
+ this.machineUuid = config.machine_uuid || config.device_id || null;
8072
+ console.log(`[Daemon] Loaded cached Machine UUID: ${this.machineUuid}`);
8030
8073
  }
8031
8074
  await this.ipcServer.start();
8032
8075
  console.log("[Daemon] IPC server started");
@@ -8078,8 +8121,10 @@ var Daemon = class _Daemon {
8078
8121
  return {
8079
8122
  running: true,
8080
8123
  machineId: this.machineId,
8081
- deviceId: this.deviceId,
8082
- // EP726: UUID for unified device identification
8124
+ machineUuid: this.machineUuid,
8125
+ // EP1091: New preferred field name
8126
+ deviceId: this.machineUuid,
8127
+ // EP726: Kept for backward compatibility
8083
8128
  hostname: os8.hostname(),
8084
8129
  platform: os8.platform(),
8085
8130
  arch: os8.arch(),
@@ -8791,18 +8836,19 @@ var Daemon = class _Daemon {
8791
8836
  this.liveConnections.add(projectPath);
8792
8837
  this.pendingConnections.delete(projectPath);
8793
8838
  const authMessage = message;
8839
+ const effectiveMachineUuid = authMessage.machineUuid || authMessage.deviceId;
8794
8840
  if (authMessage.userId && authMessage.workspaceId) {
8795
- await this.configureGitUser(projectPath, authMessage.userId, authMessage.workspaceId, this.machineId, projectId, authMessage.deviceId);
8841
+ await this.configureGitUser(projectPath, authMessage.userId, authMessage.workspaceId, this.machineId, projectId, effectiveMachineUuid);
8796
8842
  await this.installGitHooks(projectPath);
8797
8843
  }
8798
8844
  if (authMessage.deviceName) {
8799
8845
  this.deviceName = authMessage.deviceName;
8800
8846
  console.log(`[Daemon] Device name: ${this.deviceName}`);
8801
8847
  }
8802
- if (authMessage.deviceId) {
8803
- this.deviceId = authMessage.deviceId;
8804
- console.log(`[Daemon] Device ID (UUID): ${this.deviceId}`);
8805
- await this.cacheDeviceId(authMessage.deviceId);
8848
+ if (effectiveMachineUuid) {
8849
+ this.machineUuid = effectiveMachineUuid;
8850
+ console.log(`[Daemon] Machine UUID: ${this.machineUuid}`);
8851
+ await this.cacheMachineUuid(effectiveMachineUuid);
8806
8852
  }
8807
8853
  if (authMessage.flyMachineId) {
8808
8854
  this.flyMachineId = authMessage.flyMachineId;
@@ -8824,9 +8870,6 @@ var Daemon = class _Daemon {
8824
8870
  this.reconcileWorktrees(projectId, projectPath, client).catch((err) => {
8825
8871
  console.warn("[Daemon] EP1003: Reconciliation report failed:", err.message);
8826
8872
  });
8827
- this.reconcilePendingCleanups(projectId, projectPath).catch((err) => {
8828
- console.warn("[Daemon] EP1047: Cleanup queue reconciliation failed:", err.message);
8829
- });
8830
8873
  });
8831
8874
  client.on("module_state_changed", async (message) => {
8832
8875
  if (message.type === "module_state_changed") {
@@ -8836,7 +8879,7 @@ var Daemon = class _Daemon {
8836
8879
  console.log(`[Daemon] EP1003: State change for non-local module ${moduleUid} (mode: ${devMode || "unknown"})`);
8837
8880
  return;
8838
8881
  }
8839
- if (checkoutMachineId && checkoutMachineId !== this.deviceId) {
8882
+ if (checkoutMachineId && checkoutMachineId !== this.machineUuid) {
8840
8883
  console.log(`[Daemon] EP1003: State change for ${moduleUid} handled by different machine: ${checkoutMachineId}`);
8841
8884
  return;
8842
8885
  }
@@ -8941,12 +8984,13 @@ var Daemon = class _Daemon {
8941
8984
  * EP595: Configure git with user and workspace ID for post-checkout hook
8942
8985
  * EP655: Added machineId for device isolation in multi-device environments
8943
8986
  * EP725: Added projectId for main branch badge tracking
8944
- * EP726: Added deviceId (UUID) for unified device identification
8987
+ * EP726: Added machineUuid (UUID) for unified device identification
8988
+ * EP1091: Renamed deviceId param to machineUuid for consistency
8945
8989
  *
8946
8990
  * This stores the IDs in .git/config so the post-checkout hook can
8947
8991
  * update module.checkout_* fields when git operations happen from terminal.
8948
8992
  */
8949
- async configureGitUser(projectPath, userId, workspaceId, machineId, projectId, deviceId) {
8993
+ async configureGitUser(projectPath, userId, workspaceId, machineId, projectId, machineUuid) {
8950
8994
  try {
8951
8995
  const { execSync: execSync9 } = await import("child_process");
8952
8996
  execSync9(`git config episoda.userId ${userId}`, {
@@ -8969,14 +9013,14 @@ var Daemon = class _Daemon {
8969
9013
  encoding: "utf8",
8970
9014
  stdio: "pipe"
8971
9015
  });
8972
- if (deviceId) {
8973
- execSync9(`git config episoda.deviceId ${deviceId}`, {
9016
+ if (machineUuid) {
9017
+ execSync9(`git config episoda.deviceId ${machineUuid}`, {
8974
9018
  cwd: projectPath,
8975
9019
  encoding: "utf8",
8976
9020
  stdio: "pipe"
8977
9021
  });
8978
9022
  }
8979
- console.log(`[Daemon] Configured git for project: episoda.userId=${userId}, machineId=${machineId}, projectId=${projectId}${deviceId ? `, deviceId=${deviceId}` : ""}`);
9023
+ console.log(`[Daemon] Configured git for project: episoda.userId=${userId}, machineId=${machineId}, projectId=${projectId}${machineUuid ? `, machineUuid=${machineUuid}` : ""}`);
8980
9024
  } catch (error) {
8981
9025
  console.warn(`[Daemon] Failed to configure git user for ${projectPath}:`, error instanceof Error ? error.message : error);
8982
9026
  }
@@ -9018,30 +9062,34 @@ var Daemon = class _Daemon {
9018
9062
  }
9019
9063
  }
9020
9064
  /**
9021
- * EP726: Cache device UUID to config file
9065
+ * EP726: Cache machine UUID to config file
9066
+ * EP1091: Renamed from cacheDeviceId, writes machine_uuid (new) and device_id (backward compat)
9022
9067
  *
9023
- * Persists the device_id (UUID) received from the server so it's available
9068
+ * Persists the machine UUID received from the server so it's available
9024
9069
  * on daemon restart without needing to re-register the device.
9025
9070
  */
9026
- async cacheDeviceId(deviceId) {
9071
+ async cacheMachineUuid(machineUuid) {
9027
9072
  try {
9028
9073
  const config = await (0, import_core12.loadConfig)();
9029
9074
  if (!config) {
9030
- console.warn("[Daemon] Cannot cache device ID - no config found");
9075
+ console.warn("[Daemon] Cannot cache machine UUID - no config found");
9031
9076
  return;
9032
9077
  }
9033
- if (config.device_id === deviceId) {
9078
+ if (config.machine_uuid === machineUuid) {
9034
9079
  return;
9035
9080
  }
9036
9081
  const updatedConfig = {
9037
9082
  ...config,
9038
- device_id: deviceId,
9083
+ machine_uuid: machineUuid,
9084
+ // EP1091: New preferred field
9085
+ device_id: machineUuid,
9086
+ // Backward compatibility
9039
9087
  machine_id: this.machineId
9040
9088
  };
9041
9089
  await (0, import_core12.saveConfig)(updatedConfig);
9042
- console.log(`[Daemon] Cached device ID to config: ${deviceId}`);
9090
+ console.log(`[Daemon] Cached machine UUID to config: ${machineUuid}`);
9043
9091
  } catch (error) {
9044
- console.warn("[Daemon] Failed to cache device ID:", error instanceof Error ? error.message : error);
9092
+ console.warn("[Daemon] Failed to cache machine UUID:", error instanceof Error ? error.message : error);
9045
9093
  }
9046
9094
  }
9047
9095
  /**
@@ -9102,14 +9150,14 @@ var Daemon = class _Daemon {
9102
9150
  */
9103
9151
  async syncMachineProjectPath(projectId, projectPath) {
9104
9152
  try {
9105
- if (!this.deviceId) {
9106
- console.warn("[Daemon] EP995: Cannot sync project path - deviceId not available");
9153
+ if (!this.machineUuid) {
9154
+ console.warn("[Daemon] EP995: Cannot sync project path - machineUuid not available");
9107
9155
  return;
9108
9156
  }
9109
9157
  const config = await (0, import_core12.loadConfig)();
9110
9158
  if (!config) return;
9111
9159
  const apiUrl = config.api_url || "https://episoda.dev";
9112
- const response = await fetchWithAuth(`${apiUrl}/api/account/machines/${this.deviceId}`, {
9160
+ const response = await fetchWithAuth(`${apiUrl}/api/account/machines/${this.machineUuid}`, {
9113
9161
  method: "PATCH",
9114
9162
  headers: {
9115
9163
  "Content-Type": "application/json"
@@ -9190,8 +9238,8 @@ var Daemon = class _Daemon {
9190
9238
  async reconcileWorktrees(projectId, projectPath, client) {
9191
9239
  console.log(`[Daemon] EP1003: Starting reconciliation report for project ${projectId}`);
9192
9240
  try {
9193
- if (!this.deviceId) {
9194
- console.log("[Daemon] EP1003: Cannot reconcile - deviceId not available yet");
9241
+ if (!this.machineUuid) {
9242
+ console.log("[Daemon] EP1003: Cannot reconcile - machineUuid not available yet");
9195
9243
  return;
9196
9244
  }
9197
9245
  const config = await (0, import_core12.loadConfig)();
@@ -9202,7 +9250,7 @@ var Daemon = class _Daemon {
9202
9250
  let modulesResponse;
9203
9251
  try {
9204
9252
  modulesResponse = await fetchWithAuth(
9205
- `${apiUrl}/api/modules?state=doing,review&dev_mode=local&checkout_machine_id=${this.deviceId}&project_id=${projectId}`,
9253
+ `${apiUrl}/api/modules?state=doing,review&dev_mode=local&checkout_machine_id=${this.machineUuid}&project_id=${projectId}`,
9206
9254
  { signal: controller.signal }
9207
9255
  );
9208
9256
  } finally {
@@ -9251,7 +9299,7 @@ var Daemon = class _Daemon {
9251
9299
  }
9252
9300
  const report = {
9253
9301
  projectId,
9254
- machineId: this.deviceId,
9302
+ machineId: this.machineUuid,
9255
9303
  modules: moduleStatuses,
9256
9304
  orphanTunnels: orphanTunnels.length > 0 ? orphanTunnels : void 0
9257
9305
  };
@@ -9272,8 +9320,11 @@ var Daemon = class _Daemon {
9272
9320
  /**
9273
9321
  * EP1047: Process pending cleanup queue entries for this machine
9274
9322
  *
9275
- * On daemon startup/reconnect, fetch pending worktree cleanup tasks from the queue
9276
- * and process them. This catches any cleanups missed while the daemon was offline.
9323
+ * @deprecated EP1091: No longer called on connect. Server-side cron now processes
9324
+ * the cleanup queue and sends WebSocket commands to connected daemons. This is
9325
+ * more reliable than daemon-side polling since it works even if daemon stays connected.
9326
+ *
9327
+ * Kept for potential manual invocation or debugging purposes.
9277
9328
  *
9278
9329
  * Flow:
9279
9330
  * 1. Query server for pending cleanup tasks for this machine
@@ -9281,11 +9332,11 @@ var Daemon = class _Daemon {
9281
9332
  * 3. Report success/failure back to server
9282
9333
  */
9283
9334
  async reconcilePendingCleanups(projectId, projectPath) {
9284
- if (!this.deviceId) {
9285
- console.log("[Daemon] EP1047: Cannot reconcile cleanups - deviceId not available yet");
9335
+ if (!this.machineUuid) {
9336
+ console.log("[Daemon] EP1047: Cannot reconcile cleanups - machineUuid not available yet");
9286
9337
  return;
9287
9338
  }
9288
- console.log(`[Daemon] EP1047: Checking for pending cleanup tasks for machine ${this.deviceId}`);
9339
+ console.log(`[Daemon] EP1047: Checking for pending cleanup tasks for machine ${this.machineUuid}`);
9289
9340
  try {
9290
9341
  const config = await (0, import_core12.loadConfig)();
9291
9342
  if (!config) {
@@ -9298,7 +9349,7 @@ var Daemon = class _Daemon {
9298
9349
  let response;
9299
9350
  try {
9300
9351
  response = await fetchWithAuth(
9301
- `${apiUrl}/api/cli/background-ops?machine_id=${this.deviceId}`,
9352
+ `${apiUrl}/api/cli/background-ops?machine_id=${this.machineUuid}`,
9302
9353
  { signal: controller.signal }
9303
9354
  );
9304
9355
  } finally {