fvtt-world-cli 1.1.2 → 1.2.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/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
 
@@ -29,6 +30,7 @@ Use these families to narrow your search, then check their help for supported op
29
30
  | Edit world content | `actor`, `item`, `journal`, `scene`, `macro`, `playlist`, `table`, `cards` |
30
31
  | Edit embedded content | Nested families such as `actor.item`, `playlist.sound`, `table.result`, and `*.effect` |
31
32
  | Edit scene placeables | `scene.token`, `scene.wall`, `scene.region`, and related families |
33
+ | Edit scene levels (v14) | `scene.level` |
32
34
  | Manage encounters | `combat`, `combat.combatant`, `combat.group` |
33
35
  | Organize content and access | `folder`, `*.ownership.set`, `user` |
34
36
  | Manage chat and settings | `chat`, `setting` |
@@ -63,6 +65,29 @@ fvtt-world-cli scene token list --scene-id <sceneId> --json
63
65
  fvtt-world-cli scene token get --scene-id <sceneId> --token-id <tokenId> --json
64
66
  ```
65
67
 
68
+ ### Build a scene with levels
69
+
70
+ On Foundry v14 a scene's maps live on its levels. Pass the levels when you create the scene:
71
+
72
+ ```bash
73
+ fvtt-world-cli scene create --name "Tower" --json \
74
+ --data-json '{"levels":[{"name":"Ground","background":{"src":"worlds/<worldId>/maps/ground.webp"}},
75
+ {"name":"Roof","elevation":{"bottom":20,"top":40}}]}'
76
+ fvtt-world-cli scene level list --scene-id <sceneId> --json
77
+ fvtt-world-cli scene level update --scene-id <sceneId> --level-id <levelId> \
78
+ --patch-json '{"foreground":{"src":"worlds/<worldId>/maps/roof.webp"}}' --json
79
+ ```
80
+
81
+ The first supplied level becomes the initial one. To start players on another level, run `scene
82
+ update --scene-id <sceneId> --initial-level <levelId>`; `--patch-json '{"initialLevel":null}'` goes
83
+ back to the first level. A scene keeps at least one level, so `scene level delete` and `delete-many`
84
+ refuse to remove the last one.
85
+
86
+ Deleting a level also deletes its tokens and every placeable that exists only on that level. A dry
87
+ run does not list them, so read the scene's placeables before deleting a level.
88
+
89
+ [Compatibility](compatibility.md) lists the scene map fields each Foundry version accepts.
90
+
66
91
  ### Send a batch of commands
67
92
 
68
93
  Save these lines as `requests.ndjson`:
@@ -131,19 +156,24 @@ List rows contain fewer fields than `get` results. Read the target with `get` be
131
156
  update. Reads return authored state; some commands accept `include` options for derived or expensive
132
157
  data. Check the command's help for available projections and filters.
133
158
 
159
+ Actor results carry `itemCount` in place of the owned items; `actor get` and `actor get-many`
160
+ return the items with `--include items`. `system info --include modules` lists installed modules.
161
+
134
162
  ### Updates and merge semantics
135
163
 
136
164
  Use JSON flags such as `--data-json` and `--patch-json` for structured values. Updates are patches:
137
165
 
138
166
  - Nested objects merge recursively.
139
167
  - 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.
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.
142
172
  - Embedded collections follow their family's Foundry semantics.
143
173
 
144
174
  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.
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.
147
177
 
148
178
  Prefer dedicated commands for creating embedded documents. A parent update that adds entries
149
179
  without `_id`, such as new `behaviors` in `scene.region.update`, can create them but return an
@@ -163,6 +193,12 @@ A timeout or disconnect can happen after a write commits. An update error with `
163
193
  or `details.indeterminate` can also mean that some changes persisted. Read affected state before
164
194
  retrying and send only what remains to be done.
165
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
+ `&amp;`, 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
+
166
202
  An idempotency key identifies one logical request and can prevent duplicate effects while its
167
203
  cached state survives. Reuse it only for the same request; a corrected or remaining patch is a
168
204
  new operation and needs a fresh key. Follow the [delivery rules](protocol.md#delivery-states-and-retries)
@@ -207,7 +243,7 @@ inspect `auth pending`, then use `auth approve <code> --yes` for the selected re
207
243
 
208
244
  | Command | Effect |
209
245
  |---|---|
210
- | `auth status` | Show bridge state and public profile metadata |
246
+ | `auth status` | Show bridge state, the commands the connected bridge advertised, and public profile metadata |
211
247
  | `auth pending` | List pending codes, expiry, and browser/world/GM identity |
212
248
  | `auth approve [code] [--yes]` | Approve a selected request; omit the code only when exactly one is pending |
213
249
  | `auth deny <code>` | Reject a pending request |
@@ -6,9 +6,12 @@ The bridge supports Foundry VTT v13 and v14. Check your connected version with
6
6
  | Area | v13 | v14 | What to do |
7
7
  |---|---|---|---|
8
8
  | Measured templates | Available | Removed from core | Use `scene.template` commands on v13. They are unavailable on v14. |
9
+ | Scene maps | One map per scene: `background`, `backgroundColor`, `foreground`, `foregroundElevation` and `fog.overlay` on the scene | Each level has its own map; the scene has `levels` and `initialLevel` | On v14, set maps through `levels` in `scene.create` or with `scene.level` commands. The scene-level map fields, including `fog.overlay`, are refused on v14, and `scene.level`, `levels` and `initialLevel` are refused on v13. |
10
+ | Placeable levels | Placeables have no level fields | A token sits on one `level`; tiles, walls, lights, sounds, notes, drawings and regions have a `levels` list, where an empty list means every level | On v14, read `level` or `levels` from placeable results to see what a level holds before deleting it. Placeable writes that set `level` or `levels` are refused on v13. |
9
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. |
10
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. |
11
13
  | User permission names | `TEMPLATE_CREATE` | `REGION_CREATE` | Use the permission keys reported by the connected Foundry version when calling `user.permissions.set`. |
14
+ | 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. |
12
15
 
13
16
  Scene placeable fields and available region behavior types also differ. Read the target document
14
17
  before preparing a patch; valid data from one version is not automatically valid on the other.
package/docs/protocol.md CHANGED
@@ -178,7 +178,11 @@ The protocol package exports `REQUEST_SCHEMA` and `COMMAND_RESPONSE_SCHEMA` for
178
178
  Document results use type-named keys and expose `id` as the public identifier. A source `_id`
179
179
  mirror may accompany it. List responses use smaller projections than single-document reads;
180
180
  filters apply before pagination. Previewed documents have no persistent identity, so an id
181
- observed during a preview must not be reused.
181
+ observed during a preview must not be reused. Some result fields depend on the connected Foundry
182
+ version; [Foundry compatibility](compatibility.md) lists them.
183
+
184
+ World document reads carry `compendiumSource` and `duplicateSource`, hoisted from Foundry's
185
+ `_stats` and `null` when absent.
182
186
 
183
187
  Broadcast actions report `dispatched` and target users rather than a confirmed player-side result.
184
188
  The active/inactive split is included where knowable.
@@ -195,10 +199,12 @@ world state when the return value does not establish the effect.
195
199
 
196
200
  ### Setting results
197
201
 
198
- Setting writes return the observed `value` and its `previous` value. Registered types or callbacks
199
- may normalize the input. Confirmation establishes that the previous value changed, not that the
200
- requested value was stored exactly. Writing an already-stored value succeeds as unchanged without
201
- calling Foundry. `requiresReload: true` indicates that the GM client needs a reload.
202
+ Setting writes return the observed `value` and its `previous` value, as `game.settings.get`
203
+ returns them after the registered type's cast. Confirmation compares the JSON Foundry stores with
204
+ the JSON of the validated request; a different stored value, for example one rewritten by an
205
+ `onChange` callback, is a mismatch error with a `paths` entry for `value`. Writing an
206
+ already-stored value succeeds as unchanged without calling Foundry. `requiresReload: true`
207
+ indicates that the GM client needs a reload.
202
208
 
203
209
  ## Write confirmation
204
210
 
@@ -206,11 +212,24 @@ Document writes report success only after confirmation. Foundry can resolve a ve
206
212
  write without throwing, and hooks can remove or rewrite part of a patch. Single and bulk updates
207
213
  pass Foundry a private copy, then compare stored state with the original request.
208
214
 
215
+ Confirmation compares each requested leaf path that survives Foundry's cleaning with the stored
216
+ source. Foundry drops a key the document's schema does not declare without an error before that
217
+ comparison, so a write made only of undeclared keys reports success with nothing stored. A deletion (`-=key` or
218
+ Foundry 14's `ForcedDeletion`) is confirmed when the key is absent. A forced replacement (`==key`
219
+ or Foundry 14's `ForcedReplacement`) is confirmed when the whole stored value at that path equals the requested
220
+ value after Foundry's cleaning.
221
+
209
222
  A valid patch whose requested state is already stored remains a successful no-op. A validation
210
223
  failure recovered after an unwritten update returns `INVALID_PARAMS`. Unconfirmed, partial, or
211
224
  indeterminate updates can return `INTERNAL_ERROR` with these details:
212
225
 
213
- - `fields`: requested fields whose state was not confirmed.
226
+ - `fields`: requested top-level fields whose state was not confirmed.
227
+ - `paths`: up to 20 entries `{ path, requested, stored }`, one per leaf path where the stored value
228
+ differs from the request. `absent: "stored"` replaces `stored` when the key is missing from the
229
+ stored source, and `absent: "requested"` replaces `requested` for a deletion that did not land.
230
+ A long string, or an object or array whose JSON is long, is shortened to a window around the
231
+ first differing character; `truncated` then carries `format` (`text` or `json`), `offset`, and
232
+ the full `requestedLength` and `storedLength`. `pathsTruncated` counts the entries beyond 20.
214
233
  - `partial: true`: stored data changed in a requested field, but the full requested state was
215
234
  not reached. A hook may have applied only part of a nested object or replaced a value.
216
235
  - `changedFields`: requested top-level fields observed to change, including fields that changed
@@ -220,6 +239,12 @@ indeterminate updates can return `INTERNAL_ERROR` with these details:
220
239
  - `indeterminate: true`: confirmation could not establish the outcome. Some or all of the write
221
240
  may have persisted. `changedFields` is `null` when the before/after comparison was unavailable.
222
241
 
242
+ When stored differs from requested, the message says that the bridge cannot verify whether Foundry
243
+ core, the game system or a module normalized the value, or whether a hook rewrote, dropped or vetoed
244
+ the change. A blocking module is listed as one possible cause, never as the diagnosis. Resending the
245
+ same patch does not change the result. Every confirmed write that can report a mismatch carries the
246
+ same `paths`, and so does a `dropped` bulk outcome when the stored state was compared.
247
+
223
248
  Parent patches that create embedded entries without `_id`, such as new `behaviors` in
224
249
  `scene.region.update`, return an indeterminate error after writing because parent update results
225
250
  cannot confirm those creations. A retry may duplicate them. Read the embedded collection first,
@@ -230,10 +255,10 @@ new operation with a fresh idempotency key if using one.
230
255
 
231
256
  ### Patch shape checks
232
257
 
233
- Single updates, bulk updates, and dry runs reject ambiguous field spellings, invalid or discarded
234
- array values, and dotted writes inside arrays with `INVALID_PARAMS` before mutation. Recognized
235
- legacy fields are checked against their migrated destinations as well. Send whole arrays; ordinary
236
- dotted object-property writes remain supported where the schema permits them.
258
+ Single updates, bulk updates, and dry runs reject invalid or discarded array values, top-level
259
+ dotted writes into array fields, and writes to legacy fields the running core moved, with
260
+ `INVALID_PARAMS` before mutation. Send whole arrays; ordinary dotted object-property writes remain
261
+ supported where the schema permits them. Other key spellings reach Foundry as written.
237
262
 
238
263
  Errors identify the `field` and may include `arrayField`, `requested`, `stored`, or migration
239
264
  destination details. Bulk failures add the entry's `index` and `id`; single failures use document
@@ -341,8 +366,8 @@ The wait has two phases because the decision can outlast a normal request timeou
341
366
  policy can change before dispatch.
342
367
 
343
368
  The module supplies each opaque `approvalId`; callers do not construct one. Approval request schemas
344
- are closed. `approval.await`, `approval.cancel`, and `policy.snapshot` do not appear in `commands`,
345
- `system.info` command inventory, or bridge status. `schema <command>` still returns their schemas.
369
+ are closed. `approval.await`, `approval.cancel`, and `policy.snapshot` do not appear in `commands` or
370
+ bridge status. `schema <command>` still returns their schemas.
346
371
  The bridge handshake advertises them because the daemon must forward them.
347
372
 
348
373
  ## Delivery states and retries
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fvtt-world-cli",
3
- "version": "1.1.2",
3
+ "version": "1.2.1",
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.1.2"
59
+ "@fvtt-world-cli/protocol": "1.2.1"
60
60
  }
61
61
  }
@@ -75,6 +75,14 @@ A macro that throws fails `macro.execute` with a partial outcome — read what i
75
75
  retrying — while a macro that catches its own errors still returns `null`, which proves nothing;
76
76
  verify effects with reads, and treat `MACRO_TIMEOUT` as indeterminate — the macro may still be
77
77
  running.
78
+ On Foundry v14 maps live on levels: pass `levels` in `scene.create`, then use `scene.level.*`;
79
+ level ids in a preview are provisional.
80
+ Deleting a level also deletes its tokens and every placeable only on that level, and the dry run
81
+ does not list them: read the scene's placeables (token `level`, others' `levels`) and tell the user
82
+ what the delete removes before running it.
83
+ An unlinked token keeps its own overrides, which edits to its world actor never reach. Before
84
+ editing an actor that has placed tokens, read its tokens' overrides and tell the user which tokens
85
+ the edit will not change.
78
86
  `setting.set` cannot touch this module's own namespace (`SETTING_PROTECTED` is final; never retry).
79
87
  When a setting write returns `requiresReload: true`, the change needs a GM-client reload to take
80
88
  effect; `system.reload` performs it, drops the bridge, and requires a reconnect wait before the
@@ -87,12 +95,13 @@ signal, not a failure — reconnect and continue rather than retrying the reload
87
95
  Discover → inspect schema → locate → read → smallest patch → dry-run → commit → verify. Never
88
96
  guess a command name or parameter.
89
97
 
90
- 1. Run `fvtt-world-cli commands --json`. Continue only when `policy.applied` is `true` and the
91
- required command is present. A `false` value means no bridge answered and the output is the static
92
- registry, not the client's permissions. Report an absent command instead of seeking another way
93
- to produce the same effect. Warn the user before a command marked `"approval": true`, and prefer
94
- one `*-many` command when it can replace several approvals. Dotted protocol names map to spaced
95
- CLI subcommands, such as `actor.item.update` to `actor item update`.
98
+ 1. Run `fvtt-world-cli commands --json`. Continue only when `policy.applied` (top level, beside
99
+ `result`) is `true` and the required command is present. A `false` value means no bridge
100
+ answered and the output is the static registry, not the client's permissions. Report an absent
101
+ command instead of seeking another way to produce the same effect. Warn the user before a
102
+ command marked `"approval": true`, and prefer one `*-many` command when it can replace several
103
+ approvals. Dotted protocol names map to spaced CLI subcommands, such as `actor.item.update` to
104
+ `actor item update`.
96
105
  2. `fvtt-world-cli schema <command>` shows the exact request parameters, required fields, enums,
97
106
  and whether unknown fields are accepted; `fvtt-world-cli <command path> --help` maps flags.
98
107
  3. Locate targets with the narrowest query: a family `list --name <substring> --limit <n>` when
@@ -101,8 +110,9 @@ guess a command name or parameter.
101
110
  never the basis of a write.
102
111
  4. `get` the complete target before mutating and work from the returned ids.
103
112
  5. Build the smallest valid patch. Nested objects merge recursively; plain arrays replace
104
- wholesale; dotted paths reach permitted leaves; deletion operators remove permitted keys. A
105
- `get` result contains read-only and derived fields, so build the patch from accepted fields only.
113
+ wholesale; dotted paths reach permitted leaves; `{"-=key": null}` deletes a permitted key, and
114
+ `{"==key": value}` replaces its whole value instead of merging. A `get` result contains
115
+ read-only and derived fields, so build the patch from accepted fields only.
106
116
  For arrays and extensible `system`/`flags` data: read, preserve everything that
107
117
  stays, change only the intended part.
108
118
  6. Dry-run every nontrivial mutation with the global `--dry-run` flag. It runs the same
@@ -135,7 +145,8 @@ details}}`, alongside `protocolVersion`, `type`, and the correlation `id` (echoi
135
145
  Documents live under a type-named key inside `result` that varies between commands but is stable
136
146
  for each one. `id` is the public identifier; a `_id` mirror may accompany it. Values requested
137
147
  through `include` flags may be derived or version-dependent. Write them back only when the
138
- update schema explicitly accepts them.
148
+ update schema explicitly accepts them. Actor reads return `itemCount` in place of owned items; add
149
+ `--include items` when the task needs them, or use `actor item list` and `actor item get`.
139
150
 
140
151
  ## Retry classification
141
152
 
@@ -165,6 +176,10 @@ Classify a failure before reacting:
165
176
  observed to change, not fully applied nested values. An embedded creation without a requested
166
177
  id may already exist despite an indeterminate error. Send only the remaining changes as a new
167
178
  operation with a fresh idempotency key, if using one.
179
+ - An update `INTERNAL_ERROR` with `details.paths` is a stored-vs-requested mismatch. Compare
180
+ `requested` and `stored` for each path. A representation-only difference, such as HTML escaping (`&` stored as `&amp;`) or
181
+ whitespace, can be accepted as done. Report any other difference to the user. The same patch
182
+ stores the same result, so the next request is either a different patch or none.
168
183
  - Correct a structured Foundry rejection and submit the corrected content as a new operation.
169
184
 
170
185
  ## Bulk writes and actions
@@ -1 +1 @@
1
- d1757739e55e01589814ed919912bbe5423afa99aee16a1422fbaee5bed11c8a
1
+ 5b4188ba39ed8f9f1c041cec9c5f0c3fb5d05db7910670d0ff2bb9c5d7d43dde