@synth1s/cloak 1.1.3 → 1.3.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/README.md CHANGED
@@ -18,8 +18,6 @@ Cloak gives each account its own isolated directory using Claude Code's official
18
18
  npm install -g @synth1s/cloak
19
19
  ```
20
20
 
21
- That's it. No setup required. All commands work immediately.
22
-
23
21
  ## Quick start
24
22
 
25
23
  ```bash
@@ -29,9 +27,12 @@ cloak create work
29
27
  # Log out, log in with another account, then:
30
28
  cloak create home
31
29
 
30
+ # Set up shell integration (recommended)
31
+ echo 'eval "$(cloak init)"' >> ~/.bashrc && source ~/.bashrc
32
+
32
33
  # Throw on a cloak and go
33
- cloak launch work
34
- cloak launch home
34
+ claude -a work
35
+ claude -a home
35
36
  ```
36
37
 
37
38
  ## Commands
@@ -39,45 +40,45 @@ cloak launch home
39
40
  | Command | Description |
40
41
  |---------|-------------|
41
42
  | `cloak create [name]` | Save current session as a new cloak |
42
- | `cloak launch <name> [args...]` | Throw on a cloak and launch Claude |
43
+ | `cloak switch <name>` | Set `CLAUDE_CONFIG_DIR` for the current shell |
43
44
  | `cloak list` | See all cloaks in your wardrobe |
44
45
  | `cloak whoami` | Which cloak are you wearing? |
45
46
  | `cloak delete <name>` | Discard a cloak |
46
47
  | `cloak rename <old> <new>` | Rename a cloak |
47
- | `cloak switch <name>` | Set `CLAUDE_CONFIG_DIR` without launching |
48
- | `cloak init` | Output shell integration code (optional) |
48
+ | `cloak init` | Output shell integration code |
49
49
 
50
- ## Concurrent sessions
50
+ ## Shell integration (recommended)
51
51
 
52
- Different terminal, different cloak. No conflicts.
52
+ Add to your `.bashrc` or `.zshrc`:
53
53
 
54
54
  ```bash
55
- # Terminal A — wearing the work cloak:
56
- cloak launch work
57
-
58
- # Terminal B — wearing the home cloak:
59
- cloak launch home
55
+ eval "$(cloak init)"
60
56
  ```
61
57
 
62
- ## Shell integration (optional)
58
+ This enables:
59
+
60
+ | Command | Description |
61
+ |---------|-------------|
62
+ | `claude -a <name>` | Throw on a cloak and launch Claude |
63
+ | `claude -a <name> [args...]` | Throw on a cloak and launch with arguments |
64
+ | `claude account create [name]` | Save current session as a new cloak |
65
+ | `claude account switch <name>` | Wear a different cloak |
66
+ | `claude account list` | See all cloaks in your wardrobe |
67
+ | `claude account whoami` | Which cloak are you wearing? |
68
+ | `claude account delete <name>` | Discard a cloak |
69
+ | `claude account rename <old> <new>` | Rename a cloak |
70
+
71
+ ## Concurrent sessions
63
72
 
64
- Want `claude -a work` and `claude account` syntax? Add to your `.bashrc` or `.zshrc`:
73
+ Different terminal, different cloak. No conflicts.
65
74
 
66
75
  ```bash
67
- eval "$(cloak init)"
68
- ```
69
-
70
- This enables:
76
+ # Terminal A — wearing the work cloak:
77
+ claude -a work
71
78
 
72
- | Command | Routes to |
73
- |---------|-----------|
74
- | `claude -a <name>` | `cloak launch <name>` |
75
- | `claude account create [name]` | `cloak create` |
76
- | `claude account list` | `cloak list` |
77
- | `claude account whoami` | `cloak whoami` |
78
- | `claude account switch <name>` | `cloak switch` (sets env in current shell) |
79
- | `claude account delete <name>` | `cloak delete` |
80
- | `claude account rename <old> <new>` | `cloak rename` |
79
+ # Terminal B wearing the home cloak:
80
+ claude -a home
81
+ ```
81
82
 
82
83
  ## How it works
83
84
 
@@ -95,11 +96,12 @@ Each cloak is an isolated directory that acts as a [`CLAUDE_CONFIG_DIR`](https:/
95
96
  └── ...
96
97
  ```
97
98
 
98
- When you run `cloak launch work`, Cloak sets `CLAUDE_CONFIG_DIR=~/.cloak/profiles/work` and spawns Claude Code. Each terminal gets its own environment, so you can wear different cloaks simultaneously.
99
+ When you run `claude -a work`, Cloak sets `CLAUDE_CONFIG_DIR=~/.cloak/profiles/work` in your current shell and launches Claude Code. Each terminal gets its own environment, so you can wear different cloaks simultaneously.
99
100
 
100
101
  ## Requirements
101
102
 
102
103
  - Node.js >= 18
104
+ - bash or zsh (for shell integration)
103
105
 
104
106
  ## Documentation
105
107
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synth1s/cloak",
3
- "version": "1.1.3",
3
+ "version": "1.3.0",
4
4
  "description": "Cloak your Claude. Switch identities in seconds.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -12,7 +12,6 @@ import { listAccounts } from './commands/list.js'
12
12
  import { deleteAccount } from './commands/delete.js'
13
13
  import { whoami } from './commands/whoami.js'
14
14
  import { renameAccount } from './commands/rename.js'
15
- import { launchAccount } from './commands/launch.js'
16
15
  import { initShell } from './commands/init.js'
17
16
 
18
17
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -59,12 +58,6 @@ program
59
58
  .description('Rename a cloak')
60
59
  .action(renameAccount)
61
60
 
62
- program
63
- .command('launch <name>')
64
- .description('Wear a cloak and launch Claude')
65
- .argument('[args...]', 'Arguments to pass to claude')
66
- .action((name, args) => launchAccount(name, args))
67
-
68
61
  program
69
62
  .command('init')
70
63
  .description('Output shell integration code')
@@ -14,16 +14,6 @@ export function getInitScript() {
14
14
  ' if [ $exit_code -eq 0 ]; then',
15
15
  ' eval "$output"',
16
16
  ' fi',
17
- ' elif [ "$subcmd" = "launch" ]; then',
18
- ' local name="$1"',
19
- ' shift',
20
- ' local output',
21
- ' output=$(command cloak switch --print-env "$name")',
22
- ' local exit_code=$?',
23
- ' if [ $exit_code -eq 0 ]; then',
24
- ' eval "$output"',
25
- ' command claude "$@"',
26
- ' fi',
27
17
  ' else',
28
18
  ' command cloak "$subcmd" "$@"',
29
19
  ' fi',
@@ -1,6 +1,8 @@
1
1
  import chalk from 'chalk'
2
+ import inquirer from 'inquirer'
2
3
  import { profileDir, profileExists, getActiveProfile } from '../lib/paths.js'
3
4
  import { validateAccountName } from '../lib/validate.js'
5
+ import { getRcFilePath, isAlreadyInstalled, installToRcFile } from '../lib/setup.js'
4
6
 
5
7
  export async function switchAccount(name, options = {}) {
6
8
  const validation = validateAccountName(name)
@@ -25,13 +27,46 @@ export async function switchAccount(name, options = {}) {
25
27
 
26
28
  const dir = profileDir(name)
27
29
 
30
+ // Shell integration is active — output for eval
28
31
  if (options.printEnv) {
29
32
  process.stdout.write(`export CLAUDE_CONFIG_DIR=${dir}\n`)
30
33
  process.stdout.write(`echo "${chalk.green(`✔ Now wearing cloak "${name}".`)}"\n`)
31
34
  return
32
35
  }
33
36
 
34
- // Manual instructions (no shell integration)
35
- console.log(chalk.dim('Run this command to switch:'))
36
- console.log(`\n export CLAUDE_CONFIG_DIR=${dir}\n`)
37
+ // No shell integration prompt user to set it up
38
+ console.log(chalk.yellow('\n⚠ Shell integration is required to switch accounts.\n'))
39
+
40
+ let choice = options.setupChoice
41
+ if (choice === undefined) {
42
+ const answer = await inquirer.prompt([{
43
+ type: 'list',
44
+ name: 'choice',
45
+ message: 'How would you like to proceed?',
46
+ choices: [
47
+ { name: 'Set it up now (recommended)', value: 'auto' },
48
+ { name: 'Show manual instructions', value: 'manual' },
49
+ ],
50
+ }])
51
+ choice = answer.choice
52
+ }
53
+
54
+ if (choice === 'auto') {
55
+ const rcFile = getRcFilePath()
56
+ if (!isAlreadyInstalled(rcFile)) {
57
+ installToRcFile(rcFile)
58
+ console.log(chalk.green(`✔ Shell integration added to ${rcFile}`))
59
+ } else {
60
+ console.log(chalk.dim(` Already installed in ${rcFile}`))
61
+ }
62
+ console.log(chalk.dim(`\n Reload your shell to activate:`))
63
+ console.log(chalk.dim(` source ${rcFile}\n`))
64
+ console.log(chalk.dim(` Then run:`))
65
+ console.log(chalk.dim(` claude account switch ${name}\n`))
66
+ } else {
67
+ console.log(chalk.dim('\n Add this to your shell config:'))
68
+ console.log(` eval "$(cloak init)"`)
69
+ console.log(chalk.dim('\n Then reload and run:'))
70
+ console.log(chalk.dim(` claude account switch ${name}\n`))
71
+ }
37
72
  }
@@ -0,0 +1,27 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs'
2
+ import { join } from 'path'
3
+ import { homedir } from 'os'
4
+
5
+ function getHome() {
6
+ return process.env.HOME || homedir()
7
+ }
8
+
9
+ export function getRcFilePath() {
10
+ const shell = process.env.SHELL || ''
11
+ if (shell.includes('zsh')) {
12
+ return join(getHome(), '.zshrc')
13
+ }
14
+ return join(getHome(), '.bashrc')
15
+ }
16
+
17
+ export function isAlreadyInstalled(rcFilePath) {
18
+ if (!existsSync(rcFilePath)) return false
19
+ const content = readFileSync(rcFilePath, 'utf8')
20
+ return content.includes('cloak init')
21
+ }
22
+
23
+ export function installToRcFile(rcFilePath) {
24
+ if (isAlreadyInstalled(rcFilePath)) return
25
+ const line = '\neval "$(cloak init)"\n'
26
+ writeFileSync(rcFilePath, line, { flag: 'a' })
27
+ }
@@ -1,29 +0,0 @@
1
- import { spawn as defaultSpawn } from 'child_process'
2
- import chalk from 'chalk'
3
- import { profileDir, profileExists } from '../lib/paths.js'
4
- import { validateAccountName } from '../lib/validate.js'
5
-
6
- export function launchAccount(name, extraArgs = [], spawner = defaultSpawn) {
7
- const validation = validateAccountName(name)
8
- if (!validation.valid) {
9
- return Promise.reject(new Error(validation.error))
10
- }
11
-
12
- if (!profileExists(name)) {
13
- return Promise.reject(new Error(`Account "${name}" not found. Run: claude account create ${name}`))
14
- }
15
-
16
- process.env.CLAUDE_CONFIG_DIR = profileDir(name)
17
- console.log(chalk.green(`✔ Now wearing cloak "${name}".`))
18
-
19
- return new Promise((resolve, reject) => {
20
- const child = spawner('claude', extraArgs, {
21
- stdio: 'inherit',
22
- env: process.env,
23
- })
24
-
25
- child.on('close', (code) => {
26
- resolve(code)
27
- })
28
- })
29
- }