coding-tool-x 3.3.7 → 3.3.9

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 (89) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +253 -326
  3. package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
  5. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  6. package/dist/web/assets/Home-D7KX7iF8.js +1 -0
  7. package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
  8. package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
  9. package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
  10. package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
  12. package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
  13. package/dist/web/assets/index-CL-qpoJ_.js +2 -0
  14. package/dist/web/assets/index-D_5dRFOL.css +1 -0
  15. package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
  16. package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
  17. package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
  18. package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
  19. package/dist/web/index.html +7 -7
  20. package/docs/home.png +0 -0
  21. package/package.json +14 -5
  22. package/src/commands/daemon.js +3 -2
  23. package/src/commands/security.js +1 -2
  24. package/src/commands/toggle-proxy.js +100 -5
  25. package/src/config/paths.js +718 -90
  26. package/src/server/api/agents.js +1 -1
  27. package/src/server/api/channels.js +9 -0
  28. package/src/server/api/claude-hooks.js +13 -8
  29. package/src/server/api/codex-channels.js +9 -0
  30. package/src/server/api/codex-proxy.js +27 -15
  31. package/src/server/api/gemini-proxy.js +22 -11
  32. package/src/server/api/hooks.js +45 -0
  33. package/src/server/api/oauth-credentials.js +163 -0
  34. package/src/server/api/opencode-proxy.js +22 -10
  35. package/src/server/api/plugins.js +2 -1
  36. package/src/server/api/proxy.js +39 -44
  37. package/src/server/api/skills.js +91 -13
  38. package/src/server/api/ui-config.js +5 -0
  39. package/src/server/codex-proxy-server.js +90 -70
  40. package/src/server/gemini-proxy-server.js +107 -88
  41. package/src/server/index.js +2 -0
  42. package/src/server/opencode-proxy-server.js +381 -225
  43. package/src/server/proxy-server.js +86 -60
  44. package/src/server/services/alias.js +3 -3
  45. package/src/server/services/channels.js +21 -24
  46. package/src/server/services/codex-channels.js +158 -255
  47. package/src/server/services/codex-config.js +2 -5
  48. package/src/server/services/codex-env-manager.js +423 -0
  49. package/src/server/services/codex-settings-manager.js +21 -357
  50. package/src/server/services/codex-statistics-service.js +3 -27
  51. package/src/server/services/config-export-service.js +43 -9
  52. package/src/server/services/config-registry-service.js +3 -2
  53. package/src/server/services/config-sync-manager.js +1 -1
  54. package/src/server/services/favorites.js +4 -3
  55. package/src/server/services/gemini-channels.js +14 -12
  56. package/src/server/services/gemini-statistics-service.js +3 -25
  57. package/src/server/services/mcp-service.js +35 -19
  58. package/src/server/services/model-detector.js +4 -3
  59. package/src/server/services/native-keychain.js +243 -0
  60. package/src/server/services/native-oauth-adapters.js +891 -0
  61. package/src/server/services/network-access.js +39 -1
  62. package/src/server/services/notification-hooks.js +951 -0
  63. package/src/server/services/oauth-credentials-service.js +786 -0
  64. package/src/server/services/oauth-utils.js +49 -0
  65. package/src/server/services/opencode-channels.js +19 -15
  66. package/src/server/services/opencode-sessions.js +2 -2
  67. package/src/server/services/opencode-settings-manager.js +169 -16
  68. package/src/server/services/opencode-statistics-service.js +3 -27
  69. package/src/server/services/plugins-service.js +115 -15
  70. package/src/server/services/prompts-service.js +2 -3
  71. package/src/server/services/proxy-log-helper.js +242 -0
  72. package/src/server/services/proxy-runtime.js +6 -4
  73. package/src/server/services/repo-scanner-base.js +12 -4
  74. package/src/server/services/request-logger.js +7 -7
  75. package/src/server/services/security-config.js +4 -4
  76. package/src/server/services/session-cache.js +2 -2
  77. package/src/server/services/sessions.js +2 -2
  78. package/src/server/services/settings-manager.js +13 -0
  79. package/src/server/services/skill-service.js +867 -368
  80. package/src/server/services/statistics-service.js +5 -5
  81. package/src/server/services/ui-config.js +4 -3
  82. package/src/server/services/workspace-service.js +1 -1
  83. package/src/server/websocket-server.js +5 -4
  84. package/dist/web/assets/Home-BsSioaaB.css +0 -1
  85. package/dist/web/assets/Home-obifg_9E.js +0 -1
  86. package/dist/web/assets/index-C7LPdVsN.js +0 -2
  87. package/dist/web/assets/index-eEmjZKWP.css +0 -1
  88. package/docs/bannel.png +0 -0
  89. package/docs/model-redirection.md +0 -251
@@ -0,0 +1,242 @@
1
+ function toNumber(value) {
2
+ const num = Number(value);
3
+ return Number.isFinite(num) ? num : 0;
4
+ }
5
+
6
+ function normalizeToolSource(source = '') {
7
+ const normalized = String(source || '').trim().toLowerCase();
8
+ if (normalized === 'claude' || normalized === 'claude-code') return 'claude';
9
+ if (normalized === 'codex') return 'codex';
10
+ if (normalized === 'gemini') return 'gemini';
11
+ if (normalized === 'opencode') return 'opencode';
12
+ return 'claude';
13
+ }
14
+
15
+ function normalizeUsageTokens(source, tokens = {}) {
16
+ const normalizedSource = normalizeToolSource(source);
17
+ const input = toNumber(tokens.input);
18
+ const output = toNumber(tokens.output);
19
+ const cacheCreation = toNumber(tokens.cacheCreation);
20
+ const cacheRead = toNumber(tokens.cacheRead);
21
+ const cached = toNumber(tokens.cached);
22
+ const reasoning = toNumber(tokens.reasoning);
23
+ let total = toNumber(tokens.total);
24
+
25
+ if (total <= 0) {
26
+ if (normalizedSource === 'claude') {
27
+ total = input + output + cacheCreation + cacheRead;
28
+ } else {
29
+ total = input + output;
30
+ }
31
+ }
32
+
33
+ return {
34
+ input,
35
+ output,
36
+ cacheCreation,
37
+ cacheRead,
38
+ cached,
39
+ reasoning,
40
+ total
41
+ };
42
+ }
43
+
44
+ function hasMeaningfulUsage(source, tokens = {}) {
45
+ const normalized = normalizeUsageTokens(source, tokens);
46
+ if (normalized.total > 0) return true;
47
+ if (normalized.input > 0 || normalized.output > 0) return true;
48
+ if (normalized.cacheCreation > 0 || normalized.cacheRead > 0) return true;
49
+ if (normalized.cached > 0 || normalized.reasoning > 0) return true;
50
+ return false;
51
+ }
52
+
53
+ function formatRealtimeTime(timestamp = Date.now()) {
54
+ return new Date(timestamp).toLocaleTimeString('zh-CN', {
55
+ hour12: false,
56
+ hour: '2-digit',
57
+ minute: '2-digit',
58
+ second: '2-digit'
59
+ });
60
+ }
61
+
62
+ function buildSuccessLogPayload({
63
+ source,
64
+ requestId,
65
+ channel,
66
+ model,
67
+ tokens,
68
+ cost = 0,
69
+ timestamp = Date.now()
70
+ }) {
71
+ const normalized = normalizeUsageTokens(source, tokens);
72
+ return {
73
+ type: 'log',
74
+ status: 'success',
75
+ id: requestId,
76
+ time: formatRealtimeTime(timestamp),
77
+ channel,
78
+ model: model || '',
79
+ inputTokens: normalized.input,
80
+ outputTokens: normalized.output,
81
+ cacheCreation: normalized.cacheCreation,
82
+ cacheRead: normalized.cacheRead,
83
+ cachedTokens: normalized.cached,
84
+ reasoningTokens: normalized.reasoning,
85
+ totalTokens: normalized.total,
86
+ cost,
87
+ source: normalizeToolSource(source),
88
+ timestamp
89
+ };
90
+ }
91
+
92
+ function buildFailureLogPayload({
93
+ source,
94
+ requestId,
95
+ channel,
96
+ model,
97
+ message,
98
+ error,
99
+ statusCode,
100
+ stage,
101
+ timestamp = Date.now()
102
+ }) {
103
+ const errorMessage = String(
104
+ error?.message
105
+ || message
106
+ || error
107
+ || 'Request failed'
108
+ );
109
+
110
+ return {
111
+ type: 'log',
112
+ status: 'error',
113
+ id: requestId || `${normalizeToolSource(source)}-error-${timestamp}-${Math.random().toString(36).slice(2, 8)}`,
114
+ time: formatRealtimeTime(timestamp),
115
+ channel: channel || 'Unknown',
116
+ model: model || '',
117
+ message: errorMessage,
118
+ error: errorMessage,
119
+ statusCode: Number.isFinite(Number(statusCode)) ? Number(statusCode) : null,
120
+ stage: stage || 'proxy',
121
+ inputTokens: 0,
122
+ outputTokens: 0,
123
+ cacheCreation: 0,
124
+ cacheRead: 0,
125
+ cachedTokens: 0,
126
+ reasoningTokens: 0,
127
+ totalTokens: 0,
128
+ cost: 0,
129
+ source: normalizeToolSource(source),
130
+ timestamp
131
+ };
132
+ }
133
+
134
+ function publishUsageLog({
135
+ source,
136
+ metadata = {},
137
+ model,
138
+ tokens,
139
+ calculateCost,
140
+ broadcastLog,
141
+ recordRequest,
142
+ recordSuccess,
143
+ allowBroadcast = true
144
+ }) {
145
+ if (!hasMeaningfulUsage(source, tokens)) {
146
+ return null;
147
+ }
148
+
149
+ const normalizedSource = normalizeToolSource(source);
150
+ const normalizedTokens = normalizeUsageTokens(normalizedSource, tokens);
151
+ const requestId = metadata.id || `${normalizedSource}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
152
+ const timestamp = Date.now();
153
+ const cost = typeof calculateCost === 'function'
154
+ ? calculateCost(model || '', normalizedTokens)
155
+ : 0;
156
+
157
+ if (allowBroadcast && typeof broadcastLog === 'function') {
158
+ broadcastLog(buildSuccessLogPayload({
159
+ source: normalizedSource,
160
+ requestId,
161
+ channel: metadata.channel,
162
+ model,
163
+ tokens: normalizedTokens,
164
+ cost,
165
+ timestamp
166
+ }));
167
+ }
168
+
169
+ if (typeof recordRequest === 'function') {
170
+ recordRequest({
171
+ id: requestId,
172
+ timestamp: new Date(metadata.startTime || timestamp).toISOString(),
173
+ toolType: normalizedSource === 'claude' ? 'claude-code' : normalizedSource,
174
+ channel: metadata.channel,
175
+ channelId: metadata.channelId,
176
+ model: model || '',
177
+ tokens: {
178
+ input: normalizedTokens.input,
179
+ output: normalizedTokens.output,
180
+ reasoning: normalizedTokens.reasoning,
181
+ cached: normalizedTokens.cached,
182
+ cacheCreation: normalizedTokens.cacheCreation,
183
+ cacheRead: normalizedTokens.cacheRead,
184
+ total: normalizedTokens.total
185
+ },
186
+ duration: Math.max(0, timestamp - toNumber(metadata.startTime || timestamp)),
187
+ success: true,
188
+ cost
189
+ });
190
+ }
191
+
192
+ if (typeof recordSuccess === 'function' && metadata.channelId) {
193
+ recordSuccess(metadata.channelId, normalizedSource);
194
+ }
195
+
196
+ return {
197
+ cost,
198
+ tokens: normalizedTokens,
199
+ timestamp
200
+ };
201
+ }
202
+
203
+ function publishFailureLog({
204
+ source,
205
+ metadata = {},
206
+ channel,
207
+ model,
208
+ message,
209
+ error,
210
+ statusCode,
211
+ stage,
212
+ broadcastLog
213
+ }) {
214
+ if (typeof broadcastLog !== 'function') {
215
+ return null;
216
+ }
217
+
218
+ const payload = buildFailureLogPayload({
219
+ source,
220
+ requestId: metadata.id,
221
+ channel: channel || metadata.channel,
222
+ model: model || metadata.model,
223
+ message,
224
+ error,
225
+ statusCode,
226
+ stage
227
+ });
228
+ broadcastLog(payload);
229
+ return payload;
230
+ }
231
+
232
+ module.exports = {
233
+ toNumber,
234
+ normalizeToolSource,
235
+ normalizeUsageTokens,
236
+ hasMeaningfulUsage,
237
+ formatRealtimeTime,
238
+ buildSuccessLogPayload,
239
+ buildFailureLogPayload,
240
+ publishUsageLog,
241
+ publishFailureLog
242
+ };
@@ -3,11 +3,13 @@ const path = require('path');
3
3
  const { PATHS } = require('../../config/paths');
4
4
 
5
5
  function getRuntimeFilePath(proxyType) {
6
- const ccToolDir = PATHS.base;
7
- if (!fs.existsSync(ccToolDir)) {
8
- fs.mkdirSync(ccToolDir, { recursive: true });
6
+ const filePath = PATHS.proxyRuntime?.[proxyType]
7
+ || path.join(path.dirname(PATHS.proxyRuntime.claude), `${proxyType}-proxy.json`);
8
+ const dir = path.dirname(filePath);
9
+ if (!fs.existsSync(dir)) {
10
+ fs.mkdirSync(dir, { recursive: true });
9
11
  }
10
- return path.join(ccToolDir, `${proxyType}-proxy-runtime.json`);
12
+ return filePath;
11
13
  }
12
14
 
13
15
  function saveProxyStartTime(proxyType, preserveExisting = false) {
@@ -11,7 +11,7 @@ const https = require('https');
11
11
  const http = require('http');
12
12
  const { createWriteStream } = require('fs');
13
13
  const AdmZip = require('adm-zip');
14
- const { HOME_DIR } = require('../../config/paths');
14
+ const { PATHS, getRepoScannerReposPath, getRepoScannerCachePath } = require('../../config/paths');
15
15
 
16
16
  // 缓存有效期(5分钟)
17
17
  const CACHE_TTL = 5 * 60 * 1000;
@@ -70,9 +70,9 @@ class RepoScannerBase {
70
70
  this.fileExtension = options.fileExtension || '.md';
71
71
  this.defaultRepos = options.defaultRepos || [];
72
72
 
73
- this.configDir = path.join(HOME_DIR, '.cc-tool');
74
- this.reposConfigPath = path.join(this.configDir, `${this.type}-repos.json`);
75
- this.cachePath = path.join(this.configDir, `${this.type}-cache.json`);
73
+ this.configDir = PATHS.config;
74
+ this.reposConfigPath = getRepoScannerReposPath(this.type);
75
+ this.cachePath = getRepoScannerCachePath(this.type);
76
76
 
77
77
  // 内存缓存
78
78
  this.itemsCache = null;
@@ -89,6 +89,14 @@ class RepoScannerBase {
89
89
  if (!fs.existsSync(this.configDir)) {
90
90
  fs.mkdirSync(this.configDir, { recursive: true });
91
91
  }
92
+ const reposDir = path.dirname(this.reposConfigPath);
93
+ if (!fs.existsSync(reposDir)) {
94
+ fs.mkdirSync(reposDir, { recursive: true });
95
+ }
96
+ const cacheDir = path.dirname(this.cachePath);
97
+ if (!fs.existsSync(cacheDir)) {
98
+ fs.mkdirSync(cacheDir, { recursive: true });
99
+ }
92
100
  }
93
101
 
94
102
  // ==================== 仓库配置管理 ====================
@@ -18,7 +18,7 @@ const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { PATHS } = require('../../config/paths');
20
20
 
21
- const CC_TOOL_DIR = PATHS.base;
21
+ const REQUEST_SNAPSHOTS_DIR = path.dirname(PATHS.requestSnapshots.claude);
22
22
 
23
23
  function ensureDir(dir) {
24
24
  if (!fs.existsSync(dir)) {
@@ -55,8 +55,8 @@ function persistProxyRequestSnapshot(source, payload) {
55
55
  if (!isProxyRequestLoggingEnabled()) return;
56
56
 
57
57
  try {
58
- ensureDir(CC_TOOL_DIR);
59
- const logPath = path.join(CC_TOOL_DIR, `${source}-requests.jsonl`);
58
+ const logPath = PATHS.requestSnapshots[source] || path.join(REQUEST_SNAPSHOTS_DIR, `${source}.jsonl`);
59
+ ensureDir(path.dirname(logPath));
60
60
  fs.appendFile(logPath, `${JSON.stringify(payload)}\n`, (error) => {
61
61
  if (error) {
62
62
  console.error(`[request-logger] Failed to persist ${source} request snapshot:`, error);
@@ -97,8 +97,8 @@ function createApiRequestLogger() {
97
97
  };
98
98
 
99
99
  try {
100
- ensureDir(path.join(CC_TOOL_DIR, 'logs'));
101
- const logPath = path.join(CC_TOOL_DIR, 'logs', 'api-requests.jsonl');
100
+ ensureDir(PATHS.logs);
101
+ const logPath = path.join(PATHS.logs, 'api-requests.jsonl');
102
102
  fs.appendFile(logPath, `${JSON.stringify(entry)}\n`, (err) => {
103
103
  if (err) {
104
104
  console.error('[request-logger] Failed to write API request log:', err);
@@ -122,7 +122,7 @@ function createApiRequestLogger() {
122
122
  };
123
123
  }
124
124
 
125
- const CLAUDE_TEMPLATE_PATH = path.join(CC_TOOL_DIR, 'claude-request-template.json');
125
+ const CLAUDE_TEMPLATE_PATH = PATHS.claudeRequestTemplate;
126
126
  const CLAUDE_TEMPLATE_MIN_SYSTEM_CHARS = 100;
127
127
 
128
128
  const FALLBACK_CLAUDE_SYSTEM = Object.freeze([
@@ -280,7 +280,7 @@ function persistClaudeRequestTemplate(body) {
280
280
  if (systemCharCount < CLAUDE_TEMPLATE_MIN_SYSTEM_CHARS) return;
281
281
 
282
282
  try {
283
- ensureDir(CC_TOOL_DIR);
283
+ ensureDir(path.dirname(CLAUDE_TEMPLATE_PATH));
284
284
  const template = { updatedAt: Date.now(), userId, system, tools };
285
285
  fs.writeFile(CLAUDE_TEMPLATE_PATH, JSON.stringify(template), (err) => {
286
286
  if (err) console.error('[request-logger] Failed to write claude-request-template.json:', err);
@@ -3,8 +3,7 @@ const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { PATHS } = require('../../config/paths');
5
5
 
6
- const SECURITY_DIR = PATHS.base;
7
- const SECURITY_FILE = path.join(SECURITY_DIR, 'security.json');
6
+ const SECURITY_FILE = PATHS.security;
8
7
 
9
8
  const DEFAULT_SECURITY_CONFIG = {
10
9
  passwordHash: '',
@@ -17,8 +16,9 @@ const PBKDF2_KEYLEN = 64;
17
16
  const PBKDF2_DIGEST = 'sha512';
18
17
 
19
18
  function ensureSecurityDir() {
20
- if (!fs.existsSync(SECURITY_DIR)) {
21
- fs.mkdirSync(SECURITY_DIR, { recursive: true });
19
+ const dir = path.dirname(SECURITY_FILE);
20
+ if (!fs.existsSync(dir)) {
21
+ fs.mkdirSync(dir, { recursive: true });
22
22
  }
23
23
  }
24
24
 
@@ -11,7 +11,7 @@ let hasMessagesPersisted = {};
11
11
  let hasMessagesPersistTimer = null;
12
12
 
13
13
  function getCcToolDir() {
14
- return PATHS.base;
14
+ return path.dirname(PATHS.sessionHasCache);
15
15
  }
16
16
 
17
17
  function ensureDirExists(dir) {
@@ -52,7 +52,7 @@ function invalidateProjectsCache(configOrPath) {
52
52
  projectsCache.delete(key);
53
53
  }
54
54
 
55
- const hasMessagesCacheFile = path.join(getCcToolDir(), 'session-has-cache.json');
55
+ const hasMessagesCacheFile = PATHS.sessionHasCache;
56
56
  loadHasMessagesCacheFromDisk();
57
57
 
58
58
  function loadHasMessagesCacheFromDisk() {
@@ -29,12 +29,12 @@ function getOrderFilePath() {
29
29
 
30
30
  // Get path for storing fork relations
31
31
  function getForkRelationsFilePath() {
32
- return path.join(PATHS.base, 'fork-relations.json');
32
+ return PATHS.forkRelations;
33
33
  }
34
34
 
35
35
  // Get path for storing session order
36
36
  function getSessionOrderFilePath() {
37
- return path.join(getCcToolDir(), 'session-order.json');
37
+ return PATHS.sessionOrder;
38
38
  }
39
39
 
40
40
  // Get saved project order
@@ -84,6 +84,18 @@ function restoreSettings() {
84
84
  }
85
85
  }
86
86
 
87
+ function deleteBackup() {
88
+ try {
89
+ if (fs.existsSync(getBackupPath())) {
90
+ fs.unlinkSync(getBackupPath());
91
+ }
92
+ return { success: true };
93
+ } catch (err) {
94
+ console.warn('Failed to delete Claude backup file:', err.message);
95
+ return { success: false, error: err.message };
96
+ }
97
+ }
98
+
87
99
  // 设置代理配置
88
100
  function setProxyConfig(proxyPort) {
89
101
  try {
@@ -156,6 +168,7 @@ module.exports = {
156
168
  writeSettings,
157
169
  backupSettings,
158
170
  restoreSettings,
171
+ deleteBackup,
159
172
  setProxyConfig,
160
173
  isProxyConfig,
161
174
  getCurrentProxyPort