workon 3.2.1 → 3.2.3

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/cli.js CHANGED
@@ -11,18 +11,99 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/lib/config.ts
13
13
  import Conf from "conf";
14
- var TRANSIENT_PROPS, Config;
14
+ import { openSync, closeSync, unlinkSync, existsSync, mkdirSync } from "fs";
15
+ import { dirname } from "path";
16
+ var TRANSIENT_PROPS, FileLock, Config;
15
17
  var init_config = __esm({
16
18
  "src/lib/config.ts"() {
17
19
  "use strict";
18
20
  TRANSIENT_PROPS = ["pkg", "work"];
19
- Config = class {
21
+ FileLock = class _FileLock {
22
+ lockPath;
23
+ fd = null;
24
+ static LOCK_TIMEOUT_MS = 5e3;
25
+ static RETRY_INTERVAL_MS = 50;
26
+ constructor(configPath) {
27
+ this.lockPath = `${configPath}.lock`;
28
+ }
29
+ async acquire() {
30
+ const startTime = Date.now();
31
+ const lockDir = dirname(this.lockPath);
32
+ if (!existsSync(lockDir)) {
33
+ mkdirSync(lockDir, { recursive: true });
34
+ }
35
+ while (Date.now() - startTime < _FileLock.LOCK_TIMEOUT_MS) {
36
+ try {
37
+ this.fd = openSync(this.lockPath, "wx");
38
+ return;
39
+ } catch (error) {
40
+ if (error.code === "EEXIST") {
41
+ try {
42
+ const stat = await import("fs").then((fs) => fs.promises.stat(this.lockPath));
43
+ const age = Date.now() - stat.mtimeMs;
44
+ if (age > _FileLock.LOCK_TIMEOUT_MS) {
45
+ try {
46
+ unlinkSync(this.lockPath);
47
+ } catch {
48
+ }
49
+ }
50
+ } catch {
51
+ }
52
+ await new Promise((resolve2) => setTimeout(resolve2, _FileLock.RETRY_INTERVAL_MS));
53
+ } else {
54
+ throw error;
55
+ }
56
+ }
57
+ }
58
+ throw new Error("Failed to acquire config lock: timeout");
59
+ }
60
+ release() {
61
+ if (this.fd !== null) {
62
+ try {
63
+ closeSync(this.fd);
64
+ } catch {
65
+ }
66
+ this.fd = null;
67
+ }
68
+ try {
69
+ unlinkSync(this.lockPath);
70
+ } catch {
71
+ }
72
+ }
73
+ };
74
+ Config = class _Config {
75
+ static _instance = null;
20
76
  _transient = {};
77
+ // Using definite assignment assertion since singleton pattern may return existing instance
21
78
  _store;
79
+ _lock;
22
80
  constructor() {
81
+ if (_Config._instance && process.env.NODE_ENV !== "test") {
82
+ return _Config._instance;
83
+ }
23
84
  this._store = new Conf({
24
- projectName: "workon"
85
+ projectName: "workon",
86
+ ...process.env.WORKON_CONFIG_DIR && { cwd: process.env.WORKON_CONFIG_DIR }
25
87
  });
88
+ this._lock = new FileLock(this._store.path);
89
+ if (process.env.NODE_ENV !== "test") {
90
+ _Config._instance = this;
91
+ }
92
+ }
93
+ /**
94
+ * Get the singleton instance (creates one if needed)
95
+ */
96
+ static getInstance() {
97
+ if (!_Config._instance) {
98
+ _Config._instance = new _Config();
99
+ }
100
+ return _Config._instance;
101
+ }
102
+ /**
103
+ * Reset the singleton instance (for testing purposes)
104
+ */
105
+ static resetInstance() {
106
+ _Config._instance = null;
26
107
  }
27
108
  get(key, defaultValue) {
28
109
  const rootKey = key.split(".")[0];
@@ -37,10 +118,9 @@ var init_config = __esm({
37
118
  this._transient[key] = value;
38
119
  } else {
39
120
  if (value === void 0) {
40
- this._store.set(key, value);
41
- } else {
42
- this._store.set(key, value);
121
+ throw new Error(`Cannot set '${key}' to undefined. Use delete() to remove keys.`);
43
122
  }
123
+ this._store.set(key, value);
44
124
  }
45
125
  }
46
126
  has(key) {
@@ -58,6 +138,9 @@ var init_config = __esm({
58
138
  this._store.delete(key);
59
139
  }
60
140
  }
141
+ /**
142
+ * Get all projects. Returns a fresh copy from the store.
143
+ */
61
144
  getProjects() {
62
145
  return this.get("projects") ?? {};
63
146
  }
@@ -65,15 +148,50 @@ var init_config = __esm({
65
148
  const projects = this.getProjects();
66
149
  return projects[name];
67
150
  }
151
+ /**
152
+ * Set a project with file locking to prevent race conditions.
153
+ * This ensures atomic read-modify-write operations.
154
+ */
155
+ async setProjectSafe(name, config) {
156
+ await this._lock.acquire();
157
+ try {
158
+ const freshProjects = this._store.get("projects") ?? {};
159
+ freshProjects[name] = config;
160
+ this._store.set("projects", freshProjects);
161
+ } finally {
162
+ this._lock.release();
163
+ }
164
+ }
165
+ /**
166
+ * Synchronous version of setProject for backwards compatibility.
167
+ * Note: This is less safe than setProjectSafe() in concurrent scenarios.
168
+ * Consider migrating to setProjectSafe() for critical operations.
169
+ */
68
170
  setProject(name, config) {
69
- const projects = this.getProjects();
70
- projects[name] = config;
71
- this.set("projects", projects);
171
+ const freshProjects = this._store.get("projects") ?? {};
172
+ freshProjects[name] = config;
173
+ this._store.set("projects", freshProjects);
72
174
  }
175
+ /**
176
+ * Delete a project with file locking to prevent race conditions.
177
+ */
178
+ async deleteProjectSafe(name) {
179
+ await this._lock.acquire();
180
+ try {
181
+ const freshProjects = this._store.get("projects") ?? {};
182
+ delete freshProjects[name];
183
+ this._store.set("projects", freshProjects);
184
+ } finally {
185
+ this._lock.release();
186
+ }
187
+ }
188
+ /**
189
+ * Synchronous version of deleteProject for backwards compatibility.
190
+ */
73
191
  deleteProject(name) {
74
- const projects = this.getProjects();
75
- delete projects[name];
76
- this.set("projects", projects);
192
+ const freshProjects = this._store.get("projects") ?? {};
193
+ delete freshProjects[name];
194
+ this._store.set("projects", freshProjects);
77
195
  }
78
196
  getDefaults() {
79
197
  return this.get("project_defaults");
@@ -280,7 +398,7 @@ var init_environment = __esm({
280
398
  }
281
399
  static ensureConfigured() {
282
400
  if (!this.configured) {
283
- this.config = new Config();
401
+ this.config = Config.getInstance();
284
402
  this.log = {
285
403
  debug: () => {
286
404
  },
@@ -345,18 +463,22 @@ var init_cwd = __esm({
345
463
  const { project, isShellMode, shellCommands } = context;
346
464
  const projectPath = project.path.path;
347
465
  if (isShellMode) {
348
- shellCommands.push(`cd "${projectPath}"`);
466
+ shellCommands.push(`pushd "${projectPath}" > /dev/null`);
349
467
  } else {
350
468
  const shell = process.env.SHELL || "/bin/bash";
351
- spawn(shell, [], {
469
+ const child = spawn(shell, ["-i"], {
352
470
  cwd: projectPath,
353
471
  stdio: "inherit"
354
472
  });
473
+ await new Promise((resolve2, reject) => {
474
+ child.on("close", () => resolve2());
475
+ child.on("error", (err) => reject(err));
476
+ });
355
477
  }
356
478
  },
357
479
  generateShellCommand(context) {
358
480
  const projectPath = context.project.path.path;
359
- return [`cd "${projectPath}"`];
481
+ return [`pushd "${projectPath}" > /dev/null`];
360
482
  }
361
483
  };
362
484
  }
@@ -421,7 +543,7 @@ var init_ide = __esm({
421
543
  const projectPath = project.path.path;
422
544
  const ide = project.ide || "code";
423
545
  if (isShellMode) {
424
- shellCommands.push(`${ide} "${projectPath}" &`);
546
+ shellCommands.push(`set +m; ${ide} "${projectPath}" &>/dev/null &`);
425
547
  } else {
426
548
  spawn2(ide, [projectPath], {
427
549
  detached: true,
@@ -432,7 +554,7 @@ var init_ide = __esm({
432
554
  generateShellCommand(context) {
433
555
  const projectPath = context.project.path.path;
434
556
  const ide = context.project.ide || "code";
435
- return [`${ide} "${projectPath}" &`];
557
+ return [`set +m; ${ide} "${projectPath}" &>/dev/null &`];
436
558
  }
437
559
  };
438
560
  }
@@ -628,10 +750,10 @@ var init_claude = __esm({
628
750
  }
629
751
  static getClaudeCommand(config) {
630
752
  if (typeof config === "boolean" || config === void 0) {
631
- return "claude";
753
+ return "claude --dangerously-skip-permissions";
632
754
  }
633
755
  const flags = config.flags || [];
634
- return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
756
+ return flags.length > 0 ? `claude --dangerously-skip-permissions ${flags.join(" ")}` : "claude --dangerously-skip-permissions";
635
757
  }
636
758
  static get processing() {
637
759
  return {
@@ -1094,12 +1216,213 @@ var init_registry = __esm({
1094
1216
  }
1095
1217
  });
1096
1218
 
1219
+ // src/lib/sanitize.ts
1220
+ function sanitizeForShell(input6) {
1221
+ if (!input6) return "";
1222
+ return input6.replace(/[^a-zA-Z0-9_\-.]/g, "_");
1223
+ }
1224
+ function escapeForSingleQuotes(input6) {
1225
+ if (!input6) return "";
1226
+ return input6.replace(/'/g, "'\\''");
1227
+ }
1228
+ var init_sanitize = __esm({
1229
+ "src/lib/sanitize.ts"() {
1230
+ "use strict";
1231
+ }
1232
+ });
1233
+
1234
+ // src/lib/tmux.ts
1235
+ import { exec as execCallback, spawn as spawn7 } from "child_process";
1236
+ import { promisify } from "util";
1237
+ var exec, TmuxManager;
1238
+ var init_tmux = __esm({
1239
+ "src/lib/tmux.ts"() {
1240
+ "use strict";
1241
+ init_sanitize();
1242
+ exec = promisify(execCallback);
1243
+ TmuxManager = class {
1244
+ sessionPrefix = "workon-";
1245
+ async isTmuxAvailable() {
1246
+ try {
1247
+ await exec("which tmux");
1248
+ return true;
1249
+ } catch {
1250
+ return false;
1251
+ }
1252
+ }
1253
+ async sessionExists(sessionName) {
1254
+ try {
1255
+ await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
1256
+ return true;
1257
+ } catch {
1258
+ return false;
1259
+ }
1260
+ }
1261
+ getSessionName(projectName) {
1262
+ return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
1263
+ }
1264
+ async killSession(sessionName) {
1265
+ try {
1266
+ await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
1267
+ return true;
1268
+ } catch {
1269
+ return false;
1270
+ }
1271
+ }
1272
+ async createSplitSession(projectName, projectPath, claudeArgs = []) {
1273
+ const sessionName = this.getSessionName(projectName);
1274
+ const escapedSession = escapeForSingleQuotes(sessionName);
1275
+ const escapedPath = escapeForSingleQuotes(projectPath);
1276
+ if (await this.sessionExists(sessionName)) {
1277
+ await this.killSession(sessionName);
1278
+ }
1279
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1280
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1281
+ await exec(
1282
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
1283
+ );
1284
+ await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
1285
+ await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1286
+ return sessionName;
1287
+ }
1288
+ async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1289
+ const sessionName = this.getSessionName(projectName);
1290
+ const escapedSession = escapeForSingleQuotes(sessionName);
1291
+ const escapedPath = escapeForSingleQuotes(projectPath);
1292
+ if (await this.sessionExists(sessionName)) {
1293
+ await this.killSession(sessionName);
1294
+ }
1295
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1296
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1297
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1298
+ await exec(
1299
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
1300
+ );
1301
+ await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
1302
+ await exec(
1303
+ `tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
1304
+ );
1305
+ await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
1306
+ await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
1307
+ await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1308
+ return sessionName;
1309
+ }
1310
+ async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
1311
+ const sessionName = this.getSessionName(projectName);
1312
+ const escapedSession = escapeForSingleQuotes(sessionName);
1313
+ const escapedPath = escapeForSingleQuotes(projectPath);
1314
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1315
+ if (await this.sessionExists(sessionName)) {
1316
+ await this.killSession(sessionName);
1317
+ }
1318
+ await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
1319
+ await exec(
1320
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
1321
+ );
1322
+ await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
1323
+ await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1324
+ return sessionName;
1325
+ }
1326
+ async attachToSession(sessionName) {
1327
+ const escapedSession = escapeForSingleQuotes(sessionName);
1328
+ if (process.env.TMUX) {
1329
+ await exec(`tmux switch-client -t '${escapedSession}'`);
1330
+ } else {
1331
+ const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
1332
+ const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1333
+ if (useiTermIntegration) {
1334
+ spawn7("tmux", ["-CC", "attach-session", "-t", sessionName], {
1335
+ stdio: "inherit",
1336
+ detached: true
1337
+ });
1338
+ } else {
1339
+ spawn7("tmux", ["attach-session", "-t", sessionName], {
1340
+ stdio: "inherit",
1341
+ detached: true
1342
+ });
1343
+ }
1344
+ }
1345
+ }
1346
+ buildShellCommands(projectName, projectPath, claudeArgs = []) {
1347
+ const sessionName = this.getSessionName(projectName);
1348
+ const escapedSession = escapeForSingleQuotes(sessionName);
1349
+ const escapedPath = escapeForSingleQuotes(projectPath);
1350
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1351
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1352
+ return [
1353
+ `# Create tmux split session for ${sanitizeForShell(projectName)}`,
1354
+ `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1355
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
1356
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
1357
+ `tmux select-pane -t '${escapedSession}:0.0'`,
1358
+ this.getAttachCommand(sessionName)
1359
+ ];
1360
+ }
1361
+ buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1362
+ const sessionName = this.getSessionName(projectName);
1363
+ const escapedSession = escapeForSingleQuotes(sessionName);
1364
+ const escapedPath = escapeForSingleQuotes(projectPath);
1365
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1366
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1367
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1368
+ return [
1369
+ `# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
1370
+ `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1371
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
1372
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
1373
+ `tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
1374
+ `tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
1375
+ `tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
1376
+ `tmux select-pane -t '${escapedSession}:0.0'`,
1377
+ this.getAttachCommand(sessionName)
1378
+ ];
1379
+ }
1380
+ buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
1381
+ const sessionName = this.getSessionName(projectName);
1382
+ const escapedSession = escapeForSingleQuotes(sessionName);
1383
+ const escapedPath = escapeForSingleQuotes(projectPath);
1384
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1385
+ return [
1386
+ `# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
1387
+ `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1388
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
1389
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
1390
+ `tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
1391
+ `tmux select-pane -t '${escapedSession}:0.0'`,
1392
+ this.getAttachCommand(sessionName)
1393
+ ];
1394
+ }
1395
+ getAttachCommand(sessionName) {
1396
+ const escapedSession = escapeForSingleQuotes(sessionName);
1397
+ if (process.env.TMUX) {
1398
+ return `tmux switch-client -t '${escapedSession}'`;
1399
+ }
1400
+ const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
1401
+ const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1402
+ if (useiTermIntegration) {
1403
+ return `tmux -CC attach-session -t '${escapedSession}'`;
1404
+ }
1405
+ return `tmux attach-session -t '${escapedSession}'`;
1406
+ }
1407
+ async listWorkonSessions() {
1408
+ try {
1409
+ const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
1410
+ return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
1411
+ } catch {
1412
+ return [];
1413
+ }
1414
+ }
1415
+ };
1416
+ }
1417
+ });
1418
+
1097
1419
  // src/types/constants.ts
1098
1420
  var IDE_CHOICES;
1099
1421
  var init_constants = __esm({
1100
1422
  "src/types/constants.ts"() {
1101
1423
  "use strict";
1102
1424
  IDE_CHOICES = [
1425
+ { name: "Cursor", value: "cursor" },
1103
1426
  { name: "Visual Studio Code", value: "vscode" },
1104
1427
  { name: "Visual Studio Code (code)", value: "code" },
1105
1428
  { name: "IntelliJ IDEA", value: "idea" },
@@ -1264,8 +1587,7 @@ async function initProject(defaultName, fromUser, ctx) {
1264
1587
  ide,
1265
1588
  events
1266
1589
  };
1267
- projects[name] = projectConfig;
1268
- config.set("projects", projects);
1590
+ await config.setProjectSafe(name, projectConfig);
1269
1591
  log.info("Your project has been initialized.");
1270
1592
  log.info(`Use 'workon ${name}' to start working!`);
1271
1593
  }
@@ -1286,8 +1608,7 @@ async function initBranch(defaultName, ctx) {
1286
1608
  branch
1287
1609
  });
1288
1610
  const branchConfig = mergedConfig;
1289
- projects[branchName] = branchConfig;
1290
- config.set("projects", projects);
1611
+ await config.setProjectSafe(branchName, branchConfig);
1291
1612
  log.info("Your branch configuration has been initialized.");
1292
1613
  log.info(`Use 'workon ${branchName}' to start working!`);
1293
1614
  }
@@ -1485,7 +1806,7 @@ async function createProjectManage(ctx) {
1485
1806
  default: true
1486
1807
  });
1487
1808
  if (confirmed) {
1488
- config.setProject(name, projectConfig);
1809
+ await config.setProjectSafe(name, projectConfig);
1489
1810
  log.info(`Project '${name}' created successfully!`);
1490
1811
  }
1491
1812
  }
@@ -1563,7 +1884,7 @@ async function editProjectManage(ctx) {
1563
1884
  default: true
1564
1885
  });
1565
1886
  if (confirmed) {
1566
- config.setProject(name, updatedConfig);
1887
+ await config.setProjectSafe(name, updatedConfig);
1567
1888
  log.info(`Project '${name}' updated successfully!`);
1568
1889
  }
1569
1890
  }
@@ -1589,7 +1910,7 @@ async function deleteProjectManage(ctx) {
1589
1910
  });
1590
1911
  if (deleteAll) {
1591
1912
  for (const branch of branches) {
1592
- config.deleteProject(branch);
1913
+ await config.deleteProjectSafe(branch);
1593
1914
  }
1594
1915
  }
1595
1916
  }
@@ -1598,7 +1919,7 @@ async function deleteProjectManage(ctx) {
1598
1919
  default: false
1599
1920
  });
1600
1921
  if (confirmed) {
1601
- config.deleteProject(name);
1922
+ await config.deleteProjectSafe(name);
1602
1923
  log.info(`Project '${name}' deleted.`);
1603
1924
  }
1604
1925
  }
@@ -1678,7 +1999,7 @@ async function editBranchManage(projectName, ctx) {
1678
1999
  default: true
1679
2000
  });
1680
2001
  if (confirmed) {
1681
- config.setProject(branchName, updatedConfig);
2002
+ await config.setProjectSafe(branchName, updatedConfig);
1682
2003
  log.info(`Branch configuration '${branchName}' updated successfully!`);
1683
2004
  }
1684
2005
  }
@@ -1703,7 +2024,7 @@ async function deleteBranchManage(projectName, ctx) {
1703
2024
  default: false
1704
2025
  });
1705
2026
  if (confirmed) {
1706
- config.deleteProject(branchName);
2027
+ await config.deleteProjectSafe(branchName);
1707
2028
  log.info(`Branch configuration '${branchName}' deleted.`);
1708
2029
  }
1709
2030
  }
@@ -1732,215 +2053,21 @@ var init_interactive = __esm({
1732
2053
  }
1733
2054
  });
1734
2055
 
1735
- // src/commands/index.ts
1736
- init_config();
1737
- init_environment();
1738
- init_registry();
1739
- import { Command as Command8 } from "commander";
1740
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1741
- import { join, dirname } from "path";
1742
- import { fileURLToPath } from "url";
1743
- import loog from "loog";
1744
- import omelette from "omelette";
1745
- import File7 from "phylo";
1746
-
1747
2056
  // src/commands/open.ts
1748
- init_environment();
2057
+ var open_exports = {};
2058
+ __export(open_exports, {
2059
+ createOpenCommand: () => createOpenCommand,
2060
+ runOpen: () => runOpen
2061
+ });
1749
2062
  import { Command } from "commander";
1750
2063
  import File4 from "phylo";
1751
-
1752
- // src/lib/tmux.ts
1753
- import { exec as execCallback, spawn as spawn7 } from "child_process";
1754
- import { promisify } from "util";
1755
-
1756
- // src/lib/sanitize.ts
1757
- function sanitizeForShell(input6) {
1758
- if (!input6) return "";
1759
- return input6.replace(/[^a-zA-Z0-9_\-.]/g, "_");
1760
- }
1761
- function escapeForSingleQuotes(input6) {
1762
- if (!input6) return "";
1763
- return input6.replace(/'/g, "'\\''");
1764
- }
1765
-
1766
- // src/lib/tmux.ts
1767
- var exec = promisify(execCallback);
1768
- var TmuxManager = class {
1769
- sessionPrefix = "workon-";
1770
- async isTmuxAvailable() {
1771
- try {
1772
- await exec("which tmux");
1773
- return true;
1774
- } catch {
1775
- return false;
1776
- }
1777
- }
1778
- async sessionExists(sessionName) {
1779
- try {
1780
- await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
1781
- return true;
1782
- } catch {
1783
- return false;
1784
- }
1785
- }
1786
- getSessionName(projectName) {
1787
- return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
1788
- }
1789
- async killSession(sessionName) {
1790
- try {
1791
- await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
1792
- return true;
1793
- } catch {
1794
- return false;
1795
- }
1796
- }
1797
- async createSplitSession(projectName, projectPath, claudeArgs = []) {
1798
- const sessionName = this.getSessionName(projectName);
1799
- const escapedSession = escapeForSingleQuotes(sessionName);
1800
- const escapedPath = escapeForSingleQuotes(projectPath);
1801
- if (await this.sessionExists(sessionName)) {
1802
- await this.killSession(sessionName);
1803
- }
1804
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1805
- const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1806
- await exec(
1807
- `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
1808
- );
1809
- await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
1810
- await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1811
- return sessionName;
1812
- }
1813
- async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1814
- const sessionName = this.getSessionName(projectName);
1815
- const escapedSession = escapeForSingleQuotes(sessionName);
1816
- const escapedPath = escapeForSingleQuotes(projectPath);
1817
- if (await this.sessionExists(sessionName)) {
1818
- await this.killSession(sessionName);
1819
- }
1820
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1821
- const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1822
- const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1823
- await exec(
1824
- `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
1825
- );
1826
- await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
1827
- await exec(
1828
- `tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
1829
- );
1830
- await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
1831
- await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
1832
- await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1833
- return sessionName;
1834
- }
1835
- async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
1836
- const sessionName = this.getSessionName(projectName);
1837
- const escapedSession = escapeForSingleQuotes(sessionName);
1838
- const escapedPath = escapeForSingleQuotes(projectPath);
1839
- const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1840
- if (await this.sessionExists(sessionName)) {
1841
- await this.killSession(sessionName);
1842
- }
1843
- await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
1844
- await exec(
1845
- `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
1846
- );
1847
- await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
1848
- await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1849
- return sessionName;
1850
- }
1851
- async attachToSession(sessionName) {
1852
- const escapedSession = escapeForSingleQuotes(sessionName);
1853
- if (process.env.TMUX) {
1854
- await exec(`tmux switch-client -t '${escapedSession}'`);
1855
- } else {
1856
- const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
1857
- const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1858
- if (useiTermIntegration) {
1859
- spawn7("tmux", ["-CC", "attach-session", "-t", sessionName], {
1860
- stdio: "inherit",
1861
- detached: true
1862
- });
1863
- } else {
1864
- spawn7("tmux", ["attach-session", "-t", sessionName], {
1865
- stdio: "inherit",
1866
- detached: true
1867
- });
1868
- }
1869
- }
1870
- }
1871
- buildShellCommands(projectName, projectPath, claudeArgs = []) {
1872
- const sessionName = this.getSessionName(projectName);
1873
- const escapedSession = escapeForSingleQuotes(sessionName);
1874
- const escapedPath = escapeForSingleQuotes(projectPath);
1875
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1876
- const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1877
- return [
1878
- `# Create tmux split session for ${sanitizeForShell(projectName)}`,
1879
- `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1880
- `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
1881
- `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
1882
- `tmux select-pane -t '${escapedSession}:0.0'`,
1883
- this.getAttachCommand(sessionName)
1884
- ];
1885
- }
1886
- buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1887
- const sessionName = this.getSessionName(projectName);
1888
- const escapedSession = escapeForSingleQuotes(sessionName);
1889
- const escapedPath = escapeForSingleQuotes(projectPath);
1890
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1891
- const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1892
- const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1893
- return [
1894
- `# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
1895
- `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1896
- `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
1897
- `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
1898
- `tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
1899
- `tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
1900
- `tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
1901
- `tmux select-pane -t '${escapedSession}:0.0'`,
1902
- this.getAttachCommand(sessionName)
1903
- ];
1904
- }
1905
- buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
1906
- const sessionName = this.getSessionName(projectName);
1907
- const escapedSession = escapeForSingleQuotes(sessionName);
1908
- const escapedPath = escapeForSingleQuotes(projectPath);
1909
- const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1910
- return [
1911
- `# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
1912
- `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1913
- `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
1914
- `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
1915
- `tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
1916
- `tmux select-pane -t '${escapedSession}:0.0'`,
1917
- this.getAttachCommand(sessionName)
1918
- ];
1919
- }
1920
- getAttachCommand(sessionName) {
1921
- const escapedSession = escapeForSingleQuotes(sessionName);
1922
- if (process.env.TMUX) {
1923
- return `tmux switch-client -t '${escapedSession}'`;
1924
- }
1925
- const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
1926
- const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1927
- if (useiTermIntegration) {
1928
- return `tmux -CC attach-session -t '${escapedSession}'`;
1929
- }
1930
- return `tmux attach-session -t '${escapedSession}'`;
1931
- }
1932
- async listWorkonSessions() {
1933
- try {
1934
- const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
1935
- return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
1936
- } catch {
1937
- return [];
1938
- }
2064
+ async function runOpen(projectArg, options, ctx) {
2065
+ const { log } = ctx;
2066
+ if (options.debug) {
2067
+ log.setLogLevel("debug");
1939
2068
  }
1940
- };
1941
-
1942
- // src/commands/open.ts
1943
- init_registry();
2069
+ await processProject(projectArg, options, ctx);
2070
+ }
1944
2071
  function createOpenCommand(ctx) {
1945
2072
  const { config, log } = ctx;
1946
2073
  const command = new Command("open").description("Open a project by passing its project id").argument("[project]", "The id of the project to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("-n, --dry-run", "Show what would happen without executing").option("--shell", "Output shell commands instead of spawning processes").action(async (projectArg, options) => {
@@ -1985,9 +2112,9 @@ async function processProject(projectParam, options, ctx) {
1985
2112
  const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
1986
2113
  await switchTo(projectEnv, requestedCommands, options, ctx);
1987
2114
  } else {
1988
- log.debug(`Project '${projectName}' not found, starting interactive mode`);
1989
- const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
1990
- await runInteractive2({ config, log, environment, suggestedName: projectName });
2115
+ log.error(`Project '${projectName}' not found.`);
2116
+ log.info(`Run 'workon' without arguments to see available projects or create a new one.`);
2117
+ process.exit(1);
1991
2118
  }
1992
2119
  }
1993
2120
  function validateRequestedCommands(requestedCommands, projectConfig, projectName) {
@@ -2055,7 +2182,8 @@ function resolveCommandDependencies(requestedCommands, project) {
2055
2182
  }
2056
2183
  function getClaudeArgs(project) {
2057
2184
  const claudeConfig = project.events.claude;
2058
- return typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
2185
+ const userFlags = typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
2186
+ return ["--dangerously-skip-permissions", ...userFlags];
2059
2187
  }
2060
2188
  async function getNpmCommand(project) {
2061
2189
  const npmConfig = project.events.npm;
@@ -2069,12 +2197,25 @@ async function handleTmuxLayout(project, layout, options, shellCommands, events,
2069
2197
  let tmuxHandled = false;
2070
2198
  if (isShellMode) {
2071
2199
  if (await tmux.isTmuxAvailable()) {
2200
+ const remainingEvents = events.filter((e) => !layout.handledEvents.includes(e));
2201
+ for (const event of remainingEvents) {
2202
+ const eventHandler = EventRegistry.getEventByName(event);
2203
+ if (eventHandler) {
2204
+ const cmds = eventHandler.processing.generateShellCommand({
2205
+ project,
2206
+ isShellMode: true,
2207
+ shellCommands: []
2208
+ });
2209
+ shellCommands.push(...cmds);
2210
+ }
2211
+ }
2072
2212
  const commands = buildLayoutShellCommands(tmux, project, layout);
2073
2213
  shellCommands.push(...commands);
2074
2214
  tmuxHandled = true;
2075
2215
  } else {
2076
2216
  log.debug("Tmux not available, falling back to normal mode");
2077
- buildFallbackCommands(shellCommands, project, layout);
2217
+ const remainingEvents = events.filter((e) => !layout.handledEvents.includes(e));
2218
+ buildFallbackCommandsWithEvents(shellCommands, project, layout, remainingEvents, ctx);
2078
2219
  tmuxHandled = true;
2079
2220
  }
2080
2221
  } else if (!dryRun) {
@@ -2098,7 +2239,7 @@ async function handleTmuxLayout(project, layout, options, shellCommands, events,
2098
2239
  await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2099
2240
  }
2100
2241
  }
2101
- if (!dryRun) {
2242
+ if (!dryRun && !isShellMode) {
2102
2243
  for (const event of events.filter((e) => !layout.handledEvents.includes(e))) {
2103
2244
  await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2104
2245
  }
@@ -2119,14 +2260,19 @@ function buildLayoutShellCommands(tmux, project, layout) {
2119
2260
  return tmux.buildTwoPaneNpmShellCommands(project.name, project.path.path, layout.npmCommand);
2120
2261
  }
2121
2262
  }
2122
- function buildFallbackCommands(shellCommands, project, layout) {
2123
- shellCommands.push(`cd "${project.path.path}"`);
2124
- if (layout.type === "split-claude" || layout.type === "three-pane") {
2125
- const claudeCommand = layout.claudeArgs.length > 0 ? `claude ${layout.claudeArgs.join(" ")}` : "claude";
2126
- shellCommands.push(claudeCommand);
2127
- }
2128
- if (layout.npmCommand) {
2129
- shellCommands.push(layout.npmCommand);
2263
+ function buildFallbackCommandsWithEvents(shellCommands, project, layout, remainingEvents, _ctx) {
2264
+ shellCommands.push(`echo "\u26A0 tmux not available - install with: brew install tmux" >&2`);
2265
+ shellCommands.push(`pushd "${project.path.path}" > /dev/null`);
2266
+ for (const event of remainingEvents) {
2267
+ const eventHandler = EventRegistry.getEventByName(event);
2268
+ if (eventHandler) {
2269
+ const cmds = eventHandler.processing.generateShellCommand({
2270
+ project,
2271
+ isShellMode: true,
2272
+ shellCommands: []
2273
+ });
2274
+ shellCommands.push(...cmds);
2275
+ }
2130
2276
  }
2131
2277
  }
2132
2278
  async function createTmuxSession(tmux, project, layout) {
@@ -2227,6 +2373,27 @@ Available commands for '${projectName}':`);
2227
2373
  console.log(` workon ${projectName}:cwd --shell # Output shell commands
2228
2374
  `);
2229
2375
  }
2376
+ var init_open = __esm({
2377
+ "src/commands/open.ts"() {
2378
+ "use strict";
2379
+ init_environment();
2380
+ init_tmux();
2381
+ init_registry();
2382
+ }
2383
+ });
2384
+
2385
+ // src/commands/index.ts
2386
+ init_config();
2387
+ init_environment();
2388
+ init_registry();
2389
+ init_open();
2390
+ import { Command as Command8 } from "commander";
2391
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
2392
+ import { join, dirname as dirname2 } from "path";
2393
+ import { fileURLToPath } from "url";
2394
+ import loog from "loog";
2395
+ import omelette from "omelette";
2396
+ import File7 from "phylo";
2230
2397
 
2231
2398
  // src/commands/config/index.ts
2232
2399
  import { Command as Command5 } from "commander";
@@ -2449,7 +2616,7 @@ async function createProject(ctx) {
2449
2616
  default: true
2450
2617
  });
2451
2618
  if (confirmed) {
2452
- config.setProject(name, projectConfig);
2619
+ await config.setProjectSafe(name, projectConfig);
2453
2620
  log.info(`Project '${name}' created successfully!`);
2454
2621
  log.info(`Use 'workon ${name}' to start working!`);
2455
2622
  } else {
@@ -2539,7 +2706,7 @@ async function editProject(ctx) {
2539
2706
  default: true
2540
2707
  });
2541
2708
  if (confirmed) {
2542
- config.setProject(name, updatedConfig);
2709
+ await config.setProjectSafe(name, updatedConfig);
2543
2710
  log.info(`Project '${name}' updated successfully!`);
2544
2711
  } else {
2545
2712
  log.info("Edit cancelled.");
@@ -2562,7 +2729,7 @@ async function deleteProject(ctx) {
2562
2729
  default: false
2563
2730
  });
2564
2731
  if (confirmed) {
2565
- config.deleteProject(name);
2732
+ await config.deleteProjectSafe(name);
2566
2733
  log.info(`Project '${name}' deleted.`);
2567
2734
  } else {
2568
2735
  log.info("Delete cancelled.");
@@ -2588,7 +2755,7 @@ async function listProjects(ctx) {
2588
2755
 
2589
2756
  // src/commands/add.ts
2590
2757
  import { Command as Command7 } from "commander";
2591
- import { existsSync, readFileSync } from "fs";
2758
+ import { existsSync as existsSync2, readFileSync } from "fs";
2592
2759
  import { basename, resolve } from "path";
2593
2760
  import File6 from "phylo";
2594
2761
  import { confirm as confirm5 } from "@inquirer/prompts";
@@ -2610,7 +2777,7 @@ async function addProject(pathArg, options, ctx) {
2610
2777
  const projects = config.getProjects();
2611
2778
  const targetPath = resolve(pathArg);
2612
2779
  log.debug(`Resolved path: ${targetPath}`);
2613
- if (!existsSync(targetPath)) {
2780
+ if (!existsSync2(targetPath)) {
2614
2781
  log.error(`Path does not exist: ${targetPath}`);
2615
2782
  process.exit(1);
2616
2783
  }
@@ -2644,7 +2811,7 @@ async function addProject(pathArg, options, ctx) {
2644
2811
  return;
2645
2812
  }
2646
2813
  }
2647
- const ide = options.ide || discovery.detectedIde || "vscode";
2814
+ const ide = options.ide || discovery.detectedIde || "code";
2648
2815
  log.debug(`IDE: ${ide}`);
2649
2816
  let relativePath = targetPath;
2650
2817
  if (defaults?.base) {
@@ -2672,7 +2839,10 @@ async function addProject(pathArg, options, ctx) {
2672
2839
  projectConfig.events.npm = scripts.dev ? "dev" : "start";
2673
2840
  }
2674
2841
  }
2675
- config.setProject(projectName, projectConfig);
2842
+ if (discovery.hasClaude) {
2843
+ projectConfig.events.claude = true;
2844
+ }
2845
+ await config.setProjectSafe(projectName, projectConfig);
2676
2846
  log.info(`Added project '${projectName}'`);
2677
2847
  log.info(` Path: ${relativePath}`);
2678
2848
  log.info(` IDE: ${ide}`);
@@ -2686,11 +2856,12 @@ function discoverProject(targetPath, log) {
2686
2856
  name: dirName,
2687
2857
  isNode: false,
2688
2858
  isBun: false,
2859
+ hasClaude: false,
2689
2860
  detectedIde: null,
2690
2861
  packageJson: null
2691
2862
  };
2692
2863
  const packageJsonPath = resolve(targetPath, "package.json");
2693
- if (existsSync(packageJsonPath)) {
2864
+ if (existsSync2(packageJsonPath)) {
2694
2865
  discovery.isNode = true;
2695
2866
  log.debug("Detected Node project (package.json found)");
2696
2867
  try {
@@ -2706,25 +2877,34 @@ function discoverProject(targetPath, log) {
2706
2877
  }
2707
2878
  }
2708
2879
  const bunLockPath = resolve(targetPath, "bun.lockb");
2709
- if (existsSync(bunLockPath)) {
2880
+ if (existsSync2(bunLockPath)) {
2710
2881
  discovery.isBun = true;
2711
2882
  log.debug("Detected Bun project (bun.lockb found)");
2712
2883
  }
2713
2884
  const vscodeDir = resolve(targetPath, ".vscode");
2885
+ const cursorDir = resolve(targetPath, ".cursor");
2714
2886
  const ideaDir = resolve(targetPath, ".idea");
2715
- if (existsSync(vscodeDir)) {
2716
- discovery.detectedIde = "vscode";
2887
+ if (existsSync2(cursorDir)) {
2888
+ discovery.detectedIde = "cursor";
2889
+ log.debug("Detected Cursor (.cursor directory found)");
2890
+ } else if (existsSync2(vscodeDir)) {
2891
+ discovery.detectedIde = "code";
2717
2892
  log.debug("Detected VS Code (.vscode directory found)");
2718
- } else if (existsSync(ideaDir)) {
2893
+ } else if (existsSync2(ideaDir)) {
2719
2894
  discovery.detectedIde = "idea";
2720
2895
  log.debug("Detected IntelliJ IDEA (.idea directory found)");
2721
2896
  }
2897
+ const claudeMdPath = resolve(targetPath, "CLAUDE.md");
2898
+ if (existsSync2(claudeMdPath)) {
2899
+ discovery.hasClaude = true;
2900
+ log.debug("Detected Claude Code project (CLAUDE.md found)");
2901
+ }
2722
2902
  return discovery;
2723
2903
  }
2724
2904
 
2725
2905
  // src/commands/index.ts
2726
2906
  var __filename = fileURLToPath(import.meta.url);
2727
- var __dirname = dirname(__filename);
2907
+ var __dirname = dirname2(__filename);
2728
2908
  function findPackageJson() {
2729
2909
  const paths = [
2730
2910
  join(__dirname, "../package.json"),
@@ -2732,7 +2912,7 @@ function findPackageJson() {
2732
2912
  join(process.cwd(), "package.json")
2733
2913
  ];
2734
2914
  for (const p of paths) {
2735
- if (existsSync2(p)) {
2915
+ if (existsSync3(p)) {
2736
2916
  return p;
2737
2917
  }
2738
2918
  }
@@ -2750,7 +2930,7 @@ function createCli() {
2750
2930
  config.set("pkg", packageJson);
2751
2931
  EnvironmentRecognizer.configure(config, log);
2752
2932
  const completion = setupCompletion(config);
2753
- program2.name("workon").description("Work on something great!").version(packageJson.version).argument("[project]", "Project name to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("--completion", "Setup shell tab completion").option("--shell", "Output shell commands for evaluation").option("--init", "Generate shell integration function").hook("preAction", async (thisCommand) => {
2933
+ program2.name("workon").description("Work on something great!").version(packageJson.version).enablePositionalOptions().argument("[project]", "Project name to open (supports project:command syntax)").option("-d, --debug", "Enable debug logging").option("--completion", "Setup shell tab completion").option("--shell", "Output shell commands for evaluation").option("--init", "Generate shell integration function").hook("preAction", async (thisCommand) => {
2754
2934
  const opts = thisCommand.opts();
2755
2935
  if (opts.debug) {
2756
2936
  log.setLogLevel("debug");
@@ -2772,10 +2952,8 @@ function createCli() {
2772
2952
  return;
2773
2953
  }
2774
2954
  if (project) {
2775
- const args = ["open", project];
2776
- if (options.shell) args.push("--shell");
2777
- if (options.debug) args.push("--debug");
2778
- await program2.parseAsync(["node", "workon", ...args]);
2955
+ const { runOpen: runOpen2 } = await Promise.resolve().then(() => (init_open(), open_exports));
2956
+ await runOpen2(project, { debug: options.debug, shell: options.shell }, { config, log });
2779
2957
  return;
2780
2958
  }
2781
2959
  const environment = await EnvironmentRecognizer.recognize(File7.cwd());
@@ -2874,6 +3052,15 @@ workon() {
2874
3052
  }
2875
3053
 
2876
3054
  // src/cli.ts
3055
+ if (process.stdout.isTTY) {
3056
+ process.stdout.write("");
3057
+ }
2877
3058
  var program = createCli();
2878
- program.parse();
3059
+ program.parseAsync().catch((error) => {
3060
+ if (error?.name === "ExitPromptError" || error?.message?.includes("SIGINT")) {
3061
+ process.exit(130);
3062
+ }
3063
+ console.error(error);
3064
+ process.exit(1);
3065
+ });
2879
3066
  //# sourceMappingURL=cli.js.map