@adversity/coding-tool-x 3.0.6 → 3.1.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/CHANGELOG.md +38 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +92 -13
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/ui.js +8 -1
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +39 -2
- package/src/config/loader.js +74 -8
- package/src/config/paths.js +105 -33
- package/src/index.js +67 -4
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +32 -19
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +17 -3
- package/src/server/index.js +164 -48
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +30 -19
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +70 -12
- package/src/server/services/codex-channels.js +61 -23
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +26 -12
- package/src/server/services/env-manager.js +126 -18
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +37 -15
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +132 -3
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-BxudHPiX.js +0 -1
- package/dist/web/assets/index-D2VfwJBa.js +0 -14
- package/dist/web/assets/index-oXBzu0bd.css +0 -41
- package/dist/web/assets/naive-ui-DT-Uur8K.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/permissions.js +0 -385
- package/src/server/services/permission-templates-service.js +0 -308
package/dist/web/index.html
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
<link rel="icon" href="/favicon.ico">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>CC-TOOL - ClaudeCode增强工作助手</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/naive-ui-
|
|
13
|
-
<link rel="
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-lAkrRC3h.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/markdown-C9MYpaSi.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vue-vendor-DqyWIXEb.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/vendors-CO3Upi1d.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/naive-ui-CSrLusZZ.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/icons-kcfLIMBB.js">
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Ufv5rCa5.css">
|
|
14
16
|
</head>
|
|
15
17
|
<body>
|
|
16
18
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adversity/coding-tool-x",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/ctx.js",
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "npm run test:basic && npm run test:api",
|
|
12
|
+
"test:basic": "node scripts/test-basic.js",
|
|
13
|
+
"test:api": "node scripts/test-api-consistency.js",
|
|
12
14
|
"build:web": "cd src/web && npm run build",
|
|
13
15
|
"dev:web": "cd src/web && npm run dev",
|
|
14
16
|
"dev:server": "nodemon"
|
package/src/commands/channels.js
CHANGED
|
@@ -47,6 +47,22 @@ function getChannelServices(cliType) {
|
|
|
47
47
|
updateChannel,
|
|
48
48
|
getProxyStatus: getGeminiProxyStatus
|
|
49
49
|
};
|
|
50
|
+
} else if (cliType === 'opencode') {
|
|
51
|
+
const {
|
|
52
|
+
getChannels,
|
|
53
|
+
createChannel,
|
|
54
|
+
updateChannel
|
|
55
|
+
} = require('../server/services/opencode-channels');
|
|
56
|
+
const { getOpenCodeProxyStatus } = require('../server/opencode-proxy-server');
|
|
57
|
+
return {
|
|
58
|
+
getAllChannels: () => {
|
|
59
|
+
const result = getChannels();
|
|
60
|
+
return Array.isArray(result?.channels) ? result.channels : [];
|
|
61
|
+
},
|
|
62
|
+
createChannel,
|
|
63
|
+
updateChannel,
|
|
64
|
+
getProxyStatus: getOpenCodeProxyStatus
|
|
65
|
+
};
|
|
50
66
|
}
|
|
51
67
|
}
|
|
52
68
|
|
|
@@ -62,6 +78,11 @@ async function handleChannelManagement() {
|
|
|
62
78
|
const config = loadConfig();
|
|
63
79
|
const cliType = config.currentCliType || 'claude';
|
|
64
80
|
const services = getChannelServices(cliType);
|
|
81
|
+
if (!services || typeof services.createChannel !== 'function') {
|
|
82
|
+
console.log(chalk.red(`当前 CLI 类型 (${cliType}) 暂不支持添加渠道`));
|
|
83
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
65
86
|
if (!services || typeof services.getAllChannels !== 'function') {
|
|
66
87
|
console.log(chalk.red(`当前 CLI 类型 (${cliType}) 暂不支持渠道管理`));
|
|
67
88
|
await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
|
|
@@ -200,6 +221,31 @@ async function handleAddChannel() {
|
|
|
200
221
|
modelAnswer.model.trim(),
|
|
201
222
|
{ websiteUrl: answers.websiteUrl.trim() || undefined }
|
|
202
223
|
);
|
|
224
|
+
} else if (cliType === 'opencode') {
|
|
225
|
+
const extraAnswers = await inquirer.prompt([
|
|
226
|
+
{
|
|
227
|
+
type: 'input',
|
|
228
|
+
name: 'wireApi',
|
|
229
|
+
message: 'Wire API (默认: openai):',
|
|
230
|
+
default: 'openai'
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
type: 'input',
|
|
234
|
+
name: 'model',
|
|
235
|
+
message: '默认模型(可选,直接回车跳过):'
|
|
236
|
+
}
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
channel = services.createChannel(
|
|
240
|
+
answers.name.trim(),
|
|
241
|
+
answers.baseUrl.trim(),
|
|
242
|
+
answers.apiKey.trim(),
|
|
243
|
+
{
|
|
244
|
+
wireApi: extraAnswers.wireApi.trim() || 'openai',
|
|
245
|
+
model: extraAnswers.model.trim() || null,
|
|
246
|
+
websiteUrl: answers.websiteUrl.trim() || undefined
|
|
247
|
+
}
|
|
248
|
+
);
|
|
203
249
|
}
|
|
204
250
|
|
|
205
251
|
console.log(chalk.green(`\n✅ 渠道添加成功: ${channel.name}\n`));
|
|
@@ -237,7 +283,8 @@ async function handleChannelStatus() {
|
|
|
237
283
|
const sources = [
|
|
238
284
|
{ key: 'claude', label: 'Claude (Claude Code)' },
|
|
239
285
|
{ key: 'codex', label: 'Codex (OpenAI)' },
|
|
240
|
-
{ key: 'gemini', label: 'Gemini' }
|
|
286
|
+
{ key: 'gemini', label: 'Gemini' },
|
|
287
|
+
{ key: 'opencode', label: 'OpenCode' }
|
|
241
288
|
];
|
|
242
289
|
|
|
243
290
|
sources.forEach((source) => {
|
package/src/commands/cli-type.js
CHANGED
|
@@ -6,7 +6,8 @@ const { loadConfig, saveConfig } = require('../config/loader');
|
|
|
6
6
|
const CLI_TYPES = {
|
|
7
7
|
claude: { name: 'Claude Code', color: 'cyan' },
|
|
8
8
|
codex: { name: 'Codex', color: 'green' },
|
|
9
|
-
gemini: { name: 'Gemini', color: 'magenta' }
|
|
9
|
+
gemini: { name: 'Gemini', color: 'magenta' },
|
|
10
|
+
opencode: { name: 'OpenCode', color: 'yellow' }
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -20,8 +21,9 @@ async function handleSwitchCliType() {
|
|
|
20
21
|
|
|
21
22
|
const config = loadConfig();
|
|
22
23
|
const currentType = config.currentCliType || 'claude';
|
|
24
|
+
const currentTypeInfo = CLI_TYPES[currentType] || CLI_TYPES.claude;
|
|
23
25
|
|
|
24
|
-
console.log(chalk.gray(`当前类型: ${
|
|
26
|
+
console.log(chalk.gray(`当前类型: ${currentTypeInfo.name}\n`));
|
|
25
27
|
|
|
26
28
|
// 构建类型选项
|
|
27
29
|
const choices = Object.entries(CLI_TYPES).map(([type, info]) => {
|
package/src/commands/daemon.js
CHANGED
|
@@ -2,6 +2,8 @@ const pm2 = require('pm2');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { loadConfig } = require('../config/loader');
|
|
5
|
+
const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
|
|
6
|
+
const { findProcessByPort } = require('../utils/port-helper');
|
|
5
7
|
|
|
6
8
|
const PM2_APP_NAME = 'cc-tool';
|
|
7
9
|
|
|
@@ -50,6 +52,47 @@ async function getCCToolProcess() {
|
|
|
50
52
|
return list.find(proc => proc.name === PM2_APP_NAME);
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
function sleep(ms) {
|
|
56
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isPortOwnedByPid(port, pid) {
|
|
60
|
+
if (!pid || pid <= 0) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const pids = findProcessByPort(port);
|
|
64
|
+
return pids.includes(String(pid));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function waitForServiceReady(port, timeoutMs = 6000, intervalMs = 300) {
|
|
68
|
+
const startAt = Date.now();
|
|
69
|
+
let lastProcess = null;
|
|
70
|
+
let stablePassCount = 0;
|
|
71
|
+
|
|
72
|
+
while (Date.now() - startAt < timeoutMs) {
|
|
73
|
+
lastProcess = await getCCToolProcess();
|
|
74
|
+
if (lastProcess && lastProcess.pm2_env.status === 'online') {
|
|
75
|
+
const ownsPort = isPortOwnedByPid(port, lastProcess.pid);
|
|
76
|
+
if (ownsPort) {
|
|
77
|
+
// 连续多次检查通过,避免“瞬时 online 但马上崩溃”的误报
|
|
78
|
+
stablePassCount += 1;
|
|
79
|
+
} else {
|
|
80
|
+
stablePassCount = 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (stablePassCount >= 3) {
|
|
84
|
+
return { ready: true, process: lastProcess };
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
stablePassCount = 0;
|
|
88
|
+
}
|
|
89
|
+
await sleep(intervalMs);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
lastProcess = await getCCToolProcess();
|
|
93
|
+
return { ready: false, process: lastProcess };
|
|
94
|
+
}
|
|
95
|
+
|
|
53
96
|
/**
|
|
54
97
|
* 启动服务(后台)
|
|
55
98
|
*/
|
|
@@ -70,13 +113,20 @@ async function handleStart() {
|
|
|
70
113
|
}
|
|
71
114
|
|
|
72
115
|
const config = loadConfig();
|
|
73
|
-
const port = config.ports?.webUI ||
|
|
116
|
+
const port = config.ports?.webUI || 19999;
|
|
117
|
+
|
|
118
|
+
// 检查是否启用 LAN 访问 (--host 标志)
|
|
119
|
+
const enableHost = process.argv.includes('--host');
|
|
120
|
+
const pmArgs = ['ui', '--daemon'];
|
|
121
|
+
if (enableHost) {
|
|
122
|
+
pmArgs.push('--host');
|
|
123
|
+
}
|
|
74
124
|
|
|
75
125
|
// 启动 PM2 进程
|
|
76
126
|
pm2.start({
|
|
77
127
|
name: PM2_APP_NAME,
|
|
78
128
|
script: path.join(__dirname, '../index.js'),
|
|
79
|
-
args:
|
|
129
|
+
args: pmArgs,
|
|
80
130
|
interpreter: 'node',
|
|
81
131
|
autorestart: true,
|
|
82
132
|
max_memory_restart: '500M',
|
|
@@ -84,19 +134,44 @@ async function handleStart() {
|
|
|
84
134
|
NODE_ENV: 'production',
|
|
85
135
|
CC_TOOL_PORT: port
|
|
86
136
|
},
|
|
87
|
-
output: path.join(require('os').homedir(), '.
|
|
88
|
-
error: path.join(require('os').homedir(), '.
|
|
137
|
+
output: path.join(require('os').homedir(), '.cc-tool/logs/cc-tool-out.log'),
|
|
138
|
+
error: path.join(require('os').homedir(), '.cc-tool/logs/cc-tool-error.log'),
|
|
89
139
|
merge_logs: true,
|
|
90
140
|
log_date_format: 'YYYY-MM-DD HH:mm:ss'
|
|
91
|
-
}, (err) => {
|
|
141
|
+
}, async (err) => {
|
|
92
142
|
if (err) {
|
|
93
143
|
console.error(chalk.red('\n❌ 启动服务失败:'), err.message);
|
|
94
144
|
disconnectPM2();
|
|
95
145
|
process.exit(1);
|
|
96
146
|
}
|
|
97
147
|
|
|
148
|
+
try {
|
|
149
|
+
const readyState = await waitForServiceReady(port);
|
|
150
|
+
if (!readyState.ready) {
|
|
151
|
+
const statusText = readyState.process?.pm2_env?.status || 'unknown';
|
|
152
|
+
console.error(chalk.red('\n❌ Coding-Tool 服务启动失败,进程未就绪\n'));
|
|
153
|
+
console.error(chalk.gray(`PM2 状态: ${statusText}`));
|
|
154
|
+
console.error(chalk.yellow('💡 请使用 ctx logs ui 查看详细日志\n'));
|
|
155
|
+
|
|
156
|
+
pm2.delete(PM2_APP_NAME, () => {
|
|
157
|
+
pm2.dump(() => {
|
|
158
|
+
disconnectPM2();
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
} catch (checkError) {
|
|
165
|
+
console.error(chalk.red('\n❌ 启动后健康检查失败:'), checkError.message);
|
|
166
|
+
disconnectPM2();
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
98
170
|
console.log(chalk.green('\n✅ Coding-Tool 服务已启动(后台运行)\n'));
|
|
99
171
|
console.log(chalk.gray(`Web UI: http://localhost:${port}`));
|
|
172
|
+
if (enableHost) {
|
|
173
|
+
console.log(chalk.yellow(`⚠️ LAN 访问已启用 (http://<your-ip>:${port})`));
|
|
174
|
+
}
|
|
100
175
|
console.log(chalk.gray('\n可以安全关闭此终端窗口'));
|
|
101
176
|
console.log(chalk.gray('\n常用命令:'));
|
|
102
177
|
console.log(chalk.gray(' ') + chalk.cyan('ctx status') + chalk.gray(' - 查看服务状态'));
|
|
@@ -208,7 +283,7 @@ async function handleStatus() {
|
|
|
208
283
|
console.log(chalk.bold('📱 Web UI 服务:'));
|
|
209
284
|
if (existing && existing.pm2_env.status === 'online') {
|
|
210
285
|
console.log(chalk.green(' ✅ 状态: 运行中'));
|
|
211
|
-
console.log(chalk.gray(` 🌐 地址: http://localhost:${config.ports?.webUI ||
|
|
286
|
+
console.log(chalk.gray(` 🌐 地址: http://localhost:${config.ports?.webUI || 19999}`));
|
|
212
287
|
console.log(chalk.gray(` 🔑 进程 ID: ${existing.pid}`));
|
|
213
288
|
console.log(chalk.gray(` ⏱️ 运行时长: ${formatUptime(existing.pm2_env.pm_uptime)}`));
|
|
214
289
|
console.log(chalk.gray(` 💾 内存使用: ${formatMemory(existing.monit?.memory)}`));
|
|
@@ -219,21 +294,25 @@ async function handleStatus() {
|
|
|
219
294
|
|
|
220
295
|
// 代理服务状态(从运行时文件检测)
|
|
221
296
|
const fs = require('fs');
|
|
222
|
-
|
|
223
|
-
const claudeActive = fs.existsSync(
|
|
224
|
-
const codexActive = fs.existsSync(
|
|
225
|
-
const geminiActive = fs.existsSync(
|
|
297
|
+
ensureStorageDirMigrated();
|
|
298
|
+
const claudeActive = fs.existsSync(PATHS.activeChannel.claude);
|
|
299
|
+
const codexActive = fs.existsSync(PATHS.activeChannel.codex);
|
|
300
|
+
const geminiActive = fs.existsSync(PATHS.activeChannel.gemini);
|
|
301
|
+
const opencodeActive = fs.existsSync(PATHS.activeChannel.opencode);
|
|
226
302
|
|
|
227
303
|
console.log(chalk.bold('\n🔌 代理服务:'));
|
|
228
304
|
|
|
229
305
|
console.log(chalk.gray(' Claude: ') + (claudeActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
|
|
230
|
-
chalk.gray(` (http://localhost:${config.ports?.proxy ||
|
|
306
|
+
chalk.gray(` (http://localhost:${config.ports?.proxy || 20088})`));
|
|
231
307
|
|
|
232
308
|
console.log(chalk.gray(' Codex: ') + (codexActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
|
|
233
|
-
chalk.gray(` (http://localhost:${config.ports?.codexProxy ||
|
|
309
|
+
chalk.gray(` (http://localhost:${config.ports?.codexProxy || 20089})`));
|
|
234
310
|
|
|
235
311
|
console.log(chalk.gray(' Gemini: ') + (geminiActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
|
|
236
|
-
chalk.gray(` (http://localhost:${config.ports?.geminiProxy ||
|
|
312
|
+
chalk.gray(` (http://localhost:${config.ports?.geminiProxy || 20090})`));
|
|
313
|
+
|
|
314
|
+
console.log(chalk.gray(' OpenCode:') + (opencodeActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
|
|
315
|
+
chalk.gray(` (http://localhost:${config.ports?.opencodeProxy || 20091})`));
|
|
237
316
|
|
|
238
317
|
console.log(chalk.bold('\n💡 提示:'));
|
|
239
318
|
console.log(chalk.gray(' • 代理服务通过 Web UI 界面控制'));
|
package/src/commands/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { exec } = require('child_process');
|
|
6
6
|
const { promisify } = require('util');
|
|
7
|
-
const { loadConfig } = require('../config/loader');
|
|
7
|
+
const { loadConfig, getConfigFilePath } = require('../config/loader');
|
|
8
8
|
const { isPortInUse } = require('../utils/port-helper');
|
|
9
9
|
|
|
10
10
|
const execAsync = promisify(exec);
|
|
@@ -114,7 +114,7 @@ async function checkNodeVersion() {
|
|
|
114
114
|
* 检查配置文件
|
|
115
115
|
*/
|
|
116
116
|
async function checkConfigFiles() {
|
|
117
|
-
const configPath =
|
|
117
|
+
const configPath = getConfigFilePath();
|
|
118
118
|
const exists = fs.existsSync(configPath);
|
|
119
119
|
|
|
120
120
|
if (exists) {
|
|
@@ -130,7 +130,7 @@ async function checkConfigFiles() {
|
|
|
130
130
|
name: '配置文件',
|
|
131
131
|
status: 'fail',
|
|
132
132
|
message: '配置文件存在但解析失败',
|
|
133
|
-
suggestion: '使用 ctx
|
|
133
|
+
suggestion: '使用 ctx reset 重置配置'
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
} else {
|
|
@@ -149,10 +149,11 @@ async function checkConfigFiles() {
|
|
|
149
149
|
async function checkPorts() {
|
|
150
150
|
const config = loadConfig();
|
|
151
151
|
const ports = {
|
|
152
|
-
'Web UI': config.ports?.webUI ||
|
|
153
|
-
'Claude Proxy': config.ports?.proxy ||
|
|
154
|
-
'Codex Proxy': config.ports?.codexProxy ||
|
|
155
|
-
'Gemini Proxy': config.ports?.geminiProxy ||
|
|
152
|
+
'Web UI': config.ports?.webUI || 19999,
|
|
153
|
+
'Claude Proxy': config.ports?.proxy || 20088,
|
|
154
|
+
'Codex Proxy': config.ports?.codexProxy || 20089,
|
|
155
|
+
'Gemini Proxy': config.ports?.geminiProxy || 20090,
|
|
156
|
+
'OpenCode Proxy': config.ports?.opencodeProxy || 20091
|
|
156
157
|
};
|
|
157
158
|
|
|
158
159
|
const conflicts = [];
|
|
@@ -175,7 +176,7 @@ async function checkPorts() {
|
|
|
175
176
|
name: '端口检查',
|
|
176
177
|
status: 'warning',
|
|
177
178
|
message: `以下端口被占用: ${conflicts.join(', ')}`,
|
|
178
|
-
suggestion: '
|
|
179
|
+
suggestion: '如果服务正在运行这是正常的;否则运行 ctx port 修改端口'
|
|
179
180
|
};
|
|
180
181
|
}
|
|
181
182
|
}
|
|
@@ -207,7 +208,7 @@ async function checkClaudeConfig() {
|
|
|
207
208
|
* 检查日志目录
|
|
208
209
|
*/
|
|
209
210
|
async function checkLogsDirectory() {
|
|
210
|
-
const logsDir = path.join(os.homedir(), '.
|
|
211
|
+
const logsDir = path.join(os.homedir(), '.cc-tool', 'logs');
|
|
211
212
|
const exists = fs.existsSync(logsDir);
|
|
212
213
|
|
|
213
214
|
if (exists) {
|
package/src/commands/list.js
CHANGED
|
@@ -89,7 +89,7 @@ async function listRecentSessionsAcrossProjects(config, limit = null) {
|
|
|
89
89
|
const maxSessions = limit || 15; // 默认显示15条最新对话
|
|
90
90
|
const spinner = ora('加载最新对话...').start();
|
|
91
91
|
|
|
92
|
-
const sessions = getRecentSessions(config, maxSessions);
|
|
92
|
+
const sessions = await getRecentSessions(config, maxSessions);
|
|
93
93
|
|
|
94
94
|
if (sessions.length === 0) {
|
|
95
95
|
spinner.fail('暂无会话记录');
|
package/src/commands/logs.js
CHANGED
|
@@ -4,13 +4,14 @@ const path = require('path');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { spawn } = require('child_process');
|
|
6
6
|
|
|
7
|
-
const LOGS_DIR = path.join(os.homedir(), '.
|
|
7
|
+
const LOGS_DIR = path.join(os.homedir(), '.cc-tool', 'logs');
|
|
8
8
|
|
|
9
9
|
const LOG_FILES = {
|
|
10
10
|
ui: 'cc-tool-out.log',
|
|
11
11
|
claude: 'claude-proxy.log',
|
|
12
12
|
codex: 'codex-proxy.log',
|
|
13
|
-
gemini: 'gemini-proxy.log'
|
|
13
|
+
gemini: 'gemini-proxy.log',
|
|
14
|
+
opencode: 'opencode-proxy.log'
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -46,7 +47,7 @@ async function handleLogs(type = null, options = {}) {
|
|
|
46
47
|
const logFile = LOG_FILES[type];
|
|
47
48
|
if (!logFile) {
|
|
48
49
|
console.error(chalk.red(`\n❌ 无效的日志类型: ${type}\n`));
|
|
49
|
-
console.log(chalk.gray('支持的类型: ui, claude, codex, gemini\n'));
|
|
50
|
+
console.log(chalk.gray('支持的类型: ui, claude, codex, gemini, opencode\n'));
|
|
50
51
|
process.exit(1);
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -249,7 +250,8 @@ function getTypeColor(type) {
|
|
|
249
250
|
ui: chalk.blue,
|
|
250
251
|
claude: chalk.green,
|
|
251
252
|
codex: chalk.cyan,
|
|
252
|
-
gemini: chalk.magenta
|
|
253
|
+
gemini: chalk.magenta,
|
|
254
|
+
opencode: chalk.yellow
|
|
253
255
|
};
|
|
254
256
|
return colors[type] || chalk.gray;
|
|
255
257
|
}
|
|
@@ -8,6 +8,11 @@ const { loadConfig, saveConfig } = require('../config/loader');
|
|
|
8
8
|
* 配置端口
|
|
9
9
|
*/
|
|
10
10
|
async function handlePortConfig() {
|
|
11
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
12
|
+
console.log(chalk.yellow('\n当前环境不支持交互式端口配置,请在本地终端中运行 `ctx port`。\n'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
console.clear();
|
|
12
17
|
console.log(chalk.bold.cyan('\n╔═══════════════════════════════════════╗'));
|
|
13
18
|
console.log(chalk.bold.cyan('║ 端口配置 ║'));
|
|
@@ -18,8 +23,9 @@ async function handlePortConfig() {
|
|
|
18
23
|
console.log(chalk.cyan('当前端口配置:'));
|
|
19
24
|
console.log(chalk.gray(`• Web UI 页面端口: ${config.ports.webUI} (同时用于 WebSocket)`));
|
|
20
25
|
console.log(chalk.gray(`• Claude 代理端口: ${config.ports.proxy}`));
|
|
21
|
-
console.log(chalk.gray(`• Codex 代理端口: ${config.ports.codexProxy ||
|
|
22
|
-
console.log(chalk.gray(`• Gemini 代理端口: ${config.ports.geminiProxy ||
|
|
26
|
+
console.log(chalk.gray(`• Codex 代理端口: ${config.ports.codexProxy || 20089}`));
|
|
27
|
+
console.log(chalk.gray(`• Gemini 代理端口: ${config.ports.geminiProxy || 20090}`));
|
|
28
|
+
console.log(chalk.gray(`• OpenCode 代理端口: ${config.ports.opencodeProxy || 20091}\n`));
|
|
23
29
|
|
|
24
30
|
console.log(chalk.yellow('说明:'));
|
|
25
31
|
console.log(chalk.gray('• 端口范围: 1024-65535'));
|
|
@@ -57,7 +63,7 @@ async function handlePortConfig() {
|
|
|
57
63
|
type: 'input',
|
|
58
64
|
name: 'codexProxy',
|
|
59
65
|
message: 'Codex 代理服务端口:',
|
|
60
|
-
default: config.ports.codexProxy ||
|
|
66
|
+
default: config.ports.codexProxy || 20089,
|
|
61
67
|
validate: (input) => {
|
|
62
68
|
const port = parseInt(input);
|
|
63
69
|
if (isNaN(port) || port < 1024 || port > 65535) {
|
|
@@ -70,7 +76,20 @@ async function handlePortConfig() {
|
|
|
70
76
|
type: 'input',
|
|
71
77
|
name: 'geminiProxy',
|
|
72
78
|
message: 'Gemini 代理服务端口:',
|
|
73
|
-
default: config.ports.geminiProxy ||
|
|
79
|
+
default: config.ports.geminiProxy || 20090,
|
|
80
|
+
validate: (input) => {
|
|
81
|
+
const port = parseInt(input);
|
|
82
|
+
if (isNaN(port) || port < 1024 || port > 65535) {
|
|
83
|
+
return '端口必须是 1024-65535 之间的数字';
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'opencodeProxy',
|
|
91
|
+
message: 'OpenCode 代理服务端口:',
|
|
92
|
+
default: config.ports.opencodeProxy || 20091,
|
|
74
93
|
validate: (input) => {
|
|
75
94
|
const port = parseInt(input);
|
|
76
95
|
if (isNaN(port) || port < 1024 || port > 65535) {
|
|
@@ -87,6 +106,7 @@ async function handlePortConfig() {
|
|
|
87
106
|
proxy: parseInt(answers.proxy),
|
|
88
107
|
codexProxy: parseInt(answers.codexProxy),
|
|
89
108
|
geminiProxy: parseInt(answers.geminiProxy),
|
|
109
|
+
opencodeProxy: parseInt(answers.opencodeProxy),
|
|
90
110
|
};
|
|
91
111
|
|
|
92
112
|
// 保存配置(保留其余字段)
|
|
@@ -11,12 +11,17 @@ const CHANNEL_CONFIG = {
|
|
|
11
11
|
codex: {
|
|
12
12
|
name: 'Codex',
|
|
13
13
|
icon: '🔵',
|
|
14
|
-
apiPath: '/api/codex
|
|
14
|
+
apiPath: '/api/codex/proxy'
|
|
15
15
|
},
|
|
16
16
|
gemini: {
|
|
17
17
|
name: 'Gemini',
|
|
18
18
|
icon: '🟣',
|
|
19
|
-
apiPath: '/api/gemini
|
|
19
|
+
apiPath: '/api/gemini/proxy'
|
|
20
|
+
},
|
|
21
|
+
opencode: {
|
|
22
|
+
name: 'OpenCode',
|
|
23
|
+
icon: '🟠',
|
|
24
|
+
apiPath: '/api/opencode/proxy'
|
|
20
25
|
}
|
|
21
26
|
};
|
|
22
27
|
|
|
@@ -25,7 +30,7 @@ const CHANNEL_CONFIG = {
|
|
|
25
30
|
*/
|
|
26
31
|
function httpRequest(method, path, data = null) {
|
|
27
32
|
const config = loadConfig();
|
|
28
|
-
const port = config.ports?.webUI ||
|
|
33
|
+
const port = config.ports?.webUI || 19999;
|
|
29
34
|
|
|
30
35
|
return new Promise((resolve, reject) => {
|
|
31
36
|
const postData = data ? JSON.stringify(data) : null;
|
|
@@ -80,7 +85,7 @@ function httpRequest(method, path, data = null) {
|
|
|
80
85
|
*/
|
|
81
86
|
async function checkUIService() {
|
|
82
87
|
try {
|
|
83
|
-
await httpRequest('GET', '/api/
|
|
88
|
+
await httpRequest('GET', '/api/proxy/status');
|
|
84
89
|
return true;
|
|
85
90
|
} catch (err) {
|
|
86
91
|
return false;
|
|
@@ -94,7 +99,7 @@ async function handleProxyStart(channel) {
|
|
|
94
99
|
const channelInfo = CHANNEL_CONFIG[channel];
|
|
95
100
|
if (!channelInfo) {
|
|
96
101
|
console.error(chalk.red(`\n❌ 无效的渠道类型: ${channel}\n`));
|
|
97
|
-
console.log(chalk.gray('支持的渠道: claude, codex, gemini\n'));
|
|
102
|
+
console.log(chalk.gray('支持的渠道: claude, codex, gemini, opencode\n'));
|
|
98
103
|
process.exit(1);
|
|
99
104
|
}
|
|
100
105
|
|
|
@@ -203,7 +208,8 @@ async function handleProxyStatus(channel) {
|
|
|
203
208
|
|
|
204
209
|
try {
|
|
205
210
|
const response = await httpRequest('GET', `${channelInfo.apiPath}/status`);
|
|
206
|
-
const
|
|
211
|
+
const payload = response.data || {};
|
|
212
|
+
const status = payload.proxy || payload;
|
|
207
213
|
|
|
208
214
|
console.log(chalk.bold.cyan(`\n╔══════════════════════════════════════╗`));
|
|
209
215
|
console.log(chalk.bold.cyan(`║ ${channelInfo.name} 代理服务状态 ║`));
|
package/src/commands/search.js
CHANGED
|
@@ -13,7 +13,7 @@ const { loadAliases } = require('../server/services/alias');
|
|
|
13
13
|
async function searchSessionsAcrossProjects(config, keyword) {
|
|
14
14
|
const spinner = ora(`🔍 正在搜索 "${keyword}"...`).start();
|
|
15
15
|
|
|
16
|
-
const projects = getProjects(config);
|
|
16
|
+
const projects = await getProjects(config);
|
|
17
17
|
const aliases = loadAliases();
|
|
18
18
|
const allResults = [];
|
|
19
19
|
|
package/src/commands/security.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const
|
|
4
|
+
const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
|
|
5
5
|
|
|
6
|
-
const SECURITY_FILE = path.join(
|
|
6
|
+
const SECURITY_FILE = path.join(PATHS.base, 'security.json');
|
|
7
7
|
|
|
8
8
|
function showSecurityHelp() {
|
|
9
9
|
console.log(chalk.yellow('\n🔐 安全设置命令:'));
|
|
@@ -13,6 +13,7 @@ function showSecurityHelp() {
|
|
|
13
13
|
|
|
14
14
|
async function handleSecurityReset() {
|
|
15
15
|
console.log(chalk.cyan('\n🔐 安全设置 - 关闭访问密码\n'));
|
|
16
|
+
ensureStorageDirMigrated();
|
|
16
17
|
|
|
17
18
|
if (!fs.existsSync(SECURITY_FILE)) {
|
|
18
19
|
console.log(chalk.yellow('⚠️ 未检测到安全配置文件'));
|