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.
- package/README.md +45 -33
- package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
- package/dist/web/assets/Home-BwTaYu3q.js +1 -0
- package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
- package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
- package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
- package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
- package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-Btfvh0GG.js +2 -0
- package/dist/web/assets/index-CLNa6DhH.css +1 -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 +3 -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 +46 -6
- package/src/commands/port-config.js +83 -75
- package/src/commands/proxy-control.js +59 -26
- 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 +96 -11
- package/src/index.js +10 -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/omp-channels.js +349 -0
- package/src/server/api/omp-config.js +164 -0
- package/src/server/api/omp-projects.js +76 -0
- package/src/server/api/omp-proxy.js +154 -0
- package/src/server/api/omp-sessions.js +334 -0
- package/src/server/api/omp-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/omp-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 +296 -39
- 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/notification-hooks.js +138 -6
- package/src/server/services/omp-auth-providers.js +294 -0
- package/src/server/services/omp-channels.js +94 -0
- package/src/server/services/omp-config.js +269 -0
- package/src/server/services/omp-sessions.js +490 -0
- package/src/server/services/omp-settings-manager.js +975 -0
- package/src/server/services/omp-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 +34 -28
- 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/shared/platforms.js +201 -0
- 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
|
@@ -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
|
+
};
|