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.
- package/.cursorindexingignore +3 -0
- package/.vscode/terminals.json +11 -0
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +100 -0
- package/README.md +213 -0
- package/bin/workon +4 -4
- package/cli/config/index.js +1 -0
- package/cli/config/list.js +3 -1
- package/cli/config/set.js +9 -3
- package/cli/config/unset.js +16 -3
- package/cli/index.js +102 -40
- package/cli/interactive.js +16 -6
- package/cli/open.js +83 -37
- package/docs/ideas.md +49 -0
- package/lib/environment/index.js +12 -5
- package/package.json +5 -3
- package/.npmignore +0 -1
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(
|
|
3
|
-
const
|
|
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(
|
|
10
|
-
|
|
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
|
});
|
package/cli/config/index.js
CHANGED
package/cli/config/list.js
CHANGED
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;
|
package/cli/config/unset.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
52
|
-
|
|
59
|
+
|
|
53
60
|
if (params.debug) {
|
|
54
61
|
me.log.setLogLevel('debug');
|
|
55
62
|
}
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
let
|
|
93
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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] [
|
|
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
|
}
|
package/cli/interactive.js
CHANGED
|
@@ -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
|
|
7
|
+
class interactive extends command {
|
|
8
8
|
execute (params) {
|
|
9
9
|
let me = this;
|
|
10
10
|
me.log.debug();
|
|
11
|
-
me.
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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
|
|
8
|
+
execute (params) {
|
|
9
9
|
let me = this;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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 && (
|
|
26
|
+
if (environment.$isProjectEnvironment && (project === 'this' || project === '.')) {
|
|
32
27
|
me.log.info(`Open current: ${environment.project.name}`);
|
|
33
28
|
} else {
|
|
34
|
-
if (
|
|
35
|
-
let cfg = projects[
|
|
36
|
-
cfg.name =
|
|
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 '${
|
|
40
|
-
return me.startInteractiveMode(
|
|
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 (
|
|
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([
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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...*
|
package/lib/environment/index.js
CHANGED
|
@@ -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
|
-
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.
|
|
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@
|
|
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":
|
|
39
|
+
"bin": {
|
|
40
|
+
"workon": "bin/workon"
|
|
41
|
+
}
|
|
40
42
|
}
|
package/.npmignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
bin/workon-debug
|