episoda 0.2.32 → 0.2.34

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
@@ -1549,15 +1549,15 @@ var require_git_executor = __commonJS({
1549
1549
  try {
1550
1550
  const { stdout: gitDir } = await execAsync("git rev-parse --git-dir", { cwd, timeout: 5e3 });
1551
1551
  const gitDirPath = gitDir.trim();
1552
- const fs13 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1552
+ const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1553
1553
  const rebaseMergePath = `${gitDirPath}/rebase-merge`;
1554
1554
  const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
1555
1555
  try {
1556
- await fs13.access(rebaseMergePath);
1556
+ await fs15.access(rebaseMergePath);
1557
1557
  inRebase = true;
1558
1558
  } catch {
1559
1559
  try {
1560
- await fs13.access(rebaseApplyPath);
1560
+ await fs15.access(rebaseApplyPath);
1561
1561
  inRebase = true;
1562
1562
  } catch {
1563
1563
  inRebase = false;
@@ -1611,9 +1611,9 @@ var require_git_executor = __commonJS({
1611
1611
  error: validation.error || "UNKNOWN_ERROR"
1612
1612
  };
1613
1613
  }
1614
- const fs13 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1614
+ const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1615
1615
  try {
1616
- await fs13.access(command.path);
1616
+ await fs15.access(command.path);
1617
1617
  return {
1618
1618
  success: false,
1619
1619
  error: "WORKTREE_EXISTS",
@@ -1628,6 +1628,9 @@ var require_git_executor = __commonJS({
1628
1628
  const args = ["worktree", "add"];
1629
1629
  if (command.create) {
1630
1630
  args.push("-b", command.branch, command.path);
1631
+ if (command.startPoint) {
1632
+ args.push(command.startPoint);
1633
+ }
1631
1634
  } else {
1632
1635
  args.push(command.path, command.branch);
1633
1636
  }
@@ -1664,9 +1667,9 @@ var require_git_executor = __commonJS({
1664
1667
  */
1665
1668
  async executeWorktreeRemove(command, cwd, options) {
1666
1669
  try {
1667
- const fs13 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1670
+ const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1668
1671
  try {
1669
- await fs13.access(command.path);
1672
+ await fs15.access(command.path);
1670
1673
  } catch {
1671
1674
  return {
1672
1675
  success: false,
@@ -1819,10 +1822,10 @@ var require_git_executor = __commonJS({
1819
1822
  */
1820
1823
  async executeCloneBare(command, options) {
1821
1824
  try {
1822
- const fs13 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1823
- const path15 = await Promise.resolve().then(() => __importStar(require("path")));
1825
+ const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1826
+ const path17 = await Promise.resolve().then(() => __importStar(require("path")));
1824
1827
  try {
1825
- await fs13.access(command.path);
1828
+ await fs15.access(command.path);
1826
1829
  return {
1827
1830
  success: false,
1828
1831
  error: "BRANCH_ALREADY_EXISTS",
@@ -1831,9 +1834,9 @@ var require_git_executor = __commonJS({
1831
1834
  };
1832
1835
  } catch {
1833
1836
  }
1834
- const parentDir = path15.dirname(command.path);
1837
+ const parentDir = path17.dirname(command.path);
1835
1838
  try {
1836
- await fs13.mkdir(parentDir, { recursive: true });
1839
+ await fs15.mkdir(parentDir, { recursive: true });
1837
1840
  } catch {
1838
1841
  }
1839
1842
  const { stdout, stderr } = await execAsync(
@@ -1876,22 +1879,22 @@ var require_git_executor = __commonJS({
1876
1879
  */
1877
1880
  async executeProjectInfo(cwd, options) {
1878
1881
  try {
1879
- const fs13 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1880
- const path15 = await Promise.resolve().then(() => __importStar(require("path")));
1882
+ const fs15 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1883
+ const path17 = await Promise.resolve().then(() => __importStar(require("path")));
1881
1884
  let currentPath = cwd;
1882
1885
  let projectPath = cwd;
1883
1886
  let bareRepoPath;
1884
1887
  for (let i = 0; i < 10; i++) {
1885
- const bareDir = path15.join(currentPath, ".bare");
1886
- const episodaDir = path15.join(currentPath, ".episoda");
1888
+ const bareDir = path17.join(currentPath, ".bare");
1889
+ const episodaDir = path17.join(currentPath, ".episoda");
1887
1890
  try {
1888
- await fs13.access(bareDir);
1889
- await fs13.access(episodaDir);
1891
+ await fs15.access(bareDir);
1892
+ await fs15.access(episodaDir);
1890
1893
  projectPath = currentPath;
1891
1894
  bareRepoPath = bareDir;
1892
1895
  break;
1893
1896
  } catch {
1894
- const parentPath = path15.dirname(currentPath);
1897
+ const parentPath = path17.dirname(currentPath);
1895
1898
  if (parentPath === currentPath) {
1896
1899
  break;
1897
1900
  }
@@ -2104,7 +2107,7 @@ var require_websocket_client = __commonJS({
2104
2107
  clearTimeout(this.reconnectTimeout);
2105
2108
  this.reconnectTimeout = void 0;
2106
2109
  }
2107
- return new Promise((resolve4, reject) => {
2110
+ return new Promise((resolve5, reject) => {
2108
2111
  const connectionTimeout = setTimeout(() => {
2109
2112
  if (this.ws) {
2110
2113
  this.ws.terminate();
@@ -2131,7 +2134,7 @@ var require_websocket_client = __commonJS({
2131
2134
  daemonPid: this.daemonPid
2132
2135
  });
2133
2136
  this.startHeartbeat();
2134
- resolve4();
2137
+ resolve5();
2135
2138
  });
2136
2139
  this.ws.on("pong", () => {
2137
2140
  if (this.heartbeatTimeoutTimer) {
@@ -2247,13 +2250,13 @@ var require_websocket_client = __commonJS({
2247
2250
  if (!this.ws || !this.isConnected) {
2248
2251
  throw new Error("WebSocket not connected");
2249
2252
  }
2250
- return new Promise((resolve4, reject) => {
2253
+ return new Promise((resolve5, reject) => {
2251
2254
  this.ws.send(JSON.stringify(message), (error) => {
2252
2255
  if (error) {
2253
2256
  console.error("[EpisodaClient] Failed to send message:", error);
2254
2257
  reject(error);
2255
2258
  } else {
2256
- resolve4();
2259
+ resolve5();
2257
2260
  }
2258
2261
  });
2259
2262
  });
@@ -2482,34 +2485,34 @@ var require_auth = __commonJS({
2482
2485
  Object.defineProperty(exports2, "__esModule", { value: true });
2483
2486
  exports2.getConfigDir = getConfigDir5;
2484
2487
  exports2.getConfigPath = getConfigPath4;
2485
- exports2.loadConfig = loadConfig6;
2488
+ exports2.loadConfig = loadConfig9;
2486
2489
  exports2.saveConfig = saveConfig3;
2487
2490
  exports2.validateToken = validateToken;
2488
- var fs13 = __importStar(require("fs"));
2489
- var path15 = __importStar(require("path"));
2490
- var os3 = __importStar(require("os"));
2491
+ var fs15 = __importStar(require("fs"));
2492
+ var path17 = __importStar(require("path"));
2493
+ var os4 = __importStar(require("os"));
2491
2494
  var child_process_1 = require("child_process");
2492
2495
  var DEFAULT_CONFIG_FILE = "config.json";
2493
2496
  function getConfigDir5() {
2494
- return process.env.EPISODA_CONFIG_DIR || path15.join(os3.homedir(), ".episoda");
2497
+ return process.env.EPISODA_CONFIG_DIR || path17.join(os4.homedir(), ".episoda");
2495
2498
  }
2496
2499
  function getConfigPath4(configPath) {
2497
2500
  if (configPath) {
2498
2501
  return configPath;
2499
2502
  }
2500
- return path15.join(getConfigDir5(), DEFAULT_CONFIG_FILE);
2503
+ return path17.join(getConfigDir5(), DEFAULT_CONFIG_FILE);
2501
2504
  }
2502
2505
  function ensureConfigDir(configPath) {
2503
- const dir = path15.dirname(configPath);
2504
- const isNew = !fs13.existsSync(dir);
2506
+ const dir = path17.dirname(configPath);
2507
+ const isNew = !fs15.existsSync(dir);
2505
2508
  if (isNew) {
2506
- fs13.mkdirSync(dir, { recursive: true, mode: 448 });
2509
+ fs15.mkdirSync(dir, { recursive: true, mode: 448 });
2507
2510
  }
2508
2511
  if (process.platform === "darwin") {
2509
- const nosyncPath = path15.join(dir, ".nosync");
2510
- if (isNew || !fs13.existsSync(nosyncPath)) {
2512
+ const nosyncPath = path17.join(dir, ".nosync");
2513
+ if (isNew || !fs15.existsSync(nosyncPath)) {
2511
2514
  try {
2512
- fs13.writeFileSync(nosyncPath, "", { mode: 384 });
2515
+ fs15.writeFileSync(nosyncPath, "", { mode: 384 });
2513
2516
  (0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
2514
2517
  stdio: "ignore",
2515
2518
  timeout: 5e3
@@ -2519,13 +2522,13 @@ var require_auth = __commonJS({
2519
2522
  }
2520
2523
  }
2521
2524
  }
2522
- async function loadConfig6(configPath) {
2525
+ async function loadConfig9(configPath) {
2523
2526
  const fullPath = getConfigPath4(configPath);
2524
- if (!fs13.existsSync(fullPath)) {
2527
+ if (!fs15.existsSync(fullPath)) {
2525
2528
  return null;
2526
2529
  }
2527
2530
  try {
2528
- const content = fs13.readFileSync(fullPath, "utf8");
2531
+ const content = fs15.readFileSync(fullPath, "utf8");
2529
2532
  const config = JSON.parse(content);
2530
2533
  return config;
2531
2534
  } catch (error) {
@@ -2538,7 +2541,7 @@ var require_auth = __commonJS({
2538
2541
  ensureConfigDir(fullPath);
2539
2542
  try {
2540
2543
  const content = JSON.stringify(config, null, 2);
2541
- fs13.writeFileSync(fullPath, content, { mode: 384 });
2544
+ fs15.writeFileSync(fullPath, content, { mode: 384 });
2542
2545
  } catch (error) {
2543
2546
  throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
2544
2547
  }
@@ -2649,7 +2652,7 @@ var require_dist = __commonJS({
2649
2652
 
2650
2653
  // src/index.ts
2651
2654
  var import_commander = require("commander");
2652
- var import_core15 = __toESM(require_dist());
2655
+ var import_core18 = __toESM(require_dist());
2653
2656
 
2654
2657
  // src/commands/dev.ts
2655
2658
  var import_core4 = __toESM(require_dist());
@@ -2986,7 +2989,7 @@ async function startDaemon() {
2986
2989
  const pid = child.pid;
2987
2990
  const pidPath = getPidFilePath();
2988
2991
  fs2.writeFileSync(pidPath, pid.toString(), "utf-8");
2989
- await new Promise((resolve4) => setTimeout(resolve4, 500));
2992
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
2990
2993
  const runningPid = isDaemonRunning();
2991
2994
  if (!runningPid) {
2992
2995
  throw new Error("Daemon failed to start");
@@ -3008,7 +3011,7 @@ async function stopDaemon(timeout = 5e3) {
3008
3011
  while (Date.now() - startTime < timeout) {
3009
3012
  try {
3010
3013
  process.kill(pid, 0);
3011
- await new Promise((resolve4) => setTimeout(resolve4, 100));
3014
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
3012
3015
  } catch (error) {
3013
3016
  const pidPath2 = getPidFilePath();
3014
3017
  if (fs2.existsSync(pidPath2)) {
@@ -3038,7 +3041,7 @@ var import_core2 = __toESM(require_dist());
3038
3041
  var getSocketPath = () => path3.join((0, import_core2.getConfigDir)(), "daemon.sock");
3039
3042
  var DEFAULT_TIMEOUT = 15e3;
3040
3043
  async function sendCommand(command, params, timeout = DEFAULT_TIMEOUT) {
3041
- return new Promise((resolve4, reject) => {
3044
+ return new Promise((resolve5, reject) => {
3042
3045
  const socket = net.createConnection(getSocketPath());
3043
3046
  const requestId = crypto.randomUUID();
3044
3047
  let buffer = "";
@@ -3069,7 +3072,7 @@ async function sendCommand(command, params, timeout = DEFAULT_TIMEOUT) {
3069
3072
  clearTimeout(timeoutHandle);
3070
3073
  socket.end();
3071
3074
  if (response.success) {
3072
- resolve4(response.data);
3075
+ resolve5(response.data);
3073
3076
  } else {
3074
3077
  reject(new Error(response.error || "Command failed"));
3075
3078
  }
@@ -3132,18 +3135,18 @@ var fs5 = __toESM(require("fs"));
3132
3135
  // src/utils/port-check.ts
3133
3136
  var net2 = __toESM(require("net"));
3134
3137
  async function isPortInUse(port) {
3135
- return new Promise((resolve4) => {
3138
+ return new Promise((resolve5) => {
3136
3139
  const server = net2.createServer();
3137
3140
  server.once("error", (err) => {
3138
3141
  if (err.code === "EADDRINUSE") {
3139
- resolve4(true);
3142
+ resolve5(true);
3140
3143
  } else {
3141
- resolve4(false);
3144
+ resolve5(false);
3142
3145
  }
3143
3146
  });
3144
3147
  server.once("listening", () => {
3145
3148
  server.close();
3146
- resolve4(false);
3149
+ resolve5(false);
3147
3150
  });
3148
3151
  server.listen(port);
3149
3152
  });
@@ -3262,14 +3265,19 @@ var WorktreeManager = class _WorktreeManager {
3262
3265
  action: "fetch",
3263
3266
  remote: "origin"
3264
3267
  }, { cwd: this.bareRepoPath });
3265
- if (!fetchResult.success) {
3266
- console.warn("[worktree-manager] Failed to fetch from origin:", fetchResult.output);
3268
+ if (!fetchResult.success && createBranch) {
3269
+ console.error("[worktree-manager] Failed to fetch from origin:", fetchResult.output);
3270
+ return {
3271
+ success: false,
3272
+ error: "Failed to fetch latest refs from origin. Cannot create worktree with stale refs."
3273
+ };
3267
3274
  }
3268
3275
  const result = await this.gitExecutor.execute({
3269
3276
  action: "worktree_add",
3270
3277
  path: worktreePath,
3271
3278
  branch: branchName,
3272
- create: createBranch
3279
+ create: createBranch,
3280
+ startPoint: createBranch ? "origin/main" : void 0
3273
3281
  }, { cwd: this.bareRepoPath });
3274
3282
  if (!result.success) {
3275
3283
  return {
@@ -3510,7 +3518,7 @@ var WorktreeManager = class _WorktreeManager {
3510
3518
  const lockContent = fs3.readFileSync(lockPath, "utf-8").trim();
3511
3519
  const lockPid = parseInt(lockContent, 10);
3512
3520
  if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
3513
- await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
3521
+ await new Promise((resolve5) => setTimeout(resolve5, retryInterval));
3514
3522
  continue;
3515
3523
  }
3516
3524
  } catch {
@@ -3524,7 +3532,7 @@ var WorktreeManager = class _WorktreeManager {
3524
3532
  } catch {
3525
3533
  continue;
3526
3534
  }
3527
- await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
3535
+ await new Promise((resolve5) => setTimeout(resolve5, retryInterval));
3528
3536
  continue;
3529
3537
  }
3530
3538
  throw err;
@@ -3791,7 +3799,7 @@ async function fetchProjectPath(config, projectId) {
3791
3799
  return null;
3792
3800
  }
3793
3801
  }
3794
- async function syncProjectPath(config, projectId, path15) {
3802
+ async function syncProjectPath(config, projectId, path17) {
3795
3803
  if (!config.device_id || !config.access_token) {
3796
3804
  return false;
3797
3805
  }
@@ -3804,7 +3812,7 @@ async function syncProjectPath(config, projectId, path15) {
3804
3812
  "Authorization": `Bearer ${config.access_token}`,
3805
3813
  "Content-Type": "application/json"
3806
3814
  },
3807
- body: JSON.stringify({ path: path15 })
3815
+ body: JSON.stringify({ path: path17 })
3808
3816
  });
3809
3817
  if (!response.ok) {
3810
3818
  console.debug(`[MachineSettings] syncProjectPath failed: ${response.status} ${response.statusText}`);
@@ -3895,7 +3903,7 @@ async function devCommand(options = {}) {
3895
3903
  const killedCount = killAllEpisodaProcesses();
3896
3904
  if (killedCount > 0) {
3897
3905
  status.info(`Cleaned up ${killedCount} stale process${killedCount > 1 ? "es" : ""}`);
3898
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
3906
+ await new Promise((resolve5) => setTimeout(resolve5, 2e3));
3899
3907
  }
3900
3908
  }
3901
3909
  let projectPath;
@@ -3955,7 +3963,7 @@ async function devCommand(options = {}) {
3955
3963
  for (let retry = 0; retry < CONNECTION_MAX_RETRIES && !connected; retry++) {
3956
3964
  if (retry > 0) {
3957
3965
  status.info(`Retrying connection (attempt ${retry + 1}/${CONNECTION_MAX_RETRIES})...`);
3958
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
3966
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
3959
3967
  }
3960
3968
  try {
3961
3969
  const result = await addProject(config.project_id, projectPath);
@@ -4578,10 +4586,10 @@ async function initiateDeviceFlow(apiUrl, machineId) {
4578
4586
  return await response.json();
4579
4587
  }
4580
4588
  async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
4581
- return new Promise((resolve4) => {
4589
+ return new Promise((resolve5) => {
4582
4590
  const timeout = setTimeout(() => {
4583
4591
  status.error("Authorization timed out");
4584
- resolve4(false);
4592
+ resolve5(false);
4585
4593
  }, expiresIn * 1e3);
4586
4594
  const url = `${apiUrl}/api/oauth/authorize-stream?device_code=${deviceCode}`;
4587
4595
  const curlProcess = (0, import_child_process5.spawn)("curl", ["-N", url]);
@@ -4607,26 +4615,26 @@ async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
4607
4615
  if (eventType === "authorized") {
4608
4616
  clearTimeout(timeout);
4609
4617
  curlProcess.kill();
4610
- resolve4(true);
4618
+ resolve5(true);
4611
4619
  return;
4612
4620
  } else if (eventType === "denied") {
4613
4621
  clearTimeout(timeout);
4614
4622
  curlProcess.kill();
4615
4623
  status.error("Authorization denied by user");
4616
- resolve4(false);
4624
+ resolve5(false);
4617
4625
  return;
4618
4626
  } else if (eventType === "expired") {
4619
4627
  clearTimeout(timeout);
4620
4628
  curlProcess.kill();
4621
4629
  status.error("Authorization code expired");
4622
- resolve4(false);
4630
+ resolve5(false);
4623
4631
  return;
4624
4632
  } else if (eventType === "error") {
4625
4633
  const errorData = JSON.parse(data);
4626
4634
  clearTimeout(timeout);
4627
4635
  curlProcess.kill();
4628
4636
  status.error(`Authorization error: ${errorData.error_description || errorData.error || "Unknown error"}`);
4629
- resolve4(false);
4637
+ resolve5(false);
4630
4638
  return;
4631
4639
  }
4632
4640
  } catch (error) {
@@ -4636,13 +4644,13 @@ async function monitorAuthorization(apiUrl, deviceCode, expiresIn) {
4636
4644
  curlProcess.on("error", (error) => {
4637
4645
  clearTimeout(timeout);
4638
4646
  status.error(`Failed to monitor authorization: ${error.message}`);
4639
- resolve4(false);
4647
+ resolve5(false);
4640
4648
  });
4641
4649
  curlProcess.on("close", (code) => {
4642
4650
  clearTimeout(timeout);
4643
4651
  if (code !== 0 && code !== null) {
4644
4652
  status.error(`Authorization monitoring failed with code ${code}`);
4645
- resolve4(false);
4653
+ resolve5(false);
4646
4654
  }
4647
4655
  });
4648
4656
  });
@@ -5398,7 +5406,7 @@ async function fetchWithRetry(url, options, maxRetries = 3) {
5398
5406
  lastError = error instanceof Error ? error : new Error("Network error");
5399
5407
  }
5400
5408
  if (attempt < maxRetries - 1) {
5401
- await new Promise((resolve4) => setTimeout(resolve4, Math.pow(2, attempt) * 1e3));
5409
+ await new Promise((resolve5) => setTimeout(resolve5, Math.pow(2, attempt) * 1e3));
5402
5410
  }
5403
5411
  }
5404
5412
  throw lastError || new Error("Request failed after retries");
@@ -5981,7 +5989,7 @@ async function updateCommand(options = {}) {
5981
5989
  }
5982
5990
  if (options.restart && daemonWasRunning) {
5983
5991
  status.info("Restarting daemon...");
5984
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
5992
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
5985
5993
  if (startDaemon2()) {
5986
5994
  status.success("\u2713 Daemon restarted");
5987
5995
  status.info("");
@@ -6002,8 +6010,534 @@ async function updateCommand(options = {}) {
6002
6010
  }
6003
6011
  }
6004
6012
 
6013
+ // src/commands/run.ts
6014
+ var import_child_process9 = require("child_process");
6015
+ var import_core15 = __toESM(require_dist());
6016
+
6017
+ // src/utils/env-cache.ts
6018
+ var fs13 = __toESM(require("fs"));
6019
+ var path15 = __toESM(require("path"));
6020
+ var os3 = __toESM(require("os"));
6021
+ var DEFAULT_CACHE_TTL = 60;
6022
+ var CACHE_DIR = path15.join(os3.homedir(), ".episoda", "cache");
6023
+ function getCacheFilePath(projectId) {
6024
+ return path15.join(CACHE_DIR, `env-vars-${projectId}.json`);
6025
+ }
6026
+ function ensureCacheDir() {
6027
+ if (!fs13.existsSync(CACHE_DIR)) {
6028
+ fs13.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
6029
+ }
6030
+ }
6031
+ function readCache(projectId) {
6032
+ try {
6033
+ const cacheFile = getCacheFilePath(projectId);
6034
+ if (!fs13.existsSync(cacheFile)) {
6035
+ return null;
6036
+ }
6037
+ const content = fs13.readFileSync(cacheFile, "utf-8");
6038
+ const data = JSON.parse(content);
6039
+ if (!data.vars || typeof data.vars !== "object" || !data.fetchedAt) {
6040
+ return null;
6041
+ }
6042
+ return data;
6043
+ } catch {
6044
+ return null;
6045
+ }
6046
+ }
6047
+ function writeCache(projectId, vars) {
6048
+ try {
6049
+ ensureCacheDir();
6050
+ const cacheFile = getCacheFilePath(projectId);
6051
+ const data = {
6052
+ vars,
6053
+ fetchedAt: Date.now(),
6054
+ projectId
6055
+ };
6056
+ fs13.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
6057
+ } catch (error) {
6058
+ console.warn("[env-cache] Failed to write cache:", error instanceof Error ? error.message : error);
6059
+ }
6060
+ }
6061
+ function isCacheValid(cache, ttlSeconds) {
6062
+ const ageMs = Date.now() - cache.fetchedAt;
6063
+ return ageMs < ttlSeconds * 1e3;
6064
+ }
6065
+ async function fetchEnvVarsWithCache(apiUrl, accessToken, options = {}) {
6066
+ const {
6067
+ noCache = false,
6068
+ cacheTtl = DEFAULT_CACHE_TTL,
6069
+ offline = false,
6070
+ projectId = "default"
6071
+ } = options;
6072
+ if (offline) {
6073
+ const cache = readCache(projectId);
6074
+ if (cache && Object.keys(cache.vars).length > 0) {
6075
+ return {
6076
+ envVars: cache.vars,
6077
+ fromCache: true,
6078
+ cacheAge: Date.now() - cache.fetchedAt
6079
+ };
6080
+ }
6081
+ throw new Error(
6082
+ "Offline mode requires cached env vars, but no cache found.\nRun without --offline first to populate the cache."
6083
+ );
6084
+ }
6085
+ if (!noCache) {
6086
+ const cache = readCache(projectId);
6087
+ if (cache && isCacheValid(cache, cacheTtl)) {
6088
+ return {
6089
+ envVars: cache.vars,
6090
+ fromCache: true,
6091
+ cacheAge: Date.now() - cache.fetchedAt
6092
+ };
6093
+ }
6094
+ }
6095
+ try {
6096
+ const envVars = await fetchEnvVars(apiUrl, accessToken);
6097
+ if (Object.keys(envVars).length > 0) {
6098
+ writeCache(projectId, envVars);
6099
+ }
6100
+ return {
6101
+ envVars,
6102
+ fromCache: false
6103
+ };
6104
+ } catch (error) {
6105
+ const cache = readCache(projectId);
6106
+ if (cache && Object.keys(cache.vars).length > 0) {
6107
+ const cacheAge = Date.now() - cache.fetchedAt;
6108
+ console.warn(
6109
+ `[env-cache] Failed to fetch env vars, using stale cache (${Math.round(cacheAge / 1e3)}s old)`
6110
+ );
6111
+ return {
6112
+ envVars: cache.vars,
6113
+ fromCache: true,
6114
+ cacheAge
6115
+ };
6116
+ }
6117
+ throw new Error(
6118
+ `Failed to fetch environment variables: ${error instanceof Error ? error.message : error}
6119
+ No cached values available as fallback.`
6120
+ );
6121
+ }
6122
+ }
6123
+
6124
+ // src/commands/run.ts
6125
+ async function runCommand(args, options = {}) {
6126
+ if (args.length === 0) {
6127
+ throw new Error(
6128
+ "No command specified.\n\nUsage:\n episoda run <command> [args...]\n episoda run -- <command> [args...]\n\nExamples:\n episoda run npm run dev\n episoda run -- npm run dev --port 3001\n episoda run python manage.py runserver"
6129
+ );
6130
+ }
6131
+ const config = await (0, import_core15.loadConfig)();
6132
+ if (!config || !config.access_token) {
6133
+ throw new Error(
6134
+ "Not authenticated. Please run `episoda auth` first."
6135
+ );
6136
+ }
6137
+ const apiUrl = config.api_url || "https://episoda.dev";
6138
+ const cacheOptions = {
6139
+ noCache: options.noCache,
6140
+ cacheTtl: options.cacheTtl,
6141
+ offline: options.offline,
6142
+ projectId: config.project_id
6143
+ };
6144
+ if (options.verbose) {
6145
+ status.info("Fetching environment variables...");
6146
+ }
6147
+ const { envVars, fromCache, cacheAge } = await fetchEnvVarsWithCache(
6148
+ apiUrl,
6149
+ config.access_token,
6150
+ cacheOptions
6151
+ );
6152
+ const varCount = Object.keys(envVars).length;
6153
+ if (varCount === 0) {
6154
+ status.warning("No environment variables found. Command will run with system env only.");
6155
+ } else if (options.verbose) {
6156
+ if (fromCache) {
6157
+ status.info(`Using ${varCount} cached env vars (${Math.round(cacheAge / 1e3)}s old)`);
6158
+ } else {
6159
+ status.info(`Fetched ${varCount} env vars from server`);
6160
+ }
6161
+ status.info(`Variables: ${Object.keys(envVars).join(", ")}`);
6162
+ }
6163
+ const mergedEnv = {
6164
+ ...process.env,
6165
+ ...envVars
6166
+ };
6167
+ const [command, ...commandArgs] = args;
6168
+ if (options.verbose) {
6169
+ status.info(`Running: ${command} ${commandArgs.join(" ")}`);
6170
+ status.info("");
6171
+ }
6172
+ const child = (0, import_child_process9.spawn)(command, commandArgs, {
6173
+ stdio: "inherit",
6174
+ env: mergedEnv,
6175
+ shell: process.platform === "win32"
6176
+ // Use shell on Windows for .cmd/.bat files
6177
+ });
6178
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
6179
+ const signalHandlers = /* @__PURE__ */ new Map();
6180
+ for (const signal of signals) {
6181
+ const handler = () => {
6182
+ if (child.pid) {
6183
+ child.kill(signal);
6184
+ }
6185
+ };
6186
+ signalHandlers.set(signal, handler);
6187
+ process.on(signal, handler);
6188
+ }
6189
+ child.on("error", (error) => {
6190
+ for (const [signal, handler] of signalHandlers) {
6191
+ process.removeListener(signal, handler);
6192
+ }
6193
+ if (error.code === "ENOENT") {
6194
+ status.error(`Command not found: ${command}`);
6195
+ process.exit(127);
6196
+ } else {
6197
+ status.error(`Failed to start command: ${error.message}`);
6198
+ process.exit(1);
6199
+ }
6200
+ });
6201
+ child.on("exit", (code, signal) => {
6202
+ for (const [sig, handler] of signalHandlers) {
6203
+ process.removeListener(sig, handler);
6204
+ }
6205
+ if (signal) {
6206
+ process.exit(128 + (signalToNumber(signal) || 0));
6207
+ } else {
6208
+ process.exit(code ?? 1);
6209
+ }
6210
+ });
6211
+ }
6212
+ function signalToNumber(signal) {
6213
+ const signalMap = {
6214
+ SIGHUP: 1,
6215
+ SIGINT: 2,
6216
+ SIGQUIT: 3,
6217
+ SIGTERM: 15
6218
+ };
6219
+ return signalMap[signal];
6220
+ }
6221
+
6222
+ // src/commands/shell.ts
6223
+ var import_child_process10 = require("child_process");
6224
+ var import_core16 = __toESM(require_dist());
6225
+ async function shellCommand(shell, options = {}) {
6226
+ const config = await (0, import_core16.loadConfig)();
6227
+ if (!config || !config.access_token) {
6228
+ throw new Error(
6229
+ "Not authenticated. Please run `episoda auth` first."
6230
+ );
6231
+ }
6232
+ const apiUrl = config.api_url || "https://episoda.dev";
6233
+ const cacheOptions = {
6234
+ noCache: options.noCache,
6235
+ cacheTtl: options.cacheTtl,
6236
+ offline: options.offline,
6237
+ projectId: config.project_id
6238
+ };
6239
+ status.info("Fetching environment variables...");
6240
+ const { envVars, fromCache, cacheAge } = await fetchEnvVarsWithCache(
6241
+ apiUrl,
6242
+ config.access_token,
6243
+ cacheOptions
6244
+ );
6245
+ const varCount = Object.keys(envVars).length;
6246
+ if (varCount === 0) {
6247
+ status.warning("No environment variables found.");
6248
+ } else if (fromCache) {
6249
+ status.info(`Using ${varCount} cached env vars (${Math.round(cacheAge / 1e3)}s old)`);
6250
+ } else {
6251
+ status.info(`Fetched ${varCount} env vars from server`);
6252
+ }
6253
+ if (options.print) {
6254
+ printExports(envVars);
6255
+ return;
6256
+ }
6257
+ const targetShell = shell || process.env.SHELL || "/bin/bash";
6258
+ status.info(`Opening ${targetShell} with ${varCount} environment variables...`);
6259
+ status.info('Type "exit" to return to your normal shell.');
6260
+ status.info("");
6261
+ const mergedEnv = {
6262
+ ...process.env,
6263
+ ...envVars,
6264
+ // Set a marker so users know they're in an episoda shell
6265
+ EPISODA_SHELL: "1"
6266
+ };
6267
+ const child = (0, import_child_process10.spawn)(targetShell, [], {
6268
+ stdio: "inherit",
6269
+ env: mergedEnv
6270
+ });
6271
+ child.on("error", (error) => {
6272
+ if (error.code === "ENOENT") {
6273
+ status.error(`Shell not found: ${targetShell}`);
6274
+ process.exit(127);
6275
+ } else {
6276
+ status.error(`Failed to start shell: ${error.message}`);
6277
+ process.exit(1);
6278
+ }
6279
+ });
6280
+ child.on("exit", (code) => {
6281
+ process.exit(code ?? 0);
6282
+ });
6283
+ }
6284
+ function printExports(envVars) {
6285
+ for (const [key, value] of Object.entries(envVars)) {
6286
+ const escapedValue = value.replace(/'/g, "'\\''");
6287
+ console.log(`export ${key}='${escapedValue}'`);
6288
+ }
6289
+ }
6290
+
6291
+ // src/commands/env.ts
6292
+ var fs14 = __toESM(require("fs"));
6293
+ var path16 = __toESM(require("path"));
6294
+ var readline = __toESM(require("readline"));
6295
+ var import_core17 = __toESM(require_dist());
6296
+ async function envListCommand(options = {}) {
6297
+ const config = await (0, import_core17.loadConfig)();
6298
+ if (!config || !config.access_token) {
6299
+ throw new Error("Not authenticated. Please run `episoda auth` first.");
6300
+ }
6301
+ const apiUrl = config.api_url || "https://episoda.dev";
6302
+ const url = `${apiUrl}/api/projects/${config.project_id}/env-vars`;
6303
+ const response = await fetch(url, {
6304
+ headers: {
6305
+ "Authorization": `Bearer ${config.access_token}`,
6306
+ "Content-Type": "application/json"
6307
+ }
6308
+ });
6309
+ if (!response.ok) {
6310
+ throw new Error(`Failed to list env vars: ${response.status} ${response.statusText}`);
6311
+ }
6312
+ const data = await response.json();
6313
+ if (!data.success || !data.env_vars) {
6314
+ throw new Error("Failed to parse env vars response");
6315
+ }
6316
+ if (data.env_vars.length === 0) {
6317
+ status.info("No environment variables configured.");
6318
+ status.info("Add one with: episoda env set KEY=value");
6319
+ return;
6320
+ }
6321
+ console.log("\nEnvironment Variables:");
6322
+ console.log("\u2500".repeat(60));
6323
+ for (const envVar of data.env_vars) {
6324
+ const envBadge = envVar.environment === "all" ? "" : ` [${envVar.environment}]`;
6325
+ const sealed = envVar.is_sealed ? " (sealed)" : "";
6326
+ if (options.showValues) {
6327
+ const preview = envVar.value_preview || "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
6328
+ console.log(` ${envVar.key}${envBadge}${sealed} = ${preview}`);
6329
+ } else {
6330
+ console.log(` ${envVar.key}${envBadge}${sealed}`);
6331
+ }
6332
+ }
6333
+ console.log("");
6334
+ console.log(`Total: ${data.env_vars.length} variable(s)`);
6335
+ if (!options.showValues) {
6336
+ console.log("\nTip: Use --show-values to see value previews");
6337
+ }
6338
+ }
6339
+ async function envSetCommand(keyValue, options = {}) {
6340
+ const config = await (0, import_core17.loadConfig)();
6341
+ if (!config || !config.access_token) {
6342
+ throw new Error("Not authenticated. Please run `episoda auth` first.");
6343
+ }
6344
+ const apiUrl = config.api_url || "https://episoda.dev";
6345
+ let key;
6346
+ let value;
6347
+ const eqIndex = keyValue.indexOf("=");
6348
+ if (eqIndex === -1) {
6349
+ key = keyValue;
6350
+ value = await promptForValue(key);
6351
+ } else {
6352
+ key = keyValue.slice(0, eqIndex);
6353
+ value = keyValue.slice(eqIndex + 1);
6354
+ }
6355
+ if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
6356
+ throw new Error(
6357
+ `Invalid key format: ${key}
6358
+ Keys must be uppercase with underscores (e.g., API_KEY, DATABASE_URL)`
6359
+ );
6360
+ }
6361
+ const url = `${apiUrl}/api/projects/${config.project_id}/env-vars`;
6362
+ const response = await fetch(url, {
6363
+ method: "POST",
6364
+ headers: {
6365
+ "Authorization": `Bearer ${config.access_token}`,
6366
+ "Content-Type": "application/json"
6367
+ },
6368
+ body: JSON.stringify({
6369
+ key,
6370
+ value,
6371
+ environment: options.environment || "dev"
6372
+ })
6373
+ });
6374
+ const data = await response.json();
6375
+ if (!response.ok) {
6376
+ if (response.status === 409) {
6377
+ status.info(`${key} already exists, updating...`);
6378
+ await updateEnvVar(apiUrl, config.access_token, config.project_id, key, value);
6379
+ return;
6380
+ }
6381
+ throw new Error(data.error?.message || `Failed to set env var: ${response.status}`);
6382
+ }
6383
+ status.success(`Set ${key} successfully`);
6384
+ }
6385
+ async function updateEnvVar(apiUrl, accessToken, projectId, key, value) {
6386
+ const listUrl = `${apiUrl}/api/projects/${projectId}/env-vars`;
6387
+ const listResponse = await fetch(listUrl, {
6388
+ headers: {
6389
+ "Authorization": `Bearer ${accessToken}`,
6390
+ "Content-Type": "application/json"
6391
+ }
6392
+ });
6393
+ if (!listResponse.ok) {
6394
+ throw new Error("Failed to find existing env var for update");
6395
+ }
6396
+ const listData = await listResponse.json();
6397
+ const existingVar = listData.env_vars?.find((v) => v.key === key);
6398
+ if (!existingVar) {
6399
+ throw new Error(`Env var ${key} not found for update`);
6400
+ }
6401
+ const updateUrl = `${apiUrl}/api/projects/${projectId}/env-vars/${existingVar.id}`;
6402
+ const updateResponse = await fetch(updateUrl, {
6403
+ method: "PATCH",
6404
+ headers: {
6405
+ "Authorization": `Bearer ${accessToken}`,
6406
+ "Content-Type": "application/json"
6407
+ },
6408
+ body: JSON.stringify({ value })
6409
+ });
6410
+ if (!updateResponse.ok) {
6411
+ throw new Error(`Failed to update env var: ${updateResponse.status}`);
6412
+ }
6413
+ status.success(`Updated ${key} successfully`);
6414
+ }
6415
+ async function envRemoveCommand(key) {
6416
+ const config = await (0, import_core17.loadConfig)();
6417
+ if (!config || !config.access_token) {
6418
+ throw new Error("Not authenticated. Please run `episoda auth` first.");
6419
+ }
6420
+ const apiUrl = config.api_url || "https://episoda.dev";
6421
+ const listUrl = `${apiUrl}/api/projects/${config.project_id}/env-vars`;
6422
+ const listResponse = await fetch(listUrl, {
6423
+ headers: {
6424
+ "Authorization": `Bearer ${config.access_token}`,
6425
+ "Content-Type": "application/json"
6426
+ }
6427
+ });
6428
+ if (!listResponse.ok) {
6429
+ throw new Error("Failed to list env vars");
6430
+ }
6431
+ const listData = await listResponse.json();
6432
+ const existingVar = listData.env_vars?.find((v) => v.key === key);
6433
+ if (!existingVar) {
6434
+ throw new Error(`Env var ${key} not found`);
6435
+ }
6436
+ const deleteUrl = `${apiUrl}/api/projects/${config.project_id}/env-vars/${existingVar.id}`;
6437
+ const deleteResponse = await fetch(deleteUrl, {
6438
+ method: "DELETE",
6439
+ headers: {
6440
+ "Authorization": `Bearer ${config.access_token}`
6441
+ }
6442
+ });
6443
+ if (!deleteResponse.ok) {
6444
+ throw new Error(`Failed to remove env var: ${deleteResponse.status}`);
6445
+ }
6446
+ status.success(`Removed ${key}`);
6447
+ }
6448
+ async function envPullCommand(options = {}) {
6449
+ const config = await (0, import_core17.loadConfig)();
6450
+ if (!config || !config.access_token) {
6451
+ throw new Error("Not authenticated. Please run `episoda auth` first.");
6452
+ }
6453
+ const apiUrl = config.api_url || "https://episoda.dev";
6454
+ status.info("Fetching environment variables...");
6455
+ const envVars = await fetchEnvVars(apiUrl, config.access_token);
6456
+ if (Object.keys(envVars).length === 0) {
6457
+ status.warning("No environment variables found.");
6458
+ return;
6459
+ }
6460
+ const header = [
6461
+ "# AUTO-GENERATED by episoda env pull",
6462
+ "# Do not edit - changes will be overwritten",
6463
+ `# Source of truth: ${apiUrl}/settings/env-vars`,
6464
+ `# Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
6465
+ "#",
6466
+ "# To update: episoda env set KEY=value",
6467
+ "# To refresh: episoda env pull",
6468
+ ""
6469
+ ].join("\n");
6470
+ const envContent = Object.entries(envVars).map(([key, value]) => {
6471
+ if (/[\s'"#$`\\]/.test(value) || value.includes("\n")) {
6472
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
6473
+ return `${key}="${escaped}"`;
6474
+ }
6475
+ return `${key}=${value}`;
6476
+ }).join("\n") + "\n";
6477
+ const fullContent = header + envContent;
6478
+ if (options.stdout) {
6479
+ console.log(fullContent);
6480
+ return;
6481
+ }
6482
+ const filename = options.file || ".env";
6483
+ const filepath = path16.resolve(process.cwd(), filename);
6484
+ fs14.writeFileSync(filepath, fullContent, { mode: 384 });
6485
+ status.success(`Wrote ${Object.keys(envVars).length} env vars to ${filename}`);
6486
+ }
6487
+ async function promptForValue(key) {
6488
+ if (!process.stdin.isTTY) {
6489
+ return new Promise((resolve5, reject) => {
6490
+ const rl = readline.createInterface({
6491
+ input: process.stdin,
6492
+ output: process.stdout
6493
+ });
6494
+ rl.question(`Enter value for ${key}: `, (answer) => {
6495
+ rl.close();
6496
+ resolve5(answer);
6497
+ });
6498
+ rl.on("close", () => {
6499
+ reject(new Error("Input closed"));
6500
+ });
6501
+ });
6502
+ }
6503
+ return new Promise((resolve5, reject) => {
6504
+ const rl = readline.createInterface({
6505
+ input: process.stdin,
6506
+ output: process.stdout
6507
+ });
6508
+ let value = "";
6509
+ let resolved = false;
6510
+ const cleanup = () => {
6511
+ if (process.stdin.isTTY) {
6512
+ process.stdin.setRawMode(false);
6513
+ }
6514
+ process.stdin.removeListener("data", dataHandler);
6515
+ rl.close();
6516
+ };
6517
+ const dataHandler = (char) => {
6518
+ const str = char.toString();
6519
+ if (str === "\n" || str === "\r" || str === "\r\n") {
6520
+ resolved = true;
6521
+ cleanup();
6522
+ console.log("");
6523
+ resolve5(value);
6524
+ } else if (str === "") {
6525
+ cleanup();
6526
+ reject(new Error("Cancelled"));
6527
+ } else if (str === "\x7F" || str === "\b") {
6528
+ value = value.slice(0, -1);
6529
+ } else {
6530
+ value += str;
6531
+ }
6532
+ };
6533
+ process.stdin.setRawMode(true);
6534
+ process.stdout.write(`Enter value for ${key}: `);
6535
+ process.stdin.on("data", dataHandler);
6536
+ });
6537
+ }
6538
+
6005
6539
  // src/index.ts
6006
- import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core15.VERSION);
6540
+ import_commander.program.name("episoda").description("Episoda CLI - local development with git worktree isolation").version(import_core18.VERSION);
6007
6541
  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) => {
6008
6542
  try {
6009
6543
  await authCommand(options);
@@ -6159,5 +6693,74 @@ function formatUptime(seconds) {
6159
6693
  const mins = Math.floor(seconds % 3600 / 60);
6160
6694
  return `${hours}h ${mins}m`;
6161
6695
  }
6696
+ import_commander.program.command("run").description("Run a command with environment variables injected from the database").argument("<command...>", "Command to run with injected env vars").option("--no-cache", "Force fresh fetch from server").option("--cache-ttl <seconds>", "Cache TTL in seconds (default: 60)", parseInt).option("--offline", "Use cached values only, no server call").option("--verbose", "Show fetched variable names").allowUnknownOption().action(async (commandArgs, options) => {
6697
+ try {
6698
+ const args = process.argv.slice(process.argv.indexOf("run") + 1);
6699
+ const separatorIndex = args.indexOf("--");
6700
+ let finalArgs;
6701
+ if (separatorIndex >= 0) {
6702
+ finalArgs = args.slice(separatorIndex + 1);
6703
+ } else {
6704
+ finalArgs = commandArgs.filter(
6705
+ (arg) => !arg.startsWith("--no-cache") && !arg.startsWith("--cache-ttl") && !arg.startsWith("--offline") && !arg.startsWith("--verbose")
6706
+ );
6707
+ }
6708
+ await runCommand(finalArgs, {
6709
+ noCache: options.cache === false,
6710
+ cacheTtl: options.cacheTtl,
6711
+ offline: options.offline,
6712
+ verbose: options.verbose
6713
+ });
6714
+ } catch (error) {
6715
+ status.error(`Run failed: ${error instanceof Error ? error.message : String(error)}`);
6716
+ process.exit(1);
6717
+ }
6718
+ });
6719
+ import_commander.program.command("shell").description("Open an interactive shell with environment variables injected").argument("[shell]", "Shell to use (default: $SHELL)").option("--print", "Print export statements instead of opening shell").option("--no-cache", "Force fresh fetch from server").option("--cache-ttl <seconds>", "Cache TTL in seconds (default: 60)", parseInt).option("--offline", "Use cached values only").action(async (shell, options) => {
6720
+ try {
6721
+ await shellCommand(shell, {
6722
+ print: options.print,
6723
+ noCache: options.cache === false,
6724
+ cacheTtl: options.cacheTtl,
6725
+ offline: options.offline
6726
+ });
6727
+ } catch (error) {
6728
+ status.error(`Shell failed: ${error instanceof Error ? error.message : String(error)}`);
6729
+ process.exit(1);
6730
+ }
6731
+ });
6732
+ var envCmd = import_commander.program.command("env").description("Manage environment variables");
6733
+ envCmd.command("list").description("List environment variables").option("--show-values", "Show value previews (masked)").action(async (options) => {
6734
+ try {
6735
+ await envListCommand({ showValues: options.showValues });
6736
+ } catch (error) {
6737
+ status.error(`Env list failed: ${error instanceof Error ? error.message : String(error)}`);
6738
+ process.exit(1);
6739
+ }
6740
+ });
6741
+ envCmd.command("set").description("Set an environment variable").argument("<key=value>", "KEY=value pair (omit value to prompt)").option("-e, --environment <env>", "Environment: all, dev, preview, prod (default: dev)").action(async (keyValue, options) => {
6742
+ try {
6743
+ await envSetCommand(keyValue, { environment: options.environment });
6744
+ } catch (error) {
6745
+ status.error(`Env set failed: ${error instanceof Error ? error.message : String(error)}`);
6746
+ process.exit(1);
6747
+ }
6748
+ });
6749
+ envCmd.command("remove").description("Remove an environment variable").argument("<key>", "Variable key to remove").action(async (key) => {
6750
+ try {
6751
+ await envRemoveCommand(key);
6752
+ } catch (error) {
6753
+ status.error(`Env remove failed: ${error instanceof Error ? error.message : String(error)}`);
6754
+ process.exit(1);
6755
+ }
6756
+ });
6757
+ envCmd.command("pull").description("Generate .env file from database (explicit file generation)").option("-f, --file <filename>", "Output filename (default: .env)").option("--stdout", "Print to stdout instead of file").action(async (options) => {
6758
+ try {
6759
+ await envPullCommand({ file: options.file, stdout: options.stdout });
6760
+ } catch (error) {
6761
+ status.error(`Env pull failed: ${error instanceof Error ? error.message : String(error)}`);
6762
+ process.exit(1);
6763
+ }
6764
+ });
6162
6765
  import_commander.program.parse();
6163
6766
  //# sourceMappingURL=index.js.map