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 +11 -0
- package/bin/cli.js +56 -1
- package/package.json +1 -1
- package/src/runner.js +16 -0
- package/src/setup.js +43 -0
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
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
|
|