episoda 0.2.27 → 0.2.29

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
@@ -2649,7 +2649,7 @@ var require_dist = __commonJS({
2649
2649
 
2650
2650
  // src/index.ts
2651
2651
  var import_commander = require("commander");
2652
- var import_core14 = __toESM(require_dist());
2652
+ var import_core15 = __toESM(require_dist());
2653
2653
 
2654
2654
  // src/commands/dev.ts
2655
2655
  var import_core4 = __toESM(require_dist());
@@ -3671,8 +3671,8 @@ var WorktreeManager = class _WorktreeManager {
3671
3671
  console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
3672
3672
  console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
3673
3673
  try {
3674
- const { execSync: execSync7 } = require("child_process");
3675
- execSync7(script, {
3674
+ const { execSync: execSync9 } = require("child_process");
3675
+ execSync9(script, {
3676
3676
  cwd: worktree.worktreePath,
3677
3677
  stdio: "inherit",
3678
3678
  timeout: TIMEOUT_MINUTES * 60 * 1e3,
@@ -3706,8 +3706,8 @@ var WorktreeManager = class _WorktreeManager {
3706
3706
  console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
3707
3707
  console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
3708
3708
  try {
3709
- const { execSync: execSync7 } = require("child_process");
3710
- execSync7(script, {
3709
+ const { execSync: execSync9 } = require("child_process");
3710
+ execSync9(script, {
3711
3711
  cwd: worktree.worktreePath,
3712
3712
  stdio: "inherit",
3713
3713
  timeout: TIMEOUT_MINUTES * 60 * 1e3,
@@ -4886,6 +4886,60 @@ ${exportLine}
4886
4886
 
4887
4887
  // src/commands/status.ts
4888
4888
  var import_core8 = __toESM(require_dist());
4889
+
4890
+ // src/utils/update-checker.ts
4891
+ var import_child_process7 = require("child_process");
4892
+ var semver = __toESM(require("semver"));
4893
+ var PACKAGE_NAME = "episoda";
4894
+ var NPM_REGISTRY = "https://registry.npmjs.org";
4895
+ async function checkForUpdates(currentVersion) {
4896
+ try {
4897
+ const controller = new AbortController();
4898
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
4899
+ const response = await fetch(`${NPM_REGISTRY}/${PACKAGE_NAME}/latest`, {
4900
+ signal: controller.signal
4901
+ });
4902
+ clearTimeout(timeoutId);
4903
+ if (!response.ok) {
4904
+ return { currentVersion, latestVersion: currentVersion, updateAvailable: false };
4905
+ }
4906
+ const data = await response.json();
4907
+ const latestVersion = data.version;
4908
+ return {
4909
+ currentVersion,
4910
+ latestVersion,
4911
+ updateAvailable: semver.gt(latestVersion, currentVersion)
4912
+ };
4913
+ } catch (error) {
4914
+ return { currentVersion, latestVersion: currentVersion, updateAvailable: false, offline: true };
4915
+ }
4916
+ }
4917
+ function performBackgroundUpdate() {
4918
+ try {
4919
+ const child = (0, import_child_process7.spawn)("npm", ["update", "-g", PACKAGE_NAME], {
4920
+ detached: true,
4921
+ stdio: "ignore",
4922
+ // Use shell on Windows for proper npm execution
4923
+ shell: process.platform === "win32"
4924
+ });
4925
+ child.unref();
4926
+ } catch (error) {
4927
+ }
4928
+ }
4929
+ function getInstalledVersion() {
4930
+ try {
4931
+ const output = (0, import_child_process7.execSync)(`npm list -g ${PACKAGE_NAME} --json`, {
4932
+ stdio: ["pipe", "pipe", "pipe"],
4933
+ timeout: 1e4
4934
+ }).toString();
4935
+ const data = JSON.parse(output);
4936
+ return data?.dependencies?.[PACKAGE_NAME]?.version || null;
4937
+ } catch {
4938
+ return null;
4939
+ }
4940
+ }
4941
+
4942
+ // src/commands/status.ts
4889
4943
  async function statusCommand(options = {}) {
4890
4944
  status.info("Checking CLI status...");
4891
4945
  status.info("");
@@ -4904,7 +4958,17 @@ async function statusCommand(options = {}) {
4904
4958
  status.info("Configuration:");
4905
4959
  status.info(` Project ID: ${config.project_id}`);
4906
4960
  status.info(` API URL: ${config.api_url}`);
4907
- status.info(` CLI Version: ${import_core8.VERSION}`);
4961
+ if (!options.skipUpdateCheck) {
4962
+ const updateResult = await checkForUpdates(import_core8.VERSION);
4963
+ if (updateResult.updateAvailable) {
4964
+ status.info(` CLI Version: ${import_core8.VERSION} \u2B06 updating to ${updateResult.latestVersion}...`);
4965
+ performBackgroundUpdate();
4966
+ } else {
4967
+ status.info(` CLI Version: ${import_core8.VERSION} \u2713`);
4968
+ }
4969
+ } else {
4970
+ status.info(` CLI Version: ${import_core8.VERSION}`);
4971
+ }
4908
4972
  status.info(` Config file: ${(0, import_core8.getConfigPath)()}`);
4909
4973
  status.info("");
4910
4974
  if (!config.access_token || config.access_token === "") {
@@ -5797,8 +5861,142 @@ async function listWorktrees(options) {
5797
5861
  console.log("");
5798
5862
  }
5799
5863
 
5864
+ // src/commands/update.ts
5865
+ var import_child_process8 = require("child_process");
5866
+ var import_core14 = __toESM(require_dist());
5867
+ async function isDaemonRunning2() {
5868
+ try {
5869
+ const daemonStatus = await getStatus();
5870
+ return !!daemonStatus;
5871
+ } catch {
5872
+ return false;
5873
+ }
5874
+ }
5875
+ function stopDaemon2() {
5876
+ try {
5877
+ (0, import_child_process8.execSync)("episoda stop", { stdio: "pipe" });
5878
+ return true;
5879
+ } catch {
5880
+ return false;
5881
+ }
5882
+ }
5883
+ function startDaemon2() {
5884
+ try {
5885
+ const child = (0, import_child_process8.spawn)("episoda", ["dev"], {
5886
+ detached: true,
5887
+ stdio: "ignore",
5888
+ shell: process.platform === "win32"
5889
+ });
5890
+ child.unref();
5891
+ return true;
5892
+ } catch {
5893
+ return false;
5894
+ }
5895
+ }
5896
+ function performUpdate() {
5897
+ try {
5898
+ (0, import_child_process8.execSync)("npm update -g episoda", {
5899
+ stdio: "pipe",
5900
+ timeout: 12e4
5901
+ // 2 minute timeout
5902
+ });
5903
+ return { success: true };
5904
+ } catch (error) {
5905
+ const message = error instanceof Error ? error.message : String(error);
5906
+ if (message.includes("EACCES") || message.includes("permission denied")) {
5907
+ return {
5908
+ success: false,
5909
+ error: "Permission denied. Try running with sudo:\n sudo npm update -g episoda"
5910
+ };
5911
+ }
5912
+ return { success: false, error: message };
5913
+ }
5914
+ }
5915
+ async function updateCommand(options = {}) {
5916
+ const currentVersion = import_core14.VERSION;
5917
+ status.info(`Current version: ${currentVersion}`);
5918
+ status.info("Checking for updates...");
5919
+ const result = await checkForUpdates(currentVersion);
5920
+ if (result.offline) {
5921
+ status.warning("\u26A0 Could not check for updates (offline or network error)");
5922
+ status.info(` Current version: ${currentVersion}`);
5923
+ status.info("");
5924
+ status.info("Check your internet connection and try again.");
5925
+ return;
5926
+ }
5927
+ if (!result.updateAvailable) {
5928
+ status.success(`\u2713 Already up to date (${currentVersion})`);
5929
+ return;
5930
+ }
5931
+ status.info(`Update available: ${result.currentVersion} \u2192 ${result.latestVersion}`);
5932
+ if (options.check) {
5933
+ status.info("");
5934
+ status.info('Run "episoda update" to install the update.');
5935
+ return;
5936
+ }
5937
+ if (!options.yes) {
5938
+ status.info("");
5939
+ status.info("To update, run:");
5940
+ status.info(" episoda update -y");
5941
+ status.info("");
5942
+ status.info("Or with daemon restart:");
5943
+ status.info(" episoda update -y --restart");
5944
+ return;
5945
+ }
5946
+ const daemonWasRunning = options.restart ? await isDaemonRunning2() : false;
5947
+ if (options.restart && daemonWasRunning) {
5948
+ status.info("Stopping daemon before update...");
5949
+ if (!stopDaemon2()) {
5950
+ status.warning("Could not stop daemon gracefully, continuing with update...");
5951
+ }
5952
+ }
5953
+ status.info("Updating...");
5954
+ const updateResult = performUpdate();
5955
+ if (!updateResult.success) {
5956
+ status.error(`\u2717 Update failed: ${updateResult.error}`);
5957
+ if (options.restart && daemonWasRunning) {
5958
+ status.info("Restarting daemon...");
5959
+ startDaemon2();
5960
+ }
5961
+ process.exit(1);
5962
+ }
5963
+ const installedVersion = getInstalledVersion();
5964
+ if (installedVersion && installedVersion !== result.latestVersion) {
5965
+ status.warning(`\u26A0 Update completed but version mismatch detected`);
5966
+ status.info(` Expected: ${result.latestVersion}`);
5967
+ status.info(` Installed: ${installedVersion}`);
5968
+ status.info("");
5969
+ status.info("Try running manually: npm update -g episoda");
5970
+ } else if (installedVersion) {
5971
+ status.success(`\u2713 Updated to ${installedVersion}`);
5972
+ } else {
5973
+ status.success(`\u2713 Updated to ${result.latestVersion}`);
5974
+ }
5975
+ if (options.restart && daemonWasRunning) {
5976
+ status.info("Restarting daemon...");
5977
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
5978
+ if (startDaemon2()) {
5979
+ status.success("\u2713 Daemon restarted");
5980
+ status.info("");
5981
+ status.info('Run "episoda status" to verify connection.');
5982
+ } else {
5983
+ status.warning("Could not restart daemon automatically.");
5984
+ status.info('Run "episoda dev" to start the daemon.');
5985
+ }
5986
+ } else if (options.restart) {
5987
+ status.info("Daemon was not running, skipping restart.");
5988
+ } else {
5989
+ const daemonRunning = await isDaemonRunning2();
5990
+ if (daemonRunning) {
5991
+ status.info("");
5992
+ status.info("Note: Restart the daemon to use the new version:");
5993
+ status.info(" episoda stop && episoda dev");
5994
+ }
5995
+ }
5996
+ }
5997
+
5800
5998
  // src/index.ts
5801
- import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core14.VERSION);
5999
+ import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core15.VERSION);
5802
6000
  import_commander.program.command("auth").description("Authenticate to Episoda via OAuth and configure CLI").option("--api-url <url>", "API URL (default: https://episoda.dev)").action(async (options) => {
5803
6001
  try {
5804
6002
  await authCommand(options);
@@ -5830,9 +6028,13 @@ import_commander.program.command("dev").description("Connect to Episoda and star
5830
6028
  process.exit(1);
5831
6029
  }
5832
6030
  });
5833
- import_commander.program.command("status").description("Show connection status").option("--verify", "Verify connection is healthy (not just connected)").option("--local", "Only check local daemon state (faster, but may be stale)").action(async (options) => {
6031
+ import_commander.program.command("status").description("Show connection status").option("--verify", "Verify connection is healthy (not just connected)").option("--local", "Only check local daemon state (faster, but may be stale)").option("--skip-update-check", "Skip CLI version update check (faster)").action(async (options) => {
5834
6032
  try {
5835
- await statusCommand({ verify: options.verify, local: options.local });
6033
+ await statusCommand({
6034
+ verify: options.verify,
6035
+ local: options.local,
6036
+ skipUpdateCheck: options.skipUpdateCheck
6037
+ });
5836
6038
  } catch (error) {
5837
6039
  status.error(`Status check failed: ${error instanceof Error ? error.message : String(error)}`);
5838
6040
  process.exit(1);
@@ -5854,6 +6056,14 @@ import_commander.program.command("disconnect").description("Disconnect from epis
5854
6056
  process.exit(1);
5855
6057
  }
5856
6058
  });
6059
+ import_commander.program.command("update").description("Check for updates and optionally install them").option("-y, --yes", "Auto-update without prompting").option("--check", "Just check for updates, do not install").option("--restart", "Restart daemon after update if it was running").action(async (options) => {
6060
+ try {
6061
+ await updateCommand(options);
6062
+ } catch (error) {
6063
+ status.error(`Update failed: ${error instanceof Error ? error.message : String(error)}`);
6064
+ process.exit(1);
6065
+ }
6066
+ });
5857
6067
  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) => {
5858
6068
  try {
5859
6069
  await cloneCommand(slug, options);