workon 1.0.0-alpha.1 → 1.1.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.
@@ -0,0 +1,3 @@
1
+
2
+ # Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
3
+ .specstory/**
@@ -0,0 +1,11 @@
1
+ {
2
+ "autorun": true,
3
+ "artificialDelay": 1000,
4
+ "terminals": [
5
+ {
6
+ "name": "Persistent",
7
+ "focus": true,
8
+ "command": "cc"
9
+ }
10
+ ]
11
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
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.1.0"></a>
6
+ # [1.1.0](https://github.com/israelroldan/workon/compare/v1.0.0...v1.1.0) (2025-08-06)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * Update package.json format for npm publish ([6c5e9e0](https://github.com/israelroldan/workon/commit/6c5e9e0))
12
+
13
+
14
+ ### Features
15
+
16
+ * Enhance CLI with new 'claude' command support ([70b3f96](https://github.com/israelroldan/workon/commit/70b3f96))
17
+ * Improve shell integration for workon command ([f5b6ff0](https://github.com/israelroldan/workon/commit/f5b6ff0))
18
+
19
+
20
+
5
21
  <a name="1.0.0-alpha.1"></a>
6
22
  # [1.0.0-alpha.1](https://code.palu.io/israel/workon/compare/v1.0.0-alpha.0...v1.0.0-alpha.1) (2017-06-26)
7
23
 
package/CLAUDE.md ADDED
@@ -0,0 +1,100 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ `workon` is a Node.js CLI tool for managing development projects and environments. It helps developers quickly switch between projects, configure IDEs, and manage git branches through an interactive interface.
8
+
9
+ ## Development Commands
10
+
11
+ ### Release Management
12
+ ```bash
13
+ npm run release # Create a new release using standard-version
14
+ ```
15
+
16
+ ### Installation and Usage
17
+ ```bash
18
+ npm install # Install dependencies
19
+ node bin/workon # Run the CLI tool directly
20
+ npm link # Install globally for development
21
+ ```
22
+
23
+ ## Architecture
24
+
25
+ ### Core Components
26
+
27
+ **CLI Entry Point (`bin/workon`)**
28
+ - Main executable that initializes logging and runs the CLI
29
+ - Supports `--debug` flag for detailed logging
30
+
31
+ **Command System (`cli/`)**
32
+ - `cli/index.js`: Main CLI class extending switchit's container pattern
33
+ - `cli/interactive.js`: Interactive mode for project setup and management
34
+ - `cli/open.js`: Project opening and environment switching
35
+ - `cli/config/`: Configuration management commands
36
+
37
+ **Project Management (`lib/`)**
38
+ - `lib/project.js`: Project class with path, IDE, events, and branch properties
39
+ - `lib/environment/`: Environment recognition and project detection
40
+ - `lib/config.js`: Configuration storage using the `conf` package
41
+
42
+ ### Key Architectural Patterns
43
+
44
+ **Environment Recognition**
45
+ - Automatically detects if current directory is a configured project
46
+ - Matches project paths against configuration
47
+ - Integrates with git to detect current branch
48
+ - Supports branch-specific project configurations (e.g., `project#branch`)
49
+
50
+ **Configuration System**
51
+ - Uses `conf` package for persistent storage
52
+ - Separates transient properties (`pkg`, `work`) from persistent config
53
+ - Stores project definitions and defaults
54
+ - Configuration lives in user's config directory
55
+
56
+ **Interactive Flow**
57
+ - Context-aware prompts based on current environment
58
+ - Different options when inside vs outside project directories
59
+ - Supports creating new projects, branches, and managing existing ones
60
+
61
+ **Event System**
62
+ - Projects can define events that trigger when opening:
63
+ - `cwd`: Change terminal directory to project path
64
+ - `ide`: Open project in configured IDE (VS Code, IntelliJ, Atom)
65
+ - `web`: Open project homepage in browser
66
+ - Events are processed when switching to a project
67
+
68
+ ### Project Configuration Structure
69
+
70
+ Projects are stored with these properties:
71
+ - `name`: Project identifier
72
+ - `path`: Relative path from base directory
73
+ - `ide`: IDE command (`vscode`, `idea`, `atom`)
74
+ - `events`: Object defining which events to trigger
75
+ - `branch`: Git branch (for branch-specific configs)
76
+
77
+ ### Dependencies
78
+
79
+ **Core Dependencies**
80
+ - `switchit`: CLI framework for commands and arguments
81
+ - `inquirer`: Interactive command-line prompts
82
+ - `conf`: Configuration storage
83
+ - `phylo`: File system utilities
84
+ - `simple-git`: Git integration
85
+ - `loog`: Logging system
86
+
87
+ **Utility Dependencies**
88
+ - `deep-assign`: Object merging
89
+ - `flat`: Object flattening
90
+ - `omelette`: Shell completion
91
+ - `openurl2`: Browser opening
92
+
93
+ ## Development Notes
94
+
95
+ - The tool uses a class-based architecture with command inheritance
96
+ - Configuration is automatically managed and persisted
97
+ - Environment detection works by matching canonical paths
98
+ - Interactive mode provides different UX based on context
99
+ - Shell completion is supported via omelette
100
+ - Git integration detects current branch for branch-specific configs
package/README.md CHANGED
@@ -1,2 +1,215 @@
1
1
  # workon
2
2
 
3
+ > Work on something great! 🚀
4
+
5
+ A productivity CLI tool that helps developers quickly switch between projects with automatic environment setup. No more manually navigating to project directories, opening IDEs, or remembering project-specific commands.
6
+
7
+ ## Features
8
+
9
+ ✨ **Smart Project Switching** - Switch between projects in your current shell (no nested processes!)
10
+ 🔧 **IDE Integration** - Automatically open projects in VS Code, IntelliJ IDEA, or Atom
11
+ 🌐 **Web Integration** - Launch project websites and documentation
12
+ 🌳 **Git Branch Support** - Different configurations for different branches
13
+ 📁 **Auto Directory Change** - Seamlessly `cd` into project directories
14
+ ⚡ **Interactive Setup** - Guided project configuration
15
+ 🔄 **Backward Compatible** - Legacy nested shell mode still available
16
+
17
+ ## Installation
18
+
19
+ ### Option 1: Global Installation (Recommended)
20
+
21
+ ```bash
22
+ # Install globally
23
+ npm install -g workon
24
+
25
+ # Set up shell integration (one time setup)
26
+ echo 'eval "$(workon --init)"' >> ~/.zshrc # for zsh
27
+ echo 'eval "$(workon --init)"' >> ~/.bashrc # for bash
28
+
29
+ # Reload your shell
30
+ source ~/.zshrc # or ~/.bashrc
31
+ ```
32
+
33
+ ### Option 2: Using npx (No Global Install)
34
+
35
+ ```bash
36
+ # Set up shell integration
37
+ echo 'eval "$(npx workon --init)"' >> ~/.zshrc # for zsh
38
+ echo 'eval "$(npx workon --init)"' >> ~/.bashrc # for bash
39
+
40
+ # Reload your shell
41
+ source ~/.zshrc # or ~/.bashrc
42
+ ```
43
+
44
+ ### Verify Installation
45
+
46
+ ```bash
47
+ workon --help
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ 1. **Create your first project:**
53
+ ```bash
54
+ workon # Start interactive mode
55
+ ```
56
+
57
+ 2. **Switch to a project:**
58
+ ```bash
59
+ workon myproject # Automatically cd + open IDE
60
+ ```
61
+
62
+ 3. **List available projects:**
63
+ ```bash
64
+ workon config list
65
+ ```
66
+
67
+ ## Project Configuration
68
+
69
+ Projects are configured with:
70
+
71
+ - **Path**: Where your project lives
72
+ - **IDE**: Which editor to open (`code`, `idea`, `atom`)
73
+ - **Events**: What happens when switching to the project
74
+ - `cwd`: Change directory to project path
75
+ - `ide`: Open project in configured IDE
76
+ - `web`: Open project homepage/docs
77
+
78
+ ### Interactive Project Setup
79
+
80
+ Run `workon` to start the interactive setup wizard:
81
+
82
+ ```bash
83
+ $ workon
84
+ 8 v1.0.0-alpha.1
85
+ Yb db dP .d8b. 8d8b 8.dP .d8b. 8d8b.
86
+ YbdPYbdP 8' .8 8P 88b 8' .8 8P Y8
87
+ YP YP `Y8P' 8 8 Yb `Y8P' 8 8
88
+
89
+ ? What do you want to do?
90
+ ❯ Start a new project
91
+ Open an existing project
92
+ Manage projects
93
+ Exit
94
+ ```
95
+
96
+ ### Manual Configuration
97
+
98
+ ```bash
99
+ # Set base directory for all projects
100
+ workon config set project_defaults.base ~/code
101
+
102
+ # Add a project
103
+ workon config set projects.myapp.path myapp
104
+ workon config set projects.myapp.ide code
105
+ workon config set projects.myapp.events.cwd true
106
+ workon config set projects.myapp.events.ide true
107
+ ```
108
+
109
+ ## Usage Examples
110
+
111
+ ### Basic Project Switching
112
+ ```bash
113
+ # Switch to project (changes directory + opens IDE)
114
+ workon myproject
115
+
116
+ # Shell mode - outputs commands instead of executing
117
+ workon myproject --shell
118
+ # Output: cd "/path/to/myproject"
119
+ # code "/path/to/myproject" &
120
+ ```
121
+
122
+ ### Branch-Specific Configuration
123
+ ```bash
124
+ # Configure different settings for a git branch
125
+ workon myproject#feature-branch
126
+ ```
127
+
128
+ ### Legacy Mode (Nested Shells)
129
+ ```bash
130
+ # Use the old behavior if needed (spawns new shell)
131
+ workon myproject --no-shell # (this is the default)
132
+ ```
133
+
134
+ ### Configuration Management
135
+ ```bash
136
+ # List all configuration
137
+ workon config list
138
+
139
+ # Set a value
140
+ workon config set key value
141
+
142
+ # Remove a value
143
+ workon config unset key
144
+ ```
145
+
146
+ ## Shell Integration Details
147
+
148
+ The modern shell integration works by:
149
+
150
+ 1. **Setup**: `workon --init` outputs a shell function
151
+ 2. **Usage**: When you run `workon myproject`, the function:
152
+ - Calls `workon myproject --shell` to get commands
153
+ - Uses `eval` to execute them in your current shell
154
+ 3. **Result**: No nested shells, seamless directory changes
155
+
156
+ ### Manual Shell Integration
157
+
158
+ If you prefer not to modify your shell config:
159
+
160
+ ```bash
161
+ # Get commands and execute manually
162
+ eval "$(workon myproject --shell)"
163
+
164
+ # Or see what commands would run
165
+ workon myproject --shell
166
+ ```
167
+
168
+ ## Advanced Usage
169
+
170
+ ### Environment Detection
171
+
172
+ `workon` automatically detects when you're in a configured project directory and shows relevant options.
173
+
174
+ ### Shell Completion
175
+
176
+ Set up tab completion:
177
+
178
+ ```bash
179
+ workon --setup-completion
180
+ ```
181
+
182
+ ### Debug Mode
183
+
184
+ Troubleshoot with debug output:
185
+
186
+ ```bash
187
+ workon myproject --debug
188
+ ```
189
+
190
+ ## Configuration Storage
191
+
192
+ Configuration is stored using the [`conf`](https://github.com/sindresorhus/conf) package in your system's config directory:
193
+
194
+ - **macOS**: `~/Library/Preferences/workon/`
195
+ - **Linux**: `~/.config/workon/`
196
+ - **Windows**: `%APPDATA%/workon/`
197
+
198
+ ## Migration from Legacy Mode
199
+
200
+ If you've been using the nested shell mode, the new shell integration provides the same functionality without the shell nesting issues. Both modes continue to work:
201
+
202
+ - **Legacy**: `workon myproject` (spawns new shell)
203
+ - **Modern**: `workon myproject` (with shell integration setup)
204
+
205
+ ## Contributing
206
+
207
+ This project uses [standard-version](https://github.com/conventional-changelog/standard-version) for releases.
208
+
209
+ ```bash
210
+ npm run release
211
+ ```
212
+
213
+ ## License
214
+
215
+ MIT © [Israel Roldan](https://github.com/israelroldan)
package/bin/workon CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- const isDebug = /-{1,2}de?b?u?g?\b/.test(process.argv[2]);
3
- const log = require('loog')({
2
+ const isDebug = !!process.argv.find(param => /-{1,2}de?b?u?g?\b/.test(param));
3
+ const console = require('loog')({
4
4
  prefixStyle: 'ascii',
5
5
  logLevel: isDebug ? 'debug' : 'info'
6
6
  });
7
7
 
8
8
  const workon = require('../cli');
9
- new workon(log).run().catch(e => {
10
- log.error(isDebug ? e.stack : (e.message ? e.message : e));
9
+ new workon(console).run().catch(e => {
10
+ console.error(isDebug ? e.stack : (e.message ? e.message : e));
11
11
  process.exit(1);
12
12
  });
@@ -7,6 +7,7 @@ const unset = require('./unset');
7
7
  class config extends container {}
8
8
 
9
9
  config.define({
10
+ help: 'manage configuration parameters',
10
11
  commands: {
11
12
  '': 'list',
12
13
  list,
@@ -19,6 +19,8 @@ class list extends command {
19
19
  }
20
20
  }
21
21
 
22
- list.define({});
22
+ list.define({
23
+ help: 'List configuration parameters'
24
+ });
23
25
 
24
26
  module.exports = list;
package/cli/config/set.js CHANGED
@@ -3,11 +3,17 @@ const { command } = require('../base');
3
3
  class set extends command {
4
4
  execute (params) {
5
5
  let me = this;
6
-
7
- me.log.info('set config here');
6
+ me.config.set(params.key, params.value);
8
7
  }
9
8
  }
10
9
 
11
- set.define({});
10
+ set.define({
11
+ help: {
12
+ '': 'Set a configuration parameter',
13
+ key: 'The configuration parameter to set',
14
+ value: 'The value to set'
15
+ },
16
+ parameters: '{key} {value}'
17
+ });
12
18
 
13
19
  module.exports = set;
@@ -3,11 +3,24 @@ const { command } = require('../base');
3
3
  class unset extends command {
4
4
  execute (params) {
5
5
  let me = this;
6
-
7
- me.log.info('unset config here');
6
+ if (!params.silent && !me.config.has(params.key)) {
7
+ throw new Error(`Unknown config ${params.key}`);
8
+ }
9
+ me.config.delete(params.key);
10
+ if (!params.silent) {
11
+ me.log.info(`Entry ${params.key} was removed from the configuration`);
12
+ }
8
13
  }
9
14
  }
10
15
 
11
- unset.define({});
16
+ unset.define({
17
+ help: {
18
+ '': 'Remove a configuration parameter',
19
+ silent: 'Suppress console output',
20
+ key: 'The configuration parameter to remove'
21
+ },
22
+ switches: '[silent:boolean=false]',
23
+ parameters: '{key}'
24
+ });
12
25
 
13
26
  module.exports = unset;
package/cli/index.js CHANGED
@@ -1,8 +1,8 @@
1
- const File = require('phylo');
2
1
  const {container} = require('./base');
3
- const Config = require('../lib/config')
4
- const omelette = require('omelette');
5
2
 
3
+ const File = require('phylo');
4
+
5
+ const Config = require('../lib/config');
6
6
  const {EnvironmentRecognizer} = require('../lib/environment');
7
7
 
8
8
  const config = require('./config');
@@ -16,8 +16,13 @@ class workon extends container {
16
16
  me.log = log || require('loog')({
17
17
  prefixStyle: 'ascii'
18
18
  });
19
+ }
20
+
21
+ run () {
22
+ let me = this;
19
23
  me.initialize();
20
24
  me.prepareCompletion();
25
+ return super.run();
21
26
  }
22
27
 
23
28
  initialize () {
@@ -39,58 +44,112 @@ class workon extends container {
39
44
  'list', 'set', 'reset'
40
45
  ]
41
46
  };
42
- let projects = Object.keys(me.config.get('projects')).forEach((id) => {
43
- tree[id] = null;
44
- })
45
- me.completion = omelette('workon').tree(tree);
47
+ let projects = me.config.get('projects');
48
+ if (projects) {
49
+ Object.keys(projects).forEach((id) => {
50
+ tree[id] = null;
51
+ });
52
+ }
53
+ me.completion = require('omelette')('workon').tree(tree);
46
54
  me.completion.init();
47
55
  }
48
56
 
49
57
  execute (params, args) {
50
58
  let me = this;
51
- me.log.debug('');
52
-
59
+
53
60
  if (params.debug) {
54
61
  me.log.setLogLevel('debug');
55
62
  }
56
- if (params.version) {
57
- me.log.info(`workon v${me.config.get('pkg').version}`);
58
- return true;
59
- } else if (params.help) {
60
- me.log.info('Show help');
61
- return true;
62
- } else if (params.setup) {
63
+
64
+ if (params['setup-completion']) {
63
65
  me.log.debug('Configuring command-line completion');
64
66
  me.completion.setupShellInitFile();
65
67
  return true;
68
+ } else if (params.init) {
69
+ me.log.debug('Generating shell integration function');
70
+ me.outputShellInit();
71
+ return true;
66
72
  } else {
73
+ let prom = Promise.resolve();
67
74
  if (!me.environment) {
68
- return EnvironmentRecognizer.recognize(File.cwd())
75
+ prom = EnvironmentRecognizer.recognize(File.cwd())
69
76
  .then((environment) => {
70
77
  me.environment = environment;
71
- return super.execute(params, args).catch((err) => {
72
- if (args._args.length == 0) {
73
- let interactiveCmd = me.commands.lookup('interactive').create(me);
74
- return interactiveCmd.run([])
75
- } else {
76
- let cmdNames = me.constructor.getAspects().commands.filter((c)=>!!c).map((c)=>c.name);
77
- let firstCmd = args._args.filter((a)=>!/^-/.test(a))[0];
78
- if (!~cmdNames.indexOf(firstCmd)) {
79
- let openCmd = me.commands.lookup('open').create(me);
80
- return openCmd.run(args._args);
81
- } else {
82
- throw err;
83
- }
84
- }
85
- });
86
78
  });
87
79
  }
80
+ return prom.then(() => super.execute(params, args).catch((err) => me.maybeOpen(err, args)));
88
81
  }
89
82
  }
90
83
 
91
- logo () {
92
- let version = this.config.get('pkg').version;
93
- return ` 8\u001b[2m${' '.repeat(Math.max(15-version.length-1, 1))+'v'+version}\u001b[22m\nYb db dP .d8b. 8d8b 8.dP \u001b[92m.d8b. 8d8b.\u001b[0m\n YbdPYbdP 8' .8 8P 88b \u001b[92m8' .8 8P Y8\u001b[0m\n YP YP \`Y8P' 8 8 Yb \u001b[92m\`Y8P' 8 8\u001b[0m`;
84
+ outputShellInit () {
85
+ let me = this;
86
+
87
+ // Get list of available commands from switchit
88
+ let cmdNames = me.constructor.getAspects().commands.filter((c) => !!c).map((c) => c.name);
89
+
90
+ // Get list of available switches from switchit
91
+ let switches = me.constructor.getAspects().switches || [];
92
+ let switchFlags = [];
93
+ switches.forEach(sw => {
94
+ switchFlags.push('--' + sw.name);
95
+ if (sw.char) {
96
+ switchFlags.push('-' + sw.char);
97
+ }
98
+ });
99
+
100
+ // Add built-in switchit flags (help and version are automatically added by switchit)
101
+ let builtinFlags = ['--help', '-h', '--version', '-v', 'help'];
102
+
103
+ // Combine all non-shell commands and flags, removing duplicates
104
+ let nonShellCommands = [...new Set(cmdNames.concat(switchFlags).concat(builtinFlags))];
105
+ let casePattern = nonShellCommands.join('|');
106
+
107
+ // Generate shell function that wraps workon calls
108
+ let shellFunction = `
109
+ # workon shell integration
110
+ workon() {
111
+ # Commands that should NOT use shell mode
112
+ case "$1" in
113
+ ${casePattern})
114
+ command workon "$@"
115
+ return $?
116
+ ;;
117
+ esac
118
+
119
+ # Default behavior: use shell mode for project opening
120
+ local output
121
+ output=$(command workon --shell "$@" 2>&1)
122
+ local exit_code=$?
123
+
124
+ if [[ $exit_code -eq 0 && -n "$output" ]]; then
125
+ # Execute shell commands if workon succeeded and output exists
126
+ eval "$output"
127
+ else
128
+ # Show any error output
129
+ [[ -n "$output" ]] && echo "$output" >&2
130
+ return $exit_code
131
+ fi
132
+ }`;
133
+ console.log(shellFunction);
134
+ }
135
+
136
+ maybeOpen (err, args) {
137
+ let me = this;
138
+ let cmdNames = me.constructor.getAspects().commands.filter((c) => !!c).map((c) => c.name);
139
+ let firstCmd = args._args.filter((a) => !/^-/.test(a))[0];
140
+ if (!~cmdNames.indexOf(firstCmd)) {
141
+ // ex. "workon projectName"
142
+ let openCmd = me.commands.lookup('open').create(me);
143
+ // Copy shell parameter to the open command
144
+ if (me.params.shell) {
145
+ openCmd.params = openCmd.params || {};
146
+ openCmd.params.shell = true;
147
+ }
148
+ return openCmd.run(args._args);
149
+ } else {
150
+ // ex. "workon config asdf"
151
+ throw err;
152
+ }
94
153
  }
95
154
  }
96
155
 
@@ -98,14 +157,17 @@ workon.define({
98
157
  help: {
99
158
  '': 'Work on something great!',
100
159
  debug: 'Provide debug logging output',
101
- help: 'Show help',
102
- version: 'Show version',
103
- 'setup-completion': 'Configure command line tab completion (see help for details)'
160
+ 'setup-completion': 'Configure command line tab completion (see help for details)',
161
+ shell: 'Output shell commands instead of spawning processes',
162
+ init: 'Generate shell integration function for seamless directory switching'
104
163
  },
105
- switches: '[debug:boolean=false] [version:boolean=false] [help:boolean=false] [setup-completion:boolean=false]',
164
+ switches: '[d#debug:boolean=false] [setup-completion:boolean=false] [shell:boolean=false] [init:boolean=false]',
106
165
  commands: {
107
166
  '': 'open',
108
- interactive,
167
+ interactive: {
168
+ type: interactive,
169
+ private: true
170
+ },
109
171
  open,
110
172
  config
111
173
  }
@@ -4,21 +4,21 @@ const File = require('phylo');
4
4
  const Prompt = require('inquirer/lib/prompts/input');
5
5
  const deepAssign = require('deep-assign');
6
6
 
7
- class init extends command {
7
+ class interactive extends command {
8
8
  execute (params) {
9
9
  let me = this;
10
10
  me.log.debug();
11
- me.log.log(me.root().logo());
11
+ me.showLogo();
12
12
  me.log.log();
13
13
  let name;
14
14
  let fromUser = false;
15
15
  if (!params.identifier || params.identifier === 'undefined') {
16
- me.log.debug('Name was not provided, autodetecting');
16
+ me.log.debug('Name was not provided, auto-detecting');
17
17
  if (me.root().project) {
18
18
  me.log.debug('Name derived form current project');
19
19
  name = me.root().project;
20
20
  } else {
21
- me.log.debug('Name derived from current working directory')
21
+ me.log.debug('Name derived from current working directory');
22
22
  name = File.cwd().name;
23
23
  }
24
24
  } else {
@@ -28,6 +28,16 @@ class init extends command {
28
28
  return me.startInteractive(name, fromUser);
29
29
  }
30
30
 
31
+ showLogo () {
32
+ let me = this;
33
+ let version = me.config.get('pkg').version;
34
+
35
+ if (!this._hasShownLogo) {
36
+ this.log.log(` 8\u001b[2m${' '.repeat(Math.max(15-version.length-1, 1))+'v'+version}\u001b[22m\nYb db dP .d8b. 8d8b 8.dP \u001b[92m.d8b. 8d8b.\u001b[0m\n YbdPYbdP 8' .8 8P 88b \u001b[92m8' .8 8P Y8\u001b[0m\n YP YP \`Y8P' 8 8 Yb \u001b[92m\`Y8P' 8 8\u001b[0m`);
37
+ this._hasShownLogo = true;
38
+ }
39
+ }
40
+
31
41
  startInteractive (defaultName, fromUser, showMain = false) {
32
42
  let me = this;
33
43
  let environment = me.root().environment;
@@ -268,7 +278,7 @@ class init extends command {
268
278
  }
269
279
  }
270
280
 
271
- init.define({
281
+ interactive.define({
272
282
  parameters: {
273
283
  identifier: {
274
284
  type: 'string',
@@ -277,4 +287,4 @@ init.define({
277
287
  }
278
288
  });
279
289
 
280
- module.exports = init;
290
+ module.exports = interactive;
package/cli/open.js CHANGED
@@ -5,22 +5,17 @@ const spawn = require('child_process').spawn;
5
5
  const File = require('phylo');
6
6
 
7
7
  class open extends command {
8
- execute (params, args) {
8
+ execute (params) {
9
9
  let me = this;
10
- let defaults = me.config.get('project_defaults');
11
- let baseDir = File.from(defaults.base);
12
- let projects = me.config.get('projects');
13
- let cwp = null;
14
- switch (params.identifier) {
15
- case undefined:
16
- me.log.debug(`No project name provided, starting interactive mode`);
17
- return me.startInteractiveMode();
18
- default:
19
- return me.processProjectIdentifier(params.identifier);
10
+ if (params.project) {
11
+ return me.processProject(params.project);
12
+ } else {
13
+ me.log.debug(`No project name provided, starting interactive mode`);
14
+ return me.startInteractiveMode();
20
15
  }
21
16
  }
22
17
 
23
- processProjectIdentifier (identifier) {
18
+ processProject (project) {
24
19
  let me = this;
25
20
  let environment = me.root().environment;
26
21
 
@@ -28,27 +23,27 @@ class open extends command {
28
23
  if (!projects) {
29
24
  me.config.set('projects', {});
30
25
  } else {
31
- if (environment.$isProjectEnvironment && (identifier === 'this' || identifier === '.')) {
26
+ if (environment.$isProjectEnvironment && (project === 'this' || project === '.')) {
32
27
  me.log.info(`Open current: ${environment.project.name}`);
33
28
  } else {
34
- if (identifier in projects) {
35
- let cfg = projects[identifier];
36
- cfg.name = identifier;
29
+ if (project in projects) {
30
+ let cfg = projects[project];
31
+ cfg.name = project;
37
32
  me.switchTo(ProjectEnvironment.load(cfg, me.config.get('project_defaults')));
38
33
  } else {
39
- me.log.debug(`Project '${identifier}' not found, starting interactive mode`);
40
- return me.startInteractiveMode(identifier);
34
+ me.log.debug(`Project '${project}' not found, starting interactive mode`);
35
+ return me.startInteractiveMode(project);
41
36
  }
42
37
  }
43
38
  }
44
39
  }
45
40
 
46
- startInteractiveMode (identifier) {
41
+ startInteractiveMode (project) {
47
42
  let me = this;
48
43
  let root = me.root();
49
44
 
50
45
  let interactiveCmd = root.commands.lookup('interactive').create(root);
51
- return interactiveCmd.dispatch(new me.args.constructor([identifier]))
46
+ return interactiveCmd.dispatch(new me.args.constructor([project]))
52
47
  }
53
48
 
54
49
  switchTo (environment) {
@@ -62,7 +57,18 @@ class open extends command {
62
57
  me.log.debug(`IDE command is: ${project.ide}`);
63
58
  me.log.debug(`Actions are: ${events}`);
64
59
 
60
+ // Initialize shell commands collector if in shell mode
61
+ let isShellMode = me.params.shell || me.root().params.shell;
62
+ if (isShellMode) {
63
+ me.shellCommands = [];
64
+ }
65
+
65
66
  events.forEach(me.processEvent.bind(me));
67
+
68
+ // Output collected shell commands if in shell mode
69
+ if (isShellMode && me.shellCommands.length > 0) {
70
+ console.log(me.shellCommands.join('\n'));
71
+ }
66
72
  }
67
73
 
68
74
  processEvent (event) {
@@ -80,21 +86,56 @@ class open extends command {
80
86
  if (event in scripts) {
81
87
  me.log.debug(`Found script with event name, unfortunately scripts are not yet supported.`);
82
88
  }
83
- switch (event) {
84
- case 'ide':
85
- spawn(project.ide, [project.path.path]);
86
- break;
87
- case 'cwd':
88
- spawn(process.env.SHELL, ['-i'], {
89
- cwd: environment.project.path.path,
90
- stdio: 'inherit'
91
- });
92
- break;
93
- case 'web':
94
- if (homepage) {
95
- require("openurl2").open(homepage);
96
- }
97
- break;
89
+ if (!me.params['dry-run']) {
90
+ // Check if we're in shell mode
91
+ let isShellMode = me.params.shell || me.root().params.shell;
92
+ me.log.debug(`Shell mode is: ${isShellMode}`);
93
+
94
+ switch (event) {
95
+ case 'ide':
96
+ if (isShellMode) {
97
+ me.shellCommands.push(`${project.ide} "${project.path.path}" &`);
98
+ } else {
99
+ spawn(project.ide, [project.path.path]);
100
+ }
101
+ break;
102
+ case 'cwd':
103
+ if (isShellMode) {
104
+ me.shellCommands.push(`cd "${environment.project.path.path}"`);
105
+ } else {
106
+ spawn(process.env.SHELL, ['-i'], {
107
+ cwd: environment.project.path.path,
108
+ stdio: 'inherit'
109
+ });
110
+ }
111
+ break;
112
+ case 'web':
113
+ if (homepage) {
114
+ if (isShellMode) {
115
+ // Different approaches based on OS
116
+ let openCmd;
117
+ switch (process.platform) {
118
+ case 'darwin': openCmd = 'open'; break;
119
+ case 'win32': openCmd = 'start'; break;
120
+ default: openCmd = 'xdg-open'; break;
121
+ }
122
+ me.shellCommands.push(`${openCmd} "${homepage}" &`);
123
+ } else {
124
+ require("openurl2").open(homepage);
125
+ }
126
+ }
127
+ break;
128
+ case 'claude':
129
+ if (isShellMode) {
130
+ me.shellCommands.push(`claude &`);
131
+ } else {
132
+ spawn('claude', [], {
133
+ cwd: environment.project.path.path,
134
+ stdio: 'inherit'
135
+ });
136
+ }
137
+ break;
138
+ }
98
139
  }
99
140
  if (`before${capitalEvt}` in scripts) {
100
141
  me.log.debug(`Found 'after' script, unfortunately scripts are not yet supported.`);
@@ -103,8 +144,13 @@ class open extends command {
103
144
  }
104
145
 
105
146
  open.define({
106
- switches: '[debug:boolean=false]',
107
- parameters: '[identifier:string]'
147
+ help: {
148
+ '': 'open a project by passing its project id',
149
+ project: 'The id of the project to open',
150
+ shell: 'Output shell commands instead of spawning processes'
151
+ },
152
+ switches: '[d#debug:boolean=false] [n#dry-run:boolean=false] [shell:boolean=false]',
153
+ parameters: '[p#project:string]'
108
154
  });
109
155
 
110
156
  module.exports = open;
package/docs/ideas.md ADDED
@@ -0,0 +1,49 @@
1
+ # Ideas for workon
2
+
3
+ This document contains ideas for future enhancements to the workon project.
4
+
5
+ ## Interactive Project Management
6
+
7
+ ### Project Configuration Editor
8
+ Create an interactive mode for editing project configurations through guided prompts instead of manual file editing.
9
+
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
36
+
37
+ ### Advanced claude Event Options
38
+ Extend the claude event with more configuration options:
39
+ ```json
40
+ "claude": {
41
+ "mode": "interactive",
42
+ "flags": ["--resume"],
43
+ "project_context": true
44
+ }
45
+ ```
46
+
47
+ ## Future Ideas
48
+
49
+ *Add more ideas here as they come up...*
@@ -64,14 +64,21 @@ class EnvironmentRecognizer {
64
64
  let allProjects = me.projects;
65
65
  if (!allProjects || refresh) {
66
66
  let defaults = me.config.get('project_defaults');
67
+ if (!defaults || !defaults.base) {
68
+ allProjects = [];
69
+ me.projects = allProjects;
70
+ return allProjects;
71
+ }
67
72
  let baseDir = File.from(defaults.base);
68
73
  let projectsMap = me.config.get('projects');
69
74
  allProjects = [];
70
- for (let name in projectsMap) {
71
- let project = projectsMap[name];
72
- project.name = name;
73
- project.path = baseDir.join(project.path);
74
- allProjects.push(project);
75
+ if (projectsMap) {
76
+ for (let name in projectsMap) {
77
+ let project = projectsMap[name];
78
+ project.name = name;
79
+ project.path = baseDir.join(project.path);
80
+ allProjects.push(project);
81
+ }
75
82
  }
76
83
  me.projects = allProjects;
77
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workon",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.1.0",
4
4
  "description": "Work on something great!",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git@code.palu.io:israel/workon.git"
11
+ "url": "git+ssh://git@github.com/israelroldan/workon.git"
12
12
  },
13
13
  "keywords": [
14
14
  "productivity"
@@ -36,5 +36,7 @@
36
36
  "simple-git": "^1.73.0",
37
37
  "switchit": "^1.0.7"
38
38
  },
39
- "bin": "bin/workon"
39
+ "bin": {
40
+ "workon": "bin/workon"
41
+ }
40
42
  }
package/.npmignore DELETED
@@ -1 +0,0 @@
1
- bin/workon-debug