@chatcode/cco-market 1.45.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 (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/README.zh.md +143 -0
  4. package/UPDATE-API-V1.md +132 -0
  5. package/client/client.js +11534 -0
  6. package/cordis.patch.yml +5 -0
  7. package/lib/accelerate.js +184 -0
  8. package/lib/agents.js +36 -0
  9. package/lib/backup.js +572 -0
  10. package/lib/catalog-local-match.js +126 -0
  11. package/lib/catalog-npm.js +111 -0
  12. package/lib/changelog.js +242 -0
  13. package/lib/channels.js +64 -0
  14. package/lib/check.js +1067 -0
  15. package/lib/compatibility.js +198 -0
  16. package/lib/diagnostics.js +49 -0
  17. package/lib/discovery-compatibility.js +260 -0
  18. package/lib/dsh-cli.js +949 -0
  19. package/lib/dsh-install.js +108 -0
  20. package/lib/gist.js +352 -0
  21. package/lib/groups.js +97 -0
  22. package/lib/home-paths.js +42 -0
  23. package/lib/hot.js +519 -0
  24. package/lib/http.js +39 -0
  25. package/lib/index.js +97 -0
  26. package/lib/install.js +387 -0
  27. package/lib/log.js +197 -0
  28. package/lib/ndjson.js +154 -0
  29. package/lib/net.js +98 -0
  30. package/lib/order.js +284 -0
  31. package/lib/patch.js +521 -0
  32. package/lib/pnpm-compat.js +490 -0
  33. package/lib/presets.js +278 -0
  34. package/lib/profile.js +911 -0
  35. package/lib/region-probe.js +93 -0
  36. package/lib/regions.js +253 -0
  37. package/lib/registry.js +204 -0
  38. package/lib/restart.js +373 -0
  39. package/lib/routes.js +4372 -0
  40. package/lib/settings.js +109 -0
  41. package/lib/skill-market/api.js +119 -0
  42. package/lib/skill-market/config.js +29 -0
  43. package/lib/skill-market/installer.js +352 -0
  44. package/lib/skill-market/runtime.js +13 -0
  45. package/lib/skill-market/tui.js +119 -0
  46. package/lib/skill-market/types.js +1 -0
  47. package/lib/skill-market/web-routes.js +123 -0
  48. package/lib/snapshot.js +500 -0
  49. package/lib/source-migration.js +54 -0
  50. package/lib/sources.js +596 -0
  51. package/lib/store.js +91 -0
  52. package/lib/themes.js +102 -0
  53. package/lib/trial.js +116 -0
  54. package/lib/types/accelerate.d.ts +85 -0
  55. package/lib/types/agents.d.ts +22 -0
  56. package/lib/types/backup.d.ts +131 -0
  57. package/lib/types/catalog-local-match.d.ts +50 -0
  58. package/lib/types/catalog-npm.d.ts +47 -0
  59. package/lib/types/changelog.d.ts +102 -0
  60. package/lib/types/channels.d.ts +56 -0
  61. package/lib/types/check.d.ts +266 -0
  62. package/lib/types/compatibility.d.ts +64 -0
  63. package/lib/types/diagnostics.d.ts +29 -0
  64. package/lib/types/discovery-compatibility.d.ts +80 -0
  65. package/lib/types/dsh-cli.d.ts +324 -0
  66. package/lib/types/dsh-install.d.ts +46 -0
  67. package/lib/types/gist.d.ts +53 -0
  68. package/lib/types/groups.d.ts +34 -0
  69. package/lib/types/home-paths.d.ts +16 -0
  70. package/lib/types/hot.d.ts +221 -0
  71. package/lib/types/http.d.ts +12 -0
  72. package/lib/types/index.d.ts +14 -0
  73. package/lib/types/install.d.ts +172 -0
  74. package/lib/types/log.d.ts +43 -0
  75. package/lib/types/ndjson.d.ts +52 -0
  76. package/lib/types/net.d.ts +58 -0
  77. package/lib/types/order.d.ts +100 -0
  78. package/lib/types/patch.d.ts +129 -0
  79. package/lib/types/pnpm-compat.d.ts +93 -0
  80. package/lib/types/presets.d.ts +90 -0
  81. package/lib/types/profile.d.ts +253 -0
  82. package/lib/types/region-probe.d.ts +50 -0
  83. package/lib/types/regions.d.ts +122 -0
  84. package/lib/types/registry.d.ts +86 -0
  85. package/lib/types/restart.d.ts +194 -0
  86. package/lib/types/routes.d.ts +67 -0
  87. package/lib/types/settings.d.ts +78 -0
  88. package/lib/types/skill-market/api.d.ts +10 -0
  89. package/lib/types/skill-market/config.d.ts +3 -0
  90. package/lib/types/skill-market/installer.d.ts +23 -0
  91. package/lib/types/skill-market/runtime.d.ts +10 -0
  92. package/lib/types/skill-market/tui.d.ts +4 -0
  93. package/lib/types/skill-market/types.d.ts +71 -0
  94. package/lib/types/skill-market/web-routes.d.ts +19 -0
  95. package/lib/types/snapshot.d.ts +93 -0
  96. package/lib/types/source-migration.d.ts +11 -0
  97. package/lib/types/sources.d.ts +216 -0
  98. package/lib/types/store.d.ts +30 -0
  99. package/lib/types/themes.d.ts +40 -0
  100. package/lib/types/trial.d.ts +61 -0
  101. package/lib/types/update-api-v1.d.ts +66 -0
  102. package/lib/types/updates.d.ts +123 -0
  103. package/lib/types/verify.d.ts +139 -0
  104. package/lib/update-api-v1.js +215 -0
  105. package/lib/updates.js +361 -0
  106. package/lib/verify.js +453 -0
  107. package/package.json +121 -0
  108. package/src/accelerate.ts +213 -0
  109. package/src/agents.ts +43 -0
  110. package/src/backup.ts +583 -0
  111. package/src/catalog-local-match.ts +144 -0
  112. package/src/catalog-npm.ts +120 -0
  113. package/src/changelog.ts +282 -0
  114. package/src/channels.ts +70 -0
  115. package/src/check.ts +1239 -0
  116. package/src/client/CommentsModal.tsx +119 -0
  117. package/src/client/Diagnostics.tsx +907 -0
  118. package/src/client/ErrorBoundary.tsx +111 -0
  119. package/src/client/InstallToast.tsx +31 -0
  120. package/src/client/Market.module.css +830 -0
  121. package/src/client/MarketSection.tsx +5301 -0
  122. package/src/client/OperationsPanel.tsx +365 -0
  123. package/src/client/SettingsCard.tsx +621 -0
  124. package/src/client/SkillMarket.module.css +21 -0
  125. package/src/client/SkillMarketSection.tsx +162 -0
  126. package/src/client/comments.ts +54 -0
  127. package/src/client/globals.d.ts +13 -0
  128. package/src/client/index.ts +180 -0
  129. package/src/client/locales.ts +1112 -0
  130. package/src/client/market-data.ts +1321 -0
  131. package/src/client/operations.ts +201 -0
  132. package/src/client/preset-panel.tsx +263 -0
  133. package/src/client/primitives.d.ts +140 -0
  134. package/src/client/self-check.ts +147 -0
  135. package/src/client/snapshot-panel.tsx +244 -0
  136. package/src/compatibility.ts +237 -0
  137. package/src/diagnostics.ts +84 -0
  138. package/src/discovery-compatibility.ts +315 -0
  139. package/src/dsh-cli.ts +1126 -0
  140. package/src/dsh-install.ts +118 -0
  141. package/src/gist.ts +362 -0
  142. package/src/groups.ts +111 -0
  143. package/src/home-paths.ts +53 -0
  144. package/src/hot.ts +628 -0
  145. package/src/http.ts +41 -0
  146. package/src/index.ts +128 -0
  147. package/src/install.ts +420 -0
  148. package/src/log.ts +206 -0
  149. package/src/ndjson.ts +185 -0
  150. package/src/net.ts +106 -0
  151. package/src/order.ts +303 -0
  152. package/src/patch.ts +522 -0
  153. package/src/pnpm-compat.ts +527 -0
  154. package/src/presets.ts +344 -0
  155. package/src/profile.ts +940 -0
  156. package/src/region-probe.ts +97 -0
  157. package/src/regions.ts +310 -0
  158. package/src/registry.ts +250 -0
  159. package/src/restart.ts +396 -0
  160. package/src/routes.ts +4506 -0
  161. package/src/settings.ts +141 -0
  162. package/src/skill-market/api.ts +130 -0
  163. package/src/skill-market/config.ts +32 -0
  164. package/src/skill-market/installer.ts +337 -0
  165. package/src/skill-market/runtime.ts +14 -0
  166. package/src/skill-market/tui.ts +157 -0
  167. package/src/skill-market/types.ts +80 -0
  168. package/src/skill-market/web-routes.ts +132 -0
  169. package/src/snapshot.ts +532 -0
  170. package/src/source-migration.ts +61 -0
  171. package/src/sources.ts +565 -0
  172. package/src/store.ts +89 -0
  173. package/src/themes.ts +125 -0
  174. package/src/trial.ts +156 -0
  175. package/src/update-api-v1.ts +277 -0
  176. package/src/updates.ts +400 -0
  177. package/src/verify.ts +492 -0
package/src/order.ts ADDED
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Community bundle ordering — issue #98 (phase 2): let the user reorder the
3
+ * community bundles of the profile's layer stack, with author-declared
4
+ * before/after rules enforced before anything is written.
5
+ *
6
+ * Official in-box bundles (@deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app,
7
+ * @deepseek-ai/dsh-headless) are fixed: they keep their exact positions in
8
+ * the stack, are never part of a user-supplied order, and are never added,
9
+ * removed or duplicated by a reorder (#98 boundary). The profile's own
10
+ * cordis.patch.yml and --patch overlays are not part of the bundle stack and
11
+ * are never touched here.
12
+ *
13
+ * Pure functions plus one manifest write-back; no processes, no network.
14
+ */
15
+
16
+ import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
17
+ import { createRequire } from 'node:module'
18
+ import { join } from 'node:path'
19
+ import { findDshInstallDir } from './dsh-install.ts'
20
+
21
+ /** Profile bundles that ship with the dsh host and must stay put (#98). */
22
+ export const INBOX_BUNDLES = new Set([
23
+ '@deepseek-ai/dsh-base',
24
+ '@deepseek-ai/dsh-web-app',
25
+ '@deepseek-ai/dsh-headless',
26
+ ])
27
+
28
+ /**
29
+ * Atomic same-directory replace (write temp + rename): a crash mid-write can
30
+ * never leave the profile manifest truncated, which would break every later
31
+ * pnpm run. Used for every package.json write this module makes.
32
+ */
33
+ function writeFileAtomic(file: string, content: string): void {
34
+ const temp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
35
+ writeFileSync(temp, content)
36
+ renameSync(temp, file)
37
+ }
38
+
39
+ /** The bundle stack as it appears in the profile manifest. */
40
+ export interface BundleStack {
41
+ /** Full ordered list from dsh.profile.bundles. */
42
+ bundles: string[]
43
+ /** The subset that may be reordered (community bundles). */
44
+ community: string[]
45
+ }
46
+
47
+ /** Author-declared ordering constraints of one bundle. */
48
+ export interface BundleRule {
49
+ name: string
50
+ /** This bundle must load after every name in this list. */
51
+ after: string[]
52
+ /** This bundle must load before every name in this list. */
53
+ before: string[]
54
+ }
55
+
56
+ /** A violated before/after rule in the current or proposed order. */
57
+ export interface OrderConflict {
58
+ name: string
59
+ reason: string
60
+ }
61
+
62
+ /** Read the profile's bundle stack (empty when the manifest is unreadable). */
63
+ export function readBundleStack(profileDir: string): BundleStack {
64
+ try {
65
+ const manifest = JSON.parse(readFileSync(join(profileDir, 'package.json'), 'utf8')) as {
66
+ dsh?: { profile?: { bundles?: unknown } }
67
+ }
68
+ const bundles = Array.isArray(manifest.dsh?.profile?.bundles)
69
+ ? manifest.dsh.profile.bundles.filter((name): name is string => typeof name === 'string')
70
+ : []
71
+ return {
72
+ bundles,
73
+ community: bundles.filter(name => !INBOX_BUNDLES.has(name)),
74
+ }
75
+ } catch {
76
+ return { bundles: [], community: [] }
77
+ }
78
+ }
79
+
80
+ /**
81
+ * The bundle package.json, resolved the way the dsh boot resolves bundles
82
+ * (dsh-app-boot's resolveBundleDir, mirrored by check.ts): the dsh
83
+ * installation anchor first — official in-box bundles live in the install's
84
+ * node_modules, never the profile's — then the profile directory, whose
85
+ * createRequire search paths also cover pnpm workspace-root hoisting
86
+ * (`<profiles>/node_modules` when the profile lives under `<profiles>/<name>`).
87
+ */
88
+ function resolveBundlePackageJson(profileDir: string, name: string): string | null {
89
+ const dshInstall = findDshInstallDir()
90
+ const anchors = [
91
+ dshInstall !== null ? join(dshInstall, 'package.json') : null,
92
+ join(profileDir, 'package.json'),
93
+ ]
94
+ for (const anchor of anchors) {
95
+ if (anchor === null) continue
96
+ let paths: string[] = []
97
+ try {
98
+ paths = createRequire(anchor).resolve.paths(name) ?? []
99
+ } catch {
100
+ continue
101
+ }
102
+ for (const searchPath of paths) {
103
+ const candidate = join(searchPath, name)
104
+ if (existsSync(join(candidate, 'package.json'))) return join(candidate, 'package.json')
105
+ }
106
+ }
107
+ return null
108
+ }
109
+
110
+ /**
111
+ * Read each bundle's declared ordering rules from its package manifest
112
+ * (`dsh.bundle.order.{before,after}` — a list of bundle package names).
113
+ * Unresolvable packages and missing declarations contribute nothing.
114
+ */
115
+ export function readBundleRules(profileDir: string): BundleRule[] {
116
+ const { bundles } = readBundleStack(profileDir)
117
+ const rules: BundleRule[] = []
118
+ for (const name of bundles) {
119
+ const packageJson = resolveBundlePackageJson(profileDir, name)
120
+ if (packageJson === null) continue
121
+ try {
122
+ const manifest = JSON.parse(
123
+ readFileSync(packageJson, 'utf8'),
124
+ ) as { dsh?: { bundle?: { order?: unknown } } }
125
+ const order = manifest.dsh?.bundle?.order
126
+ if (order === null || typeof order !== 'object' || Array.isArray(order)) continue
127
+ const listOf = (value: unknown): string[] => Array.isArray(value)
128
+ ? value.filter((item): item is string => typeof item === 'string')
129
+ : []
130
+ const rule: BundleRule = {
131
+ name,
132
+ after: listOf((order as Record<string, unknown>).after),
133
+ before: listOf((order as Record<string, unknown>).before),
134
+ }
135
+ if (rule.after.length > 0 || rule.before.length > 0) rules.push(rule)
136
+ } catch { /* package unreadable — no rule */ }
137
+ }
138
+ return rules
139
+ }
140
+
141
+ /**
142
+ * Check a bundle order against the declared before/after rules. Rules naming
143
+ * bundles outside `order` are ignored (a rule for a not-yet-installed bundle
144
+ * must not block the current stack).
145
+ * @returns every violated rule with a readable reason; [] when all hold.
146
+ */
147
+ export function validateOrder(bundleNames: string[], rules: BundleRule[]): OrderConflict[] {
148
+ const position = new Map(bundleNames.map((name, index) => [name, index]))
149
+ const conflicts: OrderConflict[] = []
150
+ for (const rule of rules) {
151
+ const pos = position.get(rule.name)
152
+ if (pos === undefined) continue
153
+ for (const other of rule.after) {
154
+ const otherPos = position.get(other)
155
+ if (otherPos === undefined) continue
156
+ if (otherPos >= pos) {
157
+ conflicts.push({
158
+ name: rule.name,
159
+ reason: `must load after ${other}, but ${other} is currently before/equal (position ${otherPos} vs ${pos})`,
160
+ })
161
+ }
162
+ }
163
+ for (const other of rule.before) {
164
+ const otherPos = position.get(other)
165
+ if (otherPos === undefined) continue
166
+ if (otherPos <= pos) {
167
+ conflicts.push({
168
+ name: rule.name,
169
+ reason: `must load before ${other}, but ${other} is currently after/equal (position ${otherPos} vs ${pos})`,
170
+ })
171
+ }
172
+ }
173
+ }
174
+ return conflicts
175
+ }
176
+
177
+ /**
178
+ * Merge a community-bundle permutation into the full stack. Official in-box
179
+ * bundles keep their EXACT positions (never moved); community bundles are
180
+ * replaced by `newOrder` in order of appearance. Pure — nothing is written.
181
+ * @returns the merged full stack, or the rejection reason when `newOrder` is
182
+ * not a permutation of the community bundles (duplicates, additions,
183
+ * omissions, official names).
184
+ */
185
+ export function mergeOrder(bundles: string[], newOrder: string[]): { ok: true; bundles: string[] } | { ok: false; error: string } {
186
+ const communitySet = new Set(bundles.filter(name => !INBOX_BUNDLES.has(name)))
187
+ if (new Set(newOrder).size !== newOrder.length) {
188
+ return { ok: false, error: 'duplicate bundle names in the new order / 新顺序包含重复的 bundle' }
189
+ }
190
+ if (newOrder.length !== communitySet.size) {
191
+ return { ok: false, error: 'the new order must contain exactly the current community bundles / 新顺序必须恰好包含全部社区 bundle' }
192
+ }
193
+ for (const name of newOrder) {
194
+ if (!communitySet.has(name)) {
195
+ return { ok: false, error: `${name} is not a reorderable community bundle / ${name} 不是可排序的社区 bundle` }
196
+ }
197
+ }
198
+ const merged = [...bundles]
199
+ let cursor = 0
200
+ for (let index = 0; index < merged.length; index += 1) {
201
+ const name = merged[index]
202
+ if (name === undefined || INBOX_BUNDLES.has(name)) continue
203
+ merged[index] = newOrder[cursor]
204
+ cursor += 1
205
+ }
206
+ return { ok: true, bundles: merged }
207
+ }
208
+
209
+ /**
210
+ * Topologically sort the community bundles by their before/after rules — the
211
+ * "auto-fix" counterpart to validateOrder. Returns null when no declared rule
212
+ * applies to the current stack (nothing to suggest). With rules, Kahn's
213
+ * algorithm breaks ties by the CURRENT order: unconstrained bundles keep
214
+ * their current relative order and constrained bundles move only as far as
215
+ * the rules require — the suggestion is the minimal change that satisfies
216
+ * every rule, never an arbitrary canonical rewrite of a hand-picked order
217
+ * (issue #125 review).
218
+ * @returns the suggested community order, null when there are no rules, or a
219
+ * cycle report when the constraints cannot be satisfied (references to
220
+ * unlisted bundles ignored).
221
+ */
222
+ export function suggestOrder(
223
+ bundleNames: string[],
224
+ rules: BundleRule[],
225
+ ): { ok: true; order: string[] } | { ok: false; cycle: string[] } | null {
226
+ const names = bundleNames.filter(name => !INBOX_BUNDLES.has(name))
227
+ const inOrder = new Set(names)
228
+ const active = rules.filter(rule => inOrder.has(rule.name))
229
+ // No rule applies to the current stack — nothing to suggest.
230
+ if (active.length === 0) return null
231
+ const position = new Map(names.map((name, index) => [name, index]))
232
+ // Constraint: "a must load before b" (from a.before or b.after) → edge a → b.
233
+ const beforeOf = new Map<string, Set<string>>() // name → bundles that must come after it
234
+ const deps = new Map<string, Set<string>>() // name → bundles that must come before it
235
+ for (const name of names) {
236
+ beforeOf.set(name, new Set())
237
+ deps.set(name, new Set())
238
+ }
239
+ const addEdge = (a: string, b: string): void => {
240
+ if (!inOrder.has(a) || !inOrder.has(b) || a === b) return
241
+ beforeOf.get(a)?.add(b)
242
+ deps.get(b)?.add(a)
243
+ }
244
+ for (const rule of active) {
245
+ for (const other of rule.before) addEdge(rule.name, other)
246
+ for (const other of rule.after) addEdge(other, rule.name)
247
+ }
248
+ const remaining = new Map<string, Set<string>>()
249
+ for (const [name, depsOf] of deps) remaining.set(name, new Set(depsOf))
250
+ const ready: string[] = names.filter(name => (remaining.get(name)?.size ?? 0) === 0)
251
+ const ordered: string[] = []
252
+ while (ready.length > 0) {
253
+ // Minimal-change tie-break: among the ready bundles, prefer the one that
254
+ // comes FIRST in the current order. Bundles the rules do not constrain
255
+ // therefore keep their current relative order; constrained bundles move
256
+ // only as far as the rules require (issue #125 review).
257
+ let best = 0
258
+ for (let i = 1; i < ready.length; i += 1) {
259
+ const a = ready[i]
260
+ const b = ready[best]
261
+ if (a !== undefined && b !== undefined && (position.get(a) ?? 0) < (position.get(b) ?? 0)) best = i
262
+ }
263
+ const name = ready.splice(best, 1)[0]
264
+ if (name === undefined) break
265
+ ordered.push(name)
266
+ for (const dependent of beforeOf.get(name) ?? []) {
267
+ const depsOf = remaining.get(dependent)
268
+ if (depsOf === undefined) continue
269
+ depsOf.delete(name)
270
+ if (depsOf.size === 0 && !ordered.includes(dependent) && !ready.includes(dependent)) ready.push(dependent)
271
+ }
272
+ }
273
+ if (ordered.length < names.length) {
274
+ return { ok: false, cycle: names.filter(name => !ordered.includes(name)) }
275
+ }
276
+ return { ok: true, order: ordered }
277
+ }
278
+
279
+ /**
280
+ * Apply a new community-bundle order to the profile manifest. The official
281
+ * in-box bundles keep their exact positions; `newOrder` must be a permutation
282
+ * of the current community bundles (no duplicates, no additions, no
283
+ * omissions). On any failure the manifest is left untouched.
284
+ * @returns the new full stack on success, or an error description.
285
+ */
286
+ export function applyBundleOrder(profileDir: string, newOrder: string[]): { ok: true; bundles: string[] } | { ok: false; error: string } {
287
+ const { bundles } = readBundleStack(profileDir)
288
+ const merged = mergeOrder(bundles, newOrder)
289
+ if (!merged.ok) return merged
290
+ try {
291
+ const manifestPath = join(profileDir, 'package.json')
292
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as {
293
+ dsh?: { profile?: { bundles?: unknown } }
294
+ }
295
+ manifest.dsh ??= {}
296
+ manifest.dsh.profile ??= {}
297
+ manifest.dsh.profile.bundles = merged.bundles
298
+ writeFileAtomic(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
299
+ return merged
300
+ } catch (error) {
301
+ return { ok: false, error: error instanceof Error ? error.message : String(error) }
302
+ }
303
+ }