camstack 1.2.213 → 1.2.214

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.
@@ -6783,17 +6783,17 @@ function enumerateSchemaFields(schema, prefix = "") {
6783
6783
  const out = [];
6784
6784
  const shape = root.shape;
6785
6785
  for (const [key, field] of Object.entries(shape)) {
6786
- const path2 = prefix ? `${prefix}.${key}` : key;
6786
+ const path = prefix ? `${prefix}.${key}` : key;
6787
6787
  const inner = unwrapSchema(field);
6788
6788
  if (inner instanceof z4.ZodObject) {
6789
- out.push(...enumerateSchemaFields(inner, path2));
6789
+ out.push(...enumerateSchemaFields(inner, path));
6790
6790
  continue;
6791
6791
  }
6792
6792
  const leaf = leafKind(field);
6793
6793
  if (leaf) {
6794
6794
  const quantity = quantityOfField(field);
6795
6795
  out.push({
6796
- path: path2,
6796
+ path,
6797
6797
  kind: leaf.kind,
6798
6798
  ...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
6799
6799
  ...quantity !== void 0 ? { quantity } : {}
@@ -7605,17 +7605,17 @@ function countWhenLeaves(node) {
7605
7605
  for (const child of node.children) total += countWhenLeaves(child);
7606
7606
  return total;
7607
7607
  }
7608
- function forEachLeaf(node, visit, path2 = "when") {
7608
+ function forEachLeaf(node, visit, path = "when") {
7609
7609
  if (isLeaf(node)) {
7610
- visit(node, path2);
7610
+ visit(node, path);
7611
7611
  return;
7612
7612
  }
7613
7613
  if (node.kind === "not") {
7614
- forEachLeaf(node.child, visit, `${path2}.0`);
7614
+ forEachLeaf(node.child, visit, `${path}.0`);
7615
7615
  return;
7616
7616
  }
7617
7617
  node.children.forEach((child, i) => {
7618
- forEachLeaf(child, visit, `${path2}.${i}`);
7618
+ forEachLeaf(child, visit, `${path}.${i}`);
7619
7619
  });
7620
7620
  }
7621
7621
  function hasEventLeaves(node) {
@@ -7643,22 +7643,22 @@ var LEAF_WORDS = {
7643
7643
  function say(kind) {
7644
7644
  return LEAF_WORDS[kind] ?? kind;
7645
7645
  }
7646
- function eventNeed(node, path2, negated) {
7646
+ function eventNeed(node, path, negated) {
7647
7647
  if (isLeaf(node)) {
7648
7648
  if (!isEventLeaf(node)) return { paths: [] };
7649
- if (negated) return { error: `"not" is applied to ${say(node.kind)} (${path2}) \u2014 an event is an instant, so "it did not happen" is true at every other moment and can never start a run. Negate a state ("is") instead, or remove the "not"` };
7649
+ if (negated) return { error: `"not" is applied to ${say(node.kind)} (${path}) \u2014 an event is an instant, so "it did not happen" is true at every other moment and can never start a run. Negate a state ("is") instead, or remove the "not"` };
7650
7650
  return { paths: [{
7651
- path: path2,
7651
+ path,
7652
7652
  kind: node.kind
7653
7653
  }] };
7654
7654
  }
7655
- if (node.kind === "not") return eventNeed(node.child, `${path2}.0`, !negated);
7655
+ if (node.kind === "not") return eventNeed(node.child, `${path}.0`, !negated);
7656
7656
  const conjunctive = node.kind === "all" !== negated;
7657
7657
  const parts = [];
7658
7658
  for (let i = 0; i < node.children.length; i += 1) {
7659
7659
  const child = node.children[i];
7660
7660
  if (child === void 0) continue;
7661
- const need = eventNeed(child, `${path2}.${i}`, negated);
7661
+ const need = eventNeed(child, `${path}.${i}`, negated);
7662
7662
  if ("error" in need) return need;
7663
7663
  parts.push(need);
7664
7664
  }
@@ -7957,11 +7957,11 @@ function stepMetaOf(action) {
7957
7957
  function j(value) {
7958
7958
  return JSON.stringify(value);
7959
7959
  }
7960
- function emitLeaf(trigger, path2) {
7961
- const at = j(path2);
7960
+ function emitLeaf(trigger, path) {
7961
+ const at = j(path);
7962
7962
  switch (trigger.kind) {
7963
7963
  case "schedule":
7964
- return ` // event leaf ${path2}: schedule \u2014 armed to the next minute boundary and
7964
+ return ` // event leaf ${path}: schedule \u2014 armed to the next minute boundary and
7965
7965
  // RE-ARMED from the wall clock every tick, so it cannot drift the way a
7966
7966
  // setInterval(24h) does. No cron library: a block cannot resolve one.
7967
7967
  {
@@ -7978,7 +7978,7 @@ function emitLeaf(trigger, path2) {
7978
7978
  disposers.push(() => { if (timer !== undefined) clearTimeout(timer) })
7979
7979
  }`;
7980
7980
  case "device-event":
7981
- return ` // event leaf ${path2}: device event
7981
+ return ` // event leaf ${path}: device event
7982
7982
  disposers.push(
7983
7983
  ctx.eventBus.subscribe(
7984
7984
  { category: ${j(trigger.category)}${trigger.deviceId === void 0 ? "" : `, deviceId: ${j(trigger.deviceId)}`} },
@@ -7995,7 +7995,7 @@ function emitLeaf(trigger, path2) {
7995
7995
  )`;
7996
7996
  case "device-becomes": {
7997
7997
  const becomes = trigger.becomes === void 0 ? "undefined" : j(trigger.becomes);
7998
- return ` // event leaf ${path2}: device state \u2014 the mirror is the authority, the
7998
+ return ` // event leaf ${path}: device state \u2014 the mirror is the authority, the
7999
7999
  // event only WAKES the evaluation, so a dropped event delays and never loses.
8000
8000
  {
8001
8001
  let previous: unknown = undefined
@@ -8037,11 +8037,11 @@ function emitLeaf(trigger, path2) {
8037
8037
  }`;
8038
8038
  }
8039
8039
  case "manual":
8040
- return ` // event leaf ${path2}: manual \u2014 fired by automation-control.trigger
8040
+ return ` // event leaf ${path}: manual \u2014 fired by automation-control.trigger
8041
8041
  // ("Run actions"), which runs the THEN half with an EMPTY vars bag. A manual
8042
8042
  // run bypasses only the cooldown wait, never the single/queued limits.`;
8043
8043
  case "interval":
8044
- return ` // event leaf ${path2}: interval \u2014 every ${trigger.minutes} min, first run jittered so
8044
+ return ` // event leaf ${path}: interval \u2014 every ${trigger.minutes} min, first run jittered so
8045
8045
  // blocks restarted together do not all fire on the same second.
8046
8046
  {
8047
8047
  let interval: ReturnType<typeof setInterval> | undefined
@@ -8055,7 +8055,7 @@ function emitLeaf(trigger, path2) {
8055
8055
  })
8056
8056
  }`;
8057
8057
  case "detection":
8058
- return ` // event leaf ${path2}: detection \u2014 ONE fire per (device, track); a rejected event is
8058
+ return ` // event leaf ${path}: detection \u2014 ONE fire per (device, track); a rejected event is
8059
8059
  // traced with its reason and never fires.
8060
8060
  {
8061
8061
  const filter = new DetectionTriggerFilter(${j(trigger)})
@@ -8081,7 +8081,7 @@ function emitLeaf(trigger, path2) {
8081
8081
  )
8082
8082
  }`;
8083
8083
  case "stationary":
8084
- return ` // event leaf ${path2}: stationary \u2014 a parked object appeared / departed. Stateless:
8084
+ return ` // event leaf ${path}: stationary \u2014 a parked object appeared / departed. Stateless:
8085
8085
  // the registry emits one event per transition. A dropped event only misses a fire;
8086
8086
  // the stationary-present STATE leaf reads the registry, not this event.
8087
8087
  {
@@ -8152,10 +8152,10 @@ function generateAutomationBlock(input) {
8152
8152
  const runMode = recipe.runMode ?? { mode: "single" };
8153
8153
  const stepMeta = recipe.actions.map(stepMetaOf);
8154
8154
  const eventLeaves = [];
8155
- forEachLeaf(recipe.when, (leaf, path2) => {
8155
+ forEachLeaf(recipe.when, (leaf, path) => {
8156
8156
  if (isEventLeaf(leaf)) eventLeaves.push({
8157
8157
  leaf,
8158
- path: path2
8158
+ path
8159
8159
  });
8160
8160
  });
8161
8161
  return `import type {
@@ -8689,7 +8689,7 @@ ${recipe.actions.map((a, i) => emitAction(a, i)).join("\n")}
8689
8689
  disposers.push(() => clearInterval(mirrorReconcile))
8690
8690
  }
8691
8691
 
8692
- ${eventLeaves.map(({ leaf, path: path2 }) => emitLeaf(leaf, path2)).join("\n\n")}
8692
+ ${eventLeaves.map(({ leaf, path }) => emitLeaf(leaf, path)).join("\n\n")}
8693
8693
 
8694
8694
  return () => {
8695
8695
  stopped = true
@@ -9007,7 +9007,7 @@ function checkPickedUnit(unit, target) {
9007
9007
  target
9008
9008
  });
9009
9009
  }
9010
- function normalizeLeaf(leaf, resolver, path2) {
9010
+ function normalizeLeaf(leaf, resolver, path) {
9011
9011
  const quantity = resolver.fieldQuantity(leaf.cap, leaf.fieldPath);
9012
9012
  const base = quantity === void 0 ? void 0 : baseUnitForQuantity(quantity);
9013
9013
  const wantsUnit = isQuantityValue(leaf.value) || leaf.unit !== void 0;
@@ -9051,7 +9051,7 @@ function normalizeLeaf(leaf, resolver, path2) {
9051
9051
  return {
9052
9052
  leaf,
9053
9053
  warnings: [bareWarning({
9054
- path: path2,
9054
+ path,
9055
9055
  deviceId: leaf.deviceId,
9056
9056
  field: leaf.fieldPath,
9057
9057
  value: leaf.value,
@@ -9059,17 +9059,17 @@ function normalizeLeaf(leaf, resolver, path2) {
9059
9059
  })]
9060
9060
  };
9061
9061
  }
9062
- function normalizeConditionUnits(condition, resolver, path2 = "when") {
9062
+ function normalizeConditionUnits(condition, resolver, path = "when") {
9063
9063
  switch (condition.kind) {
9064
9064
  case "device-is": {
9065
- const { leaf, warnings } = normalizeLeaf(condition, resolver, path2);
9065
+ const { leaf, warnings } = normalizeLeaf(condition, resolver, path);
9066
9066
  return {
9067
9067
  value: leaf,
9068
9068
  warnings
9069
9069
  };
9070
9070
  }
9071
9071
  case "not": {
9072
- const inner = normalizeConditionUnits(condition.child, resolver, `${path2}.child`);
9072
+ const inner = normalizeConditionUnits(condition.child, resolver, `${path}.child`);
9073
9073
  return {
9074
9074
  value: {
9075
9075
  ...condition,
@@ -9080,7 +9080,7 @@ function normalizeConditionUnits(condition, resolver, path2 = "when") {
9080
9080
  }
9081
9081
  case "all":
9082
9082
  case "any": {
9083
- const children = condition.children.map((child, i) => normalizeConditionUnits(child, resolver, `${path2}.children.${i}`));
9083
+ const children = condition.children.map((child, i) => normalizeConditionUnits(child, resolver, `${path}.children.${i}`));
9084
9084
  return {
9085
9085
  value: {
9086
9086
  ...condition,
@@ -9096,7 +9096,7 @@ function normalizeConditionUnits(condition, resolver, path2 = "when") {
9096
9096
  };
9097
9097
  }
9098
9098
  }
9099
- function normalizeCapStep(step, resolver, path2) {
9099
+ function normalizeCapStep(step, resolver, path) {
9100
9100
  const deviceId = typeof step.deviceId === "number" ? step.deviceId : void 0;
9101
9101
  const args = { ...step.args };
9102
9102
  const argUnits = { ...step.argUnits };
@@ -9117,7 +9117,7 @@ function normalizeCapStep(step, resolver, path2) {
9117
9117
  });
9118
9118
  argUnits[arg] = pickedUnit(given.unit);
9119
9119
  } else if (typeof given === "number" && argUnits[arg] === void 0 && hasUnitChoice(meta.quantity)) warnings.push(bareWarning({
9120
- path: `${path2}.args.${arg}`,
9120
+ path: `${path}.args.${arg}`,
9121
9121
  ...deviceId === void 0 ? {} : { deviceId },
9122
9122
  field: arg,
9123
9123
  value: given,
@@ -9134,7 +9134,7 @@ function normalizeCapStep(step, resolver, path2) {
9134
9134
  warnings
9135
9135
  };
9136
9136
  }
9137
- function normalizeActionUnits(actions, resolver, path2 = "steps") {
9137
+ function normalizeActionUnits(actions, resolver, path = "steps") {
9138
9138
  const out = [];
9139
9139
  const warnings = [];
9140
9140
  actions.forEach((action, i) => {
@@ -9142,7 +9142,7 @@ function normalizeActionUnits(actions, resolver, path2 = "steps") {
9142
9142
  out.push(action);
9143
9143
  return;
9144
9144
  }
9145
- const normalized = normalizeCapStep(action, resolver, `${path2}.${i}`);
9145
+ const normalized = normalizeCapStep(action, resolver, `${path}.${i}`);
9146
9146
  out.push(normalized.value);
9147
9147
  warnings.push(...normalized.warnings);
9148
9148
  });
@@ -50400,6 +50400,12 @@ var TerminalInstanceInfoSchema = z4.object({
50400
50400
  instanceId: z4.string(),
50401
50401
  cameraStableId: z4.string(),
50402
50402
  nodeId: z4.string(),
50403
+ /**
50404
+ * False while the node the instance is bound to is not in the cluster
50405
+ * topology (forgotten, renamed, re-paired). The instance is kept; the
50406
+ * operator re-points it with `updateInstance({ targetNodeId })`.
50407
+ */
50408
+ nodeInCluster: z4.boolean(),
50403
50409
  profileId: z4.string(),
50404
50410
  profileLabel: z4.string(),
50405
50411
  name: z4.string(),
@@ -50459,6 +50465,8 @@ var terminalSessionCapability = {
50459
50465
  }),
50460
50466
  updateInstance: method(z4.object({
50461
50467
  instanceId: z4.string().min(1),
50468
+ /** Re-point the instance to another node; the profile must exist there. */
50469
+ targetNodeId: z4.string().min(1).max(256).optional(),
50462
50470
  name: z4.string().trim().min(1).max(160).optional(),
50463
50471
  executable: z4.string().max(1024).optional(),
50464
50472
  args: z4.array(z4.string().max(2048)).max(64).optional(),
@@ -53618,7 +53626,14 @@ var RemoteLayoutGroupSchema = z4.object({
53618
53626
  col: z4.number().int().min(0).max(24),
53619
53627
  rowSpan: z4.number().int().min(1).max(24),
53620
53628
  colSpan: z4.number().int().min(1).max(24),
53621
- title: z4.string().min(1).max(40).optional()
53629
+ title: z4.string().min(1).max(40).optional(),
53630
+ /**
53631
+ * How much room a divider takes: `compact` (the default) is a slim rule,
53632
+ * `normal` a full grid row. Other frames ignore it.
53633
+ */
53634
+ size: z4.enum(["compact", "normal"]).optional(),
53635
+ /** Keep the title in the layout but draw only the rule. */
53636
+ hideTitle: z4.boolean().optional()
53622
53637
  });
53623
53638
  function rectsOverlap(a, b) {
53624
53639
  return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
@@ -53629,11 +53644,11 @@ function rectContains(outer, inner) {
53629
53644
  function refineGroups(grid, ctx) {
53630
53645
  const groups = grid.groups ?? [];
53631
53646
  const byId = /* @__PURE__ */ new Map();
53632
- const fail = (message, path2) => {
53647
+ const fail = (message, path) => {
53633
53648
  ctx.addIssue({
53634
53649
  code: "custom",
53635
53650
  message,
53636
- path: path2
53651
+ path
53637
53652
  });
53638
53653
  };
53639
53654
  groups.forEach((group, index) => {
@@ -86041,74 +86056,6 @@ var SmartIrDocumentSchema = z4.object({
86041
86056
  commands: z4.record(z4.string(), z4.unknown())
86042
86057
  });
86043
86058
 
86044
- // src/discovery/hub-finder.ts
86045
- import * as fs from "fs";
86046
- import * as path from "path";
86047
- import {
86048
- defaultSessionDir,
86049
- isSessionFile,
86050
- resolveHubUrl,
86051
- validateHubAddress
86052
- } from "@camstack/develop";
86053
- import {
86054
- discoverHubs
86055
- } from "@camstack/develop/hub-discovery";
86056
- function validateManualHubUrl(value) {
86057
- return validateHubAddress(value);
86058
- }
86059
- function knownHubCandidates(deps) {
86060
- const dir = deps.sessionDir ?? defaultSessionDir();
86061
- const env = deps.env ?? process.env;
86062
- const out = [];
86063
- let files = [];
86064
- try {
86065
- files = fs.readdirSync(dir);
86066
- } catch {
86067
- files = [];
86068
- }
86069
- for (const file of files) {
86070
- if (file.endsWith(".json")) {
86071
- try {
86072
- const parsed = JSON.parse(fs.readFileSync(path.join(dir, file), "utf8"));
86073
- if (isSessionFile(parsed)) {
86074
- out.push({
86075
- url: parsed.server,
86076
- source: `saved session ${parsed.username}`
86077
- });
86078
- }
86079
- } catch {
86080
- }
86081
- }
86082
- }
86083
- const fromEnv = resolveHubUrl({ env, session: null });
86084
- if (fromEnv) {
86085
- const name = (env["CAMSTACK_HUB_URL"] ?? "").trim() === "" ? "CAMSTACK_SERVER" : "CAMSTACK_HUB_URL";
86086
- out.push({ url: fromEnv.url, source: name });
86087
- }
86088
- if (deps.workspaceRoot !== void 0) {
86089
- const scratch = resolveHubUrl({
86090
- env: {},
86091
- workspaceRoot: deps.workspaceRoot,
86092
- session: null
86093
- });
86094
- if (scratch) out.push({ url: scratch.url, source: "scratchpad/.hub-url" });
86095
- }
86096
- return out;
86097
- }
86098
- async function findHubs(deps = {}) {
86099
- return discoverHubs({
86100
- known: knownHubCandidates(deps),
86101
- ...deps.browse ? { browse: deps.browse } : {},
86102
- ...deps.probe ? { probe: deps.probe } : {},
86103
- ...deps.mdnsTimeoutMs !== void 0 ? { mdnsTimeoutMs: deps.mdnsTimeoutMs } : {},
86104
- ...deps.onProblem ? { onProblem: deps.onProblem } : {}
86105
- });
86106
- }
86107
- function describeHub(hub) {
86108
- const identity = [hub.nodeId, hub.version === null ? null : `v${hub.version}`].filter((p) => p !== null).join(" ");
86109
- return `${hub.url}${identity === "" ? "" : ` ${identity}`} (${hub.sources.join(", ")})`;
86110
- }
86111
-
86112
86059
  export {
86113
86060
  isReadingQuantity,
86114
86061
  FFMPEG_PATH_ENV,
@@ -86128,8 +86075,5 @@ export {
86128
86075
  DETECTOR_PERF,
86129
86076
  ALL_CAPABILITY_DEFINITIONS,
86130
86077
  classifyInferenceHardware,
86131
- acceleratorKindOf,
86132
- validateManualHubUrl,
86133
- findHubs,
86134
- describeHub
86078
+ acceleratorKindOf
86135
86079
  };
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/http/hub-http.ts
4
+ import * as http from "http";
5
+ import * as https from "https";
6
+ import { Buffer } from "buffer";
7
+ import { createHubAgent, explainTlsFailure, resolveHubTls } from "@camstack/develop";
8
+ var DEFAULT_TIMEOUT_MS = 3e4;
9
+ function hubRequest(url, opts = {}) {
10
+ const parsed = new URL(url);
11
+ const isHttps = parsed.protocol === "https:";
12
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
13
+ return new Promise((resolve, reject) => {
14
+ const requestOptions = {
15
+ protocol: parsed.protocol,
16
+ hostname: parsed.hostname,
17
+ port: parsed.port || (isHttps ? 443 : 80),
18
+ path: `${parsed.pathname}${parsed.search}`,
19
+ method: opts.method ?? "GET",
20
+ headers: { ...opts.headers },
21
+ timeout: timeoutMs,
22
+ ...isHttps ? { agent: createHubAgent(resolveHubTls(parsed.origin, opts.env, opts.sessionDir)) } : {}
23
+ };
24
+ const onResponse = (res) => {
25
+ const chunks = [];
26
+ res.on("data", (chunk) => chunks.push(chunk));
27
+ res.on("end", () => {
28
+ const buffer = Buffer.concat(chunks);
29
+ const status = res.statusCode ?? 0;
30
+ resolve({
31
+ ok: status >= 200 && status < 300,
32
+ status,
33
+ statusText: res.statusMessage ?? "",
34
+ headers: res.headers,
35
+ text: () => Promise.resolve(buffer.toString("utf8")),
36
+ json: () => Promise.resolve().then(() => JSON.parse(buffer.toString("utf8")))
37
+ });
38
+ });
39
+ res.on("error", reject);
40
+ };
41
+ const req = isHttps ? https.request(requestOptions, onResponse) : http.request(requestOptions, onResponse);
42
+ req.on("error", (err) => {
43
+ const explained = isHttps ? explainTlsFailure(`${parsed.protocol}//${parsed.host}`, err) : null;
44
+ reject(explained === null ? err : new Error(explained, { cause: err }));
45
+ });
46
+ req.on("timeout", () => {
47
+ req.destroy(new Error(`Hub request to ${parsed.host} timed out after ${timeoutMs}ms`));
48
+ });
49
+ if (opts.body !== void 0) req.write(opts.body);
50
+ req.end();
51
+ });
52
+ }
53
+
54
+ export {
55
+ hubRequest
56
+ };
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/discovery/hub-finder.ts
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import {
7
+ defaultSessionDir,
8
+ isSessionFile,
9
+ resolveHubUrl,
10
+ validateHubAddress
11
+ } from "@camstack/develop";
12
+ import {
13
+ discoverHubs
14
+ } from "@camstack/develop/hub-discovery";
15
+ function validateManualHubUrl(value) {
16
+ return validateHubAddress(value);
17
+ }
18
+ function knownHubCandidates(deps) {
19
+ const dir = deps.sessionDir ?? defaultSessionDir();
20
+ const env = deps.env ?? process.env;
21
+ const out = [];
22
+ let files = [];
23
+ try {
24
+ files = fs.readdirSync(dir);
25
+ } catch {
26
+ files = [];
27
+ }
28
+ for (const file of files) {
29
+ if (file.endsWith(".json")) {
30
+ try {
31
+ const parsed = JSON.parse(fs.readFileSync(path.join(dir, file), "utf8"));
32
+ if (isSessionFile(parsed)) {
33
+ out.push({
34
+ url: parsed.server,
35
+ source: `saved session ${parsed.username}`
36
+ });
37
+ }
38
+ } catch {
39
+ }
40
+ }
41
+ }
42
+ const fromEnv = resolveHubUrl({ env, session: null });
43
+ if (fromEnv) {
44
+ const name = (env["CAMSTACK_HUB_URL"] ?? "").trim() === "" ? "CAMSTACK_SERVER" : "CAMSTACK_HUB_URL";
45
+ out.push({ url: fromEnv.url, source: name });
46
+ }
47
+ if (deps.workspaceRoot !== void 0) {
48
+ const scratch = resolveHubUrl({
49
+ env: {},
50
+ workspaceRoot: deps.workspaceRoot,
51
+ session: null
52
+ });
53
+ if (scratch) out.push({ url: scratch.url, source: "scratchpad/.hub-url" });
54
+ }
55
+ return out;
56
+ }
57
+ async function findHubs(deps = {}) {
58
+ return discoverHubs({
59
+ known: knownHubCandidates(deps),
60
+ ...deps.browse ? { browse: deps.browse } : {},
61
+ ...deps.probe ? { probe: deps.probe } : {},
62
+ ...deps.mdnsTimeoutMs !== void 0 ? { mdnsTimeoutMs: deps.mdnsTimeoutMs } : {},
63
+ ...deps.onProblem ? { onProblem: deps.onProblem } : {}
64
+ });
65
+ }
66
+ function describeHub(hub) {
67
+ const identity = [hub.nodeId, hub.version === null ? null : `v${hub.version}`].filter((p) => p !== null).join(" ");
68
+ return `${hub.url}${identity === "" ? "" : ` ${identity}`} (${hub.sources.join(", ")})`;
69
+ }
70
+
71
+ export {
72
+ validateManualHubUrl,
73
+ findHubs,
74
+ describeHub
75
+ };