zerogterm 0.8.0-alpha → 0.9.0-alpha.1
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/LICENSE +21 -21
- package/README.md +2 -1
- package/dist/main/main/main.js +295 -1
- package/dist/main/main/mcp-audit.js +12 -0
- package/dist/main/main/mcp-control.js +95 -0
- package/dist/main/main/mcp-execution-broker.js +126 -0
- package/dist/main/main/mcp-execution-policy.js +43 -0
- package/dist/main/main/mcp-protocol.js +51 -0
- package/dist/main/main/mcp-server.js +221 -0
- package/dist/main/main/preload.cjs +28 -0
- package/dist/main/main/prompt-classifier.js +14 -0
- package/dist/main/main/workspace-store.js +3 -0
- package/dist/manifest.json +1 -0
- package/dist/renderer/assets/index-BovKjPi0.css +1 -0
- package/dist/renderer/assets/index-DYB0NFWF.js +142 -0
- package/dist/renderer/index.html +2 -2
- package/package.json +11 -6
- package/dist/renderer/assets/index-BPS6JAQV.js +0 -142
- package/dist/renderer/assets/index-p0gJDyuE.css +0 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const DEFAULT_MCP_EXECUTION_POLICY = {
|
|
2
|
+
allowShellOperators: false,
|
|
3
|
+
allowRemoteSessions: false,
|
|
4
|
+
maxCommandLength: 512,
|
|
5
|
+
maxRuntimeMs: 30_000,
|
|
6
|
+
maxOutputBytes: 16 * 1024
|
|
7
|
+
};
|
|
8
|
+
const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
9
|
+
const SHELL_OPERATORS = /[|;&<>`$(){}\\]/;
|
|
10
|
+
const CREDENTIAL_SHAPE = /(?:bearer\s+|password\s*=|token\s*=|api[_-]?key\s*=|-----begin\s+[^\r\n]+-----)/i;
|
|
11
|
+
const SAFE_REMOTE_COMMANDS = /^(?:pwd|cd(?:\s+\.?\/?[A-Za-z0-9_./~-]+)?|whoami|hostname|uname(?:\s+-a)?|ls(?:\s+(?:-[al]{1,2}|\.?\/?[A-Za-z0-9_./~-]+))?|df(?:\s+-h)?(?:\s+\.?\/?[A-Za-z0-9_./~-]+)?)$/;
|
|
12
|
+
export function validateMcpCommand(input) {
|
|
13
|
+
const policy = { ...DEFAULT_MCP_EXECUTION_POLICY, ...(input.policy ?? {}) };
|
|
14
|
+
if (typeof input.command !== 'string')
|
|
15
|
+
return denied(policy, 'Command must be text.');
|
|
16
|
+
const command = input.command.trim();
|
|
17
|
+
if (!command)
|
|
18
|
+
return denied(policy, 'Command is required.');
|
|
19
|
+
if (command.length > policy.maxCommandLength)
|
|
20
|
+
return denied(policy, 'Command is too long.');
|
|
21
|
+
if (/[\r\n]/.test(command))
|
|
22
|
+
return denied(policy, 'Only one command line may be requested.');
|
|
23
|
+
if (CONTROL.test(command))
|
|
24
|
+
return denied(policy, 'Command contains control characters.');
|
|
25
|
+
if (CREDENTIAL_SHAPE.test(command))
|
|
26
|
+
return denied(policy, 'Credential-like command content is disabled.');
|
|
27
|
+
if (!policy.allowShellOperators && SHELL_OPERATORS.test(command))
|
|
28
|
+
return denied(policy, 'Shell operators are disabled.');
|
|
29
|
+
if (input.sessionKind === 'ssh' && !policy.allowRemoteSessions)
|
|
30
|
+
return denied(policy, 'Remote command execution is disabled.');
|
|
31
|
+
return { allowed: true, command, displayCommand: redactCommand(command), policy };
|
|
32
|
+
}
|
|
33
|
+
export function isSafeRemoteCommand(command) {
|
|
34
|
+
return SAFE_REMOTE_COMMANDS.test(command.trim());
|
|
35
|
+
}
|
|
36
|
+
function redactCommand(command) {
|
|
37
|
+
if (CREDENTIAL_SHAPE.test(command))
|
|
38
|
+
return '[redacted command: credential-like content]';
|
|
39
|
+
return command;
|
|
40
|
+
}
|
|
41
|
+
function denied(policy, reason) {
|
|
42
|
+
return { allowed: false, reason, policy };
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function isMcpCapability(value) {
|
|
2
|
+
return value === 'workspace:read' || value === 'workspace:restore' || value === 'workspace:write' ||
|
|
3
|
+
value === 'session:read' || value === 'session:create' || value === 'session:close' || value === 'session:execute';
|
|
4
|
+
}
|
|
5
|
+
export function requireMcpCapability(lease, capability, now = Date.now()) {
|
|
6
|
+
if (!lease)
|
|
7
|
+
throw new Error('No AI control lease is active.');
|
|
8
|
+
if (lease.expiresAt <= now)
|
|
9
|
+
throw new Error('The AI control lease has expired.');
|
|
10
|
+
if (!lease.capabilities.includes(capability))
|
|
11
|
+
throw new Error(`The AI connection lacks ${capability} capability.`);
|
|
12
|
+
}
|
|
13
|
+
export function requireBoundedString(value, field, max = 256) {
|
|
14
|
+
if (typeof value !== 'string' || !value.trim())
|
|
15
|
+
throw new Error(`${field} is required.`);
|
|
16
|
+
const clean = value.trim();
|
|
17
|
+
if (clean.length > max)
|
|
18
|
+
throw new Error(`${field} is too long.`);
|
|
19
|
+
if (/[\u0000-\u001f\u007f]/.test(clean))
|
|
20
|
+
throw new Error(`${field} contains control characters.`);
|
|
21
|
+
return clean;
|
|
22
|
+
}
|
|
23
|
+
export function requireWorkspaceId(value) {
|
|
24
|
+
return requireBoundedString(value, 'workspaceId', 64);
|
|
25
|
+
}
|
|
26
|
+
export function requireSessionId(value) {
|
|
27
|
+
return requireBoundedString(value, 'sessionId', 128);
|
|
28
|
+
}
|
|
29
|
+
export function requireWorkspaceName(value) {
|
|
30
|
+
const name = requireBoundedString(value, 'name', 49);
|
|
31
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9_.]|-| ){0,48}$/.test(name)) {
|
|
32
|
+
throw new Error('Workspace name contains unsupported characters.');
|
|
33
|
+
}
|
|
34
|
+
return name;
|
|
35
|
+
}
|
|
36
|
+
export function requireStoredWorkspaceFile(value) {
|
|
37
|
+
if (!value || typeof value !== 'object')
|
|
38
|
+
throw new Error('A workspace file is required.');
|
|
39
|
+
const file = value;
|
|
40
|
+
if (file.version !== 1 || !Array.isArray(file.workspaces))
|
|
41
|
+
throw new Error('Invalid workspace file.');
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
export function boundedDeadline(value, defaultMs = 10_000, maxMs = 60_000) {
|
|
45
|
+
if (value === undefined)
|
|
46
|
+
return defaultMs;
|
|
47
|
+
if (!Number.isInteger(value) || value < 100 || value > maxMs) {
|
|
48
|
+
throw new Error(`deadlineMs must be between 100 and ${maxMs}.`);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { McpControl } from './mcp-control.js';
|
|
7
|
+
import { requireWorkspaceId } from './mcp-protocol.js';
|
|
8
|
+
const MAX_BODY_BYTES = 128 * 1024;
|
|
9
|
+
export const DEFAULT_MCP_PORT = 60056;
|
|
10
|
+
const DEFAULT_CAPABILITIES = ['workspace:read', 'session:read'];
|
|
11
|
+
const ALL_CAPABILITIES = [
|
|
12
|
+
'workspace:read', 'workspace:restore', 'workspace:write',
|
|
13
|
+
'session:read', 'session:create', 'session:close', 'session:execute'
|
|
14
|
+
];
|
|
15
|
+
/** Local authenticated MCP endpoint owned by the running ZeroG instance. */
|
|
16
|
+
export class McpServerHost {
|
|
17
|
+
providers;
|
|
18
|
+
control;
|
|
19
|
+
version;
|
|
20
|
+
configuredPort;
|
|
21
|
+
token;
|
|
22
|
+
http;
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.providers = options.providers;
|
|
25
|
+
this.control = options.control ?? new McpControl();
|
|
26
|
+
this.version = options.version ?? '0.0.0';
|
|
27
|
+
this.configuredPort = options.port ?? DEFAULT_MCP_PORT;
|
|
28
|
+
this.token = options.token ?? process.env.MCP_BEARER_TOKEN ?? randomBytes(32).toString('base64url');
|
|
29
|
+
}
|
|
30
|
+
status() {
|
|
31
|
+
return this.control.status();
|
|
32
|
+
}
|
|
33
|
+
async start() {
|
|
34
|
+
if (this.http)
|
|
35
|
+
return { endpoint: this.endpoint(), token: this.token };
|
|
36
|
+
this.http = createServer((req, res) => { void this.handle(req, res); });
|
|
37
|
+
await new Promise((resolve, reject) => {
|
|
38
|
+
this.http.once('error', reject);
|
|
39
|
+
this.http.listen(this.configuredPort, '127.0.0.1', () => {
|
|
40
|
+
this.http.off('error', reject);
|
|
41
|
+
resolve();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
const address = this.http.address();
|
|
45
|
+
const port = typeof address === 'object' && address ? address.port : this.configuredPort;
|
|
46
|
+
this.control.enable(`http://127.0.0.1:${port}/mcp`);
|
|
47
|
+
return { endpoint: this.endpoint(), token: this.token };
|
|
48
|
+
}
|
|
49
|
+
async stop() {
|
|
50
|
+
this.control.disable();
|
|
51
|
+
if (this.http) {
|
|
52
|
+
await new Promise((resolve) => this.http.close(() => resolve()));
|
|
53
|
+
this.http = undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
endpoint() {
|
|
57
|
+
const address = this.http?.address();
|
|
58
|
+
const port = typeof address === 'object' && address ? address.port : this.configuredPort;
|
|
59
|
+
return `http://127.0.0.1:${port}/mcp`;
|
|
60
|
+
}
|
|
61
|
+
async handle(req, res) {
|
|
62
|
+
if (req.url !== '/mcp')
|
|
63
|
+
return respond(res, 404, { error: 'Not found' });
|
|
64
|
+
if (!this.authorized(req))
|
|
65
|
+
return respond(res, 401, { error: 'Authorization required' }, { 'WWW-Authenticate': 'Bearer' });
|
|
66
|
+
if (req.method !== 'POST' && req.method !== 'GET' && req.method !== 'DELETE') {
|
|
67
|
+
return respond(res, 405, { error: 'Method not allowed' }, { Allow: 'GET, POST, DELETE' });
|
|
68
|
+
}
|
|
69
|
+
if (req.method === 'POST') {
|
|
70
|
+
const body = await readJson(req);
|
|
71
|
+
if (body.error)
|
|
72
|
+
return respond(res, body.status, { error: body.error });
|
|
73
|
+
// Stateless transport: each HTTP request gets an independent protocol
|
|
74
|
+
// wrapper, so one client cannot wedge the desktop-wide MCP endpoint.
|
|
75
|
+
const mcp = this.buildMcpServer();
|
|
76
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
|
77
|
+
await mcp.connect(transport);
|
|
78
|
+
await transport.handleRequest(req, res, body.value);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
respond(res, 400, { error: 'This stateless endpoint accepts POST requests only.' });
|
|
82
|
+
}
|
|
83
|
+
authorized(req) {
|
|
84
|
+
const value = req.headers.authorization;
|
|
85
|
+
if (!value?.startsWith('Bearer '))
|
|
86
|
+
return false;
|
|
87
|
+
const received = Buffer.from(value.slice(7));
|
|
88
|
+
const expected = Buffer.from(this.token);
|
|
89
|
+
return received.length === expected.length && timingSafeEqual(received, expected);
|
|
90
|
+
}
|
|
91
|
+
buildMcpServer() {
|
|
92
|
+
const server = new McpServer({ name: 'zerogterm', version: this.version });
|
|
93
|
+
server.registerTool('zerog_get_connection_status', {
|
|
94
|
+
description: 'Return ZeroG MCP connection and AI lease state.',
|
|
95
|
+
inputSchema: z.object({})
|
|
96
|
+
}, async () => text(this.control.status()));
|
|
97
|
+
server.registerTool('zerog_list_workspaces', {
|
|
98
|
+
description: 'List saved ZeroG workspaces and pane metadata.',
|
|
99
|
+
inputSchema: z.object({ clientId: z.string().min(1) })
|
|
100
|
+
}, async ({ clientId }) => {
|
|
101
|
+
this.control.require(clientId, 'workspace:read');
|
|
102
|
+
return text(await this.providers.listWorkspaces());
|
|
103
|
+
});
|
|
104
|
+
server.registerTool('zerog_list_sessions', {
|
|
105
|
+
description: 'List ZeroG terminal sessions without terminal output or credentials.',
|
|
106
|
+
inputSchema: z.object({ clientId: z.string().min(1) })
|
|
107
|
+
}, async ({ clientId }) => {
|
|
108
|
+
this.control.require(clientId, 'session:read');
|
|
109
|
+
return text(await this.providers.listSessions());
|
|
110
|
+
});
|
|
111
|
+
server.registerTool('zerog_restore_workspace', {
|
|
112
|
+
description: 'Restore a saved workspace and recreate only missing sessions. SSH authentication remains user-controlled.',
|
|
113
|
+
inputSchema: z.object({ clientId: z.string().min(1), workspaceId: z.string().min(1).max(64) })
|
|
114
|
+
}, async ({ clientId, workspaceId }) => {
|
|
115
|
+
this.control.require(clientId, 'workspace:restore');
|
|
116
|
+
if (!this.providers.restoreWorkspace)
|
|
117
|
+
throw new Error('Workspace restoration is not available.');
|
|
118
|
+
return text(await this.providers.restoreWorkspace(requireWorkspaceId(workspaceId)));
|
|
119
|
+
});
|
|
120
|
+
server.registerTool('zerog_create_workspace', {
|
|
121
|
+
description: 'Create and select a new blank workspace using the next workspace-N name.',
|
|
122
|
+
inputSchema: z.object({ clientId: z.string().min(1) })
|
|
123
|
+
}, async ({ clientId }) => {
|
|
124
|
+
this.control.require(clientId, 'workspace:write');
|
|
125
|
+
if (!this.providers.createWorkspace)
|
|
126
|
+
throw new Error('Workspace creation is not available.');
|
|
127
|
+
return text(await this.providers.createWorkspace(''));
|
|
128
|
+
});
|
|
129
|
+
server.registerTool('zerog_create_project_workspace', {
|
|
130
|
+
description: 'Create or replace a workspace containing local project panes. Does not execute commands.',
|
|
131
|
+
inputSchema: z.object({
|
|
132
|
+
clientId: z.string().min(1),
|
|
133
|
+
name: z.string().min(1).max(49),
|
|
134
|
+
projects: z.array(z.object({ name: z.string().min(1).max(64), cwd: z.string().min(1).max(512), backend: z.string().max(32).optional() })).min(1).max(4)
|
|
135
|
+
})
|
|
136
|
+
}, async ({ clientId, name, projects }) => {
|
|
137
|
+
this.control.require(clientId, 'workspace:write');
|
|
138
|
+
this.control.require(clientId, 'session:create');
|
|
139
|
+
if (!this.providers.createProjectWorkspace)
|
|
140
|
+
throw new Error('Project workspace creation is not available.');
|
|
141
|
+
return text(await this.providers.createProjectWorkspace({ name, projects }));
|
|
142
|
+
});
|
|
143
|
+
server.registerTool('zerog_create_ssh_session', {
|
|
144
|
+
description: 'Open a new SSH session in the active ZeroG workspace. Authentication and host-key prompts remain user-controlled.',
|
|
145
|
+
inputSchema: z.object({
|
|
146
|
+
clientId: z.string().min(1),
|
|
147
|
+
target: z.string().min(1).max(512),
|
|
148
|
+
name: z.string().min(1).max(128).optional()
|
|
149
|
+
})
|
|
150
|
+
}, async ({ clientId, target, name }) => {
|
|
151
|
+
this.control.require(clientId, 'session:create');
|
|
152
|
+
if (!this.providers.createSshSession)
|
|
153
|
+
throw new Error('SSH session creation is not available.');
|
|
154
|
+
return text(await this.providers.createSshSession({ target, ...(name === undefined ? {} : { name }) }));
|
|
155
|
+
});
|
|
156
|
+
server.registerTool('zerog_request_command', {
|
|
157
|
+
description: 'Request a local command for explicit user approval. This never executes the command by itself.',
|
|
158
|
+
inputSchema: z.object({ clientId: z.string().min(1), sessionId: z.string().min(1).max(128), command: z.string().min(1).max(512), timeoutMs: z.number().int().min(100).max(30000).optional(), outputBytes: z.number().int().min(256).max(16384).optional() })
|
|
159
|
+
}, async ({ clientId, sessionId, command, timeoutMs, outputBytes }) => {
|
|
160
|
+
this.control.require(clientId, 'session:execute');
|
|
161
|
+
if (!this.providers.requestCommand)
|
|
162
|
+
throw new Error('Command requests are not available.');
|
|
163
|
+
return text(await this.providers.requestCommand({ clientId, sessionId, command, ...(timeoutMs === undefined ? {} : { timeoutMs }), ...(outputBytes === undefined ? {} : { outputBytes }) }));
|
|
164
|
+
});
|
|
165
|
+
server.registerTool('zerog_get_command_result', {
|
|
166
|
+
description: 'Read the state of a command request owned by this client.',
|
|
167
|
+
inputSchema: z.object({ clientId: z.string().min(1), requestId: z.string().min(1).max(128) })
|
|
168
|
+
}, async ({ clientId, requestId }) => {
|
|
169
|
+
this.control.require(clientId, 'session:execute');
|
|
170
|
+
if (!this.providers.getCommandResult)
|
|
171
|
+
throw new Error('Command requests are not available.');
|
|
172
|
+
return text(await this.providers.getCommandResult({ clientId, requestId }));
|
|
173
|
+
});
|
|
174
|
+
server.registerTool('zerog_cancel_command', {
|
|
175
|
+
description: 'Cancel a pending command request owned by this client.',
|
|
176
|
+
inputSchema: z.object({ clientId: z.string().min(1), requestId: z.string().min(1).max(128) })
|
|
177
|
+
}, async ({ clientId, requestId }) => {
|
|
178
|
+
this.control.require(clientId, 'session:execute');
|
|
179
|
+
if (!this.providers.cancelCommand)
|
|
180
|
+
throw new Error('Command requests are not available.');
|
|
181
|
+
return text(await this.providers.cancelCommand({ clientId, requestId }));
|
|
182
|
+
});
|
|
183
|
+
server.registerTool('zerog_acquire_control', {
|
|
184
|
+
description: 'Acquire the single AI control lease for this local ZeroG instance.',
|
|
185
|
+
inputSchema: z.object({ clientId: z.string().min(1).max(128), clientName: z.string().max(128).optional(), capabilities: z.array(z.enum(ALL_CAPABILITIES)).optional() })
|
|
186
|
+
}, async ({ clientId, clientName, capabilities }) => text(this.control.connect(clientId, clientName, (capabilities ?? DEFAULT_CAPABILITIES))));
|
|
187
|
+
server.registerTool('zerog_renew_control', {
|
|
188
|
+
description: 'Renew the current AI control lease.',
|
|
189
|
+
inputSchema: z.object({ clientId: z.string().min(1) })
|
|
190
|
+
}, async ({ clientId }) => text(this.control.renew(clientId)));
|
|
191
|
+
server.registerTool('zerog_upgrade_control', {
|
|
192
|
+
description: 'Add capabilities to the current AI control lease without taking control from another client.',
|
|
193
|
+
inputSchema: z.object({ clientId: z.string().min(1), capabilities: z.array(z.enum(ALL_CAPABILITIES)).min(1) })
|
|
194
|
+
}, async ({ clientId, capabilities }) => text(this.control.upgrade(clientId, capabilities)));
|
|
195
|
+
return server;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function text(value) {
|
|
199
|
+
return { content: [{ type: 'text', text: JSON.stringify(value) }] };
|
|
200
|
+
}
|
|
201
|
+
function respond(res, status, body, headers = {}) {
|
|
202
|
+
res.writeHead(status, { 'Content-Type': 'application/json', ...headers });
|
|
203
|
+
res.end(JSON.stringify(body));
|
|
204
|
+
}
|
|
205
|
+
async function readJson(req) {
|
|
206
|
+
let size = 0;
|
|
207
|
+
const chunks = [];
|
|
208
|
+
for await (const chunk of req) {
|
|
209
|
+
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
210
|
+
size += data.length;
|
|
211
|
+
if (size > MAX_BODY_BYTES)
|
|
212
|
+
return { error: 'Request body is too large.', status: 413 };
|
|
213
|
+
chunks.push(data);
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
return { value: JSON.parse(Buffer.concat(chunks).toString('utf8')), status: 200 };
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return { error: 'Request body must be valid JSON.', status: 400 };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -21,6 +21,34 @@ const api = {
|
|
|
21
21
|
},
|
|
22
22
|
loadWorkspaces: () => ipcRenderer.invoke('workspaces:load'),
|
|
23
23
|
saveWorkspaces: (file) => ipcRenderer.invoke('workspaces:save', file),
|
|
24
|
+
mcpStatus: () => ipcRenderer.invoke('mcp:status'),
|
|
25
|
+
revokeMcpControl: () => ipcRenderer.invoke('mcp:revoke'),
|
|
26
|
+
startMcp: () => ipcRenderer.invoke('mcp:start'),
|
|
27
|
+
stopMcp: () => ipcRenderer.invoke('mcp:stop'),
|
|
28
|
+
onMcpStatus: (callback) => {
|
|
29
|
+
const listener = (_event, payload) => callback(payload);
|
|
30
|
+
ipcRenderer.on('mcp:status', listener);
|
|
31
|
+
return () => ipcRenderer.removeListener('mcp:status', listener);
|
|
32
|
+
},
|
|
33
|
+
onMcpWorkspaceRestored: (callback) => {
|
|
34
|
+
const listener = (_event, payload) => callback(payload);
|
|
35
|
+
ipcRenderer.on('mcp:workspace-restored', listener);
|
|
36
|
+
return () => ipcRenderer.removeListener('mcp:workspace-restored', listener);
|
|
37
|
+
},
|
|
38
|
+
onMcpSshSessionCreated: (callback) => {
|
|
39
|
+
const listener = (_event, payload) => callback(payload);
|
|
40
|
+
ipcRenderer.on('mcp:ssh-session-created', listener);
|
|
41
|
+
return () => ipcRenderer.removeListener('mcp:ssh-session-created', listener);
|
|
42
|
+
},
|
|
43
|
+
listMcpExecutions: () => ipcRenderer.invoke('mcp:executions:list'),
|
|
44
|
+
approveMcpExecution: (requestId) => ipcRenderer.invoke('mcp:execution:approve', requestId),
|
|
45
|
+
rejectMcpExecution: (requestId) => ipcRenderer.invoke('mcp:execution:reject', requestId),
|
|
46
|
+
cancelMcpExecution: (requestId) => ipcRenderer.invoke('mcp:execution:cancel', requestId),
|
|
47
|
+
onMcpExecution: (callback) => {
|
|
48
|
+
const listener = (_event, payload) => callback(payload);
|
|
49
|
+
ipcRenderer.on('mcp:execution', listener);
|
|
50
|
+
return () => ipcRenderer.removeListener('mcp:execution', listener);
|
|
51
|
+
},
|
|
24
52
|
listBackends: () => ipcRenderer.invoke('sessions:backends'),
|
|
25
53
|
listWslDistributions: () => ipcRenderer.invoke('sessions:wslDistributions'),
|
|
26
54
|
createLocalSession: (request) => ipcRenderer.invoke('sessions:createLocal', request),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const PASSWORD = /password|passwd|login\s*:/i;
|
|
2
|
+
const PASSPHRASE = /passphrase|private\s+key/i;
|
|
3
|
+
const CODE = /verification|one[- ]time|security\s+code|otp|authenticator/i;
|
|
4
|
+
const HOST_KEY = /are you sure you want to continue connecting|authenticity of host|fingerprint|host key/i;
|
|
5
|
+
const ANSI = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
6
|
+
/** Classify untrusted terminal text without ever accepting an answer. */
|
|
7
|
+
export function classifyMcpPrompt(raw) {
|
|
8
|
+
const text = raw.replace(ANSI, '').replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '').slice(0, 512);
|
|
9
|
+
const kind = PASSPHRASE.test(text) ? 'passphrase'
|
|
10
|
+
: CODE.test(text) ? 'verification-code'
|
|
11
|
+
: HOST_KEY.test(text) ? 'host-key'
|
|
12
|
+
: PASSWORD.test(text) ? 'password' : 'unknown';
|
|
13
|
+
return { kind, text, answerableByMcp: false };
|
|
14
|
+
}
|
|
@@ -96,6 +96,7 @@ function normalizeMember(value) {
|
|
|
96
96
|
const screenName = optionalText(item.screenName, 64);
|
|
97
97
|
const sshTarget = optionalText(item.sshTarget);
|
|
98
98
|
const backend = optionalText(item.backend, 32);
|
|
99
|
+
const cwd = optionalText(item.cwd, 512);
|
|
99
100
|
if (host)
|
|
100
101
|
member.host = host;
|
|
101
102
|
if (screenName)
|
|
@@ -104,6 +105,8 @@ function normalizeMember(value) {
|
|
|
104
105
|
member.sshTarget = sshTarget;
|
|
105
106
|
if (backend)
|
|
106
107
|
member.backend = backend;
|
|
108
|
+
if (cwd)
|
|
109
|
+
member.cwd = cwd;
|
|
107
110
|
return member;
|
|
108
111
|
}
|
|
109
112
|
function normalizeView(value, memberIds) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion":"0.1.0","project_name":"zerogterm","slug":"zerogterm","tagline":"A Linux-first persistent Electron terminal workspace","description":"A Linux-first persistent Electron terminal workspace","repo_url":"https://github.com/HappyMonkeyAI/ZeroGTerm","website_url":"https://github.com/HappyMonkeyAI/ZeroGTerm#readme","author_name":"Stephen Phillips","author_email":"stephen.z.phillips@wifispark.com","license":"MIT","categories":["ai-tools"],"tags":["agent","ai","api","electron","llm","python","rag","screen","ssh","terminal","workflow","xterm"],"screenshots":[],"generated_summary":"A Linux-first persistent Electron terminal workspace","submission_cta_copy":"List your project with one command.","validation_status":"valid","generated_at":"2026-09-02T14:31:05.787Z","tool_version":"0.7.0-alpha3","source":{"workspace_path":"C:\\Users\\steph\\Documents\\development\\zerogterm","readme_path":"C:\\Users\\steph\\Documents\\development\\zerogterm\\README.md","package_json_path":"C:\\Users\\steph\\Documents\\development\\zerogterm\\package.json","git_remote":"https://github.com/HappyMonkeyAI/ZeroGTerm"},"submission_auth_required":true,"auth_mode":"api_key"}
|
|
@@ -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{font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:#d8dee9;background:#0a0c10;font-size:13px;line-height:1.4;color-scheme:dark}*{box-sizing:border-box}body{margin:0;min-width:980px;overflow:hidden;background:#0a0c10}button,input{font:inherit}button{color:inherit;border:0;cursor:pointer}button:focus-visible,input:focus-visible{outline:1px solid #7aa2f7;outline-offset:2px}.app-shell{height:100vh;background:#0a0c10}.window-bar{height:48px;display:flex;align-items:center;border-bottom:1px solid #202630;background:#0f1218;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:#7aa2f7;font:700 9px JetBrains Mono,monospace}.brand-name{font-size:13px;font-weight:650;letter-spacing:.01em}.brand-version{margin-left:-3px;color:#697386;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:#8b93a3;background:transparent;white-space:nowrap}.workspace-tab.active{background:#151a22;border-color:#252d39;color:#d8dee9}.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:#1b2536;border-color:#7aa2f7;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:#697386;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:#303b4b;color:#d8dee9}.workspace-rename{margin:3px 0 0;width:100%;min-width:0;padding:1px 5px;border:1px solid #303b4b;border-radius:4px;background:#0d1117;color:#d8dee9;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:#9ece6a;display:inline-block;flex:none}.tab-meta{font:11px JetBrains Mono,monospace;color:#697386;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:#8b93a3;background:transparent}.bar-button:hover{background:#1a202a;color:#d8dee9}.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 #202630;background:#0d1015;flex:none}.rail-button{width:32px;height:32px;display:grid;place-items:center;border-radius:5px;color:#697386;background:transparent}.rail-button:hover,.rail-button.active{background:#1a202a;color:#d8dee9}.rail-button.active{box-shadow:inset 2px 0 #7aa2f7}.rail-spacer{flex:1}.session-drawer{width:238px;max-width:40vw;display:flex;flex-direction:column;border-right:1px solid #202630;background:#0f1218;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:#697386;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 #2a3340;border-radius:4px;background:#151a22;color:#9aa4b2}.square-button:hover{border-color:#7aa2f7;color:#d8dee9}.drawer-tools{display:flex;gap:2px;padding:0 10px 10px;border-bottom:1px solid #202630}.drawer-tool{padding:5px 7px;border-radius:4px;color:#697386;background:transparent;font-size:11px}.drawer-tool span{margin-left:5px;color:#4e5969}.drawer-tool:hover,.drawer-tool.active{background:#1a202a;color:#d8dee9}.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:#8b93a3}.session-row:hover{background:#151a22}.session-row.active{background:#1c2531;color:#d8dee9;box-shadow:inset 2px 0 #7aa2f7}.status-dot{width:6px;height:6px;border-radius:50%;background:#4e5969;flex:none}.status-dot.connected{background:#9ece6a}.status-dot.detached{background:#e0af68}.status-dot.error{background:#f7768e}.mcp-stop-button{color:#f7768e;border-color:#f7768e73}.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:#9ece6a}.mcp-state-dot.connected{background:#73daca;box-shadow:0 0 0 3px #73daca29}.mcp-state-dot.revoked,.mcp-state-dot.error{background:#f7768e}.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:#697386;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.session-state{font:11px JetBrains Mono,monospace;color:#697386}.session-empty{display:flex;flex-direction:column;gap:6px;padding:18px 10px;color:#697386}.session-empty b{color:#8b93a3;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 #e0af68;border-radius:3px;color:#e0af68;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:#697386;font-size:13px;line-height:1;opacity:0}.forward-row:hover .forward-close,.forward-close:focus-visible{opacity:1}.forward-close:hover{background:#303b4b;color:#f7768e}.dialog-disclosure{align-self:flex-start;padding:3px 0;background:transparent;color:#7aa2f7;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 #e0af68;border-radius:4px;color:#e0af68;font-size:11px;line-height:1.45}.session-group-label{padding:8px 10px 4px;color:#697386;font:10px JetBrains Mono,monospace;letter-spacing:.04em}.drawer-bottom{margin-top:auto;padding:10px 9px;border-top:1px solid #202630}.drawer-action{width:100%;display:flex;align-items:center;gap:8px;padding:7px 5px;color:#8b93a3;background:transparent;text-align:left;border-radius:4px;font-size:11px}.drawer-action:hover{background:#151a22;color:#d8dee9}.drawer-action kbd{margin-left:auto;color:#4e5969;font:9px JetBrains Mono,monospace}.drawer-status{display:flex;align-items:center;gap:8px;margin-top:13px;padding:5px;color:#697386;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:#0a0c10}.workspace-head{height:42px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid #202630;background:#0d1015}.location{display:flex;align-items:center;gap:8px;color:#8b93a3;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:#8b93a3;background:#151a22;border:1px solid #2a3340}.inline-restore:hover{color:#d8dee9;border-color:#7aa2f7}.slash{color:#3d4654}.muted-text{color:#697386}.layout-controls{display:flex;align-items:center;gap:2px}.head-controls{display:flex;align-items:center;gap:18px}.control-label{font-size:9px;color:#4e5969;letter-spacing:.08em;margin-right:6px}.layout-button{width:26px;height:25px;display:grid;place-items:center;border-radius:4px;background:transparent;color:#697386}.layout-button:hover:not(:disabled),.layout-button.active{background:#1a202a;color:#9ece6a}.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:#202630;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:#7aa2f780}.sidebar-resizer:focus-visible,.pane-resizer:focus-visible{outline:none;background:#7aa2f7}.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:#202630}.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:#0b0e13;font:11px JetBrains Mono,monospace;color:#8b93a3}.pane-browser-head{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:4px 4px 4px 8px;border-bottom:1px solid #202630}.pane-browser-path{min-width:0;overflow:hidden;direction:rtl;text-align:left;white-space:nowrap;text-overflow:ellipsis;color:#c0c8d8}.pane-browser-actions{display:flex;gap:2px;flex:none}.pane-browser-icon{width:19px;height:19px;display:grid;place-items:center;padding:0;border:1px solid transparent;border-radius:3px;color:#697386;background:transparent}.pane-browser-icon:hover:not(:disabled){color:#d8dee9;background:#1a202a;border-color:#2a3340}.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:#c0c8d8;text-align:left;font:inherit;-webkit-user-select:none;user-select:none}.pane-browser-row:hover:not(:disabled){background:#151b24}.pane-browser-row:focus-visible{outline:1px solid #7aa2f7;outline-offset:-1px}.pane-browser-row.pane-browser-file{color:#5d6675;cursor:default}.pane-browser-parent{color:#7aa2f7}.pane-browser-name{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.pane-browser-kind{margin-left:auto;flex:none;color:#5d6675;font-size:10px}.pane-browser-note{margin:0;padding:8px;color:#697386;line-height:1.5}.pane-browser-error{color:#f7768e}.pane-browser-asking{color:#e0af68}.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 #202630;color:#5d6675;font-size:10px}.pane-browser-send{flex:none;width:21px;height:21px;display:grid;place-items:center;padding:0;border:1px solid #2a3340;border-radius:4px;color:#7aa2f7;background:#141a23}.pane-browser-send:hover:not(:disabled){color:#9ece6a;background:#9ece6a24;border-color:#9ece6a73}.pane-browser-send:disabled{opacity:.4;cursor:default}.pane-browse.active{color:#7aa2f7;background:#151b24;border-color:#2a3340}.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:#0a0c10}.terminal-pane{outline:1px solid #202630;outline-offset:-1px}.terminal-pane.focused{outline:1px solid #7aa2f7;outline-offset:-1px}.pane-title{height:29px;display:flex;align-items:center;justify-content:space-between;padding:0 9px;border-bottom:1px solid #202630;color:#8b93a3;font:10px JetBrains Mono,monospace}.pane-actions,.pane-dim{color:#4e5969;font-size:9px}.pane-actions{display:flex;align-items:center;gap:5px}.pane-nav,.pane-maximize,.pane-close,.pane-proceed,.pane-browse,.pane-mic{width:19px;height:19px;display:grid;place-items:center;padding:0;border:1px solid transparent;border-radius:3px;color:#697386;background:transparent}.pane-nav:hover,.pane-maximize:hover,.pane-browse:hover:not(:disabled),.pane-mic:hover{color:#d8dee9;background:#1a202a;border-color:#2a3340}.pane-close:hover{color:#f7768e;background:#f7768e24;border-color:#f7768e66}.pane-proceed:hover{color:#9ece6a;background:#9ece6a24;border-color:#9ece6a73}.pane-mic:disabled,.pane-browse:disabled{opacity:.4;cursor:default}.pane-mic.listening{color:#f7768e;background:#f7768e29;border-color:#f7768e8c;animation:mic-pulse 1.2s ease-in-out infinite}.pane-mic.transcribing{color:#e0af68;background:#e0af6824;border-color:#e0af6880;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-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:#0a0c10;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:#0d1015}.empty-pane-body{display:flex;align-items:center;justify-content:center;flex-direction:column;gap:6px;height:100%;color:#697386}.empty-glyph{display:grid;place-items:center;width:30px;height:30px;border:1px dashed #344050;border-radius:5px;color:#697386;font-size:18px}.empty-pane-body strong{font-size:11px;color:#8b93a3;font-weight:550}.empty-pane-body small{font-size:10px}.empty-pane-body button{margin-top:8px;padding:6px 9px;border:1px solid #2a3340;border-radius:4px;background:#151a22;color:#8b93a3;font-size:10px}.empty-pane-body button:hover{border-color:#7aa2f7;color:#d8dee9}.status-bar{height:25px;display:flex;align-items:center;gap:8px;padding:0 10px;border-top:1px solid #202630;background:#0f1218;color:#697386;font:10px JetBrains Mono,monospace}.status-separator{width:1px;height:11px;background:#2a3340}.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:#11161e;border:1px solid #303b4b;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 #202630}.overview-head h2,.modal-head h2{margin:4px 0 0;font-size:17px;font-weight:600}.close-button{padding:4px 7px;border:1px solid #2a3340;border-radius:4px;background:#151a22;color:#697386;font:10px JetBrains Mono,monospace}.close-button:hover{color:#d8dee9;border-color:#7aa2f7}.thumbnail-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:18px 20px}.thumbnail{padding:0;text-align:left;background:#0d1015;border:1px solid #252f3c;border-radius:6px;overflow:hidden}.thumbnail:hover,.thumbnail.active{border-color:#7aa2f7}.thumbnail.active{box-shadow:0 0 0 1px #7aa2f733}.thumbnail-preview{height:100px;padding:13px;background:#0a0c10;border-bottom:1px solid #202630;display:flex;flex-direction:column;gap:6px;position:relative}.thumbnail-line{height:3px;width:70%;background:#4e5969;border-radius:2px}.thumbnail-line.short{width:43%}.thumbnail-line.cyan{background:#7dcfff}.thumbnail-line.green{background:#9ece6a;width:56%}.thumbnail-cursor{position:absolute;left:13px;bottom:15px;width:5px;height:9px;background:#9ece6a}.thumbnail-label{display:flex;flex-direction:column;gap:3px;padding:9px 10px}.thumbnail-label b{font-size:11px;font-weight:550;color:#d8dee9}.thumbnail-label small{font:10px JetBrains Mono,monospace;color:#697386}.overview-empty{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;gap:7px;padding:36px;color:#697386}.overview-empty span{font-size:24px}.overview-empty b{color:#d8dee9;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 #202630}.overview-foot button{padding:7px 9px;border:1px solid #2a3340;border-radius:4px;background:#151a22;color:#8b93a3;font-size:10px}.overview-foot button:hover{color:#d8dee9;border-color:#7aa2f7}.overview-hint{margin-left:auto;color:#4e5969;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 #1b222c}.shortcut-what{color:#c0c8d8;font-size:12px}.shortcut-chord{padding:3px 6px;border:1px solid #2a3340;border-radius:5px;background:#161d27;color:#c0c8d8;min-width:118px}.shortcut-chord:hover{border-color:#3b82f6}.shortcut-chord.capturing{border-color:#7aa2f7;background:#16213a}.shortcut-chord kbd{font:10px JetBrains Mono,monospace}.shortcut-prompt{color:#7aa2f7;font:10px JetBrains Mono,monospace}.shortcut-reset{padding:3px 8px;border:1px solid transparent;border-radius:5px;background:transparent;color:#697386;font-size:11px}.shortcut-reset:hover:not(:disabled){color:#d8dee9;background:#1a202a;border-color:#2a3340}.shortcut-reset:disabled{opacity:.35;cursor:default}.shortcut-problem{grid-column:1 / -1;color:#e0af68;font-size:11px;line-height:1.4}.settings-warning{padding:9px 11px;border:1px solid rgba(224,175,104,.4);border-radius:6px;background:#e0af6814;color:#e0af68!important}.context-menu{position:fixed;z-index:40;min-width:178px;padding:4px;background:#11161e;border:1px solid #303b4b;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:#c0c8d8;font-size:12px;text-align:left}.context-menu-item:hover:not(:disabled){background:#1b2330}.context-menu-item:focus-visible{outline:1px solid #7aa2f7;outline-offset:-1px}.context-menu-item:disabled{opacity:.38;cursor:default}.context-menu-item.danger:hover:not(:disabled){color:#f7768e;background:#f7768e24}.context-menu-item .icon-svg{width:14px;height:14px;flex:none;color:#8b93a3}.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:#11161e;border:1px solid #303b4b;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:#8b93a3;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 #1b222c}.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 #2a3340;border-bottom-width:2px;border-radius:4px;background:#161d27;color:#c0c8d8;font:10px JetBrains Mono,monospace;white-space:nowrap}.help-keys td{padding:5px 0;color:#8b93a3;font-size:12px;line-height:1.45}.help-note{display:flex;gap:8px;margin:14px 0 0;padding:10px;border:1px solid #2a3340;border-radius:6px;background:#0e131b;color:#8b93a3;font-size:11.5px;line-height:1.5}.help-note .icon-svg{flex:none;width:15px;height:15px;margin-top:1px;color:#7aa2f7}.help-features dl{margin:0}.help-features dt{margin-top:12px;color:#d8dee9;font-size:12.5px;font-weight:600}.help-features dt:first-child{margin-top:0}.help-features dd{margin:3px 0 0;color:#8b93a3;font-size:12px;line-height:1.5}.help-foot{flex:none;padding-top:12px;color:#5d6675;font-size:10.5px}.modal-card{width:390px;padding:0 20px 18px;background:#11161e;border:1px solid #303b4b;border-radius:8px;box-shadow:0 20px 80px #0009}.modal-card p{color:#8b93a3;font-size:12px;line-height:1.5;margin:17px 0}.modal-card label{display:block;margin-top:14px;color:#8b93a3;font-size:11px}.modal-card input{display:block;width:100%;margin-top:6px;padding:9px 10px;border:1px solid #2a3340;border-radius:4px;background:#0a0c10;color:#d8dee9;font:12px JetBrains Mono,monospace}.modal-card input:focus{border-color:#7aa2f7;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 #252f3c;border-radius:6px;background:#0d1015;color:#8b93a3;text-align:left}.dialog-tab b{color:#d8dee9;font-size:12px;font-weight:550}.dialog-tab small{font:10px JetBrains Mono,monospace;color:#4e5969}.dialog-tab:hover:not(:disabled){border-color:#7aa2f7}.dialog-tab:disabled{opacity:.6}.dialog-tab.active{border-color:#7aa2f7;background:#172033;box-shadow:0 0 0 1px #7aa2f733}.modal-actions{display:flex;justify-content:flex-end;gap:7px;margin-top:20px}.modal-actions button{padding:8px 11px;border:1px solid #2a3340;border-radius:4px;background:#151a22;color:#8b93a3;font-size:11px}.modal-actions button:hover{color:#d8dee9;border-color:#7aa2f7}.primary-button{background:#9ece6a!important;border-color:#9ece6a!important;color:#0a0c10!important;font-weight:600}.primary-button:disabled{opacity:.55;cursor:wait}.approval-card code{display:block;padding:11px;border:1px solid #2a3340;border-radius:4px;background:#0a0c10;color:#7dcfff;font:12px JetBrains Mono,monospace}.warning-text{color:#e0af68}@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:#7aa2f7}.session-ghosted{opacity:.58;border:1px dashed #344050}.session-ghosted:hover{opacity:.9}.session-error b{color:#f7768e}.session-empty button{align-self:flex-start;padding:5px 8px;border:1px solid #2a3340;border-radius:4px;background:#151a22;color:#8b93a3;font-size:10px}.history-empty button:hover{border-color:#7aa2f7;color:#d8dee9}.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 #303b4b;border-radius:7px;background:#11161e;box-shadow:0 18px 55px #0009}.palette-query{width:100%;padding:8px 10px;border:1px solid #303b4b;border-radius:5px;background:#0d1117;color:#d8dee9}.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:#8b93a3}.palette-row.active{background:#1c2531;color:#d8dee9;box-shadow:inset 2px 0 #7aa2f7}.palette-command{min-width:0;font:12px JetBrains Mono,monospace;white-space:pre-wrap;word-break:break-all}.palette-command b{color:#7aa2f7;font-weight:700}.palette-meta{flex:none;display:flex;align-items:baseline;gap:8px;font:10px JetBrains Mono,monospace;color:#697386}.palette-cwd{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.palette-failed{padding:1px 4px;border:1px solid #f7768e;border-radius:3px;color:#f7768e}.palette-empty{display:flex;flex-direction:column;align-items:flex-start;gap:7px;padding:18px 8px;color:#697386}.palette-empty b{color:#8b93a3;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:#697386;font-size:10px;line-height:1.4}.settings-snippet{margin:8px 0 0;max-height:220px;overflow:auto;padding:10px;border:1px solid #202630;border-radius:5px;background:#0d1117;color:#9aa4b2;font:10px JetBrains Mono,monospace;line-height:1.5;white-space:pre-wrap}.settings-note{margin-top:12px;padding:9px 10px;border:1px solid #252d39;border-radius:5px;color:#8b93a3;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 #303b4b;border-radius:7px;background:#11161e;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:#d8dee9}.history-head button{background:transparent;color:#697386;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 #202630;background:transparent;color:#8b93a3}.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:#1a202a;color:#d8dee9}.history-item b,.history-item small{display:block}.history-item small,.history-note,.history-empty{color:#697386;font-size:10px}.history-kind{min-width:43px;color:#7aa2f7;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-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:#11161e;border:1px solid #303b4b;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 #202630;background:#0d1015;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:#8b93a3;text-align:left}.settings-nav-item b{font-size:12px;font-weight:550}.settings-nav-item small{font:10px JetBrains Mono,monospace;color:#4e5969}.settings-nav-item:hover{background:#151a22;color:#d8dee9}.settings-nav-item.active{background:#172033;border-color:#2a3852;color:#d8dee9;box-shadow:inset 2px 0 #7aa2f7}.settings-page{padding:16px 20px 18px;overflow:auto}.settings-blurb{margin:0 0 4px;color:#8b93a3;font-size:12px;line-height:1.5}.settings-field{display:block;margin-top:14px;color:#8b93a3;font-size:11px}.settings-field-label{display:block;margin-bottom:6px}.settings-field small{display:block;margin-top:5px;color:#697386;font-size:10px;line-height:1.45}.settings-field select,.settings-field input{display:block;width:100%;padding:8px 10px;border:1px solid #2a3340;border-radius:4px;background:#0a0c10;color:#d8dee9;font:12px JetBrains Mono,monospace}.settings-field select:focus,.settings-field input:focus{border-color:#7aa2f7;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:#7aa2f7}.settings-range output{min-width:88px;text-align:right;color:#d8dee9;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:#9ece6a}.settings-toggle span{display:flex;flex-direction:column;gap:3px}.settings-toggle b{color:#d8dee9;font-size:12px;font-weight:550}.settings-toggle small{color:#697386;font-size:10px;line-height:1.45}.settings-toggle-fixed{cursor:default}.settings-toggle-fixed b{color:#8b93a3}.settings-toggle-fixed input{accent-color:#4e5969}.settings-preview{display:flex;flex-direction:column;margin-top:16px;padding:12px;border:1px solid #202630;border-radius:5px;background:#0a0c10;color:#9ece6a;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 #252f3c;border-radius:6px;background:#0d1015;color:#8b93a3;text-align:left}.settings-engine b{color:#d8dee9;font-size:12px;font-weight:600}.settings-engine small{font-size:10px;line-height:1.45}.settings-engine:hover{border-color:#7aa2f7}.settings-engine.active{border-color:#7aa2f7;box-shadow:0 0 0 1px #7aa2f733}.settings-test{margin-top:18px;padding:12px;border:1px solid #252f3c;border-radius:6px;background:#0d1015}.settings-test-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.settings-test-head b{display:block;color:#d8dee9;font-size:12px;font-weight:550}.settings-test-head small{color:#697386;font-size:10px;line-height:1.45}.settings-test-button{flex:none;padding:7px 11px;border:1px solid #2a3340;border-radius:4px;background:#151a22;color:#8b93a3;font-size:11px}.settings-test-button:hover:not(:disabled){border-color:#7aa2f7;color:#d8dee9}.settings-test-button:disabled{opacity:.6;cursor:wait}.settings-test-button.recording{border-color:#f7768e;color:#f7768e}.settings-test-status,.settings-test-metrics{margin:9px 0 0;color:#697386;font:10px JetBrains Mono,monospace}.settings-test-error{margin:9px 0 0;color:#f7768e;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 #2a3340;border-radius:4px;background:#151a22;color:#8b93a3;font-size:11px}.settings-key-row button:hover:not(:disabled){border-color:#7aa2f7;color:#d8dee9}.settings-key-row button:disabled{opacity:.5}.settings-field .settings-key-notice{color:#9ece6a}.settings-field .settings-key-error{color:#f7768e}.settings-test-transcript{display:block;margin-top:9px;padding:10px;border:1px solid #2a3340;border-radius:4px;background:#0a0c10;color:#7dcfff;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 #202630}.settings-hint{color:#4e5969;font:10px JetBrains Mono,monospace}.settings-foot button{padding:7px 11px;border:1px solid #2a3340;border-radius:4px;background:#151a22;color:#8b93a3;font-size:11px}.settings-foot button:hover{border-color:#e0af68;color:#e0af68}@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 #202630}.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:#11161e;border:1px solid #303b4b;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 #202630}.sftp-head h2{margin:4px 0 0;font-size:16px;font-weight:600}.sftp-body{display:grid;grid-template-columns:1fr 1fr;gap:1px;flex:1;min-height:0;background:#202630}.sftp-pane{display:flex;flex-direction:column;min-height:0;min-width:0;background:#11161e}.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:#697386}.sftp-pane-tools button:hover:not(:disabled){background:#1a202a;color:#9ece6a}.sftp-pane-tools button:disabled{opacity:.4;cursor:default}.sftp-pane-tools .sftp-danger:hover:not(:disabled){color:#f7768e}.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 #2a3340;border-radius:4px;background:#0a0c10;color:#d8dee9;font:11px JetBrains Mono,monospace}.sftp-path input:focus{border-color:#7aa2f7;outline:0}.sftp-list{flex:1;min-height:0;overflow-y:auto;border-top:1px solid #202630}.sftp-row{display:grid;grid-template-columns:16px 1fr auto auto;align-items:center;gap:9px;width:100%;padding:6px 12px;background:transparent;color:#8b93a3;text-align:left;border-bottom:1px solid #171d26}.sftp-row:hover{background:#151a22}.sftp-row.selected{background:#1a202a;color:#d8dee9;box-shadow:inset 2px 0 #9ece6a}.sftp-row.is-directory{color:#7aa2f7}.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:#4e5969;white-space:nowrap}.sftp-modified{min-width:96px;text-align:right}.sftp-empty{display:grid;gap:4px;padding:26px 16px;text-align:center;color:#8b93a3}.sftp-empty small{color:#4e5969}.sftp-error b{color:#f7768e}.sftp-pane-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border-top:1px solid #202630}.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:#4e5969}.sftp-prompt,.sftp-inline-form{padding:14px 20px;border-top:1px solid #202630;background:#151a22}.sftp-prompt pre{margin:8px 0 0;padding:9px 10px;max-height:120px;overflow:auto;border:1px solid #2a3340;border-radius:4px;background:#0a0c10;color:#e0af68;font:11px JetBrains Mono,monospace;white-space:pre-wrap}.sftp-prompt label,.sftp-inline-form label{display:block;margin-top:12px;color:#8b93a3;font-size:11px}.sftp-prompt input,.sftp-inline-form input{display:block;width:100%;margin-top:6px;padding:8px 10px;border:1px solid #2a3340;border-radius:4px;background:#0a0c10;color:#d8dee9;font:12px JetBrains Mono,monospace}.sftp-inline-form p{margin:10px 0 0;color:#8b93a3;font-size:12px}.sftp-danger-button{background:#f7768e;color:#11161e}.sftp-foot{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:9px 20px;border-top:1px solid #202630;font-size:11px;color:#8b93a3}.sftp-hint{color:#4e5969;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 #2a3340;border-radius:4px;background:#0a0c10;overflow:hidden}.sftp-progress-fill{position:absolute;inset:0 auto 0 0;background:#1e3a2a;transition:width .18s linear}.sftp-progress span{position:relative;font:10px JetBrains Mono,monospace;color:#9ece6a}: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-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-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-mic{color:#68768a}: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}
|