@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/lib/verify.js ADDED
@@ -0,0 +1,453 @@
1
+ /**
2
+ * Post-install activation verification (P0-2): what "installed" actually
3
+ * means for a package in a dsh profile.
4
+ *
5
+ * Two sources of truth, in strict order of authority:
6
+ *
7
+ * 1. The LOADER INVENTORY (observed): whatever the loader is running right
8
+ * now is live, full stop. A plain library with no `dsh` field can be
9
+ * loaded by name from someone else's bundle patch — the official
10
+ * dsh-base patch loads `@deepseek-ai/dsh-tools`, which has no `dsh`
11
+ * field at all — so no manifest check may overrule it (#135).
12
+ * 2. The profile manifest (inferred): `<profile>/package.json` →
13
+ * `dsh.profile.bundles`, what the dsh CLI reconciled. This predicts what
14
+ * the NEXT boot will load, and is the only evidence available for a
15
+ * package that is not currently running.
16
+ *
17
+ * State taxonomy (IMPROVEMENT-PLAN P0-2):
18
+ * live – running in the current composition (hot mount or loader entry)
19
+ * restart – installed and will activate on the next boot, but not live now
20
+ * inert – installed but not a profile-layer plugin (plain dependency, or
21
+ * client-only — the market shim-mounts those at boot)
22
+ * broken – would fail to load: listed as a bundle without a dsh surface,
23
+ * or a declared entry artifact that is missing
24
+ * missing – not present in node_modules
25
+ */
26
+ import { readFileSync } from 'node:fs';
27
+ import { Script } from 'node:vm';
28
+ import { join } from 'node:path';
29
+ import { listHotMounts, parseSimplePatch } from './hot.js';
30
+ import { userPatchPackageReferences } from './patch.js';
31
+ import { bundlePatchInsertedIds, hasDshManifest, hasLoadableEntry, profileDir, readInstalled } from './profile.js';
32
+ /** The profile manifest's `dsh.profile.bundles` — what the CLI reconciled. */
33
+ function readBundles(profile, explicitDir) {
34
+ try {
35
+ const manifest = JSON.parse(readFileSync(join(profileDir(profile, explicitDir), 'package.json'), 'utf8'));
36
+ const bundles = manifest.dsh?.profile?.bundles;
37
+ return new Set(Array.isArray(bundles) ? bundles.filter((n) => typeof n === 'string') : []);
38
+ }
39
+ catch {
40
+ return new Set();
41
+ }
42
+ }
43
+ /**
44
+ * True when `live` contains the package itself or a subpath entry of it.
45
+ *
46
+ * The live set (see `liveNames` in routes.ts) holds loader entry names — the
47
+ * `name:` field of each bundle patch row. Bundles usually name the bare
48
+ * package (`dshmarket`, `@scope/pkg`), but may point at a subpath entry
49
+ * (`@vectorize-io/hindsight-coding-agents/dsh`, `aegis/extensions/dsh/index.js`).
50
+ * Either form means the package's fiber is up and it must read as live;
51
+ * a different package sharing a name prefix (`@scope/pkg2` vs `@scope/pkg`)
52
+ * must not — the `/` bound keeps the match a real subpath.
53
+ */
54
+ function liveIncludes(live, packageName) {
55
+ if (live.has(packageName))
56
+ return true;
57
+ const prefix = `${packageName}/`;
58
+ for (const name of live)
59
+ if (name.startsWith(prefix))
60
+ return true;
61
+ return false;
62
+ }
63
+ /**
64
+ * True when a loader entry this package's OWN bundle patch inserts is up.
65
+ *
66
+ * A carrier bundle (#103) ships no plugin of its own: its patch mounts
67
+ * ANOTHER package with configuration, so the live entry carries that other
68
+ * package's name and `liveIncludes` can never match. The entry ID is the
69
+ * part that belongs to this package — its patch created it — which is why
70
+ * matching on it is both sufficient and precise: a neighbour that happens
71
+ * to mount the same package does so under a different id.
72
+ *
73
+ * Without this the market kept telling users to restart for a plugin that
74
+ * had been running since the restart (#156).
75
+ */
76
+ function carriedRowLive(live, profileDirectory, packageName) {
77
+ try {
78
+ return bundlePatchInsertedIds(join(profileDirectory, 'node_modules', packageName))
79
+ .some(id => live.has(`#${id}`));
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ function readPkgDsh(profile, name, explicitDir) {
86
+ try {
87
+ const manifest = JSON.parse(readFileSync(join(profileDir(profile, explicitDir), 'node_modules', name, 'package.json'), 'utf8'));
88
+ return manifest.dsh ?? {};
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ function patchTextOf(profile, name, explicitDir) {
95
+ try {
96
+ return readFileSync(join(profileDir(profile, explicitDir), 'node_modules', name, 'cordis.patch.yml'), 'utf8');
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ /**
103
+ * Verify the activation state of one installed package.
104
+ * @param live - names live in the current composition; defaults to the
105
+ * market's hot-mount table (injectable for tests).
106
+ */
107
+ /**
108
+ * Whether the profile's OWN `cordis.patch.yml` inserts this package by name.
109
+ *
110
+ * A third evidence source beside the loader inventory and the package's own
111
+ * manifest, and the one that was missing (#165): a plugin the user wired up
112
+ * themselves declares nothing, is not hot-mounted until the next boot, and so
113
+ * fell through to `broken` — the market told them the install had failed
114
+ * verification while the plugin was, in fact, working.
115
+ *
116
+ * Read with the same parser the uninstall guard uses. Unreadable returns
117
+ * null, which is treated here as NO evidence rather than as evidence: this
118
+ * only ever upgrades a verdict away from `broken`, so being unsure has to
119
+ * leave the stricter answer standing.
120
+ */
121
+ function patchLoads(activeProfileDir, name) {
122
+ const references = userPatchPackageReferences(join(activeProfileDir, 'cordis.patch.yml'), name);
123
+ return references !== null && references.length > 0;
124
+ }
125
+ export function verifyActivation(profile, name, live = new Set(listHotMounts()), explicitDir, isDisabled = false) {
126
+ const activeProfileDir = profileDir(profile, explicitDir);
127
+ const bundles = readBundles(profile, activeProfileDir);
128
+ const inBundles = bundles.has(name);
129
+ const dsh = readPkgDsh(profile, name, activeProfileDir);
130
+ if (dsh === null) {
131
+ return { state: 'missing', reasons: ['未安装 / not installed'], bundle: inBundles, hot: false };
132
+ }
133
+ // A user-disabled plugin reads as disabled, never as "restart to apply":
134
+ // the switch state (market disable list or the user patch layer) is the
135
+ // dominant fact, and the loader keeps it off on every boot.
136
+ if (isDisabled) {
137
+ return {
138
+ state: 'disabled',
139
+ reasons: ['已停用(市场开关或补丁层),重启后保持关闭 / disabled (market toggle or the patch layer) — stays off across restarts'],
140
+ bundle: inBundles,
141
+ hot: false,
142
+ };
143
+ }
144
+ const dir = join(activeProfileDir, 'node_modules', name);
145
+ // OBSERVED beats INFERRED (#135): the loader inventory is ground truth, so
146
+ // a package the loader is running is live no matter what its manifest says.
147
+ // Plain library packages legitimately carry no `dsh` field and are still
148
+ // loaded by name from a bundle patch — @deepseek-ai/dsh-tools is loaded by
149
+ // the official dsh-base patch and has no `dsh` field at all — so this check
150
+ // has to come before any manifest-based verdict.
151
+ const loaderLive = liveIncludes(live, name) || carriedRowLive(live, activeProfileDir, name);
152
+ if (!hasDshManifest(dir)) {
153
+ if (loaderLive) {
154
+ return {
155
+ state: 'live',
156
+ reasons: ['已由 Loader 加载(该包未声明 dsh 元数据,由某个 bundle patch 按名加载)/ loaded by the loader (no dsh metadata of its own — a bundle patch loads it by name)'],
157
+ bundle: inBundles,
158
+ hot: true,
159
+ };
160
+ }
161
+ // Not live and no dsh surface: for a package the profile lists as a
162
+ // BUNDLE this is a real defect; for a plain dependency it is normal —
163
+ // most dependencies are libraries, not plugins (#135).
164
+ if (inBundles && !patchLoads(activeProfileDir, name)) {
165
+ return {
166
+ state: 'broken',
167
+ reasons: ['已列入 profile bundle 层但未声明 dsh 元数据,加载会失败 / listed in the profile bundle layer but declares no dsh metadata — loading it fails'],
168
+ bundle: true,
169
+ hot: false,
170
+ };
171
+ }
172
+ return inBundles
173
+ ? {
174
+ state: 'restart',
175
+ reasons: ['由你自己的 cordis.patch.yml 按名加载,重启后生效 / loaded by name from your own cordis.patch.yml — live after a restart'],
176
+ bundle: true,
177
+ hot: false,
178
+ }
179
+ : {
180
+ state: 'inert',
181
+ reasons: ['普通依赖(未声明 dsh 元数据),不是 profile 层插件;若它由某个 bundle patch 按名加载,启动后会显示为已加载 / a plain dependency with no dsh metadata — not a profile-layer plugin; if some bundle patch loads it by name it will read as live once running'],
182
+ bundle: false,
183
+ hot: false,
184
+ };
185
+ }
186
+ // Carrier bundles (#103) ship no entry of their own — what they mount is
187
+ // the point — so judge by "is anything loadable", not by this package's
188
+ // own artifact.
189
+ if (!loaderLive && !hasLoadableEntry(activeProfileDir, name) && !patchLoads(activeProfileDir, name)) {
190
+ return {
191
+ state: 'broken',
192
+ reasons: [
193
+ '声明的入口产物缺失(源码检出或构建被拦),下次启动会失败 / the declared entry artifact is missing (source-only checkout or blocked build) — the next boot would fail',
194
+ ],
195
+ bundle: inBundles,
196
+ hot: false,
197
+ };
198
+ }
199
+ if (loaderLive) {
200
+ const clientOnly = dsh.bundle === undefined && dsh.client !== undefined;
201
+ return {
202
+ state: 'live',
203
+ reasons: [
204
+ clientOnly
205
+ ? '已热加载(纯客户端插件 shim)/ live via the client-only shim'
206
+ : '已热加载(bundle patch)/ live via its bundle patch',
207
+ ],
208
+ bundle: inBundles,
209
+ hot: true,
210
+ };
211
+ }
212
+ if (inBundles) {
213
+ const patch = patchTextOf(profile, name, activeProfileDir);
214
+ const complex = patch !== null && parseSimplePatch(patch) === null;
215
+ return {
216
+ state: 'restart',
217
+ reasons: [
218
+ complex
219
+ ? 'bundle patch 含配置/表达式,热挂载仅支持纯 insert;重启后由 bundle 层生效 / the bundle patch contains config/expression rows; hot-mount only supports plain inserts — it activates on restart'
220
+ : '已进入 profile bundle 层但本次未能热挂载;重启后生效 / in the bundle layer but not hot-mounted this session — it activates on restart',
221
+ ],
222
+ bundle: true,
223
+ hot: false,
224
+ };
225
+ }
226
+ // Not a profile-layer plugin. Client-only packages never enter bundles
227
+ // (the dsh CLI skips them), so the market shim-mounts them at boot —
228
+ // they still work, but "installed" never means "bundle layer".
229
+ if (dsh.client !== undefined) {
230
+ return {
231
+ state: 'inert',
232
+ reasons: [
233
+ '未声明 dsh.bundle,不会进入 profile bundle 层(纯客户端插件);重启后由市场自动挂载生效 / no dsh.bundle — client-only plugins never enter the bundle layer; the market shim-mounts them at the next boot',
234
+ ],
235
+ bundle: false,
236
+ hot: false,
237
+ };
238
+ }
239
+ return {
240
+ state: 'inert',
241
+ reasons: [
242
+ '未声明 dsh.bundle,已作为普通依赖安装,不会成为 profile 层 / no dsh.bundle — installed as a plain dependency, never a profile-layer plugin',
243
+ ],
244
+ bundle: false,
245
+ hot: false,
246
+ };
247
+ }
248
+ /**
249
+ * Correct a post-UPDATE verdict for a plugin that was already running.
250
+ *
251
+ * `verifyActivation` answers "is this name in the live loader inventory".
252
+ * That is the right question after an install and the wrong one after an
253
+ * update: the plugin was already live, so the answer stays "live" while the
254
+ * process keeps serving the module it imported at boot. Replacing files under
255
+ * a running composition does not re-import anything.
256
+ *
257
+ * Measured on a real host rather than reasoned about — updating the market
258
+ * from 1.11.3 to 1.12.2 left `/dsh-market/status` reporting 1.11.3 with an
259
+ * unchanged boot id, while the update route called it hot-loaded in the same
260
+ * response. The browser half genuinely does refresh (the host re-serves the
261
+ * client bundle from disk), which is what makes the wrong verdict credible:
262
+ * the UI visibly becomes the new version while the server half does not.
263
+ *
264
+ * Only a plugin that was ALREADY live is affected. One that was missing,
265
+ * broken or disabled beforehand has nothing loaded to shadow the new build,
266
+ * so its fresh mount really does run the new code.
267
+ *
268
+ * Client-only packages are excluded for the same reason from the other end:
269
+ * they have no host half to go stale, and the browser fetches their bundle
270
+ * from disk on the next page load. Telling their users to restart would be
271
+ * #156 again, in a narrower place — see `hasHostHalf`.
272
+ * @param result the verdict computed from the loader inventory
273
+ * @param hostHalfWasLive whether a HOST half was live BEFORE the replacement
274
+ */
275
+ export function activationAfterReplace(result, hostHalfWasLive) {
276
+ if (!hostHalfWasLive || result.state !== 'live')
277
+ return result;
278
+ return {
279
+ ...result,
280
+ state: 'restart',
281
+ hot: false,
282
+ reasons: ['新版本已就位,但运行中的进程仍在使用启动时加载的旧模块——重启后生效(页面本身会立即变成新版,服务端不会) / the new build is in place, but the running process still serves the module it imported at boot — restart to apply (the page itself updates immediately; the server half does not)'],
283
+ };
284
+ }
285
+ /**
286
+ * Whether a package has a host (Node) half at all.
287
+ *
288
+ * A `dsh.client`-only package — themes, skins, most pure-UI plugins — runs
289
+ * no server code: the market shim-mounts it so the loader has a live row,
290
+ * and the browser re-fetches its bundle from disk on the next page load. An
291
+ * update to one takes effect on refresh, with no restart to ask for.
292
+ */
293
+ export function hasHostHalf(profile, name, explicitDir) {
294
+ const dsh = readPkgDsh(profile, name, explicitDir);
295
+ if (dsh === null)
296
+ return false;
297
+ // Only a DEFINITE client-only package is excluded — the same test
298
+ // verifyActivation uses for its own verdict. Testing `dsh.bundle` on its
299
+ // own would read a package that declares neither key (`"dsh": {}`, which
300
+ // the bundle layer still loads) as client-only, and quietly disable the
301
+ // correction for it.
302
+ return !(dsh.bundle === undefined && dsh.client !== undefined);
303
+ }
304
+ /**
305
+ * The client bundle path a package's `exports["./client"]` names, relative
306
+ * to the package root — or `null` when it cannot be resolved CONFIDENTLY.
307
+ *
308
+ * Returning null is the important half. This feeds a post-install check
309
+ * whose only job is to catch a corrupt bundle, and a resolver that guessed
310
+ * wrong would report a healthy plugin as broken — worse than the silence it
311
+ * replaces. So every shape this does not fully understand resolves to null
312
+ * and the check simply does not run: unresolvable is not evidence of damage.
313
+ *
314
+ * Handles the two shapes real plugins ship: a plain string, and a
315
+ * conditional object. For the object, only `browser` and `default` are
316
+ * consulted — those are the conditions the host's client loader actually
317
+ * activates; `import`/`require` describe a Node resolution this file is not
318
+ * modelling, and picking one of those could name a different artifact.
319
+ * Nested conditions recurse; anything else (arrays, non-relative targets)
320
+ * gives up.
321
+ */
322
+ export function clientBundlePath(exportsField, depth = 0) {
323
+ if (depth > 4)
324
+ return null;
325
+ if (typeof exportsField === 'string') {
326
+ // Only a relative in-package path. A bare specifier or URL is a shape
327
+ // this resolver does not model.
328
+ return exportsField.startsWith('./') ? exportsField : null;
329
+ }
330
+ if (exportsField === null || typeof exportsField !== 'object' || Array.isArray(exportsField))
331
+ return null;
332
+ const conditions = exportsField;
333
+ for (const key of ['browser', 'default']) {
334
+ if (conditions[key] === undefined)
335
+ continue;
336
+ const resolved = clientBundlePath(conditions[key], depth + 1);
337
+ if (resolved !== null)
338
+ return resolved;
339
+ }
340
+ return null;
341
+ }
342
+ /**
343
+ * Whether a package's client bundle still parses as JavaScript (#222).
344
+ *
345
+ * pnpm can leave a half-written or patch-mangled bundle behind — the report
346
+ * describes a profile whose client bundle was broken after an update. The
347
+ * browser is where that surfaces today, as a blank settings page long after
348
+ * the operation reported success, with nothing connecting the two.
349
+ *
350
+ * `vm.Script` COMPILES without executing: it catches the syntax damage this
351
+ * is looking for and never runs plugin code, so a hostile bundle gains
352
+ * nothing. A missing `dsh.client`, an unresolvable exports field, or a file
353
+ * that is simply absent all return ok — this check only ever fires on a file
354
+ * it actually read and actually failed to parse. Everything ambiguous stays
355
+ * silent, because a false "your plugin is corrupt" is the one outcome worse
356
+ * than not checking.
357
+ */
358
+ /**
359
+ * V8's wording when a classic-script parse trips over module syntax.
360
+ *
361
+ * Matched on the message because there is no flag-free way to compile a
362
+ * module here (`SourceTextModule` needs --experimental-vm-modules, which the
363
+ * host process does not set). If V8 ever rewords these, the failure mode is
364
+ * the false positive we had before rather than a missed real break — the
365
+ * safer direction of the two.
366
+ */
367
+ const MODULE_SYNTAX_ERROR = /Unexpected token 'export'|Cannot use import statement outside a module|await is only valid in async functions and the top level bodies of modules/;
368
+ /**
369
+ * Every installed plugin whose client bundle will not parse (#222 by
370
+ * @MicroMilo).
371
+ *
372
+ * The per-package check above only ever looked at what an operation added,
373
+ * which misses the failure that was actually reported: pnpm re-extracts the
374
+ * WHOLE tree on any install, so updating one plugin can restore another
375
+ * plugin's pristine — and broken — bundle, or fail to re-apply a patch that
376
+ * was holding it together. The damage then surfaces at the next boot as
377
+ * "failed to load plugins", with nothing connecting it to the install that
378
+ * caused it.
379
+ *
380
+ * Cheap enough to run on every operation: 0.40ms per plugin including the
381
+ * read (measured on a 385KB bundle), so a 30-plugin profile costs ~12ms
382
+ * against an install that takes seconds.
383
+ *
384
+ * Silent on ESM bundles, like the per-package check it calls — see there for
385
+ * why. Widening the sweep is exactly what would have turned that one false
386
+ * "corrupt" into one per ESM plugin in the profile.
387
+ *
388
+ * Callers compare a before-list with an after-list rather than reporting
389
+ * this one directly — a profile can carry a broken bundle indefinitely, and
390
+ * re-reporting a problem the user already had would put them in front of
391
+ * something this operation did not cause and cannot undo.
392
+ */
393
+ export function brokenClientBundles(profile, explicitDir) {
394
+ const broken = [];
395
+ for (const name of Object.keys(readInstalled(profile, explicitDir))) {
396
+ const check = checkClientBundle(profile, name, explicitDir);
397
+ if (!check.ok)
398
+ broken.push({ name, reason: check.reason ?? 'parse failed' });
399
+ }
400
+ return broken;
401
+ }
402
+ /** Bundles broken after an operation that were intact before it. */
403
+ export function newlyBrokenBundles(before, after) {
404
+ const seen = new Set(before.map(entry => entry.name));
405
+ return after.filter(entry => !seen.has(entry.name));
406
+ }
407
+ export function checkClientBundle(profile, name, explicitDir) {
408
+ const root = join(profileDir(profile, explicitDir), 'node_modules', name);
409
+ let manifest;
410
+ try {
411
+ manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
412
+ }
413
+ catch {
414
+ return { ok: true, reason: null };
415
+ }
416
+ if (manifest.dsh?.client === undefined)
417
+ return { ok: true, reason: null };
418
+ const exportsField = manifest.exports;
419
+ const relative = exportsField !== null && typeof exportsField === 'object' && !Array.isArray(exportsField)
420
+ ? clientBundlePath(exportsField['./client'])
421
+ : null;
422
+ if (relative === null)
423
+ return { ok: true, reason: null };
424
+ let source;
425
+ try {
426
+ source = readFileSync(join(root, relative), 'utf8');
427
+ }
428
+ catch {
429
+ // Declared but absent is verifyActivation's territory (a missing entry
430
+ // artifact is already `broken` there); duplicating it here would report
431
+ // one problem twice in two different vocabularies.
432
+ return { ok: true, reason: null };
433
+ }
434
+ try {
435
+ new Script(source, { filename: relative });
436
+ return { ok: true, reason: null };
437
+ }
438
+ catch (error) {
439
+ const message = error instanceof Error ? error.message : String(error);
440
+ // `new Script` compiles a CLASSIC script, so perfectly valid module
441
+ // syntax is a SyntaxError to it: `export`, `import`, and top-level
442
+ // `await` all throw. This package ships CJS, which is why nobody noticed
443
+ // — but a plugin whose client bundle is ESM was being told its file was
444
+ // corrupt, and offered a rollback for a file that is fine.
445
+ //
446
+ // That is the exact outcome this check's own contract forbids: a false
447
+ // "your plugin is corrupt" is worse than not checking. Module syntax
448
+ // means "cannot judge with this parser", not "broken".
449
+ if (MODULE_SYNTAX_ERROR.test(message))
450
+ return { ok: true, reason: null };
451
+ return { ok: false, reason: message };
452
+ }
453
+ }
package/package.json ADDED
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "@chatcode/cco-market",
3
+ "description": "ChatCode CLI Plugin Market — browse, search, and one-click install community plugins. · ChatCode CLI 可视化插件市场:逛一逛,点一下,装好。",
4
+ "version": "1.45.1",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/dsh-market/dsh-market.git"
12
+ },
13
+ "homepage": "https://dshmarket.com",
14
+ "keywords": [
15
+ "deepseek",
16
+ "harness",
17
+ "dsh",
18
+ "dsh-plugin",
19
+ "marketplace",
20
+ "market",
21
+ "plugin-manager"
22
+ ],
23
+ "scripts": {
24
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
25
+ "build": "tsc -p tsconfig.json && npm run build:client",
26
+ "build:client": "tsdown && node scripts/normalize-client-banner.mjs",
27
+ "check": "npm run typecheck && npm run build && node scripts/restart-smoke.mjs",
28
+ "test": "vitest run",
29
+ "test:compat": "vitest run --config vitest.compat.config.ts",
30
+ "prepack": "npm run build && node scripts/preflight.mjs",
31
+ "prepare": "npm run build",
32
+ "validate:registry": "node scripts/validate-registry.mjs",
33
+ "snapshot": "node -e \"require('node:fs').mkdirSync('data',{recursive:true})\" && curl -sf https://awesome-dsh-plugin.com/plugins.json -o data/registry-snapshot.json && curl -sf https://awesome-dsh-plugin.com/readmes.json -o data/readmes-snapshot.json",
34
+ "check:web-auth-capture": "node scripts/check-web-auth-capture.mjs",
35
+ "test:web": "node scripts/check-web-auth-capture.mjs && vitest run --config vitest.web.config.ts",
36
+ "build:site": "node scripts/build-site.mjs",
37
+ "verify:brand": "node scripts/verify-brand.mjs"
38
+ },
39
+ "dependencies": {
40
+ "js-yaml": "^4.3.2",
41
+ "undici": "^7.29.0",
42
+ "yauzl": "^3.4.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@deepseek-ai/cordis": "^4.0.1",
46
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.7 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
47
+ "@deepseek-ai/schemastery": "^3.18.1"
48
+ },
49
+ "dsh": {
50
+ "bundle": {
51
+ "patch": "./cordis.patch.yml"
52
+ },
53
+ "client": {
54
+ "inject": [
55
+ "@deepseek-ai/dsh-client-runtime",
56
+ "@deepseek-ai/dsh-client-locale",
57
+ "@deepseek-ai/dsh-client-ui-settings",
58
+ "@deepseek-ai/dsh-client-ui-theme"
59
+ ],
60
+ "platform": "web"
61
+ }
62
+ },
63
+ "exports": {
64
+ ".": {
65
+ "types": "./lib/types/index.d.ts",
66
+ "default": "./lib/index.js"
67
+ },
68
+ "./update-api-v1": {
69
+ "types": "./lib/types/update-api-v1.d.ts",
70
+ "default": "./lib/update-api-v1.js"
71
+ },
72
+ "./client": "./client/client.js",
73
+ "./cordis.patch.yml": "./cordis.patch.yml",
74
+ "./package.json": "./package.json"
75
+ },
76
+ "files": [
77
+ "lib",
78
+ "src",
79
+ "client",
80
+ "UPDATE-API-V1.md",
81
+ "cordis.patch.yml",
82
+ "LICENSE"
83
+ ],
84
+ "devDependencies": {
85
+ "@deepseek-ai/cordis": "^4.0.1",
86
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
87
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
88
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.7",
89
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
90
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
91
+ "@deepseek-ai/schemastery": "^3.18.1",
92
+ "@testing-library/react": "^16.3.2",
93
+ "@types/js-yaml": "^4.0.9",
94
+ "@types/node": "^26.2.0",
95
+ "@types/react": "~18.3.1",
96
+ "@types/react-dom": "~18.3.1",
97
+ "@types/yauzl": "^2.10.3",
98
+ "@types/yazl": "^3.3.1",
99
+ "jsdom": "^29.1.1",
100
+ "lightningcss": "^1.33.0",
101
+ "marked": "^18.0.10",
102
+ "playwright": "^1.62.1",
103
+ "react": "^18.3.1",
104
+ "react-dom": "~18.3.1",
105
+ "tsdown": "^0.22.14",
106
+ "typescript": "^7.0.2",
107
+ "vitest": "^4.1.10",
108
+ "yazl": "^3.3.1"
109
+ },
110
+ "publishConfig": {
111
+ "registry": "https://registry.npmjs.org/"
112
+ },
113
+ "peerDependenciesMeta": {
114
+ "@deepseek-ai/dsh-settings": {
115
+ "optional": true
116
+ },
117
+ "@deepseek-ai/schemastery": {
118
+ "optional": true
119
+ }
120
+ }
121
+ }