fvtt-world-cli 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +131 -23
- package/docs/commands.md +15 -6
- package/docs/compatibility.md +1 -0
- package/docs/protocol.md +33 -9
- package/package.json +2 -2
- package/skills/foundry-world-editor/SKILL.md +17 -8
- package/skills/foundry-world-editor/SKILL.md.sha256 +1 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { readFileSync as readFileSync2 } from "node:fs";
|
|
|
10
10
|
import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
11
11
|
|
|
12
12
|
// ../protocol/src/constants.js
|
|
13
|
-
var PROTOCOL_VERSION = "1.2.
|
|
13
|
+
var PROTOCOL_VERSION = "1.2.1";
|
|
14
14
|
var PROTOCOL_COMPONENTS = Object.freeze({
|
|
15
15
|
MODULE: "module",
|
|
16
16
|
CLI_DAEMON: "cli-daemon",
|
|
@@ -312,7 +312,10 @@ var batchIdsProperty = {
|
|
|
312
312
|
}
|
|
313
313
|
};
|
|
314
314
|
var tokenIncludeProperty = {
|
|
315
|
-
include: { type: "array", items: { type: "string", enum: ["prepared"] } }
|
|
315
|
+
include: { type: "array", items: { type: "string", enum: ["prepared", "delta"] } }
|
|
316
|
+
};
|
|
317
|
+
var tokenListIncludeProperty = {
|
|
318
|
+
include: { type: "array", items: { type: "string", enum: ["delta"] } }
|
|
316
319
|
};
|
|
317
320
|
var compendiumIncludeProperty = {
|
|
318
321
|
include: { type: "array", items: { type: "string", enum: ["effects"] } }
|
|
@@ -1757,6 +1760,16 @@ var compendiumCommands = {
|
|
|
1757
1760
|
...compendiumIncludeProperty
|
|
1758
1761
|
},
|
|
1759
1762
|
additionalProperties: false
|
|
1763
|
+
}),
|
|
1764
|
+
"compendium.get-many": cmd({
|
|
1765
|
+
type: "object",
|
|
1766
|
+
required: ["pack", "ids"],
|
|
1767
|
+
properties: {
|
|
1768
|
+
pack: { type: "string", minLength: 1 },
|
|
1769
|
+
...batchIdsProperty,
|
|
1770
|
+
...compendiumIncludeProperty
|
|
1771
|
+
},
|
|
1772
|
+
additionalProperties: false
|
|
1760
1773
|
})
|
|
1761
1774
|
};
|
|
1762
1775
|
|
|
@@ -3223,7 +3236,8 @@ var sceneEmbeddedCommands = {
|
|
|
3223
3236
|
properties: {
|
|
3224
3237
|
sceneId: { type: "string", minLength: 1 },
|
|
3225
3238
|
...nameFilterProperty,
|
|
3226
|
-
...paginationProperties
|
|
3239
|
+
...paginationProperties,
|
|
3240
|
+
...tokenListIncludeProperty
|
|
3227
3241
|
},
|
|
3228
3242
|
additionalProperties: false
|
|
3229
3243
|
}),
|
|
@@ -3293,6 +3307,19 @@ var sceneEmbeddedCommands = {
|
|
|
3293
3307
|
},
|
|
3294
3308
|
{ mutation: true }
|
|
3295
3309
|
),
|
|
3310
|
+
"scene.token.restore-delta": cmd(
|
|
3311
|
+
{
|
|
3312
|
+
type: "object",
|
|
3313
|
+
required: ["sceneId", "tokenId"],
|
|
3314
|
+
properties: {
|
|
3315
|
+
sceneId: { type: "string", minLength: 1 },
|
|
3316
|
+
tokenId: { type: "string", minLength: 1 },
|
|
3317
|
+
...dryRunProperty
|
|
3318
|
+
},
|
|
3319
|
+
additionalProperties: false
|
|
3320
|
+
},
|
|
3321
|
+
{ mutation: true }
|
|
3322
|
+
),
|
|
3296
3323
|
...batchWriteCommands("scene.token", {
|
|
3297
3324
|
scopeProperties: sceneScopeProperties,
|
|
3298
3325
|
createSchema: tokenCreateSchema,
|
|
@@ -5356,6 +5383,7 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
|
|
|
5356
5383
|
"scene.token.update": "allow",
|
|
5357
5384
|
"scene.token.delete": "approve",
|
|
5358
5385
|
"scene.token.clone": "allow",
|
|
5386
|
+
"scene.token.restore-delta": "approve",
|
|
5359
5387
|
"scene.token.create-many": "allow",
|
|
5360
5388
|
"scene.token.update-many": "allow",
|
|
5361
5389
|
"scene.token.delete-many": "approve",
|
|
@@ -5653,6 +5681,7 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
|
|
|
5653
5681
|
"compendium.list": "allow",
|
|
5654
5682
|
"compendium.index": "allow",
|
|
5655
5683
|
"compendium.get": "allow",
|
|
5684
|
+
"compendium.get-many": "allow",
|
|
5656
5685
|
"folder.list": "allow",
|
|
5657
5686
|
"folder.get": "allow",
|
|
5658
5687
|
"folder.create": "allow",
|
|
@@ -5682,7 +5711,12 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
|
|
|
5682
5711
|
|
|
5683
5712
|
// ../protocol/src/command-risk.js
|
|
5684
5713
|
var DESTRUCTIVE_VERBS = Object.freeze(["delete", "delete-many"]);
|
|
5685
|
-
var DESTRUCTIVE_COMMANDS = Object.freeze([
|
|
5714
|
+
var DESTRUCTIVE_COMMANDS = Object.freeze([
|
|
5715
|
+
"chat.flush",
|
|
5716
|
+
"file.move",
|
|
5717
|
+
"scene.fog.reset",
|
|
5718
|
+
"scene.token.restore-delta"
|
|
5719
|
+
]);
|
|
5686
5720
|
var APPROVE_EXTRA_COMMANDS = Object.freeze(["system.reload", "user.create"]);
|
|
5687
5721
|
var DENIED_BY_DEFAULT_COMMANDS = Object.freeze([
|
|
5688
5722
|
"macro.execute",
|
|
@@ -8833,7 +8867,10 @@ function parseActorIncludeFields(value) {
|
|
|
8833
8867
|
]);
|
|
8834
8868
|
}
|
|
8835
8869
|
function parseTokenIncludeFields(value) {
|
|
8836
|
-
return parseAllowedIncludeFields(value, ["prepared"]);
|
|
8870
|
+
return parseAllowedIncludeFields(value, ["prepared", "delta"]);
|
|
8871
|
+
}
|
|
8872
|
+
function parseTokenListIncludeFields(value) {
|
|
8873
|
+
return parseAllowedIncludeFields(value, ["delta"]);
|
|
8837
8874
|
}
|
|
8838
8875
|
function parseSystemInfoIncludeFields(value) {
|
|
8839
8876
|
return parseAllowedIncludeFields(value, ["modules"]);
|
|
@@ -10366,8 +10403,11 @@ function renderBroadcastLines(result) {
|
|
|
10366
10403
|
`dispatched: ${Boolean(result?.dispatched)} \u2014 a broadcast writes nothing, so there is no state to confirm afterwards`
|
|
10367
10404
|
];
|
|
10368
10405
|
}
|
|
10369
|
-
function
|
|
10370
|
-
return
|
|
10406
|
+
function renderProvenanceLines(document) {
|
|
10407
|
+
return [
|
|
10408
|
+
...document?.compendiumSource ? [`compendiumSource: ${document.compendiumSource}`] : [],
|
|
10409
|
+
...document?.duplicateSource ? [`duplicateSource: ${document.duplicateSource}`] : []
|
|
10410
|
+
];
|
|
10371
10411
|
}
|
|
10372
10412
|
function renderOwnershipLines(ownership) {
|
|
10373
10413
|
if (!ownership || typeof ownership !== "object") {
|
|
@@ -10479,7 +10519,7 @@ function renderItemDetails(item, prefixLines = []) {
|
|
|
10479
10519
|
if (item && Object.hasOwn(item, "effects")) {
|
|
10480
10520
|
lines.push(`effects: ${JSON.stringify(item.effects ?? [], null, 2)}`);
|
|
10481
10521
|
}
|
|
10482
|
-
lines.push(...
|
|
10522
|
+
lines.push(...renderProvenanceLines(item));
|
|
10483
10523
|
if (item && Object.hasOwn(item, "ownership")) {
|
|
10484
10524
|
lines.push(...renderOwnershipLines(item.ownership));
|
|
10485
10525
|
}
|
|
@@ -10505,7 +10545,7 @@ function renderActorDetails(actor) {
|
|
|
10505
10545
|
if (actor && Object.hasOwn(actor, "effects")) {
|
|
10506
10546
|
lines.push(`effects: ${JSON.stringify(actor.effects ?? [], null, 2)}`);
|
|
10507
10547
|
}
|
|
10508
|
-
lines.push(...
|
|
10548
|
+
lines.push(...renderProvenanceLines(actor));
|
|
10509
10549
|
if (actor && Object.hasOwn(actor, "ownership")) {
|
|
10510
10550
|
lines.push(...renderOwnershipLines(actor.ownership));
|
|
10511
10551
|
}
|
|
@@ -10551,7 +10591,7 @@ function renderCardsDetails(cards) {
|
|
|
10551
10591
|
`cards: ${rows.length}`,
|
|
10552
10592
|
...rows.map((card) => ` ${renderCardLine(card)}`)
|
|
10553
10593
|
];
|
|
10554
|
-
lines.push(...
|
|
10594
|
+
lines.push(...renderProvenanceLines(cards));
|
|
10555
10595
|
if (cards && Object.hasOwn(cards, "ownership")) {
|
|
10556
10596
|
lines.push(...renderOwnershipLines(cards.ownership));
|
|
10557
10597
|
}
|
|
@@ -11090,7 +11130,7 @@ function renderJournalDetails(journal) {
|
|
|
11090
11130
|
`sort: ${journal?.sort ?? 0}`,
|
|
11091
11131
|
`pages: ${pages.length}`
|
|
11092
11132
|
];
|
|
11093
|
-
lines.push(...
|
|
11133
|
+
lines.push(...renderProvenanceLines(journal));
|
|
11094
11134
|
if (journal && Object.hasOwn(journal, "ownership")) {
|
|
11095
11135
|
lines.push(...renderOwnershipLines(journal.ownership));
|
|
11096
11136
|
}
|
|
@@ -11206,7 +11246,7 @@ function renderMacroDetails(macro) {
|
|
|
11206
11246
|
`flags: ${JSON.stringify(macro?.flags ?? {}, null, 2)}`,
|
|
11207
11247
|
`command: ${macro?.command ?? ""}`
|
|
11208
11248
|
];
|
|
11209
|
-
lines.push(...
|
|
11249
|
+
lines.push(...renderProvenanceLines(macro));
|
|
11210
11250
|
if (macro && Object.hasOwn(macro, "ownership")) {
|
|
11211
11251
|
lines.push(...renderOwnershipLines(macro.ownership));
|
|
11212
11252
|
}
|
|
@@ -11229,7 +11269,7 @@ function renderPlaylistDetails(playlist) {
|
|
|
11229
11269
|
`flags: ${JSON.stringify(playlist?.flags ?? {}, null, 2)}`,
|
|
11230
11270
|
`sounds: ${playlist?.sounds?.length ?? 0}`
|
|
11231
11271
|
];
|
|
11232
|
-
lines.push(...
|
|
11272
|
+
lines.push(...renderProvenanceLines(playlist));
|
|
11233
11273
|
if (playlist && Object.hasOwn(playlist, "ownership")) {
|
|
11234
11274
|
lines.push(...renderOwnershipLines(playlist.ownership));
|
|
11235
11275
|
}
|
|
@@ -11288,7 +11328,7 @@ function renderSceneDetails(scene) {
|
|
|
11288
11328
|
for (const level of Array.isArray(scene?.levels) ? scene.levels : []) {
|
|
11289
11329
|
lines.push(`level: ${renderLevelRow(level)}`);
|
|
11290
11330
|
}
|
|
11291
|
-
lines.push(...
|
|
11331
|
+
lines.push(...renderProvenanceLines(scene));
|
|
11292
11332
|
if (scene && Object.hasOwn(scene, "ownership")) {
|
|
11293
11333
|
lines.push(...renderOwnershipLines(scene.ownership));
|
|
11294
11334
|
}
|
|
@@ -11328,11 +11368,29 @@ function renderTokenDetails(sceneId, token) {
|
|
|
11328
11368
|
`disposition: ${token?.disposition ?? ""}`,
|
|
11329
11369
|
`texture: ${JSON.stringify(token?.texture ?? null)}`
|
|
11330
11370
|
];
|
|
11371
|
+
if (token && Object.hasOwn(token, "delta")) {
|
|
11372
|
+
lines.push(`delta: ${token.delta === null ? "none" : "stored (use --json for values)"}`);
|
|
11373
|
+
}
|
|
11331
11374
|
if (token && Object.hasOwn(token, "prepared")) {
|
|
11332
11375
|
lines.push(`prepared: ${Object.keys(token.prepared ?? {}).join(", ")} (use --json for values)`);
|
|
11333
11376
|
}
|
|
11334
11377
|
return lines.join("\n");
|
|
11335
11378
|
}
|
|
11379
|
+
function tokenDeltaSummaryCell(token) {
|
|
11380
|
+
if (!token || !Object.hasOwn(token, "deltaSummary")) return "";
|
|
11381
|
+
const summary = token.deltaSummary;
|
|
11382
|
+
if (summary === null) return " overrides=none";
|
|
11383
|
+
return ` overrides=${(summary.overriddenKeys ?? []).join(",")} (item entries ${summary.itemEntries}, effect entries ${summary.effectEntries})`;
|
|
11384
|
+
}
|
|
11385
|
+
function renderTokenDeltaRestore(result) {
|
|
11386
|
+
const discarded = result.discarded;
|
|
11387
|
+
const verb = result.dryRun ? "[dry-run] would discard" : "Discarded";
|
|
11388
|
+
return [
|
|
11389
|
+
`scene: ${result.sceneId}`,
|
|
11390
|
+
`token: ${result.tokenId}`,
|
|
11391
|
+
result.changed ? `${verb} token overrides: ${(discarded?.overriddenKeys ?? []).join(", ")} (item entries ${discarded?.itemEntries ?? 0}, effect entries ${discarded?.effectEntries ?? 0})` : "No token-level overrides; nothing to restore."
|
|
11392
|
+
].join("\n");
|
|
11393
|
+
}
|
|
11336
11394
|
function tokenItemPrefix(result) {
|
|
11337
11395
|
const lines = [
|
|
11338
11396
|
`scene: ${result.sceneId}`,
|
|
@@ -11617,7 +11675,7 @@ function renderTableDetails(table) {
|
|
|
11617
11675
|
`results: ${results.length}`,
|
|
11618
11676
|
...results.map((result) => ` ${renderTableResultLine(result)}`)
|
|
11619
11677
|
];
|
|
11620
|
-
lines.push(...
|
|
11678
|
+
lines.push(...renderProvenanceLines(table));
|
|
11621
11679
|
if (table && Object.hasOwn(table, "ownership")) {
|
|
11622
11680
|
lines.push(...renderOwnershipLines(table.ownership));
|
|
11623
11681
|
}
|
|
@@ -12407,6 +12465,17 @@ var RENDERERS = registerRenderers([
|
|
|
12407
12465
|
`document: ${JSON.stringify(result.document ?? null, null, 2)}`
|
|
12408
12466
|
].join("\n")
|
|
12409
12467
|
),
|
|
12468
|
+
customRenderer("compendium.get-many", (result) => {
|
|
12469
|
+
const entries = result.entries ?? [];
|
|
12470
|
+
return [
|
|
12471
|
+
`pack: ${result.pack}`,
|
|
12472
|
+
`Entries (${entries.length})`,
|
|
12473
|
+
...entries.map(
|
|
12474
|
+
(entry) => `${entry.entryId} ${entry.documentName ?? ""} ${entry.document?.name ?? ""}${Array.isArray(entry.effects) ? ` effects=${entry.effects.length}` : ""}`
|
|
12475
|
+
),
|
|
12476
|
+
"(use --json for the documents)"
|
|
12477
|
+
].join("\n");
|
|
12478
|
+
}),
|
|
12410
12479
|
listRenderer("folder.list", {
|
|
12411
12480
|
key: "folders",
|
|
12412
12481
|
empty: "No folders found.",
|
|
@@ -12540,13 +12609,14 @@ var RENDERERS = registerRenderers([
|
|
|
12540
12609
|
empty: "No tokens found.",
|
|
12541
12610
|
heading: "Tokens",
|
|
12542
12611
|
prefix: scenePrefix,
|
|
12543
|
-
row: (token) => `${token.id} ${token.name} actor=${token.actorId ?? "-"} linked=${String(Boolean(token.actorLink))} (${token.x},${token.y})${levelMembershipCell(token)}`
|
|
12612
|
+
row: (token) => `${token.id} ${token.name} actor=${token.actorId ?? "-"} linked=${String(Boolean(token.actorLink))} (${token.x},${token.y})${levelMembershipCell(token)}${tokenDeltaSummaryCell(token)}`
|
|
12544
12613
|
}),
|
|
12545
12614
|
detailRenderer(
|
|
12546
12615
|
["scene.token.get", "scene.token.create", "scene.token.update", "scene.token.clone"],
|
|
12547
12616
|
(result) => renderTokenDetails(result.sceneId, result.token)
|
|
12548
12617
|
),
|
|
12549
12618
|
deleteRenderer("scene.token.delete", "token", { suffix: sceneSuffix }),
|
|
12619
|
+
detailRenderer("scene.token.restore-delta", (result) => renderTokenDeltaRestore(result)),
|
|
12550
12620
|
listRenderer("scene.token.item.list", {
|
|
12551
12621
|
key: "items",
|
|
12552
12622
|
empty: "No items found.",
|
|
@@ -15684,11 +15754,18 @@ function registerCombat({ program, dependencies }) {
|
|
|
15684
15754
|
}
|
|
15685
15755
|
|
|
15686
15756
|
// src/commands/compendium.ts
|
|
15757
|
+
function addCompendiumIncludeOption(command) {
|
|
15758
|
+
return command.option(
|
|
15759
|
+
"--include <fields>",
|
|
15760
|
+
"Comma-separated extra fields to include (allowed: effects)",
|
|
15761
|
+
parseCompendiumIncludeFields
|
|
15762
|
+
);
|
|
15763
|
+
}
|
|
15687
15764
|
function registerCompendium({ program, dependencies }) {
|
|
15688
15765
|
const compendium = program.command("compendium").description("Foundry compendium pack commands (read)");
|
|
15689
15766
|
compendium.addHelpText(
|
|
15690
15767
|
"after",
|
|
15691
|
-
"\nResult key (--json): .result.packs[] (list), .result.entries[] (index), .result.document (get \u2014 Foundry's raw source object)."
|
|
15768
|
+
"\nResult key (--json): .result.packs[] (list), .result.entries[] (index), .result.document (get \u2014 Foundry's raw source object plus id, compendiumSource and duplicateSource), .result.entries[] (get-many \u2014 one get result per requested id, in request order)."
|
|
15692
15769
|
);
|
|
15693
15770
|
addPaginationOptions(compendium.command("list")).action(async function listCompendiums(options) {
|
|
15694
15771
|
await executeRemoteCommand({
|
|
@@ -15718,10 +15795,8 @@ function registerCompendium({ program, dependencies }) {
|
|
|
15718
15795
|
dependencies
|
|
15719
15796
|
});
|
|
15720
15797
|
});
|
|
15721
|
-
|
|
15722
|
-
"--
|
|
15723
|
-
"Comma-separated extra fields to include (allowed: effects)",
|
|
15724
|
-
parseCompendiumIncludeFields
|
|
15798
|
+
addCompendiumIncludeOption(
|
|
15799
|
+
compendium.command("get").requiredOption("--pack <pack>", "Compendium pack id").requiredOption("--entry-id <entryId>", "Compendium entry id")
|
|
15725
15800
|
).action(async function getCompendiumEntry(options) {
|
|
15726
15801
|
await executeRemoteCommand({
|
|
15727
15802
|
commandName: "compendium.get",
|
|
@@ -15734,6 +15809,20 @@ function registerCompendium({ program, dependencies }) {
|
|
|
15734
15809
|
dependencies
|
|
15735
15810
|
});
|
|
15736
15811
|
});
|
|
15812
|
+
addCompendiumIncludeOption(
|
|
15813
|
+
compendium.command("get-many").description("Read several entries of one compendium pack in a single call").requiredOption("--pack <pack>", "Compendium pack id").requiredOption("--ids <list>", "Comma-separated entry ids (atomic: all must exist)", parseIdList)
|
|
15814
|
+
).action(async function getManyCompendiumEntries(options) {
|
|
15815
|
+
await executeRemoteCommand({
|
|
15816
|
+
commandName: "compendium.get-many",
|
|
15817
|
+
params: {
|
|
15818
|
+
pack: options.pack,
|
|
15819
|
+
ids: options.ids,
|
|
15820
|
+
...options.include ? { include: options.include } : {}
|
|
15821
|
+
},
|
|
15822
|
+
command: this,
|
|
15823
|
+
dependencies
|
|
15824
|
+
});
|
|
15825
|
+
});
|
|
15737
15826
|
}
|
|
15738
15827
|
|
|
15739
15828
|
// src/commands/config.ts
|
|
@@ -17028,17 +17117,26 @@ function registerScene({ program, dependencies }) {
|
|
|
17028
17117
|
const sceneToken = scene.command("token").description("Scene-embedded token commands");
|
|
17029
17118
|
addNameFilterOption(
|
|
17030
17119
|
addPaginationOptions(sceneToken.command("list").requiredOption("--scene-id <sceneId>", "Scene id"))
|
|
17120
|
+
).option(
|
|
17121
|
+
"--include <fields>",
|
|
17122
|
+
"Comma-separated extra fields to include (allowed: delta \u2014 a derived per-token summary of the token-level overrides, null when there are none)",
|
|
17123
|
+
parseTokenListIncludeFields
|
|
17031
17124
|
).action(async function listSceneTokens(options) {
|
|
17032
17125
|
await executeRemoteCommand({
|
|
17033
17126
|
commandName: "scene.token.list",
|
|
17034
|
-
params: {
|
|
17127
|
+
params: {
|
|
17128
|
+
sceneId: options.sceneId,
|
|
17129
|
+
...nameFilterParams(options),
|
|
17130
|
+
...paginationParams(options),
|
|
17131
|
+
...options.include ? { include: options.include } : {}
|
|
17132
|
+
},
|
|
17035
17133
|
command: this,
|
|
17036
17134
|
dependencies
|
|
17037
17135
|
});
|
|
17038
17136
|
});
|
|
17039
17137
|
sceneToken.command("get").requiredOption("--scene-id <sceneId>", "Scene id").requiredOption("--token-id <tokenId>", "Token id").option(
|
|
17040
17138
|
"--include <fields>",
|
|
17041
|
-
"Comma-separated extra fields to include (allowed: prepared)",
|
|
17139
|
+
"Comma-separated extra fields to include (allowed: prepared, delta \u2014 the stored ActorDelta source, null for a linked token or when none is stored)",
|
|
17042
17140
|
parseTokenIncludeFields
|
|
17043
17141
|
).action(async function getSceneToken(options) {
|
|
17044
17142
|
await executeRemoteCommand({
|
|
@@ -17093,6 +17191,16 @@ function registerScene({ program, dependencies }) {
|
|
|
17093
17191
|
dependencies
|
|
17094
17192
|
});
|
|
17095
17193
|
});
|
|
17194
|
+
sceneToken.command("restore-delta").description(
|
|
17195
|
+
"Discard every token-level override of an unlinked token (items, effects, system data, name, \u2026) so it follows its world actor again"
|
|
17196
|
+
).requiredOption("--scene-id <sceneId>", "Scene id").requiredOption("--token-id <tokenId>", "Unlinked token id").action(async function restoreSceneTokenDelta(options) {
|
|
17197
|
+
await executeRemoteCommand({
|
|
17198
|
+
commandName: "scene.token.restore-delta",
|
|
17199
|
+
params: { sceneId: options.sceneId, tokenId: options.tokenId },
|
|
17200
|
+
command: this,
|
|
17201
|
+
dependencies
|
|
17202
|
+
});
|
|
17203
|
+
});
|
|
17096
17204
|
registerBatchWriteCommands(sceneToken, {
|
|
17097
17205
|
prefix: "scene.token",
|
|
17098
17206
|
noun: "token",
|
package/docs/commands.md
CHANGED
|
@@ -17,8 +17,9 @@ becomes `scene token get`. Nested commands require the parent IDs as well as the
|
|
|
17
17
|
|
|
18
18
|
With a bridge connected, discovery omits denied commands and marks approval requirements with
|
|
19
19
|
`approval: true`. If the daemon or bridge is unavailable, it returns the static registry with
|
|
20
|
-
`policy.applied: false
|
|
21
|
-
`schema` and `--help` always
|
|
20
|
+
`policy.applied: false`; `policy` sits at the top level of the response, beside `result`.
|
|
21
|
+
Authentication and protocol errors do not trigger that fallback. `schema` and `--help` always
|
|
22
|
+
describe the static registry, including denied commands.
|
|
22
23
|
|
|
23
24
|
## Capability map
|
|
24
25
|
|
|
@@ -164,13 +165,15 @@ Use JSON flags such as `--data-json` and `--patch-json` for structured values. U
|
|
|
164
165
|
|
|
165
166
|
- Nested objects merge recursively.
|
|
166
167
|
- Ordinary arrays replace as a whole.
|
|
167
|
-
- Dotted paths target object properties where permitted; dotted
|
|
168
|
-
|
|
168
|
+
- Dotted paths target object properties where permitted; a top-level dotted write into an array
|
|
169
|
+
field, such as `c.0`, is refused.
|
|
170
|
+
- Foundry's operator keys work at any permitted nested level: `{"-=key": null}` deletes the key,
|
|
171
|
+
and `{"==key": value}` replaces the key's whole value instead of merging into it.
|
|
169
172
|
- Embedded collections follow their family's Foundry semantics.
|
|
170
173
|
|
|
171
174
|
Read arrays before editing and preserve unchanged values. For a wall's `c` field, send the complete
|
|
172
|
-
`[x1, y1, x2, y2]`, not `c.0`.
|
|
173
|
-
mutation, including in bulk writes and dry runs. Correct the fields identified by the error.
|
|
175
|
+
`[x1, y1, x2, y2]`, not `c.0`. Array writes that Foundry would discard return `INVALID_PARAMS`
|
|
176
|
+
before mutation, including in bulk writes and dry runs. Correct the fields identified by the error.
|
|
174
177
|
|
|
175
178
|
Prefer dedicated commands for creating embedded documents. A parent update that adds entries
|
|
176
179
|
without `_id`, such as new `behaviors` in `scene.region.update`, can create them but return an
|
|
@@ -190,6 +193,12 @@ A timeout or disconnect can happen after a write commits. An update error with `
|
|
|
190
193
|
or `details.indeterminate` can also mean that some changes persisted. Read affected state before
|
|
191
194
|
retrying and send only what remains to be done.
|
|
192
195
|
|
|
196
|
+
An update whose stored state differs from the request fails with `INTERNAL_ERROR`, and
|
|
197
|
+
`details.paths` lists the differing paths with the requested and the stored value. Foundry core, the
|
|
198
|
+
game system or a module may have changed the value; Foundry stores a raw `&` in rich text as
|
|
199
|
+
`&`, for example. The bridge cannot tell which, so the caller judges from the values. Sending
|
|
200
|
+
the same patch again stores the same result.
|
|
201
|
+
|
|
193
202
|
An idempotency key identifies one logical request and can prevent duplicate effects while its
|
|
194
203
|
cached state survives. Reuse it only for the same request; a corrected or remaining patch is a
|
|
195
204
|
new operation and needs a fresh key. Follow the [delivery rules](protocol.md#delivery-states-and-retries)
|
package/docs/compatibility.md
CHANGED
|
@@ -11,6 +11,7 @@ The bridge supports Foundry VTT v13 and v14. Check your connected version with
|
|
|
11
11
|
| Scene thumbnail rendering | Renders the scene | Renders the scene's initial level by default | On v14, expect the initial level in the thumbnail. Check the returned dimensions and image. |
|
|
12
12
|
| Scene thumbnail files | Uses a stable scene-based filename | Uses a filename based on the image content | Use the returned path. On v14, a changed image can leave an older thumbnail file behind. |
|
|
13
13
|
| User permission names | `TEMPLATE_CREATE` | `REGION_CREATE` | Use the permission keys reported by the connected Foundry version when calling `user.permissions.set`. |
|
|
14
|
+
| Legacy update operators `-=key` (deletion) and `==key` (forced replacement) | Work without a warning | Work, and the GM browser console logs a deprecation warning for each such key, stating that support will be removed in Version 16 | Nothing changes for v13 or v14 writes. The bridge passes the operator keys through without translating them to Foundry 14's operator objects, so they will stop working once Foundry removes them. |
|
|
14
15
|
|
|
15
16
|
Scene placeable fields and available region behavior types also differ. Read the target document
|
|
16
17
|
before preparing a patch; valid data from one version is not automatically valid on the other.
|
package/docs/protocol.md
CHANGED
|
@@ -181,6 +181,9 @@ filters apply before pagination. Previewed documents have no persistent identity
|
|
|
181
181
|
observed during a preview must not be reused. Some result fields depend on the connected Foundry
|
|
182
182
|
version; [Foundry compatibility](compatibility.md) lists them.
|
|
183
183
|
|
|
184
|
+
World document reads carry `compendiumSource` and `duplicateSource`, hoisted from Foundry's
|
|
185
|
+
`_stats` and `null` when absent.
|
|
186
|
+
|
|
184
187
|
Broadcast actions report `dispatched` and target users rather than a confirmed player-side result.
|
|
185
188
|
The active/inactive split is included where knowable.
|
|
186
189
|
|
|
@@ -196,10 +199,12 @@ world state when the return value does not establish the effect.
|
|
|
196
199
|
|
|
197
200
|
### Setting results
|
|
198
201
|
|
|
199
|
-
Setting writes return the observed `value` and its `previous` value
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
Setting writes return the observed `value` and its `previous` value, as `game.settings.get`
|
|
203
|
+
returns them after the registered type's cast. Confirmation compares the JSON Foundry stores with
|
|
204
|
+
the JSON of the validated request; a different stored value, for example one rewritten by an
|
|
205
|
+
`onChange` callback, is a mismatch error with a `paths` entry for `value`. Writing an
|
|
206
|
+
already-stored value succeeds as unchanged without calling Foundry. `requiresReload: true`
|
|
207
|
+
indicates that the GM client needs a reload.
|
|
203
208
|
|
|
204
209
|
## Write confirmation
|
|
205
210
|
|
|
@@ -207,11 +212,24 @@ Document writes report success only after confirmation. Foundry can resolve a ve
|
|
|
207
212
|
write without throwing, and hooks can remove or rewrite part of a patch. Single and bulk updates
|
|
208
213
|
pass Foundry a private copy, then compare stored state with the original request.
|
|
209
214
|
|
|
215
|
+
Confirmation compares each requested leaf path that survives Foundry's cleaning with the stored
|
|
216
|
+
source. Foundry drops a key the document's schema does not declare without an error before that
|
|
217
|
+
comparison, so a write made only of undeclared keys reports success with nothing stored. A deletion (`-=key` or
|
|
218
|
+
Foundry 14's `ForcedDeletion`) is confirmed when the key is absent. A forced replacement (`==key`
|
|
219
|
+
or Foundry 14's `ForcedReplacement`) is confirmed when the whole stored value at that path equals the requested
|
|
220
|
+
value after Foundry's cleaning.
|
|
221
|
+
|
|
210
222
|
A valid patch whose requested state is already stored remains a successful no-op. A validation
|
|
211
223
|
failure recovered after an unwritten update returns `INVALID_PARAMS`. Unconfirmed, partial, or
|
|
212
224
|
indeterminate updates can return `INTERNAL_ERROR` with these details:
|
|
213
225
|
|
|
214
|
-
- `fields`: requested fields whose state was not confirmed.
|
|
226
|
+
- `fields`: requested top-level fields whose state was not confirmed.
|
|
227
|
+
- `paths`: up to 20 entries `{ path, requested, stored }`, one per leaf path where the stored value
|
|
228
|
+
differs from the request. `absent: "stored"` replaces `stored` when the key is missing from the
|
|
229
|
+
stored source, and `absent: "requested"` replaces `requested` for a deletion that did not land.
|
|
230
|
+
A long string, or an object or array whose JSON is long, is shortened to a window around the
|
|
231
|
+
first differing character; `truncated` then carries `format` (`text` or `json`), `offset`, and
|
|
232
|
+
the full `requestedLength` and `storedLength`. `pathsTruncated` counts the entries beyond 20.
|
|
215
233
|
- `partial: true`: stored data changed in a requested field, but the full requested state was
|
|
216
234
|
not reached. A hook may have applied only part of a nested object or replaced a value.
|
|
217
235
|
- `changedFields`: requested top-level fields observed to change, including fields that changed
|
|
@@ -221,6 +239,12 @@ indeterminate updates can return `INTERNAL_ERROR` with these details:
|
|
|
221
239
|
- `indeterminate: true`: confirmation could not establish the outcome. Some or all of the write
|
|
222
240
|
may have persisted. `changedFields` is `null` when the before/after comparison was unavailable.
|
|
223
241
|
|
|
242
|
+
When stored differs from requested, the message says that the bridge cannot verify whether Foundry
|
|
243
|
+
core, the game system or a module normalized the value, or whether a hook rewrote, dropped or vetoed
|
|
244
|
+
the change. A blocking module is listed as one possible cause, never as the diagnosis. Resending the
|
|
245
|
+
same patch does not change the result. Every confirmed write that can report a mismatch carries the
|
|
246
|
+
same `paths`, and so does a `dropped` bulk outcome when the stored state was compared.
|
|
247
|
+
|
|
224
248
|
Parent patches that create embedded entries without `_id`, such as new `behaviors` in
|
|
225
249
|
`scene.region.update`, return an indeterminate error after writing because parent update results
|
|
226
250
|
cannot confirm those creations. A retry may duplicate them. Read the embedded collection first,
|
|
@@ -231,10 +255,10 @@ new operation with a fresh idempotency key if using one.
|
|
|
231
255
|
|
|
232
256
|
### Patch shape checks
|
|
233
257
|
|
|
234
|
-
Single updates, bulk updates, and dry runs reject
|
|
235
|
-
array
|
|
236
|
-
|
|
237
|
-
|
|
258
|
+
Single updates, bulk updates, and dry runs reject invalid or discarded array values, top-level
|
|
259
|
+
dotted writes into array fields, and writes to legacy fields the running core moved, with
|
|
260
|
+
`INVALID_PARAMS` before mutation. Send whole arrays; ordinary dotted object-property writes remain
|
|
261
|
+
supported where the schema permits them. Other key spellings reach Foundry as written.
|
|
238
262
|
|
|
239
263
|
Errors identify the `field` and may include `arrayField`, `requested`, `stored`, or migration
|
|
240
264
|
destination details. Bulk failures add the entry's `index` and `id`; single failures use document
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fvtt-world-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Safe local CLI and authenticated bridge for automating live Foundry VTT worlds.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foundry-vtt",
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
"zod": "^3.24.3"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@fvtt-world-cli/protocol": "1.2.
|
|
59
|
+
"@fvtt-world-cli/protocol": "1.2.1"
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -80,6 +80,9 @@ level ids in a preview are provisional.
|
|
|
80
80
|
Deleting a level also deletes its tokens and every placeable only on that level, and the dry run
|
|
81
81
|
does not list them: read the scene's placeables (token `level`, others' `levels`) and tell the user
|
|
82
82
|
what the delete removes before running it.
|
|
83
|
+
An unlinked token keeps its own overrides, which edits to its world actor never reach. Before
|
|
84
|
+
editing an actor that has placed tokens, read its tokens' overrides and tell the user which tokens
|
|
85
|
+
the edit will not change.
|
|
83
86
|
`setting.set` cannot touch this module's own namespace (`SETTING_PROTECTED` is final; never retry).
|
|
84
87
|
When a setting write returns `requiresReload: true`, the change needs a GM-client reload to take
|
|
85
88
|
effect; `system.reload` performs it, drops the bridge, and requires a reconnect wait before the
|
|
@@ -92,12 +95,13 @@ signal, not a failure — reconnect and continue rather than retrying the reload
|
|
|
92
95
|
Discover → inspect schema → locate → read → smallest patch → dry-run → commit → verify. Never
|
|
93
96
|
guess a command name or parameter.
|
|
94
97
|
|
|
95
|
-
1. Run `fvtt-world-cli commands --json`. Continue only when `policy.applied`
|
|
96
|
-
required command is present. A `false` value means no bridge
|
|
97
|
-
registry, not the client's permissions. Report an absent
|
|
98
|
-
to produce the same effect. Warn the user before a
|
|
99
|
-
one `*-many` command when it can replace several
|
|
100
|
-
CLI subcommands, such as `actor.item.update` to
|
|
98
|
+
1. Run `fvtt-world-cli commands --json`. Continue only when `policy.applied` (top level, beside
|
|
99
|
+
`result`) is `true` and the required command is present. A `false` value means no bridge
|
|
100
|
+
answered and the output is the static registry, not the client's permissions. Report an absent
|
|
101
|
+
command instead of seeking another way to produce the same effect. Warn the user before a
|
|
102
|
+
command marked `"approval": true`, and prefer one `*-many` command when it can replace several
|
|
103
|
+
approvals. Dotted protocol names map to spaced CLI subcommands, such as `actor.item.update` to
|
|
104
|
+
`actor item update`.
|
|
101
105
|
2. `fvtt-world-cli schema <command>` shows the exact request parameters, required fields, enums,
|
|
102
106
|
and whether unknown fields are accepted; `fvtt-world-cli <command path> --help` maps flags.
|
|
103
107
|
3. Locate targets with the narrowest query: a family `list --name <substring> --limit <n>` when
|
|
@@ -106,8 +110,9 @@ guess a command name or parameter.
|
|
|
106
110
|
never the basis of a write.
|
|
107
111
|
4. `get` the complete target before mutating and work from the returned ids.
|
|
108
112
|
5. Build the smallest valid patch. Nested objects merge recursively; plain arrays replace
|
|
109
|
-
wholesale; dotted paths reach permitted leaves;
|
|
110
|
-
`
|
|
113
|
+
wholesale; dotted paths reach permitted leaves; `{"-=key": null}` deletes a permitted key, and
|
|
114
|
+
`{"==key": value}` replaces its whole value instead of merging. A `get` result contains
|
|
115
|
+
read-only and derived fields, so build the patch from accepted fields only.
|
|
111
116
|
For arrays and extensible `system`/`flags` data: read, preserve everything that
|
|
112
117
|
stays, change only the intended part.
|
|
113
118
|
6. Dry-run every nontrivial mutation with the global `--dry-run` flag. It runs the same
|
|
@@ -171,6 +176,10 @@ Classify a failure before reacting:
|
|
|
171
176
|
observed to change, not fully applied nested values. An embedded creation without a requested
|
|
172
177
|
id may already exist despite an indeterminate error. Send only the remaining changes as a new
|
|
173
178
|
operation with a fresh idempotency key, if using one.
|
|
179
|
+
- An update `INTERNAL_ERROR` with `details.paths` is a stored-vs-requested mismatch. Compare
|
|
180
|
+
`requested` and `stored` for each path. A representation-only difference, such as HTML escaping (`&` stored as `&`) or
|
|
181
|
+
whitespace, can be accepted as done. Report any other difference to the user. The same patch
|
|
182
|
+
stores the same result, so the next request is either a different patch or none.
|
|
174
183
|
- Correct a structured Foundry rejection and submit the corrected content as a new operation.
|
|
175
184
|
|
|
176
185
|
## Bulk writes and actions
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5b4188ba39ed8f9f1c041cec9c5f0c3fb5d05db7910670d0ff2bb9c5d7d43dde
|