@daylenjeez/ccm-switch 1.2.2 → 1.2.4
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 +23 -5
- package/README.zh-CN.md +30 -12
- package/dist/i18n/en.js +15 -11
- package/dist/i18n/zh.d.ts +13 -11
- package/dist/i18n/zh.js +15 -11
- package/dist/index.js +84 -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/i18n/en.ts +17 -11
- package/src/i18n/zh.ts +17 -11
- package/src/index.ts +96 -44
- 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,22 @@ 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
|
+
Then remove the CLI:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npm uninstall -g @daylenjeez/ccm-switch
|
|
209
|
+
```
|
|
210
|
+
|
|
193
211
|
## 📄 License
|
|
194
212
|
|
|
195
213
|
[MIT](./LICENSE)
|
package/README.zh-CN.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://github.com/daylenjeez/ccm-switch/blob/main/LICENSE)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
12
|
|
|
13
|
-
English
|
|
13
|
+
[English](https://github.com/daylenjeez/ccm-switch/blob/main/README.md) | 中文文档
|
|
14
14
|
|
|
15
15
|
[安装](#-安装) · [快速开始](#-快速开始) · [命令一览](#-命令一览) · [工作原理](#%EF%B8%8F-工作原理)
|
|
16
16
|
|
|
@@ -20,12 +20,12 @@ English | [中文文档](https://github.com/daylenjeez/ccm-switch/blob/main/READ
|
|
|
20
20
|
|
|
21
21
|
## ✨ 亮点
|
|
22
22
|
|
|
23
|
-
- 🔌 **cc-switch
|
|
24
|
-
- 🧙
|
|
25
|
-
- ⚡
|
|
26
|
-
- 🛡️
|
|
27
|
-
- 🚀
|
|
28
|
-
- 🌍
|
|
23
|
+
- 🔌 **cc-switch Integration** — 直接读取 `cc-switch` 数据库,无需迁移
|
|
24
|
+
- 🧙 **Interactive Wizard** — `ccm add` 逐步引导,输入 `<` 可返回上一步
|
|
25
|
+
- ⚡ **One-command Switch** — `ccm use OpenRouter` 或 `ccm ls` 方向键选择
|
|
26
|
+
- 🛡️ **Safe Switching** — 自动保留 `language`、`permissions` 等个人设置
|
|
27
|
+
- 🚀 **Zero Config** — 直接 `ccm init`,跟着提示走,无需阅读文档
|
|
28
|
+
- 🌍 **i18n** — `ccm locale set zh/en` 切换界面语言
|
|
29
29
|
|
|
30
30
|
## 📦 安装
|
|
31
31
|
|
|
@@ -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,22 @@ 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
|
+
然后移除 CLI:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npm uninstall -g @daylenjeez/ccm-switch
|
|
209
|
+
```
|
|
210
|
+
|
|
193
211
|
## 📄 License
|
|
194
212
|
|
|
195
213
|
[MIT](./LICENSE)
|
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,19 @@ 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.done": "✓ Cleanup complete",
|
|
121
125
|
// store errors
|
|
122
126
|
"store.db_not_found": "cc-switch database not found: {path}",
|
|
123
127
|
};
|
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,17 @@ 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.done": "✓ 清理完成";
|
|
104
106
|
readonly "store.db_not_found": "cc-switch 数据库不存在: {path}";
|
|
105
107
|
};
|
|
106
108
|
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,19 @@ 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.done": "✓ 清理完成",
|
|
121
125
|
// store errors
|
|
122
126
|
"store.db_not_found": "cc-switch 数据库不存在: {path}",
|
|
123
127
|
};
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@ import { ccSwitchExists } from "./store/cc-switch.js";
|
|
|
6
6
|
import { readClaudeSettings, applyProfile } 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,98 @@ 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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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);
|
|
199
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
|
+
console.log(chalk.green(t("clear.done")));
|
|
200
244
|
});
|
|
201
245
|
// ccm list
|
|
202
246
|
program
|
|
@@ -245,8 +289,7 @@ program
|
|
|
245
289
|
initialValue: initial >= 0 ? profiles[initial].name : profiles[0].name,
|
|
246
290
|
});
|
|
247
291
|
if (clack.isCancel(selected)) {
|
|
248
|
-
|
|
249
|
-
return;
|
|
292
|
+
process.exit(0);
|
|
250
293
|
}
|
|
251
294
|
switchTo(selected);
|
|
252
295
|
}
|
|
@@ -528,8 +571,7 @@ program
|
|
|
528
571
|
options,
|
|
529
572
|
});
|
|
530
573
|
if (clack.isCancel(selected)) {
|
|
531
|
-
|
|
532
|
-
return;
|
|
574
|
+
process.exit(0);
|
|
533
575
|
}
|
|
534
576
|
name = selected;
|
|
535
577
|
}
|
|
@@ -675,8 +717,7 @@ program
|
|
|
675
717
|
options,
|
|
676
718
|
});
|
|
677
719
|
if (clack.isCancel(selected)) {
|
|
678
|
-
|
|
679
|
-
return;
|
|
720
|
+
process.exit(0);
|
|
680
721
|
}
|
|
681
722
|
name = selected;
|
|
682
723
|
}
|
|
@@ -809,10 +850,6 @@ const SUPPORTED_LOCALES = [
|
|
|
809
850
|
];
|
|
810
851
|
const switchLocale = (code) => {
|
|
811
852
|
const rc = readRc();
|
|
812
|
-
if (!rc) {
|
|
813
|
-
console.log(chalk.yellow(t("common.not_init")));
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
853
|
rc.locale = code;
|
|
817
854
|
writeRc(rc);
|
|
818
855
|
setLocale(code);
|
|
@@ -837,7 +874,9 @@ localeCmd
|
|
|
837
874
|
options,
|
|
838
875
|
initialValue: current,
|
|
839
876
|
});
|
|
840
|
-
if (clack.isCancel(selected)
|
|
877
|
+
if (clack.isCancel(selected))
|
|
878
|
+
process.exit(0);
|
|
879
|
+
if (selected === current)
|
|
841
880
|
return;
|
|
842
881
|
switchLocale(selected);
|
|
843
882
|
}
|
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/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,21 @@ 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.done": "✓ Cleanup complete",
|
|
145
|
+
|
|
140
146
|
// store errors
|
|
141
147
|
"store.db_not_found": "cc-switch database not found: {path}",
|
|
142
148
|
};
|
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,21 @@ 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.done": "✓ 清理完成",
|
|
143
|
+
|
|
138
144
|
// store errors
|
|
139
145
|
"store.db_not_found": "cc-switch 数据库不存在: {path}",
|
|
140
146
|
} 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
|
|
7
|
+
import { readClaudeSettings, applyProfile } 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,110 @@ 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
|
+
}
|
|
185
187
|
if (current) {
|
|
186
|
-
|
|
188
|
+
standaloneStore.setCurrent(current);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(chalk.green(t("sync.done", { count: String(profiles.length) })));
|
|
192
|
+
if (current) {
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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);
|
|
218
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
|
+
console.log(chalk.green(t("clear.done")));
|
|
219
274
|
});
|
|
220
275
|
|
|
221
276
|
// ccm list
|
|
@@ -270,8 +325,7 @@ program
|
|
|
270
325
|
});
|
|
271
326
|
|
|
272
327
|
if (clack.isCancel(selected)) {
|
|
273
|
-
|
|
274
|
-
return;
|
|
328
|
+
process.exit(0);
|
|
275
329
|
}
|
|
276
330
|
switchTo(selected as string);
|
|
277
331
|
} else {
|
|
@@ -583,8 +637,7 @@ program
|
|
|
583
637
|
});
|
|
584
638
|
|
|
585
639
|
if (clack.isCancel(selected)) {
|
|
586
|
-
|
|
587
|
-
return;
|
|
640
|
+
process.exit(0);
|
|
588
641
|
}
|
|
589
642
|
name = selected as string;
|
|
590
643
|
} else {
|
|
@@ -741,8 +794,7 @@ program
|
|
|
741
794
|
});
|
|
742
795
|
|
|
743
796
|
if (clack.isCancel(selected)) {
|
|
744
|
-
|
|
745
|
-
return;
|
|
797
|
+
process.exit(0);
|
|
746
798
|
}
|
|
747
799
|
name = selected as string;
|
|
748
800
|
} else {
|
|
@@ -887,7 +939,6 @@ const SUPPORTED_LOCALES: Array<{ code: string; label: string }> = [
|
|
|
887
939
|
|
|
888
940
|
const switchLocale = (code: string) => {
|
|
889
941
|
const rc = readRc();
|
|
890
|
-
if (!rc) { console.log(chalk.yellow(t("common.not_init"))); return; }
|
|
891
942
|
rc.locale = code as "zh" | "en";
|
|
892
943
|
writeRc(rc);
|
|
893
944
|
setLocale(code as "zh" | "en");
|
|
@@ -916,7 +967,8 @@ localeCmd
|
|
|
916
967
|
initialValue: current,
|
|
917
968
|
});
|
|
918
969
|
|
|
919
|
-
if (clack.isCancel(selected)
|
|
970
|
+
if (clack.isCancel(selected)) process.exit(0);
|
|
971
|
+
if (selected === current) return;
|
|
920
972
|
switchLocale(selected as string);
|
|
921
973
|
} else {
|
|
922
974
|
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
|
}
|