termplex 0.1.5 → 0.1.7
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 +4 -1
- package/dist/index.js +33 -12
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
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,13 +83,14 @@ 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 |
|
|
89
90
|
| `--editor-size <n>` | Override editor width percentage |
|
|
90
91
|
| `--sidebar <cmd>` | Override sidebar command |
|
|
91
92
|
| `--server <value>` | Server pane: `true`, `false`, or a command |
|
|
93
|
+
| `--mouse` / `--no-mouse` | Enable/disable tmux mouse mode (default: on) |
|
|
92
94
|
| `-h, --help` | Show help message |
|
|
93
95
|
| `-v, --version` | Show version number |
|
|
94
96
|
|
|
@@ -101,6 +103,7 @@ Config resolution order: **CLI flags > .termplex > machine config > preset > def
|
|
|
101
103
|
| `panes` | `3` | Number of editor panes |
|
|
102
104
|
| `editor-size` | `75` | Width percentage for the editor grid |
|
|
103
105
|
| `server` | `true` | Server pane: `true` (shell), `false` (none), or a command |
|
|
106
|
+
| `mouse` | `true` | Enable tmux mouse mode: `true` or `false` |
|
|
104
107
|
| `layout` | | Default layout preset |
|
|
105
108
|
|
|
106
109
|
Machine config is stored at `~/.config/termplex/config`:
|
package/dist/index.js
CHANGED
|
@@ -85,7 +85,8 @@ var DEFAULT_OPTIONS = {
|
|
|
85
85
|
editorPanes: 3,
|
|
86
86
|
editorSize: 75,
|
|
87
87
|
sidebarCommand: "lazygit",
|
|
88
|
-
server: "true"
|
|
88
|
+
server: "true",
|
|
89
|
+
secondaryEditor: ""
|
|
89
90
|
};
|
|
90
91
|
function parseServer(value) {
|
|
91
92
|
if (value === "false" || value === "") {
|
|
@@ -100,7 +101,8 @@ var PRESETS = {
|
|
|
100
101
|
minimal: { editorPanes: 1, server: "false" },
|
|
101
102
|
full: { editorPanes: 3, server: "true" },
|
|
102
103
|
pair: { editorPanes: 2, server: "true" },
|
|
103
|
-
cli: { editorPanes: 1, server: "npm login" }
|
|
104
|
+
cli: { editorPanes: 1, server: "npm login" },
|
|
105
|
+
mtop: { editorPanes: 2, server: "true", secondaryEditor: "mtop" }
|
|
104
106
|
};
|
|
105
107
|
function isPresetName(value) {
|
|
106
108
|
return value in PRESETS;
|
|
@@ -120,7 +122,8 @@ function planLayout(partial) {
|
|
|
120
122
|
editor: opts.editor,
|
|
121
123
|
sidebarCommand: opts.sidebarCommand,
|
|
122
124
|
hasServer,
|
|
123
|
-
serverCommand
|
|
125
|
+
serverCommand,
|
|
126
|
+
secondaryEditor: opts.secondaryEditor || null
|
|
124
127
|
};
|
|
125
128
|
}
|
|
126
129
|
|
|
@@ -289,23 +292,31 @@ function resolveConfig(targetDir, cliOverrides) {
|
|
|
289
292
|
const panes = pick(cliOverrides.panes, "panes");
|
|
290
293
|
const editorSize = pick(cliOverrides["editor-size"], "editor-size");
|
|
291
294
|
const server = pick(cliOverrides.server, "server");
|
|
295
|
+
const mouse = cliOverrides.mouse ?? (project.has("mouse") ? project.get("mouse") !== "false" : getConfig("mouse") !== "false");
|
|
292
296
|
const result = { ...base };
|
|
293
297
|
if (editor !== void 0) result.editor = editor;
|
|
294
298
|
if (sidebar !== void 0) result.sidebarCommand = sidebar;
|
|
295
299
|
if (panes !== void 0) result.editorPanes = parseInt(panes, 10);
|
|
296
300
|
if (editorSize !== void 0) result.editorSize = parseInt(editorSize, 10);
|
|
297
301
|
if (server !== void 0) result.server = server;
|
|
298
|
-
return result;
|
|
302
|
+
return { opts: result, mouse };
|
|
299
303
|
}
|
|
300
|
-
function
|
|
304
|
+
function configureMouseMode(sessionName, mouse) {
|
|
305
|
+
try {
|
|
306
|
+
tmux(`set-option -t "${sessionName}" mouse ${mouse ? "on" : "off"}`);
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function buildSession(sessionName, targetDir, plan, mouse) {
|
|
301
311
|
tmux(`new-session -d -s "${sessionName}" -c "${targetDir}"`);
|
|
302
312
|
const rootId = tmux(`display -t "${sessionName}:0" -p "#{pane_id}"`);
|
|
313
|
+
configureMouseMode(sessionName, mouse);
|
|
303
314
|
splitPane(rootId, "h", plan.sidebarSize, targetDir, plan.sidebarCommand || void 0);
|
|
304
315
|
const serverCount = plan.hasServer ? 1 : 0;
|
|
305
316
|
const totalRight = plan.rightColumnEditorCount + serverCount;
|
|
306
317
|
let rightColId = null;
|
|
307
318
|
if (totalRight > 0) {
|
|
308
|
-
const firstCmd = plan.rightColumnEditorCount > 0 ? plan.editor || void 0 : plan.serverCommand ?? void 0;
|
|
319
|
+
const firstCmd = plan.rightColumnEditorCount > 0 ? plan.secondaryEditor ?? (plan.editor || void 0) : plan.serverCommand ?? void 0;
|
|
309
320
|
rightColId = splitPane(rootId, "h", 50, targetDir, firstCmd);
|
|
310
321
|
}
|
|
311
322
|
let target = rootId;
|
|
@@ -322,7 +333,7 @@ function buildSession(sessionName, targetDir, plan) {
|
|
|
322
333
|
const pct = Math.floor(
|
|
323
334
|
(totalRight - i) / (totalRight - i + 1) * 100
|
|
324
335
|
);
|
|
325
|
-
const cmd = isServer ? plan.serverCommand ?? void 0 : plan.editor || void 0;
|
|
336
|
+
const cmd = isServer ? plan.serverCommand ?? void 0 : plan.secondaryEditor ?? (plan.editor || void 0);
|
|
326
337
|
target = splitPane(target, "v", pct, targetDir, cmd);
|
|
327
338
|
}
|
|
328
339
|
}
|
|
@@ -337,10 +348,14 @@ async function launch(targetDir, cliOverrides) {
|
|
|
337
348
|
process.exit(1);
|
|
338
349
|
}
|
|
339
350
|
await ensureTmux();
|
|
340
|
-
const opts = resolveConfig(targetDir, cliOverrides ?? {});
|
|
351
|
+
const { opts, mouse } = resolveConfig(targetDir, cliOverrides ?? {});
|
|
341
352
|
const plan = planLayout(opts);
|
|
342
353
|
if (plan.editor) await ensureCommand(plan.editor);
|
|
343
354
|
if (plan.sidebarCommand) await ensureCommand(plan.sidebarCommand);
|
|
355
|
+
if (plan.secondaryEditor) {
|
|
356
|
+
const secondaryBin = plan.secondaryEditor.split(" ")[0];
|
|
357
|
+
await ensureCommand(secondaryBin);
|
|
358
|
+
}
|
|
344
359
|
if (plan.serverCommand) {
|
|
345
360
|
const serverBin = plan.serverCommand.split(" ")[0];
|
|
346
361
|
await ensureCommand(serverBin);
|
|
@@ -353,13 +368,14 @@ async function launch(targetDir, cliOverrides) {
|
|
|
353
368
|
execSync(`tmux kill-session -t "${sessionName}"`, { stdio: "ignore" });
|
|
354
369
|
} else {
|
|
355
370
|
console.log(`Attaching to existing session: ${sessionName}`);
|
|
371
|
+
configureMouseMode(sessionName, mouse);
|
|
356
372
|
configureTmuxTitle();
|
|
357
373
|
execSync(`tmux attach-session -t "${sessionName}"`, { stdio: "inherit" });
|
|
358
374
|
return;
|
|
359
375
|
}
|
|
360
376
|
} catch {
|
|
361
377
|
}
|
|
362
|
-
buildSession(sessionName, targetDir, plan);
|
|
378
|
+
buildSession(sessionName, targetDir, plan, mouse);
|
|
363
379
|
try {
|
|
364
380
|
configureTmuxTitle();
|
|
365
381
|
execSync(`tmux attach-session -t "${sessionName}"`, { stdio: "inherit" });
|
|
@@ -383,12 +399,13 @@ Options:
|
|
|
383
399
|
-h, --help Show this help message
|
|
384
400
|
-v, --version Show version number
|
|
385
401
|
-f, --force Kill existing session and recreate it
|
|
386
|
-
-l, --layout <preset> Use a layout preset (minimal, full, pair, cli)
|
|
402
|
+
-l, --layout <preset> Use a layout preset (minimal, full, pair, cli, mtop)
|
|
387
403
|
--editor <cmd> Override editor command
|
|
388
404
|
--panes <n> Override number of editor panes
|
|
389
405
|
--editor-size <n> Override editor width %
|
|
390
406
|
--sidebar <cmd> Override sidebar command
|
|
391
407
|
--server <value> Server pane: true, false, or a command
|
|
408
|
+
--mouse / --no-mouse Enable/disable mouse mode (default: on)
|
|
392
409
|
|
|
393
410
|
Config keys:
|
|
394
411
|
editor Command for coding panes (default: claude)
|
|
@@ -396,6 +413,7 @@ Config keys:
|
|
|
396
413
|
panes Number of editor panes (default: 3)
|
|
397
414
|
editor-size Width % for editor grid (default: 75)
|
|
398
415
|
server Server pane toggle (default: true)
|
|
416
|
+
mouse Enable tmux mouse mode (default: true)
|
|
399
417
|
layout Default layout preset
|
|
400
418
|
|
|
401
419
|
Layout presets:
|
|
@@ -403,6 +421,7 @@ Layout presets:
|
|
|
403
421
|
full 3 editor panes + server (default)
|
|
404
422
|
pair 2 editor panes + server
|
|
405
423
|
cli 1 editor pane + server (npm login)
|
|
424
|
+
mtop editor + mtop + server + lazygit sidebar
|
|
406
425
|
|
|
407
426
|
Per-project config:
|
|
408
427
|
Place a .termplex file in your project root with key=value pairs.
|
|
@@ -430,7 +449,8 @@ var parseOpts = {
|
|
|
430
449
|
panes: { type: "string" },
|
|
431
450
|
"editor-size": { type: "string" },
|
|
432
451
|
sidebar: { type: "string" },
|
|
433
|
-
server: { type: "string" }
|
|
452
|
+
server: { type: "string" },
|
|
453
|
+
mouse: { type: "boolean" }
|
|
434
454
|
}
|
|
435
455
|
};
|
|
436
456
|
function safeParse() {
|
|
@@ -445,7 +465,7 @@ function safeParse() {
|
|
|
445
465
|
}
|
|
446
466
|
var { values, positionals } = safeParse();
|
|
447
467
|
if (values.version) {
|
|
448
|
-
console.log("0.1.
|
|
468
|
+
console.log("0.1.7");
|
|
449
469
|
process.exit(0);
|
|
450
470
|
}
|
|
451
471
|
if (values.help) {
|
|
@@ -539,6 +559,7 @@ switch (subcommand) {
|
|
|
539
559
|
if (values["editor-size"]) overrides["editor-size"] = values["editor-size"];
|
|
540
560
|
if (values.sidebar) overrides.sidebar = values.sidebar;
|
|
541
561
|
if (values.server) overrides.server = values.server;
|
|
562
|
+
if (values.mouse !== void 0) overrides.mouse = values.mouse;
|
|
542
563
|
if (values.force) overrides.force = true;
|
|
543
564
|
await launch(targetDir, overrides);
|
|
544
565
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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 -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\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 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 },\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.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 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 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 // --- 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 = 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 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);\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"]}
|
|
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, mtop)\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 mtop editor + mtop + server + lazygit sidebar\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 { 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.secondaryEditor ?? (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.secondaryEditor ?? (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.secondaryEditor) {\n const secondaryBin = plan.secondaryEditor.split(\" \")[0]!;\n await ensureCommand(secondaryBin);\n }\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 secondaryEditor: string;\n}\n\nconst DEFAULT_OPTIONS: LayoutOptions = {\n editor: \"claude\",\n editorPanes: 3,\n editorSize: 75,\n sidebarCommand: \"lazygit\",\n server: \"true\",\n secondaryEditor: \"\",\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 secondaryEditor: 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\" | \"mtop\";\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 mtop: { editorPanes: 2, server: \"true\", secondaryEditor: \"mtop\" },\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 secondaryEditor: opts.secondaryEditor || null,\n };\n}\n"],"mappings":"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","names":["existsSync","join","resolve","join","existsSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termplex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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
|
}
|