ccnew 0.1.10
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 +107 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/core/apply.js +152 -0
- package/core/backup.js +53 -0
- package/core/constants.js +78 -0
- package/core/desktop-service.js +403 -0
- package/core/desktop-state.js +1021 -0
- package/core/index.js +1468 -0
- package/core/paths.js +99 -0
- package/core/presets.js +171 -0
- package/core/probe.js +70 -0
- package/core/routing.js +334 -0
- package/core/store.js +218 -0
- package/core/utils.js +225 -0
- package/core/writers/codex.js +102 -0
- package/core/writers/index.js +16 -0
- package/core/writers/openclaw.js +93 -0
- package/core/writers/opencode.js +91 -0
- package/desktop/assets/fml-icon.png +0 -0
- package/desktop/assets/march-mark.svg +26 -0
- package/desktop/main.js +275 -0
- package/desktop/preload.cjs +67 -0
- package/desktop/preload.js +49 -0
- package/desktop/renderer/app.js +327 -0
- package/desktop/renderer/index.html +130 -0
- package/desktop/renderer/styles.css +490 -0
- package/package.json +111 -0
- package/scripts/build-web.mjs +95 -0
- package/scripts/desktop-dev.mjs +90 -0
- package/scripts/desktop-pack-win.mjs +81 -0
- package/scripts/postinstall.mjs +49 -0
- package/scripts/prepublish-check.mjs +57 -0
- package/scripts/serve-site.mjs +51 -0
- package/site/app.js +10 -0
- package/site/assets/fml-icon.png +0 -0
- package/site/assets/march-mark.svg +26 -0
- package/site/index.html +337 -0
- package/site/styles.css +840 -0
- package/src/App.tsx +1557 -0
- package/src/components/layout/app-sidebar.tsx +103 -0
- package/src/components/layout/top-toolbar.tsx +44 -0
- package/src/components/layout/workspace-tabs.tsx +32 -0
- package/src/components/providers/inspector-panel.tsx +84 -0
- package/src/components/providers/metric-strip.tsx +26 -0
- package/src/components/providers/provider-editor.tsx +87 -0
- package/src/components/providers/provider-table.tsx +85 -0
- package/src/components/ui/logo-mark.tsx +32 -0
- package/src/features/mcp/mcp-view.tsx +45 -0
- package/src/features/prompts/prompts-view.tsx +40 -0
- package/src/features/providers/providers-view.tsx +40 -0
- package/src/features/providers/types.ts +26 -0
- package/src/features/skills/skills-view.tsx +44 -0
- package/src/hooks/use-control-workspace.ts +235 -0
- package/src/index.css +22 -0
- package/src/lib/client.ts +726 -0
- package/src/lib/query-client.ts +3 -0
- package/src/lib/workspace-sections.ts +34 -0
- package/src/main.tsx +14 -0
- package/src/types.ts +137 -0
- package/src/vite-env.d.ts +64 -0
- package/src-tauri/README.md +11 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { applyProvider, applyStoredProvider } from "./apply.js";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_BASE_URL,
|
|
4
|
+
DEFAULT_OPENCLAW_BASE_URL,
|
|
5
|
+
DEFAULT_PRIMARY_MODEL,
|
|
6
|
+
DEFAULT_PROVIDER_NAME,
|
|
7
|
+
MODEL_DEFINITIONS,
|
|
8
|
+
PLATFORM_META,
|
|
9
|
+
SUPPORTED_PLATFORMS
|
|
10
|
+
} from "./constants.js";
|
|
11
|
+
import {
|
|
12
|
+
deleteMcpServerFromDesktop as deleteMcpInState,
|
|
13
|
+
deletePromptFromDesktop as deletePromptInState,
|
|
14
|
+
deleteSkillFromDesktop as deleteSkillInState,
|
|
15
|
+
getDesktopState,
|
|
16
|
+
toggleMcpServerForPlatform,
|
|
17
|
+
togglePromptFromDesktop as togglePromptInState,
|
|
18
|
+
toggleSkillRepoFromDesktop as toggleSkillRepoInState,
|
|
19
|
+
upsertMcpServerFromDesktop as upsertMcpInState,
|
|
20
|
+
upsertPromptFromDesktop as upsertPromptInState,
|
|
21
|
+
upsertSkillFromDesktop as upsertSkillInState
|
|
22
|
+
} from "./desktop-state.js";
|
|
23
|
+
import { getPlatformTargetFiles } from "./paths.js";
|
|
24
|
+
import {
|
|
25
|
+
buildRoutingSnapshot,
|
|
26
|
+
ensureRoutingProviders,
|
|
27
|
+
maybeFailoverPlatform,
|
|
28
|
+
recordProbeResults,
|
|
29
|
+
setPrimaryProvider,
|
|
30
|
+
upsertPlatformRouting
|
|
31
|
+
} from "./routing.js";
|
|
32
|
+
import { getBestProbeResult, probeBaseUrls } from "./probe.js";
|
|
33
|
+
import { getPreset, listPresets, removePreset, upsertPreset } from "./presets.js";
|
|
34
|
+
import { getCurrentProvider, listProviders } from "./store.js";
|
|
35
|
+
import { buildOpenClawBaseUrl, maskApiKey, normalizeBaseUrl, readJson, writeJson } from "./utils.js";
|
|
36
|
+
|
|
37
|
+
function serializeProvider(provider, currentProviderId, routingState) {
|
|
38
|
+
return {
|
|
39
|
+
id: provider.id,
|
|
40
|
+
name: provider.name,
|
|
41
|
+
baseUrl: provider.baseUrl,
|
|
42
|
+
model: provider.model || DEFAULT_PRIMARY_MODEL,
|
|
43
|
+
maskedApiKey: maskApiKey(provider.apiKey),
|
|
44
|
+
createdAt: provider.createdAt || null,
|
|
45
|
+
updatedAt: provider.updatedAt || null,
|
|
46
|
+
isActive: provider.id === currentProviderId,
|
|
47
|
+
health: routingState?.health || "unknown",
|
|
48
|
+
lastLatency: routingState?.lastLatency ?? null,
|
|
49
|
+
lastCheckedAt: routingState?.lastCheckedAt || null,
|
|
50
|
+
lastError: routingState?.lastError || null,
|
|
51
|
+
failoverRank: Number.isFinite(routingState?.failoverRank) ? routingState.failoverRank : null,
|
|
52
|
+
failoverRole: routingState?.role || "standby",
|
|
53
|
+
costTier: routingState?.costTier || "unknown"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildRoutingContexts() {
|
|
58
|
+
return SUPPORTED_PLATFORMS.map((platform) => {
|
|
59
|
+
const providers = listProviders(platform);
|
|
60
|
+
const currentProvider = getCurrentProvider(platform);
|
|
61
|
+
ensureRoutingProviders(platform, providers, currentProvider?.id || null);
|
|
62
|
+
return {
|
|
63
|
+
platform,
|
|
64
|
+
providers,
|
|
65
|
+
currentProviderId: currentProvider?.id || null
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildPlatformSnapshot(platform, routingPlatform) {
|
|
71
|
+
const providers = listProviders(platform);
|
|
72
|
+
const currentProvider = getCurrentProvider(platform);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
id: platform,
|
|
76
|
+
label: PLATFORM_META[platform].label,
|
|
77
|
+
command: PLATFORM_META[platform].command,
|
|
78
|
+
defaultBaseUrl: platform === "openclaw" ? DEFAULT_OPENCLAW_BASE_URL : DEFAULT_BASE_URL,
|
|
79
|
+
defaultProviderName: DEFAULT_PROVIDER_NAME,
|
|
80
|
+
currentProviderId: currentProvider?.id || null,
|
|
81
|
+
currentProviderName: currentProvider?.name || null,
|
|
82
|
+
providerCount: providers.length,
|
|
83
|
+
targetFiles: getPlatformTargetFiles(platform),
|
|
84
|
+
providers: providers.map((provider) =>
|
|
85
|
+
serializeProvider(
|
|
86
|
+
provider,
|
|
87
|
+
currentProvider?.id || null,
|
|
88
|
+
routingPlatform?.providerStates?.find((item) => item.providerId === provider.id)
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validatePlatform(platform) {
|
|
95
|
+
if (!SUPPORTED_PLATFORMS.includes(platform)) {
|
|
96
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildInputForPlatform(platform, input) {
|
|
101
|
+
const name = `${input?.name || DEFAULT_PROVIDER_NAME}`.trim() || DEFAULT_PROVIDER_NAME;
|
|
102
|
+
const apiKey = `${input?.apiKey || ""}`.trim();
|
|
103
|
+
const fallbackBaseUrl = platform === "openclaw" ? DEFAULT_OPENCLAW_BASE_URL : DEFAULT_BASE_URL;
|
|
104
|
+
const baseUrl = normalizeBaseUrl(`${input?.baseUrl || fallbackBaseUrl}`.trim());
|
|
105
|
+
const model = `${input?.model || DEFAULT_PRIMARY_MODEL}`.trim() || DEFAULT_PRIMARY_MODEL;
|
|
106
|
+
|
|
107
|
+
if (!apiKey) {
|
|
108
|
+
throw new Error("API Key 不能为空");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
apiKey,
|
|
114
|
+
baseUrl,
|
|
115
|
+
model
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getDesktopSnapshot() {
|
|
120
|
+
const desktopState = getDesktopState();
|
|
121
|
+
const routing = buildRoutingSnapshot(buildRoutingContexts());
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
appName: "ccon",
|
|
125
|
+
version: "0.1.10",
|
|
126
|
+
generatedAt: new Date().toISOString(),
|
|
127
|
+
models: MODEL_DEFINITIONS,
|
|
128
|
+
platforms: SUPPORTED_PLATFORMS.map((platform) =>
|
|
129
|
+
buildPlatformSnapshot(
|
|
130
|
+
platform,
|
|
131
|
+
routing.platforms.find((item) => item.platform === platform)
|
|
132
|
+
)
|
|
133
|
+
),
|
|
134
|
+
mcpServers: desktopState.mcpServers,
|
|
135
|
+
prompts: desktopState.prompts,
|
|
136
|
+
skills: desktopState.skills,
|
|
137
|
+
skillRepos: desktopState.skillRepos,
|
|
138
|
+
presets: listPresets(),
|
|
139
|
+
routing
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function saveProviderFromDesktop(platform, input, options = {}) {
|
|
144
|
+
validatePlatform(platform);
|
|
145
|
+
const providerInput = buildInputForPlatform(platform, input);
|
|
146
|
+
const result = applyProvider(platform, providerInput, {
|
|
147
|
+
backup: options.backup !== false,
|
|
148
|
+
overwrite: options.overwrite === true,
|
|
149
|
+
activate: options.activate !== false
|
|
150
|
+
});
|
|
151
|
+
const providers = listProviders(platform);
|
|
152
|
+
setPrimaryProvider(
|
|
153
|
+
platform,
|
|
154
|
+
result.provider.id,
|
|
155
|
+
providers.map((provider) => provider.id)
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
snapshot: getDesktopSnapshot(),
|
|
160
|
+
result: {
|
|
161
|
+
backupDir: result.backupDir,
|
|
162
|
+
targetFiles: result.targetFiles
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function activateProviderFromDesktop(platform, nameOrId, options = {}) {
|
|
168
|
+
validatePlatform(platform);
|
|
169
|
+
const result = applyStoredProvider(platform, nameOrId, {
|
|
170
|
+
backup: options.backup !== false,
|
|
171
|
+
overwrite: options.overwrite === true,
|
|
172
|
+
activate: true
|
|
173
|
+
});
|
|
174
|
+
const providers = listProviders(platform);
|
|
175
|
+
setPrimaryProvider(
|
|
176
|
+
platform,
|
|
177
|
+
result.provider.id,
|
|
178
|
+
providers.map((provider) => provider.id)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
snapshot: getDesktopSnapshot(),
|
|
183
|
+
result: {
|
|
184
|
+
backupDir: result.backupDir,
|
|
185
|
+
targetFiles: result.targetFiles
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function toggleMcpFromDesktop(serverId, platform) {
|
|
191
|
+
validatePlatform(platform);
|
|
192
|
+
toggleMcpServerForPlatform(serverId, platform);
|
|
193
|
+
return getDesktopSnapshot();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function upsertMcpFromDesktop(input) {
|
|
197
|
+
upsertMcpInState(input);
|
|
198
|
+
return getDesktopSnapshot();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function deleteMcpFromDesktop(serverId) {
|
|
202
|
+
deleteMcpInState(serverId);
|
|
203
|
+
return getDesktopSnapshot();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function togglePromptFromDesktop(promptId) {
|
|
207
|
+
togglePromptInState(promptId);
|
|
208
|
+
return getDesktopSnapshot();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function upsertPromptFromDesktop(input) {
|
|
212
|
+
upsertPromptInState(input);
|
|
213
|
+
return getDesktopSnapshot();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function deletePromptFromDesktop(promptId) {
|
|
217
|
+
deletePromptInState(promptId);
|
|
218
|
+
return getDesktopSnapshot();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function upsertSkillFromDesktop(input) {
|
|
222
|
+
upsertSkillInState(input);
|
|
223
|
+
return getDesktopSnapshot();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function deleteSkillFromDesktop(skillId) {
|
|
227
|
+
deleteSkillInState(skillId);
|
|
228
|
+
return getDesktopSnapshot();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function toggleSkillRepoFromDesktop(repoId) {
|
|
232
|
+
toggleSkillRepoInState(repoId);
|
|
233
|
+
return getDesktopSnapshot();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function probePlatformProvidersFromDesktop(platform) {
|
|
237
|
+
validatePlatform(platform);
|
|
238
|
+
const currentSnapshot = getDesktopSnapshot();
|
|
239
|
+
const snapshot = currentSnapshot.platforms.find((item) => item.id === platform) || buildPlatformSnapshot(platform);
|
|
240
|
+
const providerUrls = snapshot.providers.map((provider) => provider.baseUrl);
|
|
241
|
+
|
|
242
|
+
if (providerUrls.length === 0) {
|
|
243
|
+
return {
|
|
244
|
+
snapshot,
|
|
245
|
+
results: [],
|
|
246
|
+
best: null
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const results = await probeBaseUrls(providerUrls, { timeoutMs: 5000 });
|
|
251
|
+
const providers = listProviders(platform);
|
|
252
|
+
recordProbeResults(platform, providers, results);
|
|
253
|
+
const currentProvider = getCurrentProvider(platform);
|
|
254
|
+
const failover = maybeFailoverPlatform(platform, providers, currentProvider?.id || null);
|
|
255
|
+
if (failover?.nextProviderId) {
|
|
256
|
+
applyStoredProvider(platform, failover.nextProviderId, {
|
|
257
|
+
backup: true,
|
|
258
|
+
overwrite: false,
|
|
259
|
+
activate: true
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
snapshot: getDesktopSnapshot(),
|
|
265
|
+
results,
|
|
266
|
+
best: getBestProbeResult(results),
|
|
267
|
+
failover: failover || null
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function probeCandidateFromDesktop(platform, baseUrl) {
|
|
272
|
+
validatePlatform(platform);
|
|
273
|
+
const normalizedBaseUrl = normalizeBaseUrl(`${baseUrl || ""}`.trim());
|
|
274
|
+
|
|
275
|
+
if (!normalizedBaseUrl) {
|
|
276
|
+
throw new Error("测试地址不能为空");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const finalUrl =
|
|
280
|
+
platform === "openclaw" && !normalizedBaseUrl.endsWith("/v1")
|
|
281
|
+
? buildOpenClawBaseUrl(normalizedBaseUrl)
|
|
282
|
+
: normalizedBaseUrl;
|
|
283
|
+
const results = await probeBaseUrls([finalUrl], { timeoutMs: 5000 });
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
result: results[0] || null
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function savePresetFromDesktop(input) {
|
|
291
|
+
return {
|
|
292
|
+
preset: upsertPreset(input),
|
|
293
|
+
snapshot: getDesktopSnapshot()
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function deletePresetFromDesktop(name) {
|
|
298
|
+
return {
|
|
299
|
+
preset: removePreset(name),
|
|
300
|
+
snapshot: getDesktopSnapshot()
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function applyPresetFromDesktop(name, options = {}) {
|
|
305
|
+
const preset = getPreset(name);
|
|
306
|
+
if (!preset) {
|
|
307
|
+
throw new Error(`Preset not found: ${name}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const applied = [];
|
|
311
|
+
const skipped = [];
|
|
312
|
+
|
|
313
|
+
for (const platform of SUPPORTED_PLATFORMS) {
|
|
314
|
+
const providers = listProviders(platform);
|
|
315
|
+
const currentProvider = getCurrentProvider(platform);
|
|
316
|
+
const matchingProvider =
|
|
317
|
+
providers.find((provider) => provider.name.trim().toLowerCase() === preset.providerName.trim().toLowerCase()) ||
|
|
318
|
+
currentProvider ||
|
|
319
|
+
providers[0] ||
|
|
320
|
+
null;
|
|
321
|
+
const apiKey = matchingProvider?.apiKey || "";
|
|
322
|
+
|
|
323
|
+
if (!apiKey) {
|
|
324
|
+
skipped.push({
|
|
325
|
+
platform,
|
|
326
|
+
reason: "缺少可复用 API Key"
|
|
327
|
+
});
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const input = {
|
|
332
|
+
name: preset.providerName,
|
|
333
|
+
apiKey,
|
|
334
|
+
baseUrl: platform === "openclaw" ? preset.openclawBaseUrl : preset.commonBaseUrl,
|
|
335
|
+
model: preset.model || DEFAULT_PRIMARY_MODEL
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
saveProviderFromDesktop(platform, input, {
|
|
339
|
+
backup: options.backup !== false,
|
|
340
|
+
overwrite: options.overwrite === true,
|
|
341
|
+
activate: true
|
|
342
|
+
});
|
|
343
|
+
applied.push({
|
|
344
|
+
platform,
|
|
345
|
+
providerName: preset.providerName
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
preset,
|
|
351
|
+
applied,
|
|
352
|
+
skipped,
|
|
353
|
+
snapshot: getDesktopSnapshot()
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function exportPresetsToDesktop(filePath) {
|
|
358
|
+
const targetPath = `${filePath || ""}`.trim();
|
|
359
|
+
if (!targetPath) {
|
|
360
|
+
throw new Error("导出路径不能为空");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
writeJson(targetPath, {
|
|
364
|
+
appName: "ccon",
|
|
365
|
+
version: "0.1.10",
|
|
366
|
+
exportedAt: new Date().toISOString(),
|
|
367
|
+
presets: listPresets()
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
targetPath,
|
|
372
|
+
count: listPresets().length
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function importPresetsFromDesktop(filePath) {
|
|
377
|
+
const targetPath = `${filePath || ""}`.trim();
|
|
378
|
+
if (!targetPath) {
|
|
379
|
+
throw new Error("导入路径不能为空");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const payload = readJson(targetPath, null);
|
|
383
|
+
const presets = Array.isArray(payload?.presets) ? payload.presets : Array.isArray(payload) ? payload : [];
|
|
384
|
+
if (presets.length === 0) {
|
|
385
|
+
throw new Error("未找到可导入的预设");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const imported = [];
|
|
389
|
+
for (const preset of presets) {
|
|
390
|
+
imported.push(upsertPreset(preset));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
imported,
|
|
395
|
+
snapshot: getDesktopSnapshot()
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function updateRoutingFromDesktop(platform, input = {}) {
|
|
400
|
+
validatePlatform(platform);
|
|
401
|
+
upsertPlatformRouting(platform, input);
|
|
402
|
+
return getDesktopSnapshot();
|
|
403
|
+
}
|