fvtt-world-cli 1.2.0 → 1.3.0

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
@@ -17,8 +17,9 @@ becomes `scene token get`. Nested commands require the parent IDs as well as the
17
17
 
18
18
  With a bridge connected, discovery omits denied commands and marks approval requirements with
19
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.
20
+ `policy.applied: false`; `policy` sits at the top level of the response, beside `result`.
21
+ Authentication and protocol errors do not trigger that fallback. `schema` and `--help` always
22
+ describe the static registry, including denied commands.
22
23
 
23
24
  ## Capability map
24
25
 
@@ -27,13 +28,13 @@ Use these families to narrow your search, then check their help for supported op
27
28
  | Task | Command families |
28
29
  |---|---|
29
30
  | 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 embedded content | Nested families such as `actor.item`, `journal.page`, `playlist.sound`, `table.result`, and `*.effect` |
31
32
  | Edit scene placeables | `scene.token`, `scene.wall`, `scene.region`, and related families |
32
33
  | Edit scene levels (v14) | `scene.level` |
33
34
  | Manage encounters | `combat`, `combat.combatant`, `combat.group` |
34
35
  | Organize content and access | `folder`, `*.ownership.set`, `user` |
35
36
  | Manage chat and settings | `chat`, `setting` |
36
- | Find content or missing assets | `world.search`, `world.audit-files` |
37
+ | Find content, references, or missing assets | `world.search`, `world.references`, `world.audit-files` |
37
38
  | Read packs or import world documents | `compendium`, supported `*.import-from-compendium` commands |
38
39
  | Manage assets | `file` |
39
40
  | Control playback and gameplay | Actions within `playlist`, `table`, `cards`, `combat`, `scene`, and `game` |
@@ -164,13 +165,15 @@ Use JSON flags such as `--data-json` and `--patch-json` for structured values. U
164
165
 
165
166
  - Nested objects merge recursively.
166
167
  - Ordinary arrays replace as a whole.
167
- - Dotted paths target object properties where permitted; dotted writes inside arrays are refused.
168
- - Foundry deletion syntax can remove permitted nested keys.
168
+ - Dotted paths target object properties where permitted; a top-level dotted write into an array
169
+ field, such as `c.0`, is refused.
170
+ - Foundry's operator keys work at any permitted nested level: `{"-=key": null}` deletes the key,
171
+ and `{"==key": value}` replaces the key's whole value instead of merging into it.
169
172
  - Embedded collections follow their family's Foundry semantics.
170
173
 
171
174
  Read arrays before editing and preserve unchanged values. For a wall's `c` field, send the complete
172
- `[x1, y1, x2, y2]`, not `c.0`. Invalid or ambiguous patch shapes return `INVALID_PARAMS` before
173
- mutation, including in bulk writes and dry runs. Correct the fields identified by the error.
175
+ `[x1, y1, x2, y2]`, not `c.0`. Array writes that Foundry would discard return `INVALID_PARAMS`
176
+ before mutation, including in bulk writes and dry runs. Correct the fields identified by the error.
174
177
 
175
178
  Prefer dedicated commands for creating embedded documents. A parent update that adds entries
176
179
  without `_id`, such as new `behaviors` in `scene.region.update`, can create them but return an
@@ -190,6 +193,12 @@ A timeout or disconnect can happen after a write commits. An update error with `
190
193
  or `details.indeterminate` can also mean that some changes persisted. Read affected state before
191
194
  retrying and send only what remains to be done.
192
195
 
196
+ An update whose stored state differs from the request fails with `INTERNAL_ERROR`, and
197
+ `details.paths` lists the differing paths with the requested and the stored value. Foundry core, the
198
+ game system or a module may have changed the value; Foundry stores a raw `&` in rich text as
199
+ `&`, for example. The bridge cannot tell which, so the caller judges from the values. Sending
200
+ the same patch again stores the same result.
201
+
193
202
  An idempotency key identifies one logical request and can prevent duplicate effects while its
194
203
  cached state survives. Reuse it only for the same request; a corrected or remaining patch is a
195
204
  new operation and needs a fresh key. Follow the [delivery rules](protocol.md#delivery-states-and-retries)
@@ -268,7 +277,7 @@ For scripts and JSON output, pass `--yes` to skip the listing and prompt.
268
277
  ## Local configuration
269
278
 
270
279
  `config get` shows the config path and non-secret settings. `config set-upload-limit <size>`
271
- changes the raw upload-byte limit; restart the daemon for it to take effect.
280
+ changes the raw byte limit for uploads and file reads; it takes effect after a daemon restart.
272
281
 
273
282
  If set, `XDG_CONFIG_HOME` places configuration under `$XDG_CONFIG_HOME/fvtt-world-cli`. Otherwise:
274
283
 
@@ -11,6 +11,8 @@ The bridge supports Foundry VTT v13 and v14. Check your connected version with
11
11
  | Scene thumbnail rendering | Renders the scene | Renders the scene's initial level by default | On v14, expect the initial level in the thumbnail. Check the returned dimensions and image. |
12
12
  | Scene thumbnail files | Uses a stable scene-based filename | Uses a filename based on the image content | Use the returned path. On v14, a changed image can leave an older thumbnail file behind. |
13
13
  | User permission names | `TEMPLATE_CREATE` | `REGION_CREATE` | Use the permission keys reported by the connected Foundry version when calling `user.permissions.set`. |
14
+ | Relative content links | `@UUID[..<id>]` points at nothing | `@UUID[..<id>]` names a document one parent up | `world.references` reports a `..` link only on v14. |
15
+ | Legacy update operators `-=key` (deletion) and `==key` (forced replacement) | Work without a warning | Work, and the GM browser console logs a deprecation warning for each such key, stating that support will be removed in Version 16 | Nothing changes for v13 or v14 writes. The bridge passes the operator keys through without translating them to Foundry 14's operator objects, so they will stop working once Foundry removes them. |
14
16
 
15
17
  Scene placeable fields and available region behavior types also differ. Read the target document
16
18
  before preparing a patch; valid data from one version is not automatically valid on the other.
package/docs/protocol.md CHANGED
@@ -27,12 +27,13 @@ a correlated control-request error.
27
27
 
28
28
  ## Size limits
29
29
 
30
- The upload limit and the transport frame limit are distinct: the first bounds raw upload content,
31
- the second accounts for encoding and envelope overhead and is never lower than what legitimate
32
- large read responses require. The daemon advertises its effective limits during the handshake, and
33
- the bridge checks response size before sending, so an oversized response returns
34
- `PAYLOAD_TOO_LARGE` instead of destroying the shared session. A persisted upload-limit change is
35
- applied to the transport only after the daemon restarts.
30
+ The upload limit and the transport frame limit are distinct: the first bounds raw upload content
31
+ and the raw size of a `file.read`, the second accounts for encoding and envelope overhead and is
32
+ never lower than what legitimate large read responses require. The daemon advertises its effective
33
+ limits during the handshake, and the bridge checks response size before sending, so an oversized
34
+ response, or one the browser cannot serialize, returns `PAYLOAD_TOO_LARGE` instead of destroying the
35
+ shared session. A persisted upload-limit change is applied to the transport only after the daemon
36
+ restarts.
36
37
 
37
38
  ## Pairing
38
39
 
@@ -181,9 +182,17 @@ filters apply before pagination. Previewed documents have no persistent identity
181
182
  observed during a preview must not be reused. Some result fields depend on the connected Foundry
182
183
  version; [Foundry compatibility](compatibility.md) lists them.
183
184
 
185
+ World document reads carry `compendiumSource` and `duplicateSource`, hoisted from Foundry's
186
+ `_stats` and `null` when absent.
187
+
184
188
  Broadcast actions report `dispatched` and target users rather than a confirmed player-side result.
185
189
  The active/inactive split is included where knowable.
186
190
 
191
+ A setting value or macro return value node with no JSON form, such as a function, an own accessor
192
+ property or a circular reference, is replaced by `{"$unserializable": "<reason>"}` and listed with
193
+ its path in an `...Omissions` field. A value with omissions is incomplete; writing it back stores the
194
+ markers as data.
195
+
187
196
  ### Macro results
188
197
 
189
198
  `macro.execute` returns the macro's value and observed chat messages. `chatCapture` describes the
@@ -196,10 +205,12 @@ world state when the return value does not establish the effect.
196
205
 
197
206
  ### Setting results
198
207
 
199
- Setting writes return the observed `value` and its `previous` value. Registered types or callbacks
200
- may normalize the input. Confirmation establishes that the previous value changed, not that the
201
- requested value was stored exactly. Writing an already-stored value succeeds as unchanged without
202
- calling Foundry. `requiresReload: true` indicates that the GM client needs a reload.
208
+ Setting writes return the observed `value` and its `previous` value, as `game.settings.get`
209
+ returns them after the registered type's cast. Confirmation compares the JSON Foundry stores with
210
+ the JSON of the validated request; a different stored value, for example one rewritten by an
211
+ `onChange` callback, is a mismatch error with a `paths` entry for `value`. Writing an
212
+ already-stored value succeeds as unchanged without calling Foundry. `requiresReload: true`
213
+ indicates that the GM client needs a reload.
203
214
 
204
215
  ## Write confirmation
205
216
 
@@ -207,11 +218,24 @@ Document writes report success only after confirmation. Foundry can resolve a ve
207
218
  write without throwing, and hooks can remove or rewrite part of a patch. Single and bulk updates
208
219
  pass Foundry a private copy, then compare stored state with the original request.
209
220
 
221
+ Confirmation compares each requested leaf path that survives Foundry's cleaning with the stored
222
+ source. Foundry drops a key the document's schema does not declare without an error before that
223
+ comparison, so a write made only of undeclared keys reports success with nothing stored. A deletion (`-=key` or
224
+ Foundry 14's `ForcedDeletion`) is confirmed when the key is absent. A forced replacement (`==key`
225
+ or Foundry 14's `ForcedReplacement`) is confirmed when the whole stored value at that path equals the requested
226
+ value after Foundry's cleaning.
227
+
210
228
  A valid patch whose requested state is already stored remains a successful no-op. A validation
211
229
  failure recovered after an unwritten update returns `INVALID_PARAMS`. Unconfirmed, partial, or
212
230
  indeterminate updates can return `INTERNAL_ERROR` with these details:
213
231
 
214
- - `fields`: requested fields whose state was not confirmed.
232
+ - `fields`: requested top-level fields whose state was not confirmed.
233
+ - `paths`: up to 20 entries `{ path, requested, stored }`, one per leaf path where the stored value
234
+ differs from the request. `absent: "stored"` replaces `stored` when the key is missing from the
235
+ stored source, and `absent: "requested"` replaces `requested` for a deletion that did not land.
236
+ A long string, or an object or array whose JSON is long, is shortened to a window around the
237
+ first differing character; `truncated` then carries `format` (`text` or `json`), `offset`, and
238
+ the full `requestedLength` and `storedLength`. `pathsTruncated` counts the entries beyond 20.
215
239
  - `partial: true`: stored data changed in a requested field, but the full requested state was
216
240
  not reached. A hook may have applied only part of a nested object or replaced a value.
217
241
  - `changedFields`: requested top-level fields observed to change, including fields that changed
@@ -221,6 +245,12 @@ indeterminate updates can return `INTERNAL_ERROR` with these details:
221
245
  - `indeterminate: true`: confirmation could not establish the outcome. Some or all of the write
222
246
  may have persisted. `changedFields` is `null` when the before/after comparison was unavailable.
223
247
 
248
+ When stored differs from requested, the message says that the bridge cannot verify whether Foundry
249
+ core, the game system or a module normalized the value, or whether a hook rewrote, dropped or vetoed
250
+ the change. A blocking module is listed as one possible cause, never as the diagnosis. Resending the
251
+ same patch does not change the result. Every confirmed write that can report a mismatch carries the
252
+ same `paths`, and so does a `dropped` bulk outcome when the stored state was compared.
253
+
224
254
  Parent patches that create embedded entries without `_id`, such as new `behaviors` in
225
255
  `scene.region.update`, return an indeterminate error after writing because parent update results
226
256
  cannot confirm those creations. A retry may duplicate them. Read the embedded collection first,
@@ -231,10 +261,10 @@ new operation with a fresh idempotency key if using one.
231
261
 
232
262
  ### Patch shape checks
233
263
 
234
- Single updates, bulk updates, and dry runs reject ambiguous field spellings, invalid or discarded
235
- array values, and dotted writes inside arrays with `INVALID_PARAMS` before mutation. Recognized
236
- legacy fields are checked against their migrated destinations as well. Send whole arrays; ordinary
237
- dotted object-property writes remain supported where the schema permits them.
264
+ Single updates, bulk updates, and dry runs reject invalid or discarded array values, top-level
265
+ dotted writes into array fields, and writes to legacy fields the running core moved, with
266
+ `INVALID_PARAMS` before mutation. Send whole arrays; ordinary dotted object-property writes remain
267
+ supported where the schema permits them. Other key spellings reach Foundry as written.
238
268
 
239
269
  Errors identify the `field` and may include `arrayField`, `requested`, `stored`, or migration
240
270
  destination details. Bulk failures add the entry's `index` and `id`; single failures use document
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fvtt-world-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Safe local CLI and authenticated bridge for automating live Foundry VTT worlds.",
5
5
  "keywords": [
6
6
  "foundry-vtt",
@@ -56,6 +56,6 @@
56
56
  "zod": "^3.24.3"
57
57
  },
58
58
  "devDependencies": {
59
- "@fvtt-world-cli/protocol": "1.2.0"
59
+ "@fvtt-world-cli/protocol": "1.3.0"
60
60
  }
61
61
  }
@@ -19,10 +19,11 @@ fvtt-world-cli system info --json
19
19
  ```
20
20
 
21
21
  Proceed only when `.result.bridge.status` is `connected`, and verify the reported world and GM are
22
- the intended targets. If the daemon is not running, start `fvtt-world-cli bridge serve` as a
23
- persistent background process (it does not daemonize itself) and leave it running for the whole
24
- session. If no bridge connects, a GM must have the target world open in a browser with the
25
- `fvtt-world-cli` module enabled and paired.
22
+ the intended targets. If `system info` answers at all, a daemon is running; use it. Start
23
+ `fvtt-world-cli bridge serve` only when the daemon is unreachable, as a persistent background
24
+ process (it does not daemonize itself), and leave it running for the whole session. If no bridge
25
+ connects, a GM must have the target world open in a browser with the `fvtt-world-cli` module
26
+ enabled and paired.
26
27
 
27
28
  Pairing is a first-run trust decision that needs the human: ask the GM to open Module Settings →
28
29
  Authorization, name the browser in the `Browser label` field, and choose Pair; run
@@ -80,6 +81,12 @@ level ids in a preview are provisional.
80
81
  Deleting a level also deletes its tokens and every placeable only on that level, and the dry run
81
82
  does not list them: read the scene's placeables (token `level`, others' `levels`) and tell the user
82
83
  what the delete removes before running it.
84
+ An unlinked token keeps its own overrides, which edits to its world actor never reach. Before
85
+ editing an actor that has placed tokens, read its tokens' overrides
86
+ (`scene token get --include delta,prepared`) and tell the user which tokens the edit will not
87
+ change.
88
+ Before deleting or relinking a document, run `world references --uuid <uuid>` and tell the user
89
+ which stored references will point at nothing.
83
90
  `setting.set` cannot touch this module's own namespace (`SETTING_PROTECTED` is final; never retry).
84
91
  When a setting write returns `requiresReload: true`, the change needs a GM-client reload to take
85
92
  effect; `system.reload` performs it, drops the bridge, and requires a reconnect wait before the
@@ -92,12 +99,13 @@ signal, not a failure — reconnect and continue rather than retrying the reload
92
99
  Discover → inspect schema → locate → read → smallest patch → dry-run → commit → verify. Never
93
100
  guess a command name or parameter.
94
101
 
95
- 1. Run `fvtt-world-cli commands --json`. Continue only when `policy.applied` is `true` and the
96
- required command is present. A `false` value means no bridge answered and the output is the static
97
- registry, not the client's permissions. Report an absent command instead of seeking another way
98
- to produce the same effect. Warn the user before a command marked `"approval": true`, and prefer
99
- one `*-many` command when it can replace several approvals. Dotted protocol names map to spaced
100
- CLI subcommands, such as `actor.item.update` to `actor item update`.
102
+ 1. Run `fvtt-world-cli commands --json`. Continue only when `policy.applied` (top level, beside
103
+ `result`) is `true` and the required command is present. A `false` value means no bridge
104
+ answered and the output is the static registry, not the client's permissions. Report an absent
105
+ command instead of seeking another way to produce the same effect. Warn the user before a
106
+ command marked `"approval": true`, and prefer one `*-many` command when it can replace several
107
+ approvals. Dotted protocol names map to spaced CLI subcommands, such as `actor.item.update` to
108
+ `actor item update`.
101
109
  2. `fvtt-world-cli schema <command>` shows the exact request parameters, required fields, enums,
102
110
  and whether unknown fields are accepted; `fvtt-world-cli <command path> --help` maps flags.
103
111
  3. Locate targets with the narrowest query: a family `list --name <substring> --limit <n>` when
@@ -106,8 +114,9 @@ guess a command name or parameter.
106
114
  never the basis of a write.
107
115
  4. `get` the complete target before mutating and work from the returned ids.
108
116
  5. Build the smallest valid patch. Nested objects merge recursively; plain arrays replace
109
- wholesale; dotted paths reach permitted leaves; deletion operators remove permitted keys. A
110
- `get` result contains read-only and derived fields, so build the patch from accepted fields only.
117
+ wholesale; dotted paths reach permitted leaves; `{"-=key": null}` deletes a permitted key, and
118
+ `{"==key": value}` replaces its whole value instead of merging. A `get` result contains
119
+ read-only and derived fields, so build the patch from accepted fields only.
111
120
  For arrays and extensible `system`/`flags` data: read, preserve everything that
112
121
  stays, change only the intended part.
113
122
  6. Dry-run every nontrivial mutation with the global `--dry-run` flag. It runs the same
@@ -171,6 +180,10 @@ Classify a failure before reacting:
171
180
  observed to change, not fully applied nested values. An embedded creation without a requested
172
181
  id may already exist despite an indeterminate error. Send only the remaining changes as a new
173
182
  operation with a fresh idempotency key, if using one.
183
+ - An update `INTERNAL_ERROR` with `details.paths` is a stored-vs-requested mismatch. Compare
184
+ `requested` and `stored` for each path. A representation-only difference, such as HTML escaping (`&` stored as `&amp;`) or
185
+ whitespace, can be accepted as done. Report any other difference to the user. The same patch
186
+ stores the same result, so the next request is either a different patch or none.
174
187
  - Correct a structured Foundry rejection and submit the corrected content as a new operation.
175
188
 
176
189
  ## Bulk writes and actions
@@ -1 +1 @@
1
- 248c52464e776236de6f937530e220dcb77b45a8b5cc754adbb4cfb4671a8c66
1
+ 07f46d03812a74d1643f7483eb3eb926244dd1837b7afb11280c3072bfa3d5bd