@chenpu17/cc-gw 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 chenpu17
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # cc-gw
2
+
3
+ > **中文** | [English](#english)
4
+
5
+ cc-gw 是一个面向 Claude Code 与同类客户端的本地多模型网关,负责:
6
+
7
+ - 归一化 `/v1/messages` 请求并映射到不同 Provider / 模型
8
+ - 复刻 Claude API 的流式与工具调用语义
9
+ - 记录请求日志、Token(含缓存命中)、TTFT/TPOT 等运行指标
10
+ - 提供可视化 Web 管理台与守护进程 CLI
11
+
12
+ 核心组件:
13
+
14
+ | 模块 | 说明 |
15
+ | ---- | ---- |
16
+ | `@cc-gw/server` | Fastify 服务,实现协议转换、模型路由、Provider 适配与日志存储 |
17
+ | `@cc-gw/web` | React + Vite Web UI,包含仪表盘、日志面板、模型管理、系统设置 |
18
+ | `@cc-gw/cli` | CLI 守护工具,封装 start/stop/restart/status 并托管 PID/日志 |
19
+
20
+ ## 快速开始
21
+
22
+ ### 推荐方式:npm 全局安装
23
+
24
+ ```bash
25
+ npm install -g @chenpu17/cc-gw
26
+ cc-gw start --daemon --port 4100
27
+ ```
28
+
29
+ 首启会在 `~/.cc-gw/config.json` 生成配置模板,推荐直接通过 Web UI (`http://127.0.0.1:4100/ui`) 完成所有后续配置与调整。`cc-gw status`、`cc-gw stop`、`cc-gw restart` 可用于日常运维。
30
+
31
+ ### 从源码构建(开发者)
32
+
33
+ 前置:Node.js 18.18+(推荐 20 LTS)、pnpm 8+
34
+
35
+ ```bash
36
+ pnpm install
37
+ pnpm --filter @cc-gw/server build
38
+ pnpm --filter @cc-gw/web build
39
+ pnpm --filter @cc-gw/cli exec tsx index.ts start --daemon --port 4100
40
+ ```
41
+
42
+ ### 连接 Claude Code
43
+ 1. 启动 cc-gw 并确认配置中 `host` 为 `127.0.0.1`,`port` 与 CLI 启动一致。
44
+ 2. 在安装了 Claude Code 的终端设置环境变量:
45
+ ```bash
46
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:4100
47
+ claude "help me review this file"
48
+ ```
49
+ 3. cc-gw 会根据 `modelRoutes`/默认策略将 Claude 请求路由到已配置的目标模型(如 Kimi、火山 DeepSeek、OpenAI 或自建模型)。
50
+
51
+ ## 配置说明
52
+
53
+ 配置文件位于 `~/.cc-gw/config.json`:
54
+
55
+ ```json
56
+ {
57
+ "host": "127.0.0.1",
58
+ "port": 4100,
59
+ "providers": [
60
+ {
61
+ "id": "kimi",
62
+ "label": "Moonshot Kimi",
63
+ "type": "kimi",
64
+ "baseUrl": "https://api.moonshot.cn/v1",
65
+ "apiKey": "sk-...",
66
+ "models": [
67
+ { "id": "kimi-k2-0905-preview", "label": "Kimi K2" }
68
+ ]
69
+ },
70
+ {
71
+ "id": "anthropic",
72
+ "label": "Claude",
73
+ "type": "anthropic",
74
+ "baseUrl": "https://api.anthropic.com",
75
+ "apiKey": "sk-ant-...",
76
+ "models": [
77
+ { "id": "claude-3-5-sonnet-latest" }
78
+ ]
79
+ }
80
+ ],
81
+ "defaults": {
82
+ "completion": "kimi:kimi-k2-0905-preview",
83
+ "reasoning": "anthropic:claude-3-5-sonnet-latest",
84
+ "background": "kimi:kimi-k2-0905-preview",
85
+ "longContextThreshold": 60000
86
+ },
87
+ "modelRoutes": {
88
+ "claude-sonnet-4-20250514": "kimi:kimi-k2-0905-preview",
89
+ "claude-opus-4-1-20250805": "anthropic:claude-3-5-sonnet-latest"
90
+ },
91
+ "logRetentionDays": 30,
92
+ "storePayloads": true
93
+ }
94
+ ```
95
+
96
+ 字段要点(建议仍以 Web UI “系统设置 / 模型管理” 进行操作,下列仅便于理解结构):
97
+
98
+ - `providers`:定义上游服务;`type` 支持 `openai | anthropic | kimi | deepseek | custom`。
99
+ - 模型标识使用 `providerId:modelId` 形式供路由引用。
100
+ - `modelRoutes`:将 Claude 发起的模型名映射到上游模型;未命中时使用 `defaults`。
101
+ - `storePayloads`:是否在 SQLite 中压缩保存原始请求/响应(Brotli),关闭后仅保留元信息。
102
+ - 推荐通过 Web UI 的“模型管理 / 系统设置”在线编辑并热加载,无需手工修改文件。
103
+
104
+ ### 环境变量
105
+
106
+ | 变量 | 说明 |
107
+ | ---- | ---- |
108
+ | `PORT` | CLI 启动时临时覆盖监听端口 |
109
+ | `CC_GW_UI_ROOT` | 指定 Web UI 静态目录(默认自动检测 `@cc-gw/web` build 结果) |
110
+ | `CC_GW_DEBUG_ENDPOINTS` | 设为 `1` 可在日志中输出下游请求 URL |
111
+
112
+ ## Web 管理台
113
+
114
+ 访问 `http://<host>:<port>/ui`,主要页面:
115
+
116
+ - **Dashboard**:展示请求量、Token 使用、缓存命中、各模型 TTFT(Time To First Token)/TPOT(Total Processing Time)、SQLite 数据库占用。
117
+ - **请求日志**:多条件筛选(时间、Provider、模型、状态),查看压缩日志详情,支持分页导出与清理。
118
+ - **模型管理**:维护 Provider 列表、预置模型、路由策略;一键测试连通性(发送诊断 PROMPT)。
119
+ - **系统设置**:端口、日志保留策略、是否存储请求 payload、日志清理工具。
120
+
121
+ UI 支持中英文、深色/浅色主题以及移动端响应式布局,提供键盘可达性(Skip Link、焦点管理)。
122
+
123
+ ## CLI 守护
124
+
125
+ ```bash
126
+ pnpm --filter @cc-gw/cli exec tsx index.ts start [--daemon] [--port 4100]
127
+ pnpm --filter @cc-gw/cli exec tsx index.ts stop
128
+ pnpm --filter @cc-gw/cli exec tsx index.ts restart [--daemon] [--port 4100]
129
+ pnpm --filter @cc-gw/cli exec tsx index.ts status
130
+ ```
131
+
132
+ - 守护模式下 PID/日志存放于 `~/.cc-gw/cc-gw.pid` 与 `~/.cc-gw/logs/cc-gw.log`。
133
+ - `status` 会回显配置与日志路径,便于排查。
134
+
135
+ ## 数据与日志
136
+
137
+ - 数据库:`~/.cc-gw/data/gateway.db`(`better-sqlite3`)。
138
+ - `request_logs`:请求摘要、路由结果、耗时、TTFT/TPOT。
139
+ - `request_payloads`:压缩的请求/响应正文(Brotli)。
140
+ - `daily_metrics`:按日聚合的调用次数与 Token 统计。
141
+ - 日志:`~/.cc-gw/logs/cc-gw.log`,包含请求生命周期、Provider 调用与 usage 摘要(`event: usage.metrics`)。
142
+
143
+ ## 常见问题
144
+
145
+ - **Web UI 404**:请确认执行过 `pnpm --filter @cc-gw/web build`,并在 CLI 启动时自动或手动设置 `CC_GW_UI_ROOT`。
146
+ - **usage 中无 `cached_tokens`**:部分 Provider(如火山 DeepSeek)需开启 `stream_options.include_usage` 或提供专有缓存参数;cc-gw 已在支持的适配器中自动注入,如仍为 `null` 需确认上游是否支持。
147
+ - **日志数据库过大**:可在“系统设置”关闭 payload 保存或缩短保留天数;Web UI 亦提供手动清理工具。
148
+
149
+ ---
150
+
151
+ ## English
152
+
153
+ cc-gw is a local gateway tailored for Claude Code and similar Anthropic-compatible clients. It normalizes `/v1/messages`, routes traffic across heterogeneous providers, mirrors Claude’s streaming & tool semantics, and records detailed metrics that surface in a bilingual Web console and CLI daemon.
154
+
155
+ ### Highlights
156
+
157
+ | Feature | Details |
158
+ | ------- | ------- |
159
+ | Protocol adaptation | Converts Claude-style payloads into OpenAI-, Anthropic-, Kimi-, and DeepSeek-compatible requests while preserving tool calls and reasoning blocks. |
160
+ | Model routing | Maps incoming model IDs to configured upstream providers with fallbacks for long-context and background tasks. |
161
+ | Observability | Persists request logs, token usage (including cache hits), TTFT, TPOT, and daily aggregates in SQLite with Brotli-compressed payloads. |
162
+ | Web console | React + Vite UI with dashboards, filters, provider CRUD, bilingual copy, and responsive layout. |
163
+ | CLI daemon | `cc-gw` command wraps start/stop/restart/status, manages PID/log files, and scaffolds a default config on first launch. |
164
+
165
+ ### Quick Start
166
+
167
+ ```bash
168
+ npm install -g @chenpu17/cc-gw
169
+ cc-gw start --daemon --port 4100
170
+ ```
171
+
172
+ The first launch writes `~/.cc-gw/config.json`. Manage everything through the Web UI at `http://127.0.0.1:4100/ui`. Use `cc-gw status`, `cc-gw stop`, and `cc-gw restart` to control the daemon.
173
+
174
+ ### From Source (contributors)
175
+
176
+ ```bash
177
+ pnpm install
178
+ pnpm --filter @cc-gw/server build
179
+ pnpm --filter @cc-gw/web build
180
+ pnpm --filter @cc-gw/cli exec tsx index.ts start --daemon --port 4100
181
+ ```
182
+
183
+ Connect Claude Code by pointing `ANTHROPIC_BASE_URL` to your local gateway:
184
+
185
+ ```bash
186
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:4100
187
+ claude "help me review this file"
188
+ ```
189
+
190
+ ### Configuration Snapshot
191
+
192
+ - Providers include `type`, `baseUrl`, `apiKey`, and `models` descriptions.
193
+ - Model routes use `providerId:modelId` syntax to remap Claude requests.
194
+ - `storePayloads` toggles compressed body retention; disable to keep only metadata.
195
+ - Web UI allows editing without restarting; CLI restart will pick up bundle changes after rebuilds.
196
+
197
+ ### Observability & Storage
198
+
199
+ - SQLite file under `~/.cc-gw/data/gateway.db` tracks logs and aggregated metrics.
200
+ - Dashboard surfaces per-model TTFT/TPOT, cache hits, and DB size.
201
+ - Logs can be filtered/exported/cleaned directly from the UI.
202
+
203
+ ### CLI Reference
204
+
205
+ | Command | Description |
206
+ | ------- | ----------- |
207
+ | `cc-gw start [--daemon] [--port]` | Launch the Fastify server, auto-creating config if missing. |
208
+ | `cc-gw stop` | Send SIGTERM and remove stale PID files. |
209
+ | `cc-gw restart` | Convenience wrapper for stop + start. |
210
+ | `cc-gw status` | Show running status, PID, log directory, config path. |
211
+
212
+ ### Environment
213
+
214
+ | Variable | Purpose |
215
+ | -------- | ------- |
216
+ | `PORT` | Override listening port at launch. |
217
+ | `CC_GW_UI_ROOT` | Manually point to the built web assets. |
218
+ | `CC_GW_DEBUG_ENDPOINTS=1` | Log upstream provider endpoints for debugging. |
219
+
220
+ ### Tips
221
+
222
+ - Always rebuild `@cc-gw/server` and `@cc-gw/web` before restarts to ensure the daemon picks up new code.
223
+ - If cache statistics remain zero, verify whether the upstream provider exposes `cached_tokens` or equivalent details.
224
+ - Back up `~/.cc-gw/` (config, logs, SQLite DB) for migrations or disaster recovery.
225
+
226
+ ---
227
+
228
+ 欢迎通过 Issue / PR 反馈改进意见,也可以在 Web UI 的“关于”页查看贡献者信息与致谢。
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@chenpu17/cc-gw",
3
+ "version": "0.2.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "pnpm --filter server dev",
8
+ "build": "pnpm run build:server && pnpm run build:web",
9
+ "build:server": "pnpm --filter @cc-gw/server build",
10
+ "build:cli": "pnpm --filter @cc-gw/cli build",
11
+ "build:web": "pnpm --filter @cc-gw/web build",
12
+ "build:all": "pnpm run build:server && pnpm run build:cli && pnpm run build:web",
13
+ "release:bundle": "pnpm run build:all && node scripts/build-release.mjs",
14
+ "prepack": "pnpm run build:all",
15
+ "lint": "pnpm exec eslint .",
16
+ "format": "pnpm exec prettier --check .",
17
+ "format:write": "pnpm exec prettier --write .",
18
+ "typecheck": "pnpm -r exec tsc --noEmit",
19
+ "test": "pnpm exec vitest run"
20
+ },
21
+ "bin": {
22
+ "cc-gw": "src/cli/dist/index.js"
23
+ },
24
+ "files": [
25
+ "src/cli/dist",
26
+ "src/server/dist",
27
+ "src/web/dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "pnpm": {
32
+ "peerDependencyRules": {
33
+ "ignoreMissing": [
34
+ "react",
35
+ "react-dom"
36
+ ]
37
+ }
38
+ },
39
+ "packageManager": "pnpm@9.0.0",
40
+ "engines": {
41
+ "node": ">=18.18.0"
42
+ },
43
+ "description": "Self-hosted Claude Code compatible gateway with web console and CLI daemon.",
44
+ "license": "MIT",
45
+ "keywords": [
46
+ "claude",
47
+ "gateway",
48
+ "ai",
49
+ "fastify"
50
+ ],
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/chenpu17/cc-gw.git"
54
+ },
55
+ "dependencies": {
56
+ "@fastify/cors": "^9.0.1",
57
+ "@fastify/static": "^7.0.4",
58
+ "better-sqlite3": "^9.0.0",
59
+ "commander": "^12.0.0",
60
+ "colorette": "^2.0.20",
61
+ "fastify": "^4.26.2",
62
+ "open": "^10.1.0",
63
+ "tiktoken": "^1.0.21",
64
+ "undici": "^6.11.1"
65
+ },
66
+ "devDependencies": {
67
+ "@eslint/js": "^9.5.0",
68
+ "@types/node": "^20.12.7",
69
+ "eslint": "^8.57.0",
70
+ "globals": "^15.0.0",
71
+ "prettier": "^3.2.5",
72
+ "typescript": "^5.4.3",
73
+ "typescript-eslint": "^7.5.0",
74
+ "vitest": "^1.6.0"
75
+ }
76
+ }
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+
3
+ // index.ts
4
+ import { Command } from "commander";
5
+ import { spawn } from "child_process";
6
+ import fs from "fs";
7
+ import { promises as fsp } from "fs";
8
+ import os from "os";
9
+ import path from "path";
10
+ import process from "process";
11
+ import { fileURLToPath } from "url";
12
+ import { green, yellow } from "colorette";
13
+ var program = new Command();
14
+ var DEFAULT_PORT = 3456;
15
+ var HOME_DIR = path.join(os.homedir(), ".cc-gw");
16
+ var PID_FILE = path.join(HOME_DIR, "cc-gw.pid");
17
+ var LOG_DIR = path.join(HOME_DIR, "logs");
18
+ var LOG_FILE = path.join(LOG_DIR, "cc-gw.log");
19
+ var CONFIG_FILE = path.join(HOME_DIR, "config.json");
20
+ function resolveServerEntry() {
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+ const candidates = [
24
+ path.resolve(__dirname, "../../server/dist/index.js"),
25
+ path.resolve(__dirname, "../server/dist/index.js"),
26
+ path.resolve(__dirname, "../../../src/server/dist/index.js")
27
+ ];
28
+ for (const candidate of candidates) {
29
+ if (fs.existsSync(candidate)) {
30
+ return candidate;
31
+ }
32
+ }
33
+ throw new Error("Server bundle not found. \u8BF7\u5148\u6784\u5EFA @cc-gw/server (pnpm --filter @cc-gw/server build)");
34
+ }
35
+ function resolveWebDist() {
36
+ const __filename = fileURLToPath(import.meta.url);
37
+ const __dirname = path.dirname(__filename);
38
+ const candidates = [
39
+ path.resolve(__dirname, "../web/dist"),
40
+ path.resolve(__dirname, "../../web/dist"),
41
+ path.resolve(__dirname, "../../../src/web/dist"),
42
+ path.resolve(process.cwd(), "src/web/dist")
43
+ ];
44
+ for (const candidate of candidates) {
45
+ if (fs.existsSync(candidate)) {
46
+ return candidate;
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ async function ensureHomeDir() {
52
+ await fsp.mkdir(LOG_DIR, { recursive: true });
53
+ }
54
+ async function ensureConfigTemplate(port) {
55
+ try {
56
+ await fsp.access(CONFIG_FILE);
57
+ return false;
58
+ } catch {
59
+ const selectedPort = port ? Number.parseInt(port, 10) || DEFAULT_PORT : DEFAULT_PORT;
60
+ const template = {
61
+ host: "127.0.0.1",
62
+ port: selectedPort,
63
+ providers: [],
64
+ defaults: {
65
+ completion: null,
66
+ reasoning: null,
67
+ background: null,
68
+ longContextThreshold: 6e4
69
+ },
70
+ logRetentionDays: 30,
71
+ modelRoutes: {},
72
+ storePayloads: true
73
+ };
74
+ await fsp.mkdir(path.dirname(CONFIG_FILE), { recursive: true });
75
+ await fsp.writeFile(CONFIG_FILE, JSON.stringify(template, null, 2), "utf-8");
76
+ return true;
77
+ }
78
+ }
79
+ async function readPid() {
80
+ try {
81
+ const raw = await fsp.readFile(PID_FILE, "utf-8");
82
+ const pid = Number.parseInt(raw.trim(), 10);
83
+ return Number.isNaN(pid) ? null : pid;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ async function writePid(pid) {
89
+ if (pid == null)
90
+ return;
91
+ await fsp.writeFile(PID_FILE, String(pid), "utf-8");
92
+ }
93
+ async function removePid() {
94
+ try {
95
+ await fsp.unlink(PID_FILE);
96
+ } catch {
97
+ }
98
+ }
99
+ function isProcessAlive(pid) {
100
+ try {
101
+ process.kill(pid, 0);
102
+ return true;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+ async function isServiceRunning() {
108
+ const pid = await readPid();
109
+ if (!pid)
110
+ return { running: false };
111
+ if (isProcessAlive(pid)) {
112
+ return { running: true, pid };
113
+ }
114
+ await removePid();
115
+ return { running: false };
116
+ }
117
+ async function handleStart(options) {
118
+ await ensureHomeDir();
119
+ const { running, pid } = await isServiceRunning();
120
+ if (running && pid) {
121
+ console.log(yellow(`cc-gw \u5DF2\u5728\u8FD0\u884C (pid: ${pid})`));
122
+ return;
123
+ }
124
+ const configCreated = await ensureConfigTemplate(options.port);
125
+ const serverEntry = resolveServerEntry();
126
+ const env = { ...process.env };
127
+ if (options.port) {
128
+ env.PORT = options.port;
129
+ }
130
+ if (!env.CC_GW_UI_ROOT) {
131
+ const uiRoot = resolveWebDist();
132
+ if (uiRoot) {
133
+ env.CC_GW_UI_ROOT = uiRoot;
134
+ }
135
+ }
136
+ const spawnOptions = {
137
+ env,
138
+ detached: Boolean(options.daemon),
139
+ stdio: options.daemon ? [
140
+ "ignore",
141
+ fs.openSync(LOG_FILE, "a"),
142
+ fs.openSync(LOG_FILE, "a")
143
+ ] : "inherit"
144
+ };
145
+ const child = spawn(process.execPath, [serverEntry], spawnOptions);
146
+ child.on("error", (err) => {
147
+ console.error("\u542F\u52A8 cc-gw \u5931\u8D25:", err);
148
+ });
149
+ await writePid(child.pid);
150
+ if (options.daemon) {
151
+ child.unref();
152
+ console.log(green(`cc-gw \u5DF2\u4EE5\u5B88\u62A4\u8FDB\u7A0B\u65B9\u5F0F\u542F\u52A8 (pid: ${child.pid})`));
153
+ }
154
+ const effectivePort = options.port ? Number.parseInt(options.port, 10) || DEFAULT_PORT : DEFAULT_PORT;
155
+ if (configCreated) {
156
+ console.log(green(`\u5DF2\u5728 ${CONFIG_FILE} \u751F\u6210\u9ED8\u8BA4\u914D\u7F6E`));
157
+ console.log(yellow(`\u9996\u6B21\u542F\u52A8\uFF1A\u5F85\u670D\u52A1\u5C31\u7EEA\u540E\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u8BBF\u95EE http://127.0.0.1:${effectivePort}/ui \u8FDB\u884C\u914D\u7F6E\u3002`));
158
+ }
159
+ }
160
+ async function handleStop() {
161
+ const pid = await readPid();
162
+ if (!pid) {
163
+ console.log(yellow("cc-gw \u672A\u5728\u8FD0\u884C\u3002"));
164
+ return;
165
+ }
166
+ if (!isProcessAlive(pid)) {
167
+ console.log(yellow("\u68C0\u6D4B\u5230\u9648\u65E7\u7684 PID \u6587\u4EF6\uFF0C\u5DF2\u6E05\u7406\u3002"));
168
+ await removePid();
169
+ return;
170
+ }
171
+ try {
172
+ process.kill(pid, "SIGTERM");
173
+ console.log(green(`\u5DF2\u5411\u8FDB\u7A0B ${pid} \u53D1\u9001 SIGTERM`));
174
+ } catch (err) {
175
+ console.error(`\u505C\u6B62 cc-gw \u5931\u8D25: ${err.message}`);
176
+ } finally {
177
+ await removePid();
178
+ }
179
+ }
180
+ async function handleStatus() {
181
+ const { running, pid } = await isServiceRunning();
182
+ if (running && pid) {
183
+ console.log(green(`cc-gw \u6B63\u5728\u8FD0\u884C (pid: ${pid})`));
184
+ } else {
185
+ console.log(yellow("cc-gw \u672A\u5728\u8FD0\u884C\u3002"));
186
+ }
187
+ console.log(`PID \u6587\u4EF6: ${PID_FILE}`);
188
+ console.log(`\u65E5\u5FD7\u76EE\u5F55: ${LOG_DIR}`);
189
+ console.log(`\u914D\u7F6E\u6587\u4EF6: ${CONFIG_FILE}`);
190
+ }
191
+ program.name("cc-gw").description("Claude Code Gateway CLI").version("0.1.0");
192
+ program.command("start").description("\u542F\u52A8 cc-gw \u670D\u52A1").option("--daemon", "\u4EE5\u5B88\u62A4\u8FDB\u7A0B\u65B9\u5F0F\u8FD0\u884C").option("--port <port>", "\u6307\u5B9A\u670D\u52A1\u76D1\u542C\u7AEF\u53E3").action(async (options) => {
193
+ try {
194
+ await handleStart(options);
195
+ } catch (err) {
196
+ console.error(err.message);
197
+ process.exitCode = 1;
198
+ }
199
+ });
200
+ program.command("stop").description("\u505C\u6B62 cc-gw \u670D\u52A1").action(async () => {
201
+ await handleStop();
202
+ });
203
+ program.command("restart").description("\u91CD\u542F cc-gw \u670D\u52A1").option("--daemon", "\u4EE5\u5B88\u62A4\u8FDB\u7A0B\u65B9\u5F0F\u8FD0\u884C").option("--port <port>", "\u6307\u5B9A\u670D\u52A1\u76D1\u542C\u7AEF\u53E3").action(async (options) => {
204
+ await handleStop();
205
+ await handleStart(options);
206
+ });
207
+ program.command("status").description("\u67E5\u770B cc-gw \u8FD0\u884C\u72B6\u6001").action(async () => {
208
+ await handleStatus();
209
+ });
210
+ program.parseAsync(process.argv);