camstack 1.2.157 → 1.2.158

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.
@@ -21004,6 +21004,33 @@ var deviceExportCapability = {
21004
21004
  unexposeDevice: method(UnexposeInputSchema, external_exports.void(), { kind: "mutation" })
21005
21005
  }
21006
21006
  };
21007
+ var LOCATION_ICON_IDS = [
21008
+ "map-pin",
21009
+ "house",
21010
+ "door-open",
21011
+ "sofa",
21012
+ "utensils",
21013
+ "bed-double",
21014
+ "bath",
21015
+ "briefcase",
21016
+ "car",
21017
+ "circle-parking",
21018
+ "route",
21019
+ "trees",
21020
+ "umbrella",
21021
+ "waves-ladder",
21022
+ "fence",
21023
+ "package",
21024
+ "warehouse",
21025
+ "store",
21026
+ "sun",
21027
+ "radar"
21028
+ ];
21029
+ var LocationIconIdSchema = external_exports.enum(LOCATION_ICON_IDS);
21030
+ var LocationIconAssignmentSchema = external_exports.object({
21031
+ locationKey: external_exports.string(),
21032
+ icon: LocationIconIdSchema
21033
+ });
21007
21034
  var ProviderStatusSchema = external_exports.object({
21008
21035
  connected: external_exports.boolean(),
21009
21036
  deviceCount: external_exports.number(),
@@ -21653,7 +21680,11 @@ var deviceManagerCapability = {
21653
21680
  * reference this label keep their `meta.location` value — the
21654
21681
  * registry is purely a suggestion list. To purge from devices,
21655
21682
  * iterate them and call `setLocation(deviceId, null)`
21656
- * separately (or pass `cascade: true` to do it in one shot). */
21683
+ * separately (or pass `cascade: true` to do it in one shot).
21684
+ *
21685
+ * The room's ICON goes with it, always — a key left behind survives in a
21686
+ * map nobody lists, and a room later re-created under the same name would
21687
+ * silently inherit an icon the operator never chose for it. */
21657
21688
  removeLocation: method(external_exports.object({
21658
21689
  name: external_exports.string(),
21659
21690
  cascade: external_exports.boolean().optional()
@@ -21674,7 +21705,13 @@ var deviceManagerCapability = {
21674
21705
  * a suggestion list, and the devices that most need a rename are exactly
21675
21706
  * the ones carrying a label nobody registered. `to` renames onto an
21676
21707
  * existing room by MERGING into it, leaving no duplicate label. An empty
21677
- * `to` throws before anything is written. */
21708
+ * `to` throws before anything is written.
21709
+ *
21710
+ * The room's ICON travels with the rename, including when `from` was
21711
+ * never registered — otherwise it orphans on a key no location carries
21712
+ * any more. A merge keeps the TARGET's icon when it has one: renaming
21713
+ * "Sala" into "Salone" moves devices into Salone, it does not restyle
21714
+ * it. */
21678
21715
  renameLocation: method(external_exports.object({
21679
21716
  from: external_exports.string(),
21680
21717
  to: external_exports.string()
@@ -21682,6 +21719,39 @@ var deviceManagerCapability = {
21682
21719
  kind: "mutation",
21683
21720
  auth: "admin"
21684
21721
  }),
21722
+ /** Every location that carries an ICON, as `(locationKey, icon)` pairs.
21723
+ *
21724
+ * ADDITIVE, deliberately: `listLocations` keeps returning `string[]` —
21725
+ * every consumer of it (the Device Info autocomplete, the HA adoption
21726
+ * import, the recorder) reads a list of labels and none of them wants a
21727
+ * shape change. A surface that wants icons asks for them and joins.
21728
+ *
21729
+ * Only assignments that EXIST are returned — a location with no icon is
21730
+ * absent, not `map-pin`. The fallback belongs to the renderer
21731
+ * (`resolveLocationIcon`), not to the store, so "the operator chose the
21732
+ * pin" and "the operator chose nothing" stay distinguishable.
21733
+ *
21734
+ * The join key is `locationIconKey(label)` (trimmed, lower-cased) — the
21735
+ * same equality the registry uses everywhere. Never the operator's
21736
+ * casing: a case-only rename is a real edit here, and a map keyed on the
21737
+ * displayed spelling would orphan its entry on exactly that edit. */
21738
+ listLocationIcons: method(external_exports.void(), external_exports.array(LocationIconAssignmentSchema)),
21739
+ /** Set (or clear) the icon of a location label. `icon: null` removes the
21740
+ * assignment. The label need NOT be registered — the registry is a
21741
+ * suggestion list and devices carry labels nobody registered — but an
21742
+ * empty / whitespace-only label throws. Idempotent.
21743
+ *
21744
+ * The icon vocabulary is CLOSED (`LOCATION_ICON_IDS`) because the admin UI
21745
+ * and the viewer draw from different icon modules; see
21746
+ * `device/location-icon.ts` for the list and for what a surface that
21747
+ * cannot draw a member must do (fall back to `map-pin`, never a hole). */
21748
+ setLocationIcon: method(external_exports.object({
21749
+ location: external_exports.string(),
21750
+ icon: LocationIconIdSchema.nullable()
21751
+ }), external_exports.void(), {
21752
+ kind: "mutation",
21753
+ auth: "admin"
21754
+ }),
21685
21755
  /** Soft-disable / re-enable the device. Drivers consult
21686
21756
  * `BaseDevice.disabled` to gate lifecycle hooks. */
21687
21757
  setDisabled: method(external_exports.object({
@@ -42950,6 +43020,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
42950
43020
  addonId: null,
42951
43021
  access: "view"
42952
43022
  },
43023
+ "deviceManager.listLocationIcons": {
43024
+ capName: "device-manager",
43025
+ capScope: "system",
43026
+ addonId: null,
43027
+ access: "view"
43028
+ },
42953
43029
  "deviceManager.listLocations": {
42954
43030
  capName: "device-manager",
42955
43031
  capScope: "system",
@@ -43094,6 +43170,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
43094
43170
  addonId: null,
43095
43171
  access: "create"
43096
43172
  },
43173
+ "deviceManager.setLocationIcon": {
43174
+ capName: "device-manager",
43175
+ capScope: "system",
43176
+ addonId: null,
43177
+ access: "create"
43178
+ },
43097
43179
  "deviceManager.setMetadata": {
43098
43180
  capName: "device-manager",
43099
43181
  capScope: "system",
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-LFAXPGXW.js";
4
+ } from "./chunk-GDVPY652.js";
5
5
  import "./chunk-LMMQX4CK.js";
6
6
 
7
7
  // src/cli.ts
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-MZZ42ZMY.js");
41
+ await import("./launcher-MMBEAJYM.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-MZZ42ZMY.js");
86
+ await import("./launcher-MMBEAJYM.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -1140,7 +1140,7 @@ function isUnknown(_value) {
1140
1140
  return true;
1141
1141
  }
1142
1142
  async function resolveServerInteractive(presetNamespace) {
1143
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-AGYCTXAP.js");
1143
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-HGPO3SNF.js");
1144
1144
  if (presetNamespace) {
1145
1145
  const spinner4 = clack.spinner();
1146
1146
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -5,7 +5,7 @@ import {
5
5
  filterHubNodes,
6
6
  resolveHubFromDiscovered,
7
7
  runDiscover
8
- } from "./chunk-LFAXPGXW.js";
8
+ } from "./chunk-GDVPY652.js";
9
9
  import "./chunk-LMMQX4CK.js";
10
10
  export {
11
11
  DEFAULT_HUB_HTTPS_PORT,
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-BETx_5mH.js
23637
- var require_dist_BETx_5mH = __commonJS({
23638
- "../system/dist/dist-BETx_5mH.js"(exports) {
23636
+ // ../system/dist/dist-D6kqBW1G.js
23637
+ var require_dist_D6kqBW1G = __commonJS({
23638
+ "../system/dist/dist-D6kqBW1G.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -31763,6 +31763,35 @@ var require_dist_BETx_5mH = __commonJS({
31763
31763
  unexposeDevice: method(UnexposeInputSchema, zod.z.void(), { kind: "mutation" })
31764
31764
  }
31765
31765
  };
31766
+ var LocationIconIdSchema = zod.z.enum([
31767
+ "map-pin",
31768
+ "house",
31769
+ "door-open",
31770
+ "sofa",
31771
+ "utensils",
31772
+ "bed-double",
31773
+ "bath",
31774
+ "briefcase",
31775
+ "car",
31776
+ "circle-parking",
31777
+ "route",
31778
+ "trees",
31779
+ "umbrella",
31780
+ "waves-ladder",
31781
+ "fence",
31782
+ "package",
31783
+ "warehouse",
31784
+ "store",
31785
+ "sun",
31786
+ "radar"
31787
+ ]);
31788
+ var LocationIconAssignmentSchema = zod.z.object({
31789
+ locationKey: zod.z.string(),
31790
+ icon: LocationIconIdSchema
31791
+ });
31792
+ function locationIconKey(label) {
31793
+ return label.trim().toLowerCase();
31794
+ }
31766
31795
  var ProviderStatusSchema = zod.z.object({
31767
31796
  connected: zod.z.boolean(),
31768
31797
  deviceCount: zod.z.number(),
@@ -32412,7 +32441,11 @@ var require_dist_BETx_5mH = __commonJS({
32412
32441
  * reference this label keep their `meta.location` value — the
32413
32442
  * registry is purely a suggestion list. To purge from devices,
32414
32443
  * iterate them and call `setLocation(deviceId, null)`
32415
- * separately (or pass `cascade: true` to do it in one shot). */
32444
+ * separately (or pass `cascade: true` to do it in one shot).
32445
+ *
32446
+ * The room's ICON goes with it, always — a key left behind survives in a
32447
+ * map nobody lists, and a room later re-created under the same name would
32448
+ * silently inherit an icon the operator never chose for it. */
32416
32449
  removeLocation: method(zod.z.object({
32417
32450
  name: zod.z.string(),
32418
32451
  cascade: zod.z.boolean().optional()
@@ -32433,7 +32466,13 @@ var require_dist_BETx_5mH = __commonJS({
32433
32466
  * a suggestion list, and the devices that most need a rename are exactly
32434
32467
  * the ones carrying a label nobody registered. `to` renames onto an
32435
32468
  * existing room by MERGING into it, leaving no duplicate label. An empty
32436
- * `to` throws before anything is written. */
32469
+ * `to` throws before anything is written.
32470
+ *
32471
+ * The room's ICON travels with the rename, including when `from` was
32472
+ * never registered — otherwise it orphans on a key no location carries
32473
+ * any more. A merge keeps the TARGET's icon when it has one: renaming
32474
+ * "Sala" into "Salone" moves devices into Salone, it does not restyle
32475
+ * it. */
32437
32476
  renameLocation: method(zod.z.object({
32438
32477
  from: zod.z.string(),
32439
32478
  to: zod.z.string()
@@ -32441,6 +32480,39 @@ var require_dist_BETx_5mH = __commonJS({
32441
32480
  kind: "mutation",
32442
32481
  auth: "admin"
32443
32482
  }),
32483
+ /** Every location that carries an ICON, as `(locationKey, icon)` pairs.
32484
+ *
32485
+ * ADDITIVE, deliberately: `listLocations` keeps returning `string[]` —
32486
+ * every consumer of it (the Device Info autocomplete, the HA adoption
32487
+ * import, the recorder) reads a list of labels and none of them wants a
32488
+ * shape change. A surface that wants icons asks for them and joins.
32489
+ *
32490
+ * Only assignments that EXIST are returned — a location with no icon is
32491
+ * absent, not `map-pin`. The fallback belongs to the renderer
32492
+ * (`resolveLocationIcon`), not to the store, so "the operator chose the
32493
+ * pin" and "the operator chose nothing" stay distinguishable.
32494
+ *
32495
+ * The join key is `locationIconKey(label)` (trimmed, lower-cased) — the
32496
+ * same equality the registry uses everywhere. Never the operator's
32497
+ * casing: a case-only rename is a real edit here, and a map keyed on the
32498
+ * displayed spelling would orphan its entry on exactly that edit. */
32499
+ listLocationIcons: method(zod.z.void(), zod.z.array(LocationIconAssignmentSchema)),
32500
+ /** Set (or clear) the icon of a location label. `icon: null` removes the
32501
+ * assignment. The label need NOT be registered — the registry is a
32502
+ * suggestion list and devices carry labels nobody registered — but an
32503
+ * empty / whitespace-only label throws. Idempotent.
32504
+ *
32505
+ * The icon vocabulary is CLOSED (`LOCATION_ICON_IDS`) because the admin UI
32506
+ * and the viewer draw from different icon modules; see
32507
+ * `device/location-icon.ts` for the list and for what a surface that
32508
+ * cannot draw a member must do (fall back to `map-pin`, never a hole). */
32509
+ setLocationIcon: method(zod.z.object({
32510
+ location: zod.z.string(),
32511
+ icon: LocationIconIdSchema.nullable()
32512
+ }), zod.z.void(), {
32513
+ kind: "mutation",
32514
+ auth: "admin"
32515
+ }),
32444
32516
  /** Soft-disable / re-enable the device. Drivers consult
32445
32517
  * `BaseDevice.disabled` to gate lifecycle hooks. */
32446
32518
  setDisabled: method(zod.z.object({
@@ -54341,6 +54413,12 @@ var require_dist_BETx_5mH = __commonJS({
54341
54413
  addonId: null,
54342
54414
  access: "view"
54343
54415
  },
54416
+ "deviceManager.listLocationIcons": {
54417
+ capName: "device-manager",
54418
+ capScope: "system",
54419
+ addonId: null,
54420
+ access: "view"
54421
+ },
54344
54422
  "deviceManager.listLocations": {
54345
54423
  capName: "device-manager",
54346
54424
  capScope: "system",
@@ -54485,6 +54563,12 @@ var require_dist_BETx_5mH = __commonJS({
54485
54563
  addonId: null,
54486
54564
  access: "create"
54487
54565
  },
54566
+ "deviceManager.setLocationIcon": {
54567
+ capName: "device-manager",
54568
+ capScope: "system",
54569
+ addonId: null,
54570
+ access: "create"
54571
+ },
54488
54572
  "deviceManager.setMetadata": {
54489
54573
  capName: "device-manager",
54490
54574
  capScope: "system",
@@ -61909,6 +61993,12 @@ var require_dist_BETx_5mH = __commonJS({
61909
61993
  return LegacyStorageLocationDefaultSchema;
61910
61994
  }
61911
61995
  });
61996
+ Object.defineProperty(exports, "LocationIconIdSchema", {
61997
+ enumerable: true,
61998
+ get: function() {
61999
+ return LocationIconIdSchema;
62000
+ }
62001
+ });
61912
62002
  Object.defineProperty(exports, "METHOD_ACCESS_MAP", {
61913
62003
  enumerable: true,
61914
62004
  get: function() {
@@ -62311,6 +62401,12 @@ var require_dist_BETx_5mH = __commonJS({
62311
62401
  return localNetworkCapability;
62312
62402
  }
62313
62403
  });
62404
+ Object.defineProperty(exports, "locationIconKey", {
62405
+ enumerable: true,
62406
+ get: function() {
62407
+ return locationIconKey;
62408
+ }
62409
+ });
62314
62410
  Object.defineProperty(exports, "logDestinationCapability", {
62315
62411
  enumerable: true,
62316
62412
  get: function() {
@@ -62557,7 +62653,7 @@ var require_alerts_addon = __commonJS({
62557
62653
  [Symbol.toStringTag]: { value: "Module" }
62558
62654
  });
62559
62655
  require_chunk_Cek0wNdY();
62560
- var require_dist10 = require_dist_BETx_5mH();
62656
+ var require_dist10 = require_dist_D6kqBW1G();
62561
62657
  function selectExpired(alerts, cutoffMs) {
62562
62658
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
62563
62659
  }
@@ -63376,7 +63472,7 @@ var require_console_logging = __commonJS({
63376
63472
  [Symbol.toStringTag]: { value: "Module" }
63377
63473
  });
63378
63474
  require_chunk_Cek0wNdY();
63379
- var require_dist10 = require_dist_BETx_5mH();
63475
+ var require_dist10 = require_dist_D6kqBW1G();
63380
63476
  var require_formatter = require_formatter_DqAKDlvN();
63381
63477
  var LEVEL_RANK = {
63382
63478
  debug: 0,
@@ -63470,7 +63566,7 @@ var require_core_blocks_addon = __commonJS({
63470
63566
  "use strict";
63471
63567
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
63472
63568
  var require_chunk = require_chunk_Cek0wNdY();
63473
- var require_dist10 = require_dist_BETx_5mH();
63569
+ var require_dist10 = require_dist_D6kqBW1G();
63474
63570
  var node_crypto = __require("crypto");
63475
63571
  var node_fs_promises = __require("fs/promises");
63476
63572
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -64367,11 +64463,11 @@ var require_core_blocks = __commonJS({
64367
64463
  }
64368
64464
  });
64369
64465
 
64370
- // ../system/dist/retired-settings-keys-BnTBoLTv.js
64371
- var require_retired_settings_keys_BnTBoLTv = __commonJS({
64372
- "../system/dist/retired-settings-keys-BnTBoLTv.js"(exports) {
64466
+ // ../system/dist/retired-settings-keys-BPIyHR4_.js
64467
+ var require_retired_settings_keys_BPIyHR4 = __commonJS({
64468
+ "../system/dist/retired-settings-keys-BPIyHR4_.js"(exports) {
64373
64469
  "use strict";
64374
- var require_dist10 = require_dist_BETx_5mH();
64470
+ var require_dist10 = require_dist_D6kqBW1G();
64375
64471
  function settingsStoreIsAuthoritativeHere(env) {
64376
64472
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
64377
64473
  return raw === "" || raw === "hub";
@@ -66608,8 +66704,8 @@ var require_device_manager_addon = __commonJS({
66608
66704
  [Symbol.toStringTag]: { value: "Module" }
66609
66705
  });
66610
66706
  require_chunk_Cek0wNdY();
66611
- var require_dist10 = require_dist_BETx_5mH();
66612
- var require_retired_settings_keys = require_retired_settings_keys_BnTBoLTv();
66707
+ var require_dist10 = require_dist_D6kqBW1G();
66708
+ var require_retired_settings_keys = require_retired_settings_keys_BPIyHR4();
66613
66709
  var node_crypto = __require("crypto");
66614
66710
  var _camstack_types_node = require_node();
66615
66711
  var JOB_HISTORY = 20;
@@ -70335,6 +70431,42 @@ var require_device_manager_addon = __commonJS({
70335
70431
  await pctx.settings.writeAddonStore({ locations: [...current, trimmed] });
70336
70432
  });
70337
70433
  }
70434
+ async function listLocationIcons(pctx) {
70435
+ const icons = (await pctx.metaStore.readStore()).locationIcons ?? {};
70436
+ return Object.entries(icons).map(([locationKey, icon]) => ({
70437
+ locationKey,
70438
+ icon
70439
+ })).toSorted((a, b) => a.locationKey.localeCompare(b.locationKey));
70440
+ }
70441
+ async function setLocationIcon(pctx, input) {
70442
+ const trimmed = input.location.trim();
70443
+ if (trimmed.length === 0) throw new Error("[device-manager] setLocationIcon: location must be non-empty");
70444
+ const key = require_dist10.locationIconKey(trimmed);
70445
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70446
+ const icons = (await pctx.metaStore.readStore()).locationIcons ?? {};
70447
+ if (input.icon === null) {
70448
+ if (!(key in icons)) return;
70449
+ const { [key]: _cleared, ...kept } = icons;
70450
+ await pctx.settings.writeAddonStore({ locationIcons: kept });
70451
+ return;
70452
+ }
70453
+ if (icons[key] === input.icon) return;
70454
+ await pctx.settings.writeAddonStore({ locationIcons: {
70455
+ ...icons,
70456
+ [key]: input.icon
70457
+ } });
70458
+ });
70459
+ }
70460
+ function renameLocationIcon(icons, fromKey, toKey) {
70461
+ const moved = icons[fromKey];
70462
+ if (moved === void 0) return null;
70463
+ if (fromKey === toKey) return null;
70464
+ const { [fromKey]: _dropped, ...rest } = icons;
70465
+ return toKey in rest ? rest : {
70466
+ ...rest,
70467
+ [toKey]: moved
70468
+ };
70469
+ }
70338
70470
  async function renameLocation(pctx, input) {
70339
70471
  const from = input.from.trim();
70340
70472
  const to = input.to.trim();
@@ -70343,13 +70475,19 @@ var require_device_manager_addon = __commonJS({
70343
70475
  const fromKey = from.toLowerCase();
70344
70476
  const toKey = to.toLowerCase();
70345
70477
  await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70346
- const current = (await pctx.metaStore.readStore()).locations ?? [];
70347
- if (!current.some((l) => l.trim().toLowerCase() === fromKey)) return;
70478
+ const store = await pctx.metaStore.readStore();
70479
+ const current = store.locations ?? [];
70480
+ const registered = current.some((l) => l.trim().toLowerCase() === fromKey);
70481
+ const movedIcons = renameLocationIcon(store.locationIcons ?? {}, fromKey, toKey);
70482
+ if (!registered && movedIcons === null) return;
70348
70483
  const kept = current.filter((l) => {
70349
70484
  const key = l.trim().toLowerCase();
70350
70485
  return key !== fromKey && key !== toKey;
70351
70486
  });
70352
- await pctx.settings.writeAddonStore({ locations: [...kept, to] });
70487
+ await pctx.settings.writeAddonStore({
70488
+ ...registered ? { locations: [...kept, to] } : {},
70489
+ ...movedIcons !== null ? { locationIcons: movedIcons } : {}
70490
+ });
70353
70491
  });
70354
70492
  const moved = await pctx.metaStore.withMetaWriteLock(async () => {
70355
70493
  const out = [];
@@ -70387,10 +70525,19 @@ var require_device_manager_addon = __commonJS({
70387
70525
  const trimmed = input.name.trim();
70388
70526
  if (trimmed.length === 0) return;
70389
70527
  await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
70390
- const current = (await pctx.metaStore.readStore()).locations ?? [];
70391
- const remaining = current.filter((l) => l.toLowerCase() !== trimmed.toLowerCase());
70392
- if (remaining.length === current.length) return;
70393
- await pctx.settings.writeAddonStore({ locations: remaining });
70528
+ const store = await pctx.metaStore.readStore();
70529
+ const current = store.locations ?? [];
70530
+ const key = require_dist10.locationIconKey(trimmed);
70531
+ const remaining = current.filter((l) => l.toLowerCase() !== key);
70532
+ const droppedLabel = remaining.length !== current.length;
70533
+ const icons = store.locationIcons ?? {};
70534
+ const droppedIcon = key in icons;
70535
+ if (!droppedLabel && !droppedIcon) return;
70536
+ const { [key]: _removedIcon, ...keptIcons } = icons;
70537
+ await pctx.settings.writeAddonStore({
70538
+ ...droppedLabel ? { locations: remaining } : {},
70539
+ ...droppedIcon ? { locationIcons: keptIcons } : {}
70540
+ });
70394
70541
  });
70395
70542
  if (input.cascade !== true) return;
70396
70543
  const cleared = await pctx.metaStore.withMetaWriteLock(async () => {
@@ -70481,12 +70628,24 @@ var require_device_manager_addon = __commonJS({
70481
70628
  const nextDeviceId = raw["nextDeviceId"];
70482
70629
  const locations = raw["locations"];
70483
70630
  const roleDisplayDefaults = raw["roleDisplayDefaults"];
70631
+ const locationIcons = decodeLocationIcons(raw["locationIcons"]);
70484
70632
  return {
70485
70633
  ...typeof nextDeviceId === "number" && Number.isFinite(nextDeviceId) ? { nextDeviceId } : {},
70486
70634
  ...Array.isArray(locations) ? { locations: locations.filter((l) => typeof l === "string") } : {},
70487
- ...isRoleDisplayDefaults(roleDisplayDefaults) ? { roleDisplayDefaults } : {}
70635
+ ...isRoleDisplayDefaults(roleDisplayDefaults) ? { roleDisplayDefaults } : {},
70636
+ ...locationIcons !== null ? { locationIcons } : {}
70488
70637
  };
70489
70638
  }
70639
+ function decodeLocationIcons(raw) {
70640
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
70641
+ const out = {};
70642
+ for (const [key, value] of Object.entries(raw)) {
70643
+ const parsed = require_dist10.LocationIconIdSchema.safeParse(value);
70644
+ if (!parsed.success) continue;
70645
+ out[key] = parsed.data;
70646
+ }
70647
+ return out;
70648
+ }
70490
70649
  function isRoleDisplayDefaults(value) {
70491
70650
  return value !== null && typeof value === "object" && !Array.isArray(value);
70492
70651
  }
@@ -72127,6 +72286,8 @@ var require_device_manager_addon = __commonJS({
72127
72286
  addLocation: (input) => addLocation(pctx, input),
72128
72287
  removeLocation: (input) => removeLocation(pctx, input),
72129
72288
  renameLocation: (input) => renameLocation(pctx, input),
72289
+ listLocationIcons: () => listLocationIcons(pctx),
72290
+ setLocationIcon: (input) => setLocationIcon(pctx, input),
72130
72291
  listPersistedByAddon: (input) => listPersistedByAddon(pctx, input),
72131
72292
  listAll: (input) => listAll(pctx, input),
72132
72293
  getDevice: (input) => getDevice(pctx, input),
@@ -72333,7 +72494,7 @@ var require_hub_forwarder = __commonJS({
72333
72494
  [Symbol.toStringTag]: { value: "Module" }
72334
72495
  });
72335
72496
  require_chunk_Cek0wNdY();
72336
- var require_dist10 = require_dist_BETx_5mH();
72497
+ var require_dist10 = require_dist_D6kqBW1G();
72337
72498
  var require_formatter = require_formatter_DqAKDlvN();
72338
72499
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
72339
72500
  var HubForwarderDestination = class {
@@ -72470,7 +72631,7 @@ var require_liveness_monitor_addon = __commonJS({
72470
72631
  "use strict";
72471
72632
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
72472
72633
  require_chunk_Cek0wNdY();
72473
- var require_dist10 = require_dist_BETx_5mH();
72634
+ var require_dist10 = require_dist_D6kqBW1G();
72474
72635
  var NO_DEVICES = "liveness:no-devices";
72475
72636
  var ALL_OFFLINE = "liveness:all-devices-offline";
72476
72637
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -72666,7 +72827,7 @@ var require_local_auth_addon = __commonJS({
72666
72827
  [Symbol.toStringTag]: { value: "Module" }
72667
72828
  });
72668
72829
  var require_chunk = require_chunk_Cek0wNdY();
72669
- var require_dist10 = require_dist_BETx_5mH();
72830
+ var require_dist10 = require_dist_D6kqBW1G();
72670
72831
  var node_crypto = __require("crypto");
72671
72832
  node_crypto = require_chunk.__toESM(node_crypto);
72672
72833
  var crypto$1 = __require("crypto");
@@ -80479,7 +80640,7 @@ var require_loki_logging = __commonJS({
80479
80640
  [Symbol.toStringTag]: { value: "Module" }
80480
80641
  });
80481
80642
  require_chunk_Cek0wNdY();
80482
- var require_dist10 = require_dist_BETx_5mH();
80643
+ var require_dist10 = require_dist_D6kqBW1G();
80483
80644
  function sanitizeLabelName(raw) {
80484
80645
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
80485
80646
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -81066,7 +81227,7 @@ var require_native_metrics_addon = __commonJS({
81066
81227
  [Symbol.toStringTag]: { value: "Module" }
81067
81228
  });
81068
81229
  var require_chunk = require_chunk_Cek0wNdY();
81069
- var require_dist10 = require_dist_BETx_5mH();
81230
+ var require_dist10 = require_dist_D6kqBW1G();
81070
81231
  var node_fs_promises = __require("fs/promises");
81071
81232
  var node_fs = __require("fs");
81072
81233
  node_fs = require_chunk.__toESM(node_fs);
@@ -83819,7 +83980,7 @@ var require_filesystem_storage_addon = __commonJS({
83819
83980
  [Symbol.toStringTag]: { value: "Module" }
83820
83981
  });
83821
83982
  var require_chunk = require_chunk_Cek0wNdY();
83822
- var require_dist10 = require_dist_BETx_5mH();
83983
+ var require_dist10 = require_dist_D6kqBW1G();
83823
83984
  var node_crypto = __require("crypto");
83824
83985
  var node_fs_promises = __require("fs/promises");
83825
83986
  var node_path = __require("path");
@@ -85046,8 +85207,8 @@ var require_sqlite_settings_addon = __commonJS({
85046
85207
  [Symbol.toStringTag]: { value: "Module" }
85047
85208
  });
85048
85209
  var require_chunk = require_chunk_Cek0wNdY();
85049
- var require_dist10 = require_dist_BETx_5mH();
85050
- var require_retired_settings_keys = require_retired_settings_keys_BnTBoLTv();
85210
+ var require_dist10 = require_dist_D6kqBW1G();
85211
+ var require_retired_settings_keys = require_retired_settings_keys_BPIyHR4();
85051
85212
  var require_wal_checkpoint_policy = require_wal_checkpoint_policy_CCJZngds();
85052
85213
  var node_crypto = __require("crypto");
85053
85214
  var node_path = __require("path");
@@ -87834,7 +87995,7 @@ var require_storage_orchestrator_addon = __commonJS({
87834
87995
  [Symbol.toStringTag]: { value: "Module" }
87835
87996
  });
87836
87997
  var require_chunk = require_chunk_Cek0wNdY();
87837
- var require_dist10 = require_dist_BETx_5mH();
87998
+ var require_dist10 = require_dist_D6kqBW1G();
87838
87999
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
87839
88000
  var zod = require_zod();
87840
88001
  var node_crypto = __require("crypto");
@@ -91561,7 +91722,7 @@ var require_system_config_addon = __commonJS({
91561
91722
  [Symbol.toStringTag]: { value: "Module" }
91562
91723
  });
91563
91724
  require_chunk_Cek0wNdY();
91564
- var require_dist10 = require_dist_BETx_5mH();
91725
+ var require_dist10 = require_dist_D6kqBW1G();
91565
91726
  var SECTION_TITLES = {
91566
91727
  server: "Server",
91567
91728
  auth: "Authentication"
@@ -109622,7 +109783,7 @@ var require_winston_logging = __commonJS({
109622
109783
  [Symbol.toStringTag]: { value: "Module" }
109623
109784
  });
109624
109785
  var require_chunk = require_chunk_Cek0wNdY();
109625
- var require_dist10 = require_dist_BETx_5mH();
109786
+ var require_dist10 = require_dist_D6kqBW1G();
109626
109787
  var require_formatter = require_formatter_DqAKDlvN();
109627
109788
  var node_path = __require("path");
109628
109789
  node_path = require_chunk.__toESM(node_path);
@@ -135889,7 +136050,7 @@ var require_dist3 = __commonJS({
135889
136050
  "use strict";
135890
136051
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
135891
136052
  var require_chunk = require_chunk_Cek0wNdY();
135892
- var require_dist10 = require_dist_BETx_5mH();
136053
+ var require_dist10 = require_dist_D6kqBW1G();
135893
136054
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
135894
136055
  require_alerts();
135895
136056
  var require_formatter = require_formatter_DqAKDlvN();
@@ -225140,6 +225301,41 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225140
225301
  unexposeDevice: require_sleep.method(UnexposeInputSchema, zod.z.void(), { kind: "mutation" })
225141
225302
  }
225142
225303
  };
225304
+ var LOCATION_ICON_IDS = [
225305
+ "map-pin",
225306
+ "house",
225307
+ "door-open",
225308
+ "sofa",
225309
+ "utensils",
225310
+ "bed-double",
225311
+ "bath",
225312
+ "briefcase",
225313
+ "car",
225314
+ "circle-parking",
225315
+ "route",
225316
+ "trees",
225317
+ "umbrella",
225318
+ "waves-ladder",
225319
+ "fence",
225320
+ "package",
225321
+ "warehouse",
225322
+ "store",
225323
+ "sun",
225324
+ "radar"
225325
+ ];
225326
+ var LOCATION_ICON_FALLBACK = "map-pin";
225327
+ var LocationIconIdSchema = zod.z.enum(LOCATION_ICON_IDS);
225328
+ var LocationIconAssignmentSchema = zod.z.object({
225329
+ locationKey: zod.z.string(),
225330
+ icon: LocationIconIdSchema
225331
+ });
225332
+ function locationIconKey(label) {
225333
+ return label.trim().toLowerCase();
225334
+ }
225335
+ function resolveLocationIcon(value) {
225336
+ const parsed = LocationIconIdSchema.safeParse(value);
225337
+ return parsed.success ? parsed.data : LOCATION_ICON_FALLBACK;
225338
+ }
225143
225339
  var ProviderStatusSchema = zod.z.object({
225144
225340
  connected: zod.z.boolean(),
225145
225341
  deviceCount: zod.z.number(),
@@ -225789,7 +225985,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225789
225985
  * reference this label keep their `meta.location` value — the
225790
225986
  * registry is purely a suggestion list. To purge from devices,
225791
225987
  * iterate them and call `setLocation(deviceId, null)`
225792
- * separately (or pass `cascade: true` to do it in one shot). */
225988
+ * separately (or pass `cascade: true` to do it in one shot).
225989
+ *
225990
+ * The room's ICON goes with it, always — a key left behind survives in a
225991
+ * map nobody lists, and a room later re-created under the same name would
225992
+ * silently inherit an icon the operator never chose for it. */
225793
225993
  removeLocation: require_sleep.method(zod.z.object({
225794
225994
  name: zod.z.string(),
225795
225995
  cascade: zod.z.boolean().optional()
@@ -225810,7 +226010,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225810
226010
  * a suggestion list, and the devices that most need a rename are exactly
225811
226011
  * the ones carrying a label nobody registered. `to` renames onto an
225812
226012
  * existing room by MERGING into it, leaving no duplicate label. An empty
225813
- * `to` throws before anything is written. */
226013
+ * `to` throws before anything is written.
226014
+ *
226015
+ * The room's ICON travels with the rename, including when `from` was
226016
+ * never registered — otherwise it orphans on a key no location carries
226017
+ * any more. A merge keeps the TARGET's icon when it has one: renaming
226018
+ * "Sala" into "Salone" moves devices into Salone, it does not restyle
226019
+ * it. */
225814
226020
  renameLocation: require_sleep.method(zod.z.object({
225815
226021
  from: zod.z.string(),
225816
226022
  to: zod.z.string()
@@ -225818,6 +226024,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
225818
226024
  kind: "mutation",
225819
226025
  auth: "admin"
225820
226026
  }),
226027
+ /** Every location that carries an ICON, as `(locationKey, icon)` pairs.
226028
+ *
226029
+ * ADDITIVE, deliberately: `listLocations` keeps returning `string[]` —
226030
+ * every consumer of it (the Device Info autocomplete, the HA adoption
226031
+ * import, the recorder) reads a list of labels and none of them wants a
226032
+ * shape change. A surface that wants icons asks for them and joins.
226033
+ *
226034
+ * Only assignments that EXIST are returned — a location with no icon is
226035
+ * absent, not `map-pin`. The fallback belongs to the renderer
226036
+ * (`resolveLocationIcon`), not to the store, so "the operator chose the
226037
+ * pin" and "the operator chose nothing" stay distinguishable.
226038
+ *
226039
+ * The join key is `locationIconKey(label)` (trimmed, lower-cased) — the
226040
+ * same equality the registry uses everywhere. Never the operator's
226041
+ * casing: a case-only rename is a real edit here, and a map keyed on the
226042
+ * displayed spelling would orphan its entry on exactly that edit. */
226043
+ listLocationIcons: require_sleep.method(zod.z.void(), zod.z.array(LocationIconAssignmentSchema)),
226044
+ /** Set (or clear) the icon of a location label. `icon: null` removes the
226045
+ * assignment. The label need NOT be registered — the registry is a
226046
+ * suggestion list and devices carry labels nobody registered — but an
226047
+ * empty / whitespace-only label throws. Idempotent.
226048
+ *
226049
+ * The icon vocabulary is CLOSED (`LOCATION_ICON_IDS`) because the admin UI
226050
+ * and the viewer draw from different icon modules; see
226051
+ * `device/location-icon.ts` for the list and for what a surface that
226052
+ * cannot draw a member must do (fall back to `map-pin`, never a hole). */
226053
+ setLocationIcon: require_sleep.method(zod.z.object({
226054
+ location: zod.z.string(),
226055
+ icon: LocationIconIdSchema.nullable()
226056
+ }), zod.z.void(), {
226057
+ kind: "mutation",
226058
+ auth: "admin"
226059
+ }),
225821
226060
  /** Soft-disable / re-enable the device. Drivers consult
225822
226061
  * `BaseDevice.disabled` to gate lifecycle hooks. */
225823
226062
  setDisabled: require_sleep.method(zod.z.object({
@@ -252295,6 +252534,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252295
252534
  addonId: null,
252296
252535
  access: "view"
252297
252536
  },
252537
+ "deviceManager.listLocationIcons": {
252538
+ capName: "device-manager",
252539
+ capScope: "system",
252540
+ addonId: null,
252541
+ access: "view"
252542
+ },
252298
252543
  "deviceManager.listLocations": {
252299
252544
  capName: "device-manager",
252300
252545
  capScope: "system",
@@ -252439,6 +252684,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252439
252684
  addonId: null,
252440
252685
  access: "create"
252441
252686
  },
252687
+ "deviceManager.setLocationIcon": {
252688
+ capName: "device-manager",
252689
+ capScope: "system",
252690
+ addonId: null,
252691
+ access: "create"
252692
+ },
252442
252693
  "deviceManager.setMetadata": {
252443
252694
  capName: "device-manager",
252444
252695
  capScope: "system",
@@ -260005,6 +260256,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
260005
260256
  addLocation: (input) => dispatch("deviceManager", "addLocation", "mutation", input),
260006
260257
  removeLocation: (input) => dispatch("deviceManager", "removeLocation", "mutation", input),
260007
260258
  renameLocation: (input) => dispatch("deviceManager", "renameLocation", "mutation", input),
260259
+ listLocationIcons: (input) => dispatch("deviceManager", "listLocationIcons", "query", input),
260260
+ setLocationIcon: (input) => dispatch("deviceManager", "setLocationIcon", "mutation", input),
260008
260261
  listPersistedByAddon: (input) => dispatch("deviceManager", "listPersistedByAddon", "query", input),
260009
260262
  listAll: (input) => dispatch("deviceManager", "listAll", "query", input),
260010
260263
  getChildren: (input) => dispatch("deviceManager", "getChildren", "query", input),
@@ -263552,6 +263805,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263552
263805
  exports.LEGACY_READ_ONLY_CONFIG_KEY = LEGACY_READ_ONLY_CONFIG_KEY;
263553
263806
  exports.LOAD_CONTRIBUTION_ATTRIBUTIONS = LOAD_CONTRIBUTION_ATTRIBUTIONS;
263554
263807
  exports.LOAD_CONTRIBUTION_ROLES = LOAD_CONTRIBUTION_ROLES;
263808
+ exports.LOCATION_ICON_FALLBACK = LOCATION_ICON_FALLBACK;
263809
+ exports.LOCATION_ICON_IDS = LOCATION_ICON_IDS;
263555
263810
  exports.LOG_CHANNEL_TICK_MS = LOG_CHANNEL_TICK_MS;
263556
263811
  exports.LOG_LEVEL_RANK = LOG_LEVEL_RANK;
263557
263812
  exports.LTS_SCOPE_CLASS_SEPARATOR = LTS_SCOPE_CLASS_SEPARATOR;
@@ -263592,6 +263847,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263592
263847
  exports.LlmUsageSchema = LlmUsageSchema;
263593
263848
  exports.LoadContributionSchema = LoadContributionSchema;
263594
263849
  exports.LocateSegmentResultSchema = LocateSegmentResultSchema;
263850
+ exports.LocationIconAssignmentSchema = LocationIconAssignmentSchema;
263851
+ exports.LocationIconIdSchema = LocationIconIdSchema;
263595
263852
  exports.LocationStatSchema = LocationStatSchema;
263596
263853
  exports.LockControlStatusSchema = LockControlStatusSchema;
263597
263854
  exports.LockStateSchema = LockStateSchema;
@@ -264491,6 +264748,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
264491
264748
  exports.llmRuntimeCapability = llmRuntimeCapability;
264492
264749
  exports.loadContributionCapability = loadContributionCapability;
264493
264750
  exports.localNetworkCapability = localNetworkCapability;
264751
+ exports.locationIconKey = locationIconKey;
264494
264752
  exports.locationSimilarity = locationSimilarity;
264495
264753
  exports.lockControlCapability = lockControlCapability;
264496
264754
  exports.logBannerArgs = require_canonical_hash.logBannerArgs;
@@ -264615,6 +264873,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
264615
264873
  exports.resolveEgressDecodeHwAccel = resolveEgressDecodeHwAccel;
264616
264874
  exports.resolveFormat = resolveFormat;
264617
264875
  exports.resolveHydratedFieldValue = require_sleep.resolveHydratedFieldValue;
264876
+ exports.resolveLocationIcon = resolveLocationIcon;
264618
264877
  exports.resolveLocationMode = resolveLocationMode;
264619
264878
  exports.resolveMethodAuth = resolveMethodAuth;
264620
264879
  exports.resolveModelFormat = resolveModelFormat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.157",
3
+ "version": "1.2.158",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",