episoda 0.2.49 → 0.2.51

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.
@@ -2277,21 +2277,36 @@ var require_websocket_client = __commonJS({
2277
2277
  }
2278
2278
  }
2279
2279
  }
2280
+ /**
2281
+ * EP1034: Remove all event handlers
2282
+ * Useful when completely resetting a client or when the daemon
2283
+ * disconnects a project and creates a fresh connection.
2284
+ * Note: Internal reconnection (scheduleReconnect) uses the same client instance,
2285
+ * so handlers persist across reconnects - this is by design.
2286
+ */
2287
+ clearAllHandlers() {
2288
+ this.eventHandlers.clear();
2289
+ }
2280
2290
  /**
2281
2291
  * Send a message to the server
2282
2292
  * @param message - Client message to send
2293
+ * @returns true if message was sent, false if not connected
2294
+ *
2295
+ * EP1034: Changed to return false instead of throwing when disconnected.
2296
+ * This prevents daemon crashes when async operations complete during reconnection.
2283
2297
  */
2284
2298
  async send(message) {
2285
2299
  if (!this.ws || !this.isConnected) {
2286
- throw new Error("WebSocket not connected");
2300
+ console.warn("[EpisodaClient] Cannot send - WebSocket not connected");
2301
+ return false;
2287
2302
  }
2288
- return new Promise((resolve3, reject) => {
2303
+ return new Promise((resolve3) => {
2289
2304
  this.ws.send(JSON.stringify(message), (error) => {
2290
2305
  if (error) {
2291
2306
  console.error("[EpisodaClient] Failed to send message:", error);
2292
- reject(error);
2307
+ resolve3(false);
2293
2308
  } else {
2294
- resolve3();
2309
+ resolve3(true);
2295
2310
  }
2296
2311
  });
2297
2312
  });
@@ -2518,7 +2533,7 @@ var require_auth = __commonJS({
2518
2533
  };
2519
2534
  })();
2520
2535
  Object.defineProperty(exports2, "__esModule", { value: true });
2521
- exports2.getConfigDir = getConfigDir7;
2536
+ exports2.getConfigDir = getConfigDir8;
2522
2537
  exports2.getConfigPath = getConfigPath;
2523
2538
  exports2.loadConfig = loadConfig7;
2524
2539
  exports2.saveConfig = saveConfig2;
@@ -2528,14 +2543,14 @@ var require_auth = __commonJS({
2528
2543
  var os7 = __importStar(require("os"));
2529
2544
  var child_process_1 = require("child_process");
2530
2545
  var DEFAULT_CONFIG_FILE = "config.json";
2531
- function getConfigDir7() {
2546
+ function getConfigDir8() {
2532
2547
  return process.env.EPISODA_CONFIG_DIR || path19.join(os7.homedir(), ".episoda");
2533
2548
  }
2534
2549
  function getConfigPath(configPath) {
2535
2550
  if (configPath) {
2536
2551
  return configPath;
2537
2552
  }
2538
- return path19.join(getConfigDir7(), DEFAULT_CONFIG_FILE);
2553
+ return path19.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
2539
2554
  }
2540
2555
  function ensureConfigDir(configPath) {
2541
2556
  const dir = path19.dirname(configPath);
@@ -2703,7 +2718,7 @@ var require_package = __commonJS({
2703
2718
  "package.json"(exports2, module2) {
2704
2719
  module2.exports = {
2705
2720
  name: "episoda",
2706
- version: "0.2.49",
2721
+ version: "0.2.51",
2707
2722
  description: "CLI tool for Episoda local development workflow orchestration",
2708
2723
  main: "dist/index.js",
2709
2724
  types: "dist/index.d.ts",
@@ -3080,11 +3095,16 @@ var IPCServer = class {
3080
3095
  };
3081
3096
 
3082
3097
  // src/daemon/daemon-process.ts
3083
- var import_core11 = __toESM(require_dist());
3098
+ var import_core12 = __toESM(require_dist());
3084
3099
 
3085
3100
  // src/utils/update-checker.ts
3086
3101
  var import_child_process2 = require("child_process");
3087
3102
  var semver = __toESM(require("semver"));
3103
+
3104
+ // src/ipc/ipc-client.ts
3105
+ var import_core5 = __toESM(require_dist());
3106
+
3107
+ // src/utils/update-checker.ts
3088
3108
  var PACKAGE_NAME = "episoda";
3089
3109
  var NPM_REGISTRY = "https://registry.npmjs.org";
3090
3110
  async function checkForUpdates(currentVersion) {
@@ -3743,12 +3763,12 @@ async function handleExec(command, projectPath) {
3743
3763
  // src/daemon/handlers/stale-commit-cleanup.ts
3744
3764
  var import_child_process4 = require("child_process");
3745
3765
  var import_util = require("util");
3746
- var import_core5 = __toESM(require_dist());
3766
+ var import_core6 = __toESM(require_dist());
3747
3767
  var execAsync = (0, import_util.promisify)(import_child_process4.exec);
3748
3768
  async function cleanupStaleCommits(projectPath) {
3749
3769
  try {
3750
3770
  const machineId = await getMachineId();
3751
- const config = await (0, import_core5.loadConfig)();
3771
+ const config = await (0, import_core6.loadConfig)();
3752
3772
  if (!config?.access_token) {
3753
3773
  return {
3754
3774
  success: false,
@@ -3998,9 +4018,9 @@ var path7 = __toESM(require("path"));
3998
4018
  var os2 = __toESM(require("os"));
3999
4019
 
4000
4020
  // src/tunnel/tunnel-api.ts
4001
- var import_core6 = __toESM(require_dist());
4021
+ var import_core7 = __toESM(require_dist());
4002
4022
  async function provisionNamedTunnel(moduleId, port = 3e3) {
4003
- const config = await (0, import_core6.loadConfig)();
4023
+ const config = await (0, import_core7.loadConfig)();
4004
4024
  if (!config?.access_token) {
4005
4025
  return { success: false, error: "Not authenticated" };
4006
4026
  }
@@ -4035,7 +4055,7 @@ async function provisionNamedTunnel(moduleId, port = 3e3) {
4035
4055
  }
4036
4056
  }
4037
4057
  async function provisionNamedTunnelByUid(moduleUid, port = 3e3) {
4038
- const config = await (0, import_core6.loadConfig)();
4058
+ const config = await (0, import_core7.loadConfig)();
4039
4059
  if (!config?.access_token) {
4040
4060
  return { success: false, error: "Not authenticated" };
4041
4061
  }
@@ -4072,7 +4092,7 @@ async function updateTunnelStatus(moduleUid, status, error) {
4072
4092
  if (!moduleUid || moduleUid === "LOCAL") {
4073
4093
  return;
4074
4094
  }
4075
- const config = await (0, import_core6.loadConfig)();
4095
+ const config = await (0, import_core7.loadConfig)();
4076
4096
  if (!config?.access_token) {
4077
4097
  return;
4078
4098
  }
@@ -4097,7 +4117,7 @@ async function clearTunnelUrl(moduleUid) {
4097
4117
  if (!moduleUid || moduleUid === "LOCAL") {
4098
4118
  return;
4099
4119
  }
4100
- const config = await (0, import_core6.loadConfig)();
4120
+ const config = await (0, import_core7.loadConfig)();
4101
4121
  if (!config?.access_token) {
4102
4122
  return;
4103
4123
  }
@@ -5254,7 +5274,7 @@ var http = __toESM(require("http"));
5254
5274
  var fs11 = __toESM(require("fs"));
5255
5275
  var path12 = __toESM(require("path"));
5256
5276
  var import_events2 = require("events");
5257
- var import_core7 = __toESM(require_dist());
5277
+ var import_core8 = __toESM(require_dist());
5258
5278
 
5259
5279
  // src/utils/port-check.ts
5260
5280
  var net2 = __toESM(require("net"));
@@ -5621,7 +5641,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
5621
5641
  // ============ Private Methods ============
5622
5642
  async fetchEnvVars(projectPath) {
5623
5643
  try {
5624
- const config = await (0, import_core7.loadConfig)();
5644
+ const config = await (0, import_core8.loadConfig)();
5625
5645
  if (!config?.access_token || !config?.project_id) {
5626
5646
  return {};
5627
5647
  }
@@ -5781,7 +5801,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
5781
5801
  return new Promise((resolve3) => setTimeout(resolve3, ms));
5782
5802
  }
5783
5803
  getLogsDir() {
5784
- const logsDir = path12.join((0, import_core7.getConfigDir)(), "logs");
5804
+ const logsDir = path12.join((0, import_core8.getConfigDir)(), "logs");
5785
5805
  if (!fs11.existsSync(logsDir)) {
5786
5806
  fs11.mkdirSync(logsDir, { recursive: true });
5787
5807
  }
@@ -6227,7 +6247,7 @@ function getPreviewManager() {
6227
6247
 
6228
6248
  // src/utils/dev-server.ts
6229
6249
  var import_child_process10 = require("child_process");
6230
- var import_core8 = __toESM(require_dist());
6250
+ var import_core9 = __toESM(require_dist());
6231
6251
  var fs12 = __toESM(require("fs"));
6232
6252
  var path13 = __toESM(require("path"));
6233
6253
  var MAX_RESTART_ATTEMPTS = 5;
@@ -6237,7 +6257,7 @@ var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
6237
6257
  var NODE_MEMORY_LIMIT_MB = 2048;
6238
6258
  var activeServers = /* @__PURE__ */ new Map();
6239
6259
  function getLogsDir() {
6240
- const logsDir = path13.join((0, import_core8.getConfigDir)(), "logs");
6260
+ const logsDir = path13.join((0, import_core9.getConfigDir)(), "logs");
6241
6261
  if (!fs12.existsSync(logsDir)) {
6242
6262
  fs12.mkdirSync(logsDir, { recursive: true });
6243
6263
  }
@@ -6438,7 +6458,7 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
6438
6458
  console.log(`[DevServer] EP932: Starting dev server for ${moduleUid} on port ${port} (auto-restart: ${autoRestart})...`);
6439
6459
  let injectedEnvVars = {};
6440
6460
  try {
6441
- const config = await (0, import_core8.loadConfig)();
6461
+ const config = await (0, import_core9.loadConfig)();
6442
6462
  if (config?.access_token && config?.project_id) {
6443
6463
  const apiUrl = config.api_url || "https://episoda.dev";
6444
6464
  const result = await fetchEnvVarsWithCache(apiUrl, config.access_token, {
@@ -6628,7 +6648,7 @@ function getPortFromPackageJson(projectPath) {
6628
6648
  // src/daemon/worktree-manager.ts
6629
6649
  var fs14 = __toESM(require("fs"));
6630
6650
  var path15 = __toESM(require("path"));
6631
- var import_core9 = __toESM(require_dist());
6651
+ var import_core10 = __toESM(require_dist());
6632
6652
  function validateModuleUid(moduleUid) {
6633
6653
  if (!moduleUid || typeof moduleUid !== "string" || !moduleUid.trim()) {
6634
6654
  return false;
@@ -6653,7 +6673,7 @@ var WorktreeManager = class _WorktreeManager {
6653
6673
  this.projectRoot = projectRoot;
6654
6674
  this.bareRepoPath = path15.join(projectRoot, ".bare");
6655
6675
  this.configPath = path15.join(projectRoot, ".episoda", "config.json");
6656
- this.gitExecutor = new import_core9.GitExecutor();
6676
+ this.gitExecutor = new import_core10.GitExecutor();
6657
6677
  }
6658
6678
  /**
6659
6679
  * Initialize worktree manager from existing project root
@@ -7271,7 +7291,7 @@ async function findProjectRoot(startPath) {
7271
7291
  var path16 = __toESM(require("path"));
7272
7292
  var fs15 = __toESM(require("fs"));
7273
7293
  var os5 = __toESM(require("os"));
7274
- var import_core10 = __toESM(require_dist());
7294
+ var import_core11 = __toESM(require_dist());
7275
7295
  function getEpisodaRoot2() {
7276
7296
  return process.env.EPISODA_ROOT || path16.join(os5.homedir(), "episoda");
7277
7297
  }
@@ -7285,7 +7305,7 @@ function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
7285
7305
  };
7286
7306
  }
7287
7307
  async function getWorktreeInfoForModule(moduleUid) {
7288
- const config = await (0, import_core10.loadConfig)();
7308
+ const config = await (0, import_core11.loadConfig)();
7289
7309
  if (!config?.workspace_slug || !config?.project_slug) {
7290
7310
  console.warn("[Worktree] Missing workspace_slug or project_slug in config");
7291
7311
  return null;
@@ -7436,7 +7456,7 @@ async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
7436
7456
  refresh_token: tokenResponse.refresh_token || config.refresh_token,
7437
7457
  expires_at: now + tokenResponse.expires_in * 1e3
7438
7458
  };
7439
- await (0, import_core11.saveConfig)(updatedConfig);
7459
+ await (0, import_core12.saveConfig)(updatedConfig);
7440
7460
  console.log("[Daemon] EP904: Access token refreshed successfully");
7441
7461
  return updatedConfig;
7442
7462
  } catch (error) {
@@ -7445,7 +7465,7 @@ async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
7445
7465
  }
7446
7466
  }
7447
7467
  async function fetchWithAuth(url, options = {}, retryOnUnauthorized = true) {
7448
- let config = await (0, import_core11.loadConfig)();
7468
+ let config = await (0, import_core12.loadConfig)();
7449
7469
  if (!config?.access_token) {
7450
7470
  throw new Error("No access token configured");
7451
7471
  }
@@ -7472,7 +7492,7 @@ async function fetchWithAuth(url, options = {}, retryOnUnauthorized = true) {
7472
7492
  }
7473
7493
  async function fetchEnvVars2() {
7474
7494
  try {
7475
- const config = await (0, import_core11.loadConfig)();
7495
+ const config = await (0, import_core12.loadConfig)();
7476
7496
  if (!config?.project_id) {
7477
7497
  console.warn("[Daemon] EP973: No project_id in config, cannot fetch env vars");
7478
7498
  return {};
@@ -7555,7 +7575,7 @@ var Daemon = class _Daemon {
7555
7575
  console.log("[Daemon] Starting Episoda daemon...");
7556
7576
  this.machineId = await getMachineId();
7557
7577
  console.log(`[Daemon] Machine ID: ${this.machineId}`);
7558
- const config = await (0, import_core11.loadConfig)();
7578
+ const config = await (0, import_core12.loadConfig)();
7559
7579
  if (config?.device_id) {
7560
7580
  this.deviceId = config.device_id;
7561
7581
  console.log(`[Daemon] Loaded cached Device ID (UUID): ${this.deviceId}`);
@@ -7692,7 +7712,7 @@ var Daemon = class _Daemon {
7692
7712
  };
7693
7713
  });
7694
7714
  this.ipcServer.on("verify-server-connection", async () => {
7695
- const config = await (0, import_core11.loadConfig)();
7715
+ const config = await (0, import_core12.loadConfig)();
7696
7716
  if (!config?.access_token || !config?.api_url) {
7697
7717
  return {
7698
7718
  verified: false,
@@ -7865,7 +7885,7 @@ var Daemon = class _Daemon {
7865
7885
  console.warn(`[Daemon] Stale connection detected for ${projectPath}, forcing reconnection`);
7866
7886
  await this.disconnectProject(projectPath);
7867
7887
  }
7868
- const config = await (0, import_core11.loadConfig)();
7888
+ const config = await (0, import_core12.loadConfig)();
7869
7889
  if (!config || !config.access_token) {
7870
7890
  throw new Error("No access token found. Please run: episoda auth");
7871
7891
  }
@@ -7886,8 +7906,8 @@ var Daemon = class _Daemon {
7886
7906
  wsUrl = `${wsProtocol}//${wsHostname}:${wsPort}`;
7887
7907
  }
7888
7908
  console.log(`[Daemon] Connecting to ${wsUrl} for project ${projectId}...`);
7889
- const client = new import_core11.EpisodaClient();
7890
- const gitExecutor = new import_core11.GitExecutor();
7909
+ const client = new import_core12.EpisodaClient();
7910
+ const gitExecutor = new import_core12.GitExecutor();
7891
7911
  const connection = {
7892
7912
  projectId,
7893
7913
  projectPath,
@@ -8057,7 +8077,7 @@ var Daemon = class _Daemon {
8057
8077
  }
8058
8078
  console.log(`[Daemon] EP1024: Using worktree path ${worktree.path} for ${cmd.moduleUid}`);
8059
8079
  const port = cmd.port || detectDevPort(worktree.path);
8060
- const devConfig = await (0, import_core11.loadConfig)();
8080
+ const devConfig = await (0, import_core12.loadConfig)();
8061
8081
  const customCommand = devConfig?.project_settings?.worktree_dev_server_script;
8062
8082
  const startResult = await previewManager.startPreview({
8063
8083
  moduleUid: cmd.moduleUid,
@@ -8474,7 +8494,7 @@ var Daemon = class _Daemon {
8474
8494
  */
8475
8495
  async cacheDeviceId(deviceId) {
8476
8496
  try {
8477
- const config = await (0, import_core11.loadConfig)();
8497
+ const config = await (0, import_core12.loadConfig)();
8478
8498
  if (!config) {
8479
8499
  console.warn("[Daemon] Cannot cache device ID - no config found");
8480
8500
  return;
@@ -8487,7 +8507,7 @@ var Daemon = class _Daemon {
8487
8507
  device_id: deviceId,
8488
8508
  machine_id: this.machineId
8489
8509
  };
8490
- await (0, import_core11.saveConfig)(updatedConfig);
8510
+ await (0, import_core12.saveConfig)(updatedConfig);
8491
8511
  console.log(`[Daemon] Cached device ID to config: ${deviceId}`);
8492
8512
  } catch (error) {
8493
8513
  console.warn("[Daemon] Failed to cache device ID:", error instanceof Error ? error.message : error);
@@ -8501,7 +8521,7 @@ var Daemon = class _Daemon {
8501
8521
  */
8502
8522
  async syncProjectSettings(projectId) {
8503
8523
  try {
8504
- const config = await (0, import_core11.loadConfig)();
8524
+ const config = await (0, import_core12.loadConfig)();
8505
8525
  if (!config) return;
8506
8526
  const apiUrl = config.api_url || "https://episoda.dev";
8507
8527
  const response = await fetchWithAuth(`${apiUrl}/api/projects/${projectId}/settings`);
@@ -8535,7 +8555,7 @@ var Daemon = class _Daemon {
8535
8555
  cached_at: Date.now()
8536
8556
  }
8537
8557
  };
8538
- await (0, import_core11.saveConfig)(updatedConfig);
8558
+ await (0, import_core12.saveConfig)(updatedConfig);
8539
8559
  console.log(`[Daemon] EP973: Project settings synced (slugs: ${projectSlug}/${workspaceSlug})`);
8540
8560
  }
8541
8561
  } catch (error) {
@@ -8555,7 +8575,7 @@ var Daemon = class _Daemon {
8555
8575
  console.warn("[Daemon] EP995: Cannot sync project path - deviceId not available");
8556
8576
  return;
8557
8577
  }
8558
- const config = await (0, import_core11.loadConfig)();
8578
+ const config = await (0, import_core12.loadConfig)();
8559
8579
  if (!config) return;
8560
8580
  const apiUrl = config.api_url || "https://episoda.dev";
8561
8581
  const response = await fetchWithAuth(`${apiUrl}/api/account/machines/${this.deviceId}`, {
@@ -8588,7 +8608,7 @@ var Daemon = class _Daemon {
8588
8608
  */
8589
8609
  async updateModuleWorktreeStatus(moduleUid, status, worktreePath, errorMessage) {
8590
8610
  try {
8591
- const config = await (0, import_core11.loadConfig)();
8611
+ const config = await (0, import_core12.loadConfig)();
8592
8612
  if (!config) return;
8593
8613
  const apiUrl = config.api_url || "https://episoda.dev";
8594
8614
  const body = {
@@ -8643,7 +8663,7 @@ var Daemon = class _Daemon {
8643
8663
  console.log("[Daemon] EP1003: Cannot reconcile - deviceId not available yet");
8644
8664
  return;
8645
8665
  }
8646
- const config = await (0, import_core11.loadConfig)();
8666
+ const config = await (0, import_core12.loadConfig)();
8647
8667
  if (!config) return;
8648
8668
  const apiUrl = config.api_url || "https://episoda.dev";
8649
8669
  const controller = new AbortController();
@@ -8729,7 +8749,7 @@ var Daemon = class _Daemon {
8729
8749
  try {
8730
8750
  const envVars = await fetchEnvVars2();
8731
8751
  console.log(`[Daemon] EP1002: Fetched ${Object.keys(envVars).length} env vars for ${moduleUid}`);
8732
- const config = await (0, import_core11.loadConfig)();
8752
+ const config = await (0, import_core12.loadConfig)();
8733
8753
  const setupConfig = config?.project_settings;
8734
8754
  await this.runWorktreeSetupSync(
8735
8755
  moduleUid,
@@ -8907,7 +8927,7 @@ var Daemon = class _Daemon {
8907
8927
  }
8908
8928
  this.healthCheckInProgress = true;
8909
8929
  try {
8910
- const config = await (0, import_core11.loadConfig)();
8930
+ const config = await (0, import_core12.loadConfig)();
8911
8931
  if (config?.access_token) {
8912
8932
  await this.performHealthChecks(config);
8913
8933
  }
@@ -9026,7 +9046,7 @@ var Daemon = class _Daemon {
9026
9046
  */
9027
9047
  async fetchActiveModuleUids(projectId) {
9028
9048
  try {
9029
- const config = await (0, import_core11.loadConfig)();
9049
+ const config = await (0, import_core12.loadConfig)();
9030
9050
  if (!config?.access_token || !config?.api_url) {
9031
9051
  return null;
9032
9052
  }
@@ -9129,7 +9149,7 @@ var Daemon = class _Daemon {
9129
9149
  async restartTunnel(moduleUid, port) {
9130
9150
  const previewManager = getPreviewManager();
9131
9151
  try {
9132
- const config = await (0, import_core11.loadConfig)();
9152
+ const config = await (0, import_core12.loadConfig)();
9133
9153
  if (!config?.access_token) {
9134
9154
  console.error(`[Daemon] EP833: No access token for tunnel restart`);
9135
9155
  return;