cc-cast 1.3.5

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.
@@ -0,0 +1,145 @@
1
+ import Database from "better-sqlite3";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { existsSync, readFileSync, writeFileSync } from "fs";
5
+ import type { DataStore, Profile } from "./interface.js";
6
+ import { t } from "../i18n/index.js";
7
+
8
+ const DB_PATH = join(homedir(), ".cc-switch", "cc-switch.db");
9
+ const SETTINGS_PATH = join(homedir(), ".cc-switch", "settings.json");
10
+
11
+ export function ccSwitchExists(): boolean {
12
+ return existsSync(DB_PATH);
13
+ }
14
+
15
+ export class CcSwitchStore implements DataStore {
16
+ private db: Database.Database;
17
+
18
+ constructor() {
19
+ if (!existsSync(DB_PATH)) {
20
+ throw new Error(t("store.db_not_found", { path: DB_PATH }));
21
+ }
22
+ this.db = new Database(DB_PATH);
23
+ }
24
+
25
+ list(): Profile[] {
26
+ const rows = this.db
27
+ .prepare(
28
+ `SELECT id, name, settings_config FROM providers WHERE app_type = 'claude' ORDER BY sort_index`
29
+ )
30
+ .all() as Array<{ id: string; name: string; settings_config: string }>;
31
+
32
+ return rows.map((row) => ({
33
+ id: row.id,
34
+ name: row.name,
35
+ settingsConfig: JSON.parse(row.settings_config),
36
+ }));
37
+ }
38
+
39
+ get(name: string): Profile | undefined {
40
+ const row = this.db
41
+ .prepare(
42
+ `SELECT id, name, settings_config FROM providers WHERE app_type = 'claude' AND name = ?`
43
+ )
44
+ .get(name) as
45
+ | { id: string; name: string; settings_config: string }
46
+ | undefined;
47
+
48
+ if (!row) return undefined;
49
+ return {
50
+ id: row.id,
51
+ name: row.name,
52
+ settingsConfig: JSON.parse(row.settings_config),
53
+ };
54
+ }
55
+
56
+ save(name: string, settingsConfig: Record<string, unknown>): void {
57
+ const existing = this.get(name);
58
+ if (existing) {
59
+ this.db
60
+ .prepare(
61
+ `UPDATE providers SET settings_config = ? WHERE app_type = 'claude' AND name = ?`
62
+ )
63
+ .run(JSON.stringify(settingsConfig), name);
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
+
72
+ const id = crypto.randomUUID();
73
+ this.db
74
+ .prepare(
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)`
79
+ )
80
+ .run(id, name, JSON.stringify(settingsConfig), Date.now(), sortIndex);
81
+ }
82
+ }
83
+
84
+ remove(name: string): boolean {
85
+ const result = this.db
86
+ .prepare(
87
+ `DELETE FROM providers WHERE app_type = 'claude' AND name = ?`
88
+ )
89
+ .run(name);
90
+ return result.changes > 0;
91
+ }
92
+
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
103
+ if (!existsSync(SETTINGS_PATH)) return undefined;
104
+ try {
105
+ const settings = JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
106
+ const currentId = settings.currentProviderClaude;
107
+ if (!currentId) return undefined;
108
+ const row = this.db
109
+ .prepare(
110
+ `SELECT name FROM providers WHERE app_type = 'claude' AND id = ?`
111
+ )
112
+ .get(currentId) as { name: string } | undefined;
113
+ return row?.name;
114
+ } catch {
115
+ return undefined;
116
+ }
117
+ }
118
+
119
+ setCurrent(name: string): void {
120
+ const profile = this.get(name);
121
+ if (!profile) throw new Error(t("error.not_found", { name }));
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
+
135
+ const settings = existsSync(SETTINGS_PATH)
136
+ ? JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"))
137
+ : {};
138
+ settings.currentProviderClaude = profile.id;
139
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
140
+ }
141
+
142
+ close(): void {
143
+ this.db.close();
144
+ }
145
+ }
@@ -0,0 +1 @@
1
+ export type { DataStore, Profile } from "../types.js";
@@ -0,0 +1,80 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
+ import type { DataStore, Profile } from "./interface.js";
5
+ import { t } from "../i18n/index.js";
6
+
7
+ const CC_CAST_DIR = join(homedir(), ".cc-cast");
8
+ const CONFIG_PATH = join(CC_CAST_DIR, "config.json");
9
+
10
+ interface StandaloneConfig {
11
+ current?: string;
12
+ profiles: Record<string, Record<string, unknown>>;
13
+ }
14
+
15
+ function ensureDir(): void {
16
+ if (!existsSync(CC_CAST_DIR)) {
17
+ mkdirSync(CC_CAST_DIR, { recursive: true });
18
+ }
19
+ }
20
+
21
+ function readConfig(): StandaloneConfig {
22
+ if (!existsSync(CONFIG_PATH)) {
23
+ return { profiles: {} };
24
+ }
25
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
26
+ }
27
+
28
+ function writeConfig(config: StandaloneConfig): void {
29
+ ensureDir();
30
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
31
+ }
32
+
33
+ export class StandaloneStore implements DataStore {
34
+ list(): Profile[] {
35
+ const config = readConfig();
36
+ return Object.entries(config.profiles).map(([name, settingsConfig]) => ({
37
+ id: name,
38
+ name,
39
+ settingsConfig,
40
+ }));
41
+ }
42
+
43
+ get(name: string): Profile | undefined {
44
+ const config = readConfig();
45
+ const settingsConfig = config.profiles[name];
46
+ if (!settingsConfig) return undefined;
47
+ return { id: name, name, settingsConfig };
48
+ }
49
+
50
+ save(name: string, settingsConfig: Record<string, unknown>): void {
51
+ const config = readConfig();
52
+ config.profiles[name] = settingsConfig;
53
+ writeConfig(config);
54
+ }
55
+
56
+ remove(name: string): boolean {
57
+ const config = readConfig();
58
+ if (!(name in config.profiles)) return false;
59
+ delete config.profiles[name];
60
+ if (config.current === name) {
61
+ config.current = undefined;
62
+ }
63
+ writeConfig(config);
64
+ return true;
65
+ }
66
+
67
+ getCurrent(): string | undefined {
68
+ return readConfig().current;
69
+ }
70
+
71
+ setCurrent(name: string): void {
72
+ const config = readConfig();
73
+ if (!(name in config.profiles)) {
74
+ throw new Error(t("error.not_found", { name }));
75
+ }
76
+ config.current = name;
77
+ writeConfig(config);
78
+ }
79
+
80
+ }
package/src/types.ts ADDED
@@ -0,0 +1,19 @@
1
+ export interface Profile {
2
+ id: string;
3
+ name: string;
4
+ settingsConfig: Record<string, unknown>;
5
+ }
6
+
7
+ export interface DataStore {
8
+ list(): Profile[];
9
+ get(name: string): Profile | undefined;
10
+ save(name: string, settingsConfig: Record<string, unknown>): void;
11
+ remove(name: string): boolean;
12
+ getCurrent(): string | undefined;
13
+ setCurrent(name: string): void;
14
+ }
15
+
16
+ export interface RcConfig {
17
+ aliases?: Record<string, string>;
18
+ locale?: "zh" | "en";
19
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { homedir } from "os";
2
+ import { join } from "path";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
+ import { spawnSync } from "child_process";
5
+ import type { RcConfig } from "./types.js";
6
+ import type { DataStore } from "./store/interface.js";
7
+ import { StandaloneStore } from "./store/standalone.js";
8
+ import { CcSwitchStore, ccSwitchExists } from "./store/cc-switch.js";
9
+
10
+ const CC_CAST_DIR = join(homedir(), ".cc-cast");
11
+ const RC_PATH = join(CC_CAST_DIR, "rc.json");
12
+
13
+ export function readRc(): RcConfig {
14
+ if (!existsSync(RC_PATH)) {
15
+ writeRc({});
16
+ return {};
17
+ }
18
+ try {
19
+ return JSON.parse(readFileSync(RC_PATH, "utf-8"));
20
+ } catch {
21
+ writeRc({});
22
+ return {};
23
+ }
24
+ }
25
+
26
+ export function writeRc(rc: RcConfig): void {
27
+ if (!existsSync(CC_CAST_DIR)) {
28
+ mkdirSync(CC_CAST_DIR, { recursive: true });
29
+ }
30
+ writeFileSync(RC_PATH, JSON.stringify(rc, null, 2));
31
+ }
32
+
33
+ export function getStore(): DataStore {
34
+ if (ccSwitchExists()) {
35
+ return new CcSwitchStore();
36
+ }
37
+ return new StandaloneStore();
38
+ }
39
+
40
+ export function isCcSwitchGuiRunning(): boolean {
41
+ try {
42
+ const result = spawnSync("pgrep", ["-f", "cc-switch"], { encoding: "utf-8" });
43
+ return result.status === 0 && (result.stdout?.trim().length ?? 0) > 0;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src"]
14
+ }