fvtt-world-cli 1.1.1 → 1.2.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.1.1";
13
+ var PROTOCOL_VERSION = "1.2.0";
14
14
  var PROTOCOL_COMPONENTS = Object.freeze({
15
15
  MODULE: "module",
16
16
  CLI_DAEMON: "cli-daemon",
@@ -174,6 +174,7 @@ var ERROR_CODES = Object.freeze({
174
174
  TILE_NOT_FOUND: "TILE_NOT_FOUND",
175
175
  SOUND_NOT_FOUND: "SOUND_NOT_FOUND",
176
176
  WALL_NOT_FOUND: "WALL_NOT_FOUND",
177
+ LEVEL_NOT_FOUND: "LEVEL_NOT_FOUND",
177
178
  NOTE_NOT_FOUND: "NOTE_NOT_FOUND",
178
179
  DRAWING_NOT_FOUND: "DRAWING_NOT_FOUND",
179
180
  LIGHT_NOT_FOUND: "LIGHT_NOT_FOUND",
@@ -228,7 +229,8 @@ var ERROR_CODES = Object.freeze({
228
229
  APPROVAL_TIMEOUT: "APPROVAL_TIMEOUT",
229
230
  APPROVAL_CANCELLED: "APPROVAL_CANCELLED",
230
231
  APPROVAL_QUEUE_FULL: "APPROVAL_QUEUE_FULL",
231
- APPROVAL_UNKNOWN: "APPROVAL_UNKNOWN"
232
+ APPROVAL_UNKNOWN: "APPROVAL_UNKNOWN",
233
+ APPROVAL_STALE: "APPROVAL_STALE"
232
234
  });
233
235
 
234
236
  // ../protocol/src/schemas/shared.js
@@ -298,7 +300,7 @@ var includeProperty = {
298
300
  var actorIncludeProperty = {
299
301
  include: {
300
302
  type: "array",
301
- items: { type: "string", enum: ["flags", "effects", "items.flags", "items.effects", "prepared"] }
303
+ items: { type: "string", enum: ["flags", "effects", "items", "items.flags", "items.effects", "prepared"] }
302
304
  }
303
305
  };
304
306
  var batchIdsProperty = {
@@ -3152,6 +3154,68 @@ var executableBehaviorPatchSchema = {
3152
3154
  additionalProperties: true,
3153
3155
  minProperties: 1
3154
3156
  };
3157
+ var nullableNumberSchema = { type: ["number", "null"] };
3158
+ var levelImageProperties = {
3159
+ src: nullableStringSchema,
3160
+ tint: { type: "string", minLength: 1 },
3161
+ alphaThreshold: { type: "number" }
3162
+ };
3163
+ var levelCreateSchema = {
3164
+ type: "object",
3165
+ required: ["name"],
3166
+ properties: {
3167
+ name: { type: "string", minLength: 1 },
3168
+ elevation: {
3169
+ type: "object",
3170
+ required: [],
3171
+ properties: { bottom: nullableNumberSchema, top: nullableNumberSchema },
3172
+ additionalProperties: false
3173
+ },
3174
+ background: {
3175
+ type: "object",
3176
+ required: [],
3177
+ properties: { color: { type: "string", minLength: 1 }, ...levelImageProperties },
3178
+ additionalProperties: false
3179
+ },
3180
+ foreground: {
3181
+ type: "object",
3182
+ required: [],
3183
+ properties: levelImageProperties,
3184
+ additionalProperties: false
3185
+ },
3186
+ fog: {
3187
+ type: "object",
3188
+ required: [],
3189
+ properties: { src: nullableStringSchema },
3190
+ additionalProperties: false
3191
+ },
3192
+ textures: {
3193
+ type: "object",
3194
+ required: [],
3195
+ properties: {
3196
+ anchorX: { type: "number" },
3197
+ anchorY: { type: "number" },
3198
+ offsetX: { type: "integer" },
3199
+ offsetY: { type: "integer" },
3200
+ fit: { type: "string", minLength: 1 },
3201
+ scaleX: { type: "number" },
3202
+ scaleY: { type: "number" },
3203
+ rotation: { type: "number" }
3204
+ },
3205
+ additionalProperties: false
3206
+ },
3207
+ visibility: {
3208
+ type: "object",
3209
+ required: [],
3210
+ properties: { levels: { type: "array", items: { type: "string", minLength: 1 } } },
3211
+ additionalProperties: false
3212
+ },
3213
+ sort: { type: "integer" },
3214
+ flags: freeformObjectSchema
3215
+ },
3216
+ additionalProperties: false
3217
+ };
3218
+ var levelPatchSchema = patchFrom(levelCreateSchema);
3155
3219
  var sceneEmbeddedCommands = {
3156
3220
  "scene.token.list": cmd({
3157
3221
  type: "object",
@@ -3743,6 +3807,85 @@ var sceneEmbeddedCommands = {
3743
3807
  createSchema: wallCreateSchema,
3744
3808
  patchSchema: wallPatchSchema
3745
3809
  }),
3810
+ "scene.level.list": cmd({
3811
+ type: "object",
3812
+ required: ["sceneId"],
3813
+ properties: {
3814
+ sceneId: { type: "string", minLength: 1 },
3815
+ ...paginationProperties
3816
+ },
3817
+ additionalProperties: false
3818
+ }),
3819
+ "scene.level.get": cmd({
3820
+ type: "object",
3821
+ required: ["sceneId", "levelId"],
3822
+ properties: {
3823
+ sceneId: { type: "string", minLength: 1 },
3824
+ levelId: { type: "string", minLength: 1 }
3825
+ },
3826
+ additionalProperties: false
3827
+ }),
3828
+ "scene.level.create": cmd(
3829
+ {
3830
+ type: "object",
3831
+ required: ["sceneId", "data"],
3832
+ properties: {
3833
+ sceneId: { type: "string", minLength: 1 },
3834
+ data: levelCreateSchema,
3835
+ ...dryRunProperty,
3836
+ ...idempotencyKeyProperty
3837
+ },
3838
+ additionalProperties: false
3839
+ },
3840
+ { mutation: true }
3841
+ ),
3842
+ "scene.level.update": cmd(
3843
+ {
3844
+ type: "object",
3845
+ required: ["sceneId", "levelId", "patch"],
3846
+ properties: {
3847
+ sceneId: { type: "string", minLength: 1 },
3848
+ levelId: { type: "string", minLength: 1 },
3849
+ patch: levelPatchSchema,
3850
+ ...dryRunProperty
3851
+ },
3852
+ additionalProperties: false
3853
+ },
3854
+ { mutation: true }
3855
+ ),
3856
+ "scene.level.delete": cmd(
3857
+ {
3858
+ type: "object",
3859
+ required: ["sceneId", "levelId"],
3860
+ properties: {
3861
+ sceneId: { type: "string", minLength: 1 },
3862
+ levelId: { type: "string", minLength: 1 },
3863
+ ...dryRunProperty
3864
+ },
3865
+ additionalProperties: false
3866
+ },
3867
+ { mutation: true }
3868
+ ),
3869
+ "scene.level.clone": cmd(
3870
+ {
3871
+ type: "object",
3872
+ required: ["sceneId", "levelId"],
3873
+ properties: {
3874
+ sceneId: { type: "string", minLength: 1 },
3875
+ levelId: { type: "string", minLength: 1 },
3876
+ patch: levelPatchSchema,
3877
+ ...dryRunProperty,
3878
+ ...idempotencyKeyProperty
3879
+ },
3880
+ additionalProperties: false
3881
+ },
3882
+ { mutation: true }
3883
+ ),
3884
+ ...batchWriteCommands("scene.level", {
3885
+ scopeProperties: sceneScopeProperties,
3886
+ createSchema: levelCreateSchema,
3887
+ patchSchema: levelPatchSchema
3888
+ }),
3746
3889
  "scene.note.list": cmd({
3747
3890
  type: "object",
3748
3891
  required: ["sceneId"],
@@ -4287,6 +4430,10 @@ var sceneBackgroundSchema = {
4287
4430
  scaleX: { type: "number" },
4288
4431
  scaleY: { type: "number" },
4289
4432
  rotation: { type: "number" },
4433
+ anchorX: { type: "number" },
4434
+ anchorY: { type: "number" },
4435
+ fit: { type: "string", minLength: 1 },
4436
+ alphaThreshold: { type: "number" },
4290
4437
  tint: { type: "string", minLength: 1 }
4291
4438
  },
4292
4439
  additionalProperties: false,
@@ -4298,15 +4445,12 @@ var sceneGridSchema = {
4298
4445
  properties: {
4299
4446
  type: { type: "number" },
4300
4447
  size: { type: "number" },
4301
- sizeX: { type: "number" },
4302
- sizeY: { type: "number" },
4303
4448
  distance: { type: "number" },
4304
4449
  units: { type: "string" },
4305
4450
  style: { type: "string" },
4306
4451
  thickness: { type: "number" },
4307
4452
  color: { type: "string", minLength: 1 },
4308
- alpha: { type: "number" },
4309
- diagonals: { type: "number" }
4453
+ alpha: { type: "number" }
4310
4454
  },
4311
4455
  additionalProperties: false,
4312
4456
  minProperties: 1
@@ -4316,6 +4460,7 @@ var sceneCreateSchema = {
4316
4460
  required: ["name"],
4317
4461
  properties: {
4318
4462
  name: { type: "string", minLength: 1 },
4463
+ folder: nullableStringSchema,
4319
4464
  active: { type: "boolean" },
4320
4465
  navigation: { type: "boolean" },
4321
4466
  navOrder: { type: "number" },
@@ -4332,7 +4477,7 @@ var sceneCreateSchema = {
4332
4477
  navName: nullableStringSchema,
4333
4478
  thumb: nullableStringSchema,
4334
4479
  sort: { type: "number" },
4335
- initialLevel: nullableStringSchema,
4480
+ levels: { type: "array", minItems: 1, items: levelCreateSchema },
4336
4481
  playlist: nullableStringSchema,
4337
4482
  playlistSound: nullableStringSchema,
4338
4483
  journal: nullableStringSchema,
@@ -4347,7 +4492,10 @@ var sceneCreateSchema = {
4347
4492
  },
4348
4493
  additionalProperties: false
4349
4494
  };
4350
- var scenePatchSchema = patchFrom(sceneCreateSchema);
4495
+ var scenePatchSchema = patchFrom(sceneCreateSchema, {
4496
+ omit: ["levels"],
4497
+ prepend: { initialLevel: nullableStringSchema }
4498
+ });
4351
4499
  var sceneCommands = {
4352
4500
  "scene.list": cmd(nameFilteredListSchema),
4353
4501
  "scene.get": cmd(sceneIdSchema),
@@ -4560,7 +4708,14 @@ var settingCommands = {
4560
4708
  // ../protocol/src/schemas/system.js
4561
4709
  var systemCommands = {
4562
4710
  "system.ping": cmd(emptyObjectSchema),
4563
- "system.info": cmd(emptyObjectSchema),
4711
+ "system.info": cmd({
4712
+ type: "object",
4713
+ required: [],
4714
+ properties: {
4715
+ include: { type: "array", items: { type: "string", enum: ["modules"] } }
4716
+ },
4717
+ additionalProperties: false
4718
+ }),
4564
4719
  "system.reload": cmd(emptyObjectSchema)
4565
4720
  };
4566
4721
 
@@ -5256,6 +5411,15 @@ var DEFAULT_COMMAND_PROFILE = deepFreeze({
5256
5411
  "scene.wall.create-many": "allow",
5257
5412
  "scene.wall.update-many": "allow",
5258
5413
  "scene.wall.delete-many": "approve",
5414
+ "scene.level.list": "allow",
5415
+ "scene.level.get": "allow",
5416
+ "scene.level.create": "allow",
5417
+ "scene.level.update": "allow",
5418
+ "scene.level.delete": "approve",
5419
+ "scene.level.clone": "allow",
5420
+ "scene.level.create-many": "allow",
5421
+ "scene.level.update-many": "allow",
5422
+ "scene.level.delete-many": "approve",
5259
5423
  "scene.note.list": "allow",
5260
5424
  "scene.note.get": "allow",
5261
5425
  "scene.note.create": "allow",
@@ -6118,11 +6282,22 @@ function createCliConfigStore(configPath = getDefaultCliConfigPath()) {
6118
6282
  }
6119
6283
  };
6120
6284
  }
6121
- function mergePersistedCliConfig(existingConfig, updates) {
6122
- return PersistedCliConfigSchema.parse({
6123
- ...existingConfig ?? createEmptyConfig(),
6124
- ...updates
6125
- });
6285
+ var DAEMON_OWNED_CONFIG_KEYS = ["version", "daemonUrl", "deviceCredential", "pairings"];
6286
+ function withDaemonOwnedKeys(config, owner) {
6287
+ const merged = { ...config };
6288
+ for (const key of DAEMON_OWNED_CONFIG_KEYS) {
6289
+ if (owner[key] === void 0) delete merged[key];
6290
+ else merged[key] = owner[key];
6291
+ }
6292
+ return merged;
6293
+ }
6294
+ function writeCliConfigUpdate(configStore, updates) {
6295
+ configStore.writeConfig(
6296
+ PersistedCliConfigSchema.parse({
6297
+ ...configStore.readConfig() ?? createEmptyConfig(),
6298
+ ...updates
6299
+ })
6300
+ );
6126
6301
  }
6127
6302
  function resolveRemoteCommandConfig({
6128
6303
  flagDaemonUrl,
@@ -6168,8 +6343,8 @@ function readPendingApprovalDetails(envelope) {
6168
6343
  var DaemonTransportError = class extends Error {
6169
6344
  code;
6170
6345
  details;
6171
- constructor(code, message, details) {
6172
- super(message);
6346
+ constructor(code, message, details, options) {
6347
+ super(message, options);
6173
6348
  this.name = "DaemonTransportError";
6174
6349
  this.code = code;
6175
6350
  this.details = details;
@@ -6614,7 +6789,7 @@ var BridgeSessionStore = class {
6614
6789
  ok: false,
6615
6790
  error: createProtocolError({
6616
6791
  code: ERROR_CODES.UNKNOWN_COMMAND,
6617
- message: `Bridge session does not advertise support for ${request.command}; run system.info to see the connected bridge's supported command inventory (the module may be older than this CLI)`,
6792
+ message: `Bridge session does not advertise support for ${request.command}; details.supportedCommands lists the commands the connected bridge advertised, and \`fvtt-world-cli auth status\` reports them without the internal approval and policy commands; the Foundry module may be older than this CLI, or the GM page may need a reload`,
6618
6793
  details: {
6619
6794
  command: request.command,
6620
6795
  supportedCommands: this.activeSession.commands
@@ -7160,11 +7335,9 @@ var BridgeDaemon = class {
7160
7335
  }
7161
7336
  persistConfig() {
7162
7337
  if (!this.configStore) return;
7163
- const persisted = this.configStore.readConfig();
7164
- this.config = PersistedCliConfigSchema.parse({
7165
- ...this.config,
7166
- ...persisted?.uploadLimitBytes === void 0 ? {} : { uploadLimitBytes: persisted.uploadLimitBytes }
7167
- });
7338
+ this.config = PersistedCliConfigSchema.parse(
7339
+ withDaemonOwnedKeys(this.configStore.readConfig() ?? this.config, this.config)
7340
+ );
7168
7341
  this.configStore.writeConfig(this.config);
7169
7342
  }
7170
7343
  handlePairingRequest(socket, state, message) {
@@ -7971,23 +8144,35 @@ async function connectDaemonClient({
7971
8144
  socket.send(JSON.stringify(createClientHello({ credential: deviceCredential ?? "" })));
7972
8145
  });
7973
8146
  socket.once("message", (data) => {
7974
- const parsed = parseBridgeMessage(normalizeIncomingData(data));
7975
- const skew = parsed.ok ? protocolVersionSkewError(parsed.value) : null;
7976
- if (skew) {
8147
+ const decoded = decodeTransportFrame(data);
8148
+ if (!decoded.ok) {
8149
+ clearTimeout(timer);
8150
+ reject(decoded.error);
8151
+ socket.close();
8152
+ return;
8153
+ }
8154
+ const message = decoded.value;
8155
+ if (message.type !== MESSAGE_TYPES.CLIENT_HELLO_ACK) {
7977
8156
  clearTimeout(timer);
7978
- reject(skew);
8157
+ reject(
8158
+ new DaemonTransportError(
8159
+ ERROR_CODES.INVALID_MESSAGE,
8160
+ "Daemon returned an unexpected message type",
8161
+ {
8162
+ reason: "unexpected_type"
8163
+ }
8164
+ )
8165
+ );
7979
8166
  socket.close();
7980
8167
  return;
7981
8168
  }
7982
- const validation = parsed.ok ? validateTransportMessage(parsed.value) : { ok: false };
7983
- const message = parsed.ok && validation.ok ? parsed.value : null;
7984
- if (!message || message.type !== MESSAGE_TYPES.CLIENT_HELLO_ACK || !message.ok) {
8169
+ if (!message.ok) {
7985
8170
  clearTimeout(timer);
7986
8171
  reject(
7987
8172
  new DaemonTransportError(
7988
- message?.error?.code ?? ERROR_CODES.UNAUTHORIZED,
7989
- message?.error?.message ?? "Daemon rejected client authentication",
7990
- message?.error?.details
8173
+ message.error?.code ?? ERROR_CODES.UNAUTHORIZED,
8174
+ message.error?.message ?? "Daemon rejected client authentication",
8175
+ message.error?.details
7991
8176
  )
7992
8177
  );
7993
8178
  socket.close();
@@ -8002,7 +8187,8 @@ async function connectDaemonClient({
8002
8187
  new DaemonTransportError(
8003
8188
  ERROR_CODES.DAEMON_UNAVAILABLE,
8004
8189
  error instanceof Error ? error.message : String(error),
8005
- { reason: "connect_error" }
8190
+ { reason: "connect_error" },
8191
+ { cause: error }
8006
8192
  )
8007
8193
  );
8008
8194
  });
@@ -8522,23 +8708,21 @@ function parseCsvList(value, label) {
8522
8708
  }
8523
8709
  return items;
8524
8710
  }
8525
- var INCLUDE_ALLOWED = ["flags", "effects"];
8526
- function parseIncludeFields(value) {
8711
+ function parseAllowedIncludeFields(value, allowed) {
8527
8712
  const fields = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
8528
8713
  if (fields.length === 0) {
8529
- throw new InvalidArgumentError(
8530
- `--include must name at least one field (allowed: ${INCLUDE_ALLOWED.join(", ")})`
8531
- );
8714
+ throw new InvalidArgumentError(`--include must name at least one field (allowed: ${allowed.join(", ")})`);
8532
8715
  }
8533
8716
  for (const field of fields) {
8534
- if (!INCLUDE_ALLOWED.includes(field)) {
8535
- throw new InvalidArgumentError(
8536
- `--include only supports ${INCLUDE_ALLOWED.join(", ")}, received ${field}`
8537
- );
8717
+ if (!allowed.includes(field)) {
8718
+ throw new InvalidArgumentError(`--include only supports ${allowed.join(", ")}, received ${field}`);
8538
8719
  }
8539
8720
  }
8540
8721
  return fields;
8541
8722
  }
8723
+ function parseIncludeFields(value) {
8724
+ return parseAllowedIncludeFields(value, ["flags", "effects"]);
8725
+ }
8542
8726
  var OWNERSHIP_LEVELS = [-1, 0, 1, 2, 3];
8543
8727
  var INTEGER_PATTERN = /^-?\d+$/;
8544
8728
  function parseOwnershipLevel(value) {
@@ -8638,56 +8822,24 @@ function parseWhisperIds(value) {
8638
8822
  }
8639
8823
  return ids;
8640
8824
  }
8641
- var ACTOR_INCLUDE_ALLOWED = ["flags", "effects", "items.flags", "items.effects", "prepared"];
8642
8825
  function parseActorIncludeFields(value) {
8643
- const fields = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
8644
- if (fields.length === 0) {
8645
- throw new InvalidArgumentError(
8646
- `--include must name at least one field (allowed: ${ACTOR_INCLUDE_ALLOWED.join(", ")})`
8647
- );
8648
- }
8649
- for (const field of fields) {
8650
- if (!ACTOR_INCLUDE_ALLOWED.includes(field)) {
8651
- throw new InvalidArgumentError(
8652
- `--include only supports ${ACTOR_INCLUDE_ALLOWED.join(", ")}, received ${field}`
8653
- );
8654
- }
8655
- }
8656
- return fields;
8826
+ return parseAllowedIncludeFields(value, [
8827
+ "flags",
8828
+ "effects",
8829
+ "items",
8830
+ "items.flags",
8831
+ "items.effects",
8832
+ "prepared"
8833
+ ]);
8657
8834
  }
8658
- var TOKEN_INCLUDE_ALLOWED = ["prepared"];
8659
8835
  function parseTokenIncludeFields(value) {
8660
- const fields = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
8661
- if (fields.length === 0) {
8662
- throw new InvalidArgumentError(
8663
- `--include must name at least one field (allowed: ${TOKEN_INCLUDE_ALLOWED.join(", ")})`
8664
- );
8665
- }
8666
- for (const field of fields) {
8667
- if (!TOKEN_INCLUDE_ALLOWED.includes(field)) {
8668
- throw new InvalidArgumentError(
8669
- `--include only supports ${TOKEN_INCLUDE_ALLOWED.join(", ")}, received ${field}`
8670
- );
8671
- }
8672
- }
8673
- return fields;
8836
+ return parseAllowedIncludeFields(value, ["prepared"]);
8837
+ }
8838
+ function parseSystemInfoIncludeFields(value) {
8839
+ return parseAllowedIncludeFields(value, ["modules"]);
8674
8840
  }
8675
- var COMPENDIUM_INCLUDE_ALLOWED = ["effects"];
8676
8841
  function parseCompendiumIncludeFields(value) {
8677
- const fields = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
8678
- if (fields.length === 0) {
8679
- throw new InvalidArgumentError(
8680
- `--include must name at least one field (allowed: ${COMPENDIUM_INCLUDE_ALLOWED.join(", ")})`
8681
- );
8682
- }
8683
- for (const field of fields) {
8684
- if (!COMPENDIUM_INCLUDE_ALLOWED.includes(field)) {
8685
- throw new InvalidArgumentError(
8686
- `--include only supports ${COMPENDIUM_INCLUDE_ALLOWED.join(", ")}, received ${field}`
8687
- );
8688
- }
8689
- }
8690
- return fields;
8842
+ return parseAllowedIncludeFields(value, ["effects"]);
8691
8843
  }
8692
8844
  function parseCompendiumFieldsList(value) {
8693
8845
  return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
@@ -8785,44 +8937,10 @@ function createItemUpdateParams(options) {
8785
8937
  ...options.include ? { include: options.include } : {}
8786
8938
  };
8787
8939
  }
8788
- function createSceneUpdateParams(options) {
8789
- return {
8790
- sceneId: options.sceneId,
8791
- patch: assertNonEmptyPatch({
8792
- ...stringField("name", options.name),
8793
- ...booleanField("active", options.active),
8794
- ...booleanField("navigation", options.navigation),
8795
- ...numberField("navOrder", options.navOrder),
8796
- ...numberField("width", options.width),
8797
- ...numberField("height", options.height),
8798
- ...jsonObjectField("grid", options.gridJson, "--grid-json"),
8799
- ...jsonObjectField("background", options.backgroundJson, "--background-json"),
8800
- ...booleanField("tokenVision", options.tokenVision),
8801
- ...truthyStringField("weather", options.weather),
8802
- ...numberField("padding", options.padding),
8803
- ...numberField("shiftX", options.shiftX),
8804
- ...numberField("shiftY", options.shiftY),
8805
- ...truthyStringField("navName", options.navName),
8806
- ...truthyStringField("thumb", options.thumb),
8807
- ...truthyStringField("foreground", options.foreground),
8808
- ...numberField("foregroundElevation", options.foregroundElevation),
8809
- ...numberField("sort", options.sort),
8810
- ...truthyStringField("initialLevel", options.initialLevel),
8811
- ...truthyStringField("playlist", options.playlist),
8812
- ...truthyStringField("playlistSound", options.playlistSound),
8813
- ...truthyStringField("journal", options.journal),
8814
- ...truthyStringField("journalEntryPage", options.journalEntryPage),
8815
- ...jsonObjectField("environment", options.environmentJson, "--environment-json"),
8816
- ...jsonObjectField("fog", options.fogJson, "--fog-json"),
8817
- ...jsonObjectField("initial", options.initialJson, "--initial-json"),
8818
- ...jsonObjectField("transition", options.transitionJson, "--transition-json"),
8819
- ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
8820
- })
8821
- };
8822
- }
8823
8940
  function buildScenePatch(options) {
8824
8941
  return {
8825
8942
  ...stringField("name", options.name),
8943
+ ...folderField(options),
8826
8944
  ...booleanField("active", options.active),
8827
8945
  ...booleanField("navigation", options.navigation),
8828
8946
  ...numberField("navOrder", options.navOrder),
@@ -8851,6 +8969,15 @@ function buildScenePatch(options) {
8851
8969
  ...jsonObjectField("transition", options.transitionJson, "--transition-json")
8852
8970
  };
8853
8971
  }
8972
+ function createSceneUpdateParams(options) {
8973
+ return {
8974
+ sceneId: options.sceneId,
8975
+ patch: assertNonEmptyPatch({
8976
+ ...buildScenePatch(options),
8977
+ ...optionalJsonObject(options.patchJson, "--patch-json") ?? {}
8978
+ })
8979
+ };
8980
+ }
8854
8981
  function createSceneCreateParams(options) {
8855
8982
  return {
8856
8983
  data: {
@@ -9656,6 +9783,23 @@ function createSceneWallCloneParams(options) {
9656
9783
  ...optionalPatch(optionalJsonObject(options.patchJson, "--patch-json") ?? {})
9657
9784
  };
9658
9785
  }
9786
+ function createSceneLevelCreateParams(options) {
9787
+ return { sceneId: options.sceneId, data: optionalJsonObject(options.dataJson, "--data-json") ?? {} };
9788
+ }
9789
+ function createSceneLevelUpdateParams(options) {
9790
+ return {
9791
+ sceneId: options.sceneId,
9792
+ levelId: options.levelId,
9793
+ patch: assertNonEmptyPatch(optionalJsonObject(options.patchJson, "--patch-json") ?? {})
9794
+ };
9795
+ }
9796
+ function createSceneLevelCloneParams(options) {
9797
+ return {
9798
+ sceneId: options.sceneId,
9799
+ levelId: options.levelId,
9800
+ ...optionalPatch(optionalJsonObject(options.patchJson, "--patch-json") ?? {})
9801
+ };
9802
+ }
9659
9803
  function createSceneNoteCreateParams(options) {
9660
9804
  return { sceneId: options.sceneId, data: optionalJsonObject(options.dataJson, "--data-json") ?? {} };
9661
9805
  }
@@ -10368,7 +10512,7 @@ function renderActorDetails(actor) {
10368
10512
  if (actor && Object.hasOwn(actor, "prepared")) {
10369
10513
  lines.push(`prepared: ${Object.keys(actor.prepared ?? {}).join(", ")} (use --json for values)`);
10370
10514
  }
10371
- lines.push(`items: ${items.length}`);
10515
+ lines.push(`items: ${actor?.itemCount ?? items.length}`);
10372
10516
  items.forEach((item) => {
10373
10517
  lines.push(`item: ${renderItemSummaryLine(item)}`);
10374
10518
  });
@@ -11127,16 +11271,23 @@ function renderSceneDetails(scene) {
11127
11271
  const lines = [
11128
11272
  `id: ${scene?.id}`,
11129
11273
  `name: ${scene?.name}`,
11274
+ `folder: ${scene?.folder ?? ""}`,
11130
11275
  `active: ${String(Boolean(scene?.active))}`,
11131
11276
  `navigation: ${String(Boolean(scene?.navigation))}`,
11132
11277
  `nav order: ${scene?.navOrder ?? 0}`,
11133
11278
  `size: ${scene?.width}x${scene?.height}`,
11134
11279
  `grid: ${JSON.stringify(scene?.grid ?? null)}`,
11135
- `background: ${JSON.stringify(scene?.background ?? null)}`,
11280
+ ...scene && Object.hasOwn(scene, "background") ? [`background: ${JSON.stringify(scene.background ?? null)}`] : [],
11136
11281
  `token vision: ${String(Boolean(scene?.tokenVision))}`,
11137
11282
  `weather: ${scene?.weather ?? ""}`,
11138
11283
  `playlist: ${scene?.playlist ?? ""}`
11139
11284
  ];
11285
+ if (scene && Object.hasOwn(scene, "initialLevel")) {
11286
+ lines.push(`initial level: ${scene.initialLevel ?? "(first level)"}`);
11287
+ }
11288
+ for (const level of Array.isArray(scene?.levels) ? scene.levels : []) {
11289
+ lines.push(`level: ${renderLevelRow(level)}`);
11290
+ }
11140
11291
  lines.push(...renderCompendiumSourceLines(scene));
11141
11292
  if (scene && Object.hasOwn(scene, "ownership")) {
11142
11293
  lines.push(...renderOwnershipLines(scene.ownership));
@@ -11148,6 +11299,20 @@ function renderSceneDetails(scene) {
11148
11299
  }
11149
11300
  return lines.join("\n");
11150
11301
  }
11302
+ function levelMembership(placeable) {
11303
+ if (placeable && Object.hasOwn(placeable, "level")) return ["level", String(placeable.level)];
11304
+ if (placeable && Object.hasOwn(placeable, "levels"))
11305
+ return ["levels", placeable.levels.join(",") || "(all)"];
11306
+ return null;
11307
+ }
11308
+ function levelMembershipLines(placeable) {
11309
+ const membership = levelMembership(placeable);
11310
+ return membership ? [`${membership[0]}: ${membership[1]}`] : [];
11311
+ }
11312
+ function levelMembershipCell(placeable) {
11313
+ const membership = levelMembership(placeable);
11314
+ return membership ? ` ${membership[0]}=${membership[1]}` : "";
11315
+ }
11151
11316
  function renderTokenDetails(sceneId, token) {
11152
11317
  const lines = [
11153
11318
  `scene: ${sceneId}`,
@@ -11157,6 +11322,7 @@ function renderTokenDetails(sceneId, token) {
11157
11322
  `actorLink: ${String(Boolean(token?.actorLink))}`,
11158
11323
  `position: (${token?.x ?? 0}, ${token?.y ?? 0})`,
11159
11324
  `elevation: ${token?.elevation ?? 0}`,
11325
+ ...levelMembershipLines(token),
11160
11326
  `rotation: ${token?.rotation ?? 0}`,
11161
11327
  `hidden: ${String(Boolean(token?.hidden))}`,
11162
11328
  `disposition: ${token?.disposition ?? ""}`,
@@ -11205,6 +11371,7 @@ function renderTileDetails(sceneId, tile) {
11205
11371
  `size: ${tile?.width}x${tile?.height}`,
11206
11372
  `rotation: ${tile?.rotation ?? 0}`,
11207
11373
  `elevation: ${tile?.elevation ?? 0}`,
11374
+ ...levelMembershipLines(tile),
11208
11375
  `sort: ${tile?.sort ?? 0}`,
11209
11376
  `hidden: ${String(Boolean(tile?.hidden))}`,
11210
11377
  `locked: ${String(Boolean(tile?.locked))}`,
@@ -11218,6 +11385,7 @@ function renderSoundDetails(sceneId, sound) {
11218
11385
  `path: ${sound?.path ?? ""}`,
11219
11386
  `position: (${sound?.x ?? 0}, ${sound?.y ?? 0})`,
11220
11387
  `radius: ${sound?.radius ?? 0}`,
11388
+ ...levelMembershipLines(sound),
11221
11389
  `volume: ${sound?.volume ?? ""}`,
11222
11390
  `walls: ${String(Boolean(sound?.walls))}`,
11223
11391
  `easing: ${String(Boolean(sound?.easing))}`,
@@ -11225,11 +11393,30 @@ function renderSoundDetails(sceneId, sound) {
11225
11393
  `hidden: ${String(Boolean(sound?.hidden))}`
11226
11394
  ].join("\n");
11227
11395
  }
11396
+ function renderLevelRow(level) {
11397
+ const elevation = level?.elevation ?? {};
11398
+ return `${level?.id} ${level?.name ?? ""} elevation=${elevation.bottom ?? "-inf"}..${elevation.top ?? "+inf"} sort=${level?.sort ?? 0}`;
11399
+ }
11400
+ function renderLevelDetails(sceneId, level) {
11401
+ return [
11402
+ `scene: ${sceneId}`,
11403
+ `id: ${level?.id}`,
11404
+ `name: ${level?.name ?? ""}`,
11405
+ `elevation: ${JSON.stringify(level?.elevation ?? null)}`,
11406
+ `sort: ${level?.sort ?? 0}`,
11407
+ `background: ${JSON.stringify(level?.background ?? null)}`,
11408
+ `foreground: ${JSON.stringify(level?.foreground ?? null)}`,
11409
+ `fog: ${JSON.stringify(level?.fog ?? null)}`,
11410
+ `textures: ${JSON.stringify(level?.textures ?? null)}`,
11411
+ `visibility: ${JSON.stringify(level?.visibility ?? null)}`
11412
+ ].join("\n");
11413
+ }
11228
11414
  function renderWallDetails(sceneId, wall) {
11229
11415
  return [
11230
11416
  `scene: ${sceneId}`,
11231
11417
  `id: ${wall?.id}`,
11232
11418
  `c: ${JSON.stringify(wall?.c ?? null)}`,
11419
+ ...levelMembershipLines(wall),
11233
11420
  `door: ${wall?.door ?? 0}`,
11234
11421
  `ds: ${wall?.ds ?? 0}`,
11235
11422
  `doorSound: ${wall?.doorSound ?? ""}`,
@@ -11252,6 +11439,7 @@ function renderNoteDetails(sceneId, note) {
11252
11439
  `texture: ${JSON.stringify(note?.texture ?? null)}`,
11253
11440
  `iconSize: ${note?.iconSize ?? ""}`,
11254
11441
  `elevation: ${note?.elevation ?? 0}`,
11442
+ ...levelMembershipLines(note),
11255
11443
  `sort: ${note?.sort ?? 0}`,
11256
11444
  `global: ${String(Boolean(note?.global))}`
11257
11445
  ].join("\n");
@@ -11267,6 +11455,7 @@ function renderDrawingDetails(sceneId, drawing) {
11267
11455
  `shape: ${JSON.stringify(drawing?.shape ?? null)}`,
11268
11456
  `rotation: ${drawing?.rotation ?? 0}`,
11269
11457
  `elevation: ${drawing?.elevation ?? 0}`,
11458
+ ...levelMembershipLines(drawing),
11270
11459
  `sort: ${drawing?.sort ?? 0}`,
11271
11460
  `fillColor: ${drawing?.fillColor ?? ""}`,
11272
11461
  `strokeColor: ${drawing?.strokeColor ?? ""}`,
@@ -11281,6 +11470,7 @@ function renderLightDetails(sceneId, light) {
11281
11470
  `name: ${light?.name ?? ""}`,
11282
11471
  `position: (${light?.x ?? 0}, ${light?.y ?? 0})`,
11283
11472
  `elevation: ${light?.elevation ?? 0}`,
11473
+ ...levelMembershipLines(light),
11284
11474
  `rotation: ${light?.rotation ?? 0}`,
11285
11475
  `walls: ${String(Boolean(light?.walls))}`,
11286
11476
  `vision: ${String(Boolean(light?.vision))}`,
@@ -11311,6 +11501,7 @@ function renderRegionDetails(sceneId, region) {
11311
11501
  `color: ${region?.color ?? ""}`,
11312
11502
  `visibility: ${region?.visibility ?? ""}`,
11313
11503
  `elevation: ${JSON.stringify(region?.elevation ?? null)}`,
11504
+ ...levelMembershipLines(region),
11314
11505
  `shapes: ${JSON.stringify(region?.shapes ?? [])}`,
11315
11506
  `behaviors: ${JSON.stringify(region?.behaviors ?? [])}`,
11316
11507
  `locked: ${String(Boolean(region?.locked))}`
@@ -11613,10 +11804,12 @@ var RENDERERS = registerRenderers([
11613
11804
  `world: ${result.world?.title ?? "Unknown World"} [${result.world?.id ?? "unknown-world"}]`,
11614
11805
  `user: ${result.user?.name ?? "Unknown User"} [${result.user?.id ?? "unknown-user"}] GM=${String(Boolean(result.user?.isGM))}`,
11615
11806
  `bridge: ${result.bridge?.status ?? "unknown"}`,
11616
- `modules: ${(result.modules ?? []).length}`,
11807
+ `modules: ${result.moduleCount ?? "unknown"} installed, ${result.activeModuleCount ?? "unknown"} active`,
11808
+ ...(result.modules ?? []).map(
11809
+ (module) => ` ${module.id} ${module.version ?? "unknown"} ${module.active ? "active" : "inactive"}`
11810
+ ),
11617
11811
  `limits.uploadBytes: ${result.limits?.uploadBytes ?? "unknown"} (source: ${result.limits?.uploadSource ?? "unknown"})`,
11618
- `limits.wsMaxPayloadBytes: ${result.limits?.wsMaxPayloadBytes ?? "unknown"}`,
11619
- `commands: ${(result.commands ?? []).join(", ")}`
11812
+ `limits.wsMaxPayloadBytes: ${result.limits?.wsMaxPayloadBytes ?? "unknown"}`
11620
11813
  ].join("\n")
11621
11814
  ),
11622
11815
  detailRenderer(
@@ -12347,7 +12540,7 @@ var RENDERERS = registerRenderers([
12347
12540
  empty: "No tokens found.",
12348
12541
  heading: "Tokens",
12349
12542
  prefix: scenePrefix,
12350
- row: (token) => `${token.id} ${token.name} actor=${token.actorId ?? "-"} linked=${String(Boolean(token.actorLink))} (${token.x},${token.y})`
12543
+ row: (token) => `${token.id} ${token.name} actor=${token.actorId ?? "-"} linked=${String(Boolean(token.actorLink))} (${token.x},${token.y})${levelMembershipCell(token)}`
12351
12544
  }),
12352
12545
  detailRenderer(
12353
12546
  ["scene.token.get", "scene.token.create", "scene.token.update", "scene.token.clone"],
@@ -12371,7 +12564,7 @@ var RENDERERS = registerRenderers([
12371
12564
  empty: "No tiles found.",
12372
12565
  heading: "Tiles",
12373
12566
  prefix: scenePrefix,
12374
- row: (tile) => `${tile.id} (${tile.x},${tile.y}) ${tile.width}x${tile.height} hidden=${String(Boolean(tile.hidden))}`
12567
+ row: (tile) => `${tile.id} (${tile.x},${tile.y}) ${tile.width}x${tile.height} hidden=${String(Boolean(tile.hidden))}${levelMembershipCell(tile)}`
12375
12568
  }),
12376
12569
  detailRenderer(
12377
12570
  ["scene.tile.get", "scene.tile.create", "scene.tile.update", "scene.tile.clone"],
@@ -12383,7 +12576,7 @@ var RENDERERS = registerRenderers([
12383
12576
  empty: "No sounds found.",
12384
12577
  heading: "Sounds",
12385
12578
  prefix: scenePrefix,
12386
- row: (sound) => `${sound.id} ${sound.path ?? ""} (${sound.x},${sound.y}) r=${sound.radius} hidden=${String(Boolean(sound.hidden))}`
12579
+ row: (sound) => `${sound.id} ${sound.path ?? ""} (${sound.x},${sound.y}) r=${sound.radius} hidden=${String(Boolean(sound.hidden))}${levelMembershipCell(sound)}`
12387
12580
  }),
12388
12581
  detailRenderer(
12389
12582
  ["scene.sound.get", "scene.sound.create", "scene.sound.update", "scene.sound.clone"],
@@ -12395,20 +12588,32 @@ var RENDERERS = registerRenderers([
12395
12588
  empty: "No walls found.",
12396
12589
  heading: "Walls",
12397
12590
  prefix: scenePrefix,
12398
- row: (wall) => `${wall.id} ${wall.door ?? 0}/${wall.ds ?? 0}/${wall.doorSound ?? ""} c=[${(wall.c ?? []).join(",")}]`
12591
+ row: (wall) => `${wall.id} ${wall.door ?? 0}/${wall.ds ?? 0}/${wall.doorSound ?? ""} c=[${(wall.c ?? []).join(",")}]${levelMembershipCell(wall)}`
12399
12592
  }),
12400
12593
  detailRenderer(
12401
12594
  ["scene.wall.get", "scene.wall.create", "scene.wall.update", "scene.wall.clone"],
12402
12595
  (result) => renderWallDetails(result.sceneId, result.wall)
12403
12596
  ),
12404
12597
  deleteRenderer("scene.wall.delete", "wall", { suffix: sceneSuffix }),
12598
+ listRenderer("scene.level.list", {
12599
+ key: "levels",
12600
+ empty: "No levels found.",
12601
+ heading: "Levels",
12602
+ prefix: scenePrefix,
12603
+ row: (level) => renderLevelRow(level)
12604
+ }),
12605
+ detailRenderer(
12606
+ ["scene.level.get", "scene.level.create", "scene.level.update", "scene.level.clone"],
12607
+ (result) => renderLevelDetails(result.sceneId, result.level)
12608
+ ),
12609
+ deleteRenderer("scene.level.delete", "level", { suffix: sceneSuffix }),
12405
12610
  detailRenderer(BATCH_WRITE_COMMANDS, (result, command) => renderBatchWriteResult(command, result)),
12406
12611
  listRenderer("scene.note.list", {
12407
12612
  key: "notes",
12408
12613
  empty: "No notes found.",
12409
12614
  heading: "Notes",
12410
12615
  prefix: scenePrefix,
12411
- row: (note) => `${note.id} ${note.text ?? ""} (${note.x},${note.y}) ${note.texture?.src ?? ""} ${note.iconSize ?? ""}`
12616
+ row: (note) => `${note.id} ${note.text ?? ""} (${note.x},${note.y}) ${note.texture?.src ?? ""} ${note.iconSize ?? ""}${levelMembershipCell(note)}`
12412
12617
  }),
12413
12618
  detailRenderer(
12414
12619
  ["scene.note.get", "scene.note.create", "scene.note.update", "scene.note.clone"],
@@ -12420,7 +12625,7 @@ var RENDERERS = registerRenderers([
12420
12625
  empty: "No drawings found.",
12421
12626
  heading: "Drawings",
12422
12627
  prefix: scenePrefix,
12423
- row: (drawing) => `${drawing.id} ${drawing.text ?? ""} (${drawing.x},${drawing.y}) ${drawing.shape?.type ?? ""} ${String(Boolean(drawing.hidden))} ${drawing.name ?? "(no name)"}`
12628
+ row: (drawing) => `${drawing.id} ${drawing.text ?? ""} (${drawing.x},${drawing.y}) ${drawing.shape?.type ?? ""} ${String(Boolean(drawing.hidden))} ${drawing.name ?? "(no name)"}${levelMembershipCell(drawing)}`
12424
12629
  }),
12425
12630
  detailRenderer(
12426
12631
  ["scene.drawing.get", "scene.drawing.create", "scene.drawing.update", "scene.drawing.clone"],
@@ -12432,7 +12637,7 @@ var RENDERERS = registerRenderers([
12432
12637
  empty: "No lights found.",
12433
12638
  heading: "Lights",
12434
12639
  prefix: scenePrefix,
12435
- row: (light) => `${light.id} (${light.x},${light.y}) ${light.config?.dim ?? ""}/${light.config?.bright ?? ""} ${String(Boolean(light.hidden))} ${light.name ?? "(no name)"}`
12640
+ row: (light) => `${light.id} (${light.x},${light.y}) ${light.config?.dim ?? ""}/${light.config?.bright ?? ""} ${String(Boolean(light.hidden))} ${light.name ?? "(no name)"}${levelMembershipCell(light)}`
12436
12641
  }),
12437
12642
  detailRenderer(
12438
12643
  ["scene.light.get", "scene.light.create", "scene.light.update", "scene.light.clone"],
@@ -12456,7 +12661,7 @@ var RENDERERS = registerRenderers([
12456
12661
  empty: "No regions found.",
12457
12662
  heading: "Regions",
12458
12663
  prefix: scenePrefix,
12459
- row: (region) => `${region.id} ${region.name ?? ""} ${region.visibility ?? ""} shapes=${region.shapesCount ?? 0} behaviors=${region.behaviorsCount ?? 0}`
12664
+ row: (region) => `${region.id} ${region.name ?? ""} ${region.visibility ?? ""} shapes=${region.shapesCount ?? 0} behaviors=${region.behaviorsCount ?? 0}${levelMembershipCell(region)}`
12460
12665
  }),
12461
12666
  detailRenderer(
12462
12667
  ["scene.region.get", "scene.region.create", "scene.region.update", "scene.region.clone"],
@@ -12855,11 +13060,17 @@ function addReservedIncludeOption(command, presence) {
12855
13060
  function addActorIncludeOption(command) {
12856
13061
  return command.option(
12857
13062
  "--include <fields>",
12858
- "Comma-separated extra fields (items.flags, items.effects, prepared add output; flags/effects are always present, so those two have no effect)",
13063
+ "Comma-separated extra fields (items adds the owned items, which items.flags and items.effects also imply; prepared adds derived data; flags/effects are always present, so those two have no effect)",
12859
13064
  parseActorIncludeFields
12860
13065
  );
12861
13066
  }
13067
+ var INITIAL_LEVEL_FIELD = {
13068
+ flag: "--initial-level <initialLevel>",
13069
+ help: "Initial level id (Foundry v14)"
13070
+ };
12862
13071
  var SCENE_FIELDS = [
13072
+ FOLDER_ID_FIELD,
13073
+ CLEAR_FOLDER_FIELD,
12863
13074
  { flag: "--active <active>", help: "Scene active state", parser: parseBoolean },
12864
13075
  { flag: "--navigation <navigation>", help: "Scene navigation visibility", parser: parseBoolean },
12865
13076
  { flag: "--nav-order <navOrder>", help: "Scene navigation order", parser: parseNumber },
@@ -12872,14 +13083,14 @@ var SCENE_FIELDS = [
12872
13083
  { flag: "--shift-y <shiftY>", help: "Background Y shift", parser: parseNumber },
12873
13084
  { flag: "--nav-name <navName>", help: "Navigation label" },
12874
13085
  { flag: "--thumb <thumb>", help: "Thumbnail image path" },
12875
- { flag: "--foreground <foreground>", help: "Foreground image path" },
13086
+ { flag: "--foreground <foreground>", help: "Foreground image path (Foundry v13)" },
12876
13087
  {
12877
13088
  flag: "--foreground-elevation <foregroundElevation>",
12878
- help: "Foreground image elevation",
13089
+ help: "Foreground image elevation (Foundry v13)",
12879
13090
  parser: parseNumber
12880
13091
  },
12881
13092
  { flag: "--sort <sort>", help: "Sort order", parser: parseNumber },
12882
- { flag: "--initial-level <initialLevel>", help: "Initial elevation level id" },
13093
+ INITIAL_LEVEL_FIELD,
12883
13094
  { flag: "--playlist <playlistId>", help: "Linked playlist id" },
12884
13095
  { flag: "--playlist-sound <playlistSoundId>", help: "Linked playlist sound id" },
12885
13096
  { flag: "--journal <journalId>", help: "Linked journal id" },
@@ -12899,14 +13110,15 @@ var SCENE_FIELDS = [
12899
13110
  {
12900
13111
  flag: "--background-json <json>",
12901
13112
  help: {
12902
- create: "Scene background as JSON object",
12903
- update: "Scene background patch as JSON object",
12904
- clone: "Scene background override as JSON object"
13113
+ create: "Scene background as JSON object (Foundry v13)",
13114
+ update: "Scene background patch as JSON object (Foundry v13)",
13115
+ clone: "Scene background override as JSON object (Foundry v13)"
12905
13116
  }
12906
13117
  }
12907
13118
  ];
12908
13119
  function addSceneFieldOptions(command, verb) {
12909
- return addFieldOptions(command, verb, SCENE_FIELDS);
13120
+ const specs = verb === "create" ? SCENE_FIELDS.filter((spec) => spec !== INITIAL_LEVEL_FIELD) : SCENE_FIELDS;
13121
+ return addFieldOptions(command, verb, specs);
12910
13122
  }
12911
13123
  var TOKEN_NAME_FIELD = {
12912
13124
  flag: "--name <name>",
@@ -14350,7 +14562,9 @@ function createDaemonControlRunner(dependencies) {
14350
14562
  `);
14351
14563
  else if (response.ok) write(dependencies.stdout, `${JSON.stringify(response.result, null, 2)}
14352
14564
  `);
14353
- else
14565
+ else write(dependencies.stderr, `${renderProtocolError(response.error)}
14566
+ `);
14567
+ if (!response.ok)
14354
14568
  throw new CommanderError4(
14355
14569
  exitCodeForErrorCode(response.error?.code),
14356
14570
  "fvtt-world-cli.remoteError",
@@ -14683,11 +14897,9 @@ function recordSkillInstalls(dependencies, roots, { remove = false } = {}) {
14683
14897
  else current.add(root);
14684
14898
  }
14685
14899
  const skillInstalls = [...current].sort();
14686
- dependencies.configStore.writeConfig(
14687
- mergePersistedCliConfig(
14688
- existing,
14689
- skillInstalls.length > 0 ? { skillInstalls } : { skillInstalls: void 0 }
14690
- )
14900
+ writeCliConfigUpdate(
14901
+ dependencies.configStore,
14902
+ skillInstalls.length > 0 ? { skillInstalls } : { skillInstalls: void 0 }
14691
14903
  );
14692
14904
  } catch (error) {
14693
14905
  const message = error instanceof Error ? error.message : String(error);
@@ -15546,12 +15758,7 @@ function registerConfig({ program, dependencies }) {
15546
15758
  });
15547
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) {
15548
15760
  const uploadLimitBytes = parseUploadLimitBytes(size);
15549
- const persistedConfig = getStoredConfig(dependencies);
15550
- const nextConfig = mergePersistedCliConfig(persistedConfig, {
15551
- daemonUrl: persistedConfig?.daemonUrl ?? DEFAULT_DAEMON_URL,
15552
- uploadLimitBytes
15553
- });
15554
- dependencies.configStore.writeConfig(nextConfig);
15761
+ writeCliConfigUpdate(dependencies.configStore, { uploadLimitBytes });
15555
15762
  const payload = {
15556
15763
  configPath: dependencies.configStore.getConfigPath(),
15557
15764
  uploadLimitBytes,
@@ -16679,6 +16886,12 @@ function registerPlaylist({ program, dependencies }) {
16679
16886
 
16680
16887
  // src/commands/scene.ts
16681
16888
  import { Option as Option11 } from "commander";
16889
+ function addSceneLevelScopeOption(command) {
16890
+ return command.requiredOption("--scene-id <sceneId>", "Scene id");
16891
+ }
16892
+ function addSceneLevelTargetOptions(command, levelDescription = "Level id") {
16893
+ return addSceneLevelScopeOption(command).requiredOption("--level-id <levelId>", levelDescription);
16894
+ }
16682
16895
  function registerScene({ program, dependencies }) {
16683
16896
  const { registerOwnershipSet, registerCompendiumImport, registerBatchWriteCommands } = createSharedRegistrars(dependencies);
16684
16897
  const scene = program.command("scene").description("Foundry scene commands");
@@ -17254,6 +17467,58 @@ function registerScene({ program, dependencies }) {
17254
17467
  });
17255
17468
  });
17256
17469
  registerBatchWriteCommands(sceneWall, { prefix: "scene.wall", noun: "wall" });
17470
+ const sceneLevel = scene.command("level").description("Scene level commands (Foundry v14)");
17471
+ addPaginationOptions(addSceneLevelScopeOption(sceneLevel.command("list"))).action(
17472
+ async function listSceneLevels(options) {
17473
+ await executeRemoteCommand({
17474
+ commandName: "scene.level.list",
17475
+ params: { sceneId: options.sceneId, ...paginationParams(options) },
17476
+ command: this,
17477
+ dependencies
17478
+ });
17479
+ }
17480
+ );
17481
+ addSceneLevelTargetOptions(sceneLevel.command("get")).action(async function getSceneLevel(options) {
17482
+ await executeRemoteCommand({
17483
+ commandName: "scene.level.get",
17484
+ params: { sceneId: options.sceneId, levelId: options.levelId },
17485
+ command: this,
17486
+ dependencies
17487
+ });
17488
+ });
17489
+ addSceneLevelScopeOption(addIdempotencyKeyOption(sceneLevel.command("create"))).option("--data-json <json>", "Level data as a JSON object (name is required; e.g. elevation/background)").action(async function createSceneLevel(options) {
17490
+ await executeRemoteCommand({
17491
+ commandName: "scene.level.create",
17492
+ params: createSceneLevelCreateParams(options),
17493
+ command: this,
17494
+ dependencies
17495
+ });
17496
+ });
17497
+ addSceneLevelTargetOptions(sceneLevel.command("update")).option("--patch-json <json>", "Level patch as a JSON object (e.g. background/foreground/elevation)").action(async function updateSceneLevel(options) {
17498
+ await executeRemoteCommand({
17499
+ commandName: "scene.level.update",
17500
+ params: createSceneLevelUpdateParams(options),
17501
+ command: this,
17502
+ dependencies
17503
+ });
17504
+ });
17505
+ addSceneLevelTargetOptions(addIdempotencyKeyOption(sceneLevel.command("clone")), "Source level id").option("--patch-json <json>", "Override fields for the clone as a JSON object").action(async function cloneSceneLevel(options) {
17506
+ await executeRemoteCommand({
17507
+ commandName: "scene.level.clone",
17508
+ params: createSceneLevelCloneParams(options),
17509
+ command: this,
17510
+ dependencies
17511
+ });
17512
+ });
17513
+ addSceneLevelTargetOptions(sceneLevel.command("delete")).action(async function deleteSceneLevel(options) {
17514
+ await executeRemoteCommand({
17515
+ commandName: "scene.level.delete",
17516
+ params: { sceneId: options.sceneId, levelId: options.levelId },
17517
+ command: this,
17518
+ dependencies
17519
+ });
17520
+ });
17521
+ registerBatchWriteCommands(sceneLevel, { prefix: "scene.level", noun: "level" });
17257
17522
  const sceneNote = scene.command("note").description("Scene-embedded note (map pin) commands");
17258
17523
  addPaginationOptions(sceneNote.command("list").requiredOption("--scene-id <sceneId>", "Scene id")).action(
17259
17524
  async function listSceneNotes(options) {
@@ -18008,7 +18273,7 @@ function registerSystem({ program, dependencies }) {
18008
18273
  const system = program.command("system").description("Foundry bridge system commands");
18009
18274
  system.addHelpText(
18010
18275
  "after",
18011
- "\nResult key (--json): .result.pong + .result.bridge (ping); .result.foundry.* / .result.system.* / .result.world.* / .result.module.* / .result.modules[] / .result.bridge.* / .result.limits.* / .result.commands[] (info)."
18276
+ "\nResult key (--json): .result.pong + .result.bridge (ping); .result.foundry.* / .result.system.* / .result.world.* / .result.module.* / .result.moduleCount / .result.activeModuleCount / .result.modules[] (with --include modules) / .result.bridge.* / .result.limits.* (info)."
18012
18277
  );
18013
18278
  system.command("ping").action(async function ping() {
18014
18279
  await executeRemoteCommand({
@@ -18018,10 +18283,14 @@ function registerSystem({ program, dependencies }) {
18018
18283
  dependencies
18019
18284
  });
18020
18285
  });
18021
- system.command("info").action(async function info() {
18286
+ system.command("info").option(
18287
+ "--include <fields>",
18288
+ "Comma-separated extra fields (modules adds the installed module list)",
18289
+ parseSystemInfoIncludeFields
18290
+ ).action(async function info(options) {
18022
18291
  await executeRemoteCommand({
18023
18292
  commandName: "system.info",
18024
- params: {},
18293
+ params: options.include ? { include: options.include } : {},
18025
18294
  command: this,
18026
18295
  dependencies
18027
18296
  });