@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.
@@ -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-Bv4iPZiS.js"></script>
3
- <link rel="stylesheet" crossorigin href="/assets/index-DAKKO3a9.css">
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-R33XRLBM.js";
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, rename, unlink } from "node:fs/promises";
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 { randomUUID } from "node:crypto";
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
- const path = join(folder, `${randomUUID()}${ext}`);
134
- await rename(temporary, path);
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 as randomUUID2 } from "node:crypto";
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 readFile(`/proc/${pid}/stat`, "utf8");
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 readFile(`/proc/${foreground}/cmdline`, "utf8")).split("\0", 2);
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
- constructor(onTitle, onResponse) {
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 = parse(readFileSync(saved, "utf8"));
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 = 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`, stringify([...this.entries.values()].map((s) => s.info)), { mode: 384 });
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 = randomUUID2();
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 rm(join2(this.config.dataDir, "uploads", id), { recursive: true, force: true });
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 randomUUID3 } from "node:crypto";
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 = randomUUID3();
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.3",
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.12.0",
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"
@@ -2,7 +2,7 @@ import {
2
2
  serviceName,
3
3
  serviceProtocol,
4
4
  serviceVersion
5
- } from "./chunk-R33XRLBM.js";
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-TCUYGJ4K.js");
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");