termplex 0.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.js +511 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 juan294
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# termplex
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://github.com/juan294/termplex/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/termplex)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
Launch configurable multi-pane terminal workspaces with one command.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm i -g termplex
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Node >= 18. tmux is installed automatically on first launch if missing (macOS via Homebrew, Linux via apt/dnf/yum/pacman).
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
termplex . # launch workspace in current directory
|
|
22
|
+
termplex add myapp ~/code/myapp # register a project
|
|
23
|
+
termplex myapp # launch by project name
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Default Layout (full preset)
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
termplex . (panes=3, editor=claude, sidebar=lazygit, server=true)
|
|
30
|
+
|
|
31
|
+
┌─────────────────── 75% ───────────────────┬──── 25% ────┐
|
|
32
|
+
│ │ │ │
|
|
33
|
+
│ claude (1) │ claude (3) │ lazygit │
|
|
34
|
+
│ │ │ │
|
|
35
|
+
├───────────────────┤────────────────────────│ │
|
|
36
|
+
│ │ │ │
|
|
37
|
+
│ claude (2) │ server (shell) │ │
|
|
38
|
+
│ │ │ │
|
|
39
|
+
└───────────────────┴────────────────────────┴─────────────┘
|
|
40
|
+
left col right col sidebar
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Layout Presets
|
|
44
|
+
|
|
45
|
+
| Preset | Panes | Server | Use case |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| `full` | 3 | yes | Default -- multi-agent coding + dev server |
|
|
48
|
+
| `pair` | 2 | yes | Two editors + dev server |
|
|
49
|
+
| `minimal` | 1 | no | Simple editor + sidebar only |
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
termplex . --layout minimal # 1 editor pane, no server
|
|
53
|
+
termplex . -l pair # 2 editors + server
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Per-project Config
|
|
57
|
+
|
|
58
|
+
Drop a `.termplex` file in your project root to override machine-level config:
|
|
59
|
+
|
|
60
|
+
```ini
|
|
61
|
+
# .termplex
|
|
62
|
+
layout=minimal
|
|
63
|
+
editor=vim
|
|
64
|
+
server=npm run dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Config resolution order: **CLI flags > .termplex > machine config > preset > defaults**
|
|
68
|
+
|
|
69
|
+
## Commands
|
|
70
|
+
|
|
71
|
+
| Command | Description |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `termplex <target>` | Launch workspace (project name, path, or `.`) |
|
|
74
|
+
| `termplex add <name> <path>` | Register a project name to a directory |
|
|
75
|
+
| `termplex remove <name>` | Remove a registered project |
|
|
76
|
+
| `termplex list` | List all registered projects |
|
|
77
|
+
| `termplex set <key> [value]` | Set a machine-level config value |
|
|
78
|
+
| `termplex config` | Show current machine configuration |
|
|
79
|
+
|
|
80
|
+
## CLI Flags
|
|
81
|
+
|
|
82
|
+
| Flag | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `-l, --layout <preset>` | Use a layout preset (`minimal`, `full`, `pair`) |
|
|
85
|
+
| `--editor <cmd>` | Override editor command |
|
|
86
|
+
| `--panes <n>` | Override number of editor panes |
|
|
87
|
+
| `--editor-size <n>` | Override editor width percentage |
|
|
88
|
+
| `--sidebar <cmd>` | Override sidebar command |
|
|
89
|
+
| `--server <value>` | Server pane: `true`, `false`, or a command |
|
|
90
|
+
| `-h, --help` | Show help message |
|
|
91
|
+
| `-v, --version` | Show version number |
|
|
92
|
+
|
|
93
|
+
## Config Keys
|
|
94
|
+
|
|
95
|
+
| Key | Default | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `editor` | `claude` | Command launched in editor panes |
|
|
98
|
+
| `sidebar` | `lazygit` | Command launched in the sidebar pane |
|
|
99
|
+
| `panes` | `3` | Number of editor panes |
|
|
100
|
+
| `editor-size` | `75` | Width percentage for the editor grid |
|
|
101
|
+
| `server` | `true` | Server pane: `true` (shell), `false` (none), or a command |
|
|
102
|
+
| `layout` | | Default layout preset |
|
|
103
|
+
|
|
104
|
+
Machine config is stored at `~/.config/termplex/config`:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
termplex set editor vim # use vim as the editor
|
|
108
|
+
termplex set server "npm run dev" # run dev server automatically
|
|
109
|
+
termplex set layout minimal # default to minimal preset
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Alias
|
|
113
|
+
|
|
114
|
+
termplex is also available as `ws` for quick access:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
ws .
|
|
118
|
+
ws myapp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Docs
|
|
122
|
+
|
|
123
|
+
- [Architecture](docs/architecture.md) -- module map, layout algorithm, build pipeline
|
|
124
|
+
- [User Manual](docs/user-manual.md) -- full command reference, walkthrough, troubleshooting
|
|
125
|
+
- [Publishing](docs/publishing.md) -- npm publish checklist
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
[MIT](./LICENSE)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { parseArgs } from "util";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// src/config.ts
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
var CONFIG_DIR = join(homedir(), ".config", "termplex");
|
|
12
|
+
var PROJECTS_FILE = join(CONFIG_DIR, "projects");
|
|
13
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config");
|
|
14
|
+
function ensureConfig() {
|
|
15
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
16
|
+
if (!existsSync(PROJECTS_FILE)) writeFileSync(PROJECTS_FILE, "");
|
|
17
|
+
if (!existsSync(CONFIG_FILE)) writeFileSync(CONFIG_FILE, "editor=claude\n");
|
|
18
|
+
}
|
|
19
|
+
function readKV(file) {
|
|
20
|
+
ensureConfig();
|
|
21
|
+
const map = /* @__PURE__ */ new Map();
|
|
22
|
+
if (!existsSync(file)) return map;
|
|
23
|
+
const content = readFileSync(file, "utf-8").trim();
|
|
24
|
+
if (!content) return map;
|
|
25
|
+
for (const line of content.split("\n")) {
|
|
26
|
+
const idx = line.indexOf("=");
|
|
27
|
+
if (idx === -1) continue;
|
|
28
|
+
map.set(line.slice(0, idx), line.slice(idx + 1));
|
|
29
|
+
}
|
|
30
|
+
return map;
|
|
31
|
+
}
|
|
32
|
+
function writeKV(file, map) {
|
|
33
|
+
const lines = [...map.entries()].map(([k, v]) => `${k}=${v}`);
|
|
34
|
+
writeFileSync(file, lines.join("\n") + "\n");
|
|
35
|
+
}
|
|
36
|
+
function readKVFile(path) {
|
|
37
|
+
const map = /* @__PURE__ */ new Map();
|
|
38
|
+
if (!existsSync(path)) return map;
|
|
39
|
+
const content = readFileSync(path, "utf-8").trim();
|
|
40
|
+
if (!content) return map;
|
|
41
|
+
for (const line of content.split("\n")) {
|
|
42
|
+
const idx = line.indexOf("=");
|
|
43
|
+
if (idx === -1) continue;
|
|
44
|
+
map.set(line.slice(0, idx), line.slice(idx + 1));
|
|
45
|
+
}
|
|
46
|
+
return map;
|
|
47
|
+
}
|
|
48
|
+
function addProject(name, path) {
|
|
49
|
+
const projects = readKV(PROJECTS_FILE);
|
|
50
|
+
projects.set(name, path);
|
|
51
|
+
writeKV(PROJECTS_FILE, projects);
|
|
52
|
+
}
|
|
53
|
+
function removeProject(name) {
|
|
54
|
+
const projects = readKV(PROJECTS_FILE);
|
|
55
|
+
projects.delete(name);
|
|
56
|
+
writeKV(PROJECTS_FILE, projects);
|
|
57
|
+
}
|
|
58
|
+
function getProject(name) {
|
|
59
|
+
return readKV(PROJECTS_FILE).get(name);
|
|
60
|
+
}
|
|
61
|
+
function listProjects() {
|
|
62
|
+
return readKV(PROJECTS_FILE);
|
|
63
|
+
}
|
|
64
|
+
function setConfig(key, value) {
|
|
65
|
+
const config = readKV(CONFIG_FILE);
|
|
66
|
+
config.set(key, value);
|
|
67
|
+
writeKV(CONFIG_FILE, config);
|
|
68
|
+
}
|
|
69
|
+
function getConfig(key) {
|
|
70
|
+
return readKV(CONFIG_FILE).get(key);
|
|
71
|
+
}
|
|
72
|
+
function listConfig() {
|
|
73
|
+
return readKV(CONFIG_FILE);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/launcher.ts
|
|
77
|
+
import { existsSync as existsSync2 } from "fs";
|
|
78
|
+
import { basename, join as join2 } from "path";
|
|
79
|
+
import { createInterface } from "readline";
|
|
80
|
+
import { execSync } from "child_process";
|
|
81
|
+
|
|
82
|
+
// src/layout.ts
|
|
83
|
+
var DEFAULT_OPTIONS = {
|
|
84
|
+
editor: "claude",
|
|
85
|
+
editorPanes: 3,
|
|
86
|
+
editorSize: 75,
|
|
87
|
+
sidebarCommand: "lazygit",
|
|
88
|
+
server: "true"
|
|
89
|
+
};
|
|
90
|
+
function parseServer(value) {
|
|
91
|
+
if (value === "false" || value === "") {
|
|
92
|
+
return { hasServer: false, serverCommand: null };
|
|
93
|
+
}
|
|
94
|
+
if (value === "true") {
|
|
95
|
+
return { hasServer: true, serverCommand: null };
|
|
96
|
+
}
|
|
97
|
+
return { hasServer: true, serverCommand: value };
|
|
98
|
+
}
|
|
99
|
+
var PRESETS = {
|
|
100
|
+
minimal: { editorPanes: 1, server: "false" },
|
|
101
|
+
full: { editorPanes: 3, server: "true" },
|
|
102
|
+
pair: { editorPanes: 2, server: "true" }
|
|
103
|
+
};
|
|
104
|
+
function isPresetName(value) {
|
|
105
|
+
return value in PRESETS;
|
|
106
|
+
}
|
|
107
|
+
function getPreset(name) {
|
|
108
|
+
return PRESETS[name];
|
|
109
|
+
}
|
|
110
|
+
function planLayout(partial) {
|
|
111
|
+
const opts = { ...DEFAULT_OPTIONS, ...partial };
|
|
112
|
+
const leftColumnCount = Math.ceil(opts.editorPanes / 2);
|
|
113
|
+
const { hasServer, serverCommand } = parseServer(opts.server);
|
|
114
|
+
return {
|
|
115
|
+
editorSize: opts.editorSize,
|
|
116
|
+
sidebarSize: 100 - opts.editorSize,
|
|
117
|
+
leftColumnCount,
|
|
118
|
+
rightColumnEditorCount: opts.editorPanes - leftColumnCount,
|
|
119
|
+
editor: opts.editor,
|
|
120
|
+
sidebarCommand: opts.sidebarCommand,
|
|
121
|
+
hasServer,
|
|
122
|
+
serverCommand
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/launcher.ts
|
|
127
|
+
function tmux(cmd) {
|
|
128
|
+
return execSync(`tmux ${cmd}`, { encoding: "utf-8" }).trim();
|
|
129
|
+
}
|
|
130
|
+
function splitPane(targetId, dir, size, cwd, command) {
|
|
131
|
+
const cmdPart = command ? ` "${command}; exec $SHELL"` : "";
|
|
132
|
+
return tmux(
|
|
133
|
+
`split-window -${dir} -t "${targetId}" -l ${size}% -c "${cwd}" -P -F "#{pane_id}"${cmdPart}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
function isCommandInstalled(cmd) {
|
|
137
|
+
try {
|
|
138
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
139
|
+
return true;
|
|
140
|
+
} catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function getInstallCommand() {
|
|
145
|
+
if (process.platform === "darwin") {
|
|
146
|
+
try {
|
|
147
|
+
execSync("command -v brew", { stdio: "ignore" });
|
|
148
|
+
return "brew install tmux";
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (process.platform === "linux") {
|
|
154
|
+
const managers = [
|
|
155
|
+
["apt-get", "sudo apt-get install -y tmux"],
|
|
156
|
+
["dnf", "sudo dnf install -y tmux"],
|
|
157
|
+
["yum", "sudo yum install -y tmux"],
|
|
158
|
+
["pacman", "sudo pacman -S --noconfirm tmux"]
|
|
159
|
+
];
|
|
160
|
+
for (const [bin, cmd] of managers) {
|
|
161
|
+
try {
|
|
162
|
+
execSync(`command -v ${bin}`, { stdio: "ignore" });
|
|
163
|
+
return cmd;
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function prompt(question) {
|
|
171
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
172
|
+
return new Promise((resolve2) => {
|
|
173
|
+
rl.question(question, (answer) => {
|
|
174
|
+
rl.close();
|
|
175
|
+
resolve2(answer.trim().toLowerCase());
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
var KNOWN_INSTALL_COMMANDS = {
|
|
180
|
+
claude: () => "npm install -g @anthropic-ai/claude-code",
|
|
181
|
+
lazygit: () => {
|
|
182
|
+
if (process.platform === "darwin") {
|
|
183
|
+
try {
|
|
184
|
+
execSync("command -v brew", { stdio: "ignore" });
|
|
185
|
+
return "brew install lazygit";
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (process.platform === "linux") {
|
|
191
|
+
try {
|
|
192
|
+
execSync("command -v brew", { stdio: "ignore" });
|
|
193
|
+
return "brew install lazygit";
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
async function ensureCommand(cmd) {
|
|
202
|
+
if (isCommandInstalled(cmd)) return;
|
|
203
|
+
const getInstall = KNOWN_INSTALL_COMMANDS[cmd];
|
|
204
|
+
const installCmd = getInstall ? getInstall() : null;
|
|
205
|
+
if (!installCmd) {
|
|
206
|
+
console.error(
|
|
207
|
+
`\`${cmd}\` is required but not installed, and no known install method was found.`
|
|
208
|
+
);
|
|
209
|
+
console.error(
|
|
210
|
+
`Please install \`${cmd}\` manually or change your config with: termplex config set editor <command>`
|
|
211
|
+
);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
console.log(`\`${cmd}\` is required but not installed on this machine.`);
|
|
215
|
+
const answer = await prompt(`Install it now with \`${installCmd}\`? [Y/n] `);
|
|
216
|
+
if (answer && answer !== "y" && answer !== "yes") {
|
|
217
|
+
console.log(`\`${cmd}\` is required for this workspace layout. Exiting.`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
console.log(`Running: ${installCmd}`);
|
|
221
|
+
try {
|
|
222
|
+
execSync(installCmd, { stdio: "inherit" });
|
|
223
|
+
} catch {
|
|
224
|
+
console.error(
|
|
225
|
+
`Failed to install \`${cmd}\`. Please install it manually and try again.`
|
|
226
|
+
);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
if (!isCommandInstalled(cmd)) {
|
|
230
|
+
console.error(`\`${cmd}\` still not found after install. Please check your PATH.`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
console.log(`\`${cmd}\` installed successfully!
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
async function ensureTmux() {
|
|
237
|
+
if (isCommandInstalled("tmux")) return;
|
|
238
|
+
const installCmd = getInstallCommand();
|
|
239
|
+
if (!installCmd) {
|
|
240
|
+
console.error(
|
|
241
|
+
"tmux is required but not installed, and no supported package manager was found."
|
|
242
|
+
);
|
|
243
|
+
console.error("Please install tmux manually and try again.");
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
console.log("tmux is required but not installed on this machine.");
|
|
247
|
+
const answer = await prompt(`Install it now with \`${installCmd}\`? [Y/n] `);
|
|
248
|
+
if (answer && answer !== "y" && answer !== "yes") {
|
|
249
|
+
console.log("tmux is required for termplex to work. Exiting.");
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
console.log(`Running: ${installCmd}`);
|
|
253
|
+
try {
|
|
254
|
+
execSync(installCmd, { stdio: "inherit" });
|
|
255
|
+
} catch {
|
|
256
|
+
console.error(
|
|
257
|
+
"Failed to install tmux. Please install it manually and try again."
|
|
258
|
+
);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
if (!isCommandInstalled("tmux")) {
|
|
262
|
+
console.error("tmux still not found after install. Please check your PATH.");
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
console.log("tmux installed successfully!\n");
|
|
266
|
+
}
|
|
267
|
+
function resolveConfig(targetDir, cliOverrides) {
|
|
268
|
+
const project = readKVFile(join2(targetDir, ".termplex"));
|
|
269
|
+
const layoutKey = cliOverrides.layout ?? project.get("layout") ?? getConfig("layout");
|
|
270
|
+
let base = {};
|
|
271
|
+
if (layoutKey) {
|
|
272
|
+
if (isPresetName(layoutKey)) {
|
|
273
|
+
base = getPreset(layoutKey);
|
|
274
|
+
} else {
|
|
275
|
+
console.warn(`Unknown layout preset: "${layoutKey}", using defaults.`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const pick = (cli, projKey) => cli ?? project.get(projKey) ?? getConfig(projKey);
|
|
279
|
+
const editor = pick(cliOverrides.editor, "editor");
|
|
280
|
+
const sidebar = pick(cliOverrides.sidebar, "sidebar");
|
|
281
|
+
const panes = pick(cliOverrides.panes, "panes");
|
|
282
|
+
const editorSize = pick(cliOverrides["editor-size"], "editor-size");
|
|
283
|
+
const server = pick(cliOverrides.server, "server");
|
|
284
|
+
const result = { ...base };
|
|
285
|
+
if (editor !== void 0) result.editor = editor;
|
|
286
|
+
if (sidebar !== void 0) result.sidebarCommand = sidebar;
|
|
287
|
+
if (panes !== void 0) result.editorPanes = parseInt(panes, 10);
|
|
288
|
+
if (editorSize !== void 0) result.editorSize = parseInt(editorSize, 10);
|
|
289
|
+
if (server !== void 0) result.server = server;
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
function buildSession(sessionName, targetDir, plan) {
|
|
293
|
+
tmux(`new-session -d -s "${sessionName}" -c "${targetDir}"`);
|
|
294
|
+
const rootId = tmux(`display -t "${sessionName}:0" -p "#{pane_id}"`);
|
|
295
|
+
splitPane(rootId, "h", plan.sidebarSize, targetDir, plan.sidebarCommand || void 0);
|
|
296
|
+
const rightColId = splitPane(rootId, "h", 50, targetDir, plan.editor || void 0);
|
|
297
|
+
let target = rootId;
|
|
298
|
+
for (let i = 1; i < plan.leftColumnCount; i++) {
|
|
299
|
+
const pct = Math.floor(
|
|
300
|
+
(plan.leftColumnCount - i) / (plan.leftColumnCount - i + 1) * 100
|
|
301
|
+
);
|
|
302
|
+
target = splitPane(target, "v", pct, targetDir, plan.editor || void 0);
|
|
303
|
+
}
|
|
304
|
+
const serverCount = plan.hasServer ? 1 : 0;
|
|
305
|
+
const totalRight = plan.rightColumnEditorCount + serverCount;
|
|
306
|
+
target = rightColId;
|
|
307
|
+
for (let i = 1; i < totalRight; i++) {
|
|
308
|
+
const isServer = plan.hasServer && i === totalRight - 1;
|
|
309
|
+
const pct = Math.floor(
|
|
310
|
+
(totalRight - i) / (totalRight - i + 1) * 100
|
|
311
|
+
);
|
|
312
|
+
const cmd = isServer ? plan.serverCommand ?? void 0 : plan.editor || void 0;
|
|
313
|
+
target = splitPane(target, "v", pct, targetDir, cmd);
|
|
314
|
+
}
|
|
315
|
+
if (plan.editor) {
|
|
316
|
+
tmux(`respawn-pane -k -t "${rootId}" -c "${targetDir}" "${plan.editor}; exec $SHELL"`);
|
|
317
|
+
}
|
|
318
|
+
tmux(`select-pane -t "${rootId}"`);
|
|
319
|
+
}
|
|
320
|
+
async function launch(targetDir, cliOverrides) {
|
|
321
|
+
if (!existsSync2(targetDir)) {
|
|
322
|
+
console.error(`Directory not found: ${targetDir}`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
await ensureTmux();
|
|
326
|
+
const opts = resolveConfig(targetDir, cliOverrides ?? {});
|
|
327
|
+
const plan = planLayout(opts);
|
|
328
|
+
if (plan.editor) await ensureCommand(plan.editor);
|
|
329
|
+
if (plan.sidebarCommand) await ensureCommand(plan.sidebarCommand);
|
|
330
|
+
if (plan.serverCommand) {
|
|
331
|
+
const serverBin = plan.serverCommand.split(" ")[0];
|
|
332
|
+
await ensureCommand(serverBin);
|
|
333
|
+
}
|
|
334
|
+
const dirName = basename(targetDir).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
335
|
+
const sessionName = `tp-${dirName}`;
|
|
336
|
+
try {
|
|
337
|
+
execSync(`tmux has-session -t "${sessionName}"`, { stdio: "ignore" });
|
|
338
|
+
console.log(`Attaching to existing session: ${sessionName}`);
|
|
339
|
+
execSync(`tmux attach-session -t "${sessionName}"`, { stdio: "inherit" });
|
|
340
|
+
return;
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
buildSession(sessionName, targetDir, plan);
|
|
344
|
+
try {
|
|
345
|
+
execSync(`tmux attach-session -t "${sessionName}"`, { stdio: "inherit" });
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/index.ts
|
|
351
|
+
var HELP = `
|
|
352
|
+
termplex \u2014 Launch configurable multi-pane terminal workspaces
|
|
353
|
+
|
|
354
|
+
Usage:
|
|
355
|
+
termplex <target> Launch workspace (project name, path, or '.')
|
|
356
|
+
termplex add <name> <path> Register a project name \u2192 path mapping
|
|
357
|
+
termplex remove <name> Remove a registered project
|
|
358
|
+
termplex list List all registered projects
|
|
359
|
+
termplex set <key> [value] Set a machine-level config value
|
|
360
|
+
termplex config Show current machine configuration
|
|
361
|
+
|
|
362
|
+
Options:
|
|
363
|
+
-h, --help Show this help message
|
|
364
|
+
-v, --version Show version number
|
|
365
|
+
-l, --layout <preset> Use a layout preset (minimal, full, pair)
|
|
366
|
+
--editor <cmd> Override editor command
|
|
367
|
+
--panes <n> Override number of editor panes
|
|
368
|
+
--editor-size <n> Override editor width %
|
|
369
|
+
--sidebar <cmd> Override sidebar command
|
|
370
|
+
--server <value> Server pane: true, false, or a command
|
|
371
|
+
|
|
372
|
+
Config keys:
|
|
373
|
+
editor Command for coding panes (default: claude)
|
|
374
|
+
sidebar Command for sidebar pane (default: lazygit)
|
|
375
|
+
panes Number of editor panes (default: 3)
|
|
376
|
+
editor-size Width % for editor grid (default: 75)
|
|
377
|
+
server Server pane toggle (default: true)
|
|
378
|
+
layout Default layout preset
|
|
379
|
+
|
|
380
|
+
Layout presets:
|
|
381
|
+
minimal 1 editor pane, no server
|
|
382
|
+
full 3 editor panes + server (default)
|
|
383
|
+
pair 2 editor panes + server
|
|
384
|
+
|
|
385
|
+
Per-project config:
|
|
386
|
+
Place a .termplex file in your project root with key=value pairs.
|
|
387
|
+
Project config overrides machine config; CLI flags override both.
|
|
388
|
+
|
|
389
|
+
Examples:
|
|
390
|
+
termplex . Launch workspace in current directory
|
|
391
|
+
termplex myapp Launch workspace for registered project
|
|
392
|
+
termplex add myapp ~/code/app Register a project
|
|
393
|
+
termplex set editor claude Set the editor command
|
|
394
|
+
termplex . --layout minimal Launch with minimal preset
|
|
395
|
+
termplex . --server "npm run dev" Launch with custom server command
|
|
396
|
+
`.trim();
|
|
397
|
+
function showHelp() {
|
|
398
|
+
console.log(HELP);
|
|
399
|
+
}
|
|
400
|
+
var { values, positionals } = parseArgs({
|
|
401
|
+
allowPositionals: true,
|
|
402
|
+
options: {
|
|
403
|
+
help: { type: "boolean", short: "h" },
|
|
404
|
+
version: { type: "boolean", short: "v" },
|
|
405
|
+
layout: { type: "string", short: "l" },
|
|
406
|
+
editor: { type: "string" },
|
|
407
|
+
panes: { type: "string" },
|
|
408
|
+
"editor-size": { type: "string" },
|
|
409
|
+
sidebar: { type: "string" },
|
|
410
|
+
server: { type: "string" }
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
if (values.version) {
|
|
414
|
+
console.log("0.1.0");
|
|
415
|
+
process.exit(0);
|
|
416
|
+
}
|
|
417
|
+
if (values.help) {
|
|
418
|
+
showHelp();
|
|
419
|
+
process.exit(0);
|
|
420
|
+
}
|
|
421
|
+
var [subcommand, ...args] = positionals;
|
|
422
|
+
if (!subcommand) {
|
|
423
|
+
showHelp();
|
|
424
|
+
process.exit(0);
|
|
425
|
+
}
|
|
426
|
+
switch (subcommand) {
|
|
427
|
+
case "add": {
|
|
428
|
+
const [name, path] = args;
|
|
429
|
+
if (!name || !path) {
|
|
430
|
+
console.error("Usage: termplex add <name> <path>");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
const resolved = resolve(path.replace(/^~/, process.env.HOME ?? ""));
|
|
434
|
+
addProject(name, resolved);
|
|
435
|
+
console.log(`Registered: ${name} \u2192 ${resolved}`);
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
case "remove": {
|
|
439
|
+
const [name] = args;
|
|
440
|
+
if (!name) {
|
|
441
|
+
console.error("Usage: termplex remove <name>");
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
removeProject(name);
|
|
445
|
+
console.log(`Removed: ${name}`);
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
case "list": {
|
|
449
|
+
const projects = listProjects();
|
|
450
|
+
if (projects.size === 0) {
|
|
451
|
+
console.log("No projects registered. Use: termplex add <name> <path>");
|
|
452
|
+
} else {
|
|
453
|
+
console.log("Registered projects:");
|
|
454
|
+
for (const [name, path] of projects) {
|
|
455
|
+
console.log(` ${name} \u2192 ${path}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
case "set": {
|
|
461
|
+
const [key, value] = args;
|
|
462
|
+
if (!key) {
|
|
463
|
+
console.error("Usage: termplex set <key> [value]");
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
setConfig(key, value ?? "");
|
|
467
|
+
if (value) {
|
|
468
|
+
console.log(`Set ${key} \u2192 ${value}`);
|
|
469
|
+
} else {
|
|
470
|
+
console.log(`Set ${key} \u2192 (empty, will open plain shell)`);
|
|
471
|
+
}
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
case "config": {
|
|
475
|
+
const config = listConfig();
|
|
476
|
+
console.log("Machine config:");
|
|
477
|
+
for (const [key, value] of config) {
|
|
478
|
+
console.log(` ${key} \u2192 ${value || "(plain shell)"}`);
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
default: {
|
|
483
|
+
const target = subcommand;
|
|
484
|
+
let targetDir;
|
|
485
|
+
if (target === ".") {
|
|
486
|
+
targetDir = process.cwd();
|
|
487
|
+
} else if (target.startsWith("/") || target.startsWith("~")) {
|
|
488
|
+
targetDir = resolve(target.replace(/^~/, process.env.HOME ?? ""));
|
|
489
|
+
} else {
|
|
490
|
+
const path = getProject(target);
|
|
491
|
+
if (!path) {
|
|
492
|
+
console.error(`Unknown project: ${target}`);
|
|
493
|
+
console.error(
|
|
494
|
+
`Register it with: termplex add ${target} /path/to/project`
|
|
495
|
+
);
|
|
496
|
+
console.error(`Or see available: termplex list`);
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
targetDir = path;
|
|
500
|
+
}
|
|
501
|
+
const overrides = {};
|
|
502
|
+
if (values.layout) overrides.layout = values.layout;
|
|
503
|
+
if (values.editor) overrides.editor = values.editor;
|
|
504
|
+
if (values.panes) overrides.panes = values.panes;
|
|
505
|
+
if (values["editor-size"]) overrides["editor-size"] = values["editor-size"];
|
|
506
|
+
if (values.sidebar) overrides.sidebar = values.sidebar;
|
|
507
|
+
if (values.server) overrides.server = values.server;
|
|
508
|
+
await launch(targetDir, overrides);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/launcher.ts","../src/layout.ts"],"sourcesContent":["import { parseArgs } from \"node:util\";\nimport { resolve } from \"node:path\";\nimport {\n addProject,\n removeProject,\n getProject,\n listProjects,\n setConfig,\n listConfig,\n} from \"./config.js\";\nimport { launch } from \"./launcher.js\";\nimport type { CLIOverrides } from \"./launcher.js\";\n\nconst HELP = `\ntermplex — Launch configurable multi-pane terminal workspaces\n\nUsage:\n termplex <target> Launch workspace (project name, path, or '.')\n termplex add <name> <path> Register a project name → path mapping\n termplex remove <name> Remove a registered project\n termplex list List all registered projects\n termplex set <key> [value] Set a machine-level config value\n termplex config Show current machine configuration\n\nOptions:\n -h, --help Show this help message\n -v, --version Show version number\n -l, --layout <preset> Use a layout preset (minimal, full, pair)\n --editor <cmd> Override editor command\n --panes <n> Override number of editor panes\n --editor-size <n> Override editor width %\n --sidebar <cmd> Override sidebar command\n --server <value> Server pane: true, false, or a command\n\nConfig keys:\n editor Command for coding panes (default: claude)\n sidebar Command for sidebar pane (default: lazygit)\n panes Number of editor panes (default: 3)\n editor-size Width % for editor grid (default: 75)\n server Server pane toggle (default: true)\n layout Default layout preset\n\nLayout presets:\n minimal 1 editor pane, no server\n full 3 editor panes + server (default)\n pair 2 editor panes + server\n\nPer-project config:\n Place a .termplex file in your project root with key=value pairs.\n Project config overrides machine config; CLI flags override both.\n\nExamples:\n termplex . Launch workspace in current directory\n termplex myapp Launch workspace for registered project\n termplex add myapp ~/code/app Register a project\n termplex set editor claude Set the editor command\n termplex . --layout minimal Launch with minimal preset\n termplex . --server \"npm run dev\" Launch with custom server command\n`.trim();\n\nfunction showHelp(): void {\n console.log(HELP);\n}\n\nconst { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n layout: { type: \"string\", short: \"l\" },\n editor: { type: \"string\" },\n panes: { type: \"string\" },\n \"editor-size\": { type: \"string\" },\n sidebar: { type: \"string\" },\n server: { type: \"string\" },\n },\n});\n\nif (values.version) {\n console.log(__VERSION__);\n process.exit(0);\n}\n\nif (values.help) {\n showHelp();\n process.exit(0);\n}\n\nconst [subcommand, ...args] = positionals;\n\nif (!subcommand) {\n showHelp();\n process.exit(0);\n}\n\nswitch (subcommand) {\n case \"add\": {\n const [name, path] = args;\n if (!name || !path) {\n console.error(\"Usage: termplex add <name> <path>\");\n process.exit(1);\n }\n const resolved = resolve(path.replace(/^~/, process.env.HOME ?? \"\"));\n addProject(name, resolved);\n console.log(`Registered: ${name} → ${resolved}`);\n break;\n }\n\n case \"remove\": {\n const [name] = args;\n if (!name) {\n console.error(\"Usage: termplex remove <name>\");\n process.exit(1);\n }\n removeProject(name);\n console.log(`Removed: ${name}`);\n break;\n }\n\n case \"list\": {\n const projects = listProjects();\n if (projects.size === 0) {\n console.log(\"No projects registered. Use: termplex add <name> <path>\");\n } else {\n console.log(\"Registered projects:\");\n for (const [name, path] of projects) {\n console.log(` ${name} → ${path}`);\n }\n }\n break;\n }\n\n case \"set\": {\n const [key, value] = args;\n if (!key) {\n console.error(\"Usage: termplex set <key> [value]\");\n process.exit(1);\n }\n setConfig(key, value ?? \"\");\n if (value) {\n console.log(`Set ${key} → ${value}`);\n } else {\n console.log(`Set ${key} → (empty, will open plain shell)`);\n }\n break;\n }\n\n case \"config\": {\n const config = listConfig();\n console.log(\"Machine config:\");\n for (const [key, value] of config) {\n console.log(` ${key} → ${value || \"(plain shell)\"}`);\n }\n break;\n }\n\n default: {\n // Treat as launch target (project name, path, or '.')\n const target = subcommand;\n let targetDir: string;\n\n if (target === \".\") {\n targetDir = process.cwd();\n } else if (target.startsWith(\"/\") || target.startsWith(\"~\")) {\n targetDir = resolve(target.replace(/^~/, process.env.HOME ?? \"\"));\n } else {\n const path = getProject(target);\n if (!path) {\n console.error(`Unknown project: ${target}`);\n console.error(\n `Register it with: termplex add ${target} /path/to/project`,\n );\n console.error(`Or see available: termplex list`);\n process.exit(1);\n }\n targetDir = path;\n }\n\n const overrides: CLIOverrides = {};\n if (values.layout) overrides.layout = values.layout;\n if (values.editor) overrides.editor = values.editor;\n if (values.panes) overrides.panes = values.panes;\n if (values[\"editor-size\"]) overrides[\"editor-size\"] = values[\"editor-size\"];\n if (values.sidebar) overrides.sidebar = values.sidebar;\n if (values.server) overrides.server = values.server;\n\n await launch(targetDir, overrides);\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst CONFIG_DIR = join(homedir(), \".config\", \"termplex\");\nconst PROJECTS_FILE = join(CONFIG_DIR, \"projects\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"config\");\n\nfunction ensureConfig(): void {\n mkdirSync(CONFIG_DIR, { recursive: true });\n if (!existsSync(PROJECTS_FILE)) writeFileSync(PROJECTS_FILE, \"\");\n if (!existsSync(CONFIG_FILE)) writeFileSync(CONFIG_FILE, \"editor=claude\\n\");\n}\n\nfunction readKV(file: string): Map<string, string> {\n ensureConfig();\n const map = new Map<string, string>();\n if (!existsSync(file)) return map;\n const content = readFileSync(file, \"utf-8\").trim();\n if (!content) return map;\n for (const line of content.split(\"\\n\")) {\n const idx = line.indexOf(\"=\");\n if (idx === -1) continue;\n map.set(line.slice(0, idx), line.slice(idx + 1));\n }\n return map;\n}\n\nfunction writeKV(file: string, map: Map<string, string>): void {\n const lines = [...map.entries()].map(([k, v]) => `${k}=${v}`);\n writeFileSync(file, lines.join(\"\\n\") + \"\\n\");\n}\n\n// --- Per-project config ---\n\nexport function readKVFile(path: string): Map<string, string> {\n const map = new Map<string, string>();\n if (!existsSync(path)) return map;\n const content = readFileSync(path, \"utf-8\").trim();\n if (!content) return map;\n for (const line of content.split(\"\\n\")) {\n const idx = line.indexOf(\"=\");\n if (idx === -1) continue;\n map.set(line.slice(0, idx), line.slice(idx + 1));\n }\n return map;\n}\n\n// --- Projects ---\n\nexport function addProject(name: string, path: string): void {\n const projects = readKV(PROJECTS_FILE);\n projects.set(name, path);\n writeKV(PROJECTS_FILE, projects);\n}\n\nexport function removeProject(name: string): void {\n const projects = readKV(PROJECTS_FILE);\n projects.delete(name);\n writeKV(PROJECTS_FILE, projects);\n}\n\nexport function getProject(name: string): string | undefined {\n return readKV(PROJECTS_FILE).get(name);\n}\n\nexport function listProjects(): Map<string, string> {\n return readKV(PROJECTS_FILE);\n}\n\n// --- Machine config ---\n\nexport function setConfig(key: string, value: string): void {\n const config = readKV(CONFIG_FILE);\n config.set(key, value);\n writeKV(CONFIG_FILE, config);\n}\n\nexport function getConfig(key: string): string | undefined {\n return readKV(CONFIG_FILE).get(key);\n}\n\nexport function listConfig(): Map<string, string> {\n return readKV(CONFIG_FILE);\n}\n","import { existsSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { createInterface } from \"node:readline\";\nimport { execSync } from \"node:child_process\";\nimport { planLayout, isPresetName, getPreset } from \"./layout.js\";\nimport type { LayoutOptions, LayoutPlan } from \"./layout.js\";\nimport { getConfig, readKVFile } from \"./config.js\";\n\nexport interface CLIOverrides {\n layout?: string;\n editor?: string;\n panes?: string;\n \"editor-size\"?: string;\n sidebar?: string;\n server?: string;\n}\n\nfunction tmux(cmd: string): string {\n return execSync(`tmux ${cmd}`, { encoding: \"utf-8\" }).trim();\n}\n\nfunction splitPane(\n targetId: string,\n dir: \"h\" | \"v\",\n size: number,\n cwd: string,\n command?: string,\n): string {\n const cmdPart = command ? ` \"${command}; exec $SHELL\"` : \"\";\n return tmux(\n `split-window -${dir} -t \"${targetId}\" -l ${size}% -c \"${cwd}\" -P -F \"#{pane_id}\"${cmdPart}`,\n );\n}\n\nfunction isCommandInstalled(cmd: string): boolean {\n try {\n execSync(`command -v ${cmd}`, { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction getInstallCommand(): string | null {\n if (process.platform === \"darwin\") {\n try {\n execSync(\"command -v brew\", { stdio: \"ignore\" });\n return \"brew install tmux\";\n } catch {\n return null;\n }\n }\n\n if (process.platform === \"linux\") {\n const managers: [string, string][] = [\n [\"apt-get\", \"sudo apt-get install -y tmux\"],\n [\"dnf\", \"sudo dnf install -y tmux\"],\n [\"yum\", \"sudo yum install -y tmux\"],\n [\"pacman\", \"sudo pacman -S --noconfirm tmux\"],\n ];\n for (const [bin, cmd] of managers) {\n try {\n execSync(`command -v ${bin}`, { stdio: \"ignore\" });\n return cmd;\n } catch {\n // try next\n }\n }\n }\n\n return null;\n}\n\nfunction prompt(question: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase());\n });\n });\n}\n\nconst KNOWN_INSTALL_COMMANDS: Record<string, () => string | null> = {\n claude: () => \"npm install -g @anthropic-ai/claude-code\",\n lazygit: () => {\n if (process.platform === \"darwin\") {\n try {\n execSync(\"command -v brew\", { stdio: \"ignore\" });\n return \"brew install lazygit\";\n } catch {\n return null;\n }\n }\n if (process.platform === \"linux\") {\n try {\n execSync(\"command -v brew\", { stdio: \"ignore\" });\n return \"brew install lazygit\";\n } catch {\n return null;\n }\n }\n return null;\n },\n};\n\nasync function ensureCommand(cmd: string): Promise<void> {\n if (isCommandInstalled(cmd)) return;\n\n const getInstall = KNOWN_INSTALL_COMMANDS[cmd];\n const installCmd = getInstall ? getInstall() : null;\n\n if (!installCmd) {\n console.error(\n `\\`${cmd}\\` is required but not installed, and no known install method was found.`,\n );\n console.error(\n `Please install \\`${cmd}\\` manually or change your config with: termplex config set editor <command>`,\n );\n process.exit(1);\n }\n\n console.log(`\\`${cmd}\\` is required but not installed on this machine.`);\n const answer = await prompt(`Install it now with \\`${installCmd}\\`? [Y/n] `);\n\n if (answer && answer !== \"y\" && answer !== \"yes\") {\n console.log(`\\`${cmd}\\` is required for this workspace layout. Exiting.`);\n process.exit(1);\n }\n\n console.log(`Running: ${installCmd}`);\n try {\n execSync(installCmd, { stdio: \"inherit\" });\n } catch {\n console.error(\n `Failed to install \\`${cmd}\\`. Please install it manually and try again.`,\n );\n process.exit(1);\n }\n\n if (!isCommandInstalled(cmd)) {\n console.error(`\\`${cmd}\\` still not found after install. Please check your PATH.`);\n process.exit(1);\n }\n\n console.log(`\\`${cmd}\\` installed successfully!\\n`);\n}\n\nasync function ensureTmux(): Promise<void> {\n if (isCommandInstalled(\"tmux\")) return;\n\n const installCmd = getInstallCommand();\n if (!installCmd) {\n console.error(\n \"tmux is required but not installed, and no supported package manager was found.\",\n );\n console.error(\"Please install tmux manually and try again.\");\n process.exit(1);\n }\n\n console.log(\"tmux is required but not installed on this machine.\");\n const answer = await prompt(`Install it now with \\`${installCmd}\\`? [Y/n] `);\n\n if (answer && answer !== \"y\" && answer !== \"yes\") {\n console.log(\"tmux is required for termplex to work. Exiting.\");\n process.exit(1);\n }\n\n console.log(`Running: ${installCmd}`);\n try {\n execSync(installCmd, { stdio: \"inherit\" });\n } catch {\n console.error(\n \"Failed to install tmux. Please install it manually and try again.\",\n );\n process.exit(1);\n }\n\n if (!isCommandInstalled(\"tmux\")) {\n console.error(\"tmux still not found after install. Please check your PATH.\");\n process.exit(1);\n }\n\n console.log(\"tmux installed successfully!\\n\");\n}\n\nexport function resolveConfig(targetDir: string, cliOverrides: CLIOverrides): Partial<LayoutOptions> {\n const project = readKVFile(join(targetDir, \".termplex\"));\n\n // Resolve layout preset: CLI > project > global\n const layoutKey = cliOverrides.layout ?? project.get(\"layout\") ?? getConfig(\"layout\");\n let base: Partial<LayoutOptions> = {};\n if (layoutKey) {\n if (isPresetName(layoutKey)) {\n base = getPreset(layoutKey);\n } else {\n console.warn(`Unknown layout preset: \"${layoutKey}\", using defaults.`);\n }\n }\n\n // Layer: CLI > project > global > preset (for each config key)\n const pick = (cli: string | undefined, projKey: string): string | undefined =>\n cli ?? project.get(projKey) ?? getConfig(projKey);\n\n const editor = pick(cliOverrides.editor, \"editor\");\n const sidebar = pick(cliOverrides.sidebar, \"sidebar\");\n const panes = pick(cliOverrides.panes, \"panes\");\n const editorSize = pick(cliOverrides[\"editor-size\"], \"editor-size\");\n const server = pick(cliOverrides.server, \"server\");\n\n const result: Partial<LayoutOptions> = { ...base };\n if (editor !== undefined) result.editor = editor;\n if (sidebar !== undefined) result.sidebarCommand = sidebar;\n if (panes !== undefined) result.editorPanes = parseInt(panes, 10);\n if (editorSize !== undefined) result.editorSize = parseInt(editorSize, 10);\n if (server !== undefined) result.server = server;\n\n return result;\n}\n\nfunction buildSession(sessionName: string, targetDir: string, plan: LayoutPlan): void {\n // Create detached session and capture the root pane ID\n tmux(`new-session -d -s \"${sessionName}\" -c \"${targetDir}\"`);\n const rootId = tmux(`display -t \"${sessionName}:0\" -p \"#{pane_id}\"`);\n\n // Split right for sidebar — pass command directly to avoid timing issues\n splitPane(rootId, \"h\", plan.sidebarSize, targetDir, plan.sidebarCommand || undefined);\n\n // Split left area into two columns — right col first pane gets editor\n const rightColId = splitPane(rootId, \"h\", 50, targetDir, plan.editor || undefined);\n\n // --- Left column: additional editor panes ---\n let target = rootId;\n for (let i = 1; i < plan.leftColumnCount; i++) {\n const pct = Math.floor(\n ((plan.leftColumnCount - i) / (plan.leftColumnCount - i + 1)) * 100,\n );\n target = splitPane(target, \"v\", pct, targetDir, plan.editor || undefined);\n }\n\n // --- Right column: additional editor panes + optional server pane ---\n const serverCount = plan.hasServer ? 1 : 0;\n const totalRight = plan.rightColumnEditorCount + serverCount;\n target = rightColId;\n for (let i = 1; i < totalRight; i++) {\n const isServer = plan.hasServer && i === totalRight - 1;\n const pct = Math.floor(\n ((totalRight - i) / (totalRight - i + 1)) * 100,\n );\n const cmd = isServer\n ? (plan.serverCommand ?? undefined)\n : (plan.editor || undefined);\n target = splitPane(target, \"v\", pct, targetDir, cmd);\n }\n\n // Root pane was created with a plain shell — replace it with the editor\n if (plan.editor) {\n tmux(`respawn-pane -k -t \"${rootId}\" -c \"${targetDir}\" \"${plan.editor}; exec $SHELL\"`);\n }\n\n // Focus the first editor pane\n tmux(`select-pane -t \"${rootId}\"`);\n}\n\nexport async function launch(targetDir: string, cliOverrides?: CLIOverrides): Promise<void> {\n if (!existsSync(targetDir)) {\n console.error(`Directory not found: ${targetDir}`);\n process.exit(1);\n }\n\n await ensureTmux();\n\n const opts = resolveConfig(targetDir, cliOverrides ?? {});\n const plan = planLayout(opts);\n\n if (plan.editor) await ensureCommand(plan.editor);\n if (plan.sidebarCommand) await ensureCommand(plan.sidebarCommand);\n if (plan.serverCommand) {\n const serverBin = plan.serverCommand.split(\" \")[0]!;\n await ensureCommand(serverBin);\n }\n\n const dirName = basename(targetDir).replace(/[^a-zA-Z0-9_-]/g, \"_\");\n const sessionName = `tp-${dirName}`;\n\n // If session already exists, just re-attach\n try {\n execSync(`tmux has-session -t \"${sessionName}\"`, { stdio: \"ignore\" });\n console.log(`Attaching to existing session: ${sessionName}`);\n execSync(`tmux attach-session -t \"${sessionName}\"`, { stdio: \"inherit\" });\n return;\n } catch {\n // Session doesn't exist — create it below\n }\n\n buildSession(sessionName, targetDir, plan);\n\n try {\n execSync(`tmux attach-session -t \"${sessionName}\"`, { stdio: \"inherit\" });\n } catch {\n // tmux exited (user detached / closed) — that's fine\n }\n}\n","export interface LayoutOptions {\n editor: string;\n editorPanes: number;\n editorSize: number;\n sidebarCommand: string;\n server: string;\n}\n\nconst DEFAULT_OPTIONS: LayoutOptions = {\n editor: \"claude\",\n editorPanes: 3,\n editorSize: 75,\n sidebarCommand: \"lazygit\",\n server: \"true\",\n};\n\nexport interface LayoutPlan {\n editorSize: number;\n sidebarSize: number;\n leftColumnCount: number;\n rightColumnEditorCount: number;\n editor: string;\n sidebarCommand: string;\n hasServer: boolean;\n serverCommand: string | null;\n}\n\nfunction parseServer(value: string): { hasServer: boolean; serverCommand: string | null } {\n if (value === \"false\" || value === \"\") {\n return { hasServer: false, serverCommand: null };\n }\n if (value === \"true\") {\n return { hasServer: true, serverCommand: null };\n }\n return { hasServer: true, serverCommand: value };\n}\n\nexport type PresetName = \"minimal\" | \"full\" | \"pair\";\n\nconst PRESETS: Record<PresetName, Partial<LayoutOptions>> = {\n minimal: { editorPanes: 1, server: \"false\" },\n full: { editorPanes: 3, server: \"true\" },\n pair: { editorPanes: 2, server: \"true\" },\n};\n\nexport function isPresetName(value: string): value is PresetName {\n return value in PRESETS;\n}\n\nexport function getPreset(name: PresetName): Partial<LayoutOptions> {\n return PRESETS[name];\n}\n\nexport function planLayout(partial?: Partial<LayoutOptions>): LayoutPlan {\n const opts = { ...DEFAULT_OPTIONS, ...partial };\n const leftColumnCount = Math.ceil(opts.editorPanes / 2);\n const { hasServer, serverCommand } = parseServer(opts.server);\n return {\n editorSize: opts.editorSize,\n sidebarSize: 100 - opts.editorSize,\n leftColumnCount,\n rightColumnEditorCount: opts.editorPanes - leftColumnCount,\n editor: opts.editor,\n sidebarCommand: opts.sidebarCommand,\n hasServer,\n serverCommand,\n };\n}\n"],"mappings":"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","names":["existsSync","join","resolve","join","existsSync"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "termplex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Launch configurable multi-pane terminal workspaces with one command",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@10.29.2",
|
|
7
|
+
"bin": {
|
|
8
|
+
"termplex": "./dist/index.js",
|
|
9
|
+
"ws": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
15
|
+
"lint": "eslint src/",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:coverage": "vitest run --coverage",
|
|
19
|
+
"prepublishOnly": "pnpm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"terminal",
|
|
23
|
+
"multiplexer",
|
|
24
|
+
"workspace",
|
|
25
|
+
"tmux",
|
|
26
|
+
"developer-tools",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"author": "juan294",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/juan294/termplex.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/juan294/termplex#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/juan294/termplex/issues"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.2.3",
|
|
47
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
48
|
+
"tsup": "^8.0.0",
|
|
49
|
+
"typescript": "^5.7.0",
|
|
50
|
+
"vitest": "^3.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|