claude-duo 2.0.0

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/ClaudeDuo.bat ADDED
@@ -0,0 +1,2 @@
1
+ @echo off
2
+ start "" wscript.exe "%~dp0Launch-ClaudeDuo.vbs"
package/ClaudeDuo.ps1 ADDED
@@ -0,0 +1,375 @@
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Claude Duo — run two Claude Code sessions in one Windows Terminal window.
5
+ #>
6
+ [CmdletBinding()]
7
+ param(
8
+ [string]$Left,
9
+ [string]$Right,
10
+ [ValidateSet('vertical', 'horizontal')]
11
+ [string]$Split = 'vertical',
12
+ [switch]$Maximized,
13
+ [switch]$NoGui
14
+ )
15
+
16
+ $ErrorActionPreference = 'Stop'
17
+ Add-Type -AssemblyName System.Windows.Forms
18
+ Add-Type -AssemblyName System.Drawing
19
+ [System.Windows.Forms.Application]::EnableVisualStyles()
20
+
21
+ $AppName = 'Claude Duo'
22
+ $ConfigDir = Join-Path $env:APPDATA 'ClaudeDuo'
23
+ $ConfigPath = Join-Path $ConfigDir 'config.json'
24
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
25
+
26
+ function Find-ClaudeCmd {
27
+ $candidates = @(
28
+ (Get-Command claude.cmd -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source),
29
+ 'C:\nodejs\claude.cmd',
30
+ (Join-Path $env:APPDATA 'npm\claude.cmd')
31
+ ) | Where-Object { $_ }
32
+
33
+ foreach ($path in $candidates) {
34
+ if ((Test-Path -LiteralPath $path) -and ($path -match '\.(cmd|exe)$')) { return $path }
35
+ }
36
+ return $null
37
+ }
38
+
39
+ function Find-WindowsTerminal {
40
+ $cmd = Get-Command wt.exe -ErrorAction SilentlyContinue
41
+ if ($cmd) { return $cmd.Source }
42
+ $store = Join-Path $env:LOCALAPPDATA 'Microsoft\WindowsApps\wt.exe'
43
+ if (Test-Path -LiteralPath $store) { return $store }
44
+ return $null
45
+ }
46
+
47
+ function Read-Config {
48
+ $defaults = [ordered]@{
49
+ LeftFolder = [Environment]::GetFolderPath('MyDocuments')
50
+ RightFolder = [Environment]::GetFolderPath('MyDocuments')
51
+ SameFolder = $true
52
+ Split = 'vertical'
53
+ Maximized = $true
54
+ SecondAccount = $true
55
+ }
56
+ if (-not (Test-Path -LiteralPath $ConfigPath)) { return $defaults }
57
+ try {
58
+ $raw = Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json
59
+ foreach ($key in @($defaults.Keys)) {
60
+ if ($null -ne $raw.$key) { $defaults[$key] = $raw.$key }
61
+ }
62
+ } catch { }
63
+ return $defaults
64
+ }
65
+
66
+ function Save-Config {
67
+ param([hashtable]$Config)
68
+ if (-not (Test-Path -LiteralPath $ConfigDir)) {
69
+ New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
70
+ }
71
+ ($Config | ConvertTo-Json) | Set-Content -LiteralPath $ConfigPath -Encoding UTF8
72
+ }
73
+
74
+ function Show-Alert {
75
+ param([string]$Text, [string]$Title = $AppName)
76
+ [System.Windows.Forms.MessageBox]::Show(
77
+ $Text, $Title,
78
+ [System.Windows.Forms.MessageBoxButtons]::OK,
79
+ [System.Windows.Forms.MessageBoxIcon]::Warning
80
+ ) | Out-Null
81
+ }
82
+
83
+ function ConvertTo-WtQuoted {
84
+ param([string]$Value)
85
+ if ([string]::IsNullOrWhiteSpace($Value)) { return '""' }
86
+ if ($Value -notmatch '[\s"]') { return $Value }
87
+ return '"' + ($Value -replace '"', '\"') + '"'
88
+ }
89
+
90
+ function Start-ClaudeDuo {
91
+ param(
92
+ [string]$LeftFolder,
93
+ [string]$RightFolder,
94
+ [string]$SplitMode,
95
+ [bool]$MaximizeWindow,
96
+ [bool]$SecondAccount
97
+ )
98
+
99
+ $wt = Find-WindowsTerminal
100
+ $claude = Find-ClaudeCmd
101
+
102
+ if (-not $wt) {
103
+ throw "Windows Terminal was not found. Install it from the Microsoft Store (search 'Windows Terminal'), then try again."
104
+ }
105
+ if (-not $claude) {
106
+ throw "Claude Code was not found. Install it, or add claude.cmd to PATH."
107
+ }
108
+ if (-not (Test-Path -LiteralPath $LeftFolder)) {
109
+ throw "Left folder does not exist:`n$LeftFolder"
110
+ }
111
+ if (-not (Test-Path -LiteralPath $RightFolder)) {
112
+ throw "Right folder does not exist:`n$RightFolder"
113
+ }
114
+
115
+ # Start-Process joins -ArgumentList with spaces and does not quote.
116
+ # Titles must not contain spaces, and -d paths with spaces must be quoted.
117
+ $splitFlag = if ($SplitMode -eq 'horizontal') { '-H' } else { '-V' }
118
+ $leftDir = ConvertTo-WtQuoted $LeftFolder
119
+ $rightDir = ConvertTo-WtQuoted $RightFolder
120
+ $leftCmd = ConvertTo-WtQuoted (Join-Path $ScriptDir 'Run-Claude-A.cmd')
121
+ $rightLauncher = if ($SecondAccount) { 'Run-Claude-B.cmd' } else { 'Run-Claude-A.cmd' }
122
+ $rightCmd = ConvertTo-WtQuoted (Join-Path $ScriptDir $rightLauncher)
123
+
124
+ $parts = New-Object System.Collections.Generic.List[string]
125
+ $parts.Add('--window new')
126
+ if ($MaximizeWindow) { $parts.Add('--maximized') }
127
+ $parts.Add("new-tab --title Claude-A --suppressApplicationTitle -d $leftDir cmd.exe /k $leftCmd")
128
+ $parts.Add(';')
129
+ $parts.Add("split-pane $splitFlag -s 0.5 --title Claude-B --suppressApplicationTitle -d $rightDir cmd.exe /k $rightCmd")
130
+ $arguments = ($parts -join ' ')
131
+
132
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
133
+ $psi.FileName = $wt
134
+ $psi.Arguments = $arguments
135
+ $psi.UseShellExecute = $true
136
+ [void][System.Diagnostics.Process]::Start($psi)
137
+ }
138
+
139
+ function New-DesktopShortcut {
140
+ $desktop = [Environment]::GetFolderPath('Desktop')
141
+ $lnkPath = Join-Path $desktop 'Claude Duo.lnk'
142
+ $target = Join-Path $ScriptDir 'Launch-ClaudeDuo.vbs'
143
+ $wt = Find-WindowsTerminal
144
+
145
+ $shell = New-Object -ComObject WScript.Shell
146
+ $shortcut = $shell.CreateShortcut($lnkPath)
147
+ $shortcut.TargetPath = $target
148
+ $shortcut.WorkingDirectory = $ScriptDir
149
+ $shortcut.Description = 'Open two Claude Code sessions in one window'
150
+ if ($wt) { $shortcut.IconLocation = "$wt,0" }
151
+ $shortcut.Save()
152
+ return $lnkPath
153
+ }
154
+
155
+ function New-Label {
156
+ param($Text, $X, $Y, $Width = 460, $Height = 22, $Color = '#E8E4DF', $Size = 9, $Bold = $false)
157
+ $label = New-Object System.Windows.Forms.Label
158
+ $label.Text = $Text
159
+ $label.Location = New-Object System.Drawing.Point($X, $Y)
160
+ $label.Size = New-Object System.Drawing.Size($Width, $Height)
161
+ $label.ForeColor = [System.Drawing.ColorTranslator]::FromHtml($Color)
162
+ $label.BackColor = [System.Drawing.Color]::Transparent
163
+ $fontStyle = if ($Bold) { [System.Drawing.FontStyle]::Bold } else { [System.Drawing.FontStyle]::Regular }
164
+ $label.Font = New-Object System.Drawing.Font('Segoe UI', $Size, $fontStyle)
165
+ return $label
166
+ }
167
+
168
+ function New-TextBox {
169
+ param($X, $Y, $Width = 372)
170
+ $box = New-Object System.Windows.Forms.TextBox
171
+ $box.Location = New-Object System.Drawing.Point($X, $Y)
172
+ $box.Size = New-Object System.Drawing.Size($Width, 28)
173
+ $box.Font = New-Object System.Drawing.Font('Segoe UI', 9)
174
+ $box.BorderStyle = 'FixedSingle'
175
+ $box.BackColor = [System.Drawing.ColorTranslator]::FromHtml('#2A2622')
176
+ $box.ForeColor = [System.Drawing.ColorTranslator]::FromHtml('#F4EFE8')
177
+ return $box
178
+ }
179
+
180
+ function New-Button {
181
+ param($Text, $X, $Y, $Width, $Height, $Back, $Fore)
182
+ $btn = New-Object System.Windows.Forms.Button
183
+ $btn.Text = $Text
184
+ $btn.Location = New-Object System.Drawing.Point($X, $Y)
185
+ $btn.Size = New-Object System.Drawing.Size($Width, $Height)
186
+ $btn.FlatStyle = 'Flat'
187
+ $btn.FlatAppearance.BorderSize = 0
188
+ $btn.BackColor = [System.Drawing.ColorTranslator]::FromHtml($Back)
189
+ $btn.ForeColor = [System.Drawing.ColorTranslator]::FromHtml($Fore)
190
+ $btn.Font = New-Object System.Drawing.Font('Segoe UI', 9, [System.Drawing.FontStyle]::Bold)
191
+ $btn.Cursor = [System.Windows.Forms.Cursors]::Hand
192
+ return $btn
193
+ }
194
+
195
+ function Show-Gui {
196
+ $config = Read-Config
197
+ $bg = [System.Drawing.ColorTranslator]::FromHtml('#161412')
198
+ $panel = [System.Drawing.ColorTranslator]::FromHtml('#1F1C19')
199
+
200
+ $form = New-Object System.Windows.Forms.Form
201
+ $form.Text = $AppName
202
+ $form.Size = New-Object System.Drawing.Size(520, 540)
203
+ $form.StartPosition = 'CenterScreen'
204
+ $form.FormBorderStyle = 'FixedSingle'
205
+ $form.MaximizeBox = $false
206
+ $form.BackColor = $bg
207
+ $form.ForeColor = [System.Drawing.ColorTranslator]::FromHtml('#F4EFE8')
208
+ $form.Font = New-Object System.Drawing.Font('Segoe UI', 9)
209
+
210
+ $form.Controls.Add((New-Label $AppName 24 18 460 32 '#F4EFE8' 18 $true))
211
+ $form.Controls.Add((New-Label 'Two Claude Code sessions in one window.' 24 52 460 22 '#B7A99A' 9))
212
+
213
+ $folderPanel = New-Object System.Windows.Forms.Panel
214
+ $folderPanel.Location = New-Object System.Drawing.Point(20, 88)
215
+ $folderPanel.Size = New-Object System.Drawing.Size(464, 168)
216
+ $folderPanel.BackColor = $panel
217
+ $form.Controls.Add($folderPanel)
218
+
219
+ $folderPanel.Controls.Add((New-Label 'Folder A (left / top)' 16 12 300 20 '#B7A99A' 8))
220
+ $txtLeft = New-TextBox 16 34 348
221
+ $txtLeft.Text = [string]$config.LeftFolder
222
+ $folderPanel.Controls.Add($txtLeft)
223
+ $btnBrowseA = New-Button 'Browse' 372 32 76 28 '#3A342E' '#F4EFE8'
224
+ $folderPanel.Controls.Add($btnBrowseA)
225
+
226
+ $folderPanel.Controls.Add((New-Label 'Folder B (right / bottom)' 16 72 300 20 '#B7A99A' 8))
227
+ $txtRight = New-TextBox 16 94 348
228
+ $txtRight.Text = [string]$config.RightFolder
229
+ $folderPanel.Controls.Add($txtRight)
230
+ $btnBrowseB = New-Button 'Browse' 372 92 76 28 '#3A342E' '#F4EFE8'
231
+ $folderPanel.Controls.Add($btnBrowseB)
232
+
233
+ $chkSame = New-Object System.Windows.Forms.CheckBox
234
+ $chkSame.Text = 'Use the same folder for both'
235
+ $chkSame.Location = New-Object System.Drawing.Point(16, 132)
236
+ $chkSame.Size = New-Object System.Drawing.Size(420, 22)
237
+ $chkSame.ForeColor = [System.Drawing.ColorTranslator]::FromHtml('#E8E4DF')
238
+ $chkSame.Checked = [bool]$config.SameFolder
239
+ $folderPanel.Controls.Add($chkSame)
240
+
241
+ $optPanel = New-Object System.Windows.Forms.Panel
242
+ $optPanel.Location = New-Object System.Drawing.Point(20, 268)
243
+ $optPanel.Size = New-Object System.Drawing.Size(464, 92)
244
+ $optPanel.BackColor = $panel
245
+ $form.Controls.Add($optPanel)
246
+
247
+ $radioSide = New-Object System.Windows.Forms.RadioButton
248
+ $radioSide.Text = 'Side by side'
249
+ $radioSide.Location = New-Object System.Drawing.Point(16, 18)
250
+ $radioSide.Size = New-Object System.Drawing.Size(140, 24)
251
+ $radioSide.ForeColor = [System.Drawing.ColorTranslator]::FromHtml('#F4EFE8')
252
+ $radioSide.Checked = ([string]$config.Split -ne 'horizontal')
253
+ $optPanel.Controls.Add($radioSide)
254
+
255
+ $radioStack = New-Object System.Windows.Forms.RadioButton
256
+ $radioStack.Text = 'One above the other'
257
+ $radioStack.Location = New-Object System.Drawing.Point(170, 18)
258
+ $radioStack.Size = New-Object System.Drawing.Size(170, 24)
259
+ $radioStack.ForeColor = [System.Drawing.ColorTranslator]::FromHtml('#F4EFE8')
260
+ $radioStack.Checked = ([string]$config.Split -eq 'horizontal')
261
+ $optPanel.Controls.Add($radioStack)
262
+
263
+ $chkMax = New-Object System.Windows.Forms.CheckBox
264
+ $chkMax.Text = 'Maximize'
265
+ $chkMax.Location = New-Object System.Drawing.Point(350, 18)
266
+ $chkMax.Size = New-Object System.Drawing.Size(100, 24)
267
+ $chkMax.ForeColor = [System.Drawing.ColorTranslator]::FromHtml('#F4EFE8')
268
+ $chkMax.Checked = [bool]$config.Maximized
269
+ $optPanel.Controls.Add($chkMax)
270
+
271
+ $chkSecond = New-Object System.Windows.Forms.CheckBox
272
+ $chkSecond.Text = 'Right pane: second Claude account (login once)'
273
+ $chkSecond.Location = New-Object System.Drawing.Point(16, 50)
274
+ $chkSecond.Size = New-Object System.Drawing.Size(430, 24)
275
+ $chkSecond.ForeColor = [System.Drawing.ColorTranslator]::FromHtml('#F4EFE8')
276
+ if ($null -eq $config.SecondAccount) { $chkSecond.Checked = $true } else { $chkSecond.Checked = [bool]$config.SecondAccount }
277
+ $optPanel.Controls.Add($chkSecond)
278
+
279
+ $btnLaunch = New-Button 'Open 2 Claude Code' 20 388 300 44 '#D97757' '#1A120E'
280
+ $btnLaunch.Font = New-Object System.Drawing.Font('Segoe UI', 11, [System.Drawing.FontStyle]::Bold)
281
+ $form.Controls.Add($btnLaunch)
282
+
283
+ $btnShortcut = New-Button 'Pin to Desktop' 328 388 156 44 '#3A342E' '#F4EFE8'
284
+ $form.Controls.Add($btnShortcut)
285
+
286
+ $claudePath = Find-ClaudeCmd
287
+ $wtPath = Find-WindowsTerminal
288
+ $claudeOk = if ($claudePath) { 'Claude Code found' } else { 'Claude Code NOT found' }
289
+ $wtOk = if ($wtPath) { 'Windows Terminal found' } else { 'Windows Terminal NOT found' }
290
+ $statusColor = if ($claudePath -and $wtPath) { '#8FBF8A' } else { '#E08A6A' }
291
+ $status = New-Label "$claudeOk · $wtOk" 24 444 460 22 $statusColor 8
292
+ $form.Controls.Add($status)
293
+
294
+ $ui = @{
295
+ Left = $txtLeft
296
+ Right = $txtRight
297
+ BrowseB = $btnBrowseB
298
+ Same = $chkSame
299
+ Side = $radioSide
300
+ Stack = $radioStack
301
+ Max = $chkMax
302
+ Second = $chkSecond
303
+ }
304
+
305
+ $syncRight = {
306
+ if ($ui.Same.Checked) {
307
+ $ui.Right.Text = $ui.Left.Text
308
+ $ui.Right.Enabled = $false
309
+ $ui.BrowseB.Enabled = $false
310
+ } else {
311
+ $ui.Right.Enabled = $true
312
+ $ui.BrowseB.Enabled = $true
313
+ }
314
+ }.GetNewClosure()
315
+
316
+ $pickFolder = {
317
+ param($box)
318
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
319
+ $dialog.Description = 'Choose a project folder for Claude Code'
320
+ $dialog.ShowNewFolderButton = $true
321
+ if ($box.Text -and (Test-Path -LiteralPath $box.Text)) { $dialog.SelectedPath = $box.Text }
322
+ if ($dialog.ShowDialog() -eq 'OK') {
323
+ $box.Text = $dialog.SelectedPath
324
+ & $syncRight
325
+ }
326
+ }
327
+
328
+ $btnBrowseA.Add_Click({ & $pickFolder $ui.Left }.GetNewClosure())
329
+ $btnBrowseB.Add_Click({ & $pickFolder $ui.Right }.GetNewClosure())
330
+ $chkSame.Add_CheckedChanged({ & $syncRight }.GetNewClosure())
331
+ $txtLeft.Add_TextChanged({ if ($ui.Same.Checked) { $ui.Right.Text = $ui.Left.Text } }.GetNewClosure())
332
+ & $syncRight
333
+
334
+ $btnLaunch.Add_Click({
335
+ $splitMode = if ($ui.Stack.Checked) { 'horizontal' } else { 'vertical' }
336
+ $cfg = @{
337
+ LeftFolder = $ui.Left.Text.Trim()
338
+ RightFolder = $ui.Right.Text.Trim()
339
+ SameFolder = [bool]$ui.Same.Checked
340
+ Split = $splitMode
341
+ Maximized = [bool]$ui.Max.Checked
342
+ SecondAccount = [bool]$ui.Second.Checked
343
+ }
344
+ try {
345
+ Start-ClaudeDuo -LeftFolder $cfg.LeftFolder -RightFolder $cfg.RightFolder -SplitMode $cfg.Split -MaximizeWindow $cfg.Maximized -SecondAccount $cfg.SecondAccount
346
+ Save-Config $cfg
347
+ } catch {
348
+ Show-Alert $_.Exception.Message
349
+ }
350
+ }.GetNewClosure())
351
+
352
+ $btnShortcut.Add_Click({
353
+ try {
354
+ $path = New-DesktopShortcut
355
+ Show-Alert "Shortcut created:`n$path"
356
+ } catch {
357
+ Show-Alert $_.Exception.Message
358
+ }
359
+ }.GetNewClosure())
360
+
361
+ $form.ShowDialog() | Out-Null
362
+ }
363
+
364
+ # --- entry ---
365
+ if ($NoGui) {
366
+ $cfg = Read-Config
367
+ $leftFolder = if ($Left) { $Left } else { [string]$cfg.LeftFolder }
368
+ $rightFolder = if ($Right) { $Right } else { if ([bool]$cfg.SameFolder) { $leftFolder } else { [string]$cfg.RightFolder } }
369
+ $splitMode = if ($PSBoundParameters.ContainsKey('Split')) { $Split } else { [string]$cfg.Split }
370
+ $max = if ($PSBoundParameters.ContainsKey('Maximized')) { [bool]$Maximized } else { [bool]$cfg.Maximized }
371
+ $second = if ($null -eq $cfg.SecondAccount) { $true } else { [bool]$cfg.SecondAccount }
372
+ Start-ClaudeDuo -LeftFolder $leftFolder -RightFolder $rightFolder -SplitMode $splitMode -MaximizeWindow $max -SecondAccount $second
373
+ } else {
374
+ Show-Gui
375
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muhammad Ferasat Ali
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ ' Silent launcher — no PowerShell console flash
2
+ Set fso = CreateObject("Scripting.FileSystemObject")
3
+ folder = fso.GetParentFolderName(WScript.ScriptFullName)
4
+ ps1 = folder & "\ClaudeDuo.ps1"
5
+ cmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & ps1 & """"
6
+ Set sh = CreateObject("Wscript.Shell")
7
+ sh.Run cmd, 0, False
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Claude Duo
2
+
3
+ Open-source Windows tool that opens **two [Claude Code](https://docs.anthropic.com/en/docs/claude-code) sessions in one window**.
4
+
5
+ Optional: left pane = your current account, right pane = a **second Claude account** (separate login).
6
+
7
+ ## Requirements
8
+
9
+ - Windows 10 or 11
10
+ - [Windows Terminal](https://aka.ms/terminal)
11
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (`npm install -g @anthropic-ai/claude-code`)
12
+
13
+ ## Install (npm)
14
+
15
+ ```bash
16
+ npm install -g claude-duo
17
+ claude-duo
18
+ ```
19
+
20
+ One-shot, no install:
21
+
22
+ ```bash
23
+ npx claude-duo
24
+ ```
25
+
26
+ Needs Windows 10/11, [Windows Terminal](https://aka.ms/terminal), and [Claude Code](https://docs.anthropic.com/en/docs/claude-code).
27
+
28
+ ### From GitHub (no npm)
29
+
30
+ ```bash
31
+ git clone https://github.com/mferasatali/claude-duo.git
32
+ ```
33
+
34
+ Double-click **`ClaudeDuo.bat`**, or **Pin to Desktop** in the app.
35
+
36
+ ## Use
37
+
38
+ 1. Pick the project folder(s).
39
+ 2. Keep **Right pane: second Claude account** checked if you want two logins.
40
+ 3. Click **Open 2 Claude Code**.
41
+ 4. Left pane uses your default Claude login. Right pane asks you to sign in once with the second account (saved under `%USERPROFILE%\.claude-account-2`).
42
+ 5. Click a pane, type a task, press Enter.
43
+
44
+ Do not type `claude` in a blank Command Prompt. The tool starts it for you.
45
+
46
+ ## Command line
47
+
48
+ ```powershell
49
+ powershell -ExecutionPolicy Bypass -File .\ClaudeDuo.ps1 -NoGui -Left "C:\projA" -Right "C:\projB" -Split vertical -Maximized
50
+ ```
51
+
52
+ `-Split` is `vertical` (side by side) or `horizontal` (stacked).
53
+
54
+ ## How it works
55
+
56
+ - Uses Windows Terminal split panes (`wt.exe`).
57
+ - Account A runs with your normal Claude config.
58
+ - Account B sets `CLAUDE_CONFIG_DIR` so logins do not overwrite each other.
59
+
60
+ ## License
61
+
62
+ [MIT](LICENSE)
@@ -0,0 +1,14 @@
1
+ @echo off
2
+ REM Account A: default Claude login
3
+ set "PATH=C:\nodejs;%APPDATA%\npm;%PATH%"
4
+ where claude.cmd >nul 2>&1
5
+ if %ERRORLEVEL%==0 (
6
+ claude.cmd %*
7
+ goto :eof
8
+ )
9
+ if exist "C:\nodejs\claude.cmd" (
10
+ "C:\nodejs\claude.cmd" %*
11
+ goto :eof
12
+ )
13
+ echo Claude Code not found. Install: npm install -g @anthropic-ai/claude-code
14
+ pause
@@ -0,0 +1,16 @@
1
+ @echo off
2
+ REM Account B: separate Claude login. First run asks you to sign in.
3
+ set "PATH=C:\nodejs;%APPDATA%\npm;%PATH%"
4
+ set "CLAUDE_CONFIG_DIR=%USERPROFILE%\.claude-account-2"
5
+ if not exist "%CLAUDE_CONFIG_DIR%" mkdir "%CLAUDE_CONFIG_DIR%"
6
+ where claude.cmd >nul 2>&1
7
+ if %ERRORLEVEL%==0 (
8
+ claude.cmd %*
9
+ goto :eof
10
+ )
11
+ if exist "C:\nodejs\claude.cmd" (
12
+ "C:\nodejs\claude.cmd" %*
13
+ goto :eof
14
+ )
15
+ echo Claude Code not found. Install: npm install -g @anthropic-ai/claude-code
16
+ pause
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const { spawn } = require('child_process')
5
+ const path = require('path')
6
+
7
+ if (process.platform !== 'win32') {
8
+ console.error('claude-duo is a Windows tool (Windows Terminal + Claude Code).')
9
+ process.exit(1)
10
+ }
11
+
12
+ const ps1 = path.join(__dirname, '..', 'ClaudeDuo.ps1')
13
+ const child = spawn(
14
+ 'powershell.exe',
15
+ ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ps1, ...process.argv.slice(2)],
16
+ { stdio: 'inherit', windowsHide: false }
17
+ )
18
+
19
+ child.on('exit', (code, signal) => {
20
+ if (signal) process.kill(process.pid, signal)
21
+ process.exit(code == null ? 1 : code)
22
+ })
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "claude-duo",
3
+ "version": "2.0.0",
4
+ "description": "Open two Claude Code sessions in one Windows Terminal window, with optional second-account login.",
5
+ "bin": {
6
+ "claude-duo": "bin/claude-duo.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "ClaudeDuo.ps1",
11
+ "ClaudeDuo.bat",
12
+ "Launch-ClaudeDuo.vbs",
13
+ "Run-Claude-A.cmd",
14
+ "Run-Claude-B.cmd",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "os": ["win32"],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/mferasatali/claude-duo.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/mferasatali/claude-duo/issues"
28
+ },
29
+ "homepage": "https://github.com/mferasatali/claude-duo#readme",
30
+ "keywords": [
31
+ "claude",
32
+ "claude-code",
33
+ "windows-terminal",
34
+ "split-pane"
35
+ ],
36
+ "author": "Muhammad Ferasat Ali",
37
+ "license": "MIT"
38
+ }