clideck 1.31.15 → 1.31.17
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/activity.js +11 -66
- package/ansi-utils.js +7 -0
- package/claude-session.js +17 -0
- package/config.js +3 -5
- package/handlers.js +13 -11
- package/http-util.js +20 -0
- package/package.json +1 -1
- package/plugins/voice-input/clideck-plugin.json +1 -1
- package/plugins/voice-input/client.js +27 -18
- package/preset-utils.js +13 -0
- package/public/index.html +52 -0
- package/public/js/app.js +25 -9
- package/public/js/settings.js +1 -0
- package/public/js/terminals.js +127 -6
- package/public/tailwind.css +1 -1
- package/server.js +2 -3
- package/session-agents.js +1 -18
- package/session-ask.js +1 -19
- package/sessions.js +14 -17
- package/telemetry-receiver.js +11 -15
- package/transcript.js +2 -2
package/activity.js
CHANGED
|
@@ -1,77 +1,22 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
let interval = null;
|
|
5
|
-
const net = {}; // id → byte counters
|
|
6
|
-
const stream = {}; // id → burst timing
|
|
1
|
+
// Lightweight per-session I/O counters. The old per-second stats broadcast was
|
|
2
|
+
// removed after frontend status tracking moved away from I/O heuristics.
|
|
3
|
+
const net = {}; // id -> byte counters
|
|
7
4
|
|
|
8
5
|
function ensure(id) {
|
|
9
|
-
if (!net[id]) net[id] = { in: 0, out: 0
|
|
10
|
-
|
|
6
|
+
if (!net[id]) net[id] = { in: 0, out: 0 };
|
|
7
|
+
return net[id];
|
|
11
8
|
}
|
|
12
9
|
|
|
13
10
|
function trackIn(id, bytes) {
|
|
14
|
-
ensure(id);
|
|
15
|
-
|
|
16
|
-
stream[id].lastInAt = Date.now();
|
|
11
|
+
const n = ensure(id);
|
|
12
|
+
n.in += bytes;
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
function lastInputAt(id) { return stream[id]?.lastInAt || 0; }
|
|
20
|
-
|
|
21
15
|
function trackOut(id, data) {
|
|
22
|
-
ensure(id);
|
|
23
|
-
|
|
24
|
-
const s = stream[id];
|
|
25
|
-
net[id].out += data.length;
|
|
26
|
-
if (now - s.lastOutAt > 2000) s.burstStart = now;
|
|
27
|
-
s.lastOutAt = now;
|
|
28
|
-
s.lastChunk = data;
|
|
16
|
+
const n = ensure(id);
|
|
17
|
+
n.out += data.length;
|
|
29
18
|
}
|
|
30
19
|
|
|
31
|
-
function
|
|
32
|
-
if (interval) return;
|
|
33
|
-
interval = setInterval(() => {
|
|
34
|
-
if (!sessions.size) return;
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
const stats = {};
|
|
37
|
-
for (const [id] of sessions) {
|
|
38
|
-
ensure(id);
|
|
39
|
-
const n = net[id];
|
|
40
|
-
const s = stream[id];
|
|
41
|
-
const rawRateOut = n.out - n.prevOut;
|
|
42
|
-
const rawRateIn = n.in - n.prevIn;
|
|
43
|
-
n.prevOut = n.out;
|
|
44
|
-
n.prevIn = n.in;
|
|
45
|
-
const silence = s.lastOutAt ? now - s.lastOutAt : 0;
|
|
46
|
-
const burstMs = s.burstStart && silence < 2000 ? now - s.burstStart : 0;
|
|
47
|
-
stats[id] = { rawRateOut, rawRateIn, burstMs };
|
|
48
|
-
}
|
|
49
|
-
if (Object.keys(stats).length) broadcast({ type: 'stats', stats });
|
|
50
|
-
// Debug: PTY active state + last output chars per session (1s tick)
|
|
51
|
-
const active = [];
|
|
52
|
-
for (const [id] of sessions) {
|
|
53
|
-
const s = stream[id]; if (!s?.lastOutAt) continue;
|
|
54
|
-
const state = now - s.lastOutAt < 2000 ? 'ACTIVE' : 'SILENT';
|
|
55
|
-
const last = (s.lastChunk || '').replace(/\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b./g, '').replace(/[\r\n]+/g, ' ').trim().slice(-80);
|
|
56
|
-
active.push(`${id.slice(0,8)}=${state} [${last}]`);
|
|
57
|
-
}
|
|
58
|
-
// if (active.length) console.log(`[pty] ${active.join(' | ')}`);
|
|
59
|
-
}, 1000);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function stop() {
|
|
63
|
-
clearInterval(interval);
|
|
64
|
-
interval = null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function isActive(id) {
|
|
68
|
-
const s = stream[id];
|
|
69
|
-
return s ? (Date.now() - s.lastOutAt < 2000) : false;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function lastOutputAt(id) { return stream[id]?.lastOutAt || 0; }
|
|
73
|
-
function lastChunk(id) { return stream[id]?.lastChunk || ''; }
|
|
74
|
-
|
|
75
|
-
function clear(id) { delete net[id]; delete stream[id]; }
|
|
20
|
+
function clear(id) { delete net[id]; }
|
|
76
21
|
|
|
77
|
-
module.exports = {
|
|
22
|
+
module.exports = { trackIn, trackOut, clear };
|
package/ansi-utils.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const CLAUDE_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2
|
+
|
|
3
|
+
function updateClaudeSessionToken(sess, token, clideckId, options = {}) {
|
|
4
|
+
const next = String(token || '').trim();
|
|
5
|
+
if (!sess || sess.presetId !== 'claude-code' || !CLAUDE_SESSION_ID_RE.test(next)) return false;
|
|
6
|
+
if (sess.sessionToken === next) return false;
|
|
7
|
+
const prev = sess.sessionToken;
|
|
8
|
+
sess.sessionToken = next;
|
|
9
|
+
|
|
10
|
+
const label = options.label || 'Claude';
|
|
11
|
+
const source = options.source ? ` via ${options.source}` : '';
|
|
12
|
+
const previous = prev ? `${prev.slice(0, 12)}... -> ` : '';
|
|
13
|
+
console.log(`${label}: updated Claude session ID for ${clideckId.slice(0, 8)}${source}: ${previous}${next.slice(0, 12)}...`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { CLAUDE_SESSION_ID_RE, updateClaudeSessionToken };
|
package/config.js
CHANGED
|
@@ -3,7 +3,8 @@ const { join } = require('path');
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { DATA_DIR } = require('./paths');
|
|
6
|
-
const { defaultShell
|
|
6
|
+
const { defaultShell } = require('./utils');
|
|
7
|
+
const { presetForCommand } = require('./preset-utils');
|
|
7
8
|
|
|
8
9
|
const CONFIG_PATH = join(DATA_DIR, 'config.json');
|
|
9
10
|
|
|
@@ -63,10 +64,7 @@ function isPresetEnabled(preset) {
|
|
|
63
64
|
}
|
|
64
65
|
const EXPOSED_PRESETS = PRESETS.filter(isPresetEnabled);
|
|
65
66
|
|
|
66
|
-
function matchPreset(cmd) {
|
|
67
|
-
const bin = binName(cmd.command);
|
|
68
|
-
return PRESETS.find(p => binName(p.command) === bin);
|
|
69
|
-
}
|
|
67
|
+
function matchPreset(cmd) { return presetForCommand(cmd, PRESETS, { usePresetId: false }); }
|
|
70
68
|
|
|
71
69
|
function firstCommandToken(command) {
|
|
72
70
|
const raw = String(command || '').trim();
|
package/handlers.js
CHANGED
|
@@ -7,6 +7,7 @@ const sessions = require('./sessions');
|
|
|
7
7
|
const themes = require('./themes');
|
|
8
8
|
const presets = JSON.parse(readFileSync(join(__dirname, 'agent-presets.json'), 'utf8'));
|
|
9
9
|
const { listDirs, binName, defaultShell } = require('./utils');
|
|
10
|
+
const { presetForCommand: findPresetForCommand } = require('./preset-utils');
|
|
10
11
|
const { PORT } = require('./runtime');
|
|
11
12
|
for (const p of presets) if (p.presetId === 'shell') p.command = defaultShell;
|
|
12
13
|
function isPresetEnabled(preset) {
|
|
@@ -72,15 +73,10 @@ function getInstalledVersion(bin) {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
function presetForCommand(cmd) {
|
|
75
|
-
|
|
76
|
-
const preset = presets.find(p => p.presetId === cmd.presetId);
|
|
77
|
-
if (preset) return preset;
|
|
78
|
-
}
|
|
79
|
-
const bin = binName(cmd?.command);
|
|
80
|
-
return presets.find(p => binName(p.command) === bin);
|
|
76
|
+
return findPresetForCommand(cmd, presets);
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
function
|
|
79
|
+
function rawCommandEnv(cmd) {
|
|
84
80
|
return cmd?.env && typeof cmd.env === 'object' && !Array.isArray(cmd.env) ? cmd.env : {};
|
|
85
81
|
}
|
|
86
82
|
|
|
@@ -93,7 +89,7 @@ function expandHomePath(value) {
|
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
function configRootFor(preset, cmd) {
|
|
96
|
-
const env =
|
|
92
|
+
const env = rawCommandEnv(cmd);
|
|
97
93
|
if (preset?.presetId === 'claude-code') return expandHomePath(env.CLAUDE_CONFIG_DIR) || join(os.homedir(), '.claude');
|
|
98
94
|
if (preset?.presetId === 'codex') return expandHomePath(env.CODEX_HOME) || join(os.homedir(), '.codex');
|
|
99
95
|
if (preset?.presetId === 'gemini-cli') return join(expandHomePath(env.GEMINI_CLI_HOME) || os.homedir(), '.gemini');
|
|
@@ -262,7 +258,7 @@ function detectTelemetryConfig(c) {
|
|
|
262
258
|
if (preset.available && preset.minVersion && !preset.versionOk) {
|
|
263
259
|
detected = false;
|
|
264
260
|
reason = `Update required (${preset.minVersion}+)`;
|
|
265
|
-
} else if (!detected && cmd.telemetryEnabled && preset.telemetryAutoSetup && preset.available && preset.versionOk && !attemptedRepairs.has(cmd.id || preset.presetId)) {
|
|
261
|
+
} else if (!detected && cmd.telemetryEnabled && cmd.telemetrySetupConsent === true && preset.telemetryAutoSetup && preset.available && preset.versionOk && !attemptedRepairs.has(cmd.id || preset.presetId)) {
|
|
266
262
|
attemptedRepairs.add(cmd.id || preset.presetId);
|
|
267
263
|
const repaired = applyTelemetryConfig(preset, cmd);
|
|
268
264
|
if (repaired.success) {
|
|
@@ -399,6 +395,7 @@ function onConnection(ws) {
|
|
|
399
395
|
cfg = { ...cfg, ...msg.config };
|
|
400
396
|
detectTelemetryConfig(cfg);
|
|
401
397
|
config.save(cfg);
|
|
398
|
+
plugins.notifyConfig(cfg);
|
|
402
399
|
sessions.broadcast({ type: 'config', config: configForClient() });
|
|
403
400
|
break;
|
|
404
401
|
|
|
@@ -417,11 +414,13 @@ function onConnection(ws) {
|
|
|
417
414
|
if (targetCmd ? cmd.id === targetCmd.id : presetForCommand(cmd)?.presetId === preset.presetId) {
|
|
418
415
|
cmd.telemetryEnabled = result.success;
|
|
419
416
|
cmd.telemetryStatus = result.success ? { ok: true } : { ok: false, error: result.message };
|
|
417
|
+
if (result.success) cmd.telemetrySetupConsent = true;
|
|
420
418
|
// Enable the agent when setup succeeds, disable if it fails
|
|
421
419
|
if (result.success) cmd.enabled = true;
|
|
422
420
|
}
|
|
423
421
|
}
|
|
424
422
|
config.save(cfg);
|
|
423
|
+
plugins.notifyConfig(cfg);
|
|
425
424
|
sessions.broadcast({ type: 'config', config: configForClient() });
|
|
426
425
|
ws.send(JSON.stringify({
|
|
427
426
|
type: 'telemetry.autosetup.result',
|
|
@@ -448,12 +447,14 @@ function onConnection(ws) {
|
|
|
448
447
|
for (const cmd of cfg.commands) {
|
|
449
448
|
if (targetCmd ? cmd.id === targetCmd.id : presetForCommand(cmd)?.presetId === preset.presetId) {
|
|
450
449
|
cmd.telemetryEnabled = enable && result.success;
|
|
450
|
+
cmd.telemetrySetupConsent = enable && result.success;
|
|
451
451
|
cmd.telemetryStatus = enable
|
|
452
452
|
? (result.success ? { ok: true } : { ok: false, error: result.message })
|
|
453
453
|
: null;
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
config.save(cfg);
|
|
457
|
+
plugins.notifyConfig(cfg);
|
|
457
458
|
sessions.broadcast({ type: 'config', config: configForClient() });
|
|
458
459
|
break;
|
|
459
460
|
}
|
|
@@ -659,6 +660,7 @@ function onConnection(ws) {
|
|
|
659
660
|
|
|
660
661
|
case 'remote.install': {
|
|
661
662
|
const update = !!msg.update;
|
|
663
|
+
const restartAfterUpdate = !!msg.restart;
|
|
662
664
|
const proc = require('child_process').spawn('npm', ['install', '-g', 'clideck-remote'], {
|
|
663
665
|
shell: true, stdio: ['ignore', 'pipe', 'pipe'],
|
|
664
666
|
});
|
|
@@ -666,8 +668,8 @@ function onConnection(ws) {
|
|
|
666
668
|
proc.stderr.on('data', d => ws.send(JSON.stringify({ type: 'remote.install.progress', text: d.toString() })));
|
|
667
669
|
proc.on('close', code => {
|
|
668
670
|
remoteUpdateCache = null;
|
|
669
|
-
if (code !== 0 || !update) {
|
|
670
|
-
ws.send(JSON.stringify({ type: 'remote.install.done', success: code === 0, update }));
|
|
671
|
+
if (code !== 0 || !update || !restartAfterUpdate) {
|
|
672
|
+
ws.send(JSON.stringify({ type: 'remote.install.done', success: code === 0, update, restarted: false }));
|
|
671
673
|
return;
|
|
672
674
|
}
|
|
673
675
|
require('child_process').execFile('clideck-remote', ['restart', '--json'], { timeout: 10000, shell: process.platform === 'win32', env: remoteCliEnv() }, (err, stdout) => {
|
package/http-util.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function sendJson(res, status, payload) {
|
|
2
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
3
|
+
res.end(JSON.stringify(payload));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function isLoopback(req) {
|
|
7
|
+
const addr = req.socket?.remoteAddress || '';
|
|
8
|
+
return addr === '::1' || addr === '127.0.0.1' || addr.startsWith('127.') || addr.startsWith('::ffff:127.');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function sameProject(a, b) {
|
|
12
|
+
return (a.projectId || null) === (b.projectId || null);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function projectName(projects, projectId) {
|
|
16
|
+
if (!projectId) return 'No project';
|
|
17
|
+
return projects.find(p => p.id === projectId)?.name || projectId;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { sendJson, isLoopback, sameProject, projectName };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "voice-input",
|
|
3
3
|
"name": "Voice Input",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.2",
|
|
5
5
|
"author": "CliDeck",
|
|
6
6
|
"description": "Dictate prompts with your voice using Whisper speech-to-text",
|
|
7
7
|
"icon": "<svg class=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/><line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/></svg>",
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
let settings = { enabled: false, backend: 'openai', hotkey: 'F4' };
|
|
2
2
|
let recordingState = null; // { startTime, mediaRecorder, stream, cancelled, sessionId }
|
|
3
|
+
let transcribing = false;
|
|
3
4
|
let activeToast = null;
|
|
4
|
-
let
|
|
5
|
+
let micControl = null;
|
|
5
6
|
let _api = null;
|
|
7
|
+
const MIC_ICON = '<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="8" y1="22" x2="16" y2="22"/></svg>';
|
|
6
8
|
|
|
7
9
|
function toast(message, type, persistent) {
|
|
8
10
|
if (activeToast) activeToast.dismiss();
|
|
@@ -39,20 +41,22 @@ function float32ToBase64(f32) {
|
|
|
39
41
|
// --- Button state ---
|
|
40
42
|
|
|
41
43
|
function updateButton() {
|
|
42
|
-
if (!
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
if (!micControl) return;
|
|
45
|
+
const hotkey = settings.hotkey || 'F4';
|
|
46
|
+
micControl.setVisible(!!settings.enabled);
|
|
47
|
+
micControl.setActive(!!recordingState);
|
|
48
|
+
micControl.setBusy(!!transcribing);
|
|
49
|
+
micControl.setTitle(recordingState
|
|
50
|
+
? `Stop recording (${hotkey})`
|
|
51
|
+
: transcribing ? 'Transcribing...'
|
|
52
|
+
: `Voice Input (${hotkey})`);
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
// --- Recording ---
|
|
53
56
|
|
|
54
57
|
async function startRecording() {
|
|
55
|
-
if (!_api || recordingState) return;
|
|
58
|
+
if (!_api || recordingState || transcribing) return;
|
|
59
|
+
if (!settings.enabled) { toast('Voice Input is disabled', 'error'); return; }
|
|
56
60
|
const sessionId = _api.getActiveSessionId();
|
|
57
61
|
if (!sessionId) { toast('No active terminal', 'error'); return; }
|
|
58
62
|
|
|
@@ -85,6 +89,8 @@ async function startRecording() {
|
|
|
85
89
|
return;
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
transcribing = true;
|
|
93
|
+
updateButton();
|
|
88
94
|
toast('Transcribing...', 'info', true);
|
|
89
95
|
|
|
90
96
|
try {
|
|
@@ -93,6 +99,8 @@ async function startRecording() {
|
|
|
93
99
|
const b64 = float32ToBase64(pcm);
|
|
94
100
|
_api.send('transcribe', { audio: b64, sessionId: state.sessionId });
|
|
95
101
|
} catch (e) {
|
|
102
|
+
transcribing = false;
|
|
103
|
+
updateButton();
|
|
96
104
|
toast('Audio decode failed', 'error');
|
|
97
105
|
}
|
|
98
106
|
};
|
|
@@ -100,7 +108,6 @@ async function startRecording() {
|
|
|
100
108
|
mr.start(100);
|
|
101
109
|
recordingState = { startTime: Date.now(), mediaRecorder: mr, stream, cancelled: false, sessionId };
|
|
102
110
|
updateButton();
|
|
103
|
-
toast('REC \u25cf', 'error', true);
|
|
104
111
|
} catch (e) {
|
|
105
112
|
toast('Mic: ' + e.message, 'error');
|
|
106
113
|
}
|
|
@@ -156,7 +163,7 @@ export function init(api) {
|
|
|
156
163
|
|
|
157
164
|
api.onMessage('settings', msg => {
|
|
158
165
|
settings = { ...settings, ...msg };
|
|
159
|
-
|
|
166
|
+
updateButton();
|
|
160
167
|
bindHotkey();
|
|
161
168
|
});
|
|
162
169
|
|
|
@@ -166,6 +173,8 @@ export function init(api) {
|
|
|
166
173
|
});
|
|
167
174
|
|
|
168
175
|
api.onMessage('result', msg => {
|
|
176
|
+
transcribing = false;
|
|
177
|
+
updateButton();
|
|
169
178
|
if (activeToast) { activeToast.dismiss(); activeToast = null; }
|
|
170
179
|
if (msg.skipped || !msg.text) return;
|
|
171
180
|
const sid = msg.sessionId || _api.getActiveSessionId();
|
|
@@ -175,22 +184,22 @@ export function init(api) {
|
|
|
175
184
|
});
|
|
176
185
|
|
|
177
186
|
api.onMessage('error', msg => {
|
|
187
|
+
transcribing = false;
|
|
188
|
+
updateButton();
|
|
178
189
|
toast(msg.error || 'Error', 'error');
|
|
179
190
|
});
|
|
180
191
|
|
|
181
192
|
api.send('getSettings');
|
|
182
193
|
|
|
183
|
-
|
|
194
|
+
micControl = api.addTerminalInputButton({
|
|
195
|
+
id: 'voice-input',
|
|
184
196
|
title: 'Voice Input (F4)',
|
|
185
|
-
icon:
|
|
197
|
+
icon: MIC_ICON,
|
|
186
198
|
onClick() {
|
|
187
|
-
if (!settings.enabled) { toast('Voice Input is disabled', 'error'); return; }
|
|
188
199
|
if (!recordingState) startRecording();
|
|
189
200
|
else stopRecording();
|
|
190
201
|
},
|
|
191
202
|
});
|
|
192
|
-
|
|
193
|
-
btnEl.addEventListener('mousedown', e => e.preventDefault());
|
|
194
|
-
if (!settings.enabled && btnEl) btnEl.style.display = 'none';
|
|
203
|
+
updateButton();
|
|
195
204
|
bindHotkey();
|
|
196
205
|
}
|
package/preset-utils.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { binName } = require('./utils');
|
|
2
|
+
|
|
3
|
+
function presetForCommand(cmd, presets, options = {}) {
|
|
4
|
+
const usePresetId = options.usePresetId !== false;
|
|
5
|
+
if (usePresetId && cmd?.presetId) {
|
|
6
|
+
const preset = presets.find(p => p.presetId === cmd.presetId);
|
|
7
|
+
if (preset) return preset;
|
|
8
|
+
}
|
|
9
|
+
const bin = binName(cmd?.command);
|
|
10
|
+
return presets.find(p => binName(p.command) === bin);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { presetForCommand };
|
package/public/index.html
CHANGED
|
@@ -53,6 +53,58 @@
|
|
|
53
53
|
flex-shrink: 0;
|
|
54
54
|
height: 100%;
|
|
55
55
|
}
|
|
56
|
+
.terminal-input-actions {
|
|
57
|
+
position: absolute;
|
|
58
|
+
right: 18px;
|
|
59
|
+
bottom: 18px;
|
|
60
|
+
z-index: 20;
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
pointer-events: auto;
|
|
65
|
+
transition: opacity .18s cubic-bezier(.16,1,.3,1), transform .22s cubic-bezier(.16,1,.3,1);
|
|
66
|
+
}
|
|
67
|
+
.terminal-input-actions.is-hidden {
|
|
68
|
+
opacity: 0;
|
|
69
|
+
transform: translateY(12px) scale(.92);
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
}
|
|
72
|
+
.terminal-input-action {
|
|
73
|
+
width: 42px;
|
|
74
|
+
height: 42px;
|
|
75
|
+
display: inline-flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
border-radius: 9999px;
|
|
79
|
+
border: 1px solid color-mix(in srgb,var(--color-border) 58%,transparent);
|
|
80
|
+
background: linear-gradient(180deg,color-mix(in srgb,var(--color-raised) 92%,transparent),color-mix(in srgb,var(--color-surface) 90%,transparent));
|
|
81
|
+
color: var(--color-soft);
|
|
82
|
+
box-shadow: 0 18px 38px -18px var(--color-shadow),0 0 0 1px color-mix(in srgb,var(--color-bright) 5%,transparent) inset;
|
|
83
|
+
transition: color .16s ease,border-color .16s ease,background-color .16s ease,transform .22s cubic-bezier(.16,1,.3,1);
|
|
84
|
+
}
|
|
85
|
+
.terminal-input-action svg {
|
|
86
|
+
width: 18px;
|
|
87
|
+
height: 18px;
|
|
88
|
+
}
|
|
89
|
+
.terminal-input-action:hover {
|
|
90
|
+
color: var(--color-bright);
|
|
91
|
+
border-color: color-mix(in srgb,var(--color-dim) 82%,transparent);
|
|
92
|
+
transform: translateY(-2px) scale(1.04);
|
|
93
|
+
}
|
|
94
|
+
.terminal-input-action.is-active {
|
|
95
|
+
color: var(--color-bright);
|
|
96
|
+
border-color: color-mix(in srgb,var(--color-dim) 82%,transparent);
|
|
97
|
+
animation: terminalMicPulse 1.05s ease-in-out infinite;
|
|
98
|
+
}
|
|
99
|
+
.terminal-input-action.is-busy {
|
|
100
|
+
color: var(--color-bright);
|
|
101
|
+
pointer-events: none;
|
|
102
|
+
animation: terminalMicPulse 1.25s ease-in-out infinite;
|
|
103
|
+
}
|
|
104
|
+
@keyframes terminalMicPulse {
|
|
105
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.38); }
|
|
106
|
+
50% { box-shadow: 0 0 0 0.55rem rgba(59, 130, 246, 0); }
|
|
107
|
+
}
|
|
56
108
|
#main { min-width: 0; }
|
|
57
109
|
#mobile-nav-toggle,
|
|
58
110
|
#mobile-nav-close,
|
package/public/js/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { state, send, flushQueuedSends } from './state.js';
|
|
2
2
|
import { esc, binName, resolveIconPath, randomUUID } from './utils.js';
|
|
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';
|
|
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, addTerminalInputAction, removeTerminalInputActionsForPlugin, trackTerminalInputData } from './terminals.js';
|
|
4
4
|
import { renderSettings, updateVersionFooter } from './settings.js';
|
|
5
5
|
import { openCreator, closeCreator, refreshCreator } from './creator.js';
|
|
6
6
|
import { handleDirsResponse, handleMkdirResponse, openFolderPicker } from './folder-picker.js';
|
|
@@ -369,7 +369,7 @@ function connect() {
|
|
|
369
369
|
case 'remote.update':
|
|
370
370
|
remoteUpdateInfo = msg?.available ? msg : null;
|
|
371
371
|
if (remoteUpdateInfo?.available && remoteInstalled && !remoteInstallMode) {
|
|
372
|
-
startRemoteInstall({ update: true, auto: true });
|
|
372
|
+
startRemoteInstall({ update: true, auto: true, connectAfter: remoteState === 'waiting' || remoteState === 'paired' || remoteModalOpen });
|
|
373
373
|
}
|
|
374
374
|
if (remotePreflight?.pending) {
|
|
375
375
|
remotePreflight.updateSeen = true;
|
|
@@ -1014,6 +1014,7 @@ async function loadPlugins(list) {
|
|
|
1014
1014
|
for (const id of loadedPlugins) {
|
|
1015
1015
|
if (!activeIds.has(id)) {
|
|
1016
1016
|
unregisterAllForPlugin(id);
|
|
1017
|
+
removeTerminalInputActionsForPlugin(id);
|
|
1017
1018
|
for (const [key] of pluginMessageHandlers) {
|
|
1018
1019
|
if (key.startsWith(`plugin.${id}.`)) pluginMessageHandlers.delete(key);
|
|
1019
1020
|
}
|
|
@@ -1066,9 +1067,10 @@ async function loadPlugins(list) {
|
|
|
1066
1067
|
send(event, data = {}) { send({ ...data, type: `plugin.${plugin.id}.${event}` }); },
|
|
1067
1068
|
onMessage(event, fn) { pluginMessageHandlers.set(`plugin.${plugin.id}.${event}`, fn); },
|
|
1068
1069
|
addToolbarButton(opts) { return addPluginToolbarButton(plugin.id, opts); },
|
|
1070
|
+
addTerminalInputButton(opts) { return addTerminalInputAction(plugin.id, opts); },
|
|
1069
1071
|
getActiveSessionId() { return state.active; },
|
|
1070
1072
|
getTerminalSelection() { const e = state.terms.get(state.active); return e ? e.term.getSelection() : ''; },
|
|
1071
|
-
writeToSession(id, text) { send({ type: 'input', id, data: text }); },
|
|
1073
|
+
writeToSession(id, text) { trackTerminalInputData(id, text); send({ type: 'input', id, data: text }); },
|
|
1072
1074
|
toast(message, opts) { return showToast(message, opts); },
|
|
1073
1075
|
registerHotkey(combo, callback) { return registerHotkey(plugin.id, combo, callback); },
|
|
1074
1076
|
unregisterHotkey(combo) { unregisterHotkey(plugin.id, combo); },
|
|
@@ -1220,7 +1222,7 @@ function finishRemotePreflight() {
|
|
|
1220
1222
|
return;
|
|
1221
1223
|
}
|
|
1222
1224
|
if (remoteUpdateInfo?.available) {
|
|
1223
|
-
startRemoteInstall({ update: true, auto: true });
|
|
1225
|
+
startRemoteInstall({ update: true, auto: true, connectAfter: true });
|
|
1224
1226
|
return;
|
|
1225
1227
|
}
|
|
1226
1228
|
if (remoteState === 'idle') {
|
|
@@ -1382,7 +1384,7 @@ function handleRemoteStatus(msg) {
|
|
|
1382
1384
|
if (wasPaired) { stopRemoteStats(); setRemoteLock(false); }
|
|
1383
1385
|
}
|
|
1384
1386
|
if (remoteUpdateInfo?.available && remoteInstalled && !remoteInstallMode) {
|
|
1385
|
-
startRemoteInstall({ update: true, auto: true });
|
|
1387
|
+
startRemoteInstall({ update: true, auto: true, connectAfter: remoteState === 'waiting' || remoteState === 'paired' || remoteModalOpen });
|
|
1386
1388
|
return;
|
|
1387
1389
|
}
|
|
1388
1390
|
updateRemoteButton();
|
|
@@ -1402,7 +1404,7 @@ function handleRemotePaired(msg) {
|
|
|
1402
1404
|
updateRemoteButton();
|
|
1403
1405
|
startRemotePoll();
|
|
1404
1406
|
if (remoteUpdateInfo?.available && remoteInstalled && !remoteInstallMode) {
|
|
1405
|
-
startRemoteInstall({ update: true, auto: true });
|
|
1407
|
+
startRemoteInstall({ update: true, auto: true, connectAfter: true });
|
|
1406
1408
|
return;
|
|
1407
1409
|
}
|
|
1408
1410
|
if (remotePreflight?.pending) {
|
|
@@ -1437,25 +1439,36 @@ function appendInstallLog(text) {
|
|
|
1437
1439
|
}
|
|
1438
1440
|
|
|
1439
1441
|
function startRemoteInstall(opts = {}) {
|
|
1440
|
-
|
|
1442
|
+
const update = !!opts.update;
|
|
1443
|
+
const auto = !!opts.auto;
|
|
1444
|
+
const connectAfter = opts.connectAfter !== undefined
|
|
1445
|
+
? !!opts.connectAfter
|
|
1446
|
+
: (!auto || remoteModalOpen || remoteState === 'waiting' || remoteState === 'paired');
|
|
1447
|
+
remoteInstallMode = { update, auto, connectAfter };
|
|
1441
1448
|
const log = document.getElementById('remote-install-log');
|
|
1442
1449
|
log.textContent = '';
|
|
1443
1450
|
if (remoteInstallMode.update) {
|
|
1444
1451
|
appendInstallLog(`Updating clideck-remote to ${remoteUpdateInfo?.latest || 'latest'}...\n`);
|
|
1445
1452
|
}
|
|
1446
1453
|
setRemotePane('installing');
|
|
1447
|
-
if (!remoteModalOpen) openRemoteModal();
|
|
1448
|
-
send({ type: 'remote.install', update: remoteInstallMode.update });
|
|
1454
|
+
if (!remoteModalOpen && remoteInstallMode.connectAfter) openRemoteModal();
|
|
1455
|
+
send({ type: 'remote.install', update: remoteInstallMode.update, restart: remoteInstallMode.connectAfter });
|
|
1449
1456
|
}
|
|
1450
1457
|
|
|
1451
1458
|
function handleInstallDone(msg) {
|
|
1452
1459
|
const success = !!msg?.success;
|
|
1453
1460
|
const wasUpdate = !!msg?.update || !!remoteInstallMode?.update;
|
|
1461
|
+
const mode = remoteInstallMode || {};
|
|
1454
1462
|
remoteInstallMode = null;
|
|
1455
1463
|
if (success) {
|
|
1456
1464
|
remoteInstalled = true;
|
|
1457
1465
|
remoteUpdateInfo = null;
|
|
1458
1466
|
if (wasUpdate) {
|
|
1467
|
+
if (!mode.connectAfter && !msg?.restart) {
|
|
1468
|
+
send({ type: 'remote.status', forceUpdate: true });
|
|
1469
|
+
updateRemoteButton();
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1459
1472
|
remoteState = 'connecting';
|
|
1460
1473
|
setRemotePane('connecting');
|
|
1461
1474
|
send({ type: 'remote.status', forceUpdate: true });
|
|
@@ -1470,6 +1483,9 @@ function handleInstallDone(msg) {
|
|
|
1470
1483
|
const log = document.getElementById('remote-install-log');
|
|
1471
1484
|
log.textContent += `\n— ${msg?.error || 'Install failed'}. Check permissions or run manually:\n npm install -g clideck-remote\n`;
|
|
1472
1485
|
log.scrollTop = log.scrollHeight;
|
|
1486
|
+
if (!remoteModalOpen) {
|
|
1487
|
+
showToast('Mobile Remote update failed. Run `npm install -g clideck-remote` manually.', { type: 'error', duration: 6000 });
|
|
1488
|
+
}
|
|
1473
1489
|
}
|
|
1474
1490
|
}
|
|
1475
1491
|
|
package/public/js/settings.js
CHANGED
|
@@ -614,6 +614,7 @@ function saveConfig() {
|
|
|
614
614
|
outputMarker: existing.outputMarker || preset?.outputMarker || null,
|
|
615
615
|
env: parseEnvText(card.querySelector('.agent-env')?.value || ''),
|
|
616
616
|
telemetryEnabled: telemetryEnabledForCommand({ ...existing, presetId }, command),
|
|
617
|
+
telemetrySetupConsent: !!existing.telemetrySetupConsent,
|
|
617
618
|
telemetryStatus: existing.telemetryStatus || null,
|
|
618
619
|
bridge: existing.bridge || preset?.bridge,
|
|
619
620
|
userAdded: !!existing.userAdded,
|
package/public/js/terminals.js
CHANGED
|
@@ -43,6 +43,113 @@ const LIGHT_BALLS = ['#0891b2', '#059669', '#7c3aed'];
|
|
|
43
43
|
const URL_RE = /\bhttps?:\/\/[^\s<>"'`]+/g;
|
|
44
44
|
const JUMP_LATEST_THRESHOLD_ROWS = 3;
|
|
45
45
|
const JUMP_LATEST_VISIBLE_CLASS = 'is-visible';
|
|
46
|
+
const terminalInputActions = new Map();
|
|
47
|
+
let terminalInputActionSeq = 0;
|
|
48
|
+
let terminalInputActionsEl = null;
|
|
49
|
+
|
|
50
|
+
function ensureTerminalInputActionsEl() {
|
|
51
|
+
if (terminalInputActionsEl?.isConnected) return terminalInputActionsEl;
|
|
52
|
+
const host = document.getElementById('terminals');
|
|
53
|
+
if (!host) return null;
|
|
54
|
+
terminalInputActionsEl = document.createElement('div');
|
|
55
|
+
terminalInputActionsEl.className = 'terminal-input-actions is-hidden';
|
|
56
|
+
host.appendChild(terminalInputActionsEl);
|
|
57
|
+
return terminalInputActionsEl;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function refreshTerminalInputActions() {
|
|
61
|
+
const el = ensureTerminalInputActionsEl();
|
|
62
|
+
if (!el) return;
|
|
63
|
+
const entry = state.active ? state.terms.get(state.active) : null;
|
|
64
|
+
let visibleCount = 0;
|
|
65
|
+
for (const action of terminalInputActions.values()) {
|
|
66
|
+
const visible = action.visible !== false;
|
|
67
|
+
action.button.style.display = visible ? '' : 'none';
|
|
68
|
+
action.button.classList.toggle('is-active', !!action.active);
|
|
69
|
+
action.button.classList.toggle('is-busy', !!action.busy);
|
|
70
|
+
if (visible) visibleCount++;
|
|
71
|
+
}
|
|
72
|
+
el.classList.toggle('is-hidden', !entry || visibleCount === 0 || !!entry.inputHasText || !!entry.scrolledUp);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function setTerminalInputAction(action, patch) {
|
|
76
|
+
Object.assign(action, patch);
|
|
77
|
+
if (patch.title !== undefined) action.button.title = patch.title || '';
|
|
78
|
+
if (patch.icon !== undefined) action.button.innerHTML = patch.icon || '';
|
|
79
|
+
refreshTerminalInputActions();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function addTerminalInputAction(pluginId, opts = {}) {
|
|
83
|
+
const id = opts.id || `action-${++terminalInputActionSeq}`;
|
|
84
|
+
const key = `${pluginId}:${id}`;
|
|
85
|
+
terminalInputActions.get(key)?.remove?.();
|
|
86
|
+
const button = document.createElement('button');
|
|
87
|
+
button.type = 'button';
|
|
88
|
+
button.className = 'terminal-input-action';
|
|
89
|
+
button.title = opts.title || '';
|
|
90
|
+
button.innerHTML = opts.icon || '';
|
|
91
|
+
button.dataset.pluginId = pluginId;
|
|
92
|
+
button.dataset.actionId = id;
|
|
93
|
+
const action = {
|
|
94
|
+
pluginId,
|
|
95
|
+
id,
|
|
96
|
+
button,
|
|
97
|
+
title: opts.title || '',
|
|
98
|
+
icon: opts.icon || '',
|
|
99
|
+
onClick: typeof opts.onClick === 'function' ? opts.onClick : null,
|
|
100
|
+
visible: opts.visible !== false,
|
|
101
|
+
active: false,
|
|
102
|
+
busy: false,
|
|
103
|
+
};
|
|
104
|
+
button.addEventListener('mousedown', e => e.preventDefault());
|
|
105
|
+
button.addEventListener('click', (e) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
const sessionId = state.active;
|
|
109
|
+
if (!sessionId) return;
|
|
110
|
+
action.onClick?.(sessionId);
|
|
111
|
+
state.terms.get(sessionId)?.term?.focus();
|
|
112
|
+
});
|
|
113
|
+
action.remove = () => {
|
|
114
|
+
terminalInputActions.delete(key);
|
|
115
|
+
button.remove();
|
|
116
|
+
refreshTerminalInputActions();
|
|
117
|
+
};
|
|
118
|
+
terminalInputActions.set(key, action);
|
|
119
|
+
ensureTerminalInputActionsEl()?.appendChild(button);
|
|
120
|
+
refreshTerminalInputActions();
|
|
121
|
+
return {
|
|
122
|
+
setVisible(visible) { setTerminalInputAction(action, { visible: !!visible }); },
|
|
123
|
+
setActive(active) { setTerminalInputAction(action, { active: !!active }); },
|
|
124
|
+
setBusy(busy) { setTerminalInputAction(action, { busy: !!busy }); },
|
|
125
|
+
setTitle(title) { setTerminalInputAction(action, { title: title || '' }); },
|
|
126
|
+
setIcon(icon) { setTerminalInputAction(action, { icon: icon || '' }); },
|
|
127
|
+
remove: action.remove,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function removeTerminalInputActionsForPlugin(pluginId) {
|
|
132
|
+
for (const action of [...terminalInputActions.values()]) {
|
|
133
|
+
if (action.pluginId === pluginId) action.remove();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function trackTerminalInputData(id, data) {
|
|
138
|
+
const entry = state.terms.get(id);
|
|
139
|
+
if (!entry || !data || data.startsWith('\x1b')) return;
|
|
140
|
+
let length = entry.inputLength || 0;
|
|
141
|
+
for (const ch of data) {
|
|
142
|
+
if (ch === '\r' || ch === '\n' || ch === '\x03' || ch === '\x15') length = 0;
|
|
143
|
+
else if (ch === '\x7f' || ch === '\b') length = Math.max(0, length - 1);
|
|
144
|
+
else if (ch >= ' ') length += 1;
|
|
145
|
+
}
|
|
146
|
+
entry.inputLength = length;
|
|
147
|
+
const hasText = length > 0;
|
|
148
|
+
if (entry.inputHasText !== hasText) {
|
|
149
|
+
entry.inputHasText = hasText;
|
|
150
|
+
if (id === state.active) refreshTerminalInputActions();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
46
153
|
|
|
47
154
|
function cleanUrlMatch(text, index) {
|
|
48
155
|
let url = text;
|
|
@@ -117,8 +224,13 @@ function createJumpLatestButton(term) {
|
|
|
117
224
|
return btn;
|
|
118
225
|
}
|
|
119
226
|
|
|
120
|
-
function updateJumpLatestButton(term, btn) {
|
|
121
|
-
|
|
227
|
+
function updateJumpLatestButton(id, term, btn) {
|
|
228
|
+
const scrolledUp = shouldShowJumpLatest(term);
|
|
229
|
+
btn.classList.toggle(JUMP_LATEST_VISIBLE_CLASS, scrolledUp);
|
|
230
|
+
const entry = state.terms.get(id);
|
|
231
|
+
if (!entry || entry.scrolledUp === scrolledUp) return;
|
|
232
|
+
entry.scrolledUp = scrolledUp;
|
|
233
|
+
if (id === state.active) refreshTerminalInputActions();
|
|
122
234
|
}
|
|
123
235
|
|
|
124
236
|
function startBounce(container) {
|
|
@@ -233,7 +345,10 @@ async function copyTerminalSelection(sessionId) {
|
|
|
233
345
|
async function pasteIntoTerminal(sessionId) {
|
|
234
346
|
try {
|
|
235
347
|
const text = await navigator.clipboard.readText();
|
|
236
|
-
if (text)
|
|
348
|
+
if (text) {
|
|
349
|
+
trackTerminalInputData(sessionId, text);
|
|
350
|
+
send({ type: 'input', id: sessionId, data: text });
|
|
351
|
+
}
|
|
237
352
|
} catch {
|
|
238
353
|
showToast('Clipboard read failed.', { type: 'error' });
|
|
239
354
|
}
|
|
@@ -469,7 +584,10 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
469
584
|
const fit = new FitAddon.FitAddon();
|
|
470
585
|
term.loadAddon(fit);
|
|
471
586
|
addLinkProvider(term);
|
|
472
|
-
term.onData(data =>
|
|
587
|
+
term.onData(data => {
|
|
588
|
+
trackTerminalInputData(id, data);
|
|
589
|
+
send({ type: 'input', id, data });
|
|
590
|
+
});
|
|
473
591
|
|
|
474
592
|
// [TRANSCRIPT-CAPTURE] initial settled capture plus one delayed idle save
|
|
475
593
|
let _captureTimer = null, _renderSilent = false, _lastTyping = 0, _initialCaptureDone = false, _idleSaveTimer = null;
|
|
@@ -549,7 +667,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
549
667
|
term.open(el);
|
|
550
668
|
const jumpLatestBtn = createJumpLatestButton(term);
|
|
551
669
|
el.appendChild(jumpLatestBtn);
|
|
552
|
-
const refreshJumpLatest = () => updateJumpLatestButton(term, jumpLatestBtn);
|
|
670
|
+
const refreshJumpLatest = () => updateJumpLatestButton(id, term, jumpLatestBtn);
|
|
553
671
|
term.onScroll(refreshJumpLatest);
|
|
554
672
|
term.onWriteParsed(refreshJumpLatest);
|
|
555
673
|
attachToTerminal(term, presetId);
|
|
@@ -607,7 +725,8 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
607
725
|
}
|
|
608
726
|
}, 500);
|
|
609
727
|
const cancelFitRaf = () => { if (fitRaf) { cancelAnimationFrame(fitRaf); fitRaf = 0; } };
|
|
610
|
-
state.terms.set(id, { term, fit, el, ro, cancelFitRaf, onContextMenu, themeId, commandId, presetId: presetId || null, projectId: projectId || null, muted: !!muted, working: false, workStartedAt: null, stopBounce, queue: (data) => { if (!fitted) { pending.push(data); return true; } return false; }, lastActivityAt: Date.now(), unread: false, lastPreviewText: lastPreview || '', searchText: '' });
|
|
728
|
+
state.terms.set(id, { term, fit, el, ro, cancelFitRaf, onContextMenu, inputLength: 0, inputHasText: false, scrolledUp: false, themeId, commandId, presetId: presetId || null, projectId: projectId || null, muted: !!muted, working: false, workStartedAt: null, stopBounce, queue: (data) => { if (!fitted) { pending.push(data); return true; } return false; }, lastActivityAt: Date.now(), unread: false, lastPreviewText: lastPreview || '', searchText: '' });
|
|
729
|
+
refreshTerminalInputActions();
|
|
611
730
|
document.getElementById('empty').style.display = 'none';
|
|
612
731
|
document.getElementById('terminals').style.pointerEvents = '';
|
|
613
732
|
if (muted) requestAnimationFrame(() => updateMuteIndicator(id));
|
|
@@ -632,6 +751,7 @@ export function removeTerminal(id) {
|
|
|
632
751
|
if (next) select(next);
|
|
633
752
|
else {
|
|
634
753
|
state.active = null;
|
|
754
|
+
refreshTerminalInputActions();
|
|
635
755
|
document.getElementById('empty').style.display = 'flex';
|
|
636
756
|
document.getElementById('terminals').style.pointerEvents = 'none';
|
|
637
757
|
}
|
|
@@ -666,6 +786,7 @@ export function select(id) {
|
|
|
666
786
|
if (!document.querySelector('[contenteditable="true"]')) entry.term.focus();
|
|
667
787
|
}
|
|
668
788
|
state.active = id;
|
|
789
|
+
refreshTerminalInputActions();
|
|
669
790
|
localStorage.setItem('clideck.activeSessionId', id);
|
|
670
791
|
}
|
|
671
792
|
|
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-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))}}
|
|
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!important}.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
|
@@ -4,6 +4,7 @@ const { join, extname, resolve } = require('path');
|
|
|
4
4
|
const { WebSocketServer } = require('ws');
|
|
5
5
|
const { ensurePtyHelper } = require('./utils');
|
|
6
6
|
const { PORT, HOST, localUrl } = require('./runtime');
|
|
7
|
+
const { updateClaudeSessionToken } = require('./claude-session');
|
|
7
8
|
|
|
8
9
|
function terminalLink(url, text = url) {
|
|
9
10
|
return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
|
|
@@ -170,6 +171,7 @@ const server = http.createServer((req, res) => {
|
|
|
170
171
|
: null;
|
|
171
172
|
if (clideckId) {
|
|
172
173
|
const sess = allSessions.get(clideckId);
|
|
174
|
+
updateClaudeSessionToken(sess, sessionId, clideckId, { label: 'Claude', source: `hook:${route}` });
|
|
173
175
|
if (route === 'start') {
|
|
174
176
|
sessions.broadcast({ type: 'session.status', id: clideckId, working: true, source: 'hook' });
|
|
175
177
|
} else if (route === 'stop' || route === 'idle') {
|
|
@@ -290,15 +292,12 @@ const wss = new WebSocketServer({
|
|
|
290
292
|
});
|
|
291
293
|
wss.on('connection', onConnection);
|
|
292
294
|
|
|
293
|
-
const activity = require('./activity');
|
|
294
|
-
activity.start(sessions.getSessions(), sessions.broadcast);
|
|
295
295
|
sessions.startAutoSave(() => require('./handlers').getConfig());
|
|
296
296
|
|
|
297
297
|
// Graceful shutdown: persist sessions before exit
|
|
298
298
|
const { getConfig } = require('./handlers');
|
|
299
299
|
function onShutdown() {
|
|
300
300
|
plugins.shutdown();
|
|
301
|
-
activity.stop();
|
|
302
301
|
sessions.shutdown(getConfig());
|
|
303
302
|
removeLockIfOwned();
|
|
304
303
|
process.exit(0);
|
package/session-agents.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
3
|
-
res.end(JSON.stringify(payload));
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
function isLoopback(req) {
|
|
7
|
-
const addr = req.socket?.remoteAddress || '';
|
|
8
|
-
return addr === '::1' || addr === '127.0.0.1' || addr.startsWith('127.') || addr.startsWith('::ffff:127.');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function sameProject(a, b) {
|
|
12
|
-
return (a.projectId || null) === (b.projectId || null);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function projectName(projects, projectId) {
|
|
16
|
-
if (!projectId) return 'No project';
|
|
17
|
-
return projects.find(p => p.id === projectId)?.name || projectId;
|
|
18
|
-
}
|
|
1
|
+
const { sendJson, isLoopback, sameProject, projectName } = require('./http-util');
|
|
19
2
|
|
|
20
3
|
function agentRow(id, s, callerId, projects) {
|
|
21
4
|
const project = projectName(projects, s.projectId);
|
package/session-ask.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const transcript = require('./transcript');
|
|
2
|
+
const { sendJson, isLoopback, sameProject, projectName } = require('./http-util');
|
|
2
3
|
|
|
3
4
|
const MAX_BODY = 2 * 1024 * 1024;
|
|
4
5
|
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
@@ -6,11 +7,6 @@ const MAX_TIMEOUT_MS = 60 * 60 * 1000;
|
|
|
6
7
|
const BRACKETED_PASTE_START = '\x1b[200~';
|
|
7
8
|
const BRACKETED_PASTE_END = '\x1b[201~';
|
|
8
9
|
|
|
9
|
-
function sendJson(res, status, payload) {
|
|
10
|
-
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
11
|
-
res.end(JSON.stringify(payload));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
10
|
function readJson(req) {
|
|
15
11
|
return new Promise((resolve, reject) => {
|
|
16
12
|
let body = '';
|
|
@@ -35,26 +31,12 @@ function jsonError(message, status = 400) {
|
|
|
35
31
|
return err;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
function isLoopback(req) {
|
|
39
|
-
const addr = req.socket?.remoteAddress || '';
|
|
40
|
-
return addr === '::1' || addr === '127.0.0.1' || addr.startsWith('127.') || addr.startsWith('::ffff:127.');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
34
|
function normalizeTimeout(ms) {
|
|
44
35
|
const n = Number(ms || DEFAULT_TIMEOUT_MS);
|
|
45
36
|
if (!Number.isFinite(n) || n <= 0) return DEFAULT_TIMEOUT_MS;
|
|
46
37
|
return Math.min(Math.round(n), MAX_TIMEOUT_MS);
|
|
47
38
|
}
|
|
48
39
|
|
|
49
|
-
function sameProject(a, b) {
|
|
50
|
-
return (a.projectId || null) === (b.projectId || null);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function projectName(projects, projectId) {
|
|
54
|
-
if (!projectId) return 'No project';
|
|
55
|
-
return projects.find(p => p.id === projectId)?.name || projectId;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
40
|
function parseScopedTarget(target) {
|
|
59
41
|
const text = String(target || '').trim();
|
|
60
42
|
if (!text.startsWith('@')) return null;
|
package/sessions.js
CHANGED
|
@@ -8,11 +8,12 @@ const transcript = require('./transcript');
|
|
|
8
8
|
const telemetry = require('./telemetry-receiver');
|
|
9
9
|
const opencodeBridge = require('./opencode-bridge');
|
|
10
10
|
const plugins = require('./plugin-loader');
|
|
11
|
+
const { presetForCommand } = require('./preset-utils');
|
|
12
|
+
const { stripAnsi } = require('./ansi-utils');
|
|
11
13
|
|
|
12
14
|
const THEMES = require('./themes');
|
|
13
15
|
const MAX_BUFFER = 2 * 1024 * 1024;
|
|
14
16
|
const { PORT, localUrl } = require('./runtime');
|
|
15
|
-
const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b./g;
|
|
16
17
|
const PRESETS = JSON.parse(require('fs').readFileSync(join(__dirname, 'agent-presets.json'), 'utf8'));
|
|
17
18
|
for (const p of PRESETS) if (p.presetId === 'shell') p.command = defaultShell;
|
|
18
19
|
const { DATA_DIR } = require('./paths');
|
|
@@ -37,6 +38,9 @@ function broadcast(msg) {
|
|
|
37
38
|
const raw = JSON.stringify(msg);
|
|
38
39
|
for (const c of clients) if (c.readyState === 1) c.send(raw);
|
|
39
40
|
if (msg.type === 'session.status') {
|
|
41
|
+
// Status broadcasts currently also apply the local state transition. This is
|
|
42
|
+
// intentional for now but couples transport with session state; if this area
|
|
43
|
+
// changes, split it into a dedicated setSessionStatus() transition first.
|
|
40
44
|
const s = sessions.get(msg.id);
|
|
41
45
|
if (s) {
|
|
42
46
|
s.working = !!msg.working;
|
|
@@ -59,14 +63,7 @@ function broadcast(msg) {
|
|
|
59
63
|
|
|
60
64
|
// --- Spawn a PTY and wire up a session ---
|
|
61
65
|
|
|
62
|
-
function presetForCommand(cmd)
|
|
63
|
-
if (cmd?.presetId) {
|
|
64
|
-
const preset = PRESETS.find(p => p.presetId === cmd.presetId);
|
|
65
|
-
if (preset) return preset;
|
|
66
|
-
}
|
|
67
|
-
const bin = binName(cmd?.command);
|
|
68
|
-
return PRESETS.find(p => binName(p.command) === bin);
|
|
69
|
-
}
|
|
66
|
+
function matchPreset(cmd) { return presetForCommand(cmd, PRESETS); }
|
|
70
67
|
|
|
71
68
|
function commandEnv(cmd) {
|
|
72
69
|
const env = {};
|
|
@@ -78,7 +75,7 @@ function commandEnv(cmd) {
|
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
function buildTelemetryEnv(id, cmd) {
|
|
81
|
-
const preset =
|
|
78
|
+
const preset = matchPreset(cmd);
|
|
82
79
|
const telemetryEnabled = cmd.telemetryEnabled ?? (preset?.presetId === 'claude-code');
|
|
83
80
|
const env = { CLIDECK_SESSION_ID: id, CLIDECK_PORT: String(PORT), CLIDECK_URL: localUrl() };
|
|
84
81
|
if (!preset?.telemetryEnv || !telemetryEnabled) return env;
|
|
@@ -114,7 +111,7 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
|
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
const sessionIdRe = cmd.sessionIdPattern ? new RegExp(cmd.sessionIdPattern, 'i') : null;
|
|
117
|
-
const preset =
|
|
114
|
+
const preset = matchPreset(cmd);
|
|
118
115
|
const session = { name, themeId, commandId, cwd, pty: term, chunks: [], chunksSize: 0, sessionToken: savedToken || null, projectId: projectId || null, presetId: preset?.presetId || 'shell', working: undefined };
|
|
119
116
|
sessions.set(id, session);
|
|
120
117
|
transcript.setFinalizeOnIdle(id, ['claude-code', 'codex', 'gemini-cli', 'opencode', 'clideck-agent'].includes(session.presetId) ? session.presetId : null);
|
|
@@ -134,7 +131,7 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
|
|
|
134
131
|
// Capture session ID from output
|
|
135
132
|
if (sessionIdRe && !session.sessionToken) {
|
|
136
133
|
const joined = session.chunks.join('');
|
|
137
|
-
const match = joined.match(sessionIdRe) || joined
|
|
134
|
+
const match = joined.match(sessionIdRe) || stripAnsi(joined).match(sessionIdRe);
|
|
138
135
|
if (match) {
|
|
139
136
|
session.sessionToken = match[1];
|
|
140
137
|
console.log(`Session ${id.slice(0, 8)}: captured token via output regex: ${match[1].slice(0, 12)}…`);
|
|
@@ -196,12 +193,12 @@ function create(msg, ws, cfg) {
|
|
|
196
193
|
return;
|
|
197
194
|
}
|
|
198
195
|
|
|
199
|
-
const createdPresetId =
|
|
196
|
+
const createdPresetId = matchPreset(cmd)?.presetId || 'shell';
|
|
200
197
|
const installId = msg.installId || undefined;
|
|
201
198
|
broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId: createdPresetId, projectId, installId });
|
|
202
199
|
|
|
203
200
|
// Immediate setup notification if config not detected
|
|
204
|
-
const preset =
|
|
201
|
+
const preset = matchPreset(cmd);
|
|
205
202
|
if (preset && (preset.telemetrySetup || preset.bridge) && !(cmd.telemetryEnabled && cmd.telemetryStatus?.ok)) {
|
|
206
203
|
broadcast({ type: 'session.needsSetup', id });
|
|
207
204
|
}
|
|
@@ -228,7 +225,7 @@ function createProgrammatic(opts, cfg) {
|
|
|
228
225
|
const s = sessions.get(id);
|
|
229
226
|
if (s && opts.ephemeral) s.ephemeral = true;
|
|
230
227
|
|
|
231
|
-
const presetId =
|
|
228
|
+
const presetId = matchPreset(cmd)?.presetId || 'shell';
|
|
232
229
|
broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId, projectId });
|
|
233
230
|
return { id };
|
|
234
231
|
}
|
|
@@ -278,7 +275,7 @@ function resume(msg, ws, cfg) {
|
|
|
278
275
|
resumable = resumable.filter(s => s.id !== id);
|
|
279
276
|
broadcast({ type: 'sessions.resumable', list: getResumable(cfg) });
|
|
280
277
|
|
|
281
|
-
const resumePresetId =
|
|
278
|
+
const resumePresetId = matchPreset(cmd)?.presetId || saved.presetId || 'shell';
|
|
282
279
|
broadcast({ type: 'created', id, name: saved.name, themeId: saved.themeId || saved.profileId || 'default', commandId: saved.commandId, presetId: resumePresetId, projectId: saved.projectId || null, muted: !!saved.muted, resumed: true, lastPreview: saved.lastPreview || '' });
|
|
283
280
|
}
|
|
284
281
|
|
|
@@ -422,7 +419,7 @@ function getResumable(cfg) {
|
|
|
422
419
|
if (s.presetId) return s;
|
|
423
420
|
const cmd = (cfg.commands || []).find(c => c.id === s.commandId);
|
|
424
421
|
if (!cmd) return { ...s, presetId: 'shell' };
|
|
425
|
-
const preset =
|
|
422
|
+
const preset = matchPreset(cmd);
|
|
426
423
|
return { ...s, presetId: preset?.presetId || 'shell' };
|
|
427
424
|
});
|
|
428
425
|
}
|
package/telemetry-receiver.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// CLI agents export telemetry events here; we capture agent session IDs
|
|
3
3
|
// (for resume) and detect whether telemetry is configured (setup prompts).
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const { updateClaudeSessionToken } = require('./claude-session');
|
|
6
|
+
|
|
6
7
|
const activity = new Map(); // sessionId → has received events
|
|
7
8
|
const lastEvent = new Map(); // sessionId → last OTEL event name (+ kind)
|
|
8
9
|
const pendingSetup = new Map(); // sessionId → timer (waiting for first event)
|
|
@@ -10,6 +11,10 @@ const codexMenuPoll = new Map(); // sessionId → interval (polling for menu aft
|
|
|
10
11
|
const codexPendingStop = new Map(); // sessionId → ts (notify hook arrived; wait for next response.completed)
|
|
11
12
|
const codexOutputDone = new Map(); // sessionId → ts (fallback if notify never fires)
|
|
12
13
|
const codexPendingIdle = new Map(); // sessionId → timer (tiny settle before committing idle)
|
|
14
|
+
// Codex does not currently expose one reliable "fully idle" signal across streamed
|
|
15
|
+
// output, tool calls, and approval menus. This state machine is the best available
|
|
16
|
+
// approach for now; do not add more timing patches without fixture tests from real
|
|
17
|
+
// Codex event sequences.
|
|
13
18
|
const codexToolPhasePending = new Set(); // sessionId set once Codex has announced a tool-call phase, cleared when the phase resolves
|
|
14
19
|
const codexPendingTools = new Map(); // sessionId → Set(callId) for approved Codex tool calls still awaiting a result
|
|
15
20
|
let broadcastFn = null;
|
|
@@ -206,6 +211,10 @@ function handleLogs(req, res) {
|
|
|
206
211
|
? attrs['conversation.id']
|
|
207
212
|
: (attrs['session.id'] || attrs['conversation.id']);
|
|
208
213
|
if (agentSessionId && sess) {
|
|
214
|
+
if (serviceName === 'claude-code') {
|
|
215
|
+
updateClaudeSessionToken(sess, agentSessionId, resolvedId, { label: 'Telemetry' });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
209
218
|
// Prefer interactive session ID (Gemini sends non-interactive init events first)
|
|
210
219
|
const dominated = sess.sessionToken && attrs['interactive'] === true;
|
|
211
220
|
if (!sess.sessionToken || dominated) {
|
|
@@ -274,14 +283,6 @@ function markCodexStart(id, source = 'hook') {
|
|
|
274
283
|
broadcastFn?.({ type: 'session.status', id, working: true, source });
|
|
275
284
|
}
|
|
276
285
|
|
|
277
|
-
function markCodexIdle(id, source = 'hook') {
|
|
278
|
-
codexPendingStop.delete(id);
|
|
279
|
-
codexOutputDone.delete(id);
|
|
280
|
-
codexToolPhasePending.delete(id);
|
|
281
|
-
clearPendingTools(id);
|
|
282
|
-
scheduleCodexIdle(id, source);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
286
|
function scheduleCodexIdle(id, source) {
|
|
286
287
|
cancelCodexPendingIdle(id);
|
|
287
288
|
const timer = setTimeout(() => {
|
|
@@ -311,9 +312,4 @@ function clear(id) {
|
|
|
311
312
|
|
|
312
313
|
function getLastEvent(id) { return lastEvent.get(id) || ''; }
|
|
313
314
|
|
|
314
|
-
|
|
315
|
-
function hasEvents(id) {
|
|
316
|
-
return activity.has(id);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
module.exports = { init, handleLogs, clear, hasEvents, getLastEvent, cancelCodexMenuPoll, watchSession, armCodexStop, markCodexStart, markCodexIdle };
|
|
315
|
+
module.exports = { init, handleLogs, clear, getLastEvent, cancelCodexMenuPoll, watchSession, armCodexStop, markCodexStart };
|
package/transcript.js
CHANGED
|
@@ -4,9 +4,9 @@ const { DATA_DIR } = require('./paths');
|
|
|
4
4
|
const builder = require('./transcript-normalizer');
|
|
5
5
|
const parser = require('./transcript-parser');
|
|
6
6
|
const candidate = require('./transcript-candidate');
|
|
7
|
+
const { stripAnsi } = require('./ansi-utils');
|
|
7
8
|
|
|
8
9
|
const DIR = join(DATA_DIR, 'transcripts');
|
|
9
|
-
const ANSI_RE = /\x1b[\[\]()#;?]*[0-9;]*[a-zA-Z@`~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b.|\r|\x07/g;
|
|
10
10
|
const MAX_CACHE = 50 * 1024;
|
|
11
11
|
const LEGACY_SUFFIXES = ['-parsed.jsonl', '.screen'];
|
|
12
12
|
|
|
@@ -153,7 +153,7 @@ function trackOutput(id, data) {
|
|
|
153
153
|
function flush(id) {
|
|
154
154
|
const buf = outputBuf[id];
|
|
155
155
|
if (!buf?.text) return;
|
|
156
|
-
const clean = buf.text
|
|
156
|
+
const clean = stripAnsi(buf.text).replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
157
157
|
const lines = clean.split('\n').map(l => l.trim()).filter(l => l.length > 2);
|
|
158
158
|
buf.text = '';
|
|
159
159
|
if (lines.length) store(id, 'agent', lines.join('\n'));
|