@synth1s/cloak 2.3.2 → 2.4.1

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
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/synth1s/cloak/actions/workflows/ci.yml/badge.svg)](https://github.com/synth1s/cloak/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/@synth1s/cloak)](https://www.npmjs.com/package/@synth1s/cloak)
5
+ [![downloads](https://img.shields.io/npm/dw/@synth1s/cloak)](https://www.npmjs.com/package/@synth1s/cloak)
5
6
  [![license](https://img.shields.io/npm/l/@synth1s/cloak)](LICENSE)
6
7
 
7
8
  **Stop logging out. Start switching.**
@@ -62,6 +63,8 @@ claude -a home # in another terminal, at the same time
62
63
  | `cloak whoami` | Which cloak are you wearing? |
63
64
  | `cloak delete <name>` | Discard a cloak |
64
65
  | `cloak rename <old> <new>` | Rename a cloak |
66
+ | `cloak bind <name>` | Bind this directory to a cloak |
67
+ | `cloak unbind` | Remove directory binding |
65
68
 
66
69
  With shell integration (`eval "$(cloak init)"`):
67
70
 
@@ -84,6 +87,20 @@ claude -a home
84
87
 
85
88
  Each account is a completely isolated directory. No file overlap, no token conflicts.
86
89
 
90
+ ## Auto-switch by directory
91
+
92
+ Bind a cloak to a project directory. Claude automatically uses the right account.
93
+
94
+ ```bash
95
+ cd ~/projects/company
96
+ cloak bind work
97
+
98
+ cd ~/projects/personal
99
+ cloak bind home
100
+ ```
101
+
102
+ From now on, `claude` in those directories uses the bound account — no manual switch needed.
103
+
87
104
  ## Context bar
88
105
 
89
106
  Every command shows which identity is active:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synth1s/cloak",
3
- "version": "2.3.2",
3
+ "version": "2.4.1",
4
4
  "description": "Cloak your Claude. Switch identities in seconds.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -13,6 +13,8 @@ import { listAccounts } from './commands/list.js'
13
13
  import { deleteAccount } from './commands/delete.js'
14
14
  import { whoami } from './commands/whoami.js'
15
15
  import { renameAccount } from './commands/rename.js'
16
+ import { bindAccount } from './commands/bind.js'
17
+ import { unbindAccount } from './commands/unbind.js'
16
18
  import { initShell } from './commands/init.js'
17
19
 
18
20
  const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -35,6 +37,10 @@ Quick start:
35
37
  cloak switch work Switch to a cloak
36
38
  cloak list See all your cloaks
37
39
 
40
+ Auto-switch by directory:
41
+ cloak bind work Bind current directory to a cloak
42
+ cd ~/project && claude Auto-uses the bound cloak
43
+
38
44
  Shell integration (recommended):
39
45
  eval "$(cloak init)" Add to .bashrc/.zshrc for:
40
46
  claude -a work Switch and launch Claude in one step
@@ -94,6 +100,22 @@ program
94
100
  return renameAccount(oldName, newName)
95
101
  })
96
102
 
103
+ program
104
+ .command('bind <name>')
105
+ .description('Bind this directory to a cloak')
106
+ .action((name) => {
107
+ renderContextBar('bind')
108
+ return bindAccount(name)
109
+ })
110
+
111
+ program
112
+ .command('unbind')
113
+ .description('Unbind this directory')
114
+ .action(() => {
115
+ renderContextBar('unbind')
116
+ return unbindAccount()
117
+ })
118
+
97
119
  program
98
120
  .command('context-bar', { hidden: true })
99
121
  .argument('<command>')
@@ -0,0 +1,24 @@
1
+ import { writeFileSync } from 'fs'
2
+ import { join } from 'path'
3
+ import { profileExists } from '../lib/paths.js'
4
+ import { validateAccountName } from '../lib/validate.js'
5
+ import * as msg from '../lib/messages.js'
6
+
7
+ export async function bindAccount(name, dir = process.cwd()) {
8
+ const validation = validateAccountName(name)
9
+ if (!validation.valid) {
10
+ console.error(msg.validationError(validation.error))
11
+ process.exit(1)
12
+ return
13
+ }
14
+
15
+ if (!profileExists(name)) {
16
+ console.error(msg.accountNotFound(name))
17
+ console.error(msg.suggestCreate(name))
18
+ process.exit(1)
19
+ return
20
+ }
21
+
22
+ writeFileSync(join(dir, '.cloak'), name + '\n')
23
+ console.log(msg.cloakBound(name))
24
+ }
@@ -43,6 +43,17 @@ export function getInitScript() {
43
43
  ' command claude "$@"',
44
44
  ' fi',
45
45
  ' else',
46
+ ' if [ -f ".cloak" ]; then',
47
+ ' local _bind_name',
48
+ ' _bind_name=$(cat .cloak 2>/dev/null | tr -d "[:space:]")',
49
+ ' if [ -n "$_bind_name" ]; then',
50
+ ' local _bind_output',
51
+ ' _bind_output=$(command cloak switch --print-env "$_bind_name" 2>/dev/null)',
52
+ ' if [ $? -eq 0 ]; then',
53
+ ' eval "$_bind_output"',
54
+ ' fi',
55
+ ' fi',
56
+ ' fi',
46
57
  ' if [ -n "$CLAUDE_CONFIG_DIR" ]; then',
47
58
  ' command cloak context-bar claude',
48
59
  ' fi',
@@ -0,0 +1,16 @@
1
+ import { unlinkSync, existsSync } from 'fs'
2
+ import { join } from 'path'
3
+ import * as msg from '../lib/messages.js'
4
+
5
+ export async function unbindAccount(dir = process.cwd()) {
6
+ const cloakFile = join(dir, '.cloak')
7
+
8
+ if (!existsSync(cloakFile)) {
9
+ console.error(msg.noCloakFile())
10
+ process.exit(1)
11
+ return
12
+ }
13
+
14
+ unlinkSync(cloakFile)
15
+ console.log(msg.cloakUnbound())
16
+ }
@@ -128,6 +128,20 @@ export function shellIntegrationTip(rcFile) {
128
128
  chalk.dim(` echo 'eval "$(cloak init)"' >> ${file} && source ${file}\n\n`)
129
129
  }
130
130
 
131
+ // --- Bind/unbind ---
132
+
133
+ export function cloakBound(name) {
134
+ return `${icon.success} Bound this directory to cloak ${chalk.bold(`"${name}"`)}.`
135
+ }
136
+
137
+ export function cloakUnbound() {
138
+ return `${icon.success} Unbound this directory.`
139
+ }
140
+
141
+ export function noCloakFile() {
142
+ return `${icon.error} No .cloak file in this directory.`
143
+ }
144
+
131
145
  // --- Active cloak indicator (shown on claude launch) ---
132
146
 
133
147
  export function wearingCloak(name) {