workon 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.history/.gitignore_20250806202718 +30 -0
- package/.history/.gitignore_20250806231444 +32 -0
- package/.history/.gitignore_20250806231450 +32 -0
- package/.history/lib/tmux_20250806233103.js +109 -0
- package/.history/lib/tmux_20250806233219.js +109 -0
- package/.history/lib/tmux_20250806233223.js +109 -0
- package/.history/lib/tmux_20250806233230.js +109 -0
- package/.history/lib/tmux_20250806233231.js +109 -0
- package/CHANGELOG.md +21 -0
- package/cli/manage.js +179 -10
- package/cli/open.js +217 -6
- package/docs/ideas.md +79 -35
- package/lib/tmux.js +190 -0
- package/lib/validation.js +71 -1
- package/package.json +1 -1
- package/.history/package_20250806214300.json +0 -42
- package/.history/package_20250806215528.json +0 -43
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ---> Node
|
|
2
|
+
# Logs
|
|
3
|
+
logs
|
|
4
|
+
*.log
|
|
5
|
+
npm-debug.log*
|
|
6
|
+
|
|
7
|
+
# Runtime data
|
|
8
|
+
pids
|
|
9
|
+
*.pid
|
|
10
|
+
*.seed
|
|
11
|
+
|
|
12
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
13
|
+
lib-cov
|
|
14
|
+
|
|
15
|
+
# Coverage directory used by tools like istanbul
|
|
16
|
+
coverage
|
|
17
|
+
|
|
18
|
+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
19
|
+
.grunt
|
|
20
|
+
|
|
21
|
+
# node-waf configuration
|
|
22
|
+
.lock-wscript
|
|
23
|
+
|
|
24
|
+
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
25
|
+
build/Release
|
|
26
|
+
|
|
27
|
+
# Dependency directory
|
|
28
|
+
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
|
29
|
+
node_modules
|
|
30
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ---> Node
|
|
2
|
+
# Logs
|
|
3
|
+
logs
|
|
4
|
+
*.log
|
|
5
|
+
npm-debug.log*
|
|
6
|
+
|
|
7
|
+
# Runtime data
|
|
8
|
+
pids
|
|
9
|
+
*.pid
|
|
10
|
+
*.seed
|
|
11
|
+
|
|
12
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
13
|
+
lib-cov
|
|
14
|
+
|
|
15
|
+
# Coverage directory used by tools like istanbul
|
|
16
|
+
coverage
|
|
17
|
+
|
|
18
|
+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
19
|
+
.grunt
|
|
20
|
+
|
|
21
|
+
# node-waf configuration
|
|
22
|
+
.lock-wscript
|
|
23
|
+
|
|
24
|
+
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
25
|
+
build/Release
|
|
26
|
+
|
|
27
|
+
# Dependency directory
|
|
28
|
+
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
|
29
|
+
node_modules
|
|
30
|
+
|
|
31
|
+
.history
|
|
32
|
+
.specstory
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ---> Node
|
|
2
|
+
# Logs
|
|
3
|
+
logs
|
|
4
|
+
*.log
|
|
5
|
+
npm-debug.log*
|
|
6
|
+
|
|
7
|
+
# Runtime data
|
|
8
|
+
pids
|
|
9
|
+
*.pid
|
|
10
|
+
*.seed
|
|
11
|
+
|
|
12
|
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
13
|
+
lib-cov
|
|
14
|
+
|
|
15
|
+
# Coverage directory used by tools like istanbul
|
|
16
|
+
coverage
|
|
17
|
+
|
|
18
|
+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
19
|
+
.grunt
|
|
20
|
+
|
|
21
|
+
# node-waf configuration
|
|
22
|
+
.lock-wscript
|
|
23
|
+
|
|
24
|
+
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
25
|
+
build/Release
|
|
26
|
+
|
|
27
|
+
# Dependency directory
|
|
28
|
+
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
|
29
|
+
node_modules
|
|
30
|
+
|
|
31
|
+
.history
|
|
32
|
+
.specstory
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const exec = promisify(require('child_process').exec);
|
|
4
|
+
|
|
5
|
+
class TmuxManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.sessionPrefix = 'workon-';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async isTmuxAvailable() {
|
|
11
|
+
try {
|
|
12
|
+
await exec('which tmux');
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async sessionExists(sessionName) {
|
|
20
|
+
try {
|
|
21
|
+
await exec(`tmux -CC has-session -t "${sessionName}"`);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getSessionName(projectName) {
|
|
29
|
+
return `${this.sessionPrefix}${projectName}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async killSession(sessionName) {
|
|
33
|
+
try {
|
|
34
|
+
await exec(`tmux -CC kill-session -t "${sessionName}"`);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
42
|
+
const sessionName = this.getSessionName(projectName);
|
|
43
|
+
|
|
44
|
+
// Kill existing session if it exists
|
|
45
|
+
if (await this.sessionExists(sessionName)) {
|
|
46
|
+
await this.killSession(sessionName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const claudeCommand = claudeArgs.length > 0
|
|
50
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
51
|
+
: 'claude';
|
|
52
|
+
|
|
53
|
+
// Create new tmux session with claude in the first pane
|
|
54
|
+
await exec(`tmux -CC new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
55
|
+
|
|
56
|
+
// Split window horizontally and run shell in second pane
|
|
57
|
+
await exec(`tmux -CC split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
58
|
+
|
|
59
|
+
// Set focus on claude pane (left pane)
|
|
60
|
+
await exec(`tmux -CC select-pane -t "${sessionName}:0.0"`);
|
|
61
|
+
|
|
62
|
+
return sessionName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async attachToSession(sessionName) {
|
|
66
|
+
// Check if we're already in a tmux session
|
|
67
|
+
if (process.env.TMUX) {
|
|
68
|
+
// If we're already in tmux, switch to the session
|
|
69
|
+
await exec(`tmux -CC switch-client -t "${sessionName}"`);
|
|
70
|
+
} else {
|
|
71
|
+
// If not in tmux, attach to the session
|
|
72
|
+
spawn('tmux', ['-CC', 'attach-session', '-t', sessionName], {
|
|
73
|
+
stdio: 'inherit'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
79
|
+
const sessionName = this.getSessionName(projectName);
|
|
80
|
+
const claudeCommand = claudeArgs.length > 0
|
|
81
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
82
|
+
: 'claude';
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
`# Create tmux split session for ${projectName}`,
|
|
86
|
+
`tmux -CC has-session -t "${sessionName}" 2>/dev/null && tmux -CC kill-session -t "${sessionName}"`,
|
|
87
|
+
`tmux -CC new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
88
|
+
`tmux -CC split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
89
|
+
`tmux -CC select-pane -t "${sessionName}:0.0"`,
|
|
90
|
+
process.env.TMUX
|
|
91
|
+
? `tmux -CC switch-client -t "${sessionName}"`
|
|
92
|
+
: `tmux -CC attach-session -t "${sessionName}"`
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async listWorkonSessions() {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await exec('tmux -CC list-sessions -F "#{session_name}"');
|
|
99
|
+
return stdout.trim()
|
|
100
|
+
.split('\n')
|
|
101
|
+
.filter(session => session.startsWith(this.sessionPrefix))
|
|
102
|
+
.map(session => session.replace(this.sessionPrefix, ''));
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = TmuxManager;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const exec = promisify(require('child_process').exec);
|
|
4
|
+
|
|
5
|
+
class TmuxManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.sessionPrefix = 'workon-';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async isTmuxAvailable() {
|
|
11
|
+
try {
|
|
12
|
+
await exec('which tmux');
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async sessionExists(sessionName) {
|
|
20
|
+
try {
|
|
21
|
+
await exec(`tmux has-session -t "${sessionName}"`);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getSessionName(projectName) {
|
|
29
|
+
return `${this.sessionPrefix}${projectName}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async killSession(sessionName) {
|
|
33
|
+
try {
|
|
34
|
+
await exec(`tmux kill-session -t "${sessionName}"`);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
42
|
+
const sessionName = this.getSessionName(projectName);
|
|
43
|
+
|
|
44
|
+
// Kill existing session if it exists
|
|
45
|
+
if (await this.sessionExists(sessionName)) {
|
|
46
|
+
await this.killSession(sessionName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const claudeCommand = claudeArgs.length > 0
|
|
50
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
51
|
+
: 'claude';
|
|
52
|
+
|
|
53
|
+
// Create new tmux session with claude in the first pane
|
|
54
|
+
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
55
|
+
|
|
56
|
+
// Split window horizontally and run shell in second pane
|
|
57
|
+
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
58
|
+
|
|
59
|
+
// Set focus on claude pane (left pane)
|
|
60
|
+
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
61
|
+
|
|
62
|
+
return sessionName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async attachToSession(sessionName) {
|
|
66
|
+
// Check if we're already in a tmux session
|
|
67
|
+
if (process.env.TMUX) {
|
|
68
|
+
// If we're already in tmux, switch to the session
|
|
69
|
+
await exec(`tmux switch-client -t "${sessionName}"`);
|
|
70
|
+
} else {
|
|
71
|
+
// If not in tmux, attach to the session
|
|
72
|
+
spawn('tmux', ['', 'attach-session', '-t', sessionName], {
|
|
73
|
+
stdio: 'inherit'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
79
|
+
const sessionName = this.getSessionName(projectName);
|
|
80
|
+
const claudeCommand = claudeArgs.length > 0
|
|
81
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
82
|
+
: 'claude';
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
`# Create tmux split session for ${projectName}`,
|
|
86
|
+
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
87
|
+
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
88
|
+
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
89
|
+
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
90
|
+
process.env.TMUX
|
|
91
|
+
? `tmux switch-client -t "${sessionName}"`
|
|
92
|
+
: `tmux attach-session -t "${sessionName}"`
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async listWorkonSessions() {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
99
|
+
return stdout.trim()
|
|
100
|
+
.split('\n')
|
|
101
|
+
.filter(session => session.startsWith(this.sessionPrefix))
|
|
102
|
+
.map(session => session.replace(this.sessionPrefix, ''));
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = TmuxManager;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const exec = promisify(require('child_process').exec);
|
|
4
|
+
|
|
5
|
+
class TmuxManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.sessionPrefix = 'workon-';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async isTmuxAvailable() {
|
|
11
|
+
try {
|
|
12
|
+
await exec('which tmux');
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async sessionExists(sessionName) {
|
|
20
|
+
try {
|
|
21
|
+
await exec(`tmux -CC has-session -t "${sessionName}"`);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getSessionName(projectName) {
|
|
29
|
+
return `${this.sessionPrefix}${projectName}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async killSession(sessionName) {
|
|
33
|
+
try {
|
|
34
|
+
await exec(`tmux -CC kill-session -t "${sessionName}"`);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
42
|
+
const sessionName = this.getSessionName(projectName);
|
|
43
|
+
|
|
44
|
+
// Kill existing session if it exists
|
|
45
|
+
if (await this.sessionExists(sessionName)) {
|
|
46
|
+
await this.killSession(sessionName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const claudeCommand = claudeArgs.length > 0
|
|
50
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
51
|
+
: 'claude';
|
|
52
|
+
|
|
53
|
+
// Create new tmux session with claude in the first pane
|
|
54
|
+
await exec(`tmux -CC new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
55
|
+
|
|
56
|
+
// Split window horizontally and run shell in second pane
|
|
57
|
+
await exec(`tmux -CC split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
58
|
+
|
|
59
|
+
// Set focus on claude pane (left pane)
|
|
60
|
+
await exec(`tmux -CC select-pane -t "${sessionName}:0.0"`);
|
|
61
|
+
|
|
62
|
+
return sessionName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async attachToSession(sessionName) {
|
|
66
|
+
// Check if we're already in a tmux session
|
|
67
|
+
if (process.env.TMUX) {
|
|
68
|
+
// If we're already in tmux, switch to the session
|
|
69
|
+
await exec(`tmux -CC switch-client -t "${sessionName}"`);
|
|
70
|
+
} else {
|
|
71
|
+
// If not in tmux, attach to the session
|
|
72
|
+
spawn('tmux', ['-CC', 'attach-session', '-t', sessionName], {
|
|
73
|
+
stdio: 'inherit'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
79
|
+
const sessionName = this.getSessionName(projectName);
|
|
80
|
+
const claudeCommand = claudeArgs.length > 0
|
|
81
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
82
|
+
: 'claude';
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
`# Create tmux split session for ${projectName}`,
|
|
86
|
+
`tmux -CC has-session -t "${sessionName}" 2>/dev/null && tmux -CC kill-session -t "${sessionName}"`,
|
|
87
|
+
`tmux -CC new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
88
|
+
`tmux -CC split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
89
|
+
`tmux -CC select-pane -t "${sessionName}:0.0"`,
|
|
90
|
+
process.env.TMUX
|
|
91
|
+
? `tmux -CC switch-client -t "${sessionName}"`
|
|
92
|
+
: `tmux -CC attach-session -t "${sessionName}"`
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async listWorkonSessions() {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await exec('tmux -CC list-sessions -F "#{session_name}"');
|
|
99
|
+
return stdout.trim()
|
|
100
|
+
.split('\n')
|
|
101
|
+
.filter(session => session.startsWith(this.sessionPrefix))
|
|
102
|
+
.map(session => session.replace(this.sessionPrefix, ''));
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = TmuxManager;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const exec = promisify(require('child_process').exec);
|
|
4
|
+
|
|
5
|
+
class TmuxManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.sessionPrefix = 'workon-';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async isTmuxAvailable() {
|
|
11
|
+
try {
|
|
12
|
+
await exec('which tmux');
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async sessionExists(sessionName) {
|
|
20
|
+
try {
|
|
21
|
+
await exec(`tmux has-session -t "${sessionName}"`);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getSessionName(projectName) {
|
|
29
|
+
return `${this.sessionPrefix}${projectName}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async killSession(sessionName) {
|
|
33
|
+
try {
|
|
34
|
+
await exec(`tmux kill-session -t "${sessionName}"`);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
42
|
+
const sessionName = this.getSessionName(projectName);
|
|
43
|
+
|
|
44
|
+
// Kill existing session if it exists
|
|
45
|
+
if (await this.sessionExists(sessionName)) {
|
|
46
|
+
await this.killSession(sessionName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const claudeCommand = claudeArgs.length > 0
|
|
50
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
51
|
+
: 'claude';
|
|
52
|
+
|
|
53
|
+
// Create new tmux session with claude in the first pane
|
|
54
|
+
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
55
|
+
|
|
56
|
+
// Split window horizontally and run shell in second pane
|
|
57
|
+
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
58
|
+
|
|
59
|
+
// Set focus on claude pane (left pane)
|
|
60
|
+
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
61
|
+
|
|
62
|
+
return sessionName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async attachToSession(sessionName) {
|
|
66
|
+
// Check if we're already in a tmux session
|
|
67
|
+
if (process.env.TMUX) {
|
|
68
|
+
// If we're already in tmux, switch to the session
|
|
69
|
+
await exec(`tmux switch-client -t "${sessionName}"`);
|
|
70
|
+
} else {
|
|
71
|
+
// If not in tmux, attach to the session
|
|
72
|
+
spawn('tmux', ['-CC', 'attach-session', '-t', sessionName], {
|
|
73
|
+
stdio: 'inherit'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
79
|
+
const sessionName = this.getSessionName(projectName);
|
|
80
|
+
const claudeCommand = claudeArgs.length > 0
|
|
81
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
82
|
+
: 'claude';
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
`# Create tmux split session for ${projectName}`,
|
|
86
|
+
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
87
|
+
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
88
|
+
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
89
|
+
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
90
|
+
process.env.TMUX
|
|
91
|
+
? `tmux switch-client -t "${sessionName}"`
|
|
92
|
+
: `tmux attach-session -t "${sessionName}"`
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async listWorkonSessions() {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
99
|
+
return stdout.trim()
|
|
100
|
+
.split('\n')
|
|
101
|
+
.filter(session => session.startsWith(this.sessionPrefix))
|
|
102
|
+
.map(session => session.replace(this.sessionPrefix, ''));
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = TmuxManager;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const exec = promisify(require('child_process').exec);
|
|
4
|
+
|
|
5
|
+
class TmuxManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.sessionPrefix = 'workon-';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async isTmuxAvailable() {
|
|
11
|
+
try {
|
|
12
|
+
await exec('which tmux');
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async sessionExists(sessionName) {
|
|
20
|
+
try {
|
|
21
|
+
await exec(`tmux has-session -t "${sessionName}"`);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getSessionName(projectName) {
|
|
29
|
+
return `${this.sessionPrefix}${projectName}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async killSession(sessionName) {
|
|
33
|
+
try {
|
|
34
|
+
await exec(`tmux kill-session -t "${sessionName}"`);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createSplitSession(projectName, projectPath, claudeArgs = []) {
|
|
42
|
+
const sessionName = this.getSessionName(projectName);
|
|
43
|
+
|
|
44
|
+
// Kill existing session if it exists
|
|
45
|
+
if (await this.sessionExists(sessionName)) {
|
|
46
|
+
await this.killSession(sessionName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const claudeCommand = claudeArgs.length > 0
|
|
50
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
51
|
+
: 'claude';
|
|
52
|
+
|
|
53
|
+
// Create new tmux session with claude in the first pane
|
|
54
|
+
await exec(`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`);
|
|
55
|
+
|
|
56
|
+
// Split window horizontally and run shell in second pane
|
|
57
|
+
await exec(`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`);
|
|
58
|
+
|
|
59
|
+
// Set focus on claude pane (left pane)
|
|
60
|
+
await exec(`tmux select-pane -t "${sessionName}:0.0"`);
|
|
61
|
+
|
|
62
|
+
return sessionName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async attachToSession(sessionName) {
|
|
66
|
+
// Check if we're already in a tmux session
|
|
67
|
+
if (process.env.TMUX) {
|
|
68
|
+
// If we're already in tmux, switch to the session
|
|
69
|
+
await exec(`tmux switch-client -t "${sessionName}"`);
|
|
70
|
+
} else {
|
|
71
|
+
// If not in tmux, attach to the session
|
|
72
|
+
spawn('tmux', ['-CC', 'attach-session', '-t', sessionName], {
|
|
73
|
+
stdio: 'inherit'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
buildShellCommands(projectName, projectPath, claudeArgs = []) {
|
|
79
|
+
const sessionName = this.getSessionName(projectName);
|
|
80
|
+
const claudeCommand = claudeArgs.length > 0
|
|
81
|
+
? `claude ${claudeArgs.join(' ')}`
|
|
82
|
+
: 'claude';
|
|
83
|
+
|
|
84
|
+
return [
|
|
85
|
+
`# Create tmux split session for ${projectName}`,
|
|
86
|
+
`tmux has-session -t "${sessionName}" 2>/dev/null && tmux kill-session -t "${sessionName}"`,
|
|
87
|
+
`tmux new-session -d -s "${sessionName}" -c "${projectPath}" '${claudeCommand}'`,
|
|
88
|
+
`tmux split-window -h -t "${sessionName}" -c "${projectPath}"`,
|
|
89
|
+
`tmux select-pane -t "${sessionName}:0.0"`,
|
|
90
|
+
process.env.TMUX
|
|
91
|
+
? `tmux switch-client -t "${sessionName}"`
|
|
92
|
+
: `tmux attach-session -t "${sessionName}"`
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async listWorkonSessions() {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await exec('tmux list-sessions -F "#{session_name}"');
|
|
99
|
+
return stdout.trim()
|
|
100
|
+
.split('\n')
|
|
101
|
+
.filter(session => session.startsWith(this.sessionPrefix))
|
|
102
|
+
.map(session => session.replace(this.sessionPrefix, ''));
|
|
103
|
+
} catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = TmuxManager;
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
<a name="1.4.0"></a>
|
|
6
|
+
# [1.4.0](https://github.com/israelroldan/workon/compare/v1.3.0...v1.4.0) (2025-08-06)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Integrate NPM command support and enhance terminal layouts ([f877ec4](https://github.com/israelroldan/workon/commit/f877ec4))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
<a name="1.3.0"></a>
|
|
16
|
+
# [1.3.0](https://github.com/israelroldan/workon/compare/v1.2.1...v1.3.0) (2025-08-06)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* Add split terminal support for 'claude' event (tmux based) ([c700afc](https://github.com/israelroldan/workon/commit/c700afc))
|
|
22
|
+
* Enhance 'claude' event configuration and validation ([12387e4](https://github.com/israelroldan/workon/commit/12387e4))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
5
26
|
<a name="1.2.1"></a>
|
|
6
27
|
## [1.2.1](https://github.com/israelroldan/workon/compare/v1.2.0...v1.2.1) (2025-08-06)
|
|
7
28
|
|