workon 1.0.0 → 1.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.
- package/.cursorindexingignore +3 -0
- package/.history/package_20250806214300.json +42 -0
- package/.history/package_20250806215528.json +43 -0
- package/.vscode/terminals.json +11 -0
- package/CHANGELOG.md +26 -0
- package/cli/index.js +35 -2
- package/cli/manage.js +381 -0
- package/cli/open.js +10 -0
- package/docs/ideas.md +49 -0
- package/lib/validation.js +104 -0
- package/package.json +3 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workon",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Work on something great!",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"release": "standard-version"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+ssh://git@github.com/israelroldan/workon.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"productivity"
|
|
15
|
+
],
|
|
16
|
+
"author": "Israel Roldan (me@isro.me)",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"cz-conventional-changelog": "^2.0.0",
|
|
20
|
+
"standard-version": "^4.2.0"
|
|
21
|
+
},
|
|
22
|
+
"config": {
|
|
23
|
+
"commitizen": {
|
|
24
|
+
"path": "./node_modules/cz-conventional-changelog"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"conf": "^1.1.2",
|
|
29
|
+
"deep-assign": "^2.0.0",
|
|
30
|
+
"flat": "^2.0.1",
|
|
31
|
+
"inquirer": "^3.1.1",
|
|
32
|
+
"loog": "^1.4.0",
|
|
33
|
+
"omelette": "^0.4.4",
|
|
34
|
+
"openurl2": "^1.0.1",
|
|
35
|
+
"phylo": "^1.0.0-beta.7",
|
|
36
|
+
"simple-git": "^1.73.0",
|
|
37
|
+
"switchit": "^1.0.7"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"workon": "bin/workon"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "workon",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Work on something great!",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"release": "standard-version"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+ssh://git@github.com/israelroldan/workon.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"productivity"
|
|
15
|
+
],
|
|
16
|
+
"author": "Israel Roldan (me@isro.me)",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"cz-conventional-changelog": "^2.0.0",
|
|
20
|
+
"standard-version": "^4.2.0"
|
|
21
|
+
},
|
|
22
|
+
"config": {
|
|
23
|
+
"commitizen": {
|
|
24
|
+
"path": "./node_modules/cz-conventional-changelog"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"conf": "^1.1.2",
|
|
29
|
+
"deep-assign": "^2.0.0",
|
|
30
|
+
"flat": "^2.0.1",
|
|
31
|
+
"inquirer": "^3.1.1",
|
|
32
|
+
"loog": "^1.4.0",
|
|
33
|
+
"omelette": "^0.4.4",
|
|
34
|
+
"openurl2": "^1.0.1",
|
|
35
|
+
"phylo": "^1.0.0-beta.7",
|
|
36
|
+
"simple-git": "^1.73.0",
|
|
37
|
+
"switchit": "^1.0.7"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"workon": "bin/workon",
|
|
41
|
+
"wo": "bin/workon"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
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.2.0"></a>
|
|
6
|
+
# [1.2.0](https://github.com/israelroldan/workon/compare/v1.1.0...v1.2.0) (2025-08-06)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Introduce interactive project management feature ([3157e27](https://github.com/israelroldan/workon/commit/3157e27))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
<a name="1.1.0"></a>
|
|
16
|
+
# [1.1.0](https://github.com/israelroldan/workon/compare/v1.0.0...v1.1.0) (2025-08-06)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* Update package.json format for npm publish ([6c5e9e0](https://github.com/israelroldan/workon/commit/6c5e9e0))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* Enhance CLI with new 'claude' command support ([70b3f96](https://github.com/israelroldan/workon/commit/70b3f96))
|
|
27
|
+
* Improve shell integration for workon command ([f5b6ff0](https://github.com/israelroldan/workon/commit/f5b6ff0))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
5
31
|
<a name="1.0.0-alpha.1"></a>
|
|
6
32
|
# [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
33
|
|
package/cli/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const {EnvironmentRecognizer} = require('../lib/environment');
|
|
|
8
8
|
const config = require('./config');
|
|
9
9
|
const interactive = require('./interactive');
|
|
10
10
|
const open = require('./open');
|
|
11
|
+
const manage = require('./manage');
|
|
11
12
|
|
|
12
13
|
class workon extends container {
|
|
13
14
|
constructor (log) {
|
|
@@ -83,15 +84,46 @@ class workon extends container {
|
|
|
83
84
|
|
|
84
85
|
outputShellInit () {
|
|
85
86
|
let me = this;
|
|
87
|
+
|
|
88
|
+
// Get list of available commands from switchit
|
|
89
|
+
let cmdNames = me.constructor.getAspects().commands.filter((c) => !!c).map((c) => c.name);
|
|
90
|
+
|
|
91
|
+
// Get list of available switches from switchit
|
|
92
|
+
let switches = me.constructor.getAspects().switches || [];
|
|
93
|
+
let switchFlags = [];
|
|
94
|
+
switches.forEach(sw => {
|
|
95
|
+
switchFlags.push('--' + sw.name);
|
|
96
|
+
if (sw.char) {
|
|
97
|
+
switchFlags.push('-' + sw.char);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Add built-in switchit flags (help and version are automatically added by switchit)
|
|
102
|
+
let builtinFlags = ['--help', '-h', '--version', '-v', 'help'];
|
|
103
|
+
|
|
104
|
+
// Combine all non-shell commands and flags, removing duplicates
|
|
105
|
+
let nonShellCommands = [...new Set(cmdNames.concat(switchFlags).concat(builtinFlags))];
|
|
106
|
+
let casePattern = nonShellCommands.join('|');
|
|
107
|
+
|
|
86
108
|
// Generate shell function that wraps workon calls
|
|
87
109
|
let shellFunction = `
|
|
88
110
|
# workon shell integration
|
|
89
111
|
workon() {
|
|
90
|
-
|
|
112
|
+
# Commands and flags that should NOT use shell mode
|
|
113
|
+
case "$1" in
|
|
114
|
+
${casePattern})
|
|
115
|
+
command workon "$@"
|
|
116
|
+
return $?
|
|
117
|
+
;;
|
|
118
|
+
esac
|
|
119
|
+
|
|
120
|
+
# If no arguments provided, run interactive mode directly
|
|
121
|
+
if [[ $# -eq 0 ]]; then
|
|
91
122
|
command workon "$@"
|
|
92
123
|
return $?
|
|
93
124
|
fi
|
|
94
125
|
|
|
126
|
+
# Default behavior: use shell mode for project opening
|
|
95
127
|
local output
|
|
96
128
|
output=$(command workon --shell "$@" 2>&1)
|
|
97
129
|
local exit_code=$?
|
|
@@ -144,7 +176,8 @@ workon.define({
|
|
|
144
176
|
private: true
|
|
145
177
|
},
|
|
146
178
|
open,
|
|
147
|
-
config
|
|
179
|
+
config,
|
|
180
|
+
manage
|
|
148
181
|
}
|
|
149
182
|
});
|
|
150
183
|
|
package/cli/manage.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
const { command } = require('./base');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const File = require('phylo');
|
|
4
|
+
const ProjectValidator = require('../lib/validation');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
class manage extends command {
|
|
8
|
+
execute(params) {
|
|
9
|
+
let me = this;
|
|
10
|
+
me.validator = new ProjectValidator(me.config);
|
|
11
|
+
me.showLogo();
|
|
12
|
+
return me.startManagement();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
showLogo() {
|
|
16
|
+
let me = this;
|
|
17
|
+
let version = me.config.get('pkg').version;
|
|
18
|
+
|
|
19
|
+
if (!this._hasShownLogo) {
|
|
20
|
+
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`);
|
|
21
|
+
this._hasShownLogo = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async startManagement() {
|
|
26
|
+
let me = this;
|
|
27
|
+
const projects = me.config.get('projects') || {};
|
|
28
|
+
const projectNames = Object.keys(projects);
|
|
29
|
+
|
|
30
|
+
const mainMenu = {
|
|
31
|
+
type: 'list',
|
|
32
|
+
name: 'action',
|
|
33
|
+
message: 'Project Management',
|
|
34
|
+
choices: [
|
|
35
|
+
{
|
|
36
|
+
name: 'Create new project',
|
|
37
|
+
value: 'create'
|
|
38
|
+
},
|
|
39
|
+
...(projectNames.length > 0 ? [
|
|
40
|
+
{
|
|
41
|
+
name: 'Edit existing project',
|
|
42
|
+
value: 'edit'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Delete project',
|
|
46
|
+
value: 'delete'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'List all projects',
|
|
50
|
+
value: 'list'
|
|
51
|
+
}
|
|
52
|
+
] : []),
|
|
53
|
+
new inquirer.Separator(),
|
|
54
|
+
{
|
|
55
|
+
name: 'Exit',
|
|
56
|
+
value: 'exit'
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const answer = await inquirer.prompt([mainMenu]);
|
|
62
|
+
|
|
63
|
+
switch (answer.action) {
|
|
64
|
+
case 'create':
|
|
65
|
+
return me.createProject();
|
|
66
|
+
case 'edit':
|
|
67
|
+
return me.editProject();
|
|
68
|
+
case 'delete':
|
|
69
|
+
return me.deleteProject();
|
|
70
|
+
case 'list':
|
|
71
|
+
return me.listProjects();
|
|
72
|
+
case 'exit':
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async createProject() {
|
|
78
|
+
let me = this;
|
|
79
|
+
const defaults = me.config.get('project_defaults') || {};
|
|
80
|
+
const projects = me.config.get('projects') || {};
|
|
81
|
+
|
|
82
|
+
me.log.log('\n🚀 Create New Project\n');
|
|
83
|
+
|
|
84
|
+
const questions = [
|
|
85
|
+
{
|
|
86
|
+
type: 'input',
|
|
87
|
+
name: 'name',
|
|
88
|
+
message: 'Project name:',
|
|
89
|
+
default: File.cwd().name,
|
|
90
|
+
validate: (value) => me.validator.validateProjectName(value, projects)
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'input',
|
|
94
|
+
name: 'path',
|
|
95
|
+
message: 'Project path (relative to base directory):',
|
|
96
|
+
default: (answers) => answers.name,
|
|
97
|
+
validate: (value) => {
|
|
98
|
+
const basePath = defaults.base || process.cwd();
|
|
99
|
+
return me.validator.validateProjectPath(value, basePath);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: 'list',
|
|
104
|
+
name: 'ide',
|
|
105
|
+
message: 'IDE/Editor:',
|
|
106
|
+
choices: [
|
|
107
|
+
{ name: 'Visual Studio Code', value: 'code' },
|
|
108
|
+
{ name: 'IntelliJ IDEA', value: 'idea' },
|
|
109
|
+
{ name: 'Atom', value: 'atom' },
|
|
110
|
+
{ name: 'Sublime Text', value: 'subl' },
|
|
111
|
+
{ name: 'Vim', value: 'vim' },
|
|
112
|
+
{ name: 'Emacs', value: 'emacs' }
|
|
113
|
+
],
|
|
114
|
+
default: defaults.ide || 'code'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: 'input',
|
|
118
|
+
name: 'homepage',
|
|
119
|
+
message: 'Project homepage (optional):',
|
|
120
|
+
validate: (value) => me.validator.validateUrl(value)
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: 'checkbox',
|
|
124
|
+
name: 'events',
|
|
125
|
+
message: 'Select events to enable:',
|
|
126
|
+
choices: [
|
|
127
|
+
{ name: 'Change directory (cwd)', value: 'cwd', checked: true },
|
|
128
|
+
{ name: 'Open in IDE', value: 'ide', checked: true },
|
|
129
|
+
{ name: 'Open homepage in browser', value: 'web' },
|
|
130
|
+
{ name: 'Launch Claude Code', value: 'claude' }
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const answers = await inquirer.prompt(questions);
|
|
136
|
+
|
|
137
|
+
// Convert events array to object
|
|
138
|
+
const events = {};
|
|
139
|
+
answers.events.forEach(event => {
|
|
140
|
+
events[event] = 'true';
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const projectConfig = {
|
|
144
|
+
path: answers.path,
|
|
145
|
+
ide: answers.ide,
|
|
146
|
+
events: events
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (answers.homepage) {
|
|
150
|
+
projectConfig.homepage = answers.homepage;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const confirm = await inquirer.prompt([{
|
|
154
|
+
type: 'confirm',
|
|
155
|
+
name: 'confirmed',
|
|
156
|
+
message: 'Create this project?',
|
|
157
|
+
default: true
|
|
158
|
+
}]);
|
|
159
|
+
|
|
160
|
+
if (confirm.confirmed) {
|
|
161
|
+
projects[answers.name] = projectConfig;
|
|
162
|
+
me.config.set('projects', projects);
|
|
163
|
+
me.log.log(`\n✅ Project '${answers.name}' created successfully!`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const continuePrompt = await inquirer.prompt([{
|
|
167
|
+
type: 'confirm',
|
|
168
|
+
name: 'continue',
|
|
169
|
+
message: 'Return to main menu?',
|
|
170
|
+
default: true
|
|
171
|
+
}]);
|
|
172
|
+
|
|
173
|
+
if (continuePrompt.continue) {
|
|
174
|
+
return me.startManagement();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async editProject() {
|
|
179
|
+
let me = this;
|
|
180
|
+
const projects = me.config.get('projects') || {};
|
|
181
|
+
const projectNames = Object.keys(projects);
|
|
182
|
+
|
|
183
|
+
if (projectNames.length === 0) {
|
|
184
|
+
me.log.log('No projects found.');
|
|
185
|
+
return me.startManagement();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const projectChoice = await inquirer.prompt([{
|
|
189
|
+
type: 'list',
|
|
190
|
+
name: 'projectName',
|
|
191
|
+
message: 'Select project to edit:',
|
|
192
|
+
choices: projectNames.map(name => ({
|
|
193
|
+
name: `${name} (${projects[name].path})`,
|
|
194
|
+
value: name
|
|
195
|
+
}))
|
|
196
|
+
}]);
|
|
197
|
+
|
|
198
|
+
const project = projects[projectChoice.projectName];
|
|
199
|
+
const currentEvents = Object.keys(project.events || {}).filter(key => project.events[key] === 'true');
|
|
200
|
+
|
|
201
|
+
me.log.log(`\n✏️ Edit Project: ${projectChoice.projectName}\n`);
|
|
202
|
+
|
|
203
|
+
const questions = [
|
|
204
|
+
{
|
|
205
|
+
type: 'input',
|
|
206
|
+
name: 'path',
|
|
207
|
+
message: 'Project path:',
|
|
208
|
+
default: project.path,
|
|
209
|
+
validate: (value) => {
|
|
210
|
+
const defaults = me.config.get('project_defaults') || {};
|
|
211
|
+
const basePath = defaults.base || process.cwd();
|
|
212
|
+
return me.validator.validateProjectPath(value, basePath);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
type: 'list',
|
|
217
|
+
name: 'ide',
|
|
218
|
+
message: 'IDE/Editor:',
|
|
219
|
+
choices: [
|
|
220
|
+
{ name: 'Visual Studio Code', value: 'code' },
|
|
221
|
+
{ name: 'IntelliJ IDEA', value: 'idea' },
|
|
222
|
+
{ name: 'Atom', value: 'atom' },
|
|
223
|
+
{ name: 'Sublime Text', value: 'subl' },
|
|
224
|
+
{ name: 'Vim', value: 'vim' },
|
|
225
|
+
{ name: 'Emacs', value: 'emacs' }
|
|
226
|
+
],
|
|
227
|
+
default: project.ide || 'code'
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
type: 'input',
|
|
231
|
+
name: 'homepage',
|
|
232
|
+
message: 'Project homepage:',
|
|
233
|
+
default: project.homepage || '',
|
|
234
|
+
validate: (value) => me.validator.validateUrl(value)
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
type: 'checkbox',
|
|
238
|
+
name: 'events',
|
|
239
|
+
message: 'Select events to enable:',
|
|
240
|
+
choices: [
|
|
241
|
+
{ name: 'Change directory (cwd)', value: 'cwd', checked: currentEvents.includes('cwd') },
|
|
242
|
+
{ name: 'Open in IDE', value: 'ide', checked: currentEvents.includes('ide') },
|
|
243
|
+
{ name: 'Open homepage in browser', value: 'web', checked: currentEvents.includes('web') },
|
|
244
|
+
{ name: 'Launch Claude Code', value: 'claude', checked: currentEvents.includes('claude') }
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
const answers = await inquirer.prompt(questions);
|
|
250
|
+
|
|
251
|
+
// Convert events array to object
|
|
252
|
+
const events = {};
|
|
253
|
+
answers.events.forEach(event => {
|
|
254
|
+
events[event] = 'true';
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const updatedProject = {
|
|
258
|
+
path: answers.path,
|
|
259
|
+
ide: answers.ide,
|
|
260
|
+
events: events
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
if (answers.homepage) {
|
|
264
|
+
updatedProject.homepage = answers.homepage;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const confirm = await inquirer.prompt([{
|
|
268
|
+
type: 'confirm',
|
|
269
|
+
name: 'confirmed',
|
|
270
|
+
message: 'Save changes?',
|
|
271
|
+
default: true
|
|
272
|
+
}]);
|
|
273
|
+
|
|
274
|
+
if (confirm.confirmed) {
|
|
275
|
+
projects[projectChoice.projectName] = updatedProject;
|
|
276
|
+
me.config.set('projects', projects);
|
|
277
|
+
me.log.log(`\n✅ Project '${projectChoice.projectName}' updated successfully!`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const continuePrompt = await inquirer.prompt([{
|
|
281
|
+
type: 'confirm',
|
|
282
|
+
name: 'continue',
|
|
283
|
+
message: 'Return to main menu?',
|
|
284
|
+
default: true
|
|
285
|
+
}]);
|
|
286
|
+
|
|
287
|
+
if (continuePrompt.continue) {
|
|
288
|
+
return me.startManagement();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async deleteProject() {
|
|
293
|
+
let me = this;
|
|
294
|
+
const projects = me.config.get('projects') || {};
|
|
295
|
+
const projectNames = Object.keys(projects);
|
|
296
|
+
|
|
297
|
+
if (projectNames.length === 0) {
|
|
298
|
+
me.log.log('No projects found.');
|
|
299
|
+
return me.startManagement();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const projectChoice = await inquirer.prompt([{
|
|
303
|
+
type: 'list',
|
|
304
|
+
name: 'projectName',
|
|
305
|
+
message: 'Select project to delete:',
|
|
306
|
+
choices: projectNames.map(name => ({
|
|
307
|
+
name: `${name} (${projects[name].path})`,
|
|
308
|
+
value: name
|
|
309
|
+
}))
|
|
310
|
+
}]);
|
|
311
|
+
|
|
312
|
+
const confirm = await inquirer.prompt([{
|
|
313
|
+
type: 'confirm',
|
|
314
|
+
name: 'confirmed',
|
|
315
|
+
message: `Are you sure you want to delete '${projectChoice.projectName}'?`,
|
|
316
|
+
default: false
|
|
317
|
+
}]);
|
|
318
|
+
|
|
319
|
+
if (confirm.confirmed) {
|
|
320
|
+
delete projects[projectChoice.projectName];
|
|
321
|
+
me.config.set('projects', projects);
|
|
322
|
+
me.log.log(`\n✅ Project '${projectChoice.projectName}' deleted successfully!`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const continuePrompt = await inquirer.prompt([{
|
|
326
|
+
type: 'confirm',
|
|
327
|
+
name: 'continue',
|
|
328
|
+
message: 'Return to main menu?',
|
|
329
|
+
default: true
|
|
330
|
+
}]);
|
|
331
|
+
|
|
332
|
+
if (continuePrompt.continue) {
|
|
333
|
+
return me.startManagement();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async listProjects() {
|
|
338
|
+
let me = this;
|
|
339
|
+
const projects = me.config.get('projects') || {};
|
|
340
|
+
const projectNames = Object.keys(projects);
|
|
341
|
+
|
|
342
|
+
if (projectNames.length === 0) {
|
|
343
|
+
me.log.log('No projects found.');
|
|
344
|
+
return me.startManagement();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
me.log.log('\n📋 All Projects:\n');
|
|
348
|
+
|
|
349
|
+
projectNames.forEach(name => {
|
|
350
|
+
const project = projects[name];
|
|
351
|
+
const events = Object.keys(project.events || {}).filter(key => project.events[key] === 'true');
|
|
352
|
+
|
|
353
|
+
me.log.log(`${name}:`);
|
|
354
|
+
me.log.log(` Path: ${project.path}`);
|
|
355
|
+
me.log.log(` IDE: ${project.ide}`);
|
|
356
|
+
if (project.homepage) {
|
|
357
|
+
me.log.log(` Homepage: ${project.homepage}`);
|
|
358
|
+
}
|
|
359
|
+
me.log.log(` Events: ${events.join(', ') || 'none'}`);
|
|
360
|
+
me.log.log('');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const continuePrompt = await inquirer.prompt([{
|
|
364
|
+
type: 'confirm',
|
|
365
|
+
name: 'continue',
|
|
366
|
+
message: 'Return to main menu?',
|
|
367
|
+
default: true
|
|
368
|
+
}]);
|
|
369
|
+
|
|
370
|
+
if (continuePrompt.continue) {
|
|
371
|
+
return me.startManagement();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
manage.define({
|
|
377
|
+
help: 'Interactive project management',
|
|
378
|
+
switches: '[d#debug:boolean=false] [h#help:boolean=false]'
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
module.exports = manage;
|
package/cli/open.js
CHANGED
|
@@ -125,6 +125,16 @@ class open extends command {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
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;
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
140
|
if (`before${capitalEvt}` in scripts) {
|
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...*
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const File = require('phylo');
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
class ProjectValidator {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
validateProjectName(name, existingProjects = {}) {
|
|
11
|
+
if (!name || name.trim() === '') {
|
|
12
|
+
return 'Project name cannot be empty';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (name in existingProjects) {
|
|
16
|
+
return 'Project already exists';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
20
|
+
return 'Project name can only contain letters, numbers, underscores, and hyphens';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
validateProjectPath(projectPath, basePath) {
|
|
27
|
+
if (!projectPath || projectPath.trim() === '') {
|
|
28
|
+
return 'Project path cannot be empty';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fullPath = basePath ? path.resolve(basePath, projectPath) : path.resolve(projectPath);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const fileObj = new File(fullPath);
|
|
35
|
+
if (!fileObj.exists) {
|
|
36
|
+
return `Directory does not exist: ${fullPath}`;
|
|
37
|
+
}
|
|
38
|
+
if (!fileObj.isDirectory) {
|
|
39
|
+
return `Path is not a directory: ${fullPath}`;
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return `Invalid path: ${error.message}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
validateIdeCommand(ideCommand) {
|
|
49
|
+
if (!ideCommand || ideCommand.trim() === '') {
|
|
50
|
+
return 'IDE command cannot be empty';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const validIdeCommands = ['code', 'idea', 'atom', 'subl', 'vim', 'emacs'];
|
|
54
|
+
if (!validIdeCommands.includes(ideCommand)) {
|
|
55
|
+
return `Unknown IDE command. Supported: ${validIdeCommands.join(', ')}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async validateIdeAvailability(ideCommand) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const child = spawn('which', [ideCommand], { stdio: 'pipe' });
|
|
64
|
+
child.on('close', (code) => {
|
|
65
|
+
if (code === 0) {
|
|
66
|
+
resolve(true);
|
|
67
|
+
} else {
|
|
68
|
+
resolve(`IDE command '${ideCommand}' not found in PATH`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
child.on('error', () => {
|
|
72
|
+
resolve(`Could not check if '${ideCommand}' is available`);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
validateEvents(events) {
|
|
78
|
+
if (!events || typeof events !== 'object') {
|
|
79
|
+
return 'Events must be an object';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const validEvents = ['cwd', 'ide', 'web', 'claude'];
|
|
83
|
+
const invalidEvents = Object.keys(events).filter(event => !validEvents.includes(event));
|
|
84
|
+
|
|
85
|
+
if (invalidEvents.length > 0) {
|
|
86
|
+
return `Invalid events: ${invalidEvents.join(', ')}. Valid events: ${validEvents.join(', ')}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
validateUrl(url) {
|
|
93
|
+
if (!url) return true; // Optional field
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
new URL(url);
|
|
97
|
+
return true;
|
|
98
|
+
} catch {
|
|
99
|
+
return 'Invalid URL format';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = ProjectValidator;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workon",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Work on something great!",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"switchit": "^1.0.7"
|
|
38
38
|
},
|
|
39
39
|
"bin": {
|
|
40
|
-
"workon": "bin/workon"
|
|
40
|
+
"workon": "bin/workon",
|
|
41
|
+
"wo": "bin/workon"
|
|
41
42
|
}
|
|
42
43
|
}
|