haltija 1.12.8 → 1.13.0-beta.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 (56) hide show
  1. package/CHANGELOG.md +257 -2
  2. package/README.md +9 -4
  3. package/apps/desktop/isolation.js +8 -2
  4. package/apps/desktop/machine-channel.js +105 -0
  5. package/apps/desktop/machine-response.global.js +74 -0
  6. package/apps/desktop/main.js +68 -25
  7. package/apps/desktop/package.json +4 -1
  8. package/apps/desktop/ports.js +78 -0
  9. package/apps/desktop/preload.js +3 -1
  10. package/apps/desktop/renderer/isolation.js +6 -2
  11. package/apps/desktop/renderer/machine-relay.js +39 -18
  12. package/apps/desktop/renderer/machine-response.js +33 -0
  13. package/apps/desktop/renderer/tabs.js +9 -1
  14. package/apps/desktop/renderer.js +5 -5
  15. package/apps/desktop/resources/component.js +52 -52
  16. package/apps/desktop/server-env.js +8 -0
  17. package/apps/desktop/terminal.html +46 -34
  18. package/bin/hj.mjs +6 -2
  19. package/bin/tosijs-dev.mjs +14 -7
  20. package/bin/version.mjs +1 -1
  21. package/dist/client.js +0 -17
  22. package/dist/component.d.ts +1 -1
  23. package/dist/component.esm.js +52 -52
  24. package/dist/component.js +52 -52
  25. package/dist/desktop-isolation.d.ts +11 -5
  26. package/dist/desktop-server-env.d.ts +2 -0
  27. package/dist/hj-install.d.ts +9 -0
  28. package/dist/hj.js +22 -146
  29. package/dist/index.js +1356 -1116
  30. package/dist/loader-snippet.d.ts +37 -0
  31. package/dist/machine-channel.d.ts +21 -2
  32. package/dist/machine-log.d.ts +5 -0
  33. package/dist/machine-response.d.ts +30 -0
  34. package/dist/playwright-server.d.ts +4 -2
  35. package/dist/ports.d.ts +109 -0
  36. package/dist/server.d.ts +20 -4
  37. package/dist/server.js +1353 -1113
  38. package/dist/test-in-browser.d.ts +99 -0
  39. package/dist/test-ports.d.ts +15 -0
  40. package/dist/test.d.ts +30 -0
  41. package/dist/test.js +159 -25
  42. package/dist/transports.d.ts +207 -0
  43. package/dist/version.d.ts +1 -1
  44. package/dist/ws-origin.d.ts +55 -0
  45. package/docs/AGENTIC-IDE.md +7 -4
  46. package/docs/DECISIONS.md +168 -0
  47. package/docs/EXECUTIVE-SUMMARY.md +2 -2
  48. package/docs/README.md +1 -1
  49. package/docs/ROADMAP-HISTORY.md +533 -0
  50. package/llms.txt +25 -12
  51. package/package.json +29 -3
  52. package/bin/build-bookmarklet.ts +0 -107
  53. package/bin/server.ts +0 -9
  54. package/bin/tosijs-dev.ts +0 -74
  55. package/dist/.metadata_never_index +0 -0
  56. package/dist/machine-socket.d.ts +0 -71
package/CHANGELOG.md CHANGED
@@ -1,6 +1,261 @@
1
1
  # Changelog
2
2
 
3
- ## 1.12.8 (unreleased)
3
+ ## 1.13.0-beta.1 (2026-09-26)
4
+
5
+ Beta (`npm i haltija@beta`); `latest` stays 1.12.9. Breaking changes are listed first.
6
+
7
+ ### Known issue: any web page can call the REST API (#44)
8
+
9
+ The REST API answers any origin (`Access-Control-Allow-Origin: *`) and needs no token by default,
10
+ so a page you visit can call `/eval` in your connected tabs, or `/shutdown`, and read the results.
11
+ This predates 1.13. What 1.13 changes: with HTTPS on by default, https pages in **Safari** can now
12
+ reach it once the certificate is trusted, where mixed-content blocking used to stop them. Until an
13
+ origin gate lands (planned before 1.13.0), start the server with `--token <secret>` (or set
14
+ `HALTIJA_TOKEN`) on any machine where that matters, and don't leave a server running you aren't
15
+ using.
16
+
17
+ ### Changed (breaking)
18
+
19
+ - **A server now opens BOTH transports by default** (#32a). `DEV_CHANNEL_MODE` defaults to `both`
20
+ instead of `http`, so HTTP 8700 *and* HTTPS 8701 come up whichever project starts the channel.
21
+
22
+ #32 reported this as a missing-certificate problem — `foresight/node_modules/haltija/certs`
23
+ didn't exist, so that instance came up HTTP-only. The causation runs the other way: certs are
24
+ only generated when HTTPS is *wanted*, and the mode defaulted to `http`. The certs were absent
25
+ **because** the transport was, so shipping certs in the package would have fixed nothing.
26
+
27
+ The default is a cross-project decision, which is what makes it worth changing rather than
28
+ documenting. The channel is shared, so an HTTP-only default is not a choice a project makes for
29
+ itself — it decides, for every neighbour on the machine, whether `https://` pages can connect at
30
+ all. In #32 that cost tosijs-3d its entire channel while every indicator said healthy: `hj where`
31
+ reported "haltija 1.12.2, 1 tab", the dev server logged "dev-channel ready", and every command
32
+ routed into an unrelated project's page. An unrecognised `DEV_CHANNEL_MODE` now falls back to
33
+ `both` rather than `http`, so a typo cannot produce the half-open channel either.
34
+
35
+ **`--http` now actually does something.** It was listed in `--help` but never parsed — it
36
+ "worked" only because the `else` branch made HTTP the default. It is a real flag now, and
37
+ choosing it (or `--https`) explicitly on a shared server prints what it costs your neighbours
38
+ (#32d). The default is never warned about; a warning that fires routinely is one nobody reads.
39
+
40
+ - **The TLS certificate moved to `~/.haltija/certs`** — machine-level, not per-install. This is a
41
+ consequence of the above: per-install certs were nearly invisible while almost nobody had any,
42
+ but with HTTPS on by default they mean the browser's "I trust this" memory is invalidated every
43
+ time a different install wins the race to bind 8701, and again after every `npm install` that
44
+ wipes `node_modules`. The user would be asked to trust localhost repeatedly with no way to make
45
+ it stop. An existing per-install certificate is **adopted** (copied forward) rather than
46
+ regenerated, so trust you have already granted survives the move. The private key is written
47
+ `0600` into a `0700` directory. Override with `HALTIJA_CERTS_DIR`.
48
+
49
+ If neither `mkcert` nor `openssl` is present, HTTPS is skipped with an explanation and HTTP still
50
+ serves — a missing tool is not a broken haltija.
51
+
52
+ - **The desktop app's internal chrome-widget port moved from 8701 to 8710** (#32a). `8701` was
53
+ *also* the default for the public HTTPS listener — one number with two independent literals, in
54
+ `src/server.ts` and `apps/desktop/main.js`, neither file aware of the other. They cannot both bind.
55
+
56
+ This was live, not latent. Verified on a developer machine mid-fix: one process (a project's
57
+ `haltija --both`) held **8700 and 8701 together**, because both transports share a process. Launch
58
+ the desktop app in `serverMode: 'builtin'` against that and `killZombieServer()` POSTs `/shutdown`
59
+ to 8701 to claim it for a private widget — shutting down that project's **HTTP** channel as
60
+ collateral. It also blocked #32's headline request outright: "both transports by default" cannot
61
+ ship while the desktop app claims one of them.
62
+
63
+ **The internal port moved rather than HTTPS**, because HTTPS 8701 has callers we do not ship —
64
+ the injected loader and bookmarklet hardcode `https://localhost:8701` (see #38 for the last time
65
+ that string drifted), adopters' dev servers import from it, and self-signed-cert trust is
66
+ per-origin, so moving it would silently un-trust the cert and present as a dead channel. The
67
+ internal port's only client is the app's own renderer, updated in the same binary.
68
+
69
+ **Who is affected:** anyone who targeted the chrome widget as `HALTIJA_PORT=8701 hj tree` should
70
+ now use `8710`. A desktop app older than 1.13.0 still holds 8701, so a new `--both` server may
71
+ fail to bind HTTPS until that app restarts. Setting `HALTIJA_INTERNAL_PORT=8701` by hand now
72
+ prints what will break instead of surfacing as a bind failure in a process you forgot was
73
+ involved.
74
+
75
+ Every well-known port and its role now lives in **`src/ports.ts`** (8700 public HTTP / 8701 public
76
+ HTTPS / 8710 internal HTTP / 8711 reserved), compiled to a CJS twin for Electron main the same way
77
+ `server-env.js` is. `src/ports.test.ts` asserts the public and internal blocks stay disjoint — and
78
+ asserts the *roles*, not the literals, since two literals agreeing is precisely what the bug was.
79
+ Verified against a real `--private --app` launch: chrome widget connected, ports released on
80
+ teardown, and the unrelated `--both` server on 8700/8701 untouched throughout.
81
+
82
+ - **Only the server holding 8700 takes the well-known HTTPS port 8701.** A server started with
83
+ `--port`/`--name`, or one that fell back off a busy 8700 onto an ephemeral port, now opens HTTPS
84
+ on an ephemeral port shown in its banner and `/status`, unless given `--https-port`. Before
85
+ this, with `both` as the default, the first 1.13 server started in any project could capture
86
+ every project's https pages while the real channel and `hj` saw 0 tabs.
87
+
88
+ - **The startup banner and the `server` export prefer HTTP.** They preferred HTTPS, which with
89
+ `both` as the default pointed `curl` at a self-signed certificate and made the DEV MODE
90
+ one-liner fail silently on http pages. `server` is the HTTP listener whenever HTTP is up; use
91
+ `httpsServer` for the HTTPS one.
92
+
93
+ - **A server run from a source checkout installs `hj` by copy, never by symlink,** and only a
94
+ bundle whose stamped version matches the server's. The old symlink fallback pinned
95
+ `~/.local/bin/hj` to that checkout permanently, because a symlinked `hj` is treated as a
96
+ deliberate choice and never repaired.
97
+
98
+ ### Added
99
+
100
+ - **`testInBrowser`** — jest-shaped tests whose assertions run on the host and whose probes run in a
101
+ real browser (#51). The body stays on the host with its real `expect`; only the probe crosses.
102
+ Coordinates (`clickAt`, `dragFrom`) are first-class alongside selectors, because SVG-based UI has
103
+ no selectable element to name. Design credit tosijs-3d; corpus-liveness requirement from
104
+ tosijs-3d-ensemble; the numbers that made the case from tosijs-ui.
105
+
106
+ - **The embed snippet falls back to the other transport** (#32c). It tries the transport matching
107
+ the page, then the other; an https page drops to http only on `localhost`. If neither works it
108
+ logs `haltija: no channel reachable` in the page console, naming the HTTPS address to accept,
109
+ instead of failing silently. The snippet now has one source (`src/loader-snippet.ts`) shared by
110
+ the `/docs` copy and its Playwright suite, and `bun run test:engines` runs that suite in
111
+ Chromium, Firefox and WebKit.
112
+
113
+ ### Fixed
114
+
115
+ - **Whether an https page can reach `http://localhost` depends on the browser, and the docs now
116
+ say so.** Chromium and Firefox allow it; **Safari (WebKit) blocks it as mixed content**. The docs
117
+ had said it was always blocked. Earlier in this release they were changed to say it was never
118
+ blocked, based on a Chromium-only measurement. Both were wrong. The effect is that the fallback
119
+ above helps Chromium and Firefox, but https pages in Safari still need the HTTPS transport. That
120
+ is the strongest reason for `both` being the default. #33's original "mixed content" diagnosis
121
+ was correct for Safari.
122
+
123
+ - **`haltija/server`'s types no longer require `@types/bun`.** Its `.d.ts` exported Bun's own
124
+ server type, so a TypeScript project importing it failed to compile with "Cannot find name
125
+ 'Bun'". `server`, `httpServer` and `httpsServer` are now typed as a small `ListeningServer`
126
+ interface (`port`, `hostname`, `url`, `stop()`); the runtime values are unchanged. Found by the
127
+ new publish workflow's consumer smoke test.
128
+
129
+ - **The machine-level certificate is renewed before it expires**, and one that can't be read is
130
+ replaced. It used to be regenerated by every `npm install`; kept in `~/.haltija/certs` it would
131
+ have lapsed after the openssl fallback's 365 days, while `/status` still reported HTTPS up.
132
+ Writing, adopting or renewing it now leaves a receipt in `~/.haltija/machine-actions.log` and a
133
+ line on stderr. A replaced pair is kept as `*.bak`, since `HALTIJA_CERTS_DIR` may point at a
134
+ directory you own. A key that doesn't match its cert is replaced too, so a pair left
135
+ mismatched by two servers renewing at once heals on the next start. A failed renewal keeps
136
+ using the current cert while it is still valid.
137
+
138
+ - **A bad certificate costs HTTPS only.** A mismatched or corrupt pair in `~/.haltija/certs` used
139
+ to stop every haltija server on the machine from starting, HTTP included. `/status` (and so
140
+ `hj where`) now names the real reason HTTPS is down; it used to blame "port N is held by
141
+ another process" for certificate problems.
142
+
143
+ - **`testInBrowser`: `clickAt` and `dragFrom` send pointer events** as well as mouse events, with
144
+ `buttons` set, and moves go to the element under the pointer. Pointer-driven controls (3D camera
145
+ orbits) ignored them before, and the drag still reported success. `clickAt` also accepts a real
146
+ point above the fold on a scrolled page, which it rejected as "outside the document", and
147
+ `click`/`type`/`press` now fail on a `{ success: false }` reply from a bridge instead of passing.
148
+
149
+ - **The test suites no longer touch the real 8701 or `~/.haltija/certs`.** The Playwright helper
150
+ opted out of HTTPS with a variable nothing had read since v0.1.7, which stopped mattering only
151
+ while `http` was the default. CI now fails if a suite writes anything under `~/.haltija`.
152
+
153
+ - **The README no longer says "localhost only by default".** The server listens on all
154
+ interfaces, and without `--token` any page or anything on the network can call its REST API.
155
+
156
+ ## 1.12.9
157
+
158
+ Remediation of a Tier 1 review that returned **BLOCK** on the already-published 1.12.7/1.12.8.
159
+ Full report: `reviews/1.12.9-tier1-v1.12.6-to-v1.12.8.md`.
160
+
161
+ ### Security
162
+
163
+ - **Babylon.js is pinned and integrity-checked (review B1).** The 3D preview added in 1.12.7 loaded
164
+ `cdn.babylonjs.com/babylon.js` — rolling latest, no pin, no SRI — into the one frame holding a
165
+ postMessage relay that reaches `spawn('sh','-c')` and the filesystem. The app strips CSP headers
166
+ globally and the iframe has no sandbox, so a CDN compromise, a bad publish, or a TLS-intercepting
167
+ proxy meant arbitrary code execution on the developer's machine. Shipped, ironically, in the
168
+ release that removed a *network*-reachable RCE. Now pinned to `v9.25.0` with `integrity` +
169
+ `crossorigin`, so substituted bytes cannot execute at all.
170
+ - **The renderer relay now default-denies its sender.** It validated message *shape* only, so any
171
+ frame in the renderer could reach shell and filesystem. It now requires `event.source` to be a
172
+ known terminal iframe. This is what turned "third-party code in a preview pane" into "third-party
173
+ code with a shell", and it would have handed shell access to any plain `<iframe>` added later.
174
+ - **`/ws/terminal` upgrades are restricted (review B3).** It accepted unauthenticated
175
+ cross-origin upgrades and volunteers `{shellId, cwd}` on connect, streaming the developer's
176
+ working directory and absolute file paths to any web page. It now admits: a **loopback hostname**
177
+ origin, an opaque/`file:` origin that presents a per-launch nonce the desktop app gives only to
178
+ its own terminal frame, or a request with **no `Origin` header at all** (a non-browser client — a
179
+ browser always sends one cross-origin).
180
+
181
+ **Stated precisely, because an earlier draft of this bullet was false in three directions and a
182
+ pre-publish review caught it.** What is *not* claimed: `/ws/agent` and `/ws/browser` are
183
+ deliberately **not** gated — the REST surface already answers any origin
184
+ (`Access-Control-Allow-Origin: *`), so any page can already `POST /tree` and `/eval`; gating the
185
+ socket alone would close nothing and would break non-browser agents, which is why an attempt to
186
+ do so was reverted during this release. And the `/ws/terminal` rule is not "local machine only":
187
+ a loopback match is **hostname-only, any port**, so a third-party script on your own
188
+ `localhost:3000` dev server qualifies, and an absent `Origin` is admitted from anywhere on the
189
+ LAN because the server binds `0.0.0.0`. **`--token` is what addresses those**, not this gate.
190
+ - **The channel grant no longer leaks to child processes.** `HALTIJA_MACHINE_CHANNEL=1` was spread
191
+ into every command the terminal or an agent spawned, so a nested `bunx haltija` would start
192
+ treating its own stdin as an authenticated control channel.
193
+
194
+ ### `haltija/test` no longer mutates a browser nobody chose (#42)
195
+
196
+ `resolveTestServerUrl` falls back to `http://localhost:8700` — the *shared* interactive server — and
197
+ the only gate was a stderr warning. This project proved on itself that a warning is not a boundary:
198
+ on 2026-09-03 our own suite adopted another project's server and called `navigate`/`click` against
199
+ its six live tabs. We fixed our lane and left the published library adopting, and **a hazard fixed
200
+ in your own lane but left in the library you ship is not fixed**.
201
+
202
+ Mutating calls (`navigate`, `click`, `type`, `press`, `refresh`) now **throw** when no
203
+ `HALTIJA_URL`/`HALTIJA_PORT` was set, with a message naming the remedy. Read-only calls (`status`,
204
+ `query`, `eval`) are unaffected — reading from a shared server harms nobody, and a guard that blocks
205
+ harmless calls gets disabled wholesale. `HALTIJA_TEST_ALLOW_SHARED=1` opts back in deliberately.
206
+
207
+ **This is a behaviour change in a published API.** Anyone whose suite relied on the 8700 fallback
208
+ for mutating calls will now get a loud error instead of silently driving whichever browser answered
209
+ — which is the point.
210
+
211
+ ### Dependencies
212
+
213
+ - **`tosijs-schema` was pinned to an exact `1.5.1`, four minors behind.** Surfaced by a new Tier 0
214
+ check (from tosijs-ui#61's finding #2: *declared ranges whose window excludes what a consumer
215
+ installs today*). An exact pin in a regular dependency also invites a duplicate install alongside
216
+ a consumer's own copy. Moved to `^1.9.0` after verifying the upgrade rather than assuming it:
217
+ 977 unit + 125 e2e green, and every schema-derived generated artifact (`API.md`, `DOCS.md`,
218
+ `llms.txt`, `bin/hints.json`, `apps/mcp/src/endpoints.json`) rebuilt **byte-identical**.
219
+
220
+ ### Coverage and structure (the two gaps the reviews left open)
221
+
222
+ - **Nothing exercised the terminal's WebSocket**, which is why a regression that broke it was
223
+ invisible to 977 green tests: `/terminal/command` travels over the stdio pipe and never needed
224
+ the socket, so commands kept working. Two guards now: a live upgrade test against a real private
225
+ server (raw HTTP upgrade, so it can set `Origin` — which the browser WebSocket API forbids), and
226
+ a wiring test asserting `main.js` actually *passes* what `buildServerEnv` can grant. The second
227
+ is the one that matters: the function was already tested; the defect was a caller that silently
228
+ dropped an argument. Both verified by mutation.
229
+ - **The base64 Response shim existed twice and had already diverged** (only one copy had
230
+ `dataUrl()`). Now one source, `src/machine-response.ts`, built twice — ESM for the renderer's
231
+ modules, a global for `terminal.html`, which is an inline-script page with no import mechanism.
232
+
233
+ ### Fixes
234
+
235
+ - **Window recreation was dead code (review B2).** The 1.12.7 stdout refactor tested
236
+ `__NEED_WINDOW__` against the *residual line buffer*; the marker always arrives newline-terminated,
237
+ so the check was always false and the correct one below it unreachable. Every `hj` command against
238
+ a windowless-but-alive desktop app hung 8s and failed.
239
+ - **Root cause of the above: the wire protocol was implemented twice.** `main.js` hand-rolled the
240
+ prefixes, the line split and the parse while `src/machine-channel.ts` owned and tested them. It is
241
+ now a generated twin (`apps/desktop/machine-channel.js`), like `server-env.js`.
242
+ - **Timeout layering was inverted**, so a ~30s command (`npm ci`, a slow build) reported "Timed out
243
+ on the machine channel" and discarded the server's real output. Each outer deadline is now
244
+ strictly longer than the one it wraps (server 30s → main 45s → iframe 50s), so a timeout at a
245
+ layer means that layer genuinely lost the answer.
246
+ - **The desktop guard test only recognised template-literal fetches**, so
247
+ `fetch(getServerUrl() + '/files/read')` passed green and 410'd at runtime — the exact silent
248
+ breakage it exists to prevent. It now matches the call and the path independently (verified by
249
+ mutation), skips comments, and states its remaining line-scoped limit rather than implying none.
250
+ - Bun's Unix-socket defect is now **filed upstream** (oven-sh/bun#41381) and mirrored in
251
+ `UPSTREAM.md`, per "we file, we don't fix". The stdio channel stays regardless — it is good on its
252
+ own merits.
253
+ - Docs corrected: `docs/AGENTIC-IDE.md` no longer advertises two routes that now return 410;
254
+ `CLAUDE.md` no longer contradicts itself about whether the tabs were restored; published versions
255
+ are no longer marked `(unreleased)`; and the absolute "no machine-control surface on any
256
+ transport" claim is stated precisely, since an earlier version of it was false.
257
+
258
+ ## 1.12.8
4
259
 
5
260
  - **Electron discovery assumed macOS, so Linux re-downloaded Electron every run (#43).** The npx-cache
6
261
  search looked for `Electron.app` — a macOS bundle — behind a `platform() !== 'win32'` guard, so
@@ -14,7 +269,7 @@
14
269
  right all along — decide the binary name, so there is one authority instead of two to drift.
15
270
  Reported by snowfox with the diagnosis and the fix.
16
271
 
17
- ## 1.12.7 (unreleased)
272
+ ## 1.12.7
18
273
 
19
274
  ### File preview: 3D models, video, audio and fonts
20
275
 
package/README.md CHANGED
@@ -331,8 +331,13 @@ app also exists but isn't required for either.
331
331
 
332
332
  - Widget is always visible (no silent snooping)
333
333
  - User can pause or kill connection anytime
334
- - Localhost only by default
335
- - HTTPS mode with auto-generated certs
334
+ - **Not localhost-only.** The server listens on all interfaces (HTTP 8700 and HTTPS 8701 by
335
+ default), and without `--token` any page you visit, or anything on your network, can call its
336
+ REST API — including `/eval` in your connected tabs. Use `--token <secret>` (or
337
+ `HALTIJA_TOKEN`) on any network you don't trust, and don't leave a server running you aren't
338
+ using.
339
+ - Machine-level HTTPS certificate in `~/.haltija/certs`, auto-generated (mkcert if installed,
340
+ otherwise openssl) and renewed before it expires
336
341
 
337
342
  ---
338
343
 
@@ -419,10 +424,10 @@ HALTIJA_NAME=dashboard hj tree # any shell can address it by name
419
424
 
420
425
  What this means for you, depending on how you used 1.3.0-beta.7:
421
426
 
422
- - **`bunx haltija` desktop app** — works the same; no migration needed. The outer "chrome" widget that lets the app inspect itself now lives on a separate internal port (8701) so it never appears in agent-facing window listings.
427
+ - **`bunx haltija` desktop app** — works the same; no migration needed. The outer "chrome" widget that lets the app inspect itself now lives on a separate internal port (8701 then, 8710 since 1.13.0) so it never appears in agent-facing window listings.
423
428
  - **`HALTIJA_SESSION` / `--session` / `--secure` / the click-to-copy session badge** — gone. If you were setting `HALTIJA_SESSION` in your shell, replace it with `HALTIJA_NAME` (and start the server with `--name <foo>`) or `HALTIJA_PORT`.
424
429
  - **`inject(url, { session })`** — the `session` option is removed. If you need auth, use `inject(url, { token })` (matches `haltija --token <secret>`); for embedding without auth, just `inject(url)` or `inject(url, { mode: 'headless' })`.
425
- - **`hj-chrome` exclusion logic** — gone from the public REST API. To inspect the desktop app's outer UI from `hj`, target the internal port directly: `HALTIJA_PORT=8701 hj tree`.
430
+ - **`hj-chrome` exclusion logic** — gone from the public REST API. To inspect the desktop app's outer UI from `hj`, target the internal port directly: `HALTIJA_PORT=8710 hj tree`. *(That port was 8701 in 1.4.0–1.12.x; it moved in 1.13.0 because 8701 is the public HTTPS listener — see [#32](https://github.com/tonioloewald/haltija/issues/32).)*
426
431
 
427
432
  Net code change: ~830 lines removed, ~150 added back for the simpler model. Test count went up (we now have integration coverage for the token stub, named instances, and auto-port fallback that the previous betas lacked).
428
433
 
@@ -48,8 +48,14 @@ __export(exports_desktop_isolation, {
48
48
  resolveServerUrl: () => resolveServerUrl
49
49
  });
50
50
  module.exports = __toCommonJS(exports_desktop_isolation);
51
- var SHARED_PUBLIC_URL = "http://localhost:8700";
52
- var SHARED_INTERNAL_PORT = 8701;
51
+
52
+ // src/ports.ts
53
+ var DEFAULT_HTTP_PORT = 8700;
54
+ var DEFAULT_INTERNAL_PORT = 8710;
55
+
56
+ // src/desktop-isolation.ts
57
+ var SHARED_PUBLIC_URL = `http://localhost:${DEFAULT_HTTP_PORT}`;
58
+ var SHARED_INTERNAL_PORT = DEFAULT_INTERNAL_PORT;
53
59
  function resolveInternalPort(env) {
54
60
  const raw = env.HALTIJA_INTERNAL_PORT;
55
61
  if (raw === undefined || raw === "")
@@ -0,0 +1,105 @@
1
+ /** ⚠️ AUTO-GENERATED FROM src/machine-channel.ts — DO NOT EDIT. Run: bun run build */
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toCommonJS = (from) => {
10
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
11
+ if (entry)
12
+ return entry;
13
+ entry = __defProp({}, "__esModule", { value: true });
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (var key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(entry, key))
17
+ __defProp(entry, key, {
18
+ get: __accessProp.bind(from, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ __moduleCache.set(from, entry);
23
+ return entry;
24
+ };
25
+ var __moduleCache;
26
+ var __returnValue = (v) => v;
27
+ function __exportSetter(name, newValue) {
28
+ this[name] = __returnValue.bind(null, newValue);
29
+ }
30
+ var __export = (target, all) => {
31
+ for (var name in all)
32
+ __defProp(target, name, {
33
+ get: all[name],
34
+ enumerable: true,
35
+ configurable: true,
36
+ set: __exportSetter.bind(all, name)
37
+ });
38
+ };
39
+
40
+ // src/machine-channel.ts
41
+ var exports_machine_channel = {};
42
+ __export(exports_machine_channel, {
43
+ MACHINE_CHANNEL_ENV: () => MACHINE_CHANNEL_ENV,
44
+ MACHINE_REQ_PREFIX: () => MACHINE_REQ_PREFIX,
45
+ MACHINE_RES_PREFIX: () => MACHINE_RES_PREFIX,
46
+ childEnvWithoutChannel: () => childEnvWithoutChannel,
47
+ formatRequest: () => formatRequest,
48
+ formatResponse: () => formatResponse,
49
+ machineChannelEnabled: () => machineChannelEnabled,
50
+ parseRequestLine: () => parseRequestLine,
51
+ parseResponseLine: () => parseResponseLine,
52
+ splitLines: () => splitLines
53
+ });
54
+ module.exports = __toCommonJS(exports_machine_channel);
55
+ var MACHINE_REQ_PREFIX = "HJ_MACHINE_REQ ";
56
+ var MACHINE_RES_PREFIX = "HJ_MACHINE_RES ";
57
+ var MACHINE_CHANNEL_ENV = "HALTIJA_MACHINE_CHANNEL";
58
+ function machineChannelEnabled(env) {
59
+ return env[MACHINE_CHANNEL_ENV] === "1";
60
+ }
61
+ function formatRequest(req) {
62
+ return MACHINE_REQ_PREFIX + JSON.stringify(req) + `
63
+ `;
64
+ }
65
+ function formatResponse(res) {
66
+ return MACHINE_RES_PREFIX + JSON.stringify(res) + `
67
+ `;
68
+ }
69
+ function parseRequestLine(line) {
70
+ if (!line.startsWith(MACHINE_REQ_PREFIX))
71
+ return null;
72
+ try {
73
+ const parsed = JSON.parse(line.slice(MACHINE_REQ_PREFIX.length));
74
+ if (typeof parsed?.id !== "string" || typeof parsed?.path !== "string")
75
+ return null;
76
+ if (!parsed.path.startsWith("/"))
77
+ return null;
78
+ return { method: "GET", ...parsed };
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+ function parseResponseLine(line) {
84
+ if (!line.startsWith(MACHINE_RES_PREFIX))
85
+ return null;
86
+ try {
87
+ const parsed = JSON.parse(line.slice(MACHINE_RES_PREFIX.length));
88
+ if (typeof parsed?.id !== "string" || typeof parsed?.status !== "number")
89
+ return null;
90
+ return { headers: {}, bodyB64: "", ...parsed };
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ function splitLines(buffer) {
96
+ const parts = buffer.split(`
97
+ `);
98
+ const rest = parts.pop() ?? "";
99
+ return { lines: parts, rest };
100
+ }
101
+ function childEnvWithoutChannel(env = process.env) {
102
+ const copy = { ...env };
103
+ delete copy[MACHINE_CHANNEL_ENV];
104
+ return copy;
105
+ }
@@ -0,0 +1,74 @@
1
+ /** ⚠️ AUTO-GENERATED FROM src/machine-response.ts — DO NOT EDIT. Run: bun run build */
2
+ (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toCommonJS = (from) => {
11
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
12
+ if (entry)
13
+ return entry;
14
+ entry = __defProp({}, "__esModule", { value: true });
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (var key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(entry, key))
18
+ __defProp(entry, key, {
19
+ get: __accessProp.bind(from, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ __moduleCache.set(from, entry);
24
+ return entry;
25
+ };
26
+ var __moduleCache;
27
+ var __returnValue = (v) => v;
28
+ function __exportSetter(name, newValue) {
29
+ this[name] = __returnValue.bind(null, newValue);
30
+ }
31
+ var __export = (target, all) => {
32
+ for (var name in all)
33
+ __defProp(target, name, {
34
+ get: all[name],
35
+ enumerable: true,
36
+ configurable: true,
37
+ set: __exportSetter.bind(all, name)
38
+ });
39
+ };
40
+
41
+ // src/machine-response.ts
42
+ var exports_machine_response = {};
43
+ __export(exports_machine_response, {
44
+ machineResponseError: () => machineResponseError,
45
+ makeMachineResponse: () => makeMachineResponse
46
+ });
47
+ function makeMachineResponse(status, bodyB64, headers = {}) {
48
+ let decoded = null;
49
+ const decode = () => {
50
+ if (decoded === null) {
51
+ const bytes = Uint8Array.from(atob(bodyB64 || ""), (c) => c.charCodeAt(0));
52
+ decoded = new TextDecoder().decode(bytes);
53
+ }
54
+ return decoded;
55
+ };
56
+ return {
57
+ ok: status >= 200 && status < 300,
58
+ status,
59
+ headers,
60
+ b64: bodyB64 || "",
61
+ dataUrl: () => `data:${headers["content-type"] || "application/octet-stream"};base64,${bodyB64 || ""}`,
62
+ json: async () => JSON.parse(decode() || "{}"),
63
+ text: async () => decode()
64
+ };
65
+ }
66
+ function machineResponseError(status, message) {
67
+ return makeMachineResponse(status, btoa(JSON.stringify({ success: false, error: message })), {
68
+ "content-type": "application/json"
69
+ });
70
+ }
71
+ if (typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined") {
72
+ globalThis.__hjMachineResponse = { makeMachineResponse, machineResponseError };
73
+ }
74
+ })();