termplex 0.1.6 → 0.1.8
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/README.md +2 -1
- package/dist/index.js +249 -92
- package/package.json +12 -2
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ termplex . (panes=3, editor=claude, sidebar=lazygit, server=true)
|
|
|
48
48
|
| `pair` | 2 | yes | Two editors + dev server |
|
|
49
49
|
| `minimal` | 1 | no | Simple editor + sidebar only |
|
|
50
50
|
| `cli` | 1 | yes | CLI tool development -- editor + npm login |
|
|
51
|
+
| `mtop` | 2 | yes | System monitoring -- editor + mtop + server |
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
54
|
termplex . --layout minimal # 1 editor pane, no server
|
|
@@ -82,7 +83,7 @@ Config resolution order: **CLI flags > .termplex > machine config > preset > def
|
|
|
82
83
|
|
|
83
84
|
| Flag | Description |
|
|
84
85
|
|---|---|
|
|
85
|
-
| `-l, --layout <preset>` | Use a layout preset (`minimal`, `full`, `pair`, `cli`) |
|
|
86
|
+
| `-l, --layout <preset>` | Use a layout preset (`minimal`, `full`, `pair`, `cli`, `mtop`) |
|
|
86
87
|
| `-f, --force` | Kill existing session and recreate it |
|
|
87
88
|
| `--editor <cmd>` | Override editor command |
|
|
88
89
|
| `--panes <n>` | Override number of editor panes |
|
package/dist/index.js
CHANGED
|
@@ -16,11 +16,10 @@ function ensureConfig() {
|
|
|
16
16
|
if (!existsSync(PROJECTS_FILE)) writeFileSync(PROJECTS_FILE, "");
|
|
17
17
|
if (!existsSync(CONFIG_FILE)) writeFileSync(CONFIG_FILE, "editor=claude\n");
|
|
18
18
|
}
|
|
19
|
-
function
|
|
20
|
-
ensureConfig();
|
|
19
|
+
function readKVFile(path) {
|
|
21
20
|
const map = /* @__PURE__ */ new Map();
|
|
22
|
-
if (!existsSync(
|
|
23
|
-
const content = readFileSync(
|
|
21
|
+
if (!existsSync(path)) return map;
|
|
22
|
+
const content = readFileSync(path, "utf-8").trim();
|
|
24
23
|
if (!content) return map;
|
|
25
24
|
for (const line of content.split("\n")) {
|
|
26
25
|
const idx = line.indexOf("=");
|
|
@@ -29,22 +28,14 @@ function readKV(file) {
|
|
|
29
28
|
}
|
|
30
29
|
return map;
|
|
31
30
|
}
|
|
31
|
+
function readKV(file) {
|
|
32
|
+
ensureConfig();
|
|
33
|
+
return readKVFile(file);
|
|
34
|
+
}
|
|
32
35
|
function writeKV(file, map) {
|
|
33
36
|
const lines = [...map.entries()].map(([k, v]) => `${k}=${v}`);
|
|
34
37
|
writeFileSync(file, lines.join("\n") + "\n");
|
|
35
38
|
}
|
|
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
39
|
function addProject(name, path) {
|
|
49
40
|
const projects = readKV(PROJECTS_FILE);
|
|
50
41
|
projects.set(name, path);
|
|
@@ -52,8 +43,9 @@ function addProject(name, path) {
|
|
|
52
43
|
}
|
|
53
44
|
function removeProject(name) {
|
|
54
45
|
const projects = readKV(PROJECTS_FILE);
|
|
55
|
-
projects.delete(name);
|
|
46
|
+
const existed = projects.delete(name);
|
|
56
47
|
writeKV(PROJECTS_FILE, projects);
|
|
48
|
+
return existed;
|
|
57
49
|
}
|
|
58
50
|
function getProject(name) {
|
|
59
51
|
return readKV(PROJECTS_FILE).get(name);
|
|
@@ -85,7 +77,8 @@ var DEFAULT_OPTIONS = {
|
|
|
85
77
|
editorPanes: 3,
|
|
86
78
|
editorSize: 75,
|
|
87
79
|
sidebarCommand: "lazygit",
|
|
88
|
-
server: "true"
|
|
80
|
+
server: "true",
|
|
81
|
+
secondaryEditor: ""
|
|
89
82
|
};
|
|
90
83
|
function parseServer(value) {
|
|
91
84
|
if (value === "false" || value === "") {
|
|
@@ -100,7 +93,8 @@ var PRESETS = {
|
|
|
100
93
|
minimal: { editorPanes: 1, server: "false" },
|
|
101
94
|
full: { editorPanes: 3, server: "true" },
|
|
102
95
|
pair: { editorPanes: 2, server: "true" },
|
|
103
|
-
cli: { editorPanes: 1, server: "npm login" }
|
|
96
|
+
cli: { editorPanes: 1, server: "npm login" },
|
|
97
|
+
mtop: { editorPanes: 2, server: "true", secondaryEditor: "mtop" }
|
|
104
98
|
};
|
|
105
99
|
function isPresetName(value) {
|
|
106
100
|
return value in PRESETS;
|
|
@@ -120,7 +114,8 @@ function planLayout(partial) {
|
|
|
120
114
|
editor: opts.editor,
|
|
121
115
|
sidebarCommand: opts.sidebarCommand,
|
|
122
116
|
hasServer,
|
|
123
|
-
serverCommand
|
|
117
|
+
serverCommand,
|
|
118
|
+
secondaryEditor: opts.secondaryEditor || null
|
|
124
119
|
};
|
|
125
120
|
}
|
|
126
121
|
|
|
@@ -149,32 +144,6 @@ function isCommandInstalled(cmd) {
|
|
|
149
144
|
return false;
|
|
150
145
|
}
|
|
151
146
|
}
|
|
152
|
-
function getInstallCommand() {
|
|
153
|
-
if (process.platform === "darwin") {
|
|
154
|
-
try {
|
|
155
|
-
execSync("command -v brew", { stdio: "ignore" });
|
|
156
|
-
return "brew install tmux";
|
|
157
|
-
} catch {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (process.platform === "linux") {
|
|
162
|
-
const managers = [
|
|
163
|
-
["apt-get", "sudo apt-get install -y tmux"],
|
|
164
|
-
["dnf", "sudo dnf install -y tmux"],
|
|
165
|
-
["yum", "sudo yum install -y tmux"],
|
|
166
|
-
["pacman", "sudo pacman -S --noconfirm tmux"]
|
|
167
|
-
];
|
|
168
|
-
for (const [bin, cmd] of managers) {
|
|
169
|
-
try {
|
|
170
|
-
execSync(`command -v ${bin}`, { stdio: "ignore" });
|
|
171
|
-
return cmd;
|
|
172
|
-
} catch {
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
147
|
function prompt(question) {
|
|
179
148
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
180
149
|
return new Promise((resolve2) => {
|
|
@@ -187,7 +156,7 @@ function prompt(question) {
|
|
|
187
156
|
var KNOWN_INSTALL_COMMANDS = {
|
|
188
157
|
claude: () => "npm install -g @anthropic-ai/claude-code",
|
|
189
158
|
lazygit: () => {
|
|
190
|
-
if (process.platform === "darwin") {
|
|
159
|
+
if (process.platform === "darwin" || process.platform === "linux") {
|
|
191
160
|
try {
|
|
192
161
|
execSync("command -v brew", { stdio: "ignore" });
|
|
193
162
|
return "brew install lazygit";
|
|
@@ -195,14 +164,32 @@ var KNOWN_INSTALL_COMMANDS = {
|
|
|
195
164
|
return null;
|
|
196
165
|
}
|
|
197
166
|
}
|
|
198
|
-
|
|
167
|
+
return null;
|
|
168
|
+
},
|
|
169
|
+
tmux: () => {
|
|
170
|
+
if (process.platform === "darwin") {
|
|
199
171
|
try {
|
|
200
172
|
execSync("command -v brew", { stdio: "ignore" });
|
|
201
|
-
return "brew install
|
|
173
|
+
return "brew install tmux";
|
|
202
174
|
} catch {
|
|
203
175
|
return null;
|
|
204
176
|
}
|
|
205
177
|
}
|
|
178
|
+
if (process.platform === "linux") {
|
|
179
|
+
const managers = [
|
|
180
|
+
["apt-get", "sudo apt-get install -y tmux"],
|
|
181
|
+
["dnf", "sudo dnf install -y tmux"],
|
|
182
|
+
["yum", "sudo yum install -y tmux"],
|
|
183
|
+
["pacman", "sudo pacman -S --noconfirm tmux"]
|
|
184
|
+
];
|
|
185
|
+
for (const [bin, cmd] of managers) {
|
|
186
|
+
try {
|
|
187
|
+
execSync(`command -v ${bin}`, { stdio: "ignore" });
|
|
188
|
+
return cmd;
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
206
193
|
return null;
|
|
207
194
|
}
|
|
208
195
|
};
|
|
@@ -215,7 +202,7 @@ async function ensureCommand(cmd) {
|
|
|
215
202
|
`\`${cmd}\` is required but not installed, and no known install method was found.`
|
|
216
203
|
);
|
|
217
204
|
console.error(
|
|
218
|
-
`Please install \`${cmd}\` manually or change your config with: termplex
|
|
205
|
+
`Please install \`${cmd}\` manually or change your config with: termplex set editor <command>`
|
|
219
206
|
);
|
|
220
207
|
process.exit(1);
|
|
221
208
|
}
|
|
@@ -241,37 +228,6 @@ async function ensureCommand(cmd) {
|
|
|
241
228
|
console.log(`\`${cmd}\` installed successfully!
|
|
242
229
|
`);
|
|
243
230
|
}
|
|
244
|
-
async function ensureTmux() {
|
|
245
|
-
if (isCommandInstalled("tmux")) return;
|
|
246
|
-
const installCmd = getInstallCommand();
|
|
247
|
-
if (!installCmd) {
|
|
248
|
-
console.error(
|
|
249
|
-
"tmux is required but not installed, and no supported package manager was found."
|
|
250
|
-
);
|
|
251
|
-
console.error("Please install tmux manually and try again.");
|
|
252
|
-
process.exit(1);
|
|
253
|
-
}
|
|
254
|
-
console.log("tmux is required but not installed on this machine.");
|
|
255
|
-
const answer = await prompt(`Install it now with \`${installCmd}\`? [Y/n] `);
|
|
256
|
-
if (answer && answer !== "y" && answer !== "yes") {
|
|
257
|
-
console.log("tmux is required for termplex to work. Exiting.");
|
|
258
|
-
process.exit(1);
|
|
259
|
-
}
|
|
260
|
-
console.log(`Running: ${installCmd}`);
|
|
261
|
-
try {
|
|
262
|
-
execSync(installCmd, { stdio: "inherit" });
|
|
263
|
-
} catch {
|
|
264
|
-
console.error(
|
|
265
|
-
"Failed to install tmux. Please install it manually and try again."
|
|
266
|
-
);
|
|
267
|
-
process.exit(1);
|
|
268
|
-
}
|
|
269
|
-
if (!isCommandInstalled("tmux")) {
|
|
270
|
-
console.error("tmux still not found after install. Please check your PATH.");
|
|
271
|
-
process.exit(1);
|
|
272
|
-
}
|
|
273
|
-
console.log("tmux installed successfully!\n");
|
|
274
|
-
}
|
|
275
231
|
function resolveConfig(targetDir, cliOverrides) {
|
|
276
232
|
const project = readKVFile(join2(targetDir, ".termplex"));
|
|
277
233
|
const layoutKey = cliOverrides.layout ?? project.get("layout") ?? getConfig("layout");
|
|
@@ -280,7 +236,7 @@ function resolveConfig(targetDir, cliOverrides) {
|
|
|
280
236
|
if (isPresetName(layoutKey)) {
|
|
281
237
|
base = getPreset(layoutKey);
|
|
282
238
|
} else {
|
|
283
|
-
console.warn(`Unknown layout preset: "${layoutKey}",
|
|
239
|
+
console.warn(`Unknown layout preset: "${layoutKey}". Valid presets: minimal, full, pair, cli, mtop. Using defaults.`);
|
|
284
240
|
}
|
|
285
241
|
}
|
|
286
242
|
const pick = (cli, projKey) => cli ?? project.get(projKey) ?? getConfig(projKey);
|
|
@@ -293,8 +249,24 @@ function resolveConfig(targetDir, cliOverrides) {
|
|
|
293
249
|
const result = { ...base };
|
|
294
250
|
if (editor !== void 0) result.editor = editor;
|
|
295
251
|
if (sidebar !== void 0) result.sidebarCommand = sidebar;
|
|
296
|
-
if (panes !== void 0)
|
|
297
|
-
|
|
252
|
+
if (panes !== void 0) {
|
|
253
|
+
const parsed = parseInt(panes, 10);
|
|
254
|
+
if (Number.isNaN(parsed) || parsed < 1) {
|
|
255
|
+
console.warn(`Invalid panes value: "${panes}". Must be a positive integer. Using default (3).`);
|
|
256
|
+
result.editorPanes = 3;
|
|
257
|
+
} else {
|
|
258
|
+
result.editorPanes = parsed;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (editorSize !== void 0) {
|
|
262
|
+
const parsed = parseInt(editorSize, 10);
|
|
263
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > 99) {
|
|
264
|
+
console.warn(`Invalid editor-size value: "${editorSize}". Must be 1-99. Using default (75).`);
|
|
265
|
+
result.editorSize = 75;
|
|
266
|
+
} else {
|
|
267
|
+
result.editorSize = parsed;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
298
270
|
if (server !== void 0) result.server = server;
|
|
299
271
|
return { opts: result, mouse };
|
|
300
272
|
}
|
|
@@ -313,7 +285,7 @@ function buildSession(sessionName, targetDir, plan, mouse) {
|
|
|
313
285
|
const totalRight = plan.rightColumnEditorCount + serverCount;
|
|
314
286
|
let rightColId = null;
|
|
315
287
|
if (totalRight > 0) {
|
|
316
|
-
const firstCmd = plan.rightColumnEditorCount > 0 ? plan.editor || void 0 : plan.serverCommand ?? void 0;
|
|
288
|
+
const firstCmd = plan.rightColumnEditorCount > 0 ? plan.secondaryEditor ?? (plan.editor || void 0) : plan.serverCommand ?? void 0;
|
|
317
289
|
rightColId = splitPane(rootId, "h", 50, targetDir, firstCmd);
|
|
318
290
|
}
|
|
319
291
|
let target = rootId;
|
|
@@ -330,7 +302,7 @@ function buildSession(sessionName, targetDir, plan, mouse) {
|
|
|
330
302
|
const pct = Math.floor(
|
|
331
303
|
(totalRight - i) / (totalRight - i + 1) * 100
|
|
332
304
|
);
|
|
333
|
-
const cmd = isServer ? plan.serverCommand ?? void 0 : plan.editor || void 0;
|
|
305
|
+
const cmd = isServer ? plan.serverCommand ?? void 0 : plan.secondaryEditor ?? (plan.editor || void 0);
|
|
334
306
|
target = splitPane(target, "v", pct, targetDir, cmd);
|
|
335
307
|
}
|
|
336
308
|
}
|
|
@@ -344,11 +316,15 @@ async function launch(targetDir, cliOverrides) {
|
|
|
344
316
|
console.error(`Directory not found: ${targetDir}`);
|
|
345
317
|
process.exit(1);
|
|
346
318
|
}
|
|
347
|
-
await
|
|
319
|
+
await ensureCommand("tmux");
|
|
348
320
|
const { opts, mouse } = resolveConfig(targetDir, cliOverrides ?? {});
|
|
349
321
|
const plan = planLayout(opts);
|
|
350
322
|
if (plan.editor) await ensureCommand(plan.editor);
|
|
351
323
|
if (plan.sidebarCommand) await ensureCommand(plan.sidebarCommand);
|
|
324
|
+
if (plan.secondaryEditor) {
|
|
325
|
+
const secondaryBin = plan.secondaryEditor.split(" ")[0];
|
|
326
|
+
await ensureCommand(secondaryBin);
|
|
327
|
+
}
|
|
352
328
|
if (plan.serverCommand) {
|
|
353
329
|
const serverBin = plan.serverCommand.split(" ")[0];
|
|
354
330
|
await ensureCommand(serverBin);
|
|
@@ -376,9 +352,150 @@ async function launch(targetDir, cliOverrides) {
|
|
|
376
352
|
}
|
|
377
353
|
}
|
|
378
354
|
|
|
355
|
+
// src/completion.ts
|
|
356
|
+
function bashCompletion() {
|
|
357
|
+
return `_termplex_completions() {
|
|
358
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
359
|
+
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
360
|
+
|
|
361
|
+
local subcommands="add remove list set config completion"
|
|
362
|
+
local flags="--help --version --force --layout --editor --panes --editor-size --sidebar --server --mouse --no-mouse -h -v -f -l"
|
|
363
|
+
local config_keys="editor sidebar panes editor-size server mouse layout"
|
|
364
|
+
local presets="minimal full pair cli mtop"
|
|
365
|
+
|
|
366
|
+
local projects=""
|
|
367
|
+
if [[ -f "\${HOME}/.config/termplex/projects" ]]; then
|
|
368
|
+
projects=$(cut -d= -f1 "\${HOME}/.config/termplex/projects")
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
372
|
+
COMPREPLY=($(compgen -W "\${subcommands} \${projects} ." -- "\${cur}"))
|
|
373
|
+
return
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
local subcmd="\${COMP_WORDS[1]}"
|
|
377
|
+
|
|
378
|
+
case "\${subcmd}" in
|
|
379
|
+
remove)
|
|
380
|
+
COMPREPLY=($(compgen -W "\${projects}" -- "\${cur}"))
|
|
381
|
+
;;
|
|
382
|
+
set)
|
|
383
|
+
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
384
|
+
COMPREPLY=($(compgen -W "\${config_keys}" -- "\${cur}"))
|
|
385
|
+
elif [[ \${COMP_CWORD} -eq 3 && "\${prev}" == "layout" ]]; then
|
|
386
|
+
COMPREPLY=($(compgen -W "\${presets}" -- "\${cur}"))
|
|
387
|
+
fi
|
|
388
|
+
;;
|
|
389
|
+
add|list|config|completion)
|
|
390
|
+
;;
|
|
391
|
+
*)
|
|
392
|
+
case "\${prev}" in
|
|
393
|
+
--layout|-l)
|
|
394
|
+
COMPREPLY=($(compgen -W "\${presets}" -- "\${cur}"))
|
|
395
|
+
;;
|
|
396
|
+
--editor|--panes|--editor-size|--sidebar|--server)
|
|
397
|
+
;;
|
|
398
|
+
*)
|
|
399
|
+
COMPREPLY=($(compgen -W "\${flags}" -- "\${cur}"))
|
|
400
|
+
;;
|
|
401
|
+
esac
|
|
402
|
+
;;
|
|
403
|
+
esac
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
complete -F _termplex_completions termplex
|
|
407
|
+
complete -F _termplex_completions ws`;
|
|
408
|
+
}
|
|
409
|
+
function zshCompletion() {
|
|
410
|
+
return `_termplex() {
|
|
411
|
+
local -a subcommands flags config_keys presets projects
|
|
412
|
+
|
|
413
|
+
subcommands=(add remove list set config completion)
|
|
414
|
+
flags=(--help --version --force --layout --editor --panes --editor-size --sidebar --server --mouse --no-mouse -h -v -f -l)
|
|
415
|
+
config_keys=(editor sidebar panes editor-size server mouse layout)
|
|
416
|
+
presets=(minimal full pair cli mtop)
|
|
417
|
+
|
|
418
|
+
if [[ -f "\${HOME}/.config/termplex/projects" ]]; then
|
|
419
|
+
projects=(\${(f)"$(cut -d= -f1 "\${HOME}/.config/termplex/projects")"})
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
if (( CURRENT == 2 )); then
|
|
423
|
+
_alternative \\
|
|
424
|
+
'subcommands:subcommand:compadd -a subcommands' \\
|
|
425
|
+
'projects:project:compadd -a projects' \\
|
|
426
|
+
'special:special:compadd .'
|
|
427
|
+
return
|
|
428
|
+
fi
|
|
429
|
+
|
|
430
|
+
local subcmd="\${words[2]}"
|
|
431
|
+
|
|
432
|
+
case "\${subcmd}" in
|
|
433
|
+
remove)
|
|
434
|
+
compadd -a projects
|
|
435
|
+
;;
|
|
436
|
+
set)
|
|
437
|
+
if (( CURRENT == 3 )); then
|
|
438
|
+
compadd -a config_keys
|
|
439
|
+
elif (( CURRENT == 4 )) && [[ "\${words[3]}" == "layout" ]]; then
|
|
440
|
+
compadd -a presets
|
|
441
|
+
fi
|
|
442
|
+
;;
|
|
443
|
+
add|list|config|completion)
|
|
444
|
+
;;
|
|
445
|
+
*)
|
|
446
|
+
case "\${words[CURRENT-1]}" in
|
|
447
|
+
--layout|-l)
|
|
448
|
+
compadd -a presets
|
|
449
|
+
;;
|
|
450
|
+
--editor|--panes|--editor-size|--sidebar|--server)
|
|
451
|
+
;;
|
|
452
|
+
*)
|
|
453
|
+
compadd -a flags
|
|
454
|
+
;;
|
|
455
|
+
esac
|
|
456
|
+
;;
|
|
457
|
+
esac
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
compdef _termplex termplex
|
|
461
|
+
compdef _termplex ws`;
|
|
462
|
+
}
|
|
463
|
+
function fishCompletion() {
|
|
464
|
+
const subcommands = "add remove list set config completion";
|
|
465
|
+
const noSubcmd = `not __fish_seen_subcommand_from ${subcommands}`;
|
|
466
|
+
const lines = [];
|
|
467
|
+
for (const cmd of ["termplex", "ws"]) {
|
|
468
|
+
lines.push(`# Completions for ${cmd}`);
|
|
469
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "add" -d "Register a project"`);
|
|
470
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "remove" -d "Remove a project"`);
|
|
471
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "list" -d "List projects"`);
|
|
472
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "set" -d "Set config value"`);
|
|
473
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "config" -d "Show config"`);
|
|
474
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "completion" -d "Output completion script"`);
|
|
475
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "(cut -d= -f1 ~/.config/termplex/projects 2>/dev/null)" -d "project"`);
|
|
476
|
+
lines.push(`complete -c ${cmd} -n "${noSubcmd}" -f -a "." -d "Current directory"`);
|
|
477
|
+
lines.push(`complete -c ${cmd} -n "__fish_seen_subcommand_from remove" -f -a "(cut -d= -f1 ~/.config/termplex/projects 2>/dev/null)" -d "project"`);
|
|
478
|
+
lines.push(`complete -c ${cmd} -n "__fish_seen_subcommand_from set" -f -a "editor sidebar panes editor-size server mouse layout"`);
|
|
479
|
+
lines.push(`complete -c ${cmd} -s h -l help -d "Show help"`);
|
|
480
|
+
lines.push(`complete -c ${cmd} -s v -l version -d "Show version"`);
|
|
481
|
+
lines.push(`complete -c ${cmd} -s f -l force -d "Force recreate session"`);
|
|
482
|
+
lines.push(`complete -c ${cmd} -s l -l layout -rf -a "minimal full pair cli mtop" -d "Layout preset"`);
|
|
483
|
+
lines.push(`complete -c ${cmd} -l editor -rf -d "Editor command"`);
|
|
484
|
+
lines.push(`complete -c ${cmd} -l panes -rf -d "Number of editor panes"`);
|
|
485
|
+
lines.push(`complete -c ${cmd} -l editor-size -rf -d "Editor width %%"`);
|
|
486
|
+
lines.push(`complete -c ${cmd} -l sidebar -rf -d "Sidebar command"`);
|
|
487
|
+
lines.push(`complete -c ${cmd} -l server -rf -d "Server pane"`);
|
|
488
|
+
lines.push(`complete -c ${cmd} -l mouse -d "Enable mouse mode"`);
|
|
489
|
+
lines.push(`complete -c ${cmd} -l no-mouse -d "Disable mouse mode"`);
|
|
490
|
+
lines.push("");
|
|
491
|
+
}
|
|
492
|
+
return lines.join("\n").trimEnd();
|
|
493
|
+
}
|
|
494
|
+
|
|
379
495
|
// src/index.ts
|
|
380
496
|
var HELP = `
|
|
381
497
|
termplex \u2014 Launch configurable multi-pane terminal workspaces
|
|
498
|
+
Aliases: ws
|
|
382
499
|
|
|
383
500
|
Usage:
|
|
384
501
|
termplex <target> Launch workspace (project name, path, or '.')
|
|
@@ -387,12 +504,13 @@ Usage:
|
|
|
387
504
|
termplex list List all registered projects
|
|
388
505
|
termplex set <key> [value] Set a machine-level config value
|
|
389
506
|
termplex config Show current machine configuration
|
|
507
|
+
termplex completion <shell> Output shell completion script (bash, zsh, fish)
|
|
390
508
|
|
|
391
509
|
Options:
|
|
392
510
|
-h, --help Show this help message
|
|
393
511
|
-v, --version Show version number
|
|
394
512
|
-f, --force Kill existing session and recreate it
|
|
395
|
-
-l, --layout <preset> Use a layout preset (minimal, full, pair, cli)
|
|
513
|
+
-l, --layout <preset> Use a layout preset (minimal, full, pair, cli, mtop)
|
|
396
514
|
--editor <cmd> Override editor command
|
|
397
515
|
--panes <n> Override number of editor panes
|
|
398
516
|
--editor-size <n> Override editor width %
|
|
@@ -414,11 +532,17 @@ Layout presets:
|
|
|
414
532
|
full 3 editor panes + server (default)
|
|
415
533
|
pair 2 editor panes + server
|
|
416
534
|
cli 1 editor pane + server (npm login)
|
|
535
|
+
mtop editor + mtop + server + lazygit sidebar
|
|
417
536
|
|
|
418
537
|
Per-project config:
|
|
419
538
|
Place a .termplex file in your project root with key=value pairs.
|
|
420
539
|
Project config overrides machine config; CLI flags override both.
|
|
421
540
|
|
|
541
|
+
Shell completion:
|
|
542
|
+
Bash: echo 'eval "$(termplex completion bash)"' >> ~/.bashrc
|
|
543
|
+
Zsh: echo 'eval "$(termplex completion zsh)"' >> ~/.zshrc
|
|
544
|
+
Fish: termplex completion fish > ~/.config/fish/completions/termplex.fish
|
|
545
|
+
|
|
422
546
|
Examples:
|
|
423
547
|
termplex . Launch workspace in current directory
|
|
424
548
|
termplex myapp Launch workspace for registered project
|
|
@@ -432,6 +556,7 @@ function showHelp() {
|
|
|
432
556
|
}
|
|
433
557
|
var parseOpts = {
|
|
434
558
|
allowPositionals: true,
|
|
559
|
+
allowNegative: true,
|
|
435
560
|
options: {
|
|
436
561
|
help: { type: "boolean", short: "h" },
|
|
437
562
|
version: { type: "boolean", short: "v" },
|
|
@@ -457,7 +582,7 @@ function safeParse() {
|
|
|
457
582
|
}
|
|
458
583
|
var { values, positionals } = safeParse();
|
|
459
584
|
if (values.version) {
|
|
460
|
-
console.log("0.1.
|
|
585
|
+
console.log("0.1.8");
|
|
461
586
|
process.exit(0);
|
|
462
587
|
}
|
|
463
588
|
if (values.help) {
|
|
@@ -487,8 +612,13 @@ switch (subcommand) {
|
|
|
487
612
|
console.error("Usage: termplex remove <name>");
|
|
488
613
|
process.exit(1);
|
|
489
614
|
}
|
|
490
|
-
removeProject(name);
|
|
491
|
-
|
|
615
|
+
const existed = removeProject(name);
|
|
616
|
+
if (existed) {
|
|
617
|
+
console.log(`Removed: ${name}`);
|
|
618
|
+
} else {
|
|
619
|
+
console.error(`Project not found: ${name}`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
492
622
|
break;
|
|
493
623
|
}
|
|
494
624
|
case "list": {
|
|
@@ -509,6 +639,10 @@ switch (subcommand) {
|
|
|
509
639
|
console.error("Usage: termplex set <key> [value]");
|
|
510
640
|
process.exit(1);
|
|
511
641
|
}
|
|
642
|
+
const VALID_KEYS = ["editor", "sidebar", "panes", "editor-size", "server", "mouse", "layout"];
|
|
643
|
+
if (!VALID_KEYS.includes(key)) {
|
|
644
|
+
console.warn(`Warning: unknown config key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
645
|
+
}
|
|
512
646
|
setConfig(key, value ?? "");
|
|
513
647
|
if (value) {
|
|
514
648
|
console.log(`Set ${key} \u2192 ${value}`);
|
|
@@ -525,6 +659,30 @@ switch (subcommand) {
|
|
|
525
659
|
}
|
|
526
660
|
break;
|
|
527
661
|
}
|
|
662
|
+
case "completion": {
|
|
663
|
+
const [shell] = args;
|
|
664
|
+
if (!shell || !["bash", "zsh", "fish"].includes(shell)) {
|
|
665
|
+
console.error("Usage: termplex completion [bash|zsh|fish]");
|
|
666
|
+
console.error("");
|
|
667
|
+
console.error("Setup:");
|
|
668
|
+
console.error(` Bash: echo 'eval "$(termplex completion bash)"' >> ~/.bashrc`);
|
|
669
|
+
console.error(` Zsh: echo 'eval "$(termplex completion zsh)"' >> ~/.zshrc`);
|
|
670
|
+
console.error(" Fish: termplex completion fish > ~/.config/fish/completions/termplex.fish");
|
|
671
|
+
process.exit(shell ? 1 : 0);
|
|
672
|
+
}
|
|
673
|
+
switch (shell) {
|
|
674
|
+
case "bash":
|
|
675
|
+
console.log(bashCompletion());
|
|
676
|
+
break;
|
|
677
|
+
case "zsh":
|
|
678
|
+
console.log(zshCompletion());
|
|
679
|
+
break;
|
|
680
|
+
case "fish":
|
|
681
|
+
console.log(fishCompletion());
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
528
686
|
default: {
|
|
529
687
|
const target = subcommand;
|
|
530
688
|
let targetDir;
|
|
@@ -556,4 +714,3 @@ switch (subcommand) {
|
|
|
556
714
|
await launch(targetDir, overrides);
|
|
557
715
|
}
|
|
558
716
|
}
|
|
559
|
-
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termplex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Launch configurable multi-pane terminal workspaces with one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.29.2",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"test": "vitest run",
|
|
17
17
|
"test:watch": "vitest",
|
|
18
18
|
"test:coverage": "vitest run --coverage",
|
|
19
|
-
"prepublishOnly": "pnpm run build"
|
|
19
|
+
"prepublishOnly": "pnpm run build",
|
|
20
|
+
"prepare": "husky"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"terminal",
|
|
@@ -42,11 +43,20 @@
|
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=18"
|
|
44
45
|
},
|
|
46
|
+
"pnpm": {
|
|
47
|
+
"onlyBuiltDependencies": [
|
|
48
|
+
"esbuild"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
45
51
|
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
46
53
|
"@types/node": "^25.2.3",
|
|
47
54
|
"@vitest/coverage-v8": "^3.0.0",
|
|
55
|
+
"eslint": "^10.0.2",
|
|
56
|
+
"husky": "^9.1.7",
|
|
48
57
|
"tsup": "^8.0.0",
|
|
49
58
|
"typescript": "^5.7.0",
|
|
59
|
+
"typescript-eslint": "^8.56.1",
|
|
50
60
|
"vitest": "^3.0.0"
|
|
51
61
|
}
|
|
52
62
|
}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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 -f, --force Kill existing session and recreate it\n -l, --layout <preset> Use a layout preset (minimal, full, pair, cli)\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 --mouse / --no-mouse Enable/disable mouse mode (default: on)\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 mouse Enable tmux mouse mode (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 cli 1 editor pane + server (npm login)\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 parseOpts = {\n allowPositionals: true,\n options: {\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n force: { type: \"boolean\", short: \"f\" },\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 mouse: { type: \"boolean\" },\n },\n} as const;\n\nfunction safeParse() {\n try {\n return parseArgs(parseOpts);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Error: ${msg}`);\n console.error(`Run 'termplex --help' for usage information.`);\n process.exit(1);\n }\n}\n\nconst { values, positionals } = safeParse();\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 if (values.mouse !== undefined) overrides.mouse = values.mouse;\n if (values.force) overrides.force = true;\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 mouse?: boolean;\n force?: boolean;\n}\n\nfunction configureTmuxTitle(): void {\n try {\n tmux(`set-option -g set-titles on`);\n tmux(`set-option -g set-titles-string '#{s/^tp-//:session_name}'`);\n } catch {\n // Non-critical — continue if title config fails\n }\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 interface ResolvedConfig {\n opts: Partial<LayoutOptions>;\n mouse: boolean;\n}\n\nexport function resolveConfig(targetDir: string, cliOverrides: CLIOverrides): ResolvedConfig {\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 // Mouse: CLI > project > global > default (true)\n const mouse = cliOverrides.mouse ?? (project.has(\"mouse\") ? project.get(\"mouse\") !== \"false\" : (getConfig(\"mouse\") !== \"false\"));\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 { opts: result, mouse };\n}\n\nfunction configureMouseMode(sessionName: string, mouse: boolean): void {\n try {\n tmux(`set-option -t \"${sessionName}\" mouse ${mouse ? \"on\" : \"off\"}`);\n } catch {\n // Non-critical — continue if mouse config fails\n }\n}\n\nfunction buildSession(sessionName: string, targetDir: string, plan: LayoutPlan, mouse: boolean): 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 // Enable/disable mouse mode for this session\n configureMouseMode(sessionName, mouse);\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 // --- Right column (only if there are editor panes or a server pane) ---\n const serverCount = plan.hasServer ? 1 : 0;\n const totalRight = plan.rightColumnEditorCount + serverCount;\n let rightColId: string | null = null;\n if (totalRight > 0) {\n const firstCmd = plan.rightColumnEditorCount > 0\n ? (plan.editor || undefined)\n : (plan.serverCommand ?? undefined);\n rightColId = splitPane(rootId, \"h\", 50, targetDir, firstCmd);\n }\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 if (rightColId) {\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\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, mouse } = 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, kill it with --force or re-attach\n try {\n execSync(`tmux has-session -t \"${sessionName}\"`, { stdio: \"ignore\" });\n if (cliOverrides?.force) {\n execSync(`tmux kill-session -t \"${sessionName}\"`, { stdio: \"ignore\" });\n } else {\n console.log(`Attaching to existing session: ${sessionName}`);\n configureMouseMode(sessionName, mouse);\n configureTmuxTitle();\n execSync(`tmux attach-session -t \"${sessionName}\"`, { stdio: \"inherit\" });\n return;\n }\n } catch {\n // Session doesn't exist — create it below\n }\n\n buildSession(sessionName, targetDir, plan, mouse);\n\n try {\n configureTmuxTitle();\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\" | \"cli\";\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 cli: { editorPanes: 1, server: \"npm login\" },\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"]}
|