fvtt-world-cli 1.1.0 → 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/docs/commands.md CHANGED
@@ -1,340 +1,108 @@
1
1
  # Commands
2
2
 
3
- This is the human-readable overview of the `fvtt-world-cli` command surface. The `worldctl` executable
4
- is an equivalent short alias. This guide uses the canonical name. It explains shared behavior
5
- and helps you find the relevant command family. For exact syntax in the installed version, use the
6
- CLI's discovery commands.
3
+ `fvtt-world-cli` and its short alias `worldctl` accept the same commands. World operations require
4
+ a running daemon and an open, paired GM session; see [Getting started](getting-started.md).
5
+
6
+ ## Finding the right command
7
7
 
8
8
  ```bash
9
- fvtt-world-cli commands
10
- fvtt-world-cli schema <protocol-command>
11
- fvtt-world-cli <command path> --help
12
- fvtt-world-cli docs [document]
9
+ fvtt-world-cli commands --json
10
+ fvtt-world-cli schema actor.update
11
+ fvtt-world-cli actor update --help
13
12
  ```
14
13
 
15
- For example, `fvtt-world-cli schema actor.update` shows the protocol request schema while
16
- `fvtt-world-cli actor update --help` shows its CLI flags. `docs` lists and prints the documentation
17
- shipped with the installed CLI, this guide included. Agents operate through the packaged agent
18
- skill, managed with the `fvtt-world-cli skill install`, `skill update`, and `skill remove`
19
- commands; [Agent skill](skill.md) covers the whole lifecycle.
14
+ `commands` lists available operations, `schema` shows accepted protocol parameters, and `--help`
15
+ shows CLI flags. Protocol names use dots; CLI commands use spaces. For example, `scene.token.get`
16
+ becomes `scene token get`. Nested commands require the parent IDs as well as the target's ID.
20
17
 
21
- ## Before you begin
18
+ With a bridge connected, discovery omits denied commands and marks approval requirements with
19
+ `approval: true`. If the daemon or bridge is unavailable, it returns the static registry with
20
+ `policy.applied: false`. Authentication and protocol errors do not trigger that fallback.
21
+ `schema` and `--help` always describe the static registry, including denied commands.
22
22
 
23
- Start the local daemon and keep an authenticated GM client open in the target Foundry world:
23
+ ## Capability map
24
24
 
25
- ```bash
26
- fvtt-world-cli bridge serve
27
- ```
25
+ Use these families to narrow your search, then check their help for supported operations.
26
+
27
+ | Task | Command families |
28
+ |---|---|
29
+ | Edit world content | `actor`, `item`, `journal`, `scene`, `macro`, `playlist`, `table`, `cards` |
30
+ | Edit embedded content | Nested families such as `actor.item`, `playlist.sound`, `table.result`, and `*.effect` |
31
+ | Edit scene placeables | `scene.token`, `scene.wall`, `scene.region`, and related families |
32
+ | Manage encounters | `combat`, `combat.combatant`, `combat.group` |
33
+ | Organize content and access | `folder`, `*.ownership.set`, `user` |
34
+ | Manage chat and settings | `chat`, `setting` |
35
+ | Find content or missing assets | `world.search`, `world.audit-files` |
36
+ | Read packs or import world documents | `compendium`, supported `*.import-from-compendium` commands |
37
+ | Manage assets | `file` |
38
+ | Control playback and gameplay | Actions within `playlist`, `table`, `cards`, `combat`, `scene`, and `game` |
39
+ | Show content to players | `scene pull-users`, `journal show`, `image show` |
40
+ | Inspect the connection | `system info`, `system ping` |
28
41
 
29
- On first run, that GM client is paired once. With the daemon running, Foundry's Module Settings →
30
- Authorization carries a Pair button, and one command in the terminal covers the rest: it waits for
31
- the request, prints the Origin, world, GM, and browser label it carries, and asks for a yes or no.
42
+ ## Common workflows
32
43
 
33
- ```bash
34
- fvtt-world-cli auth
35
- ```
44
+ ### Find, inspect, update, verify
36
45
 
37
- The wait can be started before or after Pair is clicked: a request that is already pending surfaces
38
- immediately. Reading a request over before deciding, or approving from a script, is the two-step path
39
- instead, where non-interactive approval requires `--yes`:
46
+ Check the connected world before making changes:
40
47
 
41
48
  ```bash
42
- fvtt-world-cli auth pending
43
- fvtt-world-cli auth approve [code]
49
+ fvtt-world-cli system info --json
50
+ fvtt-world-cli actor list --name "Goblin" --json
51
+ fvtt-world-cli actor get --actor-id <id> --json
52
+ fvtt-world-cli --dry-run actor update --actor-id <id> --name "Goblin Scout" --json
53
+ fvtt-world-cli actor update --actor-id <id> --name "Goblin Scout" --json
54
+ fvtt-world-cli actor get --actor-id <id> --json
44
55
  ```
45
56
 
46
- The full first-run walkthrough is in [Getting started](getting-started.md).
57
+ ### Work with an embedded document
47
58
 
48
- Confirm the connection before reading or writing:
59
+ Supply the complete parent ID chain. Read the parent to find missing IDs:
49
60
 
50
61
  ```bash
51
- fvtt-world-cli system info --json
62
+ fvtt-world-cli scene token list --scene-id <sceneId> --json
63
+ fvtt-world-cli scene token get --scene-id <sceneId> --token-id <tokenId> --json
52
64
  ```
53
65
 
54
- Profiles can be inspected with `auth list`, revoked independently or pruned once they fall idle, and
55
- the active slot can be cleared with `bridge release`. The daemon never prints a secret.
56
-
57
- ### Authorization commands
58
-
59
- - `auth` with no subcommand waits for a pairing request and approves it on the spot. It prints an
60
- instruction to click Pair in the module's Authorization window, and the request surfaces in the
61
- terminal as soon as the daemon receives it, including one that was already pending when the command
62
- started. Several live requests are not an obstacle to it as they are to `auth approve` with no code:
63
- the wait renders the earliest one and asks about that, and a later request stays pending for another
64
- run. The wait itself is indefinite: it ends on Ctrl+C, on an answer, or if the daemon connection
65
- drops, which is reported as `DAEMON_UNAVAILABLE` rather than a silent stop and is safe to re-run once
66
- the daemon is back. `y` or `yes` approves the displayed code and exits 0, any other answer denies that
67
- code and exits 1 with `PAIRING_DECLINED`.
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.
73
- Delivery is a long-poll control call the CLI re-issues on its own, and the daemon answers each call
74
- inside its own park cap, so an ordinary wait does not end in a timeout; a daemon that stops answering
75
- altogether still trips the client's request timeout and is reported as `DAEMON_UNAVAILABLE`. The
76
- command is interactive-only: `--json`, and a stdin that is not a terminal, each fail
77
- immediately and name `auth pending` plus `auth approve --yes` as the path for scripts.
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.
85
- - `auth status` shows bridge state and public profile metadata.
86
- - `auth pending` lists approval candidates with code, expiry, exact Origin, world, GM, browser client
87
- id, browser label, and module version.
88
- - `auth approve [code] [--yes]` approves one request. The code may be omitted only when exactly one
89
- request is pending. Interactive use confirms the displayed identity, including the browser label and
90
- client id, and then approves that exact request: one that expired or disappeared while the prompt
91
- waited fails rather than approving whatever else is pending. When no single pending request can be
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.
99
- - `auth deny <code>` rejects a pending request.
100
- - `auth list` shows non-secret profile metadata, including each profile's `clientId` and `label`.
101
- A label is set in the browser at pairing time and no control operation renames a stored record, so
102
- a browser that needs a different label unpairs and pairs again. A browser that only forgot its local
103
- credential keeps its record, and the next approval adopts the label that request carried. Labels are
104
- not unique, so two browsers may share one. A profile's `lastSeenAt` is stamped when it is approved,
105
- when its browser connects the bridge, when that connection ends, and when a hello is turned away
106
- because another profile owns the slot, so the timestamp measures how long the profile has been idle
107
- rather than how long ago it last connected. A second browser that is opened daily while the first
108
- one holds the bridge therefore stays fresh and is never pruned out from under its owner.
109
- - `auth prune [--older-than <days>] [--yes]` deletes the profiles that have gone unused. A profile is
110
- a candidate when its `lastSeenAt` is older than the cutoff, which defaults to 30 days; the active
111
- bridge profile and the holder of an abnormal-disconnect lease are never deleted, however idle their
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
117
- `PAIRING_PROMPT_ABORTED` and exits 1 on the same terms as `auth approve`. A listing that found no
118
- candidate asks nothing and still runs the operation, so the command's output is the daemon's own empty
119
- result rather than a local verdict. The listing is a preview only: the daemon recomputes the set,
120
- against the cutoff as it stands when the command runs, and its own result is what the command prints.
121
- The executed set can therefore be larger than the one the prompt counted, because a profile that
122
- crossed the cutoff while the prompt waited is removed although it was never listed; it can equally be
123
- smaller, because a profile that became active or was already removed between the two steps is
124
- reported as the daemon left it. There is no dry-run mode, and `--older-than 0` treats every profile
125
- as idle, which is the one case where a preview can name a lease holder the daemon then keeps.
126
- `auth list` does not expose the lease, and a lease holder's last-seen timestamp is fresh by
127
- definition, so no realistic threshold selects it. Scripts pass `--yes`, which skips both the preview
128
- and the prompt; `--json` requires `--yes` as well, because the confirmation is never mixed into JSON
129
- output, and without it the command stops with exit 2.
130
- - `auth revoke <pairingId>` deletes one daemon profile and disconnects it if active.
131
- - `auth rotate-client --yes` replaces the device-local CLI/Companion credential and closes existing
132
- local-client sockets without invalidating browser pairings.
133
- - `bridge release` clears the active slot or abnormal-disconnect lease without deleting a profile.
134
- An active browser stopped by release stays stopped until its operator chooses Connect.
135
-
136
- Every command in this section is answered by the daemon alone, with no Foundry browser involved, so
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.
139
-
140
- In Foundry, Connect reuses the stored browser credential and Disconnect releases the slot without
141
- touching it. Unpair waits for confirmed daemon revocation
142
- before deleting that credential. If revocation cannot be confirmed, it retains the credential and
143
- offers Forget local as the explicit recovery path; Forget local does not revoke the daemon profile.
144
- `BRIDGE_BUSY` likewise preserves the credential: release the current bridge and choose Connect.
145
-
146
- ### Local configuration
147
-
148
- - `config get` shows the config path and non-secret settings.
149
- - `config set-upload-limit <size>` persists the raw upload-byte limit. The daemon preserves this
150
- field across later pairing and bridge-session writes, but a running daemon must be restarted before
151
- its WebSocket transport and the browser bridge advertise the new limit. JSON output includes
152
- `daemonRestartRequired: true`.
153
-
154
- ## Finding the right command
155
-
156
- Command names describe the document nesting. Dots are used in the protocol registry; spaces are used
157
- on the CLI:
66
+ ### Send a batch of commands
158
67
 
159
- | Goal | Protocol family | CLI shape |
160
- |---|---|---|
161
- | World actors | `actor.*` | `fvtt-world-cli actor …` |
162
- | Items embedded in an actor | `actor.item.*` | `fvtt-world-cli actor item …` |
163
- | Effects on an actor item | `actor.item.effect.*` | `fvtt-world-cli actor item effect …` |
164
- | Tokens embedded in a scene | `scene.token.*` | `fvtt-world-cli scene token …` |
165
- | Effects on a placed token | `scene.token.effect.*` | `fvtt-world-cli scene token effect …` |
68
+ Save these lines as `requests.ndjson`:
166
69
 
167
- Use `fvtt-world-cli commands --json` for the current inventory. The exact operation set
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.
171
-
172
- ## Capability map
173
-
174
- ### World content
175
-
176
- - `actor`, `item`, `journal`, `scene`, `macro`, `playlist`, `table`, and `cards` manage world
177
- documents.
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.
194
- - `combat` manages encounters and exposes explicit encounter transitions.
195
- - `folder` manages document organization.
196
- - Dedicated `*.ownership.set` commands change supported document ownership.
197
-
198
- Common world-document operations include `list`, `get`, `get-many`, `create`, `update`, `clone`, and
199
- `delete`. The exact set varies by family.
200
-
201
- ### Embedded content
202
-
203
- - `actor.item` manages an actor's embedded items.
204
- - `*.effect` families manage ActiveEffects on actors, items, actor items, tokens, and token items.
205
- - `journal.category` manages journal page categories.
206
- - `playlist.sound` manages playlist tracks.
207
- - `table.result` manages roll-table rows.
208
- - `cards.card` manages cards inside a stack.
209
- - `combat.combatant` and `combat.group` manage encounter membership.
210
- - `scene.token`, `tile`, `sound`, `wall`, `note`, `drawing`, `light`, `template`, and `region` manage
211
- scene placeables.
212
- - `scene.region.behavior` manages region behaviors; writes that supply executable core behavior
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`.
218
-
219
- Embedded commands require the complete parent ID chain; a read of the parent supplies those IDs when
220
- they are not already known.
70
+ ```jsonl
71
+ {"id":"check-bridge","command":"system.ping","params":{}}
72
+ {"id":"inspect-world","command":"system.info","params":{}}
73
+ ```
221
74
 
222
- ### Actions
75
+ ```bash
76
+ fvtt-world-cli exec --stdin < requests.ndjson
77
+ ```
223
78
 
224
- Some commands invoke a typed Foundry action instead of ordinary CRUD:
225
-
226
- - playlist and playlist-sound playback;
227
- - roll-table draw and reset;
228
- - card shuffle, reset, deal, draw, and pass;
229
- - combat start, activation, advancement, and initiative;
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.
262
-
263
- Actions can have Foundry, system, or module side effects. Their result describes what the bridge can
264
- confirm, which may differ from a document post-state, so each action's schema and help are worth
265
- reading before automating it.
266
-
267
- ### Discovery and maintenance
268
-
269
- - `world.search` finds content across supported world and optional compendium indexes.
270
- - `world.audit-files` finds document references to missing managed assets.
271
- - `compendium.list`, `compendium.index`, and `compendium.get` read pack content.
272
- - Supported `*.import-from-compendium` commands create world documents from pack sources.
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).
275
-
276
- ### Managed files
277
-
278
- `file` commands operate on Foundry's managed `data` source. Reads can inspect managed assets. Writes
279
- are restricted to the active world's allowed tree and exclude its manifest, databases, and packs.
280
- Document references are updated separately with an explicit document command.
281
-
282
- See [Security](security.md#file-write-boundary) before automating file writes.
79
+ Requests run in order. Output is NDJSON, with the supplied `id` and zero-based line `index` on each
80
+ response. Failures do not stop later requests unless you pass `--stop-on-error`; any failure makes
81
+ the process exit non-zero.
283
82
 
284
83
  ## Shared command behavior
285
84
 
286
85
  ### Command permissions and approval
287
86
 
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:
87
+ Set allow, approve, or deny under Configure Settings Module Settings World CLI Command
88
+ permissions. The policy belongs to the browser profile holding the bridge. The window shows every
89
+ command, including those hidden from CLI discovery. See [Security](security.md#permissions-and-destructive-actions)
90
+ for default permissions and review limits.
295
91
 
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
- ```
92
+ When approval is required, the CLI waits for the GM to answer Foundry's Command Approval window.
93
+ Its waiting message goes to stderr, leaving stdout for JSON results.
94
+ The *Approval timeout (minutes)* setting controls the deadline.
299
95
 
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.
96
+ Ctrl+C requests cancellation. Only `APPROVAL_CANCELLED` proves it succeeded. If execution has
97
+ started or cancellation cannot be confirmed, inspect world state before retrying.
98
+ [Protocol](protocol.md#delivery-states-and-retries) lists the outcomes and safe next actions.
332
99
 
333
100
  #### Commands that are off by default
334
101
 
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:
102
+ These commands ship with the deny behavior: hidden from discovery and refused, even as dry runs,
103
+ until a GM enables them in the Command permissions window. Each one executes code, changes who can
104
+ do what, or persists outside the world's own data. Approve is the middle ground: a GM who wants to
105
+ review every macro body before it runs sets `macro.execute` to approve rather than allow.
338
106
 
339
107
  - `macro.execute`
340
108
  - `setting.set`
@@ -345,184 +113,138 @@ them in the Command permissions window of the browser profile holding the bridge
345
113
  - `scene.region.behavior.executable.update`
346
114
  - `scene.region.behavior.executable.clone`
347
115
 
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
-
386
116
  ### JSON output
387
117
 
388
- Use `--json` for automation. Successful requests use a stable envelope:
389
-
390
- ```json
391
- {
392
- "protocolVersion": "…",
393
- "type": "command.response",
394
- "id": "…",
395
- "ok": true,
396
- "result": {
397
- "actor": {}
398
- }
399
- }
400
- ```
118
+ Use `--json` for automation. World commands return `ok: true` with `result`, or `ok: false` with
119
+ an `error` containing a code, message, and optional details. Failures exit non-zero. Branch on
120
+ `error.code`, not the message text. See the [response examples](protocol.md#request-and-response-example).
401
121
 
402
- Documents are stored under a type-named result key such as `actor`, `items`, `scene`, or `outcomes`.
403
- The key varies between commands but is stable for each one, so a first response shows what to script
404
- against. Errors use `ok: false`, a stable code, a
405
- message, and optional details; see [Protocol](protocol.md#error-model).
122
+ Results use command-specific keys such as `actor`, `items`, or `outcomes`. Use returned document
123
+ `id` fields to address later commands.
406
124
 
407
- Serialized Foundry documents generally expose `id` as the public identifier and may also expose the
408
- source `_id` mirror. Use the documented `id` fields for subsequent commands.
125
+ ### Reading collections and documents
409
126
 
410
- ### Input validation
127
+ Paginated commands accept `--limit` and `--offset`. Continue until the response's `hasMore` is false;
128
+ `total` describes the matching collection. Supported filters apply before pagination.
411
129
 
412
- Input is validated locally and again at the Foundry boundary. Unknown options, missing required
413
- options, and malformed CLI values are usage errors. Protocol payloads use closed schemas where the
414
- bridge owns the writable field set and sanitized open schemas where Foundry or a game system owns
415
- extensible data.
130
+ List rows contain fewer fields than `get` results. Read the target with `get` before preparing an
131
+ update. Reads return authored state; some commands accept `include` options for derived or expensive
132
+ data. Check the command's help for available projections and filters.
416
133
 
417
- Use JSON flags such as `--data-json`, `--patch-json`, and family-specific JSON flags for structured
418
- values. The command schema is the definitive description of accepted keys.
134
+ ### Updates and merge semantics
419
135
 
420
- ### Lists, filters, and pagination
136
+ Use JSON flags such as `--data-json` and `--patch-json` for structured values. Updates are patches:
421
137
 
422
- List-like commands that support pagination accept `limit` and `offset` and return collection data
423
- plus `total` and `hasMore`. One response is not guaranteed to contain the entire collection; paging
424
- continues until `hasMore` is false.
138
+ - Nested objects merge recursively.
139
+ - Ordinary arrays replace as a whole.
140
+ - Dotted paths target object properties where permitted; dotted writes inside arrays are refused.
141
+ - Foundry deletion syntax can remove permitted nested keys.
142
+ - Embedded collections follow their family's Foundry semantics.
425
143
 
426
- Some collections support a case-insensitive `name` filter before pagination. Search commands use
427
- their own matching rules and are not interchangeable with a list filter. Whether a filter exists is
428
- recorded in each command's schema.
144
+ Read arrays before editing and preserve unchanged values. For a wall's `c` field, send the complete
145
+ `[x1, y1, x2, y2]`, not `c.0`. Invalid or ambiguous patch shapes return `INVALID_PARAMS` before
146
+ mutation, including in bulk writes and dry runs. Correct the fields identified by the error.
429
147
 
430
- ### Reads and projections
148
+ Prefer dedicated commands for creating embedded documents. A parent update that adds entries
149
+ without `_id`, such as new `behaviors` in `scene.region.update`, can create them but return an
150
+ indeterminate error. Inspect the collection before retrying to avoid duplicates.
431
151
 
432
- Single-document `get` operations return the documented authored projection. Some commands accept
433
- `include` values for derived or expensive data. Derived data is explicitly identified and can vary by
434
- Foundry version and game system.
152
+ ### Dry run
435
153
 
436
- List rows are intentionally smaller than `get` results and are not sufficient to construct an update
437
- from; that starts from a fresh `get`.
154
+ The global `--dry-run` flag validates and prepares a mutation without writing. Results include
155
+ `dryRun: true`. A preview cannot predict execution-dependent effects or reserve world state.
438
156
 
439
- ### Updates and merge semantics
157
+ Previews skip approval and report `approvalRequired: true` if the real command would wait.
158
+ Denied commands remain denied in previews.
440
159
 
441
- Updates are patches, not full replacements:
160
+ ### Failures and retries
442
161
 
443
- - nested objects merge recursively;
444
- - ordinary arrays replace as a whole;
445
- - dotted paths target a nested leaf where the schema permits them;
446
- - Foundry deletion syntax can remove permitted nested keys;
447
- - embedded-document collections follow their family-specific Foundry semantics.
162
+ A timeout or disconnect can happen after a write commits. An update error with `details.partial`
163
+ or `details.indeterminate` can also mean that some changes persisted. Read affected state before
164
+ retrying and send only what remains to be done.
448
165
 
449
- Arrays and extensible system data are the easiest fields to clobber: an array patch replaces the
450
- whole array, so one built from stale state silently drops entries. A fresh read before editing, the
451
- smallest patch that expresses the change, and a read-back afterwards avoid that.
166
+ An idempotency key identifies one logical request and can prevent duplicate effects while its
167
+ cached state survives. Reuse it only for the same request; a corrected or remaining patch is a
168
+ new operation and needs a fresh key. Follow the [delivery rules](protocol.md#delivery-states-and-retries)
169
+ when a connection fails, since some failures also lose the cached state.
452
170
 
453
- ### Dry run
171
+ Bulk writes are not transactions. Inspect `complete` and every entry in `outcomes` before deciding
172
+ what to retry.
454
173
 
455
- All mutation commands accept the global `--dry-run` flag:
174
+ ### Actions
456
175
 
457
- ```bash
458
- fvtt-world-cli --dry-run actor update --actor-id <id> --name "New name" --json
459
- ```
176
+ `scene pull-users`, `journal show`, and `image show` target every connected player when
177
+ `--user-ids` is omitted. Supply a non-empty list to select players. Their results confirm dispatch,
178
+ not that a player saw the content.
460
179
 
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.
180
+ `macro execute` can leave effects behind after an error, and `MACRO_TIMEOUT` does not stop the
181
+ macro. Verify affected state before retrying. See [Executable content](security.md#executable-content)
182
+ before enabling macro execution or executable region behaviors.
464
183
 
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.
184
+ ### File paths
468
185
 
469
- ### Idempotency and retries
186
+ File commands use Foundry's managed `data` source. Writes belong under `worlds/<worldId>/` and
187
+ exclude the world's manifest, databases, and packs. Supply literal paths, without pre-encoding
188
+ filename characters. Change document references separately after a file operation.
189
+ See [File write boundary](security.md#file-write-boundary) for the exact restrictions.
470
190
 
471
- Commands with duplicate-creation or non-repeatable-action risk may require or accept an idempotency
472
- key. An operation that timed out or disconnected may already have reached Foundry, so a blind retry
473
- can apply it twice. Key semantics and delivery-state retry rules are defined in
474
- [Protocol](protocol.md#idempotency) and
475
- [Protocol](protocol.md#delivery-states-and-retries).
191
+ ## Authorization commands
476
192
 
477
- ### Batch reads and bulk writes
193
+ These commands require the daemon but no open Foundry browser. Prefix the commands below with
194
+ `fvtt-world-cli`.
478
195
 
479
- `get-many` reduces round trips for independent reads. `exec --stdin` sends NDJSON commands over one
480
- connection while retaining an individual response for each request.
196
+ ### Wait for pairing
481
197
 
482
- Families that expose `create-many`, `update-many`, or `delete-many` validate the envelope and each
483
- element before dispatch, but the persistence layer is not transactional. Inspect `complete` and every
484
- entry in `outcomes`; see [Protocol](protocol.md#batch-requests-and-bulk-writes).
198
+ `auth` waits for the earliest pending pairing request and displays its identity. Start it before
199
+ or after clicking *Pair* in Foundry. `y` or `yes` approves; any other answer denies that request.
200
+ Ctrl+C or ended input during the prompt leaves it pending.
485
201
 
486
- ### File paths
202
+ The wait has no overall deadline; `--timeout-ms` does not set one. If the daemon becomes unavailable,
203
+ restart `auth` after restoring it. Bare `auth` requires an interactive terminal. Scripts should
204
+ inspect `auth pending`, then use `auth approve <code> --yes` for the selected request.
487
205
 
488
- Pass literal managed-data paths. The bridge normalizes and encodes document asset references where
489
- appropriate; callers should not pre-encode ordinary filename characters. URLs, virtual texture IDs,
490
- and other special values follow the receiving field's schema.
206
+ ### Manage pairings and connections
491
207
 
492
- ## Common workflows
208
+ | Command | Effect |
209
+ |---|---|
210
+ | `auth status` | Show bridge state and public profile metadata |
211
+ | `auth pending` | List pending codes, expiry, and browser/world/GM identity |
212
+ | `auth approve [code] [--yes]` | Approve a selected request; omit the code only when exactly one is pending |
213
+ | `auth deny <code>` | Reject a pending request |
214
+ | `auth list` | List paired browsers and their last-seen times |
215
+ | `auth prune [--older-than <days>] [--yes]` | Remove idle pairings, defaulting to 30 days |
216
+ | `auth revoke <pairingId>` | Delete a pairing and disconnect it if active |
217
+ | `auth rotate-client --yes` | Replace the local CLI credential and close local-client sockets; browser pairings remain valid |
218
+ | `bridge release` | Free the active slot or reconnect lease without deleting its pairing |
493
219
 
494
- ### Find, inspect, update, verify
220
+ Interactive `auth approve` always acts on the request it displayed. If that request expires,
221
+ it fails without choosing another. A negative answer leaves the request pending, unlike bare
222
+ `auth`, which denies it. Scripts must pass `--yes`.
495
223
 
496
- ```bash
497
- fvtt-world-cli actor list --name "Goblin" --json
498
- fvtt-world-cli actor get --actor-id <id> --json
499
- fvtt-world-cli --dry-run actor update --actor-id <id> --name "Goblin Scout" --json
500
- fvtt-world-cli actor update --actor-id <id> --name "Goblin Scout" --json
501
- fvtt-world-cli actor get --actor-id <id> --json
502
- ```
224
+ Browser labels need not be unique; use client ids to distinguish them. To rename a browser,
225
+ unpair and pair again. *Disconnect* preserves its pairing. *Unpair* revokes it, while *Forget local*
226
+ only deletes the browser's credential. After `bridge release`, the released browser stays stopped
227
+ until its operator chooses *Connect*.
503
228
 
504
- ### Discover an unfamiliar command
229
+ ### Pruning idle pairings
505
230
 
506
- ```bash
507
- fvtt-world-cli commands --json
508
- fvtt-world-cli schema scene.token.create
509
- fvtt-world-cli scene token create --help
510
- ```
231
+ `auth prune` uses `lastSeenAt` to find idle pairings and protects the active bridge and any holder
232
+ of a reconnect lease. Interactive use lists candidates and asks once before removal. The daemon
233
+ recomputes candidates when the command runs, so a pairing that crosses the cutoff while the
234
+ prompt waits can be removed even if it was not listed. Use `auth revoke <pairingId>` to remove
235
+ only a specific profile.
511
236
 
512
- ### Work with an embedded document
237
+ There is no dry run. `--older-than 0` selects all idle pairings, subject to active-bridge and
238
+ lease protection. An empty initial listing skips the prompt but still runs the prune operation.
239
+ For scripts and JSON output, pass `--yes` to skip the listing and prompt.
513
240
 
514
- ```bash
515
- fvtt-world-cli scene token list --scene-id <sceneId> --json
516
- fvtt-world-cli scene token get --scene-id <sceneId> --token-id <tokenId> --json
517
- ```
241
+ ## Local configuration
518
242
 
519
- First-run setup is covered in [Getting started](getting-started.md).
243
+ `config get` shows the config path and non-secret settings. `config set-upload-limit <size>`
244
+ changes the raw upload-byte limit; restart the daemon for it to take effect.
520
245
 
521
- ## Unsupported boundaries
246
+ If set, `XDG_CONFIG_HOME` places configuration under `$XDG_CONFIG_HOME/fvtt-world-cli`. Otherwise:
522
247
 
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.
248
+ - Linux: `~/.config/fvtt-world-cli`.
249
+ - macOS: `~/Library/Application Support/fvtt-world-cli`.
250
+ - Windows: `%APPDATA%\fvtt-world-cli`.