coding-tool-x 3.6.13 → 3.7.1

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 (88) hide show
  1. package/README.md +45 -33
  2. package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
  4. package/dist/web/assets/Home-BwTaYu3q.js +1 -0
  5. package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
  6. package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
  7. package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
  8. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
  9. package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
  10. package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-Btfvh0GG.js +2 -0
  14. package/dist/web/assets/index-CLNa6DhH.css +1 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +3 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +46 -6
  22. package/src/commands/port-config.js +83 -75
  23. package/src/commands/proxy-control.js +59 -26
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/switch.js +5 -1
  26. package/src/commands/toggle-proxy.js +131 -53
  27. package/src/commands/ui.js +3 -4
  28. package/src/commands/workspace.js +3 -35
  29. package/src/config/default.js +19 -2
  30. package/src/config/loader.js +5 -76
  31. package/src/config/paths.js +96 -11
  32. package/src/index.js +10 -16
  33. package/src/server/api/commands.js +1 -1
  34. package/src/server/api/config-export.js +4 -2
  35. package/src/server/api/config-registry.js +40 -34
  36. package/src/server/api/config.js +5 -1
  37. package/src/server/api/dashboard.js +22 -6
  38. package/src/server/api/mcp.js +5 -3
  39. package/src/server/api/omp-channels.js +349 -0
  40. package/src/server/api/omp-config.js +164 -0
  41. package/src/server/api/omp-projects.js +76 -0
  42. package/src/server/api/omp-proxy.js +154 -0
  43. package/src/server/api/omp-sessions.js +334 -0
  44. package/src/server/api/omp-statistics.js +40 -0
  45. package/src/server/api/plugins.js +1 -1
  46. package/src/server/api/prompts.js +3 -2
  47. package/src/server/api/settings.js +2 -0
  48. package/src/server/api/skills.js +1 -1
  49. package/src/server/api/workspaces.js +3 -41
  50. package/src/server/index.js +41 -0
  51. package/src/server/omp-proxy-server.js +66 -0
  52. package/src/server/services/agents-service.js +2 -1
  53. package/src/server/services/base/proxy-utils.js +1 -0
  54. package/src/server/services/channel-balance.js +151 -1
  55. package/src/server/services/channel-scheduler.js +7 -1
  56. package/src/server/services/commands-service.js +11 -2
  57. package/src/server/services/config-export-service.js +296 -39
  58. package/src/server/services/config-registry-service.js +20 -18
  59. package/src/server/services/config-sync-manager.js +115 -6
  60. package/src/server/services/config-sync-service.js +4 -2
  61. package/src/server/services/config-templates-service.js +183 -45
  62. package/src/server/services/mcp-service.js +217 -33
  63. package/src/server/services/notification-hooks.js +138 -6
  64. package/src/server/services/omp-auth-providers.js +294 -0
  65. package/src/server/services/omp-channels.js +94 -0
  66. package/src/server/services/omp-config.js +269 -0
  67. package/src/server/services/omp-sessions.js +490 -0
  68. package/src/server/services/omp-settings-manager.js +975 -0
  69. package/src/server/services/omp-statistics-service.js +317 -0
  70. package/src/server/services/plugins-service.js +673 -11
  71. package/src/server/services/prompts-service.js +112 -7
  72. package/src/server/services/proxy-log-helper.js +1 -0
  73. package/src/server/services/proxy-runtime.js +5 -1
  74. package/src/server/services/sessions.js +19 -35
  75. package/src/server/services/skill-service.js +13 -3
  76. package/src/server/services/speed-test.js +34 -28
  77. package/src/server/services/ui-config.js +50 -12
  78. package/src/server/services/workspace-service.js +117 -225
  79. package/src/server/websocket-server.js +7 -1
  80. package/src/shared/platforms.js +201 -0
  81. package/src/utils/session.js +4 -10
  82. package/dist/web/assets/Home-BiaKZMah.js +0 -1
  83. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  84. package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
  85. package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
  86. package/dist/web/assets/index-BpBFF3b2.js +0 -2
  87. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  88. package/src/utils/cli-flags.js +0 -7
@@ -0,0 +1,294 @@
1
+ const { spawnSync } = require('child_process');
2
+ const ompConfig = require('./omp-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 || ompConfig.resolveOmpRuntime(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
+ };
@@ -0,0 +1,94 @@
1
+ const { PATHS } = require('../../config/paths');
2
+ const BaseChannelService = require('./base/base-channel-service');
3
+ const {
4
+ writeManagedOmpProviders,
5
+ removeManagedOmpProviders,
6
+ isManagedOmpProvidersActive,
7
+ getLastManagedOmpSyncResult
8
+ } = require('./omp-settings-manager');
9
+
10
+ class OmpChannelService extends BaseChannelService {
11
+ constructor() {
12
+ super({
13
+ platform: 'omp',
14
+ channelsFilePath: PATHS.channels.omp,
15
+ defaultGatewaySource: 'openai_compatible',
16
+ isProxyRunning: () => isManagedOmpProvidersActive()
17
+ });
18
+ }
19
+
20
+ _applyDefaults(channel) {
21
+ const normalized = super._applyDefaults(channel);
22
+ normalized.providerKey = normalized.providerKey || normalized.provider || normalized.name || normalized.id;
23
+ normalized.providerApi = normalized.providerApi || normalized.api || normalized.wireApi || 'openai-completions';
24
+ normalized.model = normalized.model || null;
25
+ normalized.models = Array.isArray(normalized.models) ? normalized.models : [];
26
+ normalized.allowedModels = Array.isArray(normalized.allowedModels) ? normalized.allowedModels : [];
27
+ normalized.speedTestModel = normalized.speedTestModel || null;
28
+ normalized.modelRedirects = Array.isArray(normalized.modelRedirects) ? normalized.modelRedirects : [];
29
+ return normalized;
30
+ }
31
+
32
+ _applyToNativeSettings(channel) {
33
+ writeManagedOmpProviders([channel]);
34
+ }
35
+
36
+ _onAfterCreate(_channel, allChannels) {
37
+ this.syncManagedOmpProviders(allChannels);
38
+ }
39
+
40
+ _onAfterUpdate(_oldChannel, _newChannel, allChannels) {
41
+ this.syncManagedOmpProviders(allChannels);
42
+ }
43
+
44
+ _onAfterDelete(_channel, allChannels) {
45
+ this.syncManagedOmpProviders(allChannels);
46
+ }
47
+
48
+ syncManagedOmpProviders(channels = this.getChannels().channels) {
49
+ const enabledChannels = (channels || []).filter(channel => channel.enabled !== false);
50
+ if (enabledChannels.length === 0) {
51
+ removeManagedOmpProviders();
52
+ return getLastManagedOmpSyncResult();
53
+ }
54
+ writeManagedOmpProviders(enabledChannels);
55
+ return getLastManagedOmpSyncResult();
56
+ }
57
+
58
+ disableManagedOmpProviders() {
59
+ removeManagedOmpProviders();
60
+ return getLastManagedOmpSyncResult();
61
+ }
62
+
63
+ syncManagedProviderExtension(channels = this.getChannels().channels) {
64
+ return this.syncManagedOmpProviders(channels);
65
+ }
66
+
67
+ disableManagedProviderExtension() {
68
+ this.disableManagedOmpProviders();
69
+ }
70
+ }
71
+
72
+ const service = new OmpChannelService();
73
+
74
+ module.exports = {
75
+ getChannels: () => service.getChannels(),
76
+ createChannel: (name, baseUrl, apiKey, extra = {}) => service.createChannel({
77
+ name,
78
+ baseUrl,
79
+ apiKey,
80
+ ...extra
81
+ }),
82
+ updateChannel: (id, updates) => service.updateChannel(id, updates),
83
+ markChannelAsRecentlyUsed: (id) => service.updateChannel(id, {}),
84
+ deleteChannel: (id) => service.deleteChannel(id),
85
+ getEnabledChannels: () => service.getEnabledChannels(),
86
+ saveChannelOrder: (order) => service.saveChannelOrder(order),
87
+ applyChannelToSettings: (id) => service.applyChannelToSettings(id),
88
+ disableAllChannels: () => service.disableAllChannels(),
89
+ getEffectiveApiKey: (channel) => channel?.apiKey || null,
90
+ syncManagedOmpProviders: (channels) => service.syncManagedOmpProviders(channels),
91
+ disableManagedOmpProviders: () => service.disableManagedOmpProviders(),
92
+ syncManagedProviderExtension: (channels) => service.syncManagedProviderExtension(channels),
93
+ disableManagedProviderExtension: () => service.disableManagedProviderExtension()
94
+ };
@@ -0,0 +1,269 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execFileSync } = require('child_process');
4
+ const yaml = require('js-yaml');
5
+ const { HOME_DIR, PATHS = {} } = require('../../config/paths');
6
+
7
+ const DEFAULT_OMP_COMMAND = 'omp';
8
+
9
+ function expandHome(input = '') {
10
+ const value = String(input || '').trim();
11
+ if (!value) return '';
12
+ if (value === '~') return HOME_DIR;
13
+ if (value.startsWith('~/')) return path.join(HOME_DIR, value.slice(2));
14
+ return value;
15
+ }
16
+
17
+ function normalizeProfileName(profile = '') {
18
+ const value = String(profile || '').trim();
19
+ if (!value || value === 'default') return '';
20
+ return value;
21
+ }
22
+
23
+ function getOmpProfile(env = process.env) {
24
+ return normalizeProfileName(env.OMP_PROFILE);
25
+ }
26
+
27
+ function isAbsolutePathLike(value = '') {
28
+ return path.isAbsolute(value) || /^[a-zA-Z]:[\\/]/.test(value) || String(value).startsWith('\\\\');
29
+ }
30
+
31
+ function getOmpConfigRoot(env = process.env) {
32
+ const configured = expandHome(env.OMP_CONFIG_DIR || '');
33
+ if (!configured) return path.join(HOME_DIR, '.omp');
34
+ return isAbsolutePathLike(configured) ? configured : path.join(HOME_DIR, configured);
35
+ }
36
+
37
+ function getDefaultOmpAgentDir(env = process.env) {
38
+ const configRoot = getOmpConfigRoot(env);
39
+ const profile = getOmpProfile(env);
40
+ return profile
41
+ ? path.join(configRoot, 'profiles', profile, 'agent')
42
+ : path.join(configRoot, 'agent');
43
+ }
44
+
45
+ function getFallbackOmpAgentDir(runtime, env = process.env) {
46
+ const configured = expandHome(env.OMP_CODING_AGENT_DIR || '');
47
+ if (configured) return configured;
48
+ return getDefaultOmpAgentDir(env);
49
+ }
50
+
51
+ function buildCommandEnv(env = process.env) {
52
+ return {
53
+ ...process.env,
54
+ ...env
55
+ };
56
+ }
57
+
58
+ function runOmpCommand(command, args = [], env = process.env, options = {}, stdio = ['ignore', 'pipe', 'pipe']) {
59
+ const runner = options.commandRunner || execFileSync;
60
+ return runner(command, args, {
61
+ encoding: 'utf8',
62
+ env: buildCommandEnv(env),
63
+ stdio,
64
+ timeout: options.timeout || 3000
65
+ });
66
+ }
67
+
68
+ function readOmpAgentDirFromCommand(command, env = process.env, options = {}) {
69
+ try {
70
+ const output = runOmpCommand(command, ['config', 'path'], env, options);
71
+ const agentDir = String(output || '').trim().split(/\r?\n/).find(Boolean);
72
+ return agentDir ? path.resolve(expandHome(agentDir)) : '';
73
+ } catch {
74
+ return '';
75
+ }
76
+ }
77
+
78
+ function getOmpAgentDir(env = process.env, options = {}) {
79
+ const runtime = options.runtime || resolveOmpRuntime(env, options);
80
+ if (runtime.runtime === 'omp' && runtime.installed) {
81
+ const commandAgentDir = readOmpAgentDirFromCommand(runtime.command, env, options);
82
+ if (commandAgentDir) {
83
+ return commandAgentDir;
84
+ }
85
+ }
86
+ return path.resolve(expandHome(getFallbackOmpAgentDir(runtime, env)));
87
+ }
88
+
89
+ function getOmpPaths(env = process.env, options = {}) {
90
+ const runtime = options.runtime || resolveOmpRuntime(env, options);
91
+ const agentDir = getOmpAgentDir(env, { ...options, runtime });
92
+ return {
93
+ agentDir,
94
+ config: path.join(agentDir, 'config.yml'),
95
+ settings: path.join(agentDir, 'config.yml'),
96
+ settingsJsonLegacy: path.join(agentDir, 'settings.json'),
97
+ auth: path.join(agentDir, 'auth.json'),
98
+ models: path.join(agentDir, 'models.yml'),
99
+ modelsYml: path.join(agentDir, 'models.yml'),
100
+ modelsJsonLegacy: path.join(agentDir, 'models.json'),
101
+ mcp: path.join(agentDir, 'mcp.json'),
102
+ sessions: path.join(agentDir, 'sessions'),
103
+ skills: path.join(agentDir, 'skills'),
104
+ prompts: path.join(agentDir, 'prompts'),
105
+ commands: path.join(agentDir, 'commands'),
106
+ notes: path.join(agentDir, 'notes'),
107
+ extensions: path.join(agentDir, 'extensions'),
108
+ themes: path.join(agentDir, 'themes'),
109
+ packages: path.join(agentDir, 'packages'),
110
+ managedProviderExtension: path.join(agentDir, 'extensions', 'coding-tool-x-provider.ts'),
111
+ managedVisibilityState: path.join(PATHS.storage || path.join(HOME_DIR, '.cc-tool', 'storage'), 'omp-managed-visibility.json')
112
+ };
113
+ }
114
+
115
+ function ensureOmpDir(dirPath) {
116
+ if (!fs.existsSync(dirPath)) {
117
+ fs.mkdirSync(dirPath, { recursive: true });
118
+ }
119
+ }
120
+
121
+ function readJsonFile(filePath, fallback = {}) {
122
+ try {
123
+ if (!fs.existsSync(filePath)) return fallback;
124
+ const raw = fs.readFileSync(filePath, 'utf8');
125
+ return raw.trim() ? JSON.parse(raw) : fallback;
126
+ } catch {
127
+ return fallback;
128
+ }
129
+ }
130
+
131
+ function writeJsonFile(filePath, data) {
132
+ ensureOmpDir(path.dirname(filePath));
133
+ fs.writeFileSync(filePath, JSON.stringify(data || {}, null, 2), 'utf8');
134
+ }
135
+
136
+ function readYamlFile(filePath, fallback = {}) {
137
+ try {
138
+ if (!fs.existsSync(filePath)) return fallback;
139
+ const raw = fs.readFileSync(filePath, 'utf8');
140
+ if (!raw.trim()) return fallback;
141
+ const parsed = yaml.load(raw);
142
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
143
+ } catch {
144
+ return fallback;
145
+ }
146
+ }
147
+
148
+ function writeYamlFile(filePath, data) {
149
+ ensureOmpDir(path.dirname(filePath));
150
+ fs.writeFileSync(filePath, yaml.dump(data || {}, {
151
+ lineWidth: 120,
152
+ noRefs: true,
153
+ sortKeys: false
154
+ }), 'utf8');
155
+ }
156
+
157
+ function readOmpSettings() {
158
+ const paths = getOmpPaths();
159
+ return readYamlFile(paths.settings, readJsonFile(paths.settingsJsonLegacy, {}));
160
+ }
161
+
162
+ function writeOmpSettings(settings) {
163
+ writeYamlFile(getOmpPaths().settings, settings || {});
164
+ }
165
+
166
+ function getOmpCommand(env = process.env) {
167
+ return String(env.OMP_COMMAND || DEFAULT_OMP_COMMAND).trim() || DEFAULT_OMP_COMMAND;
168
+ }
169
+
170
+ function commandExists(command, env = process.env, options = {}) {
171
+ try {
172
+ runOmpCommand(command, ['--version'], env, options, 'ignore');
173
+ return true;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+
179
+ function resolveOmpRuntime(env = process.env, options = {}) {
180
+ const configured = env.OMP_COMMAND;
181
+ if (configured) {
182
+ const command = getOmpCommand(env);
183
+ return {
184
+ command,
185
+ runtime: 'omp',
186
+ installed: commandExists(command, env, options),
187
+ configured: true,
188
+ commandSource: 'OMP_COMMAND'
189
+ };
190
+ }
191
+
192
+ if (commandExists(DEFAULT_OMP_COMMAND, env, options)) {
193
+ return {
194
+ command: DEFAULT_OMP_COMMAND,
195
+ runtime: 'omp',
196
+ installed: true,
197
+ configured: false,
198
+ commandSource: 'path'
199
+ };
200
+ }
201
+
202
+ return {
203
+ command: DEFAULT_OMP_COMMAND,
204
+ runtime: 'omp',
205
+ installed: false,
206
+ configured: false,
207
+ commandSource: 'fallback'
208
+ };
209
+ }
210
+
211
+ function isOmpInstalled(env = process.env, options = {}) {
212
+ const runtime = options.runtime || resolveOmpRuntime(env, options);
213
+ if (runtime.installed) {
214
+ return true;
215
+ }
216
+ return fs.existsSync(getOmpPaths(env, { ...options, runtime }).agentDir);
217
+ }
218
+
219
+ function readTextFile(filePath, fallback = '') {
220
+ try {
221
+ if (!fs.existsSync(filePath)) return fallback;
222
+ return fs.readFileSync(filePath, 'utf8');
223
+ } catch {
224
+ return fallback;
225
+ }
226
+ }
227
+
228
+ function getOmpStatus(env = process.env, options = {}) {
229
+ const runtime = resolveOmpRuntime(env, options);
230
+ const paths = getOmpPaths(env, { ...options, runtime });
231
+ return {
232
+ installed: isOmpInstalled(env, { ...options, runtime }),
233
+ runtime: runtime.runtime,
234
+ command: runtime.command,
235
+ commandSource: runtime.commandSource,
236
+ agentDir: paths.agentDir,
237
+ settingsPath: paths.settings,
238
+ authPath: paths.auth,
239
+ modelsPath: paths.modelsYml,
240
+ modelsYmlPath: paths.modelsYml,
241
+ modelsJsonLegacyPath: paths.modelsJsonLegacy,
242
+ sessionsDir: paths.sessions,
243
+ skillsDir: paths.skills,
244
+ promptsDir: paths.prompts,
245
+ commandsDir: paths.commands,
246
+ extensionsDir: paths.extensions,
247
+ themesDir: paths.themes,
248
+ packagesDir: paths.packages
249
+ };
250
+ }
251
+
252
+ module.exports = {
253
+ expandHome,
254
+ getOmpAgentDir,
255
+ getOmpPaths,
256
+ ensureOmpDir,
257
+ readJsonFile,
258
+ readYamlFile,
259
+ readTextFile,
260
+ writeJsonFile,
261
+ writeYamlFile,
262
+ readOmpSettings,
263
+ writeOmpSettings,
264
+ getOmpCommand,
265
+ getOmpProfile,
266
+ resolveOmpRuntime,
267
+ isOmpInstalled,
268
+ getOmpStatus
269
+ };