tabby-control-tmux 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nir Israel Hen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # ControlTmux for Tabby
2
+
3
+ Control tmux on remote hosts from a right-click menu in Tabby SSH tabs.
4
+
5
+ Right-click an SSH tab (title or terminal body) to get a **ControlTmux** section:
6
+
7
+ - **New session** – default name, or *Named…* (types `tmux new-session -s ` for you to complete)
8
+ - **Attach to…** – pick from the sessions running on the host
9
+ - **Kill session** – pick a session to kill
10
+ - **Detach** – detach the current client
11
+ - **Close session** – kill the session you are in
12
+
13
+ Each item has a single-key shortcut shown on the right of the menu.
14
+
15
+ If tmux isn't installed on the host, the menu offers **Install tmux…**, which detects the package
16
+ manager (apt, dnf, yum, apk, pacman, zypper, brew) and runs the install in the tab, using `sudo`
17
+ when you aren't root (you type the password in the terminal).
18
+
19
+ Sessions are listed over a separate exec channel on the same SSH connection, so your terminal is never disturbed.
20
+ It works alongside [tabby-tmux](https://github.com/ruanimal/tabby-tmux) (control mode); the two serve different purposes.
21
+
22
+ ## Preview
23
+
24
+ ```
25
+ ControlTmux
26
+ ├─ New session N ▸ Default name D
27
+ │ Named… N
28
+ ├─ Attach to… A ▸ 1 main (attached)
29
+ │ 2 work
30
+ ├─ Kill session K ▸ 1 main …
31
+ ├─ ───────────
32
+ ├─ Detach D
33
+ └─ Close session C
34
+ ```
35
+
36
+ ## Install
37
+
38
+ Settings → Plugins → search for `tabby-control-tmux`, then restart Tabby.
39
+
40
+ ## Build from source
41
+
42
+ ```bash
43
+ npm install --legacy-peer-deps
44
+ npm run build
45
+ ```
46
+
47
+ Copy `package.json` and `dist/` into `<Tabby config dir>/plugins/node_modules/tabby-control-tmux/`
48
+ (macOS: `~/Library/Application Support/tabby/plugins/node_modules/`) and restart Tabby.
49
+
50
+ ## Notes
51
+
52
+ - Actions type commands into the active tab, so use them at a shell prompt.
53
+ - Requires `tmux` on the remote host (or use the install item).
54
+
55
+ ## License
56
+
57
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ !function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t(require("@angular/core"),require("tabby-core"));else if("function"==typeof define&&define.amd)define(["@angular/core","tabby-core"],t);else{var n="object"==typeof exports?t(require("@angular/core"),require("tabby-core")):t(e["@angular/core"],e["tabby-core"]);for(var s in n)("object"==typeof exports?exports:e)[s]=n[s]}}(global,(e,t)=>(()=>{"use strict";var n={860(t){t.exports=e},650(e){e.exports=t}};const s={};function o(e){const t=s[e];if(void 0!==t)return t.exports;const r=s[e]={exports:{}};return n[e](r,r.exports,o),r.exports}o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};let r={};o.r(r),o.d(r,{default:()=>f});var a=o(860),l=o(650);const i=[["apt-get","apt-get update && apt-get install -y tmux"],["dnf","dnf install -y tmux"],["yum","yum install -y tmux"],["apk","apk add tmux"],["pacman","pacman -Sy --noconfirm tmux"],["zypper","zypper --non-interactive install tmux"],["brew","brew install tmux"]];let c=class{async exec(e,t){const n=e.sshSession?.ssh;if(!n)return{out:"",ok:!1};const s=await n.activateChannel(await n.openSessionChannel()),o=[],r=new Promise(e=>{s.closed$.subscribe({complete:e,next:e}),s.eof$.subscribe({complete:e,next:e}),setTimeout(e,8e3)});return s.data$.subscribe(e=>o.push(e)),await s.requestExec(t),await r,{out:Buffer.concat(o).toString(),ok:!0}}async isInstalled(e){return"yes"===(await this.exec(e,"command -v tmux >/dev/null 2>&1 && echo yes || echo no")).out.trim()}async listSessions(e){return(await this.exec(e,"tmux ls -F '#{session_name}\t#{session_windows}\t#{session_attached}' 2>/dev/null")).out.split("\n").filter(e=>e.trim()).map(e=>{const[t,n,s]=e.split("\t");return{name:t,windows:+n,attached:+s>0}})}async buildInstallCommand(e){const t=i.map(([e])=>`command -v ${e} >/dev/null 2>&1 && echo ${e}`).join("; "),n=await this.exec(e,t),s=n.out.split("\n")[0]?.trim(),o=i.find(([e])=>e===s);if(!o)return null;const r=o[1];return"brew"===o[0]?r:`if [ "$(id -u)" -eq 0 ]; then ${r}; else sudo sh -c '${r}'; fi`}q(e){return`'${e.replace(/'/g,"'\\''")}'`}newSession(e,t){e.sendInput(`tmux new-session${t?" -s "+this.q(t):""}\r`)}attach(e,t){e.sendInput(`tmux attach-session -t ${this.q(t)}\r`)}newNamedSession(e){e.sendInput("tmux new-session -s ")}closeCurrent(e){e.sendInput("tmux kill-session\r")}detach(e){e.sendInput("tmux detach-client 2>/dev/null\r")}async kill(e,t){await this.exec(e,`tmux kill-session -t ${this.q(t)}`)}install(e,t){e.sendInput(t+"\r")}};c=function(e,t,n,s){var o,r=arguments.length,a=r<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,n,s);else for(var l=e.length-1;l>=0;l--)(o=e[l])&&(a=(r<3?o(a):r>3?o(t,n,a):o(t,n))||a);return r>3&&a&&Object.defineProperty(t,n,a),a}([(0,a.Injectable)({providedIn:"root"})],c);var u;let d=class extends l.TabContextMenuItemProvider{constructor(e){super(),this.tmux=e,this.weight=-.5}getItems(e){return async function(e,t){const n=t;if(!n.sshSession)return[];try{if(!await e.isInstalled(n)){const t=await e.buildInstallCommand(n);return[{label:"ControlTmux",submenu:[t?{label:"tmux is not installed — Install tmux…",click:()=>e.install(n,t)}:{label:"tmux is not installed (no supported package manager found)",enabled:!1}]}]}const t=await e.listSessions(n),s=e=>t.map((t,n)=>({label:t.name+(t.attached?" (attached)":""),accelerator:n<9?String(n+1):void 0,click:()=>e(t.name)})),o=(e,t,n)=>({label:e,accelerator:t,...n});return[{label:"ControlTmux",submenu:[o("New session","N",{submenu:[o("Default name","D",{click:()=>e.newSession(n)}),o("Named…","N",{click:()=>e.newNamedSession(n)})]}),o("Attach to…","A",{enabled:t.length>0,submenu:s(t=>e.attach(n,t))}),o("Kill session","K",{enabled:t.length>0,submenu:s(t=>e.kill(n,t))}),{type:"separator"},o("Detach","D",{click:()=>e.detach(n)}),o("Close session","C",{click:()=>e.closeCurrent(n)})]}]}catch{return[]}}(this.tmux,e)}};d=function(e,t,n,s){var o,r=arguments.length,a=r<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,n,s);else for(var l=e.length-1;l>=0;l--)(o=e[l])&&(a=(r<3?o(a):r>3?o(t,n,a):o(t,n))||a);return r>3&&a&&Object.defineProperty(t,n,a),a}([(0,a.Injectable)(),function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}("design:paramtypes",["function"==typeof(u=void 0!==c&&c)?u:Object])],d);let p=class{};p=function(e,t,n,s){var o,r=arguments.length,a=r<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,n):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,n,s);else for(var l=e.length-1;l>=0;l--)(o=e[l])&&(a=(r<3?o(a):r>3?o(t,n,a):o(t,n))||a);return r>3&&a&&Object.defineProperty(t,n,a),a}([(0,a.NgModule)({providers:[{provide:l.TabContextMenuItemProvider,useClass:d,multi:!0}]})],p);const f=p;return r})());
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "tabby-control-tmux",
3
+ "version": "0.1.0",
4
+ "description": "ControlTmux: right-click tmux controls for SSH tabs: new, attach, detach, close, and one-click tmux install",
5
+ "keywords": ["tabby-plugin"],
6
+ "main": "dist/index.js",
7
+ "typings": "typings/index.d.ts",
8
+ "files": ["dist", "typings"],
9
+ "scripts": {
10
+ "build": "webpack --mode production",
11
+ "watch": "webpack --mode development --watch"
12
+ },
13
+ "author": "Nir Israel Hen",
14
+ "repository": { "type": "git", "url": "git+https://github.com/controlfix87/tabby-control-tmux.git" },
15
+ "homepage": "https://github.com/controlfix87/tabby-control-tmux#readme",
16
+ "license": "MIT",
17
+ "peerDependencies": {
18
+ "tabby-core": "*",
19
+ "tabby-ssh": "*",
20
+ "tabby-terminal": "*"
21
+ },
22
+ "devDependencies": {
23
+ "@angular/core": "^15",
24
+ "rxjs": "^7",
25
+ "ts-loader": "^9",
26
+ "typescript": "~4.9",
27
+ "webpack": "^5",
28
+ "webpack-cli": "^5"
29
+ }
30
+ }
@@ -0,0 +1,2 @@
1
+ export default class ControlTmuxModule {
2
+ }