clideck 1.31.10 → 1.31.12
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 +11 -1
- package/bin/clideck.js +10 -2
- package/clideck-agents-cli.js +14 -8
- package/clideck-ask-cli.js +15 -4
- package/handlers.js +36 -19
- package/opencode-plugin/clideck-bridge.js +3 -1
- package/package.json +1 -1
- package/public/index.html +1 -0
- package/public/js/app.js +48 -6
- package/public/js/creator.js +4 -4
- package/public/js/prompts.js +2 -2
- package/public/js/settings.js +6 -4
- package/public/js/state.js +47 -0
- package/public/js/utils.js +10 -0
- package/public/tailwind.css +1 -1
- package/server.js +5 -15
- package/session-agents.js +28 -13
- package/session-ask.js +59 -14
- package/transcript.js +50 -9
package/README.md
CHANGED
|
@@ -57,14 +57,24 @@ clideck --port 4001
|
|
|
57
57
|
<img src="assets/autopilot.gif" width="720" alt="Autopilot routing work between agents">
|
|
58
58
|
</p>
|
|
59
59
|
|
|
60
|
-
**Ask another session** - from inside any CliDeck session, an agent can consult another session
|
|
60
|
+
**Ask another session** - from inside any CliDeck session, an agent can consult another session and get the answer back as command output:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
+
clideck agents
|
|
63
64
|
clideck ask --session "Reviewer" --message "Review this output and return findings." --timeout 10m
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
CliDeck injects the message into the real target terminal, submits it, waits for the target session to finish, then returns the latest response to the caller.
|
|
67
68
|
|
|
69
|
+
By default, target lookup is limited to the caller's project. For cross-project asks, discover the full address first:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
clideck agents --all
|
|
73
|
+
clideck ask "@website/Docs Writer" "Check if the docs mention the new CLI flags." --timeout 15m
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If project or session names contain spaces, quote the whole target. The target is another LLM agent, not a fast CLI command, so callers should set both `clideck ask --timeout` and their own shell/tool timeout high enough. If the target session is busy, CliDeck does not queue the message; the caller gets a clear busy response and can retry later or ask another idle session.
|
|
77
|
+
|
|
68
78
|
**Mobile remote** - the agents keep running on the local machine. status, prompts, history, and replies stay available from a phone while away. E2E encrypted, no account needed.
|
|
69
79
|
|
|
70
80
|
**Native terminals** - each session opens into its real terminal. keys go straight to the agent, nothing sits in the middle.
|
package/bin/clideck.js
CHANGED
|
@@ -8,7 +8,7 @@ function usage() {
|
|
|
8
8
|
'',
|
|
9
9
|
'Usage:',
|
|
10
10
|
' clideck [--host <host>] [--port <port>]',
|
|
11
|
-
' clideck agents [--json]',
|
|
11
|
+
' clideck agents [--json] [--all]',
|
|
12
12
|
' clideck ask --session <name-or-id> --message <text> [--timeout 10m]',
|
|
13
13
|
'',
|
|
14
14
|
'Options:',
|
|
@@ -21,24 +21,32 @@ function usage() {
|
|
|
21
21
|
' clideck agents',
|
|
22
22
|
' Lists active sessions in the same project as the caller session.',
|
|
23
23
|
' Use this first when an agent needs to discover who it can ask.',
|
|
24
|
+
' Add --all to list cross-project targets with @project/session ask addresses.',
|
|
24
25
|
'',
|
|
25
26
|
' clideck ask',
|
|
26
27
|
' Use from inside a CliDeck session when one agent needs an answer from another session.',
|
|
27
28
|
'',
|
|
28
29
|
'Ask behavior:',
|
|
29
|
-
'
|
|
30
|
+
' Unscoped target lookup is limited to the same project as the caller session.',
|
|
31
|
+
' Cross-project asks must use an explicit @project/session target.',
|
|
30
32
|
' CliDeck sends the message into the real target terminal, presses Enter, waits for the',
|
|
31
33
|
' target to finish, then prints the target agent response to stdout.',
|
|
34
|
+
' The target is another LLM agent. It may need minutes to think, read files, and use tools.',
|
|
35
|
+
' Set both `--timeout` and your shell/tool-call timeout high enough, or your caller may exit',
|
|
36
|
+
' before the target agent finishes.',
|
|
32
37
|
'',
|
|
33
38
|
'Examples:',
|
|
34
39
|
' clideck agents',
|
|
35
40
|
' clideck agents --json',
|
|
41
|
+
' clideck agents --all',
|
|
36
42
|
' clideck ask --session "Reviewer" --message "Review my changes and return only findings."',
|
|
37
43
|
' clideck ask "research manager" "Check this plan and tell me what is missing." --timeout 15m',
|
|
44
|
+
' clideck ask "@website/Docs Writer" "Check if the docs mention the new CLI flags." --timeout 15m',
|
|
38
45
|
' cat notes.md | clideck ask --session "Docs Writer" --timeout 10m',
|
|
39
46
|
'',
|
|
40
47
|
'Notes for agents:',
|
|
41
48
|
' Run `clideck agents` to discover available same-project sessions.',
|
|
49
|
+
' Run `clideck agents --all` before a cross-project ask.',
|
|
42
50
|
' Run `clideck ask --help` for the exact ask command contract.',
|
|
43
51
|
' If the target name has spaces, quote it.',
|
|
44
52
|
' If several sessions have the same name in the same project, use the session id.',
|
package/clideck-agents-cli.js
CHANGED
|
@@ -4,13 +4,15 @@ const https = require('https');
|
|
|
4
4
|
function usage() {
|
|
5
5
|
return [
|
|
6
6
|
'Usage:',
|
|
7
|
-
' clideck agents [--json]',
|
|
7
|
+
' clideck agents [--json] [--all]',
|
|
8
8
|
'',
|
|
9
9
|
'Lists active CliDeck sessions in the same project as the caller session.',
|
|
10
10
|
'Use this from inside a CliDeck session before `clideck ask` to discover target names.',
|
|
11
|
+
'Use --all to discover cross-project targets and their @project/session ask addresses.',
|
|
11
12
|
'',
|
|
12
13
|
'Options:',
|
|
13
14
|
' --json Print machine-readable JSON.',
|
|
15
|
+
' --all List sessions across all projects.',
|
|
14
16
|
' --url <url> CliDeck server URL. Default: CLIDECK_URL or http://127.0.0.1:<port>.',
|
|
15
17
|
' -h, --help Show this help.',
|
|
16
18
|
].join('\n');
|
|
@@ -18,10 +20,11 @@ function usage() {
|
|
|
18
20
|
|
|
19
21
|
function parseArgs(args) {
|
|
20
22
|
const port = process.env.CLIDECK_PORT || process.env.PORT || '4000';
|
|
21
|
-
const out = { json: false, url: process.env.CLIDECK_URL || `http://127.0.0.1:${port}` };
|
|
23
|
+
const out = { json: false, all: false, url: process.env.CLIDECK_URL || `http://127.0.0.1:${port}` };
|
|
22
24
|
for (let i = 0; i < args.length; i++) {
|
|
23
25
|
const arg = args[i];
|
|
24
26
|
if (arg === '--json') out.json = true;
|
|
27
|
+
else if (arg === '--all') out.all = true;
|
|
25
28
|
else if (arg === '--url') out.url = args[++i];
|
|
26
29
|
else if (arg === '--help' || arg === '-h') out.help = true;
|
|
27
30
|
else throw new Error(`Unknown argument: ${arg}`);
|
|
@@ -29,10 +32,11 @@ function parseArgs(args) {
|
|
|
29
32
|
return out;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
function getJson(url, callerSessionId) {
|
|
35
|
+
function getJson(url, callerSessionId, all = false) {
|
|
33
36
|
return new Promise((resolve, reject) => {
|
|
34
37
|
const target = new URL('/api/session/agents', url);
|
|
35
38
|
target.searchParams.set('callerSessionId', callerSessionId);
|
|
39
|
+
if (all) target.searchParams.set('all', '1');
|
|
36
40
|
const client = target.protocol === 'https:' ? https : http;
|
|
37
41
|
const req = client.get(target, (res) => {
|
|
38
42
|
let data = '';
|
|
@@ -53,13 +57,15 @@ function getJson(url, callerSessionId) {
|
|
|
53
57
|
});
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
function formatAgents(agents) {
|
|
57
|
-
if (!agents.length) return 'No active sessions found in this project.';
|
|
60
|
+
function formatAgents(agents, opts = {}) {
|
|
61
|
+
if (!agents.length) return opts.all ? 'No active sessions found.' : 'No active sessions found in this project.';
|
|
58
62
|
return agents.map(a => {
|
|
59
63
|
const marker = a.caller ? 'self' : 'peer';
|
|
60
64
|
const status = a.working ? 'working' : 'idle';
|
|
61
65
|
const preview = a.lastPreview ? ` - ${a.lastPreview}` : '';
|
|
62
|
-
|
|
66
|
+
const address = a.address && a.address !== a.name ? ` ask=${a.address}` : '';
|
|
67
|
+
const project = opts.all && a.project ? ` project="${a.project}"` : '';
|
|
68
|
+
return `${a.name} (${marker}, ${a.preset}, ${status}) id=${a.id}${address}${project}${preview}`;
|
|
63
69
|
}).join('\n');
|
|
64
70
|
}
|
|
65
71
|
|
|
@@ -73,9 +79,9 @@ async function run(args) {
|
|
|
73
79
|
const callerSessionId = process.env.CLIDECK_SESSION_ID || '';
|
|
74
80
|
if (!callerSessionId) throw new Error('CLIDECK_SESSION_ID is missing. Run this from inside a CliDeck session.');
|
|
75
81
|
|
|
76
|
-
const res = await getJson(opts.url, callerSessionId);
|
|
82
|
+
const res = await getJson(opts.url, callerSessionId, opts.all);
|
|
77
83
|
if (opts.json) process.stdout.write(JSON.stringify(res.agents || [], null, 2) + '\n');
|
|
78
|
-
else process.stdout.write(formatAgents(res.agents || []) + '\n');
|
|
84
|
+
else process.stdout.write(formatAgents(res.agents || [], opts) + '\n');
|
|
79
85
|
} catch (e) {
|
|
80
86
|
process.stderr.write(`${e.message}\n`);
|
|
81
87
|
process.exitCode = 1;
|
package/clideck-ask-cli.js
CHANGED
|
@@ -6,11 +6,19 @@ function usage() {
|
|
|
6
6
|
'Usage:',
|
|
7
7
|
' clideck ask --session <name-or-id> --message <text> [--timeout 10m]',
|
|
8
8
|
' clideck ask <name-or-id> <message> [--timeout 10m]',
|
|
9
|
+
' clideck ask "@project-name/session-name" <message> [--timeout 10m]',
|
|
9
10
|
' cat file.txt | clideck ask --session <name-or-id> [--timeout 10m]',
|
|
10
11
|
'',
|
|
11
12
|
'Use from inside a CliDeck session when this agent needs an answer from another active session.',
|
|
12
|
-
'
|
|
13
|
-
'
|
|
13
|
+
'Unscoped target lookup is limited to the same project as the caller session.',
|
|
14
|
+
'Use @project/session only when you intentionally need to ask across projects.',
|
|
15
|
+
'Run `clideck agents` or `clideck agents --all` first to discover available target sessions.',
|
|
16
|
+
'',
|
|
17
|
+
'Important for agents:',
|
|
18
|
+
' The target is another LLM agent, not a fast CLI command. It may need minutes to read files,',
|
|
19
|
+
' think, use tools, and answer. Set BOTH the `clideck ask --timeout` value and your own',
|
|
20
|
+
' shell/tool-call timeout high enough. If your shell tool kills this process first, the target',
|
|
21
|
+
' agent may keep working but you will lose the response.',
|
|
14
22
|
'',
|
|
15
23
|
'Options:',
|
|
16
24
|
' -s, --session <name-or-id> Target session name or id.',
|
|
@@ -109,10 +117,12 @@ function findAgent(agents, target) {
|
|
|
109
117
|
if (!text) return null;
|
|
110
118
|
const byId = agents.filter(a => a.id === text);
|
|
111
119
|
if (byId.length === 1) return byId[0];
|
|
120
|
+
const byAddress = agents.filter(a => a.address === text);
|
|
121
|
+
if (byAddress.length === 1) return byAddress[0];
|
|
112
122
|
const exact = agents.filter(a => a.name === text);
|
|
113
123
|
if (exact.length === 1) return exact[0];
|
|
114
124
|
const lower = text.toLowerCase();
|
|
115
|
-
const insensitive = agents.filter(a => String(a.name || '').toLowerCase() === lower);
|
|
125
|
+
const insensitive = agents.filter(a => String(a.name || '').toLowerCase() === lower || String(a.address || '').toLowerCase() === lower);
|
|
116
126
|
return insensitive.length === 1 ? insensitive[0] : null;
|
|
117
127
|
}
|
|
118
128
|
|
|
@@ -133,7 +143,8 @@ function startProgressHints(opts, callerSessionId) {
|
|
|
133
143
|
const tick = async () => {
|
|
134
144
|
if (stopped) return;
|
|
135
145
|
try {
|
|
136
|
-
const
|
|
146
|
+
const all = String(opts.session || '').trim().startsWith('@') ? '&all=1' : '';
|
|
147
|
+
const path = `/api/session/agents?callerSessionId=${encodeURIComponent(callerSessionId)}${all}`;
|
|
137
148
|
const res = await getJson(opts.url, path, 4000);
|
|
138
149
|
const agent = findAgent(res.agents || [], opts.session);
|
|
139
150
|
const elapsed = formatDuration(Date.now() - started);
|
package/handlers.js
CHANGED
|
@@ -167,7 +167,13 @@ function hasExistingHook(arr, hookFile, route) {
|
|
|
167
167
|
return !!arr?.some(h => h.hooks?.some(x => {
|
|
168
168
|
if (!x.command?.includes(hookFile) || !x.command?.includes(` ${route}`)) return false;
|
|
169
169
|
const hookPath = extractQuotedPath(x.command, hookFile);
|
|
170
|
-
|
|
170
|
+
if (!hookPath || !existsSync(hookPath)) return false;
|
|
171
|
+
const command = String(x.command).replace(/\\/g, '/');
|
|
172
|
+
const normalizedPath = hookPath.replace(/\\/g, '/');
|
|
173
|
+
const quotedIdx = command.indexOf(`"${normalizedPath}"`);
|
|
174
|
+
if (quotedIdx < 0) return false;
|
|
175
|
+
const suffix = command.slice(quotedIdx + normalizedPath.length + 2).trim().split(/\s+/);
|
|
176
|
+
return suffix[0] === String(PORT) && suffix[1] === route;
|
|
171
177
|
}));
|
|
172
178
|
}
|
|
173
179
|
|
|
@@ -196,6 +202,19 @@ function codexConfigLooksHealthy(content, port, codexHome) {
|
|
|
196
202
|
return !!helperPath && existsSync(helperPath);
|
|
197
203
|
}
|
|
198
204
|
|
|
205
|
+
function opencodeBridgeLooksHealthy() {
|
|
206
|
+
const bridgePath = join(opencodePluginDir, 'clideck-bridge.js');
|
|
207
|
+
if (!existsSync(bridgePath)) return false;
|
|
208
|
+
try {
|
|
209
|
+
const content = readFileSync(bridgePath, 'utf8');
|
|
210
|
+
return content.includes('/opencode-events')
|
|
211
|
+
&& content.includes('CLIDECK_URL')
|
|
212
|
+
&& content.includes('CLIDECK_PORT');
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
199
218
|
function detectTelemetryConfig(c) {
|
|
200
219
|
const port = String(PORT);
|
|
201
220
|
let changed = false;
|
|
@@ -237,7 +256,7 @@ function detectTelemetryConfig(c) {
|
|
|
237
256
|
if (!detected) reason = 'Needs re-patch';
|
|
238
257
|
} catch {}
|
|
239
258
|
} else if (preset.presetId === 'opencode') {
|
|
240
|
-
detected =
|
|
259
|
+
detected = opencodeBridgeLooksHealthy();
|
|
241
260
|
if (!detected) reason = 'Needs re-patch';
|
|
242
261
|
} else { continue; }
|
|
243
262
|
if (preset.available && preset.minVersion && !preset.versionOk) {
|
|
@@ -307,27 +326,16 @@ function onConnection(ws) {
|
|
|
307
326
|
const transcript = require('./transcript');
|
|
308
327
|
const sess = sessions.getSessions().get(msg.id);
|
|
309
328
|
if (sess) {
|
|
310
|
-
transcript.
|
|
311
|
-
|
|
312
|
-
sess._finalizeOnIdle = false;
|
|
313
|
-
// if (sess.presetId === 'claude-code') {
|
|
314
|
-
// console.log(`[claude] terminal.buffer finalize session=${msg.id.slice(0,8)} lines=${msg.lines?.length || 0}`);
|
|
315
|
-
// }
|
|
316
|
-
transcript.commitAgentCandidate(msg.id, sess.presetId);
|
|
317
|
-
}
|
|
318
|
-
let choices = require('./transcript').detectMenu(msg.lines, sess.presetId);
|
|
329
|
+
const rawChoices = transcript.detectMenu(msg.lines, sess.presetId);
|
|
330
|
+
let choices = rawChoices;
|
|
319
331
|
// Codex: only trust menu detection if last OTEL event was response.completed
|
|
320
332
|
if (choices && sess.presetId === 'codex') {
|
|
321
333
|
const last = require('./telemetry-receiver').getLastEvent(msg.id);
|
|
322
334
|
if (!last.startsWith('codex.sse_event:response.completed')) {
|
|
323
|
-
// console.log(`[codex] menu rejected — lastEvent=${last} session=${msg.id.slice(0,8)}`);
|
|
324
335
|
choices = null;
|
|
325
|
-
} else {
|
|
326
|
-
// console.log(`[codex] menu accepted session=${msg.id.slice(0,8)}`);
|
|
327
336
|
}
|
|
328
337
|
}
|
|
329
338
|
if (choices && sess.presetId === 'claude-code' && msg.menuVersion && (sess._menuConsumedVersion || 0) >= msg.menuVersion) {
|
|
330
|
-
// console.log(`[claude] menu ignored stale version=${msg.menuVersion} consumed=${sess._menuConsumedVersion || 0} session=${msg.id.slice(0,8)}`);
|
|
331
339
|
choices = null;
|
|
332
340
|
}
|
|
333
341
|
let key = choices ? JSON.stringify(choices) : '';
|
|
@@ -335,22 +343,27 @@ function onConnection(ws) {
|
|
|
335
343
|
// Once that exact menu was approved, ignore repeated detections of the
|
|
336
344
|
// same signature until the next real turn starts.
|
|
337
345
|
if (choices && sess.presetId === 'claude-code' && key === (sess._resolvedMenuKey || '')) {
|
|
338
|
-
// console.log(`[claude] menu ignored resolved key session=${msg.id.slice(0,8)}`);
|
|
339
346
|
choices = null;
|
|
340
347
|
key = '';
|
|
341
348
|
}
|
|
349
|
+
const candidateLines = (choices || (rawChoices && sess.presetId === 'claude-code'))
|
|
350
|
+
? transcript.stripMenu(msg.lines, sess.presetId)
|
|
351
|
+
: msg.lines;
|
|
352
|
+
transcript.updateAgentCandidate(msg.id, sess.presetId, candidateLines);
|
|
353
|
+
if (!sess.working && sess._finalizeOnIdle) {
|
|
354
|
+
sess._finalizeOnIdle = false;
|
|
355
|
+
transcript.commitAgentCandidate(msg.id, sess.presetId);
|
|
356
|
+
}
|
|
342
357
|
// Auto-approve: send Enter immediately when menu detected
|
|
343
358
|
if (choices && plugins.shouldAutoApproveMenu(msg.id)) {
|
|
344
359
|
setTimeout(() => sessions.input({ id: msg.id, data: '\r' }), 500);
|
|
345
360
|
}
|
|
361
|
+
if (choices) transcript.commitAgentCandidate(msg.id, sess.presetId);
|
|
346
362
|
if (key !== (sess._menuKey || '')) {
|
|
347
363
|
sess._menuKey = key;
|
|
348
364
|
sessions.broadcast({ type: 'session.menu', id: msg.id, choices: choices || [] });
|
|
349
365
|
if (choices) {
|
|
350
366
|
if (sess.presetId === 'claude-code' && msg.menuVersion) sess._menuActiveVersion = msg.menuVersion;
|
|
351
|
-
// if (sess.presetId === 'claude-code') {
|
|
352
|
-
// console.log(`[claude] menu detected session=${msg.id.slice(0,8)} choices=${choices.length} version=${msg.menuVersion || 0}`);
|
|
353
|
-
// }
|
|
354
367
|
plugins.notifyMenu(msg.id, choices);
|
|
355
368
|
if (sess.presetId === 'codex') require('./telemetry-receiver').cancelCodexMenuPoll(msg.id);
|
|
356
369
|
sessions.broadcast({ type: 'session.status', id: msg.id, working: false, source: 'menu' });
|
|
@@ -475,6 +488,10 @@ function onConnection(ws) {
|
|
|
475
488
|
ws.send(JSON.stringify({ type: 'project.openPath.result', id: msg.id, success: false, error: 'Project path is not set' }));
|
|
476
489
|
break;
|
|
477
490
|
}
|
|
491
|
+
if (process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
|
|
492
|
+
ws.send(JSON.stringify({ type: 'project.openPath.result', id: msg.id, success: false, headless: true, path: proj.path }));
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
478
495
|
const cmd = process.platform === 'darwin'
|
|
479
496
|
? 'open'
|
|
480
497
|
: process.platform === 'win32'
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// Forwards session events to CliDeck server via HTTP POST.
|
|
3
3
|
// Install: copy to ~/.config/opencode/plugins/clideck-bridge.js
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const env = globalThis.process?.env || {};
|
|
6
|
+
const baseUrl = env.CLIDECK_URL || `http://localhost:${env.CLIDECK_PORT || "4000"}`;
|
|
7
|
+
const CLIDECK_URL = `${baseUrl.replace(/\/$/, "")}/opencode-events`;
|
|
6
8
|
|
|
7
9
|
function post(payload) {
|
|
8
10
|
fetch(CLIDECK_URL, {
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -173,6 +173,7 @@
|
|
|
173
173
|
<span id="save-indicator" class="save-indicator" title="Sessions saved">
|
|
174
174
|
<svg class="save-tick" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
|
175
175
|
<svg class="save-spin" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M12 2a10 10 0 0 1 10 10"/></svg>
|
|
176
|
+
<svg class="save-offline" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v7"/><path d="M12 17h.01"/></svg>
|
|
176
177
|
</span>
|
|
177
178
|
<button class="icon-btn w-7 h-7 flex items-center justify-center rounded-md border border-slate-600 text-slate-400 hover:bg-slate-700 hover:text-slate-200 transition-colors" id="btn-new-project" title="New project">
|
|
178
179
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><path d="M12 11v6M9 14h6"/></svg>
|
package/public/js/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { state, send } from './state.js';
|
|
2
|
-
import { esc, binName, resolveIconPath } from './utils.js';
|
|
1
|
+
import { state, send, flushQueuedSends } from './state.js';
|
|
2
|
+
import { esc, binName, resolveIconPath, randomUUID } from './utils.js';
|
|
3
3
|
import { addTerminal, removeTerminal, select, startRename, startProjectRename, setSessionTheme, openMenu, closeMenu, setStatus, updateMuteIndicator, updatePreview, markUnread, applyFilter, setTab, renderResumable, regroupSessions, toggleProjectCollapse, setSessionProject, estimateSize, restartComplete, positionMenu, addPill, updatePill, removePill, appendPillLog, setPillLogs, closePillLog } from './terminals.js';
|
|
4
4
|
import { renderSettings, updateVersionFooter } from './settings.js';
|
|
5
5
|
import { openCreator, closeCreator, refreshCreator } from './creator.js';
|
|
@@ -43,6 +43,8 @@ function connect() {
|
|
|
43
43
|
|
|
44
44
|
state.ws.onopen = () => {
|
|
45
45
|
reconnectReplaySkip = new Set(state.terms.keys());
|
|
46
|
+
setServerConnectionState(true);
|
|
47
|
+
flushQueuedSends();
|
|
46
48
|
send({ type: 'remote.status' });
|
|
47
49
|
};
|
|
48
50
|
|
|
@@ -273,7 +275,29 @@ function connect() {
|
|
|
273
275
|
break;
|
|
274
276
|
}
|
|
275
277
|
case 'project.openPath.result':
|
|
276
|
-
if (!msg.success)
|
|
278
|
+
if (!msg.success) {
|
|
279
|
+
if (msg.headless && msg.path) {
|
|
280
|
+
const copied = (() => {
|
|
281
|
+
if (navigator.clipboard) {
|
|
282
|
+
navigator.clipboard.writeText(msg.path).catch(() => {});
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const ta = document.createElement('textarea');
|
|
287
|
+
ta.value = msg.path;
|
|
288
|
+
ta.style.cssText = 'position:fixed;opacity:0';
|
|
289
|
+
document.body.appendChild(ta);
|
|
290
|
+
ta.select();
|
|
291
|
+
const ok = document.execCommand('copy');
|
|
292
|
+
ta.remove();
|
|
293
|
+
return ok;
|
|
294
|
+
} catch { return false; }
|
|
295
|
+
})();
|
|
296
|
+
showToast(msg.path, { title: copied ? 'Path copied to clipboard' : 'No file manager — project path', duration: copied ? 4000 : 8000 });
|
|
297
|
+
} else {
|
|
298
|
+
showToast(msg.error || 'Failed to open project folder', { type: 'error' });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
277
301
|
break;
|
|
278
302
|
case 'sessions.saved':
|
|
279
303
|
flashSaveIndicator();
|
|
@@ -355,7 +379,10 @@ function connect() {
|
|
|
355
379
|
}
|
|
356
380
|
};
|
|
357
381
|
|
|
358
|
-
state.ws.onclose = () =>
|
|
382
|
+
state.ws.onclose = () => {
|
|
383
|
+
setServerConnectionState(false);
|
|
384
|
+
setTimeout(connect, 1000);
|
|
385
|
+
};
|
|
359
386
|
}
|
|
360
387
|
|
|
361
388
|
// Mobile sidebar
|
|
@@ -765,7 +792,7 @@ function openProjectCreator() {
|
|
|
765
792
|
if (!name) { nameInput.focus(); return; }
|
|
766
793
|
const projects = state.cfg.projects || [];
|
|
767
794
|
projects.push({
|
|
768
|
-
id:
|
|
795
|
+
id: randomUUID(),
|
|
769
796
|
name,
|
|
770
797
|
path: path || undefined,
|
|
771
798
|
color: PROJECT_COLORS[projects.length % PROJECT_COLORS.length],
|
|
@@ -1075,7 +1102,7 @@ function renderProjectActions() {
|
|
|
1075
1102
|
let saveTimer = null;
|
|
1076
1103
|
function flashSaveIndicator() {
|
|
1077
1104
|
const el = document.getElementById('save-indicator');
|
|
1078
|
-
if (!el) return;
|
|
1105
|
+
if (!el || el.classList.contains('offline')) return;
|
|
1079
1106
|
clearTimeout(saveTimer);
|
|
1080
1107
|
el.classList.add('saving');
|
|
1081
1108
|
el.classList.remove('saved');
|
|
@@ -1086,6 +1113,21 @@ function flashSaveIndicator() {
|
|
|
1086
1113
|
}, 1500);
|
|
1087
1114
|
}
|
|
1088
1115
|
|
|
1116
|
+
function setServerConnectionState(online) {
|
|
1117
|
+
const el = document.getElementById('save-indicator');
|
|
1118
|
+
if (!el) return;
|
|
1119
|
+
el.classList.toggle('offline', !online);
|
|
1120
|
+
if (!online) {
|
|
1121
|
+
clearTimeout(saveTimer);
|
|
1122
|
+
el.classList.remove('saving', 'saved');
|
|
1123
|
+
}
|
|
1124
|
+
el.title = online
|
|
1125
|
+
? 'Sessions saved'
|
|
1126
|
+
: '';
|
|
1127
|
+
if (online) el.removeAttribute('data-tooltip');
|
|
1128
|
+
else el.dataset.tooltip = 'CliDeck server offline. Changes you make here will run when the server reconnects.';
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1089
1131
|
function initSessionScrollbarVisibility() {
|
|
1090
1132
|
const el = document.getElementById('session-list');
|
|
1091
1133
|
if (!el) return;
|
package/public/js/creator.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { state, send } from './state.js';
|
|
2
|
-
import { esc, agentIcon, binName } from './utils.js';
|
|
2
|
+
import { esc, agentIcon, binName, randomUUID } from './utils.js';
|
|
3
3
|
import { openFolderPicker } from './folder-picker.js';
|
|
4
4
|
import { estimateSize } from './terminals.js';
|
|
5
5
|
import { showToast } from './toast.js';
|
|
@@ -130,7 +130,7 @@ function ensureCommandForPreset(preset) {
|
|
|
130
130
|
let cmd = findCommandForPreset(preset);
|
|
131
131
|
if (cmd) return cmd;
|
|
132
132
|
cmd = {
|
|
133
|
-
id:
|
|
133
|
+
id: randomUUID(),
|
|
134
134
|
presetId: preset.presetId,
|
|
135
135
|
label: preset.name,
|
|
136
136
|
icon: preset.icon,
|
|
@@ -166,7 +166,7 @@ function ensureShellCommand() {
|
|
|
166
166
|
}
|
|
167
167
|
if (!command) return null;
|
|
168
168
|
cmd = {
|
|
169
|
-
id:
|
|
169
|
+
id: randomUUID(),
|
|
170
170
|
presetId: 'shell',
|
|
171
171
|
label: 'Shell',
|
|
172
172
|
icon: shellPreset?.icon || 'terminal',
|
|
@@ -408,7 +408,7 @@ function showInstallToast(preset) {
|
|
|
408
408
|
showToast('Could not find a shell command to run the installer.', { type: 'error', title: 'Install Failed' });
|
|
409
409
|
return;
|
|
410
410
|
}
|
|
411
|
-
const installId =
|
|
411
|
+
const installId = randomUUID();
|
|
412
412
|
send({ type: 'create', commandId: shellCmd.id, name: `Installing ${preset.name}`, installId, ...estimateSize() });
|
|
413
413
|
const handler = (e) => {
|
|
414
414
|
const msg = JSON.parse(e.data);
|
package/public/js/prompts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Prompt Library — manage saved prompts and // trigger autocomplete
|
|
2
2
|
import { state, send } from './state.js';
|
|
3
|
-
import { esc } from './utils.js';
|
|
3
|
+
import { esc, randomUUID } from './utils.js';
|
|
4
4
|
|
|
5
5
|
// --- Panel rendering ---
|
|
6
6
|
|
|
@@ -160,7 +160,7 @@ function openEditor(idx) {
|
|
|
160
160
|
if (existing) {
|
|
161
161
|
state.cfg.prompts[idx] = { ...existing, name, text };
|
|
162
162
|
} else {
|
|
163
|
-
state.cfg.prompts.push({ id:
|
|
163
|
+
state.cfg.prompts.push({ id: randomUUID(), name, text });
|
|
164
164
|
}
|
|
165
165
|
save();
|
|
166
166
|
closeEditor();
|
package/public/js/settings.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { state, send } from './state.js';
|
|
2
|
-
import { esc, debounce, agentIcon, binName } from './utils.js';
|
|
2
|
+
import { esc, debounce, agentIcon, binName, randomUUID } from './utils.js';
|
|
3
3
|
import { openFolderPicker } from './folder-picker.js';
|
|
4
4
|
|
|
5
5
|
// ── Category navigation ──
|
|
@@ -17,6 +17,8 @@ function switchCategory(catId) {
|
|
|
17
17
|
document.querySelectorAll('.settings-panel').forEach(p => p.classList.add('hidden'));
|
|
18
18
|
const panel = document.getElementById(`settings-${catId}`);
|
|
19
19
|
if (panel) panel.classList.remove('hidden');
|
|
20
|
+
const overlay = document.getElementById('settings-overlay');
|
|
21
|
+
if (overlay) overlay.scrollTop = 0;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
document.getElementById('settings-nav').addEventListener('click', (e) => {
|
|
@@ -321,7 +323,7 @@ function openPresetMenu(anchorEl) {
|
|
|
321
323
|
const presetId = item.dataset.preset;
|
|
322
324
|
if (presetId === 'custom') {
|
|
323
325
|
state.cfg.commands.push({
|
|
324
|
-
id:
|
|
326
|
+
id: randomUUID(), label: '', icon: 'terminal', command: '',
|
|
325
327
|
enabled: true, defaultPath: '', isAgent: false, canResume: false,
|
|
326
328
|
resumeCommand: null, sessionIdPattern: null,
|
|
327
329
|
telemetryEnabled: false, telemetryStatus: null, env: {}, userAdded: true,
|
|
@@ -329,7 +331,7 @@ function openPresetMenu(anchorEl) {
|
|
|
329
331
|
} else {
|
|
330
332
|
const p = presets.find(x => x.presetId === presetId);
|
|
331
333
|
if (p) state.cfg.commands.push({
|
|
332
|
-
id:
|
|
334
|
+
id: randomUUID(), presetId: p.presetId, label: p.name, icon: p.icon, command: p.command,
|
|
333
335
|
enabled: true, defaultPath: '', isAgent: p.isAgent, canResume: p.canResume,
|
|
334
336
|
resumeCommand: p.resumeCommand, sessionIdPattern: p.sessionIdPattern,
|
|
335
337
|
outputMarker: p.outputMarker || null,
|
|
@@ -598,7 +600,7 @@ function saveConfig() {
|
|
|
598
600
|
const presetId = card.querySelector('.agent-preset')?.value || null;
|
|
599
601
|
const preset = presetId ? state.presets.find(p => p.presetId === presetId) : null;
|
|
600
602
|
return {
|
|
601
|
-
id: existing.id ||
|
|
603
|
+
id: existing.id || randomUUID(),
|
|
602
604
|
presetId,
|
|
603
605
|
label: card.querySelector('.agent-name').value.trim() || 'Untitled',
|
|
604
606
|
icon: existing.icon || preset?.icon || 'terminal',
|
package/public/js/state.js
CHANGED
|
@@ -13,6 +13,53 @@ export const state = {
|
|
|
13
13
|
remoteVersion: null,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const queuedMessages = [];
|
|
17
|
+
const QUEUEABLE_TYPES = new Set([
|
|
18
|
+
'checkAvailability',
|
|
19
|
+
'close',
|
|
20
|
+
'config.update',
|
|
21
|
+
'create',
|
|
22
|
+
'plugin.delete',
|
|
23
|
+
'plugin.install',
|
|
24
|
+
'project.delete',
|
|
25
|
+
'project.openPath',
|
|
26
|
+
'remote.install',
|
|
27
|
+
'remote.pair',
|
|
28
|
+
'remote.unpair',
|
|
29
|
+
'session.mute',
|
|
30
|
+
'session.restart',
|
|
31
|
+
'session.resume',
|
|
32
|
+
'session.setProject',
|
|
33
|
+
'session.theme',
|
|
34
|
+
'telemetry.autosetup',
|
|
35
|
+
'telemetry.configure',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function canSendNow() {
|
|
39
|
+
return state.ws && state.ws.readyState === WebSocket.OPEN;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function enqueue(msg) {
|
|
43
|
+
if (!QUEUEABLE_TYPES.has(msg?.type)) return false;
|
|
44
|
+
if (msg.type === 'config.update') {
|
|
45
|
+
const idx = queuedMessages.findIndex(item => item.type === 'config.update');
|
|
46
|
+
if (idx >= 0) queuedMessages[idx] = msg;
|
|
47
|
+
else queuedMessages.push(msg);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
queuedMessages.push(msg);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
16
54
|
export function send(msg) {
|
|
55
|
+
if (!canSendNow()) return enqueue(msg);
|
|
17
56
|
state.ws.send(JSON.stringify(msg));
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function flushQueuedSends() {
|
|
61
|
+
if (!canSendNow()) return;
|
|
62
|
+
while (queuedMessages.length) {
|
|
63
|
+
state.ws.send(JSON.stringify(queuedMessages.shift()));
|
|
64
|
+
}
|
|
18
65
|
}
|
package/public/js/utils.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export function randomUUID() {
|
|
2
|
+
if (typeof crypto?.randomUUID === 'function') return crypto.randomUUID();
|
|
3
|
+
const arr = new Uint8Array(16);
|
|
4
|
+
crypto.getRandomValues(arr);
|
|
5
|
+
arr[6] = (arr[6] & 0x0f) | 0x40;
|
|
6
|
+
arr[8] = (arr[8] & 0x3f) | 0x80;
|
|
7
|
+
const h = [...arr].map(b => b.toString(16).padStart(2, '0'));
|
|
8
|
+
return `${h.slice(0,4).join('')}-${h.slice(4,6).join('')}-${h.slice(6,8).join('')}-${h.slice(8,10).join('')}-${h.slice(10,16).join('')}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
export function binName(command) {
|
|
2
12
|
const m = command.match(/^(['"])(.*?)\1/);
|
|
3
13
|
const exec = m ? m[2] : command;
|
package/public/tailwind.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[34px\]{min-width:34px}.min-w-\[354px\]{min-width:354px}.max-w-2xl{max-width:42rem}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-px>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1px*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-400\/20{border-color:rgba(251,191,36,.2)}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-red-500\/30{border-color:rgba(239,68,68,.3)}.border-rose-400\/30{border-color:rgba(251,113,133,.3)}.border-slate-600{--tw-border-opacity:1;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85/var(--tw-border-opacity,1))}.border-slate-700\/30{border-color:rgba(51,65,85,.3)}.border-slate-700\/40{border-color:rgba(51,65,85,.4)}.border-slate-700\/50{border-color:rgba(51,65,85,.5)}.border-slate-700\/60{border-color:rgba(51,65,85,.6)}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/10{background-color:rgba(59,130,246,.1)}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-600\/20{background-color:rgba(5,150,105,.2)}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-600\/20{background-color:rgba(220,38,38,.2)}.bg-rose-500\/10{background-color:rgba(244,63,94,.1)}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-slate-700\/50{background-color:rgba(51,65,85,.5)}.bg-slate-700\/60{background-color:rgba(51,65,85,.6)}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.bg-slate-800\/30{background-color:rgba(30,41,59,.3)}.bg-slate-800\/40{background-color:rgba(30,41,59,.4)}.bg-slate-800\/50{background-color:rgba(30,41,59,.5)}.bg-slate-800\/60{background-color:rgba(30,41,59,.6)}.bg-slate-800\/80{background-color:rgba(30,41,59,.8)}.bg-slate-800\/95{background-color:rgba(30,41,59,.95)}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-slate-900\/50{background-color:rgba(15,23,42,.5)}.bg-slate-900\/70{background-color:rgba(15,23,42,.7)}.bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.bg-slate-950\/50{background-color:rgba(2,6,23,.5)}.bg-transparent{background-color:transparent}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[3px\]{padding:3px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-3\.5{padding-bottom:.875rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-3\.5{padding-top:.875rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.45\]{line-height:1.45}.leading-\[1\.4\]{line-height:1.4}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-200\/90{color:hsla(48,97%,77%,.9)}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-400\/80{color:rgba(251,191,36,.8)}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-emerald-400\/80{color:rgba(52,211,153,.8)}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-500\/70{color:rgba(16,185,129,.7)}.text-indigo-400{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-rose-100{--tw-text-opacity:1;color:rgb(255 228 230/var(--tw-text-opacity,1))}.text-rose-200\/90{color:rgba(254,205,211,.9)}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-400\/80{color:rgba(251,113,133,.8)}.text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.accent-blue-500{accent-color:#3b82f6}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-black\/40{--tw-shadow-color:rgba(0,0,0,.4);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color:rgba(0,0,0,.5);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color:rgba(0,0,0,.6);--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-white\/40{--tw-ring-color:hsla(0,0%,100%,.4)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.bg-slate-950{background-color:var(--color-base)!important}.bg-slate-900{background-color:var(--color-surface)!important}.bg-slate-800{background-color:var(--color-raised)!important}.bg-slate-700{background-color:var(--color-muted)!important}.bg-slate-800\/50{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.bg-slate-800\/30{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.bg-slate-800\/60{background-color:color-mix(in srgb,var(--color-raised) 60%,transparent)!important}.bg-slate-800\/80{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.bg-slate-900\/70{background-color:color-mix(in srgb,var(--color-surface) 70%,transparent)!important}.bg-slate-800\/95{background-color:color-mix(in srgb,var(--color-raised) 95%,transparent)!important}.bg-slate-700\/50{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.bg-slate-700\/60{background-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.hover\:bg-slate-700:hover{background-color:var(--color-muted)!important}.hover\:bg-slate-800:hover{background-color:var(--color-raised)!important}.hover\:bg-slate-800\/50:hover{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.hover\:bg-slate-800\/30:hover{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.hover\:bg-slate-700\/50:hover{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.hover\:bg-slate-700\/70:hover{background-color:color-mix(in srgb,var(--color-muted) 70%,transparent)!important}.text-slate-200{color:var(--color-text)!important}.text-slate-300{color:var(--color-soft)!important}.text-slate-400{color:var(--color-dim)!important}.text-slate-500{color:var(--color-subtle)!important}.text-slate-600{color:var(--color-border)!important}.hover\:text-slate-200:hover{color:var(--color-text)!important}.hover\:text-slate-300:hover{color:var(--color-soft)!important}.hover\:text-slate-400:hover{color:var(--color-dim)!important}.border-slate-600{border-color:var(--color-border)!important}.border-slate-700{border-color:var(--color-muted)!important}.border-slate-700\/50{border-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.border-slate-700\/40{border-color:color-mix(in srgb,var(--color-muted) 40%,transparent)!important}.border-slate-700\/60{border-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.border-slate-600\/60{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.hover\:border-slate-500:hover{border-color:var(--color-subtle)!important}.focus\:border-slate-600\/60:focus{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.focus\:bg-slate-800\/80:focus{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.placeholder-slate-500::-moz-placeholder{color:var(--color-subtle)!important}.placeholder-slate-500::placeholder{color:var(--color-subtle)!important}.placeholder-slate-600::-moz-placeholder{color:var(--color-border)!important}.placeholder-slate-600::placeholder{color:var(--color-border)!important}.ring-slate-500{--tw-ring-color:var(--color-subtle)!important}.bg-black\/60{background-color:var(--color-overlay)!important}.shadow-black\/40,.shadow-black\/50,.shadow-black\/60{--tw-shadow-color:var(--color-shadow)!important}.disabled\:bg-slate-600:disabled{background-color:var(--color-border)!important}.disabled\:text-slate-400:disabled{color:var(--color-dim)!important}:root{--color-preview:#a2a2a2;--color-time:#7c7d7d;--color-time-recent:#5cbd6d;--color-dormant:#555;--color-proj-meta:#7c7d7d;--color-search-bg:#2e2f2f;--color-search-text:#acacac;--color-session-hover:hsla(0,0%,100%,.04);--color-header-icon:#cbd5e1;--color-rail-icon:#a5a6a6}.light{--color-preview:#626262;--color-time:#666;--color-time-recent:#51a868;--color-dormant:#aaa;--color-proj-meta:#666;--color-search-bg:#f6f5f5;--color-search-text:#626262;--color-session-hover:rgba(0,0,0,.04);--color-header-icon:#3d4450;--color-rail-icon:#636261}.session-preview{color:var(--color-preview)!important}.session-time{color:var(--color-time)!important}.session-time.recent{color:var(--color-time-recent)!important}.session-status.dormant{color:var(--color-dormant)!important}.group[data-id]:hover,.pill-row:hover,.resumable-row:hover{background-color:var(--color-session-hover)!important}.project-count,.project-menu-btn{color:var(--color-proj-meta)!important}#search-input{background-color:var(--color-search-bg)!important;color:var(--color-search-text)!important}#search-input::-moz-placeholder{color:var(--color-search-text)!important;opacity:.7}#search-input::placeholder{color:var(--color-search-text)!important;opacity:.7}.relative:has(#search-input)>svg{color:var(--color-search-text)!important}.rail-btn:not(.text-slate-200){color:var(--color-rail-icon)!important}.rail-btn.bg-slate-800{background-color:var(--color-rail-active)!important}.icon-btn{color:var(--color-header-icon)!important;border-color:var(--color-border)!important;font-weight:700}.icon-btn svg{stroke-width:2}:root{--color-separator:#2e2f2f}.light{--color-separator:#d8d3cd}#nav-rail{background-color:var(--color-rail)!important}#sidebar{background-color:var(--color-sidebar)!important;border-right-color:var(--color-separator)!important}#sidebar input[type=text],#sidebar select{background-color:var(--color-sidebar-input)!important;border-color:var(--color-sidebar-border)!important}.group:hover{background-color:var(--color-chat-hover)!important}.group.active-session{background-color:var(--color-chat-active)!important}.theme-toggle{position:relative;width:36px;height:36px}.theme-toggle svg{position:absolute;inset:0;margin:auto;transition:opacity .3s ease,transform .3s ease}.theme-toggle .icon-sun{opacity:0;transform:rotate(-90deg) scale(.5)}.light .theme-toggle .icon-sun,.theme-toggle .icon-moon{opacity:1;transform:rotate(0) scale(1)}.light .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}html{transition:color .3s ease,background-color .3s ease}.term-wrap{visibility:hidden;position:absolute;overflow:hidden;top:4px;left:4px;right:4px;bottom:0}.term-wrap.active{visibility:visible}.term-wrap .xterm{height:100%}.tmx-jump-latest{position:absolute;right:18px;bottom:18px;z-index:20;width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;border:1px solid color-mix(in srgb,var(--color-border) 58%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--color-raised) 92%,transparent),color-mix(in srgb,var(--color-surface) 90%,transparent));color:var(--color-soft);box-shadow:0 18px 38px -18px var(--color-shadow),0 0 0 1px color-mix(in srgb,var(--color-bright) 5%,transparent) inset;opacity:0;pointer-events:none;transform:translateY(12px) scale(.92);transition:opacity .18s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1),color .16s ease,border-color .16s ease,background-color .16s ease}.tmx-jump-latest.is-visible{opacity:1;pointer-events:auto;transform:translateY(0) scale(1)}.tmx-jump-latest:hover{color:var(--color-bright);border-color:color-mix(in srgb,var(--color-dim) 82%,transparent);transform:translateY(-2px) scale(1.04)}.tmx-jump-latest.settling{opacity:0;pointer-events:none;transform:translateY(16px) scale(.94)}.tmx-jump-latest-glow{position:absolute;inset:-8px;border-radius:inherit;background:radial-gradient(circle at 50% 35%,color-mix(in srgb,var(--color-accent-subtle) 78%,transparent),transparent 62%);opacity:0;transform:scale(.82);transition:opacity .22s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1)}.tmx-jump-latest:hover .tmx-jump-latest-glow{opacity:1;transform:scale(1)}.tmx-jump-latest-icon{position:relative}.save-indicator,.tmx-jump-latest-icon,.tmx-jump-latest-icon svg{width:18px;height:18px}.save-indicator{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-rose-400\/40:hover{border-color:rgba(251,113,133,.4)}.hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity,1))}.hover\:bg-amber-500\/20:hover{background-color:rgba(245,158,11,.2)}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-blue-500\/20:hover{background-color:rgba(59,130,246,.2)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:bg-rose-400\/20:hover{background-color:rgba(251,113,133,.2)}.hover\:bg-rose-500\/10:hover{background-color:rgba(244,63,94,.1)}.hover\:bg-rose-500\/20:hover{background-color:rgba(244,63,94,.2)}.hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.hover\:bg-slate-700\/50:hover{background-color:rgba(51,65,85,.5)}.hover\:bg-slate-700\/60:hover{background-color:rgba(51,65,85,.6)}.hover\:bg-slate-700\/70:hover{background-color:rgba(51,65,85,.7)}.hover\:bg-slate-800\/30:hover{background-color:rgba(30,41,59,.3)}.hover\:bg-slate-800\/40:hover{background-color:rgba(30,41,59,.4)}.hover\:bg-slate-800\/50:hover{background-color:rgba(30,41,59,.5)}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-indigo-400:hover{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-300:hover{--tw-text-opacity:1;color:rgb(253 164 175/var(--tw-text-opacity,1))}.hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-slate-500:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(100 116 139/var(--tw-ring-opacity,1))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-slate-600\/60:focus{border-color:rgba(71,85,105,.6)}.focus\:bg-slate-800\/80:focus{background-color:rgba(30,41,59,.8)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:rgba(59,130,246,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-600:disabled{--tw-bg-opacity:1;background-color:rgb(71 85 105/var(--tw-bg-opacity,1))}.disabled\:text-slate-400:disabled{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-warning:#ef4444;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-warning:#dc2626;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[34px\]{min-width:34px}.min-w-\[354px\]{min-width:354px}.max-w-2xl{max-width:42rem}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-px>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1px*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-400\/20{border-color:rgba(251,191,36,.2)}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-red-500\/30{border-color:rgba(239,68,68,.3)}.border-rose-400\/30{border-color:rgba(251,113,133,.3)}.border-slate-600{--tw-border-opacity:1;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85/var(--tw-border-opacity,1))}.border-slate-700\/30{border-color:rgba(51,65,85,.3)}.border-slate-700\/40{border-color:rgba(51,65,85,.4)}.border-slate-700\/50{border-color:rgba(51,65,85,.5)}.border-slate-700\/60{border-color:rgba(51,65,85,.6)}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/10{background-color:rgba(59,130,246,.1)}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-600\/20{background-color:rgba(5,150,105,.2)}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-600\/20{background-color:rgba(220,38,38,.2)}.bg-rose-500\/10{background-color:rgba(244,63,94,.1)}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-slate-700\/50{background-color:rgba(51,65,85,.5)}.bg-slate-700\/60{background-color:rgba(51,65,85,.6)}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.bg-slate-800\/30{background-color:rgba(30,41,59,.3)}.bg-slate-800\/40{background-color:rgba(30,41,59,.4)}.bg-slate-800\/50{background-color:rgba(30,41,59,.5)}.bg-slate-800\/60{background-color:rgba(30,41,59,.6)}.bg-slate-800\/80{background-color:rgba(30,41,59,.8)}.bg-slate-800\/95{background-color:rgba(30,41,59,.95)}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-slate-900\/50{background-color:rgba(15,23,42,.5)}.bg-slate-900\/70{background-color:rgba(15,23,42,.7)}.bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.bg-slate-950\/50{background-color:rgba(2,6,23,.5)}.bg-transparent{background-color:transparent}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[3px\]{padding:3px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-3\.5{padding-bottom:.875rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-3\.5{padding-top:.875rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.45\]{line-height:1.45}.leading-\[1\.4\]{line-height:1.4}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-200\/90{color:hsla(48,97%,77%,.9)}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-400\/80{color:rgba(251,191,36,.8)}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-emerald-400\/80{color:rgba(52,211,153,.8)}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-500\/70{color:rgba(16,185,129,.7)}.text-indigo-400{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-rose-100{--tw-text-opacity:1;color:rgb(255 228 230/var(--tw-text-opacity,1))}.text-rose-200\/90{color:rgba(254,205,211,.9)}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-400\/80{color:rgba(251,113,133,.8)}.text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.accent-blue-500{accent-color:#3b82f6}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-black\/40{--tw-shadow-color:rgba(0,0,0,.4);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color:rgba(0,0,0,.5);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color:rgba(0,0,0,.6);--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-white\/40{--tw-ring-color:hsla(0,0%,100%,.4)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.bg-slate-950{background-color:var(--color-base)!important}.bg-slate-900{background-color:var(--color-surface)!important}.bg-slate-800{background-color:var(--color-raised)!important}.bg-slate-700{background-color:var(--color-muted)!important}.bg-slate-800\/50{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.bg-slate-800\/30{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.bg-slate-800\/60{background-color:color-mix(in srgb,var(--color-raised) 60%,transparent)!important}.bg-slate-800\/80{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.bg-slate-900\/70{background-color:color-mix(in srgb,var(--color-surface) 70%,transparent)!important}.bg-slate-800\/95{background-color:color-mix(in srgb,var(--color-raised) 95%,transparent)!important}.bg-slate-700\/50{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.bg-slate-700\/60{background-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.hover\:bg-slate-700:hover{background-color:var(--color-muted)!important}.hover\:bg-slate-800:hover{background-color:var(--color-raised)!important}.hover\:bg-slate-800\/50:hover{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.hover\:bg-slate-800\/30:hover{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.hover\:bg-slate-700\/50:hover{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.hover\:bg-slate-700\/70:hover{background-color:color-mix(in srgb,var(--color-muted) 70%,transparent)!important}.text-slate-200{color:var(--color-text)!important}.text-slate-300{color:var(--color-soft)!important}.text-slate-400{color:var(--color-dim)!important}.text-slate-500{color:var(--color-subtle)!important}.text-slate-600{color:var(--color-border)!important}.hover\:text-slate-200:hover{color:var(--color-text)!important}.hover\:text-slate-300:hover{color:var(--color-soft)!important}.hover\:text-slate-400:hover{color:var(--color-dim)!important}.border-slate-600{border-color:var(--color-border)!important}.border-slate-700{border-color:var(--color-muted)!important}.border-slate-700\/50{border-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.border-slate-700\/40{border-color:color-mix(in srgb,var(--color-muted) 40%,transparent)!important}.border-slate-700\/60{border-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.border-slate-600\/60{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.hover\:border-slate-500:hover{border-color:var(--color-subtle)!important}.focus\:border-slate-600\/60:focus{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.focus\:bg-slate-800\/80:focus{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.placeholder-slate-500::-moz-placeholder{color:var(--color-subtle)!important}.placeholder-slate-500::placeholder{color:var(--color-subtle)!important}.placeholder-slate-600::-moz-placeholder{color:var(--color-border)!important}.placeholder-slate-600::placeholder{color:var(--color-border)!important}.ring-slate-500{--tw-ring-color:var(--color-subtle)!important}.bg-black\/60{background-color:var(--color-overlay)!important}.shadow-black\/40,.shadow-black\/50,.shadow-black\/60{--tw-shadow-color:var(--color-shadow)!important}.disabled\:bg-slate-600:disabled{background-color:var(--color-border)!important}.disabled\:text-slate-400:disabled{color:var(--color-dim)!important}:root{--color-preview:#a2a2a2;--color-time:#7c7d7d;--color-time-recent:#5cbd6d;--color-dormant:#555;--color-proj-meta:#7c7d7d;--color-search-bg:#2e2f2f;--color-search-text:#acacac;--color-session-hover:hsla(0,0%,100%,.04);--color-header-icon:#cbd5e1;--color-rail-icon:#a5a6a6}.light{--color-preview:#626262;--color-time:#666;--color-time-recent:#51a868;--color-dormant:#aaa;--color-proj-meta:#666;--color-search-bg:#f6f5f5;--color-search-text:#626262;--color-session-hover:rgba(0,0,0,.04);--color-header-icon:#3d4450;--color-rail-icon:#636261}.session-preview{color:var(--color-preview)!important}.session-time{color:var(--color-time)!important}.session-time.recent{color:var(--color-time-recent)!important}.session-status.dormant{color:var(--color-dormant)!important}.group[data-id]:hover,.pill-row:hover,.resumable-row:hover{background-color:var(--color-session-hover)!important}.project-count,.project-menu-btn{color:var(--color-proj-meta)!important}#search-input{background-color:var(--color-search-bg)!important;color:var(--color-search-text)!important}#search-input::-moz-placeholder{color:var(--color-search-text)!important;opacity:.7}#search-input::placeholder{color:var(--color-search-text)!important;opacity:.7}.relative:has(#search-input)>svg{color:var(--color-search-text)!important}.rail-btn:not(.text-slate-200){color:var(--color-rail-icon)!important}.rail-btn.bg-slate-800{background-color:var(--color-rail-active)!important}.icon-btn{color:var(--color-header-icon)!important;border-color:var(--color-border)!important;font-weight:700}.icon-btn svg{stroke-width:2}:root{--color-separator:#2e2f2f}.light{--color-separator:#d8d3cd}#nav-rail{background-color:var(--color-rail)!important}#sidebar{background-color:var(--color-sidebar)!important;border-right-color:var(--color-separator)!important}#sidebar input[type=text],#sidebar select{background-color:var(--color-sidebar-input)!important;border-color:var(--color-sidebar-border)!important}.group:hover{background-color:var(--color-chat-hover)!important}.group.active-session{background-color:var(--color-chat-active)!important}.theme-toggle{position:relative;width:36px;height:36px}.theme-toggle svg{position:absolute;inset:0;margin:auto;transition:opacity .3s ease,transform .3s ease}.theme-toggle .icon-sun{opacity:0;transform:rotate(-90deg) scale(.5)}.light .theme-toggle .icon-sun,.theme-toggle .icon-moon{opacity:1;transform:rotate(0) scale(1)}.light .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}html{transition:color .3s ease,background-color .3s ease}.term-wrap{visibility:hidden;position:absolute;overflow:hidden;top:4px;left:4px;right:4px;bottom:0}.term-wrap.active{visibility:visible}.term-wrap .xterm{height:100%}.tmx-jump-latest{position:absolute;right:18px;bottom:18px;z-index:20;width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;border:1px solid color-mix(in srgb,var(--color-border) 58%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--color-raised) 92%,transparent),color-mix(in srgb,var(--color-surface) 90%,transparent));color:var(--color-soft);box-shadow:0 18px 38px -18px var(--color-shadow),0 0 0 1px color-mix(in srgb,var(--color-bright) 5%,transparent) inset;opacity:0;pointer-events:none;transform:translateY(12px) scale(.92);transition:opacity .18s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1),color .16s ease,border-color .16s ease,background-color .16s ease}.tmx-jump-latest.is-visible{opacity:1;pointer-events:auto;transform:translateY(0) scale(1)}.tmx-jump-latest:hover{color:var(--color-bright);border-color:color-mix(in srgb,var(--color-dim) 82%,transparent);transform:translateY(-2px) scale(1.04)}.tmx-jump-latest.settling{opacity:0;pointer-events:none;transform:translateY(16px) scale(.94)}.tmx-jump-latest-glow{position:absolute;inset:-8px;border-radius:inherit;background:radial-gradient(circle at 50% 35%,color-mix(in srgb,var(--color-accent-subtle) 78%,transparent),transparent 62%);opacity:0;transform:scale(.82);transition:opacity .22s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1)}.tmx-jump-latest:hover .tmx-jump-latest-glow{opacity:1;transform:scale(1)}.tmx-jump-latest-icon{position:relative}.save-indicator,.tmx-jump-latest-icon,.tmx-jump-latest-icon svg{width:18px;height:18px}.save-indicator{position:relative;z-index:20;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator .save-offline{display:none;color:var(--color-warning)}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}.save-indicator.offline{color:var(--color-warning);border-color:color-mix(in srgb,var(--color-warning) 76%,var(--color-search-text));background:color-mix(in srgb,var(--color-warning) 10%,transparent);opacity:.9}.save-indicator.offline .save-spin,.save-indicator.offline .save-tick{display:none}.save-indicator.offline .save-offline{display:block}.save-indicator.offline:hover:after{content:attr(data-tooltip);position:absolute;right:-2px;top:calc(100% + 8px);z-index:500;width:260px;padding:8px 10px;border-radius:10px;border:1px solid color-mix(in srgb,var(--color-warning) 32%,var(--color-muted));background:color-mix(in srgb,var(--color-raised) 96%,transparent);color:var(--color-text);box-shadow:0 18px 40px -18px var(--color-shadow);font-size:11px;font-weight:500;line-height:1.35;text-align:left;pointer-events:none;animation:status-tip-in .08s ease-out}@keyframes status-tip-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:translateY(0)}}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-rose-400\/40:hover{border-color:rgba(251,113,133,.4)}.hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity,1))}.hover\:bg-amber-500\/20:hover{background-color:rgba(245,158,11,.2)}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-blue-500\/20:hover{background-color:rgba(59,130,246,.2)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:bg-rose-400\/20:hover{background-color:rgba(251,113,133,.2)}.hover\:bg-rose-500\/10:hover{background-color:rgba(244,63,94,.1)}.hover\:bg-rose-500\/20:hover{background-color:rgba(244,63,94,.2)}.hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.hover\:bg-slate-700\/50:hover{background-color:rgba(51,65,85,.5)}.hover\:bg-slate-700\/60:hover{background-color:rgba(51,65,85,.6)}.hover\:bg-slate-700\/70:hover{background-color:rgba(51,65,85,.7)}.hover\:bg-slate-800\/30:hover{background-color:rgba(30,41,59,.3)}.hover\:bg-slate-800\/40:hover{background-color:rgba(30,41,59,.4)}.hover\:bg-slate-800\/50:hover{background-color:rgba(30,41,59,.5)}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-indigo-400:hover{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-300:hover{--tw-text-opacity:1;color:rgb(253 164 175/var(--tw-text-opacity,1))}.hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-slate-500:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(100 116 139/var(--tw-ring-opacity,1))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-slate-600\/60:focus{border-color:rgba(71,85,105,.6)}.focus\:bg-slate-800\/80:focus{background-color:rgba(30,41,59,.8)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:rgba(59,130,246,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-600:disabled{--tw-bg-opacity:1;background-color:rgb(71 85 105/var(--tw-bg-opacity,1))}.disabled\:text-slate-400:disabled{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
package/server.js
CHANGED
|
@@ -128,7 +128,6 @@ const server = http.createServer((req, res) => {
|
|
|
128
128
|
const route = req.url.slice('/hook/codex/'.length);
|
|
129
129
|
const clideckId = payload.clideck_id;
|
|
130
130
|
const threadId = payload['thread-id'] || payload.session_id;
|
|
131
|
-
// console.log(`[codex] notify clideck=${clideckId ? clideckId.slice(0,8) : 'none'} thread=${threadId ? threadId.slice(0,8) : 'none'}`);
|
|
132
131
|
const allSessions = sessions.getSessions();
|
|
133
132
|
let matchedId = null;
|
|
134
133
|
if (clideckId && allSessions.has(clideckId)) {
|
|
@@ -148,7 +147,6 @@ const server = http.createServer((req, res) => {
|
|
|
148
147
|
if (route === 'start') telemetry.markCodexStart(matchedId, 'hook');
|
|
149
148
|
else if (route === 'stop') telemetry.armCodexStop(matchedId);
|
|
150
149
|
}
|
|
151
|
-
// if (!matchedId) console.log(`[codex] hook ${route} no match clideck=${clideckId ? clideckId.slice(0,8) : 'none'} thread=${threadId ? threadId.slice(0,8) : 'none'}`);
|
|
152
150
|
} catch {}
|
|
153
151
|
res.writeHead(200).end('{}');
|
|
154
152
|
});
|
|
@@ -170,31 +168,23 @@ const server = http.createServer((req, res) => {
|
|
|
170
168
|
: sessionId
|
|
171
169
|
? [...allSessions].find(([, s]) => s.sessionToken === sessionId)?.[0]
|
|
172
170
|
: null;
|
|
173
|
-
// console.log(`[claude] hook ${route} clideck=${payload.clideck_id?.slice(0,8) || 'none'} session=${sessionId?.slice(0,8) || 'none'} match=${clideckId?.slice(0,8) || 'none'}`);
|
|
174
171
|
if (clideckId) {
|
|
175
172
|
const sess = allSessions.get(clideckId);
|
|
176
173
|
if (route === 'start') {
|
|
177
|
-
// console.log(`[claude] status working=true source=hook session=${clideckId.slice(0,8)}`);
|
|
178
174
|
sessions.broadcast({ type: 'session.status', id: clideckId, working: true, source: 'hook' });
|
|
179
175
|
} else if (route === 'stop' || route === 'idle') {
|
|
180
|
-
// console.log(`[claude] status working=false source=hook session=${clideckId.slice(0,8)}`);
|
|
181
176
|
sessions.broadcast({ type: 'session.status', id: clideckId, working: false, source: 'hook' });
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
if (route === 'stop' && sess && !sess.working) {
|
|
186
|
-
// console.log(`[claude] stop capture session=${clideckId.slice(0,8)} source=claude-stop`);
|
|
177
|
+
// Stop and idle both mean Claude is settled enough to snapshot the
|
|
178
|
+
// visible transcript. Some Claude flows emit only the idle signal.
|
|
179
|
+
if ((route === 'stop' || route === 'idle') && sess && !sess.working) {
|
|
187
180
|
setTimeout(() => sessions.broadcast({ type: 'terminal.capture', id: clideckId }), 500);
|
|
188
181
|
}
|
|
189
182
|
} else if (route === 'menu') {
|
|
190
183
|
// PreToolUse: trigger terminal capture — detectMenu will set idle if a choice menu is visible
|
|
191
184
|
const menuVersion = sess ? ((sess._menuVersion || 0) + 1) : 1;
|
|
192
185
|
if (sess) sess._menuVersion = menuVersion;
|
|
193
|
-
// console.log(`[claude] menu capture session=${clideckId.slice(0,8)} source=claude-menu version=${menuVersion}`);
|
|
194
186
|
setTimeout(() => sessions.broadcast({ type: 'terminal.capture', id: clideckId, menuVersion }), 500);
|
|
195
187
|
}
|
|
196
|
-
} else {
|
|
197
|
-
// console.log(`[claude] hook ${route} no-match`);
|
|
198
188
|
}
|
|
199
189
|
} catch {}
|
|
200
190
|
res.writeHead(200).end('{}');
|
|
@@ -242,13 +232,13 @@ const server = http.createServer((req, res) => {
|
|
|
242
232
|
|
|
243
233
|
// Session-to-session ask bridge used by the `clideck ask` CLI command.
|
|
244
234
|
if (req.method === 'POST' && req.url === '/api/session/ask') {
|
|
245
|
-
require('./session-ask').handleHttp(req, res, sessions);
|
|
235
|
+
require('./session-ask').handleHttp(req, res, sessions, () => config.load());
|
|
246
236
|
return;
|
|
247
237
|
}
|
|
248
238
|
|
|
249
239
|
// Agent discovery bridge used by the `clideck agents` CLI command.
|
|
250
240
|
if (req.method === 'GET' && req.url.startsWith('/api/session/agents')) {
|
|
251
|
-
require('./session-agents').handleHttp(req, res, sessions);
|
|
241
|
+
require('./session-agents').handleHttp(req, res, sessions, () => config.load());
|
|
252
242
|
return;
|
|
253
243
|
}
|
|
254
244
|
|
package/session-agents.js
CHANGED
|
@@ -12,7 +12,28 @@ function sameProject(a, b) {
|
|
|
12
12
|
return (a.projectId || null) === (b.projectId || null);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function
|
|
15
|
+
function projectName(projects, projectId) {
|
|
16
|
+
if (!projectId) return 'No project';
|
|
17
|
+
return projects.find(p => p.id === projectId)?.name || projectId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function agentRow(id, s, callerId, projects) {
|
|
21
|
+
const project = projectName(projects, s.projectId);
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
name: s.name || id.slice(0, 8),
|
|
25
|
+
preset: s.presetId || 'shell',
|
|
26
|
+
projectId: s.projectId || null,
|
|
27
|
+
project,
|
|
28
|
+
address: s.projectId ? `@${project}/${s.name || id.slice(0, 8)}` : s.name || id.slice(0, 8),
|
|
29
|
+
working: !!s.working,
|
|
30
|
+
lastPreview: s.lastPreview || '',
|
|
31
|
+
lastActivityAt: s.lastActivityAt || null,
|
|
32
|
+
caller: id === callerId,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function listProjectAgents(callerSessionId, sessionsApi, cfg = {}, all = false) {
|
|
16
37
|
const sessions = sessionsApi.getSessions();
|
|
17
38
|
const callerId = String(callerSessionId || '').trim();
|
|
18
39
|
const caller = sessions.get(callerId);
|
|
@@ -22,20 +43,13 @@ function listProjectAgents(callerSessionId, sessionsApi) {
|
|
|
22
43
|
throw err;
|
|
23
44
|
}
|
|
24
45
|
|
|
46
|
+
const projects = Array.isArray(cfg.projects) ? cfg.projects : [];
|
|
25
47
|
return [...sessions]
|
|
26
|
-
.filter(([, s]) => sameProject(caller, s))
|
|
27
|
-
.map(([id, s]) => (
|
|
28
|
-
id,
|
|
29
|
-
name: s.name || id.slice(0, 8),
|
|
30
|
-
preset: s.presetId || 'shell',
|
|
31
|
-
working: !!s.working,
|
|
32
|
-
lastPreview: s.lastPreview || '',
|
|
33
|
-
lastActivityAt: s.lastActivityAt || null,
|
|
34
|
-
caller: id === callerId,
|
|
35
|
-
}));
|
|
48
|
+
.filter(([, s]) => all || sameProject(caller, s))
|
|
49
|
+
.map(([id, s]) => agentRow(id, s, callerId, projects));
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
async function handleHttp(req, res, sessionsApi) {
|
|
52
|
+
async function handleHttp(req, res, sessionsApi, getConfig = () => ({})) {
|
|
39
53
|
try {
|
|
40
54
|
if (!isLoopback(req)) {
|
|
41
55
|
const err = new Error('CliDeck agents only accepts local requests');
|
|
@@ -43,7 +57,8 @@ async function handleHttp(req, res, sessionsApi) {
|
|
|
43
57
|
throw err;
|
|
44
58
|
}
|
|
45
59
|
const url = new URL(req.url, 'http://127.0.0.1');
|
|
46
|
-
const
|
|
60
|
+
const all = url.searchParams.get('all') === '1' || url.searchParams.get('all') === 'true';
|
|
61
|
+
const agents = listProjectAgents(url.searchParams.get('callerSessionId'), sessionsApi, getConfig() || {}, all);
|
|
47
62
|
sendJson(res, 200, { agents });
|
|
48
63
|
} catch (e) {
|
|
49
64
|
sendJson(res, e.status || 500, { error: e.message || 'CliDeck agents failed' });
|
package/session-ask.js
CHANGED
|
@@ -50,9 +50,62 @@ function sameProject(a, b) {
|
|
|
50
50
|
return (a.projectId || null) === (b.projectId || null);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function projectName(projects, projectId) {
|
|
54
|
+
if (!projectId) return 'No project';
|
|
55
|
+
return projects.find(p => p.id === projectId)?.name || projectId;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseScopedTarget(target) {
|
|
59
|
+
const text = String(target || '').trim();
|
|
60
|
+
if (!text.startsWith('@')) return null;
|
|
61
|
+
const slash = text.indexOf('/');
|
|
62
|
+
if (slash <= 1 || slash === text.length - 1) {
|
|
63
|
+
throw jsonError('Cross-project target must use @project/session');
|
|
64
|
+
}
|
|
65
|
+
return { project: text.slice(1, slash).trim(), session: text.slice(slash + 1).trim() };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveProject(projects, nameOrId) {
|
|
69
|
+
const text = String(nameOrId || '').trim();
|
|
70
|
+
const byId = projects.filter(p => p.id === text);
|
|
71
|
+
if (byId.length === 1) return byId[0];
|
|
72
|
+
const exact = projects.filter(p => p.name === text);
|
|
73
|
+
if (exact.length === 1) return exact[0];
|
|
74
|
+
if (exact.length > 1) throw jsonError(`Multiple projects named "${text}". Use the project id.`, 409);
|
|
75
|
+
const lower = text.toLowerCase();
|
|
76
|
+
const insensitive = projects.filter(p => String(p.name || '').toLowerCase() === lower);
|
|
77
|
+
if (insensitive.length === 1) return insensitive[0];
|
|
78
|
+
if (insensitive.length > 1) throw jsonError(`Multiple projects named "${text}". Use the project id.`, 409);
|
|
79
|
+
throw jsonError(`No project named "${text}"`, 404);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function findInProject(candidates, target, projectLabel) {
|
|
83
|
+
const byId = candidates.filter(([id]) => id === target);
|
|
84
|
+
if (byId.length === 1) return byId[0];
|
|
85
|
+
|
|
86
|
+
const exact = candidates.filter(([, s]) => s.name === target);
|
|
87
|
+
if (exact.length === 1) return exact[0];
|
|
88
|
+
if (exact.length > 1) throw jsonError(`Multiple sessions named "${target}" in ${projectLabel}. Use the session id.`);
|
|
89
|
+
|
|
90
|
+
const lower = target.toLowerCase();
|
|
91
|
+
const insensitive = candidates.filter(([, s]) => String(s.name || '').toLowerCase() === lower);
|
|
92
|
+
if (insensitive.length === 1) return insensitive[0];
|
|
93
|
+
if (insensitive.length > 1) throw jsonError(`Multiple sessions named "${target}" in ${projectLabel}. Use the session id.`);
|
|
94
|
+
throw jsonError(`No session named "${target}" in ${projectLabel}`, 404);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function findTarget(sessions, callerId, caller, target, cfg = {}) {
|
|
54
98
|
const trimmed = String(target || '').trim();
|
|
55
99
|
if (!trimmed) throw jsonError('Target session is required');
|
|
100
|
+
const projects = Array.isArray(cfg.projects) ? cfg.projects : [];
|
|
101
|
+
const scoped = parseScopedTarget(trimmed);
|
|
102
|
+
|
|
103
|
+
if (scoped) {
|
|
104
|
+
const project = resolveProject(projects, scoped.project);
|
|
105
|
+
const projectSessions = [...sessions]
|
|
106
|
+
.filter(([id, s]) => id !== callerId && (s.projectId || null) === project.id);
|
|
107
|
+
return findInProject(projectSessions, scoped.session, `project "${project.name || project.id}"`);
|
|
108
|
+
}
|
|
56
109
|
|
|
57
110
|
const byId = sessions.get(trimmed);
|
|
58
111
|
if (byId) {
|
|
@@ -63,15 +116,7 @@ function findTarget(sessions, callerId, caller, target) {
|
|
|
63
116
|
|
|
64
117
|
const sameProjectSessions = [...sessions]
|
|
65
118
|
.filter(([id, s]) => id !== callerId && sameProject(caller, s));
|
|
66
|
-
|
|
67
|
-
if (exact.length === 1) return exact[0];
|
|
68
|
-
if (exact.length > 1) throw jsonError(`Multiple sessions named "${trimmed}" in this project. Use the session id.`);
|
|
69
|
-
|
|
70
|
-
const lower = trimmed.toLowerCase();
|
|
71
|
-
const insensitive = sameProjectSessions.filter(([, s]) => String(s.name || '').toLowerCase() === lower);
|
|
72
|
-
if (insensitive.length === 1) return insensitive[0];
|
|
73
|
-
if (insensitive.length > 1) throw jsonError(`Multiple sessions named "${trimmed}" in this project. Use the session id.`);
|
|
74
|
-
throw jsonError(`No session named "${trimmed}" in this project`, 404);
|
|
119
|
+
return findInProject(sameProjectSessions, trimmed, `project "${projectName(projects, caller.projectId)}"`);
|
|
75
120
|
}
|
|
76
121
|
|
|
77
122
|
function latestAgentTextSince(sessionId, sinceTs) {
|
|
@@ -167,13 +212,13 @@ function waitForAnswer({ sessionsApi, targetId, sinceTs, timeoutMs }) {
|
|
|
167
212
|
});
|
|
168
213
|
}
|
|
169
214
|
|
|
170
|
-
async function askSession(payload, sessionsApi) {
|
|
215
|
+
async function askSession(payload, sessionsApi, cfg = {}) {
|
|
171
216
|
const sessions = sessionsApi.getSessions();
|
|
172
217
|
const callerId = String(payload.callerSessionId || '').trim();
|
|
173
218
|
const caller = sessions.get(callerId);
|
|
174
219
|
if (!caller) throw jsonError('Caller session is not active', 404);
|
|
175
220
|
|
|
176
|
-
const [targetId, target] = findTarget(sessions, callerId, caller, payload.target);
|
|
221
|
+
const [targetId, target] = findTarget(sessions, callerId, caller, payload.target, cfg);
|
|
177
222
|
if (target.working) {
|
|
178
223
|
throw jsonError(`Target session "${target.name}" is busy. CliDeck ask only sends to idle sessions. Try again later, choose another idle session, or ask the user how to proceed.`, 409);
|
|
179
224
|
}
|
|
@@ -193,11 +238,11 @@ async function askSession(payload, sessionsApi) {
|
|
|
193
238
|
return { targetSessionId: targetId, targetName: target.name, response };
|
|
194
239
|
}
|
|
195
240
|
|
|
196
|
-
async function handleHttp(req, res, sessionsApi) {
|
|
241
|
+
async function handleHttp(req, res, sessionsApi, getConfig = () => ({})) {
|
|
197
242
|
try {
|
|
198
243
|
if (!isLoopback(req)) throw jsonError('CliDeck ask only accepts local requests', 403);
|
|
199
244
|
const payload = await readJson(req);
|
|
200
|
-
const result = await askSession(payload, sessionsApi);
|
|
245
|
+
const result = await askSession(payload, sessionsApi, getConfig() || {});
|
|
201
246
|
sendJson(res, 200, result);
|
|
202
247
|
} catch (e) {
|
|
203
248
|
sendJson(res, e.status || 500, { error: e.message || 'CliDeck ask failed' });
|
package/transcript.js
CHANGED
|
@@ -262,26 +262,67 @@ function clear(id) {
|
|
|
262
262
|
// Finds the footer line, then walks upward collecting only the contiguous menu block.
|
|
263
263
|
const MENU_MARKERS = { 'claude-code': /[❯›]/, codex: /[›❯]/, 'gemini-cli': /●/ };
|
|
264
264
|
const MENU_CHOICE_RE = /^\s*(?:[│❯›●•]\s+)*(\d+)\.\s+(.+)$/;
|
|
265
|
-
|
|
265
|
+
const MENU_TOP_RE = /^\s*[╭┌┏╔].*[╮┐┓╗]\s*$/;
|
|
266
|
+
const MENU_BOTTOM_RE = /^\s*[╰└┗╚].*[╯┘┛╝]\s*$/;
|
|
267
|
+
const MENU_RULE_RE = /^\s*[─━═-]{5,}\s*$/;
|
|
268
|
+
const TURN_MARKERS = {
|
|
269
|
+
'claude-code': /^(?:[│ ]\s*)?[⏺•●❯›]\s/,
|
|
270
|
+
codex: /^(?:│\s*)?[•›]\s/,
|
|
271
|
+
'gemini-cli': /^(?:✦| > )/,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
function cleanMenuLabel(text) {
|
|
275
|
+
return String(text || '').replace(/[│┃║]\s*$/u, '').trim();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function detectMenuBlock(lines, presetId) {
|
|
266
279
|
const marker = MENU_MARKERS[presetId];
|
|
267
280
|
if (!marker) return null;
|
|
268
281
|
// Only scan the bottom 40 lines — menus are always near the visible area
|
|
269
282
|
const scanStart = Math.max(0, lines.length - 40);
|
|
270
|
-
let
|
|
283
|
+
let footerLineIdx = -1;
|
|
271
284
|
for (let i = lines.length - 1; i >= scanStart; i--) {
|
|
272
|
-
if (/\besc\b|\(esc\)/i.test(lines[i])) {
|
|
285
|
+
if (/\besc\b|\(esc\)/i.test(lines[i])) { footerLineIdx = i; break; }
|
|
273
286
|
}
|
|
274
|
-
if (
|
|
287
|
+
if (footerLineIdx < 0) return null;
|
|
275
288
|
const choices = [];
|
|
276
|
-
|
|
289
|
+
let firstChoiceIdx = -1;
|
|
290
|
+
const searchFrom = MENU_CHOICE_RE.test(lines[footerLineIdx]) ? footerLineIdx : footerLineIdx - 1;
|
|
291
|
+
for (let i = searchFrom; i >= scanStart; i--) {
|
|
277
292
|
if (!lines[i].trim() || /^[│\s]+$/.test(lines[i])) continue;
|
|
293
|
+
if (MENU_RULE_RE.test(lines[i]) || MENU_BOTTOM_RE.test(lines[i])) continue;
|
|
278
294
|
const m = lines[i].match(MENU_CHOICE_RE);
|
|
279
|
-
if (!m) { if (/^\s{2,}\S/.test(lines[i])) continue; break; }
|
|
295
|
+
if (!m) { if (choices.length && /^\s{2,}\S/.test(lines[i])) continue; break; }
|
|
280
296
|
if (choices.length && +m[1] >= +choices[0].value) break;
|
|
281
|
-
choices.unshift({ value: m[1], label: m[2]
|
|
297
|
+
choices.unshift({ value: m[1], label: cleanMenuLabel(m[2]), selected: marker.test(lines[i]) });
|
|
298
|
+
firstChoiceIdx = i;
|
|
282
299
|
}
|
|
283
300
|
if (!choices.some(c => c.selected)) return null;
|
|
284
|
-
|
|
301
|
+
if (!choices.length) return null;
|
|
302
|
+
let startIdx = firstChoiceIdx;
|
|
303
|
+
const turnMarker = TURN_MARKERS[presetId];
|
|
304
|
+
for (let i = startIdx - 1; i >= scanStart; i--) {
|
|
305
|
+
if (turnMarker?.test(lines[i])) break;
|
|
306
|
+
if (lines[i].trim()) startIdx = i;
|
|
307
|
+
if (MENU_TOP_RE.test(lines[i])) { startIdx = i; break; }
|
|
308
|
+
}
|
|
309
|
+
let endIdx = footerLineIdx;
|
|
310
|
+
if (MENU_CHOICE_RE.test(lines[footerLineIdx])) {
|
|
311
|
+
for (let i = footerLineIdx + 1; i < Math.min(lines.length, footerLineIdx + 6); i++) {
|
|
312
|
+
if (MENU_BOTTOM_RE.test(lines[i])) { endIdx = i; break; }
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { choices, startIdx, endIdx };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function detectMenu(lines, presetId) {
|
|
319
|
+
return detectMenuBlock(lines, presetId)?.choices || null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function stripMenu(lines, presetId) {
|
|
323
|
+
const block = detectMenuBlock(lines, presetId);
|
|
324
|
+
if (!block) return lines;
|
|
325
|
+
return lines.filter((_, i) => i < block.startIdx || i > block.endIdx);
|
|
285
326
|
}
|
|
286
327
|
|
|
287
|
-
module.exports = { init, trackInput, recordInjectedInput, trackOutput, updateAgentCandidate, commitAgentCandidate, clearAgentCandidate, parseTurnsFromLines, getTurns, getEntriesSince, getCache, getReplayText, clear, setPrefix, setFinalizeOnIdle, detectMenu };
|
|
328
|
+
module.exports = { init, trackInput, recordInjectedInput, trackOutput, updateAgentCandidate, commitAgentCandidate, clearAgentCandidate, parseTurnsFromLines, getTurns, getEntriesSince, getCache, getReplayText, clear, setPrefix, setFinalizeOnIdle, detectMenu, stripMenu };
|