coding-tool-x 3.6.13 → 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.
- package/README.md +46 -33
- package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dma_j2Rq.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-C49Y1PLH.js} +1 -1
- package/dist/web/assets/Home-DGGC8H4g.js +1 -0
- package/dist/web/assets/Home-ewgeIhF7.css +1 -0
- package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-B1ECTHyY.js} +1 -1
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
- package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
- package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
- package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-CRyVhjYQ.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-CO9tclVt.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-CM26UrnJ.css +1 -0
- package/dist/web/assets/index-EQq56pRa.js +2 -0
- package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +2 -1
- package/src/commands/channels.js +85 -24
- package/src/commands/daemon.js +7 -4
- package/src/commands/doctor.js +17 -8
- package/src/commands/logs.js +48 -6
- package/src/commands/port-config.js +83 -75
- package/src/commands/proxy-control.js +52 -19
- package/src/commands/stats.js +37 -19
- package/src/commands/switch.js +5 -1
- package/src/commands/toggle-proxy.js +131 -53
- package/src/commands/ui.js +3 -4
- package/src/commands/workspace.js +3 -35
- package/src/config/default.js +19 -2
- package/src/config/loader.js +5 -76
- package/src/config/paths.js +99 -11
- package/src/index.js +11 -16
- package/src/server/api/commands.js +1 -1
- package/src/server/api/config-export.js +4 -2
- package/src/server/api/config-registry.js +40 -34
- package/src/server/api/config.js +5 -1
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/mcp.js +5 -3
- package/src/server/api/pi-channels.js +349 -0
- package/src/server/api/pi-config.js +164 -0
- package/src/server/api/pi-projects.js +76 -0
- package/src/server/api/pi-proxy.js +154 -0
- package/src/server/api/pi-sessions.js +334 -0
- package/src/server/api/pi-statistics.js +40 -0
- package/src/server/api/plugins.js +1 -1
- package/src/server/api/prompts.js +3 -2
- package/src/server/api/settings.js +2 -0
- package/src/server/api/skills.js +1 -1
- package/src/server/api/workspaces.js +3 -41
- package/src/server/index.js +41 -0
- package/src/server/pi-proxy-server.js +66 -0
- package/src/server/services/agents-service.js +2 -1
- package/src/server/services/base/proxy-utils.js +1 -0
- package/src/server/services/channel-balance.js +151 -1
- package/src/server/services/channel-scheduler.js +7 -1
- package/src/server/services/commands-service.js +11 -2
- package/src/server/services/config-export-service.js +275 -18
- package/src/server/services/config-registry-service.js +20 -18
- package/src/server/services/config-sync-manager.js +115 -6
- package/src/server/services/config-sync-service.js +4 -2
- package/src/server/services/config-templates-service.js +183 -45
- package/src/server/services/mcp-service.js +217 -33
- package/src/server/services/pi-auth-providers.js +294 -0
- package/src/server/services/pi-channels.js +94 -0
- package/src/server/services/pi-config.js +287 -0
- package/src/server/services/pi-sessions.js +490 -0
- package/src/server/services/pi-settings-manager.js +571 -0
- package/src/server/services/pi-statistics-service.js +317 -0
- package/src/server/services/plugins-service.js +673 -11
- package/src/server/services/prompts-service.js +112 -7
- package/src/server/services/proxy-log-helper.js +1 -0
- package/src/server/services/proxy-runtime.js +5 -1
- package/src/server/services/sessions.js +19 -35
- package/src/server/services/skill-service.js +13 -3
- package/src/server/services/speed-test.js +9 -3
- package/src/server/services/ui-config.js +50 -12
- package/src/server/services/workspace-service.js +117 -225
- package/src/server/websocket-server.js +7 -1
- package/src/utils/session.js +4 -10
- package/dist/web/assets/Home-BiaKZMah.js +0 -1
- package/dist/web/assets/Home-CIua-b-o.css +0 -1
- package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
- package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
- package/dist/web/assets/index-BpBFF3b2.js +0 -2
- package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
- package/src/utils/cli-flags.js +0 -7
|
@@ -19,7 +19,7 @@ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homed
|
|
|
19
19
|
const MCP_SERVERS_FILE = PATHS.mcpServers;
|
|
20
20
|
|
|
21
21
|
// 各平台配置文件路径
|
|
22
|
-
const CLAUDE_CONFIG_PATH = path.join(HOME_DIR, '.claude.json');
|
|
22
|
+
const CLAUDE_CONFIG_PATH = NATIVE_PATHS.claude.mcp || path.join(HOME_DIR, '.claude.json');
|
|
23
23
|
const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
|
|
24
24
|
const GEMINI_CONFIG_PATH = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
|
|
25
25
|
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
@@ -28,6 +28,8 @@ const OPENCODE_CONFIG_PATHS = {
|
|
|
28
28
|
json: path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
|
|
29
29
|
legacy: path.join(OPENCODE_CONFIG_DIR, 'config.json')
|
|
30
30
|
};
|
|
31
|
+
const PI_MCP_CONFIG_PATH = NATIVE_PATHS.pi?.mcp
|
|
32
|
+
|| path.join(NATIVE_PATHS.pi?.dir || path.join(HOME_DIR, '.omp', 'agent'), 'mcp.json');
|
|
31
33
|
|
|
32
34
|
// MCP 客户端连接池
|
|
33
35
|
// serverId -> { client, timestamp }
|
|
@@ -36,6 +38,9 @@ const POOL_TTL = 5 * 60 * 1000; // 5 minutes
|
|
|
36
38
|
const STREAMABLE_HTTP_TYPE = 'streamable_http';
|
|
37
39
|
const MCP_SERVER_TYPES = ['stdio', STREAMABLE_HTTP_TYPE, 'sse'];
|
|
38
40
|
const REMOTE_MCP_SERVER_TYPES = [STREAMABLE_HTTP_TYPE, 'sse'];
|
|
41
|
+
const MCP_PLATFORM_KEYS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
|
|
42
|
+
const OMP_MCP_SCHEMA_URL = 'https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json';
|
|
43
|
+
const OMP_SERVER_NAME_PATTERN = /^[a-zA-Z0-9_.-]{1,100}$/;
|
|
39
44
|
|
|
40
45
|
function normalizeServerSpec(spec = {}) {
|
|
41
46
|
if (!spec || typeof spec !== 'object') {
|
|
@@ -532,7 +537,8 @@ const DEFAULT_SERVER_APPS = {
|
|
|
532
537
|
claude: true,
|
|
533
538
|
codex: false,
|
|
534
539
|
gemini: false,
|
|
535
|
-
opencode: false
|
|
540
|
+
opencode: false,
|
|
541
|
+
pi: false
|
|
536
542
|
};
|
|
537
543
|
|
|
538
544
|
function normalizeServerApps(apps = {}, fallbackApps = DEFAULT_SERVER_APPS) {
|
|
@@ -540,7 +546,8 @@ function normalizeServerApps(apps = {}, fallbackApps = DEFAULT_SERVER_APPS) {
|
|
|
540
546
|
claude: apps.claude !== undefined ? !!apps.claude : !!fallbackApps.claude,
|
|
541
547
|
codex: apps.codex !== undefined ? !!apps.codex : !!fallbackApps.codex,
|
|
542
548
|
gemini: apps.gemini !== undefined ? !!apps.gemini : !!fallbackApps.gemini,
|
|
543
|
-
opencode: apps.opencode !== undefined ? !!apps.opencode : !!fallbackApps.opencode
|
|
549
|
+
opencode: apps.opencode !== undefined ? !!apps.opencode : !!fallbackApps.opencode,
|
|
550
|
+
pi: apps.pi !== undefined ? !!apps.pi : !!fallbackApps.pi
|
|
544
551
|
};
|
|
545
552
|
}
|
|
546
553
|
|
|
@@ -648,7 +655,7 @@ async function toggleServerApp(serverId, app, enabled) {
|
|
|
648
655
|
throw new Error(`MCP 服务器 "${serverId}" 不存在`);
|
|
649
656
|
}
|
|
650
657
|
|
|
651
|
-
if (!
|
|
658
|
+
if (!MCP_PLATFORM_KEYS.includes(app)) {
|
|
652
659
|
throw new Error(`无效的平台: ${app}`);
|
|
653
660
|
}
|
|
654
661
|
|
|
@@ -720,28 +727,12 @@ async function syncServerToAllPlatforms(server, previousApps = null) {
|
|
|
720
727
|
return previous[platform] && !apps[platform];
|
|
721
728
|
};
|
|
722
729
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (apps.codex) {
|
|
730
|
-
await syncServerToPlatform(server, 'codex');
|
|
731
|
-
} else if (shouldRemoveFromPlatform('codex')) {
|
|
732
|
-
await removeServerFromPlatform(server.id, 'codex');
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (apps.gemini) {
|
|
736
|
-
await syncServerToPlatform(server, 'gemini');
|
|
737
|
-
} else if (shouldRemoveFromPlatform('gemini')) {
|
|
738
|
-
await removeServerFromPlatform(server.id, 'gemini');
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
if (apps.opencode) {
|
|
742
|
-
await syncServerToPlatform(server, 'opencode');
|
|
743
|
-
} else if (shouldRemoveFromPlatform('opencode')) {
|
|
744
|
-
await removeServerFromPlatform(server.id, 'opencode');
|
|
730
|
+
for (const platform of MCP_PLATFORM_KEYS) {
|
|
731
|
+
if (apps[platform]) {
|
|
732
|
+
await syncServerToPlatform(server, platform);
|
|
733
|
+
} else if (shouldRemoveFromPlatform(platform)) {
|
|
734
|
+
await removeServerFromPlatform(server.id, platform);
|
|
735
|
+
}
|
|
745
736
|
}
|
|
746
737
|
}
|
|
747
738
|
|
|
@@ -749,10 +740,9 @@ async function syncServerToAllPlatforms(server, previousApps = null) {
|
|
|
749
740
|
* 从所有平台移除服务器
|
|
750
741
|
*/
|
|
751
742
|
async function removeServerFromAllPlatforms(serverId) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
await removeServerFromPlatform(serverId, 'opencode');
|
|
743
|
+
for (const platform of MCP_PLATFORM_KEYS) {
|
|
744
|
+
await removeServerFromPlatform(serverId, platform);
|
|
745
|
+
}
|
|
756
746
|
}
|
|
757
747
|
|
|
758
748
|
/**
|
|
@@ -773,6 +763,9 @@ async function syncServerToPlatform(server, platform) {
|
|
|
773
763
|
case 'opencode':
|
|
774
764
|
syncToOpenCodeConfig(server);
|
|
775
765
|
break;
|
|
766
|
+
case 'pi':
|
|
767
|
+
syncToOmpMcpConfig(server);
|
|
768
|
+
break;
|
|
776
769
|
}
|
|
777
770
|
console.log(`[MCP] Synced "${server.id}" to ${platform}`);
|
|
778
771
|
} catch (err) {
|
|
@@ -799,6 +792,9 @@ async function removeServerFromPlatform(serverId, platform) {
|
|
|
799
792
|
case 'opencode':
|
|
800
793
|
removeFromOpenCodeConfig(serverId);
|
|
801
794
|
break;
|
|
795
|
+
case 'pi':
|
|
796
|
+
removeFromOmpMcpConfig(serverId);
|
|
797
|
+
break;
|
|
802
798
|
}
|
|
803
799
|
console.log(`[MCP] Removed "${serverId}" from ${platform}`);
|
|
804
800
|
} catch (err) {
|
|
@@ -947,6 +943,133 @@ function removeFromGeminiConfig(serverId) {
|
|
|
947
943
|
}
|
|
948
944
|
}
|
|
949
945
|
|
|
946
|
+
// ============================================================================
|
|
947
|
+
// OMP 配置同步
|
|
948
|
+
// ============================================================================
|
|
949
|
+
|
|
950
|
+
function validateOmpServerName(serverId) {
|
|
951
|
+
if (!OMP_SERVER_NAME_PATTERN.test(serverId)) {
|
|
952
|
+
throw new Error(`OMP MCP 服务器 ID "${serverId}" 无效。OMP 仅支持 1-100 个字母、数字、下划线、点或连字符。`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function readOmpMcpConfig() {
|
|
957
|
+
const config = readJsonFile(PI_MCP_CONFIG_PATH, { mcpServers: {} });
|
|
958
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
959
|
+
return { mcpServers: {} };
|
|
960
|
+
}
|
|
961
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)) {
|
|
962
|
+
config.mcpServers = {};
|
|
963
|
+
}
|
|
964
|
+
return config;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function copyCommonMcpFields(source, target) {
|
|
968
|
+
for (const key of ['timeout', 'auth', 'oauth']) {
|
|
969
|
+
if (source[key] !== undefined) {
|
|
970
|
+
target[key] = source[key];
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return target;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function convertToOmpMcpFormat(spec = {}) {
|
|
977
|
+
const sourceType = spec.type || 'stdio';
|
|
978
|
+
let result;
|
|
979
|
+
|
|
980
|
+
if (sourceType === 'stdio') {
|
|
981
|
+
result = {
|
|
982
|
+
type: 'stdio',
|
|
983
|
+
command: spec.command || ''
|
|
984
|
+
};
|
|
985
|
+
if (Array.isArray(spec.args) && spec.args.length > 0) {
|
|
986
|
+
result.args = spec.args;
|
|
987
|
+
}
|
|
988
|
+
if (spec.env && Object.keys(spec.env).length > 0) {
|
|
989
|
+
result.env = spec.env;
|
|
990
|
+
}
|
|
991
|
+
if (spec.cwd) {
|
|
992
|
+
result.cwd = spec.cwd;
|
|
993
|
+
}
|
|
994
|
+
} else if (sourceType === STREAMABLE_HTTP_TYPE) {
|
|
995
|
+
result = {
|
|
996
|
+
type: 'http',
|
|
997
|
+
url: spec.url || ''
|
|
998
|
+
};
|
|
999
|
+
if (spec.headers && Object.keys(spec.headers).length > 0) {
|
|
1000
|
+
result.headers = spec.headers;
|
|
1001
|
+
}
|
|
1002
|
+
} else if (sourceType === 'sse') {
|
|
1003
|
+
result = {
|
|
1004
|
+
type: 'sse',
|
|
1005
|
+
url: spec.url || ''
|
|
1006
|
+
};
|
|
1007
|
+
if (spec.headers && Object.keys(spec.headers).length > 0) {
|
|
1008
|
+
result.headers = spec.headers;
|
|
1009
|
+
}
|
|
1010
|
+
} else {
|
|
1011
|
+
result = extractServerSpec(spec);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return copyCommonMcpFields(spec, result);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function convertFromOmpMcpFormat(spec = {}) {
|
|
1018
|
+
const sourceType = spec.type || 'stdio';
|
|
1019
|
+
let result;
|
|
1020
|
+
|
|
1021
|
+
if (sourceType === 'stdio') {
|
|
1022
|
+
result = {
|
|
1023
|
+
type: 'stdio',
|
|
1024
|
+
command: spec.command || ''
|
|
1025
|
+
};
|
|
1026
|
+
if (spec.args) {
|
|
1027
|
+
result.args = spec.args;
|
|
1028
|
+
}
|
|
1029
|
+
if (spec.env) {
|
|
1030
|
+
result.env = spec.env;
|
|
1031
|
+
}
|
|
1032
|
+
if (spec.cwd) {
|
|
1033
|
+
result.cwd = spec.cwd;
|
|
1034
|
+
}
|
|
1035
|
+
} else if (sourceType === 'http') {
|
|
1036
|
+
result = {
|
|
1037
|
+
type: STREAMABLE_HTTP_TYPE,
|
|
1038
|
+
url: spec.url || ''
|
|
1039
|
+
};
|
|
1040
|
+
if (spec.headers) {
|
|
1041
|
+
result.headers = spec.headers;
|
|
1042
|
+
}
|
|
1043
|
+
} else if (sourceType === 'sse') {
|
|
1044
|
+
result = {
|
|
1045
|
+
type: 'sse',
|
|
1046
|
+
url: spec.url || ''
|
|
1047
|
+
};
|
|
1048
|
+
if (spec.headers) {
|
|
1049
|
+
result.headers = spec.headers;
|
|
1050
|
+
}
|
|
1051
|
+
} else {
|
|
1052
|
+
result = convertFromCodexFormat(spec);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
return copyCommonMcpFields(spec, result);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function syncToOmpMcpConfig(server) {
|
|
1059
|
+
validateOmpServerName(server.id);
|
|
1060
|
+
const config = readOmpMcpConfig();
|
|
1061
|
+
config.mcpServers[server.id] = convertToOmpMcpFormat(server.server);
|
|
1062
|
+
writeJsonFile(PI_MCP_CONFIG_PATH, config);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function removeFromOmpMcpConfig(serverId) {
|
|
1066
|
+
const config = readOmpMcpConfig();
|
|
1067
|
+
if (config.mcpServers && config.mcpServers[serverId]) {
|
|
1068
|
+
delete config.mcpServers[serverId];
|
|
1069
|
+
writeJsonFile(PI_MCP_CONFIG_PATH, config);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
950
1073
|
// ============================================================================
|
|
951
1074
|
// OpenCode 配置同步
|
|
952
1075
|
// ============================================================================
|
|
@@ -1108,6 +1231,9 @@ async function importFromPlatform(platform) {
|
|
|
1108
1231
|
case 'opencode':
|
|
1109
1232
|
importedCount = importFromOpenCode(servers);
|
|
1110
1233
|
break;
|
|
1234
|
+
case 'pi':
|
|
1235
|
+
importedCount = importFromOmp(servers);
|
|
1236
|
+
break;
|
|
1111
1237
|
default:
|
|
1112
1238
|
throw new Error(`无效的平台: ${platform}`);
|
|
1113
1239
|
}
|
|
@@ -1250,6 +1376,39 @@ function importFromOpenCode(servers) {
|
|
|
1250
1376
|
return count;
|
|
1251
1377
|
}
|
|
1252
1378
|
|
|
1379
|
+
/**
|
|
1380
|
+
* 从 OMP 导入
|
|
1381
|
+
*/
|
|
1382
|
+
function importFromOmp(servers) {
|
|
1383
|
+
const config = readOmpMcpConfig();
|
|
1384
|
+
const mcpServers = config.mcpServers || {};
|
|
1385
|
+
let count = 0;
|
|
1386
|
+
|
|
1387
|
+
for (const [id, spec] of Object.entries(mcpServers)) {
|
|
1388
|
+
const convertedSpec = convertFromOmpMcpFormat(spec || {});
|
|
1389
|
+
|
|
1390
|
+
if (servers[id]) {
|
|
1391
|
+
servers[id].apps = normalizeServerApps(servers[id].apps);
|
|
1392
|
+
if (!servers[id].apps.pi) {
|
|
1393
|
+
servers[id].apps.pi = true;
|
|
1394
|
+
count++;
|
|
1395
|
+
}
|
|
1396
|
+
} else {
|
|
1397
|
+
servers[id] = {
|
|
1398
|
+
id,
|
|
1399
|
+
name: id,
|
|
1400
|
+
server: convertedSpec,
|
|
1401
|
+
apps: { claude: false, codex: false, gemini: false, opencode: false, pi: true },
|
|
1402
|
+
createdAt: Date.now(),
|
|
1403
|
+
updatedAt: Date.now()
|
|
1404
|
+
};
|
|
1405
|
+
count++;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
return count;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1253
1412
|
/**
|
|
1254
1413
|
* 从 Codex 格式转换
|
|
1255
1414
|
*/
|
|
@@ -1311,7 +1470,8 @@ function getStats() {
|
|
|
1311
1470
|
claude: serverList.filter(s => s.apps?.claude).length,
|
|
1312
1471
|
codex: serverList.filter(s => s.apps?.codex).length,
|
|
1313
1472
|
gemini: serverList.filter(s => s.apps?.gemini).length,
|
|
1314
|
-
opencode: serverList.filter(s => s.apps?.opencode).length
|
|
1473
|
+
opencode: serverList.filter(s => s.apps?.opencode).length,
|
|
1474
|
+
pi: serverList.filter(s => s.apps?.pi).length
|
|
1315
1475
|
};
|
|
1316
1476
|
}
|
|
1317
1477
|
|
|
@@ -1763,7 +1923,7 @@ function updateServerOrder(serverIds) {
|
|
|
1763
1923
|
|
|
1764
1924
|
/**
|
|
1765
1925
|
* 导出所有 MCP 配置
|
|
1766
|
-
* @param {string} format - 导出格式: 'json' | 'claude' | 'codex' | 'opencode' | 'gemini'
|
|
1926
|
+
* @param {string} format - 导出格式: 'json' | 'claude' | 'codex' | 'opencode' | 'gemini' | 'pi'
|
|
1767
1927
|
*/
|
|
1768
1928
|
function exportServers(format = 'json') {
|
|
1769
1929
|
const servers = getAllServers();
|
|
@@ -1777,6 +1937,8 @@ function exportServers(format = 'json') {
|
|
|
1777
1937
|
return exportForOpenCode(servers);
|
|
1778
1938
|
case 'gemini':
|
|
1779
1939
|
return exportForGemini(servers);
|
|
1940
|
+
case 'pi':
|
|
1941
|
+
return exportForOmp(servers);
|
|
1780
1942
|
case 'json':
|
|
1781
1943
|
default:
|
|
1782
1944
|
return exportAsJson(servers);
|
|
@@ -1876,6 +2038,26 @@ function exportForGemini(servers) {
|
|
|
1876
2038
|
};
|
|
1877
2039
|
}
|
|
1878
2040
|
|
|
2041
|
+
/**
|
|
2042
|
+
* 导出为 OMP 格式
|
|
2043
|
+
*/
|
|
2044
|
+
function exportForOmp(servers) {
|
|
2045
|
+
const mcpServers = {};
|
|
2046
|
+
|
|
2047
|
+
for (const [id, server] of Object.entries(servers)) {
|
|
2048
|
+
if (server.apps?.pi) {
|
|
2049
|
+
validateOmpServerName(id);
|
|
2050
|
+
mcpServers[id] = convertToOmpMcpFormat(server.server);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
return {
|
|
2055
|
+
format: 'pi',
|
|
2056
|
+
content: JSON.stringify({ $schema: OMP_MCP_SCHEMA_URL, mcpServers }, null, 2),
|
|
2057
|
+
filename: 'omp-mcp-config.json'
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
|
|
1879
2061
|
module.exports = {
|
|
1880
2062
|
getAllServers,
|
|
1881
2063
|
getServer,
|
|
@@ -1895,6 +2077,8 @@ module.exports = {
|
|
|
1895
2077
|
exportServers,
|
|
1896
2078
|
_test: {
|
|
1897
2079
|
extractMcpHint,
|
|
1898
|
-
buildMcpFailureResult
|
|
2080
|
+
buildMcpFailureResult,
|
|
2081
|
+
convertToOmpMcpFormat,
|
|
2082
|
+
convertFromOmpMcpFormat
|
|
1899
2083
|
}
|
|
1900
2084
|
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
const { spawnSync } = require('child_process');
|
|
2
|
+
const piConfig = require('./pi-config');
|
|
3
|
+
|
|
4
|
+
const COMMAND_TIMEOUT_MS = 5000;
|
|
5
|
+
const TOKEN_LIST_TIMEOUT_MS = 3000;
|
|
6
|
+
const CACHE_TTL_MS = 15000;
|
|
7
|
+
|
|
8
|
+
const LOGIN_PROVIDER_IDS = new Set([
|
|
9
|
+
'openai-codex',
|
|
10
|
+
'openai-codex-device',
|
|
11
|
+
'anthropic',
|
|
12
|
+
'github-copilot',
|
|
13
|
+
'cursor',
|
|
14
|
+
'devin',
|
|
15
|
+
'google-antigravity',
|
|
16
|
+
'google-gemini-cli',
|
|
17
|
+
'xai-oauth',
|
|
18
|
+
'gitlab-duo',
|
|
19
|
+
'gitlab-duo-agent',
|
|
20
|
+
'alibaba-coding-plan',
|
|
21
|
+
'zhipu-coding-plan',
|
|
22
|
+
'kimi-code',
|
|
23
|
+
'zai',
|
|
24
|
+
'qwen-portal',
|
|
25
|
+
'minimax-code',
|
|
26
|
+
'minimax-code-cn',
|
|
27
|
+
'perplexity'
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const ACCOUNT_CHECK_PROVIDER_IDS = new Set([
|
|
31
|
+
'openai-codex',
|
|
32
|
+
'openai-codex-device',
|
|
33
|
+
'anthropic',
|
|
34
|
+
'google-gemini-cli',
|
|
35
|
+
'github-copilot'
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const PROVIDER_ALIASES = {
|
|
39
|
+
codex: 'openai-codex',
|
|
40
|
+
openai: 'openai-codex',
|
|
41
|
+
'openai-codex': 'openai-codex',
|
|
42
|
+
'openai-codex-device': 'openai-codex-device',
|
|
43
|
+
claude: 'anthropic',
|
|
44
|
+
anthropic: 'anthropic',
|
|
45
|
+
gemini: 'google-gemini-cli',
|
|
46
|
+
'gemini-cli': 'google-gemini-cli',
|
|
47
|
+
'google-gemini-cli': 'google-gemini-cli',
|
|
48
|
+
copilot: 'github-copilot',
|
|
49
|
+
'github-copilot': 'github-copilot'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let snapshotCache = {};
|
|
53
|
+
|
|
54
|
+
function normalizeProviderId(value = '') {
|
|
55
|
+
return String(value || '')
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
59
|
+
.replace(/-+/g, '-')
|
|
60
|
+
.replace(/^-|-$/g, '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveProviderAlias(value = '') {
|
|
64
|
+
const id = normalizeProviderId(value);
|
|
65
|
+
return PROVIDER_ALIASES[id] || id;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeCommandOutput(value) {
|
|
69
|
+
if (value === undefined || value === null) return '';
|
|
70
|
+
return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sanitizeIdentity(value = '') {
|
|
74
|
+
const text = String(value || '').trim();
|
|
75
|
+
if (!text) return '';
|
|
76
|
+
return text
|
|
77
|
+
.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, (email) => {
|
|
78
|
+
const [name, domain] = email.split('@');
|
|
79
|
+
if (!name || !domain) return '***';
|
|
80
|
+
const visible = name.length <= 2 ? `${name[0] || '*'}***` : `${name.slice(0, 2)}***${name.slice(-1)}`;
|
|
81
|
+
return `${visible}@${domain}`;
|
|
82
|
+
})
|
|
83
|
+
.replace(/sk-[A-Za-z0-9_-]{8,}/g, 'sk-***')
|
|
84
|
+
.replace(/nvapi-[A-Za-z0-9_-]{8,}/g, 'nvapi-***')
|
|
85
|
+
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer ***')
|
|
86
|
+
.replace(/[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,}/g, 'jwt.***')
|
|
87
|
+
.replace(/[A-Za-z0-9_=-]{32,}/g, '***');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function runCommand(command, args = [], options = {}) {
|
|
91
|
+
const runner = options.commandRunner || spawnSync;
|
|
92
|
+
const result = runner(command, args, {
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
env: {
|
|
95
|
+
...process.env,
|
|
96
|
+
...(options.env || {})
|
|
97
|
+
},
|
|
98
|
+
timeout: options.timeout || COMMAND_TIMEOUT_MS
|
|
99
|
+
});
|
|
100
|
+
if (typeof result === 'string' || Buffer.isBuffer(result)) {
|
|
101
|
+
return { status: 0, stdout: normalizeCommandOutput(result), stderr: '' };
|
|
102
|
+
}
|
|
103
|
+
return result || { status: 0, stdout: '', stderr: '' };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseJsonCommand(command, args, options = {}) {
|
|
107
|
+
const result = runCommand(command, args, options);
|
|
108
|
+
const status = result.status === undefined || result.status === null ? 0 : result.status;
|
|
109
|
+
const stdout = normalizeCommandOutput(result.stdout).trim();
|
|
110
|
+
const stderr = normalizeCommandOutput(result.stderr).trim();
|
|
111
|
+
if (result.error) {
|
|
112
|
+
return { ok: false, status, error: result.error.message, stderr };
|
|
113
|
+
}
|
|
114
|
+
if (!stdout) {
|
|
115
|
+
return { ok: status === 0, status, value: null, stderr };
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return { ok: status === 0, status, value: JSON.parse(stdout), stderr };
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return { ok: false, status, error: error.message, stderr };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseAccountLine(line = '', index) {
|
|
125
|
+
const sanitized = sanitizeIdentity(line)
|
|
126
|
+
.replace(/^\s*[-*]\s*/, '')
|
|
127
|
+
.replace(/^\s*\[?\d+\]?\s*[:.)-]?\s*/, '')
|
|
128
|
+
.trim();
|
|
129
|
+
if (!sanitized || /^no oauth accounts found/i.test(sanitized)) return null;
|
|
130
|
+
return {
|
|
131
|
+
index: index + 1,
|
|
132
|
+
identity: sanitized
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function listProviderAccounts(command, providerId, options = {}) {
|
|
137
|
+
const result = runCommand(command, ['token', providerId, '--list'], {
|
|
138
|
+
...options,
|
|
139
|
+
timeout: options.tokenListTimeout || TOKEN_LIST_TIMEOUT_MS
|
|
140
|
+
});
|
|
141
|
+
const status = result.status === undefined || result.status === null ? 0 : result.status;
|
|
142
|
+
const stdout = normalizeCommandOutput(result.stdout);
|
|
143
|
+
const stderr = normalizeCommandOutput(result.stderr);
|
|
144
|
+
const combined = `${stdout}\n${stderr}`;
|
|
145
|
+
|
|
146
|
+
if (result.error) {
|
|
147
|
+
return { checked: true, loggedIn: false, accountCount: 0, accounts: [], error: result.error.message };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const accounts = stdout
|
|
151
|
+
.split(/\r?\n/)
|
|
152
|
+
.map((line, index) => parseAccountLine(line, index))
|
|
153
|
+
.filter(Boolean);
|
|
154
|
+
|
|
155
|
+
const noAccounts = /no oauth accounts found/i.test(combined);
|
|
156
|
+
return {
|
|
157
|
+
checked: true,
|
|
158
|
+
loggedIn: accounts.length > 0,
|
|
159
|
+
accountCount: accounts.length,
|
|
160
|
+
accounts,
|
|
161
|
+
error: status !== 0 && !noAccounts && accounts.length === 0
|
|
162
|
+
? sanitizeIdentity(stderr || stdout || `exit code ${status}`)
|
|
163
|
+
: null
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isLikelyLoginProvider(provider = {}) {
|
|
168
|
+
const id = normalizeProviderId(provider.id);
|
|
169
|
+
const name = String(provider.name || '').toLowerCase();
|
|
170
|
+
return LOGIN_PROVIDER_IDS.has(id)
|
|
171
|
+
|| id.includes('oauth')
|
|
172
|
+
|| id.includes('codex')
|
|
173
|
+
|| id.includes('copilot')
|
|
174
|
+
|| id.includes('gemini-cli')
|
|
175
|
+
|| name.includes('subscription')
|
|
176
|
+
|| name.includes('coding plan')
|
|
177
|
+
|| name.includes('token plan')
|
|
178
|
+
|| name.includes('claude pro')
|
|
179
|
+
|| name.includes('chatgpt plus')
|
|
180
|
+
|| name.includes('chatgpt pro');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function normalizeProvider(provider = {}) {
|
|
184
|
+
const id = normalizeProviderId(provider.id);
|
|
185
|
+
return {
|
|
186
|
+
id,
|
|
187
|
+
name: String(provider.name || provider.id || id),
|
|
188
|
+
loginCapable: isLikelyLoginProvider(provider)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildEmptySnapshot(runtime, reason) {
|
|
193
|
+
return {
|
|
194
|
+
available: false,
|
|
195
|
+
reason,
|
|
196
|
+
runtime,
|
|
197
|
+
brokerStatus: null,
|
|
198
|
+
gatewayStatus: null,
|
|
199
|
+
providers: [],
|
|
200
|
+
supportedProviders: [],
|
|
201
|
+
aliases: { ...PROVIDER_ALIASES },
|
|
202
|
+
checkedAt: new Date().toISOString()
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getOmpAuthProviderSnapshot(options = {}) {
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
const cacheKey = options.accountCheck === false
|
|
209
|
+
? (options.includeStatus === false ? 'metadata' : 'metadata-status')
|
|
210
|
+
: 'full';
|
|
211
|
+
const cached = snapshotCache[cacheKey];
|
|
212
|
+
if (!options.forceRefresh && cached && now - cached.loadedAt < CACHE_TTL_MS) {
|
|
213
|
+
return cached.value;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const env = options.env || process.env;
|
|
217
|
+
const runtime = options.runtime || piConfig.resolvePiRuntime(env, options.runtimeOptions || {});
|
|
218
|
+
if (!runtime || runtime.runtime !== 'omp' || !runtime.installed) {
|
|
219
|
+
const value = buildEmptySnapshot(runtime || null, 'omp-not-available');
|
|
220
|
+
snapshotCache[cacheKey] = { loadedAt: now, value };
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const command = runtime.command;
|
|
225
|
+
const listResult = parseJsonCommand(command, ['auth-broker', 'list', '--json'], options);
|
|
226
|
+
if (!listResult.ok || !Array.isArray(listResult.value)) {
|
|
227
|
+
const value = buildEmptySnapshot(runtime, listResult.error || listResult.stderr || 'auth-provider-list-failed');
|
|
228
|
+
snapshotCache[cacheKey] = { loadedAt: now, value };
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const supportedProviders = listResult.value
|
|
233
|
+
.map(normalizeProvider)
|
|
234
|
+
.filter(provider => provider.id);
|
|
235
|
+
const loginProviders = supportedProviders
|
|
236
|
+
.filter(provider => provider.loginCapable)
|
|
237
|
+
.map(provider => {
|
|
238
|
+
if (options.accountCheck === false || !ACCOUNT_CHECK_PROVIDER_IDS.has(provider.id)) {
|
|
239
|
+
return {
|
|
240
|
+
...provider,
|
|
241
|
+
checked: false,
|
|
242
|
+
loggedIn: null,
|
|
243
|
+
accountCount: null,
|
|
244
|
+
accounts: [],
|
|
245
|
+
error: null
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
...provider,
|
|
250
|
+
...listProviderAccounts(command, provider.id, options)
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const brokerStatus = options.includeStatus === false
|
|
255
|
+
? { value: null }
|
|
256
|
+
: parseJsonCommand(command, ['auth-broker', 'status', '--json'], options);
|
|
257
|
+
const gatewayStatus = options.includeStatus === false
|
|
258
|
+
? { value: null }
|
|
259
|
+
: parseJsonCommand(command, ['auth-gateway', 'status', '--json'], options);
|
|
260
|
+
|
|
261
|
+
const value = {
|
|
262
|
+
available: true,
|
|
263
|
+
runtime,
|
|
264
|
+
brokerStatus: brokerStatus.value || null,
|
|
265
|
+
gatewayStatus: gatewayStatus.value || null,
|
|
266
|
+
providers: loginProviders,
|
|
267
|
+
supportedProviders,
|
|
268
|
+
aliases: { ...PROVIDER_ALIASES },
|
|
269
|
+
checkedAt: new Date().toISOString()
|
|
270
|
+
};
|
|
271
|
+
snapshotCache[cacheKey] = { loadedAt: now, value };
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function findAuthProviderForKey(providerKey, snapshot) {
|
|
276
|
+
const resolved = resolveProviderAlias(providerKey);
|
|
277
|
+
const providers = Array.isArray(snapshot?.providers) ? snapshot.providers : [];
|
|
278
|
+
return providers.find(provider => provider.id === resolved) || null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function clearOmpAuthProviderCache() {
|
|
282
|
+
snapshotCache = {};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = {
|
|
286
|
+
LOGIN_PROVIDER_IDS,
|
|
287
|
+
ACCOUNT_CHECK_PROVIDER_IDS,
|
|
288
|
+
clearOmpAuthProviderCache,
|
|
289
|
+
findAuthProviderForKey,
|
|
290
|
+
getOmpAuthProviderSnapshot,
|
|
291
|
+
normalizeProviderId,
|
|
292
|
+
resolveProviderAlias,
|
|
293
|
+
sanitizeIdentity
|
|
294
|
+
};
|