coding-tool-x 3.3.4 → 3.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.
- package/package.json +2 -1
- package/src/commands/doctor.js +3 -3
- package/src/commands/export-config.js +2 -2
- package/src/commands/logs.js +42 -9
- package/src/commands/port-config.js +2 -2
- package/src/commands/switch.js +2 -2
- package/src/config/default.js +4 -1
- package/src/config/loader.js +5 -2
- package/src/config/paths.js +25 -21
- package/src/reset-config.js +3 -5
- package/src/server/api/agents.js +2 -3
- package/src/server/api/claude-hooks.js +67 -10
- package/src/server/api/opencode-sessions.js +2 -2
- package/src/server/api/pm2-autostart.js +20 -8
- package/src/server/api/proxy.js +2 -3
- package/src/server/api/sessions.js +6 -6
- package/src/server/index.js +5 -9
- package/src/server/services/agents-service.js +6 -3
- package/src/server/services/channels.js +6 -7
- package/src/server/services/codex-channels.js +4 -1
- package/src/server/services/codex-config.js +4 -1
- package/src/server/services/codex-settings-manager.js +18 -9
- package/src/server/services/commands-service.js +2 -2
- package/src/server/services/config-export-service.js +7 -6
- package/src/server/services/config-registry-service.js +7 -6
- package/src/server/services/config-sync-manager.js +2 -2
- package/src/server/services/config-sync-service.js +2 -2
- package/src/server/services/env-checker.js +2 -2
- package/src/server/services/favorites.js +3 -4
- package/src/server/services/gemini-channels.js +4 -4
- package/src/server/services/gemini-config.js +2 -2
- package/src/server/services/gemini-sessions.js +3 -3
- package/src/server/services/gemini-settings-manager.js +5 -5
- package/src/server/services/mcp-service.js +7 -4
- package/src/server/services/model-detector.js +2 -2
- package/src/server/services/opencode-channels.js +5 -5
- package/src/server/services/plugins-service.js +3 -4
- package/src/server/services/prompts-service.js +7 -4
- package/src/server/services/proxy-runtime.js +2 -2
- package/src/server/services/repo-scanner-base.js +2 -2
- package/src/server/services/request-logger.js +2 -2
- package/src/server/services/security-config.js +2 -2
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/session-converter.js +9 -4
- package/src/server/services/sessions.js +8 -5
- package/src/server/services/settings-manager.js +3 -4
- package/src/server/services/skill-service.js +5 -5
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/ui-config.js +3 -4
- package/src/server/websocket-server.js +2 -2
- package/src/utils/home-dir.js +82 -0
- package/src/utils/port-helper.js +34 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coding-tool-x",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.5",
|
|
4
4
|
"description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"test:basic": "node scripts/test-basic.js",
|
|
13
13
|
"test:api": "node scripts/test-api-consistency.js",
|
|
14
14
|
"test:codex-agents": "node scripts/test-codex-agents.js",
|
|
15
|
+
"test:windows": "node scripts/test-windows-regression.js",
|
|
15
16
|
"benchmark:codex": "node scripts/benchmark-codex-loading.js",
|
|
16
17
|
"build:web": "cd src/web && npm run build",
|
|
17
18
|
"dev:web": "cd src/web && npm run dev",
|
package/src/commands/doctor.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const os = require('os');
|
|
5
4
|
const { exec } = require('child_process');
|
|
6
5
|
const { promisify } = require('util');
|
|
7
6
|
const { loadConfig, getConfigFilePath } = require('../config/loader');
|
|
7
|
+
const { PATHS, NATIVE_PATHS } = require('../config/paths');
|
|
8
8
|
const { isPortInUse } = require('../utils/port-helper');
|
|
9
9
|
|
|
10
10
|
const execAsync = promisify(exec);
|
|
@@ -185,7 +185,7 @@ async function checkPorts() {
|
|
|
185
185
|
* 检查 Claude Code 配置
|
|
186
186
|
*/
|
|
187
187
|
async function checkClaudeConfig() {
|
|
188
|
-
const settingsPath =
|
|
188
|
+
const settingsPath = NATIVE_PATHS.claude.settings;
|
|
189
189
|
const exists = fs.existsSync(settingsPath);
|
|
190
190
|
|
|
191
191
|
if (exists) {
|
|
@@ -208,7 +208,7 @@ async function checkClaudeConfig() {
|
|
|
208
208
|
* 检查日志目录
|
|
209
209
|
*/
|
|
210
210
|
async function checkLogsDirectory() {
|
|
211
|
-
const logsDir =
|
|
211
|
+
const logsDir = PATHS.logs;
|
|
212
212
|
const exists = fs.existsSync(logsDir);
|
|
213
213
|
|
|
214
214
|
if (exists) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const archiver = require('archiver');
|
|
5
4
|
const chalk = require('chalk');
|
|
5
|
+
const { HOME_DIR } = require('../config/paths');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 导出 Claude Code 配置为 ZIP 压缩包
|
|
@@ -14,7 +14,7 @@ async function exportConfig(options = {}) {
|
|
|
14
14
|
try {
|
|
15
15
|
console.log(chalk.blue('🚀 开始导出 Claude Code 配置...'));
|
|
16
16
|
|
|
17
|
-
const homeDir =
|
|
17
|
+
const homeDir = HOME_DIR;
|
|
18
18
|
const claudeDir = path.join(homeDir, '.claude');
|
|
19
19
|
const currentDir = process.cwd();
|
|
20
20
|
|
package/src/commands/logs.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const os = require('os');
|
|
5
4
|
const { spawn } = require('child_process');
|
|
5
|
+
const { PATHS } = require('../config/paths');
|
|
6
6
|
|
|
7
|
-
const LOGS_DIR =
|
|
7
|
+
const LOGS_DIR = PATHS.logs;
|
|
8
8
|
|
|
9
9
|
const LOG_FILES = {
|
|
10
10
|
ui: 'cc-tool-out.log',
|
|
@@ -145,29 +145,59 @@ function showLastLines(filePath, lines) {
|
|
|
145
145
|
/**
|
|
146
146
|
* 实时跟踪日志文件
|
|
147
147
|
*/
|
|
148
|
+
function buildFollowProcessSpec(filePath, runtimePlatform = process.platform) {
|
|
149
|
+
if (runtimePlatform === 'win32') {
|
|
150
|
+
return {
|
|
151
|
+
command: 'powershell',
|
|
152
|
+
args: [
|
|
153
|
+
'-NoProfile',
|
|
154
|
+
'-Command',
|
|
155
|
+
`Get-Content -Path '${String(filePath).replace(/'/g, "''")}' -Tail 50 -Wait`
|
|
156
|
+
],
|
|
157
|
+
options: { windowsHide: true }
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
command: 'tail',
|
|
163
|
+
args: ['-n', '50', '-f', filePath],
|
|
164
|
+
options: {}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
148
168
|
function tailFile(filePath) {
|
|
149
169
|
console.log(chalk.gray('按 Ctrl+C 停止跟踪\n'));
|
|
150
170
|
|
|
151
|
-
const
|
|
171
|
+
const followSpec = buildFollowProcessSpec(filePath);
|
|
172
|
+
const isWindows = followSpec.command.toLowerCase() === 'powershell';
|
|
173
|
+
const followProcess = spawn(followSpec.command, followSpec.args, followSpec.options);
|
|
152
174
|
|
|
153
|
-
|
|
175
|
+
followProcess.stdout.on('data', (data) => {
|
|
154
176
|
process.stdout.write(data.toString());
|
|
155
177
|
});
|
|
156
178
|
|
|
157
|
-
|
|
179
|
+
followProcess.stderr.on('data', (data) => {
|
|
158
180
|
process.stderr.write(chalk.red(data.toString()));
|
|
159
181
|
});
|
|
160
182
|
|
|
161
|
-
|
|
183
|
+
followProcess.on('error', (err) => {
|
|
162
184
|
console.error(chalk.red(`\n❌ 跟踪日志失败: ${err.message}\n`));
|
|
185
|
+
if (isWindows) {
|
|
186
|
+
console.log(chalk.gray('提示: 请确认系统可用 powershell 命令。\n'));
|
|
187
|
+
}
|
|
163
188
|
process.exit(1);
|
|
164
189
|
});
|
|
165
190
|
|
|
166
191
|
// 处理退出信号
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
const handleSigint = () => {
|
|
193
|
+
followProcess.kill();
|
|
169
194
|
console.log(chalk.gray('\n\n已停止跟踪日志\n'));
|
|
170
195
|
process.exit(0);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
process.once('SIGINT', handleSigint);
|
|
199
|
+
followProcess.once('close', () => {
|
|
200
|
+
process.removeListener('SIGINT', handleSigint);
|
|
171
201
|
});
|
|
172
202
|
}
|
|
173
203
|
|
|
@@ -257,5 +287,8 @@ function getTypeColor(type) {
|
|
|
257
287
|
}
|
|
258
288
|
|
|
259
289
|
module.exports = {
|
|
260
|
-
handleLogs
|
|
290
|
+
handleLogs,
|
|
291
|
+
_test: {
|
|
292
|
+
buildFollowProcessSpec
|
|
293
|
+
}
|
|
261
294
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// 端口配置命令
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
|
-
const os = require('os');
|
|
5
4
|
const { loadConfig, saveConfig } = require('../config/loader');
|
|
5
|
+
const { HOME_DIR } = require('../config/paths');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 配置端口
|
|
@@ -112,7 +112,7 @@ async function handlePortConfig() {
|
|
|
112
112
|
// 保存配置(保留其余字段)
|
|
113
113
|
saveConfig({
|
|
114
114
|
...config,
|
|
115
|
-
projectsDir: config.projectsDir.replace(
|
|
115
|
+
projectsDir: config.projectsDir.replace(HOME_DIR, '~'),
|
|
116
116
|
ports: config.ports,
|
|
117
117
|
});
|
|
118
118
|
|
package/src/commands/switch.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// 切换项目命令
|
|
2
2
|
const chalk = require('chalk');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const { getAvailableProjects } = require('../utils/session');
|
|
5
4
|
const { promptSelectProject } = require('../ui/prompts');
|
|
6
5
|
const { saveConfig } = require('../config/loader');
|
|
6
|
+
const { HOME_DIR } = require('../config/paths');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* 切换项目
|
|
@@ -31,7 +31,7 @@ async function switchProject(config) {
|
|
|
31
31
|
// 保存到配置文件(保留其余字段)
|
|
32
32
|
saveConfig({
|
|
33
33
|
...config,
|
|
34
|
-
projectsDir: config.projectsDir.replace(
|
|
34
|
+
projectsDir: config.projectsDir.replace(HOME_DIR, '~')
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
// 使用解析后的名称显示
|
package/src/config/default.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// 默认配置
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const { resolvePreferredHomeDir } = require('../utils/home-dir');
|
|
4
5
|
const modelMetadataConfig = require('./model-metadata.json');
|
|
5
6
|
|
|
7
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
8
|
+
|
|
6
9
|
const DEFAULT_CONFIG = {
|
|
7
|
-
projectsDir: path.join(
|
|
10
|
+
projectsDir: path.join(HOME_DIR, '.claude', 'projects'),
|
|
8
11
|
defaultProject: null,
|
|
9
12
|
maxDisplaySessions: 100,
|
|
10
13
|
pageSize: 15,
|
package/src/config/loader.js
CHANGED
|
@@ -4,11 +4,14 @@ const path = require('path');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const DEFAULT_CONFIG = require('./default');
|
|
6
6
|
const { PATHS, ensureStorageDirMigrated } = require('./paths');
|
|
7
|
+
const { resolvePreferredHomeDir } = require('../utils/home-dir');
|
|
7
8
|
const eventBus = require('../plugins/event-bus');
|
|
8
9
|
|
|
10
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
11
|
+
|
|
9
12
|
const LEGACY_CONFIG_FILES = [
|
|
10
13
|
path.join(__dirname, '../../config.json'),
|
|
11
|
-
path.join(
|
|
14
|
+
path.join(HOME_DIR, '.claude', 'config.json')
|
|
12
15
|
];
|
|
13
16
|
|
|
14
17
|
function getConfigFilePath() {
|
|
@@ -21,7 +24,7 @@ function getConfigFilePath() {
|
|
|
21
24
|
*/
|
|
22
25
|
function expandHome(filepath) {
|
|
23
26
|
if (filepath.startsWith('~')) {
|
|
24
|
-
return path.join(
|
|
27
|
+
return path.join(HOME_DIR, filepath.slice(1));
|
|
25
28
|
}
|
|
26
29
|
return filepath;
|
|
27
30
|
}
|
package/src/config/paths.js
CHANGED
|
@@ -3,16 +3,19 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const { resolvePreferredHomeDir } = require('../utils/home-dir');
|
|
7
|
+
|
|
8
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
6
9
|
|
|
7
10
|
// 基础目录
|
|
8
|
-
const CC_TOOL_BASE_DIR = path.join(
|
|
11
|
+
const CC_TOOL_BASE_DIR = path.join(HOME_DIR, '.cc-tool');
|
|
9
12
|
// 兼容旧变量名,避免外部调用方断裂
|
|
10
13
|
const CTX_BASE_DIR = CC_TOOL_BASE_DIR;
|
|
11
14
|
|
|
12
15
|
// 旧目录(升级时自动合并到 ~/.cc-tool)
|
|
13
16
|
const LEGACY_BASE_DIRS = [
|
|
14
|
-
path.join(
|
|
15
|
-
path.join(
|
|
17
|
+
path.join(HOME_DIR, '.claude', 'ctx'),
|
|
18
|
+
path.join(HOME_DIR, '.claude', 'cc-tool')
|
|
16
19
|
];
|
|
17
20
|
|
|
18
21
|
let migrationChecked = false;
|
|
@@ -127,7 +130,7 @@ const PATHS = {
|
|
|
127
130
|
notifyHook: path.join(CC_TOOL_BASE_DIR, 'notify-hook.js'),
|
|
128
131
|
|
|
129
132
|
// Skills 安装目录(注意:这个仍使用 Claude 原生路径)
|
|
130
|
-
skills: path.join(
|
|
133
|
+
skills: path.join(HOME_DIR, '.claude', 'skills'),
|
|
131
134
|
|
|
132
135
|
// MCP 配置(注意:这个仍使用 Claude 原生路径)
|
|
133
136
|
mcpConfig: path.join(CC_TOOL_BASE_DIR, 'mcp-config.json'),
|
|
@@ -148,41 +151,42 @@ const PATHS = {
|
|
|
148
151
|
const NATIVE_PATHS = {
|
|
149
152
|
// Claude Code 原生配置
|
|
150
153
|
claude: {
|
|
151
|
-
settings: path.join(
|
|
152
|
-
settingsBackup: path.join(
|
|
153
|
-
projects: path.join(
|
|
154
|
+
settings: path.join(HOME_DIR, '.claude', 'settings.json'),
|
|
155
|
+
settingsBackup: path.join(HOME_DIR, '.claude', 'settings.json.cc-tool-backup'),
|
|
156
|
+
projects: path.join(HOME_DIR, '.claude', 'projects')
|
|
154
157
|
},
|
|
155
158
|
|
|
156
159
|
// Codex 原生配置
|
|
157
160
|
codex: {
|
|
158
|
-
config: path.join(
|
|
159
|
-
configBackup: path.join(
|
|
160
|
-
auth: path.join(
|
|
161
|
-
authBackup: path.join(
|
|
162
|
-
sessions: path.join(
|
|
161
|
+
config: path.join(HOME_DIR, '.codex', 'config.toml'),
|
|
162
|
+
configBackup: path.join(HOME_DIR, '.codex', 'config.toml.cc-tool-backup'),
|
|
163
|
+
auth: path.join(HOME_DIR, '.codex', 'auth.json'),
|
|
164
|
+
authBackup: path.join(HOME_DIR, '.codex', 'auth.json.cc-tool-backup'),
|
|
165
|
+
sessions: path.join(HOME_DIR, '.codex', 'sessions')
|
|
163
166
|
},
|
|
164
167
|
|
|
165
168
|
// Gemini 原生配置
|
|
166
169
|
gemini: {
|
|
167
|
-
env: path.join(
|
|
168
|
-
envBackup: path.join(
|
|
169
|
-
tmp: path.join(
|
|
170
|
+
env: path.join(HOME_DIR, '.gemini', '.env'),
|
|
171
|
+
envBackup: path.join(HOME_DIR, '.gemini', '.env.cc-tool-backup'),
|
|
172
|
+
tmp: path.join(HOME_DIR, '.gemini', 'tmp')
|
|
170
173
|
},
|
|
171
174
|
|
|
172
175
|
// OpenCode 原生配置
|
|
173
176
|
opencode: {
|
|
174
|
-
data: path.join(
|
|
175
|
-
config: path.join(
|
|
176
|
-
sessions: path.join(
|
|
177
|
-
projects: path.join(
|
|
178
|
-
messages: path.join(
|
|
179
|
-
log: path.join(
|
|
177
|
+
data: path.join(HOME_DIR, '.local', 'share', 'opencode'),
|
|
178
|
+
config: path.join(HOME_DIR, '.config', 'opencode'),
|
|
179
|
+
sessions: path.join(HOME_DIR, '.local', 'share', 'opencode', 'storage', 'session'),
|
|
180
|
+
projects: path.join(HOME_DIR, '.local', 'share', 'opencode', 'storage', 'project'),
|
|
181
|
+
messages: path.join(HOME_DIR, '.local', 'share', 'opencode', 'storage', 'message'),
|
|
182
|
+
log: path.join(HOME_DIR, '.local', 'share', 'opencode', 'log')
|
|
180
183
|
}
|
|
181
184
|
};
|
|
182
185
|
|
|
183
186
|
module.exports = {
|
|
184
187
|
PATHS,
|
|
185
188
|
NATIVE_PATHS,
|
|
189
|
+
HOME_DIR,
|
|
186
190
|
CTX_BASE_DIR,
|
|
187
191
|
CC_TOOL_BASE_DIR,
|
|
188
192
|
LEGACY_BASE_DIRS,
|
package/src/reset-config.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { PATHS, ensureStorageDirMigrated } = require('./config/paths');
|
|
2
|
+
const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('./config/paths');
|
|
5
3
|
|
|
6
4
|
// 恢复配置到默认状态
|
|
7
5
|
async function resetConfig() {
|
|
@@ -25,8 +23,8 @@ async function resetConfig() {
|
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
// 2. 检查并恢复 settings.json
|
|
28
|
-
const settingsPath =
|
|
29
|
-
const backupPath =
|
|
26
|
+
const settingsPath = NATIVE_PATHS.claude.settings;
|
|
27
|
+
const backupPath = NATIVE_PATHS.claude.settingsBackup;
|
|
30
28
|
|
|
31
29
|
if (fs.existsSync(backupPath)) {
|
|
32
30
|
console.log('发现备份文件,正在恢复...');
|
package/src/server/api/agents.js
CHANGED
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
const express = require('express');
|
|
8
8
|
const fs = require('fs');
|
|
9
|
-
const os = require('os');
|
|
10
9
|
const path = require('path');
|
|
11
10
|
const { AgentsService } = require('../services/agents-service');
|
|
12
|
-
const { PATHS } = require('../../config/paths');
|
|
11
|
+
const { PATHS, HOME_DIR } = require('../../config/paths');
|
|
13
12
|
|
|
14
13
|
const router = express.Router();
|
|
15
14
|
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'opencode'];
|
|
16
15
|
const agentServices = new Map();
|
|
17
|
-
const DEFAULT_PROJECT_ALLOWED_ROOTS = [
|
|
16
|
+
const DEFAULT_PROJECT_ALLOWED_ROOTS = [HOME_DIR, process.cwd()];
|
|
18
17
|
|
|
19
18
|
function isSupportedPlatform(platform) {
|
|
20
19
|
return SUPPORTED_PLATFORMS.includes(platform);
|
|
@@ -5,18 +5,21 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const https = require('https');
|
|
7
7
|
const http = require('http');
|
|
8
|
+
const { resolvePreferredHomeDir, normalizeWindowsHomePath } = require('../../utils/home-dir');
|
|
9
|
+
|
|
10
|
+
// 检测操作系统
|
|
11
|
+
const platform = os.platform(); // 'darwin' | 'win32' | 'linux'
|
|
12
|
+
|
|
13
|
+
const HOME_DIR = resolvePreferredHomeDir(platform, process.env, os.homedir());
|
|
8
14
|
|
|
9
15
|
// Claude settings.json 路径
|
|
10
|
-
const CLAUDE_SETTINGS_PATH = path.join(
|
|
16
|
+
const CLAUDE_SETTINGS_PATH = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
11
17
|
|
|
12
18
|
// UI 配置路径(记录用户是否主动关闭过、飞书配置等)
|
|
13
|
-
const UI_CONFIG_PATH = path.join(
|
|
19
|
+
const UI_CONFIG_PATH = path.join(HOME_DIR, '.cc-tool', 'ui-config.json');
|
|
14
20
|
|
|
15
21
|
// 通知脚本路径(用于飞书通知)
|
|
16
|
-
const NOTIFY_SCRIPT_PATH = path.join(
|
|
17
|
-
|
|
18
|
-
// 检测操作系统
|
|
19
|
-
const platform = os.platform(); // 'darwin' | 'win32' | 'linux'
|
|
22
|
+
const NOTIFY_SCRIPT_PATH = path.join(HOME_DIR, '.cc-tool', 'notify-hook.js');
|
|
20
23
|
|
|
21
24
|
// 读取 Claude settings.json
|
|
22
25
|
function readClaudeSettings() {
|
|
@@ -197,6 +200,42 @@ function parseNotifyTypeMarker(command) {
|
|
|
197
200
|
return marker ? marker[2].toLowerCase() : null;
|
|
198
201
|
}
|
|
199
202
|
|
|
203
|
+
function getStopHookCommand(settings) {
|
|
204
|
+
const hooks = settings?.hooks?.Stop;
|
|
205
|
+
if (!Array.isArray(hooks) || hooks.length === 0) {
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
208
|
+
const firstHook = hooks[0]?.hooks;
|
|
209
|
+
if (!Array.isArray(firstHook) || firstHook.length === 0) {
|
|
210
|
+
return '';
|
|
211
|
+
}
|
|
212
|
+
return firstHook[0]?.command || '';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function normalizePathForCompare(rawPath) {
|
|
216
|
+
return String(rawPath || '').replace(/\\/g, '/');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function shouldRepairStopHook(settings, expectedScriptPath = NOTIFY_SCRIPT_PATH, fileExists = fs.existsSync) {
|
|
220
|
+
const command = getStopHookCommand(settings);
|
|
221
|
+
if (!command || !command.includes('notify-hook.js')) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const markerType = parseNotifyTypeMarker(command);
|
|
226
|
+
if (!markerType) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const normalizedCommand = normalizePathForCompare(command);
|
|
231
|
+
const normalizedExpected = normalizePathForCompare(expectedScriptPath);
|
|
232
|
+
if (!normalizedCommand.includes(normalizedExpected)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return !fileExists(expectedScriptPath);
|
|
237
|
+
}
|
|
238
|
+
|
|
200
239
|
function buildStopHookCommand(type) {
|
|
201
240
|
const notifyType = type === 'dialog' ? 'dialog' : 'notification';
|
|
202
241
|
return `node "${NOTIFY_SCRIPT_PATH}" --cc-notify-type=${notifyType}`;
|
|
@@ -302,7 +341,9 @@ function updateStopHook(systemNotification, feishu) {
|
|
|
302
341
|
}
|
|
303
342
|
} else {
|
|
304
343
|
// 生成并写入通知脚本
|
|
305
|
-
writeNotifyScript({ systemNotification, feishu })
|
|
344
|
+
if (!writeNotifyScript({ systemNotification, feishu })) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
306
347
|
|
|
307
348
|
// 更新 Stop hook 指向通知脚本
|
|
308
349
|
settings.hooks = settings.hooks || {};
|
|
@@ -335,9 +376,22 @@ function initDefaultHooks() {
|
|
|
335
376
|
const settings = readClaudeSettings();
|
|
336
377
|
const currentStatus = parseStopHookStatus(settings);
|
|
337
378
|
|
|
338
|
-
// 如果已经有 Stop hook
|
|
379
|
+
// 如果已经有 Stop hook 配置,优先尝试自愈旧路径,再决定是否跳过
|
|
339
380
|
if (currentStatus.enabled) {
|
|
340
|
-
|
|
381
|
+
if (shouldRepairStopHook(settings)) {
|
|
382
|
+
const systemNotification = {
|
|
383
|
+
enabled: true,
|
|
384
|
+
type: currentStatus.type || 'notification'
|
|
385
|
+
};
|
|
386
|
+
const feishu = getFeishuConfig();
|
|
387
|
+
if (updateStopHook(systemNotification, feishu)) {
|
|
388
|
+
console.log('[Claude Hooks] 检测到旧版 Stop hook 路径,已自动修复');
|
|
389
|
+
} else {
|
|
390
|
+
console.warn('[Claude Hooks] Stop hook 路径修复失败,保留原配置');
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
console.log('[Claude Hooks] 已存在 Stop hook 配置,跳过初始化');
|
|
394
|
+
}
|
|
341
395
|
return;
|
|
342
396
|
}
|
|
343
397
|
|
|
@@ -499,5 +553,8 @@ module.exports._test = {
|
|
|
499
553
|
generateSystemNotificationCommand,
|
|
500
554
|
parseStopHookStatus,
|
|
501
555
|
parseNotifyTypeMarker,
|
|
502
|
-
buildStopHookCommand
|
|
556
|
+
buildStopHookCommand,
|
|
557
|
+
normalizeWindowsHomePath,
|
|
558
|
+
resolvePreferredHomeDir,
|
|
559
|
+
shouldRepairStopHook
|
|
503
560
|
};
|
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
} = require('../services/opencode-sessions');
|
|
15
15
|
const { loadAliases } = require('../services/alias');
|
|
16
16
|
const { broadcastLog } = require('../websocket-server');
|
|
17
|
-
const
|
|
17
|
+
const { HOME_DIR } = require('../../config/paths');
|
|
18
18
|
|
|
19
19
|
function isNotFoundError(error) {
|
|
20
20
|
if (!error || !error.message) {
|
|
@@ -295,7 +295,7 @@ module.exports = (config) => {
|
|
|
295
295
|
|
|
296
296
|
const projects = getProjects();
|
|
297
297
|
const project = projects.find(p => p.name === projectName);
|
|
298
|
-
const cwd = session.directory || project?.fullPath ||
|
|
298
|
+
const cwd = session.directory || project?.fullPath || HOME_DIR;
|
|
299
299
|
const command = `opencode -r ${sessionId}`;
|
|
300
300
|
const quotedCwd = `"${String(cwd).replace(/"/g, '\\"')}"`;
|
|
301
301
|
const copyCommand = `cd ${quotedCwd} && ${command}`;
|
|
@@ -3,11 +3,18 @@ const { exec } = require('child_process');
|
|
|
3
3
|
const { promisify } = require('util');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
|
-
const os = require('os');
|
|
7
6
|
const pm2 = require('pm2');
|
|
7
|
+
const { HOME_DIR } = require('../../config/paths');
|
|
8
8
|
|
|
9
9
|
const execAsync = promisify(exec);
|
|
10
10
|
|
|
11
|
+
function getExecOptions(timeout = 30000, runtimePlatform = process.platform) {
|
|
12
|
+
if (runtimePlatform === 'win32') {
|
|
13
|
+
return { timeout };
|
|
14
|
+
}
|
|
15
|
+
return { shell: '/bin/bash', timeout };
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* Check if PM2 autostart is enabled
|
|
13
20
|
* by looking for PM2 startup script in system
|
|
@@ -18,7 +25,7 @@ async function checkAutoStartStatus() {
|
|
|
18
25
|
|
|
19
26
|
if (platform === 'darwin') {
|
|
20
27
|
// macOS - check for LaunchDaemon
|
|
21
|
-
const launchDaemonsPath = path.join(
|
|
28
|
+
const launchDaemonsPath = path.join(HOME_DIR, 'Library/LaunchDaemons');
|
|
22
29
|
const pm2Files = fs.existsSync(launchDaemonsPath)
|
|
23
30
|
? fs.readdirSync(launchDaemonsPath).filter(f => f.includes('pm2'))
|
|
24
31
|
: [];
|
|
@@ -27,11 +34,11 @@ async function checkAutoStartStatus() {
|
|
|
27
34
|
} else if (platform === 'linux') {
|
|
28
35
|
// Linux - check for systemd service
|
|
29
36
|
const systemdPath = '/etc/systemd/system/pm2-root.service';
|
|
30
|
-
const userSystemdPath = path.join(
|
|
37
|
+
const userSystemdPath = path.join(HOME_DIR, '.config/systemd/user/pm2-*.service');
|
|
31
38
|
|
|
32
39
|
const rootExists = fs.existsSync(systemdPath);
|
|
33
|
-
const userExists = fs.existsSync(path.join(
|
|
34
|
-
fs.readdirSync(path.join(
|
|
40
|
+
const userExists = fs.existsSync(path.join(HOME_DIR, '.config/systemd/user')) &&
|
|
41
|
+
fs.readdirSync(path.join(HOME_DIR, '.config/systemd/user')).some(f => f.includes('pm2'));
|
|
35
42
|
|
|
36
43
|
return { enabled: rootExists || userExists, platform: 'linux' };
|
|
37
44
|
} else if (platform === 'win32') {
|
|
@@ -104,7 +111,7 @@ async function enableAutoStart() {
|
|
|
104
111
|
|
|
105
112
|
console.log(`Running startup command: ${command}`);
|
|
106
113
|
|
|
107
|
-
exec(command,
|
|
114
|
+
exec(command, getExecOptions(30000), (execErr, stdout, stderr) => {
|
|
108
115
|
pm2.disconnect();
|
|
109
116
|
|
|
110
117
|
if (execErr) {
|
|
@@ -163,7 +170,7 @@ async function disableAutoStart() {
|
|
|
163
170
|
|
|
164
171
|
console.log(`Running unstartup command: ${command}`);
|
|
165
172
|
|
|
166
|
-
exec(command,
|
|
173
|
+
exec(command, getExecOptions(30000), (execErr, stdout, stderr) => {
|
|
167
174
|
pm2.disconnect();
|
|
168
175
|
|
|
169
176
|
if (execErr) {
|
|
@@ -195,7 +202,7 @@ async function disableAutoStart() {
|
|
|
195
202
|
});
|
|
196
203
|
}
|
|
197
204
|
|
|
198
|
-
|
|
205
|
+
function createPm2AutostartRouter() {
|
|
199
206
|
const router = express.Router();
|
|
200
207
|
|
|
201
208
|
/**
|
|
@@ -266,4 +273,9 @@ module.exports = () => {
|
|
|
266
273
|
});
|
|
267
274
|
|
|
268
275
|
return router;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = createPm2AutostartRouter;
|
|
279
|
+
module.exports._test = {
|
|
280
|
+
getExecOptions
|
|
269
281
|
};
|
package/src/server/api/proxy.js
CHANGED
|
@@ -12,10 +12,9 @@ const {
|
|
|
12
12
|
} = require('../services/settings-manager');
|
|
13
13
|
const { getAllChannels } = require('../services/channels');
|
|
14
14
|
const { clearAllLogs } = require('../websocket-server');
|
|
15
|
-
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
15
|
+
const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
|
-
const os = require('os');
|
|
19
18
|
|
|
20
19
|
function sanitizeChannelForResponse(channel) {
|
|
21
20
|
if (!channel) return null;
|
|
@@ -285,7 +284,7 @@ router.post('/stop', async (req, res) => {
|
|
|
285
284
|
|
|
286
285
|
// 3. 删除备份文件和active-channel.json
|
|
287
286
|
if (hasBackup()) {
|
|
288
|
-
const backupPath =
|
|
287
|
+
const backupPath = NATIVE_PATHS.claude.settingsBackup;
|
|
289
288
|
if (fs.existsSync(backupPath)) {
|
|
290
289
|
fs.unlinkSync(backupPath);
|
|
291
290
|
console.log('✅ Removed backup file');
|
|
@@ -2,11 +2,12 @@ const express = require('express');
|
|
|
2
2
|
const router = express.Router();
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
-
const os = require('os');
|
|
6
5
|
const readline = require('readline');
|
|
7
6
|
const { getSessionsForProject, deleteSession, forkSession, saveSessionOrder, parseRealProjectPath, searchSessions, getRecentSessions, searchSessionsAcrossProjects, hasActualMessages } = require('../services/sessions');
|
|
8
7
|
const { loadAliases } = require('../services/alias');
|
|
9
8
|
const { broadcastLog } = require('../websocket-server');
|
|
9
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
10
|
+
const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
|
|
10
11
|
|
|
11
12
|
module.exports = (config) => {
|
|
12
13
|
// GET /api/sessions/search/global - Search sessions across all projects
|
|
@@ -120,12 +121,12 @@ module.exports = (config) => {
|
|
|
120
121
|
const year = now.getFullYear();
|
|
121
122
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
122
123
|
const day = String(now.getDate()).padStart(2, '0');
|
|
123
|
-
sessionDir = path.join(
|
|
124
|
+
sessionDir = path.join(NATIVE_PATHS.codex.sessions, String(year), month, day);
|
|
124
125
|
sessionFile = path.join(sessionDir, `${newSessionId}.jsonl`);
|
|
125
126
|
} else if (toolType === 'gemini') {
|
|
126
127
|
// Gemini: ~/.gemini/tmp/{hash}/chats/{sessionId}.json
|
|
127
128
|
const pathHash = crypto.createHash('sha256').update(fullPath).digest('hex');
|
|
128
|
-
sessionDir = path.join(
|
|
129
|
+
sessionDir = path.join(NATIVE_PATHS.gemini.tmp, pathHash, 'chats');
|
|
129
130
|
sessionFile = path.join(sessionDir, `${newSessionId}.json`);
|
|
130
131
|
} else {
|
|
131
132
|
return res.status(400).json({ error: 'Invalid toolType. Must be claude, codex, or gemini' });
|
|
@@ -243,7 +244,7 @@ module.exports = (config) => {
|
|
|
243
244
|
let sessionFile = null;
|
|
244
245
|
const possiblePaths = [
|
|
245
246
|
path.join(fullPath, '.claude', 'sessions', sessionId + '.jsonl'),
|
|
246
|
-
path.join(
|
|
247
|
+
path.join(CLAUDE_PROJECTS_DIR, projectName, sessionId + '.jsonl')
|
|
247
248
|
];
|
|
248
249
|
|
|
249
250
|
console.log(`[Messages API] Trying paths:`, possiblePaths);
|
|
@@ -423,7 +424,6 @@ module.exports = (config) => {
|
|
|
423
424
|
const { projectName, sessionId } = req.params;
|
|
424
425
|
const path = require('path');
|
|
425
426
|
const fs = require('fs');
|
|
426
|
-
const os = require('os');
|
|
427
427
|
|
|
428
428
|
// Parse real project path (important for cross-project sessions)
|
|
429
429
|
const { fullPath } = parseRealProjectPath(projectName);
|
|
@@ -436,7 +436,7 @@ module.exports = (config) => {
|
|
|
436
436
|
const possiblePaths = [
|
|
437
437
|
projectSessionFile,
|
|
438
438
|
// Location 2: User's .claude/projects directory (ClaudeCode default)
|
|
439
|
-
path.join(
|
|
439
|
+
path.join(CLAUDE_PROJECTS_DIR, projectName, sessionId + '.jsonl')
|
|
440
440
|
];
|
|
441
441
|
|
|
442
442
|
for (const testPath of possiblePaths) {
|