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/skill.md ADDED
@@ -0,0 +1,65 @@
1
+ # Agent skill
2
+
3
+ fvtt-world-cli ships with `foundry-world-editor`, an installable Agent Skill — a short operating
4
+ manual that teaches an AI agent to drive this CLI safely: how to bring the stack up and check its
5
+ health, the read → preview → commit → verify loop, how to classify failures before retrying, and
6
+ where the hard safety boundaries are. Skills follow the open Agent Skills standard, so the same
7
+ file works in Claude Code, Codex, and every other agent that reads `SKILL.md` files.
8
+
9
+ The skill deliberately contains no command inventory. Agents discover the exact command surface
10
+ from the CLI itself (`commands --json`, `schema <command>`), so the skill stays valid as commands
11
+ evolve; it carries only the knowledge that runtime discovery cannot provide. The skill itself lives
12
+ at [`skills/foundry-world-editor/SKILL.md`](../skills/foundry-world-editor/SKILL.md).
13
+
14
+ ## Installing
15
+
16
+ ```bash
17
+ fvtt-world-cli skill install
18
+ ```
19
+
20
+ The default installation delegates to the ecosystem's skills CLI (`npx skills add`), which detects
21
+ the agents present on the machine. It keeps one canonical copy under `~/.agents/skills` — the
22
+ vendor-neutral location of the Agent Skills standard — and points each agent's own skill directory
23
+ at it, so every agent reads the same single copy.
24
+
25
+ An explicit destination works without the skills CLI or network access:
26
+
27
+ ```bash
28
+ fvtt-world-cli skill install --to <skills-directory>
29
+ ```
30
+
31
+ `--to` performs a direct copy and records the location in the CLI's local configuration, so that
32
+ copy participates in updates later. `--link` symlinks instead of copying, which keeps the installed
33
+ skill permanently identical to the CLI it came from.
34
+
35
+ ## Staying up to date
36
+
37
+ The skill is versioned together with the CLI, and the CLI keeps installed copies current on its
38
+ own: updating the npm package refreshes them, and the daemon performs the same check at startup for
39
+ installations updated by other means, such as a git checkout. For updating purposes a copy is one
40
+ of two kinds:
41
+
42
+ - An **unmodified** copy — exactly what some version of the CLI shipped — is replaced silently with
43
+ the current version.
44
+ - A **modified** copy — one with local edits — is never replaced automatically. It produces a
45
+ warning instead, and keeps producing it until the difference is resolved.
46
+
47
+ The explicit update command follows the same rule and can override it:
48
+
49
+ ```bash
50
+ fvtt-world-cli skill update
51
+ fvtt-world-cli skill update --force
52
+ ```
53
+
54
+ The CLI tells an old shipped version apart from a local edit by a content checksum recorded inside
55
+ every installed copy, so an outdated installation is never mistaken for a customized one.
56
+
57
+ ## Removing
58
+
59
+ ```bash
60
+ fvtt-world-cli skill remove
61
+ fvtt-world-cli skill remove --to <skills-directory>
62
+ ```
63
+
64
+ The default removal uninstalls the canonical copy, the agent links pointing at it, and every
65
+ location recorded for `--to` installations; `--to` removes one location and forgets it.
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "fvtt-world-cli",
3
+ "version": "1.0.0",
4
+ "description": "Safe local CLI and authenticated bridge for automating live Foundry VTT worlds.",
5
+ "license": "MIT",
6
+ "author": "DroiD16",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/DroiD16/fvtt-world-cli.git"
10
+ },
11
+ "homepage": "https://github.com/DroiD16/fvtt-world-cli#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/DroiD16/fvtt-world-cli/issues"
14
+ },
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "type": "module",
22
+ "bin": {
23
+ "fvtt-world-cli": "./bin/fvtt-world-cli.js",
24
+ "worldctl": "./bin/fvtt-world-cli.js"
25
+ },
26
+ "files": [
27
+ "bin/",
28
+ "dist/",
29
+ "docs/",
30
+ "skills/"
31
+ ],
32
+ "scripts": {
33
+ "test": "vitest run packages/cli/tests",
34
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --external:commander --external:pino --external:tsx --external:ws --external:zod --outfile=dist/index.js",
35
+ "typecheck": "tsc --project ../../tsconfig.json",
36
+ "prepack": "npm run build && node ./bin/prepare-package-assets.mjs",
37
+ "postpack": "node ./bin/prepare-package-assets.mjs --clean",
38
+ "postinstall": "node ./bin/sync-installed-skill.mjs"
39
+ },
40
+ "dependencies": {
41
+ "commander": "^13.1.0",
42
+ "pino": "^9.6.0",
43
+ "tsx": "^4.23.12",
44
+ "ws": "^8.21.0",
45
+ "zod": "^3.24.3"
46
+ },
47
+ "devDependencies": {
48
+ "@fvtt-world-cli/protocol": "1.0.0"
49
+ }
50
+ }
@@ -0,0 +1,156 @@
1
+ ---
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.
4
+ ---
5
+
6
+ # Foundry World Editor (fvtt-world-cli)
7
+
8
+ `fvtt-world-cli` talks to a local authenticated daemon that relays typed commands to a bridge
9
+ module running in an open GM Foundry browser session. Every mutation executes through Foundry's
10
+ own Document APIs inside that session. Treat the world as concurrent state: users, game systems,
11
+ modules, and hooks can change it between commands.
12
+
13
+ ## Health check
14
+
15
+ Confirm the runtime before planning any work:
16
+
17
+ ```bash
18
+ fvtt-world-cli system info --json
19
+ ```
20
+
21
+ Proceed only when `.result.bridge.status` is `connected`, and verify the reported world and GM are
22
+ the intended targets. If the daemon is not running, start `fvtt-world-cli bridge serve` as a
23
+ persistent background process (it does not daemonize itself) and leave it running for the whole
24
+ session. If no bridge connects, a GM must have the target world open in a browser with the
25
+ `fvtt-world-cli` module enabled and paired.
26
+
27
+ Pairing is a first-run trust decision that needs the human: ask the GM to open Module Settings →
28
+ Authorization, name the browser in the `Browser label` field, and choose Pair; run
29
+ `fvtt-world-cli auth pending`, confirm the listed Origin, world, GM, browser label, and client id,
30
+ then `auth approve <code> --yes`. Never run bare `fvtt-world-cli auth`: that is the human's
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
33
+ belongs to one browser, so the same GM on the same world can hold several records, and the label is
34
+ chosen in the browser and fixed once paired. Labels are not unique and can render as nothing, so the
35
+ client id is what tells concurrent requests apart and confirms that a re-pair replaces the record you
36
+ meant; on a first pair it is newly minted, with nothing to match it against. Non-interactive use
37
+ requires `--yes`, which prints no identity and skips the confirmation prompt: read all five fields
38
+ from `auth pending` first, and never approve a request whose Origin, world, or GM you did not
39
+ expect.
40
+
41
+ `BRIDGE_NOT_READY` means the daemon is reachable but no GM client is connected yet. `BRIDGE_BUSY`
42
+ means another paired browser holds the active bridge slot: clear it with
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.
45
+
46
+ ## Hard rules
47
+
48
+ - 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.
51
+ - Address documents by `id`, never by name; names are not unique. Embedded ids are meaningful only
52
+ with their complete parent chain (actor → item → effect, scene → token → item).
53
+ - Ownership changes go through dedicated `*.ownership.set` commands; raw `ownership` is rejected
54
+ 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
+
59
+ ## The working loop
60
+
61
+ Discover → inspect schema → locate → read → smallest patch → dry-run → commit → verify. Never
62
+ guess a command name or parameter.
63
+
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.
68
+ 2. `fvtt-world-cli schema <command>` shows the exact request parameters, required fields, enums,
69
+ and whether unknown fields are accepted; `fvtt-world-cli <command path> --help` maps flags.
70
+ 3. Locate targets with the narrowest query: a family `list --name <substring> --limit <n>` when
71
+ the family is known, `world.search` when it is not, `compendium.index` for one pack. Page with
72
+ `limit`/`offset` until `hasMore` is false. List and search rows are lean discovery projections,
73
+ never the basis of a write.
74
+ 4. `get` the complete target before mutating and work from the returned ids.
75
+ 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
79
+ stays, change only the intended part.
80
+ 6. Dry-run every nontrivial mutation with the global `--dry-run` flag. It runs the same
81
+ validation, sanitization, permission, capability, and security checks, then stops before
82
+ persistence; check both `ok` and `.result.dryRun`. A preview reports only what is knowable
83
+ before execution and reserves nothing.
84
+ 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.
86
+ 8. Verify with a fresh read. This matters most after open-schema writes, bulk operations, and
87
+ actions, where confirmation can say less than an observed post-state.
88
+
89
+ The whole loop in miniature:
90
+
91
+ ```bash
92
+ fvtt-world-cli actor list --name "Goblin" --limit 10 --json
93
+ fvtt-world-cli actor get --actor-id <id> --json
94
+ fvtt-world-cli --dry-run actor update --actor-id <id> --name "Goblin Scout" --json
95
+ fvtt-world-cli actor update --actor-id <id> --name "Goblin Scout" --json
96
+ fvtt-world-cli actor get --actor-id <id> --json
97
+ ```
98
+
99
+ ## Reading results
100
+
101
+ Success responses use `{ok: true, result}` and failures `{ok: false, error: {code, message,
102
+ details}}`, alongside `protocolVersion`, `type`, and the correlation `id` (echoing your `id` in
103
+ `exec --stdin` batches). Branch on `error.code`, never on message text.
104
+ Documents live under a type-named key inside `result` that varies between commands but is stable
105
+ 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
107
+ update schema explicitly accepts them.
108
+
109
+ ## Retry classification
110
+
111
+ Classify a failure before reacting:
112
+
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.
122
+
123
+ ## Bulk writes and actions
124
+
125
+ Bulk commands (`create-many`, `update-many`, `delete-many`) share single-command validation but
126
+ are not transactions: check the top-level `complete` flag and inspect every entry in `outcomes`.
127
+ Persisted statuses such as `created`, `updated`, and `deleted` mean something different from
128
+ `unchanged`, `alreadyDeleted`, `dropped`, and `unknown`; the authoritative status set lives in the
129
+ protocol constants, not here. Action commands
130
+ (playback, draws, deals, combat transitions, thumbnails, fog) call fixed typed Foundry methods and
131
+ can trigger hooks, chat output, and system automation; read the schema, dry-run first, and
132
+ interpret confirmation fields literally rather than assuming an ordinary update happened.
133
+
134
+ ## Managed files
135
+
136
+ File commands address Foundry's managed `data` source only, and writes are confined to the active
137
+ world's allowed tree. A file mutation never rewrites document fields: upload or move the asset,
138
+ take the normalized returned path, update each document reference with an explicit document
139
+ command, verify, and only then delete the old file. `world.audit-files` finds references to
140
+ missing assets; it is not an orphan-file collector.
141
+
142
+ ## Context hygiene
143
+
144
+ Large worlds overflow agent context fast. Use server-side filters and small pages, project
145
+ responses with targeted `jq` expressions, fetch bounded id sets with `get-many`, and send
146
+ independent batches through `exec --stdin` (one NDJSON response per request, each judged on its
147
+ own). Request derived fields only when needed, and never dump a whole world, search index, or
148
+ binary file into context.
149
+
150
+ ## Deeper reference
151
+
152
+ `fvtt-world-cli docs` lists the durable contract documents shipped with the CLI, and
153
+ `fvtt-world-cli docs <name>` prints one: `commands` (human overview and workflows), `protocol`
154
+ (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.
@@ -0,0 +1 @@
1
+ 9551cf818565ba2d70903bb4a28425b233c2b13cbc93625d61b37eb76ef0e0c4