clideck 1.31.4 → 1.31.6
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/codex-hooks.js +7 -7
- package/config.js +3 -0
- package/handlers.js +71 -40
- package/package.json +1 -1
- package/public/js/app.js +27 -10
- package/public/js/creator.js +44 -23
- package/public/js/settings.js +113 -24
- package/public/js/terminals.js +44 -0
- package/public/tailwind.css +1 -1
- package/sessions.js +34 -17
- package/transcript.js +5 -1
package/codex-hooks.js
CHANGED
|
@@ -18,7 +18,7 @@ function readHooksFile(path) {
|
|
|
18
18
|
if (!existsSync(path)) return { hooks: {} };
|
|
19
19
|
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
20
20
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
21
|
-
throw new Error('Invalid
|
|
21
|
+
throw new Error('Invalid Codex hooks.json shape');
|
|
22
22
|
}
|
|
23
23
|
if (!parsed.hooks || typeof parsed.hooks !== 'object' || Array.isArray(parsed.hooks)) parsed.hooks = {};
|
|
24
24
|
return parsed;
|
|
@@ -33,8 +33,8 @@ function stripClideckHooks(groups) {
|
|
|
33
33
|
.filter(group => group.hooks.length);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function installCodexHooks(
|
|
37
|
-
const hooksPath = join(
|
|
36
|
+
function installCodexHooks(codexHome, nodePath, helperPath, port) {
|
|
37
|
+
const hooksPath = join(codexHome, 'hooks.json');
|
|
38
38
|
const doc = readHooksFile(hooksPath);
|
|
39
39
|
|
|
40
40
|
for (const [event, route] of Object.entries(EVENTS)) {
|
|
@@ -53,8 +53,8 @@ function installCodexHooks(home, nodePath, helperPath, port) {
|
|
|
53
53
|
writeFileSync(hooksPath, JSON.stringify(doc, null, 2) + '\n');
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function removeCodexHooks(
|
|
57
|
-
const hooksPath = join(
|
|
56
|
+
function removeCodexHooks(codexHome) {
|
|
57
|
+
const hooksPath = join(codexHome, 'hooks.json');
|
|
58
58
|
if (!existsSync(hooksPath)) return;
|
|
59
59
|
const doc = readHooksFile(hooksPath);
|
|
60
60
|
|
|
@@ -73,9 +73,9 @@ function removeCodexHooks(home) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function codexHooksHealthy(
|
|
76
|
+
function codexHooksHealthy(codexHome, helperPath, port) {
|
|
77
77
|
try {
|
|
78
|
-
const doc = readHooksFile(join(
|
|
78
|
+
const doc = readHooksFile(join(codexHome, 'hooks.json'));
|
|
79
79
|
for (const [event, route] of Object.entries(EVENTS)) {
|
|
80
80
|
const groups = Array.isArray(doc.hooks?.[event]) ? doc.hooks[event] : [];
|
|
81
81
|
const expected = commandFor(process.execPath.replace(/\\/g, '/'), helperPath, port, route);
|
package/config.js
CHANGED
|
@@ -38,6 +38,7 @@ const DEFAULTS = {
|
|
|
38
38
|
{
|
|
39
39
|
id: '1', label: 'Shell', icon: 'terminal', command: defaultShell, enabled: true,
|
|
40
40
|
defaultPath: '', isAgent: false, canResume: false, resumeCommand: null, sessionIdPattern: null,
|
|
41
|
+
env: {},
|
|
41
42
|
},
|
|
42
43
|
],
|
|
43
44
|
confirmClose: true,
|
|
@@ -114,6 +115,7 @@ function migrate(cfg) {
|
|
|
114
115
|
if (cmd.resumeCommand === undefined) cmd.resumeCommand = preset?.resumeCommand || null;
|
|
115
116
|
if (cmd.sessionIdPattern === undefined) cmd.sessionIdPattern = preset?.sessionIdPattern || null;
|
|
116
117
|
if (cmd.outputMarker === undefined) cmd.outputMarker = preset?.outputMarker || null;
|
|
118
|
+
if (!cmd.env || typeof cmd.env !== 'object' || Array.isArray(cmd.env)) cmd.env = {};
|
|
117
119
|
// Claude Code telemetry is built-in, always on
|
|
118
120
|
if (preset?.telemetryEnabled === true) cmd.telemetryEnabled = true;
|
|
119
121
|
else if (preset?.presetId === 'claude-code') cmd.telemetryEnabled = true;
|
|
@@ -140,6 +142,7 @@ function migrate(cfg) {
|
|
|
140
142
|
isAgent: preset.isAgent, canResume: preset.canResume,
|
|
141
143
|
resumeCommand: preset.resumeCommand, sessionIdPattern: preset.sessionIdPattern,
|
|
142
144
|
outputMarker: preset.outputMarker || null,
|
|
145
|
+
env: {},
|
|
143
146
|
});
|
|
144
147
|
}
|
|
145
148
|
}
|
package/handlers.js
CHANGED
|
@@ -19,7 +19,8 @@ function clientPresets() {
|
|
|
19
19
|
}
|
|
20
20
|
function filterClientCommands(commands) {
|
|
21
21
|
const allowedPresetIds = new Set(clientPresets().map(p => p.presetId));
|
|
22
|
-
|
|
22
|
+
const knownPresetIds = new Set(presets.map(p => p.presetId));
|
|
23
|
+
return (commands || []).filter(cmd => !cmd.presetId || allowedPresetIds.has(cmd.presetId) || !knownPresetIds.has(cmd.presetId));
|
|
23
24
|
}
|
|
24
25
|
const transcript = require('./transcript');
|
|
25
26
|
const plugins = require('./plugin-loader');
|
|
@@ -70,6 +71,35 @@ function getInstalledVersion(bin) {
|
|
|
70
71
|
return '';
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
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);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function commandEnv(cmd) {
|
|
84
|
+
return cmd?.env && typeof cmd.env === 'object' && !Array.isArray(cmd.env) ? cmd.env : {};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function expandHomePath(value) {
|
|
88
|
+
const text = String(value || '').trim();
|
|
89
|
+
if (!text) return '';
|
|
90
|
+
if (text === '~') return os.homedir();
|
|
91
|
+
if (text.startsWith('~/') || text.startsWith('~\\')) return join(os.homedir(), text.slice(2));
|
|
92
|
+
return text;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function configRootFor(preset, cmd) {
|
|
96
|
+
const env = commandEnv(cmd);
|
|
97
|
+
if (preset?.presetId === 'claude-code') return expandHomePath(env.CLAUDE_CONFIG_DIR) || join(os.homedir(), '.claude');
|
|
98
|
+
if (preset?.presetId === 'codex') return expandHomePath(env.CODEX_HOME) || join(os.homedir(), '.codex');
|
|
99
|
+
if (preset?.presetId === 'gemini-cli') return join(expandHomePath(env.GEMINI_CLI_HOME) || os.homedir(), '.gemini');
|
|
100
|
+
return os.homedir();
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
function checkRemoteUpdate(ws) {
|
|
74
104
|
const now = Date.now();
|
|
75
105
|
if (remoteUpdateCache && now - remoteUpdateCheckedAt < REMOTE_UPDATE_INTERVAL) {
|
|
@@ -154,11 +184,11 @@ function codexHooksFeatureEnabled(content) {
|
|
|
154
184
|
return false;
|
|
155
185
|
}
|
|
156
186
|
|
|
157
|
-
function codexConfigLooksHealthy(content, port) {
|
|
187
|
+
function codexConfigLooksHealthy(content, port, codexHome) {
|
|
158
188
|
if (!content.includes('[otel]') || !content.includes(`localhost:${port}`)) return false;
|
|
159
189
|
const codexHookPath = join(__dirname, 'bin', 'codex-hook.js').replace(/\\/g, '/');
|
|
160
190
|
if (!codexHooksFeatureEnabled(content)) return false;
|
|
161
|
-
if (!codexHooksHealthy(
|
|
191
|
+
if (!codexHooksHealthy(codexHome, codexHookPath, port)) return false;
|
|
162
192
|
const notifyLine = content.match(/^\s*notify\s*=\s*\[(.+)\]\s*$/m)?.[1] || '';
|
|
163
193
|
if (!notifyLine.includes('notify-helper')) return false;
|
|
164
194
|
const quoted = [...notifyLine.matchAll(/"([^"]+)"/g)].map(m => m[1]);
|
|
@@ -167,7 +197,6 @@ function codexConfigLooksHealthy(content, port) {
|
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
function detectTelemetryConfig(c) {
|
|
170
|
-
const home = os.homedir();
|
|
171
200
|
const port = String(PORT);
|
|
172
201
|
let changed = false;
|
|
173
202
|
const attemptedRepairs = new Set();
|
|
@@ -175,14 +204,13 @@ function detectTelemetryConfig(c) {
|
|
|
175
204
|
for (let pass = 0; pass < 2; pass++) {
|
|
176
205
|
let repairedAny = false;
|
|
177
206
|
for (const cmd of c.commands || []) {
|
|
178
|
-
const
|
|
179
|
-
const preset = presets.find(p => binName(p.command) === bin);
|
|
207
|
+
const preset = presetForCommand(cmd);
|
|
180
208
|
if (!preset) continue;
|
|
181
209
|
let detected = false;
|
|
182
210
|
let reason = '';
|
|
183
211
|
if (preset.presetId === 'claude-code') {
|
|
184
212
|
try {
|
|
185
|
-
const s = JSON.parse(readFileSync(join(
|
|
213
|
+
const s = JSON.parse(readFileSync(join(configRootFor(preset, cmd), 'settings.json'), 'utf8'));
|
|
186
214
|
const hooks = s.hooks || {};
|
|
187
215
|
detected = hasExistingHook(hooks.UserPromptSubmit, 'claude-hook.js', 'start')
|
|
188
216
|
&& hasExistingHook(hooks.Stop, 'claude-hook.js', 'stop')
|
|
@@ -193,13 +221,14 @@ function detectTelemetryConfig(c) {
|
|
|
193
221
|
} catch {}
|
|
194
222
|
} else if (preset.presetId === 'codex') {
|
|
195
223
|
try {
|
|
196
|
-
const
|
|
197
|
-
|
|
224
|
+
const codexHome = configRootFor(preset, cmd);
|
|
225
|
+
const content = readFileSync(join(codexHome, 'config.toml'), 'utf8');
|
|
226
|
+
detected = codexConfigLooksHealthy(content, port, codexHome);
|
|
198
227
|
if (!detected) reason = 'Needs re-patch';
|
|
199
228
|
} catch {}
|
|
200
229
|
} else if (preset.presetId === 'gemini-cli') {
|
|
201
230
|
try {
|
|
202
|
-
const s = JSON.parse(readFileSync(join(
|
|
231
|
+
const s = JSON.parse(readFileSync(join(configRootFor(preset, cmd), 'settings.json'), 'utf8'));
|
|
203
232
|
const hooks = s.hooks || {};
|
|
204
233
|
detected = hasExistingHook(hooks.BeforeAgent, 'gemini-hook.js', 'start')
|
|
205
234
|
&& hasExistingHook(hooks.AfterAgent, 'gemini-hook.js', 'stop')
|
|
@@ -214,9 +243,9 @@ function detectTelemetryConfig(c) {
|
|
|
214
243
|
if (preset.available && preset.minVersion && !preset.versionOk) {
|
|
215
244
|
detected = false;
|
|
216
245
|
reason = `Update required (${preset.minVersion}+)`;
|
|
217
|
-
} else if (!detected && cmd.telemetryEnabled && preset.telemetryAutoSetup && preset.available && preset.versionOk && !attemptedRepairs.has(preset.presetId)) {
|
|
218
|
-
attemptedRepairs.add(preset.presetId);
|
|
219
|
-
const repaired = applyTelemetryConfig(preset);
|
|
246
|
+
} else if (!detected && cmd.telemetryEnabled && preset.telemetryAutoSetup && preset.available && preset.versionOk && !attemptedRepairs.has(cmd.id || preset.presetId)) {
|
|
247
|
+
attemptedRepairs.add(cmd.id || preset.presetId);
|
|
248
|
+
const repaired = applyTelemetryConfig(preset, cmd);
|
|
220
249
|
if (repaired.success) {
|
|
221
250
|
repairedAny = true;
|
|
222
251
|
continue;
|
|
@@ -360,11 +389,12 @@ function onConnection(ws) {
|
|
|
360
389
|
}
|
|
361
390
|
|
|
362
391
|
case 'telemetry.autosetup': {
|
|
363
|
-
const
|
|
392
|
+
const targetCmd = msg.commandId ? cfg.commands.find(c => c.id === msg.commandId) : null;
|
|
393
|
+
const preset = targetCmd ? presetForCommand(targetCmd) : presets.find(p => p.presetId === msg.presetId);
|
|
364
394
|
if (!preset?.telemetryAutoSetup) break;
|
|
365
|
-
const result = applyTelemetryConfig(preset);
|
|
395
|
+
const result = applyTelemetryConfig(preset, targetCmd);
|
|
366
396
|
for (const cmd of cfg.commands) {
|
|
367
|
-
if (
|
|
397
|
+
if (targetCmd ? cmd.id === targetCmd.id : presetForCommand(cmd)?.presetId === preset.presetId) {
|
|
368
398
|
cmd.telemetryEnabled = result.success;
|
|
369
399
|
cmd.telemetryStatus = result.success ? { ok: true } : { ok: false, error: result.message };
|
|
370
400
|
// Enable the agent when setup succeeds, disable if it fails
|
|
@@ -375,7 +405,8 @@ function onConnection(ws) {
|
|
|
375
405
|
sessions.broadcast({ type: 'config', config: configForClient() });
|
|
376
406
|
ws.send(JSON.stringify({
|
|
377
407
|
type: 'telemetry.autosetup.result',
|
|
378
|
-
presetId:
|
|
408
|
+
presetId: preset.presetId,
|
|
409
|
+
commandId: msg.commandId || null,
|
|
379
410
|
success: result.success,
|
|
380
411
|
output: result.message,
|
|
381
412
|
}));
|
|
@@ -383,18 +414,19 @@ function onConnection(ws) {
|
|
|
383
414
|
}
|
|
384
415
|
|
|
385
416
|
case 'telemetry.configure': {
|
|
386
|
-
const
|
|
417
|
+
const targetCmd = msg.commandId ? cfg.commands.find(c => c.id === msg.commandId) : null;
|
|
418
|
+
const preset = targetCmd ? presetForCommand(targetCmd) : presets.find(p => p.presetId === msg.presetId);
|
|
387
419
|
if (!preset) break;
|
|
388
420
|
const enable = !!msg.enable;
|
|
389
421
|
let result;
|
|
390
422
|
if (enable) {
|
|
391
|
-
result = applyTelemetryConfig(preset);
|
|
423
|
+
result = applyTelemetryConfig(preset, targetCmd);
|
|
392
424
|
} else {
|
|
393
|
-
result = removeTelemetryConfig(preset);
|
|
425
|
+
result = removeTelemetryConfig(preset, targetCmd);
|
|
394
426
|
}
|
|
395
427
|
// Update all matching commands in config
|
|
396
428
|
for (const cmd of cfg.commands) {
|
|
397
|
-
if (
|
|
429
|
+
if (targetCmd ? cmd.id === targetCmd.id : presetForCommand(cmd)?.presetId === preset.presetId) {
|
|
398
430
|
cmd.telemetryEnabled = enable && result.success;
|
|
399
431
|
cmd.telemetryStatus = enable
|
|
400
432
|
? (result.success ? { ok: true } : { ok: false, error: result.message })
|
|
@@ -576,13 +608,12 @@ function onConnection(ws) {
|
|
|
576
608
|
}
|
|
577
609
|
|
|
578
610
|
// Deterministic telemetry config writers per agent — no AI, no YOLO
|
|
579
|
-
function applyTelemetryConfig(preset) {
|
|
611
|
+
function applyTelemetryConfig(preset, cmd = null) {
|
|
580
612
|
const port = String(PORT);
|
|
581
|
-
const home = os.homedir();
|
|
582
613
|
|
|
583
614
|
try {
|
|
584
615
|
if (preset.presetId === 'claude-code') {
|
|
585
|
-
const configPath = join(
|
|
616
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
586
617
|
let settings = {};
|
|
587
618
|
if (existsSync(configPath)) {
|
|
588
619
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -608,11 +639,12 @@ function applyTelemetryConfig(preset) {
|
|
|
608
639
|
settings.hooks = hooks;
|
|
609
640
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
610
641
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
611
|
-
return { success: true, message:
|
|
642
|
+
return { success: true, message: `Added hooks to ${configPath} — Claude will ask for one-time approval` };
|
|
612
643
|
}
|
|
613
644
|
|
|
614
645
|
if (preset.presetId === 'codex') {
|
|
615
|
-
const
|
|
646
|
+
const codexHome = configRootFor(preset, cmd);
|
|
647
|
+
const configPath = join(codexHome, 'config.toml');
|
|
616
648
|
let content = '';
|
|
617
649
|
if (existsSync(configPath)) content = readFileSync(configPath, 'utf8');
|
|
618
650
|
const hasOtel = content.includes('[otel]');
|
|
@@ -620,7 +652,7 @@ function applyTelemetryConfig(preset) {
|
|
|
620
652
|
const hasNotify = /^\s*notify\s*=.*notify-helper/m.test(content);
|
|
621
653
|
const hasWrongOtel = content.includes(`endpoint = "http://localhost:${port}/v1/logs"`);
|
|
622
654
|
const codexHookPath = join(__dirname, 'bin', 'codex-hook.js').replace(/\\/g, '/');
|
|
623
|
-
const hasHooks = codexHooksFeatureEnabled(content) && codexHooksHealthy(
|
|
655
|
+
const hasHooks = codexHooksFeatureEnabled(content) && codexHooksHealthy(codexHome, codexHookPath, port);
|
|
624
656
|
if (hasOtel && hasCurrentOtel && hasNotify && !hasWrongOtel && hasHooks) {
|
|
625
657
|
return { success: true, message: 'Already configured' };
|
|
626
658
|
}
|
|
@@ -630,12 +662,12 @@ function applyTelemetryConfig(preset) {
|
|
|
630
662
|
if (!valid.ok) return { success: false, message: valid.error };
|
|
631
663
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
632
664
|
writeFileSync(configPath, nextContent);
|
|
633
|
-
installCodexHooks(
|
|
665
|
+
installCodexHooks(codexHome, process.execPath.replace(/\\/g, '/'), codexHookPath, port);
|
|
634
666
|
return { success: true, message: 'Configured. If Codex shows "2 hooks need review", open /hooks and approve the CliDeck hooks once.' };
|
|
635
667
|
}
|
|
636
668
|
|
|
637
669
|
if (preset.presetId === 'gemini-cli') {
|
|
638
|
-
const configPath = join(
|
|
670
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
639
671
|
let settings = {};
|
|
640
672
|
if (existsSync(configPath)) {
|
|
641
673
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -665,7 +697,7 @@ function applyTelemetryConfig(preset) {
|
|
|
665
697
|
if (settings.telemetry?.target === 'local' && /localhost:\d+/.test(String(settings.telemetry?.otlpEndpoint || ''))) delete settings.telemetry;
|
|
666
698
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
667
699
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
668
|
-
return { success: true, message:
|
|
700
|
+
return { success: true, message: `Added CliDeck hooks to ${configPath}` };
|
|
669
701
|
}
|
|
670
702
|
|
|
671
703
|
if (preset.presetId === 'opencode') {
|
|
@@ -684,12 +716,10 @@ function applyTelemetryConfig(preset) {
|
|
|
684
716
|
}
|
|
685
717
|
}
|
|
686
718
|
|
|
687
|
-
function removeTelemetryConfig(preset) {
|
|
688
|
-
const home = os.homedir();
|
|
689
|
-
|
|
719
|
+
function removeTelemetryConfig(preset, cmd = null) {
|
|
690
720
|
try {
|
|
691
721
|
if (preset.presetId === 'claude-code') {
|
|
692
|
-
const configPath = join(
|
|
722
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
693
723
|
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
694
724
|
let settings = {};
|
|
695
725
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -702,23 +732,24 @@ function removeTelemetryConfig(preset) {
|
|
|
702
732
|
}
|
|
703
733
|
if (!Object.keys(settings.hooks).length) delete settings.hooks;
|
|
704
734
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
705
|
-
return { success: true, message:
|
|
735
|
+
return { success: true, message: `Removed CliDeck hooks from ${configPath}` };
|
|
706
736
|
}
|
|
707
737
|
|
|
708
738
|
if (preset.presetId === 'codex') {
|
|
709
|
-
const
|
|
739
|
+
const codexHome = configRootFor(preset, cmd);
|
|
740
|
+
const configPath = join(codexHome, 'config.toml');
|
|
710
741
|
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
711
742
|
let content = readFileSync(configPath, 'utf8');
|
|
712
743
|
content = content.replace(/\n?\[otel\][^\[]*/, '');
|
|
713
744
|
content = content.replace(/\n?notify\s*=\s*\[.*?notify-helper.*?\]\s*/g, '');
|
|
714
745
|
content = content.replace(/\n?codex_hooks\s*=\s*(true|false)\s*/g, '\n');
|
|
715
746
|
writeFileSync(configPath, content.trimEnd() + '\n');
|
|
716
|
-
removeCodexHooks(
|
|
717
|
-
return { success: true, message:
|
|
747
|
+
removeCodexHooks(codexHome);
|
|
748
|
+
return { success: true, message: `Removed otel + CliDeck hooks from ${configPath}` };
|
|
718
749
|
}
|
|
719
750
|
|
|
720
751
|
if (preset.presetId === 'gemini-cli') {
|
|
721
|
-
const configPath = join(
|
|
752
|
+
const configPath = join(configRootFor(preset, cmd), 'settings.json');
|
|
722
753
|
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
723
754
|
let settings = {};
|
|
724
755
|
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
@@ -731,7 +762,7 @@ function removeTelemetryConfig(preset) {
|
|
|
731
762
|
if (settings.hooks && !Object.keys(settings.hooks).length) delete settings.hooks;
|
|
732
763
|
if (settings.telemetry?.target === 'local' && /localhost:\d+/.test(String(settings.telemetry?.otlpEndpoint || ''))) delete settings.telemetry;
|
|
733
764
|
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
734
|
-
return { success: true, message:
|
|
765
|
+
return { success: true, message: `Removed CliDeck hooks from ${configPath}` };
|
|
735
766
|
}
|
|
736
767
|
|
|
737
768
|
if (preset.presetId === 'opencode') {
|
package/package.json
CHANGED
package/public/js/app.js
CHANGED
|
@@ -16,6 +16,19 @@ import { renderPrompts } from './prompts.js';
|
|
|
16
16
|
const shownAgentHealthToasts = new Set();
|
|
17
17
|
let reconnectReplaySkip = null;
|
|
18
18
|
|
|
19
|
+
function normalizeTerminalHistoryText(text) {
|
|
20
|
+
return `${text || ''}\n`.replace(/\r?\n/g, '\r\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function presetForCommand(cmd) {
|
|
24
|
+
if (cmd?.presetId) {
|
|
25
|
+
const byId = state.presets.find(p => p.presetId === cmd.presetId);
|
|
26
|
+
if (byId) return byId;
|
|
27
|
+
}
|
|
28
|
+
const bin = binName(cmd?.command);
|
|
29
|
+
return state.presets.find(p => binName(p.command) === bin);
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
function connect() {
|
|
20
33
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
21
34
|
state.ws = new WebSocket(`${wsProtocol}//${location.host}`);
|
|
@@ -67,7 +80,9 @@ function connect() {
|
|
|
67
80
|
}
|
|
68
81
|
msg.list.forEach(s => addTerminal(s.id, s.name, s.themeId, s.commandId, s.projectId, s.muted, s.lastPreview, s.presetId));
|
|
69
82
|
if (!state.active || !state.terms.has(state.active)) {
|
|
70
|
-
|
|
83
|
+
const savedActive = localStorage.getItem('clideck.activeSessionId');
|
|
84
|
+
const nextId = savedActive && liveIds.has(savedActive) ? savedActive : msg.list[0]?.id;
|
|
85
|
+
if (nextId) select(nextId);
|
|
71
86
|
}
|
|
72
87
|
}
|
|
73
88
|
break;
|
|
@@ -110,7 +125,8 @@ function connect() {
|
|
|
110
125
|
case 'session.history': {
|
|
111
126
|
const entry = state.terms.get(msg.id);
|
|
112
127
|
if (msg.replay && reconnectReplaySkip?.has(msg.id) && entry) break;
|
|
113
|
-
|
|
128
|
+
const historyText = normalizeTerminalHistoryText(msg.text);
|
|
129
|
+
if (entry && !entry.queue(historyText)) entry.term.write(historyText);
|
|
114
130
|
updatePreview(msg.id);
|
|
115
131
|
break;
|
|
116
132
|
}
|
|
@@ -209,7 +225,7 @@ function connect() {
|
|
|
209
225
|
break;
|
|
210
226
|
}
|
|
211
227
|
case 'telemetry.autosetup.result': {
|
|
212
|
-
const toast = document.querySelector(`[data-setup-preset="${msg.presetId}"]`);
|
|
228
|
+
const toast = document.querySelector(msg.commandId ? `[data-command-id="${msg.commandId}"]` : `[data-setup-preset="${msg.presetId}"]`);
|
|
213
229
|
if (!toast) break;
|
|
214
230
|
const actionsEl = toast.querySelector('.setup-actions');
|
|
215
231
|
if (msg.success) {
|
|
@@ -240,7 +256,7 @@ function connect() {
|
|
|
240
256
|
toast.remove();
|
|
241
257
|
};
|
|
242
258
|
} else {
|
|
243
|
-
shownSetup.delete(msg.presetId);
|
|
259
|
+
shownSetup.delete(msg.commandId || msg.presetId);
|
|
244
260
|
const btn = toast.querySelector('.auto-setup-btn');
|
|
245
261
|
btn.textContent = 'Failed — configure manually';
|
|
246
262
|
btn.className = 'auto-setup-btn flex-1 px-3 py-2 text-xs font-medium bg-red-600/20 text-red-400 border border-red-500/30 rounded-lg cursor-default';
|
|
@@ -469,11 +485,12 @@ function showTelemetrySetup(commandId, sessionId) {
|
|
|
469
485
|
if (!cmd) return;
|
|
470
486
|
// Skip if telemetry is already configured via settings
|
|
471
487
|
if (cmd.telemetryEnabled && cmd.telemetryStatus?.ok) return;
|
|
472
|
-
const
|
|
473
|
-
|
|
488
|
+
const preset = presetForCommand(cmd);
|
|
489
|
+
if (!preset) return;
|
|
474
490
|
const setupRaw = preset.telemetrySetup || preset.pluginSetup;
|
|
475
|
-
|
|
476
|
-
shownSetup.
|
|
491
|
+
const setupKey = commandId || preset.presetId;
|
|
492
|
+
if (!setupRaw || shownSetup.has(setupKey)) return;
|
|
493
|
+
shownSetup.add(setupKey);
|
|
477
494
|
|
|
478
495
|
const port = location.port || '4000';
|
|
479
496
|
const setupText = setupRaw.replace(/\{\{port\}\}/g, port);
|
|
@@ -512,7 +529,7 @@ function showTelemetrySetup(commandId, sessionId) {
|
|
|
512
529
|
</div>`;
|
|
513
530
|
|
|
514
531
|
toast.querySelectorAll('.dismiss-btn').forEach(b => b.onclick = () => {
|
|
515
|
-
shownSetup.delete(
|
|
532
|
+
shownSetup.delete(setupKey);
|
|
516
533
|
toast.remove();
|
|
517
534
|
});
|
|
518
535
|
|
|
@@ -522,7 +539,7 @@ function showTelemetrySetup(commandId, sessionId) {
|
|
|
522
539
|
autoBtn.disabled = true;
|
|
523
540
|
autoBtn.innerHTML = `<svg class="w-3.5 h-3.5 inline animate-spin -mt-px mr-1.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 2a10 10 0 0 1 10 10"/></svg>Configuring…`;
|
|
524
541
|
autoBtn.className = 'auto-setup-btn flex-1 px-3 py-2 text-xs font-medium bg-slate-700 text-slate-300 rounded-lg cursor-wait';
|
|
525
|
-
send({ type: 'telemetry.autosetup', presetId: preset.presetId });
|
|
542
|
+
send({ type: 'telemetry.autosetup', presetId: preset.presetId, commandId });
|
|
526
543
|
};
|
|
527
544
|
}
|
|
528
545
|
|
package/public/js/creator.js
CHANGED
|
@@ -27,6 +27,15 @@ function findCommandForPreset(p) {
|
|
|
27
27
|
|| state.cfg.commands.find(c => binName(c.command) === binName(p.command));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function presetForCommand(cmd) {
|
|
31
|
+
if (cmd?.presetId) {
|
|
32
|
+
const byId = state.presets.find(p => p.presetId === cmd.presetId);
|
|
33
|
+
if (byId) return byId;
|
|
34
|
+
}
|
|
35
|
+
const bin = binName(cmd?.command);
|
|
36
|
+
return state.presets.find(p => binName(p.command) === bin);
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
function telemetryEnabledForPreset(preset, existing) {
|
|
31
40
|
if (preset?.telemetryEnabled === true) return true;
|
|
32
41
|
return !!existing?.telemetryEnabled;
|
|
@@ -53,8 +62,10 @@ function isPresetUnpatched(p) {
|
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
function renderPresetButtons() {
|
|
56
|
-
return sortedPresets().map(
|
|
57
|
-
|
|
65
|
+
return sortedPresets().map(item => {
|
|
66
|
+
const p = item.preset;
|
|
67
|
+
const cmd = item.command;
|
|
68
|
+
if (item.type === 'preset' && isPresetMissing(p)) {
|
|
58
69
|
return `
|
|
59
70
|
<div class="w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-left text-slate-500">
|
|
60
71
|
<span class="opacity-40">${agentIcon(p.icon, 24)}</span>
|
|
@@ -62,7 +73,7 @@ function renderPresetButtons() {
|
|
|
62
73
|
<button class="install-btn px-2.5 py-1 text-[11px] font-medium text-blue-400 hover:text-blue-300 bg-blue-500/10 hover:bg-blue-500/20 rounded-md transition-colors" data-preset="${p.presetId}">Add</button>
|
|
63
74
|
</div>`;
|
|
64
75
|
}
|
|
65
|
-
if (isPresetOutdated(p)) {
|
|
76
|
+
if (item.type === 'preset' && isPresetOutdated(p)) {
|
|
66
77
|
return `
|
|
67
78
|
<div class="w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-left text-slate-500">
|
|
68
79
|
<span class="opacity-40">${agentIcon(p.icon, 24)}</span>
|
|
@@ -70,38 +81,44 @@ function renderPresetButtons() {
|
|
|
70
81
|
<button class="install-btn px-2.5 py-1 text-[11px] font-medium text-rose-400 hover:text-rose-300 bg-rose-500/10 hover:bg-rose-500/20 rounded-md transition-colors" data-preset="${p.presetId}">Update</button>
|
|
71
82
|
</div>`;
|
|
72
83
|
}
|
|
73
|
-
if (
|
|
84
|
+
if (cmd && p?.telemetryAutoSetup && p.available !== false && p.versionOk !== false && !cmd.telemetryStatus?.ok) {
|
|
74
85
|
return `
|
|
75
86
|
<div class="w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-left text-slate-500">
|
|
76
87
|
<span class="opacity-40">${agentIcon(p.icon, 24)}</span>
|
|
77
|
-
<span class="flex-1 min-w-0">${esc(p.name)}</span>
|
|
78
|
-
<button class="setup-btn px-2.5 py-1 text-[11px] font-medium text-amber-400 hover:text-amber-300 bg-amber-500/10 hover:bg-amber-500/20 rounded-md transition-colors" data-preset="${p.presetId}">Setup</button>
|
|
88
|
+
<span class="flex-1 min-w-0">${esc(cmd.label || p.name)}</span>
|
|
89
|
+
<button class="setup-btn px-2.5 py-1 text-[11px] font-medium text-amber-400 hover:text-amber-300 bg-amber-500/10 hover:bg-amber-500/20 rounded-md transition-colors" data-preset="${p.presetId}" data-command-id="${cmd.id}">Setup</button>
|
|
79
90
|
</div>`;
|
|
80
91
|
}
|
|
81
92
|
return `
|
|
82
|
-
<button class="preset-btn w-full flex items-center gap-2.5 px-3 py-2 rounded-md hover:bg-slate-700/70 text-sm transition-colors text-left text-slate-300" data-preset="${p
|
|
83
|
-
<span>${agentIcon(p
|
|
84
|
-
<span class="flex-1 min-w-0">${esc(p
|
|
93
|
+
<button class="preset-btn w-full flex items-center gap-2.5 px-3 py-2 rounded-md hover:bg-slate-700/70 text-sm transition-colors text-left text-slate-300" data-preset="${p?.presetId || ''}" data-command-id="${cmd?.id || ''}">
|
|
94
|
+
<span>${agentIcon(cmd?.icon || p?.icon || 'terminal', 24)}</span>
|
|
95
|
+
<span class="flex-1 min-w-0">${esc(cmd?.label || p?.name || 'Agent')}</span>
|
|
85
96
|
</button>`;
|
|
86
97
|
}).join('');
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
function sortedPresets() {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
101
|
+
const missing = [...state.presets]
|
|
102
|
+
.filter(p => isPresetMissing(p) || isPresetOutdated(p))
|
|
103
|
+
.map(p => ({ type: 'preset', preset: p }));
|
|
104
|
+
const commands = (state.cfg.commands || [])
|
|
105
|
+
.filter(c => c.enabled !== false)
|
|
106
|
+
.map(c => ({ type: 'command', command: c, preset: presetForCommand(c) || { name: c.label, icon: c.icon || 'terminal', isAgent: !!c.isAgent } }));
|
|
107
|
+
const shell = commands.filter(item => !item.command.isAgent);
|
|
108
|
+
const agents = [...missing, ...commands.filter(item => item.command.isAgent)];
|
|
97
109
|
const lastId = localStorage.getItem(MRU_KEY);
|
|
98
110
|
if (lastId) {
|
|
99
|
-
const idx = agents.findIndex(
|
|
111
|
+
const idx = agents.findIndex(item => (item.command?.id || item.preset?.presetId) === lastId);
|
|
100
112
|
if (idx > 0) agents.unshift(...agents.splice(idx, 1));
|
|
101
113
|
}
|
|
102
114
|
return [...agents, ...shell];
|
|
103
115
|
}
|
|
104
116
|
|
|
117
|
+
function createFromCommand(cmd, sessionName, cwd, projectId) {
|
|
118
|
+
send({ type: 'create', commandId: cmd.id, name: sessionName, cwd, projectId: projectId || undefined, ...estimateSize() });
|
|
119
|
+
localStorage.setItem(MRU_KEY, cmd.id);
|
|
120
|
+
}
|
|
121
|
+
|
|
105
122
|
function createFromPreset(preset, sessionName, cwd, projectId) {
|
|
106
123
|
// Find existing command matching this preset
|
|
107
124
|
const cmd = ensureCommandForPreset(preset);
|
|
@@ -128,6 +145,7 @@ function ensureCommandForPreset(preset) {
|
|
|
128
145
|
telemetryEnabled: telemetryEnabledForPreset(preset),
|
|
129
146
|
telemetryStatus: null,
|
|
130
147
|
bridge: preset.bridge,
|
|
148
|
+
env: {},
|
|
131
149
|
};
|
|
132
150
|
state.cfg.commands.push(cmd);
|
|
133
151
|
send({ type: 'config.update', config: state.cfg });
|
|
@@ -162,6 +180,7 @@ function ensureShellCommand() {
|
|
|
162
180
|
outputMarker: null,
|
|
163
181
|
telemetryEnabled: false,
|
|
164
182
|
telemetryStatus: null,
|
|
183
|
+
env: {},
|
|
165
184
|
};
|
|
166
185
|
state.cfg.commands.push(cmd);
|
|
167
186
|
send({ type: 'config.update', config: state.cfg });
|
|
@@ -308,16 +327,17 @@ export function openCreator() {
|
|
|
308
327
|
}
|
|
309
328
|
const setupBtn = e.target.closest('.setup-btn');
|
|
310
329
|
if (setupBtn) {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
330
|
+
const cmd = state.cfg.commands.find(c => c.id === setupBtn.dataset.commandId);
|
|
331
|
+
const preset = cmd ? presetForCommand(cmd) : state.presets.find(p => p.presetId === setupBtn.dataset.preset);
|
|
332
|
+
if (!preset || !cmd) return;
|
|
314
333
|
document.dispatchEvent(new CustomEvent('clideck:setup', { detail: { commandId: cmd.id } }));
|
|
315
334
|
return;
|
|
316
335
|
}
|
|
317
336
|
const btn = e.target.closest('.preset-btn');
|
|
318
337
|
if (!btn) return;
|
|
319
|
-
const
|
|
320
|
-
|
|
338
|
+
const cmd = state.cfg.commands.find(c => c.id === btn.dataset.commandId);
|
|
339
|
+
const preset = cmd ? presetForCommand(cmd) : state.presets.find(p => p.presetId === btn.dataset.preset);
|
|
340
|
+
if (!cmd && !preset) return;
|
|
321
341
|
if (projTrigger && !projHidden.value) {
|
|
322
342
|
showToast('Choose a project or select `None (outside project hierarchy)`.', { title: 'Choose Project', type: 'warn' });
|
|
323
343
|
projTrigger.focus();
|
|
@@ -326,7 +346,8 @@ export function openCreator() {
|
|
|
326
346
|
const name = nameInput.value.trim() || fallbackName;
|
|
327
347
|
const cwd = cwdInput.value.trim() || undefined;
|
|
328
348
|
const projectId = projHidden?.value && projHidden.value !== NO_PROJECT_VALUE ? projHidden.value : undefined;
|
|
329
|
-
|
|
349
|
+
if (cmd) createFromCommand(cmd, name, cwd, projectId);
|
|
350
|
+
else createFromPreset(preset, name, cwd, projectId);
|
|
330
351
|
closeCreator();
|
|
331
352
|
});
|
|
332
353
|
}
|
package/public/js/settings.js
CHANGED
|
@@ -157,8 +157,7 @@ function openIconPicker(triggerEl, cardIdx) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
function telemetryPreset(cmd) {
|
|
160
|
-
|
|
161
|
-
return (state.presets || []).find(p => binName(p.command) === bin);
|
|
160
|
+
return presetForCommand(cmd);
|
|
162
161
|
}
|
|
163
162
|
|
|
164
163
|
function presetForCommand(existing, command) {
|
|
@@ -171,6 +170,24 @@ function presetForCommand(existing, command) {
|
|
|
171
170
|
return presets.find(p => binName(p.command) === bin) || null;
|
|
172
171
|
}
|
|
173
172
|
|
|
173
|
+
function envToText(env) {
|
|
174
|
+
return Object.entries(env || {}).map(([k, v]) => `${k}=${v ?? ''}`).join('\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseEnvText(text) {
|
|
178
|
+
const env = {};
|
|
179
|
+
for (const raw of String(text || '').split(/\r?\n/)) {
|
|
180
|
+
const line = raw.trim();
|
|
181
|
+
if (!line || line.startsWith('#')) continue;
|
|
182
|
+
const idx = line.indexOf('=');
|
|
183
|
+
if (idx <= 0) continue;
|
|
184
|
+
const key = line.slice(0, idx).trim();
|
|
185
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
|
186
|
+
env[key] = line.slice(idx + 1).trim();
|
|
187
|
+
}
|
|
188
|
+
return env;
|
|
189
|
+
}
|
|
190
|
+
|
|
174
191
|
function telemetryEnabledForCommand(existing, command) {
|
|
175
192
|
const preset = presetForCommand(existing, command);
|
|
176
193
|
if (preset?.telemetryEnabled === true) return true;
|
|
@@ -193,9 +210,24 @@ function integrationSection(c) {
|
|
|
193
210
|
</div>`;
|
|
194
211
|
}
|
|
195
212
|
|
|
213
|
+
function deleteProviderButtonHTML() {
|
|
214
|
+
return `
|
|
215
|
+
<button class="agent-del w-8 h-8 flex items-center justify-center rounded-lg border border-slate-700/60 bg-slate-900/50 text-slate-500 hover:text-rose-300 hover:border-rose-400/40 hover:bg-rose-500/10 transition-colors" title="Remove provider" aria-label="Remove provider">
|
|
216
|
+
<svg class="w-4 h-4 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
217
|
+
<path d="M18 6 6 18M6 6l12 12"/>
|
|
218
|
+
</svg>
|
|
219
|
+
</button>`;
|
|
220
|
+
}
|
|
221
|
+
|
|
196
222
|
function renderAgentList() {
|
|
197
223
|
document.getElementById('agent-list').innerHTML = state.cfg.commands.map((c, i) => {
|
|
198
|
-
const
|
|
224
|
+
const preset = presetForCommand(c);
|
|
225
|
+
const isPrimaryPresetCommand = !!preset && state.cfg.commands.findIndex(x => presetForCommand(x)?.presetId === preset.presetId) === i;
|
|
226
|
+
const canDelete = !!c.userAdded || !isPrimaryPresetCommand;
|
|
227
|
+
const presetOptions = [
|
|
228
|
+
'<option value="">Custom</option>',
|
|
229
|
+
...(state.presets || []).map(p => `<option value="${esc(p.presetId)}" ${(c.presetId || preset?.presetId || '') === p.presetId ? 'selected' : ''}>${esc(p.name)}</option>`),
|
|
230
|
+
].join('');
|
|
199
231
|
return `
|
|
200
232
|
<div class="agent-card p-4 bg-slate-800/50 border border-slate-700/50 rounded-lg" data-idx="${i}">
|
|
201
233
|
<div class="flex items-center gap-3 mb-3">
|
|
@@ -207,12 +239,26 @@ function renderAgentList() {
|
|
|
207
239
|
<input type="checkbox" ${c.enabled ? 'checked' : ''} class="agent-enabled accent-blue-500">
|
|
208
240
|
On
|
|
209
241
|
</label>
|
|
210
|
-
|
|
242
|
+
<div class="agent-delete-slot flex items-center justify-end min-w-[34px]">
|
|
243
|
+
${canDelete ? deleteProviderButtonHTML() : ''}
|
|
244
|
+
</div>
|
|
211
245
|
</div>
|
|
212
246
|
<div class="mb-3">
|
|
213
247
|
<label class="block text-xs text-slate-500 mb-1">Command</label>
|
|
214
248
|
<input type="text" value="${esc(c.command)}" class="agent-command w-full px-2 py-1.5 text-sm bg-slate-900 border border-slate-700 rounded text-slate-200 placeholder-slate-500 outline-none focus:border-blue-500 transition-colors font-mono" placeholder="e.g. claude, codex, gemini">
|
|
215
249
|
</div>
|
|
250
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3">
|
|
251
|
+
<div>
|
|
252
|
+
<label class="block text-xs text-slate-500 mb-1">Agent type</label>
|
|
253
|
+
<select class="agent-preset w-full px-2 py-1.5 text-sm bg-slate-900 border border-slate-700 rounded text-slate-200 outline-none focus:border-blue-500 transition-colors">
|
|
254
|
+
${presetOptions}
|
|
255
|
+
</select>
|
|
256
|
+
</div>
|
|
257
|
+
<div>
|
|
258
|
+
<label class="block text-xs text-slate-500 mb-1">Environment <span class="text-slate-600">optional</span></label>
|
|
259
|
+
<textarea rows="1" class="agent-env w-full px-2 py-1.5 text-sm bg-slate-900 border border-slate-700 rounded text-slate-200 placeholder-slate-500 outline-none focus:border-blue-500 transition-colors font-mono resize-y" placeholder="CODEX_HOME=~/.codex-work">${esc(envToText(c.env))}</textarea>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
216
262
|
<div class="mb-3">
|
|
217
263
|
<label class="flex items-center gap-2 text-sm text-slate-300 cursor-pointer select-none">
|
|
218
264
|
<input type="checkbox" ${c.isAgent ? 'checked' : ''} class="agent-is-agent accent-blue-500">
|
|
@@ -278,18 +324,20 @@ function openPresetMenu(anchorEl) {
|
|
|
278
324
|
id: crypto.randomUUID(), label: '', icon: 'terminal', command: '',
|
|
279
325
|
enabled: true, defaultPath: '', isAgent: false, canResume: false,
|
|
280
326
|
resumeCommand: null, sessionIdPattern: null,
|
|
281
|
-
telemetryEnabled: false, telemetryStatus: null,
|
|
327
|
+
telemetryEnabled: false, telemetryStatus: null, env: {}, userAdded: true,
|
|
282
328
|
});
|
|
283
329
|
} else {
|
|
284
330
|
const p = presets.find(x => x.presetId === presetId);
|
|
285
331
|
if (p) state.cfg.commands.push({
|
|
286
|
-
id: crypto.randomUUID(), label: p.name, icon: p.icon, command: p.command,
|
|
332
|
+
id: crypto.randomUUID(), presetId: p.presetId, label: p.name, icon: p.icon, command: p.command,
|
|
287
333
|
enabled: true, defaultPath: '', isAgent: p.isAgent, canResume: p.canResume,
|
|
288
334
|
resumeCommand: p.resumeCommand, sessionIdPattern: p.sessionIdPattern,
|
|
289
335
|
outputMarker: p.outputMarker || null,
|
|
290
336
|
telemetryEnabled: telemetryEnabledForCommand({ presetId: p.presetId, command: p.command }, p.command),
|
|
291
337
|
telemetryStatus: null,
|
|
292
338
|
bridge: p.bridge,
|
|
339
|
+
env: {},
|
|
340
|
+
userAdded: true,
|
|
293
341
|
});
|
|
294
342
|
}
|
|
295
343
|
renderAgentList();
|
|
@@ -324,6 +372,20 @@ agentList.addEventListener('click', (e) => {
|
|
|
324
372
|
return;
|
|
325
373
|
}
|
|
326
374
|
if (e.target.classList.contains('agent-del')) {
|
|
375
|
+
const slot = e.target.closest('.agent-delete-slot');
|
|
376
|
+
slot.innerHTML = `
|
|
377
|
+
<div class="flex items-center gap-1.5 px-2 py-1 rounded-lg border border-rose-400/30 bg-rose-500/10 text-[11px]">
|
|
378
|
+
<span class="text-rose-200/90 whitespace-nowrap">Delete?</span>
|
|
379
|
+
<button class="agent-del-confirm px-1.5 py-0.5 rounded text-rose-100 hover:bg-rose-400/20 transition-colors">Yes</button>
|
|
380
|
+
<button class="agent-del-cancel px-1.5 py-0.5 rounded text-slate-400 hover:text-slate-200 hover:bg-slate-700/60 transition-colors">No</button>
|
|
381
|
+
</div>`;
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (e.target.classList.contains('agent-del-cancel')) {
|
|
385
|
+
e.target.closest('.agent-delete-slot').innerHTML = deleteProviderButtonHTML();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (e.target.classList.contains('agent-del-confirm')) {
|
|
327
389
|
const idx = +e.target.closest('.agent-card').dataset.idx;
|
|
328
390
|
state.cfg.commands.splice(idx, 1);
|
|
329
391
|
renderAgentList();
|
|
@@ -332,6 +394,27 @@ agentList.addEventListener('click', (e) => {
|
|
|
332
394
|
});
|
|
333
395
|
|
|
334
396
|
agentList.addEventListener('change', (e) => {
|
|
397
|
+
if (e.target.classList.contains('agent-preset')) {
|
|
398
|
+
const idx = +e.target.closest('.agent-card').dataset.idx;
|
|
399
|
+
const cmd = state.cfg.commands[idx];
|
|
400
|
+
const preset = (state.presets || []).find(p => p.presetId === e.target.value);
|
|
401
|
+
if (cmd && preset) {
|
|
402
|
+
Object.assign(cmd, {
|
|
403
|
+
presetId: preset.presetId,
|
|
404
|
+
icon: preset.icon,
|
|
405
|
+
isAgent: preset.isAgent,
|
|
406
|
+
canResume: preset.canResume,
|
|
407
|
+
resumeCommand: preset.resumeCommand,
|
|
408
|
+
sessionIdPattern: preset.sessionIdPattern,
|
|
409
|
+
outputMarker: preset.outputMarker || null,
|
|
410
|
+
bridge: preset.bridge,
|
|
411
|
+
telemetryEnabled: telemetryEnabledForCommand({ ...cmd, presetId: preset.presetId }, cmd.command),
|
|
412
|
+
});
|
|
413
|
+
renderAgentList();
|
|
414
|
+
saveConfig();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
335
418
|
if (e.target.classList.contains('agent-is-agent')) {
|
|
336
419
|
const card = e.target.closest('.agent-card');
|
|
337
420
|
card.querySelector('.agent-resume-section').classList.toggle('hidden', !e.target.checked);
|
|
@@ -343,24 +426,25 @@ agentList.addEventListener('change', (e) => {
|
|
|
343
426
|
// When enabling an agent that needs setup, trigger auto-setup
|
|
344
427
|
if (e.target.classList.contains('agent-enabled') && e.target.checked) {
|
|
345
428
|
const idx = +e.target.closest('.agent-card').dataset.idx;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
429
|
+
const cmd = state.cfg.commands[idx];
|
|
430
|
+
const preset = telemetryPreset(cmd);
|
|
431
|
+
if (preset?.telemetryAutoSetup && !cmd.telemetryEnabled) {
|
|
432
|
+
send({ type: 'telemetry.autosetup', presetId: preset.presetId, commandId: cmd.id });
|
|
433
|
+
return; // config broadcast from server will re-render with enabled + telemetryEnabled
|
|
434
|
+
}
|
|
352
435
|
}
|
|
353
436
|
// When disabling an agent that has setup, remove patches only if no other commands of the same agent are enabled
|
|
354
437
|
if (e.target.classList.contains('agent-enabled') && !e.target.checked) {
|
|
355
438
|
const idx = +e.target.closest('.agent-card').dataset.idx;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
439
|
+
const cmd = state.cfg.commands[idx];
|
|
440
|
+
const preset = telemetryPreset(cmd);
|
|
441
|
+
if (preset && cmd.telemetryEnabled) {
|
|
442
|
+
const hasCustomEnv = Object.keys(cmd.env || {}).length > 0;
|
|
443
|
+
const othersEnabled = state.cfg.commands.some((c, i) => i !== idx && c.enabled && telemetryPreset(c)?.presetId === preset.presetId && JSON.stringify(c.env || {}) === JSON.stringify(cmd.env || {}));
|
|
444
|
+
if (!othersEnabled) {
|
|
445
|
+
send({ type: 'telemetry.configure', presetId: preset.presetId, commandId: hasCustomEnv ? cmd.id : undefined, enable: false });
|
|
446
|
+
}
|
|
362
447
|
}
|
|
363
|
-
}
|
|
364
448
|
}
|
|
365
449
|
saveConfig();
|
|
366
450
|
});
|
|
@@ -511,21 +595,26 @@ function saveConfig() {
|
|
|
511
595
|
state.cfg.commands = [...agentCards].map((card, i) => {
|
|
512
596
|
const existing = state.cfg.commands[i] || {};
|
|
513
597
|
const command = card.querySelector('.agent-command').value.trim() || state.cfg.defaultShell;
|
|
598
|
+
const presetId = card.querySelector('.agent-preset')?.value || null;
|
|
599
|
+
const preset = presetId ? state.presets.find(p => p.presetId === presetId) : null;
|
|
514
600
|
return {
|
|
515
601
|
id: existing.id || crypto.randomUUID(),
|
|
602
|
+
presetId,
|
|
516
603
|
label: card.querySelector('.agent-name').value.trim() || 'Untitled',
|
|
517
|
-
icon: existing.icon || 'terminal',
|
|
604
|
+
icon: existing.icon || preset?.icon || 'terminal',
|
|
518
605
|
command,
|
|
519
606
|
enabled: card.querySelector('.agent-enabled').checked,
|
|
520
607
|
defaultPath: existing.defaultPath || '',
|
|
521
608
|
isAgent: card.querySelector('.agent-is-agent').checked,
|
|
522
609
|
canResume: card.querySelector('.agent-can-resume').checked,
|
|
523
610
|
resumeCommand: card.querySelector('.agent-resume-cmd')?.value.trim() || null,
|
|
524
|
-
sessionIdPattern: existing.sessionIdPattern || null,
|
|
525
|
-
outputMarker: existing.outputMarker || null,
|
|
526
|
-
|
|
611
|
+
sessionIdPattern: existing.sessionIdPattern || preset?.sessionIdPattern || null,
|
|
612
|
+
outputMarker: existing.outputMarker || preset?.outputMarker || null,
|
|
613
|
+
env: parseEnvText(card.querySelector('.agent-env')?.value || ''),
|
|
614
|
+
telemetryEnabled: telemetryEnabledForCommand({ ...existing, presetId }, command),
|
|
527
615
|
telemetryStatus: existing.telemetryStatus || null,
|
|
528
|
-
bridge: existing.bridge,
|
|
616
|
+
bridge: existing.bridge || preset?.bridge,
|
|
617
|
+
userAdded: !!existing.userAdded,
|
|
529
618
|
};
|
|
530
619
|
});
|
|
531
620
|
|
package/public/js/terminals.js
CHANGED
|
@@ -41,6 +41,7 @@ const DARK_BALLS = ['#00e5ff', '#5df0d6', '#9b8cff'];
|
|
|
41
41
|
const LIGHT_BALLS = ['#0891b2', '#059669', '#7c3aed'];
|
|
42
42
|
|
|
43
43
|
const URL_RE = /\bhttps?:\/\/[^\s<>"'`]+/g;
|
|
44
|
+
const JUMP_LATEST_THRESHOLD_ROWS = 3;
|
|
44
45
|
|
|
45
46
|
function cleanUrlMatch(text, index) {
|
|
46
47
|
let url = text;
|
|
@@ -84,6 +85,40 @@ function addLinkProvider(term) {
|
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
function shouldShowJumpLatest(term) {
|
|
89
|
+
const buf = term.buffer.active;
|
|
90
|
+
return (buf.baseY - buf.viewportY) > JUMP_LATEST_THRESHOLD_ROWS;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createJumpLatestButton(term) {
|
|
94
|
+
const btn = document.createElement('button');
|
|
95
|
+
btn.type = 'button';
|
|
96
|
+
btn.className = 'tmx-jump-latest';
|
|
97
|
+
btn.title = 'Jump to latest';
|
|
98
|
+
btn.setAttribute('aria-label', 'Jump to latest output');
|
|
99
|
+
btn.innerHTML = `
|
|
100
|
+
<span class="tmx-jump-latest-glow"></span>
|
|
101
|
+
<span class="tmx-jump-latest-icon">
|
|
102
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
103
|
+
<path d="M12 5v14"/>
|
|
104
|
+
<path d="m6.5 13.5 5.5 5.5 5.5-5.5"/>
|
|
105
|
+
</svg>
|
|
106
|
+
</span>`;
|
|
107
|
+
btn.addEventListener('click', (event) => {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
event.stopPropagation();
|
|
110
|
+
btn.classList.add('settling');
|
|
111
|
+
term.scrollToBottom();
|
|
112
|
+
term.focus();
|
|
113
|
+
setTimeout(() => btn.classList.remove('settling'), 260);
|
|
114
|
+
});
|
|
115
|
+
return btn;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function updateJumpLatestButton(term, btn) {
|
|
119
|
+
btn.classList.toggle('visible', shouldShowJumpLatest(term));
|
|
120
|
+
}
|
|
121
|
+
|
|
87
122
|
function startBounce(container) {
|
|
88
123
|
const isDark = !document.documentElement.classList.contains('light');
|
|
89
124
|
const colors = isDark ? DARK_BALLS : LIGHT_BALLS;
|
|
@@ -427,6 +462,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
427
462
|
minimumContrastRatio: MIN_CONTRAST_RATIO,
|
|
428
463
|
cursorBlink: true,
|
|
429
464
|
scrollback: 10000,
|
|
465
|
+
smoothScrollDuration: 180,
|
|
430
466
|
});
|
|
431
467
|
const fit = new FitAddon.FitAddon();
|
|
432
468
|
term.loadAddon(fit);
|
|
@@ -509,6 +545,11 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
509
545
|
}, 0);
|
|
510
546
|
|
|
511
547
|
term.open(el);
|
|
548
|
+
const jumpLatestBtn = createJumpLatestButton(term);
|
|
549
|
+
el.appendChild(jumpLatestBtn);
|
|
550
|
+
const refreshJumpLatest = () => updateJumpLatestButton(term, jumpLatestBtn);
|
|
551
|
+
term.onScroll(refreshJumpLatest);
|
|
552
|
+
term.onWriteParsed(refreshJumpLatest);
|
|
512
553
|
attachToTerminal(term, presetId);
|
|
513
554
|
const onContextMenu = (e) => {
|
|
514
555
|
if (e.shiftKey) return;
|
|
@@ -537,6 +578,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
537
578
|
for (const chunk of pending) term.write(chunk);
|
|
538
579
|
pending = null;
|
|
539
580
|
updatePreview(id);
|
|
581
|
+
refreshJumpLatest();
|
|
540
582
|
return;
|
|
541
583
|
}
|
|
542
584
|
if (fitRaf) return;
|
|
@@ -559,6 +601,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
559
601
|
for (const chunk of pending) term.write(chunk);
|
|
560
602
|
pending = null;
|
|
561
603
|
updatePreview(id);
|
|
604
|
+
refreshJumpLatest();
|
|
562
605
|
}
|
|
563
606
|
}, 500);
|
|
564
607
|
const cancelFitRaf = () => { if (fitRaf) { cancelAnimationFrame(fitRaf); fitRaf = 0; } };
|
|
@@ -621,6 +664,7 @@ export function select(id) {
|
|
|
621
664
|
if (!document.querySelector('[contenteditable="true"]')) entry.term.focus();
|
|
622
665
|
}
|
|
623
666
|
state.active = id;
|
|
667
|
+
localStorage.setItem('clideck.activeSessionId', id);
|
|
624
668
|
}
|
|
625
669
|
|
|
626
670
|
// --- Preview & status ---
|
package/public/tailwind.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[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-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;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-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\/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-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%}.save-indicator{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-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-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}
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:.25rem}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.bottom-0{bottom:0}.bottom-5{bottom:1.25rem}.left-2\.5{left:.625rem}.right-0{right:0}.right-3{right:.75rem}.right-5{right:1.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.z-10{z-index:10}.z-\[200\]{z-index:200}.z-\[250\]{z-index:250}.z-\[260\]{z-index:260}.z-\[300\]{z-index:300}.z-\[400\]{z-index:400}.z-\[500\]{z-index:500}.mx-4{margin-left:1rem;margin-right:1rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.-mt-2{margin-top:-.5rem}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-0\.5{margin-left:.125rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-6{margin-left:1.5rem}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-\[18px\]{height:18px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[160px\]{max-height:160px}.max-h-\[400px\]{max-height:400px}.max-h-\[460px\]{max-height:460px}.min-h-0{min-height:0}.min-h-\[200px\]{min-height:200px}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[180px\]{width:180px}.w-\[18px\]{width:18px}.w-\[340px\]{width:340px}.w-\[354px\]{width:354px}.w-\[360px\]{width:360px}.w-\[420px\]{width:420px}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[160px\]{min-width:160px}.min-w-\[16px\]{min-width:16px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[34px\]{min-width:34px}.min-w-\[354px\]{min-width:354px}.max-w-2xl{max-width:42rem}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-1\/2,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-y{resize:vertical}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-px>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1px*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-400\/20{border-color:rgba(251,191,36,.2)}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-red-500\/30{border-color:rgba(239,68,68,.3)}.border-rose-400\/30{border-color:rgba(251,113,133,.3)}.border-slate-600{--tw-border-opacity:1;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85/var(--tw-border-opacity,1))}.border-slate-700\/30{border-color:rgba(51,65,85,.3)}.border-slate-700\/40{border-color:rgba(51,65,85,.4)}.border-slate-700\/50{border-color:rgba(51,65,85,.5)}.border-slate-700\/60{border-color:rgba(51,65,85,.6)}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/10{background-color:rgba(59,130,246,.1)}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-emerald-600\/20{background-color:rgba(5,150,105,.2)}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-600\/20{background-color:rgba(220,38,38,.2)}.bg-rose-500\/10{background-color:rgba(244,63,94,.1)}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-slate-700\/50{background-color:rgba(51,65,85,.5)}.bg-slate-700\/60{background-color:rgba(51,65,85,.6)}.bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity,1))}.bg-slate-800\/30{background-color:rgba(30,41,59,.3)}.bg-slate-800\/40{background-color:rgba(30,41,59,.4)}.bg-slate-800\/50{background-color:rgba(30,41,59,.5)}.bg-slate-800\/60{background-color:rgba(30,41,59,.6)}.bg-slate-800\/80{background-color:rgba(30,41,59,.8)}.bg-slate-800\/95{background-color:rgba(30,41,59,.95)}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-slate-900\/50{background-color:rgba(15,23,42,.5)}.bg-slate-900\/70{background-color:rgba(15,23,42,.7)}.bg-slate-950{--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity,1))}.bg-slate-950\/50{background-color:rgba(2,6,23,.5)}.bg-transparent{background-color:transparent}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[3px\]{padding:3px}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-3\.5{padding-bottom:.875rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-9{padding-left:2.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-3\.5{padding-top:.875rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-\[1\.45\]{line-height:1.45}.leading-\[1\.4\]{line-height:1.4}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-200\/90{color:hsla(48,97%,77%,.9)}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-400\/80{color:rgba(251,191,36,.8)}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-emerald-400\/80{color:rgba(52,211,153,.8)}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-emerald-500\/70{color:rgba(16,185,129,.7)}.text-indigo-400{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-rose-100{--tw-text-opacity:1;color:rgb(255 228 230/var(--tw-text-opacity,1))}.text-rose-200\/90{color:rgba(254,205,211,.9)}.text-rose-400{--tw-text-opacity:1;color:rgb(251 113 133/var(--tw-text-opacity,1))}.text-rose-400\/80{color:rgba(251,113,133,.8)}.text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-500{--tw-text-opacity:1;color:rgb(234 179 8/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.placeholder-slate-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-500::placeholder{--tw-placeholder-opacity:1;color:rgb(100 116 139/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.placeholder-slate-600::placeholder{--tw-placeholder-opacity:1;color:rgb(71 85 105/var(--tw-placeholder-opacity,1))}.accent-blue-500{accent-color:#3b82f6}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-black\/40{--tw-shadow-color:rgba(0,0,0,.4);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/50{--tw-shadow-color:rgba(0,0,0,.5);--tw-shadow:var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color:rgba(0,0,0,.6);--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-white\/40{--tw-ring-color:hsla(0,0%,100%,.4)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.bg-slate-950{background-color:var(--color-base)!important}.bg-slate-900{background-color:var(--color-surface)!important}.bg-slate-800{background-color:var(--color-raised)!important}.bg-slate-700{background-color:var(--color-muted)!important}.bg-slate-800\/50{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.bg-slate-800\/30{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.bg-slate-800\/60{background-color:color-mix(in srgb,var(--color-raised) 60%,transparent)!important}.bg-slate-800\/80{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.bg-slate-900\/70{background-color:color-mix(in srgb,var(--color-surface) 70%,transparent)!important}.bg-slate-800\/95{background-color:color-mix(in srgb,var(--color-raised) 95%,transparent)!important}.bg-slate-700\/50{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.bg-slate-700\/60{background-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.hover\:bg-slate-700:hover{background-color:var(--color-muted)!important}.hover\:bg-slate-800:hover{background-color:var(--color-raised)!important}.hover\:bg-slate-800\/50:hover{background-color:color-mix(in srgb,var(--color-raised) 50%,transparent)!important}.hover\:bg-slate-800\/30:hover{background-color:color-mix(in srgb,var(--color-raised) 30%,transparent)!important}.hover\:bg-slate-700\/50:hover{background-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.hover\:bg-slate-700\/70:hover{background-color:color-mix(in srgb,var(--color-muted) 70%,transparent)!important}.text-slate-200{color:var(--color-text)!important}.text-slate-300{color:var(--color-soft)!important}.text-slate-400{color:var(--color-dim)!important}.text-slate-500{color:var(--color-subtle)!important}.text-slate-600{color:var(--color-border)!important}.hover\:text-slate-200:hover{color:var(--color-text)!important}.hover\:text-slate-300:hover{color:var(--color-soft)!important}.hover\:text-slate-400:hover{color:var(--color-dim)!important}.border-slate-600{border-color:var(--color-border)!important}.border-slate-700{border-color:var(--color-muted)!important}.border-slate-700\/50{border-color:color-mix(in srgb,var(--color-muted) 50%,transparent)!important}.border-slate-700\/40{border-color:color-mix(in srgb,var(--color-muted) 40%,transparent)!important}.border-slate-700\/60{border-color:color-mix(in srgb,var(--color-muted) 60%,transparent)!important}.border-slate-600\/60{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.hover\:border-slate-500:hover{border-color:var(--color-subtle)!important}.focus\:border-slate-600\/60:focus{border-color:color-mix(in srgb,var(--color-border) 60%,transparent)!important}.focus\:bg-slate-800\/80:focus{background-color:color-mix(in srgb,var(--color-raised) 80%,transparent)!important}.placeholder-slate-500::-moz-placeholder{color:var(--color-subtle)!important}.placeholder-slate-500::placeholder{color:var(--color-subtle)!important}.placeholder-slate-600::-moz-placeholder{color:var(--color-border)!important}.placeholder-slate-600::placeholder{color:var(--color-border)!important}.ring-slate-500{--tw-ring-color:var(--color-subtle)!important}.bg-black\/60{background-color:var(--color-overlay)!important}.shadow-black\/40,.shadow-black\/50,.shadow-black\/60{--tw-shadow-color:var(--color-shadow)!important}.disabled\:bg-slate-600:disabled{background-color:var(--color-border)!important}.disabled\:text-slate-400:disabled{color:var(--color-dim)!important}:root{--color-preview:#a2a2a2;--color-time:#7c7d7d;--color-time-recent:#5cbd6d;--color-dormant:#555;--color-proj-meta:#7c7d7d;--color-search-bg:#2e2f2f;--color-search-text:#acacac;--color-session-hover:hsla(0,0%,100%,.04);--color-header-icon:#cbd5e1;--color-rail-icon:#a5a6a6}.light{--color-preview:#626262;--color-time:#666;--color-time-recent:#51a868;--color-dormant:#aaa;--color-proj-meta:#666;--color-search-bg:#f6f5f5;--color-search-text:#626262;--color-session-hover:rgba(0,0,0,.04);--color-header-icon:#3d4450;--color-rail-icon:#636261}.session-preview{color:var(--color-preview)!important}.session-time{color:var(--color-time)!important}.session-time.recent{color:var(--color-time-recent)!important}.session-status.dormant{color:var(--color-dormant)!important}.group[data-id]:hover,.pill-row:hover,.resumable-row:hover{background-color:var(--color-session-hover)!important}.project-count,.project-menu-btn{color:var(--color-proj-meta)!important}#search-input{background-color:var(--color-search-bg)!important;color:var(--color-search-text)!important}#search-input::-moz-placeholder{color:var(--color-search-text)!important;opacity:.7}#search-input::placeholder{color:var(--color-search-text)!important;opacity:.7}.relative:has(#search-input)>svg{color:var(--color-search-text)!important}.rail-btn:not(.text-slate-200){color:var(--color-rail-icon)!important}.rail-btn.bg-slate-800{background-color:var(--color-rail-active)!important}.icon-btn{color:var(--color-header-icon)!important;border-color:var(--color-border)!important;font-weight:700}.icon-btn svg{stroke-width:2}:root{--color-separator:#2e2f2f}.light{--color-separator:#d8d3cd}#nav-rail{background-color:var(--color-rail)!important}#sidebar{background-color:var(--color-sidebar)!important;border-right-color:var(--color-separator)!important}#sidebar input[type=text],#sidebar select{background-color:var(--color-sidebar-input)!important;border-color:var(--color-sidebar-border)!important}.group:hover{background-color:var(--color-chat-hover)!important}.group.active-session{background-color:var(--color-chat-active)!important}.theme-toggle{position:relative;width:36px;height:36px}.theme-toggle svg{position:absolute;inset:0;margin:auto;transition:opacity .3s ease,transform .3s ease}.theme-toggle .icon-sun{opacity:0;transform:rotate(-90deg) scale(.5)}.light .theme-toggle .icon-sun,.theme-toggle .icon-moon{opacity:1;transform:rotate(0) scale(1)}.light .theme-toggle .icon-moon{opacity:0;transform:rotate(90deg) scale(.5)}html{transition:color .3s ease,background-color .3s ease}.term-wrap{visibility:hidden;position:absolute;overflow:hidden;top:4px;left:4px;right:4px;bottom:0}.term-wrap.active{visibility:visible}.tmx-jump-latest.term-wrap.active{opacity:1;pointer-events:auto;transform:translateY(0) scale(1)}.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.visible{opacity:1;pointer-events:auto;transform:translateY(0) scale(1)}.tmx-jump-latest:hover{color:var(--color-bright);border-color:color-mix(in srgb,var(--color-dim) 82%,transparent);transform:translateY(-2px) scale(1.04)}.tmx-jump-latest.settling{opacity:0;pointer-events:none;transform:translateY(16px) scale(.94)}.tmx-jump-latest-glow{position:absolute;inset:-8px;border-radius:inherit;background:radial-gradient(circle at 50% 35%,color-mix(in srgb,var(--color-accent-subtle) 78%,transparent),transparent 62%);opacity:0;transform:scale(.82);transition:opacity .22s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1)}.tmx-jump-latest:hover .tmx-jump-latest-glow{opacity:1;transform:scale(1)}.tmx-jump-latest-icon{position:relative}.save-indicator,.tmx-jump-latest-icon,.tmx-jump-latest-icon svg{width:18px;height:18px}.save-indicator{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--color-search-text);margin-right:4px;opacity:.35;transition:opacity .4s ease}.save-indicator.saved{opacity:.5}.save-indicator .save-tick{color:var(--color-time-recent);display:block}.save-indicator .save-spin{display:none;color:var(--color-dim);animation:save-rotate 1.5s cubic-bezier(.4,0,.2,1) infinite}.save-indicator.saving .save-tick{display:none}.save-indicator.saving .save-spin{display:block}.save-indicator.saving{opacity:.5}@keyframes save-rotate{to{transform:rotate(1turn)}}.tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.5)}.light .tmx-toast{box-shadow:0 25px 50px -12px rgba(0,0,0,.25),0 0 0 1px rgba(0,0,0,.05)}.drop-highlight{border-radius:.25rem;background-color:rgba(59,130,246,.1);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:rgba(59,130,246,.3)}.project-drop-line{height:2px;margin:.125rem .5rem;border-radius:9999px;--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.plugin-chevron.collapsed,.project-chevron.collapsed svg{transform:rotate(-90deg)}.tmx-scroll{scrollbar-width:thin;scrollbar-color:transparent transparent}.tmx-scroll::-webkit-scrollbar{width:10px}.tmx-scroll::-webkit-scrollbar-track{background:transparent}.tmx-scroll::-webkit-scrollbar-thumb{background:transparent;border:2px solid transparent;border-radius:9999px;-webkit-transition:background-color .2s ease,border-color .2s ease;transition:background-color .2s ease,border-color .2s ease}.tmx-scroll.is-scrolling,.tmx-scroll:hover{scrollbar-color:color-mix(in srgb,var(--color-muted) 78%,transparent) color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-track,.tmx-scroll:hover::-webkit-scrollbar-track{background:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb,.tmx-scroll:hover::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--color-muted) 78%,transparent);border-color:color-mix(in srgb,var(--color-sidebar) 70%,transparent)}.tmx-scroll.is-scrolling::-webkit-scrollbar-thumb:hover,.tmx-scroll:hover::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--color-subtle) 85%,transparent)}.prompt-autocomplete{position:fixed;width:340px;background:var(--color-raised);border:1px solid var(--color-muted);border-radius:10px;box-shadow:0 20px 40px -8px rgba(0,0,0,.5);z-index:100;display:flex;flex-direction:column;overflow:hidden;animation:pa-in .15s ease}@keyframes pa-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.pa-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent)}.pa-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-subtle)}.pa-query{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--color-dim);background:color-mix(in srgb,var(--color-muted) 40%,transparent);padding:2px 6px;border-radius:4px}.pa-list{overflow-y:auto;padding:4px;max-height:184px}.pa-item{padding:8px 10px;border-radius:6px;cursor:pointer;transition:background-color .1s}.pa-item:hover,.pa-selected{background:color-mix(in srgb,var(--color-muted) 40%,transparent)}.pa-name{font-size:13px;font-weight:500;color:var(--color-text)}.pa-name,.pa-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pa-text{font-size:11px;color:var(--color-subtle);margin-top:2px}.pa-item mark{background:rgba(59,130,246,.25);color:inherit;border-radius:2px;padding:0 1px}.pa-footer{display:flex;gap:12px;justify-content:center;padding:6px 12px;border-top:1px solid color-mix(in srgb,var(--color-muted) 50%,transparent);font-size:10px;color:var(--color-subtle)}.pa-footer kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;padding:1px 4px;border-radius:3px;background:color-mix(in srgb,var(--color-muted) 50%,transparent);color:var(--color-dim)}.pa-empty{padding:20px 12px;text-align:center;font-size:13px;color:var(--color-subtle)}.pa-hint{padding:0 12px 12px;text-align:center;font-size:11px;color:var(--color-border)}.pa-hint kbd{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:color-mix(in srgb,var(--color-muted) 50%,transparent);padding:1px 4px;border-radius:3px}.empty\:hidden:empty{display:none}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-rose-400\/40:hover{border-color:rgba(251,113,133,.4)}.hover\:border-slate-500:hover{--tw-border-opacity:1;border-color:rgb(100 116 139/var(--tw-border-opacity,1))}.hover\:bg-amber-500\/20:hover{background-color:rgba(245,158,11,.2)}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-blue-500\/20:hover{background-color:rgba(59,130,246,.2)}.hover\:bg-red-500:hover{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.hover\:bg-rose-400\/20:hover{background-color:rgba(251,113,133,.2)}.hover\:bg-rose-500\/10:hover{background-color:rgba(244,63,94,.1)}.hover\:bg-rose-500\/20:hover{background-color:rgba(244,63,94,.2)}.hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.hover\:bg-slate-700\/50:hover{background-color:rgba(51,65,85,.5)}.hover\:bg-slate-700\/60:hover{background-color:rgba(51,65,85,.6)}.hover\:bg-slate-700\/70:hover{background-color:rgba(51,65,85,.7)}.hover\:bg-slate-800\/30:hover{background-color:rgba(30,41,59,.3)}.hover\:bg-slate-800\/40:hover{background-color:rgba(30,41,59,.4)}.hover\:bg-slate-800\/50:hover{background-color:rgba(30,41,59,.5)}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-emerald-300:hover{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-indigo-400:hover{--tw-text-opacity:1;color:rgb(129 140 248/var(--tw-text-opacity,1))}.hover\:text-red-300:hover{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.hover\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\:text-rose-300:hover{--tw-text-opacity:1;color:rgb(253 164 175/var(--tw-text-opacity,1))}.hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240/var(--tw-text-opacity,1))}.hover\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-slate-500:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(100 116 139/var(--tw-ring-opacity,1))}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-slate-600\/60:focus{border-color:rgba(71,85,105,.6)}.focus\:bg-slate-800\/80:focus{background-color:rgba(30,41,59,.8)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:rgba(59,130,246,.3)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-600:disabled{--tw-bg-opacity:1;background-color:rgb(71 85 105/var(--tw-bg-opacity,1))}.disabled\:text-slate-400:disabled{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
package/sessions.js
CHANGED
|
@@ -10,7 +10,7 @@ const opencodeBridge = require('./opencode-bridge');
|
|
|
10
10
|
const plugins = require('./plugin-loader');
|
|
11
11
|
|
|
12
12
|
const THEMES = require('./themes');
|
|
13
|
-
const MAX_BUFFER =
|
|
13
|
+
const MAX_BUFFER = 2 * 1024 * 1024;
|
|
14
14
|
const { PORT, localUrl } = require('./runtime');
|
|
15
15
|
const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b./g;
|
|
16
16
|
const PRESETS = JSON.parse(require('fs').readFileSync(join(__dirname, 'agent-presets.json'), 'utf8'));
|
|
@@ -59,9 +59,26 @@ function broadcast(msg) {
|
|
|
59
59
|
|
|
60
60
|
// --- Spawn a PTY and wire up a session ---
|
|
61
61
|
|
|
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
|
+
}
|
|
70
|
+
|
|
71
|
+
function commandEnv(cmd) {
|
|
72
|
+
const env = {};
|
|
73
|
+
if (!cmd?.env || typeof cmd.env !== 'object' || Array.isArray(cmd.env)) return env;
|
|
74
|
+
for (const [key, value] of Object.entries(cmd.env)) {
|
|
75
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) env[key] = String(value ?? '');
|
|
76
|
+
}
|
|
77
|
+
return env;
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
function buildTelemetryEnv(id, cmd) {
|
|
63
|
-
const
|
|
64
|
-
const preset = PRESETS.find(p => binName(p.command) === bin);
|
|
81
|
+
const preset = presetForCommand(cmd);
|
|
65
82
|
const telemetryEnabled = cmd.telemetryEnabled ?? (preset?.presetId === 'claude-code');
|
|
66
83
|
const env = { CLIDECK_SESSION_ID: id, CLIDECK_PORT: String(PORT), CLIDECK_URL: localUrl() };
|
|
67
84
|
if (!preset?.telemetryEnv || !telemetryEnabled) return env;
|
|
@@ -85,19 +102,19 @@ function isLightTheme(themeId) {
|
|
|
85
102
|
function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken, projectId, cols, rows) {
|
|
86
103
|
const telemetryEnv = buildTelemetryEnv(id, cmd);
|
|
87
104
|
const colorEnv = isLightTheme(themeId) ? { COLORFGBG: '0;15' } : { COLORFGBG: '15;0' };
|
|
105
|
+
const extraEnv = commandEnv(cmd);
|
|
88
106
|
let term;
|
|
89
107
|
try {
|
|
90
108
|
term = pty.spawn(parts[0], parts.slice(1), {
|
|
91
109
|
name: 'xterm-256color', cols: cols || 80, rows: rows || 24, cwd,
|
|
92
|
-
env: { ...process.env, ...telemetryEnv, ...colorEnv },
|
|
110
|
+
env: { ...process.env, ...extraEnv, ...telemetryEnv, ...colorEnv },
|
|
93
111
|
});
|
|
94
112
|
} catch (e) {
|
|
95
113
|
return e;
|
|
96
114
|
}
|
|
97
115
|
|
|
98
116
|
const sessionIdRe = cmd.sessionIdPattern ? new RegExp(cmd.sessionIdPattern, 'i') : null;
|
|
99
|
-
const
|
|
100
|
-
const preset = PRESETS.find(p => binName(p.command) === bin);
|
|
117
|
+
const preset = presetForCommand(cmd);
|
|
101
118
|
const session = { name, themeId, commandId, cwd, pty: term, chunks: [], chunksSize: 0, sessionToken: savedToken || null, projectId: projectId || null, presetId: preset?.presetId || 'shell', working: undefined };
|
|
102
119
|
sessions.set(id, session);
|
|
103
120
|
transcript.setFinalizeOnIdle(id, ['claude-code', 'codex', 'gemini-cli', 'opencode', 'clideck-agent'].includes(session.presetId) ? session.presetId : null);
|
|
@@ -105,7 +122,7 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
|
|
|
105
122
|
// Always watch telemetry-backed agents so OTLP fallback matching can attach
|
|
106
123
|
// early events to this session even when the agent omits clideck.session_id.
|
|
107
124
|
// The receiver itself decides whether to surface a setup prompt.
|
|
108
|
-
if (preset?.telemetryEnv) telemetry.watchSession(id,
|
|
125
|
+
if (preset?.telemetryEnv) telemetry.watchSession(id, binName(cmd.command));
|
|
109
126
|
if (preset?.bridge === 'opencode') opencodeBridge.watchSession(id, cwd);
|
|
110
127
|
|
|
111
128
|
term.onData((data) => {
|
|
@@ -179,13 +196,12 @@ function create(msg, ws, cfg) {
|
|
|
179
196
|
return;
|
|
180
197
|
}
|
|
181
198
|
|
|
182
|
-
const createdPresetId =
|
|
199
|
+
const createdPresetId = presetForCommand(cmd)?.presetId || 'shell';
|
|
183
200
|
const installId = msg.installId || undefined;
|
|
184
201
|
broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId: createdPresetId, projectId, installId });
|
|
185
202
|
|
|
186
203
|
// Immediate setup notification if config not detected
|
|
187
|
-
const
|
|
188
|
-
const preset = PRESETS.find(p => binName(p.command) === bin);
|
|
204
|
+
const preset = presetForCommand(cmd);
|
|
189
205
|
if (preset && (preset.telemetrySetup || preset.bridge) && !(cmd.telemetryEnabled && cmd.telemetryStatus?.ok)) {
|
|
190
206
|
broadcast({ type: 'session.needsSetup', id });
|
|
191
207
|
}
|
|
@@ -212,7 +228,7 @@ function createProgrammatic(opts, cfg) {
|
|
|
212
228
|
const s = sessions.get(id);
|
|
213
229
|
if (s && opts.ephemeral) s.ephemeral = true;
|
|
214
230
|
|
|
215
|
-
const presetId =
|
|
231
|
+
const presetId = presetForCommand(cmd)?.presetId || 'shell';
|
|
216
232
|
broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId, projectId });
|
|
217
233
|
return { id };
|
|
218
234
|
}
|
|
@@ -262,7 +278,7 @@ function resume(msg, ws, cfg) {
|
|
|
262
278
|
resumable = resumable.filter(s => s.id !== id);
|
|
263
279
|
broadcast({ type: 'sessions.resumable', list: getResumable(cfg) });
|
|
264
280
|
|
|
265
|
-
const resumePresetId =
|
|
281
|
+
const resumePresetId = presetForCommand(cmd)?.presetId || saved.presetId || 'shell';
|
|
266
282
|
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 || '' });
|
|
267
283
|
}
|
|
268
284
|
|
|
@@ -406,13 +422,18 @@ function getResumable(cfg) {
|
|
|
406
422
|
if (s.presetId) return s;
|
|
407
423
|
const cmd = (cfg.commands || []).find(c => c.id === s.commandId);
|
|
408
424
|
if (!cmd) return { ...s, presetId: 'shell' };
|
|
409
|
-
const preset =
|
|
425
|
+
const preset = presetForCommand(cmd);
|
|
410
426
|
return { ...s, presetId: preset?.presetId || 'shell' };
|
|
411
427
|
});
|
|
412
428
|
}
|
|
413
429
|
|
|
414
430
|
function sendBuffers(ws) {
|
|
415
431
|
for (const [id, s] of sessions) {
|
|
432
|
+
if (s.chunks.length) {
|
|
433
|
+
const data = s.chunks.join('');
|
|
434
|
+
ws.send(JSON.stringify({ type: 'output', id, data, replay: true }));
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
416
437
|
if (['claude-code', 'codex', 'gemini-cli', 'opencode', 'clideck-agent'].includes(s.presetId) && !s.working) {
|
|
417
438
|
const text = transcript.getReplayText(id, s.presetId);
|
|
418
439
|
if (text) {
|
|
@@ -420,10 +441,6 @@ function sendBuffers(ws) {
|
|
|
420
441
|
continue;
|
|
421
442
|
}
|
|
422
443
|
}
|
|
423
|
-
if (s.chunks.length) {
|
|
424
|
-
const data = s.chunks.join('');
|
|
425
|
-
ws.send(JSON.stringify({ type: 'output', id, data, replay: true }));
|
|
426
|
-
}
|
|
427
444
|
}
|
|
428
445
|
}
|
|
429
446
|
|
package/transcript.js
CHANGED
|
@@ -226,9 +226,13 @@ function getTurns(id, n, order) {
|
|
|
226
226
|
|
|
227
227
|
function getCache() { return { ...cache }; }
|
|
228
228
|
|
|
229
|
+
function hasSettledReplay(entries) {
|
|
230
|
+
return entries?.some(e => e.role === 'agent') && entries[entries.length - 1]?.role === 'agent';
|
|
231
|
+
}
|
|
232
|
+
|
|
229
233
|
function getReplayText(id, presetId) {
|
|
230
234
|
const entries = builder.compactEntries(entriesById[id], presetId);
|
|
231
|
-
if (!entries
|
|
235
|
+
if (!hasSettledReplay(entries)) return '';
|
|
232
236
|
const marks = {
|
|
233
237
|
'claude-code': { user: '❯', agent: '⏺' },
|
|
234
238
|
codex: { user: '›', agent: '•' },
|