coding-tool-x 3.6.10 → 3.6.12

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.
@@ -5,14 +5,14 @@
5
5
  <link rel="icon" href="/favicon.ico">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>coding-tool-x · AI 编程工作台</title>
8
- <script type="module" crossorigin src="/assets/index-CfBxeWy0.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-y-eEayxR.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-BNJfEgCA.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-B5RqjEjQ.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/naive-ui-kcWDeJwl.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/icons-QFXzEgqM.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/vendors-DQA4Hw9v.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
15
- <link rel="stylesheet" crossorigin href="/assets/index-Dl2Bz258.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-Dsh4Q5Cg.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.6.10",
3
+ "version": "3.6.12",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@ const {
11
11
  getPortToolIssue,
12
12
  formatPortToolIssue
13
13
  } = require('../utils/port-helper');
14
+ const { hasHostFlag } = require('../utils/cli-flags');
14
15
 
15
16
  const PM2_APP_NAME = 'cc-tool';
16
17
  const STARTUP_LOG_FILE = 'cc-tool-out.log';
@@ -499,7 +500,7 @@ async function handleStart() {
499
500
  try {
500
501
  const config = loadConfig();
501
502
  const port = config.ports?.webUI || 19999;
502
- const enableHost = process.argv.includes('--host');
503
+ const enableHost = hasHostFlag();
503
504
  const enableHttps = process.argv.includes('--https');
504
505
  fs.mkdirSync(PATHS.logs, { recursive: true });
505
506
 
@@ -733,6 +734,7 @@ module.exports = {
733
734
  _test: {
734
735
  detectStalePm2RuntimeIssue,
735
736
  extractMissingPm2ForkScriptPath,
737
+ buildStartOptions,
736
738
  shouldTreatPortOwnershipAsReady,
737
739
  getManagedPorts,
738
740
  shouldStopPM2Process
@@ -3,13 +3,14 @@ const { startServer } = require('../server');
3
3
  const open = require('open');
4
4
  const { getProxyStatus } = require('../server/proxy-server');
5
5
  const { loadConfig } = require('../config/loader');
6
+ const { hasHostFlag } = require('../utils/cli-flags');
6
7
 
7
8
  async function handleUI() {
8
9
  // 检查是否为 daemon 模式(PM2 启动)
9
10
  const isDaemon = process.argv.includes('--daemon');
10
11
 
11
- // 检查是否启用 LAN 访问 (--host 标志)
12
- const enableHost = process.argv.includes('--host');
12
+ // 检查是否启用 LAN 访问 (--host/--hosts 标志)
13
+ const enableHost = hasHostFlag();
13
14
  const enableHttps = process.argv.includes('--https');
14
15
  const host = enableHost ? '0.0.0.0' : '127.0.0.1';
15
16
  const protocol = enableHttps ? 'https' : 'http';
@@ -18,7 +19,7 @@ async function handleUI() {
18
19
  console.clear();
19
20
  console.log(chalk.cyan.bold('\n[NET] 启动 Coding-Tool Web UI...\n'));
20
21
  if (enableHost) {
21
- console.log(chalk.yellow('[WARN] LAN 访问已启用 (--host)\n'));
22
+ console.log(chalk.yellow('[WARN] LAN 访问已启用 (--host/--hosts)\n'));
22
23
  }
23
24
  if (enableHttps) {
24
25
  console.log(chalk.yellow('[LOCK] 已启用本地 HTTPS 模式 (--https)\n'));
package/src/index.js CHANGED
@@ -16,6 +16,7 @@ const { handleUpdate } = require('./commands/update');
16
16
  const { ensureStorageDirMigrated } = require('./config/paths');
17
17
  const PluginManager = require('./plugins/plugin-manager');
18
18
  const eventBus = require('./plugins/event-bus');
19
+ const { hasHostFlag } = require('./utils/cli-flags');
19
20
  const chalk = require('chalk');
20
21
  const path = require('path');
21
22
  const fs = require('fs');
@@ -53,9 +54,12 @@ function showHelp() {
53
54
  console.log(' ctx ui 前台启动 Web UI(仅本地访问)');
54
55
  console.log(' ctx ui --https 前台启动 Web UI(本地 HTTPS)');
55
56
  console.log(' ctx ui --host 前台启动 Web UI(允许 LAN 访问)');
57
+ console.log(' ctx ui --hosts 前台启动 Web UI(同 --host)');
58
+ console.log(' ctx --hosts 前台启动 Web UI(同 ctx ui --host)');
56
59
  console.log(' ctx ui start 后台启动 Web UI');
57
60
  console.log(' ctx ui start --https 后台启动 Web UI(本地 HTTPS)');
58
61
  console.log(' ctx ui start --host 后台启动 Web UI(允许 LAN 访问)');
62
+ console.log(' ctx ui start --hosts 后台启动 Web UI(同 --host)');
59
63
  console.log(' ctx ui stop 停止 Web UI');
60
64
  console.log(' ctx ui restart 重启 Web UI\n');
61
65
 
@@ -156,6 +160,13 @@ async function main() {
156
160
  return;
157
161
  }
158
162
 
163
+ // LAN 快捷入口,等同于 ctx ui --host/--hosts
164
+ if (hasHostFlag(process.argv) && !args.some((arg) => arg && !arg.startsWith('-'))) {
165
+ const { handleUI } = require('./commands/ui');
166
+ await handleUI();
167
+ return;
168
+ }
169
+
159
170
  // daemon 命令兼容(保持与 README 中旧命令一致)
160
171
  if (args[0] === 'daemon') {
161
172
  const subCommand = args[1] || 'status';
@@ -31,6 +31,15 @@ function resolveChannelsByType(exportData) {
31
31
 
32
32
  function buildPreviewSummary(data) {
33
33
  const channelsByType = resolveChannelsByType(data);
34
+ const pluginsByPlatform = data?.data?.pluginsByPlatform && typeof data.data.pluginsByPlatform === 'object'
35
+ ? data.data.pluginsByPlatform
36
+ : {};
37
+ const platformPlugins = Object.values(pluginsByPlatform).flatMap((snapshot) => (
38
+ Array.isArray(snapshot?.plugins) ? snapshot.plugins : []
39
+ ));
40
+ const plugins = platformPlugins.length > 0
41
+ ? platformPlugins
42
+ : (Array.isArray(data.data.plugins) ? data.data.plugins : []);
34
43
  const allChannels = [
35
44
  ...channelsByType.claude.map(c => ({ ...c, type: c.type || 'claude' })),
36
45
  ...channelsByType.codex.map(c => ({ ...c, type: c.type || 'codex' })),
@@ -44,7 +53,7 @@ function buildPreviewSummary(data) {
44
53
  counts: {
45
54
  configTemplates: (data.data.configTemplates || []).length,
46
55
  channels: allChannels.length,
47
- plugins: (data.data.plugins || []).length
56
+ plugins: plugins.length
48
57
  },
49
58
  items: {
50
59
  configTemplates: (data.data.configTemplates || []).map(t => ({
@@ -57,9 +66,10 @@ function buildPreviewSummary(data) {
57
66
  name: c.name,
58
67
  type: c.type
59
68
  })),
60
- plugins: (data.data.plugins || []).map(p => ({
69
+ plugins: plugins.map(p => ({
61
70
  name: p.name,
62
71
  type: p.type,
72
+ platform: p.platform,
63
73
  version: p.version
64
74
  }))
65
75
  }
@@ -23,7 +23,6 @@ const { startProxyServer } = require('./proxy-server');
23
23
  const { startCodexProxyServer } = require('./codex-proxy-server');
24
24
  const { startGeminiProxyServer } = require('./gemini-proxy-server');
25
25
  const { startOpenCodeProxyServer, collectProxyModelList } = require('./opencode-proxy-server');
26
- const { createRemoteMutationGuard } = require('./services/network-access');
27
26
  const { createApiRequestLogger } = require('./services/request-logger');
28
27
  const { inspectWebBuildState, ensureWebDistReady } = require('./services/web-build');
29
28
  const { ensureHttpsCredentials } = require('./services/https-cert');
@@ -149,8 +148,6 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
149
148
 
150
149
  const app = express();
151
150
  const useHttps = options.useHttps === true || process.argv.includes('--https');
152
- const lanMode = host === '0.0.0.0';
153
- const allowRemoteMutation = process.env.CC_TOOL_ALLOW_REMOTE_WRITE === 'true';
154
151
 
155
152
  // Middleware
156
153
  app.use(express.json({ limit: '100mb' }));
@@ -170,15 +167,6 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
170
167
  // API 请求日志(由 CC_TOOL_LOG_API_REQUESTS=true 环境变量控制,默认关闭)
171
168
  app.use('/api', createApiRequestLogger());
172
169
 
173
- if (lanMode) {
174
- app.use('/api', createRemoteMutationGuard({
175
- enabled: true,
176
- allowRemoteMutation,
177
- message: '出于安全考虑,LAN 模式默认仅允许本机执行写操作。可设置 CC_TOOL_ALLOW_REMOTE_WRITE=true 覆盖。'
178
- }));
179
-
180
- }
181
-
182
170
  // API Routes
183
171
  app.use('/api/projects', require('./api/projects')(config));
184
172
  app.use('/api/sessions', require('./api/sessions')(config));
@@ -316,9 +304,6 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
316
304
  console.log(chalk.gray(' [TIP] 首次访问自签名证书时,浏览器可能会提示手动信任本地证书'));
317
305
  }
318
306
 
319
- if (host === '0.0.0.0' && !allowRemoteMutation) {
320
- console.log(chalk.yellow(' [LOCK] 已启用 LAN 安全保护:远程写操作默认禁用'));
321
- }
322
307
  // 自动恢复代理状态
323
308
  autoRestoreProxies();
324
309
 
@@ -12,7 +12,6 @@ const REQUEST_TIMEOUT_MS = 8000;
12
12
  const MAX_RESPONSE_BYTES = 1024 * 1024;
13
13
  const QUOTA_UNIT = 500000;
14
14
  const VELOERA_QUOTA_UNIT = 1000000;
15
- const NEWCLI_QUOTA_UNIT = 1000000;
16
15
  const VALID_SOURCES = new Set(['claude', 'codex', 'gemini', 'opencode']);
17
16
  const GATEWAY_PATH_SEGMENTS = new Set(['openai', 'anthropic', 'claude', 'gemini', 'codex']);
18
17
  const HUB_PLATFORMS = new Set(['new-api', 'one-api', 'one-hub', 'done-hub', 'veloera', 'anyrouter', 'aihubmix']);
@@ -416,7 +415,6 @@ async function tryRequestJson(url, options) {
416
415
  function detectPlatformByUrlHint(...values) {
417
416
  const value = values.map(item => String(item || '')).join(' ').toLowerCase();
418
417
  if (value.includes('88code') || value.includes('88-code') || value.includes('rainapp.top')) return '88code';
419
- if (value.includes('code.newcli.com') || value.includes('newcli') || value.includes('new-cli')) return 'newcli';
420
418
  if (value.includes('openrouter.ai') || value.includes('openrouter')) return 'openrouter';
421
419
  if (value.includes('aihubmix')) return 'aihubmix';
422
420
  if (value.includes('dashscope') || value.includes('dashscope.aliyuncs.com') || value.includes('compatible-mode')) return 'dashscope';
@@ -602,77 +600,6 @@ function buildSiliconFlowUserInfoSnapshot(payload) {
602
600
  });
603
601
  }
604
602
 
605
- function normalizeNewCliQuotaUnit(value) {
606
- return normalizeQuotaUnit(value, NEWCLI_QUOTA_UNIT);
607
- }
608
-
609
- function extractNewCliSubscriptionItems(payload) {
610
- if (!payload || typeof payload !== 'object') return [];
611
- const data = payload.data && typeof payload.data === 'object' ? payload.data : payload;
612
- const subscription = data.subscription && typeof data.subscription === 'object' ? data.subscription : data;
613
- const candidates = [
614
- subscription.active,
615
- subscription.subscriptions,
616
- subscription.items,
617
- data.subscriptions,
618
- data.items,
619
- Array.isArray(subscription) ? subscription : null,
620
- Array.isArray(data) ? data : null
621
- ];
622
- for (const candidate of candidates) {
623
- if (Array.isArray(candidate)) return candidate;
624
- }
625
- return [];
626
- }
627
-
628
- function shouldUseNewCliSubscription(item = {}) {
629
- if (!item || typeof item !== 'object') return false;
630
- if (item.isActive === false) return false;
631
- const status = String(item.status || item.subscriptionStatus || '').trim().toUpperCase();
632
- return !status || status === 'ACTIVE' || status === '活跃中';
633
- }
634
-
635
- function buildNewCliDashboardSnapshot(payload) {
636
- const items = extractNewCliSubscriptionItems(payload).filter(shouldUseNewCliSubscription);
637
- if (!items.length) return makeHiddenSnapshot('newcli');
638
-
639
- const data = payload?.data && typeof payload.data === 'object' ? payload.data : payload;
640
- const unit = normalizeNewCliQuotaUnit(data?.quotaUnit || data?.quota_unit || data?.quota_per_unit || data?.quotaPerUnit);
641
- let remainingRaw = 0;
642
- let usedRaw = 0;
643
- let totalRaw = 0;
644
- let hasRemaining = false;
645
- let hasTotal = false;
646
-
647
- for (const item of items) {
648
- const plan = item.plan && typeof item.plan === 'object' ? item.plan : {};
649
- const remaining = readMoneyField(item, 'quotaRemaining', 'quota_remaining', 'remainingQuota', 'remaining_quota');
650
- const used = readMoneyField(item, 'quotaUsed', 'quota_used', 'usedQuota', 'used_quota') || 0;
651
- const limit = readMoneyField(item, 'quotaLimit', 'quota_limit')
652
- ?? readMoneyField(plan, 'quotaLimit', 'quota_limit');
653
-
654
- if (remaining != null) {
655
- hasRemaining = true;
656
- remainingRaw += Math.max(0, remaining);
657
- usedRaw += Math.max(0, used);
658
- if (limit != null) {
659
- hasTotal = true;
660
- totalRaw += Math.max(0, limit);
661
- } else {
662
- totalRaw += Math.max(0, remaining + used);
663
- }
664
- }
665
- }
666
-
667
- if (!hasRemaining) return makeHiddenSnapshot('newcli');
668
-
669
- return makeVisibleSnapshot('newcli', {
670
- remaining: remainingRaw / unit,
671
- used: usedRaw / unit,
672
- total: (hasTotal || usedRaw > 0) ? totalRaw / unit : undefined
673
- });
674
- }
675
-
676
603
  function extractQuotaUnitFromStatus(payload) {
677
604
  if (!payload || typeof payload !== 'object') return null;
678
605
  const data = payload.data && typeof payload.data === 'object' ? payload.data : payload;
@@ -864,41 +791,6 @@ function buildSiliconFlowApiBaseCandidates(baseUrl, channel = {}) {
864
791
  return candidates;
865
792
  }
866
793
 
867
- function buildNewCliWebBaseCandidates(baseUrl, channel = {}) {
868
- const candidates = [];
869
- const inputs = [
870
- channel.balanceBaseUrl,
871
- channel.websiteUrl,
872
- channel.baseUrl,
873
- baseUrl
874
- ].filter(Boolean);
875
-
876
- for (const input of inputs) {
877
- try {
878
- const parsed = new URL(input);
879
- addUnique(candidates, parsed.origin);
880
- } catch {}
881
- }
882
- return candidates;
883
- }
884
-
885
- function buildNewCliAuthHeaderCandidates(token) {
886
- const raw = stripBearer(token).replace(/^cookie:\s*/i, '').trim();
887
- if (!raw) return [];
888
- if (raw.includes('=')) {
889
- return [{
890
- Accept: 'application/json',
891
- Cookie: raw
892
- }];
893
- }
894
- return [
895
- buildAuthHeaders(raw),
896
- { Accept: 'application/json', Cookie: `auth_token=${raw}` },
897
- { Accept: 'application/json', Cookie: `session=${raw}` },
898
- { Accept: 'application/json', Cookie: `__Secure-authjs.session-token=${raw}` }
899
- ];
900
- }
901
-
902
794
  function hasGatewayPathSegment(value) {
903
795
  try {
904
796
  const parsed = new URL(normalizeBaseUrl(value));
@@ -1155,27 +1047,12 @@ function buildBalanceProbeStrategies(baseUrl, channel = {}, token = '') {
1155
1047
  add({ type: 'siliconflow-user-info', baseUrl, apiBase });
1156
1048
  }
1157
1049
  };
1158
- const addNewCliStrategies = () => {
1159
- for (const webBase of buildNewCliWebBaseCandidates(baseUrl, channel)) {
1160
- add({
1161
- type: 'newcli-dashboard',
1162
- baseUrl,
1163
- webBase,
1164
- requiresDedicatedToken: !hasDedicatedBalanceToken(channel)
1165
- });
1166
- }
1167
- };
1168
1050
 
1169
1051
  if (UNSUPPORTED_BALANCE_PLATFORMS.has(hintedPlatform)) {
1170
1052
  add({ type: 'unsupported-balance', platform: hintedPlatform, baseUrl });
1171
1053
  return strategies;
1172
1054
  }
1173
1055
 
1174
- if (hintedPlatform === 'newcli') {
1175
- addNewCliStrategies();
1176
- return strategies;
1177
- }
1178
-
1179
1056
  if (hintedPlatform === 'siliconflow') {
1180
1057
  addSiliconFlowStrategies();
1181
1058
  return strategies;
@@ -1347,21 +1224,6 @@ async function runSiliconFlowUserInfoStrategy(strategy, token) {
1347
1224
  return buildSiliconFlowUserInfoSnapshot(payload);
1348
1225
  }
1349
1226
 
1350
- async function runNewCliDashboardStrategy(strategy, token, channel = {}) {
1351
- if (strategy.requiresDedicatedToken || !hasDedicatedBalanceToken(channel)) {
1352
- return makeHiddenSnapshot('newcli');
1353
- }
1354
-
1355
- for (const headers of buildNewCliAuthHeaderCandidates(token)) {
1356
- const payload = await tryRequestJson(joinUrl(strategy.webBase || strategy.baseUrl, '/api/user/dashboard'), {
1357
- headers
1358
- });
1359
- const snapshot = buildNewCliDashboardSnapshot(payload);
1360
- if (snapshot.visible) return snapshot;
1361
- }
1362
- return makeHiddenSnapshot('newcli');
1363
- }
1364
-
1365
1227
  async function runBalanceStrategy(strategy, token, channel = {}) {
1366
1228
  if (!strategy || !strategy.type) {
1367
1229
  return { snapshot: makeHiddenSnapshot(), strategy };
@@ -1433,12 +1295,6 @@ async function runBalanceStrategy(strategy, token, channel = {}) {
1433
1295
  strategy
1434
1296
  };
1435
1297
  }
1436
- if (strategy.type === 'newcli-dashboard') {
1437
- return {
1438
- snapshot: await runNewCliDashboardStrategy(strategy, token, channel),
1439
- strategy
1440
- };
1441
- }
1442
1298
 
1443
1299
  return { snapshot: makeHiddenSnapshot(), strategy };
1444
1300
  }
@@ -1464,7 +1320,6 @@ async function probeBalanceFromBase(baseUrl, channel, token) {
1464
1320
  strategy.type?.startsWith('88code') ? '88code'
1465
1321
  : strategy.type?.startsWith('openrouter') ? 'openrouter'
1466
1322
  : strategy.type?.startsWith('siliconflow') ? 'siliconflow'
1467
- : strategy.type?.startsWith('newcli') ? 'newcli'
1468
1323
  : strategy.type?.startsWith('sub2api') ? 'sub2api'
1469
1324
  : strategy.platform || null
1470
1325
  );
@@ -1731,10 +1586,8 @@ module.exports = {
1731
1586
  build88CodeApiBaseCandidates,
1732
1587
  buildOpenRouterApiBaseCandidates,
1733
1588
  buildSiliconFlowApiBaseCandidates,
1734
- buildNewCliWebBaseCandidates,
1735
1589
  build88CodeUsageSnapshot,
1736
1590
  build88CodeSubscriptionSnapshot,
1737
- buildNewCliDashboardSnapshot,
1738
1591
  buildBalanceProbeStrategies,
1739
1592
  getEnabledBalanceChannels,
1740
1593
  runBalanceStrategy,