@dadigua/web-terminal 0.1.3 → 0.1.5
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/API.md +4 -0
- package/README.md +20 -4
- package/config.example.yaml +2 -0
- package/dist/assets/index-Cdq-0BxC.js +28 -0
- package/dist/assets/index-DIMl7N0D.css +1 -0
- package/dist/index.html +2 -2
- package/dist-server/{app-TCUYGJ4K.js → app-FFT46HD7.js} +148 -22
- package/dist-server/{chunk-R33XRLBM.js → chunk-2APXDIFJ.js} +5 -3
- package/dist-server/cli.js +15 -5
- package/dist-server/vscodeInstaller-AIKL2DRR.js +210 -0
- package/package.json +5 -3
- package/release/web-terminal.vsix +0 -0
- package/dist/assets/index-Bv4iPZiS.js +0 -27
- package/dist/assets/index-DAKKO3a9.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;-webkit-user-select:text;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}:root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;color:#d9e5e9;background:#0b1219;font-synthesis:none}*{box-sizing:border-box}body{margin:0}button,input,.draft-input{font:inherit}button{cursor:pointer;border:1px solid #2a3c47;background:#182630;color:#d9e5e9;border-radius:8px;padding:8px 11px;transition:background .15s,border-color .15s}button:hover:not(:disabled){background:#25404a;border-color:#4c7580}button:disabled{opacity:.45;cursor:default}input,.draft-input{min-width:0;border:1px solid #2b414d;background:#111f29;color:#e9f1f3;border-radius:8px;padding:10px 11px;outline:none}input:focus,.draft-input:focus{border-color:#55b5aa;box-shadow:0 0 0 3px #55b5aa22}.primary{background:#4fb6a8;color:#06201f;border-color:#4fb6a8;font-weight:700}.primary:hover:not(:disabled){background:#70d0c2;color:#06201f}.app{display:flex;flex-direction:column;height:var(--app-height,100dvh);min-height:0;position:fixed;top:var(--app-top,0px);left:0;right:0}.brand-mark{display:inline-grid;place-items:center;width:36px;height:36px;border-radius:9px;background:#174a4a;color:#7de8d4;font-weight:800;font-size:19px;font-family:monospace}.tabs-bar{position:relative;display:flex;flex:none;align-items:center;min-width:0;min-height:32px;background:#0d1821;border-bottom:1px solid #263640;padding:3px 5px;gap:3px;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;font-size:12px;container-type:inline-size}.session-list{display:flex;align-items:stretch;gap:2px;overflow-x:auto;overflow-y:hidden;min-width:0;flex:1;scrollbar-width:none}.session-list::-webkit-scrollbar{display:none}.session{display:flex;align-items:center;flex:1 1 124px;min-width:64px;max-width:148px;height:26px;container:session-tab / inline-size;border-radius:4px;background:#13212b}.session.active{background:#263a45;box-shadow:inset 0 -1px #80cdbf}.session-select{display:flex;align-items:center;gap:4px;flex:1;min-width:0;height:26px;text-align:left;background:transparent;border:0;border-radius:4px;padding:3px 4px;overflow:hidden}.session-select strong{flex:1;min-width:0;font-size:12px;font-weight:400;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-select small{flex:0 1 auto;font-size:11px;color:#849da7;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:44px}.session .end,.tab-more{display:grid;place-items:center;flex:none;width:16px;height:22px;border:0;background:transparent;font-size:15px;line-height:1;color:#9ab0bc;padding:0;border-radius:3px;opacity:0}.tab-more{display:none}.session:hover .tab-more,.session:focus-within .tab-more{display:grid;opacity:1}.session:hover .end,.session:focus-within .end,.session.active .end{opacity:1}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#6e8290;flex:none}.dot.live{background:#64d6ae;box-shadow:0 0 0 2px #64d6ae20}.dot.offline{background:transparent;border:1px solid #dc8c7f}.command-icon{flex:none;font-family:monospace;font-size:12px;color:#b5d8d2}.command-icon.claude{color:#db997c}.tab-actions{display:flex;flex:none;gap:1px}.tab-actions button{display:grid;place-items:center;min-width:26px;height:26px;border:0;background:transparent;padding:3px;font-size:18px;border-radius:4px}.tab-actions svg{width:15px;height:15px}.workspace{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column}.session-group{display:flex;align-items:center;gap:2px;flex:0 1 auto;min-width:min-content;box-shadow:inset 0 -2px var(--group-color);padding:0 1px}.session-group.collapsed{flex:none;box-shadow:none}.session-group-header{display:flex;align-items:center;flex:none;max-width:136px;gap:1px}.session-group-label{display:flex;align-items:center;gap:4px;height:24px;min-width:0;max-width:122px;padding:2px 5px;border:1px solid transparent;border-radius:4px;background:var(--group-color);color:#101821;font-size:12px;font-weight:500;white-space:nowrap}.session-group-label:hover:not(:disabled){background:var(--group-color);border-color:#e4eef8}.session-group-label.active{border-color:#edf7ff;box-shadow:0 0 0 1px #edf7ff33}.group-name{overflow:hidden;text-overflow:ellipsis;min-width:0}.group-count{font-size:10px;opacity:.8}.group-chevron{font-size:13px;flex:none;line-height:14px}.group-output{width:8px;height:8px;flex:none;border-radius:50%;border:1.5px solid #10182138;border-top-color:#101821;border-right-color:#101821;animation:output-spin .7s linear infinite}.group-more{flex:none;width:14px;height:22px;padding:0;border:0;border-radius:3px;background:transparent;font-size:15px;color:var(--group-color);opacity:0}.session-group-header:hover .group-more,.session-group-header:focus-within .group-more{opacity:1}.tabs-bar button:focus-visible,.tab-menu button:focus-visible{outline:1px solid #9ed8ee;outline-offset:-1px}.tab-menu{position:fixed;z-index:120;width:218px;max-width:calc(100vw - 16px);max-height:calc(100dvh - 16px);overflow-y:auto;padding:4px;border:1px solid #39515f;border-radius:6px;background:#172630;box-shadow:0 6px 24px #0007;font:12px/18px ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;color:#d9e5e9}.tab-menu button{display:flex;align-items:center;gap:7px;width:100%;min-height:28px;text-align:left;padding:4px 7px;border:0;border-radius:3px;background:transparent;font-size:12px;overflow-wrap:anywhere}.tab-menu button:hover:not(:disabled){background:#29414e}.tab-menu-caption{padding:6px 7px 2px;color:#91a8b5;font-size:11px}.tab-menu hr{border:0;border-top:1px solid #30444f;margin:4px}.menu-color{flex:none;width:8px;height:8px;border-radius:50%}.menu-check{margin-left:auto}.session-group-dialog{width:min(92vw,320px);padding:18px}.session-group-dialog h2{font-size:15px}.session-group-dialog label{display:block;font-size:12px;margin:12px 0 5px}.session-group-dialog input{width:100%;font-size:12px;padding:7px 9px}.group-colors{display:flex;gap:5px;flex-wrap:wrap;border:0;padding:0;margin:14px 0}.group-colors legend{font-size:12px;margin-bottom:8px}.group-colors button{display:grid;place-items:center;width:23px;height:23px;min-width:23px;padding:0;border:2px solid transparent;border-radius:50%;color:#101821;font-size:12px}.group-colors button[aria-pressed=true]{border-color:#e8f3ff}.session-group-dialog .session-close-actions button{font-size:12px;padding:6px 10px}@container session-tab (max-width:115px){.session-select small{display:none}}@container (max-width:360px){.session{max-width:130px}}@media(hover:none)and (pointer:coarse){.tabs-bar{min-height:38px}.session,.session-select{height:32px}.session{min-width:94px}.session .end,.tab-more,.group-more{display:grid;width:22px;height:30px;opacity:1}.session-group-label{height:30px}.tab-actions button{height:32px;min-width:30px}}.content{flex:1;min-height:0;display:flex}.terminal-pane{flex:1;min-width:0;display:flex;flex-direction:column;background:#101821}.terminal-wrap{flex:1;min-height:0;padding:15px 12px 4px;overflow:auto;overscroll-behavior:contain}.terminal-host{width:100%;height:100%;min-height:0}.xterm{height:100%}.draft-row{display:flex;min-width:0}.draft-row .draft-input{flex:1;font-size:12px}.mobile-keys{display:none}.upload-state{display:flex;align-items:center;gap:10px;justify-content:space-between;background:#18313a;padding:9px 14px;color:#b5e8df;font-size:12px;word-break:break-all}.upload-state.error,.error{color:#ffb3aa}.upload-state button{margin-left:auto;font-size:11px;padding:4px 7px;white-space:nowrap}.hover-card{position:fixed;z-index:10;width:220px;padding:7px;background:#1a2c35;border:1px solid #45616b;border-radius:9px;box-shadow:0 12px 35px #0009;pointer-events:none}.hover-card img{display:block;width:100%;max-height:165px;object-fit:contain}.hover-card span{display:block;font-size:11px;margin-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hidden{display:none}.auth-shell{min-height:100dvh;display:grid;place-items:center;padding:20px;background:radial-gradient(circle at 50% 28%,#163039,#0b1219 55%)}.auth-card{width:min(100%,390px);padding:32px;background:#12212b;border:1px solid #2b414b;border-radius:17px;box-shadow:0 20px 70px #0005;display:grid;gap:13px}.auth-card h1{font-size:23px;margin:8px 0 0}.auth-card p{font-size:13px;color:#96adb7;line-height:1.5;margin:0 0 14px}.auth-card label{font-size:12px;color:#a7bec6}.auth-card .primary{margin-top:6px}.auth-card .error{font-size:12px}@media(max-width:760px){.content{position:relative}.terminal-wrap{padding:8px 5px 0}}.empty-session{position:absolute;z-index:2;inset:0 0 75px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;background:#101821;color:#d5e7e9}.empty-session strong{font-size:18px}.empty-session p{font-size:13px;color:#89a3ad}.empty-session button{margin-top:10px}.terminal-pane{position:relative}.workspace{min-height:0}.upload-thumbnail{width:38px;height:30px;object-fit:cover;border-radius:4px;flex:none}.upload-state>span{flex:1}.upload-state button{margin-left:0}@media(hover:none)and (pointer:coarse){.mobile-keys{display:flex;gap:4px;padding:6px;flex-wrap:wrap;border-top:1px solid #243640}.mobile-keys button{flex:1;min-width:34px;min-height:42px;font-size:12px;padding:6px 4px;touch-action:manipulation}}.session-close-dialog{width:min(92vw,380px);box-sizing:border-box;border:1px solid #38535f;border-radius:12px;padding:22px;background:#12212b;color:#d9e5e9;box-shadow:0 20px 70px #0008}.session-close-dialog::backdrop{background:#0008}.session-close-dialog h2{font-size:17px;margin:0 0 12px;overflow-wrap:anywhere}.session-close-dialog p{font-size:13px;line-height:1.6;color:#a7bec6}.session-close-dialog .error{color:#ffb3aa}.session-close-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.session-ended-dialog{width:min(92vw,440px)}.session-ended-path{overflow-wrap:anywhere;font-family:ui-monospace,monospace}.session-ended-dialog .session-close-actions{flex-wrap:wrap}.terminal-progress{display:inline-flex;flex:none;width:14px;height:14px;color:#7bdfcd}.terminal-progress svg{transform:rotate(-90deg)}.progress-track{opacity:.2}.progress-state-2{color:#ed9388}.progress-state-4{color:#e3b967}.progress-state-3 svg{animation:output-spin .7s linear infinite}@media(prefers-reduced-motion:reduce){.progress-state-3 svg{animation:none}}.message-layer{position:fixed;z-index:100;top:max(16px,env(safe-area-inset-top));left:0;right:0;display:flex;justify-content:center;padding:0 16px;pointer-events:none}.message-toast{display:flex;align-items:center;gap:10px;max-width:min(520px,100%);padding:11px 14px;border:1px solid #3a515e;border-radius:9px;background:#20323e;color:#e7f0f3;box-shadow:0 6px 24px #0006;font-size:13px;line-height:1.5;pointer-events:auto;animation:message-appear .16s ease-out}.message-icon{flex:none;display:grid;place-items:center;width:18px;height:18px;border-radius:50%;background:#58c1b2;color:#09231f;font-weight:700;font-family:serif}.message-text{overflow-wrap:anywhere;min-width:0}.message-close{flex:none;padding:0 3px;border:0;background:transparent;color:#9fb5c0;font-size:19px;line-height:22px}@keyframes message-appear{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@media(prefers-reduced-motion:reduce){.message-toast{animation:none}}.directory-picker{width:min(560px,calc(100vw - 24px));max-height:calc(100dvh - 24px);border:1px solid #38535f;border-radius:12px;padding:18px;background:#12212b;color:#d9e5e9;box-shadow:0 20px 70px #0008;overflow:auto}.directory-picker::backdrop{background:#0007}.directory-picker header,.directory-picker footer{display:flex;align-items:center;justify-content:space-between;gap:8px}.directory-picker header button{background:transparent;border:0;font-size:20px}.directory-picker footer{justify-content:flex-end;margin-top:14px}.directory-path{display:flex;gap:6px;margin:14px 0 8px}.directory-path input{flex:1;width:0;font-size:12px}.directory-path button{padding:7px 9px;flex:none}.directory-filter{width:100%;font-size:12px}.directory-entries{height:min(260px,35dvh);overflow:auto;margin-top:8px}.directory-entries button{display:flex;align-items:center;gap:10px;width:100%;text-align:left;border:0;background:transparent;border-radius:5px;font-size:13px;overflow-wrap:anywhere}.directory-entries button span:last-child{margin-left:auto}.directory-entries p{font-size:12px;color:#8fa7af;padding:12px}.workspace-shortcuts small{display:block;color:#8fa7af;margin-bottom:6px}.workspace-shortcuts>div{display:flex;flex-wrap:wrap;gap:6px;max-height:100px;overflow:auto}.workspace-shortcuts button{font-size:12px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.directory-picker .error{font-size:12px;overflow-wrap:anywhere}@media(hover:none)and (pointer:coarse){.draft-row .draft-input{font-size:16px}.mobile-keys{flex-wrap:nowrap;overflow-x:auto;flex:none}.mobile-keys button{min-width:38px}.app .terminal-pane{min-height:0}}.draft-input{min-height:40px;max-height:100px;line-height:20px;white-space:pre-wrap;overflow-wrap:anywhere;overflow-y:auto;cursor:text}.draft-input:empty:before{content:attr(data-placeholder);color:#78909c;pointer-events:none}.session-pane[hidden]{display:none}.composer-dock{position:relative;flex:none;height:calc(112px + env(safe-area-inset-bottom,0px));container-type:inline-size;z-index:6}.composer-panel{position:absolute;left:12px;right:12px;bottom:max(10px,env(safe-area-inset-bottom));display:flex;flex-direction:column;gap:8px;padding:8px;border:1px solid #304953;border-radius:13px;background:#12232c;box-shadow:0 -6px 22px #0003}.composer-panel:focus-within{border-color:#4b978f;box-shadow:0 -6px 22px #0003,0 0 0 2px #4fb6a812}.composer-panel .draft-input{width:100%;min-height:38px;max-height:min(236px,max(38px,calc(var(--app-height,100dvh) - 180px)));padding:8px;border:0;border-radius:6px;background:transparent;font-size:14px;line-height:22px;box-shadow:none;scrollbar-width:thin;overscroll-behavior:contain}.composer-actions,.composer-tools{display:flex;align-items:center;gap:4px}.composer-actions{gap:8px;min-height:32px}.composer-tools{flex:none}.composer-tools button{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:4px 7px;border:0;border-radius:6px;background:transparent;color:#96b0b9;font-size:11px}.composer-tools button:hover:not(:disabled){background:#223e48;color:#def3ee}.composer-divider{height:14px;width:1px;background:#30444d;margin:0 3px}.composer-hint{flex:1;text-align:right;color:#78939e;font-size:10px;white-space:nowrap}.composer-send{display:inline-flex;align-items:center;justify-content:center;gap:7px;margin-left:auto;min-width:70px;height:32px;padding:6px 11px;border:1px solid #4fb6a8;border-radius:8px;background:#4fb6a8;color:#062c28;font-size:12px;font-weight:600}.composer-send:hover:not(:disabled){border-color:#76d5c5;background:#76d5c5}.composer-send:disabled{opacity:1;border-color:#2c434c;background:#1a3039;color:#6c8791}.composer-attachments{display:flex;align-items:center;gap:8px;padding:6px;border:1px solid #304953;border-radius:11px;background:#142a31;min-width:0}.attachment-count{display:inline-flex;align-items:center;gap:3px;flex:none;font-size:12px;color:#b5d8d2;white-space:nowrap}.attachment-list{display:flex;gap:6px;min-width:0;flex:1;overflow-x:auto;scrollbar-width:thin}.attachment-card{flex:none;width:112px;padding:4px;border:1px solid #39545e;border-radius:8px;background:#10212a;overflow:hidden}.attachment-card .upload-thumbnail,.attachment-text{display:block;width:100%;height:76px;margin:0;border:1px solid #2b414d;border-radius:5px;background:#0b1821;object-fit:cover}.attachment-text{padding:5px;font:9px/12px ui-monospace,monospace;white-space:pre-wrap;overflow:hidden;overflow-wrap:anywhere;color:#b5d8d2}.attachment-caption{display:flex;align-items:center;gap:3px;height:23px;font-size:11px}.attachment-caption span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.attachment-caption button{border:0;background:transparent;padding:0 3px;font-size:17px;color:#96b0b9}.attachment-status{display:flex;align-items:center;justify-content:space-between;font-size:9px;line-height:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#86a59f}.attachment-status button{border:0;background:transparent;font-size:10px;padding:0 2px}.attachment-card.error{border-color:#bf766b}.attachment-card.error .attachment-status{color:#ffb3aa}.attachment-clear{flex:none;border:0;background:#263e42;font-size:11px;padding:5px 7px;white-space:nowrap}.composer-attachments{max-height:160px}@container (max-width:360px){.composer-attachments{gap:4px}.attachment-count{font-size:10px}.attachment-clear{font-size:10px;padding:5px 4px}}.composer-panel .mobile-keys{padding:0;border:0}@container (max-width:480px){.composer-hint{display:none}}@media(hover:none)and (pointer:coarse){.composer-dock{height:calc(124px + env(safe-area-inset-bottom,0px))}.composer-panel{left:4px;right:4px;bottom:max(6px,env(safe-area-inset-bottom));gap:2px;padding:4px;border-radius:10px}.composer-panel .draft-input{min-height:32px;padding:4px 6px;font-size:16px;line-height:24px}.composer-actions{gap:4px}.composer-tools{gap:2px}.composer-tools button{height:36px;min-width:36px}.composer-divider{margin:0 1px}.composer-send{height:36px;min-width:64px;padding:6px 8px}.composer-panel .mobile-keys{gap:2px}.composer-panel .mobile-keys button{min-height:36px;border-color:transparent;background:transparent;border-radius:6px}.composer-panel .mobile-keys button:hover:not(:disabled){background:#223e48}}.client-settings label{display:block;margin:14px 0 8px;font-size:13px}.client-settings input{width:100%}.font-preview{padding:12px;border:1px solid #304953;border-radius:8px;overflow-wrap:anywhere}.terminal-selection-handle{position:absolute;z-index:15;width:40px;height:44px;padding:0;border:0;background:transparent;border-radius:0;touch-action:none;cursor:grab}.terminal-selection-handle:before{content:"";position:absolute;left:19px;top:0;width:2px;height:22px;background:#7bdfcd}.terminal-selection-handle:after{content:"";position:absolute;left:12px;top:18px;width:16px;height:16px;border-radius:50%;background:#7bdfcd;border:2px solid #101821}.terminal-selection-handle:hover:not(:disabled){background:transparent}.terminal-touch-actions{position:absolute;z-index:16;display:flex;gap:4px;padding:4px;border:1px solid #42616c;border-radius:9px;background:#182b35;box-shadow:0 4px 18px #0006;touch-action:none}.terminal-touch-actions button{min-width:48px;min-height:40px;padding:6px 10px;font-size:13px}.terminal-touch-actions[hidden],.terminal-selection-handle[hidden]{display:none}.client-settings{width:min(92vw,520px);max-height:calc(var(--app-height,100dvh) - 32px);overflow:auto}.settings-section{border-top:1px solid #304953;margin-top:18px;padding-top:16px}.settings-section h3{font-size:14px;margin:0 0 10px}.client-settings select{width:100%;padding:10px 11px;border:1px solid #38535f;border-radius:8px;background:#111f29;color:#e9f1f3;font:inherit}.client-settings .settings-section p{margin:8px 0 12px}.tab-actions button[aria-label=设置]{display:inline-flex;align-items:center;justify-content:center}.composer-tools .copy-mode-toggle[aria-pressed=true]{background:#28594f;color:#a6f3df;box-shadow:inset 0 0 0 1px #5abfa9}.dot.live.output-active{background:transparent;border:1.5px solid #64d6ae35;border-top-color:#8df8d4;border-right-color:#8df8d4;box-shadow:0 0 5px #64d6ae40;animation:output-spin .7s linear infinite}@keyframes output-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.dot.live.output-active,.group-output{animation:none}.dot.live.output-active{border-color:#8df8d4}.group-output{border-color:#101821}}.file-preview-dialog{width:min(960px,calc(100vw - 32px));max-height:calc(var(--app-height,100dvh) - 32px);padding:18px;border:1px solid #38535f;border-radius:13px;background:#12212b;color:#d9e5e9;box-shadow:0 20px 70px #0008;overflow:auto}.file-preview-dialog::backdrop{background:#0009}.file-preview-dialog header,.file-preview-dialog footer{display:flex;align-items:center;gap:10px}.file-preview-dialog header h2{min-width:0;flex:1;font-size:17px;overflow-wrap:anywhere;margin:0}.file-preview-dialog header button{border:0;background:transparent;font-size:22px;padding:2px 8px}.file-preview-dialog .file-path{font-size:11px;color:#8ea7b0;overflow-wrap:anywhere;line-height:1.5}.file-preview-content{max-height:calc(var(--app-height,100dvh) - 210px);min-height:80px;overflow:auto;border:1px solid #2b414d;border-radius:8px;background:#0b1821}.file-preview-content pre{margin:0;padding:14px;font:12px/1.6 ui-monospace,monospace;white-space:pre;tab-size:2}.file-preview-content img{display:block;max-width:100%;max-height:calc(var(--app-height,100dvh) - 230px);object-fit:contain;margin:auto}.file-preview-content>p{margin:16px;font-size:13px;overflow-wrap:anywhere}.file-preview-dialog footer{justify-content:flex-end;margin-top:14px;font-size:12px}.file-preview-dialog footer span{margin-right:auto;color:#8ea7b0}.file-preview-dialog footer button{font-size:12px}.session[draggable=true],.session-group-header[draggable=true]{cursor:grab}.session.dragging,.session-group.dragging{opacity:.45}.session.drop-before{box-shadow:inset 2px 0 #a3e4d5}.session.drop-after{box-shadow:inset -2px 0 #a3e4d5}.session-group-header.drop-group{outline:2px solid var(--group-color);outline-offset:1px;border-radius:4px}.session-list.drop-end:after{content:"";flex:none;width:2px;background:#a3e4d5}
|
package/dist/index.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta name="theme-color" content="#101419"/><link rel="icon" href="/favicon.svg"/><title>Web Terminal</title> <script type="module" crossorigin src="/assets/index-
|
|
3
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
2
|
+
<html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta name="theme-color" content="#101419"/><link rel="icon" href="/favicon.svg"/><title>Web Terminal</title> <script type="module" crossorigin src="/assets/index-Cdq-0BxC.js"></script>
|
|
3
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DIMl7N0D.css">
|
|
4
4
|
</head><body><div id="root"></div></body></html>
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
serviceName,
|
|
3
3
|
serviceProtocol,
|
|
4
4
|
serviceVersion
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2APXDIFJ.js";
|
|
6
6
|
|
|
7
7
|
// src/server/app.ts
|
|
8
8
|
import express from "express";
|
|
@@ -44,9 +44,10 @@ function authenticate(token) {
|
|
|
44
44
|
|
|
45
45
|
// src/server/files.ts
|
|
46
46
|
import { constants } from "node:fs";
|
|
47
|
-
import { mkdir, readdir, open, realpath,
|
|
47
|
+
import { mkdir, readdir, open, realpath, unlink, writeFile, appendFile, readFile, rm } from "node:fs/promises";
|
|
48
48
|
import { resolve, basename, dirname, join } from "node:path";
|
|
49
|
-
import {
|
|
49
|
+
import { randomBytes } from "node:crypto";
|
|
50
|
+
import { parse, stringify } from "yaml";
|
|
50
51
|
import sharp from "sharp";
|
|
51
52
|
var HttpError = class extends Error {
|
|
52
53
|
constructor(status, message) {
|
|
@@ -129,15 +130,43 @@ var Files = class {
|
|
|
129
130
|
if (!meta.width || !meta.height) throw new HttpError(415, "\u56FE\u7247\u65E0\u6CD5\u89E3\u7801");
|
|
130
131
|
const folder = join(this.config.dataDir, "uploads", sessionId);
|
|
131
132
|
await mkdir(folder, { recursive: true, mode: 448 });
|
|
133
|
+
await mkdir(this.config.uploadDir, { recursive: true, mode: 448 });
|
|
132
134
|
const ext = { "image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp", "image/gif": ".gif" }[mime];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
let path;
|
|
136
|
+
for (; ; ) {
|
|
137
|
+
path = join(this.config.uploadDir, `${randomBytes(6).toString("hex")}${ext}`);
|
|
138
|
+
try {
|
|
139
|
+
await writeFile(path, bytes, { flag: "wx", mode: 384 });
|
|
140
|
+
break;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error.code !== "EEXIST") throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await appendFile(join(folder, "files.yaml"), stringify([path]), { mode: 384 });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
await unlink(path);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
135
151
|
return { path, name: basename(originalName).replace(/[\x00-\x1f\x7f]/g, "").slice(0, 160) || basename(path), size: bytes.length, mime, isImage: true, width: meta.width, height: meta.height };
|
|
136
152
|
} finally {
|
|
137
153
|
await unlink(temporary).catch(() => {
|
|
138
154
|
});
|
|
139
155
|
}
|
|
140
156
|
}
|
|
157
|
+
async removeUploads(sessionId) {
|
|
158
|
+
const folder = join(this.config.dataDir, "uploads", sessionId);
|
|
159
|
+
let paths;
|
|
160
|
+
try {
|
|
161
|
+
paths = parse(await readFile(join(folder, "files.yaml"), "utf8"));
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error.code !== "ENOENT") throw error;
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(paths)) {
|
|
166
|
+
await Promise.all(paths.filter((path) => typeof path === "string" && /^[a-f0-9]{12}\.(?:png|jpg|webp|gif)$/.test(basename(path))).map((path) => rm(path, { force: true })));
|
|
167
|
+
}
|
|
168
|
+
await rm(folder, { recursive: true, force: true });
|
|
169
|
+
}
|
|
141
170
|
};
|
|
142
171
|
function imageMime(bytes) {
|
|
143
172
|
if (bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) return "image/png";
|
|
@@ -148,10 +177,9 @@ function imageMime(bytes) {
|
|
|
148
177
|
|
|
149
178
|
// src/server/sessions.ts
|
|
150
179
|
import { readFileSync, writeFileSync, renameSync, existsSync } from "node:fs";
|
|
151
|
-
import { rm } from "node:fs/promises";
|
|
152
180
|
import { join as join2 } from "node:path";
|
|
153
|
-
import { randomUUID
|
|
154
|
-
import { parse, stringify } from "yaml";
|
|
181
|
+
import { randomUUID } from "node:crypto";
|
|
182
|
+
import { parse as parse2, stringify as stringify2 } from "yaml";
|
|
155
183
|
import * as pty from "node-pty";
|
|
156
184
|
|
|
157
185
|
// src/shared/terminalInput.ts
|
|
@@ -161,12 +189,12 @@ function pasteText(text, bracketedPaste) {
|
|
|
161
189
|
}
|
|
162
190
|
|
|
163
191
|
// src/server/processInfo.ts
|
|
164
|
-
import { readFile, readlink } from "node:fs/promises";
|
|
192
|
+
import { readFile as readFile2, readlink } from "node:fs/promises";
|
|
165
193
|
import { basename as basename2 } from "node:path";
|
|
166
194
|
async function foregroundInfo(pid, fallback) {
|
|
167
195
|
let foreground = pid;
|
|
168
196
|
try {
|
|
169
|
-
const stat = await
|
|
197
|
+
const stat = await readFile2(`/proc/${pid}/stat`, "utf8");
|
|
170
198
|
const group = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[5]);
|
|
171
199
|
if (Number.isInteger(group) && group > 0) foreground = group;
|
|
172
200
|
} catch {
|
|
@@ -182,7 +210,7 @@ async function foregroundInfo(pid, fallback) {
|
|
|
182
210
|
if (/\/claude\/versions\//.test(executable)) processName = "claude";
|
|
183
211
|
else if (basename2(executable) === "codex") processName = "codex";
|
|
184
212
|
else if (/^(node|nodejs|bun)$/.test(basename2(executable))) {
|
|
185
|
-
const argv = (await
|
|
213
|
+
const argv = (await readFile2(`/proc/${foreground}/cmdline`, "utf8")).split("\0", 2);
|
|
186
214
|
if (/[/\\](?:@anthropic-ai[/\\]claude-code|claude-code)[/\\]/.test(argv[1] || "")) processName = "claude";
|
|
187
215
|
else if (/[/\\]@openai[/\\]codex[/\\]/.test(argv[1] || "")) processName = "codex";
|
|
188
216
|
}
|
|
@@ -261,10 +289,29 @@ var TerminalScreen = class {
|
|
|
261
289
|
cursorVisible = true;
|
|
262
290
|
mouseEncoding = 0;
|
|
263
291
|
margins = [1, 30];
|
|
264
|
-
|
|
292
|
+
progress;
|
|
293
|
+
constructor(onTitle, onResponse, onProgress = () => {
|
|
294
|
+
}) {
|
|
265
295
|
this.terminal.loadAddon(this.serializer);
|
|
266
296
|
this.terminal.onTitleChange((title) => onTitle(title.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 512)));
|
|
267
297
|
this.terminal.onData(onResponse);
|
|
298
|
+
const setProgress = (progress) => {
|
|
299
|
+
this.progress = progress;
|
|
300
|
+
onProgress(progress);
|
|
301
|
+
};
|
|
302
|
+
this.terminal.parser.registerOscHandler(9, (data) => {
|
|
303
|
+
if (!data.startsWith("4;")) return false;
|
|
304
|
+
const match = /^4;([0-4])(?:;(\d+))?$/.exec(data);
|
|
305
|
+
if (!match) return true;
|
|
306
|
+
const state = Number(match[1]);
|
|
307
|
+
if (state === 0) setProgress(void 0);
|
|
308
|
+
else {
|
|
309
|
+
const previous = this.progress?.value ?? 0;
|
|
310
|
+
const value = state === 3 ? previous : match[2] !== void 0 ? Math.min(100, Number(match[2])) : state === 1 ? 0 : previous;
|
|
311
|
+
setProgress({ state, value });
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
});
|
|
268
315
|
for (const [code, color] of [[10, "d8d8/e3e3/e8e8"], [11, "1010/1818/2121"]]) {
|
|
269
316
|
this.terminal.parser.registerOscHandler(code, (data) => {
|
|
270
317
|
if (data !== "?") return false;
|
|
@@ -286,6 +333,7 @@ var TerminalScreen = class {
|
|
|
286
333
|
this.cursorVisible = true;
|
|
287
334
|
this.mouseEncoding = 0;
|
|
288
335
|
this.margins = [1, this.terminal.rows];
|
|
336
|
+
setProgress(void 0);
|
|
289
337
|
return false;
|
|
290
338
|
});
|
|
291
339
|
}
|
|
@@ -344,12 +392,12 @@ var Sessions = class {
|
|
|
344
392
|
this.files = files;
|
|
345
393
|
const saved = join2(config.dataDir, "sessions.yaml");
|
|
346
394
|
if (existsSync(saved)) {
|
|
347
|
-
const records =
|
|
395
|
+
const records = parse2(readFileSync(saved, "utf8"));
|
|
348
396
|
if (!Array.isArray(records)) throw new Error("sessions.yaml \u683C\u5F0F\u65E0\u6548");
|
|
349
397
|
for (const record of records) {
|
|
350
398
|
if (!/^[a-f0-9-]{36}$/.test(record.id)) continue;
|
|
351
399
|
const { id, name, cwd, createdAt } = record;
|
|
352
|
-
const info = { id, name, cwd, createdAt, running: false };
|
|
400
|
+
const info = { id, name, cwd, createdAt, running: false, outputActive: false };
|
|
353
401
|
this.entries.set(info.id, this.makeSession(info));
|
|
354
402
|
}
|
|
355
403
|
}
|
|
@@ -358,18 +406,29 @@ var Sessions = class {
|
|
|
358
406
|
files;
|
|
359
407
|
entries = /* @__PURE__ */ new Map();
|
|
360
408
|
shuttingDown = false;
|
|
409
|
+
watchers = /* @__PURE__ */ new Set();
|
|
410
|
+
refreshTimer;
|
|
411
|
+
refreshing = false;
|
|
361
412
|
makeSession(info) {
|
|
362
413
|
const session = { info, clients: /* @__PURE__ */ new Map(), pendingBytes: 0, screen: new TerminalScreen(
|
|
363
414
|
(title) => {
|
|
364
|
-
info.title
|
|
415
|
+
if (info.title !== title) {
|
|
416
|
+
info.title = title;
|
|
417
|
+
this.publish();
|
|
418
|
+
}
|
|
365
419
|
},
|
|
366
|
-
(data) => session.process?.write(data)
|
|
420
|
+
(data) => session.process?.write(data),
|
|
421
|
+
(progress) => {
|
|
422
|
+
if (info.progress?.state === progress?.state && info.progress?.value === progress?.value) return;
|
|
423
|
+
info.progress = progress;
|
|
424
|
+
this.publish();
|
|
425
|
+
}
|
|
367
426
|
) };
|
|
368
427
|
return session;
|
|
369
428
|
}
|
|
370
429
|
save() {
|
|
371
430
|
const file = join2(this.config.dataDir, "sessions.yaml");
|
|
372
|
-
writeFileSync(`${file}.tmp`,
|
|
431
|
+
writeFileSync(`${file}.tmp`, stringify2([...this.entries.values()].map((s) => s.info)), { mode: 384 });
|
|
373
432
|
renameSync(`${file}.tmp`, file);
|
|
374
433
|
}
|
|
375
434
|
get(id) {
|
|
@@ -392,13 +451,59 @@ var Sessions = class {
|
|
|
392
451
|
async list() {
|
|
393
452
|
return Promise.all([...this.entries.keys()].map((id) => this.current(id)));
|
|
394
453
|
}
|
|
454
|
+
publish(ws) {
|
|
455
|
+
const message = { type: "sessions", sessions: [...this.entries.values()].map((session) => ({ ...session.info })) };
|
|
456
|
+
if (ws) send(ws, message);
|
|
457
|
+
else for (const watcher of this.watchers) send(watcher, message);
|
|
458
|
+
}
|
|
459
|
+
watch(ws) {
|
|
460
|
+
this.watchers.add(ws);
|
|
461
|
+
this.publish(ws);
|
|
462
|
+
ws.once("close", () => {
|
|
463
|
+
this.watchers.delete(ws);
|
|
464
|
+
if (!this.watchers.size) {
|
|
465
|
+
clearInterval(this.refreshTimer);
|
|
466
|
+
this.refreshTimer = void 0;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
if (!this.refreshTimer) this.refreshTimer = setInterval(() => void this.refresh(), 5e3);
|
|
470
|
+
void this.refresh();
|
|
471
|
+
}
|
|
472
|
+
async refresh() {
|
|
473
|
+
if (this.refreshing || this.shuttingDown) return;
|
|
474
|
+
this.refreshing = true;
|
|
475
|
+
try {
|
|
476
|
+
await this.list();
|
|
477
|
+
if (!this.shuttingDown) this.publish();
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.error("\u4F1A\u8BDD\u72B6\u6001\u66F4\u65B0\u5931\u8D25", error);
|
|
480
|
+
} finally {
|
|
481
|
+
this.refreshing = false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
stopActivity(session) {
|
|
485
|
+
clearTimeout(session.activityTimer);
|
|
486
|
+
session.activityTimer = void 0;
|
|
487
|
+
session.info.outputActive = false;
|
|
488
|
+
}
|
|
489
|
+
outputActivity(session) {
|
|
490
|
+
clearTimeout(session.activityTimer);
|
|
491
|
+
if (!session.info.outputActive) {
|
|
492
|
+
session.info.outputActive = true;
|
|
493
|
+
this.publish();
|
|
494
|
+
}
|
|
495
|
+
session.activityTimer = setTimeout(() => {
|
|
496
|
+
this.stopActivity(session);
|
|
497
|
+
this.publish();
|
|
498
|
+
}, 800);
|
|
499
|
+
}
|
|
395
500
|
async create(name, cwd) {
|
|
396
501
|
if (this.entries.size >= 32) throw new HttpError(409, "\u6700\u591A\u4FDD\u7559 32 \u4E2A\u4F1A\u8BDD\uFF0C\u8BF7\u7ED3\u675F\u4E0D\u9700\u8981\u7684\u4F1A\u8BDD");
|
|
397
502
|
if (name !== void 0 && (typeof name !== "string" || name.length > 80)) throw new HttpError(400, "\u4F1A\u8BDD\u540D\u79F0\u6700\u591A 80 \u5B57\u7B26");
|
|
398
503
|
if (cwd !== void 0 && typeof cwd !== "string") throw new HttpError(400, "\u5DE5\u4F5C\u76EE\u5F55\u65E0\u6548");
|
|
399
504
|
const dir = await this.files.directory(cwd || this.config.defaultCwd);
|
|
400
|
-
const id =
|
|
401
|
-
const info = { id, name: name?.trim() || `\u7EC8\u7AEF ${this.entries.size + 1}`, cwd: dir, createdAt: (/* @__PURE__ */ new Date()).toISOString(), running: true };
|
|
505
|
+
const id = randomUUID();
|
|
506
|
+
const info = { id, name: name?.trim() || `\u7EC8\u7AEF ${this.entries.size + 1}`, cwd: dir, createdAt: (/* @__PURE__ */ new Date()).toISOString(), running: true, outputActive: false };
|
|
402
507
|
const session = this.makeSession(info);
|
|
403
508
|
try {
|
|
404
509
|
this.entries.set(id, session);
|
|
@@ -409,6 +514,7 @@ var Sessions = class {
|
|
|
409
514
|
throw error;
|
|
410
515
|
}
|
|
411
516
|
this.save();
|
|
517
|
+
this.publish();
|
|
412
518
|
return info;
|
|
413
519
|
}
|
|
414
520
|
spawnTerminal(command, args, cwd, cols, rows) {
|
|
@@ -428,6 +534,8 @@ var Sessions = class {
|
|
|
428
534
|
const terminalProcess = this.spawnTerminal(this.config.shell, shellArgs, session.info.cwd, 100, 30);
|
|
429
535
|
session.process = terminalProcess;
|
|
430
536
|
terminalProcess.onData((data) => {
|
|
537
|
+
if (this.shuttingDown || this.entries.get(session.info.id) !== session) return;
|
|
538
|
+
if (data) this.outputActivity(session);
|
|
431
539
|
session.pendingBytes += data.length;
|
|
432
540
|
if (session.pendingBytes > 256 * 1024) terminalProcess.pause();
|
|
433
541
|
void session.screen.write(data, () => {
|
|
@@ -441,7 +549,10 @@ var Sessions = class {
|
|
|
441
549
|
if (this.shuttingDown) return;
|
|
442
550
|
void session.screen.run(() => {
|
|
443
551
|
session.info.running = false;
|
|
552
|
+
session.info.progress = void 0;
|
|
553
|
+
this.stopActivity(session);
|
|
444
554
|
this.save();
|
|
555
|
+
this.publish();
|
|
445
556
|
for (const ws of session.clients.keys()) {
|
|
446
557
|
send(ws, { type: "exit", exitCode });
|
|
447
558
|
ws.close(4404, "\u4F1A\u8BDD\u5DF2\u7ED3\u675F");
|
|
@@ -521,17 +632,24 @@ var Sessions = class {
|
|
|
521
632
|
}
|
|
522
633
|
async remove(id) {
|
|
523
634
|
const session = this.get(id);
|
|
635
|
+
this.stopActivity(session);
|
|
524
636
|
session.process?.kill();
|
|
525
637
|
for (const ws of session.clients.keys()) ws.close(1e3, "\u4F1A\u8BDD\u5DF2\u7ED3\u675F");
|
|
526
638
|
this.entries.delete(id);
|
|
527
639
|
this.save();
|
|
640
|
+
this.publish();
|
|
528
641
|
await session.screen.dispose();
|
|
529
|
-
await
|
|
642
|
+
await this.files.removeUploads(id);
|
|
530
643
|
}
|
|
531
644
|
async shutdown() {
|
|
532
645
|
this.shuttingDown = true;
|
|
533
646
|
this.save();
|
|
647
|
+
clearInterval(this.refreshTimer);
|
|
648
|
+
this.refreshTimer = void 0;
|
|
649
|
+
for (const ws of this.watchers) ws.close(1012, "\u670D\u52A1\u91CD\u542F\u4E2D");
|
|
650
|
+
this.watchers.clear();
|
|
534
651
|
for (const session of this.entries.values()) {
|
|
652
|
+
this.stopActivity(session);
|
|
535
653
|
for (const ws of session.clients.keys()) ws.close(1012, "\u670D\u52A1\u91CD\u542F\u4E2D");
|
|
536
654
|
session.process?.kill();
|
|
537
655
|
}
|
|
@@ -570,12 +688,12 @@ function embeddingHeaders(path, embed, dev) {
|
|
|
570
688
|
}
|
|
571
689
|
|
|
572
690
|
// src/server/app.ts
|
|
573
|
-
import { randomUUID as
|
|
691
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
574
692
|
import { fileURLToPath } from "node:url";
|
|
575
693
|
async function createApp(config, options = {}) {
|
|
576
694
|
mkdirSync(config.dataDir, { recursive: true, mode: 448 });
|
|
577
695
|
const app = express();
|
|
578
|
-
const instanceId =
|
|
696
|
+
const instanceId = randomUUID2();
|
|
579
697
|
app.disable("x-powered-by");
|
|
580
698
|
const server = createServer(app);
|
|
581
699
|
const files = new Files(config);
|
|
@@ -667,6 +785,7 @@ async function createApp(config, options = {}) {
|
|
|
667
785
|
});
|
|
668
786
|
wss.on("connection", (ws) => {
|
|
669
787
|
let sessionId;
|
|
788
|
+
let watching = false;
|
|
670
789
|
const timer = setTimeout(() => ws.close(4401, "\u9700\u8981 token"), 5e3);
|
|
671
790
|
ws.once("close", () => clearTimeout(timer));
|
|
672
791
|
ws.on("error", () => {
|
|
@@ -675,6 +794,7 @@ async function createApp(config, options = {}) {
|
|
|
675
794
|
try {
|
|
676
795
|
if (binary) throw new Error("\u6D88\u606F\u683C\u5F0F\u65E0\u6548");
|
|
677
796
|
const message = JSON.parse(raw.toString());
|
|
797
|
+
if (watching) throw new Error("\u4F1A\u8BDD\u72B6\u6001\u8BA2\u9605\u53EA\u652F\u6301\u8BFB\u53D6");
|
|
678
798
|
if (!sessionId) {
|
|
679
799
|
if (message.type !== "auth" || !validToken(message.token, config.token)) {
|
|
680
800
|
ws.close(4401, "token \u65E0\u6548");
|
|
@@ -685,6 +805,12 @@ async function createApp(config, options = {}) {
|
|
|
685
805
|
ws.close(4406, "\u8BF7\u5237\u65B0\u9875\u9762");
|
|
686
806
|
return;
|
|
687
807
|
}
|
|
808
|
+
if (message.scope === "sessions") {
|
|
809
|
+
watching = true;
|
|
810
|
+
clearTimeout(timer);
|
|
811
|
+
sessions.watch(ws);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
688
814
|
if (typeof message.sessionId !== "string") throw new Error("\u4F1A\u8BDD\u65E0\u6548");
|
|
689
815
|
const session = sessions.get(message.sessionId);
|
|
690
816
|
sessionId = session.info.id;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@dadigua/web-terminal",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.5",
|
|
5
5
|
type: "module",
|
|
6
6
|
scripts: {
|
|
7
7
|
dev: "tsx src/server/index.ts --dev",
|
|
@@ -11,10 +11,11 @@ var package_default = {
|
|
|
11
11
|
check: "tsc --noEmit",
|
|
12
12
|
"build:clients": "tsx scripts/build-clients.ts",
|
|
13
13
|
"package:vscode": "pnpm build:clients && tsx scripts/package-vscode.ts",
|
|
14
|
+
"install:vscode": "pnpm package:vscode && tsx src/cli.ts install-vscode",
|
|
14
15
|
electron: "pnpm build:clients && electron dist-clients/electron",
|
|
15
16
|
"package:electron:win": "pnpm build:clients && tsx scripts/package-electron.ts --win",
|
|
16
17
|
"package:electron:linux": "pnpm build:clients && tsx scripts/package-electron.ts --linux",
|
|
17
|
-
prepack: "pnpm build"
|
|
18
|
+
prepack: "pnpm build && pnpm package:vscode"
|
|
18
19
|
},
|
|
19
20
|
dependencies: {
|
|
20
21
|
"@xterm/addon-fit": "^0.11.0",
|
|
@@ -50,7 +51,7 @@ var package_default = {
|
|
|
50
51
|
engines: {
|
|
51
52
|
node: ">=22"
|
|
52
53
|
},
|
|
53
|
-
packageManager: "pnpm@11.
|
|
54
|
+
packageManager: "pnpm@11.28.2",
|
|
54
55
|
bin: {
|
|
55
56
|
"web-terminal": "bin/web-terminal.mjs"
|
|
56
57
|
},
|
|
@@ -58,6 +59,7 @@ var package_default = {
|
|
|
58
59
|
"bin/",
|
|
59
60
|
"dist-server/",
|
|
60
61
|
"dist/",
|
|
62
|
+
"release/web-terminal.vsix",
|
|
61
63
|
"config.example.yaml",
|
|
62
64
|
"README.md",
|
|
63
65
|
"API.md"
|
package/dist-server/cli.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
serviceName,
|
|
3
3
|
serviceProtocol,
|
|
4
4
|
serviceVersion
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2APXDIFJ.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
@@ -34,11 +34,13 @@ function loadConfig(defaults = {}) {
|
|
|
34
34
|
if (!token || token.length > 1024) throw new Error("token \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u4E14\u6700\u591A 1024 \u4E2A\u5B57\u7B26");
|
|
35
35
|
const port = Number(process.env.PORT || input.port || 3840);
|
|
36
36
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("port \u65E0\u6548");
|
|
37
|
+
const uploadHome = resolve(process.env.WEB_TERMINAL_HOME || resolve(homedir(), ".web-terminal"));
|
|
37
38
|
return {
|
|
38
39
|
host: String(process.env.HOST || input.host || "127.0.0.1"),
|
|
39
40
|
port,
|
|
40
41
|
dataDir,
|
|
41
42
|
token,
|
|
43
|
+
uploadDir: resolve(input.uploadDir || defaults.uploadDir || resolve(uploadHome, "uploads")),
|
|
42
44
|
defaultCwd: realpathSync(resolve(input.defaultCwd || homedir())),
|
|
43
45
|
shell: String(input.shell || process.env.SHELL || (process.platform === "win32" ? "powershell.exe" : "/bin/bash")),
|
|
44
46
|
machineId: `${hostname()}-${createHash("sha256").update(dataDir).digest("hex").slice(0, 8)}`,
|
|
@@ -292,7 +294,8 @@ async function main() {
|
|
|
292
294
|
home: { type: "string" },
|
|
293
295
|
config: { type: "string" },
|
|
294
296
|
version: { type: "boolean" },
|
|
295
|
-
help: { type: "boolean" }
|
|
297
|
+
help: { type: "boolean" },
|
|
298
|
+
vsix: { type: "string" }
|
|
296
299
|
} });
|
|
297
300
|
if (values.version) {
|
|
298
301
|
console.log(JSON.stringify({ service: serviceName, version: serviceVersion }));
|
|
@@ -300,9 +303,16 @@ async function main() {
|
|
|
300
303
|
}
|
|
301
304
|
const action = positionals[0] || "start";
|
|
302
305
|
if (values.help) {
|
|
303
|
-
console.log("web-terminal [start|restart|stop|status] [--url http://localhost:3840] [--config /path/config.yaml] [--home /path/state]\n\u914D\u7F6E\u3001token\u3001\u8FDB\u7A0B\u72B6\u6001\u548C\u65E5\u5FD7\u9ED8\u8BA4\u4F4D\u4E8E ~/.web-terminal/\u3002\u91CD\u542F\u4F1A\u7ED3\u675F\u666E\u901A Shell \u4F1A\u8BDD\u3002");
|
|
306
|
+
console.log("web-terminal [start|restart|stop|status] [--url http://localhost:3840] [--config /path/config.yaml] [--home /path/state]\nweb-terminal install-vscode [--vsix /path/web-terminal.vsix]\n\u5B89\u88C5\u81EA\u5E26\u63D2\u4EF6\u5230\u5DF2\u68C0\u6D4B\u5230\u7684 VS Code Stable/Insiders\uFF1BWSL \u4E0B\u540C\u65F6\u5B89\u88C5 Windows \u5BBF\u4E3B\u3002\n\u914D\u7F6E\u3001token\u3001\u8FDB\u7A0B\u72B6\u6001\u548C\u65E5\u5FD7\u9ED8\u8BA4\u4F4D\u4E8E ~/.web-terminal/\u3002\u91CD\u542F\u4F1A\u7ED3\u675F\u666E\u901A Shell \u4F1A\u8BDD\u3002");
|
|
304
307
|
return;
|
|
305
308
|
}
|
|
309
|
+
if (action === "install-vscode") {
|
|
310
|
+
const { installVSCodeExtension } = await import("./vscodeInstaller-AIKL2DRR.js");
|
|
311
|
+
await installVSCodeExtension({ vsixPath: values.vsix, onInstalled: (result) => console.log(`${result.target} \u5DF2\u5B89\u88C5 ${result.extension}`) });
|
|
312
|
+
console.log("\u8BF7\u5728\u5DF2\u6253\u5F00\u7684 VS Code \u7A97\u53E3\u8FD0\u884C Developer: Reload Window \u4EE5\u52A0\u8F7D\u63D2\u4EF6\u3002");
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (values.vsix) throw new Error("--vsix \u4EC5\u7528\u4E8E install-vscode");
|
|
306
316
|
const url = normalizeServerUrl(values.url);
|
|
307
317
|
if (action === "status") {
|
|
308
318
|
console.log(JSON.stringify(await probeService(url)));
|
|
@@ -319,11 +329,11 @@ async function main() {
|
|
|
319
329
|
const configFile = resolve3(values.config || process.env.WEB_TERMINAL_CONFIG || paths.config);
|
|
320
330
|
process.env.WEB_TERMINAL_CONFIG = configFile;
|
|
321
331
|
process.chdir(dirname(configFile));
|
|
322
|
-
const config = loadConfig({ dataDir: join2(paths.directory, "data") });
|
|
332
|
+
const config = loadConfig({ dataDir: join2(paths.directory, "data"), uploadDir: join2(paths.base, "uploads") });
|
|
323
333
|
config.host = new URL(url).hostname === "localhost" ? "127.0.0.1" : new URL(url).hostname.replace(/^\[|\]$/g, "");
|
|
324
334
|
config.port = Number(new URL(url).port || 80);
|
|
325
335
|
const instanceId = randomUUID2();
|
|
326
|
-
const { createApp } = await import("./app-
|
|
336
|
+
const { createApp } = await import("./app-FFT46HD7.js");
|
|
327
337
|
const runtime = await createApp(config, { managed: true, instanceId, staticDir: fileURLToPath(new URL("../dist/", import.meta.url)) });
|
|
328
338
|
const stamp = await processStamp(process.pid);
|
|
329
339
|
if (!stamp) throw new Error("\u65E0\u6CD5\u53D6\u5F97\u8FDB\u7A0B\u542F\u52A8\u6807\u8BC6\uFF0C\u62D2\u7EDD\u542F\u52A8\u4E0D\u53EF\u5B89\u5168\u7BA1\u7406\u7684\u540E\u53F0");
|