ninja-terminals 2.4.0 → 2.4.1

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/public/app.js CHANGED
@@ -3,313 +3,20 @@
3
3
  const WS_BASE = `ws://${location.host}`;
4
4
  const API_BASE = '';
5
5
  const AUTH_API = '/api';
6
- const TOKEN_KEY = 'ninja_token';
7
-
8
- // Session readiness gate — resolves when session is validated (or validation is skipped)
9
- let sessionReadyResolve;
10
- const sessionReady = new Promise(resolve => { sessionReadyResolve = resolve; });
11
-
12
- // ── Auth Module ──────────────────────────────────────────────
13
-
14
6
  const auth = {
15
7
  token: null,
16
- user: null,
17
- tier: null,
18
- terminalsMax: 2,
19
- validating: false,
20
-
21
- init() {
22
- const stored = localStorage.getItem(TOKEN_KEY);
23
- if (!stored) return false;
24
-
25
- try {
26
- // Decode JWT payload (base64url)
27
- const parts = stored.split('.');
28
- if (parts.length !== 3) {
29
- localStorage.removeItem(TOKEN_KEY);
30
- return false;
31
- }
32
-
33
- const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
34
-
35
- // Check expiration
36
- if (payload.exp && payload.exp * 1000 < Date.now()) {
37
- localStorage.removeItem(TOKEN_KEY);
38
- return false;
39
- }
40
-
41
- this.token = stored;
42
- this.user = payload.sub || payload.email || payload.username || null;
43
- return true;
44
- } catch {
45
- localStorage.removeItem(TOKEN_KEY);
46
- return false;
47
- }
48
- },
49
-
50
- async tryBootstrap() {
51
- try {
52
- const res = await fetch(`${API_BASE}/api/auth/bootstrap`);
53
- if (!res.ok) return false;
54
-
55
- const data = await res.json();
56
- if (!data.token) return false;
57
-
58
- // Validate token format
59
- const parts = data.token.split('.');
60
- if (parts.length !== 3) return false;
61
-
62
- const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
63
- if (payload.exp && payload.exp * 1000 < Date.now()) return false;
64
-
65
- // Save to localStorage and use
66
- localStorage.setItem(TOKEN_KEY, data.token);
67
- this.token = data.token;
68
- this.user = payload.sub || payload.email || payload.username || null;
69
- return true;
70
- } catch {
71
- return false;
72
- }
73
- },
74
-
75
- async login(usernameOrEmail, password) {
76
- const res = await fetch(`${AUTH_API}/auth/login`, {
77
- method: 'POST',
78
- headers: { 'Content-Type': 'application/json' },
79
- body: JSON.stringify({ username: usernameOrEmail, password }),
80
- });
81
-
82
- if (!res.ok) {
83
- const err = await res.json().catch(() => ({}));
84
- throw new Error(err.message || 'Login failed');
85
- }
86
-
87
- const data = await res.json();
88
- this.token = data.token || data.accessToken;
89
- localStorage.setItem(TOKEN_KEY, this.token);
90
-
91
- await this.validateTier();
92
- return data;
93
- },
94
-
95
- async register(username, email, password) {
96
- const res = await fetch(`${AUTH_API}/auth/register`, {
97
- method: 'POST',
98
- headers: { 'Content-Type': 'application/json' },
99
- body: JSON.stringify({ username, email, password }),
100
- });
101
-
102
- if (!res.ok) {
103
- const err = await res.json().catch(() => ({}));
104
- throw new Error(err.message || 'Registration failed');
105
- }
106
-
107
- const data = await res.json();
108
- this.token = data.token || data.accessToken;
109
- localStorage.setItem(TOKEN_KEY, this.token);
110
-
111
- await this.validateTier();
112
- return data;
8
+ getAuthHeader() {
9
+ return {};
113
10
  },
114
-
115
- async activateLicense(key) {
116
- const res = await fetch(`${AUTH_API}/ninja/activate-license`, {
117
- method: 'POST',
118
- headers: { 'Content-Type': 'application/json' },
119
- body: JSON.stringify({ licenseKey: key }),
120
- });
121
-
122
- if (!res.ok) {
123
- const err = await res.json().catch(() => ({}));
124
- throw new Error(err.message || 'Invalid license key');
125
- }
126
-
127
- const data = await res.json();
128
- this.token = data.token || data.accessToken;
129
- localStorage.setItem(TOKEN_KEY, this.token);
130
-
131
- await this.validateTier();
132
- return data;
11
+ logout() {},
12
+ init() {
13
+ return true;
133
14
  },
134
-
135
15
  async validateTier() {
136
- this.validating = true;
137
- try {
138
- const res = await fetch(`${API_BASE}/api/session`, {
139
- method: 'POST',
140
- headers: {
141
- 'Content-Type': 'application/json',
142
- ...this.getAuthHeader(),
143
- },
144
- body: JSON.stringify({ token: this.token }),
145
- });
146
-
147
- if (!res.ok) {
148
- // 401 = token truly invalid/expired, need re-login
149
- if (res.status === 401) {
150
- console.warn('Session validation failed: token invalid');
151
- this.token = null;
152
- localStorage.removeItem(TOKEN_KEY);
153
- return { needsLogin: true };
154
- }
155
- // Other errors (500, network) — proceed with defaults
156
- console.warn('Session validation failed, using defaults');
157
- return { needsLogin: false };
158
- }
159
-
160
- const data = await res.json();
161
- this.tier = data.tier || 'free';
162
- this.terminalsMax = data.terminalsMax || 2;
163
- if (data.user) this.user = data.user;
164
- return { needsLogin: false };
165
- } finally {
166
- this.validating = false;
167
- }
168
- },
169
-
170
- async logout() {
171
- try {
172
- await fetch(`${API_BASE}/api/session`, {
173
- method: 'DELETE',
174
- headers: this.getAuthHeader(),
175
- });
176
- } catch {
177
- // Ignore errors on logout
178
- }
179
-
180
- this.token = null;
181
- this.user = null;
182
- this.tier = null;
183
- localStorage.removeItem(TOKEN_KEY);
184
-
185
- showAuthOverlay();
186
- },
187
-
188
- getAuthHeader() {
189
- return this.token ? { 'Authorization': `Bearer ${this.token}` } : {};
16
+ return { needsLogin: false };
190
17
  },
191
18
  };
192
19
 
193
- // ── Auth UI ──────────────────────────────────────────────────
194
-
195
- function showAuthOverlay() {
196
- // Auth disabled - app is free, never show auth overlay
197
- return;
198
- }
199
-
200
- function hideAuthOverlay() {
201
- const overlay = document.getElementById('auth-overlay');
202
- overlay.classList.add('hidden');
203
- }
204
-
205
- function setupAuthForms() {
206
- const loginForm = document.getElementById('login-form');
207
- const registerForm = document.getElementById('register-form');
208
- const licenseForm = document.getElementById('license-form');
209
- const loginError = document.getElementById('login-error');
210
- const registerError = document.getElementById('register-error');
211
- const logoutBtn = document.getElementById('logout-btn');
212
- const showRegisterLink = document.getElementById('show-register');
213
- const authToggleText = document.getElementById('auth-toggle-text');
214
-
215
- // Toggle between login and register
216
- let showingRegister = false;
217
-
218
- function toggleAuthMode() {
219
- showingRegister = !showingRegister;
220
- if (showingRegister) {
221
- loginForm.classList.add('hidden');
222
- registerForm.classList.remove('hidden');
223
- authToggleText.innerHTML = 'Already have an account? <a href="#" id="show-register">Sign in</a>';
224
- document.getElementById('register-username').focus();
225
- } else {
226
- registerForm.classList.add('hidden');
227
- loginForm.classList.remove('hidden');
228
- authToggleText.innerHTML = 'Don\'t have an account? <a href="#" id="show-register">Sign up</a>';
229
- document.getElementById('login-email').focus();
230
- }
231
- // Re-attach click handler to new link
232
- document.getElementById('show-register').addEventListener('click', (e) => {
233
- e.preventDefault();
234
- toggleAuthMode();
235
- });
236
- loginError.textContent = '';
237
- registerError.textContent = '';
238
- }
239
-
240
- showRegisterLink.addEventListener('click', (e) => {
241
- e.preventDefault();
242
- toggleAuthMode();
243
- });
244
-
245
- // Login form
246
- loginForm.addEventListener('submit', async (e) => {
247
- e.preventDefault();
248
- loginError.textContent = '';
249
-
250
- const email = document.getElementById('login-email').value.trim();
251
- const password = document.getElementById('login-password').value;
252
-
253
- try {
254
- await auth.login(email, password);
255
- hideAuthOverlay();
256
- startApp();
257
- sessionReadyResolve();
258
- } catch (err) {
259
- loginError.textContent = err.message;
260
- }
261
- });
262
-
263
- // Register form
264
- registerForm.addEventListener('submit', async (e) => {
265
- e.preventDefault();
266
- registerError.textContent = '';
267
-
268
- const username = document.getElementById('register-username').value.trim();
269
- const email = document.getElementById('register-email').value.trim();
270
- const password = document.getElementById('register-password').value;
271
-
272
- if (password.length < 8) {
273
- registerError.textContent = 'Password must be at least 8 characters';
274
- return;
275
- }
276
-
277
- try {
278
- await auth.register(username, email, password);
279
- hideAuthOverlay();
280
- startApp();
281
- sessionReadyResolve();
282
- } catch (err) {
283
- registerError.textContent = err.message;
284
- }
285
- });
286
-
287
- // License form
288
- licenseForm.addEventListener('submit', async (e) => {
289
- e.preventDefault();
290
- loginError.textContent = '';
291
-
292
- const key = document.getElementById('license-key').value.trim();
293
- if (!key) {
294
- loginError.textContent = 'Please enter a license key';
295
- return;
296
- }
297
-
298
- try {
299
- await auth.activateLicense(key);
300
- hideAuthOverlay();
301
- startApp();
302
- sessionReadyResolve();
303
- } catch (err) {
304
- loginError.textContent = err.message;
305
- }
306
- });
307
-
308
- logoutBtn.addEventListener('click', () => {
309
- auth.logout();
310
- });
311
- }
312
-
313
20
  // ── State ────────────────────────────────────────────────────
314
21
 
315
22
  const state = {
@@ -376,6 +83,24 @@ const TASK_STATUS_LABELS = {
376
83
  unknown: 'UNKNOWN',
377
84
  };
378
85
 
86
+ const AGENT_TYPE_LABELS = {
87
+ claude: 'Claude',
88
+ codex: 'Codex',
89
+ opencode: 'OpenCode',
90
+ shell: 'Shell',
91
+ mixed: 'Mixed',
92
+ duo: 'Duo',
93
+ };
94
+
95
+ const AGENT_TYPE_CLASSES = {
96
+ claude: 'agent-claude',
97
+ codex: 'agent-codex',
98
+ opencode: 'agent-opencode',
99
+ shell: 'agent-shell',
100
+ mixed: 'agent-mixed',
101
+ duo: 'agent-duo',
102
+ };
103
+
379
104
  // ── Utilities ────────────────────────────────────────────────
380
105
 
381
106
  function timestamp() {
@@ -393,6 +118,10 @@ function getTerminalFeedClass(id) {
393
118
  return `feed-t${idx + 1}`;
394
119
  }
395
120
 
121
+ function getAgentTypeClass(agentType) {
122
+ return AGENT_TYPE_CLASSES[agentType] || 'agent-claude';
123
+ }
124
+
396
125
  // ── Activity Feed ────────────────────────────────────────────
397
126
 
398
127
  function addFeedEntry(message, terminalId) {
@@ -423,12 +152,13 @@ function escapeHtml(str) {
423
152
  // ── Terminal Creation ────────────────────────────────────────
424
153
 
425
154
  function createTerminalUI(termData) {
426
- const { id, label, status, elapsed, progress, taskName } = termData;
155
+ const { id, label, status, elapsed, progress, taskName, agentType } = termData;
427
156
 
428
157
  // Pane
429
158
  const pane = document.createElement('div');
430
159
  pane.className = 'terminal-pane';
431
160
  pane.id = `pane-${id}`;
161
+ pane.classList.add(getAgentTypeClass(agentType));
432
162
 
433
163
  // Header
434
164
  const header = document.createElement('div');
@@ -660,6 +390,7 @@ function createTerminalUI(termData) {
660
390
  const termState = {
661
391
  id,
662
392
  label: label || `Terminal ${id.slice(0, 6)}`,
393
+ agentType: agentType || 'claude',
663
394
  status: status || 'idle',
664
395
  progress: progress || 0,
665
396
  elapsed: elapsed || '',
@@ -964,6 +695,11 @@ function updateTerminalState(id, newStatus, extra) {
964
695
  if (t.labelEl) t.labelEl.textContent = extra.label;
965
696
  }
966
697
  if (extra.taskName !== undefined) t.taskName = extra.taskName;
698
+ if (extra.agentType !== undefined) {
699
+ t.agentType = extra.agentType;
700
+ t.paneEl.classList.remove('agent-claude', 'agent-codex', 'agent-opencode', 'agent-shell', 'agent-mixed', 'agent-duo');
701
+ t.paneEl.classList.add(getAgentTypeClass(extra.agentType));
702
+ }
967
703
  }
968
704
 
969
705
  // Update state icon
@@ -1248,7 +984,6 @@ window.addEventListener('resize', () => {
1248
984
  // ── Start App (after auth) ───────────────────────────────────
1249
985
 
1250
986
  async function startApp() {
1251
- // Request desktop notification permission
1252
987
  requestNotificationPermission();
1253
988
 
1254
989
  // Setup sidebar
@@ -1294,23 +1029,37 @@ async function startApp() {
1294
1029
 
1295
1030
  function setupAddTerminal() {
1296
1031
  const btn = document.getElementById('add-terminal-btn');
1032
+ const preset = document.getElementById('agent-preset-select');
1297
1033
  if (!btn) return;
1298
1034
 
1299
1035
  // Store last used directory
1300
1036
  let lastCwd = localStorage.getItem('ninja-last-cwd') || '/Users/davidmini/Desktop/Projects';
1037
+ let lastAgentType = localStorage.getItem('ninja-last-agent-type') || 'claude';
1038
+
1039
+ if (preset) {
1040
+ const validPresets = new Set(['claude', 'codex', 'opencode', 'shell', 'mixed', 'duo']);
1041
+ if (!validPresets.has(lastAgentType)) lastAgentType = 'claude';
1042
+ preset.value = lastAgentType;
1043
+ preset.addEventListener('change', () => {
1044
+ lastAgentType = preset.value;
1045
+ localStorage.setItem('ninja-last-agent-type', lastAgentType);
1046
+ });
1047
+ }
1301
1048
 
1302
1049
  btn.addEventListener('click', async () => {
1303
1050
  try {
1304
- const cwd = prompt('Working directory:', lastCwd);
1305
- if (!cwd) return;
1051
+ const agentType = preset?.value || lastAgentType || 'claude';
1052
+ const cwd = lastCwd;
1306
1053
 
1307
1054
  lastCwd = cwd;
1055
+ lastAgentType = agentType;
1308
1056
  localStorage.setItem('ninja-last-cwd', cwd);
1057
+ localStorage.setItem('ninja-last-agent-type', agentType);
1309
1058
 
1310
1059
  const res = await fetch(`${API_BASE}/api/terminals`, {
1311
1060
  method: 'POST',
1312
1061
  headers: { 'Content-Type': 'application/json', ...auth.getAuthHeader() },
1313
- body: JSON.stringify({ cwd }),
1062
+ body: JSON.stringify({ cwd, agentType }),
1314
1063
  });
1315
1064
 
1316
1065
  if (!res.ok) {
@@ -1321,7 +1070,7 @@ function setupAddTerminal() {
1321
1070
 
1322
1071
  const terminal = await res.json();
1323
1072
  createTerminalUI(terminal);
1324
- addFeedEntry(`Terminal added: T${terminal.id}`);
1073
+ addFeedEntry(`Terminal added: T${terminal.id} (${AGENT_TYPE_LABELS[terminal.agentType] || terminal.agentType || 'claude'})`);
1325
1074
  } catch (err) {
1326
1075
  console.error('Failed to add terminal:', err);
1327
1076
  alert('Failed to add terminal');
@@ -1401,10 +1150,8 @@ function setupLearnings() {
1401
1150
  // ── Initialize ───────────────────────────────────────────────
1402
1151
 
1403
1152
  async function init() {
1404
- // Auth disabled — app is free, start immediately
1405
- hideAuthOverlay();
1153
+ auth.init();
1406
1154
  startApp();
1407
- sessionReadyResolve();
1408
1155
  }
1409
1156
 
1410
1157
  init();
package/public/index.html CHANGED
@@ -26,56 +26,27 @@
26
26
  </div>
27
27
  </div>
28
28
 
29
- <!-- Auth Overlay (disabled - app is free) -->
30
- <div id="auth-overlay" style="display:none;">
31
- <div class="auth-card">
32
- <div class="auth-stripes">
33
- <div class="stripe s1"></div>
34
- <div class="stripe s2"></div>
35
- <div class="stripe s3"></div>
36
- <div class="stripe s4"></div>
37
- </div>
38
- <div class="auth-card-inner">
39
- <h1 class="logo-text">NINJA TERMINALS</h1>
40
- <p class="auth-subtitle">Multi-Agent Claude Code Orchestrator</p>
41
- <!-- Login Form -->
42
- <form id="login-form">
43
- <input type="text" id="login-email" placeholder="Email or username" required autocomplete="username">
44
- <input type="password" id="login-password" placeholder="Password" required autocomplete="current-password">
45
- <button type="submit" class="auth-btn">Sign In</button>
46
- <p class="auth-error" id="login-error"></p>
47
- </form>
48
-
49
- <!-- Register Form (hidden by default) -->
50
- <form id="register-form" class="hidden">
51
- <input type="text" id="register-username" placeholder="Username" required autocomplete="username">
52
- <input type="email" id="register-email" placeholder="Email" required autocomplete="email">
53
- <input type="password" id="register-password" placeholder="Password (min 8 chars)" required autocomplete="new-password" minlength="8">
54
- <button type="submit" class="auth-btn">Create Account</button>
55
- <p class="auth-error" id="register-error"></p>
56
- </form>
57
-
58
- <div class="auth-divider"><span>or</span></div>
59
- <form id="license-form">
60
- <input type="text" id="license-key" placeholder="Enter license key" autocomplete="off">
61
- <button type="submit" class="auth-btn auth-btn-secondary">Activate License</button>
62
- </form>
63
- <p class="auth-footer" id="auth-toggle-text">Don't have an account? <a href="#" id="show-register">Sign up</a></p>
64
- </div>
65
- </div>
66
- </div>
67
-
68
29
  <div id="app">
69
30
  <aside id="sidebar">
70
31
  <div class="sidebar-header">
71
32
  <div class="logo-card">
72
33
  <span class="logo">NINJA TERMINALS</span>
73
34
  <span class="logo-sub">Multi-Agent Orchestrator</span>
74
- <button id="add-terminal-btn" class="add-terminal-btn" title="Add Terminal">+</button>
75
- <button id="clear-all-btn" class="clear-all-btn" title="Clear All Terminals">🗑</button>
76
- <button id="learnings-btn" class="learnings-btn" title="View Session Learnings">🧠</button>
77
- <button id="logout-btn" class="logout-btn" title="Sign out">Logout</button>
78
- </div>
35
+ <label class="agent-preset" for="agent-preset-select">
36
+ <span class="agent-preset-label">Agent preset</span>
37
+ <select id="agent-preset-select" class="agent-preset-select" aria-label="Agent preset">
38
+ <option value="claude">Claude</option>
39
+ <option value="codex">Codex</option>
40
+ <option value="opencode">OpenCode</option>
41
+ <option value="shell">Shell</option>
42
+ <option value="mixed">Mixed</option>
43
+ <option value="duo">Duo</option>
44
+ </select>
45
+ </label>
46
+ <button id="add-terminal-btn" class="add-terminal-btn" title="Add Terminal">+</button>
47
+ <button id="clear-all-btn" class="clear-all-btn" title="Clear All Terminals">🗑</button>
48
+ <button id="learnings-btn" class="learnings-btn" title="View Session Learnings">🧠</button>
49
+ </div>
79
50
  <div class="logo-stripes">
80
51
  <div class="stripe s1"></div>
81
52
  <div class="stripe s2"></div>
package/public/style.css CHANGED
@@ -301,6 +301,7 @@ main {
301
301
  position: relative;
302
302
  border: 1px solid var(--border);
303
303
  transition: border-color 0.2s;
304
+ --agent-accent: var(--feed-t1);
304
305
  }
305
306
 
306
307
  .terminal-pane.active {
@@ -349,6 +350,24 @@ main {
349
350
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
350
351
  }
351
352
 
353
+ .terminal-pane.agent-claude { --agent-accent: var(--feed-t1); }
354
+ .terminal-pane.agent-codex { --agent-accent: var(--feed-t2); }
355
+ .terminal-pane.agent-opencode { --agent-accent: var(--feed-t3); }
356
+ .terminal-pane.agent-shell { --agent-accent: var(--feed-t4); }
357
+ .terminal-pane.agent-mixed { --agent-accent: var(--state-working); }
358
+ .terminal-pane.agent-duo { --agent-accent: var(--state-done); }
359
+
360
+ .terminal-pane::before {
361
+ content: '';
362
+ position: absolute;
363
+ inset: 0 auto 0 0;
364
+ width: 4px;
365
+ background: var(--agent-accent);
366
+ box-shadow: 0 0 12px color-mix(in srgb, var(--agent-accent) 55%, transparent);
367
+ pointer-events: none;
368
+ z-index: 2;
369
+ }
370
+
352
371
  /* ── Pane Header ────────────────────────────── */
353
372
 
354
373
  .pane-header {
@@ -362,6 +381,17 @@ main {
362
381
  cursor: default;
363
382
  user-select: none;
364
383
  position: relative;
384
+ overflow: hidden;
385
+ }
386
+
387
+ .pane-header::after {
388
+ content: '';
389
+ position: absolute;
390
+ inset: auto 0 0 0;
391
+ height: 2px;
392
+ background: linear-gradient(90deg, var(--agent-accent), color-mix(in srgb, var(--agent-accent) 35%, transparent));
393
+ opacity: 0.9;
394
+ pointer-events: none;
365
395
  }
366
396
 
367
397
  /* ── Pane Label (retro cream badge) ── */
@@ -371,7 +401,7 @@ main {
371
401
  font-size: 16px;
372
402
  letter-spacing: 2px;
373
403
  color: var(--bg);
374
- background: var(--cream);
404
+ background: linear-gradient(90deg, var(--cream), color-mix(in srgb, var(--cream) 78%, var(--agent-accent) 22%));
375
405
  padding: 2px 10px;
376
406
  border-radius: 2px;
377
407
  white-space: nowrap;
@@ -382,11 +412,13 @@ main {
382
412
  position: relative;
383
413
  }
384
414
 
385
- /* Color band on left edge of badge — set per terminal index via nth-child */
386
- .terminal-pane:nth-child(4n+1) .pane-label { border-left: 3px solid var(--feed-t1); }
387
- .terminal-pane:nth-child(4n+2) .pane-label { border-left: 3px solid var(--feed-t2); }
388
- .terminal-pane:nth-child(4n+3) .pane-label { border-left: 3px solid var(--feed-t3); }
389
- .terminal-pane:nth-child(4n+4) .pane-label { border-left: 3px solid var(--feed-t4); }
415
+ /* Agent-based color band on left edge of badge */
416
+ .terminal-pane.agent-claude .pane-label { border-left: 3px solid var(--feed-t1); }
417
+ .terminal-pane.agent-codex .pane-label { border-left: 3px solid var(--feed-t2); }
418
+ .terminal-pane.agent-opencode .pane-label { border-left: 3px solid var(--feed-t3); }
419
+ .terminal-pane.agent-shell .pane-label { border-left: 3px solid var(--feed-t4); }
420
+ .terminal-pane.agent-mixed .pane-label { border-left: 3px solid var(--state-working); }
421
+ .terminal-pane.agent-duo .pane-label { border-left: 3px solid var(--state-done); }
390
422
 
391
423
  .pane-label.editing {
392
424
  background: #1a1a1a;
@@ -401,6 +433,46 @@ main {
401
433
  font-family: 'Space Grotesk', sans-serif;
402
434
  }
403
435
 
436
+ .agent-preset {
437
+ display: flex;
438
+ flex-direction: column;
439
+ gap: 4px;
440
+ margin: 10px 0 8px;
441
+ padding: 0 16px;
442
+ }
443
+
444
+ .agent-preset-label {
445
+ font-family: 'Space Grotesk', sans-serif;
446
+ font-size: 10px;
447
+ font-weight: 700;
448
+ letter-spacing: 1px;
449
+ text-transform: uppercase;
450
+ color: var(--cream-dark);
451
+ }
452
+
453
+ .agent-preset-select {
454
+ width: 100%;
455
+ background: #141414;
456
+ border: 1px solid var(--border);
457
+ color: var(--text-bright);
458
+ font-family: 'Space Grotesk', sans-serif;
459
+ font-size: 11px;
460
+ font-weight: 600;
461
+ padding: 6px 8px;
462
+ border-radius: 3px;
463
+ outline: none;
464
+ }
465
+
466
+ .agent-preset-select:focus {
467
+ border-color: var(--border-active);
468
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.05);
469
+ }
470
+
471
+ .agent-preset-select option {
472
+ background: var(--surface);
473
+ color: var(--text-bright);
474
+ }
475
+
404
476
  .pane-state {
405
477
  display: flex;
406
478
  align-items: center;