coding-tool-x 3.7.1 → 3.7.3

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 (36) hide show
  1. package/README.md +5 -5
  2. package/dist/web/assets/{Analytics-Dpje9L84.js → Analytics-BqDjJUJl.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-Cz6kasek.js → ConfigTemplates-DV2KFuC4.js} +1 -1
  4. package/dist/web/assets/{Home-BwTaYu3q.js → Home-D7hovkk9.js} +1 -1
  5. package/dist/web/assets/{PluginManager-DsN-xNXx.js → PluginManager-Bd3hhc5A.js} +1 -1
  6. package/dist/web/assets/{ProjectList-4BCObimU.js → ProjectList-BB4Whns8.js} +1 -1
  7. package/dist/web/assets/{SessionList-D4c8FRzq.js → SessionList-CvurKSRK.js} +1 -1
  8. package/dist/web/assets/{SkillManager-C-KYbXB5.js → SkillManager-DQjRXOFg.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-B-BlPJHX.js → WorkspaceManager-CZeXnxu1.js} +1 -1
  10. package/dist/web/assets/{icons-Dop5j7Bf.js → icons-nGdSKQn2.js} +1 -1
  11. package/dist/web/assets/index-BovYCFsd.js +2 -0
  12. package/dist/web/assets/index-kQCC5dlu.css +1 -0
  13. package/dist/web/index.html +3 -3
  14. package/package.json +1 -1
  15. package/src/commands/ui.js +1 -0
  16. package/src/index.js +3 -2
  17. package/src/server/api/channels.js +14 -1
  18. package/src/server/api/codex-channels.js +21 -1
  19. package/src/server/api/gemini-channels.js +21 -1
  20. package/src/server/api/oauth-credentials.js +15 -2
  21. package/src/server/api/omp-channels.js +117 -39
  22. package/src/server/api/opencode-channels.js +21 -1
  23. package/src/server/index.js +13 -5
  24. package/src/server/services/channel-sync-utils.js +368 -0
  25. package/src/server/services/channels.js +119 -0
  26. package/src/server/services/codex-channels.js +125 -0
  27. package/src/server/services/gemini-channels.js +123 -0
  28. package/src/server/services/model-detector.js +15 -2
  29. package/src/server/services/native-oauth-adapters.js +212 -1
  30. package/src/server/services/network-access.js +5 -0
  31. package/src/server/services/oauth-credentials-service.js +110 -3
  32. package/src/server/services/omp-channels.js +304 -2
  33. package/src/server/services/omp-settings-manager.js +2 -0
  34. package/src/server/services/opencode-channels.js +172 -4
  35. package/dist/web/assets/index-Btfvh0GG.js +0 -2
  36. package/dist/web/assets/index-CLNa6DhH.css +0 -1
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
  const crypto = require('crypto');
5
+ const { spawnSync } = require('child_process');
5
6
  const toml = require('toml');
6
7
  const tomlStringify = require('@iarna/toml').stringify;
7
8
  const { NATIVE_PATHS, PATHS } = require('../../config/paths');
@@ -13,7 +14,7 @@ const { syncCodexUserEnvironment } = require('./codex-env-manager');
13
14
  const nativeKeychain = require('./native-keychain');
14
15
  const { maskToken, decodeJwtPayload, removeFileIfExists, sha256 } = require('./oauth-utils');
15
16
 
16
- const SUPPORTED_TOOLS = ['claude', 'codex', 'gemini', 'opencode'];
17
+ const SUPPORTED_TOOLS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
17
18
  const GEMINI_MAIN_ACCOUNT_KEY = 'main-account';
18
19
  const GEMINI_KEYCHAIN_SERVICE = 'gemini-cli-oauth';
19
20
  const CODEX_KEYCHAIN_SERVICE = 'Codex Auth';
@@ -1006,6 +1007,202 @@ function inspectOpenCodeState() {
1006
1007
  };
1007
1008
  }
1008
1009
 
1010
+ function resolveOmpRuntime() {
1011
+ try {
1012
+ const ompConfig = require('./omp-config');
1013
+ const runtime = ompConfig.resolveOmpRuntime(process.env, {});
1014
+ return runtime && runtime.runtime === 'omp' && runtime.installed ? runtime : null;
1015
+ } catch {
1016
+ return null;
1017
+ }
1018
+ }
1019
+
1020
+ function runOmpCommand(args = [], options = {}) {
1021
+ const runtime = options.runtime || resolveOmpRuntime();
1022
+ if (!runtime) {
1023
+ return { ok: false, status: 127, stdout: '', stderr: 'OMP CLI not installed' };
1024
+ }
1025
+
1026
+ const result = spawnSync(runtime.command, args, {
1027
+ encoding: 'utf8',
1028
+ env: {
1029
+ ...process.env,
1030
+ ...(options.env || {})
1031
+ },
1032
+ timeout: options.timeout || 10000
1033
+ });
1034
+ const status = result?.status === undefined || result?.status === null ? 0 : result.status;
1035
+ return {
1036
+ ok: !result?.error && status === 0,
1037
+ status,
1038
+ stdout: String(result?.stdout || ''),
1039
+ stderr: String(result?.stderr || ''),
1040
+ error: result?.error || null,
1041
+ command: runtime.command
1042
+ };
1043
+ }
1044
+
1045
+ function getOmpAuthSnapshot(options = {}) {
1046
+ try {
1047
+ const { getOmpAuthProviderSnapshot } = require('./omp-auth-providers');
1048
+ return getOmpAuthProviderSnapshot({
1049
+ accountCheck: true,
1050
+ includeStatus: false,
1051
+ ...options
1052
+ });
1053
+ } catch {
1054
+ return null;
1055
+ }
1056
+ }
1057
+
1058
+ function readAllOmpNativeOAuth() {
1059
+ const snapshot = getOmpAuthSnapshot({ forceRefresh: true });
1060
+ if (!snapshot?.available || !Array.isArray(snapshot.providers)) {
1061
+ return [];
1062
+ }
1063
+
1064
+ return snapshot.providers
1065
+ .filter(provider => provider?.loggedIn || Number(provider?.accountCount || 0) > 0)
1066
+ .flatMap((provider) => {
1067
+ const accounts = Array.isArray(provider.accounts) && provider.accounts.length > 0
1068
+ ? provider.accounts
1069
+ : [{ index: 1, identity: '' }];
1070
+ return accounts.map((account) => ({
1071
+ providerId: provider.id,
1072
+ accountId: String(account.index || '').trim(),
1073
+ accountEmail: String(account.identity || '').trim(),
1074
+ storage: 'auth-broker',
1075
+ primaryToken: ''
1076
+ }));
1077
+ });
1078
+ }
1079
+
1080
+ function readOmpNativeOAuth() {
1081
+ return readAllOmpNativeOAuth()[0] || null;
1082
+ }
1083
+
1084
+ function clearOmpOAuth() {
1085
+ const credentials = readAllOmpNativeOAuth();
1086
+ const providerIds = [...new Set(credentials.map(item => item.providerId).filter(Boolean))];
1087
+ providerIds.forEach((providerId) => {
1088
+ runOmpCommand(['auth-broker', 'logout', providerId], { timeout: 10000 });
1089
+ });
1090
+ try {
1091
+ require('./omp-auth-providers').clearOmpAuthProviderCache();
1092
+ } catch {
1093
+ // cache invalidation is best-effort
1094
+ }
1095
+ }
1096
+
1097
+ function disableOmpOAuthCredential(credential = {}) {
1098
+ const providerId = String(credential.providerId || '').trim();
1099
+ if (!providerId) {
1100
+ clearOmpOAuth();
1101
+ return;
1102
+ }
1103
+ runOmpCommand(['auth-broker', 'logout', providerId], { timeout: 10000 });
1104
+ try {
1105
+ require('./omp-auth-providers').clearOmpAuthProviderCache();
1106
+ } catch {
1107
+ // cache invalidation is best-effort
1108
+ }
1109
+ }
1110
+
1111
+ function buildOmpImportPayload(credential = {}) {
1112
+ if (credential.importPayload && typeof credential.importPayload === 'object') {
1113
+ return credential.importPayload;
1114
+ }
1115
+
1116
+ const access = String(credential.accessToken || credential.primaryToken || '').trim();
1117
+ if (!access) {
1118
+ return null;
1119
+ }
1120
+
1121
+ return {
1122
+ provider: credential.providerId,
1123
+ credential_type: credential.credentialType || 'oauth',
1124
+ identity_key: credential.identityKey || credential.accountId || credential.accountEmail || undefined,
1125
+ data: {
1126
+ access,
1127
+ refresh: credential.refreshToken || '',
1128
+ expires: credential.expiresAt || null,
1129
+ accountId: credential.accountId || undefined,
1130
+ accountEmail: credential.accountEmail || undefined
1131
+ }
1132
+ };
1133
+ }
1134
+
1135
+ function hasOmpNativeCredential(providerId) {
1136
+ return readAllOmpNativeOAuth().some((entry) => (
1137
+ !providerId || entry.providerId === providerId
1138
+ ));
1139
+ }
1140
+
1141
+ function applyOmpOAuth(credential = {}) {
1142
+ const providerId = String(credential.providerId || '').trim();
1143
+ if (!providerId) {
1144
+ throw new Error('OMP OAuth credential requires providerId');
1145
+ }
1146
+
1147
+ const payload = buildOmpImportPayload(credential);
1148
+ if (!payload) {
1149
+ if (hasOmpNativeCredential(providerId)) {
1150
+ return { storage: 'auth-broker-existing' };
1151
+ }
1152
+ throw new Error('OMP OAuth credential requires an import payload or access token');
1153
+ }
1154
+
1155
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctx-omp-oauth-'));
1156
+ const tempPath = path.join(tempDir, `${providerId}.json`);
1157
+ try {
1158
+ fs.writeFileSync(tempPath, JSON.stringify(payload, null, 2), 'utf8');
1159
+ ensureFileMode(tempPath);
1160
+ const result = runOmpCommand(['auth-broker', 'import', tempPath, '--provider', providerId], { timeout: 15000 });
1161
+ if (!result.ok) {
1162
+ const detail = result.stderr.trim() || result.stdout.trim() || result.error?.message || `exit code ${result.status}`;
1163
+ throw new Error(`OMP auth-broker import failed: ${detail}`);
1164
+ }
1165
+ try {
1166
+ require('./omp-auth-providers').clearOmpAuthProviderCache();
1167
+ } catch {
1168
+ // cache invalidation is best-effort
1169
+ }
1170
+ return { storage: 'auth-broker' };
1171
+ } finally {
1172
+ try {
1173
+ removeFileIfExists(tempPath);
1174
+ fs.rmSync(tempDir, { recursive: true, force: true });
1175
+ } catch {
1176
+ // ignore cleanup failures
1177
+ }
1178
+ }
1179
+ }
1180
+
1181
+ function inspectOmpState() {
1182
+ const { getOmpProxyStatus } = require('../omp-proxy-server');
1183
+ const { isManagedOmpProvidersActive } = require('./omp-settings-manager');
1184
+ const proxyStatus = getOmpProxyStatus();
1185
+ const nativeCredentials = readAllOmpNativeOAuth();
1186
+
1187
+ let channelConfigured = false;
1188
+ try {
1189
+ channelConfigured = Boolean(isManagedOmpProvidersActive());
1190
+ } catch {
1191
+ channelConfigured = false;
1192
+ }
1193
+
1194
+ return {
1195
+ tool: 'omp',
1196
+ mode: proxyStatus.running || channelConfigured
1197
+ ? 'proxy'
1198
+ : (nativeCredentials.length > 0 ? 'oauth' : 'idle'),
1199
+ proxyRunning: Boolean(proxyStatus.running || channelConfigured),
1200
+ oauthPresent: nativeCredentials.length > 0,
1201
+ channelConfigured,
1202
+ nativeCredential: nativeCredentials[0] ? buildNativeSummary(nativeCredentials[0]) : null
1203
+ };
1204
+ }
1205
+
1009
1206
  function inspectTool(tool) {
1010
1207
  switch (tool) {
1011
1208
  case 'claude':
@@ -1016,6 +1213,8 @@ function inspectTool(tool) {
1016
1213
  return inspectGeminiState();
1017
1214
  case 'opencode':
1018
1215
  return inspectOpenCodeState();
1216
+ case 'omp':
1217
+ return inspectOmpState();
1019
1218
  default:
1020
1219
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1021
1220
  }
@@ -1031,6 +1230,8 @@ function readNativeOAuth(tool) {
1031
1230
  return readGeminiNativeOAuth();
1032
1231
  case 'opencode':
1033
1232
  return readOpenCodeNativeOAuth();
1233
+ case 'omp':
1234
+ return readOmpNativeOAuth();
1034
1235
  default:
1035
1236
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1036
1237
  }
@@ -1052,6 +1253,8 @@ function readAllNativeOAuth(tool) {
1052
1253
  }
1053
1254
  case 'opencode':
1054
1255
  return readAllOpenCodeNativeOAuth();
1256
+ case 'omp':
1257
+ return readAllOmpNativeOAuth();
1055
1258
  default:
1056
1259
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1057
1260
  }
@@ -1071,6 +1274,9 @@ function clearNativeOAuth(tool) {
1071
1274
  case 'opencode':
1072
1275
  clearOpenCodeOAuth();
1073
1276
  return;
1277
+ case 'omp':
1278
+ clearOmpOAuth();
1279
+ return;
1074
1280
  default:
1075
1281
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1076
1282
  }
@@ -1090,6 +1296,9 @@ function disableNativeOAuthCredential(tool, credential = {}) {
1090
1296
  case 'opencode':
1091
1297
  disableOpenCodeOAuthCredential(credential);
1092
1298
  return;
1299
+ case 'omp':
1300
+ disableOmpOAuthCredential(credential);
1301
+ return;
1093
1302
  default:
1094
1303
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1095
1304
  }
@@ -1105,6 +1314,8 @@ function applyOAuthCredential(tool, credential) {
1105
1314
  return applyGeminiOAuth(credential);
1106
1315
  case 'opencode':
1107
1316
  return applyOpenCodeOAuth(credential);
1317
+ case 'omp':
1318
+ return applyOmpOAuth(credential);
1108
1319
  default:
1109
1320
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1110
1321
  }
@@ -72,6 +72,10 @@ function createRemoteMutationGuard(options = {}) {
72
72
  };
73
73
  }
74
74
 
75
+ function isRemoteMutationAllowedByEnv(env = process.env) {
76
+ return String(env.CC_TOOL_ALLOW_REMOTE_WRITE || '').toLowerCase() !== 'false';
77
+ }
78
+
75
79
  function createRemoteRouteGuard(options = {}) {
76
80
  const enabled = options.enabled === true;
77
81
  const allowRemoteAccess = options.allowRemoteAccess === true;
@@ -113,6 +117,7 @@ module.exports = {
113
117
  isLoopbackRequest,
114
118
  isSameOriginRequest,
115
119
  createRemoteMutationGuard,
120
+ isRemoteMutationAllowedByEnv,
116
121
  createRemoteRouteGuard,
117
122
  createSameOriginGuard
118
123
  };
@@ -240,6 +240,70 @@ function parseOpenCodeImport(rawText) {
240
240
  };
241
241
  }
242
242
 
243
+ function pickOmpCredentialEntry(parsed) {
244
+ if (Array.isArray(parsed)) {
245
+ return parsed.find(item => item && typeof item === 'object') || null;
246
+ }
247
+ if (!parsed || typeof parsed !== 'object') {
248
+ return null;
249
+ }
250
+ if (parsed.provider || parsed.providerId || parsed.provider_id || parsed.providerKey || parsed.credential_type || parsed.data) {
251
+ return parsed;
252
+ }
253
+ const entry = Object.entries(parsed).find(([, value]) => value && typeof value === 'object');
254
+ return entry ? { provider: entry[0], ...entry[1] } : parsed;
255
+ }
256
+
257
+ function parseOmpImport(rawText) {
258
+ const parsed = tryParseJson(rawText);
259
+ if (!parsed || typeof parsed !== 'object') {
260
+ throw new Error('OMP OAuth 仅支持 JSON 导入。');
261
+ }
262
+
263
+ const entry = pickOmpCredentialEntry(parsed);
264
+ if (!entry || typeof entry !== 'object') {
265
+ throw new Error('OMP OAuth 导入缺少 provider。');
266
+ }
267
+
268
+ const data = entry.data && typeof entry.data === 'object' ? entry.data : entry;
269
+ const providerId = safeString(
270
+ entry.providerId
271
+ || entry.provider_id
272
+ || entry.providerKey
273
+ || entry.provider
274
+ || data.providerId
275
+ || data.provider
276
+ );
277
+ if (!providerId) {
278
+ throw new Error('OMP OAuth 导入缺少 providerId。');
279
+ }
280
+
281
+ const accessToken = safeString(
282
+ data.accessToken
283
+ || data.access_token
284
+ || data.access
285
+ || data.token
286
+ || data.authToken
287
+ );
288
+ const credentialType = safeString(entry.credentialType || entry.credential_type || data.type || 'oauth') || 'oauth';
289
+ if (credentialType === 'oauth' && !accessToken && !entry.data) {
290
+ throw new Error('OMP OAuth 导入缺少 access/accessToken。');
291
+ }
292
+
293
+ return {
294
+ providerId,
295
+ credentialType,
296
+ accessToken,
297
+ refreshToken: safeString(data.refreshToken || data.refresh_token || data.refresh),
298
+ expiresAt: safeNumber(data.expiresAt || data.expiry_date || data.expiryDate || data.expires),
299
+ accountId: safeString(data.accountId || data.account_id || entry.accountId || entry.account_id),
300
+ accountEmail: safeString(data.accountEmail || data.email || entry.accountEmail || entry.email),
301
+ identityKey: safeString(entry.identityKey || entry.identity_key || data.identityKey || data.identity_key),
302
+ importPayload: parsed,
303
+ primaryToken: accessToken
304
+ };
305
+ }
306
+
243
307
  function parseCredentialInput(tool, rawText) {
244
308
  switch (tool) {
245
309
  case 'claude':
@@ -250,6 +314,8 @@ function parseCredentialInput(tool, rawText) {
250
314
  return parseGeminiImport(rawText);
251
315
  case 'opencode':
252
316
  return parseOpenCodeImport(rawText);
317
+ case 'omp':
318
+ return parseOmpImport(rawText);
253
319
  default:
254
320
  throw new Error(`Unsupported OAuth tool: ${tool}`);
255
321
  }
@@ -261,7 +327,7 @@ function buildCredentialName(tool, metadata, providedName = '') {
261
327
  return explicit;
262
328
  }
263
329
 
264
- if (tool === 'opencode' && safeString(metadata.providerId)) {
330
+ if ((tool === 'opencode' || tool === 'omp') && safeString(metadata.providerId)) {
265
331
  const accountLabel = safeString(metadata.accountId || metadata.accountEmail);
266
332
  return accountLabel
267
333
  ? `${tool} - ${metadata.providerId} - ${accountLabel}`
@@ -386,6 +452,19 @@ function extractSecrets(tool, metadata) {
386
452
  enterpriseUrl: metadata.enterpriseUrl || '',
387
453
  primaryToken: metadata.primaryToken || metadata.accessToken || ''
388
454
  };
455
+ case 'omp':
456
+ return {
457
+ providerId: metadata.providerId || '',
458
+ credentialType: metadata.credentialType || 'oauth',
459
+ accessToken: metadata.accessToken || '',
460
+ refreshToken: metadata.refreshToken || '',
461
+ expiresAt: metadata.expiresAt || null,
462
+ accountId: metadata.accountId || '',
463
+ accountEmail: metadata.accountEmail || '',
464
+ identityKey: metadata.identityKey || '',
465
+ importPayload: metadata.importPayload || null,
466
+ primaryToken: metadata.primaryToken || metadata.accessToken || ''
467
+ };
389
468
  default:
390
469
  return { primaryToken: metadata.primaryToken || metadata.accessToken || '' };
391
470
  }
@@ -395,7 +474,7 @@ function stableFingerprintValue(tool, metadata) {
395
474
  // 优先使用稳定标识符,避免 access token 轮换导致重复记录
396
475
  const stableId = metadata.accountEmail
397
476
  || metadata.accountId
398
- || (tool === 'opencode' ? metadata.providerId : '')
477
+ || ((tool === 'opencode' || tool === 'omp') ? metadata.providerId : '')
399
478
  || metadata.refreshToken
400
479
  || metadata.primaryToken
401
480
  || metadata.accessToken
@@ -559,6 +638,11 @@ function cleanupManagedArtifacts(tool) {
559
638
 
560
639
  if (tool === 'opencode') {
561
640
  opencodeSettingsManager.deleteBackup?.();
641
+ return;
642
+ }
643
+
644
+ if (tool === 'omp') {
645
+ return;
562
646
  }
563
647
  }
564
648
 
@@ -597,6 +681,14 @@ async function stopProxyIfRunning(tool) {
597
681
  }
598
682
  return false;
599
683
  }
684
+ case 'omp': {
685
+ const { stopOmpProxyServer, getOmpProxyStatus } = require('../omp-proxy-server');
686
+ if (getOmpProxyStatus().running) {
687
+ await stopOmpProxyServer();
688
+ return true;
689
+ }
690
+ return false;
691
+ }
600
692
  default:
601
693
  throw new Error(`Unsupported OAuth tool: ${tool}`);
602
694
  }
@@ -625,6 +717,11 @@ function disableAllChannelsForTool(tool) {
625
717
  disableAllChannels();
626
718
  break;
627
719
  }
720
+ case 'omp': {
721
+ const { disableAllChannels } = require('./omp-channels');
722
+ disableAllChannels();
723
+ break;
724
+ }
628
725
  }
629
726
  } catch (err) {
630
727
  console.warn(`[OAuth] Failed to disable channels for ${tool}:`, err.message);
@@ -635,7 +732,7 @@ async function applyStoredCredential(tool, credentialId) {
635
732
  const entry = findStoredCredential(tool, credentialId);
636
733
  const proxyStopped = await stopProxyIfRunning(tool);
637
734
  cleanupManagedArtifacts(tool);
638
- if (tool !== 'opencode') {
735
+ if (tool !== 'opencode' && tool !== 'omp') {
639
736
  disableAllChannelsForTool(tool);
640
737
  }
641
738
  applyOAuthCredential(tool, entry.secrets);
@@ -818,6 +915,16 @@ async function fetchCredentialUsage(tool, credentialId) {
818
915
  return await fetchCodexUsage(accessToken);
819
916
  }
820
917
  }
918
+ case 'omp': {
919
+ const providerId = entry.providerId || secrets.providerId || '';
920
+ if (providerId.includes('claude') || providerId.includes('anthropic')) {
921
+ return await fetchClaudeUsage(accessToken);
922
+ } else if (providerId.includes('gemini') || providerId.includes('google')) {
923
+ return await fetchGeminiUsage(accessToken);
924
+ } else {
925
+ return await fetchCodexUsage(accessToken);
926
+ }
927
+ }
821
928
  default:
822
929
  return { error: `不支持的工具: ${tool}` };
823
930
  }