workon 2.1.3 → 3.0.0

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.
Files changed (66) hide show
  1. package/README.md +19 -4
  2. package/bin/workon +1 -11
  3. package/dist/cli.js +2227 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.cjs +1216 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +280 -0
  8. package/dist/index.d.ts +280 -0
  9. package/dist/index.js +1173 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +68 -21
  12. package/.claude/settings.local.json +0 -11
  13. package/.cursorindexingignore +0 -3
  14. package/.history/.gitignore_20250806202718 +0 -30
  15. package/.history/.gitignore_20250806231444 +0 -32
  16. package/.history/.gitignore_20250806231450 +0 -32
  17. package/.history/lib/tmux_20250806233103.js +0 -109
  18. package/.history/lib/tmux_20250806233219.js +0 -109
  19. package/.history/lib/tmux_20250806233223.js +0 -109
  20. package/.history/lib/tmux_20250806233230.js +0 -109
  21. package/.history/lib/tmux_20250806233231.js +0 -109
  22. package/.history/lib/tmux_20250807120751.js +0 -190
  23. package/.history/lib/tmux_20250807120757.js +0 -190
  24. package/.history/lib/tmux_20250807120802.js +0 -190
  25. package/.history/lib/tmux_20250807120808.js +0 -190
  26. package/.history/package_20250807114243.json +0 -43
  27. package/.history/package_20250807114257.json +0 -43
  28. package/.history/package_20250807114404.json +0 -43
  29. package/.history/package_20250807114409.json +0 -43
  30. package/.history/package_20250807114510.json +0 -43
  31. package/.history/package_20250807114637.json +0 -43
  32. package/.vscode/launch.json +0 -20
  33. package/.vscode/terminals.json +0 -11
  34. package/CHANGELOG.md +0 -110
  35. package/CLAUDE.md +0 -100
  36. package/cli/base.js +0 -16
  37. package/cli/config/index.js +0 -19
  38. package/cli/config/list.js +0 -26
  39. package/cli/config/set.js +0 -19
  40. package/cli/config/unset.js +0 -26
  41. package/cli/index.js +0 -184
  42. package/cli/interactive.js +0 -290
  43. package/cli/manage.js +0 -413
  44. package/cli/open.js +0 -414
  45. package/commands/base.js +0 -105
  46. package/commands/core/cwd/index.js +0 -86
  47. package/commands/core/ide/index.js +0 -84
  48. package/commands/core/web/index.js +0 -109
  49. package/commands/extensions/claude/index.js +0 -211
  50. package/commands/extensions/docker/index.js +0 -167
  51. package/commands/extensions/npm/index.js +0 -208
  52. package/commands/registry.js +0 -196
  53. package/demo-colon-syntax.js +0 -57
  54. package/docs/adr/001-command-centric-architecture.md +0 -304
  55. package/docs/adr/002-positional-command-arguments.md +0 -402
  56. package/docs/ideas.md +0 -93
  57. package/lib/config.js +0 -51
  58. package/lib/environment/base.js +0 -12
  59. package/lib/environment/index.js +0 -108
  60. package/lib/environment/project.js +0 -26
  61. package/lib/project.js +0 -68
  62. package/lib/tmux.js +0 -223
  63. package/lib/validation.js +0 -120
  64. package/test-architecture.js +0 -145
  65. package/test-colon-syntax.js +0 -85
  66. package/test-registry.js +0 -57
package/dist/index.cjs ADDED
@@ -0,0 +1,1216 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ BaseEnvironment: () => BaseEnvironment,
34
+ Config: () => Config,
35
+ EnvironmentRecognizer: () => EnvironmentRecognizer,
36
+ EventRegistry: () => EventRegistry,
37
+ Project: () => Project,
38
+ ProjectEnvironment: () => ProjectEnvironment,
39
+ TmuxManager: () => TmuxManager
40
+ });
41
+ module.exports = __toCommonJS(src_exports);
42
+
43
+ // src/lib/config.ts
44
+ var import_conf = __toESM(require("conf"), 1);
45
+ var TRANSIENT_PROPS = ["pkg", "work"];
46
+ var Config = class {
47
+ _transient = {};
48
+ _store;
49
+ constructor() {
50
+ this._store = new import_conf.default({
51
+ projectName: "workon"
52
+ });
53
+ }
54
+ get(key, defaultValue) {
55
+ const rootKey = key.split(".")[0];
56
+ if (TRANSIENT_PROPS.includes(rootKey)) {
57
+ return this._transient[key] ?? defaultValue;
58
+ }
59
+ return this._store.get(key, defaultValue);
60
+ }
61
+ set(key, value) {
62
+ const rootKey = key.split(".")[0];
63
+ if (TRANSIENT_PROPS.includes(rootKey)) {
64
+ this._transient[key] = value;
65
+ } else {
66
+ if (value === void 0) {
67
+ this._store.set(key, value);
68
+ } else {
69
+ this._store.set(key, value);
70
+ }
71
+ }
72
+ }
73
+ has(key) {
74
+ const rootKey = key.split(".")[0];
75
+ if (TRANSIENT_PROPS.includes(rootKey)) {
76
+ return Object.prototype.hasOwnProperty.call(this._transient, key);
77
+ }
78
+ return this._store.has(key);
79
+ }
80
+ delete(key) {
81
+ const rootKey = key.split(".")[0];
82
+ if (TRANSIENT_PROPS.includes(rootKey)) {
83
+ delete this._transient[key];
84
+ } else {
85
+ this._store.delete(key);
86
+ }
87
+ }
88
+ getProjects() {
89
+ return this.get("projects") ?? {};
90
+ }
91
+ getProject(name) {
92
+ const projects = this.getProjects();
93
+ return projects[name];
94
+ }
95
+ setProject(name, config) {
96
+ const projects = this.getProjects();
97
+ projects[name] = config;
98
+ this.set("projects", projects);
99
+ }
100
+ deleteProject(name) {
101
+ const projects = this.getProjects();
102
+ delete projects[name];
103
+ this.set("projects", projects);
104
+ }
105
+ getDefaults() {
106
+ return this.get("project_defaults");
107
+ }
108
+ setDefaults(defaults) {
109
+ this.set("project_defaults", defaults);
110
+ }
111
+ get path() {
112
+ return this._store.path;
113
+ }
114
+ get store() {
115
+ return this._store.store;
116
+ }
117
+ };
118
+
119
+ // src/lib/project.ts
120
+ var import_phylo = __toESM(require("phylo"), 1);
121
+ var import_deep_assign = __toESM(require("deep-assign"), 1);
122
+ var Project = class {
123
+ name;
124
+ _base;
125
+ _path;
126
+ _ide;
127
+ _events = {};
128
+ _branch;
129
+ _homepage;
130
+ _defaults;
131
+ _initialCfg;
132
+ constructor(name, cfg, defaults) {
133
+ this._defaults = defaults ?? { base: "" };
134
+ this._initialCfg = { path: name, events: {}, ...cfg };
135
+ this.name = cfg?.name ?? name;
136
+ const merged = (0, import_deep_assign.default)({}, this._defaults, this._initialCfg);
137
+ if (merged.base) {
138
+ this.base = merged.base;
139
+ }
140
+ if (merged.path) {
141
+ this.path = merged.path;
142
+ }
143
+ if (merged.ide) {
144
+ this._ide = merged.ide;
145
+ }
146
+ if (merged.events) {
147
+ this._events = merged.events;
148
+ }
149
+ if (merged.branch) {
150
+ this._branch = merged.branch;
151
+ }
152
+ if (merged.homepage) {
153
+ this._homepage = merged.homepage;
154
+ }
155
+ }
156
+ set base(path) {
157
+ this._base = import_phylo.default.from(path).absolutify();
158
+ }
159
+ get base() {
160
+ return this._base;
161
+ }
162
+ set ide(cmd) {
163
+ this._ide = cmd;
164
+ }
165
+ get ide() {
166
+ return this._ide;
167
+ }
168
+ set events(eventCfg) {
169
+ this._events = eventCfg;
170
+ }
171
+ get events() {
172
+ return this._events;
173
+ }
174
+ set path(path) {
175
+ if (this._base) {
176
+ this._path = this._base.join(path);
177
+ } else {
178
+ this._path = import_phylo.default.from(path);
179
+ }
180
+ this._path = this._path.absolutify();
181
+ }
182
+ get path() {
183
+ if (!this._path) {
184
+ throw new Error("Project path not set");
185
+ }
186
+ return this._path;
187
+ }
188
+ set branch(branch) {
189
+ this._branch = branch;
190
+ }
191
+ get branch() {
192
+ return this._branch;
193
+ }
194
+ set homepage(url) {
195
+ this._homepage = url;
196
+ }
197
+ get homepage() {
198
+ return this._homepage;
199
+ }
200
+ static $isProject = true;
201
+ $isProject = true;
202
+ };
203
+
204
+ // src/lib/tmux.ts
205
+ var import_child_process = require("child_process");
206
+ var import_util = require("util");
207
+ var exec = (0, import_util.promisify)(import_child_process.exec);
208
+ var TmuxManager = class {
209
+ sessionPrefix = "workon-";
210
+ async isTmuxAvailable() {
211
+ try {
212
+ await exec("which tmux");
213
+ return true;
214
+ } catch {
215
+ return false;
216
+ }
217
+ }
218
+ async sessionExists(sessionName) {
219
+ try {
220
+ await exec(`tmux has-session -t "${sessionName}"`);
221
+ return true;
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
226
+ getSessionName(projectName) {
227
+ return `${this.sessionPrefix}${projectName}`;
228
+ }
229
+ async killSession(sessionName) {
230
+ try {
231
+ await exec(`tmux kill-session -t "${sessionName}"`);
232
+ return true;
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+ async createSplitSession(projectName, projectPath, claudeArgs = []) {
238
+ const sessionName = this.getSessionName(projectName);
239
+ if (await this.sessionExists(sessionName)) {
240
+ await this.killSession(sessionName);
241
+ }
242
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
243
+ await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
244
+ await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
245
+ await exec(`tmux select-pane -t "${sessionName}:0.0"`);
246
+ return sessionName;
247
+ }
248
+ async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
249
+ const sessionName = this.getSessionName(projectName);
250
+ if (await this.sessionExists(sessionName)) {
251
+ await this.killSession(sessionName);
252
+ }
253
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
254
+ await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
255
+ await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
256
+ await exec(`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`);
257
+ await exec(`tmux set-option -t "${sessionName}:0.2" remain-on-exit on`);
258
+ await exec(`tmux resize-pane -t "${sessionName}:0.2" -y 10`);
259
+ await exec(`tmux select-pane -t "${sessionName}:0.0"`);
260
+ return sessionName;
261
+ }
262
+ async createTwoPaneNpmSession(projectName, projectPath, npmCommand = "npm run dev") {
263
+ const sessionName = this.getSessionName(projectName);
264
+ if (await this.sessionExists(sessionName)) {
265
+ await this.killSession(sessionName);
266
+ }
267
+ await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`);
268
+ await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`);
269
+ await exec(`tmux set-option -t "${sessionName}:0.1" remain-on-exit on`);
270
+ await exec(`tmux select-pane -t "${sessionName}:0.0"`);
271
+ return sessionName;
272
+ }
273
+ async attachToSession(sessionName) {
274
+ if (process.env.TMUX) {
275
+ await exec(`tmux switch-client -t "${sessionName}"`);
276
+ } else {
277
+ const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || !!process.env.ITERM_SESSION_ID;
278
+ const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
279
+ if (useiTermIntegration) {
280
+ (0, import_child_process.spawn)("tmux", ["-CC", "attach-session", "-t", sessionName], {
281
+ stdio: "inherit",
282
+ detached: true
283
+ });
284
+ } else {
285
+ (0, import_child_process.spawn)("tmux", ["attach-session", "-t", sessionName], {
286
+ stdio: "inherit",
287
+ detached: true
288
+ });
289
+ }
290
+ }
291
+ }
292
+ buildShellCommands(projectName, projectPath, claudeArgs = []) {
293
+ const sessionName = this.getSessionName(projectName);
294
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
295
+ return [
296
+ `# Create tmux split session for ${projectName}`,
297
+ `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
298
+ `tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
299
+ `tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
300
+ `tmux select-pane -t "${sessionName}:0.0"`,
301
+ this.getAttachCommand(sessionName)
302
+ ];
303
+ }
304
+ buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = "npm run dev") {
305
+ const sessionName = this.getSessionName(projectName);
306
+ const claudeCommand = claudeArgs.length > 0 ? `claude ${claudeArgs.join(" ")}` : "claude";
307
+ return [
308
+ `# Create tmux three-pane session for ${projectName}`,
309
+ `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
310
+ `tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
311
+ `tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
312
+ `tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`,
313
+ `tmux set-option -t "${sessionName}:0.2" remain-on-exit on`,
314
+ `tmux resize-pane -t "${sessionName}:0.2" -y 10`,
315
+ `tmux select-pane -t "${sessionName}:0.0"`,
316
+ this.getAttachCommand(sessionName)
317
+ ];
318
+ }
319
+ buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = "npm run dev") {
320
+ const sessionName = this.getSessionName(projectName);
321
+ return [
322
+ `# Create tmux two-pane session with npm for ${projectName}`,
323
+ `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
324
+ `tmux new-session -d -s "${sessionName}" -c "${projectPath}"`,
325
+ `tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`,
326
+ `tmux set-option -t "${sessionName}:0.1" remain-on-exit on`,
327
+ `tmux select-pane -t "${sessionName}:0.0"`,
328
+ this.getAttachCommand(sessionName)
329
+ ];
330
+ }
331
+ getAttachCommand(sessionName) {
332
+ if (process.env.TMUX) {
333
+ return `tmux switch-client -t "${sessionName}"`;
334
+ }
335
+ const isITerm = process.env.TERM_PROGRAM === "iTerm.app" || process.env.LC_TERMINAL === "iTerm2" || process.env.ITERM_SESSION_ID;
336
+ const useiTermIntegration = isITerm && !process.env.TMUX_CC_NOT_SUPPORTED;
337
+ if (useiTermIntegration) {
338
+ return `tmux -CC attach-session -t "${sessionName}"`;
339
+ }
340
+ return `tmux attach-session -t "${sessionName}"`;
341
+ }
342
+ async listWorkonSessions() {
343
+ try {
344
+ const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
345
+ return stdout.trim().split("\n").filter((session) => session.startsWith(this.sessionPrefix)).map((session) => session.replace(this.sessionPrefix, ""));
346
+ } catch {
347
+ return [];
348
+ }
349
+ }
350
+ };
351
+
352
+ // src/lib/environment.ts
353
+ var import_phylo2 = __toESM(require("phylo"), 1);
354
+ var import_simple_git = require("simple-git");
355
+ var BaseEnvironment = class {
356
+ $isProjectEnvironment = false;
357
+ };
358
+ var ProjectEnvironment = class _ProjectEnvironment {
359
+ $isProjectEnvironment = true;
360
+ project;
361
+ constructor(projectCfg) {
362
+ this.project = new Project(projectCfg.name, projectCfg);
363
+ }
364
+ static load(cfg, defaults) {
365
+ const project = new Project(cfg.name, cfg, defaults);
366
+ return new _ProjectEnvironment({ ...cfg, name: project.name });
367
+ }
368
+ };
369
+ var EnvironmentRecognizer = class {
370
+ static config;
371
+ static log;
372
+ static projects = [];
373
+ static configured = false;
374
+ static configure(config, log) {
375
+ if (this.configured) {
376
+ return;
377
+ }
378
+ this.config = config;
379
+ this.log = log;
380
+ this.configured = true;
381
+ }
382
+ static async recognize(dir) {
383
+ this.ensureConfigured();
384
+ const theDir = import_phylo2.default.from(dir).canonicalize();
385
+ this.log.debug("Directory to recognize is: " + theDir.canonicalPath());
386
+ const allProjects = this.getAllProjects();
387
+ const matching = allProjects.filter((p) => p.path.canonicalPath() === theDir.path);
388
+ if (matching.length === 0) {
389
+ return new BaseEnvironment();
390
+ }
391
+ this.log.debug(`Found ${matching.length} matching projects`);
392
+ const base = matching.find((p) => !p.name.includes("#")) ?? matching[0];
393
+ this.log.debug("Base project is: " + base.name);
394
+ const gitDir = base.path.up(".git");
395
+ if (gitDir) {
396
+ try {
397
+ const git = (0, import_simple_git.simpleGit)(gitDir.path);
398
+ const branchSummary = await git.branchLocal();
399
+ base.branch = branchSummary.current;
400
+ } catch {
401
+ }
402
+ }
403
+ return this.getProjectEnvironment(base, matching);
404
+ }
405
+ static getAllProjects(refresh = false) {
406
+ if (this.projects.length > 0 && !refresh) {
407
+ return this.projects;
408
+ }
409
+ const defaults = this.config.getDefaults();
410
+ if (!defaults?.base) {
411
+ this.projects = [];
412
+ return this.projects;
413
+ }
414
+ const baseDir = import_phylo2.default.from(defaults.base);
415
+ const projectsMap = this.config.getProjects();
416
+ this.projects = Object.entries(projectsMap).map(([name, project]) => ({
417
+ ...project,
418
+ name,
419
+ path: baseDir.join(project.path)
420
+ }));
421
+ return this.projects;
422
+ }
423
+ static getProjectEnvironment(base, _matching) {
424
+ const exactName = `${base.name}#${base.branch}`;
425
+ const exactProj = this.projects.find((p) => p.name === exactName);
426
+ const toProjectConfig = (p) => ({
427
+ name: p.name,
428
+ path: p.path.path,
429
+ // Convert PhyloFile to string path
430
+ ide: p.ide,
431
+ homepage: p.homepage,
432
+ events: p.events,
433
+ branch: p.branch,
434
+ exactName
435
+ });
436
+ if (exactProj) {
437
+ return new ProjectEnvironment({ ...toProjectConfig(exactProj), branch: base.branch });
438
+ }
439
+ return new ProjectEnvironment(toProjectConfig(base));
440
+ }
441
+ static ensureConfigured() {
442
+ if (!this.configured) {
443
+ this.config = new Config();
444
+ this.log = {
445
+ debug: () => {
446
+ },
447
+ info: () => {
448
+ },
449
+ log: () => {
450
+ },
451
+ warn: () => {
452
+ },
453
+ error: () => {
454
+ },
455
+ setLogLevel: () => {
456
+ }
457
+ };
458
+ this.configured = true;
459
+ }
460
+ }
461
+ };
462
+
463
+ // src/events/core/cwd.ts
464
+ var import_child_process2 = require("child_process");
465
+ var CwdEvent = class {
466
+ static get metadata() {
467
+ return {
468
+ name: "cwd",
469
+ displayName: "Change directory (cwd)",
470
+ description: "Change current working directory to project path",
471
+ category: "core",
472
+ requiresTmux: false,
473
+ dependencies: []
474
+ };
475
+ }
476
+ static get validation() {
477
+ return {
478
+ validateConfig(config) {
479
+ if (typeof config === "boolean" || config === "true" || config === "false") {
480
+ return true;
481
+ }
482
+ return "cwd config must be a boolean (true/false)";
483
+ }
484
+ };
485
+ }
486
+ static get configuration() {
487
+ return {
488
+ async configureInteractive() {
489
+ return true;
490
+ },
491
+ getDefaultConfig() {
492
+ return true;
493
+ }
494
+ };
495
+ }
496
+ static get processing() {
497
+ return {
498
+ async processEvent(context) {
499
+ const { project, isShellMode, shellCommands } = context;
500
+ const projectPath = project.path.path;
501
+ if (isShellMode) {
502
+ shellCommands.push(`cd "${projectPath}"`);
503
+ } else {
504
+ const shell = process.env.SHELL || "/bin/bash";
505
+ (0, import_child_process2.spawn)(shell, [], {
506
+ cwd: projectPath,
507
+ stdio: "inherit"
508
+ });
509
+ }
510
+ },
511
+ generateShellCommand(context) {
512
+ const projectPath = context.project.path.path;
513
+ return [`cd "${projectPath}"`];
514
+ }
515
+ };
516
+ }
517
+ static get tmux() {
518
+ return null;
519
+ }
520
+ static get help() {
521
+ return {
522
+ usage: "cwd: true | false",
523
+ description: "Change the current working directory to the project path",
524
+ examples: [
525
+ { config: true, description: "Enable directory change" },
526
+ { config: false, description: "Disable directory change" }
527
+ ]
528
+ };
529
+ }
530
+ };
531
+
532
+ // src/events/core/ide.ts
533
+ var import_child_process3 = require("child_process");
534
+ var IdeEvent = class {
535
+ static get metadata() {
536
+ return {
537
+ name: "ide",
538
+ displayName: "Open in IDE",
539
+ description: "Open project in configured IDE/editor",
540
+ category: "core",
541
+ requiresTmux: false,
542
+ dependencies: []
543
+ };
544
+ }
545
+ static get validation() {
546
+ return {
547
+ validateConfig(config) {
548
+ if (typeof config === "boolean" || config === "true" || config === "false") {
549
+ return true;
550
+ }
551
+ return "ide config must be a boolean (true/false)";
552
+ }
553
+ };
554
+ }
555
+ static get configuration() {
556
+ return {
557
+ async configureInteractive() {
558
+ return true;
559
+ },
560
+ getDefaultConfig() {
561
+ return true;
562
+ }
563
+ };
564
+ }
565
+ static get processing() {
566
+ return {
567
+ async processEvent(context) {
568
+ const { project, isShellMode, shellCommands } = context;
569
+ const projectPath = project.path.path;
570
+ const ide = project.ide || "code";
571
+ if (isShellMode) {
572
+ shellCommands.push(`${ide} "${projectPath}" &`);
573
+ } else {
574
+ (0, import_child_process3.spawn)(ide, [projectPath], {
575
+ detached: true,
576
+ stdio: "ignore"
577
+ }).unref();
578
+ }
579
+ },
580
+ generateShellCommand(context) {
581
+ const projectPath = context.project.path.path;
582
+ const ide = context.project.ide || "code";
583
+ return [`${ide} "${projectPath}" &`];
584
+ }
585
+ };
586
+ }
587
+ static get tmux() {
588
+ return null;
589
+ }
590
+ static get help() {
591
+ return {
592
+ usage: "ide: true | false",
593
+ description: "Open the project in the configured IDE",
594
+ examples: [
595
+ { config: true, description: "Enable IDE opening" },
596
+ { config: false, description: "Disable IDE opening" }
597
+ ]
598
+ };
599
+ }
600
+ };
601
+
602
+ // src/events/core/web.ts
603
+ var import_child_process4 = require("child_process");
604
+ var import_os = require("os");
605
+ var WebEvent = class _WebEvent {
606
+ static get metadata() {
607
+ return {
608
+ name: "web",
609
+ displayName: "Open homepage in browser",
610
+ description: "Open project homepage in web browser",
611
+ category: "core",
612
+ requiresTmux: false,
613
+ dependencies: []
614
+ };
615
+ }
616
+ static get validation() {
617
+ return {
618
+ validateConfig(config) {
619
+ if (typeof config === "boolean" || config === "true" || config === "false") {
620
+ return true;
621
+ }
622
+ return "web config must be a boolean (true/false)";
623
+ }
624
+ };
625
+ }
626
+ static get configuration() {
627
+ return {
628
+ async configureInteractive() {
629
+ return true;
630
+ },
631
+ getDefaultConfig() {
632
+ return true;
633
+ }
634
+ };
635
+ }
636
+ static getOpenCommand() {
637
+ const os = (0, import_os.platform)();
638
+ switch (os) {
639
+ case "darwin":
640
+ return "open";
641
+ case "win32":
642
+ return "start";
643
+ default:
644
+ return "xdg-open";
645
+ }
646
+ }
647
+ static get processing() {
648
+ return {
649
+ async processEvent(context) {
650
+ const { project, isShellMode, shellCommands } = context;
651
+ const homepage = project.homepage;
652
+ if (!homepage) {
653
+ console.warn("No homepage configured for project");
654
+ return;
655
+ }
656
+ const openCmd = _WebEvent.getOpenCommand();
657
+ if (isShellMode) {
658
+ shellCommands.push(`${openCmd} "${homepage}" &`);
659
+ } else {
660
+ (0, import_child_process4.spawn)(openCmd, [homepage], {
661
+ detached: true,
662
+ stdio: "ignore"
663
+ }).unref();
664
+ }
665
+ },
666
+ generateShellCommand(context) {
667
+ const homepage = context.project.homepage;
668
+ if (!homepage) return [];
669
+ const openCmd = _WebEvent.getOpenCommand();
670
+ return [`${openCmd} "${homepage}" &`];
671
+ }
672
+ };
673
+ }
674
+ static get tmux() {
675
+ return null;
676
+ }
677
+ static get help() {
678
+ return {
679
+ usage: "web: true | false",
680
+ description: "Open the project homepage in the default browser",
681
+ examples: [
682
+ { config: true, description: "Enable browser opening" },
683
+ { config: false, description: "Disable browser opening" }
684
+ ]
685
+ };
686
+ }
687
+ };
688
+
689
+ // src/events/extensions/claude.ts
690
+ var import_child_process5 = require("child_process");
691
+ var import_prompts = require("@inquirer/prompts");
692
+ var ClaudeEvent = class _ClaudeEvent {
693
+ static get metadata() {
694
+ return {
695
+ name: "claude",
696
+ displayName: "Launch Claude Code",
697
+ description: "Launch Claude Code with optional flags and configuration",
698
+ category: "development",
699
+ requiresTmux: true,
700
+ dependencies: ["claude"]
701
+ };
702
+ }
703
+ static get validation() {
704
+ return {
705
+ validateConfig(config) {
706
+ if (typeof config === "boolean" || config === "true" || config === "false") {
707
+ return true;
708
+ }
709
+ if (typeof config === "object" && config !== null) {
710
+ const cfg = config;
711
+ if (cfg.flags !== void 0) {
712
+ if (!Array.isArray(cfg.flags)) {
713
+ return "claude.flags must be an array of strings";
714
+ }
715
+ for (const flag of cfg.flags) {
716
+ if (typeof flag !== "string") {
717
+ return "claude.flags must contain only strings";
718
+ }
719
+ if (!flag.startsWith("-")) {
720
+ return `Invalid flag "${flag}": flags must start with - or --`;
721
+ }
722
+ }
723
+ }
724
+ if (cfg.split_terminal !== void 0 && typeof cfg.split_terminal !== "boolean") {
725
+ return "claude.split_terminal must be a boolean";
726
+ }
727
+ return true;
728
+ }
729
+ return "claude config must be a boolean or object with flags/split_terminal";
730
+ }
731
+ };
732
+ }
733
+ static get configuration() {
734
+ return {
735
+ async configureInteractive() {
736
+ const useAdvanced = await (0, import_prompts.confirm)({
737
+ message: "Configure advanced Claude options?",
738
+ default: false
739
+ });
740
+ if (!useAdvanced) {
741
+ return true;
742
+ }
743
+ const flagsInput = await (0, import_prompts.input)({
744
+ message: "Enter Claude flags (comma-separated, e.g., --resume, --debug):",
745
+ default: ""
746
+ });
747
+ const flags = flagsInput.split(",").map((f) => f.trim()).filter((f) => f.length > 0 && f.startsWith("-"));
748
+ const splitTerminal = await (0, import_prompts.confirm)({
749
+ message: "Use split terminal layout (Claude + shell)?",
750
+ default: true
751
+ });
752
+ if (flags.length === 0 && !splitTerminal) {
753
+ return true;
754
+ }
755
+ const config = {};
756
+ if (flags.length > 0) config.flags = flags;
757
+ if (splitTerminal) config.split_terminal = splitTerminal;
758
+ return config;
759
+ },
760
+ getDefaultConfig() {
761
+ return true;
762
+ }
763
+ };
764
+ }
765
+ static getClaudeCommand(config) {
766
+ if (typeof config === "boolean" || config === void 0) {
767
+ return "claude";
768
+ }
769
+ const flags = config.flags || [];
770
+ return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
771
+ }
772
+ static get processing() {
773
+ return {
774
+ async processEvent(context) {
775
+ const { project, isShellMode, shellCommands } = context;
776
+ const claudeConfig = project.events.claude;
777
+ const claudeCommand = _ClaudeEvent.getClaudeCommand(claudeConfig);
778
+ if (isShellMode) {
779
+ shellCommands.push(claudeCommand);
780
+ } else {
781
+ const args = claudeCommand.split(" ").slice(1);
782
+ (0, import_child_process5.spawn)("claude", args, {
783
+ cwd: project.path.path,
784
+ stdio: "inherit"
785
+ });
786
+ }
787
+ },
788
+ generateShellCommand(context) {
789
+ const claudeConfig = context.project.events.claude;
790
+ return [_ClaudeEvent.getClaudeCommand(claudeConfig)];
791
+ }
792
+ };
793
+ }
794
+ static get tmux() {
795
+ return {
796
+ getLayoutPriority() {
797
+ return 100;
798
+ },
799
+ contributeToLayout(enabledCommands) {
800
+ if (enabledCommands.includes("npm")) {
801
+ return "three-pane";
802
+ }
803
+ return "split";
804
+ }
805
+ };
806
+ }
807
+ static get help() {
808
+ return {
809
+ usage: "claude: true | { flags: string[], split_terminal: boolean }",
810
+ description: "Launch Claude Code in the project directory",
811
+ examples: [
812
+ { config: true, description: "Launch Claude with defaults" },
813
+ { config: { flags: ["--resume"] }, description: "Resume previous session" },
814
+ {
815
+ config: { flags: ["--model", "opus"], split_terminal: true },
816
+ description: "Use Opus model with split terminal"
817
+ }
818
+ ]
819
+ };
820
+ }
821
+ };
822
+
823
+ // src/events/extensions/docker.ts
824
+ var import_child_process6 = require("child_process");
825
+ var import_prompts2 = require("@inquirer/prompts");
826
+ var DockerEvent = class _DockerEvent {
827
+ static get metadata() {
828
+ return {
829
+ name: "docker",
830
+ displayName: "Docker container management",
831
+ description: "Start/stop Docker containers for the project",
832
+ category: "development",
833
+ requiresTmux: false,
834
+ dependencies: ["docker"]
835
+ };
836
+ }
837
+ static get validation() {
838
+ return {
839
+ validateConfig(config) {
840
+ if (typeof config === "boolean" || config === "true" || config === "false") {
841
+ return true;
842
+ }
843
+ if (typeof config === "string") {
844
+ return true;
845
+ }
846
+ if (typeof config === "object" && config !== null) {
847
+ const cfg = config;
848
+ if (cfg.compose_file !== void 0 && typeof cfg.compose_file !== "string") {
849
+ return "docker.compose_file must be a string";
850
+ }
851
+ if (cfg.services !== void 0) {
852
+ if (!Array.isArray(cfg.services)) {
853
+ return "docker.services must be an array";
854
+ }
855
+ for (const service of cfg.services) {
856
+ if (typeof service !== "string") {
857
+ return "docker.services must contain only strings";
858
+ }
859
+ }
860
+ }
861
+ return true;
862
+ }
863
+ return "docker config must be a boolean, string (compose file), or object";
864
+ }
865
+ };
866
+ }
867
+ static get configuration() {
868
+ return {
869
+ async configureInteractive() {
870
+ const composeFile = await (0, import_prompts2.input)({
871
+ message: "Enter docker-compose file path:",
872
+ default: "docker-compose.yml"
873
+ });
874
+ const servicesInput = await (0, import_prompts2.input)({
875
+ message: "Enter services to start (comma-separated, leave empty for all):",
876
+ default: ""
877
+ });
878
+ const services = servicesInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
879
+ if (composeFile === "docker-compose.yml" && services.length === 0) {
880
+ return { compose_file: "docker-compose.yml" };
881
+ }
882
+ if (services.length === 0) {
883
+ return composeFile;
884
+ }
885
+ return {
886
+ compose_file: composeFile,
887
+ services
888
+ };
889
+ },
890
+ getDefaultConfig() {
891
+ return { compose_file: "docker-compose.yml" };
892
+ }
893
+ };
894
+ }
895
+ static getDockerCommand(config) {
896
+ if (typeof config === "boolean" || config === void 0) {
897
+ return "docker-compose up -d";
898
+ }
899
+ if (typeof config === "string") {
900
+ return `docker-compose -f ${config} up -d`;
901
+ }
902
+ const composeFile = config.compose_file || "docker-compose.yml";
903
+ const services = config.services?.join(" ") || "";
904
+ return `docker-compose -f ${composeFile} up -d ${services}`.trim();
905
+ }
906
+ static get processing() {
907
+ return {
908
+ async processEvent(context) {
909
+ const { project, isShellMode, shellCommands } = context;
910
+ const dockerConfig = project.events.docker;
911
+ const dockerCommand = _DockerEvent.getDockerCommand(
912
+ dockerConfig
913
+ );
914
+ if (isShellMode) {
915
+ shellCommands.push(dockerCommand);
916
+ } else {
917
+ const [cmd, ...args] = dockerCommand.split(" ");
918
+ (0, import_child_process6.spawn)(cmd, args, {
919
+ cwd: project.path.path,
920
+ stdio: "inherit"
921
+ });
922
+ }
923
+ },
924
+ generateShellCommand(context) {
925
+ const dockerConfig = context.project.events.docker;
926
+ return [_DockerEvent.getDockerCommand(dockerConfig)];
927
+ }
928
+ };
929
+ }
930
+ static get tmux() {
931
+ return null;
932
+ }
933
+ static get help() {
934
+ return {
935
+ usage: 'docker: true | "compose-file.yml" | { compose_file: string, services?: string[] }',
936
+ description: "Start Docker containers for the project",
937
+ examples: [
938
+ { config: true, description: "Use default docker-compose.yml" },
939
+ { config: "docker-compose.dev.yml", description: "Use custom compose file" },
940
+ {
941
+ config: { compose_file: "docker-compose.yml", services: ["web", "db"] },
942
+ description: "Start specific services"
943
+ }
944
+ ]
945
+ };
946
+ }
947
+ };
948
+
949
+ // src/events/extensions/npm.ts
950
+ var import_child_process7 = require("child_process");
951
+ var import_prompts3 = require("@inquirer/prompts");
952
+ var NpmEvent = class _NpmEvent {
953
+ static get metadata() {
954
+ return {
955
+ name: "npm",
956
+ displayName: "Run NPM command",
957
+ description: "Execute NPM scripts in project directory",
958
+ category: "development",
959
+ requiresTmux: true,
960
+ dependencies: ["npm"]
961
+ };
962
+ }
963
+ static get validation() {
964
+ return {
965
+ validateConfig(config) {
966
+ if (typeof config === "boolean" || config === "true" || config === "false") {
967
+ return true;
968
+ }
969
+ if (typeof config === "string") {
970
+ if (config.trim().length === 0) {
971
+ return "npm script name cannot be empty";
972
+ }
973
+ return true;
974
+ }
975
+ if (typeof config === "object" && config !== null) {
976
+ const cfg = config;
977
+ if (typeof cfg.command !== "string" || cfg.command.trim().length === 0) {
978
+ return "npm.command must be a non-empty string";
979
+ }
980
+ if (cfg.watch !== void 0 && typeof cfg.watch !== "boolean") {
981
+ return "npm.watch must be a boolean";
982
+ }
983
+ if (cfg.auto_restart !== void 0 && typeof cfg.auto_restart !== "boolean") {
984
+ return "npm.auto_restart must be a boolean";
985
+ }
986
+ return true;
987
+ }
988
+ return "npm config must be a boolean, string (script name), or object";
989
+ }
990
+ };
991
+ }
992
+ static get configuration() {
993
+ return {
994
+ async configureInteractive() {
995
+ const scriptName = await (0, import_prompts3.input)({
996
+ message: "Enter NPM script to run:",
997
+ default: "dev"
998
+ });
999
+ const useAdvanced = await (0, import_prompts3.confirm)({
1000
+ message: "Configure advanced NPM options?",
1001
+ default: false
1002
+ });
1003
+ if (!useAdvanced) {
1004
+ return scriptName;
1005
+ }
1006
+ const watch = await (0, import_prompts3.confirm)({
1007
+ message: "Enable watch mode?",
1008
+ default: false
1009
+ });
1010
+ const autoRestart = await (0, import_prompts3.confirm)({
1011
+ message: "Auto-restart on crash?",
1012
+ default: false
1013
+ });
1014
+ if (!watch && !autoRestart) {
1015
+ return scriptName;
1016
+ }
1017
+ return {
1018
+ command: scriptName,
1019
+ watch,
1020
+ auto_restart: autoRestart
1021
+ };
1022
+ },
1023
+ getDefaultConfig() {
1024
+ return "dev";
1025
+ }
1026
+ };
1027
+ }
1028
+ static getNpmCommand(config) {
1029
+ if (typeof config === "boolean" || config === void 0) {
1030
+ return "npm run dev";
1031
+ }
1032
+ if (typeof config === "string") {
1033
+ return `npm run ${config}`;
1034
+ }
1035
+ return `npm run ${config.command}`;
1036
+ }
1037
+ static get processing() {
1038
+ return {
1039
+ async processEvent(context) {
1040
+ const { project, isShellMode, shellCommands } = context;
1041
+ const npmConfig = project.events.npm;
1042
+ const npmCommand = _NpmEvent.getNpmCommand(npmConfig);
1043
+ if (isShellMode) {
1044
+ shellCommands.push(npmCommand);
1045
+ } else {
1046
+ const [cmd, ...args] = npmCommand.split(" ");
1047
+ (0, import_child_process7.spawn)(cmd, args, {
1048
+ cwd: project.path.path,
1049
+ stdio: "inherit"
1050
+ });
1051
+ }
1052
+ },
1053
+ generateShellCommand(context) {
1054
+ const npmConfig = context.project.events.npm;
1055
+ return [_NpmEvent.getNpmCommand(npmConfig)];
1056
+ }
1057
+ };
1058
+ }
1059
+ static get tmux() {
1060
+ return {
1061
+ getLayoutPriority() {
1062
+ return 50;
1063
+ },
1064
+ contributeToLayout(enabledCommands) {
1065
+ if (enabledCommands.includes("claude")) {
1066
+ return "three-pane";
1067
+ }
1068
+ return "two-pane-npm";
1069
+ }
1070
+ };
1071
+ }
1072
+ static get help() {
1073
+ return {
1074
+ usage: 'npm: true | "script" | { command: string, watch?: boolean, auto_restart?: boolean }',
1075
+ description: "Run an NPM script in the project directory",
1076
+ examples: [
1077
+ { config: true, description: "Run npm run dev" },
1078
+ { config: "test", description: "Run npm run test" },
1079
+ { config: { command: "dev", watch: true }, description: "Run dev with watch mode" }
1080
+ ]
1081
+ };
1082
+ }
1083
+ };
1084
+
1085
+ // src/events/registry.ts
1086
+ var ALL_EVENTS = [CwdEvent, IdeEvent, WebEvent, ClaudeEvent, DockerEvent, NpmEvent];
1087
+ var EventRegistryClass = class {
1088
+ _events = /* @__PURE__ */ new Map();
1089
+ _initialized = false;
1090
+ /**
1091
+ * Initialize the registry by registering all events
1092
+ */
1093
+ async initialize() {
1094
+ if (this._initialized) return;
1095
+ this.registerEvents();
1096
+ this._initialized = true;
1097
+ }
1098
+ /**
1099
+ * Register all event classes
1100
+ */
1101
+ registerEvents() {
1102
+ for (const EventClass of ALL_EVENTS) {
1103
+ if (this.isValidEvent(EventClass)) {
1104
+ const metadata = EventClass.metadata;
1105
+ this._events.set(metadata.name, EventClass);
1106
+ }
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Validate if a class is a proper event
1111
+ */
1112
+ isValidEvent(EventClass) {
1113
+ try {
1114
+ if (typeof EventClass !== "function") return false;
1115
+ const metadata = EventClass.metadata;
1116
+ return metadata !== void 0 && typeof metadata.name === "string" && typeof metadata.displayName === "string" && typeof EventClass.validation === "object" && typeof EventClass.configuration === "object" && typeof EventClass.processing === "object";
1117
+ } catch {
1118
+ return false;
1119
+ }
1120
+ }
1121
+ /**
1122
+ * Get all valid event names from registered events
1123
+ */
1124
+ getValidEventNames() {
1125
+ this.ensureInitialized();
1126
+ return Array.from(this._events.keys());
1127
+ }
1128
+ /**
1129
+ * Get event by name
1130
+ */
1131
+ getEventByName(name) {
1132
+ this.ensureInitialized();
1133
+ return this._events.get(name) ?? null;
1134
+ }
1135
+ /**
1136
+ * Get all events for management UI
1137
+ */
1138
+ getEventsForManageUI() {
1139
+ this.ensureInitialized();
1140
+ const events = [];
1141
+ for (const [name, EventClass] of this._events) {
1142
+ const metadata = EventClass.metadata;
1143
+ events.push({
1144
+ name: metadata.displayName,
1145
+ value: name,
1146
+ description: metadata.description
1147
+ });
1148
+ }
1149
+ return events.sort((a, b) => a.name.localeCompare(b.name));
1150
+ }
1151
+ /**
1152
+ * Get events that support tmux integration
1153
+ */
1154
+ getTmuxEnabledEvents() {
1155
+ this.ensureInitialized();
1156
+ const tmuxEvents = [];
1157
+ for (const [name, EventClass] of this._events) {
1158
+ const tmux = EventClass.tmux;
1159
+ if (tmux) {
1160
+ tmuxEvents.push({
1161
+ name,
1162
+ event: EventClass,
1163
+ priority: tmux.getLayoutPriority ? tmux.getLayoutPriority() : 0
1164
+ });
1165
+ }
1166
+ }
1167
+ return tmuxEvents.sort((a, b) => b.priority - a.priority);
1168
+ }
1169
+ /**
1170
+ * Get all available events with their metadata
1171
+ */
1172
+ getAllEvents() {
1173
+ this.ensureInitialized();
1174
+ const events = [];
1175
+ for (const [name, EventClass] of this._events) {
1176
+ const typedClass = EventClass;
1177
+ events.push({
1178
+ name,
1179
+ metadata: typedClass.metadata,
1180
+ hasValidation: !!typedClass.validation,
1181
+ hasConfiguration: !!typedClass.configuration,
1182
+ hasProcessing: !!typedClass.processing,
1183
+ hasTmux: !!typedClass.tmux,
1184
+ hasHelp: !!typedClass.help
1185
+ });
1186
+ }
1187
+ return events;
1188
+ }
1189
+ /**
1190
+ * Ensure registry is initialized
1191
+ */
1192
+ ensureInitialized() {
1193
+ if (!this._initialized) {
1194
+ throw new Error("EventRegistry must be initialized before use. Call initialize() first.");
1195
+ }
1196
+ }
1197
+ /**
1198
+ * Clear the registry (useful for testing)
1199
+ */
1200
+ clear() {
1201
+ this._events.clear();
1202
+ this._initialized = false;
1203
+ }
1204
+ };
1205
+ var EventRegistry = new EventRegistryClass();
1206
+ // Annotate the CommonJS export names for ESM import in node:
1207
+ 0 && (module.exports = {
1208
+ BaseEnvironment,
1209
+ Config,
1210
+ EnvironmentRecognizer,
1211
+ EventRegistry,
1212
+ Project,
1213
+ ProjectEnvironment,
1214
+ TmuxManager
1215
+ });
1216
+ //# sourceMappingURL=index.cjs.map