@openthink/ui-leaf 0.4.0 → 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 +11 -11
- 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 +62 -40
- 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 -725
- package/dist/cli.js.map +0 -1
- package/dist/dev-server-DapOoULX.d.ts +0 -5
- package/dist/index.js +0 -556
- 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
|
|
|
@@ -97,7 +97,7 @@ await mount({
|
|
|
97
97
|
mutations, // Record<string, MutationHandler> (optional)
|
|
98
98
|
viewsRoot, // optional, default: <cwd>/views
|
|
99
99
|
title, // optional, default: "ui-leaf"
|
|
100
|
-
port, // optional, default: 5810 (auto-bumps if busy)
|
|
100
|
+
port, // optional, default: 5810 (auto-bumps if busy; pass 0 for OS-assigned)
|
|
101
101
|
openBrowser, // optional, default: true
|
|
102
102
|
shell, // optional, "tab" | "app", default: "tab"
|
|
103
103
|
csp, // optional, default: "off" (see Hardening)
|
|
@@ -130,7 +130,7 @@ mount({
|
|
|
130
130
|
|
|
131
131
|
- **Locks `connect-src` to same-origin** — the architectural lock. Views cannot fetch external APIs; all data flows through `data` and `mutations`.
|
|
132
132
|
- **Permits HTTPS images and fonts** so views can load CDN assets normally.
|
|
133
|
-
- **Allows inline styles
|
|
133
|
+
- **Allows inline styles and inline scripts** for React.
|
|
134
134
|
|
|
135
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).
|
|
136
136
|
|
|
@@ -156,22 +156,22 @@ mount({
|
|
|
156
156
|
|
|
157
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.
|
|
158
158
|
|
|
159
|
-
### Data-at-rest
|
|
159
|
+
### Data-at-rest
|
|
160
160
|
|
|
161
|
-
|
|
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.
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
For sensitive payloads use `dataLoader` instead of `data`:
|
|
164
164
|
|
|
165
165
|
```ts
|
|
166
166
|
mount({
|
|
167
167
|
view: "report",
|
|
168
168
|
dataLoader: async () => {
|
|
169
|
-
return await db.fetchSensitiveRecords(); // stays in memory; never
|
|
169
|
+
return await db.fetchSensitiveRecords(); // stays in memory; never appears in HTML
|
|
170
170
|
},
|
|
171
171
|
});
|
|
172
172
|
```
|
|
173
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.
|
|
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.
|
|
175
175
|
|
|
176
176
|
## Sharing views across users
|
|
177
177
|
|
|
@@ -291,7 +291,7 @@ Each pending mutation has a unique `id`. Multiple mutations can be in flight con
|
|
|
291
291
|
|
|
292
292
|
### Driving from Node via `mount()` directly
|
|
293
293
|
|
|
294
|
-
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):
|
|
295
295
|
|
|
296
296
|
```ts
|
|
297
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"
|