viewable-one 0.0.0-stage → 0.12.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 (89) hide show
  1. package/CHANGELOG.md +1460 -0
  2. package/LICENSE +21 -0
  3. package/README.md +755 -2
  4. package/SPAWN.md +2105 -0
  5. package/glytch.mjs +517 -0
  6. package/lib/acp.mjs +978 -0
  7. package/lib/admission.mjs +105 -0
  8. package/lib/agent-selection.mjs +123 -0
  9. package/lib/agents.mjs +913 -0
  10. package/lib/atomic.mjs +96 -0
  11. package/lib/auth.mjs +1387 -0
  12. package/lib/authenticatedHttp.mjs +68 -0
  13. package/lib/boardside.mjs +225 -0
  14. package/lib/child.mjs +1252 -0
  15. package/lib/cli-output.mjs +176 -0
  16. package/lib/cloudflared-release.json +25 -0
  17. package/lib/cloudflared.mjs +81 -0
  18. package/lib/codex-rpc.mjs +92 -0
  19. package/lib/codex.mjs +747 -0
  20. package/lib/codexApps.mjs +332 -0
  21. package/lib/codexPlanWindow.mjs +84 -0
  22. package/lib/codexToolInputs.mjs +139 -0
  23. package/lib/codingChecks.mjs +55 -0
  24. package/lib/compose.mjs +76 -0
  25. package/lib/computerApps.mjs +110 -0
  26. package/lib/conversationRules.mjs +45 -0
  27. package/lib/detect.mjs +605 -0
  28. package/lib/devserver.mjs +659 -0
  29. package/lib/digest.mjs +290 -0
  30. package/lib/doctor.mjs +464 -0
  31. package/lib/exitClass.mjs +64 -0
  32. package/lib/glytchFolder.mjs +105 -0
  33. package/lib/hosted.mjs +112 -0
  34. package/lib/imageAttachments.mjs +31 -0
  35. package/lib/init-connect.mjs +418 -0
  36. package/lib/kill.mjs +104 -0
  37. package/lib/logindaemon.mjs +976 -0
  38. package/lib/logs.mjs +120 -0
  39. package/lib/mcpjson.mjs +242 -0
  40. package/lib/memberPreview.mjs +165 -0
  41. package/lib/multiwatch.mjs +428 -0
  42. package/lib/pagePictures.mjs +396 -0
  43. package/lib/pageRoutes.mjs +67 -0
  44. package/lib/paths.mjs +9 -0
  45. package/lib/permissionAsk.mjs +418 -0
  46. package/lib/picker.mjs +300 -0
  47. package/lib/platform.mjs +60 -0
  48. package/lib/previewGateway.mjs +129 -0
  49. package/lib/previewTunnel.mjs +85 -0
  50. package/lib/procs.mjs +428 -0
  51. package/lib/project-mcp.mjs +288 -0
  52. package/lib/projectPaths.mjs +31 -0
  53. package/lib/refusal.mjs +70 -0
  54. package/lib/registry.mjs +204 -0
  55. package/lib/repomap.mjs +322 -0
  56. package/lib/restartPass.mjs +151 -0
  57. package/lib/retired-address.mjs +78 -0
  58. package/lib/rowsAnswers.mjs +389 -0
  59. package/lib/rowsEvents.mjs +86 -0
  60. package/lib/run-engine.mjs +16 -0
  61. package/lib/runner-health.mjs +57 -0
  62. package/lib/runner.mjs +1569 -0
  63. package/lib/runnerAdmission.mjs +35 -0
  64. package/lib/runnerApi.mjs +312 -0
  65. package/lib/runnerCompletion.mjs +406 -0
  66. package/lib/runnerLease.mjs +37 -0
  67. package/lib/runnerMaintenance.mjs +27 -0
  68. package/lib/runnerPace.mjs +283 -0
  69. package/lib/runnerPerformance.mjs +26 -0
  70. package/lib/runnerRealtime.mjs +340 -0
  71. package/lib/runnerRegister.mjs +96 -0
  72. package/lib/runnerSettings.mjs +40 -0
  73. package/lib/runnerSettlement.mjs +138 -0
  74. package/lib/runnerWorkspace.mjs +162 -0
  75. package/lib/serverSweep.mjs +90 -0
  76. package/lib/sharedPreview.mjs +292 -0
  77. package/lib/signIn.mjs +47 -0
  78. package/lib/spawn.mjs +330 -0
  79. package/lib/sweep.mjs +161 -0
  80. package/lib/sync.mjs +484 -0
  81. package/lib/uninstall-project.mjs +588 -0
  82. package/lib/uninstall.mjs +457 -0
  83. package/lib/update.mjs +302 -0
  84. package/lib/usageLimit.mjs +108 -0
  85. package/lib/windows-command.mjs +85 -0
  86. package/lib/worktree.mjs +1138 -0
  87. package/lib/writePolicy.mjs +45 -0
  88. package/package.json +58 -4
  89. package/spawn-runner.mjs +167 -0
package/README.md CHANGED
@@ -1,3 +1,756 @@
1
- # Temporary Holding Version
1
+ # Viewable
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Viewable connects coding agents to the Viewable board over a hosted
4
+ MCP endpoint (`https://app.viewable.one/mcp`), the same one claude.ai,
5
+ Claude Code, Codex and opencode all reach over OAuth. Agents can read the
6
+ board, propose changes, ask you questions on the canvas, and report their
7
+ work. A background runner can also start Claude Code, Codex and opencode
8
+ sessions requested from the board.
9
+
10
+ The package is one command, `viewable`: the background runner, plus `init`,
11
+ `doctor`, `login` and `uninstall` to connect a project, sign this machine in
12
+ and take it all away again. It starts no server of its own. The board tools
13
+ are served from the hosted endpoint, and a run is a row your runner claims,
14
+ so both work the same whether or not the app is open.
15
+
16
+ Linking a folder is for the project files - the file tree, documents,
17
+ briefs on disk. `.glytch/` inside the project holds only the saved agent
18
+ selection, image staging and a repo-map cache: this machine's own scratch,
19
+ never the board.
20
+
21
+ ## Quick start
22
+
23
+ You need Node.js 22 or newer and Chrome or Edge for the board's folder link.
24
+
25
+ 1. Install the coding agents you want to use - Claude Code, Codex, opencode.
26
+ 2. `npm install -g viewable-one@latest` (Windows PowerShell: `npm.cmd i -g viewable-one@latest`).
27
+ Never use sudo. If npm reports a permissions error, use a user-owned prefix.
28
+ Persist its bin directory in your shell startup configuration or Windows user
29
+ PATH. Open a new normal terminal and check `viewable -v` (`viewable.cmd -v`
30
+ in PowerShell) before continuing. A temporary PATH export is not enough.
31
+ 3. `viewable init` inside the project folder. Choose one or more agents with
32
+ the arrow keys (`j`/`k` also work); Enter or Space checks the row under the
33
+ cursor, `a` checks or unchecks all, and Enter on the "Continue" row below
34
+ the agents finishes. Without a terminal - a script, CI, a pipe - there is
35
+ no picker to drive: pass `--agents claude,codex` (names, comma separated)
36
+ or `--yes` for every agent this machine already has installed.
37
+ 4. Open the board and run an idea on your coding agent. With one project
38
+ connected it is picked for you; with more, pick one in Settings >
39
+ Connection. Linking the folder in the app is optional: it only shows the
40
+ project's files on the board.
41
+
42
+ `viewable init` signs this machine in and then connects each selected agent, so
43
+ a browser opens once for the machine and once per agent rather than leaving
44
+ you a list of commands to run - Claude Code needs 2.1.186 or newer for its
45
+ part, and below that floor `init` prints the `/mcp` step instead.
46
+
47
+ Then restart your coding agent's MCP connection to load the board tools - in
48
+ Codex, the project must also be trusted - and ask it to read the board.
49
+ `viewable doctor` names anything that still needs attention.
50
+
51
+ Repeat `viewable init` in each project you want to connect. Installing the npm
52
+ package and signing this machine in are one-time steps; agent choices and
53
+ board connections are per project.
54
+
55
+ ### Windows
56
+
57
+ PowerShell blocks npm's shim by default: `viewable : File ...\viewable.ps1
58
+ cannot be loaded because running scripts is disabled on this system`. Either
59
+ run the command with `.cmd` once (`viewable.cmd init`), or allow your own
60
+ scripts once with `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` and
61
+ use plain `viewable init` from then on. Check `node -v` is 22 or newer first,
62
+ and run `viewable init` from inside the project folder itself, not a parent
63
+ folder or a shortcut to it.
64
+
65
+ ## Uninstall
66
+
67
+ `viewable uninstall` is the one command: it disconnects every project this
68
+ machine has registered (agent sign-outs, config entries, `.glytch/agents.json`,
69
+ each folder's registry entry), stops the login daemon and removes its file,
70
+ revokes this machine's board sign-in and forgets the credential, and deletes
71
+ `~/.viewable` entirely. It confirms once - the plan is printed and it asks you
72
+ to type `yes` - before touching anything.
73
+
74
+ ```bash
75
+ viewable uninstall
76
+ ```
77
+
78
+ - `--project` disconnects only the current folder; nothing machine-wide runs.
79
+ - `--keep-login` leaves this machine signed in to the board.
80
+ - `--dry-run` reports what each step would do and changes nothing.
81
+ - `--yes` skips the confirmation prompt (required without a terminal).
82
+
83
+ Two things it cannot do for you, printed at the end: uninstall the npm
84
+ package itself (`npm uninstall -g viewable-one`, or `npm unlink -g viewable-one` from
85
+ `bridge/` in a checkout), and unlink the folder in the app's Project panel.
86
+
87
+ `forget`, `stop` and `logout` remain as the three partial verbs `uninstall`
88
+ runs together - use one of them directly when you want only its own piece.
89
+
90
+ ## Choose coding agents
91
+
92
+ | Selection | Project configuration | Board MCP tools | Sessions launched from the board |
93
+ | --- | --- | --- | --- |
94
+ | `claude` | `.mcp.json` | Yes, once you sign in (`/mcp` in a session) | Yes |
95
+ | `codex` | `.codex/config.toml` | Yes, once you sign in (`codex mcp login viewable`) | Yes |
96
+ | `opencode` | `opencode.jsonc` if present, otherwise `opencode.json` | Yes, once you sign in (`opencode mcp auth viewable`) | Yes |
97
+
98
+ The board is one hosted connection now (`https://app.viewable.one/mcp`), not a
99
+ binary this package spawns: `viewable init` writes the URL into each selected
100
+ agent's configuration, and the agent's own OAuth flow is the one-time step
101
+ that finishes connecting it - `viewable doctor` names the same step again if
102
+ you skip it.
103
+
104
+ `viewable init` defaults to detected agents on the first run and the saved
105
+ selection on later runs. You can select an agent before installing it; the
106
+ configuration will be ready, and `doctor` will report the missing executable.
107
+
108
+ For scripts, pass the selection explicitly:
109
+
110
+ ```bash
111
+ viewable init --agents claude,codex
112
+ viewable init --agents claude,opencode,codex
113
+ ```
114
+
115
+ `viewable init --yes` accepts saved choices, or all detected agents when there
116
+ are no saved choices. It fails if neither provides an agent. Without a
117
+ terminal, a first run requires `--agents` or `--yes`; subsequent runs can reuse
118
+ the saved selection without a prompt.
119
+
120
+ Choosing an agent here connects its MCP tools. Which agent runs a prompt is
121
+ chosen separately on the board.
122
+
123
+ ## What setup changes
124
+
125
+ `viewable init`:
126
+
127
+ - Saves the selection in `.glytch/agents.json` in the project.
128
+ - Registers the project in `~/.glytch/folders.json`.
129
+ - Adds the board's hosted URL (`https://app.viewable.one/mcp`) to each
130
+ selected agent's project configuration.
131
+ - Signs this machine in when it is not already, the same flow as
132
+ `viewable login`. `--no-login` skips it.
133
+ - Runs each selected and installed agent's own sign-in. `--no-agent-login`
134
+ skips all of them.
135
+ - Installs or repairs one login daemon that watches registered projects.
136
+ - Reports which coding agents are installed and Claude Code's login status.
137
+
138
+ Existing board entries are preserved, including custom commands and disabled
139
+ entries. Other servers and settings are kept; Codex and opencode comments
140
+ are kept too. A file that cannot be safely extended is reported without being
141
+ overwritten. Codex inline tables such as `mcp_servers = {...}` may need to be
142
+ converted to TOML sections before adding a new board entry.
143
+
144
+ One exception to "preserved": an entry written by an older version of this
145
+ package is recognised as its own and upgraded in place on the next `glytch
146
+ init` or `viewable doctor`. That covers both shapes it could be in - an entry
147
+ that spawns a local command, which is rewritten to the hosted URL because no
148
+ such server runs any more, and an entry keyed `board` already sitting at
149
+ the hosted URL, which is renamed to the key this version writes. Any other
150
+ custom entry, including one that merely looks similar, is left untouched.
151
+
152
+ The MCP server key is `viewable`. A `board` entry pointing anywhere but the
153
+ hosted URL is left exactly alone.
154
+
155
+ The board is one hosted connection, so there is no more `PM_BOARD_ROOT` or
156
+ working directory to write into a Codex or opencode board entry, and no
157
+ per-machine path to update if you copy a config or move the project - the URL
158
+ is the same everywhere.
159
+
160
+ What each agent still needs once, per project, is its own sign-in, and `init`
161
+ now runs it for you rather than printing it. Each agent keeps its own token
162
+ under its own account; there is no shared credential written into any config
163
+ file. Claude Code's shell verb needs 2.1.186 or newer - below that, `init`
164
+ prints the `/mcp` step to run inside a session. An agent that already holds a
165
+ token is skipped, except Codex, which does not report whether it holds one,
166
+ so its sign-in runs every time and short-circuits itself. With no terminal,
167
+ or with `--no-agent-login`, `init` prints each command instead of running it,
168
+ and `viewable doctor` names the same step again if you skip it.
169
+
170
+ `viewable doctor` and background repair maintain the saved selection. Changing
171
+ it does not uninstall an existing connection; it stops maintaining deselected
172
+ agents. Older installs retain their legacy Claude/opencode repair behavior
173
+ until `viewable init` saves choices. An invalid selection file stops repair
174
+ rather than silently choosing other agents.
175
+
176
+ ### Background runner
177
+
178
+ | Platform | Login daemon |
179
+ | --- | --- |
180
+ | macOS | LaunchAgent at `~/Library/LaunchAgents/ai.glytch.runner.plist` |
181
+ | Windows | Per-user onlogon task `ai.glytch.runner`, registered from `~/.glytch/ai.glytch.runner.xml`, running `~/.glytch/ai.glytch.runner.cmd` |
182
+ | WSL / Linux | User service at `~/.config/systemd/user/ai.glytch.runner.service` |
183
+
184
+ The daemon carries `PM_SPAWN_RUNNER=on` and your shell's PATH. It runs approved
185
+ board requests using your coding agent subscription; installing the bridge
186
+ itself does not make a model call. `viewable stop` stops the daemon. On Linux or
187
+ WSL without a user systemd service, setup prints the manual command:
188
+
189
+ ```bash
190
+ PM_SPAWN_RUNNER=on viewable runner watch
191
+ ```
192
+
193
+ On Windows the task is registered from an XML definition rather than from
194
+ `schtasks /create /sc onlogon /tr`: the command line names no principal, so
195
+ Task Scheduler registers the logon trigger for the Users group, and writing
196
+ an all-users task into the root of the library needs an administrator. The
197
+ XML names your own account on the trigger and on the principal
198
+ (`InteractiveToken`, `LeastPrivilege`), which is a registration you may make
199
+ yourself. If Task Scheduler still refuses it - a policy, or a task of that
200
+ name owned by another account - `viewable doctor` says so and names the two
201
+ ways out instead of sending you back to `viewable init`: run `viewable init` once
202
+ from a terminal opened with **Run as administrator**, or skip the daemon and
203
+ run the runner by hand:
204
+
205
+ ```powershell
206
+ $env:PM_SPAWN_RUNNER="on"; viewable runner watch
207
+ ```
208
+
209
+ or, in `cmd.exe`:
210
+
211
+ ```bat
212
+ set PM_SPAWN_RUNNER=on && viewable runner watch
213
+ ```
214
+
215
+ ### MCP-only setup by hand
216
+
217
+ For board tools without the background runner, install the package and add
218
+ one of these project configurations directly, then sign in. There is no path
219
+ or working directory to fill in: the board is one hosted URL, the same on
220
+ every machine.
221
+
222
+ Claude Code, in `.mcp.json`:
223
+
224
+ ```json
225
+ { "mcpServers": { "viewable": { "type": "http", "url": "https://app.viewable.one/mcp" } } }
226
+ ```
227
+
228
+ Then, in a session on this project, run `/mcp` and authenticate the board
229
+ connection.
230
+
231
+ Codex, in `.codex/config.toml`:
232
+
233
+ ```toml
234
+ [mcp_servers.viewable]
235
+ url = "https://app.viewable.one/mcp"
236
+ ```
237
+
238
+ Then sign in: `codex mcp login viewable`.
239
+
240
+ opencode, in `opencode.json` or `opencode.jsonc`:
241
+
242
+ ```json
243
+ { "mcp": { "viewable": { "type": "remote", "url": "https://app.viewable.one/mcp", "enabled": true } } }
244
+ ```
245
+
246
+ Then sign in: `opencode mcp auth viewable`.
247
+
248
+ Configuration references: [Codex MCP](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)
249
+ and [opencode MCP](https://opencode.ai/docs/mcp-servers/).
250
+
251
+ ## The board tools
252
+
253
+ Fifteen tools reach the board from a session, over the hosted endpoint. They
254
+ work with no app open at all; what to authenticate with when no browser is
255
+ signed in is in [Reading the board with no app
256
+ open](#reading-the-board-with-no-app-open).
257
+
258
+ | tool | what it does |
259
+ | --- | --- |
260
+ | `get_board(board_id?, tag?, group?, kind?, since_days?, match?, limit?, view?, geometry?, detail?)` | The board as a readable digest: ids, one line per idea, groups, edges, question state, open flags, ages. Slim by default. The filters narrow it, and the reply always says what it left out. `view:"full"` returns everything, `geometry:true` adds positions and sizes. |
261
+ | `get_node(id)` | One node in full from the graph mirror, with its incoming and outgoing edges. Images attached to the idea come back as image blocks to look at, each labelled with the node and field it came from. An image the mirror does not hold is named as unavailable rather than quietly left out. |
262
+ | `get_library()` | The component library a DRAWN idea is written against, generated from the same registry the app renders with: every tag, its attributes, which are required, a working example, and the rules a drawing must keep. Read it once before passing `snippet` on `addIdea` or `addTextNote`. About 2k tokens, so once per session, not once per drawing. |
263
+ | `propose(ops[], label?)` | Sends board change requests and returns its op id. Every create, edit and delete lands as a proposal you accept or reject. |
264
+ | `ask_user(question, why, options?, from_id?)` | Adds a question to an idea's conversation and returns at once; the answer comes back through `check_answer` or `collect_session`. `why` is required: it is shown under the question and is the context that travels with it. With no `from_id` the question goes on the idea the run belongs to. |
265
+ | `check_answer(question_id, wait_s?)` | Where an earlier question stands: answered with the decision, still open, dismissed, or not yet ingested. With `wait_s` (up to 50) it waits that long for the answer and returns as soon as it lands; an agent that cannot go on without the answer keeps calling it until it is answered or dismissed. |
266
+ | `report(did[], decide?, flags?, title?)` | The end of session report, as ideas rather than a paragraph. See below. |
267
+ | `collect_session(since?, key?)` | What you have said about this session's run: answered questions with the decision, questions still open, and notes on any idea the run produced. Pass `since` for only what is new. |
268
+ | `set_brief_status(id, status, result?)` | Moves a brief through draft, sent or done. Done needs a one line result, which lands as an idea. This applies without a proposal, the way a comment does. |
269
+ | `list_sessions()` | Every run row on this account, in one of three states: open to messages, running one-shot, or finished. Only the first can be talked to. |
270
+ | `mark(kind, ids[], why, evidence?)` | Points at one or more ideas that deserve attention, with a reason and a receipt. It changes nothing on the board: the mark draws as a badge and lands in your inbox, where you act on it or dismiss it. `why` and the evidence are required - a mark is read far from the turn that made it. A pair you already dismissed is not raised again until one of the ideas changes. |
271
+ | `get_log(since?, limit?)` | The calling agent's own log: one line per turn it took, what it read and called, what it produced, what it cost, and what you did with the result afterwards - accepted, rejected, dismissed, edited or deleted, with your reason. Its own rows only, never another agent's. |
272
+ | `send_message(to_session_id, message, from)` | Delivers a message into a running persistent session, which its runner hands over as a user turn within a couple of seconds (the row equivalent of a reply file, `bridge/lib/rowsAnswers.mjs`). Refused for a finished session, a one-shot session and an unknown id, each with the reason. |
273
+ | `open_session(title, brief?)` | Accepted and ignored. Session frames are gone, so this opens nothing; it stays so a session that still calls it is told plainly instead of getting an unknown tool error. |
274
+ | `close_session()` | Accepted and ignored, for the same reason. Calling `report` is the whole ending. |
275
+
276
+ `list_sessions` and `send_message` are a PULL, not a push. Nothing holds a
277
+ session's stdin open over the network, so a session is OPEN TO MESSAGES only
278
+ while it keeps calling tools, STALE once it has gone quiet without closing,
279
+ and FINISHED once it has. `send_message` refuses a STALE or FINISHED target,
280
+ and a message to an OPEN one is delivered the next time that session calls
281
+ `collect_session`.
282
+
283
+ There is deliberately no way to answer a question or run an idea from these
284
+ tools. Questions are yours to answer. A run uses your own Claude Code or Codex plan.
285
+
286
+ Every rule these tools state, they also check. A bad call is refused before
287
+ any file is written and every violation comes back in one message, so nothing
288
+ lands half applied on your board: `report`'s counts, lengths and evidence
289
+ rule, `propose`'s op count, call names and `$name` references, `ask_user`'s
290
+ options and required `why`, `set_brief_status`'s status and result.
291
+
292
+ ### Reading the board with no app open
293
+
294
+ Every board read goes over the hosted endpoint. There is no mode to turn on
295
+ and no local board server to start. What decides whether a read has anything
296
+ to authenticate with:
297
+
298
+ 1. **`viewable login`** - opens a browser, you sign in and approve, and the
299
+ credential lands in `~/.glytch/auth.json` (mode 0600; `GLYTCH_HOME` moves
300
+ it). This is the OAuth flow claude.ai's own connector uses
301
+ (`supabase/functions/board-oauth`), driven from a terminal instead of a
302
+ phone. `viewable logout` forgets it.
303
+ 2. **`GLYTCH_BOARD_TOKEN`**, for a script that would rather not open a
304
+ browser: a personal access token minted in Viewable (**Settings,
305
+ Connection, Access tokens** - it starts with `pmb_`, shown once). It wins
306
+ outright over a `viewable login` credential and needs no file.
307
+
308
+ That bearer is what every board tool call uses, including every call made by
309
+ a session the runner started - its own bearer is resolved the same way, once
310
+ per run (see `bridge/SPAWN.md`).
311
+
312
+ If a read says the token was refused or expired, run `viewable login` again, or
313
+ mint a fresh personal access token and set it again. `viewable doctor`'s board
314
+ sign-in row names the account the credential actually reaches, which is worth
315
+ checking when work lands somewhere you are not looking.
316
+
317
+ ### How `ask_user` waits, and `check_answer` collects
318
+
319
+ `ask_user` does not wait. It writes the question note and returns a
320
+ `question_id` (the note's id) at once, because nothing on a stateless HTTP endpoint can hold
321
+ a connection open while you think. `check_answer(question_id)` is the way
322
+ back to that one question's decision, dismissed or open state, and it works
323
+ across restarts because it re-reads the row rather than anything held in
324
+ memory.
325
+
326
+ `collect_session(since?)` is the wider door: everything THIS run's own key
327
+ has asked that now has an answer (every one, every call - there is no time
328
+ filter on an answered question), every message another session sent this
329
+ one (drained, `delivered_at`-stamped, on the way out), and notes written
330
+ on any idea the run produced (these ARE filtered by `since`). Pass the
331
+ `next_since` a previous call returned to see only what is new.
332
+
333
+ **A runner-spawned child gets this pushed, not polled by hand.** While the
334
+ child is still running, the runner itself calls `collect_session` under the
335
+ child's own run key every `PM_ROWS_POLL_MS` (default 5s) and, for anything
336
+ new, writes a message onto the child's stdin the same door a human's typed
337
+ reply uses - an answered question once (the runner's own delivered-set is
338
+ what stops the "every answered question, every call" shape above from
339
+ saying the same ruling to the child twice), a message as `[message from
340
+ <from>] <text>` (`bridge/lib/rowsAnswers.mjs`). After the child exits, ONE
341
+ more `collect_session` call catches anything that arrived in the gap between
342
+ the last live poll and the exit - a persistent session's late answer or
343
+ message becomes a resume request through the same mechanism a reply typed
344
+ into an already-finished session does; a one-shot session has nothing to
345
+ resume into, so it is logged and dropped. See `bridge/SPAWN.md`, "Answers and
346
+ comments coming back", for the full mechanism and its one known gap.
347
+
348
+ ### How `report` lands
349
+
350
+ `report` takes three lists, and each lands differently:
351
+
352
+ - **`did[]`** goes to the board's own Viewable Agent, which reads the board and
353
+ places it: an item about an idea that already exists becomes an edit or a
354
+ tag on it, and the rest land as ideas beside their relatives, all as
355
+ proposals you rule on. The result names each idea and where it went, so
356
+ write each line in words that name the ideas it touches.
357
+ - **`decide[]`** lands each fork as a live question note with its own
358
+ durable `question_id`, and each item needs its own `why`, for the same
359
+ reason `ask_user` does.
360
+ - **`flags[]`** lands each one as a note on the idea it is about.
361
+ Every flag must carry evidence the board cannot produce about itself: a
362
+ commit sha, a `file:line`, a test count, or a shipped on date. A flag
363
+ without one is refused.
364
+
365
+ While the app is closed, the report still lands: `did[]` arrives as machine
366
+ ideas inside one titled frame, drawn rather than dropped as plain text,
367
+ pale until you touch them and with nothing to accept. Nothing waits in a
368
+ queue and nothing is lost.
369
+
370
+ `summary` is a deprecated single blob form, kept for a hand rolled MCP client
371
+ that cannot produce `did`.
372
+
373
+ ## Running sessions from the board
374
+
375
+ The spawn runner is the outside actor that turns "run a session on this" into
376
+ a real process, because a browser cannot spawn one. The full contract, every
377
+ row it reads and writes and every bound it respects, is in
378
+ [SPAWN.md](./SPAWN.md).
379
+
380
+ A run is a row in Postgres, reached through `supabase/functions/board-runner/`
381
+ (`bridge/lib/runnerApi.mjs`), an ordinary session and a chat turn alike.
382
+
383
+ The shape of a run:
384
+
385
+ 1. An idea (or a DM, for a chat turn) files a run row, unapproved.
386
+ It never runs until it is approved.
387
+ 2. Approval is the click in the app, or an agent acting under its own caps.
388
+ 3. A watcher polls `claim`, which atomically hands back the oldest approved,
389
+ unclaimed, pending row on one of this machine's registered projects -
390
+ claiming a row IS the run's start, with nothing left to wait on. It then
391
+ gives the session its own git worktree and starts the engine. A planning
392
+ draft is the exception: it researches read-only in the project checkout,
393
+ so no worktree is made for it. A chat turn is the other exception: it
394
+ runs in the DM's own long-lived chat worktree and mints no tree per
395
+ message.
396
+ 4. The run's card on the board shows it working, driven by the row's own
397
+ `status` and by `run_events` streaming its output live. At the end the
398
+ runner writes ONE `update` carrying the verdict, the cost and the
399
+ sidecar together; `result` is write-once.
400
+ 5. The session reports back through `report` before it finishes (a chat
401
+ turn's answer belongs in the room instead - it proposes nothing to the
402
+ board at all).
403
+
404
+ The runner is off by default and has no default on path: nothing spawns
405
+ without `PM_SPAWN_RUNNER=on`, which the login daemon carries in its own file
406
+ so it is set once at install rather than once per session. The MCP server does
407
+ not import the runner, so a session cannot start one however it is prompted.
408
+
409
+ ### The engines
410
+
411
+ | engine | what it is |
412
+ | --- | --- |
413
+ | `claude` | Claude Code, and the default. Fenced by a named tool allowlist. |
414
+ | `opencode` | opencode, run with `--auto`, which approves anything not explicitly denied. Select opencode during `viewable init` to register its project board connection. |
415
+ | `codex` | Codex, through its own app-server protocol and your signed-in Codex account. Sandboxed to workspace writes, and it never approves an escalation. Like every engine, its receipts show turns and time, never a cost. See [CODEX.md](CODEX.md). |
416
+ | `acp-claude` | The same Claude Code reached through the Agent Client Protocol adapter instead of the CLI. Same allowlist, answered at the permission door rather than passed as a flag. The cost on its receipt is an estimate, because ACP reports tokens and no dollars, and it is the least exercised of these. |
417
+
418
+ An unknown engine name falls back to the default rather than failing, and the
419
+ receipt says which one ran, so a typo runs something rather than nothing.
420
+
421
+ "Fenced" is narrower than it sounds: the allowlist includes `Bash(node:*)` and
422
+ `Bash(npx:*)`, so a Claude session can run arbitrary code too, and both
423
+ engines can write to any path the process can reach. What bounds the damage is
424
+ the git worktree and the wall clock. opencode also
425
+ namespaces the board tools as `glytch_get_board`, `glytch_report`,
426
+ `glytch_ask_user`, so
427
+ telling an opencode session to call `report` names something that does not
428
+ exist.
429
+
430
+ ### Planning drafts
431
+
432
+ A run can ask for a plan instead of an implementation. The session researches
433
+ read-only, writes nothing, and returns the plan as its final answer: findings
434
+ in their own sections, then the work under an `## Implementation plan`
435
+ heading with one `### Step N: ...` heading per item. The app reads that shape
436
+ to lay the plan out for you to edit, approve or reject. An approved plan is
437
+ handed to a second session as the exact instructions to follow.
438
+
439
+ ### Reference images
440
+
441
+ An idea can carry images: screenshots, mockups, anything on it or on an idea
442
+ wired into it. Every attached image is delivered or the run is refused before an
443
+ agent starts and before anything is billed, and the refusal names which
444
+ attachment and why. Nothing is silently dropped.
445
+
446
+ - PNG, JPEG, GIF and WebP are supported. Another format, or damaged data,
447
+ refuses the run.
448
+ - Claude Code streaming, Codex and ACP take the images as native image
449
+ content. An engine that has no image channel gets the files written beside
450
+ the run instead, with their paths listed in the prompt.
451
+ - An ACP adapter that does not advertise image support refuses the turn
452
+ rather than sending the text without the pictures.
453
+ - Sessions are told to inspect each reference and state what they see before
454
+ planning or editing, and to compare finished visual work against it.
455
+
456
+ Delivery is checked all the way to the engine. Whether the model then reads
457
+ the picture correctly is the model's own business, so treat a session's
458
+ description of a reference as a claim to verify, not a guarantee.
459
+
460
+ ### What it costs
461
+
462
+ Installing the package and exchanging board files make no model calls.
463
+ Your coding agent can still bill for its own work, and the board agent may
464
+ use a model to place reports.
465
+
466
+ Turning on the runner is different: from that point the board can start coding
467
+ agents on your machine, and they bill your subscription. The bounds are real
468
+ and they stay on. Nothing spawns without `PM_SPAWN_RUNNER=on`, nothing starts
469
+ without an approval, each implementing session gets its own git worktree (a
470
+ read-only planning draft stays in the project checkout), a failed session is
471
+ not retried, and editing an idea does not re-run it. Rerun is its own press.
472
+
473
+ One wall bounds a single run: the wall clock, 90 minutes by default and 2
474
+ hours at most. Past it the process is killed. There is no spend or token
475
+ limit on a run; the free AI that comes with a new account is the only limit,
476
+ and it applies to Viewable's free AI, not to your own subscription.
477
+
478
+ What you do not get is a confirmation box per run, deliberately: a dialog you
479
+ click through fifty times is not a safety feature. The cost of a careless run
480
+ is real money, and the run happens the moment you ask for it.
481
+
482
+ ## Sessions in a git worktree
483
+
484
+ Dispatched sessions run in their own git worktree by default (read-only
485
+ planning drafts being the exception), and a session's board entry is the
486
+ hosted URL - `{"type":"http","url":"https://app.viewable.one/mcp"}`, or
487
+ `{"type":"remote", ...}` on opencode. There is no local process and no folder
488
+ to point it at, so a session reaches the board over the network the same way
489
+ whichever worktree it is standing in.
490
+
491
+ `PM_BOARD_ROOT` is how you point the runner, or a `bridge/lib` script, at a
492
+ project folder that is not its working directory's: set it in the runner's own
493
+ environment (the login daemon's file, or `PM_SPAWN_RUNNER=on
494
+ PM_BOARD_ROOT=... viewable runner watch` by hand).
495
+
496
+ A session reports by calling the `report` tool. There is no fallback behind
497
+ it: a session that never calls it has told the board nothing.
498
+
499
+ ## Commands
500
+
501
+ | command | what it does |
502
+ | --- | --- |
503
+ | `viewable init` | Choose coding agents and connect this project. Safe to run twice. |
504
+ | `viewable init --agents claude,codex` | Configure the named agents without a prompt. |
505
+ | `viewable init --yes` | Use saved choices or detected agents. |
506
+ | `viewable login` | Sign in to the hosted board over OAuth. See ["Reading the board with no app open"](#reading-the-board-with-no-app-open). |
507
+ | `viewable logout` | Forget the signed-in credential (`~/.glytch/auth.json`). |
508
+ | `viewable doctor` | Grouped Project, Runner, Coding agents and System checks. Labels are `OK`, `WARN` or `FAIL`; exit 0 when no check fails. |
509
+ | `viewable forget` | Stop watching this folder. Nothing inside it is deleted: `.mcp.json`, `.glytch/` and every session it holds stay. |
510
+ | `viewable stop` | Stop the login daemon. The file it runs from stays on disk, so `viewable init` brings it back without a reinstall. |
511
+ | `viewable uninstall` | Every registered project, the login daemon, this machine's board sign-in, and `~/.viewable` - one command. Confirms once; `--project`, `--keep-login`, `--dry-run` and `--yes` narrow or skip pieces of it. |
512
+ | `viewable runner ...` | The spawn runner. `watch` with no `PM_BOARD_ROOT` watches every registered folder; with it set, it watches that one. |
513
+ | `glytch mcp` | Retired. Prints one line pointing at `viewable init` and `viewable login`, and exits 1. There is no local board MCP server to start. |
514
+ | `viewable --version` | The installed version. |
515
+
516
+ `pm-spawn-runner` is installed as an alias of `viewable runner`, so anything
517
+ already wired to that name keeps working. The second bin alias every version
518
+ up to 0.6.0 installed is gone from 0.7.0, with the local MCP server it
519
+ started.
520
+
521
+ The runner's own subcommands act on `PM_BOARD_ROOT` or the folder you are
522
+ standing in: `watch` polls and runs what is approved, `once` does one pass
523
+ instead of a loop, and `merge`/`delete-worktree`, each taking a worktree
524
+ path, finish with a session's branch by hand.
525
+
526
+ `request`, `approve`, `status`, `show` and `release` are retired. They filed,
527
+ approved, listed, inspected and released a run as a FILE; a run is a row now,
528
+ and this CLI holds no Supabase JWT to reach the app's own
529
+ `run_request`/`run_approve` RPCs directly. Each prints a one-line pointer at
530
+ `supabase/functions/board-runner/` and exits 1. File and approve a run from
531
+ the board instead.
532
+
533
+ Unchanged one-shot worktrees are removed after a run. Worktrees containing
534
+ commits or uncommitted changes are kept for review. If Git cannot verify the
535
+ changes or comparison base, cleanup keeps the worktree and reports why.
536
+ Persistent prompt sessions and agent chat worktrees stay available for resume;
537
+ a question-only turn does not end that conversation or delete its workspace.
538
+
539
+ Run `viewable update` (`viewable.cmd update` in PowerShell) to install the latest
540
+ published package and restart the computer's connection. It verifies the CLI
541
+ from a new normal terminal before touching the service (with Volta, through
542
+ `volta which viewable`), switches an old npx
543
+ service to the persistent installation, and restarts even when the script path
544
+ and version stay the same. It waits for a successful hosted heartbeat from a
545
+ new process reporting that version before claiming success. Folders, sign-in
546
+ and agent configuration are kept; it does not run init or an agent session.
547
+
548
+ What changed in each version is on the
549
+ [release notes](https://glytch.ai/release-notes) page. To check an update
550
+ worked, open Settings in the app: each connected computer shows the version it
551
+ runs.
552
+
553
+ Older versions (including 0.11.0) have no update command. If `viewable` is missing
554
+ or update is unrecognized, first install with `npm i -g viewable@latest` (Windows:
555
+ `npm.cmd i -g viewable-one@latest`), make the bin directory persistent as above, and
556
+ verify `viewable -v` in a new terminal. Then use `viewable update` once a published
557
+ version supports it. This repository change does not publish that version.
558
+
559
+ The daily version checker stays notice-only. Installation, PATH, restart or
560
+ connection failures report partial completion and a next step; retry update
561
+ after fixing them. Without a working user service (including older WSL without
562
+ systemd), update does not report a verified connection. No command here
563
+ publishes npm or redeploys the app or Supabase functions.
564
+
565
+ `init` also verifies the persistent CLI, compares the loaded process's version
566
+ proof and replaces an older or unverified loaded process even at the same path.
567
+ A process that has attested the installed version is left running, including
568
+ while busy or waiting for its next heartbeat. It waits for its folder's fresh
569
+ successful heartbeat before printing that it is watching. Setup copied from
570
+ the app carries a non-secret attempt id; only a matching verified CLI version
571
+ and that attempt's heartbeat completes the setup screen. Local `cli.json` and
572
+ `health.json` under `~/.viewable` hold version/process proof without credentials.
573
+
574
+ ## Where things live
575
+
576
+ - `~/.glytch/folders.json`, the registry of folders the daemon watches, and
577
+ `~/.glytch/logs/runner.log`, rotated at 5 MB with three kept. `GLYTCH_HOME`
578
+ moves both, and the daemon file with them.
579
+ - `.glytch/agents.json`, the project's saved coding-agent selection.
580
+ - The selected agents' project configs, from the setup table above.
581
+ - The login daemon's own file for your platform, from the table above.
582
+ `GLYTCH_NO_DAEMON_INSTALL=1` writes it and registers nothing.
583
+ - `.glytch/images/<id>/` in the linked folder, where an image on its way to a
584
+ run is staged, and the repo-map cache beside it. Both are this machine's own
585
+ scratch. Nothing in this package writes a board file: every board write and
586
+ every run is a row, reached over the hosted endpoint.
587
+
588
+ A second project needs no restart: `viewable init` in another folder adds it to
589
+ the registry, and the daemon is watching that file, so it starts watching the
590
+ new folder within seconds without touching sessions already running elsewhere.
591
+ `viewable forget` is the other half. Nothing needs `launchctl`, `schtasks` or
592
+ `systemctl` by hand.
593
+
594
+ ## Troubleshooting
595
+
596
+ `viewable doctor` is the first thing to run. It groups checks under Project,
597
+ Runner, Coding agents and System, and exits 0 only when no check fails. The
598
+ `OK`, `WARN` and `FAIL` labels remain visible without color; interactive
599
+ terminals color them, while piped output is plain. Set `NO_COLOR` to disable
600
+ styling (`NO_COLOR` wins over `FORCE_COLOR`). Run it inside the linked project
601
+ folder, for example `cd ~/Projects/my-project`; from your home folder doctor
602
+ prints a warning because the registry, mirror and heartbeat may belong to a
603
+ different project.
604
+
605
+ The daemon checks are separate: `daemon loaded` means the operating system is
606
+ holding the login service, and `daemon beating` means the board has heard from
607
+ it about this folder recently - which is also how you know the folder is
608
+ linked. Immediately after `viewable init`, a loaded daemon may need a few seconds
609
+ before its first beat; rerun doctor after that grace period. The rows cover the
610
+ platform, folder and agent configuration, daemon PATH, daemon state and
611
+ version, the heartbeat, the board sign-in and which account it reaches, coding
612
+ agents, Claude Code login, git and the npm version.
613
+
614
+ Common failures, and what the row says:
615
+
616
+ | row | what it means, and what to do |
617
+ | --- | --- |
618
+ | `daemon beating never` | The daemon is loaded but has not checked this folder yet, or this folder is not registered. Wait a few seconds and rerun doctor; if it persists, run `viewable init` from this project folder. |
619
+ | `daemon beating stopped` | This folder has not been reported recently; a stale beat alone does not prove the daemon is stopped. If it persists, restart with `viewable stop` then `viewable init --yes`, and inspect the runner log. |
620
+ | `board sign-in signed in as <someone else>` | The credential works, but it reaches a different account from the board you are watching. `viewable logout` then `viewable login` as the right one. |
621
+ | `daemon loaded not loaded` | The OS does not have `ai.glytch.runner`. Run `viewable init` from this project folder. |
622
+ | `daemon loaded no systemd here` | An older WSL with no `systemctl --user`. Run `PM_SPAWN_RUNNER=on viewable runner watch` instead. |
623
+ | `coding agents none` | "no coding agent on PATH - install Claude Code, opencode or Codex". |
624
+ | `Claude Code login logged out` | "run `claude` once and log in, or sessions fail at spawn". |
625
+ | `git missing` (a warning) | "no git - sessions run unisolated in the linked folder, with no worktree of their own". On Windows the row adds that Claude Code's Bash tool needs Git for Windows. |
626
+ | `.mcp.json unreadable` | The file exists and is not JSON glytch can extend. It is your work, so nothing was written: add the `viewable` server by hand. |
627
+ | `version unknown` | npm did not answer. Being offline is not a broken install. |
628
+
629
+ One failure doctor cannot see: an opencode session whose board tools do
630
+ nothing. Run `viewable init` in the folder and include opencode in the
631
+ selection. opencode namespaces the tools as `glytch_get_board`,
632
+ `glytch_report`, `glytch_ask_user` and so on, so a session told to call
633
+ `report` or `get_board` by their plain names finds nothing there.
634
+
635
+ ## For maintainers
636
+
637
+ `npm ci` then `npm test` inside `bridge/` runs the suite on Node's built in
638
+ runner: the CLI's commands, the doctor rows, the wall clock, and the spawn
639
+ runner end to end. It also covers the hosted board tools in
640
+ `supabase/functions/board-mcp/tools.mjs`, which live in the app's repository
641
+ but are exercised from here. The spawn runner's tests never touch a real
642
+ account, because `PM_CLAUDE_BIN` points at a fake binary that exits, hangs or
643
+ floods on demand. CI runs everything on `ubuntu-latest` and `windows-latest`,
644
+ and both block.
645
+
646
+ Versions follow semver over the commands, their flags, and what the runner
647
+ promises a session, not the internals: renaming a command, dropping a flag or
648
+ changing that promise is breaking, and rewriting how one of them does its job
649
+ is not. The board tools are served from the hosted endpoint and ship on their
650
+ own schedule, not with this package. [CHANGELOG.md](./CHANGELOG.md) is the
651
+ record, written by hand from the commit bodies.
652
+
653
+ One workflow publishes, **release glytch**, and it has two ways in.
654
+
655
+ The usual one is the Actions tab, Run workflow, and a `patch`, `minor` or
656
+ `major` bump. It installs dependencies, runs tests, updates
657
+ `bridge/package.json` and `bridge/package-lock.json`, rotates the changelog's
658
+ `## [Unreleased]` section into a dated version, commits, tags
659
+ `glytch-v<version>`, pushes and publishes, so pull afterwards. The bump is
660
+ refused when nothing sits under `## [Unreleased]`: a version with no changelog
661
+ entry is a release nobody can read. `scripts/release-glytch.mjs` with a bump
662
+ prepares that same commit locally.
663
+
664
+ The other is a tag, for a version already written into `package.json` and
665
+ already dated in the changelog - there is nothing left to bump, so pushing the
666
+ tag is the whole release:
667
+
668
+ ```bash
669
+ git tag -a glytch-v0.8.0 -m "glytch v0.8.0"
670
+ git push origin glytch-v0.8.0
671
+ ```
672
+
673
+ The prefix changed on 2026-09-14 when the release path took the package's
674
+ name. Tags cut before then carry the old prefix; the workflow no longer
675
+ answers to them, and they stay in the history as they were cut.
676
+
677
+ It checks the tag against `package.json` before it publishes, because a tag
678
+ pushed by hand can disagree with the file and a publish is the wrong place to
679
+ find that out.
680
+
681
+ Neither path needs an npm token. Publishing is over OIDC trusted publishing,
682
+ switched on once by hand on npmjs.com under the package's Publishing access.
683
+
684
+ Nothing publishes on a bridge commit. This package is what a stranger
685
+ installs, the runner spends that stranger's subscription, and a publish cannot
686
+ be taken back.
687
+
688
+ ### Developing against a local app
689
+
690
+ `GLYTCH_APP_URL=http://localhost:<port> viewable login` works once the app's dev server (`npm run dev`, or `npx vite --port <port>`
691
+ from the repo root) is up - `vite.config.ts` generates a dev-server proxy
692
+ from `routes.json`'s own rewrites (`vite/routeProxy.ts`), so the discovery
693
+ document, `/oauth/*` and `/mcp` reach the real, deployed Supabase functions
694
+ instead of falling through to the SPA's `index.html`. The discovery
695
+ document itself still names the production issuer
696
+ (`https://app.viewable.one`) no matter what port the app is running on -
697
+ `board-oauth`'s issuer is a deploy-time config value, not derived from the
698
+ request - so this doesn't stand up a local OAuth server; it's for testing
699
+ the local app's UI against the real, deployed board while signed in.
700
+
701
+ ### Automatic preview tunnel setup
702
+
703
+ Installing the package does not download Cloudflare's `cloudflared`; there is no
704
+ install script. The runner downloads it the first time a shared preview starts,
705
+ so that first preview needs the network. If the download fails, the preview does
706
+ not start and the next one tries again; nothing else in the runner needs it.
707
+ No Homebrew, administrator access, or system service is needed.
708
+
709
+ Automatic downloads support macOS x64/ARM64, Linux x64/ARM64, and Windows x64.
710
+ Windows ARM64 has no native binary in this upstream release; unsupported systems
711
+ can supply a compatible executable through `PM_CLOUDFLARED_BIN`. WSL uses Linux.
712
+
713
+ Official release `2026.8.3` assets and SHA-256 digests are pinned in
714
+ `lib/cloudflared-release.json`, from the Cloudflare GitHub release API. Downloads
715
+ are bounded and verified before use, then cached under the user's home directory
716
+ at `.glytch/bin/cloudflared/<version>/<platform>-<arch>/`. Cached content is
717
+ verified and a changed executable is repaired from the verified asset. The
718
+ `PM_CLOUDFLARED_BIN` override bypasses managed setup. Updating the pinned release
719
+ requires a package update. Downloading it does not open a tunnel; startup still
720
+ requires the board-member authorization configuration.
721
+
722
+ Shared previews also require the deployed `preview-grant` authorization
723
+ function and runner configuration: set its public verifier JWK in
724
+ `PREVIEW_VERIFY_JWK` and the
725
+ exact app origins in `PM_PREVIEW_APP_ORIGINS` (comma-separated HTTPS origins;
726
+ localhost loopback is allowed for local development). The tunnel binary alone
727
+ does not provide preview access, and missing configuration fails closed.
728
+
729
+ ### Reading setup and health checks
730
+
731
+ `viewable init` and `viewable doctor` group output into Project, Runner, Coding agents,
732
+ and System sections. OK, WARN and FAIL labels remain visible without color;
733
+ interactive terminals add green, yellow and red. Piped output is plain by default.
734
+ `NO_COLOR` disables styling, including when `FORCE_COLOR` is set. Failure summaries
735
+ count the checks that need attention and include a separate Next steps section.
736
+
737
+ Run project commands from the folder linked to your board. A prominent warning
738
+ appears when the current directory is your home folder. A loaded daemon with no
739
+ heartbeat may still be starting; doctor distinguishes that from an unloaded daemon.
740
+
741
+ ## License
742
+
743
+ MIT. See [LICENSE](./LICENSE).
744
+
745
+ ## Moving from Glytch
746
+
747
+ If you already installed `glytch` globally, run
748
+ `npm install -g viewable-one@latest --force`, then use `viewable` (or the
749
+ compatible `glytch` command). The flag lets npm replace the old command aliases.
750
+ For a clean install, use `npm install -g viewable-one@latest`.
751
+ The last `glytch` package forwards to `viewable-one`. Saved settings, tokens,
752
+ projects and runs stay in `~/.glytch` and each project’s `.glytch` folder;
753
+ existing service and board connection names stay valid. No setup is lost.
754
+ Every `VIEWABLE_*` environment variable takes precedence over its matching
755
+ `GLYTCH_*` variable, which remains supported. New connections use
756
+ `https://app.viewable.one/mcp`; existing saved addresses keep working.