workon 1.3.0 → 1.4.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ <a name="1.4.0"></a>
6
+ # [1.4.0](https://github.com/israelroldan/workon/compare/v1.3.0...v1.4.0) (2025-08-06)
7
+
8
+
9
+ ### Features
10
+
11
+ * Integrate NPM command support and enhance terminal layouts ([f877ec4](https://github.com/israelroldan/workon/commit/f877ec4))
12
+
13
+
14
+
5
15
  <a name="1.3.0"></a>
6
16
  # [1.3.0](https://github.com/israelroldan/workon/compare/v1.2.1...v1.3.0) (2025-08-06)
7
17
 
package/cli/manage.js CHANGED
@@ -127,7 +127,8 @@ class manage extends command {
127
127
  { name: 'Change directory (cwd)', value: 'cwd', checked: true },
128
128
  { name: 'Open in IDE', value: 'ide', checked: true },
129
129
  { name: 'Open homepage in browser', value: 'web' },
130
- { name: 'Launch Claude Code', value: 'claude' }
130
+ { name: 'Launch Claude Code', value: 'claude' },
131
+ { name: 'Run NPM command', value: 'npm' }
131
132
  ]
132
133
  }
133
134
  ];
@@ -141,6 +142,10 @@ class manage extends command {
141
142
  // Ask for Claude-specific configuration
142
143
  const claudeConfig = await me.configureClaudeEvent();
143
144
  events[event] = claudeConfig;
145
+ } else if (event === 'npm') {
146
+ // Ask for NPM-specific configuration
147
+ const npmConfig = await me.configureNpmEvent();
148
+ events[event] = npmConfig;
144
149
  } else {
145
150
  events[event] = 'true';
146
151
  }
@@ -247,7 +252,8 @@ class manage extends command {
247
252
  { name: 'Change directory (cwd)', value: 'cwd', checked: currentEvents.includes('cwd') },
248
253
  { name: 'Open in IDE', value: 'ide', checked: currentEvents.includes('ide') },
249
254
  { name: 'Open homepage in browser', value: 'web', checked: currentEvents.includes('web') },
250
- { name: 'Launch Claude Code', value: 'claude', checked: currentEvents.includes('claude') }
255
+ { name: 'Launch Claude Code', value: 'claude', checked: currentEvents.includes('claude') },
256
+ { name: 'Run NPM command', value: 'npm', checked: currentEvents.includes('npm') }
251
257
  ]
252
258
  }
253
259
  ];
@@ -276,6 +282,25 @@ class manage extends command {
276
282
  } else {
277
283
  events[event] = await me.configureClaudeEvent();
278
284
  }
285
+ } else if (event === 'npm') {
286
+ // If npm was previously configured with advanced options, preserve or update them
287
+ const existingNpmConfig = project.events && project.events.npm;
288
+ if (existingNpmConfig && typeof existingNpmConfig === 'object') {
289
+ const keepConfig = await inquirer.prompt([{
290
+ type: 'confirm',
291
+ name: 'keep',
292
+ message: 'Keep existing NPM configuration?',
293
+ default: true
294
+ }]);
295
+
296
+ if (keepConfig.keep) {
297
+ events[event] = existingNpmConfig;
298
+ } else {
299
+ events[event] = await me.configureNpmEvent();
300
+ }
301
+ } else {
302
+ events[event] = await me.configureNpmEvent();
303
+ }
279
304
  } else {
280
305
  events[event] = 'true';
281
306
  }
@@ -451,6 +476,70 @@ class manage extends command {
451
476
 
452
477
  return config;
453
478
  }
479
+
480
+ async configureNpmEvent() {
481
+ let me = this;
482
+
483
+ me.log.log('\nšŸ“¦ Configure NPM Event\n');
484
+
485
+ const npmQuestions = [
486
+ {
487
+ type: 'input',
488
+ name: 'command',
489
+ message: 'NPM script to run (e.g., dev, start, test):',
490
+ default: 'dev',
491
+ validate: (value) => {
492
+ if (!value.trim()) {
493
+ return 'NPM command cannot be empty';
494
+ }
495
+ return true;
496
+ }
497
+ },
498
+ {
499
+ type: 'confirm',
500
+ name: 'useAdvanced',
501
+ message: 'Configure advanced NPM options?',
502
+ default: false
503
+ }
504
+ ];
505
+
506
+ const basicAnswers = await inquirer.prompt(npmQuestions);
507
+
508
+ if (!basicAnswers.useAdvanced) {
509
+ return basicAnswers.command;
510
+ }
511
+
512
+ const advancedQuestions = [
513
+ {
514
+ type: 'confirm',
515
+ name: 'watch',
516
+ message: 'Enable watch mode (if supported by command)?',
517
+ default: true
518
+ },
519
+ {
520
+ type: 'confirm',
521
+ name: 'auto_restart',
522
+ message: 'Auto-restart on crashes?',
523
+ default: false
524
+ }
525
+ ];
526
+
527
+ const advancedAnswers = await inquirer.prompt(advancedQuestions);
528
+
529
+ const config = {
530
+ command: basicAnswers.command
531
+ };
532
+
533
+ if (advancedAnswers.watch) {
534
+ config.watch = true;
535
+ }
536
+
537
+ if (advancedAnswers.auto_restart) {
538
+ config.auto_restart = true;
539
+ }
540
+
541
+ return config;
542
+ }
454
543
  }
455
544
 
456
545
  manage.define({
package/cli/open.js CHANGED
@@ -64,17 +64,26 @@ class open extends command {
64
64
  me.shellCommands = [];
65
65
  }
66
66
 
67
- // Check for split terminal scenario
67
+ // Intelligent layout detection
68
68
  const hasCwd = events.includes('cwd');
69
69
  const hasClaudeEvent = events.includes('claude');
70
- const claudeConfig = project.events.claude;
71
- const shouldUseSplitTerminal = hasCwd && hasClaudeEvent &&
72
- claudeConfig && typeof claudeConfig === 'object' && claudeConfig.split_terminal;
73
-
74
- if (shouldUseSplitTerminal) {
70
+ const hasNpmEvent = events.includes('npm');
71
+
72
+ if (hasCwd && hasClaudeEvent && hasNpmEvent) {
73
+ // Three-pane layout: Claude + Terminal + NPM
74
+ await me.handleThreePaneLayout(project, isShellMode);
75
+ // Process other events except cwd, claude, and npm
76
+ events.filter(e => !['cwd', 'claude', 'npm'].includes(e)).forEach(me.processEvent.bind(me));
77
+ } else if (hasCwd && hasNpmEvent) {
78
+ // Two-pane layout: Terminal + NPM (no Claude)
79
+ await me.handleTwoPaneNpmLayout(project, isShellMode);
80
+ // Process other events except cwd and npm
81
+ events.filter(e => !['cwd', 'npm'].includes(e)).forEach(me.processEvent.bind(me));
82
+ } else if (hasCwd && hasClaudeEvent) {
83
+ // Two-pane layout: Claude + Terminal (existing split terminal)
75
84
  await me.handleSplitTerminal(project, isShellMode);
76
85
  // Process other events except cwd and claude
77
- events.filter(e => e !== 'cwd' && e !== 'claude').forEach(me.processEvent.bind(me));
86
+ events.filter(e => !['cwd', 'claude'].includes(e)).forEach(me.processEvent.bind(me));
78
87
  } else {
79
88
  // Normal event processing
80
89
  events.forEach(me.processEvent.bind(me));
@@ -135,6 +144,118 @@ class open extends command {
135
144
  }
136
145
  }
137
146
 
147
+ async handleThreePaneLayout(project, isShellMode) {
148
+ let me = this;
149
+ const tmux = new TmuxManager();
150
+ const claudeConfig = project.events.claude;
151
+ const claudeArgs = (claudeConfig && claudeConfig.flags) ? claudeConfig.flags : [];
152
+ const npmConfig = project.events.npm;
153
+ const npmCommand = me.getNpmCommand(npmConfig);
154
+
155
+ if (isShellMode) {
156
+ // Check if tmux is available
157
+ if (await tmux.isTmuxAvailable()) {
158
+ const commands = tmux.buildThreePaneShellCommands(
159
+ project.name,
160
+ project.path.path,
161
+ claudeArgs,
162
+ npmCommand
163
+ );
164
+ me.shellCommands.push(...commands);
165
+ } else {
166
+ // Fall back to normal behavior if tmux is not available
167
+ me.log.debug('Tmux not available, falling back to normal mode');
168
+ me.shellCommands.push(`cd "${project.path.path}"`);
169
+ const claudeCommand = claudeArgs.length > 0
170
+ ? `claude ${claudeArgs.join(' ')}`
171
+ : 'claude';
172
+ me.shellCommands.push(claudeCommand);
173
+ me.shellCommands.push(npmCommand);
174
+ }
175
+ } else {
176
+ // Direct execution mode
177
+ if (await tmux.isTmuxAvailable()) {
178
+ try {
179
+ const sessionName = await tmux.createThreePaneSession(
180
+ project.name,
181
+ project.path.path,
182
+ claudeArgs,
183
+ npmCommand
184
+ );
185
+ await tmux.attachToSession(sessionName);
186
+ } catch (error) {
187
+ me.log.debug(`Failed to create tmux session: ${error.message}`);
188
+ // Fall back to normal behavior
189
+ me.processEvent('cwd');
190
+ me.processEvent('claude');
191
+ me.processEvent('npm');
192
+ }
193
+ } else {
194
+ me.log.debug('Tmux not available, falling back to normal mode');
195
+ // Fall back to normal behavior
196
+ me.processEvent('cwd');
197
+ me.processEvent('claude');
198
+ me.processEvent('npm');
199
+ }
200
+ }
201
+ }
202
+
203
+ async handleTwoPaneNpmLayout(project, isShellMode) {
204
+ let me = this;
205
+ const tmux = new TmuxManager();
206
+ const npmConfig = project.events.npm;
207
+ const npmCommand = me.getNpmCommand(npmConfig);
208
+
209
+ if (isShellMode) {
210
+ // Check if tmux is available
211
+ if (await tmux.isTmuxAvailable()) {
212
+ const commands = tmux.buildTwoPaneNpmShellCommands(
213
+ project.name,
214
+ project.path.path,
215
+ npmCommand
216
+ );
217
+ me.shellCommands.push(...commands);
218
+ } else {
219
+ // Fall back to normal behavior if tmux is not available
220
+ me.log.debug('Tmux not available, falling back to normal mode');
221
+ me.shellCommands.push(`cd "${project.path.path}"`);
222
+ me.shellCommands.push(npmCommand);
223
+ }
224
+ } else {
225
+ // Direct execution mode
226
+ if (await tmux.isTmuxAvailable()) {
227
+ try {
228
+ const sessionName = await tmux.createTwoPaneNpmSession(
229
+ project.name,
230
+ project.path.path,
231
+ npmCommand
232
+ );
233
+ await tmux.attachToSession(sessionName);
234
+ } catch (error) {
235
+ me.log.debug(`Failed to create tmux session: ${error.message}`);
236
+ // Fall back to normal behavior
237
+ me.processEvent('cwd');
238
+ me.processEvent('npm');
239
+ }
240
+ } else {
241
+ me.log.debug('Tmux not available, falling back to normal mode');
242
+ // Fall back to normal behavior
243
+ me.processEvent('cwd');
244
+ me.processEvent('npm');
245
+ }
246
+ }
247
+ }
248
+
249
+ getNpmCommand(npmConfig) {
250
+ if (typeof npmConfig === 'string') {
251
+ return `npm run ${npmConfig}`;
252
+ } else if (npmConfig && typeof npmConfig === 'object' && npmConfig.command) {
253
+ return `npm run ${npmConfig.command}`;
254
+ } else {
255
+ return 'npm run dev';
256
+ }
257
+ }
258
+
138
259
  processEvent (event) {
139
260
  let me = this;
140
261
  let environment = me.root().environment;
@@ -213,6 +334,18 @@ class open extends command {
213
334
  });
214
335
  }
215
336
  break;
337
+ case 'npm':
338
+ let npmCommand = me.getNpmCommand(project.events.npm);
339
+
340
+ if (isShellMode) {
341
+ me.shellCommands.push(npmCommand);
342
+ } else {
343
+ spawn('npm', ['run', npmCommand.replace('npm run ', '')], {
344
+ cwd: environment.project.path.path,
345
+ stdio: 'inherit'
346
+ });
347
+ }
348
+ break;
216
349
  }
217
350
  }
218
351
  if (`before${capitalEvt}` in scripts) {
package/docs/ideas.md CHANGED
@@ -2,86 +2,92 @@
2
2
 
3
3
  This document contains ideas for future enhancements to the workon project.
4
4
 
5
- ## Interactive Project Management
5
+ ## NPM Command Integration
6
6
 
7
- ### Project Configuration Editor
8
- Create an interactive mode for editing project configurations through guided prompts instead of manual file editing.
7
+ ### Three-Pane Development Layout
8
+ When `cwd`, `claude`, and `npm` events are enabled, create a three-pane tmux layout:
9
+ - **Left pane**: Claude Code running in project directory (full height)
10
+ - **Top-right pane**: Shell terminal in project directory
11
+ - **Bottom-right pane**: NPM command running (e.g., `npm run dev`, `npm test`)
9
12
 
10
- **Features:**
11
- - Interactive project creation wizard with step-by-step guidance
12
- - Edit existing project properties (name, path, IDE, events) through prompts
13
- - Validate project paths and IDE commands during configuration
14
- - Preview configuration changes before saving
15
- - Bulk operations for managing multiple projects
16
-
17
- **Implementation considerations:**
18
- - Extend existing inquirer-based interactive system
19
- - Add new command like `workon config` or `workon manage --interactive`
20
- - Provide different flows for:
21
- - Creating new projects
22
- - Editing existing projects
23
- - Bulk project management
24
- - Include validation for:
25
- - Directory paths existence
26
- - IDE command availability
27
- - Event configuration correctness
28
-
29
- **Benefits:**
30
- - Lower barrier to entry for new users
31
- - Reduced configuration errors through validation
32
- - More discoverable project management features
33
- - Better UX compared to manual JSON editing
34
-
35
- ## Enhanced Events
13
+ **Implementation approach:**
14
+ - Extend current split terminal to support three panes
15
+ - Create initial vertical split (Claude | Terminal)
16
+ - Split the right terminal pane horizontally (Terminal | npm)
17
+ - Use tmux: `split-window -v` on the right pane
18
+ - Auto-run specified npm command in bottom-right pane
36
19
 
37
- ### Advanced claude Event Options āœ… IMPLEMENTED
38
- The claude event now supports advanced configuration options:
20
+ **Configuration:**
39
21
  ```json
40
- "claude": {
41
- "flags": ["--resume", "--debug"]
22
+ {
23
+ "events": {
24
+ "cwd": "true",
25
+ "claude": {
26
+ "flags": ["--resume"],
27
+ "split_terminal": true
28
+ },
29
+ "npm": "dev"
30
+ }
42
31
  }
43
32
  ```
44
33
 
45
- **Available through:** `workon manage` → Configure advanced Claude options
46
-
47
- ### Split Terminal with Claude + CWD
48
- When both `claude` and `cwd` events are enabled, automatically create a split terminal layout:
49
- - **Left pane**: Claude Code running in project directory
50
- - **Right pane**: Shell terminal in project directory
51
-
52
- **Implementation approach:**
53
- - Use tmux to create split session
54
- - Detect when both events are present
55
- - Create session: `tmux new-session -d -s "workon-{project}"`
56
- - Split horizontally: `tmux split-window -h`
57
- - Run claude in left pane, shell in right pane
58
- - Attach to session
59
-
60
- **Configuration:**
34
+ **Alternative configuration:**
61
35
  ```json
62
- "claude": {
63
- "flags": ["--resume"],
64
- "split_terminal": true
36
+ {
37
+ "events": {
38
+ "cwd": "true",
39
+ "claude": "true",
40
+ "npm": {
41
+ "command": "dev",
42
+ "watch": true,
43
+ "auto_restart": false
44
+ }
45
+ }
65
46
  }
66
47
  ```
67
48
 
68
49
  **Benefits:**
69
- - Claude and terminal side-by-side for optimal workflow
70
- - Easy switching between AI assistance and command execution
71
- - Persistent session that can be reattached
50
+ - Complete development environment in one tmux session
51
+ - Claude AI + Terminal + Development server all visible
52
+ - Perfect for web development workflows
53
+ - Automatic npm script execution
72
54
 
73
- ### Future claude Event Enhancements
74
- Additional options that could be implemented:
75
- ```json
76
- "claude": {
77
- "flags": ["--resume"],
78
- "mode": "interactive",
79
- "project_context": true,
80
- "working_directory": "src/",
81
- "tmux_layout": "even-horizontal"
82
- }
55
+ **Tmux Layout:**
56
+ ```
57
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
58
+ │ │ Terminal │
59
+ │ Claude ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
60
+ │ (full │ npm run dev │
61
+ │ height) │ │
62
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
63
+ ```
64
+
65
+ ### Two-Pane Terminal + NPM Layout
66
+ When `cwd` and `npm` events are enabled (without Claude), create a two-pane tmux layout:
67
+ - **Left pane**: Shell terminal in project directory
68
+ - **Right pane**: NPM command running (e.g., `npm run dev`, `npm test`)
69
+
70
+ **Tmux Layout:**
71
+ ```
72
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
73
+ │ │ │
74
+ │ Terminal │ npm run dev │
75
+ │ │ │
76
+ │ │ │
77
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
83
78
  ```
84
79
 
80
+ **Use cases:**
81
+ - Traditional development workflow without AI assistance
82
+ - Monitoring build output while running commands
83
+ - Side-by-side terminal and dev server
84
+
85
85
  ## Future Ideas
86
86
 
87
+ ### Auto-enable Split Terminal
88
+ When both `cwd` and `claude` events are enabled, automatically enable split terminal mode without requiring explicit configuration.
89
+
90
+ ### Project Templates
91
+ Pre-configured project templates for common development stacks (React, Node.js, Python, etc.) with appropriate events and npm commands.
92
+
87
93
  *Add more ideas here as they come up...*
package/lib/tmux.js CHANGED
@@ -62,6 +62,53 @@ class TmuxManager {
62
62
  return sessionName;
63
63
  }
64
64
 
65
+ async createThreePaneSession(projectName, projectPath, claudeArgs = [], npmCommand = 'npm run dev') {
66
+ const sessionName = this.getSessionName(projectName);
67
+
68
+ // Kill existing session if it exists
69
+ if (await this.sessionExists(sessionName)) {
70
+ await this.killSession(sessionName);
71
+ }
72
+
73
+ const claudeCommand = claudeArgs.length > 0
74
+ ? `claude ${claudeArgs.join(' ')}`
75
+ : 'claude';
76
+
77
+ // Create new tmux session with claude in the first pane (left side)
78
+ await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
79
+
80
+ // Split window vertically - creates right side
81
+ await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
82
+
83
+ // Split the right pane horizontally - creates top-right and bottom-right
84
+ await exec(`tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`);
85
+
86
+ // Set focus on claude pane (left pane)
87
+ await exec(`tmux select-pane -t "${sessionName}:0.0"`);
88
+
89
+ return sessionName;
90
+ }
91
+
92
+ async createTwoPaneNpmSession(projectName, projectPath, npmCommand = 'npm run dev') {
93
+ const sessionName = this.getSessionName(projectName);
94
+
95
+ // Kill existing session if it exists
96
+ if (await this.sessionExists(sessionName)) {
97
+ await this.killSession(sessionName);
98
+ }
99
+
100
+ // Create new tmux session with shell in the first pane (left side)
101
+ await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}"`);
102
+
103
+ // Split window vertically and run npm command in right pane
104
+ await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`);
105
+
106
+ // Set focus on terminal pane (left pane)
107
+ await exec(`tmux select-pane -t "${sessionName}:0.0"`);
108
+
109
+ return sessionName;
110
+ }
111
+
65
112
  async attachToSession(sessionName) {
66
113
  // Check if we're already in a tmux session
67
114
  if (process.env.TMUX) {
@@ -93,6 +140,40 @@ class TmuxManager {
93
140
  ];
94
141
  }
95
142
 
143
+ buildThreePaneShellCommands(projectName, projectPath, claudeArgs = [], npmCommand = 'npm run dev') {
144
+ const sessionName = this.getSessionName(projectName);
145
+ const claudeCommand = claudeArgs.length > 0
146
+ ? `claude ${claudeArgs.join(' ')}`
147
+ : 'claude';
148
+
149
+ return [
150
+ `# Create tmux three-pane session for ${projectName}`,
151
+ `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
152
+ `tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
153
+ `tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
154
+ `tmux split-window -v -t "${sessionName}:0.1" -c "${projectPath}" '${npmCommand}'`,
155
+ `tmux select-pane -t "${sessionName}:0.0"`,
156
+ process.env.TMUX
157
+ ? `tmux switch-client -t "${sessionName}"`
158
+ : `tmux attach-session -t "${sessionName}"`
159
+ ];
160
+ }
161
+
162
+ buildTwoPaneNpmShellCommands(projectName, projectPath, npmCommand = 'npm run dev') {
163
+ const sessionName = this.getSessionName(projectName);
164
+
165
+ return [
166
+ `# Create tmux two-pane session with npm for ${projectName}`,
167
+ `tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
168
+ `tmux new-session -d -s "${sessionName}" -c "${projectPath}"`,
169
+ `tmux split-window -h -t "${sessionName}" -c "${projectPath}" '${npmCommand}'`,
170
+ `tmux select-pane -t "${sessionName}:0.0"`,
171
+ process.env.TMUX
172
+ ? `tmux switch-client -t "${sessionName}"`
173
+ : `tmux attach-session -t "${sessionName}"`
174
+ ];
175
+ }
176
+
96
177
  async listWorkonSessions() {
97
178
  try {
98
179
  const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
package/lib/validation.js CHANGED
@@ -79,7 +79,7 @@ class ProjectValidator {
79
79
  return 'Events must be an object';
80
80
  }
81
81
 
82
- const validEvents = ['cwd', 'ide', 'web', 'claude'];
82
+ const validEvents = ['cwd', 'ide', 'web', 'claude', 'npm'];
83
83
  const invalidEvents = Object.keys(events).filter(event => !validEvents.includes(event));
84
84
 
85
85
  if (invalidEvents.length > 0) {
@@ -94,6 +94,14 @@ class ProjectValidator {
94
94
  }
95
95
  }
96
96
 
97
+ // Validate npm event configuration if present
98
+ if (events.npm && typeof events.npm === 'object') {
99
+ const npmValidation = this.validateNpmConfig(events.npm);
100
+ if (npmValidation !== true) {
101
+ return npmValidation;
102
+ }
103
+ }
104
+
97
105
  return true;
98
106
  }
99
107
 
@@ -126,6 +134,31 @@ class ProjectValidator {
126
134
  return true;
127
135
  }
128
136
 
137
+ validateNpmConfig(config) {
138
+ if (typeof config !== 'object') {
139
+ return 'NPM configuration must be an object';
140
+ }
141
+
142
+ // Validate command if present
143
+ if (config.command) {
144
+ if (typeof config.command !== 'string' || config.command.trim() === '') {
145
+ return 'NPM command must be a non-empty string';
146
+ }
147
+ }
148
+
149
+ // Validate watch if present
150
+ if (config.watch !== undefined && typeof config.watch !== 'boolean') {
151
+ return 'NPM watch must be a boolean';
152
+ }
153
+
154
+ // Validate auto_restart if present
155
+ if (config.auto_restart !== undefined && typeof config.auto_restart !== 'boolean') {
156
+ return 'NPM auto_restart must be a boolean';
157
+ }
158
+
159
+ return true;
160
+ }
161
+
129
162
  validateUrl(url) {
130
163
  if (!url) return true; // Optional field
131
164
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workon",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Work on something great!",
5
5
  "main": "index.js",
6
6
  "scripts": {