tmux-weblink 2.0.3 → 2.0.4
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/lib/agent-client.js +1 -1
- package/dist/lib/pages/shell.js +2 -2
- package/dist/lib/shared-layout.js +30 -14
- package/package.json +1 -1
package/dist/lib/agent-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{WebSocket as
|
|
1
|
+
import{WebSocket as k}from"ws";import{attachTerminal as J}from"./attach-terminal.js";import{encodeBinaryFrame as j,decodeBinaryFrame as x,BIN_KIND_HTTP_BODY as A}from"./agent-channel.js";const K=parseInt(process.env.TMUX_WEB_AGENT_HEARTBEAT_MS||"15000",10),W=parseInt(process.env.TMUX_WEB_AGENT_SESSIONS_POLL_MS||"10000",10),F=new Set(["content-type","content-disposition","location","cache-control","etag"]);function Q(o){const T=o.log??(e=>console.log(`[agent] ${e}`)),g=e=>console.error(`[agent] ${e}`);let s=null,S=1e3,B=null,m=!1;const d=new Map,f=new Map,h=new Map,b=new Set;function $(e){return b.has(e)?Promise.resolve():new Promise(t=>{h.set(e,t),setTimeout(()=>{h.delete(e)&&t()},3e4)})}let p=null,y=null,E="",w=null;const _={state:"idle",agentId:null,hub:o.hub,name:o.name};function u(e,t){t!==void 0&&(_.lastError=t),_.state=e,_.agentId=w,o.onStatus?.({..._})}function c(e){s&&s.readyState===k.OPEN&&s.send(JSON.stringify(e))}function C(){let e;try{e=o.listSessions()}catch{return}const t=JSON.stringify(e);t!==E&&(E=t,c({type:"sessions",sessions:e}))}function v(){p||(p=setInterval(C,W)),y||(y=setInterval(()=>c({type:"ping",ts:Date.now()}),K))}function H(){p&&(clearInterval(p),p=null),y&&(clearInterval(y),y=null)}async function O(e){let t;if(e.hasBody){await $(e.id),b.delete(e.id);const a=f.get(e.id);f.delete(e.id),t=a&&a.length?Buffer.concat(a):Buffer.alloc(0)}const n=new URL(e.path,"http://agent.invalid");let i;try{const a={method:e.method,headers:e.headers};t&&t.length&&(a.body=t),i=await Promise.race([o.app.request(new Request(n,a)),new Promise((G,D)=>setTimeout(()=>D(new Error("app.request timed out")),3e4))])}catch(a){g(`tunnel request failed: ${e.method} ${e.path} ${a.message}`),c({type:"http_resp",id:e.id,status:500,headers:{"content-type":"application/json"},hasBody:!1});return}const I={};i.headers.forEach((a,l)=>{F.has(l.toLowerCase())&&(I[l]=a)});const r=Buffer.from(await i.arrayBuffer());if(c({type:"http_resp",id:e.id,status:i.status,headers:I,hasBody:r.length>0}),r.length>0){for(let l=0;l<r.length;l+=65536){if(!s||s.readyState!==k.OPEN)return;s.send(j(A,e.id,r.subarray(l,l+65536)))}c({type:"http_body_end",id:e.id})}}function L(e){const t=e.session;let n=!1;try{n=o.listSessions().some(r=>r.name===t)}catch{n=!1}if(!n){c({type:"attach_err",connId:e.connId,session:t,message:"no such session"});return}let i=null;const I=J(t,{send:r=>c({type:"ws_to_hub",connId:e.connId,msg:r}),onMessage:r=>{i=r},onClose:()=>{}},{terminalBufferConfig:o.terminalBufferConfig,getSessionPaneTarget:o.getSessionPaneTarget,capturePaneTail:o.capturePaneTail,capturePaneHistoryChunk:o.capturePaneHistoryChunk,toCrlf:o.toCrlf,handleClientMessage:o.handleClientMessage,acquireControlClient:o.acquireControlClient,onPtyExit:(r,a)=>{c({type:"ws_close",connId:e.connId,code:1e3,reason:`tmux exited (${a})`})},onSpawnError:(r,a)=>{c({type:"attach_err",connId:e.connId,session:t,message:a})},onAttached:r=>{c({type:"attach_ok",connId:e.connId,session:t})}});d.set(e.connId,{onMessage:r=>i?.(r),dispose:()=>{d.delete(e.connId),I.dispose()}})}function R(e){let t;try{t=JSON.parse(e)}catch{return}switch(t.type){case"hello_ok":w=t.agentId,S=1e3,T(`connected to hub as "${w}"`),u("connected"),C(),v();break;case"hello_err":if(g(`hub rejected us: ${t.reason}`),u("error",t.reason),s)try{s.close(4003,"hello rejected")}catch{}break;case"sessions_req":C();break;case"http_req":O(t);break;case"http_body_end":{b.add(t.id);const n=h.get(t.id);n&&(h.delete(t.id),n());break}case"attach":L(t);break;case"ws_to_agent":{const n=d.get(t.connId);if(n)try{n.onMessage(JSON.stringify(t.msg))}catch{}break}case"detach":{const n=d.get(t.connId);n&&n.dispose();break}case"pong":break;default:break}}function q(e){const t=x(e);if(t&&t.kind===A){const n=f.get(t.id)??[];n.push(t.payload),f.set(t.id,n)}}function M(){H();for(const e of d.values())try{e.dispose()}catch{}d.clear(),f.clear(),h.clear(),b.clear(),E=""}function P(){if(s){try{s.removeAllListeners()}catch{}s=null}if(M(),m)return;u("connecting");const e=S+Math.random()*500;T(`reconnecting in ${Math.round(e)}ms\u2026`),B=setTimeout(N,e),S=Math.min(S*2,6e4)}function N(){if(m||s)return;const e=o.hub.replace(/\/+$/,"").replace(/^http:/,"ws:").replace(/^https:/,"wss:")+"/agent/ws";let t;try{t=new k(e)}catch(n){g(`bad hub URL: ${n.message} (${e})`),u("error",`bad hub URL: ${n.message}`);return}s=t,u("connecting"),t.on("open",()=>{T(`connecting to ${e}\u2026`),c({type:"hello",token:o.token,name:o.name,version:o.version})}),t.on("message",(n,i)=>{i?q(Array.isArray(n)?Buffer.concat(n):Buffer.isBuffer(n)?n:Buffer.from(n)):R(typeof n=="string"?n:Buffer.from(n).toString("utf-8"))}),t.on("close",()=>{s===t&&P()}),t.on("error",n=>{g(`connection error: ${n.message}`),s===t&&P()})}function U(){if(!m){if(m=!0,B&&clearTimeout(B),M(),s){try{s.removeAllListeners(),s.close(1e3,"shutdown")}catch{}s=null}w=null,u("idle")}}return N(),{stop:U,status:()=>({..._})}}export{Q as startAgentClient};
|
package/dist/lib/pages/shell.js
CHANGED
|
@@ -770,9 +770,9 @@ window.__TMUX_WEB_SHELL__ = ${f};
|
|
|
770
770
|
await import('/assets/shell-client.js');
|
|
771
771
|
${t?m(a,[]):""}
|
|
772
772
|
// Wrapper: refresh sidebar list, then open session
|
|
773
|
-
window.__onSessionCreated = async function(name) {
|
|
773
|
+
window.__onSessionCreated = async function(name, agentId) {
|
|
774
774
|
await window.__refreshSidebar();
|
|
775
|
-
window.__openSession(name);
|
|
775
|
+
window.__openSession(name, agentId || undefined);
|
|
776
776
|
};
|
|
777
777
|
${h("__onSessionCreated")}
|
|
778
778
|
</script>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}from"./html.js";import{icon as a,iconPath as
|
|
1
|
+
import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}from"./html.js";import{icon as a,iconPath as n}from"./icons.js";const d=[{id:"vscode",name:"VS Code",dot:"#007acc"},{id:"ghostty",name:"Ghostty",dot:"#ff5f00"},{id:"warm-clay",name:"Warm Clay",dot:"#b86b52"},{id:"dark-cove",name:"Dark Cove",dot:"#7aa2f7"}];function r(t="var(--panel-accent)"){return`box-shadow: 0 0 0 2px ${t}; outline: none;`}function y(t=""){return`
|
|
2
2
|
@media (prefers-reduced-motion: reduce) {
|
|
3
3
|
*, *::before, *::after {
|
|
4
4
|
animation-duration: 0.01ms !important;
|
|
@@ -7,15 +7,15 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
7
7
|
scroll-behavior: auto !important;
|
|
8
8
|
}
|
|
9
9
|
${t}
|
|
10
|
-
}`}function k(t="vscode"){const
|
|
10
|
+
}`}function k(t="vscode"){const o=d.find(e=>e.id===t)??d[0],i=d.map(e=>{const s=e.id===t;return`<button type="button" class="theme-option${s?" active":""}" data-theme="${e.id}" role="menuitem">
|
|
11
11
|
<span class="theme-dot" style="background:${e.dot}"></span>
|
|
12
12
|
${c(e.name)}
|
|
13
13
|
${s?a("check",'class="theme-check" aria-hidden="true"'):""}
|
|
14
14
|
</button>`}).join(`
|
|
15
15
|
`);return`<div class="theme-switcher" id="theme-switcher">
|
|
16
16
|
<button type="button" class="theme-switcher-btn" aria-label="Theme" aria-haspopup="true" aria-expanded="false">
|
|
17
|
-
<span class="theme-dot" style="background:${
|
|
18
|
-
<span>${c(
|
|
17
|
+
<span class="theme-dot" style="background:${o.dot}"></span>
|
|
18
|
+
<span>${c(o.name)}</span>
|
|
19
19
|
</button>
|
|
20
20
|
<div class="theme-switcher-popover" role="menu">
|
|
21
21
|
${i}
|
|
@@ -217,7 +217,7 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
${y()}
|
|
220
|
-
${t}`}function
|
|
220
|
+
${t}`}function A(t){const{commandbarEnabled:o,title:i="TMUX Sessions",themeTemplate:e="vscode"}=t;return`<script>
|
|
221
221
|
(function() {
|
|
222
222
|
const token = localStorage.getItem('tmux-web-token');
|
|
223
223
|
if (token) {
|
|
@@ -236,7 +236,7 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
236
236
|
<header class="fixed-header">
|
|
237
237
|
<div class="brand"><a href="/" aria-label="Go to home">tmux<span>-weblink</span></a></div>
|
|
238
238
|
<div class="header-actions">
|
|
239
|
-
${
|
|
239
|
+
${o?w("Search"):""}
|
|
240
240
|
<button class="header-btn" id="notes-toggle" title="Global notes" aria-label="Global notes">
|
|
241
241
|
${a("notes")}
|
|
242
242
|
<span>Notes</span>
|
|
@@ -248,10 +248,10 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
248
248
|
</a>
|
|
249
249
|
</div>
|
|
250
250
|
</header>
|
|
251
|
-
<script>${I()}</script>`}function
|
|
251
|
+
<script>${I()}</script>`}function T(t){const{activePage:o,refreshHref:i}=t;function e(l,x,f,g,v=""){return`<a href="${x}" class="${l!==null&&l===o?"sidebar-btn current":"sidebar-btn"}"${v}>
|
|
252
252
|
<svg viewBox="0 0 24 24" fill="currentColor">${f}</svg>
|
|
253
253
|
${g}
|
|
254
|
-
</a>`}const s=
|
|
254
|
+
</a>`}const s=n("sessions"),p=n("notes"),m=n("schedule"),b=n("history"),u=n("quick-commands"),h=n("file"),E=n("refresh");return`<aside class="action-sidebar">
|
|
255
255
|
<p class="sidebar-label">Actions</p>
|
|
256
256
|
<button class="sidebar-btn primary" id="new-session-btn">
|
|
257
257
|
${a("add")}
|
|
@@ -269,7 +269,7 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
269
269
|
${a("refresh")}
|
|
270
270
|
Refresh
|
|
271
271
|
</a>
|
|
272
|
-
</aside>`}function
|
|
272
|
+
</aside>`}function M(){return` /* \u2500\u2500 New session modal \u2500\u2500 */
|
|
273
273
|
.modal-backdrop {
|
|
274
274
|
display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.3);
|
|
275
275
|
z-index: 500; align-items: center; justify-content: center;
|
|
@@ -349,6 +349,20 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
349
349
|
const errorEl = document.getElementById('ns-error');
|
|
350
350
|
const onCreated = '${t??""}';
|
|
351
351
|
|
|
352
|
+
// The new session is created on the machine of the currently selected
|
|
353
|
+
// session (window.__tmuxWebScopeAgent is set by the shell client; agent
|
|
354
|
+
// page spaces also set it via the injected scope). Falls back to this
|
|
355
|
+
// machine when nothing is selected.
|
|
356
|
+
function selectedAgentId() {
|
|
357
|
+
var a = window.__tmuxWebScopeAgent;
|
|
358
|
+
return (typeof a === 'string' && a) ? a : '';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function selectedBase() {
|
|
362
|
+
var id = selectedAgentId();
|
|
363
|
+
return id ? '/a/' + encodeURIComponent(id) : '';
|
|
364
|
+
}
|
|
365
|
+
|
|
352
366
|
function openModal() {
|
|
353
367
|
modal.classList.add('open');
|
|
354
368
|
nameInput.value = '';
|
|
@@ -412,7 +426,7 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
412
426
|
if (!val) { closeDropdown(); return; }
|
|
413
427
|
debounceTimer = setTimeout(async () => {
|
|
414
428
|
try {
|
|
415
|
-
const res = await fetch('/api/fs/list?path=' + encodeURIComponent(val));
|
|
429
|
+
const res = await fetch(selectedBase() + '/api/fs/list?path=' + encodeURIComponent(val));
|
|
416
430
|
const data = await res.json();
|
|
417
431
|
renderDropdown(data.dirs || []);
|
|
418
432
|
} catch { closeDropdown(); }
|
|
@@ -429,7 +443,8 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
429
443
|
submitBtn.textContent = 'Creating\u2026';
|
|
430
444
|
errorEl.style.display = 'none';
|
|
431
445
|
try {
|
|
432
|
-
const
|
|
446
|
+
const base = selectedBase();
|
|
447
|
+
const res = await fetch(base + '/api/sessions/new', {
|
|
433
448
|
method: 'POST',
|
|
434
449
|
headers: { 'Content-Type': 'application/json' },
|
|
435
450
|
body: JSON.stringify({ name, dir: dir || undefined }),
|
|
@@ -437,8 +452,9 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
437
452
|
const data = await res.json();
|
|
438
453
|
if (!res.ok) { showError(data.error || 'Failed to create session.'); return; }
|
|
439
454
|
closeModal();
|
|
440
|
-
|
|
441
|
-
window
|
|
455
|
+
const agentId = selectedAgentId() || null;
|
|
456
|
+
if (onCreated && typeof window[onCreated] === 'function') { window[onCreated](name, agentId); return; }
|
|
457
|
+
window.location.href = base + '/s/' + encodeURIComponent(name);
|
|
442
458
|
} catch { showError('Network error. Please try again.'); }
|
|
443
459
|
finally { submitBtn.disabled = false; submitBtn.textContent = 'Create'; }
|
|
444
460
|
}
|
|
@@ -463,4 +479,4 @@ import{commandbarButtonHTML as w}from"./commandbar.js";import{escapeHtml as c}fr
|
|
|
463
479
|
}
|
|
464
480
|
if (e.key === 'Enter') submit();
|
|
465
481
|
});
|
|
466
|
-
})();`}export{
|
|
482
|
+
})();`}export{M as newSessionModalCSS,D as newSessionModalHTML,j as newSessionModalScript,y as reducedMotion,A as sharedHeader,B as sharedLayoutCSS,T as sharedSidebar,k as themeSwitcherButtonHTML,I as themeSwitcherScript};
|