fvtt-world-cli 1.1.1 → 1.1.2

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 CHANGED
@@ -1,27 +1,21 @@
1
1
  # World CLI for Foundry VTT
2
2
 
3
- [![Foundry VTT v13–v14](https://img.shields.io/badge/Foundry%20VTT-v13%E2%80%93v14-ff6400)](https://foundryvtt.com)
3
+ [![Supported Foundry versions](https://img.shields.io/endpoint?url=https%3A%2F%2Ffoundryshields.com%2Fversion%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2FDroiD16%2Ffvtt-world-cli%2Fmain%2Fpackages%2Ffoundry-module%2Fmodule.json)](https://foundryvtt.com)
4
+ [![npm](https://img.shields.io/npm/v/fvtt-world-cli)](https://www.npmjs.com/package/fvtt-world-cli)
5
+ [![npm downloads](https://img.shields.io/npm/dm/fvtt-world-cli)](https://www.npmjs.com/package/fvtt-world-cli)
6
+ [![license](https://img.shields.io/github/license/DroiD16/fvtt-world-cli)](LICENSE)
4
7
 
5
8
  Tell your AI agent what should change in your Foundry VTT world, and it happens in the live
6
9
  world, validated by Foundry, visible to your players immediately.
7
10
 
8
- ```text
9
- You → agent: "The scimitar in Valeros's inventory should burn targets on hit.
10
- Add a Flaming effect to it."
11
-
12
- agent → fvtt-world-cli:
13
- actor list --name Valeros find the character
14
- actor item list --name scimitar find the sword in their inventory
15
- actor item effect create … add the effect to it
16
-
17
- agent → you: "Done. Flaming effect added, already live in the world."
18
- ```
11
+ ![An AI agent adds lights and goblins to a Foundry tavern scene, shown before and after](https://raw.githubusercontent.com/DroiD16/fvtt-world-cli/main/publishing/media/cover.webp)
19
12
 
20
13
  Modern agents already understand requests like that. What they have lacked is a safe way into
21
- Foundry: driving the browser UI is brittle and token consuming, and editing world files on disk
22
- bypasses everything Foundry does to keep a world consistent. fvtt-world-cli is the missing bridge,
23
- a command line wired into your open GM session that performs every change through Foundry's own
24
- APIs, exactly as if a GM had made it in the UI.
14
+ Foundry. Driving the browser UI is brittle and uses extra tokens. Editing world files on disk
15
+ bypasses Foundry's validation.
16
+
17
+ World CLI for Foundry VTT connects your agent to your open GM session. Every change runs through
18
+ Foundry's own APIs, exactly as if a GM had made it in the UI.
25
19
 
26
20
  Two things it's not. It is not an AI game master: it does not run the game, it only edits the
27
21
  world when asked. And it includes no AI of its own: you connect the agent you already use, such as
@@ -40,12 +34,12 @@ A few asks it handles end to end:
40
34
  - "Turn the bestiary goblin into a flying one that throws dynamite for 2d6 damage, and add it to
41
35
  the scene."
42
36
 
43
- [Commands](docs/commands.md) maps the full surface.
37
+ See [Commands](docs/commands.md) for command families and usage examples.
44
38
 
45
39
  ## Setting up
46
40
 
47
- The simplest setup is one step: point your AI agent at this repository and ask it to set
48
- fvtt-world-cli up. Then follow its lead. It will most likely ask you to install the module in
41
+ The simplest setup is one step: point your AI agent at this repository and ask it to set up
42
+ World CLI for Foundry VTT. Then follow its lead. It will most likely ask you to install the module in
49
43
  Foundry and click *Pair* there, and it handles the rest itself.
50
44
 
51
45
  ### Manual setup
@@ -73,6 +67,9 @@ agent works with the world.
73
67
  fvtt-world-cli bridge serve
74
68
  ```
75
69
 
70
+ Keep this terminal open and use a second terminal for the remaining commands. Keep the target
71
+ world open in Foundry, logged in as a GM.
72
+
76
73
  4. Pair the browser: choose *Pair* in the module's Authorization window, reachable from the
77
74
  *World CLI* group in the scene controls or from the module's settings. Then approve the request
78
75
  from the terminal. It shows the requesting origin, world, GM, and browser, and asks for a yes
@@ -82,7 +79,10 @@ agent works with the world.
82
79
  fvtt-world-cli auth
83
80
  ```
84
81
 
85
- 5. Optionally, install the packaged skill into your AI agent; the next section explains what it
82
+ 5. Check the connection in Foundry. The *World CLI* icon in the scene controls turns green when
83
+ connected. Open *Bridge status* from the same group to see the connection details.
84
+
85
+ 6. Optionally, install the packaged skill into your AI agent; the next section explains what it
86
86
  does:
87
87
 
88
88
  ```bash
@@ -106,11 +106,9 @@ instructions survive updates.
106
106
 
107
107
  ## Built to be trusted with a live world
108
108
 
109
- The whole design assumes an automated caller that must not be able to exceed its intended
110
- authority; [Security](docs/security.md) covers the boundaries in full. In short:
109
+ You control which browser connects and which commands the agent may run. The main safeguards are:
111
110
 
112
- - Everything stays on your machine: the daemon accepts loopback connections only, and there is no
113
- internet-facing mode.
111
+ - The daemon accepts connections only from your machine, with no internet-facing mode.
114
112
  - Nothing connects without your approval: every browser pairs once through an explicit yes at your
115
113
  terminal, and no secrets are printed along the way.
116
114
  - Foundry remains the authority: every change runs through the same Document APIs and validation
@@ -119,21 +117,23 @@ authority; [Security](docs/security.md) covers the boundaries in full. In short:
119
117
  validation and guards as a real call and stops before mutation.
120
118
  - Every command has a permission in the active Foundry client: run, ask the GM, or refuse.
121
119
  Destructive commands ask the GM by default.
122
- - There is no arbitrary-code path: commands are typed and validated on both sides of the transport,
123
- and executable content such as scripted region behaviors is blocked on every write route.
124
- - File access is confined to the active world's managed assets and always excludes its manifest,
125
- databases, and packs.
120
+ - Commands are typed and validated on both sides of the connection. Macro execution and regions
121
+ that trigger macros are disabled by default and require the GM to enable dedicated commands.
122
+ Region behaviors that execute script code are blocked on every write route.
123
+ - File commands read assets within Foundry's managed `data` source. Writes are limited to the
124
+ active world's asset folders and always exclude its manifest, databases, and packs.
125
+
126
+ [Security](docs/security.md) explains these safeguards and their limits.
126
127
 
127
128
  ## Foundry compatibility
128
129
 
129
- Supported on Foundry VTT v13 and v14 (both verified). Version-dependent behavior is
130
- capability-gated and reported per command; [Compatibility](docs/compatibility.md) lists the
131
- differences that matter to operators.
130
+ Supported and verified on Foundry VTT v13 and v14. Some commands are available only on certain
131
+ Foundry versions. See [Compatibility](docs/compatibility.md) for the differences.
132
132
 
133
133
  ## Documentation
134
134
 
135
- [docs/README.md](docs/README.md) maps the full set: commands, protocol, security, architecture,
136
- compatibility, and the agent skill. The same documents ship inside the package and are printed by
137
- `fvtt-world-cli docs [document]`, so the installed CLI is self-describing offline.
135
+ The [documentation index](docs/README.md) links to the command reference, setup guides, security,
136
+ compatibility, and technical documentation. These documents also ship with the CLI. Read them
137
+ offline with `fvtt-world-cli docs [document]`.
138
138
 
139
139
  Bug reports and questions go to [GitHub Issues](https://github.com/DroiD16/fvtt-world-cli/issues).
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { readFileSync as readFileSync2 } from "node:fs";
10
10
  import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
11
11
 
12
12
  // ../protocol/src/constants.js
13
- var PROTOCOL_VERSION = "1.1.1";
13
+ var PROTOCOL_VERSION = "1.1.2";
14
14
  var PROTOCOL_COMPONENTS = Object.freeze({
15
15
  MODULE: "module",
16
16
  CLI_DAEMON: "cli-daemon",
@@ -228,7 +228,8 @@ var ERROR_CODES = Object.freeze({
228
228
  APPROVAL_TIMEOUT: "APPROVAL_TIMEOUT",
229
229
  APPROVAL_CANCELLED: "APPROVAL_CANCELLED",
230
230
  APPROVAL_QUEUE_FULL: "APPROVAL_QUEUE_FULL",
231
- APPROVAL_UNKNOWN: "APPROVAL_UNKNOWN"
231
+ APPROVAL_UNKNOWN: "APPROVAL_UNKNOWN",
232
+ APPROVAL_STALE: "APPROVAL_STALE"
232
233
  });
233
234
 
234
235
  // ../protocol/src/schemas/shared.js
package/docs/README.md CHANGED
@@ -1,71 +1,15 @@
1
1
  # Documentation
2
2
 
3
- The canonical command is `fvtt-world-cli`. The shorter `worldctl` executable is an equivalent bin
4
- entry for the same CLI and may be used in interactive workflows.
5
-
6
- fvtt-world-cli exposes a local, authenticated command line for inspecting and changing a live
7
- Foundry world through Foundry's own APIs.
8
-
9
- Authorization uses one-time pairing; the first-run flow is covered in
10
- [Getting started](getting-started.md). Config defaults are `$XDG_CONFIG_HOME/fvtt-world-cli`
11
- or `~/.config/fvtt-world-cli` on Linux, `~/Library/Application Support/fvtt-world-cli` on macOS, and
12
- `%APPDATA%\\fvtt-world-cli` on Windows.
13
-
14
- ## Choose a route
15
-
16
- If you are operating the CLI yourself, start with [Commands](commands.md). It explains what the tool
17
- can do, groups related operations by task, and links to the detailed contracts that matter for each
18
- workflow.
19
-
20
- If an AI agent or another program is operating the CLI, install the packaged agent skill into the
21
- agent with `fvtt-world-cli skill install`. The skill carries the operating workflow and defers
22
- command specifics to the CLI's own discovery surface, so agents never rely on a copied command
23
- inventory in prose; [Agent skill](skill.md) explains why it exists and how it is kept current.
24
-
25
- ## Documentation map
3
+ Choose a guide for the task at hand. Examples use `fvtt-world-cli`; `worldctl` is an equivalent
4
+ short alias. These guides also ship with the CLI, available through `fvtt-world-cli docs [document]`.
26
5
 
27
6
  | Document | Audience | Purpose |
28
7
  |---|---|---|
29
- | [Getting started](getting-started.md) | People | First-run daemon, pairing, and bridge walkthrough |
30
- | [Commands](commands.md) | People | Capabilities, common workflows, shared command behavior, and navigation by task |
31
- | [Agent skill](skill.md) | Agent operators | Why the packaged skill exists, installing it, how updates and removal work |
8
+ | [Getting started](getting-started.md) | People | Installation, pairing, updates, and troubleshooting |
9
+ | [Commands](commands.md) | People | Commands, workflows, access management, and local configuration |
10
+ | [Agent skill](skill.md) | Agent operators | Installing, updating, and removing the packaged skill |
32
11
  | [Protocol](protocol.md) | Implementers and automation consumers | Integration contract for transport, sessions, delivery, approval, and errors |
33
12
  | [Architecture](architecture.md) | Contributors | Component responsibilities and request flow |
34
13
  | [Security](security.md) | Operators and contributors | Trust boundaries, permissions, managed files, and known risks |
35
14
  | [Foundry compatibility](compatibility.md) | Operators and contributors | Current differences between supported Foundry major versions |
36
15
  | [Changelog](../CHANGELOG.md) | Users and contributors | Versioned history of user-visible changes |
37
-
38
- ## Finding exact command syntax
39
-
40
- The running CLI is the authoritative reference for the installed version:
41
-
42
- ```bash
43
- fvtt-world-cli commands
44
- fvtt-world-cli commands --json
45
- fvtt-world-cli schema actor.update
46
- fvtt-world-cli actor update --help
47
- fvtt-world-cli docs protocol
48
- ```
49
-
50
- `commands --json` enumerates the available commands and identifies mutations. `schema` returns the
51
- request schema used by local validation. Command help maps protocol parameters to CLI flags. `docs`
52
- lists and prints these documentation files as shipped with the installed CLI.
53
-
54
- ## Sources of truth
55
-
56
- Documentation explains the public behavior but does not duplicate exhaustive machine-readable
57
- inventories.
58
-
59
- - Command names, mutation classification, and request schemas, per family:
60
- [`packages/protocol/src/schemas/`](../packages/protocol/src/schemas/), assembled into the registry
61
- by [`packages/protocol/src/commands.js`](../packages/protocol/src/commands.js)
62
- - Shared constants and stable error codes:
63
- [`packages/protocol/src/constants.js`](../packages/protocol/src/constants.js)
64
- - Global flags, the JSON output contract printed in `--help`, and program assembly:
65
- [`packages/cli/src/program.ts`](../packages/cli/src/program.ts)
66
- - Per-command flags and command registration, per group:
67
- [`packages/cli/src/commands/`](../packages/cli/src/commands/)
68
- - Human-readable rendering of command results, per family:
69
- [`packages/cli/src/render/`](../packages/cli/src/render/)
70
- - Foundry-side behavior: [`packages/foundry-module/scripts/`](../packages/foundry-module/scripts/)
71
- - Live coverage: [`scripts/live-smoke.mjs`](../scripts/live-smoke.mjs)
@@ -1,248 +1,92 @@
1
1
  # Architecture
2
2
 
3
- fvtt-world-cli is a monorepo containing a command-line client, a local daemon, a shared protocol,
4
- and a Foundry module. The components share contracts but have distinct runtime responsibilities.
3
+ The `fvtt-world-cli` monorepo contains a CLI, a local daemon, a shared protocol, and a Foundry
4
+ module. Keeping them together lets their contracts change in one release.
5
5
 
6
6
  ## Runtime roles
7
7
 
8
- ### CLI
8
+ | Component | Owns |
9
+ |---|---|
10
+ | `packages/cli` | Command parsing, local configuration and validation, transport, discovery, and output |
11
+ | Daemon, within `packages/cli` | Loopback listener, authentication, pairings, the active bridge, request routing, timeouts, and idempotency coordination |
12
+ | `packages/protocol` | Command registry, request and transport schemas, mutation classification, error codes, and shared constants |
13
+ | `packages/foundry-module` | GM checks, command permissions and approval, document validation, capability adapters, execution, serialization, and write confirmation |
9
14
 
10
- `packages/cli` owns:
11
-
12
- - command parsing and help;
13
- - local configuration;
14
- - local request-schema validation;
15
- - WebSocket client transport;
16
- - JSON and human-readable output;
17
- - local discovery commands;
18
- - reading explicitly supplied operator files for upload or macro input.
19
-
20
- The CLI does not load Foundry or mutate world storage. It converts CLI flags into typed protocol
21
- requests and presents structured responses.
22
-
23
- ### Daemon
24
-
25
- The daemon runs as part of the CLI package and owns:
26
-
27
- - the loopback WebSocket listener;
28
- - device-local client authentication and the persistent pairing registry;
29
- - the single active bridge session;
30
- - request correlation and forwarding;
31
- - forward timeouts and heartbeat state;
32
- - transport size limits;
33
- - runtime idempotency coordination and caching.
34
-
35
- The daemon does not interpret Foundry document payloads or provide world access without an active
36
- authenticated bridge.
37
-
38
- ### Protocol package
39
-
40
- `packages/protocol` owns:
41
-
42
- - protocol and message constants;
43
- - command names and mutation classification;
44
- - request schemas;
45
- - stable error codes;
46
- - shared limits and enums;
47
- - envelope validation.
48
-
49
- The protocol registry is the source for runtime command discovery. Exhaustive command inventories are
50
- not copied into documentation.
51
-
52
- ### Foundry module
53
-
54
- `packages/foundry-module` runs inside the logged-in Foundry GM client and owns:
55
-
56
- - second-boundary request validation;
57
- - GM permission checks;
58
- - the command-permission gate and the human approval loop it opens;
59
- - protected-metadata sanitization;
60
- - document lookup and serialization;
61
- - capability adaptation across supported Foundry versions;
62
- - mutation preparation and dry-run previews;
63
- - execution through Foundry Document APIs and reviewed typed actions;
64
- - observable write confirmation;
65
- - managed-file containment.
66
-
67
- The bridge checks command permissions after readiness, GM authority, parameter validation, and write
68
- permission. It checks them immediately before handler lookup. Direct commands and commands released
69
- by GM approval therefore use the same guards. The approved route skips only the permission gate for
70
- that invocation.
71
-
72
- The default permission profile is generated from one three-bucket rule in the protocol package,
73
- with deny taking precedence over approve over allow: an explicit list denies commands that can
74
- execute code, change who can do what, or persist outside the world's own data; destructive
75
- commands and listed exceptions require approval; everything else is allowed. Keeping the rule in
76
- the protocol package, next to the registry, means a new command cannot ship without a default
77
- behavior, and the generated profile is byte-pinned so the rule and its output cannot drift apart.
78
-
79
- The module ships plain browser-compatible JavaScript. Its generated protocol mirror is produced from
80
- the canonical protocol package.
15
+ The daemon routes requests without interpreting Foundry document payloads. The module ships
16
+ browser-compatible JavaScript with a generated mirror of the protocol package.
81
17
 
82
18
  ## Core assumption
83
19
 
84
20
  An authenticated GM client is open in the target world. The bridge acts through that client's
85
- Foundry runtime and authority. It is not a headless database editor and does not bypass Foundry's
86
- document lifecycle, validation, permissions, hooks, or installed system/module behavior.
21
+ runtime and authority, using Foundry's document lifecycle, permissions, validation, and hooks.
22
+ Installed systems and modules remain part of the execution environment.
87
23
 
88
24
  ## Request flow
89
25
 
90
26
  ```text
91
27
  CLI invocation
92
28
  -> parse flags and validate request schema
93
- -> connect and authenticate to local daemon
94
- -> correlate and forward to active bridge
29
+ -> authenticate to local daemon
30
+ -> forward to the active bridge
95
31
  -> validate, authorize, sanitize, and capability-check
96
- -> resolve Foundry documents
97
32
  -> prepare preview or execute through a Foundry API
98
- -> serialize observed result or structured error
99
- -> relay response by request ID
100
- -> render JSON or human output
33
+ -> confirm and serialize the result, or return a structured error
34
+ -> relay by request ID and render output
101
35
  ```
102
36
 
103
- The bridge advertises its supported commands during the handshake. The daemon forwards only commands
104
- advertised by the active session.
105
-
106
- A command that requires GM approval pauses before dispatch. The module keeps the invocation in
107
- memory and returns a pending approval. The CLI then requests the outcome through short polls keyed by
108
- the approval identifier. If the GM allows the command, the module runs the normal guard sequence
109
- again before dispatch.
110
-
111
- ## Validation boundaries
37
+ The bridge advertises its commands during the handshake. The daemon forwards only advertised
38
+ commands. A command that requires GM approval waits before execution and passes through the
39
+ guards again after approval. The protocol registry supplies every command's default permission.
112
40
 
113
- The CLI validation pass provides fast feedback and avoids unnecessary connections. The bridge repeats
114
- validation because the transport input remains untrusted and because Foundry-side capability and
115
- document validation require the live runtime.
41
+ ## Validation and command design
116
42
 
117
- Closed protocol schemas define the complete accepted top-level field set for document families owned
118
- by the bridge. Open schemas preserve system/module extensibility but pass through shared sanitization
119
- before validation, diffing, preview, or dispatch.
43
+ CLI validation gives early feedback. The bridge repeats it because transport input is untrusted
44
+ and only the live runtime can check Foundry capabilities and document state.
120
45
 
121
- Foundry DataModels remain the final authority for system-specific and version-specific values.
46
+ Closed schemas enumerate writable fields. Open schemas allow system and module data but use
47
+ shared sanitization before validation, preview, or execution. Foundry DataModels validate the
48
+ system-specific and version-specific values.
122
49
 
123
- ## Command architecture
124
-
125
- Commands are explicit typed handlers rather than a generic RPC. Related document families share
126
- preparation, guard, serialization, and bulk seams so their behavior does not diverge between create,
127
- update, clone, dry-run, and bulk routes.
128
-
129
- CRUD handlers operate through document methods. Action handlers call a fixed reviewed Foundry method
130
- and report only the result that can be observed or confirmed. Command-specific behavior is discovered
131
- from the registry and CLI schema surface.
50
+ Each command has an explicit schema and handler. Document handlers call Foundry document methods;
51
+ action handlers call a fixed, reviewed Foundry method. Related operations reuse preparation,
52
+ guards, serialization, and bulk helpers so a preview or bulk write cannot bypass a single
53
+ command's restrictions.
132
54
 
133
55
  ## Mutation model
134
56
 
135
- Mutations are serialized where family behavior requires ordering, but the bridge does not claim a
136
- global transaction. The Foundry UI, systems, modules, and other clients remain concurrent writers.
137
-
138
- A dry run performs the same preparation and guards as a real command, then stops before persistence.
139
- Real commands confirm stored state where their contract depends on a write landing. Native Foundry
140
- batch calls can partially apply, so bulk results include per-element outcomes.
57
+ A dry run performs the same preparation and guards as a real command, then stops before
58
+ persistence. It reserves no state. The Foundry UI, systems, modules, and other clients remain
59
+ concurrent writers, and bulk calls are not transactions.
141
60
 
142
- A capability check asks whether the connected Foundry can perform an operation. Approval is the GM's
143
- decision to let one invocation run. Confirmation checks whether Foundry persisted a completed write.
144
- An approved command can still fail during execution or confirmation.
145
-
146
- Idempotency keys reduce duplicate effects across response loss while the relevant daemon/bridge cache
147
- entry exists. They do not create durable distributed transactions.
61
+ Write handlers confirm stored state before reporting success, since Foundry can veto or modify a
62
+ request without throwing. A valid no-op succeeds; a partial or unprovable write returns an error.
63
+ [Write confirmation](protocol.md#write-confirmation) defines how the original request is preserved
64
+ and compared with the result.
148
65
 
149
66
  ## Serialization
150
67
 
151
- Readers serialize authored source state from Foundry document sources. Derived runtime values are
152
- included only through explicit projections and are identified as derived.
153
-
154
- List rows are lean discovery projections. Single-document reads expose richer authored projections.
155
- This keeps large collections bounded while allowing callers to inspect a target before mutation.
156
-
157
- Result shapes are intentionally narrower than arbitrary Foundry document models. Extensible writes
158
- can therefore accept valid system/module data that a curated read does not echo field-for-field.
159
-
160
- ## Managed files
68
+ Reads return authored state from Foundry document sources. Derived runtime values require explicit
69
+ projections and are identified as derived. Lists use smaller projections than single-document
70
+ reads to keep collection responses bounded.
161
71
 
162
- File commands use Foundry's public managed-file APIs. Reads address the managed `data` source. Writes
163
- are contained to the active world's allowed asset tree and exclude the world manifest, databases,
164
- and packs.
165
-
166
- The bridge receives upload bytes over the local transport; it never resolves an operator-machine
167
- absolute path. File mutations and document-reference mutations remain separate explicit commands.
168
-
169
- See [Security](security.md#file-write-boundary) for the full boundary.
72
+ Results expose selected fields, so an extensible write may accept system or module data that a
73
+ read does not echo field-for-field.
170
74
 
171
75
  ## Session lifecycle
172
76
 
173
- The first bridge connection attempt occurs after Foundry is ready. Authentication or protocol
174
- rejection is terminal for that module load so a persistent configuration problem does not create a
175
- reconnect loop. A session that completed the handshake and later loses transport reconnects with
176
- bounded exponential backoff.
177
-
178
- The daemon persists multiple pairing profiles but routes through one active bridge. A profile is owned
179
- by one browser: its uniqueness key is Origin, world, GM, and the browser's own persistent client
180
- identifier, which is why the same person can keep two browsers paired to one world and GM and why
181
- re-pairing rotates only the re-pairing browser's credential. Making the browser the unit of ownership
182
- also makes the human label meaningful, so the label travels with the pairing request instead of being
183
- editable daemon-side metadata: it is fixed between pairing approvals, and an approval that reuses an
184
- existing record adopts the label that request carried. The design keeps slot ownership
185
- unambiguous. A socket receives its role only after completed authentication rather than from a
186
- claimed message type, and only a same-pairing socket can take over the slot, as the tab-reload
187
- recovery path. Intentional goodbye, release, and revocation clear ownership before close handling,
188
- while only an abnormal close creates a short reclaim lease. Daemon-initiated release is terminal
189
- for the released client, so reconnection remains an explicit operator action. Every way a
190
- pairing attempt can end shares one idempotent cleanup path, so the authorization UI cannot retain a
191
- stale pending state.
192
-
193
- While serving, the daemon owns authentication and connection configuration writes, and preserves a
194
- concurrently changed upload limit until a restart applies it to the transport. Daemon control
195
- operations for pairing, profiles, release, and client credential rotation form the future Companion
196
- boundary and remain separate from the Foundry command registry.
197
-
198
- One of those operations parks instead of answering at once: the wait for a pairing request holds its
199
- response until a request arrives or the daemon's own park cap elapses. That cap is what keeps a parked
200
- answer inside the caller's request timeout, so an unanswered wait ends in an empty result the CLI
201
- re-issues rather than in a transport failure; a cap at or above the client's wait would turn every
202
- unanswered wait into a transport failure.
203
-
204
- Approval waits use the same bounded polling pattern inside the Foundry module. The module answers the
205
- original request with a pending approval. Later polls wait within the transport timeout and ask again
206
- after an ordinary reconnect.
207
-
208
- Both halves of the wait use runtime state. The browser session holds the decision and retained
209
- outcome. The daemon links an idempotency key to the approval in its idempotency store. Neither half
210
- persists this state. Losing it produces an indeterminate result rather than an automatic retry.
211
-
212
- Normative handshake, takeover, lease, and release semantics are defined in
213
- [Protocol](protocol.md#bridge-sessions); the authentication guarantees and host validation rules are
214
- stated in [Security](security.md#authentication).
215
-
216
- ### Client-side status signal
217
-
218
- The module's own UI needs to react to connection changes rather than read state once, so the bridge
219
- client publishes every status transition instead of assigning the field silently. The Foundry module
220
- re-emits those transitions as the `fvtt-world-cli.statusChanged` hook, which makes the same signal
221
- available to macros and other modules in the GM client. It is a client-side extension point only and
222
- carries no wire-protocol meaning; the daemon and the CLI neither send nor observe it.
223
-
224
- The hook fires once per actual change, on the client transport status or on the handshake
225
- acknowledgement, and receives the same snapshot that `system info` reports as `bridge`: `status`,
226
- `url`, `helloAcknowledged`, `hasEstablishedSession`, `lastConnectedAt`, `reconnectAttempts`,
227
- `terminalStopReason`, and `protocolVersionMismatch`. The last field is `null` unless the module
228
- refuses a handshake because the protocol versions differ. It then reports both versions and names
229
- the older component as `module`, `cli-daemon`, or `unknown`. The status window uses that value to
230
- show the required update. Readiness requires `status === "connected"` and `helloAcknowledged` because
231
- the socket opens before the daemon acknowledges the handshake. Losing the socket clears the
232
- acknowledgement before publishing the status change.
233
- `helloAcknowledged` stays in the snapshot for consumers that need the distinction; the module's own
234
- windows fold it into the connection state they display rather than showing it as its own field.
235
-
236
- Credential changes are not transitions of this hook. Pairing and unpairing refresh the module's own
237
- windows and toolbar indicator through an internal signal, since the connection state itself has not
238
- changed at that moment.
77
+ The daemon routes commands through one active GM browser, which determines their world and
78
+ permissions. Pairing and connection management use separate daemon controls so they remain
79
+ available without that browser.
80
+
81
+ The browser owns command approvals; the daemon owns request routing and idempotency coordination.
82
+ Both keep temporary state. [Protocol](protocol.md#bridge-sessions) defines ownership, reconnects,
83
+ and failure handling, plus the module's [local status hook](protocol.md#client-side-status-signal).
239
84
 
240
85
  ## Compatibility strategy
241
86
 
242
- The bridge supports the designated Foundry major versions through narrow capability adapters and
243
- explicit guards. It refuses a version-dependent request when it cannot provide the documented result
244
- honestly.
87
+ Narrow adapters handle differences between supported Foundry versions. Unsupported operations
88
+ return `UNSUPPORTED_OPERATION`. The CLI, daemon, and module must share the same release version.
245
89
 
246
- Mocks verify contracts and edge cases but cannot establish real Foundry compatibility. The live smoke
247
- workflow is the authority for executed coverage. Current operator-visible differences are summarized
248
- in [Foundry compatibility](compatibility.md).
90
+ Mocks check contracts and edge cases. Only the [live smoke workflow](../scripts/live-smoke.mjs)
91
+ establishes which operations were exercised against Foundry. See
92
+ [Foundry compatibility](compatibility.md) for differences users need to act on.