fvtt-world-cli 1.0.0 → 1.1.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: foundry-world-editor
3
- description: Use when a request involves reading or changing anything in a live Foundry VTT world or mentions fvtt-world-cli, worldctl, or the Foundry bridge/daemon.
3
+ description: Use for live Foundry VTT reads or edits, or work with fvtt-world-cli, worldctl, the bridge, or daemon.
4
4
  ---
5
5
 
6
6
  # Foundry World Editor (fvtt-world-cli)
@@ -29,7 +29,7 @@ Authorization, name the browser in the `Browser label` field, and choose Pair; r
29
29
  `fvtt-world-cli auth pending`, confirm the listed Origin, world, GM, browser label, and client id,
30
30
  then `auth approve <code> --yes`. Never run bare `fvtt-world-cli auth`: that is the human's
31
31
  one-command pairing wait, it blocks until a request arrives or Ctrl+C ends it, and it refuses
32
- `--json` hand that command to the GM at their own terminal instead of running it here. A pairing
32
+ `--json`. Hand that command to the GM at their own terminal instead of running it here. A pairing
33
33
  belongs to one browser, so the same GM on the same world can hold several records, and the label is
34
34
  chosen in the browser and fixed once paired. Labels are not unique and can render as nothing, so the
35
35
  client id is what tells concurrent requests apart and confirms that a re-pair replaces the record you
@@ -41,30 +41,58 @@ expect.
41
41
  `BRIDGE_NOT_READY` means the daemon is reachable but no GM client is connected yet. `BRIDGE_BUSY`
42
42
  means another paired browser holds the active bridge slot: clear it with
43
43
  `fvtt-world-cli bridge release` and have the GM choose Connect in the module's Authorization
44
- window or World CLI scene-controls group do not start a new pairing.
44
+ window or World CLI scene-controls group. Keep the existing pairing.
45
+
46
+ `UNSUPPORTED_PROTOCOL_VERSION` means the installed components come from different releases. Read
47
+ `details.staleComponent`. Update the Foundry module for `module`. Update the CLI and restart the
48
+ daemon for `cli-daemon`. Compare the reported versions for `unknown`.
45
49
 
46
50
  ## Hard rules
47
51
 
48
52
  - Pass `--json` on every automated call.
49
- - Never edit Foundry world files on disk and never automate the Foundry browser UI; this CLI is
50
- the control surface.
53
+ - Keep all world edits in the CLI. Leave Foundry world files and the browser UI untouched.
51
54
  - Address documents by `id`, never by name; names are not unique. Embedded ids are meaningful only
52
55
  with their complete parent chain (actor → item → effect, scene → token → item).
53
56
  - Ownership changes go through dedicated `*.ownership.set` commands; raw `ownership` is rejected
54
57
  in ordinary payloads.
55
- - The CLI cannot execute JavaScript, write settings, edit compendium packs in place, or reach
56
- outside the managed file boundary, and executable region-behavior types are rejected on write.
57
- Do not look for workarounds; report the limitation instead.
58
+ - The CLI cannot evaluate arbitrary JavaScript, edit compendium packs in place, or reach outside
59
+ the managed file boundary, and ordinary region-behavior writes reject executable types. Do not
60
+ look for workarounds; report the limitation instead.
61
+ - Check stderr before diagnosing a hung command. An approval-listed command prints a waiting line
62
+ while the GM decides in Foundry. Deletions require approval by default.
63
+
64
+ ## Disabled commands
65
+
66
+ Some commands ship denied. A command that `schema <command>` or `--help` knows but a
67
+ `commands --json` listing with `policy.applied: true` omits is disabled by the GM of this bridge:
68
+ report that it needs enabling in the Command permissions window instead of hunting for an
69
+ equivalent. `macro.execute` is the only way to run code and is not a workaround for a missing
70
+ command.
71
+
72
+ ## Command-specific cautions
73
+
74
+ A macro that throws fails `macro.execute` with a partial outcome — read what it touched before
75
+ retrying — while a macro that catches its own errors still returns `null`, which proves nothing;
76
+ verify effects with reads, and treat `MACRO_TIMEOUT` as indeterminate — the macro may still be
77
+ running.
78
+ `setting.set` cannot touch this module's own namespace (`SETTING_PROTECTED` is final; never retry).
79
+ When a setting write returns `requiresReload: true`, the change needs a GM-client reload to take
80
+ effect; `system.reload` performs it, drops the bridge, and requires a reconnect wait before the
81
+ next command. The reload commonly races its own result: the page can go away before the confirming
82
+ `reloading: true` is delivered, so a disconnect or `APPROVAL_UNKNOWN` there is the expected success
83
+ signal, not a failure — reconnect and continue rather than retrying the reload.
58
84
 
59
85
  ## The working loop
60
86
 
61
87
  Discover → inspect schema → locate → read → smallest patch → dry-run → commit → verify. Never
62
88
  guess a command name or parameter.
63
89
 
64
- 1. `fvtt-world-cli commands --json` lists the installed commands and marks mutations. Dotted
65
- protocol names map to spaced CLI subcommands (`actor.item.update` `actor item update`). A
66
- command present in the registry but not advertised by the connected bridge is a client/bridge
67
- version mismatch.
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`.
68
96
  2. `fvtt-world-cli schema <command>` shows the exact request parameters, required fields, enums,
69
97
  and whether unknown fields are accepted; `fvtt-world-cli <command path> --help` maps flags.
70
98
  3. Locate targets with the narrowest query: a family `list --name <substring> --limit <n>` when
@@ -73,16 +101,19 @@ guess a command name or parameter.
73
101
  never the basis of a write.
74
102
  4. `get` the complete target before mutating and work from the returned ids.
75
103
  5. Build the smallest valid patch. Nested objects merge recursively; plain arrays replace
76
- wholesale; dotted paths reach permitted leaves; deletion operators remove permitted keys. Never
77
- send a `get` result back as a patch — it contains read-only and derived fields the update
78
- schema rejects. For arrays and extensible `system`/`flags` data: read, preserve everything that
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.
106
+ For arrays and extensible `system`/`flags` data: read, preserve everything that
79
107
  stays, change only the intended part.
80
108
  6. Dry-run every nontrivial mutation with the global `--dry-run` flag. It runs the same
81
109
  validation, sanitization, permission, capability, and security checks, then stops before
82
110
  persistence; check both `ok` and `.result.dryRun`. A preview reports only what is knowable
83
- before execution and reserves nothing.
111
+ before execution and reserves nothing. Approval does not hold a preview. A denied command still
112
+ fails, and `approvalRequired: true` means the commit will wait for the GM.
84
113
  7. Commit with the same content. Attach `--idempotency-key <stable-key>` to any create, clone,
85
- import, upload, or action that might be retried one stable key per logical operation.
114
+ import, upload, or action that might be retried. Use one key per logical operation. After response
115
+ loss, follow the retry classification below because the delivery state determines whether to
116
+ reuse the key or create a fresh one.
86
117
  8. Verify with a fresh read. This matters most after open-schema writes, bulk operations, and
87
118
  actions, where confirmation can say less than an observed post-state.
88
119
 
@@ -103,22 +134,30 @@ details}}`, alongside `protocolVersion`, `type`, and the correlation `id` (echoi
103
134
  `exec --stdin` batches). Branch on `error.code`, never on message text.
104
135
  Documents live under a type-named key inside `result` that varies between commands but is stable
105
136
  for each one. `id` is the public identifier; a `_id` mirror may accompany it. Values requested
106
- through `include` flags may be derived or version-dependent never write them back unless the
137
+ through `include` flags may be derived or version-dependent. Write them back only when the
107
138
  update schema explicitly accepts them.
108
139
 
109
140
  ## Retry classification
110
141
 
111
142
  Classify a failure before reacting:
112
143
 
113
- - Usage or validation error fix the request.
114
- - Permission, safety, or capability error a different operation or runtime is needed; retrying
115
- the same call cannot succeed.
116
- - Not forwarded (connection refused, `BRIDGE_NOT_READY`) safe to retry once the stack is
117
- restored.
118
- - Forwarded but unresolved (`BRIDGE_TIMEOUT`, `BRIDGE_DISCONNECTED`, a timeout after send) the
119
- mutation may already have committed: inspect world state first, and reuse the same idempotency
120
- key when retrying the same logical request. Never mint a new key because a response was lost.
121
- - Structured Foundry rejection — correct the content and resubmit as a new operation.
144
+ - Fix usage and validation errors before sending a new request.
145
+ - Treat permission, safety, and capability errors as unavailable operations. Change the operation or
146
+ runtime instead of retrying the same call.
147
+ - Connection refusal, `BRIDGE_NOT_READY`, and `IDEMPOTENCY_STORE_FULL` mean Foundry received nothing.
148
+ Retry after restoring the connection or after earlier keys settle. Reuse the same request and key.
149
+ - `BRIDGE_TIMEOUT` and a response timeout after send are unresolved deliveries. Read world state
150
+ first. Reuse the same key only while the bridge session that carried the request remains connected.
151
+ - `BRIDGE_DISCONNECTED` is indeterminate. Read world state, report the result, and use a fresh key if
152
+ the operation still needs to run.
153
+ - `COMMAND_DENIED` means nothing ran and the command is unavailable in that client. Report the
154
+ limitation instead of seeking another command with the same effect.
155
+ - `APPROVAL_DENIED`, `APPROVAL_TIMEOUT`, and `APPROVAL_CANCELLED` mean nothing ran. Report the outcome
156
+ and wait for user direction. `APPROVAL_QUEUE_FULL` also means nothing ran, but it can clear after
157
+ earlier requests settle.
158
+ - `APPROVAL_UNKNOWN` and unconfirmed cancellation are indeterminate. Read the affected documents,
159
+ report the result, and use a fresh key if the operation still needs to run.
160
+ - Correct a structured Foundry rejection and submit the corrected content as a new operation.
122
161
 
123
162
  ## Bulk writes and actions
124
163
 
@@ -152,5 +191,5 @@ binary file into context.
152
191
  `fvtt-world-cli docs` lists the durable contract documents shipped with the CLI, and
153
192
  `fvtt-world-cli docs <name>` prints one: `commands` (human overview and workflows), `protocol`
154
193
  (delivery, error, and session semantics), `security` (trust boundary), `compatibility` (Foundry
155
- version differences), `getting-started` (first-run pairing walkthrough). The runtime registry
156
- `commands --json` and `schema` always outranks prose.
194
+ version differences), `getting-started` (first-run pairing walkthrough). The runtime registry in
195
+ `commands --json` and `schema` always outranks prose.
@@ -1 +1 @@
1
- 9551cf818565ba2d70903bb4a28425b233c2b13cbc93625d61b37eb76ef0e0c4
1
+ 1834777e1d540cb4cc11b6eac862ff99878a6e4920ebbc1e2599871893e19dc9
@@ -1,89 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { cpSync, lstatSync, readFileSync, rmSync, statSync } from "node:fs";
3
- import { homedir, platform } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
-
7
- const SKILL_NAME = "foundry-world-editor";
8
-
9
- try {
10
- const binDirectory = dirname(fileURLToPath(import.meta.url));
11
- const sourceDirectory = [resolve(binDirectory, "../skills"), resolve(binDirectory, "../../../skills")]
12
- .map((candidate) => resolve(candidate, SKILL_NAME))
13
- .find((candidate) => {
14
- try {
15
- return statSync(join(candidate, "SKILL.md")).isFile();
16
- } catch {
17
- return false;
18
- }
19
- });
20
- if (!sourceDirectory) process.exit(0);
21
- const packaged = readFileSync(join(sourceDirectory, "SKILL.md"), "utf8");
22
-
23
- const home = process.env.HOME?.trim() || homedir();
24
- const xdgHome = process.env.XDG_CONFIG_HOME?.trim();
25
- const configPath = xdgHome
26
- ? join(xdgHome, "fvtt-world-cli", "config.json")
27
- : platform() === "darwin"
28
- ? join(home, "Library", "Application Support", "fvtt-world-cli", "config.json")
29
- : platform() === "win32"
30
- ? join(
31
- process.env.APPDATA?.trim() || join(home, "AppData", "Roaming"),
32
- "fvtt-world-cli",
33
- "config.json"
34
- )
35
- : join(home, ".config", "fvtt-world-cli", "config.json");
36
-
37
- const roots = new Set([join(home, ".agents", "skills")]);
38
- try {
39
- const config = JSON.parse(readFileSync(configPath, "utf8"));
40
- if (Array.isArray(config?.skillInstalls)) {
41
- for (const root of config.skillInstalls) {
42
- if (typeof root === "string" && root.length > 0) roots.add(root);
43
- }
44
- }
45
- } catch {
46
- // no or unreadable config: the standard location is still checked
47
- }
48
-
49
- for (const root of roots) {
50
- try {
51
- const destination = join(root, SKILL_NAME);
52
- const existing = (() => {
53
- try {
54
- return lstatSync(destination);
55
- } catch {
56
- return null;
57
- }
58
- })();
59
- if (!existing || existing.isSymbolicLink()) continue;
60
-
61
- const installed = readFileSync(join(destination, "SKILL.md"), "utf8");
62
- if (installed === packaged) continue;
63
-
64
- const recorded = (() => {
65
- try {
66
- return readFileSync(join(destination, "SKILL.md.sha256"), "utf8").trim();
67
- } catch {
68
- return null;
69
- }
70
- })();
71
-
72
- if (recorded === createHash("sha256").update(installed).digest("hex")) {
73
- rmSync(destination, { recursive: true, force: true });
74
- cpSync(sourceDirectory, destination, { recursive: true });
75
- console.error(
76
- `fvtt-world-cli: updated the installed agent skill at ${destination} to the packaged version.`
77
- );
78
- } else {
79
- console.error(
80
- `fvtt-world-cli: the installed agent skill at ${destination} has local modifications and was NOT updated. Compare them, then run \`fvtt-world-cli skill update --force\` to replace it.`
81
- );
82
- }
83
- } catch {
84
- continue;
85
- }
86
- }
87
- } catch {
88
- process.exit(0);
89
- }