feat: add multi-tab feature to open 1-10 Claude sessions at once

- Add number input (1-10) next to "Run Claude" button in toolbar
- When tabCount > 1, create sessions named "1-projectname", "2-projectname", etc.
- All sessions share the same working directory (case folder)
- First session is auto-selected after creation
- Add CSS for small number input in toolbar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-19 02:55:10 +01:00
co-authored by Claude Opus 4.5
parent 27323217a1
commit 5b2091bd86
3 changed files with 79 additions and 17 deletions
+56 -17
View File
@@ -678,32 +678,71 @@ class ClaudemanApp {
async runClaude() {
const caseName = document.getElementById('quickStartCase').value || 'testcase';
const tabCount = Math.min(10, Math.max(1, parseInt(document.getElementById('tabCount').value) || 1));
this.terminal.clear();
this.terminal.writeln(`\x1b[1;32m Starting Claude in ${caseName}...\x1b[0m`);
this.terminal.writeln(`\x1b[1;32m Starting ${tabCount} Claude session(s) in ${caseName}...\x1b[0m`);
this.terminal.writeln('');
try {
const res = await fetch('/api/quick-start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ caseName })
});
const data = await res.json();
// Get case path first
const caseRes = await fetch(`/api/cases/${caseName}`);
const caseData = await caseRes.json();
if (!data.success) throw new Error(data.error);
this.activeSessionId = data.sessionId;
this.loadQuickStartCases();
// Send resize
const dims = this.fitAddon.proposeDimensions();
if (dims) {
await fetch(`/api/sessions/${data.sessionId}/resize`, {
// Create the case if it doesn't exist
if (!caseData.path) {
const createCaseRes = await fetch('/api/cases', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cols: dims.cols, rows: dims.rows })
body: JSON.stringify({ name: caseName, description: '' })
});
const createCaseData = await createCaseRes.json();
if (!createCaseData.success) throw new Error(createCaseData.error || 'Failed to create case');
}
const workingDir = caseData.path || `${process.env.HOME}/claudeman-cases/${caseName}`;
let firstSessionId = null;
// Create multiple sessions
for (let i = 1; i <= tabCount; i++) {
const sessionName = tabCount > 1 ? `${i}-${caseName}` : caseName;
// Create session
const createRes = await fetch('/api/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ workingDir, name: sessionName })
});
const createData = await createRes.json();
if (!createData.success) throw new Error(createData.error);
// Start interactive mode
await fetch(`/api/sessions/${createData.session.id}/interactive`, {
method: 'POST'
});
// Track first session
if (i === 1) {
firstSessionId = createData.session.id;
}
this.terminal.writeln(`\x1b[90m Created session ${i}/${tabCount}: ${sessionName}\x1b[0m`);
}
// Select the first session
if (firstSessionId) {
this.activeSessionId = firstSessionId;
this.loadQuickStartCases();
// Send resize
const dims = this.fitAddon.proposeDimensions();
if (dims) {
await fetch(`/api/sessions/${firstSessionId}/resize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cols: dims.cols, rows: dims.rows })
});
}
}
this.terminal.focus();
+1
View File
@@ -73,6 +73,7 @@
<button class="btn-toolbar btn-primary" onclick="app.runClaude()" title="Run Claude (Ctrl+Enter)">
Run Claude
</button>
<input type="number" id="tabCount" class="toolbar-input-sm" value="1" min="1" max="10" title="Number of tabs to open (1-10)">
<button class="btn-toolbar" onclick="app.runShell()" title="Run Shell">
Shell
</button>
+22
View File
@@ -539,6 +539,28 @@ body {
max-width: 50vw;
}
.toolbar-input-sm {
padding: 0.3rem 0.4rem;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-size: 0.75rem;
width: 40px;
text-align: center;
}
.toolbar-input-sm:focus {
outline: none;
border-color: var(--accent);
}
.toolbar-input-sm::-webkit-inner-spin-button,
.toolbar-input-sm::-webkit-outer-spin-button {
opacity: 1;
height: 16px;
}
#dirDisplay {
max-width: 300px;
overflow: hidden;