@openthink/ui-leaf 0.3.3 → 0.5.0
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.
- package/README.md +22 -10
- package/package.json +14 -17
- package/packages/cli/dist/cli.js +957 -0
- package/packages/cli/dist/cli.js.map +1 -0
- package/{dist → packages/cli/dist}/index.d.ts +73 -32
- package/packages/cli/dist/index.js +698 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/cli/dist/server-Bp6cms3O.d.ts +7 -0
- package/{dist → packages/cli/dist}/view.d.ts +1 -1
- package/packages/cli/package.json +22 -0
- package/dist/cli.js +0 -680
- package/dist/cli.js.map +0 -1
- package/dist/dev-server-DapOoULX.d.ts +0 -5
- package/dist/index.js +0 -511
- package/dist/index.js.map +0 -1
- /package/{dist → packages/cli/dist}/cli.d.ts +0 -0
- /package/{dist → packages/cli/dist}/view.js +0 -0
- /package/{dist → packages/cli/dist}/view.js.map +0 -0
package/README.md
CHANGED
|
@@ -78,11 +78,11 @@ Three reasons:
|
|
|
78
78
|
|
|
79
79
|
## How it works
|
|
80
80
|
|
|
81
|
-
`mount()`
|
|
81
|
+
`mount()` compiles your view file once with `Bun.build`, injects data into `window.__UI_LEAF__`, starts a `Bun.serve` HTTP server, and opens the user's default browser. Mutations from the view POST back to a localhost endpoint with a per-launch random token; the runtime dispatches them to the handlers you registered. Browser tab close → heartbeat stops → server shuts down → `view.closed` resolves and your CLI continues.
|
|
82
82
|
|
|
83
|
-
The transport is HTTP + JSON over loopback. The token is in `window.__UI_LEAF__.token`,
|
|
83
|
+
The transport is HTTP + JSON over loopback. The token is in `window.__UI_LEAF__.token`, served inline in the compiled HTML — so the token only protects against drive-by cross-origin requests in the user's browser, not against other processes on the same machine. Any local process that can reach `127.0.0.1:<port>` can fetch `GET /`, grep the token out, and call `/mutate` or `/api/data` with it; treat any local process you don't trust as having the same access as the view. View bundling resolves React from `ui-leaf`'s installed location, so your project doesn't need to install React.
|
|
84
84
|
|
|
85
|
-
The same trust boundary applies to the `data` you pass to `mount()`. The payload is JSON-inlined into `window.__UI_LEAF__.data` in the
|
|
85
|
+
The same trust boundary applies to the `data` you pass to `mount()`. The payload is JSON-inlined into `window.__UI_LEAF__.data` in the served HTML and held in memory for the mount lifetime — readable by the same set of same-UID local processes that can read the token. For PHI, PCI, financial records, or anything else where a same-UID local reader is in your threat model, use `dataLoader` instead — the loader's return value is served at a token-gated `/api/data` endpoint and never appears in the HTML. See "Data-at-rest" below for details.
|
|
86
86
|
|
|
87
87
|
## API surface
|
|
88
88
|
|
|
@@ -92,11 +92,12 @@ import type { ViewProps, MutationHandler } from "@openthink/ui-leaf/view";
|
|
|
92
92
|
|
|
93
93
|
await mount({
|
|
94
94
|
view, // resolves <viewsRoot>/<view>.tsx
|
|
95
|
-
data, // JSON-serializable, becomes data prop
|
|
95
|
+
data, // JSON-serializable, becomes data prop (convenience default)
|
|
96
|
+
dataLoader, // optional async fn; serves data via authenticated /api/data (no disk write)
|
|
96
97
|
mutations, // Record<string, MutationHandler> (optional)
|
|
97
98
|
viewsRoot, // optional, default: <cwd>/views
|
|
98
99
|
title, // optional, default: "ui-leaf"
|
|
99
|
-
port, // optional, default: 5810 (auto-bumps if busy)
|
|
100
|
+
port, // optional, default: 5810 (auto-bumps if busy; pass 0 for OS-assigned)
|
|
100
101
|
openBrowser, // optional, default: true
|
|
101
102
|
shell, // optional, "tab" | "app", default: "tab"
|
|
102
103
|
csp, // optional, default: "off" (see Hardening)
|
|
@@ -129,7 +130,7 @@ mount({
|
|
|
129
130
|
|
|
130
131
|
- **Locks `connect-src` to same-origin** — the architectural lock. Views cannot fetch external APIs; all data flows through `data` and `mutations`.
|
|
131
132
|
- **Permits HTTPS images and fonts** so views can load CDN assets normally.
|
|
132
|
-
- **Allows inline styles
|
|
133
|
+
- **Allows inline styles and inline scripts** for React.
|
|
133
134
|
|
|
134
135
|
Because the policy is sent as an HTTP response header, views cannot relax it at runtime. The only way to weaken the policy is to change the `mount()` call (i.e. fork the consumer CLI, not the view).
|
|
135
136
|
|
|
@@ -155,11 +156,22 @@ mount({
|
|
|
155
156
|
|
|
156
157
|
Be deliberate — every name you add becomes a viable rebinding target. Don't add public DNS names or LAN hostnames you don't fully control.
|
|
157
158
|
|
|
158
|
-
### Data-at-rest
|
|
159
|
+
### Data-at-rest
|
|
159
160
|
|
|
160
|
-
|
|
161
|
+
The `data` you pass to `mount()` is JSON-inlined into the compiled HTML that `GET /` serves, and held in memory for the mount lifetime. It is **not written to disk** — the compiled HTML exists only in process memory. A same-UID local process that can reach `127.0.0.1:<port>` can still recover the data by fetching `GET /` (no token required), so `data` is appropriate for routine payloads but not for PHI, PCI, or financial records where in-HTML exposure is in your threat model.
|
|
161
162
|
|
|
162
|
-
|
|
163
|
+
For sensitive payloads use `dataLoader` instead of `data`:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
mount({
|
|
167
|
+
view: "report",
|
|
168
|
+
dataLoader: async () => {
|
|
169
|
+
return await db.fetchSensitiveRecords(); // stays in memory; never appears in HTML
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`dataLoader` invokes the function once at mount time, captures the result in-process, and serves it at a token-gated `GET /api/data` endpoint using the same per-launch token as `/mutate`. The view fetches `/api/data` on first render. The payload never appears in the HTML — it can only be read by a caller that already has the token. Note that a local process that can fetch `GET /` (no auth) can still read the token from `window.__UI_LEAF__.token` and then call `/api/data` — so `dataLoader` hardens against HTML-level exposure, not against a determined same-UID reader.
|
|
163
175
|
|
|
164
176
|
## Sharing views across users
|
|
165
177
|
|
|
@@ -279,7 +291,7 @@ Each pending mutation has a unique `id`. Multiple mutations can be in flight con
|
|
|
279
291
|
|
|
280
292
|
### Driving from Node via `mount()` directly
|
|
281
293
|
|
|
282
|
-
If your consumer is itself Node (or you want a thin in-process integration), use the SDK directly. Pass `silent: true` to
|
|
294
|
+
If your consumer is itself Node (or you want a thin in-process integration), use the SDK directly. Pass `silent: true` to redirect stdout to stderr for the lifetime of the server, keeping stdout clean for your own protocol (capture `process.stdout.write` *before* calling `mount()` if you need to write to real stdout from the same process):
|
|
283
295
|
|
|
284
296
|
```ts
|
|
285
297
|
const realStdoutWrite = process.stdout.write.bind(process.stdout);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openthink/ui-leaf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Customizable browser views, on demand, for any CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Matt Pardini",
|
|
@@ -25,41 +25,38 @@
|
|
|
25
25
|
},
|
|
26
26
|
"exports": {
|
|
27
27
|
".": {
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
29
|
-
"import": "./dist/index.js"
|
|
28
|
+
"types": "./packages/cli/dist/index.d.ts",
|
|
29
|
+
"import": "./packages/cli/dist/index.js"
|
|
30
30
|
},
|
|
31
31
|
"./view": {
|
|
32
|
-
"types": "./dist/view.d.ts",
|
|
33
|
-
"import": "./dist/view.js"
|
|
32
|
+
"types": "./packages/cli/dist/view.d.ts",
|
|
33
|
+
"import": "./packages/cli/dist/view.js"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
-
"main": "./dist/index.js",
|
|
37
|
-
"types": "./dist/index.d.ts",
|
|
36
|
+
"main": "./packages/cli/dist/index.js",
|
|
37
|
+
"types": "./packages/cli/dist/index.d.ts",
|
|
38
38
|
"bin": {
|
|
39
|
-
"ui-leaf": "./dist/cli.js"
|
|
39
|
+
"ui-leaf": "./packages/cli/dist/cli.js"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
|
-
"dist",
|
|
42
|
+
"packages/cli/dist",
|
|
43
|
+
"packages/cli/package.json",
|
|
43
44
|
"README.md",
|
|
44
45
|
"LICENSE"
|
|
45
46
|
],
|
|
47
|
+
"workspaces": ["packages/*"],
|
|
46
48
|
"scripts": {
|
|
47
|
-
"build": "
|
|
48
|
-
"dev": "
|
|
49
|
-
"typecheck": "tsc --noEmit",
|
|
49
|
+
"build": "bun run --filter '@openthink/ui-leaf-cli' build",
|
|
50
|
+
"dev": "bun run --filter '@openthink/ui-leaf-cli' dev",
|
|
51
|
+
"typecheck": "tsc --noEmit -p packages/cli/tsconfig.src.json",
|
|
50
52
|
"test": "bun test",
|
|
51
53
|
"prepublishOnly": "bun run build"
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
54
56
|
"@types/node": "^25.6.0",
|
|
55
|
-
"@types/react": "^19.2.14",
|
|
56
|
-
"@types/react-dom": "^19.2.3",
|
|
57
|
-
"tsup": "^8.4.0",
|
|
58
57
|
"typescript": "^6.0.0"
|
|
59
58
|
},
|
|
60
59
|
"dependencies": {
|
|
61
|
-
"@rsbuild/core": "^2.0.1",
|
|
62
|
-
"@rsbuild/plugin-react": "^2.0.0",
|
|
63
60
|
"open": "^11.0.0",
|
|
64
61
|
"react": "^19.2.5",
|
|
65
62
|
"react-dom": "^19.2.5"
|