@camstack/addon-provider-bosch 0.1.49 → 0.1.50

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.50] - 2026-11-11
11
+
12
+ ### Added
13
+
14
+ - HomeCom: a read-only resource-tree explorer action for diagnosing what a gateway serves
15
+
10
16
  ## [0.1.49] - 2026-11-11
11
17
 
12
18
  ### Changed
@@ -1,4 +1,4 @@
1
- const require_bosch_instances = require("./bosch-instances-BHFcR1TK.js");
1
+ const require_bosch_instances = require("./bosch-instances-CYBnIcHg.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/shared/sign-in-redirects.ts
4
4
  /** The addresses each vendor sends the browser to after a sign-in; no browser can open them. Kept free of imports so the form schema can name them. */
@@ -1,4 +1,4 @@
1
- import { X as errMsg } from "./bosch-instances-B2e4DAI1.mjs";
1
+ import { Q as errMsg } from "./bosch-instances-CdvcBYye.mjs";
2
2
  import { randomBytes } from "node:crypto";
3
3
  //#region src/shared/sign-in-redirects.ts
4
4
  /** The addresses each vendor sends the browser to after a sign-in; no browser can open them. Kept free of imports so the form schema can name them. */
@@ -102380,6 +102380,12 @@ Object.defineProperty(exports, "_enum", {
102380
102380
  return _enum;
102381
102381
  }
102382
102382
  });
102383
+ Object.defineProperty(exports, "_null", {
102384
+ enumerable: true,
102385
+ get: function() {
102386
+ return _null;
102387
+ }
102388
+ });
102383
102389
  Object.defineProperty(exports, "array", {
102384
102390
  enumerable: true,
102385
102391
  get: function() {
@@ -102398,6 +102404,12 @@ Object.defineProperty(exports, "binaryCapability", {
102398
102404
  return binaryCapability;
102399
102405
  }
102400
102406
  });
102407
+ Object.defineProperty(exports, "boolean", {
102408
+ enumerable: true,
102409
+ get: function() {
102410
+ return boolean;
102411
+ }
102412
+ });
102401
102413
  Object.defineProperty(exports, "boschRowsMigration", {
102402
102414
  enumerable: true,
102403
102415
  get: function() {
@@ -102326,4 +102326,4 @@ function keepBodyKeys(provider, keys) {
102326
102326
  };
102327
102327
  }
102328
102328
  //#endregion
102329
- export { RecordIntegrationInstances as A, array as B, powerMeterCapability as C, switchCapability as D, runningStateCapability as E, entryBody as F, string as G, number$1 as H, isSecretMarker as I, DeviceRole as J, union as K, isSecretMarkerLike as L, customAction as M, defineCustomActions as N, temperatureSensorCapability as O, defineEntryKind as P, rowsDepsOver as R, numericSensorCapability as S, runningFromDishwasherOperation as T, object as U, literal as V, record as W, errMsg as X, DeviceType as Y, EventCategory as Z, heatPumpControlCapability as _, BaseDevice as a, ingestReading as b, RUNNING_STATE_COLD as c, climateControlCapability as d, consumablesCapability as f, eventEmitterCapability as g, enumSensorCapability as h, testBoschInstance as i, createEvent as j, waterHeaterCapability as k, batteryCapability as l, dishwasherCapability as m, boschRowsPorts as n, BaseDeviceProvider as o, deviceAdoptionCapability as p, unknown as q, keepBodyKeys as r, DishwasherOperationStateSchema as s, boschRowsMigration as t, binaryCapability as u, heatPumpStatusCapability as v, quantityFromUnit as w, integrationInstancesCapability as x, heatingValveCapability as y, _enum as z };
102329
+ export { EventCategory as $, RecordIntegrationInstances as A, _null as B, powerMeterCapability as C, switchCapability as D, runningStateCapability as E, entryBody as F, object as G, boolean as H, isSecretMarker as I, union as J, record as K, isSecretMarkerLike as L, customAction as M, defineCustomActions as N, temperatureSensorCapability as O, defineEntryKind as P, errMsg as Q, rowsDepsOver as R, numericSensorCapability as S, runningFromDishwasherOperation as T, literal as U, array as V, number$1 as W, DeviceRole as X, unknown as Y, DeviceType as Z, heatPumpControlCapability as _, BaseDevice as a, ingestReading as b, RUNNING_STATE_COLD as c, climateControlCapability as d, consumablesCapability as f, eventEmitterCapability as g, enumSensorCapability as h, testBoschInstance as i, createEvent as j, waterHeaterCapability as k, batteryCapability as l, dishwasherCapability as m, boschRowsPorts as n, BaseDeviceProvider as o, deviceAdoptionCapability as p, string as q, keepBodyKeys as r, DishwasherOperationStateSchema as s, boschRowsMigration as t, binaryCapability as u, heatPumpStatusCapability as v, quantityFromUnit as w, integrationInstancesCapability as x, heatingValveCapability as y, _enum as z };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-BHFcR1TK.js");
3
- const require_account_integration_offline = require("../account-integration-offline-Dcx0sq_v.js");
2
+ const require_bosch_instances = require("../bosch-instances-CYBnIcHg.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-7CH3r5jI.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_tls = require("node:tls");
6
6
  let node_zlib = require("node:zlib");
@@ -1,5 +1,5 @@
1
- import { A as RecordIntegrationInstances, B as array, C as powerMeterCapability, E as runningStateCapability, F as entryBody, G as string, I as isSecretMarker, J as DeviceRole, K as union, L as isSecretMarkerLike, M as customAction, N as defineCustomActions, P as defineEntryKind, R as rowsDepsOver, S as numericSensorCapability, T as runningFromDishwasherOperation, U as object, V as literal, W as record, X as errMsg, Y as DeviceType, Z as EventCategory, a as BaseDevice, b as ingestReading, c as RUNNING_STATE_COLD, f as consumablesCapability, g as eventEmitterCapability, h as enumSensorCapability, i as testBoschInstance, j as createEvent, m as dishwasherCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as unknown, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, u as binaryCapability, w as quantityFromUnit, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-B2e4DAI1.mjs";
2
- import { a as loginFailureForAttempt, c as HOME_CONNECT_REDIRECT_URI, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, t as setIntegrationDevicesOnline } from "../account-integration-offline-BKDol6kz.mjs";
1
+ import { $ as EventCategory, A as RecordIntegrationInstances, C as powerMeterCapability, E as runningStateCapability, F as entryBody, G as object, I as isSecretMarker, J as union, K as record, L as isSecretMarkerLike, M as customAction, N as defineCustomActions, P as defineEntryKind, Q as errMsg, R as rowsDepsOver, S as numericSensorCapability, T as runningFromDishwasherOperation, U as literal, V as array, X as DeviceRole, Y as unknown, Z as DeviceType, a as BaseDevice, b as ingestReading, c as RUNNING_STATE_COLD, f as consumablesCapability, g as eventEmitterCapability, h as enumSensorCapability, i as testBoschInstance, j as createEvent, m as dishwasherCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as string, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, u as binaryCapability, w as quantityFromUnit, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-CdvcBYye.mjs";
2
+ import { a as loginFailureForAttempt, c as HOME_CONNECT_REDIRECT_URI, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, t as setIntegrationDevicesOnline } from "../account-integration-offline-B1Wgr_rT.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { connect } from "node:tls";
5
5
  import { inflateRawSync } from "node:zlib";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-BHFcR1TK.js");
3
- const require_account_integration_offline = require("../account-integration-offline-Dcx0sq_v.js");
2
+ const require_bosch_instances = require("../bosch-instances-CYBnIcHg.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-7CH3r5jI.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  //#region src/homecom/config.ts
6
6
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -178,6 +178,280 @@ function buildHomeComFormSchema() {
178
178
  ]
179
179
  }] };
180
180
  }
181
+ var EXPLORER_MAX_NODES_CAP = 1500;
182
+ var VALUE_MAX_CHARS = 200;
183
+ /** Roots walked when `/` does not list its children. */
184
+ var EXPLORER_SEED_ROOTS = [
185
+ "/system",
186
+ "/heatSources",
187
+ "/heatingCircuits",
188
+ "/dhwCircuits",
189
+ "/gateway",
190
+ "/notifications",
191
+ "/recordings",
192
+ "/devices",
193
+ "/solarCircuits",
194
+ "/ventilation"
195
+ ];
196
+ /** Under the 60 s UDS request timeout a forked addon's action answers within. */
197
+ var EXPLORER_MAX_DEADLINE_MS = 5e4;
198
+ var DROPPED_SAMPLE_MAX = 20;
199
+ var UNVISITED_SAMPLE_MAX = 100;
200
+ var PATH_SHAPE = /^\/(?:[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*)?$/;
201
+ /**
202
+ * A resource path this walker will request: absolute, plain segments, and no `.` or `..`
203
+ * segment — URL resolution would otherwise lift the request out of the gateway's tree.
204
+ */
205
+ function isResourcePath(path) {
206
+ if (!PATH_SHAPE.test(path)) return false;
207
+ return path.split("/").slice(1).every((segment) => segment !== "." && segment !== "..");
208
+ }
209
+ var explorerOutcomeSchema = require_bosch_instances._enum([
210
+ "ok",
211
+ "forbidden",
212
+ "not-found",
213
+ "rate-limited",
214
+ "error"
215
+ ]);
216
+ var exploredResourceSchema = require_bosch_instances.object({
217
+ path: require_bosch_instances.string(),
218
+ httpStatus: require_bosch_instances.number(),
219
+ outcome: explorerOutcomeSchema,
220
+ type: require_bosch_instances.string().optional(),
221
+ value: require_bosch_instances.union([
222
+ require_bosch_instances.string(),
223
+ require_bosch_instances.number(),
224
+ require_bosch_instances.boolean(),
225
+ require_bosch_instances._null()
226
+ ]).optional(),
227
+ unit: require_bosch_instances.string().optional(),
228
+ writeable: require_bosch_instances.boolean().optional(),
229
+ recordable: require_bosch_instances.boolean().optional(),
230
+ allowedValues: require_bosch_instances.array(require_bosch_instances.string()).optional(),
231
+ minValue: require_bosch_instances.number().optional(),
232
+ maxValue: require_bosch_instances.number().optional(),
233
+ childCount: require_bosch_instances.number().optional(),
234
+ /** References of this container that were not a walkable path. */
235
+ droppedReferences: require_bosch_instances.number().optional(),
236
+ error: require_bosch_instances.string().optional()
237
+ });
238
+ var explorerSummarySchema = require_bosch_instances.object({
239
+ ok: require_bosch_instances.number(),
240
+ forbidden: require_bosch_instances.number(),
241
+ "not-found": require_bosch_instances.number(),
242
+ "rate-limited": require_bosch_instances.number(),
243
+ error: require_bosch_instances.number()
244
+ });
245
+ var explorerStopReasonSchema = require_bosch_instances._enum([
246
+ "complete",
247
+ "max-nodes",
248
+ "rate-limited",
249
+ "deadline"
250
+ ]);
251
+ function isRecord$3(value) {
252
+ return typeof value === "object" && value !== null && !Array.isArray(value);
253
+ }
254
+ function finite$1(value) {
255
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
256
+ }
257
+ function text$1(value) {
258
+ return typeof value === "string" && value.length > 0 ? value : void 0;
259
+ }
260
+ function truncate(value) {
261
+ return value.length > VALUE_MAX_CHARS ? `${value.slice(0, VALUE_MAX_CHARS - 1)}…` : value;
262
+ }
263
+ function shownValue(value) {
264
+ if (value === null || value === void 0) return null;
265
+ if (typeof value === "number" || typeof value === "boolean") return value;
266
+ if (typeof value === "string") return truncate(value);
267
+ return truncate(JSON.stringify(value));
268
+ }
269
+ function outcomeOf(status) {
270
+ if (status >= 200 && status < 300) return "ok";
271
+ if (status === 403) return "forbidden";
272
+ if (status === 404) return "not-found";
273
+ if (status === 429) return "rate-limited";
274
+ return "error";
275
+ }
276
+ /** What a reference names: its `id`, else the part of its `uri` after `/resource`. */
277
+ function referenceText(ref) {
278
+ if (!isRecord$3(ref)) return null;
279
+ const id = text$1(ref["id"]);
280
+ if (id !== void 0 && id.startsWith("/")) return id;
281
+ const uri = text$1(ref["uri"]);
282
+ if (uri === void 0) return id ?? null;
283
+ const at = uri.indexOf("/resource/");
284
+ if (at < 0) return uri;
285
+ return uri.slice(at + 9);
286
+ }
287
+ /** The walkable resource path a reference names, or null. */
288
+ function referencePath(ref) {
289
+ const path = referenceText(ref);
290
+ return path !== null && isResourcePath(path) ? path : null;
291
+ }
292
+ function referencesOf(body) {
293
+ if (!isRecord$3(body)) return null;
294
+ const refs = body["references"];
295
+ if (!Array.isArray(refs)) return null;
296
+ const paths = [];
297
+ const dropped = [];
298
+ for (const ref of refs) {
299
+ const path = referencePath(ref);
300
+ if (path !== null) paths.push(path);
301
+ else dropped.push(truncate(referenceText(ref) ?? JSON.stringify(ref) ?? "undefined"));
302
+ }
303
+ return {
304
+ total: refs.length,
305
+ paths,
306
+ dropped
307
+ };
308
+ }
309
+ function describeLeaf(path, status, body) {
310
+ if (!isRecord$3(body)) return {
311
+ path,
312
+ httpStatus: status,
313
+ outcome: "ok"
314
+ };
315
+ const allowed = body["allowedValues"];
316
+ const allowedValues = Array.isArray(allowed) ? allowed.map((v) => truncate(String(v))) : void 0;
317
+ const type = text$1(body["type"]);
318
+ const unit = text$1(body["unitOfMeasure"]);
319
+ const minValue = finite$1(body["minValue"]);
320
+ const maxValue = finite$1(body["maxValue"]);
321
+ const writeable = body["writeable"];
322
+ const recordable = body["recordable"];
323
+ return {
324
+ path,
325
+ httpStatus: status,
326
+ outcome: "ok",
327
+ ...type !== void 0 ? { type } : {},
328
+ ..."value" in body ? { value: shownValue(body["value"]) } : {},
329
+ ...unit !== void 0 ? { unit } : {},
330
+ ...typeof writeable === "number" || typeof writeable === "boolean" ? { writeable: Boolean(writeable) } : {},
331
+ ...typeof recordable === "number" || typeof recordable === "boolean" ? { recordable: Boolean(recordable) } : {},
332
+ ...allowedValues !== void 0 ? { allowedValues } : {},
333
+ ...minValue !== void 0 ? { minValue } : {},
334
+ ...maxValue !== void 0 ? { maxValue } : {}
335
+ };
336
+ }
337
+ function emptySummary() {
338
+ return {
339
+ ok: 0,
340
+ forbidden: 0,
341
+ "not-found": 0,
342
+ "rate-limited": 0,
343
+ error: 0
344
+ };
345
+ }
346
+ function clampDeadline(requested) {
347
+ return Math.min(Math.max(Math.floor(requested ?? 45e3), 1), EXPLORER_MAX_DEADLINE_MS);
348
+ }
349
+ function clampNodes(requested) {
350
+ return Math.min(Math.max(Math.floor(requested ?? 400), 1), EXPLORER_MAX_NODES_CAP);
351
+ }
352
+ var defaultSleep = (ms) => new Promise((resolve) => {
353
+ setTimeout(resolve, ms);
354
+ });
355
+ async function exploreHomeComResources(input) {
356
+ const root = input.root ?? "/";
357
+ if (!isResourcePath(root)) throw new Error(`Not a resource path: ${root}`);
358
+ const maxNodes = clampNodes(input.maxNodes);
359
+ const delayMs = Math.max(input.delayMs ?? 150, 150);
360
+ const sleep = input.sleep ?? defaultSleep;
361
+ const now = input.now ?? Date.now;
362
+ const deadlineAt = now() + clampDeadline(input.deadlineMs);
363
+ const queue = [root];
364
+ const queued = new Set([root]);
365
+ const resources = [];
366
+ const summary = emptySummary();
367
+ let calls = 0;
368
+ let stoppedBy = "complete";
369
+ let droppedReferences = 0;
370
+ const droppedSamples = [];
371
+ const enqueue = (paths) => {
372
+ for (const path of paths) {
373
+ if (queued.has(path)) continue;
374
+ queued.add(path);
375
+ queue.push(path);
376
+ }
377
+ };
378
+ while (queue.length > 0) {
379
+ if (calls >= maxNodes) {
380
+ stoppedBy = "max-nodes";
381
+ break;
382
+ }
383
+ if (now() + (calls > 0 ? delayMs : 0) >= deadlineAt) {
384
+ stoppedBy = "deadline";
385
+ break;
386
+ }
387
+ const path = queue.shift();
388
+ if (path === void 0) break;
389
+ if (calls > 0) await sleep(delayMs);
390
+ calls += 1;
391
+ let answer;
392
+ try {
393
+ answer = await input.read(path);
394
+ } catch (err) {
395
+ const error = err instanceof Error ? err.message : String(err);
396
+ resources.push({
397
+ path,
398
+ httpStatus: 0,
399
+ outcome: "error",
400
+ error
401
+ });
402
+ summary.error += 1;
403
+ continue;
404
+ }
405
+ const outcome = outcomeOf(answer.status);
406
+ summary[outcome] += 1;
407
+ if (outcome === "rate-limited") {
408
+ resources.push({
409
+ path,
410
+ httpStatus: answer.status,
411
+ outcome
412
+ });
413
+ stoppedBy = "rate-limited";
414
+ break;
415
+ }
416
+ if (outcome !== "ok") {
417
+ resources.push({
418
+ path,
419
+ httpStatus: answer.status,
420
+ outcome
421
+ });
422
+ if (path === "/" && root === "/") enqueue(EXPLORER_SEED_ROOTS);
423
+ continue;
424
+ }
425
+ const children = referencesOf(answer.body);
426
+ if (children !== null) {
427
+ const type = isRecord$3(answer.body) ? text$1(answer.body["type"]) : void 0;
428
+ resources.push({
429
+ path,
430
+ httpStatus: answer.status,
431
+ outcome,
432
+ type: type ?? "refEnum",
433
+ childCount: children.total,
434
+ ...children.dropped.length > 0 ? { droppedReferences: children.dropped.length } : {}
435
+ });
436
+ droppedReferences += children.dropped.length;
437
+ for (const ref of children.dropped) if (droppedSamples.length < DROPPED_SAMPLE_MAX) droppedSamples.push(`${path} -> ${ref}`);
438
+ enqueue(children.paths);
439
+ continue;
440
+ }
441
+ resources.push(describeLeaf(path, answer.status, answer.body));
442
+ if (path === "/" && root === "/") enqueue(EXPLORER_SEED_ROOTS);
443
+ }
444
+ return {
445
+ root,
446
+ resources,
447
+ summary,
448
+ stoppedBy,
449
+ unvisited: queue.length,
450
+ unvisitedPaths: queue.slice(0, UNVISITED_SAMPLE_MAX),
451
+ droppedReferences,
452
+ droppedSamples
453
+ };
454
+ }
181
455
  //#endregion
182
456
  //#region src/homecom/custom-actions.ts
183
457
  var gatewayChoice = require_bosch_instances.object({
@@ -186,6 +460,27 @@ var gatewayChoice = require_bosch_instances.object({
186
460
  brand: HomeComBrandSchema,
187
461
  region: HomeComRegionSchema
188
462
  });
463
+ var exploreResourcesInput = require_bosch_instances.object({
464
+ /** The HomeCom integration instance; may be omitted when exactly one is connected. */
465
+ integrationId: require_bosch_instances.string().min(1).optional(),
466
+ root: require_bosch_instances.string().refine(isResourcePath, "an absolute resource path such as /heatSources, no . or .. segment").optional(),
467
+ maxNodes: require_bosch_instances.number().int().min(1).max(EXPLORER_MAX_NODES_CAP).optional(),
468
+ /** Wall-clock budget; the walk returns what it has, with `stoppedBy: 'deadline'`. */
469
+ deadlineMs: require_bosch_instances.number().int().min(1e3).max(EXPLORER_MAX_DEADLINE_MS).optional()
470
+ });
471
+ var exploreResourcesOutput = require_bosch_instances.object({
472
+ integrationId: require_bosch_instances.string(),
473
+ /** Null when the integration has no snapshot yet: unknown, not empty. */
474
+ gatewayId: require_bosch_instances.string().nullable(),
475
+ root: require_bosch_instances.string(),
476
+ stoppedBy: explorerStopReasonSchema,
477
+ unvisited: require_bosch_instances.number(),
478
+ unvisitedPaths: require_bosch_instances.array(require_bosch_instances.string()),
479
+ droppedReferences: require_bosch_instances.number(),
480
+ droppedSamples: require_bosch_instances.array(require_bosch_instances.string()),
481
+ summary: explorerSummarySchema,
482
+ resources: require_bosch_instances.array(exploredResourceSchema)
483
+ });
189
484
  var homeComActions = require_bosch_instances.defineCustomActions({
190
485
  beginSignIn: require_bosch_instances.customAction(require_bosch_instances.object({}), require_bosch_instances.object({
191
486
  attemptId: require_bosch_instances.string().min(1),
@@ -219,9 +514,95 @@ var homeComActions = require_bosch_instances.defineCustomActions({
219
514
  }), {
220
515
  kind: "mutation",
221
516
  auth: "admin"
517
+ }),
518
+ /** Read-only walk of the gateway's resource tree, for diagnosing what it serves. */
519
+ exploreResources: require_bosch_instances.customAction(exploreResourcesInput, exploreResourcesOutput, {
520
+ kind: "query",
521
+ auth: "admin"
222
522
  })
223
523
  });
224
524
  //#endregion
525
+ //#region src/homecom/homecom-explore-action.ts
526
+ /** The action refused before it walked anything; `reason` says why. */
527
+ var HomeComExploreRefusedError = class extends Error {
528
+ reason;
529
+ constructor(reason, message) {
530
+ super(message);
531
+ this.name = "HomeComExploreRefusedError";
532
+ this.reason = reason;
533
+ }
534
+ };
535
+ function pickIntegration(input, connected) {
536
+ if (input.integrationId !== void 0) return input.integrationId;
537
+ if (connected.length === 1 && connected[0] !== void 0) return connected[0];
538
+ if (connected.length === 0) throw new HomeComExploreRefusedError("not-connected", "No HomeCom integration is connected.");
539
+ throw new HomeComExploreRefusedError("ambiguous", `Several HomeCom integrations are connected; name one with integrationId (${connected.join(", ")}).`);
540
+ }
541
+ /**
542
+ * The `exploreResources` action: a read-only walk of one integration's gateway. One walk
543
+ * per integration at a time — a second call while one runs is refused, never queued.
544
+ */
545
+ function createHomeComExploreAction(deps) {
546
+ const running = /* @__PURE__ */ new Set();
547
+ return async (input) => {
548
+ const integrationId = pickIntegration(input, deps.listConnected());
549
+ if (running.has(integrationId)) throw new HomeComExploreRefusedError("already-running", `A resource walk is already running for HomeCom integration ${integrationId}.`);
550
+ const client = deps.getClient(integrationId);
551
+ if (client === null) throw new HomeComExploreRefusedError("not-connected", `HomeCom integration ${integrationId} is not connected.`);
552
+ running.add(integrationId);
553
+ try {
554
+ return await walk(input, integrationId, client, deps);
555
+ } finally {
556
+ running.delete(integrationId);
557
+ }
558
+ };
559
+ }
560
+ async function walk(input, integrationId, client, deps) {
561
+ const result = await exploreHomeComResources({
562
+ read: (path) => client.readResource(path),
563
+ ...input.root !== void 0 ? { root: input.root } : {},
564
+ ...input.maxNodes !== void 0 ? { maxNodes: input.maxNodes } : {},
565
+ ...input.deadlineMs !== void 0 ? { deadlineMs: input.deadlineMs } : {},
566
+ ...deps.sleep !== void 0 ? { sleep: deps.sleep } : {},
567
+ ...deps.now !== void 0 ? { now: deps.now } : {}
568
+ });
569
+ const gatewayId = deps.getGatewayId(integrationId);
570
+ if (result.droppedReferences > 0) deps.logger.warn("HomeCom resource walk dropped references that are not a walkable path", {
571
+ tags: { integrationId },
572
+ meta: {
573
+ gatewayId,
574
+ droppedReferences: result.droppedReferences,
575
+ samples: result.droppedSamples
576
+ }
577
+ });
578
+ deps.logger.info("HomeCom resource tree explored", {
579
+ tags: { integrationId },
580
+ meta: {
581
+ gatewayId,
582
+ root: result.root,
583
+ visited: result.resources.length,
584
+ stoppedBy: result.stoppedBy,
585
+ unvisited: result.unvisited,
586
+ droppedReferences: result.droppedReferences,
587
+ ...result.summary
588
+ }
589
+ });
590
+ return {
591
+ integrationId,
592
+ gatewayId,
593
+ ...result
594
+ };
595
+ }
596
+ /** The addon's deps: registered integrations that hold a live client; gateway from the snapshot. */
597
+ function homeComExploreDeps(wiring) {
598
+ return {
599
+ listConnected: () => wiring.registry.list().filter((id) => wiring.connections.getClient(id) !== null),
600
+ getClient: (id) => wiring.connections.getClient(id),
601
+ getGatewayId: (id) => wiring.connections.getSnapshot(id)?.gateway.gatewayId ?? null,
602
+ logger: wiring.logger
603
+ };
604
+ }
605
+ //#endregion
225
606
  //#region src/homecom/homecom-device-type.ts
226
607
  /**
227
608
  * Device types the PointT gateway list reports. The set is the one the MIT
@@ -1592,6 +1973,13 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1592
1973
  },
1593
1974
  async writeHeatPump(writes) {
1594
1975
  for (const write of writes) await putWrite(write);
1976
+ },
1977
+ async readResource(path) {
1978
+ const res = await authed(homeComResourceUrl(gatewayId, path), { method: "GET" });
1979
+ return {
1980
+ status: res.status,
1981
+ body: res.body
1982
+ };
1595
1983
  }
1596
1984
  };
1597
1985
  }
@@ -3095,6 +3483,11 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
3095
3483
  });
3096
3484
  await this.reconcileIntegrations();
3097
3485
  this.subscribeIntegrationLifecycle();
3486
+ const explore = createHomeComExploreAction(homeComExploreDeps({
3487
+ registry: this.clients,
3488
+ connections: homeComConnections,
3489
+ logger: this.ctx.logger
3490
+ }));
3098
3491
  return {
3099
3492
  providers: [
3100
3493
  ...regs,
@@ -3121,7 +3514,8 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
3121
3514
  signOut: async () => ({
3122
3515
  signedOut: true,
3123
3516
  summary: "Signed out."
3124
- })
3517
+ }),
3518
+ exploreResources: explore
3125
3519
  }
3126
3520
  };
3127
3521
  }
@@ -1,5 +1,5 @@
1
- import { A as RecordIntegrationInstances, B as array, F as entryBody, G as string, H as number, M as customAction, N as defineCustomActions, P as defineEntryKind, R as rowsDepsOver, U as object, V as literal, X as errMsg, Y as DeviceType, Z as EventCategory, _ as heatPumpControlCapability, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, k as waterHeaterCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, r as keepBodyKeys, t as boschRowsMigration, v as heatPumpStatusCapability, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-B2e4DAI1.mjs";
2
- import { a as loginFailureForAttempt, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, s as HOMECOM_REDIRECT_URI, t as setIntegrationDevicesOnline } from "../account-integration-offline-BKDol6kz.mjs";
1
+ import { $ as EventCategory, A as RecordIntegrationInstances, B as _null, F as entryBody, G as object, H as boolean, J as union, M as customAction, N as defineCustomActions, P as defineEntryKind, Q as errMsg, R as rowsDepsOver, U as literal, V as array, W as number, Z as DeviceType, _ as heatPumpControlCapability, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, k as waterHeaterCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as string, r as keepBodyKeys, t as boschRowsMigration, v as heatPumpStatusCapability, x as integrationInstancesCapability, z as _enum } from "../bosch-instances-CdvcBYye.mjs";
2
+ import { a as loginFailureForAttempt, i as createSignInAttemptStore, n as cascadeRemoveDevicesForIntegration, o as parseOAuthCallback, r as buildSignInGuideFields, s as HOMECOM_REDIRECT_URI, t as setIntegrationDevicesOnline } from "../account-integration-offline-B1Wgr_rT.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  //#region src/homecom/config.ts
5
5
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -177,6 +177,280 @@ function buildHomeComFormSchema() {
177
177
  ]
178
178
  }] };
179
179
  }
180
+ var EXPLORER_MAX_NODES_CAP = 1500;
181
+ var VALUE_MAX_CHARS = 200;
182
+ /** Roots walked when `/` does not list its children. */
183
+ var EXPLORER_SEED_ROOTS = [
184
+ "/system",
185
+ "/heatSources",
186
+ "/heatingCircuits",
187
+ "/dhwCircuits",
188
+ "/gateway",
189
+ "/notifications",
190
+ "/recordings",
191
+ "/devices",
192
+ "/solarCircuits",
193
+ "/ventilation"
194
+ ];
195
+ /** Under the 60 s UDS request timeout a forked addon's action answers within. */
196
+ var EXPLORER_MAX_DEADLINE_MS = 5e4;
197
+ var DROPPED_SAMPLE_MAX = 20;
198
+ var UNVISITED_SAMPLE_MAX = 100;
199
+ var PATH_SHAPE = /^\/(?:[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*)?$/;
200
+ /**
201
+ * A resource path this walker will request: absolute, plain segments, and no `.` or `..`
202
+ * segment — URL resolution would otherwise lift the request out of the gateway's tree.
203
+ */
204
+ function isResourcePath(path) {
205
+ if (!PATH_SHAPE.test(path)) return false;
206
+ return path.split("/").slice(1).every((segment) => segment !== "." && segment !== "..");
207
+ }
208
+ var explorerOutcomeSchema = _enum([
209
+ "ok",
210
+ "forbidden",
211
+ "not-found",
212
+ "rate-limited",
213
+ "error"
214
+ ]);
215
+ var exploredResourceSchema = object({
216
+ path: string(),
217
+ httpStatus: number(),
218
+ outcome: explorerOutcomeSchema,
219
+ type: string().optional(),
220
+ value: union([
221
+ string(),
222
+ number(),
223
+ boolean(),
224
+ _null()
225
+ ]).optional(),
226
+ unit: string().optional(),
227
+ writeable: boolean().optional(),
228
+ recordable: boolean().optional(),
229
+ allowedValues: array(string()).optional(),
230
+ minValue: number().optional(),
231
+ maxValue: number().optional(),
232
+ childCount: number().optional(),
233
+ /** References of this container that were not a walkable path. */
234
+ droppedReferences: number().optional(),
235
+ error: string().optional()
236
+ });
237
+ var explorerSummarySchema = object({
238
+ ok: number(),
239
+ forbidden: number(),
240
+ "not-found": number(),
241
+ "rate-limited": number(),
242
+ error: number()
243
+ });
244
+ var explorerStopReasonSchema = _enum([
245
+ "complete",
246
+ "max-nodes",
247
+ "rate-limited",
248
+ "deadline"
249
+ ]);
250
+ function isRecord$3(value) {
251
+ return typeof value === "object" && value !== null && !Array.isArray(value);
252
+ }
253
+ function finite$1(value) {
254
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
255
+ }
256
+ function text$1(value) {
257
+ return typeof value === "string" && value.length > 0 ? value : void 0;
258
+ }
259
+ function truncate(value) {
260
+ return value.length > VALUE_MAX_CHARS ? `${value.slice(0, VALUE_MAX_CHARS - 1)}…` : value;
261
+ }
262
+ function shownValue(value) {
263
+ if (value === null || value === void 0) return null;
264
+ if (typeof value === "number" || typeof value === "boolean") return value;
265
+ if (typeof value === "string") return truncate(value);
266
+ return truncate(JSON.stringify(value));
267
+ }
268
+ function outcomeOf(status) {
269
+ if (status >= 200 && status < 300) return "ok";
270
+ if (status === 403) return "forbidden";
271
+ if (status === 404) return "not-found";
272
+ if (status === 429) return "rate-limited";
273
+ return "error";
274
+ }
275
+ /** What a reference names: its `id`, else the part of its `uri` after `/resource`. */
276
+ function referenceText(ref) {
277
+ if (!isRecord$3(ref)) return null;
278
+ const id = text$1(ref["id"]);
279
+ if (id !== void 0 && id.startsWith("/")) return id;
280
+ const uri = text$1(ref["uri"]);
281
+ if (uri === void 0) return id ?? null;
282
+ const at = uri.indexOf("/resource/");
283
+ if (at < 0) return uri;
284
+ return uri.slice(at + 9);
285
+ }
286
+ /** The walkable resource path a reference names, or null. */
287
+ function referencePath(ref) {
288
+ const path = referenceText(ref);
289
+ return path !== null && isResourcePath(path) ? path : null;
290
+ }
291
+ function referencesOf(body) {
292
+ if (!isRecord$3(body)) return null;
293
+ const refs = body["references"];
294
+ if (!Array.isArray(refs)) return null;
295
+ const paths = [];
296
+ const dropped = [];
297
+ for (const ref of refs) {
298
+ const path = referencePath(ref);
299
+ if (path !== null) paths.push(path);
300
+ else dropped.push(truncate(referenceText(ref) ?? JSON.stringify(ref) ?? "undefined"));
301
+ }
302
+ return {
303
+ total: refs.length,
304
+ paths,
305
+ dropped
306
+ };
307
+ }
308
+ function describeLeaf(path, status, body) {
309
+ if (!isRecord$3(body)) return {
310
+ path,
311
+ httpStatus: status,
312
+ outcome: "ok"
313
+ };
314
+ const allowed = body["allowedValues"];
315
+ const allowedValues = Array.isArray(allowed) ? allowed.map((v) => truncate(String(v))) : void 0;
316
+ const type = text$1(body["type"]);
317
+ const unit = text$1(body["unitOfMeasure"]);
318
+ const minValue = finite$1(body["minValue"]);
319
+ const maxValue = finite$1(body["maxValue"]);
320
+ const writeable = body["writeable"];
321
+ const recordable = body["recordable"];
322
+ return {
323
+ path,
324
+ httpStatus: status,
325
+ outcome: "ok",
326
+ ...type !== void 0 ? { type } : {},
327
+ ..."value" in body ? { value: shownValue(body["value"]) } : {},
328
+ ...unit !== void 0 ? { unit } : {},
329
+ ...typeof writeable === "number" || typeof writeable === "boolean" ? { writeable: Boolean(writeable) } : {},
330
+ ...typeof recordable === "number" || typeof recordable === "boolean" ? { recordable: Boolean(recordable) } : {},
331
+ ...allowedValues !== void 0 ? { allowedValues } : {},
332
+ ...minValue !== void 0 ? { minValue } : {},
333
+ ...maxValue !== void 0 ? { maxValue } : {}
334
+ };
335
+ }
336
+ function emptySummary() {
337
+ return {
338
+ ok: 0,
339
+ forbidden: 0,
340
+ "not-found": 0,
341
+ "rate-limited": 0,
342
+ error: 0
343
+ };
344
+ }
345
+ function clampDeadline(requested) {
346
+ return Math.min(Math.max(Math.floor(requested ?? 45e3), 1), EXPLORER_MAX_DEADLINE_MS);
347
+ }
348
+ function clampNodes(requested) {
349
+ return Math.min(Math.max(Math.floor(requested ?? 400), 1), EXPLORER_MAX_NODES_CAP);
350
+ }
351
+ var defaultSleep = (ms) => new Promise((resolve) => {
352
+ setTimeout(resolve, ms);
353
+ });
354
+ async function exploreHomeComResources(input) {
355
+ const root = input.root ?? "/";
356
+ if (!isResourcePath(root)) throw new Error(`Not a resource path: ${root}`);
357
+ const maxNodes = clampNodes(input.maxNodes);
358
+ const delayMs = Math.max(input.delayMs ?? 150, 150);
359
+ const sleep = input.sleep ?? defaultSleep;
360
+ const now = input.now ?? Date.now;
361
+ const deadlineAt = now() + clampDeadline(input.deadlineMs);
362
+ const queue = [root];
363
+ const queued = new Set([root]);
364
+ const resources = [];
365
+ const summary = emptySummary();
366
+ let calls = 0;
367
+ let stoppedBy = "complete";
368
+ let droppedReferences = 0;
369
+ const droppedSamples = [];
370
+ const enqueue = (paths) => {
371
+ for (const path of paths) {
372
+ if (queued.has(path)) continue;
373
+ queued.add(path);
374
+ queue.push(path);
375
+ }
376
+ };
377
+ while (queue.length > 0) {
378
+ if (calls >= maxNodes) {
379
+ stoppedBy = "max-nodes";
380
+ break;
381
+ }
382
+ if (now() + (calls > 0 ? delayMs : 0) >= deadlineAt) {
383
+ stoppedBy = "deadline";
384
+ break;
385
+ }
386
+ const path = queue.shift();
387
+ if (path === void 0) break;
388
+ if (calls > 0) await sleep(delayMs);
389
+ calls += 1;
390
+ let answer;
391
+ try {
392
+ answer = await input.read(path);
393
+ } catch (err) {
394
+ const error = err instanceof Error ? err.message : String(err);
395
+ resources.push({
396
+ path,
397
+ httpStatus: 0,
398
+ outcome: "error",
399
+ error
400
+ });
401
+ summary.error += 1;
402
+ continue;
403
+ }
404
+ const outcome = outcomeOf(answer.status);
405
+ summary[outcome] += 1;
406
+ if (outcome === "rate-limited") {
407
+ resources.push({
408
+ path,
409
+ httpStatus: answer.status,
410
+ outcome
411
+ });
412
+ stoppedBy = "rate-limited";
413
+ break;
414
+ }
415
+ if (outcome !== "ok") {
416
+ resources.push({
417
+ path,
418
+ httpStatus: answer.status,
419
+ outcome
420
+ });
421
+ if (path === "/" && root === "/") enqueue(EXPLORER_SEED_ROOTS);
422
+ continue;
423
+ }
424
+ const children = referencesOf(answer.body);
425
+ if (children !== null) {
426
+ const type = isRecord$3(answer.body) ? text$1(answer.body["type"]) : void 0;
427
+ resources.push({
428
+ path,
429
+ httpStatus: answer.status,
430
+ outcome,
431
+ type: type ?? "refEnum",
432
+ childCount: children.total,
433
+ ...children.dropped.length > 0 ? { droppedReferences: children.dropped.length } : {}
434
+ });
435
+ droppedReferences += children.dropped.length;
436
+ for (const ref of children.dropped) if (droppedSamples.length < DROPPED_SAMPLE_MAX) droppedSamples.push(`${path} -> ${ref}`);
437
+ enqueue(children.paths);
438
+ continue;
439
+ }
440
+ resources.push(describeLeaf(path, answer.status, answer.body));
441
+ if (path === "/" && root === "/") enqueue(EXPLORER_SEED_ROOTS);
442
+ }
443
+ return {
444
+ root,
445
+ resources,
446
+ summary,
447
+ stoppedBy,
448
+ unvisited: queue.length,
449
+ unvisitedPaths: queue.slice(0, UNVISITED_SAMPLE_MAX),
450
+ droppedReferences,
451
+ droppedSamples
452
+ };
453
+ }
180
454
  //#endregion
181
455
  //#region src/homecom/custom-actions.ts
182
456
  var gatewayChoice = object({
@@ -185,6 +459,27 @@ var gatewayChoice = object({
185
459
  brand: HomeComBrandSchema,
186
460
  region: HomeComRegionSchema
187
461
  });
462
+ var exploreResourcesInput = object({
463
+ /** The HomeCom integration instance; may be omitted when exactly one is connected. */
464
+ integrationId: string().min(1).optional(),
465
+ root: string().refine(isResourcePath, "an absolute resource path such as /heatSources, no . or .. segment").optional(),
466
+ maxNodes: number().int().min(1).max(EXPLORER_MAX_NODES_CAP).optional(),
467
+ /** Wall-clock budget; the walk returns what it has, with `stoppedBy: 'deadline'`. */
468
+ deadlineMs: number().int().min(1e3).max(EXPLORER_MAX_DEADLINE_MS).optional()
469
+ });
470
+ var exploreResourcesOutput = object({
471
+ integrationId: string(),
472
+ /** Null when the integration has no snapshot yet: unknown, not empty. */
473
+ gatewayId: string().nullable(),
474
+ root: string(),
475
+ stoppedBy: explorerStopReasonSchema,
476
+ unvisited: number(),
477
+ unvisitedPaths: array(string()),
478
+ droppedReferences: number(),
479
+ droppedSamples: array(string()),
480
+ summary: explorerSummarySchema,
481
+ resources: array(exploredResourceSchema)
482
+ });
188
483
  var homeComActions = defineCustomActions({
189
484
  beginSignIn: customAction(object({}), object({
190
485
  attemptId: string().min(1),
@@ -218,9 +513,95 @@ var homeComActions = defineCustomActions({
218
513
  }), {
219
514
  kind: "mutation",
220
515
  auth: "admin"
516
+ }),
517
+ /** Read-only walk of the gateway's resource tree, for diagnosing what it serves. */
518
+ exploreResources: customAction(exploreResourcesInput, exploreResourcesOutput, {
519
+ kind: "query",
520
+ auth: "admin"
221
521
  })
222
522
  });
223
523
  //#endregion
524
+ //#region src/homecom/homecom-explore-action.ts
525
+ /** The action refused before it walked anything; `reason` says why. */
526
+ var HomeComExploreRefusedError = class extends Error {
527
+ reason;
528
+ constructor(reason, message) {
529
+ super(message);
530
+ this.name = "HomeComExploreRefusedError";
531
+ this.reason = reason;
532
+ }
533
+ };
534
+ function pickIntegration(input, connected) {
535
+ if (input.integrationId !== void 0) return input.integrationId;
536
+ if (connected.length === 1 && connected[0] !== void 0) return connected[0];
537
+ if (connected.length === 0) throw new HomeComExploreRefusedError("not-connected", "No HomeCom integration is connected.");
538
+ throw new HomeComExploreRefusedError("ambiguous", `Several HomeCom integrations are connected; name one with integrationId (${connected.join(", ")}).`);
539
+ }
540
+ /**
541
+ * The `exploreResources` action: a read-only walk of one integration's gateway. One walk
542
+ * per integration at a time — a second call while one runs is refused, never queued.
543
+ */
544
+ function createHomeComExploreAction(deps) {
545
+ const running = /* @__PURE__ */ new Set();
546
+ return async (input) => {
547
+ const integrationId = pickIntegration(input, deps.listConnected());
548
+ if (running.has(integrationId)) throw new HomeComExploreRefusedError("already-running", `A resource walk is already running for HomeCom integration ${integrationId}.`);
549
+ const client = deps.getClient(integrationId);
550
+ if (client === null) throw new HomeComExploreRefusedError("not-connected", `HomeCom integration ${integrationId} is not connected.`);
551
+ running.add(integrationId);
552
+ try {
553
+ return await walk(input, integrationId, client, deps);
554
+ } finally {
555
+ running.delete(integrationId);
556
+ }
557
+ };
558
+ }
559
+ async function walk(input, integrationId, client, deps) {
560
+ const result = await exploreHomeComResources({
561
+ read: (path) => client.readResource(path),
562
+ ...input.root !== void 0 ? { root: input.root } : {},
563
+ ...input.maxNodes !== void 0 ? { maxNodes: input.maxNodes } : {},
564
+ ...input.deadlineMs !== void 0 ? { deadlineMs: input.deadlineMs } : {},
565
+ ...deps.sleep !== void 0 ? { sleep: deps.sleep } : {},
566
+ ...deps.now !== void 0 ? { now: deps.now } : {}
567
+ });
568
+ const gatewayId = deps.getGatewayId(integrationId);
569
+ if (result.droppedReferences > 0) deps.logger.warn("HomeCom resource walk dropped references that are not a walkable path", {
570
+ tags: { integrationId },
571
+ meta: {
572
+ gatewayId,
573
+ droppedReferences: result.droppedReferences,
574
+ samples: result.droppedSamples
575
+ }
576
+ });
577
+ deps.logger.info("HomeCom resource tree explored", {
578
+ tags: { integrationId },
579
+ meta: {
580
+ gatewayId,
581
+ root: result.root,
582
+ visited: result.resources.length,
583
+ stoppedBy: result.stoppedBy,
584
+ unvisited: result.unvisited,
585
+ droppedReferences: result.droppedReferences,
586
+ ...result.summary
587
+ }
588
+ });
589
+ return {
590
+ integrationId,
591
+ gatewayId,
592
+ ...result
593
+ };
594
+ }
595
+ /** The addon's deps: registered integrations that hold a live client; gateway from the snapshot. */
596
+ function homeComExploreDeps(wiring) {
597
+ return {
598
+ listConnected: () => wiring.registry.list().filter((id) => wiring.connections.getClient(id) !== null),
599
+ getClient: (id) => wiring.connections.getClient(id),
600
+ getGatewayId: (id) => wiring.connections.getSnapshot(id)?.gateway.gatewayId ?? null,
601
+ logger: wiring.logger
602
+ };
603
+ }
604
+ //#endregion
224
605
  //#region src/homecom/homecom-device-type.ts
225
606
  /**
226
607
  * Device types the PointT gateway list reports. The set is the one the MIT
@@ -1591,6 +1972,13 @@ function createHomeComApiClient(config, gatewayId, deps = {}) {
1591
1972
  },
1592
1973
  async writeHeatPump(writes) {
1593
1974
  for (const write of writes) await putWrite(write);
1975
+ },
1976
+ async readResource(path) {
1977
+ const res = await authed(homeComResourceUrl(gatewayId, path), { method: "GET" });
1978
+ return {
1979
+ status: res.status,
1980
+ body: res.body
1981
+ };
1594
1982
  }
1595
1983
  };
1596
1984
  }
@@ -3094,6 +3482,11 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
3094
3482
  });
3095
3483
  await this.reconcileIntegrations();
3096
3484
  this.subscribeIntegrationLifecycle();
3485
+ const explore = createHomeComExploreAction(homeComExploreDeps({
3486
+ registry: this.clients,
3487
+ connections: homeComConnections,
3488
+ logger: this.ctx.logger
3489
+ }));
3097
3490
  return {
3098
3491
  providers: [
3099
3492
  ...regs,
@@ -3120,7 +3513,8 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
3120
3513
  signOut: async () => ({
3121
3514
  signedOut: true,
3122
3515
  summary: "Signed out."
3123
- })
3516
+ }),
3517
+ exploreResources: explore
3124
3518
  }
3125
3519
  };
3126
3520
  }
package/dist/shc/addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region \0rolldown/runtime.js
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  //#endregion
5
- const require_bosch_instances = require("../bosch-instances-BHFcR1TK.js");
5
+ const require_bosch_instances = require("../bosch-instances-CYBnIcHg.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_https = require("node:https");
8
8
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { A as RecordIntegrationInstances, B as array, D as switchCapability, F as entryBody, G as string, H as number, M as customAction, N as defineCustomActions, O as temperatureSensorCapability, P as defineEntryKind, R as rowsDepsOver, U as object, V as literal, X as errMsg, Y as DeviceType, Z as EventCategory, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, l as batteryCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, r as keepBodyKeys, t as boschRowsMigration, x as integrationInstancesCapability, y as heatingValveCapability, z as _enum } from "../bosch-instances-B2e4DAI1.mjs";
1
+ import { $ as EventCategory, A as RecordIntegrationInstances, D as switchCapability, F as entryBody, G as object, M as customAction, N as defineCustomActions, O as temperatureSensorCapability, P as defineEntryKind, Q as errMsg, R as rowsDepsOver, U as literal, V as array, W as number, Z as DeviceType, a as BaseDevice, d as climateControlCapability, i as testBoschInstance, l as batteryCapability, n as boschRowsPorts, o as BaseDeviceProvider, p as deviceAdoptionCapability, q as string, r as keepBodyKeys, t as boschRowsMigration, x as integrationInstancesCapability, y as heatingValveCapability, z as _enum } from "../bosch-instances-CdvcBYye.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "Bosch smart-home device-provider addons for CamStack — SHC, Home Connect, and HomeCom cloud integrations (scaffold)",
5
5
  "keywords": [
6
6
  "camstack",