claude-pool 0.1.0 → 0.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/README.md CHANGED
@@ -125,6 +125,7 @@ When an account reaches its 5-hour limit, `claude-pool` catches it and migrates
125
125
  |---|---|
126
126
  | `claude-pool` | Launch Claude session with active account & auto-failover |
127
127
  | `claude-pool setup` | Interactive wizard to connect all accounts from scratch |
128
+ | `claude-pool switch [account]` | Manually switch active account with zero session loss |
128
129
  | `claude-pool limits` / `remaining` | View exact % session and weekly quota remaining + reset countdowns |
129
130
  | `claude-pool dashboard` | Unified pool dashboard with today's consumption & gauges |
130
131
  | `claude-pool usage [account]` | Deep token breakdown (Input, Output, Cache Read/Create) per account |
@@ -138,6 +139,16 @@ When an account reaches its 5-hour limit, `claude-pool` catches it and migrates
138
139
 
139
140
  ---
140
141
 
142
+ ## ⚡ In-Session Slash Commands
143
+
144
+ You don't need to exit an active Claude session to check limits or switch accounts! `claude-pool setup` automatically installs custom slash commands into all configured accounts:
145
+
146
+ - **`/limits`** (or `/pool-limits`): Prints real-time remaining quota percentages and reset timers across all accounts right inside your chat transcript.
147
+ - **`/pool`**: Displays the aggregated token usage dashboard.
148
+ - **`/switch <account>`** (or `/pool-switch <account>`): Transfers your active session transcript and working directory to the target account. Type `/exit` to seamlessly relaunch on the newly switched account with 100% memory preserved!
149
+
150
+ ---
151
+
141
152
  ## 🧠 How It Works Under The Hood
142
153
 
143
154
  Claude Code stores project sessions as local JSONL transcripts:
package/bin/cli.js CHANGED
@@ -4,12 +4,14 @@ import { ConfigManager } from '../src/config.js';
4
4
  import { MetricsEngine } from '../src/metrics.js';
5
5
  import { QuotaEngine } from '../src/quota.js';
6
6
  import { ProcessRunner } from '../src/runner.js';
7
- import { runSetupWizard, checkAuthStatus, runAccountLogin } from '../src/setup.js';
7
+ import { runSetupWizard, checkAuthStatus, runAccountLogin, ensureAllSlashCommands } from '../src/setup.js';
8
8
  import { colors, formatTokenCount, makePill, makeGauge, printTable } from '../src/ui.js';
9
9
 
10
10
  const KNOWN_COMMANDS = new Set([
11
11
  'start',
12
12
  'setup',
13
+ 'switch',
14
+ 'use',
13
15
  'limits',
14
16
  'remaining',
15
17
  'quota',
@@ -33,6 +35,9 @@ const firstArg = args[0];
33
35
  const cfg = new ConfigManager();
34
36
 
35
37
  async function handleCommand() {
38
+ const accounts = cfg.loadAccounts();
39
+ ensureAllSlashCommands(accounts);
40
+
36
41
  if (!firstArg || (!firstArg.startsWith('-') && !KNOWN_COMMANDS.has(firstArg))) {
37
42
  // Default: run interactive session with failover
38
43
  const runner = new ProcessRunner(cfg);
@@ -52,6 +57,55 @@ async function handleCommand() {
52
57
  break;
53
58
  }
54
59
 
60
+ case 'switch':
61
+ case 'use': {
62
+ let targetName = args[1];
63
+ const accounts = cfg.loadAccounts();
64
+
65
+ if (!targetName) {
66
+ console.log(`\n${colors.indigo}${colors.bold}Switch Active Claude Account:${colors.reset}\n`);
67
+ accounts.forEach((a, idx) => {
68
+ const activeTag = idx === 0 ? `${colors.emerald} [ACTIVE]${colors.reset}` : '';
69
+ const emailTag = a.email ? ` (${a.email})` : '';
70
+ console.log(` ${colors.bold}${idx + 1})${colors.reset} ${a.name}${emailTag}${activeTag}`);
71
+ });
72
+ console.log();
73
+ const { default: readline } = await import('readline/promises');
74
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
75
+ const choice = await rl.question(`Select account (1-${accounts.length}): `);
76
+ rl.close();
77
+ const chosenIdx = parseInt(choice.trim(), 10) - 1;
78
+ if (chosenIdx >= 0 && chosenIdx < accounts.length) {
79
+ targetName = accounts[chosenIdx].name;
80
+ } else {
81
+ console.log(`${colors.red}Invalid selection.${colors.reset}`);
82
+ return;
83
+ }
84
+ }
85
+
86
+ const prevActive = accounts[0];
87
+ const target = accounts.find(a => a.name.toLowerCase() === targetName.toLowerCase());
88
+ if (!target) {
89
+ console.log(`${colors.red}Error: Account '${targetName}' not found in pool.${colors.reset}`);
90
+ return;
91
+ }
92
+
93
+ target.rateLimitedUntil = null;
94
+ cfg.updateAccountPriority(target.name, 1);
95
+
96
+ if (prevActive && prevActive.name.toLowerCase() !== target.name.toLowerCase()) {
97
+ const { SessionSyncEngine } = await import('../src/sessionSync.js');
98
+ const syncedId = SessionSyncEngine.transferSession(prevActive, target);
99
+ if (syncedId) {
100
+ console.log(`${colors.emerald}✓ Synchronized active session (${syncedId.slice(0, 12)}...) to ${target.name}.${colors.reset}`);
101
+ }
102
+ }
103
+
104
+ console.log(`\n${colors.emerald}✓ Active account is now: ${colors.bold}${target.name} (Priority 1)${colors.reset}`);
105
+ console.log(`${colors.dim}Run ${colors.reset}${colors.indigo}claude-pool${colors.reset}${colors.dim} to start working on ${target.name}.${colors.reset}\n`);
106
+ break;
107
+ }
108
+
55
109
  case 'limits':
56
110
  case 'remaining':
57
111
  case 'quota': {
@@ -312,6 +366,7 @@ ${colors.bold}USAGE:${colors.reset}
312
366
 
313
367
  ${colors.bold}COMMANDS:${colors.reset}
314
368
  ${colors.indigo}claude-pool${colors.reset} Launch Claude session with pooled accounts & auto-failover
369
+ ${colors.indigo}claude-pool switch [acct]${colors.reset} Manually switch active account (or interactive picker)
315
370
  ${colors.indigo}claude-pool setup${colors.reset} Guided setup wizard to connect accounts from scratch
316
371
  ${colors.indigo}claude-pool limits${colors.reset} Inspect remaining session/weekly % quota & reset timers
317
372
  ${colors.indigo}claude-pool dashboard${colors.reset} Real-time aggregated token metrics & model stats
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-pool",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Multi-account token pooling and seamless failover manager for Claude Code CLI",
5
5
  "main": "bin/cli.js",
6
6
  "type": "module",
package/src/runner.js CHANGED
@@ -4,6 +4,7 @@ import os from 'os';
4
4
  import { SessionSyncEngine } from './sessionSync.js';
5
5
  import { MetricsEngine } from './metrics.js';
6
6
  import { QuotaEngine } from './quota.js';
7
+ import { ensureAllSlashCommands } from './setup.js';
7
8
  import { renderSessionHeader, renderFailoverBanner, colors } from './ui.js';
8
9
 
9
10
  export class ProcessRunner {
@@ -59,6 +60,9 @@ export class ProcessRunner {
59
60
  return;
60
61
  }
61
62
 
63
+ // Automatically ensure /limits, /pool, /switch are installed across all accounts and project
64
+ ensureAllSlashCommands(accounts);
65
+
62
66
  let resumeSessionId = null;
63
67
  for (let i = 0; i < extraArgs.length; i++) {
64
68
  if ((extraArgs[i] === '-r' || extraArgs[i] === '--resume') && i + 1 < extraArgs.length) {
@@ -95,6 +99,18 @@ export class ProcessRunner {
95
99
  break;
96
100
  }
97
101
 
102
+ // Check if user manually switched account during the session (e.g. via /pool-switch)
103
+ const refreshedAccounts = this.configManager.loadAccounts();
104
+ const readyRefreshed = this.getReadyAccounts(refreshedAccounts);
105
+ if (readyRefreshed.length > 0 && readyRefreshed[0].name.toLowerCase() !== activeAccount.name.toLowerCase()) {
106
+ const newActive = readyRefreshed[0];
107
+ console.log(`\n${colors.emerald}✓ Account switch detected! Continuing session on ${newActive.name}...${colors.reset}\n`);
108
+ const latestId = SessionSyncEngine.getLatestSessionId(newActive);
109
+ if (latestId) resumeSessionId = latestId;
110
+ activeAccount = newActive;
111
+ continue;
112
+ }
113
+
98
114
  if (exitCode === 0) {
99
115
  console.log(`\n${colors.emerald}✓ Claude session finished normally.${colors.reset}`);
100
116
  break;
package/src/setup.js CHANGED
@@ -57,6 +57,46 @@ export function checkAuthStatus(configDir) {
57
57
  return { ok: false, email: null, plan: null };
58
58
  }
59
59
 
60
+ export function installSlashCommands(configDir) {
61
+ const cmdDir = path.join(configDir, 'commands');
62
+ try {
63
+ fs.mkdirSync(cmdDir, { recursive: true });
64
+ // /pool
65
+ fs.writeFileSync(
66
+ path.join(cmdDir, 'pool.md'),
67
+ '---\ndescription: View Claude Pool token dashboard and status\n---\nRun `npx -y claude-pool dashboard` (or `claude-pool dashboard`) using the command-line/Bash tool and display the exact formatted output.\n',
68
+ 'utf8'
69
+ );
70
+ // /limits
71
+ fs.writeFileSync(
72
+ path.join(cmdDir, 'limits.md'),
73
+ '---\ndescription: View remaining session and weekly quotas across all accounts in the pool\n---\nRun `npx -y claude-pool limits` (or `claude-pool limits`) using the command-line/Bash tool and display the exact formatted output table.\n',
74
+ 'utf8'
75
+ );
76
+ // /switch
77
+ fs.writeFileSync(
78
+ path.join(cmdDir, 'switch.md'),
79
+ '---\ndescription: Switch active Claude account in the pool\n---\nRun `npx -y claude-pool switch $ARGUMENTS` (or `claude-pool switch $ARGUMENTS`) using the command-line/Bash tool and display the result.\nIf switched successfully, inform the user that their session was synchronized and they can run `/exit` to restart immediately on the new account.\n',
80
+ 'utf8'
81
+ );
82
+ // Also include /pool-limits and /pool-switch aliases
83
+ fs.copyFileSync(path.join(cmdDir, 'limits.md'), path.join(cmdDir, 'pool-limits.md'));
84
+ fs.copyFileSync(path.join(cmdDir, 'switch.md'), path.join(cmdDir, 'pool-switch.md'));
85
+ } catch {}
86
+ }
87
+
88
+ export function ensureAllSlashCommands(accounts = []) {
89
+ try {
90
+ installSlashCommands(path.join(os.homedir(), '.claude'));
91
+ for (const a of accounts) {
92
+ if (a && a.configDir) installSlashCommands(a.configDir);
93
+ }
94
+ // Also install to current working directory .claude if it exists or in project
95
+ const localClaudeDir = path.join(process.cwd(), '.claude');
96
+ installSlashCommands(localClaudeDir);
97
+ } catch {}
98
+ }
99
+
60
100
  export async function runSetupWizard() {
61
101
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
62
102
 
@@ -159,6 +199,9 @@ export async function runSetupWizard() {
159
199
  rl.close();
160
200
 
161
201
  if (accounts.length > 0) {
202
+ for (const a of accounts) {
203
+ installSlashCommands(a.configDir);
204
+ }
162
205
  cfg.saveAccounts(accounts);
163
206
  console.log(`\n${colors.emerald}✓ Setup complete! Your Claude Pool is configured:${colors.reset}\n`);
164
207