@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
@@ -0,0 +1,100 @@
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
+ /** Profile bundles that ship with the dsh host and must stay put (#98). */
16
+ export declare const INBOX_BUNDLES: Set<string>;
17
+ /** The bundle stack as it appears in the profile manifest. */
18
+ export interface BundleStack {
19
+ /** Full ordered list from dsh.profile.bundles. */
20
+ bundles: string[];
21
+ /** The subset that may be reordered (community bundles). */
22
+ community: string[];
23
+ }
24
+ /** Author-declared ordering constraints of one bundle. */
25
+ export interface BundleRule {
26
+ name: string;
27
+ /** This bundle must load after every name in this list. */
28
+ after: string[];
29
+ /** This bundle must load before every name in this list. */
30
+ before: string[];
31
+ }
32
+ /** A violated before/after rule in the current or proposed order. */
33
+ export interface OrderConflict {
34
+ name: string;
35
+ reason: string;
36
+ }
37
+ /** Read the profile's bundle stack (empty when the manifest is unreadable). */
38
+ export declare function readBundleStack(profileDir: string): BundleStack;
39
+ /**
40
+ * Read each bundle's declared ordering rules from its package manifest
41
+ * (`dsh.bundle.order.{before,after}` — a list of bundle package names).
42
+ * Unresolvable packages and missing declarations contribute nothing.
43
+ */
44
+ export declare function readBundleRules(profileDir: string): BundleRule[];
45
+ /**
46
+ * Check a bundle order against the declared before/after rules. Rules naming
47
+ * bundles outside `order` are ignored (a rule for a not-yet-installed bundle
48
+ * must not block the current stack).
49
+ * @returns every violated rule with a readable reason; [] when all hold.
50
+ */
51
+ export declare function validateOrder(bundleNames: string[], rules: BundleRule[]): OrderConflict[];
52
+ /**
53
+ * Merge a community-bundle permutation into the full stack. Official in-box
54
+ * bundles keep their EXACT positions (never moved); community bundles are
55
+ * replaced by `newOrder` in order of appearance. Pure — nothing is written.
56
+ * @returns the merged full stack, or the rejection reason when `newOrder` is
57
+ * not a permutation of the community bundles (duplicates, additions,
58
+ * omissions, official names).
59
+ */
60
+ export declare function mergeOrder(bundles: string[], newOrder: string[]): {
61
+ ok: true;
62
+ bundles: string[];
63
+ } | {
64
+ ok: false;
65
+ error: string;
66
+ };
67
+ /**
68
+ * Topologically sort the community bundles by their before/after rules — the
69
+ * "auto-fix" counterpart to validateOrder. Returns null when no declared rule
70
+ * applies to the current stack (nothing to suggest). With rules, Kahn's
71
+ * algorithm breaks ties by the CURRENT order: unconstrained bundles keep
72
+ * their current relative order and constrained bundles move only as far as
73
+ * the rules require — the suggestion is the minimal change that satisfies
74
+ * every rule, never an arbitrary canonical rewrite of a hand-picked order
75
+ * (issue #125 review).
76
+ * @returns the suggested community order, null when there are no rules, or a
77
+ * cycle report when the constraints cannot be satisfied (references to
78
+ * unlisted bundles ignored).
79
+ */
80
+ export declare function suggestOrder(bundleNames: string[], rules: BundleRule[]): {
81
+ ok: true;
82
+ order: string[];
83
+ } | {
84
+ ok: false;
85
+ cycle: string[];
86
+ } | null;
87
+ /**
88
+ * Apply a new community-bundle order to the profile manifest. The official
89
+ * in-box bundles keep their exact positions; `newOrder` must be a permutation
90
+ * of the current community bundles (no duplicates, no additions, no
91
+ * omissions). On any failure the manifest is left untouched.
92
+ * @returns the new full stack on success, or an error description.
93
+ */
94
+ export declare function applyBundleOrder(profileDir: string, newOrder: string[]): {
95
+ ok: true;
96
+ bundles: string[];
97
+ } | {
98
+ ok: false;
99
+ error: string;
100
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Patch-layer plugin toggles — hot disable/enable through the profile's
3
+ * user patch layer (cordis.patch.yml), the mechanism ported from
4
+ * Noob-stupid/dsh-plugin-hub's plugin console.
5
+ *
6
+ * DSH composes a web profile from the bundle layers + the user patch layer
7
+ * (`$CHATCODE_CLI_HOME/profiles/<name>/cordis.patch.yml`), with per-key override
8
+ * semantics: a patch row `- id: X` + `disabled: true` stops that loader
9
+ * entry, and `disabled: false` force-enables one a lower layer disabled.
10
+ * The profile's config-file watcher (HMR) re-composes within ~1s of the
11
+ * save — no restart — and the loader re-applies the same file on every
12
+ * boot, so the choice survives restarts through the official mechanism.
13
+ *
14
+ * The market ALSO keeps its own in-memory/state.json bookkeeping (hot-mount
15
+ * shims have no bundle row to patch, and the client's disable list drives
16
+ * the switches); this module is the durable, HMR-driven layer on top.
17
+ *
18
+ * Safety (borrowed from the plugin-hub implementation):
19
+ * - writes are serialized so concurrent toggles cannot interleave a
20
+ * read-modify-write;
21
+ * - an append is REFUSED when the patch file is not a valid entry list —
22
+ * a malformed file (e.g. a stray `[]` followed by items) is never made
23
+ * worse, the market reports it instead;
24
+ * - host infrastructure rows (transport / hot-reload / storage / settings
25
+ * chains) are protected and refuse to toggle.
26
+ */
27
+ /** The slice of the loader tree this module needs. */
28
+ export interface PatchHost {
29
+ loader: {
30
+ entries(): Iterable<{
31
+ options?: {
32
+ id?: string;
33
+ name?: string;
34
+ config?: {
35
+ path?: string;
36
+ };
37
+ };
38
+ }>;
39
+ };
40
+ }
41
+ /** True when the module name sits on the host infrastructure chain. */
42
+ export declare function isProtectedModule(moduleName: string | undefined): boolean;
43
+ /**
44
+ * Resolve the profile's user patch layer. Prefers the path the loader's
45
+ * cordis:include entry actually read (authoritative under hosts that own
46
+ * the profile directory, like DSH Desktop); falls back to the conventional
47
+ * `<profile>/cordis.patch.yml`.
48
+ */
49
+ export declare function findUserPatchPath(host: PatchHost, profileDir: string): string;
50
+ /** What the user patch layer currently says about rows. */
51
+ export interface PatchState {
52
+ /** Row ids the user patch disables (`disabled: true`). */
53
+ disables: string[];
54
+ /** Row ids the user patch force-enables (`disabled: false`). */
55
+ forced: string[];
56
+ /** Row ids the user patch inserts. */
57
+ inserts: string[];
58
+ }
59
+ /**
60
+ * Line-wise scan of one patch file — the plugin-hub shapes. Deliberately
61
+ * not a YAML parse: the file may hold structures the market's dialect
62
+ * rejects, but a plain `- id: X` + `disabled: true|false` pair is enough
63
+ * to know what the user patch layer says.
64
+ */
65
+ export declare function readUserPatchState(patchPath: string): PatchState;
66
+ /**
67
+ * User-authored insert rows that still load `packageName` (or one of its
68
+ * exported subpaths). This evidence deliberately stays separate from the
69
+ * package's own bundle declaration: uninstall may clean market-owned rows,
70
+ * but it must never rewrite an insert the user owns.
71
+ *
72
+ * A missing patch is the ordinary empty-profile shape. `null` means the
73
+ * existing file could not be read as DSH's patch dialect; callers must treat
74
+ * that as indeterminate and refuse the destructive step.
75
+ */
76
+ export declare function userPatchPackageReferences(patchPath: string, packageName: string): string[] | null;
77
+ /**
78
+ * The user-patch row ids one installed package owns: its bundle patch's
79
+ * insert rows, plus the loader entries currently carrying its name.
80
+ * Empty for client-only packages (no bundle rows) — the market's own
81
+ * state.json mechanism covers those, and there is nothing to patch.
82
+ * Market-owned namespaces (hot-mount `mkt-*`, shim `client-*`) are
83
+ * excluded: their rows live in the market's own include subtree, and a
84
+ * permanent patch row targeting them would be a boot-time orphan.
85
+ */
86
+ export declare function rowIdsForPackage(host: PatchHost, profileDirectory: string, packageName: string): string[];
87
+ /**
88
+ * The ids of OTHER plugins a package's bundle patch DISABLES — top-level
89
+ * `- id: X` + `disabled: true` rows targeting plugins it does not own. This is
90
+ * the precise marker of a bundle whose toggle-off can brick the boot (#224):
91
+ * dsh-postgres-backends disables session-persistence-jsonl, so once the market
92
+ * also disables the postgres backends nothing provides sessionPersistence.
93
+ *
94
+ * A bundle that merely RECONFIGURES a neighbour is deliberately NOT counted:
95
+ * the e2e fixture-cross tweaks dshm-fixture-b's config, and #147 requires
96
+ * disabling it to leave that neighbour live — dropping such a bundle from the
97
+ * stack broke its re-enable. Config-only side effects stay on the normal #147
98
+ * path; only a foreign `disabled: true` triggers the bundle removal. Removing
99
+ * the bundle still neutralizes any config side effects it carries, since its
100
+ * whole patch stops applying.
101
+ *
102
+ * Reads both patch sources like rowIdsForPackage — the declared dsh.bundle.patch
103
+ * and the conventional root cordis.patch.yml — so either form is detected.
104
+ */
105
+ export declare function carrierDisableIds(profileDirectory: string, packageName: string): string[];
106
+ /**
107
+ * Per-package patch-layer flags for the installed list: names whose rows the
108
+ * user patch layer disables / force-enables. These cover toggles made
109
+ * OUTSIDE the market (hand-edited cordis.patch.yml, dsh-web-plugin-manager,
110
+ * the dsh CLI), which the market's own state.json never sees.
111
+ */
112
+ export declare function packagePatchFlags(host: PatchHost, profileDirectory: string, names: readonly string[], state: PatchState): {
113
+ disabled: string[];
114
+ forced: string[];
115
+ };
116
+ /** Disable one row: append `- id: X` + `disabled: true` (idempotent). */
117
+ export declare function disableRow(patchPath: string, rowId: string): Promise<{
118
+ ok: boolean;
119
+ reason: string | null;
120
+ }>;
121
+ /** Enable one row: remove the `disabled: true` block; force-enable with
122
+ * `disabled: false` when a lower layer (bundle/home patch) holds it down. */
123
+ export declare function enableRow(patchPath: string, rowId: string): Promise<{
124
+ ok: boolean;
125
+ reason: string | null;
126
+ }>;
127
+ /** Remove every disable/force block the market (or the user) wrote for a
128
+ * row — the uninstall cleanup, so a removed plugin leaves no orphan rows. */
129
+ export declare function removeRowBlocks(patchPath: string, rowIds: readonly string[]): void;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * pnpm compatibility layer — everything the market needs to know about how
3
+ * different pnpm majors behave inside a dsh profile directory, kept pure and
4
+ * separately testable (test/unit + test/integration exercise this module
5
+ * against real pnpm 9/10/11).
6
+ *
7
+ * Verified behavior matrix (2026-08, pnpm 9.15.9 / 10.28.2 / 11.21.0):
8
+ * - workspace root, `add` without -w: pnpm 9 fails ERR_PNPM_ADDING_TO_ROOT;
9
+ * pnpm 10/11 succeed.
10
+ * - `add -w` where NO pnpm-workspace.yaml exists: ALL majors fail with
11
+ * "--workspace-root may only be used inside a workspace".
12
+ * - modules dir built by pnpm 9, then pnpm 10/11 mutate it: a modules-layout
13
+ * compatibility error (public-hoist-pattern on Unix; virtual-store path
14
+ * length can be the first mismatch pnpm reports on Windows).
15
+ */
16
+ /**
17
+ * Decide the argv for a `dsh plugin <add|remove> …` call in the given profile.
18
+ *
19
+ * pnpm 9 refuses to add at a workspace root without -w (#17, #20); every
20
+ * pnpm major refuses -w when the directory is NOT a workspace. So the flag
21
+ * is injected exactly when the profile has a pnpm-workspace.yaml.
22
+ * @param profileDir - resolved profile directory (owns pnpm-workspace.yaml, or not).
23
+ * @param pluginArgs - the raw args, e.g. ['add', 'dshmarket@latest'].
24
+ * @returns args with -w injected when — and only when — the profile is a workspace root.
25
+ */
26
+ export declare function pluginArgsFor(profileDir: string, pluginArgs: string[]): string[];
27
+ /** One recognized pnpm failure, with a bilingual explanation for the UI. */
28
+ /**
29
+ * The namespace whose packages the dsh runtime provides rather than npm.
30
+ *
31
+ * A peer dependency on one of these is a statement about the host, not a
32
+ * package to download — and several of them are never published at all.
33
+ */
34
+ export declare const HOST_NAMESPACE_RE: RegExp;
35
+ export interface PnpmFailure {
36
+ code: 'adding-to-root' | 'not-a-workspace' | 'hoist-pattern-diff' | 'pnpm-missing' | 'release-age-violation' | 'ignored-builds' | 'git-prepare-not-allowed' | 'git-prepare-failed' | 'tarball-url-mismatch' | 'fetch-404' | 'transient-network' | 'fetch-timeout' | 'unexpected-store' | 'patch-failed' | 'missing-tarball-integrity' | 'windows-file-locked' | 'pnpm-unusable' | 'missing-local-dependency';
37
+ /** Bilingual, actionable message shown to the user instead of the raw wall of text. */
38
+ message: string;
39
+ /** True when re-running `pnpm install` in the profile is the documented recovery. */
40
+ recoverable: boolean;
41
+ /**
42
+ * Show this message INSTEAD of the captured output, not after it.
43
+ *
44
+ * Normally the raw text is worth keeping: it is pnpm's own account of what
45
+ * happened, and the explanation sits under it. Set only where the captured
46
+ * bytes carry nothing a user can read — cmd.exe writes its errors in the
47
+ * OEM code page, which arrives here as replacement characters, so pasting
48
+ * them under an explanation adds noise and hides the explanation (#502).
49
+ */
50
+ replaceOutput?: boolean;
51
+ /**
52
+ * The package pnpm could not resolve, when the failure names one.
53
+ *
54
+ * Exposed because the NAME alone does not say what went wrong: the same
55
+ * 404 is a ghost entry the user must delete when the package is a direct
56
+ * dependency of the profile, and an unpublished host peer the market can
57
+ * retry around when it is not (#289). Only a caller holding the profile
58
+ * manifest can tell those apart, so the classifier reports the fact and
59
+ * leaves the judgement to it.
60
+ */
61
+ pkg?: string;
62
+ }
63
+ /**
64
+ * Momentary network failures — worth exactly one automatic retry (#83).
65
+ * pnpm 5xx fetch codes, its meta-fetch give-up, and the raw socket errors
66
+ * that surface through dsh's wrapper. Permanent shapes (404, auth) are
67
+ * deliberately absent: retrying those just doubles the wait for bad news.
68
+ */
69
+ export declare function isTransientPnpmFailure(output: string): boolean;
70
+ /**
71
+ * pnpm's per-request fetch timeout: the abort surfaces as a DOMException
72
+ * ("The operation was aborted due to timeout", code 23) through undici —
73
+ * pnpm logs it as `GET … error (23)` before giving up. This is the failure
74
+ * shape for large tarballs (github: sources download the WHOLE repo, even
75
+ * for a `#path:` subdirectory plugin) on slow networks: pnpm's default
76
+ * 60-second limit is simply not enough, so a plain retry fails again at the
77
+ * same limit. The market's recovery re-runs once with a longer
78
+ * fetchTimeout (see withHoistRecovery).
79
+ */
80
+ export declare function isFetchTimeoutFailure(output: string): boolean;
81
+ /**
82
+ * Map a failed pnpm run's combined output to a known failure mode.
83
+ *
84
+ * dsh's own wrapper line ("dsh: pnpm failed in profile directory …") names no
85
+ * cause, so the market must recognize pnpm's real diagnostics itself (#20).
86
+ * @param output - stdout+stderr of the failed run.
87
+ * @param exitCode - the run's exit status, when the caller has it (null when
88
+ * the process was signalled). Only a
89
+ * failure whose whole signal IS the status reads it (#502); everything else
90
+ * is recognized from what pnpm said.
91
+ * @returns the classified failure, or null when unrecognized (raw output is then shown as-is).
92
+ */
93
+ export declare function classifyPnpmFailure(output: string, exitCode?: number | null): PnpmFailure | null;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Named plugin presets — issue #98 (phase 3), the "save different plugin
3
+ * combinations" product shape. A preset captures the community-bundle order
4
+ * and the disabled-plugin list of the profile at save time; applying one
5
+ * replays the composition under the candidate order (trialValidate), refuses
6
+ * on failure, auto-snapshots the profile (createProfileSnapshot), and only
7
+ * then writes the bundle order and disable list.
8
+ *
9
+ * Presets persist in `<profile>/.dsh-market/presets.json` (market-owned
10
+ * state, like snapshots) — deliberately separate from state.json, whose
11
+ * shape routes.ts owns.
12
+ */
13
+ import { type TrialDiff, type TrialIssue } from './trial.ts';
14
+ /** Maximum presets stored per profile (quota — issue #98 analysis). */
15
+ export declare const MAX_PRESETS = 50;
16
+ export interface Preset {
17
+ name: string;
18
+ /** Community-bundle order this preset restores. */
19
+ bundleOrder: string[];
20
+ /** Disabled plugin names this preset restores. */
21
+ disabled: string[];
22
+ createdAt: number;
23
+ }
24
+ export interface PresetResult {
25
+ ok: boolean;
26
+ error?: string;
27
+ /** Set when applyPreset auto-created a pre-change snapshot. */
28
+ snapshot?: string;
29
+ }
30
+ /** All saved presets, newest first. */
31
+ export declare function listPresets(profileDir: string): Preset[];
32
+ /**
33
+ * Save the current composition state as a named preset. The bundle order is
34
+ * validated against the current community bundles so a stale snapshot can
35
+ * never be stored.
36
+ */
37
+ export declare function savePreset(profileDir: string, name: unknown, bundleOrder: unknown, disabled: unknown): PresetResult;
38
+ /** Delete a named preset. */
39
+ export declare function deletePreset(profileDir: string, name: unknown): PresetResult;
40
+ /** The concrete change a preset apply would make — computed BEFORE writing. */
41
+ export interface PresetChange {
42
+ /** Bundles whose position changes under the preset order. */
43
+ reordered: string[];
44
+ /** Plugins the preset would ENABLE (currently disabled, enabled by the preset). */
45
+ enabled: string[];
46
+ /** Plugins the preset would DISABLE (currently enabled, disabled by the preset). */
47
+ disabled: string[];
48
+ /** True when nothing would change. */
49
+ noop: boolean;
50
+ }
51
+ export interface PresetApplyResult extends PresetResult {
52
+ changes?: PresetChange;
53
+ /** Set when the preset order fails trial validation — errors + current-vs-candidate diff (issue #125 review). */
54
+ trial?: {
55
+ errors: TrialIssue[];
56
+ warnings: TrialIssue[];
57
+ diff: TrialDiff;
58
+ };
59
+ }
60
+ /**
61
+ * The preset's bundle set vs the profile's current community bundles —
62
+ * a stale preset (saved before a plugin was installed/uninstalled) can no
63
+ * longer be applied as-is, but its intent (enabled/disabled plugins,
64
+ * relative order) is still previewable.
65
+ */
66
+ export interface PresetMismatch {
67
+ /** Bundles in the current profile that the preset does not mention. */
68
+ missing: string[];
69
+ /** Bundles the preset mentions that are not installed anymore. */
70
+ extra: string[];
71
+ /** True when the preset's bundle set differs from the current one. */
72
+ stale: boolean;
73
+ }
74
+ /**
75
+ * Preview what applying a preset would change, WITHOUT writing anything.
76
+ * A stale preset (bundle set mismatch) is NOT a hard failure: the preview
77
+ * reports the mismatch alongside the still-computable changes (relative
78
+ * order + enabled/disabled diffs over the intersection).
79
+ */
80
+ export declare function previewPreset(profileDir: string, name: unknown): PresetResult & {
81
+ changes?: PresetChange;
82
+ mismatch?: PresetMismatch;
83
+ };
84
+ /**
85
+ * Apply a saved preset: trial-validate the candidate order first (refuse
86
+ * without writing on any boot-breaking issue), auto-snapshot the profile,
87
+ * then write the bundle order and the disable list. The response carries the
88
+ * change preview so the UI can report exactly what moved.
89
+ */
90
+ export declare function applyPreset(profileDir: string, name: unknown, maxSnapshots?: number): PresetApplyResult;
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Profile filesystem reads — everything the market learns from a dsh
3
+ * profile directory (manifest, lockfile, installed package trees). Pure
4
+ * functions of the directory contents; no processes, no network.
5
+ */
6
+ /**
7
+ * Whether a profile name follows DSH's own directory-name contract.
8
+ *
9
+ * Keep this aligned with `@deepseek-ai/dsh-app-boot`'s
10
+ * `resolveProfileDir`: dots, spaces, and Unicode are ordinary name
11
+ * characters; only empty, traversal-shaped, launcher-owned, or
12
+ * separator-bearing names are refused.
13
+ */
14
+ export declare function isDshProfileName(profile: string): boolean;
15
+ /**
16
+ * Resolve a profile name beneath the canonical ChatCode CLI Home.
17
+ * An explicit directory is used by hosts, such as DSH Desktop, that own the
18
+ * active profile location rather than deriving it from process environment.
19
+ */
20
+ export declare function profileDir(profile: string, explicitDir?: string): string;
21
+ /**
22
+ * The in-box bundles dsh's profile templates install themselves — the ONLY
23
+ * names the market hides from the installed list. Community plugins may
24
+ * legitimately publish under the official scope (#28), so a whole-scope
25
+ * filter would make them invisible and fail install validation.
26
+ * (Diagnosis and fix proposed in #28 by @Lograthmic.)
27
+ */
28
+ export declare const INBOX_BUNDLES: Set<string>;
29
+ /** Community dependencies of the profile (in-box bundles filtered out). */
30
+ export declare function readInstalled(profile: string, explicitDir?: string): Record<string, string>;
31
+ /**
32
+ * RAW dependency map of the profile manifest — including the in-box bundles
33
+ * readInstalled() filters out. This is the rollback snapshot (#65): restoring
34
+ * a filtered view would delete @deepseek-ai/dsh-base and friends.
35
+ */
36
+ export declare function readManifestDeps(profile: string, explicitDir?: string): Record<string, string>;
37
+ /** Exact rollback state owned by one profile package operation. */
38
+ export interface ProfileManifestSnapshot {
39
+ dependencies: Record<string, string>;
40
+ profileBundles: {
41
+ present: false;
42
+ } | {
43
+ present: true;
44
+ value: unknown;
45
+ };
46
+ }
47
+ /** Read dependencies and the exact `dsh.profile.bundles` field before a package operation. */
48
+ export declare function readProfileManifestSnapshot(profile: string, explicitDir?: string): ProfileManifestSnapshot;
49
+ /**
50
+ * Restore the profile manifest fields a package operation may mutate:
51
+ * `dependencies` and `dsh.profile.bundles`. pnpm and `dsh plugin add` can
52
+ * write both before a later fetch or build-script failure (#65, #69, #339),
53
+ * leaving either an unresolvable dependency or a bundle the next boot cannot
54
+ * activate. Every unrelated manifest field remains untouched. The lockfile is
55
+ * left as-is; pnpm reconciles it from the manifest on the next run.
56
+ *
57
+ * The write is atomic because rollback runs after another operation already
58
+ * failed; a partial repair must not turn a valid profile into invalid JSON.
59
+ * @returns names whose entries were dropped or reverted, empty when nothing changed.
60
+ */
61
+ export declare function restoreProfileManifest(profile: string, snapshot: ProfileManifestSnapshot, explicitDir?: string): string[];
62
+ /**
63
+ * Remove a package from BOTH manifest lists — dependencies and
64
+ * dsh.profile.bundles. The uninstall counterpart of restoreProfileManifest:
65
+ * pnpm can fail a remove after deleting node_modules but before saving
66
+ * package.json (the #65 write-order's mirror image — a file locked mid-
67
+ * unlink aborts the run), leaving the manifest pointing at a package that
68
+ * no longer exists on disk. The next boot then fails to activate the ghost
69
+ * dependency. When disk truth says the package is gone, this finishes the
70
+ * removal the CLI could not. Every other manifest field is untouched.
71
+ *
72
+ * Written atomically because it runs only after something already went wrong
73
+ * mid-uninstall, so it is the worst place to leave a half-written manifest.
74
+ * @returns true when either list still mentioned the package.
75
+ */
76
+ export declare function dropFromManifest(profile: string, name: string, explicitDir?: string): boolean;
77
+ /** The version actually present in the profile's node_modules, or null. */
78
+ export declare function readInstalledVersion(profile: string, name: string, explicitDir?: string): string | null;
79
+ /** The installed package manifest, or null when absent or malformed. */
80
+ export declare function readInstalledManifest(profile: string, name: string, explicitDir?: string): unknown | null;
81
+ /**
82
+ * Whether a package or one of its direct dependencies ships a native addon.
83
+ *
84
+ * The question behind it: can unloading this plugin actually free its files?
85
+ * For ordinary JavaScript, yes — and on POSIX it does not even matter,
86
+ * because replacing an open file leaves the old inode to whoever holds it.
87
+ * For a native addon it is no on both counts: Node has no dlclose, so once a
88
+ * `.node` is loaded the process holds it until it exits. On Windows that
89
+ * turns "uninstall, then install again" into an EPERM on the rename, which
90
+ * is what @yandidan1 hit with node-hid (#441) — and no amount of disabling,
91
+ * unmounting or uninstalling from inside the running process can fix it.
92
+ *
93
+ * Deliberately a cheap structural check rather than a scan. Walking a
94
+ * dependency's tree for `*.node` means recursing through packages that can
95
+ * be tens of thousands of files, on the uninstall path, to answer a question
96
+ * three `existsSync` calls answer for every native module built or shipped
97
+ * the conventional way: node-gyp's `build/Release`, prebuild's `prebuilds/`,
98
+ * and the `binding.gyp` that names the addon in the first place.
99
+ *
100
+ * Direct dependencies are included because that is where these live: the
101
+ * plugin is JavaScript and the addon is a package it depends on, hoisted to
102
+ * the profile root beside it.
103
+ * @param profile - profile name.
104
+ * @param name - the installed package to ask about.
105
+ * @param explicitDir - resolved profile directory, when the caller has it.
106
+ * @returns true when a native addon is present in the package or a direct dependency.
107
+ */
108
+ export declare function holdsNativeAddon(profile: string, name: string, explicitDir?: string): boolean;
109
+ /**
110
+ * Strong repository identities for a locally linked dependency (#141).
111
+ * Explicit github: specs already carry this evidence; only link:/file: need
112
+ * filesystem discovery. This compatibility wrapper returns only declared
113
+ * package.json identities; Git origins are exposed separately as hints.
114
+ */
115
+ export declare function readInstalledRepoIdentities(profile: string, name: string, spec: string, explicitDir?: string): string[];
116
+ export interface InstalledRepoEvidence {
117
+ identities: string[];
118
+ hints: string[];
119
+ }
120
+ /**
121
+ * Discover declared repository identities and weaker local-origin hints. A
122
+ * package.json repository declaration is authoritative; Git origin is only a
123
+ * disambiguation hint because a checkout may legitimately point at a fork.
124
+ */
125
+ export declare function readInstalledRepoEvidence(profile: string, name: string, spec: string, explicitDir?: string): InstalledRepoEvidence;
126
+ /** Pinned commit per `owner/repo` from the profile lockfile's codeload tarball URLs. */
127
+ export declare function readLockCommits(profile: string, explicitDir?: string): Map<string, string>;
128
+ /**
129
+ * Commit recorded for a non-codeload git resolution (`type: git` in pnpm's
130
+ * lockfile). Matched against the install spec so a Gitea/GitLab URL can
131
+ * compare HEAD without mistaking a same-named npm package (#525).
132
+ */
133
+ export declare function readGitResolutionCommit(profile: string, spec: string, explicitDir?: string): string | null;
134
+ /** True when the installed package's manifest declares a dsh plugin surface. */
135
+ export declare function hasDshManifest(dir: string): boolean;
136
+ /**
137
+ * True when the package's declared entry artifact actually exists — github
138
+ * source checkouts of build-required plugins ship no lib/, and promoting one
139
+ * into the bundle layer bricks the next boot (ERR_MODULE_NOT_FOUND kills the
140
+ * whole profile, #18).
141
+ */
142
+ export declare function entryArtifactExists(dir: string): boolean;
143
+ /**
144
+ * Package names a bundle patch mounts — the `name:` rows of the package's
145
+ * declared `dsh.bundle.patch` file. Line-wise on purpose: the strict
146
+ * hot-mount parser rejects config/expression rows, but for "what does this
147
+ * bundle bring in" any name row counts.
148
+ */
149
+ export declare function bundlePatchTargets(dir: string): string[];
150
+ /**
151
+ * Loader entry ids a bundle patch inserts. Cordis refuses to boot a tree
152
+ * with a duplicate entry id ("duplicate loader entry id: storage", #122), so
153
+ * these are what two bundles can collide on.
154
+ */
155
+ export declare function bundlePatchEntryIds(dir: string): string[];
156
+ /**
157
+ * Loader entry ids the patch INSERTS — the rows the package owns, as opposed
158
+ * to rows of OTHER plugins it merely configures (#147).
159
+ *
160
+ * A bundle patch has two kinds of entry:
161
+ *
162
+ * - insert: ← rows this package brings into the tree
163
+ * - id: vision-router
164
+ * name: dsh-vision-router
165
+ * - id: attachment-local ← someone else's row, only reconfigured
166
+ * config: { maxImageBytes: … }
167
+ *
168
+ * Treating both as "this package's rows" made disabling one plugin write
169
+ * `disabled: true` onto the official rows it tuned — killing attachments and
170
+ * the DeepSeek model with it.
171
+ */
172
+ export declare function bundlePatchInsertedIds(dir: string): string[];
173
+ /**
174
+ * `name:` and `id:` rows of the package's declared bundle patch. Line-wise
175
+ * on purpose: the strict hot-mount parser rejects config/expression rows,
176
+ * but for "what does this bundle bring in" any row counts. `insertedIds` is
177
+ * the subset nested under an `insert:` key (#147).
178
+ */
179
+ /**
180
+ * Rows of one patch file. Exported because a package may ship its patch at
181
+ * the conventional path INSTEAD of declaring `dsh.bundle.patch`, and the
182
+ * patch layer has to read that one by the same rules — a second hand-rolled
183
+ * scan drifted from this one and re-introduced #147 on that path (it closed
184
+ * the insert block only on `id:` lines, so `- disable:` followed by nested
185
+ * ids claimed the neighbour's rows).
186
+ */
187
+ export declare function parsePatchRows(text: string): {
188
+ names: string[];
189
+ ids: string[];
190
+ insertedIds: string[];
191
+ };
192
+ /** The profile manifest's `dsh.profile.bundles` — what the CLI reconciled. */
193
+ export declare function readProfileBundles(profileDirectory: string): string[];
194
+ /**
195
+ * Drop one bundle from the profile manifest's `dsh.profile.bundles`, leaving
196
+ * the package installed as a dependency. This is the carrier-bundle half of a
197
+ * toggle-off (#224): a bundle whose patch reconfigures plugins it does NOT own
198
+ * (dsh-postgres-backends disables session-persistence-jsonl and reroutes
199
+ * storage-domain) keeps applying those side-effect rows on every boot while it
200
+ * stays in the stack, and the #147 ownership rule deliberately never writes
201
+ * them — so removing the bundle from the stack is the only thing that stops
202
+ * them all at once. The package itself stays installed; enabling re-adds it.
203
+ * @returns true when the bundle was present and removed.
204
+ */
205
+ export declare function removeProfileBundle(profileDirectory: string, name: string): boolean;
206
+ /**
207
+ * Re-add a bundle to `dsh.profile.bundles` after a carrier toggle-off (#224).
208
+ * Idempotent: a bundle already present is left untouched. The name is appended
209
+ * (the install flow appends too); the loader re-validates ordering on the next
210
+ * composition, so a declared before/after rule surfaces there rather than here.
211
+ * @returns true when the bundle was added, false when it was already present.
212
+ */
213
+ export declare function addProfileBundle(profileDirectory: string, name: string): boolean;
214
+ /**
215
+ * Loader entry ids a newly added package would collide on with bundles the
216
+ * profile ALREADY loads (#122).
217
+ *
218
+ * Cordis hard-fails the whole tree on a duplicate id, so this is not a
219
+ * cosmetic conflict: installing a TUI bundle into a web profile (both
220
+ * declare `id: storage`) leaves DSH unable to start at all, with an error
221
+ * naming neither plugin. Checked against the profile's own bundle list so a
222
+ * package is never compared with itself.
223
+ * @returns colliding ids mapped to the already-installed bundle that owns them.
224
+ */
225
+ export declare function conflictingEntryIds(profileDirectory: string, candidate: string, installedBundles: readonly string[]): {
226
+ id: string;
227
+ owner: string;
228
+ }[];
229
+ /**
230
+ * Whether the loader has anything to load for this package: its own entry
231
+ * artifact, or — for CARRIER bundles — patch rows naming other packages that
232
+ * do have one.
233
+ *
234
+ * Carriers are why `entryArtifactExists` alone is the wrong test (#103):
235
+ * `@linxin666/dsh-skins` ships skin assets plus a patch mounting
236
+ * `@linxin666/dsh-client-ui-skin-center`, and declares no main/exports/
237
+ * index.js of its own. Judged by its own entry it looks like the
238
+ * source-only checkout the #18 guard removes — so the market both flagged it
239
+ * broken AND uninstalled it right after installing.
240
+ * @param profileDirectory - resolved profile directory (host-authoritative under Desktop).
241
+ * @param name - installed package name.
242
+ */
243
+ export declare function hasLoadableEntry(profileDirectory: string, name: string): boolean;
244
+ /** Plugin subdirectories (depth 2) of a collection checkout, as relative paths. */
245
+ export declare function pluginSubdirs(root: string): string[];
246
+ /**
247
+ * Allow the given packages' build scripts in the profile's
248
+ * pnpm-workspace.yaml `allowBuilds` block (the key dsh profiles use),
249
+ * merging with existing entries and leaving the rest of the yaml intact.
250
+ * (#6 by @qichuang321.)
251
+ * @returns every package now allowed.
252
+ */
253
+ export declare function setAllowBuilds(profile: string, packages: string[], explicitDir?: string): string[];