workon 3.1.0 → 3.2.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 (3) hide show
  1. package/dist/cli.js +1602 -1093
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,27 +9,313 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/events/extensions/npm.ts
13
- var npm_exports = {};
14
- __export(npm_exports, {
15
- NpmEvent: () => NpmEvent,
16
- default: () => npm_default
12
+ // src/lib/config.ts
13
+ import Conf from "conf";
14
+ var TRANSIENT_PROPS, Config;
15
+ var init_config = __esm({
16
+ "src/lib/config.ts"() {
17
+ "use strict";
18
+ TRANSIENT_PROPS = ["pkg", "work"];
19
+ Config = class {
20
+ _transient = {};
21
+ _store;
22
+ constructor() {
23
+ this._store = new Conf({
24
+ projectName: "workon"
25
+ });
26
+ }
27
+ get(key, defaultValue) {
28
+ const rootKey = key.split(".")[0];
29
+ if (TRANSIENT_PROPS.includes(rootKey)) {
30
+ return this._transient[key] ?? defaultValue;
31
+ }
32
+ return this._store.get(key, defaultValue);
33
+ }
34
+ set(key, value) {
35
+ const rootKey = key.split(".")[0];
36
+ if (TRANSIENT_PROPS.includes(rootKey)) {
37
+ this._transient[key] = value;
38
+ } else {
39
+ if (value === void 0) {
40
+ this._store.set(key, value);
41
+ } else {
42
+ this._store.set(key, value);
43
+ }
44
+ }
45
+ }
46
+ has(key) {
47
+ const rootKey = key.split(".")[0];
48
+ if (TRANSIENT_PROPS.includes(rootKey)) {
49
+ return Object.prototype.hasOwnProperty.call(this._transient, key);
50
+ }
51
+ return this._store.has(key);
52
+ }
53
+ delete(key) {
54
+ const rootKey = key.split(".")[0];
55
+ if (TRANSIENT_PROPS.includes(rootKey)) {
56
+ delete this._transient[key];
57
+ } else {
58
+ this._store.delete(key);
59
+ }
60
+ }
61
+ getProjects() {
62
+ return this.get("projects") ?? {};
63
+ }
64
+ getProject(name) {
65
+ const projects = this.getProjects();
66
+ return projects[name];
67
+ }
68
+ setProject(name, config) {
69
+ const projects = this.getProjects();
70
+ projects[name] = config;
71
+ this.set("projects", projects);
72
+ }
73
+ deleteProject(name) {
74
+ const projects = this.getProjects();
75
+ delete projects[name];
76
+ this.set("projects", projects);
77
+ }
78
+ getDefaults() {
79
+ return this.get("project_defaults");
80
+ }
81
+ setDefaults(defaults) {
82
+ this.set("project_defaults", defaults);
83
+ }
84
+ get path() {
85
+ return this._store.path;
86
+ }
87
+ get store() {
88
+ return this._store.store;
89
+ }
90
+ };
91
+ }
17
92
  });
18
- import { spawn as spawn7 } from "child_process";
19
- import { input as input3, confirm as confirm2 } from "@inquirer/prompts";
20
- var NpmEvent, npm_default;
21
- var init_npm = __esm({
22
- "src/events/extensions/npm.ts"() {
93
+
94
+ // src/lib/project.ts
95
+ import File from "phylo";
96
+ import deepAssign from "deep-assign";
97
+ var Project;
98
+ var init_project = __esm({
99
+ "src/lib/project.ts"() {
23
100
  "use strict";
24
- NpmEvent = class _NpmEvent {
101
+ Project = class {
102
+ name;
103
+ _base;
104
+ _path;
105
+ _ide;
106
+ _events = {};
107
+ _branch;
108
+ _homepage;
109
+ _defaults;
110
+ _initialCfg;
111
+ constructor(name, cfg, defaults) {
112
+ this._defaults = defaults ?? { base: "" };
113
+ this._initialCfg = { path: name, events: {}, ...cfg };
114
+ this.name = cfg?.name ?? name;
115
+ const merged = deepAssign({}, this._defaults, this._initialCfg);
116
+ if (merged.base) {
117
+ this.base = merged.base;
118
+ }
119
+ if (merged.path) {
120
+ this.path = merged.path;
121
+ }
122
+ if (merged.ide) {
123
+ this._ide = merged.ide;
124
+ }
125
+ if (merged.events) {
126
+ this._events = merged.events;
127
+ }
128
+ if (merged.branch) {
129
+ this._branch = merged.branch;
130
+ }
131
+ if (merged.homepage) {
132
+ this._homepage = merged.homepage;
133
+ }
134
+ }
135
+ set base(path) {
136
+ this._base = File.from(path).absolutify();
137
+ }
138
+ get base() {
139
+ return this._base;
140
+ }
141
+ set ide(cmd) {
142
+ this._ide = cmd;
143
+ }
144
+ get ide() {
145
+ return this._ide;
146
+ }
147
+ set events(eventCfg) {
148
+ this._events = eventCfg;
149
+ }
150
+ get events() {
151
+ return this._events;
152
+ }
153
+ set path(path) {
154
+ if (this._base) {
155
+ this._path = this._base.join(path);
156
+ } else {
157
+ this._path = File.from(path);
158
+ }
159
+ this._path = this._path.absolutify();
160
+ }
161
+ get path() {
162
+ if (!this._path) {
163
+ throw new Error("Project path not set");
164
+ }
165
+ return this._path;
166
+ }
167
+ set branch(branch) {
168
+ this._branch = branch;
169
+ }
170
+ get branch() {
171
+ return this._branch;
172
+ }
173
+ set homepage(url) {
174
+ this._homepage = url;
175
+ }
176
+ get homepage() {
177
+ return this._homepage;
178
+ }
179
+ static $isProject = true;
180
+ $isProject = true;
181
+ };
182
+ }
183
+ });
184
+
185
+ // src/lib/environment.ts
186
+ import File2 from "phylo";
187
+ import { simpleGit } from "simple-git";
188
+ var BaseEnvironment, ProjectEnvironment, EnvironmentRecognizer;
189
+ var init_environment = __esm({
190
+ "src/lib/environment.ts"() {
191
+ "use strict";
192
+ init_config();
193
+ init_project();
194
+ BaseEnvironment = class {
195
+ $isProjectEnvironment = false;
196
+ };
197
+ ProjectEnvironment = class _ProjectEnvironment {
198
+ $isProjectEnvironment = true;
199
+ project;
200
+ constructor(projectCfg) {
201
+ this.project = new Project(projectCfg.name, projectCfg);
202
+ }
203
+ static load(cfg, defaults) {
204
+ const project = new Project(cfg.name, cfg, defaults);
205
+ return new _ProjectEnvironment({ ...cfg, name: project.name });
206
+ }
207
+ };
208
+ EnvironmentRecognizer = class {
209
+ static config;
210
+ static log;
211
+ static projects = [];
212
+ static configured = false;
213
+ static configure(config, log) {
214
+ if (this.configured) {
215
+ return;
216
+ }
217
+ this.config = config;
218
+ this.log = log;
219
+ this.configured = true;
220
+ }
221
+ static async recognize(dir) {
222
+ this.ensureConfigured();
223
+ const theDir = File2.from(dir).canonicalize();
224
+ this.log.debug("Directory to recognize is: " + theDir.canonicalPath());
225
+ const allProjects = this.getAllProjects();
226
+ const matching = allProjects.filter((p) => p.path.canonicalPath() === theDir.path);
227
+ if (matching.length === 0) {
228
+ return new BaseEnvironment();
229
+ }
230
+ this.log.debug(`Found ${matching.length} matching projects`);
231
+ const base = matching.find((p) => !p.name.includes("#")) ?? matching[0];
232
+ this.log.debug("Base project is: " + base.name);
233
+ const gitDir = base.path.up(".git");
234
+ if (gitDir) {
235
+ try {
236
+ const git = simpleGit(gitDir.path);
237
+ const branchSummary = await git.branchLocal();
238
+ base.branch = branchSummary.current;
239
+ } catch {
240
+ }
241
+ }
242
+ return this.getProjectEnvironment(base, matching);
243
+ }
244
+ static getAllProjects(refresh = false) {
245
+ if (this.projects.length > 0 && !refresh) {
246
+ return this.projects;
247
+ }
248
+ const defaults = this.config.getDefaults();
249
+ if (!defaults?.base) {
250
+ this.projects = [];
251
+ return this.projects;
252
+ }
253
+ const baseDir = File2.from(defaults.base);
254
+ const projectsMap = this.config.getProjects();
255
+ this.projects = Object.entries(projectsMap).map(([name, project]) => ({
256
+ ...project,
257
+ name,
258
+ path: baseDir.join(project.path)
259
+ }));
260
+ return this.projects;
261
+ }
262
+ static getProjectEnvironment(base, _matching) {
263
+ const exactName = `${base.name}#${base.branch}`;
264
+ const exactProj = this.projects.find((p) => p.name === exactName);
265
+ const toProjectConfig = (p) => ({
266
+ name: p.name,
267
+ path: p.path.path,
268
+ // Convert PhyloFile to string path
269
+ ide: p.ide,
270
+ homepage: p.homepage,
271
+ events: p.events,
272
+ branch: p.branch,
273
+ exactName
274
+ });
275
+ if (exactProj) {
276
+ return new ProjectEnvironment({ ...toProjectConfig(exactProj), branch: base.branch });
277
+ }
278
+ return new ProjectEnvironment(toProjectConfig(base));
279
+ }
280
+ static ensureConfigured() {
281
+ if (!this.configured) {
282
+ this.config = new Config();
283
+ this.log = {
284
+ debug: () => {
285
+ },
286
+ info: () => {
287
+ },
288
+ log: () => {
289
+ },
290
+ warn: () => {
291
+ },
292
+ error: () => {
293
+ },
294
+ setLogLevel: () => {
295
+ }
296
+ };
297
+ this.configured = true;
298
+ }
299
+ }
300
+ };
301
+ }
302
+ });
303
+
304
+ // src/events/core/cwd.ts
305
+ import { spawn as spawn2 } from "child_process";
306
+ var CwdEvent;
307
+ var init_cwd = __esm({
308
+ "src/events/core/cwd.ts"() {
309
+ "use strict";
310
+ CwdEvent = class {
25
311
  static get metadata() {
26
312
  return {
27
- name: "npm",
28
- displayName: "Run NPM command",
29
- description: "Execute NPM scripts in project directory",
30
- category: "development",
31
- requiresTmux: true,
32
- dependencies: ["npm"]
313
+ name: "cwd",
314
+ displayName: "Change directory (cwd)",
315
+ description: "Change current working directory to project path",
316
+ category: "core",
317
+ requiresTmux: false,
318
+ dependencies: []
33
319
  };
34
320
  }
35
321
  static get validation() {
@@ -38,157 +324,813 @@ var init_npm = __esm({
38
324
  if (typeof config === "boolean" || config === "true" || config === "false") {
39
325
  return true;
40
326
  }
41
- if (typeof config === "string") {
42
- if (config.trim().length === 0) {
43
- return "npm script name cannot be empty";
44
- }
45
- return true;
46
- }
47
- if (typeof config === "object" && config !== null) {
48
- const cfg = config;
49
- if (typeof cfg.command !== "string" || cfg.command.trim().length === 0) {
50
- return "npm.command must be a non-empty string";
51
- }
52
- if (cfg.watch !== void 0 && typeof cfg.watch !== "boolean") {
53
- return "npm.watch must be a boolean";
54
- }
55
- if (cfg.auto_restart !== void 0 && typeof cfg.auto_restart !== "boolean") {
56
- return "npm.auto_restart must be a boolean";
57
- }
58
- return true;
59
- }
60
- return "npm config must be a boolean, string (script name), or object";
327
+ return "cwd config must be a boolean (true/false)";
61
328
  }
62
329
  };
63
330
  }
64
331
  static get configuration() {
65
332
  return {
66
333
  async configureInteractive() {
67
- const scriptName = await input3({
68
- message: "Enter NPM script to run:",
69
- default: "dev"
70
- });
71
- const useAdvanced = await confirm2({
72
- message: "Configure advanced NPM options?",
73
- default: false
74
- });
75
- if (!useAdvanced) {
76
- return scriptName;
77
- }
78
- const watch = await confirm2({
79
- message: "Enable watch mode?",
80
- default: false
81
- });
82
- const autoRestart = await confirm2({
83
- message: "Auto-restart on crash?",
84
- default: false
85
- });
86
- if (!watch && !autoRestart) {
87
- return scriptName;
88
- }
89
- return {
90
- command: scriptName,
91
- watch,
92
- auto_restart: autoRestart
93
- };
334
+ return true;
94
335
  },
95
336
  getDefaultConfig() {
96
- return "dev";
337
+ return true;
97
338
  }
98
339
  };
99
340
  }
100
- static getNpmCommand(config) {
101
- if (typeof config === "boolean" || config === void 0) {
102
- return "npm run dev";
103
- }
104
- if (typeof config === "string") {
105
- return `npm run ${config}`;
106
- }
107
- return `npm run ${config.command}`;
108
- }
109
341
  static get processing() {
110
342
  return {
111
343
  async processEvent(context) {
112
344
  const { project, isShellMode, shellCommands } = context;
113
- const npmConfig = project.events.npm;
114
- const npmCommand = _NpmEvent.getNpmCommand(npmConfig);
345
+ const projectPath = project.path.path;
115
346
  if (isShellMode) {
116
- shellCommands.push(npmCommand);
347
+ shellCommands.push(`cd "${projectPath}"`);
117
348
  } else {
118
- const [cmd, ...args] = npmCommand.split(" ");
119
- spawn7(cmd, args, {
120
- cwd: project.path.path,
349
+ const shell = process.env.SHELL || "/bin/bash";
350
+ spawn2(shell, [], {
351
+ cwd: projectPath,
121
352
  stdio: "inherit"
122
353
  });
123
354
  }
124
355
  },
125
356
  generateShellCommand(context) {
126
- const npmConfig = context.project.events.npm;
127
- return [_NpmEvent.getNpmCommand(npmConfig)];
357
+ const projectPath = context.project.path.path;
358
+ return [`cd "${projectPath}"`];
128
359
  }
129
360
  };
130
361
  }
131
362
  static get tmux() {
363
+ return null;
364
+ }
365
+ static get help() {
132
366
  return {
133
- getLayoutPriority() {
134
- return 50;
367
+ usage: "cwd: true | false",
368
+ description: "Change the current working directory to the project path",
369
+ examples: [
370
+ { config: true, description: "Enable directory change" },
371
+ { config: false, description: "Disable directory change" }
372
+ ]
373
+ };
374
+ }
375
+ };
376
+ }
377
+ });
378
+
379
+ // src/events/core/ide.ts
380
+ import { spawn as spawn3 } from "child_process";
381
+ var IdeEvent;
382
+ var init_ide = __esm({
383
+ "src/events/core/ide.ts"() {
384
+ "use strict";
385
+ IdeEvent = class {
386
+ static get metadata() {
387
+ return {
388
+ name: "ide",
389
+ displayName: "Open in IDE",
390
+ description: "Open project in configured IDE/editor",
391
+ category: "core",
392
+ requiresTmux: false,
393
+ dependencies: []
394
+ };
395
+ }
396
+ static get validation() {
397
+ return {
398
+ validateConfig(config) {
399
+ if (typeof config === "boolean" || config === "true" || config === "false") {
400
+ return true;
401
+ }
402
+ return "ide config must be a boolean (true/false)";
403
+ }
404
+ };
405
+ }
406
+ static get configuration() {
407
+ return {
408
+ async configureInteractive() {
409
+ return true;
135
410
  },
136
- contributeToLayout(enabledCommands) {
137
- if (enabledCommands.includes("claude")) {
138
- return "three-pane";
411
+ getDefaultConfig() {
412
+ return true;
413
+ }
414
+ };
415
+ }
416
+ static get processing() {
417
+ return {
418
+ async processEvent(context) {
419
+ const { project, isShellMode, shellCommands } = context;
420
+ const projectPath = project.path.path;
421
+ const ide = project.ide || "code";
422
+ if (isShellMode) {
423
+ shellCommands.push(`${ide} "${projectPath}" &`);
424
+ } else {
425
+ spawn3(ide, [projectPath], {
426
+ detached: true,
427
+ stdio: "ignore"
428
+ }).unref();
139
429
  }
140
- return "two-pane-npm";
430
+ },
431
+ generateShellCommand(context) {
432
+ const projectPath = context.project.path.path;
433
+ const ide = context.project.ide || "code";
434
+ return [`${ide} "${projectPath}" &`];
141
435
  }
142
436
  };
143
437
  }
438
+ static get tmux() {
439
+ return null;
440
+ }
144
441
  static get help() {
145
442
  return {
146
- usage: 'npm: true | "script" | { command: string, watch?: boolean, auto_restart?: boolean }',
147
- description: "Run an NPM script in the project directory",
443
+ usage: "ide: true | false",
444
+ description: "Open the project in the configured IDE",
148
445
  examples: [
149
- { config: true, description: "Run npm run dev" },
150
- { config: "test", description: "Run npm run test" },
151
- { config: { command: "dev", watch: true }, description: "Run dev with watch mode" }
446
+ { config: true, description: "Enable IDE opening" },
447
+ { config: false, description: "Disable IDE opening" }
152
448
  ]
153
449
  };
154
450
  }
155
451
  };
156
- npm_default = NpmEvent;
157
452
  }
158
453
  });
159
454
 
160
- // src/commands/interactive.ts
161
- var interactive_exports = {};
162
- __export(interactive_exports, {
163
- runInteractive: () => runInteractive
164
- });
165
- import { select, input as input4, checkbox } from "@inquirer/prompts";
166
- import File3 from "phylo";
167
- import deepAssign2 from "deep-assign";
168
- async function runInteractive(ctx) {
169
- const { config, log, environment, suggestedName } = ctx;
170
- showLogo(config);
171
- log.log("");
172
- const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name : File3.cwd().name);
173
- const fromUser = !!suggestedName;
174
- await startInteractive(defaultName, fromUser, ctx);
175
- }
176
- function showLogo(config) {
177
- const version = config.get("pkg")?.version ?? "unknown";
178
- console.log(
179
- ` 8\x1B[2m${" ".repeat(Math.max(15 - version.length - 1, 1)) + "v" + version}\x1B[22m
180
- Yb db dP .d8b. 8d8b 8.dP \x1B[92m.d8b. 8d8b.\x1B[0m
181
- YbdPYbdP 8' .8 8P 88b \x1B[92m8' .8 8P Y8\x1B[0m
182
- YP YP \`Y8P' 8 8 Yb \x1B[92m\`Y8P' 8 8\x1B[0m`
183
- );
184
- }
185
- async function startInteractive(defaultName, fromUser, ctx, showMain = false) {
186
- const { log, environment } = ctx;
187
- log.debug(`Name '${defaultName}' was${fromUser ? "" : " not"} provided by the user`);
188
- const question = getFirstQuestion(defaultName, fromUser, environment, showMain);
189
- const action = await select(question);
190
- switch (action) {
191
- case "exit":
455
+ // src/events/core/web.ts
456
+ import { spawn as spawn4 } from "child_process";
457
+ import { platform } from "os";
458
+ var WebEvent;
459
+ var init_web = __esm({
460
+ "src/events/core/web.ts"() {
461
+ "use strict";
462
+ WebEvent = class _WebEvent {
463
+ static get metadata() {
464
+ return {
465
+ name: "web",
466
+ displayName: "Open homepage in browser",
467
+ description: "Open project homepage in web browser",
468
+ category: "core",
469
+ requiresTmux: false,
470
+ dependencies: []
471
+ };
472
+ }
473
+ static get validation() {
474
+ return {
475
+ validateConfig(config) {
476
+ if (typeof config === "boolean" || config === "true" || config === "false") {
477
+ return true;
478
+ }
479
+ return "web config must be a boolean (true/false)";
480
+ }
481
+ };
482
+ }
483
+ static get configuration() {
484
+ return {
485
+ async configureInteractive() {
486
+ return true;
487
+ },
488
+ getDefaultConfig() {
489
+ return true;
490
+ }
491
+ };
492
+ }
493
+ static getOpenCommand() {
494
+ const os = platform();
495
+ switch (os) {
496
+ case "darwin":
497
+ return "open";
498
+ case "win32":
499
+ return "start";
500
+ default:
501
+ return "xdg-open";
502
+ }
503
+ }
504
+ static get processing() {
505
+ return {
506
+ async processEvent(context) {
507
+ const { project, isShellMode, shellCommands } = context;
508
+ const homepage = project.homepage;
509
+ if (!homepage) {
510
+ console.warn("No homepage configured for project");
511
+ return;
512
+ }
513
+ const openCmd = _WebEvent.getOpenCommand();
514
+ if (isShellMode) {
515
+ shellCommands.push(`${openCmd} "${homepage}" &`);
516
+ } else {
517
+ spawn4(openCmd, [homepage], {
518
+ detached: true,
519
+ stdio: "ignore"
520
+ }).unref();
521
+ }
522
+ },
523
+ generateShellCommand(context) {
524
+ const homepage = context.project.homepage;
525
+ if (!homepage) return [];
526
+ const openCmd = _WebEvent.getOpenCommand();
527
+ return [`${openCmd} "${homepage}" &`];
528
+ }
529
+ };
530
+ }
531
+ static get tmux() {
532
+ return null;
533
+ }
534
+ static get help() {
535
+ return {
536
+ usage: "web: true | false",
537
+ description: "Open the project homepage in the default browser",
538
+ examples: [
539
+ { config: true, description: "Enable browser opening" },
540
+ { config: false, description: "Disable browser opening" }
541
+ ]
542
+ };
543
+ }
544
+ };
545
+ }
546
+ });
547
+
548
+ // src/events/extensions/claude.ts
549
+ import { spawn as spawn5 } from "child_process";
550
+ import { input, confirm } from "@inquirer/prompts";
551
+ var ClaudeEvent;
552
+ var init_claude = __esm({
553
+ "src/events/extensions/claude.ts"() {
554
+ "use strict";
555
+ ClaudeEvent = class _ClaudeEvent {
556
+ static get metadata() {
557
+ return {
558
+ name: "claude",
559
+ displayName: "Launch Claude Code",
560
+ description: "Launch Claude Code with optional flags and configuration",
561
+ category: "development",
562
+ requiresTmux: true,
563
+ dependencies: ["claude"]
564
+ };
565
+ }
566
+ static get validation() {
567
+ return {
568
+ validateConfig(config) {
569
+ if (typeof config === "boolean" || config === "true" || config === "false") {
570
+ return true;
571
+ }
572
+ if (typeof config === "object" && config !== null) {
573
+ const cfg = config;
574
+ if (cfg.flags !== void 0) {
575
+ if (!Array.isArray(cfg.flags)) {
576
+ return "claude.flags must be an array of strings";
577
+ }
578
+ for (const flag of cfg.flags) {
579
+ if (typeof flag !== "string") {
580
+ return "claude.flags must contain only strings";
581
+ }
582
+ if (!flag.startsWith("-")) {
583
+ return `Invalid flag "${flag}": flags must start with - or --`;
584
+ }
585
+ }
586
+ }
587
+ if (cfg.split_terminal !== void 0 && typeof cfg.split_terminal !== "boolean") {
588
+ return "claude.split_terminal must be a boolean";
589
+ }
590
+ return true;
591
+ }
592
+ return "claude config must be a boolean or object with flags/split_terminal";
593
+ }
594
+ };
595
+ }
596
+ static get configuration() {
597
+ return {
598
+ async configureInteractive() {
599
+ const useAdvanced = await confirm({
600
+ message: "Configure advanced Claude options?",
601
+ default: false
602
+ });
603
+ if (!useAdvanced) {
604
+ return true;
605
+ }
606
+ const flagsInput = await input({
607
+ message: "Enter Claude flags (comma-separated, e.g., --resume, --debug):",
608
+ default: ""
609
+ });
610
+ const flags = flagsInput.split(",").map((f) => f.trim()).filter((f) => f.length > 0 && f.startsWith("-"));
611
+ const splitTerminal = await confirm({
612
+ message: "Use split terminal layout (Claude + shell)?",
613
+ default: true
614
+ });
615
+ if (flags.length === 0 && !splitTerminal) {
616
+ return true;
617
+ }
618
+ const config = {};
619
+ if (flags.length > 0) config.flags = flags;
620
+ if (splitTerminal) config.split_terminal = splitTerminal;
621
+ return config;
622
+ },
623
+ getDefaultConfig() {
624
+ return true;
625
+ }
626
+ };
627
+ }
628
+ static getClaudeCommand(config) {
629
+ if (typeof config === "boolean" || config === void 0) {
630
+ return "claude";
631
+ }
632
+ const flags = config.flags || [];
633
+ return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
634
+ }
635
+ static get processing() {
636
+ return {
637
+ async processEvent(context) {
638
+ const { project, isShellMode, shellCommands } = context;
639
+ const claudeConfig = project.events.claude;
640
+ const claudeCommand = _ClaudeEvent.getClaudeCommand(claudeConfig);
641
+ if (isShellMode) {
642
+ shellCommands.push(claudeCommand);
643
+ } else {
644
+ const args = claudeCommand.split(" ").slice(1);
645
+ spawn5("claude", args, {
646
+ cwd: project.path.path,
647
+ stdio: "inherit"
648
+ });
649
+ }
650
+ },
651
+ generateShellCommand(context) {
652
+ const claudeConfig = context.project.events.claude;
653
+ return [_ClaudeEvent.getClaudeCommand(claudeConfig)];
654
+ }
655
+ };
656
+ }
657
+ static get tmux() {
658
+ return {
659
+ getLayoutPriority() {
660
+ return 100;
661
+ },
662
+ contributeToLayout(enabledCommands) {
663
+ if (enabledCommands.includes("npm")) {
664
+ return "three-pane";
665
+ }
666
+ return "split";
667
+ }
668
+ };
669
+ }
670
+ static get help() {
671
+ return {
672
+ usage: "claude: true | { flags: string[], split_terminal: boolean }",
673
+ description: "Launch Claude Code in the project directory",
674
+ examples: [
675
+ { config: true, description: "Launch Claude with defaults" },
676
+ { config: { flags: ["--resume"] }, description: "Resume previous session" },
677
+ {
678
+ config: { flags: ["--model", "opus"], split_terminal: true },
679
+ description: "Use Opus model with split terminal"
680
+ }
681
+ ]
682
+ };
683
+ }
684
+ };
685
+ }
686
+ });
687
+
688
+ // src/events/extensions/docker.ts
689
+ import { spawn as spawn6 } from "child_process";
690
+ import { input as input2 } from "@inquirer/prompts";
691
+ var DockerEvent;
692
+ var init_docker = __esm({
693
+ "src/events/extensions/docker.ts"() {
694
+ "use strict";
695
+ DockerEvent = class _DockerEvent {
696
+ static get metadata() {
697
+ return {
698
+ name: "docker",
699
+ displayName: "Docker container management",
700
+ description: "Start/stop Docker containers for the project",
701
+ category: "development",
702
+ requiresTmux: false,
703
+ dependencies: ["docker"]
704
+ };
705
+ }
706
+ static get validation() {
707
+ return {
708
+ validateConfig(config) {
709
+ if (typeof config === "boolean" || config === "true" || config === "false") {
710
+ return true;
711
+ }
712
+ if (typeof config === "string") {
713
+ return true;
714
+ }
715
+ if (typeof config === "object" && config !== null) {
716
+ const cfg = config;
717
+ if (cfg.compose_file !== void 0 && typeof cfg.compose_file !== "string") {
718
+ return "docker.compose_file must be a string";
719
+ }
720
+ if (cfg.services !== void 0) {
721
+ if (!Array.isArray(cfg.services)) {
722
+ return "docker.services must be an array";
723
+ }
724
+ for (const service of cfg.services) {
725
+ if (typeof service !== "string") {
726
+ return "docker.services must contain only strings";
727
+ }
728
+ }
729
+ }
730
+ return true;
731
+ }
732
+ return "docker config must be a boolean, string (compose file), or object";
733
+ }
734
+ };
735
+ }
736
+ static get configuration() {
737
+ return {
738
+ async configureInteractive() {
739
+ const composeFile = await input2({
740
+ message: "Enter docker-compose file path:",
741
+ default: "docker-compose.yml"
742
+ });
743
+ const servicesInput = await input2({
744
+ message: "Enter services to start (comma-separated, leave empty for all):",
745
+ default: ""
746
+ });
747
+ const services = servicesInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
748
+ if (composeFile === "docker-compose.yml" && services.length === 0) {
749
+ return { compose_file: "docker-compose.yml" };
750
+ }
751
+ if (services.length === 0) {
752
+ return composeFile;
753
+ }
754
+ return {
755
+ compose_file: composeFile,
756
+ services
757
+ };
758
+ },
759
+ getDefaultConfig() {
760
+ return { compose_file: "docker-compose.yml" };
761
+ }
762
+ };
763
+ }
764
+ static getDockerCommand(config) {
765
+ if (typeof config === "boolean" || config === void 0) {
766
+ return "docker-compose up -d";
767
+ }
768
+ if (typeof config === "string") {
769
+ return `docker-compose -f ${config} up -d`;
770
+ }
771
+ const composeFile = config.compose_file || "docker-compose.yml";
772
+ const services = config.services?.join(" ") || "";
773
+ return `docker-compose -f ${composeFile} up -d ${services}`.trim();
774
+ }
775
+ static get processing() {
776
+ return {
777
+ async processEvent(context) {
778
+ const { project, isShellMode, shellCommands } = context;
779
+ const dockerConfig = project.events.docker;
780
+ const dockerCommand = _DockerEvent.getDockerCommand(
781
+ dockerConfig
782
+ );
783
+ if (isShellMode) {
784
+ shellCommands.push(dockerCommand);
785
+ } else {
786
+ const [cmd, ...args] = dockerCommand.split(" ");
787
+ spawn6(cmd, args, {
788
+ cwd: project.path.path,
789
+ stdio: "inherit"
790
+ });
791
+ }
792
+ },
793
+ generateShellCommand(context) {
794
+ const dockerConfig = context.project.events.docker;
795
+ return [_DockerEvent.getDockerCommand(dockerConfig)];
796
+ }
797
+ };
798
+ }
799
+ static get tmux() {
800
+ return null;
801
+ }
802
+ static get help() {
803
+ return {
804
+ usage: 'docker: true | "compose-file.yml" | { compose_file: string, services?: string[] }',
805
+ description: "Start Docker containers for the project",
806
+ examples: [
807
+ { config: true, description: "Use default docker-compose.yml" },
808
+ { config: "docker-compose.dev.yml", description: "Use custom compose file" },
809
+ {
810
+ config: { compose_file: "docker-compose.yml", services: ["web", "db"] },
811
+ description: "Start specific services"
812
+ }
813
+ ]
814
+ };
815
+ }
816
+ };
817
+ }
818
+ });
819
+
820
+ // src/events/extensions/npm.ts
821
+ var npm_exports = {};
822
+ __export(npm_exports, {
823
+ NpmEvent: () => NpmEvent,
824
+ default: () => npm_default
825
+ });
826
+ import { spawn as spawn7 } from "child_process";
827
+ import { input as input3, confirm as confirm2 } from "@inquirer/prompts";
828
+ var NpmEvent, npm_default;
829
+ var init_npm = __esm({
830
+ "src/events/extensions/npm.ts"() {
831
+ "use strict";
832
+ NpmEvent = class _NpmEvent {
833
+ static get metadata() {
834
+ return {
835
+ name: "npm",
836
+ displayName: "Run NPM command",
837
+ description: "Execute NPM scripts in project directory",
838
+ category: "development",
839
+ requiresTmux: true,
840
+ dependencies: ["npm"]
841
+ };
842
+ }
843
+ static get validation() {
844
+ return {
845
+ validateConfig(config) {
846
+ if (typeof config === "boolean" || config === "true" || config === "false") {
847
+ return true;
848
+ }
849
+ if (typeof config === "string") {
850
+ if (config.trim().length === 0) {
851
+ return "npm script name cannot be empty";
852
+ }
853
+ return true;
854
+ }
855
+ if (typeof config === "object" && config !== null) {
856
+ const cfg = config;
857
+ if (typeof cfg.command !== "string" || cfg.command.trim().length === 0) {
858
+ return "npm.command must be a non-empty string";
859
+ }
860
+ if (cfg.watch !== void 0 && typeof cfg.watch !== "boolean") {
861
+ return "npm.watch must be a boolean";
862
+ }
863
+ if (cfg.auto_restart !== void 0 && typeof cfg.auto_restart !== "boolean") {
864
+ return "npm.auto_restart must be a boolean";
865
+ }
866
+ return true;
867
+ }
868
+ return "npm config must be a boolean, string (script name), or object";
869
+ }
870
+ };
871
+ }
872
+ static get configuration() {
873
+ return {
874
+ async configureInteractive() {
875
+ const scriptName = await input3({
876
+ message: "Enter NPM script to run:",
877
+ default: "dev"
878
+ });
879
+ const useAdvanced = await confirm2({
880
+ message: "Configure advanced NPM options?",
881
+ default: false
882
+ });
883
+ if (!useAdvanced) {
884
+ return scriptName;
885
+ }
886
+ const watch = await confirm2({
887
+ message: "Enable watch mode?",
888
+ default: false
889
+ });
890
+ const autoRestart = await confirm2({
891
+ message: "Auto-restart on crash?",
892
+ default: false
893
+ });
894
+ if (!watch && !autoRestart) {
895
+ return scriptName;
896
+ }
897
+ return {
898
+ command: scriptName,
899
+ watch,
900
+ auto_restart: autoRestart
901
+ };
902
+ },
903
+ getDefaultConfig() {
904
+ return "dev";
905
+ }
906
+ };
907
+ }
908
+ static getNpmCommand(config) {
909
+ if (typeof config === "boolean" || config === void 0) {
910
+ return "npm run dev";
911
+ }
912
+ if (typeof config === "string") {
913
+ return `npm run ${config}`;
914
+ }
915
+ return `npm run ${config.command}`;
916
+ }
917
+ static get processing() {
918
+ return {
919
+ async processEvent(context) {
920
+ const { project, isShellMode, shellCommands } = context;
921
+ const npmConfig = project.events.npm;
922
+ const npmCommand = _NpmEvent.getNpmCommand(npmConfig);
923
+ if (isShellMode) {
924
+ shellCommands.push(npmCommand);
925
+ } else {
926
+ const [cmd, ...args] = npmCommand.split(" ");
927
+ spawn7(cmd, args, {
928
+ cwd: project.path.path,
929
+ stdio: "inherit"
930
+ });
931
+ }
932
+ },
933
+ generateShellCommand(context) {
934
+ const npmConfig = context.project.events.npm;
935
+ return [_NpmEvent.getNpmCommand(npmConfig)];
936
+ }
937
+ };
938
+ }
939
+ static get tmux() {
940
+ return {
941
+ getLayoutPriority() {
942
+ return 50;
943
+ },
944
+ contributeToLayout(enabledCommands) {
945
+ if (enabledCommands.includes("claude")) {
946
+ return "three-pane";
947
+ }
948
+ return "two-pane-npm";
949
+ }
950
+ };
951
+ }
952
+ static get help() {
953
+ return {
954
+ usage: 'npm: true | "script" | { command: string, watch?: boolean, auto_restart?: boolean }',
955
+ description: "Run an NPM script in the project directory",
956
+ examples: [
957
+ { config: true, description: "Run npm run dev" },
958
+ { config: "test", description: "Run npm run test" },
959
+ { config: { command: "dev", watch: true }, description: "Run dev with watch mode" }
960
+ ]
961
+ };
962
+ }
963
+ };
964
+ npm_default = NpmEvent;
965
+ }
966
+ });
967
+
968
+ // src/events/registry.ts
969
+ var ALL_EVENTS, EventRegistryClass, EventRegistry;
970
+ var init_registry = __esm({
971
+ "src/events/registry.ts"() {
972
+ "use strict";
973
+ init_cwd();
974
+ init_ide();
975
+ init_web();
976
+ init_claude();
977
+ init_docker();
978
+ init_npm();
979
+ ALL_EVENTS = [CwdEvent, IdeEvent, WebEvent, ClaudeEvent, DockerEvent, NpmEvent];
980
+ EventRegistryClass = class {
981
+ _events = /* @__PURE__ */ new Map();
982
+ _initialized = false;
983
+ /**
984
+ * Initialize the registry by registering all events
985
+ */
986
+ async initialize() {
987
+ if (this._initialized) return;
988
+ this.registerEvents();
989
+ this._initialized = true;
990
+ }
991
+ /**
992
+ * Register all event classes
993
+ */
994
+ registerEvents() {
995
+ for (const EventClass of ALL_EVENTS) {
996
+ if (this.isValidEvent(EventClass)) {
997
+ const metadata = EventClass.metadata;
998
+ this._events.set(metadata.name, EventClass);
999
+ }
1000
+ }
1001
+ }
1002
+ /**
1003
+ * Validate if a class is a proper event
1004
+ */
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
+ }
1013
+ }
1014
+ /**
1015
+ * Get all valid event names from registered events
1016
+ */
1017
+ getValidEventNames() {
1018
+ this.ensureInitialized();
1019
+ return Array.from(this._events.keys());
1020
+ }
1021
+ /**
1022
+ * Get event by name
1023
+ */
1024
+ getEventByName(name) {
1025
+ this.ensureInitialized();
1026
+ return this._events.get(name) ?? null;
1027
+ }
1028
+ /**
1029
+ * Get all events for management UI
1030
+ */
1031
+ getEventsForManageUI() {
1032
+ this.ensureInitialized();
1033
+ const events = [];
1034
+ for (const [name, EventClass] of this._events) {
1035
+ const metadata = EventClass.metadata;
1036
+ events.push({
1037
+ name: metadata.displayName,
1038
+ value: name,
1039
+ description: metadata.description
1040
+ });
1041
+ }
1042
+ return events.sort((a, b) => a.name.localeCompare(b.name));
1043
+ }
1044
+ /**
1045
+ * Get events that support tmux integration
1046
+ */
1047
+ getTmuxEnabledEvents() {
1048
+ this.ensureInitialized();
1049
+ const tmuxEvents = [];
1050
+ for (const [name, EventClass] of this._events) {
1051
+ const tmux = EventClass.tmux;
1052
+ if (tmux) {
1053
+ tmuxEvents.push({
1054
+ name,
1055
+ event: EventClass,
1056
+ priority: tmux.getLayoutPriority ? tmux.getLayoutPriority() : 0
1057
+ });
1058
+ }
1059
+ }
1060
+ return tmuxEvents.sort((a, b) => b.priority - a.priority);
1061
+ }
1062
+ /**
1063
+ * Get all available events with their metadata
1064
+ */
1065
+ getAllEvents() {
1066
+ this.ensureInitialized();
1067
+ const events = [];
1068
+ for (const [name, EventClass] of this._events) {
1069
+ const typedClass = EventClass;
1070
+ events.push({
1071
+ 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
1078
+ });
1079
+ }
1080
+ return events;
1081
+ }
1082
+ /**
1083
+ * Ensure registry is initialized
1084
+ */
1085
+ ensureInitialized() {
1086
+ if (!this._initialized) {
1087
+ throw new Error("EventRegistry must be initialized before use. Call initialize() first.");
1088
+ }
1089
+ }
1090
+ /**
1091
+ * Clear the registry (useful for testing)
1092
+ */
1093
+ clear() {
1094
+ this._events.clear();
1095
+ this._initialized = false;
1096
+ }
1097
+ };
1098
+ EventRegistry = new EventRegistryClass();
1099
+ }
1100
+ });
1101
+
1102
+ // src/commands/interactive.ts
1103
+ var interactive_exports = {};
1104
+ __export(interactive_exports, {
1105
+ runInteractive: () => runInteractive
1106
+ });
1107
+ import { select, input as input4, checkbox, confirm as confirm3 } from "@inquirer/prompts";
1108
+ import File3 from "phylo";
1109
+ import deepAssign2 from "deep-assign";
1110
+ async function runInteractive(ctx) {
1111
+ const { config, log, environment, suggestedName } = ctx;
1112
+ showLogo(config);
1113
+ log.log("");
1114
+ const defaultName = suggestedName ?? (environment.$isProjectEnvironment ? environment.project.name : File3.cwd().name);
1115
+ const fromUser = !!suggestedName;
1116
+ await startInteractive(defaultName, fromUser, ctx);
1117
+ }
1118
+ function showLogo(config) {
1119
+ const version = config.get("pkg")?.version ?? "unknown";
1120
+ console.log(
1121
+ ` 8\x1B[2m${" ".repeat(Math.max(15 - version.length - 1, 1)) + "v" + version}\x1B[22m
1122
+ Yb db dP .d8b. 8d8b 8.dP \x1B[92m.d8b. 8d8b.\x1B[0m
1123
+ YbdPYbdP 8' .8 8P 88b \x1B[92m8' .8 8P Y8\x1B[0m
1124
+ YP YP \`Y8P' 8 8 Yb \x1B[92m\`Y8P' 8 8\x1B[0m`
1125
+ );
1126
+ }
1127
+ async function startInteractive(defaultName, fromUser, ctx, showMain = false) {
1128
+ const { log, environment } = ctx;
1129
+ log.debug(`Name '${defaultName}' was${fromUser ? "" : " not"} provided by the user`);
1130
+ const question = getFirstQuestion(defaultName, fromUser, environment, showMain);
1131
+ const action = await select(question);
1132
+ switch (action) {
1133
+ case "exit":
192
1134
  return;
193
1135
  case "more":
194
1136
  await startInteractive(defaultName, fromUser, ctx, true);
@@ -200,17 +1142,17 @@ async function startInteractive(defaultName, fromUser, ctx, showMain = false) {
200
1142
  await initBranch(defaultName, ctx);
201
1143
  return;
202
1144
  case "switch-project":
203
- log.info("Switch to an existing project");
204
- break;
1145
+ await switchProject(ctx);
1146
+ return;
205
1147
  case "switch-branch":
206
- log.info("Switch to an existing branch");
207
- break;
1148
+ await switchBranch(defaultName, ctx);
1149
+ return;
208
1150
  case "manage-projects":
209
- log.info("Manage existing projects");
210
- break;
1151
+ await manageProjects(ctx);
1152
+ return;
211
1153
  case "manage-branches":
212
- log.info("Manage existing branches");
213
- break;
1154
+ await manageBranches(defaultName, ctx);
1155
+ return;
214
1156
  }
215
1157
  }
216
1158
  function getFirstQuestion(defaultName, fromUser, environment, showMain) {
@@ -279,358 +1221,523 @@ async function initProject(defaultName, fromUser, ctx) {
279
1221
  answerFile = defaultBase.join(answerFile.path);
280
1222
  }
281
1223
  try {
282
- const canonical = answerFile.canonicalize();
283
- if (canonical) {
284
- answerFile = canonical;
285
- } else {
286
- answerFile = answerFile.absolutify();
1224
+ const canonical = answerFile.canonicalize();
1225
+ if (canonical) {
1226
+ answerFile = canonical;
1227
+ } else {
1228
+ answerFile = answerFile.absolutify();
1229
+ }
1230
+ } catch {
1231
+ answerFile = answerFile.absolutify();
1232
+ }
1233
+ basePath = answerFile.relativize(defaultBase.path).path;
1234
+ }
1235
+ const ide = await select({
1236
+ message: "What is the IDE?",
1237
+ choices: IDE_CHOICES
1238
+ });
1239
+ const selectedEvents = await checkbox({
1240
+ message: "Which events should take place when opening?",
1241
+ choices: [
1242
+ { name: "Change terminal cwd to project path", value: "cwd", checked: true },
1243
+ { name: "Open project in IDE", value: "ide", checked: true }
1244
+ ]
1245
+ });
1246
+ const events = {
1247
+ cwd: selectedEvents.includes("cwd"),
1248
+ ide: selectedEvents.includes("ide")
1249
+ };
1250
+ const projectConfig = {
1251
+ path: basePath,
1252
+ ide,
1253
+ events
1254
+ };
1255
+ projects[name] = projectConfig;
1256
+ config.set("projects", projects);
1257
+ log.info("Your project has been initialized.");
1258
+ log.info(`Use 'workon ${name}' to start working!`);
1259
+ }
1260
+ async function initBranch(defaultName, ctx) {
1261
+ const { config, log } = ctx;
1262
+ const projects = config.getProjects();
1263
+ const branch = await input4({
1264
+ message: "What is the name of the branch?",
1265
+ validate: (value) => {
1266
+ if (/\w+#\w+/.test(value)) return `Branch name can't contain the "#" sign`;
1267
+ if (`${defaultName}#${value}` in projects) return "Branch already exists.";
1268
+ return true;
1269
+ }
1270
+ });
1271
+ const branchName = `${defaultName}#${branch}`;
1272
+ const baseProject = projects[defaultName];
1273
+ const branchConfig = deepAssign2({}, baseProject, { branch });
1274
+ delete branchConfig.name;
1275
+ projects[branchName] = branchConfig;
1276
+ config.set("projects", projects);
1277
+ log.info("Your branch configuration has been initialized.");
1278
+ log.info(`Use 'workon ${branchName}' to start working!`);
1279
+ }
1280
+ async function switchProject(ctx) {
1281
+ const { config, log } = ctx;
1282
+ const projects = config.getProjects();
1283
+ const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
1284
+ if (baseProjects.length === 0) {
1285
+ log.info('No projects configured yet. Use "Start a new project" to create one.');
1286
+ return;
1287
+ }
1288
+ const projectName = await select({
1289
+ message: "Select a project to open:",
1290
+ choices: baseProjects.map((name) => ({
1291
+ name: `${name} (${projects[name].path})`,
1292
+ value: name
1293
+ }))
1294
+ });
1295
+ await openProject(projectName, ctx);
1296
+ }
1297
+ async function switchBranch(projectName, ctx) {
1298
+ const { config, log } = ctx;
1299
+ const projects = config.getProjects();
1300
+ const branchPrefix = `${projectName}#`;
1301
+ const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
1302
+ if (branches.length === 0) {
1303
+ log.info(`No branch configurations found for '${projectName}'.`);
1304
+ log.info('Use "Start a branch" to create one.');
1305
+ return;
1306
+ }
1307
+ const branchConfig = await select({
1308
+ message: "Select a branch configuration:",
1309
+ choices: branches.map((name) => ({
1310
+ name: name.substring(branchPrefix.length),
1311
+ value: name
1312
+ }))
1313
+ });
1314
+ await openProject(branchConfig, ctx);
1315
+ }
1316
+ async function manageProjects(ctx) {
1317
+ const { config } = ctx;
1318
+ await EventRegistry.initialize();
1319
+ const projects = config.getProjects();
1320
+ const hasProjects = Object.keys(projects).length > 0;
1321
+ const choices = [
1322
+ { name: "Create new project", value: "create" },
1323
+ ...hasProjects ? [
1324
+ { name: "Edit project", value: "edit" },
1325
+ { name: "Delete project", value: "delete" },
1326
+ { name: "List projects", value: "list" }
1327
+ ] : [],
1328
+ { name: "Back", value: "back" }
1329
+ ];
1330
+ const action = await select({
1331
+ message: "Manage projects:",
1332
+ choices
1333
+ });
1334
+ switch (action) {
1335
+ case "create":
1336
+ await createProjectManage(ctx);
1337
+ break;
1338
+ case "edit":
1339
+ await editProjectManage(ctx);
1340
+ break;
1341
+ case "delete":
1342
+ await deleteProjectManage(ctx);
1343
+ break;
1344
+ case "list":
1345
+ listProjectsManage(ctx);
1346
+ break;
1347
+ case "back":
1348
+ return;
1349
+ }
1350
+ await manageProjects(ctx);
1351
+ }
1352
+ async function manageBranches(projectName, ctx) {
1353
+ const { config } = ctx;
1354
+ const projects = config.getProjects();
1355
+ const branchPrefix = `${projectName}#`;
1356
+ const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
1357
+ const choices = [
1358
+ { name: "Create new branch config", value: "create" },
1359
+ ...branches.length > 0 ? [
1360
+ { name: "Edit branch config", value: "edit" },
1361
+ { name: "Delete branch config", value: "delete" },
1362
+ { name: "List branch configs", value: "list" }
1363
+ ] : [],
1364
+ { name: "Back", value: "back" }
1365
+ ];
1366
+ const action = await select({
1367
+ message: `Manage branches for '${projectName}':`,
1368
+ choices
1369
+ });
1370
+ switch (action) {
1371
+ case "create":
1372
+ await initBranch(projectName, ctx);
1373
+ break;
1374
+ case "edit":
1375
+ await editBranchManage(projectName, ctx);
1376
+ break;
1377
+ case "delete":
1378
+ await deleteBranchManage(projectName, ctx);
1379
+ break;
1380
+ case "list":
1381
+ listBranchesManage(projectName, ctx);
1382
+ break;
1383
+ case "back":
1384
+ return;
1385
+ }
1386
+ await manageBranches(projectName, ctx);
1387
+ }
1388
+ async function openProject(projectName, ctx) {
1389
+ const { config, log } = ctx;
1390
+ const projects = config.getProjects();
1391
+ if (!(projectName in projects)) {
1392
+ log.error(`Project '${projectName}' not found.`);
1393
+ return;
1394
+ }
1395
+ await EventRegistry.initialize();
1396
+ const projectConfig = projects[projectName];
1397
+ const projectCfg = { ...projectConfig, name: projectName };
1398
+ const projectEnv = ProjectEnvironment.load(projectCfg, config.getDefaults());
1399
+ log.info(`Opening project '${projectName}'...`);
1400
+ const events = Object.keys(projectConfig.events).filter(
1401
+ (e) => projectConfig.events[e]
1402
+ );
1403
+ for (const event of events) {
1404
+ const eventHandler = EventRegistry.getEventByName(event);
1405
+ if (eventHandler && eventHandler.processing) {
1406
+ await eventHandler.processing.processEvent({
1407
+ project: projectEnv.project,
1408
+ isShellMode: false,
1409
+ shellCommands: []
1410
+ });
1411
+ }
1412
+ }
1413
+ }
1414
+ async function createProjectManage(ctx) {
1415
+ const { config, log } = ctx;
1416
+ const defaults = config.getDefaults();
1417
+ const projects = config.getProjects();
1418
+ const name = await input4({
1419
+ message: "Project name:",
1420
+ validate: (value) => {
1421
+ if (!value.trim()) return "Name is required";
1422
+ if (!/^[\w-]+$/.test(value))
1423
+ return "Name can only contain letters, numbers, underscores, and hyphens";
1424
+ if (value in projects) return "Project already exists";
1425
+ return true;
1426
+ }
1427
+ });
1428
+ const defaultPath = defaults?.base ? File3.from(defaults.base).join(name).path : name;
1429
+ const pathInput = await input4({
1430
+ message: "Project path:",
1431
+ default: defaultPath
1432
+ });
1433
+ let relativePath = pathInput;
1434
+ if (defaults?.base) {
1435
+ const baseDir = File3.from(defaults.base);
1436
+ const pathFile = File3.from(pathInput);
1437
+ try {
1438
+ if (pathFile.isAbsolute()) {
1439
+ relativePath = pathFile.relativize(baseDir.path).path;
1440
+ }
1441
+ } catch {
1442
+ relativePath = pathInput;
1443
+ }
1444
+ }
1445
+ const ide = await select({
1446
+ message: "Select IDE:",
1447
+ choices: IDE_CHOICES
1448
+ });
1449
+ const availableEvents = EventRegistry.getEventsForManageUI();
1450
+ const selectedEvents = await checkbox({
1451
+ message: "Select events to enable:",
1452
+ choices: availableEvents.map((e) => ({
1453
+ name: `${e.name} - ${e.description}`,
1454
+ value: e.value,
1455
+ checked: e.value === "cwd" || e.value === "ide"
1456
+ }))
1457
+ });
1458
+ const events = {};
1459
+ for (const eventName of selectedEvents) {
1460
+ const eventHandler = EventRegistry.getEventByName(eventName);
1461
+ if (eventHandler) {
1462
+ const eventConfig = await eventHandler.configuration.configureInteractive();
1463
+ events[eventName] = eventConfig;
1464
+ }
1465
+ }
1466
+ const projectConfig = {
1467
+ path: relativePath,
1468
+ ide,
1469
+ events
1470
+ };
1471
+ const confirmed = await confirm3({
1472
+ message: "Save this project?",
1473
+ default: true
1474
+ });
1475
+ if (confirmed) {
1476
+ config.setProject(name, projectConfig);
1477
+ log.info(`Project '${name}' created successfully!`);
1478
+ }
1479
+ }
1480
+ async function editProjectManage(ctx) {
1481
+ const { config, log } = ctx;
1482
+ const projects = config.getProjects();
1483
+ const defaults = config.getDefaults();
1484
+ const baseProjects = Object.keys(projects).filter((name2) => !name2.includes("#"));
1485
+ if (baseProjects.length === 0) {
1486
+ log.info("No projects to edit.");
1487
+ return;
1488
+ }
1489
+ const name = await select({
1490
+ message: "Select project to edit:",
1491
+ choices: baseProjects.map((n) => ({ name: n, value: n }))
1492
+ });
1493
+ const project = projects[name];
1494
+ const pathInput = await input4({
1495
+ message: "Project path:",
1496
+ default: project.path
1497
+ });
1498
+ let relativePath = pathInput;
1499
+ if (defaults?.base) {
1500
+ const baseDir = File3.from(defaults.base);
1501
+ const pathFile = File3.from(pathInput);
1502
+ try {
1503
+ if (pathFile.isAbsolute()) {
1504
+ relativePath = pathFile.relativize(baseDir.path).path;
287
1505
  }
288
1506
  } catch {
289
- answerFile = answerFile.absolutify();
1507
+ relativePath = pathInput;
290
1508
  }
291
- basePath = answerFile.relativize(defaultBase.path).path;
292
1509
  }
293
1510
  const ide = await select({
294
- message: "What is the IDE?",
295
- choices: IDE_CHOICES
1511
+ message: "Select IDE:",
1512
+ choices: IDE_CHOICES,
1513
+ default: project.ide || "vscode"
296
1514
  });
297
- const selectedEvents = await checkbox({
298
- message: "Which events should take place when opening?",
299
- choices: [
300
- { name: "Change terminal cwd to project path", value: "cwd", checked: true },
301
- { name: "Open project in IDE", value: "ide", checked: true }
302
- ]
1515
+ const keepEvents = await confirm3({
1516
+ message: "Keep existing event configuration?",
1517
+ default: true
303
1518
  });
304
- const events = {
305
- cwd: selectedEvents.includes("cwd"),
306
- ide: selectedEvents.includes("ide")
307
- };
308
- const projectConfig = {
309
- path: basePath,
1519
+ let events = project.events;
1520
+ if (!keepEvents) {
1521
+ const availableEvents = EventRegistry.getEventsForManageUI();
1522
+ const currentEvents = Object.keys(project.events);
1523
+ const selectedEvents = await checkbox({
1524
+ message: "Select events to enable:",
1525
+ choices: availableEvents.map((e) => ({
1526
+ name: `${e.name} - ${e.description}`,
1527
+ value: e.value,
1528
+ checked: currentEvents.includes(e.value)
1529
+ }))
1530
+ });
1531
+ events = {};
1532
+ for (const eventName of selectedEvents) {
1533
+ if (project.events[eventName]) {
1534
+ events[eventName] = project.events[eventName];
1535
+ } else {
1536
+ const eventHandler = EventRegistry.getEventByName(eventName);
1537
+ if (eventHandler) {
1538
+ const eventConfig = await eventHandler.configuration.configureInteractive();
1539
+ events[eventName] = eventConfig;
1540
+ }
1541
+ }
1542
+ }
1543
+ }
1544
+ const updatedConfig = {
1545
+ path: relativePath,
310
1546
  ide,
311
1547
  events
312
1548
  };
313
- projects[name] = projectConfig;
314
- config.set("projects", projects);
315
- log.info("Your project has been initialized.");
316
- log.info(`Use 'workon ${name}' to start working!`);
1549
+ const confirmed = await confirm3({
1550
+ message: "Save changes?",
1551
+ default: true
1552
+ });
1553
+ if (confirmed) {
1554
+ config.setProject(name, updatedConfig);
1555
+ log.info(`Project '${name}' updated successfully!`);
1556
+ }
317
1557
  }
318
- async function initBranch(defaultName, ctx) {
1558
+ async function deleteProjectManage(ctx) {
319
1559
  const { config, log } = ctx;
320
1560
  const projects = config.getProjects();
321
- const branch = await input4({
322
- message: "What is the name of the branch?",
323
- validate: (value) => {
324
- if (/\w+#\w+/.test(value)) return `Branch name can't contain the "#" sign`;
325
- if (`${defaultName}#${value}` in projects) return "Branch already exists.";
326
- return true;
327
- }
328
- });
329
- const branchName = `${defaultName}#${branch}`;
330
- const baseProject = projects[defaultName];
331
- const branchConfig = deepAssign2({}, baseProject, { branch });
332
- delete branchConfig.name;
333
- projects[branchName] = branchConfig;
334
- config.set("projects", projects);
335
- log.info("Your branch configuration has been initialized.");
336
- log.info(`Use 'workon ${branchName}' to start working!`);
337
- }
338
- var IDE_CHOICES;
339
- var init_interactive = __esm({
340
- "src/commands/interactive.ts"() {
341
- "use strict";
342
- IDE_CHOICES = [
343
- { name: "Visual Studio Code", value: "vscode" },
344
- { name: "IntelliJ IDEA", value: "idea" },
345
- { name: "Atom", value: "atom" }
346
- ];
1561
+ const baseProjects = Object.keys(projects).filter((name2) => !name2.includes("#"));
1562
+ if (baseProjects.length === 0) {
1563
+ log.info("No projects to delete.");
1564
+ return;
347
1565
  }
348
- });
349
-
350
- // src/commands/index.ts
351
- import { Command as Command8 } from "commander";
352
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
353
- import { join, dirname } from "path";
354
- import { fileURLToPath } from "url";
355
- import loog from "loog";
356
- import omelette from "omelette";
357
- import File7 from "phylo";
358
-
359
- // src/lib/config.ts
360
- import Conf from "conf";
361
- var TRANSIENT_PROPS = ["pkg", "work"];
362
- var Config = class {
363
- _transient = {};
364
- _store;
365
- constructor() {
366
- this._store = new Conf({
367
- projectName: "workon"
1566
+ const name = await select({
1567
+ message: "Select project to delete:",
1568
+ choices: baseProjects.map((n) => ({ name: n, value: n }))
1569
+ });
1570
+ const branchPrefix = `${name}#`;
1571
+ const branches = Object.keys(projects).filter((n) => n.startsWith(branchPrefix));
1572
+ if (branches.length > 0) {
1573
+ log.warn(`This project has ${branches.length} branch configuration(s).`);
1574
+ const deleteAll = await confirm3({
1575
+ message: "Delete all branch configurations as well?",
1576
+ default: false
368
1577
  });
369
- }
370
- get(key, defaultValue) {
371
- const rootKey = key.split(".")[0];
372
- if (TRANSIENT_PROPS.includes(rootKey)) {
373
- return this._transient[key] ?? defaultValue;
374
- }
375
- return this._store.get(key, defaultValue);
376
- }
377
- set(key, value) {
378
- const rootKey = key.split(".")[0];
379
- if (TRANSIENT_PROPS.includes(rootKey)) {
380
- this._transient[key] = value;
381
- } else {
382
- if (value === void 0) {
383
- this._store.set(key, value);
384
- } else {
385
- this._store.set(key, value);
1578
+ if (deleteAll) {
1579
+ for (const branch of branches) {
1580
+ config.deleteProject(branch);
386
1581
  }
387
1582
  }
388
1583
  }
389
- has(key) {
390
- const rootKey = key.split(".")[0];
391
- if (TRANSIENT_PROPS.includes(rootKey)) {
392
- return Object.prototype.hasOwnProperty.call(this._transient, key);
393
- }
394
- return this._store.has(key);
395
- }
396
- delete(key) {
397
- const rootKey = key.split(".")[0];
398
- if (TRANSIENT_PROPS.includes(rootKey)) {
399
- delete this._transient[key];
400
- } else {
401
- this._store.delete(key);
402
- }
403
- }
404
- getProjects() {
405
- return this.get("projects") ?? {};
406
- }
407
- getProject(name) {
408
- const projects = this.getProjects();
409
- return projects[name];
410
- }
411
- setProject(name, config) {
412
- const projects = this.getProjects();
413
- projects[name] = config;
414
- this.set("projects", projects);
415
- }
416
- deleteProject(name) {
417
- const projects = this.getProjects();
418
- delete projects[name];
419
- this.set("projects", projects);
420
- }
421
- getDefaults() {
422
- return this.get("project_defaults");
423
- }
424
- setDefaults(defaults) {
425
- this.set("project_defaults", defaults);
426
- }
427
- get path() {
428
- return this._store.path;
429
- }
430
- get store() {
431
- return this._store.store;
1584
+ const confirmed = await confirm3({
1585
+ message: `Are you sure you want to delete '${name}'?`,
1586
+ default: false
1587
+ });
1588
+ if (confirmed) {
1589
+ config.deleteProject(name);
1590
+ log.info(`Project '${name}' deleted.`);
432
1591
  }
433
- };
434
-
435
- // src/lib/environment.ts
436
- import File2 from "phylo";
437
- import { simpleGit } from "simple-git";
438
-
439
- // src/lib/project.ts
440
- import File from "phylo";
441
- import deepAssign from "deep-assign";
442
- var Project = class {
443
- name;
444
- _base;
445
- _path;
446
- _ide;
447
- _events = {};
448
- _branch;
449
- _homepage;
450
- _defaults;
451
- _initialCfg;
452
- constructor(name, cfg, defaults) {
453
- this._defaults = defaults ?? { base: "" };
454
- this._initialCfg = { path: name, events: {}, ...cfg };
455
- this.name = cfg?.name ?? name;
456
- const merged = deepAssign({}, this._defaults, this._initialCfg);
457
- if (merged.base) {
458
- this.base = merged.base;
459
- }
460
- if (merged.path) {
461
- this.path = merged.path;
462
- }
463
- if (merged.ide) {
464
- this._ide = merged.ide;
465
- }
466
- if (merged.events) {
467
- this._events = merged.events;
468
- }
469
- if (merged.branch) {
470
- this._branch = merged.branch;
471
- }
472
- if (merged.homepage) {
473
- this._homepage = merged.homepage;
1592
+ }
1593
+ function listProjectsManage(ctx) {
1594
+ const { config } = ctx;
1595
+ const projects = config.getProjects();
1596
+ const defaults = config.getDefaults();
1597
+ console.log("\nConfigured projects:\n");
1598
+ const baseProjects = Object.keys(projects).filter((name) => !name.includes("#"));
1599
+ for (const name of baseProjects) {
1600
+ const project = projects[name];
1601
+ const fullPath = defaults?.base ? File3.from(defaults.base).join(project.path).path : project.path;
1602
+ console.log(` ${name}`);
1603
+ console.log(` Path: ${fullPath}`);
1604
+ console.log(` IDE: ${project.ide || "not set"}`);
1605
+ console.log(` Events: ${Object.keys(project.events).join(", ") || "none"}`);
1606
+ const branchPrefix = `${name}#`;
1607
+ const branches = Object.keys(projects).filter((n) => n.startsWith(branchPrefix));
1608
+ if (branches.length > 0) {
1609
+ console.log(` Branches: ${branches.length}`);
474
1610
  }
1611
+ console.log();
475
1612
  }
476
- set base(path) {
477
- this._base = File.from(path).absolutify();
478
- }
479
- get base() {
480
- return this._base;
481
- }
482
- set ide(cmd) {
483
- this._ide = cmd;
484
- }
485
- get ide() {
486
- return this._ide;
487
- }
488
- set events(eventCfg) {
489
- this._events = eventCfg;
490
- }
491
- get events() {
492
- return this._events;
493
- }
494
- set path(path) {
495
- if (this._base) {
496
- this._path = this._base.join(path);
497
- } else {
498
- this._path = File.from(path);
499
- }
500
- this._path = this._path.absolutify();
1613
+ }
1614
+ async function editBranchManage(projectName, ctx) {
1615
+ const { config, log } = ctx;
1616
+ const projects = config.getProjects();
1617
+ const branchPrefix = `${projectName}#`;
1618
+ const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
1619
+ if (branches.length === 0) {
1620
+ log.info("No branch configurations to edit.");
1621
+ return;
501
1622
  }
502
- get path() {
503
- if (!this._path) {
504
- throw new Error("Project path not set");
1623
+ const branchName = await select({
1624
+ message: "Select branch configuration to edit:",
1625
+ choices: branches.map((n) => ({
1626
+ name: n.substring(branchPrefix.length),
1627
+ value: n
1628
+ }))
1629
+ });
1630
+ const branch = projects[branchName];
1631
+ const keepEvents = await confirm3({
1632
+ message: "Keep existing event configuration?",
1633
+ default: true
1634
+ });
1635
+ let events = branch.events;
1636
+ if (!keepEvents) {
1637
+ const availableEvents = EventRegistry.getEventsForManageUI();
1638
+ const currentEvents = Object.keys(branch.events);
1639
+ const selectedEvents = await checkbox({
1640
+ message: "Select events to enable:",
1641
+ choices: availableEvents.map((e) => ({
1642
+ name: `${e.name} - ${e.description}`,
1643
+ value: e.value,
1644
+ checked: currentEvents.includes(e.value)
1645
+ }))
1646
+ });
1647
+ events = {};
1648
+ for (const eventName of selectedEvents) {
1649
+ if (branch.events[eventName]) {
1650
+ events[eventName] = branch.events[eventName];
1651
+ } else {
1652
+ const eventHandler = EventRegistry.getEventByName(eventName);
1653
+ if (eventHandler) {
1654
+ const eventConfig = await eventHandler.configuration.configureInteractive();
1655
+ events[eventName] = eventConfig;
1656
+ }
1657
+ }
505
1658
  }
506
- return this._path;
507
- }
508
- set branch(branch) {
509
- this._branch = branch;
510
- }
511
- get branch() {
512
- return this._branch;
513
1659
  }
514
- set homepage(url) {
515
- this._homepage = url;
516
- }
517
- get homepage() {
518
- return this._homepage;
519
- }
520
- static $isProject = true;
521
- $isProject = true;
522
- };
523
-
524
- // src/lib/environment.ts
525
- var BaseEnvironment = class {
526
- $isProjectEnvironment = false;
527
- };
528
- var ProjectEnvironment = class _ProjectEnvironment {
529
- $isProjectEnvironment = true;
530
- project;
531
- constructor(projectCfg) {
532
- this.project = new Project(projectCfg.name, projectCfg);
1660
+ const updatedConfig = {
1661
+ ...branch,
1662
+ events
1663
+ };
1664
+ const confirmed = await confirm3({
1665
+ message: "Save changes?",
1666
+ default: true
1667
+ });
1668
+ if (confirmed) {
1669
+ config.setProject(branchName, updatedConfig);
1670
+ log.info(`Branch configuration '${branchName}' updated successfully!`);
533
1671
  }
534
- static load(cfg, defaults) {
535
- const project = new Project(cfg.name, cfg, defaults);
536
- return new _ProjectEnvironment({ ...cfg, name: project.name });
1672
+ }
1673
+ async function deleteBranchManage(projectName, ctx) {
1674
+ const { config, log } = ctx;
1675
+ const projects = config.getProjects();
1676
+ const branchPrefix = `${projectName}#`;
1677
+ const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
1678
+ if (branches.length === 0) {
1679
+ log.info("No branch configurations to delete.");
1680
+ return;
537
1681
  }
538
- };
539
- var EnvironmentRecognizer = class {
540
- static config;
541
- static log;
542
- static projects = [];
543
- static configured = false;
544
- static configure(config, log) {
545
- if (this.configured) {
546
- return;
547
- }
548
- this.config = config;
549
- this.log = log;
550
- this.configured = true;
551
- }
552
- static async recognize(dir) {
553
- this.ensureConfigured();
554
- const theDir = File2.from(dir).canonicalize();
555
- this.log.debug("Directory to recognize is: " + theDir.canonicalPath());
556
- const allProjects = this.getAllProjects();
557
- const matching = allProjects.filter((p) => p.path.canonicalPath() === theDir.path);
558
- if (matching.length === 0) {
559
- return new BaseEnvironment();
560
- }
561
- this.log.debug(`Found ${matching.length} matching projects`);
562
- const base = matching.find((p) => !p.name.includes("#")) ?? matching[0];
563
- this.log.debug("Base project is: " + base.name);
564
- const gitDir = base.path.up(".git");
565
- if (gitDir) {
566
- try {
567
- const git = simpleGit(gitDir.path);
568
- const branchSummary = await git.branchLocal();
569
- base.branch = branchSummary.current;
570
- } catch {
571
- }
572
- }
573
- return this.getProjectEnvironment(base, matching);
1682
+ const branchName = await select({
1683
+ message: "Select branch configuration to delete:",
1684
+ choices: branches.map((n) => ({
1685
+ name: n.substring(branchPrefix.length),
1686
+ value: n
1687
+ }))
1688
+ });
1689
+ const confirmed = await confirm3({
1690
+ message: `Are you sure you want to delete '${branchName}'?`,
1691
+ default: false
1692
+ });
1693
+ if (confirmed) {
1694
+ config.deleteProject(branchName);
1695
+ log.info(`Branch configuration '${branchName}' deleted.`);
574
1696
  }
575
- static getAllProjects(refresh = false) {
576
- if (this.projects.length > 0 && !refresh) {
577
- return this.projects;
578
- }
579
- const defaults = this.config.getDefaults();
580
- if (!defaults?.base) {
581
- this.projects = [];
582
- return this.projects;
583
- }
584
- const baseDir = File2.from(defaults.base);
585
- const projectsMap = this.config.getProjects();
586
- this.projects = Object.entries(projectsMap).map(([name, project]) => ({
587
- ...project,
588
- name,
589
- path: baseDir.join(project.path)
590
- }));
591
- return this.projects;
592
- }
593
- static getProjectEnvironment(base, _matching) {
594
- const exactName = `${base.name}#${base.branch}`;
595
- const exactProj = this.projects.find((p) => p.name === exactName);
596
- const toProjectConfig = (p) => ({
597
- name: p.name,
598
- path: p.path.path,
599
- // Convert PhyloFile to string path
600
- ide: p.ide,
601
- homepage: p.homepage,
602
- events: p.events,
603
- branch: p.branch,
604
- exactName
605
- });
606
- if (exactProj) {
607
- return new ProjectEnvironment({ ...toProjectConfig(exactProj), branch: base.branch });
608
- }
609
- return new ProjectEnvironment(toProjectConfig(base));
610
- }
611
- static ensureConfigured() {
612
- if (!this.configured) {
613
- this.config = new Config();
614
- this.log = {
615
- debug: () => {
616
- },
617
- info: () => {
618
- },
619
- log: () => {
620
- },
621
- warn: () => {
622
- },
623
- error: () => {
624
- },
625
- setLogLevel: () => {
626
- }
627
- };
628
- this.configured = true;
629
- }
1697
+ }
1698
+ function listBranchesManage(projectName, ctx) {
1699
+ const { config } = ctx;
1700
+ const projects = config.getProjects();
1701
+ const branchPrefix = `${projectName}#`;
1702
+ const branches = Object.keys(projects).filter((name) => name.startsWith(branchPrefix));
1703
+ console.log(`
1704
+ Branch configurations for '${projectName}':
1705
+ `);
1706
+ for (const branchName of branches) {
1707
+ const branch = projects[branchName];
1708
+ const shortName = branchName.substring(branchPrefix.length);
1709
+ console.log(` ${shortName}`);
1710
+ console.log(` Events: ${Object.keys(branch.events).join(", ") || "none"}`);
1711
+ console.log();
630
1712
  }
631
- };
1713
+ }
1714
+ var IDE_CHOICES;
1715
+ var init_interactive = __esm({
1716
+ "src/commands/interactive.ts"() {
1717
+ "use strict";
1718
+ init_environment();
1719
+ init_registry();
1720
+ IDE_CHOICES = [
1721
+ { name: "Visual Studio Code", value: "vscode" },
1722
+ { name: "IntelliJ IDEA", value: "idea" },
1723
+ { name: "Atom", value: "atom" }
1724
+ ];
1725
+ }
1726
+ });
1727
+
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";
632
1738
 
633
1739
  // src/commands/open.ts
1740
+ init_environment();
634
1741
  import { Command } from "commander";
635
1742
  import File4 from "phylo";
636
1743
 
@@ -782,616 +1889,8 @@ var TmuxManager = class {
782
1889
  }
783
1890
  };
784
1891
 
785
- // src/events/core/cwd.ts
786
- import { spawn as spawn2 } from "child_process";
787
- var CwdEvent = class {
788
- static get metadata() {
789
- return {
790
- name: "cwd",
791
- displayName: "Change directory (cwd)",
792
- description: "Change current working directory to project path",
793
- category: "core",
794
- requiresTmux: false,
795
- dependencies: []
796
- };
797
- }
798
- static get validation() {
799
- return {
800
- validateConfig(config) {
801
- if (typeof config === "boolean" || config === "true" || config === "false") {
802
- return true;
803
- }
804
- return "cwd config must be a boolean (true/false)";
805
- }
806
- };
807
- }
808
- static get configuration() {
809
- return {
810
- async configureInteractive() {
811
- return true;
812
- },
813
- getDefaultConfig() {
814
- return true;
815
- }
816
- };
817
- }
818
- static get processing() {
819
- return {
820
- async processEvent(context) {
821
- const { project, isShellMode, shellCommands } = context;
822
- const projectPath = project.path.path;
823
- if (isShellMode) {
824
- shellCommands.push(`cd "${projectPath}"`);
825
- } else {
826
- const shell = process.env.SHELL || "/bin/bash";
827
- spawn2(shell, [], {
828
- cwd: projectPath,
829
- stdio: "inherit"
830
- });
831
- }
832
- },
833
- generateShellCommand(context) {
834
- const projectPath = context.project.path.path;
835
- return [`cd "${projectPath}"`];
836
- }
837
- };
838
- }
839
- static get tmux() {
840
- return null;
841
- }
842
- static get help() {
843
- return {
844
- usage: "cwd: true | false",
845
- description: "Change the current working directory to the project path",
846
- examples: [
847
- { config: true, description: "Enable directory change" },
848
- { config: false, description: "Disable directory change" }
849
- ]
850
- };
851
- }
852
- };
853
-
854
- // src/events/core/ide.ts
855
- import { spawn as spawn3 } from "child_process";
856
- var IdeEvent = class {
857
- static get metadata() {
858
- return {
859
- name: "ide",
860
- displayName: "Open in IDE",
861
- description: "Open project in configured IDE/editor",
862
- category: "core",
863
- requiresTmux: false,
864
- dependencies: []
865
- };
866
- }
867
- static get validation() {
868
- return {
869
- validateConfig(config) {
870
- if (typeof config === "boolean" || config === "true" || config === "false") {
871
- return true;
872
- }
873
- return "ide config must be a boolean (true/false)";
874
- }
875
- };
876
- }
877
- static get configuration() {
878
- return {
879
- async configureInteractive() {
880
- return true;
881
- },
882
- getDefaultConfig() {
883
- return true;
884
- }
885
- };
886
- }
887
- static get processing() {
888
- return {
889
- async processEvent(context) {
890
- const { project, isShellMode, shellCommands } = context;
891
- const projectPath = project.path.path;
892
- const ide = project.ide || "code";
893
- if (isShellMode) {
894
- shellCommands.push(`${ide} "${projectPath}" &`);
895
- } else {
896
- spawn3(ide, [projectPath], {
897
- detached: true,
898
- stdio: "ignore"
899
- }).unref();
900
- }
901
- },
902
- generateShellCommand(context) {
903
- const projectPath = context.project.path.path;
904
- const ide = context.project.ide || "code";
905
- return [`${ide} "${projectPath}" &`];
906
- }
907
- };
908
- }
909
- static get tmux() {
910
- return null;
911
- }
912
- static get help() {
913
- return {
914
- usage: "ide: true | false",
915
- description: "Open the project in the configured IDE",
916
- examples: [
917
- { config: true, description: "Enable IDE opening" },
918
- { config: false, description: "Disable IDE opening" }
919
- ]
920
- };
921
- }
922
- };
923
-
924
- // src/events/core/web.ts
925
- import { spawn as spawn4 } from "child_process";
926
- import { platform } from "os";
927
- var WebEvent = class _WebEvent {
928
- static get metadata() {
929
- return {
930
- name: "web",
931
- displayName: "Open homepage in browser",
932
- description: "Open project homepage in web browser",
933
- category: "core",
934
- requiresTmux: false,
935
- dependencies: []
936
- };
937
- }
938
- static get validation() {
939
- return {
940
- validateConfig(config) {
941
- if (typeof config === "boolean" || config === "true" || config === "false") {
942
- return true;
943
- }
944
- return "web config must be a boolean (true/false)";
945
- }
946
- };
947
- }
948
- static get configuration() {
949
- return {
950
- async configureInteractive() {
951
- return true;
952
- },
953
- getDefaultConfig() {
954
- return true;
955
- }
956
- };
957
- }
958
- static getOpenCommand() {
959
- const os = platform();
960
- switch (os) {
961
- case "darwin":
962
- return "open";
963
- case "win32":
964
- return "start";
965
- default:
966
- return "xdg-open";
967
- }
968
- }
969
- static get processing() {
970
- return {
971
- async processEvent(context) {
972
- const { project, isShellMode, shellCommands } = context;
973
- const homepage = project.homepage;
974
- if (!homepage) {
975
- console.warn("No homepage configured for project");
976
- return;
977
- }
978
- const openCmd = _WebEvent.getOpenCommand();
979
- if (isShellMode) {
980
- shellCommands.push(`${openCmd} "${homepage}" &`);
981
- } else {
982
- spawn4(openCmd, [homepage], {
983
- detached: true,
984
- stdio: "ignore"
985
- }).unref();
986
- }
987
- },
988
- generateShellCommand(context) {
989
- const homepage = context.project.homepage;
990
- if (!homepage) return [];
991
- const openCmd = _WebEvent.getOpenCommand();
992
- return [`${openCmd} "${homepage}" &`];
993
- }
994
- };
995
- }
996
- static get tmux() {
997
- return null;
998
- }
999
- static get help() {
1000
- return {
1001
- usage: "web: true | false",
1002
- description: "Open the project homepage in the default browser",
1003
- examples: [
1004
- { config: true, description: "Enable browser opening" },
1005
- { config: false, description: "Disable browser opening" }
1006
- ]
1007
- };
1008
- }
1009
- };
1010
-
1011
- // src/events/extensions/claude.ts
1012
- import { spawn as spawn5 } from "child_process";
1013
- import { input, confirm } from "@inquirer/prompts";
1014
- var ClaudeEvent = class _ClaudeEvent {
1015
- static get metadata() {
1016
- return {
1017
- name: "claude",
1018
- displayName: "Launch Claude Code",
1019
- description: "Launch Claude Code with optional flags and configuration",
1020
- category: "development",
1021
- requiresTmux: true,
1022
- dependencies: ["claude"]
1023
- };
1024
- }
1025
- static get validation() {
1026
- return {
1027
- validateConfig(config) {
1028
- if (typeof config === "boolean" || config === "true" || config === "false") {
1029
- return true;
1030
- }
1031
- if (typeof config === "object" && config !== null) {
1032
- const cfg = config;
1033
- if (cfg.flags !== void 0) {
1034
- if (!Array.isArray(cfg.flags)) {
1035
- return "claude.flags must be an array of strings";
1036
- }
1037
- for (const flag of cfg.flags) {
1038
- if (typeof flag !== "string") {
1039
- return "claude.flags must contain only strings";
1040
- }
1041
- if (!flag.startsWith("-")) {
1042
- return `Invalid flag "${flag}": flags must start with - or --`;
1043
- }
1044
- }
1045
- }
1046
- if (cfg.split_terminal !== void 0 && typeof cfg.split_terminal !== "boolean") {
1047
- return "claude.split_terminal must be a boolean";
1048
- }
1049
- return true;
1050
- }
1051
- return "claude config must be a boolean or object with flags/split_terminal";
1052
- }
1053
- };
1054
- }
1055
- static get configuration() {
1056
- return {
1057
- async configureInteractive() {
1058
- const useAdvanced = await confirm({
1059
- message: "Configure advanced Claude options?",
1060
- default: false
1061
- });
1062
- if (!useAdvanced) {
1063
- return true;
1064
- }
1065
- const flagsInput = await input({
1066
- message: "Enter Claude flags (comma-separated, e.g., --resume, --debug):",
1067
- default: ""
1068
- });
1069
- const flags = flagsInput.split(",").map((f) => f.trim()).filter((f) => f.length > 0 && f.startsWith("-"));
1070
- const splitTerminal = await confirm({
1071
- message: "Use split terminal layout (Claude + shell)?",
1072
- default: true
1073
- });
1074
- if (flags.length === 0 && !splitTerminal) {
1075
- return true;
1076
- }
1077
- const config = {};
1078
- if (flags.length > 0) config.flags = flags;
1079
- if (splitTerminal) config.split_terminal = splitTerminal;
1080
- return config;
1081
- },
1082
- getDefaultConfig() {
1083
- return true;
1084
- }
1085
- };
1086
- }
1087
- static getClaudeCommand(config) {
1088
- if (typeof config === "boolean" || config === void 0) {
1089
- return "claude";
1090
- }
1091
- const flags = config.flags || [];
1092
- return flags.length > 0 ? `claude ${flags.join(" ")}` : "claude";
1093
- }
1094
- static get processing() {
1095
- return {
1096
- async processEvent(context) {
1097
- const { project, isShellMode, shellCommands } = context;
1098
- const claudeConfig = project.events.claude;
1099
- const claudeCommand = _ClaudeEvent.getClaudeCommand(claudeConfig);
1100
- if (isShellMode) {
1101
- shellCommands.push(claudeCommand);
1102
- } else {
1103
- const args = claudeCommand.split(" ").slice(1);
1104
- spawn5("claude", args, {
1105
- cwd: project.path.path,
1106
- stdio: "inherit"
1107
- });
1108
- }
1109
- },
1110
- generateShellCommand(context) {
1111
- const claudeConfig = context.project.events.claude;
1112
- return [_ClaudeEvent.getClaudeCommand(claudeConfig)];
1113
- }
1114
- };
1115
- }
1116
- static get tmux() {
1117
- return {
1118
- getLayoutPriority() {
1119
- return 100;
1120
- },
1121
- contributeToLayout(enabledCommands) {
1122
- if (enabledCommands.includes("npm")) {
1123
- return "three-pane";
1124
- }
1125
- return "split";
1126
- }
1127
- };
1128
- }
1129
- static get help() {
1130
- return {
1131
- usage: "claude: true | { flags: string[], split_terminal: boolean }",
1132
- description: "Launch Claude Code in the project directory",
1133
- examples: [
1134
- { config: true, description: "Launch Claude with defaults" },
1135
- { config: { flags: ["--resume"] }, description: "Resume previous session" },
1136
- {
1137
- config: { flags: ["--model", "opus"], split_terminal: true },
1138
- description: "Use Opus model with split terminal"
1139
- }
1140
- ]
1141
- };
1142
- }
1143
- };
1144
-
1145
- // src/events/extensions/docker.ts
1146
- import { spawn as spawn6 } from "child_process";
1147
- import { input as input2 } from "@inquirer/prompts";
1148
- var DockerEvent = class _DockerEvent {
1149
- static get metadata() {
1150
- return {
1151
- name: "docker",
1152
- displayName: "Docker container management",
1153
- description: "Start/stop Docker containers for the project",
1154
- category: "development",
1155
- requiresTmux: false,
1156
- dependencies: ["docker"]
1157
- };
1158
- }
1159
- static get validation() {
1160
- return {
1161
- validateConfig(config) {
1162
- if (typeof config === "boolean" || config === "true" || config === "false") {
1163
- return true;
1164
- }
1165
- if (typeof config === "string") {
1166
- return true;
1167
- }
1168
- if (typeof config === "object" && config !== null) {
1169
- const cfg = config;
1170
- if (cfg.compose_file !== void 0 && typeof cfg.compose_file !== "string") {
1171
- return "docker.compose_file must be a string";
1172
- }
1173
- if (cfg.services !== void 0) {
1174
- if (!Array.isArray(cfg.services)) {
1175
- return "docker.services must be an array";
1176
- }
1177
- for (const service of cfg.services) {
1178
- if (typeof service !== "string") {
1179
- return "docker.services must contain only strings";
1180
- }
1181
- }
1182
- }
1183
- return true;
1184
- }
1185
- return "docker config must be a boolean, string (compose file), or object";
1186
- }
1187
- };
1188
- }
1189
- static get configuration() {
1190
- return {
1191
- async configureInteractive() {
1192
- const composeFile = await input2({
1193
- message: "Enter docker-compose file path:",
1194
- default: "docker-compose.yml"
1195
- });
1196
- const servicesInput = await input2({
1197
- message: "Enter services to start (comma-separated, leave empty for all):",
1198
- default: ""
1199
- });
1200
- const services = servicesInput.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1201
- if (composeFile === "docker-compose.yml" && services.length === 0) {
1202
- return { compose_file: "docker-compose.yml" };
1203
- }
1204
- if (services.length === 0) {
1205
- return composeFile;
1206
- }
1207
- return {
1208
- compose_file: composeFile,
1209
- services
1210
- };
1211
- },
1212
- getDefaultConfig() {
1213
- return { compose_file: "docker-compose.yml" };
1214
- }
1215
- };
1216
- }
1217
- static getDockerCommand(config) {
1218
- if (typeof config === "boolean" || config === void 0) {
1219
- return "docker-compose up -d";
1220
- }
1221
- if (typeof config === "string") {
1222
- return `docker-compose -f ${config} up -d`;
1223
- }
1224
- const composeFile = config.compose_file || "docker-compose.yml";
1225
- const services = config.services?.join(" ") || "";
1226
- return `docker-compose -f ${composeFile} up -d ${services}`.trim();
1227
- }
1228
- static get processing() {
1229
- return {
1230
- async processEvent(context) {
1231
- const { project, isShellMode, shellCommands } = context;
1232
- const dockerConfig = project.events.docker;
1233
- const dockerCommand = _DockerEvent.getDockerCommand(
1234
- dockerConfig
1235
- );
1236
- if (isShellMode) {
1237
- shellCommands.push(dockerCommand);
1238
- } else {
1239
- const [cmd, ...args] = dockerCommand.split(" ");
1240
- spawn6(cmd, args, {
1241
- cwd: project.path.path,
1242
- stdio: "inherit"
1243
- });
1244
- }
1245
- },
1246
- generateShellCommand(context) {
1247
- const dockerConfig = context.project.events.docker;
1248
- return [_DockerEvent.getDockerCommand(dockerConfig)];
1249
- }
1250
- };
1251
- }
1252
- static get tmux() {
1253
- return null;
1254
- }
1255
- static get help() {
1256
- return {
1257
- usage: 'docker: true | "compose-file.yml" | { compose_file: string, services?: string[] }',
1258
- description: "Start Docker containers for the project",
1259
- examples: [
1260
- { config: true, description: "Use default docker-compose.yml" },
1261
- { config: "docker-compose.dev.yml", description: "Use custom compose file" },
1262
- {
1263
- config: { compose_file: "docker-compose.yml", services: ["web", "db"] },
1264
- description: "Start specific services"
1265
- }
1266
- ]
1267
- };
1268
- }
1269
- };
1270
-
1271
- // src/events/registry.ts
1272
- init_npm();
1273
- var ALL_EVENTS = [CwdEvent, IdeEvent, WebEvent, ClaudeEvent, DockerEvent, NpmEvent];
1274
- var EventRegistryClass = class {
1275
- _events = /* @__PURE__ */ new Map();
1276
- _initialized = false;
1277
- /**
1278
- * Initialize the registry by registering all events
1279
- */
1280
- async initialize() {
1281
- if (this._initialized) return;
1282
- this.registerEvents();
1283
- this._initialized = true;
1284
- }
1285
- /**
1286
- * Register all event classes
1287
- */
1288
- registerEvents() {
1289
- for (const EventClass of ALL_EVENTS) {
1290
- if (this.isValidEvent(EventClass)) {
1291
- const metadata = EventClass.metadata;
1292
- this._events.set(metadata.name, EventClass);
1293
- }
1294
- }
1295
- }
1296
- /**
1297
- * Validate if a class is a proper event
1298
- */
1299
- isValidEvent(EventClass) {
1300
- try {
1301
- if (typeof EventClass !== "function") return false;
1302
- const metadata = EventClass.metadata;
1303
- return metadata !== void 0 && typeof metadata.name === "string" && typeof metadata.displayName === "string" && typeof EventClass.validation === "object" && typeof EventClass.configuration === "object" && typeof EventClass.processing === "object";
1304
- } catch {
1305
- return false;
1306
- }
1307
- }
1308
- /**
1309
- * Get all valid event names from registered events
1310
- */
1311
- getValidEventNames() {
1312
- this.ensureInitialized();
1313
- return Array.from(this._events.keys());
1314
- }
1315
- /**
1316
- * Get event by name
1317
- */
1318
- getEventByName(name) {
1319
- this.ensureInitialized();
1320
- return this._events.get(name) ?? null;
1321
- }
1322
- /**
1323
- * Get all events for management UI
1324
- */
1325
- getEventsForManageUI() {
1326
- this.ensureInitialized();
1327
- const events = [];
1328
- for (const [name, EventClass] of this._events) {
1329
- const metadata = EventClass.metadata;
1330
- events.push({
1331
- name: metadata.displayName,
1332
- value: name,
1333
- description: metadata.description
1334
- });
1335
- }
1336
- return events.sort((a, b) => a.name.localeCompare(b.name));
1337
- }
1338
- /**
1339
- * Get events that support tmux integration
1340
- */
1341
- getTmuxEnabledEvents() {
1342
- this.ensureInitialized();
1343
- const tmuxEvents = [];
1344
- for (const [name, EventClass] of this._events) {
1345
- const tmux = EventClass.tmux;
1346
- if (tmux) {
1347
- tmuxEvents.push({
1348
- name,
1349
- event: EventClass,
1350
- priority: tmux.getLayoutPriority ? tmux.getLayoutPriority() : 0
1351
- });
1352
- }
1353
- }
1354
- return tmuxEvents.sort((a, b) => b.priority - a.priority);
1355
- }
1356
- /**
1357
- * Get all available events with their metadata
1358
- */
1359
- getAllEvents() {
1360
- this.ensureInitialized();
1361
- const events = [];
1362
- for (const [name, EventClass] of this._events) {
1363
- const typedClass = EventClass;
1364
- events.push({
1365
- name,
1366
- metadata: typedClass.metadata,
1367
- hasValidation: !!typedClass.validation,
1368
- hasConfiguration: !!typedClass.configuration,
1369
- hasProcessing: !!typedClass.processing,
1370
- hasTmux: !!typedClass.tmux,
1371
- hasHelp: !!typedClass.help
1372
- });
1373
- }
1374
- return events;
1375
- }
1376
- /**
1377
- * Ensure registry is initialized
1378
- */
1379
- ensureInitialized() {
1380
- if (!this._initialized) {
1381
- throw new Error("EventRegistry must be initialized before use. Call initialize() first.");
1382
- }
1383
- }
1384
- /**
1385
- * Clear the registry (useful for testing)
1386
- */
1387
- clear() {
1388
- this._events.clear();
1389
- this._initialized = false;
1390
- }
1391
- };
1392
- var EventRegistry = new EventRegistryClass();
1393
-
1394
1892
  // src/commands/open.ts
1893
+ init_registry();
1395
1894
  function createOpenCommand(ctx) {
1396
1895
  const { config, log } = ctx;
1397
1896
  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) => {
@@ -1806,8 +2305,9 @@ function createConfigCommand(ctx) {
1806
2305
  }
1807
2306
 
1808
2307
  // src/commands/manage.ts
2308
+ init_registry();
1809
2309
  import { Command as Command6 } from "commander";
1810
- import { select as select2, input as input5, confirm as confirm3, checkbox as checkbox2 } from "@inquirer/prompts";
2310
+ import { select as select2, input as input5, confirm as confirm4, checkbox as checkbox2 } from "@inquirer/prompts";
1811
2311
  import File5 from "phylo";
1812
2312
  var IDE_CHOICES2 = [
1813
2313
  { name: "Visual Studio Code", value: "vscode" },
@@ -1939,7 +2439,7 @@ async function createProject(ctx) {
1939
2439
  }
1940
2440
  console.log("\nProject configuration:");
1941
2441
  console.log(JSON.stringify(projectConfig, null, 2));
1942
- const confirmed = await confirm3({
2442
+ const confirmed = await confirm4({
1943
2443
  message: "Save this project?",
1944
2444
  default: true
1945
2445
  });
@@ -1990,7 +2490,7 @@ async function editProject(ctx) {
1990
2490
  message: "Project homepage URL:",
1991
2491
  default: project.homepage || ""
1992
2492
  });
1993
- const keepEvents = await confirm3({
2493
+ const keepEvents = await confirm4({
1994
2494
  message: "Keep existing event configuration?",
1995
2495
  default: true
1996
2496
  });
@@ -2029,7 +2529,7 @@ async function editProject(ctx) {
2029
2529
  }
2030
2530
  console.log("\nUpdated configuration:");
2031
2531
  console.log(JSON.stringify(updatedConfig, null, 2));
2032
- const confirmed = await confirm3({
2532
+ const confirmed = await confirm4({
2033
2533
  message: "Save changes?",
2034
2534
  default: true
2035
2535
  });
@@ -2052,7 +2552,7 @@ async function deleteProject(ctx) {
2052
2552
  message: "Select project to delete:",
2053
2553
  choices: projectNames.map((n) => ({ name: n, value: n }))
2054
2554
  });
2055
- const confirmed = await confirm3({
2555
+ const confirmed = await confirm4({
2056
2556
  message: `Are you sure you want to delete '${name}'?`,
2057
2557
  default: false
2058
2558
  });
@@ -2086,7 +2586,7 @@ import { Command as Command7 } from "commander";
2086
2586
  import { existsSync, readFileSync } from "fs";
2087
2587
  import { basename, resolve } from "path";
2088
2588
  import File6 from "phylo";
2089
- import { confirm as confirm4 } from "@inquirer/prompts";
2589
+ import { confirm as confirm5 } from "@inquirer/prompts";
2090
2590
  function createAddCommand(ctx) {
2091
2591
  const { log } = ctx;
2092
2592
  return new Command7("add").description("Add a project from a directory path").argument("[path]", "Path to the project directory (defaults to current directory)", ".").option("-d, --debug", "Enable debug logging").option("-n, --name <name>", "Override the detected project name").option(
@@ -2130,7 +2630,7 @@ async function addProject(pathArg, options, ctx) {
2130
2630
  process.exit(1);
2131
2631
  }
2132
2632
  if (projectName in projects && !options.force) {
2133
- const overwrite = await confirm4({
2633
+ const overwrite = await confirm5({
2134
2634
  message: `Project '${projectName}' already exists. Overwrite?`,
2135
2635
  default: false
2136
2636
  });
@@ -2245,32 +2745,41 @@ function createCli() {
2245
2745
  config.set("pkg", packageJson);
2246
2746
  EnvironmentRecognizer.configure(config, log);
2247
2747
  const completion = setupCompletion(config);
2248
- program2.name("workon").description("Work on something great!").version(packageJson.version).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) => {
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) => {
2249
2749
  const opts = thisCommand.opts();
2250
2750
  if (opts.debug) {
2251
2751
  log.setLogLevel("debug");
2252
2752
  }
2253
- }).action(async (options) => {
2254
- if (options.debug) {
2255
- log.setLogLevel("debug");
2256
- }
2257
- if (options.completion) {
2258
- log.debug("Setting up command-line completion");
2259
- completion.setupShellInitFile();
2260
- return;
2261
- }
2262
- if (options.init) {
2263
- log.debug("Generating shell integration function");
2264
- outputShellInit(program2);
2265
- return;
2753
+ }).action(
2754
+ async (project, options) => {
2755
+ if (options.debug) {
2756
+ log.setLogLevel("debug");
2757
+ }
2758
+ if (options.completion) {
2759
+ log.debug("Setting up command-line completion");
2760
+ completion.setupShellInitFile();
2761
+ return;
2762
+ }
2763
+ if (options.init) {
2764
+ log.debug("Generating shell integration function");
2765
+ outputShellInit(program2);
2766
+ return;
2767
+ }
2768
+ 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]);
2773
+ return;
2774
+ }
2775
+ const environment = await EnvironmentRecognizer.recognize(File7.cwd());
2776
+ program2.setOptionValue("_environment", environment);
2777
+ program2.setOptionValue("_config", config);
2778
+ program2.setOptionValue("_log", log);
2779
+ const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
2780
+ await runInteractive2({ config, log, environment });
2266
2781
  }
2267
- const environment = await EnvironmentRecognizer.recognize(File7.cwd());
2268
- program2.setOptionValue("_environment", environment);
2269
- program2.setOptionValue("_config", config);
2270
- program2.setOptionValue("_log", log);
2271
- const { runInteractive: runInteractive2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
2272
- await runInteractive2({ config, log, environment });
2273
- });
2782
+ );
2274
2783
  program2.setOptionValue("_config", config);
2275
2784
  program2.setOptionValue("_log", log);
2276
2785
  program2.addCommand(createOpenCommand({ config, log }));