clideck 1.31.4 → 1.31.5

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 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 ~/.codex/hooks.json shape');
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(home, nodePath, helperPath, port) {
37
- const hooksPath = join(home, '.codex', 'hooks.json');
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(home) {
57
- const hooksPath = join(home, '.codex', 'hooks.json');
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(home, helperPath, port) {
76
+ function codexHooksHealthy(codexHome, helperPath, port) {
77
77
  try {
78
- const doc = readHooksFile(join(home, '.codex', 'hooks.json'));
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
- return (commands || []).filter(cmd => !cmd.presetId || allowedPresetIds.has(cmd.presetId));
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(os.homedir(), codexHookPath, port)) return false;
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 bin = binName(cmd.command);
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(home, '.claude', 'settings.json'), 'utf8'));
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 content = readFileSync(join(home, '.codex', 'config.toml'), 'utf8');
197
- detected = codexConfigLooksHealthy(content, port);
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(home, '.gemini', 'settings.json'), 'utf8'));
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 preset = presets.find(p => p.presetId === msg.presetId);
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 (binName(cmd.command) === binName(preset.command)) {
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: msg.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 preset = presets.find(p => p.presetId === msg.presetId);
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 (binName(cmd.command) === binName(preset.command)) {
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(home, '.claude', 'settings.json');
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: 'Added hooks to ~/.claude/settings.json — Claude will ask for one-time approval' };
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 configPath = join(home, '.codex', 'config.toml');
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(home, codexHookPath, port);
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(home, process.execPath.replace(/\\/g, '/'), codexHookPath, port);
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(home, '.gemini', 'settings.json');
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: 'Added CliDeck hooks to ~/.gemini/settings.json' };
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(home, '.claude', 'settings.json');
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: 'Removed CliDeck hooks from ~/.claude/settings.json' };
735
+ return { success: true, message: `Removed CliDeck hooks from ${configPath}` };
706
736
  }
707
737
 
708
738
  if (preset.presetId === 'codex') {
709
- const configPath = join(home, '.codex', 'config.toml');
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(home);
717
- return { success: true, message: 'Removed otel + CliDeck hooks from ~/.codex config' };
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(home, '.gemini', 'settings.json');
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: 'Removed CliDeck hooks from ~/.gemini/settings.json' };
765
+ return { success: true, message: `Removed CliDeck hooks from ${configPath}` };
735
766
  }
736
767
 
737
768
  if (preset.presetId === 'opencode') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.31.4",
3
+ "version": "1.31.5",
4
4
  "description": "One screen for all your AI coding agents — run, monitor, and manage multiple CLI agents from a single browser tab",
5
5
  "main": "server.js",
6
6
  "bin": {
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
- if (msg.list.length) select(msg.list[0].id);
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
- if (entry && !entry.queue(msg.text + '\n')) entry.term.write(msg.text + '\n');
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 bin = binName(cmd.command);
473
- const preset = state.presets.find(p => binName(p.command) === bin);
488
+ const preset = presetForCommand(cmd);
489
+ if (!preset) return;
474
490
  const setupRaw = preset.telemetrySetup || preset.pluginSetup;
475
- if (!setupRaw || shownSetup.has(preset.presetId)) return;
476
- shownSetup.add(preset.presetId);
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(preset.presetId);
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
 
@@ -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(p => {
57
- if (isPresetMissing(p)) {
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 (isPresetUnpatched(p)) {
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.presetId}">
83
- <span>${agentIcon(p.icon, 24)}</span>
84
- <span class="flex-1 min-w-0">${esc(p.name)}</span>
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 all = [...state.presets].filter(p => {
91
- if (isPresetMissing(p)) return true;
92
- const cmd = findCommandForPreset(p);
93
- return !cmd || cmd.enabled !== false;
94
- });
95
- const shell = all.filter(p => !p.isAgent);
96
- const agents = all.filter(p => p.isAgent);
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(p => p.presetId === lastId);
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 preset = state.presets.find(p => p.presetId === setupBtn.dataset.preset);
312
- if (!preset) return;
313
- const cmd = ensureCommandForPreset(preset);
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 preset = state.presets.find(p => p.presetId === btn.dataset.preset);
320
- if (!preset) return;
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
- createFromPreset(preset, name, cwd, projectId);
349
+ if (cmd) createFromCommand(cmd, name, cwd, projectId);
350
+ else createFromPreset(preset, name, cwd, projectId);
330
351
  closeCreator();
331
352
  });
332
353
  }
@@ -157,8 +157,7 @@ function openIconPicker(triggerEl, cardIdx) {
157
157
  }
158
158
 
159
159
  function telemetryPreset(cmd) {
160
- const bin = binName(cmd.command);
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 isBuiltIn = !!telemetryPreset(c);
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
- ${isBuiltIn ? '' : '<button class="agent-del text-slate-500 hover:text-red-400 px-1 text-lg transition-colors" title="Remove">&times;</button>'}
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
- const cmd = state.cfg.commands[idx];
347
- const preset = telemetryPreset(cmd);
348
- if (preset?.telemetryAutoSetup && !cmd.telemetryEnabled) {
349
- send({ type: 'telemetry.autosetup', presetId: preset.presetId });
350
- return; // config broadcast from server will re-render with enabled + telemetryEnabled
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
- const cmd = state.cfg.commands[idx];
357
- const preset = telemetryPreset(cmd);
358
- if (preset && cmd.telemetryEnabled) {
359
- const othersEnabled = state.cfg.commands.some((c, i) => i !== idx && c.enabled && telemetryPreset(c)?.presetId === preset.presetId);
360
- if (!othersEnabled) {
361
- send({ type: 'telemetry.configure', presetId: preset.presetId, enable: false });
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
- telemetryEnabled: telemetryEnabledForCommand(existing, command),
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
 
@@ -621,6 +621,7 @@ export function select(id) {
621
621
  if (!document.querySelector('[contenteditable="true"]')) entry.term.focus();
622
622
  }
623
623
  state.active = id;
624
+ localStorage.setItem('clideck.activeSessionId', id);
624
625
  }
625
626
 
626
627
  // --- Preview & status ---
@@ -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}.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-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 = 200 * 1024;
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 bin = binName(cmd.command);
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 bin = binName(cmd.command);
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, bin);
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 = PRESETS.find(p => binName(p.command) === binName(cmd.command))?.presetId || 'shell';
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 bin = binName(cmd.command);
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 = PRESETS.find(p => binName(p.command) === binName(cmd.command))?.presetId || 'shell';
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 = PRESETS.find(p => binName(p.command) === binName(cmd.command))?.presetId || saved.presetId || 'shell';
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 = PRESETS.find(p => binName(p.command) === binName(cmd.command));
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?.length) return '';
235
+ if (!hasSettledReplay(entries)) return '';
232
236
  const marks = {
233
237
  'claude-code': { user: '❯', agent: '⏺' },
234
238
  codex: { user: '›', agent: '•' },