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 CHANGED
@@ -1,77 +1,22 @@
1
- // Per-session I/O tracking and burst detection.
2
- // Broadcasts rate + burst data so the frontend can derive working/idle status.
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, prevIn: 0, prevOut: 0 };
10
- if (!stream[id]) stream[id] = { lastOutAt: 0, burstStart: 0 };
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
- net[id].in += bytes;
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
- const now = Date.now();
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 start(sessions, broadcast) {
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 = { start, stop, trackIn, trackOut, isActive, lastOutputAt, lastInputAt, lastChunk, clear };
22
+ module.exports = { trackIn, trackOut, clear };
package/ansi-utils.js ADDED
@@ -0,0 +1,7 @@
1
+ const ANSI_RE = /\x1b[\[\]()#;?]*[0-9;?]*[ -/]*[@-~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b.|\r|\x07/g;
2
+
3
+ function stripAnsi(value) {
4
+ return String(value || '').replace(ANSI_RE, '');
5
+ }
6
+
7
+ module.exports = { ANSI_RE, stripAnsi };
@@ -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, binName } = require('./utils');
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
- if (cmd?.presetId) {
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 commandEnv(cmd) {
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 = commandEnv(cmd);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.31.15",
3
+ "version": "1.31.17",
4
4
  "description": "One screen for all your AI coding agents — run, monitor, and manage multiple CLI agents from a single browser tab",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "voice-input",
3
3
  "name": "Voice Input",
4
- "version": "1.2.1",
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 btnEl = null;
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 (!btnEl) return;
43
- if (recordingState) {
44
- btnEl.style.color = '#ef4444';
45
- btnEl.title = 'Stop recording (or press ' + (settings.hotkey || 'F4') + ')';
46
- } else {
47
- btnEl.style.color = '';
48
- btnEl.title = 'Voice Input (' + (settings.hotkey || 'F4') + ')';
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
- if (btnEl) btnEl.style.display = settings.enabled ? '' : 'none';
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
- btnEl = api.addToolbarButton({
194
+ micControl = api.addTerminalInputButton({
195
+ id: 'voice-input',
184
196
  title: 'Voice Input (F4)',
185
- 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 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"/></svg>',
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
  }
@@ -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
- remoteInstallMode = { update: !!opts.update, auto: !!opts.auto };
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
 
@@ -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,
@@ -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
- btn.classList.toggle(JUMP_LATEST_VISIBLE_CLASS, shouldShowJumpLatest(term));
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) send({ type: 'input', id: sessionId, data: 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 => send({ type: 'input', id, 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
 
@@ -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
- 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
- }
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 = presetForCommand(cmd);
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 = presetForCommand(cmd);
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.replace(ANSI_RE, '').match(sessionIdRe);
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 = presetForCommand(cmd)?.presetId || 'shell';
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 = presetForCommand(cmd);
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 = presetForCommand(cmd)?.presetId || 'shell';
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 = presetForCommand(cmd)?.presetId || saved.presetId || 'shell';
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 = presetForCommand(cmd);
422
+ const preset = matchPreset(cmd);
426
423
  return { ...s, presetId: preset?.presetId || 'shell' };
427
424
  });
428
425
  }
@@ -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 ioActivity = require('./activity');
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
- // Returns true if we've received telemetry events for this session
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.replace(ANSI_RE, '').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
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'));