coding-tool-x 3.5.6 → 3.5.8

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.
Files changed (69) hide show
  1. package/README.md +17 -0
  2. package/bin/ctx.js +6 -1
  3. package/dist/web/assets/{Analytics-CRNCHeui.js → Analytics-BzoNzfbi.js} +2 -2
  4. package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
  5. package/dist/web/assets/{ConfigTemplates-C0erJdo2.js → ConfigTemplates-O4ikBt1o.js} +1 -1
  6. package/dist/web/assets/{Home-CL5z6Q4d.js → Home-BQjsnblU.js} +1 -1
  7. package/dist/web/assets/Home-qzk118Of.css +1 -0
  8. package/dist/web/assets/{PluginManager-hDx0XMO_.js → PluginManager-DS_DJnVc.js} +1 -1
  9. package/dist/web/assets/ProjectList-CqYDtsHx.js +1 -0
  10. package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
  11. package/dist/web/assets/SessionList-CfPtcq6Y.css +1 -0
  12. package/dist/web/assets/SessionList-DMlLtMCz.js +1 -0
  13. package/dist/web/assets/{SkillManager-D6Vwpajh.js → SkillManager-DpNE02r0.js} +1 -1
  14. package/dist/web/assets/{WorkspaceManager-C3TjeOPy.js → WorkspaceManager-DMY7_SHh.js} +1 -1
  15. package/dist/web/assets/icons-CEq2hYB-.js +1 -0
  16. package/dist/web/assets/index-Clf0l3wc.js +2 -0
  17. package/dist/web/assets/index-Dih_bOsv.css +1 -0
  18. package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
  19. package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
  20. package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
  21. package/dist/web/index.html +7 -7
  22. package/docs/Caddyfile.example +19 -0
  23. package/docs/reverse-proxy-https.md +57 -0
  24. package/package.json +2 -1
  25. package/src/commands/daemon.js +33 -5
  26. package/src/commands/export-config.js +6 -6
  27. package/src/commands/ui.js +12 -3
  28. package/src/config/default.js +2 -6
  29. package/src/config/loader.js +2 -2
  30. package/src/config/paths.js +166 -33
  31. package/src/index.js +124 -34
  32. package/src/server/api/agents.js +52 -2
  33. package/src/server/api/commands.js +38 -2
  34. package/src/server/api/plugins.js +104 -1
  35. package/src/server/api/sessions.js +5 -5
  36. package/src/server/index.js +25 -5
  37. package/src/server/services/agents-service.js +269 -62
  38. package/src/server/services/commands-service.js +281 -81
  39. package/src/server/services/config-export-service.js +7 -7
  40. package/src/server/services/config-registry-service.js +4 -5
  41. package/src/server/services/config-sync-manager.js +61 -41
  42. package/src/server/services/config-sync-service.js +3 -3
  43. package/src/server/services/gemini-channels.js +5 -5
  44. package/src/server/services/gemini-config.js +3 -4
  45. package/src/server/services/gemini-sessions.js +23 -20
  46. package/src/server/services/gemini-settings-manager.js +2 -3
  47. package/src/server/services/https-cert.js +171 -0
  48. package/src/server/services/mcp-service.js +9 -14
  49. package/src/server/services/native-oauth-adapters.js +3 -3
  50. package/src/server/services/network-access.js +47 -2
  51. package/src/server/services/notification-hooks.js +11 -5
  52. package/src/server/services/opencode-sessions.js +4 -4
  53. package/src/server/services/opencode-settings-manager.js +3 -3
  54. package/src/server/services/plugins-service.js +499 -23
  55. package/src/server/services/prompts-service.js +5 -9
  56. package/src/server/services/sessions.js +2 -2
  57. package/src/server/services/skill-service.js +155 -18
  58. package/src/server/services/web-ui-runtime.js +54 -0
  59. package/src/server/websocket-server.js +11 -4
  60. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  61. package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
  62. package/dist/web/assets/ProjectList-BNsz96av.js +0 -1
  63. package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
  64. package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
  65. package/dist/web/assets/SessionList-CG1UhFo3.js +0 -1
  66. package/dist/web/assets/icons-CQuif85v.js +0 -1
  67. package/dist/web/assets/index-GuER-BmS.js +0 -2
  68. package/dist/web/assets/index-VGAxnLqi.css +0 -1
  69. package/dist/web/assets/vue-vendor-aWwwFAao.js +0 -45
@@ -6,29 +6,24 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
- const os = require('os');
10
9
  const toml = require('@iarna/toml');
11
10
  const { spawn } = require('child_process');
12
11
  const http = require('http');
13
12
  const https = require('https');
14
13
  const { McpClient, buildMissingCommandMessage, createMissingCommandHint } = require('./mcp-client');
15
- const { NATIVE_PATHS, PATHS } = require('../../config/paths');
16
- const { resolvePreferredHomeDir } = require('../../utils/home-dir');
17
-
18
- const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
14
+ const { NATIVE_PATHS, PATHS, HOME_DIR, getNativePathDir, joinNativeBasePath } = require('../../config/paths');
19
15
 
20
16
  // MCP 配置文件路径
21
17
  const MCP_SERVERS_FILE = PATHS.mcpServers;
22
18
 
23
19
  // 各平台配置文件路径
24
- const CLAUDE_CONFIG_PATH = path.join(HOME_DIR, '.claude.json');
20
+ const CLAUDE_CONFIG_PATH = joinNativeBasePath(HOME_DIR, '.claude.json');
25
21
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
26
- const GEMINI_CONFIG_PATH = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
27
- const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
22
+ const GEMINI_CONFIG_PATH = NATIVE_PATHS.gemini.settings;
28
23
  const OPENCODE_CONFIG_PATHS = {
29
- jsonc: path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
30
- json: path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
31
- legacy: path.join(OPENCODE_CONFIG_DIR, 'config.json')
24
+ jsonc: NATIVE_PATHS.opencode.configJsonc,
25
+ json: NATIVE_PATHS.opencode.configJson,
26
+ legacy: NATIVE_PATHS.opencode.configLegacy
32
27
  };
33
28
 
34
29
  // MCP 客户端连接池
@@ -279,7 +274,7 @@ function readJsonFile(filePath, defaultValue = {}) {
279
274
  * 安全写入 JSON 文件(原子写入)
280
275
  */
281
276
  function writeJsonFile(filePath, data) {
282
- ensureDir(path.dirname(filePath));
277
+ ensureDir(getNativePathDir(filePath));
283
278
  const tempPath = filePath + '.tmp';
284
279
  fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
285
280
  fs.renameSync(tempPath, filePath);
@@ -305,7 +300,7 @@ function readTomlFile(filePath, defaultValue = {}) {
305
300
  * 安全写入 TOML 文件(原子写入)
306
301
  */
307
302
  function writeTomlFile(filePath, data) {
308
- ensureDir(path.dirname(filePath));
303
+ ensureDir(getNativePathDir(filePath));
309
304
  const tempPath = filePath + '.tmp';
310
305
  fs.writeFileSync(tempPath, toml.stringify(data), 'utf-8');
311
306
  fs.renameSync(tempPath, filePath);
@@ -412,7 +407,7 @@ function readOpenCodeConfig() {
412
407
  * 写入 OpenCode 配置(保持 JSON 格式)
413
408
  */
414
409
  function writeOpenCodeConfig(filePath, data) {
415
- ensureDir(path.dirname(filePath));
410
+ ensureDir(getNativePathDir(filePath));
416
411
  const tempPath = filePath + '.tmp';
417
412
  fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
418
413
  fs.renameSync(tempPath, filePath);
@@ -4,7 +4,7 @@ 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 { NATIVE_PATHS, PATHS } = require('../../config/paths');
7
+ const { NATIVE_PATHS, PATHS, getNativePathDir } = require('../../config/paths');
8
8
  const claudeSettingsManager = require('./settings-manager');
9
9
  const codexSettingsManager = require('./codex-settings-manager');
10
10
  const geminiSettingsManager = require('./gemini-settings-manager');
@@ -35,7 +35,7 @@ function ensureFileMode(filePath, mode = 0o600) {
35
35
  }
36
36
 
37
37
  function writeJsonFile(filePath, value) {
38
- ensureDir(path.dirname(filePath));
38
+ ensureDir(getNativePathDir(filePath));
39
39
  fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8');
40
40
  ensureFileMode(filePath);
41
41
  }
@@ -562,7 +562,7 @@ function applyGeminiOAuth(credential) {
562
562
  }
563
563
  };
564
564
 
565
- ensureDir(path.dirname(NATIVE_PATHS.gemini.oauthCredentialsEncrypted));
565
+ ensureDir(getNativePathDir(NATIVE_PATHS.gemini.oauthCredentialsEncrypted));
566
566
  fs.writeFileSync(
567
567
  NATIVE_PATHS.gemini.oauthCredentialsEncrypted,
568
568
  encryptGeminiPayload(JSON.stringify(payload, null, 2)),
@@ -16,6 +16,21 @@ function isLoopbackAddress(address) {
16
16
  return false;
17
17
  }
18
18
 
19
+ function hasTrustedProxySocket(req) {
20
+ if (!req) return false;
21
+ return isLoopbackAddress(req.socket && req.socket.remoteAddress);
22
+ }
23
+
24
+ function getForwardedHeaderValue(req, headerName) {
25
+ if (!hasTrustedProxySocket(req) || !req.headers) {
26
+ return '';
27
+ }
28
+
29
+ const rawValue = req.headers[headerName];
30
+ const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
31
+ return String(value || '').split(',')[0].trim();
32
+ }
33
+
19
34
  function isLoopbackRequest(req) {
20
35
  if (!req) return false;
21
36
  const socketAddress = req.socket && req.socket.remoteAddress;
@@ -30,6 +45,34 @@ function isLoopbackRequest(req) {
30
45
  return true;
31
46
  }
32
47
 
48
+ function getRequestProtocol(req) {
49
+ const forwardedProto = getForwardedHeaderValue(req, 'x-forwarded-proto').toLowerCase();
50
+ if (forwardedProto === 'https' || forwardedProto === 'https:') {
51
+ return 'https:';
52
+ }
53
+ if (forwardedProto === 'http' || forwardedProto === 'http:') {
54
+ return 'http:';
55
+ }
56
+ return req && req.socket && req.socket.encrypted ? 'https:' : 'http:';
57
+ }
58
+
59
+ function getRequestHost(req) {
60
+ const forwardedHost = getForwardedHeaderValue(req, 'x-forwarded-host');
61
+ if (forwardedHost) {
62
+ return forwardedHost;
63
+ }
64
+
65
+ if (!req || !req.headers) {
66
+ return '';
67
+ }
68
+
69
+ const host = req.headers.host;
70
+ if (Array.isArray(host)) {
71
+ return String(host[0] || '').trim();
72
+ }
73
+ return String(host || '').trim();
74
+ }
75
+
33
76
  function isSameOriginRequest(req) {
34
77
  if (!req) return false;
35
78
  const origin = req.headers && req.headers.origin;
@@ -37,14 +80,14 @@ function isSameOriginRequest(req) {
37
80
  return true;
38
81
  }
39
82
 
40
- const host = req.headers && req.headers.host;
83
+ const host = getRequestHost(req);
41
84
  if (!host) {
42
85
  return false;
43
86
  }
44
87
 
45
88
  try {
46
89
  const originUrl = new URL(origin);
47
- return originUrl.host === host;
90
+ return originUrl.host.toLowerCase() === host.toLowerCase();
48
91
  } catch (error) {
49
92
  return false;
50
93
  }
@@ -124,6 +167,8 @@ module.exports = {
124
167
  normalizeAddress,
125
168
  isLoopbackAddress,
126
169
  isLoopbackRequest,
170
+ getRequestProtocol,
171
+ getRequestHost,
127
172
  isSameOriginRequest,
128
173
  isRemoteMutationAllowed,
129
174
  createRemoteMutationGuard,
@@ -4,11 +4,12 @@ const os = require('os');
4
4
  const http = require('http');
5
5
  const https = require('https');
6
6
  const { execSync, execFileSync } = require('child_process');
7
- const { PATHS, NATIVE_PATHS } = require('../../config/paths');
7
+ const { PATHS, NATIVE_PATHS, getNativePathDir, joinNativeBasePath } = require('../../config/paths');
8
8
  const { loadConfig } = require('../../config/loader');
9
9
  const { loadUIConfig, saveUIConfig } = require('./ui-config');
10
10
  const codexSettingsManager = require('./codex-settings-manager');
11
11
  const geminiSettingsManager = require('./gemini-settings-manager');
12
+ const { getWebUiProtocol } = require('./web-ui-runtime');
12
13
 
13
14
  const MANAGED_HOOK_NAME = 'coding-tool-notify';
14
15
  const MANAGED_OPENCODE_PLUGIN_FILE = 'coding-tool-notify.js';
@@ -18,7 +19,7 @@ function normalizeType(type) {
18
19
  }
19
20
 
20
21
  function ensureParentDir(filePath) {
21
- const dir = path.dirname(filePath);
22
+ const dir = getNativePathDir(filePath);
22
23
  if (!fs.existsSync(dir)) {
23
24
  fs.mkdirSync(dir, { recursive: true });
24
25
  }
@@ -169,9 +170,10 @@ function buildBrowserNotificationEndpoint() {
169
170
  const config = loadConfig();
170
171
  const port = Number(config?.ports?.webUI);
171
172
  const resolvedPort = Number.isFinite(port) && port > 0 ? port : 19999;
172
- return `http://127.0.0.1:${resolvedPort}/api/hooks/browser-event`;
173
+ const protocol = getWebUiProtocol();
174
+ return `${protocol}://127.0.0.1:${resolvedPort}/api/hooks/browser-event`;
173
175
  } catch (error) {
174
- return 'http://127.0.0.1:19999/api/hooks/browser-event';
176
+ return `${getWebUiProtocol()}://127.0.0.1:19999/api/hooks/browser-event`;
175
177
  }
176
178
  }
177
179
 
@@ -267,7 +269,7 @@ function buildGeminiCommand(type) {
267
269
  }
268
270
 
269
271
  function getOpenCodeManagedPluginPath() {
270
- return path.join(NATIVE_PATHS.opencode.config, 'plugins', MANAGED_OPENCODE_PLUGIN_FILE);
272
+ return joinNativeBasePath(NATIVE_PATHS.opencode.plugins, MANAGED_OPENCODE_PLUGIN_FILE);
271
273
  }
272
274
 
273
275
  function buildOpenCodePluginContent(type) {
@@ -746,6 +748,10 @@ function sendBrowserNotification(payload) {
746
748
  timeout: 5000
747
749
  }
748
750
 
751
+ if (urlObj.protocol === 'https:' && ['127.0.0.1', 'localhost', '::1'].includes(urlObj.hostname)) {
752
+ options.rejectUnauthorized = false
753
+ }
754
+
749
755
  const requestModule = urlObj.protocol === 'https:' ? https : http
750
756
  const request = requestModule.request(options, () => resolve())
751
757
  request.on('error', () => resolve())
@@ -2,7 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { execFileSync } = require('child_process');
5
- const { NATIVE_PATHS, PATHS } = require('../../config/paths');
5
+ const { NATIVE_PATHS, PATHS, joinNativeBasePath } = require('../../config/paths');
6
6
 
7
7
  /**
8
8
  * OpenCode 会话服务
@@ -11,7 +11,7 @@ const { NATIVE_PATHS, PATHS } = require('../../config/paths');
11
11
 
12
12
  const PROJECT_ORDER_FILE = PATHS.opencodeProjectOrder;
13
13
  const SESSION_ORDER_FILE = PATHS.opencodeSessionOrder;
14
- const OPENCODE_DB_PATH = path.join(NATIVE_PATHS.opencode.data, 'opencode.db');
14
+ const OPENCODE_DB_PATH = joinNativeBasePath(NATIVE_PATHS.opencode.data, 'opencode.db');
15
15
  const COUNTS_CACHE_TTL_MS = 30 * 1000;
16
16
  const EMPTY_COUNTS = Object.freeze({ projectCount: 0, sessionCount: 0 });
17
17
 
@@ -234,12 +234,12 @@ function getOpenCodeDataDir() {
234
234
 
235
235
  // 兼容导出:保留旧路径函数
236
236
  function getSessionsDir() {
237
- return path.join(getOpenCodeDataDir(), 'storage', 'session');
237
+ return NATIVE_PATHS?.opencode?.sessions || joinNativeBasePath(getOpenCodeDataDir(), 'storage', 'session');
238
238
  }
239
239
 
240
240
  // 兼容导出:保留旧路径函数
241
241
  function getProjectsDir() {
242
- return path.join(getOpenCodeDataDir(), 'storage', 'project');
242
+ return NATIVE_PATHS?.opencode?.projects || joinNativeBasePath(getOpenCodeDataDir(), 'storage', 'project');
243
243
  }
244
244
 
245
245
  function getProjectOrder() {
@@ -23,9 +23,9 @@ function resolveModelCost(modelId) {
23
23
 
24
24
  const CONFIG_DIR = NATIVE_PATHS.opencode.config;
25
25
  const CONFIG_PATHS = {
26
- config: path.join(CONFIG_DIR, 'config.json'),
27
- opencode: path.join(CONFIG_DIR, 'opencode.json'),
28
- opencodec: path.join(CONFIG_DIR, 'opencode.jsonc')
26
+ config: NATIVE_PATHS.opencode.configLegacy,
27
+ opencode: NATIVE_PATHS.opencode.configJson,
28
+ opencodec: NATIVE_PATHS.opencode.configJsonc
29
29
  };
30
30
  const BACKUP_SUFFIX = '.cc-tool-backup';
31
31
  const EMPTY_SENTINEL = '__CC_TOOL_NO_FILE__';