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.
Files changed (62) hide show
  1. package/README.md +107 -0
  2. package/build/icon.ico +0 -0
  3. package/build/icon.png +0 -0
  4. package/core/apply.js +152 -0
  5. package/core/backup.js +53 -0
  6. package/core/constants.js +78 -0
  7. package/core/desktop-service.js +403 -0
  8. package/core/desktop-state.js +1021 -0
  9. package/core/index.js +1468 -0
  10. package/core/paths.js +99 -0
  11. package/core/presets.js +171 -0
  12. package/core/probe.js +70 -0
  13. package/core/routing.js +334 -0
  14. package/core/store.js +218 -0
  15. package/core/utils.js +225 -0
  16. package/core/writers/codex.js +102 -0
  17. package/core/writers/index.js +16 -0
  18. package/core/writers/openclaw.js +93 -0
  19. package/core/writers/opencode.js +91 -0
  20. package/desktop/assets/fml-icon.png +0 -0
  21. package/desktop/assets/march-mark.svg +26 -0
  22. package/desktop/main.js +275 -0
  23. package/desktop/preload.cjs +67 -0
  24. package/desktop/preload.js +49 -0
  25. package/desktop/renderer/app.js +327 -0
  26. package/desktop/renderer/index.html +130 -0
  27. package/desktop/renderer/styles.css +490 -0
  28. package/package.json +111 -0
  29. package/scripts/build-web.mjs +95 -0
  30. package/scripts/desktop-dev.mjs +90 -0
  31. package/scripts/desktop-pack-win.mjs +81 -0
  32. package/scripts/postinstall.mjs +49 -0
  33. package/scripts/prepublish-check.mjs +57 -0
  34. package/scripts/serve-site.mjs +51 -0
  35. package/site/app.js +10 -0
  36. package/site/assets/fml-icon.png +0 -0
  37. package/site/assets/march-mark.svg +26 -0
  38. package/site/index.html +337 -0
  39. package/site/styles.css +840 -0
  40. package/src/App.tsx +1557 -0
  41. package/src/components/layout/app-sidebar.tsx +103 -0
  42. package/src/components/layout/top-toolbar.tsx +44 -0
  43. package/src/components/layout/workspace-tabs.tsx +32 -0
  44. package/src/components/providers/inspector-panel.tsx +84 -0
  45. package/src/components/providers/metric-strip.tsx +26 -0
  46. package/src/components/providers/provider-editor.tsx +87 -0
  47. package/src/components/providers/provider-table.tsx +85 -0
  48. package/src/components/ui/logo-mark.tsx +32 -0
  49. package/src/features/mcp/mcp-view.tsx +45 -0
  50. package/src/features/prompts/prompts-view.tsx +40 -0
  51. package/src/features/providers/providers-view.tsx +40 -0
  52. package/src/features/providers/types.ts +26 -0
  53. package/src/features/skills/skills-view.tsx +44 -0
  54. package/src/hooks/use-control-workspace.ts +235 -0
  55. package/src/index.css +22 -0
  56. package/src/lib/client.ts +726 -0
  57. package/src/lib/query-client.ts +3 -0
  58. package/src/lib/workspace-sections.ts +34 -0
  59. package/src/main.tsx +14 -0
  60. package/src/types.ts +137 -0
  61. package/src/vite-env.d.ts +64 -0
  62. 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
+ }