fvtt-world-cli 1.0.0 → 1.1.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.
- package/README.md +18 -15
- package/bin/fvtt-world-cli.js +12 -0
- package/dist/index.js +2479 -128
- package/docs/README.md +2 -2
- package/docs/architecture.md +43 -11
- package/docs/commands.md +198 -37
- package/docs/compatibility.md +4 -2
- package/docs/getting-started.md +19 -10
- package/docs/protocol.md +114 -23
- package/docs/security.md +155 -22
- package/docs/skill.md +12 -8
- package/package.json +5 -6
- package/skills/foundry-world-editor/SKILL.md +68 -29
- package/skills/foundry-world-editor/SKILL.md.sha256 +1 -1
- package/bin/sync-installed-skill.mjs +0 -89
package/docs/README.md
CHANGED
|
@@ -29,7 +29,7 @@ inventory in prose; [Agent skill](skill.md) explains why it exists and how it is
|
|
|
29
29
|
| [Getting started](getting-started.md) | People | First-run daemon, pairing, and bridge walkthrough |
|
|
30
30
|
| [Commands](commands.md) | People | Capabilities, common workflows, shared command behavior, and navigation by task |
|
|
31
31
|
| [Agent skill](skill.md) | Agent operators | Why the packaged skill exists, installing it, how updates and removal work |
|
|
32
|
-
| [Protocol](protocol.md) | Implementers and automation consumers |
|
|
32
|
+
| [Protocol](protocol.md) | Implementers and automation consumers | Integration contract for transport, sessions, delivery, approval, and errors |
|
|
33
33
|
| [Architecture](architecture.md) | Contributors | Component responsibilities and request flow |
|
|
34
34
|
| [Security](security.md) | Operators and contributors | Trust boundaries, permissions, managed files, and known risks |
|
|
35
35
|
| [Foundry compatibility](compatibility.md) | Operators and contributors | Current differences between supported Foundry major versions |
|
|
@@ -47,7 +47,7 @@ fvtt-world-cli actor update --help
|
|
|
47
47
|
fvtt-world-cli docs protocol
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
`commands --json` enumerates the
|
|
50
|
+
`commands --json` enumerates the available commands and identifies mutations. `schema` returns the
|
|
51
51
|
request schema used by local validation. Command help maps protocol parameters to CLI flags. `docs`
|
|
52
52
|
lists and prints these documentation files as shipped with the installed CLI.
|
|
53
53
|
|
package/docs/architecture.md
CHANGED
|
@@ -55,6 +55,7 @@ not copied into documentation.
|
|
|
55
55
|
|
|
56
56
|
- second-boundary request validation;
|
|
57
57
|
- GM permission checks;
|
|
58
|
+
- the command-permission gate and the human approval loop it opens;
|
|
58
59
|
- protected-metadata sanitization;
|
|
59
60
|
- document lookup and serialization;
|
|
60
61
|
- capability adaptation across supported Foundry versions;
|
|
@@ -63,6 +64,18 @@ not copied into documentation.
|
|
|
63
64
|
- observable write confirmation;
|
|
64
65
|
- managed-file containment.
|
|
65
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
|
+
|
|
66
79
|
The module ships plain browser-compatible JavaScript. Its generated protocol mirror is produced from
|
|
67
80
|
the canonical protocol package.
|
|
68
81
|
|
|
@@ -90,6 +103,11 @@ CLI invocation
|
|
|
90
103
|
The bridge advertises its supported commands during the handshake. The daemon forwards only commands
|
|
91
104
|
advertised by the active session.
|
|
92
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
|
+
|
|
93
111
|
## Validation boundaries
|
|
94
112
|
|
|
95
113
|
The CLI validation pass provides fast feedback and avoids unnecessary connections. The bridge repeats
|
|
@@ -121,6 +139,10 @@ A dry run performs the same preparation and guards as a real command, then stops
|
|
|
121
139
|
Real commands confirm stored state where their contract depends on a write landing. Native Foundry
|
|
122
140
|
batch calls can partially apply, so bulk results include per-element outcomes.
|
|
123
141
|
|
|
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
|
+
|
|
124
146
|
Idempotency keys reduce duplicate effects across response loss while the relevant daemon/bridge cache
|
|
125
147
|
entry exists. They do not create durable distributed transactions.
|
|
126
148
|
|
|
@@ -160,11 +182,11 @@ re-pairing rotates only the re-pairing browser's credential. Making the browser
|
|
|
160
182
|
also makes the human label meaningful, so the label travels with the pairing request instead of being
|
|
161
183
|
editable daemon-side metadata: it is fixed between pairing approvals, and an approval that reuses an
|
|
162
184
|
existing record adopts the label that request carried. The design keeps slot ownership
|
|
163
|
-
unambiguous
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
168
190
|
pairing attempt can end shares one idempotent cleanup path, so the authorization UI cannot retain a
|
|
169
191
|
stale pending state.
|
|
170
192
|
|
|
@@ -177,7 +199,15 @@ One of those operations parks instead of answering at once: the wait for a pairi
|
|
|
177
199
|
response until a request arrives or the daemon's own park cap elapses. That cap is what keeps a parked
|
|
178
200
|
answer inside the caller's request timeout, so an unanswered wait ends in an empty result the CLI
|
|
179
201
|
re-issues rather than in a transport failure; a cap at or above the client's wait would turn every
|
|
180
|
-
wait into
|
|
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.
|
|
181
211
|
|
|
182
212
|
Normative handshake, takeover, lease, and release semantics are defined in
|
|
183
213
|
[Protocol](protocol.md#bridge-sessions); the authentication guarantees and host validation rules are
|
|
@@ -193,11 +223,13 @@ carries no wire-protocol meaning; the daemon and the CLI neither send nor observ
|
|
|
193
223
|
|
|
194
224
|
The hook fires once per actual change, on the client transport status or on the handshake
|
|
195
225
|
acknowledgement, and receives the same snapshot that `system info` reports as `bridge`: `status`,
|
|
196
|
-
`url`, `helloAcknowledged`, `hasEstablishedSession`, `lastConnectedAt`, `reconnectAttempts`,
|
|
197
|
-
`terminalStopReason`.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
the
|
|
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.
|
|
201
233
|
`helloAcknowledged` stays in the snapshot for consumers that need the distinction; the module's own
|
|
202
234
|
windows fold it into the connection state they display rather than showing it as its own field.
|
|
203
235
|
|
package/docs/commands.md
CHANGED
|
@@ -65,21 +65,23 @@ the active slot can be cleared with `bridge release`. The daemon never prints a
|
|
|
65
65
|
drops, which is reported as `DAEMON_UNAVAILABLE` rather than a silent stop and is safe to re-run once
|
|
66
66
|
the daemon is back. `y` or `yes` approves the displayed code and exits 0, any other answer denies that
|
|
67
67
|
code and exits 1 with `PAIRING_DECLINED`.
|
|
68
|
-
|
|
69
|
-
pending until it expires or another run answers for it. An ended stdin
|
|
70
|
-
|
|
71
|
-
does not read keystrokes itself, so Ctrl+C ends the run as an
|
|
68
|
+
Interrupting the confirmation instead leaves the request untouched: after Ctrl+C or an ended
|
|
69
|
+
stdin it stays pending until it expires or another run answers for it. An ended stdin reports
|
|
70
|
+
`PAIRING_PROMPT_ABORTED` and exits 1, and Ctrl+C does the same while output goes to the terminal;
|
|
71
|
+
with output redirected the prompt does not read keystrokes itself, so Ctrl+C ends the run as an
|
|
72
|
+
ordinary interrupt instead.
|
|
72
73
|
Delivery is a long-poll control call the CLI re-issues on its own, and the daemon answers each call
|
|
73
74
|
inside its own park cap, so an ordinary wait does not end in a timeout; a daemon that stops answering
|
|
74
75
|
altogether still trips the client's request timeout and is reported as `DAEMON_UNAVAILABLE`. The
|
|
75
76
|
command is interactive-only: `--json`, and a stdin that is not a terminal, each fail
|
|
76
77
|
immediately and name `auth pending` plus `auth approve --yes` as the path for scripts.
|
|
77
|
-
`--timeout-ms` cannot cut the wait short either
|
|
78
|
-
seconds is raised to it
|
|
79
|
-
response the daemon is still holding. That long poll is the only call the flag cannot
|
|
80
|
-
approval or denial that follows the answer
|
|
81
|
-
|
|
82
|
-
other `auth` verbs and `bridge release` do
|
|
78
|
+
`--timeout-ms` cannot cut the wait short either. For the long-poll call, a value below the
|
|
79
|
+
daemon's park cap plus five seconds is raised to it, because a smaller client wait would abandon
|
|
80
|
+
a parked response the daemon is still holding. That long poll is the only call the flag cannot
|
|
81
|
+
shorten. The approval or denial that follows the answer takes `--timeout-ms` as given for its
|
|
82
|
+
client wait, and so does the listing an interactive `auth approve` or `auth prune` reads before
|
|
83
|
+
its prompt, as the other `auth` verbs and `bridge release` do. Without the flag that wait is
|
|
84
|
+
60 seconds.
|
|
83
85
|
- `auth status` shows bridge state and public profile metadata.
|
|
84
86
|
- `auth pending` lists approval candidates with code, expiry, exact Origin, world, GM, browser client
|
|
85
87
|
id, browser label, and module version.
|
|
@@ -87,13 +89,13 @@ the active slot can be cleared with `bridge release`. The daemon never prints a
|
|
|
87
89
|
request is pending. Interactive use confirms the displayed identity, including the browser label and
|
|
88
90
|
client id, and then approves that exact request: one that expired or disappeared while the prompt
|
|
89
91
|
waited fails rather than approving whatever else is pending. When no single pending request can be
|
|
90
|
-
shown
|
|
91
|
-
stops instead of asking. Answering anything but `y` or `yes` cancels the
|
|
92
|
-
`PAIRING_DECLINED` and exits 1
|
|
93
|
-
request
|
|
94
|
-
prompt also leaves the request pending, and reports
|
|
95
|
-
terms as the wait. Scripts must pass `--yes`.
|
|
96
|
-
does not rename it.
|
|
92
|
+
shown, because nothing is pending, the code is unknown, or several candidates exist, it names the
|
|
93
|
+
live pending codes and stops instead of asking. Answering anything but `y` or `yes` cancels the
|
|
94
|
+
approval, reports `PAIRING_DECLINED` and exits 1, the same code and exit the bare `auth` wait
|
|
95
|
+
reports for a declined request; the wait also denies the request outright, while this verb leaves
|
|
96
|
+
it pending. Interrupting the prompt also leaves the request pending, and reports
|
|
97
|
+
`PAIRING_PROMPT_ABORTED` with exit 1 on the same terms as the wait. Scripts must pass `--yes`.
|
|
98
|
+
The stored label is the one the browser sent; approval does not rename it.
|
|
97
99
|
- `auth deny <code>` rejects a pending request.
|
|
98
100
|
- `auth list` shows non-secret profile metadata, including each profile's `clientId` and `label`.
|
|
99
101
|
A label is set in the browser at pairing time and no control operation renames a stored record, so
|
|
@@ -107,11 +109,11 @@ the active slot can be cleared with `bridge release`. The daemon never prints a
|
|
|
107
109
|
- `auth prune [--older-than <days>] [--yes]` deletes the profiles that have gone unused. A profile is
|
|
108
110
|
a candidate when its `lastSeenAt` is older than the cutoff, which defaults to 30 days; the active
|
|
109
111
|
bridge profile and the holder of an abnormal-disconnect lease are never deleted, however idle their
|
|
110
|
-
stored timestamp looks. Interactive use lists the candidates it found
|
|
111
|
-
last-seen timestamp, and pairing id
|
|
112
|
-
`auth list` reports as active, so a browser that has stayed connected past the
|
|
113
|
-
nor counted in the prompt, matching what the daemon will do. Answering
|
|
114
|
-
`yes` removes nothing, reports `PAIRING_DECLINED` and exits 1; interrupting the prompt reports
|
|
112
|
+
stored timestamp looks. Interactive use lists the candidates it found, with label, client id,
|
|
113
|
+
world, GM, last-seen timestamp, and pairing id, and asks once for the whole set. The listing
|
|
114
|
+
skips the profile `auth list` reports as active, so a browser that has stayed connected past the
|
|
115
|
+
cutoff is neither shown nor counted in the prompt, matching what the daemon will do. Answering
|
|
116
|
+
anything but `y` or `yes` removes nothing, reports `PAIRING_DECLINED` and exits 1; interrupting the prompt reports
|
|
115
117
|
`PAIRING_PROMPT_ABORTED` and exits 1 on the same terms as `auth approve`. A listing that found no
|
|
116
118
|
candidate asks nothing and still runs the operation, so the command's output is the daemon's own empty
|
|
117
119
|
result rather than a local verdict. The listing is a preview only: the daemon recomputes the set,
|
|
@@ -120,7 +122,7 @@ the active slot can be cleared with `bridge release`. The daemon never prints a
|
|
|
120
122
|
crossed the cutoff while the prompt waited is removed although it was never listed; it can equally be
|
|
121
123
|
smaller, because a profile that became active or was already removed between the two steps is
|
|
122
124
|
reported as the daemon left it. There is no dry-run mode, and `--older-than 0` treats every profile
|
|
123
|
-
as idle, which is the one case where a preview can name a lease holder the daemon then keeps
|
|
125
|
+
as idle, which is the one case where a preview can name a lease holder the daemon then keeps.
|
|
124
126
|
`auth list` does not expose the lease, and a lease holder's last-seen timestamp is fresh by
|
|
125
127
|
definition, so no realistic threshold selects it. Scripts pass `--yes`, which skips both the preview
|
|
126
128
|
and the prompt; `--json` requires `--yes` as well, because the confirmation is never mixed into JSON
|
|
@@ -132,8 +134,8 @@ the active slot can be cleared with `bridge release`. The daemon never prints a
|
|
|
132
134
|
An active browser stopped by release stays stopped until its operator chooses Connect.
|
|
133
135
|
|
|
134
136
|
Every command in this section is answered by the daemon alone, with no Foundry browser involved, so
|
|
135
|
-
a daemon that is not running or not reachable ends any of them
|
|
136
|
-
`bridge release` alike
|
|
137
|
+
a daemon that is not running or not reachable ends any of them, the `auth` verbs and
|
|
138
|
+
`bridge release` alike, with `DAEMON_UNAVAILABLE` and exit 3 rather than a command-level failure.
|
|
137
139
|
|
|
138
140
|
In Foundry, Connect reuses the stored browser credential and Disconnect releases the slot without
|
|
139
141
|
touching it. Unpair waits for confirmed daemon revocation
|
|
@@ -162,8 +164,10 @@ on the CLI:
|
|
|
162
164
|
| Tokens embedded in a scene | `scene.token.*` | `fvtt-world-cli scene token …` |
|
|
163
165
|
| Effects on a placed token | `scene.token.effect.*` | `fvtt-world-cli scene token effect …` |
|
|
164
166
|
|
|
165
|
-
Use `fvtt-world-cli commands --json` for the
|
|
167
|
+
Use `fvtt-world-cli commands --json` for the current inventory. The exact operation set
|
|
166
168
|
varies by family, so a nearby document family is not a reliable guide to what another one supports.
|
|
169
|
+
With a bridge connected, that inventory is also filtered by the GM client's command permissions, as
|
|
170
|
+
[Discovery under a policy](#discovery-under-a-policy) describes.
|
|
167
171
|
|
|
168
172
|
## Capability map
|
|
169
173
|
|
|
@@ -171,7 +175,22 @@ varies by family, so a nearby document family is not a reliable guide to what an
|
|
|
171
175
|
|
|
172
176
|
- `actor`, `item`, `journal`, `scene`, `macro`, `playlist`, `table`, and `cards` manage world
|
|
173
177
|
documents.
|
|
174
|
-
- `chat` reads, creates, and deletes chat messages.
|
|
178
|
+
- `chat` reads, creates, and deletes chat messages. `chat flush` erases the entire log at once and
|
|
179
|
+
asks for GM approval by default because nothing brings the messages back.
|
|
180
|
+
- `user` manages Foundry user accounts. `user update` edits harmless profile fields, `user create`
|
|
181
|
+
and `user delete` ask for approval by default, and `user role set` plus `user permissions set` are
|
|
182
|
+
[off by default](#commands-that-are-off-by-default). No command reads or writes a password, and
|
|
183
|
+
the account holding the bridge cannot demote or delete itself; see
|
|
184
|
+
[Security](security.md#users).
|
|
185
|
+
- `setting` lists registrations and reads values, including `setting get-many` for batch reads.
|
|
186
|
+
`setting set` and `setting set-many` write world, client, and user scopes but are
|
|
187
|
+
[off by default](#commands-that-are-off-by-default); they refuse this module's own namespace in
|
|
188
|
+
every mode, and a write to an already-stored value is reported as unchanged without touching
|
|
189
|
+
Foundry. The result's `value` is the value read back after the write, not the value requested: a
|
|
190
|
+
setting's registered type or `onChange` handler may normalize or clamp what was asked for, so
|
|
191
|
+
compare `value` against the requested input when the exact stored form matters. The write is
|
|
192
|
+
confirmed only in that Foundry no longer holds the `previous` value. A result carrying
|
|
193
|
+
`requiresReload: true` names the follow-up that Foundry expects, which `system reload` performs.
|
|
175
194
|
- `combat` manages encounters and exposes explicit encounter transitions.
|
|
176
195
|
- `folder` manages document organization.
|
|
177
196
|
- Dedicated `*.ownership.set` commands change supported document ownership.
|
|
@@ -191,7 +210,11 @@ Common world-document operations include `list`, `get`, `get-many`, `create`, `u
|
|
|
191
210
|
- `scene.token`, `tile`, `sound`, `wall`, `note`, `drawing`, `light`, `template`, and `region` manage
|
|
192
211
|
scene placeables.
|
|
193
212
|
- `scene.region.behavior` manages region behaviors; writes that supply executable core behavior
|
|
194
|
-
types are rejected (see [Security](security.md#executable-content)).
|
|
213
|
+
types are rejected (see [Security](security.md#executable-content)). The separate
|
|
214
|
+
`scene.region.behavior.executable` family, [off by default](#commands-that-are-off-by-default),
|
|
215
|
+
authors `executeMacro` behaviors that reference an existing world macro; `executeScript` has no
|
|
216
|
+
command surface at all. Deleting an executable behavior uses the ordinary
|
|
217
|
+
`scene region behavior delete`.
|
|
195
218
|
|
|
196
219
|
Embedded commands require the complete parent ID chain; a read of the parent supplies those IDs when
|
|
197
220
|
they are not already known.
|
|
@@ -204,7 +227,38 @@ Some commands invoke a typed Foundry action instead of ordinary CRUD:
|
|
|
204
227
|
- roll-table draw and reset;
|
|
205
228
|
- card shuffle, reset, deal, draw, and pass;
|
|
206
229
|
- combat start, activation, advancement, and initiative;
|
|
207
|
-
- scene thumbnail generation and fog reset
|
|
230
|
+
- scene thumbnail generation and fog reset;
|
|
231
|
+
- scene activation (`scene activate`) and pulling active users to a scene (`scene pull-users`);
|
|
232
|
+
- showing a journal entry (`journal show`) or an image (`image show`) to players;
|
|
233
|
+
- pausing or resuming the game clock for everyone (`game pause`);
|
|
234
|
+
- reloading the GM client (`system reload`), which asks for approval by default because it drops
|
|
235
|
+
the bridge until the client reconnects;
|
|
236
|
+
- macro execution (`macro execute`), [off by default](#commands-that-are-off-by-default).
|
|
237
|
+
|
|
238
|
+
`scene pull-users`, `journal show`, and `image show` reach every connected player when no
|
|
239
|
+
`--user-ids` list is given. A supplied list has to name at least one user: an empty one is
|
|
240
|
+
indistinguishable from "everyone" on the Foundry side, while the result would report that nobody was
|
|
241
|
+
addressed.
|
|
242
|
+
|
|
243
|
+
`macro execute` runs a world macro the GM can already execute, waits for it to finish up to a
|
|
244
|
+
bounded `--macro-timeout-ms`, and reports the returned value plus the chat messages it observed the
|
|
245
|
+
macro create. A macro that outlives the timeout keeps running in the GM browser and the command
|
|
246
|
+
returns the indeterminate `MACRO_TIMEOUT`, so the effect is verified by reads. A script macro that
|
|
247
|
+
throws fails the command and the error names what the macro raised; the outcome is partial, because
|
|
248
|
+
whatever the macro changed before it threw stays changed. A macro that catches its own errors still
|
|
249
|
+
reports a `null` return, and a macro is free to reload the page or navigate away, which ends the
|
|
250
|
+
bridge session the same way any disconnect does. Effects therefore deserve a read-back whenever the
|
|
251
|
+
return value alone does not prove them.
|
|
252
|
+
|
|
253
|
+
Each key of `--args-json` becomes a named variable Foundry splices into the compiled macro, so an
|
|
254
|
+
argument name must be a plain JavaScript identifier (letters, digits, `_` or `$`, not starting with
|
|
255
|
+
a digit) and cannot reuse a name Foundry already binds (`speaker`, `actor`, `token`, `character`,
|
|
256
|
+
`scope`). Any other name is refused before the macro runs.
|
|
257
|
+
|
|
258
|
+
The result's `chatCapture` field says how much of the chat the run observed: `captured` when every
|
|
259
|
+
message the macro was expected to create was seen, `not-created` when a chat macro created none,
|
|
260
|
+
`partial` when only some were seen, and `unknown` when this client could not watch the chat log at
|
|
261
|
+
all.
|
|
208
262
|
|
|
209
263
|
Actions can have Foundry, system, or module side effects. Their result describes what the bridge can
|
|
210
264
|
confirm, which may differ from a document post-state, so each action's schema and help are worth
|
|
@@ -216,7 +270,8 @@ reading before automating it.
|
|
|
216
270
|
- `world.audit-files` finds document references to missing managed assets.
|
|
217
271
|
- `compendium.list`, `compendium.index`, and `compendium.get` read pack content.
|
|
218
272
|
- Supported `*.import-from-compendium` commands create world documents from pack sources.
|
|
219
|
-
- `user` and `setting
|
|
273
|
+
- `user list`/`user get` and `setting list`/`setting get`/`setting get-many` are the discovery
|
|
274
|
+
side of the [user and setting families](#world-content).
|
|
220
275
|
|
|
221
276
|
### Managed files
|
|
222
277
|
|
|
@@ -228,6 +283,106 @@ See [Security](security.md#file-write-boundary) before automating file writes.
|
|
|
228
283
|
|
|
229
284
|
## Shared command behavior
|
|
230
285
|
|
|
286
|
+
### Command permissions and approval
|
|
287
|
+
|
|
288
|
+
Every command has one of three behaviors in the GM client that holds the bridge: allow, approve, or
|
|
289
|
+
deny. A GM edits them under Configure Settings → Module Settings → World CLI → Command permissions.
|
|
290
|
+
The settings belong to the browser profile, so another browser or machine can apply a different
|
|
291
|
+
policy.
|
|
292
|
+
|
|
293
|
+
An approved command waits for the GM instead of failing. Foundry opens the Command Approval window.
|
|
294
|
+
It shows the command, its targets, and its parameters. The CLI writes one status line to stderr:
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
Waiting for GM approval in Foundry (command actor.delete, expires 2026-08-28T18:20:00.000Z). Press Ctrl+C to request cancellation.
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The line uses stderr in both output modes, so `--json` stdout still carries one envelope. Allow runs
|
|
301
|
+
the command at the time of the decision. The module repeats the normal guards first because world
|
|
302
|
+
state and permissions may have changed while the request waited. The CLI then returns the command's
|
|
303
|
+
success or error.
|
|
304
|
+
|
|
305
|
+
The wait tolerates short daemon outages, bridge reconnects, and poll timeouts while the approval
|
|
306
|
+
remains open. Other outcomes use structured errors:
|
|
307
|
+
|
|
308
|
+
| Code | Meaning | State |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| `COMMAND_DENIED` | The permission is deny, whether at the request or by the time an approved command runs | Not executed |
|
|
311
|
+
| `APPROVAL_DENIED` | The GM chose Deny | Not executed |
|
|
312
|
+
| `APPROVAL_TIMEOUT` | No decision was taken before the approval expired | Not executed |
|
|
313
|
+
| `APPROVAL_CANCELLED` | A cancellation the GM client confirmed won the decision | Not executed |
|
|
314
|
+
| `APPROVAL_QUEUE_FULL` | The module refused admission before showing the request | Not executed |
|
|
315
|
+
| `APPROVAL_UNKNOWN` | The module no longer holds the decision | Indeterminate |
|
|
316
|
+
|
|
317
|
+
`APPROVAL_UNKNOWN` means the client can no longer prove whether the command ran. Read the affected
|
|
318
|
+
world state before trying again. `APPROVAL_QUEUE_FULL` means the module refused the request before
|
|
319
|
+
execution. Retry after the GM clears earlier requests.
|
|
320
|
+
|
|
321
|
+
Ctrl+C asks the GM client to cancel a waiting decision. Only `APPROVAL_CANCELLED` proves that the
|
|
322
|
+
command will not run. If the command has started or the client cannot confirm cancellation, the CLI
|
|
323
|
+
reports an indeterminate result.
|
|
324
|
+
|
|
325
|
+
The default policy sorts commands into the three behaviors by what a mistake would cost. Commands
|
|
326
|
+
that can execute code, change who can do what, or persist outside the world's own data are denied
|
|
327
|
+
until a human enables them; the next section lists them. Commands that destroy world data ask for
|
|
328
|
+
approval, as do `system.reload` and `user.create`; the destructive set is the `delete` and
|
|
329
|
+
`delete-many` verbs, plus `file.move`, `scene.fog.reset`, and `chat.flush`. The remaining commands
|
|
330
|
+
run on their own unless they are exempt from the policy. The Command permissions window and
|
|
331
|
+
`fvtt-world-cli commands --json` show the current inventory.
|
|
332
|
+
|
|
333
|
+
#### Commands that are off by default
|
|
334
|
+
|
|
335
|
+
The following commands ship with the deny behavior. They stay invisible to
|
|
336
|
+
[discovery](#discovery-under-a-policy) and refuse to run, even as dry runs, until a GM enables
|
|
337
|
+
them in the Command permissions window of the browser profile holding the bridge:
|
|
338
|
+
|
|
339
|
+
- `macro.execute`
|
|
340
|
+
- `setting.set`
|
|
341
|
+
- `setting.set-many`
|
|
342
|
+
- `user.role.set`
|
|
343
|
+
- `user.permissions.set`
|
|
344
|
+
- `scene.region.behavior.executable.create`
|
|
345
|
+
- `scene.region.behavior.executable.update`
|
|
346
|
+
- `scene.region.behavior.executable.clone`
|
|
347
|
+
|
|
348
|
+
They are denied by default because each one executes code, changes who can do what, or persists
|
|
349
|
+
outside the world's own data. Enabling one is a per-browser-profile decision, and the approval
|
|
350
|
+
behavior remains available as a middle ground: a GM who wants to see every macro body before it
|
|
351
|
+
runs sets `macro.execute` to approve rather than allow. [Security](security.md) describes what each
|
|
352
|
+
of these surfaces can and cannot do.
|
|
353
|
+
|
|
354
|
+
`system.ping`, `system.info`, and the internal approval-wait commands always run. This keeps the
|
|
355
|
+
bridge able to report its state and finish an existing decision. The permissions window omits those
|
|
356
|
+
commands. Pairing and other `auth` operations run in the daemon, outside the command policy.
|
|
357
|
+
|
|
358
|
+
Foundry plays its standard interface notification when a request enters an empty queue. The `Play a
|
|
359
|
+
sound on approval requests` setting controls it. Browsers may delay the first sound until the GM
|
|
360
|
+
interacts with the page after a reload.
|
|
361
|
+
|
|
362
|
+
Set the approval deadline with `Approval timeout (minutes)` in the main Module Settings form. The
|
|
363
|
+
form and protocol enforce the supported range. Expiry refuses the command without running it.
|
|
364
|
+
|
|
365
|
+
Approval state lives in the GM client's memory. Reloading that client or ending its bridge session
|
|
366
|
+
can produce `BRIDGE_DISCONNECTED` or `APPROVAL_UNKNOWN`. Both are indeterminate. Read the affected
|
|
367
|
+
world state before another write, then use a fresh idempotency key if the command still needs to run.
|
|
368
|
+
The complete state and retry contract is in [Protocol](protocol.md#approval-flow).
|
|
369
|
+
|
|
370
|
+
### Discovery under a policy
|
|
371
|
+
|
|
372
|
+
With the bridge reachable, `commands` and `commands --json` describe what that GM client will
|
|
373
|
+
actually run. The listing omits denied commands. JSON marks approval waits with `"approval": true`;
|
|
374
|
+
plain output uses an `approval` tag. The JSON envelope also contains
|
|
375
|
+
`policy: { "applied": true, "source": "bridge" }`.
|
|
376
|
+
|
|
377
|
+
When no bridge answers, the listing falls back to the full static registry and says so:
|
|
378
|
+
`policy: { "applied": false, "source": "static", "reason": … }` in JSON, and a warning on stderr in
|
|
379
|
+
plain output. Only an unavailable daemon or bridge triggers fallback. Authentication, validation,
|
|
380
|
+
and protocol failures return errors.
|
|
381
|
+
|
|
382
|
+
`schema`, `--help`, and this documentation describe the whole static registry. If a caller sends a
|
|
383
|
+
denied command found there, the GM client returns `COMMAND_DENIED`. Discovery hides; the client
|
|
384
|
+
enforces.
|
|
385
|
+
|
|
231
386
|
### JSON output
|
|
232
387
|
|
|
233
388
|
Use `--json` for automation. Successful requests use a stable envelope:
|
|
@@ -303,9 +458,13 @@ All mutation commands accept the global `--dry-run` flag:
|
|
|
303
458
|
fvtt-world-cli --dry-run actor update --actor-id <id> --name "New name" --json
|
|
304
459
|
```
|
|
305
460
|
|
|
306
|
-
The result uses the normal command shape and includes `dryRun: true`. The preview contract
|
|
307
|
-
dry run executes, what it can report, and its
|
|
308
|
-
|
|
461
|
+
The result uses the normal command shape and includes `dryRun: true`. The preview contract is
|
|
462
|
+
defined in [Protocol](protocol.md#dry-run): what a dry run executes, what it can report, and its
|
|
463
|
+
non-reservation of state.
|
|
464
|
+
|
|
465
|
+
Approval does not hold a preview. A command whose permission is approve previews without asking the
|
|
466
|
+
GM, and its result carries `approvalRequired: true` so the caller knows the commit will wait. The GM
|
|
467
|
+
client returns `COMMAND_DENIED` for a denied preview.
|
|
309
468
|
|
|
310
469
|
### Idempotency and retries
|
|
311
470
|
|
|
@@ -361,7 +520,9 @@ First-run setup is covered in [Getting started](getting-started.md).
|
|
|
361
520
|
|
|
362
521
|
## Unsupported boundaries
|
|
363
522
|
|
|
364
|
-
The CLI intentionally does not provide arbitrary JavaScript
|
|
365
|
-
unrestricted filesystem access, generic RPC, compendium editing,
|
|
366
|
-
|
|
367
|
-
|
|
523
|
+
The CLI intentionally does not provide arbitrary JavaScript evaluation, direct world-database
|
|
524
|
+
writes, unrestricted filesystem access, generic RPC, compendium editing, or transactional Foundry
|
|
525
|
+
batches. Code runs only through `macro.execute` and `executeMacro` region behaviors, both off by
|
|
526
|
+
default; when a GM keeps them on approve, the window shows a macro execution's own body and names
|
|
527
|
+
the macro a region behavior would run. [Security](security.md) describes the trust
|
|
528
|
+
boundary and [Foundry compatibility](compatibility.md) the version-dependent capabilities.
|
package/docs/compatibility.md
CHANGED
|
@@ -7,9 +7,9 @@ result honestly.
|
|
|
7
7
|
## Operator contract
|
|
8
8
|
|
|
9
9
|
- `fvtt-world-cli system info --json` reports the connected Foundry, system, module, limits, and
|
|
10
|
-
|
|
10
|
+
discoverable command inventory.
|
|
11
11
|
- `UNSUPPORTED_OPERATION` is a capability result, not a transient transport failure.
|
|
12
|
-
- A dry run validates a proposed mutation
|
|
12
|
+
- A dry run validates a proposed mutation; it does not forecast values that require execution.
|
|
13
13
|
|
|
14
14
|
## Known differences
|
|
15
15
|
|
|
@@ -21,6 +21,8 @@ result honestly.
|
|
|
21
21
|
| Scene placeable fields | Older document models | Some families add fields | Open-family writes pass through sanitized data; reads expose the documented projection |
|
|
22
22
|
| Region behaviors | Core v13 type set | Additional core types | Executable core behavior types remain guarded; other types are Foundry-validated |
|
|
23
23
|
| Combat and action APIs | Version-specific signatures | Version-specific signatures | The bridge adapts known signatures and refuses unsupported behavior |
|
|
24
|
+
| Scene activation | `Scene#activate` runs for any caller | `Scene#activate` throws for non-GM users | The bridge requires GM authority on both versions |
|
|
25
|
+
| User permission names | `TEMPLATE_CREATE` | Renamed to `REGION_CREATE` | `user.permissions.set` validates keys against the connected version's live permission set rather than a fixed list |
|
|
24
26
|
|
|
25
27
|
This table describes current operator-visible differences, not implementation evidence. Exact
|
|
26
28
|
capabilities remain defined by the connected bridge and exercised by live smoke tests.
|
package/docs/getting-started.md
CHANGED
|
@@ -16,7 +16,7 @@ tool is in use.
|
|
|
16
16
|
|
|
17
17
|
The daemon should be running before the Foundry GM client loads the world: the module tries to
|
|
18
18
|
connect once when the world loads, and if the daemon is down at that moment it gives up with a
|
|
19
|
-
warning. Connecting after that is a manual retry
|
|
19
|
+
warning. Connecting after that is a manual retry: Connect in the scene controls, or a client
|
|
20
20
|
reload.
|
|
21
21
|
|
|
22
22
|
## 2. Pair the Foundry GM client
|
|
@@ -27,10 +27,10 @@ browser.
|
|
|
27
27
|
It starts in the module's Authorization window. The quickest way there is the `World CLI` group at
|
|
28
28
|
the bottom of the left scene controls; its icon doubles as a status light:
|
|
29
29
|
|
|
30
|
-
- muted grey
|
|
31
|
-
- amber
|
|
32
|
-
- green
|
|
33
|
-
- red
|
|
30
|
+
- muted grey: not paired yet
|
|
31
|
+
- amber: connecting
|
|
32
|
+
- green: the bridge is up
|
|
33
|
+
- red: stopped or failed
|
|
34
34
|
|
|
35
35
|
The same windows are also reachable through Configure Settings → Module Settings:
|
|
36
36
|
|
|
@@ -48,15 +48,15 @@ record.
|
|
|
48
48
|
|
|
49
49
|

|
|
50
50
|
|
|
51
|
-
Choose Pair, then approve the request from a terminal on the daemon's machine
|
|
51
|
+
Choose Pair, then approve the request from a terminal on the daemon's machine. The approval is
|
|
52
52
|
what turns the request into a stored permission:
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
fvtt-world-cli auth
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
The command waits for the pairing request
|
|
59
|
-
shows who is asking
|
|
58
|
+
The command waits for the pairing request; starting it before or after clicking Pair works either
|
|
59
|
+
way. It shows who is asking, with the origin, world, GM, browser label, and client id, and asks
|
|
60
60
|
`Approve pairing request <code>? [y/N]`. Typing `y` and pressing Enter approves it; any other
|
|
61
61
|
answer denies it. To read a request over before deciding, or to approve from a script, the same
|
|
62
62
|
approval exists as two steps:
|
|
@@ -68,7 +68,7 @@ fvtt-world-cli auth approve <code> # add --yes in a script to skip the confirm
|
|
|
68
68
|
|
|
69
69
|
## 3. Confirm the bridge
|
|
70
70
|
|
|
71
|
-
On approval the browser stores its pairing credential and starts the bridge immediately
|
|
71
|
+
On approval the browser stores its pairing credential and starts the bridge immediately. No
|
|
72
72
|
reload is needed, and the scene-controls icon turns green. From now on the bridge connects on its
|
|
73
73
|
own as soon as the world loads: that is the client-scoped `Connect automatically` setting, enabled
|
|
74
74
|
by default; with it disabled the bridge stays offline until Connect is chosen.
|
|
@@ -81,6 +81,15 @@ fvtt-world-cli system info --json
|
|
|
81
81
|
|
|
82
82
|
It reports `bridge.status` as `connected`.
|
|
83
83
|
|
|
84
|
+
Commands use the permissions stored for that browser profile. Run `fvtt-world-cli commands` after
|
|
85
|
+
the bridge connects to see which commands the client will run. A daemon that rejects the browser
|
|
86
|
+
credential returns an error instead of the static registry.
|
|
87
|
+
|
|
88
|
+
Destructive commands ask the GM by default. Set each command's behavior under Configure Settings →
|
|
89
|
+
Module Settings → World CLI → Command permissions. Set the waiting time in the `Approval timeout
|
|
90
|
+
(minutes)` field in the main Module Settings form. [Commands](commands.md#command-permissions-and-approval)
|
|
91
|
+
describes the behavior in full.
|
|
92
|
+
|
|
84
93
|
## When something deviates
|
|
85
94
|
|
|
86
95
|
The Bridge status window (in the scene controls or the module settings) is the first place to
|
|
@@ -96,7 +105,7 @@ abnormal-disconnect lease.
|
|
|
96
105
|
|
|
97
106
|
One bridge is active at a time, so a second paired browser's connection attempt stops on
|
|
98
107
|
`BRIDGE_BUSY` and stays stopped. Switching browsers is a Disconnect on the active one followed by
|
|
99
|
-
Connect on the other
|
|
108
|
+
Connect on the other; pairing plays no part in it.
|
|
100
109
|
|
|
101
110
|
Authorization holds the two exits. Unpair revokes this browser's access and removes its credential
|
|
102
111
|
once the daemon confirms the revocation. Forget local is the recovery for a daemon that cannot be
|