tmux-weblink 1.4.2 → 1.4.3
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 +10 -10
- package/dist/lib/agents-watch.js +2 -2
- package/dist/lib/tmux-capture.js +1 -1
- package/dist/lib/watched-panes.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{chmodSync as
|
|
2
|
+
import{chmodSync as Be,existsSync as B,mkdirSync as Ve,readdirSync as ue,readFileSync as le,statSync as D,unlinkSync as Ge,writeFileSync as ze}from"node:fs";import{readFile as Je}from"node:fs/promises";import{execSync as V,execFileSync as me}from"node:child_process";import{createRequire as Ke}from"node:module";import j from"node:path";import{fileURLToPath as Qe}from"node:url";import{Hono as Xe}from"hono";import{serve as Ye}from"@hono/node-server";import{WebSocketServer as Ze,WebSocket as fe}from"ws";import*as et from"node-pty";try{const e=j.dirname(Ke(import.meta.url).resolve("node-pty/package.json")),t=j.join(e,"prebuilds");if(B(t))for(const r of ue(t)){const n=j.join(t,r,"spawn-helper");B(n)&&!(D(n).mode&73)&&Be(n,493)}}catch{}import{listSessions as _}from"./sessions.js";import{renderLoginPage as tt,renderNotesIndex as rt,renderNotesPage as nt,renderSettings as ot,renderThemeSettings as st,renderScheduleIndex as it,renderHistoryIndex as at,renderQuickCommandsPage as dt,renderFilesIndex as ct,renderShell as ut}from"./frontend.js";import{loadSecurityConfig as lt,saveSecurityConfig as pe}from"./lib/security-config.js";import{hashPassword as he,verifyPassword as ye,validatePassword as ge,TokenStore as mt}from"./lib/auth.js";import{RateLimiter as ft}from"./lib/rateLimiter.js";import{atomicWriteFileSync as pt}from"./lib/atomicWrite.js";import{resolveFsPath as H,resolveFsRoots as we,MAX_FILE_BYTES as je,walkRecursive as ht}from"./lib/fs-access.js";import{captureSessionWindowsWithPath as yt}from"./lib/tmux-windows.js";import{audit as l}from"./lib/auditLog.js";import{db as x}from"./lib/db.js";import{getSessionAccessMap as E}from"./lib/session-access.js";import{listWindowHistory as gt,clearWindowHistory as wt}from"./lib/window-history.js";import{loadExtensions as jt,spawnExtensionBackend as kt,registerExtensionRoutes as vt}from"./lib/ext-loader.js";import{SchedulerService as St,isValidScheduleInput as xt,isValidRescheduleInput as Tt}from"./lib/scheduler.js";import{getScheduleDelayError as ke}from"./lib/schedule-delay.js";import{handleClientMessage as bt}from"./lib/ws-message.js";import{loadDotEnv as At}from"./lib/load-env.js";import{cmdAdd as qt,cmdRemove as _t,cmdList as Ct,cmdSetup as It,cmdTheme as Et,printUsage as ve,printVersion as Rt}from"./lib/cli.js";import{readSettings as te,writeSettings as Ot}from"./lib/settings.js";import{readActiveTheme as Mt,setActiveThemeTemplate as Se}from"./lib/theme-store.js";import{isThemeTemplateId as xe,THEME_TEMPLATE_IDS as Lt}from"./lib/themes/index.js";import{installPlugin as Pt,uninstallPlugin as Nt}from"./lib/plugins.js";import{buildCommandbarSessions as O}from"./lib/commandbar.js";import{pinView as Dt,unpinView as Ht,listPinnedViews as Ft}from"./lib/pinned-views.js";import{listWindowLabels as Ut,setWindowLabel as Wt}from"./lib/window-labels.js";import{captureAndStoreWindows as Te,getStoredWindows as be}from"./lib/session-windows.js";import{acquireControlClient as $t,killAllControlClients as Bt}from"./lib/tmux-control.js";import{buildSidebarSessions as Vt}from"./lib/sessions-sidebar.js";import{createQuickCommand as Gt,deleteQuickCommand as zt,listQuickCommands as Ae,updateQuickCommand as Jt}from"./lib/quick-commands.js";import{getSessionPaneTarget as Kt,capturePaneTail as Qt,capturePaneHistoryChunk as Xt,toCrlf as qe}from"./lib/tmux-capture.js";import{readTerminalBufferConfig as Yt}from"./lib/terminal-config.js";import{ImageUploadError as Zt,saveUploadedImage as er}from"./lib/image-upload.js";import{getSystemStatus as tr,getTopProcesses as rr,killProcess as nr}from"./lib/system-monitor.js";import{listSessionWindows as or,selectSessionWindow as sr,newSessionWindow as ir,renameSessionWindow as ar,newTmuxSession as dr,renameSession as cr,killSession as ur,TmuxWindowsError as K}from"./lib/tmux-windows.js";At();const Q=Yt(),m=lt(),g=new mt,M=new ft;let F=!1;const X="tmux-web-token",lr=365;function _e(e){return e==="127.0.0.1"||e==="::1"||e==="::ffff:127.0.0.1"}function Y(e){if(m.security.trustProxy){const t=(e.req.header("x-forwarded-for")||"").split(",")[0].trim();if(t)return t}return e.env?.incoming?.socket?.remoteAddress||"unknown"}function mr(e){if(m.security.trustProxy){const t=(e.headers["x-forwarded-for"]||"").split(",")[0].trim();if(t)return t}return e.socket.remoteAddress||"unknown"}function re(e){const t=e.req.header("authorization");if(t?.startsWith("Bearer "))return t.slice(7);const r=e.req.header("cookie");if(!r)return null;const n=r.match(new RegExp(`(?:^|;\\s*)${X}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function Ce(e){const t=e.headers.authorization;if(typeof t=="string"&&t.startsWith("Bearer "))return t.slice(7);const r=e.headers.cookie;if(typeof r!="string")return null;const n=r.match(new RegExp(`(?:^|;\\s*)${X}=([^;]+)`));return n?decodeURIComponent(n[1]):null}function gn(e){const t=re(e);return t?g.validateToken(t):null}function wn(e){const t=Ce(e);return t?g.validateToken(t):null}function Ie(e,t){const r=e.req.header("x-forwarded-proto")==="https"||e.req.url.startsWith("https:");e.header("Set-Cookie",`${X}=${encodeURIComponent(t)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${lr*86400}${r?"; Secure":""}`)}function fr(e){e.header("Set-Cookie",`${X}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`)}function a(){return async(e,t)=>{const r=re(e);return!r||!g.validateToken(r)?(l("http_unauthorized",{ip:Y(e)}),e.json({error:"unauthorized"},401)):t()}}function pr(e){const t=encodeURIComponent(e.req.url);return e.redirect(`/login?returnTo=${t}`,302)}function R(){return async(e,t)=>{const r=re(e);return!r||!g.validateToken(r)?pr(e):t()}}const U=new Map;function hr(e){let t=0;for(const r of U.values())r.ip===e&&t++;return t}function ne(e,t,r){try{e.close(t,r)}catch{}}function jn(e,t){e.readyState===fe.OPEN&&e.send(JSON.stringify(t))}function yr(e,t){let r="xterm";(t==="ghostty"||t==="xterm")&&(r=t);const n=process.env.TMUX_WEB_TERMINAL_RENDERER?.trim().toLowerCase();(n==="ghostty"||n==="xterm")&&(r=n);for(const i of e)i==="--ghostty"&&(r="ghostty"),i==="--xterm"&&(r="xterm");return r}const gr=7;function Ee(e){return typeof e!="number"||!Number.isFinite(e)?gr:Math.min(365,Math.max(1,Math.round(e)))}const Re=process.argv.slice(2);{const e=Re.filter(t=>t!=="--ghostty"&&t!=="--xterm");if(e.length>0){const[t,r]=e;switch(t){case"add":r||(console.error("usage: tmux-web add <package>"),process.exit(1)),await qt(r),process.exit(0);case"remove":case"rm":r||(console.error("usage: tmux-web remove <package>"),process.exit(1)),await _t(r),process.exit(0);case"list":case"ls":await Ct(),process.exit(0);case"setup":await It(e),process.exit(0);case"theme":await Et(e.slice(1)),process.exit(0);case"help":case"--help":case"-h":ve(),process.exit(0);case"-V":case"--version":case"-v":Rt(),process.exit(0);default:console.error(`unknown argument: ${t}`),ve(),process.exit(1)}}}function k(e,t){e.readyState===fe.OPEN&&e.send(JSON.stringify(t))}const W=new Set,Oe=[];await x.read(),x.data.sessionAccess??=[],x.data.pinnedViews??=[],x.data.watchedPanes??=[],x.data.triggeredTasks??=[],x.data.quickCommands??=[];const oe=await te();let T=await Mt();const v=oe.commandbar===!0,se=yr(Re,oe.terminalRenderer),Me=Ee(oe.scheduleHistoryDays),Le=j.join(process.cwd(),"extensions"),Pe=await jt(Le);for(const e of Pe)e.start&&Oe.push(kt(e.dir,e));const L=new St({db:x,historyRetentionMs:Me*864e5,onMissedTask:e=>console.warn(`[scheduler] dropped missed task ${e.id} (was due ${new Date(e.fireAt).toISOString()})`)});await L.restoreFromDb();const s=new Xe;s.use("*",async(e,t)=>{await t(),e.header("X-Content-Type-Options","nosniff"),e.header("X-Frame-Options","DENY"),e.header("Content-Security-Policy",["default-src 'self'","script-src 'self' 'unsafe-inline'","style-src 'self' 'unsafe-inline'","connect-src 'self' ws: wss: http: https:","img-src 'self' data:","font-src 'self'","worker-src 'self' blob:"].join("; ")),e.header("Referrer-Policy","strict-origin-when-cross-origin")}),s.use("*",async(e,t)=>{const r=e.req.method;if(r==="GET"||r==="HEAD"||r==="OPTIONS")return t();const n=e.req.header("sec-fetch-site");return n&&n!=="same-origin"&&n!=="none"?e.text("cross-site request blocked",403):t()}),vt(s,Le,Pe);const wr=j.dirname(Qe(import.meta.url)),jr=[j.join(wr,"assets"),j.join(process.cwd(),"dist","assets")];s.get("/assets/:file",async e=>{const t=e.req.param("file");if(!/^[a-zA-Z0-9._-]+$/.test(t))return e.notFound();for(const r of jr){const n=j.join(r,t);if(!B(n))continue;const i=await Je(n),o=j.extname(t),d={".css":"text/css; charset=utf-8",".js":"application/javascript; charset=utf-8",".map":"application/json; charset=utf-8"};return e.body(i,200,{"Content-Type":d[o]??"application/octet-stream","Cache-Control":o===".js"||o===".css"?"no-cache":"public, max-age=3600"})}return e.notFound()});const kr=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
3
3
|
<rect x="2.5" y="5.5" width="27" height="21" rx="4" fill="#0d1117" stroke="#7dd3fc" stroke-width="2"/>
|
|
4
4
|
<path d="M8 13l4.2 3.1L8 19.2" fill="none" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
5
|
<line x1="15.5" y1="19.6" x2="22.5" y2="19.6" stroke="#7dd3fc" stroke-width="2.2" stroke-linecap="round"/>
|
|
6
|
-
</svg>`,
|
|
6
|
+
</svg>`,Ne=e=>e.body(kr,200,{"Content-Type":"image/svg+xml; charset=utf-8","Cache-Control":"public, max-age=86400"});s.get("/favicon.svg",Ne),s.get("/favicon.ico",Ne);const vr={name:"tmux-weblink",short_name:"tmux-web",description:"Access your tmux sessions from the browser",start_url:"/",display:"standalone",background_color:"#0d1117",theme_color:"#0d1117",icons:[{src:"/assets/icon-192.png",sizes:"192x192",type:"image/png"},{src:"/assets/icon-512.png",sizes:"512x512",type:"image/png"}]};s.get("/manifest.json",e=>e.json(vr,200,{"Cache-Control":"public, max-age=3600"}));const Sr=`// tmux-weblink Service Worker
|
|
7
7
|
const CACHE = "tmux-weblink-v1";
|
|
8
8
|
const ASSETS = ["/", "/favicon.svg"];
|
|
9
9
|
self.addEventListener("install", (e) => {
|
|
@@ -14,14 +14,14 @@ self.addEventListener("activate", (e) => { e.waitUntil(clients.claim()); });
|
|
|
14
14
|
self.addEventListener("fetch", (e) => {
|
|
15
15
|
e.respondWith(fetch(e.request).catch(() => caches.match(e.request)));
|
|
16
16
|
});
|
|
17
|
-
`;s.get("/sw.js",e=>e.body(Sr,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!m.passwordHash,n=e.req.query("error");return e.html(
|
|
18
|
-
`)){if(!c.trim())continue;const y=c[0],p=c[1],
|
|
19
|
-
`)){if(!y.trim())continue;const p=y.split(" ");if(p.length>=3){const
|
|
20
|
-
`).length;c.additions=
|
|
17
|
+
`;s.get("/sw.js",e=>e.body(Sr,200,{"Content-Type":"application/javascript; charset=utf-8"})),s.get("/login",e=>{const r=e.req.query("setup")==="1"?!0:!m.passwordHash,n=e.req.query("error");return e.html(tt({setupMode:r,error:n?decodeURIComponent(n):void 0,theme:T}))}),s.get("/",R(),e=>{const t=v?O(_(),E()):[],r=we();return e.html(ut({theme:T,commandbarEnabled:v,commandbarSessions:t,fsRoots:r,terminalCfg:Q,renderer:se,scrollback:Q.initialLines+2*Q.historyChunk}))}),s.get("/notes",R(),e=>{const t=v?O(_(),E()):[];return e.html(rt(x.data.notes,T,v,t))}),s.get("/notes/:session",R(),e=>{const t=decodeURIComponent(e.req.param("session")),r=v?O(_(),E()):[];return e.html(nt(t,T,v,r))}),s.get("/schedule",R(),e=>{const t=v?O(_(),E()):[];return e.html(it(L.list(),L.listTriggered(),T,Me,v,t))}),s.get("/history",R(),e=>{const t=_(),r=v?O(t,E()):[],n=new Set(t.map(i=>i.name));return e.html(at(gt(),T,v,r,n))}),s.get("/quick-commands",R(),e=>{const t=v?O(_(),E()):[];return e.html(dt(Ae(),T,v,t))}),s.get("/files",R(),e=>{const t=v?O(_(),E()):[],r=we();return e.html(ct(T,v,t,r))}),s.post("/api/history/clear",a(),async e=>(await wt(),e.json({ok:!0}))),s.get("/api/quick-commands",a(),e=>e.json(Ae())),s.post("/api/quick-commands",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Gt(t);return"error"in r?e.json({error:r.error},400):e.json(r,201)}),s.patch("/api/quick-commands/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=await Jt(e.req.param("id"),t);return"error"in r?e.json({error:r.error},r.status):e.json(r)}),s.delete("/api/quick-commands/:id",a(),async e=>await zt(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.post("/api/auth/password",async e=>{const t=Y(e),r=M.check(t);if(!r.allowed)return l("rate_limited",{ip:t,retryAfterMs:r.retryAfterMs,permanentLock:r.permanentLock}),r.permanentLock?e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403):e.json({error:"Too many attempts",retryAfterMs:r.retryAfterMs},429);let n;try{n=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const i=typeof n.password=="string"?n.password:"";if(!m.passwordHash){if(!m.security.allowRemoteSetup&&!_e(t))return l("setup_rejected_remote",{ip:t}),e.json({error:"First-run setup must be performed from localhost"},403);if(F)return e.json({error:"Password setup in progress"},409);const f=ge(i);if(f)return e.json({error:f},400);F=!0;try{m.passwordHash=await he(i),pe(m),l("password_set",{ip:t})}finally{F=!1}const{plaintext:c}=g.createAccessToken("setup",m.security.tokenTtlDays);return Ie(e,c),e.json({ok:!0,token:c,setupMode:!0})}if(!await ye(i,m.passwordHash)){const f=M.recordFailure(t);return l("auth_failed",{ip:t,method:"password",failures:f.failures,permanentLock:f.permanentLock}),f.permanentLock?(g.revokeAll(),l("permanent_lock",{ip:t,failures:f.failures}),e.json({error:"Server locked after too many failed attempts",permanentLock:!0},403)):f.allowed?e.json({error:"Incorrect password"},401):e.json({error:"Too many attempts",retryAfterMs:f.retryAfterMs},429)}M.recordSuccess(t);const d=`browser-${t}`,{plaintext:w}=g.createAccessToken(d,m.security.tokenTtlDays);return Ie(e,w),l("auth_success",{ip:t,method:"password",tokenName:d}),e.json({ok:!0,token:w})}),s.post("/api/auth/token",a(),async e=>{const t=Y(e);let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const n=typeof r.name=="string"?r.name:`api-${t}`,i=typeof r.ttlDays=="number"?r.ttlDays:m.security.tokenTtlDays,{stored:o,plaintext:d}=g.createAccessToken(n,i);return l("token_created",{ip:t,name:o.name,tokenId:o.id}),e.json({id:o.id,name:o.name,token:d,expiresAt:o.expiresAt})}),s.get("/api/auth/tokens",a(),e=>e.json(g.list().map(t=>({id:t.id,name:t.name,createdAt:t.createdAt,lastUsedAt:t.lastUsedAt,expiresAt:t.expiresAt})))),s.delete("/api/auth/tokens/:id",a(),e=>g.revoke(e.req.param("id"))?(l("token_revoked",{ip:Y(e),tokenId:e.req.param("id")}),e.json({ok:!0})):e.json({error:"not found"},404)),s.post("/api/auth/logout",a(),e=>(fr(e),e.json({ok:!0}))),s.get("/settings",R(),async e=>{const t=await te(),r=t.terminalRenderer??"xterm";return e.html(ot({settings:t,renderer:se,rendererOverridden:se!==r,theme:T,plugins:t.plugins??[],saved:e.req.query("saved")==="1",error:e.req.query("error")?decodeURIComponent(e.req.query("error")):void 0}))}),s.post("/settings",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=await te(),n=t.terminalRenderer==="ghostty"?"ghostty":"xterm",i=t.defaultView==="recent"?"recent":"default",o=Ee(typeof t.scheduleHistoryDays=="string"?Number(t.scheduleHistoryDays):void 0);return await Ot({...r,commandbar:t.commandbar!==void 0,terminalRenderer:n,defaultView:i,scheduleHistoryDays:o}),e.redirect("/settings?saved=1",303)}),s.post("/settings/plugins",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings?error="+encodeURIComponent("invalid form body"),303)}const r=t.action,n=typeof t.pkg=="string"?t.pkg.trim():"";if(!n)return e.redirect("/settings?error="+encodeURIComponent("missing package name"),303);const i=r==="remove"?await Nt(n):r==="add"?await Pt(n):{ok:!1,output:"unknown action"};return i.ok?e.redirect("/settings?saved=1",303):e.redirect("/settings?error="+encodeURIComponent(i.output.slice(0,800)),303)}),s.get("/settings/theme",R(),e=>e.html(st({theme:T,saved:e.req.query("saved")==="1"}))),s.post("/settings/theme",a(),async e=>{let t;try{t=await e.req.parseBody()}catch{return e.redirect("/settings/theme?error=1",303)}const r=t.template;return typeof r!="string"||!xe(r)?e.redirect("/settings/theme",303):(T=await Se(r),e.redirect("/settings/theme?saved=1",303))});const xr={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"};s.get("/api/theme",a(),e=>e.json({active:T.template,templates:Lt.map(t=>({id:t,name:xr[t]}))})),s.post("/api/theme",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=t.template;return typeof r!="string"||!xe(r)?e.json({error:"invalid theme template"},400):(T=await Se(r),e.json({ok:!0,active:T.template}))}),s.get("/api/system/status",a(),e=>e.json(tr())),s.get("/api/system/processes",a(),e=>e.json(rr())),s.post("/api/system/kill",a(),async e=>{const{pid:t}=await e.req.json();if(typeof t!="number"||!Number.isInteger(t)||t<=0)return e.json({error:"invalid pid"},400);const r=nr(t);return r.ok?e.json({ok:!0}):e.json({error:r.error},500)}),s.get("/api/sessions",a(),e=>v?e.json(O(_(),E())):e.json({error:"commandbar disabled"},404)),s.post("/api/sessions/new",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=typeof t.name=="string"?t.name.trim():"";if(!r)return e.json({error:"name is required"},400);if(!/^[a-zA-Z0-9_\-. ]+$/.test(r))return e.json({error:"name contains invalid characters"},400);const n=typeof t.dir=="string"&&t.dir.trim()?t.dir.trim():void 0;if(_().some(o=>o.name===r))return e.json({error:"session already exists"},409);try{return dr(r,n),e.json({ok:!0})}catch(o){const d=o instanceof K?o.message:"failed to create session";return e.json({error:d},500)}}),s.post("/api/sessions/rename",a(),async e=>{try{const{oldName:t,newName:r}=await e.req.json();return!t||!r?e.json({error:"oldName and newName required"},400):/^[a-zA-Z0-9_\-. ]+$/.test(r)?(cr(t,r),e.json({ok:!0})):e.json({error:"invalid characters in name"},400)}catch{return e.json({error:"rename failed"},500)}}),s.post("/api/sessions/kill",a(),async e=>{try{const{name:t}=await e.req.json();return t?(ur(t),e.json({ok:!0})):e.json({error:"name required"},400)}catch{return e.json({error:"kill failed"},500)}}),s.get("/api/fs/session-path",a(),e=>{const t=e.req.query("session");if(!t)return e.json({error:"session is required"},400);try{const r=yt(t),i=r.find(o=>o.active)?.path??r[0]?.path??process.env.HOME??"/";return e.json({path:i})}catch{return e.json({path:process.env.HOME??"/"})}}),s.get("/api/fs/list",a(),e=>{const t=process.env.HOME??"/";let r=e.req.query("path")??t;r.startsWith("~")&&(r=t+r.slice(1)),r.startsWith("/")||(r=j.join(t,r));const n=e.req.query("recursive")==="true";let i=r,o="",d=r.endsWith("/");if(!d)try{d=D(r).isDirectory()}catch{}d||(i=j.dirname(r),o=j.basename(r).toLowerCase());try{const w=ue(i),f=[],c=[];for(const y of w)if(!y.startsWith(".")&&!(o&&!y.toLowerCase().startsWith(o))){try{const p=j.join(i,y);D(p).isDirectory()?(f.push(p),n&&ht(p,f,c,0)):c.push(p)}catch{}if(f.length+c.length>=5e3)break}return e.json({dirs:f,files:c})}catch{return e.json({dirs:[],files:[]})}});function Tr(e){const t={repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0};try{const r=V("git rev-parse --show-toplevel 2>/dev/null",{cwd:e,encoding:"utf-8",timeout:3e3}).trim();if(!r)return t;let n;try{n=V("git branch --show-current 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}catch{n=V("git rev-parse --short HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3}).trim()}n||(n="HEAD");let i="";try{i=V("git status --porcelain 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3})}catch{}const o=[];for(const c of i.split(`
|
|
18
|
+
`)){if(!c.trim())continue;const y=c[0],p=c[1],A=c.substring(3).trim(),C=p!==" "?p:y;o.push({path:A,status:C,staged:y,unstaged:p,additions:0,deletions:0})}const d=new Map;try{const c=V("git diff --numstat HEAD 2>/dev/null",{cwd:r,encoding:"utf-8",timeout:3e3});for(const y of c.split(`
|
|
19
|
+
`)){if(!y.trim())continue;const p=y.split(" ");if(p.length>=3){const A=parseInt(p[0],10)||0,C=parseInt(p[1],10)||0;d.set(p[2],{added:A,deleted:C})}}}catch{}for(const c of o){const y=d.get(c.path);y&&(c.additions=y.added,c.deletions=y.deleted)}let w=0,f=0;for(const c of o)w+=c.additions,f+=c.deletions;for(const c of o)if(c.status==="?"&&c.additions===0)try{const y=j.join(r,c.path),A=le(y,"utf-8").split(`
|
|
20
|
+
`).length;c.additions=A,w+=A}catch{}return{repoRoot:r,branch:n,files:o,linesAdded:w,linesRemoved:f}}catch{return t}}s.get("/api/git/status",a(),e=>{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);try{const r=H(t),n=Tr(r);return e.json(n)}catch(r){return r.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):r.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({repoRoot:null,branch:null,files:[],linesAdded:0,linesRemoved:0})}}),s.get("/api/git/diff",a(),e=>{const t=e.req.query("path"),r=e.req.query("file");if(!t||!r)return e.json({error:"path and file are required"},400);try{const n=H(t);if(console.error("[git/diff] path=%s resolved=%s file=%s",t,n,r),!B(n)||!D(n).isDirectory())return console.error("[git/diff] resolved path does not exist or is not a directory: %s",n),e.json({error:"invalid repository path: "+n},400);try{const i=me("/usr/bin/git",["diff","HEAD","--",r],{cwd:n,encoding:"utf-8",timeout:5e3}),o=me("/usr/bin/git",["diff","--cached","--",r],{cwd:n,encoding:"utf-8",timeout:5e3});return e.json({diff:i,stagedDiff:o})}catch(i){throw console.error("[git/diff] execFileSync failed:",i?.code,i.message),i}}catch(n){return n.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):n.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):(console.error("[git/diff] exception:",n),e.json({error:String(n)},500))}}),s.get("/api/file",a(),e=>{try{const t=e.req.query("path");if(!t)return e.json({error:"path is required"},400);const r=H(t);if(!D(r).isFile())return e.json({error:"not a file"},400);const n=D(r).size;if(n>je)return e.json({error:"file too large",size:n,maxBytes:je},413);const i=le(r,"utf-8");return e.json({path:r,content:i,size:n})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"internal error"},500)}}),s.put("/api/file",a(),async e=>{try{const t=await e.req.json();if(!t.path||typeof t.content!="string")return e.json({error:"path and content are required"},400);const r=H(t.path);return pt(r,t.content),e.json({ok:!0})}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"write failed"},500)}}),s.post("/api/file/delete",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=H(t.path);return D(r).isFile()?(Ge(r),e.json({ok:!0})):e.json({error:"not a file"},400)}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):t.code==="ENOENT"?e.json({error:"not found"},404):e.json({error:"delete failed"},500)}}),s.post("/api/file/touch",a(),async e=>{try{const t=await e.req.json();if(!t.path)return e.json({error:"path is required"},400);const r=H(t.path);return B(r)?e.json({error:"file already exists"},409):(Ve(j.dirname(r),{recursive:!0}),ze(r,"","utf-8"),e.json({ok:!0,path:r}))}catch(t){return t.message==="FS_ROOTS_NOT_CONFIGURED"?e.json({error:"file access not configured"},403):t.message==="PATH_NOT_ALLOWED"?e.json({error:"path not allowed"},403):e.json({error:"touch failed"},500)}});function ie(e){return{...Vt(_(),E(),Ft()),currentSession:e??null}}function De(e){const t=typeof e.sessionName=="string"?e.sessionName.trim():"";if(!t)return{error:"sessionName is required"};if(e.windowIndex===void 0)return{sessionName:t};const r=e.windowIndex;return typeof r!="number"||!Number.isInteger(r)||r<0?{error:"windowIndex must be a non-negative integer"}:{sessionName:t,windowIndex:r}}s.get("/api/sidebar/sessions",a(),e=>{const t=e.req.query("currentSession");return e.json(ie(typeof t=="string"&&t?t:void 0))}),s.get("/api/sidebar/session-windows/:session",a(),e=>{const t=decodeURIComponent(e.req.param("session"));return e.json(be(t))}),s.post("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=De(t);return"error"in r?e.json({error:r.error},400):(await Dt(r.sessionName,r.windowIndex),e.json(ie()))}),s.delete("/api/pinned-views",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=De(t);return"error"in r?e.json({error:r.error},400):(await Ht(r.sessionName,r.windowIndex),e.json(ie()))}),s.get("/api/notes",a(),e=>{const t=[...x.data.notes].sort((r,n)=>(n.updatedAt??0)-(r.updatedAt??0));return e.json(t)}),s.get("/api/notes/:scope",a(),e=>{const t=decodeURIComponent(e.req.param("scope")),r=x.data.notes.find(n=>n.scope===t);return r?e.json(r):e.json(null,404)}),s.put("/api/notes/:scope",a(),async e=>{const t=decodeURIComponent(e.req.param("scope"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}if(typeof r.content!="string")return e.json({error:"content must be string"},400);const n={scope:t,content:r.content,updatedAt:Date.now()},i=x.data.notes.findIndex(o=>o.scope===t);return i>=0?x.data.notes[i]=n:x.data.notes.push(n),await x.write(),e.json({ok:!0})}),s.post("/api/session/:session/upload",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));if(!_().some(o=>o.name===t))return e.json({error:"session not found"},404);let n;try{n=await e.req.parseBody()}catch{return e.json({error:"invalid multipart body"},400)}const i=n.file;if(!(i instanceof File))return e.json({error:"missing file field"},400);try{const o=await i.arrayBuffer(),d=Buffer.from(o),{path:w}=await er(d,i.type||void 0,i.name||void 0);return e.json({path:w})}catch(o){return o instanceof Zt?e.json({error:o.message},o.status):(console.error("[upload]",o),e.json({error:"upload failed"},500))}}),s.get("/api/session/:session/windows",a(),e=>{const t=decodeURIComponent(e.req.param("session")),r=new Map(Ut(t).map(o=>[o.windowIndex,o.label])),n=new Map(be(t).map(o=>[o.index,o])),i=or(t).map(o=>({...o,label:r.get(o.index)??null,worktree:n.get(o.index)?.worktree??!1}));return e.json(i)}),s.post("/api/session/:session/window-label",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);const i=typeof r.label=="string"?r.label:"",o=await Wt(t,n,i);return e.json(o)}),s.post("/api/session/:session/select-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);try{return sr(t,n),e.json({ok:!0})}catch(i){return i instanceof K?e.json({error:i.message},i.status):(console.error("[select-window]",i),e.json({error:"select-window failed"},500))}}),s.post("/api/session/:session/rename-window",a(),async e=>{const t=decodeURIComponent(e.req.param("session"));let r;try{r=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const{windowIndex:n}=r;if(typeof n!="number"||!Number.isInteger(n)||n<0)return e.json({error:"windowIndex must be a non-negative integer"},400);if(typeof r.name!="string"||!r.name.trim())return e.json({error:"name is required"},400);try{return ar(t,n,r.name),Te(t),e.json({ok:!0})}catch(i){return i instanceof K?e.json({error:i.message},i.status):(console.error("[rename-window]",i),e.json({error:"rename-window failed"},500))}}),s.post("/api/session/:session/new-window",a(),e=>{const t=decodeURIComponent(e.req.param("session"));try{return ir(t),Te(t),e.json({ok:!0})}catch(r){return r instanceof K?e.json({error:r.message},r.status):(console.error("[new-window]",r),e.json({error:"new-window failed"},500))}}),s.get("/api/schedule",a(),e=>e.json(L.list(e.req.query("session")))),s.post("/api/schedule",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ke(t);if(r)return e.json({error:r},400);if(!xt(t))return e.json({error:"invalid body"},400);const n=await L.create(t);return e.json({id:n.id,fireAt:n.fireAt})}),s.delete("/api/schedule/:id",a(),async e=>await L.delete(e.req.param("id"))?e.json({ok:!0}):e.json({error:"not found"},404)),s.patch("/api/schedule/:id",a(),async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:"invalid json"},400)}const r=ke(t);if(r)return e.json({error:r},400);if(!Tt(t))return e.json({error:"invalid body"},400);const n=await L.reschedule(e.req.param("id"),t.delayMs);return n?e.json({id:n.id,fireAt:n.fireAt}):e.json({error:"not found"},404)});const br=parseInt(process.env.PORT||"21000",10),Ar=Ye({fetch:s.fetch,port:br,hostname:"0.0.0.0"},e=>{console.log(`tmux-web running at http://${e.port}`)}),ae=new Ze({noServer:!0});function He(e,t,r){e.write(`HTTP/1.1 ${t} ${r}\r
|
|
21
21
|
\r
|
|
22
|
-
`),e.destroy()}Ar.on("upgrade",(e,t,r)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),i=n.pathname.match(/^\/ws\/(.+)$/);if(!i){t.destroy();return}const o=mr(e),d=e.headers.origin,w=m.security.allowedOrigins;if(d&&!(d===`http://${e.headers.host}`||d===`https://${e.headers.host}`)&&(w.length===0||!w.includes(d))){l("ws_rejected_origin",{ip:o,origin:d}),
|
|
23
|
-
`})}}},
|
|
22
|
+
`),e.destroy()}Ar.on("upgrade",(e,t,r)=>{const n=new URL(e.url||"/",`http://${e.headers.host}`),i=n.pathname.match(/^\/ws\/(.+)$/);if(!i){t.destroy();return}const o=mr(e),d=e.headers.origin,w=m.security.allowedOrigins;if(d&&!(d===`http://${e.headers.host}`||d===`https://${e.headers.host}`)&&(w.length===0||!w.includes(d))){l("ws_rejected_origin",{ip:o,origin:d}),He(t,403,"Origin not allowed");return}const f=hr(o);if(f>=m.security.maxConnectionsPerIp){l("ws_rejected_per_ip_cap",{ip:o,liveFromIp:f}),He(t,429,"Too many connections");return}const c=Ce(e)||n.searchParams.get("token")||"",y=c?g.validateToken(c):null;ae.handleUpgrade(e,t,r,p=>{ae.emit("connection",p,e,decodeURIComponent(i[1]),o,y)})}),ae.on("connection",(e,t,r,n,i)=>{const o={ws:e,ip:n,authenticated:!1,authTimeout:null};U.set(e,o),l("ws_connected",{ip:n});let d=null;const{initialLines:w,historyChunk:f,syncIdleMs:c,syncMaxMs:y}=Q;let p=!0,A="",C=null,G=null,z=r,P=null;const J=()=>{C&&(clearTimeout(C),C=null),G&&(clearTimeout(G),G=null)},de=()=>{if(p){J(),p=!1;try{z=Kt(r)}catch{z=r}try{const S=Qt(z,w);k(e,{type:"snapshot",data:qe(S),lines:w})}catch{k(e,{type:"data",data:`\r
|
|
23
|
+
`})}A&&(k(e,{type:"data",data:A}),A="")}},Ue=()=>{p&&(C&&clearTimeout(C),C=setTimeout(de,c))};function ce(){try{d=et.spawn("tmux",["attach-session","-t",r],{name:"xterm-256color",cols:80,rows:24,cwd:process.env.HOME||"/",env:process.env})}catch(u){k(e,{type:"data",data:`\r
|
|
24
24
|
\x1B[31mFailed to attach to tmux session "${r}": ${u.message}\x1B[0m\r
|
|
25
|
-
`}),
|
|
25
|
+
`}),ne(e,1011,"pty spawn failed");return}W.add(d);let S=-1,h="";P=$t(r,({activeIndex:u,windows:b})=>{k(e,{type:"window_changed",activeIndex:u,windows:b});const N=b.map(q=>q.index).join(","),$=u!==S||N!==h;S=u,h=N}),G=setTimeout(de,y),d.onData(u=>{if(p){A+=u,Ue();return}k(e,{type:"data",data:u})}),d.onExit(({exitCode:u})=>{J(),d&&W.delete(d),d=null,k(e,{type:"data",data:`\r
|
|
26
26
|
\x1B[2m--- tmux exited (code ${u}) ---\x1B[0m\r
|
|
27
|
-
`}),
|
|
27
|
+
`}),ne(e,1e3,"pty exited")})}function Z(S,h){o.authenticated=!0,o.authTimeout&&(clearTimeout(o.authTimeout),o.authTimeout=null),l("auth_success",{ip:n,method:S,tokenName:h}),k(e,{type:"auth.ok",setupMode:!m.passwordHash}),ce()}function I(S,h={}){k(e,{type:"auth.failed",message:S,...h})}async function We(S){let h;try{h=JSON.parse(S)}catch{return!1}if(h.type==="auth.token"&&typeof h.token=="string"){const u=g.validateToken(h.token);if(!u){l("token_auth_failed",{ip:n});const b=M.recordFailure(n);return b.permanentLock&&(g.revokeAll(),l("permanent_lock",{ip:n,failures:b.failures})),I("Invalid or expired token",{permanentLock:b.permanentLock,retryAfterMs:b.retryAfterMs}),!0}return g.touch(u.tokenHash),Z("token",u.name),!0}if(h.type==="auth"&&typeof h.password=="string"){const u=M.check(n);if(!u.allowed)return l("rate_limited",{ip:n,retryAfterMs:u.retryAfterMs,permanentLock:u.permanentLock}),I("Too many attempts",{permanentLock:u.permanentLock,retryAfterMs:u.retryAfterMs}),!0;if(!m.passwordHash){if(!m.security.allowRemoteSetup&&!_e(n))return l("setup_rejected_remote",{ip:n}),I("First-run setup must be performed from localhost"),!0;if(F)return I("Password setup in progress"),!0;const q=ge(h.password);if(q)return I(q),!0;F=!0;try{m.passwordHash=await he(h.password),pe(m),l("password_set",{ip:n})}finally{F=!1}const{stored:ee,plaintext:$e}=g.createAccessToken("setup",m.security.tokenTtlDays);return k(e,{type:"auth.ok",setupMode:!0,token:$e}),l("token_created",{ip:n,name:ee.name,tokenId:ee.id}),l("auth_success",{ip:n,method:"password",tokenName:ee.name}),ce(),!0}if(!m.passwordHash)return I("Server not configured"),!0;if(!await ye(h.password,m.passwordHash)){const q=M.recordFailure(n);return l("auth_failed",{ip:n,method:"password",failures:q.failures,permanentLock:q.permanentLock}),q.permanentLock&&(g.revokeAll(),l("permanent_lock",{ip:n,failures:q.failures})),I("Incorrect password",{permanentLock:q.permanentLock,retryAfterMs:q.retryAfterMs}),!0}M.recordSuccess(n);const N=`ws-${n}`,{plaintext:$}=g.createAccessToken(N,m.security.tokenTtlDays);return Z("password",N),k(e,{type:"auth.ok",setupMode:!1,token:$}),!0}return!1}i?(g.touch(i.tokenHash),Z("token",i.name)):(k(e,{type:"auth.required",setupMode:!m.passwordHash}),o.authTimeout=setTimeout(()=>{o.authenticated||(l("auth_timeout",{ip:n}),I("Authentication timeout"),ne(e,4e3,"Auth timeout"))},m.security.authTimeoutMs)),e.on("message",async S=>{const h=typeof S=="string"?S:S.toString("utf-8");if(!o.authenticated){if(h.length>1e6){k(e,{type:"auth.failed",message:"Message too large"});return}await We(h)||I("Authentication required");return}if(!d)return;try{if(bt(h,d))return}catch{return}let u;try{u=JSON.parse(h)}catch{return}if(u.type==="load_history"&&typeof u.before=="number"){const b=Math.max(0,Math.floor(u.before));try{const{data:N,lines:$}=Xt(z,b,f);k(e,{type:"history",data:qe(N),before:b,lines:$})}catch{k(e,{type:"history",data:"",before:b,lines:0})}}}),e.on("close",()=>{o.authTimeout&&clearTimeout(o.authTimeout),U.delete(e),l("ws_disconnected",{ip:n}),J(),P&&(P(),P=null),d&&(W.delete(d),d.kill(),d=null)}),e.on("error",()=>{o.authTimeout&&clearTimeout(o.authTimeout),U.delete(e),J(),P&&(P(),P=null),d&&(W.delete(d),d.kill(),d=null)})});function Fe(){L.cleanup(),Bt();for(const[e,t]of U){try{e.close(1001,"Server shutting down")}catch{}t.authTimeout&&clearTimeout(t.authTimeout)}U.clear();for(const e of W)try{e.kill()}catch{}W.clear();for(const e of Oe)try{e.kill("SIGTERM")}catch{}M.dispose(),process.exit(0)}process.on("SIGINT",Fe),process.on("SIGTERM",Fe),setInterval(()=>{g.purgeExpired()},3600*1e3).unref();
|
package/dist/lib/agents-watch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{execFileSync as k}from"node:child_process";import{capturePaneLines as v}from"./tmux-capture.js";import{listSessionPanes as S}from"./tmux-panes.js";import{getWatchedPanes as B,pruneWatchedPanes as M,recordWatchedPane as D}from"./watched-panes.js";import{identifyAgent as y,detectState as b}from"./agent-detect.js";function C(){const
|
|
2
|
-
`)){const c=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);c&&
|
|
1
|
+
import{execFileSync as k}from"node:child_process";import{capturePaneLines as v}from"./tmux-capture.js";import{listSessionPanes as S}from"./tmux-panes.js";import{getWatchedPanes as B,pruneWatchedPanes as M,recordWatchedPane as D}from"./watched-panes.js";import{identifyAgent as y,detectState as b}from"./agent-detect.js";function C(){const e=new Map;try{const o=k("ps",["-axo","pid=,ppid=,command="],{encoding:"utf-8",timeout:3e3,maxBuffer:8388608});for(const a of o.split(`
|
|
2
|
+
`)){const c=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);c&&e.set(parseInt(c[1],10),{ppid:parseInt(c[2],10),command:c[3]})}}catch{}return e}function A(e,o){if(o.size===0||e<=0)return[];const a=new Map;for(const[n,t]of o){const s=a.get(t.ppid);s?s.push(n):a.set(t.ppid,[n])}const c=[],f=[...a.get(e)??[]],p=o.get(e);p&&c.push(p.command);let m=0;for(;f.length&&m<200;){m++;const n=f.shift(),t=o.get(n);t&&c.push(t.command);const s=a.get(n);s&&f.push(...s)}return c}const l=new Map;function T(e){let o=5381;for(let a=0;a<e.length;a++)o=(o<<5)+o+e.charCodeAt(a)>>>0;return o}async function N(){const e=B();if(e.length===0)return[];const o=new Map;for(const n of e)o.has(n.sessionName)||o.set(n.sessionName,S(n.sessionName));const a=new Set;for(const n of o.values())for(const t of n)a.add(t.paneId);await M(a);for(const n of l.keys())a.has(n)||l.delete(n);const c=C(),f=new Set(e.map(n=>n.paneId)),p=[...e];for(const[n,t]of o){const s=t.find(d=>d.active&&d.windowActive);if(!s||f.has(s.paneId)||!y(s.command,A(s.pid,c)))continue;const u={paneId:s.paneId,sessionName:n,windowIndex:s.windowIndex,paneIndex:s.paneIndex};await D(u),f.add(s.paneId),p.unshift({...u,watchedAt:Date.now()})}const m=[];for(const n of p){const t=o.get(n.sessionName)?.find(h=>h.paneId===n.paneId);if(!t)continue;const s=A(t.pid,c),u=y(t.command,s);if(!u)continue;let d="unknown";try{const h=v(t.target,0);d=b(u,h);const g=T(h),x=l.get(t.paneId);l.set(t.paneId,g),d==="idle"&&x!==void 0&&x!==g&&(d="working")}catch{}m.push({paneId:t.paneId,sessionName:n.sessionName,windowIndex:t.windowIndex,paneIndex:t.paneIndex,agent:u,state:d,watchedAt:n.watchedAt})}return m}let i=null,I=[],P=0,r=null;const W=1500;function w(){P=Date.now(),N().then(e=>{I=e}).catch(()=>{})}function q(){return I}function z(){return i!==null}function F(){if(!i||r)return;const e=Date.now()-P;if(e>=W){w();return}r=setTimeout(()=>{r=null,w()},W-e),typeof r.unref=="function"&&r.unref()}function L(e=3500){i||(w(),i=setInterval(w,e),typeof i.unref=="function"&&i.unref())}function H(){i&&(clearInterval(i),i=null),r&&(clearTimeout(r),r=null),I=[]}export{q as getCachedAgentStatuses,z as isBackgroundWatchRunning,N as probeWatchedPanes,F as requestProbe,L as startBackgroundWatch,H as stopBackgroundWatch};
|
package/dist/lib/tmux-capture.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{execFileSync as f}from"node:child_process";const l=3e3;function g(n){return n.replace(/\r?\n/g,`\r
|
|
2
|
-
`)}function o(n){return f("tmux",n,{encoding:"utf-8",timeout:l}).trimEnd()}function S(n){return o(["display-message","-p","-t",n,"#{session_name}:#{window_index}.#{pane_index}"])}function m(n){const t=o(["display-message","-p","-t",n,"#{history_size}"]),e=parseInt(t,10);return Number.isFinite(e)?e:0}function _(n){return o(["display-message","-p","-t",n,"#{alternate_on}"])==="1"}function c(n,t,e){const i=["capture-pane","-t",n,"-p","-S",String(t)];return e!==void 0&&i.push("-E",String(e)),o(i)}function h(n,t){return c(n,-t)}function y(n,t,e){const i=m(n);if(t>=i)return{data:"",lines:0};const p=-(t+e),a=-(t+1);let r=c(n,p,a);const u=r===""?0:r.split(`
|
|
2
|
+
`)}function o(n){return f("tmux",n,{encoding:"utf-8",timeout:l}).trimEnd()}function S(n){return o(["display-message","-p","-t",n,"#{session_name}:#{window_index}.#{pane_index}"])}function m(n){const t=o(["display-message","-p","-t",n,"#{history_size}"]),e=parseInt(t,10);return Number.isFinite(e)?e:0}function _(n){return o(["display-message","-p","-t",n,"#{alternate_on}"])==="1"}function c(n,t,e){const i=["capture-pane","-t",n,"-p","-e","-S",String(t)];return e!==void 0&&i.push("-E",String(e)),o(i)}function h(n,t){return c(n,-t)}function y(n,t,e){const i=m(n);if(t>=i)return{data:"",lines:0};const p=-(t+e),a=-(t+1);let r=c(n,p,a);const u=r===""?0:r.split(`
|
|
3
3
|
`).length,s=Math.min(e,u,i-t);return s<u&&s>0&&(r=r.split(`
|
|
4
4
|
`).slice(-s).join(`
|
|
5
5
|
`)),{data:r,lines:s}}export{y as capturePaneHistoryChunk,c as capturePaneLines,h as capturePaneTail,m as getPaneHistorySize,S as getSessionPaneTarget,_ as isAlternateScreen,g as toCrlf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{db as a}from"./db.js";const d=50;async function
|
|
1
|
+
import{db as a}from"./db.js";const d=50;async function s(e){const t={...e,watchedAt:Date.now()},n=a.data.watchedPanes.filter(c=>c.paneId!==e.paneId);a.data.watchedPanes=[t,...n].slice(0,d),await a.write()}function h(){return a.data.watchedPanes}async function r(e){const t=a.data.watchedPanes.length;a.data.watchedPanes=a.data.watchedPanes.filter(n=>e.has(n.paneId)),a.data.watchedPanes.length!==t&&await a.write()}export{d as MAX_WATCHED_PANES,h as getWatchedPanes,r as pruneWatchedPanes,s as recordWatchedPane};
|