@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,50 @@
1
+ /**
2
+ * Deciding a download region by asking the network, once.
3
+ *
4
+ * The alternative was reading the system time zone, and it is wrong for
5
+ * exactly the people who most need this to work: someone in Shanghai behind
6
+ * a corporate proxy or a VPN reaches the official registry perfectly well
7
+ * and would be routed onto mirrors they do not need, while a Chinese-locale
8
+ * machine sitting in Singapore would be routed onto mirrors that are further
9
+ * away than the origin. A time zone says where a clock is. It does not say
10
+ * what the network can reach, which is the only question here.
11
+ *
12
+ * So the probe measures the thing itself: it asks both registries for the
13
+ * same small document and takes whichever answers first. That is
14
+ * self-correcting in a way a lookup table cannot be — the machine behind the
15
+ * proxy measures fast official access and stays global, without anyone
16
+ * having to enumerate the exceptions.
17
+ *
18
+ * It runs ONCE, when no region has ever been decided, and its answer is
19
+ * persisted as the decision. Re-probing every boot would let a market
20
+ * silently change routes between runs, which is the kind of behaviour that
21
+ * makes "it was fast yesterday" impossible to debug.
22
+ */
23
+ import { type Region } from './regions.ts';
24
+ /**
25
+ * Ask every region's registry the same question; return whichever answers
26
+ * first.
27
+ *
28
+ * First-past-the-post rather than a latency comparison, deliberately. From
29
+ * inside China the official registry usually does answer eventually, so
30
+ * "did it fail" is the wrong test and "which came back first" is the right
31
+ * one. From outside, the mainland mirror is reachable but further, and the
32
+ * same rule picks global. One rule, both directions, no thresholds to tune.
33
+ *
34
+ * @param timeoutMs - how long to wait before giving up on all of them.
35
+ * @param env - environment, for the registry overrides.
36
+ * @returns the winning region, or `global` when nothing answered — an
37
+ * unreachable network is not evidence for switching routes.
38
+ */
39
+ export declare function probeRegion(timeoutMs?: number, env?: NodeJS.ProcessEnv): Promise<Region>;
40
+ /**
41
+ * The region to run under, probing only if nothing has ever decided one.
42
+ *
43
+ * @param stored - the region already on record, or undefined.
44
+ * @returns the region, and whether this call decided it (which is what earns
45
+ * the user a one-time notice explaining the choice).
46
+ */
47
+ export declare function resolveRegion(stored: Region | undefined, timeoutMs?: number, env?: NodeJS.ProcessEnv): Promise<{
48
+ region: Region;
49
+ probed: boolean;
50
+ }>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Download regions: which route the market's own network requests take.
3
+ *
4
+ * Almost every external request the market makes lands on npm's registry or
5
+ * on GitHub — the plugin catalog, update checks, package downloads, author
6
+ * avatars, README screenshots. From mainland China several of those can be
7
+ * slow, which is why this is ONE setting rather than a
8
+ * row of them: "npm mirror", "GitHub proxy" and "image proxy" are three
9
+ * spellings of a single question the user is actually being asked, which is
10
+ * where they are.
11
+ *
12
+ * The routing table is the single source of truth. Every consumer asks it
13
+ * rather than reaching for a hardcoded host, so adding a region is a table
14
+ * entry instead of a search across six modules.
15
+ *
16
+ * Each route has a ChatCode-first environment escape hatch, with the former
17
+ * `DSHM_*` spelling retained only as a compatibility input.
18
+ * (src/registry.ts). The China route has service-specific public-proxy
19
+ * fallbacks; those come and go, and a user whose routes have died needs a way
20
+ * out that is not "wait for the next release".
21
+ */
22
+ /** A region the market can download from. */
23
+ export type Region = 'global' | 'china';
24
+ /** Every region a user may pick. */
25
+ export declare const REGIONS: readonly Region[];
26
+ /** Narrow an untrusted value to a Region, or null. */
27
+ export declare function asRegion(value: unknown): Region | null;
28
+ /**
29
+ * The npm registry the market and pnpm read, no trailing slash.
30
+ *
31
+ * Exported because callers need to tell "this region uses the default" from
32
+ * "this region names a mirror" — the difference between leaving a spawned
33
+ * pnpm's registry alone and setting it.
34
+ */
35
+ export declare const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
36
+ /** GitHub transports that fail independently on filtered networks. */
37
+ export type GithubService = 'git' | 'raw' | 'avatar';
38
+ /** A prefix proxy, or null for the canonical GitHub address. */
39
+ export type GithubRoute = string | null;
40
+ /** Ordered candidates per GitHub service. */
41
+ export type GithubRoutes = Record<GithubService, GithubRoute[]>;
42
+ /**
43
+ * One place the catalog can be read from.
44
+ *
45
+ * Two kinds because the two routes are genuinely different transports, not
46
+ * two URLs. The npm route reads a published package — which is what lets the
47
+ * catalog ride the same mirror as everything else, and gives it a version
48
+ * number that can be rolled back when a bad build ships.
49
+ */
50
+ export type CatalogSource = {
51
+ kind: 'url';
52
+ url: string;
53
+ } | {
54
+ kind: 'npm';
55
+ registry: string;
56
+ pkg: string;
57
+ };
58
+ /** Where one region sends each kind of request. `null` means "go direct". */
59
+ export interface RegionRoutes {
60
+ /** npm registry base, no trailing slash. */
61
+ npmRegistry: string;
62
+ /** Prefix proxy for github.com-family URLs, or null to go direct. */
63
+ githubProxy: string | null;
64
+ /** Ordered routes per service; every list ends in a direct escape path. */
65
+ githubRoutes: GithubRoutes;
66
+ /**
67
+ * Where to look for the catalog, in order. Later entries are fallbacks.
68
+ *
69
+ * The catalog is the FIRST request the market makes, so a mirror that has
70
+ * gone down must mean a slow market rather than an empty one — every
71
+ * region ends its list at an address that has always worked.
72
+ */
73
+ catalog: CatalogSource[];
74
+ }
75
+ /**
76
+ * Normalize a user-maintained prefix, or reject it.
77
+ *
78
+ * Public mirrors receive the complete destination URL in their path. Only
79
+ * HTTPS prefixes without embedded credentials or query fragments are safe to
80
+ * persist and show again in the settings UI.
81
+ */
82
+ export declare function normalizeGithubProxy(value: unknown): string | null;
83
+ /** Apply (or clear) the persisted UI escape route. */
84
+ export declare function setCustomGithubProxy(proxy: string | null): void;
85
+ /** Whether the operator-owned environment variable disables UI changes. */
86
+ export declare function githubProxyManaged(env?: NodeJS.ProcessEnv): boolean;
87
+ /** Remember one verified route without changing other GitHub services. */
88
+ export declare function rememberGithubRoute(service: GithubService, route: GithubRoute): void;
89
+ /** Forget learned winners after the configured candidates change. */
90
+ export declare function resetGithubRoutePreferences(): void;
91
+ /**
92
+ * The routes for a region, with environment overrides applied.
93
+ *
94
+ * Overrides win over the table because they are the user's statement about
95
+ * their own network, and they are the way out when a public proxy dies.
96
+ *
97
+ * `CHATCODE_CLI_MARKET_REGISTRY_URL` names the catalog URL. The former
98
+ * `DSHM_REGISTRY_URL` spelling keeps the same meaning as a compatibility
99
+ * input. When set, either form
100
+ * when set it REPLACES the source list rather than heading it: someone
101
+ * pointing the market at their own catalog does not want it quietly
102
+ * reverting to ours.
103
+ */
104
+ export declare function routesFor(region: Region, env?: NodeJS.ProcessEnv): RegionRoutes;
105
+ /** Ordered candidates with this process's last verified winner first. */
106
+ export declare function githubRoutesFor(service: GithubService, region?: Region, env?: NodeJS.ProcessEnv): GithubRoute[];
107
+ /** The region in force. */
108
+ export declare function activeRegion(): Region;
109
+ /** Set the region in force. Callers are responsible for their own caches. */
110
+ export declare function setActiveRegion(region: Region): void;
111
+ /**
112
+ * Wrap a github.com-family URL in a prefix proxy.
113
+ *
114
+ * The proxy takes the full absolute URL as its path (`{proxy}/{url}`) rather
115
+ * than a rewritten hostname, so the same joining rule works for every
116
+ * GitHub service a caller has explicitly allowed through public routes.
117
+ *
118
+ * @param proxy - the prefix, or null to go direct.
119
+ * @param url - an absolute https URL on a github.com-family host.
120
+ * @returns the proxied URL, or `url` unchanged when there is no proxy.
121
+ */
122
+ export declare function throughProxy(proxy: string | null, url: string): string;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Registry access: the curated list from awesome-dsh-plugin.com, fetched
3
+ * fresh on every request. See `loadRegistry` for why there is nothing
4
+ * behind it any more.
5
+ */
6
+ import { type Region } from './regions.ts';
7
+ export interface RegistryPlugin {
8
+ name: string;
9
+ owner: string;
10
+ url: string;
11
+ /** One legacy category id or several category ids. */
12
+ category: string | string[];
13
+ description: Record<string, string>;
14
+ npm?: string | null;
15
+ tarball?: string | null;
16
+ stars?: number | null;
17
+ /**
18
+ * npm downloads in the last 30 days, when the entry has a published
19
+ * package. `null`/absent means "no npm package" — a coverage gap, not a
20
+ * zero — so sorting must not read it as "less popular than 0".
21
+ */
22
+ downloads?: number | null;
23
+ install: string;
24
+ added: string;
25
+ /**
26
+ * Catalog-side deprecation flags (#60): supplied by awesome-dsh-plugin,
27
+ * absent for every normal entry — the market only consumes them, so a
28
+ * catalog without the fields behaves exactly as before.
29
+ */
30
+ deprecated?: boolean;
31
+ /** Catalog name of the suggested replacement plugin, when deprecated. */
32
+ replacement?: string;
33
+ }
34
+ /**
35
+ * Category ids for one catalog entry, de-duplicated in declaration order.
36
+ *
37
+ * Catalog JSON is an external input, so malformed array members are omitted
38
+ * here and an entry with no usable category is rejected by `asRegistry`.
39
+ */
40
+ export declare function pluginCategories(plugin: Pick<RegistryPlugin, 'category'>): string[];
41
+ export interface Registry {
42
+ updated: string;
43
+ count: number;
44
+ categories: Record<string, Record<string, string>>;
45
+ plugins: RegistryPlugin[];
46
+ }
47
+ /**
48
+ * Drop what we remember, so the next call is unconditional.
49
+ *
50
+ * Exists for tests: the memo is module state, and a spec that asserted a
51
+ * 304 would otherwise leak a validator into the next one.
52
+ */
53
+ export declare function forgetCatalog(): void;
54
+ /**
55
+ * The catalog, revalidated every time it is asked for.
56
+ *
57
+ * There used to be three answers here — live, a one-hour in-memory cache,
58
+ * and a snapshot bundled into the npm package — and only the first was
59
+ * correct. The other two were indistinguishable from it on screen, so a
60
+ * machine that could not reach the registry browsed the publish-time file
61
+ * (839 entries against 1367 live, and frozen forever for anyone on an older
62
+ * release), while a machine that COULD reach it still saw an hour-old
63
+ * listing of a catalog that grows by ~250 entries a day.
64
+ *
65
+ * For a catalog, stale is not a degraded answer, it is a wrong one: a plugin
66
+ * published this morning reads as "does not exist". So there is one source
67
+ * now, and a failure is a failure — the caller reports it and offers a
68
+ * retry, which is a state the user can act on. In particular a network
69
+ * failure is NEVER answered from `served`: an origin that cannot be reached
70
+ * has not confirmed anything, and quietly handing back the last catalog
71
+ * would rebuild exactly the fallback this replaced.
72
+ * @throws when the catalog cannot be fetched or does not look like one.
73
+ */
74
+ export declare function loadRegistry(region?: Region): Promise<Registry>;
75
+ /**
76
+ * A catalog failure with the facts needed to classify it, in the message
77
+ * itself.
78
+ *
79
+ * The market shows this string and the log export carries it, so it is the
80
+ * whole of what a bug report will contain. "The operation was aborted due to
81
+ * timeout" alone cannot distinguish a slow link from a blocked one from a
82
+ * proxy this process cannot use — and Node's `fetch` ignores HTTP_PROXY
83
+ * entirely (measured on Node 25), so a machine whose only route out is a
84
+ * proxy fails here every time while every other tool on it works.
85
+ */
86
+ export declare function describeFetchFailure(error: unknown, elapsedMs: number, attempts?: number): string;
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Self-restart: relaunch the exact ChatCode CLI invocation that booted this host so
3
+ * pending (non-hot) plugin changes take effect without the user leaving the
4
+ * UI. Contributed in #14 by @ysyyhhh; ported onto the layered architecture.
5
+ *
6
+ * Safety model: the endpoint accepts only direct same-origin loopback
7
+ * requests (no forwarding headers), refuses while a plugin operation runs,
8
+ * and deployments under a supervisor (systemd/launchd/pm2) can disable the
9
+ * whole feature with `allowRestart: false` — the supervisor owns restarts.
10
+ */
11
+ import type { IncomingMessage } from 'node:http';
12
+ /** @internal test hook — production callers use detectedDebugger() only. */
13
+ export declare function setDetectedDebuggerOverride(value: 'inspector' | null | undefined): void;
14
+ /**
15
+ * The process supervisor running this host, when one can be identified —
16
+ * `null` when nothing says so.
17
+ *
18
+ * This exists because the failure it prevents is the worst one the market
19
+ * can cause. Under systemd's default `KillMode=control-group`, everything in
20
+ * the unit's cgroup dies with the main process — including the detached
21
+ * helper that was supposed to bring the replacement up. So "restart" killed
22
+ * a production service and nothing came back (#229 by @SkillBase-Al: "杀死了
23
+ * 服务但是无法重复启动服务"). `allowRestart: false` was always the documented
24
+ * answer, but it is opt-in, and nothing told the operator to opt in until
25
+ * after they had already lost the service.
26
+ *
27
+ * TWO signals are required, and the second is the whole reason this function
28
+ * is not a one-line env check. `INVOCATION_ID` is INHERITED: every
29
+ * descendant of a systemd unit carries it, which on Linux includes an
30
+ * ordinary desktop terminal (its shell descends from a user-session unit)
31
+ * and a CI runner (the agent is a unit — this repo's own smoke test caught
32
+ * that). Treating inheritance as ownership would disable the button for a
33
+ * large population of hosts where it works fine, which is a worse bug than
34
+ * the one being fixed.
35
+ *
36
+ * The parent test is what distinguishes being the unit's own main process
37
+ * from merely descending from one: a terminal's node has the shell as its
38
+ * parent and a runner's has the agent. It used to be `ppid === 1`, which is
39
+ * only true for SYSTEM units. A per-user unit's service is forked by that
40
+ * user's manager instance — `systemd --user`, an ordinary PID — so user-unit
41
+ * hosts read as unsupervised, and one-click restart killed them for good
42
+ * (#471 by @automagik-genie, with the journal to prove the whole chain:
43
+ * clean SIGTERM exit → `Restart=on-failure` does not fire → `KillMode=mixed`
44
+ * SIGKILLs the detached helper before it can spawn the replacement). So the
45
+ * parent counts as a manager when it is PID 1 or its comm is `systemd` —
46
+ * the one name both manager instances share and the false-positive parents
47
+ * (shells, CI agents) never carry. `/proc` is Linux-only, which is exactly
48
+ * as wide as systemd itself; anywhere it cannot be read the answer stays
49
+ * "not a manager".
50
+ *
51
+ * Scoped to systemd on purpose. pm2 sets `pm_id`, but it is inherited the
52
+ * same way and pm2's God daemon — not PID 1 — is the parent, so there is no
53
+ * equivalent second signal; a guess there would reintroduce exactly the
54
+ * false positive this pair exists to avoid. launchd has no marker at all.
55
+ * Both still need the explicit setting: detection is a safety net over the
56
+ * documented option, never a replacement for it.
57
+ */
58
+ export declare function detectedSupervisor(env?: NodeJS.ProcessEnv, ppid?: number, parentComm?: (pid: number) => string | null): string | null;
59
+ /**
60
+ * Whether this host process is under a debugger, when one can be identified —
61
+ * `'inspector'` or `null`.
62
+ *
63
+ * Parallel to `detectedSupervisor()` (#229): a runtime latch for the restart
64
+ * route and status poll, NOT an entry in `restartAllowed()`. Folding it into
65
+ * `restartAllowed()` would write `allowRestart: false` through the settings
66
+ * page while a debug session is open, and the switch would stay off after
67
+ * the debugger detaches (#447).
68
+ *
69
+ * An explicit `allowRestart: true` does NOT override this latch either —
70
+ * unlike systemd, there is no documented deployment shape where killing a
71
+ * debug-attached host from the market UI is the right answer.
72
+ *
73
+ * Primary signal: `inspector.url()` is set (covers `--inspect` at boot,
74
+ * `inspector.open()`, and SIGUSR1 attach). Secondary: inspect-family flags
75
+ * in `execArgv` and `NODE_OPTIONS`, matched by token prefix — not a
76
+ * `/inspect/` substring, so script paths like `.../inspect-tool.js` do not
77
+ * false-positive. Includes `--inspect-wait` for Node versions that expose it
78
+ * as a distinct flag before `inspector.url()` is populated.
79
+ *
80
+ * Accepted false positive (same trade as #229 not guessing pm2): a host left
81
+ * listening on an inspector port — including `NODE_OPTIONS=--inspect` with
82
+ * nobody attached — is treated as debug-owned and gets no one-click restart.
83
+ */
84
+ export declare function detectedDebugger(inspectorUrl?: string | undefined, execArgv?: readonly string[], nodeOptions?: string): 'inspector' | null;
85
+ /**
86
+ * Self-restart is enabled by default, disabled by an explicit false — and
87
+ * disabled by DEFAULT under a detected supervisor, which owns restarts and
88
+ * whose process group would take the replacement helper down with it.
89
+ *
90
+ * An explicit `true` still wins: an operator who has configured their unit
91
+ * for it (`KillMode=process`, or a wrapper that survives) is making a
92
+ * statement about their own deployment, and this should not overrule it.
93
+ */
94
+ export declare function restartAllowed(config: {
95
+ allowRestart?: boolean;
96
+ }, env?: NodeJS.ProcessEnv, ppid?: number): boolean;
97
+ /**
98
+ * The port this process is serving on, read off the request that asked for
99
+ * the restart.
100
+ *
101
+ * The alternative is to parse it out of the launch argv, which is wrong for
102
+ * every host that binds from config or an env var. The Host header is what
103
+ * the browser actually reached us on, so it is the port the replacement has
104
+ * to take over — and it is already validated against Origin by the guard
105
+ * below before any of this runs.
106
+ * @returns the port, or null when the header carries none (a default port).
107
+ */
108
+ export declare function servingPort(request: Pick<IncomingMessage, 'headers'>): number | null;
109
+ /** Whether a process-control request came from this Web host on loopback. */
110
+ export declare function trustedRestartRequest(request: Pick<IncomingMessage, 'headers' | 'socket'>): boolean;
111
+ /**
112
+ * Whether a download navigation may fetch a sensitive GET export.
113
+ * Browsers do NOT send an Origin header on same-origin GET navigations
114
+ * (`<a href="/..." download>`), so unlike process-control requests a missing
115
+ * Origin is the NORMAL shape of a user-initiated download and must pass.
116
+ * Keep the rest of the posture: loopback peer only, no proxy forwarding
117
+ * headers, and — when an Origin IS present (fetch/CORS attempts) — it must
118
+ * still match Host so a cross-origin page cannot read the export.
119
+ */
120
+ export declare function trustedDownloadRequest(request: Pick<IncomingMessage, 'headers' | 'socket'>): boolean;
121
+ /** The exact boot invocation the detached restart helper replays. */
122
+ export declare function restartLaunch(): {
123
+ file: string;
124
+ args: string[];
125
+ cwd: string;
126
+ viaShell: boolean;
127
+ };
128
+ /**
129
+ * Platform-correct spawn invocation for the replacement host (#40 by
130
+ * @1123762794): on Windows a `detached` spawn maps to DETACHED_PROCESS — the
131
+ * new host gets NO console, and every console child it later spawns (e.g.
132
+ * ChatCode CLI sandbox tool runners) pops a visible node window. Wrapping the launch
133
+ * in `powershell -WindowStyle Hidden` gives the host a HIDDEN console that
134
+ * children inherit instead. POSIX keeps the plain detached spawn.
135
+ */
136
+ export declare function respawnInvocation(launch: {
137
+ file: string;
138
+ args: string[];
139
+ viaShell: boolean;
140
+ }, platform?: NodeJS.Platform): {
141
+ file: string;
142
+ args: string[];
143
+ viaShell: boolean;
144
+ detached: boolean;
145
+ };
146
+ /** What scheduleRestart reports back to the caller for logging/response. */
147
+ export interface RestartResult {
148
+ pid: number;
149
+ helperPid: number | undefined;
150
+ logOut: string;
151
+ logErr: string;
152
+ }
153
+ /**
154
+ * Source for the detached helper that outlives this process and brings the
155
+ * replacement up.
156
+ *
157
+ * Extracted so the waiting can be tested by RUNNING it, which is the only
158
+ * way this class of bug shows itself: every part of the old helper looked
159
+ * right in isolation.
160
+ *
161
+ * What it fixes (#177, reported on Windows 11, reproducible every time): the
162
+ * helper slept a flat 1500ms and spawned. The old process had exited, but
163
+ * the listening socket had not been released yet, so the replacement died
164
+ * instantly with EADDRINUSE — and the spawn was wrapped in `catch {}`, so
165
+ * nothing was written anywhere. The user saw a restart button that did
166
+ * nothing. The docstring above it even claimed the helper "waits for our
167
+ * port to free up"; it never did.
168
+ *
169
+ * So: wait for the port to actually go quiet, then start, then CHECK that
170
+ * something came up, and write a diagnosis when it did not. A restart that
171
+ * fails must leave evidence — this one is invisible by construction, since
172
+ * the process that would have logged it is the one that just exited.
173
+ * @param port - the port the replacement must bind; when unknown, the helper
174
+ * falls back to the old fixed delay, which is better than nothing.
175
+ */
176
+ export declare function restartHelperSource(spawned: {
177
+ file: string;
178
+ args: string[];
179
+ viaShell: boolean;
180
+ detached: boolean;
181
+ }, launch: {
182
+ cwd: string;
183
+ }, logs: {
184
+ out: string;
185
+ err: string;
186
+ }, port: number | null): string;
187
+ /**
188
+ * Relaunch this exact ChatCode CLI entry after a detached handoff, then stop this
189
+ * process. The helper outlives us (detached + unref), waits for our port to
190
+ * be released before starting the replacement, and logs under tmpdir.
191
+ * @param port - the port this process is serving on, so the helper can wait
192
+ * for it rather than guessing at a delay.
193
+ */
194
+ export declare function scheduleRestart(port?: number | null): RestartResult;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * HTTP routes bridging the browser market UI to the host. This layer only
3
+ * parses requests, calls the service modules, and serializes responses —
4
+ * process spawning lives in dsh-cli.ts, filesystem reads in profile.ts,
5
+ * orchestration in install.ts / themes.ts / updates.ts.
6
+ *
7
+ * Security: the install route executes a shell command, so it accepts only
8
+ * same-origin POSTs and only sources present in the curated registry.
9
+ */
10
+ import type { IncomingMessage, ServerResponse } from 'node:http';
11
+ import { type PluginCommandRuntime } from './dsh-cli.ts';
12
+ import { type AgentsLookup } from './agents.ts';
13
+ import { type Channel } from './channels.ts';
14
+ import { type Region } from './regions.ts';
15
+ import { type LoaderEntry } from './themes.ts';
16
+ export type { LoaderEntry } from './themes.ts';
17
+ export type { UpdateStatus } from './updates.ts';
18
+ export interface WebServerService {
19
+ register(route: {
20
+ kind: 'exact' | 'prefix';
21
+ path: string;
22
+ handler: (request: IncomingMessage, response: ServerResponse) => void | Promise<void>;
23
+ }): () => void;
24
+ }
25
+ export interface MarketHost {
26
+ webServer: WebServerService;
27
+ loader: {
28
+ entries(): Iterable<LoaderEntry>;
29
+ };
30
+ plugin(plugin: unknown, config: unknown): {
31
+ await(): Promise<unknown>;
32
+ dispose(): Promise<unknown> | void;
33
+ };
34
+ on?(event: string, callback: (fiber: {
35
+ entry?: {
36
+ options?: {
37
+ name?: string;
38
+ };
39
+ };
40
+ }) => void): () => void;
41
+ logger?: {
42
+ info?(message: string): void;
43
+ warn(message: string): void;
44
+ };
45
+ }
46
+ export interface MarketConfig {
47
+ /** Profile the market installs into; matches the profile serving this UI. */
48
+ profile: string;
49
+ /** Host-authoritative profile directory; ordinary DSH derives it from DSH_HOME. */
50
+ profileDirectory?: string;
51
+ /** Detached self-restart is unsafe under systemd/launchd/pm2; operators can disable it (#14). */
52
+ allowRestart?: boolean;
53
+ /** Which release channel the market offers ITSELF from; other plugins never follow it. */
54
+ channel?: Channel;
55
+ /** Which mirrors every outbound request uses; undefined until decided. */
56
+ region?: Region;
57
+ /** Snapshots retained per profile (issue #98); defaults to DEFAULT_MAX_SNAPSHOTS. */
58
+ maxSnapshots?: number;
59
+ }
60
+ export declare function marketVersion(): string;
61
+ /**
62
+ * Register the market's HTTP routes.
63
+ * @param host - Acquired webServer + shell services.
64
+ * @param config - Validated market configuration.
65
+ * @returns Disposer removing every registered route.
66
+ */
67
+ export declare function mountMarketRoutes(host: MarketHost, config: MarketConfig, commandRuntime?: PluginCommandRuntime, agentsLookup?: AgentsLookup): () => void;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The market's own settings namespace: the half that makes `allowRestart`
3
+ * a switch on the plugin configuration page instead of a line the user has
4
+ * to hand-write into cordis.yml.
5
+ *
6
+ * `allowRestart: false` is the documented answer for a host owned by
7
+ * systemd, launchd or pm2 — a supervisor restarts it, so the market's
8
+ * one-click restart must not launch a second one. Until now the only way to
9
+ * say that was editing YAML in the right place with the right indentation,
10
+ * where a stray space stops the profile booting.
11
+ *
12
+ * Only `allowRestart` is exposed. `profile` names which profile this
13
+ * instance manages: it is decided at mount from the composition or the
14
+ * command line, and a running instance cannot switch to another one, so
15
+ * offering it as a field would promise something the write cannot deliver.
16
+ *
17
+ * The release channel is NOT here either, and that is a correction rather
18
+ * than an omission. It was, briefly, and it made this namespace a second
19
+ * writer for a value the market already stores in its own state.json: the
20
+ * mount read the user's saved channel off disk, then `onChange` assigned
21
+ * `source().channel` — which knows nothing about that file — straight back
22
+ * over it. The choice survived exactly until the next settings event.
23
+ *
24
+ * Only a real host could show that; the unit lane mounts the routes without
25
+ * this layer at all. `allowRestart` needs this door because its only other
26
+ * one is hand-edited YAML. The channel has a control of its own on the
27
+ * plugin configuration page, so a second door bought nothing and cost the
28
+ * setting its memory.
29
+ *
30
+ * The wiring rides the scoped fiber, so a host with no settings service —
31
+ * every dsh before 0.1.0-rc.7 — simply never runs any of this and the entry
32
+ * configuration stands as composed. That is why this needs no version check
33
+ * of its own.
34
+ *
35
+ * It depends on the SERVICE and nothing else, which is the whole point of
36
+ * the shape below. This module used to import two convenience helpers,
37
+ * `installSettingsSection` and `settingsNamespace`, from
38
+ * `@deepseek-ai/dsh-settings`. dsh 0.1.2-alpha.1 deleted both — and a
39
+ * missing NAMED EXPORT is not a missing service. `ctx.inject` degrades
40
+ * quietly; an ESM named import that resolves to nothing is a SyntaxError at
41
+ * module evaluation, which cordis's loader reports as a failed entry and the
42
+ * host exits 1. Installing the market stopped the host from booting at all:
43
+ *
44
+ * SyntaxError: The requested module '@deepseek-ai/dsh-settings' does not
45
+ * provide an export named 'installSettingsSection'
46
+ *
47
+ * The service itself never changed — `sctx.settings.register(ns, schema,
48
+ * { base })` is identical in 0.1.0-rc.7 and 0.1.2-alpha.2. Only the two
49
+ * wrappers went away. So this inlines what the wrapper did (verified against
50
+ * its source: an inject, a register, a watch, and an unload effect) and
51
+ * validates the namespace here. Nothing about the graceful-degradation story
52
+ * changes; it just stops being conditional on an export that upstream is
53
+ * free to move.
54
+ */
55
+ import type { Context } from '@deepseek-ai/cordis';
56
+ import z from '@deepseek-ai/schemastery';
57
+ /** Namespace the card on the browser side keys itself to. */
58
+ export declare const MARKET_SETTINGS_NS = "dsh-market";
59
+ /** The market settings a user may edit at runtime. */
60
+ export interface MarketSettings {
61
+ allowRestart: boolean;
62
+ }
63
+ export declare const MarketSettings: z<MarketSettings>;
64
+ /**
65
+ * Wire the namespace so a saved change reaches the routes immediately.
66
+ *
67
+ * The routes read `allowRestart` off this object on every request (the
68
+ * status route reports the capability, the restart route enforces it), so
69
+ * updating it in place is what makes a toggle take effect without a
70
+ * restart — which would be a poor thing to require of a setting whose whole
71
+ * subject is restarting.
72
+ *
73
+ * @param ctx - the plugin context owning the wiring.
74
+ * @param resolved - the live config object the routes read.
75
+ */
76
+ export declare function installMarketSettings(ctx: Context, resolved: {
77
+ allowRestart?: boolean;
78
+ }): void;
@@ -0,0 +1,10 @@
1
+ import type { ResolvedSkillMarketConfig, SkillListQuery, SkillListResult } from './types.ts';
2
+ /** Client for the public skill catalog and archive endpoints. */
3
+ export declare class SkillMarketApi {
4
+ private readonly config;
5
+ constructor(config: ResolvedSkillMarketConfig);
6
+ /** Fetch one validated catalog page. */
7
+ list(query?: SkillListQuery, signal?: AbortSignal): Promise<SkillListResult>;
8
+ /** Download a skill archive; the caller consumes and size-caps the body. */
9
+ download(id: number, signal?: AbortSignal): Promise<Response>;
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { ResolvedSkillMarketConfig, SkillMarketConfig } from './types.ts';
2
+ /** Resolve and validate deployment-varying skill-market settings. */
3
+ export declare function resolveSkillMarketConfig(input: SkillMarketConfig | undefined): ResolvedSkillMarketConfig;
@@ -0,0 +1,23 @@
1
+ import type { ResolvedSkillMarketConfig, InstalledSkill, SkillInstallLocation, SkillInstallResult } from './types.ts';
2
+ import type { SkillMarketApi } from './api.ts';
3
+ export declare const SKILL_MARKET_META_FILE = ".skill-market.json";
4
+ /** Installs validated catalog archives into ChatCode CLI skill roots. */
5
+ export declare class SkillMarketInstaller {
6
+ private readonly api;
7
+ private readonly config;
8
+ constructor(api: SkillMarketApi, config: ResolvedSkillMarketConfig);
9
+ /** Install or update one catalog skill for a local session workspace. */
10
+ install(options: {
11
+ readonly id: number;
12
+ readonly name: string;
13
+ readonly version?: string;
14
+ readonly location: SkillInstallLocation;
15
+ readonly cwd: string;
16
+ readonly signal?: AbortSignal;
17
+ }): Promise<SkillInstallResult>;
18
+ private downloadArchive;
19
+ /** List market-managed skills visible from a local session workspace. */
20
+ installed(cwd: string): Promise<readonly InstalledSkill[]>;
21
+ }
22
+ /** Resolve the two skill roots visible to a session workspace. */
23
+ export declare function resolveSkillRoots(cwd: string): Promise<Record<SkillInstallLocation, string>>;
@@ -0,0 +1,10 @@
1
+ import { SkillMarketApi } from './api.ts';
2
+ import { SkillMarketInstaller } from './installer.ts';
3
+ import type { ResolvedSkillMarketConfig } from './types.ts';
4
+ /** Shared skill-market operations consumed by Web routes and the terminal scene. */
5
+ export declare class SkillMarketRuntime {
6
+ readonly config: ResolvedSkillMarketConfig;
7
+ readonly api: SkillMarketApi;
8
+ readonly installer: SkillMarketInstaller;
9
+ constructor(config: ResolvedSkillMarketConfig);
10
+ }
@@ -0,0 +1,4 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { SkillMarketRuntime } from './runtime.ts';
3
+ /** Register `/skill-market` and its full-screen terminal scene together. */
4
+ export declare function installSkillMarketTui(ctx: Context, runtime: SkillMarketRuntime): void;