claude-multi-account 0.1.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/LICENSE +21 -0
- package/README.md +72 -0
- package/bin/claudeswitch.js +2 -0
- package/package.json +48 -0
- package/src/cli.js +66 -0
- package/src/commands/add.js +50 -0
- package/src/commands/current.js +11 -0
- package/src/commands/list.js +20 -0
- package/src/commands/remove.js +37 -0
- package/src/commands/use.js +57 -0
- package/src/config.js +43 -0
- package/src/index.js +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ClaudeSwitch Contributors
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# claudeswitch
|
|
2
|
+
|
|
3
|
+
Save multiple [Claude Code](https://docs.claude.com/claude-code) accounts (personal, work, a friend's, etc.) and switch between them instantly — no logging out, no re-entering an OTP.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
Claude Code reads its credentials and settings from a config directory, normally `~/.claude`, or wherever the `CLAUDE_CONFIG_DIR` environment variable points. `claudeswitch` copies that directory into its own per-account folder (`~/.claudeswitch/accounts/<name>/`) and, when you switch, launches `claude` with `CLAUDE_CONFIG_DIR` pointed at the saved folder for that account.
|
|
8
|
+
|
|
9
|
+
Everything stays on your machine — `claudeswitch` never talks to a network of its own, it only copies files locally.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g claudeswitch
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Log into Claude Code normally, then snapshot that session:
|
|
21
|
+
claude login
|
|
22
|
+
claudeswitch add --name personal
|
|
23
|
+
|
|
24
|
+
# Log into a different account, save it under another name:
|
|
25
|
+
claude login
|
|
26
|
+
claudeswitch add --name work
|
|
27
|
+
|
|
28
|
+
# See what you've saved:
|
|
29
|
+
claudeswitch list
|
|
30
|
+
|
|
31
|
+
# Switch — this launches `claude` with that account's credentials:
|
|
32
|
+
claudeswitch use work
|
|
33
|
+
|
|
34
|
+
# Remove an account you no longer need:
|
|
35
|
+
claudeswitch remove work
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Commands
|
|
39
|
+
|
|
40
|
+
| Command | Description |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `claudeswitch add --name <alias>` | Save the currently logged-in Claude Code account under `<alias>`. Add `--from <dir>` to copy a specific config directory instead of the active one, or `--force` to overwrite without prompting. |
|
|
43
|
+
| `claudeswitch use <alias>` | Point `CLAUDE_CONFIG_DIR` at the saved account and launch `claude`. Anything after `use <alias>` is forwarded to `claude` as arguments. |
|
|
44
|
+
| `claudeswitch list` (alias `ls`) | List saved accounts; the active one (as last set by `use`) is marked with `*`. |
|
|
45
|
+
| `claudeswitch remove <alias>` (alias `rm`) | Delete a saved account's folder and its entry. Prompts for confirmation unless `-y`/`--yes` is passed. |
|
|
46
|
+
| `claudeswitch current` | Print the name of the account last switched to. |
|
|
47
|
+
|
|
48
|
+
### Using it in your current shell instead of a subprocess
|
|
49
|
+
|
|
50
|
+
`claudeswitch use <alias>` spawns `claude` as a child process. If you'd rather keep using `claude` directly in your current shell, print the env var instead and eval it:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# bash/zsh
|
|
54
|
+
eval "$(claudeswitch use personal --print-env)"
|
|
55
|
+
|
|
56
|
+
# PowerShell — copy the printed value manually, or wrap similarly in your profile
|
|
57
|
+
claudeswitch use personal --print-env
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Where things are stored
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
~/.claudeswitch/
|
|
64
|
+
├── accounts.json # metadata: names, paths, current account
|
|
65
|
+
└── accounts/
|
|
66
|
+
├── personal/ # full copy of that account's CLAUDE_CONFIG_DIR
|
|
67
|
+
└── work/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-multi-account",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ClaudeSwitch — switch between multiple Claude Code accounts instantly, without logging out or re-entering an OTP.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"cli",
|
|
9
|
+
"account-switcher",
|
|
10
|
+
"anthropic"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "ClaudeSwitch Contributors",
|
|
14
|
+
"homepage": "https://github.com/dhyaneshsiddhartha15/claude-switcher-v2#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/dhyaneshsiddhartha15/claude-switcher-v2.git",
|
|
18
|
+
"directory": "packages/cli"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/dhyaneshsiddhartha15/claude-switcher-v2/issues"
|
|
22
|
+
},
|
|
23
|
+
"type": "commonjs",
|
|
24
|
+
"main": "src/index.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"claudeswitch": "./bin/claudeswitch.js",
|
|
27
|
+
"cswitch": "./bin/claudeswitch.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin",
|
|
31
|
+
"src",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"start": "node bin/claudeswitch.js",
|
|
40
|
+
"test": "node --test test/"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"chalk": "^4.1.2",
|
|
44
|
+
"commander": "^12.1.0",
|
|
45
|
+
"fs-extra": "^11.2.0",
|
|
46
|
+
"prompts": "^2.4.2"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const pkg = require('../package.json');
|
|
4
|
+
|
|
5
|
+
const add = require('./commands/add');
|
|
6
|
+
const use = require('./commands/use');
|
|
7
|
+
const list = require('./commands/list');
|
|
8
|
+
const remove = require('./commands/remove');
|
|
9
|
+
const current = require('./commands/current');
|
|
10
|
+
|
|
11
|
+
function run(argv) {
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name('claudeswitch')
|
|
16
|
+
.description('Save and switch between multiple Claude Code accounts instantly.')
|
|
17
|
+
.version(pkg.version);
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command('add')
|
|
21
|
+
.description('Save the currently logged-in Claude Code account under an alias')
|
|
22
|
+
.requiredOption('--name <alias>', 'name to save this account as')
|
|
23
|
+
.option('--from <dir>', 'source config directory to copy (defaults to $CLAUDE_CONFIG_DIR or ~/.claude)')
|
|
24
|
+
.option('--force', 'overwrite an existing account with the same name without prompting')
|
|
25
|
+
.action((opts) => add(opts.name, opts));
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('use <alias>')
|
|
29
|
+
.description('Switch to a saved account and launch Claude Code with it')
|
|
30
|
+
.option('--print-env', 'print an export/set command instead of launching Claude Code')
|
|
31
|
+
.allowUnknownOption(true)
|
|
32
|
+
.action((alias, opts, command) => use(alias, { ...opts, args: command.args.slice(1) }));
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command('list')
|
|
36
|
+
.alias('ls')
|
|
37
|
+
.description('List all saved accounts')
|
|
38
|
+
.action(() => list());
|
|
39
|
+
|
|
40
|
+
program
|
|
41
|
+
.command('remove <alias>')
|
|
42
|
+
.alias('rm')
|
|
43
|
+
.description('Remove a saved account and delete its stored credentials')
|
|
44
|
+
.option('-y, --yes', 'skip the confirmation prompt')
|
|
45
|
+
.action((alias, opts) => remove(alias, opts));
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command('current')
|
|
49
|
+
.description('Print the currently active account (as last set by "use")')
|
|
50
|
+
.action(() => current());
|
|
51
|
+
|
|
52
|
+
program.exitOverride();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
program.parse(argv);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err.code && err.code.startsWith('commander.')) {
|
|
58
|
+
process.exitCode = err.exitCode ?? 1;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.error(chalk.red(err.message || err));
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { run };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const prompts = require('prompts');
|
|
4
|
+
const { accountDir, defaultClaudeConfigDir, loadState, saveState } = require('../config');
|
|
5
|
+
|
|
6
|
+
module.exports = async function add(name, opts) {
|
|
7
|
+
if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
8
|
+
console.error(chalk.red('Error: account name must be non-empty and contain only letters, numbers, "-" and "_".'));
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const sourceDir = opts.from || defaultClaudeConfigDir();
|
|
14
|
+
if (!fs.existsSync(sourceDir)) {
|
|
15
|
+
console.error(chalk.red(`Error: no Claude Code config found at ${sourceDir}.`));
|
|
16
|
+
console.error(chalk.gray('Log in with "claude" first, or pass --from <dir> to point at an existing config.'));
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const state = loadState();
|
|
22
|
+
const dest = accountDir(name);
|
|
23
|
+
|
|
24
|
+
if (fs.existsSync(dest) && !opts.force) {
|
|
25
|
+
const { overwrite } = await prompts({
|
|
26
|
+
type: 'confirm',
|
|
27
|
+
name: 'overwrite',
|
|
28
|
+
message: `Account "${name}" already exists. Overwrite it?`,
|
|
29
|
+
initial: false,
|
|
30
|
+
});
|
|
31
|
+
if (!overwrite) {
|
|
32
|
+
console.log(chalk.gray('Aborted.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fs.removeSync(dest);
|
|
38
|
+
fs.copySync(sourceDir, dest, { dereference: true });
|
|
39
|
+
|
|
40
|
+
state.accounts[name] = {
|
|
41
|
+
name,
|
|
42
|
+
path: dest,
|
|
43
|
+
createdAt: state.accounts[name]?.createdAt || new Date().toISOString(),
|
|
44
|
+
updatedAt: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
saveState(state);
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green(`Saved account "${name}" from ${sourceDir}.`));
|
|
49
|
+
console.log(chalk.gray(`Run "claudeswitch use ${name}" to switch to it.`));
|
|
50
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { loadState } = require('../config');
|
|
3
|
+
|
|
4
|
+
module.exports = function current() {
|
|
5
|
+
const state = loadState();
|
|
6
|
+
if (!state.current || !state.accounts[state.current]) {
|
|
7
|
+
console.log(chalk.gray('No account currently active (via claudeswitch).'));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
console.log(state.current);
|
|
11
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { loadState } = require('../config');
|
|
3
|
+
|
|
4
|
+
module.exports = function list() {
|
|
5
|
+
const state = loadState();
|
|
6
|
+
const names = Object.keys(state.accounts);
|
|
7
|
+
|
|
8
|
+
if (names.length === 0) {
|
|
9
|
+
console.log(chalk.gray('No accounts saved yet. Run "claudeswitch add --name <alias>" first.'));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
console.log(chalk.bold('Saved accounts:'));
|
|
14
|
+
for (const name of names.sort()) {
|
|
15
|
+
const account = state.accounts[name];
|
|
16
|
+
const marker = name === state.current ? chalk.green('* ') : ' ';
|
|
17
|
+
const label = name === state.current ? chalk.green.bold(name) : name;
|
|
18
|
+
console.log(`${marker}${label}${chalk.gray(` (${account.path})`)}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const prompts = require('prompts');
|
|
4
|
+
const { loadState, saveState } = require('../config');
|
|
5
|
+
|
|
6
|
+
module.exports = async function remove(name, opts) {
|
|
7
|
+
const state = loadState();
|
|
8
|
+
const account = state.accounts[name];
|
|
9
|
+
|
|
10
|
+
if (!account) {
|
|
11
|
+
console.error(chalk.red(`Error: no saved account named "${name}".`));
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!opts.yes) {
|
|
17
|
+
const { confirm } = await prompts({
|
|
18
|
+
type: 'confirm',
|
|
19
|
+
name: 'confirm',
|
|
20
|
+
message: `Remove account "${name}" and delete its stored credentials? This cannot be undone.`,
|
|
21
|
+
initial: false,
|
|
22
|
+
});
|
|
23
|
+
if (!confirm) {
|
|
24
|
+
console.log(chalk.gray('Aborted.'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fs.removeSync(account.path);
|
|
30
|
+
delete state.accounts[name];
|
|
31
|
+
if (state.current === name) {
|
|
32
|
+
state.current = null;
|
|
33
|
+
}
|
|
34
|
+
saveState(state);
|
|
35
|
+
|
|
36
|
+
console.log(chalk.green(`Removed account "${name}".`));
|
|
37
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const { loadState, saveState } = require('../config');
|
|
5
|
+
|
|
6
|
+
module.exports = async function use(name, opts) {
|
|
7
|
+
const state = loadState();
|
|
8
|
+
const account = state.accounts[name];
|
|
9
|
+
|
|
10
|
+
if (!account) {
|
|
11
|
+
console.error(chalk.red(`Error: no saved account named "${name}".`));
|
|
12
|
+
console.error(chalk.gray('Run "claudeswitch list" to see available accounts.'));
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(account.path)) {
|
|
18
|
+
console.error(chalk.red(`Error: account folder for "${name}" is missing (${account.path}).`));
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
state.current = name;
|
|
24
|
+
saveState(state);
|
|
25
|
+
|
|
26
|
+
if (opts.printEnv) {
|
|
27
|
+
// For shells that want to eval the env var into the current session instead
|
|
28
|
+
// of spawning a nested Claude process, e.g. eval "$(claudeswitch use hashir --print-env)".
|
|
29
|
+
const isWindows = process.platform === 'win32';
|
|
30
|
+
console.log(isWindows ? `set CLAUDE_CONFIG_DIR=${account.path}` : `export CLAUDE_CONFIG_DIR="${account.path}"`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.green(`Switching to "${name}"...`));
|
|
35
|
+
|
|
36
|
+
const env = { ...process.env, CLAUDE_CONFIG_DIR: account.path };
|
|
37
|
+
const args = opts.args || [];
|
|
38
|
+
const child = spawn('claude', args, {
|
|
39
|
+
env,
|
|
40
|
+
stdio: 'inherit',
|
|
41
|
+
shell: process.platform === 'win32',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on('error', (err) => {
|
|
45
|
+
if (err.code === 'ENOENT') {
|
|
46
|
+
console.error(chalk.red('Error: could not find the "claude" command on your PATH.'));
|
|
47
|
+
console.error(chalk.gray('Install Claude Code first: https://docs.claude.com/claude-code'));
|
|
48
|
+
} else {
|
|
49
|
+
console.error(chalk.red(`Error launching Claude Code: ${err.message}`));
|
|
50
|
+
}
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
child.on('exit', (code) => {
|
|
55
|
+
process.exitCode = code === null ? 1 : code;
|
|
56
|
+
});
|
|
57
|
+
};
|
package/src/config.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
|
|
5
|
+
const HOME_DIR = path.join(os.homedir(), '.claudeswitch');
|
|
6
|
+
const ACCOUNTS_DIR = path.join(HOME_DIR, 'accounts');
|
|
7
|
+
const STATE_FILE = path.join(HOME_DIR, 'accounts.json');
|
|
8
|
+
|
|
9
|
+
function defaultClaudeConfigDir() {
|
|
10
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function accountDir(name) {
|
|
14
|
+
return path.join(ACCOUNTS_DIR, name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function loadState() {
|
|
18
|
+
fs.ensureDirSync(HOME_DIR);
|
|
19
|
+
fs.ensureDirSync(ACCOUNTS_DIR);
|
|
20
|
+
if (!fs.existsSync(STATE_FILE)) {
|
|
21
|
+
return { current: null, accounts: {} };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return fs.readJsonSync(STATE_FILE);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
throw new Error(`Could not read ${STATE_FILE}: ${err.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function saveState(state) {
|
|
31
|
+
fs.ensureDirSync(HOME_DIR);
|
|
32
|
+
fs.writeJsonSync(STATE_FILE, state, { spaces: 2 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
HOME_DIR,
|
|
37
|
+
ACCOUNTS_DIR,
|
|
38
|
+
STATE_FILE,
|
|
39
|
+
defaultClaudeConfigDir,
|
|
40
|
+
accountDir,
|
|
41
|
+
loadState,
|
|
42
|
+
saveState,
|
|
43
|
+
};
|
package/src/index.js
ADDED