coding-tool-x 3.6.12 → 3.7.0

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 (80) hide show
  1. package/README.md +46 -33
  2. package/dist/web/assets/{Analytics-BuBlsbcI.js → Analytics-Dma_j2Rq.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-DQczxuOX.js → ConfigTemplates-C49Y1PLH.js} +1 -1
  4. package/dist/web/assets/Home-DGGC8H4g.js +1 -0
  5. package/dist/web/assets/Home-ewgeIhF7.css +1 -0
  6. package/dist/web/assets/{PluginManager-C8gAlaps.js → PluginManager-B1ECTHyY.js} +1 -1
  7. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
  8. package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
  9. package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
  10. package/dist/web/assets/{SkillManager-BDbNbNkK.js → SkillManager-CRyVhjYQ.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-DGGaGW2c.js → WorkspaceManager-CO9tclVt.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-CM26UrnJ.css +1 -0
  14. package/dist/web/assets/index-EQq56pRa.js +2 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +2 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +48 -6
  22. package/src/commands/port-config.js +81 -75
  23. package/src/commands/proxy-control.js +52 -19
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/toggle-proxy.js +131 -53
  26. package/src/commands/ui.js +3 -4
  27. package/src/config/default.js +14 -3
  28. package/src/config/paths.js +99 -11
  29. package/src/index.js +11 -16
  30. package/src/server/api/commands.js +1 -1
  31. package/src/server/api/config-export.js +4 -2
  32. package/src/server/api/config-registry.js +40 -34
  33. package/src/server/api/config.js +2 -1
  34. package/src/server/api/dashboard.js +22 -6
  35. package/src/server/api/mcp.js +5 -3
  36. package/src/server/api/pi-channels.js +349 -0
  37. package/src/server/api/pi-config.js +164 -0
  38. package/src/server/api/pi-projects.js +76 -0
  39. package/src/server/api/pi-proxy.js +154 -0
  40. package/src/server/api/pi-sessions.js +334 -0
  41. package/src/server/api/pi-statistics.js +40 -0
  42. package/src/server/api/plugins.js +1 -1
  43. package/src/server/api/prompts.js +3 -2
  44. package/src/server/api/skills.js +1 -1
  45. package/src/server/api/workspaces.js +2 -2
  46. package/src/server/index.js +41 -0
  47. package/src/server/pi-proxy-server.js +66 -0
  48. package/src/server/services/agents-service.js +2 -1
  49. package/src/server/services/base/proxy-utils.js +1 -0
  50. package/src/server/services/channel-balance.js +151 -1
  51. package/src/server/services/channel-scheduler.js +7 -1
  52. package/src/server/services/commands-service.js +11 -2
  53. package/src/server/services/config-export-service.js +275 -18
  54. package/src/server/services/config-registry-service.js +20 -18
  55. package/src/server/services/config-sync-manager.js +115 -6
  56. package/src/server/services/config-sync-service.js +4 -2
  57. package/src/server/services/config-templates-service.js +183 -45
  58. package/src/server/services/mcp-service.js +217 -33
  59. package/src/server/services/pi-auth-providers.js +294 -0
  60. package/src/server/services/pi-channels.js +94 -0
  61. package/src/server/services/pi-config.js +287 -0
  62. package/src/server/services/pi-sessions.js +490 -0
  63. package/src/server/services/pi-settings-manager.js +571 -0
  64. package/src/server/services/pi-statistics-service.js +317 -0
  65. package/src/server/services/plugins-service.js +673 -11
  66. package/src/server/services/prompts-service.js +112 -7
  67. package/src/server/services/proxy-log-helper.js +1 -0
  68. package/src/server/services/proxy-runtime.js +5 -1
  69. package/src/server/services/skill-service.js +13 -3
  70. package/src/server/services/speed-test.js +9 -3
  71. package/src/server/services/ui-config.js +50 -12
  72. package/src/server/services/workspace-service.js +19 -5
  73. package/src/server/websocket-server.js +7 -1
  74. package/dist/web/assets/Home-BvAzzWnB.js +0 -1
  75. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  76. package/dist/web/assets/ProjectList-nqgi6_cR.js +0 -1
  77. package/dist/web/assets/SessionList-D8AxMYy6.js +0 -1
  78. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  79. package/dist/web/assets/index-y-eEayxR.js +0 -2
  80. package/src/utils/cli-flags.js +0 -7
@@ -0,0 +1,66 @@
1
+ const { loadConfig } = require('../config/loader');
2
+ const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
3
+ const { syncManagedOmpProviders, disableManagedOmpProviders, getEnabledChannels } = require('./services/pi-channels');
4
+
5
+ let running = false;
6
+ let currentPort = null;
7
+ let lastSyncResult = null;
8
+
9
+ async function startPiProxyServer(options = {}) {
10
+ const preserveStartTime = options.preserveStartTime || false;
11
+ const config = loadConfig();
12
+ currentPort = config.ports?.piProxy || 20092;
13
+ lastSyncResult = syncManagedOmpProviders();
14
+ running = true;
15
+ saveProxyStartTime('pi', preserveStartTime);
16
+ return {
17
+ success: true,
18
+ port: currentPort,
19
+ sync: lastSyncResult,
20
+ warnings: lastSyncResult?.warnings || []
21
+ };
22
+ }
23
+
24
+ async function stopPiProxyServer(options = {}) {
25
+ const clearStartTime = options.clearStartTime !== false;
26
+ const stoppedPort = currentPort;
27
+ lastSyncResult = disableManagedOmpProviders();
28
+ running = false;
29
+ currentPort = null;
30
+ if (clearStartTime) {
31
+ clearProxyStartTime('pi');
32
+ }
33
+ return {
34
+ success: true,
35
+ port: stoppedPort,
36
+ sync: lastSyncResult,
37
+ warnings: lastSyncResult?.warnings || []
38
+ };
39
+ }
40
+
41
+ function getPiProxyStatus() {
42
+ const config = loadConfig();
43
+ const startTime = getProxyStartTime('pi', { allowRecovery: running });
44
+ const runtime = getProxyRuntime('pi', { allowRecovery: running });
45
+ const enabledCount = getEnabledChannels().length;
46
+ const validation = lastSyncResult?.validation || null;
47
+ return {
48
+ running,
49
+ port: currentPort,
50
+ defaultPort: config.ports?.piProxy || 20092,
51
+ startTime,
52
+ runtime,
53
+ mode: 'models-yml-provider-config',
54
+ enabledChannelsCount: enabledCount,
55
+ modelsPath: lastSyncResult?.modelsPath || lastSyncResult?.path || null,
56
+ backupPath: lastSyncResult?.backupPath || null,
57
+ modelsValidation: validation,
58
+ warnings: lastSyncResult?.warnings || []
59
+ };
60
+ }
61
+
62
+ module.exports = {
63
+ startPiProxyServer,
64
+ stopPiProxyServer,
65
+ getPiProxyStatus
66
+ };
@@ -26,7 +26,8 @@ const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
26
26
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
27
27
  const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
28
28
  const CODEX_AGENTS_DIR = path.join(path.dirname(CODEX_CONFIG_PATH), 'agents');
29
- const CLAUDE_AGENTS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'agents');
29
+ const CLAUDE_AGENTS_DIR = NATIVE_PATHS.claude.agents
30
+ || path.join(NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings), 'agents');
30
31
  const GEMINI_AGENTS_DIR = path.join(NATIVE_PATHS.gemini.dir, 'agents');
31
32
  const CODEX_CONFIG_MODES = new Set(['none', 'managed', 'custom']);
32
33
 
@@ -101,6 +101,7 @@ function normalizeGatewaySourceType(value, fallback = 'claude') {
101
101
  if (normalized === 'claude') return 'claude';
102
102
  if (normalized === 'codex') return 'codex';
103
103
  if (normalized === 'gemini') return 'gemini';
104
+ if (normalized === 'opencode') return 'opencode';
104
105
  if (normalized === 'openai_compatible') return 'openai_compatible';
105
106
  return fallback;
106
107
  }
@@ -12,7 +12,8 @@ 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 VALID_SOURCES = new Set(['claude', 'codex', 'gemini', 'opencode']);
15
+ const NEWCLI_QUOTA_UNIT = 1000000;
16
+ const VALID_SOURCES = new Set(['claude', 'codex', 'gemini', 'opencode', 'pi']);
16
17
  const GATEWAY_PATH_SEGMENTS = new Set(['openai', 'anthropic', 'claude', 'gemini', 'codex']);
17
18
  const HUB_PLATFORMS = new Set(['new-api', 'one-api', 'one-hub', 'done-hub', 'veloera', 'anyrouter', 'aihubmix']);
18
19
  const UNSUPPORTED_BALANCE_PLATFORMS = new Set(['dashscope', 'modelscope']);
@@ -415,6 +416,7 @@ async function tryRequestJson(url, options) {
415
416
  function detectPlatformByUrlHint(...values) {
416
417
  const value = values.map(item => String(item || '')).join(' ').toLowerCase();
417
418
  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';
418
420
  if (value.includes('openrouter.ai') || value.includes('openrouter')) return 'openrouter';
419
421
  if (value.includes('aihubmix')) return 'aihubmix';
420
422
  if (value.includes('dashscope') || value.includes('dashscope.aliyuncs.com') || value.includes('compatible-mode')) return 'dashscope';
@@ -600,6 +602,77 @@ function buildSiliconFlowUserInfoSnapshot(payload) {
600
602
  });
601
603
  }
602
604
 
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
+
603
676
  function extractQuotaUnitFromStatus(payload) {
604
677
  if (!payload || typeof payload !== 'object') return null;
605
678
  const data = payload.data && typeof payload.data === 'object' ? payload.data : payload;
@@ -791,6 +864,41 @@ function buildSiliconFlowApiBaseCandidates(baseUrl, channel = {}) {
791
864
  return candidates;
792
865
  }
793
866
 
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
+
794
902
  function hasGatewayPathSegment(value) {
795
903
  try {
796
904
  const parsed = new URL(normalizeBaseUrl(value));
@@ -1047,12 +1155,27 @@ function buildBalanceProbeStrategies(baseUrl, channel = {}, token = '') {
1047
1155
  add({ type: 'siliconflow-user-info', baseUrl, apiBase });
1048
1156
  }
1049
1157
  };
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
+ };
1050
1168
 
1051
1169
  if (UNSUPPORTED_BALANCE_PLATFORMS.has(hintedPlatform)) {
1052
1170
  add({ type: 'unsupported-balance', platform: hintedPlatform, baseUrl });
1053
1171
  return strategies;
1054
1172
  }
1055
1173
 
1174
+ if (hintedPlatform === 'newcli') {
1175
+ addNewCliStrategies();
1176
+ return strategies;
1177
+ }
1178
+
1056
1179
  if (hintedPlatform === 'siliconflow') {
1057
1180
  addSiliconFlowStrategies();
1058
1181
  return strategies;
@@ -1224,6 +1347,21 @@ async function runSiliconFlowUserInfoStrategy(strategy, token) {
1224
1347
  return buildSiliconFlowUserInfoSnapshot(payload);
1225
1348
  }
1226
1349
 
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
+
1227
1365
  async function runBalanceStrategy(strategy, token, channel = {}) {
1228
1366
  if (!strategy || !strategy.type) {
1229
1367
  return { snapshot: makeHiddenSnapshot(), strategy };
@@ -1295,6 +1433,12 @@ async function runBalanceStrategy(strategy, token, channel = {}) {
1295
1433
  strategy
1296
1434
  };
1297
1435
  }
1436
+ if (strategy.type === 'newcli-dashboard') {
1437
+ return {
1438
+ snapshot: await runNewCliDashboardStrategy(strategy, token, channel),
1439
+ strategy
1440
+ };
1441
+ }
1298
1442
 
1299
1443
  return { snapshot: makeHiddenSnapshot(), strategy };
1300
1444
  }
@@ -1320,6 +1464,7 @@ async function probeBalanceFromBase(baseUrl, channel, token) {
1320
1464
  strategy.type?.startsWith('88code') ? '88code'
1321
1465
  : strategy.type?.startsWith('openrouter') ? 'openrouter'
1322
1466
  : strategy.type?.startsWith('siliconflow') ? 'siliconflow'
1467
+ : strategy.type?.startsWith('newcli') ? 'newcli'
1323
1468
  : strategy.type?.startsWith('sub2api') ? 'sub2api'
1324
1469
  : strategy.platform || null
1325
1470
  );
@@ -1477,6 +1622,9 @@ function getChannelsForSource(source) {
1477
1622
  if (source === 'opencode') {
1478
1623
  return require('./opencode-channels').getChannels().channels || [];
1479
1624
  }
1625
+ if (source === 'pi') {
1626
+ return require('./pi-channels').getChannels().channels || [];
1627
+ }
1480
1628
  return [];
1481
1629
  }
1482
1630
 
@@ -1586,8 +1734,10 @@ module.exports = {
1586
1734
  build88CodeApiBaseCandidates,
1587
1735
  buildOpenRouterApiBaseCandidates,
1588
1736
  buildSiliconFlowApiBaseCandidates,
1737
+ buildNewCliWebBaseCandidates,
1589
1738
  build88CodeUsageSnapshot,
1590
1739
  build88CodeSubscriptionSnapshot,
1740
+ buildNewCliDashboardSnapshot,
1591
1741
  buildBalanceProbeStrategies,
1592
1742
  getEnabledBalanceChannels,
1593
1743
  runBalanceStrategy,
@@ -2,6 +2,7 @@ const { getAllChannels } = require('./channels');
2
2
  const { getChannels: getCodexChannels } = require('./codex-channels');
3
3
  const { getChannels: getGeminiChannels } = require('./gemini-channels');
4
4
  const { getChannels: getOpenCodeChannels } = require('./opencode-channels');
5
+ const { getChannels: getPiChannels } = require('./pi-channels');
5
6
  const { isChannelAvailable, getChannelHealthStatus, setOnChannelFrozen, setChannelListProvider } = require('./channel-health');
6
7
 
7
8
  const channelProviders = {
@@ -17,6 +18,10 @@ const channelProviders = {
17
18
  opencode: () => {
18
19
  const data = getOpenCodeChannels();
19
20
  return Array.isArray(data?.channels) ? data.channels : [];
21
+ },
22
+ pi: () => {
23
+ const data = getPiChannels();
24
+ return Array.isArray(data?.channels) ? data.channels : [];
20
25
  }
21
26
  };
22
27
 
@@ -33,7 +38,8 @@ const schedulerStates = {
33
38
  claude: createState(),
34
39
  codex: createState(),
35
40
  gemini: createState(),
36
- opencode: createState()
41
+ opencode: createState(),
42
+ pi: createState()
37
43
  };
38
44
 
39
45
  function getState(source = 'claude') {
@@ -22,9 +22,10 @@ const {
22
22
 
23
23
  // 默认仓库源
24
24
  const DEFAULT_REPOS = [];
25
- const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
25
+ const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
26
26
  const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
27
- const CLAUDE_COMMANDS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'commands');
27
+ const CLAUDE_COMMANDS_DIR = NATIVE_PATHS.claude.commands
28
+ || path.join(NATIVE_PATHS.claude.dir || path.dirname(NATIVE_PATHS.claude.settings), 'commands');
28
29
  const CODEX_COMMANDS_DIR = path.join(path.dirname(NATIVE_PATHS.codex.config), 'commands');
29
30
  const GEMINI_COMMANDS_DIR = path.join(NATIVE_PATHS.gemini.dir, 'commands');
30
31
 
@@ -57,6 +58,11 @@ const PLATFORM_CONFIG = {
57
58
  projectCommandsDir: (projectPath) => path.join(projectPath, '.gemini', 'commands'),
58
59
  repoType: 'gemini-commands',
59
60
  fileExtension: '.toml'
61
+ },
62
+ pi: {
63
+ userCommandsDir: NATIVE_PATHS.pi.commands || path.join(NATIVE_PATHS.pi.dir, 'commands'),
64
+ projectCommandsDir: (projectPath) => path.join(projectPath, '.omp', 'commands'),
65
+ repoType: 'pi-commands'
60
66
  }
61
67
  };
62
68
 
@@ -373,6 +379,9 @@ class CommandsService {
373
379
  if (this.platform === 'gemini') {
374
380
  return generateGeminiCommandToml({ description, body });
375
381
  }
382
+ if (this.platform === 'pi') {
383
+ return body || '';
384
+ }
376
385
 
377
386
  const frontmatterData = {};
378
387
  if (description) frontmatterData.description = description;