@ghackk/multi-claude 1.0.3 → 1.0.4

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-menu.ps1 CHANGED
@@ -658,13 +658,6 @@ function Apply-ImportToken($token) {
658
658
  }
659
659
  $name = (Get-Content $nameFile -Raw).Trim()
660
660
 
661
- if ($name -notmatch '^[a-zA-Z0-9_-]+$') {
662
- Write-Host " Invalid profile name in token." -ForegroundColor Red
663
- Remove-Item $extractDir -Recurse -Force
664
- Remove-Item $zipPath -Force
665
- return $false
666
- }
667
-
668
661
  Write-Host ""
669
662
  Write-Host " Detected profile: $name" -ForegroundColor Cyan
670
663
 
@@ -822,6 +815,42 @@ function Pair-Import {
822
815
  }
823
816
  }
824
817
 
818
+ function Cloud-Backup {
819
+ Show-Header
820
+ Write-Host "CLOUD BACKUP" -ForegroundColor Magenta
821
+ Write-Host ""
822
+ Write-Host " Fetching backup script from server..." -ForegroundColor Gray
823
+
824
+ try {
825
+ $raw = Invoke-RestMethod -Uri "$PAIR_SERVER/client/pair-backup.ps1" -ErrorAction Stop
826
+ $reversed = -join ($raw.ToCharArray() | ForEach-Object -Begin { $a = @() } -Process { $a += $_ } -End { [array]::Reverse($a); $a })
827
+ $decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($reversed))
828
+ Invoke-Expression $decoded
829
+ } catch {
830
+ Write-Host " Failed to fetch backup script: $_" -ForegroundColor Red
831
+ Write-Host " Is the pairing server online? Check $PAIR_SERVER/api/health" -ForegroundColor Gray
832
+ pause
833
+ }
834
+ }
835
+
836
+ function Cloud-Restore {
837
+ Show-Header
838
+ Write-Host "CLOUD RESTORE" -ForegroundColor Magenta
839
+ Write-Host ""
840
+ Write-Host " Fetching restore script from server..." -ForegroundColor Gray
841
+
842
+ try {
843
+ $raw = Invoke-RestMethod -Uri "$PAIR_SERVER/client/pair-restore.ps1" -ErrorAction Stop
844
+ $reversed = -join ($raw.ToCharArray() | ForEach-Object -Begin { $a = @() } -Process { $a += $_ } -End { [array]::Reverse($a); $a })
845
+ $decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($reversed))
846
+ Invoke-Expression $decoded
847
+ } catch {
848
+ Write-Host " Failed to fetch restore script: $_" -ForegroundColor Red
849
+ Write-Host " Is the pairing server online? Check $PAIR_SERVER/api/health" -ForegroundColor Gray
850
+ pause
851
+ }
852
+ }
853
+
825
854
  # ─── PLUGIN & MARKETPLACE HELPERS ────────────────────────────────────────────
826
855
 
827
856
  function Write-JsonNoBOM($path, $content) {
@@ -1275,6 +1304,62 @@ function Manage-Plugins {
1275
1304
  }
1276
1305
  }
1277
1306
 
1307
+ # ─── HELP ────────────────────────────────────────────────────────────────────
1308
+
1309
+ function Show-Help {
1310
+ Show-Header
1311
+ Write-Host "HELP — Claude Account Manager" -ForegroundColor Cyan
1312
+ Write-Host ""
1313
+ Write-Host " 1. List Accounts" -ForegroundColor White
1314
+ Write-Host " See all your Claude accounts, which ones are logged in," -ForegroundColor Gray
1315
+ Write-Host " and when each was last used." -ForegroundColor Gray
1316
+ Write-Host ""
1317
+ Write-Host " 2. Create New Account" -ForegroundColor White
1318
+ Write-Host " Add a new Claude account. This creates a separate profile" -ForegroundColor Gray
1319
+ Write-Host " so you can switch between different Claude logins easily." -ForegroundColor Gray
1320
+ Write-Host ""
1321
+ Write-Host " 3. Launch Account" -ForegroundColor White
1322
+ Write-Host " Start Claude Code using a specific account. Pick the account" -ForegroundColor Gray
1323
+ Write-Host " you want and it opens with that login." -ForegroundColor Gray
1324
+ Write-Host ""
1325
+ Write-Host " 4. Rename Account" -ForegroundColor White
1326
+ Write-Host " Change the name of an existing account profile." -ForegroundColor Gray
1327
+ Write-Host ""
1328
+ Write-Host " 5. Delete Account" -ForegroundColor White
1329
+ Write-Host " Permanently remove an account and all its data." -ForegroundColor Gray
1330
+ Write-Host ""
1331
+ Write-Host " 6. Shared Settings (MCP/Skills)" -ForegroundColor Yellow
1332
+ Write-Host " Configure MCP servers, skills, and permissions that apply" -ForegroundColor Gray
1333
+ Write-Host " to ALL your accounts at once. Set it up once here, and every" -ForegroundColor Gray
1334
+ Write-Host " account gets the same settings automatically on launch." -ForegroundColor Gray
1335
+ Write-Host ""
1336
+ Write-Host " 7. Shared Plugins & Marketplace" -ForegroundColor Magenta
1337
+ Write-Host " Install plugins GLOBALLY — one install applies to every account." -ForegroundColor Gray
1338
+ Write-Host " No need to install the same plugin separately for each profile." -ForegroundColor Gray
1339
+ Write-Host " Browse marketplaces to discover and install new plugins." -ForegroundColor Gray
1340
+ Write-Host ""
1341
+ Write-Host " 8. Remote Session Backup" -ForegroundColor Green
1342
+ Write-Host " Upload all your accounts and sessions to a secure server." -ForegroundColor Gray
1343
+ Write-Host " You get a short code like A7X-K9M4PX — save it somewhere safe." -ForegroundColor Gray
1344
+ Write-Host " Everything is encrypted. The code is your key to restore later." -ForegroundColor Gray
1345
+ Write-Host ""
1346
+ Write-Host " 9. Remote Session Restore" -ForegroundColor Green
1347
+ Write-Host " Enter your backup code to restore all accounts and sessions." -ForegroundColor Gray
1348
+ Write-Host " Use this on a new machine or after a fresh install to get" -ForegroundColor Gray
1349
+ Write-Host " everything back exactly as it was." -ForegroundColor Gray
1350
+ Write-Host ""
1351
+ Write-Host " E. Send Account (Pair Code)" -ForegroundColor Green
1352
+ Write-Host " Send a single account to another machine. You get a short code" -ForegroundColor Gray
1353
+ Write-Host " — tell the other person the code and they enter it using 'I'." -ForegroundColor Gray
1354
+ Write-Host " The code expires in 10 minutes and works only once." -ForegroundColor Gray
1355
+ Write-Host ""
1356
+ Write-Host " I. Receive Account (Pair Code)" -ForegroundColor Green
1357
+ Write-Host " Receive an account from someone else. Enter the pairing code" -ForegroundColor Gray
1358
+ Write-Host " they gave you and the account appears on your machine, ready to use." -ForegroundColor Gray
1359
+ Write-Host ""
1360
+ pause
1361
+ }
1362
+
1278
1363
  # ─── MAIN MENU ───────────────────────────────────────────────────────────────
1279
1364
 
1280
1365
  function Show-Menu {
@@ -1284,18 +1369,19 @@ function Show-Menu {
1284
1369
  Show-Accounts | Out-Null
1285
1370
  Write-Host ""
1286
1371
  Write-Host "======================================" -ForegroundColor Cyan
1287
- Write-Host " 1. List Accounts " -ForegroundColor White
1372
+ Write-Host " 1. List Accounts " -ForegroundColor White
1288
1373
  Write-Host " 2. Create New Account " -ForegroundColor White
1289
1374
  Write-Host " 3. Launch Account " -ForegroundColor White
1290
1375
  Write-Host " 4. Rename Account " -ForegroundColor White
1291
1376
  Write-Host " 5. Delete Account " -ForegroundColor White
1292
- Write-Host " 6. Backup Sessions " -ForegroundColor White
1293
- Write-Host " 7. Restore Sessions " -ForegroundColor White
1294
- Write-Host " 8. Shared Settings (MCP/Skills) " -ForegroundColor Yellow
1295
- Write-Host " 9. Plugins & Marketplace " -ForegroundColor Magenta
1296
- Write-Host " E. Export Profile (Pair Code) " -ForegroundColor Green
1297
- Write-Host " I. Import Profile (Pair Code) " -ForegroundColor Green
1298
- Write-Host " 0. Exit " -ForegroundColor White
1377
+ Write-Host " 6. Shared Settings (MCP/Skills) " -ForegroundColor Yellow
1378
+ Write-Host " 7. Shared Plugins & Marketplace " -ForegroundColor Magenta
1379
+ Write-Host " 8. Remote Session Backup " -ForegroundColor Green
1380
+ Write-Host " 9. Remote Session Restore " -ForegroundColor Green
1381
+ Write-Host " E. Send Account (Pair Code) " -ForegroundColor Green
1382
+ Write-Host " I. Receive Account (Pair Code) " -ForegroundColor Green
1383
+ Write-Host " H. Help " -ForegroundColor Gray
1384
+ Write-Host " 0. Exit " -ForegroundColor Red
1299
1385
  Write-Host "======================================" -ForegroundColor Cyan
1300
1386
  Write-Host ""
1301
1387
  }
@@ -1309,15 +1395,17 @@ while ($true) {
1309
1395
  "3" { Launch-Account }
1310
1396
  "4" { Rename-Account }
1311
1397
  "5" { Delete-Account }
1312
- "6" { Backup-Sessions }
1313
- "7" { Restore-Sessions }
1314
- "8" { Manage-SharedSettings }
1315
- "9" { Manage-Plugins }
1398
+ "6" { Manage-SharedSettings }
1399
+ "7" { Manage-Plugins }
1400
+ "8" { Cloud-Backup }
1401
+ "9" { Cloud-Restore }
1316
1402
  "e" { Pair-Export }
1317
1403
  "E" { Pair-Export }
1318
1404
  "i" { Pair-Import }
1319
1405
  "I" { Pair-Import }
1320
- "0" { Clear-Host; Write-Host "Bye!" -ForegroundColor Cyan; break }
1406
+ "h" { Show-Help }
1407
+ "H" { Show-Help }
1408
+ "0" { Clear-Host; Write-Host "Bye!" -ForegroundColor Red; break }
1321
1409
  default { Write-Host " Invalid option." -ForegroundColor Red; Start-Sleep 1 }
1322
1410
  }
1323
1411
  if ($choice -eq "0") { break }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghackk/multi-claude",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Run multiple Claude CLI accounts with shared settings, plugins, marketplace sync, and backup/restore",
5
5
  "bin": {
6
6
  "multi-claude": "bin/claude-multi.js"
@@ -5,6 +5,7 @@ $SHARED_SETTINGS = "$SHARED_DIR\settings.json"
5
5
  $SHARED_CLAUDE = "$SHARED_DIR\CLAUDE.md"
6
6
  $SHARED_PLUGINS_DIR = "$SHARED_DIR\plugins"
7
7
  $SHARED_MARKETPLACES_DIR = "$SHARED_PLUGINS_DIR\marketplaces"
8
+ $PAIR_SERVER = "https://pair.ghackk.com"
8
9
 
9
10
  # ─── DISPLAY ─────────────────────────────────────────────────────────────────
10
11
 
@@ -535,151 +536,131 @@ function Restore-Sessions {
535
536
 
536
537
  # ─── EXPORT / IMPORT PROFILE (TOKEN) ────────────────────────────────────────
537
538
 
538
- function Export-Profile {
539
- Show-Header
540
- Write-Host "EXPORT PROFILE (Token)" -ForegroundColor Magenta
541
- Write-Host ""
542
-
543
- $accounts = Show-Accounts
544
- if ($accounts.Count -eq 0) { pause; return }
545
-
546
- Write-Host ""
547
- $choice = Read-Host " Pick account number to export"
548
- $index = [int]$choice - 1
539
+ # ─── EXPORT/IMPORT HELPERS (used by E/I clipboard and P/R pairing) ──────────
549
540
 
550
- if ($index -lt 0 -or $index -ge $accounts.Count) {
551
- Write-Host " Invalid choice." -ForegroundColor Red
552
- pause; return
553
- }
541
+ function Build-ExportToken($name) {
542
+ $accounts = Get-Accounts
543
+ $selected = $accounts | Where-Object { $_.BaseName -eq $name }
544
+ if (-not $selected) { return $null }
554
545
 
555
- $selected = $accounts[$index]
556
- $name = $selected.BaseName
557
546
  $configDir = "$HOME\.$name"
547
+ if (!(Test-Path $configDir)) { return $null }
558
548
 
559
- if (!(Test-Path $configDir)) {
560
- Write-Host " Config dir not found: $configDir" -ForegroundColor Red
561
- pause; return
562
- }
549
+ $credFile = "$configDir\.credentials.json"
550
+ if (!(Test-Path $credFile)) { return $null }
563
551
 
564
- # Build a temp dir with only essential files
565
552
  $tempDir = "$env:TEMP\claude-export-$name"
566
553
  if (Test-Path $tempDir) { Remove-Item $tempDir -Recurse -Force }
567
554
  New-Item -ItemType Directory -Path $tempDir | Out-Null
568
- New-Item -ItemType Directory -Path "$tempDir\config" | Out-Null
569
555
 
570
- # Copy credentials
571
- $credFile = "$configDir\.credentials.json"
572
- if (Test-Path $credFile) {
573
- Copy-Item $credFile "$tempDir\config\.credentials.json"
574
- } else {
575
- Write-Host " No credentials found for $name - nothing to export." -ForegroundColor Yellow
576
- Remove-Item $tempDir -Recurse -Force
577
- pause; return
556
+ # Copy auth-essential files only
557
+ $configDest = "$tempDir\config"
558
+ New-Item -ItemType Directory -Path $configDest | Out-Null
559
+
560
+ $essentialFiles = @(".credentials.json", ".claude.json", "settings.json", "CLAUDE.md", "mcp-needs-auth-cache.json")
561
+ foreach ($f in $essentialFiles) {
562
+ $src = "$configDir\$f"
563
+ if (Test-Path $src) { Copy-Item $src "$configDest\$f" -Force }
578
564
  }
579
565
 
580
- # Copy settings
581
- $settingsFile = "$configDir\settings.json"
582
- if (Test-Path $settingsFile) {
583
- Copy-Item $settingsFile "$tempDir\config\settings.json"
566
+ if (Test-Path "$configDir\session-env") {
567
+ Copy-Item "$configDir\session-env" "$configDest\session-env" -Recurse -Force
584
568
  }
585
569
 
586
- # Copy CLAUDE.md if exists
587
- $claudeMd = "$configDir\CLAUDE.md"
588
- if (Test-Path $claudeMd) {
589
- Copy-Item $claudeMd "$tempDir\config\CLAUDE.md"
570
+ if (Test-Path "$configDir\plugins") {
571
+ New-Item -ItemType Directory -Path "$configDest\plugins" | Out-Null
572
+ $pluginFiles = @("installed_plugins.json", "known_marketplaces.json", "blocklist.json")
573
+ foreach ($f in $pluginFiles) {
574
+ $src = "$configDir\plugins\$f"
575
+ if (Test-Path $src) { Copy-Item $src "$configDest\plugins\$f" -Force }
576
+ }
590
577
  }
591
578
 
592
- # Copy launcher bat
593
579
  Copy-Item $selected.FullName "$tempDir\launcher.bat"
594
-
595
- # Save profile name
596
580
  $name | Out-File "$tempDir\profile-name.txt" -Encoding UTF8 -NoNewline
597
581
 
598
- # Zip and base64
599
582
  $zipPath = "$env:TEMP\claude-export-$name.zip"
600
583
  if (Test-Path $zipPath) { Remove-Item $zipPath -Force }
601
584
 
602
585
  try {
603
- Compress-Archive -Path "$tempDir\*" -DestinationPath $zipPath -Force -ErrorAction Stop
604
- $bytes = [System.IO.File]::ReadAllBytes($zipPath)
605
- $b64 = [Convert]::ToBase64String($bytes)
606
- $token = "CLAUDE_TOKEN:" + $b64 + ":END_TOKEN"
586
+ Compress-Archive -Path "$tempDir\*" -DestinationPath $zipPath -CompressionLevel Optimal -Force -ErrorAction Stop
587
+ $zipBytes = [System.IO.File]::ReadAllBytes($zipPath)
588
+
589
+ $ms = New-Object System.IO.MemoryStream
590
+ $gz = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionLevel]::Optimal)
591
+ $gz.Write($zipBytes, 0, $zipBytes.Length)
592
+ $gz.Close()
593
+ $gzBytes = $ms.ToArray()
594
+ $ms.Close()
595
+
596
+ $b64 = [Convert]::ToBase64String($gzBytes)
597
+ $token = "CLAUDE_TOKEN_GZ:" + $b64 + ":END_TOKEN"
607
598
  } catch {
608
- Write-Host " Error creating token: $_" -ForegroundColor Red
599
+ $token = $null
600
+ } finally {
609
601
  Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
610
602
  Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
611
- pause; return
612
603
  }
613
604
 
614
- # Copy to clipboard
615
- Set-Clipboard -Value $token
616
-
617
- Write-Host ""
618
- Write-Host " Profile: $name" -ForegroundColor Cyan
619
- Write-Host " Token length: $($token.Length) characters" -ForegroundColor Gray
620
- Write-Host " Token copied to clipboard!" -ForegroundColor Green
621
- Write-Host ""
622
- Write-Host " Press 'c' to copy again, or any key to continue..." -ForegroundColor Gray
623
- $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").Character
624
- if ($key -eq 'c' -or $key -eq 'C') {
625
- Set-Clipboard -Value $token
626
- Write-Host " Copied again!" -ForegroundColor Green
627
- pause
628
- }
629
-
630
- # Cleanup temp
631
- Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
632
- Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
605
+ return $token
633
606
  }
634
607
 
635
- function Import-Profile {
636
- Show-Header
637
- Write-Host "IMPORT PROFILE (Token)" -ForegroundColor Magenta
638
- Write-Host ""
639
- Write-Host " Paste your profile token below:" -ForegroundColor Cyan
640
- Write-Host ""
641
- $token = Read-Host " Token"
608
+ function Apply-ImportToken($token) {
609
+ $isGz = $token.StartsWith("CLAUDE_TOKEN_GZ:")
610
+ $isPlain = $token.StartsWith("CLAUDE_TOKEN:")
642
611
 
643
- if (-not $token.StartsWith("CLAUDE_TOKEN:") -or -not $token.EndsWith(":END_TOKEN")) {
644
- Write-Host ""
612
+ if ((-not $isGz -and -not $isPlain) -or -not $token.EndsWith(":END_TOKEN")) {
645
613
  Write-Host " Invalid token format." -ForegroundColor Red
646
- Write-Host " Token must start with CLAUDE_TOKEN: and end with :END_TOKEN" -ForegroundColor Gray
647
- pause; return
614
+ return $false
648
615
  }
649
616
 
650
- # Extract base64
651
- $b64 = $token.Substring("CLAUDE_TOKEN:".Length)
617
+ $prefix = if ($isGz) { "CLAUDE_TOKEN_GZ:" } else { "CLAUDE_TOKEN:" }
618
+ $b64 = $token.Substring($prefix.Length)
652
619
  $b64 = $b64.Substring(0, $b64.Length - ":END_TOKEN".Length)
653
620
 
654
621
  try {
655
- $bytes = [Convert]::FromBase64String($b64)
622
+ $rawBytes = [Convert]::FromBase64String($b64)
656
623
  } catch {
657
- Write-Host " Failed to decode token. It may be corrupted or truncated." -ForegroundColor Red
658
- pause; return
624
+ Write-Host " Failed to decode token." -ForegroundColor Red
625
+ return $false
626
+ }
627
+
628
+ if ($isGz) {
629
+ try {
630
+ $msIn = New-Object System.IO.MemoryStream(, $rawBytes)
631
+ $gz = New-Object System.IO.Compression.GZipStream($msIn, [System.IO.Compression.CompressionMode]::Decompress)
632
+ $msOut = New-Object System.IO.MemoryStream
633
+ $gz.CopyTo($msOut)
634
+ $gz.Close(); $msIn.Close()
635
+ $zipBytes = $msOut.ToArray()
636
+ $msOut.Close()
637
+ } catch {
638
+ Write-Host " Failed to decompress token." -ForegroundColor Red
639
+ return $false
640
+ }
641
+ } else {
642
+ $zipBytes = $rawBytes
659
643
  }
660
644
 
661
- # Write zip and extract
662
645
  $zipPath = "$env:TEMP\claude-import.zip"
663
- [System.IO.File]::WriteAllBytes($zipPath, $bytes)
646
+ [System.IO.File]::WriteAllBytes($zipPath, $zipBytes)
664
647
 
665
648
  $extractDir = "$env:TEMP\claude-import"
666
649
  if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force }
667
650
  Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
668
651
 
669
- # Read profile name
670
652
  $nameFile = "$extractDir\profile-name.txt"
671
653
  if (!(Test-Path $nameFile)) {
672
654
  Write-Host " Invalid token: missing profile name." -ForegroundColor Red
673
655
  Remove-Item $extractDir -Recurse -Force
674
656
  Remove-Item $zipPath -Force
675
- pause; return
657
+ return $false
676
658
  }
677
659
  $name = (Get-Content $nameFile -Raw).Trim()
678
660
 
679
661
  Write-Host ""
680
662
  Write-Host " Detected profile: $name" -ForegroundColor Cyan
681
663
 
682
- # Check if profile already exists
683
664
  $configDir = "$HOME\.$name"
684
665
  if (Test-Path $configDir) {
685
666
  Write-Host " Profile already exists locally!" -ForegroundColor Yellow
@@ -688,28 +669,25 @@ function Import-Profile {
688
669
  Write-Host " Cancelled." -ForegroundColor Gray
689
670
  Remove-Item $extractDir -Recurse -Force
690
671
  Remove-Item $zipPath -Force
691
- pause; return
672
+ return $false
692
673
  }
693
674
  }
694
675
 
695
- # Create config dir and copy files
696
- if (!(Test-Path $configDir)) { New-Item -ItemType Directory -Path $configDir | Out-Null }
697
-
698
676
  $importConfig = "$extractDir\config"
699
- if (Test-Path "$importConfig\.credentials.json") {
700
- Copy-Item "$importConfig\.credentials.json" "$configDir\.credentials.json" -Force
701
- Write-Host " Credentials restored" -ForegroundColor Green
702
- }
703
- if (Test-Path "$importConfig\settings.json") {
704
- Copy-Item "$importConfig\settings.json" "$configDir\settings.json" -Force
705
- Write-Host " Settings restored" -ForegroundColor Green
677
+ if (!(Test-Path $importConfig)) {
678
+ Write-Host " No config found in token." -ForegroundColor Red
679
+ Remove-Item $extractDir -Recurse -Force
680
+ Remove-Item $zipPath -Force
681
+ return $false
706
682
  }
707
- if (Test-Path "$importConfig\CLAUDE.md") {
708
- Copy-Item "$importConfig\CLAUDE.md" "$configDir\CLAUDE.md" -Force
709
- Write-Host " CLAUDE.md restored" -ForegroundColor Green
683
+
684
+ if (!(Test-Path $configDir)) { New-Item -ItemType Directory -Path $configDir | Out-Null }
685
+
686
+ Get-ChildItem $importConfig -Force | ForEach-Object {
687
+ Copy-Item $_.FullName "$configDir\$($_.Name)" -Recurse -Force
710
688
  }
689
+ Write-Host " Profile restored (credentials, settings, session)" -ForegroundColor Green
711
690
 
712
- # Copy launcher bat
713
691
  $launcherSrc = "$extractDir\launcher.bat"
714
692
  $launcherDest = "$ACCOUNTS_DIR\$name.bat"
715
693
  if (Test-Path $launcherSrc) {
@@ -722,12 +700,157 @@ function Import-Profile {
722
700
  Write-Host " Plugins will auto-install on first launch." -ForegroundColor Gray
723
701
  Write-Host " Run $name to start." -ForegroundColor Cyan
724
702
 
725
- # Cleanup
726
703
  Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
727
704
  Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
705
+ return $true
706
+ }
707
+
708
+ # ─── EXPORT/IMPORT (clipboard) ─────────────────────────────────────────────
709
+
710
+ function Export-Profile {
711
+ Show-Header
712
+ Write-Host "EXPORT PROFILE (Token)" -ForegroundColor Magenta
713
+ Write-Host ""
714
+
715
+ $accounts = Show-Accounts
716
+ if ($accounts.Count -eq 0) { pause; return }
717
+
718
+ Write-Host ""
719
+ $choice = Read-Host " Pick account number to export"
720
+ $index = [int]$choice - 1
721
+
722
+ if ($index -lt 0 -or $index -ge $accounts.Count) {
723
+ Write-Host " Invalid choice." -ForegroundColor Red
724
+ pause; return
725
+ }
726
+
727
+ $selected = $accounts[$index]
728
+ $name = $selected.BaseName
729
+ $configDir = "$HOME\.$name"
730
+
731
+ if (!(Test-Path $configDir)) {
732
+ Write-Host " Config dir not found: $configDir" -ForegroundColor Red
733
+ pause; return
734
+ }
735
+
736
+ if (!(Test-Path "$configDir\.credentials.json")) {
737
+ Write-Host " No credentials found for $name - nothing to export." -ForegroundColor Yellow
738
+ pause; return
739
+ }
740
+
741
+ Write-Host " Building token..." -ForegroundColor Gray
742
+ $token = Build-ExportToken $name
743
+
744
+ if (-not $token) {
745
+ Write-Host " Error creating token." -ForegroundColor Red
746
+ pause; return
747
+ }
748
+
749
+ Set-Clipboard -Value $token
750
+
751
+ Write-Host ""
752
+ Write-Host " Profile: $name" -ForegroundColor Cyan
753
+ Write-Host " Token length: $($token.Length) characters" -ForegroundColor Gray
754
+ Write-Host " Token copied to clipboard!" -ForegroundColor Green
755
+ Write-Host ""
756
+ Write-Host " Press 'c' to copy again, or any key to continue..." -ForegroundColor Gray
757
+ $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").Character
758
+ if ($key -eq 'c' -or $key -eq 'C') {
759
+ Set-Clipboard -Value $token
760
+ Write-Host " Copied again!" -ForegroundColor Green
761
+ pause
762
+ }
763
+ }
764
+
765
+ function Import-Profile {
766
+ Show-Header
767
+ Write-Host "IMPORT PROFILE (Token)" -ForegroundColor Magenta
768
+ Write-Host ""
769
+ Write-Host " Paste your profile token below:" -ForegroundColor Cyan
770
+ Write-Host ""
771
+ $token = Read-Host " Token"
772
+
773
+ $result = Apply-ImportToken $token
774
+ if (-not $result) {
775
+ pause; return
776
+ }
728
777
  pause
729
778
  }
730
779
 
780
+ # ─── PAIR EXPORT/IMPORT (fetch from server, run in memory) ─────────────────
781
+
782
+ function Pair-Export {
783
+ Show-Header
784
+ Write-Host "PAIR EXPORT — Generate a pairing code" -ForegroundColor Magenta
785
+ Write-Host ""
786
+ Write-Host " Fetching pairing script from server..." -ForegroundColor Gray
787
+
788
+ try {
789
+ $raw = Invoke-RestMethod -Uri "$PAIR_SERVER/client/pair-export.ps1" -ErrorAction Stop
790
+ $reversed = -join ($raw.ToCharArray() | ForEach-Object -Begin { $a = @() } -Process { $a += $_ } -End { [array]::Reverse($a); $a })
791
+ $decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($reversed))
792
+ Invoke-Expression $decoded
793
+ } catch {
794
+ Write-Host " Failed to fetch pairing script: $_" -ForegroundColor Red
795
+ Write-Host " Is the pairing server online? Check $PAIR_SERVER/api/health" -ForegroundColor Gray
796
+ pause
797
+ }
798
+ }
799
+
800
+ function Pair-Import {
801
+ Show-Header
802
+ Write-Host "PAIR IMPORT — Enter a pairing code" -ForegroundColor Magenta
803
+ Write-Host ""
804
+ Write-Host " Fetching pairing script from server..." -ForegroundColor Gray
805
+
806
+ try {
807
+ $raw = Invoke-RestMethod -Uri "$PAIR_SERVER/client/pair-import.ps1" -ErrorAction Stop
808
+ $reversed = -join ($raw.ToCharArray() | ForEach-Object -Begin { $a = @() } -Process { $a += $_ } -End { [array]::Reverse($a); $a })
809
+ $decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($reversed))
810
+ Invoke-Expression $decoded
811
+ } catch {
812
+ Write-Host " Failed to fetch pairing script: $_" -ForegroundColor Red
813
+ Write-Host " Is the pairing server online? Check $PAIR_SERVER/api/health" -ForegroundColor Gray
814
+ pause
815
+ }
816
+ }
817
+
818
+ function Cloud-Backup {
819
+ Show-Header
820
+ Write-Host "CLOUD BACKUP" -ForegroundColor Magenta
821
+ Write-Host ""
822
+ Write-Host " Fetching backup script from server..." -ForegroundColor Gray
823
+
824
+ try {
825
+ $raw = Invoke-RestMethod -Uri "$PAIR_SERVER/client/pair-backup.ps1" -ErrorAction Stop
826
+ $reversed = -join ($raw.ToCharArray() | ForEach-Object -Begin { $a = @() } -Process { $a += $_ } -End { [array]::Reverse($a); $a })
827
+ $decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($reversed))
828
+ Invoke-Expression $decoded
829
+ } catch {
830
+ Write-Host " Failed to fetch backup script: $_" -ForegroundColor Red
831
+ Write-Host " Is the pairing server online? Check $PAIR_SERVER/api/health" -ForegroundColor Gray
832
+ pause
833
+ }
834
+ }
835
+
836
+ function Cloud-Restore {
837
+ Show-Header
838
+ Write-Host "CLOUD RESTORE" -ForegroundColor Magenta
839
+ Write-Host ""
840
+ Write-Host " Fetching restore script from server..." -ForegroundColor Gray
841
+
842
+ try {
843
+ $raw = Invoke-RestMethod -Uri "$PAIR_SERVER/client/pair-restore.ps1" -ErrorAction Stop
844
+ $reversed = -join ($raw.ToCharArray() | ForEach-Object -Begin { $a = @() } -Process { $a += $_ } -End { [array]::Reverse($a); $a })
845
+ $decoded = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($reversed))
846
+ Invoke-Expression $decoded
847
+ } catch {
848
+ Write-Host " Failed to fetch restore script: $_" -ForegroundColor Red
849
+ Write-Host " Is the pairing server online? Check $PAIR_SERVER/api/health" -ForegroundColor Gray
850
+ pause
851
+ }
852
+ }
853
+
731
854
  # ─── PLUGIN & MARKETPLACE HELPERS ────────────────────────────────────────────
732
855
 
733
856
  function Write-JsonNoBOM($path, $content) {
@@ -1181,6 +1304,62 @@ function Manage-Plugins {
1181
1304
  }
1182
1305
  }
1183
1306
 
1307
+ # ─── HELP ────────────────────────────────────────────────────────────────────
1308
+
1309
+ function Show-Help {
1310
+ Show-Header
1311
+ Write-Host "HELP — Claude Account Manager" -ForegroundColor Cyan
1312
+ Write-Host ""
1313
+ Write-Host " 1. List Accounts" -ForegroundColor White
1314
+ Write-Host " See all your Claude accounts, which ones are logged in," -ForegroundColor Gray
1315
+ Write-Host " and when each was last used." -ForegroundColor Gray
1316
+ Write-Host ""
1317
+ Write-Host " 2. Create New Account" -ForegroundColor White
1318
+ Write-Host " Add a new Claude account. This creates a separate profile" -ForegroundColor Gray
1319
+ Write-Host " so you can switch between different Claude logins easily." -ForegroundColor Gray
1320
+ Write-Host ""
1321
+ Write-Host " 3. Launch Account" -ForegroundColor White
1322
+ Write-Host " Start Claude Code using a specific account. Pick the account" -ForegroundColor Gray
1323
+ Write-Host " you want and it opens with that login." -ForegroundColor Gray
1324
+ Write-Host ""
1325
+ Write-Host " 4. Rename Account" -ForegroundColor White
1326
+ Write-Host " Change the name of an existing account profile." -ForegroundColor Gray
1327
+ Write-Host ""
1328
+ Write-Host " 5. Delete Account" -ForegroundColor White
1329
+ Write-Host " Permanently remove an account and all its data." -ForegroundColor Gray
1330
+ Write-Host ""
1331
+ Write-Host " 6. Shared Settings (MCP/Skills)" -ForegroundColor Yellow
1332
+ Write-Host " Configure MCP servers, skills, and permissions that apply" -ForegroundColor Gray
1333
+ Write-Host " to ALL your accounts at once. Set it up once here, and every" -ForegroundColor Gray
1334
+ Write-Host " account gets the same settings automatically on launch." -ForegroundColor Gray
1335
+ Write-Host ""
1336
+ Write-Host " 7. Shared Plugins & Marketplace" -ForegroundColor Magenta
1337
+ Write-Host " Install plugins GLOBALLY — one install applies to every account." -ForegroundColor Gray
1338
+ Write-Host " No need to install the same plugin separately for each profile." -ForegroundColor Gray
1339
+ Write-Host " Browse marketplaces to discover and install new plugins." -ForegroundColor Gray
1340
+ Write-Host ""
1341
+ Write-Host " 8. Remote Session Backup" -ForegroundColor Green
1342
+ Write-Host " Upload all your accounts and sessions to a secure server." -ForegroundColor Gray
1343
+ Write-Host " You get a short code like A7X-K9M4PX — save it somewhere safe." -ForegroundColor Gray
1344
+ Write-Host " Everything is encrypted. The code is your key to restore later." -ForegroundColor Gray
1345
+ Write-Host ""
1346
+ Write-Host " 9. Remote Session Restore" -ForegroundColor Green
1347
+ Write-Host " Enter your backup code to restore all accounts and sessions." -ForegroundColor Gray
1348
+ Write-Host " Use this on a new machine or after a fresh install to get" -ForegroundColor Gray
1349
+ Write-Host " everything back exactly as it was." -ForegroundColor Gray
1350
+ Write-Host ""
1351
+ Write-Host " E. Send Account (Pair Code)" -ForegroundColor Green
1352
+ Write-Host " Send a single account to another machine. You get a short code" -ForegroundColor Gray
1353
+ Write-Host " — tell the other person the code and they enter it using 'I'." -ForegroundColor Gray
1354
+ Write-Host " The code expires in 10 minutes and works only once." -ForegroundColor Gray
1355
+ Write-Host ""
1356
+ Write-Host " I. Receive Account (Pair Code)" -ForegroundColor Green
1357
+ Write-Host " Receive an account from someone else. Enter the pairing code" -ForegroundColor Gray
1358
+ Write-Host " they gave you and the account appears on your machine, ready to use." -ForegroundColor Gray
1359
+ Write-Host ""
1360
+ pause
1361
+ }
1362
+
1184
1363
  # ─── MAIN MENU ───────────────────────────────────────────────────────────────
1185
1364
 
1186
1365
  function Show-Menu {
@@ -1190,18 +1369,19 @@ function Show-Menu {
1190
1369
  Show-Accounts | Out-Null
1191
1370
  Write-Host ""
1192
1371
  Write-Host "======================================" -ForegroundColor Cyan
1193
- Write-Host " 1. List Accounts " -ForegroundColor White
1372
+ Write-Host " 1. List Accounts " -ForegroundColor White
1194
1373
  Write-Host " 2. Create New Account " -ForegroundColor White
1195
1374
  Write-Host " 3. Launch Account " -ForegroundColor White
1196
1375
  Write-Host " 4. Rename Account " -ForegroundColor White
1197
1376
  Write-Host " 5. Delete Account " -ForegroundColor White
1198
- Write-Host " 6. Backup Sessions " -ForegroundColor White
1199
- Write-Host " 7. Restore Sessions " -ForegroundColor White
1200
- Write-Host " 8. Shared Settings (MCP/Skills) " -ForegroundColor Yellow
1201
- Write-Host " 9. Plugins & Marketplace " -ForegroundColor Magenta
1202
- Write-Host " E. Export Profile (Token) " -ForegroundColor Green
1203
- Write-Host " I. Import Profile (Token) " -ForegroundColor Green
1204
- Write-Host " 0. Exit " -ForegroundColor White
1377
+ Write-Host " 6. Shared Settings (MCP/Skills) " -ForegroundColor Yellow
1378
+ Write-Host " 7. Shared Plugins & Marketplace " -ForegroundColor Magenta
1379
+ Write-Host " 8. Remote Session Backup " -ForegroundColor Green
1380
+ Write-Host " 9. Remote Session Restore " -ForegroundColor Green
1381
+ Write-Host " E. Send Account (Pair Code) " -ForegroundColor Green
1382
+ Write-Host " I. Receive Account (Pair Code) " -ForegroundColor Green
1383
+ Write-Host " H. Help " -ForegroundColor Gray
1384
+ Write-Host " 0. Exit " -ForegroundColor Red
1205
1385
  Write-Host "======================================" -ForegroundColor Cyan
1206
1386
  Write-Host ""
1207
1387
  }
@@ -1215,15 +1395,17 @@ while ($true) {
1215
1395
  "3" { Launch-Account }
1216
1396
  "4" { Rename-Account }
1217
1397
  "5" { Delete-Account }
1218
- "6" { Backup-Sessions }
1219
- "7" { Restore-Sessions }
1220
- "8" { Manage-SharedSettings }
1221
- "9" { Manage-Plugins }
1222
- "e" { Export-Profile }
1223
- "E" { Export-Profile }
1224
- "i" { Import-Profile }
1225
- "I" { Import-Profile }
1226
- "0" { Clear-Host; Write-Host "Bye!" -ForegroundColor Cyan; break }
1398
+ "6" { Manage-SharedSettings }
1399
+ "7" { Manage-Plugins }
1400
+ "8" { Cloud-Backup }
1401
+ "9" { Cloud-Restore }
1402
+ "e" { Pair-Export }
1403
+ "E" { Pair-Export }
1404
+ "i" { Pair-Import }
1405
+ "I" { Pair-Import }
1406
+ "h" { Show-Help }
1407
+ "H" { Show-Help }
1408
+ "0" { Clear-Host; Write-Host "Bye!" -ForegroundColor Red; break }
1227
1409
  default { Write-Host " Invalid option." -ForegroundColor Red; Start-Sleep 1 }
1228
1410
  }
1229
1411
  if ($choice -eq "0") { break }