tmux-weblink 0.3.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/README.md +70 -0
- package/dist/assets/addon-fit-D5N335P2.js +16 -0
- package/dist/assets/terminal-client.js +4 -0
- package/dist/assets/xterm-NI46XXQY.js +51 -0
- package/dist/assets/xterm.css +285 -0
- package/dist/browser/terminal-client.js +4 -0
- package/dist/frontend.js +1 -0
- package/dist/index.js +12 -0
- package/dist/lib/agent-detect.js +3 -0
- package/dist/lib/agents-watch.js +2 -0
- package/dist/lib/atomicWrite.js +1 -0
- package/dist/lib/auditLog.js +1 -0
- package/dist/lib/auth.js +2 -0
- package/dist/lib/cli.js +32 -0
- package/dist/lib/commandbar.js +555 -0
- package/dist/lib/db.js +1 -0
- package/dist/lib/drawer-resize.js +166 -0
- package/dist/lib/drawer-script.js +8 -0
- package/dist/lib/ext-loader.js +1 -0
- package/dist/lib/html.js +1 -0
- package/dist/lib/icons.js +1 -0
- package/dist/lib/image-upload.js +1 -0
- package/dist/lib/load-env.js +6 -0
- package/dist/lib/mobile-toolbar.js +313 -0
- package/dist/lib/notes-db.js +18 -0
- package/dist/lib/notes-drawer.js +210 -0
- package/dist/lib/notes-utils.js +15 -0
- package/dist/lib/pages/agents-index.js +105 -0
- package/dist/lib/pages/history-index.js +85 -0
- package/dist/lib/pages/landing.js +78 -0
- package/dist/lib/pages/login.js +191 -0
- package/dist/lib/pages/notes-index.js +65 -0
- package/dist/lib/pages/notes-page.js +158 -0
- package/dist/lib/pages/quick-commands.js +361 -0
- package/dist/lib/pages/schedule-index.js +392 -0
- package/dist/lib/pages/settings.js +206 -0
- package/dist/lib/pages/terminal.js +364 -0
- package/dist/lib/pinned-views.js +1 -0
- package/dist/lib/plugins.js +3 -0
- package/dist/lib/quick-commands.js +1 -0
- package/dist/lib/rateLimiter.js +2 -0
- package/dist/lib/schedule-delay.js +9 -0
- package/dist/lib/scheduler-drawer.js +379 -0
- package/dist/lib/scheduler.js +1 -0
- package/dist/lib/security-config.js +2 -0
- package/dist/lib/session-access.js +1 -0
- package/dist/lib/session-windows.js +1 -0
- package/dist/lib/sessions-drawer.js +634 -0
- package/dist/lib/sessions-sidebar.js +1 -0
- package/dist/lib/settings.js +2 -0
- package/dist/lib/setup-features.js +1 -0
- package/dist/lib/setup-prompts.js +3 -0
- package/dist/lib/shared-layout.js +412 -0
- package/dist/lib/state-paths.js +1 -0
- package/dist/lib/terminal-config.js +1 -0
- package/dist/lib/theme-store.js +2 -0
- package/dist/lib/theme.js +28 -0
- package/dist/lib/themes/dark-cove.js +1 -0
- package/dist/lib/themes/ghostty.js +1 -0
- package/dist/lib/themes/index.js +1 -0
- package/dist/lib/themes/types.js +0 -0
- package/dist/lib/themes/vscode.js +1 -0
- package/dist/lib/themes/warm-clay.js +1 -0
- package/dist/lib/tmux-capture.js +5 -0
- package/dist/lib/tmux-control.js +2 -0
- package/dist/lib/tmux-panes.js +2 -0
- package/dist/lib/tmux-windows.js +4 -0
- package/dist/lib/watched-panes.js +1 -0
- package/dist/lib/window-history.js +1 -0
- package/dist/lib/window-labels.js +1 -0
- package/dist/lib/windows-drawer.js +364 -0
- package/dist/lib/ws-message.js +1 -0
- package/dist/sessions.js +2 -0
- package/package.json +62 -0
- package/scripts/fix-pty-perms.mjs +16 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import{cssVarsStyle as w}from"../theme.js";import{escapeHtml as i,escapeAttr as c}from"../html.js";import{notesDrawerCSS as v,notesDrawerHTML as y,notesDrawerScript as $}from"../notes-drawer.js";import{schedulerDrawerCSS as k,schedulerDrawerHTML as S,schedulerDrawerScript as L}from"../scheduler-drawer.js";import{windowsDrawerCSS as D,windowsDrawerHTML as E,windowsDrawerScript as F}from"../windows-drawer.js";import{sessionsDrawerCSS as M,sessionsDrawerHTML as I,sessionsDrawerScript as T}from"../sessions-drawer.js";import{mobileToolbarCSS as C,mobileToolbarHTML as B,mobileToolbarScript as O}from"../mobile-toolbar.js";import{icon as m,extIcon as W}from"../icons.js";import{commandbarButtonHTML as z,commandbarCSS as H,commandbarHTML as P,commandbarScript as R}from"../commandbar.js";import{drawerResizeCSS as A,drawerResizeHandleHTML as h,drawerResizeScript as x}from"../drawer-resize.js";import{themeSwitcherButtonHTML as j,themeSwitcherScript as J}from"../shared-layout.js";function _(){return`
|
|
2
|
+
.ext-backdrop {
|
|
3
|
+
position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 999;
|
|
4
|
+
opacity: 0; pointer-events: none; transition: opacity 0.2s ease;
|
|
5
|
+
}
|
|
6
|
+
.ext-backdrop.open { opacity: 1; pointer-events: auto; }
|
|
7
|
+
.ext-drawer {
|
|
8
|
+
position: fixed; right: 0; top: 0; height: 100%; width: 360px; z-index: 1000;
|
|
9
|
+
background: var(--panel-bg); border-left: 1px solid var(--panel-border);
|
|
10
|
+
display: flex; flex-direction: column;
|
|
11
|
+
transform: translateX(100%); transition: transform 0.25s ease;
|
|
12
|
+
}
|
|
13
|
+
.ext-drawer.open { transform: translateX(0); }
|
|
14
|
+
.ext-panel {
|
|
15
|
+
position: fixed; right: 0; top: 0; height: 100%; width: min(960px, calc(100vw - 48px)); z-index: 1001;
|
|
16
|
+
background: var(--panel-bg); border-left: 1px solid var(--panel-border);
|
|
17
|
+
display: flex; flex-direction: column;
|
|
18
|
+
transform: translateX(100%); transition: transform 0.25s ease;
|
|
19
|
+
}
|
|
20
|
+
.ext-panel.open { transform: translateX(0); }
|
|
21
|
+
${A()}
|
|
22
|
+
.ext-drawer .drawer-header,
|
|
23
|
+
.ext-panel .drawer-header {
|
|
24
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
25
|
+
padding: 10px 16px; border-bottom: 1px solid var(--panel-border);
|
|
26
|
+
font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
|
|
27
|
+
color: var(--panel-accent); font-family: var(--font-mono); flex-shrink: 0;
|
|
28
|
+
}
|
|
29
|
+
.ext-drawer .drawer-header button,
|
|
30
|
+
.ext-panel .drawer-header button {
|
|
31
|
+
background: none; border: none; color: var(--panel-muted); cursor: pointer;
|
|
32
|
+
font-size: var(--text-lg); line-height: 1; padding: 2px 6px; border-radius: 4px; transition: color 0.15s;
|
|
33
|
+
}
|
|
34
|
+
.ext-drawer .drawer-header button:hover,
|
|
35
|
+
.ext-panel .drawer-header button:hover { color: var(--panel-accent); }
|
|
36
|
+
.ext-drawer iframe { flex: 1; border: none; width: 100%; height: 0; }
|
|
37
|
+
.ext-panel iframe { flex: 1; border: none; width: 100%; height: 100%; }
|
|
38
|
+
`}function U(e){const n=e.id;return`
|
|
39
|
+
<div id="ext-${n}-backdrop" class="ext-backdrop"></div>
|
|
40
|
+
<div id="ext-${n}-drawer" class="ext-drawer resizable-drawer">
|
|
41
|
+
${h()}
|
|
42
|
+
<div class="drawer-header">
|
|
43
|
+
<span>${i(e.icon)} ${i(e.name)}</span>
|
|
44
|
+
<button id="ext-${n}-close">×</button>
|
|
45
|
+
</div>
|
|
46
|
+
<iframe id="ext-${n}-frame" src="/ext/${n}/ui/index.html"></iframe>
|
|
47
|
+
</div>`}function X(e){const n=e.id,t=e.panel;if(!t)return"";const r=t.title??e.name;return`
|
|
48
|
+
<div id="ext-${n}-panel-backdrop" class="ext-backdrop"></div>
|
|
49
|
+
<div id="ext-${n}-panel" class="ext-panel resizable-drawer">
|
|
50
|
+
${h()}
|
|
51
|
+
<div class="drawer-header">
|
|
52
|
+
<span>${i(e.icon)} ${i(r)}</span>
|
|
53
|
+
<button id="ext-${n}-panel-close">×</button>
|
|
54
|
+
</div>
|
|
55
|
+
<iframe id="ext-${n}-panel-frame" src="/ext/${n}/ui/${c(t.entry)}"></iframe>
|
|
56
|
+
</div>`}function V(e,n){const t=e.id,r=JSON.stringify(e.config),o=e.panel?.entry??null,d=e.panel?.defaultWidth??960;return`
|
|
57
|
+
${x(`ext-${t}-drawer`,`tmux-web:drawer-width:ext:${t}`,360)}
|
|
58
|
+
${o?x(`ext-${t}-panel`,`tmux-web:panel-width:ext:${t}`,d):""}
|
|
59
|
+
(function() {
|
|
60
|
+
const backdrop = document.getElementById('ext-${t}-backdrop');
|
|
61
|
+
const drawer = document.getElementById('ext-${t}-drawer');
|
|
62
|
+
const frame = document.getElementById('ext-${t}-frame');
|
|
63
|
+
const toggle = document.getElementById('ext-${t}-toggle');
|
|
64
|
+
const close = document.getElementById('ext-${t}-close');
|
|
65
|
+
const panelBackdrop = document.getElementById('ext-${t}-panel-backdrop');
|
|
66
|
+
const panel = document.getElementById('ext-${t}-panel');
|
|
67
|
+
const panelFrame = document.getElementById('ext-${t}-panel-frame');
|
|
68
|
+
const panelClose = document.getElementById('ext-${t}-panel-close');
|
|
69
|
+
|
|
70
|
+
function notifyFrame(targetFrame, type) {
|
|
71
|
+
if (targetFrame && targetFrame.contentWindow) {
|
|
72
|
+
targetFrame.contentWindow.postMessage({ type }, '*');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function openDrawer() {
|
|
77
|
+
drawer.classList.add('open');
|
|
78
|
+
backdrop.classList.add('open');
|
|
79
|
+
sendMessages(frame);
|
|
80
|
+
notifyFrame(frame, 'ext:open');
|
|
81
|
+
}
|
|
82
|
+
function closeDrawer() {
|
|
83
|
+
drawer.classList.remove('open');
|
|
84
|
+
backdrop.classList.remove('open');
|
|
85
|
+
notifyFrame(frame, 'ext:close');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function openPanel() {
|
|
89
|
+
if (!panel || !panelBackdrop || !panelFrame) return;
|
|
90
|
+
panel.classList.add('open');
|
|
91
|
+
panelBackdrop.classList.add('open');
|
|
92
|
+
closeDrawer();
|
|
93
|
+
sendMessages(panelFrame);
|
|
94
|
+
notifyFrame(panelFrame, 'ext:open');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function closePanel() {
|
|
98
|
+
if (!panel || !panelBackdrop || !panelFrame) return;
|
|
99
|
+
panel.classList.remove('open');
|
|
100
|
+
panelBackdrop.classList.remove('open');
|
|
101
|
+
notifyFrame(panelFrame, 'ext:close');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
toggle.addEventListener('click', () => {
|
|
105
|
+
drawer.classList.contains('open') ? closeDrawer() : openDrawer();
|
|
106
|
+
});
|
|
107
|
+
close.addEventListener('click', closeDrawer);
|
|
108
|
+
backdrop.addEventListener('click', closeDrawer);
|
|
109
|
+
if (panelClose) panelClose.addEventListener('click', closePanel);
|
|
110
|
+
if (panelBackdrop) panelBackdrop.addEventListener('click', closePanel);
|
|
111
|
+
|
|
112
|
+
const cfg = ${r};
|
|
113
|
+
|
|
114
|
+
function sendMessages(targetFrame) {
|
|
115
|
+
if (!targetFrame || !targetFrame.contentWindow) return;
|
|
116
|
+
targetFrame.contentWindow.postMessage({ type: 'ext:context', context: { session: ${JSON.stringify(n)}, host: location.origin } }, '*');
|
|
117
|
+
targetFrame.contentWindow.postMessage({ type: 'ext:config', config: cfg }, '*');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Defensive: if iframe already loaded (shouldn't happen), send immediately
|
|
121
|
+
if (frame.contentDocument && frame.contentDocument.readyState === 'complete') {
|
|
122
|
+
sendMessages(frame);
|
|
123
|
+
} else {
|
|
124
|
+
frame.addEventListener('load', () => sendMessages(frame), { once: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (panelFrame) {
|
|
128
|
+
if (panelFrame.contentDocument && panelFrame.contentDocument.readyState === 'complete') {
|
|
129
|
+
sendMessages(panelFrame);
|
|
130
|
+
} else {
|
|
131
|
+
panelFrame.addEventListener('load', () => sendMessages(panelFrame), { once: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
window.addEventListener('message', (e) => {
|
|
136
|
+
if (e.data?.type === 'ext:resize' && e.source === frame.contentWindow) {
|
|
137
|
+
frame.style.height = e.data.height + 'px';
|
|
138
|
+
}
|
|
139
|
+
if (panelFrame && e.data?.type === 'ext:panel-open' && e.source === frame.contentWindow) {
|
|
140
|
+
openPanel();
|
|
141
|
+
}
|
|
142
|
+
if (panelFrame && e.data?.type === 'ext:panel-close' && e.source === panelFrame.contentWindow) {
|
|
143
|
+
closePanel();
|
|
144
|
+
}
|
|
145
|
+
// ext:ready fires when the bridge initialises; send (or re-send) config
|
|
146
|
+
if (e.data?.type === 'ext:ready' && e.source === frame.contentWindow) {
|
|
147
|
+
sendMessages(frame);
|
|
148
|
+
}
|
|
149
|
+
if (panelFrame && e.data?.type === 'ext:ready' && e.source === panelFrame.contentWindow) {
|
|
150
|
+
sendMessages(panelFrame);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}());`}function re(e,n=[],t){const r=n.filter(a=>a.slot==="sidebar"),{commandbarEnabled:o=!1,commandbarSessions:d=[],quickCommands:g=[],agentsEnabled:b=!1,theme:s,renderer:f="xterm"}=t,l=t.terminal??{initialLines:1e3,historyChunk:500,syncIdleMs:200,syncMaxMs:3e3},u=l.initialLines+2*l.historyChunk,p=[{label:"Switch window",meta:`Windows in ${e}`,subView:"windows"},{label:"Quick Commands",meta:"Paste configured command",subView:"quickCommands"},{label:"Send Command",meta:"Send input to active window",clickTargetId:"type-toggle"},{label:"Open sessions sidebar",meta:"Recent and pinned sessions",clickTargetId:"sessions-toggle"},{label:"Open notes",meta:`Notes for ${e}`,clickTargetId:"notes-toggle"},{label:"Open scheduler",meta:`Schedule command in ${e}`,clickTargetId:"sched-toggle"}];return b&&p.push({label:"View All Agents",meta:"Running agents",href:"/agents"}),`<!DOCTYPE html>
|
|
154
|
+
<html lang="en">
|
|
155
|
+
<head>
|
|
156
|
+
<meta charset="UTF-8" />
|
|
157
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
158
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
159
|
+
<title>tmux: ${i(e)}</title>
|
|
160
|
+
<style>
|
|
161
|
+
${w(s.shell)}
|
|
162
|
+
html, body { background: var(--page-bg); color: var(--page-fg); height: 100%; width: 100%; overflow: hidden; }
|
|
163
|
+
body { display: flex; flex-direction: column; }
|
|
164
|
+
header {
|
|
165
|
+
padding: 8px 12px;
|
|
166
|
+
background: var(--header-gradient);
|
|
167
|
+
border-bottom: 1px solid var(--panel-border);
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 8px;
|
|
171
|
+
flex-shrink: 0;
|
|
172
|
+
min-height: 56px;
|
|
173
|
+
}
|
|
174
|
+
header .brand {
|
|
175
|
+
font-size: var(--text-base); font-weight: 600; letter-spacing: -0.01em; color: var(--page-fg);
|
|
176
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
177
|
+
}
|
|
178
|
+
header .brand span { color: var(--panel-accent); font-weight: 500; }
|
|
179
|
+
header .brand a { color: inherit; text-decoration: none; }
|
|
180
|
+
header .session {
|
|
181
|
+
font-size: var(--text-sm);
|
|
182
|
+
color: var(--panel-muted);
|
|
183
|
+
background: var(--page-bg);
|
|
184
|
+
border: 1px solid var(--panel-border);
|
|
185
|
+
padding: 7px 14px;
|
|
186
|
+
border-radius: 10px;
|
|
187
|
+
max-width: 200px;
|
|
188
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
189
|
+
}
|
|
190
|
+
header .status {
|
|
191
|
+
margin-left: auto;
|
|
192
|
+
font-size: var(--text-xs);
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: 8px;
|
|
196
|
+
color: var(--panel-muted);
|
|
197
|
+
}
|
|
198
|
+
header .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--panel-muted); transition: background 0.2s; }
|
|
199
|
+
header .dot.connected { background: var(--panel-success); }
|
|
200
|
+
@media (max-width: 560px) { header .status #status-text { display: none; } }
|
|
201
|
+
#terminal-container { flex: 1; width: 100%; min-height: 0; overflow: hidden; background: var(--terminal-bg); }
|
|
202
|
+
#terminal-container .xterm { height: 100%; padding: 0; }
|
|
203
|
+
/* Hide whatever the active renderer mounts (xterm .xterm div or ghostty canvas)
|
|
204
|
+
until the first fit, so neither renderer flashes an unsized terminal. */
|
|
205
|
+
#terminal-container.terminal-pending > * { visibility: hidden; }
|
|
206
|
+
#terminal-container .xterm-viewport { overflow-y: auto; }
|
|
207
|
+
#terminal-container.terminal-drag-over {
|
|
208
|
+
outline: 2px dashed var(--panel-accent);
|
|
209
|
+
outline-offset: -2px;
|
|
210
|
+
background: color-mix(in srgb, var(--panel-accent) 6%, transparent);
|
|
211
|
+
}
|
|
212
|
+
.header-icon-btn,
|
|
213
|
+
header .notes-btn,
|
|
214
|
+
header .sched-btn,
|
|
215
|
+
header .ext-btn,
|
|
216
|
+
header .theme-switcher-btn,
|
|
217
|
+
header .cmdbar-btn {
|
|
218
|
+
display: flex; align-items: center; justify-content: center;
|
|
219
|
+
background: none; border: none; color: var(--panel-muted); cursor: pointer;
|
|
220
|
+
min-width: 44px; min-height: 44px; padding: 8px; border-radius: 10px;
|
|
221
|
+
transition: color 0.15s, background 0.15s;
|
|
222
|
+
font-size: var(--text-sm); text-decoration: none; font-family: inherit;
|
|
223
|
+
}
|
|
224
|
+
.header-icon-btn:hover,
|
|
225
|
+
header .notes-btn:hover,
|
|
226
|
+
header .sched-btn:hover,
|
|
227
|
+
header .ext-btn:hover,
|
|
228
|
+
header .theme-switcher-btn:hover,
|
|
229
|
+
header .cmdbar-btn:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
230
|
+
.header-icon-btn:focus-visible,
|
|
231
|
+
header .notes-btn:focus-visible,
|
|
232
|
+
header .sched-btn:focus-visible,
|
|
233
|
+
header .ext-btn:focus-visible,
|
|
234
|
+
header .theme-switcher-btn:focus-visible,
|
|
235
|
+
header .cmdbar-btn:focus-visible { box-shadow: 0 0 0 2px var(--panel-accent); outline: none; }
|
|
236
|
+
.header-icon-btn svg,
|
|
237
|
+
header .notes-btn svg,
|
|
238
|
+
header .sched-btn svg,
|
|
239
|
+
header .ext-btn svg,
|
|
240
|
+
header .theme-switcher-btn svg,
|
|
241
|
+
header .cmdbar-btn svg { width: 18px; height: 18px; fill: currentColor; flex-shrink: 0; }
|
|
242
|
+
header .theme-switcher { position: relative; }
|
|
243
|
+
header .theme-switcher-popover {
|
|
244
|
+
position: absolute; top: calc(100% + 8px); right: 0;
|
|
245
|
+
min-width: 180px; max-width: calc(100vw - 24px);
|
|
246
|
+
background: var(--panel-bg); border: 1px solid var(--panel-border);
|
|
247
|
+
border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.12); padding: 6px;
|
|
248
|
+
display: none; z-index: 300;
|
|
249
|
+
}
|
|
250
|
+
header .theme-switcher.open .theme-switcher-popover { display: block; }
|
|
251
|
+
header .theme-option {
|
|
252
|
+
display: flex; align-items: center; gap: 10px; width: 100%;
|
|
253
|
+
min-height: 44px; padding: 10px 12px; border-radius: 8px; border: none; background: none;
|
|
254
|
+
color: var(--page-fg); font-size: var(--text-sm); cursor: pointer; text-align: left; font-family: inherit;
|
|
255
|
+
}
|
|
256
|
+
header .theme-option:hover { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); color: var(--panel-accent); }
|
|
257
|
+
header .theme-option:focus-visible { box-shadow: 0 0 0 2px var(--panel-accent); outline: none; }
|
|
258
|
+
header .theme-option.active { color: var(--panel-accent); font-weight: 500; }
|
|
259
|
+
header .theme-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
|
|
260
|
+
header .theme-check { width: 16px; height: 16px; margin-left: auto; color: var(--panel-accent); }
|
|
261
|
+
@media (max-width: 640px) {
|
|
262
|
+
header { gap: 4px; padding: 8px; }
|
|
263
|
+
header .session { display: none; }
|
|
264
|
+
header .brand span { display: none; }
|
|
265
|
+
}
|
|
266
|
+
@media (max-width: 380px) {
|
|
267
|
+
header .status { display: none; }
|
|
268
|
+
}
|
|
269
|
+
${o?H():""}
|
|
270
|
+
${v()}
|
|
271
|
+
${k()}
|
|
272
|
+
${D()}
|
|
273
|
+
${M()}
|
|
274
|
+
${C()}
|
|
275
|
+
${_()}
|
|
276
|
+
@media (prefers-reduced-motion: reduce) {
|
|
277
|
+
*, *::before, *::after {
|
|
278
|
+
animation-duration: 0.01ms !important;
|
|
279
|
+
animation-iteration-count: 1 !important;
|
|
280
|
+
transition-duration: 0.01ms !important;
|
|
281
|
+
scroll-behavior: auto !important;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
</style>
|
|
285
|
+
</head>
|
|
286
|
+
<body>
|
|
287
|
+
<header>
|
|
288
|
+
<div class="brand"><a href="/" aria-label="Go to home">tmux<span>-weblink</span></a></div>
|
|
289
|
+
<span class="session">${i(e)}</span>
|
|
290
|
+
${o?z("Sessions"):""}
|
|
291
|
+
<button class="notes-btn" id="notes-toggle" title="Session notes" aria-label="Session notes">
|
|
292
|
+
${m("notes")}
|
|
293
|
+
</button>
|
|
294
|
+
<button class="sched-btn" id="sched-toggle" title="Schedule command" aria-label="Schedule command">
|
|
295
|
+
${m("schedule")}
|
|
296
|
+
</button>
|
|
297
|
+
${r.map(a=>`<button class="ext-btn" id="ext-${a.id}-toggle" title="${c(a.name)}" aria-label="${c(a.name)}">${W(a.id,a.icon)}</button>`).join(`
|
|
298
|
+
`)}
|
|
299
|
+
${j(s.template)}
|
|
300
|
+
<div class="status">
|
|
301
|
+
<div class="dot" id="status-dot"></div>
|
|
302
|
+
<span id="status-text">connecting</span>
|
|
303
|
+
</div>
|
|
304
|
+
</header>
|
|
305
|
+
<script>${J()}</script>
|
|
306
|
+
<div id="terminal-container" class="terminal-pending"></div>
|
|
307
|
+
${B()}
|
|
308
|
+
${o?P():""}
|
|
309
|
+
${y(`Notes - ${e}`)}
|
|
310
|
+
${S(`Scheduler - ${e}`)}
|
|
311
|
+
${E(`Windows - ${e}`)}
|
|
312
|
+
${I()}
|
|
313
|
+
${r.map(a=>U(a)).join(`
|
|
314
|
+
`)}
|
|
315
|
+
${r.map(a=>X(a)).join(`
|
|
316
|
+
`)}
|
|
317
|
+
|
|
318
|
+
<script type="module">
|
|
319
|
+
window.__TMUX_WEB_TERMINAL__ = ${JSON.stringify({sessionName:e,terminal:l,scrollback:u,theme:s.terminal,renderer:f}).replace(/</g,"\\u003c")};
|
|
320
|
+
await import('/assets/terminal-client.js');
|
|
321
|
+
|
|
322
|
+
// ========== NOTES ==========
|
|
323
|
+
${$(`session:${e}`)}
|
|
324
|
+
|
|
325
|
+
// ========== SCHEDULER ==========
|
|
326
|
+
${L(e)}
|
|
327
|
+
|
|
328
|
+
// ========== WINDOWS ==========
|
|
329
|
+
${F(e)}
|
|
330
|
+
|
|
331
|
+
// ========== SESSIONS SIDEBAR ==========
|
|
332
|
+
${T(e)}
|
|
333
|
+
|
|
334
|
+
// ========== WINDOW DEEP-LINK (?window=N) ==========
|
|
335
|
+
{
|
|
336
|
+
const wParam = new URLSearchParams(location.search).get('window');
|
|
337
|
+
const wIndex = wParam === null ? NaN : parseInt(wParam, 10);
|
|
338
|
+
if (Number.isInteger(wIndex) && wIndex >= 0) {
|
|
339
|
+
fetch('/api/session/' + encodeURIComponent(${JSON.stringify(e)}) + '/select-window', {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: { 'Content-Type': 'application/json' },
|
|
342
|
+
body: JSON.stringify({ windowIndex: wIndex }),
|
|
343
|
+
}).catch(() => {});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ========== MOBILE TOOLBAR ==========
|
|
348
|
+
${O(e)}
|
|
349
|
+
|
|
350
|
+
// ========== COMMANDBAR ==========
|
|
351
|
+
${o?R(d,p,{sessionName:e},g):""}
|
|
352
|
+
|
|
353
|
+
// ========== NOTES ==========
|
|
354
|
+
// (notes and scheduler scripts already included above; extensions below)
|
|
355
|
+
</script>
|
|
356
|
+
|
|
357
|
+
${r.length>0?`<script>
|
|
358
|
+
// Extension bootstrap: plain script (not module) so it runs before the module
|
|
359
|
+
// awaits terminal-client import, avoiding a race where iframes load during that await.
|
|
360
|
+
${r.map(a=>V(a,e)).join(`
|
|
361
|
+
`)}
|
|
362
|
+
</script>`:""}
|
|
363
|
+
</body>
|
|
364
|
+
</html>`}export{re as renderTerminal};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{db as r}from"./db.js";function u(n,e){return e===void 0?n:`${n}:${e}`}function s(n,e,t){if(n.sessionName!==e)return!1;const i=n.windowIndex;return t===void 0?i===void 0:i===t}function a(){return[...r.data.pinnedViews??[]].sort((n,e)=>e.pinnedAt-n.pinnedAt)}async function c(n,e){const t=Date.now(),i=r.data.pinnedViews??=[],d=i.findIndex(o=>s(o,n,e));if(d>=0)i[d].pinnedAt=t;else{const o={sessionName:n,pinnedAt:t};e!==void 0&&(o.windowIndex=e),i.push(o)}await r.write()}async function p(n,e){const t=r.data.pinnedViews??=[],i=t.filter(d=>!s(d,n,e));i.length!==t.length&&(r.data.pinnedViews=i,await r.write())}export{a as listPinnedViews,c as pinView,p as unpinView,u as viewKey};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{execFile as d,execFileSync as p}from"node:child_process";import{writeFile as w,mkdir as g}from"node:fs/promises";import{existsSync as l}from"node:fs";import u from"node:path";import{promisify as y}from"node:util";import{getDataRoot as x}from"./state-paths.js";import{readSettings as s,writeSettings as a}from"./settings.js";const $=y(d),e=x();async function r(){await g(e,{recursive:!0});const t=u.join(e,"package.json");l(t)||await w(t,JSON.stringify({name:"tmux-web-plugins",private:!0,version:"1.0.0",description:"tmux-web plugin install dir \u2014 managed by `tmux-web add/remove`"},null,2)+`
|
|
2
|
+
`)}function c(t){p("npm",t,{cwd:e,stdio:"inherit"})}async function A(t){await r(),console.log(`\u25B8 installing ${t}`),c(["install",t]);const n=await s();n.plugins??=[],n.plugins.includes(t)?console.log(`\u2713 ${t} already enabled`):(n.plugins.push(t),await a(n),console.log(`\u2713 enabled ${t}`))}async function F(t){l(u.join(e,"node_modules",t))&&(console.log(`\u25B8 uninstalling ${t}`),c(["uninstall",t]));const n=await s();n.plugins?.includes(t)?(n.plugins=n.plugins.filter(i=>i!==t),await a(n),console.log(`\u2713 disabled ${t}`)):console.log(`(${t} was not enabled)`)}function I(){return e}const b=/^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(@[\w.^~*>=<.-]+)?$/i;function f(t){return typeof t=="string"&&t.length>0&&t.length<=214&&b.test(t)}async function m(t){try{const{stdout:n,stderr:i}=await $("npm",t,{cwd:e});return{ok:!0,output:(n+i).trim()}}catch(n){return{ok:!1,output:[n?.stdout,n?.stderr,n?.message].filter(Boolean).join(`
|
|
3
|
+
`).trim()||"npm command failed"}}}async function _(t){if(!f(t))return{ok:!1,output:`invalid package name: ${t}`};await r();const n=await m(["install",t]);if(!n.ok)return n;const i=await s();return i.plugins??=[],i.plugins.includes(t)||(i.plugins.push(t),await a(i)),n}async function R(t){if(!f(t))return{ok:!1,output:`invalid package name: ${t}`};let n="";if(l(u.join(e,"node_modules",t))){const o=await m(["uninstall",t]);if(!o.ok)return o;n=o.output}const i=await s();return i.plugins?.includes(t)&&(i.plugins=i.plugins.filter(o=>o!==t),await a(i)),{ok:!0,output:n}}export{A as cmdAdd,F as cmdRemove,I as getPluginDir,_ as installPlugin,f as isValidPackageName,R as uninstallPlugin};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{randomUUID as m}from"node:crypto";import{db as n}from"./db.js";function i(){return n.data.quickCommands??=[],n.data.quickCommands}function l(){return[...i()].sort((r,t)=>t.updatedAt!==r.updatedAt?t.updatedAt-r.updatedAt:r.title.localeCompare(t.title))}function c(r){const t=typeof r.title=="string"?r.title.trim():"",e=typeof r.command=="string"?r.command.trim():"",o=typeof r.description=="string"?r.description.trim():"";return t?e?t.length>120?{ok:!1,error:"title must be 120 characters or fewer"}:e.length>4096?{ok:!1,error:"command must be 4096 characters or fewer"}:o.length>240?{ok:!1,error:"description must be 240 characters or fewer"}:{ok:!0,title:t,command:e,...o?{description:o}:{}}:{ok:!1,error:"command is required"}:{ok:!1,error:"title is required"}}async function p(r){const t=c(r);if(!t.ok)return{error:t.error};const e=Date.now(),o={id:m(),title:t.title,command:t.command,description:t.description,createdAt:e,updatedAt:e};return i().push(o),await n.write(),o}async function k(r,t){const e=c(t);if(!e.ok)return{error:e.error,status:400};const o=i(),a=o.findIndex(s=>s.id===r);if(a<0)return{error:"not found",status:404};const d={...o[a],title:e.title,command:e.command,description:e.description,updatedAt:Date.now()};return o[a]=d,await n.write(),d}async function w(r){const t=i(),e=t.length;return n.data.quickCommands=t.filter(o=>o.id!==r),n.data.quickCommands.length===e?!1:(await n.write(),!0)}export{p as createQuickCommand,w as deleteQuickCommand,l as listQuickCommands,k as updateQuickCommand,c as validateQuickCommandInput};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import*as o from"node:fs";import*as f from"node:path";import{getDataRoot as k}from"./state-paths.js";import{atomicWriteFileSync as d}from"./atomicWrite.js";const s=6e4,a=60*s,l=[0,0,0,1*s,5*s,15*s,30*s,1*a,4*a,24*a,-1],m=l.length-1,h=1024,c=k(),u=f.join(c,"rate-limits.json");function p(i){return i>=m?-1:l[i]??0}function b(){return{failures:0,lockedUntil:0,permanentLock:!1,lastSeen:Date.now()}}class w{buckets=new Map;constructor(){this.load()}check(t){const e=this.buckets.get(t);return e?(e.lastSeen=Date.now(),e.permanentLock?{allowed:!1,permanentLock:!0,failures:e.failures}:e.lockedUntil>Date.now()?{allowed:!1,retryAfterMs:e.lockedUntil-Date.now(),failures:e.failures}:{allowed:!0,failures:e.failures}):{allowed:!0,failures:0}}recordFailure(t){let e=this.buckets.get(t);e||(this.evictIfFull(),e=b(),this.buckets.set(t,e)),e.failures++,e.lastSeen=Date.now();const r=p(e.failures);return r===-1?(e.permanentLock=!0,e.lockedUntil=0):r>0&&(e.lockedUntil=Date.now()+r),this.save(),this.check(t)}recordSuccess(t){this.buckets.has(t)&&(this.buckets.delete(t),this.save())}getFailureCount(t){return this.buckets.get(t)?.failures??0}isPermanentlyLocked(t){return this.buckets.get(t)?.permanentLock??!1}dispose(){}evictIfFull(){if(this.buckets.size<h)return;let t=null,e=1/0;for(const[r,n]of this.buckets)n.permanentLock||n.lastSeen<e&&(e=n.lastSeen,t=r);t&&this.buckets.delete(t)}load(){let t;try{t=o.readFileSync(u,"utf8")}catch(r){if(r.code==="ENOENT")return;console.error("[RateLimiter] failed to read state, starting empty:",r);return}let e;try{e=JSON.parse(t)}catch{console.error("[RateLimiter] corrupt state, starting empty");return}e&&typeof e=="object"&&e._version===2&&typeof e.ips=="object"&&(this.buckets=new Map(Object.entries(e.ips)))}save(){o.existsSync(c)||o.mkdirSync(c,{recursive:!0,mode:448});const t={ips:Object.fromEntries(this.buckets),_version:2};d(u,JSON.stringify(t,null,2)+`
|
|
2
|
+
`,384)}}export{w as RateLimiter};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const o=864e5,u=31536e6,m=36e5,l="Use: 1h, 5m, 30s, 1m30s, 70h, 30d",a="Maximum delay is 1 year (e.g. 365d, 8760h)",s="delay must be between 1ms and 1 year";function i(t){const r=(t||"").trim().toLowerCase();if(!r)return null;const e=r.match(/^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/);if(!e||!e[1]&&!e[2]&&!e[3]&&!e[4])return null;const n=(parseInt(e[1]||"0",10)*86400+parseInt(e[2]||"0",10)*3600+parseInt(e[3]||"0",10)*60+parseInt(e[4]||"0",10))*1e3;return n>0?n:null}function c(t){return Number.isFinite(t)&&t>=1&&t<=31536e6}function p(t){if(!t||typeof t!="object")return null;const r=t.delayMs;return typeof r!="number"||!Number.isFinite(r)?null:r<1||r>31536e6?s:null}function M(){return`
|
|
2
|
+
function parseDelay(str) {
|
|
3
|
+
str = (str || '').trim().toLowerCase();
|
|
4
|
+
if (!str) return null;
|
|
5
|
+
const m = str.match(/^(?:(\\d+)d)?(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$/);
|
|
6
|
+
if (!m || (!m[1] && !m[2] && !m[3] && !m[4])) return null;
|
|
7
|
+
const ms = ((parseInt(m[1] || '0', 10) * 86400) + (parseInt(m[2] || '0', 10) * 3600) + (parseInt(m[3] || '0', 10) * 60) + parseInt(m[4] || '0', 10)) * 1000;
|
|
8
|
+
return ms > 0 ? ms : null;
|
|
9
|
+
}`.trim()}export{m as ARM_SCAN_INTERVAL_MS,l as DELAY_INVALID_MESSAGE,a as DELAY_MAX_MESSAGE,s as DELAY_RANGE_MESSAGE,u as MAX_SCHEDULE_MS,o as MAX_TIMER_MS,p as getScheduleDelayError,c as isValidDelayMs,i as parseDelayMs,M as scheduleDelayParseScript};
|