paneful 0.8.3 → 0.8.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/dist/server/index.js +1 -0
- package/dist/server/port-monitor.js +181 -0
- package/dist/server/ws-handler.js +13 -0
- package/dist/web/assets/index-BtMt9GQ0.css +32 -0
- package/dist/web/assets/index-D9fr7DHS.js +349 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-CHLTIy23.css +0 -32
- package/dist/web/assets/index-DSsnA6Av.js +0 -349
package/dist/server/index.js
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
// Strip ANSI escape codes from PTY output
|
|
3
|
+
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?(?:\x07|\x1b\\)|\x1b[()][0-9A-B]/g;
|
|
4
|
+
// Match dev-server URLs like http://localhost:3000, http://127.0.0.1:8080, etc.
|
|
5
|
+
const PORT_RE = /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[?::1?\]?):(\d{1,5})/g;
|
|
6
|
+
export class PortMonitor {
|
|
7
|
+
terminals = new Map();
|
|
8
|
+
alivePorts = new Map(); // projectId → alive ports
|
|
9
|
+
pollTimer = null;
|
|
10
|
+
onChange;
|
|
11
|
+
destroyed = false;
|
|
12
|
+
constructor(onChange) {
|
|
13
|
+
this.onChange = onChange;
|
|
14
|
+
this.pollTimer = setInterval(() => this.poll(), 5000);
|
|
15
|
+
}
|
|
16
|
+
scanOutput(terminalId, projectId, data) {
|
|
17
|
+
if (this.destroyed)
|
|
18
|
+
return;
|
|
19
|
+
let info = this.terminals.get(terminalId);
|
|
20
|
+
if (!info) {
|
|
21
|
+
info = { projectId, ports: new Set(), lineBuffer: '' };
|
|
22
|
+
this.terminals.set(terminalId, info);
|
|
23
|
+
}
|
|
24
|
+
// Already found ports for this terminal — skip scanning.
|
|
25
|
+
// If the port goes down, the poll scrubs it from info.ports,
|
|
26
|
+
// so scanning resumes automatically on restart.
|
|
27
|
+
if (info.ports.size > 0)
|
|
28
|
+
return;
|
|
29
|
+
// Append data to line buffer, strip ANSI codes
|
|
30
|
+
const clean = (info.lineBuffer + data).replace(ANSI_RE, '');
|
|
31
|
+
const lines = clean.split(/\r?\n/);
|
|
32
|
+
// Keep last incomplete line in buffer
|
|
33
|
+
info.lineBuffer = lines.pop() ?? '';
|
|
34
|
+
let found = false;
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
PORT_RE.lastIndex = 0;
|
|
37
|
+
let match;
|
|
38
|
+
while ((match = PORT_RE.exec(line)) !== null) {
|
|
39
|
+
const port = parseInt(match[1], 10);
|
|
40
|
+
if (port > 0 && port <= 65535 && !info.ports.has(port)) {
|
|
41
|
+
info.ports.add(port);
|
|
42
|
+
found = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Also scan the buffer (for single-line output without trailing newline)
|
|
47
|
+
PORT_RE.lastIndex = 0;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = PORT_RE.exec(info.lineBuffer)) !== null) {
|
|
50
|
+
const port = parseInt(match[1], 10);
|
|
51
|
+
if (port > 0 && port <= 65535 && !info.ports.has(port)) {
|
|
52
|
+
info.ports.add(port);
|
|
53
|
+
found = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (found) {
|
|
57
|
+
this.poll();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
removeTerminal(terminalId) {
|
|
61
|
+
const info = this.terminals.get(terminalId);
|
|
62
|
+
if (!info)
|
|
63
|
+
return;
|
|
64
|
+
this.terminals.delete(terminalId);
|
|
65
|
+
this.rebuildAndNotify();
|
|
66
|
+
}
|
|
67
|
+
removeProject(projectId) {
|
|
68
|
+
for (const [tid, info] of this.terminals) {
|
|
69
|
+
if (info.projectId === projectId) {
|
|
70
|
+
this.terminals.delete(tid);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const hadPorts = this.alivePorts.has(projectId);
|
|
74
|
+
this.alivePorts.delete(projectId);
|
|
75
|
+
if (hadPorts) {
|
|
76
|
+
this.notify();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
destroy() {
|
|
80
|
+
this.destroyed = true;
|
|
81
|
+
if (this.pollTimer) {
|
|
82
|
+
clearInterval(this.pollTimer);
|
|
83
|
+
this.pollTimer = null;
|
|
84
|
+
}
|
|
85
|
+
this.terminals.clear();
|
|
86
|
+
this.alivePorts.clear();
|
|
87
|
+
}
|
|
88
|
+
async poll() {
|
|
89
|
+
if (this.destroyed)
|
|
90
|
+
return;
|
|
91
|
+
// Build projectId → Set<port> from all terminals
|
|
92
|
+
const projectPorts = new Map();
|
|
93
|
+
for (const info of this.terminals.values()) {
|
|
94
|
+
if (info.ports.size === 0)
|
|
95
|
+
continue;
|
|
96
|
+
let set = projectPorts.get(info.projectId);
|
|
97
|
+
if (!set) {
|
|
98
|
+
set = new Set();
|
|
99
|
+
projectPorts.set(info.projectId, set);
|
|
100
|
+
}
|
|
101
|
+
for (const p of info.ports)
|
|
102
|
+
set.add(p);
|
|
103
|
+
}
|
|
104
|
+
// TCP-probe each unique port
|
|
105
|
+
const uniquePorts = new Set();
|
|
106
|
+
for (const set of projectPorts.values()) {
|
|
107
|
+
for (const p of set)
|
|
108
|
+
uniquePorts.add(p);
|
|
109
|
+
}
|
|
110
|
+
const probeResults = new Map();
|
|
111
|
+
await Promise.all([...uniquePorts].map((port) => new Promise((resolve) => {
|
|
112
|
+
const sock = net.createConnection({ port, host: '127.0.0.1' }, () => {
|
|
113
|
+
probeResults.set(port, true);
|
|
114
|
+
sock.destroy();
|
|
115
|
+
resolve();
|
|
116
|
+
});
|
|
117
|
+
sock.on('error', () => {
|
|
118
|
+
probeResults.set(port, false);
|
|
119
|
+
resolve();
|
|
120
|
+
});
|
|
121
|
+
sock.setTimeout(2000, () => {
|
|
122
|
+
probeResults.set(port, false);
|
|
123
|
+
sock.destroy();
|
|
124
|
+
resolve();
|
|
125
|
+
});
|
|
126
|
+
})));
|
|
127
|
+
if (this.destroyed)
|
|
128
|
+
return;
|
|
129
|
+
// Scrub dead ports from terminal tracking so stale entries
|
|
130
|
+
// don't cause overlap when another project reuses the same port
|
|
131
|
+
for (const info of this.terminals.values()) {
|
|
132
|
+
for (const p of info.ports) {
|
|
133
|
+
if (!probeResults.get(p)) {
|
|
134
|
+
info.ports.delete(p);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Build new alive state
|
|
139
|
+
const newAlive = new Map();
|
|
140
|
+
for (const [projectId, ports] of projectPorts) {
|
|
141
|
+
const alive = new Set();
|
|
142
|
+
for (const p of ports) {
|
|
143
|
+
if (probeResults.get(p))
|
|
144
|
+
alive.add(p);
|
|
145
|
+
}
|
|
146
|
+
if (alive.size > 0) {
|
|
147
|
+
newAlive.set(projectId, alive);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Check if changed
|
|
151
|
+
if (this.setsEqual(newAlive))
|
|
152
|
+
return;
|
|
153
|
+
this.alivePorts = newAlive;
|
|
154
|
+
this.notify();
|
|
155
|
+
}
|
|
156
|
+
rebuildAndNotify() {
|
|
157
|
+
// After terminal removal, re-poll immediately to update state
|
|
158
|
+
this.poll();
|
|
159
|
+
}
|
|
160
|
+
setsEqual(newAlive) {
|
|
161
|
+
if (newAlive.size !== this.alivePorts.size)
|
|
162
|
+
return false;
|
|
163
|
+
for (const [pid, ports] of newAlive) {
|
|
164
|
+
const existing = this.alivePorts.get(pid);
|
|
165
|
+
if (!existing || existing.size !== ports.size)
|
|
166
|
+
return false;
|
|
167
|
+
for (const p of ports) {
|
|
168
|
+
if (!existing.has(p))
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
notify() {
|
|
175
|
+
const result = {};
|
|
176
|
+
for (const [pid, ports] of this.alivePorts) {
|
|
177
|
+
result[pid] = [...ports];
|
|
178
|
+
}
|
|
179
|
+
this.onChange(result);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
2
2
|
import { newProject } from './project-store.js';
|
|
3
|
+
import { PortMonitor } from './port-monitor.js';
|
|
3
4
|
export class WsHandler {
|
|
4
5
|
wss;
|
|
5
6
|
client = null;
|
|
6
7
|
ptyManager;
|
|
7
8
|
projectStore;
|
|
9
|
+
portMonitor;
|
|
8
10
|
idleTimer = null;
|
|
9
11
|
onIdle;
|
|
10
12
|
constructor(server, ptyManager, projectStore, options) {
|
|
11
13
|
this.ptyManager = ptyManager;
|
|
12
14
|
this.projectStore = projectStore;
|
|
13
15
|
this.onIdle = options?.onIdle;
|
|
16
|
+
this.portMonitor = new PortMonitor((ports) => {
|
|
17
|
+
this.send({ type: 'port:status', ports });
|
|
18
|
+
});
|
|
14
19
|
this.wss = new WebSocketServer({ noServer: true });
|
|
15
20
|
server.on('upgrade', (req, socket, head) => {
|
|
16
21
|
if (req.url === '/ws') {
|
|
@@ -80,6 +85,7 @@ export class WsHandler {
|
|
|
80
85
|
this.ptyManager.resize(msg.terminalId, msg.cols, msg.rows);
|
|
81
86
|
break;
|
|
82
87
|
case 'pty:kill': {
|
|
88
|
+
this.portMonitor.removeTerminal(msg.terminalId);
|
|
83
89
|
const projectId = this.ptyManager.kill(msg.terminalId);
|
|
84
90
|
if (projectId) {
|
|
85
91
|
this.projectStore.removeTerminal(projectId, msg.terminalId);
|
|
@@ -88,6 +94,7 @@ export class WsHandler {
|
|
|
88
94
|
break;
|
|
89
95
|
}
|
|
90
96
|
case 'project:kill': {
|
|
97
|
+
this.portMonitor.removeProject(msg.projectId);
|
|
91
98
|
const killed = this.ptyManager.killProject(msg.projectId);
|
|
92
99
|
for (const tid of killed) {
|
|
93
100
|
this.send({ type: 'pty:exit', terminalId: tid, exitCode: 0 });
|
|
@@ -100,6 +107,7 @@ export class WsHandler {
|
|
|
100
107
|
break;
|
|
101
108
|
}
|
|
102
109
|
case 'project:remove': {
|
|
110
|
+
this.portMonitor.removeProject(msg.projectId);
|
|
103
111
|
const killed = this.ptyManager.killProject(msg.projectId);
|
|
104
112
|
for (const tid of killed) {
|
|
105
113
|
this.send({ type: 'pty:exit', terminalId: tid, exitCode: 0 });
|
|
@@ -109,11 +117,16 @@ export class WsHandler {
|
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
}
|
|
120
|
+
destroy() {
|
|
121
|
+
this.portMonitor.destroy();
|
|
122
|
+
}
|
|
112
123
|
handlePtySpawn(terminalId, projectId, cwd) {
|
|
113
124
|
try {
|
|
114
125
|
this.ptyManager.spawn(terminalId, projectId, cwd, (tid, data) => {
|
|
115
126
|
this.send({ type: 'pty:output', terminalId: tid, data });
|
|
127
|
+
this.portMonitor.scanOutput(tid, projectId, data);
|
|
116
128
|
}, (tid, exitCode) => {
|
|
129
|
+
this.portMonitor.removeTerminal(tid);
|
|
117
130
|
this.send({ type: 'pty:exit', terminalId: tid, exitCode });
|
|
118
131
|
});
|
|
119
132
|
this.projectStore.addTerminal(projectId, terminalId);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width: 640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width: 768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width: 1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width: 1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width: 1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.-left-\[2px\]{left:-2px}.-top-\[2px\]{top:-2px}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-3{margin-left:.75rem;margin-right:.75rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-1\.5{height:.375rem}.h-1\/2{height:50%}.h-16{height:4rem}.h-4{height:1rem}.h-7{height:1.75rem}.h-9{height:2.25rem}.h-\[4px\]{height:4px}.h-\[8px\]{height:8px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[80vh\]{max-height:80vh}.min-h-0{min-height:0px}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-16{width:4rem}.w-\[380px\]{width:380px}.w-\[400px\]{width:400px}.w-\[440px\]{width:440px}.w-\[4px\]{width:4px}.w-\[8px\]{width:8px}.w-full{width:100%}.w-px{width:1px}.w-screen{width:100vw}.min-w-0{min-width:0px}.min-w-\[24px\]{min-width:24px}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-col-resize{cursor:col-resize}.cursor-grab{cursor:grab}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-6{gap:1.5rem}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-\[var\(--accent\)\]{border-color:var(--accent)}.border-\[var\(--border\)\]{border-color:var(--border)}.border-\[var\(--danger\)\]{border-color:var(--danger)}.border-transparent{border-color:transparent}.bg-\[var\(--border\)\]{background-color:var(--border)}.bg-\[var\(--danger\)\]{background-color:var(--danger)}.bg-\[var\(--success\)\]{background-color:var(--success)}.bg-\[var\(--surface-0\)\]{background-color:var(--surface-0)}.bg-\[var\(--surface-1\)\]{background-color:var(--surface-1)}.bg-\[var\(--surface-2\)\]{background-color:var(--surface-2)}.bg-\[var\(--surface-3\)\]{background-color:var(--surface-3)}.bg-accent{background-color:var(--accent)}.bg-black\/50{background-color:#00000080}.bg-transparent{background-color:transparent}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pt-1{padding-top:.25rem}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-relaxed{line-height:1.625}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-\[var\(--accent\)\]{color:var(--accent)}.text-\[var\(--danger\)\]{color:var(--danger)}.text-\[var\(--text-muted\)\]{color:var(--text-muted)}.text-\[var\(--text-primary\)\]{color:var(--text-primary)}.text-\[var\(--text-secondary\)\]{color:var(--text-secondary)}.text-accent{color:var(--accent)}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.opacity-0{opacity:0}.opacity-40{opacity:.4}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_1px_0_var\(--border\)\]{--tw-shadow: 0 1px 0 var(--border);--tw-shadow-colored: 0 1px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-inset{--tw-ring-inset: inset}.ring-\[var\(--accent\)\]{--tw-ring-color: var(--accent)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}:root{--surface-0: #0a0a0f;--surface-1: #111118;--surface-2: #1a1a24;--surface-3: #252532;--border: #2a2a3a;--border-focus: #5b5bd6;--accent: #5b5bd6;--accent-dim: #3a3a8c;--text-primary: #ececf1;--text-secondary: #a1a1b5;--text-muted: #6b6b80;--danger: #e5484d;--danger-dim: #3c1618;--success: #30a46c}[data-theme=light]{--surface-0: #ffffff;--surface-1: #f5f5f7;--surface-2: #ebebef;--surface-3: #dddde3;--border: #d0d0d8;--border-focus: #5b5bd6;--accent: #5b5bd6;--accent-dim: #e0e0ff;--text-primary: #1a1a2e;--text-secondary: #555570;--text-muted: #8888a0;--danger: #dc3545;--danger-dim: #fde8ea;--success: #1a8f5c}@media (prefers-color-scheme: light){[data-theme=system]{--surface-0: #ffffff;--surface-1: #f5f5f7;--surface-2: #ebebef;--surface-3: #dddde3;--border: #d0d0d8;--border-focus: #5b5bd6;--accent: #5b5bd6;--accent-dim: #e0e0ff;--text-primary: #1a1a2e;--text-secondary: #555570;--text-muted: #8888a0;--danger: #dc3545;--danger-dim: #fde8ea;--success: #1a8f5c}}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden;background:var(--surface-0);color:var(--text-primary);font-family:-apple-system,BlinkMacSystemFont,Inter,Segoe UI,sans-serif}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}.xterm{padding:4px 8px}.xterm-viewport::-webkit-scrollbar{width:6px}.xterm-viewport::-webkit-scrollbar-track{background:transparent}.xterm-viewport::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideIn{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes slideOut{0%{transform:translate(0)}to{transform:translate(-100%)}}.animate-fade-in{animation:fadeIn .15s ease-out}.placeholder\:text-\[var\(--text-muted\)\]::-moz-placeholder{color:var(--text-muted)}.placeholder\:text-\[var\(--text-muted\)\]::placeholder{color:var(--text-muted)}.hover\:bg-\[var\(--accent\)\]:hover{background-color:var(--accent)}.hover\:bg-\[var\(--danger-dim\)\]:hover{background-color:var(--danger-dim)}.hover\:bg-\[var\(--surface-2\)\]:hover{background-color:var(--surface-2)}.hover\:bg-\[var\(--surface-3\)\]:hover{background-color:var(--surface-3)}.hover\:bg-green-500\/10:hover{background-color:#22c55e1a}.hover\:text-\[var\(--danger\)\]:hover{color:var(--danger)}.hover\:text-\[var\(--success\)\]:hover{color:var(--success)}.hover\:text-\[var\(--text-primary\)\]:hover{color:var(--text-primary)}.hover\:text-\[var\(--text-secondary\)\]:hover{color:var(--text-secondary)}.hover\:text-yellow-400:hover{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-40:hover{opacity:.4}.focus\:border-\[var\(--danger\)\]:focus{border-color:var(--danger)}.focus\:border-accent:focus{border-color:var(--accent)}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-\[var\(--accent\)\]:active{background-color:var(--accent)}.active\:opacity-60:active{opacity:.6}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.group:hover .group-hover\:flex{display:flex}.group:active .group-active\:bg-accent{background-color:var(--accent)}@media (min-width: 640px){.sm\:inline{display:inline}}/**
|
|
2
|
+
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
+
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
+
* https://github.com/chjj/term.js
|
|
5
|
+
* @license MIT
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
|
15
|
+
* all copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
* THE SOFTWARE.
|
|
24
|
+
*
|
|
25
|
+
* Originally forked from (with the author's permission):
|
|
26
|
+
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
+
* http://bellard.org/jslinux/
|
|
28
|
+
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
+
* The original design remains. The terminal itself
|
|
30
|
+
* has been extended to include xterm CSI codes, among
|
|
31
|
+
* other features.
|
|
32
|
+
*/.xterm{cursor:text;position:relative;-moz-user-select:none;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;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.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;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::-moz-selection{color:transparent}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;-moz-user-select:text;user-select:text;white-space:pre}.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{-webkit-text-decoration:double underline;text-decoration:double underline}.xterm-underline-3{-webkit-text-decoration:wavy underline;text-decoration:wavy underline}.xterm-underline-4{-webkit-text-decoration:dotted underline;text-decoration:dotted underline}.xterm-underline-5{-webkit-text-decoration:dashed underline;text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;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}
|