@daylenjeez/ccm-switch 1.2.2 → 1.2.3
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 +19 -5
- package/README.zh-CN.md +19 -5
- package/dist/claude.d.ts +1 -0
- package/dist/claude.js +9 -0
- package/dist/i18n/en.js +17 -11
- package/dist/i18n/zh.d.ts +15 -11
- package/dist/i18n/zh.js +17 -11
- package/dist/index.js +95 -45
- package/dist/store/cc-switch.js +25 -2
- package/dist/types.d.ts +0 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +7 -13
- package/package.json +1 -1
- package/src/claude.ts +8 -0
- package/src/i18n/en.ts +19 -11
- package/src/i18n/zh.ts +19 -11
- package/src/index.ts +107 -43
- package/src/store/cc-switch.ts +33 -2
- package/src/types.ts +0 -1
- package/src/utils.ts +9 -13
package/README.md
CHANGED
|
@@ -52,17 +52,17 @@ ccm add # Interactive wizard to add a provider
|
|
|
52
52
|
|
|
53
53
|
## 🔌 cc-switch Integration
|
|
54
54
|
|
|
55
|
-
Already using [cc-switch](https://github.com/farion1231/cc-switch)? ccm
|
|
55
|
+
Already using [cc-switch](https://github.com/farion1231/cc-switch)? ccm can sync its configurations into standalone storage:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
$ ccm init
|
|
59
59
|
cc-switch detected. Import configurations from it? (Y/n)
|
|
60
|
-
✓ Initialized
|
|
61
|
-
✓
|
|
60
|
+
✓ Initialized
|
|
61
|
+
✓ Synced 4 configurations
|
|
62
62
|
Active: OpenRouter
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
You can also run `ccm sync` at any time to pull the latest cc-switch configurations into `~/.ccm/config.json`.
|
|
66
66
|
|
|
67
67
|
## ➕ Adding Configurations
|
|
68
68
|
|
|
@@ -132,7 +132,9 @@ Aliases are stored in `~/.ccm/rc.json`:
|
|
|
132
132
|
| `ccm modify [name]` | `edit` | Edit existing configuration |
|
|
133
133
|
| `ccm remove [name]` | `rm` | Interactive or named delete |
|
|
134
134
|
| `ccm current` | | Show active configuration |
|
|
135
|
-
| `ccm config` | |
|
|
135
|
+
| `ccm config` | | View data source mode (deprecated) |
|
|
136
|
+
| `ccm sync` | | Sync cc-switch configs into standalone |
|
|
137
|
+
| `ccm clear` | | Clean up data files |
|
|
136
138
|
|
|
137
139
|
### Aliases
|
|
138
140
|
|
|
@@ -190,6 +192,18 @@ Claude Code reads `~/.claude/settings.json` on startup. The `env` field controls
|
|
|
190
192
|
|
|
191
193
|
`ccm use` writes the selected profile into `settings.json` while preserving personal settings (`language`, `permissions`, etc.). Restart Claude Code to apply.
|
|
192
194
|
|
|
195
|
+
## 🗑️ Uninstall
|
|
196
|
+
|
|
197
|
+
Removing `ccm` only removes the CLI itself. Your data files are left behind.
|
|
198
|
+
|
|
199
|
+
Use `ccm clear` to delete them automatically, or clean them up manually:
|
|
200
|
+
|
|
201
|
+
- `~/.ccm/rc.json` — aliases and locale
|
|
202
|
+
- `~/.ccm/config.json` — profiles
|
|
203
|
+
- `~/.claude/settings.json` — may still contain an active `env` profile written by ccm
|
|
204
|
+
|
|
205
|
+
> Do **not** delete `~/.cc-switch/cc-switch.db` unless you are also uninstalling cc-switch; that database is owned by cc-switch.
|
|
206
|
+
|
|
193
207
|
## 📄 License
|
|
194
208
|
|
|
195
209
|
[MIT](./LICENSE)
|
package/README.zh-CN.md
CHANGED
|
@@ -52,17 +52,17 @@ ccm add # 交互式向导添加供应商
|
|
|
52
52
|
|
|
53
53
|
## 🔌 cc-switch 集成
|
|
54
54
|
|
|
55
|
-
已经在用 [cc-switch](https://github.com/farion1231/cc-switch)?ccm
|
|
55
|
+
已经在用 [cc-switch](https://github.com/farion1231/cc-switch)?ccm 可以将它的配置同步到本地:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
$ ccm init
|
|
59
59
|
检测到 cc-switch 已安装,是否从中导入配置?(Y/n)
|
|
60
|
-
✓
|
|
61
|
-
✓
|
|
60
|
+
✓ 初始化完成
|
|
61
|
+
✓ 已同步 4 个配置
|
|
62
62
|
当前激活: OpenRouter
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
你也可以随时运行 `ccm sync` 将最新的 cc-switch 配置同步到 `~/.ccm/config.json`。
|
|
66
66
|
|
|
67
67
|
## ➕ 添加配置
|
|
68
68
|
|
|
@@ -132,7 +132,9 @@ ANTHROPIC_DEFAULT_HAIKU_MODEL (可选):
|
|
|
132
132
|
| `ccm modify [name]` | `edit` | 修改已有配置 |
|
|
133
133
|
| `ccm remove [name]` | `rm` | 交互式或指定名称删除 |
|
|
134
134
|
| `ccm current` | | 显示当前激活配置 |
|
|
135
|
-
| `ccm config` | |
|
|
135
|
+
| `ccm config` | | 查看数据源模式(已废弃) |
|
|
136
|
+
| `ccm sync` | | 从 cc-switch 同步配置到本地 |
|
|
137
|
+
| `ccm clear` | | 清理数据文件 |
|
|
136
138
|
|
|
137
139
|
### 别名管理
|
|
138
140
|
|
|
@@ -190,6 +192,18 @@ Claude Code 启动时读取 `~/.claude/settings.json`,`env` 字段控制 API
|
|
|
190
192
|
|
|
191
193
|
`ccm use` 将选中配置写入 `settings.json`,同时保留 `language`、`permissions` 等个人设置。重启 Claude Code 后生效。
|
|
192
194
|
|
|
195
|
+
## 🗑️ 卸载
|
|
196
|
+
|
|
197
|
+
卸载 `ccm` 只会删除 CLI 程序本身,相关的数据文件仍然保留。
|
|
198
|
+
|
|
199
|
+
你可以先运行 `ccm clear` 自动清理数据文件,也可以手动删除:
|
|
200
|
+
|
|
201
|
+
- `~/.ccm/rc.json` — 别名和语言设置
|
|
202
|
+
- `~/.ccm/config.json` — 配置方案
|
|
203
|
+
- `~/.claude/settings.json` — 可能仍包含 ccm 写入的 `env` 配置
|
|
204
|
+
|
|
205
|
+
> 除非你也卸载了 cc-switch,否则**不要**删除 `~/.cc-switch/cc-switch.db`;该数据库由 cc-switch 管理。
|
|
206
|
+
|
|
193
207
|
## 📄 License
|
|
194
208
|
|
|
195
209
|
[MIT](./LICENSE)
|
package/dist/claude.d.ts
CHANGED
package/dist/claude.js
CHANGED
|
@@ -20,6 +20,15 @@ export function applyProfile(settingsConfig) {
|
|
|
20
20
|
const merged = { ...preserved, ...settingsConfig };
|
|
21
21
|
writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2));
|
|
22
22
|
}
|
|
23
|
+
export function clearEnvFromSettings() {
|
|
24
|
+
if (!existsSync(SETTINGS_PATH))
|
|
25
|
+
return;
|
|
26
|
+
const current = readClaudeSettings();
|
|
27
|
+
if (!("env" in current))
|
|
28
|
+
return;
|
|
29
|
+
delete current.env;
|
|
30
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(current, null, 2));
|
|
31
|
+
}
|
|
23
32
|
export function getSettingsPath() {
|
|
24
33
|
return SETTINGS_PATH;
|
|
25
34
|
}
|
package/dist/i18n/en.js
CHANGED
|
@@ -17,24 +17,15 @@ const en = {
|
|
|
17
17
|
// init
|
|
18
18
|
"init.description": "Initialize ccm",
|
|
19
19
|
"init.cc_switch_found": "cc-switch detected. Import configurations from it? (Y/n) ",
|
|
20
|
-
"init.
|
|
21
|
-
"init.current": "Active: {name}",
|
|
22
|
-
"init.no_current": "No active configuration",
|
|
23
|
-
"init.done_cc_switch": "✓ Initialized in cc-switch mode",
|
|
24
|
-
"init.done_standalone": "✓ Initialized in standalone mode",
|
|
20
|
+
"init.done": "✓ Initialized",
|
|
25
21
|
// config
|
|
26
|
-
"config.description": "View
|
|
27
|
-
"config.current_mode": "Current mode: {mode}",
|
|
28
|
-
"config.switch_confirm": "Switch mode? (y/N) ",
|
|
29
|
-
"config.cc_switch_not_installed": "cc-switch is not installed",
|
|
30
|
-
"config.switched": "✓ Switched to {mode} mode",
|
|
22
|
+
"config.description": "View data source mode (deprecated)",
|
|
31
23
|
// list
|
|
32
24
|
"list.description": "List and select configurations",
|
|
33
25
|
"list.empty": "No configurations yet. Use ccm save <name> to save current config",
|
|
34
26
|
"list.header": "Available configurations:",
|
|
35
27
|
"list.select": "Select configuration:",
|
|
36
28
|
"list.current_marker": "(current)",
|
|
37
|
-
"list.cancelled": "Cancelled",
|
|
38
29
|
"list.choose_number": "Enter number to switch (Enter to skip): ",
|
|
39
30
|
// current
|
|
40
31
|
"current.description": "Show the currently active configuration",
|
|
@@ -118,6 +109,21 @@ const en = {
|
|
|
118
109
|
"alias.rm_alias": "Alias {name}",
|
|
119
110
|
"alias.rm_config": "Config {target}",
|
|
120
111
|
"alias.rm_choose": "Choose (1/2): ",
|
|
112
|
+
// sync
|
|
113
|
+
"sync.description": "Sync configurations from cc-switch",
|
|
114
|
+
"sync.no_cc_switch": "cc-switch database not detected",
|
|
115
|
+
"sync.empty": "No Claude configurations found in cc-switch",
|
|
116
|
+
"sync.done": "✓ Synced {count} configurations",
|
|
117
|
+
"sync.current": "Active: {name}",
|
|
118
|
+
"sync.no_current": "No active configuration",
|
|
119
|
+
// clear
|
|
120
|
+
"clear.description": "Clean up ccm data files",
|
|
121
|
+
"clear.confirm": "Delete all ccm data files? (y/N) ",
|
|
122
|
+
"clear.cancelled": "Cancelled",
|
|
123
|
+
"clear.removed": "✓ Deleted {path}",
|
|
124
|
+
"clear.clear_env": "Also clear env config in ~/.claude/settings.json? (y/N) ",
|
|
125
|
+
"clear.env_cleared": "✓ Cleared env config",
|
|
126
|
+
"clear.done": "✓ Cleanup complete",
|
|
121
127
|
// store errors
|
|
122
128
|
"store.db_not_found": "cc-switch database not found: {path}",
|
|
123
129
|
};
|
package/dist/i18n/zh.d.ts
CHANGED
|
@@ -12,22 +12,13 @@ declare const zh: {
|
|
|
12
12
|
readonly "suggest.use_list": "使用 ccm list 查看所有可用配置";
|
|
13
13
|
readonly "init.description": "初始化 ccm";
|
|
14
14
|
readonly "init.cc_switch_found": "检测到 cc-switch 已安装,是否从中导入配置?(Y/n) ";
|
|
15
|
-
readonly "init.
|
|
16
|
-
readonly "
|
|
17
|
-
readonly "init.no_current": "当前无激活配置";
|
|
18
|
-
readonly "init.done_cc_switch": "✓ 已初始化为 cc-switch 模式";
|
|
19
|
-
readonly "init.done_standalone": "✓ 已初始化为独立模式";
|
|
20
|
-
readonly "config.description": "查看或切换数据源模式";
|
|
21
|
-
readonly "config.current_mode": "当前模式: {mode}";
|
|
22
|
-
readonly "config.switch_confirm": "是否切换模式?(y/N) ";
|
|
23
|
-
readonly "config.cc_switch_not_installed": "cc-switch 未安装";
|
|
24
|
-
readonly "config.switched": "✓ 已切换为 {mode} 模式";
|
|
15
|
+
readonly "init.done": "✓ 初始化完成";
|
|
16
|
+
readonly "config.description": "查看数据源模式(已废弃)";
|
|
25
17
|
readonly "list.description": "列出并选择配置方案";
|
|
26
18
|
readonly "list.empty": "暂无配置方案。使用 ccm save <name> 保存当前配置";
|
|
27
19
|
readonly "list.header": "可用配置:";
|
|
28
20
|
readonly "list.select": "选择配置:";
|
|
29
21
|
readonly "list.current_marker": "(当前)";
|
|
30
|
-
readonly "list.cancelled": "已取消";
|
|
31
22
|
readonly "list.choose_number": "输入序号切换 (回车跳过): ";
|
|
32
23
|
readonly "current.description": "显示当前生效的配置";
|
|
33
24
|
readonly "current.none": "当前无激活配置";
|
|
@@ -101,6 +92,19 @@ declare const zh: {
|
|
|
101
92
|
readonly "alias.rm_alias": "别名 {name}";
|
|
102
93
|
readonly "alias.rm_config": "配置 {target}";
|
|
103
94
|
readonly "alias.rm_choose": "请选择 (1/2): ";
|
|
95
|
+
readonly "sync.description": "从 cc-switch 同步配置";
|
|
96
|
+
readonly "sync.no_cc_switch": "未检测到 cc-switch 数据库";
|
|
97
|
+
readonly "sync.empty": "cc-switch 中没有找到 Claude 配置";
|
|
98
|
+
readonly "sync.done": "✓ 已同步 {count} 个配置";
|
|
99
|
+
readonly "sync.current": "当前激活: {name}";
|
|
100
|
+
readonly "sync.no_current": "当前无激活配置";
|
|
101
|
+
readonly "clear.description": "清理 ccm 数据文件";
|
|
102
|
+
readonly "clear.confirm": "确认删除所有 ccm 数据文件?(y/N) ";
|
|
103
|
+
readonly "clear.cancelled": "已取消清理";
|
|
104
|
+
readonly "clear.removed": "✓ 已删除 {path}";
|
|
105
|
+
readonly "clear.clear_env": "是否同时清除 ~/.claude/settings.json 中的 env 配置?(y/N) ";
|
|
106
|
+
readonly "clear.env_cleared": "✓ 已清除 env 配置";
|
|
107
|
+
readonly "clear.done": "✓ 清理完成";
|
|
104
108
|
readonly "store.db_not_found": "cc-switch 数据库不存在: {path}";
|
|
105
109
|
};
|
|
106
110
|
export type TranslationKey = keyof typeof zh;
|
package/dist/i18n/zh.js
CHANGED
|
@@ -17,24 +17,15 @@ const zh = {
|
|
|
17
17
|
// init
|
|
18
18
|
"init.description": "初始化 ccm",
|
|
19
19
|
"init.cc_switch_found": "检测到 cc-switch 已安装,是否从中导入配置?(Y/n) ",
|
|
20
|
-
"init.
|
|
21
|
-
"init.current": "当前激活: {name}",
|
|
22
|
-
"init.no_current": "当前无激活配置",
|
|
23
|
-
"init.done_cc_switch": "✓ 已初始化为 cc-switch 模式",
|
|
24
|
-
"init.done_standalone": "✓ 已初始化为独立模式",
|
|
20
|
+
"init.done": "✓ 初始化完成",
|
|
25
21
|
// config
|
|
26
|
-
"config.description": "
|
|
27
|
-
"config.current_mode": "当前模式: {mode}",
|
|
28
|
-
"config.switch_confirm": "是否切换模式?(y/N) ",
|
|
29
|
-
"config.cc_switch_not_installed": "cc-switch 未安装",
|
|
30
|
-
"config.switched": "✓ 已切换为 {mode} 模式",
|
|
22
|
+
"config.description": "查看数据源模式(已废弃)",
|
|
31
23
|
// list
|
|
32
24
|
"list.description": "列出并选择配置方案",
|
|
33
25
|
"list.empty": "暂无配置方案。使用 ccm save <name> 保存当前配置",
|
|
34
26
|
"list.header": "可用配置:",
|
|
35
27
|
"list.select": "选择配置:",
|
|
36
28
|
"list.current_marker": "(当前)",
|
|
37
|
-
"list.cancelled": "已取消",
|
|
38
29
|
"list.choose_number": "输入序号切换 (回车跳过): ",
|
|
39
30
|
// current
|
|
40
31
|
"current.description": "显示当前生效的配置",
|
|
@@ -118,6 +109,21 @@ const zh = {
|
|
|
118
109
|
"alias.rm_alias": "别名 {name}",
|
|
119
110
|
"alias.rm_config": "配置 {target}",
|
|
120
111
|
"alias.rm_choose": "请选择 (1/2): ",
|
|
112
|
+
// sync
|
|
113
|
+
"sync.description": "从 cc-switch 同步配置",
|
|
114
|
+
"sync.no_cc_switch": "未检测到 cc-switch 数据库",
|
|
115
|
+
"sync.empty": "cc-switch 中没有找到 Claude 配置",
|
|
116
|
+
"sync.done": "✓ 已同步 {count} 个配置",
|
|
117
|
+
"sync.current": "当前激活: {name}",
|
|
118
|
+
"sync.no_current": "当前无激活配置",
|
|
119
|
+
// clear
|
|
120
|
+
"clear.description": "清理 ccm 数据文件",
|
|
121
|
+
"clear.confirm": "确认删除所有 ccm 数据文件?(y/N) ",
|
|
122
|
+
"clear.cancelled": "已取消清理",
|
|
123
|
+
"clear.removed": "✓ 已删除 {path}",
|
|
124
|
+
"clear.clear_env": "是否同时清除 ~/.claude/settings.json 中的 env 配置?(y/N) ",
|
|
125
|
+
"clear.env_cleared": "✓ 已清除 env 配置",
|
|
126
|
+
"clear.done": "✓ 清理完成",
|
|
121
127
|
// store errors
|
|
122
128
|
"store.db_not_found": "cc-switch 数据库不存在: {path}",
|
|
123
129
|
};
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import { Command } from "commander";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { readRc, writeRc, getStore } from "./utils.js";
|
|
5
5
|
import { ccSwitchExists } from "./store/cc-switch.js";
|
|
6
|
-
import { readClaudeSettings, applyProfile } from "./claude.js";
|
|
6
|
+
import { readClaudeSettings, applyProfile, clearEnvFromSettings, getSettingsPath } from "./claude.js";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
|
-
import { writeFileSync, readFileSync, unlinkSync } from "fs";
|
|
10
|
-
import { tmpdir } from "os";
|
|
9
|
+
import { writeFileSync, readFileSync, unlinkSync, existsSync } from "fs";
|
|
10
|
+
import { tmpdir, homedir } from "os";
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
import { t, setLocale } from "./i18n/index.js";
|
|
13
13
|
import * as clack from "@clack/prompts";
|
|
@@ -26,14 +26,9 @@ function ask(question) {
|
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
-
// Helper: ensure
|
|
29
|
+
// Helper: ensure store ready
|
|
30
30
|
function ensureStore() {
|
|
31
|
-
|
|
32
|
-
if (!store) {
|
|
33
|
-
console.log(chalk.yellow(t("common.not_init")));
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
return store;
|
|
31
|
+
return getStore();
|
|
37
32
|
}
|
|
38
33
|
// Helper: format env for display
|
|
39
34
|
function formatEnv(env) {
|
|
@@ -154,49 +149,109 @@ program
|
|
|
154
149
|
.command("init")
|
|
155
150
|
.description(t("init.description"))
|
|
156
151
|
.action(async () => {
|
|
152
|
+
const rc = readRc();
|
|
153
|
+
writeRc({ aliases: rc?.aliases, locale: rc?.locale });
|
|
154
|
+
console.log(chalk.green(t("init.done")));
|
|
157
155
|
if (ccSwitchExists()) {
|
|
158
156
|
const use = await ask(t("init.cc_switch_found"));
|
|
159
157
|
if (use.toLowerCase() !== "n") {
|
|
160
|
-
writeRc({ mode: "cc-switch" });
|
|
161
158
|
const { CcSwitchStore } = await import("./store/cc-switch.js");
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
159
|
+
const { StandaloneStore } = await import("./store/standalone.js");
|
|
160
|
+
const ccStore = new CcSwitchStore();
|
|
161
|
+
const standaloneStore = new StandaloneStore();
|
|
162
|
+
const profiles = ccStore.list();
|
|
163
|
+
const current = ccStore.getCurrent();
|
|
164
|
+
for (const profile of profiles) {
|
|
165
|
+
standaloneStore.save(profile.name, profile.settingsConfig);
|
|
166
|
+
}
|
|
167
|
+
if (current) {
|
|
168
|
+
standaloneStore.setCurrent(current);
|
|
169
|
+
}
|
|
170
|
+
console.log(chalk.green(t("sync.done", { count: String(profiles.length) })));
|
|
167
171
|
if (current) {
|
|
168
|
-
console.log(chalk.gray(t("
|
|
172
|
+
console.log(chalk.gray(t("sync.current", { name: current })));
|
|
169
173
|
}
|
|
170
174
|
else {
|
|
171
|
-
console.log(chalk.gray(t("
|
|
175
|
+
console.log(chalk.gray(t("sync.no_current")));
|
|
172
176
|
}
|
|
173
|
-
|
|
177
|
+
ccStore.close();
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
|
-
writeRc({ mode: "standalone" });
|
|
177
|
-
console.log(chalk.green(t("init.done_standalone")));
|
|
178
180
|
});
|
|
179
|
-
// ccm config
|
|
181
|
+
// ccm config (deprecated, kept for compatibility)
|
|
180
182
|
program
|
|
181
183
|
.command("config")
|
|
182
184
|
.description(t("config.description"))
|
|
183
185
|
.action(async () => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
console.log(chalk.gray("ccm now only uses standalone mode. No configuration needed."));
|
|
187
|
+
});
|
|
188
|
+
// ccm sync
|
|
189
|
+
program
|
|
190
|
+
.command("sync")
|
|
191
|
+
.description(t("sync.description"))
|
|
192
|
+
.action(async () => {
|
|
193
|
+
if (!ccSwitchExists()) {
|
|
194
|
+
console.log(chalk.red(t("sync.no_cc_switch")));
|
|
187
195
|
return;
|
|
188
196
|
}
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
const { CcSwitchStore } = await import("./store/cc-switch.js");
|
|
198
|
+
const { StandaloneStore } = await import("./store/standalone.js");
|
|
199
|
+
const ccStore = new CcSwitchStore();
|
|
200
|
+
const standaloneStore = new StandaloneStore();
|
|
201
|
+
const profiles = ccStore.list();
|
|
202
|
+
if (profiles.length === 0) {
|
|
203
|
+
console.log(chalk.yellow(t("sync.empty")));
|
|
204
|
+
ccStore.close();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
for (const profile of profiles) {
|
|
208
|
+
standaloneStore.save(profile.name, profile.settingsConfig);
|
|
209
|
+
}
|
|
210
|
+
const current = ccStore.getCurrent();
|
|
211
|
+
if (current) {
|
|
212
|
+
standaloneStore.setCurrent(current);
|
|
213
|
+
}
|
|
214
|
+
console.log(chalk.green(t("sync.done", { count: String(profiles.length) })));
|
|
215
|
+
if (current) {
|
|
216
|
+
console.log(chalk.gray(t("sync.current", { name: current })));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(chalk.gray(t("sync.no_current")));
|
|
220
|
+
}
|
|
221
|
+
ccStore.close();
|
|
222
|
+
});
|
|
223
|
+
// ccm clear
|
|
224
|
+
program
|
|
225
|
+
.command("clear")
|
|
226
|
+
.description(t("clear.description"))
|
|
227
|
+
.action(async () => {
|
|
228
|
+
const confirm = await ask(t("clear.confirm"));
|
|
229
|
+
if (confirm.toLowerCase() !== "y") {
|
|
230
|
+
console.log(chalk.gray(t("clear.cancelled")));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const rcPath = join(homedir(), ".ccm", "rc.json");
|
|
234
|
+
const configPath = join(homedir(), ".ccm", "config.json");
|
|
235
|
+
if (existsSync(configPath)) {
|
|
236
|
+
unlinkSync(configPath);
|
|
237
|
+
console.log(chalk.green(t("clear.removed", { path: configPath })));
|
|
238
|
+
}
|
|
239
|
+
if (existsSync(rcPath)) {
|
|
240
|
+
unlinkSync(rcPath);
|
|
241
|
+
console.log(chalk.green(t("clear.removed", { path: rcPath })));
|
|
242
|
+
}
|
|
243
|
+
const settingsPath = getSettingsPath();
|
|
244
|
+
if (existsSync(settingsPath)) {
|
|
245
|
+
const settings = readClaudeSettings();
|
|
246
|
+
if ("env" in settings) {
|
|
247
|
+
const clearEnv = await ask(t("clear.clear_env"));
|
|
248
|
+
if (clearEnv.toLowerCase() === "y") {
|
|
249
|
+
clearEnvFromSettings();
|
|
250
|
+
console.log(chalk.green(t("clear.env_cleared")));
|
|
251
|
+
}
|
|
196
252
|
}
|
|
197
|
-
writeRc({ mode: newMode });
|
|
198
|
-
console.log(chalk.green(t("config.switched", { mode: newMode })));
|
|
199
253
|
}
|
|
254
|
+
console.log(chalk.green(t("clear.done")));
|
|
200
255
|
});
|
|
201
256
|
// ccm list
|
|
202
257
|
program
|
|
@@ -245,8 +300,7 @@ program
|
|
|
245
300
|
initialValue: initial >= 0 ? profiles[initial].name : profiles[0].name,
|
|
246
301
|
});
|
|
247
302
|
if (clack.isCancel(selected)) {
|
|
248
|
-
|
|
249
|
-
return;
|
|
303
|
+
process.exit(0);
|
|
250
304
|
}
|
|
251
305
|
switchTo(selected);
|
|
252
306
|
}
|
|
@@ -528,8 +582,7 @@ program
|
|
|
528
582
|
options,
|
|
529
583
|
});
|
|
530
584
|
if (clack.isCancel(selected)) {
|
|
531
|
-
|
|
532
|
-
return;
|
|
585
|
+
process.exit(0);
|
|
533
586
|
}
|
|
534
587
|
name = selected;
|
|
535
588
|
}
|
|
@@ -675,8 +728,7 @@ program
|
|
|
675
728
|
options,
|
|
676
729
|
});
|
|
677
730
|
if (clack.isCancel(selected)) {
|
|
678
|
-
|
|
679
|
-
return;
|
|
731
|
+
process.exit(0);
|
|
680
732
|
}
|
|
681
733
|
name = selected;
|
|
682
734
|
}
|
|
@@ -809,10 +861,6 @@ const SUPPORTED_LOCALES = [
|
|
|
809
861
|
];
|
|
810
862
|
const switchLocale = (code) => {
|
|
811
863
|
const rc = readRc();
|
|
812
|
-
if (!rc) {
|
|
813
|
-
console.log(chalk.yellow(t("common.not_init")));
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
864
|
rc.locale = code;
|
|
817
865
|
writeRc(rc);
|
|
818
866
|
setLocale(code);
|
|
@@ -837,7 +885,9 @@ localeCmd
|
|
|
837
885
|
options,
|
|
838
886
|
initialValue: current,
|
|
839
887
|
});
|
|
840
|
-
if (clack.isCancel(selected)
|
|
888
|
+
if (clack.isCancel(selected))
|
|
889
|
+
process.exit(0);
|
|
890
|
+
if (selected === current)
|
|
841
891
|
return;
|
|
842
892
|
switchLocale(selected);
|
|
843
893
|
}
|
package/dist/store/cc-switch.js
CHANGED
|
@@ -46,10 +46,17 @@ export class CcSwitchStore {
|
|
|
46
46
|
.run(JSON.stringify(settingsConfig), name);
|
|
47
47
|
}
|
|
48
48
|
else {
|
|
49
|
+
const maxSort = this.db
|
|
50
|
+
.prepare(`SELECT COALESCE(MAX(sort_index), -1) as max_sort FROM providers WHERE app_type = 'claude'`)
|
|
51
|
+
.get();
|
|
52
|
+
const sortIndex = (maxSort?.max_sort ?? -1) + 1;
|
|
49
53
|
const id = crypto.randomUUID();
|
|
50
54
|
this.db
|
|
51
|
-
.prepare(`INSERT INTO providers (
|
|
52
|
-
|
|
55
|
+
.prepare(`INSERT INTO providers (
|
|
56
|
+
id, app_type, name, settings_config, website_url, category,
|
|
57
|
+
created_at, sort_index, notes, icon, icon_color, meta, is_current, in_failover_queue
|
|
58
|
+
) VALUES (?, 'claude', ?, ?, NULL, NULL, ?, ?, NULL, NULL, NULL, '{}', 0, 0)`)
|
|
59
|
+
.run(id, name, JSON.stringify(settingsConfig), Date.now(), sortIndex);
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
remove(name) {
|
|
@@ -59,6 +66,13 @@ export class CcSwitchStore {
|
|
|
59
66
|
return result.changes > 0;
|
|
60
67
|
}
|
|
61
68
|
getCurrent() {
|
|
69
|
+
// Prefer DB is_current so we stay in sync with cc-switch UI
|
|
70
|
+
const dbRow = this.db
|
|
71
|
+
.prepare(`SELECT name FROM providers WHERE app_type = 'claude' AND is_current = 1 LIMIT 1`)
|
|
72
|
+
.get();
|
|
73
|
+
if (dbRow)
|
|
74
|
+
return dbRow.name;
|
|
75
|
+
// Fallback to settings.json
|
|
62
76
|
if (!existsSync(SETTINGS_PATH))
|
|
63
77
|
return undefined;
|
|
64
78
|
try {
|
|
@@ -79,6 +93,15 @@ export class CcSwitchStore {
|
|
|
79
93
|
const profile = this.get(name);
|
|
80
94
|
if (!profile)
|
|
81
95
|
throw new Error(t("error.not_found", { name }));
|
|
96
|
+
const tx = this.db.transaction(() => {
|
|
97
|
+
this.db
|
|
98
|
+
.prepare(`UPDATE providers SET is_current = 0 WHERE app_type = 'claude'`)
|
|
99
|
+
.run();
|
|
100
|
+
this.db
|
|
101
|
+
.prepare(`UPDATE providers SET is_current = 1 WHERE app_type = 'claude' AND id = ?`)
|
|
102
|
+
.run(profile.id);
|
|
103
|
+
});
|
|
104
|
+
tx();
|
|
82
105
|
if (existsSync(SETTINGS_PATH)) {
|
|
83
106
|
const settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
84
107
|
settings.currentProviderClaude = profile.id;
|
package/dist/types.d.ts
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RcConfig } from "./types.js";
|
|
2
2
|
import type { DataStore } from "./store/interface.js";
|
|
3
|
-
export declare function readRc(): RcConfig
|
|
3
|
+
export declare function readRc(): RcConfig;
|
|
4
4
|
export declare function writeRc(rc: RcConfig): void;
|
|
5
|
-
export declare function getStore(): DataStore
|
|
5
|
+
export declare function getStore(): DataStore;
|
package/dist/utils.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { homedir } from "os";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
-
import { CcSwitchStore } from "./store/cc-switch.js";
|
|
5
4
|
import { StandaloneStore } from "./store/standalone.js";
|
|
6
5
|
const CCM_DIR = join(homedir(), ".ccm");
|
|
7
6
|
const RC_PATH = join(CCM_DIR, "rc.json");
|
|
8
7
|
export function readRc() {
|
|
9
|
-
if (!existsSync(RC_PATH))
|
|
10
|
-
|
|
8
|
+
if (!existsSync(RC_PATH)) {
|
|
9
|
+
writeRc({});
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
11
12
|
try {
|
|
12
13
|
return JSON.parse(readFileSync(RC_PATH, "utf-8"));
|
|
13
14
|
}
|
|
14
15
|
catch {
|
|
15
|
-
|
|
16
|
+
writeRc({});
|
|
17
|
+
return {};
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
export function writeRc(rc) {
|
|
@@ -22,13 +24,5 @@ export function writeRc(rc) {
|
|
|
22
24
|
writeFileSync(RC_PATH, JSON.stringify(rc, null, 2));
|
|
23
25
|
}
|
|
24
26
|
export function getStore() {
|
|
25
|
-
|
|
26
|
-
if (!rc)
|
|
27
|
-
return null;
|
|
28
|
-
if (rc.mode === "cc-switch") {
|
|
29
|
-
return new CcSwitchStore();
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
return new StandaloneStore();
|
|
33
|
-
}
|
|
27
|
+
return new StandaloneStore();
|
|
34
28
|
}
|
package/package.json
CHANGED
package/src/claude.ts
CHANGED
|
@@ -25,6 +25,14 @@ export function applyProfile(settingsConfig: Record<string, unknown>): void {
|
|
|
25
25
|
writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export function clearEnvFromSettings(): void {
|
|
29
|
+
if (!existsSync(SETTINGS_PATH)) return;
|
|
30
|
+
const current = readClaudeSettings();
|
|
31
|
+
if (!("env" in current)) return;
|
|
32
|
+
delete current.env;
|
|
33
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(current, null, 2));
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
export function getSettingsPath(): string {
|
|
29
37
|
return SETTINGS_PATH;
|
|
30
38
|
}
|
package/src/i18n/en.ts
CHANGED
|
@@ -23,18 +23,10 @@ const en: Record<TranslationKey, string> = {
|
|
|
23
23
|
// init
|
|
24
24
|
"init.description": "Initialize ccm",
|
|
25
25
|
"init.cc_switch_found":"cc-switch detected. Import configurations from it? (Y/n) ",
|
|
26
|
-
"init.
|
|
27
|
-
"init.current": "Active: {name}",
|
|
28
|
-
"init.no_current": "No active configuration",
|
|
29
|
-
"init.done_cc_switch": "✓ Initialized in cc-switch mode",
|
|
30
|
-
"init.done_standalone": "✓ Initialized in standalone mode",
|
|
26
|
+
"init.done": "✓ Initialized",
|
|
31
27
|
|
|
32
28
|
// config
|
|
33
|
-
"config.description": "View
|
|
34
|
-
"config.current_mode": "Current mode: {mode}",
|
|
35
|
-
"config.switch_confirm": "Switch mode? (y/N) ",
|
|
36
|
-
"config.cc_switch_not_installed": "cc-switch is not installed",
|
|
37
|
-
"config.switched": "✓ Switched to {mode} mode",
|
|
29
|
+
"config.description": "View data source mode (deprecated)",
|
|
38
30
|
|
|
39
31
|
// list
|
|
40
32
|
"list.description": "List and select configurations",
|
|
@@ -42,7 +34,6 @@ const en: Record<TranslationKey, string> = {
|
|
|
42
34
|
"list.header": "Available configurations:",
|
|
43
35
|
"list.select": "Select configuration:",
|
|
44
36
|
"list.current_marker": "(current)",
|
|
45
|
-
"list.cancelled": "Cancelled",
|
|
46
37
|
"list.choose_number": "Enter number to switch (Enter to skip): ",
|
|
47
38
|
|
|
48
39
|
// current
|
|
@@ -137,6 +128,23 @@ const en: Record<TranslationKey, string> = {
|
|
|
137
128
|
"alias.rm_config": "Config {target}",
|
|
138
129
|
"alias.rm_choose": "Choose (1/2): ",
|
|
139
130
|
|
|
131
|
+
// sync
|
|
132
|
+
"sync.description": "Sync configurations from cc-switch",
|
|
133
|
+
"sync.no_cc_switch": "cc-switch database not detected",
|
|
134
|
+
"sync.empty": "No Claude configurations found in cc-switch",
|
|
135
|
+
"sync.done": "✓ Synced {count} configurations",
|
|
136
|
+
"sync.current": "Active: {name}",
|
|
137
|
+
"sync.no_current": "No active configuration",
|
|
138
|
+
|
|
139
|
+
// clear
|
|
140
|
+
"clear.description": "Clean up ccm data files",
|
|
141
|
+
"clear.confirm": "Delete all ccm data files? (y/N) ",
|
|
142
|
+
"clear.cancelled": "Cancelled",
|
|
143
|
+
"clear.removed": "✓ Deleted {path}",
|
|
144
|
+
"clear.clear_env": "Also clear env config in ~/.claude/settings.json? (y/N) ",
|
|
145
|
+
"clear.env_cleared": "✓ Cleared env config",
|
|
146
|
+
"clear.done": "✓ Cleanup complete",
|
|
147
|
+
|
|
140
148
|
// store errors
|
|
141
149
|
"store.db_not_found": "cc-switch database not found: {path}",
|
|
142
150
|
};
|
package/src/i18n/zh.ts
CHANGED
|
@@ -21,18 +21,10 @@ const zh = {
|
|
|
21
21
|
// init
|
|
22
22
|
"init.description": "初始化 ccm",
|
|
23
23
|
"init.cc_switch_found":"检测到 cc-switch 已安装,是否从中导入配置?(Y/n) ",
|
|
24
|
-
"init.
|
|
25
|
-
"init.current": "当前激活: {name}",
|
|
26
|
-
"init.no_current": "当前无激活配置",
|
|
27
|
-
"init.done_cc_switch": "✓ 已初始化为 cc-switch 模式",
|
|
28
|
-
"init.done_standalone": "✓ 已初始化为独立模式",
|
|
24
|
+
"init.done": "✓ 初始化完成",
|
|
29
25
|
|
|
30
26
|
// config
|
|
31
|
-
"config.description": "
|
|
32
|
-
"config.current_mode": "当前模式: {mode}",
|
|
33
|
-
"config.switch_confirm": "是否切换模式?(y/N) ",
|
|
34
|
-
"config.cc_switch_not_installed": "cc-switch 未安装",
|
|
35
|
-
"config.switched": "✓ 已切换为 {mode} 模式",
|
|
27
|
+
"config.description": "查看数据源模式(已废弃)",
|
|
36
28
|
|
|
37
29
|
// list
|
|
38
30
|
"list.description": "列出并选择配置方案",
|
|
@@ -40,7 +32,6 @@ const zh = {
|
|
|
40
32
|
"list.header": "可用配置:",
|
|
41
33
|
"list.select": "选择配置:",
|
|
42
34
|
"list.current_marker": "(当前)",
|
|
43
|
-
"list.cancelled": "已取消",
|
|
44
35
|
"list.choose_number": "输入序号切换 (回车跳过): ",
|
|
45
36
|
|
|
46
37
|
// current
|
|
@@ -135,6 +126,23 @@ const zh = {
|
|
|
135
126
|
"alias.rm_config": "配置 {target}",
|
|
136
127
|
"alias.rm_choose": "请选择 (1/2): ",
|
|
137
128
|
|
|
129
|
+
// sync
|
|
130
|
+
"sync.description": "从 cc-switch 同步配置",
|
|
131
|
+
"sync.no_cc_switch": "未检测到 cc-switch 数据库",
|
|
132
|
+
"sync.empty": "cc-switch 中没有找到 Claude 配置",
|
|
133
|
+
"sync.done": "✓ 已同步 {count} 个配置",
|
|
134
|
+
"sync.current": "当前激活: {name}",
|
|
135
|
+
"sync.no_current": "当前无激活配置",
|
|
136
|
+
|
|
137
|
+
// clear
|
|
138
|
+
"clear.description": "清理 ccm 数据文件",
|
|
139
|
+
"clear.confirm": "确认删除所有 ccm 数据文件?(y/N) ",
|
|
140
|
+
"clear.cancelled": "已取消清理",
|
|
141
|
+
"clear.removed": "✓ 已删除 {path}",
|
|
142
|
+
"clear.clear_env": "是否同时清除 ~/.claude/settings.json 中的 env 配置?(y/N) ",
|
|
143
|
+
"clear.env_cleared": "✓ 已清除 env 配置",
|
|
144
|
+
"clear.done": "✓ 清理完成",
|
|
145
|
+
|
|
138
146
|
// store errors
|
|
139
147
|
"store.db_not_found": "cc-switch 数据库不存在: {path}",
|
|
140
148
|
} as const;
|
package/src/index.ts
CHANGED
|
@@ -4,11 +4,11 @@ import { Command } from "commander";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { readRc, writeRc, getStore } from "./utils.js";
|
|
6
6
|
import { ccSwitchExists } from "./store/cc-switch.js";
|
|
7
|
-
import { readClaudeSettings, applyProfile, getSettingsPath } from "./claude.js";
|
|
7
|
+
import { readClaudeSettings, applyProfile, clearEnvFromSettings, getSettingsPath } from "./claude.js";
|
|
8
8
|
import { createInterface } from "readline";
|
|
9
9
|
import { spawnSync } from "child_process";
|
|
10
|
-
import { writeFileSync, readFileSync, unlinkSync } from "fs";
|
|
11
|
-
import { tmpdir } from "os";
|
|
10
|
+
import { writeFileSync, readFileSync, unlinkSync, existsSync } from "fs";
|
|
11
|
+
import { tmpdir, homedir } from "os";
|
|
12
12
|
import { join } from "path";
|
|
13
13
|
import { t, setLocale } from "./i18n/index.js";
|
|
14
14
|
import * as clack from "@clack/prompts";
|
|
@@ -31,14 +31,9 @@ function ask(question: string): Promise<string> {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Helper: ensure
|
|
34
|
+
// Helper: ensure store ready
|
|
35
35
|
function ensureStore() {
|
|
36
|
-
|
|
37
|
-
if (!store) {
|
|
38
|
-
console.log(chalk.yellow(t("common.not_init")));
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
return store;
|
|
36
|
+
return getStore();
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
// Helper: format env for display
|
|
@@ -172,50 +167,122 @@ program
|
|
|
172
167
|
.command("init")
|
|
173
168
|
.description(t("init.description"))
|
|
174
169
|
.action(async () => {
|
|
170
|
+
const rc = readRc();
|
|
171
|
+
writeRc({ aliases: rc?.aliases, locale: rc?.locale });
|
|
172
|
+
console.log(chalk.green(t("init.done")));
|
|
173
|
+
|
|
175
174
|
if (ccSwitchExists()) {
|
|
176
175
|
const use = await ask(t("init.cc_switch_found"));
|
|
177
176
|
if (use.toLowerCase() !== "n") {
|
|
178
|
-
writeRc({ mode: "cc-switch" });
|
|
179
177
|
const { CcSwitchStore } = await import("./store/cc-switch.js");
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
178
|
+
const { StandaloneStore } = await import("./store/standalone.js");
|
|
179
|
+
const ccStore = new CcSwitchStore();
|
|
180
|
+
const standaloneStore = new StandaloneStore();
|
|
181
|
+
const profiles = ccStore.list();
|
|
182
|
+
const current = ccStore.getCurrent();
|
|
183
|
+
|
|
184
|
+
for (const profile of profiles) {
|
|
185
|
+
standaloneStore.save(profile.name, profile.settingsConfig);
|
|
186
|
+
}
|
|
187
|
+
if (current) {
|
|
188
|
+
standaloneStore.setCurrent(current);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(chalk.green(t("sync.done", { count: String(profiles.length) })));
|
|
185
192
|
if (current) {
|
|
186
|
-
console.log(chalk.gray(t("
|
|
193
|
+
console.log(chalk.gray(t("sync.current", { name: current })));
|
|
187
194
|
} else {
|
|
188
|
-
console.log(chalk.gray(t("
|
|
195
|
+
console.log(chalk.gray(t("sync.no_current")));
|
|
189
196
|
}
|
|
190
|
-
|
|
197
|
+
ccStore.close();
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
|
-
|
|
194
|
-
writeRc({ mode: "standalone" });
|
|
195
|
-
console.log(chalk.green(t("init.done_standalone")));
|
|
196
200
|
});
|
|
197
201
|
|
|
198
|
-
// ccm config
|
|
202
|
+
// ccm config (deprecated, kept for compatibility)
|
|
199
203
|
program
|
|
200
204
|
.command("config")
|
|
201
205
|
.description(t("config.description"))
|
|
202
206
|
.action(async () => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
console.log(chalk.gray("ccm now only uses standalone mode. No configuration needed."));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// ccm sync
|
|
211
|
+
program
|
|
212
|
+
.command("sync")
|
|
213
|
+
.description(t("sync.description"))
|
|
214
|
+
.action(async () => {
|
|
215
|
+
if (!ccSwitchExists()) {
|
|
216
|
+
console.log(chalk.red(t("sync.no_cc_switch")));
|
|
206
217
|
return;
|
|
207
218
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
const { CcSwitchStore } = await import("./store/cc-switch.js");
|
|
220
|
+
const { StandaloneStore } = await import("./store/standalone.js");
|
|
221
|
+
const ccStore = new CcSwitchStore();
|
|
222
|
+
const standaloneStore = new StandaloneStore();
|
|
223
|
+
|
|
224
|
+
const profiles = ccStore.list();
|
|
225
|
+
if (profiles.length === 0) {
|
|
226
|
+
console.log(chalk.yellow(t("sync.empty")));
|
|
227
|
+
ccStore.close();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const profile of profiles) {
|
|
232
|
+
standaloneStore.save(profile.name, profile.settingsConfig);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const current = ccStore.getCurrent();
|
|
236
|
+
if (current) {
|
|
237
|
+
standaloneStore.setCurrent(current);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log(chalk.green(t("sync.done", { count: String(profiles.length) })));
|
|
241
|
+
if (current) {
|
|
242
|
+
console.log(chalk.gray(t("sync.current", { name: current })));
|
|
243
|
+
} else {
|
|
244
|
+
console.log(chalk.gray(t("sync.no_current")));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
ccStore.close();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ccm clear
|
|
251
|
+
program
|
|
252
|
+
.command("clear")
|
|
253
|
+
.description(t("clear.description"))
|
|
254
|
+
.action(async () => {
|
|
255
|
+
const confirm = await ask(t("clear.confirm"));
|
|
256
|
+
if (confirm.toLowerCase() !== "y") {
|
|
257
|
+
console.log(chalk.gray(t("clear.cancelled")));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const rcPath = join(homedir(), ".ccm", "rc.json");
|
|
262
|
+
const configPath = join(homedir(), ".ccm", "config.json");
|
|
263
|
+
|
|
264
|
+
if (existsSync(configPath)) {
|
|
265
|
+
unlinkSync(configPath);
|
|
266
|
+
console.log(chalk.green(t("clear.removed", { path: configPath })));
|
|
267
|
+
}
|
|
268
|
+
if (existsSync(rcPath)) {
|
|
269
|
+
unlinkSync(rcPath);
|
|
270
|
+
console.log(chalk.green(t("clear.removed", { path: rcPath })));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const settingsPath = getSettingsPath();
|
|
274
|
+
if (existsSync(settingsPath)) {
|
|
275
|
+
const settings = readClaudeSettings();
|
|
276
|
+
if ("env" in settings) {
|
|
277
|
+
const clearEnv = await ask(t("clear.clear_env"));
|
|
278
|
+
if (clearEnv.toLowerCase() === "y") {
|
|
279
|
+
clearEnvFromSettings();
|
|
280
|
+
console.log(chalk.green(t("clear.env_cleared")));
|
|
281
|
+
}
|
|
215
282
|
}
|
|
216
|
-
writeRc({ mode: newMode });
|
|
217
|
-
console.log(chalk.green(t("config.switched", { mode: newMode })));
|
|
218
283
|
}
|
|
284
|
+
|
|
285
|
+
console.log(chalk.green(t("clear.done")));
|
|
219
286
|
});
|
|
220
287
|
|
|
221
288
|
// ccm list
|
|
@@ -270,8 +337,7 @@ program
|
|
|
270
337
|
});
|
|
271
338
|
|
|
272
339
|
if (clack.isCancel(selected)) {
|
|
273
|
-
|
|
274
|
-
return;
|
|
340
|
+
process.exit(0);
|
|
275
341
|
}
|
|
276
342
|
switchTo(selected as string);
|
|
277
343
|
} else {
|
|
@@ -583,8 +649,7 @@ program
|
|
|
583
649
|
});
|
|
584
650
|
|
|
585
651
|
if (clack.isCancel(selected)) {
|
|
586
|
-
|
|
587
|
-
return;
|
|
652
|
+
process.exit(0);
|
|
588
653
|
}
|
|
589
654
|
name = selected as string;
|
|
590
655
|
} else {
|
|
@@ -741,8 +806,7 @@ program
|
|
|
741
806
|
});
|
|
742
807
|
|
|
743
808
|
if (clack.isCancel(selected)) {
|
|
744
|
-
|
|
745
|
-
return;
|
|
809
|
+
process.exit(0);
|
|
746
810
|
}
|
|
747
811
|
name = selected as string;
|
|
748
812
|
} else {
|
|
@@ -887,7 +951,6 @@ const SUPPORTED_LOCALES: Array<{ code: string; label: string }> = [
|
|
|
887
951
|
|
|
888
952
|
const switchLocale = (code: string) => {
|
|
889
953
|
const rc = readRc();
|
|
890
|
-
if (!rc) { console.log(chalk.yellow(t("common.not_init"))); return; }
|
|
891
954
|
rc.locale = code as "zh" | "en";
|
|
892
955
|
writeRc(rc);
|
|
893
956
|
setLocale(code as "zh" | "en");
|
|
@@ -916,7 +979,8 @@ localeCmd
|
|
|
916
979
|
initialValue: current,
|
|
917
980
|
});
|
|
918
981
|
|
|
919
|
-
if (clack.isCancel(selected)
|
|
982
|
+
if (clack.isCancel(selected)) process.exit(0);
|
|
983
|
+
if (selected === current) return;
|
|
920
984
|
switchLocale(selected as string);
|
|
921
985
|
} else {
|
|
922
986
|
console.log(chalk.bold(`\n${t("locale.list_header")}\n`));
|
package/src/store/cc-switch.ts
CHANGED
|
@@ -62,12 +62,22 @@ export class CcSwitchStore implements DataStore {
|
|
|
62
62
|
)
|
|
63
63
|
.run(JSON.stringify(settingsConfig), name);
|
|
64
64
|
} else {
|
|
65
|
+
const maxSort = this.db
|
|
66
|
+
.prepare(
|
|
67
|
+
`SELECT COALESCE(MAX(sort_index), -1) as max_sort FROM providers WHERE app_type = 'claude'`
|
|
68
|
+
)
|
|
69
|
+
.get() as { max_sort: number } | undefined;
|
|
70
|
+
const sortIndex = (maxSort?.max_sort ?? -1) + 1;
|
|
71
|
+
|
|
65
72
|
const id = crypto.randomUUID();
|
|
66
73
|
this.db
|
|
67
74
|
.prepare(
|
|
68
|
-
`INSERT INTO providers (
|
|
75
|
+
`INSERT INTO providers (
|
|
76
|
+
id, app_type, name, settings_config, website_url, category,
|
|
77
|
+
created_at, sort_index, notes, icon, icon_color, meta, is_current, in_failover_queue
|
|
78
|
+
) VALUES (?, 'claude', ?, ?, NULL, NULL, ?, ?, NULL, NULL, NULL, '{}', 0, 0)`
|
|
69
79
|
)
|
|
70
|
-
.run(id, name, JSON.stringify(settingsConfig), Date.now());
|
|
80
|
+
.run(id, name, JSON.stringify(settingsConfig), Date.now(), sortIndex);
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
|
|
@@ -81,6 +91,15 @@ export class CcSwitchStore implements DataStore {
|
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
getCurrent(): string | undefined {
|
|
94
|
+
// Prefer DB is_current so we stay in sync with cc-switch UI
|
|
95
|
+
const dbRow = this.db
|
|
96
|
+
.prepare(
|
|
97
|
+
`SELECT name FROM providers WHERE app_type = 'claude' AND is_current = 1 LIMIT 1`
|
|
98
|
+
)
|
|
99
|
+
.get() as { name: string } | undefined;
|
|
100
|
+
if (dbRow) return dbRow.name;
|
|
101
|
+
|
|
102
|
+
// Fallback to settings.json
|
|
84
103
|
if (!existsSync(SETTINGS_PATH)) return undefined;
|
|
85
104
|
try {
|
|
86
105
|
const settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
@@ -101,6 +120,18 @@ export class CcSwitchStore implements DataStore {
|
|
|
101
120
|
const profile = this.get(name);
|
|
102
121
|
if (!profile) throw new Error(t("error.not_found", { name }));
|
|
103
122
|
|
|
123
|
+
const tx = this.db.transaction(() => {
|
|
124
|
+
this.db
|
|
125
|
+
.prepare(`UPDATE providers SET is_current = 0 WHERE app_type = 'claude'`)
|
|
126
|
+
.run();
|
|
127
|
+
this.db
|
|
128
|
+
.prepare(
|
|
129
|
+
`UPDATE providers SET is_current = 1 WHERE app_type = 'claude' AND id = ?`
|
|
130
|
+
)
|
|
131
|
+
.run(profile.id);
|
|
132
|
+
});
|
|
133
|
+
tx();
|
|
134
|
+
|
|
104
135
|
if (existsSync(SETTINGS_PATH)) {
|
|
105
136
|
const settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
|
|
106
137
|
settings.currentProviderClaude = profile.id;
|
package/src/types.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -3,18 +3,21 @@ import { join } from "path";
|
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
4
|
import type { RcConfig } from "./types.js";
|
|
5
5
|
import type { DataStore } from "./store/interface.js";
|
|
6
|
-
import { CcSwitchStore } from "./store/cc-switch.js";
|
|
7
6
|
import { StandaloneStore } from "./store/standalone.js";
|
|
8
7
|
|
|
9
8
|
const CCM_DIR = join(homedir(), ".ccm");
|
|
10
9
|
const RC_PATH = join(CCM_DIR, "rc.json");
|
|
11
10
|
|
|
12
|
-
export function readRc(): RcConfig
|
|
13
|
-
if (!existsSync(RC_PATH))
|
|
11
|
+
export function readRc(): RcConfig {
|
|
12
|
+
if (!existsSync(RC_PATH)) {
|
|
13
|
+
writeRc({});
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
14
16
|
try {
|
|
15
17
|
return JSON.parse(readFileSync(RC_PATH, "utf-8"));
|
|
16
18
|
} catch {
|
|
17
|
-
|
|
19
|
+
writeRc({});
|
|
20
|
+
return {};
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -25,13 +28,6 @@ export function writeRc(rc: RcConfig): void {
|
|
|
25
28
|
writeFileSync(RC_PATH, JSON.stringify(rc, null, 2));
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
export function getStore(): DataStore
|
|
29
|
-
|
|
30
|
-
if (!rc) return null;
|
|
31
|
-
|
|
32
|
-
if (rc.mode === "cc-switch") {
|
|
33
|
-
return new CcSwitchStore();
|
|
34
|
-
} else {
|
|
35
|
-
return new StandaloneStore();
|
|
36
|
-
}
|
|
31
|
+
export function getStore(): DataStore {
|
|
32
|
+
return new StandaloneStore();
|
|
37
33
|
}
|