zerogterm 0.9.2-alpha.1 → 1.0.0
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/README.md +37 -13
- package/bin/zerogterm.cjs +7 -1
- package/dist/main/main/main.js +32 -6
- package/dist/main/main/port-forward-service.js +2 -5
- package/dist/main/main/pty-lifecycle.js +47 -0
- package/dist/main/main/session-service.js +17 -15
- package/dist/main/main/sftp-service.js +2 -3
- package/dist/main/main/ssh-inventory.js +7 -5
- package/dist/renderer/assets/index-B-yoOhye.css +1 -0
- package/dist/renderer/assets/index-BeAly2uZ.js +149 -0
- package/dist/renderer/assets/{ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm → ort-wasm-simd-threaded.asyncify-CxOG5pUO.wasm} +0 -0
- package/dist/renderer/assets/voice-worker-DvImI8sL.js +40 -0
- package/dist/renderer/index.html +2 -2
- package/package.json +38 -11
- package/scripts/patch-node-pty.mjs +55 -0
- package/dist/manifest.json +0 -1
- package/dist/renderer/assets/index-B7wWHnX0.js +0 -149
- package/dist/renderer/assets/index-DJfpGOKA.css +0 -1
- package/dist/renderer/assets/voice-worker-DoNT0zEM.js +0 -37
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
ZeroG Terminal is an Electron workspace manager for persistent terminal and SSH sessions on Linux and Windows, with smart features to help keep you productive. It is designed with AI tooling in mind: leave Codex, Claude Code, or other terminal-based agents running in persistent sessions, then return to them without repeating setup and resume commands.
|
|
4
4
|
|
|
5
|
-
ZeroG Terminal is
|
|
5
|
+
ZeroG Terminal is a multi-session terminal and SSH workspace. It uses `screen` where available for resumable sessions and falls back to a direct shell PTY when `screen` is not installed.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -15,14 +15,15 @@ ZeroG Terminal is an alpha project, but it is already useful as a multi-session
|
|
|
15
15
|
- SSH sessions for hosts, `user@host`, and `user@host:port` targets.
|
|
16
16
|
- SSH configuration discovery from known connections, including remote `screen` session discovery. In the sidebar's Connections tab, clicking a saved connection opens the connect dialog with the host filled in, and double-clicking it skips the dialog and opens the host in a new pane.
|
|
17
17
|
- Reconnect to existing local or remote `screen` sessions from the Screens view.
|
|
18
|
-
- Remote screen
|
|
18
|
+
- Remote screen discovery for the selected SSH destination, including manually entered hosts, with refresh and visible failure states. Background discovery uses key/agent authentication; password-only hosts cannot be queried through the terminal connection.
|
|
19
|
+
- Remote screen attachment launched directly through SSH, preserving the account, custom port and SSH config alias, with authentication and host-key prompts left to the user.
|
|
19
20
|
- Shared ports over SSH, in a Ports view opened from the rail: forward a port on a remote host so it answers on this machine, or a port here so it answers on the remote. Each tunnel is its own SSH connection, so a host needs no terminal open first. Ports bind to loopback unless you widen them, and are remembered between launches.
|
|
20
21
|
- A directory browser inside a pane, opened from the folder button in its title bar: the pane splits, terminal on one side and the directories on the other, with a draggable divider. Double-click a folder to take the shell there. It works on SSH panes and on local ones, WSL included, follows a `cd` you type by hand, and is remembered per pane when you switch workspaces or relaunch.
|
|
21
22
|
- An SFTP transfer panel, opened from the ⇅ button above the panes: local files on the left, the active SSH session's host on the right, with upload, download, new folder, rename, and delete. It connects to the host that session is already using and opens at the directory its shell is standing in, so a file can go straight to the project being worked on.
|
|
22
23
|
- Session history for reconnecting to sessions after a relaunch, with bounded structured history and no stored secrets.
|
|
23
24
|
- A ranked command palette on `Ctrl+Shift+R`, in the spirit of [McFly](https://github.com/cantino/mcfly): commands you have run, ranked by directory, host, recency, frequency, whether they worked, and whether you picked them before. Off by default — it is the one feature that stores what you typed — and it refuses anything that looks like it carries a credential.
|
|
24
25
|
- Workspaces for grouping sessions and quickly switching between projects or tasks. Drag the tabs beside the wordmark to put them in the order you want; `Ctrl+Shift+1` … `9` follow that order, and it is remembered between launches. Right-click a tab to rename it, duplicate it, move it, or close it. Each workspace keeps its own panes, layout, focused terminal, and maximized pane, so switching to one restores the arrangement you left it in. Workspaces survive a relaunch: local `screen` terminals reattach on their own, and SSH panes come back as ghost rows that reconnect when clicked, rather than dialling out to a host on startup.
|
|
25
|
-
- Session overview, collapsible sidebar, keyboard shortcuts, and
|
|
26
|
+
- Session overview, collapsible sidebar, keyboard shortcuts, and nine colour themes: Tokyo Night, Light, Dracula, Nord, Solarized Dark, Monokai, One Dark, Synthwave and High contrast. The palettes are matched by hand to those open-source themes.
|
|
26
27
|
- xterm.js terminal rendering with scrollback preservation while changing layouts.
|
|
27
28
|
- Voice input, either with Whisper ONNX inside the app through Transformers.js or through an OpenAI-compatible transcription server you point it at — on this machine, on the LAN, or hosted; transcribed text is typed into the selected terminal without automatic execution.
|
|
28
29
|
- A per-pane proceed button that sends a configurable phrase — `OK, proceed` by default — for waving an agent on without typing the same reply again.
|
|
@@ -38,9 +39,9 @@ See the project walkthrough on [YouTube](https://youtu.be/4aJZCxLHD14).
|
|
|
38
39
|
|
|
39
40
|
## Release status
|
|
40
41
|
|
|
41
|
-
ZeroG Terminal is
|
|
42
|
+
ZeroG Terminal 1.0.0 is the first stable release. The current release is `1.0.0`; the version history is tracked in [versions.txt](versions.txt). The running version is shown beside the wordmark in the title bar, read from the app itself rather than written into the interface, so it is accurate in a packaged build too.
|
|
42
43
|
|
|
43
|
-
The GitHub Releases page provides a Windows x64 installer and portable executable plus a Linux x64 AppImage and Debian package for each desktop release.
|
|
44
|
+
The GitHub Releases page provides a Windows x64 installer and portable executable plus a Linux x64 AppImage and Debian package for each desktop release. The npm package remains available for developers who prefer to launch ZeroG from Node.js.
|
|
44
45
|
|
|
45
46
|
## Terminal shortcuts
|
|
46
47
|
|
|
@@ -386,13 +387,13 @@ how the threshold gets tuned for a particular microphone.
|
|
|
386
387
|
The simplest way to try the published package is through `npx`:
|
|
387
388
|
|
|
388
389
|
```bash
|
|
389
|
-
npx zerogterm
|
|
390
|
+
npx --package=electron@43 --package=zerogterm zerogterm
|
|
390
391
|
```
|
|
391
392
|
|
|
392
|
-
The
|
|
393
|
+
The npm launcher needs Electron alongside ZeroG Terminal; the command above installs both into the temporary npx environment. To use the launcher repeatedly without downloading on each invocation, install it globally:
|
|
393
394
|
|
|
394
395
|
```bash
|
|
395
|
-
npm install --global zerogterm
|
|
396
|
+
npm install --global electron@43 zerogterm
|
|
396
397
|
zerogterm
|
|
397
398
|
```
|
|
398
399
|
|
|
@@ -405,14 +406,14 @@ project workspaces, open SSH sessions, and request commands for your approval.
|
|
|
405
406
|
1. Start ZeroG Terminal with MCP enabled:
|
|
406
407
|
|
|
407
408
|
```bash
|
|
408
|
-
ZEROG_MCP_ENABLED=1 npx zerogterm
|
|
409
|
+
ZEROG_MCP_ENABLED=1 npx --package=electron@43 --package=zerogterm zerogterm
|
|
409
410
|
```
|
|
410
411
|
|
|
411
412
|
On Windows PowerShell, use:
|
|
412
413
|
|
|
413
414
|
```powershell
|
|
414
415
|
$env:ZEROG_MCP_ENABLED = "1"
|
|
415
|
-
npx zerogterm
|
|
416
|
+
npx --package=electron@43 --package=zerogterm zerogterm
|
|
416
417
|
```
|
|
417
418
|
|
|
418
419
|
2. Open **Settings → AI & voice → MCP** in ZeroG Terminal. Copy the displayed
|
|
@@ -469,7 +470,7 @@ general one — there, `npm install` compiles it.
|
|
|
469
470
|
|
|
470
471
|
### Desktop releases
|
|
471
472
|
|
|
472
|
-
Download the latest release for your platform from [GitHub Releases](https://github.com/HappyMonkeyAI/ZeroGTerm/releases).
|
|
473
|
+
Download the latest release for your platform from [GitHub Releases](https://github.com/HappyMonkeyAI/ZeroGTerm/releases). Review the release notes before installing; SSH, WSL, PowerShell, Git Bash, and any remote `screen` sessions are provided by the host system rather than bundled with the app.
|
|
473
474
|
|
|
474
475
|
#### Windows
|
|
475
476
|
|
|
@@ -502,14 +503,37 @@ picker), and Git Bash where Git for Windows is installed.
|
|
|
502
503
|
do not survive app exit. Remote SSH sessions are unaffected by the local shell
|
|
503
504
|
backend, and a remote host with `screen` still gives persistent sessions there.
|
|
504
505
|
|
|
506
|
+
The pinned `node-pty` dependency receives a small ConPTY teardown patch during
|
|
507
|
+
install and build (`scripts/patch-node-pty.mjs`). Review that patch before changing
|
|
508
|
+
its version. Run `node scripts/pty-shutdown-smoke.cjs` on Windows to check forced
|
|
509
|
+
and graceful shutdown with a real child process. Release evaluation evidence and
|
|
510
|
+
remaining sign-off items are in [the release check report](docs/release-readiness-screen-check.md).
|
|
511
|
+
|
|
512
|
+
Windows source packaging rebuilds native dependencies. Install Visual Studio 2022
|
|
513
|
+
Build Tools with the C++ desktop workload by importing [`.vsconfig`](.vsconfig),
|
|
514
|
+
then run `powershell -ExecutionPolicy Bypass -File scripts/ensure-windows-build-tools.ps1`.
|
|
515
|
+
The helper checks the selected MSVC toolset and installs its missing Spectre libraries
|
|
516
|
+
through Visual Studio Installer if needed; Windows may request administrator approval.
|
|
517
|
+
Then run `npm run package:win`. The normal native rebuild and installed application
|
|
518
|
+
have been verified; compiler protections remain enabled.
|
|
519
|
+
|
|
520
|
+
Linux packaging uses `/opt/ZeroGTerminal` to avoid Electron's sandbox launcher issue
|
|
521
|
+
with spaces in installation paths; the application menu still says **ZeroG Terminal**.
|
|
522
|
+
The Debian package declares its audio and graphics runtime libraries explicitly.
|
|
523
|
+
|
|
524
|
+
The Electron downloader has a scoped `global-agent` override to remove the vulnerable
|
|
525
|
+
`sprintf-js` dependency chain. `node scripts/check-downloader-proxy.mjs` verifies proxy
|
|
526
|
+
routing and `NO_PROXY` bypass using local fixtures. CI runs this check and the full audit.
|
|
527
|
+
|
|
528
|
+
|
|
505
529
|
## Verification
|
|
506
530
|
|
|
507
531
|
The current main branch has the following local verification coverage:
|
|
508
532
|
|
|
509
533
|
- `npm run typecheck`: passes.
|
|
510
|
-
- `npm test`:
|
|
534
|
+
- `npm test`: 64 test files; 1,169 passed and 3 platform skips on Windows, all 1,172 passed on Linux with `ZEROG_LIVE_SCREEN=1`.
|
|
511
535
|
- `npm run build`: passes and writes `dist/main` plus `dist/renderer`.
|
|
512
|
-
- `npm audit --
|
|
536
|
+
- `npm audit --audit-level=moderate`: zero advisories in the verified dependency tree.
|
|
513
537
|
|
|
514
538
|
## License
|
|
515
539
|
|
package/bin/zerogterm.cjs
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const { spawn } = require('node:child_process');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
-
|
|
5
|
+
let electron;
|
|
6
|
+
try {
|
|
7
|
+
electron = require('electron');
|
|
8
|
+
} catch {
|
|
9
|
+
console.error('Electron runtime was not found. Install both packages: npm install --global electron@43 zerogterm');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
const packageRoot = path.resolve(__dirname, '..');
|
|
8
14
|
const child = spawn(electron, [packageRoot, ...process.argv.slice(2)], {
|
package/dist/main/main/main.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { shutdownPtys } from './pty-lifecycle.js';
|
|
1
2
|
import { app, BrowserWindow, clipboard, ipcMain, Menu, safeStorage, session, shell } from 'electron';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { randomUUID } from 'node:crypto';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { writeClipboardText } from './clipboard.js';
|
|
6
|
-
import { ScreenService, parseWslDistributions } from './session-service.js';
|
|
7
|
+
import { ScreenService, sshExecutable, parseWslDistributions } from './session-service.js';
|
|
7
8
|
import { discoverShellBackends } from './shell-catalog.js';
|
|
8
9
|
import { SessionHistoryStore, defaultHistoryPath } from './session-history.js';
|
|
9
10
|
import { CommandHistoryStore, defaultCommandHistoryPath } from './command-history-store.js';
|
|
@@ -517,7 +518,9 @@ ipcMain.handle('sessions:createSsh', async (_event, request) => {
|
|
|
517
518
|
throw new Error('createSshSession requires { target: string }');
|
|
518
519
|
}
|
|
519
520
|
const name = typeof request.name === 'string' ? request.name : undefined;
|
|
520
|
-
|
|
521
|
+
if (request.screenName !== undefined && typeof request.screenName !== 'string')
|
|
522
|
+
throw new Error('Invalid screen name');
|
|
523
|
+
return service.createSsh(request.target, name, request.screenName);
|
|
521
524
|
});
|
|
522
525
|
ipcMain.handle('connections:listKnown', () => listKnownConnections());
|
|
523
526
|
ipcMain.handle('screens:discoverRemote', async (_event, input) => {
|
|
@@ -526,11 +529,15 @@ ipcMain.handle('screens:discoverRemote', async (_event, input) => {
|
|
|
526
529
|
const { execFile } = await import('node:child_process');
|
|
527
530
|
const { promisify } = await import('node:util');
|
|
528
531
|
try {
|
|
529
|
-
const { stdout } = await promisify(execFile)(
|
|
532
|
+
const { stdout } = await promisify(execFile)(sshExecutable(), command.args, { timeout: 15000, maxBuffer: 256 * 1024 });
|
|
530
533
|
return parseRemoteScreenList(stdout, connection.alias);
|
|
531
534
|
}
|
|
532
535
|
catch (error) {
|
|
533
|
-
|
|
536
|
+
// screen exits with 1 when there are no sockets. Other failures must reach
|
|
537
|
+
// the UI rather than pretending that the host has no running sessions.
|
|
538
|
+
if (error?.code === 1 && /No Sockets found/i.test(error.stdout ?? ''))
|
|
539
|
+
return [];
|
|
540
|
+
throw new Error('Screen discovery failed. Check SSH key/agent access and that screen is installed on the host.');
|
|
534
541
|
}
|
|
535
542
|
});
|
|
536
543
|
ipcMain.handle('screens:attachRemote', (_event, input, screenName) => {
|
|
@@ -783,8 +790,27 @@ app.whenReady().then(() => {
|
|
|
783
790
|
createWindow();
|
|
784
791
|
});
|
|
785
792
|
});
|
|
786
|
-
|
|
787
|
-
|
|
793
|
+
let shutdownStarted = false;
|
|
794
|
+
let shutdownComplete = false;
|
|
795
|
+
app.on('before-quit', (event) => {
|
|
796
|
+
if (shutdownComplete)
|
|
797
|
+
return;
|
|
798
|
+
event.preventDefault();
|
|
799
|
+
if (shutdownStarted)
|
|
800
|
+
return;
|
|
801
|
+
shutdownStarted = true;
|
|
802
|
+
service.detachAll();
|
|
803
|
+
sftp.closeAll();
|
|
804
|
+
forwards.closeAll();
|
|
805
|
+
void Promise.allSettled([shutdownPtys(), agentHooks.stop()]).then((results) => {
|
|
806
|
+
for (const result of results) {
|
|
807
|
+
if (result.status === 'rejected')
|
|
808
|
+
console.error('[zerog] shutdown failed', result.reason);
|
|
809
|
+
}
|
|
810
|
+
}).finally(() => {
|
|
811
|
+
shutdownComplete = true;
|
|
812
|
+
app.quit();
|
|
813
|
+
});
|
|
788
814
|
});
|
|
789
815
|
app.on('window-all-closed', () => {
|
|
790
816
|
if (process.platform !== 'darwin')
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
+
import { spawnManagedPty } from './pty-lifecycle.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { FORWARD_SETTLE_MS, buildForwardArgs, describeForward, readForwardOutcome } from './port-forward-protocol.js';
|
|
5
5
|
import { detectPrompt } from './sftp-protocol.js';
|
|
6
|
-
// node-pty is a native addon with no ESM entry point, and this file is compiled
|
|
7
|
-
// as a module. Same shim as session-service.ts and sftp-service.ts.
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
6
|
/** A ceiling on live tunnels, so a stuck panel cannot spawn clients forever. */
|
|
10
7
|
const MAX_FORWARDS = 16;
|
|
11
8
|
/** Nothing at all from the client within this long means the host never answered. */
|
|
@@ -33,7 +30,7 @@ export class PortForwardService {
|
|
|
33
30
|
onEvent;
|
|
34
31
|
settleMs;
|
|
35
32
|
constructor(options = {}) {
|
|
36
|
-
this.spawnPty = options.spawnPty ??
|
|
33
|
+
this.spawnPty = options.spawnPty ?? spawnManagedPty;
|
|
37
34
|
this.onEvent = options.onEvent ?? (() => undefined);
|
|
38
35
|
this.settleMs = options.settleMs ?? FORWARD_SETTLE_MS;
|
|
39
36
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
/** All three PTY owners must finish native teardown before Electron exits. */
|
|
4
|
+
export class PtyLifecycle {
|
|
5
|
+
spawn;
|
|
6
|
+
closing = false;
|
|
7
|
+
processes = new Map();
|
|
8
|
+
constructor(spawn) {
|
|
9
|
+
this.spawn = spawn;
|
|
10
|
+
}
|
|
11
|
+
start = (file, args, options) => {
|
|
12
|
+
if (this.closing)
|
|
13
|
+
throw new Error('The application is shutting down.');
|
|
14
|
+
const process = this.spawn(file, args, options);
|
|
15
|
+
let exited;
|
|
16
|
+
this.processes.set(process, new Promise((resolve) => { exited = resolve; }));
|
|
17
|
+
process.onExit(() => { this.processes.delete(process); exited(); });
|
|
18
|
+
return process;
|
|
19
|
+
};
|
|
20
|
+
async shutdown(timeoutMs = 6000) {
|
|
21
|
+
this.closing = true;
|
|
22
|
+
const waiting = [...this.processes.values()];
|
|
23
|
+
for (const process of this.processes.keys()) {
|
|
24
|
+
try {
|
|
25
|
+
process.kill();
|
|
26
|
+
}
|
|
27
|
+
catch { /* exit callback may already be queued */ }
|
|
28
|
+
}
|
|
29
|
+
if (!waiting.length)
|
|
30
|
+
return;
|
|
31
|
+
let timer;
|
|
32
|
+
try {
|
|
33
|
+
await Promise.race([
|
|
34
|
+
Promise.all(waiting),
|
|
35
|
+
new Promise((_, reject) => {
|
|
36
|
+
timer = setTimeout(() => reject(new Error('PTY shutdown timed out.')), timeoutMs);
|
|
37
|
+
})
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const lifecycle = new PtyLifecycle((file, args, options) => require('node-pty').spawn(file, args, options));
|
|
46
|
+
export const spawnManagedPty = lifecycle.start;
|
|
47
|
+
export const shutdownPtys = () => lifecycle.shutdown();
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import { spawnManagedPty } from './pty-lifecycle.js';
|
|
1
2
|
import { execFile } from 'node:child_process';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { WSL_HOME, defaultShellBackend, findOpenSshTool, isLocalShellBackend, ptyStartDirectory, resolveShellBackend, withWslStartDirectory } from './shell-catalog.js';
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
8
|
const NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,48}$/;
|
|
10
9
|
/**
|
|
11
10
|
* host, user@host, host:port, user@host:port — no shell metacharacters.
|
|
@@ -125,14 +124,6 @@ export function parseScreenList(output) {
|
|
|
125
124
|
];
|
|
126
125
|
});
|
|
127
126
|
}
|
|
128
|
-
function loadPty() {
|
|
129
|
-
try {
|
|
130
|
-
return require('node-pty');
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
throw new Error('PTY support is not installed. Run: npm install node-pty (needs make/gcc-c++).');
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
127
|
export class ScreenService {
|
|
137
128
|
ptys = new Map();
|
|
138
129
|
sshSessions = new Map();
|
|
@@ -150,7 +141,7 @@ export class ScreenService {
|
|
|
150
141
|
spawnPty;
|
|
151
142
|
constructor(options = {}) {
|
|
152
143
|
this.onEvent = options.onEvent;
|
|
153
|
-
this.spawnPty = options.spawnPty ??
|
|
144
|
+
this.spawnPty = options.spawnPty ?? spawnManagedPty;
|
|
154
145
|
}
|
|
155
146
|
async available() {
|
|
156
147
|
try {
|
|
@@ -221,8 +212,10 @@ export class ScreenService {
|
|
|
221
212
|
this.onEvent?.('created', session, true);
|
|
222
213
|
return session;
|
|
223
214
|
}
|
|
224
|
-
async createSsh(targetInput, name) {
|
|
215
|
+
async createSsh(targetInput, name, screenName) {
|
|
225
216
|
const { target } = validateSshTarget(targetInput);
|
|
217
|
+
if (screenName !== undefined)
|
|
218
|
+
validateSessionName(screenName);
|
|
226
219
|
const safeName = validateSessionName(name?.trim() || target.replace(/[^a-zA-Z0-9_.-]+/g, '-').slice(0, 40));
|
|
227
220
|
const session = {
|
|
228
221
|
id: `ssh:${randomUUID()}`,
|
|
@@ -233,7 +226,8 @@ export class ScreenService {
|
|
|
233
226
|
status: 'detached',
|
|
234
227
|
lastSeen: new Date().toISOString(),
|
|
235
228
|
sshTarget: target,
|
|
236
|
-
backend: 'ssh',
|
|
229
|
+
backend: screenName ? 'screen' : 'ssh',
|
|
230
|
+
...(screenName ? { screenName, persistence: 'screen' } : {}),
|
|
237
231
|
scope: 'remote',
|
|
238
232
|
source: 'active'
|
|
239
233
|
};
|
|
@@ -285,6 +279,8 @@ export class ScreenService {
|
|
|
285
279
|
throw new Error(`Unknown session: ${id}`);
|
|
286
280
|
}
|
|
287
281
|
const { args } = validateSshTarget(session.sshTarget);
|
|
282
|
+
if (session.screenName)
|
|
283
|
+
args.push('screen', '-x', validateSessionName(session.screenName));
|
|
288
284
|
session.status = 'connected';
|
|
289
285
|
session.lastSeen = new Date().toISOString();
|
|
290
286
|
try {
|
|
@@ -327,7 +323,9 @@ export class ScreenService {
|
|
|
327
323
|
cols,
|
|
328
324
|
rows,
|
|
329
325
|
cwd,
|
|
330
|
-
|
|
326
|
+
// ConPTY does not set TERM from node-pty's name option. OpenSSH needs
|
|
327
|
+
// it to request a remote terminal that screen can recognise.
|
|
328
|
+
env: { ...process.env, TERM: 'xterm-256color' }
|
|
331
329
|
});
|
|
332
330
|
const handle = {
|
|
333
331
|
write: (data) => proc.write(data),
|
|
@@ -341,9 +339,13 @@ export class ScreenService {
|
|
|
341
339
|
}
|
|
342
340
|
}
|
|
343
341
|
};
|
|
344
|
-
|
|
342
|
+
const record = { handle, kind: sessionId.startsWith('ssh:') ? 'ssh' : 'local', cols, rows };
|
|
343
|
+
this.ptys.set(sessionId, record);
|
|
345
344
|
proc.onData(onData);
|
|
346
345
|
proc.onExit(() => {
|
|
346
|
+
// A detached PTY can exit after a new attachment already owns this id.
|
|
347
|
+
if (this.ptys.get(sessionId) !== record)
|
|
348
|
+
return;
|
|
347
349
|
this.ptys.delete(sessionId);
|
|
348
350
|
const fallback = this.fallbackLocalSessions.get(sessionId);
|
|
349
351
|
if (fallback) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnManagedPty } from './pty-lifecycle.js';
|
|
1
2
|
// The stateful half of SFTP support: one live `sftp` client per remote host,
|
|
2
3
|
// with commands serialised onto it and its answers turned into data by
|
|
3
4
|
// sftp-protocol.ts.
|
|
@@ -10,9 +11,7 @@
|
|
|
10
11
|
import { randomUUID } from 'node:crypto';
|
|
11
12
|
import { homedir } from 'node:os';
|
|
12
13
|
import { baseName, joinRemote, sortEntries } from '../shared/files.js';
|
|
13
|
-
import { createRequire } from 'node:module';
|
|
14
14
|
import { buildSftpArgs, detectFatal, detectPrompt, endsWithPrompt, findError, isSafeRemotePath, parseListing, parseProgress, lastClientMessage, parsePwd, quoteLocalPath, quoteRemotePath, responseBody } from './sftp-protocol.js';
|
|
15
|
-
const require = createRequire(import.meta.url);
|
|
16
15
|
/**
|
|
17
16
|
* Wide enough that a long `ls -l` record is never wrapped by the pty, which
|
|
18
17
|
* would split one file's row across two lines and lose its name. Rows are
|
|
@@ -72,7 +71,7 @@ export class SftpService {
|
|
|
72
71
|
spawnPty;
|
|
73
72
|
onEvent;
|
|
74
73
|
constructor(options = {}) {
|
|
75
|
-
this.spawnPty = options.spawnPty ??
|
|
74
|
+
this.spawnPty = options.spawnPty ?? spawnManagedPty;
|
|
76
75
|
this.onEvent = options.onEvent ?? (() => undefined);
|
|
77
76
|
}
|
|
78
77
|
/**
|
|
@@ -102,26 +102,28 @@ export function validateKnownConnection(input) {
|
|
|
102
102
|
}
|
|
103
103
|
function sshArgs(connection) {
|
|
104
104
|
const c = validateKnownConnection(connection);
|
|
105
|
-
|
|
105
|
+
// Keep config aliases intact so IdentityFile, ProxyJump and Match still apply.
|
|
106
|
+
const host = c.source ? c.alias : (c.hostName ?? c.alias);
|
|
107
|
+
const destination = c.user ? `${c.user}@${host}` : host;
|
|
106
108
|
const args = [];
|
|
107
109
|
if (c.port)
|
|
108
110
|
args.push('-p', String(c.port));
|
|
109
|
-
args.push(
|
|
111
|
+
args.push('--', destination);
|
|
110
112
|
return args;
|
|
111
113
|
}
|
|
112
114
|
export function buildRemoteScreenDiscoveryArgs(connection) {
|
|
113
|
-
return { file: 'ssh', args: [...sshArgs(connection), 'screen', '-ls'] };
|
|
115
|
+
return { file: 'ssh', args: ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', ...sshArgs(connection), 'screen', '-ls'] };
|
|
114
116
|
}
|
|
115
117
|
export function buildRemoteScreenAttachArgs(connection, screenName) {
|
|
116
118
|
if (!SCREEN.test(screenName))
|
|
117
119
|
throw new Error('Invalid screen session name.');
|
|
118
|
-
return { file: 'ssh', args: [...sshArgs(connection), 'screen', '-x', screenName] };
|
|
120
|
+
return { file: 'ssh', args: ['-tt', ...sshArgs(connection), 'screen', '-x', screenName] };
|
|
119
121
|
}
|
|
120
122
|
export function parseRemoteScreenList(output, host) {
|
|
121
123
|
if (!TOKEN.test(host))
|
|
122
124
|
throw new Error('Invalid remote host identity.');
|
|
123
125
|
return output.split(/\r?\n/).flatMap((line) => {
|
|
124
|
-
const match = line.trim().match(/^\d+\.(\S+)\s
|
|
126
|
+
const match = line.trim().match(/^\d+\.(\S+)\s+(?:\([^)]*\)\s+)*\((Attached|Detached)\)\s*$/i);
|
|
125
127
|
if (!match || !SCREEN.test(match[1]))
|
|
126
128
|
return [];
|
|
127
129
|
const name = match[1];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;inset:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .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;inset:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-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{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}:root{--surface-0: #0a0c10;--surface-1: #0d1015;--surface-2: #11161e;--surface-3: #151a22;--surface-4: #1a202a;--line-1: #202630;--line-2: #2a3340;--line-3: #303b4b;--text-1: #d8dee9;--text-2: #8b93a3;--text-3: #697386;--text-4: #4e5969;--icon: #aab3c5;--blue: #7aa2f7;--green: #9ece6a;--yellow: #e0af68;--red: #f7768e;--cyan: #7dcfff;--teal: #73daca;--blue-rgb: 122, 162, 247;--green-rgb: 158, 206, 106;--yellow-rgb: 224, 175, 104;--red-rgb: 247, 118, 142;--cyan-rgb: 125, 207, 255;--teal-rgb: 115, 218, 202}:root{font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:var(--text-1);background:var(--surface-0);font-size:13px;line-height:1.4;color-scheme:dark}*{box-sizing:border-box}body{margin:0;min-width:980px;overflow:hidden;background:var(--surface-0)}button,input{font:inherit}button{color:inherit;border:0;cursor:pointer}button:focus-visible,input:focus-visible{outline:1px solid var(--blue);outline-offset:2px}.app-shell{height:100vh;background:var(--surface-0)}.window-bar{height:48px;display:flex;align-items:center;border-bottom:1px solid var(--line-1);background:var(--surface-1);padding:0 12px;gap:14px}.window-brand{width:205px;display:flex;align-items:center;gap:9px;flex:none}.brand-mark{display:grid;place-items:center;width:25px;height:25px;border:1px solid #3b82f6;border-radius:5px;color:var(--blue);font:700 9px JetBrains Mono,monospace}.brand-name{font-size:13px;font-weight:650;letter-spacing:.01em}.brand-version{margin-left:-3px;color:var(--text-3);font:10px JetBrains Mono,monospace;letter-spacing:.01em;white-space:nowrap}.workspace-tabs{display:flex;align-items:center;gap:6px;min-width:0;overflow:auto}.workspace-tab{height:32px;display:flex;align-items:center;gap:8px;padding:0 12px;border:1px solid transparent;border-radius:5px;color:var(--text-2);background:transparent;white-space:nowrap}.workspace-tab.active{background:var(--surface-3);border-color:var(--line-2);color:var(--text-1)}.workspace-tab-slot{position:relative;display:flex;align-items:center;min-width:0}.workspace-tab-slot.closable .workspace-tab{padding-right:26px}.workspace-tab-slot.dragging{z-index:3}.workspace-tab-slot.dragging .workspace-tab{background:color-mix(in srgb,var(--blue) 13%,var(--surface-1));border-color:var(--blue);box-shadow:0 6px 18px #0007;cursor:grabbing}.workspace-tab:active{cursor:grabbing}.workspace-tab-close{position:absolute;right:5px;width:16px;height:16px;display:grid;place-items:center;padding:0;border:0;border-radius:3px;background:transparent;color:var(--text-3);font-size:13px;line-height:1;opacity:0}.workspace-tab-slot:hover .workspace-tab-close,.workspace-tab-close:focus-visible{opacity:1}.workspace-tab-close:hover{background:var(--line-3);color:var(--text-1)}.workspace-rename{margin:3px 0 0;width:100%;min-width:0;padding:1px 5px;border:1px solid var(--line-3);border-radius:4px;background:var(--surface-1);color:var(--text-1);font:600 16px inherit;letter-spacing:-.01em}.drawer-head-name{min-width:0;flex:1}.connection-dot,.pane-live{width:6px;height:6px;border-radius:50%;background:var(--green);display:inline-block;flex:none}.tab-meta{font:11px JetBrains Mono,monospace;color:var(--text-3);margin-left:2px}.window-actions{margin-left:auto;display:flex;align-items:center;gap:4px;flex:none}.bar-button{height:30px;display:flex;align-items:center;gap:7px;padding:0 9px;border-radius:4px;color:var(--text-2);background:transparent}.bar-button:hover{background:var(--surface-4);color:var(--text-1)}.icon{font-size:14px;line-height:1;color:currentColor}.icon-svg{width:14px;height:14px;display:block;flex:none}.square-button .icon-svg,.rail-button .icon-svg,.layout-button .icon-svg,.inline-restore .icon-svg{width:15px;height:15px}.body-shell{height:calc(100vh - 48px);display:flex}.rail{width:48px;display:flex;flex-direction:column;align-items:center;gap:5px;padding:10px 0;border-right:1px solid var(--line-1);background:var(--surface-1);flex:none}.rail-button{width:32px;height:32px;display:grid;place-items:center;border-radius:5px;color:var(--text-3);background:transparent}.rail-button:hover,.rail-button.active{background:var(--surface-4);color:var(--text-1)}.rail-button.active{box-shadow:inset 2px 0 var(--blue)}.rail-spacer{flex:1}.session-drawer{width:238px;max-width:40vw;display:flex;flex-direction:column;border-right:1px solid var(--line-1);background:var(--surface-1);flex:none}.drawer-head{display:flex;align-items:center;justify-content:space-between;padding:18px 14px 12px;gap:8px}.drawer-head-actions{display:flex;align-items:center;gap:6px}.eyebrow{display:block;color:var(--text-3);font-size:10px;font-weight:700;letter-spacing:.1em}.drawer-head h1{margin:3px 0 0;font-size:16px;font-weight:600;letter-spacing:-.01em}.square-button{width:27px;height:27px;display:grid;place-items:center;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2)}.square-button:hover{border-color:var(--blue);color:var(--text-1)}.drawer-tools{display:flex;gap:2px;padding:0 10px 10px;border-bottom:1px solid var(--line-1)}.drawer-tool{padding:5px 7px;border-radius:4px;color:var(--text-3);background:transparent;font-size:11px}.drawer-tool span{margin-left:5px;color:var(--text-4)}.drawer-tool:hover,.drawer-tool.active{background:var(--surface-4);color:var(--text-1)}.session-list{padding:8px 7px;overflow:auto;flex:1}.session-row{width:100%;display:flex;align-items:center;gap:9px;padding:9px 8px;text-align:left;border-radius:5px;background:transparent;color:var(--text-2)}.session-row:hover{background:var(--surface-3)}.session-row.active{background:var(--surface-4);color:var(--text-1);box-shadow:inset 2px 0 var(--blue)}.status-dot{width:6px;height:6px;border-radius:50%;background:var(--text-4);flex:none}.status-dot.connected{background:var(--green)}.status-dot.detached{background:var(--yellow)}.status-dot.error{background:var(--red)}.mcp-stop-button{color:var(--red);border-color:rgba(var(--red-rgb),.45)}.mcp-settings{border-top:1px solid var(--border);margin-top:18px;padding-top:18px}.mcp-state-dot{width:9px;height:9px;border-radius:50%;background:var(--muted);box-shadow:0 0 0 3px #8080801f}.mcp-state-dot.listening{background:var(--green)}.mcp-state-dot.connected{background:var(--teal);box-shadow:0 0 0 3px rgba(var(--teal-rgb),.16)}.mcp-state-dot.revoked,.mcp-state-dot.error{background:var(--red)}.settings-value{display:block;overflow-wrap:anywhere;padding:8px 10px;background:var(--panel);border:1px solid var(--border)}.token-value{max-height:72px;overflow:auto}.session-copy{min-width:0;display:flex;flex-direction:column;gap:2px;flex:1}.session-copy b{font-size:12px;font-weight:550;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-copy small{font:10px JetBrains Mono,monospace;color:var(--text-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-state{font:11px JetBrains Mono,monospace;color:var(--text-3)}.session-empty{display:flex;flex-direction:column;gap:6px;padding:18px 10px;color:var(--text-3)}.session-empty b{color:var(--text-2);font-size:12px;font-weight:550}.session-empty small{font-size:11px;line-height:1.45}.forward-row{gap:7px}.forward-open{flex:1;min-width:0;display:flex;padding:0;text-align:left;background:transparent;color:inherit}.forward-badge{flex:none;padding:1px 5px;border:1px solid var(--yellow);border-radius:3px;color:var(--yellow);font:9px JetBrains Mono,monospace;letter-spacing:.04em}.forward-close{flex:none;width:18px;height:18px;display:grid;place-items:center;padding:0;border-radius:3px;background:transparent;color:var(--text-3);font-size:13px;line-height:1;opacity:0}.forward-row:hover .forward-close,.forward-close:focus-visible{opacity:1}.forward-close:hover{background:var(--line-3);color:var(--red)}.dialog-disclosure{align-self:flex-start;padding:3px 0;background:transparent;color:var(--blue);font-size:11px}.dialog-check{flex-direction:row;align-items:center;gap:8px}.dialog-check input{width:auto;margin:0}.dialog-warning{margin:0;padding:8px 10px;border:1px solid var(--yellow);border-radius:4px;color:var(--yellow);font-size:11px;line-height:1.45}.session-group-label{padding:8px 10px 4px;color:var(--text-3);font:10px JetBrains Mono,monospace;letter-spacing:.04em}.drawer-bottom{margin-top:auto;padding:10px 9px;border-top:1px solid var(--line-1)}.drawer-action{width:100%;display:flex;align-items:center;gap:8px;padding:7px 5px;color:var(--text-2);background:transparent;text-align:left;border-radius:4px;font-size:11px}.drawer-action:hover{background:var(--surface-3);color:var(--text-1)}.drawer-action kbd{margin-left:auto;color:var(--text-4);font:9px JetBrains Mono,monospace}.drawer-status{display:flex;align-items:center;gap:8px;margin-top:13px;padding:5px;color:var(--text-3);font:10px JetBrains Mono,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.workspace{display:flex;flex:1;flex-direction:column;min-width:0;background:var(--surface-0)}.workspace-head{height:42px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--line-1);background:var(--surface-1)}.location{display:flex;align-items:center;gap:8px;color:var(--text-2);font:11px JetBrains Mono,monospace;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.inline-restore{width:24px;height:24px;display:grid;place-items:center;border-radius:4px;color:var(--text-2);background:var(--surface-3);border:1px solid var(--line-2)}.inline-restore:hover{color:var(--text-1);border-color:var(--blue)}.slash{color:var(--line-3)}.muted-text{color:var(--text-3)}.layout-controls{display:flex;align-items:center;gap:2px}.head-controls{display:flex;align-items:center;gap:18px}.control-label{font-size:9px;color:var(--text-4);letter-spacing:.08em;margin-right:6px}.layout-button{width:26px;height:25px;display:grid;place-items:center;border-radius:4px;background:transparent;color:var(--text-3)}.layout-button:hover:not(:disabled),.layout-button.active{background:var(--surface-4);color:var(--green)}.layout-button:disabled{opacity:.4;cursor:default}.layout-button .icon-svg{width:15px;height:15px}.pane-grid{display:grid;flex:1;min-height:0;gap:1px;background:var(--line-1);padding:0;position:relative}.sidebar-resizer,.pane-resizer{background:transparent;border:none;padding:0;transition:background .12s ease}.sidebar-resizer:hover,.pane-resizer:hover,.sidebar-resizer.dragging,.pane-resizer.dragging{background:rgba(var(--blue-rgb),.5)}.sidebar-resizer:focus-visible,.pane-resizer:focus-visible{outline:none;background:var(--blue)}.sidebar-resizer{width:5px;flex:none;margin-left:-3px;margin-right:-2px;cursor:col-resize;position:relative;z-index:2}.pane-resizer{position:absolute;z-index:3}.pane-resizer-column{top:0;bottom:0;width:7px;transform:translate(-50%);cursor:col-resize}.pane-resizer-row{left:0;right:0;height:7px;transform:translateY(-50%);cursor:row-resize}.pane-body{flex:1;min-height:0;min-width:0;display:flex;flex-direction:column;position:relative}.pane-body-split{display:grid;grid-template-rows:1fr;gap:1px;background:var(--line-1)}.pane-browser-resizer{top:0;bottom:0;width:7px;transform:translate(-50%);cursor:col-resize}.pane-browser{min-width:0;min-height:0;display:flex;flex-direction:column;background:var(--surface-0);font:11px JetBrains Mono,monospace;color:var(--text-2)}.pane-browser-head{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:4px 4px 4px 8px;border-bottom:1px solid var(--line-1)}.pane-browser-path{min-width:0;overflow:hidden;direction:rtl;text-align:left;white-space:nowrap;text-overflow:ellipsis;color:var(--text-1)}.pane-browser-actions{display:flex;align-items:center;gap:8px;flex:none}.pane-browser-hidden-toggle{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text-3);white-space:nowrap;-webkit-user-select:none;user-select:none}.pane-browser-hidden-toggle input{margin:0}.pane-browser-icon{width:19px;height:19px;display:grid;place-items:center;padding:0;border:1px solid transparent;border-radius:3px;color:var(--text-3);background:transparent}.pane-browser-icon:hover:not(:disabled){color:var(--text-1);background:var(--surface-4);border-color:var(--line-2)}.pane-browser-icon:disabled{opacity:.4;cursor:default}.pane-browser-list{flex:1;min-height:0;overflow-y:auto;padding:2px 0}.pane-browser-row{width:100%;display:flex;align-items:center;gap:6px;padding:2px 8px;border:none;background:transparent;color:var(--text-1);text-align:left;font:inherit;-webkit-user-select:none;user-select:none}.pane-browser-row:hover:not(:disabled){background:var(--surface-3)}.pane-browser-row:focus-visible{outline:1px solid var(--blue);outline-offset:-1px}.pane-browser-row.pane-browser-file{color:var(--text-3);cursor:default}.pane-browser-parent{color:var(--blue)}.pane-browser-name{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.pane-browser-kind{margin-left:auto;flex:none;color:var(--text-3);font-size:10px}.pane-browser-note{margin:0;padding:8px;color:var(--text-3);line-height:1.5}.pane-browser-error{color:var(--red)}.pane-browser-asking{color:var(--yellow)}.pane-browser-foot{flex:none;display:flex;align-items:center;justify-content:space-between;gap:6px;padding:3px 4px 3px 8px;border-top:1px solid var(--line-1);color:var(--text-3);font-size:10px}.pane-browser-send{flex:none;width:21px;height:21px;display:grid;place-items:center;padding:0;border:1px solid var(--line-2);border-radius:4px;color:var(--blue);background:var(--surface-2)}.pane-browser-send:hover:not(:disabled){color:var(--green);background:rgba(var(--green-rgb),.14);border-color:rgba(var(--green-rgb),.45)}.pane-browser-send:disabled{opacity:.4;cursor:default}.pane-browse.active{color:var(--blue);background:var(--surface-3);border-color:var(--line-2)}.pane-grid.maximized-pane-grid .pane-resizer{display:none}.pane-grid.split-v{grid-template-columns:1fr 1fr}.pane-grid.split-h{grid-template-rows:1fr 1fr}.pane-grid.grid{grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr}.pane-grid.maximized-pane-grid{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}.pane-grid:not(.maximized-pane-grid) .overflow-pane,.pane-grid.maximized-pane-grid .pane:not(.maximized-pane){display:none}.pane.dormant-pane{display:none}.pane{min-width:0;min-height:0;display:flex;flex-direction:column;background:var(--surface-0)}.terminal-pane{outline:1px solid var(--line-1);outline-offset:-1px}.terminal-pane.focused{outline:1px solid var(--blue);outline-offset:-1px}.pane-title{height:29px;display:flex;align-items:center;justify-content:space-between;padding:0 9px;border-bottom:1px solid var(--line-1);color:var(--text-2);font:10px JetBrains Mono,monospace}.pane-actions,.pane-dim{color:var(--text-4);font-size:9px}.pane-actions{display:flex;align-items:center;gap:5px}.pane-nav,.pane-maximize,.pane-close,.pane-proceed,.pane-ai,.pane-custom,.pane-edit,.pane-link,.pane-browse,.pane-mic{width:19px;height:19px;display:grid;place-items:center;padding:0;border:1px solid rgba(255,255,255,.14);border-radius:3px;color:var(--icon);background:#ffffff0d}.pane-proceed{color:var(--green);background:rgba(var(--green-rgb),.08);border-color:rgba(var(--green-rgb),.3)}.pane-ai{color:var(--blue);background:rgba(var(--blue-rgb),.08);border-color:rgba(var(--blue-rgb),.3)}.pane-custom{color:var(--yellow);background:rgba(var(--yellow-rgb),.08);border-color:rgba(var(--yellow-rgb),.3)}.pane-nav:hover,.pane-maximize:hover,.pane-browse:hover:not(:disabled),.pane-mic:hover{color:var(--text-1);background:var(--surface-4);border-color:var(--line-2)}.pane-close:hover{color:var(--red);background:rgba(var(--red-rgb),.14);border-color:rgba(var(--red-rgb),.4)}.pane-proceed:hover{color:var(--green);background:rgba(var(--green-rgb),.14);border-color:rgba(var(--green-rgb),.45)}.pane-custom{font:600 10px/1 inherit;cursor:pointer}.pane-custom:hover{color:var(--yellow);background:rgba(var(--yellow-rgb),.14);border-color:rgba(var(--yellow-rgb),.45)}.pane-ai:hover{color:var(--blue);background:rgba(var(--blue-rgb),.14);border-color:rgba(var(--blue-rgb),.45)}.pane-edit:hover:not(:disabled){color:var(--text-1);background:var(--surface-4);border-color:var(--line-2)}.pane-edit:disabled,.pane-mic:disabled,.pane-browse:disabled{opacity:.4;cursor:default}.pane-mic.listening{color:var(--red);background:rgba(var(--red-rgb),.16);border-color:rgba(var(--red-rgb),.55);animation:mic-pulse 1.2s ease-in-out infinite}.pane-mic.transcribing{color:var(--yellow);background:rgba(var(--yellow-rgb),.14);border-color:rgba(var(--yellow-rgb),.5);animation:mic-pulse .8s ease-in-out infinite}@keyframes mic-pulse{0%,to{transform:scale(1)}50%{transform:scale(1.15)}}.pane-nav .icon-svg,.pane-maximize .icon-svg,.pane-close .icon-svg,.pane-proceed .icon-svg,.pane-ai .icon-svg,.pane-edit .icon-svg,.pane-mic .icon-svg{width:13px;height:13px}.pane-live{width:5px;height:5px;margin-right:5px}.terminal{flex:1;min-height:0;min-width:0;padding:0;overflow:visible;position:relative;background:var(--surface-0);line-height:1.2;font-variant-ligatures:none}.terminal .xterm{position:absolute;inset:0;height:100%!important;width:100%!important;padding:0;box-sizing:border-box}.terminal .xterm-viewport{overflow-y:auto!important}.terminal .xterm-screen{position:relative}.terminal .xterm-rows{line-height:inherit}.empty-pane{background:var(--surface-1)}.empty-pane-body{display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;height:100%;color:var(--text-3)}.empty-glyph{display:grid;place-items:center;width:30px;height:30px;border:1px dashed var(--line-3);border-radius:5px;color:var(--text-3);font-size:18px}.empty-pane-body strong{font-size:11px;color:var(--text-2);font-weight:550}.empty-pane-body small{font-size:10px}.empty-pane-body button{margin-top:8px;padding:6px 9px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2);font-size:10px}.empty-pane-body button:hover{border-color:var(--blue);color:var(--text-1)}.status-bar{height:25px;display:flex;align-items:center;gap:8px;padding:0 10px;border-top:1px solid var(--line-1);background:var(--surface-1);color:var(--text-3);font:10px JetBrains Mono,monospace}.status-separator{width:1px;height:11px;background:var(--line-2)}.status-spacer{flex:1}.overview-layer,.modal-layer{position:fixed;inset:0;z-index:20;display:grid;place-items:center;background:#07090dcc}.overview{width:min(780px,calc(100vw - 100px));background:var(--surface-2);border:1px solid var(--line-3);border-radius:8px;box-shadow:0 20px 80px #0009}.overview-head,.modal-head{display:flex;justify-content:space-between;align-items:flex-start;padding:18px 20px 14px;border-bottom:1px solid var(--line-1)}.overview-head h2,.modal-head h2{margin:4px 0 0;font-size:17px;font-weight:600}.close-button{padding:4px 7px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-3);font:10px JetBrains Mono,monospace}.close-button:hover{color:var(--text-1);border-color:var(--blue)}.thumbnail-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:18px 20px}.thumbnail{padding:0;text-align:left;background:var(--surface-1);border:1px solid var(--line-2);border-radius:6px;overflow:hidden}.thumbnail:hover,.thumbnail.active{border-color:var(--blue)}.thumbnail.active{box-shadow:0 0 0 1px rgba(var(--blue-rgb),.2)}.thumbnail-preview{height:100px;padding:13px;background:var(--surface-0);border-bottom:1px solid var(--line-1);display:flex;flex-direction:column;gap:6px;position:relative}.thumbnail-line{height:3px;width:70%;background:var(--text-4);border-radius:2px}.thumbnail-line.short{width:43%}.thumbnail-line.cyan{background:var(--cyan)}.thumbnail-line.green{background:var(--green);width:56%}.thumbnail-cursor{position:absolute;left:13px;bottom:15px;width:5px;height:9px;background:var(--green)}.thumbnail-label{display:flex;flex-direction:column;gap:3px;padding:9px 10px}.thumbnail-label b{font-size:11px;font-weight:550;color:var(--text-1)}.thumbnail-label small{font:10px JetBrains Mono,monospace;color:var(--text-3)}.overview-empty{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;gap:7px;padding:36px;color:var(--text-3)}.overview-empty span{font-size:24px}.overview-empty b{color:var(--text-1);font-size:12px}.overview-empty small{font-size:11px}.overview-foot{display:flex;align-items:center;gap:8px;padding:12px 20px;border-top:1px solid var(--line-1)}.overview-foot button{padding:7px 9px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2);font-size:10px}.overview-foot button:hover{color:var(--text-1);border-color:var(--blue)}.overview-hint{margin-left:auto;color:var(--text-4);font:10px JetBrains Mono,monospace}.shortcut-list{display:flex;flex-direction:column;gap:2px;margin:4px 0 16px}.shortcut-row{display:grid;grid-template-columns:1fr auto auto;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid var(--surface-4)}.shortcut-what{color:var(--text-1);font-size:12px}.shortcut-chord{padding:3px 6px;border:1px solid var(--line-2);border-radius:5px;background:var(--surface-3);color:var(--text-1);min-width:118px}.shortcut-chord:hover{border-color:#3b82f6}.shortcut-chord.capturing{border-color:var(--blue);background:color-mix(in srgb,var(--blue) 11%,var(--surface-1))}.shortcut-chord kbd{font:10px JetBrains Mono,monospace}.shortcut-prompt{color:var(--blue);font:10px JetBrains Mono,monospace}.shortcut-reset{padding:3px 8px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--text-3);font-size:11px}.shortcut-reset:hover:not(:disabled){color:var(--text-1);background:var(--surface-4);border-color:var(--line-2)}.shortcut-reset:disabled{opacity:.35;cursor:default}.shortcut-problem{grid-column:1 / -1;color:var(--yellow);font-size:11px;line-height:1.4}.settings-warning{padding:9px 11px;border:1px solid rgba(var(--yellow-rgb),.4);border-radius:6px;background:rgba(var(--yellow-rgb),.08);color:var(--yellow)!important}.context-menu{position:fixed;z-index:40;min-width:178px;padding:4px;background:var(--surface-2);border:1px solid var(--line-3);border-radius:7px;box-shadow:0 14px 44px #0009}.context-menu-item{width:100%;display:flex;align-items:center;gap:8px;padding:6px 8px;border:none;border-radius:4px;background:transparent;color:var(--text-1);font-size:12px;text-align:left}.context-menu-item:hover:not(:disabled){background:var(--surface-4)}.context-menu-item:focus-visible{outline:1px solid var(--blue);outline-offset:-1px}.context-menu-item:disabled{opacity:.38;cursor:default}.context-menu-item.danger:hover:not(:disabled){color:var(--red);background:rgba(var(--red-rgb),.14)}.context-menu-item .icon-svg{width:14px;height:14px;flex:none;color:var(--text-2)}.context-menu-gap{width:14px;flex:none}.help-card{width:min(880px,calc(100vw - 80px));max-height:calc(100vh - 80px);display:flex;flex-direction:column;padding:0 20px 14px;background:var(--surface-2);border:1px solid var(--line-3);border-radius:8px;box-shadow:0 20px 80px #0009}.help-body{display:grid;grid-template-columns:minmax(300px,360px) 1fr;gap:22px;overflow-y:auto;padding-bottom:4px}@media(max-width:820px){.help-body{grid-template-columns:1fr}}.help-body h3{margin:0 0 8px;color:var(--text-2);font:600 10px JetBrains Mono,monospace;letter-spacing:.08em;text-transform:uppercase}.help-keys table{width:100%;border-collapse:collapse}.help-keys tr:not(:last-child) th,.help-keys tr:not(:last-child) td{border-bottom:1px solid var(--surface-4)}.help-keys th{width:1%;padding:5px 10px 5px 0;text-align:left;font-weight:400;white-space:nowrap;vertical-align:top}.help-keys kbd{display:inline-block;padding:2px 5px;border:1px solid var(--line-2);border-bottom-width:2px;border-radius:4px;background:var(--surface-3);color:var(--text-1);font:10px JetBrains Mono,monospace;white-space:nowrap}.help-keys td{padding:5px 0;color:var(--text-2);font-size:12px;line-height:1.45}.help-note{display:flex;gap:8px;margin:14px 0 0;padding:10px;border:1px solid var(--line-2);border-radius:6px;background:var(--surface-1);color:var(--text-2);font-size:11.5px;line-height:1.5}.help-note .icon-svg{flex:none;width:15px;height:15px;margin-top:1px;color:var(--blue)}.help-features dl{margin:0}.help-features dt{margin-top:12px;color:var(--text-1);font-size:12.5px;font-weight:600}.help-features dt:first-child{margin-top:0}.help-features dd{margin:3px 0 0;color:var(--text-2);font-size:12px;line-height:1.5}.help-foot{flex:none;padding-top:12px;color:var(--text-3);font-size:10.5px}.modal-card{width:390px;padding:0 20px 18px;background:var(--surface-2);border:1px solid var(--line-3);border-radius:8px;box-shadow:0 20px 80px #0009}.modal-card p{color:var(--text-2);font-size:12px;line-height:1.5;margin:17px 0}.modal-card label{display:block;margin-top:14px;color:var(--text-2);font-size:11px}.modal-card input{display:block;width:100%;margin-top:6px;padding:9px 10px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);color:var(--text-1);font:12px JetBrains Mono,monospace}.modal-card input:focus{border-color:var(--blue);outline:0}.dialog-tabs{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-top:16px}.dialog-tab{display:flex;flex-direction:column;gap:3px;padding:9px 11px;border:1px solid var(--line-2);border-radius:6px;background:var(--surface-1);color:var(--text-2);text-align:left}.dialog-tab b{color:var(--text-1);font-size:12px;font-weight:550}.dialog-tab small{font:10px JetBrains Mono,monospace;color:var(--text-4)}.dialog-tab:hover:not(:disabled){border-color:var(--blue)}.dialog-tab:disabled{opacity:.6}.dialog-tab.active{border-color:var(--blue);background:color-mix(in srgb,var(--blue) 10%,var(--surface-1));box-shadow:0 0 0 1px rgba(var(--blue-rgb),.2)}.modal-actions{display:flex;justify-content:flex-end;gap:7px;margin-top:20px}.modal-actions button{padding:8px 11px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2);font-size:11px}.modal-actions button:hover{color:var(--text-1);border-color:var(--blue)}.primary-button{background:var(--green)!important;border-color:var(--green)!important;color:var(--surface-0)!important;font-weight:600}.primary-button:disabled{opacity:.55;cursor:wait}.approval-card code{display:block;padding:11px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);color:var(--cyan);font:12px JetBrains Mono,monospace}.warning-text{color:var(--yellow)}@media(max-width:1100px){.session-drawer{max-width:30vw}.window-brand{width:90px}.brand-name,.brand-version,.bar-button span{display:none}.thumbnail-grid{grid-template-columns:repeat(2,1fr)}}.history-button{color:var(--blue)}.session-ghosted{opacity:.58;border:1px dashed var(--line-3)}.session-ghosted:hover{opacity:.9}.session-error b{color:var(--red)}.session-empty button{align-self:flex-start;padding:5px 8px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2);font-size:10px}.history-empty button:hover{border-color:var(--blue);color:var(--text-1)}.palette-layer{position:fixed;inset:0;z-index:25;background:#07090d99}.palette{position:fixed;z-index:30;top:80px;left:50%;transform:translate(-50%);width:min(720px,calc(100vw - 80px));display:flex;flex-direction:column;padding:10px;border:1px solid var(--line-3);border-radius:7px;background:var(--surface-2);box-shadow:0 18px 55px #0009}.palette-query{width:100%;padding:8px 10px;border:1px solid var(--line-3);border-radius:5px;background:var(--surface-1);color:var(--text-1)}.palette-list{margin-top:8px;max-height:min(420px,calc(100vh - 220px));overflow:auto}.palette-row{width:100%;display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:7px 8px;border-radius:4px;text-align:left;background:transparent;color:var(--text-2)}.palette-row.active{background:var(--surface-4);color:var(--text-1);box-shadow:inset 2px 0 var(--blue)}.palette-command{min-width:0;font:12px JetBrains Mono,monospace;white-space:pre-wrap;word-break:break-all}.palette-command b{color:var(--blue);font-weight:700}.palette-meta{flex:none;display:flex;align-items:baseline;gap:8px;font:10px JetBrains Mono,monospace;color:var(--text-3)}.palette-cwd{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.palette-failed{padding:1px 4px;border:1px solid var(--red);border-radius:3px;color:var(--red)}.palette-empty{display:flex;flex-direction:column;align-items:flex-start;gap:7px;padding:18px 8px;color:var(--text-3)}.palette-empty b{color:var(--text-2);font-size:12px;font-weight:550}.palette-empty small{font-size:11px;line-height:1.45}:root[data-theme=light] .palette-layer{background:#25304433}:root[data-theme=light] .palette-query,:root[data-theme=light] .settings-snippet{background:#f7f9fc;border-color:#dbe2ec;color:#253044}:root[data-theme=light] .palette-row.active{background:#dbe7f5;color:#253044}:root[data-theme=light] .palette-command b{color:#2459a6}:root[data-theme=light] .palette-failed{border-color:#b3213b;color:#b3213b}:root[data-theme=light] .settings-note{border-color:#dbe2ec;color:#536176}.palette-note{display:block;padding:9px 4px 2px;color:var(--text-3);font-size:10px;line-height:1.4}.settings-snippet{margin:8px 0 0;max-height:220px;overflow:auto;padding:10px;border:1px solid var(--line-1);border-radius:5px;background:var(--surface-1);color:var(--text-2);font:10px JetBrains Mono,monospace;line-height:1.5;white-space:pre-wrap}.settings-note{margin-top:12px;padding:9px 10px;border:1px solid var(--line-2);border-radius:5px;color:var(--text-2);font-size:11px;line-height:1.45}.history-popover{position:fixed;z-index:30;top:58px;left:245px;width:285px;max-height:min(440px,calc(100vh - 76px));overflow:auto;padding:10px;border:1px solid var(--line-3);border-radius:7px;background:var(--surface-2);box-shadow:0 18px 55px #0009}.history-layer{position:fixed;inset:0;z-index:25}.history-head{display:flex;justify-content:space-between;align-items:center;padding:3px 4px 9px;color:var(--text-1)}.history-head button{background:transparent;color:var(--text-3);font-size:18px}.history-item{width:100%;display:flex;align-items:center;justify-content:space-between;gap:9px;padding:8px 5px;text-align:left;border-top:1px solid var(--line-1);background:transparent;color:var(--text-2)}.history-main{display:flex;align-items:center;gap:9px;min-width:0}.history-text{display:flex;flex-direction:column;align-items:flex-start;min-width:0}.history-remove{margin-left:8px;background:transparent;color:#536176;border:none;cursor:pointer;font-size:16px;line-height:1}.history-item:hover .history-remove{color:#253044}:root[data-theme=light] .history-remove{color:#68768a}.history-item:hover{background:var(--surface-4);color:var(--text-1)}.history-item b,.history-item small{display:block}.history-item small,.history-note,.history-empty{color:var(--text-3);font-size:10px}.history-kind{min-width:43px;color:var(--blue);font:9px JetBrains Mono,monospace}.history-empty{padding:13px 5px}.history-note{display:block;padding:9px 4px 2px;line-height:1.4}:root[data-theme=light] .palette,:root[data-theme=light] .history-popover{background:#fff;border-color:#c7d2e1;color:#253044}:root[data-theme=light] .history-head{color:#253044}:root[data-theme=light] .history-head button{color:#536176}:root[data-theme=light] .history-item{border-top-color:#dbe2ec;color:#536176;background:transparent}:root[data-theme=light] .history-item:hover{background:#e1e9f4;color:#253044}:root[data-theme=light] .history-item small,:root[data-theme=light] .history-note,:root[data-theme=light] .history-empty{color:#68768a}:root[data-theme=light] .history-kind{color:#2459a6}:root[data-theme=light]{color:#253044;background:#f7f9fc;color-scheme:light}:root[data-theme=light] body,:root[data-theme=light] .app-shell,:root[data-theme=light] .workspace,:root[data-theme=light] .pane,:root[data-theme=light] .terminal,:root[data-theme=light] .thumbnail-preview{background:#f7f9fc}:root[data-theme=light] .window-bar,:root[data-theme=light] .session-drawer,:root[data-theme=light] .status-bar{background:#fff;border-color:#dbe2ec}:root[data-theme=light] .rail,:root[data-theme=light] .workspace-head,:root[data-theme=light] .empty-pane{background:#eef2f7;border-color:#dbe2ec}:root[data-theme=light] .pane-grid{background:#dbe2ec}:root[data-theme=light] .workspace-tab,:root[data-theme=light] .bar-button,:root[data-theme=light] .rail-button,:root[data-theme=light] .drawer-tool,:root[data-theme=light] .drawer-action,:root[data-theme=light] .location,:root[data-theme=light] .layout-button{color:#5c687b}:root[data-theme=light] .workspace-tab.active,:root[data-theme=light] .bar-button:hover,:root[data-theme=light] .rail-button:hover,:root[data-theme=light] .rail-button.active,:root[data-theme=light] .drawer-tool:hover,:root[data-theme=light] .drawer-tool.active,:root[data-theme=light] .session-row:hover,:root[data-theme=light] .inline-restore,:root[data-theme=light] .layout-button:hover:not(:disabled),:root[data-theme=light] .layout-button.active{background:#e1e9f4;color:#253044}:root[data-theme=light] .workspace-tab.active,:root[data-theme=light] .session-row.active{border-color:#c7d5e8;background:#dbe7f5;color:#253044}:root[data-theme=light] .session-row,:root[data-theme=light] .pane-title,:root[data-theme=light] .drawer-head h1,:root[data-theme=light] .empty-pane-body strong,:root[data-theme=light] .thumbnail-label b,:root[data-theme=light] .overview-empty b{color:#253044}:root[data-theme=light] .eyebrow,:root[data-theme=light] .tab-meta,:root[data-theme=light] .session-copy small,:root[data-theme=light] .session-state,:root[data-theme=light] .session-empty,:root[data-theme=light] .drawer-status,:root[data-theme=light] .muted-text,:root[data-theme=light] .pane-actions,:root[data-theme=light] .pane-dim,:root[data-theme=light] .status-bar,:root[data-theme=light] .overview-hint,:root[data-theme=light] .thumbnail-label small,:root[data-theme=light] .overview-empty,:root[data-theme=light] .modal-card p,:root[data-theme=light] .modal-card label{color:#68768a}:root[data-theme=light] .brand-mark{color:#2459a6;border-color:#6d9bd6}:root[data-theme=light] .square-button,:root[data-theme=light] .inline-restore,:root[data-theme=light] .empty-pane-body button,:root[data-theme=light] .overview-foot button,:root[data-theme=light] .close-button,:root[data-theme=light] .modal-actions button,:root[data-theme=light] .modal-card input,:root[data-theme=light] .approval-card code{background:#fff;border-color:#c7d2e1;color:#536176}:root[data-theme=light] .session-row.active{box-shadow:inset 2px 0 #2459a6}:root[data-theme=light] .rail-button.active{box-shadow:inset 2px 0 #2459a6}:root[data-theme=light] .layout-button.active{color:#16803c}:root[data-theme=light] .pane-close:hover{color:#c53030}:root[data-theme=light] .pane-proceed:hover{color:#16803c;background:#16803c1a;border-color:#16803c59}:root[data-theme=light] .pane-ai:hover{color:#2459a6;background:#2459a61a;border-color:#2459a659}:root[data-theme=light] .pane-mic.listening{color:#c53030}:root[data-theme=light] .pane-mic.transcribing{color:#b7791f}:root[data-theme=light] .overview,:root[data-theme=light] .modal-card{background:#fff;border-color:#c7d2e1;box-shadow:0 20px 80px #53617633}:root[data-theme=light] .overview-head,:root[data-theme=light] .modal-head,:root[data-theme=light] .overview-foot,:root[data-theme=light] .pane-title{border-color:#dbe2ec}:root[data-theme=light] .thumbnail{background:#eef2f7;border-color:#c7d2e1}:root[data-theme=light] .thumbnail-preview,:root[data-theme=light] .approval-card code,:root[data-theme=light] .workspace-rename,:root[data-theme=light] .modal-card input{background:#f7f9fc;border-color:#dbe2ec}:root[data-theme=light] .workspace-rename{color:#253044}:root[data-theme=light] .workspace-tab-close,:root[data-theme=light] .forward-close{color:#5c687b}:root[data-theme=light] .workspace-tab-close:hover{background:#c7d5e8;color:#253044}:root[data-theme=light] .forward-close:hover{background:#c7d5e8;color:#b3213b}:root[data-theme=light] .forward-badge,:root[data-theme=light] .dialog-warning{border-color:#b07d1a;color:#8a5d00}:root[data-theme=light] .dialog-disclosure{color:#2459a6}:root[data-theme=light] .modal-layer,:root[data-theme=light] .overview-layer{background:#25304433}:root[data-theme=light] .empty-glyph{border-color:#b8c6d8}:root[data-theme=light] .primary-button{background:#16803c!important;border-color:#16803c!important;color:#fff!important}.settings-card{display:flex;flex-direction:column;width:min(860px,calc(100vw - 80px));max-height:min(680px,calc(100vh - 90px));background:var(--surface-2);border:1px solid var(--line-3);border-radius:8px;box-shadow:0 20px 80px #0009;overflow:hidden}.settings-body{display:grid;grid-template-columns:196px 1fr;min-height:0;flex:1}.settings-nav{display:flex;flex-direction:column;gap:2px;padding:12px 10px;border-right:1px solid var(--line-1);background:var(--surface-1);overflow:auto}.settings-nav-item{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border:1px solid transparent;border-radius:5px;background:none;color:var(--text-2);text-align:left}.settings-nav-item b{font-size:12px;font-weight:550}.settings-nav-item small{font:10px JetBrains Mono,monospace;color:var(--text-4)}.settings-nav-item:hover{background:var(--surface-3);color:var(--text-1)}.settings-nav-item.active{background:color-mix(in srgb,var(--blue) 10%,var(--surface-1));border-color:color-mix(in srgb,var(--blue) 25%,var(--surface-1));color:var(--text-1);box-shadow:inset 2px 0 var(--blue)}.settings-page{padding:16px 20px 18px;overflow:auto}.settings-blurb{margin:0 0 4px;color:var(--text-2);font-size:12px;line-height:1.5}.settings-field{display:block;margin-top:14px;color:var(--text-2);font-size:11px}.settings-field-label{display:block;margin-bottom:6px}.settings-field small{display:block;margin-top:5px;color:var(--text-3);font-size:10px;line-height:1.45}.settings-field select,.settings-field input{display:block;width:100%;padding:8px 10px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);color:var(--text-1);font:12px JetBrains Mono,monospace}.settings-field select:focus,.settings-field input:focus{border-color:var(--blue);outline:0}.settings-range{display:flex;align-items:center;gap:10px}.settings-range input[type=range]{flex:1;padding:0;border:0;background:none;accent-color:var(--blue)}.settings-range output{min-width:88px;text-align:right;color:var(--text-1);font:11px JetBrains Mono,monospace}.settings-toggle{display:flex;align-items:flex-start;gap:9px;margin-top:16px}.settings-toggle input{margin-top:2px;accent-color:var(--green)}.settings-toggle span{display:flex;flex-direction:column;gap:3px}.settings-toggle b{color:var(--text-1);font-size:12px;font-weight:550}.settings-toggle small{color:var(--text-3);font-size:10px;line-height:1.45}.settings-toggle-fixed{cursor:default}.settings-toggle-fixed b{color:var(--text-2)}.settings-toggle-fixed input{accent-color:var(--text-4)}.settings-preview{display:flex;flex-direction:column;margin-top:16px;padding:12px;border:1px solid var(--line-1);border-radius:5px;background:var(--surface-0);color:var(--green);overflow-x:auto;white-space:pre}.settings-engines{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:12px}.settings-engine{display:flex;flex-direction:column;gap:5px;padding:11px 12px;border:1px solid var(--line-2);border-radius:6px;background:var(--surface-1);color:var(--text-2);text-align:left}.settings-engine b{color:var(--text-1);font-size:12px;font-weight:600}.settings-engine small{font-size:10px;line-height:1.45}.settings-engine:hover{border-color:var(--blue)}.settings-engine.active{border-color:var(--blue);box-shadow:0 0 0 1px rgba(var(--blue-rgb),.2)}.settings-test{margin-top:18px;padding:12px;border:1px solid var(--line-2);border-radius:6px;background:var(--surface-1)}.settings-test-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.settings-test-head b{display:block;color:var(--text-1);font-size:12px;font-weight:550}.settings-test-head small{color:var(--text-3);font-size:10px;line-height:1.45}.settings-test-button{flex:none;padding:7px 11px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2);font-size:11px}.settings-test-button:hover:not(:disabled){border-color:var(--blue);color:var(--text-1)}.settings-test-button:disabled{opacity:.6;cursor:wait}.settings-test-button.recording{border-color:var(--red);color:var(--red)}.settings-test-status,.settings-test-metrics{margin:9px 0 0;color:var(--text-3);font:10px JetBrains Mono,monospace}.settings-test-error{margin:9px 0 0;color:var(--red);font-size:11px;line-height:1.45}.settings-key-row{display:flex;align-items:center;gap:8px}.settings-key-row input{flex:1;min-width:0}.settings-key-row button{flex:none;padding:8px 11px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2);font-size:11px}.settings-key-row button:hover:not(:disabled){border-color:var(--blue);color:var(--text-1)}.settings-key-row button:disabled{opacity:.5}.settings-field .settings-key-notice{color:var(--green)}.settings-field .settings-key-error{color:var(--red)}.settings-test-transcript{display:block;margin-top:9px;padding:10px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);color:var(--cyan);font:12px JetBrains Mono,monospace;white-space:pre-wrap}.settings-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:22px;padding-top:14px;border-top:1px solid var(--line-1)}.settings-hint{color:var(--text-4);font:10px JetBrains Mono,monospace}.settings-foot button{padding:7px 11px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-3);color:var(--text-2);font-size:11px}.settings-foot button:hover{border-color:var(--yellow);color:var(--yellow)}@media(max-width:780px){.settings-body{grid-template-columns:1fr}.settings-nav{flex-direction:row;overflow-x:auto;border-right:0;border-bottom:1px solid var(--line-1)}.settings-engines{grid-template-columns:1fr}}:root[data-theme=light] .dialog-tab{background:#f2f5fa;border-color:#dbe2ec;color:#5c687b}:root[data-theme=light] .dialog-tab b{color:#253044}:root[data-theme=light] .dialog-tab small{color:#68768a}:root[data-theme=light] .dialog-tab.active{background:#dbe7f5;border-color:#6d9bd6}:root[data-theme=light] .settings-card{background:#fff;border-color:#c7d2e1}:root[data-theme=light] .settings-nav,:root[data-theme=light] .settings-engine,:root[data-theme=light] .settings-test{background:#f2f5fa;border-color:#dbe2ec}:root[data-theme=light] .settings-nav{border-right-color:#dbe2ec}:root[data-theme=light] .settings-nav-item{color:#5c687b}:root[data-theme=light] .settings-nav-item:hover{background:#e1e9f4;color:#253044}:root[data-theme=light] .settings-nav-item.active{background:#dbe7f5;border-color:#c7d5e8;color:#253044;box-shadow:inset 2px 0 #2459a6}:root[data-theme=light] .settings-nav-item small,:root[data-theme=light] .settings-blurb,:root[data-theme=light] .settings-field,:root[data-theme=light] .settings-field small,:root[data-theme=light] .settings-toggle small,:root[data-theme=light] .settings-engine small,:root[data-theme=light] .settings-test-head small,:root[data-theme=light] .settings-test-status,:root[data-theme=light] .settings-test-metrics,:root[data-theme=light] .settings-hint{color:#68768a}:root[data-theme=light] .settings-toggle b,:root[data-theme=light] .settings-engine b,:root[data-theme=light] .settings-test-head b,:root[data-theme=light] .settings-range output{color:#253044}:root[data-theme=light] .settings-field select,:root[data-theme=light] .settings-field input,:root[data-theme=light] .settings-preview,:root[data-theme=light] .settings-test-transcript,:root[data-theme=light] .settings-test-button,:root[data-theme=light] .settings-key-row button,:root[data-theme=light] .settings-foot button{background:#fff;border-color:#c7d2e1;color:#536176}:root[data-theme=light] .settings-field .settings-key-notice{color:#16803c}:root[data-theme=light] .settings-preview{color:#16803c}:root[data-theme=light] .settings-test-transcript{color:#2459a6}:root[data-theme=light] .settings-foot{border-top-color:#dbe2ec}.sftp-layer{position:fixed;inset:0;z-index:22;display:grid;place-items:center;background:#07090dcc}.sftp-panel{display:flex;flex-direction:column;width:min(1180px,calc(100vw - 80px));height:min(760px,calc(100vh - 90px));background:var(--surface-2);border:1px solid var(--line-3);border-radius:8px;box-shadow:0 20px 80px #0009;overflow:hidden}.sftp-head{display:flex;justify-content:space-between;align-items:flex-start;padding:16px 20px 13px;border-bottom:1px solid var(--line-1)}.sftp-head h2{margin:4px 0 0;font-size:16px;font-weight:600}.sftp-hidden-toggle{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--text-2);white-space:nowrap;-webkit-user-select:none;user-select:none;margin:6px 12px 0 auto}.sftp-hidden-toggle input{margin:0}.sftp-body{display:grid;grid-template-columns:1fr 1fr;gap:1px;flex:1;min-height:0;background:var(--line-1)}.sftp-pane{display:flex;flex-direction:column;min-height:0;min-width:0;background:var(--surface-2)}.sftp-pane-head{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:10px 12px 8px}.sftp-pane-head .eyebrow{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sftp-pane-tools{display:flex;gap:2px;flex:none}.sftp-pane-tools button{width:26px;height:25px;display:grid;place-items:center;border-radius:4px;background:transparent;color:var(--text-3)}.sftp-pane-tools button:hover:not(:disabled){background:var(--surface-4);color:var(--green)}.sftp-pane-tools button:disabled{opacity:.4;cursor:default}.sftp-pane-tools .sftp-danger:hover:not(:disabled){color:var(--red)}.sftp-pane-tools .icon-svg{width:15px;height:15px}.sftp-path{padding:0 12px 8px}.sftp-path input{width:100%;padding:6px 8px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);color:var(--text-1);font:11px JetBrains Mono,monospace}.sftp-path input:focus{border-color:var(--blue);outline:0}.sftp-list{flex:1;min-height:0;overflow-y:auto;border-top:1px solid var(--line-1)}.sftp-row{display:grid;grid-template-columns:16px 1fr auto auto;align-items:center;gap:9px;width:100%;padding:6px 12px;background:transparent;color:var(--text-2);text-align:left;border-bottom:1px solid var(--surface-3)}.sftp-row:hover{background:var(--surface-3)}.sftp-row.selected{background:var(--surface-4);color:var(--text-1);box-shadow:inset 2px 0 var(--green)}.sftp-row.is-directory{color:var(--blue)}.sftp-row.is-directory.selected{color:#9ecbff}.sftp-row .icon-svg{width:15px;height:15px}.sftp-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:12px JetBrains Mono,monospace}.sftp-size,.sftp-modified{font:10px JetBrains Mono,monospace;color:var(--text-4);white-space:nowrap}.sftp-modified{min-width:96px;text-align:right}.sftp-empty{display:grid;gap:4px;padding:26px 16px;text-align:center;color:var(--text-2)}.sftp-empty small{color:var(--text-4)}.sftp-error b{color:var(--red)}.sftp-pane-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border-top:1px solid var(--line-1)}.sftp-pane-foot .primary-button{display:inline-flex;align-items:center;gap:6px}.sftp-pane-foot .icon-svg{width:14px;height:14px}.sftp-count{font-size:10px;color:var(--text-4)}.sftp-prompt,.sftp-inline-form{padding:14px 20px;border-top:1px solid var(--line-1);background:var(--surface-3)}.sftp-prompt pre{margin:8px 0 0;padding:9px 10px;max-height:120px;overflow:auto;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);color:var(--yellow);font:11px JetBrains Mono,monospace;white-space:pre-wrap}.sftp-prompt label,.sftp-inline-form label{display:block;margin-top:12px;color:var(--text-2);font-size:11px}.sftp-prompt input,.sftp-inline-form input{display:block;width:100%;margin-top:6px;padding:8px 10px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);color:var(--text-1);font:12px JetBrains Mono,monospace}.sftp-inline-form p{margin:10px 0 0;color:var(--text-2);font-size:12px}.sftp-danger-button{background:var(--red);color:var(--surface-2)}.sftp-foot{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:9px 20px;border-top:1px solid var(--line-1);font-size:11px;color:var(--text-2)}.sftp-hint{color:var(--text-4);font-size:10px;white-space:nowrap}.sftp-notice{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sftp-progress{position:relative;flex:1;min-width:0;padding:4px 8px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface-0);overflow:hidden}.sftp-progress-fill{position:absolute;inset:0 auto 0 0;background:color-mix(in srgb,var(--green) 14%,var(--surface-1));transition:width .18s linear}.sftp-progress span{position:relative;font:10px JetBrains Mono,monospace;color:var(--green)}:root[data-theme=light] .sftp-layer{background:#1c2a3ab3}:root[data-theme=light] .sftp-panel,:root[data-theme=light] .sftp-pane{background:#fff;border-color:#dbe2ec}:root[data-theme=light] .sftp-head,:root[data-theme=light] .sftp-list,:root[data-theme=light] .sftp-pane-foot,:root[data-theme=light] .sftp-foot,:root[data-theme=light] .sftp-prompt,:root[data-theme=light] .sftp-inline-form{border-color:#dbe2ec}:root[data-theme=light] .sftp-body{background:#dbe2ec}:root[data-theme=light] .sftp-prompt,:root[data-theme=light] .sftp-inline-form{background:#eef2f7}:root[data-theme=light] .sftp-row{color:#536176;border-bottom-color:#eef2f7}:root[data-theme=light] .sftp-row:hover{background:#f2f6fb}:root[data-theme=light] .sftp-row.selected{background:#dbe7f5;color:#253044;box-shadow:inset 2px 0 #16803c}:root[data-theme=light] .sftp-row.is-directory{color:#2459a6}:root[data-theme=light] .sftp-path input,:root[data-theme=light] .sftp-prompt input,:root[data-theme=light] .sftp-inline-form input,:root[data-theme=light] .sftp-prompt pre,:root[data-theme=light] .sftp-progress{background:#fff;border-color:#c7d2e1;color:#253044}:root[data-theme=light] .sftp-prompt pre{color:#a15c00}:root[data-theme=light] .sftp-size,:root[data-theme=light] .sftp-modified,:root[data-theme=light] .sftp-count,:root[data-theme=light] .sftp-hint,:root[data-theme=light] .sftp-hidden-toggle,:root[data-theme=light] .sftp-empty small{color:#68768a}:root[data-theme=light] .sftp-notice,:root[data-theme=light] .sftp-empty,:root[data-theme=light] .sftp-inline-form p,:root[data-theme=light] .sftp-foot{color:#536176}:root[data-theme=light] .sftp-pane-tools button{color:#68768a}:root[data-theme=light] .sftp-pane-tools button:hover:not(:disabled){background:#dbe7f5;color:#16803c}:root[data-theme=light] .sftp-progress-fill{background:#d6efdd}:root[data-theme=light] .sftp-progress span{color:#16803c}:root[data-theme=light] .sftp-danger-button{color:#fff;background:#c53030}:root[data-theme=light] .pane-browser{background:#fff;color:#68768a}:root[data-theme=light] .pane-browser-head{border-color:#dbe2ec}:root[data-theme=light] .pane-browser-path,:root[data-theme=light] .pane-browser-row{color:#253044}:root[data-theme=light] .pane-browser-icon{color:#5c687b}:root[data-theme=light] .pane-browser-hidden-toggle{color:#5c687b}:root[data-theme=light] .pane-browser-icon:hover:not(:disabled){color:#253044;background:#eef2f7;border-color:#dbe2ec}:root[data-theme=light] .pane-browser-row:hover:not(:disabled){background:#eef2f7}:root[data-theme=light] .pane-browser-row.pane-browser-file,:root[data-theme=light] .pane-browser-kind{color:#68768a}:root[data-theme=light] .pane-browser-parent{color:#2459a6}:root[data-theme=light] .pane-browser-note{color:#536176}:root[data-theme=light] .pane-browser-error{color:#b3213b}:root[data-theme=light] .pane-browser-asking{color:#8a5d00}:root[data-theme=light] .pane-browser-foot{border-color:#dbe2ec;color:#68768a}:root[data-theme=light] .pane-browser-send{color:#2459a6;background:#eef2f7;border-color:#dbe2ec}:root[data-theme=light] .pane-browser-send:hover:not(:disabled){color:#16803c;background:#16803c1f;border-color:#16803c66}:root[data-theme=light] .pane-browse.active{color:#2459a6;background:#dbe7f5;border-color:#dbe2ec}:root[data-theme=light] .brand-version{color:#68768a}:root[data-theme=light] .workspace-tab-slot.dragging .workspace-tab{background:#dbe7f5;border-color:#2459a6;box-shadow:0 6px 18px #25304433}:root[data-theme=light] .connection-dot,:root[data-theme=light] .pane-live{background:#16803c}:root[data-theme=light] .drawer-tools,:root[data-theme=light] .drawer-bottom{border-color:#dbe2ec}:root[data-theme=light] .session-group-label{color:#536176}:root[data-theme=light] .session-ghosted{border-color:#c7d5e8}:root[data-theme=light] .session-error b{color:#b3213b}:root[data-theme=light] .status-dot{background:#68768a}:root[data-theme=light] .status-dot.connected{background:#16803c}:root[data-theme=light] .status-dot.detached{background:#b7791f}:root[data-theme=light] .status-dot.error{background:#c53030}:root[data-theme=light] .slash{color:#b8c6d8}:root[data-theme=light] .control-label{color:#68768a}:root[data-theme=light] .pane-body-split,:root[data-theme=light] .status-separator{background:#dbe2ec}:root[data-theme=light] .terminal-pane{outline-color:#dbe2ec}:root[data-theme=light] .terminal-pane.focused{outline-color:#2459a6}:root[data-theme=light] .sidebar-resizer:hover,:root[data-theme=light] .pane-resizer:hover,:root[data-theme=light] .sidebar-resizer.dragging,:root[data-theme=light] .pane-resizer.dragging{background:#2459a659}:root[data-theme=light] .sidebar-resizer:focus-visible,:root[data-theme=light] .pane-resizer:focus-visible{background:#2459a6}:root[data-theme=light] .pane-nav,:root[data-theme=light] .pane-maximize,:root[data-theme=light] .pane-close,:root[data-theme=light] .pane-proceed,:root[data-theme=light] .pane-ai,:root[data-theme=light] .pane-custom,:root[data-theme=light] .pane-edit,:root[data-theme=light] .pane-mic{color:#4a5568}:root[data-theme=light] .pane-nav,:root[data-theme=light] .pane-maximize,:root[data-theme=light] .pane-close,:root[data-theme=light] .pane-proceed,:root[data-theme=light] .pane-ai,:root[data-theme=light] .pane-custom,:root[data-theme=light] .pane-edit,:root[data-theme=light] .pane-link,:root[data-theme=light] .pane-browse,:root[data-theme=light] .pane-mic{background:#2530440d;border-color:#25304429}:root[data-theme=light] .pane-proceed{color:#16803c;background:#16803c14;border-color:#16803c59}:root[data-theme=light] .pane-ai{color:#2459a6;background:#2459a614;border-color:#2459a659}:root[data-theme=light] .pane-custom{color:#8a5d00;background:#b7791f1a;border-color:#b7791f66}:root[data-theme=light] .pane-nav:hover,:root[data-theme=light] .pane-maximize:hover,:root[data-theme=light] .pane-browse:hover:not(:disabled),:root[data-theme=light] .pane-mic:hover{color:#253044;background:#eef2f7;border-color:#dbe2ec}:root[data-theme=light] .thumbnail-line{background:#c7d5e8}:root[data-theme=light] .thumbnail-line.cyan{background:#2459a6}:root[data-theme=light] .thumbnail-line.green,:root[data-theme=light] .thumbnail-cursor{background:#16803c}:root[data-theme=light] .shortcut-row{border-color:#dbe2ec}:root[data-theme=light] .shortcut-what{color:#253044}:root[data-theme=light] .shortcut-chord{border-color:#dbe2ec;background:#eef2f7;color:#253044}:root[data-theme=light] .shortcut-chord:hover{border-color:#2459a6}:root[data-theme=light] .shortcut-chord.capturing{border-color:#2459a6;background:#dbe7f5}:root[data-theme=light] .shortcut-prompt{color:#2459a6}:root[data-theme=light] .shortcut-reset{color:#68768a}:root[data-theme=light] .shortcut-reset:hover:not(:disabled){color:#253044;background:#eef2f7;border-color:#dbe2ec}:root[data-theme=light] .shortcut-problem,:root[data-theme=light] .warning-text{color:#8a5d00}:root[data-theme=light] .settings-warning{border-color:#b07d1a73;background:#e0af6829;color:#8a5d00!important}:root[data-theme=light] .context-menu{background:#fff;border-color:#c7d5e8;box-shadow:0 14px 44px #25304433}:root[data-theme=light] .context-menu-item{color:#253044}:root[data-theme=light] .context-menu-item:hover:not(:disabled){background:#eef2f7}:root[data-theme=light] .context-menu-item:focus-visible{outline-color:#2459a6}:root[data-theme=light] .context-menu-item.danger:hover:not(:disabled){color:#b3213b;background:#c530301a}:root[data-theme=light] .context-menu-item .icon-svg{color:#536176}:root[data-theme=light] .help-card{background:#fff;border-color:#c7d5e8;box-shadow:0 20px 80px #25304433}:root[data-theme=light] .help-body h3,:root[data-theme=light] .help-keys td,:root[data-theme=light] .help-features dd{color:#536176}:root[data-theme=light] .help-keys tr:not(:last-child) th,:root[data-theme=light] .help-keys tr:not(:last-child) td{border-color:#dbe2ec}:root[data-theme=light] .help-keys kbd{border-color:#dbe2ec;background:#eef2f7;color:#253044}:root[data-theme=light] .help-note{border-color:#dbe2ec;background:#f2f5fa;color:#536176}:root[data-theme=light] .help-note .icon-svg{color:#2459a6}:root[data-theme=light] .help-features dt{color:#253044}:root[data-theme=light] .help-foot{color:#68768a}:root[data-theme=light] .palette-meta,:root[data-theme=light] .palette-empty,:root[data-theme=light] .palette-note{color:#536176}:root[data-theme=light] .palette-empty b,:root[data-theme=light] .settings-toggle-fixed b{color:#253044}:root[data-theme=light] .history-button{color:#2459a6}:root[data-theme=light] .settings-test-error,:root[data-theme=light] .sftp-error b{color:#b3213b}.pane-link-wrap{position:relative;display:inline-flex}.pane-link:hover:not(:disabled){color:var(--teal);background:rgba(var(--teal-rgb),.14);border-color:rgba(var(--teal-rgb),.45)}.pane-link.ready,.pane-link.active{color:var(--teal)}.pane-link:disabled{opacity:.55;cursor:default}.pane-link .icon-svg{width:12px;height:12px}.pane-link-menu{position:absolute;top:24px;right:0;z-index:20;min-width:190px;display:grid;gap:2px;padding:4px;background:var(--surface-2);border:1px solid var(--line-2);border-radius:4px;box-shadow:0 6px 18px #00000073}.pane-link-menu button{padding:5px 8px;text-align:left;color:var(--text-1);background:transparent;border:0;border-radius:3px;font:10px JetBrains Mono,monospace}.pane-link-menu button:hover:not(:disabled){background:var(--surface-4)}.pane-link-empty{padding:5px 8px;color:var(--text-2);font:10px JetBrains Mono,monospace}.pane-link-bar{display:flex;align-items:center;gap:6px;padding:3px 9px;color:var(--teal);background:rgba(var(--teal-rgb),.08);border-bottom:1px solid var(--line-1);font:10px JetBrains Mono,monospace}.pane-link-bar.paused{color:var(--yellow);background:rgba(var(--yellow-rgb),.1)}.pane-link-bar.broken{color:var(--text-2);background:transparent}.pane-link-bar .icon-svg{width:11px;height:11px;flex:none}.pane-link-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pane-link-bar button{padding:1px 7px;color:inherit;background:transparent;border:1px solid currentColor;border-radius:3px;font:inherit;opacity:.85}.pane-link-bar button:hover{opacity:1}:root[data-theme=light] .pane-link-bar{color:#0f766e;background:#0f766e14}:root[data-theme=light] .pane-link-bar.paused{color:#92600a;background:#92600a1a}:root[data-theme=light] .pane-link-bar.broken{color:#5b6475;background:transparent}:root[data-theme=light] .pane-link.ready,:root[data-theme=light] .pane-link.active{color:#0f766e}:root[data-theme=light] .pane-link-menu{background:#fff;border-color:#cfd6e2}:root[data-theme=light] .pane-link-menu button{color:#1f2733}:root[data-theme=light] .pane-link-menu button:hover:not(:disabled){background:#eef1f6}.custom-button-row{display:grid;grid-template-columns:18px minmax(0,1fr) minmax(0,2fr);align-items:center;gap:6px;margin-top:4px}.custom-button-slot{color:var(--text-3);font:600 11px/1 inherit;text-align:center}.editor-card{width:min(1100px,calc(100vw - 48px));height:min(760px,calc(100vh - 48px));display:flex;flex-direction:column;background:var(--surface-2);border:1px solid var(--line-3);border-radius:8px;box-shadow:0 20px 80px #0009;overflow:hidden}.editor-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:14px 18px 12px;border-bottom:1px solid var(--line-1)}.editor-title h2{margin:4px 0 0;font-size:16px;font-weight:600}.editor-dirty{color:var(--yellow)}.editor-actions{display:flex;align-items:center;gap:8px}.editor-body{flex:1;min-height:0;display:grid;grid-template-columns:260px minmax(0,1fr)}.editor-files{min-width:0;min-height:0;display:flex;flex-direction:column;border-right:1px solid var(--line-1)}.editor-files .pane-browser{flex:1}.editor-main{min-width:0;min-height:0;display:flex;flex-direction:column}.editor-text{flex:1;min-height:0;width:100%;box-sizing:border-box;padding:12px 14px;resize:none;border:0;outline:none;background:var(--surface-0);color:var(--text-1);font:13px/1.5 JetBrains Mono,Cascadia Code,Consolas,monospace;tab-size:4;white-space:pre;overflow:auto}.editor-empty{flex:1;display:grid;place-content:center;justify-items:center;gap:6px;color:var(--text-3);background:var(--surface-0);font-size:12px}.editor-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 14px;background:rgba(var(--yellow-rgb),.12);border-bottom:1px solid rgba(var(--yellow-rgb),.4);color:var(--yellow);font-size:12px}.editor-banner span{flex:1 1 280px}.editor-foot{display:flex;justify-content:space-between;gap:12px;padding:6px 14px;border-top:1px solid var(--line-1);color:var(--text-3);font:11px JetBrains Mono,monospace}.editor-status{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pane-browser-file-choice{cursor:pointer}.pane-browser-current{color:var(--blue);background:var(--surface-3)}:root[data-theme=light] .editor-card{background:#f7f8fa;border-color:#cdd3dc}:root[data-theme=light] .editor-head,:root[data-theme=light] .editor-files,:root[data-theme=light] .editor-foot{border-color:#dde1e8}:root[data-theme=light] .editor-text,:root[data-theme=light] .editor-empty{background:#fff;color:#1f2733}.pane-dock{min-width:0;min-height:0;display:flex;flex-direction:column}.editor-docked{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;background:var(--surface-0);border-left:1px solid var(--line-1)}.editor-dock-head{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:4px 8px;border-bottom:1px solid var(--line-1);font:11px JetBrains Mono,monospace;color:var(--text-2)}.editor-dock-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.editor-dock-actions{display:flex;align-items:center;gap:5px}.editor-dock-actions .primary-button{padding:2px 8px;font-size:11px}.editor-dock-actions .pane-nav .icon-svg{width:13px;height:13px}.editor-files-docked{flex:0 1 40%;min-height:90px;border-right:0;border-bottom:1px solid var(--line-1)}.editor-docked .editor-main{flex:1}:root[data-theme=light] .editor-docked{background:#f7f8fa;border-color:#dde1e8}:root[data-theme=light] .editor-dock-head,:root[data-theme=light] .editor-files-docked{border-color:#dde1e8}:root[data-theme=dracula]{--surface-0: #282a36;--surface-1: #2d2f3d;--surface-2: #343746;--surface-3: #3b3e50;--surface-4: #44475a;--line-1: #3b3e50;--line-2: #44475a;--line-3: #565a75;--text-1: #f8f8f2;--text-2: #b6b9d1;--text-3: #8f98c4;--text-4: #6272a4;--icon: #d3d6ea;--blue: #bd93f9;--blue-rgb: 189, 147, 249;--green: #50fa7b;--green-rgb: 80, 250, 123;--yellow: #f1fa8c;--yellow-rgb: 241, 250, 140;--red: #ff5555;--red-rgb: 255, 85, 85;--cyan: #8be9fd;--cyan-rgb: 139, 233, 253;--teal: #69e8c6;--teal-rgb: 105, 232, 198}:root[data-theme=nord]{--surface-0: #2e3440;--surface-1: #333a47;--surface-2: #3b4252;--surface-3: #434c5e;--surface-4: #4c566a;--line-1: #3b4252;--line-2: #4c566a;--line-3: #5a657a;--text-1: #e5e9f0;--text-2: #b4bccb;--text-3: #8d96a9;--text-4: #6b7589;--icon: #d8dee9;--blue: #88c0d0;--blue-rgb: 136, 192, 208;--green: #a3be8c;--green-rgb: 163, 190, 140;--yellow: #ebcb8b;--yellow-rgb: 235, 203, 139;--red: #d4767f;--red-rgb: 212, 118, 127;--cyan: #8fbcbb;--cyan-rgb: 143, 188, 187;--teal: #a3d1c8;--teal-rgb: 163, 209, 200}:root[data-theme=solarized-dark]{--surface-0: #002b36;--surface-1: #00323e;--surface-2: #073642;--surface-3: #0d4252;--surface-4: #175566;--line-1: #0f4a5a;--line-2: #1c5b6c;--line-3: #2b6f80;--text-1: #93a1a1;--text-2: #839496;--text-3: #6c8187;--text-4: #586e75;--icon: #b4c0c0;--blue: #268bd2;--blue-rgb: 38, 139, 210;--green: #859900;--green-rgb: 133, 153, 0;--yellow: #b58900;--yellow-rgb: 181, 137, 0;--red: #dc322f;--red-rgb: 220, 50, 47;--cyan: #2aa198;--cyan-rgb: 42, 161, 152;--teal: #2aa198;--teal-rgb: 42, 161, 152}:root[data-theme=monokai]{--surface-0: #272822;--surface-1: #2d2e27;--surface-2: #34352c;--surface-3: #3e3d32;--surface-4: #49483e;--line-1: #3e3d32;--line-2: #49483e;--line-3: #5c5b4d;--text-1: #f8f8f2;--text-2: #bdbdae;--text-3: #90907f;--text-4: #75715e;--icon: #d8d8cc;--blue: #66d9ef;--blue-rgb: 102, 217, 239;--green: #a6e22e;--green-rgb: 166, 226, 46;--yellow: #e6db74;--yellow-rgb: 230, 219, 116;--red: #f92672;--red-rgb: 249, 38, 114;--cyan: #a1efe4;--cyan-rgb: 161, 239, 228;--teal: #7fe0d4;--teal-rgb: 127, 224, 212}:root[data-theme=one-dark]{--surface-0: #282c34;--surface-1: #2c313a;--surface-2: #313640;--surface-3: #383e49;--surface-4: #3e4451;--line-1: #353b45;--line-2: #3e4451;--line-3: #4b5263;--text-1: #c8ccd4;--text-2: #9da5b4;--text-3: #7f848e;--text-4: #5c6370;--icon: #bcc2cd;--blue: #61afef;--blue-rgb: 97, 175, 239;--green: #98c379;--green-rgb: 152, 195, 121;--yellow: #e5c07b;--yellow-rgb: 229, 192, 123;--red: #e06c75;--red-rgb: 224, 108, 117;--cyan: #56b6c2;--cyan-rgb: 86, 182, 194;--teal: #6cc4b8;--teal-rgb: 108, 196, 184}:root[data-theme=synthwave]{--surface-0: #262335;--surface-1: #2a2640;--surface-2: #2d2842;--surface-3: #34294f;--surface-4: #3b3060;--line-1: #34294f;--line-2: #433866;--line-3: #5a4b88;--text-1: #f4eefc;--text-2: #bcb2d6;--text-3: #9486b8;--text-4: #6f6194;--icon: #dcd2f2;--blue: #ff7edb;--blue-rgb: 255, 126, 219;--green: #72f1b8;--green-rgb: 114, 241, 184;--yellow: #fede5d;--yellow-rgb: 254, 222, 93;--red: #fe4450;--red-rgb: 254, 68, 80;--cyan: #36f9f6;--cyan-rgb: 54, 249, 246;--teal: #03edf9;--teal-rgb: 3, 237, 249}:root[data-theme=high-contrast]{--surface-0: #000000;--surface-1: #0a0a0a;--surface-2: #101010;--surface-3: #1c1c1c;--surface-4: #262626;--line-1: #6b6b6b;--line-2: #9a9a9a;--line-3: #cfcfcf;--text-1: #ffffff;--text-2: #ececec;--text-3: #d0d0d0;--text-4: #a8a8a8;--icon: #ffffff;--blue: #6fc3ff;--blue-rgb: 111, 195, 255;--green: #3fff6a;--green-rgb: 63, 255, 106;--yellow: #ffe14d;--yellow-rgb: 255, 225, 77;--red: #ff6b6b;--red-rgb: 255, 107, 107;--cyan: #4de8ff;--cyan-rgb: 77, 232, 255;--teal: #4dffd2;--teal-rgb: 77, 255, 210}:root[data-theme=high-contrast] .pane-nav,:root[data-theme=high-contrast] .pane-maximize,:root[data-theme=high-contrast] .pane-close,:root[data-theme=high-contrast] .pane-proceed,:root[data-theme=high-contrast] .pane-ai,:root[data-theme=high-contrast] .pane-custom,:root[data-theme=high-contrast] .pane-edit,:root[data-theme=high-contrast] .pane-link,:root[data-theme=high-contrast] .pane-browse,:root[data-theme=high-contrast] .pane-mic{border-color:currentColor}:root[data-theme=synthwave] .pane-nav:not(:disabled),:root[data-theme=synthwave] .pane-maximize:not(:disabled),:root[data-theme=synthwave] .pane-close:not(:disabled),:root[data-theme=synthwave] .pane-proceed:not(:disabled),:root[data-theme=synthwave] .pane-ai:not(:disabled),:root[data-theme=synthwave] .pane-custom:not(:disabled),:root[data-theme=synthwave] .pane-edit:not(:disabled),:root[data-theme=synthwave] .pane-link:not(:disabled),:root[data-theme=synthwave] .pane-browse:not(:disabled),:root[data-theme=synthwave] .pane-mic:not(:disabled),:root[data-theme=synthwave] .bar-button:not(:disabled),:root[data-theme=synthwave] .rail-button:not(:disabled){box-shadow:0 0 6px color-mix(in srgb,currentColor 40%,transparent)}:root[data-theme=synthwave] .pane-nav:hover:not(:disabled),:root[data-theme=synthwave] .pane-maximize:hover:not(:disabled),:root[data-theme=synthwave] .pane-close:hover:not(:disabled),:root[data-theme=synthwave] .pane-proceed:hover:not(:disabled),:root[data-theme=synthwave] .pane-ai:hover:not(:disabled),:root[data-theme=synthwave] .pane-custom:hover:not(:disabled),:root[data-theme=synthwave] .pane-edit:hover:not(:disabled),:root[data-theme=synthwave] .pane-link:hover:not(:disabled),:root[data-theme=synthwave] .pane-browse:hover:not(:disabled),:root[data-theme=synthwave] .pane-mic:hover:not(:disabled),:root[data-theme=synthwave] .bar-button:hover:not(:disabled),:root[data-theme=synthwave] .rail-button:hover:not(:disabled){box-shadow:0 0 8px color-mix(in srgb,currentColor 65%,transparent),0 0 16px color-mix(in srgb,currentColor 30%,transparent)}:root[data-theme=synthwave] .pane-nav,:root[data-theme=synthwave] .pane-maximize,:root[data-theme=synthwave] .pane-close,:root[data-theme=synthwave] .pane-proceed,:root[data-theme=synthwave] .pane-ai,:root[data-theme=synthwave] .pane-custom,:root[data-theme=synthwave] .pane-edit,:root[data-theme=synthwave] .pane-link,:root[data-theme=synthwave] .pane-browse,:root[data-theme=synthwave] .pane-mic .icon-svg{filter:drop-shadow(0 0 3px currentColor)}:root[data-theme=synthwave] .pane-custom:not(:disabled){text-shadow:0 0 6px currentColor}
|