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.
- package/CHANGELOG.md +20 -0
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-D7KX7iF8.js +1 -0
- package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
- package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
- package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
- package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
- package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-CL-qpoJ_.js +2 -0
- package/dist/web/assets/index-D_5dRFOL.css +1 -0
- package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
- package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
- package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
- package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
- package/dist/web/index.html +7 -7
- package/docs/home.png +0 -0
- package/package.json +14 -5
- package/src/commands/daemon.js +3 -2
- package/src/commands/security.js +1 -2
- package/src/commands/toggle-proxy.js +100 -5
- package/src/config/paths.js +718 -90
- package/src/server/api/agents.js +1 -1
- package/src/server/api/channels.js +9 -0
- package/src/server/api/claude-hooks.js +13 -8
- package/src/server/api/codex-channels.js +9 -0
- package/src/server/api/codex-proxy.js +27 -15
- package/src/server/api/gemini-proxy.js +22 -11
- package/src/server/api/hooks.js +45 -0
- package/src/server/api/oauth-credentials.js +163 -0
- package/src/server/api/opencode-proxy.js +22 -10
- package/src/server/api/plugins.js +2 -1
- package/src/server/api/proxy.js +39 -44
- package/src/server/api/skills.js +91 -13
- package/src/server/api/ui-config.js +5 -0
- package/src/server/codex-proxy-server.js +90 -70
- package/src/server/gemini-proxy-server.js +107 -88
- package/src/server/index.js +2 -0
- package/src/server/opencode-proxy-server.js +381 -225
- package/src/server/proxy-server.js +86 -60
- package/src/server/services/alias.js +3 -3
- package/src/server/services/channels.js +21 -24
- package/src/server/services/codex-channels.js +158 -255
- package/src/server/services/codex-config.js +2 -5
- package/src/server/services/codex-env-manager.js +423 -0
- package/src/server/services/codex-settings-manager.js +21 -357
- package/src/server/services/codex-statistics-service.js +3 -27
- package/src/server/services/config-export-service.js +43 -9
- package/src/server/services/config-registry-service.js +3 -2
- package/src/server/services/config-sync-manager.js +1 -1
- package/src/server/services/favorites.js +4 -3
- package/src/server/services/gemini-channels.js +14 -12
- package/src/server/services/gemini-statistics-service.js +3 -25
- package/src/server/services/mcp-service.js +35 -19
- package/src/server/services/model-detector.js +4 -3
- package/src/server/services/native-keychain.js +243 -0
- package/src/server/services/native-oauth-adapters.js +891 -0
- package/src/server/services/network-access.js +39 -1
- package/src/server/services/notification-hooks.js +951 -0
- package/src/server/services/oauth-credentials-service.js +786 -0
- package/src/server/services/oauth-utils.js +49 -0
- package/src/server/services/opencode-channels.js +19 -15
- package/src/server/services/opencode-sessions.js +2 -2
- package/src/server/services/opencode-settings-manager.js +169 -16
- package/src/server/services/opencode-statistics-service.js +3 -27
- package/src/server/services/plugins-service.js +115 -15
- package/src/server/services/prompts-service.js +2 -3
- package/src/server/services/proxy-log-helper.js +242 -0
- package/src/server/services/proxy-runtime.js +6 -4
- package/src/server/services/repo-scanner-base.js +12 -4
- package/src/server/services/request-logger.js +7 -7
- package/src/server/services/security-config.js +4 -4
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/sessions.js +2 -2
- package/src/server/services/settings-manager.js +13 -0
- package/src/server/services/skill-service.js +867 -368
- package/src/server/services/statistics-service.js +5 -5
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +1 -1
- package/src/server/websocket-server.js +5 -4
- package/dist/web/assets/Home-BsSioaaB.css +0 -1
- package/dist/web/assets/Home-obifg_9E.js +0 -1
- package/dist/web/assets/index-C7LPdVsN.js +0 -2
- package/dist/web/assets/index-eEmjZKWP.css +0 -1
- package/docs/bannel.png +0 -0
- package/docs/model-redirection.md +0 -251
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
5
|
+
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Gemini 渠道管理服务(多渠道架构)
|
|
@@ -31,9 +32,9 @@ function getGeminiDir() {
|
|
|
31
32
|
|
|
32
33
|
// 获取渠道存储文件路径
|
|
33
34
|
function getChannelsFilePath() {
|
|
34
|
-
const
|
|
35
|
-
if (!fs.existsSync(
|
|
36
|
-
fs.mkdirSync(
|
|
35
|
+
const channelsDir = path.dirname(PATHS.channels.gemini);
|
|
36
|
+
if (!fs.existsSync(channelsDir)) {
|
|
37
|
+
fs.mkdirSync(channelsDir, { recursive: true });
|
|
37
38
|
}
|
|
38
39
|
return PATHS.channels.gemini;
|
|
39
40
|
}
|
|
@@ -252,14 +253,6 @@ function updateChannel(channelId, updates) {
|
|
|
252
253
|
console.log(`[Gemini Single-channel mode] Enabled "${nextChannel.name}", disabled all others`);
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
// Prevent disabling last enabled channel when proxy is OFF
|
|
256
|
-
if (!isProxyRunning && !nextChannel.enabled && oldChannel.enabled) {
|
|
257
|
-
const enabledCount = data.channels.filter(ch => ch.enabled).length;
|
|
258
|
-
if (enabledCount === 0) {
|
|
259
|
-
throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
256
|
saveChannels(data);
|
|
264
257
|
|
|
265
258
|
// Only sync .env when proxy is OFF.
|
|
@@ -299,6 +292,8 @@ function applyChannelToSettings(channelId, channels = null) {
|
|
|
299
292
|
saveChannels(data);
|
|
300
293
|
}
|
|
301
294
|
|
|
295
|
+
clearNativeOAuth('gemini');
|
|
296
|
+
|
|
302
297
|
const geminiDir = getGeminiDir();
|
|
303
298
|
|
|
304
299
|
if (!fs.existsSync(geminiDir)) {
|
|
@@ -455,6 +450,12 @@ function saveChannelOrder(order) {
|
|
|
455
450
|
saveChannels(data);
|
|
456
451
|
}
|
|
457
452
|
|
|
453
|
+
function disableAllChannels() {
|
|
454
|
+
const data = loadChannels();
|
|
455
|
+
data.channels.forEach(ch => { ch.enabled = false; });
|
|
456
|
+
saveChannels(data);
|
|
457
|
+
}
|
|
458
|
+
|
|
458
459
|
module.exports = {
|
|
459
460
|
getChannels,
|
|
460
461
|
createChannel,
|
|
@@ -465,5 +466,6 @@ module.exports = {
|
|
|
465
466
|
saveChannelOrder,
|
|
466
467
|
isProxyConfig,
|
|
467
468
|
getGeminiDir,
|
|
468
|
-
applyChannelToSettings
|
|
469
|
+
applyChannelToSettings,
|
|
470
|
+
disableAllChannels
|
|
469
471
|
};
|
|
@@ -4,34 +4,12 @@ const {
|
|
|
4
4
|
getDailyStatistics: getSharedDailyStatistics,
|
|
5
5
|
getTodayStatistics: getSharedTodayStatistics
|
|
6
6
|
} = require('./statistics-service');
|
|
7
|
+
const { normalizeUsageTokens, toNumber } = require('./proxy-log-helper');
|
|
7
8
|
|
|
8
9
|
const TOOL_TYPE = 'gemini';
|
|
9
10
|
|
|
10
|
-
function toNumber(value) {
|
|
11
|
-
const num = Number(value);
|
|
12
|
-
return Number.isFinite(num) ? num : 0;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function normalizeToolTokens(tokens = {}) {
|
|
16
|
-
const input = toNumber(tokens.input);
|
|
17
|
-
const output = toNumber(tokens.output);
|
|
18
|
-
const cached = toNumber(tokens.cached);
|
|
19
|
-
const cacheCreation = toNumber(tokens.cacheCreation);
|
|
20
|
-
const cacheRead = toNumber(tokens.cacheRead || cached);
|
|
21
|
-
const total = toNumber(tokens.total) || (input + output);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
input,
|
|
25
|
-
output,
|
|
26
|
-
cached,
|
|
27
|
-
cacheCreation,
|
|
28
|
-
cacheRead,
|
|
29
|
-
total
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
11
|
function toLegacyEntryShape(entry = {}, includeName = false) {
|
|
34
|
-
const normalized =
|
|
12
|
+
const normalized = normalizeUsageTokens(TOOL_TYPE, entry.tokens || {});
|
|
35
13
|
const result = {
|
|
36
14
|
requests: toNumber(entry.requests),
|
|
37
15
|
tokens: {
|
|
@@ -123,7 +101,7 @@ function buildDailyStatistics(sharedDaily = {}, fallbackDate) {
|
|
|
123
101
|
}
|
|
124
102
|
|
|
125
103
|
function recordRequest(requestData = {}) {
|
|
126
|
-
const normalizedTokens =
|
|
104
|
+
const normalizedTokens = normalizeUsageTokens(TOOL_TYPE, requestData.tokens || {});
|
|
127
105
|
return recordSharedRequest({
|
|
128
106
|
...requestData,
|
|
129
107
|
toolType: TOOL_TYPE,
|
|
@@ -12,14 +12,13 @@ const { spawn } = require('child_process');
|
|
|
12
12
|
const http = require('http');
|
|
13
13
|
const https = require('https');
|
|
14
14
|
const { McpClient, buildMissingCommandMessage, createMissingCommandHint } = require('./mcp-client');
|
|
15
|
-
const { NATIVE_PATHS } = require('../../config/paths');
|
|
15
|
+
const { NATIVE_PATHS, PATHS } = require('../../config/paths');
|
|
16
16
|
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
17
17
|
|
|
18
18
|
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
19
19
|
|
|
20
20
|
// MCP 配置文件路径
|
|
21
|
-
const
|
|
22
|
-
const MCP_SERVERS_FILE = path.join(CC_TOOL_DIR, 'mcp-servers.json');
|
|
21
|
+
const MCP_SERVERS_FILE = PATHS.mcpServers;
|
|
23
22
|
|
|
24
23
|
// 各平台配置文件路径
|
|
25
24
|
const CLAUDE_CONFIG_PATH = path.join(HOME_DIR, '.claude.json');
|
|
@@ -290,15 +289,16 @@ function writeJsonFile(filePath, data) {
|
|
|
290
289
|
* 安全读取 TOML 文件
|
|
291
290
|
*/
|
|
292
291
|
function readTomlFile(filePath, defaultValue = {}) {
|
|
292
|
+
if (!fs.existsSync(filePath)) {
|
|
293
|
+
return defaultValue;
|
|
294
|
+
}
|
|
295
|
+
|
|
293
296
|
try {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return toml.parse(content);
|
|
297
|
-
}
|
|
297
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
298
|
+
return toml.parse(content);
|
|
298
299
|
} catch (err) {
|
|
299
|
-
|
|
300
|
+
throw new Error(`Failed to parse ${filePath}: ${err.message}`);
|
|
300
301
|
}
|
|
301
|
-
return defaultValue;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
/**
|
|
@@ -593,14 +593,14 @@ async function saveServer(server, options = {}) {
|
|
|
593
593
|
server.apps = normalizeServerApps(server.apps, previousApps || DEFAULT_SERVER_APPS);
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
-
servers[server.id] = server;
|
|
597
|
-
writeJsonFile(MCP_SERVERS_FILE, servers);
|
|
598
|
-
|
|
599
596
|
// 同步到各平台配置
|
|
600
597
|
if (syncPlatforms) {
|
|
601
598
|
await syncServerToAllPlatforms(server, previousApps);
|
|
602
599
|
}
|
|
603
600
|
|
|
601
|
+
servers[server.id] = server;
|
|
602
|
+
writeJsonFile(MCP_SERVERS_FILE, servers);
|
|
603
|
+
|
|
604
604
|
return server;
|
|
605
605
|
}
|
|
606
606
|
|
|
@@ -615,12 +615,12 @@ async function deleteServer(id) {
|
|
|
615
615
|
return false;
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
-
delete servers[id];
|
|
619
|
-
writeJsonFile(MCP_SERVERS_FILE, servers);
|
|
620
|
-
|
|
621
618
|
// 从所有平台配置中移除
|
|
622
619
|
await removeServerFromAllPlatforms(id);
|
|
623
620
|
|
|
621
|
+
delete servers[id];
|
|
622
|
+
writeJsonFile(MCP_SERVERS_FILE, servers);
|
|
623
|
+
|
|
624
624
|
return true;
|
|
625
625
|
}
|
|
626
626
|
|
|
@@ -642,8 +642,6 @@ async function toggleServerApp(serverId, app, enabled) {
|
|
|
642
642
|
server.apps[app] = enabled;
|
|
643
643
|
server.updatedAt = Date.now();
|
|
644
644
|
|
|
645
|
-
writeJsonFile(MCP_SERVERS_FILE, servers);
|
|
646
|
-
|
|
647
645
|
// 同步到对应平台
|
|
648
646
|
if (enabled) {
|
|
649
647
|
await syncServerToPlatform(server, app);
|
|
@@ -651,6 +649,8 @@ async function toggleServerApp(serverId, app, enabled) {
|
|
|
651
649
|
await removeServerFromPlatform(serverId, app);
|
|
652
650
|
}
|
|
653
651
|
|
|
652
|
+
writeJsonFile(MCP_SERVERS_FILE, servers);
|
|
653
|
+
|
|
654
654
|
return server;
|
|
655
655
|
}
|
|
656
656
|
|
|
@@ -790,6 +790,7 @@ async function removeServerFromPlatform(serverId, platform) {
|
|
|
790
790
|
console.log(`[MCP] Removed "${serverId}" from ${platform}`);
|
|
791
791
|
} catch (err) {
|
|
792
792
|
console.error(`[MCP] Failed to remove "${serverId}" from ${platform}:`, err.message);
|
|
793
|
+
throw err;
|
|
793
794
|
}
|
|
794
795
|
}
|
|
795
796
|
|
|
@@ -833,14 +834,22 @@ function removeFromClaudeConfig(serverId) {
|
|
|
833
834
|
* 同步到 Codex 配置
|
|
834
835
|
*/
|
|
835
836
|
function syncToCodexConfig(server) {
|
|
837
|
+
if (!fs.existsSync(CODEX_CONFIG_PATH)) {
|
|
838
|
+
throw new Error('Codex config.toml not found. Please run Codex CLI at least once before syncing MCP servers.');
|
|
839
|
+
}
|
|
840
|
+
|
|
836
841
|
const config = readTomlFile(CODEX_CONFIG_PATH, {});
|
|
842
|
+
const nextSpec = convertToCodexFormat(server.server);
|
|
837
843
|
|
|
838
844
|
if (!config.mcp_servers) {
|
|
839
845
|
config.mcp_servers = {};
|
|
840
846
|
}
|
|
841
847
|
|
|
842
|
-
|
|
843
|
-
|
|
848
|
+
if (JSON.stringify(config.mcp_servers[server.id] || null) === JSON.stringify(nextSpec)) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
config.mcp_servers[server.id] = nextSpec;
|
|
844
853
|
|
|
845
854
|
writeTomlFile(CODEX_CONFIG_PATH, config);
|
|
846
855
|
}
|
|
@@ -849,10 +858,17 @@ function syncToCodexConfig(server) {
|
|
|
849
858
|
* 从 Codex 配置移除
|
|
850
859
|
*/
|
|
851
860
|
function removeFromCodexConfig(serverId) {
|
|
861
|
+
if (!fs.existsSync(CODEX_CONFIG_PATH)) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
852
865
|
const config = readTomlFile(CODEX_CONFIG_PATH, {});
|
|
853
866
|
|
|
854
867
|
if (config.mcp_servers && config.mcp_servers[serverId]) {
|
|
855
868
|
delete config.mcp_servers[serverId];
|
|
869
|
+
if (Object.keys(config.mcp_servers).length === 0) {
|
|
870
|
+
delete config.mcp_servers;
|
|
871
|
+
}
|
|
856
872
|
writeTomlFile(CODEX_CONFIG_PATH, config);
|
|
857
873
|
}
|
|
858
874
|
}
|
|
@@ -11,7 +11,7 @@ const { URL } = require('url');
|
|
|
11
11
|
const crypto = require('crypto');
|
|
12
12
|
const zlib = require('zlib');
|
|
13
13
|
const { loadConfig } = require('../../config/loader');
|
|
14
|
-
const {
|
|
14
|
+
const { PATHS } = require('../../config/paths');
|
|
15
15
|
|
|
16
16
|
// 内置模型优先级(当配置缺失时兜底)
|
|
17
17
|
const MODEL_PRIORITY = {
|
|
@@ -669,11 +669,12 @@ function collectResponseBody(res) {
|
|
|
669
669
|
* Get cache file path
|
|
670
670
|
*/
|
|
671
671
|
function getCacheFilePath() {
|
|
672
|
-
const
|
|
672
|
+
const filePath = PATHS.channelModels;
|
|
673
|
+
const dir = path.dirname(filePath);
|
|
673
674
|
if (!fs.existsSync(dir)) {
|
|
674
675
|
fs.mkdirSync(dir, { recursive: true });
|
|
675
676
|
}
|
|
676
|
-
return
|
|
677
|
+
return filePath;
|
|
677
678
|
}
|
|
678
679
|
|
|
679
680
|
/**
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
const { spawnSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
function runCommand(command, args, options = {}) {
|
|
4
|
+
const result = spawnSync(command, args, {
|
|
5
|
+
encoding: 'utf8',
|
|
6
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
7
|
+
...options
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
if (result.error) {
|
|
11
|
+
throw result.error;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getWindowsPowerShellCommand() {
|
|
18
|
+
return process.env.ComSpec && process.env.ComSpec.toLowerCase().includes('powershell')
|
|
19
|
+
? process.env.ComSpec
|
|
20
|
+
: 'powershell.exe';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runPowerShell(script, env = {}) {
|
|
24
|
+
return runCommand(getWindowsPowerShellCommand(), [
|
|
25
|
+
'-NoProfile',
|
|
26
|
+
'-NonInteractive',
|
|
27
|
+
'-Command',
|
|
28
|
+
script
|
|
29
|
+
], {
|
|
30
|
+
env: {
|
|
31
|
+
...process.env,
|
|
32
|
+
...env
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isSupported() {
|
|
38
|
+
if (process.platform === 'darwin') return true;
|
|
39
|
+
if (process.platform === 'win32') return true;
|
|
40
|
+
|
|
41
|
+
if (process.platform === 'linux') {
|
|
42
|
+
try {
|
|
43
|
+
const result = runCommand('secret-tool', ['--help']);
|
|
44
|
+
return result.status === 0 || result.status === 1;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getPassword(service, account) {
|
|
54
|
+
if (!service || !account) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
if (process.platform === 'darwin') {
|
|
60
|
+
const result = runCommand('security', [
|
|
61
|
+
'find-generic-password',
|
|
62
|
+
'-a',
|
|
63
|
+
String(account),
|
|
64
|
+
'-w',
|
|
65
|
+
'-s',
|
|
66
|
+
String(service)
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
if (result.status !== 0) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return String(result.stdout || '').trim() || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (process.platform === 'linux') {
|
|
77
|
+
const result = runCommand('secret-tool', [
|
|
78
|
+
'lookup',
|
|
79
|
+
'service',
|
|
80
|
+
String(service),
|
|
81
|
+
'account',
|
|
82
|
+
String(account)
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
if (result.status !== 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return String(result.stdout || '').trim() || null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (process.platform === 'win32') {
|
|
93
|
+
const script = `
|
|
94
|
+
Add-Type -AssemblyName System.Runtime.WindowsRuntime
|
|
95
|
+
$vault = New-Object Windows.Security.Credentials.PasswordVault
|
|
96
|
+
try {
|
|
97
|
+
$cred = $vault.Retrieve($env:CC_TOOL_SERVICE, $env:CC_TOOL_ACCOUNT)
|
|
98
|
+
$cred.RetrievePassword()
|
|
99
|
+
[Console]::Out.Write($cred.Password)
|
|
100
|
+
exit 0
|
|
101
|
+
} catch {
|
|
102
|
+
exit 2
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
const result = runPowerShell(script, {
|
|
106
|
+
CC_TOOL_SERVICE: String(service),
|
|
107
|
+
CC_TOOL_ACCOUNT: String(account)
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (result.status !== 0) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return String(result.stdout || '').trim() || null;
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function setPassword(service, account, password) {
|
|
124
|
+
if (!service || !account) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
if (process.platform === 'darwin') {
|
|
130
|
+
const result = runCommand('security', [
|
|
131
|
+
'add-generic-password',
|
|
132
|
+
'-U',
|
|
133
|
+
'-a',
|
|
134
|
+
String(account),
|
|
135
|
+
'-s',
|
|
136
|
+
String(service),
|
|
137
|
+
'-w',
|
|
138
|
+
String(password ?? '')
|
|
139
|
+
]);
|
|
140
|
+
return result.status === 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (process.platform === 'linux') {
|
|
144
|
+
deletePassword(service, account);
|
|
145
|
+
const result = runCommand('secret-tool', [
|
|
146
|
+
'store',
|
|
147
|
+
'--label',
|
|
148
|
+
String(service),
|
|
149
|
+
'service',
|
|
150
|
+
String(service),
|
|
151
|
+
'account',
|
|
152
|
+
String(account)
|
|
153
|
+
], {
|
|
154
|
+
input: String(password ?? '')
|
|
155
|
+
});
|
|
156
|
+
return result.status === 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (process.platform === 'win32') {
|
|
160
|
+
const script = `
|
|
161
|
+
Add-Type -AssemblyName System.Runtime.WindowsRuntime
|
|
162
|
+
$vault = New-Object Windows.Security.Credentials.PasswordVault
|
|
163
|
+
try {
|
|
164
|
+
$existing = $vault.Retrieve($env:CC_TOOL_SERVICE, $env:CC_TOOL_ACCOUNT)
|
|
165
|
+
$vault.Remove($existing)
|
|
166
|
+
} catch {}
|
|
167
|
+
$credential = New-Object Windows.Security.Credentials.PasswordCredential($env:CC_TOOL_SERVICE, $env:CC_TOOL_ACCOUNT, $env:CC_TOOL_PASSWORD)
|
|
168
|
+
$vault.Add($credential)
|
|
169
|
+
exit 0
|
|
170
|
+
`;
|
|
171
|
+
const result = runPowerShell(script, {
|
|
172
|
+
CC_TOOL_SERVICE: String(service),
|
|
173
|
+
CC_TOOL_ACCOUNT: String(account),
|
|
174
|
+
CC_TOOL_PASSWORD: String(password ?? '')
|
|
175
|
+
});
|
|
176
|
+
return result.status === 0;
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function deletePassword(service, account) {
|
|
186
|
+
if (!service || !account) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
if (process.platform === 'darwin') {
|
|
192
|
+
const result = runCommand('security', [
|
|
193
|
+
'delete-generic-password',
|
|
194
|
+
'-a',
|
|
195
|
+
String(account),
|
|
196
|
+
'-s',
|
|
197
|
+
String(service)
|
|
198
|
+
]);
|
|
199
|
+
return result.status === 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (process.platform === 'linux') {
|
|
203
|
+
const result = runCommand('secret-tool', [
|
|
204
|
+
'clear',
|
|
205
|
+
'service',
|
|
206
|
+
String(service),
|
|
207
|
+
'account',
|
|
208
|
+
String(account)
|
|
209
|
+
]);
|
|
210
|
+
return result.status === 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (process.platform === 'win32') {
|
|
214
|
+
const script = `
|
|
215
|
+
Add-Type -AssemblyName System.Runtime.WindowsRuntime
|
|
216
|
+
$vault = New-Object Windows.Security.Credentials.PasswordVault
|
|
217
|
+
try {
|
|
218
|
+
$credential = $vault.Retrieve($env:CC_TOOL_SERVICE, $env:CC_TOOL_ACCOUNT)
|
|
219
|
+
$vault.Remove($credential)
|
|
220
|
+
exit 0
|
|
221
|
+
} catch {
|
|
222
|
+
exit 2
|
|
223
|
+
}
|
|
224
|
+
`;
|
|
225
|
+
const result = runPowerShell(script, {
|
|
226
|
+
CC_TOOL_SERVICE: String(service),
|
|
227
|
+
CC_TOOL_ACCOUNT: String(account)
|
|
228
|
+
});
|
|
229
|
+
return result.status === 0;
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = {
|
|
239
|
+
isSupported,
|
|
240
|
+
getPassword,
|
|
241
|
+
setPassword,
|
|
242
|
+
deletePassword
|
|
243
|
+
};
|