@camstack/addon-model-studio 1.1.50 → 1.1.52

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.
Files changed (18) hide show
  1. package/dist/{MotionZonesSettings-OKgi_6ij.mjs → MotionZonesSettings-C0cFDSjW.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-CDq1cPBg.mjs → PrivacyMaskSettings-B0xou5VT.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-BiYS5EQf.mjs → SceneMonitorEditor-CM4NQvb5.mjs} +3 -3
  4. package/dist/_stub.js +10 -10
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DesTVb3n.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DCDHH2z9.mjs} +4 -4
  6. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CE4txY-m.mjs +26 -0
  7. package/dist/{hostInit-BVkEE3at.mjs → hostInit-DuCMcgRq.mjs} +3 -3
  8. package/dist/model-studio.addon.js +157 -125
  9. package/dist/model-studio.addon.mjs +152 -118
  10. package/dist/{player-overlays-ChKwd9V6.mjs → player-overlays-p43mMwWT.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-CmBG3EBf.mjs → responsive-BWVkSjgC.mjs} +1 -1
  13. package/dist/{square-Z063y6nN.mjs → square-DNWB0Fvw.mjs} +1 -1
  14. package/dist/{trash-2-DrMIwp0M.mjs → trash-2-BKgeax2l.mjs} +1 -1
  15. package/dist/{use-device-snapshot-CxvtHnr6.mjs → use-device-snapshot-CkQG2lkA.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-WBWKfCvF.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-CZmB5SRo.mjs} +1 -1
  17. package/package.json +3 -2
  18. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CX9ONuvO.mjs +0 -26
@@ -23,109 +23,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let node_child_process = require("node:child_process");
25
25
  let node_crypto = require("node:crypto");
26
- node_crypto = __toESM(node_crypto, 1);
26
+ node_crypto = __toESM(node_crypto);
27
27
  let node_fs = require("node:fs");
28
- let node_fs$1 = __toESM(node_fs, 1);
29
28
  node_fs = __toESM(node_fs);
30
29
  let node_os = require("node:os");
31
30
  node_os = __toESM(node_os);
32
31
  let node_path = require("node:path");
33
- let node_path$1 = __toESM(node_path, 1);
34
32
  node_path = __toESM(node_path);
35
- let node_util = require("node:util");
36
- let node_zlib = require("node:zlib");
37
- //#region ../system/dist/file-data-plane-BhKdxJgf.mjs
38
- /** Build fetch headers, including HF auth token for huggingface.co URLs */
39
- function buildHeaders(url) {
40
- const headers = { "User-Agent": "CamStack/1.0" };
41
- const hfToken = process.env["HF_TOKEN"] ?? process.env["HUGGING_FACE_HUB_TOKEN"];
42
- if (hfToken && url.includes("huggingface.co")) headers["Authorization"] = `Bearer ${hfToken}`;
43
- return headers;
44
- }
45
- var DEFAULT_MAX_REDIRECTS = 5;
46
- function normalizeDownloadOptions(third) {
47
- if (typeof third === "function") return { onProgress: third };
48
- return third ?? {};
49
- }
50
- function isRedirectStatus(status) {
51
- return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
52
- }
53
- function resolveRedirectUrl(current, location) {
54
- return new URL(location, current);
55
- }
56
- async function downloadFile(url, destPath, onProgressOrOptions) {
57
- if (node_fs$1.existsSync(destPath)) return destPath;
58
- const opts = normalizeDownloadOptions(onProgressOrOptions);
59
- const fetchImpl = opts.fetchImpl ?? fetch;
60
- const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
61
- node_fs$1.mkdirSync(node_path$1.dirname(destPath), { recursive: true });
62
- const tmpPath = destPath + ".downloading";
63
- try {
64
- let current = url;
65
- const seen = /* @__PURE__ */ new Set();
66
- let response;
67
- const manual = opts.redirectPolicy !== void 0;
68
- for (let hop = 0; hop <= maxRedirects; hop++) {
69
- const parsed = new URL(current);
70
- if (opts.redirectPolicy) opts.redirectPolicy(parsed, hop);
71
- if (seen.has(parsed.href)) throw new Error(`Redirect loop detected at ${parsed.href}`);
72
- seen.add(parsed.href);
73
- const controller = opts.timeoutMs !== void 0 ? new AbortController() : void 0;
74
- const timer = controller && opts.timeoutMs !== void 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
75
- try {
76
- response = await fetchImpl(current, {
77
- redirect: manual ? "manual" : "follow",
78
- headers: buildHeaders(current),
79
- ...controller ? { signal: controller.signal } : {}
80
- });
81
- } finally {
82
- if (timer) clearTimeout(timer);
83
- }
84
- if (manual && isRedirectStatus(response.status)) {
85
- const location = response.headers.get("location");
86
- if (!location) throw new Error(`Redirect ${response.status} missing Location header`);
87
- if (hop >= maxRedirects) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
88
- current = resolveRedirectUrl(current, location).href;
89
- continue;
90
- }
91
- break;
92
- }
93
- if (!response) throw new Error(`No response downloading ${url}`);
94
- if (manual && isRedirectStatus(response.status)) throw new Error(`Too many redirects (max ${maxRedirects}) downloading ${url}`);
95
- if (!response.ok) throw new Error(`HTTP ${response.status} downloading ${url}`);
96
- if (!response.body) throw new Error(`No response body from ${url}`);
97
- const total = parseInt(response.headers.get("content-length") ?? "0", 10);
98
- let downloaded = 0;
99
- const fileStream = node_fs$1.createWriteStream(tmpPath);
100
- const reader = response.body.getReader();
101
- try {
102
- for (;;) {
103
- const { done, value } = await reader.read();
104
- if (done || !value) break;
105
- downloaded += value.length;
106
- if (opts.maxBytes !== void 0 && downloaded > opts.maxBytes) throw new Error(`Downloaded ${downloaded} bytes exceeds the ${opts.maxBytes}-byte limit`);
107
- fileStream.write(value);
108
- opts.onProgress?.(downloaded, total);
109
- }
110
- } finally {
111
- fileStream.end();
112
- await new Promise((resolve, reject) => {
113
- fileStream.on("finish", resolve);
114
- fileStream.on("error", reject);
115
- });
116
- }
117
- node_fs$1.renameSync(tmpPath, destPath);
118
- return destPath;
119
- } catch (err) {
120
- try {
121
- node_fs$1.unlinkSync(tmpPath);
122
- } catch {}
123
- throw err;
124
- }
125
- }
126
- (0, node_util.promisify)(node_zlib.brotliCompress);
127
- (0, node_util.promisify)(node_zlib.gzip);
128
- //#endregion
33
+ let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
129
34
  //#region src/scrypted/scrypted-download-policy.ts
130
35
  /**
131
36
  * Strict per-hop allow-list for Scrypted catalog downloads.
@@ -4338,7 +4243,7 @@ function initializeContext(params) {
4338
4243
  external: params?.external ?? void 0
4339
4244
  };
4340
4245
  }
4341
- function process$1(schema, ctx, _params = {
4246
+ function process(schema, ctx, _params = {
4342
4247
  path: [],
4343
4248
  schemaPath: []
4344
4249
  }) {
@@ -4375,7 +4280,7 @@ function process$1(schema, ctx, _params = {
4375
4280
  const parent = schema._zod.parent;
4376
4281
  if (parent) {
4377
4282
  if (!result.ref) result.ref = parent;
4378
- process$1(parent, ctx, params);
4283
+ process(parent, ctx, params);
4379
4284
  ctx.seen.get(parent).isParent = true;
4380
4285
  }
4381
4286
  }
@@ -4595,7 +4500,7 @@ var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
4595
4500
  ...params,
4596
4501
  processors
4597
4502
  });
4598
- process$1(schema, ctx);
4503
+ process(schema, ctx);
4599
4504
  extractDefs(ctx, schema);
4600
4505
  return finalize(ctx, schema);
4601
4506
  };
@@ -4607,7 +4512,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
4607
4512
  io,
4608
4513
  processors
4609
4514
  });
4610
- process$1(schema, ctx);
4515
+ process(schema, ctx);
4611
4516
  extractDefs(ctx, schema);
4612
4517
  return finalize(ctx, schema);
4613
4518
  };
@@ -4721,7 +4626,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
4721
4626
  if (typeof minimum === "number") json.minItems = minimum;
4722
4627
  if (typeof maximum === "number") json.maxItems = maximum;
4723
4628
  json.type = "array";
4724
- json.items = process$1(def.element, ctx, {
4629
+ json.items = process(def.element, ctx, {
4725
4630
  ...params,
4726
4631
  path: [...params.path, "items"]
4727
4632
  });
@@ -4732,7 +4637,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
4732
4637
  json.type = "object";
4733
4638
  json.properties = {};
4734
4639
  const shape = def.shape;
4735
- for (const key in shape) json.properties[key] = process$1(shape[key], ctx, {
4640
+ for (const key in shape) json.properties[key] = process(shape[key], ctx, {
4736
4641
  ...params,
4737
4642
  path: [
4738
4643
  ...params.path,
@@ -4750,7 +4655,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
4750
4655
  if (def.catchall?._zod.def.type === "never") json.additionalProperties = false;
4751
4656
  else if (!def.catchall) {
4752
4657
  if (ctx.io === "output") json.additionalProperties = false;
4753
- } else if (def.catchall) json.additionalProperties = process$1(def.catchall, ctx, {
4658
+ } else if (def.catchall) json.additionalProperties = process(def.catchall, ctx, {
4754
4659
  ...params,
4755
4660
  path: [...params.path, "additionalProperties"]
4756
4661
  });
@@ -4758,7 +4663,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
4758
4663
  var unionProcessor = (schema, ctx, json, params) => {
4759
4664
  const def = schema._zod.def;
4760
4665
  const isExclusive = def.inclusive === false;
4761
- const options = def.options.map((x, i) => process$1(x, ctx, {
4666
+ const options = def.options.map((x, i) => process(x, ctx, {
4762
4667
  ...params,
4763
4668
  path: [
4764
4669
  ...params.path,
@@ -4771,7 +4676,7 @@ var unionProcessor = (schema, ctx, json, params) => {
4771
4676
  };
4772
4677
  var intersectionProcessor = (schema, ctx, json, params) => {
4773
4678
  const def = schema._zod.def;
4774
- const a = process$1(def.left, ctx, {
4679
+ const a = process(def.left, ctx, {
4775
4680
  ...params,
4776
4681
  path: [
4777
4682
  ...params.path,
@@ -4779,7 +4684,7 @@ var intersectionProcessor = (schema, ctx, json, params) => {
4779
4684
  0
4780
4685
  ]
4781
4686
  });
4782
- const b = process$1(def.right, ctx, {
4687
+ const b = process(def.right, ctx, {
4783
4688
  ...params,
4784
4689
  path: [
4785
4690
  ...params.path,
@@ -4796,7 +4701,7 @@ var tupleProcessor = (schema, ctx, _json, params) => {
4796
4701
  json.type = "array";
4797
4702
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
4798
4703
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
4799
- const prefixItems = def.items.map((x, i) => process$1(x, ctx, {
4704
+ const prefixItems = def.items.map((x, i) => process(x, ctx, {
4800
4705
  ...params,
4801
4706
  path: [
4802
4707
  ...params.path,
@@ -4804,7 +4709,7 @@ var tupleProcessor = (schema, ctx, _json, params) => {
4804
4709
  i
4805
4710
  ]
4806
4711
  }));
4807
- const rest = def.rest ? process$1(def.rest, ctx, {
4712
+ const rest = def.rest ? process(def.rest, ctx, {
4808
4713
  ...params,
4809
4714
  path: [
4810
4715
  ...params.path,
@@ -4835,7 +4740,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
4835
4740
  const keyType = def.keyType;
4836
4741
  const patterns = keyType._zod.bag?.patterns;
4837
4742
  if (def.mode === "loose" && patterns && patterns.size > 0) {
4838
- const valueSchema = process$1(def.valueType, ctx, {
4743
+ const valueSchema = process(def.valueType, ctx, {
4839
4744
  ...params,
4840
4745
  path: [
4841
4746
  ...params.path,
@@ -4846,11 +4751,11 @@ var recordProcessor = (schema, ctx, _json, params) => {
4846
4751
  json.patternProperties = {};
4847
4752
  for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;
4848
4753
  } else {
4849
- if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$1(def.keyType, ctx, {
4754
+ if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process(def.keyType, ctx, {
4850
4755
  ...params,
4851
4756
  path: [...params.path, "propertyNames"]
4852
4757
  });
4853
- json.additionalProperties = process$1(def.valueType, ctx, {
4758
+ json.additionalProperties = process(def.valueType, ctx, {
4854
4759
  ...params,
4855
4760
  path: [...params.path, "additionalProperties"]
4856
4761
  });
@@ -4863,7 +4768,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
4863
4768
  };
4864
4769
  var nullableProcessor = (schema, ctx, json, params) => {
4865
4770
  const def = schema._zod.def;
4866
- const inner = process$1(def.innerType, ctx, params);
4771
+ const inner = process(def.innerType, ctx, params);
4867
4772
  const seen = ctx.seen.get(schema);
4868
4773
  if (ctx.target === "openapi-3.0") {
4869
4774
  seen.ref = def.innerType;
@@ -4872,27 +4777,27 @@ var nullableProcessor = (schema, ctx, json, params) => {
4872
4777
  };
4873
4778
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
4874
4779
  const def = schema._zod.def;
4875
- process$1(def.innerType, ctx, params);
4780
+ process(def.innerType, ctx, params);
4876
4781
  const seen = ctx.seen.get(schema);
4877
4782
  seen.ref = def.innerType;
4878
4783
  };
4879
4784
  var defaultProcessor = (schema, ctx, json, params) => {
4880
4785
  const def = schema._zod.def;
4881
- process$1(def.innerType, ctx, params);
4786
+ process(def.innerType, ctx, params);
4882
4787
  const seen = ctx.seen.get(schema);
4883
4788
  seen.ref = def.innerType;
4884
4789
  json.default = JSON.parse(JSON.stringify(def.defaultValue));
4885
4790
  };
4886
4791
  var prefaultProcessor = (schema, ctx, json, params) => {
4887
4792
  const def = schema._zod.def;
4888
- process$1(def.innerType, ctx, params);
4793
+ process(def.innerType, ctx, params);
4889
4794
  const seen = ctx.seen.get(schema);
4890
4795
  seen.ref = def.innerType;
4891
4796
  if (ctx.io === "input") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
4892
4797
  };
4893
4798
  var catchProcessor = (schema, ctx, json, params) => {
4894
4799
  const def = schema._zod.def;
4895
- process$1(def.innerType, ctx, params);
4800
+ process(def.innerType, ctx, params);
4896
4801
  const seen = ctx.seen.get(schema);
4897
4802
  seen.ref = def.innerType;
4898
4803
  let catchValue;
@@ -4907,26 +4812,26 @@ var pipeProcessor = (schema, ctx, _json, params) => {
4907
4812
  const def = schema._zod.def;
4908
4813
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
4909
4814
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
4910
- process$1(innerType, ctx, params);
4815
+ process(innerType, ctx, params);
4911
4816
  const seen = ctx.seen.get(schema);
4912
4817
  seen.ref = innerType;
4913
4818
  };
4914
4819
  var readonlyProcessor = (schema, ctx, json, params) => {
4915
4820
  const def = schema._zod.def;
4916
- process$1(def.innerType, ctx, params);
4821
+ process(def.innerType, ctx, params);
4917
4822
  const seen = ctx.seen.get(schema);
4918
4823
  seen.ref = def.innerType;
4919
4824
  json.readOnly = true;
4920
4825
  };
4921
4826
  var optionalProcessor = (schema, ctx, _json, params) => {
4922
4827
  const def = schema._zod.def;
4923
- process$1(def.innerType, ctx, params);
4828
+ process(def.innerType, ctx, params);
4924
4829
  const seen = ctx.seen.get(schema);
4925
4830
  seen.ref = def.innerType;
4926
4831
  };
4927
4832
  var lazyProcessor = (schema, ctx, _json, params) => {
4928
4833
  const innerType = schema._zod.innerType;
4929
- process$1(innerType, ctx, params);
4834
+ process(innerType, ctx, params);
4930
4835
  const seen = ctx.seen.get(schema);
4931
4836
  seen.ref = innerType;
4932
4837
  };
@@ -6535,6 +6440,40 @@ var BaseAddon = class {
6535
6440
  deviceSettingsSchema() {
6536
6441
  return null;
6537
6442
  }
6443
+ /**
6444
+ * INTEGRATION-LEVEL SETTINGS — declare which of this addon's global sections
6445
+ * ARE the configuration of its integration.
6446
+ *
6447
+ * Return the `ConfigSection.id`s, from {@link globalSettingsSchema}, that an
6448
+ * operator should find on the addon's integration page (System →
6449
+ * Integrations → <name>) rather than only in the cluster-wide list of every
6450
+ * addon. Empty (the default) means the addon has no integration-level
6451
+ * settings and no such surface is offered — this is opt-in, because whether
6452
+ * an addon's configuration IS its integration's configuration depends on the
6453
+ * nature of the integration.
6454
+ *
6455
+ * WHAT THIS IS NOT. It is not a scope. The selected sections keep living in
6456
+ * the ONE global schema, in the ONE addon store, written by the ONE
6457
+ * `updateGlobalSettings` path. There is deliberately no
6458
+ * `updateIntegrationSettings`: a second write path is how a surface acquires
6459
+ * a second store key, and this repo has shipped that twice (`btmPath@hub`,
6460
+ * D266). Selecting sections cannot introduce a key that selecting cannot.
6461
+ *
6462
+ * WHY IT IS A LIST OF SECTION IDS AND NOT A MARKER ON THE SECTION.
6463
+ * `ConfigFieldBase` used to carry `scope?: 'device' | 'global'` and it was
6464
+ * removed with the reason recorded at
6465
+ * `packages/types/src/interfaces/config-ui.ts:249` — *"a field's scope is
6466
+ * determined by WHICH schema it lives in, not by a field-level marker."* A
6467
+ * marker sprinkled across sections also has to borrow a field that already
6468
+ * means something else; borrowing `section.tab` put the literal word
6469
+ * "integration" into an operator-facing tab bar, because `tab` means "how to
6470
+ * GROUP this visually" and cannot also mean "where this lives" (D269
6471
+ * supersedes D268). One declaration, in one place, next to the schema whose
6472
+ * ids it names.
6473
+ */
6474
+ integrationSettingSections() {
6475
+ return [];
6476
+ }
6538
6477
  async getGlobalSettings(overlay, cap, nodeId) {
6539
6478
  const schema = this.globalSettingsSchema(cap);
6540
6479
  if (!schema) return { sections: [] };
@@ -6545,6 +6484,55 @@ var BaseAddon = class {
6545
6484
  } : projected);
6546
6485
  }
6547
6486
  /**
6487
+ * The integration-level view of this addon's settings: exactly the sections
6488
+ * named by {@link integrationSettingSections}, hydrated from the SAME store
6489
+ * `getGlobalSettings` reads, and narrowed to cluster-scoped fields.
6490
+ *
6491
+ * Returns `null` when the addon declared nothing — an addon that opts out has
6492
+ * no integration settings surface at all, rather than an empty one that reads
6493
+ * as a failed load.
6494
+ *
6495
+ * Three properties hold BY CONSTRUCTION, which is why they are here in core
6496
+ * and not in whichever UI happens to render this:
6497
+ *
6498
+ * 1. **One key.** The payload is a SUBSET of the global schema, so a field
6499
+ * shown here is the same field, with the same bare key, that the addon's
6500
+ * own page shows. There is no integration-specific writer — callers save
6501
+ * through `updateGlobalSettings` — so a second store key is unreachable,
6502
+ * not merely discouraged.
6503
+ * 2. **No node scope.** `perNode: true` fields are DROPPED. Their store key
6504
+ * is `<key>@<nodeId>` and an integration is not a node; whichever node
6505
+ * such a field silently picked would be a wrong answer for the operator
6506
+ * who opened the page (D266).
6507
+ * 3. **No silent typo.** A declared id that names no section throws. The
6508
+ * alternative — skip it — turns a rename into a surface that quietly
6509
+ * empties, which looks exactly like an addon with nothing to configure.
6510
+ */
6511
+ async getIntegrationSettings(nodeId) {
6512
+ const declared = this.integrationSettingSections();
6513
+ if (declared.length === 0) return null;
6514
+ const schema = this.globalSettingsSchema();
6515
+ if (!schema) throw new Error(`${this.constructor.name}: integrationSettingSections() names [${declared.join(", ")}] but globalSettingsSchema() returns null.`);
6516
+ const byId = new Map(schema.sections.map((section) => [section.id, section]));
6517
+ const sections = [];
6518
+ for (const id of declared) {
6519
+ const section = byId.get(id);
6520
+ if (!section) throw new Error(`${this.constructor.name}: integrationSettingSections() names unknown section "${id}". Known sections: [${[...byId.keys()].join(", ")}].`);
6521
+ const fields = dropPerNodeFields(section.fields);
6522
+ if (fields.length === 0) continue;
6523
+ sections.push({
6524
+ ...section,
6525
+ fields
6526
+ });
6527
+ }
6528
+ if (sections.length === 0) return null;
6529
+ const projected = await this.resolveGlobalStore(nodeId);
6530
+ return hydrateSchema({
6531
+ ...schema,
6532
+ sections
6533
+ }, projected);
6534
+ }
6535
+ /**
6548
6536
  * The raw addon store PROJECTED onto the target node's bare per-node keys:
6549
6537
  * every `perNode: true` field carries THAT node's scoped value on its bare
6550
6538
  * key (absent scoped key ⇒ key absent, so the schema `default` wins — no
@@ -6848,6 +6836,41 @@ var BaseAddon = class {
6848
6836
  * `hydrateSchema` does. Valueless structural fields (separator/info/…)
6849
6837
  * don't declare `perNode` and are excluded by the `in` narrowing.
6850
6838
  */
6839
+ /**
6840
+ * The same fields with every `perNode: true` one removed, recursing into layout
6841
+ * containers exactly as {@link collectPerNodeFieldKeys} does. A container left
6842
+ * with no child is dropped rather than rendered empty.
6843
+ *
6844
+ * Used by `getIntegrationSettings`: an integration is not a node, so a field
6845
+ * whose store key is `<key>@<nodeId>` has no node to belong to there.
6846
+ */
6847
+ function dropPerNodeFields(fields) {
6848
+ const kept = [];
6849
+ for (const field of fields) {
6850
+ if (field.type === "group") {
6851
+ const inner = dropPerNodeFields(field.fields);
6852
+ if (inner.length > 0) kept.push({
6853
+ ...field,
6854
+ fields: inner
6855
+ });
6856
+ continue;
6857
+ }
6858
+ if (field.type === "sub-tabs") {
6859
+ const tabs = field.tabs.map((tab) => ({
6860
+ ...tab,
6861
+ fields: dropPerNodeFields(tab.fields)
6862
+ })).filter((tab) => tab.fields.length > 0);
6863
+ if (tabs.length > 0) kept.push({
6864
+ ...field,
6865
+ tabs
6866
+ });
6867
+ continue;
6868
+ }
6869
+ if ("perNode" in field && field.perNode === true) continue;
6870
+ kept.push(field);
6871
+ }
6872
+ return kept;
6873
+ }
6851
6874
  function collectPerNodeFieldKeys(fields) {
6852
6875
  const collected = [];
6853
6876
  for (const field of fields) {
@@ -10078,6 +10101,9 @@ method(object({
10078
10101
  kind: "mutation",
10079
10102
  auth: "admin"
10080
10103
  }), method(object({
10104
+ addonId: string(),
10105
+ nodeId: string().optional()
10106
+ }), SettingsSchemaWithValuesSchema.nullable()), method(object({
10081
10107
  addonId: string(),
10082
10108
  deviceId: number(),
10083
10109
  nodeId: string().optional()
@@ -28516,6 +28542,12 @@ Object.freeze({
28516
28542
  addonId: null,
28517
28543
  access: "view"
28518
28544
  },
28545
+ "addonSettings.getIntegrationSettings": {
28546
+ capName: "addon-settings",
28547
+ capScope: "system",
28548
+ addonId: null,
28549
+ access: "view"
28550
+ },
28519
28551
  "addonSettings.updateDeviceSettings": {
28520
28552
  capName: "addon-settings",
28521
28553
  capScope: "system",
@@ -37339,7 +37371,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
37339
37371
  },
37340
37372
  downloadOnnx: async (url, destDir, modelId) => {
37341
37373
  const filename = `camstack-${modelId}.onnx`;
37342
- return downloadFile(url, node_path.default.join(destDir, filename));
37374
+ return (0, _camstack_system_addon_utils.downloadFile)(url, node_path.default.join(destDir, filename));
37343
37375
  },
37344
37376
  writeJob: async (job) => {
37345
37377
  const tmpDir = this.ctx.dataDir ?? node_os.default.tmpdir();
@@ -37643,7 +37675,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
37643
37675
  const descriptor = await importScryptedCatalogModel(input, {
37644
37676
  stagingDir: node_path.default.join(this.ctx.dataDir ?? node_os.default.tmpdir(), "scrypted-import"),
37645
37677
  modelsDir,
37646
- downloadFile: (url, destPath) => downloadFile(url, destPath, {
37678
+ downloadFile: (url, destPath) => (0, _camstack_system_addon_utils.downloadFile)(url, destPath, {
37647
37679
  redirectPolicy: assertScryptedDownloadUrl,
37648
37680
  maxBytes: SCRYPTED_IMPORT_MAX_FILE_BYTES,
37649
37681
  timeoutMs: 6e4
@@ -37700,7 +37732,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
37700
37732
  const modelsDir = await this.resolveModelsDir();
37701
37733
  const filename = cat.url.split("/").at(-1) ?? `${descriptor.entry.id}.${cat.format}`;
37702
37734
  const destPath = node_path.default.join(modelsDir, filename);
37703
- await downloadFile(cat.url, destPath);
37735
+ await (0, _camstack_system_addon_utils.downloadFile)(cat.url, destPath);
37704
37736
  const bytes = (await node_fs.default.promises.stat(destPath)).size;
37705
37737
  const declaredBytes = cat.sizeMB * 1e6;
37706
37738
  if (bytes < declaredBytes * .5 || bytes > declaredBytes * 2) {
@@ -37742,7 +37774,7 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
37742
37774
  const filename = `${input.modelId}${traits.extension}`;
37743
37775
  const modelsDir = await this.resolveModelsDir();
37744
37776
  const destPath = node_path.default.join(modelsDir, filename);
37745
- await downloadFile(downloadUrl, destPath);
37777
+ await (0, _camstack_system_addon_utils.downloadFile)(downloadUrl, destPath);
37746
37778
  const bytes = (await node_fs.default.promises.stat(destPath)).size;
37747
37779
  if (bytes < 1e4) {
37748
37780
  await node_fs.default.promises.rm(destPath, { force: true });