workon 2.1.3 → 3.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.
Files changed (66) hide show
  1. package/README.md +19 -4
  2. package/bin/workon +1 -11
  3. package/dist/cli.js +2364 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.cjs +1216 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +280 -0
  8. package/dist/index.d.ts +280 -0
  9. package/dist/index.js +1173 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +68 -21
  12. package/.claude/settings.local.json +0 -11
  13. package/.cursorindexingignore +0 -3
  14. package/.history/.gitignore_20250806202718 +0 -30
  15. package/.history/.gitignore_20250806231444 +0 -32
  16. package/.history/.gitignore_20250806231450 +0 -32
  17. package/.history/lib/tmux_20250806233103.js +0 -109
  18. package/.history/lib/tmux_20250806233219.js +0 -109
  19. package/.history/lib/tmux_20250806233223.js +0 -109
  20. package/.history/lib/tmux_20250806233230.js +0 -109
  21. package/.history/lib/tmux_20250806233231.js +0 -109
  22. package/.history/lib/tmux_20250807120751.js +0 -190
  23. package/.history/lib/tmux_20250807120757.js +0 -190
  24. package/.history/lib/tmux_20250807120802.js +0 -190
  25. package/.history/lib/tmux_20250807120808.js +0 -190
  26. package/.history/package_20250807114243.json +0 -43
  27. package/.history/package_20250807114257.json +0 -43
  28. package/.history/package_20250807114404.json +0 -43
  29. package/.history/package_20250807114409.json +0 -43
  30. package/.history/package_20250807114510.json +0 -43
  31. package/.history/package_20250807114637.json +0 -43
  32. package/.vscode/launch.json +0 -20
  33. package/.vscode/terminals.json +0 -11
  34. package/CHANGELOG.md +0 -110
  35. package/CLAUDE.md +0 -100
  36. package/cli/base.js +0 -16
  37. package/cli/config/index.js +0 -19
  38. package/cli/config/list.js +0 -26
  39. package/cli/config/set.js +0 -19
  40. package/cli/config/unset.js +0 -26
  41. package/cli/index.js +0 -184
  42. package/cli/interactive.js +0 -290
  43. package/cli/manage.js +0 -413
  44. package/cli/open.js +0 -414
  45. package/commands/base.js +0 -105
  46. package/commands/core/cwd/index.js +0 -86
  47. package/commands/core/ide/index.js +0 -84
  48. package/commands/core/web/index.js +0 -109
  49. package/commands/extensions/claude/index.js +0 -211
  50. package/commands/extensions/docker/index.js +0 -167
  51. package/commands/extensions/npm/index.js +0 -208
  52. package/commands/registry.js +0 -196
  53. package/demo-colon-syntax.js +0 -57
  54. package/docs/adr/001-command-centric-architecture.md +0 -304
  55. package/docs/adr/002-positional-command-arguments.md +0 -402
  56. package/docs/ideas.md +0 -93
  57. package/lib/config.js +0 -51
  58. package/lib/environment/base.js +0 -12
  59. package/lib/environment/index.js +0 -108
  60. package/lib/environment/project.js +0 -26
  61. package/lib/project.js +0 -68
  62. package/lib/tmux.js +0 -223
  63. package/lib/validation.js +0 -120
  64. package/test-architecture.js +0 -145
  65. package/test-colon-syntax.js +0 -85
  66. package/test-registry.js +0 -57
@@ -1,109 +0,0 @@
1
- const BaseCommand = require('../../base');
2
-
3
- /**
4
- * Web Command
5
- * Opens the project homepage in a web browser
6
- */
7
- class WebCommand extends BaseCommand {
8
- static get metadata() {
9
- return {
10
- name: 'web',
11
- displayName: 'Open homepage in browser',
12
- description: 'Open project homepage in web browser',
13
- category: 'core',
14
- requiresTmux: false,
15
- dependencies: []
16
- };
17
- }
18
-
19
- static get validation() {
20
- return {
21
- validateConfig(config) {
22
- // Web command accepts boolean or string 'true'
23
- if (config === true || config === 'true' || config === false || config === 'false') {
24
- return true;
25
- }
26
- return 'Web configuration must be a boolean or string "true"/"false"';
27
- }
28
- };
29
- }
30
-
31
- static get configuration() {
32
- return {
33
- async configureInteractive() {
34
- // Web event just enables/disables opening the homepage
35
- // The actual homepage URL is configured at the project level
36
- return 'true';
37
- },
38
-
39
- getDefaultConfig() {
40
- return 'true';
41
- }
42
- };
43
- }
44
-
45
- static get processing() {
46
- return {
47
- async processEvent(context) {
48
- const { project, isShellMode, shellCommands } = context;
49
- const homepage = project.homepage;
50
-
51
- if (!homepage) {
52
- // No homepage configured, skip
53
- return;
54
- }
55
-
56
- if (isShellMode) {
57
- // Different approaches based on OS
58
- let openCmd;
59
- switch (process.platform) {
60
- case 'darwin': openCmd = 'open'; break;
61
- case 'win32': openCmd = 'start'; break;
62
- default: openCmd = 'xdg-open'; break;
63
- }
64
- shellCommands.push(`${openCmd} "${homepage}" &`);
65
- } else {
66
- // In non-shell mode, use openurl2
67
- require("openurl2").open(homepage);
68
- }
69
- },
70
-
71
- generateShellCommand(context) {
72
- const { project } = context;
73
- const homepage = project.homepage;
74
-
75
- if (!homepage) {
76
- return [];
77
- }
78
-
79
- let openCmd;
80
- switch (process.platform) {
81
- case 'darwin': openCmd = 'open'; break;
82
- case 'win32': openCmd = 'start'; break;
83
- default: openCmd = 'xdg-open'; break;
84
- }
85
-
86
- return [`${openCmd} "${homepage}" &`];
87
- }
88
- };
89
- }
90
-
91
- static get help() {
92
- return {
93
- usage: 'web: true',
94
- description: 'Opens the project homepage in the default web browser',
95
- examples: [
96
- {
97
- config: 'web: true',
98
- description: 'Enable opening project homepage when switching to project'
99
- },
100
- {
101
- config: 'web: false',
102
- description: 'Disable automatic homepage opening'
103
- }
104
- ]
105
- };
106
- }
107
- }
108
-
109
- module.exports = WebCommand;
@@ -1,211 +0,0 @@
1
- const BaseCommand = require('../../base');
2
- const inquirer = require('inquirer');
3
- const spawn = require('child_process').spawn;
4
-
5
- /**
6
- * Claude Command
7
- * Launches Claude Code with optional configuration
8
- */
9
- class ClaudeCommand extends BaseCommand {
10
- static get metadata() {
11
- return {
12
- name: 'claude',
13
- displayName: 'Launch Claude Code',
14
- description: 'Launch Claude Code with optional flags and configuration',
15
- category: 'development',
16
- requiresTmux: true,
17
- dependencies: ['claude']
18
- };
19
- }
20
-
21
- static get validation() {
22
- return {
23
- validateConfig(config) {
24
- if (config === true || config === 'true' || config === false || config === 'false') {
25
- return true;
26
- }
27
-
28
- if (typeof config === 'object' && config !== null) {
29
- // Validate flags if present
30
- if (config.flags) {
31
- if (!Array.isArray(config.flags)) {
32
- return 'Claude flags must be an array';
33
- }
34
-
35
- // Basic flag validation - ensure they start with - or --
36
- const invalidFlags = config.flags.filter(flag =>
37
- typeof flag !== 'string' || (!flag.startsWith('-') && !flag.startsWith('--'))
38
- );
39
-
40
- if (invalidFlags.length > 0) {
41
- return `Invalid Claude flags: ${invalidFlags.join(', ')}. Flags must start with - or --`;
42
- }
43
- }
44
-
45
- // Validate split_terminal if present
46
- if (config.split_terminal !== undefined && typeof config.split_terminal !== 'boolean') {
47
- return 'Claude split_terminal must be a boolean';
48
- }
49
-
50
- return true;
51
- }
52
-
53
- return 'Claude configuration must be a boolean, string "true"/"false", or configuration object';
54
- }
55
- };
56
- }
57
-
58
- static get configuration() {
59
- return {
60
- async configureInteractive() {
61
- console.log('\nāš™ļø Configure Claude Event\n');
62
-
63
- const claudeQuestions = [
64
- {
65
- type: 'confirm',
66
- name: 'useAdvanced',
67
- message: 'Configure advanced Claude options?',
68
- default: false
69
- }
70
- ];
71
-
72
- const claudeAnswer = await inquirer.prompt(claudeQuestions);
73
-
74
- if (!claudeAnswer.useAdvanced) {
75
- return 'true';
76
- }
77
-
78
- const advancedQuestions = [
79
- {
80
- type: 'input',
81
- name: 'flags',
82
- message: 'Claude flags (comma-separated, e.g. --resume,--debug):',
83
- filter: (input) => {
84
- if (!input.trim()) return [];
85
- return input.split(',').map(flag => flag.trim()).filter(flag => flag);
86
- }
87
- },
88
- {
89
- type: 'confirm',
90
- name: 'split_terminal',
91
- message: 'Enable split terminal (Claude + shell side-by-side with tmux)?',
92
- default: false
93
- }
94
- ];
95
-
96
- const advancedAnswers = await inquirer.prompt(advancedQuestions);
97
-
98
- const configObj = {};
99
-
100
- if (advancedAnswers.flags && advancedAnswers.flags.length > 0) {
101
- configObj.flags = advancedAnswers.flags;
102
- }
103
-
104
- if (advancedAnswers.split_terminal) {
105
- configObj.split_terminal = true;
106
- }
107
-
108
- return Object.keys(configObj).length > 0 ? configObj : 'true';
109
- },
110
-
111
- getDefaultConfig() {
112
- return 'true';
113
- }
114
- };
115
- }
116
-
117
- static get processing() {
118
- return {
119
- async processEvent(context) {
120
- const { project, isShellMode, shellCommands } = context;
121
- const claudeConfig = project.events.claude;
122
-
123
- let claudeArgs = [];
124
-
125
- // Handle advanced Claude configuration
126
- if (claudeConfig && typeof claudeConfig === 'object') {
127
- if (claudeConfig.flags && Array.isArray(claudeConfig.flags)) {
128
- claudeArgs = claudeArgs.concat(claudeConfig.flags);
129
- }
130
- }
131
-
132
- if (isShellMode) {
133
- let claudeCommand = claudeArgs.length > 0
134
- ? `claude ${claudeArgs.join(' ')}`
135
- : 'claude';
136
- shellCommands.push(claudeCommand);
137
- } else {
138
- spawn('claude', claudeArgs, {
139
- cwd: project.path.path,
140
- stdio: 'inherit'
141
- });
142
- }
143
- },
144
-
145
- generateShellCommand(context) {
146
- const { project } = context;
147
- const claudeConfig = project.events.claude;
148
-
149
- let claudeArgs = [];
150
-
151
- // Handle advanced Claude configuration
152
- if (claudeConfig && typeof claudeConfig === 'object') {
153
- if (claudeConfig.flags && Array.isArray(claudeConfig.flags)) {
154
- claudeArgs = claudeArgs.concat(claudeConfig.flags);
155
- }
156
- }
157
-
158
- const claudeCommand = claudeArgs.length > 0
159
- ? `claude ${claudeArgs.join(' ')}`
160
- : 'claude';
161
-
162
- return [claudeCommand];
163
- }
164
- };
165
- }
166
-
167
- static get tmux() {
168
- return {
169
- getLayoutPriority() {
170
- return 100; // High priority for Claude
171
- },
172
-
173
- contributeToLayout(enabledCommands) {
174
- // Claude prefers split terminal layouts
175
- const hasCwd = enabledCommands.some(cmd => cmd.name === 'cwd');
176
- const hasNpm = enabledCommands.some(cmd => cmd.name === 'npm');
177
-
178
- if (hasCwd && hasNpm) {
179
- return 'three-pane'; // Claude + Terminal + NPM
180
- } else if (hasCwd) {
181
- return 'split-terminal'; // Claude + Terminal
182
- } else {
183
- return 'single'; // Just Claude
184
- }
185
- }
186
- };
187
- }
188
-
189
- static get help() {
190
- return {
191
- usage: 'claude: <configuration>',
192
- description: 'Launch Claude Code with optional flags and configuration',
193
- examples: [
194
- {
195
- config: 'claude: true',
196
- description: 'Launch Claude Code with default settings'
197
- },
198
- {
199
- config: 'claude: { flags: ["--resume", "--debug"] }',
200
- description: 'Launch Claude with specific flags'
201
- },
202
- {
203
- config: 'claude: { split_terminal: true }',
204
- description: 'Launch Claude in split terminal with tmux'
205
- }
206
- ]
207
- };
208
- }
209
- }
210
-
211
- module.exports = ClaudeCommand;
@@ -1,167 +0,0 @@
1
- const BaseCommand = require('../../base');
2
- const inquirer = require('inquirer');
3
- const spawn = require('child_process').spawn;
4
-
5
- /**
6
- * Docker Command
7
- * Manages Docker containers for the project
8
- *
9
- * This demonstrates how easy it is to add a new command to the system!
10
- * Just create this file and the system automatically discovers it.
11
- */
12
- class DockerCommand extends BaseCommand {
13
- static get metadata() {
14
- return {
15
- name: 'docker',
16
- displayName: 'Docker container management',
17
- description: 'Start/stop Docker containers for the project',
18
- category: 'development',
19
- requiresTmux: false,
20
- dependencies: ['docker']
21
- };
22
- }
23
-
24
- static get validation() {
25
- return {
26
- validateConfig(config) {
27
- if (config === true || config === 'true' || config === false || config === 'false') {
28
- return true;
29
- }
30
-
31
- if (typeof config === 'string' && config.trim() !== '') {
32
- return true; // Docker compose file path
33
- }
34
-
35
- if (typeof config === 'object' && config !== null) {
36
- if (config.compose_file && typeof config.compose_file !== 'string') {
37
- return 'Docker compose_file must be a string';
38
- }
39
- if (config.services && !Array.isArray(config.services)) {
40
- return 'Docker services must be an array';
41
- }
42
- return true;
43
- }
44
-
45
- return 'Docker configuration must be a boolean, string, or configuration object';
46
- }
47
- };
48
- }
49
-
50
- static get configuration() {
51
- return {
52
- async configureInteractive() {
53
- console.log('\n🐳 Configure Docker Event\n');
54
-
55
- const dockerQuestions = [
56
- {
57
- type: 'input',
58
- name: 'compose_file',
59
- message: 'Docker Compose file path (relative to project):',
60
- default: 'docker-compose.yml'
61
- },
62
- {
63
- type: 'input',
64
- name: 'services',
65
- message: 'Services to start (comma-separated, or leave empty for all):',
66
- filter: (input) => {
67
- if (!input.trim()) return [];
68
- return input.split(',').map(s => s.trim()).filter(s => s);
69
- }
70
- }
71
- ];
72
-
73
- const answers = await inquirer.prompt(dockerQuestions);
74
-
75
- const config = {
76
- compose_file: answers.compose_file
77
- };
78
-
79
- if (answers.services.length > 0) {
80
- config.services = answers.services;
81
- }
82
-
83
- return config;
84
- },
85
-
86
- getDefaultConfig() {
87
- return { compose_file: 'docker-compose.yml' };
88
- }
89
- };
90
- }
91
-
92
- static get processing() {
93
- return {
94
- async processEvent(context) {
95
- const { project, isShellMode, shellCommands } = context;
96
- const dockerConfig = project.events.docker;
97
-
98
- let composeFile = 'docker-compose.yml';
99
- let services = [];
100
-
101
- if (typeof dockerConfig === 'string') {
102
- composeFile = dockerConfig;
103
- } else if (dockerConfig && typeof dockerConfig === 'object') {
104
- composeFile = dockerConfig.compose_file || composeFile;
105
- services = dockerConfig.services || [];
106
- }
107
-
108
- const servicesArg = services.length > 0 ? services.join(' ') : '';
109
- const dockerCommand = `docker-compose -f ${composeFile} up -d ${servicesArg}`.trim();
110
-
111
- if (isShellMode) {
112
- shellCommands.push(dockerCommand);
113
- } else {
114
- const args = ['-f', composeFile, 'up', '-d'];
115
- if (services.length > 0) {
116
- args.push(...services);
117
- }
118
- spawn('docker-compose', args, {
119
- cwd: project.path.path,
120
- stdio: 'inherit'
121
- });
122
- }
123
- },
124
-
125
- generateShellCommand(context) {
126
- const { project } = context;
127
- const dockerConfig = project.events.docker;
128
-
129
- let composeFile = 'docker-compose.yml';
130
- let services = [];
131
-
132
- if (typeof dockerConfig === 'string') {
133
- composeFile = dockerConfig;
134
- } else if (dockerConfig && typeof dockerConfig === 'object') {
135
- composeFile = dockerConfig.compose_file || composeFile;
136
- services = dockerConfig.services || [];
137
- }
138
-
139
- const servicesArg = services.length > 0 ? services.join(' ') : '';
140
- return [`docker-compose -f ${composeFile} up -d ${servicesArg}`.trim()];
141
- }
142
- };
143
- }
144
-
145
- static get help() {
146
- return {
147
- usage: 'docker: <compose-file> | <configuration>',
148
- description: 'Start Docker containers using docker-compose',
149
- examples: [
150
- {
151
- config: 'docker: true',
152
- description: 'Start containers using default docker-compose.yml'
153
- },
154
- {
155
- config: 'docker: "docker-compose.dev.yml"',
156
- description: 'Use specific compose file'
157
- },
158
- {
159
- config: 'docker: { compose_file: "docker-compose.yml", services: ["web", "db"] }',
160
- description: 'Start only specific services'
161
- }
162
- ]
163
- };
164
- }
165
- }
166
-
167
- module.exports = DockerCommand;
@@ -1,208 +0,0 @@
1
- const BaseCommand = require('../../base');
2
- const inquirer = require('inquirer');
3
- const spawn = require('child_process').spawn;
4
-
5
- /**
6
- * NPM Command
7
- * Executes NPM scripts in the project directory
8
- */
9
- class NpmCommand extends BaseCommand {
10
- static get metadata() {
11
- return {
12
- name: 'npm',
13
- displayName: 'Run NPM command',
14
- description: 'Execute NPM scripts in project directory',
15
- category: 'development',
16
- requiresTmux: true,
17
- dependencies: ['npm']
18
- };
19
- }
20
-
21
- static get validation() {
22
- return {
23
- validateConfig(config) {
24
- if (config === true || config === 'true' || config === false || config === 'false') {
25
- return true;
26
- }
27
-
28
- if (typeof config === 'string' && config.trim() !== '') {
29
- return true; // Simple string command
30
- }
31
-
32
- if (typeof config === 'object' && config !== null) {
33
- // Validate command if present
34
- if (config.command) {
35
- if (typeof config.command !== 'string' || config.command.trim() === '') {
36
- return 'NPM command must be a non-empty string';
37
- }
38
- }
39
-
40
- // Validate watch if present
41
- if (config.watch !== undefined && typeof config.watch !== 'boolean') {
42
- return 'NPM watch must be a boolean';
43
- }
44
-
45
- // Validate auto_restart if present
46
- if (config.auto_restart !== undefined && typeof config.auto_restart !== 'boolean') {
47
- return 'NPM auto_restart must be a boolean';
48
- }
49
-
50
- return true;
51
- }
52
-
53
- return 'NPM configuration must be a boolean, string command, or configuration object';
54
- }
55
- };
56
- }
57
-
58
- static get configuration() {
59
- return {
60
- async configureInteractive() {
61
- console.log('\nšŸ“¦ Configure NPM Event\n');
62
-
63
- const npmQuestions = [
64
- {
65
- type: 'input',
66
- name: 'command',
67
- message: 'NPM script to run (e.g., dev, start, test):',
68
- default: 'dev',
69
- validate: (value) => {
70
- if (!value.trim()) {
71
- return 'NPM command cannot be empty';
72
- }
73
- return true;
74
- }
75
- },
76
- {
77
- type: 'confirm',
78
- name: 'useAdvanced',
79
- message: 'Configure advanced NPM options?',
80
- default: false
81
- }
82
- ];
83
-
84
- const basicAnswers = await inquirer.prompt(npmQuestions);
85
-
86
- if (!basicAnswers.useAdvanced) {
87
- return basicAnswers.command;
88
- }
89
-
90
- const advancedQuestions = [
91
- {
92
- type: 'confirm',
93
- name: 'watch',
94
- message: 'Enable watch mode (if supported by command)?',
95
- default: true
96
- },
97
- {
98
- type: 'confirm',
99
- name: 'auto_restart',
100
- message: 'Auto-restart on crashes?',
101
- default: false
102
- }
103
- ];
104
-
105
- const advancedAnswers = await inquirer.prompt(advancedQuestions);
106
-
107
- const configObj = {
108
- command: basicAnswers.command
109
- };
110
-
111
- if (advancedAnswers.watch) {
112
- configObj.watch = true;
113
- }
114
-
115
- if (advancedAnswers.auto_restart) {
116
- configObj.auto_restart = true;
117
- }
118
-
119
- return configObj;
120
- },
121
-
122
- getDefaultConfig() {
123
- return 'dev';
124
- }
125
- };
126
- }
127
-
128
- static get processing() {
129
- return {
130
- async processEvent(context) {
131
- const { project, isShellMode, shellCommands } = context;
132
- const npmConfig = project.events.npm;
133
- const npmCommand = NpmCommand._getNpmCommand(npmConfig);
134
-
135
- if (isShellMode) {
136
- shellCommands.push(npmCommand);
137
- } else {
138
- const scriptName = npmCommand.replace('npm run ', '');
139
- spawn('npm', ['run', scriptName], {
140
- cwd: project.path.path,
141
- stdio: 'inherit'
142
- });
143
- }
144
- },
145
-
146
- generateShellCommand(context) {
147
- const { project } = context;
148
- const npmConfig = project.events.npm;
149
- return [NpmCommand._getNpmCommand(npmConfig)];
150
- }
151
- };
152
- }
153
-
154
- static _getNpmCommand(npmConfig) {
155
- if (typeof npmConfig === 'string') {
156
- return `npm run ${npmConfig}`;
157
- } else if (npmConfig && typeof npmConfig === 'object' && npmConfig.command) {
158
- return `npm run ${npmConfig.command}`;
159
- } else {
160
- return 'npm run dev';
161
- }
162
- }
163
-
164
- static get tmux() {
165
- return {
166
- getLayoutPriority() {
167
- return 50; // Medium priority for NPM
168
- },
169
-
170
- contributeToLayout(enabledCommands) {
171
- // NPM works well in multi-pane layouts
172
- const hasCwd = enabledCommands.some(cmd => cmd.name === 'cwd');
173
- const hasClaude = enabledCommands.some(cmd => cmd.name === 'claude');
174
-
175
- if (hasCwd && hasClaude) {
176
- return 'three-pane'; // Claude + Terminal + NPM
177
- } else if (hasCwd) {
178
- return 'two-pane-npm'; // Terminal + NPM
179
- } else {
180
- return 'single'; // Just NPM
181
- }
182
- }
183
- };
184
- }
185
-
186
- static get help() {
187
- return {
188
- usage: 'npm: <script-name> | <configuration>',
189
- description: 'Execute NPM scripts in the project directory',
190
- examples: [
191
- {
192
- config: 'npm: "dev"',
193
- description: 'Run npm run dev'
194
- },
195
- {
196
- config: 'npm: { command: "test", watch: true }',
197
- description: 'Run tests in watch mode'
198
- },
199
- {
200
- config: 'npm: { command: "start", auto_restart: true }',
201
- description: 'Run start script with auto-restart on crashes'
202
- }
203
- ]
204
- };
205
- }
206
- }
207
-
208
- module.exports = NpmCommand;