ai-profile-switch 0.0.0-stage → 2.2.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.ko.md +162 -0
- package/README.md +161 -2
- package/bin/cli.mjs +22 -0
- package/package.json +41 -4
- package/src/commands/add.mjs +19 -0
- package/src/commands/init.mjs +109 -0
- package/src/commands/install-shell.mjs +13 -0
- package/src/commands/list.mjs +28 -0
- package/src/commands/manage.mjs +96 -0
- package/src/commands/mcp/index.mjs +27 -0
- package/src/commands/migrate.mjs +56 -0
- package/src/commands/remove.mjs +33 -0
- package/src/commands/rename.mjs +14 -0
- package/src/commands/share.mjs +58 -0
- package/src/commands/shell.mjs +159 -0
- package/src/commands/update.mjs +50 -0
- package/src/commands/use.mjs +14 -0
- package/src/index.mjs +114 -0
- package/src/lib/config.mjs +71 -0
- package/src/lib/constants.mjs +40 -0
- package/src/lib/json-fs.mjs +30 -0
- package/src/lib/profile.mjs +395 -0
- package/src/lib/prompt.mjs +148 -0
- package/src/lib/self-update.mjs +235 -0
- package/src/lib/shared.mjs +156 -0
- package/src/lib/shell.mjs +228 -0
- package/src/lib/ui.mjs +89 -0
- package/src/lib/updater.mjs +55 -0
- package/src/shell-templates/integration.fish +32 -0
- package/src/shell-templates/integration.ps1 +78 -0
- package/src/shell-templates/integration.sh +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 claude-switch 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.ko.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# ai-profile-switch
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/ai-profile-switch)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
`CLAUDE_CONFIG_DIR`로 [Claude Code 계정](https://code.claude.com/docs/en/iam#log-in-with-multiple-accounts)을 전환합니다. 실행에 필요한 외부 패키지는 없습니다.
|
|
7
|
+
|
|
8
|
+
[English](./README.md) · [전체 가이드](https://ftery0.github.io/ai-profile-switch/ko/guide/)
|
|
9
|
+
|
|
10
|
+
예전 이름은 `claude-account-switch` 입니다. 설치된 셸은 다음 자동 업데이트 때 이 이름으로 넘어오고, `claude-account-switch` 명령도 같은 명령의 별칭으로 남습니다. [기존 사용자](#기존-사용자)를 참고하세요.
|
|
11
|
+
|
|
12
|
+
## 시작
|
|
13
|
+
|
|
14
|
+
Node.js 18.19+ (18계열) 또는 20.10+와 기존 Claude Code 설치를 사용합니다. Claude Code가 없다면 [공식 설치 가이드](https://code.claude.com/docs/en/setup)를 따릅니다.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx ai-profile-switch@latest init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
프로필 이름(예: `work`, `personal`)을 정하고 기존 설정 하나를 가져올지 선택한 뒤, 새 터미널을 엽니다. `init`은 첫 설정용이며, 다시 실행하면 현재 설정만 보여줍니다.
|
|
21
|
+
|
|
22
|
+
## 평소에는 두 명령만
|
|
23
|
+
|
|
24
|
+
`cpf`와 `claude`는 **터미널의 셸 프롬프트**(`%` 또는 `$`)에서 실행합니다. Claude 채팅창에 들어가 있다면 먼저 그곳에 `/exit`를 입력하고 터미널 프롬프트가 나올 때까지 기다립니다.
|
|
25
|
+
|
|
26
|
+
1. 터미널에서 `cpf`를 실행하면 조회·전환·추가·이름 변경·삭제·종료 메뉴가 열립니다.
|
|
27
|
+
2. ↑/↓와 Enter로 선택합니다. 작업을 마치면 Exit를 선택해 Claude 실행 없이 터미널로 돌아옵니다.
|
|
28
|
+
3. 터미널에서 `claude`를 실행하고 프로필을 고릅니다. Claude가 요청하면 로그인합니다.
|
|
29
|
+
|
|
30
|
+
바로 실행하고 싶을 때는 다음 명령을 사용합니다.
|
|
31
|
+
|
|
32
|
+
| 명령 | 하는 일 |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `cpf work` | `work`를 활성 프로필로 지정 |
|
|
35
|
+
| `cpf --list` | 전체 프로필과 현재 활성 프로필 확인 |
|
|
36
|
+
| `cpf add work2` | `work2` 프로필 추가 |
|
|
37
|
+
| `cpf rename wrok2 work2` | `wrok2`를 `work2`로 이름 변경 |
|
|
38
|
+
| `cpf remove work2` | 확인 후 `work2` 삭제 |
|
|
39
|
+
| `cpf --help` | 명령어 사용법 확인 |
|
|
40
|
+
|
|
41
|
+
예시의 이름은 본인의 프로필 이름으로 바꿉니다. 이름 하나만 쓰면 항상 해당 프로필로 전환합니다. `cpf add`, `cpf rename`, `cpf remove`, `cpf list`도 각각 그 이름의 프로필로 전환하므로, 목록 조회는 `cpf --list`를 사용합니다.
|
|
42
|
+
|
|
43
|
+
프로필이 여러 개이면 대화형 터미널의 `claude` 실행 시 현재 활성 프로필에서 시작하는 선택기가 나옵니다. 스크립트에서는 선택기 없이 활성 프로필을 사용합니다. `--help`, `--version`을 포함한 Claude 인자는 그대로 전달됩니다. 전환은 이후 실행에 적용되며, 이미 실행 중인 Claude 세션은 기존 프로필과 계정을 유지합니다.
|
|
44
|
+
|
|
45
|
+
## 계정 하나 더 추가하기
|
|
46
|
+
|
|
47
|
+
1. 터미널에서 프로필을 만듭니다.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cpf add work2
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
2. 터미널에서 Claude를 실행합니다.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. 선택기에서 `work2`를 고르고, Claude가 요청하면 사용할 계정으로 로그인합니다.
|
|
60
|
+
|
|
61
|
+
프로필을 만드는 것만으로 전환되거나 로그인되지는 않습니다. 명령을 외우지 않고 `cpf` 메뉴에서 추가해도 됩니다.
|
|
62
|
+
|
|
63
|
+
## 원래 쓰던 기본 계정 가져오기
|
|
64
|
+
|
|
65
|
+
프로필로 가져오지 않고 원래 Claude CLI를 실행하려면 Bash/zsh 터미널에서 `command claude`로 실행 함수를 우회할 수 있습니다. Claude의 기본 설정을 사용하되, `CLAUDE_CONFIG_DIR`을 따로 설정했다면 해당 경로를 사용합니다.
|
|
66
|
+
|
|
67
|
+
프로필을 만드는 것만으로 기본 `~/.claude` 설정이 자동 이관되거나 로그인되지는 않습니다. 처음 `init`을 실행할 때 해당 원본과 대상 프로필을 고를 수 있습니다. 이미 프로필이 있으면 `init`은 요약만 보여주므로 별도로 이관합니다.
|
|
68
|
+
|
|
69
|
+
1. 원본 또는 대상 프로필을 사용하는 Claude 세션마다 `/exit`를 입력해 종료하고 터미널로 돌아옵니다.
|
|
70
|
+
2. `cpf --list`로 대상 프로필을 고릅니다. 새로 필요하면 `cpf add work`로 만듭니다.
|
|
71
|
+
3. 터미널의 이 저장소 루트에서 아래 명령을 실행하고 복사를 확인합니다.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node bin/cli.mjs migrate work --from ~/.claude
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
4. 터미널에서 `claude`를 실행하고 `work`를 선택합니다. 요청하면 로그인합니다. 예시의 `work`는 고른 프로필 이름으로 바꿉니다.
|
|
78
|
+
|
|
79
|
+
원본 `~/.claude`와 `~/.claude.json`은 그대로 보존합니다. 기존 대상 데이터를 교체한 경우에도 이전 데이터는 성공 후 표시되는 백업 경로에 남깁니다. 대상 데이터 충돌이나 복사 중 대상 변경을 감지하면 이관을 중단합니다. macOS Keychain 인증은 복사하지 않으므로 설정을 가져와도 다시 로그인해야 할 수 있습니다. [자세한 이관 안내](https://ftery0.github.io/ai-profile-switch/ko/guide/commands#기존-설정-가져오기)를 참고하세요.
|
|
80
|
+
|
|
81
|
+
## 프로필 이름 잘못 썼을 때
|
|
82
|
+
|
|
83
|
+
이름을 바꿀 프로필을 사용하는 Claude 세션을 모두 종료한 뒤 실행합니다. 예를 들어 `wrok2` 오타를 고치려면:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cpf rename wrok2 work2
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
대화 기록·설정·목록 순서·활성 상태를 보존합니다. `work2`가 이미 있으면 중단합니다. 설정 경로가 바뀌므로 macOS에서는 다시 로그인해야 할 수 있습니다. 이름을 바꾼 뒤 새 Claude 세션을 실행하세요.
|
|
90
|
+
|
|
91
|
+
## 같이 쓸 것과 따로 쓸 것
|
|
92
|
+
|
|
93
|
+
기본으로는 `settings.json`과 `commands`만 모든 프로필이 함께 씁니다. 나머지는 프로필마다 따로입니다. 함께 쓰고 싶은 항목은 `cpf share add`로 추가합니다.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cpf share # 무엇을 같이 쓰는지, 프로필별로 맞는지 보기
|
|
97
|
+
cpf share add CLAUDE.md # 지금 프로필의 CLAUDE.md를 모든 프로필이 같이 씀
|
|
98
|
+
cpf share add skills # 개인 스킬을 하나씩 같이 씀
|
|
99
|
+
cpf share remove skills # 그만 같이 씀 — 각 프로필에 복사본이 남음
|
|
100
|
+
cpf share sync # _shared에 새로 넣은 항목을 지금 바로 연결
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| 같이 쓸 수 있는 것 | 늘 프로필마다 따로 |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `CLAUDE.md` `keybindings.json` `skills` `agents` `rules` `hooks` `output-styles` `workflows` `themes` | 로그인·`.claude.json`(MCP 서버·프로젝트 신뢰), 대화 기록, `projects`, 플러그인, `skills/synced`(계정에서 내려온 스킬) |
|
|
106
|
+
|
|
107
|
+
- 같이 쓰는 원본은 `~/.claude-profiles/_shared/<항목>`에 있고, 각 프로필에는 링크가 놓입니다. `_shared/CLAUDE.md`를 다른 파일로 가는 링크로 바꿔도 됩니다.
|
|
108
|
+
- 처음 추가할 때 `_shared`에 없으면 지금 프로필의 내용을 복사해 옵니다.
|
|
109
|
+
- `claude`로 실행할 때와 새 프로필을 만들 때 링크를 다시 맞춥니다. 내용이 다른 파일이 이미 있으면 지우지 않고 경고만 합니다. 옮기거나 지운 뒤 `cpf share sync`를 실행하세요.
|
|
110
|
+
- `skillOverrides`·`enabledMcpjsonServers` 같은 설정은 공유 `settings.json`에 넣으면 모든 프로필에 적용됩니다. MCP 서버 등록과 로그인은 프로필마다 `claude mcp add`·`/mcp`로 합니다.
|
|
111
|
+
|
|
112
|
+
## 기존 사용자
|
|
113
|
+
|
|
114
|
+
이 저장소의 변경을 사용하려면 **저장소 루트에서** 다음 명령을 실행하고 새 터미널을 엽니다.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
node bin/cli.mjs install-shell
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
npm 배포 버전의 셸 명령어와 로컬 실행 파일을 설치·복구·갱신하려면 아래 명령을 사용하고 새 터미널을 엽니다.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx ai-profile-switch@latest install-shell
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`install-shell`은 프로필 추가나 로그인을 하지 않습니다. 기존 셸의 `cpf`에서 새 관리 명령을 쓰려면 먼저 갱신해야 합니다. 1.x 사용자도 로컬 실행 파일을 사용하도록 `install-shell`을 한 번 실행합니다. 기존 `claude-pick`은 Claude 실행 없이 활성 프로필을 고르는 명령으로 계속 지원합니다.
|
|
127
|
+
|
|
128
|
+
셸은 `~/.claude-profiles/_runtime`을 사용하므로 일상적인 사용에 글로벌 설치나 임시 npx 캐시가 필요하지 않습니다. Node.js·npm·Claude Code는 계속 설치되어 있어야 합니다.
|
|
129
|
+
|
|
130
|
+
대화형 Claude 실행 시 하루 한 번 백그라운드에서 이 도구의 새 버전을 확인합니다. 검증된 호환 버전은 다음 실행부터 사용하며, 실행 중인 세션의 파일·계정 데이터·하네스 훅·셸 설정 파일을 덮어쓰지 않습니다. 오프라인이나 다운로드 실패 시 기존 버전으로 계속 실행합니다. 비대화형 실행에서는 자동 업데이트하지 않습니다.
|
|
131
|
+
|
|
132
|
+
`CLAUDE_SWITCH_DISABLE_AUTO_UPDATE=1`로 자동 업데이트를 끌 수 있습니다. 배포 버전으로 즉시 갱신하려면 `npx ai-profile-switch@latest update`를 실행하고, 설치 없이 확인하려면 `--check`를 붙입니다. 글로벌 npm 설치본과 셸 실행 파일은 별개이며, 자동 업데이트는 셸 실행 파일만 갱신합니다. 실행 중인 세션 보호를 위해 이전 실행 파일은 보존합니다.
|
|
133
|
+
|
|
134
|
+
## 전체 CLI 명령어
|
|
135
|
+
|
|
136
|
+
아래 명령은 저장소 루트에서 소스를 직접 실행합니다. 배포 버전을 쓸 때는 `node bin/cli.mjs`를 `npx ai-profile-switch@latest`로 바꿉니다. 글로벌 설치를 했다면 `ai-profile-switch`를 사용할 수 있습니다.
|
|
137
|
+
|
|
138
|
+
| 명령 | 용도 |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `node bin/cli.mjs manage` | `cpf`와 같은 관리 메뉴 열기 |
|
|
141
|
+
| `node bin/cli.mjs manage --list` | 관리 명령으로 프로필 조회 |
|
|
142
|
+
| `node bin/cli.mjs init` | 첫 설정 또는 기존 설정 요약 |
|
|
143
|
+
| `node bin/cli.mjs add work2` | 프로필 추가 |
|
|
144
|
+
| `node bin/cli.mjs rename wrok2 work2` | 프로필 이름 변경 |
|
|
145
|
+
| `node bin/cli.mjs remove work2` | 확인 후 프로필 삭제 |
|
|
146
|
+
| `node bin/cli.mjs list` | 프로필과 활성 상태 조회 |
|
|
147
|
+
| `node bin/cli.mjs use work` | 활성 프로필 변경 |
|
|
148
|
+
| `node bin/cli.mjs migrate work --from ~/.claude` | 기존 설정을 안전하게 복사 |
|
|
149
|
+
| `node bin/cli.mjs install-shell` | 셸 명령어·로컬 실행 파일 설치·복구·갱신 |
|
|
150
|
+
| `node bin/cli.mjs update --check` | 배포된 새 버전을 설치 없이 확인 |
|
|
151
|
+
| `node bin/cli.mjs update` | 로컬 실행 파일 갱신 |
|
|
152
|
+
| `node bin/cli.mjs share add skills` | 개인 스킬을 모든 프로필이 같이 씀 |
|
|
153
|
+
| `node bin/cli.mjs mcp list` | 과거 MCP 이름과 전환 안내 조회 |
|
|
154
|
+
| `node bin/cli.mjs rename --help` | 파일 변경 없이 해당 명령의 도움말 확인 |
|
|
155
|
+
|
|
156
|
+
이관은 원본을 보존하며 데이터가 충돌하면 멈춥니다. 스킬·에이전트·규칙·훅·`CLAUDE.md`는 선택한 프로필에 보존됩니다. macOS Keychain 인증은 복사하지 않아 새 프로필에서 로그인할 수 있습니다. [이관·호환 안내](https://ftery0.github.io/ai-profile-switch/ko/guide/commands)를 참고하세요.
|
|
157
|
+
|
|
158
|
+
2 버전부터 별도 MCP 편집기와 Claude 설치 관리를 없앴습니다. 공식 `claude mcp`와 Claude의 [업데이트 방식](https://code.claude.com/docs/en/setup#update-manually)을 사용합니다. 과거 MCP 파일은 그대로 보존하며 자동 업데이트는 이 도구만 대상으로 하며 Claude Code는 갱신하지 않습니다.
|
|
159
|
+
|
|
160
|
+
플랫폼 가이드: [macOS](docs/setup-macos.ko.md) · [Linux](docs/setup-linux.ko.md) · [Windows](docs/setup-windows.ko.md)
|
|
161
|
+
|
|
162
|
+
[MIT](./LICENSE)
|
package/README.md
CHANGED
|
@@ -1,3 +1,162 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ai-profile-switch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ai-profile-switch)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
Profile switching for [Claude Code](https://code.claude.com/docs/en/iam#log-in-with-multiple-accounts) using `CLAUDE_CONFIG_DIR`. No runtime dependencies.
|
|
7
|
+
|
|
8
|
+
[한국어](./README.ko.md) · [Full guide](https://ftery0.github.io/ai-profile-switch/guide/)
|
|
9
|
+
|
|
10
|
+
Formerly `claude-account-switch`. Installed shells move to this name on their next automatic update, and `claude-account-switch` remains an alias of the same command. See [existing users](#existing-users).
|
|
11
|
+
|
|
12
|
+
## Start
|
|
13
|
+
|
|
14
|
+
Use Node.js 18.19+ (18.x) or 20.10+ and your existing Claude Code installation. If Claude Code is missing, follow its [official installation guide](https://code.claude.com/docs/en/setup).
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx ai-profile-switch@latest init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Choose profile names (for example, `work` and `personal`), optionally copy one existing configuration, then open a new terminal. `init` is first-time setup; running it again only shows your existing setup.
|
|
21
|
+
|
|
22
|
+
## Two commands for everyday use
|
|
23
|
+
|
|
24
|
+
Run `cpf` and `claude` at your **terminal's shell prompt** (`%` or `$`). If you are inside Claude's chat, enter `/exit` there first and wait for the terminal prompt.
|
|
25
|
+
|
|
26
|
+
1. At the terminal, run `cpf` to open the list, switch, add, rename, remove, or exit menu.
|
|
27
|
+
2. Choose with ↑/↓ and Enter. When finished, select Exit to return to the terminal without launching Claude.
|
|
28
|
+
3. At the terminal, run `claude`, choose a profile, and sign in if Claude asks.
|
|
29
|
+
|
|
30
|
+
For a direct action, use:
|
|
31
|
+
|
|
32
|
+
| Command | What it does |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `cpf work` | Make `work` active |
|
|
35
|
+
| `cpf --list` | Show profiles and the active one |
|
|
36
|
+
| `cpf add work2` | Create a profile named `work2` |
|
|
37
|
+
| `cpf rename wrok2 work2` | Rename `wrok2` to `work2` |
|
|
38
|
+
| `cpf remove work2` | Delete `work2` after confirmation |
|
|
39
|
+
| `cpf --help` | Show the command guide |
|
|
40
|
+
|
|
41
|
+
Replace the example names with yours. A single name always selects that profile, even `cpf add`, `cpf rename`, `cpf remove`, or `cpf list`; use `cpf --list` to list profiles.
|
|
42
|
+
|
|
43
|
+
With multiple profiles, interactive `claude` launches show a picker, starting on the active profile. In scripts, `claude` uses the active profile without a picker. Claude arguments, including `--help` and `--version`, pass through. Switching affects future launches; already running Claude sessions keep their profile and account.
|
|
44
|
+
|
|
45
|
+
## Add another account
|
|
46
|
+
|
|
47
|
+
1. At the terminal, create the profile:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cpf add work2
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
2. Start Claude from the terminal:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. Select `work2` in the picker, then sign in to the intended account when Claude asks.
|
|
60
|
+
|
|
61
|
+
Creating a profile does not switch to it or sign in for you. The `cpf` menu can also add a profile without memorizing the command.
|
|
62
|
+
|
|
63
|
+
## Bring in your existing default account
|
|
64
|
+
|
|
65
|
+
To run the original Claude CLI without importing a profile, use `command claude` at a Bash/zsh terminal prompt to bypass the wrapper. It uses Claude's normal configuration; if you have set `CLAUDE_CONFIG_DIR` separately, that path applies.
|
|
66
|
+
|
|
67
|
+
Your normal `~/.claude` configuration is not automatically imported or logged in when you create a profile. First-time `init` lets you select that source and the destination profile. If profiles already exist, rerunning `init` only shows a summary; use an explicit migration instead.
|
|
68
|
+
|
|
69
|
+
1. Enter `/exit` in every Claude session using the source or destination, then return to the terminal.
|
|
70
|
+
2. Choose a destination with `cpf --list`. If you need a new profile, create it with `cpf add work`.
|
|
71
|
+
3. From this repository's root in the terminal, copy into that profile and confirm the prompt:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node bin/cli.mjs migrate work --from ~/.claude
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
4. At the terminal, run `claude`, select `work`, and sign in if requested. Replace `work` with your chosen profile name.
|
|
78
|
+
|
|
79
|
+
The original `~/.claude` and `~/.claude.json` are preserved. If existing destination data is replaced, its previous copy is also retained at the backup path printed after success. Conflicting data or detected destination changes stop migration. macOS Keychain credentials are not copied, so importing configuration does not guarantee an authenticated session. See [migration details](https://ftery0.github.io/ai-profile-switch/guide/commands#import-an-existing-configuration).
|
|
80
|
+
|
|
81
|
+
## Fix a profile name
|
|
82
|
+
|
|
83
|
+
Close every Claude session using the profile before renaming it. For example, to correct `wrok2`:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cpf rename wrok2 work2
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Renaming preserves the profile's history, settings, list position, and active selection. It stops if `work2` already exists. The configuration path changes, so macOS may ask you to sign in again. Start a new Claude session after renaming.
|
|
90
|
+
|
|
91
|
+
## Shared and per-profile items
|
|
92
|
+
|
|
93
|
+
By default only `settings.json` and `commands` are shared. Everything else belongs to one profile. Add what you want every profile to use with `cpf share add`.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cpf share # Show what is shared and whether each profile is in sync
|
|
97
|
+
cpf share add CLAUDE.md # Share the active profile's CLAUDE.md with every profile
|
|
98
|
+
cpf share add skills # Share personal skills, linked one by one
|
|
99
|
+
cpf share remove skills # Stop sharing; each profile keeps a copy
|
|
100
|
+
cpf share sync # Link items you just added to _shared
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Can be shared | Always per profile |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `CLAUDE.md` `keybindings.json` `skills` `agents` `rules` `hooks` `output-styles` `workflows` `themes` | Sign-in and `.claude.json` (MCP servers, project trust), history, `projects`, plugins, `skills/synced` (skills from the account) |
|
|
106
|
+
|
|
107
|
+
- The shared copy lives in `~/.claude-profiles/_shared/<entry>` and each profile gets a link. `_shared/CLAUDE.md` may itself link to another file.
|
|
108
|
+
- The first `add` copies the active profile's content when `_shared` has none.
|
|
109
|
+
- Links are refreshed when `claude` launches a profile and when a profile is created. A profile file with different content is never replaced; you get a warning. Move or delete it, then run `cpf share sync`.
|
|
110
|
+
- Settings such as `skillOverrides` and `enabledMcpjsonServers` apply to every profile when placed in the shared `settings.json`. Register MCP servers and sign in per profile with `claude mcp add` and `/mcp`.
|
|
111
|
+
|
|
112
|
+
## Existing users
|
|
113
|
+
|
|
114
|
+
To use changes from this source checkout, run this **from the repository root**, then open a new terminal:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
node bin/cli.mjs install-shell
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For the published npm version, use this to install, repair, or refresh the shell commands and local runtime, then open a new terminal:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx ai-profile-switch@latest install-shell
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`install-shell` does not add profiles or sign in. Existing shells need the refresh before their `cpf` function can use the new management commands. Users of 1.x also run `install-shell` once to enable the local runtime. The older `claude-pick` command remains available for selecting an active profile without launching Claude.
|
|
127
|
+
|
|
128
|
+
The shell uses `~/.claude-profiles/_runtime`, so a global installation and the temporary npx cache are not needed for daily use. Node.js, npm, and Claude Code must remain available.
|
|
129
|
+
|
|
130
|
+
Interactive Claude launches check for this tool's updates once daily in the background. A verified compatible package becomes active on the next launch. Active sessions keep their existing files; account data, harness hooks, and shell profiles are not rewritten. Offline or failed downloads keep the installed version. Noninteractive launches do not update.
|
|
131
|
+
|
|
132
|
+
Set `CLAUDE_SWITCH_DISABLE_AUTO_UPDATE=1` to disable automatic updates. For an immediate update to the published version, run `npx ai-profile-switch@latest update`; add `--check` to check without installing. Global npm installations are independent; the shell runtime updates itself. Previous runtime versions are retained to protect active sessions.
|
|
133
|
+
|
|
134
|
+
## Full CLI reference
|
|
135
|
+
|
|
136
|
+
These commands run the source checkout from the repository root. For a released version, replace `node bin/cli.mjs` with `npx ai-profile-switch@latest`, or `ai-profile-switch` if globally installed.
|
|
137
|
+
|
|
138
|
+
| Command | Purpose |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `node bin/cli.mjs manage` | Open the same menu as `cpf` |
|
|
141
|
+
| `node bin/cli.mjs manage --list` | List profiles through the management interface |
|
|
142
|
+
| `node bin/cli.mjs init` | First setup or existing setup summary |
|
|
143
|
+
| `node bin/cli.mjs add work2` | Create a profile |
|
|
144
|
+
| `node bin/cli.mjs rename wrok2 work2` | Rename a profile |
|
|
145
|
+
| `node bin/cli.mjs remove work2` | Delete a profile after confirmation |
|
|
146
|
+
| `node bin/cli.mjs list` | Show profiles and active selection |
|
|
147
|
+
| `node bin/cli.mjs use work` | Change the active profile |
|
|
148
|
+
| `node bin/cli.mjs migrate work --from ~/.claude` | Copy existing configuration safely |
|
|
149
|
+
| `node bin/cli.mjs install-shell` | Install, repair, or refresh the local runtime and shell commands |
|
|
150
|
+
| `node bin/cli.mjs update --check` | Check for a published update without installing |
|
|
151
|
+
| `node bin/cli.mjs update` | Update the local runtime |
|
|
152
|
+
| `node bin/cli.mjs share add skills` | Share personal skills with every profile |
|
|
153
|
+
| `node bin/cli.mjs mcp list` | Read legacy MCP names and migration guidance |
|
|
154
|
+
| `node bin/cli.mjs rename --help` | Show help for a command without changing files |
|
|
155
|
+
|
|
156
|
+
Migration preserves the source and stops on conflicting data. Skills, agents, rules, hooks, and `CLAUDE.md` stay with the selected profile. macOS Keychain credentials are not copied; Claude may require login in the new profile. See [migration and compatibility](https://ftery0.github.io/ai-profile-switch/guide/commands).
|
|
157
|
+
|
|
158
|
+
Version 2 removes the separate MCP editor and Claude installer. Use official `claude mcp` and Claude's [update mechanism](https://code.claude.com/docs/en/setup#update-manually). Existing legacy MCP files remain unchanged. Automatic updates affect only this tool, never Claude Code.
|
|
159
|
+
|
|
160
|
+
Platform guides: [macOS](docs/setup-macos.md) · [Linux](docs/setup-linux.md) · [Windows](docs/setup-windows.md)
|
|
161
|
+
|
|
162
|
+
[MIT](./LICENSE)
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, lstatSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const profiles = join(homedir(), '.claude-profiles');
|
|
9
|
+
const installedEntry = join(profiles, '_runtime', 'bin', 'cli.mjs');
|
|
10
|
+
const pointer = join(profiles, '_runtime-current.json');
|
|
11
|
+
let entry = join(dirname(fileURLToPath(import.meta.url)), '../src/index.mjs');
|
|
12
|
+
if (existsSync(installedEntry) && realpathSync(fileURLToPath(import.meta.url)) === realpathSync(installedEntry) && existsSync(pointer)) {
|
|
13
|
+
if (lstatSync(pointer).isSymbolicLink()) throw new Error('Runtime pointer must not be a symlink');
|
|
14
|
+
const { directory } = JSON.parse(readFileSync(pointer, 'utf8'));
|
|
15
|
+
if (typeof directory !== 'string' || !/^v[\w.-]+$/.test(directory)) throw new Error('Invalid runtime pointer');
|
|
16
|
+
const versions = join(profiles, '_runtime-updates');
|
|
17
|
+
const runtime = join(versions, directory);
|
|
18
|
+
if (lstatSync(versions).isSymbolicLink() || lstatSync(runtime).isSymbolicLink()) throw new Error('Runtime must not be a symlink');
|
|
19
|
+
entry = join(runtime, 'src/index.mjs');
|
|
20
|
+
}
|
|
21
|
+
const { run } = await import(pathToFileURL(entry).href);
|
|
22
|
+
run(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-profile-switch",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Profile switching for AI coding CLIs, starting with Claude Code (formerly claude-account-switch)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-profile-switch": "bin/cli.mjs",
|
|
8
|
+
"claude-account-switch": "bin/cli.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "^18.19.0 || >=20.10.0"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --test",
|
|
19
|
+
"docs:dev": "vitepress dev website",
|
|
20
|
+
"docs:build": "vitepress build website",
|
|
21
|
+
"docs:preview": "vitepress preview website"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"ai",
|
|
25
|
+
"claude",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"multi-account",
|
|
28
|
+
"profile-manager",
|
|
29
|
+
"cli"
|
|
30
|
+
],
|
|
31
|
+
"author": "haejun <haejun7388@gmail.com>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/ftery0/ai-profile-switch.git"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"vitepress": "^1.6.4"
|
|
39
|
+
},
|
|
40
|
+
"claudeAccountSwitch": {
|
|
41
|
+
"runtimeProtocol": 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { color, success } from '../lib/ui.mjs';
|
|
2
|
+
import { createProfile, validateProfileName, profileExists } from '../lib/profile.mjs';
|
|
3
|
+
import { readMeta } from '../lib/config.mjs';
|
|
4
|
+
import { syncShared } from '../lib/shared.mjs';
|
|
5
|
+
import { reportConflicts } from './share.mjs';
|
|
6
|
+
|
|
7
|
+
export async function add(name) {
|
|
8
|
+
const err = validateProfileName(name);
|
|
9
|
+
if (err) throw new Error(err);
|
|
10
|
+
const meta = readMeta();
|
|
11
|
+
if (profileExists(name) || meta.profiles.includes(name)) {
|
|
12
|
+
throw new Error(`Profile "${name}" already exists`);
|
|
13
|
+
}
|
|
14
|
+
createProfile(name, meta.shareSettings !== false);
|
|
15
|
+
reportConflicts(syncShared([name]));
|
|
16
|
+
success(`Created profile: ${name}`);
|
|
17
|
+
console.log(` Run ${color.cyan('claude')}, choose ${color.cyan(name)}, and sign in to the intended account.`);
|
|
18
|
+
console.log(` To switch without launching: ${color.cyan(`cpf ${name}`)}`);
|
|
19
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { color, box, success, warn } from '../lib/ui.mjs';
|
|
4
|
+
import * as prompt from '../lib/prompt.mjs';
|
|
5
|
+
import { readMeta, writeMeta } from '../lib/config.mjs';
|
|
6
|
+
import { createProfile, profileExists, validateProfileName, migrateDir, migrationSource } from '../lib/profile.mjs';
|
|
7
|
+
import { HOME, DEFAULT_CLAUDE_DIR, IS_WINDOWS } from '../lib/constants.mjs';
|
|
8
|
+
import { installAllShells } from '../lib/shell.mjs';
|
|
9
|
+
|
|
10
|
+
export function existingClaudeSources() {
|
|
11
|
+
const candidates = [
|
|
12
|
+
process.env.CLAUDE_CONFIG_DIR,
|
|
13
|
+
DEFAULT_CLAUDE_DIR,
|
|
14
|
+
join(HOME, '.claude-work'),
|
|
15
|
+
join(HOME, '.claude-personal'),
|
|
16
|
+
].filter(Boolean);
|
|
17
|
+
return [...new Set(candidates.map(path => resolve(path)))].filter(path => {
|
|
18
|
+
const source = migrationSource(path);
|
|
19
|
+
return existsSync(source.directory) || existsSync(source.userConfig);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function init() {
|
|
24
|
+
const current = readMeta();
|
|
25
|
+
if (current.profiles.length > 0) {
|
|
26
|
+
warn('ai-profile-switch is already initialized.');
|
|
27
|
+
console.log(` Profiles: ${current.profiles.join(', ')}`);
|
|
28
|
+
console.log(` Active: ${current.activeProfile || 'none'}`);
|
|
29
|
+
console.log(` Add an account: ${color.cyan('cpf add <name>')}`);
|
|
30
|
+
console.log(` Repair shell integration: ${color.cyan('npx ai-profile-switch@latest install-shell')}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log();
|
|
35
|
+
console.log(box([
|
|
36
|
+
`Welcome to ${color.bold('Claude Switch!')}`,
|
|
37
|
+
'Multi-account manager for Claude Code',
|
|
38
|
+
]));
|
|
39
|
+
console.log();
|
|
40
|
+
warn('Close Claude sessions using the configuration you will import. Keep source and destination sessions closed until migration finishes.');
|
|
41
|
+
const count = await prompt.number('How many profiles do you want to set up?', 2);
|
|
42
|
+
console.log();
|
|
43
|
+
const names = [];
|
|
44
|
+
for (let index = 0; index < count; index++) {
|
|
45
|
+
const defaultName = count === 1 ? 'main' : index === 0 ? 'work' : index === 1 ? 'personal' : '';
|
|
46
|
+
const message = count === 1 ? 'Profile name:' : `Profile ${index + 1} name:`;
|
|
47
|
+
names.push(await askProfileName(message, defaultName, names));
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
const activeProfile = names.length === 1
|
|
51
|
+
? names[0]
|
|
52
|
+
: await prompt.select('Which profile should be active by default?', names);
|
|
53
|
+
if (!activeProfile) return;
|
|
54
|
+
const shareSettings = await prompt.confirm('Share settings.json and commands across profiles?', true);
|
|
55
|
+
const sources = existingClaudeSources();
|
|
56
|
+
let migration;
|
|
57
|
+
if (sources.length > 0) {
|
|
58
|
+
const source = await prompt.select('Copy an existing Claude configuration?', [
|
|
59
|
+
...sources.map(path => ({ label: path, value: path })),
|
|
60
|
+
{ label: 'Skip', value: '__skip__' },
|
|
61
|
+
]);
|
|
62
|
+
if (!source) return;
|
|
63
|
+
if (source !== '__skip__') {
|
|
64
|
+
const target = names.length === 1 ? names[0] : await prompt.select('Copy into which profile?', names);
|
|
65
|
+
if (!target) return;
|
|
66
|
+
migration = { source, target };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const name of names) {
|
|
70
|
+
if (profileExists(name)) throw new Error(`Profile directory "${name}" already exists. Choose another name.`);
|
|
71
|
+
}
|
|
72
|
+
if (migration) {
|
|
73
|
+
const { backupPath } = migrateDir(migration.source, migration.target, shareSettings);
|
|
74
|
+
success(`Copied ${migration.source} → profile: ${migration.target}`);
|
|
75
|
+
if (backupPath) warn(`Previous destination data backup: ${backupPath}`);
|
|
76
|
+
}
|
|
77
|
+
for (const name of names) {
|
|
78
|
+
if (migration?.target !== name) createProfile(name, shareSettings);
|
|
79
|
+
success(`Profile ready: ${name}`);
|
|
80
|
+
}
|
|
81
|
+
writeMeta({ ...readMeta(), activeProfile, shareSettings });
|
|
82
|
+
const { newlyInstalled, alreadyInstalled } = installAllShells();
|
|
83
|
+
const shells = [...newlyInstalled, ...alreadyInstalled];
|
|
84
|
+
if (shells.length > 0) success(`Shell integration installed (${shells.join(', ')})`);
|
|
85
|
+
success(`Active profile: ${activeProfile}`);
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(` Open a new terminal and run ${color.cyan('claude')}. Claude will check authentication.`);
|
|
88
|
+
if (names.length > 1) {
|
|
89
|
+
const other = names.find(name => name !== activeProfile);
|
|
90
|
+
const command = IS_WINDOWS ? `cpf ${other}; claude` : `cpf ${other} && claude`;
|
|
91
|
+
console.log(` To use another profile: ${color.cyan(command)}`);
|
|
92
|
+
}
|
|
93
|
+
if (migration) {
|
|
94
|
+
warn('Original configuration was preserved. macOS Keychain credentials are not copied.');
|
|
95
|
+
}
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function askProfileName(message, defaultName, names) {
|
|
100
|
+
while (true) {
|
|
101
|
+
const name = await prompt.text(message, defaultName);
|
|
102
|
+
const error = validateProfileName(name);
|
|
103
|
+
if (error || names.includes(name) || profileExists(name)) {
|
|
104
|
+
console.log(` ${color.red(error || `"${name}" is already used — choose a different name`)}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
return name;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { PROFILES_DIR } from '../lib/constants.mjs';
|
|
3
|
+
import { installAllShells } from '../lib/shell.mjs';
|
|
4
|
+
import { success, info } from '../lib/ui.mjs';
|
|
5
|
+
|
|
6
|
+
export async function installShell() {
|
|
7
|
+
mkdirSync(PROFILES_DIR, { recursive: true });
|
|
8
|
+
const { newlyInstalled, alreadyInstalled } = installAllShells();
|
|
9
|
+
const shells = [...newlyInstalled, ...alreadyInstalled];
|
|
10
|
+
success('Local shell runtime installed.');
|
|
11
|
+
if (shells.length) success(`Shell integration ready (${shells.join(', ')})`);
|
|
12
|
+
info('Open a new terminal. Run claude to launch or cpf to manage profiles.');
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { color, info } from '../lib/ui.mjs';
|
|
2
|
+
import { listProfiles, profileDir } from '../lib/profile.mjs';
|
|
3
|
+
import { getActiveProfile } from '../lib/config.mjs';
|
|
4
|
+
|
|
5
|
+
export async function list() {
|
|
6
|
+
const profiles = listProfiles();
|
|
7
|
+
|
|
8
|
+
if (profiles.length === 0) {
|
|
9
|
+
info('No profiles configured. Run cpf to add one, or npx ai-profile-switch@latest init for first setup.');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const active = getActiveProfile();
|
|
14
|
+
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(` ${color.bold('Profiles:')}`);
|
|
17
|
+
console.log();
|
|
18
|
+
for (const name of profiles) {
|
|
19
|
+
const isActive = name === active;
|
|
20
|
+
const marker = isActive ? color.green(' * ') : ' ';
|
|
21
|
+
const label = isActive ? color.bold(name) : name;
|
|
22
|
+
const dir = color.dim(profileDir(name));
|
|
23
|
+
console.log(` ${marker}${label} ${dir}`);
|
|
24
|
+
}
|
|
25
|
+
console.log();
|
|
26
|
+
console.log(` Active: ${color.green(active || 'none')}`);
|
|
27
|
+
console.log();
|
|
28
|
+
}
|