@dsh-plugin/dsh-loader 1.0.0 → 1.1.0-dev.32278873528

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 (73) hide show
  1. package/README.md +251 -226
  2. package/{README.zh-CN.md → README.zh_CN.md} +227 -202
  3. package/bin/dshloader.mjs +42 -42
  4. package/cordis.patch.yml +8 -8
  5. package/dist/adapters/dsh-1-x.d.ts +23 -0
  6. package/dist/adapters/dsh-1-x.js +223 -0
  7. package/dist/adapters/dsh-1-x.js.map +1 -0
  8. package/dist/adapters/index.d.ts +22 -0
  9. package/dist/adapters/index.js +57 -0
  10. package/dist/adapters/index.js.map +1 -0
  11. package/dist/api.d.ts +13 -0
  12. package/dist/api.js +46 -0
  13. package/dist/api.js.map +1 -0
  14. package/dist/client.d.ts +157 -0
  15. package/dist/client.js +295 -0
  16. package/dist/client.js.map +1 -0
  17. package/dist/index.d.ts +52 -0
  18. package/dist/index.js +89 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/registry.d.ts +35 -0
  21. package/dist/registry.js +229 -0
  22. package/dist/registry.js.map +1 -0
  23. package/dist/services/services.d.ts +4 -0
  24. package/dist/services/services.js +28 -0
  25. package/dist/services/services.js.map +1 -0
  26. package/dist/services/settings.d.ts +41 -0
  27. package/dist/services/settings.js +161 -0
  28. package/dist/services/settings.js.map +1 -0
  29. package/dist/services/web.d.ts +10 -0
  30. package/dist/services/web.js +63 -0
  31. package/dist/services/web.js.map +1 -0
  32. package/dist/setup.d.ts +32 -0
  33. package/dist/setup.js +119 -0
  34. package/dist/setup.js.map +1 -0
  35. package/dist/types.d.ts +111 -0
  36. package/dist/types.js +12 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/version.d.ts +2 -0
  39. package/{src → dist}/version.js +5 -5
  40. package/dist/version.js.map +1 -0
  41. package/lib/client.js +283 -0
  42. package/lib/client.js.map +1 -0
  43. package/package.json +74 -59
  44. package/src/stable/agent.d.ts +1 -1
  45. package/src/stable/agent.js +2 -2
  46. package/src/stable/llm.d.ts +1 -1
  47. package/src/stable/llm.js +2 -2
  48. package/src/stable/runtime.d.ts +1 -1
  49. package/src/stable/runtime.js +5 -5
  50. package/src/stable/schema-form.d.ts +1 -1
  51. package/src/stable/schema-form.js +2 -2
  52. package/src/stable/settings.d.ts +1 -1
  53. package/src/stable/settings.js +2 -2
  54. package/src/stable/tools.d.ts +1 -1
  55. package/src/stable/tools.js +5 -5
  56. package/src/stable/ui-primitives.d.ts +4 -4
  57. package/src/stable/ui-primitives.js +6 -6
  58. package/src/stable/ui-settings.d.ts +6 -6
  59. package/src/stable/ui-settings.js +4 -4
  60. package/src/stable/ui-slots.d.ts +1 -1
  61. package/src/stable/ui-slots.js +2 -2
  62. package/src/stable/web-react.d.ts +1 -1
  63. package/src/stable/web-react.js +2 -2
  64. package/src/adapters/dsh-1-x.js +0 -236
  65. package/src/adapters/index.js +0 -59
  66. package/src/api.js +0 -56
  67. package/src/client.js +0 -330
  68. package/src/index.js +0 -105
  69. package/src/registry.js +0 -242
  70. package/src/services/services.js +0 -28
  71. package/src/services/settings.js +0 -174
  72. package/src/services/web.js +0 -71
  73. package/src/setup.mjs +0 -125
package/README.md CHANGED
@@ -1,226 +1,251 @@
1
- # dshloader
2
-
3
- [English](#english) | [中文](README.zh-CN.md)
4
-
5
- ---
6
-
7
- ## English
8
-
9
- A runtime compatibility shim for **dsh** (DeepSeek Harness) cordis bundle
10
- plugins. dshloader decouples third-party plugins from dsh's internal service
11
- names, module paths, package names, and RPC details through a version-aware
12
- **adapter registry**, so that when dsh upgrades and breaks internal APIs, you
13
- only upgrade dshloader — plugins keep working unchanged.
14
-
15
- ### Why
16
-
17
- dsh is moving fast and its internal surface changes between releases:
18
-
19
- - `httpServer` was renamed to `webServer` — old plugins that inject
20
- `httpServer` hang forever.
21
- - Deep source imports like
22
- `@deepseek-ai/dsh-client-runtime/src/client/sessions/context-provenance.ts`
23
- break when dsh ships no `src/`.
24
- - Client UI packages like `@deepseek-ai/dsh-client-ui-primitives` could be
25
- renamed in future dsh versions, breaking every plugin that imports them
26
- directly.
27
- - The official `dsh-host-apiproxy` hardcodes a settings namespace whitelist,
28
- so third-party settings cards never appear in the Web UI.
29
-
30
- dshloader absorbs these (and future) breaks behind a **stable API**:
31
- `ctx.dshLoader` on the host, `window.__dshLoader__` in the browser, and
32
- `@dsh-plugin/dsh-loader/*` stable subpaths for package imports.
33
-
34
- ### Quick start
35
-
36
- #### 1. Install dshloader into a profile
37
-
38
- ```sh
39
- dsh plugin --profile <name> add /path/to/dshloader
40
- # or
41
- DSH_HOME=~/.dsh npx dshloader setup <name>
42
- ```
43
-
44
- #### 2. Plugin `package.json` only depend on dshloader
45
-
46
- ```json
47
- {
48
- "dependencies": {
49
- "@dsh-plugin/dsh-loader": "link:..."
50
- }
51
- }
52
- ```
53
-
54
- > **Plugins must NOT declare any `@deepseek-ai/*` dependency.** All dsh
55
- > packages are accessed through dshloader's stable subpaths.
56
-
57
- #### 3. Host side — use `ctx.dshLoader`
58
-
59
- ```js
60
- export const inject = ['dshLoader'];
61
-
62
- export async function apply(ctx) {
63
- // Settings: register a namespace
64
- const scope = ctx.dshLoader.settings.register('my-plugin', schema);
65
-
66
- // Web: register routes and WebSocket upgrades
67
- ctx.dshLoader.web.get('/api/my-plugin/status', (req, res) => res.json({ ok: true }));
68
- ctx.dshLoader.web.registerUpgrade({ path: '/ws/my-plugin', handler: fn });
69
-
70
- // Services: read cordis services
71
- const sessions = ctx.dshLoader.services.get('sessions');
72
- }
73
- ```
74
-
75
- #### 4. Import dsh packages via stable subpaths
76
-
77
- ```js
78
- // Host packages
79
- const { defineTool } = require('@dsh-plugin/dsh-loader/tools');
80
-
81
- // Client UI packages (in client bundle source)
82
- import { IconCloseFill14 } from '@dsh-plugin/dsh-loader/ui-primitives';
83
- ```
84
-
85
- **Stable subpath real dsh package mapping (dsh 1.x):**
86
-
87
- | Stable subpath | Real dsh package |
88
- |---|---|
89
- | `@dsh-plugin/dsh-loader/tools` | `@deepseek-ai/dsh-tools` |
90
- | `@dsh-plugin/dsh-loader/llm` | `@deepseek-ai/dsh-llm` |
91
- | `@dsh-plugin/dsh-loader/agent` | `@deepseek-ai/dsh-agent` |
92
- | `@dsh-plugin/dsh-loader/settings` | `@deepseek-ai/dsh-settings` |
93
- | `@dsh-plugin/dsh-loader/ui-primitives` | `@deepseek-ai/dsh-client-ui-primitives` |
94
- | `@dsh-plugin/dsh-loader/ui-slots` | `@deepseek-ai/dsh-client-ui-slots` |
95
- | `@dsh-plugin/dsh-loader/ui-settings` | `@deepseek-ai/dsh-client-ui-settings/client` |
96
- | `@dsh-plugin/dsh-loader/web-react` | `@deepseek-ai/dsh-client-web-react` |
97
- | `@dsh-plugin/dsh-loader/schema-form` | `@deepseek-ai/dsh-client-schema-form` |
98
- | `@dsh-plugin/dsh-loader/runtime` | `@deepseek-ai/dsh-client-runtime/client` |
99
-
100
- When dsh renames a package, only the dshloader adapter changes — plugin
101
- source and bundle stay the same.
102
-
103
- #### 5. Client side — use `window.__dshLoader__`
104
-
105
- ```js
106
- // Read cordis client services
107
- const conv = window.__dshLoader__.services.get('conversation');
108
-
109
- // Register a package alias at runtime (fallback)
110
- window.__dshLoader__.registerPackageAlias('@old/pkg', '@new/pkg');
111
- ```
112
-
113
- #### 6. Build config — mark stable subpaths as external
114
-
115
- ```ts
116
- const CLIENT_EXTERNALS = [
117
- 'react', 'react/jsx-runtime', 'react-dom', 'react-dom/client', 'cordis',
118
- '@dsh-plugin/dsh-loader/ui-primitives',
119
- '@dsh-plugin/dsh-loader/ui-slots',
120
- '@dsh-plugin/dsh-loader/ui-settings',
121
- '@dsh-plugin/dsh-loader/web-react',
122
- '@dsh-plugin/dsh-loader/schema-form',
123
- '@dsh-plugin/dsh-loader/runtime',
124
- ]
125
- ```
126
-
127
- ### How it works
128
-
129
- ```
130
- plugin ──▶ ctx.dshLoader.{settings,web,services} ──▶ dshloader adapter
131
-
132
-
133
- real dsh (current version)
134
-
135
- plugin bundle ──▶ require('@dsh-plugin/dsh-loader/ui-primitives')
136
-
137
- ▼ (__ModuleLoader__ wrapper maps stable name)
138
- require('@deepseek-ai/dsh-client-ui-primitives')
139
-
140
-
141
- dsh module table
142
- ```
143
-
144
- 1. **Version detection** reads `node_modules/@deepseek-ai/dsh/package.json`
145
- (or `DSHLOADER_DSH_VERSION` for tests/override).
146
- 2. **AdapterRegistry** selects the best adapter for the detected version
147
- (exact range nearest-low fallback → clear error).
148
- 3. The selected **adapter** registers service aliases, installs package-name
149
- mapping hooks (host: `Module._resolveFilename`; client:
150
- `__ModuleLoader__.load` wrapper), and (only when opted in) the settings
151
- whitelist bypass bridge. All registrations use `ctx.reflect.provide` /
152
- `ctx.effect`, so cordis auto-recycles them on fiber unload.
153
-
154
- > **Load order does not matter.** cordis is reactive dependency injection:
155
- > plugins declaring `inject: [...]` stay `PENDING` until the alias is
156
- > provided, regardless of where dshloader sits in `cordis.patch.yml`.
157
-
158
- ### Settings whitelist bypass (`exposeAllNamespaces`)
159
-
160
- By default dshloader **does not** bypass the official settings namespace
161
- whitelist. Opt in explicitly:
162
-
163
- - env: `DSHLOADER_EXPOSE_ALL_SETTINGS=1`
164
- - profile `package.json`: `dsh.dshloader.exposeAllNamespaces: true`
165
-
166
- > **Security trade-off**: enabling this removes the official default-deny
167
- > boundary for browser settings access. Only enable it in profiles where you
168
- > trust every installed plugin.
169
-
170
- ### CLI
171
-
172
- ```
173
- dshloader setup <profile> Inject dshloader into a profile (dep + patch).
174
- dshloader dump-config <profile> Run `dsh --profile <name> --dump-config`.
175
- dshloader info [profile] Print loader version, detected dsh version,
176
- selected adapter.
177
- ```
178
-
179
- ### Rollback / disable
180
-
181
- - Disable per launch: `DSHLOADER_DISABLE=1 dsh web`
182
- - Remove: `dsh plugin --profile <name> rm @dsh-plugin/dsh-loader`
183
-
184
- ### Project layout
185
-
186
- ```
187
- src/
188
- index.js host bundle entry (name / inject / apply)
189
- client.js client bundle entry (immediately tier)
190
- api.js DshLoaderHostAPI construction
191
- registry.js AdapterRegistry + version detection
192
- version.js loader version + log prefix
193
- stable/ stable subpath re-exports (ui-primitives, tools, ...)
194
- services/
195
- settings.js settings stable API
196
- web.js web stable API
197
- services.js services stable API (get / alias)
198
- adapters/
199
- dsh-1-x.js dsh 1.x adapter
200
- index.js adapter registration
201
- setup.mjs profile injection + dump-config + info
202
- bin/dshloader.mjs CLI entry
203
- docs/
204
- api.md full API reference (Chinese)
205
- design.md design document (Chinese)
206
- tests/ L1 (unit) / module (L2) / integration (L3)
207
- examples/
208
- sample-plugin/ minimal example plugin
209
- dsh-aux-state/ example using ctx.dshLoader only
210
- ```
211
-
212
- ### Develop
213
-
214
- ```sh
215
- pnpm install
216
- npm test # all tests
217
- npm run test:l1 # unit
218
- npm run test:l2 # module
219
- npm run test:l3 # integration
220
- ```
221
-
222
- Node.js >= 18, `node --test`, no extra test framework.
223
-
224
- ### License
225
-
226
- BSD-3-Clause
1
+ <div align="center">
2
+
3
+ ![Banner](./docs/banner.png)
4
+
5
+ # dshloader
6
+
7
+ **A version-aware runtime compatibility shim that keeps third-party plugins working unchanged across dsh (DeepSeek Harness) upgrades.**
8
+
9
+ [English](#english) | [简体中文](README.zh_CN.md)
10
+
11
+ [![DSH Plugin](https://img.shields.io/badge/DeepSeek%20Harness-plugin-4f7cff)](https://github.com/topics/dsh-plugin)
12
+ <a href="https://github.com/dsh-plugins/dsh-loader/actions/workflows/npm-publish.yml">
13
+ <img src="https://github.com/dsh-plugins/dsh-loader/actions/workflows/npm-publish.yml/badge.svg" alt="Build Status">
14
+ </a>
15
+ <a href="https://www.npmjs.com/package/@dsh-plugin/dsh-loader">
16
+ <img src="https://img.shields.io/npm/v/@dsh-plugin/dsh-loader.svg?sanitize=true" alt="Version">
17
+ </a>
18
+ <a href="https://www.npmjs.com/package/@dsh-plugin/dsh-loader">
19
+ <img src="https://img.shields.io/npm/l/@dsh-plugin/dsh-loader.svg?sanitize=true" alt="License">
20
+ </a>
21
+
22
+ </div>
23
+
24
+ ## English
25
+
26
+ A runtime compatibility shim for **dsh** (DeepSeek Harness) cordis bundle
27
+ plugins. dshloader decouples third-party plugins from dsh's internal service
28
+ names, module paths, package names, and RPC details through a version-aware
29
+ **adapter registry**, so that when dsh upgrades and breaks internal APIs, you
30
+ only upgrade dshloader plugins keep working unchanged.
31
+
32
+ ### Why
33
+
34
+ dsh is moving fast and its internal surface changes between releases:
35
+
36
+ - `httpServer` was renamed to `webServer` — old plugins that inject
37
+ `httpServer` hang forever.
38
+ - Deep source imports like
39
+ `@deepseek-ai/dsh-client-runtime/src/client/sessions/context-provenance.ts`
40
+ break when dsh ships no `src/`.
41
+ - Client UI packages like `@deepseek-ai/dsh-client-ui-primitives` could be
42
+ renamed in future dsh versions, breaking every plugin that imports them
43
+ directly.
44
+ - The official `dsh-host-apiproxy` hardcodes a settings namespace whitelist,
45
+ so third-party settings cards never appear in the Web UI.
46
+
47
+ dshloader absorbs these (and future) breaks behind a **stable API**:
48
+ `ctx.dshLoader` on the host, `window.__dshLoader__` in the browser, and
49
+ `@dsh-plugin/dsh-loader/*` stable subpaths for package imports.
50
+
51
+ ### Quick start
52
+
53
+ #### 1. Install dshloader into a profile
54
+
55
+ ```sh
56
+ dsh plugin --profile <name> add /path/to/dshloader
57
+ # or
58
+ DSH_HOME=~/.dsh npx dshloader setup <name>
59
+ ```
60
+
61
+ #### 2. Plugin `package.json` — only depend on dshloader
62
+
63
+ ```json
64
+ {
65
+ "dependencies": {
66
+ "@dsh-plugin/dsh-loader": "link:..."
67
+ }
68
+ }
69
+ ```
70
+
71
+ > **Plugins must NOT declare any `@deepseek-ai/*` dependency.** All dsh
72
+ > packages are accessed through dshloader's stable subpaths.
73
+
74
+ #### 3. Host side — use `ctx.dshLoader`
75
+
76
+ ```js
77
+ export const inject = ['dshLoader'];
78
+
79
+ export async function apply(ctx) {
80
+ // Settings: register a namespace
81
+ const scope = ctx.dshLoader.settings.register('my-plugin', schema);
82
+
83
+ // Web: register routes and WebSocket upgrades
84
+ ctx.dshLoader.web.get('/api/my-plugin/status', (req, res) => res.json({ ok: true }));
85
+ ctx.dshLoader.web.registerUpgrade({ path: '/ws/my-plugin', handler: fn });
86
+
87
+ // Services: read cordis services
88
+ const sessions = ctx.dshLoader.services.get('sessions');
89
+ }
90
+ ```
91
+
92
+ #### 4. Import dsh packages via stable subpaths
93
+
94
+ ```js
95
+ // Host packages
96
+ const { defineTool } = require('@dsh-plugin/dsh-loader/tools');
97
+
98
+ // Client UI packages (in client bundle source)
99
+ import { IconCloseFill14 } from '@dsh-plugin/dsh-loader/ui-primitives';
100
+ ```
101
+
102
+ **Stable subpath → real dsh package mapping (dsh 1.x):**
103
+
104
+ | Stable subpath | Real dsh package |
105
+ |---|---|
106
+ | `@dsh-plugin/dsh-loader/tools` | `@deepseek-ai/dsh-tools` |
107
+ | `@dsh-plugin/dsh-loader/llm` | `@deepseek-ai/dsh-llm` |
108
+ | `@dsh-plugin/dsh-loader/agent` | `@deepseek-ai/dsh-agent` |
109
+ | `@dsh-plugin/dsh-loader/settings` | `@deepseek-ai/dsh-settings` |
110
+ | `@dsh-plugin/dsh-loader/ui-primitives` | `@deepseek-ai/dsh-client-ui-primitives` |
111
+ | `@dsh-plugin/dsh-loader/ui-slots` | `@deepseek-ai/dsh-client-ui-slots` |
112
+ | `@dsh-plugin/dsh-loader/ui-settings` | `@deepseek-ai/dsh-client-ui-settings/client` |
113
+ | `@dsh-plugin/dsh-loader/web-react` | `@deepseek-ai/dsh-client-web-react` |
114
+ | `@dsh-plugin/dsh-loader/schema-form` | `@deepseek-ai/dsh-client-schema-form` |
115
+ | `@dsh-plugin/dsh-loader/runtime` | `@deepseek-ai/dsh-client-runtime/client` |
116
+
117
+ When dsh renames a package, only the dshloader adapter changes — plugin
118
+ source and bundle stay the same.
119
+
120
+ #### 5. Client side — use `window.__dshLoader__`
121
+
122
+ ```js
123
+ // Read cordis client services
124
+ const conv = window.__dshLoader__.services.get('conversation');
125
+
126
+ // Register a package alias at runtime (fallback)
127
+ window.__dshLoader__.registerPackageAlias('@old/pkg', '@new/pkg');
128
+ ```
129
+
130
+ #### 6. Build config — mark stable subpaths as external
131
+
132
+ ```ts
133
+ const CLIENT_EXTERNALS = [
134
+ 'react', 'react/jsx-runtime', 'react-dom', 'react-dom/client', 'cordis',
135
+ '@dsh-plugin/dsh-loader/ui-primitives',
136
+ '@dsh-plugin/dsh-loader/ui-slots',
137
+ '@dsh-plugin/dsh-loader/ui-settings',
138
+ '@dsh-plugin/dsh-loader/web-react',
139
+ '@dsh-plugin/dsh-loader/schema-form',
140
+ '@dsh-plugin/dsh-loader/runtime',
141
+ ]
142
+ ```
143
+
144
+ ### How it works
145
+
146
+ ```
147
+ plugin ──▶ ctx.dshLoader.{settings,web,services} ──▶ dshloader adapter
148
+
149
+
150
+ real dsh (current version)
151
+
152
+ plugin bundle ──▶ require('@dsh-plugin/dsh-loader/ui-primitives')
153
+
154
+ (__ModuleLoader__ wrapper maps stable name)
155
+ require('@deepseek-ai/dsh-client-ui-primitives')
156
+
157
+
158
+ dsh module table
159
+ ```
160
+
161
+ 1. **Version detection** reads `node_modules/@deepseek-ai/dsh/package.json`
162
+ (or `DSHLOADER_DSH_VERSION` for tests/override).
163
+ 2. **AdapterRegistry** selects the best adapter for the detected version
164
+ (exact → range → nearest-low fallback clear error).
165
+ 3. The selected **adapter** registers service aliases, installs package-name
166
+ mapping hooks (host: `Module._resolveFilename`; client:
167
+ `__ModuleLoader__.load` wrapper), and (only when opted in) the settings
168
+ whitelist bypass bridge. All registrations use `ctx.reflect.provide` /
169
+ `ctx.effect`, so cordis auto-recycles them on fiber unload.
170
+
171
+ > **Load order does not matter.** cordis is reactive dependency injection:
172
+ > plugins declaring `inject: [...]` stay `PENDING` until the alias is
173
+ > provided, regardless of where dshloader sits in `cordis.patch.yml`.
174
+
175
+ ### Settings whitelist bypass (`exposeAllNamespaces`)
176
+
177
+ By default dshloader **does not** bypass the official settings namespace
178
+ whitelist. Opt in explicitly:
179
+
180
+ - env: `DSHLOADER_EXPOSE_ALL_SETTINGS=1`
181
+ - profile `package.json`: `dsh.dshloader.exposeAllNamespaces: true`
182
+
183
+ > **Security trade-off**: enabling this removes the official default-deny
184
+ > boundary for browser settings access. Only enable it in profiles where you
185
+ > trust every installed plugin.
186
+
187
+ ### CLI
188
+
189
+ ```
190
+ dshloader setup <profile> Inject dshloader into a profile (dep + patch).
191
+ dshloader dump-config <profile> Run `dsh --profile <name> --dump-config`.
192
+ dshloader info [profile] Print loader version, detected dsh version,
193
+ selected adapter.
194
+ ```
195
+
196
+ ### Rollback / disable
197
+
198
+ - Disable per launch: `DSHLOADER_DISABLE=1 dsh web`
199
+ - Remove: `dsh plugin --profile <name> rm @dsh-plugin/dsh-loader`
200
+
201
+ ### Project layout
202
+
203
+ ```
204
+ src/
205
+ index.ts host bundle entry (name / inject / apply)
206
+ client.ts client bundle entry (immediately tier)
207
+ api.ts DshLoaderHostAPI construction
208
+ registry.ts AdapterRegistry + version detection
209
+ types.ts shared host/client TypeScript types
210
+ version.ts loader version + log prefix
211
+ stable/ stable subpath re-exports (ui-primitives, tools, ...)
212
+ services/
213
+ settings.ts settings stable API
214
+ web.ts web stable API
215
+ services.ts services stable API (get / alias)
216
+ adapters/
217
+ dsh-1-x.ts dsh 1.x adapter
218
+ index.ts adapter registration
219
+ setup.ts profile injection + dump-config + info
220
+ bin/dshloader.mjs CLI entry
221
+ dist/ compiled host build (tsc output, git-ignored)
222
+ lib/ compiled client bundle (tsdown output, git-ignored)
223
+ tsconfig.json typecheck config
224
+ tsconfig.build.json host build config (emits dist/)
225
+ tsdown.client.config.mjs client bundle build config
226
+ docs/
227
+ api.md full API reference (Chinese)
228
+ design.md design document (Chinese)
229
+ tests/ L1 (unit) / module (L2) / integration (L3)
230
+ examples/
231
+ sample-plugin/ minimal example plugin
232
+ dsh-aux-state/ example using ctx.dshLoader only
233
+ ```
234
+
235
+ ### Develop
236
+
237
+ ```sh
238
+ pnpm install
239
+ npm run typecheck # type-check src/**/*.ts
240
+ npm run build # compile host (dist/) + client bundle (lib/)
241
+ npm test # all tests
242
+ npm run test:l1 # unit
243
+ npm run test:l2 # module
244
+ npm run test:l3 # integration
245
+ ```
246
+
247
+ Node.js >= 18, `node --test`, no extra test framework.
248
+
249
+ ### License
250
+
251
+ BSD-3-Clause