ninja-terminals 2.3.9 → 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/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;
package/server.js CHANGED
@@ -35,10 +35,10 @@ const {
35
35
  } = require('./lib/runtime-session');
36
36
 
37
37
  // ── Config ──────────────────────────────────────────────────
38
- const PREFERRED_PORT = parseInt(process.env.PORT || '3300', 10);
38
+ const PREFERRED_PORT = parseInt(process.env.PORT || process.env.HTTP_PORT || '3300', 10);
39
39
  const BIND_HOST = process.env.NINJA_BIND_HOST || '127.0.0.1';
40
40
  const DEFAULT_TERMINALS = parseInt(process.env.DEFAULT_TERMINALS || '4', 10);
41
- const CLAUDE_CMD = process.env.CLAUDE_CMD || 'claude --dangerously-skip-permissions';
41
+ const CLAUDE_CMD = process.env.CLAUDE_CMD || process.env.CLAUDE_CHROME_CMD || 'claude --chrome --model claude-opus-4-5-20251101';
42
42
  const SHELL = process.env.SHELL || '/bin/zsh';
43
43
  const PROJECT_DIR = __dirname;
44
44
  const DEFAULT_CWD = process.env.DEFAULT_CWD || null; // Set to target project path to avoid cross-project prompts
@@ -47,12 +47,17 @@ const INJECT_GUIDANCE = process.env.INJECT_GUIDANCE !== 'false'; // Default tru
47
47
  // Fleet modes — preset terminal configurations
48
48
  const FLEET_MODES = {
49
49
  claude: ['claude', 'claude', 'claude', 'claude'],
50
+ codex: ['codex', 'codex', 'codex', 'codex'],
51
+ opencode: ['opencode', 'opencode', 'opencode', 'opencode'],
50
52
  teams: ['claude', 'opencode', 'codex', 'shell'],
51
53
  mixed: ['claude', 'claude', 'opencode', 'shell'],
52
54
  shell: ['shell', 'shell', 'shell', 'shell'],
53
55
  duo: ['claude', 'opencode'],
54
56
  };
55
57
  const FLEET_MODE = process.env.NINJA_MODE || 'claude';
58
+ // Resolve the claude binary from PATH by default so it works on any machine.
59
+ // Override with CLAUDE_CMD (full command) or CLAUDE_BIN (binary path) if needed.
60
+ const CLAUDE_BIN = process.env.CLAUDE_BIN || 'claude';
56
61
 
57
62
  const sleep = ms => new Promise(r => setTimeout(r, ms));
58
63
 
@@ -152,7 +157,7 @@ function getTerminalRules(terminalId) {
152
157
  // ── Terminal Spawning ───────────────────────────────────────
153
158
 
154
159
  const AGENT_COMMANDS = {
155
- claude: process.env.CLAUDE_CMD || 'claude --dangerously-skip-permissions',
160
+ claude: process.env.CLAUDE_CMD || `${CLAUDE_BIN} --dangerously-skip-permissions`,
156
161
  opencode: 'opencode',
157
162
  codex: 'codex',
158
163
  shell: null, // No command — just shell
@@ -197,6 +202,9 @@ function spawnTerminal(label, scope = [], cwd = null, tier = 'pro', agentType =
197
202
  env: {
198
203
  ...cleanEnv,
199
204
  TERM: 'xterm-256color',
205
+ COLORTERM: 'truecolor',
206
+ FORCE_COLOR: '1',
207
+ CLICOLOR_FORCE: '1',
200
208
  HOME: require('os').homedir(),
201
209
  PATH: `${require('os').homedir()}/.local/bin:/opt/homebrew/bin:${process.env.PATH || ''}`,
202
210
  SHELL_SESSIONS_DISABLE: '1',
@@ -1292,13 +1300,16 @@ async function startServer() {
1292
1300
  server.listen(selectedPort, BIND_HOST, () => {
1293
1301
  const url = `http://localhost:${selectedPort}`;
1294
1302
  console.log(`[bind] Listening on ${BIND_HOST}:${selectedPort}`);
1303
+ const fleetConfig = FLEET_MODES[FLEET_MODE] || FLEET_MODES.claude;
1304
+ const terminalCount = DEFAULT_TERMINALS > 0 ? Math.min(DEFAULT_TERMINALS, fleetConfig.length) : fleetConfig.length;
1295
1305
  const session = writeRuntimeSession({
1296
1306
  port: selectedPort,
1297
1307
  host: BIND_HOST,
1298
1308
  url,
1299
1309
  cwd: DEFAULT_CWD || process.cwd(),
1300
- terminals: DEFAULT_TERMINALS,
1310
+ terminals: terminalCount,
1301
1311
  command: 'ninja-terminals',
1312
+ launchConfig: { mode: FLEET_MODE, terminalCount },
1302
1313
  });
1303
1314
 
1304
1315
  console.log(`Ninja Terminals v2 running on ${url}`);
@@ -1314,8 +1325,6 @@ async function startServer() {
1314
1325
  startSessionHeartbeat(sessionCache, handleSessionInvalidation, 5 * 60 * 1000);
1315
1326
 
1316
1327
  // Auto-spawn terminals based on fleet mode
1317
- const fleetConfig = FLEET_MODES[FLEET_MODE] || FLEET_MODES.claude;
1318
- const terminalCount = DEFAULT_TERMINALS > 0 ? Math.min(DEFAULT_TERMINALS, fleetConfig.length) : fleetConfig.length;
1319
1328
  const agentLabels = { claude: 'Claude', opencode: 'OpenCode', codex: 'Codex', shell: 'Shell' };
1320
1329
 
1321
1330
  console.log(`Auto-spawning ${terminalCount} terminals (mode: ${FLEET_MODE})...`);