coding-tool-x 3.3.4 → 3.3.6
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/dist/web/assets/{Analytics-B6CWdkhx.js → Analytics-TtaduRqL.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BW6LEgd8.js → ConfigTemplates-BP2lLBMN.js} +1 -1
- package/dist/web/assets/{Home-B2B2gS2-.js → Home-CbbyopS-.js} +1 -1
- package/dist/web/assets/{PluginManager-Bqc7ldY-.js → PluginManager-HmISlyMK.js} +1 -1
- package/dist/web/assets/{ProjectList-BFdZZm_8.js → ProjectList-DoN8Hjbu.js} +1 -1
- package/dist/web/assets/{SessionList-B_Tp37kM.js → SessionList-Da8BYzNi.js} +1 -1
- package/dist/web/assets/{SkillManager-ul2rcS3o.js → SkillManager-DqLAXh9o.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Dp5Jvdtu.js → WorkspaceManager-B_TxOgPW.js} +1 -1
- package/dist/web/assets/{index-CSBDZxYn.js → index-By3mDEvx.js} +2 -2
- package/dist/web/assets/{index-DxRneGyu.css → index-CsWInMQV.css} +1 -1
- package/dist/web/index.html +2 -2
- 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/commands/update.js +21 -6
- 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/config-export.js +21 -2
- 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 +96 -28
- 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-client.js +101 -14
- package/src/server/services/mcp-service.js +98 -8
- 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
|
@@ -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) {
|
package/src/server/index.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
5
|
const { loadConfig } = require('../config/loader');
|
|
6
|
-
const { ensureStorageDirMigrated } = require('../config/paths');
|
|
6
|
+
const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
|
|
7
7
|
const { startWebSocketServer: attachWebSocketServer } = require('./websocket-server');
|
|
8
8
|
const { isPortInUse, killProcessByPort, waitForPortRelease } = require('../utils/port-helper');
|
|
9
9
|
const { isProxyConfig } = require('./services/settings-manager');
|
|
@@ -266,14 +266,10 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
266
266
|
// 自动恢复代理状态
|
|
267
267
|
function autoRestoreProxies() {
|
|
268
268
|
const config = loadConfig();
|
|
269
|
-
const os = require('os');
|
|
270
269
|
const fs = require('fs');
|
|
271
|
-
const path = require('path');
|
|
272
|
-
|
|
273
|
-
const ccToolDir = path.join(os.homedir(), '.cc-tool');
|
|
274
270
|
|
|
275
271
|
// 检查 Claude 代理状态文件
|
|
276
|
-
const claudeActiveFile =
|
|
272
|
+
const claudeActiveFile = PATHS.activeChannel.claude;
|
|
277
273
|
if (fs.existsSync(claudeActiveFile)) {
|
|
278
274
|
console.log(chalk.cyan('\n🔄 检测到 Claude 代理状态文件,正在自动启动...'));
|
|
279
275
|
const proxyPort = config.ports?.proxy || 20088;
|
|
@@ -287,7 +283,7 @@ function autoRestoreProxies() {
|
|
|
287
283
|
}
|
|
288
284
|
|
|
289
285
|
// 检查 Codex 代理状态文件
|
|
290
|
-
const codexActiveFile =
|
|
286
|
+
const codexActiveFile = PATHS.activeChannel.codex;
|
|
291
287
|
if (fs.existsSync(codexActiveFile)) {
|
|
292
288
|
console.log(chalk.cyan('\n🔄 检测到 Codex 代理状态文件,正在自动启动...'));
|
|
293
289
|
const codexProxyPort = config.ports?.codexProxy || 20089;
|
|
@@ -312,7 +308,7 @@ function autoRestoreProxies() {
|
|
|
312
308
|
}
|
|
313
309
|
|
|
314
310
|
// 检查 Gemini 代理状态文件
|
|
315
|
-
const geminiActiveFile =
|
|
311
|
+
const geminiActiveFile = PATHS.activeChannel.gemini;
|
|
316
312
|
if (fs.existsSync(geminiActiveFile)) {
|
|
317
313
|
console.log(chalk.cyan('\n🔄 检测到 Gemini 代理状态文件,正在自动启动...'));
|
|
318
314
|
const geminiProxyPort = config.ports?.geminiProxy || 20090;
|
|
@@ -332,7 +328,7 @@ function autoRestoreProxies() {
|
|
|
332
328
|
}
|
|
333
329
|
|
|
334
330
|
// 检查 OpenCode 代理状态文件
|
|
335
|
-
const opencodeActiveFile =
|
|
331
|
+
const opencodeActiveFile = PATHS.activeChannel.opencode;
|
|
336
332
|
if (fs.existsSync(opencodeActiveFile)) {
|
|
337
333
|
console.log(chalk.cyan('\n🔄 检测到 OpenCode 代理状态文件,正在自动启动...'));
|
|
338
334
|
const opencodeProxyPort = config.ports?.opencodeProxy || 20091;
|
|
@@ -12,18 +12,21 @@ const toml = require('toml');
|
|
|
12
12
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
13
13
|
const { RepoScannerBase } = require('./repo-scanner-base');
|
|
14
14
|
const { NATIVE_PATHS } = require('../../config/paths');
|
|
15
|
+
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
15
16
|
|
|
16
17
|
// 默认仓库源
|
|
17
18
|
const DEFAULT_REPOS = [];
|
|
18
19
|
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'opencode'];
|
|
19
20
|
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
20
21
|
const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
|
|
21
|
-
const
|
|
22
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
23
|
+
const CODEX_AGENTS_DIR = path.join(path.dirname(CODEX_CONFIG_PATH), 'agents');
|
|
24
|
+
const CLAUDE_AGENTS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'agents');
|
|
22
25
|
const CODEX_CONFIG_MODES = new Set(['none', 'managed', 'custom']);
|
|
23
26
|
|
|
24
27
|
const PLATFORM_CONFIG = {
|
|
25
28
|
claude: {
|
|
26
|
-
userAgentsDir:
|
|
29
|
+
userAgentsDir: CLAUDE_AGENTS_DIR,
|
|
27
30
|
projectAgentsDir: (projectPath) => path.join(projectPath, '.claude', 'agents'),
|
|
28
31
|
repoType: 'agents'
|
|
29
32
|
},
|
|
@@ -206,7 +209,7 @@ function resolveCodexConfigPath(configPath) {
|
|
|
206
209
|
if (!normalized) return '';
|
|
207
210
|
|
|
208
211
|
if (normalized.startsWith('~/')) {
|
|
209
|
-
return path.join(
|
|
212
|
+
return path.join(HOME_DIR, normalized.slice(2));
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
if (path.isAbsolute(normalized)) {
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
2
|
const { isProxyConfig } = require('./settings-manager');
|
|
3
|
+
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
5
4
|
|
|
6
5
|
function getChannelsFilePath() {
|
|
7
|
-
const dir =
|
|
6
|
+
const dir = PATHS.base;
|
|
8
7
|
if (!fs.existsSync(dir)) {
|
|
9
8
|
fs.mkdirSync(dir, { recursive: true });
|
|
10
9
|
}
|
|
11
|
-
return
|
|
10
|
+
return PATHS.channels.claude;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
function getActiveChannelIdPath() {
|
|
15
|
-
const dir =
|
|
14
|
+
const dir = PATHS.base;
|
|
16
15
|
if (!fs.existsSync(dir)) {
|
|
17
16
|
fs.mkdirSync(dir, { recursive: true });
|
|
18
17
|
}
|
|
19
|
-
return
|
|
18
|
+
return PATHS.activeChannel.claude;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
function getClaudeSettingsPath() {
|
|
23
|
-
return
|
|
22
|
+
return NATIVE_PATHS.claude.settings;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
function saveActiveChannelId(channelId) {
|
|
@@ -4,9 +4,12 @@ const os = require('os');
|
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
const toml = require('toml');
|
|
6
6
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
7
|
+
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
7
8
|
const { getCodexDir } = require('./codex-config');
|
|
8
9
|
const { injectEnvToShell, removeEnvFromShell, isProxyConfig } = require('./codex-settings-manager');
|
|
9
10
|
|
|
11
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Codex 渠道管理服务(多渠道架构)
|
|
12
15
|
*
|
|
@@ -30,7 +33,7 @@ function normalizeGatewaySourceType(value, fallback = 'codex') {
|
|
|
30
33
|
|
|
31
34
|
// 获取渠道存储文件路径
|
|
32
35
|
function getChannelsFilePath() {
|
|
33
|
-
const ccToolDir = path.join(
|
|
36
|
+
const ccToolDir = path.join(HOME_DIR, '.cc-tool');
|
|
34
37
|
if (!fs.existsSync(ccToolDir)) {
|
|
35
38
|
fs.mkdirSync(ccToolDir, { recursive: true });
|
|
36
39
|
}
|
|
@@ -2,12 +2,15 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const toml = require('toml');
|
|
5
|
+
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
6
|
+
|
|
7
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* 获取 Codex 配置目录
|
|
8
11
|
*/
|
|
9
12
|
function getCodexDir() {
|
|
10
|
-
return path.join(
|
|
13
|
+
return path.join(HOME_DIR, '.codex');
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -3,23 +3,26 @@ const path = require('path');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const toml = require('toml');
|
|
5
5
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
6
|
+
const { resolvePreferredHomeDir, isWindowsLikePlatform } = require('../../utils/home-dir');
|
|
7
|
+
|
|
8
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
6
9
|
|
|
7
10
|
// Codex 配置文件路径
|
|
8
11
|
function getConfigPath() {
|
|
9
|
-
return path.join(
|
|
12
|
+
return path.join(HOME_DIR, '.codex', 'config.toml');
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
function getAuthPath() {
|
|
13
|
-
return path.join(
|
|
16
|
+
return path.join(HOME_DIR, '.codex', 'auth.json');
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
// 备份文件路径
|
|
17
20
|
function getConfigBackupPath() {
|
|
18
|
-
return path.join(
|
|
21
|
+
return path.join(HOME_DIR, '.codex', 'config.toml.cc-tool-backup');
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
function getAuthBackupPath() {
|
|
22
|
-
return path.join(
|
|
25
|
+
return path.join(HOME_DIR, '.codex', 'auth.json.cc-tool-backup');
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
// 检查配置文件是否存在
|
|
@@ -74,7 +77,7 @@ function writeFileAtomic(filePath, content) {
|
|
|
74
77
|
|
|
75
78
|
function normalizeHomePath(filePath) {
|
|
76
79
|
const normalizedPath = String(filePath || '').replace(/\\/g, '/');
|
|
77
|
-
const normalizedHome =
|
|
80
|
+
const normalizedHome = HOME_DIR.replace(/\\/g, '/');
|
|
78
81
|
if (normalizedPath.startsWith(normalizedHome)) {
|
|
79
82
|
return `~${normalizedPath.slice(normalizedHome.length)}`;
|
|
80
83
|
}
|
|
@@ -111,11 +114,13 @@ function isPowerShellProfile(filePath) {
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
function getShellConfigCandidates() {
|
|
114
|
-
const homeDir =
|
|
117
|
+
const homeDir = HOME_DIR;
|
|
115
118
|
const shell = String(process.env.SHELL || '').toLowerCase();
|
|
116
119
|
const candidates = [];
|
|
117
120
|
|
|
118
|
-
if (process.platform
|
|
121
|
+
if (isWindowsLikePlatform(process.platform, process.env)) {
|
|
122
|
+
const oneDriveDir = process.env.OneDrive || process.env.ONEDRIVE || '';
|
|
123
|
+
|
|
119
124
|
if (shell.includes('zsh')) {
|
|
120
125
|
candidates.push(path.join(homeDir, '.zshrc'));
|
|
121
126
|
}
|
|
@@ -127,6 +132,10 @@ function getShellConfigCandidates() {
|
|
|
127
132
|
|
|
128
133
|
candidates.push(path.join(homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
129
134
|
candidates.push(path.join(homeDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
135
|
+
if (oneDriveDir) {
|
|
136
|
+
candidates.push(path.join(oneDriveDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
137
|
+
candidates.push(path.join(oneDriveDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'));
|
|
138
|
+
}
|
|
130
139
|
candidates.push(path.join(homeDir, '.bashrc'));
|
|
131
140
|
candidates.push(path.join(homeDir, '.profile'));
|
|
132
141
|
} else if (shell.includes('zsh')) {
|
|
@@ -154,7 +163,7 @@ function getShellConfigCandidates() {
|
|
|
154
163
|
|
|
155
164
|
function getShellReloadCommand(configPath) {
|
|
156
165
|
if (!configPath) {
|
|
157
|
-
return process.platform
|
|
166
|
+
return isWindowsLikePlatform(process.platform, process.env) ? '重启终端' : 'source ~/.zshrc';
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
const displayPath = normalizeHomePath(configPath);
|
|
@@ -176,7 +185,7 @@ function getShellReloadCommand(configPath) {
|
|
|
176
185
|
return 'source ~/.profile';
|
|
177
186
|
}
|
|
178
187
|
|
|
179
|
-
if (process.platform
|
|
188
|
+
if (isWindowsLikePlatform(process.platform, process.env)) {
|
|
180
189
|
return '. $PROFILE';
|
|
181
190
|
}
|
|
182
191
|
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const os = require('os');
|
|
11
10
|
const { RepoScannerBase } = require('./repo-scanner-base');
|
|
12
11
|
const { NATIVE_PATHS } = require('../../config/paths');
|
|
13
12
|
const {
|
|
@@ -19,10 +18,11 @@ const {
|
|
|
19
18
|
const DEFAULT_REPOS = [];
|
|
20
19
|
const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
|
|
21
20
|
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
21
|
+
const CLAUDE_COMMANDS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'commands');
|
|
22
22
|
|
|
23
23
|
const PLATFORM_CONFIG = {
|
|
24
24
|
claude: {
|
|
25
|
-
userCommandsDir:
|
|
25
|
+
userCommandsDir: CLAUDE_COMMANDS_DIR,
|
|
26
26
|
projectCommandsDir: (projectPath) => path.join(projectPath, '.claude', 'commands'),
|
|
27
27
|
repoType: 'commands'
|
|
28
28
|
},
|
|
@@ -5,24 +5,28 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const os = require('os');
|
|
9
8
|
const AdmZip = require('adm-zip');
|
|
10
9
|
const configTemplatesService = require('./config-templates-service');
|
|
11
10
|
const channelsService = require('./channels');
|
|
11
|
+
const codexChannelsService = require('./codex-channels');
|
|
12
|
+
const geminiChannelsService = require('./gemini-channels');
|
|
13
|
+
const opencodeChannelsService = require('./opencode-channels');
|
|
12
14
|
const { AgentsService } = require('./agents-service');
|
|
13
15
|
const { CommandsService } = require('./commands-service');
|
|
14
16
|
const { SkillService } = require('./skill-service');
|
|
17
|
+
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
15
18
|
|
|
16
19
|
const CONFIG_VERSION = '1.2.0';
|
|
17
20
|
const SKILL_FILE_ENCODING = 'base64';
|
|
18
21
|
const SKILL_IGNORE_DIRS = new Set(['.git']);
|
|
19
22
|
const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
|
|
20
|
-
const CC_TOOL_DIR =
|
|
21
|
-
const LEGACY_CC_TOOL_DIR =
|
|
22
|
-
const CLAUDE_SETTINGS_PATH =
|
|
23
|
+
const CC_TOOL_DIR = PATHS.base;
|
|
24
|
+
const LEGACY_CC_TOOL_DIR = PATHS.base;
|
|
25
|
+
const CLAUDE_SETTINGS_PATH = NATIVE_PATHS.claude.settings;
|
|
23
26
|
const LEGACY_PLUGINS_DIR = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'installed');
|
|
24
27
|
const LEGACY_PLUGINS_REGISTRY = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'registry.json');
|
|
25
|
-
const
|
|
28
|
+
const CLAUDE_PLUGINS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'plugins');
|
|
29
|
+
const NATIVE_PLUGINS_REGISTRY = path.join(CLAUDE_PLUGINS_DIR, 'installed_plugins.json');
|
|
26
30
|
const PLUGIN_IGNORE_DIRS = new Set(['.git', 'node_modules', '.DS_Store']);
|
|
27
31
|
const PLUGIN_IGNORE_FILES = new Set(['.DS_Store']);
|
|
28
32
|
const PLUGIN_SENSITIVE_PATTERNS = [
|
|
@@ -413,6 +417,14 @@ function writeTextFile(baseDir, relativePath, content, overwrite) {
|
|
|
413
417
|
return 'success';
|
|
414
418
|
}
|
|
415
419
|
|
|
420
|
+
function getAllChannelsByType() {
|
|
421
|
+
const claude = channelsService.getAllChannels() || [];
|
|
422
|
+
const codex = codexChannelsService.getChannels()?.channels || [];
|
|
423
|
+
const gemini = geminiChannelsService.getChannels()?.channels || [];
|
|
424
|
+
const opencode = opencodeChannelsService.getChannels()?.channels || [];
|
|
425
|
+
return { claude, codex, gemini, opencode };
|
|
426
|
+
}
|
|
427
|
+
|
|
416
428
|
/**
|
|
417
429
|
* 导出所有配置为JSON
|
|
418
430
|
* @returns {Object} 配置导出对象
|
|
@@ -423,8 +435,9 @@ function exportAllConfigs() {
|
|
|
423
435
|
const allConfigTemplates = configTemplatesService.getAllTemplates();
|
|
424
436
|
const customConfigTemplates = allConfigTemplates.filter(t => !t.isBuiltin);
|
|
425
437
|
|
|
426
|
-
//
|
|
427
|
-
const
|
|
438
|
+
// 获取所有频道配置(向后兼容:channels 仍保留 Claude 渠道)
|
|
439
|
+
const channelsByType = getAllChannelsByType();
|
|
440
|
+
const channels = channelsByType.claude || [];
|
|
428
441
|
|
|
429
442
|
// 获取工作区配置
|
|
430
443
|
const workspaceService = require('./workspace-service');
|
|
@@ -513,6 +526,7 @@ function exportAllConfigs() {
|
|
|
513
526
|
data: {
|
|
514
527
|
configTemplates: customConfigTemplates,
|
|
515
528
|
channels: channels || [],
|
|
529
|
+
channelsByType,
|
|
516
530
|
workspaces: workspaces || { workspaces: [] },
|
|
517
531
|
favorites: favorites || { favorites: [] },
|
|
518
532
|
agents: agents || [],
|
|
@@ -601,6 +615,7 @@ async function importConfigs(importData, options = {}) {
|
|
|
601
615
|
const {
|
|
602
616
|
configTemplates = [],
|
|
603
617
|
channels = [],
|
|
618
|
+
channelsByType = null,
|
|
604
619
|
workspaces = null,
|
|
605
620
|
favorites = null,
|
|
606
621
|
agents = [],
|
|
@@ -615,6 +630,16 @@ async function importConfigs(importData, options = {}) {
|
|
|
615
630
|
claudeHooks = null
|
|
616
631
|
} = importData.data;
|
|
617
632
|
|
|
633
|
+
const hasTypedChannels = channelsByType && typeof channelsByType === 'object';
|
|
634
|
+
const importChannelsByType = {
|
|
635
|
+
claude: hasTypedChannels && Array.isArray(channelsByType.claude)
|
|
636
|
+
? channelsByType.claude
|
|
637
|
+
: (Array.isArray(channels) ? channels : []),
|
|
638
|
+
codex: hasTypedChannels && Array.isArray(channelsByType.codex) ? channelsByType.codex : [],
|
|
639
|
+
gemini: hasTypedChannels && Array.isArray(channelsByType.gemini) ? channelsByType.gemini : [],
|
|
640
|
+
opencode: hasTypedChannels && Array.isArray(channelsByType.opencode) ? channelsByType.opencode : []
|
|
641
|
+
};
|
|
642
|
+
|
|
618
643
|
// 导入配置模板
|
|
619
644
|
for (const template of configTemplates) {
|
|
620
645
|
try {
|
|
@@ -642,29 +667,72 @@ async function importConfigs(importData, options = {}) {
|
|
|
642
667
|
}
|
|
643
668
|
}
|
|
644
669
|
|
|
645
|
-
//
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
670
|
+
// 导入频道配置(兼容旧结构 channels 和新结构 channelsByType)
|
|
671
|
+
const importTypedChannels = (type, service, createChannel, findExisting = null) => {
|
|
672
|
+
const sourceChannels = importChannelsByType[type];
|
|
673
|
+
for (const channel of sourceChannels) {
|
|
674
|
+
try {
|
|
675
|
+
const existingChannels = service.getChannels
|
|
676
|
+
? (service.getChannels()?.channels || [])
|
|
677
|
+
: (service.getAllChannels?.() || []);
|
|
678
|
+
const existing = typeof findExisting === 'function'
|
|
679
|
+
? findExisting(existingChannels, channel)
|
|
680
|
+
: existingChannels.find(c => c.id === channel.id);
|
|
681
|
+
|
|
682
|
+
if (existing && !overwrite) {
|
|
683
|
+
results.channels.skipped++;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
655
686
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
687
|
+
if (existing && overwrite) {
|
|
688
|
+
service.updateChannel(existing.id, { ...channel, id: existing.id });
|
|
689
|
+
} else {
|
|
690
|
+
createChannel(channel);
|
|
691
|
+
}
|
|
692
|
+
results.channels.success++;
|
|
693
|
+
} catch (err) {
|
|
694
|
+
console.error(`[ConfigImport] 导入${type}频道失败: ${channel.name}`, err);
|
|
695
|
+
results.channels.failed++;
|
|
661
696
|
}
|
|
662
|
-
results.channels.success++;
|
|
663
|
-
} catch (err) {
|
|
664
|
-
console.error(`[ConfigImport] 导入频道失败: ${channel.name}`, err);
|
|
665
|
-
results.channels.failed++;
|
|
666
697
|
}
|
|
667
|
-
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
importTypedChannels('claude', channelsService, channel => {
|
|
701
|
+
const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
|
|
702
|
+
channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
|
|
703
|
+
}, (existingChannels, channel) => existingChannels.find(c => c.id === channel.id));
|
|
704
|
+
|
|
705
|
+
importTypedChannels('codex', codexChannelsService, channel => {
|
|
706
|
+
const {
|
|
707
|
+
name,
|
|
708
|
+
providerKey,
|
|
709
|
+
baseUrl,
|
|
710
|
+
apiKey,
|
|
711
|
+
wireApi,
|
|
712
|
+
...extraConfig
|
|
713
|
+
} = channel;
|
|
714
|
+
codexChannelsService.createChannel(name, providerKey, baseUrl, apiKey, wireApi, extraConfig);
|
|
715
|
+
}, (existingChannels, channel) => existingChannels.find(c =>
|
|
716
|
+
(channel.id && c.id === channel.id) ||
|
|
717
|
+
(channel.providerKey && c.providerKey === channel.providerKey)
|
|
718
|
+
));
|
|
719
|
+
|
|
720
|
+
importTypedChannels('gemini', geminiChannelsService, channel => {
|
|
721
|
+
const { name, baseUrl, apiKey, model, ...extraConfig } = channel;
|
|
722
|
+
geminiChannelsService.createChannel(name, baseUrl, apiKey, model, extraConfig);
|
|
723
|
+
}, (existingChannels, channel) => existingChannels.find(c =>
|
|
724
|
+
(channel.id && c.id === channel.id) ||
|
|
725
|
+
(channel.name && c.name === channel.name)
|
|
726
|
+
));
|
|
727
|
+
|
|
728
|
+
importTypedChannels('opencode', opencodeChannelsService, channel => {
|
|
729
|
+
const { name, baseUrl, apiKey, ...extraConfig } = channel;
|
|
730
|
+
opencodeChannelsService.createChannel(name, baseUrl, apiKey, extraConfig);
|
|
731
|
+
}, (existingChannels, channel) => existingChannels.find(c =>
|
|
732
|
+
(channel.id && c.id === channel.id) ||
|
|
733
|
+
(channel.providerKey && c.providerKey === channel.providerKey) ||
|
|
734
|
+
(channel.name && channel.baseUrl && c.name === channel.name && c.baseUrl === channel.baseUrl)
|
|
735
|
+
));
|
|
668
736
|
|
|
669
737
|
// 导入工作区配置
|
|
670
738
|
if (workspaces && overwrite) {
|
|
@@ -799,7 +867,7 @@ async function importConfigs(importData, options = {}) {
|
|
|
799
867
|
results.plugins.failed++;
|
|
800
868
|
continue;
|
|
801
869
|
}
|
|
802
|
-
targetDir = path.join(
|
|
870
|
+
targetDir = path.join(CLAUDE_PLUGINS_DIR, pluginId);
|
|
803
871
|
registryPath = NATIVE_PLUGINS_REGISTRY;
|
|
804
872
|
} else {
|
|
805
873
|
console.warn(`[ConfigImport] Unknown plugin type: ${pluginType}`);
|
|
@@ -9,19 +9,20 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
-
const
|
|
12
|
+
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
13
13
|
|
|
14
14
|
// Configuration paths
|
|
15
|
-
const CC_TOOL_DIR =
|
|
15
|
+
const CC_TOOL_DIR = PATHS.base;
|
|
16
16
|
const REGISTRY_FILE = path.join(CC_TOOL_DIR, 'config-registry.json');
|
|
17
17
|
const CONFIGS_DIR = path.join(CC_TOOL_DIR, 'configs');
|
|
18
18
|
|
|
19
19
|
// Claude Code native directories
|
|
20
|
+
const CLAUDE_HOME_DIR = path.dirname(NATIVE_PATHS.claude.settings);
|
|
20
21
|
const CLAUDE_DIRS = {
|
|
21
|
-
skills: path.join(
|
|
22
|
-
commands: path.join(
|
|
23
|
-
agents: path.join(
|
|
24
|
-
plugins: path.join(
|
|
22
|
+
skills: path.join(CLAUDE_HOME_DIR, 'skills'),
|
|
23
|
+
commands: path.join(CLAUDE_HOME_DIR, 'commands'),
|
|
24
|
+
agents: path.join(CLAUDE_HOME_DIR, 'agents'),
|
|
25
|
+
plugins: path.join(CLAUDE_HOME_DIR, 'plugins')
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
// Valid config types
|
|
@@ -20,10 +20,10 @@ const os = require('os');
|
|
|
20
20
|
const toml = require('toml');
|
|
21
21
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
22
22
|
const { convertSkillToCodex, convertCommandToCodex } = require('./format-converter');
|
|
23
|
-
const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
23
|
+
const { PATHS, NATIVE_PATHS, HOME_DIR, ensureStorageDirMigrated } = require('../../config/paths');
|
|
24
24
|
|
|
25
25
|
// Paths
|
|
26
|
-
const HOME = os.homedir();
|
|
26
|
+
const HOME = HOME_DIR || os.homedir();
|
|
27
27
|
const CC_TOOL_CONFIGS = path.join(PATHS.base, 'configs');
|
|
28
28
|
const CLAUDE_CODE_DIR = path.join(HOME, '.claude');
|
|
29
29
|
const CODEX_DIR = path.join(HOME, '.codex');
|