workon 3.2.0 → 3.2.2

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
@@ -236,7 +236,8 @@ var init_environment = __esm({
236
236
  const git = simpleGit(gitDir.path);
237
237
  const branchSummary = await git.branchLocal();
238
238
  base.branch = branchSummary.current;
239
- } catch {
239
+ } catch (error) {
240
+ this.log.debug(`Git branch detection failed: ${error.message}`);
240
241
  }
241
242
  }
242
243
  return this.getProjectEnvironment(base, matching);
@@ -302,7 +303,7 @@ var init_environment = __esm({
302
303
  });
303
304
 
304
305
  // src/events/core/cwd.ts
305
- import { spawn as spawn2 } from "child_process";
306
+ import { spawn } from "child_process";
306
307
  var CwdEvent;
307
308
  var init_cwd = __esm({
308
309
  "src/events/core/cwd.ts"() {
@@ -344,18 +345,22 @@ var init_cwd = __esm({
344
345
  const { project, isShellMode, shellCommands } = context;
345
346
  const projectPath = project.path.path;
346
347
  if (isShellMode) {
347
- shellCommands.push(`cd "${projectPath}"`);
348
+ shellCommands.push(`pushd "${projectPath}" > /dev/null`);
348
349
  } else {
349
350
  const shell = process.env.SHELL || "/bin/bash";
350
- spawn2(shell, [], {
351
+ const child = spawn(shell, ["-i"], {
351
352
  cwd: projectPath,
352
353
  stdio: "inherit"
353
354
  });
355
+ await new Promise((resolve2, reject) => {
356
+ child.on("close", () => resolve2());
357
+ child.on("error", (err) => reject(err));
358
+ });
354
359
  }
355
360
  },
356
361
  generateShellCommand(context) {
357
362
  const projectPath = context.project.path.path;
358
- return [`cd "${projectPath}"`];
363
+ return [`pushd "${projectPath}" > /dev/null`];
359
364
  }
360
365
  };
361
366
  }
@@ -377,7 +382,7 @@ var init_cwd = __esm({
377
382
  });
378
383
 
379
384
  // src/events/core/ide.ts
380
- import { spawn as spawn3 } from "child_process";
385
+ import { spawn as spawn2 } from "child_process";
381
386
  var IdeEvent;
382
387
  var init_ide = __esm({
383
388
  "src/events/core/ide.ts"() {
@@ -420,9 +425,9 @@ var init_ide = __esm({
420
425
  const projectPath = project.path.path;
421
426
  const ide = project.ide || "code";
422
427
  if (isShellMode) {
423
- shellCommands.push(`${ide} "${projectPath}" &`);
428
+ shellCommands.push(`set +m; ${ide} "${projectPath}" &>/dev/null &`);
424
429
  } else {
425
- spawn3(ide, [projectPath], {
430
+ spawn2(ide, [projectPath], {
426
431
  detached: true,
427
432
  stdio: "ignore"
428
433
  }).unref();
@@ -431,7 +436,7 @@ var init_ide = __esm({
431
436
  generateShellCommand(context) {
432
437
  const projectPath = context.project.path.path;
433
438
  const ide = context.project.ide || "code";
434
- return [`${ide} "${projectPath}" &`];
439
+ return [`set +m; ${ide} "${projectPath}" &>/dev/null &`];
435
440
  }
436
441
  };
437
442
  }
@@ -453,7 +458,7 @@ var init_ide = __esm({
453
458
  });
454
459
 
455
460
  // src/events/core/web.ts
456
- import { spawn as spawn4 } from "child_process";
461
+ import { spawn as spawn3 } from "child_process";
457
462
  import { platform } from "os";
458
463
  var WebEvent;
459
464
  var init_web = __esm({
@@ -514,7 +519,7 @@ var init_web = __esm({
514
519
  if (isShellMode) {
515
520
  shellCommands.push(`${openCmd} "${homepage}" &`);
516
521
  } else {
517
- spawn4(openCmd, [homepage], {
522
+ spawn3(openCmd, [homepage], {
518
523
  detached: true,
519
524
  stdio: "ignore"
520
525
  }).unref();
@@ -546,7 +551,7 @@ var init_web = __esm({
546
551
  });
547
552
 
548
553
  // src/events/extensions/claude.ts
549
- import { spawn as spawn5 } from "child_process";
554
+ import { spawn as spawn4 } from "child_process";
550
555
  import { input, confirm } from "@inquirer/prompts";
551
556
  var ClaudeEvent;
552
557
  var init_claude = __esm({
@@ -642,7 +647,7 @@ var init_claude = __esm({
642
647
  shellCommands.push(claudeCommand);
643
648
  } else {
644
649
  const args = claudeCommand.split(" ").slice(1);
645
- spawn5("claude", args, {
650
+ spawn4("claude", args, {
646
651
  cwd: project.path.path,
647
652
  stdio: "inherit"
648
653
  });
@@ -686,7 +691,7 @@ var init_claude = __esm({
686
691
  });
687
692
 
688
693
  // src/events/extensions/docker.ts
689
- import { spawn as spawn6 } from "child_process";
694
+ import { spawn as spawn5 } from "child_process";
690
695
  import { input as input2 } from "@inquirer/prompts";
691
696
  var DockerEvent;
692
697
  var init_docker = __esm({
@@ -784,7 +789,7 @@ var init_docker = __esm({
784
789
  shellCommands.push(dockerCommand);
785
790
  } else {
786
791
  const [cmd, ...args] = dockerCommand.split(" ");
787
- spawn6(cmd, args, {
792
+ spawn5(cmd, args, {
788
793
  cwd: project.path.path,
789
794
  stdio: "inherit"
790
795
  });
@@ -823,7 +828,7 @@ __export(npm_exports, {
823
828
  NpmEvent: () => NpmEvent,
824
829
  default: () => npm_default
825
830
  });
826
- import { spawn as spawn7 } from "child_process";
831
+ import { spawn as spawn6 } from "child_process";
827
832
  import { input as input3, confirm as confirm2 } from "@inquirer/prompts";
828
833
  var NpmEvent, npm_default;
829
834
  var init_npm = __esm({
@@ -924,7 +929,7 @@ var init_npm = __esm({
924
929
  shellCommands.push(npmCommand);
925
930
  } else {
926
931
  const [cmd, ...args] = npmCommand.split(" ");
927
- spawn7(cmd, args, {
932
+ spawn6(cmd, args, {
928
933
  cwd: project.path.path,
929
934
  stdio: "inherit"
930
935
  });
@@ -993,23 +998,19 @@ var init_registry = __esm({
993
998
  */
994
999
  registerEvents() {
995
1000
  for (const EventClass of ALL_EVENTS) {
996
- if (this.isValidEvent(EventClass)) {
997
- const metadata = EventClass.metadata;
998
- this._events.set(metadata.name, EventClass);
1001
+ if (this.isValidEventClass(EventClass)) {
1002
+ this._events.set(EventClass.metadata.name, EventClass);
999
1003
  }
1000
1004
  }
1001
1005
  }
1002
1006
  /**
1003
- * Validate if a class is a proper event
1007
+ * Type guard to check if an object is a valid EventHandlerClass
1004
1008
  */
1005
- isValidEvent(EventClass) {
1006
- try {
1007
- if (typeof EventClass !== "function") return false;
1008
- const metadata = EventClass.metadata;
1009
- return metadata !== void 0 && typeof metadata.name === "string" && typeof metadata.displayName === "string" && typeof EventClass.validation === "object" && typeof EventClass.configuration === "object" && typeof EventClass.processing === "object";
1010
- } catch {
1011
- return false;
1012
- }
1009
+ isValidEventClass(obj) {
1010
+ if (typeof obj !== "function" && typeof obj !== "object") return false;
1011
+ if (obj === null) return false;
1012
+ const candidate = obj;
1013
+ return candidate.metadata !== void 0 && typeof candidate.metadata.name === "string" && typeof candidate.metadata.displayName === "string" && candidate.validation !== void 0 && typeof candidate.validation.validateConfig === "function" && candidate.configuration !== void 0 && typeof candidate.configuration.configureInteractive === "function" && candidate.processing !== void 0 && typeof candidate.processing.processEvent === "function";
1013
1014
  }
1014
1015
  /**
1015
1016
  * Get all valid event names from registered events
@@ -1031,12 +1032,11 @@ var init_registry = __esm({
1031
1032
  getEventsForManageUI() {
1032
1033
  this.ensureInitialized();
1033
1034
  const events = [];
1034
- for (const [name, EventClass] of this._events) {
1035
- const metadata = EventClass.metadata;
1035
+ for (const [name, eventClass] of this._events) {
1036
1036
  events.push({
1037
- name: metadata.displayName,
1037
+ name: eventClass.metadata.displayName,
1038
1038
  value: name,
1039
- description: metadata.description
1039
+ description: eventClass.metadata.description
1040
1040
  });
1041
1041
  }
1042
1042
  return events.sort((a, b) => a.name.localeCompare(b.name));
@@ -1047,13 +1047,13 @@ var init_registry = __esm({
1047
1047
  getTmuxEnabledEvents() {
1048
1048
  this.ensureInitialized();
1049
1049
  const tmuxEvents = [];
1050
- for (const [name, EventClass] of this._events) {
1051
- const tmux = EventClass.tmux;
1050
+ for (const [name, eventClass] of this._events) {
1051
+ const tmux = eventClass.tmux;
1052
1052
  if (tmux) {
1053
1053
  tmuxEvents.push({
1054
1054
  name,
1055
- event: EventClass,
1056
- priority: tmux.getLayoutPriority ? tmux.getLayoutPriority() : 0
1055
+ event: eventClass,
1056
+ priority: tmux.getLayoutPriority()
1057
1057
  });
1058
1058
  }
1059
1059
  }
@@ -1065,16 +1065,15 @@ var init_registry = __esm({
1065
1065
  getAllEvents() {
1066
1066
  this.ensureInitialized();
1067
1067
  const events = [];
1068
- for (const [name, EventClass] of this._events) {
1069
- const typedClass = EventClass;
1068
+ for (const [name, eventClass] of this._events) {
1070
1069
  events.push({
1071
1070
  name,
1072
- metadata: typedClass.metadata,
1073
- hasValidation: !!typedClass.validation,
1074
- hasConfiguration: !!typedClass.configuration,
1075
- hasProcessing: !!typedClass.processing,
1076
- hasTmux: !!typedClass.tmux,
1077
- hasHelp: !!typedClass.help
1071
+ metadata: eventClass.metadata,
1072
+ hasValidation: !!eventClass.validation,
1073
+ hasConfiguration: !!eventClass.configuration,
1074
+ hasProcessing: !!eventClass.processing,
1075
+ hasTmux: !!eventClass.tmux,
1076
+ hasHelp: !!eventClass.help
1078
1077
  });
1079
1078
  }
1080
1079
  return events;
@@ -1099,6 +1098,224 @@ var init_registry = __esm({
1099
1098
  }
1100
1099
  });
1101
1100
 
1101
+ // src/lib/sanitize.ts
1102
+ function sanitizeForShell(input6) {
1103
+ if (!input6) return "";
1104
+ return input6.replace(/[^a-zA-Z0-9_\-.]/g, "_");
1105
+ }
1106
+ function escapeForSingleQuotes(input6) {
1107
+ if (!input6) return "";
1108
+ return input6.replace(/'/g, "'\\''");
1109
+ }
1110
+ var init_sanitize = __esm({
1111
+ "src/lib/sanitize.ts"() {
1112
+ "use strict";
1113
+ }
1114
+ });
1115
+
1116
+ // src/lib/tmux.ts
1117
+ import { exec as execCallback, spawn as spawn7 } from "child_process";
1118
+ import { promisify } from "util";
1119
+ var exec, TmuxManager;
1120
+ var init_tmux = __esm({
1121
+ "src/lib/tmux.ts"() {
1122
+ "use strict";
1123
+ init_sanitize();
1124
+ exec = promisify(execCallback);
1125
+ TmuxManager = class {
1126
+ sessionPrefix = "workon-";
1127
+ async isTmuxAvailable() {
1128
+ try {
1129
+ await exec("which tmux");
1130
+ return true;
1131
+ } catch {
1132
+ return false;
1133
+ }
1134
+ }
1135
+ async sessionExists(sessionName) {
1136
+ try {
1137
+ await exec(`tmux has-session -t '${escapeForSingleQuotes(sessionName)}'`);
1138
+ return true;
1139
+ } catch {
1140
+ return false;
1141
+ }
1142
+ }
1143
+ getSessionName(projectName) {
1144
+ return `${this.sessionPrefix}${sanitizeForShell(projectName)}`;
1145
+ }
1146
+ async killSession(sessionName) {
1147
+ try {
1148
+ await exec(`tmux kill-session -t '${escapeForSingleQuotes(sessionName)}'`);
1149
+ return true;
1150
+ } catch {
1151
+ return false;
1152
+ }
1153
+ }
1154
+ async createSplitSession(projectName, projectPath, claudeArgs = []) {
1155
+ const sessionName = this.getSessionName(projectName);
1156
+ const escapedSession = escapeForSingleQuotes(sessionName);
1157
+ const escapedPath = escapeForSingleQuotes(projectPath);
1158
+ if (await this.sessionExists(sessionName)) {
1159
+ await this.killSession(sessionName);
1160
+ }
1161
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1162
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1163
+ await exec(
1164
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
1165
+ );
1166
+ await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
1167
+ await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1168
+ return sessionName;
1169
+ }
1170
+ async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1171
+ const sessionName = this.getSessionName(projectName);
1172
+ const escapedSession = escapeForSingleQuotes(sessionName);
1173
+ const escapedPath = escapeForSingleQuotes(projectPath);
1174
+ if (await this.sessionExists(sessionName)) {
1175
+ await this.killSession(sessionName);
1176
+ }
1177
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1178
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1179
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1180
+ await exec(
1181
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`
1182
+ );
1183
+ await exec(`tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`);
1184
+ await exec(
1185
+ `tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`
1186
+ );
1187
+ await exec(`tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`);
1188
+ await exec(`tmux resize-pane -t '${escapedSession}:0.2' -y 10`);
1189
+ await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1190
+ return sessionName;
1191
+ }
1192
+ async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
1193
+ const sessionName = this.getSessionName(projectName);
1194
+ const escapedSession = escapeForSingleQuotes(sessionName);
1195
+ const escapedPath = escapeForSingleQuotes(projectPath);
1196
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1197
+ if (await this.sessionExists(sessionName)) {
1198
+ await this.killSession(sessionName);
1199
+ }
1200
+ await exec(`tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`);
1201
+ await exec(
1202
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`
1203
+ );
1204
+ await exec(`tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`);
1205
+ await exec(`tmux select-pane -t '${escapedSession}:0.0'`);
1206
+ return sessionName;
1207
+ }
1208
+ async attachToSession(sessionName) {
1209
+ const escapedSession = escapeForSingleQuotes(sessionName);
1210
+ if (process.env.TMUX) {
1211
+ await exec(`tmux switch-client -t '${escapedSession}'`);
1212
+ } else {
1213
+ const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
1214
+ const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1215
+ if (useiTermIntegration) {
1216
+ spawn7("tmux", ["-CC", "attach-session", "-t", sessionName], {
1217
+ stdio: "inherit",
1218
+ detached: true
1219
+ });
1220
+ } else {
1221
+ spawn7("tmux", ["attach-session", "-t", sessionName], {
1222
+ stdio: "inherit",
1223
+ detached: true
1224
+ });
1225
+ }
1226
+ }
1227
+ }
1228
+ buildShellCommands(projectName, projectPath, claudeArgs = []) {
1229
+ const sessionName = this.getSessionName(projectName);
1230
+ const escapedSession = escapeForSingleQuotes(sessionName);
1231
+ const escapedPath = escapeForSingleQuotes(projectPath);
1232
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1233
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1234
+ return [
1235
+ `# Create tmux split session for ${sanitizeForShell(projectName)}`,
1236
+ `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1237
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
1238
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
1239
+ `tmux select-pane -t '${escapedSession}:0.0'`,
1240
+ this.getAttachCommand(sessionName)
1241
+ ];
1242
+ }
1243
+ buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1244
+ const sessionName = this.getSessionName(projectName);
1245
+ const escapedSession = escapeForSingleQuotes(sessionName);
1246
+ const escapedPath = escapeForSingleQuotes(projectPath);
1247
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1248
+ const escapedClaudeCmd = escapeForSingleQuotes(claudeCommand);
1249
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1250
+ return [
1251
+ `# Create tmux three-pane session for ${sanitizeForShell(projectName)}`,
1252
+ `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1253
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}' '${escapedClaudeCmd}'`,
1254
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}'`,
1255
+ `tmux split-window -v -t '${escapedSession}:0.1' -c '${escapedPath}' '${escapedNpmCmd}'`,
1256
+ `tmux set-option -t '${escapedSession}:0.2' remain-on-exit on`,
1257
+ `tmux resize-pane -t '${escapedSession}:0.2' -y 10`,
1258
+ `tmux select-pane -t '${escapedSession}:0.0'`,
1259
+ this.getAttachCommand(sessionName)
1260
+ ];
1261
+ }
1262
+ buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
1263
+ const sessionName = this.getSessionName(projectName);
1264
+ const escapedSession = escapeForSingleQuotes(sessionName);
1265
+ const escapedPath = escapeForSingleQuotes(projectPath);
1266
+ const escapedNpmCmd = escapeForSingleQuotes(npmCommand);
1267
+ return [
1268
+ `# Create tmux two-pane session with npm for ${sanitizeForShell(projectName)}`,
1269
+ `tmux has-session -t '${escapedSession}' 2>/dev/null && tmux kill-session -t '${escapedSession}'`,
1270
+ `tmux new-session -d -s '${escapedSession}' -c '${escapedPath}'`,
1271
+ `tmux split-window -h -t '${escapedSession}' -c '${escapedPath}' '${escapedNpmCmd}'`,
1272
+ `tmux set-option -t '${escapedSession}:0.1' remain-on-exit on`,
1273
+ `tmux select-pane -t '${escapedSession}:0.0'`,
1274
+ this.getAttachCommand(sessionName)
1275
+ ];
1276
+ }
1277
+ getAttachCommand(sessionName) {
1278
+ const escapedSession = escapeForSingleQuotes(sessionName);
1279
+ if (process.env.TMUX) {
1280
+ return `tmux switch-client -t '${escapedSession}'`;
1281
+ }
1282
+ const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
1283
+ const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1284
+ if (useiTermIntegration) {
1285
+ return `tmux -CC attach-session -t '${escapedSession}'`;
1286
+ }
1287
+ return `tmux attach-session -t '${escapedSession}'`;
1288
+ }
1289
+ async listWorkonSessions() {
1290
+ try {
1291
+ const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
1292
+ return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
1293
+ } catch {
1294
+ return [];
1295
+ }
1296
+ }
1297
+ };
1298
+ }
1299
+ });
1300
+
1301
+ // src/types/constants.ts
1302
+ var IDE_CHOICES;
1303
+ var init_constants = __esm({
1304
+ "src/types/constants.ts"() {
1305
+ "use strict";
1306
+ IDE_CHOICES = [
1307
+ { name: "Cursor", value: "cursor" },
1308
+ { name: "Visual Studio Code", value: "vscode" },
1309
+ { name: "Visual Studio Code (code)", value: "code" },
1310
+ { name: "IntelliJ IDEA", value: "idea" },
1311
+ { name: "Atom", value: "atom" },
1312
+ { name: "Sublime Text", value: "subl" },
1313
+ { name: "Vim", value: "vim" },
1314
+ { name: "Emacs", value: "emacs" }
1315
+ ];
1316
+ }
1317
+ });
1318
+
1102
1319
  // src/commands/interactive.ts
1103
1320
  var interactive_exports = {};
1104
1321
  __export(interactive_exports, {
@@ -1270,8 +1487,10 @@ async function initBranch(defaultName, ctx) {
1270
1487
  });
1271
1488
  const branchName = `${defaultName}#${branch}`;
1272
1489
  const baseProject = projects[defaultName];
1273
- const branchConfig = deepAssign2({}, baseProject, { branch });
1274
- delete branchConfig.name;
1490
+ const { name: _excludedName, ...mergedConfig } = deepAssign2({}, baseProject, {
1491
+ branch
1492
+ });
1493
+ const branchConfig = mergedConfig;
1275
1494
  projects[branchName] = branchConfig;
1276
1495
  config.set("projects", projects);
1277
1496
  log.info("Your branch configuration has been initialized.");
@@ -1315,7 +1534,6 @@ async function switchBranch(projectName, ctx) {
1315
1534
  }
1316
1535
  async function manageProjects(ctx) {
1317
1536
  const { config } = ctx;
1318
- await EventRegistry.initialize();
1319
1537
  const projects = config.getProjects();
1320
1538
  const hasProjects = Object.keys(projects).length > 0;
1321
1539
  const choices = [
@@ -1392,7 +1610,6 @@ async function openProject(projectName, ctx) {
1392
1610
  log.error(`Project '${projectName}' not found.`);
1393
1611
  return;
1394
1612
  }
1395
- await EventRegistry.initialize();
1396
1613
  const projectConfig = projects[projectName];
1397
1614
  const projectCfg = { ...projectConfig, name: projectName };
1398
1615
  const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
@@ -1402,7 +1619,7 @@ async function openProject(projectName, ctx) {
1402
1619
  );
1403
1620
  for (const event of events) {
1404
1621
  const eventHandler = EventRegistry.getEventByName(event);
1405
- if (eventHandler && eventHandler.processing) {
1622
+ if (eventHandler) {
1406
1623
  await eventHandler.processing.processEvent({
1407
1624
  project: projectEnv.project,
1408
1625
  isShellMode: false,
@@ -1711,193 +1928,36 @@ Branch configurations for '${projectName}':
1711
1928
  console.log();
1712
1929
  }
1713
1930
  }
1714
- var IDE_CHOICES;
1715
1931
  var init_interactive = __esm({
1716
1932
  "src/commands/interactive.ts"() {
1717
1933
  "use strict";
1718
1934
  init_environment();
1719
1935
  init_registry();
1720
- IDE_CHOICES = [
1721
- { name: "Visual Studio Code", value: "vscode" },
1722
- { name: "IntelliJ IDEA", value: "idea" },
1723
- { name: "Atom", value: "atom" }
1724
- ];
1936
+ init_constants();
1725
1937
  }
1726
1938
  });
1727
1939
 
1728
- // src/commands/index.ts
1729
- init_config();
1730
- init_environment();
1731
- import { Command as Command8 } from "commander";
1732
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1733
- import { join, dirname } from "path";
1734
- import { fileURLToPath } from "url";
1735
- import loog from "loog";
1736
- import omelette from "omelette";
1737
- import File7 from "phylo";
1738
-
1739
1940
  // src/commands/open.ts
1740
- init_environment();
1941
+ var open_exports = {};
1942
+ __export(open_exports, {
1943
+ createOpenCommand: () => createOpenCommand,
1944
+ runOpen: () => runOpen
1945
+ });
1741
1946
  import { Command } from "commander";
1742
1947
  import File4 from "phylo";
1743
-
1744
- // src/lib/tmux.ts
1745
- import { exec as execCallback, spawn } from "child_process";
1746
- import { promisify } from "util";
1747
- var exec = promisify(execCallback);
1748
- var TmuxManager = class {
1749
- sessionPrefix = "workon-";
1750
- async isTmuxAvailable() {
1751
- try {
1752
- await exec("which tmux");
1753
- return true;
1754
- } catch {
1755
- return false;
1756
- }
1757
- }
1758
- async sessionExists(sessionName) {
1759
- try {
1760
- await exec(`tmux has-session -t "${sessionName}"`);
1761
- return true;
1762
- } catch {
1763
- return false;
1764
- }
1765
- }
1766
- getSessionName(projectName) {
1767
- return `${this.sessionPrefix}${projectName}`;
1768
- }
1769
- async killSession(sessionName) {
1770
- try {
1771
- await exec(`tmux kill-session -t "${sessionName}"`);
1772
- return true;
1773
- } catch {
1774
- return false;
1775
- }
1776
- }
1777
- async createSplitSession(projectName, projectPath, claudeArgs = []) {
1778
- const sessionName = this.getSessionName(projectName);
1779
- if (await this.sessionExists(sessionName)) {
1780
- await this.killSession(sessionName);
1781
- }
1782
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1783
- await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
1784
- await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
1785
- await exec(`tmux select-pane -t "${sessionName}:0.0"`);
1786
- return sessionName;
1787
- }
1788
- async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1789
- const sessionName = this.getSessionName(projectName);
1790
- if (await this.sessionExists(sessionName)) {
1791
- await this.killSession(sessionName);
1792
- }
1793
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1794
- await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
1795
- await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
1796
- await exec(`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`);
1797
- await exec(`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`);
1798
- await exec(`tmux resize-pane -t "${sessionName}:0.2" -y 10`);
1799
- await exec(`tmux select-pane -t "${sessionName}:0.0"`);
1800
- return sessionName;
1801
- }
1802
- async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
1803
- const sessionName = this.getSessionName(projectName);
1804
- if (await this.sessionExists(sessionName)) {
1805
- await this.killSession(sessionName);
1806
- }
1807
- await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`);
1808
- await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`);
1809
- await exec(`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`);
1810
- await exec(`tmux select-pane -t "${sessionName}:0.0"`);
1811
- return sessionName;
1812
- }
1813
- async attachToSession(sessionName) {
1814
- if (process.env.TMUX) {
1815
- await exec(`tmux switch-client -t "${sessionName}"`);
1816
- } else {
1817
- const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
1818
- const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1819
- if (useiTermIntegration) {
1820
- spawn("tmux", ["-CC", "attach-session", "-t", sessionName], {
1821
- stdio: "inherit",
1822
- detached: true
1823
- });
1824
- } else {
1825
- spawn("tmux", ["attach-session", "-t", sessionName], {
1826
- stdio: "inherit",
1827
- detached: true
1828
- });
1829
- }
1830
- }
1831
- }
1832
- buildShellCommands(projectName, projectPath, claudeArgs = []) {
1833
- const sessionName = this.getSessionName(projectName);
1834
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1835
- return [
1836
- `# Create tmux split session for ${projectName}`,
1837
- `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
1838
- `tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
1839
- `tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
1840
- `tmux select-pane -t "${sessionName}:0.0"`,
1841
- this.getAttachCommand(sessionName)
1842
- ];
1843
- }
1844
- buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
1845
- const sessionName = this.getSessionName(projectName);
1846
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
1847
- return [
1848
- `# Create tmux three-pane session for ${projectName}`,
1849
- `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
1850
- `tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
1851
- `tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
1852
- `tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`,
1853
- `tmux set-option -t "${sessionName}:0.2" remain-on-exit on`,
1854
- `tmux resize-pane -t "${sessionName}:0.2" -y 10`,
1855
- `tmux select-pane -t "${sessionName}:0.0"`,
1856
- this.getAttachCommand(sessionName)
1857
- ];
1858
- }
1859
- buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
1860
- const sessionName = this.getSessionName(projectName);
1861
- return [
1862
- `# Create tmux two-pane session with npm for ${projectName}`,
1863
- `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
1864
- `tmux new-session -d -s "${sessionName}" -c "${projectPath}"`,
1865
- `tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`,
1866
- `tmux set-option -t "${sessionName}:0.1" remain-on-exit on`,
1867
- `tmux select-pane -t "${sessionName}:0.0"`,
1868
- this.getAttachCommand(sessionName)
1869
- ];
1870
- }
1871
- getAttachCommand(sessionName) {
1872
- if (process.env.TMUX) {
1873
- return `tmux switch-client -t "${sessionName}"`;
1874
- }
1875
- const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
1876
- const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
1877
- if (useiTermIntegration) {
1878
- return `tmux -CC attach-session -t "${sessionName}"`;
1879
- }
1880
- return `tmux attach-session -t "${sessionName}"`;
1881
- }
1882
- async listWorkonSessions() {
1883
- try {
1884
- const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
1885
- return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
1886
- } catch {
1887
- return [];
1888
- }
1948
+ async function runOpen(projectArg, options, ctx) {
1949
+ const { log } = ctx;
1950
+ if (options.debug) {
1951
+ log.setLogLevel("debug");
1889
1952
  }
1890
- };
1891
-
1892
- // src/commands/open.ts
1893
- init_registry();
1953
+ await processProject(projectArg, options, ctx);
1954
+ }
1894
1955
  function createOpenCommand(ctx) {
1895
1956
  const { config, log } = ctx;
1896
1957
  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) => {
1897
1958
  if (options.debug) {
1898
1959
  log.setLogLevel("debug");
1899
1960
  }
1900
- await EventRegistry.initialize();
1901
1961
  if (projectArg) {
1902
1962
  await processProject(projectArg, options, ctx);
1903
1963
  } else {
@@ -2004,32 +2064,47 @@ function resolveCommandDependencies(requestedCommands, project) {
2004
2064
  }
2005
2065
  return [...new Set(resolved)];
2006
2066
  }
2007
- async function handleSplitTerminal(project, isShellMode, dryRun, shellCommands, events, ctx) {
2067
+ function getClaudeArgs(project) {
2068
+ const claudeConfig = project.events.claude;
2069
+ return typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
2070
+ }
2071
+ async function getNpmCommand(project) {
2072
+ const npmConfig = project.events.npm;
2073
+ const { NpmEvent: NpmEvent2 } = await Promise.resolve().then(() => (init_npm(), npm_exports));
2074
+ return NpmEvent2.getNpmCommand(npmConfig);
2075
+ }
2076
+ async function handleTmuxLayout(project, layout, options, shellCommands, events, ctx) {
2008
2077
  const { log } = ctx;
2009
2078
  const tmux = new TmuxManager();
2010
- const claudeConfig = project.events.claude;
2011
- const claudeArgs = typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
2079
+ const { isShellMode, dryRun } = options;
2012
2080
  let tmuxHandled = false;
2013
2081
  if (isShellMode) {
2014
2082
  if (await tmux.isTmuxAvailable()) {
2015
- const commands = tmux.buildShellCommands(project.name, project.path.path, claudeArgs);
2083
+ const remainingEvents = events.filter((e) => !layout.handledEvents.includes(e));
2084
+ for (const event of remainingEvents) {
2085
+ const eventHandler = EventRegistry.getEventByName(event);
2086
+ if (eventHandler) {
2087
+ const cmds = eventHandler.processing.generateShellCommand({
2088
+ project,
2089
+ isShellMode: true,
2090
+ shellCommands: []
2091
+ });
2092
+ shellCommands.push(...cmds);
2093
+ }
2094
+ }
2095
+ const commands = buildLayoutShellCommands(tmux, project, layout);
2016
2096
  shellCommands.push(...commands);
2017
2097
  tmuxHandled = true;
2018
2098
  } else {
2019
2099
  log.debug("Tmux not available, falling back to normal mode");
2020
- shellCommands.push(`cd "${project.path.path}"`);
2021
- const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
2022
- shellCommands.push(claudeCommand);
2100
+ const remainingEvents = events.filter((e) => !layout.handledEvents.includes(e));
2101
+ buildFallbackCommandsWithEvents(shellCommands, project, layout, remainingEvents, ctx);
2023
2102
  tmuxHandled = true;
2024
2103
  }
2025
2104
  } else if (!dryRun) {
2026
2105
  if (await tmux.isTmuxAvailable()) {
2027
2106
  try {
2028
- const sessionName = await tmux.createSplitSession(
2029
- project.name,
2030
- project.path.path,
2031
- claudeArgs
2032
- );
2107
+ const sessionName = await createTmuxSession(tmux, project, layout);
2033
2108
  await tmux.attachToSession(sessionName);
2034
2109
  tmuxHandled = true;
2035
2110
  } catch (error) {
@@ -2039,139 +2114,108 @@ async function handleSplitTerminal(project, isShellMode, dryRun, shellCommands,
2039
2114
  log.debug("Tmux not available, falling back to normal event processing");
2040
2115
  }
2041
2116
  } else {
2042
- log.info(`Would create split tmux session '${project.name}' with Claude`);
2117
+ log.info(layout.dryRunMessage);
2043
2118
  tmuxHandled = true;
2044
2119
  }
2045
2120
  if (!tmuxHandled && !dryRun) {
2046
- for (const event of events.filter((e) => ["cwd", "claude"].includes(e))) {
2121
+ for (const event of events.filter((e) => layout.handledEvents.includes(e))) {
2047
2122
  await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2048
2123
  }
2049
2124
  }
2050
- if (!dryRun) {
2051
- for (const event of events.filter((e) => !["cwd", "claude"].includes(e))) {
2125
+ if (!dryRun && !isShellMode) {
2126
+ for (const event of events.filter((e) => !layout.handledEvents.includes(e))) {
2052
2127
  await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2053
2128
  }
2054
2129
  }
2055
2130
  }
2056
- async function handleThreePaneLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
2057
- const { log } = ctx;
2058
- const tmux = new TmuxManager();
2059
- const claudeConfig = project.events.claude;
2060
- const claudeArgs = typeof claudeConfig === "object" && claudeConfig.flags ? claudeConfig.flags : [];
2061
- const npmConfig = project.events.npm;
2062
- const { NpmEvent: NpmEvent2 } = await Promise.resolve().then(() => (init_npm(), npm_exports));
2063
- const npmCommand = NpmEvent2.getNpmCommand(npmConfig);
2064
- let tmuxHandled = false;
2065
- if (isShellMode) {
2066
- if (await tmux.isTmuxAvailable()) {
2067
- const commands = tmux.buildThreePaneShellCommands(
2131
+ function buildLayoutShellCommands(tmux, project, layout) {
2132
+ switch (layout.type) {
2133
+ case "split-claude":
2134
+ return tmux.buildShellCommands(project.name, project.path.path, layout.claudeArgs);
2135
+ case "three-pane":
2136
+ return tmux.buildThreePaneShellCommands(
2068
2137
  project.name,
2069
2138
  project.path.path,
2070
- claudeArgs,
2071
- npmCommand
2139
+ layout.claudeArgs,
2140
+ layout.npmCommand
2072
2141
  );
2073
- shellCommands.push(...commands);
2074
- tmuxHandled = true;
2075
- } else {
2076
- log.debug("Tmux not available, falling back to normal mode");
2077
- shellCommands.push(`cd "${project.path.path}"`);
2078
- shellCommands.push(claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude");
2079
- shellCommands.push(npmCommand);
2080
- tmuxHandled = true;
2081
- }
2082
- } else if (!dryRun) {
2083
- if (await tmux.isTmuxAvailable()) {
2084
- try {
2085
- const sessionName = await tmux.createThreePaneSession(
2086
- project.name,
2087
- project.path.path,
2088
- claudeArgs,
2089
- npmCommand
2090
- );
2091
- await tmux.attachToSession(sessionName);
2092
- tmuxHandled = true;
2093
- } catch (error) {
2094
- log.debug(`Failed to create tmux session: ${error.message}`);
2095
- }
2096
- } else {
2097
- log.debug("Tmux not available, falling back to normal event processing");
2098
- }
2099
- } else {
2100
- log.info(`Would create three-pane tmux session '${project.name}' with Claude and NPM`);
2101
- tmuxHandled = true;
2102
- }
2103
- if (!tmuxHandled && !dryRun) {
2104
- for (const event of events.filter((e) => ["cwd", "claude", "npm"].includes(e))) {
2105
- await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2106
- }
2142
+ case "two-pane-npm":
2143
+ return tmux.buildTwoPaneNpmShellCommands(project.name, project.path.path, layout.npmCommand);
2107
2144
  }
2108
- if (!dryRun) {
2109
- for (const event of events.filter((e) => !["cwd", "claude", "npm"].includes(e))) {
2110
- await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2145
+ }
2146
+ function buildFallbackCommandsWithEvents(shellCommands, project, layout, remainingEvents, _ctx) {
2147
+ shellCommands.push(`echo "\u26A0 tmux not available - install with: brew install tmux" >&2`);
2148
+ shellCommands.push(`pushd "${project.path.path}" > /dev/null`);
2149
+ for (const event of remainingEvents) {
2150
+ const eventHandler = EventRegistry.getEventByName(event);
2151
+ if (eventHandler) {
2152
+ const cmds = eventHandler.processing.generateShellCommand({
2153
+ project,
2154
+ isShellMode: true,
2155
+ shellCommands: []
2156
+ });
2157
+ shellCommands.push(...cmds);
2111
2158
  }
2112
2159
  }
2113
2160
  }
2114
- async function handleTwoPaneNpmLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
2115
- const { log } = ctx;
2116
- const tmux = new TmuxManager();
2117
- const npmConfig = project.events.npm;
2118
- const { NpmEvent: NpmEvent2 } = await Promise.resolve().then(() => (init_npm(), npm_exports));
2119
- const npmCommand = NpmEvent2.getNpmCommand(npmConfig);
2120
- let tmuxHandled = false;
2121
- if (isShellMode) {
2122
- if (await tmux.isTmuxAvailable()) {
2123
- const commands = tmux.buildTwoPaneNpmShellCommands(
2161
+ async function createTmuxSession(tmux, project, layout) {
2162
+ switch (layout.type) {
2163
+ case "split-claude":
2164
+ return tmux.createSplitSession(project.name, project.path.path, layout.claudeArgs);
2165
+ case "three-pane":
2166
+ return tmux.createThreePaneSession(
2124
2167
  project.name,
2125
2168
  project.path.path,
2126
- npmCommand
2169
+ layout.claudeArgs,
2170
+ layout.npmCommand
2127
2171
  );
2128
- shellCommands.push(...commands);
2129
- tmuxHandled = true;
2130
- } else {
2131
- log.debug("Tmux not available, falling back to normal mode");
2132
- shellCommands.push(`cd "${project.path.path}"`);
2133
- shellCommands.push(npmCommand);
2134
- tmuxHandled = true;
2135
- }
2136
- } else if (!dryRun) {
2137
- if (await tmux.isTmuxAvailable()) {
2138
- try {
2139
- const sessionName = await tmux.createTwoPaneNpmSession(
2140
- project.name,
2141
- project.path.path,
2142
- npmCommand
2143
- );
2144
- await tmux.attachToSession(sessionName);
2145
- tmuxHandled = true;
2146
- } catch (error) {
2147
- log.debug(`Failed to create tmux session: ${error.message}`);
2148
- }
2149
- } else {
2150
- log.debug("Tmux not available, falling back to normal event processing");
2151
- }
2152
- } else {
2153
- log.info(`Would create two-pane tmux session '${project.name}' with NPM`);
2154
- tmuxHandled = true;
2155
- }
2156
- if (!tmuxHandled && !dryRun) {
2157
- for (const event of events.filter((e) => ["cwd", "npm"].includes(e))) {
2158
- await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2159
- }
2160
- }
2161
- if (!dryRun) {
2162
- for (const event of events.filter((e) => !["cwd", "npm"].includes(e))) {
2163
- await processEvent(event, { project, isShellMode, shellCommands }, ctx);
2164
- }
2172
+ case "two-pane-npm":
2173
+ return tmux.createTwoPaneNpmSession(project.name, project.path.path, layout.npmCommand);
2165
2174
  }
2166
2175
  }
2176
+ async function handleSplitTerminal(project, isShellMode, dryRun, shellCommands, events, ctx) {
2177
+ const layout = {
2178
+ type: "split-claude",
2179
+ handledEvents: ["cwd", "claude"],
2180
+ dryRunMessage: `Would create split tmux session '${project.name}' with Claude`,
2181
+ claudeArgs: getClaudeArgs(project),
2182
+ npmCommand: null
2183
+ };
2184
+ await handleTmuxLayout(project, layout, { isShellMode, dryRun }, shellCommands, events, ctx);
2185
+ }
2186
+ async function handleThreePaneLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
2187
+ const layout = {
2188
+ type: "three-pane",
2189
+ handledEvents: ["cwd", "claude", "npm"],
2190
+ dryRunMessage: `Would create three-pane tmux session '${project.name}' with Claude and NPM`,
2191
+ claudeArgs: getClaudeArgs(project),
2192
+ npmCommand: await getNpmCommand(project)
2193
+ };
2194
+ await handleTmuxLayout(project, layout, { isShellMode, dryRun }, shellCommands, events, ctx);
2195
+ }
2196
+ async function handleTwoPaneNpmLayout(project, isShellMode, dryRun, shellCommands, events, ctx) {
2197
+ const layout = {
2198
+ type: "two-pane-npm",
2199
+ handledEvents: ["cwd", "npm"],
2200
+ dryRunMessage: `Would create two-pane tmux session '${project.name}' with NPM`,
2201
+ claudeArgs: [],
2202
+ npmCommand: await getNpmCommand(project)
2203
+ };
2204
+ await handleTmuxLayout(project, layout, { isShellMode, dryRun }, shellCommands, events, ctx);
2205
+ }
2167
2206
  async function processEvent(event, context, ctx) {
2168
2207
  const { log } = ctx;
2169
2208
  log.debug(`Processing event ${event}`);
2170
2209
  const eventHandler = EventRegistry.getEventByName(event);
2171
- if (eventHandler && eventHandler.processing) {
2172
- await eventHandler.processing.processEvent(context);
2173
- } else {
2210
+ if (!eventHandler) {
2174
2211
  log.debug(`No event handler found for: ${event}`);
2212
+ return;
2213
+ }
2214
+ try {
2215
+ await eventHandler.processing.processEvent(context);
2216
+ } catch (error) {
2217
+ log.error(`Failed to process event '${event}': ${error.message}`);
2218
+ log.debug(`Event error stack: ${error.stack}`);
2175
2219
  }
2176
2220
  }
2177
2221
  async function showProjectHelp(projectName, ctx) {
@@ -2189,17 +2233,16 @@ Available commands for '${projectName}':`);
2189
2233
  for (const eventName of configuredEvents) {
2190
2234
  const eventHandler = EventRegistry.getEventByName(eventName);
2191
2235
  if (eventHandler) {
2192
- const metadata = eventHandler.metadata;
2193
- const config2 = projectConfig.events[eventName];
2236
+ const eventConfig = projectConfig.events[eventName];
2194
2237
  let configDesc = "";
2195
- if (config2 !== true && config2 !== "true") {
2196
- if (typeof config2 === "object") {
2197
- configDesc = ` (${JSON.stringify(config2)})`;
2238
+ if (eventConfig !== true && eventConfig !== "true") {
2239
+ if (typeof eventConfig === "object") {
2240
+ configDesc = ` (${JSON.stringify(eventConfig)})`;
2198
2241
  } else {
2199
- configDesc = ` (${config2})`;
2242
+ configDesc = ` (${eventConfig})`;
2200
2243
  }
2201
2244
  }
2202
- console.log(` ${eventName.padEnd(8)} - ${metadata.description}${configDesc}`);
2245
+ console.log(` ${eventName.padEnd(8)} - ${eventHandler.metadata.description}${configDesc}`);
2203
2246
  }
2204
2247
  }
2205
2248
  console.log("\nUsage examples:");
@@ -2213,6 +2256,27 @@ Available commands for '${projectName}':`);
2213
2256
  console.log(` workon ${projectName}:cwd --shell # Output shell commands
2214
2257
  `);
2215
2258
  }
2259
+ var init_open = __esm({
2260
+ "src/commands/open.ts"() {
2261
+ "use strict";
2262
+ init_environment();
2263
+ init_tmux();
2264
+ init_registry();
2265
+ }
2266
+ });
2267
+
2268
+ // src/commands/index.ts
2269
+ init_config();
2270
+ init_environment();
2271
+ init_registry();
2272
+ init_open();
2273
+ import { Command as Command8 } from "commander";
2274
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
2275
+ import { join, dirname } from "path";
2276
+ import { fileURLToPath } from "url";
2277
+ import loog from "loog";
2278
+ import omelette from "omelette";
2279
+ import File7 from "phylo";
2216
2280
 
2217
2281
  // src/commands/config/index.ts
2218
2282
  import { Command as Command5 } from "commander";
@@ -2306,25 +2370,16 @@ function createConfigCommand(ctx) {
2306
2370
 
2307
2371
  // src/commands/manage.ts
2308
2372
  init_registry();
2373
+ init_constants();
2309
2374
  import { Command as Command6 } from "commander";
2310
2375
  import { select as select2, input as input5, confirm as confirm4, checkbox as checkbox2 } from "@inquirer/prompts";
2311
2376
  import File5 from "phylo";
2312
- var IDE_CHOICES2 = [
2313
- { name: "Visual Studio Code", value: "vscode" },
2314
- { name: "Visual Studio Code (code)", value: "code" },
2315
- { name: "IntelliJ IDEA", value: "idea" },
2316
- { name: "Atom", value: "atom" },
2317
- { name: "Sublime Text", value: "subl" },
2318
- { name: "Vim", value: "vim" },
2319
- { name: "Emacs", value: "emacs" }
2320
- ];
2321
2377
  function createManageCommand(ctx) {
2322
2378
  const { log } = ctx;
2323
2379
  return new Command6("manage").description("Interactive project management").option("-d, --debug", "Enable debug logging").action(async (options) => {
2324
2380
  if (options.debug) {
2325
2381
  log.setLogLevel("debug");
2326
2382
  }
2327
- await EventRegistry.initialize();
2328
2383
  await mainMenu(ctx);
2329
2384
  });
2330
2385
  }
@@ -2406,7 +2461,7 @@ async function createProject(ctx) {
2406
2461
  }
2407
2462
  const ide = await select2({
2408
2463
  message: "Select IDE:",
2409
- choices: IDE_CHOICES2
2464
+ choices: IDE_CHOICES
2410
2465
  });
2411
2466
  const homepage = await input5({
2412
2467
  message: "Project homepage URL (optional):",
@@ -2483,7 +2538,7 @@ async function editProject(ctx) {
2483
2538
  }
2484
2539
  const ide = await select2({
2485
2540
  message: "Select IDE:",
2486
- choices: IDE_CHOICES2,
2541
+ choices: IDE_CHOICES,
2487
2542
  default: project.ide || "vscode"
2488
2543
  });
2489
2544
  const homepage = await input5({
@@ -2639,7 +2694,7 @@ async function addProject(pathArg, options, ctx) {
2639
2694
  return;
2640
2695
  }
2641
2696
  }
2642
- const ide = options.ide || discovery.detectedIde || "vscode";
2697
+ const ide = options.ide || discovery.detectedIde || "code";
2643
2698
  log.debug(`IDE: ${ide}`);
2644
2699
  let relativePath = targetPath;
2645
2700
  if (defaults?.base) {
@@ -2667,6 +2722,9 @@ async function addProject(pathArg, options, ctx) {
2667
2722
  projectConfig.events.npm = scripts.dev ? "dev" : "start";
2668
2723
  }
2669
2724
  }
2725
+ if (discovery.hasClaude) {
2726
+ projectConfig.events.claude = true;
2727
+ }
2670
2728
  config.setProject(projectName, projectConfig);
2671
2729
  log.info(`Added project '${projectName}'`);
2672
2730
  log.info(` Path: ${relativePath}`);
@@ -2681,6 +2739,7 @@ function discoverProject(targetPath, log) {
2681
2739
  name: dirName,
2682
2740
  isNode: false,
2683
2741
  isBun: false,
2742
+ hasClaude: false,
2684
2743
  detectedIde: null,
2685
2744
  packageJson: null
2686
2745
  };
@@ -2706,14 +2765,23 @@ function discoverProject(targetPath, log) {
2706
2765
  log.debug("Detected Bun project (bun.lockb found)");
2707
2766
  }
2708
2767
  const vscodeDir = resolve(targetPath, ".vscode");
2768
+ const cursorDir = resolve(targetPath, ".cursor");
2709
2769
  const ideaDir = resolve(targetPath, ".idea");
2710
- if (existsSync(vscodeDir)) {
2711
- discovery.detectedIde = "vscode";
2770
+ if (existsSync(cursorDir)) {
2771
+ discovery.detectedIde = "cursor";
2772
+ log.debug("Detected Cursor (.cursor directory found)");
2773
+ } else if (existsSync(vscodeDir)) {
2774
+ discovery.detectedIde = "code";
2712
2775
  log.debug("Detected VS Code (.vscode directory found)");
2713
2776
  } else if (existsSync(ideaDir)) {
2714
2777
  discovery.detectedIde = "idea";
2715
2778
  log.debug("Detected IntelliJ IDEA (.idea directory found)");
2716
2779
  }
2780
+ const claudeMdPath = resolve(targetPath, "CLAUDE.md");
2781
+ if (existsSync(claudeMdPath)) {
2782
+ discovery.hasClaude = true;
2783
+ log.debug("Detected Claude Code project (CLAUDE.md found)");
2784
+ }
2717
2785
  return discovery;
2718
2786
  }
2719
2787
 
@@ -2745,11 +2813,12 @@ function createCli() {
2745
2813
  config.set("pkg", packageJson);
2746
2814
  EnvironmentRecognizer.configure(config, log);
2747
2815
  const completion = setupCompletion(config);
2748
- 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", (thisCommand) => {
2816
+ 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) => {
2749
2817
  const opts = thisCommand.opts();
2750
2818
  if (opts.debug) {
2751
2819
  log.setLogLevel("debug");
2752
2820
  }
2821
+ await EventRegistry.initialize();
2753
2822
  }).action(
2754
2823
  async (project, options) => {
2755
2824
  if (options.debug) {
@@ -2766,10 +2835,8 @@ function createCli() {
2766
2835
  return;
2767
2836
  }
2768
2837
  if (project) {
2769
- const args = ["open", project];
2770
- if (options.shell) args.push("--shell");
2771
- if (options.debug) args.push("--debug");
2772
- await program2.parseAsync(["node", "workon", ...args]);
2838
+ const { runOpen: runOpen2 } = await Promise.resolve().then(() => (init_open(), open_exports));
2839
+ await runOpen2(project, { debug: options.debug, shell: options.shell }, { config, log });
2773
2840
  return;
2774
2841
  }
2775
2842
  const environment = await EnvironmentRecognizer.recognize(File7.cwd());