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 +34 -34
- package/dist/index.js +3 -2
- package/docs/README.md +5 -61
- package/docs/architecture.md +53 -209
- package/docs/commands.md +166 -444
- package/docs/compatibility.md +13 -22
- package/docs/getting-started.md +76 -74
- package/docs/images/module-settings.png +0 -0
- package/docs/protocol.md +228 -187
- package/docs/security.md +149 -322
- package/docs/skill.md +24 -33
- package/package.json +14 -2
- package/skills/foundry-world-editor/SKILL.md +8 -0
- package/skills/foundry-world-editor/SKILL.md.sha256 +1 -1
- package/docs/images/icon.svg +0 -16
- package/docs/images/mark.svg +0 -15
package/README.md
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
# World CLI for Foundry VTT
|
|
2
2
|
|
|
3
|
-
[](https://foundryvtt.com)
|
|
4
|
+
[](https://www.npmjs.com/package/fvtt-world-cli)
|
|
5
|
+
[](https://www.npmjs.com/package/fvtt-world-cli)
|
|
6
|
+
[](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
|
-
|
|
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
|
+

|
|
19
12
|
|
|
20
13
|
Modern agents already understand requests like that. What they have lacked is a safe way into
|
|
21
|
-
Foundry
|
|
22
|
-
bypasses
|
|
23
|
-
|
|
24
|
-
|
|
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)
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
130
|
-
|
|
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
|
-
[
|
|
136
|
-
compatibility, and
|
|
137
|
-
`fvtt-world-cli docs [document]
|
|
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.
|
|
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
|
-
|
|
4
|
-
|
|
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 |
|
|
30
|
-
| [Commands](commands.md) | People |
|
|
31
|
-
| [Agent skill](skill.md) | Agent operators |
|
|
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)
|
package/docs/architecture.md
CHANGED
|
@@ -1,248 +1,92 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
fvtt-world-cli
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
->
|
|
94
|
-
->
|
|
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
|
|
99
|
-
-> relay
|
|
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
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
|
247
|
-
|
|
248
|
-
|
|
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.
|