fvtt-world-cli 1.0.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/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Documentation
2
+
3
+ The canonical command is `fvtt-world-cli`. The shorter `worldctl` executable is an equivalent bin
4
+ entry for the same CLI and may be used in interactive workflows.
5
+
6
+ fvtt-world-cli exposes a local, authenticated command line for inspecting and changing a live
7
+ Foundry world through Foundry's own APIs.
8
+
9
+ Authorization uses one-time pairing; the first-run flow is covered in
10
+ [Getting started](getting-started.md). Config defaults are `$XDG_CONFIG_HOME/fvtt-world-cli`
11
+ or `~/.config/fvtt-world-cli` on Linux, `~/Library/Application Support/fvtt-world-cli` on macOS, and
12
+ `%APPDATA%\\fvtt-world-cli` on Windows.
13
+
14
+ ## Choose a route
15
+
16
+ If you are operating the CLI yourself, start with [Commands](commands.md). It explains what the tool
17
+ can do, groups related operations by task, and links to the detailed contracts that matter for each
18
+ workflow.
19
+
20
+ If an AI agent or another program is operating the CLI, install the packaged agent skill into the
21
+ agent with `fvtt-world-cli skill install`. The skill carries the operating workflow and defers
22
+ command specifics to the CLI's own discovery surface, so agents never rely on a copied command
23
+ inventory in prose; [Agent skill](skill.md) explains why it exists and how it is kept current.
24
+
25
+ ## Documentation map
26
+
27
+ | Document | Audience | Purpose |
28
+ |---|---|---|
29
+ | [Getting started](getting-started.md) | People | First-run daemon, pairing, and bridge walkthrough |
30
+ | [Commands](commands.md) | People | Capabilities, common workflows, shared command behavior, and navigation by task |
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 | Transport, session, delivery, and error semantics — the integration contract |
33
+ | [Architecture](architecture.md) | Contributors | Component responsibilities and request flow |
34
+ | [Security](security.md) | Operators and contributors | Trust boundaries, permissions, managed files, and known risks |
35
+ | [Foundry compatibility](compatibility.md) | Operators and contributors | Current differences between supported Foundry major versions |
36
+ | [Changelog](../CHANGELOG.md) | Users and contributors | Versioned history of user-visible changes |
37
+
38
+ ## Finding exact command syntax
39
+
40
+ The running CLI is the authoritative reference for the installed version:
41
+
42
+ ```bash
43
+ fvtt-world-cli commands
44
+ fvtt-world-cli commands --json
45
+ fvtt-world-cli schema actor.update
46
+ fvtt-world-cli actor update --help
47
+ fvtt-world-cli docs protocol
48
+ ```
49
+
50
+ `commands --json` enumerates the command registry and identifies mutations. `schema` returns the
51
+ request schema used by local validation. Command help maps protocol parameters to CLI flags. `docs`
52
+ lists and prints these documentation files as shipped with the installed CLI.
53
+
54
+ ## Sources of truth
55
+
56
+ Documentation explains the public behavior but does not duplicate exhaustive machine-readable
57
+ inventories.
58
+
59
+ - Command names, mutation classification, and request schemas, per family:
60
+ [`packages/protocol/src/schemas/`](../packages/protocol/src/schemas/), assembled into the registry
61
+ by [`packages/protocol/src/commands.js`](../packages/protocol/src/commands.js)
62
+ - Shared constants and stable error codes:
63
+ [`packages/protocol/src/constants.js`](../packages/protocol/src/constants.js)
64
+ - Global flags, the JSON output contract printed in `--help`, and program assembly:
65
+ [`packages/cli/src/program.ts`](../packages/cli/src/program.ts)
66
+ - Per-command flags and command registration, per group:
67
+ [`packages/cli/src/commands/`](../packages/cli/src/commands/)
68
+ - Human-readable rendering of command results, per family:
69
+ [`packages/cli/src/render/`](../packages/cli/src/render/)
70
+ - Foundry-side behavior: [`packages/foundry-module/scripts/`](../packages/foundry-module/scripts/)
71
+ - Live coverage: [`scripts/live-smoke.mjs`](../scripts/live-smoke.mjs)
@@ -0,0 +1,216 @@
1
+ # Architecture
2
+
3
+ fvtt-world-cli is a monorepo containing a command-line client, a local daemon, a shared protocol,
4
+ and a Foundry module. The components share contracts but have distinct runtime responsibilities.
5
+
6
+ ## Runtime roles
7
+
8
+ ### CLI
9
+
10
+ `packages/cli` owns:
11
+
12
+ - command parsing and help;
13
+ - local configuration;
14
+ - local request-schema validation;
15
+ - WebSocket client transport;
16
+ - JSON and human-readable output;
17
+ - local discovery commands;
18
+ - reading explicitly supplied operator files for upload or macro input.
19
+
20
+ The CLI does not load Foundry or mutate world storage. It converts CLI flags into typed protocol
21
+ requests and presents structured responses.
22
+
23
+ ### Daemon
24
+
25
+ The daemon runs as part of the CLI package and owns:
26
+
27
+ - the loopback WebSocket listener;
28
+ - device-local client authentication and the persistent pairing registry;
29
+ - the single active bridge session;
30
+ - request correlation and forwarding;
31
+ - forward timeouts and heartbeat state;
32
+ - transport size limits;
33
+ - runtime idempotency coordination and caching.
34
+
35
+ The daemon does not interpret Foundry document payloads or provide world access without an active
36
+ authenticated bridge.
37
+
38
+ ### Protocol package
39
+
40
+ `packages/protocol` owns:
41
+
42
+ - protocol and message constants;
43
+ - command names and mutation classification;
44
+ - request schemas;
45
+ - stable error codes;
46
+ - shared limits and enums;
47
+ - envelope validation.
48
+
49
+ The protocol registry is the source for runtime command discovery. Exhaustive command inventories are
50
+ not copied into documentation.
51
+
52
+ ### Foundry module
53
+
54
+ `packages/foundry-module` runs inside the logged-in Foundry GM client and owns:
55
+
56
+ - second-boundary request validation;
57
+ - GM permission checks;
58
+ - protected-metadata sanitization;
59
+ - document lookup and serialization;
60
+ - capability adaptation across supported Foundry versions;
61
+ - mutation preparation and dry-run previews;
62
+ - execution through Foundry Document APIs and reviewed typed actions;
63
+ - observable write confirmation;
64
+ - managed-file containment.
65
+
66
+ The module ships plain browser-compatible JavaScript. Its generated protocol mirror is produced from
67
+ the canonical protocol package.
68
+
69
+ ## Core assumption
70
+
71
+ An authenticated GM client is open in the target world. The bridge acts through that client's
72
+ Foundry runtime and authority. It is not a headless database editor and does not bypass Foundry's
73
+ document lifecycle, validation, permissions, hooks, or installed system/module behavior.
74
+
75
+ ## Request flow
76
+
77
+ ```text
78
+ CLI invocation
79
+ -> parse flags and validate request schema
80
+ -> connect and authenticate to local daemon
81
+ -> correlate and forward to active bridge
82
+ -> validate, authorize, sanitize, and capability-check
83
+ -> resolve Foundry documents
84
+ -> prepare preview or execute through a Foundry API
85
+ -> serialize observed result or structured error
86
+ -> relay response by request ID
87
+ -> render JSON or human output
88
+ ```
89
+
90
+ The bridge advertises its supported commands during the handshake. The daemon forwards only commands
91
+ advertised by the active session.
92
+
93
+ ## Validation boundaries
94
+
95
+ The CLI validation pass provides fast feedback and avoids unnecessary connections. The bridge repeats
96
+ validation because the transport input remains untrusted and because Foundry-side capability and
97
+ document validation require the live runtime.
98
+
99
+ Closed protocol schemas define the complete accepted top-level field set for document families owned
100
+ by the bridge. Open schemas preserve system/module extensibility but pass through shared sanitization
101
+ before validation, diffing, preview, or dispatch.
102
+
103
+ Foundry DataModels remain the final authority for system-specific and version-specific values.
104
+
105
+ ## Command architecture
106
+
107
+ Commands are explicit typed handlers rather than a generic RPC. Related document families share
108
+ preparation, guard, serialization, and bulk seams so their behavior does not diverge between create,
109
+ update, clone, dry-run, and bulk routes.
110
+
111
+ CRUD handlers operate through document methods. Action handlers call a fixed reviewed Foundry method
112
+ and report only the result that can be observed or confirmed. Command-specific behavior is discovered
113
+ from the registry and CLI schema surface.
114
+
115
+ ## Mutation model
116
+
117
+ Mutations are serialized where family behavior requires ordering, but the bridge does not claim a
118
+ global transaction. The Foundry UI, systems, modules, and other clients remain concurrent writers.
119
+
120
+ A dry run performs the same preparation and guards as a real command, then stops before persistence.
121
+ Real commands confirm stored state where their contract depends on a write landing. Native Foundry
122
+ batch calls can partially apply, so bulk results include per-element outcomes.
123
+
124
+ Idempotency keys reduce duplicate effects across response loss while the relevant daemon/bridge cache
125
+ entry exists. They do not create durable distributed transactions.
126
+
127
+ ## Serialization
128
+
129
+ Readers serialize authored source state from Foundry document sources. Derived runtime values are
130
+ included only through explicit projections and are identified as derived.
131
+
132
+ List rows are lean discovery projections. Single-document reads expose richer authored projections.
133
+ This keeps large collections bounded while allowing callers to inspect a target before mutation.
134
+
135
+ Result shapes are intentionally narrower than arbitrary Foundry document models. Extensible writes
136
+ can therefore accept valid system/module data that a curated read does not echo field-for-field.
137
+
138
+ ## Managed files
139
+
140
+ File commands use Foundry's public managed-file APIs. Reads address the managed `data` source. Writes
141
+ are contained to the active world's allowed asset tree and exclude the world manifest, databases,
142
+ and packs.
143
+
144
+ The bridge receives upload bytes over the local transport; it never resolves an operator-machine
145
+ absolute path. File mutations and document-reference mutations remain separate explicit commands.
146
+
147
+ See [Security](security.md#file-write-boundary) for the full boundary.
148
+
149
+ ## Session lifecycle
150
+
151
+ The first bridge connection attempt occurs after Foundry is ready. Authentication or protocol
152
+ rejection is terminal for that module load so a persistent configuration problem does not create a
153
+ reconnect loop. A session that completed the handshake and later loses transport reconnects with
154
+ bounded exponential backoff.
155
+
156
+ The daemon persists multiple pairing profiles but routes through one active bridge. A profile is owned
157
+ by one browser: its uniqueness key is Origin, world, GM, and the browser's own persistent client
158
+ identifier, which is why the same person can keep two browsers paired to one world and GM and why
159
+ re-pairing rotates only the re-pairing browser's credential. Making the browser the unit of ownership
160
+ also makes the human label meaningful, so the label travels with the pairing request instead of being
161
+ editable daemon-side metadata: it is fixed between pairing approvals, and an approval that reuses an
162
+ existing record adopts the label that request carried. The design keeps slot ownership
163
+ unambiguous: a socket receives its role only after completed authentication rather
164
+ than from a claimed message type; only a same-pairing socket can take over the slot, as the
165
+ tab-reload recovery path; intentional goodbye, release, and revocation clear ownership before close
166
+ handling, while only an abnormal close creates a short reclaim lease; and daemon-initiated release is
167
+ terminal for the released client so reconnection remains an explicit operator action. Every way a
168
+ pairing attempt can end shares one idempotent cleanup path, so the authorization UI cannot retain a
169
+ stale pending state.
170
+
171
+ While serving, the daemon owns authentication and connection configuration writes, and preserves a
172
+ concurrently changed upload limit until a restart applies it to the transport. Daemon control
173
+ operations for pairing, profiles, release, and client credential rotation form the future Companion
174
+ boundary and remain separate from the Foundry command registry.
175
+
176
+ One of those operations parks instead of answering at once: the wait for a pairing request holds its
177
+ response until a request arrives or the daemon's own park cap elapses. That cap is what keeps a parked
178
+ answer inside the caller's request timeout, so an unanswered wait ends in an empty result the CLI
179
+ re-issues rather than in a transport failure; a cap at or above the client's wait would turn every
180
+ wait into one.
181
+
182
+ Normative handshake, takeover, lease, and release semantics are defined in
183
+ [Protocol](protocol.md#bridge-sessions); the authentication guarantees and host validation rules are
184
+ stated in [Security](security.md#authentication).
185
+
186
+ ### Client-side status signal
187
+
188
+ The module's own UI needs to react to connection changes rather than read state once, so the bridge
189
+ client publishes every status transition instead of assigning the field silently. The Foundry module
190
+ re-emits those transitions as the `fvtt-world-cli.statusChanged` hook, which makes the same signal
191
+ available to macros and other modules in the GM client. It is a client-side extension point only and
192
+ carries no wire-protocol meaning; the daemon and the CLI neither send nor observe it.
193
+
194
+ The hook fires once per actual change, on the client transport status or on the handshake
195
+ acknowledgement, and receives the same snapshot that `system info` reports as `bridge`: `status`,
196
+ `url`, `helloAcknowledged`, `hasEstablishedSession`, `lastConnectedAt`, `reconnectAttempts`, and
197
+ `terminalStopReason`. Readiness is `status === "connected"` together with `helloAcknowledged`, because
198
+ an open socket precedes the daemon's acknowledgement. A snapshot never reports an acknowledged
199
+ handshake on a client that is no longer connected: losing the socket resets the acknowledgement before
200
+ the status transition that publishes it, so consumers cannot observe that contradictory pair.
201
+ `helloAcknowledged` stays in the snapshot for consumers that need the distinction; the module's own
202
+ windows fold it into the connection state they display rather than showing it as its own field.
203
+
204
+ Credential changes are not transitions of this hook. Pairing and unpairing refresh the module's own
205
+ windows and toolbar indicator through an internal signal, since the connection state itself has not
206
+ changed at that moment.
207
+
208
+ ## Compatibility strategy
209
+
210
+ The bridge supports the designated Foundry major versions through narrow capability adapters and
211
+ explicit guards. It refuses a version-dependent request when it cannot provide the documented result
212
+ honestly.
213
+
214
+ Mocks verify contracts and edge cases but cannot establish real Foundry compatibility. The live smoke
215
+ workflow is the authority for executed coverage. Current operator-visible differences are summarized
216
+ in [Foundry compatibility](compatibility.md).
@@ -0,0 +1,367 @@
1
+ # Commands
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.
7
+
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]
13
+ ```
14
+
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.
20
+
21
+ ## Before you begin
22
+
23
+ Start the local daemon and keep an authenticated GM client open in the target Foundry world:
24
+
25
+ ```bash
26
+ fvtt-world-cli bridge serve
27
+ ```
28
+
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.
32
+
33
+ ```bash
34
+ fvtt-world-cli auth
35
+ ```
36
+
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`:
40
+
41
+ ```bash
42
+ fvtt-world-cli auth pending
43
+ fvtt-world-cli auth approve [code]
44
+ ```
45
+
46
+ The full first-run walkthrough is in [Getting started](getting-started.md).
47
+
48
+ Confirm the connection before reading or writing:
49
+
50
+ ```bash
51
+ fvtt-world-cli system info --json
52
+ ```
53
+
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
+ Ctrl+C, or an ended stdin, at the confirmation instead leaves the request untouched, and it stays
69
+ pending until it expires or another run answers for it. An ended stdin, and Ctrl+C while output goes
70
+ to the terminal too, report `PAIRING_PROMPT_ABORTED` and exit 1; with output redirected the prompt
71
+ does not read keystrokes itself, so Ctrl+C ends the run as an ordinary interrupt instead.
72
+ Delivery is a long-poll control call the CLI re-issues on its own, and the daemon answers each call
73
+ inside its own park cap, so an ordinary wait does not end in a timeout; a daemon that stops answering
74
+ altogether still trips the client's request timeout and is reported as `DAEMON_UNAVAILABLE`. The
75
+ command is interactive-only: `--json`, and a stdin that is not a terminal, each fail
76
+ immediately and name `auth pending` plus `auth approve --yes` as the path for scripts.
77
+ `--timeout-ms` cannot cut the wait short either — a value below the daemon's park cap plus five
78
+ seconds is raised to it for the long-poll call, because a smaller client wait would abandon a parked
79
+ response the daemon is still holding. That long poll is the only call the flag cannot shorten: the
80
+ approval or denial that follows the answer, and the listing an interactive `auth approve` or
81
+ `auth prune` reads before its prompt, take `--timeout-ms` as given for their client wait, as the
82
+ other `auth` verbs and `bridge release` do; without the flag that wait is 60 seconds.
83
+ - `auth status` shows bridge state and public profile metadata.
84
+ - `auth pending` lists approval candidates with code, expiry, exact Origin, world, GM, browser client
85
+ id, browser label, and module version.
86
+ - `auth approve [code] [--yes]` approves one request. The code may be omitted only when exactly one
87
+ request is pending. Interactive use confirms the displayed identity, including the browser label and
88
+ client id, and then approves that exact request: one that expired or disappeared while the prompt
89
+ waited fails rather than approving whatever else is pending. When no single pending request can be
90
+ shown — nothing pending, an unknown code, or several candidates — it names the live pending codes and
91
+ stops instead of asking. Answering anything but `y` or `yes` cancels the approval, reports
92
+ `PAIRING_DECLINED` and exits 1 — the same code and exit the bare `auth` wait reports for a declined
93
+ request, which the wait also denies outright while this verb leaves it pending. Interrupting the
94
+ prompt also leaves the request pending, and reports `PAIRING_PROMPT_ABORTED` with exit 1 on the same
95
+ terms as the wait. Scripts must pass `--yes`. The stored label is the one the browser sent — approval
96
+ does not rename it.
97
+ - `auth deny <code>` rejects a pending request.
98
+ - `auth list` shows non-secret profile metadata, including each profile's `clientId` and `label`.
99
+ A label is set in the browser at pairing time and no control operation renames a stored record, so
100
+ a browser that needs a different label unpairs and pairs again. A browser that only forgot its local
101
+ credential keeps its record, and the next approval adopts the label that request carried. Labels are
102
+ not unique, so two browsers may share one. A profile's `lastSeenAt` is stamped when it is approved,
103
+ when its browser connects the bridge, when that connection ends, and when a hello is turned away
104
+ because another profile owns the slot, so the timestamp measures how long the profile has been idle
105
+ rather than how long ago it last connected. A second browser that is opened daily while the first
106
+ one holds the bridge therefore stays fresh and is never pruned out from under its owner.
107
+ - `auth prune [--older-than <days>] [--yes]` deletes the profiles that have gone unused. A profile is
108
+ a candidate when its `lastSeenAt` is older than the cutoff, which defaults to 30 days; the active
109
+ 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 — label, client id, world, GM,
111
+ last-seen timestamp, and pairing id — and asks once for the whole set. The listing skips the profile
112
+ `auth list` reports as active, so a browser that has stayed connected past the cutoff is neither shown
113
+ nor counted in the prompt, matching what the daemon will do. Answering anything but `y` or
114
+ `yes` removes nothing, reports `PAIRING_DECLINED` and exits 1; interrupting the prompt reports
115
+ `PAIRING_PROMPT_ABORTED` and exits 1 on the same terms as `auth approve`. A listing that found no
116
+ candidate asks nothing and still runs the operation, so the command's output is the daemon's own empty
117
+ result rather than a local verdict. The listing is a preview only: the daemon recomputes the set,
118
+ against the cutoff as it stands when the command runs, and its own result is what the command prints.
119
+ The executed set can therefore be larger than the one the prompt counted, because a profile that
120
+ crossed the cutoff while the prompt waited is removed although it was never listed; it can equally be
121
+ smaller, because a profile that became active or was already removed between the two steps is
122
+ 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 —
124
+ `auth list` does not expose the lease, and a lease holder's last-seen timestamp is fresh by
125
+ definition, so no realistic threshold selects it. Scripts pass `--yes`, which skips both the preview
126
+ and the prompt; `--json` requires `--yes` as well, because the confirmation is never mixed into JSON
127
+ output, and without it the command stops with exit 2.
128
+ - `auth revoke <pairingId>` deletes one daemon profile and disconnects it if active.
129
+ - `auth rotate-client --yes` replaces the device-local CLI/Companion credential and closes existing
130
+ local-client sockets without invalidating browser pairings.
131
+ - `bridge release` clears the active slot or abnormal-disconnect lease without deleting a profile.
132
+ An active browser stopped by release stays stopped until its operator chooses Connect.
133
+
134
+ 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 — the `auth` verbs and
136
+ `bridge release` alike — with `DAEMON_UNAVAILABLE` and exit 3 rather than a command-level failure.
137
+
138
+ In Foundry, Connect reuses the stored browser credential and Disconnect releases the slot without
139
+ touching it. Unpair waits for confirmed daemon revocation
140
+ before deleting that credential. If revocation cannot be confirmed, it retains the credential and
141
+ offers Forget local as the explicit recovery path; Forget local does not revoke the daemon profile.
142
+ `BRIDGE_BUSY` likewise preserves the credential: release the current bridge and choose Connect.
143
+
144
+ ### Local configuration
145
+
146
+ - `config get` shows the config path and non-secret settings.
147
+ - `config set-upload-limit <size>` persists the raw upload-byte limit. The daemon preserves this
148
+ field across later pairing and bridge-session writes, but a running daemon must be restarted before
149
+ its WebSocket transport and the browser bridge advertise the new limit. JSON output includes
150
+ `daemonRestartRequired: true`.
151
+
152
+ ## Finding the right command
153
+
154
+ Command names describe the document nesting. Dots are used in the protocol registry; spaces are used
155
+ on the CLI:
156
+
157
+ | Goal | Protocol family | CLI shape |
158
+ |---|---|---|
159
+ | World actors | `actor.*` | `fvtt-world-cli actor …` |
160
+ | Items embedded in an actor | `actor.item.*` | `fvtt-world-cli actor item …` |
161
+ | Effects on an actor item | `actor.item.effect.*` | `fvtt-world-cli actor item effect …` |
162
+ | Tokens embedded in a scene | `scene.token.*` | `fvtt-world-cli scene token …` |
163
+ | Effects on a placed token | `scene.token.effect.*` | `fvtt-world-cli scene token effect …` |
164
+
165
+ Use `fvtt-world-cli commands --json` for the complete current inventory. The exact operation set
166
+ varies by family, so a nearby document family is not a reliable guide to what another one supports.
167
+
168
+ ## Capability map
169
+
170
+ ### World content
171
+
172
+ - `actor`, `item`, `journal`, `scene`, `macro`, `playlist`, `table`, and `cards` manage world
173
+ documents.
174
+ - `chat` reads, creates, and deletes chat messages.
175
+ - `combat` manages encounters and exposes explicit encounter transitions.
176
+ - `folder` manages document organization.
177
+ - Dedicated `*.ownership.set` commands change supported document ownership.
178
+
179
+ Common world-document operations include `list`, `get`, `get-many`, `create`, `update`, `clone`, and
180
+ `delete`. The exact set varies by family.
181
+
182
+ ### Embedded content
183
+
184
+ - `actor.item` manages an actor's embedded items.
185
+ - `*.effect` families manage ActiveEffects on actors, items, actor items, tokens, and token items.
186
+ - `journal.category` manages journal page categories.
187
+ - `playlist.sound` manages playlist tracks.
188
+ - `table.result` manages roll-table rows.
189
+ - `cards.card` manages cards inside a stack.
190
+ - `combat.combatant` and `combat.group` manage encounter membership.
191
+ - `scene.token`, `tile`, `sound`, `wall`, `note`, `drawing`, `light`, `template`, and `region` manage
192
+ scene placeables.
193
+ - `scene.region.behavior` manages region behaviors; writes that supply executable core behavior
194
+ types are rejected (see [Security](security.md#executable-content)).
195
+
196
+ Embedded commands require the complete parent ID chain; a read of the parent supplies those IDs when
197
+ they are not already known.
198
+
199
+ ### Actions
200
+
201
+ Some commands invoke a typed Foundry action instead of ordinary CRUD:
202
+
203
+ - playlist and playlist-sound playback;
204
+ - roll-table draw and reset;
205
+ - card shuffle, reset, deal, draw, and pass;
206
+ - combat start, activation, advancement, and initiative;
207
+ - scene thumbnail generation and fog reset.
208
+
209
+ Actions can have Foundry, system, or module side effects. Their result describes what the bridge can
210
+ confirm, which may differ from a document post-state, so each action's schema and help are worth
211
+ reading before automating it.
212
+
213
+ ### Discovery and maintenance
214
+
215
+ - `world.search` finds content across supported world and optional compendium indexes.
216
+ - `world.audit-files` finds document references to missing managed assets.
217
+ - `compendium.list`, `compendium.index`, and `compendium.get` read pack content.
218
+ - Supported `*.import-from-compendium` commands create world documents from pack sources.
219
+ - `user` and `setting` provide read-only discovery surfaces.
220
+
221
+ ### Managed files
222
+
223
+ `file` commands operate on Foundry's managed `data` source. Reads can inspect managed assets. Writes
224
+ are restricted to the active world's allowed tree and exclude its manifest, databases, and packs.
225
+ Document references are updated separately with an explicit document command.
226
+
227
+ See [Security](security.md#file-write-boundary) before automating file writes.
228
+
229
+ ## Shared command behavior
230
+
231
+ ### JSON output
232
+
233
+ Use `--json` for automation. Successful requests use a stable envelope:
234
+
235
+ ```json
236
+ {
237
+ "protocolVersion": "…",
238
+ "type": "command.response",
239
+ "id": "…",
240
+ "ok": true,
241
+ "result": {
242
+ "actor": {}
243
+ }
244
+ }
245
+ ```
246
+
247
+ Documents are stored under a type-named result key such as `actor`, `items`, `scene`, or `outcomes`.
248
+ The key varies between commands but is stable for each one, so a first response shows what to script
249
+ against. Errors use `ok: false`, a stable code, a
250
+ message, and optional details; see [Protocol](protocol.md#error-model).
251
+
252
+ Serialized Foundry documents generally expose `id` as the public identifier and may also expose the
253
+ source `_id` mirror. Use the documented `id` fields for subsequent commands.
254
+
255
+ ### Input validation
256
+
257
+ Input is validated locally and again at the Foundry boundary. Unknown options, missing required
258
+ options, and malformed CLI values are usage errors. Protocol payloads use closed schemas where the
259
+ bridge owns the writable field set and sanitized open schemas where Foundry or a game system owns
260
+ extensible data.
261
+
262
+ Use JSON flags such as `--data-json`, `--patch-json`, and family-specific JSON flags for structured
263
+ values. The command schema is the definitive description of accepted keys.
264
+
265
+ ### Lists, filters, and pagination
266
+
267
+ List-like commands that support pagination accept `limit` and `offset` and return collection data
268
+ plus `total` and `hasMore`. One response is not guaranteed to contain the entire collection; paging
269
+ continues until `hasMore` is false.
270
+
271
+ Some collections support a case-insensitive `name` filter before pagination. Search commands use
272
+ their own matching rules and are not interchangeable with a list filter. Whether a filter exists is
273
+ recorded in each command's schema.
274
+
275
+ ### Reads and projections
276
+
277
+ Single-document `get` operations return the documented authored projection. Some commands accept
278
+ `include` values for derived or expensive data. Derived data is explicitly identified and can vary by
279
+ Foundry version and game system.
280
+
281
+ List rows are intentionally smaller than `get` results and are not sufficient to construct an update
282
+ from; that starts from a fresh `get`.
283
+
284
+ ### Updates and merge semantics
285
+
286
+ Updates are patches, not full replacements:
287
+
288
+ - nested objects merge recursively;
289
+ - ordinary arrays replace as a whole;
290
+ - dotted paths target a nested leaf where the schema permits them;
291
+ - Foundry deletion syntax can remove permitted nested keys;
292
+ - embedded-document collections follow their family-specific Foundry semantics.
293
+
294
+ Arrays and extensible system data are the easiest fields to clobber: an array patch replaces the
295
+ whole array, so one built from stale state silently drops entries. A fresh read before editing, the
296
+ smallest patch that expresses the change, and a read-back afterwards avoid that.
297
+
298
+ ### Dry run
299
+
300
+ All mutation commands accept the global `--dry-run` flag:
301
+
302
+ ```bash
303
+ fvtt-world-cli --dry-run actor update --actor-id <id> --name "New name" --json
304
+ ```
305
+
306
+ The result uses the normal command shape and includes `dryRun: true`. The preview contract — what a
307
+ dry run executes, what it can report, and its non-reservation of state — is defined in
308
+ [Protocol](protocol.md#dry-run).
309
+
310
+ ### Idempotency and retries
311
+
312
+ Commands with duplicate-creation or non-repeatable-action risk may require or accept an idempotency
313
+ key. An operation that timed out or disconnected may already have reached Foundry, so a blind retry
314
+ can apply it twice. Key semantics and delivery-state retry rules are defined in
315
+ [Protocol](protocol.md#idempotency) and
316
+ [Protocol](protocol.md#delivery-states-and-retries).
317
+
318
+ ### Batch reads and bulk writes
319
+
320
+ `get-many` reduces round trips for independent reads. `exec --stdin` sends NDJSON commands over one
321
+ connection while retaining an individual response for each request.
322
+
323
+ Families that expose `create-many`, `update-many`, or `delete-many` validate the envelope and each
324
+ element before dispatch, but the persistence layer is not transactional. Inspect `complete` and every
325
+ entry in `outcomes`; see [Protocol](protocol.md#batch-requests-and-bulk-writes).
326
+
327
+ ### File paths
328
+
329
+ Pass literal managed-data paths. The bridge normalizes and encodes document asset references where
330
+ appropriate; callers should not pre-encode ordinary filename characters. URLs, virtual texture IDs,
331
+ and other special values follow the receiving field's schema.
332
+
333
+ ## Common workflows
334
+
335
+ ### Find, inspect, update, verify
336
+
337
+ ```bash
338
+ fvtt-world-cli actor list --name "Goblin" --json
339
+ fvtt-world-cli actor get --actor-id <id> --json
340
+ fvtt-world-cli --dry-run actor update --actor-id <id> --name "Goblin Scout" --json
341
+ fvtt-world-cli actor update --actor-id <id> --name "Goblin Scout" --json
342
+ fvtt-world-cli actor get --actor-id <id> --json
343
+ ```
344
+
345
+ ### Discover an unfamiliar command
346
+
347
+ ```bash
348
+ fvtt-world-cli commands --json
349
+ fvtt-world-cli schema scene.token.create
350
+ fvtt-world-cli scene token create --help
351
+ ```
352
+
353
+ ### Work with an embedded document
354
+
355
+ ```bash
356
+ fvtt-world-cli scene token list --scene-id <sceneId> --json
357
+ fvtt-world-cli scene token get --scene-id <sceneId> --token-id <tokenId> --json
358
+ ```
359
+
360
+ First-run setup is covered in [Getting started](getting-started.md).
361
+
362
+ ## Unsupported boundaries
363
+
364
+ The CLI intentionally does not provide arbitrary JavaScript execution, direct world-database writes,
365
+ unrestricted filesystem access, generic RPC, compendium editing, setting writes, or transactional
366
+ Foundry batches. Consult [Security](security.md) for the trust boundary and
367
+ [Foundry compatibility](compatibility.md) for version-dependent capabilities.