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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../src/cli').run(process.argv);
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
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ ...require('./cli'),
3
+ config: require('./config'),
4
+ };