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/LICENSE +21 -0
- package/README.md +136 -0
- package/bin/fvtt-world-cli.js +47 -0
- package/bin/prepare-package-assets.mjs +22 -0
- package/bin/sync-installed-skill.mjs +89 -0
- package/dist/index.js +16200 -0
- package/docs/README.md +71 -0
- package/docs/architecture.md +216 -0
- package/docs/commands.md +367 -0
- package/docs/compatibility.md +26 -0
- package/docs/getting-started.md +104 -0
- package/docs/images/authorization-window.png +0 -0
- package/docs/images/icon.svg +16 -0
- package/docs/images/mark.svg +15 -0
- package/docs/images/module-settings.png +0 -0
- package/docs/protocol.md +238 -0
- package/docs/security.md +233 -0
- package/docs/skill.md +65 -0
- package/package.json +50 -0
- package/skills/foundry-world-editor/SKILL.md +156 -0
- package/skills/foundry-world-editor/SKILL.md.sha256 +1 -0
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).
|
package/docs/commands.md
ADDED
|
@@ -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.
|