termplex 0.1.7 → 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/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 readKV(file) {
20
- ensureConfig();
19
+ function readKVFile(path) {
21
20
  const map = /* @__PURE__ */ new Map();
22
- if (!existsSync(file)) return map;
23
- const content = readFileSync(file, "utf-8").trim();
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);
@@ -152,32 +144,6 @@ function isCommandInstalled(cmd) {
152
144
  return false;
153
145
  }
154
146
  }
155
- function getInstallCommand() {
156
- if (process.platform === "darwin") {
157
- try {
158
- execSync("command -v brew", { stdio: "ignore" });
159
- return "brew install tmux";
160
- } catch {
161
- return null;
162
- }
163
- }
164
- if (process.platform === "linux") {
165
- const managers = [
166
- ["apt-get", "sudo apt-get install -y tmux"],
167
- ["dnf", "sudo dnf install -y tmux"],
168
- ["yum", "sudo yum install -y tmux"],
169
- ["pacman", "sudo pacman -S --noconfirm tmux"]
170
- ];
171
- for (const [bin, cmd] of managers) {
172
- try {
173
- execSync(`command -v ${bin}`, { stdio: "ignore" });
174
- return cmd;
175
- } catch {
176
- }
177
- }
178
- }
179
- return null;
180
- }
181
147
  function prompt(question) {
182
148
  const rl = createInterface({ input: process.stdin, output: process.stdout });
183
149
  return new Promise((resolve2) => {
@@ -190,7 +156,7 @@ function prompt(question) {
190
156
  var KNOWN_INSTALL_COMMANDS = {
191
157
  claude: () => "npm install -g @anthropic-ai/claude-code",
192
158
  lazygit: () => {
193
- if (process.platform === "darwin") {
159
+ if (process.platform === "darwin" || process.platform === "linux") {
194
160
  try {
195
161
  execSync("command -v brew", { stdio: "ignore" });
196
162
  return "brew install lazygit";
@@ -198,14 +164,32 @@ var KNOWN_INSTALL_COMMANDS = {
198
164
  return null;
199
165
  }
200
166
  }
201
- if (process.platform === "linux") {
167
+ return null;
168
+ },
169
+ tmux: () => {
170
+ if (process.platform === "darwin") {
202
171
  try {
203
172
  execSync("command -v brew", { stdio: "ignore" });
204
- return "brew install lazygit";
173
+ return "brew install tmux";
205
174
  } catch {
206
175
  return null;
207
176
  }
208
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
+ }
209
193
  return null;
210
194
  }
211
195
  };
@@ -218,7 +202,7 @@ async function ensureCommand(cmd) {
218
202
  `\`${cmd}\` is required but not installed, and no known install method was found.`
219
203
  );
220
204
  console.error(
221
- `Please install \`${cmd}\` manually or change your config with: termplex config set editor <command>`
205
+ `Please install \`${cmd}\` manually or change your config with: termplex set editor <command>`
222
206
  );
223
207
  process.exit(1);
224
208
  }
@@ -244,37 +228,6 @@ async function ensureCommand(cmd) {
244
228
  console.log(`\`${cmd}\` installed successfully!
245
229
  `);
246
230
  }
247
- async function ensureTmux() {
248
- if (isCommandInstalled("tmux")) return;
249
- const installCmd = getInstallCommand();
250
- if (!installCmd) {
251
- console.error(
252
- "tmux is required but not installed, and no supported package manager was found."
253
- );
254
- console.error("Please install tmux manually and try again.");
255
- process.exit(1);
256
- }
257
- console.log("tmux is required but not installed on this machine.");
258
- const answer = await prompt(`Install it now with \`${installCmd}\`? [Y/n] `);
259
- if (answer && answer !== "y" && answer !== "yes") {
260
- console.log("tmux is required for termplex to work. Exiting.");
261
- process.exit(1);
262
- }
263
- console.log(`Running: ${installCmd}`);
264
- try {
265
- execSync(installCmd, { stdio: "inherit" });
266
- } catch {
267
- console.error(
268
- "Failed to install tmux. Please install it manually and try again."
269
- );
270
- process.exit(1);
271
- }
272
- if (!isCommandInstalled("tmux")) {
273
- console.error("tmux still not found after install. Please check your PATH.");
274
- process.exit(1);
275
- }
276
- console.log("tmux installed successfully!\n");
277
- }
278
231
  function resolveConfig(targetDir, cliOverrides) {
279
232
  const project = readKVFile(join2(targetDir, ".termplex"));
280
233
  const layoutKey = cliOverrides.layout ?? project.get("layout") ?? getConfig("layout");
@@ -283,7 +236,7 @@ function resolveConfig(targetDir, cliOverrides) {
283
236
  if (isPresetName(layoutKey)) {
284
237
  base = getPreset(layoutKey);
285
238
  } else {
286
- console.warn(`Unknown layout preset: "${layoutKey}", using defaults.`);
239
+ console.warn(`Unknown layout preset: "${layoutKey}". Valid presets: minimal, full, pair, cli, mtop. Using defaults.`);
287
240
  }
288
241
  }
289
242
  const pick = (cli, projKey) => cli ?? project.get(projKey) ?? getConfig(projKey);
@@ -296,8 +249,24 @@ function resolveConfig(targetDir, cliOverrides) {
296
249
  const result = { ...base };
297
250
  if (editor !== void 0) result.editor = editor;
298
251
  if (sidebar !== void 0) result.sidebarCommand = sidebar;
299
- if (panes !== void 0) result.editorPanes = parseInt(panes, 10);
300
- if (editorSize !== void 0) result.editorSize = parseInt(editorSize, 10);
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
+ }
301
270
  if (server !== void 0) result.server = server;
302
271
  return { opts: result, mouse };
303
272
  }
@@ -347,7 +316,7 @@ async function launch(targetDir, cliOverrides) {
347
316
  console.error(`Directory not found: ${targetDir}`);
348
317
  process.exit(1);
349
318
  }
350
- await ensureTmux();
319
+ await ensureCommand("tmux");
351
320
  const { opts, mouse } = resolveConfig(targetDir, cliOverrides ?? {});
352
321
  const plan = planLayout(opts);
353
322
  if (plan.editor) await ensureCommand(plan.editor);
@@ -383,9 +352,150 @@ async function launch(targetDir, cliOverrides) {
383
352
  }
384
353
  }
385
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
+
386
495
  // src/index.ts
387
496
  var HELP = `
388
497
  termplex \u2014 Launch configurable multi-pane terminal workspaces
498
+ Aliases: ws
389
499
 
390
500
  Usage:
391
501
  termplex <target> Launch workspace (project name, path, or '.')
@@ -394,6 +504,7 @@ Usage:
394
504
  termplex list List all registered projects
395
505
  termplex set <key> [value] Set a machine-level config value
396
506
  termplex config Show current machine configuration
507
+ termplex completion <shell> Output shell completion script (bash, zsh, fish)
397
508
 
398
509
  Options:
399
510
  -h, --help Show this help message
@@ -427,6 +538,11 @@ Per-project config:
427
538
  Place a .termplex file in your project root with key=value pairs.
428
539
  Project config overrides machine config; CLI flags override both.
429
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
+
430
546
  Examples:
431
547
  termplex . Launch workspace in current directory
432
548
  termplex myapp Launch workspace for registered project
@@ -440,6 +556,7 @@ function showHelp() {
440
556
  }
441
557
  var parseOpts = {
442
558
  allowPositionals: true,
559
+ allowNegative: true,
443
560
  options: {
444
561
  help: { type: "boolean", short: "h" },
445
562
  version: { type: "boolean", short: "v" },
@@ -465,7 +582,7 @@ function safeParse() {
465
582
  }
466
583
  var { values, positionals } = safeParse();
467
584
  if (values.version) {
468
- console.log("0.1.7");
585
+ console.log("0.1.8");
469
586
  process.exit(0);
470
587
  }
471
588
  if (values.help) {
@@ -495,8 +612,13 @@ switch (subcommand) {
495
612
  console.error("Usage: termplex remove <name>");
496
613
  process.exit(1);
497
614
  }
498
- removeProject(name);
499
- console.log(`Removed: ${name}`);
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
+ }
500
622
  break;
501
623
  }
502
624
  case "list": {
@@ -517,6 +639,10 @@ switch (subcommand) {
517
639
  console.error("Usage: termplex set <key> [value]");
518
640
  process.exit(1);
519
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
+ }
520
646
  setConfig(key, value ?? "");
521
647
  if (value) {
522
648
  console.log(`Set ${key} \u2192 ${value}`);
@@ -533,6 +659,30 @@ switch (subcommand) {
533
659
  }
534
660
  break;
535
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
+ }
536
686
  default: {
537
687
  const target = subcommand;
538
688
  let targetDir;
@@ -564,4 +714,3 @@ switch (subcommand) {
564
714
  await launch(targetDir, overrides);
565
715
  }
566
716
  }
567
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termplex",
3
- "version": "0.1.7",
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",
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, 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"]}