paneful 0.8.4 → 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.
@@ -416,6 +416,7 @@ async function startServer(devMode, port) {
416
416
  return;
417
417
  shuttingDown = true;
418
418
  console.log('Shutting down...');
419
+ wsHandler.destroy();
419
420
  ptyManager.killAll();
420
421
  removeLockfile();
421
422
  ipcServer.close();
@@ -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}