clideck 1.26.2 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agent-presets.json +5 -1
- package/bin/notify-helper.js +18 -0
- package/handlers.js +102 -18
- package/package.json +1 -1
- package/public/index.html +11 -1
- package/public/js/app.js +21 -6
- package/public/js/creator.js +37 -14
- package/public/js/folder-picker.js +87 -4
- package/public/js/settings.js +31 -41
- package/public/js/terminals.js +8 -5
- package/public/tailwind.css +1 -1
- package/server.js +69 -1
- package/sessions.js +14 -13
- package/telemetry-receiver.js +55 -17
- package/transcript.js +5 -3
- package/utils.js +2 -2
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ npx clideck
|
|
|
26
26
|
|
|
27
27
|
Open [http://localhost:4000](http://localhost:4000). Click **+**, pick an agent and optionally a project and role, start working.
|
|
28
28
|
|
|
29
|
-
New users get 3 built-in
|
|
29
|
+
New users get 3 built-in roles (Programmer, Reviewer, Product Manager) and 3 starter prompts in the prompt library.
|
|
30
30
|
|
|
31
31
|
Or install globally:
|
|
32
32
|
|
|
@@ -38,7 +38,7 @@ clideck
|
|
|
38
38
|
## What You Get
|
|
39
39
|
|
|
40
40
|
- **Roles** — define reusable agent identities (Programmer, Reviewer, PM) and assign them when creating sessions. Instructions are injected into the agent automatically.
|
|
41
|
-
- **Autopilot** — project-level workflow routing. Watches your role-assigned agents, waits for them to finish, forwards output to the next specialist. Fingerprints each output, tracks handoff history, and guards against repeat loops.
|
|
41
|
+
- **Autopilot** — project-level workflow routing. Watches your role-assigned agents, waits for them to finish, forwards output to the next specialist. Fingerprints each output, tracks handoff history, and guards against repeat loops. Supports 8 LLM providers (Anthropic, OpenAI, Google, Groq, xAI, Mistral, OpenRouter, Cerebras). Notifies you when work is complete or blocked.
|
|
42
42
|
- **Mobile access** — check on your agents from your phone with a QR scan. E2E encrypted.
|
|
43
43
|
- **Live working/idle status** — see which agent is thinking and which is waiting for you, without checking each terminal
|
|
44
44
|
- **Session resume** — close clideck, reopen it tomorrow, pick up where you left off
|
package/agent-presets.json
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
"resumeCommand": "claude --resume {{sessionId}}",
|
|
11
11
|
"sessionIdPattern": "Session ID:\\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})",
|
|
12
12
|
"outputMarker": "\u23fa",
|
|
13
|
-
"telemetryConfigPath": "
|
|
13
|
+
"telemetryConfigPath": "~/.claude/settings.json",
|
|
14
|
+
"telemetrySetup": "Required for working/idle status, Autopilot, notifications, and mobile remote.\n\nCliDeck will add start/stop hooks to ~/.claude/settings.json. Claude will ask for one-time approval on next launch.",
|
|
15
|
+
"telemetryAutoSetup": {
|
|
16
|
+
"label": "Patch Claude"
|
|
17
|
+
},
|
|
14
18
|
"telemetryEnv": {
|
|
15
19
|
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
|
|
16
20
|
"OTEL_LOGS_EXPORTER": "otlp",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Tiny helper for Codex notify hook.
|
|
3
|
+
// Usage: node notify-helper.js <port> <json-payload>
|
|
4
|
+
// Codex appends the JSON payload as the last argv argument.
|
|
5
|
+
// Port is passed as the first argument by the notify config.
|
|
6
|
+
|
|
7
|
+
const port = parseInt(process.argv[2], 10);
|
|
8
|
+
const payload = process.argv[process.argv.length - 1];
|
|
9
|
+
if (!port || !payload || payload === String(port)) process.exit(0);
|
|
10
|
+
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const req = http.request({
|
|
13
|
+
hostname: 'localhost', port, path: '/hook/codex/stop',
|
|
14
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
timeout: 2000,
|
|
16
|
+
});
|
|
17
|
+
req.on('error', () => {});
|
|
18
|
+
req.end(payload);
|
package/handlers.js
CHANGED
|
@@ -77,11 +77,18 @@ function detectTelemetryConfig(c) {
|
|
|
77
77
|
if (!preset) continue;
|
|
78
78
|
let detected = false;
|
|
79
79
|
if (preset.presetId === 'claude-code') {
|
|
80
|
-
|
|
80
|
+
try {
|
|
81
|
+
const s = JSON.parse(readFileSync(join(home, '.claude', 'settings.json'), 'utf8'));
|
|
82
|
+
const hooks = s.hooks || {};
|
|
83
|
+
const has = (arr, path) => arr?.some(h => h.hooks?.some(x => x.url?.includes('/hook/claude/' + path)));
|
|
84
|
+
detected = has(hooks.UserPromptSubmit, 'start') && has(hooks.Stop, 'stop') && has(hooks.StopFailure, 'stop')
|
|
85
|
+
&& has(hooks.PreToolUse, 'menu')
|
|
86
|
+
&& hooks.Notification?.some(h => h.matcher === 'idle_prompt' && h.hooks?.some(x => x.url?.includes('/hook/claude/idle')));
|
|
87
|
+
} catch {}
|
|
81
88
|
} else if (preset.presetId === 'codex') {
|
|
82
89
|
try {
|
|
83
90
|
const content = readFileSync(join(home, '.codex', 'config.toml'), 'utf8');
|
|
84
|
-
detected = content.includes('[otel]') && content.includes(`localhost:${port}`);
|
|
91
|
+
detected = content.includes('[otel]') && content.includes(`localhost:${port}`) && content.includes('notify-helper');
|
|
85
92
|
} catch {}
|
|
86
93
|
} else if (preset.presetId === 'gemini-cli') {
|
|
87
94
|
try {
|
|
@@ -139,7 +146,17 @@ function onConnection(ws) {
|
|
|
139
146
|
sessions.broadcast({ type: 'screen.updated', id: msg.id });
|
|
140
147
|
const sess = sessions.getSessions().get(msg.id);
|
|
141
148
|
if (sess) {
|
|
142
|
-
|
|
149
|
+
let choices = require('./transcript').detectMenu(msg.lines, sess.presetId);
|
|
150
|
+
// Codex: only trust menu detection if last OTEL event was response.completed
|
|
151
|
+
if (choices && sess.presetId === 'codex') {
|
|
152
|
+
const last = require('./telemetry-receiver').getLastEvent(msg.id);
|
|
153
|
+
if (!last.startsWith('codex.sse_event:response.completed')) {
|
|
154
|
+
console.log(`[codex] menu rejected — lastEvent=${last} session=${msg.id.slice(0,8)}`);
|
|
155
|
+
choices = null;
|
|
156
|
+
} else {
|
|
157
|
+
console.log(`[codex] menu accepted session=${msg.id.slice(0,8)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
143
160
|
// Auto-approve: send Enter immediately when menu detected
|
|
144
161
|
if (choices && plugins.shouldAutoApproveMenu(msg.id)) {
|
|
145
162
|
sessions.input({ id: msg.id, data: '\r' });
|
|
@@ -150,6 +167,7 @@ function onConnection(ws) {
|
|
|
150
167
|
sessions.broadcast({ type: 'session.menu', id: msg.id, choices: choices || [] });
|
|
151
168
|
if (choices) {
|
|
152
169
|
plugins.notifyMenu(msg.id, choices);
|
|
170
|
+
if (sess.presetId === 'codex') require('./telemetry-receiver').cancelCodexMenuPoll(msg.id);
|
|
153
171
|
sessions.broadcast({ type: 'session.status', id: msg.id, working: false, source: 'menu' });
|
|
154
172
|
}
|
|
155
173
|
}
|
|
@@ -187,19 +205,13 @@ function onConnection(ws) {
|
|
|
187
205
|
case 'telemetry.autosetup': {
|
|
188
206
|
const preset = presets.find(p => p.presetId === msg.presetId);
|
|
189
207
|
if (!preset?.telemetryAutoSetup) break;
|
|
190
|
-
// Only allow if caller has a live session using this preset's command
|
|
191
|
-
const liveSessions = sessions.list();
|
|
192
|
-
const hasLive = liveSessions.some(s => {
|
|
193
|
-
const cmd = cfg.commands.find(c => c.id === s.commandId);
|
|
194
|
-
return cmd && binName(cmd.command) === binName(preset.command);
|
|
195
|
-
});
|
|
196
|
-
if (!hasLive) break;
|
|
197
208
|
const result = applyTelemetryConfig(preset);
|
|
198
|
-
// Persist telemetry state in config
|
|
199
209
|
for (const cmd of cfg.commands) {
|
|
200
210
|
if (binName(cmd.command) === binName(preset.command)) {
|
|
201
211
|
cmd.telemetryEnabled = result.success;
|
|
202
212
|
cmd.telemetryStatus = result.success ? { ok: true } : { ok: false, error: result.message };
|
|
213
|
+
// Enable the agent when setup succeeds, disable if it fails
|
|
214
|
+
if (result.success) cmd.enabled = true;
|
|
203
215
|
}
|
|
204
216
|
}
|
|
205
217
|
config.save(cfg);
|
|
@@ -269,13 +281,29 @@ function onConnection(ws) {
|
|
|
269
281
|
|
|
270
282
|
case 'dirs.list': {
|
|
271
283
|
const target = msg.path || cfg.defaultPath;
|
|
272
|
-
const result = listDirs(target);
|
|
284
|
+
const result = listDirs(target, !!msg.showHidden);
|
|
273
285
|
const entries = Array.isArray(result) ? result : [];
|
|
274
286
|
const error = result.error || undefined;
|
|
275
287
|
ws.send(JSON.stringify({ type: 'dirs', path: target, entries, error }));
|
|
276
288
|
break;
|
|
277
289
|
}
|
|
278
290
|
|
|
291
|
+
case 'dirs.mkdir': {
|
|
292
|
+
const name = (msg.name || '').trim();
|
|
293
|
+
if (!name || name.includes('/') || name.includes('\\') || name === '.' || name === '..') {
|
|
294
|
+
ws.send(JSON.stringify({ type: 'dirs.mkdir', success: false, error: 'Invalid folder name' }));
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
const dirPath = join(msg.parent, name);
|
|
298
|
+
try {
|
|
299
|
+
mkdirSync(dirPath);
|
|
300
|
+
ws.send(JSON.stringify({ type: 'dirs.mkdir', success: true, path: dirPath }));
|
|
301
|
+
} catch (e) {
|
|
302
|
+
ws.send(JSON.stringify({ type: 'dirs.mkdir', success: false, error: e.message }));
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
279
307
|
case 'plugin.settings.update':
|
|
280
308
|
plugins.updateSetting(msg.pluginId, msg.key, msg.value);
|
|
281
309
|
sessions.broadcast({ type: 'plugins', list: plugins.getInfo() });
|
|
@@ -362,15 +390,54 @@ function applyTelemetryConfig(preset) {
|
|
|
362
390
|
const home = os.homedir();
|
|
363
391
|
|
|
364
392
|
try {
|
|
393
|
+
if (preset.presetId === 'claude-code') {
|
|
394
|
+
const configPath = join(home, '.claude', 'settings.json');
|
|
395
|
+
let settings = {};
|
|
396
|
+
if (existsSync(configPath)) {
|
|
397
|
+
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
398
|
+
}
|
|
399
|
+
const hooks = settings.hooks || {};
|
|
400
|
+
const endpoint = `http://localhost:${port}/hook/claude`;
|
|
401
|
+
const clideckHook = (url) => ({ hooks: [{ type: 'http', url }] });
|
|
402
|
+
const hasClideck = (arr, path) => arr?.some(h => h.hooks?.some(x => x.url?.includes('/hook/claude/' + path)));
|
|
403
|
+
if (hasClideck(hooks.UserPromptSubmit, 'start') && hasClideck(hooks.Stop, 'stop') && hasClideck(hooks.StopFailure, 'stop') && hasClideck(hooks.PreToolUse, 'menu') && hooks.Notification?.some(h => h.matcher === 'idle_prompt' && h.hooks?.some(x => x.url?.includes('/hook/claude/idle')))) {
|
|
404
|
+
return { success: true, message: 'Already configured' };
|
|
405
|
+
}
|
|
406
|
+
if (!hasClideck(hooks.UserPromptSubmit, 'start')) hooks.UserPromptSubmit = [...(hooks.UserPromptSubmit || []), clideckHook(`${endpoint}/start`)];
|
|
407
|
+
if (!hasClideck(hooks.Stop, 'stop')) hooks.Stop = [...(hooks.Stop || []), clideckHook(`${endpoint}/stop`)];
|
|
408
|
+
if (!hasClideck(hooks.StopFailure, 'stop')) hooks.StopFailure = [...(hooks.StopFailure || []), clideckHook(`${endpoint}/stop`)];
|
|
409
|
+
if (!hasClideck(hooks.Notification, 'idle')) hooks.Notification = [...(hooks.Notification || []), { matcher: 'idle_prompt', ...clideckHook(`${endpoint}/idle`) }];
|
|
410
|
+
if (!hasClideck(hooks.PreToolUse, 'menu')) hooks.PreToolUse = [...(hooks.PreToolUse || []), clideckHook(`${endpoint}/menu`)];
|
|
411
|
+
settings.hooks = hooks;
|
|
412
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
413
|
+
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
414
|
+
return { success: true, message: 'Added hooks to ~/.claude/settings.json — Claude will ask for one-time approval' };
|
|
415
|
+
}
|
|
416
|
+
|
|
365
417
|
if (preset.presetId === 'codex') {
|
|
366
418
|
const configPath = join(home, '.codex', 'config.toml');
|
|
367
419
|
let content = '';
|
|
368
420
|
if (existsSync(configPath)) content = readFileSync(configPath, 'utf8');
|
|
369
|
-
|
|
370
|
-
const
|
|
421
|
+
const hasOtel = content.includes('[otel]');
|
|
422
|
+
const hasNotify = content.includes('notify-helper');
|
|
423
|
+
if (hasOtel && hasNotify) return { success: true, message: 'Already configured' };
|
|
424
|
+
if (!hasNotify) {
|
|
425
|
+
const helperPath = join(__dirname, 'bin', 'notify-helper.js').replace(/\\/g, '/');
|
|
426
|
+
const notifyLine = `notify = ["${process.execPath.replace(/\\/g, '/')}", "${helperPath}", "${port}"]\n`;
|
|
427
|
+
// Insert before the first [section] so it stays top-level
|
|
428
|
+
const firstSection = content.search(/^\[/m);
|
|
429
|
+
if (firstSection >= 0) {
|
|
430
|
+
content = content.slice(0, firstSection) + notifyLine + '\n' + content.slice(firstSection);
|
|
431
|
+
} else {
|
|
432
|
+
content = content + '\n' + notifyLine;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!hasOtel) {
|
|
436
|
+
content = content.trimEnd() + `\n\n[otel]\nexporter = { otlp-http = { endpoint = "http://localhost:${port}/v1/logs", protocol = "json" } }\n`;
|
|
437
|
+
}
|
|
371
438
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
372
|
-
writeFileSync(configPath, content
|
|
373
|
-
return { success: true, message: 'Added
|
|
439
|
+
writeFileSync(configPath, content);
|
|
440
|
+
return { success: true, message: 'Added otel + notify to ~/.codex/config.toml' };
|
|
374
441
|
}
|
|
375
442
|
|
|
376
443
|
if (preset.presetId === 'gemini-cli') {
|
|
@@ -415,14 +482,31 @@ function removeTelemetryConfig(preset) {
|
|
|
415
482
|
const home = os.homedir();
|
|
416
483
|
|
|
417
484
|
try {
|
|
485
|
+
if (preset.presetId === 'claude-code') {
|
|
486
|
+
const configPath = join(home, '.claude', 'settings.json');
|
|
487
|
+
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
488
|
+
let settings = {};
|
|
489
|
+
try { settings = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
490
|
+
if (!settings.hooks) return { success: true, message: 'No hooks to remove' };
|
|
491
|
+
for (const event of ['UserPromptSubmit', 'Stop', 'StopFailure', 'Notification', 'PreToolUse']) {
|
|
492
|
+
const arr = settings.hooks[event];
|
|
493
|
+
if (!arr) continue;
|
|
494
|
+
settings.hooks[event] = arr.filter(h => !h.hooks?.some(x => x.url?.includes('/hook/claude/')));
|
|
495
|
+
if (!settings.hooks[event].length) delete settings.hooks[event];
|
|
496
|
+
}
|
|
497
|
+
if (!Object.keys(settings.hooks).length) delete settings.hooks;
|
|
498
|
+
writeFileSync(configPath, JSON.stringify(settings, null, 2) + '\n');
|
|
499
|
+
return { success: true, message: 'Removed CliDeck hooks from ~/.claude/settings.json' };
|
|
500
|
+
}
|
|
501
|
+
|
|
418
502
|
if (preset.presetId === 'codex') {
|
|
419
503
|
const configPath = join(home, '.codex', 'config.toml');
|
|
420
504
|
if (!existsSync(configPath)) return { success: true, message: 'No config file to clean' };
|
|
421
505
|
let content = readFileSync(configPath, 'utf8');
|
|
422
|
-
// Remove [otel] section and everything until the next section or EOF
|
|
423
506
|
content = content.replace(/\n?\[otel\][^\[]*/, '');
|
|
507
|
+
content = content.replace(/\n?notify\s*=\s*\[.*?notify-helper.*?\]\s*/g, '');
|
|
424
508
|
writeFileSync(configPath, content.trimEnd() + '\n');
|
|
425
|
-
return { success: true, message: 'Removed
|
|
509
|
+
return { success: true, message: 'Removed otel + notify from ~/.codex/config.toml' };
|
|
426
510
|
}
|
|
427
511
|
|
|
428
512
|
if (preset.presetId === 'gemini-cli') {
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -461,7 +461,17 @@
|
|
|
461
461
|
<!-- Folder picker -->
|
|
462
462
|
<div id="folder-picker" class="absolute inset-0 z-[300] bg-black/60 backdrop-blur-sm hidden items-center justify-center">
|
|
463
463
|
<div class="bg-slate-800 border border-slate-600 rounded-xl shadow-2xl shadow-black/50 w-[420px] max-h-[460px] flex flex-col">
|
|
464
|
-
<div class="px-4 py-3 border-b border-slate-700
|
|
464
|
+
<div class="px-4 py-3 border-b border-slate-700 flex items-center justify-between">
|
|
465
|
+
<span class="text-sm font-semibold">Choose Directory</span>
|
|
466
|
+
<div class="flex items-center gap-2">
|
|
467
|
+
<button id="fp-new-folder" class="p-1 rounded hover:bg-slate-700 text-slate-400 hover:text-slate-200 transition-colors" title="New folder">
|
|
468
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
|
|
469
|
+
</button>
|
|
470
|
+
<button id="fp-toggle-hidden" class="p-1 rounded hover:bg-slate-700 text-slate-500 transition-colors" title="Show hidden files">
|
|
471
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
|
472
|
+
</button>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
465
475
|
<div id="fp-path" class="px-4 py-2 text-xs text-slate-400 border-b border-slate-700 break-all"></div>
|
|
466
476
|
<div id="fp-listing" class="flex-1 overflow-y-auto py-1 min-h-[200px]"></div>
|
|
467
477
|
<div class="px-4 py-3 border-t border-slate-700 flex justify-end gap-2">
|
package/public/js/app.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { state, send } from './state.js';
|
|
2
|
-
import { esc, binName } from './utils.js';
|
|
2
|
+
import { esc, binName, resolveIconPath } from './utils.js';
|
|
3
3
|
import { addTerminal, removeTerminal, select, startRename, startProjectRename, setSessionTheme, openMenu, closeMenu, setStatus, updateMuteIndicator, updatePreview, markUnread, applyFilter, setTab, renderResumable, regroupSessions, toggleProjectCollapse, setSessionProject, estimateSize, restartComplete, positionMenu, addPill, updatePill, removePill, appendPillLog, setPillLogs, closePillLog } from './terminals.js';
|
|
4
4
|
import { renderSettings, updateVersionFooter } from './settings.js';
|
|
5
5
|
import { openCreator, closeCreator, refreshCreator } from './creator.js';
|
|
6
|
-
import { handleDirsResponse, openFolderPicker } from './folder-picker.js';
|
|
6
|
+
import { handleDirsResponse, handleMkdirResponse, openFolderPicker } from './folder-picker.js';
|
|
7
7
|
import { confirmClose } from './confirm.js';
|
|
8
8
|
import { applyTheme } from './profiles.js';
|
|
9
9
|
import { toggleMode, applyMode } from './color-mode.js';
|
|
@@ -39,6 +39,7 @@ function connect() {
|
|
|
39
39
|
renderSettings();
|
|
40
40
|
renderPrompts();
|
|
41
41
|
renderRoles();
|
|
42
|
+
refreshCreator();
|
|
42
43
|
for (const [, entry] of state.terms) applyTheme(entry.term, entry.themeId);
|
|
43
44
|
break;
|
|
44
45
|
case 'themes':
|
|
@@ -82,6 +83,17 @@ function connect() {
|
|
|
82
83
|
case 'session.status':
|
|
83
84
|
setStatus(msg.id, msg.working);
|
|
84
85
|
break;
|
|
86
|
+
// Server requests screen capture (e.g. after PermissionRequest hook)
|
|
87
|
+
case 'screen.capture': {
|
|
88
|
+
const ce = state.terms.get(msg.id);
|
|
89
|
+
if (ce?.term) {
|
|
90
|
+
const buf = ce.term.buffer.active;
|
|
91
|
+
const lines = [];
|
|
92
|
+
for (let i = 0; i < buf.length; i++) { const line = buf.getLine(i); if (line) lines.push(line.translateToString(true)); }
|
|
93
|
+
send({ type: 'terminal.buffer', id: msg.id, lines });
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
85
97
|
// Bridge preview text (OpenCode plugin)
|
|
86
98
|
case 'session.preview': {
|
|
87
99
|
const pe = state.terms.get(msg.id);
|
|
@@ -145,6 +157,9 @@ function connect() {
|
|
|
145
157
|
case 'dirs':
|
|
146
158
|
handleDirsResponse(msg);
|
|
147
159
|
break;
|
|
160
|
+
case 'dirs.mkdir':
|
|
161
|
+
handleMkdirResponse(msg);
|
|
162
|
+
break;
|
|
148
163
|
case 'session.theme': {
|
|
149
164
|
const entry = state.terms.get(msg.id);
|
|
150
165
|
if (entry) {
|
|
@@ -179,16 +194,15 @@ function connect() {
|
|
|
179
194
|
const actionsEl = toast.querySelector('.setup-actions');
|
|
180
195
|
if (msg.success) {
|
|
181
196
|
const sid = toast.dataset.sessionId;
|
|
182
|
-
const cmdId = toast.dataset.commandId;
|
|
183
197
|
actionsEl.innerHTML = `
|
|
184
198
|
<div class="flex-1 flex items-center gap-1.5 text-xs text-emerald-400">
|
|
185
199
|
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg>
|
|
186
200
|
Configured
|
|
187
201
|
</div>
|
|
188
|
-
|
|
202
|
+
${sid ? `<button class="restart-btn px-3 py-2 text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors">Restart Session</button>` : ''}
|
|
189
203
|
<button class="dismiss-btn px-3 py-2 text-xs text-slate-500 hover:text-slate-300 transition-colors">Dismiss</button>`;
|
|
190
204
|
actionsEl.querySelector('.dismiss-btn').onclick = () => toast.remove();
|
|
191
|
-
actionsEl.querySelector('.restart-btn').onclick = () => {
|
|
205
|
+
if (sid) actionsEl.querySelector('.restart-btn').onclick = () => {
|
|
192
206
|
const entry = state.terms.get(sid);
|
|
193
207
|
send({ type: 'session.restart', id: sid, themeId: entry?.themeId, cols: entry?.term?.cols, rows: entry?.term?.rows });
|
|
194
208
|
toast.remove();
|
|
@@ -384,6 +398,7 @@ document.querySelectorAll('.filter-tab').forEach(btn => {
|
|
|
384
398
|
|
|
385
399
|
// Telemetry setup notification — shown once per agent type
|
|
386
400
|
const shownSetup = new Set();
|
|
401
|
+
document.addEventListener('clideck:setup', (e) => showTelemetrySetup(e.detail.commandId, null));
|
|
387
402
|
function showTelemetrySetup(commandId, sessionId) {
|
|
388
403
|
const cmd = state.cfg.commands.find(c => c.id === commandId);
|
|
389
404
|
if (!cmd) return;
|
|
@@ -400,7 +415,7 @@ function showTelemetrySetup(commandId, sessionId) {
|
|
|
400
415
|
const [desc, ...codeParts] = setupText.split('\n\n');
|
|
401
416
|
const code = codeParts.join('\n\n');
|
|
402
417
|
const auto = preset.telemetryAutoSetup;
|
|
403
|
-
const iconSrc = preset.icon?.startsWith('/') ? preset.icon : null;
|
|
418
|
+
const iconSrc = preset.icon?.startsWith('/') ? resolveIconPath(preset.icon) : null;
|
|
404
419
|
const title = preset.bridge ? 'Bridge Plugin' : 'Status Tracking';
|
|
405
420
|
|
|
406
421
|
const toast = document.createElement('div');
|
package/public/js/creator.js
CHANGED
|
@@ -35,25 +35,35 @@ function isPresetMissing(p) {
|
|
|
35
35
|
return cmd.command === p.command;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// True if preset binary exists but telemetry/hooks are not configured yet
|
|
39
|
+
function isPresetUnpatched(p) {
|
|
40
|
+
if (p.available === false || !p.telemetryAutoSetup) return false;
|
|
41
|
+
const cmd = findCommandForPreset(p);
|
|
42
|
+
return !cmd || !cmd.telemetryEnabled;
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
function renderPresetButtons() {
|
|
39
46
|
return sortedPresets().map(p => {
|
|
40
|
-
|
|
41
|
-
if (missing) {
|
|
47
|
+
if (isPresetMissing(p)) {
|
|
42
48
|
return `
|
|
43
49
|
<div class="w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-left text-slate-500">
|
|
44
50
|
<span class="opacity-40">${agentIcon(p.icon, 24)}</span>
|
|
45
|
-
<span class="flex-1 min-w-0">
|
|
46
|
-
<span>${esc(p.name)}</span>
|
|
47
|
-
</span>
|
|
51
|
+
<span class="flex-1 min-w-0">${esc(p.name)}</span>
|
|
48
52
|
<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>
|
|
49
53
|
</div>`;
|
|
50
54
|
}
|
|
55
|
+
if (isPresetUnpatched(p)) {
|
|
56
|
+
return `
|
|
57
|
+
<div class="w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-sm text-left text-slate-500">
|
|
58
|
+
<span class="opacity-40">${agentIcon(p.icon, 24)}</span>
|
|
59
|
+
<span class="flex-1 min-w-0">${esc(p.name)}</span>
|
|
60
|
+
<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>
|
|
61
|
+
</div>`;
|
|
62
|
+
}
|
|
51
63
|
return `
|
|
52
64
|
<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}">
|
|
53
65
|
<span>${agentIcon(p.icon, 24)}</span>
|
|
54
|
-
<span class="flex-1 min-w-0">
|
|
55
|
-
<span>${esc(p.name)}</span>
|
|
56
|
-
</span>
|
|
66
|
+
<span class="flex-1 min-w-0">${esc(p.name)}</span>
|
|
57
67
|
</button>`;
|
|
58
68
|
}).join('');
|
|
59
69
|
}
|
|
@@ -92,8 +102,8 @@ function createFromPreset(preset, sessionName, cwd, projectId, roleId) {
|
|
|
92
102
|
resumeCommand: preset.resumeCommand,
|
|
93
103
|
sessionIdPattern: preset.sessionIdPattern,
|
|
94
104
|
outputMarker: preset.outputMarker || null,
|
|
95
|
-
telemetryEnabled:
|
|
96
|
-
telemetryStatus:
|
|
105
|
+
telemetryEnabled: false,
|
|
106
|
+
telemetryStatus: null,
|
|
97
107
|
bridge: preset.bridge,
|
|
98
108
|
};
|
|
99
109
|
state.cfg.commands.push(cmd);
|
|
@@ -124,13 +134,13 @@ export function openCreator() {
|
|
|
124
134
|
${(state.cfg.projects?.length) ? `
|
|
125
135
|
<input type="hidden" id="creator-project" value="">
|
|
126
136
|
<button type="button" id="creator-project-trigger" class="w-full px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-400 text-left flex items-center justify-between outline-none hover:border-slate-500 transition-colors cursor-pointer mb-2">
|
|
127
|
-
<span id="creator-project-label">Select project</span>
|
|
137
|
+
<span id="creator-project-label">Select project <span class="opacity-40">- optional</span></span>
|
|
128
138
|
<span class="text-slate-600 ml-2">▾</span>
|
|
129
139
|
</button>` : ''}
|
|
130
140
|
${(state.cfg.roles?.length) ? `
|
|
131
141
|
<input type="hidden" id="creator-role" value="">
|
|
132
142
|
<button type="button" id="creator-role-trigger" class="w-full px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-400 text-left flex items-center justify-between outline-none hover:border-slate-500 transition-colors cursor-pointer mb-2">
|
|
133
|
-
<span id="creator-role-label">Select role</span>
|
|
143
|
+
<span id="creator-role-label">Select role <span class="opacity-40">- optional</span></span>
|
|
134
144
|
<span class="text-slate-600 ml-2">▾</span>
|
|
135
145
|
</button>` : ''}
|
|
136
146
|
<input id="creator-name" type="text" maxlength="35" placeholder="Session / Agent name"
|
|
@@ -199,7 +209,7 @@ export function openCreator() {
|
|
|
199
209
|
if (!item) return;
|
|
200
210
|
hidden.value = item.dataset.value;
|
|
201
211
|
const proj = projects.find(p => p.id === item.dataset.value);
|
|
202
|
-
label.
|
|
212
|
+
label.innerHTML = proj ? esc(proj.name) : 'Select project <span class="opacity-40">- optional</span>';
|
|
203
213
|
// Auto-set working directory from project path
|
|
204
214
|
if (proj?.path) cwdInput.value = proj.path;
|
|
205
215
|
else cwdInput.value = defaultPath;
|
|
@@ -252,7 +262,7 @@ export function openCreator() {
|
|
|
252
262
|
if (!item) return;
|
|
253
263
|
hidden.value = item.dataset.value;
|
|
254
264
|
const roleName = item.dataset.name;
|
|
255
|
-
label.
|
|
265
|
+
label.innerHTML = roleName ? esc(roleName) : 'Select role <span class="opacity-40">- optional</span>';
|
|
256
266
|
// Auto-fill session name from role name (only if user hasn't typed a custom name)
|
|
257
267
|
if (roleName && (!nameInput.value.trim() || nameInput.dataset.autoFilled === '1')) {
|
|
258
268
|
nameInput.value = roleName;
|
|
@@ -286,6 +296,19 @@ export function openCreator() {
|
|
|
286
296
|
if (preset?.installCmd) showInstallToast(preset);
|
|
287
297
|
return;
|
|
288
298
|
}
|
|
299
|
+
const setupBtn = e.target.closest('.setup-btn');
|
|
300
|
+
if (setupBtn) {
|
|
301
|
+
const preset = state.presets.find(p => p.presetId === setupBtn.dataset.preset);
|
|
302
|
+
if (!preset) return;
|
|
303
|
+
let cmd = findCommandForPreset(preset);
|
|
304
|
+
if (!cmd) {
|
|
305
|
+
cmd = { id: crypto.randomUUID(), presetId: preset.presetId, label: preset.name, icon: preset.icon, command: preset.command, enabled: true, defaultPath: '', isAgent: preset.isAgent, canResume: preset.canResume, resumeCommand: preset.resumeCommand, sessionIdPattern: preset.sessionIdPattern, outputMarker: preset.outputMarker || null, telemetryEnabled: false, telemetryStatus: null, bridge: preset.bridge };
|
|
306
|
+
state.cfg.commands.push(cmd);
|
|
307
|
+
send({ type: 'config.update', config: state.cfg });
|
|
308
|
+
}
|
|
309
|
+
document.dispatchEvent(new CustomEvent('clideck:setup', { detail: { commandId: cmd.id } }));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
289
312
|
const btn = e.target.closest('.preset-btn');
|
|
290
313
|
if (!btn) return;
|
|
291
314
|
const preset = state.presets.find(p => p.presetId === btn.dataset.preset);
|
|
@@ -19,9 +19,12 @@ const overlay = document.getElementById('folder-picker');
|
|
|
19
19
|
const pathBar = document.getElementById('fp-path');
|
|
20
20
|
const listing = document.getElementById('fp-listing');
|
|
21
21
|
const selectBtn = document.getElementById('fp-select');
|
|
22
|
+
const hiddenBtn = document.getElementById('fp-toggle-hidden');
|
|
23
|
+
const newFolderBtn = document.getElementById('fp-new-folder');
|
|
22
24
|
let currentPath = '';
|
|
23
25
|
let pendingPath = '';
|
|
24
26
|
let onSelect = null;
|
|
27
|
+
let showHidden = false;
|
|
25
28
|
|
|
26
29
|
export function openFolderPicker(startPath, callback) {
|
|
27
30
|
currentPath = '';
|
|
@@ -35,6 +38,7 @@ export function closeFolderPicker() {
|
|
|
35
38
|
overlay.classList.add('hidden');
|
|
36
39
|
overlay.classList.remove('flex');
|
|
37
40
|
onSelect = null;
|
|
41
|
+
closeNewFolderInput();
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
function navigate(path) {
|
|
@@ -42,7 +46,8 @@ function navigate(path) {
|
|
|
42
46
|
pathBar.textContent = path;
|
|
43
47
|
listing.innerHTML = '<div class="p-4 text-center text-slate-500 text-sm">Loading...</div>';
|
|
44
48
|
selectBtn.disabled = true;
|
|
45
|
-
|
|
49
|
+
closeNewFolderInput();
|
|
50
|
+
send({ type: 'dirs.list', path, showHidden });
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
export function handleDirsResponse(msg) {
|
|
@@ -62,12 +67,90 @@ export function handleDirsResponse(msg) {
|
|
|
62
67
|
if (msg.entries.length === 0 && !html) {
|
|
63
68
|
html = '<div class="p-4 text-center text-slate-500 text-sm">Empty directory</div>';
|
|
64
69
|
}
|
|
65
|
-
html += msg.entries.map(name =>
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
html += msg.entries.map(name => {
|
|
71
|
+
const dimClass = name.startsWith('.') ? ' text-slate-500' : ' text-slate-200';
|
|
72
|
+
return `<div class="fp-item px-4 py-1.5 cursor-pointer hover:bg-slate-700 text-sm${dimClass} transition-colors" data-path="${esc(joinChild(currentPath, name))}">${esc(name)}</div>`;
|
|
73
|
+
}).join('');
|
|
68
74
|
listing.innerHTML = html;
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
// --- Hidden files toggle ---
|
|
78
|
+
|
|
79
|
+
function updateHiddenBtn() {
|
|
80
|
+
hiddenBtn.classList.toggle('text-slate-200', showHidden);
|
|
81
|
+
hiddenBtn.classList.toggle('text-slate-500', !showHidden);
|
|
82
|
+
hiddenBtn.title = showHidden ? 'Hide hidden files' : 'Show hidden files';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
hiddenBtn.addEventListener('click', () => {
|
|
86
|
+
showHidden = !showHidden;
|
|
87
|
+
updateHiddenBtn();
|
|
88
|
+
if (currentPath) navigate(currentPath);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// --- New folder inline input ---
|
|
92
|
+
|
|
93
|
+
let newFolderActive = false;
|
|
94
|
+
|
|
95
|
+
function closeNewFolderInput() {
|
|
96
|
+
if (!newFolderActive) return;
|
|
97
|
+
newFolderActive = false;
|
|
98
|
+
const row = listing.querySelector('.fp-new-folder-row');
|
|
99
|
+
if (row) row.remove();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function openNewFolderInput() {
|
|
103
|
+
if (newFolderActive || !currentPath) return;
|
|
104
|
+
newFolderActive = true;
|
|
105
|
+
const row = document.createElement('div');
|
|
106
|
+
row.className = 'fp-new-folder-row flex items-center gap-2 px-4 py-1.5';
|
|
107
|
+
row.innerHTML = `
|
|
108
|
+
<svg class="flex-shrink-0 text-slate-400" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
|
|
109
|
+
<input type="text" class="fp-new-folder-input flex-1 bg-slate-700 border border-slate-600 rounded px-2 py-0.5 text-sm text-slate-200 placeholder-slate-500 outline-none focus:border-blue-500 transition-colors" placeholder="Folder name" spellcheck="false" />
|
|
110
|
+
<button class="fp-new-folder-ok p-0.5 rounded hover:bg-slate-700 text-emerald-400 hover:text-emerald-300 transition-colors" title="Create">
|
|
111
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
112
|
+
</button>
|
|
113
|
+
<button class="fp-new-folder-no p-0.5 rounded hover:bg-slate-700 text-slate-400 hover:text-slate-300 transition-colors" title="Cancel">
|
|
114
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
115
|
+
</button>`;
|
|
116
|
+
listing.prepend(row);
|
|
117
|
+
const input = row.querySelector('.fp-new-folder-input');
|
|
118
|
+
input.focus();
|
|
119
|
+
|
|
120
|
+
function submit() {
|
|
121
|
+
const name = input.value.trim();
|
|
122
|
+
if (!name) { closeNewFolderInput(); return; }
|
|
123
|
+
input.disabled = true;
|
|
124
|
+
send({ type: 'dirs.mkdir', parent: currentPath, name });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
input.addEventListener('keydown', (e) => {
|
|
128
|
+
if (e.key === 'Enter') { e.preventDefault(); submit(); }
|
|
129
|
+
if (e.key === 'Escape') { e.preventDefault(); closeNewFolderInput(); }
|
|
130
|
+
});
|
|
131
|
+
row.querySelector('.fp-new-folder-ok').addEventListener('click', submit);
|
|
132
|
+
row.querySelector('.fp-new-folder-no').addEventListener('click', closeNewFolderInput);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
newFolderBtn.addEventListener('click', openNewFolderInput);
|
|
136
|
+
|
|
137
|
+
export function handleMkdirResponse(msg) {
|
|
138
|
+
if (!newFolderActive) return;
|
|
139
|
+
closeNewFolderInput();
|
|
140
|
+
if (msg.success) {
|
|
141
|
+
navigate(msg.path);
|
|
142
|
+
} else {
|
|
143
|
+
// Show error inline briefly
|
|
144
|
+
const err = document.createElement('div');
|
|
145
|
+
err.className = 'px-4 py-1.5 text-xs text-red-400';
|
|
146
|
+
err.textContent = msg.error || 'Failed to create folder';
|
|
147
|
+
listing.prepend(err);
|
|
148
|
+
setTimeout(() => err.remove(), 3000);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- Navigation and select ---
|
|
153
|
+
|
|
71
154
|
listing.addEventListener('click', (e) => {
|
|
72
155
|
const item = e.target.closest('.fp-item');
|
|
73
156
|
if (item) navigate(item.dataset.path);
|
package/public/js/settings.js
CHANGED
|
@@ -110,40 +110,14 @@ function telemetryPreset(cmd) {
|
|
|
110
110
|
function integrationSection(c) {
|
|
111
111
|
const preset = telemetryPreset(c);
|
|
112
112
|
if (!preset) return '';
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const title = 'CliDeck integration';
|
|
119
|
-
const subtitle = '(live status & resume)';
|
|
120
|
-
|
|
121
|
-
if (isClaude) {
|
|
122
|
-
return `
|
|
123
|
-
<div class="mt-3 pt-3 border-t border-slate-700/50">
|
|
124
|
-
<div class="flex items-center gap-2 text-sm text-slate-300">
|
|
125
|
-
<span style="width:8px;height:8px;border-radius:50%;background:#34d399;display:inline-block"></span>
|
|
126
|
-
${title} <span class="text-xs text-slate-500">${subtitle}</span>
|
|
127
|
-
</div>
|
|
128
|
-
<div class="mt-1 text-[11px] text-slate-500">Built-in</div>
|
|
129
|
-
</div>`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const detail = isBridge ? 'Bridge plugin' : esc(preset.telemetryConfigPath || '');
|
|
133
|
-
const toggleBg = enabled ? '#3b82f6' : '#475569';
|
|
134
|
-
const knobX = enabled ? '18' : '2';
|
|
135
|
-
|
|
113
|
+
if (!preset.telemetryAutoSetup && !preset.bridge) return '';
|
|
114
|
+
const configured = !!c.telemetryEnabled;
|
|
115
|
+
const detail = configured
|
|
116
|
+
? `<span class="text-emerald-400/80">Configured</span> — ${esc(preset.telemetryConfigPath || '')}`
|
|
117
|
+
: `<span class="text-slate-500">Not configured</span> — enable agent to set up`;
|
|
136
118
|
return `
|
|
137
119
|
<div class="mt-3 pt-3 border-t border-slate-700/50">
|
|
138
|
-
<
|
|
139
|
-
<span>${title} <span class="text-xs text-slate-500">${subtitle}</span></span>
|
|
140
|
-
<span style="position:relative;display:inline-block;width:36px;height:20px">
|
|
141
|
-
<input type="checkbox" ${enabled ? 'checked' : ''} class="agent-telemetry-toggle" data-preset="${esc(preset.presetId)}" style="position:absolute;opacity:0;width:100%;height:100%;cursor:pointer;margin:0;z-index:1">
|
|
142
|
-
<span style="position:absolute;inset:0;border-radius:10px;background:${toggleBg};transition:background .2s"></span>
|
|
143
|
-
<span style="position:absolute;top:2px;left:${knobX}px;width:16px;height:16px;border-radius:50%;background:#fff;transition:left .2s"></span>
|
|
144
|
-
</span>
|
|
145
|
-
</label>
|
|
146
|
-
<div class="mt-1 text-[11px] text-slate-500" ${!isBridge ? 'style="font-family:monospace"' : ''}>${detail}</div>
|
|
120
|
+
<div class="text-[11px] text-slate-500">${detail}</div>
|
|
147
121
|
</div>`;
|
|
148
122
|
}
|
|
149
123
|
|
|
@@ -241,8 +215,8 @@ function openPresetMenu(anchorEl) {
|
|
|
241
215
|
enabled: true, defaultPath: '', isAgent: p.isAgent, canResume: p.canResume,
|
|
242
216
|
resumeCommand: p.resumeCommand, sessionIdPattern: p.sessionIdPattern,
|
|
243
217
|
outputMarker: p.outputMarker || null,
|
|
244
|
-
telemetryEnabled:
|
|
245
|
-
telemetryStatus:
|
|
218
|
+
telemetryEnabled: false,
|
|
219
|
+
telemetryStatus: null,
|
|
246
220
|
bridge: p.bridge,
|
|
247
221
|
});
|
|
248
222
|
}
|
|
@@ -294,10 +268,27 @@ agentList.addEventListener('change', (e) => {
|
|
|
294
268
|
const card = e.target.closest('.agent-card');
|
|
295
269
|
card.querySelector('.agent-resume-fields').classList.toggle('hidden', !e.target.checked);
|
|
296
270
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
271
|
+
// When enabling an agent that needs setup, trigger auto-setup
|
|
272
|
+
if (e.target.classList.contains('agent-enabled') && e.target.checked) {
|
|
273
|
+
const idx = +e.target.closest('.agent-card').dataset.idx;
|
|
274
|
+
const cmd = state.cfg.commands[idx];
|
|
275
|
+
const preset = telemetryPreset(cmd);
|
|
276
|
+
if (preset?.telemetryAutoSetup && !cmd.telemetryEnabled) {
|
|
277
|
+
send({ type: 'telemetry.autosetup', presetId: preset.presetId });
|
|
278
|
+
return; // config broadcast from server will re-render with enabled + telemetryEnabled
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// When disabling an agent that has setup, remove patches only if no other commands of the same agent are enabled
|
|
282
|
+
if (e.target.classList.contains('agent-enabled') && !e.target.checked) {
|
|
283
|
+
const idx = +e.target.closest('.agent-card').dataset.idx;
|
|
284
|
+
const cmd = state.cfg.commands[idx];
|
|
285
|
+
const preset = telemetryPreset(cmd);
|
|
286
|
+
if (preset && cmd.telemetryEnabled) {
|
|
287
|
+
const othersEnabled = state.cfg.commands.some((c, i) => i !== idx && c.enabled && telemetryPreset(c)?.presetId === preset.presetId);
|
|
288
|
+
if (!othersEnabled) {
|
|
289
|
+
send({ type: 'telemetry.configure', presetId: preset.presetId, enable: false });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
301
292
|
}
|
|
302
293
|
saveConfig();
|
|
303
294
|
});
|
|
@@ -448,7 +439,6 @@ function saveConfig() {
|
|
|
448
439
|
state.cfg.commands = [...agentCards].map((card, i) => {
|
|
449
440
|
const existing = state.cfg.commands[i] || {};
|
|
450
441
|
const command = card.querySelector('.agent-command').value.trim() || state.cfg.defaultShell;
|
|
451
|
-
const isClaude = binName(command) === 'claude';
|
|
452
442
|
return {
|
|
453
443
|
id: existing.id || crypto.randomUUID(),
|
|
454
444
|
label: card.querySelector('.agent-name').value.trim() || 'Untitled',
|
|
@@ -461,8 +451,8 @@ function saveConfig() {
|
|
|
461
451
|
resumeCommand: card.querySelector('.agent-resume-cmd')?.value.trim() || null,
|
|
462
452
|
sessionIdPattern: existing.sessionIdPattern || null,
|
|
463
453
|
outputMarker: existing.outputMarker || null,
|
|
464
|
-
telemetryEnabled:
|
|
465
|
-
telemetryStatus:
|
|
454
|
+
telemetryEnabled: existing.telemetryEnabled || false,
|
|
455
|
+
telemetryStatus: existing.telemetryStatus || null,
|
|
466
456
|
bridge: existing.bridge,
|
|
467
457
|
};
|
|
468
458
|
});
|
package/public/js/terminals.js
CHANGED
|
@@ -340,26 +340,29 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
|
|
|
340
340
|
|
|
341
341
|
// [SCREEN-CAPTURE] extract terminal buffer when BOTH idle AND render-silent (2s)
|
|
342
342
|
// Decoupled from status: telemetry knows when agent is done, onRender knows when terminal is done
|
|
343
|
-
const
|
|
343
|
+
const _hasServerStatus = cmd?.presetId === 'claude-code' || cmd?.presetId === 'codex' || cmd?.presetId === 'gemini-cli' || cmd?.presetId === 'opencode';
|
|
344
344
|
let _screenTimer = null, _renderSilent = false;
|
|
345
345
|
function _tryScreenCapture() {
|
|
346
346
|
const entry = state.terms.get(id);
|
|
347
|
-
if (!entry?.pendingScreenCapture || (!_renderSilent && !
|
|
347
|
+
if (!entry?.pendingScreenCapture || (!_renderSilent && !_hasServerStatus) || !entry.term) return;
|
|
348
348
|
entry.pendingScreenCapture = false;
|
|
349
349
|
const buf = entry.term.buffer.active;
|
|
350
350
|
const lines = [];
|
|
351
351
|
for (let i = 0; i < buf.length; i++) { const line = buf.getLine(i); if (line) lines.push(line.translateToString(true)); }
|
|
352
352
|
send({ type: 'terminal.buffer', id, lines });
|
|
353
353
|
}
|
|
354
|
-
let
|
|
354
|
+
let _lastTyping = 0;
|
|
355
355
|
term.onData(() => { _lastTyping = Date.now(); });
|
|
356
356
|
term.onRender(() => {
|
|
357
|
-
_lastRender = Date.now();
|
|
358
357
|
_renderSilent = false;
|
|
359
358
|
clearTimeout(_screenTimer);
|
|
360
359
|
_screenTimer = setTimeout(() => { _renderSilent = true; _tryScreenCapture(); }, 2000);
|
|
361
360
|
});
|
|
362
|
-
term.onWriteParsed(() => {
|
|
361
|
+
term.onWriteParsed(() => {
|
|
362
|
+
if (Date.now() - _lastTyping < 500) return;
|
|
363
|
+
const entry = state.terms.get(id);
|
|
364
|
+
if (entry) entry.lastRenderAt = Date.now();
|
|
365
|
+
});
|
|
363
366
|
|
|
364
367
|
// Expose capture function so setStatus can trigger it when idle arrives after render silence
|
|
365
368
|
setTimeout(() => { const e = state.terms.get(id); if (e) e.tryScreenCapture = _tryScreenCapture; }, 0);
|
package/public/tailwind.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-base:#020617;--color-surface:#0f172a;--color-raised:#1e293b;--color-muted:#334155;--color-border:#475569;--color-subtle:#64748b;--color-dim:#94a3b8;--color-soft:#cbd5e1;--color-text:#e2e8f0;--color-bright:#f8fafc;--color-overlay:rgba(0,0,0,.6);--color-shadow:rgba(0,0,0,.5);--color-dialog:#1e293b;--color-stats-bg:rgba(0,0,0,.95);--color-accent-subtle:rgba(59,130,246,.15);--color-rail:#1d1f1f;--color-rail-active:#2a323f;--color-sidebar:#161717;--color-sidebar-input:#0f1010;--color-sidebar-border:#2e2f2f;--color-chat-hover:#2e2f2f;--color-chat-active:#2e2f2f;--color-rail-badge-bg:#5cbd6d;--color-rail-badge-text:#0a0a0a}.light{--color-base:#edeef1;--color-surface:#f7f8fa;--color-raised:#fff;--color-muted:#e4e6ea;--color-border:#d1d5db;--color-subtle:#8b919a;--color-dim:#5f6672;--color-soft:#3d4450;--color-text:#1a1d24;--color-bright:#0c0e12;--color-overlay:rgba(0,0,0,.25);--color-shadow:rgba(0,0,0,.08);--color-dialog:#fff;--color-stats-bg:hsla(0,0%,100%,.92);--color-accent-subtle:rgba(59,130,246,.08);--color-rail:#fcfdfd;--color-rail-active:#eae9e7;--color-sidebar:#f9fafa;--color-sidebar-input:#fff;--color-sidebar-border:#e0deda;--color-chat-hover:#f6f5f5;--color-chat-active:#eff0f0;--color-rail-badge-bg:#51a868;--color-rail-badge-text:#fff}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.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!important}.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-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-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-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-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-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\.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}.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-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/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-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.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-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}.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-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-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-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/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-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}.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!important}.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-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-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-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-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}.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-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/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-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.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-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}.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-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-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}
|
package/server.js
CHANGED
|
@@ -88,6 +88,63 @@ const server = http.createServer((req, res) => {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// Codex notify hook endpoint — deterministic turn-complete signal
|
|
92
|
+
if (req.method === 'POST' && req.url === '/hook/codex/stop') {
|
|
93
|
+
let body = '';
|
|
94
|
+
req.on('data', chunk => { body += chunk; if (body.length > 1e5) req.destroy(); });
|
|
95
|
+
req.on('end', () => {
|
|
96
|
+
try {
|
|
97
|
+
const payload = JSON.parse(body);
|
|
98
|
+
const threadId = payload['thread-id'];
|
|
99
|
+
if (threadId) {
|
|
100
|
+
const allSessions = sessions.getSessions();
|
|
101
|
+
for (const [id, s] of allSessions) {
|
|
102
|
+
if (s.sessionToken === threadId) {
|
|
103
|
+
console.log(`[codex] hook stop session=${id.slice(0,8)} thread=${threadId.slice(0,8)}`);
|
|
104
|
+
sessions.broadcast({ type: 'session.status', id, working: false, source: 'hook' });
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
res.writeHead(200).end('{}');
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Claude Code hook endpoints — deterministic start/stop/idle signals
|
|
116
|
+
if (req.method === 'POST' && req.url.startsWith('/hook/claude/')) {
|
|
117
|
+
let body = '';
|
|
118
|
+
req.on('data', chunk => { body += chunk; if (body.length > 1e5) req.destroy(); });
|
|
119
|
+
req.on('end', () => {
|
|
120
|
+
try {
|
|
121
|
+
const payload = JSON.parse(body);
|
|
122
|
+
const route = req.url.slice('/hook/claude/'.length);
|
|
123
|
+
const sessionId = payload.session_id;
|
|
124
|
+
// console.log(`[claude] hook ${route} session=${sessionId?.slice(0,8) || '?'}`);
|
|
125
|
+
if (sessionId) {
|
|
126
|
+
const allSessions = sessions.getSessions();
|
|
127
|
+
let clideckId = null;
|
|
128
|
+
for (const [id, s] of allSessions) {
|
|
129
|
+
if (s.sessionToken === sessionId) { clideckId = id; break; }
|
|
130
|
+
}
|
|
131
|
+
if (clideckId) {
|
|
132
|
+
if (route === 'start') {
|
|
133
|
+
sessions.broadcast({ type: 'session.status', id: clideckId, working: true, source: 'hook' });
|
|
134
|
+
} else if (route === 'stop' || route === 'idle') {
|
|
135
|
+
sessions.broadcast({ type: 'session.status', id: clideckId, working: false, source: 'hook' });
|
|
136
|
+
} else if (route === 'menu') {
|
|
137
|
+
// PreToolUse: trigger screen capture — detectMenu will set idle if a choice menu is visible
|
|
138
|
+
setTimeout(() => sessions.broadcast({ type: 'screen.capture', id: clideckId }), 500);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
143
|
+
res.writeHead(200).end('{}');
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
91
148
|
// OpenCode plugin bridge events
|
|
92
149
|
if (req.method === 'POST' && req.url === '/opencode-events') {
|
|
93
150
|
let body = '';
|
|
@@ -125,7 +182,18 @@ const server = http.createServer((req, res) => {
|
|
|
125
182
|
} catch { res.writeHead(500).end(); }
|
|
126
183
|
});
|
|
127
184
|
|
|
128
|
-
const
|
|
185
|
+
const allowedOrigins = new Set([
|
|
186
|
+
`http://localhost:${PORT}`, `http://127.0.0.1:${PORT}`,
|
|
187
|
+
`http://[::1]:${PORT}`, `http://${HOST}:${PORT}`,
|
|
188
|
+
]);
|
|
189
|
+
const wss = new WebSocketServer({
|
|
190
|
+
server,
|
|
191
|
+
verifyClient: ({ req }) => {
|
|
192
|
+
const origin = req.headers.origin;
|
|
193
|
+
if (!origin) return true; // non-browser clients (curl, etc.)
|
|
194
|
+
return allowedOrigins.has(origin);
|
|
195
|
+
},
|
|
196
|
+
});
|
|
129
197
|
wss.on('connection', onConnection);
|
|
130
198
|
|
|
131
199
|
const activity = require('./activity');
|
package/sessions.js
CHANGED
|
@@ -277,10 +277,16 @@ function input(msg) {
|
|
|
277
277
|
activity.trackIn(msg.id, data.length);
|
|
278
278
|
transcript.trackInput(msg.id, data);
|
|
279
279
|
sessions.get(msg.id)?.pty.write(data);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
const s = sessions.get(msg.id);
|
|
281
|
+
if (!s) return;
|
|
282
|
+
// Menu choice selected → back to working (Enter or digit keys only)
|
|
283
|
+
if (s._menuKey && !s.working && (data === '\r' || /^[1-9]$/.test(data))) {
|
|
284
|
+
s._menuKey = '';
|
|
285
|
+
broadcast({ type: 'session.menu', id: msg.id, choices: [] });
|
|
286
|
+
broadcast({ type: 'session.status', id: msg.id, working: true, source: 'menu-input' });
|
|
287
|
+
}
|
|
288
|
+
if (data === '\x1b' && s.working) {
|
|
289
|
+
telemetry.startEscIdle(msg.id);
|
|
284
290
|
}
|
|
285
291
|
}
|
|
286
292
|
function resize(msg) { sessions.get(msg.id)?.pty.resize(msg.cols, msg.rows); }
|
|
@@ -315,15 +321,14 @@ function close(msg, cfg) {
|
|
|
315
321
|
// Uses resume command if available, otherwise re-launches the original command.
|
|
316
322
|
function restart(msg, ws, cfg) {
|
|
317
323
|
const id = msg.id;
|
|
318
|
-
console.log('[restart] received', { id, themeId: msg.themeId });
|
|
324
|
+
// console.log('[restart] received', { id, themeId: msg.themeId });
|
|
319
325
|
const s = sessions.get(id);
|
|
320
|
-
if (!s) {
|
|
326
|
+
if (!s) { ws.send(JSON.stringify({ type: 'session.restarted', id, error: 'not found' })); return; }
|
|
321
327
|
const cmd = cfg.commands.find(c => c.id === s.commandId);
|
|
322
|
-
if (!cmd) {
|
|
328
|
+
if (!cmd) { ws.send(JSON.stringify({ type: 'session.restarted', id, error: 'command missing' })); return; }
|
|
323
329
|
|
|
324
330
|
const themeId = msg.themeId || s.themeId;
|
|
325
331
|
const canResume = cmd.canResume && cmd.resumeCommand && s.sessionToken;
|
|
326
|
-
console.log('[restart] canResume=', canResume, 'token=', s.sessionToken?.slice(0,12), 'cmd=', cmd.command);
|
|
327
332
|
|
|
328
333
|
let parts;
|
|
329
334
|
if (canResume) {
|
|
@@ -331,7 +336,6 @@ function restart(msg, ws, cfg) {
|
|
|
331
336
|
} else {
|
|
332
337
|
parts = parseCommand(cmd.command);
|
|
333
338
|
}
|
|
334
|
-
console.log('[restart] parts=', parts);
|
|
335
339
|
|
|
336
340
|
const savedToken = s.sessionToken;
|
|
337
341
|
const { name, cwd, commandId, projectId } = s;
|
|
@@ -341,19 +345,16 @@ function restart(msg, ws, cfg) {
|
|
|
341
345
|
opencodeBridge.clear(id);
|
|
342
346
|
transcript.clear(id);
|
|
343
347
|
|
|
344
|
-
console.log('[restart] killing old pty');
|
|
345
348
|
s.pty.kill();
|
|
346
349
|
sessions.delete(id);
|
|
347
350
|
|
|
348
|
-
console.log('[restart] spawning new pty, themeId=', themeId, 'cwd=', cwd);
|
|
349
351
|
const err = spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken, projectId, msg.cols, msg.rows);
|
|
350
352
|
if (err) {
|
|
351
|
-
console.error('[restart]
|
|
353
|
+
console.error('[restart] spawn failed:', err.message);
|
|
352
354
|
broadcast({ type: 'session.restarted', id, error: err.message });
|
|
353
355
|
return;
|
|
354
356
|
}
|
|
355
357
|
|
|
356
|
-
console.log('[restart] SUCCESS, broadcasting session.restarted');
|
|
357
358
|
broadcast({ type: 'session.restarted', id, resumed: !!canResume });
|
|
358
359
|
}
|
|
359
360
|
|
package/telemetry-receiver.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
const ioActivity = require('./activity');
|
|
6
6
|
const activity = new Map(); // sessionId → has received events
|
|
7
|
+
const lastEvent = new Map(); // sessionId → last OTEL event name (+ kind)
|
|
7
8
|
const pendingSetup = new Map(); // sessionId → timer (waiting for first event)
|
|
8
9
|
const pendingIdle = new Map(); // sessionId → timer (PTY silence → idle)
|
|
10
|
+
const codexMenuPoll = new Map(); // sessionId → interval (polling for menu after response.completed)
|
|
9
11
|
const escPendingIdle = new Map(); // sessionId → timer (Esc interrupt → confirm idle after output silence)
|
|
10
12
|
const escSuppressUntil = new Map(); // sessionId → ts (briefly ignore telemetry reassertions after Esc)
|
|
11
13
|
let broadcastFn = null;
|
|
@@ -74,18 +76,40 @@ function handleLogs(req, res) {
|
|
|
74
76
|
|
|
75
77
|
// Debug telemetry logs — uncomment as needed, do not delete
|
|
76
78
|
// if (serviceName === 'claude-code' && eventName) console.log(`[telemetry:claude] ${eventName}`);
|
|
77
|
-
// if (serviceName === 'codex_cli_rs' && eventName) console.log(`[telemetry:codex] ${eventName} session=${resolvedId.slice(0,8)}
|
|
79
|
+
// if (serviceName === 'codex_cli_rs' && eventName) console.log(`[telemetry:codex] ${eventName} ${attrs['event.kind'] ? 'kind=' + attrs['event.kind'] : ''} ${attrs['tool'] ? 'tool=' + attrs['tool'] : ''} session=${resolvedId.slice(0,8)}`);
|
|
78
80
|
// if (serviceName === 'gemini-cli' && eventName) console.log(`[telemetry:gemini] ${eventName}`);
|
|
79
81
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
+
// Track last event per session (used by menu detection validation)
|
|
83
|
+
if (eventName) lastEvent.set(resolvedId, eventName + (attrs['event.kind'] ? ':' + attrs['event.kind'] : ''));
|
|
84
|
+
|
|
85
|
+
// Status: user_prompt → working
|
|
86
|
+
// Claude uses hooks; Codex uses notify hook; Gemini uses PTY heuristic
|
|
87
|
+
const startEvents = new Set(['gemini_cli.user_prompt', 'codex.user_prompt']);
|
|
82
88
|
if (startEvents.has(eventName)) {
|
|
83
89
|
cancelPendingIdle(resolvedId);
|
|
84
90
|
broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
|
|
85
|
-
|
|
91
|
+
// Gemini uses PTY silence heuristic for idle; Codex idle comes from notify hook
|
|
92
|
+
if (serviceName !== 'codex_cli_rs') startPendingIdle(resolvedId, serviceName);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Codex: response.completed → poll for menu until found or timeout
|
|
96
|
+
if (eventName === 'codex.sse_event' && attrs['event.kind'] === 'response.completed') {
|
|
97
|
+
startCodexMenuPoll(resolvedId);
|
|
98
|
+
}
|
|
99
|
+
// Codex: tool_decision → user approved, cancel menu poll, back to working
|
|
100
|
+
if (eventName === 'codex.tool_decision') {
|
|
101
|
+
cancelCodexMenuPoll(resolvedId);
|
|
102
|
+
broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
|
|
103
|
+
}
|
|
104
|
+
// Codex: user_prompt or next sse_event cancels menu poll
|
|
105
|
+
if ((eventName === 'codex.user_prompt' || (eventName === 'codex.sse_event' && attrs['event.kind'] !== 'response.completed'))) {
|
|
106
|
+
cancelCodexMenuPoll(resolvedId);
|
|
86
107
|
}
|
|
87
108
|
|
|
88
|
-
|
|
109
|
+
// Codex: use conversation.id (maps to thread-id in notify hook)
|
|
110
|
+
const agentSessionId = serviceName === 'codex_cli_rs'
|
|
111
|
+
? attrs['conversation.id']
|
|
112
|
+
: (attrs['session.id'] || attrs['conversation.id']);
|
|
89
113
|
if (agentSessionId && sess) {
|
|
90
114
|
// Prefer interactive session ID (Gemini sends non-interactive init events first)
|
|
91
115
|
const dominated = sess.sessionToken && attrs['interactive'] === true;
|
|
@@ -124,16 +148,12 @@ function cancelPendingSetup(sessionId) {
|
|
|
124
148
|
}
|
|
125
149
|
|
|
126
150
|
// PTY activity monitor: 2s silent → idle, 2s active or user_prompt → working.
|
|
151
|
+
// Used by Gemini only — Claude uses hooks, Codex uses sse_event completion.
|
|
127
152
|
// Agent working indicators in PTY output.
|
|
128
|
-
const CLAUDE_WORKING_RE = /[✳✽✢✻·]|Working…|thinking/;
|
|
129
|
-
const CODEX_WORKING_RE = /Working|•/;
|
|
130
153
|
const GEMINI_WORKING_RE = /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/;
|
|
131
154
|
|
|
132
155
|
function startPendingIdle(id, agent) {
|
|
133
156
|
if (pendingIdle.has(id)) return; // already monitoring
|
|
134
|
-
const isClaude = agent === 'claude-code';
|
|
135
|
-
const isCodex = agent === 'codex_cli_rs';
|
|
136
|
-
const isGemini = agent === 'gemini-cli';
|
|
137
157
|
let isIdle = false;
|
|
138
158
|
let activeStart = 0;
|
|
139
159
|
const check = setInterval(() => {
|
|
@@ -145,9 +165,7 @@ function startPendingIdle(id, agent) {
|
|
|
145
165
|
// Agent override: if recent output has spinner/working chars, not silent
|
|
146
166
|
if (silent && (Date.now() - lastOut) < 2000) {
|
|
147
167
|
const chunk = ioActivity.lastChunk(id);
|
|
148
|
-
if (
|
|
149
|
-
if (isCodex && CODEX_WORKING_RE.test(chunk)) silent = false;
|
|
150
|
-
if (isGemini && GEMINI_WORKING_RE.test(chunk)) silent = false;
|
|
168
|
+
if (GEMINI_WORKING_RE.test(chunk)) silent = false;
|
|
151
169
|
}
|
|
152
170
|
if (silent && !isIdle) {
|
|
153
171
|
isIdle = true;
|
|
@@ -171,22 +189,38 @@ function cancelPendingIdle(id) {
|
|
|
171
189
|
if (timer) { clearInterval(timer); pendingIdle.delete(id); }
|
|
172
190
|
}
|
|
173
191
|
|
|
192
|
+
// Codex: after response.completed, poll screen capture every 500ms for up to 3s
|
|
193
|
+
function startCodexMenuPoll(id) {
|
|
194
|
+
cancelCodexMenuPoll(id);
|
|
195
|
+
const started = Date.now();
|
|
196
|
+
const poll = setInterval(() => {
|
|
197
|
+
if (Date.now() - started > 3000) { cancelCodexMenuPoll(id); return; }
|
|
198
|
+
broadcastFn?.({ type: 'screen.capture', id });
|
|
199
|
+
}, 500);
|
|
200
|
+
codexMenuPoll.set(id, poll);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cancelCodexMenuPoll(id) {
|
|
204
|
+
const timer = codexMenuPoll.get(id);
|
|
205
|
+
if (timer) { clearInterval(timer); codexMenuPoll.delete(id); }
|
|
206
|
+
}
|
|
207
|
+
|
|
174
208
|
function startEscIdle(id) {
|
|
175
209
|
cancelEscIdle(id);
|
|
176
210
|
const started = Date.now();
|
|
177
211
|
const ignoreUntil = started + 500;
|
|
178
|
-
console.log(`[escIdle] start session=${id.slice(0,8)}`);
|
|
212
|
+
// console.log(`[escIdle] start session=${id.slice(0,8)}`);
|
|
179
213
|
const check = setInterval(() => {
|
|
180
214
|
const lastOut = ioActivity.lastOutputAt(id);
|
|
181
215
|
const silence = Date.now() - Math.max(ignoreUntil, lastOut);
|
|
182
216
|
const elapsed = Date.now() - started;
|
|
183
217
|
if (elapsed > 10000) {
|
|
184
|
-
console.log(`[escIdle] timeout session=${id.slice(0,8)} silence=${silence}ms`);
|
|
218
|
+
// console.log(`[escIdle] timeout session=${id.slice(0,8)} silence=${silence}ms`);
|
|
185
219
|
cancelEscIdle(id);
|
|
186
220
|
return;
|
|
187
221
|
}
|
|
188
222
|
if (silence >= 2000) {
|
|
189
|
-
console.log(`[escIdle] idle session=${id.slice(0,8)} silence=${silence}ms`);
|
|
223
|
+
// console.log(`[escIdle] idle session=${id.slice(0,8)} silence=${silence}ms`);
|
|
190
224
|
escSuppressUntil.set(id, Date.now() + 2000);
|
|
191
225
|
cancelEscIdle(id);
|
|
192
226
|
broadcastFn?.({ type: 'session.status', id, working: false, source: 'esc' });
|
|
@@ -202,16 +236,20 @@ function cancelEscIdle(id) {
|
|
|
202
236
|
|
|
203
237
|
function clear(id) {
|
|
204
238
|
activity.delete(id);
|
|
239
|
+
lastEvent.delete(id);
|
|
205
240
|
cancelPendingIdle(id);
|
|
241
|
+
cancelCodexMenuPoll(id);
|
|
206
242
|
cancelEscIdle(id);
|
|
207
243
|
escSuppressUntil.delete(id);
|
|
208
244
|
const pending = pendingSetup.get(id);
|
|
209
245
|
if (pending) { clearTimeout(pending.timer); pendingSetup.delete(id); }
|
|
210
246
|
}
|
|
211
247
|
|
|
248
|
+
function getLastEvent(id) { return lastEvent.get(id) || ''; }
|
|
249
|
+
|
|
212
250
|
// Returns true if we've received telemetry events for this session
|
|
213
251
|
function hasEvents(id) {
|
|
214
252
|
return activity.has(id);
|
|
215
253
|
}
|
|
216
254
|
|
|
217
|
-
module.exports = { init, handleLogs, clear, hasEvents, watchSession, startPendingIdle, startEscIdle };
|
|
255
|
+
module.exports = { init, handleLogs, clear, hasEvents, getLastEvent, cancelCodexMenuPoll, watchSession, startPendingIdle, startEscIdle };
|
package/transcript.js
CHANGED
|
@@ -321,16 +321,18 @@ const MENU_CHOICE_RE = /^\s*(?:[│❯›●•]\s+)*(\d+)\.\s+(.+)$/;
|
|
|
321
321
|
function detectMenu(lines, presetId) {
|
|
322
322
|
const marker = MENU_MARKERS[presetId];
|
|
323
323
|
if (!marker) return null;
|
|
324
|
+
// Only scan the bottom 40 lines — menus are always near the visible area
|
|
325
|
+
const scanStart = Math.max(0, lines.length - 40);
|
|
324
326
|
let footerIdx = -1;
|
|
325
|
-
for (let i = lines.length - 1; i >=
|
|
327
|
+
for (let i = lines.length - 1; i >= scanStart; i--) {
|
|
326
328
|
if (/\besc\b|\(esc\)/i.test(lines[i])) { footerIdx = MENU_CHOICE_RE.test(lines[i]) ? i + 1 : i; break; }
|
|
327
329
|
}
|
|
328
330
|
if (footerIdx < 0) return null;
|
|
329
331
|
const choices = [];
|
|
330
|
-
for (let i = footerIdx - 1; i >=
|
|
332
|
+
for (let i = footerIdx - 1; i >= scanStart; i--) {
|
|
331
333
|
if (!lines[i].trim() || /^[│\s]+$/.test(lines[i])) continue;
|
|
332
334
|
const m = lines[i].match(MENU_CHOICE_RE);
|
|
333
|
-
if (!m) break;
|
|
335
|
+
if (!m) { if (/^\s{2,}\S/.test(lines[i])) continue; break; }
|
|
334
336
|
if (choices.length && +m[1] >= +choices[0].value) break;
|
|
335
337
|
choices.unshift({ value: m[1], label: m[2].trim(), selected: marker.test(lines[i]) });
|
|
336
338
|
}
|
package/utils.js
CHANGED
|
@@ -44,10 +44,10 @@ function resolveValidDir(dir) {
|
|
|
44
44
|
return require('os').homedir();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function listDirs(path) {
|
|
47
|
+
function listDirs(path, showHidden) {
|
|
48
48
|
try {
|
|
49
49
|
return readdirSync(path, { withFileTypes: true })
|
|
50
|
-
.filter(d => d.isDirectory() && !d.name.startsWith('.'))
|
|
50
|
+
.filter(d => d.isDirectory() && (showHidden || !d.name.startsWith('.')))
|
|
51
51
|
.map(d => d.name)
|
|
52
52
|
.sort();
|
|
53
53
|
} catch (e) {
|