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.
Files changed (89) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +253 -326
  3. package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
  5. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  6. package/dist/web/assets/Home-D7KX7iF8.js +1 -0
  7. package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
  8. package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
  9. package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
  10. package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
  12. package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
  13. package/dist/web/assets/index-CL-qpoJ_.js +2 -0
  14. package/dist/web/assets/index-D_5dRFOL.css +1 -0
  15. package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
  16. package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
  17. package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
  18. package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
  19. package/dist/web/index.html +7 -7
  20. package/docs/home.png +0 -0
  21. package/package.json +14 -5
  22. package/src/commands/daemon.js +3 -2
  23. package/src/commands/security.js +1 -2
  24. package/src/commands/toggle-proxy.js +100 -5
  25. package/src/config/paths.js +718 -90
  26. package/src/server/api/agents.js +1 -1
  27. package/src/server/api/channels.js +9 -0
  28. package/src/server/api/claude-hooks.js +13 -8
  29. package/src/server/api/codex-channels.js +9 -0
  30. package/src/server/api/codex-proxy.js +27 -15
  31. package/src/server/api/gemini-proxy.js +22 -11
  32. package/src/server/api/hooks.js +45 -0
  33. package/src/server/api/oauth-credentials.js +163 -0
  34. package/src/server/api/opencode-proxy.js +22 -10
  35. package/src/server/api/plugins.js +2 -1
  36. package/src/server/api/proxy.js +39 -44
  37. package/src/server/api/skills.js +91 -13
  38. package/src/server/api/ui-config.js +5 -0
  39. package/src/server/codex-proxy-server.js +90 -70
  40. package/src/server/gemini-proxy-server.js +107 -88
  41. package/src/server/index.js +2 -0
  42. package/src/server/opencode-proxy-server.js +381 -225
  43. package/src/server/proxy-server.js +86 -60
  44. package/src/server/services/alias.js +3 -3
  45. package/src/server/services/channels.js +21 -24
  46. package/src/server/services/codex-channels.js +158 -255
  47. package/src/server/services/codex-config.js +2 -5
  48. package/src/server/services/codex-env-manager.js +423 -0
  49. package/src/server/services/codex-settings-manager.js +21 -357
  50. package/src/server/services/codex-statistics-service.js +3 -27
  51. package/src/server/services/config-export-service.js +43 -9
  52. package/src/server/services/config-registry-service.js +3 -2
  53. package/src/server/services/config-sync-manager.js +1 -1
  54. package/src/server/services/favorites.js +4 -3
  55. package/src/server/services/gemini-channels.js +14 -12
  56. package/src/server/services/gemini-statistics-service.js +3 -25
  57. package/src/server/services/mcp-service.js +35 -19
  58. package/src/server/services/model-detector.js +4 -3
  59. package/src/server/services/native-keychain.js +243 -0
  60. package/src/server/services/native-oauth-adapters.js +891 -0
  61. package/src/server/services/network-access.js +39 -1
  62. package/src/server/services/notification-hooks.js +951 -0
  63. package/src/server/services/oauth-credentials-service.js +786 -0
  64. package/src/server/services/oauth-utils.js +49 -0
  65. package/src/server/services/opencode-channels.js +19 -15
  66. package/src/server/services/opencode-sessions.js +2 -2
  67. package/src/server/services/opencode-settings-manager.js +169 -16
  68. package/src/server/services/opencode-statistics-service.js +3 -27
  69. package/src/server/services/plugins-service.js +115 -15
  70. package/src/server/services/prompts-service.js +2 -3
  71. package/src/server/services/proxy-log-helper.js +242 -0
  72. package/src/server/services/proxy-runtime.js +6 -4
  73. package/src/server/services/repo-scanner-base.js +12 -4
  74. package/src/server/services/request-logger.js +7 -7
  75. package/src/server/services/security-config.js +4 -4
  76. package/src/server/services/session-cache.js +2 -2
  77. package/src/server/services/sessions.js +2 -2
  78. package/src/server/services/settings-manager.js +13 -0
  79. package/src/server/services/skill-service.js +867 -368
  80. package/src/server/services/statistics-service.js +5 -5
  81. package/src/server/services/ui-config.js +4 -3
  82. package/src/server/services/workspace-service.js +1 -1
  83. package/src/server/websocket-server.js +5 -4
  84. package/dist/web/assets/Home-BsSioaaB.css +0 -1
  85. package/dist/web/assets/Home-obifg_9E.js +0 -1
  86. package/dist/web/assets/index-C7LPdVsN.js +0 -2
  87. package/dist/web/assets/index-eEmjZKWP.css +0 -1
  88. package/docs/bannel.png +0 -0
  89. 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 ccToolDir = PATHS.base;
35
- if (!fs.existsSync(ccToolDir)) {
36
- fs.mkdirSync(ccToolDir, { recursive: true });
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 = normalizeToolTokens(entry.tokens || {});
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 = normalizeToolTokens(requestData.tokens || {});
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 CC_TOOL_DIR = path.join(HOME_DIR, '.cc-tool');
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
- if (fs.existsSync(filePath)) {
295
- const content = fs.readFileSync(filePath, 'utf-8');
296
- return toml.parse(content);
297
- }
297
+ const content = fs.readFileSync(filePath, 'utf-8');
298
+ return toml.parse(content);
298
299
  } catch (err) {
299
- console.error(`[MCP] Failed to read ${filePath}:`, err.message);
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
- // 转换为 Codex TOML 格式
843
- config.mcp_servers[server.id] = convertToCodexFormat(server.server);
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 { HOME_DIR } = require('../../config/paths');
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 dir = path.join(HOME_DIR, '.cc-tool');
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 path.join(dir, 'channel-models.json');
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
+ };