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 +1 -1
- package/public/app.js +66 -0
- package/public/index.html +2 -0
- package/public/style.css +57 -0
- package/server.js +23 -21
package/package.json
CHANGED
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
|
-
//
|
|
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
|
-
|
|
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:
|
|
400
|
+
terminals: [],
|
|
399
401
|
});
|
|
400
402
|
} catch (err) {
|
|
401
403
|
res.status(500).json({ error: 'Failed to create session', detail: err.message });
|