fvtt-world-cli 1.2.0 → 1.3.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/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.0";
13
+ var PROTOCOL_VERSION = "1.3.0";
14
14
  var PROTOCOL_COMPONENTS = Object.freeze({
15
15
  MODULE: "module",
16
16
  CLI_DAEMON: "cli-daemon",
@@ -76,6 +76,9 @@ function resolveEffectiveLimits(uploadLimitBytes = DEFAULT_UPLOAD_SIZE_LIMIT_BYT
76
76
  wsMaxPayloadBytes: Math.max(wsMaxPayloadForUploadLimit(uploadLimitBytes), DEFAULT_WS_MAX_PAYLOAD_BYTES)
77
77
  };
78
78
  }
79
+ var BROWSER_STRING_MAX_LENGTH = 536870888;
80
+ var READ_RESPONSE_ENVELOPE_RESERVE_CHARS = 1024 * 1024;
81
+ var READ_ENCODING_CEILING_BYTES = Math.floor((BROWSER_STRING_MAX_LENGTH - READ_RESPONSE_ENVELOPE_RESERVE_CHARS) / 4) * 3;
79
82
  var BATCH_GET_MAX_IDS = 100;
80
83
  var BATCH_WRITE_MAX_ITEMS = 100;
81
84
  var BATCH_WRITE_STATUSES = Object.freeze([
@@ -102,6 +105,7 @@ var SCENE_THUMBNAIL_MAX_BYTES = 2 * 1024 * 1024;
102
105
  var SCENE_THUMBNAIL_RESPONSE_MAX_BYTES = 1024 * 1024;
103
106
  var TABLE_DRAW_MAX_COUNT = 100;
104
107
  var SETTING_VALUE_MAX_BYTES = 256 * 1024;
108
+ var SERIALIZE_OMISSIONS_MAX = 100;
105
109
  var MACRO_EXECUTE_TIMEOUT_DEFAULT_MS = 3e4;
106
110
  var MACRO_EXECUTE_TIMEOUT_MAX_MS = 3e5;
107
111
  var SEARCH_MODES = Object.freeze(["name", "full"]);
@@ -189,6 +193,7 @@ var ERROR_CODES = Object.freeze({
189
193
  PLAYLIST_NOT_FOUND: "PLAYLIST_NOT_FOUND",
190
194
  PLAYLIST_SOUND_NOT_FOUND: "PLAYLIST_SOUND_NOT_FOUND",
191
195
  JOURNAL_CATEGORY_NOT_FOUND: "JOURNAL_CATEGORY_NOT_FOUND",
196
+ JOURNAL_PAGE_NOT_FOUND: "JOURNAL_PAGE_NOT_FOUND",
192
197
  CHAT_MESSAGE_NOT_FOUND: "CHAT_MESSAGE_NOT_FOUND",
193
198
  TABLE_NOT_FOUND: "TABLE_NOT_FOUND",
194
199
  TABLE_RESULT_NOT_FOUND: "TABLE_RESULT_NOT_FOUND",
@@ -204,7 +209,6 @@ var ERROR_CODES = Object.freeze({
204
209
  FOLDER_NOT_FOUND: "FOLDER_NOT_FOUND",
205
210
  SETTING_NOT_FOUND: "SETTING_NOT_FOUND",
206
211
  SETTING_READ_FAILED: "SETTING_READ_FAILED",
207
- SETTING_VALUE_NOT_SERIALIZABLE: "SETTING_VALUE_NOT_SERIALIZABLE",
208
212
  SETTING_PROTECTED: "SETTING_PROTECTED",
209
213
  SETTING_UNREGISTERED: "SETTING_UNREGISTERED",
210
214
  MACRO_TIMEOUT: "MACRO_TIMEOUT",
@@ -312,7 +316,10 @@ var batchIdsProperty = {
312
316
  }
313
317
  };
314
318
  var tokenIncludeProperty = {
315
- include: { type: "array", items: { type: "string", enum: ["prepared"] } }
319
+ include: { type: "array", items: { type: "string", enum: ["prepared", "delta"] } }
320
+ };
321
+ var tokenListIncludeProperty = {
322
+ include: { type: "array", items: { type: "string", enum: ["delta"] } }
316
323
  };
317
324
  var compendiumIncludeProperty = {
318
325
  include: { type: "array", items: { type: "string", enum: ["effects"] } }
@@ -812,6 +819,11 @@ var actorCommands = {
812
819
  },
813
820
  { mutation: true }
814
821
  ),
822
+ ...batchWriteCommands("actor.item", {
823
+ scopeProperties: actorScopeProperties,
824
+ createSchema: embeddedItemDataSchema,
825
+ patchSchema: itemPatchSchema
826
+ }),
815
827
  ...batchWriteCommands("actor", {
816
828
  scopeProperties: emptyScopeProperties,
817
829
  patchSchema: actorPatchSchema,
@@ -1757,6 +1769,16 @@ var compendiumCommands = {
1757
1769
  ...compendiumIncludeProperty
1758
1770
  },
1759
1771
  additionalProperties: false
1772
+ }),
1773
+ "compendium.get-many": cmd({
1774
+ type: "object",
1775
+ required: ["pack", "ids"],
1776
+ properties: {
1777
+ pack: { type: "string", minLength: 1 },
1778
+ ...batchIdsProperty,
1779
+ ...compendiumIncludeProperty
1780
+ },
1781
+ additionalProperties: false
1760
1782
  })
1761
1783
  };
1762
1784
 
@@ -1797,6 +1819,22 @@ var auditFilesSchema = {
1797
1819
  },
1798
1820
  additionalProperties: false
1799
1821
  };
1822
+ var REFERENCE_SCOPES = deepFreeze([...AUDIT_FILE_SCOPES, "folder", "setting", "chat"]);
1823
+ var REFERENCE_DEFAULT_SCOPES = deepFreeze(REFERENCE_SCOPES.filter((scope) => scope !== "chat"));
1824
+ var worldReferencesSchema = {
1825
+ type: "object",
1826
+ required: ["uuid"],
1827
+ properties: {
1828
+ uuid: { type: "string", minLength: 1 },
1829
+ scope: {
1830
+ type: "array",
1831
+ minItems: 1,
1832
+ items: { type: "string", enum: [...REFERENCE_SCOPES] }
1833
+ },
1834
+ ...paginationProperties
1835
+ },
1836
+ additionalProperties: false
1837
+ };
1800
1838
  var fileCommands = {
1801
1839
  "file.list": cmd({
1802
1840
  type: "object",
@@ -1884,7 +1922,8 @@ var fileCommands = {
1884
1922
  )
1885
1923
  };
1886
1924
  var worldAuditCommands = {
1887
- "world.audit-files": cmd(auditFilesSchema)
1925
+ "world.audit-files": cmd(auditFilesSchema),
1926
+ "world.references": cmd(worldReferencesSchema)
1888
1927
  };
1889
1928
 
1890
1929
  // ../protocol/src/schemas/folder.js
@@ -2240,6 +2279,16 @@ var journalPageCreateSchema = {
2240
2279
  var journalPagePatchSchema = patchFrom(journalPageCreateSchema, {
2241
2280
  prepend: { id: { type: "string", minLength: 1 } }
2242
2281
  });
2282
+ var journalPageOwnPatchSchema = patchFrom(journalPageCreateSchema);
2283
+ var journalPageIdSchema = {
2284
+ type: "object",
2285
+ required: ["journalId", "pageId"],
2286
+ properties: {
2287
+ journalId: { type: "string", minLength: 1 },
2288
+ pageId: { type: "string", minLength: 1 }
2289
+ },
2290
+ additionalProperties: false
2291
+ };
2243
2292
  var journalDataSchema = {
2244
2293
  type: "object",
2245
2294
  required: ["name"],
@@ -2407,6 +2456,58 @@ var journalCommands = {
2407
2456
  patchSchema: journalDocumentPatchSchema,
2408
2457
  verbs: ["update-many", "delete-many"]
2409
2458
  }),
2459
+ "journal.page.list": cmd({
2460
+ type: "object",
2461
+ required: ["journalId"],
2462
+ properties: {
2463
+ journalId: { type: "string", minLength: 1 },
2464
+ ...nameFilterProperty,
2465
+ ...paginationProperties
2466
+ },
2467
+ additionalProperties: false
2468
+ }),
2469
+ "journal.page.get": cmd(journalPageIdSchema),
2470
+ "journal.page.create": cmd(
2471
+ {
2472
+ type: "object",
2473
+ required: ["journalId", "data"],
2474
+ properties: {
2475
+ journalId: { type: "string", minLength: 1 },
2476
+ data: journalPageCreateSchema,
2477
+ ...dryRunProperty,
2478
+ ...idempotencyKeyProperty
2479
+ },
2480
+ additionalProperties: false
2481
+ },
2482
+ { mutation: true }
2483
+ ),
2484
+ "journal.page.update": cmd(
2485
+ {
2486
+ type: "object",
2487
+ required: ["journalId", "pageId", "patch"],
2488
+ properties: {
2489
+ journalId: { type: "string", minLength: 1 },
2490
+ pageId: { type: "string", minLength: 1 },
2491
+ patch: journalPageOwnPatchSchema,
2492
+ ...dryRunProperty
2493
+ },
2494
+ additionalProperties: false
2495
+ },
2496
+ { mutation: true }
2497
+ ),
2498
+ "journal.page.delete": cmd(
2499
+ {
2500
+ type: "object",
2501
+ required: ["journalId", "pageId"],
2502
+ properties: {
2503
+ journalId: { type: "string", minLength: 1 },
2504
+ pageId: { type: "string", minLength: 1 },
2505
+ ...dryRunProperty
2506
+ },
2507
+ additionalProperties: false
2508
+ },
2509
+ { mutation: true }
2510
+ ),
2410
2511
  "journal.category.list": cmd({
2411
2512
  type: "object",
2412
2513
  required: ["journalId"],
@@ -3223,7 +3324,8 @@ var sceneEmbeddedCommands = {
3223
3324
  properties: {
3224
3325
  sceneId: { type: "string", minLength: 1 },
3225
3326
  ...nameFilterProperty,
3226
- ...paginationProperties
3327
+ ...paginationProperties,
3328
+ ...tokenListIncludeProperty
3227
3329
  },
3228
3330
  additionalProperties: false
3229
3331
  }),
@@ -3293,6 +3395,19 @@ var sceneEmbeddedCommands = {
3293
3395
  },
3294
3396
  { mutation: true }
3295
3397
  ),
3398
+ "scene.token.restore-delta": cmd(
3399
+ {
3400
+ type: "object",
3401
+ required: ["sceneId", "tokenId"],
3402
+ properties: {
3403
+ sceneId: { type: "string", minLength: 1 },
3404
+ tokenId: { type: "string", minLength: 1 },
3405
+ ...dryRunProperty
3406
+ },
3407
+ additionalProperties: false
3408
+ },
3409
+ { mutation: true }
3410
+ ),
3296
3411
  ...batchWriteCommands("scene.token", {
3297
3412
  scopeProperties: sceneScopeProperties,
3298
3413
  createSchema: tokenCreateSchema,
@@ -5356,6 +5471,7 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
5356
5471
  "scene.token.update": "allow",
5357
5472
  "scene.token.delete": "approve",
5358
5473
  "scene.token.clone": "allow",
5474
+ "scene.token.restore-delta": "approve",
5359
5475
  "scene.token.create-many": "allow",
5360
5476
  "scene.token.update-many": "allow",
5361
5477
  "scene.token.delete-many": "approve",
@@ -5506,6 +5622,11 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
5506
5622
  "journal.ownership.set": "allow",
5507
5623
  "journal.update-many": "allow",
5508
5624
  "journal.delete-many": "approve",
5625
+ "journal.page.list": "allow",
5626
+ "journal.page.get": "allow",
5627
+ "journal.page.create": "allow",
5628
+ "journal.page.update": "allow",
5629
+ "journal.page.delete": "approve",
5509
5630
  "journal.category.list": "allow",
5510
5631
  "journal.category.get": "allow",
5511
5632
  "journal.category.create": "allow",
@@ -5622,6 +5743,9 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
5622
5743
  "actor.item.delete": "approve",
5623
5744
  "actor.item.clone": "allow",
5624
5745
  "actor.item.import-from-compendium": "allow",
5746
+ "actor.item.create-many": "allow",
5747
+ "actor.item.update-many": "allow",
5748
+ "actor.item.delete-many": "approve",
5625
5749
  "actor.update-many": "allow",
5626
5750
  "actor.delete-many": "approve",
5627
5751
  "actor.effect.list": "allow",
@@ -5653,6 +5777,7 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
5653
5777
  "compendium.list": "allow",
5654
5778
  "compendium.index": "allow",
5655
5779
  "compendium.get": "allow",
5780
+ "compendium.get-many": "allow",
5656
5781
  "folder.list": "allow",
5657
5782
  "folder.get": "allow",
5658
5783
  "folder.create": "allow",
@@ -5674,6 +5799,7 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
5674
5799
  "setting.set-many": "deny",
5675
5800
  "actor.import-from-compendium": "allow",
5676
5801
  "world.audit-files": "allow",
5802
+ "world.references": "allow",
5677
5803
  "world.search": "allow",
5678
5804
  "approval.await": "allow",
5679
5805
  "approval.cancel": "allow",
@@ -5682,7 +5808,12 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
5682
5808
 
5683
5809
  // ../protocol/src/command-risk.js
5684
5810
  var DESTRUCTIVE_VERBS = Object.freeze(["delete", "delete-many"]);
5685
- var DESTRUCTIVE_COMMANDS = Object.freeze(["chat.flush", "file.move", "scene.fog.reset"]);
5811
+ var DESTRUCTIVE_COMMANDS = Object.freeze([
5812
+ "chat.flush",
5813
+ "file.move",
5814
+ "scene.fog.reset",
5815
+ "scene.token.restore-delta"
5816
+ ]);
5686
5817
  var APPROVE_EXTRA_COMMANDS = Object.freeze(["system.reload", "user.create"]);
5687
5818
  var DENIED_BY_DEFAULT_COMMANDS = Object.freeze([
5688
5819
  "macro.execute",
@@ -5747,6 +5878,17 @@ function editDistance(a, b) {
5747
5878
  }
5748
5879
  return previous[cols - 1];
5749
5880
  }
5881
+ function childPath(path, key) {
5882
+ return key.includes(".") ? `${path}[${JSON.stringify(key)}]` : `${path}.${key}`;
5883
+ }
5884
+ function describeDottedKey(key, properties) {
5885
+ const root = key.split(".")[0];
5886
+ if (!key.includes(".") || !Object.hasOwn(properties, root)) {
5887
+ return "";
5888
+ }
5889
+ const nested = key.split(".").reduceRight((inner, segment) => `{${JSON.stringify(segment)}: ${inner}}`, "...");
5890
+ return `; a key at this level must be one of the listed field names, so nest the rest of the path inside "${root}": ${nested}`;
5891
+ }
5750
5892
  function describeAllowedFields(key, allowedFields) {
5751
5893
  if (allowedFields.length === 0) {
5752
5894
  return "";
@@ -5796,7 +5938,7 @@ function validateObjectSchema(schema, value, path, errors) {
5796
5938
  const additionalProperties = schema.additionalProperties ?? true;
5797
5939
  for (const key of required) {
5798
5940
  if (!Object.hasOwn(value, key)) {
5799
- errors.push(`${path}.${key} is required`);
5941
+ errors.push(`${childPath(path, key)} is required`);
5800
5942
  }
5801
5943
  }
5802
5944
  const ownKeys = Object.keys(value);
@@ -5808,25 +5950,27 @@ function validateObjectSchema(schema, value, path, errors) {
5808
5950
  for (const key of ownKeys) {
5809
5951
  if (namePattern === null) {
5810
5952
  errors.push(
5811
- `${path}.${key} cannot be validated: ${schema.propertyNames.pattern} is not a valid unicode-mode pattern`
5953
+ `${childPath(path, key)} cannot be validated: ${schema.propertyNames.pattern} is not a valid unicode-mode pattern`
5812
5954
  );
5813
5955
  } else if (!namePattern.test(key)) {
5814
- errors.push(`${path}.${key} is not an allowed property name`);
5956
+ errors.push(`${childPath(path, key)} is not an allowed property name`);
5815
5957
  }
5816
5958
  }
5817
5959
  }
5818
5960
  for (const [key, propertyValue] of Object.entries(value)) {
5819
5961
  const propertySchema = properties[key];
5820
5962
  if (propertySchema) {
5821
- validateSchema(propertySchema, propertyValue, `${path}.${key}`, errors);
5963
+ validateSchema(propertySchema, propertyValue, childPath(path, key), errors);
5822
5964
  continue;
5823
5965
  }
5824
5966
  if (additionalProperties === false) {
5825
- errors.push(`${path}.${key} is not allowed${describeAllowedFields(key, Object.keys(properties))}`);
5967
+ errors.push(
5968
+ `${childPath(path, key)} is not allowed${describeAllowedFields(key, Object.keys(properties))}` + describeDottedKey(key, properties)
5969
+ );
5826
5970
  continue;
5827
5971
  }
5828
5972
  if (isPlainObject(additionalProperties)) {
5829
- validateSchema(additionalProperties, propertyValue, `${path}.${key}`, errors);
5973
+ validateSchema(additionalProperties, propertyValue, childPath(path, key), errors);
5830
5974
  }
5831
5975
  }
5832
5976
  }
@@ -8833,7 +8977,10 @@ function parseActorIncludeFields(value) {
8833
8977
  ]);
8834
8978
  }
8835
8979
  function parseTokenIncludeFields(value) {
8836
- return parseAllowedIncludeFields(value, ["prepared"]);
8980
+ return parseAllowedIncludeFields(value, ["prepared", "delta"]);
8981
+ }
8982
+ function parseTokenListIncludeFields(value) {
8983
+ return parseAllowedIncludeFields(value, ["delta"]);
8837
8984
  }
8838
8985
  function parseSystemInfoIncludeFields(value) {
8839
8986
  return parseAllowedIncludeFields(value, ["modules"]);
@@ -8990,7 +9137,10 @@ function createSceneCreateParams(options) {
8990
9137
  function createSceneCloneParams(options) {
8991
9138
  return {
8992
9139
  sceneId: options.sceneId,
8993
- ...optionalPatch(buildScenePatch(options))
9140
+ ...optionalPatch({
9141
+ ...buildScenePatch(options),
9142
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
9143
+ })
8994
9144
  };
8995
9145
  }
8996
9146
  function createItemCloneParams(options) {
@@ -8999,7 +9149,8 @@ function createItemCloneParams(options) {
8999
9149
  ...stringField("img", options.img),
9000
9150
  ...folderField(options),
9001
9151
  ...numberField("sort", options.sort),
9002
- ...jsonObjectField("system", options.systemJson, "--system-json")
9152
+ ...jsonObjectField("system", options.systemJson, "--system-json"),
9153
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
9003
9154
  };
9004
9155
  return {
9005
9156
  itemId: options.itemId,
@@ -9010,7 +9161,8 @@ function createJournalCloneParams(options) {
9010
9161
  const patch = {
9011
9162
  ...stringField("name", options.name),
9012
9163
  ...folderField(options),
9013
- ...numberField("sort", options.sort)
9164
+ ...numberField("sort", options.sort),
9165
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
9014
9166
  };
9015
9167
  return {
9016
9168
  journalId: options.journalId,
@@ -9096,7 +9248,8 @@ function createMacroCloneParams(options) {
9096
9248
  ...stringField("command", options.command),
9097
9249
  ...stringField("img", options.img),
9098
9250
  ...folderField(options),
9099
- ...stringField("scope", options.scope)
9251
+ ...stringField("scope", options.scope),
9252
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
9100
9253
  };
9101
9254
  return {
9102
9255
  macroId: options.macroId,
@@ -9219,6 +9372,34 @@ function createJournalCategoryUpdateParams(options) {
9219
9372
  })
9220
9373
  };
9221
9374
  }
9375
+ function buildJournalPageFields(options) {
9376
+ return {
9377
+ ...stringField("name", options.name),
9378
+ ...stringField("type", options.type),
9379
+ ...numberField("sort", options.sort),
9380
+ ...options.clearCategory ? { category: null } : stringField("category", options.category),
9381
+ ...stringField("src", options.src)
9382
+ };
9383
+ }
9384
+ function createJournalPageCreateParams(options) {
9385
+ return {
9386
+ journalId: options.journalId,
9387
+ data: {
9388
+ ...buildJournalPageFields(options),
9389
+ ...optionalJsonObject(options.dataJson, "--data-json") ?? {}
9390
+ }
9391
+ };
9392
+ }
9393
+ function createJournalPageUpdateParams(options) {
9394
+ return {
9395
+ journalId: options.journalId,
9396
+ pageId: options.pageId,
9397
+ patch: assertNonEmptyPatch({
9398
+ ...buildJournalPageFields(options),
9399
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
9400
+ })
9401
+ };
9402
+ }
9222
9403
  function buildTableFields(options) {
9223
9404
  return {
9224
9405
  ...stringField("description", options.description),
@@ -9531,7 +9712,8 @@ function createActorItemCloneParams(options) {
9531
9712
  ...stringField("name", options.name),
9532
9713
  ...stringField("img", options.img),
9533
9714
  ...numberField("sort", options.sort),
9534
- ...jsonObjectField("system", options.systemJson, "--system-json")
9715
+ ...jsonObjectField("system", options.systemJson, "--system-json"),
9716
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
9535
9717
  };
9536
9718
  return {
9537
9719
  actorId: options.actorId,
@@ -9610,7 +9792,10 @@ function createActorUpdateParams(options) {
9610
9792
  function createActorCloneParams(options) {
9611
9793
  return {
9612
9794
  actorId: options.actorId,
9613
- ...optionalPatch(buildActorPatch(options))
9795
+ ...optionalPatch({
9796
+ ...buildActorPatch(options),
9797
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
9798
+ })
9614
9799
  };
9615
9800
  }
9616
9801
  function createActorImportParams(options) {
@@ -10366,8 +10551,11 @@ function renderBroadcastLines(result) {
10366
10551
  `dispatched: ${Boolean(result?.dispatched)} \u2014 a broadcast writes nothing, so there is no state to confirm afterwards`
10367
10552
  ];
10368
10553
  }
10369
- function renderCompendiumSourceLines(document) {
10370
- return document?.compendiumSource ? [`compendiumSource: ${document.compendiumSource}`] : [];
10554
+ function renderProvenanceLines(document) {
10555
+ return [
10556
+ ...document?.compendiumSource ? [`compendiumSource: ${document.compendiumSource}`] : [],
10557
+ ...document?.duplicateSource ? [`duplicateSource: ${document.duplicateSource}`] : []
10558
+ ];
10371
10559
  }
10372
10560
  function renderOwnershipLines(ownership) {
10373
10561
  if (!ownership || typeof ownership !== "object") {
@@ -10479,7 +10667,7 @@ function renderItemDetails(item, prefixLines = []) {
10479
10667
  if (item && Object.hasOwn(item, "effects")) {
10480
10668
  lines.push(`effects: ${JSON.stringify(item.effects ?? [], null, 2)}`);
10481
10669
  }
10482
- lines.push(...renderCompendiumSourceLines(item));
10670
+ lines.push(...renderProvenanceLines(item));
10483
10671
  if (item && Object.hasOwn(item, "ownership")) {
10484
10672
  lines.push(...renderOwnershipLines(item.ownership));
10485
10673
  }
@@ -10505,7 +10693,7 @@ function renderActorDetails(actor) {
10505
10693
  if (actor && Object.hasOwn(actor, "effects")) {
10506
10694
  lines.push(`effects: ${JSON.stringify(actor.effects ?? [], null, 2)}`);
10507
10695
  }
10508
- lines.push(...renderCompendiumSourceLines(actor));
10696
+ lines.push(...renderProvenanceLines(actor));
10509
10697
  if (actor && Object.hasOwn(actor, "ownership")) {
10510
10698
  lines.push(...renderOwnershipLines(actor.ownership));
10511
10699
  }
@@ -10551,7 +10739,7 @@ function renderCardsDetails(cards) {
10551
10739
  `cards: ${rows.length}`,
10552
10740
  ...rows.map((card) => ` ${renderCardLine(card)}`)
10553
10741
  ];
10554
- lines.push(...renderCompendiumSourceLines(cards));
10742
+ lines.push(...renderProvenanceLines(cards));
10555
10743
  if (cards && Object.hasOwn(cards, "ownership")) {
10556
10744
  lines.push(...renderOwnershipLines(cards.ownership));
10557
10745
  }
@@ -11090,7 +11278,7 @@ function renderJournalDetails(journal) {
11090
11278
  `sort: ${journal?.sort ?? 0}`,
11091
11279
  `pages: ${pages.length}`
11092
11280
  ];
11093
- lines.push(...renderCompendiumSourceLines(journal));
11281
+ lines.push(...renderProvenanceLines(journal));
11094
11282
  if (journal && Object.hasOwn(journal, "ownership")) {
11095
11283
  lines.push(...renderOwnershipLines(journal.ownership));
11096
11284
  }
@@ -11102,55 +11290,7 @@ function renderJournalDetails(journal) {
11102
11290
  });
11103
11291
  }
11104
11292
  pages.forEach((page, index) => {
11105
- const pageNumber = index + 1;
11106
- lines.push(`page ${pageNumber}: ${page?.id ?? ""} ${page?.name ?? ""} ${page?.type ?? ""}`);
11107
- const type = page?.type;
11108
- if (type === "image") {
11109
- if (page?.src) {
11110
- lines.push(`page ${pageNumber} src: ${page.src}`);
11111
- }
11112
- const caption = page?.image?.caption;
11113
- if (typeof caption === "string" && caption.length > 0) {
11114
- const shown = caption.length > 60 ? `${caption.slice(0, 60)}\u2026` : caption;
11115
- lines.push(`page ${pageNumber} caption: ${shown}`);
11116
- } else {
11117
- lines.push(`page ${pageNumber} caption: (none)`);
11118
- }
11119
- } else if (type === "video") {
11120
- if (page?.src) {
11121
- lines.push(`page ${pageNumber} src: ${page.src}`);
11122
- }
11123
- } else if (type === "pdf") {
11124
- if (page?.src) {
11125
- lines.push(`page ${pageNumber} src: ${page.src}`);
11126
- }
11127
- } else {
11128
- const format = page?.text?.format;
11129
- if (format === 2) {
11130
- lines.push(`page ${pageNumber} format: markdown`);
11131
- } else if (format === 1) {
11132
- lines.push(`page ${pageNumber} format: html`);
11133
- }
11134
- if (page?.text?.content) {
11135
- lines.push(`page ${pageNumber} text: ${page.text.content}`);
11136
- }
11137
- if (page?.src) {
11138
- lines.push(`page ${pageNumber} src: ${page.src}`);
11139
- }
11140
- }
11141
- const title = page?.title;
11142
- if (title && (title.show === false || typeof title.level === "number" && title.level !== 1)) {
11143
- const bits = [];
11144
- if (title.show === false) bits.push("show=false");
11145
- if (typeof title.level === "number" && title.level !== 1) bits.push(`level=${title.level}`);
11146
- lines.push(`page ${pageNumber} title: ${bits.join(" ")}`);
11147
- }
11148
- if (page?.category) {
11149
- lines.push(`page ${pageNumber} category: ${page.category}`);
11150
- }
11151
- if (page && Object.hasOwn(page, "ownership")) {
11152
- lines.push(...renderOwnershipLines(page.ownership).map((line) => `page ${pageNumber} ${line}`));
11153
- }
11293
+ lines.push(...renderJournalPageLines(page, `page ${index + 1}`));
11154
11294
  });
11155
11295
  return lines.join("\n");
11156
11296
  }
@@ -11163,6 +11303,143 @@ function renderJournalCategoryDetails(journalId, category) {
11163
11303
  `sort: ${category?.sort ?? 0}`
11164
11304
  ].join("\n");
11165
11305
  }
11306
+ function renderJournalPageLines(page, label) {
11307
+ const lines = [];
11308
+ lines.push(`${label}: ${page?.id ?? ""} ${page?.name ?? ""} ${page?.type ?? ""}`);
11309
+ const type = page?.type;
11310
+ if (type === "image") {
11311
+ if (page?.src) {
11312
+ lines.push(`${label} src: ${page.src}`);
11313
+ }
11314
+ const caption = page?.image?.caption;
11315
+ if (typeof caption === "string" && caption.length > 0) {
11316
+ const shown = caption.length > 60 ? `${caption.slice(0, 60)}\u2026` : caption;
11317
+ lines.push(`${label} caption: ${shown}`);
11318
+ } else {
11319
+ lines.push(`${label} caption: (none)`);
11320
+ }
11321
+ } else if (type === "video") {
11322
+ if (page?.src) {
11323
+ lines.push(`${label} src: ${page.src}`);
11324
+ }
11325
+ } else if (type === "pdf") {
11326
+ if (page?.src) {
11327
+ lines.push(`${label} src: ${page.src}`);
11328
+ }
11329
+ } else {
11330
+ const format = page?.text?.format;
11331
+ if (format === 2) {
11332
+ lines.push(`${label} format: markdown`);
11333
+ } else if (format === 1) {
11334
+ lines.push(`${label} format: html`);
11335
+ }
11336
+ if (page?.text?.content) {
11337
+ lines.push(`${label} text: ${page.text.content}`);
11338
+ }
11339
+ if (page?.src) {
11340
+ lines.push(`${label} src: ${page.src}`);
11341
+ }
11342
+ }
11343
+ const title = page?.title;
11344
+ if (title && (title.show === false || typeof title.level === "number" && title.level !== 1)) {
11345
+ const bits = [];
11346
+ if (title.show === false) bits.push("show=false");
11347
+ if (typeof title.level === "number" && title.level !== 1) bits.push(`level=${title.level}`);
11348
+ lines.push(`${label} title: ${bits.join(" ")}`);
11349
+ }
11350
+ if (page?.category) {
11351
+ lines.push(`${label} category: ${page.category}`);
11352
+ }
11353
+ if (page && Object.hasOwn(page, "ownership")) {
11354
+ lines.push(...renderOwnershipLines(page.ownership).map((line) => `${label} ${line}`));
11355
+ }
11356
+ return lines;
11357
+ }
11358
+ function renderJournalPageDetails(journalId, page) {
11359
+ return [`journal: ${journalId}`, ...renderJournalPageLines(page, "page")].join("\n");
11360
+ }
11361
+
11362
+ // src/render/settings.ts
11363
+ function renderSettingSummaryLine(setting) {
11364
+ const label = setting?.nameLocalized && setting.nameLocalized !== setting.name ? `${setting.nameLocalized} [${setting.name}]` : setting?.nameLocalized ?? setting?.name ?? "(unnamed)";
11365
+ return [
11366
+ setting?.id ?? `${setting?.namespace ?? "?"}.${setting?.key ?? "?"}`,
11367
+ label,
11368
+ `scope=${setting?.scope ?? "?"}`,
11369
+ `type=${setting?.type?.kind ?? "-"}`,
11370
+ `config=${Boolean(setting?.config)}`,
11371
+ `reload=${Boolean(setting?.requiresReload)}`,
11372
+ ...setting?.metadataReadFailed ? ["metadata=UNREADABLE"] : [],
11373
+ ...setting?.unaddressable ? ["address=UNADDRESSABLE"] : [],
11374
+ ...setting?.valueRedacted ? ["value=REDACTED"] : []
11375
+ ].join(" ");
11376
+ }
11377
+ function renderSettingValue(value) {
11378
+ return JSON.stringify(value ?? null, null, 2);
11379
+ }
11380
+ function renderOmissionLines(label, omissions, truncated) {
11381
+ if (!Array.isArray(omissions) || omissions.length === 0) return [];
11382
+ return [
11383
+ `${label} incomplete: ${omissions.length}${truncated ? "+" : ""} node(s) replaced by {"$unserializable": <reason>}`,
11384
+ ...omissions.map((omission) => ` ${omission?.path ?? "?"} ${omission?.reason ?? "?"}`)
11385
+ ];
11386
+ }
11387
+ function renderSettingWriteLines(row, dryRun) {
11388
+ const verb = dryRun ? "would write" : row?.changed ? "wrote" : "left unchanged";
11389
+ return [
11390
+ `${verb}: ${row?.id ?? `${row?.namespace}.${row?.key}`} (scope: ${row?.scope ?? "?"})`,
11391
+ `previous: ${renderSettingValue(row?.previous)}`,
11392
+ ...renderOmissionLines("previous", row?.previousOmissions, row?.previousOmissionsTruncated),
11393
+ `${dryRun ? "requested" : "value"}: ${renderSettingValue(row?.value)}`,
11394
+ ...renderOmissionLines("value", row?.valueOmissions, row?.valueOmissionsTruncated),
11395
+ `changed: ${Boolean(row?.changed)} / validated: ${Boolean(row?.validated)}`,
11396
+ ...row?.requiresReload ? ["requiresReload: true \u2014 Foundry acts on this only after `system reload` or a manual browser reload"] : ["requiresReload: false"]
11397
+ ];
11398
+ }
11399
+ function renderSettingWriteResult(result) {
11400
+ return renderSettingWriteLines(result, Boolean(result?.dryRun)).join("\n");
11401
+ }
11402
+ function renderSettingWriteOutcomes(result) {
11403
+ const outcomes = result?.outcomes ?? [];
11404
+ const dryRun = Boolean(result?.dryRun);
11405
+ const lines = [
11406
+ `${dryRun ? "Would write" : "Wrote"} settings (${outcomes.length}) \u2014 complete: ${Boolean(result?.complete)}`,
11407
+ ...outcomes.flatMap(
11408
+ (outcome) => outcome?.status === "updated" || outcome?.status === "unchanged" ? [
11409
+ `[${outcome.index}] ${outcome.status}`,
11410
+ ...renderSettingWriteLines(outcome, dryRun).map((line) => ` ${line}`)
11411
+ ] : [`[${outcome?.index}] ${outcome?.status} ${outcome?.id ?? ""}`]
11412
+ )
11413
+ ];
11414
+ if (result?.failure) {
11415
+ lines.push(`failure: ${result.failure.code} \u2014 ${result.failure.message}`);
11416
+ }
11417
+ return lines.join("\n");
11418
+ }
11419
+ function renderSettingBatchRows(setting) {
11420
+ return [
11421
+ renderSettingSummaryLine(setting),
11422
+ ...setting?.error ? [` error: ${setting.error.code} \u2014 ${setting.error.message}`] : [
11423
+ ` value: ${renderSettingValue(setting?.value)}`,
11424
+ ...renderOmissionLines("value", setting?.valueOmissions, setting?.valueOmissionsTruncated).map(
11425
+ (line) => ` ${line}`
11426
+ )
11427
+ ]
11428
+ ];
11429
+ }
11430
+ function renderSettingDetails(setting) {
11431
+ return [
11432
+ `setting: ${setting?.id ?? `${setting?.namespace}.${setting?.key}`}`,
11433
+ `name: ${setting?.nameLocalized ?? "(none)"}${setting?.name && setting.name !== setting.nameLocalized ? ` (raw: ${setting.name})` : ""}`,
11434
+ `hint: ${setting?.hintLocalized ?? "(none)"}${setting?.hint && setting.hint !== setting.hintLocalized ? ` (raw: ${setting.hint})` : ""}`,
11435
+ `scope: ${setting?.scope ?? "?"}`,
11436
+ `type: ${setting?.type?.kind ?? "(untyped)"}`,
11437
+ `config: ${Boolean(setting?.config)} / requiresReload: ${Boolean(setting?.requiresReload)}`,
11438
+ ...setting?.metadataReadFailed ? [`metadata: UNREADABLE (${setting.metadataReadError ?? "unknown error"})`] : [],
11439
+ setting?.valueRedacted ? "value: null (REDACTED \u2014 authorization settings are never exposed through setting commands)" : `value: ${JSON.stringify(setting?.value ?? null, null, 2)}`,
11440
+ ...renderOmissionLines("value", setting?.valueOmissions, setting?.valueOmissionsTruncated)
11441
+ ].join("\n");
11442
+ }
11166
11443
 
11167
11444
  // src/render/macros.ts
11168
11445
  var CHAT_CAPTURE_NOTES = {
@@ -11185,6 +11462,9 @@ function renderMacroExecuteResult(result) {
11185
11462
  `type: ${result?.type ?? "(unknown)"}`,
11186
11463
  `returned: ${JSON.stringify(result?.returned ?? null, null, 2)}`
11187
11464
  ];
11465
+ lines.push(
11466
+ ...renderOmissionLines("returned", result?.returnedOmissions, result?.returnedOmissionsTruncated)
11467
+ );
11188
11468
  if (result?.returnedOmitted) {
11189
11469
  lines.push(`returned omitted: ${result.returnedOmitted.code} \u2014 ${result.returnedOmitted.message}`);
11190
11470
  }
@@ -11206,7 +11486,7 @@ function renderMacroDetails(macro) {
11206
11486
  `flags: ${JSON.stringify(macro?.flags ?? {}, null, 2)}`,
11207
11487
  `command: ${macro?.command ?? ""}`
11208
11488
  ];
11209
- lines.push(...renderCompendiumSourceLines(macro));
11489
+ lines.push(...renderProvenanceLines(macro));
11210
11490
  if (macro && Object.hasOwn(macro, "ownership")) {
11211
11491
  lines.push(...renderOwnershipLines(macro.ownership));
11212
11492
  }
@@ -11229,7 +11509,7 @@ function renderPlaylistDetails(playlist) {
11229
11509
  `flags: ${JSON.stringify(playlist?.flags ?? {}, null, 2)}`,
11230
11510
  `sounds: ${playlist?.sounds?.length ?? 0}`
11231
11511
  ];
11232
- lines.push(...renderCompendiumSourceLines(playlist));
11512
+ lines.push(...renderProvenanceLines(playlist));
11233
11513
  if (playlist && Object.hasOwn(playlist, "ownership")) {
11234
11514
  lines.push(...renderOwnershipLines(playlist.ownership));
11235
11515
  }
@@ -11288,7 +11568,7 @@ function renderSceneDetails(scene) {
11288
11568
  for (const level of Array.isArray(scene?.levels) ? scene.levels : []) {
11289
11569
  lines.push(`level: ${renderLevelRow(level)}`);
11290
11570
  }
11291
- lines.push(...renderCompendiumSourceLines(scene));
11571
+ lines.push(...renderProvenanceLines(scene));
11292
11572
  if (scene && Object.hasOwn(scene, "ownership")) {
11293
11573
  lines.push(...renderOwnershipLines(scene.ownership));
11294
11574
  }
@@ -11328,11 +11608,29 @@ function renderTokenDetails(sceneId, token) {
11328
11608
  `disposition: ${token?.disposition ?? ""}`,
11329
11609
  `texture: ${JSON.stringify(token?.texture ?? null)}`
11330
11610
  ];
11611
+ if (token && Object.hasOwn(token, "delta")) {
11612
+ lines.push(`delta: ${token.delta === null ? "none" : "stored (use --json for values)"}`);
11613
+ }
11331
11614
  if (token && Object.hasOwn(token, "prepared")) {
11332
11615
  lines.push(`prepared: ${Object.keys(token.prepared ?? {}).join(", ")} (use --json for values)`);
11333
11616
  }
11334
11617
  return lines.join("\n");
11335
11618
  }
11619
+ function tokenDeltaSummaryCell(token) {
11620
+ if (!token || !Object.hasOwn(token, "deltaSummary")) return "";
11621
+ const summary = token.deltaSummary;
11622
+ if (summary === null) return " overrides=none";
11623
+ return ` overrides=${(summary.overriddenKeys ?? []).join(",")} (item entries ${summary.itemEntries}, effect entries ${summary.effectEntries})`;
11624
+ }
11625
+ function renderTokenDeltaRestore(result) {
11626
+ const discarded = result.discarded;
11627
+ const verb = result.dryRun ? "[dry-run] would discard" : "Discarded";
11628
+ return [
11629
+ `scene: ${result.sceneId}`,
11630
+ `token: ${result.tokenId}`,
11631
+ 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."
11632
+ ].join("\n");
11633
+ }
11336
11634
  function tokenItemPrefix(result) {
11337
11635
  const lines = [
11338
11636
  `scene: ${result.sceneId}`,
@@ -11520,73 +11818,6 @@ function renderRegionBehaviorDetails(sceneId, regionId, behavior) {
11520
11818
  ].join("\n");
11521
11819
  }
11522
11820
 
11523
- // src/render/settings.ts
11524
- function renderSettingSummaryLine(setting) {
11525
- const label = setting?.nameLocalized && setting.nameLocalized !== setting.name ? `${setting.nameLocalized} [${setting.name}]` : setting?.nameLocalized ?? setting?.name ?? "(unnamed)";
11526
- return [
11527
- setting?.id ?? `${setting?.namespace ?? "?"}.${setting?.key ?? "?"}`,
11528
- label,
11529
- `scope=${setting?.scope ?? "?"}`,
11530
- `type=${setting?.type?.kind ?? "-"}`,
11531
- `config=${Boolean(setting?.config)}`,
11532
- `reload=${Boolean(setting?.requiresReload)}`,
11533
- ...setting?.metadataReadFailed ? ["metadata=UNREADABLE"] : [],
11534
- ...setting?.unaddressable ? ["address=UNADDRESSABLE"] : [],
11535
- ...setting?.valueRedacted ? ["value=REDACTED"] : []
11536
- ].join(" ");
11537
- }
11538
- function renderSettingValue(value) {
11539
- return JSON.stringify(value ?? null, null, 2);
11540
- }
11541
- function renderSettingWriteLines(row, dryRun) {
11542
- const verb = dryRun ? "would write" : row?.changed ? "wrote" : "left unchanged";
11543
- return [
11544
- `${verb}: ${row?.id ?? `${row?.namespace}.${row?.key}`} (scope: ${row?.scope ?? "?"})`,
11545
- `previous: ${renderSettingValue(row?.previous)}`,
11546
- `${dryRun ? "requested" : "value"}: ${renderSettingValue(row?.value)}`,
11547
- `changed: ${Boolean(row?.changed)} / validated: ${Boolean(row?.validated)}`,
11548
- ...row?.requiresReload ? ["requiresReload: true \u2014 Foundry acts on this only after `system reload` or a manual browser reload"] : ["requiresReload: false"]
11549
- ];
11550
- }
11551
- function renderSettingWriteResult(result) {
11552
- return renderSettingWriteLines(result, Boolean(result?.dryRun)).join("\n");
11553
- }
11554
- function renderSettingWriteOutcomes(result) {
11555
- const outcomes = result?.outcomes ?? [];
11556
- const dryRun = Boolean(result?.dryRun);
11557
- const lines = [
11558
- `${dryRun ? "Would write" : "Wrote"} settings (${outcomes.length}) \u2014 complete: ${Boolean(result?.complete)}`,
11559
- ...outcomes.flatMap(
11560
- (outcome) => outcome?.status === "updated" || outcome?.status === "unchanged" ? [
11561
- `[${outcome.index}] ${outcome.status}`,
11562
- ...renderSettingWriteLines(outcome, dryRun).map((line) => ` ${line}`)
11563
- ] : [`[${outcome?.index}] ${outcome?.status} ${outcome?.id ?? ""}`]
11564
- )
11565
- ];
11566
- if (result?.failure) {
11567
- lines.push(`failure: ${result.failure.code} \u2014 ${result.failure.message}`);
11568
- }
11569
- return lines.join("\n");
11570
- }
11571
- function renderSettingBatchRows(setting) {
11572
- return [
11573
- renderSettingSummaryLine(setting),
11574
- ...setting?.error ? [` error: ${setting.error.code} \u2014 ${setting.error.message}`] : [` value: ${renderSettingValue(setting?.value)}`]
11575
- ];
11576
- }
11577
- function renderSettingDetails(setting) {
11578
- return [
11579
- `setting: ${setting?.id ?? `${setting?.namespace}.${setting?.key}`}`,
11580
- `name: ${setting?.nameLocalized ?? "(none)"}${setting?.name && setting.name !== setting.nameLocalized ? ` (raw: ${setting.name})` : ""}`,
11581
- `hint: ${setting?.hintLocalized ?? "(none)"}${setting?.hint && setting.hint !== setting.hintLocalized ? ` (raw: ${setting.hint})` : ""}`,
11582
- `scope: ${setting?.scope ?? "?"}`,
11583
- `type: ${setting?.type?.kind ?? "(untyped)"}`,
11584
- `config: ${Boolean(setting?.config)} / requiresReload: ${Boolean(setting?.requiresReload)}`,
11585
- ...setting?.metadataReadFailed ? [`metadata: UNREADABLE (${setting.metadataReadError ?? "unknown error"})`] : [],
11586
- setting?.valueRedacted ? "value: null (REDACTED \u2014 authorization settings are never exposed through setting commands)" : `value: ${JSON.stringify(setting?.value ?? null, null, 2)}`
11587
- ].join("\n");
11588
- }
11589
-
11590
11821
  // src/render/tables.ts
11591
11822
  function renderTableResultLine(result, ownerColumns = []) {
11592
11823
  const range = Array.isArray(result?.range) ? result.range.join("-") : "";
@@ -11617,7 +11848,7 @@ function renderTableDetails(table) {
11617
11848
  `results: ${results.length}`,
11618
11849
  ...results.map((result) => ` ${renderTableResultLine(result)}`)
11619
11850
  ];
11620
- lines.push(...renderCompendiumSourceLines(table));
11851
+ lines.push(...renderProvenanceLines(table));
11621
11852
  if (table && Object.hasOwn(table, "ownership")) {
11622
11853
  lines.push(...renderOwnershipLines(table.ownership));
11623
11854
  }
@@ -11892,6 +12123,28 @@ var RENDERERS = registerRenderers([
11892
12123
  }
11893
12124
  return lines.join("\n");
11894
12125
  }),
12126
+ customRenderer("world.references", (result, offset) => {
12127
+ const references = result.references ?? [];
12128
+ const target = result.target ?? {};
12129
+ const lines = [
12130
+ `target: ${target.uuid ?? ""} ${humanCell(target.name)} exists=${target.exists === null ? "unknown" : String(target.exists)}`
12131
+ ];
12132
+ if (references.length) {
12133
+ lines.push(
12134
+ `References (${references.length})`,
12135
+ ...references.map(
12136
+ (ref) => `${ref.uuid} ${humanCell(ref.name)} ${ref.field} ${ref.match}${ref.matchedUuid ? ` ${ref.matchedUuid}` : ""}`
12137
+ )
12138
+ );
12139
+ } else {
12140
+ lines.push("No stored references found.");
12141
+ }
12142
+ lines.push(...listFooterLines(result, references.length, offset));
12143
+ lines.push(
12144
+ `summary: references=${result.total ?? references.length} scannedDocuments=${result.scannedDocuments ?? 0} selfReferences=${result.selfReferences ?? 0} scopes=${(result.scopes ?? []).join(",")}`
12145
+ );
12146
+ return lines.join("\n");
12147
+ }),
11895
12148
  listRenderer("scene.list", {
11896
12149
  key: "scenes",
11897
12150
  empty: "No scenes found.",
@@ -12256,6 +12509,21 @@ var RENDERERS = registerRenderers([
12256
12509
  }),
12257
12510
  deleteRenderer("journal.delete", "journal"),
12258
12511
  detailRenderer("journal.show", (result) => renderJournalShowResult(result)),
12512
+ listRenderer("journal.page.list", {
12513
+ key: "pages",
12514
+ empty: "No pages found.",
12515
+ heading: "Pages",
12516
+ prefix: (result) => [`journal: ${result.journalId}`],
12517
+ row: (page) => `${page.id} ${page.name ?? ""} ${page.type ?? ""} sort=${page.sort ?? 0}${page.textFormat === 2 ? " format=markdown" : page.textFormat === 1 ? " format=html" : ""}`
12518
+ }),
12519
+ detailRenderer(
12520
+ ["journal.page.get", "journal.page.create", "journal.page.update"],
12521
+ (result) => renderJournalPageDetails(result.journalId, result.page)
12522
+ ),
12523
+ detailRenderer(
12524
+ "journal.page.delete",
12525
+ (result) => result.deleted ? `Deleted journal page ${result.id} from journal ${result.journalId}` : `Would delete journal page ${result.id} from journal ${result.journalId}`
12526
+ ),
12259
12527
  listRenderer("journal.category.list", {
12260
12528
  key: "categories",
12261
12529
  empty: "No categories found.",
@@ -12407,6 +12675,17 @@ var RENDERERS = registerRenderers([
12407
12675
  `document: ${JSON.stringify(result.document ?? null, null, 2)}`
12408
12676
  ].join("\n")
12409
12677
  ),
12678
+ customRenderer("compendium.get-many", (result) => {
12679
+ const entries = result.entries ?? [];
12680
+ return [
12681
+ `pack: ${result.pack}`,
12682
+ `Entries (${entries.length})`,
12683
+ ...entries.map(
12684
+ (entry) => `${entry.entryId} ${entry.documentName ?? ""} ${entry.document?.name ?? ""}${Array.isArray(entry.effects) ? ` effects=${entry.effects.length}` : ""}`
12685
+ ),
12686
+ "(use --json for the documents)"
12687
+ ].join("\n");
12688
+ }),
12410
12689
  listRenderer("folder.list", {
12411
12690
  key: "folders",
12412
12691
  empty: "No folders found.",
@@ -12540,13 +12819,14 @@ var RENDERERS = registerRenderers([
12540
12819
  empty: "No tokens found.",
12541
12820
  heading: "Tokens",
12542
12821
  prefix: scenePrefix,
12543
- row: (token) => `${token.id} ${token.name} actor=${token.actorId ?? "-"} linked=${String(Boolean(token.actorLink))} (${token.x},${token.y})${levelMembershipCell(token)}`
12822
+ row: (token) => `${token.id} ${token.name} actor=${token.actorId ?? "-"} linked=${String(Boolean(token.actorLink))} (${token.x},${token.y})${levelMembershipCell(token)}${tokenDeltaSummaryCell(token)}`
12544
12823
  }),
12545
12824
  detailRenderer(
12546
12825
  ["scene.token.get", "scene.token.create", "scene.token.update", "scene.token.clone"],
12547
12826
  (result) => renderTokenDetails(result.sceneId, result.token)
12548
12827
  ),
12549
12828
  deleteRenderer("scene.token.delete", "token", { suffix: sceneSuffix }),
12829
+ detailRenderer("scene.token.restore-delta", (result) => renderTokenDeltaRestore(result)),
12550
12830
  listRenderer("scene.token.item.list", {
12551
12831
  key: "items",
12552
12832
  empty: "No items found.",
@@ -13384,6 +13664,28 @@ function addJournalCategoryFieldOptions(command, verb) {
13384
13664
  const ordered = verb === "create" ? [JOURNAL_CATEGORY_SORT_FIELD] : [JOURNAL_CATEGORY_NAME_FIELD, JOURNAL_CATEGORY_SORT_FIELD];
13385
13665
  return addFieldOptions(command, verb, ordered);
13386
13666
  }
13667
+ var JOURNAL_PAGE_FIELDS = [
13668
+ {
13669
+ flag: "--sort <sort>",
13670
+ help: { create: "Page sort value", update: "New page sort value" },
13671
+ parser: parseNumber
13672
+ },
13673
+ {
13674
+ flag: "--category <categoryId>",
13675
+ help: {
13676
+ create: "Category id from journal category list",
13677
+ update: "New category id from journal category list"
13678
+ }
13679
+ },
13680
+ { flag: "--clear-category", help: "Explicitly set category to null", conflicts: "category" },
13681
+ {
13682
+ flag: "--src <src>",
13683
+ help: { create: "Media path of an image, pdf or video page", update: "New media path" }
13684
+ }
13685
+ ];
13686
+ function addJournalPageFieldOptions(command, verb) {
13687
+ return addFieldOptions(command, verb, JOURNAL_PAGE_FIELDS);
13688
+ }
13387
13689
  var PLAYLIST_FIELDS = [
13388
13690
  {
13389
13691
  flag: "--description <description>",
@@ -14248,7 +14550,7 @@ function registerActor({ program, dependencies }) {
14248
14550
  addActorFieldOptions(
14249
14551
  addIdempotencyKeyOption(actor.command("clone")).requiredOption("--actor-id <actorId>", "Source actor id").option("--name <name>", "Name override for the clone"),
14250
14552
  "clone"
14251
- ).action(async function cloneActorCommand(options) {
14553
+ ).option("--patch-json <json>", "Override fields for the clone as a JSON object (merged last)").action(async function cloneActorCommand(options) {
14252
14554
  await executeRemoteCommand({
14253
14555
  commandName: "actor.clone",
14254
14556
  params: createActorCloneParams(options),
@@ -14273,6 +14575,12 @@ function registerActor({ program, dependencies }) {
14273
14575
  });
14274
14576
  });
14275
14577
  const actorItem = actor.command("item").description("Foundry actor embedded item commands");
14578
+ registerBatchWriteCommands(actorItem, {
14579
+ prefix: "actor.item",
14580
+ noun: "item",
14581
+ scope: "actor",
14582
+ dataHelp: "Item data as a JSON ARRAY of objects; each element takes what `actor item create --data-json` takes plus name and type"
14583
+ });
14276
14584
  addNameFilterOption(
14277
14585
  addPaginationOptions(actorItem.command("list").requiredOption("--actor-id <actorId>", "Actor id")).option(
14278
14586
  "--include <fields>",
@@ -14338,7 +14646,7 @@ function registerActor({ program, dependencies }) {
14338
14646
  addEmbeddedItemFieldOptions(
14339
14647
  addIdempotencyKeyOption(actorItem.command("clone")).requiredOption("--actor-id <actorId>", "Actor id").requiredOption("--item-id <itemId>", "Source item id").option("--name <name>", "Name override for the clone"),
14340
14648
  "clone"
14341
- ).action(async function cloneActorItem(options) {
14649
+ ).option("--patch-json <json>", "Override fields for the clone as a JSON object (merged last)").action(async function cloneActorItem(options) {
14342
14650
  await executeRemoteCommand({
14343
14651
  commandName: "actor.item.clone",
14344
14652
  params: createActorItemCloneParams(options),
@@ -14996,7 +15304,7 @@ function registerBridge({ program, dependencies }) {
14996
15304
  });
14997
15305
  bridge.command("serve").description("Start the local bridge daemon").option("--request-timeout-ms <ms>", "Response timeout for forwarded Foundry requests", parsePositiveInt).option(
14998
15306
  "--upload-limit-bytes <size>",
14999
- "Override the file.upload size limit as bytes or a size like 100MiB (max 512MiB)"
15307
+ "Override the file.upload and file.read size limit for this run, as bytes or a size like 100MiB (max 512MiB)"
15000
15308
  ).action(async function serveBridge(options) {
15001
15309
  const bridgeConfig = getBridgeDaemonConfig(this, options, dependencies);
15002
15310
  const flagUploadLimitBytes = typeof options.uploadLimitBytes === "string" ? parseUploadLimitBytes(options.uploadLimitBytes) : void 0;
@@ -15684,11 +15992,18 @@ function registerCombat({ program, dependencies }) {
15684
15992
  }
15685
15993
 
15686
15994
  // src/commands/compendium.ts
15995
+ function addCompendiumIncludeOption(command) {
15996
+ return command.option(
15997
+ "--include <fields>",
15998
+ "Comma-separated extra fields to include (allowed: effects)",
15999
+ parseCompendiumIncludeFields
16000
+ );
16001
+ }
15687
16002
  function registerCompendium({ program, dependencies }) {
15688
16003
  const compendium = program.command("compendium").description("Foundry compendium pack commands (read)");
15689
16004
  compendium.addHelpText(
15690
16005
  "after",
15691
- "\nResult key (--json): .result.packs[] (list), .result.entries[] (index), .result.document (get \u2014 Foundry's raw source object)."
16006
+ "\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
16007
  );
15693
16008
  addPaginationOptions(compendium.command("list")).action(async function listCompendiums(options) {
15694
16009
  await executeRemoteCommand({
@@ -15718,10 +16033,8 @@ function registerCompendium({ program, dependencies }) {
15718
16033
  dependencies
15719
16034
  });
15720
16035
  });
15721
- compendium.command("get").requiredOption("--pack <pack>", "Compendium pack id").requiredOption("--entry-id <entryId>", "Compendium entry id").option(
15722
- "--include <fields>",
15723
- "Comma-separated extra fields to include (allowed: effects)",
15724
- parseCompendiumIncludeFields
16036
+ addCompendiumIncludeOption(
16037
+ compendium.command("get").requiredOption("--pack <pack>", "Compendium pack id").requiredOption("--entry-id <entryId>", "Compendium entry id")
15725
16038
  ).action(async function getCompendiumEntry(options) {
15726
16039
  await executeRemoteCommand({
15727
16040
  commandName: "compendium.get",
@@ -15734,6 +16047,20 @@ function registerCompendium({ program, dependencies }) {
15734
16047
  dependencies
15735
16048
  });
15736
16049
  });
16050
+ addCompendiumIncludeOption(
16051
+ 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)
16052
+ ).action(async function getManyCompendiumEntries(options) {
16053
+ await executeRemoteCommand({
16054
+ commandName: "compendium.get-many",
16055
+ params: {
16056
+ pack: options.pack,
16057
+ ids: options.ids,
16058
+ ...options.include ? { include: options.include } : {}
16059
+ },
16060
+ command: this,
16061
+ dependencies
16062
+ });
16063
+ });
15737
16064
  }
15738
16065
 
15739
16066
  // src/commands/config.ts
@@ -15756,7 +16083,9 @@ function registerConfig({ program, dependencies }) {
15756
16083
  write(dependencies.stdout, `${renderConfigGetHumanOutput(configPath, displayConfig)}
15757
16084
  `);
15758
16085
  });
15759
- config.command("set-upload-limit").description("Persist the file.upload size limit (raw bytes, pre-base64) for the daemon and CLI").argument("<size>", "Upload size limit as bytes or a size like 100MiB (max 512MiB)").action(function setUploadLimit(size) {
16086
+ config.command("set-upload-limit").description(
16087
+ "Persist the file.upload and file.read size limit (raw bytes, pre-base64) for the daemon and CLI"
16088
+ ).argument("<size>", "Upload size limit as bytes or a size like 100MiB (max 512MiB)").action(function setUploadLimit(size) {
15760
16089
  const uploadLimitBytes = parseUploadLimitBytes(size);
15761
16090
  writeCliConfigUpdate(dependencies.configStore, { uploadLimitBytes });
15762
16091
  const payload = {
@@ -16027,8 +16356,13 @@ function registerFile({ program, dependencies }) {
16027
16356
  dependencies
16028
16357
  });
16029
16358
  });
16030
- file.command("read").requiredOption("--path <path>", "Data-relative file path").addOption(
16359
+ file.command("read").description(
16360
+ `Read a file's content; a file over the upload limit (system info: limits.uploadBytes) or about ${Math.floor(READ_ENCODING_CEILING_BYTES / (1024 * 1024))} MiB returns PAYLOAD_TOO_LARGE`
16361
+ ).requiredOption("--path <path>", "Data-relative file path").addOption(
16031
16362
  new Option7("--encoding <encoding>", "Read encoding").choices(["text", "base64"]).makeOptionMandatory()
16363
+ ).addHelpText(
16364
+ "after",
16365
+ "\nThe CLI's receive limit follows the persisted limit (config set-upload-limit), not bridge serve --upload-limit-bytes; a larger response closes this command's connection.\nText escapes control bytes in JSON; read a binary file as base64."
16032
16366
  ).action(async function readFileCommand(options) {
16033
16367
  await executeRemoteCommand({
16034
16368
  commandName: "file.read",
@@ -16303,7 +16637,7 @@ function registerItem({ program, dependencies }) {
16303
16637
  addItemFieldOptions(
16304
16638
  addIdempotencyKeyOption(item.command("clone")).requiredOption("--item-id <itemId>", "Source item id").option("--name <name>", "Name override for the clone"),
16305
16639
  "clone"
16306
- ).action(async function cloneItem(options) {
16640
+ ).option("--patch-json <json>", "Override fields for the clone as a JSON object (merged last)").action(async function cloneItem(options) {
16307
16641
  await executeRemoteCommand({
16308
16642
  commandName: "item.clone",
16309
16643
  params: createItemCloneParams(options),
@@ -16474,7 +16808,7 @@ function registerJournal({ program, dependencies }) {
16474
16808
  addJournalFieldOptions(
16475
16809
  addIdempotencyKeyOption(journal.command("clone")).requiredOption("--journal-id <journalId>", "Source journal id").option("--name <name>", "Name override for the clone"),
16476
16810
  "clone"
16477
- ).action(async function cloneJournal(options) {
16811
+ ).option("--patch-json <json>", "Override fields for the clone as a JSON object (merged last)").action(async function cloneJournal(options) {
16478
16812
  await executeRemoteCommand({
16479
16813
  commandName: "journal.clone",
16480
16814
  params: createJournalCloneParams(options),
@@ -16490,6 +16824,64 @@ function registerJournal({ program, dependencies }) {
16490
16824
  dependencies
16491
16825
  });
16492
16826
  });
16827
+ const journalPage = journal.command("page").description("Journal-embedded page commands (one page at a time; journal update batches several)");
16828
+ journalPage.addHelpText(
16829
+ "after",
16830
+ `
16831
+ Result key (--json): .result.page (get and writes) / .result.pages[] (list), with .result.journalId.
16832
+ list returns summaries without text, system or flags, with textFormat (1 HTML, 2 Markdown, null on media pages); get returns the full page.
16833
+ Text fields belong to text pages, src to image, pdf and video pages, image.caption to image pages (schema journal.page.create).
16834
+ Text content goes through --data-json/--patch-json, e.g. --patch-json '{"text":{"content":"<p>\u2026</p>"}}'.
16835
+ Page ownership: journal ownership set --journal-id <id> --page-id <pageId>.`
16836
+ );
16837
+ addNameFilterOption(
16838
+ addPaginationOptions(journalPage.command("list").requiredOption("--journal-id <journalId>", "Journal id"))
16839
+ ).action(async function listJournalPages(options) {
16840
+ await executeRemoteCommand({
16841
+ commandName: "journal.page.list",
16842
+ params: { journalId: options.journalId, ...nameFilterParams(options), ...paginationParams(options) },
16843
+ command: this,
16844
+ dependencies
16845
+ });
16846
+ });
16847
+ journalPage.command("get").requiredOption("--journal-id <journalId>", "Journal id").requiredOption("--page-id <pageId>", "Page id").action(async function getJournalPage(options) {
16848
+ await executeRemoteCommand({
16849
+ commandName: "journal.page.get",
16850
+ params: { journalId: options.journalId, pageId: options.pageId },
16851
+ command: this,
16852
+ dependencies
16853
+ });
16854
+ });
16855
+ addJournalPageFieldOptions(
16856
+ addIdempotencyKeyOption(journalPage.command("create")).requiredOption("--journal-id <journalId>", "Journal id").requiredOption("--name <name>", "Page name").requiredOption("--type <type>", "Page type (text, image, pdf, video, or a system page type)"),
16857
+ "create"
16858
+ ).option("--data-json <json>", "Full/extra page data as a JSON object (merged last)").action(async function createJournalPage(options) {
16859
+ await executeRemoteCommand({
16860
+ commandName: "journal.page.create",
16861
+ params: createJournalPageCreateParams(options),
16862
+ command: this,
16863
+ dependencies
16864
+ });
16865
+ });
16866
+ addJournalPageFieldOptions(
16867
+ journalPage.command("update").requiredOption("--journal-id <journalId>", "Journal id").requiredOption("--page-id <pageId>", "Page id").option("--name <name>", "New page name").option("--type <type>", "New page type"),
16868
+ "update"
16869
+ ).option("--patch-json <json>", "Full/extra page patch as a JSON object (merged last)").action(async function updateJournalPage(options) {
16870
+ await executeRemoteCommand({
16871
+ commandName: "journal.page.update",
16872
+ params: createJournalPageUpdateParams(options),
16873
+ command: this,
16874
+ dependencies
16875
+ });
16876
+ });
16877
+ journalPage.command("delete").requiredOption("--journal-id <journalId>", "Journal id").requiredOption("--page-id <pageId>", "Page id").action(async function deleteJournalPage(options) {
16878
+ await executeRemoteCommand({
16879
+ commandName: "journal.page.delete",
16880
+ params: { journalId: options.journalId, pageId: options.pageId },
16881
+ command: this,
16882
+ dependencies
16883
+ });
16884
+ });
16493
16885
  const journalCategory = journal.command("category").description("Journal-embedded category commands (labels pages are grouped under)");
16494
16886
  journalCategory.addHelpText(
16495
16887
  "after",
@@ -16647,7 +17039,7 @@ function registerMacro({ program, dependencies }) {
16647
17039
  addMacroFieldOptions(
16648
17040
  addIdempotencyKeyOption(macro.command("clone")).requiredOption("--macro-id <macroId>", "Source macro id").option("--name <name>", "Name override for the clone"),
16649
17041
  "clone"
16650
- ).action(async function cloneMacroCommand(options) {
17042
+ ).option("--patch-json <json>", "Override fields for the clone as a JSON object (merged last)").action(async function cloneMacroCommand(options) {
16651
17043
  const command = await resolveMacroCommandBody(options, dependencies);
16652
17044
  await executeRemoteCommand({
16653
17045
  commandName: "macro.clone",
@@ -16669,6 +17061,10 @@ function registerMacro({ program, dependencies }) {
16669
17061
  "--macro-timeout-ms <ms>",
16670
17062
  `How long the GM client waits for the macro, in ms (default ${MACRO_EXECUTE_TIMEOUT_DEFAULT_MS}, max ${MACRO_EXECUTE_TIMEOUT_MAX_MS}). Raise the global --timeout-ms above it, or the CLI stops waiting before MACRO_TIMEOUT can be reported.`
16671
17063
  ).argParser(parsePositiveInt)
17064
+ ).addHelpText(
17065
+ "after",
17066
+ `
17067
+ A return value node with no JSON form (a function, an own accessor, a circular reference) comes back as {"$unserializable": "<reason>"} and is listed in .result.returnedOmissions (at most ${SERIALIZE_OMISSIONS_MAX} entries); a return value over the size limits is dropped and explained in .result.returnedOmitted.`
16672
17068
  ).action(async function executeMacroCommand(options) {
16673
17069
  await executeRemoteCommand({
16674
17070
  commandName: "macro.execute",
@@ -16954,7 +17350,7 @@ function registerScene({ program, dependencies }) {
16954
17350
  addSceneFieldOptions(
16955
17351
  addIdempotencyKeyOption(scene.command("clone")).requiredOption("--scene-id <sceneId>", "Source scene id").option("--name <name>", "Name override for the clone"),
16956
17352
  "clone"
16957
- ).action(async function cloneScene(options) {
17353
+ ).option("--patch-json <json>", "Override fields for the clone as a JSON object (merged last)").action(async function cloneScene(options) {
16958
17354
  await executeRemoteCommand({
16959
17355
  commandName: "scene.clone",
16960
17356
  params: createSceneCloneParams(options),
@@ -17028,17 +17424,29 @@ function registerScene({ program, dependencies }) {
17028
17424
  const sceneToken = scene.command("token").description("Scene-embedded token commands");
17029
17425
  addNameFilterOption(
17030
17426
  addPaginationOptions(sceneToken.command("list").requiredOption("--scene-id <sceneId>", "Scene id"))
17427
+ ).option(
17428
+ "--include <fields>",
17429
+ "Comma-separated extra fields to include (allowed: delta \u2014 a derived per-token summary of the token-level overrides, null when there are none)",
17430
+ parseTokenListIncludeFields
17431
+ ).addHelpText(
17432
+ "after",
17433
+ "\nThe tokens of one actor across all scenes: world references --uuid Actor.<actorId> --scope scene"
17031
17434
  ).action(async function listSceneTokens(options) {
17032
17435
  await executeRemoteCommand({
17033
17436
  commandName: "scene.token.list",
17034
- params: { sceneId: options.sceneId, ...nameFilterParams(options), ...paginationParams(options) },
17437
+ params: {
17438
+ sceneId: options.sceneId,
17439
+ ...nameFilterParams(options),
17440
+ ...paginationParams(options),
17441
+ ...options.include ? { include: options.include } : {}
17442
+ },
17035
17443
  command: this,
17036
17444
  dependencies
17037
17445
  });
17038
17446
  });
17039
17447
  sceneToken.command("get").requiredOption("--scene-id <sceneId>", "Scene id").requiredOption("--token-id <tokenId>", "Token id").option(
17040
17448
  "--include <fields>",
17041
- "Comma-separated extra fields to include (allowed: prepared)",
17449
+ "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
17450
  parseTokenIncludeFields
17043
17451
  ).action(async function getSceneToken(options) {
17044
17452
  await executeRemoteCommand({
@@ -17066,7 +17474,11 @@ function registerScene({ program, dependencies }) {
17066
17474
  addTokenFieldOptions(
17067
17475
  sceneToken.command("update").requiredOption("--scene-id <sceneId>", "Scene id").requiredOption("--token-id <tokenId>", "Token id"),
17068
17476
  "update"
17069
- ).option("--patch-json <json>", "Full/extra token patch as a JSON object (merged last)").action(async function updateSceneToken(options) {
17477
+ ).option("--patch-json <json>", "Full/extra token patch as a JSON object (merged last)").addHelpText(
17478
+ "after",
17479
+ `
17480
+ A patch with "delta": {"items": []} keeps the token's item overrides. scene token restore-delta drops every token override; scene token item update/delete changes single items.`
17481
+ ).action(async function updateSceneToken(options) {
17070
17482
  await executeRemoteCommand({
17071
17483
  commandName: "scene.token.update",
17072
17484
  params: createSceneTokenUpdateParams(options),
@@ -17093,6 +17505,16 @@ function registerScene({ program, dependencies }) {
17093
17505
  dependencies
17094
17506
  });
17095
17507
  });
17508
+ sceneToken.command("restore-delta").description(
17509
+ "Discard every token-level override of an unlinked token (items, effects, system data, name, \u2026) so it follows its world actor again"
17510
+ ).requiredOption("--scene-id <sceneId>", "Scene id").requiredOption("--token-id <tokenId>", "Unlinked token id").action(async function restoreSceneTokenDelta(options) {
17511
+ await executeRemoteCommand({
17512
+ commandName: "scene.token.restore-delta",
17513
+ params: { sceneId: options.sceneId, tokenId: options.tokenId },
17514
+ command: this,
17515
+ dependencies
17516
+ });
17517
+ });
17096
17518
  registerBatchWriteCommands(sceneToken, {
17097
17519
  prefix: "scene.token",
17098
17520
  noun: "token",
@@ -17162,12 +17584,13 @@ function registerScene({ program, dependencies }) {
17162
17584
  addEmbeddedItemFieldOptions(
17163
17585
  addIdempotencyKeyOption(sceneTokenItem.command("clone")).requiredOption("--scene-id <sceneId>", "Scene id").requiredOption("--token-id <tokenId>", "Token id").requiredOption("--item-id <itemId>", "Source item id").option("--name <name>", "Name override for the clone"),
17164
17586
  "clone"
17165
- ).action(async function cloneSceneTokenItem(options) {
17587
+ ).option("--patch-json <json>", "Override fields for the clone as a JSON object (merged last)").action(async function cloneSceneTokenItem(options) {
17166
17588
  const patch = {
17167
17589
  ...stringField("name", options.name),
17168
17590
  ...stringField("img", options.img),
17169
17591
  ...numberField("sort", options.sort),
17170
- ...jsonObjectField("system", options.systemJson, "--system-json")
17592
+ ...jsonObjectField("system", options.systemJson, "--system-json"),
17593
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
17171
17594
  };
17172
17595
  await executeRemoteCommand({
17173
17596
  commandName: "scene.token.item.clone",
@@ -18036,7 +18459,11 @@ function registerSetting({ program, dependencies }) {
18036
18459
  });
18037
18460
  }
18038
18461
  );
18039
- setting.command("get").requiredOption("--namespace <namespace>", 'Setting namespace (e.g. "core" or a module id)').requiredOption("--key <key>", "Setting key within the namespace").action(async function getSetting(options) {
18462
+ setting.command("get").requiredOption("--namespace <namespace>", 'Setting namespace (e.g. "core" or a module id)').requiredOption("--key <key>", "Setting key within the namespace").addHelpText(
18463
+ "after",
18464
+ `
18465
+ A value node with no JSON form (an accessor property, a function, a circular reference) comes back as {"$unserializable": "<reason>"} and is listed in .result.setting.valueOmissions (at most ${SERIALIZE_OMISSIONS_MAX} entries; valueOmissionsTruncated marks a longer list). Such a value is incomplete: writing it back with setting set stores the markers as data.`
18466
+ ).action(async function getSetting(options) {
18040
18467
  await executeRemoteCommand({
18041
18468
  commandName: "setting.get",
18042
18469
  params: { namespace: options.namespace, key: options.key },
@@ -18050,7 +18477,7 @@ function registerSetting({ program, dependencies }) {
18050
18477
  "--ids <list>",
18051
18478
  'Comma-separated setting ids in "namespace.key" form (e.g. core.chatBubbles,my-module.apiKey)',
18052
18479
  parseIdList
18053
- ).action(async function getManySettings(options) {
18480
+ ).addHelpText("after", "\nEach row reports omissions like setting get, in valueOmissions.").action(async function getManySettings(options) {
18054
18481
  await executeRemoteCommand({
18055
18482
  commandName: "setting.get-many",
18056
18483
  params: { ids: options.ids },
@@ -18062,6 +18489,9 @@ function registerSetting({ program, dependencies }) {
18062
18489
  "--value-json <json>",
18063
18490
  'New value as a JSON literal \u2014 true, 42, "text", [..] or {..}; a quoted string stays distinct from a boolean or a number',
18064
18491
  (value) => parseJsonValue(value, "--value-json")
18492
+ ).addHelpText(
18493
+ "after",
18494
+ '\nprevious and value report omissions like setting get, in previousOmissions and valueOmissions. {"$unserializable": ...} markers in --value-json are stored as data.'
18065
18495
  ).action(async function setSetting(options) {
18066
18496
  await executeRemoteCommand({
18067
18497
  commandName: "setting.set",
@@ -18622,6 +19052,7 @@ function registerWorld({ program, dependencies }) {
18622
19052
  "",
18623
19053
  "Result key (--json): .result.broken[] (+ .result.checkedRefs / .result.checkedFiles / .result.skipped[], total/hasMore over broken) for audit-files.",
18624
19054
  "Result key (--json): .result.results[] (+ .result.total / .result.hasMore / .result.index) for search.",
19055
+ "Result key (--json): .result.references[] (+ .result.target / .result.total / .result.hasMore / .result.scannedDocuments / .result.selfReferences / .result.scopes) for references.",
18625
19056
  "",
18626
19057
  "search: results are SECTIONED \u2014 world rows first, then pack rows, each ranked among itself.",
18627
19058
  "`score` is comparable only WITHIN a section (the two corpora are separate indexes), so compare",
@@ -18653,6 +19084,53 @@ function registerWorld({ program, dependencies }) {
18653
19084
  dependencies
18654
19085
  });
18655
19086
  });
19087
+ addPaginationOptions(
19088
+ world.command("references").description("Report where stored world data references a document, by its UUID").requiredOption(
19089
+ "--uuid <uuid>",
19090
+ "Target document UUID: Actor.<id>, Scene.<id>.Token.<id>, Compendium.<package>.<pack>.<Type>.<id>, \u2026"
19091
+ ).option(
19092
+ "--scope <list>",
19093
+ `Comma-separated document families to scan: ${REFERENCE_SCOPES.join(",")} (default: ${REFERENCE_DEFAULT_SCOPES.join(",")}; chat only on request)`,
19094
+ parseScopeList
19095
+ ).addHelpText(
19096
+ "after",
19097
+ [
19098
+ "",
19099
+ "Scans the stored data, flags and _stats included, of every world document in scope, its embedded",
19100
+ "documents, and every user. Each row names the owning document by UUID and the dotted field path in it;",
19101
+ "a match inside a token's delta is reported on the token (field delta.\u2026). An embedded target needs its",
19102
+ "full UUID (Scene.<s>.Token.<t>).",
19103
+ "match: uuid a field holds the target UUID",
19104
+ " link an @UUID[...] link or a legacy @Actor[<id>] / @Compendium[<pack>.<id>] link names the target",
19105
+ " embedded a field holds the UUID of a document embedded in the target (matchedUuid); deleting the",
19106
+ " target breaks it too",
19107
+ " key an object key names the target; a dotted key Foundry stored nested is reported at the",
19108
+ " target's id (flags.mod.Actor.<id>)",
19109
+ " relative a relative link (@UUID[.<id>], @Embed[.<id>]) in journal page text or a roll table or",
19110
+ " table result description resolves to the target or a document in it (matchedUuid)",
19111
+ " id a foreign-key field of the target's type (token actorId, note entryId, folder, setting user,",
19112
+ " chat speaker, \u2026) or a hotbar slot (hotbar.<slot>) holds the target's id; for an embedded",
19113
+ " target only where the record also names its parent or sits inside it, and never for a",
19114
+ " deeper target such as a token's actor",
19115
+ "setting: world and user settings, read as stored JSON (rows Setting.<id>, name = key, field value[.<path>]).",
19116
+ "Not found: compendium contents, client settings, bare ids outside foreign-key fields (system data, object",
19117
+ "keys, chat whisper lists) or in fields Foundry maintains itself (_stats.lastModifiedBy), name-based links",
19118
+ "(@Actor[Goblin]), relative links in other fields. References inside the target itself are counted in",
19119
+ "selfReferences. target.exists is null for a compendium target, whose pack is not loaded."
19120
+ ].join("\n")
19121
+ )
19122
+ ).action(async function findReferences(options) {
19123
+ await executeRemoteCommand({
19124
+ commandName: "world.references",
19125
+ params: {
19126
+ uuid: options.uuid,
19127
+ ...options.scope ? { scope: options.scope } : {},
19128
+ ...paginationParams(options)
19129
+ },
19130
+ command: this,
19131
+ dependencies
19132
+ });
19133
+ });
18656
19134
  addPaginationOptions(
18657
19135
  world.command("search").description("Search world (and optionally compendium) documents through the bridge index").requiredOption("--query <query>", "Search query (2-256 characters; terms are AND-combined)").addOption(
18658
19136
  new Option15(