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/CLAUDE.md +2 -22
- package/README.md +3 -1
- package/cli.js +34 -52
- package/lib/ninja-request.js +247 -0
- package/lib/settings-gen.js +0 -13
- package/mcp-server.js +7 -33
- package/ninja-ensure.js +92 -16
- package/package.json +1 -1
- package/public/app.js +55 -308
- package/public/index.html +15 -44
- package/public/style.css +78 -6
- package/server.js +15 -6
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
|
-
<
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
/*
|
|
386
|
-
.terminal-pane
|
|
387
|
-
.terminal-pane
|
|
388
|
-
.terminal-pane
|
|
389
|
-
.terminal-pane
|
|
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 --
|
|
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 ||
|
|
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:
|
|
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})...`);
|