ninja-terminals 2.1.0 → 2.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ninja-terminals",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Multi-terminal Claude Code orchestrator with DAG task management, permission hooks, and resilience",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -339,11 +339,23 @@ function createTerminalUI(termData) {
339
339
  progressFill.style.width = `${progress || 0}%`;
340
340
  progressTrack.appendChild(progressFill);
341
341
 
342
+ // Close button (X)
343
+ const closeBtn = document.createElement('button');
344
+ closeBtn.className = 'close-btn';
345
+ closeBtn.innerHTML = '×';
346
+ closeBtn.title = 'Close terminal';
347
+ closeBtn.addEventListener('mousedown', (e) => {
348
+ e.preventDefault();
349
+ e.stopPropagation();
350
+ closeTerminal(id);
351
+ });
352
+
342
353
  header.appendChild(labelEl);
343
354
  header.appendChild(stateEl);
344
355
  header.appendChild(elapsedEl);
345
356
  header.appendChild(spacer);
346
357
  header.appendChild(actionsEl);
358
+ header.appendChild(closeBtn);
347
359
  header.appendChild(progressTrack);
348
360
 
349
361
  // Double-click header to maximize/restore
@@ -1010,6 +1022,13 @@ async function startApp() {
1010
1022
  setupSidebar();
1011
1023
  setupAddTask();
1012
1024
  setupLearnings();
1025
+ setupAddTerminal();
1026
+
1027
+ // Clear all button
1028
+ const clearBtn = document.getElementById('clear-all-btn');
1029
+ if (clearBtn) {
1030
+ clearBtn.addEventListener('click', clearAllTerminals);
1031
+ }
1013
1032
 
1014
1033
  // Load existing terminals
1015
1034
  try {
@@ -1047,6 +1066,53 @@ async function startApp() {
1047
1066
 
1048
1067
  // ── Learnings Module ───────────────────────────────────────────
1049
1068
 
1069
+ function setupAddTerminal() {
1070
+ const btn = document.getElementById('add-terminal-btn');
1071
+ if (!btn) return;
1072
+
1073
+ // Store last used directory
1074
+ let lastCwd = localStorage.getItem('ninja-last-cwd') || '/Users/davidmini/Desktop/Projects';
1075
+
1076
+ btn.addEventListener('click', async () => {
1077
+ try {
1078
+ const cwd = prompt('Working directory:', lastCwd);
1079
+ if (!cwd) return;
1080
+
1081
+ lastCwd = cwd;
1082
+ localStorage.setItem('ninja-last-cwd', cwd);
1083
+
1084
+ const res = await fetch(`${API_BASE}/api/terminals`, {
1085
+ method: 'POST',
1086
+ headers: { 'Content-Type': 'application/json', ...auth.getAuthHeader() },
1087
+ body: JSON.stringify({ cwd }),
1088
+ });
1089
+
1090
+ if (!res.ok) {
1091
+ const err = await res.json();
1092
+ alert(`Failed to create terminal: ${err.error || 'Unknown error'}`);
1093
+ return;
1094
+ }
1095
+
1096
+ const terminal = await res.json();
1097
+ createTerminalUI(terminal);
1098
+ addFeedEntry(`Terminal added: T${terminal.id}`);
1099
+ } catch (err) {
1100
+ console.error('Failed to add terminal:', err);
1101
+ alert('Failed to add terminal');
1102
+ }
1103
+ });
1104
+ }
1105
+
1106
+ async function clearAllTerminals() {
1107
+ if (!confirm('Close all terminals and start fresh?')) return;
1108
+
1109
+ const ids = Array.from(state.terminals.keys());
1110
+ for (const id of ids) {
1111
+ await closeTerminal(id);
1112
+ }
1113
+ addFeedEntry('All terminals cleared');
1114
+ }
1115
+
1050
1116
  function setupLearnings() {
1051
1117
  const btn = document.getElementById('learnings-btn');
1052
1118
  const overlay = document.getElementById('learnings-overlay');
package/public/index.html CHANGED
@@ -60,6 +60,8 @@
60
60
  <div class="logo-card">
61
61
  <span class="logo">NINJA TERMINALS</span>
62
62
  <span class="logo-sub">Multi-Agent Orchestrator</span>
63
+ <button id="add-terminal-btn" class="add-terminal-btn" title="Add Terminal">+</button>
64
+ <button id="clear-all-btn" class="clear-all-btn" title="Clear All Terminals">🗑</button>
63
65
  <button id="learnings-btn" class="learnings-btn" title="View Session Learnings">🧠</button>
64
66
  <button id="logout-btn" class="logout-btn" title="Sign out">Logout</button>
65
67
  </div>
package/public/style.css CHANGED
@@ -976,3 +976,60 @@ main {
976
976
  .learnings-btn:hover {
977
977
  background: var(--border);
978
978
  }
979
+
980
+ /* ── Terminal Close Button ─────────────────────── */
981
+
982
+ .close-btn {
983
+ background: none;
984
+ border: none;
985
+ color: var(--text-muted);
986
+ font-size: 20px;
987
+ line-height: 1;
988
+ padding: 2px 8px;
989
+ cursor: pointer;
990
+ opacity: 0.5;
991
+ transition: opacity 0.15s, color 0.15s;
992
+ margin-left: 8px;
993
+ }
994
+
995
+ .close-btn:hover {
996
+ opacity: 1;
997
+ color: #ff6b6b;
998
+ }
999
+
1000
+ /* ── Add Terminal Button ─────────────────────────── */
1001
+
1002
+ .add-terminal-btn {
1003
+ background: var(--feed-t1);
1004
+ border: none;
1005
+ color: var(--bg);
1006
+ font-size: 20px;
1007
+ font-weight: bold;
1008
+ line-height: 1;
1009
+ padding: 4px 10px;
1010
+ border-radius: 4px;
1011
+ cursor: pointer;
1012
+ transition: background 0.15s, transform 0.15s;
1013
+ }
1014
+
1015
+ .add-terminal-btn:hover {
1016
+ background: var(--feed-t2);
1017
+ transform: scale(1.1);
1018
+ }
1019
+
1020
+ /* ── Clear All Button ─────────────────────────────── */
1021
+
1022
+ .clear-all-btn {
1023
+ background: none;
1024
+ border: none;
1025
+ font-size: 16px;
1026
+ padding: 4px 8px;
1027
+ cursor: pointer;
1028
+ opacity: 0.6;
1029
+ transition: opacity 0.15s, transform 0.15s;
1030
+ }
1031
+
1032
+ .clear-all-btn:hover {
1033
+ opacity: 1;
1034
+ transform: scale(1.1);
1035
+ }
package/server.js CHANGED
@@ -346,14 +346,30 @@ app.get('/health', (_req, res) => {
346
346
 
347
347
  // ── Session Endpoints ───────────────────────────────────────
348
348
 
349
- // Create session — validates token and spawns terminals
349
+ // Create session — validates token and returns existing or spawns terminals
350
350
  app.post('/api/session', requireAuth, (req, res) => {
351
351
  try {
352
352
  const { tier, terminalsMax, features, token } = req.ninjaUser;
353
353
 
354
- // Clear any existing session
354
+ // If session already exists with same token, return existing terminals
355
+ if (activeSession && activeSession.token === token) {
356
+ const existingTerminals = activeSession.terminalIds
357
+ .map(id => terminals.get(id))
358
+ .filter(Boolean)
359
+ .map(t => ({ id: t.id, label: t.label, status: t.status, cwd: t.cwd }));
360
+
361
+ console.log(`[session] Returning existing session: tier=${tier}, terminals=${existingTerminals.length}`);
362
+
363
+ return res.json({
364
+ tier,
365
+ terminalsMax,
366
+ features,
367
+ terminals: existingTerminals,
368
+ });
369
+ }
370
+
371
+ // Clear any existing session with different token
355
372
  if (activeSession) {
356
- // Kill existing terminals
357
373
  for (const id of activeSession.terminalIds) {
358
374
  const terminal = terminals.get(id);
359
375
  if (terminal) {
@@ -364,7 +380,7 @@ app.post('/api/session', requireAuth, (req, res) => {
364
380
  }
365
381
  }
366
382
 
367
- // Create new session
383
+ // Create new session (but don't auto-spawn terminals - let user add them)
368
384
  activeSession = {
369
385
  token,
370
386
  tier,
@@ -374,28 +390,14 @@ app.post('/api/session', requireAuth, (req, res) => {
374
390
  createdAt: Date.now(),
375
391
  };
376
392
 
377
- // Spawn terminals up to the tier limit
378
- const cwd = req.body?.cwd || DEFAULT_CWD;
379
- const spawnedTerminals = [];
380
-
381
- for (let i = 0; i < terminalsMax; i++) {
382
- const terminal = spawnTerminal(`T${i + 1}`, [], cwd, tier);
383
- activeSession.terminalIds.push(terminal.id);
384
- spawnedTerminals.push({
385
- id: terminal.id,
386
- label: terminal.label,
387
- status: terminal.status,
388
- cwd: terminal.cwd,
389
- });
390
- }
391
-
392
- console.log(`[session] Created session: tier=${tier}, terminals=${terminalsMax}`);
393
+ console.log(`[session] Created new session: tier=${tier}, terminalsMax=${terminalsMax}`);
393
394
 
395
+ // Return empty terminals - user can add via + button
394
396
  res.json({
395
397
  tier,
396
398
  terminalsMax,
397
399
  features,
398
- terminals: spawnedTerminals,
400
+ terminals: [],
399
401
  });
400
402
  } catch (err) {
401
403
  res.status(500).json({ error: 'Failed to create session', detail: err.message });