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,210 @@
|
|
|
1
|
+
import{notesDbScript as o}from"./notes-db.js";import{notesUtilsScript as n}from"./notes-utils.js";import{closeOtherDrawersExcept as r,wrapDrawerScript as a}from"./drawer-script.js";import{drawerResizeCSS as s,drawerResizeHandleHTML as i,drawerResizeScript as d}from"./drawer-resize.js";import{escapeHtml as c}from"./html.js";function m(){return`
|
|
2
|
+
#notes-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
|
+
#notes-backdrop.open { opacity: 1; pointer-events: auto; }
|
|
7
|
+
#notes-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
|
+
#notes-drawer.open { transform: translateX(0); }
|
|
14
|
+
${s()}
|
|
15
|
+
.drawer-header {
|
|
16
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
17
|
+
padding: 10px 16px; border-bottom: 1px solid var(--panel-border);
|
|
18
|
+
font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
|
|
19
|
+
color: var(--panel-accent); font-family: var(--font-mono); flex-shrink: 0;
|
|
20
|
+
}
|
|
21
|
+
.drawer-header button {
|
|
22
|
+
background: none; border: none; color: var(--panel-muted); cursor: pointer;
|
|
23
|
+
font-size: var(--text-lg); line-height: 1; min-width: 44px; min-height: 44px; padding: 8px; border-radius: 8px; transition: color 0.15s, background 0.15s;
|
|
24
|
+
}
|
|
25
|
+
.drawer-header button:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
26
|
+
.drawer-header button:focus-visible { box-shadow: 0 0 0 2px var(--panel-accent); outline: none; }
|
|
27
|
+
.drawer-toolbar {
|
|
28
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
29
|
+
padding: 8px 16px; border-bottom: 1px solid var(--panel-border);
|
|
30
|
+
flex-shrink: 0; gap: 8px;
|
|
31
|
+
}
|
|
32
|
+
.drawer-toolbar .badge {
|
|
33
|
+
font-size: var(--text-xs); color: var(--panel-success); letter-spacing: 0.1em; text-transform: uppercase;
|
|
34
|
+
opacity: 0; transition: opacity 0.15s; font-family: var(--font-mono);
|
|
35
|
+
}
|
|
36
|
+
.drawer-toolbar .badge.show { opacity: 1; }
|
|
37
|
+
.drawer-toolbar .actions { display: flex; gap: 8px; align-items: center; }
|
|
38
|
+
.drawer-toolbar button {
|
|
39
|
+
font-size: var(--text-xs); color: var(--panel-muted); background: none;
|
|
40
|
+
border: 1px solid var(--panel-border); padding: 6px 12px; border-radius: 6px;
|
|
41
|
+
cursor: pointer; font-family: var(--font-mono); transition: all 0.15s;
|
|
42
|
+
min-height: 44px;
|
|
43
|
+
}
|
|
44
|
+
.drawer-toolbar button:hover { border-color: var(--panel-accent); color: var(--panel-accent); }
|
|
45
|
+
.drawer-toolbar button:focus-visible { box-shadow: 0 0 0 2px var(--panel-accent); outline: none; }
|
|
46
|
+
.drawer-toolbar button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
47
|
+
.drawer-toolbar button.flash { color: var(--panel-success); border-color: var(--panel-success); }
|
|
48
|
+
#notes-editor {
|
|
49
|
+
flex: 1; padding: 16px; outline: none; overflow-y: auto;
|
|
50
|
+
font-size: var(--text-sm); line-height: 1.7; font-family: var(--font-mono);
|
|
51
|
+
white-space: pre-wrap; word-break: break-word; color: var(--page-fg);
|
|
52
|
+
scrollbar-width: thin;
|
|
53
|
+
scrollbar-color: var(--panel-border) transparent;
|
|
54
|
+
}
|
|
55
|
+
#notes-editor::-webkit-scrollbar { width: 4px; }
|
|
56
|
+
#notes-editor::-webkit-scrollbar-track { background: transparent; }
|
|
57
|
+
#notes-editor::-webkit-scrollbar-thumb {
|
|
58
|
+
background: var(--panel-border); border-radius: 2px;
|
|
59
|
+
}
|
|
60
|
+
#notes-editor::-webkit-scrollbar-thumb:hover { background: var(--panel-muted); }
|
|
61
|
+
#notes-editor:empty::before {
|
|
62
|
+
content: "Double-click to add notes..."; color: var(--panel-muted); pointer-events: none;
|
|
63
|
+
}
|
|
64
|
+
#notes-editor[contenteditable="true"] {
|
|
65
|
+
border: 1px solid color-mix(in srgb, var(--panel-accent) 30%, transparent);
|
|
66
|
+
background: color-mix(in srgb, var(--page-bg) 70%, transparent);
|
|
67
|
+
border-radius: 8px; margin: 8px;
|
|
68
|
+
}
|
|
69
|
+
#notes-editor a { color: var(--panel-accent); text-decoration: none; }
|
|
70
|
+
#notes-editor a:hover { text-decoration: underline; }
|
|
71
|
+
@media (max-width: 560px) {
|
|
72
|
+
#notes-drawer { width: min(100vw - 16px, 400px); }
|
|
73
|
+
}`}function g(e){return`
|
|
74
|
+
<div id="notes-backdrop"></div>
|
|
75
|
+
<div id="notes-drawer" class="resizable-drawer">
|
|
76
|
+
${i()}
|
|
77
|
+
<div class="drawer-header">
|
|
78
|
+
<span>${c(e)}</span>
|
|
79
|
+
<button id="drawer-close">×</button>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="drawer-toolbar">
|
|
82
|
+
<span class="badge" id="edit-badge">Editing</span>
|
|
83
|
+
<div class="actions">
|
|
84
|
+
<button id="notes-copy">Copy</button>
|
|
85
|
+
<button id="notes-export">Export .md</button>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div id="notes-editor"></div>
|
|
89
|
+
</div>`}function f(e){const t=JSON.stringify(e);return a("notes",`
|
|
90
|
+
const NOTES_SCOPE = ${t};
|
|
91
|
+
${o()}
|
|
92
|
+
${n()}
|
|
93
|
+
${d("notes-drawer","tmux-web:drawer-width:notes",360)}
|
|
94
|
+
|
|
95
|
+
const notesBackdrop = document.getElementById('notes-backdrop');
|
|
96
|
+
const notesDrawer = document.getElementById('notes-drawer');
|
|
97
|
+
const notesEditor = document.getElementById('notes-editor');
|
|
98
|
+
const notesToggle = document.getElementById('notes-toggle');
|
|
99
|
+
const drawerClose = document.getElementById('drawer-close');
|
|
100
|
+
const editBadge = document.getElementById('edit-badge');
|
|
101
|
+
const copyBtn = document.getElementById('notes-copy');
|
|
102
|
+
const exportBtn = document.getElementById('notes-export');
|
|
103
|
+
|
|
104
|
+
let notePlain = '';
|
|
105
|
+
let editing = false;
|
|
106
|
+
|
|
107
|
+
function setButtonsEnabled(enabled) {
|
|
108
|
+
copyBtn.disabled = !enabled;
|
|
109
|
+
exportBtn.disabled = !enabled;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function renderNote() {
|
|
113
|
+
const rec = await loadNote(NOTES_SCOPE);
|
|
114
|
+
notePlain = rec?.content || '';
|
|
115
|
+
notesEditor.innerHTML = linkifyHTML(escapeHTML(notePlain));
|
|
116
|
+
setButtonsEnabled(!!notePlain);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function openDrawer() {
|
|
120
|
+
${r("notes")}
|
|
121
|
+
notesDrawer.classList.add('open');
|
|
122
|
+
notesBackdrop.classList.add('open');
|
|
123
|
+
renderNote();
|
|
124
|
+
const url = new URL(location.href);
|
|
125
|
+
if (url.searchParams.get('tab') !== 'notes') {
|
|
126
|
+
url.searchParams.set('tab', 'notes');
|
|
127
|
+
history.pushState({ notesOpen: true }, '', url);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function closeDrawer() {
|
|
132
|
+
notesDrawer.classList.remove('open');
|
|
133
|
+
notesBackdrop.classList.remove('open');
|
|
134
|
+
if (editing) stopEditing();
|
|
135
|
+
const url = new URL(location.href);
|
|
136
|
+
if (url.searchParams.get('tab') === 'notes') {
|
|
137
|
+
url.searchParams.delete('tab');
|
|
138
|
+
history.pushState({}, '', url);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function startEditing() {
|
|
143
|
+
editing = true;
|
|
144
|
+
notesEditor.contentEditable = 'true';
|
|
145
|
+
notesEditor.textContent = notePlain;
|
|
146
|
+
editBadge.classList.add('show');
|
|
147
|
+
notesEditor.focus();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function stopEditing() {
|
|
151
|
+
if (!editing) return;
|
|
152
|
+
editing = false;
|
|
153
|
+
// innerText (not textContent): Enter in contenteditable inserts <br> or blocks;
|
|
154
|
+
// textContent drops those breaks and merges lines.
|
|
155
|
+
notePlain = (notesEditor.innerText ?? '').replace(/\\r\\n/g, '\\n');
|
|
156
|
+
notesEditor.contentEditable = 'false';
|
|
157
|
+
notesEditor.innerHTML = linkifyHTML(escapeHTML(notePlain));
|
|
158
|
+
editBadge.classList.remove('show');
|
|
159
|
+
await saveNote(NOTES_SCOPE, notePlain);
|
|
160
|
+
setButtonsEnabled(!!notePlain);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
notesToggle.addEventListener('click', () => {
|
|
164
|
+
if (notesDrawer.classList.contains('open')) closeDrawer();
|
|
165
|
+
else openDrawer();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
drawerClose.addEventListener('click', closeDrawer);
|
|
169
|
+
notesBackdrop.addEventListener('click', closeDrawer);
|
|
170
|
+
|
|
171
|
+
notesEditor.addEventListener('dblclick', (e) => {
|
|
172
|
+
if (!editing) { e.preventDefault(); startEditing(); }
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
notesEditor.addEventListener('blur', () => { if (editing) stopEditing(); });
|
|
176
|
+
|
|
177
|
+
notesEditor.addEventListener('keydown', (e) => {
|
|
178
|
+
if (e.key === 'Escape' && editing) { e.preventDefault(); stopEditing(); }
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
copyBtn.addEventListener('click', async () => {
|
|
182
|
+
if (!notePlain) return;
|
|
183
|
+
try {
|
|
184
|
+
await navigator.clipboard.writeText(notePlain);
|
|
185
|
+
copyBtn.textContent = 'Copied!';
|
|
186
|
+
copyBtn.classList.add('flash');
|
|
187
|
+
setTimeout(() => { copyBtn.textContent = 'Copy'; copyBtn.classList.remove('flash'); }, 1500);
|
|
188
|
+
} catch {}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
exportBtn.addEventListener('click', () => {
|
|
192
|
+
if (!notePlain) return;
|
|
193
|
+
const blob = new Blob([notePlain], { type: 'text/markdown' });
|
|
194
|
+
const a = document.createElement('a');
|
|
195
|
+
a.href = URL.createObjectURL(blob);
|
|
196
|
+
const safeName = NOTES_SCOPE.replace(/[:\\/\\\\]/g, '-');
|
|
197
|
+
a.download = \`notes-\${safeName}.md\`;
|
|
198
|
+
a.click();
|
|
199
|
+
URL.revokeObjectURL(a.href);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
window.addEventListener('popstate', () => {
|
|
203
|
+
const params = new URLSearchParams(location.search);
|
|
204
|
+
if (params.get('tab') === 'notes') openDrawer();
|
|
205
|
+
else closeDrawer();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (new URLSearchParams(location.search).get('tab') === 'notes') {
|
|
209
|
+
openDrawer();
|
|
210
|
+
}`,"closeDrawer")}export{m as notesDrawerCSS,g as notesDrawerHTML,f as notesDrawerScript};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function e(){return`
|
|
2
|
+
function linkifyHTML(text) {
|
|
3
|
+
const urlRe = /https?:\\/\\/[^\\s<>"]+/g;
|
|
4
|
+
return text.replace(urlRe, (url) =>
|
|
5
|
+
\`<a href="\${url}" target="_blank" rel="noopener noreferrer">\${url}</a>\`
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function escapeHTML(text) {
|
|
10
|
+
return text
|
|
11
|
+
.replace(/&/g, '&')
|
|
12
|
+
.replace(/</g, '<')
|
|
13
|
+
.replace(/>/g, '>')
|
|
14
|
+
.replace(/"/g, '"');
|
|
15
|
+
}`}export{e as notesUtilsScript};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import{cssVarsStyle as s}from"../theme.js";import{AGENT_LABELS as o}from"../agent-detect.js";import{commandbarCSS as i,commandbarHTML as l,commandbarScript as p}from"../commandbar.js";import{notesDrawerCSS as c,notesDrawerHTML as d,notesDrawerScript as g}from"../notes-drawer.js";import{sharedLayoutCSS as m,sharedHeader as u,sharedSidebar as x,newSessionModalHTML as f,newSessionModalScript as b}from"../shared-layout.js";function k(a,e=!1,t=[]){const n=JSON.stringify(o).replace(/</g,"\\u003c"),r=`
|
|
2
|
+
.sub { font-size: var(--text-sm); color: var(--panel-muted); margin-bottom: 24px; line-height: 1.5; }
|
|
3
|
+
.agent {
|
|
4
|
+
display: flex; align-items: center; gap: 12px; text-decoration: none;
|
|
5
|
+
padding: 14px 16px; border: 1px solid var(--panel-border); border-radius: 12px;
|
|
6
|
+
margin-bottom: 8px; background: var(--panel-bg); transition: border-color 0.15s, transform 0.1s;
|
|
7
|
+
}
|
|
8
|
+
.agent:hover { border-color: var(--panel-accent); transform: translateY(-1px); }
|
|
9
|
+
.agent .name { font-size: var(--text-sm); font-weight: 600; color: var(--page-fg); flex: 1; }
|
|
10
|
+
.agent .loc { font-size: var(--text-xs); color: var(--panel-muted); }
|
|
11
|
+
.badge {
|
|
12
|
+
font-size: var(--text-xs); letter-spacing: 0.05em; text-transform: uppercase;
|
|
13
|
+
padding: 4px 10px; border-radius: 10px; border: 1px solid var(--panel-border);
|
|
14
|
+
flex-shrink: 0; min-width: 64px; text-align: center;
|
|
15
|
+
}
|
|
16
|
+
.badge.working { color: var(--panel-success); border-color: var(--panel-success); }
|
|
17
|
+
.badge.blocked { color: #f0c674; border-color: #f0c674; }
|
|
18
|
+
.badge.idle { color: var(--panel-muted); }
|
|
19
|
+
.badge.unknown { color: var(--panel-muted); opacity: 0.7; }
|
|
20
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--panel-muted); }
|
|
21
|
+
.dot.working { background: var(--panel-success); }
|
|
22
|
+
.dot.blocked { background: #f0c674; }
|
|
23
|
+
.empty { font-size: var(--text-sm); color: var(--panel-muted); line-height: 1.6; margin-top: 20px; }
|
|
24
|
+
@media (max-width: 560px) {
|
|
25
|
+
.agent { flex-wrap: wrap; gap: 8px; }
|
|
26
|
+
.agent .loc { width: 100%; order: 3; }
|
|
27
|
+
}
|
|
28
|
+
${e?i():""}
|
|
29
|
+
${c()}`;return`<!DOCTYPE html>
|
|
30
|
+
<html lang="en">
|
|
31
|
+
<head>
|
|
32
|
+
<meta charset="UTF-8" />
|
|
33
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
34
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
35
|
+
<title>Agents - tmux-web</title>
|
|
36
|
+
<style>
|
|
37
|
+
${s(a.shell)}
|
|
38
|
+
${m(r)}
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
|
|
43
|
+
${u({commandbarEnabled:e,title:"Agents",themeTemplate:a.template})}
|
|
44
|
+
|
|
45
|
+
<div class="page-wrap">
|
|
46
|
+
<div class="page-layout">
|
|
47
|
+
${x({activePage:"agents",agentsEnabled:!0,refreshHref:"/agents"})}
|
|
48
|
+
<main class="main-panel">
|
|
49
|
+
<p class="sub">AI agents in the last panes you viewed. Updates automatically.</p>
|
|
50
|
+
<div id="agents-list">
|
|
51
|
+
<p class="empty" id="empty-msg">Loading\u2026</p>
|
|
52
|
+
</div>
|
|
53
|
+
</main>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
${f()}
|
|
58
|
+
${e?l():""}
|
|
59
|
+
${d("Notes - Global")}
|
|
60
|
+
|
|
61
|
+
<script type="module">
|
|
62
|
+
${g("__global__")}
|
|
63
|
+
${e?p(t,[]):""}
|
|
64
|
+
${b()}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<script type="module">
|
|
68
|
+
const LABELS = ${n};
|
|
69
|
+
|
|
70
|
+
function esc(s) {
|
|
71
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function render(list) {
|
|
75
|
+
const container = document.getElementById('agents-list');
|
|
76
|
+
if (!list || list.length === 0) {
|
|
77
|
+
container.innerHTML = '<p class="empty" id="empty-msg">No agents detected.<br>Open a session running Claude, Codex, OpenCode, or Cursor to start watching it.</p>';
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
container.innerHTML = list.map((a) => {
|
|
81
|
+
const label = LABELS[a.agent] || a.agent;
|
|
82
|
+
const state = a.state || 'unknown';
|
|
83
|
+
return '<a class="agent" href="/s/' + encodeURIComponent(a.sessionName) + '">' +
|
|
84
|
+
'<span class="dot ' + esc(state) + '"></span>' +
|
|
85
|
+
'<span class="name">' + esc(label) + '</span>' +
|
|
86
|
+
'<span class="loc">' + esc(a.sessionName) + ':' + a.windowIndex + '.' + a.paneIndex + '</span>' +
|
|
87
|
+
'<span class="badge ' + esc(state) + '">' + esc(state) + '</span>' +
|
|
88
|
+
'</a>';
|
|
89
|
+
}).join('');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function poll() {
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch('/api/agents', { headers: { 'accept': 'application/json' } });
|
|
95
|
+
if (res.ok) render(await res.json());
|
|
96
|
+
} catch {
|
|
97
|
+
// transient \u2014 keep last render
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
poll();
|
|
102
|
+
setInterval(poll, 2500);
|
|
103
|
+
</script>
|
|
104
|
+
</body>
|
|
105
|
+
</html>`}export{k as renderAgentsIndex};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import{cssVarsStyle as v}from"../theme.js";import{commandbarCSS as w,commandbarHTML as b,commandbarScript as y}from"../commandbar.js";import{sharedLayoutCSS as S,sharedHeader as $,sharedSidebar as M,newSessionModalHTML as H,newSessionModalScript as k}from"../shared-layout.js";function l(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function C(e,i){const t=Math.max(0,i-e),n=Math.floor(t/1e3);if(n<45)return"just now";const s=Math.floor(n/60);if(s<60)return s+"m ago";const r=Math.floor(s/60);if(r<24)return r+"h ago";const a=Math.floor(r/24);return a<30?a+"d ago":new Date(e).toLocaleDateString()}function N(e,i,t=!1,n=[],s=!1,r=new Set){const a=Date.now(),p=e.map(o=>{const x=l(o.windowName||"(unnamed)"),g=l(o.sessionName),u=l(C(o.visitedAt,a)),c=r.has(o.sessionName),d=`
|
|
2
|
+
<div class="hist-main">
|
|
3
|
+
<span class="hist-window">${x}</span>
|
|
4
|
+
<span class="hist-session">${g}${c?"":' <span class="hist-gone">gone</span>'}</span>
|
|
5
|
+
</div>
|
|
6
|
+
<span class="hist-time">${u}</span>`;return c?`<a class="hist-row" href="${"/s/"+encodeURIComponent(o.sessionName)+"?window="+o.windowIndex}">${d}</a>`:`<div class="hist-row dead" title="Session no longer exists">${d}</div>`}).join(`
|
|
7
|
+
`),m=e.length?'<div class="hist-toolbar"><button id="hist-clear" type="button">Clear history</button></div>':"",h=e.length?p:'<p class="empty">No history yet. Sessions and windows you visit will show up here.</p>',f=`
|
|
8
|
+
.hist-toolbar { display: flex; justify-content: flex-end; margin-bottom: 12px; }
|
|
9
|
+
.hist-toolbar button {
|
|
10
|
+
background: none; border: 1px solid var(--panel-border); border-radius: 6px;
|
|
11
|
+
color: var(--panel-muted); font-family: inherit; font-size: var(--text-xs);
|
|
12
|
+
padding: 8px 14px; cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s;
|
|
13
|
+
min-height: 44px;
|
|
14
|
+
}
|
|
15
|
+
.hist-toolbar button:hover { border-color: #fc8181; color: #fc8181; background: color-mix(in srgb, #fc8181 8%, transparent); }
|
|
16
|
+
.hist-toolbar button:focus-visible { box-shadow: 0 0 0 2px #fc8181; outline: none; }
|
|
17
|
+
.hist-row {
|
|
18
|
+
display: flex; align-items: center; gap: 12px; justify-content: space-between;
|
|
19
|
+
padding: 14px 16px; border: 1px solid var(--panel-border); border-radius: 10px;
|
|
20
|
+
margin-bottom: 8px; text-decoration: none; color: var(--page-fg);
|
|
21
|
+
background: var(--panel-bg); transition: border-color 0.15s, transform 0.1s;
|
|
22
|
+
}
|
|
23
|
+
.hist-row:hover { border-color: var(--panel-accent); transform: translateY(-1px); }
|
|
24
|
+
.hist-row.dead { opacity: 0.5; cursor: default; }
|
|
25
|
+
.hist-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
|
26
|
+
.hist-window {
|
|
27
|
+
font-size: var(--text-sm); font-weight: 600; color: var(--page-fg);
|
|
28
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
29
|
+
}
|
|
30
|
+
.hist-session { font-size: var(--text-xs); color: var(--panel-muted); letter-spacing: 0.03em; }
|
|
31
|
+
.hist-gone {
|
|
32
|
+
color: #fc8181; text-transform: uppercase; font-size: var(--text-xs); letter-spacing: 0.08em;
|
|
33
|
+
border: 1px solid currentColor; border-radius: 3px; padding: 0 4px; margin-left: 4px;
|
|
34
|
+
}
|
|
35
|
+
.hist-time { font-size: var(--text-xs); color: var(--panel-muted); flex-shrink: 0; }
|
|
36
|
+
.empty { font-size: var(--text-sm); color: var(--panel-muted); line-height: 1.6; margin-top: 20px; }
|
|
37
|
+
@media (max-width: 560px) {
|
|
38
|
+
.hist-row { flex-direction: column; align-items: flex-start; gap: 8px; }
|
|
39
|
+
.hist-time { align-self: flex-end; }
|
|
40
|
+
}
|
|
41
|
+
${t?w():""}`;return`<!DOCTYPE html>
|
|
42
|
+
<html lang="en">
|
|
43
|
+
<head>
|
|
44
|
+
<meta charset="UTF-8" />
|
|
45
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
46
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
47
|
+
<title>History - tmux-web</title>
|
|
48
|
+
<style>
|
|
49
|
+
${v(i.shell)}
|
|
50
|
+
${S(f)}
|
|
51
|
+
</style>
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
|
|
55
|
+
${$({commandbarEnabled:t,title:"History",themeTemplate:i.template})}
|
|
56
|
+
|
|
57
|
+
<div class="page-wrap">
|
|
58
|
+
<div class="page-layout">
|
|
59
|
+
${M({activePage:"history",agentsEnabled:s,refreshHref:"/history"})}
|
|
60
|
+
<main class="main-panel">
|
|
61
|
+
${m}
|
|
62
|
+
<div id="hist-list">${h}</div>
|
|
63
|
+
</main>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
${H()}
|
|
68
|
+
${t?b():""}
|
|
69
|
+
|
|
70
|
+
<script type="module">
|
|
71
|
+
const clearBtn = document.getElementById('hist-clear');
|
|
72
|
+
if (clearBtn) {
|
|
73
|
+
clearBtn.addEventListener('click', async () => {
|
|
74
|
+
clearBtn.disabled = true;
|
|
75
|
+
try {
|
|
76
|
+
await fetch('/api/history/clear', { method: 'POST' });
|
|
77
|
+
location.reload();
|
|
78
|
+
} catch { clearBtn.disabled = false; }
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
${t?y(n,[]):""}
|
|
82
|
+
${k()}
|
|
83
|
+
</script>
|
|
84
|
+
</body>
|
|
85
|
+
</html>`}export{N as renderHistoryIndex};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import{cssVarsStyle as w}from"../theme.js";import{notesDrawerCSS as b,notesDrawerHTML as v,notesDrawerScript as x}from"../notes-drawer.js";import{escapeHtml as $}from"../html.js";import{commandbarCSS as y,commandbarHTML as S,commandbarScript as M}from"../commandbar.js";import{sharedLayoutCSS as k,sharedHeader as T,sharedSidebar as C,newSessionModalHTML as A,newSessionModalScript as D}from"../shared-layout.js";function H(o){const s=Date.now()-o;if(s<0)return"just now";const a=Math.floor(s/1e3),n=Math.floor(a/60),t=Math.floor(n/60),e=Math.floor(t/24),i=Math.floor(e/7),c=Math.floor(e/30),l=Math.floor(e/365),r=new Intl.RelativeTimeFormat("en",{numeric:"auto"});return a<45?"just now":n<60?r.format(-n,"minute"):t<24?r.format(-t,"hour"):e<7?r.format(-e,"day"):i<5?r.format(-i,"week"):c<12?r.format(-c,"month"):r.format(-l,"year")}function L(o,s,a){if(s==="default")return o;const n=[],t=[];for(const e of o)a.has(e.name)?n.push(e):t.push(e);return n.sort((e,i)=>(a.get(i.name)??0)-(a.get(e.name)??0)),[...n,...t]}function z(o,s,a){const n=`${o.windows} window${o.windows!==1?"s":""}`,t=o.attached?" \xB7 attached":"";if(s==="default")return`${n}${t}`;const e=a.get(o.name);return e?`${n}${t} \xB7 ${H(e)}`:`${n}${t}`}function F(o,s){const{view:a,accessMap:n,commandbarEnabled:t=!1,commandbarSessions:e=[],agentsEnabled:i=!1,theme:c}=s,l=L(o,a,n),r=[{label:"Open notes",meta:"Global notes",clickTargetId:"notes-toggle"}];i&&r.push({label:"View All Agents",meta:"Running agents",href:"/agents"});const p=l.map(m=>`<a href="/s/${encodeURIComponent(m.name)}" class="session-row">
|
|
2
|
+
<span class="name">${$(m.name)}</span>
|
|
3
|
+
<span class="meta">${z(m,a,n)}</span>
|
|
4
|
+
</a>`).join(`
|
|
5
|
+
`),d=o.length===0?'<p class="empty">No tmux sessions found.<br>Create one with <code>tmux new -s mysession</code><br>or use the <strong>New Session</strong> button.</p>':"",f=a==="recent"?"/?view=recent":"/",g=a==="default"?" active":"",u=a==="recent"?" active":"",h=`
|
|
6
|
+
.session-row {
|
|
7
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
8
|
+
padding: 18px 22px; border: 1px solid var(--panel-border); border-radius: 16px;
|
|
9
|
+
margin-bottom: 12px; text-decoration: none; color: var(--page-fg);
|
|
10
|
+
background: var(--panel-bg); transition: transform 0.1s, box-shadow 0.15s, border-color 0.15s;
|
|
11
|
+
}
|
|
12
|
+
.session-row:hover {
|
|
13
|
+
border-color: var(--panel-accent);
|
|
14
|
+
box-shadow: 0 4px 20px color-mix(in srgb, var(--panel-accent) 8%, transparent);
|
|
15
|
+
transform: translateY(-1px);
|
|
16
|
+
}
|
|
17
|
+
.session-row .name { font-size: var(--text-base); font-weight: 500; color: var(--page-fg); }
|
|
18
|
+
.session-row .meta { font-size: var(--text-sm); color: var(--panel-muted); text-align: right; margin-top: 4px; }
|
|
19
|
+
.empty { font-size: var(--text-sm); color: var(--panel-muted); line-height: 1.7; margin-top: 12px; }
|
|
20
|
+
.empty code { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); padding: 3px 7px; border-radius: 6px; font-size: var(--text-xs) }
|
|
21
|
+
.empty strong { color: var(--panel-accent); font-weight: 500; }
|
|
22
|
+
.view-tabs {
|
|
23
|
+
display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px;
|
|
24
|
+
}
|
|
25
|
+
.view-tabs .tab {
|
|
26
|
+
display: inline-flex; align-items: center;
|
|
27
|
+
font-size: var(--text-sm); color: var(--panel-muted); text-decoration: none;
|
|
28
|
+
min-height: 44px; padding: 8px 16px; border-radius: 999px;
|
|
29
|
+
transition: color 0.15s, background 0.15s;
|
|
30
|
+
}
|
|
31
|
+
.view-tabs .tab:hover { color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
32
|
+
.view-tabs .tab.active { color: var(--panel-accent-on); background: var(--panel-accent); }
|
|
33
|
+
@media (max-width: 560px) {
|
|
34
|
+
.session-row { flex-direction: column; align-items: flex-start; gap: 6px; padding: 16px 18px; }
|
|
35
|
+
.session-row .meta { text-align: left; margin-top: 0; }
|
|
36
|
+
}
|
|
37
|
+
${t?y():""}
|
|
38
|
+
${b()}`;return`<!DOCTYPE html>
|
|
39
|
+
<html lang="en">
|
|
40
|
+
<head>
|
|
41
|
+
<meta charset="UTF-8" />
|
|
42
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
43
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
44
|
+
<title>tmux-weblink</title>
|
|
45
|
+
<style>
|
|
46
|
+
${w(c.shell)}
|
|
47
|
+
${k(h)}
|
|
48
|
+
</style>
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
|
|
52
|
+
${T({commandbarEnabled:t,title:"TMUX Sessions",themeTemplate:c.template})}
|
|
53
|
+
|
|
54
|
+
<div class="page-wrap">
|
|
55
|
+
<div class="page-layout">
|
|
56
|
+
${C({activePage:"home",agentsEnabled:i,refreshHref:f})}
|
|
57
|
+
<main class="main-panel">
|
|
58
|
+
<nav class="view-tabs">
|
|
59
|
+
<a href="/" class="tab${g}">Default</a>
|
|
60
|
+
<a href="/?view=recent" class="tab${u}">Last Updated</a>
|
|
61
|
+
</nav>
|
|
62
|
+
${p}
|
|
63
|
+
${d}
|
|
64
|
+
</main>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
${A()}
|
|
69
|
+
${t?S():""}
|
|
70
|
+
${v("Notes - Global")}
|
|
71
|
+
|
|
72
|
+
<script type="module">
|
|
73
|
+
${x("__global__")}
|
|
74
|
+
${t?M(e,r):""}
|
|
75
|
+
${D()}
|
|
76
|
+
</script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>`}export{F as renderLanding};
|